diff options
Diffstat (limited to 'engines/tony')
47 files changed, 25972 insertions, 0 deletions
diff --git a/engines/tony/custom.cpp b/engines/tony/custom.cpp new file mode 100644 index 0000000000..23c655e35a --- /dev/null +++ b/engines/tony/custom.cpp @@ -0,0 +1,2530 @@ +/* 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 Tony Tough source code + * + * Copyright (c) 1997-2003 Nayma Software + */ + +#include "common/system.h" +#include "common/savefile.h" +#include "tony/mpal/mpal.h" +#include "tony/mpal/memory.h" +#include "tony/custom.h" +#include "tony/font.h" +#include "tony/game.h" +#include "tony/gfxcore.h" +#include "tony/tony.h" +#include "tony/tonychar.h" +#include "tony/utils.h" + +namespace Tony { + +static const char *const kAmbianceFile[] = { + "None", + "1.ADP", // Grilli.WAV + "2.ADP", // Grilli-Ovattati.WAV + "3.ADP", // Grilli-Vento.WAV + "3.ADP", // Grilli-Vento1.WAV + "5.ADP", // Vento1.WAV + "4.ADP", // Mare1.WAV + "6.ADP" // Mare1.WAV half volume +}; + +static const MusicFileEntry kMusicFiles[] = { + {"00.ADP", 0}, {"01.ADP", 0}, {"02.ADP", 0}, {"03.ADP", 0}, + {"04.ADP", 0}, {"05.ADP", 0}, {"06.ADP", 0}, {"07.ADP", 0}, + {"08.ADP", 2450}, {"09.ADP", 0}, {"10.ADP", 0}, {"11.ADP", 0}, + {"12.ADP", 0}, {"13.ADP", 0}, {"14.ADP", 0}, {"15.ADP", 0}, + {"16.ADP", 0}, {"17.ADP", 0}, {"18.ADP", 0}, {"19.ADP", 0}, + {"20.ADP", 0}, {"21.ADP", 0}, {"22.ADP", 0}, {"23.ADP", 0}, + {"24.ADP", 0}, {"25.ADP", 0}, {"26.ADP", 0}, {"27.ADP", 0}, + {"28.ADP", 1670}, {"29.ADP", 0}, {"30.ADP", 0}, {"31.ADP", 0}, + {"32.ADP", 2900}, {"33.ADP", 0}, {"34.ADP", 0}, {"35.ADP", 0}, + {"36.ADP", 0}, {"37.ADP", 0}, {"38.ADP", 0}, {"39.ADP", 0}, + {"40.ADP", 0}, {"41.ADP", 1920}, {"42.ADP", 1560}, {"43.ADP", 1920}, + {"44.ADP", 1920}, {"45.ADP", 1920}, {"46.ADP", 1920}, {"47.ADP", 1920}, + {"48.ADP", 1920}, {"49.ADP", 1920}, {"50.ADP", 1920}, {"51.ADP", 1920}, + {"52.ADP", 1920}, {"53.ADP", 0}, {"54.ADP", 0}, {"55.ADP", 0}, + {"56.ADP", 0}, {"57.ADP", 0}, {"58.ADP", 0}, {"59.ADP", 0} +}; + + +static const char *const kJingleFileNames[] = { + "S00.ADP", "S01.ADP", "S02.ADP", "S03.ADP", "S04.ADP", + "S05.ADP", "S06.ADP", "S07.ADP", "S08.ADP", "S09.ADP", + "S10.ADP", "S11.ADP", "S12.ADP", "S13.ADP", "S14.ADP", + "S15.ADP", "S16.ADP", "S17.ADP", "S18.ADP" +}; + +void reapplyChangedHotspot() { + for (int i = 0; i < GLOBALS._curChangedHotspot; i++) + GLOBALS._loc->getItemFromCode(GLOBALS._changedHotspot[i]._dwCode)->changeHotspot(RMPoint(GLOBALS._changedHotspot[i]._nX, GLOBALS._changedHotspot[i]._nY)); +} + +void saveChangedHotspot(Common::OutSaveFile *f) { + f->writeByte(GLOBALS._curChangedHotspot); + if (GLOBALS._curChangedHotspot > 0) { + for (int i = 0; i < GLOBALS._curChangedHotspot; ++i) + GLOBALS._changedHotspot[i].save(f); + } +} + +void loadChangedHotspot(Common::InSaveFile *f) { + GLOBALS._curChangedHotspot = f->readByte(); + + if (GLOBALS._curChangedHotspot > 0) { + for (int i = 0; i < GLOBALS._curChangedHotspot; ++i) + GLOBALS._changedHotspot[i].load(f); + } +} + +/** + * Classes required for custom functions + * + * Tony (To Move him) -> You can do MPAL through the animation? I really think so + * + * SendMessage -> I'd say just theEngine.SendMessage() + * ChangeLocation -> theEngine.ChangeLocation() + * AddInventory -> theEngine.AddInventory() +*/ + +void mCharResetCodes() { + for (int i = 0; i < 10; i++) + GLOBALS._mCharacter[i]._item = GLOBALS._loc->getItemFromCode(GLOBALS._mCharacter[i]._code); + for (int i = 0; i < 10; i++) + GLOBALS._character[i]._item = GLOBALS._loc->getItemFromCode(GLOBALS._character[i]._code); +} + +void charsSaveAll(Common::OutSaveFile *f) { + for (int i = 0; i < 10; i++) { + f->writeByte(GLOBALS._isMChar[i]); + if (GLOBALS._isMChar[i]) { + GLOBALS._mCharacter[i].save(f); + } else { + GLOBALS._character[i].save(f); + } + } +} + +void charsLoadAll(Common::InSaveFile *f) { + for (int i = 0; i < 10; i++) { + GLOBALS._isMChar[i] = f->readByte(); + if (GLOBALS._isMChar[i]) + GLOBALS._mCharacter[i].load(f); + else + GLOBALS._character[i].load(f); + } +} + +void faceToMe(CORO_PARAM, uint32, uint32, uint32, uint32) { + GLOBALS._tony->setPattern(GLOBALS._tony->PAT_STANDDOWN); +} + +void backToMe(CORO_PARAM, uint32, uint32, uint32, uint32) { + GLOBALS._tony->setPattern(GLOBALS._tony->PAT_STANDUP); +} + +void leftToMe(CORO_PARAM, uint32, uint32, uint32, uint32) { + GLOBALS._tony->setPattern(GLOBALS._tony->PAT_STANDLEFT); +} + +void rightToMe(CORO_PARAM, uint32, uint32, uint32, uint32) { + GLOBALS._tony->setPattern(GLOBALS._tony->PAT_STANDRIGHT); +} + + +void tonySetPerorate(CORO_PARAM, uint32 bStatus, uint32, uint32, uint32) { + g_vm->getEngine()->setPerorate(bStatus); +} + +void mySleep(CORO_PARAM, uint32 dwTime, uint32, uint32, uint32) { + CORO_BEGIN_CONTEXT; + int i; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + if (!GLOBALS._bSkipIdle) + CORO_INVOKE_1(CoroScheduler.sleep, dwTime); + + CORO_END_CODE; +} + +void setAlwaysDisplay(CORO_PARAM, uint32 val, uint32, uint32, uint32) { + GLOBALS._bAlwaysDisplay = (val != 0); +} + + +void setPointer(CORO_PARAM, uint32 dwPointer, uint32, uint32, uint32) { + switch (dwPointer) { + case 1: + GLOBALS._pointer->setSpecialPointer(GLOBALS._pointer->PTR_ARROWUP); + break; + case 2: + GLOBALS._pointer->setSpecialPointer(GLOBALS._pointer->PTR_ARROWDOWN); + break; + case 3: + GLOBALS._pointer->setSpecialPointer(GLOBALS._pointer->PTR_ARROWLEFT); + break; + case 4: + GLOBALS._pointer->setSpecialPointer(GLOBALS._pointer->PTR_ARROWRIGHT); + break; + case 5: + GLOBALS._pointer->setSpecialPointer(GLOBALS._pointer->PTR_ARROWMAP); + break; + + default: + GLOBALS._pointer->setSpecialPointer(GLOBALS._pointer->PTR_NONE); + break; + } +} + +VoiceHeader *searchVoiceHeader(uint32 codehi, uint32 codelo) { + int code = (codehi << 16) | codelo; + + if (g_vm->_voices.size() == 0) + return NULL; + + for (uint i = 0; i < g_vm->_voices.size(); i++) { + if (g_vm->_voices[i]._code == code) + return &g_vm->_voices[i]; + } + + return NULL; +} + + +void sendTonyMessage(CORO_PARAM, uint32 dwMessage, uint32 nX, uint32 nY, uint32) { + CORO_BEGIN_CONTEXT; + RMMessage msg; + int i; + int curOffset; + VoiceHeader *curVoc; + FPSfx *voice; + RMTextDialog text; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + _ctx->curOffset = 0; + + if (GLOBALS._bSkipIdle) + return; + + _ctx->msg.load(dwMessage); + if (!_ctx->msg.isValid()) + return; + + _ctx->curVoc = searchVoiceHeader(0, dwMessage); + _ctx->voice = NULL; + if (_ctx->curVoc) { + // Is positioned within the database of entries beginning at the first + _ctx->curOffset = _ctx->curVoc->_offset; + + // First time allocation + g_vm->_vdbFP.seek(_ctx->curOffset); + g_vm->_theSound.createSfx(&_ctx->voice); + + _ctx->voice->loadVoiceFromVDB(g_vm->_vdbFP); + _ctx->curOffset = g_vm->_vdbFP.pos(); + + _ctx->voice->setLoop(false); + } + + if (GLOBALS._nTonyNextTalkType != GLOBALS._tony->TALK_NORMAL) { + CORO_INVOKE_1(GLOBALS._tony->startTalk, GLOBALS._nTonyNextTalkType); + + if (!GLOBALS._bStaticTalk) + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_NORMAL; + } else { + if (_ctx->msg.numPeriods() > 1) + CORO_INVOKE_1(GLOBALS._tony->startTalk, GLOBALS._tony->TALK_HIPS); + else + CORO_INVOKE_1(GLOBALS._tony->startTalk, GLOBALS._tony->TALK_NORMAL); + } + + if (GLOBALS._curBackText) + CORO_INVOKE_0(GLOBALS._curBackText->hide); + + GLOBALS._bTonyIsSpeaking = true; + + for (_ctx->i = 0; _ctx->i < _ctx->msg.numPeriods() && !GLOBALS._bSkipIdle; _ctx->i++) { + _ctx->text.setInput(GLOBALS._input); + + // Alignment + _ctx->text.setAlignType(RMText::HCENTER, RMText::VBOTTOM); + + // Color + _ctx->text.setColor(0, 255, 0); + + // Writes the text + _ctx->text.writeText(_ctx->msg[_ctx->i], 0); + + // Set the position + if (nX == 0 && nY == 0) + _ctx->text.setPosition(GLOBALS._tony->position() - RMPoint(0, 130) - GLOBALS._loc->scrollPosition()); + else + _ctx->text.setPosition(RMPoint(nX, nY) - GLOBALS._loc->scrollPosition()); + + // Handling for always display + if (GLOBALS._bAlwaysDisplay) { + _ctx->text.setAlwaysDisplay(); + _ctx->text.forceTime(); + } + + // Record the text + g_vm->getEngine()->linkGraphicTask(&_ctx->text); + + if (_ctx->curVoc) { + if (_ctx->i == 0) { + _ctx->voice->play(); + _ctx->text.setCustomSkipHandle2(_ctx->voice->_hEndOfBuffer); + } else { + g_vm->_vdbFP.seek(_ctx->curOffset); + g_vm->_theSound.createSfx(&_ctx->voice); + _ctx->voice->loadVoiceFromVDB(g_vm->_vdbFP); + + _ctx->curOffset = g_vm->_vdbFP.pos(); + _ctx->voice->setLoop(false); + _ctx->voice->play(); + _ctx->text.setCustomSkipHandle2(_ctx->voice->_hEndOfBuffer); + } + } + + // Wait for the end of the display + _ctx->text.setCustomSkipHandle(GLOBALS._hSkipIdle); + CORO_INVOKE_0(_ctx->text.waitForEndDisplay); + + if (_ctx->curVoc) { + _ctx->voice->stop(); + _ctx->voice->release(); + _ctx->voice = NULL; + } + } + + GLOBALS._bTonyIsSpeaking = false; + if (GLOBALS._curBackText) + GLOBALS._curBackText->show(); + + CORO_INVOKE_0(GLOBALS._tony->endTalk); + + CORO_END_CODE; +} + +void changeBoxStatus(CORO_PARAM, uint32 nLoc, uint32 nBox, uint32 nStatus, uint32) { + GLOBALS._boxes->changeBoxStatus(nLoc, nBox, nStatus); +} + + +void custLoadLocation(CORO_PARAM, uint32 nLoc, uint32 tX, uint32 tY, uint32 bUseStartPos) { + CORO_BEGIN_CONTEXT; + uint32 h; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + GLOBALS._curChangedHotspot = 0; + if (bUseStartPos != 0) + g_vm->getEngine()->loadLocation(nLoc, RMPoint(tX, tY), GLOBALS._startLocPos[nLoc]); + else + g_vm->getEngine()->loadLocation(nLoc, RMPoint(tX, tY), RMPoint(-1, -1)); + + _ctx->h = mpalQueryDoAction(0, nLoc, 0); + + // On Enter? + if (_ctx->h != CORO_INVALID_PID_VALUE) + CORO_INVOKE_2(CoroScheduler.waitForSingleObject, _ctx->h, CORO_INFINITE); + + CORO_END_CODE; +} + + +void sendFullscreenMsgStart(CORO_PARAM, uint32 nMsg, uint32 nFont, uint32, uint32) { + CORO_BEGIN_CONTEXT; + RMMessage *msg; + RMGfxClearTask clear; + int i; + RMTextDialog text; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + _ctx->msg = new RMMessage(nMsg); + + GLOBALS._fullScreenMessageLoc = GLOBALS._loc->TEMPGetNumLoc(); + GLOBALS._fullScreenMessagePt = GLOBALS._tony->position(); + + if (GLOBALS._bSkipIdle) + return; + + CORO_INVOKE_2(g_vm->getEngine()->unloadLocation, false, NULL); + GLOBALS._tony->hide(); + + for (_ctx->i = 0; _ctx->i < _ctx->msg->numPeriods() && !GLOBALS._bSkipIdle; _ctx->i++) { + _ctx->text.setInput(GLOBALS._input); + + // Alignment + _ctx->text.setAlignType(RMText::HCENTER, RMText::VCENTER); + + // Forces the text to disappear in time + _ctx->text.forceTime(); + + // Color + _ctx->text.setColor(255, 255, 255); + + // Write the text + if (nFont == 0) + _ctx->text.writeText((*_ctx->msg)[_ctx->i], 1); + else if (nFont == 1) + _ctx->text.writeText((*_ctx->msg)[_ctx->i], 0); + + // Set the position + _ctx->text.setPosition(RMPoint(320, 240)); + + _ctx->text.setAlwaysDisplay(); + _ctx->text.forceTime(); + + // Record the text + g_vm->getEngine()->linkGraphicTask(&_ctx->clear); + g_vm->getEngine()->linkGraphicTask(&_ctx->text); + + // Wait for the end of display + _ctx->text.setCustomSkipHandle(GLOBALS._hSkipIdle); + CORO_INVOKE_0(_ctx->text.waitForEndDisplay); + } + + delete _ctx->msg; + + CORO_END_CODE; +} + +void clearScreen(CORO_PARAM, uint32, uint32, uint32, uint32) { + CORO_BEGIN_CONTEXT; + char buf[256]; + RMGfxClearTask clear; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + g_vm->getEngine()->linkGraphicTask(&_ctx->clear); + + CORO_INVOKE_2(CoroScheduler.waitForSingleObject, g_vm->_hEndOfFrame, CORO_INFINITE); + + // WORKAROUND: This fixes a bug in the original source where the linked clear task + // didn't have time to be drawn and removed from the draw list before the method + // ended, thus remaining in the draw list and causing a later crash + CORO_INVOKE_2(CoroScheduler.waitForSingleObject, g_vm->_hEndOfFrame, CORO_INFINITE); + + CORO_END_CODE; +} + +void sendFullscreenMsgEnd(CORO_PARAM, uint32 bNotEnableTony, uint32, uint32, uint32) { + g_vm->getEngine()->loadLocation(GLOBALS._fullScreenMessageLoc, RMPoint(GLOBALS._fullScreenMessagePt._x, GLOBALS._fullScreenMessagePt._y), RMPoint(-1, -1)); + if (!bNotEnableTony) + GLOBALS._tony->show(); + + mCharResetCodes(); + reapplyChangedHotspot(); +} + + +void sendFullscreenMessage(CORO_PARAM, uint32 nMsg, uint32 nFont, uint32, uint32) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + CORO_INVOKE_4(sendFullscreenMsgStart, nMsg, nFont, 0, 0); + CORO_INVOKE_4(sendFullscreenMsgEnd, 0, 0, 0, 0); + + CORO_END_CODE; +} + +void noBullsEye(CORO_PARAM, uint32, uint32, uint32, uint32) { + GLOBALS._bNoBullsEye = true; +} + +void closeLocation(CORO_PARAM, uint32, uint32, uint32, uint32) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + if (!GLOBALS._bNoBullsEye) { + g_vm->getEngine()->initWipe(1); + CORO_INVOKE_0(g_vm->getEngine()->waitWipeEnd); + } + + g_vm->stopMusic(4); + + // On exit, unload + CORO_INVOKE_2(g_vm->getEngine()->unloadLocation, true, NULL); + + CORO_END_CODE; +} + + +void changeLocation(CORO_PARAM, uint32 nLoc, uint32 tX, uint32 tY, uint32 bUseStartPos) { + CORO_BEGIN_CONTEXT; + uint32 h; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + if (!GLOBALS._bNoBullsEye) { + g_vm->getEngine()->initWipe(1); + CORO_INVOKE_0(g_vm->getEngine()->waitWipeEnd); + } + + if (GLOBALS._lastTappeto != GLOBALS._ambiance[nLoc]) { + g_vm->stopMusic(4); + } + + // On exit, unfreeze + CORO_INVOKE_2(g_vm->getEngine()->unloadLocation, true, NULL); + + GLOBALS._curChangedHotspot = 0; + if (bUseStartPos != 0) + g_vm->getEngine()->loadLocation(nLoc, RMPoint(tX, tY), GLOBALS._startLocPos[nLoc]); + else + g_vm->getEngine()->loadLocation(nLoc, RMPoint(tX, tY), RMPoint(-1, -1)); + + if (GLOBALS._lastTappeto != GLOBALS._ambiance[nLoc]) { + GLOBALS._lastTappeto = GLOBALS._ambiance[nLoc]; + if (GLOBALS._lastTappeto != 0) + g_vm->playMusic(4, kAmbianceFile[GLOBALS._lastTappeto], 0, true, 2000); + } + + if (!GLOBALS._bNoBullsEye) { + g_vm->getEngine()->initWipe(2); + } + + _ctx->h = mpalQueryDoAction(0, nLoc, 0); + + if (!GLOBALS._bNoBullsEye) { + CORO_INVOKE_0(g_vm->getEngine()->waitWipeEnd); + g_vm->getEngine()->closeWipe(); + } + + GLOBALS._bNoBullsEye = false; + + // On Enter? + if (_ctx->h != CORO_INVALID_PID_VALUE) + CORO_INVOKE_2(CoroScheduler.waitForSingleObject, _ctx->h, CORO_INFINITE); + + CORO_END_CODE; +} + +void setLocStartPosition(CORO_PARAM, uint32 nLoc, uint32 lX, uint32 lY, uint32) { + GLOBALS._startLocPos[nLoc].set(lX, lY); +} + +void saveTonyPosition(CORO_PARAM, uint32, uint32, uint32, uint32) { + GLOBALS._saveTonyPos = GLOBALS._tony->position(); + GLOBALS._saveTonyLoc = GLOBALS._loc->TEMPGetNumLoc(); +} + +void restoreTonyPosition(CORO_PARAM, uint32, uint32, uint32, uint32) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + CORO_INVOKE_4(changeLocation, GLOBALS._saveTonyLoc, GLOBALS._saveTonyPos._x, GLOBALS._saveTonyPos._y, 0); + + mCharResetCodes(); + + CORO_END_CODE; +} + +void disableInput(CORO_PARAM, uint32, uint32, uint32, uint32) { + g_vm->getEngine()->disableInput(); +} + +void enableInput(CORO_PARAM, uint32, uint32, uint32, uint32) { + g_vm->getEngine()->enableInput(); +} + +void stopTony(CORO_PARAM, uint32, uint32, uint32, uint32) { + GLOBALS._tony->stopNoAction(coroParam); +} + +void custEnableGUI(CORO_PARAM, uint32, uint32, uint32, uint32) { + GLOBALS.EnableGUI(); +} + +void custDisableGUI(CORO_PARAM, uint32, uint32, uint32, uint32) { + GLOBALS.DisableGUI(); +} + +void tonyGenericTake1(CORO_PARAM, uint32 nDirection) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + GLOBALS._tony->take(nDirection, 0); + + if (!GLOBALS._bSkipIdle) + CORO_INVOKE_0(GLOBALS._tony->waitForEndPattern); + + CORO_END_CODE; +} + +void tonyGenericTake2(CORO_PARAM, uint32 nDirection) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + GLOBALS._tony->take(nDirection, 1); + + if (!GLOBALS._bSkipIdle) + CORO_INVOKE_0(GLOBALS._tony->waitForEndPattern); + + GLOBALS._tony->take(nDirection, 2); + + CORO_END_CODE; +} + +void tonyGenericPut1(CORO_PARAM, uint32 nDirection) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + GLOBALS._tony->put(nDirection, 0); + + if (!GLOBALS._bSkipIdle) + CORO_INVOKE_0(GLOBALS._tony->waitForEndPattern); + + CORO_END_CODE; +} + +void tonyGenericPut2(CORO_PARAM, uint32 nDirection) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + GLOBALS._tony->put(nDirection, 1); + + if (!GLOBALS._bSkipIdle) + CORO_INVOKE_0(GLOBALS._tony->waitForEndPattern); + + GLOBALS._tony->put(nDirection, 2); + + CORO_END_CODE; +} + + +void tonyTakeUp1(CORO_PARAM, uint32, uint32, uint32, uint32) { + tonyGenericTake1(coroParam, 0); +} + + +void tonyTakeMid1(CORO_PARAM, uint32, uint32, uint32, uint32) { + tonyGenericTake1(coroParam, 1); +} + +void tonyTakeDown1(CORO_PARAM, uint32, uint32, uint32, uint32) { + tonyGenericTake1(coroParam, 2); +} + +void tonyTakeUp2(CORO_PARAM, uint32, uint32, uint32, uint32) { + tonyGenericTake2(coroParam, 0); +} + + +void tonyTakeMid2(CORO_PARAM, uint32, uint32, uint32, uint32) { + tonyGenericTake2(coroParam, 1); +} + +void tonyTakeDown2(CORO_PARAM, uint32, uint32, uint32, uint32) { + tonyGenericTake2(coroParam, 2); +} + +void tonyPutUp1(CORO_PARAM, uint32, uint32, uint32, uint32) { + tonyGenericPut1(coroParam, 0); +} + +void tonyPutMid1(CORO_PARAM, uint32, uint32, uint32, uint32) { + tonyGenericPut1(coroParam, 1); +} + +void tonyPutDown1(CORO_PARAM, uint32, uint32, uint32, uint32) { + tonyGenericPut1(coroParam, 2); +} + +void tonyPutUp2(CORO_PARAM, uint32, uint32, uint32, uint32) { + tonyGenericPut2(coroParam, 0); +} + +void tonyPutMid2(CORO_PARAM, uint32, uint32, uint32, uint32) { + tonyGenericPut2(coroParam, 1); +} + +void tonyPutDown2(CORO_PARAM, uint32, uint32, uint32, uint32) { + tonyGenericPut2(coroParam, 2); +} + + +void tonyOnTheFloor(CORO_PARAM, uint32 dwParte, uint32, uint32, uint32) { + if (dwParte == 0) + GLOBALS._tony->setPattern(GLOBALS._tony->PAT_ONTHEFLOORLEFT); + else + GLOBALS._tony->setPattern(GLOBALS._tony->PAT_ONTHEFLOORRIGHT); +} + +void tonyGetUp(CORO_PARAM, uint32 dwParte, uint32, uint32, uint32) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + if (dwParte == 0) + GLOBALS._tony->setPattern(GLOBALS._tony->PAT_GETUPLEFT); + else + GLOBALS._tony->setPattern(GLOBALS._tony->PAT_GETUPRIGHT); + + if (!GLOBALS._bSkipIdle) + CORO_INVOKE_0(GLOBALS._tony->waitForEndPattern); + + CORO_END_CODE; +} + +void tonyShepherdess(CORO_PARAM, uint32 bIsPast, uint32, uint32, uint32) { + GLOBALS._tony->setShepherdess(bIsPast); +} + +void tonyWhistle(CORO_PARAM, uint32, uint32, uint32, uint32) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + GLOBALS._tony->setPattern(GLOBALS._tony->PAT_WHISTLERIGHT); + if (!GLOBALS._bSkipIdle) + CORO_INVOKE_0(GLOBALS._tony->waitForEndPattern); + + GLOBALS._tony->setPattern(GLOBALS._tony->PAT_STANDRIGHT); + + CORO_END_CODE; +} + +void tonySetNumTexts(uint32 dwText) { + GLOBALS._dwTonyNumTexts = dwText; + GLOBALS._bTonyInTexts = false; +} + +void tonyLaugh(CORO_PARAM, uint32 dwText, uint32, uint32, uint32) { + tonySetNumTexts(dwText); + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_LAUGH; +} + +void tonyGiggle(CORO_PARAM, uint32 dwText, uint32, uint32, uint32) { + tonySetNumTexts(dwText); + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_LAUGH2; +} + +void tonyHips(CORO_PARAM, uint32 dwText, uint32, uint32, uint32) { + tonySetNumTexts(dwText); + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_HIPS; +} + +void tonySing(CORO_PARAM, uint32 dwText, uint32, uint32, uint32) { + tonySetNumTexts(dwText); + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_SING; +} + +void tonyIndicate(CORO_PARAM, uint32 dwText, uint32, uint32, uint32) { + tonySetNumTexts(dwText); + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_INDICATE; +} + +void tonyScaredWithHands(CORO_PARAM, uint32 dwText, uint32, uint32, uint32) { + tonySetNumTexts(dwText); + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_SCARED; +} + +void tonyScaredWithoutHands(CORO_PARAM, uint32 dwText, uint32, uint32, uint32) { + tonySetNumTexts(dwText); + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_SCARED2; +} + +void tonyWithHammer(CORO_PARAM, uint32 dwText, uint32, uint32, uint32) { + tonySetNumTexts(dwText); + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_WITHHAMMER; + GLOBALS._tony->setPattern(GLOBALS._tony->PAT_WITHHAMMER); +} + +void tonyWithGlasses(CORO_PARAM, uint32 dwText, uint32, uint32, uint32) { + tonySetNumTexts(dwText); + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_WITHGLASSES; + GLOBALS._tony->setPattern(GLOBALS._tony->PAT_WITHGLASSES); +} + +void tonyWithWorm(CORO_PARAM, uint32 dwText, uint32, uint32, uint32) { + tonySetNumTexts(dwText); + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_WITHWORM; + GLOBALS._tony->setPattern(GLOBALS._tony->PAT_WITHWORM); +} + +void tonyWithRope(CORO_PARAM, uint32 dwText, uint32, uint32, uint32) { + tonySetNumTexts(dwText); + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_WITHROPE; + GLOBALS._tony->setPattern(GLOBALS._tony->PAT_WITHROPE); +} + +void tonyWithSecretary(CORO_PARAM, uint32 dwText, uint32, uint32, uint32) { + tonySetNumTexts(dwText); + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_WITHSECRETARY; + GLOBALS._tony->setPattern(GLOBALS._tony->PAT_WITHSECRETARY); +} + +void tonyWithRabbitANIM(CORO_PARAM, uint32 dwText, uint32, uint32, uint32) { + tonySetNumTexts(dwText); + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_WITHRABBIT; +} + +void tonyWithRecipeANIM(CORO_PARAM, uint32 dwText, uint32, uint32, uint32) { + tonySetNumTexts(dwText); + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_WITHRECIPE; +} + +void tonyWithCardsANIM(CORO_PARAM, uint32 dwText, uint32, uint32, uint32) { + tonySetNumTexts(dwText); + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_WITHCARDS; +} + +void tonyWithSnowmanANIM(CORO_PARAM, uint32 dwText, uint32, uint32, uint32) { + tonySetNumTexts(dwText); + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_WITHSNOWMAN; +} + +void tonyWithSnowmanStart(CORO_PARAM, uint32, uint32, uint32, uint32) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_WITHSNOWMANSTATIC; + GLOBALS._bStaticTalk = true; + CORO_INVOKE_1(GLOBALS._tony->startStatic, GLOBALS._tony->TALK_WITHSNOWMANSTATIC); + + CORO_END_CODE; +} + +void tonyWithSnowmanEnd(CORO_PARAM, uint32, uint32, uint32, uint32) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + CORO_INVOKE_1(GLOBALS._tony->endStatic, GLOBALS._tony->TALK_WITHSNOWMANSTATIC); + GLOBALS._bStaticTalk = false; + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_NORMAL; + + CORO_END_CODE; +} + +void tonyWithRabbitStart(CORO_PARAM, uint32, uint32, uint32, uint32) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_WITHRABBITSTATIC; + GLOBALS._bStaticTalk = true; + CORO_INVOKE_1(GLOBALS._tony->startStatic, GLOBALS._tony->TALK_WITHRABBITSTATIC); + + CORO_END_CODE; +} + +void tonyWithRabbitEnd(CORO_PARAM, uint32, uint32, uint32, uint32) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + CORO_INVOKE_1(GLOBALS._tony->endStatic, GLOBALS._tony->TALK_WITHRABBITSTATIC); + GLOBALS._bStaticTalk = false; + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_NORMAL; + + CORO_END_CODE; +} + +void tonyWithRecipeStart(CORO_PARAM, uint32, uint32, uint32, uint32) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_WITHRECIPESTATIC; + GLOBALS._bStaticTalk = true; + CORO_INVOKE_1(GLOBALS._tony->startStatic, GLOBALS._tony->TALK_WITHRECIPESTATIC); + + CORO_END_CODE; +} + +void tonyWithRecipeEnd(CORO_PARAM, uint32, uint32, uint32, uint32) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + CORO_INVOKE_1(GLOBALS._tony->endStatic, GLOBALS._tony->TALK_WITHRECIPESTATIC); + GLOBALS._bStaticTalk = false; + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_NORMAL; + + CORO_END_CODE; +} + +void tonyWithCardsStart(CORO_PARAM, uint32, uint32, uint32, uint32) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_WITHCARDSSTATIC; + GLOBALS._bStaticTalk = true; + CORO_INVOKE_1(GLOBALS._tony->startStatic, GLOBALS._tony->TALK_WITHCARDSSTATIC); + + CORO_END_CODE; +} + +void tonyWithCardsEnd(CORO_PARAM, uint32, uint32, uint32, uint32) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + CORO_INVOKE_1(GLOBALS._tony->endStatic, GLOBALS._tony->TALK_WITHCARDSSTATIC); + GLOBALS._bStaticTalk = false; + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_NORMAL; + + CORO_END_CODE; +} + +void tonyWithNotebookStart(CORO_PARAM, uint32, uint32, uint32, uint32) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_WITH_NOTEBOOK; + GLOBALS._bStaticTalk = true; + CORO_INVOKE_1(GLOBALS._tony->startStatic, GLOBALS._tony->TALK_WITH_NOTEBOOK); + + CORO_END_CODE; +} + +void tonyWithNotebookEnd(CORO_PARAM, uint32, uint32, uint32, uint32) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + CORO_INVOKE_1(GLOBALS._tony->endStatic, GLOBALS._tony->TALK_WITH_NOTEBOOK); + GLOBALS._bStaticTalk = false; + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_NORMAL; + + CORO_END_CODE; +} + +void tonyWithMegaphoneStart(CORO_PARAM, uint32, uint32, uint32, uint32) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_WITHMEGAPHONESTATIC; + GLOBALS._bStaticTalk = true; + CORO_INVOKE_1(GLOBALS._tony->startStatic, GLOBALS._tony->TALK_WITHMEGAPHONESTATIC); + + CORO_END_CODE; +} + +void tonyWithMegaphoneEnd(CORO_PARAM, uint32, uint32, uint32, uint32) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + CORO_INVOKE_1(GLOBALS._tony->endStatic, GLOBALS._tony->TALK_WITHMEGAPHONESTATIC); + GLOBALS._bStaticTalk = false; + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_NORMAL; + + CORO_END_CODE; +} + +void tonyWithBeardStart(CORO_PARAM, uint32, uint32, uint32, uint32) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_WITHBEARDSTATIC; + GLOBALS._bStaticTalk = true; + CORO_INVOKE_1(GLOBALS._tony->startStatic, GLOBALS._tony->TALK_WITHBEARDSTATIC); + + CORO_END_CODE; +} + +void tonyWithBeardEnd(CORO_PARAM, uint32, uint32, uint32, uint32) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + CORO_INVOKE_1(GLOBALS._tony->endStatic, GLOBALS._tony->TALK_WITHBEARDSTATIC); + GLOBALS._bStaticTalk = false; + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_NORMAL; + + CORO_END_CODE; +} + +void tonyScaredStart(CORO_PARAM, uint32, uint32, uint32, uint32) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_SCAREDSTATIC; + GLOBALS._bStaticTalk = true; + CORO_INVOKE_1(GLOBALS._tony->startStatic, GLOBALS._tony->TALK_SCAREDSTATIC); + + CORO_END_CODE; +} + +void tonyScaredEnd(CORO_PARAM, uint32, uint32, uint32, uint32) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + CORO_INVOKE_1(GLOBALS._tony->endStatic, GLOBALS._tony->TALK_SCAREDSTATIC); + GLOBALS._bStaticTalk = false; + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_NORMAL; + + CORO_END_CODE; +} + + +void tonyDisgusted(CORO_PARAM, uint32 dwText, uint32, uint32, uint32) { + tonySetNumTexts(dwText); + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_DISGUSTED; +} + +void tonySniffLeft(CORO_PARAM, uint32, uint32, uint32, uint32) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + GLOBALS._tony->setPattern(GLOBALS._tony->PAT_SNIFF_LEFT); + CORO_INVOKE_0(GLOBALS._tony->waitForEndPattern); + CORO_INVOKE_4(leftToMe, 0, 0, 0, 0); + + CORO_END_CODE; +} + +void tonySniffRight(CORO_PARAM, uint32, uint32, uint32, uint32) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + GLOBALS._tony->setPattern(GLOBALS._tony->PAT_SNIFF_RIGHT); + CORO_INVOKE_0(GLOBALS._tony->waitForEndPattern); + CORO_INVOKE_4(rightToMe, 0, 0, 0, 0); + + CORO_END_CODE; +} + +void tonySarcastic(CORO_PARAM, uint32 dwText, uint32, uint32, uint32) { + tonySetNumTexts(dwText); + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_SARCASTIC; +} + +void tonyMacbeth(CORO_PARAM, uint32 nPos, uint32, uint32, uint32) { + switch (nPos) { + case 1: + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_MACBETH1; + break; + case 2: + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_MACBETH2; + break; + case 3: + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_MACBETH3; + break; + case 4: + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_MACBETH4; + break; + case 5: + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_MACBETH5; + break; + case 6: + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_MACBETH6; + break; + case 7: + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_MACBETH7; + break; + case 8: + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_MACBETH8; + break; + case 9: + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_MACBETH9; + break; + } +} + + +void enableTony(CORO_PARAM, uint32, uint32, uint32, uint32) { + GLOBALS._tony->show(); +} + +void disableTony(CORO_PARAM, uint32 bShowShadow, uint32, uint32, uint32) { + GLOBALS._tony->hide(bShowShadow); +} + +void waitForPatternEnd(CORO_PARAM, uint32 nItem, uint32, uint32, uint32) { + CORO_BEGIN_CONTEXT; + RMItem *item; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + _ctx->item = GLOBALS._loc->getItemFromCode(nItem); + + if (!GLOBALS._bSkipIdle && _ctx->item != NULL) + CORO_INVOKE_1(_ctx->item->waitForEndPattern, GLOBALS._hSkipIdle); + + CORO_END_CODE; +} + + +void setTonyPosition(CORO_PARAM, uint32 nX, uint32 nY, uint32 nLoc, uint32) { + GLOBALS._tony->setPosition(RMPoint(nX, nY), nLoc); +} + +void moveTonyAndWait(CORO_PARAM, uint32 nX, uint32 nY, uint32, uint32) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + // WORKAROUND: Delay for a frame before starting the move to give any previous move time to finish. + // This fixes a bug in the first scene where if you immediately 'Use Door', Tony moves to the door, + // and then floats to the right rather than properly walking. + CORO_SLEEP(1); + + CORO_INVOKE_1(GLOBALS._tony->move, RMPoint(nX, nY)); + + if (!GLOBALS._bSkipIdle) + CORO_INVOKE_0(GLOBALS._tony->waitForEndMovement); + + CORO_END_CODE; +} + +void moveTony(CORO_PARAM, uint32 nX, uint32 nY, uint32, uint32) { + GLOBALS._tony->move(coroParam, RMPoint(nX, nY)); +} + +void scrollLocation(CORO_PARAM, uint32 nX, uint32 nY, uint32 sX, uint32 sY) { + CORO_BEGIN_CONTEXT; + int lx, ly; + RMPoint pt; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + // Take the scroll coordinates + _ctx->lx = (int32)nX; + _ctx->ly = (int32)nY; + + _ctx->pt = GLOBALS._loc->scrollPosition(); + + while ((_ctx->lx != 0 || _ctx->ly != 0) && !GLOBALS._bSkipIdle) { + if (_ctx->lx > 0) { + _ctx->lx -= (int32)sX; + if (_ctx->lx < 0) + _ctx->lx = 0; + _ctx->pt.offset((int32)sX, 0); + } else if (_ctx->lx < 0) { + _ctx->lx += (int32)sX; + if (_ctx->lx > 0) + _ctx->lx = 0; + _ctx->pt.offset(-(int32)sX, 0); + } + + if (_ctx->ly > 0) { + _ctx->ly -= sY; + if (_ctx->ly < 0) + _ctx->ly = 0; + _ctx->pt.offset(0, sY); + } else if (_ctx->ly < 0) { + _ctx->ly += sY; + if (_ctx->ly > 0) + _ctx->ly = 0; + _ctx->pt.offset(0, -(int32)sY); + } + + CORO_INVOKE_2(CoroScheduler.waitForSingleObject, g_vm->_hEndOfFrame, CORO_INFINITE); + + GLOBALS._loc->setScrollPosition(_ctx->pt); + GLOBALS._tony->setScrollPosition(_ctx->pt); + } + + CORO_END_CODE; +} + +void syncScrollLocation(CORO_PARAM, uint32 nX, uint32 nY, uint32 sX, uint32 sY) { + CORO_BEGIN_CONTEXT; + int lx, ly; + RMPoint pt, startpt; + uint32 dwStartTime, dwCurTime, dwTotalTime; + uint32 stepX, stepY; + int dimx, dimy; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + // Take the scroll coordinates + _ctx->lx = (int32)nX; + _ctx->ly = (int32)nY; + _ctx->dimx = _ctx->lx; + _ctx->dimy = _ctx->ly; + if (_ctx->lx < 0) + _ctx->dimx = -_ctx->lx; + + if (_ctx->ly < 0) + _ctx->dimy = -_ctx->ly; + + _ctx->stepX = sX; + _ctx->stepY = sY; + + _ctx->startpt = GLOBALS._loc->scrollPosition(); + + _ctx->dwStartTime = g_vm->getTime(); + + if (sX) + _ctx->dwTotalTime = _ctx->dimx * (1000 / 35) / sX; + else + _ctx->dwTotalTime = _ctx->dimy * (1000 / 35) / sY; + + while ((_ctx->lx != 0 || _ctx->ly != 0) && !GLOBALS._bSkipIdle) { + _ctx->dwCurTime = g_vm->getTime() - _ctx->dwStartTime; + if (_ctx->dwCurTime > _ctx->dwTotalTime) + break; + + _ctx->pt = _ctx->startpt; + + if (sX) { + if (_ctx->lx > 0) + _ctx->pt._x += (_ctx->dimx * _ctx->dwCurTime) / _ctx->dwTotalTime; + else + _ctx->pt._x -= (_ctx->dimx * _ctx->dwCurTime) / _ctx->dwTotalTime; + } else { + if (_ctx->ly > 0) + _ctx->pt._y += (_ctx->dimy * _ctx->dwCurTime) / _ctx->dwTotalTime; + else + _ctx->pt._y -= (_ctx->dimy * _ctx->dwCurTime) / _ctx->dwTotalTime; + + } + + CORO_INVOKE_2(CoroScheduler.waitForSingleObject, g_vm->_hEndOfFrame, CORO_INFINITE); + + GLOBALS._loc->setScrollPosition(_ctx->pt); + GLOBALS._tony->setScrollPosition(_ctx->pt); + + } + + + // Set the position finale + if (sX) { + if (_ctx->lx > 0) + _ctx->pt._x = _ctx->startpt._x + _ctx->dimx; + else + _ctx->pt._x = _ctx->startpt._x - _ctx->dimx; + } else { + if (_ctx->ly > 0) + _ctx->pt._y = _ctx->startpt._y + _ctx->dimy; + else + _ctx->pt._y = _ctx->startpt._y - _ctx->dimy; + } + + GLOBALS._loc->setScrollPosition(_ctx->pt); + GLOBALS._tony->setScrollPosition(_ctx->pt); + + CORO_END_CODE; +} + + +void changeHotspot(CORO_PARAM, uint32 dwCode, uint32 nX, uint32 nY, uint32) { + int i; + + for (i = 0; i < GLOBALS._curChangedHotspot; i++) { + if (GLOBALS._changedHotspot[i]._dwCode == dwCode) { + GLOBALS._changedHotspot[i]._nX = nX; + GLOBALS._changedHotspot[i]._nY = nY; + break; + } + } + + if (i == GLOBALS._curChangedHotspot) { + GLOBALS._changedHotspot[i]._dwCode = dwCode; + GLOBALS._changedHotspot[i]._nX = nX; + GLOBALS._changedHotspot[i]._nY = nY; + GLOBALS._curChangedHotspot++; + } + + GLOBALS._loc->getItemFromCode(dwCode)->changeHotspot(RMPoint(nX, nY)); +} + + +void autoSave(CORO_PARAM, uint32, uint32, uint32, uint32) { + g_vm->autoSave(coroParam); +} + +void abortGame(CORO_PARAM, uint32, uint32, uint32, uint32) { + error("script called abortGame"); +} + +void shakeScreen(CORO_PARAM, uint32 nScosse, uint32, uint32, uint32) { + CORO_BEGIN_CONTEXT; + uint32 i; + uint32 curTime; + int dirx, diry; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + _ctx->curTime = g_vm->getTime(); + + _ctx->dirx = 1; + _ctx->diry = 1; + + while (g_vm->getTime() < _ctx->curTime + nScosse) { + CORO_INVOKE_2(CoroScheduler.waitForSingleObject, g_vm->_hEndOfFrame, CORO_INFINITE); + + GLOBALS._loc->setFixedScroll(RMPoint(1 * _ctx->dirx, 1 * _ctx->diry)); + GLOBALS._tony->setFixedScroll(RMPoint(1 * _ctx->dirx, 1 * _ctx->diry)); + + _ctx->i = g_vm->_randomSource.getRandomNumber(2); + + if (_ctx->i == 0 || _ctx->i == 2) + _ctx->dirx = -_ctx->dirx; + else if (_ctx->i == 1 || _ctx->i == 2) + _ctx->diry = -_ctx->diry; + } + + GLOBALS._loc->setFixedScroll(RMPoint(0, 0)); + GLOBALS._tony->setFixedScroll(RMPoint(0, 0)); + + CORO_END_CODE; +} + +/* + * Characters + */ + +void charSetCode(CORO_PARAM, uint32 nChar, uint32 nCode, uint32, uint32) { + assert(nChar < 16); + GLOBALS._character[nChar]._code = nCode; + GLOBALS._character[nChar]._item = GLOBALS._loc->getItemFromCode(nCode); + GLOBALS._character[nChar]._r = 255; + GLOBALS._character[nChar]._g = 255; + GLOBALS._character[nChar]._b = 255; + GLOBALS._character[nChar]._talkPattern = 0; + GLOBALS._character[nChar]._startTalkPattern = 0; + GLOBALS._character[nChar]._endTalkPattern = 0; + GLOBALS._character[nChar]._standPattern = 0; + + GLOBALS._isMChar[nChar] = false; +} + +void charSetColor(CORO_PARAM, uint32 nChar, uint32 r, uint32 g, uint32 b) { + assert(nChar < 16); + GLOBALS._character[nChar]._r = r; + GLOBALS._character[nChar]._g = g; + GLOBALS._character[nChar]._b = b; +} + +void charSetTalkPattern(CORO_PARAM, uint32 nChar, uint32 tp, uint32 sp, uint32) { + assert(nChar < 16); + GLOBALS._character[nChar]._talkPattern = tp; + GLOBALS._character[nChar]._standPattern = sp; +} + +void charSetStartEndTalkPattern(CORO_PARAM, uint32 nChar, uint32 sp, uint32 ep, uint32) { + assert(nChar < 16); + GLOBALS._character[nChar]._startTalkPattern = sp; + GLOBALS._character[nChar]._endTalkPattern = ep; +} + +void charSendMessage(CORO_PARAM, uint32 nChar, uint32 dwMessage, uint32 bIsBack, uint32) { + CORO_BEGIN_CONTEXT; + RMMessage *msg; + int i; + RMPoint pt; + RMTextDialog *text; + int curOffset; + VoiceHeader *curVoc; + FPSfx *voice; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + _ctx->msg = new RMMessage(dwMessage); + _ctx->curOffset = 0; + + assert(nChar < 16); + _ctx->pt = GLOBALS._character[nChar]._item->calculatePos() - RMPoint(-60, 20) - GLOBALS._loc->scrollPosition(); + + if (GLOBALS._character[nChar]._startTalkPattern != 0) { + GLOBALS._character[nChar]._item->setPattern(GLOBALS._character[nChar]._startTalkPattern); + + CORO_INVOKE_0(GLOBALS._character[nChar]._item->waitForEndPattern); + } + + GLOBALS._character[nChar]._item->setPattern(GLOBALS._character[nChar]._talkPattern); + + _ctx->curVoc = searchVoiceHeader(0, dwMessage); + _ctx->voice = NULL; + if (_ctx->curVoc) { + // Position within the database of entries, beginning at the first + g_vm->_vdbFP.seek(_ctx->curVoc->_offset); + _ctx->curOffset = _ctx->curVoc->_offset; + } + + for (_ctx->i = 0; _ctx->i < _ctx->msg->numPeriods() && !GLOBALS._bSkipIdle; _ctx->i++) { + if (bIsBack) { + GLOBALS._curBackText = _ctx->text = new RMTextDialogScrolling(GLOBALS._loc); + if (GLOBALS._bTonyIsSpeaking) + CORO_INVOKE_0(GLOBALS._curBackText->hide); + } else + _ctx->text = new RMTextDialog; + + _ctx->text->setInput(GLOBALS._input); + + // Skipping + _ctx->text->setSkipStatus(!bIsBack); + + // Alignment + _ctx->text->setAlignType(RMText::HCENTER, RMText::VBOTTOM); + + // Color + _ctx->text->setColor(GLOBALS._character[nChar]._r, GLOBALS._character[nChar]._g, GLOBALS._character[nChar]._b); + + // Write the text + _ctx->text->writeText((*_ctx->msg)[_ctx->i], 0); + + // Set the position + _ctx->text->setPosition(_ctx->pt); + + // Set the always display + if (GLOBALS._bAlwaysDisplay) { + _ctx->text->setAlwaysDisplay(); + _ctx->text->forceTime(); + } + + // Record the text + g_vm->getEngine()->linkGraphicTask(_ctx->text); + + if (_ctx->curVoc) { + g_vm->_theSound.createSfx(&_ctx->voice); + g_vm->_vdbFP.seek(_ctx->curOffset); + _ctx->voice->loadVoiceFromVDB(g_vm->_vdbFP); + _ctx->voice->setLoop(false); + if (bIsBack) + _ctx->voice->setVolume(55); + _ctx->voice->play(); + _ctx->text->setCustomSkipHandle2(_ctx->voice->_hEndOfBuffer); + _ctx->curOffset = g_vm->_vdbFP.pos(); + } + + // Wait for the end of display + _ctx->text->setCustomSkipHandle(GLOBALS._hSkipIdle); + CORO_INVOKE_0(_ctx->text->waitForEndDisplay); + + if (_ctx->curVoc) { + _ctx->voice->stop(); + _ctx->voice->release(); + _ctx->voice = NULL; + } + + + GLOBALS._curBackText = NULL; + delete _ctx->text; + } + + if (GLOBALS._character[nChar]._endTalkPattern != 0) { + GLOBALS._character[nChar]._item->setPattern(GLOBALS._character[nChar]._endTalkPattern); + CORO_INVOKE_0(GLOBALS._character[nChar]._item->waitForEndPattern); + } + + GLOBALS._character[nChar]._item->setPattern(GLOBALS._character[nChar]._standPattern); + delete _ctx->msg; + + CORO_END_CODE; +} + +void addInventory(CORO_PARAM, uint32 dwCode, uint32, uint32, uint32) { + GLOBALS._inventory->addItem(dwCode); +} + +void removeInventory(CORO_PARAM, uint32 dwCode, uint32, uint32, uint32) { + GLOBALS._inventory->removeItem(dwCode); +} + +void changeInventoryStatus(CORO_PARAM, uint32 dwCode, uint32 dwStatus, uint32, uint32) { + GLOBALS._inventory->changeItemStatus(dwCode, dwStatus); +} + + +/* + * Master Characters + */ + +void mCharSetCode(CORO_PARAM, uint32 nChar, uint32 nCode, uint32, uint32) { + assert(nChar < 10); + GLOBALS._mCharacter[nChar]._code = nCode; + if (nCode == 0) + GLOBALS._mCharacter[nChar]._item = NULL; + else + GLOBALS._mCharacter[nChar]._item = GLOBALS._loc->getItemFromCode(nCode); + GLOBALS._mCharacter[nChar]._r = 255; + GLOBALS._mCharacter[nChar]._g = 255; + GLOBALS._mCharacter[nChar]._b = 255; + GLOBALS._mCharacter[nChar]._x = -1; + GLOBALS._mCharacter[nChar]._y = -1; + GLOBALS._mCharacter[nChar]._bAlwaysBack = 0; + + for (int i = 0; i < 10; i++) + GLOBALS._mCharacter[nChar]._numTalks[i] = 1; + + GLOBALS._mCharacter[nChar]._curGroup = 0; + + GLOBALS._isMChar[nChar] = true; +} + +void mCharResetCode(CORO_PARAM, uint32 nChar, uint32, uint32, uint32) { + GLOBALS._mCharacter[nChar]._item = GLOBALS._loc->getItemFromCode(GLOBALS._mCharacter[nChar]._code); +} + + +void mCharSetPosition(CORO_PARAM, uint32 nChar, uint32 nX, uint32 nY, uint32) { + assert(nChar < 10); + GLOBALS._mCharacter[nChar]._x = nX; + GLOBALS._mCharacter[nChar]._y = nY; +} + +void mCharSetColor(CORO_PARAM, uint32 nChar, uint32 r, uint32 g, uint32 b) { + assert(nChar < 10); + GLOBALS._mCharacter[nChar]._r = r; + GLOBALS._mCharacter[nChar]._g = g; + GLOBALS._mCharacter[nChar]._b = b; +} + +void mCharSetNumTalksInGroup(CORO_PARAM, uint32 nChar, uint32 nGroup, uint32 nTalks, uint32) { + assert(nChar < 10); + assert(nGroup < 10); + + GLOBALS._mCharacter[nChar]._numTalks[nGroup] = nTalks; +} + +void mCharSetCurrentGroup(CORO_PARAM, uint32 nChar, uint32 nGroup, uint32, uint32) { + assert(nChar < 10); + assert(nGroup < 10); + + GLOBALS._mCharacter[nChar]._curGroup = nGroup; +} + +void mCharSetNumTexts(CORO_PARAM, uint32 nChar, uint32 nTexts, uint32, uint32) { + assert(nChar < 10); + + GLOBALS._mCharacter[nChar]._numTexts = nTexts - 1; + GLOBALS._mCharacter[nChar]._bInTexts = false; +} + +void mCharSetAlwaysBack(CORO_PARAM, uint32 nChar, uint32 bAlwaysBack, uint32, uint32) { + assert(nChar < 10); + + GLOBALS._mCharacter[nChar]._bAlwaysBack = bAlwaysBack; +} + +void mCharSendMessage(CORO_PARAM, uint32 nChar, uint32 dwMessage, uint32 bIsBack, uint32 nFont) { + CORO_BEGIN_CONTEXT; + RMMessage *msg; + int i; + int parm; + RMPoint pt; + uint32 h; + RMTextDialog *text; + int curOffset; + VoiceHeader *curVoc; + FPSfx *voice; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + _ctx->msg = new RMMessage(dwMessage); + _ctx->curOffset = 0; + + assert(nChar < 10); + + bIsBack |= GLOBALS._mCharacter[nChar]._bAlwaysBack ? 1 : 0; + + // Calculates the position of the text according to the current frame + if (GLOBALS._mCharacter[nChar]._x == -1) + _ctx->pt = GLOBALS._mCharacter[nChar]._item->calculatePos() - RMPoint(-60, 20) - GLOBALS._loc->scrollPosition(); + else + _ctx->pt = RMPoint(GLOBALS._mCharacter[nChar]._x, GLOBALS._mCharacter[nChar]._y); + + // Parameter for special actions: random between the spoken + _ctx->parm = (GLOBALS._mCharacter[nChar]._curGroup * 10) + g_vm->_randomSource.getRandomNumber( + GLOBALS._mCharacter[nChar]._numTalks[GLOBALS._mCharacter[nChar]._curGroup] - 1) + 1; + + // Try to run the custom function to initialize the speech + if (GLOBALS._mCharacter[nChar]._item) { + _ctx->h = mpalQueryDoAction(30, GLOBALS._mCharacter[nChar]._item->mpalCode(), _ctx->parm); + if (_ctx->h != CORO_INVALID_PID_VALUE) { + CORO_INVOKE_2(CoroScheduler.waitForSingleObject, _ctx->h, CORO_INFINITE); + } + } + + _ctx->curVoc = searchVoiceHeader(0, dwMessage); + _ctx->voice = NULL; + if (_ctx->curVoc) { + // Position within the database of entries, beginning at the first + g_vm->_vdbFP.seek(_ctx->curVoc->_offset); + _ctx->curOffset = _ctx->curVoc->_offset; + } + + for (_ctx->i = 0; _ctx->i < _ctx->msg->numPeriods() && !GLOBALS._bSkipIdle; _ctx->i++) { + // Create a different object depending on whether it's background or not + if (bIsBack) { + GLOBALS._curBackText = _ctx->text = new RMTextDialogScrolling(GLOBALS._loc); + if (GLOBALS._bTonyIsSpeaking) + CORO_INVOKE_0(GLOBALS._curBackText->hide); + } else + _ctx->text = new RMTextDialog; + + _ctx->text->setInput(GLOBALS._input); + + // Skipping + _ctx->text->setSkipStatus(!bIsBack); + + // Alignment + _ctx->text->setAlignType(RMText::HCENTER, RMText::VBOTTOM); + + // Color + _ctx->text->setColor(GLOBALS._mCharacter[nChar]._r, GLOBALS._mCharacter[nChar]._g, GLOBALS._mCharacter[nChar]._b); + + // Write the text + _ctx->text->writeText((*_ctx->msg)[_ctx->i], nFont); + + // Set the position + _ctx->text->setPosition(_ctx->pt); + + // Set the always display + if (GLOBALS._bAlwaysDisplay) { + _ctx->text->setAlwaysDisplay(); + _ctx->text->forceTime(); + } + + // Record the text + g_vm->getEngine()->linkGraphicTask(_ctx->text); + + if (_ctx->curVoc) { + g_vm->_theSound.createSfx(&_ctx->voice); + g_vm->_vdbFP.seek(_ctx->curOffset); + _ctx->voice->loadVoiceFromVDB(g_vm->_vdbFP); + _ctx->voice->setLoop(false); + if (bIsBack) + _ctx->voice->setVolume(55); + _ctx->voice->play(); + _ctx->text->setCustomSkipHandle2(_ctx->voice->_hEndOfBuffer); + _ctx->curOffset = g_vm->_vdbFP.pos(); + } + + // Wait for the end of display + _ctx->text->setCustomSkipHandle(GLOBALS._hSkipIdle); + CORO_INVOKE_0(_ctx->text->waitForEndDisplay); + + if (_ctx->curVoc) { + _ctx->voice->stop(); + _ctx->voice->release(); + _ctx->voice = NULL; + } + + GLOBALS._curBackText = NULL; + delete _ctx->text; + } + + delete _ctx->msg; + + // Try to run the custom function to close the speech + if (GLOBALS._mCharacter[nChar]._item) { + _ctx->h = mpalQueryDoAction(31, GLOBALS._mCharacter[nChar]._item->mpalCode(), _ctx->parm); + if (_ctx->h != CORO_INVALID_PID_VALUE) + CORO_INVOKE_2(CoroScheduler.waitForSingleObject, _ctx->h, CORO_INFINITE); + } + + CORO_END_CODE; +} + +/* + * Dialogs + */ + +void sendDialogMessage(CORO_PARAM, uint32 nPers, uint32 nMsg, uint32, uint32) { + CORO_BEGIN_CONTEXT; + char *string; + RMTextDialog *text; + int parm; + uint32 h; + bool bIsBack; + VoiceHeader *curVoc; + FPSfx *voice; + RMPoint pt; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + _ctx->bIsBack = false; + + // The SendDialogMessage can go in the background if it is a character + if (nPers != 0 && GLOBALS._isMChar[nPers] && GLOBALS._mCharacter[nPers]._bAlwaysBack) + _ctx->bIsBack = true; + + _ctx->curVoc = searchVoiceHeader(GLOBALS._curDialog, nMsg); + _ctx->voice = NULL; + + if (_ctx->curVoc) { + // Position within the database of entries, beginning at the first + g_vm->_vdbFP.seek(_ctx->curVoc->_offset); + g_vm->_theSound.createSfx(&_ctx->voice); + _ctx->voice->loadVoiceFromVDB(g_vm->_vdbFP); + _ctx->voice->setLoop(false); + if (_ctx->bIsBack) + _ctx->voice->setVolume(55); + } + + _ctx->string = mpalQueryDialogPeriod(nMsg); + + if (nPers == 0) { + _ctx->text = new RMTextDialog; + _ctx->text->setColor(0, 255, 0); + _ctx->text->setPosition(GLOBALS._tony->position() - RMPoint(0, 130) - GLOBALS._loc->scrollPosition()); + _ctx->text->writeText(_ctx->string, 0); + + if (GLOBALS._dwTonyNumTexts > 0) { + if (!GLOBALS._bTonyInTexts) { + if (GLOBALS._nTonyNextTalkType != GLOBALS._tony->TALK_NORMAL) { + CORO_INVOKE_1(GLOBALS._tony->startTalk, GLOBALS._nTonyNextTalkType); + if (!GLOBALS._bStaticTalk) + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_NORMAL; + } else + CORO_INVOKE_1(GLOBALS._tony->startTalk, GLOBALS._tony->TALK_NORMAL); + + GLOBALS._bTonyInTexts = true; + } + GLOBALS._dwTonyNumTexts--; + } else { + CORO_INVOKE_1(GLOBALS._tony->startTalk, GLOBALS._nTonyNextTalkType); + if (!GLOBALS._bStaticTalk) + GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_NORMAL; + } + } else if (!GLOBALS._isMChar[nPers]) { + _ctx->text = new RMTextDialog; + + _ctx->pt = GLOBALS._character[nPers]._item->calculatePos() - RMPoint(-60, 20) - GLOBALS._loc->scrollPosition(); + + if (GLOBALS._character[nPers]._startTalkPattern != 0) { + GLOBALS._character[nPers]._item->setPattern(GLOBALS._character[nPers]._startTalkPattern); + CORO_INVOKE_0(GLOBALS._character[nPers]._item->waitForEndPattern); + } + + GLOBALS._character[nPers]._item->setPattern(GLOBALS._character[nPers]._talkPattern); + + _ctx->text->setColor(GLOBALS._character[nPers]._r, GLOBALS._character[nPers]._g, GLOBALS._character[nPers]._b); + _ctx->text->writeText(_ctx->string, 0); + _ctx->text->setPosition(_ctx->pt); + } else { + if (GLOBALS._mCharacter[nPers]._x == -1) + _ctx->pt = GLOBALS._mCharacter[nPers]._item->calculatePos() - RMPoint(-60, 20) - GLOBALS._loc->scrollPosition(); + else + _ctx->pt = RMPoint(GLOBALS._mCharacter[nPers]._x, GLOBALS._mCharacter[nPers]._y); + + // Parameter for special actions. Random between the spoken. + _ctx->parm = (GLOBALS._mCharacter[nPers]._curGroup * 10) + g_vm->_randomSource.getRandomNumber( + GLOBALS._mCharacter[nPers]._numTalks[GLOBALS._mCharacter[nPers]._curGroup] - 1) + 1; + + if (GLOBALS._mCharacter[nPers]._numTexts != 0 && GLOBALS._mCharacter[nPers]._bInTexts) { + GLOBALS._mCharacter[nPers]._numTexts--; + } else { + // Try to run the custom function to initialize the speech + _ctx->h = mpalQueryDoAction(30, GLOBALS._mCharacter[nPers]._item->mpalCode(), _ctx->parm); + if (_ctx->h != CORO_INVALID_PID_VALUE) + CORO_INVOKE_2(CoroScheduler.waitForSingleObject, _ctx->h, CORO_INFINITE); + + GLOBALS._mCharacter[nPers]._curTalk = _ctx->parm; + + if (GLOBALS._mCharacter[nPers]._numTexts != 0) { + GLOBALS._mCharacter[nPers]._bInTexts = true; + GLOBALS._mCharacter[nPers]._numTexts--; + } + } + + if (GLOBALS._mCharacter[nPers]._bAlwaysBack) { + _ctx->text = GLOBALS._curBackText = new RMTextDialogScrolling(GLOBALS._loc); + if (GLOBALS._bTonyIsSpeaking) + CORO_INVOKE_0(GLOBALS._curBackText->hide); + + _ctx->bIsBack = true; + } else + _ctx->text = new RMTextDialog; + + _ctx->text->setSkipStatus(!GLOBALS._mCharacter[nPers]._bAlwaysBack); + _ctx->text->setColor(GLOBALS._mCharacter[nPers]._r, GLOBALS._mCharacter[nPers]._g, GLOBALS._mCharacter[nPers]._b); + _ctx->text->writeText(_ctx->string, 0); + _ctx->text->setPosition(_ctx->pt); + } + + if (!GLOBALS._bSkipIdle) { + _ctx->text->setInput(GLOBALS._input); + if (GLOBALS._bAlwaysDisplay) { + _ctx->text->setAlwaysDisplay(); + _ctx->text->forceTime(); + } + _ctx->text->setAlignType(RMText::HCENTER, RMText::VBOTTOM); + g_vm->getEngine()->linkGraphicTask(_ctx->text); + + if (_ctx->curVoc) { + _ctx->voice->play(); + _ctx->text->setCustomSkipHandle2(_ctx->voice->_hEndOfBuffer); + } + + // Wait for the end of display + _ctx->text->setCustomSkipHandle(GLOBALS._hSkipIdle); + CORO_INVOKE_0(_ctx->text->waitForEndDisplay); + } + + if (_ctx->curVoc) { + _ctx->voice->stop(); + _ctx->voice->release(); + _ctx->voice = NULL; + } + + if (nPers != 0) { + if (!GLOBALS._isMChar[nPers]) { + if (GLOBALS._character[nPers]._endTalkPattern != 0) { + GLOBALS._character[nPers]._item->setPattern(GLOBALS._character[nPers]._endTalkPattern); + CORO_INVOKE_0(GLOBALS._character[nPers]._item->waitForEndPattern); + } + + GLOBALS._character[nPers]._item->setPattern(GLOBALS._character[nPers]._standPattern); + delete _ctx->text; + } else { + if ((GLOBALS._mCharacter[nPers]._bInTexts && GLOBALS._mCharacter[nPers]._numTexts == 0) || !GLOBALS._mCharacter[nPers]._bInTexts) { + // Try to run the custom function to close the speech + GLOBALS._mCharacter[nPers]._curTalk = (GLOBALS._mCharacter[nPers]._curTalk % 10) + GLOBALS._mCharacter[nPers]._curGroup * 10; + _ctx->h = mpalQueryDoAction(31, GLOBALS._mCharacter[nPers]._item->mpalCode(), GLOBALS._mCharacter[nPers]._curTalk); + if (_ctx->h != CORO_INVALID_PID_VALUE) + CORO_INVOKE_2(CoroScheduler.waitForSingleObject, _ctx->h, CORO_INFINITE); + + GLOBALS._mCharacter[nPers]._bInTexts = false; + GLOBALS._mCharacter[nPers]._numTexts = 0; + } + + GLOBALS._curBackText = NULL; + delete _ctx->text; + } + } else { + if ((GLOBALS._dwTonyNumTexts == 0 && GLOBALS._bTonyInTexts) || !GLOBALS._bTonyInTexts) { + CORO_INVOKE_0(GLOBALS._tony->endTalk); + GLOBALS._dwTonyNumTexts = 0; + GLOBALS._bTonyInTexts = false; + } + + delete _ctx->text; + } + + globalDestroy(_ctx->string); + + CORO_END_CODE; +} + + +// @@@@ This cannot be skipped!!!!!!!!!!!!!!!!!!! + +void startDialog(CORO_PARAM, uint32 nDialog, uint32 nStartGroup, uint32, uint32) { + CORO_BEGIN_CONTEXT; + uint32 nChoice; + uint32 *sl; + uint32 i, num; + char *string; + RMDialogChoice dc; + int sel; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + GLOBALS._curDialog = nDialog; + + // Call MPAL to start the dialog + mpalQueryDoDialog(nDialog, nStartGroup); + + // Wait until a choice is selected + mpalQueryDialogWaitForChoice(&_ctx->nChoice); + while (_ctx->nChoice != (uint32) - 1) { + // Get the list of options + _ctx->sl = mpalQueryDialogSelectList(_ctx->nChoice); + for (_ctx->num = 0; _ctx->sl[_ctx->num] != 0; _ctx->num++) + ; + + // If there is only one option, do it automatically, and wait for the next choice + if (_ctx->num == 1) { + mpalQueryDialogSelectionDWORD(_ctx->nChoice, _ctx->sl[0]); + globalDestroy(_ctx->sl); + + // Wait for the next choice to be made + mpalQueryDialogWaitForChoice(&_ctx->nChoice); + continue; + } + + // Making a choice for dialog + _ctx->dc.init(); + _ctx->dc.setNumChoices(_ctx->num); + + // Writeall the possible options + for (_ctx->i = 0; _ctx->i < _ctx->num; _ctx->i++) { + _ctx->string = mpalQueryDialogPeriod(_ctx->sl[_ctx->i]); + assert(_ctx->string != NULL); + _ctx->dc.addChoice(_ctx->string); + globalDestroy(_ctx->string); + } + + // Activate the object + g_vm->getEngine()->linkGraphicTask(&_ctx->dc); + CORO_INVOKE_0(_ctx->dc.show); + + // Draw the pointer + GLOBALS._pointer->setSpecialPointer(GLOBALS._pointer->PTR_NONE); + g_vm->getEngine()->enableMouse(); + + while (!(GLOBALS._input->mouseLeftClicked() && ((_ctx->sel = _ctx->dc.getSelection()) != -1))) { + CORO_INVOKE_2(CoroScheduler.waitForSingleObject, g_vm->_hEndOfFrame, CORO_INFINITE); + CORO_INVOKE_1(_ctx->dc.doFrame, GLOBALS._input->mousePos()); + } + + // Hide the pointer + g_vm->getEngine()->disableMouse(); + + CORO_INVOKE_0(_ctx->dc.hide); + mpalQueryDialogSelectionDWORD(_ctx->nChoice, _ctx->sl[_ctx->sel]); + + // Closes the choice + _ctx->dc.close(); + + globalDestroy(_ctx->sl); + + // Wait for the next choice to be made + mpalQueryDialogWaitForChoice(&_ctx->nChoice); + } + + CORO_END_CODE; +} + + +/* + * Sync between idle and mpal + */ + +void takeOwnership(CORO_PARAM, uint32 num, uint32, uint32, uint32) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + if (GLOBALS._mut[num]._ownerPid != (uint32)CoroScheduler.getCurrentPID()) { + // The mutex is currently owned by a different process. + // Wait for the event to be signalled, which means the mutex is free. + CORO_INVOKE_2(CoroScheduler.waitForSingleObject, GLOBALS._mut[num]._eventId, CORO_INFINITE); + GLOBALS._mut[num]._ownerPid = (uint32)CoroScheduler.getCurrentPID(); + } + + GLOBALS._mut[num]._lockCount++; + + CORO_END_CODE; +} + +void releaseOwnership(CORO_PARAM, uint32 num, uint32, uint32, uint32) { + if (!GLOBALS._mut[num]._lockCount) { + warning("ReleaseOwnership tried to release mutex %d, which isn't held", num); + return; + } + + if (GLOBALS._mut[num]._ownerPid != (uint32)CoroScheduler.getCurrentPID()) { + warning("ReleaseOwnership tried to release mutex %d, which is held by a different process", num); + return; + } + + GLOBALS._mut[num]._lockCount--; + if (!GLOBALS._mut[num]._lockCount) { + GLOBALS._mut[num]._ownerPid = 0; + + // Signal the event, to wake up processes waiting for the lock. + CoroScheduler.setEvent(GLOBALS._mut[num]._eventId); + } +} + +/* + * Music + * ----- + * + * Fadeout effects supposed: + * + * nFX = 0 - The new music replaces the old one + * nFX=1 - The new music interfades with the old one + * nFX=2 - The new music takes over in time from the old + * + */ + +void threadFadeInMusic(CORO_PARAM, const void *nMusic) { + CORO_BEGIN_CONTEXT; + int i; + CORO_END_CONTEXT(_ctx); + + int nChannel = *(const int *)nMusic; + + CORO_BEGIN_CODE(_ctx); + + debugC(DEBUG_INTERMEDIATE, kTonyDebugSound, "Start FadeIn Music"); + + for (_ctx->i = 0; _ctx->i < 16; _ctx->i++) { + g_vm->setMusicVolume(nChannel, _ctx->i * 4); + + CORO_INVOKE_1(CoroScheduler.sleep, 100); + } + g_vm->setMusicVolume(nChannel, 64); + + debugC(DEBUG_INTERMEDIATE, kTonyDebugSound, "End FadeIn Music"); + + CORO_KILL_SELF(); + + CORO_END_CODE; +} + +void threadFadeOutMusic(CORO_PARAM, const void *nMusic) { + CORO_BEGIN_CONTEXT; + int i; + int startVolume; + CORO_END_CONTEXT(_ctx); + + int nChannel = *(const int *)nMusic; + + CORO_BEGIN_CODE(_ctx); + + _ctx->startVolume = g_vm->getMusicVolume(nChannel); + + for (_ctx->i = 16; _ctx->i > 0 && !GLOBALS._bFadeOutStop; _ctx->i--) { + if (_ctx->i * 4 < _ctx->startVolume) + g_vm->setMusicVolume(nChannel, _ctx->i * 4); + + CORO_INVOKE_1(CoroScheduler.sleep, 100); + } + + if (!GLOBALS._bFadeOutStop) + g_vm->setMusicVolume(nChannel, 0); + + // If a jingle is played, stop it + if (nChannel == 2) + g_vm->stopMusic(2); + + CORO_KILL_SELF(); + + CORO_END_CODE; +} + +void fadeInSoundEffect(CORO_PARAM, uint32, uint32, uint32, uint32) { + CoroScheduler.createProcess(threadFadeInMusic, &GLOBALS._curSoundEffect, sizeof(int)); +} + +void fadeOutSoundEffect(CORO_PARAM, uint32, uint32, uint32, uint32) { + GLOBALS._bFadeOutStop = false; + CoroScheduler.createProcess(threadFadeOutMusic, &GLOBALS._curSoundEffect, sizeof(int)); +} + +void fadeOutJingle(CORO_PARAM, uint32, uint32, uint32, uint32) { + GLOBALS._bFadeOutStop = false; + int channel = 2; + CoroScheduler.createProcess(threadFadeOutMusic, &channel, sizeof(int)); +} + +void fadeInJingle(CORO_PARAM, uint32, uint32, uint32, uint32) { + int channel = 2; + CoroScheduler.createProcess(threadFadeInMusic, &channel, sizeof(int)); +} + +void stopSoundEffect(CORO_PARAM, uint32, uint32, uint32, uint32) { + g_vm->stopMusic(GLOBALS._curSoundEffect); +} + +void stopJingle(CORO_PARAM, uint32, uint32, uint32, uint32) { + g_vm->stopMusic(2); +} + +void muteSoundEffect(CORO_PARAM, uint32, uint32, uint32, uint32) { + g_vm->setMusicVolume(GLOBALS._curSoundEffect, 0); +} + +void demuteSoundEffect(CORO_PARAM, uint32, uint32, uint32, uint32) { + GLOBALS._bFadeOutStop = true; + g_vm->setMusicVolume(GLOBALS._curSoundEffect, 64); +} + +void muteJingle(CORO_PARAM, uint32, uint32, uint32, uint32) { + g_vm->setMusicVolume(2, 0); +} + +void demuteJingle(CORO_PARAM, uint32, uint32, uint32, uint32) { + g_vm->setMusicVolume(2, 64); +} + +void custPlayMusic(uint32 nChannel, const char *mFN, uint32 nFX, bool bLoop, int nSync = 0) { + if (nSync == 0) + nSync = 2000; + debugC(DEBUG_INTERMEDIATE, kTonyDebugMusic, "Start CustPlayMusic"); + g_vm->playMusic(nChannel, mFN, nFX, bLoop, nSync); + debugC(DEBUG_INTERMEDIATE, kTonyDebugMusic, "End CustPlayMusic"); +} + +void playSoundEffect(CORO_PARAM, uint32 nMusic, uint32 nFX, uint32 bNoLoop, uint32) { + if (nFX == 0 || nFX == 1 || nFX == 2) { + debugC(DEBUG_INTERMEDIATE, kTonyDebugSound, "PlaySoundEffect stop fadeout"); + GLOBALS._bFadeOutStop = true; + } + + GLOBALS._lastMusic = nMusic; + custPlayMusic(GLOBALS._curSoundEffect, kMusicFiles[nMusic]._name, nFX, bNoLoop ? false : true, kMusicFiles[nMusic]._sync); +} + +void playJingle(CORO_PARAM, uint32 nMusic, uint32 nFX, uint32 bLoop, uint32) { + custPlayMusic(2, kJingleFileNames[nMusic], nFX, bLoop); +} + +void playItemSfx(CORO_PARAM, uint32 nItem, uint32 nSFX, uint32, uint32) { + if (nItem == 0) { + GLOBALS._tony->playSfx(nSFX); + } else { + RMItem *item = GLOBALS._loc->getItemFromCode(nItem); + if (item) + item->playSfx(nSFX); + } +} + +void restoreMusic(CORO_PARAM) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + CORO_INVOKE_4(playSoundEffect, GLOBALS._lastMusic, 0, 0, 0); + + if (GLOBALS._lastTappeto != 0) + custPlayMusic(4, kAmbianceFile[GLOBALS._lastTappeto], 0, true); + + CORO_END_CODE; +} + +void saveMusic(Common::OutSaveFile *f) { + f->writeByte(GLOBALS._lastMusic); + f->writeByte(GLOBALS._lastTappeto); +} + +void loadMusic(Common::InSaveFile *f) { + GLOBALS._lastMusic = f->readByte(); + GLOBALS._lastTappeto = f->readByte(); +} + +void jingleFadeStart(CORO_PARAM, uint32 nJingle, uint32 bLoop, uint32, uint32) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + CORO_INVOKE_4(fadeOutSoundEffect, 0, 0, 0, 0); + CORO_INVOKE_4(muteJingle, 0, 0, 0, 0); + CORO_INVOKE_4(playJingle, nJingle, 0, bLoop, 0); + CORO_INVOKE_4(fadeInJingle, 0, 0, 0, 0); + + CORO_END_CODE; +} + +void jingleFadeEnd(CORO_PARAM, uint32 nJingle, uint32 bLoop, uint32, uint32) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + CORO_INVOKE_4(fadeOutJingle, 0, 0, 0, 0); + CORO_INVOKE_4(fadeInSoundEffect, 0, 0, 0, 0); + + CORO_END_CODE; +} + +void mustSkipIdleStart(CORO_PARAM, uint32, uint32, uint32, uint32) { + GLOBALS._bSkipIdle = true; + CoroScheduler.setEvent(GLOBALS._hSkipIdle); +} + +void mustSkipIdleEnd(CORO_PARAM, uint32, uint32, uint32, uint32) { + GLOBALS._bSkipIdle = false; + CoroScheduler.resetEvent(GLOBALS._hSkipIdle); +} + +void patIrqFreeze(CORO_PARAM, uint32 bStatus, uint32, uint32, uint32) { + // Unused in ScummVM. +} + +void openInitLoadMenu(CORO_PARAM, uint32, uint32, uint32, uint32) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + CORO_INVOKE_0(g_vm->openInitLoadMenu); + + CORO_END_CODE; +} + +void openInitOptions(CORO_PARAM, uint32, uint32, uint32, uint32) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + CORO_INVOKE_0(g_vm->openInitOptions); + + CORO_END_CODE; +} + +void doCredits(CORO_PARAM, uint32 nMsg, uint32 dwTime, uint32, uint32) { + CORO_BEGIN_CONTEXT; + RMMessage *msg; + RMTextDialog *text; + uint32 hDisable; + int i; + uint32 startTime; + + ~CoroContextTag() { + delete msg; + delete[] text; + } + + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + _ctx->msg = new RMMessage(nMsg); + _ctx->hDisable = CoroScheduler.createEvent(true, false); + + _ctx->text = new RMTextDialog[_ctx->msg->numPeriods()]; + + for (_ctx->i = 0; _ctx->i < _ctx->msg->numPeriods(); _ctx->i++) { + _ctx->text[_ctx->i].setInput(GLOBALS._input); + + // Alignment + if ((*_ctx->msg)[_ctx->i][0] == '@') { + _ctx->text[_ctx->i].setAlignType(RMText::HCENTER, RMText::VTOP); + _ctx->text[_ctx->i].writeText(&(*_ctx->msg)[_ctx->i][1], 3); + _ctx->text[_ctx->i].setPosition(RMPoint(414, 70 + _ctx->i * 26)); // 70 + } else { + _ctx->text[_ctx->i].setAlignType(RMText::HLEFT, RMText::VTOP); + _ctx->text[_ctx->i].writeText((*_ctx->msg)[_ctx->i], 3); + _ctx->text[_ctx->i].setPosition(RMPoint(260, 70 + _ctx->i * 26)); + } + + + // Set the position + _ctx->text[_ctx->i].setAlwaysDisplay(); + _ctx->text[_ctx->i].setForcedTime(dwTime * 1000); + _ctx->text[_ctx->i].setNoTab(); + + // Wait for the end of display + _ctx->text[_ctx->i].setCustomSkipHandle(_ctx->hDisable); + + // Record the text + g_vm->getEngine()->linkGraphicTask(&_ctx->text[_ctx->i]); + } + + _ctx->startTime = g_vm->getTime(); + + while (_ctx->startTime + dwTime * 1000 > g_vm->getTime()) { + CORO_INVOKE_2(CoroScheduler.waitForSingleObject, g_vm->_hEndOfFrame, CORO_INFINITE); + if (GLOBALS._input->mouseLeftClicked() || GLOBALS._input->mouseRightClicked()) + break; + if (g_vm->getEngine()->getInput().getAsyncKeyState(Common::KEYCODE_TAB)) + break; + } + + CoroScheduler.setEvent(_ctx->hDisable); + + CORO_INVOKE_2(CoroScheduler.waitForSingleObject, g_vm->_hEndOfFrame, CORO_INFINITE); + CORO_INVOKE_2(CoroScheduler.waitForSingleObject, g_vm->_hEndOfFrame, CORO_INFINITE); + + delete[] _ctx->text; + delete _ctx->msg; + _ctx->text = NULL; + _ctx->msg = NULL; + + CORO_END_CODE; +} + +BEGIN_CUSTOM_FUNCTION_MAP() + +ASSIGN(1, custLoadLocation) +ASSIGN(2, mySleep) +ASSIGN(3, setPointer) +ASSIGN(5, moveTony) +ASSIGN(6, faceToMe) +ASSIGN(7, backToMe) +ASSIGN(8, leftToMe) +ASSIGN(9, rightToMe) +ASSIGN(10, sendTonyMessage) +ASSIGN(11, changeBoxStatus) +ASSIGN(12, changeLocation) +ASSIGN(13, disableTony) +ASSIGN(14, enableTony) +ASSIGN(15, waitForPatternEnd) +ASSIGN(16, setLocStartPosition) +ASSIGN(17, scrollLocation) +ASSIGN(18, moveTonyAndWait) +ASSIGN(19, changeHotspot) +ASSIGN(20, addInventory) +ASSIGN(21, removeInventory) +ASSIGN(22, changeInventoryStatus) +ASSIGN(23, setTonyPosition) +ASSIGN(24, sendFullscreenMessage) +ASSIGN(25, saveTonyPosition) +ASSIGN(26, restoreTonyPosition) +ASSIGN(27, disableInput) +ASSIGN(28, enableInput) +ASSIGN(29, stopTony) + +ASSIGN(30, tonyTakeUp1) +ASSIGN(31, tonyTakeMid1) +ASSIGN(32, tonyTakeDown1) +ASSIGN(33, tonyTakeUp2) +ASSIGN(34, tonyTakeMid2) +ASSIGN(35, tonyTakeDown2) + +ASSIGN(72, tonyPutUp1) +ASSIGN(73, tonyPutMid1) +ASSIGN(74, tonyPutDown1) +ASSIGN(75, tonyPutUp2) +ASSIGN(76, tonyPutMid2) +ASSIGN(77, tonyPutDown2) + +ASSIGN(36, tonyOnTheFloor) +ASSIGN(37, tonyGetUp) +ASSIGN(38, tonyShepherdess) +ASSIGN(39, tonyWhistle) + +ASSIGN(40, tonyLaugh) +ASSIGN(41, tonyHips) +ASSIGN(42, tonySing) +ASSIGN(43, tonyIndicate) +ASSIGN(44, tonyScaredWithHands) +ASSIGN(49, tonyScaredWithoutHands) +ASSIGN(45, tonyWithGlasses) +ASSIGN(46, tonyWithWorm) +ASSIGN(47, tonyWithHammer) +ASSIGN(48, tonyWithRope) +ASSIGN(90, tonyWithRabbitANIM) +ASSIGN(91, tonyWithRecipeANIM) +ASSIGN(92, tonyWithCardsANIM) +ASSIGN(93, tonyWithSnowmanANIM) +ASSIGN(94, tonyWithSnowmanStart) +ASSIGN(95, tonyWithSnowmanEnd) +ASSIGN(96, tonyWithRabbitStart) +ASSIGN(97, tonyWithRabbitEnd) +ASSIGN(98, tonyWithRecipeStart) +ASSIGN(99, tonyWithRecipeEnd) +ASSIGN(100, tonyWithCardsStart) +ASSIGN(101, tonyWithCardsEnd) +ASSIGN(102, tonyWithNotebookStart) +ASSIGN(103, tonyWithNotebookEnd) +ASSIGN(104, tonyWithMegaphoneStart) +ASSIGN(105, tonyWithMegaphoneEnd) +ASSIGN(106, tonyWithBeardStart) +ASSIGN(107, tonyWithBeardEnd) +ASSIGN(108, tonyGiggle) +ASSIGN(109, tonyDisgusted) +ASSIGN(110, tonySarcastic) +ASSIGN(111, tonyMacbeth) +ASSIGN(112, tonySniffLeft) +ASSIGN(113, tonySniffRight) +ASSIGN(114, tonyScaredStart) +ASSIGN(115, tonyScaredEnd) +ASSIGN(116, tonyWithSecretary) + +ASSIGN(50, charSetCode) +ASSIGN(51, charSetColor) +ASSIGN(52, charSetTalkPattern) +ASSIGN(53, charSendMessage) +ASSIGN(54, charSetStartEndTalkPattern) + +ASSIGN(60, mCharSetCode) +ASSIGN(61, mCharSetColor) +ASSIGN(62, mCharSetCurrentGroup) +ASSIGN(63, mCharSetNumTalksInGroup) +ASSIGN(64, mCharSetNumTexts) +ASSIGN(65, mCharSendMessage) +ASSIGN(66, mCharSetPosition) +ASSIGN(67, mCharSetAlwaysBack) +ASSIGN(68, mCharResetCode) + +ASSIGN(70, startDialog) +ASSIGN(71, sendDialogMessage) + +ASSIGN(80, takeOwnership) +ASSIGN(81, releaseOwnership) + +ASSIGN(86, playSoundEffect) +ASSIGN(87, playJingle) +ASSIGN(88, fadeInSoundEffect) +ASSIGN(89, fadeOutSoundEffect) +ASSIGN(123, fadeInJingle) +ASSIGN(124, fadeOutJingle) +ASSIGN(125, muteSoundEffect) +ASSIGN(126, demuteSoundEffect) +ASSIGN(127, muteJingle) +ASSIGN(128, demuteJingle) +ASSIGN(84, stopSoundEffect) +ASSIGN(85, stopJingle) +ASSIGN(83, playItemSfx) +ASSIGN(129, jingleFadeStart) +ASSIGN(130, jingleFadeEnd) + +ASSIGN(120, shakeScreen) +ASSIGN(121, autoSave) +ASSIGN(122, abortGame) +ASSIGN(131, noBullsEye) +ASSIGN(132, sendFullscreenMsgStart) +ASSIGN(133, sendFullscreenMsgEnd) +ASSIGN(134, custEnableGUI) +ASSIGN(135, custDisableGUI) +ASSIGN(136, clearScreen) +ASSIGN(137, patIrqFreeze) +ASSIGN(138, tonySetPerorate) +ASSIGN(139, openInitLoadMenu) +ASSIGN(140, openInitOptions) +ASSIGN(141, syncScrollLocation) +ASSIGN(142, closeLocation) +ASSIGN(143, setAlwaysDisplay) +ASSIGN(144, doCredits) + +ASSIGN(200, mustSkipIdleStart); +ASSIGN(201, mustSkipIdleEnd); + +END_CUSTOM_FUNCTION_MAP() + +void processKilledCallback(Common::PROCESS *p) { + for (uint i = 0; i < 10; i++) { + if (GLOBALS._mut[i]._ownerPid == p->pid) { + // Handle scripts which don't call ReleaseOwnership, such as + // the one in loc37's vEnter when Tony is chasing the mouse. + debug(DEBUG_BASIC, "Force-releasing mutex %d after process died", i); + + GLOBALS._mut[i]._ownerPid = 0; + GLOBALS._mut[i]._lockCount = 0; + CoroScheduler.setEvent(GLOBALS._mut[i]._eventId); + } + } +} + +void setupGlobalVars(RMTony *tony, RMPointer *ptr, RMGameBoxes *box, RMLocation *loc, RMInventory *inv, RMInput *input) { + GLOBALS._tony = tony; + GLOBALS._pointer = ptr; + GLOBALS._boxes = box; + GLOBALS._loc = loc; + GLOBALS._inventory = inv; + GLOBALS._input = input; + + GLOBALS.DisableGUI = mainDisableGUI; + GLOBALS.EnableGUI = mainEnableGUI; + + GLOBALS._bAlwaysDisplay = false; + + CoroScheduler.setResourceCallback(processKilledCallback); + for (int i = 0; i < 10; i++) + GLOBALS._mut[i]._eventId = CoroScheduler.createEvent(false, true); + + for (int i = 0; i < 200; i++) + GLOBALS._ambiance[i] = 0; + + GLOBALS._ambiance[6] = AMBIANCE_CRICKETS; + GLOBALS._ambiance[7] = AMBIANCE_CRICKETS; + GLOBALS._ambiance[8] = AMBIANCE_CRICKETSMUFFLED; + GLOBALS._ambiance[10] = AMBIANCE_CRICKETS; + GLOBALS._ambiance[12] = AMBIANCE_CRICKETS; + GLOBALS._ambiance[13] = AMBIANCE_CRICKETSMUFFLED; + GLOBALS._ambiance[15] = AMBIANCE_CRICKETS; + GLOBALS._ambiance[16] = AMBIANCE_CRICKETSWIND; + GLOBALS._ambiance[18] = AMBIANCE_CRICKETS; + GLOBALS._ambiance[19] = AMBIANCE_CRICKETSWIND; + GLOBALS._ambiance[20] = AMBIANCE_CRICKETS; + GLOBALS._ambiance[23] = AMBIANCE_CRICKETS; + GLOBALS._ambiance[26] = AMBIANCE_SEAHALFVOLUME; + GLOBALS._ambiance[27] = AMBIANCE_CRICKETS; + GLOBALS._ambiance[28] = AMBIANCE_CRICKETSWIND; + GLOBALS._ambiance[31] = AMBIANCE_CRICKETS; + GLOBALS._ambiance[33] = AMBIANCE_SEA; + GLOBALS._ambiance[35] = AMBIANCE_SEA; + GLOBALS._ambiance[36] = AMBIANCE_CRICKETS; + GLOBALS._ambiance[37] = AMBIANCE_CRICKETS; + GLOBALS._ambiance[40] = AMBIANCE_CRICKETS; + GLOBALS._ambiance[41] = AMBIANCE_CRICKETS; + GLOBALS._ambiance[42] = AMBIANCE_CRICKETS; + GLOBALS._ambiance[45] = AMBIANCE_CRICKETS; + GLOBALS._ambiance[51] = AMBIANCE_CRICKETS; + GLOBALS._ambiance[52] = AMBIANCE_CRICKETSWIND1; + GLOBALS._ambiance[53] = AMBIANCE_CRICKETS; + GLOBALS._ambiance[54] = AMBIANCE_CRICKETS; + GLOBALS._ambiance[57] = AMBIANCE_WIND; + GLOBALS._ambiance[58] = AMBIANCE_WIND; + GLOBALS._ambiance[60] = AMBIANCE_WIND; + + + + // Create an event for the idle skipping + GLOBALS._hSkipIdle = CoroScheduler.createEvent(true, false); +} + +} // end of namespace Tony diff --git a/engines/tony/custom.h b/engines/tony/custom.h new file mode 100644 index 0000000000..0f1061e8cd --- /dev/null +++ b/engines/tony/custom.h @@ -0,0 +1,85 @@ +/* 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 Tony Tough source code + * + * Copyright (c) 1997-2003 Nayma Software + */ + +#ifndef TONY_CUSTOM_H +#define TONY_CUSTOM_H + +#include "common/str.h" +#include "tony/mpal/mpal.h" + +namespace Tony { + +using namespace MPAL; + +struct MusicFileEntry { + const char *_name; + int _sync; +}; + +#define INIT_CUSTOM_FUNCTION MapCustomFunctions + +#define BEGIN_CUSTOM_FUNCTION_MAP() \ + static void AssignError(int num) { \ + error("Custom function %u has been already assigned!", num); \ + } \ + void INIT_CUSTOM_FUNCTION(LPCUSTOMFUNCTION *lpMap, Common::String *lpStrMap) \ + { + +#define END_CUSTOM_FUNCTION_MAP() \ + } + +#define ASSIGN(num, func) \ + if (lpMap[num] != NULL) \ + AssignError(num); \ + lpMap[num] = func; \ + lpStrMap[num] = #func; + +class RMTony; +class RMPointer; +class RMGameBoxes; +class RMLocation; +class RMInventory; +class RMInput; + +void charsSaveAll(Common::OutSaveFile *f); +void charsLoadAll(Common::InSaveFile *f); +void mCharResetCodes(); +void saveChangedHotspot(Common::OutSaveFile *f); +void loadChangedHotspot(Common::InSaveFile *f); +void reapplyChangedHotspot(); + +void restoreMusic(CORO_PARAM); +void saveMusic(Common::OutSaveFile *f); +void loadMusic(Common::InSaveFile *f); + +void INIT_CUSTOM_FUNCTION(LPCUSTOMFUNCTION *lpMap, Common::String *lpStrMap); +void setupGlobalVars(RMTony *tony, RMPointer *ptr, RMGameBoxes *box, RMLocation *loc, RMInventory *inv, RMInput *input); + +#endif + +} // end of namespace Tony diff --git a/engines/tony/debugger.cpp b/engines/tony/debugger.cpp new file mode 100644 index 0000000000..85d9469519 --- /dev/null +++ b/engines/tony/debugger.cpp @@ -0,0 +1,130 @@ +/* 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. + * + */ + +#include "common/coroutines.h" +#include "tony/debugger.h" +#include "tony/globals.h" +#include "tony/tony.h" + +namespace Tony { + +Debugger::Debugger() : GUI::Debugger() { + DCmd_Register("continue", WRAP_METHOD(Debugger, Cmd_Exit)); + DCmd_Register("scene", WRAP_METHOD(Debugger, Cmd_Scene)); + DCmd_Register("dirty_rects", WRAP_METHOD(Debugger, Cmd_DirtyRects)); +} + +static int strToInt(const char *s) { + if (!*s) + // No string at all + return 0; + else if (toupper(s[strlen(s) - 1]) != 'H') + // Standard decimal string + return atoi(s); + + // Hexadecimal string + uint tmp = 0; + int read = sscanf(s, "%xh", &tmp); + if (read < 1) + error("strToInt failed on string \"%s\"", s); + return (int)tmp; +} + +/** + * Support process for changing the scene + */ +struct ChangeSceneDetails { + int sceneNumber; + int x; + int y; +}; + +void DebugChangeScene(CORO_PARAM, const void *param) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + uint32 result; + const ChangeSceneDetails *details = (const ChangeSceneDetails *)param; + RMPoint scenePos(details->x, details->y); + + CORO_BEGIN_CODE(_ctx); + + CORO_INVOKE_2(g_vm->getEngine()->unloadLocation, false, &result); + + g_vm->getEngine()->loadLocation(details->sceneNumber, scenePos, RMPoint(-1, -1)); + + mainEnableGUI(); + + CORO_END_CODE; +} + + +/** + * This command loads up the specified new scene number + */ +bool Debugger::Cmd_Scene(int argc, const char **argv) { + if (argc < 2) { + DebugPrintf("Usage: %s <scene number> [<x> <y>]\n", argv[0]); + return true; + } + + int sceneNumber = strToInt(argv[1]); + if (sceneNumber >= g_vm->_theBoxes.getLocBoxesCount()) { + DebugPrintf("Invalid scene\n"); + return true; + } + + RMPoint scenePos; + if (argc >= 4) { + scenePos._x = strToInt(argv[2]); + scenePos._y = strToInt(argv[3]); + } else { + // Get the box areas for the scene, and choose one so as to have a default + // position for Tony that will be in the walkable areas + RMBoxLoc *box = g_vm->_theBoxes.getBoxes(sceneNumber); + scenePos.set(box->_boxes[0]._hotspot[0]._hotx, box->_boxes[0]._hotspot[0]._hoty); + } + + // Set up a process to change the scene + ChangeSceneDetails details; + details.sceneNumber = sceneNumber; + details.x = scenePos._x; + details.y = scenePos._y; + CoroScheduler.createProcess(DebugChangeScene, &details, sizeof(ChangeSceneDetails)); + + return false; +} + +/** + * Turns showing dirty rects on or off + */ +bool Debugger::Cmd_DirtyRects(int argc, const char **argv) { + if (argc != 2) { + DebugPrintf("Usage; %s [on | off]\n", argv[0]); + return true; + } else { + g_vm->_window.showDirtyRects(strcmp(argv[1], "on") == 0); + return false; + } +} + +} // End of namespace Tony diff --git a/engines/tony/debugger.h b/engines/tony/debugger.h new file mode 100644 index 0000000000..85ba9d75b6 --- /dev/null +++ b/engines/tony/debugger.h @@ -0,0 +1,43 @@ +/* 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. + * + */ + +#ifndef TONY_DEBUGGER_H +#define TONY_DEBUGGER_H + +#include "common/scummsys.h" +#include "gui/debugger.h" + +namespace Tony { + +class Debugger : public GUI::Debugger { +public: + Debugger(); + virtual ~Debugger() {} + +protected: + bool Cmd_Scene(int argc, const char **argv); + bool Cmd_DirtyRects(int argc, const char **argv); +}; + +} // End of namespace Tony + +#endif diff --git a/engines/tony/detection.cpp b/engines/tony/detection.cpp new file mode 100644 index 0000000000..8e6d5a64c3 --- /dev/null +++ b/engines/tony/detection.cpp @@ -0,0 +1,194 @@ +/* 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. + * + * + */ + +#include "base/plugins.h" + +#include "common/memstream.h" +#include "engines/advancedDetector.h" +#include "common/system.h" +#include "graphics/colormasks.h" +#include "graphics/surface.h" + +#include "tony/tony.h" +#include "tony/game.h" + +namespace Tony { + +enum { + GF_COMPRESSED = (1 << 0) +}; + +struct TonyGameDescription { + ADGameDescription desc; +}; + +uint32 TonyEngine::getFeatures() const { + return _gameDescription->desc.flags; +} + +Common::Language TonyEngine::getLanguage() const { + return _gameDescription->desc.language; +} + +bool TonyEngine::getIsDemo() const { + return _gameDescription->desc.flags & ADGF_DEMO; +} + +bool TonyEngine::isCompressed() const { + return _gameDescription->desc.flags & GF_COMPRESSED; +} + +} // End of namespace Tony + +static const PlainGameDescriptor tonyGames[] = { + {"tony", "Tony Tough and the Night of Roasted Moths"}, + {0, 0} +}; + +#include "tony/detection_tables.h" + +class TonyMetaEngine : public AdvancedMetaEngine { +public: + TonyMetaEngine() : AdvancedMetaEngine(Tony::gameDescriptions, sizeof(Tony::TonyGameDescription), tonyGames) { + } + + virtual const char *getName() const { + return "Tony Engine"; + } + + virtual const char *getOriginalCopyright() const { + return "Tony Engine (C) Protonic Interactive"; + } + + virtual bool hasFeature(MetaEngineFeature f) const; + virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const; + virtual SaveStateList listSaves(const char *target) const; + virtual int getMaximumSaveSlot() const; + virtual void removeSaveState(const char *target, int slot) const; + SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const; +}; + +bool TonyMetaEngine::hasFeature(MetaEngineFeature f) const { + return + (f == kSupportsListSaves) || + (f == kSupportsLoadingDuringStartup) || + (f == kSupportsDeleteSave) || + (f == kSavesSupportMetaInfo) || + (f == kSavesSupportThumbnail); +} + +bool Tony::TonyEngine::hasFeature(EngineFeature f) const { + return + (f == kSupportsRTL) || + (f == kSupportsLoadingDuringRuntime) || + (f == kSupportsSavingDuringRuntime); +} + +bool TonyMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const { + const Tony::TonyGameDescription *gd = (const Tony::TonyGameDescription *)desc; + if (gd) { + *engine = new Tony::TonyEngine(syst, gd); + } + return gd != 0; +} + +SaveStateList TonyMetaEngine::listSaves(const char *target) const { + Common::SaveFileManager *saveFileMan = g_system->getSavefileManager(); + Common::StringArray filenames; + Common::String saveDesc; + Common::String pattern = "tony.0??"; + + filenames = saveFileMan->listSavefiles(pattern); + sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) + + SaveStateList saveList; + for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) { + // Obtain the last 3 digits of the filename, since they correspond to the save slot + int slotNum = atoi(file->c_str() + file->size() - 3); + + if (slotNum >= 0 && slotNum <= 999) { + byte thumbnailData[160 * 120 * 2]; + Common::String saveName; + byte difficulty; + + if (Tony::RMOptionScreen::loadThumbnailFromSaveState(slotNum, thumbnailData, saveName, difficulty)) { + // Add the save name to the savegame list + saveList.push_back(SaveStateDescriptor(slotNum, saveName)); + } + } + } + + return saveList; +} + +int TonyMetaEngine::getMaximumSaveSlot() const { + return 99; +} + +void TonyMetaEngine::removeSaveState(const char *target, int slot) const { + Common::String filename = Tony::TonyEngine::getSaveStateFileName(slot); + + g_system->getSavefileManager()->removeSavefile(filename); +} + +SaveStateDescriptor TonyMetaEngine::querySaveMetaInfos(const char *target, int slot) const { + Common::String saveName; + byte difficulty; + byte thumbData[160 * 120 * 2]; + + if (Tony::RMOptionScreen::loadThumbnailFromSaveState(slot, thumbData, saveName, difficulty)) { + // Convert the 565 thumbnail data to the needed overlay format + Common::MemoryReadStream thumbStream(thumbData, 160 * 120 * 2); + Graphics::PixelFormat destFormat = g_system->getOverlayFormat(); + Graphics::Surface *to = new Graphics::Surface(); + to->create(160, 120, destFormat); + + OverlayColor *pixels = (OverlayColor *)to->pixels; + for (int y = 0; y < to->h; ++y) { + for (int x = 0; x < to->w; ++x) { + uint8 r, g, b; + Graphics::colorToRGB<Graphics::ColorMasks<555> >(thumbStream.readUint16LE(), r, g, b); + + // converting to current OSystem Color + *pixels++ = destFormat.RGBToColor(r, g, b); + } + } + + // Create the return descriptor + SaveStateDescriptor desc(slot, saveName); + desc.setDeletableFlag(true); + desc.setWriteProtectedFlag(false); + desc.setThumbnail(to); + + return desc; + } + + return SaveStateDescriptor(); +} + + +#if PLUGIN_ENABLED_DYNAMIC(TONY) +REGISTER_PLUGIN_DYNAMIC(TONY, PLUGIN_TYPE_ENGINE, TonyMetaEngine); +#else +REGISTER_PLUGIN_STATIC(TONY, PLUGIN_TYPE_ENGINE, TonyMetaEngine); +#endif diff --git a/engines/tony/detection_tables.h b/engines/tony/detection_tables.h new file mode 100644 index 0000000000..d2bd81f083 --- /dev/null +++ b/engines/tony/detection_tables.h @@ -0,0 +1,178 @@ +/* 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. + * + */ + +namespace Tony { + +static const TonyGameDescription gameDescriptions[] = { + { + // Tony Tough English + { + "tony", + 0, + { + // TODO: AdvancedDetector seems to have a problem where it thinks data1.cab is unrecognized. + // Is it perhaps because the Agos engine also has detection entries for data1.cab? + {"data1.cab", 0, "ce82907242166bfb594d97bdb68f96d2", 4350}, + /*{"roasted.mpr", 0, "06203dbbc85fdd1e6dc8fc211c1a6207", 135911071}, + {"roasted.mpc", 0, "57c4a3860cf899443c357e0078ea6f49", 366773},*/ + AD_LISTEND + }, + Common::EN_ANY, + Common::kPlatformWindows, + ADGF_NO_FLAGS, + GUIO1(GUIO_NONE) + }, + }, + + { + // Tony Tough English Demo + { + "tony", + "Extracted Demo", + { + {"roasted.mpr", 0, "06203dbbc85fdd1e6dc8fc211c1a6207", 14972409}, + {"roasted.mpc", 0, "1e247922ec869712bfd96625bc4d3c7c", 39211}, + AD_LISTEND + }, + Common::EN_ANY, + Common::kPlatformWindows, + ADGF_DEMO, + GUIO1(GUIO_NONE) + }, + }, + + { + // Tony Tough English Demo (Compressed) + { + "tony", + "Demo", + { + {"data1.cab", 0, "7d8b6d308f96aee3968ad7910fb11e6d", 58660608}, + AD_LISTEND + }, + Common::EN_ANY, + Common::kPlatformWindows, + ADGF_DEMO | GF_COMPRESSED, + GUIO1(GUIO_NONE) + }, + }, + { + // Tony Tough French "Collection Aventure" provided by Strangerke + { + "tony", + 0, + { + {"roasted.mpr", 0, "06203dbbc85fdd1e6dc8fc211c1a6207", 135911071}, + {"roasted.mpc", 0, "e890c6a41238827bdfa9874a65618b69", 374135}, + AD_LISTEND + }, + Common::FR_FRA, + Common::kPlatformWindows, + ADGF_NO_FLAGS, + GUIO1(GUIO_NONE) + }, + }, + { + // Tony Tough German "Shoe Box" provided by Strangerke + { + "tony", + 0, + { + {"roasted.mpr", 0, "06203dbbc85fdd1e6dc8fc211c1a6207", 135911071}, + {"roasted.mpc", 0, "ccf7ab939a34de1b13df538596431684", 389554}, + AD_LISTEND + }, + Common::DE_DEU, + Common::kPlatformWindows, + ADGF_NO_FLAGS, + GUIO1(GUIO_NONE) + }, + }, + { + // Tony Tough Italian provided by Fabio Barzagli + { + "tony", + 0, + { + {"roasted.mpr", 0, "06203dbbc85fdd1e6dc8fc211c1a6207", 135911071}, + {"roasted.mpc", 0, "1dc896cdb945170d7408598f803411c1", 380001}, + AD_LISTEND + }, + Common::IT_ITA, + Common::kPlatformWindows, + ADGF_NO_FLAGS, + GUIO1(GUIO_NONE) + }, + }, + { + // Tony Tough Polish provided by Fabio Barzagli + { + "tony", + 0, + { + {"roasted.mpr", 0, "06203dbbc85fdd1e6dc8fc211c1a6207", 135911071}, + {"roasted.mpc", 0, "89733ea710669acc8e7900b115f4afef", 389625}, + AD_LISTEND + }, + Common::PL_POL, + Common::kPlatformWindows, + ADGF_NO_FLAGS, + GUIO1(GUIO_NONE) + }, + }, + { + // Tony Tough German "Gamestar" provided in bug #3566035 + { + "tony", + 0, + { + {"roasted.mpr", 0, "06203dbbc85fdd1e6dc8fc211c1a6207", 135911071}, + {"roasted.mpc", 0, "187de6f88f4083808cb66342ab55a7fd", 389904}, + AD_LISTEND + }, + Common::DE_DEU, + Common::kPlatformWindows, + ADGF_NO_FLAGS, + GUIO1(GUIO_NONE) + }, + }, + { + // Tony Tough Czech provided in bug #3565765 + { + "tony", + 0, + { + // {"data1.cab", 0, "c6d5dd8f0c1241a6e3f7861b7f27bf7b", 4350}, + {"roasted.mpr", 0, "06203dbbc85fdd1e6dc8fc211c1a6207", 135911071}, + {"roasted.mpc", 0, "a8283a101878f3ca105f1f83f07e2c40", 386491}, + AD_LISTEND + }, + Common::CZ_CZE, + Common::kPlatformWindows, + ADGF_NO_FLAGS, + GUIO1(GUIO_NONE) + }, + }, + { AD_TABLE_END_MARKER } +}; + +} // End of namespace Tony diff --git a/engines/tony/font.cpp b/engines/tony/font.cpp new file mode 100644 index 0000000000..fa018b4464 --- /dev/null +++ b/engines/tony/font.cpp @@ -0,0 +1,1179 @@ +/* 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 Tony Tough source code + * + * Copyright (c) 1997-2003 Nayma Software + */ + +#include "common/textconsole.h" +#include "tony/mpal/mpalutils.h" +#include "tony/font.h" +#include "tony/input.h" +#include "tony/inventory.h" +#include "tony/loc.h" +#include "tony/tony.h" + +namespace Tony { + +/****************************************************************************\ +* RMFont Methods +\****************************************************************************/ + +RMFont::RMFont() { + _letter = NULL; + _nLetters = _fontDimx = _fontDimy = _dimx = _dimy = 0; +} + +RMFont::~RMFont() { + unload(); +} + +void RMFont::load(const byte *buf, int nChars, int dimx, int dimy, uint32 palResID) { + _letter = new RMGfxSourceBuffer8RLEByte[nChars]; + + // Initialize the fonts + for (int i = 0; i < nChars; i++) { + // Initialize the buffer with the letters + _letter[i].init(buf + i * (dimx * dimy + 8) + 8, dimx, dimy); + _letter[i].loadPaletteWA(palResID); + } + + _fontDimx = dimx; + _fontDimy = dimy; + + _nLetters = nChars; +} + +void RMFont::load(uint32 resID, int nChars, int dimx, int dimy, uint32 palResID) { + RMRes res(resID); + + if ((int)res.size() < nChars * (dimy * dimx + 8)) + nChars = res.size() / (dimy * dimx + 8); + + load(res, nChars, dimx, dimy, palResID); +} + +void RMFont::unload() { + if (_letter != NULL) { + delete[] _letter; + _letter = NULL; + } +} + + +RMGfxPrimitive *RMFont::makeLetterPrimitive(byte bChar, int &nLength) { + RMFontPrimitive *prim; + + // Convert from character to glyph index + int nLett = convertToLetter(bChar); + assert(nLett < _nLetters); + + // Create primitive font + prim = new RMFontPrimitive(this); + prim->_nChar = nLett; + + // Get the length of the character in pixels + nLength = letterLength(bChar); + + return prim; +} + +void RMFont::draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim2) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + RMFontPrimitive *prim = (RMFontPrimitive *)prim2; + + CORO_BEGIN_CODE(_ctx); + + // Call the draw method of the letter assigned to the primitive + if (prim->_nChar != -1) + CORO_INVOKE_2(_letter[prim->_nChar].draw, bigBuf, prim); + + CORO_END_CODE; +} + +void RMFont::close() { + unload(); +} + +int RMFont::stringLen(const Common::String &text) { + if (text.empty()) + return letterLength('\0'); + + uint len = 0; + uint i; + for (i = 0; i < text.size() - 1; i++) + len += letterLength(text[i], text[i + 1]); + len += letterLength(text[i]); + + return len; +} + +int RMFont::stringLen(char bChar, char bNext) { + return letterLength(bChar, bNext); +} + +/****************************************************************************\ +* RMFontColor Methods +\****************************************************************************/ + +RMFontColor::RMFontColor() : RMFont() { + _fontR = _fontG = _fontB = 255; +} + +RMFontColor::~RMFontColor() { +} + +void RMFontColor::setBaseColor(byte r1, byte g1, byte b1) { + int r = (int)r1 << 16; + int g = (int)g1 << 16; + int b = (int)b1 << 16; + + int rstep = r / 14; + int gstep = g / 14; + int bstep = b / 14; + + byte pal[768 * 3]; + + // Check if we are already on the right color + if (_fontR == r1 && _fontG == g1 && _fontB == b1) + return; + + _fontR = r1; + _fontG = g1; + _fontB = b1; + + // Constructs a new palette for the font + for (int i = 1; i < 16; i++) { + pal[i * 3 + 0] = r >> 16; + pal[i * 3 + 1] = g >> 16; + pal[i * 3 + 2] = b >> 16; + + r -= rstep; + g -= gstep; + b -= bstep; + } + + pal[15 * 3 + 0] += 8; + pal[15 * 3 + 1] += 8; + pal[15 * 3 + 2] += 8; + + // Puts in all the letters + for (int i = 0; i < _nLetters; i++) + _letter[i].loadPaletteWA(pal); +} + +/***************************************************************************\ +* RMFontWithTables Methods +\****************************************************************************/ +int RMFontWithTables::convertToLetter(byte nChar) { + return _cTable[nChar]; +} + +int RMFontWithTables::letterLength(int nChar, int nNext) { + return (nChar != -1 ? _lTable[(byte)nChar] + _l2Table[(byte)nChar][(byte)nNext] : _lDefault); +} + +/***************************************************************************\ +* RMFontDialog Methods +\****************************************************************************/ + +void RMFontDialog::init() { + // bernie: Number of characters in the font + int nchars = + 112 // base + + 18 // polish + + 66 // russian + + 30 // czech + + 8 // french + + 5; // deutsch + + load(RES_F_PARL, nchars, 20, 20); + + // Initialize the font table + _lDefault = 13; + _hDefault = 18; + Common::fill(&_l2Table[0][0], &_l2Table[0][0] + (256 * 256), '\0'); + + for (int i = 0; i < 256; i++) { + _cTable[i] = g_vm->_cTableDialog[i]; + _lTable[i] = g_vm->_lTableDialog[i]; + } +} + + +/***************************************************************************\ +* RMFontMacc Methods +\****************************************************************************/ + +void RMFontMacc::init() { + // bernie: Number of characters in the font + int nchars = + 102 // base + + 18 // polish + + 66 // russian + + 30 // czech + + 8 // francais + + 5; // deutsch + + load(RES_F_MACC, nchars, 11, 16); + + // Default + _lDefault = 10; + _hDefault = 17; + Common::fill(&_l2Table[0][0], &_l2Table[0][0] + (256 * 256), '\0'); + + for (int i = 0; i < 256; i++) { + _cTable[i] = g_vm->_cTableMacc[i]; + _lTable[i] = g_vm->_lTableMacc[i]; + } +} + +/***************************************************************************\ +* RMFontCredits Methods +\****************************************************************************/ + +void RMFontCredits::init() { + // bernie: Number of characters in the font + int nchars = + 112 // base + + 18 // polish + + 66 // russian + + 30 // czech + + 8 // french + + 2; // deutsch + + load(RES_F_CREDITS, nchars, 27, 28, RES_F_CPAL); + + // Default + _lDefault = 10; + _hDefault = 28; + Common::fill(&_l2Table[0][0], &_l2Table[0][0] + (256 * 256), '\0'); + + for (int i = 0; i < 256; i++) { + _cTable[i] = g_vm->_cTableCred[i]; + _lTable[i] = g_vm->_lTableCred[i]; + } +} + + + +/***************************************************************************\ +* RMFontObj Methods +\****************************************************************************/ + +#define TOUPPER(a) ((a) >= 'a' && (a) <= 'z' ? (a) + 'A' - 'a' : (a)) +#define TOLOWER(a) ((a) >= 'A' && (a) <= 'Z' ? (a) + 'a' - 'A' : (a)) + +void RMFontObj::setBothCase(int nChar, int nNext, signed char spiazz) { + _l2Table[TOUPPER(nChar)][TOUPPER(nNext)] = spiazz; + _l2Table[TOUPPER(nChar)][TOLOWER(nNext)] = spiazz; + _l2Table[TOLOWER(nChar)][TOUPPER(nNext)] = spiazz; + _l2Table[TOLOWER(nChar)][TOLOWER(nNext)] = spiazz; +} + +void RMFontObj::init() { + //bernie: Number of characters in the font (solo maiuscolo) + int nchars = + 85 // base + + 9 // polish + + 33 // russian + + 15 // czech + + 0 // francais (no uppercase chars) + + 1; // deutsch + + load(RES_F_OBJ, nchars, 25, 30); + + // Initialize the font table + _lDefault = 26; + _hDefault = 30; + Common::fill(&_l2Table[0][0], &_l2Table[0][0] + (256 * 256), '\0'); + + for (int i = 0; i < 256; i++) { + _cTable[i] = g_vm->_cTableObj[i]; + _lTable[i] = g_vm->_lTableObj[i]; + } + + // Special case + setBothCase('C', 'C', 2); + setBothCase('A', 'T', -2); + setBothCase('R', 'S', 2); + setBothCase('H', 'I', -2); + setBothCase('T', 'S', 2); + setBothCase('O', 'R', 2); + setBothCase('O', 'L', 2); + setBothCase('O', 'G', 2); + setBothCase('Z', 'A', -1); + setBothCase('R', 'R', 1); + setBothCase('R', 'U', 3); +} + +/****************************************************************************\ +* RMText Methods +\****************************************************************************/ + +RMFontColor *RMText::_fonts[4] = { NULL, NULL, NULL, NULL }; + +void RMText::initStatics() { + Common::fill(&_fonts[0], &_fonts[4], (RMFontColor *)NULL); +} + +RMText::RMText() { + // Default color: white + _textR = _textG = _textB = 255; + + // Default length + _maxLineLength = 350; + + _bTrasp0 = true; + _aHorType = HCENTER; + _aVerType = VTOP; + setPriority(150); +} + +RMText::~RMText() { +} + +void RMText::unload() { + if (_fonts[0] != NULL) { + delete _fonts[0]; + delete _fonts[1]; + delete _fonts[2]; + delete _fonts[3]; + _fonts[0] = _fonts[1] = _fonts[2] = _fonts[3] = 0; + } +} + +void RMText::setMaxLineLength(int max) { + _maxLineLength = max; +} + +void RMText::removeThis(CORO_PARAM, bool &result) { + // Here we can do checks on the number of frames, time spent, etc. + result = true; +} + +void RMText::writeText(const Common::String &text, int nFont, int *time) { + // Initializes the font (only once) + if (_fonts[0] == NULL) { + _fonts[0] = new RMFontDialog; + _fonts[0]->init(); + _fonts[1] = new RMFontObj; + _fonts[1]->init(); + _fonts[2] = new RMFontMacc; + _fonts[2]->init(); + _fonts[3] = new RMFontCredits; + _fonts[3]->init(); + } + + writeText(text, _fonts[nFont], time); +} + +void RMText::writeText(Common::String text, RMFontColor *font, int *time) { + RMGfxPrimitive *prim; + + // Set the base color + font->setBaseColor(_textR, _textG, _textB); + + // Destroy the buffer before starting + destroy(); + + // If the string is empty, do nothing + if (text.empty()) + return; + + // Divide the words into lines. In this cycle, X contains the maximum length reached by a line, + // and the number of lines + Common::Array<Common::String> lines; + uint p = 0; + int j = 0; + int x = 0; + while (p < text.size()) { + j += font->stringLen(text[p]); + if (j > (((_aHorType == HLEFTPAR) && (lines.size() > 0)) ? _maxLineLength - 25 : _maxLineLength)) { + j -= font->stringLen(text[p], (p + 1 == text.size()) ? '\0' : text[p + 1]); + if (j > x) + x = j; + + // Back to the first usable space + // + // BERNIE: In the original, sentences containing words that exceed the + // width of a line caused discontinuation of the whole sentence. + // This workaround has the partial word broken up so it will still display + // + uint old_p = p; + while (text[p] != ' ' && text[p] != '-' && p > 0) + p--; + + if (p == 0) + p = old_p; + + // Check if there are any blanks to end + while ((text[p] == ' ' || text[p] == '-') && p + 1 < text.size()) + p++; + if (p == text.size()) + break; + lines.push_back(Common::String(text.c_str(), p)); + if (text[p] == ' ') + p++; + text = text.c_str() + p; + p = 0; + j = 0; + continue; + } + p++; + } + + if (j > x) + x = j; + + // Add the last line of text. + lines.push_back(text); + + x += 8; + + // Starting position for the surface: X1, Y + int width = x; + int height = (lines.size() - 1) * font->letterHeight() + font->_fontDimy; + + // Create the surface + create(width, height); + Common::fill(_buf, _buf + width * height * 2, 0); + + p = 0; + + int y = 0; + int numchar = 0; + for (uint i = 0; i < lines.size(); ++i) { + const Common::String &line = lines[i]; + + // Measure the length of the line + x = 0; + j = font->stringLen(line); + + switch (_aHorType) { + case HLEFT: + x = 0; + break; + + case HLEFTPAR: + if (i == 0) + x = 0; + else + x = 25; + break; + + case HCENTER: + x = width / 2 - j / 2; + break; + + case HRIGHT: + x = width - j - 1; + break; + } + + p = 0; + while (p < line.size()) { + if (line[p] == ' ') { + x += font->stringLen(line[p]); + p++; + continue; + } + + int len; + prim = font->makeLetterPrimitive(line[p], len); + prim->getDst()._x1 = x; + prim->getDst()._y1 = y; + addPrim(prim); + + numchar++; + + x += font->stringLen(line[p], (p + 1 == line.size()) ? '\0' : line[p + 1]); + p++; + } + p++; + y += font->letterHeight(); + } + + if (time != NULL) + *time = 1000 + numchar * (11 - GLOBALS._nCfgTextSpeed) * 14; +} + +void RMText::clipOnScreen(RMGfxPrimitive *prim) { + // Don't let it go outside the screen + if (prim->getDst()._x1 < 5) + prim->getDst()._x1 = 5; + if (prim->getDst()._y1 < 5) + prim->getDst()._y1 = 5; + if (prim->getDst()._x1 + _dimx > 635) + prim->getDst()._x1 = 635 - _dimx; + if (prim->getDst()._y1 + _dimy > 475) + prim->getDst()._y1 = 475 - _dimy; +} + +void RMText::draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + // Horizontally + if (_aHorType == HCENTER) + prim->getDst().topLeft() -= RMPoint(_dimx / 2, 0); + else if (_aHorType == HRIGHT) + prim->getDst().topLeft() -= RMPoint(_dimx, 0); + + + // Vertically + if (_aVerType == VTOP) { + + } else if (_aVerType == VCENTER) { + prim->getDst()._y1 -= _dimy / 2; + + } else if (_aVerType == VBOTTOM) { + prim->getDst()._y1 -= _dimy; + } + + clipOnScreen(prim); + + CORO_INVOKE_2(RMGfxWoodyBuffer::draw, bigBuf, prim); + + CORO_END_CODE; +} + +/** + * Set the alignment type + */ +void RMText::setAlignType(HorAlign aHor, VerAlign aVer) { + _aHorType = aHor; + _aVerType = aVer; +} + +/** + * Set the base color + */ +void RMText::setColor(byte r, byte g, byte b) { + _textR = r; + _textG = g; + _textB = b; +} + +/****************************************************************************\ +* RMTextDialog Methods +\****************************************************************************/ + +RMTextDialog::RMTextDialog() : RMText() { + _time = _startTime = 0; + _dst = RMPoint(0, 0); + + _bSkipStatus = true; + _bShowed = true; + _bForceTime = false; + _bForceNoTime = false; + _bAlwaysDisplay = false; + _bNoTab = false; + _hCustomSkip = CORO_INVALID_PID_VALUE; + _hCustomSkip2 = CORO_INVALID_PID_VALUE; + _input = NULL; + + // Create the event for displaying the end + _hEndDisplay = CoroScheduler.createEvent(false, false); +} + +RMTextDialog::~RMTextDialog() { + CoroScheduler.closeEvent(_hEndDisplay); +} + +void RMTextDialog::show() { + _bShowed = true; +} + +void RMTextDialog::hide(CORO_PARAM) { + _bShowed = false; +} + +void RMTextDialog::writeText(const Common::String &text, int font, int *time) { + RMText::writeText(text, font, &_time); + + if (time != NULL) + *time = _time; +} + +void RMTextDialog::writeText(const Common::String &text, RMFontColor *font, int *time) { + RMText::writeText(text, font, &_time); + + if (time != NULL) + *time = _time; +} + + +void RMTextDialog::setSkipStatus(bool bEnabled) { + _bSkipStatus = bEnabled; +} + +void RMTextDialog::forceTime() { + _bForceTime = true; +} + +void RMTextDialog::forceNoTime() { + _bForceNoTime = true; +} + +void RMTextDialog::setNoTab() { + _bNoTab = true; +} + +void RMTextDialog::setForcedTime(uint32 dwTime) { + _time = dwTime; +} + +void RMTextDialog::setAlwaysDisplay() { + _bAlwaysDisplay = true; +} + +void RMTextDialog::removeThis(CORO_PARAM, bool &result) { + CORO_BEGIN_CONTEXT; + bool expired; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + // Presume successful result + result = true; + + // Don't erase the background + if (_bSkipStatus) { + if (!(GLOBALS._bCfgDubbing && _hCustomSkip2 != CORO_INVALID_PID_VALUE)) { + if (GLOBALS._bCfgTimerizedText) { + if (!_bForceNoTime) { + if (g_vm->getTime() > (uint32)_time + _startTime) + return; + } + } + } + + if (!_bNoTab) { + if (g_vm->getEngine()->getInput().getAsyncKeyState(Common::KEYCODE_TAB)) + return; + } + + if (!_bNoTab) { + if (_input) { + if (_input->mouseLeftClicked() || _input->mouseRightClicked()) + return; + } + } + } + // Erase the background + else if (!(GLOBALS._bCfgDubbing && _hCustomSkip2 != CORO_INVALID_PID_VALUE)) { + if (!_bForceNoTime) { + if (g_vm->getTime() > (uint32)_time + _startTime) + return; + } + } + + // If time is forced + if (_bForceTime) { + if (g_vm->getTime() > (uint32)_time + _startTime) + return; + } + + if (_hCustomSkip != CORO_INVALID_PID_VALUE) { + CORO_INVOKE_3(CoroScheduler.waitForSingleObject, _hCustomSkip, 0, &_ctx->expired); + // == WAIT_OBJECT_0 + if (!_ctx->expired) + return; + } + + if (GLOBALS._bCfgDubbing && _hCustomSkip2 != CORO_INVALID_PID_VALUE) { + CORO_INVOKE_3(CoroScheduler.waitForSingleObject, _hCustomSkip2, 0, &_ctx->expired); + // == WAIT_OBJECT_0 + if (!_ctx->expired) + return; + } + + result = false; + + CORO_END_CODE; +} + +void RMTextDialog::unregister() { + RMGfxTask::unregister(); + assert(_nInList == 0); + CoroScheduler.setEvent(_hEndDisplay); +} + +void RMTextDialog::draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + if (_startTime == 0) + _startTime = g_vm->getTime(); + + if (_bShowed) { + if (GLOBALS._bShowSubtitles || _bAlwaysDisplay) { + prim->getDst().topLeft() = _dst; + CORO_INVOKE_2(RMText::draw, bigBuf, prim); + } + } + + CORO_END_CODE; +} + +void RMTextDialog::setCustomSkipHandle(uint32 hCustom) { + _hCustomSkip = hCustom; +} + +void RMTextDialog::setCustomSkipHandle2(uint32 hCustom) { + _hCustomSkip2 = hCustom; +} + +void RMTextDialog::waitForEndDisplay(CORO_PARAM) { + CoroScheduler.waitForSingleObject(coroParam, _hEndDisplay, CORO_INFINITE); +} + +void RMTextDialog::setInput(RMInput *input) { + _input = input; +} + +/** + * Set the position + */ +void RMTextDialog::setPosition(const RMPoint &pt) { + _dst = pt; +} + +/****************************************************************************\ +* RMTextDialogScrolling Methods +\****************************************************************************/ + +RMTextDialogScrolling::RMTextDialogScrolling() { + _curLoc = NULL; +} + +RMTextDialogScrolling::RMTextDialogScrolling(RMLocation *loc) { + _curLoc = loc; + _startScroll = loc->scrollPosition(); +} + +RMTextDialogScrolling::~RMTextDialogScrolling() { +} + +void RMTextDialogScrolling::draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim) { + CORO_BEGIN_CONTEXT; + RMPoint curDst; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + _ctx->curDst = _dst; + + if (_curLoc != NULL) + _dst -= _curLoc->scrollPosition() - _startScroll; + + CORO_INVOKE_2(RMTextDialog::draw, bigBuf, prim); + + _dst = _ctx->curDst; + + CORO_END_CODE; +} + +void RMTextDialogScrolling::clipOnScreen(RMGfxPrimitive *prim) { + // We must not do anything! +} + + +/****************************************************************************\ +* RMTextItemName Methods +\****************************************************************************/ + +RMTextItemName::RMTextItemName() : RMText() { + _item = NULL; + setPriority(220); +} + +RMTextItemName::~RMTextItemName() { +} + +void RMTextItemName::doFrame(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMLocation &loc, RMPointer &ptr, RMInventory &inv) { + CORO_BEGIN_CONTEXT; + RMItem *lastItem; + uint32 hThread; + CORO_END_CONTEXT(_ctx); + + Common::String itemName; + + CORO_BEGIN_CODE(_ctx); + + _ctx->lastItem = _item; + + // Adds to the list if there is need + if (!_nInList) + bigBuf.addPrim(new RMGfxPrimitive(this)); + + // Update the scrolling co-ordinates + _curscroll = loc.scrollPosition(); + + // Check if we are on the inventory + if (inv.itemInFocus(_mpos)) + _item = inv.whichItemIsIn(_mpos); + else + _item = loc.whichItemIsIn(_mpos); + + // If there an item, get its name + if (_item != NULL) + _item->getName(itemName); + + // Write it + writeText(itemName, 1); + + // Handle the change If the selected item is different from the previous one + if (_ctx->lastItem != _item) { + if (_item == NULL) + ptr.setSpecialPointer(RMPointer::PTR_NONE); + else { + _ctx->hThread = mpalQueryDoAction(20, _item->mpalCode(), 0); + if (_ctx->hThread == CORO_INVALID_PID_VALUE) + ptr.setSpecialPointer(RMPointer::PTR_NONE); + else + CORO_INVOKE_2(CoroScheduler.waitForSingleObject, _ctx->hThread, CORO_INFINITE); + } + } + + CORO_END_CODE; +} + + +void RMTextItemName::draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + // If there is no text, it's pointless to continue + if (_buf == NULL) + return; + + // Set the destination coordinates of the mouse + prim->getDst().topLeft() = _mpos - RMPoint(0, 30); + + CORO_INVOKE_2(RMText::draw, bigBuf, prim); + + CORO_END_CODE; +} + +RMPoint RMTextItemName::getHotspot() { + if (_item == NULL) + return _mpos + _curscroll; + else + return _item->getHotspot(); +} + +RMItem *RMTextItemName::getSelectedItem() { + return _item; +} + +bool RMTextItemName::isItemSelected() { + return _item != NULL; +} + +void RMTextItemName::setMouseCoord(const RMPoint &m) { + _mpos = m; +} + +void RMTextItemName::removeThis(CORO_PARAM, bool &result) { + result = true; +} + +/****************************************************************************\ +* RMDialogChoice Methods +\****************************************************************************/ + +RMDialogChoice::RMDialogChoice() { + RMResRaw dlg1(RES_I_DLGTEXT); + RMResRaw dlg2(RES_I_DLGTEXTLINE); + RMRes dlgpal(RES_I_DLGTEXTPAL); + + _dlgText.init(dlg1, dlg1.width(), dlg1.height()); + _dlgTextLine.init(dlg2, dlg2.width(), dlg2.height()); + + _dlgText.loadPaletteWA(dlgpal); + _dlgTextLine.loadPaletteWA(dlgpal); + + _hUnreg = CoroScheduler.createEvent(false, false); + _bRemoveFromOT = false; + + _curAdded = 0; + _bShow = false; +} + +RMDialogChoice::~RMDialogChoice() { + CoroScheduler.closeEvent(_hUnreg); +} + +void RMDialogChoice::unregister() { + RMGfxWoodyBuffer::unregister(); + assert(!_nInList); + CoroScheduler.pulseEvent(_hUnreg); + + _bRemoveFromOT = false; +} + +void RMDialogChoice::init() { + _numChoices = 0; + _drawedStrings = NULL; + _ptDrawStrings = NULL; + _curSelection = -1; + + create(640, 477); + setPriority(140); +} + + +void RMDialogChoice::close() { + if (_drawedStrings != NULL) { + delete[] _drawedStrings; + _drawedStrings = NULL; + } + + if (_ptDrawStrings != NULL) { + delete[] _ptDrawStrings; + _ptDrawStrings = NULL; + } + + destroy(); +} + +void RMDialogChoice::setNumChoices(int num) { + _numChoices = num; + _curAdded = 0; + + // Allocate space for drawn strings + _drawedStrings = new RMText[num]; + _ptDrawStrings = new RMPoint[num]; + + // Initialization + for (int i = 0; i < _numChoices; i++) { + _drawedStrings[i].setColor(0, 255, 0); + _drawedStrings[i].setAlignType(RMText::HLEFTPAR, RMText::VTOP); + _drawedStrings[i].setMaxLineLength(600); + _drawedStrings[i].setPriority(10); + } +} + +void RMDialogChoice::addChoice(const Common::String &string) { + // Draw the string + assert(_curAdded < _numChoices); + _drawedStrings[_curAdded++].writeText(string, 0); +} + +void RMDialogChoice::prepare(CORO_PARAM) { + CORO_BEGIN_CONTEXT; + int i; + RMPoint ptPos; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + addPrim(new RMGfxPrimitive(&_dlgText, RMPoint(0, 0))); + addPrim(new RMGfxPrimitive(&_dlgTextLine, RMPoint(0, 155))); + addPrim(new RMGfxPrimitive(&_dlgTextLine, RMPoint(0, 155 + 83))); + addPrim(new RMGfxPrimitive(&_dlgTextLine, RMPoint(0, 155 + 83 + 83))); + addPrim(new RMGfxPrimitive(&_dlgTextLine, RMPoint(0, 155 + 83 + 83 + 83))); + + _ctx->ptPos.set(20, 90); + + for (_ctx->i = 0; _ctx->i < _numChoices; _ctx->i++) { + addPrim(new RMGfxPrimitive(&_drawedStrings[_ctx->i], _ctx->ptPos)); + _ptDrawStrings[_ctx->i] = _ctx->ptPos; + _ctx->ptPos.offset(0, _drawedStrings[_ctx->i].getDimy() + 15); + } + + CORO_INVOKE_0(drawOT); + clearOT(); + + _ptDrawPos.set(0, 480 - _ctx->ptPos._y); + + CORO_END_CODE; +} + +void RMDialogChoice::setSelected(CORO_PARAM, int pos) { + CORO_BEGIN_CONTEXT; + RMGfxBox box; + RMRect rc; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + if (pos == _curSelection) + return; + + _ctx->box.setPriority(5); + + if (_curSelection != -1) { + _ctx->box.setColor(0xCC, 0xCC, 0xFF); + _ctx->rc.topLeft() = RMPoint(18, _ptDrawStrings[_curSelection]._y); + _ctx->rc.bottomRight() = _ctx->rc.topLeft() + RMPoint(597, _drawedStrings[_curSelection].getDimy()); + addPrim(new RMGfxPrimitive(&_ctx->box, _ctx->rc)); + + addPrim(new RMGfxPrimitive(&_drawedStrings[_curSelection], _ptDrawStrings[_curSelection])); + CORO_INVOKE_0(drawOT); + clearOT(); + } + + if (pos != -1) { + _ctx->box.setColor(100, 100, 100); + _ctx->rc.topLeft() = RMPoint(18, _ptDrawStrings[pos]._y); + _ctx->rc.bottomRight() = _ctx->rc.topLeft() + RMPoint(597, _drawedStrings[pos].getDimy()); + addPrim(new RMGfxPrimitive(&_ctx->box, _ctx->rc)); + addPrim(new RMGfxPrimitive(&_drawedStrings[pos], _ptDrawStrings[pos])); + } + + CORO_INVOKE_0(drawOT); + clearOT(); + + _curSelection = pos; + + CORO_END_CODE; +} + +void RMDialogChoice::show(CORO_PARAM, RMGfxTargetBuffer *bigBuf) { + CORO_BEGIN_CONTEXT; + RMPoint destpt; + int deltay; + int starttime; + int elaps; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + CORO_INVOKE_0(prepare); + _bShow = false; + + if (!_nInList && bigBuf != NULL) + bigBuf->addPrim(new RMGfxPrimitive(this)); + + if (0) { + _bShow = true; + } else { + _ctx->starttime = g_vm->getTime(); + _ctx->deltay = 480 - _ptDrawPos._y; + _ctx->destpt = _ptDrawPos; + _ptDrawPos.set(0, 480); + + if (!_nInList && bigBuf != NULL) + bigBuf->addPrim(new RMGfxPrimitive(this)); + _bShow = true; + + _ctx->elaps = 0; + while (_ctx->elaps < 700) { + CORO_INVOKE_2(CoroScheduler.waitForSingleObject, g_vm->_hEndOfFrame, CORO_INFINITE); + _ctx->elaps = g_vm->getTime() - _ctx->starttime; + _ptDrawPos._y = 480 - ((_ctx->deltay * 100) / 700 * _ctx->elaps) / 100; + } + + _ptDrawPos._y = _ctx->destpt._y; + } + + CORO_END_CODE; +} + +void RMDialogChoice::draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + if (_bShow == false) + return; + + prim->setDst(_ptDrawPos); + CORO_INVOKE_2(RMGfxSourceBuffer16::draw, bigBuf, prim); + + CORO_END_CODE; +} + + +void RMDialogChoice::hide(CORO_PARAM) { + CORO_BEGIN_CONTEXT; + int deltay; + int starttime; + int elaps; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + if (1) { + _ctx->starttime = g_vm->getTime(); + + _ctx->deltay = 480 - _ptDrawPos._y; + _ctx->elaps = 0; + while (_ctx->elaps < 700) { + CORO_INVOKE_2(CoroScheduler.waitForSingleObject, g_vm->_hEndOfFrame, CORO_INFINITE); + _ctx->elaps = g_vm->getTime() - _ctx->starttime; + _ptDrawPos._y = 480 - ((_ctx->deltay * 100) / 700 * (700 - _ctx->elaps)) / 100; + } + } + + _bShow = false; + _bRemoveFromOT = true; + CORO_INVOKE_2(CoroScheduler.waitForSingleObject, _hUnreg, CORO_INFINITE); + + CORO_END_CODE; +} + + +void RMDialogChoice::removeThis(CORO_PARAM, bool &result) { + result = _bRemoveFromOT; +} + +void RMDialogChoice::doFrame(CORO_PARAM, RMPoint ptMousePos) { + CORO_BEGIN_CONTEXT; + int i; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + if (ptMousePos._y > _ptDrawPos._y) { + for (_ctx->i = 0; _ctx->i < _numChoices; _ctx->i++) { + if ((ptMousePos._y >= _ptDrawPos._y + _ptDrawStrings[_ctx->i]._y) && (ptMousePos._y < _ptDrawPos._y + _ptDrawStrings[_ctx->i]._y + _drawedStrings[_ctx->i].getDimy())) { + CORO_INVOKE_1(setSelected, _ctx->i); + break; + } + } + + if (_ctx->i == _numChoices) + CORO_INVOKE_1(setSelected, -1); + } + + CORO_END_CODE; +} + +int RMDialogChoice::getSelection() { + return _curSelection; +} + +} // End of namespace Tony diff --git a/engines/tony/font.h b/engines/tony/font.h new file mode 100644 index 0000000000..13c1ddf268 --- /dev/null +++ b/engines/tony/font.h @@ -0,0 +1,379 @@ +/* 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 Tony Tough source code + * + * Copyright (c) 1997-2003 Nayma Software + */ + +#ifndef TONY_FONT_H +#define TONY_FONT_H + +#include "common/system.h" +#include "common/coroutines.h" +#include "tony/gfxcore.h" +#include "tony/resid.h" + +namespace Tony { + +class RMInput; +class RMInventory; +class RMItem; +class RMLoc; +class RMLocation; +class RMPointer; + +/** + * Manages a font, in which there is a different surface for each letter + */ +class RMFont : public RMGfxTaskSetPrior { +protected: + int _nLetters; + RMGfxSourceBuffer8RLEByte *_letter; +public: + int _fontDimx, _fontDimy; + +private: + int _dimx, _dimy; + + class RMFontPrimitive : public RMGfxPrimitive { + public: + RMFontPrimitive() : RMGfxPrimitive() { _nChar = 0; } + RMFontPrimitive(RMGfxTask *task) : RMGfxPrimitive(task) { _nChar = 0; } + virtual ~RMFontPrimitive() { } + virtual RMGfxPrimitive *duplicate() { + return new RMFontPrimitive(*this); + } + + int _nChar; + }; + +protected: + // Loads the font + void load(uint32 resID, int nChars, int dimx, int dimy, uint32 palResID = RES_F_PAL); + void load(const byte *buf, int nChars, int dimx, int dimy, uint32 palResID = RES_F_PAL); + + // Remove the font + void unload(); + +protected: + // Conversion form character to font index + virtual int convertToLetter(byte nChar) = 0; + + // Character width + virtual int letterLength(int nChar, int nNext = 0) = 0; + +public: + virtual int letterHeight() = 0; + +public: + RMFont(); + virtual ~RMFont(); + + // Initialization and closing + virtual void init() = 0; + virtual void close(); + + // Drawing + virtual void draw(CORO_PARAM, RMGfxTargetBuffer &bigBug, RMGfxPrimitive *prim); + + // Create a primitive for a letter + RMGfxPrimitive *makeLetterPrimitive(byte bChar, int &nLength); + + // Length in pixels of a string with the current font + int stringLen(const Common::String &text); + int stringLen(char bChar, char bNext = 0); +}; + + +class RMFontColor : public virtual RMFont { +private: + byte _fontR, _fontG, _fontB; + +public: + RMFontColor(); + virtual ~RMFontColor(); + virtual void setBaseColor(byte r, byte g, byte b); +}; + + +class RMFontWithTables : public virtual RMFont { +protected: + int _cTable[256]; + int _lTable[256]; + int _lDefault; + int _hDefault; + signed char _l2Table[256][256]; + +protected: + // Overloaded methods + int convertToLetter(byte nChar); + int letterLength(int nChar, int nNext = 0); + +public: + int letterHeight() { + return _hDefault; + } + virtual ~RMFontWithTables() {} +}; + + +class RMFontDialog : public RMFontColor, public RMFontWithTables { +public: + void init(); + virtual ~RMFontDialog() {} +}; + +class RMFontObj : public RMFontColor, public RMFontWithTables { +private: + void setBothCase(int nChar, int nNext, signed char spiazz); + +public: + void init(); + virtual ~RMFontObj() {} +}; + +class RMFontMacc : public RMFontColor, public RMFontWithTables { +public: + void init(); + virtual ~RMFontMacc() {} +}; + +class RMFontCredits : public RMFontColor, public RMFontWithTables { +public: + void init(); + virtual ~RMFontCredits() {} + virtual void setBaseColor(byte r, byte g, byte b) {} +}; + +/** + * Manages writing text onto9 the screen + */ +class RMText : public RMGfxWoodyBuffer { +private: + static RMFontColor *_fonts[4]; + int _maxLineLength; + +public: + enum HorAlign { + HLEFT, + HLEFTPAR, + HCENTER, + HRIGHT + }; + + enum VerAlign { + VTOP, + VCENTER, + VBOTTOM + }; + +private: + HorAlign _aHorType; + VerAlign _aVerType; + byte _textR, _textG, _textB; + +protected: + virtual void clipOnScreen(RMGfxPrimitive *prim); + +public: + RMText(); + virtual ~RMText(); + static void initStatics(); + static void unload(); + + // Set the alignment type + void setAlignType(HorAlign aHor, VerAlign aVer); + + // Sets the maximum length of a line in pixels (used to format the text) + void setMaxLineLength(int max); + + // Write the text + void writeText(const Common::String &text, int font, int *time = NULL); + void writeText(Common::String text, RMFontColor *font, int *time = NULL); + + // Overloaded function to decide when you delete the object from the OT list + virtual void removeThis(CORO_PARAM, bool &result); + + // Overloading of the Draw to center the text, if necessary + virtual void draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim); + + // Set the base color + void setColor(byte r, byte g, byte b); +}; + +/** + * Manages text in a dialog + */ +class RMTextDialog : public RMText { +protected: + int _startTime; + int _time; + bool _bSkipStatus; + RMPoint _dst; + uint32 _hEndDisplay; + bool _bShowed; + bool _bForceTime; + bool _bForceNoTime; + uint32 _hCustomSkip; + uint32 _hCustomSkip2; + RMInput *_input; + bool _bAlwaysDisplay; + bool _bNoTab; + +public: + RMTextDialog(); + virtual ~RMTextDialog(); + + // Write the text + void writeText(const Common::String &text, int font, int *time = NULL); + void writeText(const Common::String &text, RMFontColor *font, int *time = NULL); + + // Overloaded function to decide when you delete the object from the OT list + virtual void removeThis(CORO_PARAM, bool &result); + + // Overloaded de-registration + virtual void unregister(); + + // Overloading of the Draw to center the text, if necessary + virtual void draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim); + + // Set the position + void setPosition(const RMPoint &pt); + + // Waiting + void waitForEndDisplay(CORO_PARAM); + void setCustomSkipHandle(uint32 hCustomSkip); + void setCustomSkipHandle2(uint32 hCustomSkip); + void setSkipStatus(bool bEnabled); + void setForcedTime(uint32 dwTime); + void setNoTab(); + void forceTime(); + void forceNoTime(); + void setAlwaysDisplay(); + + // Set the input device, to allow skip from mouse + void setInput(RMInput *input); + + void show(); + void hide(CORO_PARAM); +}; + +class RMTextDialogScrolling : public RMTextDialog { +protected: + RMLocation *_curLoc; + RMPoint _startScroll; + + virtual void clipOnScreen(RMGfxPrimitive *prim); + +public: + RMTextDialogScrolling(); + RMTextDialogScrolling(RMLocation *loc); + virtual ~RMTextDialogScrolling(); + + virtual void draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim); +}; + + +/** + * Manages the name of a selected item on the screen + */ +class RMTextItemName : protected RMText { +protected: + RMPoint _mpos; + RMPoint _curscroll; + RMItem *_item; + +public: + RMTextItemName(); + virtual ~RMTextItemName(); + + void setMouseCoord(const RMPoint &m); + + void doFrame(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMLocation &loc, RMPointer &ptr, RMInventory &inv); + virtual void draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim); + + RMPoint getHotspot(); + RMItem *getSelectedItem(); + bool isItemSelected(); + + virtual void removeThis(CORO_PARAM, bool &result); +}; + + +/** + * Manages the selection of screen items in a box + */ +class RMDialogChoice : public RMGfxWoodyBuffer { +private: + int _curSelection; + int _numChoices; + RMText *_drawedStrings; + RMPoint *_ptDrawStrings; + int _curAdded; + bool _bShow; + RMGfxSourceBuffer8 _dlgText; + RMGfxSourceBuffer8 _dlgTextLine; + RMPoint _ptDrawPos; + uint32 _hUnreg; + bool _bRemoveFromOT; + +protected: + void prepare(CORO_PARAM); + void setSelected(CORO_PARAM, int pos); + +public: + virtual void removeThis(CORO_PARAM, bool &result); + virtual void draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim); + void unregister(); + +public: + // Initialization + RMDialogChoice(); + virtual ~RMDialogChoice(); + + // Initialization and closure + void init(); + void close(); + + // Sets the number of possible sentences, which then be added with AddChoice() + void setNumChoices(int num); + + // Adds a string with the choice + void addChoice(const Common::String &string); + + // Show and hide the selection, with possible animations. + // NOTE: If no parameter is passed to Show(), it is the obligation of + // caller to ensure that the class is inserted into OT list + void show(CORO_PARAM, RMGfxTargetBuffer *bigBuf = NULL); + void hide(CORO_PARAM); + + // Polling Update + void doFrame(CORO_PARAM, RMPoint ptMousePos); + + // Returns the currently selected item, or -1 if none is selected + int getSelection(); +}; + +} // End of namespace Tony + +#endif diff --git a/engines/tony/game.cpp b/engines/tony/game.cpp new file mode 100644 index 0000000000..1a19f2836c --- /dev/null +++ b/engines/tony/game.cpp @@ -0,0 +1,1604 @@ +/* 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 Tony Tough source code + * + * Copyright (c) 1997-2003 Nayma Software + */ + +#include "common/file.h" +#include "common/savefile.h" +#include "common/textconsole.h" +#include "graphics/cursorman.h" +#include "tony/mpal/lzo.h" +#include "tony/mpal/memory.h" +#include "tony/mpal/mpal.h" +#include "tony/mpal/mpalutils.h" +#include "tony/game.h" +#include "tony/gfxengine.h" +#include "tony/tony.h" + +namespace Tony { + +using namespace MPAL; + +// Global functions +void mainEnableGUI() { + g_vm->getEngine()->_bGUIInterface = true; + g_vm->getEngine()->_bGUIInventory = true; + g_vm->getEngine()->_bGUIOption = true; +} + +void mainDisableGUI() { + g_vm->getEngine()->_bGUIInterface = false; + g_vm->getEngine()->_bGUIInventory = false; + g_vm->getEngine()->_bGUIOption = false; +} + +/****************************************************************************\ +* RMOptionButton Methods +\****************************************************************************/ + +RMOptionButton::RMOptionButton(uint32 dwRes, RMPoint pt, bool bDoubleState) { + RMResRaw raw(dwRes); + assert(raw.isValid()); + _buf = new RMGfxSourceBuffer16(false); + _buf->init(raw, raw.width(), raw.height()); + + _rect.setRect(pt._x, pt._y, pt._x + raw.width() - 1, pt._y + raw.height() - 1); + _bActive = false; + _bHasGfx = true; + _bDoubleState = bDoubleState; +} + +RMOptionButton::RMOptionButton(const RMRect &pt) { + _rect = pt; + _bActive = false; + _bHasGfx = false; + _bDoubleState = false; + _buf = NULL; +} + +RMOptionButton::~RMOptionButton() { + if (_bHasGfx) + delete _buf; +} + +bool RMOptionButton::doFrame(const RMPoint &mousePos, bool bLeftClick, bool bRightClick) { + if (!_bDoubleState) { + if (_rect.ptInRect(mousePos)) { + if (!_bActive) { + _bActive = true; + return true; + } + } else { + if (_bActive) { + _bActive = false; + return true; + } + } + } else { + if (bLeftClick && _rect.ptInRect(mousePos)) { + _bActive = !_bActive; + return true; + } + } + + return false; +} + +void RMOptionButton::draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + if (!_bActive) + return; + + if (_bHasGfx) + CORO_INVOKE_2(_buf->draw, bigBuf, prim); + + CORO_END_CODE; +} + +void RMOptionButton::addToList(RMGfxTargetBuffer &bigBuf) { + if (_bHasGfx) + bigBuf.addPrim(new RMGfxPrimitive(this, _rect)); +} + +bool RMOptionButton::isActive() { + return _bActive; +} + +void RMOptionButton::setActiveState(bool bState) { + _bActive = bState; +} + +/****************************************************************************\ +* RMOptionSlide Methods +\****************************************************************************/ + +RMOptionSlide::RMOptionSlide(const RMPoint &pt, int nRange, int nStartValue, int slideSize) { + RMResRaw *raw; + + _pos = pt; + _nSlideSize = slideSize; + _nMax = nRange; + _nStep = 100 / _nMax; + _nValue = nStartValue; + + _sliderCenter = NULL; + _sliderLeft = NULL; + _sliderRight = NULL; + _sliderSingle = NULL; + + // Sliders + INIT_GFX16_FROMRAW(20029, _sliderCenter); + INIT_GFX16_FROMRAW(20030, _sliderLeft); + INIT_GFX16_FROMRAW(20031, _sliderRight); + INIT_GFX16_FROMRAW(20032, _sliderSingle); + + // Buttons + _pushLeft = new RMOptionButton(RMRect(pt._x - 23, pt._y, pt._x - 23 + 22, pt._y + 26)); + _pushRight = new RMOptionButton(RMRect(pt._x + _nSlideSize, pt._y, pt._x + _nSlideSize + 5 + 22, pt._y + 26)); +} + + +RMOptionSlide::~RMOptionSlide() { + delete _sliderCenter; + _sliderCenter = NULL; + delete _sliderLeft; + _sliderLeft = NULL; + delete _sliderRight; + _sliderRight = NULL; + delete _sliderSingle; + _sliderSingle = NULL; + + delete _pushLeft; + _pushLeft = NULL; + delete _pushRight; + _pushRight = NULL; +} + +bool RMOptionSlide::doFrame(const RMPoint &mousePos, bool bLeftClick, bool bRightClick) { + bool bRefresh = false; + + // Do the button DoFrame's + _pushLeft->doFrame(mousePos, bLeftClick, bRightClick); + _pushRight->doFrame(mousePos, bLeftClick, bRightClick); + + if (_pushLeft->isActive()) { + if (bLeftClick) { + bRefresh = true; + _nValue--; + } else if (bRightClick) { + bRefresh = true; + _nValue -= 3; + } + if (_nValue < 1) + _nValue = 1; + } else if (_pushRight->isActive()) { + bRefresh = true; + + if (bLeftClick) { + bRefresh = true; + _nValue++; + } else if (bRightClick) { + bRefresh = true; + _nValue += 3; + } + if (_nValue > _nMax) + _nValue = _nMax; + } + + return bRefresh; +} + +void RMOptionSlide::draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim) { + CORO_BEGIN_CONTEXT; + int i; + int val; + RMPoint pos; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + _ctx->pos = _pos; + _ctx->pos._x += 4; + _ctx->pos._y += 4; + + _ctx->val = _nValue * _nStep; + if (_ctx->val < 1) + _ctx->val = 1; + else if (_ctx->val > 100) + _ctx->val = 100; + + if (_ctx->val == 1) { + prim->setDst(_ctx->pos); + CORO_INVOKE_2(_sliderSingle->draw, bigBuf, prim); + } else { + prim->setDst(_ctx->pos); + CORO_INVOKE_2(_sliderLeft->draw, bigBuf, prim); + _ctx->pos._x += 3; + + for (_ctx->i = 1; _ctx->i < _ctx->val - 1; _ctx->i++) { + prim->setDst(_ctx->pos); + CORO_INVOKE_2(_sliderCenter->draw, bigBuf, prim); + _ctx->pos._x += 3; + } + + prim->setDst(_ctx->pos); + CORO_INVOKE_2(_sliderRight->draw, bigBuf, prim); + _ctx->pos._x += 3; + } + + CORO_END_CODE; +} + +void RMOptionSlide::addToList(RMGfxTargetBuffer &bigBuf) { + bigBuf.addPrim(new RMGfxPrimitive(this)); +} + +int RMOptionSlide::getValue() { + return _nValue; +} + +/****************************************************************************\ +* RMOptionScreen Methods +\****************************************************************************/ + +RMOptionScreen::RMOptionScreen() { + _nState = MENUNONE; + _menu = NULL; + _hideLoadSave = NULL; + _quitConfirm = NULL; + _bQuitConfirm = false; + + create(RM_SX, RM_SY); + + _buttonExit = NULL; + _buttonLoad = NULL; + _buttonSave = NULL; + _buttonGameMenu = NULL; + _buttonGfxMenu = NULL; + _buttonSoundMenu = NULL; + _buttonSave_ArrowLeft = NULL; + _buttonSave_ArrowRight = NULL; + _bEditSaveName = false; + + for (int i = 0; i < 6; i++) { + _curThumb[i] = NULL; + _buttonSave_States[i] = NULL; + } + + _statePos = 0; + _buttonQuitYes = NULL; + _buttonQuitNo = NULL; + _buttonQuit = NULL; + _saveEasy = NULL; + _saveHard = NULL; + _buttonGfx_Tips = NULL; + _buttonSound_DubbingOn = NULL; + _buttonSound_MusicOn = NULL; + _buttonSound_SFXOn = NULL; + _slideTonySpeed = NULL; + _slideTextSpeed = NULL; + _buttonGame_Lock = NULL; + _buttonGfx_Anni30 = NULL; + _sliderSound_Music = NULL; + _buttonGame_TimerizedText = NULL; + _buttonGfx_AntiAlias = NULL; + _sliderSound_SFX = NULL; + _buttonGame_Scrolling = NULL; + _buttonGfx_Sottotitoli = NULL; + _sliderSound_Dubbing = NULL; + _buttonGame_InterUp = NULL; + _buttonGfx_Trans = NULL; + + _fadeStep = 0; + _fadeY = 0; + _fadeTime = 0; + _nEditPos = 0; + _nLastState = MENUGAME; +} + +RMOptionScreen::~RMOptionScreen() { + closeState(); +} + +void RMOptionScreen::refreshAll(CORO_PARAM) { + CORO_BEGIN_CONTEXT; + RMGfxSourceBuffer16 *thumb; + RMText *title; + RMText *num[6]; + int i; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + clearOT(); + + addPrim(new RMGfxPrimitive(_menu)); + + if (_bNoLoadSave) + addPrim(new RMGfxPrimitive(_hideLoadSave, RMPoint(0, 401))); + + if (_bQuitConfirm) { + addPrim(new RMGfxPrimitive(_quitConfirm, RMPoint(270, 200))); + _buttonQuitYes->addToList(*this); + _buttonQuitNo->addToList(*this); + } + + _buttonExit->addToList(*this); + + if (_nState == MENUGAME || _nState == MENUGFX || _nState == MENUSOUND) { + _buttonQuit->addToList(*this); + _buttonLoad->addToList(*this); + _buttonSave->addToList(*this); + } + + if (_nState == MENUGAME) { + _buttonGame_Lock->addToList(*this); + _buttonGame_TimerizedText->addToList(*this); + _buttonGame_Scrolling->addToList(*this); + _buttonGame_InterUp->addToList(*this); + _slideTextSpeed->addToList(*this); + _slideTonySpeed->addToList(*this); + } else if (_nState == MENUGFX) { + _buttonGfx_Anni30->addToList(*this); + _buttonGfx_AntiAlias->addToList(*this); + _buttonGfx_Sottotitoli->addToList(*this); + _buttonGfx_Trans->addToList(*this); + _buttonGfx_Tips->addToList(*this); + } else if (_nState == MENUSOUND) { + _sliderSound_Dubbing->addToList(*this); + _sliderSound_Music->addToList(*this); + _sliderSound_SFX->addToList(*this); + _buttonSound_DubbingOn->addToList(*this); + _buttonSound_MusicOn->addToList(*this); + _buttonSound_SFXOn->addToList(*this); + } + + _ctx->thumb = NULL; + _ctx->title = NULL; + Common::fill(&_ctx->num[0], &_ctx->num[6], (RMText *)NULL); + + if (_nState == MENULOAD || _nState == MENUSAVE) { + _ctx->title = new RMText; + if (_nState == MENULOAD) { + RMMessage msg(10); + _ctx->title->writeText(msg[0], 1); + } else { + RMMessage msg(11); + _ctx->title->writeText(msg[0], 1); + } + + addPrim(new RMGfxPrimitive(_ctx->title, RMPoint(320, 10))); + + if (_curThumbDiff[0] == 0) + addPrim(new RMGfxPrimitive(_saveHard, RMPoint(48, 57))); + else if (_curThumbDiff[0] == 1) + addPrim(new RMGfxPrimitive(_saveEasy, RMPoint(48, 57))); + if (_curThumbDiff[1] == 0) + addPrim(new RMGfxPrimitive(_saveHard, RMPoint(240, 57))); + else if (_curThumbDiff[1] == 1) + addPrim(new RMGfxPrimitive(_saveEasy, RMPoint(240, 57))); + if (_curThumbDiff[2] == 0) + addPrim(new RMGfxPrimitive(_saveHard, RMPoint(432, 57))); + else if (_curThumbDiff[2] == 1) + addPrim(new RMGfxPrimitive(_saveEasy, RMPoint(432, 57))); + if (_curThumbDiff[3] == 0) + addPrim(new RMGfxPrimitive(_saveHard, RMPoint(48, 239))); + else if (_curThumbDiff[3] == 1) + addPrim(new RMGfxPrimitive(_saveEasy, RMPoint(48, 239))); + if (_curThumbDiff[4] == 0) + addPrim(new RMGfxPrimitive(_saveHard, RMPoint(240, 239))); + else if (_curThumbDiff[4] == 1) + addPrim(new RMGfxPrimitive(_saveEasy, RMPoint(240, 239))); + if (_curThumbDiff[5] == 0) + addPrim(new RMGfxPrimitive(_saveHard, RMPoint(432, 239))); + else if (_curThumbDiff[5] == 1) + addPrim(new RMGfxPrimitive(_saveEasy, RMPoint(432, 239))); + + if (_curThumb[0] && !(_bEditSaveName && _nEditPos == 0)) + addPrim(new RMGfxPrimitive(_curThumb[0], RMPoint(48, 57))); + if (_curThumb[1] && !(_bEditSaveName && _nEditPos == 1)) + addPrim(new RMGfxPrimitive(_curThumb[1], RMPoint(240, 57))); + if (_curThumb[2] && !(_bEditSaveName && _nEditPos == 2)) + addPrim(new RMGfxPrimitive(_curThumb[2], RMPoint(432, 57))); + if (_curThumb[3] && !(_bEditSaveName && _nEditPos == 3)) + addPrim(new RMGfxPrimitive(_curThumb[3], RMPoint(48, 239))); + if (_curThumb[4] && !(_bEditSaveName && _nEditPos == 4)) + addPrim(new RMGfxPrimitive(_curThumb[4], RMPoint(240, 239))); + if (_curThumb[5] && !(_bEditSaveName && _nEditPos == 5)) + addPrim(new RMGfxPrimitive(_curThumb[5], RMPoint(432, 239))); + + if (_bEditSaveName) { + _ctx->thumb = new RMGfxSourceBuffer16; + _ctx->thumb->init((byte *)g_vm->getThumbnail(), 640 / 4, 480 / 4); + + if (_nEditPos == 0) + addPrim(new RMGfxPrimitive(_ctx->thumb, RMPoint(48, 57))); + else if (_nEditPos == 1) + addPrim(new RMGfxPrimitive(_ctx->thumb, RMPoint(240, 57))); + else if (_nEditPos == 2) + addPrim(new RMGfxPrimitive(_ctx->thumb, RMPoint(432, 57))); + else if (_nEditPos == 3) + addPrim(new RMGfxPrimitive(_ctx->thumb, RMPoint(48, 239))); + else if (_nEditPos == 4) + addPrim(new RMGfxPrimitive(_ctx->thumb, RMPoint(240, 239))); + else if (_nEditPos == 5) + addPrim(new RMGfxPrimitive(_ctx->thumb, RMPoint(432, 239))); + } + + for (_ctx->i = 0; _ctx->i < 6; _ctx->i++) { + Common::String s; + + if (_bEditSaveName && _nEditPos == _ctx->i) + s = Common::String::format("%02d)%s*", _statePos + _ctx->i, _editName); + else { + if (_statePos == 0 && _ctx->i == 0) + s = "Autosave"; + else + s = Common::String::format("%02d)%s", _statePos + _ctx->i, _curThumbName[_ctx->i].c_str()); + } + + _ctx->num[_ctx->i] = new RMText; + _ctx->num[_ctx->i]->setAlignType(RMText::HLEFT, RMText::VTOP); + _ctx->num[_ctx->i]->writeText(s, 2); + } + + addPrim(new RMGfxPrimitive(_ctx->num[0], RMPoint(55 - 3, 180 + 14))); + addPrim(new RMGfxPrimitive(_ctx->num[1], RMPoint(247 - 3, 180 + 14))); + addPrim(new RMGfxPrimitive(_ctx->num[2], RMPoint(439 - 3, 180 + 14))); + addPrim(new RMGfxPrimitive(_ctx->num[3], RMPoint(55 - 3, 362 + 14))); + addPrim(new RMGfxPrimitive(_ctx->num[4], RMPoint(247 - 3, 362 + 14))); + addPrim(new RMGfxPrimitive(_ctx->num[5], RMPoint(439 - 3, 362 + 14))); + + _buttonSave_ArrowLeft->addToList(*this); + _buttonSave_ArrowRight->addToList(*this); + } + + CORO_INVOKE_0(drawOT); + + if (_nState == MENULOAD || _nState == MENUSAVE) { + if (_ctx->thumb) + delete _ctx->thumb; + + if (_ctx->title) + delete _ctx->title; + + for (_ctx->i = 0; _ctx->i < 6; _ctx->i++) { + if (_ctx->num[_ctx->i]) + delete _ctx->num[_ctx->i]; + } + } + + CORO_END_CODE; +} + +void RMOptionScreen::refreshThumbnails() { + for (int i = 0; i < 6; i++) { + if (_curThumb[i]) + delete _curThumb[i]; + + _curThumb[i] = new RMGfxSourceBuffer16; + _curThumb[i]->create(640 / 4, 480 / 4); + if (!loadThumbnailFromSaveState(_statePos + i, *_curThumb[i], _curThumbName[i], _curThumbDiff[i])) { + delete _curThumb[i]; + _curThumb[i] = NULL; + _curThumbName[i].clear(); + _curThumbDiff[i] = 11; + } + } +} + +void RMOptionScreen::initState(CORO_PARAM) { + CORO_BEGIN_CONTEXT; + RMResRaw *raw; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + if (_nState == MENUGAME || _nState == MENUGFX || _nState == MENUSOUND) + _ctx->raw = new RMResRaw(20000 + _nState); + else if (_nState == MENULOAD || _nState == MENUSAVE) { + if (_bAlterGfx) + _ctx->raw = new RMResRaw(20024); + else + _ctx->raw = new RMResRaw(20003); + } else { + error("Invalid state"); + } + + assert(_ctx->raw->isValid()); + assert(_menu == NULL); + _menu = new RMGfxSourceBuffer16(false); + _menu->init(*_ctx->raw, _ctx->raw->width(), _ctx->raw->height()); + delete _ctx->raw; + + if (_nState == MENULOAD || _nState == MENUSAVE) { + if (_bAlterGfx) { + assert(_buttonExit == NULL); + _buttonExit = new RMOptionButton(20025, RMPoint(561, 406)); + } else { + assert(_buttonExit == NULL); + _buttonExit = new RMOptionButton(20012, RMPoint(560, 404)); + } + + INIT_GFX8_FROMRAW(_ctx->raw, 20036, _saveEasy); + INIT_GFX8_FROMRAW(_ctx->raw, 20037, _saveHard); + + refreshThumbnails(); + + assert(_buttonSave_States[0] == NULL); + _buttonSave_States[0] = new RMOptionButton(RMRect(48, 57, 48 + 160, 57 + 120)); + assert(_buttonSave_States[1] == NULL); + _buttonSave_States[1] = new RMOptionButton(RMRect(240, 57, 240 + 160, 57 + 120)); + assert(_buttonSave_States[2] == NULL); + _buttonSave_States[2] = new RMOptionButton(RMRect(432, 57, 432 + 160, 57 + 120)); + assert(_buttonSave_States[3] == NULL); + _buttonSave_States[3] = new RMOptionButton(RMRect(48, 239, 48 + 160, 239 + 120)); + assert(_buttonSave_States[4] == NULL); + _buttonSave_States[4] = new RMOptionButton(RMRect(240, 239, 240 + 160, 239 + 120)); + assert(_buttonSave_States[5] == NULL); + _buttonSave_States[5] = new RMOptionButton(RMRect(432, 239, 432 + 160, 239 + 120)); + + if (_bAlterGfx) { + assert(_buttonSave_ArrowLeft == NULL); + _buttonSave_ArrowLeft = new RMOptionButton(20026, RMPoint(3, 196)); + assert(_buttonSave_ArrowRight == NULL); + _buttonSave_ArrowRight = new RMOptionButton(20027, RMPoint(601, 197)); + } else { + assert(_buttonSave_ArrowLeft == NULL); + _buttonSave_ArrowLeft = new RMOptionButton(20013, RMPoint(0, 197)); + assert(_buttonSave_ArrowRight == NULL); + _buttonSave_ArrowRight = new RMOptionButton(20014, RMPoint(601, 197)); + } + } else if (_nState == MENUGAME || _nState == MENUGFX || _nState == MENUSOUND) { + assert(_buttonExit == NULL); + _buttonExit = new RMOptionButton(20005, RMPoint(560, 405)); + assert(_buttonQuit == NULL); + _buttonQuit = new RMOptionButton(20020, RMPoint(7, 408)); + assert(_buttonLoad == NULL); + _buttonLoad = new RMOptionButton(20006, RMPoint(231, 401)); + assert(_buttonSave == NULL); + _buttonSave = new RMOptionButton(20007, RMPoint(325, 401)); + + assert(_buttonGameMenu == NULL); + _buttonGameMenu = new RMOptionButton(RMRect(24, 32, 118, 64)); + assert(_buttonGfxMenu == NULL); + _buttonGfxMenu = new RMOptionButton(RMRect(118, 32, 212, 64)); + assert(_buttonSoundMenu == NULL); + _buttonSoundMenu = new RMOptionButton(RMRect(212, 32, 306, 64)); + + _ctx->raw = new RMResRaw(20021); + assert(_ctx->raw->isValid()); + assert(_quitConfirm == NULL); + _quitConfirm = new RMGfxSourceBuffer16(false); + _quitConfirm->init(*_ctx->raw, _ctx->raw->width(), _ctx->raw->height()); + delete _ctx->raw; + + assert(_buttonQuitYes == NULL); + _buttonQuitYes = new RMOptionButton(20022, RMPoint(281, 265)); + _buttonQuitYes->setPriority(30); + assert(_buttonQuitNo == NULL); + _buttonQuitNo = new RMOptionButton(20023, RMPoint(337, 264)); + _buttonQuitNo->setPriority(30); + + if (_bNoLoadSave) { + _ctx->raw = new RMResRaw(20028); + assert(_ctx->raw->isValid()); + assert(_hideLoadSave == NULL); + _hideLoadSave = new RMGfxSourceBuffer16(false); + _hideLoadSave->init(*_ctx->raw, _ctx->raw->width(), _ctx->raw->height()); + delete _ctx->raw; + } + + // Menu GAME + if (_nState == MENUGAME) { + assert(_buttonGame_Lock == NULL); + _buttonGame_Lock = new RMOptionButton(20008, RMPoint(176, 262), true); + _buttonGame_Lock->setActiveState(GLOBALS._bCfgInvLocked); + assert(_buttonGame_TimerizedText == NULL); + _buttonGame_TimerizedText = new RMOptionButton(20009, RMPoint(463, 273), true); + _buttonGame_TimerizedText->setActiveState(!GLOBALS._bCfgTimerizedText); + assert(_buttonGame_Scrolling == NULL); + _buttonGame_Scrolling = new RMOptionButton(20010, RMPoint(315, 263), true); + _buttonGame_Scrolling->setActiveState(GLOBALS._bCfgInvNoScroll); + assert(_buttonGame_InterUp == NULL); + _buttonGame_InterUp = new RMOptionButton(20011, RMPoint(36, 258), true); + _buttonGame_InterUp->setActiveState(GLOBALS._bCfgInvUp); + + assert(_slideTextSpeed == NULL); + _slideTextSpeed = new RMOptionSlide(RMPoint(165, 122), 10, GLOBALS._nCfgTextSpeed); + assert(_slideTonySpeed == NULL); + _slideTonySpeed = new RMOptionSlide(RMPoint(165, 226), 5, GLOBALS._nCfgTonySpeed); + } + // Menu Graphics + else if (_nState == MENUGFX) { + assert(_buttonGfx_Anni30 == NULL); + _buttonGfx_Anni30 = new RMOptionButton(20015, RMPoint(247, 178), true); + _buttonGfx_Anni30->setActiveState(GLOBALS._bCfgAnni30); + assert(_buttonGfx_AntiAlias == NULL); + _buttonGfx_AntiAlias = new RMOptionButton(20016, RMPoint(430, 83), true); + _buttonGfx_AntiAlias->setActiveState(!GLOBALS._bCfgAntiAlias); + assert(_buttonGfx_Sottotitoli == NULL); + _buttonGfx_Sottotitoli = new RMOptionButton(20017, RMPoint(98, 82), true); + _buttonGfx_Sottotitoli->setActiveState(!GLOBALS._bShowSubtitles); + assert(_buttonGfx_Tips == NULL); + _buttonGfx_Tips = new RMOptionButton(20018, RMPoint(431, 246), true); + _buttonGfx_Tips->setActiveState(GLOBALS._bCfgInterTips); + assert(_buttonGfx_Trans == NULL); + _buttonGfx_Trans = new RMOptionButton(20019, RMPoint(126, 271), true); + _buttonGfx_Trans->setActiveState(!GLOBALS._bCfgTransparence); + + } else if (_nState == MENUSOUND) { + assert(_sliderSound_Dubbing == NULL); + _sliderSound_Dubbing = new RMOptionSlide(RMPoint(165, 122), 10, GLOBALS._nCfgDubbingVolume); + assert(_sliderSound_Music == NULL); + _sliderSound_Music = new RMOptionSlide(RMPoint(165, 226), 10, GLOBALS._nCfgMusicVolume); + assert(_sliderSound_SFX == NULL); + _sliderSound_SFX = new RMOptionSlide(RMPoint(165, 330), 10, GLOBALS._nCfgSFXVolume); + + assert(_buttonSound_DubbingOn == NULL); + _buttonSound_DubbingOn = new RMOptionButton(20033, RMPoint(339, 75), true); + _buttonSound_DubbingOn->setActiveState(GLOBALS._bCfgDubbing); + assert(_buttonSound_MusicOn == NULL); + _buttonSound_MusicOn = new RMOptionButton(20034, RMPoint(338, 179), true); + _buttonSound_MusicOn->setActiveState(GLOBALS._bCfgMusic); + assert(_buttonSound_SFXOn == NULL); + _buttonSound_SFXOn = new RMOptionButton(20035, RMPoint(338, 283), true); + _buttonSound_SFXOn->setActiveState(GLOBALS._bCfgSFX); + } + } + + CORO_INVOKE_0(refreshAll); + + CORO_END_CODE; +} + +void RMOptionScreen::closeState() { + delete _menu; + _menu = NULL; + + delete _buttonExit; + _buttonExit = NULL; + + if (_nState == MENULOAD || _nState == MENUSAVE) { + for (int i = 0; i < 6; i++) { + if (_curThumb[i] != NULL) { + delete _curThumb[i]; + _curThumb[i] = NULL; + } + + delete _buttonSave_States[i]; + _buttonSave_States[i] = NULL; + } + + delete _buttonSave_ArrowLeft; + _buttonSave_ArrowLeft = NULL; + delete _buttonSave_ArrowRight; + _buttonSave_ArrowRight = NULL; + + delete _saveEasy; + _saveEasy = NULL; + delete _saveHard; + _saveHard = NULL; + } + + if (_nState == MENUGAME || _nState == MENUGFX || _nState == MENUSOUND) { + delete _buttonQuit; + _buttonQuit = NULL; + delete _buttonLoad; + _buttonLoad = NULL; + delete _buttonSave; + _buttonSave = NULL; + delete _buttonGameMenu; + _buttonGameMenu = NULL; + delete _buttonGfxMenu; + _buttonGfxMenu = NULL; + delete _buttonSoundMenu; + _buttonSoundMenu = NULL; + delete _quitConfirm; + _quitConfirm = NULL; + delete _buttonQuitYes; + _buttonQuitYes = NULL; + delete _buttonQuitNo; + _buttonQuitNo = NULL; + + if (_bNoLoadSave) { + delete _hideLoadSave; + _hideLoadSave = NULL; + } + + if (_nState == MENUGAME) { + GLOBALS._bCfgInvLocked = _buttonGame_Lock->isActive(); + delete _buttonGame_Lock; + _buttonGame_Lock = NULL; + + GLOBALS._bCfgTimerizedText = !_buttonGame_TimerizedText->isActive(); + delete _buttonGame_TimerizedText; + _buttonGame_TimerizedText = NULL; + + GLOBALS._bCfgInvNoScroll = _buttonGame_Scrolling->isActive(); + delete _buttonGame_Scrolling; + _buttonGame_Scrolling = NULL; + + GLOBALS._bCfgInvUp = _buttonGame_InterUp->isActive(); + delete _buttonGame_InterUp; + _buttonGame_InterUp = NULL; + + GLOBALS._nCfgTextSpeed = _slideTextSpeed->getValue(); + delete _slideTextSpeed; + _slideTextSpeed = NULL; + + GLOBALS._nCfgTonySpeed = _slideTonySpeed->getValue(); + delete _slideTonySpeed; + _slideTonySpeed = NULL; + } else if (_nState == MENUGFX) { + GLOBALS._bCfgAnni30 = _buttonGfx_Anni30->isActive(); + delete _buttonGfx_Anni30; + _buttonGfx_Anni30 = NULL; + + GLOBALS._bCfgAntiAlias = !_buttonGfx_AntiAlias->isActive(); + delete _buttonGfx_AntiAlias; + _buttonGfx_AntiAlias = NULL; + + GLOBALS._bShowSubtitles = !_buttonGfx_Sottotitoli->isActive(); + delete _buttonGfx_Sottotitoli; + _buttonGfx_Sottotitoli = NULL; + + GLOBALS._bCfgInterTips = _buttonGfx_Tips->isActive(); + delete _buttonGfx_Tips; + _buttonGfx_Tips = NULL; + + GLOBALS._bCfgTransparence = !_buttonGfx_Trans->isActive(); + delete _buttonGfx_Trans; + _buttonGfx_Trans = NULL; + } else if (_nState == MENUSOUND) { + GLOBALS._nCfgDubbingVolume = _sliderSound_Dubbing->getValue(); + delete _sliderSound_Dubbing; + _sliderSound_Dubbing = NULL; + + GLOBALS._nCfgMusicVolume = _sliderSound_Music->getValue(); + delete _sliderSound_Music; + _sliderSound_Music = NULL; + + GLOBALS._nCfgSFXVolume = _sliderSound_SFX->getValue(); + delete _sliderSound_SFX; + _sliderSound_SFX = NULL; + + GLOBALS._bCfgDubbing = _buttonSound_DubbingOn->isActive(); + delete _buttonSound_DubbingOn; + _buttonSound_DubbingOn = NULL; + + GLOBALS._bCfgMusic = _buttonSound_MusicOn->isActive(); + delete _buttonSound_MusicOn; + _buttonSound_MusicOn = NULL; + + GLOBALS._bCfgSFX = _buttonSound_SFXOn->isActive(); + delete _buttonSound_SFXOn; + _buttonSound_SFXOn = NULL; + } + + // Save the new settings to ScummVM + g_vm->saveSoundSettings(); + } + + _nState = MENUNONE; +} + +void RMOptionScreen::reInit(RMGfxTargetBuffer &bigBuf) { + bigBuf.addPrim(new RMGfxPrimitive(this)); +} + +void RMOptionScreen::init(CORO_PARAM, RMGfxTargetBuffer &bigBuf, bool &result) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + if (_fadeStep != 0) { + result = false; + return; + } + + _fadeStep = 1; + _fadeY = -20; + _fadeTime = -1; + _bExit = false; + _bLoadMenuOnly = false; + _bNoLoadSave = false; + _bAlterGfx = false; + + bigBuf.addPrim(new RMGfxPrimitive(this)); + + if (_nState == MENULOAD || _nState == MENUSAVE || _nState == MENUNONE) + _nState = MENUGAME; + + CORO_INVOKE_0(initState); + + result = true; + + CORO_END_CODE; +} + +void RMOptionScreen::initLoadMenuOnly(CORO_PARAM, RMGfxTargetBuffer &bigBuf, bool bAlternateGfx, bool &result) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + if (_fadeStep != 0) { + result = false; + return; + } + + _fadeStep = 1; + _fadeY = -20; + _fadeTime = -1; + _bExit = false; + _bLoadMenuOnly = true; + _bNoLoadSave = false; + _bAlterGfx = bAlternateGfx; + + bigBuf.addPrim(new RMGfxPrimitive(this)); + + _nState = MENULOAD; + CORO_INVOKE_0(initState); + + result = true; + + CORO_END_CODE; +} + +void RMOptionScreen::initSaveMenuOnly(CORO_PARAM, RMGfxTargetBuffer &bigBuf, bool bAlternateGfx, bool &result) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + if (_fadeStep != 0) { + result = false; + return; + } + + _fadeStep = 1; + _fadeY = -20; + _fadeTime = -1; + _bExit = false; + _bLoadMenuOnly = true; + _bNoLoadSave = false; + _bAlterGfx = bAlternateGfx; + + bigBuf.addPrim(new RMGfxPrimitive(this)); + + _nState = MENUSAVE; + CORO_INVOKE_0(initState); + + result = true; + + CORO_END_CODE; +} + +void RMOptionScreen::initNoLoadSave(CORO_PARAM, RMGfxTargetBuffer &bigBuf, bool &result) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + if (_fadeStep != 0) { + result = false; + return; + } + + _fadeStep = 1; + _fadeY = -20; + _fadeTime = -1; + _bExit = false; + _bLoadMenuOnly = false; + _bNoLoadSave = true; + + bigBuf.addPrim(new RMGfxPrimitive(this)); + + _nState = MENUGAME; + CORO_INVOKE_0(initState); + + result = true; + + CORO_END_CODE; +} + +bool RMOptionScreen::close() { + if (_fadeStep != 6) + return false; + + // Start fade out + _fadeStep++; + _fadeTime = g_vm->getTime(); + return true; +} + +bool RMOptionScreen::isClosing() { + return _bExit; +} + +int RMOptionScreen::priority() { + // Just below the mouse + return 190; +} + +void RMOptionScreen::changeState(CORO_PARAM, OptionScreenState newState) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + _nLastState = _nState; + closeState(); + _nState = newState; + CORO_INVOKE_0(initState); + + CORO_END_CODE; +} + +void RMOptionScreen::doFrame(CORO_PARAM, RMInput *input) { + CORO_BEGIN_CONTEXT; + bool bLeftClick, bRightClick; + RMPoint mousePos; + bool bRefresh; + int i; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + + // If it is fully open, do nothing + if (_fadeStep != 6) + return; + + // Reads input + _ctx->mousePos = input->mousePos(); + _ctx->bLeftClick = input->mouseLeftClicked(); + _ctx->bRightClick = input->mouseRightClicked(); + + _ctx->bRefresh = false; + + if (_bQuitConfirm) { + _ctx->bRefresh |= _buttonQuitYes->doFrame(_ctx->mousePos, _ctx->bLeftClick, _ctx->bRightClick); + _ctx->bRefresh |= _buttonQuitNo->doFrame(_ctx->mousePos, _ctx->bLeftClick, _ctx->bRightClick); + } else { + _ctx->bRefresh |= _buttonExit->doFrame(_ctx->mousePos, _ctx->bLeftClick, _ctx->bRightClick); + + // Check if you have clicked on the output + if (_nState == MENUGAME || _nState == MENUGFX || _nState == MENUSOUND) { + // Buttons without graphics... + _buttonGameMenu->doFrame(_ctx->mousePos, _ctx->bLeftClick, _ctx->bRightClick); + _buttonGfxMenu->doFrame(_ctx->mousePos, _ctx->bLeftClick, _ctx->bRightClick); + _buttonSoundMenu->doFrame(_ctx->mousePos, _ctx->bLeftClick, _ctx->bRightClick); + + // Buttons with graphics + if (!_bNoLoadSave) { + if (!g_vm->getIsDemo()) { + _ctx->bRefresh |= _buttonLoad->doFrame(_ctx->mousePos, _ctx->bLeftClick, _ctx->bRightClick); + _ctx->bRefresh |= _buttonSave->doFrame(_ctx->mousePos, _ctx->bLeftClick, _ctx->bRightClick); + } + + _ctx->bRefresh |= _buttonQuit->doFrame(_ctx->mousePos, _ctx->bLeftClick, _ctx->bRightClick); + } + } + + if (_nState == MENUGAME) { + _ctx->bRefresh |= _buttonGame_Lock->doFrame(_ctx->mousePos, _ctx->bLeftClick, _ctx->bRightClick); + _ctx->bRefresh |= _buttonGame_TimerizedText->doFrame(_ctx->mousePos, _ctx->bLeftClick, _ctx->bRightClick); + _ctx->bRefresh |= _buttonGame_Scrolling->doFrame(_ctx->mousePos, _ctx->bLeftClick, _ctx->bRightClick); + _ctx->bRefresh |= _buttonGame_InterUp->doFrame(_ctx->mousePos, _ctx->bLeftClick, _ctx->bRightClick); + _ctx->bRefresh |= _slideTextSpeed->doFrame(_ctx->mousePos, _ctx->bLeftClick, _ctx->bRightClick); + _ctx->bRefresh |= _slideTonySpeed->doFrame(_ctx->mousePos, _ctx->bLeftClick, _ctx->bRightClick); + + } else if (_nState == MENUGFX) { + _ctx->bRefresh |= _buttonGfx_Anni30->doFrame(_ctx->mousePos, _ctx->bLeftClick, _ctx->bRightClick); + _ctx->bRefresh |= _buttonGfx_AntiAlias->doFrame(_ctx->mousePos, _ctx->bLeftClick, _ctx->bRightClick); + _ctx->bRefresh |= _buttonGfx_Sottotitoli->doFrame(_ctx->mousePos, _ctx->bLeftClick, _ctx->bRightClick); + _ctx->bRefresh |= _buttonGfx_Tips->doFrame(_ctx->mousePos, _ctx->bLeftClick, _ctx->bRightClick); + _ctx->bRefresh |= _buttonGfx_Trans->doFrame(_ctx->mousePos, _ctx->bLeftClick, _ctx->bRightClick); + + } else if (_nState == MENUSOUND) { + _ctx->bRefresh |= _sliderSound_Dubbing->doFrame(_ctx->mousePos, _ctx->bLeftClick, _ctx->bRightClick); + _ctx->bRefresh |= _sliderSound_Music->doFrame(_ctx->mousePos, _ctx->bLeftClick, _ctx->bRightClick); + _ctx->bRefresh |= _sliderSound_SFX->doFrame(_ctx->mousePos, _ctx->bLeftClick, _ctx->bRightClick); + _ctx->bRefresh |= _buttonSound_DubbingOn->doFrame(_ctx->mousePos, _ctx->bLeftClick, _ctx->bRightClick); + _ctx->bRefresh |= _buttonSound_MusicOn->doFrame(_ctx->mousePos, _ctx->bLeftClick, _ctx->bRightClick); + _ctx->bRefresh |= _buttonSound_SFXOn->doFrame(_ctx->mousePos, _ctx->bLeftClick, _ctx->bRightClick); + + } else if (_nState == MENULOAD || _nState == MENUSAVE) { + for (_ctx->i = 0; _ctx->i < 6; _ctx->i++) + _buttonSave_States[_ctx->i]->doFrame(_ctx->mousePos, _ctx->bLeftClick, _ctx->bRightClick); + + if (_statePos > 0) + _ctx->bRefresh |= _buttonSave_ArrowLeft->doFrame(_ctx->mousePos, _ctx->bLeftClick, _ctx->bRightClick); + if (_statePos < 90) + _ctx->bRefresh |= _buttonSave_ArrowRight->doFrame(_ctx->mousePos, _ctx->bLeftClick, _ctx->bRightClick); + } + } + +#define KEYPRESS(c) (g_vm->getEngine()->getInput().getAsyncKeyState(c)) +#define PROCESS_CHAR(cod, c) if (KEYPRESS(cod)) { \ + _editName[strlen(_editName) + 1] = '\0'; _editName[strlen(_editName)] = c; _ctx->bRefresh = true; } + + // State Buttons + if (_bEditSaveName) { + if (KEYPRESS(Common::KEYCODE_BACKSPACE)) { + if (_editName[0] != '\0') { + _editName[strlen(_editName) - 1] = '\0'; + _ctx->bRefresh = true; + } + } + + for (_ctx->i = 0; _ctx->i < 26 && strlen(_editName) < 12; _ctx->i++) { + if (KEYPRESS(Common::KEYCODE_LSHIFT) || + KEYPRESS(Common::KEYCODE_RSHIFT)) { + PROCESS_CHAR((Common::KeyCode)((int)'a' + _ctx->i), _ctx->i + 'A'); + } else { + PROCESS_CHAR((Common::KeyCode)((int)'a' + _ctx->i), _ctx->i + 'a'); + } + } + + for (_ctx->i = 0; _ctx->i < 10 && strlen(_editName) < 12; _ctx->i++) + PROCESS_CHAR((Common::KeyCode)((int)'0' + _ctx->i), _ctx->i + '0'); + + if (strlen(_editName) < 12) + PROCESS_CHAR(Common::KEYCODE_SPACE, ' '); + + if (strlen(_editName) < 12) + PROCESS_CHAR(Common::KEYCODE_KP0, '0'); + if (strlen(_editName) < 12) + PROCESS_CHAR(Common::KEYCODE_KP1, '1'); + if (strlen(_editName) < 12) + PROCESS_CHAR(Common::KEYCODE_KP2, '2'); + if (strlen(_editName) < 12) + PROCESS_CHAR(Common::KEYCODE_KP3, '3'); + if (strlen(_editName) < 12) + PROCESS_CHAR(Common::KEYCODE_KP4, '4'); + if (strlen(_editName) < 12) + PROCESS_CHAR(Common::KEYCODE_KP5, '5'); + if (strlen(_editName) < 12) + PROCESS_CHAR(Common::KEYCODE_KP6, '6'); + if (strlen(_editName) < 12) + PROCESS_CHAR(Common::KEYCODE_KP7, '7'); + if (strlen(_editName) < 12) + PROCESS_CHAR(Common::KEYCODE_KP8, '8'); + if (strlen(_editName) < 12) + PROCESS_CHAR(Common::KEYCODE_KP9, '9'); + + // Cancel + if (KEYPRESS(Common::KEYCODE_ESCAPE)) { + _bEditSaveName = false; + _ctx->bRefresh = true; + } + + // OK + if (KEYPRESS(Common::KEYCODE_RETURN)) { + _bEditSaveName = false; + g_vm->saveState(_statePos + _nEditPos, _editName); + close(); + } + + } else if (_ctx->bLeftClick) { + if (_nState == MENULOAD || _nState == MENUSAVE) { + if (_buttonExit->isActive()) { + if (_bLoadMenuOnly) { + // If only the loading menu, close + close(); + } else { + CORO_INVOKE_1(changeState, _nLastState); + _ctx->bRefresh = true; + } + } else if (_buttonSave_ArrowLeft->isActive()) { + if (_statePos > 0) { + _statePos -= 6; + if (_statePos < 0) + _statePos = 0; + _buttonSave_ArrowLeft->setActiveState(false); + _ctx->bRefresh = true; + refreshThumbnails(); + } + } else if (_buttonSave_ArrowRight->isActive()) { + if (_statePos < 90) { + _statePos += 6; + if (_statePos > 90) + _statePos = 90; + _buttonSave_ArrowRight->setActiveState(false); + _ctx->bRefresh = true; + refreshThumbnails(); + } + } else { + for (_ctx->i = 0; _ctx->i < 6; _ctx->i++) + if (_buttonSave_States[_ctx->i]->isActive()) { + // There by saving or loading!!! + if (_nState == MENULOAD && _curThumb[_ctx->i] != NULL) { + // Loading + CORO_INVOKE_1(g_vm->loadState, _statePos + _ctx->i); + close(); + } else if (_nState == MENUSAVE && (_statePos != 0 || _ctx->i != 0)) { + // Turn on edit mode + _bEditSaveName = true; + _nEditPos = _ctx->i; + strcpy(_editName, _curThumbName[_ctx->i].c_str()); + _ctx->bRefresh = true; + } + + break; + } + } + } + + if (_nState == MENUGAME || _nState == MENUGFX || _nState == MENUSOUND) { + if (_bQuitConfirm) { + if (_buttonQuitNo->isActive()) { + _bQuitConfirm = false; + _ctx->bRefresh = true; + } else if (_buttonQuitYes->isActive()) { + _bQuitConfirm = false; + _ctx->bRefresh = true; + + g_vm->quitGame(); + } + } else { + if (_buttonQuit->isActive()) { + _bQuitConfirm = true; + _buttonQuitNo->setActiveState(false); + _buttonQuitYes->setActiveState(false); + _ctx->bRefresh = true; + } else if (_buttonExit->isActive()) + close(); + else if (_buttonLoad->isActive()) { + CORO_INVOKE_1(changeState, MENULOAD); + _ctx->bRefresh = true; + } else if (_buttonSave->isActive()) { + CORO_INVOKE_1(changeState, MENUSAVE); + _ctx->bRefresh = true; + } else if (_buttonGameMenu->isActive() && _nState != MENUGAME) { + CORO_INVOKE_1(changeState, MENUGAME); + _ctx->bRefresh = true; + } else if (_buttonGfxMenu->isActive() && _nState != MENUGFX) { + CORO_INVOKE_1(changeState, MENUGFX); + _ctx->bRefresh = true; + } else if (_buttonSoundMenu->isActive() && _nState != MENUSOUND) { + CORO_INVOKE_1(changeState, MENUSOUND); + _ctx->bRefresh = true; + } + + if (_nState == MENUGFX) { + // These options take effect immediately + if (_buttonGfx_Anni30->isActive()) + GLOBALS._bCfgAnni30 = true; + else + GLOBALS._bCfgAnni30 = false; + + if (_buttonGfx_AntiAlias->isActive()) + GLOBALS._bCfgAntiAlias = false; + else + GLOBALS._bCfgAntiAlias = true; + + if (_buttonGfx_Trans->isActive()) + GLOBALS._bCfgTransparence = false; + else + GLOBALS._bCfgTransparence = true; + } + } + } + } + + if (_nState == MENUGAME || _nState == MENUGFX || _nState == MENUSOUND) { + if (!_bQuitConfirm && KEYPRESS(Common::KEYCODE_ESCAPE)) + close(); + } + + if (_ctx->bRefresh) + CORO_INVOKE_0(refreshAll); + + CORO_END_CODE; +} + + +void RMOptionScreen::draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim) { + CORO_BEGIN_CONTEXT; + int curTime; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + _ctx->curTime = g_vm->getTime(); + +#define FADE_SPEED 20 +#define SYNC (_ctx->curTime - _fadeTime) / 25 + + if (_bExit) + return; + + if (_fadeStep == 1) { + // Downhill fast + if (_fadeTime == -1) + _fadeY += FADE_SPEED; + else + _fadeY += FADE_SPEED * SYNC; + if (_fadeY > 480) { + _fadeY = 480; + _fadeStep++; + } + + // Set the part to draw the scrolling + prim->setSrc(RMRect(0, 480 - _fadeY, 640, 480)); + + } else if (_fadeStep == 2) { + // Bounce 1 + _fadeY -= FADE_SPEED / 2 * SYNC; + if (_fadeY < 400) { + _fadeY = 400; + _fadeStep++; + } + + prim->setSrc(RMRect(0, 480 - _fadeY, 640, 480)); + + } else if (_fadeStep == 3) { + _fadeY -= FADE_SPEED / 4 * SYNC; + if (_fadeY < 380) { + _fadeY = 380; + _fadeStep++; + } + + prim->setSrc(RMRect(0, 480 - _fadeY, 640, 480)); + + } else if (_fadeStep == 4) { + // Bounce 1 - 2 + _fadeY += FADE_SPEED / 3 * SYNC; + if (_fadeY > 420) { + _fadeY = 420; + _fadeStep++; + } + + prim->setSrc(RMRect(0, 480 - _fadeY, 640, 480)); + + } else if (_fadeStep == 5) { + _fadeY += FADE_SPEED / 2 * SYNC; + if (_fadeY > 480) { + _fadeY = 480; + _fadeStep++; + g_vm->hideLocation(); + } + + prim->setSrc(RMRect(0, 480 - _fadeY, 640, 480)); + + } else if (_fadeStep == 6) { + // Menu ON + + } else if (_fadeStep == 7) { + // Menu OFF + g_vm->showLocation(); + _fadeStep++; + + } else if (_fadeStep == 8) { + _fadeY -= FADE_SPEED * SYNC; + if (_fadeY < 0) { + _fadeY = 0; + _fadeStep++; + } + prim->setSrc(RMRect(0, 480 - _fadeY, 640, 480)); + + } else if (_fadeStep == 9) { + // Hello hello! + _bExit = true; + _fadeStep = 0; + + // Free memory + closeState(); + return; + + } else { + _fadeStep = 0; + } + + _fadeTime = _ctx->curTime; + + CORO_INVOKE_2(RMGfxWoodyBuffer::draw, bigBuf, prim); + + CORO_END_CODE; +} + +void RMOptionScreen::removeThis(CORO_PARAM, bool &result) { + if (_bExit) + result = true; + else + result = false; +} + + +bool RMOptionScreen::loadThumbnailFromSaveState(int nState, byte *lpDestBuf, Common::String &name, byte &diff) { + char namebuf[256]; + Common::InSaveFile *f; + char id[4]; + + // Cleans the destination + Common::fill(lpDestBuf, lpDestBuf + 160 * 120 * 2, 0); + name = "No name"; + diff = 10; + + // Get the savegame filename for the given slot + Common::String buf = g_vm->getSaveStateFileName(nState); + + // Try and open the savegame + f = g_system->getSavefileManager()->openForLoading(buf); + if (f == NULL) + return false; + + // Check to see if the file has a valid header + f->read(id, 4); + if (id[0] != 'R' || id[1] != 'M' || id[2] != 'S') { + delete f; + return false; + } + + if (id[3] < 0x3) { + // Very old version that doesn't have screenshots + delete f; + return true; + } + + // Load the screenshot + if ((id[3] >= 0x5) && (id[3] < 0x8)) { + // Read it as an LZO compressed data block + byte *cmpbuf; + uint32 cmpsize, size; + + cmpbuf = new byte[160 * 120 * 4]; + + // Read in the compressed data + cmpsize = f->readUint32LE(); + f->read(cmpbuf, cmpsize); + + lzo1x_decompress(cmpbuf, cmpsize, lpDestBuf, &size); + + delete[] cmpbuf; + } else { + // Read in the screenshot as an uncompressed data block + if (id[3] >= 8) + // Recent versions use hardcoded 160x120 uncomrpessed data, so size can be skipped + f->skip(4); + + f->read(lpDestBuf, 160 * 120 * 2); + } + + if (id[3] >= 0x5) { + // Read in the difficulty level + diff = f->readByte(); + } + + if (id[3] < 0x4) { + // Savegame version doesn't have a stored name + delete f; + return true; + } + + int bufSize = f->readByte(); + f->read(namebuf, bufSize); + namebuf[bufSize] = '\0'; + name = namebuf; + + delete f; + return true; +} + +/****************************************************************************\ +* RMPointer Methods +\****************************************************************************/ + +RMPointer::RMPointer() { + Common::fill(_pointer, _pointer + 16, (RMGfxSourceBuffer8 *)NULL); + Common::fill(_specialPointer, _specialPointer + 16, (RMItem *)NULL); + + _nCurPointer = _nCurSpecialPointer = 0; + _nCurCustomPointer = NULL; +} + +RMPointer::~RMPointer() { + close(); +} + +void RMPointer::init() { + for (int i = 0; i < 5; i++) { + RMResRaw res(RES_P_GO + i); + + _pointer[i] = new RMGfxSourceBuffer8RLEByteAA; + _pointer[i]->init(res, res.width(), res.height(), false); + _pointer[i]->loadPaletteWA(RES_P_PAL); + } + + for (int i = 0; i < 5; i++) { + RMRes res(RES_P_PAP1 + i); + Common::SeekableReadStream *ds = res.getReadStream(); + _specialPointer[i] = new RMItem; + _specialPointer[i]->readFromStream(*ds); + delete ds; + } + + //m_hotspot[0].set(19,5); + _hotspot[0].set(5, 1); + _hotspot[1].set(32, 28); + _hotspot[2].set(45, 23); + _hotspot[3].set(35, 25); + _hotspot[4].set(32, 28); + + // Default=GO + _nCurPointer = 0; + _nCurSpecialPointer = 0; +} + +void RMPointer::close() { + for (int i = 0; i < 5; i++) { + if (_pointer[i] != NULL) { + delete _pointer[i]; + _pointer[i] = NULL; + } + + if (_specialPointer[i] != NULL) { + delete _specialPointer[i]; + _specialPointer[i] = NULL; + } + } +} + +void RMPointer::draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim) { + CORO_BEGIN_CONTEXT; + int n; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + // Check the pointer + _ctx->n = _nCurPointer; + if (_ctx->n == TA_COMBINE) + _ctx->n = TA_USE; + + _cursorHotspot = _hotspot[_ctx->n]; + + // Call the Draw method of the pointer + if (_nCurSpecialPointer == 0) { + // WORKAROUND: updateCursor gets called too early sometimes (for example, when + // the cursor is released over the TA_PERORATE option), via setAction. + if (_ctx->n > 4) + _ctx->n = 0; + + CORO_INVOKE_2(_pointer[_ctx->n]->draw, bigBuf, prim); + } else { + if (_nCurSpecialPointer == PTR_CUSTOM) + CORO_INVOKE_2(_nCurCustomPointer->draw, bigBuf, prim); + else + // Call the draw on the special pointer + CORO_INVOKE_2(_specialPointer[_nCurSpecialPointer - 1]->draw, bigBuf, prim); + } + + CORO_END_CODE; +} + +int RMPointer::curAction() { + if (_nCurSpecialPointer != 0) + return 0; + + return _nCurPointer; +} + +/** + * Show the cursor + */ +void RMPointer::showCursor() { + if (!CursorMan.isVisible()) { + CursorMan.showMouse(true); + + updateCursor(); + } +} + +/** + * Hide the cursor + */ +void RMPointer::hideCursor() { + if (CursorMan.isVisible()) { + CursorMan.showMouse(false); + } +} + +void RMPointer::doFrame() { + // Update the cursor animation if needed. + if (_nCurSpecialPointer == 0 || _nCurSpecialPointer == PTR_CUSTOM) + return; + + RMGfxTargetBuffer buf; + if (_specialPointer[_nCurSpecialPointer - 1]->doFrame(&buf, false)) + updateCursor(); +} + +void RMPointer::updateCursor() { + // Create an intermediate buffer and draw the cursor onto it + RMGfxTargetBuffer buf; + buf.create(64, 64, 16); + RMGfxPrimitive prim; + + draw(Common::nullContext, buf, &prim); + + // Get a pointer to the cursor data + byte *cursorData = buf; + + // If in black & white mode, convert the cursor + if (GLOBALS._bCfgAnni30) { + if (!RMGfxTargetBuffer::_precalcTable) { + RMGfxTargetBuffer::createBWPrecalcTable(); + } + uint16 *src = (uint16 *)cursorData; + for (int i = 0; i < 64; i++) { + uint16 *lineP = src; + for (int j = 0; j < 64; j++) { + lineP[j] = RMGfxTargetBuffer::_precalcTable[lineP[j] & 0x7FFF]; + } + src += 64; + } + } + + // Get the raw pixel data and set the cursor to it + Graphics::PixelFormat pixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0); + CursorMan.replaceCursor(cursorData, 64, 64, _cursorHotspot._x, _cursorHotspot._y, 0, 1, &pixelFormat); +} + +/** + * Sets a new action as current + */ +void RMPointer::setAction(RMTonyAction action) { + _nCurPointer = action; + updateCursor(); +} + +/** + * Sets a new pointer + */ +void RMPointer::setSpecialPointer(PointerType ptr) { + _nCurSpecialPointer = ptr; + if (_nCurSpecialPointer && _nCurSpecialPointer != PTR_CUSTOM) + _specialPointer[ptr - 1]->setPattern(1); + + updateCursor(); +} + +RMPointer::PointerType RMPointer::getSpecialPointer() { + return (PointerType)_nCurSpecialPointer; +} + +/** + * Set the new custom pointer + */ +void RMPointer::setCustomPointer(RMGfxSourceBuffer8 *ptr) { + _nCurCustomPointer = ptr; + updateCursor(); +} + +} // End of namespace Tony diff --git a/engines/tony/game.h b/engines/tony/game.h new file mode 100644 index 0000000000..83a1ddaea1 --- /dev/null +++ b/engines/tony/game.h @@ -0,0 +1,340 @@ +/* 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 Tony Tough source code + * + * Copyright (c) 1997-2003 Nayma Software + */ + +#ifndef TONY_GAME_H +#define TONY_GAME_H + +#include "tony/gfxcore.h" +#include "tony/input.h" +#include "tony/loc.h" +#include "tony/utils.h" + +namespace Tony { + +#define INIT_GFX16_FROMRAW(dwRes, buf16) \ + raw = new RMResRaw(dwRes); \ + assert(raw->isValid()); \ + assert((buf16) == NULL); \ + (buf16) = new RMGfxSourceBuffer16(false); \ + (buf16)->init(*raw, raw->width(), raw->height()); \ + delete raw; + +#define INIT_GFX8_FROMRAW(raw, dwRes, buf8) \ + raw = new RMResRaw(dwRes); \ + assert(raw->isValid()); \ + assert((buf8) == NULL); \ + (buf8) = new RMGfxSourceBuffer8RLEByte(); \ + (buf8)->init(*raw, raw->width(), raw->height(), true); \ + delete raw; + +// X & Y dimensions of the adventure +#define RM_SX 640 +#define RM_SY 480 + +// X & Y dimensions of bigbuf +#define RM_BBX (RM_SX) +#define RM_BBY (RM_SY) + +// Skipping X & Y +#define RM_SKIPY ((RM_BBY - RM_SY) / 2) +#define RM_SKIPX 0 + +// Tony's actions +enum RMTonyAction { + TA_GOTO = 0, + TA_TAKE, + TA_USE, + TA_EXAMINE, + TA_TALK, + TA_PERORATE, + + TA_COMBINE = 10, + TA_RECEIVECOMBINE, + TA_COMBINEGIVE, + TA_RECEIVECOMBINEGIVE +}; + +// Global Functions +void mainEnableGUI(); +void mainDisableGUI(); + +// Classes +class RMPointer { +public: + enum PointerType { + PTR_NONE = 0, + PTR_ARROWUP, + PTR_ARROWDOWN, + PTR_ARROWLEFT, + PTR_ARROWRIGHT, + PTR_ARROWMAP, + PTR_CUSTOM + }; + +private: + RMGfxSourceBuffer8 *_pointer[16]; + RMPoint _hotspot[16]; + RMPoint _cursorHotspot; + + RMItem *_specialPointer[16]; + + int _nCurPointer; + int _nCurSpecialPointer; + + RMGfxSourceBuffer8 *_nCurCustomPointer; + +public: + /** + * Constructor & destructor + */ + RMPointer(); + virtual ~RMPointer(); + + /** + * Initialization + */ + void init(); + + /** + * Deinitialization + */ + void close(); + + /** + * Process a frame + */ + void doFrame(); + + /** + * draw method + */ + void draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim); + + /** + * Sets a new action as current + */ + void setAction(RMTonyAction action); + + /** + * Sets a new pointer + */ + void setSpecialPointer(PointerType ptr); + + PointerType getSpecialPointer(); + + /** + * Set the new custom pointer + */ + void setCustomPointer(RMGfxSourceBuffer8 *ptr); + + /** + * Return the current action to be applied according to the pointer + */ + int curAction(); + + /** + * Update the cursor + */ + void updateCursor(); + + /** + * Show the cursor + */ + void showCursor(); + + /** + * Hide the cursor + */ + void hideCursor(); +}; + +class RMOptionButton: public RMGfxTaskSetPrior { +public: + RMRect _rect; + RMGfxSourceBuffer16 *_buf; + bool _bActive; + bool _bHasGfx; + bool _bDoubleState; + +public: + RMOptionButton(uint32 dwRes, RMPoint pt, bool bDoubleState = false); + RMOptionButton(const RMRect &pt); + virtual ~RMOptionButton(); + + bool doFrame(const RMPoint &mousePos, bool bLeftClick, bool bRightClick); + virtual void draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim); + void addToList(RMGfxTargetBuffer &bigBuf); + bool isActive(); + void setActiveState(bool bState); +}; + +class RMOptionSlide : public RMGfxTaskSetPrior { +private: + RMOptionButton *_pushLeft; + RMOptionButton *_pushRight; + RMGfxSourceBuffer16 *_sliderCenter; + RMGfxSourceBuffer16 *_sliderLeft; + RMGfxSourceBuffer16 *_sliderRight; + RMGfxSourceBuffer16 *_sliderSingle; + int _nSlideSize; + RMPoint _pos; + int _nValue; + int _nMax; + int _nStep; + +public: + RMOptionSlide(const RMPoint &pt, int m_nRange = 100, int m_nStartValue = 0, int slideSize = 300); + virtual ~RMOptionSlide(); + + bool doFrame(const RMPoint &mousePos, bool bLeftClick, bool bRightClick); + virtual void draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim); + void addToList(RMGfxTargetBuffer &bigBuf); + + int getValue(); +}; + +class RMOptionScreen : public RMGfxWoodyBuffer { +private: + RMGfxSourceBuffer16 *_menu; + RMGfxSourceBuffer16 *_quitConfirm; + RMGfxSourceBuffer16 *_hideLoadSave; + RMOptionButton *_buttonQuitYes; + RMOptionButton *_buttonQuitNo; + RMOptionButton *_buttonExit; + RMOptionButton *_buttonQuit; + RMOptionButton *_buttonLoad; + RMOptionButton *_buttonSave; + RMOptionButton *_buttonGameMenu; + RMOptionButton *_buttonGfxMenu; + RMOptionButton *_buttonSoundMenu; + RMGfxSourceBuffer8 *_saveEasy; + RMGfxSourceBuffer8 *_saveHard; + RMGfxSourceBuffer16 *_curThumb[6]; + Common::String _curThumbName[6]; + byte _curThumbDiff[6]; + RMOptionButton *_buttonSave_States[6]; + RMOptionButton *_buttonSave_ArrowLeft; + RMOptionButton *_buttonSave_ArrowRight; + RMOptionButton *_buttonGfx_Tips; + + RMOptionButton *_buttonSound_DubbingOn; + RMOptionButton *_buttonSound_MusicOn; + RMOptionButton *_buttonSound_SFXOn; + + RMOptionSlide *_slideTonySpeed; + RMOptionSlide *_slideTextSpeed; + + + int _statePos; + bool _bEditSaveName; + int _nEditPos; + char _editName[256]; + + union { + RMOptionButton *_buttonGame_Lock; + RMOptionButton *_buttonGfx_Anni30; + RMOptionSlide *_sliderSound_Music; + }; + union { + RMOptionButton *_buttonGame_TimerizedText; + RMOptionButton *_buttonGfx_AntiAlias; + RMOptionSlide *_sliderSound_SFX; + }; + union { + RMOptionButton *_buttonGame_Scrolling; + RMOptionButton *_buttonGfx_Sottotitoli; + RMOptionSlide *_sliderSound_Dubbing; + }; + union { + RMOptionButton *_buttonGame_InterUp; + RMOptionButton *_buttonGfx_Trans; + }; + + int _fadeStep; + bool _bExit; + bool _bQuitConfirm; + int _fadeY; + int _fadeTime; + bool _bLoadMenuOnly; + bool _bNoLoadSave; + bool _bAlterGfx; + + enum OptionScreenState { + MENUGAME, + MENUGFX, + MENUSOUND, + MENULOAD, + MENUSAVE, + MENUNONE + }; + + OptionScreenState _nState; + OptionScreenState _nLastState; + +public: + RMOptionScreen(); + virtual ~RMOptionScreen(); + + void init(CORO_PARAM, RMGfxTargetBuffer &bigBuf, bool &result); + void initLoadMenuOnly(CORO_PARAM, RMGfxTargetBuffer &bigBuf, bool bAlternateGfx, bool &result); + void initSaveMenuOnly(CORO_PARAM, RMGfxTargetBuffer &bigBuf, bool bAlternateGfx, bool &result); + void initNoLoadSave(CORO_PARAM, RMGfxTargetBuffer &bigBuf, bool &result); + void reInit(RMGfxTargetBuffer &bigBuf); + bool close(); + bool isClosing(); + + // Overloaded methods + virtual int priority(); + virtual void draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim); + virtual void removeThis(CORO_PARAM, bool &result); + + /** + * Polling for the option screen + */ + void doFrame(CORO_PARAM, RMInput *m_input); + + /** + * Retrieves a savegame's thumbnail, description, and difficulty level + */ + static bool loadThumbnailFromSaveState(int numState, byte *lpDestBuf, Common::String &name, byte &diff); + +protected: + + // Initialization and state change + void initState(CORO_PARAM); + void closeState(); + void changeState(CORO_PARAM, OptionScreenState newState); + + // Repaint the options menu + void refreshAll(CORO_PARAM); + void refreshThumbnails(); +}; + +} // End of namespace Tony + +#endif diff --git a/engines/tony/gfxcore.cpp b/engines/tony/gfxcore.cpp new file mode 100644 index 0000000000..04ce01b0ed --- /dev/null +++ b/engines/tony/gfxcore.cpp @@ -0,0 +1,2192 @@ +/* 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 Tony Tough source code + * + * Copyright (c) 1997-2003 Nayma Software + */ + +#include "tony/gfxengine.h" +#include "tony/mpal/mpalutils.h" +#include "tony/tony.h" + +namespace Tony { + +/****************************************************************************\ +* RMGfxTask Methods +\****************************************************************************/ + +RMGfxTask::RMGfxTask() { + _nPrior = 0; + _nInList = 0; +} + +int RMGfxTask::priority() { + return _nPrior; +} + +void RMGfxTask::removeThis(CORO_PARAM, bool &result) { + result = true; +} + +/** + * Registration + */ +void RMGfxTask::Register() { + _nInList++; +} + +void RMGfxTask::unregister() { + _nInList--; + assert(_nInList >= 0); +} + +/****************************************************************************\ +* RMGfxTaskSetPrior Methods +\****************************************************************************/ + +void RMGfxTaskSetPrior::setPriority(int nPrior) { + _nPrior = nPrior; +} + + +/****************************************************************************\ +* RMGfxBuffer Methods +\****************************************************************************/ + +RMGfxBuffer::RMGfxBuffer() { + _dimx = _dimy = 0; + _origBuf = _buf = NULL; +} + +RMGfxBuffer::~RMGfxBuffer() { + destroy(); +} + +void RMGfxBuffer::create(int dimx, int dimy, int nBpp) { + // Destroy the buffer it is already exists + if (_buf != NULL) + destroy(); + + // Copy the parameters in the private members + _dimx = dimx; + _dimy = dimy; + + // Allocate a buffer + _origBuf = _buf = new byte[_dimx * _dimy * nBpp / 8]; + assert(_buf != NULL); + Common::fill(_origBuf, _origBuf + _dimx * _dimy * nBpp / 8, 0); +} + +void RMGfxBuffer::destroy() { + if (_origBuf != NULL && _origBuf == _buf) { + delete[] _origBuf; + _origBuf = _buf = NULL; + } +} + +void RMGfxBuffer::offsetY(int nLines, int nBpp) { + _buf += nLines * getDimx() * nBpp / 8; +} + + +RMGfxBuffer::operator byte *() { + return _buf; +} + +RMGfxBuffer::operator void *() { + return (void *)_buf; +} + +RMGfxBuffer::RMGfxBuffer(int dimx, int dimy, int nBpp) { + create(dimx, dimy, nBpp); +} + +int RMGfxBuffer::getDimx() { + return _dimx; +} + +int RMGfxBuffer::getDimy() { + return _dimy; +} + + +/****************************************************************************\ +* RMGfxSourceBuffer Methods +\****************************************************************************/ + +int RMGfxSourceBuffer::init(const byte *buf, int dimx, int dimy, bool bLoadPalette) { + create(dimx, dimy, getBpp()); + memcpy(_buf, buf, dimx * dimy * getBpp() / 8); + + // Invokes the method for preparing the surface (inherited) + prepareImage(); + + return dimx * dimy * getBpp() / 8; +} + +void RMGfxSourceBuffer::init(Common::ReadStream &ds, int dimx, int dimy, bool bLoadPalette) { + create(dimx, dimy, getBpp()); + ds.read(_buf, dimx * dimy * getBpp() / 8); + + // Invokes the method for preparing the surface (inherited) + prepareImage(); +} + +RMGfxSourceBuffer::~RMGfxSourceBuffer() { +} + +void RMGfxSourceBuffer::prepareImage() { + // Do nothing. Can be overloaded if necessary +} + +bool RMGfxSourceBuffer::clip2D(int &x1, int &y1, int &u, int &v, int &width, int &height, bool bUseSrc, RMGfxTargetBuffer *buf) { + int destw, desth; + + destw = buf->getDimx(); + desth = buf->getDimy(); + + if (!bUseSrc) { + u = v = 0; + width = _dimx; + height = _dimy; + } + + if (x1 > destw - 1) + return false; + + if (y1 > desth - 1) + return false; + + if (x1 < 0) { + width += x1; + if (width < 0) + return false; + u -= x1; + x1 = 0; + } + + if (y1 < 0) { + height += y1; + if (height < 0) + return false; + v -= y1; + y1 = 0; + } + + if (x1 + width - 1 > destw - 1) + width = destw - x1; + + if (y1 + height - 1 > desth - 1) + height = desth - y1; + + return (width > 1 && height > 1); +} + +/** + * Initializes a surface by resource Id + * + * @param resID Resource ID + * @param dimx Buffer X dimension + * @param dimy Buffer Y dimension + */ +int RMGfxSourceBuffer::init(uint32 resID, int dimx, int dimy, bool bLoadPalette) { + return init(RMRes(resID), dimx, dimy, bLoadPalette); +} + +void RMGfxSourceBuffer::offsetY(int nLines) { + RMGfxBuffer::offsetY(nLines, getBpp()); +} + +/****************************************************************************\ +* RMGfxWoodyBuffer Methods +\****************************************************************************/ + +RMGfxWoodyBuffer::~RMGfxWoodyBuffer() { + +} + +void RMGfxWoodyBuffer::draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + // Draw the OT list + CORO_INVOKE_0(drawOT); + + // Draw itself into the target buffer + CORO_INVOKE_2(RMGfxSourceBuffer16::draw, bigBuf, prim); + + CORO_END_CODE; +} + +RMGfxWoodyBuffer::RMGfxWoodyBuffer() { + +} + +RMGfxWoodyBuffer::RMGfxWoodyBuffer(int dimx, int dimy) + : RMGfxBuffer(dimx, dimy, 16) { +} + +/****************************************************************************\ +* RMGfxTargetBuffer Methods +\****************************************************************************/ + +RMGfxTargetBuffer::RMGfxTargetBuffer() { + _otlist = NULL; + _otSize = 0; + _trackDirtyRects = false; +} + +RMGfxTargetBuffer::~RMGfxTargetBuffer() { + clearOT(); +} + +void RMGfxTargetBuffer::clearOT() { + OTList *cur, *n; + + cur = _otlist; + + while (cur != NULL) { + cur->_prim->_task->unregister(); + delete cur->_prim; + n = cur->_next; + delete cur; + cur = n; + } + + _otlist = NULL; +} + +void RMGfxTargetBuffer::drawOT(CORO_PARAM) { + CORO_BEGIN_CONTEXT; + OTList *cur; + OTList *prev; + OTList *next; + RMGfxPrimitive *myprim; + bool result; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + _ctx->prev = NULL; + _ctx->cur = _otlist; + + while (_ctx->cur != NULL) { + // Call the task Draw method, passing it a copy of the original + _ctx->myprim = _ctx->cur->_prim->duplicate(); + CORO_INVOKE_2(_ctx->cur->_prim->_task->draw, *this, _ctx->myprim); + delete _ctx->myprim; + + // Check if it's time to remove the task from the OT list + CORO_INVOKE_1(_ctx->cur->_prim->_task->removeThis, _ctx->result); + if (_ctx->result) { + // De-register the task + _ctx->cur->_prim->_task->unregister(); + + // Delete task, freeing the memory + delete _ctx->cur->_prim; + _ctx->next = _ctx->cur->_next; + delete _ctx->cur; + + // If it was the first item, update the list head + if (_ctx->prev == NULL) + _otlist = _ctx->next; + // Otherwise update the next pinter of the previous item + else + _ctx->prev->_next = _ctx->next; + + _ctx->cur = _ctx->next; + } else { + // Update the pointer to the previous item, and the current to the next + _ctx->prev = _ctx->cur; + _ctx->cur = _ctx->cur->_next; + } + } + + CORO_END_CODE; +} + +void RMGfxTargetBuffer::addPrim(RMGfxPrimitive *prim) { + int nPrior; + OTList *cur, *n; + + // Warn of the OT listing + prim->_task->Register(); + + // Check the priority + nPrior = prim->_task->priority(); + n = new OTList(prim); + + // Empty list + if (_otlist == NULL) { + _otlist = n; + _otlist->_next = NULL; + } + // Inclusion in the head + else if (nPrior < _otlist->_prim->_task->priority()) { + n->_next = _otlist; + _otlist = n; + } else { + cur = _otlist; + while (cur->_next != NULL && nPrior > cur->_next->_prim->_task->priority()) + cur = cur->_next; + + n->_next = cur->_next; + cur->_next = n; + } +} + +void RMGfxTargetBuffer::addDirtyRect(const Common::Rect &r) { + assert(r.isValidRect()); + if (_trackDirtyRects && r.width() > 0 && r.height() > 0) + _currentDirtyRects.push_back(r); +} + +Common::List<Common::Rect> &RMGfxTargetBuffer::getDirtyRects() { + // Copy rects from both the current and previous frame into the output dirty rects list + Common::List<Common::Rect>::iterator i; + _dirtyRects.clear(); + for (i = _previousDirtyRects.begin(); i != _previousDirtyRects.end(); ++i) + _dirtyRects.push_back(*i); + for (i = _currentDirtyRects.begin(); i != _currentDirtyRects.end(); ++i) + _dirtyRects.push_back(*i); + + mergeDirtyRects(); + return _dirtyRects; +} + +/** + * Move the set of dirty rects from the finished current frame into the previous frame list. + */ +void RMGfxTargetBuffer::clearDirtyRects() { + Common::List<Common::Rect>::iterator i; + _previousDirtyRects.clear(); + for (i = _currentDirtyRects.begin(); i != _currentDirtyRects.end(); ++i) + _previousDirtyRects.push_back(*i); + + _currentDirtyRects.clear(); +} + +/** + * Merges any clipping rectangles that overlap to try and reduce + * the total number of clip rectangles. + */ +void RMGfxTargetBuffer::mergeDirtyRects() { + if (_dirtyRects.size() <= 1) + return; + + Common::List<Common::Rect>::iterator rOuter, rInner; + + for (rOuter = _dirtyRects.begin(); rOuter != _dirtyRects.end(); ++rOuter) { + rInner = rOuter; + while (++rInner != _dirtyRects.end()) { + + if ((*rOuter).intersects(*rInner)) { + // these two rectangles overlap or + // are next to each other - merge them + + (*rOuter).extend(*rInner); + + // remove the inner rect from the list + _dirtyRects.erase(rInner); + + // move back to beginning of list + rInner = rOuter; + } + } + } +} + +uint16 *RMGfxTargetBuffer::_precalcTable = NULL; + +/** + * Set up the black & white precalculated mapping table. This is only + * called if the user selects the black & white option. + */ +void RMGfxTargetBuffer::createBWPrecalcTable() { + _precalcTable = new uint16[0x8000]; + + for (int i = 0; i < 0x8000; i++) { + int r = (i >> 10) & 0x1F; + int g = (i >> 5) & 0x1F; + int b = i & 0x1F; + + int min = MIN(r, MIN(g, b)); + int max = MAX(r, MAX(g, b)); + + min = (min + max) / 2; + + r = CLIP(min + 8 - 8, 0, 31); + g = CLIP(min + 5 - 8, 0, 31); + b = CLIP(min + 0 - 8, 0, 31); + + _precalcTable[i] = (r << 10) | (g << 5) | b; + } +} + +/** + * Frees the black & white precalculated mapping table. + */ +void RMGfxTargetBuffer::freeBWPrecalcTable() { + delete[] _precalcTable; + _precalcTable = NULL; +} + +RMGfxTargetBuffer::operator byte *() { + return _buf; +} + +RMGfxTargetBuffer::operator void *() { + return (void *)_buf; +} + +RMGfxTargetBuffer::operator uint16 *() { + // FIXME: This may not be endian safe + return (uint16 *)_buf; +} + +/** + * Offseting buffer + */ +void RMGfxTargetBuffer::offsetY(int nLines) { + RMGfxBuffer::offsetY(nLines, 16); +} + +void RMGfxTargetBuffer::setTrackDirtyRects(bool v) { + _trackDirtyRects = v; +} + +bool RMGfxTargetBuffer::getTrackDirtyRects() const { + return _trackDirtyRects; +} + +/****************************************************************************\ +* RMGfxSourceBufferPal Methods +\****************************************************************************/ + +RMGfxSourceBufferPal::~RMGfxSourceBufferPal() { + +} + +int RMGfxSourceBufferPal::loadPaletteWA(const byte *buf, bool bSwapped) { + if (bSwapped) { + for (int i = 0; i < (1 << getBpp()); i++) { + _pal[i * 3 + 0] = buf[i * 3 + 2]; + _pal[i * 3 + 1] = buf[i * 3 + 1]; + _pal[i * 3 + 2] = buf[i * 3 + 0]; + } + } else { + memcpy(_pal, buf, (1 << getBpp()) * 3); + } + + preparePalette(); + + return (1 << getBpp()) * 3; +} + +int RMGfxSourceBufferPal::loadPalette(const byte *buf) { + for (int i = 0; i < 256; i++) + memcpy(_pal + i * 3, buf + i * 4, 3); + + preparePalette(); + + return (1 << getBpp()) * 4; +} + +void RMGfxSourceBufferPal::preparePalette() { + for (int i = 0; i < 256; i++) { + _palFinal[i] = (((int)_pal[i * 3 + 0] >> 3) << 10) | + (((int)_pal[i * 3 + 1] >> 3) << 5) | + (((int)_pal[i * 3 + 2] >> 3) << 0); + } +} + +int RMGfxSourceBufferPal::init(const byte *buf, int dimx, int dimy, bool bLoadPalette) { + // Load the RAW image + int read = RMGfxSourceBuffer::init(buf, dimx, dimy); + + // Load the palette if necessary + if (bLoadPalette) + read += loadPaletteWA(&buf[read]); + + return read; +} + +void RMGfxSourceBufferPal::init(Common::ReadStream &ds, int dimx, int dimy, bool bLoadPalette) { + // Load the RAW image + RMGfxSourceBuffer::init(ds, dimx, dimy); + + // Load the palette if necessary + if (bLoadPalette) { + byte *suxpal = new byte[256 * 3]; + ds.read(suxpal, 256 * 3); + loadPaletteWA(suxpal); + delete[] suxpal; + } +} + +int RMGfxSourceBufferPal::loadPalette(uint32 resID) { + return loadPalette(RMRes(resID)); +} + +int RMGfxSourceBufferPal::loadPaletteWA(uint32 resID, bool bSwapped) { + return loadPaletteWA(RMRes(resID), bSwapped); +} + +/****************************************************************************\ +* RMGfxSourceBuffer4 Methods +\****************************************************************************/ + +void RMGfxSourceBuffer4::draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim) { +} + +RMGfxSourceBuffer4::RMGfxSourceBuffer4(int dimx, int dimy) + : RMGfxBuffer(dimx, dimy, 4) { + setPriority(0); +} + + +/** + * Returns the number of bits per pixel of the surface + * + * @returns Bit per pixel + */ +int RMGfxSourceBuffer4::getBpp() { + return 4; +} + +void RMGfxSourceBuffer4::create(int dimx, int dimy) { + RMGfxBuffer::create(dimx, dimy, 4); +} + +/****************************************************************************\ +* RMGfxSourceBuffer8 Methods +\****************************************************************************/ + +RMGfxSourceBuffer8::~RMGfxSourceBuffer8() { + +} + +void RMGfxSourceBuffer8::draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim) { + int width, height, u, v; + int bufx = bigBuf.getDimx(); + uint16 *buf = bigBuf; + byte *raw = _buf; + + // Destination buffer + RMRect dst; + if (prim->haveDst()) + dst = prim->getDst(); + + // Clipping + if (prim->haveSrc()) { + u = prim->getSrc()._x1; + v = prim->getSrc()._y1; + + width = prim->getSrc().width(); + height = prim->getSrc().height(); + } + + if (!clip2D(dst._x1, dst._y1, u, v, width, height, prim->haveSrc(), &bigBuf)) + return; + + // Starting offset into the buffer + buf += dst._y1 * bufx + dst._x1; + + // Normal step + if (_bTrasp0) { + for (int y = 0; y < height; y++) { + raw = _buf + (y + v) * _dimx + u; + + for (int x = 0; x < width; x++) { + if (*raw) + *buf = _palFinal[*raw]; + buf++; + raw++; + } + + buf += bufx - width; + } + } else { + for (int y = 0; y < height; y++) { + raw = _buf + (y + v) * _dimx + u; + + for (int x = 0; x < width; x += 2) { + buf[0] = _palFinal[raw[0]]; + buf[1] = _palFinal[raw[1]]; + + buf += 2; + raw += 2; + } + + buf += bufx - width; + } + } + + // Specify the drawn area + bigBuf.addDirtyRect(Common::Rect(dst._x1, dst._y1, dst._x1 + width, dst._y1 + height)); +} + +RMGfxSourceBuffer8::RMGfxSourceBuffer8(int dimx, int dimy) + : RMGfxBuffer(dimx, dimy, 8) { + setPriority(0); + _bTrasp0 = false; +} + +RMGfxSourceBuffer8::RMGfxSourceBuffer8(bool bTrasp0) { + _bTrasp0 = bTrasp0; +} + + +/** + * Returns the number of bits per pixel of the surface + * + * @returns Bit per pixel + */ +int RMGfxSourceBuffer8::getBpp() { + return 8; +} + +void RMGfxSourceBuffer8::create(int dimx, int dimy) { + RMGfxBuffer::create(dimx, dimy, 8); +} + +#define GETRED(x) (((x) >> 10) & 0x1F) +#define GETGREEN(x) (((x) >> 5) & 0x1F) +#define GETBLUE(x) ((x) & 0x1F) + + +/****************************************************************************\ +* RMGfxSourceBuffer8AB Methods +\****************************************************************************/ + +RMGfxSourceBuffer8AB::~RMGfxSourceBuffer8AB() { + +} + +int RMGfxSourceBuffer8AB::calcTrasp(int fore, int back) { + int r = (GETRED(fore) >> 2) + (GETRED(back) >> 1); + int g = (GETGREEN(fore) >> 2) + (GETGREEN(back) >> 1); + int b = (GETBLUE(fore) >> 2) + (GETBLUE(back) >> 1); + + if (r > 0x1F) + r = 0x1F; + + if (g > 0x1F) + g = 0x1F; + + if (b > 0x1F) + b = 0x1F; + + return (r << 10) | (g << 5) | b; +} + + +void RMGfxSourceBuffer8AB::draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim) { + int width, height, u, v; + int bufx = bigBuf.getDimx(); + uint16 *buf = bigBuf; + byte *raw = _buf; + + // Destination buffer + RMRect dst; + if (prim->haveDst()) + dst = prim->getDst(); + + // Clipping + if (prim->haveSrc()) { + u = prim->getSrc()._x1; + v = prim->getSrc()._y1; + + width = prim->getSrc().width(); + height = prim->getSrc().height(); + } + + if (!clip2D(dst._x1, dst._y1, u, v, width, height, prim->haveSrc(), &bigBuf)) + return; + + // Starting offset into the buffer + buf += dst._y1 * bufx + dst._x1; + + // Passaggio normale + if (_bTrasp0) { + for (int y = 0; y < height; y++) { + raw = _buf + (y + v) * _dimx + u; + + for (int x = 0; x < width; x++) { + if (*raw) + *buf = calcTrasp(_palFinal[*raw], *buf); + + buf++; + raw++; + } + + buf += bufx - width; + } + } else { + for (int y = 0; y < height; y++) { + raw = _buf + (y + v) * _dimx + u; + + for (int x = 0; x < width; x += 2) { + buf[0] = calcTrasp(_palFinal[raw[0]], buf[0]); + buf[1] = calcTrasp(_palFinal[raw[1]], buf[1]); + + buf += 2; + raw += 2; + } + + buf += bufx - width; + } + } + + // Specify the drawn area + bigBuf.addDirtyRect(Common::Rect(dst._x1, dst._y1, dst._x1 + width, dst._y1 + height)); +} + + + +/****************************************************************************\ +* RMGfxSourceBuffer8RLE Methods +\****************************************************************************/ + +byte RMGfxSourceBuffer8RLE::_megaRLEBuf[512 * 1024]; + +void RMGfxSourceBuffer8RLE::setAlphaBlendColor(int color) { + _alphaBlendColor = color; +} + +RMGfxSourceBuffer8RLE::RMGfxSourceBuffer8RLE() { + _alphaBlendColor = -1; + _bNeedRLECompress = true; + _buf = NULL; + + _alphaR = _alphaG = _alphaB = 0; +} + +RMGfxSourceBuffer8RLE::~RMGfxSourceBuffer8RLE() { + if (_buf != NULL) { + delete[] _buf; + _buf = NULL; + } +} + + +int RMGfxSourceBuffer8RLE::init(const byte *buf, int dimx, int dimy, bool bLoadPalette) { + return RMGfxSourceBufferPal::init(buf, dimx, dimy, bLoadPalette); +} + +void RMGfxSourceBuffer8RLE::init(Common::ReadStream &ds, int dimx, int dimy, bool bLoadPalette) { + if (_bNeedRLECompress) { + RMGfxSourceBufferPal::init(ds, dimx, dimy, bLoadPalette); + } else { + int size = ds.readSint32LE(); + _buf = new byte[size]; + ds.read(_buf, size); + + _dimx = dimx; + _dimy = dimy; + } +} + +void RMGfxSourceBuffer8RLE::preparePalette() { + // Invoke the parent method + RMGfxSourceBuffer8::preparePalette(); + + // Handle RGB alpha blending + if (_alphaBlendColor != -1) { + _alphaR = (_palFinal[_alphaBlendColor] >> 10) & 0x1F; + _alphaG = (_palFinal[_alphaBlendColor] >> 5) & 0x1F; + _alphaB = (_palFinal[_alphaBlendColor]) & 0x1F; + } +} + +void RMGfxSourceBuffer8RLE::prepareImage() { + // Invoke the parent method + RMGfxSourceBuffer::prepareImage(); + + // Compress + compressRLE(); +} + +void RMGfxSourceBuffer8RLE::setAlreadyCompressed() { + _bNeedRLECompress = false; +} + +void RMGfxSourceBuffer8RLE::compressRLE() { + byte *startline; + byte *cur; + byte curdata; + byte *src; + byte *startsrc; + int rep; + + // Perform RLE compression for lines + cur = _megaRLEBuf; + src = _buf; + for (int y = 0; y < _dimy; y++) { + // Save the beginning of the line + startline = cur; + + // Leave space for the length of the line + cur += 2; + + // It starts from the empty space + curdata = 0; + rep = 0; + startsrc = src; + for (int x = 0; x < _dimx;) { + if ((curdata == 0 && *src == 0) || (curdata == 1 && *src == _alphaBlendColor) + || (curdata == 2 && (*src != _alphaBlendColor && *src != 0))) { + src++; + rep++; + x++; + } else { + if (curdata == 0) { + rleWriteTrasp(cur, rep); + curdata++; + } else if (curdata == 1) { + rleWriteAlphaBlend(cur, rep); + curdata++; + } else { + rleWriteData(cur, rep, startsrc); + curdata = 0; + } + + rep = 0; + startsrc = src; + } + } + + // Pending data? + if (curdata == 1) { + rleWriteAlphaBlend(cur, rep); + rleWriteData(cur, 0, NULL); + } + + if (curdata == 2) { + rleWriteData(cur, rep, startsrc); + } + + // End of line + rleWriteEOL(cur); + + // Write the length of the line + WRITE_LE_UINT16(startline, (uint16)(cur - startline)); + } + + // Delete the original image + delete[] _buf; + + // Copy the compressed image + int bufSize = cur - _megaRLEBuf; + _buf = new byte[bufSize]; + Common::copy(_megaRLEBuf, _megaRLEBuf + bufSize, _buf); +} + +void RMGfxSourceBuffer8RLE::draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim) { + byte *src; + uint16 *buf = bigBuf; + int u, v, width, height; + + // Clipping + int x1 = prim->getDst()._x1; + int y1 = prim->getDst()._y1; + if (!clip2D(x1, y1, u, v, width, height, false, &bigBuf)) + return; + + // Go forward through the RLE lines + src = _buf; + for (int y = 0; y < v; y++) + src += READ_LE_UINT16(src); + + // Calculate the position in the destination buffer + buf += y1 * bigBuf.getDimx(); + + // Loop + if (prim->isFlipped()) { +// Eliminate horizontal clipping +// width = m_dimx; +// x1=prim->Dst().x1; + + // Clipping + u = _dimx - (width + u); + x1 = (prim->getDst()._x1 + _dimx - 1) - u; + + if (width > x1) + width = x1; + + // Specify the drawn area + bigBuf.addDirtyRect(Common::Rect(x1 - width, y1, x1 + 1, y1 + height)); + + for (int y = 0; y < height; y++) { + // Decompression + rleDecompressLineFlipped(buf + x1, src + 2, u, width); + + // Next line + src += READ_LE_UINT16(src); + + // Skip to the next line + buf += bigBuf.getDimx(); + } + } else { + // Specify the drawn area + bigBuf.addDirtyRect(Common::Rect(x1, y1, x1 + width, y1 + height)); + + for (int y = 0; y < height; y++) { + // Decompression + rleDecompressLine(buf + x1, src + 2, u, width); + + // Next line + src += READ_LE_UINT16(src); + + // Skip to the next line + buf += bigBuf.getDimx(); + } + } +} + + +/****************************************************************************\ +* RMGfxSourceBuffer8RLEByte Methods +\****************************************************************************/ + +RMGfxSourceBuffer8RLEByte::~RMGfxSourceBuffer8RLEByte() { +} + +void RMGfxSourceBuffer8RLEByte::rleWriteTrasp(byte *&cur, int rep) { + assert(rep < 255); + *cur ++ = rep; +} + +void RMGfxSourceBuffer8RLEByte::rleWriteAlphaBlend(byte *&cur, int rep) { + assert(rep < 255); + *cur ++ = rep; +} + +void RMGfxSourceBuffer8RLEByte::rleWriteData(byte *&cur, int rep, byte *src) { + assert(rep < 256); + + *cur ++ = rep; + if (rep > 0) { + memcpy(cur, src, rep); + cur += rep; + src += rep; + } + + return; +} + +void RMGfxSourceBuffer8RLEByte::rleWriteEOL(byte *&cur) { + *cur ++ = 0xFF; +} + +void RMGfxSourceBuffer8RLEByte::rleDecompressLine(uint16 *dst, byte *src, int nStartSkip, int nLength) { + int n; + + if (nStartSkip == 0) + goto RLEByteDoTrasp; + + while (1) { + assert(nStartSkip > 0); + + // TRASP + n = *src++; + if (n == 0xFF) + return; + + if (n >= nStartSkip) { + dst += n - nStartSkip; + nLength -= n - nStartSkip; + if (nLength > 0) + goto RLEByteDoAlpha; + else + return; + } + nStartSkip -= n; + + + assert(nStartSkip > 0); + + // ALPHA + n = *src++; + if (n >= nStartSkip) { + n -= nStartSkip; + goto RLEByteDoAlpha2; + } + nStartSkip -= n; + + assert(nStartSkip > 0); + + // DATA + n = *src++; + if (n >= nStartSkip) { + src += nStartSkip; + n -= nStartSkip; + goto RLEByteDoCopy2; + } + nStartSkip -= n; + src += n; + } + + + while (1) { +RLEByteDoTrasp: + // Get the trasp of s**t + n = *src++; + + // EOL? + if (n == 0xFF) + return; + + dst += n; + nLength -= n; + if (nLength <= 0) + return; + +RLEByteDoAlpha: + // Alpha + n = *src++; + +RLEByteDoAlpha2: + if (n > nLength) + n = nLength; + for (int i = 0; i < n; i++) { + int r = (*dst >> 10) & 0x1F; + int g = (*dst >> 5) & 0x1F; + int b = *dst & 0x1F; + + r = (r >> 2) + (_alphaR >> 1); + g = (g >> 2) + (_alphaG >> 1); + b = (b >> 2) + (_alphaB >> 1); + + *dst ++ = (r << 10) | (g << 5) | b; + } + + nLength -= n; + if (!nLength) + return; + assert(nLength > 0); + +//RLEByteDoCopy: + // Copy the stuff + n = *src++; + +RLEByteDoCopy2: + if (n > nLength) + n = nLength; + + for (int i = 0; i < n; i++) + *dst ++ = _palFinal[*src++]; + + nLength -= n; + if (!nLength) + return; + assert(nLength > 0); + } +} + +void RMGfxSourceBuffer8RLEByte::rleDecompressLineFlipped(uint16 *dst, byte *src, int nStartSkip, int nLength) { + int n; + + if (nStartSkip == 0) + goto RLEByteFlippedDoTrasp; + + while (1) { + assert(nStartSkip > 0); + + // TRASP + n = *src++; + if (n == 0xFF) + return; + + if (n >= nStartSkip) { + dst -= n - nStartSkip; + nLength -= n - nStartSkip; + if (nLength > 0) + goto RLEByteFlippedDoAlpha; + else + return; + } + nStartSkip -= n; + + + assert(nStartSkip > 0); + + // ALPHA + n = *src++; + if (n >= nStartSkip) { + n -= nStartSkip; + goto RLEByteFlippedDoAlpha2; + } + nStartSkip -= n; + + assert(nStartSkip > 0); + + // DATA + n = *src++; + if (n >= nStartSkip) { + src += nStartSkip; + n -= nStartSkip; + goto RLEByteFlippedDoCopy2; + } + nStartSkip -= n; + src += n; + } + + + while (1) { +RLEByteFlippedDoTrasp: + // Get the trasp of s**t + n = *src++; + + // EOL? + if (n == 0xFF) + return; + + dst -= n; + nLength -= n; + if (nLength <= 0) + return; + +RLEByteFlippedDoAlpha: + // Alpha + n = *src++; + +RLEByteFlippedDoAlpha2: + if (n > nLength) + n = nLength; + for (int i = 0; i < n; i++) { + int r = (*dst >> 10) & 0x1F; + int g = (*dst >> 5) & 0x1F; + int b = *dst & 0x1F; + + r = (r >> 2) + (_alphaR >> 1); + g = (g >> 2) + (_alphaG >> 1); + b = (b >> 2) + (_alphaB >> 1); + + *dst-- = (r << 10) | (g << 5) | b; + } + + nLength -= n; + if (!nLength) + return; + assert(nLength > 0); + +//RLEByteFlippedDoCopy: + // Copy the data + n = *src++; + +RLEByteFlippedDoCopy2: + if (n > nLength) + n = nLength; + + for (int i = 0; i < n; i++) + *dst-- = _palFinal[*src++]; + + nLength -= n; + if (!nLength) + return; + assert(nLength > 0); + } +} + + +/****************************************************************************\ +* RMGfxSourceBuffer8RLEWord Methods +\****************************************************************************/ + +RMGfxSourceBuffer8RLEWord::~RMGfxSourceBuffer8RLEWord() { + +} + +void RMGfxSourceBuffer8RLEWord::rleWriteTrasp(byte *&cur, int rep) { + WRITE_LE_UINT16(cur, rep); + cur += 2; +} + +void RMGfxSourceBuffer8RLEWord::rleWriteAlphaBlend(byte *&cur, int rep) { + WRITE_LE_UINT16(cur, rep); + cur += 2; +} + +void RMGfxSourceBuffer8RLEWord::rleWriteData(byte *&cur, int rep, byte *src) { + WRITE_LE_UINT16(cur, rep); + cur += 2; + + if (rep > 0) { + memcpy(cur, src, rep); + cur += rep; + src += rep; + } +} + +void RMGfxSourceBuffer8RLEWord::rleWriteEOL(byte *&cur) { + *cur ++ = 0xFF; + *cur ++ = 0xFF; +} + +void RMGfxSourceBuffer8RLEWord::rleDecompressLine(uint16 *dst, byte *src, int nStartSkip, int nLength) { + int n; + + if (nStartSkip == 0) + goto RLEWordDoTrasp; + + while (1) { + assert(nStartSkip > 0); + + // TRASP + n = READ_LE_UINT16(src); + src += 2; + + if (n == 0xFFFF) + return; + + if (n >= nStartSkip) { + dst += n - nStartSkip; + nLength -= n - nStartSkip; + + if (nLength > 0) + goto RLEWordDoAlpha; + else + return; + } + nStartSkip -= n; + + assert(nStartSkip > 0); + + // ALPHA + n = READ_LE_UINT16(src); + src += 2; + + if (n >= nStartSkip) { + n -= nStartSkip; + goto RLEWordDoAlpha2; + } + nStartSkip -= n; + + // DATA + n = READ_LE_UINT16(src); + src += 2; + + if (n >= nStartSkip) { + src += nStartSkip; + n -= nStartSkip; + goto RLEWordDoCopy2; + } + nStartSkip -= n; + src += n; + } + + + while (1) { +RLEWordDoTrasp: + // Get the trasp of s**t + n = READ_LE_UINT16(src); + src += 2; + + // EOL? + if (n == 0xFFFF) + return; + + dst += n; + + nLength -= n; + if (nLength <= 0) + return; + +RLEWordDoAlpha: + n = READ_LE_UINT16(src); + src += 2; + +RLEWordDoAlpha2: + + if (n > nLength) + n = nLength; + + for (int i = 0; i < n; i++) { + int r = (*dst >> 10) & 0x1F; + int g = (*dst >> 5) & 0x1F; + int b = *dst & 0x1F; + + r = (r >> 2) + (_alphaR >> 1); + g = (g >> 2) + (_alphaG >> 1); + b = (b >> 2) + (_alphaB >> 1); + + *dst++ = (r << 10) | (g << 5) | b; + } + + nLength -= n; + if (!nLength) + return; + + assert(nLength > 0); + +//RLEWordDoCopy: + // Copy the data + n = READ_LE_UINT16(src); + src += 2; + +RLEWordDoCopy2: + if (n > nLength) + n = nLength; + + for (int i = 0; i < n; i++) + *dst++ = _palFinal[*src++]; + + nLength -= n; + if (!nLength) + return; + + assert(nLength > 0); + + } +} + +void RMGfxSourceBuffer8RLEWord::rleDecompressLineFlipped(uint16 *dst, byte *src, int nStartSkip, int nLength) { + int n; + + if (nStartSkip == 0) + goto RLEWordFlippedDoTrasp; + + while (1) { + assert(nStartSkip > 0); + + // TRASP + n = READ_LE_UINT16(src); + src += 2; + + if (n == 0xFFFF) + return; + + if (n >= nStartSkip) { + dst -= n - nStartSkip; + nLength -= n - nStartSkip; + + if (nLength > 0) + goto RLEWordFlippedDoAlpha; + else + return; + } + nStartSkip -= n; + + assert(nStartSkip > 0); + + // ALPHA + n = READ_LE_UINT16(src); + src += 2; + + if (n >= nStartSkip) { + n -= nStartSkip; + goto RLEWordFlippedDoAlpha2; + } + nStartSkip -= n; + + // DATA + n = READ_LE_UINT16(src); + src += 2; + + if (n >= nStartSkip) { + src += nStartSkip; + n -= nStartSkip; + goto RLEWordFlippedDoCopy2; + } + nStartSkip -= n; + src += n; + } + + + while (1) { +RLEWordFlippedDoTrasp: + // Get the trasp of s**t + n = READ_LE_UINT16(src); + src += 2; + + // EOL? + if (n == 0xFFFF) + return; + + dst -= n; + + nLength -= n; + if (nLength <= 0) + return; + +RLEWordFlippedDoAlpha: + n = READ_LE_UINT16(src); + src += 2; + +RLEWordFlippedDoAlpha2: + + if (n > nLength) + n = nLength; + + for (int i = 0; i < n; i++) { + int r = (*dst >> 10) & 0x1F; + int g = (*dst >> 5) & 0x1F; + int b = *dst & 0x1F; + + r = (r >> 2) + (_alphaR >> 1); + g = (g >> 2) + (_alphaG >> 1); + b = (b >> 2) + (_alphaB >> 1); + + *dst-- = (r << 10) | (g << 5) | b; + } + + nLength -= n; + if (!nLength) + return; + + assert(nLength > 0); + +//RLEWordFlippedDoCopy: + // Copy the data + n = READ_LE_UINT16(src); + src += 2; + +RLEWordFlippedDoCopy2: + if (n > nLength) + n = nLength; + + for (int i = 0; i < n; i++) + *dst-- = _palFinal[*src++]; + + nLength -= n; + if (!nLength) + return; + + assert(nLength > 0); + } +} + +/****************************************************************************\ +* Methods for RMGfxSourceBuffer8RLEWord +\****************************************************************************/ + +RMGfxSourceBuffer8RLEWordAB::~RMGfxSourceBuffer8RLEWordAB() { + +} + +void RMGfxSourceBuffer8RLEWordAB::rleDecompressLine(uint16 *dst, byte *src, int nStartSkip, int nLength) { + int n; + + if (!GLOBALS._bCfgTransparence) { + RMGfxSourceBuffer8RLEWord::rleDecompressLine(dst, src, nStartSkip, nLength); + return; + } + + if (nStartSkip == 0) + goto RLEWordDoTrasp; + + while (1) { + assert(nStartSkip > 0); + + // TRASP + n = READ_LE_UINT16(src); + src += 2; + + if (n == 0xFFFF) + return; + + if (n >= nStartSkip) { + dst += n - nStartSkip; + nLength -= n - nStartSkip; + + if (nLength > 0) + goto RLEWordDoAlpha; + else + return; + } + nStartSkip -= n; + + assert(nStartSkip > 0); + + // ALPHA + n = READ_LE_UINT16(src); + src += 2; + + if (n >= nStartSkip) { + n -= nStartSkip; + goto RLEWordDoAlpha2; + } + nStartSkip -= n; + + // DATA + n = READ_LE_UINT16(src); + src += 2; + + if (n >= nStartSkip) { + src += nStartSkip; + n -= nStartSkip; + goto RLEWordDoCopy2; + } + nStartSkip -= n; + src += n; + } + + + while (1) { +RLEWordDoTrasp: + // Get the trasp of s**t + n = READ_LE_UINT16(src); + src += 2; + + // EOL? + if (n == 0xFFFF) + return; + + dst += n; + + nLength -= n; + if (nLength <= 0) + return; + +RLEWordDoAlpha: + n = READ_LE_UINT16(src); + src += 2; + +RLEWordDoAlpha2: + + if (n > nLength) + n = nLength; + + // @@@ SHOULD NOT BE THERE !!!!! + for (int i = 0; i < n; i++) { + int r = (*dst >> 10) & 0x1F; + int g = (*dst >> 5) & 0x1F; + int b = *dst & 0x1F; + + r = (r >> 2) + (_alphaR >> 1); + g = (g >> 2) + (_alphaG >> 1); + b = (b >> 2) + (_alphaB >> 1); + + *dst++ = (r << 10) | (g << 5) | b; + } + + nLength -= n; + if (!nLength) + return; + + assert(nLength > 0); + +//RLEWordDoCopy: + // Copy the data + n = READ_LE_UINT16(src); + src += 2; + +RLEWordDoCopy2: + if (n > nLength) + n = nLength; + + for (int i = 0; i < n; i++) { + int r = (*dst >> 10) & 0x1F; + int g = (*dst >> 5) & 0x1F; + int b = *dst & 0x1F; + + int r2 = (_palFinal[*src] >> 10) & 0x1F; + int g2 = (_palFinal[*src] >> 5) & 0x1F; + int b2 = _palFinal[*src] & 0x1F; + + r = (r >> 1) + (r2 >> 1); + g = (g >> 1) + (g2 >> 1); + b = (b >> 1) + (b2 >> 1); + + *dst ++ = (r << 10) | (g << 5) | b; + src++; + } + + nLength -= n; + if (!nLength) + return; + + assert(nLength > 0); + } +} + +/****************************************************************************\ +* Methods for RMGfxSourceBuffer8AA +\****************************************************************************/ + +byte RMGfxSourceBuffer8AA::_megaAABuf[256 * 1024]; +byte RMGfxSourceBuffer8AA::_megaAABuf2[64 * 1024]; + +void RMGfxSourceBuffer8AA::prepareImage() { + // Invoke the parent method + RMGfxSourceBuffer::prepareImage(); + + // Prepare the buffer for anti-aliasing + calculateAA(); +} + +void RMGfxSourceBuffer8AA::calculateAA() { + byte *src, *srcaa; + + // First pass: fill the edges + Common::fill(_megaAABuf, _megaAABuf + _dimx * _dimy, 0); + + src = _buf; + srcaa = _megaAABuf; + for (int y = 0; y < _dimy; y++) { + for (int x = 0; x < _dimx; x++) { + if (*src == 0) { + if ((y > 0 && src[-_dimx] != 0) || + (y < _dimy - 1 && src[_dimx] != 0) || + (x > 0 && src[-1] != 0) || + (x < _dimx - 1 && src[1] != 0)) + *srcaa = 1; + } + + src++; + srcaa++; + } + } + + src = _buf; + srcaa = _megaAABuf; + for (int y = 0; y < _dimy; y++) { + for (int x = 0; x < _dimx; x++) { + if (*src != 0) { + if ((y > 0 && srcaa[-_dimx] == 1) || + (y < _dimy - 1 && srcaa[_dimx] == 1) || + (x > 0 && srcaa[-1] == 1) || + (x < _dimx - 1 && srcaa[1] == 1)) + *srcaa = 2; + } + + src++; + srcaa++; + } + } + + if (_aabuf != NULL) + delete[] _aabuf; + + _aabuf = new byte[_dimx * _dimy]; + memcpy(_aabuf, _megaAABuf, _dimx * _dimy); +} + +RMGfxSourceBuffer8AA::RMGfxSourceBuffer8AA() : RMGfxSourceBuffer8() { + _aabuf = NULL; +} + +RMGfxSourceBuffer8AA::~RMGfxSourceBuffer8AA() { + if (_aabuf != NULL) + delete[] _aabuf; +} + +void RMGfxSourceBuffer8AA::drawAA(RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim) { + byte *src; + uint16 *mybuf; + uint16 *buf; + int u, v, width, height; + + // Clip the sprite + int x1 = prim->getDst()._x1; + int y1 = prim->getDst()._y1; + if (!clip2D(x1, y1, u, v, width, height, false, &bigBuf)) + return; + + // Go forward through the RLE lines + src = _buf; + for (int y = 0; y < v; y++) + src += READ_LE_UINT16(src); + + // Eliminate horizontal clipping + + if (prim->isFlipped()) { + u = _dimx - (width + u); + x1 = (prim->getDst()._x1 + _dimx - 1) - u; + + if (width > x1) + width = x1; + + // Specify the drawn area + bigBuf.addDirtyRect(Common::Rect(x1 - width, y1, x1 + 1, y1 + height)); + } else { + // Specify the drawn area + bigBuf.addDirtyRect(Common::Rect(x1, y1, x1 + width, y1 + height)); + } + +// width = _dimx; +// x1 = prim->Dst().x1; + + + // Position into the destination buffer + buf = bigBuf; + buf += y1 * bigBuf.getDimx(); + + int step; + if (prim->isFlipped()) + step = -1; + else + step = 1; + + // Loop + buf += bigBuf.getDimx(); // Skip the first line + for (int y = 1; y < height - 1; y++) { + // if (prim->IsFlipped()) + // mybuf=&buf[x1+m_dimx-1]; + // else + mybuf = &buf[x1]; + + for (int x = 0; x < width; x++, mybuf += step) { + if (_aabuf[(y + v) * _dimx + x + u] == 2 && x != 0 && x != width - 1) { + int r = GETRED(mybuf[1]) + GETRED(mybuf[-1]) + GETRED(mybuf[-bigBuf.getDimx()]) + GETRED(mybuf[bigBuf.getDimx()]); + int g = GETGREEN(mybuf[1]) + GETGREEN(mybuf[-1]) + GETGREEN(mybuf[-bigBuf.getDimx()]) + GETGREEN(mybuf[bigBuf.getDimx()]); + int b = GETBLUE(mybuf[1]) + GETBLUE(mybuf[-1]) + GETBLUE(mybuf[-bigBuf.getDimx()]) + GETBLUE(mybuf[bigBuf.getDimx()]); + + r += GETRED(mybuf[0]); + g += GETGREEN(mybuf[0]); + b += GETBLUE(mybuf[0]); + + r /= 5; + g /= 5; + b /= 5; + + if (r > 31) + r = 31; + if (g > 31) + g = 31; + if (b > 31) + b = 31; + + mybuf[0] = (r << 10) | (g << 5) | b; + } + } + + // Skip to the next line + buf += bigBuf.getDimx(); + } + + // Position into the destination buffer + buf = bigBuf; + buf += y1 * bigBuf.getDimx(); + + // Looppone + buf += bigBuf.getDimx(); + for (int y = 1; y < height - 1; y++) { + // if (prim->IsFlipped()) + // mybuf=&buf[x1+m_dimx-1]; + // else + mybuf = &buf[x1]; + + for (int x = 0; x < width; x++, mybuf += step) { + if (_aabuf[(y + v) * _dimx + x + u] == 1 && x != 0 && x != width - 1) { + int r = GETRED(mybuf[1]) + GETRED(mybuf[-1]) + GETRED(mybuf[-bigBuf.getDimx()]) + GETRED(mybuf[bigBuf.getDimx()]); + int g = GETGREEN(mybuf[1]) + GETGREEN(mybuf[-1]) + GETGREEN(mybuf[-bigBuf.getDimx()]) + GETGREEN(mybuf[bigBuf.getDimx()]); + int b = GETBLUE(mybuf[1]) + GETBLUE(mybuf[-1]) + GETBLUE(mybuf[-bigBuf.getDimx()]) + GETBLUE(mybuf[bigBuf.getDimx()]); + + r += GETRED(mybuf[0]) * 2; + g += GETGREEN(mybuf[0]) * 2; + b += GETBLUE(mybuf[0]) * 2; + + r /= 6; + g /= 6; + b /= 6; + + if (r > 31) + r = 31; + if (g > 31) + g = 31; + if (b > 31) + b = 31; + + mybuf[0] = (r << 10) | (g << 5) | b; + } + } + + // Skip to the next line + buf += bigBuf.getDimx(); + } +} + + + +void RMGfxSourceBuffer8AA::draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + CORO_INVOKE_2(RMGfxSourceBuffer8::draw, bigBuf, prim); + drawAA(bigBuf, prim); + + CORO_END_CODE; +} + +/****************************************************************************\ +* RMGfxSourceBuffer8RLEAA Methods +\****************************************************************************/ + +RMGfxSourceBuffer8RLEByteAA::~RMGfxSourceBuffer8RLEByteAA() { +} + +void RMGfxSourceBuffer8RLEByteAA::prepareImage() { + RMGfxSourceBuffer::prepareImage(); + calculateAA(); + compressRLE(); +} + +void RMGfxSourceBuffer8RLEByteAA::draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + CORO_INVOKE_2(RMGfxSourceBuffer8RLE::draw, bigBuf, prim); + if (GLOBALS._bCfgAntiAlias) + drawAA(bigBuf, prim); + + CORO_END_CODE; +} + +int RMGfxSourceBuffer8RLEByteAA::init(const byte *buf, int dimx, int dimy, bool bLoadPalette) { + return RMGfxSourceBuffer8RLE::init(buf, dimx, dimy, bLoadPalette); +} + +void RMGfxSourceBuffer8RLEByteAA::init(Common::ReadStream &ds, int dimx, int dimy, bool bLoadPalette) { + RMGfxSourceBuffer8RLE::init(ds, dimx, dimy, bLoadPalette); + + if (!_bNeedRLECompress) { + // Load the anti-aliasing mask + _aabuf = new byte[dimx * dimy]; + ds.read(_aabuf, dimx * dimy); + } +} + +RMGfxSourceBuffer8RLEWordAA::~RMGfxSourceBuffer8RLEWordAA() { +} + +void RMGfxSourceBuffer8RLEWordAA::prepareImage() { + RMGfxSourceBuffer::prepareImage(); + calculateAA(); + compressRLE(); +} + +void RMGfxSourceBuffer8RLEWordAA::draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + CORO_INVOKE_2(RMGfxSourceBuffer8RLE::draw, bigBuf, prim); + if (GLOBALS._bCfgAntiAlias) + drawAA(bigBuf, prim); + + CORO_END_CODE; +} + +int RMGfxSourceBuffer8RLEWordAA::init(byte *buf, int dimx, int dimy, bool bLoadPalette) { + return RMGfxSourceBuffer8RLE::init(buf, dimx, dimy, bLoadPalette); +} + +void RMGfxSourceBuffer8RLEWordAA::init(Common::ReadStream &ds, int dimx, int dimy, bool bLoadPalette) { + RMGfxSourceBuffer8RLE::init(ds, dimx, dimy, bLoadPalette); + + if (!_bNeedRLECompress) { + // Load the anti-aliasing mask + _aabuf = new byte[dimx * dimy]; + ds.read(_aabuf, dimx * dimy); + } +} + + +/****************************************************************************\ +* RMGfxSourceBuffer16 Methods +\****************************************************************************/ + +RMGfxSourceBuffer16::RMGfxSourceBuffer16(bool bTrasp0) { + _bTrasp0 = bTrasp0; +} + +RMGfxSourceBuffer16::~RMGfxSourceBuffer16() { +} + +void RMGfxSourceBuffer16::draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim) { + uint16 *buf = bigBuf; + uint16 *raw = (uint16 *)_buf; + + int dimx = _dimx; + int dimy = _dimy; + int u = 0; + int v = 0; + int x1 = 0; + int y1 = 0; + + if (prim->haveSrc()) { + u = prim->getSrc()._x1; + v = prim->getSrc()._y1; + dimx = prim->getSrc().width(); + dimy = prim->getSrc().height(); + } + + if (prim->haveDst()) { + x1 = prim->getDst()._x1; + y1 = prim->getDst()._y1; + } + + if (!clip2D(x1, y1, u, v, dimx, dimy, true, &bigBuf)) + return; + + raw += v * _dimx + u; + buf += y1 * bigBuf.getDimx() + x1; + + if (_bTrasp0) { + for (int y = 0; y < dimy; y++) { + for (int x = 0; x < dimx;) { + while (x < dimx && raw[x] == 0) + x++; + + while (x < dimx && raw[x] != 0) { + buf[x] = raw[x]; + x++; + } + } + + raw += _dimx; + buf += bigBuf.getDimx(); + } + } else { + for (int y = 0; y < dimy; y++) { + Common::copy(raw, raw + dimx, buf); + buf += bigBuf.getDimx(); + raw += _dimx; + } + } + + // Specify the drawn area + bigBuf.addDirtyRect(Common::Rect(x1, y1, x1 + dimx, y1 + dimy)); +} + +void RMGfxSourceBuffer16::prepareImage() { + // Color space conversion if necessary! + uint16 *buf = (uint16 *)_buf; + + for (int i = 0; i < _dimx * _dimy; i++) + WRITE_LE_UINT16(&buf[i], FROM_LE_16(buf[i]) & 0x7FFF); +} + +RMGfxSourceBuffer16::RMGfxSourceBuffer16(int dimx, int dimy) + : RMGfxBuffer(dimx, dimy, 16) { + setPriority(0); + _bTrasp0 = false; +} + +/** + * Returns the number of bits per pixel of the surface + * + * @returns Bit per pixel + */ +int RMGfxSourceBuffer16::getBpp() { + return 16; +} + +void RMGfxSourceBuffer16::create(int dimx, int dimy) { + RMGfxBuffer::create(dimx, dimy, 16); +} + +/****************************************************************************\ +* RMGfxBox Methods +\****************************************************************************/ + +void RMGfxBox::removeThis(CORO_PARAM, bool &result) { + result = true; +} + +void RMGfxBox::setColor(byte r, byte g, byte b) { + r >>= 3; + g >>= 3; + b >>= 3; + _wFillColor = (r << 10) | (g << 5) | b; +} + +void RMGfxBox::draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim) { + uint16 *buf = bigBuf; + RMRect rcDst; + + // It takes the destination rectangle + rcDst = prim->getDst(); + buf += rcDst._y1 * bigBuf.getDimx() + rcDst._x1; + + // Loop through the pixels + for (int j = 0; j < rcDst.height(); j++) { + for (int i = 0; i < rcDst.width(); i++) + *buf++ = _wFillColor; + + buf += bigBuf.getDimx() - rcDst.width(); + } + + // Specify the drawn area + bigBuf.addDirtyRect(rcDst); +} + + +/****************************************************************************\ +* RMGfxClearTask Methods +\****************************************************************************/ + +int RMGfxClearTask::priority() { + // Maximum priority (must be done first) + return 1; +} + +void RMGfxClearTask::draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *) { + // Clean the target buffer + Common::fill((byte *)bigBuf, (byte *)bigBuf + (bigBuf.getDimx() * bigBuf.getDimy() * 2), 0x0); + bigBuf.addDirtyRect(Common::Rect(bigBuf.getDimx(), bigBuf.getDimy())); +} + +void RMGfxClearTask::removeThis(CORO_PARAM, bool &result) { + // The task is fine to be removed + result = true; +} + +/****************************************************************************\ +* RMGfxPrimitive Methods +\****************************************************************************/ + +RMGfxPrimitive::RMGfxPrimitive() { + _bFlag = 0; + _task = NULL; + _src.setEmpty(); + _dst.setEmpty(); + _bStretch = false; +} + +RMGfxPrimitive::RMGfxPrimitive(RMGfxTask *task) { + _task = task; + _bFlag = 0; + _bStretch = false; +} + +RMGfxPrimitive::RMGfxPrimitive(RMGfxTask *task, const RMRect &src, RMRect &dst) { + _task = task; + _src = src; + _dst = dst; + _bFlag = 0; + _bStretch = (src.width() != dst.width() || src.height() != dst.height()); +} + +RMGfxPrimitive::RMGfxPrimitive(RMGfxTask *task, const RMPoint &src, RMRect &dst) { + _task = task; + _src.topLeft() = src; + _dst = dst; + _bFlag = 0; + _bStretch = false; +} + +RMGfxPrimitive::RMGfxPrimitive(RMGfxTask *task, const RMPoint &src, RMPoint &dst) { + _task = task; + _src.topLeft() = src; + _dst.topLeft() = dst; + _bFlag = 0; + _bStretch = false; +} + +RMGfxPrimitive::RMGfxPrimitive(RMGfxTask *task, const RMRect &src, RMPoint &dst) { + _task = task; + _src = src; + _dst.topLeft() = dst; + _bFlag = 0; + _bStretch = false; +} + +RMGfxPrimitive::RMGfxPrimitive(RMGfxTask *task, const RMRect &dst) { + _task = task; + _dst = dst; + _src.setEmpty(); + _bFlag = 0; + _bStretch = false; +} + +RMGfxPrimitive::RMGfxPrimitive(RMGfxTask *task, const RMPoint &dst) { + _task = task; + _dst.topLeft() = dst; + _src.setEmpty(); + _bFlag = 0; + _bStretch = false; +} + +RMGfxPrimitive::~RMGfxPrimitive() { +} + +void RMGfxPrimitive::setFlag(byte bFlag) { + _bFlag = bFlag; +} + +void RMGfxPrimitive::setTask(RMGfxTask *task) { + _task = task; +} + +void RMGfxPrimitive::setSrc(const RMRect &src) { + _src = src; +} + +void RMGfxPrimitive::setSrc(const RMPoint &src) { + _src.topLeft() = src; +} + +void RMGfxPrimitive::setDst(const RMRect &dst) { + _dst = dst; +} + +void RMGfxPrimitive::setDst(const RMPoint &dst) { + _dst.topLeft() = dst; +} + +void RMGfxPrimitive::setStretch(bool bStretch) { + _bStretch = bStretch; +} + +bool RMGfxPrimitive::haveDst() { + return !_dst.isEmpty(); +} + +RMRect &RMGfxPrimitive::getDst() { + return _dst; +} + +bool RMGfxPrimitive::haveSrc() { + return !_src.isEmpty(); +} + +RMRect &RMGfxPrimitive::getSrc() { + return _src; +} + +/** + * Flags + */ +bool RMGfxPrimitive::isFlipped() { + return _bFlag & 1; +} + +/** + * Duplicate + */ +RMGfxPrimitive *RMGfxPrimitive::duplicate() { + return new RMGfxPrimitive(*this); +} + +} // End of namespace Tony diff --git a/engines/tony/gfxcore.h b/engines/tony/gfxcore.h new file mode 100644 index 0000000000..f0deed83ee --- /dev/null +++ b/engines/tony/gfxcore.h @@ -0,0 +1,516 @@ +/* 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 Tony Tough source code + * + * Copyright (c) 1997-2003 Nayma Software + */ + +#ifndef TONY_GFXCORE_H +#define TONY_GFXCORE_H + +#include "common/system.h" +#include "common/coroutines.h" +#include "tony/utils.h" + +namespace Tony { + +/****************************************************************************\ +* Class prototype +\****************************************************************************/ + +// Class Name Family Treee Abstract? +class RMGfxTask; // Yes +class RMGfxTaskSetPrior; // Task Yes +class RMGfxBuffer; // +class RMGfxSourceBuffer; // TaskP+[Buffer] Yes +class RMGfxTargetBuffer; // [Buffer] +class RMGfxSourceBufferPal; // Source Yes +class RMGfxSourceBuffer4; // SourcePal +class RMGfxSourceBuffer8; // SourcePal +class RMGfxSourceBuffer16; // Source +class RMGfxWoodyBuffer; // Source16+Target +class RMGfxClearTask; // Task + + +/** + * Graphics buffer + */ +class RMGfxBuffer { +protected: + int _dimx, _dimy; + byte *_buf; + byte *_origBuf; + +public: + RMGfxBuffer(); + RMGfxBuffer(int dimx, int dimy, int nBpp); + virtual ~RMGfxBuffer(); + + // Attributes + int getDimx(); + int getDimy(); + + // Creation + virtual void create(int dimx, int dimy, int nBpp); + virtual void destroy(); + + // These are valid only if the buffer is locked + operator byte *(); + operator void *(); + + // Getting the offset for a given Y position + void offsetY(int nLines, int nBpp); +}; + +/** + * Graphics primitive + */ +class RMGfxPrimitive { +public: + RMGfxTask *_task; + +protected: + RMRect _src; + RMRect _dst; + + bool _bStretch; + byte _bFlag; + +public: + RMGfxPrimitive(); + RMGfxPrimitive(RMGfxTask *task); + RMGfxPrimitive(RMGfxTask *task, const RMRect &src, RMRect &dst); + RMGfxPrimitive(RMGfxTask *task, const RMPoint &src, RMRect &dst); + RMGfxPrimitive(RMGfxTask *task, const RMPoint &src, RMPoint &dst); + RMGfxPrimitive(RMGfxTask *task, const RMRect &src, RMPoint &dst); + RMGfxPrimitive(RMGfxTask *task, const RMRect &dst); + RMGfxPrimitive(RMGfxTask *task, const RMPoint &dst); + virtual ~RMGfxPrimitive(); + void setFlag(byte bFlag); + void setTask(RMGfxTask *task); + void setSrc(const RMRect &src); + void setSrc(const RMPoint &src); + void setDst(const RMRect &dst); + void setDst(const RMPoint &dst); + void setStretch(bool bStretch); + bool haveDst(); + RMRect &getDst(); + bool haveSrc(); + RMRect &getSrc(); + + // Flags + bool isFlipped(); + + // Duplicate + virtual RMGfxPrimitive *duplicate(); +}; + + +/** + * Graphic drawing task + */ +class RMGfxTask { +protected: + int _nPrior; + int _nInList; + +public: + // Standard constructor + RMGfxTask(); + virtual ~RMGfxTask() { } + + virtual int priority(); + virtual void draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim) = 0; + virtual void removeThis(CORO_PARAM, bool &result); + + // Registration + virtual void Register(); + virtual void unregister(); +}; + + +/** + * Graphic drawing with priority + */ +class RMGfxTaskSetPrior : public RMGfxTask { +public: + virtual ~RMGfxTaskSetPrior() { } + void setPriority(int nPrior); +}; + + +/** + * Task that cleans the destination buffer + */ +class RMGfxClearTask : public RMGfxTask { +public: + virtual ~RMGfxClearTask() { } + + int priority(); + virtual void draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim); + virtual void removeThis(CORO_PARAM, bool &result); +}; + + +/** + * Task that draws a colored box + */ +class RMGfxBox : public RMGfxTaskSetPrior { +protected: + uint16 _wFillColor; + +public: + virtual ~RMGfxBox() { } + + void setColor(byte r, byte g, byte b); + virtual void draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim); + virtual void removeThis(CORO_PARAM, bool &result); +}; + + +/** + * Buffer source for the design, which is a task. This is an abstract base. + */ +class RMGfxSourceBuffer : public virtual RMGfxBuffer, public RMGfxTaskSetPrior { +public: + // Load the data for the surface + virtual int init(uint32 resID, int dimx, int dimy, bool bLoadPalette = false); + virtual int init(const byte *buf, int dimx, int dimy, bool bLoadPalette = false); + virtual void init(Common::ReadStream &ds, int dimx, int dimy, bool bLoadPalette = false); + + virtual ~RMGfxSourceBuffer(); + +protected: + virtual void prepareImage(); + bool clip2D(int &x1, int &y1, int &u, int &v, int &width, int &height, bool bUseSrc, RMGfxTargetBuffer *buf); + void offsetY(int nLines); + +public: + virtual int getBpp() = 0; +}; + + +/** + * 16-bit color source + */ +class RMGfxSourceBuffer16 : public RMGfxSourceBuffer { +protected: + virtual void prepareImage(); + bool _bTrasp0; + +public: + RMGfxSourceBuffer16(bool bUseTrasp = false); + RMGfxSourceBuffer16(int dimx, int dimy); + virtual ~RMGfxSourceBuffer16(); + + // Initialization + void create(int dimx, int dimy); + + int getBpp(); + virtual void draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim); +}; + + +/** + * Buffer source with palette + */ +class RMGfxSourceBufferPal : public RMGfxSourceBuffer { +protected: + // The size of the palette is (1 << Bpp()) * 4 + byte _pal[256 * 3]; + uint16 _palFinal[256]; + + // Post process to prepare the palette for drawing + virtual void preparePalette(); + +public: + virtual ~RMGfxSourceBufferPal(); + + virtual int init(const byte *buf, int dimx, int dimy, bool bLoadPalette = false); + virtual void init(Common::ReadStream &ds, int dimx, int dimy, bool bLoadPalette = false); + + int loadPaletteWA(uint32 resID, bool bSwapped = false); + int loadPaletteWA(const byte *buf, bool bSwapped = false); + int loadPalette(uint32 resID); + int loadPalette(const byte *buf); +}; + + +/** + * Buffer source with a 256 color palette + */ +class RMGfxSourceBuffer8 : public RMGfxSourceBufferPal { +protected: + bool _bTrasp0; + +public: + RMGfxSourceBuffer8(bool bTrasp0 = true); + RMGfxSourceBuffer8(int dimx, int dimy); + virtual ~RMGfxSourceBuffer8(); + + // Initialization + void create(int dimx, int dimy); + + int getBpp(); + virtual void draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim); +}; + + +/** + * Buffer source with a 256 color palette, and alpha blending + */ +class RMGfxSourceBuffer8AB : public RMGfxSourceBuffer8 { +protected: + int calcTrasp(int f, int b); + +public: + virtual ~RMGfxSourceBuffer8AB(); + virtual void draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim); +}; + + +/** + * Buffer source with a 256 color palette, RLE compressed + */ + +class RMGfxSourceBuffer8RLE : public virtual RMGfxSourceBuffer8 { +protected: + int _alphaBlendColor; + int _alphaR, _alphaB, _alphaG; + bool _bNeedRLECompress; + +protected: + static byte _megaRLEBuf[]; + + virtual void rleWriteTrasp(byte *&cur, int rep) = 0; + virtual void rleWriteData(byte *&cur, int rep, byte *src) = 0; + virtual void rleWriteEOL(byte *&cur) = 0; + virtual void rleWriteAlphaBlend(byte *&cur, int rep) = 0; + virtual void rleDecompressLine(uint16 *dst, byte *src, int nStartSkip, int nLength) = 0; + virtual void rleDecompressLineFlipped(uint16 *dst, byte *src, int nStartSkip, int nLength) = 0; + + // Perform image compression in RLE + void compressRLE(); + +protected: + // Overriding initialization methods + virtual void prepareImage(); + virtual void preparePalette(); + +public: + RMGfxSourceBuffer8RLE(); + virtual ~RMGfxSourceBuffer8RLE(); + + // Overload of the initialization method + virtual void init(Common::ReadStream &ds, int dimx, int dimy, bool bLoadPalette = false); + virtual int init(const byte *buf, int dimx, int dimy, bool bLoadPalette = false); + + // Draw image with RLE decompression + virtual void draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim); + + // Sets the color that will be alpha blended + void setAlphaBlendColor(int color); + + // Warn if the data is already compressed + void setAlreadyCompressed(); +}; + +class RMGfxSourceBuffer8RLEByte : public RMGfxSourceBuffer8RLE { +protected: + void rleWriteTrasp(byte * &cur, int rep); + void rleWriteAlphaBlend(byte * &cur, int rep); + void rleWriteData(byte * &cur, int rep, byte *src); + void rleWriteEOL(byte * &cur); + void rleDecompressLine(uint16 *dst, byte *src, int nStartSkip, int nLength); + void rleDecompressLineFlipped(uint16 *dst, byte *src, int nStartSkip, int nLength); + +public: + virtual ~RMGfxSourceBuffer8RLEByte(); +}; + +class RMGfxSourceBuffer8RLEWord : public RMGfxSourceBuffer8RLE { +protected: + void rleWriteTrasp(byte * &cur, int rep); + void rleWriteAlphaBlend(byte * &cur, int rep); + void rleWriteData(byte * &cur, int rep, byte *src); + void rleWriteEOL(byte * &cur); + virtual void rleDecompressLine(uint16 *dst, byte *src, int nStartSkip, int nLength); + virtual void rleDecompressLineFlipped(uint16 *dst, byte *src, int nStartSkip, int nLength); + +public: + virtual ~RMGfxSourceBuffer8RLEWord(); +}; + +class RMGfxSourceBuffer8RLEWordAB : public RMGfxSourceBuffer8RLEWord { +protected: + virtual void rleDecompressLine(uint16 *dst, byte *src, int nStartSkip, int nLength); + +public: + virtual ~RMGfxSourceBuffer8RLEWordAB(); +}; + + +/** + * Buffer source with a 256 color palette, with anti-aliasing + */ +class RMGfxSourceBuffer8AA : public virtual RMGfxSourceBuffer8 { +protected: + static byte _megaAABuf[]; + static byte _megaAABuf2[]; + byte *_aabuf; + + // Calculate the buffer for the anti-aliasing + void calculateAA(); + + // Draw the AA + void drawAA(RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim); + +protected: + void prepareImage(); + +public: + RMGfxSourceBuffer8AA(); + virtual ~RMGfxSourceBuffer8AA(); + + // Draw with anti-aliasing + virtual void draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim); +}; + + +class RMGfxSourceBuffer8RLEByteAA : public RMGfxSourceBuffer8RLEByte, public RMGfxSourceBuffer8AA { +protected: + void prepareImage(); + +public: + virtual void draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim); + + // Overloaded initialization methods + virtual void init(Common::ReadStream &ds, int dimx, int dimy, bool bLoadPalette = false); + virtual int init(const byte *buf, int dimx, int dimy, bool bLoadPalette = false); + + virtual ~RMGfxSourceBuffer8RLEByteAA(); +}; + +class RMGfxSourceBuffer8RLEWordAA : public RMGfxSourceBuffer8RLEWord, public RMGfxSourceBuffer8AA { +protected: + void prepareImage(); + +public: + virtual void draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim); + + // Overloaded initialization methods + virtual void init(Common::ReadStream &ds, int dimx, int dimy, bool bLoadPalette = false); + virtual int init(byte *buf, int dimx, int dimy, bool bLoadPalette = false); + + virtual ~RMGfxSourceBuffer8RLEWordAA(); +}; + + +/** + * Source buffer with 16 colors + */ +class RMGfxSourceBuffer4 : public RMGfxSourceBufferPal { +public: + RMGfxSourceBuffer4(); + RMGfxSourceBuffer4(int dimx, int dimy); + + // Initialization + void create(int dimx, int dimy); + + int getBpp(); + virtual void draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim); +}; + + +/** + * Destination buffer which manages its own internal list of tasks + */ +class RMGfxTargetBuffer : public virtual RMGfxBuffer { +private: + struct OTList { + RMGfxPrimitive *_prim; + OTList *_next; + + OTList(); + OTList(RMGfxPrimitive *pr) { + _prim = pr; + } + }; + + bool _trackDirtyRects; + Common::List<Common::Rect> _currentDirtyRects, _previousDirtyRects, _dirtyRects; + + void mergeDirtyRects(); + +private: +// OSystem::MutexRef csModifyingOT; + +protected: + OTList *_otlist; + int _otSize; + +public: + RMGfxTargetBuffer(); + virtual ~RMGfxTargetBuffer(); + + static uint16 *_precalcTable; + static void createBWPrecalcTable(); + static void freeBWPrecalcTable(); + + // management of the OT list + void clearOT(); + void drawOT(CORO_PARAM); + void addPrim(RMGfxPrimitive *prim); // The pointer must be delted + + operator byte *(); + operator void *(); + operator uint16 *(); + + // Offseting buffer + void offsetY(int nLines); + + // Dirty rect methods + void addDirtyRect(const Common::Rect &r); + Common::List<Common::Rect> &getDirtyRects(); + void clearDirtyRects(); + void setTrackDirtyRects(bool v); + bool getTrackDirtyRects() const; +}; + + +/** + * Ring buffer, which is both source and by destination + */ +class RMGfxWoodyBuffer: public RMGfxSourceBuffer16, public RMGfxTargetBuffer { +public: + RMGfxWoodyBuffer(); + RMGfxWoodyBuffer(int dimx, int dimy); + virtual ~RMGfxWoodyBuffer(); + + virtual void draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim); +}; + +} // End of namespace Tony + +#endif diff --git a/engines/tony/gfxengine.cpp b/engines/tony/gfxengine.cpp new file mode 100644 index 0000000000..59fb024622 --- /dev/null +++ b/engines/tony/gfxengine.cpp @@ -0,0 +1,843 @@ +/* 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 Tony Tough source code + * + * Copyright (c) 1997-2003 Nayma Software + */ + +#include "common/savefile.h" +#include "tony/mpal/lzo.h" +#include "tony/mpal/mpalutils.h" +#include "tony/custom.h" +#include "tony/gfxengine.h" +#include "tony/tony.h" + +namespace Tony { + + +/****************************************************************************\ +* RMGfxEngine Methods +\****************************************************************************/ + +void exitAllIdles(CORO_PARAM, const void *param) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + int nCurLoc = *(const int *)param; + + CORO_BEGIN_CODE(_ctx); + + // Closes idle + GLOBALS._bSkipSfxNoLoop = true; + + CORO_INVOKE_2(mpalEndIdlePoll, nCurLoc, NULL); + + GLOBALS._bIdleExited = true; + GLOBALS._bSkipSfxNoLoop = false; + + CORO_END_CODE; +} + +RMGfxEngine::RMGfxEngine() { + // Create big buffer where the frame will be rendered + _bigBuf.create(RM_BBX, RM_BBY, 16); + _bigBuf.offsetY(RM_SKIPY); + _bigBuf.setTrackDirtyRects(true); + + _nCurLoc = 0; + _curAction = TA_GOTO; + _curActionObj = 0; + _nWipeType = 0; + _hWipeEvent = 0; + _nWipeStep = 0; + _bMustEnterMenu = false; + _bWiping = false; + _bGUIOption = false; + _bGUIInterface = false; + _bGUIInventory = false; + _bAlwaysDrawMouse = false; + _bOption = false; + _bLocationLoaded = false; + _bInput = false; +} + +RMGfxEngine::~RMGfxEngine() { + // Close the buffer + _bigBuf.destroy(); +} + +void RMGfxEngine::openOptionScreen(CORO_PARAM, int type) { + CORO_BEGIN_CONTEXT; + bool bRes; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + _ctx->bRes = false; + + if (type == 0) + CORO_INVOKE_2(_opt.init, _bigBuf, _ctx->bRes); + else if (type == 1) + CORO_INVOKE_3(_opt.initLoadMenuOnly, _bigBuf, true, _ctx->bRes); + else if (type == 2) + CORO_INVOKE_2(_opt.initNoLoadSave, _bigBuf, _ctx->bRes); + else if (type == 3) + CORO_INVOKE_3(_opt.initLoadMenuOnly, _bigBuf, false, _ctx->bRes); + else if (type == 4) + CORO_INVOKE_3(_opt.initSaveMenuOnly, _bigBuf, false, _ctx->bRes); + + if (_ctx->bRes) { + g_vm->pauseSound(true); + + disableInput(); + _inv.endCombine(); + _curActionObj = 0; + _curAction = TA_GOTO; + _point.setAction(_curAction); + _point.setSpecialPointer(RMPointer::PTR_NONE); + _point.setCustomPointer(NULL); + enableMouse(); + g_vm->grabThumbnail(); + + // Exists the IDLE to avoid premature death in loading + _bMustEnterMenu = true; + if (type == 1 || type == 2) { + GLOBALS._bIdleExited = true; + } else { + CORO_INVOKE_0(_tony.stopNoAction); + + GLOBALS._bIdleExited = false; + + CoroScheduler.createProcess(exitAllIdles, &_nCurLoc, sizeof(int)); + } + } + + CORO_END_CODE; +} + +void RMGfxEngine::doFrame(CORO_PARAM, bool bDrawLocation) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + // Poll of input devices + _input.poll(); + + if (_bMustEnterMenu && GLOBALS._bIdleExited) { + _bOption = true; + _bMustEnterMenu = false; + GLOBALS._bIdleExited = false; + } + + if (_bOption) { + CORO_INVOKE_1(_opt.doFrame, &_input); + _bOption = !_opt.isClosing(); + if (!_bOption) { + disableMouse(); + enableInput(); + mpalStartIdlePoll(_nCurLoc); + g_vm->pauseSound(false); + } + } + + if (bDrawLocation && _bLocationLoaded) { + // Location and objects + _loc.doFrame(&_bigBuf); + + // Check the mouse input + if (_bInput && !_tony.inAction()) { + // If we are on the inventory, it is it who controls all input + if (_inv.haveFocus(_input.mousePos()) && !_inter.active()) { + // Left Click + // ********** + if (_input.mouseLeftClicked()/* && m_itemName.IsItemSelected()*/) { + // Left click activates the combine, if we are on an object + if (_inv.leftClick(_input.mousePos(), _curActionObj)) { + _curAction = TA_COMBINE; + _point.setAction(_curAction); + } + } else + + // Right Click + // *********** + if (_input.mouseRightClicked()) { + if (_itemName.isItemSelected()) { + _curActionObj = 0; + _inv.rightClick(_input.mousePos()); + } else + _inv.rightClick(_input.mousePos()); + } else + + // Right Release + // ************* + if (_input.mouseRightReleased()) { + if (_inv.rightRelease(_input.mousePos(), _curAction)) { + CORO_INVOKE_3(_tony.moveAndDoAction, _itemName.getHotspot(), _itemName.getSelectedItem(), _curAction); + + _curAction = TA_GOTO; + _point.setAction(_curAction); + } + } + } else { + // Options Menu + // ************ + if (_bGUIOption) { + if (!_tony.inAction() && _bInput) { + if ((_input.mouseLeftClicked() && _input.mousePos()._x < 3 && _input.mousePos()._y < 3)) { + CORO_INVOKE_1(openOptionScreen, 0); + goto SKIPCLICKSINISTRO; + } else if (_input.getAsyncKeyState(Common::KEYCODE_ESCAPE)) + CORO_INVOKE_1(openOptionScreen, 0); + else if (!g_vm->getIsDemo()) { + if (_input.getAsyncKeyState(Common::KEYCODE_F3) || _input.getAsyncKeyState(Common::KEYCODE_F5)) + // Save game screen + CORO_INVOKE_1(openOptionScreen, 4); + else if (_input.getAsyncKeyState(Common::KEYCODE_F2) || _input.getAsyncKeyState(Common::KEYCODE_F7)) + // Load game screen + CORO_INVOKE_1(openOptionScreen, 3); + } + } + } + + // Left Click + // ************** + if (_input.mouseLeftClicked() && !_inter.active()) { + + if (_curAction != TA_COMBINE) + CORO_INVOKE_3(_tony.moveAndDoAction, _itemName.getHotspot(), _itemName.getSelectedItem(), _point.curAction()); + else if (_itemName.getSelectedItem() != NULL) + CORO_INVOKE_4(_tony.moveAndDoAction, _itemName.getHotspot(), _itemName.getSelectedItem(), TA_COMBINE, _curActionObj); + + if (_curAction == TA_COMBINE) { + _inv.endCombine(); + _point.setSpecialPointer(RMPointer::PTR_NONE); + } + + _curAction = TA_GOTO; + _point.setAction(_curAction); + } + +SKIPCLICKSINISTRO: + // Right Click + // ************ + if (_curAction == TA_COMBINE) { + // During a combine, it cancels it + if (_input.mouseRightClicked()) { + _inv.endCombine(); + _curActionObj = 0; + _curAction = TA_GOTO; + _point.setAction(_curAction); + _point.setSpecialPointer(RMPointer::PTR_NONE); + } + } else if (_input.mouseRightClicked() && _itemName.isItemSelected() && _point.getSpecialPointer() == RMPointer::PTR_NONE) { + if (_bGUIInterface) { + // Before opening the interface, replaces GOTO + _curAction = TA_GOTO; + _curActionObj = 0; + _point.setAction(_curAction); + _inter.clicked(_input.mousePos()); + } + } + + + // Right Release + // ************* + if (_input.mouseRightReleased()) { + if (_bGUIInterface) { + if (_inter.released(_input.mousePos(), _curAction)) { + _point.setAction(_curAction); + CORO_INVOKE_3(_tony.moveAndDoAction, _itemName.getHotspot(), _itemName.getSelectedItem(), _curAction); + + _curAction = TA_GOTO; + _point.setAction(_curAction); + } + } + } + } + + // Update the name under the mouse pointer + _itemName.setMouseCoord(_input.mousePos()); + if (!_inter.active() && !_inv.miniActive()) + CORO_INVOKE_4(_itemName.doFrame, _bigBuf, _loc, _point, _inv); + } + + // Interface & Inventory + _inter.doFrame(_bigBuf, _input.mousePos()); + _inv.doFrame(_bigBuf, _point, _input.mousePos(), (!_tony.inAction() && !_inter.active() && _bGUIInventory)); + } + + // Animate Tony + CORO_INVOKE_2(_tony.doFrame, &_bigBuf, _nCurLoc); + + // Update screen scrolling to keep Tony in focus + if (_tony.mustUpdateScrolling() && _bLocationLoaded) { + RMPoint showThis = _tony.position(); + showThis._y -= 60; + _loc.updateScrolling(showThis); + } + + if (_bLocationLoaded) + _tony.setScrollPosition(_loc.scrollPosition()); + + if ((!_tony.inAction() && _bInput) || _bAlwaysDrawMouse) { + _point.showCursor(); + } else { + _point.hideCursor(); + } + _point.doFrame(); + + // ********************** + // Draw the list in the OT + // ********************** + CORO_INVOKE_0(_bigBuf.drawOT); + +#define FSTEP (480/32) + + // Wipe + if (_bWiping) { + switch (_nWipeType) { + case 1: + if (!(_rcWipeEllipse.bottom - _rcWipeEllipse.top >= FSTEP * 2)) { + CoroScheduler.setEvent(_hWipeEvent); + _nWipeType = 3; + break; + } + + _rcWipeEllipse.top += FSTEP; + _rcWipeEllipse.left += FSTEP; + _rcWipeEllipse.right -= FSTEP; + _rcWipeEllipse.bottom -= FSTEP; + break; + + case 2: + if (!(_rcWipeEllipse.bottom - _rcWipeEllipse.top < 480 - FSTEP)) { + CoroScheduler.setEvent(_hWipeEvent); + _nWipeType = 3; + break; + } + + _rcWipeEllipse.top -= FSTEP; + _rcWipeEllipse.left -= FSTEP; + _rcWipeEllipse.right += FSTEP; + _rcWipeEllipse.bottom += FSTEP; + break; + } + } + + CORO_END_CODE; +} + +void RMGfxEngine::initCustomDll() { + setupGlobalVars(&_tony, &_point, &g_vm->_theBoxes, &_loc, &_inv, &_input); +} + +void RMGfxEngine::itemIrq(uint32 dwItem, int nPattern, int nStatus) { + RMItem *item; + assert(GLOBALS._gfxEngine); + + if (GLOBALS._gfxEngine->_bLocationLoaded) { + item = GLOBALS._gfxEngine->_loc.getItemFromCode(dwItem); + if (item != NULL) { + if (nPattern != -1) { + item->setPattern(nPattern, true); + } + if (nStatus != -1) + item->setStatus(nStatus); + } + } +} + +void RMGfxEngine::initForNewLocation(int nLoc, RMPoint ptTonyStart, RMPoint start) { + if (start._x == -1 || start._y == -1) { + start._x = ptTonyStart._x - RM_SX / 2; + start._y = ptTonyStart._y - RM_SY / 2; + } + + _loc.setScrollPosition(start); + + if (ptTonyStart._x == 0 && ptTonyStart._y == 0) { + } else { + _tony.setPosition(ptTonyStart, nLoc); + _tony.setScrollPosition(start); + } + + _curAction = TA_GOTO; + _point.setCustomPointer(NULL); + _point.setSpecialPointer(RMPointer::PTR_NONE); + _point.setAction(_curAction); + _inter.reset(); + _inv.reset(); + + mpalStartIdlePoll(_nCurLoc); +} + +uint32 RMGfxEngine::loadLocation(int nLoc, RMPoint ptTonyStart, RMPoint start) { + _nCurLoc = nLoc; + + bool bLoaded = false; + for (int i = 0; i < 5; i++) { + // Try the loading of the location + RMRes res(_nCurLoc); + if (!res.isValid()) + continue; + + Common::SeekableReadStream *ds = res.getReadStream(); + _loc.load(*ds); + delete ds; + + initForNewLocation(nLoc, ptTonyStart, start); + bLoaded = true; + break; + } + + if (!bLoaded) + error("Location was not loaded"); + + if (_bOption) + _opt.reInit(_bigBuf); + + _bLocationLoaded = true; + + // On entering the location + return CORO_INVALID_PID_VALUE; //mpalQueryDoAction(0, m_nCurLoc, 0); +} + +void RMGfxEngine::unloadLocation(CORO_PARAM, bool bDoOnExit, uint32 *result) { + CORO_BEGIN_CONTEXT; + uint32 h; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + // Release the location + CORO_INVOKE_2(mpalEndIdlePoll, _nCurLoc, NULL); + + // On Exit? + if (bDoOnExit) { + _ctx->h = mpalQueryDoAction(1, _nCurLoc, 0); + if (_ctx->h != CORO_INVALID_PID_VALUE) + CORO_INVOKE_2(CoroScheduler.waitForSingleObject, _ctx->h, CORO_INFINITE); + } + + _bLocationLoaded = false; + + _bigBuf.clearOT(); + _loc.unload(); + + if (result != NULL) + *result = CORO_INVALID_PID_VALUE; + + CORO_END_CODE; +} + +void RMGfxEngine::init() { + // Screen loading + RMResRaw *raw; + RMGfxSourceBuffer16 *load = NULL; + INIT_GFX16_FROMRAW(20038, load); + _bigBuf.addPrim(new RMGfxPrimitive(load)); + _bigBuf.drawOT(Common::nullContext); + _bigBuf.clearOT(); + delete load; + + // Display 'Loading' screen + _bigBuf.addDirtyRect(Common::Rect(0, 0, RM_SX, RM_SY)); + g_vm->_window.getNewFrame(*this, NULL); + g_vm->_window.repaint(); + + // Activate GUI + _bGUIOption = true; + _bGUIInterface = true; + _bGUIInventory = true; + + GLOBALS._bSkipSfxNoLoop = false; + _bMustEnterMenu = false; + GLOBALS._bIdleExited = false; + _bOption = false; + _bWiping = false; + _hWipeEvent = CoroScheduler.createEvent(false, false); + + // Initialize the IRQ function for items for MPAL + GLOBALS._gfxEngine = this; + mpalInstallItemIrq(itemIrq); + + // Initialize the mouse pointer + _point.init(); + + // Initialize Tony + _tony.init(); + _tony.linkToBoxes(&g_vm->_theBoxes); + + // Initialize the inventory and the interface + _inv.init(); + _inter.init(); + + // Download the location and set priorities @@@@@ + _bLocationLoaded = false; + + enableInput(); + + // Starting the game + _tony.executeAction(20, 1, 0); +} + +void RMGfxEngine::close() { + _bigBuf.clearOT(); + + _inter.close(); + _inv.close(); + _tony.close(); + _point.close(); +} + +void RMGfxEngine::enableInput() { + _bInput = true; +} + +void RMGfxEngine::disableInput() { + _bInput = false; + _inter.reset(); +} + +void RMGfxEngine::enableMouse() { + _bAlwaysDrawMouse = true; +} + +void RMGfxEngine::disableMouse() { + _bAlwaysDrawMouse = false; +} + +#define TONY_SAVEGAME_VERSION 8 + +void RMGfxEngine::saveState(const Common::String &fn, byte *curThumb, const Common::String &name) { + Common::OutSaveFile *f; + byte *state; + char buf[4]; + RMPoint tp = _tony.position(); + + // Saving: MPAL variables, current location, and Tony inventory position + + // For now, we only save the MPAL state + uint size = mpalGetSaveStateSize(); + state = new byte[size]; + mpalSaveState(state); + + uint thumbsize = 160 * 120 * 2; + + buf[0] = 'R'; + buf[1] = 'M'; + buf[2] = 'S'; + buf[3] = TONY_SAVEGAME_VERSION; + + f = g_system->getSavefileManager()->openForSaving(fn); + if (f == NULL) + return; + + f->write(buf, 4); + f->writeUint32LE(thumbsize); + f->write(curThumb, thumbsize); + + // Difficulty level + int i = mpalQueryGlobalVar("VERSIONEFACILE"); + f->writeByte(i); + + i = strlen(name.c_str()); + f->writeByte(i); + f->write(name.c_str(), i); + f->writeUint32LE(_nCurLoc); + f->writeUint32LE(tp._x); + f->writeUint32LE(tp._y); + + f->writeUint32LE(size); + f->write(state, size); + delete[] state; + + // Inventory + size = _inv.getSaveStateSize(); + state = new byte[size]; + _inv.saveState(state); + f->writeUint32LE(size); + f->write(state, size); + delete[] state; + + // boxes + size = g_vm->_theBoxes.getSaveStateSize(); + state = new byte[size]; + g_vm->_theBoxes.saveState(state); + f->writeUint32LE(size); + f->write(state, size); + delete[] state; + + // New Ver5 + // Saves the state of the shepherdess and show yourself + bool bStat = _tony.getShepherdess(); + f->writeByte(bStat); + bStat = _inter.getPerorate(); + f->writeByte(bStat); + + // Save the chars + charsSaveAll(f); + + // Save the options + f->writeByte(GLOBALS._bCfgInvLocked); + f->writeByte(GLOBALS._bCfgInvNoScroll); + f->writeByte(GLOBALS._bCfgTimerizedText); + f->writeByte(GLOBALS._bCfgInvUp); + f->writeByte(GLOBALS._bCfgAnni30); + f->writeByte(GLOBALS._bCfgAntiAlias); + f->writeByte(GLOBALS._bShowSubtitles); + f->writeByte(GLOBALS._bCfgTransparence); + f->writeByte(GLOBALS._bCfgInterTips); + f->writeByte(GLOBALS._bCfgDubbing); + f->writeByte(GLOBALS._bCfgMusic); + f->writeByte(GLOBALS._bCfgSFX); + f->writeByte(GLOBALS._nCfgTonySpeed); + f->writeByte(GLOBALS._nCfgTextSpeed); + f->writeByte(GLOBALS._nCfgDubbingVolume); + f->writeByte(GLOBALS._nCfgMusicVolume); + f->writeByte(GLOBALS._nCfgSFXVolume); + + // Save the hotspots + saveChangedHotspot(f); + + // Save the music + saveMusic(f); + + f->finalize(); + delete f; +} + +void RMGfxEngine::loadState(CORO_PARAM, const Common::String &fn) { + // PROBLEM: You should change the location in a separate process to do the OnEnter + CORO_BEGIN_CONTEXT; + Common::InSaveFile *f; + byte *state, *statecmp; + uint size, sizecmp; + char buf[4]; + RMPoint tp; + int loc; + int ver; + int i; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + _ctx->f = g_system->getSavefileManager()->openForLoading(fn); + if (_ctx->f == NULL) + return; + _ctx->f->read(_ctx->buf, 4); + + if (_ctx->buf[0] != 'R' || _ctx->buf[1] != 'M' || _ctx->buf[2] != 'S') { + delete _ctx->f; + return; + } + + _ctx->ver = _ctx->buf[3]; + + if (_ctx->ver == 0 || _ctx->ver > TONY_SAVEGAME_VERSION) { + delete _ctx->f; + return; + } + + if (_ctx->ver >= 0x3) { + // There is a thumbnail. If the version is between 5 and 7, it's compressed + if ((_ctx->ver >= 0x5) && (_ctx->ver <= 0x7)) { + _ctx->i = 0; + _ctx->i = _ctx->f->readUint32LE(); + _ctx->f->seek(_ctx->i); + } else { + if (_ctx->ver >= 8) + // Skip thumbnail size + _ctx->f->skip(4); + + _ctx->f->seek(160 * 120 * 2, SEEK_CUR); + } + } + + if (_ctx->ver >= 0x5) { + // Skip the difficulty level + _ctx->f->seek(1, SEEK_CUR); + } + + if (_ctx->ver >= 0x4) { // Skip the savegame name, which serves no purpose + _ctx->i = _ctx->f->readByte(); + _ctx->f->seek(_ctx->i, SEEK_CUR); + } + + _ctx->loc = _ctx->f->readUint32LE(); + _ctx->tp._x = _ctx->f->readUint32LE(); + _ctx->tp._y = _ctx->f->readUint32LE(); + _ctx->size = _ctx->f->readUint32LE(); + + if ((_ctx->ver >= 0x5) && (_ctx->ver <= 7)) { + // MPAL was packed! + _ctx->sizecmp = _ctx->f->readUint32LE(); + _ctx->state = new byte[_ctx->size]; + _ctx->statecmp = new byte[_ctx->sizecmp]; + _ctx->f->read(_ctx->statecmp, _ctx->sizecmp); + lzo1x_decompress(_ctx->statecmp, _ctx->sizecmp, _ctx->state, &_ctx->size); + delete[] _ctx->statecmp; + } else { + // Read uncompressed MPAL data + _ctx->state = new byte[_ctx->size]; + _ctx->f->read(_ctx->state, _ctx->size); + } + + mpalLoadState(_ctx->state); + delete[] _ctx->state; + + // Inventory + _ctx->size = _ctx->f->readUint32LE(); + _ctx->state = new byte[_ctx->size]; + _ctx->f->read(_ctx->state, _ctx->size); + _inv.loadState(_ctx->state); + delete[] _ctx->state; + + if (_ctx->ver >= 0x2) { // Version 2: box please + _ctx->size = _ctx->f->readUint32LE(); + _ctx->state = new byte[_ctx->size]; + _ctx->f->read(_ctx->state, _ctx->size); + g_vm->_theBoxes.loadState(_ctx->state); + delete[] _ctx->state; + } + + if (_ctx->ver >= 5) { + // Version 5 + bool bStat = false; + + bStat = _ctx->f->readByte(); + _tony.setShepherdess(bStat); + bStat = _ctx->f->readByte(); + _inter.setPerorate(bStat); + + charsLoadAll(_ctx->f); + } + + if (_ctx->ver >= 6) { + // Load options + GLOBALS._bCfgInvLocked = _ctx->f->readByte(); + GLOBALS._bCfgInvNoScroll = _ctx->f->readByte(); + GLOBALS._bCfgTimerizedText = _ctx->f->readByte(); + GLOBALS._bCfgInvUp = _ctx->f->readByte(); + GLOBALS._bCfgAnni30 = _ctx->f->readByte(); + GLOBALS._bCfgAntiAlias = _ctx->f->readByte(); + GLOBALS._bShowSubtitles = _ctx->f->readByte(); + GLOBALS._bCfgTransparence = _ctx->f->readByte(); + GLOBALS._bCfgInterTips = _ctx->f->readByte(); + GLOBALS._bCfgDubbing = _ctx->f->readByte(); + GLOBALS._bCfgMusic = _ctx->f->readByte(); + GLOBALS._bCfgSFX = _ctx->f->readByte(); + GLOBALS._nCfgTonySpeed = _ctx->f->readByte(); + GLOBALS._nCfgTextSpeed = _ctx->f->readByte(); + GLOBALS._nCfgDubbingVolume = _ctx->f->readByte(); + GLOBALS._nCfgMusicVolume = _ctx->f->readByte(); + GLOBALS._nCfgSFXVolume = _ctx->f->readByte(); + + // Load hotspots + loadChangedHotspot(_ctx->f); + } + + if (_ctx->ver >= 7) { + loadMusic(_ctx->f); + } + + delete _ctx->f; + + CORO_INVOKE_2(unloadLocation, false, NULL); + loadLocation(_ctx->loc, _ctx->tp, RMPoint(-1, -1)); + _tony.setPattern(RMTony::PAT_STANDRIGHT); + + // On older versions, need to an enter action + if (_ctx->ver < 5) + mpalQueryDoAction(0, _ctx->loc, 0); + else { + // In the new ones, we just reset the mcode + mCharResetCodes(); + } + + if (_ctx->ver >= 6) + reapplyChangedHotspot(); + + CORO_INVOKE_0(restoreMusic); + + _bGUIInterface = true; + _bGUIInventory = true; + _bGUIOption = true; + + CORO_END_CODE; +} + +void RMGfxEngine::pauseSound(bool bPause) { + if (_bLocationLoaded) + _loc.pauseSound(bPause); +} + +void RMGfxEngine::initWipe(int type) { + _bWiping = true; + _nWipeType = type; + _nWipeStep = 0; + + if (_nWipeType == 1) + _rcWipeEllipse = Common::Rect(80, 0, 640 - 80, 480); + else if (_nWipeType == 2) + _rcWipeEllipse = Common::Rect(320 - FSTEP, 240 - FSTEP, 320 + FSTEP, 240 + FSTEP); +} + +void RMGfxEngine::closeWipe() { + _bWiping = false; +} + +void RMGfxEngine::waitWipeEnd(CORO_PARAM) { + CoroScheduler.waitForSingleObject(coroParam, _hWipeEvent, CORO_INFINITE); +} + +bool RMGfxEngine::canLoadSave() { + return _bInput && !_tony.inAction() && !g_vm->getIsDemo(); +} + +RMGfxEngine::operator RMGfxTargetBuffer &() { + return _bigBuf; +} + +RMInput &RMGfxEngine::getInput() { + return _input; +} + +RMPointer &RMGfxEngine::getPointer() { + return _point; +} + +/** + * Link to graphic task + */ +void RMGfxEngine::linkGraphicTask(RMGfxTask *task) { + _bigBuf.addPrim(new RMGfxPrimitive(task)); +} + +void RMGfxEngine::setPerorate(bool bpal) { + _inter.setPerorate(bpal); +} + +} // End of namespace Tony diff --git a/engines/tony/gfxengine.h b/engines/tony/gfxengine.h new file mode 100644 index 0000000000..ab32a01972 --- /dev/null +++ b/engines/tony/gfxengine.h @@ -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. + * + * 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 Tony Tough source code + * + * Copyright (c) 1997-2003 Nayma Software + */ + +#ifndef TONY_GFXENGINE_H +#define TONY_GFXENGINE_H + +#include "common/scummsys.h" +#include "common/system.h" +#include "common/rect.h" +#include "tony/mpal/memory.h" +#include "tony/game.h" +#include "tony/gfxcore.h" +#include "tony/input.h" +#include "tony/inventory.h" +#include "tony/tonychar.h" +#include "tony/utils.h" + +namespace Tony { + +class RMGfxEngine { +private: + RMGfxTargetBuffer _bigBuf; + RMInput _input; + RMPointer _point; + RMLocation _loc; + RMOptionScreen _opt; + RMTony _tony; + RMInventory _inv; + RMInterface _inter; + RMTextItemName _itemName; + + bool _bOption; + bool _bLocationLoaded; + + bool _bInput; + bool _bAlwaysDrawMouse; + + int _nCurLoc; + RMTonyAction _curAction; + int _curActionObj; + + int _nWipeType; + uint32 _hWipeEvent; + int _nWipeStep; + + bool _bMustEnterMenu; +protected: + static void itemIrq(uint32 dwItem, int nPattern, int nStatus); + void initForNewLocation(int nLoc, RMPoint ptTonyStart, RMPoint start); +public: + bool _bWiping; + Common::Rect _rcWipeEllipse; + bool _bGUIOption; + bool _bGUIInterface; + bool _bGUIInventory; +public: + RMGfxEngine(); + virtual ~RMGfxEngine(); + + // Draw the next frame + void doFrame(CORO_PARAM, bool bDrawLocation); + + // Initializes the graphics engine + void init(); + + // Closes the graphics engine + void close(); + + // Warns when entering or exits the options menu + void openOptionScreen(CORO_PARAM, int type); + + // Enables or disables mouse input + void enableInput(); + void disableInput(); + + // Enables and disables mouse draw + void enableMouse(); + void disableMouse(); + + operator RMGfxTargetBuffer &(); + RMInput &getInput(); + RMPointer &getPointer(); + + // Link to the custom function list + void initCustomDll(); + + // Link to graphic task + void linkGraphicTask(RMGfxTask *task); + + // Manage a location + uint32 loadLocation(int nLoc, RMPoint ptTonyStart, RMPoint start); + void unloadLocation(CORO_PARAM, bool bDoOnExit, uint32 *result); + int getCurrentLocation() const { return _nCurLoc; } + + // State management + void saveState(const Common::String &fn, byte *curThumb, const Common::String &name); + void loadState(CORO_PARAM, const Common::String &fn); + + // Pauses sound + void pauseSound(bool bPause); + + // Wipe + void initWipe(int type); + void closeWipe(); + void waitWipeEnd(CORO_PARAM); + + void setPerorate(bool bpal); + + bool canLoadSave(); +}; + +} // End of namespace Tony + +#endif diff --git a/engines/tony/globals.cpp b/engines/tony/globals.cpp new file mode 100644 index 0000000000..8e4ae240a0 --- /dev/null +++ b/engines/tony/globals.cpp @@ -0,0 +1,135 @@ +/* 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. + * + */ + +#include "common/algorithm.h" +#include "tony/globals.h" + +namespace Tony { + +Globals::Globals() { + _nextLoop = false; + _nextChannel = 0; + _nextSync = 0; + _curChannel = 0; + _flipflop = 0; + _curBackText = NULL; + _bTonyIsSpeaking = false; + _curChangedHotspot = 0; + _tony = NULL; + _pointer = NULL; + _boxes = NULL; + _loc = NULL; + _inventory = NULL; + _input = NULL; + _gfxEngine = NULL; + EnableGUI = NULL; + DisableGUI = NULL; + + _dwTonyNumTexts = 0; + _bTonyInTexts = false; + _bStaticTalk = false; + _bAlwaysDisplay = false; + _bIdleExited = false; + _bSkipSfxNoLoop = false; + _bNoBullsEye = false; + _curDialog = 0; + _curSoundEffect = 0; + _bFadeOutStop = false; + + _bSkipIdle = false; + _hSkipIdle = 0; + _lastMusic = 0; + _lastTappeto = 0; + Common::fill(&_ambiance[0], &_ambiance[200], 0); + _fullScreenMessageLoc = 0; + + // MPAL global variables + _mpalError = 0; + _lpiifCustom = NULL; + _lplpFunctions = NULL; + _lplpFunctionStrings = NULL; + _nObjs = 0; + _nVars = 0; + _hVars = NULL; + _lpmvVars = NULL; + _nMsgs = 0; + _hMsgs = NULL; + _lpmmMsgs = NULL; + _nDialogs = 0; + _hDialogs = NULL; + _lpmdDialogs = NULL; + _nItems = 0; + _hItems = NULL; + _lpmiItems = NULL; + _nLocations = 0; + _hLocations = NULL; + _lpmlLocations = NULL; + _nScripts = 0; + _hScripts = NULL; + _lpmsScripts = NULL; + _nResources = 0; + _lpResources = NULL; + _bExecutingAction = false; + _bExecutingDialog = false; + Common::fill(&_nPollingLocations[0], &_nPollingLocations[MAXPOLLINGLOCATIONS], 0); + Common::fill(&_hEndPollingLocations[0], &_hEndPollingLocations[MAXPOLLINGLOCATIONS], 0); + Common::fill(&_pollingThreads[0], &_pollingThreads[MAXPOLLINGLOCATIONS], 0); + _hAskChoice = 0; + _hDoneChoice = 0; + _nExecutingAction = 0; + _nExecutingDialog = 0; + _nExecutingChoice = 0; + _nSelectedChoice = 0; + _nTonyNextTalkType = RMTony::TALK_NORMAL; + _saveTonyLoc = 0; + + for (int i = 0; i < 16; ++i) { + Common::fill((byte *)&_character[i], (byte *)&_character[i] + sizeof(CharacterStruct), 0); + _isMChar[i] = false; + } + + for (int i = 0; i < 10; ++i) + Common::fill((byte *)&_mCharacter[i], (byte *)&_mCharacter[i] + sizeof(MCharacterStruct), 0); + for (int i = 0; i < 256; ++i) + Common::fill((byte *)&_changedHotspot[i], (byte *)&_changedHotspot[i] + sizeof(ChangedHotspotStruct), 0); + + // Set up globals that have explicit initial values + _bCfgInvLocked = false; + _bCfgInvNoScroll = false; + _bCfgTimerizedText = true; + _bCfgInvUp = false; + _bCfgAnni30 = false; + _bCfgAntiAlias = false; + _bCfgTransparence = true; + _bCfgInterTips = true; + _bShowSubtitles = true; + _nCfgTonySpeed = 3; + _nCfgTextSpeed = 5; + _bCfgDubbing = true; + _bCfgMusic = true; + _bCfgSFX = true; + _nCfgDubbingVolume = 10; + _nCfgMusicVolume = 7; + _nCfgSFXVolume = 10; +} + +} // End of namespace Tony diff --git a/engines/tony/globals.h b/engines/tony/globals.h new file mode 100644 index 0000000000..d8d8d3eba5 --- /dev/null +++ b/engines/tony/globals.h @@ -0,0 +1,286 @@ +/* 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. + * + */ + +#ifndef TONY_GLOBALS +#define TONY_GLOBALS + +#include "common/savefile.h" +#include "tony/gfxengine.h" +#include "tony/input.h" +#include "tony/inventory.h" +#include "tony/loc.h" +#include "tony/tonychar.h" +#include "tony/mpal/mpal.h" +#include "tony/mpal/mpaldll.h" + +namespace Tony { + +#define AMBIANCE_CRICKETS 1 +#define AMBIANCE_CRICKETSMUFFLED 2 +#define AMBIANCE_CRICKETSWIND 3 +#define AMBIANCE_CRICKETSWIND1 4 +#define AMBIANCE_WIND 5 +#define AMBIANCE_SEA 6 +#define AMBIANCE_SEAHALFVOLUME 7 + +struct CharacterStruct { + uint32 _code; + RMItem *_item; + byte _r, _g, _b; + int _talkPattern; + int _standPattern; + int _startTalkPattern, _endTalkPattern; + int _numTexts; + + void save(Common::OutSaveFile *f) { + f->writeUint32LE(_code); + f->writeUint32LE(0); + f->writeByte(_r); + f->writeByte(_g); + f->writeByte(_b); + f->writeUint32LE(_talkPattern); + f->writeUint32LE(_standPattern); + f->writeUint32LE(_startTalkPattern); + f->writeUint32LE(_endTalkPattern); + f->writeUint32LE(_numTexts); + } + void load(Common::InSaveFile *f) { + _code = f->readUint32LE(); + f->readUint32LE(); + _item = NULL; + _r = f->readByte(); + _g = f->readByte(); + _b = f->readByte(); + _talkPattern = f->readUint32LE(); + _standPattern = f->readUint32LE(); + _startTalkPattern = f->readUint32LE(); + _endTalkPattern = f->readUint32LE(); + _numTexts = f->readUint32LE(); + } +}; + +struct MCharacterStruct { + uint32 _code; + RMItem *_item; + byte _r, _g, _b; + int _x, _y; + int _numTalks[10]; + int _curGroup; + int _numTexts; + bool _bInTexts; + int _curTalk; + bool _bAlwaysBack; + + void save(Common::OutSaveFile *f) { + f->writeUint32LE(_code); + f->writeUint32LE(0); + f->writeByte(_r); + f->writeByte(_g); + f->writeByte(_b); + f->writeUint32LE(_x); + f->writeUint32LE(_y); + for (int i = 0; i < 10; ++i) + f->writeUint32LE(_numTalks[i]); + f->writeUint32LE(_curGroup); + f->writeUint32LE(_numTexts); + f->writeByte(_bInTexts); + f->writeUint32LE(_curTalk); + f->writeByte(_bAlwaysBack); + } + void load(Common::InSaveFile *f) { + _code = f->readUint32LE(); + f->readUint32LE(); + _item = NULL; + _r = f->readByte(); + _g = f->readByte(); + _b = f->readByte(); + _x = f->readUint32LE(); + _y = f->readUint32LE(); + for (int i = 0; i < 10; ++i) + _numTalks[i] = f->readUint32LE(); + _curGroup = f->readUint32LE(); + _numTexts = f->readUint32LE(); + _bInTexts = f->readByte(); + _curTalk = f->readUint32LE(); + _bAlwaysBack = f->readByte(); + } +}; + +struct ChangedHotspotStruct { + uint32 _dwCode; + uint32 _nX, _nY; + + void save(Common::OutSaveFile *f) { + f->writeUint32LE(_dwCode); + f->writeUint32LE(_nX); + f->writeUint32LE(_nY); + } + void load(Common::InSaveFile *f) { + _dwCode = f->readUint32LE(); + _nX = f->readUint32LE(); + _nY = f->readUint32LE(); + } +}; + + +/** + * Description of a call to a custom function. + */ +typedef struct { + int _nCf; + int _arg1, _arg2, _arg3, _arg4; +} CfCall; + +typedef CfCall *LpCfCall; + +struct CoroutineMutex { + CoroutineMutex() : _eventId(0), _ownerPid(0), _lockCount(0) { } + + uint32 _eventId; + uint32 _ownerPid; + uint32 _lockCount; +}; + +/****************************************************************************\ +* Global variables +\****************************************************************************/ + +/** + * Globals class + */ +class Globals { +public: + Globals(); + + Common::String _nextMusic; + bool _nextLoop; + int _nextChannel; + int _nextSync; + int _curChannel; + int _flipflop; + CharacterStruct _character[16]; + MCharacterStruct _mCharacter[10]; + ChangedHotspotStruct _changedHotspot[256]; + bool _isMChar[16]; + bool _bAlwaysDisplay; + RMPoint _saveTonyPos; + int _saveTonyLoc; + RMTextDialog *_curBackText; + bool _bTonyIsSpeaking; + int _curChangedHotspot; + bool _bCfgInvLocked; + bool _bCfgInvNoScroll; + bool _bCfgTimerizedText; + bool _bCfgInvUp; + bool _bCfgAnni30; + bool _bCfgAntiAlias; + bool _bShowSubtitles; + bool _bCfgTransparence; + bool _bCfgInterTips; + bool _bCfgDubbing; + bool _bCfgMusic; + bool _bCfgSFX; + int _nCfgTonySpeed; + int _nCfgTextSpeed; + int _nCfgDubbingVolume; + int _nCfgMusicVolume; + int _nCfgSFXVolume; + bool _bSkipSfxNoLoop; + bool _bIdleExited; + bool _bNoBullsEye; + int _curDialog; + int _curSoundEffect; + bool _bFadeOutStop; + + RMTony *_tony; + RMPointer *_pointer; + RMGameBoxes *_boxes; + RMLocation *_loc; + RMInventory *_inventory; + RMInput *_input; + RMGfxEngine *_gfxEngine; + + void (*EnableGUI)(); + void (*DisableGUI)(); + + uint32 _dwTonyNumTexts; + bool _bTonyInTexts; + bool _bStaticTalk; + RMTony::CharacterTalkType _nTonyNextTalkType; + + RMPoint _startLocPos[256]; + CoroutineMutex _mut[10]; + + bool _bSkipIdle; + uint32 _hSkipIdle; + + int _lastMusic, _lastTappeto; + + int _ambiance[200]; + RMPoint _fullScreenMessagePt; + int _fullScreenMessageLoc; + + /** + * @defgroup MPAL variables + */ + uint32 _mpalError; + LPITEMIRQFUNCTION _lpiifCustom; + LPLPCUSTOMFUNCTION _lplpFunctions; + Common::String *_lplpFunctionStrings; + uint16 _nObjs; + uint16 _nVars; + MpalHandle _hVars; + LpMpalVar _lpmvVars; + uint16 _nMsgs; + MpalHandle _hMsgs; + LpMpalMsg _lpmmMsgs; + uint16 _nDialogs; + MpalHandle _hDialogs; + LpMpalDialog _lpmdDialogs; + uint16 _nItems; + MpalHandle _hItems; + LpMpalItem _lpmiItems; + uint16 _nLocations; + MpalHandle _hLocations; + LpMpalLocation _lpmlLocations; + uint16 _nScripts; + MpalHandle _hScripts; + LpMpalScript _lpmsScripts; + Common::File _hMpr; + uint16 _nResources; + uint32 *_lpResources; + bool _bExecutingAction; + bool _bExecutingDialog; + uint32 _nPollingLocations[MAXPOLLINGLOCATIONS]; + uint32 _hEndPollingLocations[MAXPOLLINGLOCATIONS]; + uint32 _pollingThreads[MAXPOLLINGLOCATIONS]; + uint32 _hAskChoice; + uint32 _hDoneChoice; + uint32 _nExecutingAction; + uint32 _nExecutingDialog; + uint32 _nExecutingChoice; + uint32 _nSelectedChoice; +}; + +} // End of namespace Tony + +#endif // TONY_GLOBALS diff --git a/engines/tony/input.cpp b/engines/tony/input.cpp new file mode 100644 index 0000000000..b96ccaf842 --- /dev/null +++ b/engines/tony/input.cpp @@ -0,0 +1,157 @@ +/* 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 Tony Tough source code + * + * Copyright (c) 1997-2003 Nayma Software + */ + +#include "tony/gfxengine.h" +#include "tony/tony.h" + +namespace Tony { + +RMInput::RMInput() { + // Setup mouse fields + _clampMouse = false; + _mousePos.set(0, 0); + _leftButton = _rightButton = false; + _leftClickMouse = _leftReleaseMouse = false; + _rightClickMouse = _rightReleaseMouse = false; + + Common::fill((byte *)&_event, (byte *)&_event + sizeof(Common::Event), 0); + + // Setup keyboard fields + Common::fill(&_keyDown[0], &_keyDown[350], 0); +} + +RMInput::~RMInput() { +} + +void RMInput::poll() { + _leftClickMouse = _leftReleaseMouse = _rightClickMouse = _rightReleaseMouse = false; + + // Get pending events + while (g_system->getEventManager()->pollEvent(_event) && !g_vm->shouldQuit()) { + switch (_event.type) { + case Common::EVENT_MOUSEMOVE: + case Common::EVENT_LBUTTONDOWN: + case Common::EVENT_LBUTTONUP: + case Common::EVENT_RBUTTONDOWN: + case Common::EVENT_RBUTTONUP: + _mousePos.set(_event.mouse.x, _event.mouse.y); + + if (_event.type == Common::EVENT_LBUTTONDOWN) { + _leftButton = true; + _leftClickMouse = true; + } else if (_event.type == Common::EVENT_LBUTTONUP) { + _leftButton = false; + _leftReleaseMouse = true; + } else if (_event.type == Common::EVENT_RBUTTONDOWN) { + _rightButton = true; + _rightClickMouse = true; + } else if (_event.type == Common::EVENT_RBUTTONUP) { + _rightButton = false; + _rightReleaseMouse = true; + } else + continue; + + // Since a mouse button has changed, don't do any further event processing this frame + return; + + case Common::EVENT_KEYDOWN: + // Check for debugger + if ((_event.kbd.keycode == Common::KEYCODE_d) && (_event.kbd.flags & Common::KBD_CTRL)) { + // Attach to the debugger + g_vm->_debugger->attach(); + g_vm->_debugger->onFrame(); + } else { + // Flag the given key as being down + _keyDown[(int)_event.kbd.keycode] = true; + } + return; + + case Common::EVENT_KEYUP: + _keyDown[(int)_event.kbd.keycode] = false; + return; + + default: + break; + } + } +} + +bool RMInput::mouseLeft() { + return _leftButton; +} + +bool RMInput::mouseRight() { + return _rightButton; +} + +/** + * Return true if a key has been pressed + */ +bool RMInput::getAsyncKeyState(Common::KeyCode kc) { + // The act of testing for a particular key automatically clears the state, to prevent + // the same key being registered in multiple different frames + bool result = _keyDown[(int)kc]; + _keyDown[(int)kc] = false; + return result; +} + +/** + * Reading of the mouse + */ +RMPoint RMInput::mousePos() { + return _mousePos; +} + +/** + * Events of mouse clicks + */ +bool RMInput::mouseLeftClicked() { + return _leftClickMouse; +} + +bool RMInput::mouseRightClicked() { + return _rightClickMouse; +} + +bool RMInput::mouseBothClicked() { + return _leftClickMouse && _rightClickMouse; +} + +bool RMInput::mouseLeftReleased() { + return _leftReleaseMouse; +} + +bool RMInput::mouseRightReleased() { + return _rightReleaseMouse; +} + +bool RMInput::mouseBothReleased() { + return _leftReleaseMouse && _rightReleaseMouse; +} + +} // End of namespace Tony diff --git a/engines/tony/input.h b/engines/tony/input.h new file mode 100644 index 0000000000..d07eaefe34 --- /dev/null +++ b/engines/tony/input.h @@ -0,0 +1,88 @@ +/* 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 Tony Tough source code + * + * Copyright (c) 1997-2003 Nayma Software + */ + +#ifndef TONY_INPUT_H +#define TONY_INPUT_H + +#include "common/events.h" +#include "tony/utils.h" + +namespace Tony { + +class RMInput { +private: + Common::Event _event; + + // Mouse related fields + RMPoint _mousePos; + bool _clampMouse; + bool _leftButton, _rightButton; + bool _leftClickMouse, _leftReleaseMouse, _rightClickMouse, _rightReleaseMouse; + + // Keyboard related fields + bool _keyDown[350]; + +public: + RMInput(); + ~RMInput(); + + /** + * Polling (must be performed once per frame) + */ + void poll(); + + /** + * Reading of the mouse + */ + RMPoint mousePos(); + + /** + * Current status of the mouse buttons + */ + bool mouseLeft(); + bool mouseRight(); + + /** + * Events of mouse clicks + */ + bool mouseLeftClicked(); + bool mouseRightClicked(); + bool mouseBothClicked(); + bool mouseLeftReleased(); + bool mouseRightReleased(); + bool mouseBothReleased(); + + /** + * Returns true if the given key is pressed + */ + bool getAsyncKeyState(Common::KeyCode kc); +}; + +} // End of namespace Tony + +#endif diff --git a/engines/tony/inventory.cpp b/engines/tony/inventory.cpp new file mode 100644 index 0000000000..12540e5b7f --- /dev/null +++ b/engines/tony/inventory.cpp @@ -0,0 +1,938 @@ +/* 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 Tony Tough source code + * + * Copyright (c) 1997-2003 Nayma Software + */ + +#include "common/textconsole.h" +#include "tony/mpal/mpalutils.h" +#include "tony/inventory.h" +#include "tony/game.h" +#include "tony/tony.h" + +namespace Tony { + + +/****************************************************************************\ +* RMInventory Methods +\****************************************************************************/ + +RMInventory::RMInventory() { + _items = NULL; + _state = CLOSED; + _bCombining = false; + _csModifyInterface = g_system->createMutex(); + _nItems = 0; + + Common::fill(_inv, _inv + 256, 0); + _nInv = 0; + _curPutY = 0; + _curPutTime = 0; + _curPos = 0; + _bHasFocus = false; + _nSelectObj = 0; + _nCombine = 0; + _bBlinkingRight = false; + _bBlinkingLeft = false; + _miniAction = 0; +} + +RMInventory::~RMInventory() { + close(); + g_system->deleteMutex(_csModifyInterface); +} + +bool RMInventory::checkPointInside(const RMPoint &pt) { + if (!GLOBALS._bCfgInvUp) + return pt._y > RM_SY - 70; + else + return pt._y < 70; +} + + +void RMInventory::init() { + // Create the main buffer + create(RM_SX, 68); + setPriority(185); + + // Setup the inventory + _nInv = 0; + _curPos = 0; + _bCombining = false; + + // New items + _nItems = 78; // @@@ Number of takeable items + _items = new RMInventoryItem[_nItems + 1]; + + int curres = 10500; + + // Loop through the items + for (int i = 0; i <= _nItems; i++) { + // Load the items from the resource + RMRes res(curres); + assert(res.isValid()); + Common::SeekableReadStream *ds = res.getReadStream(); + + // Initialize the MPAL inventory item by reading it in. + _items[i]._icon.setInitCurPattern(false); + _items[i]._icon.readFromStream(*ds); + delete ds; + + // Puts in the default pattern 1 + _items[i]._pointer = NULL; + _items[i]._status = 1; + _items[i]._icon.setPattern(1); + _items[i]._icon.doFrame(this, false); + + curres++; + if (i == 0 || i == 28 || i == 29) + continue; + + _items[i]._pointer = new RMGfxSourceBuffer8RLEByteAA[_items[i]._icon.numPattern()]; + + for (int j = 0; j < _items[i]._icon.numPattern(); j++) { + RMResRaw raw(curres); + + assert(raw.isValid()); + + _items[i]._pointer[j].init((const byte *)raw, raw.width(), raw.height(), true); + curres++; + } + } + + _items[28]._icon.setPattern(1); + _items[29]._icon.setPattern(1); + + // Download interface + RMRes res(RES_I_MINIINTER); + assert(res.isValid()); + Common::SeekableReadStream *ds = res.getReadStream(); + _miniInterface.readFromStream(*ds); + _miniInterface.setPattern(1); + delete ds; + + // Create the text for hints on the mini interface + _hints[0].setAlignType(RMText::HCENTER, RMText::VTOP); + _hints[1].setAlignType(RMText::HCENTER, RMText::VTOP); + _hints[2].setAlignType(RMText::HCENTER, RMText::VTOP); + + // The text is taken from MPAL for translation + RMMessage msg1(15); + RMMessage msg2(13); + RMMessage msg3(14); + + _hints[0].writeText(msg1[0], 1); // Examine + _hints[1].writeText(msg2[0], 1); // Take + _hints[2].writeText(msg3[0], 1); // Use + + + // Prepare initial inventory + prepare(); + drawOT(Common::nullContext); + clearOT(); +} + +void RMInventory::close() { + // Has memory + if (_items != NULL) { + // Delete the item pointers + for (int i = 0; i <= _nItems; i++) + delete[] _items[i]._pointer; + + // Delete the items array + delete[] _items; + _items = NULL; + } + + destroy(); +} + +void RMInventory::reset() { + _state = CLOSED; + endCombine(); +} + +void RMInventory::draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim) { + CORO_BEGIN_CONTEXT; + RMPoint pos; + RMPoint pos2; + RMGfxPrimitive *p; + RMGfxPrimitive *p2; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + prim->setDst(RMPoint(0, _curPutY)); + g_system->lockMutex(_csModifyInterface); + CORO_INVOKE_2(RMGfxWoodyBuffer::draw, bigBuf, prim); + g_system->unlockMutex(_csModifyInterface); + + if (_state == SELECTING) { + + if (!GLOBALS._bCfgInvUp) { + _ctx->pos.set((_nSelectObj + 1) * 64 - 20, RM_SY - 113); + _ctx->pos2.set((_nSelectObj + 1) * 64 + 34, RM_SY - 150); + } else { + _ctx->pos.set((_nSelectObj + 1) * 64 - 20, 72 - 4); // The brown part is at the top :( + _ctx->pos2.set((_nSelectObj + 1) * 64 + 34, 119 - 4); + } + + _ctx->p = new RMGfxPrimitive(prim->_task, _ctx->pos); + _ctx->p2 = new RMGfxPrimitive(prim->_task, _ctx->pos2); + + // Draw the mini interface + CORO_INVOKE_2(_miniInterface.draw, bigBuf, _ctx->p); + + if (GLOBALS._bCfgInterTips) { + if (_miniAction == 1) // Examine + CORO_INVOKE_2(_hints[0].draw, bigBuf, _ctx->p2); + else if (_miniAction == 2) // Talk + CORO_INVOKE_2(_hints[1].draw, bigBuf, _ctx->p2); + else if (_miniAction == 3) // Use + CORO_INVOKE_2(_hints[2].draw, bigBuf, _ctx->p2); + } + + delete _ctx->p; + delete _ctx->p2; + } + + CORO_END_CODE; +} + +void RMInventory::removeThis(CORO_PARAM, bool &result) { + if (_state == CLOSED) + result = true; + else + result = false; +} + +void RMInventory::removeItem(int code) { + for (int i = 0; i < _nInv; i++) { + if (_inv[i] == code - 10000) { + g_system->lockMutex(_csModifyInterface); + + Common::copy(&_inv[i + 1], &_inv[i + 1] + (_nInv - i), &_inv[i]); + _nInv--; + + prepare(); + drawOT(Common::nullContext); + clearOT(); + g_system->unlockMutex(_csModifyInterface); + return; + } + } +} + +void RMInventory::addItem(int code) { + if (code <= 10000 || code >= 10101) { + // If we are here, it means that we are adding an item that should not be in the inventory + warning("RMInventory::addItem(%d) - Cannot find a valid icon for this item, and then it will not be added to the inventory", code); + } else { + g_system->lockMutex(_csModifyInterface); + if (_curPos + 8 == _nInv) { + // Break through the inventory! On the flashing pattern + _items[28]._icon.setPattern(2); + } + + _inv[_nInv++] = code - 10000; + + prepare(); + drawOT(Common::nullContext); + clearOT(); + g_system->unlockMutex(_csModifyInterface); + } +} + +void RMInventory::changeItemStatus(uint32 code, uint32 dwStatus) { + if (code <= 10000 || code >= 10101) { + error("RMInventory::changeItemStatus(%d) - Specified object code is not valid", code); + } else { + g_system->lockMutex(_csModifyInterface); + _items[code - 10000]._icon.setPattern(dwStatus); + _items[code - 10000]._status = dwStatus; + + prepare(); + drawOT(Common::nullContext); + clearOT(); + g_system->unlockMutex(_csModifyInterface); + } +} + + +void RMInventory::prepare() { + for (int i = 1; i < RM_SX / 64 - 1; i++) { + if (i - 1 + _curPos < _nInv) + addPrim(new RMGfxPrimitive(&_items[_inv[i - 1 + _curPos]]._icon, RMPoint(i * 64, 0))); + else + addPrim(new RMGfxPrimitive(&_items[0]._icon, RMPoint(i * 64, 0))); + } + + // Frecce + addPrim(new RMGfxPrimitive(&_items[29]._icon, RMPoint(0, 0))); + addPrim(new RMGfxPrimitive(&_items[28]._icon, RMPoint(640 - 64, 0))); +} + +bool RMInventory::miniActive() { + return _state == SELECTING; +} + +bool RMInventory::haveFocus(const RMPoint &mpos) { + // When we combine, have the focus only if we are on an arrow (to scroll) + if (_state == OPENED && _bCombining && checkPointInside(mpos) && (mpos._x < 64 || mpos._x > RM_SX - 64)) + return true; + + // If the inventory is open, focus we we go over it + if (_state == OPENED && !_bCombining && checkPointInside(mpos)) + return true; + + // If we are selecting a verb (and then right down), we always focus + if (_state == SELECTING) + return true; + + return false; +} + +void RMInventory::endCombine() { + _bCombining = false; +} + +bool RMInventory::leftClick(const RMPoint &mpos, int &nCombineObj) { + // The left click picks an item from your inventory to use it with the background + int n = mpos._x / 64; + + if (_state == OPENED) { + if (n > 0 && n < RM_SX / 64 - 1 && _inv[n - 1 + _curPos] != 0) { + _bCombining = true; //m_state = COMBINING; + _nCombine = _inv[n - 1 + _curPos]; + nCombineObj = _nCombine + 10000; + + g_vm->playUtilSFX(1); + return true; + } + } + + // Click the right arrow + if ((_state == OPENED) && _bBlinkingRight) { + g_system->lockMutex(_csModifyInterface); + _curPos++; + + if (_curPos + 8 >= _nInv) { + _bBlinkingRight = false; + _items[28]._icon.setPattern(1); + } + + if (_curPos > 0) { + _bBlinkingLeft = true; + _items[29]._icon.setPattern(2); + } + + prepare(); + drawOT(Common::nullContext); + clearOT(); + g_system->unlockMutex(_csModifyInterface); + } + // Click the left arrow + else if ((_state == OPENED) && _bBlinkingLeft) { + assert(_curPos > 0); + g_system->lockMutex(_csModifyInterface); + _curPos--; + + if (_curPos == 0) { + _bBlinkingLeft = false; + _items[29]._icon.setPattern(1); + } + + if (_curPos + 8 < _nInv) { + _bBlinkingRight = true; + _items[28]._icon.setPattern(2); + } + + prepare(); + drawOT(Common::nullContext); + clearOT(); + g_system->unlockMutex(_csModifyInterface); + } + + + return false; +} + +void RMInventory::rightClick(const RMPoint &mpos) { + assert(checkPointInside(mpos)); + + if (_state == OPENED && !_bCombining) { + // Open the context interface + int n = mpos._x / 64; + + if (n > 0 && n < RM_SX / 64 - 1 && _inv[n - 1 + _curPos] != 0) { + _state = SELECTING; + _miniAction = 0; + _nSelectObj = n - 1; + + g_vm->playUtilSFX(0); + } + } + + if ((_state == OPENED) && _bBlinkingRight) { + g_system->lockMutex(_csModifyInterface); + _curPos += 7; + if (_curPos + 8 > _nInv) + _curPos = _nInv - 8; + + if (_curPos + 8 <= _nInv) { + _bBlinkingRight = false; + _items[28]._icon.setPattern(1); + } + + if (_curPos > 0) { + _bBlinkingLeft = true; + _items[29]._icon.setPattern(2); + } + + prepare(); + drawOT(Common::nullContext); + clearOT(); + g_system->unlockMutex(_csModifyInterface); + } else if ((_state == OPENED) && _bBlinkingLeft) { + assert(_curPos > 0); + g_system->lockMutex(_csModifyInterface); + _curPos -= 7; + if (_curPos < 0) + _curPos = 0; + + if (_curPos == 0) { + _bBlinkingLeft = false; + _items[29]._icon.setPattern(1); + } + + if (_curPos + 8 < _nInv) { + _bBlinkingRight = true; + _items[28]._icon.setPattern(2); + } + + prepare(); + drawOT(Common::nullContext); + clearOT(); + g_system->unlockMutex(_csModifyInterface); + } +} + +bool RMInventory::rightRelease(const RMPoint &mpos, RMTonyAction &curAction) { + if (_state == SELECTING) { + _state = OPENED; + + if (_miniAction == 1) { // Examine + curAction = TA_EXAMINE; + return true; + } else if (_miniAction == 2) { // Talk + curAction = TA_TALK; + return true; + } else if (_miniAction == 3) { // Use + curAction = TA_USE; + return true; + } + } + + return false; +} + +#define INVSPEED 20 + +void RMInventory::doFrame(RMGfxTargetBuffer &bigBuf, RMPointer &ptr, RMPoint mpos, bool bCanOpen) { + bool bNeedRedraw = false; + + if (_state != CLOSED) { + // Clean up the OT list + g_system->lockMutex(_csModifyInterface); + clearOT(); + + // DoFrame makes all the objects currently in the inventory be displayed + // @@@ Maybe we should do all takeable objects? Please does not help + for (int i = 0; i < _nInv; i++) { + if (_items[_inv[i]]._icon.doFrame(this, false) && (i >= _curPos && i <= _curPos + 7)) + bNeedRedraw = true; + } + + if ((_state == CLOSING || _state == OPENING || _state == OPENED) && checkPointInside(mpos)) { + if (mpos._x > RM_SX - 64) { + if (_curPos + 8 < _nInv && !_bBlinkingRight) { + _items[28]._icon.setPattern(3); + _bBlinkingRight = true; + bNeedRedraw = true; + } + } else if (_bBlinkingRight) { + _items[28]._icon.setPattern(2); + _bBlinkingRight = false; + bNeedRedraw = true; + } + + if (mpos._x < 64) { + if (_curPos > 0 && !_bBlinkingLeft) { + _items[29]._icon.setPattern(3); + _bBlinkingLeft = true; + bNeedRedraw = true; + } + } else if (_bBlinkingLeft) { + _items[29]._icon.setPattern(2); + _bBlinkingLeft = false; + bNeedRedraw = true; + } + } + + if (_items[28]._icon.doFrame(this, false)) + bNeedRedraw = true; + + if (_items[29]._icon.doFrame(this, false)) + bNeedRedraw = true; + + if (bNeedRedraw) + prepare(); + + g_system->unlockMutex(_csModifyInterface); + } + + if (g_vm->getEngine()->getInput().getAsyncKeyState(Common::KEYCODE_i)) { + GLOBALS._bCfgInvLocked = !GLOBALS._bCfgInvLocked; + } + + if (_bCombining) {//m_state == COMBINING) + ptr.setCustomPointer(&_items[_nCombine]._pointer[_items[_nCombine]._status - 1]); + ptr.setSpecialPointer(RMPointer::PTR_CUSTOM); + } + + if (!GLOBALS._bCfgInvUp) { + if ((_state == CLOSED) && (mpos._y > RM_SY - 10 || GLOBALS._bCfgInvLocked) && bCanOpen) { + if (!GLOBALS._bCfgInvNoScroll) { + _state = OPENING; + _curPutY = RM_SY - 1; + _curPutTime = g_vm->getTime(); + } else { + _state = OPENED; + _curPutY = RM_SY - 68; + } + } else if (_state == OPENED) { + if ((mpos._y < RM_SY - 70 && !GLOBALS._bCfgInvLocked) || !bCanOpen) { + if (!GLOBALS._bCfgInvNoScroll) { + _state = CLOSING; + _curPutY = RM_SY - 68; + _curPutTime = g_vm->getTime(); + } else { + _state = CLOSED; + } + } + } else if (_state == OPENING) { + while (_curPutTime + INVSPEED < g_vm->getTime()) { + _curPutY -= 3; + _curPutTime += INVSPEED; + } + + if (_curPutY <= RM_SY - 68) { + _state = OPENED; + _curPutY = RM_SY - 68; + } + + } else if (_state == CLOSING) { + while (_curPutTime + INVSPEED < g_vm->getTime()) { + _curPutY += 3; + _curPutTime += INVSPEED; + } + + if (_curPutY > 480) + _state = CLOSED; + } + } else { + if ((_state == CLOSED) && (mpos._y < 10 || GLOBALS._bCfgInvLocked) && bCanOpen) { + if (!GLOBALS._bCfgInvNoScroll) { + _state = OPENING; + _curPutY = - 68; + _curPutTime = g_vm->getTime(); + } else { + _state = OPENED; + _curPutY = 0; + } + } else if (_state == OPENED) { + if ((mpos._y > 70 && !GLOBALS._bCfgInvLocked) || !bCanOpen) { + if (!GLOBALS._bCfgInvNoScroll) { + _state = CLOSING; + _curPutY = -2; + _curPutTime = g_vm->getTime(); + } else { + _state = CLOSED; + } + } + } else if (_state == OPENING) { + while (_curPutTime + INVSPEED < g_vm->getTime()) { + _curPutY += 3; + _curPutTime += INVSPEED; + } + + if (_curPutY >= 0) { + _state = OPENED; + _curPutY = 0; + } + } else if (_state == CLOSING) { + while (_curPutTime + INVSPEED < g_vm->getTime()) { + _curPutY -= 3; + _curPutTime += INVSPEED; + } + + if (_curPutY < -68) + _state = CLOSED; + } + } + + if (_state == SELECTING) { + int startx = (_nSelectObj + 1) * 64 - 20; + int starty; + + if (!GLOBALS._bCfgInvUp) + starty = RM_SY - 109; + else + starty = 70; + + // Make sure it is on one of the verbs + if (mpos._y > starty && mpos._y < starty + 45) { + if (mpos._x > startx && mpos._x < startx + 40) { + if (_miniAction != 1) { + _miniInterface.setPattern(2); + _miniAction = 1; + g_vm->playUtilSFX(1); + } + } else if (mpos._x >= startx + 40 && mpos._x < startx + 80) { + if (_miniAction != 2) { + _miniInterface.setPattern(3); + _miniAction = 2; + g_vm->playUtilSFX(1); + } + } else if (mpos._x >= startx + 80 && mpos._x < startx + 108) { + if (_miniAction != 3) { + _miniInterface.setPattern(4); + _miniAction = 3; + g_vm->playUtilSFX(1); + } + } else { + _miniInterface.setPattern(1); + _miniAction = 0; + } + } else { + _miniInterface.setPattern(1); + _miniAction = 0; + } + + // Update the mini-interface + _miniInterface.doFrame(&bigBuf, false); + } + + if ((_state != CLOSED) && !_nInList) { + bigBuf.addPrim(new RMGfxPrimitive(this)); + } +} + +bool RMInventory::itemInFocus(const RMPoint &mpt) { + if ((_state == OPENED || _state == OPENING) && checkPointInside(mpt)) + return true; + else + return false; +} + +RMItem *RMInventory::whichItemIsIn(const RMPoint &mpt) { + if (_state == OPENED) { + if (checkPointInside(mpt)) { + int n = mpt._x / 64; + if (n > 0 && n < RM_SX / 64 - 1 && _inv[n - 1 + _curPos] != 0 && (!_bCombining || _inv[n - 1 + _curPos] != _nCombine)) + return &_items[_inv[n - 1 + _curPos]]._icon; + } + } + + return NULL; +} + +int RMInventory::getSaveStateSize() { + // m_inv pattern m_nInv + return 256 * 4 + 256 * 4 + 4; +} + +void RMInventory::saveState(byte *state) { + WRITE_LE_UINT32(state, _nInv); + state += 4; + Common::copy(_inv, _inv + 256, (uint32 *)state); + state += 256 * 4; + + int x; + for (int i = 0; i < 256; i++) { + if (i < _nItems) + x = _items[i]._status; + else + x = 0; + + WRITE_LE_UINT32(state, x); + state += 4; + } +} + +int RMInventory::loadState(byte *state) { + _nInv = READ_LE_UINT32(state); + state += 4; + Common::copy((uint32 *)state, (uint32 *)state + 256, _inv); + state += 256 * 4; + + int x; + for (int i = 0; i < 256; i++) { + x = READ_LE_UINT32(state); + state += 4; + + if (i < _nItems) { + _items[i]._status = x; + _items[i]._icon.setPattern(x); + } + } + + _curPos = 0; + _bCombining = false; + + _items[29]._icon.setPattern(1); + + if (_nInv > 8) + _items[28]._icon.setPattern(2); + else + _items[28]._icon.setPattern(1); + + prepare(); + drawOT(Common::nullContext); + clearOT(); + + return getSaveStateSize(); +} + +RMInventory &RMInventory::operator+=(RMItem *item) { + addItem(item->mpalCode()); + return *this; +} + +RMInventory &RMInventory::operator+=(RMItem &item) { + addItem(item.mpalCode()); + return *this; +} + +RMInventory &RMInventory::operator+=(int code) { + addItem(code); + return *this; +} + +/****************************************************************************\ +* RMInterface methods +\****************************************************************************/ + +RMInterface::RMInterface() : RMGfxSourceBuffer8RLEByte() { + _bActive = _bPerorate = false; + _lastHotZone = -1; +} + +RMInterface::~RMInterface() { +} + +bool RMInterface::active() { + return _bActive; +} + +int RMInterface::onWhichBox(RMPoint pt) { + pt -= _openStart; + + // Check how many verbs you have to consider + int max = 4; + if (_bPerorate) + max = 5; + + // Find the verb + for (int i = 0; i < max; i++) { + if (_hotbbox[i].ptInRect(pt)) + return i; + } + + // Found no verb + return -1; +} + +void RMInterface::draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim) { + CORO_BEGIN_CONTEXT; + int h; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + prim->getDst().topLeft() = _openStart; + CORO_INVOKE_2(RMGfxSourceBuffer8RLEByte::draw, bigBuf, prim); + + // Check if there is a draw hot zone + _ctx->h = onWhichBox(_mpos); + if (_ctx->h != -1) { + prim->getDst().topLeft() = _openStart; + CORO_INVOKE_2(_hotzone[_ctx->h].draw, bigBuf, prim); + + if (_lastHotZone != _ctx->h) { + _lastHotZone = _ctx->h; + g_vm->playUtilSFX(1); + } + + if (GLOBALS._bCfgInterTips) { + prim->getDst().topLeft() = _openStart + RMPoint(70, 177); + CORO_INVOKE_2(_hints[_ctx->h].draw, bigBuf, prim); + } + } else + _lastHotZone = -1; + + CORO_END_CODE; +} + +void RMInterface::doFrame(RMGfxTargetBuffer &bigBuf, RMPoint mousepos) { + // If needed, add to the OT schedule list + if (!_nInList && _bActive) + bigBuf.addPrim(new RMGfxPrimitive(this)); + + _mpos = mousepos; +} + +void RMInterface::clicked(const RMPoint &mousepos) { + _bActive = true; + _openPos = mousepos; + + // Calculate the top left corner of the interface + _openStart = _openPos - RMPoint(_dimx / 2, _dimy / 2); + _lastHotZone = -1; + + // Keep it inside the screen + if (_openStart._x < 0) + _openStart._x = 0; + if (_openStart._y < 0) + _openStart._y = 0; + if (_openStart._x + _dimx > RM_SX) + _openStart._x = RM_SX - _dimx; + if (_openStart._y + _dimy > RM_SY) + _openStart._y = RM_SY - _dimy; + + // Play the sound effect + g_vm->playUtilSFX(0); +} + +bool RMInterface::released(const RMPoint &mousepos, RMTonyAction &action) { + if (!_bActive) + return false; + + _bActive = false; + + switch (onWhichBox(mousepos)) { + case 0: + action = TA_TAKE; + break; + + case 1: + action = TA_TALK; + break; + + case 2: + action = TA_USE; + break; + + case 3: + action = TA_EXAMINE; + break; + + case 4: + action = TA_PERORATE; + break; + + default: // No verb + return false; + } + + return true; +} + +void RMInterface::reset() { + _bActive = false; +} + +void RMInterface::setPerorate(bool bOn) { + _bPerorate = bOn; +} + +bool RMInterface::getPerorate() { + return _bPerorate; +} + +void RMInterface::init() { + RMResRaw inter(RES_I_INTERFACE); + RMRes pal(RES_I_INTERPPAL); + + setPriority(191); + + RMGfxSourceBuffer::init(inter, inter.width(), inter.height()); + loadPaletteWA(RES_I_INTERPAL); + + for (int i = 0; i < 5; i++) { + RMResRaw part(RES_I_INTERP1 + i); + + _hotzone[i].init(part, part.width(), part.height()); + _hotzone[i].loadPaletteWA(pal); + } + + _hotbbox[0].setRect(126, 123, 159, 208); // Take + _hotbbox[1].setRect(90, 130, 125, 186); // About + _hotbbox[2].setRect(110, 60, 152, 125); + _hotbbox[3].setRect(56, 51, 93, 99); + _hotbbox[4].setRect(51, 105, 82, 172); + + _hints[0].setAlignType(RMText::HRIGHT, RMText::VTOP); + _hints[1].setAlignType(RMText::HRIGHT, RMText::VTOP); + _hints[2].setAlignType(RMText::HRIGHT, RMText::VTOP); + _hints[3].setAlignType(RMText::HRIGHT, RMText::VTOP); + _hints[4].setAlignType(RMText::HRIGHT, RMText::VTOP); + + // The text is taken from MPAL for translation + RMMessage msg0(12); + RMMessage msg1(13); + RMMessage msg2(14); + RMMessage msg3(15); + RMMessage msg4(16); + + _hints[0].writeText(msg0[0], 1); // Take + _hints[1].writeText(msg1[0], 1); // Talk + _hints[2].writeText(msg2[0], 1); // Use + _hints[3].writeText(msg3[0], 1); // Examine + _hints[4].writeText(msg4[0], 1); // Show Yourself + + _bActive = false; + _bPerorate = false; + _lastHotZone = 0; +} + +void RMInterface::close() { + destroy(); + + for (int i = 0; i < 5; i++) + _hotzone[i].destroy(); +} + +} // End of namespace Tony diff --git a/engines/tony/inventory.h b/engines/tony/inventory.h new file mode 100644 index 0000000000..ce94c86c1b --- /dev/null +++ b/engines/tony/inventory.h @@ -0,0 +1,241 @@ +/* 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 Tony Tough source code + * + * Copyright (c) 1997-2003 Nayma Software + */ + +#ifndef TONY_INVENTORY_H +#define TONY_INVENTORY_H + +#include "common/scummsys.h" +#include "common/system.h" +#include "tony/font.h" +#include "tony/game.h" +#include "tony/gfxcore.h" +#include "tony/loc.h" + +namespace Tony { + +struct RMInventoryItem { + RMItem _icon; + RMGfxSourceBuffer8RLEByteAA *_pointer; + int _status; +}; + +class RMInventory : public RMGfxWoodyBuffer { +private: + enum InventoryState { + CLOSED, + OPENING, + OPENED, + CLOSING, + SELECTING + }; + +protected: + int _nItems; + RMInventoryItem *_items; + + int _inv[256]; + int _nInv; + int _curPutY; + uint32 _curPutTime; + + int _curPos; + InventoryState _state; + bool _bHasFocus; + int _nSelectObj; + int _nCombine; + bool _bCombining; + + bool _bBlinkingRight, _bBlinkingLeft; + + int _miniAction; + RMItem _miniInterface; + RMText _hints[3]; + + OSystem::MutexRef _csModifyInterface; + +protected: + /** + * Prepare the image inventory. It should be recalled whenever the inventory changes + */ + void prepare(); + + /** + * Check if the mouse Y position is conrrect, even under the inventory portion of the screen + */ + bool checkPointInside(const RMPoint &pt); + +public: + RMInventory(); + virtual ~RMInventory(); + + /** + * Prepare a frame + */ + void doFrame(RMGfxTargetBuffer &bigBuf, RMPointer &ptr, RMPoint mpos, bool bCanOpen); + + /** + * Initialization and closing + */ + void init(); + void close(); + void reset(); + + /** + * Overload test for removal from OT list + */ + virtual void removeThis(CORO_PARAM, bool &result); + + /** + * Overload the drawing of the inventory + */ + virtual void draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim); + + /** + * Method for determining whether the inventory currently has the focus + */ + bool haveFocus(const RMPoint &mpos); + + /** + * Method for determining if the mini interface is active + */ + bool miniActive(); + + /** + * Handle the left mouse click (only when the inventory has the focus) + */ + bool leftClick(const RMPoint &mpos, int &nCombineObj); + + /** + * Handle the right mouse button (only when the inventory has the focus) + */ + void rightClick(const RMPoint &mpos); + bool rightRelease(const RMPoint &mpos, RMTonyAction &curAction); + + /** + * Warn that an item combine is over + */ + void endCombine(); + +public: + /** + * Add an item to the inventory + */ + void addItem(int code); + RMInventory &operator+=(RMItem *item); + RMInventory &operator+=(RMItem &item); + RMInventory &operator+=(int code); + + /** + * Removes an item + */ + void removeItem(int code); + + /** + * We are on an object? + */ + RMItem *whichItemIsIn(const RMPoint &mpt); + bool itemInFocus(const RMPoint &mpt); + + /** + * Change the icon of an item + */ + void changeItemStatus(uint32 dwCode, uint32 dwStatus); + + /** + * Save methods + */ + int getSaveStateSize(); + void saveState(byte *state); + int loadState(byte *state); +}; + + +class RMInterface : public RMGfxSourceBuffer8RLEByte { +private: + bool _bActive; + RMPoint _mpos; + RMPoint _openPos; + RMPoint _openStart; + RMText _hints[5]; + RMGfxSourceBuffer8RLEByte _hotzone[5]; + RMRect _hotbbox[5]; + bool _bPerorate; + int _lastHotZone; + +protected: + /** + * Return which box a given point is in + */ + int onWhichBox(RMPoint pt); + +public: + RMInterface(); + virtual ~RMInterface(); + + /** + * The usual DoFrame (poll the graphics engine) + */ + void doFrame(RMGfxTargetBuffer &bigBuf, RMPoint mousepos); + + /** + * TRUE if it is active (you can select items) + */ + bool active(); + + /** + * Initialization + */ + void init(); + void close(); + + /** + * Reset the interface + */ + void reset(); + + /** + * Warns of mouse clicks and releases + */ + void clicked(const RMPoint &mousepos); + bool released(const RMPoint &mousepos, RMTonyAction &action); + + /** + * Enables or disables the fifth verb + */ + void setPerorate(bool bOn); + bool getPerorate(); + + /** + * Overloaded Draw + */ + virtual void draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim); +}; + +} // End of namespace Tony + +#endif diff --git a/engines/tony/loc.cpp b/engines/tony/loc.cpp new file mode 100644 index 0000000000..18470aa6fc --- /dev/null +++ b/engines/tony/loc.cpp @@ -0,0 +1,2280 @@ +/* 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 Tony Tough source code + * + * Copyright (c) 1997-2003 Nayma Software + */ + +#include "common/memstream.h" +#include "common/scummsys.h" +#include "tony/mpal/mpalutils.h" +#include "tony/game.h" +#include "tony/loc.h" +#include "tony/tony.h" + +namespace Tony { + +using namespace ::Tony::MPAL; + + +/****************************************************************************\ +* RMPalette Methods +\****************************************************************************/ + +void RMPalette::readFromStream(Common::ReadStream &ds) { + ds.read(_data, 1024); +} + +/****************************************************************************\ +* RMSlot Methods +\****************************************************************************/ + +void RMPattern::RMSlot::readFromStream(Common::ReadStream &ds, bool bLOX) { + // Type + byte type = ds.readByte(); + _type = (RMPattern::RMSlotType)type; + + // Data + _data = ds.readSint32LE(); + + // Position + _pos.readFromStream(ds); + + // Generic flag + _flag = ds.readByte(); +} + + +/****************************************************************************\ +* RMPattern Methods +\****************************************************************************/ + +void RMPattern::readFromStream(Common::ReadStream &ds, bool bLOX) { + // Pattern name + if (!bLOX) + _name = readString(ds); + + // Velocity + _speed = ds.readSint32LE(); + + // Position + _pos.readFromStream(ds); + + // Flag for pattern looping + _bLoop = ds.readSint32LE(); + + // Number of slots + _nSlots = ds.readSint32LE(); + + // Create and read the slots + _slots = new RMSlot[_nSlots]; + + for (int i = 0; i < _nSlots && !ds.err(); i++) { + if (bLOX) + _slots[i].readFromStream(ds, true); + else + _slots[i].readFromStream(ds, false); + } +} + +void RMPattern::updateCoord() { + _curPos = _pos + _slots[_nCurSlot].pos(); +} + +void RMPattern::stopSfx(RMSfx *sfx) { + for (int i = 0; i < _nSlots; i++) { + if (_slots[i]._type == SOUND) { + if (!sfx[_slots[i]._data]._name.empty() && sfx[_slots[i]._data]._name[0] == '_') + sfx[_slots[i]._data].stop(); + else if (GLOBALS._bSkipSfxNoLoop) + sfx[_slots[i]._data].stop(); + } + } +} + +int RMPattern::init(RMSfx *sfx, bool bPlayP0, byte *bFlag) { + // Read the current time + _nStartTime = g_vm->getTime(); + _nCurSlot = 0; + + // Find the first frame of the pattern + int i = 0; + while (_slots[i]._type != SPRITE) { + assert(i + 1 < _nSlots); + i++; + } + + _nCurSlot = i; + _nCurSprite = _slots[i]._data; + if (bFlag) + *bFlag = _slots[i]._flag; + + // Calculate the current coordinates + updateCoord(); + + // Check for sound: + // If the slot is 0, play + // If speed == 0, must play unless it goes into loop '_', or if specified by the parameter + // If speed != 0, play only the loop + for (i = 0; i < _nSlots; i++) { + if (_slots[i]._type == SOUND) { + if (i == 0) { + if (!sfx[_slots[i]._data]._name.empty() && sfx[_slots[i]._data]._name[0] == '_') { + sfx[_slots[i]._data].setVolume(_slots[i].pos()._x); + sfx[_slots[i]._data].play(true); + } else { + sfx[_slots[i]._data].setVolume(_slots[i].pos()._x); + sfx[_slots[i]._data].play(); + } + } else if (_speed == 0) { + if (bPlayP0) { + sfx[_slots[i]._data].setVolume(_slots[i].pos()._x); + sfx[_slots[i]._data].play(); + } else if (!sfx[_slots[i]._data]._name.empty() && sfx[_slots[i]._data]._name[0] == '_') { + sfx[_slots[i]._data].setVolume(_slots[i].pos()._x); + sfx[_slots[i]._data].play(true); + } + } else { + if (_bLoop && !sfx[_slots[i]._data]._name.empty() && sfx[_slots[i]._data]._name[0] == '_') { + sfx[_slots[i]._data].setVolume(_slots[i].pos()._x); + sfx[_slots[i]._data].play(true); + } + } + } + } + + return _nCurSprite; +} + +int RMPattern::update(uint32 hEndPattern, byte &bFlag, RMSfx *sfx) { + int CurTime = g_vm->getTime(); + + // If the speed is 0, then the pattern never advances + if (_speed == 0) { + CoroScheduler.pulseEvent(hEndPattern); + bFlag = _slots[_nCurSlot]._flag; + return _nCurSprite; + } + + // Is it time to change the slots? + while (_nStartTime + _speed <= (uint32)CurTime) { + _nStartTime += _speed; + if (_slots[_nCurSlot]._type == SPRITE) + _nCurSlot++; + if (_nCurSlot == _nSlots) { + _nCurSlot = 0; + bFlag = _slots[_nCurSlot]._flag; + + CoroScheduler.pulseEvent(hEndPattern); + + // @@@ If there is no loop pattern, and there's a warning that it's the final + // frame, then remain on the last frame + if (!_bLoop) { + _nCurSlot = _nSlots - 1; + bFlag = _slots[_nCurSlot]._flag; + return _nCurSprite; + } + } + + for (;;) { + switch (_slots[_nCurSlot]._type) { + case SPRITE: + // Read the next sprite + _nCurSprite = _slots[_nCurSlot]._data; + + // Update the parent & child coordinates + updateCoord(); + break; + + case SOUND: + if (sfx != NULL) { + sfx[_slots[_nCurSlot]._data].setVolume(_slots[_nCurSlot].pos()._x); + + if (sfx[_slots[_nCurSlot]._data]._name.empty() || sfx[_slots[_nCurSlot]._data]._name[0] != '_') + sfx[_slots[_nCurSlot]._data].play(false); + else + sfx[_slots[_nCurSlot]._data].play(true); + } + break; + + case COMMAND: + assert(0); + break; + + default: + assert(0); + break; + } + + if (_slots[_nCurSlot]._type == SPRITE) + break; + _nCurSlot++; + } + } + + // Return the current sprite + bFlag = _slots[_nCurSlot]._flag; + return _nCurSprite; +} + +RMPattern::RMPattern() { + _slots = NULL; + _speed = 0; + _bLoop = 0; + _nSlots = 0; + _nCurSlot = 0; + _nCurSprite = 0; + _nStartTime = 0; + _slots = NULL; +} + +/** + * Reads the position of the pattern + */ +RMPoint RMPattern::pos() { + return _curPos; +} + +RMPattern::~RMPattern() { + if (_slots != NULL) { + delete[] _slots; + _slots = NULL; + } +} + +/****************************************************************************\ +* RMSprite Methods +\****************************************************************************/ + +void RMSprite::init(RMGfxSourceBuffer *buf) { + _buf = buf; +} + +void RMSprite::LOXGetSizeFromStream(Common::SeekableReadStream &ds, int *dimx, int *dimy) { + uint32 pos = ds.pos(); + + *dimx = ds.readSint32LE(); + *dimy = ds.readSint32LE(); + + ds.seek(pos); +} + +void RMSprite::getSizeFromStream(Common::SeekableReadStream &ds, int *dimx, int *dimy) { + uint32 pos = ds.pos(); + + _name = readString(ds); + *dimx = ds.readSint32LE(); + *dimy = ds.readSint32LE(); + + ds.seek(pos); +} + +void RMSprite::readFromStream(Common::SeekableReadStream &ds, bool bLOX) { + // Sprite name + if (!bLOX) + _name = readString(ds); + + // Dimensions + int dimx = ds.readSint32LE(); + int dimy = ds.readSint32LE(); + + // Bounding box + _rcBox.readFromStream(ds); + + // Unused space + if (!bLOX) + ds.skip(32); + + // Create buffer and read + _buf->init(ds, dimx, dimy); +} + +void RMSprite::draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim) { + _buf->draw(coroParam, bigBuf, prim); +} + +void RMSprite::setPalette(byte *buf) { + ((RMGfxSourceBufferPal *)_buf)->loadPalette(buf); +} + +RMSprite::RMSprite() { + _buf = NULL; +} + +RMSprite::~RMSprite() { + if (_buf) { + delete _buf; + _buf = NULL; + } +} + + +/****************************************************************************\ +* RMSfx Methods +\****************************************************************************/ + +void RMSfx::readFromStream(Common::ReadStream &ds, bool bLOX) { + // sfx name + _name = readString(ds); + + int size = ds.readSint32LE(); + + // Read the entire buffer into a MemoryReadStream + byte *buffer = (byte *)malloc(size); + ds.read(buffer, size); + Common::SeekableReadStream *stream = new Common::MemoryReadStream(buffer, size, DisposeAfterUse::YES); + + // Create the sound effect + _fx = g_vm->createSFX(stream); + _fx->setLoop(false); +} + +RMSfx::RMSfx() { + _fx = NULL; + _bPlayingLoop = false; +} + +RMSfx::~RMSfx() { + if (_fx) { + _fx->release(); + _fx = NULL; + } +} + +void RMSfx::play(bool bLoop) { + if (_fx && !_bPlayingLoop) { + _fx->setLoop(bLoop); + _fx->play(); + + if (bLoop) + _bPlayingLoop = true; + } +} + +void RMSfx::setVolume(int vol) { + if (_fx) { + _fx->setVolume(vol); + } +} + +void RMSfx::pause(bool bPause) { + if (_fx) { + _fx->setPause(bPause); + } +} + +void RMSfx::stop() { + if (_fx) { + _fx->stop(); + _bPlayingLoop = false; + } +} + + + +/****************************************************************************\ +* RMItem Methods +\****************************************************************************/ + +int RMItem::getCurPattern() { + return _nCurPattern; +} + +RMGfxSourceBuffer *RMItem::newItemSpriteBuffer(int dimx, int dimy, bool bPreRLE) { + if (_cm == CM_256) { + RMGfxSourceBuffer8RLE *spr; + + if (_FX == 2) { // AB + spr = new RMGfxSourceBuffer8RLEWordAB; + } else if (_FX == 1) { // OMBRA+AA + if (dimx == -1 || dimx > 255) + spr = new RMGfxSourceBuffer8RLEWordAA; + else + spr = new RMGfxSourceBuffer8RLEByteAA; + + spr->setAlphaBlendColor(_FXparm); + if (bPreRLE) + spr->setAlreadyCompressed(); + } else { + if (dimx == -1 || dimx > 255) + spr = new RMGfxSourceBuffer8RLEWord; + else + spr = new RMGfxSourceBuffer8RLEByte; + + if (bPreRLE) + spr->setAlreadyCompressed(); + } + + return spr; + } else + return new RMGfxSourceBuffer16; +} + +bool RMItem::isIn(const RMPoint &pt, int *size) { + RMRect rc; + + if (!_bIsActive) + return false; + + // Search for the right bounding box to use - use the sprite's if it has one, otherwise use the generic one + if (_nCurPattern != 0 && !_sprites[_nCurSprite]._rcBox.isEmpty()) + rc = _sprites[_nCurSprite]._rcBox + calculatePos(); + else if (!_rcBox.isEmpty()) + rc = _rcBox; + // If no box, return immediately + else + return false; + + if (size != NULL) + *size = rc.size(); + + return rc.ptInRect(pt + _curScroll); +} + +void RMItem::readFromStream(Common::SeekableReadStream &ds, bool bLOX) { + // MPAL code + _mpalCode = ds.readSint32LE(); + + // Object name + _name = readString(ds); + + // Z (signed) + _z = ds.readSint32LE(); + + // Parent position + _pos.readFromStream(ds); + + // Hotspot + _hot.readFromStream(ds); + + // Bounding box + _rcBox.readFromStream(ds); + + // Number of sprites, sound effects, and patterns + _nSprites = ds.readSint32LE(); + _nSfx = ds.readSint32LE(); + _nPatterns = ds.readSint32LE(); + + // Color mode + byte cm = ds.readByte(); + _cm = (RMColorMode)cm; + + // Flag for the presence of custom palette differences + _bPal = ds.readByte(); + + if (_cm == CM_256) { + // If there is a palette, read it in + if (_bPal) + _pal.readFromStream(ds); + } + + // MPAL data + if (!bLOX) + ds.skip(20); + + _FX = ds.readByte(); + _FXparm = ds.readByte(); + + if (!bLOX) + ds.skip(106); + + // Create sub-classes + if (_nSprites > 0) + _sprites = new RMSprite[_nSprites]; + if (_nSfx > 0) + _sfx = new RMSfx[_nSfx]; + _patterns = new RMPattern[_nPatterns + 1]; + + int dimx, dimy; + // Read in class data + if (!ds.err()) { + for (int i = 0; i < _nSprites && !ds.err(); i++) { + // Download the sprites + if (bLOX) { + _sprites[i].LOXGetSizeFromStream(ds, &dimx, &dimy); + _sprites[i].init(newItemSpriteBuffer(dimx, dimy, true)); + _sprites[i].readFromStream(ds, true); + } else { + _sprites[i].getSizeFromStream(ds, &dimx, &dimy); + _sprites[i].init(newItemSpriteBuffer(dimx, dimy, false)); + _sprites[i].readFromStream(ds, false); + } + + if (_cm == CM_256 && _bPal) + _sprites[i].setPalette(_pal._data); + } + } + + if (!ds.err()) { + for (int i = 0; i < _nSfx && !ds.err(); i++) { + if (bLOX) + _sfx[i].readFromStream(ds, true); + else + _sfx[i].readFromStream(ds, false); + } + } + + // Read the pattern from pattern 1 + if (!ds.err()) { + for (int i = 1; i <= _nPatterns && !ds.err(); i++) { + if (bLOX) + _patterns[i].readFromStream(ds, true); + else + _patterns[i].readFromStream(ds, false); + } + } + + // Initialize the current pattern + if (_bInitCurPattern) + setPattern(mpalQueryItemPattern(_mpalCode)); + + // Initialize the current activation state + _bIsActive = mpalQueryItemIsActive(_mpalCode); +} + + +RMGfxPrimitive *RMItem::newItemPrimitive() { + return new RMGfxPrimitive(this); +} + +void RMItem::setScrollPosition(const RMPoint &scroll) { + _curScroll = scroll; +} + +bool RMItem::doFrame(RMGfxTargetBuffer *bigBuf, bool bAddToList) { + int oldSprite = _nCurSprite; + + // Pattern 0 = Do not draw anything! + if (_nCurPattern == 0) + return false; + + // We do an update of the pattern, which also returns the current frame + if (_nCurPattern != 0) { + _nCurSprite = _patterns[_nCurPattern].update(_hEndPattern, _bCurFlag, _sfx); + + // WORKAROUND: Currently, m_nCurSprite = -1 is used to flag that an item should be removed. + // However, this seems to be done inside a process waiting on an event pulsed inside the pattern + // Update method. So the value of m_nCurSprite = -1 is being destroyed with the return value + // replacing it. It may be that the current coroutine PulseEvent implementation is wrong somehow. + // In any case, a special check here is done for items that have ended + if (_nCurPattern == 0) + _nCurSprite = -1; + } + + // If the function returned -1, it means that the pattern has finished + if (_nCurSprite == -1) { + // We have pattern 0, so leave. The class will self de-register from the OT list + _nCurPattern = 0; + return false; + } + + // If we are not in the OT list, add ourselves + if (!_nInList && bAddToList) + bigBuf->addPrim(newItemPrimitive()); + + return oldSprite != _nCurSprite; +} + +RMPoint RMItem::calculatePos() { + return _pos + _patterns[_nCurPattern].pos(); +} + +void RMItem::draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + // If CurSprite == -1, then the pattern is finished + if (_nCurSprite == -1) + return; + + // Set the flag + prim->setFlag(_bCurFlag); + + // Offset direction for scrolling + prim->getDst().offset(-_curScroll); + + // We must offset the cordinates of the item inside the primitive + // It is estimated as nonno + (babbo + figlio) + prim->getDst().offset(calculatePos()); + + // No stretching, please + prim->setStretch(false); + + // Now we turn to the generic surface drawing routines + CORO_INVOKE_2(_sprites[_nCurSprite].draw, bigBuf, prim); + + CORO_END_CODE; +} + +/** + * Overloaded priority: it's based on Z ordering + */ +int RMItem::priority() { + return _z; +} + +/** + * Pattern number + */ +int RMItem::numPattern() { + return _nPatterns; +} + +void RMItem::removeThis(CORO_PARAM, bool &result) { + // Remove from the OT list if the current frame is -1 (pattern over) + result = (_nCurSprite == -1); +} + + +void RMItem::setStatus(int nStatus) { + _bIsActive = (nStatus > 0); +} + +RMPoint RMItem::getHotspot() { + return _hot; +} + +int RMItem::mpalCode() { + return _mpalCode; +} + +void RMItem::setPattern(int nPattern, bool bPlayP0) { + assert(nPattern >= 0 && nPattern <= _nPatterns); + + if (_sfx) { + if (_nCurPattern > 0) + _patterns[_nCurPattern].stopSfx(_sfx); + } + + // Remember the current pattern + _nCurPattern = nPattern; + + // Start the pattern to start the animation + if (_nCurPattern != 0) + _nCurSprite = _patterns[_nCurPattern].init(_sfx, bPlayP0, &_bCurFlag); + else { + _nCurSprite = -1; + + // Look for the sound effect for pattern 0 + if (bPlayP0) { + for (int i = 0; i < _nSfx; i++) { + if (_sfx[i]._name == "p0") + _sfx[i].play(); + } + } + } +} + +bool RMItem::getName(Common::String &name) { + char buf[256]; + + mpalQueryItemName(_mpalCode, buf); + name = buf; + if (buf[0] == '\0') + return false; + return true; +} + +void RMItem::unload() { + if (_patterns != NULL) { + delete[] _patterns; + _patterns = NULL; + } + + if (_sprites != NULL) { + delete[] _sprites; + _sprites = NULL; + } + + if (_sfx != NULL) { + delete[] _sfx; + _sfx = NULL; + } +} + +RMItem::RMItem() { + _bCurFlag = 0; + _patterns = NULL; + _sprites = NULL; + _sfx = NULL; + _curScroll.set(0, 0); + _bInitCurPattern = true; + _nCurPattern = 0; + _z = 0; + _cm = CM_256; + _FX = 0; + _FXparm = 0; + _mpalCode = 0; + _nSprites = 0; + _nSfx = 0; + _nPatterns = 0; + _bPal = 0; + _nCurSprite = 0; + + _bIsActive = false; + memset(_pal._data, 0, sizeof(_pal._data)); + + _hEndPattern = CoroScheduler.createEvent(false, false); +} + +RMItem::~RMItem() { + unload(); + CoroScheduler.closeEvent(_hEndPattern); +} + + +void RMItem::waitForEndPattern(CORO_PARAM, uint32 hCustomSkip) { + CORO_BEGIN_CONTEXT; + uint32 h[2]; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + if (_nCurPattern != 0) { + if (hCustomSkip == CORO_INVALID_PID_VALUE) + CORO_INVOKE_2(CoroScheduler.waitForSingleObject, _hEndPattern, CORO_INFINITE); + else { + _ctx->h[0] = hCustomSkip; + _ctx->h[1] = _hEndPattern; + CORO_INVOKE_4(CoroScheduler.waitForMultipleObjects, 2, &_ctx->h[0], false, CORO_INFINITE); + } + } + + CORO_END_CODE; +} + +void RMItem::changeHotspot(const RMPoint &pt) { + _hot = pt; +} + +void RMItem::setInitCurPattern(bool status) { + _bInitCurPattern = status; +} + +void RMItem::playSfx(int nSfx) { + if (nSfx < _nSfx) + _sfx[nSfx].play(); +} + +void RMItem::pauseSound(bool bPause) { + for (int i = 0; i < _nSfx; i++) + _sfx[i].pause(bPause); +} + + +/****************************************************************************\ +* RMWipe Methods +\****************************************************************************/ + + +RMWipe::RMWipe() { + _hUnregistered = CoroScheduler.createEvent(false, false); + _hEndOfFade = CoroScheduler.createEvent(false, false); + + _bMustRegister = false; + _bUnregister = false; + _bEndFade = false; + _bFading = false; + _nFadeStep = 0; + +} + +RMWipe::~RMWipe() { + CoroScheduler.closeEvent(_hUnregistered); + CoroScheduler.closeEvent(_hEndOfFade); +} + +int RMWipe::priority() { + return 200; +} + +void RMWipe::unregister() { + RMGfxTask::unregister(); + assert(_nInList == 0); + CoroScheduler.setEvent(_hUnregistered); +} + +void RMWipe::removeThis(CORO_PARAM, bool &result) { + result = _bUnregister; +} + +void RMWipe::waitForFadeEnd(CORO_PARAM) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + CORO_INVOKE_2(CoroScheduler.waitForSingleObject, _hEndOfFade, CORO_INFINITE); + + _bEndFade = true; + _bFading = false; + + CORO_INVOKE_2(CoroScheduler.waitForSingleObject, g_vm->_hEndOfFrame, CORO_INFINITE); + CORO_INVOKE_2(CoroScheduler.waitForSingleObject, g_vm->_hEndOfFrame, CORO_INFINITE); + + CORO_END_CODE; +} + +void RMWipe::closeFade() { + _wip0r.unload(); +} + +void RMWipe::initFade(int type) { + // Activate the fade + _bUnregister = false; + _bEndFade = false; + + _nFadeStep = 0; + + _bMustRegister = true; + + RMRes res(RES_W_CIRCLE); + Common::SeekableReadStream *ds = res.getReadStream(); + _wip0r.readFromStream(*ds); + delete ds; + + _wip0r.setPattern(1); + + _bFading = true; +} + +void RMWipe::doFrame(RMGfxTargetBuffer &bigBuf) { + if (_bMustRegister) { + bigBuf.addPrim(new RMGfxPrimitive(this)); + _bMustRegister = false; + } + + if (_bFading) { + _wip0r.doFrame(&bigBuf, false); + + _nFadeStep++; + + if (_nFadeStep == 10) { + CoroScheduler.setEvent(_hEndOfFade); + } + } +} + +void RMWipe::draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + if (_bFading) { + CORO_INVOKE_2(_wip0r.draw, bigBuf, prim); + } + + if (_bEndFade) + Common::fill((byte *)bigBuf, (byte *)bigBuf + bigBuf.getDimx() * bigBuf.getDimy() * 2, 0x0); + + CORO_END_CODE; +} + +/****************************************************************************\ +* RMCharacter Methods +\****************************************************************************/ + +/****************************************************************************/ +/* Find the shortest path between two nodes of the graph connecting the BOX */ +/* Returns path along the vector path path[] */ +/****************************************************************************/ + +short RMCharacter::findPath(short source, short destination) { + static RMBox box[MAXBOXES]; // Matrix of adjacent boxes + static short nodeCost[MAXBOXES]; // Cost per node + static short valid[MAXBOXES]; // 0:Invalid 1:Valid 2:Saturated + static short nextNode[MAXBOXES]; // Next node + short minCost, error = 0; + RMBoxLoc *cur; + + g_system->lockMutex(_csMove); + + if (source == -1 || destination == -1) { + g_system->unlockMutex(_csMove); + return 0; + } + + // Get the boxes + cur = _theBoxes->getBoxes(_curLocation); + + // Make a backup copy to work on + for (int i = 0; i < cur->_numbBox; i++) + memcpy(&box[i], &cur->_boxes[i], sizeof(RMBox)); + + // Invalidate all nodes + for (int i = 0; i < cur->_numbBox; i++) + valid[i] = 0; + + // Prepare source and variables for the procedure + nodeCost[source] = 0; + valid[source] = 1; + bool finish = false; + + // Find the shortest path + while (!finish) { + minCost = 32000; // Reset the minimum cost + error = 1; // Possible error + + // 1st cycle: explore possible new nodes + for (int i = 0; i < cur->_numbBox; i++) { + if (valid[i] == 1) { + error = 0; // Failure de-bunked + int j = 0; + while (((box[i]._adj[j]) != 1) && (j < cur->_numbBox)) + j++; + + if (j >= cur->_numbBox) + valid[i] = 2; // nodo saturated? + else { + nextNode[i] = j; + if (nodeCost[i] + 1 < minCost) + minCost = nodeCost[i] + 1; + } + } + } + + if (error) + finish = true; // All nodes saturated + + // 2nd cycle: adding new nodes that were found, saturate old nodes + for (int i = 0; i < cur->_numbBox; i++) { + if ((valid[i] == 1) && ((nodeCost[i] + 1) == minCost)) { + box[i]._adj[nextNode[i]] = 2; + nodeCost[nextNode[i]] = minCost; + valid[nextNode[i]] = 1; + for (int j = 0; j < cur->_numbBox; j++) + if (box[j]._adj[nextNode[i]] == 1) + box[j]._adj[nextNode[i]] = 0; + + if (nextNode[i] == destination) + finish = true; + } + } + } + + // Remove the path from the adjacent modified matrixes + if (!error) { + _pathLength = nodeCost[destination]; + short k = _pathLength; + _path[k] = destination; + + while (_path[k] != source) { + int i = 0; + while (box[i]._adj[_path[k]] != 2) + i++; + k--; + _path[k] = i; + } + + _pathLength++; + } + + g_system->unlockMutex(_csMove); + + return !error; +} + + +void RMCharacter::goTo(CORO_PARAM, RMPoint destcoord, bool bReversed) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + if (_pos == destcoord) { + if (_minPath == 0) { + CORO_INVOKE_0(stop); + CoroScheduler.pulseEvent(_hEndOfPath); + return; + } + } + + _status = WALK; + _lineStart = _pos; + _lineEnd = destcoord; + _dx = _lineStart._x - _lineEnd._x; + _dy = _lineStart._y - _lineEnd._y; + _fx = _dx; + _fy = _dy; + _dx = ABS(_dx); + _dy = ABS(_dy); + _walkSpeed = _curSpeed; + _walkCount = 0; + + if (bReversed) { + while (0) ; + } + + int nPatt = getCurPattern(); + + if (_dx > _dy) { + _slope = _fy / _fx; + if (_lineEnd._x < _lineStart._x) + _walkSpeed = -_walkSpeed; + _walkStatus = 1; + + // Change the pattern for the new direction + _bNeedToStop = true; + if ((_walkSpeed < 0 && !bReversed) || (_walkSpeed >= 0 && bReversed)) { + if (nPatt != PAT_WALKLEFT) + setPattern(PAT_WALKLEFT); + } else { + if (nPatt != PAT_WALKRIGHT) + setPattern(PAT_WALKRIGHT); + } + } else { + _slope = _fx / _fy; + if (_lineEnd._y < _lineStart._y) + _walkSpeed = -_walkSpeed; + _walkStatus = 0; + + _bNeedToStop = true; + if ((_walkSpeed < 0 && !bReversed) || (_walkSpeed >= 0 && bReversed)) { + if (nPatt != PAT_WALKUP) + setPattern(PAT_WALKUP); + } else { + if (nPatt != PAT_WALKDOWN) + setPattern(PAT_WALKDOWN); + } + } + + _olddx = _dx; + _olddy = _dy; + + CORO_END_CODE; +} + + +RMPoint RMCharacter::searching(char UP, char DOWN, char RIGHT, char LEFT, RMPoint point) { + short steps; + RMPoint newPt, foundPt; + short minStep = 32000; + + if (UP) { + newPt = point; + steps = 0; + while ((inWhichBox(newPt) == -1) && (newPt._y >= 0)) { + newPt._y--; + steps++; + } + if ((inWhichBox(newPt) != -1) && (steps < minStep) && + findPath(inWhichBox(_pos), inWhichBox(newPt))) { + minStep = steps; + newPt._y--; // to avoid error? + foundPt = newPt; + } + } + + if (DOWN) { + newPt = point; + steps = 0; + while ((inWhichBox(newPt) == -1) && (newPt._y < 480)) { + newPt._y++; + steps++; + } + if ((inWhichBox(newPt) != -1) && (steps < minStep) && + findPath(inWhichBox(_pos), inWhichBox(newPt))) { + minStep = steps; + newPt._y++; // to avoid error? + foundPt = newPt; + } + } + + if (RIGHT) { + newPt = point; + steps = 0; + while ((inWhichBox(newPt) == -1) && (newPt._x < 640)) { + newPt._x++; + steps++; + } + if ((inWhichBox(newPt) != -1) && (steps < minStep) && + findPath(inWhichBox(_pos), inWhichBox(newPt))) { + minStep = steps; + newPt._x++; // to avoid error? + foundPt = newPt; + } + } + + if (LEFT) { + newPt = point; + steps = 0; + while ((inWhichBox(newPt) == -1) && (newPt._x >= 0)) { + newPt._x--; + steps++; + } + if ((inWhichBox(newPt) != -1) && (steps < minStep) && + findPath(inWhichBox(_pos), inWhichBox(newPt))) { + minStep = steps; + newPt._x--; // to avoid error? + foundPt = newPt; + } + } + + if (minStep == 32000) + foundPt = point; + + return foundPt; +} + + +RMPoint RMCharacter::nearestPoint(const RMPoint &point) { + return searching(1, 1, 1, 1, point); +} + + +short RMCharacter::scanLine(const RMPoint &point) { + int Ldx, Ldy, Lcount; + float Lfx, Lfy, Lslope; + RMPoint Lstart, Lend, Lscan; + signed char Lspeed, Lstatus; + + Lstart = _pos; + Lend = point; + Ldx = Lstart._x - Lend._x; + Ldy = Lstart._y - Lend._y; + Lfx = Ldx; + Lfy = Ldy; + Ldx = ABS(Ldx); + Ldy = ABS(Ldy); + Lspeed = 1; + Lcount = 0; + + if (Ldx > Ldy) { + Lslope = Lfy / Lfx; + if (Lend._x < Lstart._x) + Lspeed = -Lspeed; + Lstatus = 1; + } else { + Lslope = Lfx / Lfy; + if (Lend._y < Lstart._y) + Lspeed = - Lspeed; + Lstatus = 0; + } + + Lscan = Lstart; // Start scanning + while (inWhichBox(Lscan) != -1) { + Lcount++; + if (Lstatus) { + Ldx = Lspeed * Lcount; + Ldy = (int)(Lslope * Ldx); + } else { + Ldy = Lspeed * Lcount; + Ldx = (int)(Lslope * Ldy); + } + + Lscan._x = Lstart._x + Ldx; + Lscan._y = Lstart._y + Ldy; + + if ((ABS(Lscan._x - Lend._x) <= 1) && (ABS(Lscan._y - Lend._y) <= 1)) + return 1; + } + + return 0; +} + +/** + * Calculates intersections between the straight line and the closest BBOX + */ +RMPoint RMCharacter::invScanLine(const RMPoint &point) { + RMPoint lStart = point; // Exchange! + RMPoint lEnd = _pos; // :-) + int lDx = lStart._x - lEnd._x; + int lDy = lStart._y - lEnd._y; + float lFx = lDx; + float lFy = lDy; + lDx = ABS(lDx); + lDy = ABS(lDy); + signed char lSpeed = 1; + int lCount = 0; + + signed char lStatus; + float lSlope; + + if (lDx > lDy) { + lSlope = lFy / lFx; + if (lEnd._x < lStart._x) + lSpeed = -lSpeed; + lStatus = 1; + } else { + lSlope = lFx / lFy; + if (lEnd._y < lStart._y) + lSpeed = -lSpeed; + lStatus = 0; + } + + RMPoint lScan = lStart; + signed char lBox = -1; + + for (;;) { + if (inWhichBox(lScan) != -1) { + if (inWhichBox(lScan) != lBox) { + if (inWhichBox(_pos) == inWhichBox(lScan) || findPath(inWhichBox(_pos), inWhichBox(lScan))) + return lScan; + else + lBox = inWhichBox(lScan); + } + } + + lCount++; + if (lStatus) { + lDx = lSpeed * lCount; + lDy = (int)(lSlope * lDx); + } else { + lDy = lSpeed * lCount; + lDx = (int)(lSlope * lDy); + } + lScan._x = lStart._x + lDx; + lScan._y = lStart._y + lDy; + + // WORKAROUND: Handles cases where the points never fall inside a bounding box + if (lScan._x < -100 || lScan._y < -100 || lScan._x >= 1000 || lScan._y >= 1000) + return point; + } +} + + +/** + * Returns the HotSpot coordinate closest to the player + */ + +RMPoint RMCharacter::nearestHotSpot(int sourcebox, int destbox) { + RMPoint hotspot; + int x, y; + int minDist = 10000000; + RMBoxLoc *cur = _theBoxes->getBoxes(_curLocation); + + for (short cc = 0; cc < cur->_boxes[sourcebox]._numHotspot; cc++) + if ((cur->_boxes[sourcebox]._hotspot[cc]._destination) == destbox) { + x = ABS(cur->_boxes[sourcebox]._hotspot[cc]._hotx - _pos._x); + y = ABS(cur->_boxes[sourcebox]._hotspot[cc]._hoty - _pos._y); + + if ((x * x + y * y) < minDist) { + minDist = x * x + y * y; + hotspot._x = cur->_boxes[sourcebox]._hotspot[cc]._hotx; + hotspot._y = cur->_boxes[sourcebox]._hotspot[cc]._hoty; + } + } + + return hotspot; +} + +void RMCharacter::draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + if (_bDrawNow) { + prim->getDst() += _fixedScroll; + + CORO_INVOKE_2(RMItem::draw, bigBuf, prim); + } + + CORO_END_CODE; +} + +void RMCharacter::newBoxEntered(int nBox) { + RMBoxLoc *cur; + + // Recall on ExitBox + mpalQueryDoAction(3, _curLocation, _curBox); + + cur = _theBoxes->getBoxes(_curLocation); + bool bOldReverse = cur->_boxes[_curBox]._bReversed; + _curBox = nBox; + + // If Z is changed, we must remove it from the OT + if (cur->_boxes[_curBox]._destZ != _z) { + _bRemoveFromOT = true; + _z = cur->_boxes[_curBox]._destZ; + } + + // Movement management is reversed, only if we are not in the shortest path. If we are in the shortest + // path, directly do the DoFrame + if (_bMovingWithoutMinpath) { + if ((cur->_boxes[_curBox]._bReversed && !bOldReverse) || (!cur->_boxes[_curBox]._bReversed && bOldReverse)) { + switch (getCurPattern()) { + case PAT_WALKUP: + setPattern(PAT_WALKDOWN); + break; + case PAT_WALKDOWN: + setPattern(PAT_WALKUP); + break; + case PAT_WALKRIGHT: + setPattern(PAT_WALKLEFT); + break; + case PAT_WALKLEFT: + setPattern(PAT_WALKRIGHT); + break; + } + } + } + + // Recall On EnterBox + mpalQueryDoAction(2, _curLocation, _curBox); +} + +void RMCharacter::doFrame(CORO_PARAM, RMGfxTargetBuffer *bigBuf, int loc) { + CORO_BEGIN_CONTEXT; + bool bEndNow; + RMBoxLoc *cur; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + _ctx->bEndNow = false; + _bEndOfPath = false; + _bDrawNow = (_curLocation == loc); + + g_system->lockMutex(_csMove); + + // If we're walking.. + if (_status != STAND) { + // If we are going horizontally + if (_walkStatus == 1) { + _dx = _walkSpeed * _walkCount; + _dy = (int)(_slope * _dx); + _pos._x = _lineStart._x + _dx; + _pos._y = _lineStart._y + _dy; + + // Right + if (((_walkSpeed > 0) && (_pos._x > _lineEnd._x)) || ((_walkSpeed < 0) && (_pos._x < _lineEnd._x))) { + _pos = _lineEnd; + _status = STAND; + _ctx->bEndNow = true; + } + } + + // If we are going vertical + if (_walkStatus == 0) { + _dy = _walkSpeed * _walkCount; + _dx = (int)(_slope * _dy); + _pos._x = _lineStart._x + _dx; + _pos._y = _lineStart._y + _dy; + + // Down + if (((_walkSpeed > 0) && (_pos._y > _lineEnd._y)) || ((_walkSpeed < 0) && (_pos._y < _lineEnd._y))) { + _pos = _lineEnd; + _status = STAND; + _ctx->bEndNow = true; + } + } + + // Check if the character came out of the BOX in error, in which case he returns immediately + if (inWhichBox(_pos) == -1) { + _pos._x = _lineStart._x + _olddx; + _pos._y = _lineStart._y + _olddy; + } + + // If we have just moved to a temporary location, and is over the shortest path, we stop permanently + if (_ctx->bEndNow && _minPath == 0) { + if (!_bEndOfPath) + CORO_INVOKE_0(stop); + _bEndOfPath = true; + CoroScheduler.pulseEvent(_hEndOfPath); + } + + _walkCount++; + + // Update the character Z. @@@ Should remove only if the Z was changed + + // Check if the box was changed + if (!_theBoxes->isInBox(_curLocation, _curBox, _pos)) + newBoxEntered(inWhichBox(_pos)); + + // Update the old coordinates + _olddx = _dx; + _olddy = _dy; + } + + // If we stop + if (_status == STAND) { + // Check if there is still the shortest path to calculate + if (_minPath == 1) { + _ctx->cur = _theBoxes->getBoxes(_curLocation); + + // If we still have to go through a box + if (_pathCount < _pathLength) { + // Check if the box we're going into is active + if (_ctx->cur->_boxes[_path[_pathCount - 1]]._bActive) { + // Move in a straight line towards the nearest hotspot, taking into account the reversing + // NEWBOX = path[pathcount-1] + CORO_INVOKE_2(goTo, nearestHotSpot(_path[_pathCount - 1], _path[_pathCount]), _ctx->cur->_boxes[_path[_pathCount - 1]]._bReversed); + _pathCount++; + } else { + // If the box is off, we can only block all + // @@@ Whilst this should not happen, because have improved + // the search for the minimum path + _minPath = 0; + if (!_bEndOfPath) + CORO_INVOKE_0(stop); + _bEndOfPath = true; + CoroScheduler.pulseEvent(_hEndOfPath); + } + } else { + // If we have already entered the last box, we just have to move in a straight line towards the + // point of arrival + // NEWBOX = InWhichBox(pathend) + _minPath = 0; + CORO_INVOKE_2(goTo, _pathEnd, _ctx->cur->_boxes[inWhichBox(_pathEnd)]._bReversed); + } + } + } + + g_system->unlockMutex(_csMove); + + // Invoke the DoFrame of the item + RMItem::doFrame(bigBuf); + + CORO_END_CODE; +} + +bool RMCharacter::endOfPath() { + return _bEndOfPath; +} + +void RMCharacter::stop(CORO_PARAM) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + _bMoving = false; + + // You never know.. + _status = STAND; + _minPath = 0; + + if (!_bNeedToStop) + return; + + _bNeedToStop = false; + + switch (getCurPattern()) { + case PAT_WALKUP: + setPattern(PAT_STANDUP); + break; + + case PAT_WALKDOWN: + setPattern(PAT_STANDDOWN); + break; + + case PAT_WALKLEFT: + setPattern(PAT_STANDLEFT); + break; + + case PAT_WALKRIGHT: + setPattern(PAT_STANDRIGHT); + break; + + default: + setPattern(PAT_STANDDOWN); + break; + } + + CORO_END_CODE; +} + +/** + * Check if the character is moving + */ +bool RMCharacter::isMoving() { + return _bMoving; +} + +inline int RMCharacter::inWhichBox(const RMPoint &pt) { + return _theBoxes->whichBox(_curLocation, pt); +} + + +void RMCharacter::move(CORO_PARAM, RMPoint pt, bool *result) { + CORO_BEGIN_CONTEXT; + RMPoint dest; + int numbox; + RMBoxLoc *cur; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + _bMoving = true; + + // 0, 0 does not do anything, just stops the character + if (pt._x == 0 && pt._y == 0) { + _minPath = 0; + _status = STAND; + CORO_INVOKE_0(stop); + if (result) + *result = true; + return; + } + + // If clicked outside the box + _ctx->numbox = inWhichBox(pt); + if (_ctx->numbox == -1) { + // Find neareste point inside the box + _ctx->dest = nearestPoint(pt); + + // ???!?? + if (_ctx->dest == pt) + _ctx->dest = invScanLine(pt); + + pt = _ctx->dest; + _ctx->numbox = inWhichBox(pt); + } + + _ctx->cur = _theBoxes->getBoxes(_curLocation); + + _minPath = 0; + _status = STAND; + _bMovingWithoutMinpath = true; + if (scanLine(pt)) + CORO_INVOKE_2(goTo, pt, _ctx->cur->_boxes[_ctx->numbox]._bReversed); + else if (findPath(inWhichBox(_pos), inWhichBox(pt))) { + _bMovingWithoutMinpath = false; + _minPath = 1; + _pathCount = 1; + _pathEnd = pt; + } else { + // @@@ This case is whether a hotspot is inside a box, but there is + // a path to get there. We use the InvScanLine to search around a point + _ctx->dest = invScanLine(pt); + pt = _ctx->dest; + + if (scanLine(pt)) + CORO_INVOKE_2(goTo, pt, _ctx->cur->_boxes[_ctx->numbox]._bReversed); + else if (findPath(inWhichBox(_pos), inWhichBox(pt))) { + _bMovingWithoutMinpath = false; + _minPath = 1; + _pathCount = 1; + _pathEnd = pt; + if (result) + *result = true; + } else { + if (result) + *result = false; + } + + return; + } + + if (result) + *result = true; + + CORO_END_CODE; +} + +void RMCharacter::setPosition(const RMPoint &pt, int newloc) { + RMBoxLoc *box; + + _minPath = 0; + _status = STAND; + _pos = pt; + + if (newloc != -1) + _curLocation = newloc; + + // Update the character's Z value + box = _theBoxes->getBoxes(_curLocation); + _curBox = inWhichBox(_pos); + assert(_curBox != -1); + _z = box->_boxes[_curBox]._destZ; + _bRemoveFromOT = true; +} + +void RMCharacter::waitForEndMovement(CORO_PARAM) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + if (_bMoving) + CORO_INVOKE_2(CoroScheduler.waitForSingleObject, _hEndOfPath, CORO_INFINITE); + + CORO_END_CODE; +} + +void RMCharacter::setFixedScroll(const RMPoint &fix) { + _fixedScroll = fix; +} + +void RMCharacter::setSpeed(int speed) { + _curSpeed = speed; +} + +void RMCharacter::removeThis(CORO_PARAM, bool &result) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + if (_bRemoveFromOT) + result = true; + else + CORO_INVOKE_1(RMItem::removeThis, result); + + CORO_END_CODE; +} + +RMCharacter::RMCharacter() { + _csMove = g_system->createMutex(); + _hEndOfPath = CoroScheduler.createEvent(false, false); + _minPath = 0; + _curSpeed = 3; + _bRemoveFromOT = false; + _bMoving = false; + _curLocation = 0; + _curBox = 0; + _dx = _dy = 0; + _olddx = _olddy = 0; + _fx = _fy = _slope = 0; + _walkSpeed = _walkStatus = 0; + _nextBox = 0; + _pathLength = _pathCount = 0; + _status = STAND; + _theBoxes = NULL; + _walkCount = 0; + _bEndOfPath = false; + _bMovingWithoutMinpath = false; + _bDrawNow = false; + _bNeedToStop = false; + + memset(_path, 0, sizeof(_path)); + + _pos.set(0, 0); +} + +RMCharacter::~RMCharacter() { + g_system->deleteMutex(_csMove); + CoroScheduler.closeEvent(_hEndOfPath); +} + +void RMCharacter::linkToBoxes(RMGameBoxes *boxes) { + _theBoxes = boxes; +} + +/****************************************************************************\ +* RMBox Methods +\****************************************************************************/ + +void RMBox::readFromStream(Common::ReadStream &ds) { + // Bbox + _left = ds.readSint32LE(); + _top = ds.readSint32LE(); + _right = ds.readSint32LE(); + _bottom = ds.readSint32LE(); + + // Adjacency + for (int i = 0; i < MAXBOXES; i++) { + _adj[i] = ds.readSint32LE(); + } + + // Misc + _numHotspot = ds.readSint32LE(); + _destZ = ds.readByte(); + byte b = ds.readByte(); + _bActive = b; + b = ds.readByte(); + _bReversed = b; + + // Reversed expansion space + for (int i = 0; i < 30; i++) + ds.readByte(); + + uint16 w; + // Hotspots + for (int i = 0; i < _numHotspot; i++) { + w = ds.readUint16LE(); + _hotspot[i]._hotx = w; + w = ds.readUint16LE(); + _hotspot[i]._hoty = w; + w = ds.readUint16LE(); + _hotspot[i]._destination = w; + } +} + +/****************************************************************************\ +* RMBoxLoc Methods +\****************************************************************************/ + +RMBoxLoc::RMBoxLoc() { + _boxes = NULL; + _numbBox = 0; +} + +RMBoxLoc::~RMBoxLoc() { + delete[] _boxes; +} + +void RMBoxLoc::readFromStream(Common::ReadStream &ds) { + char buf[2]; + + // ID and version + buf[0] = ds.readByte(); + buf[1] = ds.readByte(); + byte ver = ds.readByte(); + assert(buf[0] == 'B' && buf[1] == 'X'); + assert(ver == 3); + + // Number of boxes + _numbBox = ds.readSint32LE(); + + // Allocate memory for the boxes + _boxes = new RMBox[_numbBox]; + + // Read in boxes + for (int i = 0; i < _numbBox; i++) + _boxes[i].readFromStream(ds); +} + +void RMBoxLoc::recalcAllAdj() { + for (int i = 0; i < _numbBox; i++) { + Common::fill(_boxes[i]._adj, _boxes[i]._adj + MAXBOXES, 0); + + for (int j = 0; j < _boxes[i]._numHotspot; j++) { + if (_boxes[_boxes[i]._hotspot[j]._destination]._bActive) + _boxes[i]._adj[_boxes[i]._hotspot[j]._destination] = 1; + } + } +} + +/****************************************************************************\ +* RMGameBoxes methods +\****************************************************************************/ + +RMGameBoxes::RMGameBoxes() { + _nLocBoxes = 0; + Common::fill(_allBoxes, _allBoxes + GAME_BOXES_SIZE, (RMBoxLoc *)NULL); +} + +RMGameBoxes::~RMGameBoxes() { + for (int i = 1; i <= _nLocBoxes; ++i) + delete _allBoxes[i]; +} + +void RMGameBoxes::init() { + // Load boxes from disk + _nLocBoxes = 130; + for (int i = 1; i <= _nLocBoxes; i++) { + RMRes res(10000 + i); + + Common::SeekableReadStream *ds = res.getReadStream(); + + _allBoxes[i] = new RMBoxLoc(); + _allBoxes[i]->readFromStream(*ds); + + _allBoxes[i]->recalcAllAdj(); + + delete ds; + } +} + +void RMGameBoxes::close() { +} + +RMBoxLoc *RMGameBoxes::getBoxes(int nLoc) { + return _allBoxes[nLoc]; +} + +int RMGameBoxes::getLocBoxesCount() const { + return _nLocBoxes; +} + +bool RMGameBoxes::isInBox(int nLoc, int nBox, const RMPoint &pt) { + RMBoxLoc *cur = getBoxes(nLoc); + + if ((pt._x >= cur->_boxes[nBox]._left) && (pt._x <= cur->_boxes[nBox]._right) && + (pt._y >= cur->_boxes[nBox]._top) && (pt._y <= cur->_boxes[nBox]._bottom)) + return true; + else + return false; +} + +int RMGameBoxes::whichBox(int nLoc, const RMPoint &punto) { + RMBoxLoc *cur = getBoxes(nLoc); + + if (!cur) + return -1; + + for (int i = 0; i < cur->_numbBox; i++) { + if (cur->_boxes[i]._bActive) { + if ((punto._x >= cur->_boxes[i]._left) && (punto._x <= cur->_boxes[i]._right) && + (punto._y >= cur->_boxes[i]._top) && (punto._y <= cur->_boxes[i]._bottom)) + return i; + } + } + + return -1; +} + +void RMGameBoxes::changeBoxStatus(int nLoc, int nBox, int status) { + _allBoxes[nLoc]->_boxes[nBox]._bActive = status; + _allBoxes[nLoc]->recalcAllAdj(); +} + +int RMGameBoxes::getSaveStateSize() { + int size = 4; + + for (int i = 1; i <= _nLocBoxes; i++) { + size += 4; + size += _allBoxes[i]->_numbBox; + } + + return size; +} + +void RMGameBoxes::saveState(byte *state) { + // Save the number of locations with boxes + WRITE_LE_UINT32(state, _nLocBoxes); + state += 4; + + // For each location, write out the number of boxes and their status + for (int i = 1; i <= _nLocBoxes; i++) { + WRITE_LE_UINT32(state, _allBoxes[i]->_numbBox); + state += 4; + + for (int j = 0; j < _allBoxes[i]->_numbBox; j++) + *state++ = _allBoxes[i]->_boxes[j]._bActive; + } +} + +void RMGameBoxes::loadState(byte *state) { + // Load number of items + int nloc = READ_LE_UINT32(state); + state += 4; + + assert(nloc <= _nLocBoxes); + + int nbox; + // For each location, read the number of boxes and their status + for (int i = 1; i <= nloc; i++) { + nbox = READ_LE_UINT32(state); + state += 4; + + for (int j = 0; j < nbox ; j++) { + if (j < _allBoxes[i]->_numbBox) + _allBoxes[i]->_boxes[j]._bActive = *state; + + state++; + } + + _allBoxes[i]->recalcAllAdj(); + } +} + +/****************************************************************************\ +* RMLocation Methods +\****************************************************************************/ + +/** + * Standard constructor + */ +RMLocation::RMLocation() { + _nItems = 0; + _items = NULL; + _buf = NULL; + TEMPNumLoc = 0; + _cmode = CM_256; +} + +RMPoint RMLocation::TEMPGetTonyStart() { + return TEMPTonyStart; +} + +int RMLocation::TEMPGetNumLoc() { + return TEMPNumLoc; +} + +/** + * Load a location (.LOC) from a given data stream + * + * @param ds Data stream + * @returns True if succeeded OK, false in case of error. + */ +bool RMLocation::load(Common::SeekableReadStream &ds) { + char id[3]; + + // Reset dirty rectangling + _prevScroll.set(-1, -1); + _prevFixedScroll.set(-1, -1); + + // Check the ID + ds.read(id, 3); + + // Check if we are in a LOX + if (id[0] == 'L' && id[1] == 'O' && id[2] == 'X') + return loadLOX(ds); + + // Otherwise, check that it is a normal LOC + if (id[0] != 'L' || id[1] != 'O' || id[2] != 'C') + return false; + + // Version + byte ver = ds.readByte(); + assert(ver == 6); + + // Location name + _name = readString(ds); + + // Skip the MPAL bailouts (64 bytes) + TEMPNumLoc = ds.readSint32LE(); + TEMPTonyStart._x = ds.readSint32LE(); + TEMPTonyStart._y = ds.readSint32LE(); + ds.skip(64 - 4 * 3); + + // Skip flag associated with the background (?) + ds.skip(1); + + // Location dimensions + int dimx = ds.readSint32LE(); + int dimy = ds.readSint32LE(); + _curScroll.set(0, 0); + + // Read the color mode + byte cm = ds.readByte(); + _cmode = (RMColorMode)cm; + + // Initialize the source buffer and read the location + switch (_cmode) { + case CM_256: + _buf = new RMGfxSourceBuffer8; + break; + + case CM_65K: + _buf = new RMGfxSourceBuffer16; + break; + + default: + assert(0); + break; + }; + + // Initialize the surface, loading the palette if necessary + _buf->init(ds, dimx, dimy, true); + + // Check the size of the location +// assert(dimy!=512); + + // Number of objects + _nItems = ds.readSint32LE(); + + // Create and read in the objects + if (_nItems > 0) + _items = new RMItem[_nItems]; + + + g_vm->freezeTime(); + for (int i = 0; i < _nItems && !ds.err(); i++) + _items[i].readFromStream(ds); + g_vm->unfreezeTime(); + + return ds.err(); +} + + +bool RMLocation::loadLOX(Common::SeekableReadStream &ds) { + // Version + byte ver = ds.readByte(); + assert(ver == 1); + + // Location name + _name = readString(ds); + + // Location number + TEMPNumLoc = ds.readSint32LE(); + TEMPTonyStart._x = ds.readSint32LE(); + TEMPTonyStart._y = ds.readSint32LE(); + + // Dimensions + int dimx = ds.readSint32LE(); + int dimy = ds.readSint32LE(); + _curScroll.set(0, 0); + + // It's always 65K (16-bit) mode + _cmode = CM_65K; + _buf = new RMGfxSourceBuffer16; + + // Initialize the surface, loading in the palette if necessary + _buf->init(ds, dimx, dimy, true); + + // Number of items + _nItems = ds.readSint32LE(); + + // Create and read objects + if (_nItems > 0) + _items = new RMItem[_nItems]; + + for (int i = 0; i < _nItems && !ds.err(); i++) + _items[i].readFromStream(ds, true); + + return ds.err(); +} + + +/** + * Draw method overloaded from RMGfxSourceBUffer8 + */ +void RMLocation::draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim) { + CORO_BEGIN_CONTEXT; + bool priorTracking; + bool hasChanges; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + // Set the position of the source scrolling + if (_buf->getDimy() > RM_SY || _buf->getDimx() > RM_SX) { + prim->setSrc(RMRect(_curScroll, _curScroll + RMPoint(640, 480))); + } + + prim->setDst(_fixedScroll); + + // Check whether dirty rects are being tracked, and if there are changes, leave tracking + // turned on so a dirty rect will be added for the entire background + _ctx->priorTracking = bigBuf.getTrackDirtyRects(); + _ctx->hasChanges = (_prevScroll != _curScroll) || (_prevFixedScroll != _fixedScroll); + bigBuf.setTrackDirtyRects(_ctx->priorTracking && _ctx->hasChanges); + + // Invoke the drawing method fo the image class, which will draw the location background + CORO_INVOKE_2(_buf->draw, bigBuf, prim); + + if (_ctx->hasChanges) { + _prevScroll = _curScroll; + _prevFixedScroll = _fixedScroll; + } + bigBuf.setTrackDirtyRects(_ctx->priorTracking); + + CORO_END_CODE; +} + + +/** + * Prepare a frame, adding the location to the OT list, and all the items that have changed animation frame. + */ +void RMLocation::doFrame(RMGfxTargetBuffer *bigBuf) { + // If the location is not in the OT list, add it in + if (!_nInList) + bigBuf->addPrim(new RMGfxPrimitive(this)); + + // Process all the location items + for (int i = 0; i < _nItems; i++) + _items[i].doFrame(bigBuf); +} + + +RMItem *RMLocation::getItemFromCode(uint32 dwCode) { + for (int i = 0; i < _nItems; i++) { + if (_items[i].mpalCode() == (int)dwCode) + return &_items[i]; + } + + return NULL; +} + +RMItem *RMLocation::whichItemIsIn(const RMPoint &pt) { + int found = -1; + int foundSize = 0; + int size; + + for (int i = 0; i < _nItems; i++) { + size = 0; + if (_items[i].isIn(pt, &size)) { + if (found == -1 || size < foundSize) { + foundSize = size; + found = i; + } + } + } + + if (found == -1) + return NULL; + else + return &_items[found]; +} + +RMLocation::~RMLocation() { + unload(); +} + +void RMLocation::unload() { + // Clear memory + if (_items) { + delete[] _items; + _items = NULL; + } + + // Destroy the buffer + if (_buf) { + delete _buf; + _buf = NULL; + } +} + +void RMLocation::updateScrolling(const RMPoint &ptShowThis) { + RMPoint oldScroll = _curScroll; + + if (_curScroll._x + 250 > ptShowThis._x) { + _curScroll._x = ptShowThis._x - 250; + } else if (_curScroll._x + RM_SX - 250 < ptShowThis._x) { + _curScroll._x = ptShowThis._x + 250 - RM_SX; + } else if (ABS(_curScroll._x + RM_SX / 2 - ptShowThis._x) > 32 && _buf->getDimx() > RM_SX) { + if (_curScroll._x + RM_SX / 2 < ptShowThis._x) + _curScroll._x++; + else + _curScroll._x--; + } + + if (_curScroll._y + 180 > ptShowThis._y) { + _curScroll._y = ptShowThis._y - 180; + } else if (_curScroll._y + RM_SY - 180 < ptShowThis._y) { + _curScroll._y = ptShowThis._y + 180 - RM_SY; + } else if (ABS(_curScroll._y + RM_SY / 2 - ptShowThis._y) > 16 && _buf->getDimy() > RM_SY) { + if (_curScroll._y + RM_SY / 2 < ptShowThis._y) + _curScroll._y++; + else + _curScroll._y--; + } + + if (_curScroll._x < 0) + _curScroll._x = 0; + if (_curScroll._y < 0) + _curScroll._y = 0; + if (_curScroll._x + RM_SX > _buf->getDimx()) + _curScroll._x = _buf->getDimx() - RM_SX; + if (_curScroll._y + RM_SY > _buf->getDimy()) + _curScroll._y = _buf->getDimy() - RM_SY; + + if (oldScroll != _curScroll) { + for (int i = 0; i < _nItems; i++) + _items[i].setScrollPosition(_curScroll); + } +} + +void RMLocation::setFixedScroll(const RMPoint &scroll) { + _fixedScroll = scroll; + + for (int i = 0; i < _nItems; i++) + _items[i].setScrollPosition(_curScroll - _fixedScroll); +} + +void RMLocation::setScrollPosition(const RMPoint &scroll) { + RMPoint pt = scroll; + if (pt._x < 0) + pt._x = 0; + if (pt._y < 0) + pt._y = 0; + if (pt._x + RM_SX > _buf->getDimx()) + pt._x = _buf->getDimx() - RM_SX; + if (pt._y + RM_SY > _buf->getDimy()) + pt._y = _buf->getDimy() - RM_SY; + + _curScroll = pt; + + for (int i = 0; i < _nItems; i++) + _items[i].setScrollPosition(_curScroll); +} + + +void RMLocation::pauseSound(bool bPause) { + for (int i = 0; i < _nItems; i++) + _items[i].pauseSound(bPause); +} + +/** + * Read the current scroll position + */ +RMPoint RMLocation::scrollPosition() { + return _curScroll; +} + +/****************************************************************************\ +* RMMessage Methods +\****************************************************************************/ + +RMMessage::RMMessage(uint32 dwId) { + load(dwId); +} + +RMMessage::RMMessage() { + _lpMessage = NULL; + _nPeriods = 0; + for (int i = 0; i < 256; i++) + _lpPeriods[i] = 0; +} + +RMMessage::~RMMessage() { + if (_lpMessage) + globalDestroy(_lpMessage); +} + +void RMMessage::load(uint32 dwId) { + _lpMessage = mpalQueryMessage(dwId); + assert(_lpMessage != NULL); + + if (_lpMessage) + parseMessage(); +} + +void RMMessage::parseMessage() { + char *p; + + assert(_lpMessage != NULL); + + _nPeriods = 1; + p = _lpPeriods[0] = _lpMessage; + + for (;;) { + // Find the end of the current period + while (*p != '\0') + p++; + + // If there is another '0' at the end of the string, the end has been found + p++; + if (*p == '\0') + break; + + // Otherwise there is another line, and remember it's start + _lpPeriods[_nPeriods++] = p; + } +} + +bool RMMessage::isValid() { + return _lpMessage != NULL; +} + +int RMMessage::numPeriods() { + return _nPeriods; +} + +char *RMMessage::period(int num) { + return _lpPeriods[num]; +} + +char *RMMessage::operator[](int num) { + return _lpPeriods[num]; +} + +} // End of namespace Tony diff --git a/engines/tony/loc.h b/engines/tony/loc.h new file mode 100644 index 0000000000..04ba772458 --- /dev/null +++ b/engines/tony/loc.h @@ -0,0 +1,582 @@ +/* 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 Tony Tough source code + * + * Copyright (c) 1997-2003 Nayma Software + */ + +#ifndef TONY_LOC_H +#define TONY_LOC_H + +#include "common/scummsys.h" +#include "common/system.h" +#include "common/file.h" +#include "tony/sound.h" +#include "tony/utils.h" + +namespace Tony { + +/****************************************************************************\ +* Various defines +\****************************************************************************/ + +/** + * Valid color modes + */ +typedef enum { + CM_256, + CM_65K +} RMColorMode; + + +/****************************************************************************\ +* Class declarations +\****************************************************************************/ + +/** + * Generic palette + */ +class RMPalette { +public: + byte _data[1024]; + +public: + void readFromStream(Common::ReadStream &ds); +}; + + +/** + * Sound effect of an object + */ +class RMSfx { +public: + Common::String _name; + FPSfx *_fx; + bool _bPlayingLoop; + +public: + RMSfx(); + virtual ~RMSfx(); + + void play(bool bLoop = false); + void setVolume(int vol); + void pause(bool bPause); + void stop(); + + void readFromStream(Common::ReadStream &ds, bool bLOX = false); +}; + + +/** + * Object pattern + */ +class RMPattern { +public: + // Type of slot + enum RMSlotType { + DUMMY1 = 0, + DUMMY2, + SPRITE, + SOUND, + COMMAND, + SPECIAL + }; + + // Class slot + class RMSlot { + private: + RMPoint _pos; // Child co-ordinates + + public: + RMSlotType _type; + int _data; + byte _flag; + + public: + RMPoint pos() { + return _pos; + } + + void readFromStream(Common::ReadStream &ds, bool bLOX = false); + }; + +public: + Common::String _name; + +private: + int _speed; + RMPoint _pos; // Parent coordinates + RMPoint _curPos; // Parent + child coordinates + int _bLoop; + int _nSlots; + int _nCurSlot; + int _nCurSprite; + + RMSlot *_slots; + + uint32 _nStartTime; + +public: + RMPattern(); + virtual ~RMPattern(); + + // A warning that the pattern now and the current + int init(RMSfx *sfx, bool bPlayP0 = false, byte *bFlag = NULL); + + // Update the pattern, checking to see if it's time to change slot and executing + // any associated commands + int update(uint32 hEndPattern, byte &bFlag, RMSfx *sfx); + + // Stop a sound effect + void stopSfx(RMSfx *sfx); + + // Reads the position of the pattern + RMPoint pos(); + + void readFromStream(Common::ReadStream &ds, bool bLOX = false); + +private: + void updateCoord(); +}; + + +/** + * Sprite (frame) animation of an item + */ +class RMSprite : public RMGfxTask { +public: + Common::String _name; + RMRect _rcBox; + +protected: + RMGfxSourceBuffer *_buf; + +public: + RMSprite(); + virtual ~RMSprite(); + + void init(RMGfxSourceBuffer *buf); + virtual void draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim); + void setPalette(byte *lpBuf); + void getSizeFromStream(Common::SeekableReadStream &ds, int *dimx, int *dimy); + void LOXGetSizeFromStream(Common::SeekableReadStream &ds, int *dimx, int *dimy); + + void readFromStream(Common::SeekableReadStream &ds, bool bLOX = false); +}; + + +/** + * Data on an item + */ +class RMItem : public RMGfxTask { +public: + Common::String _name; + +protected: + int _z; + RMPoint _pos; // Coordinate ancestor + RMColorMode _cm; + RMPoint _curScroll; + + byte _FX; + byte _FXparm; + + virtual int getCurPattern(); + +private: + int _nCurPattern; + int _mpalCode; + RMPoint _hot; + RMRect _rcBox; + int _nSprites, _nSfx, _nPatterns; + byte _bPal; + RMPalette _pal; + + RMSprite *_sprites; + RMSfx *_sfx; + RMPattern *_patterns; + + byte _bCurFlag; + int _nCurSprite; + bool _bIsActive; + uint32 _hEndPattern; + bool _bInitCurPattern; + +public: + RMPoint calculatePos(); + +public: + RMItem(); + virtual ~RMItem(); + + // Process to make the object move on any animations. + // Returns TRUE if it should be redrawn on the next frame + bool doFrame(RMGfxTargetBuffer *bigBuf, bool bAddToList = true); + + // Sets the current scrolling position + void setScrollPosition(const RMPoint &scroll); + + // Overloading of check whether to remove from active list + virtual void removeThis(CORO_PARAM, bool &result); + + // Overloaded Draw + virtual void draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim); + + // Overloaded priority: it's based on Z ordering + virtual int priority(); + + // Pattern number + int numPattern(); + + // Set anew animation pattern, changing abruptly from the current + virtual void setPattern(int nPattern, bool bPlayP0 = false); + + // Set a new status + void setStatus(int nStatus); + + bool isIn(const RMPoint &pt, int *size = NULL); + RMPoint getHotspot(); + bool getName(Common::String &name); + int mpalCode(); + + // Unload + void unload(); + + // Wait for the end of the current pattern + void waitForEndPattern(CORO_PARAM, uint32 hCustomSkip = CORO_INVALID_PID_VALUE); + + // Sets a new hotspot fro the object + void changeHotspot(const RMPoint &pt); + + void setInitCurPattern(bool status); + + void playSfx(int nSfx); + + void readFromStream(Common::SeekableReadStream &ds, bool bLOX = false); + + void pauseSound(bool bPause); + +protected: + // Create a primitive that has as it's task this item + virtual RMGfxPrimitive *newItemPrimitive(); + + // Allocate memory for the sprites + virtual RMGfxSourceBuffer *newItemSpriteBuffer(int dimx, int dimy, bool bPreRLE); +}; + + +#define MAXBOXES 50 // Maximum number of allowed boxes +#define MAXHOTSPOT 20 // Maximum nimber of allowed hotspots + +class RMBox { +public: + struct Hotspot { + int _hotx, _hoty; // Hotspot coordinates + int _destination; // Hotspot destination + }; + +public: + int _left, _top, _right, _bottom; // Vertici bounding boxes + int _adj[MAXBOXES]; // List of adjacent bounding boxes + int _numHotspot; // Hotspot number + uint8 _destZ; // Z value for the bounding box + Hotspot _hotspot[MAXHOTSPOT]; // List of hotspots + + bool _bActive; + bool _bReversed; + + void readFromStream(Common::ReadStream &ds); +}; + + +class RMBoxLoc { +public: + int _numbBox; + RMBox *_boxes; + + void readFromStream(Common::ReadStream &ds); + +public: + RMBoxLoc(); + virtual ~RMBoxLoc(); + + void recalcAllAdj(); +}; + +#define GAME_BOXES_SIZE 200 + +class RMGameBoxes { +protected: + RMBoxLoc *_allBoxes[GAME_BOXES_SIZE]; + int _nLocBoxes; + +public: + RMGameBoxes(); + ~RMGameBoxes(); + + void init(); + void close(); + + // Get binding boxes for a given location + RMBoxLoc *getBoxes(int nLoc); + int getLocBoxesCount() const; + + // Return the box which contains a given point + int whichBox(int nLoc, const RMPoint &pt); + + // Check whether a point is inside a given box + bool isInBox(int nLoc, int nBox, const RMPoint &pt); + + // Change the status of a box + void changeBoxStatus(int nLoc, int nBox, int status); + + // Save state handling + int getSaveStateSize(); + void saveState(byte *buf); + void loadState(byte *buf); +}; + +class RMCharacter : protected RMItem { +public: + enum Patterns { + PAT_STANDUP = 1, + PAT_STANDDOWN, + PAT_STANDLEFT, + PAT_STANDRIGHT, + PAT_WALKUP, + PAT_WALKDOWN, + PAT_WALKLEFT, + PAT_WALKRIGHT + }; + +private: + enum CharacterStatus { + STAND, + WALK + }; + + signed short _walkCount; + int _dx, _dy, _olddx, _olddy; + float _fx, _fy, _slope; + RMPoint _lineStart, _lineEnd, _pathEnd; + signed char _walkSpeed, _walkStatus; + char _minPath; + short _nextBox; + short _path[MAXBOXES]; + short _pathLength, _pathCount; + int _curBox; + + CharacterStatus _status; + int _curSpeed; + bool _bEndOfPath; + uint32 _hEndOfPath; + OSystem::MutexRef _csMove; + int _curLocation; + bool _bRemoveFromOT; + bool _bMovingWithoutMinpath; + RMGameBoxes *_theBoxes; + + RMPoint _fixedScroll; + +private: + int inWhichBox(const RMPoint &pt); + + short findPath(short source, short destination); + RMPoint searching(char UP, char DOWN, char RIGHT, char LEFT, RMPoint point); + RMPoint nearestPoint(const RMPoint &punto); + + void goTo(CORO_PARAM, RMPoint destcoord, bool bReversed = false); + short scanLine(const RMPoint &point); + RMPoint invScanLine(const RMPoint &point); + RMPoint nearestHotSpot(int sourcebox, int destbox); + + void newBoxEntered(int nBox); + +protected: + bool _bMoving; + bool _bDrawNow; + bool _bNeedToStop; + +public: + RMCharacter(); + virtual ~RMCharacter(); + + void linkToBoxes(RMGameBoxes *theBoxes); + + virtual void removeThis(CORO_PARAM, bool &result); + + // Update the position of a character + void doFrame(CORO_PARAM, RMGfxTargetBuffer *bigBuf, int loc); + + // Overloaded draw + virtual void draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim); + + // TRUE if you just stopped + bool endOfPath(); + + // Change the pattern of a character to STOP + virtual void stop(CORO_PARAM); + + // Check if the character is moving + bool isMoving(); + + // Move the character to a certain position + void move(CORO_PARAM, RMPoint pt, bool *result = NULL); + + // Place the character in a certain position WITHOUT moving + void setPosition(const RMPoint &pt, int newloc = -1); + + // Wait for the end of movement + void waitForEndMovement(CORO_PARAM); + + void setFixedScroll(const RMPoint &fix); + void setSpeed(int speed); +}; + + +class RMWipe : public RMGfxTask { +private: + bool _bFading; + bool _bEndFade; + bool _bUnregister; + uint32 _hUnregistered; + int _nFadeStep; + uint32 _hEndOfFade; + bool _bMustRegister; + + RMItem _wip0r; + +public: + RMWipe(); + virtual ~RMWipe(); + + void doFrame(RMGfxTargetBuffer &bigBuf); + virtual void draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim); + + void initFade(int type); + void closeFade(); + void waitForFadeEnd(CORO_PARAM); + + virtual void unregister(); + virtual void removeThis(CORO_PARAM, bool &result); + virtual int priority(); +}; + + +/** + * Location + */ +class RMLocation : public RMGfxTaskSetPrior { +public: + Common::String _name; // Name + +private: + RMColorMode _cmode; // Color mode + RMGfxSourceBuffer *_buf; // Location picture + + int _nItems; // Number of objects + RMItem *_items; // Objects + + RMPoint _curScroll; // Current scroll position + RMPoint _fixedScroll; + + RMPoint _prevScroll; // Previous scroll position + RMPoint _prevFixedScroll; + +public: + // @@@@@@@@@@@@@@@@@@@@@@@ + + RMPoint TEMPTonyStart; + RMPoint TEMPGetTonyStart(); + + int TEMPNumLoc; + int TEMPGetNumLoc(); + +public: + RMLocation(); + virtual ~RMLocation(); + + // Load variations + bool load(Common::SeekableReadStream &ds); + bool loadLOX(Common::SeekableReadStream &ds); + + // Unload + void unload(); + + // Overloaded draw + virtual void draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim); + + // Prepare a frame by drawing the location and all it's items + void doFrame(RMGfxTargetBuffer *bigBuf); + + // Return the item at a given point + RMItem *whichItemIsIn(const RMPoint &pt); + + // Return the item based on it's MPAL code + RMItem *getItemFromCode(uint32 dwCode); + + // Set the current scroll position + void setScrollPosition(const RMPoint &scroll); + + // Sets an additinal offset for scrolling + void setFixedScroll(const RMPoint &scroll); + + // Update the scrolling coordinates to display the specified point + void updateScrolling(const RMPoint &ptShowThis); + + // Read the current scroll position + RMPoint scrollPosition(); + + // Pause sound + void pauseSound(bool bPause); +}; + + +/** + * MPAL message, composed of more ASCIIZ + */ +class RMMessage { +private: + char *_lpMessage; + char *_lpPeriods[256]; + int _nPeriods; + +private: + void parseMessage(); + +public: + RMMessage(); + RMMessage(uint32 dwId); + virtual ~RMMessage(); + + void load(uint32 dwId); + bool isValid(); + int numPeriods(); + char *period(int num); + char *operator[](int num); +}; + +} // End of namespace Tony + +#endif /* TONY_H */ diff --git a/engines/tony/module.mk b/engines/tony/module.mk new file mode 100644 index 0000000000..d66cf6f065 --- /dev/null +++ b/engines/tony/module.mk @@ -0,0 +1,33 @@ +MODULE := engines/tony + +MODULE_OBJS := \ + custom.o \ + debugger.o \ + detection.o \ + font.o \ + game.o \ + gfxcore.o \ + gfxengine.o \ + globals.o \ + input.o \ + inventory.o \ + loc.o \ + sound.o \ + tony.o \ + tonychar.o \ + utils.o \ + window.o \ + mpal/expr.o \ + mpal/loadmpc.o \ + mpal/memory.o \ + mpal/mpal.o \ + mpal/mpalutils.o \ + mpal/lzo.o + +# This module can be built as a plugin +ifeq ($(ENABLE_TONY), DYNAMIC_PLUGIN) +PLUGIN := 1 +endif + +# Include common rules +include $(srcdir)/rules.mk diff --git a/engines/tony/mpal/expr.cpp b/engines/tony/mpal/expr.cpp new file mode 100644 index 0000000000..824cd91651 --- /dev/null +++ b/engines/tony/mpal/expr.cpp @@ -0,0 +1,365 @@ +/* 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 Tony Tough source code + * + * Copyright (c) 1997-2003 Nayma Software + */ + +#include "tony/mpal/mpal.h" +#include "tony/mpal/memory.h" +#include "tony/mpal/mpaldll.h" +#include "tony/tony.h" + +namespace Tony { + +namespace MPAL { + + +/** + * Duplicate a mathematical expression. + * + * @param h Handle to the original expression + * @retruns Pointer to the cloned expression + */ +static byte *duplicateExpression(MpalHandle h) { + byte *orig, *clone; + + orig = (byte *)globalLock(h); + + int num = *(byte *)orig; + LpExpression one = (LpExpression)(orig+1); + + clone = (byte *)globalAlloc(GMEM_FIXED, sizeof(Expression) * num + 1); + LpExpression two = (LpExpression)(clone + 1); + + memcpy(clone, orig, sizeof(Expression) * num + 1); + + for (int i = 0; i < num; i++) { + if (one->_type == ELT_PARENTH) { + two->_type = ELT_PARENTH2; + two->_val._pson = duplicateExpression(two->_val._son); + } + + ++one; + ++two; + } + + globalUnlock(h); + return clone; +} + +static int Compute(int a, int b, byte symbol) { + switch (symbol) { + case OP_MUL: + return a * b; + case OP_DIV: + return a / b; + case OP_MODULE: + return a % b; + case OP_ADD: + return a + b; + case OP_SUB: + return a - b; + case OP_SHL: + return a << b; + case OP_SHR: + return a >> b; + case OP_MINOR: + return a < b; + case OP_MAJOR: + return a > b; + case OP_MINEQ: + return a <= b; + case OP_MAJEQ: + return a >= b; + case OP_EQUAL: + return a == b; + case OP_NOEQUAL: + return a != b; + case OP_BITAND: + return a & b; + case OP_BITXOR: + return a ^ b; + case OP_BITOR: + return a | b; + case OP_AND: + return a && b; + case OP_OR: + return a || b; + default: + GLOBALS._mpalError = 1; + break; + } + + return 0; +} + +static void solve(LpExpression one, int num) { + LpExpression two, three; + + while (num > 1) { + two = one + 1; + if ((two->_symbol == 0) || (one->_symbol & 0xF0) <= (two->_symbol & 0xF0)) { + two->_val._num = Compute(one->_val._num, two->_val._num, one->_symbol); + memmove(one, two, (num - 1) * sizeof(Expression)); + --num; + } else { + int j = 1; + three = two + 1; + while ((three->_symbol != 0) && (two->_symbol & 0xF0) > (three->_symbol & 0xF0)) { + ++two; + ++three; + ++j; + } + + three->_val._num = Compute(two->_val._num, three->_val._num, two->_symbol); + memmove(two, three, (num - j - 1) * sizeof(Expression)); + --num; + } + } +} + + +/** + * Calculates the result of a mathematical expression, replacing the current + * value of any variable. + * + * @param expr Pointer to an expression duplicated by DuplicateExpression + * @returns Value + */ +static int evaluateAndFreeExpression(byte *expr) { + int num = *expr; + LpExpression one = (LpExpression)(expr + 1); + + // 1) Substitutions of variables + LpExpression cur = one; + for (int i = 0; i < num; i++, cur++) { + if (cur->_type == ELT_VAR) { + cur->_type = ELT_NUMBER; + cur->_val._num = varGetValue(cur->_val._name); + } + } + + // 2) Replacement of brackets (using recursive calls) + cur = one; + for (int i = 0; i < num; i++, cur++) { + if (cur->_type == ELT_PARENTH2) { + cur->_type = ELT_NUMBER; + cur->_val._num = evaluateAndFreeExpression(cur->_val._pson); + } + } + + // 3) algebraic resolution + solve(one, num); + int val = one->_val._num; + globalDestroy(expr); + + return val; +} + + +/** + * Parses a mathematical expression from the MPC file + * + * @param buf Buffer containing the expression to evaluate + * @param h Pointer to a handle that, at the end of execution, + * will point to the area of memory containing the parsed expression + * @returns Pointer to the buffer immediately after the expression, or NULL if error. + */ +const byte *parseExpression(const byte *lpBuf, MpalHandle *h) { + byte *start; + + uint32 num = *lpBuf; + lpBuf++; + + if (num == 0) + return NULL; + + *h = globalAllocate(GMEM_MOVEABLE | GMEM_ZEROINIT, num * sizeof(Expression) + 1); + if (*h == NULL) + return NULL; + + start = (byte *)globalLock(*h); + *start = (byte)num; + + LpExpression cur = (LpExpression)(start + 1); + + for (uint32 i = 0;i < num; i++) { + cur->_type = *(lpBuf); + + // *(lpBuf + 1) contains the unary operator, unused => skipped + lpBuf += 2; + + switch (cur->_type) { + case ELT_NUMBER: + cur->_val._num = (int32)READ_LE_UINT32(lpBuf); + lpBuf += 4; + break; + + case ELT_VAR: + cur->_val._name = (char *)globalAlloc(GMEM_FIXED | GMEM_ZEROINIT, (*lpBuf) + 1); + if (cur->_val._name == NULL) + return NULL; + memcpy(cur->_val._name, lpBuf + 1, *lpBuf); + lpBuf += *lpBuf + 1; + break; + + case ELT_PARENTH: + lpBuf = parseExpression(lpBuf, &cur->_val._son); + if (lpBuf == NULL) + return NULL; + break; + + default: + return NULL; + } + + cur->_symbol = *lpBuf; + lpBuf++; + + cur++; + } + + if (*lpBuf != 0) + return NULL; + + lpBuf++; + + return lpBuf; +} + + +/** + * Calculate the value of a mathamatical expression + * + * @param h Handle to the expression + * @returns Numeric value + */ +int evaluateExpression(MpalHandle h) { + lockVar(); + int ret = evaluateAndFreeExpression(duplicateExpression(h)); + unlockVar(); + + return ret; +} + +/** + * Compare two mathematical expressions together + * + * @param h1 Expression to be compared + * @param h2 Expression to be compared + */ +bool compareExpressions(MpalHandle h1, MpalHandle h2) { + byte *e1, *e2; + + e1 = (byte *)globalLock(h1); + e2 = (byte *)globalLock(h2); + + int num1 = *(byte *)e1; + int num2 = *(byte *)e2; + + if (num1 != num2) { + globalUnlock(h1); + globalUnlock(h2); + return false; + } + + LpExpression one = (LpExpression)(e1 + 1); + LpExpression two = (LpExpression)(e2 + 1); + + for (int i = 0; i < num1; i++) { + if (one->_type != two->_type || (i != num1 - 1 && one->_symbol != two->_symbol)) { + globalUnlock(h1); + globalUnlock(h2); + return false; + } + + switch (one->_type) { + case ELT_NUMBER: + if (one->_val._num != two->_val._num) { + globalUnlock(h1); + globalUnlock(h2); + return false; + } + break; + + case ELT_VAR: + if (strcmp(one->_val._name, two->_val._name) != 0) { + globalUnlock(h1); + globalUnlock(h2); + return false; + } + break; + + case ELT_PARENTH: + if (!compareExpressions(one->_val._son, two->_val._son)) { + globalUnlock(h1); + globalUnlock(h2); + return false; + } + break; + } + + ++one; + ++two; + } + + globalUnlock(h1); + globalUnlock(h2); + + return true; +} + +/** + * Frees an expression that was previously parsed + * + * @param h Handle for the expression + */ +void freeExpression(MpalHandle h) { + byte *data = (byte *)globalLock(h); + int num = *data; + LpExpression cur = (LpExpression)(data + 1); + + for (int i = 0; i < num; ++i, ++cur) { + switch (cur->_type) { + case ELT_VAR: + globalDestroy(cur->_val._name); + break; + + case ELT_PARENTH: + freeExpression(cur->_val._son); + break; + + default: + break; + } + } + + globalUnlock(h); + globalFree(h); +} + +} // end of namespace MPAL + +} // end of namespace Tony diff --git a/engines/tony/mpal/expr.h b/engines/tony/mpal/expr.h new file mode 100644 index 0000000000..405624b4fe --- /dev/null +++ b/engines/tony/mpal/expr.h @@ -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. + * + * 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 Tony Tough source code + * + * Copyright (c) 1997-2003 Nayma Software + */ + +#ifndef MPAL_EXPR_H +#define MPAL_EXPR_H + +#include "tony/mpal/memory.h" + +namespace Tony { + +namespace MPAL { + +/** + * @defgroup Mathamatical operations + */ +//@{ + +#define OP_MUL ((1 << 4) | 0) +#define OP_DIV ((1 << 4) | 1) +#define OP_MODULE ((1 << 4) | 2) +#define OP_ADD ((2 << 4) | 0) +#define OP_SUB ((2 << 4) | 1) +#define OP_SHL ((3 << 4) | 0) +#define OP_SHR ((3 << 4) | 1) +#define OP_MINOR ((4 << 4) | 0) +#define OP_MAJOR ((4 << 4) | 1) +#define OP_MINEQ ((4 << 4) | 2) +#define OP_MAJEQ ((4 << 4) | 3) +#define OP_EQUAL ((5 << 4) | 0) +#define OP_NOEQUAL ((5 << 4) | 1) +#define OP_BITAND ((6 << 4) | 0) +#define OP_BITXOR ((7 << 4) | 0) +#define OP_BITOR ((8 << 4) | 0) +#define OP_AND ((9 << 4) | 0) +#define OP_OR ((10 << 4) | 0) + +//@} + +/** + * @defgroup Structures + */ + +//@{ +/** + * Mathamatical framework to manage operations + */ +typedef struct { + byte _type; // Object Type (see enum ExprListTypes) + + union { + int _num; // Identifier (if type == ELT_NUMBER) + char *_name; // Variable name (if type == ELT_VAR) + MpalHandle _son; // Handle expressions (if type == ELT_PARENTH) + byte *_pson; // Handle lockato (if type == ELT_PARENTH2) + } _val; + + byte _symbol; // Mathematic symbols (see #define OP_*) + +} Expression; +typedef Expression *LpExpression; + +//@} + +/** + * Object types that can be contained in an EXPRESSION structure + */ +enum ExprListTypes { + ELT_NUMBER = 1, + ELT_VAR = 2, + ELT_PARENTH = 3, + ELT_PARENTH2 = 4 +}; + +/****************************************************************************\ +* Function Prototypes +\****************************************************************************/ + +/** + * Parses a mathematical expression from the MPC file + * + * @param buf Buffer containing the expression to evaluate + * @param h Pointer to a handle that, at the end of execution, + * will point to the area of memory containing the parsed expression + * @returns Pointer to the buffer immediately after the expression, or NULL if error. + */ +const byte *parseExpression(const byte *lpBuf, MpalHandle *h); + +/** + * Calculate the value of a mathamatical expression + * + * @param h Handle to the expression + * @returns Numeric value + */ +int evaluateExpression(MpalHandle h); + +/** + * Compare two mathematical expressions together + * + * @param h1 Expression to be compared + * @param h2 Expression to be compared + */ +bool compareExpressions(MpalHandle h1, MpalHandle h2); + +/** + * Frees an expression that was previously parsed + * + * @param h Handle for the expression + */ +void freeExpression(MpalHandle h); + +} // end of namespace MPAL + +} // end of namespace Tony + +#endif diff --git a/engines/tony/mpal/loadmpc.cpp b/engines/tony/mpal/loadmpc.cpp new file mode 100644 index 0000000000..9c45cdf982 --- /dev/null +++ b/engines/tony/mpal/loadmpc.cpp @@ -0,0 +1,788 @@ +/* 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 Tony Tough source code + * + * Copyright (c) 1997-2003 Nayma Software + */ + +#include "mpal.h" +#include "mpaldll.h" +#include "memory.h" +#include "tony/tony.h" + +namespace Tony { + +namespace MPAL { + +/****************************************************************************\ +* Static functions +\****************************************************************************/ + +static bool compareCommands(struct Command *cmd1, struct Command *cmd2) { + if (cmd1->_type == 2 && cmd2->_type == 2) { + if (strcmp(cmd1->_lpszVarName, cmd2->_lpszVarName) == 0 && + compareExpressions(cmd1->_expr, cmd2->_expr)) + return true; + else + return false; + } else + return (memcmp(cmd1, cmd2, sizeof(struct Command)) == 0); +} + +/** + * Parses a script from the MPC file, and inserts its data into a structure + * + * @param lpBuf Buffer containing the compiled script. + * @param lpmsScript Pointer to a structure that will be filled with the + * data of the script. + * @returns Pointer to the buffer after the item, or NULL on failure. + */ +static const byte *ParseScript(const byte *lpBuf, LpMpalScript lpmsScript) { + lpmsScript->_nObj = (int32)READ_LE_UINT32(lpBuf); + lpBuf += 4; + + lpmsScript->_nMoments = READ_LE_UINT16(lpBuf); + lpBuf += 2; + + int curCmd = 0; + + for (uint i = 0; i < lpmsScript->_nMoments; i++) { + lpmsScript->_moment[i]._dwTime = (int32)READ_LE_UINT32(lpBuf); + lpBuf += 4; + lpmsScript->_moment[i]._nCmds = *lpBuf; + lpBuf++; + + for (int j = 0; j < lpmsScript->_moment[i]._nCmds; j++) { + lpmsScript->_command[curCmd]._type = *lpBuf; + lpBuf++; + switch (lpmsScript->_command[curCmd]._type) { + case 1: + lpmsScript->_command[curCmd]._nCf = READ_LE_UINT16(lpBuf); + lpBuf += 2; + lpmsScript->_command[curCmd]._arg1 = (int32)READ_LE_UINT32(lpBuf); + lpBuf += 4; + lpmsScript->_command[curCmd]._arg2 = (int32)READ_LE_UINT32(lpBuf); + lpBuf += 4; + lpmsScript->_command[curCmd]._arg3 = (int32)READ_LE_UINT32(lpBuf); + lpBuf += 4; + lpmsScript->_command[curCmd]._arg4 = (int32)READ_LE_UINT32(lpBuf); + lpBuf += 4; + break; + + case 2: { // Variable assign + int len = *lpBuf; + lpBuf++; + lpmsScript->_command[curCmd]._lpszVarName = (char *)globalAlloc(GMEM_FIXED | GMEM_ZEROINIT, len + 1); + if (lpmsScript->_command[curCmd]._lpszVarName == NULL) + return NULL; + memcpy(lpmsScript->_command[curCmd]._lpszVarName, lpBuf, len); + lpBuf += len; + + lpBuf = parseExpression(lpBuf, &lpmsScript->_command[curCmd]._expr); + if (lpBuf == NULL) + return NULL; + break; + } + default: + return NULL; + } + + lpmsScript->_moment[i]._cmdNum[j] = curCmd; + curCmd++; + } + } + return lpBuf; +} + +/** + * Frees a script allocated via a previous call to ParseScript + * + * @param lpmsScript Pointer to a script structure + */ +static void FreeScript(LpMpalScript lpmsScript) { + for (int i = 0; i < MAX_COMMANDS_PER_SCRIPT && (lpmsScript->_command[i]._type); ++i, ++lpmsScript) { + if (lpmsScript->_command[i]._type == 2) { + // Variable Assign + globalDestroy(lpmsScript->_command[i]._lpszVarName); + freeExpression(lpmsScript->_command[i]._expr); + } + } +} + +/** + * Parses a dialog from the MPC file, and inserts its data into a structure + * + * @param lpBuf Buffer containing the compiled dialog. + * @param lpmdDialog Pointer to a structure that will be filled with the + * data of the dialog. + * @returns Pointer to the buffer after the item, or NULL on failure. + */ +static const byte *parseDialog(const byte *lpBuf, LpMpalDialog lpmdDialog) { + byte *lpLock; + + lpmdDialog->_nObj = READ_LE_UINT32(lpBuf); + lpBuf += 4; + + // Periods + uint32 num = READ_LE_UINT16(lpBuf); + lpBuf += 2; + + if (num >= MAX_PERIODS_PER_DIALOG - 1) + error("Too much periods in dialog #%d", lpmdDialog->_nObj); + + uint32 i; + for (i = 0; i < num; i++) { + lpmdDialog->_periodNums[i] = READ_LE_UINT16(lpBuf); + lpBuf += 2; + lpmdDialog->_periods[i] = globalAllocate(GMEM_MOVEABLE | GMEM_ZEROINIT, *lpBuf + 1); + lpLock = (byte *)globalLock(lpmdDialog->_periods[i]); + Common::copy(lpBuf + 1, lpBuf + 1 + *lpBuf, lpLock); + globalUnlock(lpmdDialog->_periods[i]); + lpBuf += (*lpBuf) + 1; + } + + lpmdDialog->_periodNums[i] = 0; + lpmdDialog->_periods[i] = NULL; + + // Groups + num = READ_LE_UINT16(lpBuf); + lpBuf += 2; + uint32 curCmd = 0; + + if (num >= MAX_GROUPS_PER_DIALOG) + error("Too much groups in dialog #%d", lpmdDialog->_nObj); + + for (i = 0; i < num; i++) { + lpmdDialog->_group[i]._num = READ_LE_UINT16(lpBuf); + lpBuf += 2; + lpmdDialog->_group[i]._nCmds = *lpBuf; lpBuf++; + + if (lpmdDialog->_group[i]._nCmds >= MAX_COMMANDS_PER_GROUP) + error("Too much commands in group #%d in dialog #%d", lpmdDialog->_group[i]._num, lpmdDialog->_nObj); + + for (uint32 j = 0; j < lpmdDialog->_group[i]._nCmds; j++) { + lpmdDialog->_command[curCmd]._type = *lpBuf; + lpBuf++; + + switch (lpmdDialog->_command[curCmd]._type) { + // Call custom function + case 1: + lpmdDialog->_command[curCmd]._nCf = READ_LE_UINT16(lpBuf); + lpBuf += 2; + lpmdDialog->_command[curCmd]._arg1 = READ_LE_UINT32(lpBuf); + lpBuf += 4; + lpmdDialog->_command[curCmd]._arg2 = READ_LE_UINT32(lpBuf); + lpBuf += 4; + lpmdDialog->_command[curCmd]._arg3 = READ_LE_UINT32(lpBuf); + lpBuf += 4; + lpmdDialog->_command[curCmd]._arg4 = READ_LE_UINT32(lpBuf); + lpBuf += 4; + break; + + // Variable assign + case 2: { + uint32 len = *lpBuf; + lpBuf++; + lpmdDialog->_command[curCmd]._lpszVarName = (char *)globalAlloc(GMEM_FIXED | GMEM_ZEROINIT, len + 1); + if (lpmdDialog->_command[curCmd]._lpszVarName == NULL) + return NULL; + + Common::copy(lpBuf, lpBuf + len, lpmdDialog->_command[curCmd]._lpszVarName); + lpBuf += len; + + lpBuf = parseExpression(lpBuf, &lpmdDialog->_command[curCmd]._expr); + if (lpBuf == NULL) + return NULL; + break; + } + // Do Choice + case 3: + lpmdDialog->_command[curCmd]._nChoice = READ_LE_UINT16(lpBuf); + lpBuf += 2; + break; + + default: + return NULL; + } + + uint32 kk; + for (kk = 0;kk < curCmd; kk++) { + if (compareCommands(&lpmdDialog->_command[kk], &lpmdDialog->_command[curCmd])) { + lpmdDialog->_group[i]._cmdNum[j] = kk; + + // Free any data allocated for the duplictaed command + if (lpmdDialog->_command[curCmd]._type == 2) { + globalDestroy(lpmdDialog->_command[curCmd]._lpszVarName); + freeExpression(lpmdDialog->_command[curCmd]._expr); + + lpmdDialog->_command[curCmd]._lpszVarName = NULL; + lpmdDialog->_command[curCmd]._expr = 0; + lpmdDialog->_command[curCmd]._type = 0; + } + break; + } + } + + if (kk == curCmd) { + lpmdDialog->_group[i]._cmdNum[j] = curCmd; + curCmd++; + } + } + } + + if (curCmd >= MAX_COMMANDS_PER_DIALOG) + error("Too much commands in dialog #%d", lpmdDialog->_nObj); + + // Choices + num = READ_LE_UINT16(lpBuf); + lpBuf += 2; + + if (num >= MAX_CHOICES_PER_DIALOG) + error("Too much choices in dialog #%d", lpmdDialog->_nObj); + + for (i = 0; i < num; i++) { + lpmdDialog->_choice[i]._nChoice = READ_LE_UINT16(lpBuf); + lpBuf += 2; + + uint32 num2 = *lpBuf++; + + if (num2 >= MAX_SELECTS_PER_CHOICE) + error("Too much selects in choice #%d in dialog #%d", lpmdDialog->_choice[i]._nChoice, lpmdDialog->_nObj); + + for (uint32 j = 0; j < num2; j++) { + // When + switch (*lpBuf++) { + case 0: + lpmdDialog->_choice[i]._select[j]._when = NULL; + break; + + case 1: + lpBuf = parseExpression(lpBuf, &lpmdDialog->_choice[i]._select[j]._when); + if (lpBuf == NULL) + return NULL; + break; + + case 2: + return NULL; + } + + // Attrib + lpmdDialog->_choice[i]._select[j]._attr = *lpBuf++; + + // Data + lpmdDialog->_choice[i]._select[j]._dwData = READ_LE_UINT32(lpBuf); + lpBuf += 4; + + // PlayGroup + uint32 num3 = *lpBuf++; + + if (num3 >= MAX_PLAYGROUPS_PER_SELECT) + error("Too much playgroups in select #%d in choice #%d in dialog #%d", j, lpmdDialog->_choice[i]._nChoice, lpmdDialog->_nObj); + + for (uint32 z = 0; z < num3; z++) { + lpmdDialog->_choice[i]._select[j]._wPlayGroup[z] = READ_LE_UINT16(lpBuf); + lpBuf += 2; + } + + lpmdDialog->_choice[i]._select[j]._wPlayGroup[num3] = 0; + } + + // Mark the last selection + lpmdDialog->_choice[i]._select[num2]._dwData = 0; + } + + lpmdDialog->_choice[num]._nChoice = 0; + + return lpBuf; +} + + +/** + * Parses an item from the MPC file, and inserts its data into a structure + * + * @param lpBuf Buffer containing the compiled dialog. + * @param lpmiItem Pointer to a structure that will be filled with the + * data of the item. + * @returns Pointer to the buffer after the item, or NULL on failure. + * @remarks It's necessary that the structure that is passed has been + * completely initialized to 0 beforehand. + */ +static const byte *parseItem(const byte *lpBuf, LpMpalItem lpmiItem) { + lpmiItem->_nObj = (int32)READ_LE_UINT32(lpBuf); + lpBuf += 4; + + byte len = *lpBuf; + lpBuf++; + memcpy(lpmiItem->_lpszDescribe, lpBuf, MIN((byte)127, len)); + lpBuf += len; + + if (len >= MAX_DESCRIBE_SIZE) + error("Describe too long in item #%d", lpmiItem->_nObj); + + lpmiItem->_nActions=*lpBuf; + lpBuf++; + + // Allocation action + if (lpmiItem->_nActions > 0) + lpmiItem->_action = (ItemAction *)globalAlloc(GMEM_FIXED | GMEM_ZEROINIT, sizeof(struct ItemAction) * (int)lpmiItem->_nActions); + + uint32 curCmd = 0; + + for (uint32 i = 0; i < lpmiItem->_nActions; i++) { + lpmiItem->_action[i]._num = *lpBuf; + lpBuf++; + + lpmiItem->_action[i]._wParm = READ_LE_UINT16(lpBuf); + lpBuf += 2; + + if (lpmiItem->_action[i]._num == 0xFF) { + lpmiItem->_action[i]._wTime = READ_LE_UINT16(lpBuf); + lpBuf += 2; + + lpmiItem->_action[i]._perc = *lpBuf; + lpBuf++; + } + + if (*lpBuf == 0) { + lpBuf++; + lpmiItem->_action[i]._when = NULL; + } else { + lpBuf++; + lpBuf = parseExpression(lpBuf,&lpmiItem->_action[i]._when); + if (lpBuf == NULL) + return NULL; + } + + lpmiItem->_action[i]._nCmds=*lpBuf; + lpBuf++; + + if (lpmiItem->_action[i]._nCmds >= MAX_COMMANDS_PER_ACTION) + error("Too much commands in action #%d in item #%d", lpmiItem->_action[i]._num, lpmiItem->_nObj); + + for (uint32 j = 0; j < lpmiItem->_action[i]._nCmds; j++) { + lpmiItem->_command[curCmd]._type = *lpBuf; + lpBuf++; + switch (lpmiItem->_command[curCmd]._type) { + case 1: // Call custom function + lpmiItem->_command[curCmd]._nCf = READ_LE_UINT16(lpBuf); + lpBuf += 2; + lpmiItem->_command[curCmd]._arg1 = (int32)READ_LE_UINT32(lpBuf); + lpBuf += 4; + lpmiItem->_command[curCmd]._arg2 = (int32)READ_LE_UINT32(lpBuf); + lpBuf += 4; + lpmiItem->_command[curCmd]._arg3 = (int32)READ_LE_UINT32(lpBuf); + lpBuf += 4; + lpmiItem->_command[curCmd]._arg4 = (int32)READ_LE_UINT32(lpBuf); + lpBuf += 4; + break; + + case 2: // Variable assign + len = *lpBuf; + lpBuf++; + lpmiItem->_command[curCmd]._lpszVarName = (char *)globalAlloc(GMEM_FIXED | GMEM_ZEROINIT, len + 1); + if (lpmiItem->_command[curCmd]._lpszVarName == NULL) + return NULL; + memcpy(lpmiItem->_command[curCmd]._lpszVarName, lpBuf, len); + lpBuf += len; + + lpBuf = parseExpression(lpBuf, &lpmiItem->_command[curCmd]._expr); + if (lpBuf == NULL) + return NULL; + break; + + default: + return NULL; + } + + uint32 kk; + for (kk = 0; kk < curCmd; kk++) { + if (compareCommands(&lpmiItem->_command[kk], &lpmiItem->_command[curCmd])) { + lpmiItem->_action[i]._cmdNum[j] = kk; + + // Free any data allocated for the duplictaed command + if (lpmiItem->_command[curCmd]._type == 2) { + globalDestroy(lpmiItem->_command[curCmd]._lpszVarName); + freeExpression(lpmiItem->_command[curCmd]._expr); + + lpmiItem->_command[curCmd]._lpszVarName = NULL; + lpmiItem->_command[curCmd]._expr = 0; + lpmiItem->_command[curCmd]._type = 0; + } + break; + } + } + + if (kk == curCmd) { + lpmiItem->_action[i]._cmdNum[j] = curCmd; + curCmd++; + + if (curCmd >= MAX_COMMANDS_PER_ITEM) { + error("Too much commands in item #%d", lpmiItem->_nObj); + //curCmd=0; + } + } + } + } + + lpmiItem->_dwRes = READ_LE_UINT32(lpBuf); + lpBuf += 4; + + return lpBuf; +} + +/** + * Frees an item parsed from a prior call to ParseItem + * + * @param lpmiItem Pointer to an item structure + */ +static void freeItem(LpMpalItem lpmiItem) { + // Free the actions + if (lpmiItem->_action) { + for (int i = 0; i < lpmiItem->_nActions; ++i) { + if (lpmiItem->_action[i]._when != 0) + freeExpression(lpmiItem->_action[i]._when); + } + + globalDestroy(lpmiItem->_action); + } + + // Free the commands + for (int i = 0; i < MAX_COMMANDS_PER_ITEM && (lpmiItem->_command[i]._type); ++i) { + if (lpmiItem->_command[i]._type == 2) { + // Variable Assign + globalDestroy(lpmiItem->_command[i]._lpszVarName); + freeExpression(lpmiItem->_command[i]._expr); + } + } +} + +/** + * Parses a location from the MPC file, and inserts its data into a structure + * + * @param lpBuf Buffer containing the compiled location. + * @param lpmiLocation Pointer to a structure that will be filled with the + * data of the location. + * @returns Pointer to the buffer after the location, or NULL on failure. + */ +static const byte *ParseLocation(const byte *lpBuf, LpMpalLocation lpmlLocation) { + lpmlLocation->_nObj = (int32)READ_LE_UINT32(lpBuf); + lpBuf += 4; + lpmlLocation->_dwXlen = READ_LE_UINT16(lpBuf); + lpBuf += 2; + lpmlLocation->_dwYlen = READ_LE_UINT16(lpBuf); + lpBuf += 2; + lpmlLocation->_dwPicRes = READ_LE_UINT32(lpBuf); + lpBuf += 4; + + return lpBuf; +} + + +/****************************************************************************\ +* Exported functions +\****************************************************************************/ +/** + * @defgroup Exported functions + */ +//@{ + +/** + * Reads and interprets the MPC file, and create structures for various directives + * in the global variables + * + * @param lpBuf Buffer containing the MPC file data, excluding the header. + * @returns True if succeeded OK, false if failure. + */ +bool parseMpc(const byte *lpBuf) { + byte *lpTemp; + + // 1. Variables + if (lpBuf[0] != 'V' || lpBuf[1] != 'A' || lpBuf[2] != 'R' || lpBuf[3] != 'S') + return false; + + lpBuf += 4; + GLOBALS._nVars = READ_LE_UINT16(lpBuf); + lpBuf += 2; + + GLOBALS._hVars = globalAllocate(GMEM_MOVEABLE | GMEM_ZEROINIT, sizeof(MpalVar) * (uint32)GLOBALS._nVars); + if (GLOBALS._hVars == NULL) + return false; + + GLOBALS._lpmvVars = (LpMpalVar)globalLock(GLOBALS._hVars); + + for (uint16 i = 0; i < GLOBALS._nVars; i++) { + uint16 wLen = *(const byte *)lpBuf; + lpBuf++; + memcpy(GLOBALS._lpmvVars->_lpszVarName, lpBuf, MIN(wLen, (uint16)32)); + lpBuf += wLen; + GLOBALS._lpmvVars->_dwVal = READ_LE_UINT32(lpBuf); + lpBuf += 4; + + lpBuf++; // Skip 'ext' + GLOBALS._lpmvVars++; + } + + globalUnlock(GLOBALS._hVars); + + // 2. Messages + if (lpBuf[0] != 'M' || lpBuf[1] != 'S' || lpBuf[2] != 'G' || lpBuf[3] != 'S') + return false; + + lpBuf += 4; + GLOBALS._nMsgs = READ_LE_UINT16(lpBuf); + lpBuf += 2; + +#ifdef NEED_LOCK_MSGS + GLOBALS._hMsgs = globalAllocate(GMEM_MOVEABLE | GMEM_ZEROINIT, sizeof(MpalMsg) * (uint32)GLOBALS._nMsgs); + if (GLOBALS._hMsgs == NULL) + return false; + + GLOBALS._lpmmMsgs = (LpMpalMsg)globalLock(GLOBALS._hMsgs); +#else + GLOBALS._lpmmMsgs=(LPMPALMSG)globalAlloc(GMEM_FIXED | GMEM_ZEROINIT, sizeof(MPALMSG) * (uint32)GLOBALS._nMsgs); + if (GLOBALS._lpmmMsgs==NULL) + return false; +#endif + + for (uint16 i = 0; i < GLOBALS._nMsgs; i++) { + GLOBALS._lpmmMsgs->_wNum = READ_LE_UINT16(lpBuf); + lpBuf += 2; + + uint16 j; + for (j = 0; lpBuf[j] != 0;) + j += lpBuf[j] + 1; + + GLOBALS._lpmmMsgs->_hText = globalAllocate(GMEM_MOVEABLE | GMEM_ZEROINIT, j + 1); + lpTemp = (byte *)globalLock(GLOBALS._lpmmMsgs->_hText); + + for (j = 0; lpBuf[j] != 0;) { + memcpy(lpTemp, &lpBuf[j + 1], lpBuf[j]); + lpTemp += lpBuf[j]; + *lpTemp ++= '\0'; + j += lpBuf[j] + 1; + } + + lpBuf += j + 1; + *lpTemp = '\0'; + + globalUnlock(GLOBALS._lpmmMsgs->_hText); + GLOBALS._lpmmMsgs++; + } + +#ifdef NEED_LOCK_MSGS + globalUnlock(GLOBALS._hMsgs); +#endif + + // 3. Objects + if (lpBuf[0] != 'O' || lpBuf[1] != 'B' || lpBuf[2] != 'J' || lpBuf[3] != 'S') + return false; + + lpBuf += 4; + GLOBALS._nObjs = READ_LE_UINT16(lpBuf); + lpBuf += 2; + + // Check out the dialogs + GLOBALS._nDialogs = 0; + GLOBALS._hDialogs = GLOBALS._lpmdDialogs = NULL; + if (*((const byte *)lpBuf + 2) == 6 && strncmp((const char *)lpBuf + 3, "Dialog", 6) == 0) { + GLOBALS._nDialogs = READ_LE_UINT16(lpBuf); + lpBuf += 2; + + GLOBALS._hDialogs = globalAllocate(GMEM_MOVEABLE | GMEM_ZEROINIT, (uint32)GLOBALS._nDialogs * sizeof(MpalDialog)); + if (GLOBALS._hDialogs == NULL) + return false; + + GLOBALS._lpmdDialogs = (LpMpalDialog)globalLock(GLOBALS._hDialogs); + + for (uint16 i = 0; i < GLOBALS._nDialogs; i++) { + if ((lpBuf = parseDialog(lpBuf + 7, &GLOBALS._lpmdDialogs[i])) == NULL) + return false; + } + + globalUnlock(GLOBALS._hDialogs); + } + + // Check the items + GLOBALS._nItems = 0; + GLOBALS._hItems = GLOBALS._lpmiItems = NULL; + if (*(lpBuf + 2) == 4 && strncmp((const char *)lpBuf + 3, "Item", 4) == 0) { + GLOBALS._nItems = READ_LE_UINT16(lpBuf); + lpBuf += 2; + + // Allocate memory and read them in + GLOBALS._hItems = globalAllocate(GMEM_MOVEABLE | GMEM_ZEROINIT, (uint32)GLOBALS._nItems * sizeof(MpalItem)); + if (GLOBALS._hItems == NULL) + return false; + + GLOBALS._lpmiItems = (LpMpalItem)globalLock(GLOBALS._hItems); + + for (uint16 i = 0; i < GLOBALS._nItems; i++) { + if ((lpBuf = parseItem(lpBuf + 5, &GLOBALS._lpmiItems[i])) == NULL) + return false; + } + + globalUnlock(GLOBALS._hItems); + } + + // Check the locations + GLOBALS._nLocations = 0; + GLOBALS._hLocations = GLOBALS._lpmlLocations = NULL; + if (*(lpBuf + 2) == 8 && strncmp((const char *)lpBuf + 3, "Location", 8) == 0) { + GLOBALS._nLocations = READ_LE_UINT16(lpBuf); + lpBuf += 2; + + // Allocate memory and read them in + GLOBALS._hLocations = globalAllocate(GMEM_MOVEABLE | GMEM_ZEROINIT, (uint32)GLOBALS._nLocations * sizeof(MpalLocation)); + if (GLOBALS._hLocations == NULL) + return false; + + GLOBALS._lpmlLocations = (LpMpalLocation)globalLock(GLOBALS._hLocations); + + for (uint16 i = 0; i < GLOBALS._nLocations; i++) { + if ((lpBuf = ParseLocation(lpBuf + 9, &GLOBALS._lpmlLocations[i])) == NULL) + return false; + } + + globalUnlock(GLOBALS._hLocations); + } + + // Check the scripts + GLOBALS._nScripts = 0; + GLOBALS._hScripts = GLOBALS._lpmsScripts = NULL; + if (*(lpBuf + 2) == 6 && strncmp((const char *)lpBuf + 3, "Script", 6) == 0) { + GLOBALS._nScripts = READ_LE_UINT16(lpBuf); + lpBuf += 2; + + // Allocate memory + GLOBALS._hScripts = globalAllocate(GMEM_MOVEABLE | GMEM_ZEROINIT, (uint32)GLOBALS._nScripts * sizeof(MpalScript)); + if (GLOBALS._hScripts == NULL) + return false; + + GLOBALS._lpmsScripts = (LpMpalScript)globalLock(GLOBALS._hScripts); + + for (uint16 i = 0; i < GLOBALS._nScripts; i++) { + if ((lpBuf = ParseScript(lpBuf + 7, &GLOBALS._lpmsScripts[i])) == NULL) + return false; + + // Sort the various moments of the script + //qsort( + //GLOBALS.lpmsScripts[i].Moment, + //GLOBALS.lpmsScripts[i].nMoments, + //sizeof(GLOBALS.lpmsScripts[i].Moment[0]), + //(int (*)(const void *, const void *))CompareMoments + //); + } + + globalUnlock(GLOBALS._hScripts); + } + + if (lpBuf[0] != 'E' || lpBuf[1] != 'N' || lpBuf[2] != 'D' || lpBuf[3] != '0') + return false; + + return true; +} + +/** + * Free the given dialog + */ +static void freeDialog(LpMpalDialog lpmdDialog) { + // Free the periods + for (int i = 0; i < MAX_PERIODS_PER_DIALOG && (lpmdDialog->_periods[i]); ++i) + globalFree(lpmdDialog->_periods[i]); + + for (int i = 0; i < MAX_COMMANDS_PER_DIALOG && (lpmdDialog->_command[i]._type); i++) { + if (lpmdDialog->_command[i]._type == 2) { + // Variable assign + globalDestroy(lpmdDialog->_command[i]._lpszVarName); + freeExpression(lpmdDialog->_command[i]._expr); + } + } + + // Free the choices + for (int i = 0; i < MAX_CHOICES_PER_DIALOG; ++i) { + for (int j = 0; j < MAX_SELECTS_PER_CHOICE; j++) { + if (lpmdDialog->_choice[i]._select[j]._when) + freeExpression(lpmdDialog->_choice[i]._select[j]._when); + } + } +} + +/** + * Frees any data allocated from the parsing of the MPC file + */ +void freeMpc() { + // Free variables + globalFree(GLOBALS._hVars); + + // Free messages + LpMpalMsg lpmmMsgs = (LpMpalMsg)globalLock(GLOBALS._hMsgs); + for (int i = 0; i < GLOBALS._nMsgs; i++, ++lpmmMsgs) + globalFree(lpmmMsgs->_hText); + + globalUnlock(GLOBALS._hMsgs); + globalFree(GLOBALS._hMsgs); + + // Free objects + if (GLOBALS._hDialogs) { + LpMpalDialog lpmdDialogs = (LpMpalDialog)globalLock(GLOBALS._hDialogs); + + for (int i = 0; i < GLOBALS._nDialogs; i++, ++lpmdDialogs) + freeDialog(lpmdDialogs); + + globalFree(GLOBALS._hDialogs); + } + + // Free items + if (GLOBALS._hItems) { + LpMpalItem lpmiItems = (LpMpalItem)globalLock(GLOBALS._hItems); + + for (int i = 0; i < GLOBALS._nItems; ++i, ++lpmiItems) + freeItem(lpmiItems); + + globalUnlock(GLOBALS._hItems); + globalFree(GLOBALS._hItems); + } + + // Free the locations + if (GLOBALS._hLocations) { + globalFree(GLOBALS._hLocations); + } + + // Free the scripts + if (GLOBALS._hScripts) { + LpMpalScript lpmsScripts = (LpMpalScript)globalLock(GLOBALS._hScripts); + + for (int i = 0; i < GLOBALS._nScripts; ++i, ++lpmsScripts) { + FreeScript(lpmsScripts); + } + + globalUnlock(GLOBALS._hScripts); + } +} + +//@} + +} // end of namespace MPAL + +} // end of namespace Tony diff --git a/engines/tony/mpal/loadmpc.h b/engines/tony/mpal/loadmpc.h new file mode 100644 index 0000000000..20956288aa --- /dev/null +++ b/engines/tony/mpal/loadmpc.h @@ -0,0 +1,59 @@ +/* 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 Tony Tough source code + * + * Copyright (c) 1997-2003 Nayma Software + */ + +#ifndef __LOADMPC_H +#define __LOADMPC_H + +namespace Tony { + +namespace MPAL { + +/****************************************************************************\ +* Function prototypes +\****************************************************************************/ + +/** + * Reads and interprets the MPC file, and create structures for various directives + * in the global variables + * + * @param lpBuf Buffer containing the MPC file data, excluding the header. + * @returns True if succeeded OK, false if failure. + */ +bool parseMpc(const byte *lpBuf); + +/** + * Frees any data allocated from the parsing of the MPC file + */ +void freeMpc(); + +} // end of namespace MPAL + +} // end of namespace Tony + +#endif + diff --git a/engines/tony/mpal/lzo.cpp b/engines/tony/mpal/lzo.cpp new file mode 100644 index 0000000000..3d0751a5ca --- /dev/null +++ b/engines/tony/mpal/lzo.cpp @@ -0,0 +1,511 @@ +/* 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. + * + * + */ +/* minilzo.c -- mini subset of the LZO real-time data compression library + + This file is part of the LZO real-time data compression library. + + Copyright (C) 2011 Markus Franz Xaver Johannes Oberhumer + Copyright (C) 2010 Markus Franz Xaver Johannes Oberhumer + Copyright (C) 2009 Markus Franz Xaver Johannes Oberhumer + Copyright (C) 2008 Markus Franz Xaver Johannes Oberhumer + Copyright (C) 2007 Markus Franz Xaver Johannes Oberhumer + Copyright (C) 2006 Markus Franz Xaver Johannes Oberhumer + Copyright (C) 2005 Markus Franz Xaver Johannes Oberhumer + Copyright (C) 2004 Markus Franz Xaver Johannes Oberhumer + Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer + Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer + Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer + Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer + Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer + Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer + Copyright (C) 1997 Markus Franz Xaver Johannes Oberhumer + Copyright (C) 1996 Markus Franz Xaver Johannes Oberhumer + All Rights Reserved. + + The LZO library 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. + + The LZO library 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 the LZO library; see the file COPYING. + If not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + Markus F.X.J. Oberhumer + <markus@oberhumer.com> + http://www.oberhumer.com/opensource/lzo/ + */ + +#include "lzo.h" +#include "common/textconsole.h" + +namespace Tony { + +namespace MPAL { + +#define pd(a, b) ((uint32) ((a) - (b))) + +#define TEST_IP (ip < ip_end) +#define TEST_OP 1 +#define NEED_IP(x) ((void) 0) +#define NEED_OP(x) ((void) 0) +#define TEST_LB(m_pos) ((void) 0) + +#define M2_MAX_OFFSET 0x0800 +#define LZO1X + +/** + * Decompresses an LZO compressed resource + */ +int lzo1x_decompress(const byte *in, uint32 in_len, byte *out, uint32 *out_len) { + register byte *op; + register const byte *ip; + register uint32 t = 0; +#if defined(COPY_DICT) + uint32 m_off; + const byte *dict_end; +#else + register const byte *m_pos; +#endif + + const byte * const ip_end = in + in_len; +#if defined(HAVE_ANY_OP) + byte * const op_end = out + *out_len; +#endif +#if defined(LZO1Z) + uint32 last_m_off = 0; +#endif + +#if defined(COPY_DICT) + if (dict) + { + if (dict_len > M4_MAX_OFFSET) + { + dict += dict_len - M4_MAX_OFFSET; + dict_len = M4_MAX_OFFSET; + } + dict_end = dict + dict_len; + } + else + { + dict_len = 0; + dict_end = NULL; + } +#endif + + *out_len = 0; + + op = out; + ip = in; + + if (*ip > 17) + { + t = *ip++ - 17; + if (t < 4) + goto match_next; + assert(t > 0); NEED_OP(t); NEED_IP(t+1); + do *op++ = *ip++; while (--t > 0); + goto first_literal_run; + } + + while (TEST_IP && TEST_OP) + { + t = *ip++; + if (t >= 16) + goto match; + if (t == 0) + { + NEED_IP(1); + while (*ip == 0) + { + t += 255; + ip++; + NEED_IP(1); + } + t += 15 + *ip++; + } + assert(t > 0); NEED_OP(t+3); NEED_IP(t+4); +#if defined(LZO_UNALIGNED_OK_8) && defined(LZO_UNALIGNED_OK_4) + t += 3; + if (t >= 8) do + { + UA_COPY64(op, ip); + op += 8; ip += 8; t -= 8; + } while (t >= 8); + if (t >= 4) + { + UA_COPY32(op, ip); + op += 4; ip += 4; t -= 4; + } + if (t > 0) + { + *op++ = *ip++; + if (t > 1) { *op++ = *ip++; if (t > 2) { *op++ = *ip++; } } + } +#elif defined(LZO_UNALIGNED_OK_4) || defined(LZO_ALIGNED_OK_4) +#if !defined(LZO_UNALIGNED_OK_4) + if (PTR_ALIGNED2_4(op, ip)) + { +#endif + UA_COPY32(op, ip); + op += 4; ip += 4; + if (--t > 0) + { + if (t >= 4) + { + do { + UA_COPY32(op, ip); + op += 4; ip += 4; t -= 4; + } while (t >= 4); + if (t > 0) do *op++ = *ip++; while (--t > 0); + } + else + do *op++ = *ip++; while (--t > 0); + } +#if !defined(LZO_UNALIGNED_OK_4) + } + else +#endif +#endif +#if !defined(LZO_UNALIGNED_OK_4) && !defined(LZO_UNALIGNED_OK_8) + { + *op++ = *ip++; *op++ = *ip++; *op++ = *ip++; + do *op++ = *ip++; while (--t > 0); + } +#endif + +first_literal_run: + + t = *ip++; + if (t >= 16) + goto match; +#if defined(COPY_DICT) +#if defined(LZO1Z) + m_off = (1 + M2_MAX_OFFSET) + (t << 6) + (*ip++ >> 2); + last_m_off = m_off; +#else + m_off = (1 + M2_MAX_OFFSET) + (t >> 2) + (*ip++ << 2); +#endif + NEED_OP(3); + t = 3; COPY_DICT(t, m_off) +#else +#if defined(LZO1Z) + t = (1 + M2_MAX_OFFSET) + (t << 6) + (*ip++ >> 2); + m_pos = op - t; + last_m_off = t; +#else + m_pos = op - (1 + M2_MAX_OFFSET); + m_pos -= t >> 2; + m_pos -= *ip++ << 2; +#endif + TEST_LB(m_pos); NEED_OP(3); + *op++ = *m_pos++; *op++ = *m_pos++; *op++ = *m_pos; +#endif + goto match_done; + + do { +match: + if (t >= 64) + { +#if defined(COPY_DICT) +#if defined(LZO1X) + m_off = 1 + ((t >> 2) & 7) + (*ip++ << 3); + t = (t >> 5) - 1; +#elif defined(LZO1Y) + m_off = 1 + ((t >> 2) & 3) + (*ip++ << 2); + t = (t >> 4) - 3; +#elif defined(LZO1Z) + m_off = t & 0x1f; + if (m_off >= 0x1c) + m_off = last_m_off; + else + { + m_off = 1 + (m_off << 6) + (*ip++ >> 2); + last_m_off = m_off; + } + t = (t >> 5) - 1; +#endif +#else +#if defined(LZO1X) + m_pos = op - 1; + m_pos -= (t >> 2) & 7; + m_pos -= *ip++ << 3; + t = (t >> 5) - 1; +#elif defined(LZO1Y) + m_pos = op - 1; + m_pos -= (t >> 2) & 3; + m_pos -= *ip++ << 2; + t = (t >> 4) - 3; +#elif defined(LZO1Z) + { + uint32 off = t & 0x1f; + m_pos = op; + if (off >= 0x1c) + { + assert(last_m_off > 0); + m_pos -= last_m_off; + } + else + { + off = 1 + (off << 6) + (*ip++ >> 2); + m_pos -= off; + last_m_off = off; + } + } + t = (t >> 5) - 1; +#endif + TEST_LB(m_pos); assert(t > 0); NEED_OP(t+3-1); + goto copy_match; +#endif + } + else if (t >= 32) + { + t &= 31; + if (t == 0) + { + NEED_IP(1); + while (*ip == 0) + { + t += 255; + ip++; + NEED_IP(1); + } + t += 31 + *ip++; + } +#if defined(COPY_DICT) +#if defined(LZO1Z) + m_off = 1 + (ip[0] << 6) + (ip[1] >> 2); + last_m_off = m_off; +#else + m_off = 1 + (ip[0] >> 2) + (ip[1] << 6); +#endif +#else +#if defined(LZO1Z) + { + uint32 off = 1 + (ip[0] << 6) + (ip[1] >> 2); + m_pos = op - off; + last_m_off = off; + } +#elif defined(LZO_UNALIGNED_OK_2) && defined(LZO_ABI_LITTLE_ENDIAN) + m_pos = op - 1; + m_pos -= UA_GET16(ip) >> 2; +#else + m_pos = op - 1; + m_pos -= (ip[0] >> 2) + (ip[1] << 6); +#endif +#endif + ip += 2; + } + else if (t >= 16) + { +#if defined(COPY_DICT) + m_off = (t & 8) << 11; +#else + m_pos = op; + m_pos -= (t & 8) << 11; +#endif + t &= 7; + if (t == 0) + { + NEED_IP(1); + while (*ip == 0) + { + t += 255; + ip++; + NEED_IP(1); + } + t += 7 + *ip++; + } +#if defined(COPY_DICT) +#if defined(LZO1Z) + m_off += (ip[0] << 6) + (ip[1] >> 2); +#else + m_off += (ip[0] >> 2) + (ip[1] << 6); +#endif + ip += 2; + if (m_off == 0) + goto eof_found; + m_off += 0x4000; +#if defined(LZO1Z) + last_m_off = m_off; +#endif +#else +#if defined(LZO1Z) + m_pos -= (ip[0] << 6) + (ip[1] >> 2); +#elif defined(LZO_UNALIGNED_OK_2) && defined(LZO_ABI_LITTLE_ENDIAN) + m_pos -= UA_GET16(ip) >> 2; +#else + m_pos -= (ip[0] >> 2) + (ip[1] << 6); +#endif + ip += 2; + if (m_pos == op) + goto eof_found; + m_pos -= 0x4000; +#if defined(LZO1Z) + last_m_off = pd((const byte *)op, m_pos); +#endif +#endif + } + else + { +#if defined(COPY_DICT) +#if defined(LZO1Z) + m_off = 1 + (t << 6) + (*ip++ >> 2); + last_m_off = m_off; +#else + m_off = 1 + (t >> 2) + (*ip++ << 2); +#endif + NEED_OP(2); + t = 2; COPY_DICT(t, m_off) +#else +#if defined(LZO1Z) + t = 1 + (t << 6) + (*ip++ >> 2); + m_pos = op - t; + last_m_off = t; +#else + m_pos = op - 1; + m_pos -= t >> 2; + m_pos -= *ip++ << 2; +#endif + TEST_LB(m_pos); NEED_OP(2); + *op++ = *m_pos++; *op++ = *m_pos; +#endif + goto match_done; + } + +#if defined(COPY_DICT) + + NEED_OP(t+3-1); + t += 3-1; COPY_DICT(t, m_off) + +#else + + TEST_LB(m_pos); assert(t > 0); NEED_OP(t+3-1); +#if defined(LZO_UNALIGNED_OK_8) && defined(LZO_UNALIGNED_OK_4) + if (op - m_pos >= 8) + { + t += (3 - 1); + if (t >= 8) do + { + UA_COPY64(op, m_pos); + op += 8; m_pos += 8; t -= 8; + } while (t >= 8); + if (t >= 4) + { + UA_COPY32(op, m_pos); + op += 4; m_pos += 4; t -= 4; + } + if (t > 0) + { + *op++ = m_pos[0]; + if (t > 1) { *op++ = m_pos[1]; if (t > 2) { *op++ = m_pos[2]; } } + } + } + else +#elif defined(LZO_UNALIGNED_OK_4) || defined(LZO_ALIGNED_OK_4) +#if !defined(LZO_UNALIGNED_OK_4) + if (t >= 2 * 4 - (3 - 1) && PTR_ALIGNED2_4(op, m_pos)) + { + assert((op - m_pos) >= 4); +#else + if (t >= 2 * 4 - (3 - 1) && (op - m_pos) >= 4) + { +#endif + UA_COPY32(op, m_pos); + op += 4; m_pos += 4; t -= 4 - (3 - 1); + do { + UA_COPY32(op, m_pos); + op += 4; m_pos += 4; t -= 4; + } while (t >= 4); + if (t > 0) do *op++ = *m_pos++; while (--t > 0); + } + else +#endif + { +copy_match: + *op++ = *m_pos++; *op++ = *m_pos++; + do *op++ = *m_pos++; while (--t > 0); + } + +#endif + +match_done: +#if defined(LZO1Z) + t = ip[-1] & 3; +#else + t = ip[-2] & 3; +#endif + if (t == 0) + break; + +match_next: + assert(t > 0); assert(t < 4); NEED_OP(t); NEED_IP(t+1); +#if 0 + do *op++ = *ip++; while (--t > 0); +#else + *op++ = *ip++; + if (t > 1) { *op++ = *ip++; if (t > 2) { *op++ = *ip++; } } +#endif + t = *ip++; + } while (TEST_IP && TEST_OP); + } + +#if defined(HAVE_TEST_IP) || defined(HAVE_TEST_OP) + *out_len = pd(op, out); + return LZO_E_EOF_NOT_FOUND; +#endif + +eof_found: + assert(t == 1); + *out_len = pd(op, out); + return (ip == ip_end ? LZO_E_OK : + (ip < ip_end ? LZO_E_INPUT_NOT_CONSUMED : LZO_E_INPUT_OVERRUN)); + +#if defined(HAVE_NEED_IP) +input_overrun: + *out_len = pd(op, out); + return LZO_E_INPUT_OVERRUN; +#endif + +#if defined(HAVE_NEED_OP) +output_overrun: + *out_len = pd(op, out); + return LZO_E_OUTPUT_OVERRUN; +#endif + +#if defined(LZO_TEST_OVERRUN_LOOKBEHIND) +lookbehind_overrun: + *out_len = pd(op, out); + return LZO_E_LOOKBEHIND_OVERRUN; +#endif +} + +} // end of namespace MPAL + +} // end of namespace Tony diff --git a/engines/tony/mpal/lzo.h b/engines/tony/mpal/lzo.h new file mode 100644 index 0000000000..ebb1c4b516 --- /dev/null +++ b/engines/tony/mpal/lzo.h @@ -0,0 +1,111 @@ +/* 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. + * + * + */ +/* minilzo.c -- mini subset of the LZO real-time data compression library + + This file is part of the LZO real-time data compression library. + + Copyright (C) 2011 Markus Franz Xaver Johannes Oberhumer + Copyright (C) 2010 Markus Franz Xaver Johannes Oberhumer + Copyright (C) 2009 Markus Franz Xaver Johannes Oberhumer + Copyright (C) 2008 Markus Franz Xaver Johannes Oberhumer + Copyright (C) 2007 Markus Franz Xaver Johannes Oberhumer + Copyright (C) 2006 Markus Franz Xaver Johannes Oberhumer + Copyright (C) 2005 Markus Franz Xaver Johannes Oberhumer + Copyright (C) 2004 Markus Franz Xaver Johannes Oberhumer + Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer + Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer + Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer + Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer + Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer + Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer + Copyright (C) 1997 Markus Franz Xaver Johannes Oberhumer + Copyright (C) 1996 Markus Franz Xaver Johannes Oberhumer + All Rights Reserved. + + The LZO library 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. + + The LZO library 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 the LZO library; see the file COPYING. + If not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + Markus F.X.J. Oberhumer + <markus@oberhumer.com> + http://www.oberhumer.com/opensource/lzo/ + */ + +#ifndef TONY_MPAL_LZO_H +#define TONY_MPAL_LZO_H + +#include "common/scummsys.h" + +namespace Tony { + +namespace MPAL { + +/* Error codes for the compression/decompression functions. Negative + * values are errors, positive values will be used for special but + * normal events. + */ +#define LZO_E_OK 0 +#define LZO_E_ERROR (-1) +#define LZO_E_OUT_OF_MEMORY (-2) /* [lzo_alloc_func_t failure] */ +#define LZO_E_NOT_COMPRESSIBLE (-3) /* [not used right now] */ +#define LZO_E_INPUT_OVERRUN (-4) +#define LZO_E_OUTPUT_OVERRUN (-5) +#define LZO_E_LOOKBEHIND_OVERRUN (-6) +#define LZO_E_EOF_NOT_FOUND (-7) +#define LZO_E_INPUT_NOT_CONSUMED (-8) +#define LZO_E_NOT_YET_IMPLEMENTED (-9) /* [not used right now] */ +#define LZO_E_INVALID_ARGUMENT (-10) + +#define LZO1X_999_MEM_COMPRESS ((uint32) (14 * 16384L * sizeof(uint16))) + +/** + * Decompresses an LZO compressed resource + */ +int lzo1x_decompress(const byte *src, uint32 src_len, byte *dst, uint32 *dst_len); + +/** + * Comrpess a data block into an LZO stream + */ +int lzo1x_1_compress(const byte *src, uint32 src_len, byte *dst, uint32 *dst_len, void *wrkmem); + +/** + * better compression ratio at the cost of more memory and time + */ +int lzo1x_999_compress(const byte *src, uint32 src_len, byte *dst, uint32 *dst_len, void *wrkmem); + +} // end of namespace MPAL + +} // end of namespace Tony + +#endif /* already included */ diff --git a/engines/tony/mpal/memory.cpp b/engines/tony/mpal/memory.cpp new file mode 100644 index 0000000000..dfbf16e789 --- /dev/null +++ b/engines/tony/mpal/memory.cpp @@ -0,0 +1,127 @@ +/* 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. + * + * + */ + +#include "common/algorithm.h" +#include "common/textconsole.h" +#include "tony/mpal/memory.h" + +namespace Tony { + +namespace MPAL { + +/****************************************************************************\ +* MemoryManager methods +\****************************************************************************/ + +/** + * Allocates a new memory block + * @return Returns a MemoryItem instance for the new block + */ +MpalHandle MemoryManager::allocate(uint32 size, uint flags) { + MemoryItem *newItem = (MemoryItem *)malloc(sizeof(MemoryItem) + size); + newItem->_id = BLOCK_ID; + newItem->_size = size; + newItem->_lockCount = 0; + + // If requested, clear the allocated data block + if ((flags & GMEM_ZEROINIT) != 0) { + byte *dataP = newItem->_data; + Common::fill(dataP, dataP + size, 0); + } + + return (MpalHandle)newItem; +} + +/** + * Allocates a new memory block and returns its data pointer + * @return Data pointer to allocated block + */ +void *MemoryManager::alloc(uint32 size, uint flags) { + MemoryItem *item = (MemoryItem *)allocate(size, flags); + ++item->_lockCount; + return &item->_data[0]; +} + +#define OFFSETOF(type, field) ((size_t) &(((type *) 0)->field)) + +/** + * Returns a reference to the MemoryItem for a gien byte pointer + * @param block Byte pointer + */ +MemoryItem *MemoryManager::getItem(MpalHandle handle) { + MemoryItem *rec = (MemoryItem *)((byte *)handle - OFFSETOF(MemoryItem, _data)); + assert(rec->_id == BLOCK_ID); + return rec; +} + +/** + * Returns a size of a memory block given its pointer + */ +uint32 MemoryManager::getSize(MpalHandle handle) { + MemoryItem *item = (MemoryItem *)handle; + assert(item->_id == BLOCK_ID); + return item->_size; +} + +/** + * Erases a given item + */ +void MemoryManager::freeBlock(MpalHandle handle) { + MemoryItem *item = (MemoryItem *)handle; + assert(item->_id == BLOCK_ID); + free(item); +} + +/** + * Erases a given item + */ +void MemoryManager::destroyItem(MpalHandle handle) { + MemoryItem *item = getItem(handle); + assert(item->_id == BLOCK_ID); + free(item); +} + +/** + * Locks an item for access + */ +byte *MemoryManager::lockItem(MpalHandle handle) { + MemoryItem *item = (MemoryItem *)handle; + assert(item->_id == BLOCK_ID); + ++item->_lockCount; + return &item->_data[0]; +} + +/** + * Unlocks a locked item + */ +void MemoryManager::unlockItem(MpalHandle handle) { + MemoryItem *item = (MemoryItem *)handle; + assert(item->_id == BLOCK_ID); + assert(item->_lockCount > 0); + --item->_lockCount; +} + + +} // end of namespace MPAL + +} // end of namespace Tony diff --git a/engines/tony/mpal/memory.h b/engines/tony/mpal/memory.h new file mode 100644 index 0000000000..9c21cc20e6 --- /dev/null +++ b/engines/tony/mpal/memory.h @@ -0,0 +1,78 @@ +/* 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. + * + * + */ + +#ifndef TONY_MPAL_MEMORY +#define TONY_MPAL_MEMORY + +#include "common/scummsys.h" +#include "common/list.h" + +namespace Tony { + +namespace MPAL { + +typedef void *MpalHandle; + +struct MemoryItem { + uint32 _id; + uint32 _size; + int _lockCount; + byte _data[1]; + + // Casting for access to data + operator void *() { return &_data[0]; } +}; + +class MemoryManager { +private: + static MemoryItem *getItem(MpalHandle handle); +public: + static MpalHandle allocate(uint32 size, uint flags); + static void *alloc(uint32 size, uint flags); + static void freeBlock(MpalHandle handle); + static void destroyItem(MpalHandle handle); + static uint32 getSize(MpalHandle handle); + static byte *lockItem(MpalHandle handle); + static void unlockItem(MpalHandle handle); +}; + +// defines +#define globalAlloc(flags, size) MemoryManager::alloc(size, flags) +#define globalAllocate(flags, size) MemoryManager::allocate(size, flags) +#define globalFree(handle) MemoryManager::freeBlock(handle) +#define globalDestroy(handle) MemoryManager::destroyItem(handle) +#define globalLock(handle) MemoryManager::lockItem(handle) +#define globalUnlock(handle) MemoryManager::unlockItem(handle) +#define globalSize(handle) MemoryManager::getSize(handle) + +#define GMEM_FIXED 1 +#define GMEM_MOVEABLE 2 +#define GMEM_ZEROINIT 4 + +const uint32 BLOCK_ID = 0x12345678; + +} // end of namespace MPAL + +} // end of namespace Tony + +#endif diff --git a/engines/tony/mpal/mpal.cpp b/engines/tony/mpal/mpal.cpp new file mode 100644 index 0000000000..8d83363c24 --- /dev/null +++ b/engines/tony/mpal/mpal.cpp @@ -0,0 +1,2089 @@ +/* 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 Tony Tough source code + * + * Copyright (c) 1997-2003 Nayma Software + */ + +#include "common/scummsys.h" +#include "common/file.h" +#include "common/savefile.h" +#include "common/system.h" +#include "tony/tony.h" +#include "tony/mpal/lzo.h" +#include "tony/mpal/mpal.h" +#include "tony/mpal/mpaldll.h" + +namespace Tony { + +namespace MPAL { + +/****************************************************************************\ +* Internal functions +\****************************************************************************/ + +/** + * Locks the variables for access + */ +void lockVar() { + GLOBALS._lpmvVars = (LpMpalVar)globalLock(GLOBALS._hVars); +} + +/** + * Unlocks variables after use + */ +void unlockVar() { + globalUnlock(GLOBALS._hVars); +} + +/** + * Locks the messages for access + */ +static void LockMsg() { +#ifdef NEED_LOCK_MSGS + GLOBALS._lpmmMsgs = (LpMpalMsg)globalLock(GLOBALS._hMsgs); +#endif +} + +/** + * Unlocks the messages after use + */ +static void UnlockMsg() { +#ifdef NEED_LOCK_MSGS + globalUnlock(GLOBALS._hMsgs); +#endif +} + +/** + * Locks the dialogs for access + */ +static void lockDialogs() { + GLOBALS._lpmdDialogs = (LpMpalDialog)globalLock(GLOBALS._hDialogs); +} + +/** + * Unlocks the dialogs after use + */ +static void unlockDialogs() { + globalUnlock(GLOBALS._hDialogs); +} + +/** + * Locks the location data structures for access + */ +static void lockLocations() { + GLOBALS._lpmlLocations = (LpMpalLocation)globalLock(GLOBALS._hLocations); +} + +/** + * Unlocks the location structures after use + */ +static void unlockLocations() { + globalUnlock(GLOBALS._hLocations); +} + +/** + * Locks the items structures for use + */ +static void lockItems() { + GLOBALS._lpmiItems = (LpMpalItem)globalLock(GLOBALS._hItems); +} + +/** + * Unlocks the items structures after use + */ +static void unlockItems() { + globalUnlock(GLOBALS._hItems); +} + +/** + * Locks the script data structures for use + */ +static void LockScripts() { + GLOBALS._lpmsScripts = (LpMpalScript)globalLock(GLOBALS._hScripts); +} + +/** + * Unlocks the script data structures after use + */ +static void unlockScripts() { + globalUnlock(GLOBALS._hScripts); +} + +/** + * Returns the current value of a global variable + * + * @param lpszVarName Name of the variable + * @returns Current value + * @remarks Before using this method, you must call lockVar() to + * lock the global variablves for use. Then afterwards, you will + * need to remember to call UnlockVar() + */ +int32 varGetValue(const char *lpszVarName) { + LpMpalVar v = GLOBALS._lpmvVars; + + for (int i = 0; i < GLOBALS._nVars; v++, i++) + if (strcmp(lpszVarName, v->_lpszVarName) == 0) + return v->_dwVal; + + GLOBALS._mpalError = 1; + return 0; +} + +/** + * Sets the value of a MPAL global variable + * @param lpszVarName Name of the variable + * @param val Value to set + */ +void varSetValue(const char *lpszVarName, int32 val) { + LpMpalVar v = GLOBALS._lpmvVars; + + for (uint i = 0; i < GLOBALS._nVars; v++, i++) + if (strcmp(lpszVarName, v->_lpszVarName) == 0) { + v->_dwVal = val; + if (GLOBALS._lpiifCustom != NULL && strncmp(v->_lpszVarName, "Pattern.", 8) == 0) { + i = 0; + sscanf(v->_lpszVarName, "Pattern.%u", &i); + GLOBALS._lpiifCustom(i, val, -1); + } else if (GLOBALS._lpiifCustom != NULL && strncmp(v->_lpszVarName, "Status.", 7) == 0) { + i = 0; + sscanf(v->_lpszVarName,"Status.%u", &i); + GLOBALS._lpiifCustom(i, -1, val); + } + return; + } + + GLOBALS._mpalError = 1; + return; +} + +/** + * Find the index of a location within the location array. Remember to call LockLoc() beforehand. + * + * @param nLoc Location number to search for + * @returns Index, or -1 if the location is not present + * @remarks This function requires the location list to have + * first been locked with a call to LockLoc(). + */ +static int locGetOrderFromNum(uint32 nLoc) { + LpMpalLocation loc = GLOBALS._lpmlLocations; + + for (int i = 0; i < GLOBALS._nLocations; i++, loc++) + if (loc->_nObj == nLoc) + return i; + + return -1; +} + + +/** + * Find the index of a message within the messages array + * @param nMsg Message number to search for + * @returns Index, or -1 if the message is not present + * @remarks This function requires the message list to have + * first been locked with a call to LockMsg() + */ +static int msgGetOrderFromNum(uint32 nMsg) { + LpMpalMsg msg = GLOBALS._lpmmMsgs; + + for (int i = 0; i < GLOBALS._nMsgs; i++, msg++) { + if (msg->_wNum == nMsg) + return i; + } + + return -1; +} + +/** + * Find the index of an item within the items array + * @param nItem Item number to search for + * @returns Index, or -1 if the item is not present + * @remarks This function requires the item list to have + * first been locked with a call to LockItems() + */ +static int itemGetOrderFromNum(uint32 nItem) { + LpMpalItem item = GLOBALS._lpmiItems; + + for (int i = 0; i < GLOBALS._nItems; i++, item++) { + if (item->_nObj == nItem) + return i; + } + + return -1; +} + + +/** + * Find the index of a script within the scripts array + * @param nScript Script number to search for + * @returns Index, or -1 if the script is not present + * @remarks This function requires the script list to have + * first been locked with a call to LockScripts() + */ +static int scriptGetOrderFromNum(uint32 nScript) { + LpMpalScript script = GLOBALS._lpmsScripts; + + for (int i = 0; i < GLOBALS._nScripts; i++, script++) { + if (script->_nObj == nScript) + return i; + } + + return -1; +} + + +/** + * Find the index of a dialog within the dialogs array + * @param nDialog Dialog number to search for + * @returns Index, or -1 if the dialog is not present + * @remarks This function requires the dialog list to have + * first been locked with a call to LockDialogs() + */ +static int dialogGetOrderFromNum(uint32 nDialog) { + LpMpalDialog dialog = GLOBALS._lpmdDialogs; + + for (int i = 0; i < GLOBALS._nDialogs; i++, dialog++) { + if (dialog->_nObj == nDialog) + return i; + } + + return -1; +} + + +/** + * Duplicates a message + * @param nMsgOrd Index of the message inside the messages array + * @returns Pointer to the duplicated message. + * @remarks Remember to free the duplicated message when done with it. + */ +static char *DuplicateMessage(uint32 nMsgOrd) { + const char *origmsg; + char *clonemsg; + + if (nMsgOrd == (uint32)-1) + return NULL; + + origmsg = (const char *)globalLock(GLOBALS._lpmmMsgs[nMsgOrd]._hText); + + int j = 0; + while (origmsg[j] != '\0' || origmsg[j + 1] != '\0') + j++; + j += 2; + + clonemsg = (char *)globalAlloc(GMEM_FIXED | GMEM_ZEROINIT, j); + if (clonemsg == NULL) + return NULL; + + memcpy(clonemsg, origmsg, j); + globalUnlock(GLOBALS._lpmmMsgs[nMsgOrd]._hText); + + return clonemsg; +} + + +/** + * Duplicate a sentence of a dialog + * @param nDlgOrd Index of the dialog in the dialogs array + * @param nPeriod Sentence number to be duplicated. + * @returns Pointer to the duplicated phrase. Remember to free it + * when done with it. + */ +static char *duplicateDialogPeriod(uint32 nPeriod) { + const char *origmsg; + char *clonemsg; + LpMpalDialog dialog = GLOBALS._lpmdDialogs + GLOBALS._nExecutingDialog; + + for (int j = 0; dialog->_periods[j] != NULL; j++) { + if (dialog->_periodNums[j] == nPeriod) { + // Found the phrase, it should be duplicated + origmsg = (const char *)globalLock(dialog->_periods[j]); + + // Calculate the length and allocate memory + int i = 0; + while (origmsg[i] != '\0') + i++; + + clonemsg = (char *)globalAlloc(GMEM_FIXED | GMEM_ZEROINIT, i + 1); + if (clonemsg == NULL) + return NULL; + + memcpy(clonemsg, origmsg, i); + + globalUnlock(dialog->_periods[j]); + + return clonemsg; + } + } + + return NULL; +} + + +/** + * Load a resource from the MPR file + * + * @param dwId ID of the resource to load + * @returns Handle to the loaded resource + */ +MpalHandle resLoad(uint32 dwId) { + MpalHandle h; + char head[4]; + byte *temp, *buf; + + for (int i = 0; i < GLOBALS._nResources; i++) + if (GLOBALS._lpResources[i * 2] == dwId) { + GLOBALS._hMpr.seek(GLOBALS._lpResources[i * 2 + 1]); + uint32 nBytesRead = GLOBALS._hMpr.read(head, 4); + if (nBytesRead != 4) + return NULL; + if (head[0] != 'R' || head[1] != 'E' || head[2] != 'S' || head[3] != 'D') + return NULL; + + uint32 nSizeDecomp = GLOBALS._hMpr.readUint32LE(); + if (GLOBALS._hMpr.err()) + return NULL; + + uint32 nSizeComp = GLOBALS._hMpr.readUint32LE(); + if (GLOBALS._hMpr.err()) + return NULL; + + h = globalAllocate(GMEM_MOVEABLE | GMEM_ZEROINIT, nSizeDecomp + (nSizeDecomp / 1024) * 16); + buf = (byte *)globalLock(h); + temp = (byte *)globalAlloc(GMEM_FIXED | GMEM_ZEROINIT, nSizeComp); + + nBytesRead = GLOBALS._hMpr.read(temp, nSizeComp); + if (nBytesRead != nSizeComp) + return NULL; + + lzo1x_decompress(temp, nSizeComp, buf, &nBytesRead); + if (nBytesRead != nSizeDecomp) + return NULL; + + globalDestroy(temp); + globalUnlock(h); + return h; + } + + return NULL; +} + +static uint32 *getSelectList(uint32 i) { + uint32 *sl; + LpMpalDialog dialog = GLOBALS._lpmdDialogs + GLOBALS._nExecutingDialog; + + // Count how many are active selects + int num = 0; + for (int j = 0; dialog->_choice[i]._select[j]._dwData != 0; j++) { + if (dialog->_choice[i]._select[j]._curActive) + num++; + } + + // If there are 0, it's a mistake + if (num == 0) + return NULL; + + sl = (uint32 *)globalAlloc(GMEM_FIXED | GMEM_ZEROINIT, sizeof(uint32) * (num + 1)); + if (sl == NULL) + return NULL; + + // Copy all the data inside the active select list + int k = 0; + for (int j = 0; dialog->_choice[i]._select[j]._dwData != 0; j++) { + if (dialog->_choice[i]._select[j]._curActive) + sl[k++] = dialog->_choice[i]._select[j]._dwData; + } + + sl[k] = (uint32)NULL; + return sl; +} + +static uint32 *GetItemList(uint32 nLoc) { + uint32 *il; + LpMpalVar v = GLOBALS._lpmvVars; + + uint32 num = 0; + for (uint32 i = 0; i < GLOBALS._nVars; i++, v++) { + if (strncmp(v->_lpszVarName, "Location", 8) == 0 && v->_dwVal == nLoc) + num++; + } + + il = (uint32 *)globalAlloc(GMEM_FIXED | GMEM_ZEROINIT, sizeof(uint32) * (num + 1)); + if (il == NULL) + return NULL; + + v = GLOBALS._lpmvVars; + uint32 j = 0; + for (uint32 i = 0; i < GLOBALS._nVars; i++, v++) { + if (strncmp(v->_lpszVarName, "Location", 8) == 0 && v->_dwVal == nLoc) { + sscanf(v->_lpszVarName, "Location.%u", &il[j]); + j++; + } + } + + il[j] = (uint32)NULL; + return il; +} + +static LpItem getItemData(uint32 nOrdItem) { + LpMpalItem curitem = GLOBALS._lpmiItems + nOrdItem; + char *dat; + char *patlength; + + // Zeroing out the allocated memory is required!!! + LpItem ret = (LpItem)globalAlloc(GMEM_FIXED | GMEM_ZEROINIT, sizeof(Item)); + if (ret == NULL) + return NULL; + ret->_speed = 150; + + MpalHandle hDat = resLoad(curitem->_dwRes); + dat = (char *)globalLock(hDat); + + if (dat[0] == 'D' && dat[1] == 'A' && dat[2] == 'T') { + int i = dat[3]; // For version 1.0!! + dat += 4; + + if (i >= 0x10) { // From 1.0, there's a destination point for each object + ret->_destX = (int16)READ_LE_UINT16(dat); + ret->_destY = (int16)READ_LE_UINT16(dat + 2); + dat += 4; + } + + if (i >= 0x11) { // From 1.1, there's animation speed + ret->_speed = READ_LE_UINT16(dat); + dat += 2; + } else + ret->_speed = 150; + } + + ret->_numframe = *dat++; + ret->_numpattern = *dat++; + ret->_destZ = *dat++; + + // Upload the left & top co-ordinates of each frame + for (int i = 0; i < ret->_numframe; i++) { + ret->_frameslocations[i].left = (int16)READ_LE_UINT16(dat); + ret->_frameslocations[i].top = (int16)READ_LE_UINT16(dat + 2); + dat += 4; + } + + // Upload the size of each frame and calculate the right & bottom + for (int i = 0; i < ret->_numframe; i++) { + ret->_frameslocations[i].right = (int16)READ_LE_UINT16(dat) + ret->_frameslocations[i].left; + ret->_frameslocations[i].bottom = (int16)READ_LE_UINT16(dat + 2) + ret->_frameslocations[i].top; + dat += 4; + } + + // Upload the bounding boxes of each frame + for (int i = 0; i < ret->_numframe; i++) { + ret->_bbox[i].left = (int16)READ_LE_UINT16(dat); + ret->_bbox[i].top = (int16)READ_LE_UINT16(dat + 2); + ret->_bbox[i].right = (int16)READ_LE_UINT16(dat + 4); + ret->_bbox[i].bottom = (int16)READ_LE_UINT16(dat + 6); + dat += 8; + } + + // Load the animation pattern + patlength = dat; + dat += ret->_numpattern; + + for (int i = 1; i < ret->_numpattern; i++) { + for (int j = 0; j < patlength[i]; j++) + ret->_pattern[i][j] = dat[j]; + ret->_pattern[i][(int)patlength[i]] = 255; // Terminate pattern + dat += patlength[i]; + } + + // Upload the individual frames of animations + for (int i = 1; i < ret->_numframe; i++) { + uint32 dim = (uint32)(ret->_frameslocations[i].right - ret->_frameslocations[i].left) * + (uint32)(ret->_frameslocations[i].bottom - ret->_frameslocations[i].top); + ret->_frames[i] = (char *)globalAlloc(GMEM_FIXED, dim); + + if (ret->_frames[i] == NULL) + return NULL; + memcpy(ret->_frames[i], dat, dim); + dat += dim; + } + + // Check if we've got to the end of the file + int i = READ_LE_UINT16(dat); + if (i != 0xABCD) + return NULL; + + globalUnlock(hDat); + globalFree(hDat); + + return ret; +} + + +/** + * Thread that calls a custom function. It is used in scripts, so that each script + * function is executed without delaying the others. + * + * @param param pointer to a pointer to the structure that defines the call. + * @remarks The passed structure is freed when the process finishes. + */ +void CustomThread(CORO_PARAM, const void *param) { + CORO_BEGIN_CONTEXT; + LpCfCall p; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + _ctx->p = *(const LpCfCall *)param; + + CORO_INVOKE_4(GLOBALS._lplpFunctions[_ctx->p->_nCf], _ctx->p->_arg1, _ctx->p->_arg2, _ctx->p->_arg3, _ctx->p->_arg4); + + globalFree(_ctx->p); + + CORO_END_CODE; +} + + +/** + * Main process for running a script. + * + * @param param Pointer to a pointer to a structure containing the script data. + * @remarks The passed structure is freed when the process finishes. + */ +void ScriptThread(CORO_PARAM, const void *param) { + CORO_BEGIN_CONTEXT; + uint i, j, k; + uint32 dwStartTime; + uint32 dwCurTime; + uint32 dwId; + int numHandles; + LpCfCall p; + CORO_END_CONTEXT(_ctx); + + static uint32 cfHandles[MAX_COMMANDS_PER_MOMENT]; + LpMpalScript s = *(const LpMpalScript *)param; + + CORO_BEGIN_CODE(_ctx); + + _ctx->dwStartTime = g_vm->getTime(); + _ctx->numHandles = 0; + +// debugC(DEBUG_BASIC, kTonyDebugMPAL, "PlayScript(): Moments: %u\n", s->_nMoments); + for (_ctx->i = 0; _ctx->i < s->_nMoments; _ctx->i++) { + // Sleep for the required time + if (s->_moment[_ctx->i]._dwTime == -1) { + CORO_INVOKE_4(CoroScheduler.waitForMultipleObjects, _ctx->numHandles, cfHandles, true, CORO_INFINITE); + _ctx->dwStartTime = g_vm->getTime(); + } else { + _ctx->dwCurTime = g_vm->getTime(); + if (_ctx->dwCurTime < _ctx->dwStartTime + (s->_moment[_ctx->i]._dwTime * 100)) { + // debugC(DEBUG_BASIC, kTonyDebugMPAL, "PlayScript(): Sleeping %lums\n",_ctx->dwStartTime + (s->_moment[_ctx->i]._dwTime*100) - _ctx->dwCurTime); + CORO_INVOKE_1(CoroScheduler.sleep, _ctx->dwStartTime + (s->_moment[_ctx->i]._dwTime * 100) - _ctx->dwCurTime); + } + } + + _ctx->numHandles = 0; + for (_ctx->j = 0; _ctx->j < s->_moment[_ctx->i]._nCmds; _ctx->j++) { + _ctx->k = s->_moment[_ctx->i]._cmdNum[_ctx->j]; + + if (s->_command[_ctx->k]._type == 1) { + _ctx->p = (LpCfCall)globalAlloc(GMEM_FIXED, sizeof(CfCall)); + if (_ctx->p == NULL) { + GLOBALS._mpalError = 1; + + CORO_KILL_SELF(); + return; + } + + _ctx->p->_nCf = s->_command[_ctx->k]._nCf; + _ctx->p->_arg1 = s->_command[_ctx->k]._arg1; + _ctx->p->_arg2 = s->_command[_ctx->k]._arg2; + _ctx->p->_arg3 = s->_command[_ctx->k]._arg3; + _ctx->p->_arg4 = s->_command[_ctx->k]._arg4; + + // !!! New process management + if ((cfHandles[_ctx->numHandles++] = CoroScheduler.createProcess(CustomThread, &_ctx->p, sizeof(LpCfCall))) == 0) { + GLOBALS._mpalError = 1; + + CORO_KILL_SELF(); + return; + } + } else if (s->_command[_ctx->k]._type == 2) { + lockVar(); + varSetValue( + s->_command[_ctx->k]._lpszVarName, + evaluateExpression(s->_command[_ctx->k]._expr) + ); + unlockVar(); + + } else { + GLOBALS._mpalError = 1; + globalFree(s); + + CORO_KILL_SELF(); + return; + } + + // WORKAROUND: Wait for events to pulse. + CORO_SLEEP(1); + } + } + + globalFree(s); + + CORO_KILL_SELF(); + + CORO_END_CODE; +} + + +/** + * Thread that performs an action on an item. the thread always executes the action, + * so it should create a new item in which the action is the one required. + * Furthermore, the expression is not checked, but it is always performed the action. + * + * @param param Pointer to a pointer to a structure containing the action. + */ +void ActionThread(CORO_PARAM, const void *param) { + // COROUTINE + CORO_BEGIN_CONTEXT; + int j, k; + LpMpalItem item; + + ~CoroContextTag() { + if (item) + globalDestroy(item); + } + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + // The ActionThread owns the data block pointed to, so we need to make sure it's + // freed when the process exits + _ctx->item = *(const LpMpalItem *)param; + + GLOBALS._mpalError = 0; + for (_ctx->j = 0; _ctx->j < _ctx->item->_action[_ctx->item->_dwRes]._nCmds; _ctx->j++) { + _ctx->k = _ctx->item->_action[_ctx->item->_dwRes]._cmdNum[_ctx->j]; + + if (_ctx->item->_command[_ctx->k]._type == 1) { + // Custom function + debugC(DEBUG_DETAILED, kTonyDebugActions, "Action Process %d Call=%s params=%d,%d,%d,%d", + CoroScheduler.getCurrentPID(), GLOBALS._lplpFunctionStrings[_ctx->item->_command[_ctx->k]._nCf].c_str(), + _ctx->item->_command[_ctx->k]._arg1, _ctx->item->_command[_ctx->k]._arg2, + _ctx->item->_command[_ctx->k]._arg3, _ctx->item->_command[_ctx->k]._arg4 + ); + + CORO_INVOKE_4(GLOBALS._lplpFunctions[_ctx->item->_command[_ctx->k]._nCf], + _ctx->item->_command[_ctx->k]._arg1, + _ctx->item->_command[_ctx->k]._arg2, + _ctx->item->_command[_ctx->k]._arg3, + _ctx->item->_command[_ctx->k]._arg4 + + ); + } else if (_ctx->item->_command[_ctx->k]._type == 2) { + // Variable assign + debugC(DEBUG_DETAILED, kTonyDebugActions, "Action Process %d Variable=%s", + CoroScheduler.getCurrentPID(), _ctx->item->_command[_ctx->k]._lpszVarName); + + lockVar(); + varSetValue(_ctx->item->_command[_ctx->k]._lpszVarName, evaluateExpression(_ctx->item->_command[_ctx->k]._expr)); + unlockVar(); + + } else { + GLOBALS._mpalError = 1; + break; + } + + // WORKAROUND: Wait for events to pulse. + CORO_SLEEP(1); + } + + globalDestroy(_ctx->item); + _ctx->item = NULL; + + debugC(DEBUG_DETAILED, kTonyDebugActions, "Action Process %d ended", CoroScheduler.getCurrentPID()); + + CORO_END_CODE; +} + +/** + * This thread monitors a created action to detect when it ends. + * @remarks Since actions can spawn sub-actions, this needs to be a + * separate thread to determine when the outer action is done + */ +void ShutUpActionThread(CORO_PARAM, const void *param) { + // COROUTINE + CORO_BEGIN_CONTEXT; + int slotNumber; + CORO_END_CONTEXT(_ctx); + + uint32 pid = *(const uint32 *)param; + + CORO_BEGIN_CODE(_ctx); + + CORO_INVOKE_2(CoroScheduler.waitForSingleObject, pid, CORO_INFINITE); + + GLOBALS._bExecutingAction = false; + + if (g_vm->_initialLoadSlotNumber != -1) { + _ctx->slotNumber = g_vm->_initialLoadSlotNumber; + g_vm->_initialLoadSlotNumber = -1; + + CORO_INVOKE_1(g_vm->loadState, _ctx->slotNumber); + } + + + CORO_END_CODE; +} + + +/** + * Polls one location (starting point of a process) + * + * @param param Pointer to an index in the array of polling locations. + */ +void LocationPollThread(CORO_PARAM, const void *param) { + typedef struct { + uint32 _nItem, _nAction; + + uint16 _wTime; + byte _perc; + MpalHandle _when; + byte _nCmds; + uint16 _cmdNum[MAX_COMMANDS_PER_ACTION]; + uint32 _dwLastTime; + } MYACTION; + + typedef struct { + uint32 _nItem; + uint32 _hThread; + } MYTHREAD; + + CORO_BEGIN_CONTEXT; + uint32 *il; + int i, j, k; + int numitems; + int nRealItems; + LpMpalItem curItem, newItem; + int nIdleActions; + uint32 curTime; + uint32 dwSleepTime; + uint32 dwId; + int ord; + bool delayExpired; + bool expired; + + MYACTION *myActions; + MYTHREAD *myThreads; + + ~CoroContextTag() { + // Free data blocks + if (myThreads) + globalDestroy(myThreads); + if (myActions) + globalDestroy(myActions); + } + CORO_END_CONTEXT(_ctx); + + uint32 id = *((const uint32 *)param); + + CORO_BEGIN_CODE(_ctx); + + // Initialize data pointers + _ctx->myActions = NULL; + _ctx->myThreads = NULL; + + // To begin with, we need to request the item list from the location + _ctx->il = mpalQueryItemList(GLOBALS._nPollingLocations[id]); + + // Count the items + for (_ctx->numitems = 0; _ctx->il[_ctx->numitems] != 0; _ctx->numitems++) + ; + + // We look for items without idle actions, and eliminate them from the list + lockItems(); + _ctx->nIdleActions = 0; + _ctx->nRealItems = 0; + for (_ctx->i = 0; _ctx->i < _ctx->numitems; _ctx->i++) { + _ctx->ord = itemGetOrderFromNum(_ctx->il[_ctx->i]); + + if (_ctx->ord == -1) + continue; + + _ctx->curItem = GLOBALS._lpmiItems + _ctx->ord; + + _ctx->k = 0; + for (_ctx->j = 0; _ctx->j < _ctx->curItem->_nActions; _ctx->j++) { + if (_ctx->curItem->_action[_ctx->j]._num == 0xFF) + _ctx->k++; + } + + _ctx->nIdleActions += _ctx->k; + + if (_ctx->k == 0) + // We can remove this item from the list + _ctx->il[_ctx->i] = (uint32)NULL; + else + _ctx->nRealItems++; + } + unlockItems(); + + // If there is nothing left, we can exit + if (_ctx->nRealItems == 0) { + globalDestroy(_ctx->il); + CORO_KILL_SELF(); + return; + } + + _ctx->myThreads = (MYTHREAD *)globalAlloc(GMEM_FIXED | GMEM_ZEROINIT, _ctx->nRealItems * sizeof(MYTHREAD)); + if (_ctx->myThreads == NULL) { + globalDestroy(_ctx->il); + CORO_KILL_SELF(); + return; + } + + + // We have established that there is at least one item that contains idle actions. + // Now we created the mirrored copies of the idle actions. + _ctx->myActions = (MYACTION *)globalAlloc(GMEM_FIXED | GMEM_ZEROINIT, _ctx->nIdleActions * sizeof(MYACTION)); + if (_ctx->myActions == NULL) { + globalDestroy(_ctx->myThreads); + globalDestroy(_ctx->il); + CORO_KILL_SELF(); + return; + } + + lockItems(); + _ctx->k = 0; + + for (_ctx->i = 0; _ctx->i < _ctx->numitems; _ctx->i++) { + if (_ctx->il[_ctx->i] == 0) + continue; + + _ctx->curItem = GLOBALS._lpmiItems + itemGetOrderFromNum(_ctx->il[_ctx->i]); + + for (_ctx->j = 0; _ctx->j < _ctx->curItem->_nActions; _ctx->j++) { + if (_ctx->curItem->_action[_ctx->j]._num == 0xFF) { + _ctx->myActions[_ctx->k]._nItem = _ctx->il[_ctx->i]; + _ctx->myActions[_ctx->k]._nAction = _ctx->j; + + _ctx->myActions[_ctx->k]._wTime = _ctx->curItem->_action[_ctx->j]._wTime; + _ctx->myActions[_ctx->k]._perc = _ctx->curItem->_action[_ctx->j]._perc; + _ctx->myActions[_ctx->k]._when = _ctx->curItem->_action[_ctx->j]._when; + _ctx->myActions[_ctx->k]._nCmds = _ctx->curItem->_action[_ctx->j]._nCmds; + memcpy(_ctx->myActions[_ctx->k]._cmdNum, _ctx->curItem->_action[_ctx->j]._cmdNum, + MAX_COMMANDS_PER_ACTION * sizeof(uint16)); + + _ctx->myActions[_ctx->k]._dwLastTime = g_vm->getTime(); + _ctx->k++; + } + } + } + + unlockItems(); + + // We don't need the item list anymore + globalDestroy(_ctx->il); + + + // Here's the main loop + while (1) { + // Searching for idle actions requiring time to execute + _ctx->curTime = g_vm->getTime(); + _ctx->dwSleepTime = (uint32)-1L; + + for (_ctx->k = 0;_ctx->k<_ctx->nIdleActions;_ctx->k++) { + if (_ctx->curTime >= _ctx->myActions[_ctx->k]._dwLastTime + _ctx->myActions[_ctx->k]._wTime) { + _ctx->dwSleepTime = 0; + break; + } else + _ctx->dwSleepTime = MIN(_ctx->dwSleepTime, _ctx->myActions[_ctx->k]._dwLastTime + _ctx->myActions[_ctx->k]._wTime - _ctx->curTime); + } + + // We fall alseep, but always checking that the event is set when prompted for closure + CORO_INVOKE_3(CoroScheduler.waitForSingleObject, GLOBALS._hEndPollingLocations[id], _ctx->dwSleepTime, &_ctx->expired); + + //if (_ctx->k == WAIT_OBJECT_0) + if (!_ctx->expired) + break; + + for (_ctx->i = 0; _ctx->i < _ctx->nRealItems; _ctx->i++) { + if (_ctx->myThreads[_ctx->i]._nItem != 0) { + CORO_INVOKE_3(CoroScheduler.waitForSingleObject, _ctx->myThreads[_ctx->i]._hThread, 0, &_ctx->delayExpired); + + // if result == WAIT_OBJECT_0) + if (!_ctx->delayExpired) + _ctx->myThreads[_ctx->i]._nItem = 0; + } + } + + _ctx->curTime = g_vm->getTime(); + + // Loop through all the necessary idle actions + for (_ctx->k = 0; _ctx->k < _ctx->nIdleActions; _ctx->k++) { + if (_ctx->curTime >= _ctx->myActions[_ctx->k]._dwLastTime + _ctx->myActions[_ctx->k]._wTime) { + _ctx->myActions[_ctx->k]._dwLastTime += _ctx->myActions[_ctx->k]._wTime; + + // It's time to check to see if fortune is on the side of the idle action + byte randomVal = (byte)g_vm->_randomSource.getRandomNumber(99); + if (randomVal < _ctx->myActions[_ctx->k]._perc) { + // Check if there is an action running on the item + if ((GLOBALS._bExecutingAction) && (GLOBALS._nExecutingAction == _ctx->myActions[_ctx->k]._nItem)) + continue; + + // Check to see if there already another idle funning running on the item + for (_ctx->i = 0; _ctx->i < _ctx->nRealItems; _ctx->i++) { + if (_ctx->myThreads[_ctx->i]._nItem == _ctx->myActions[_ctx->k]._nItem) + break; + } + + if (_ctx->i < _ctx->nRealItems) + continue; + + // Ok, we are the only ones :) + lockItems(); + _ctx->curItem = GLOBALS._lpmiItems + itemGetOrderFromNum(_ctx->myActions[_ctx->k]._nItem); + + // Check if there is a WhenExecute expression + _ctx->j=_ctx->myActions[_ctx->k]._nAction; + if (_ctx->curItem->_action[_ctx->j]._when != NULL) { + if (!evaluateExpression(_ctx->curItem->_action[_ctx->j]._when)) { + unlockItems(); + continue; + } + } + + // Ok, we can perform the action. For convenience, we do it in a new process + _ctx->newItem = (LpMpalItem)globalAlloc(GMEM_FIXED | GMEM_ZEROINIT, sizeof(MpalItem)); + if (_ctx->newItem == false) { + globalDestroy(_ctx->myThreads); + globalDestroy(_ctx->myActions); + + CORO_KILL_SELF(); + return; + } + + memcpy(_ctx->newItem,_ctx->curItem, sizeof(MpalItem)); + unlockItems(); + + // We copy the action in #0 + //_ctx->newItem->Action[0].nCmds = _ctx->curItem->Action[_ctx->j].nCmds; + //memcpy(_ctx->newItem->Action[0].CmdNum,_ctx->curItem->Action[_ctx->j].CmdNum,_ctx->newItem->Action[0].nCmds*sizeof(_ctx->newItem->Action[0].CmdNum[0])); + _ctx->newItem->_dwRes = _ctx->j; + + // We will create an action, and will provide the necessary details + for (_ctx->i = 0; _ctx->i < _ctx->nRealItems; _ctx->i++) { + if (_ctx->myThreads[_ctx->i]._nItem == 0) + break; + } + + _ctx->myThreads[_ctx->i]._nItem = _ctx->myActions[_ctx->k]._nItem; + + // Create the process + if ((_ctx->myThreads[_ctx->i]._hThread = CoroScheduler.createProcess(ActionThread, &_ctx->newItem, sizeof(LpMpalItem))) == CORO_INVALID_PID_VALUE) { + //if ((_ctx->myThreads[_ctx->i]._hThread = (void*)_beginthread(ActionThread, 10240, (void *)_ctx->newItem)) == (void*)-1) + globalDestroy(_ctx->newItem); + globalDestroy(_ctx->myThreads); + globalDestroy(_ctx->myActions); + + CORO_KILL_SELF(); + return; + } + + // Skip all idle actions of the same item + } + } + } + } + + + // Set idle skip on + CORO_INVOKE_4(GLOBALS._lplpFunctions[200], 0, 0, 0, 0); + + for (_ctx->i = 0; _ctx->i < _ctx->nRealItems; _ctx->i++) { + if (_ctx->myThreads[_ctx->i]._nItem != 0) { + CORO_INVOKE_3(CoroScheduler.waitForSingleObject, _ctx->myThreads[_ctx->i]._hThread, 5000, &_ctx->delayExpired); + + //if (result != WAIT_OBJECT_0) + //if (_ctx->delayExpired) + // TerminateThread(_ctx->MyThreads[_ctx->i].hThread, 0); + + CoroScheduler.killMatchingProcess(_ctx->myThreads[_ctx->i]._hThread); + } + } + + // Set idle skip off + CORO_INVOKE_4(GLOBALS._lplpFunctions[201], 0, 0, 0, 0); + + CORO_END_CODE; +} + + +/** + * Wait for the end of the dialog execution thread, and then restore global + * variables indicating that the dialogue has finished. + * + * @param param Pointer to a handle to the dialog + * @remarks This additional process is used, instead of clearing variables + * within the same dialog thread, because due to the recursive nature of a dialog, + * it would be difficult to know within it when the dialog is actually ending. + */ +void ShutUpDialogThread(CORO_PARAM, const void *param) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + uint32 pid = *(const uint32 *)param; + + CORO_BEGIN_CODE(_ctx); + + CORO_INVOKE_2(CoroScheduler.waitForSingleObject, pid, CORO_INFINITE); + + GLOBALS._bExecutingDialog = false; + GLOBALS._nExecutingDialog = 0; + GLOBALS._nExecutingChoice = 0; + + CoroScheduler.setEvent(GLOBALS._hAskChoice); + + CORO_KILL_SELF(); + + CORO_END_CODE; +} + +void doChoice(CORO_PARAM, uint32 nChoice); + + +/** + * Executes a group of the current dialog. Can 'be the Starting point of a process. + * @parm nGroup Number of the group to perform + */ +void GroupThread(CORO_PARAM, const void *param) { + CORO_BEGIN_CONTEXT; + LpMpalDialog dialog; + int i, j, k; + int type; + CORO_END_CONTEXT(_ctx); + + uint32 nGroup = *(const uint32 *)param; + + CORO_BEGIN_CODE(_ctx); + + // Lock the _ctx->dialog + lockDialogs(); + + // Find the pointer to the current _ctx->dialog + _ctx->dialog = GLOBALS._lpmdDialogs + GLOBALS._nExecutingDialog; + + // Search inside the group requesting the _ctx->dialog + for (_ctx->i = 0; _ctx->dialog->_group[_ctx->i]._num != 0; _ctx->i++) { + if (_ctx->dialog->_group[_ctx->i]._num == nGroup) { + // Cycle through executing the commands of the group + for (_ctx->j = 0; _ctx->j < _ctx->dialog->_group[_ctx->i]._nCmds; _ctx->j++) { + _ctx->k = _ctx->dialog->_group[_ctx->i]._cmdNum[_ctx->j]; + + _ctx->type = _ctx->dialog->_command[_ctx->k]._type; + if (_ctx->type == 1) { + // Call custom function + CORO_INVOKE_4(GLOBALS._lplpFunctions[_ctx->dialog->_command[_ctx->k]._nCf], + _ctx->dialog->_command[_ctx->k]._arg1, + _ctx->dialog->_command[_ctx->k]._arg2, + _ctx->dialog->_command[_ctx->k]._arg3, + _ctx->dialog->_command[_ctx->k]._arg4 + ); + + } else if (_ctx->type == 2) { + // Set a variable + lockVar(); + varSetValue(_ctx->dialog->_command[_ctx->k]._lpszVarName, evaluateExpression(_ctx->dialog->_command[_ctx->k]._expr)); + unlockVar(); + + } else if (_ctx->type == 3) { + // DoChoice: call the chosen function + CORO_INVOKE_1(doChoice, (uint32)_ctx->dialog->_command[_ctx->k]._nChoice); + + } else { + GLOBALS._mpalError = 1; + unlockDialogs(); + + CORO_KILL_SELF(); + return; + } + + // WORKAROUND: Wait for events to pulse. + CORO_SLEEP(1); + } + + // The gruop is finished, so we can return to the calling function. + // If the group was the first called, then the process will automatically + // end. Otherwise it returns to the caller method + + return; + } + } + + // If we are here, it means that we have not found the requested group + GLOBALS._mpalError = 1; + unlockDialogs(); + + CORO_KILL_SELF(); + + CORO_END_CODE; +} + + +/** + * Make a choice in the current dialog. + * + * @param nChoice Number of choice to perform + */ +void doChoice(CORO_PARAM, uint32 nChoice) { + CORO_BEGIN_CONTEXT; + LpMpalDialog dialog; + int i, j, k; + uint32 nGroup; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + // Lock the dialogs + lockDialogs(); + + // Get a pointer to the current dialog + _ctx->dialog = GLOBALS._lpmdDialogs + GLOBALS._nExecutingDialog; + + // Search the choice between those required in the dialog + for (_ctx->i = 0; _ctx->dialog->_choice[_ctx->i]._nChoice != 0; _ctx->i++) { + if (_ctx->dialog->_choice[_ctx->i]._nChoice == nChoice) + break; + } + + // If nothing has been found, exit with an error + if (_ctx->dialog->_choice[_ctx->i]._nChoice == 0) { + // If we're here, we did not find the required choice + GLOBALS._mpalError = 1; + unlockDialogs(); + + CORO_KILL_SELF(); + return; + } + + // We've found the requested choice. Remember what in global variables + GLOBALS._nExecutingChoice = _ctx->i; + + while (1) { + GLOBALS._nExecutingChoice = _ctx->i; + + _ctx->k = 0; + // Calculate the expression of each selection, to see if they're active or inactive + for (_ctx->j = 0; _ctx->dialog->_choice[_ctx->i]._select[_ctx->j]._dwData != 0; _ctx->j++) { + if (_ctx->dialog->_choice[_ctx->i]._select[_ctx->j]._when == NULL) { + _ctx->dialog->_choice[_ctx->i]._select[_ctx->j]._curActive = 1; + _ctx->k++; + } else if (evaluateExpression(_ctx->dialog->_choice[_ctx->i]._select[_ctx->j]._when)) { + _ctx->dialog->_choice[_ctx->i]._select[_ctx->j]._curActive = 1; + _ctx->k++; + } else + _ctx->dialog->_choice[_ctx->i]._select[_ctx->j]._curActive = 0; + } + + // If there are no choices activated, then the dialog is finished. + if (_ctx->k == 0) { + unlockDialogs(); + break; + } + + // There are choices available to the user, so wait for them to make one + CoroScheduler.resetEvent(GLOBALS._hDoneChoice); + CoroScheduler.setEvent(GLOBALS._hAskChoice); + CORO_INVOKE_2(CoroScheduler.waitForSingleObject, GLOBALS._hDoneChoice, CORO_INFINITE); + + // Now that the choice has been made, we can run the groups associated with the choice tbontbtitq + _ctx->j = GLOBALS._nSelectedChoice; + for (_ctx->k = 0; _ctx->dialog->_choice[_ctx->i]._select[_ctx->j]._wPlayGroup[_ctx->k] != 0; _ctx->k++) { + _ctx->nGroup = _ctx->dialog->_choice[_ctx->i]._select[_ctx->j]._wPlayGroup[_ctx->k]; + CORO_INVOKE_1(GroupThread, &_ctx->nGroup); + } + + // Control attribute + if (_ctx->dialog->_choice[_ctx->i]._select[_ctx->j]._attr & (1 << 0)) { + // Bit 0 set: the end of the choice + unlockDialogs(); + break; + } + + if (_ctx->dialog->_choice[_ctx->i]._select[_ctx->j]._attr & (1 << 1)) { + // Bit 1 set: the end of the dialog + unlockDialogs(); + + CORO_KILL_SELF(); + return; + } + + // End of choic ewithout attributes. We must do it again + } + + // If we're here, we found an end choice. Return to the caller group + return; + + CORO_END_CODE; +} + + +/** + * Perform an action on a certain item. + * + * @param nAction Action number + * @param ordItem Index of the item in the items list + * @param dwParam Any parameter for the action. + * @returns Id of the process that was launched to perform the action, or + * CORO_INVALID_PID_VALUE if the action was not defined, or the item was inactive. + * @remarks You can get the index of an item from its number by using + * the itemGetOrderFromNum() function. The items list must first be locked + * by calling LockItem(). + */ +static uint32 doAction(uint32 nAction, uint32 ordItem, uint32 dwParam) { + LpMpalItem item = GLOBALS._lpmiItems; + LpMpalItem newitem; + + item+=ordItem; + Common::String buf = Common::String::format("Status.%u", item->_nObj); + if (varGetValue(buf.c_str()) <= 0) + return CORO_INVALID_PID_VALUE; + + for (int i = 0; i < item->_nActions; i++) { + if (item->_action[i]._num != nAction) + continue; + + if (item->_action[i]._wParm != dwParam) + continue; + + if (item->_action[i]._when != NULL) { + if (!evaluateExpression(item->_action[i]._when)) + continue; + } + + // Now we find the right action to be performed + // Duplicate the item and copy the current action in #i into #0 + newitem = (LpMpalItem)globalAlloc(GMEM_FIXED | GMEM_ZEROINIT, sizeof(MpalItem)); + if (newitem == NULL) + return CORO_INVALID_PID_VALUE; + + // In the new version number of the action in writing dwRes + Common::copy((byte *)item, (byte *)item + sizeof(MpalItem), (byte *)newitem); + + //newitem->_action[0]._nCmds=item->_action[i]._nCmds; + //memcpy(newitem->_action[0]._cmdNum, item->_action[i]._cmdNum, newitem->Action[0].nCmds * sizeof(newitem->_action[0]._cmdNum[0])); + + newitem->_dwRes = i; + + // And finally we can laucnh the process that will execute the action, + // and a second process to free up the memory when the action is finished. + + // !!! New process management + uint32 h; + if ((h = CoroScheduler.createProcess(ActionThread, &newitem, sizeof(LpMpalItem))) == CORO_INVALID_PID_VALUE) + return CORO_INVALID_PID_VALUE; + + if (CoroScheduler.createProcess(ShutUpActionThread, &h, sizeof(uint32)) == CORO_INVALID_PID_VALUE) + return CORO_INVALID_PID_VALUE; + + GLOBALS._nExecutingAction = item->_nObj; + GLOBALS._bExecutingAction = true; + + return h; + } + + return CORO_INVALID_PID_VALUE; +} + +/** + * Shows a dialog in a separate process. + * + * @param nDlgOrd The index of the dialog in the dialog list + * @param nGroup Number of the group to perform + * @returns The process Id of the process running the dialog + * or CORO_INVALID_PID_VALUE on error + * @remarks The dialogue runs in a thread created on purpose, + * so that must inform through an event and when 'necessary to you make a choice. + * The data on the choices may be obtained through various queries. + */ +static uint32 doDialog(uint32 nDlgOrd, uint32 nGroup) { + // Store the running dialog in a global variable + GLOBALS._nExecutingDialog = nDlgOrd; + + // Enables the flag to indicate that there is' a running dialogue + GLOBALS._bExecutingDialog = true; + + CoroScheduler.resetEvent(GLOBALS._hAskChoice); + CoroScheduler.resetEvent(GLOBALS._hDoneChoice); + + // Create a thread that performs the dialogue group + + // Create the process + uint32 h; + if ((h = CoroScheduler.createProcess(GroupThread, &nGroup, sizeof(uint32))) == CORO_INVALID_PID_VALUE) + return CORO_INVALID_PID_VALUE; + + // Create a thread that waits until the end of the dialog process, and will restore the global variables + if (CoroScheduler.createProcess(ShutUpDialogThread, &h, sizeof(uint32)) == CORO_INVALID_PID_VALUE) { + // Something went wrong, so kill the previously started dialog process + CoroScheduler.killMatchingProcess(h); + return CORO_INVALID_PID_VALUE; + } + + return h; +} + + +/** + * Takes note of the selection chosen by the user, and warns the process that was running + * the box that it can continue. + * + * @param nChoice Number of choice that was in progress + * @param dwData Since combined with select selection + * @returns True if everything is OK, false on failure + */ +bool doSelection(uint32 i, uint32 dwData) { + LpMpalDialog dialog = GLOBALS._lpmdDialogs + GLOBALS._nExecutingDialog; + int j; + + for (j = 0; dialog->_choice[i]._select[j]._dwData != 0; j++) { + if (dialog->_choice[i]._select[j]._dwData == dwData && dialog->_choice[i]._select[j]._curActive != 0) + break; + } + + if (dialog->_choice[i]._select[j]._dwData == 0) + return false; + + GLOBALS._nSelectedChoice = j; + CoroScheduler.setEvent(GLOBALS._hDoneChoice); + return true; +} + + +/** + * @defgroup Exported functions + */ +//@{ + +/** + * Initializes the MPAL library and opens the .MPC file, which will be used for all queries. + * + * @param lpszMpcFileName Name of the MPC file + * @param lpszMprFileName Name of the MPR file + * @param lplpcfArray Array of pointers to custom functions. + * @returns True if everything is OK, false on failure + */ +bool mpalInit(const char *lpszMpcFileName, const char *lpszMprFileName, + LPLPCUSTOMFUNCTION lplpcfArray, Common::String *lpcfStrings) { + byte buf[5]; + byte *cmpbuf; + + // Save the array of custom functions + GLOBALS._lplpFunctions = lplpcfArray; + GLOBALS._lplpFunctionStrings = lpcfStrings; + + // OPen the MPC file for reading + Common::File hMpc; + if (!hMpc.open(lpszMpcFileName)) + return false; + + // Read and check the header + uint32 nBytesRead = hMpc.read(buf, 5); + if (nBytesRead != 5) + return false; + + if (buf[0] != 'M' || buf[1] != 'P' || buf[2] != 'C' || buf[3] != 0x20) + return false; + + bool bCompress = buf[4]; + + // Reads the size of the uncompressed file, and allocate memory + uint32 dwSizeDecomp = hMpc.readUint32LE(); + if (hMpc.err()) + return false; + + byte *lpMpcImage = (byte *)globalAlloc(GMEM_FIXED, dwSizeDecomp + 16); + if (lpMpcImage == NULL) + return false; + + if (bCompress) { + // Get the compressed size and read the data in + uint32 dwSizeComp = hMpc.readUint32LE(); + if (hMpc.err()) + return false; + + cmpbuf = (byte *)globalAlloc(GMEM_FIXED, dwSizeComp); + if (cmpbuf == NULL) + return false; + + nBytesRead = hMpc.read(cmpbuf, dwSizeComp); + if (nBytesRead != dwSizeComp) + return false; + + // Decompress the data + lzo1x_decompress(cmpbuf, dwSizeComp, lpMpcImage, &nBytesRead); + if (nBytesRead != dwSizeDecomp) + return false; + + globalDestroy(cmpbuf); + } else { + // If the file is not compressed, we directly read in the data + nBytesRead = hMpc.read(lpMpcImage, dwSizeDecomp); + if (nBytesRead != dwSizeDecomp) + return false; + } + + // Close the file + hMpc.close(); + + // Process the data + if (parseMpc(lpMpcImage) == false) + return false; + + globalDestroy(lpMpcImage); + + // Open the MPR file + if (!GLOBALS._hMpr.open(lpszMprFileName)) + return false; + + // Seek to the end of the file to read overall information + GLOBALS._hMpr.seek(-12, SEEK_END); + + uint32 dwSizeComp = GLOBALS._hMpr.readUint32LE(); + if (GLOBALS._hMpr.err()) + return false; + + GLOBALS._nResources = GLOBALS._hMpr.readUint32LE(); + if (GLOBALS._hMpr.err()) + return false; + + nBytesRead = GLOBALS._hMpr.read(buf, 4); + if (GLOBALS._hMpr.err()) + return false; + + if (buf[0] !='E' || buf[1] != 'N' || buf[2] != 'D' || buf[3] != '0') + return false; + + // Move to the start of the resources header + GLOBALS._hMpr.seek(-(12 + (int)dwSizeComp), SEEK_END); + + GLOBALS._lpResources = (uint32 *)globalAlloc(GMEM_FIXED | GMEM_ZEROINIT, GLOBALS._nResources * 8); + if (GLOBALS._lpResources == NULL) + return false; + + cmpbuf = (byte *)globalAlloc(GMEM_FIXED | GMEM_ZEROINIT, dwSizeComp); + if (cmpbuf == NULL) + return false; + + nBytesRead = GLOBALS._hMpr.read(cmpbuf, dwSizeComp); + if (nBytesRead != dwSizeComp) + return false; + + lzo1x_decompress((const byte *)cmpbuf, dwSizeComp, (byte *)GLOBALS._lpResources, (uint32 *)&nBytesRead); + if (nBytesRead != (uint32)GLOBALS._nResources * 8) + return false; + + globalDestroy(cmpbuf); + + // Reset back to the start of the file, leaving it open + GLOBALS._hMpr.seek(0, SEEK_SET); + + // There is no action or dialog running by default + GLOBALS._bExecutingAction = false; + GLOBALS._bExecutingDialog = false; + + // There's no polling location + Common::fill(GLOBALS._nPollingLocations, GLOBALS._nPollingLocations + MAXPOLLINGLOCATIONS, 0); + + // Create the event that will be used to co-ordinate making choices and choices finishing + GLOBALS._hAskChoice = CoroScheduler.createEvent(true, false); + GLOBALS._hDoneChoice = CoroScheduler.createEvent(true, false); + + return true; +} + +/** + * Frees resources allocated by the MPAL subsystem + */ +void mpalFree() { + // Free the resource list + globalDestroy(GLOBALS._lpResources); +} + +/** + * This is a general function to communicate with the library, to request information + * about what is in the .MPC file + * + * @param wQueryType Type of query. The list is in the QueryTypes enum. + * @returns 4 bytes depending on the type of query + * @remarks This is the specialised version of the original single mpalQuery + * method that returns numeric results. + */ +uint32 mpalQueryDWORD(uint16 wQueryType, ...) { + Common::String buf; + uint32 dwRet = 0; + char *n; + + va_list v; + va_start(v, wQueryType); + + GLOBALS._mpalError = OK; + + if (wQueryType == MPQ_VERSION) { + + /* + * uint32 mpalQuery(MPQ_VERSION); + */ + dwRet = HEX_VERSION; + + } else if (wQueryType == MPQ_GLOBAL_VAR) { + /* + * uint32 mpalQuery(MPQ_GLOBAL_VAR, char * lpszVarName); + */ + lockVar(); + dwRet = (uint32)varGetValue(GETARG(char *)); + unlockVar(); + + } else if (wQueryType == MPQ_MESSAGE) { + /* + * char * mpalQuery(MPQ_MESSAGE, uint32 nMsg); + */ + error("mpalQuery(MPQ_MESSAGE, uint32 nMsg) used incorrect method variant"); + + + } else if (wQueryType == MPQ_ITEM_PATTERN) { + /* + * uint32 mpalQuery(MPQ_ITEM_PATTERN, uint32 nItem); + */ + lockVar(); + buf = Common::String::format("Pattern.%u", GETARG(uint32)); + dwRet = (uint32)varGetValue(buf.c_str()); + unlockVar(); + + } else if (wQueryType == MPQ_LOCATION_SIZE) { + /* + * uint32 mpalQuery(MPQ_LOCATION_SIZE, uint32 nLoc, uint32 dwCoord); + */ + lockLocations(); + int x = locGetOrderFromNum(GETARG(uint32)); + int y = GETARG(uint32); + if (x != -1) { + if (y == MPQ_X) + dwRet = GLOBALS._lpmlLocations[x]._dwXlen; + else if (y == MPQ_Y) + dwRet = GLOBALS._lpmlLocations[x]._dwYlen; + else + GLOBALS._mpalError = 1; + } else + GLOBALS._mpalError = 1; + + unlockLocations(); + + } else if (wQueryType == MPQ_LOCATION_IMAGE) { + /* + * HGLOBAL mpalQuery(MPQ_LOCATION_IMAGE, uint32 nLoc); + */ + error("mpalQuery(MPQ_LOCATION_IMAGE, uint32 nLoc) used incorrect variant"); + + } else if (wQueryType == MPQ_RESOURCE) { + /* + * HGLOBAL mpalQuery(MPQ_RESOURCE, uint32 dwRes); + */ + error("mpalQuery(MPQ_RESOURCE, uint32 dwRes) used incorrect variant"); + + } else if (wQueryType == MPQ_ITEM_LIST) { + /* + * uint32 mpalQuery(MPQ_ITEM_LIST, uint32 nLoc); + */ + error("mpalQuery(MPQ_ITEM_LIST, uint32 nLoc) used incorrect variant"); + + } else if (wQueryType == MPQ_ITEM_DATA) { + /* + * LpItem mpalQuery(MPQ_ITEM_DATA, uint32 nItem); + */ + error("mpalQuery(MPQ_ITEM_DATA, uint32 nItem) used incorrect variant"); + + } else if (wQueryType == MPQ_ITEM_IS_ACTIVE) { + /* + * bool mpalQuery(MPQ_ITEM_IS_ACTIVE, uint32 nItem); + */ + lockVar(); + int x = GETARG(uint32); + buf = Common::String::format("Status.%u", x); + if (varGetValue(buf.c_str()) <= 0) + dwRet = (uint32)false; + else + dwRet = (uint32)true; + + unlockVar(); + + } else if (wQueryType == MPQ_ITEM_NAME) { + /* + * uint32 mpalQuery(MPQ_ITEM_NAME, uint32 nItem, char * lpszName); + */ + lockVar(); + int x = GETARG(uint32); + n = GETARG(char *); + buf = Common::String::format("Status.%u", x); + if (varGetValue(buf.c_str()) <= 0) + n[0]='\0'; + else { + lockItems(); + int y = itemGetOrderFromNum(x); + memcpy(n, (char *)(GLOBALS._lpmiItems + y)->_lpszDescribe, MAX_DESCRIBE_SIZE); + unlockItems(); + } + + unlockVar(); + + } else if (wQueryType == MPQ_DIALOG_PERIOD) { + /* + * char *mpalQuery(MPQ_DIALOG_PERIOD, uint32 nDialog, uint32 nPeriod); + */ + error("mpalQuery(MPQ_DIALOG_PERIOD, uint32 nDialog, uint32 nPeriod) used incorrect variant"); + + } else if (wQueryType == MPQ_DIALOG_WAITFORCHOICE) { + /* + * void mpalQuery(MPQ_DIALOG_WAITFORCHOICE); + */ + error("mpalQuery(MPQ_DIALOG_WAITFORCHOICE) used incorrect variant"); + + } else if (wQueryType == MPQ_DIALOG_SELECTLIST) { + /* + * uint32 *mpalQuery(MPQ_DIALOG_SELECTLIST, uint32 nChoice); + */ + error("mpalQuery(MPQ_DIALOG_SELECTLIST, uint32 nChoice) used incorrect variant"); + + } else if (wQueryType == MPQ_DIALOG_SELECTION) { + /* + * bool mpalQuery(MPQ_DIALOG_SELECTION, uint32 nChoice, uint32 dwData); + */ + lockDialogs(); + int x = GETARG(uint32); + int y = GETARG(uint32); + dwRet = (uint32)doSelection(x, y); + + unlockDialogs(); + + } else if (wQueryType == MPQ_DO_ACTION) { + /* + * int mpalQuery(MPQ_DO_ACTION, uint32 nAction, uint32 nItem, uint32 dwParam); + */ + lockItems(); + lockVar(); + int x = GETARG(uint32); + int z = GETARG(uint32); + int y = itemGetOrderFromNum(z); + if (y != -1) { + dwRet = doAction(x, y, GETARG(uint32)); + } else { + dwRet = CORO_INVALID_PID_VALUE; + GLOBALS._mpalError = 1; + } + + unlockVar(); + unlockItems(); + + } else if (wQueryType == MPQ_DO_DIALOG) { + /* + * int mpalQuery(MPQ_DO_DIALOG, uint32 nDialog, uint32 nGroup); + */ + if (!GLOBALS._bExecutingDialog) { + lockDialogs(); + + int x = dialogGetOrderFromNum(GETARG(uint32)); + int y = GETARG(uint32); + dwRet = doDialog(x, y); + unlockDialogs(); + } + } else { + /* + * DEFAULT -> ERROR + */ + GLOBALS._mpalError = 1; + } + + va_end(v); + return dwRet; +} + +/** + * This is a general function to communicate with the library, to request information + * about what is in the .MPC file + * + * @param wQueryType Type of query. The list is in the QueryTypes enum. + * @returns 4 bytes depending on the type of query + * @remarks This is the specialised version of the original single mpalQuery + * method that returns a pointer or handle. + */ +MpalHandle mpalQueryHANDLE(uint16 wQueryType, ...) { + char *n; + Common::String buf; + va_list v; + va_start(v, wQueryType); + void *hRet = NULL; + + GLOBALS._mpalError = OK; + + if (wQueryType == MPQ_VERSION) { + /* + * uint32 mpalQuery(MPQ_VERSION); + */ + error("mpalQuery(MPQ_VERSION) used incorrect variant"); + + } else if (wQueryType == MPQ_GLOBAL_VAR) { + /* + * uint32 mpalQuery(MPQ_GLOBAL_VAR, char * lpszVarName); + */ + error("mpalQuery(MPQ_GLOBAL_VAR, char * lpszVarName) used incorrect variant"); + + } else if (wQueryType == MPQ_MESSAGE) { + /* + * char * mpalQuery(MPQ_MESSAGE, uint32 nMsg); + */ + LockMsg(); + hRet = DuplicateMessage(msgGetOrderFromNum(GETARG(uint32))); + UnlockMsg(); + + } else if (wQueryType == MPQ_ITEM_PATTERN) { + /* + * uint32 mpalQuery(MPQ_ITEM_PATTERN, uint32 nItem); + */ + error("mpalQuery(MPQ_ITEM_PATTERN, uint32 nItem) used incorrect variant"); + + } else if (wQueryType == MPQ_LOCATION_SIZE) { + /* + * uint32 mpalQuery(MPQ_LOCATION_SIZE, uint32 nLoc, uint32 dwCoord); + */ + error("mpalQuery(MPQ_LOCATION_SIZE, uint32 nLoc, uint32 dwCoord) used incorrect variant"); + + } else if (wQueryType == MPQ_LOCATION_IMAGE) { + /* + * HGLOBAL mpalQuery(MPQ_LOCATION_IMAGE, uint32 nLoc); + */ + lockLocations(); + int x = locGetOrderFromNum(GETARG(uint32)); + hRet = resLoad(GLOBALS._lpmlLocations[x]._dwPicRes); + unlockLocations(); + + } else if (wQueryType == MPQ_RESOURCE) { + /* + * HGLOBAL mpalQuery(MPQ_RESOURCE, uint32 dwRes); + */ + hRet = resLoad(GETARG(uint32)); + + } else if (wQueryType == MPQ_ITEM_LIST) { + /* + * uint32 mpalQuery(MPQ_ITEM_LIST, uint32 nLoc); + */ + lockVar(); + hRet = GetItemList(GETARG(uint32)); + lockVar(); + + } else if (wQueryType == MPQ_ITEM_DATA) { + /* + * LpItem mpalQuery(MPQ_ITEM_DATA, uint32 nItem); + */ + lockItems(); + hRet = getItemData(itemGetOrderFromNum(GETARG(uint32))); + unlockItems(); + + } else if (wQueryType == MPQ_ITEM_IS_ACTIVE) { + /* + * bool mpalQuery(MPQ_ITEM_IS_ACTIVE, uint32 nItem); + */ + error("mpalQuery(MPQ_ITEM_IS_ACTIVE, uint32 nItem) used incorrect variant"); + + } else if (wQueryType == MPQ_ITEM_NAME) { + /* + * uint32 mpalQuery(MPQ_ITEM_NAME, uint32 nItem, char *lpszName); + */ + lockVar(); + int x = GETARG(uint32); + n = GETARG(char *); + buf = Common::String::format("Status.%u", x); + if (varGetValue(buf.c_str()) <= 0) + n[0] = '\0'; + else { + lockItems(); + int y = itemGetOrderFromNum(x); + memcpy(n, (char *)(GLOBALS._lpmiItems + y)->_lpszDescribe, MAX_DESCRIBE_SIZE); + unlockItems(); + } + + unlockVar(); + + } else if (wQueryType == MPQ_DIALOG_PERIOD) { + /* + * char * mpalQuery(MPQ_DIALOG_PERIOD, uint32 nDialog, uint32 nPeriod); + */ + lockDialogs(); + int y = GETARG(uint32); + hRet = duplicateDialogPeriod(y); + unlockDialogs(); + + } else if (wQueryType == MPQ_DIALOG_WAITFORCHOICE) { + /* + * void mpalQuery(MPQ_DIALOG_WAITFORCHOICE); + */ + error("mpalQuery(MPQ_DIALOG_WAITFORCHOICE) used incorrect variant"); + + } else if (wQueryType == MPQ_DIALOG_SELECTLIST) { + /* + * uint32 *mpalQuery(MPQ_DIALOG_SELECTLIST, uint32 nChoice); + */ + lockDialogs(); + hRet = getSelectList(GETARG(uint32)); + unlockDialogs(); + + } else if (wQueryType == MPQ_DIALOG_SELECTION) { + /* + * bool mpalQuery(MPQ_DIALOG_SELECTION, uint32 nChoice, uint32 dwData); + */ + error("mpalQuery(MPQ_DIALOG_SELECTION, uint32 nChoice, uint32 dwData) used incorrect variant"); + + } else if (wQueryType == MPQ_DO_ACTION) { + /* + * int mpalQuery(MPQ_DO_ACTION, uint32 nAction, uint32 nItem, uint32 dwParam); + */ + error("mpalQuery(MPQ_DO_ACTION, uint32 nAction, uint32 nItem, uint32 dwParam) used incorrect variant"); + + } else if (wQueryType == MPQ_DO_DIALOG) { + /* + * int mpalQuery(MPQ_DO_DIALOG, uint32 nDialog, uint32 nGroup); + */ + error("mpalQuery(MPQ_DO_DIALOG, uint32 nDialog, uint32 nGroup) used incorrect variant"); + } else { + /* + * DEFAULT -> ERROR + */ + GLOBALS._mpalError = 1; + } + + va_end(v); + return hRet; +} + + +/** + * This is a general function to communicate with the library, to request information + * about what is in the .MPC file + * + * @param wQueryType Type of query. The list is in the QueryTypes enum. + * @returns 4 bytes depending on the type of query + * @remarks This is the specialised version of the original single mpalQuery + * method that needs to run within a co-routine context. + */ +void mpalQueryCORO(CORO_PARAM, uint16 wQueryType, uint32 *dwRet, ...) { + CORO_BEGIN_CONTEXT; + uint32 dwRet; + CORO_END_CONTEXT(_ctx); + + va_list v; + va_start(v, dwRet); + + CORO_BEGIN_CODE(_ctx); + + if (wQueryType == MPQ_DIALOG_WAITFORCHOICE) { + /* + * void mpalQuery(MPQ_DIALOG_WAITFORCHOICE); + */ + CORO_INVOKE_2(CoroScheduler.waitForSingleObject, GLOBALS._hAskChoice, CORO_INFINITE); + + // WORKAROUND: Introduce a single frame delay so that if there are multiple actions running, + // they all have time to be signalled before resetting the event. This fixes a problem where + // if you try to use the 'shrimp' on the parrot a second time after trying to first use it + // whilst the parrot was talking, the cursor wouldn't be re-enabled afterwards + CORO_SLEEP(1); + + CoroScheduler.resetEvent(GLOBALS._hAskChoice); + + if (GLOBALS._bExecutingDialog) + *dwRet = (uint32)GLOBALS._nExecutingChoice; + else + *dwRet = (uint32)((int)-1); + } else { + error("mpalQueryCORO called with unsupported query type"); + } + + CORO_END_CODE; + + va_end(v); +} + +/** + * Returns the current MPAL error code + * + * @returns Error code + */ +uint32 mpalGetError() { + return GLOBALS._mpalError; +} + +/** + * Execute a script. The script runs on multitasking by a thread. + * + * @param nScript Script number to run + * @returns TRUE if the script 'was launched, FALSE on failure + */ +bool mpalExecuteScript(int nScript) { + LockScripts(); + int n = scriptGetOrderFromNum(nScript); + LpMpalScript s = (LpMpalScript)globalAlloc(GMEM_FIXED | GMEM_ZEROINIT, sizeof(MpalScript)); + if (s == NULL) + return false; + + memcpy(s, GLOBALS._lpmsScripts + n, sizeof(MpalScript)); + unlockScripts(); + + // !!! New process management + if (CoroScheduler.createProcess(ScriptThread, &s, sizeof(LpMpalScript)) == CORO_INVALID_PID_VALUE) + return false; + + return true; +} + +/** + * Install a custom routine That will be called by MPAL every time the pattern + * of an item has been changed. + * + * @param lpiifCustom Custom function to install + */ +void mpalInstallItemIrq(LPITEMIRQFUNCTION lpiifCus) { + GLOBALS._lpiifCustom = lpiifCus; +} + +/** + * Process the idle actions of the items on one location. + * + * @param nLoc Number of the location whose items must be processed + * for idle actions. + * @returns TRUE if all OK, and FALSE if it exceeded the maximum limit. + * @remarks The maximum number of locations that can be polled + * simultaneously is defined defined by MAXPOLLINGFUNCIONS + */ +bool mpalStartIdlePoll(int nLoc) { + for (uint32 i = 0; i < MAXPOLLINGLOCATIONS; i++) { + if (GLOBALS._nPollingLocations[i] == (uint32)nLoc) + return false; + } + + for (uint32 i = 0; i < MAXPOLLINGLOCATIONS; i++) { + if (GLOBALS._nPollingLocations[i] == 0) { + GLOBALS._nPollingLocations[i] = nLoc; + + GLOBALS._hEndPollingLocations[i] = CoroScheduler.createEvent(true, false); +// !!! New process management + if ((GLOBALS._pollingThreads[i] = CoroScheduler.createProcess(LocationPollThread, &i, sizeof(uint32))) == CORO_INVALID_PID_VALUE) +// if ((GLOBALS.hEndPollingLocations[i] = (void*)_beginthread(LocationPollThread, 10240, (void *)i))= = (void*)-1) + return false; + + return true; + } + } + + return false; +} + + +/** + * Stop processing the idle actions of the items on one location. + * + * @param nLo Number of the location + * @returns TRUE if all OK, FALSE if the specified location was not + * in the process of polling + */ +void mpalEndIdlePoll(CORO_PARAM, int nLoc, bool *result) { + CORO_BEGIN_CONTEXT; + int i; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + for (_ctx->i = 0; _ctx->i < MAXPOLLINGLOCATIONS; _ctx->i++) { + if (GLOBALS._nPollingLocations[_ctx->i] == (uint32)nLoc) { + CoroScheduler.setEvent(GLOBALS._hEndPollingLocations[_ctx->i]); + + CORO_INVOKE_2(CoroScheduler.waitForSingleObject, GLOBALS._pollingThreads[_ctx->i], CORO_INFINITE); + + CoroScheduler.closeEvent(GLOBALS._hEndPollingLocations[_ctx->i]); + GLOBALS._nPollingLocations[_ctx->i] = 0; + + if (result) + *result = true; + return; + } + } + + if (result) + *result = false; + + CORO_END_CODE; +} + +/** + * Retrieve the length of a save state + * + * @returns Length in bytes + */ +int mpalGetSaveStateSize() { + return GLOBALS._nVars * sizeof(MpalVar) + 4; +} + +/** + * Store the save state into a buffer. The buffer must be + * length at least the size specified with mpalGetSaveStateSize + * + * @param buf Buffer where to store the state + */ +void mpalSaveState(byte *buf) { + lockVar(); + WRITE_LE_UINT32(buf, GLOBALS._nVars); + memcpy(buf + 4, (byte *)GLOBALS._lpmvVars, GLOBALS._nVars * sizeof(MpalVar)); + unlockVar(); +} + + +/** + * Load a save state from a buffer. + * + * @param buf Buffer where to store the state + * @returns Length of the state buffer in bytes + */ +int mpalLoadState(byte *buf) { + // We must destroy and recreate all the variables + globalFree(GLOBALS._hVars); + + GLOBALS._nVars = READ_LE_UINT32(buf); + + GLOBALS._hVars = globalAllocate(GMEM_ZEROINIT | GMEM_MOVEABLE, GLOBALS._nVars * sizeof(MpalVar)); + lockVar(); + memcpy((byte *)GLOBALS._lpmvVars, buf + 4, GLOBALS._nVars * sizeof(MpalVar)); + unlockVar(); + + return GLOBALS._nVars * sizeof(MpalVar) + 4; +} + +} // end of namespace MPAL + +} // end of namespace Tony diff --git a/engines/tony/mpal/mpal.h b/engines/tony/mpal/mpal.h new file mode 100644 index 0000000000..5e1b02b3fc --- /dev/null +++ b/engines/tony/mpal/mpal.h @@ -0,0 +1,518 @@ +/* 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 Tony Tough source code + * + * Copyright (c) 1997-2003 Nayma Software + */ + + +/****************************************************************************\ +* General Introduction +\****************************************************************************/ + +/* + * MPAL (MultiPurpose Adventure Language) is a high level language + * for the definition of adventure. Through the use of MPAL you can describe + * storyboard the adventure, and then use it with any user interface. + * In fact, unlike many other similar products, MPAL is not programmed through + * the whole adventure, but are defined only the locations, objects, as they may + * interact with each other, etc.. thus making MPAL useful for any type of adventure. + */ + +/****************************************************************************\ +* Structure +\****************************************************************************/ + +/* + * MPAL consists of two main files: MPAL.DLL and MPAL.H + * The first is the DLL that contains the code to interface with MPAL + * adventures, the second is the header that defines the prototypes + * functions. MPAL is compiled for Win32, and it can therefore be used with + * any compiler that supports Win32 DLL (Watcom C++, Visual C++, + * Delphi, etc.), and therefore compatible with both Windows 95 and Windows NT. + * + * To use the DLL, and 'obviously need to create a library for symbols to export. + * + */ + + +/****************************************************************************\ +* Custom Functions +\****************************************************************************/ + +/* + * A custom function and a function specified by the program that uses the + * library, to perform the particular code. The custom functions are + * retrieved from the library as specified in the source MPAL, and in particular + * in defining the behavior of an item with some action. + * + * To use the custom functions, you need to prepare an array of + * pointers to functions (such as using the type casting LPCUSTOMFUNCTION, + * (defined below), and pass it as second parameter to mpalInit (). Note you + * must specify the size of the array, as elements of pointers and which do not + * contain the same: the library will call it only those functions specified in + * the source MPAL. It can be useful, for debugging reasons, do not bet + * the shares of arrays used to debugging function, to avoid unpleasant crash, + * if it has been made an error in source and / or some oversight in the code. + * + */ + +#ifndef TONY_MPAL_H +#define TONY_MPAL_H + +#include "common/scummsys.h" +#include "common/coroutines.h" +#include "common/rect.h" +#include "common/str.h" +#include "tony/mpal/memory.h" + +namespace Tony { + +namespace MPAL { + +/****************************************************************************\ +* Macro definitions and structures +\****************************************************************************/ + +// OK value for the error codes +#define OK 0 + +#define MAXFRAMES 400 // frame animation of an object +#define MAXPATTERN 40 // pattern of animation of an object +#define MAXPOLLINGLOCATIONS 64 + +#define GETARG(type) va_arg(v, type) + +/** + * Macro for use with queries that may refer to X and Y co-ordinates + */ +enum QueryCoordinates { + MPQ_X, + MPQ_Y +}; + +/** + * Query can be used with mpalQuery methods. In practice corresponds all claims + * that can do at the library + */ +enum QueryTypes { + // General Query + MPQ_VERSION = 10, + + MPQ_GLOBAL_VAR = 50, + MPQ_RESOURCE, + MPQ_MESSAGE, + + // Query on leases + MPQ_LOCATION_IMAGE = 100, + MPQ_LOCATION_SIZE, + + // Queries about items + MPQ_ITEM_LIST = 200, + MPQ_ITEM_DATA, + MPQ_ITEM_PATTERN, + MPQ_ITEM_NAME, + MPQ_ITEM_IS_ACTIVE, + + // Query dialog + MPQ_DIALOG_PERIOD = 300, + MPQ_DIALOG_WAITFORCHOICE, + MPQ_DIALOG_SELECTLIST, + MPQ_DIALOG_SELECTION, + + // Query execution + MPQ_DO_ACTION = 400, + MPQ_DO_DIALOG +}; + +/** + * Framework to manage the animation of an item + */ +typedef struct { + char *_frames[MAXFRAMES]; + Common::Rect _frameslocations[MAXFRAMES]; + Common::Rect _bbox[MAXFRAMES]; + short _pattern[MAXPATTERN][MAXFRAMES]; + short _speed; + char _numframe; + char _numpattern; + char _curframe; + char _curpattern; + short _destX, _destY; + signed char _destZ; + short _objectID; +} Item; +typedef Item *LpItem; + + +/** + * Define a custom function, to use the language MPAL to perform various controls as a result of an action + */ +typedef void (*LPCUSTOMFUNCTION)(CORO_PARAM, uint32, uint32, uint32, uint32); +typedef LPCUSTOMFUNCTION *LPLPCUSTOMFUNCTION; + +/** + * + * Define an IRQ of an item that is called when the pattern changes or the status of an item + */ +typedef void (*LPITEMIRQFUNCTION)(uint32, int, int); +typedef LPITEMIRQFUNCTION* LPLPITEMIRQFUNCTION; + +/** + * @defgroup Macrofunctions query + * + * The following are defines used for simplifying calling the mpalQuery variants + */ +//@{ + +/** + * Gets the current version of MPAL + * + * @returns Version number (0x1232 = 1.2.3b) + */ +#define mpalQueryVersion() \ + (uint16)mpalQueryDWORD(MPQ_VERSION) + +/** + * Gets the numerical value of a global variable + * + * @param lpszVarName Variable name (ASCIIZ) + * @returns Global variable value + * @remarks This query was implemented for debugging. The program, + * if well designed, should not need to access variables from + * within the library. + */ +#define mpalQueryGlobalVar(lpszVarName) \ + mpalQueryDWORD(MPQ_GLOBAL_VAR, (const char *)(lpszVarName)) + + +/** + * Provides access to a resource inside the .MPC file + * + * @param dwResId Resource Id + * @returns Handle to a memory area containing the resource, ready for use. + */ +#define mpalQueryResource(dwResId) \ + mpalQueryHANDLE(MPQ_RESOURCE, (uint32)(dwResId)) + + +/** + * Returns a message. + * + * @param nMsg Message number + * @returns ASCIIZ message + * @remarks The returned pointer must be freed via the memory manager +* after use. The message will be in ASCIIZ format. +*/ +#define mpalQueryMessage(nMsg) \ + (char *)mpalQueryHANDLE(MPQ_MESSAGE, (uint32)(nMsg)) + + +/** + * Provides a location image + * @return Returns a picture handle + */ +#define mpalQueryLocationImage(nLoc) \ + mpalQueryHANDLE(MPQ_LOCATION_IMAGE, (uint32)(nLoc)) + + +/** + * Request the x or y size of a location in pixels + * + * @param nLoc Location number + * @param dwCoord MPQ_X or MPQ_Y coordinate to retrieve + * @returns Size + */ +#define mpalQueryLocationSize(nLoc, dwCoord) \ + mpalQueryDWORD(MPQ_LOCATION_SIZE, (uint32)(nLoc), (uint32)(dwCoord)) + + +/** + * Provides the list of objects in a location. + * + * @param nLoc Location number + * @returns List of objects (accessible by Item [0], Item [1], etc.) + */ +// TODO: Determine if this is endian safe +#define mpalQueryItemList(nLoc) \ + (uint32 *)mpalQueryHANDLE(MPQ_ITEM_LIST, (uint32)(nLoc)) + + +/** + * Provides information on an item + * + * @param nItem Item number + * @returns Structure filled with requested information + */ +#define mpalQueryItemData(nItem) \ + (LpItem)mpalQueryHANDLE(MPQ_ITEM_DATA, (uint32)(nItem)) + + +/** + * Provides the current pattern of an item + * + * @param nItem Item number + * @returns Number of animation patterns to be executed. + * @remarks By default, the pattern of 0 indicates that we should do nothing. + */ +#define mpalQueryItemPattern(nItem) \ + mpalQueryDWORD(MPQ_ITEM_PATTERN, (uint32)(nItem)) + + +/** + * Returns true if an item is active + * + * @param nItem Item number + * @returns TRUE if the item is active, FALSE otherwise + */ +#define mpalQueryItemIsActive(nItem) \ + (bool)mpalQueryDWORD(MPQ_ITEM_IS_ACTIVE, (uint32)(nItem)) + + +/** + * Returns the name of an item + * + * @param nItem Item number + * @param lpszName Pointer to a buffer of at least 33 bytes + * that will be filled with the name + * @remarks If the item is not active (ie. if its status or number + * is less than or equal to 0), the string will be empty. + */ +#define mpalQueryItemName(nItem, lpszName) \ + mpalQueryHANDLE(MPQ_ITEM_NAME, (uint32)(nItem), (char *)(lpszName)) + + +/** + * Returns a sentence of dialog. + * + * @param nDialog Dialog number + * @param nPeriod Number of words + * @returns A pointer to the string of words, or NULL on failure. + * @remarks The string must be freed after use using the memory manager. + * Unlike normal messages, the sentences of dialogue are formed by a single + * string terminated with 0. + */ +#define mpalQueryDialogPeriod(nPeriod) \ + (char *)mpalQueryHANDLE(MPQ_DIALOG_PERIOD, (uint32)(nPeriod)) + + +/** + * Wait until the moment in which the need is signaled to make a choice by the user. + * @returns Number of choice to be made, or -1 if the dialogue is finished. + */ +#define mpalQueryDialogWaitForChoice(dwRet) \ + CORO_INVOKE_2(mpalQueryCORO, MPQ_DIALOG_WAITFORCHOICE, dwRet) + +/** + * Requires a list of various options for some choice within the current dialog. + * + * @param nChoice Choice number + * @returns A pointer to an array containing the data matched to each option. + * @remarks The figure 'a uint32 specified in the source to which MPAL + * You can assign meaning that the more' suits. + * The pointer msut be freed after use using the memory memory. + */ +#define mpalQueryDialogSelectList(nChoice) \ + (uint32 *)mpalQueryHANDLE(MPQ_DIALOG_SELECTLIST, (uint32)(nChoice)) + +/** + * Warns the library that the user has selected, in a certain choice of the current dialog, + * corresponding option at a certain given. + * + * @param nChoice Choice number of the choice that was in progress + * @param dwData Option that was selected by the user. + * @returns TRUE if all OK, FALSE on failure. + * @remarks After execution of this query, MPAL continue + * Groups according to the execution of the dialogue. And necessary so the game + * remains on hold again for another chosen by mpalQueryDialogWaitForChoice (). + */ +#define mpalQueryDialogSelection(nChoice, dwData) \ + (bool)mpalQueryDWORD(MPQ_DIALOG_SELECTION, (uint32)(nChoice), (uint32)(dwData)) + +#define mpalQueryDialogSelectionDWORD(nChoice, dwData) \ + mpalQueryDWORD(MPQ_DIALOG_SELECTION, (uint32)(nChoice), (uint32)(dwData)) + +/** + * Warns the library an action was performed on a Object. + * The library will call custom functions, if necessary. + * + * @param nAction Action number + * @param nItem Item number + * @param dwParam Action parameter + * @returns Handle to the thread that is performing the action, or CORO_INVALID_PID_VALUE + * if the action is not defined for the item, or the item is inactive. + * @remarks The parameter is used primarily to implement actions + * as "U.S." involving two objects together. The action will be executed only + * if the item is active, ie if its status is a positive number greater than 0. + */ +#define mpalQueryDoAction(nAction, nItem, dwParam) \ + mpalQueryDWORD(MPQ_DO_ACTION, (uint32)(nAction), (uint32)(nItem), (uint32)(dwParam)) + +/** + * Warns the library a dialogue was required. + * + * @param nDialog Dialog number + * @param nGroup Group number to use + * @returns Handle to the thread that is running the box, or + * CORO_INVALID_PID_VALUE if the dialogue does not exist. + */ +#define mpalQueryDoDialog(nDialog, nGroup) \ + mpalQueryDWORD(MPQ_DO_DIALOG, (uint32)(nDialog), (uint32)(nGroup)) + +/** + * @defgroup Functions exported to the main game + */ +//@{ + +/** + * Initializes the MPAL library, and opens an .MPC file, which will be 'used for all queries + * @param lpszMpcFileName Name of the .MPC file, including extension + * @param lpszMprFileName Name of the .MPR file, including extension + * @param lplpcfArray Array of pointers to custom functions + * @returns TRUE if all OK, FALSE on failure + */ +bool mpalInit(const char *lpszFileName, const char *lpszMprFileName, + LPLPCUSTOMFUNCTION lplpcfArray, Common::String *lpcfStrings); + +/** + * Frees resources allocated by the MPAL subsystem + */ +void mpalFree(); + +/** + * This is a general function to communicate with the library, to request information + * about what is in the .MPC file + * + * @param wQueryType Type of query. The list is in the QueryTypes enum. + * @returns 4 bytes depending on the type of query + * @remarks This is the specialised version of the original single mpalQuery + * method that returns numeric results. + */ +uint32 mpalQueryDWORD(uint16 wQueryType, ...); + +/** + * This is a general function to communicate with the library, to request information + * about what is in the .MPC file + * + * @param wQueryType Type of query. The list is in the QueryTypes enum. + * @returns 4 bytes depending on the type of query + * @remarks This is the specialised version of the original single mpalQuery + * method that returns a pointer or handle. + */ +MpalHandle mpalQueryHANDLE(uint16 wQueryType, ...); + +/** + * This is a general function to communicate with the library, to request information + * about what is in the .MPC file + * + * @param wQueryType Type of query. The list is in the QueryTypes enum. + * @returns 4 bytes depending on the type of query + * @remarks This is the specialised version of the original single mpalQuery + * method that needs to run within a co-routine context. + */ +void mpalQueryCORO(CORO_PARAM, uint16 wQueryType, uint32 *dwRet, ...); + +/** + * Execute a script. The script runs on multitasking by a thread. + * + * @param nScript Script number to run + * @returns TRUE if the script 'was launched, FALSE on failure + */ +bool mpalExecuteScript(int nScript); + +/** + * Returns the current MPAL error code + * + * @returns Error code + */ +uint32 mpalGetError(); + +/** + * Install a custom routine That will be called by MPAL every time the pattern + * of an item has been changed. + * + * @param lpiifCustom Custom function to install + */ +void mpalInstallItemIrq(LPITEMIRQFUNCTION lpiifCustom); + +/** + * Process the idle actions of the items on one location. + * + * @param nLoc Number of the location whose items must be processed + * for idle actions. + * @returns TRUE if all OK, and FALSE if it exceeded the maximum limit. + * @remarks The maximum number of locations that can be polled + * simultaneously is defined defined by MAXPOLLINGFUNCIONS + */ +bool mpalStartIdlePoll(int nLoc); + +/** + * Stop processing the idle actions of the items on one location. + * + * @param nLo Number of the location + * @returns TRUE if all OK, FALSE if the specified location was not + * in the process of polling + */ +void mpalEndIdlePoll(CORO_PARAM, int nLoc, bool *result); + + +/** + * Load a save state from a buffer. + * + * @param buf Buffer where to store the state + * @returns Length of the state buffer in bytes + */ +int mpalLoadState(byte *buf); + +/** + * Store the save state into a buffer. The buffer must be + * length at least the size specified with mpalGetSaveStateSize + * + * @param buf Buffer where to store the state + */ +void mpalSaveState(byte *buf); + +/** + * Retrieve the length of a save state + * + * @returns Length in bytes + */ +int mpalGetSaveStateSize(); + +/** + * Locks the variables for access + */ +void lockVar(); + +/** + * Unlocks variables after use + */ +void unlockVar(); + +} // end of namespace MPAL + +} // end of namespace Tony + +#endif + diff --git a/engines/tony/mpal/mpaldll.h b/engines/tony/mpal/mpaldll.h new file mode 100644 index 0000000000..8897096f51 --- /dev/null +++ b/engines/tony/mpal/mpaldll.h @@ -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. + * + * 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 Tony Tough source code + * + * Copyright (c) 1997-2003 Nayma Software + */ + +#ifndef __MPALDLL_H +#define __MPALDLL_H + +#include "common/file.h" +#include "tony/mpal/memory.h" +#include "tony/mpal/loadmpc.h" +#include "tony/mpal/expr.h" + +namespace Tony { + +namespace MPAL { + +/****************************************************************************\ +* Defines +\****************************************************************************/ + +#define HEX_VERSION 0x0170 + +#define MAX_ACTIONS_PER_ITEM 40 +#define MAX_COMMANDS_PER_ITEM 128 +#define MAX_COMMANDS_PER_ACTION 128 +#define MAX_DESCRIBE_SIZE 64 + +#define MAX_MOMENTS_PER_SCRIPT 256 +#define MAX_COMMANDS_PER_SCRIPT 256 +#define MAX_COMMANDS_PER_MOMENT 32 + +#define MAX_GROUPS_PER_DIALOG 128 +#define MAX_COMMANDS_PER_DIALOG 480 +#define MAX_COMMANDS_PER_GROUP 64 +#define MAX_CHOICES_PER_DIALOG 64 +#define MAX_SELECTS_PER_CHOICE 64 +#define MAX_PLAYGROUPS_PER_SELECT 9 +#define MAX_PERIODS_PER_DIALOG 400 + +#define NEED_LOCK_MSGS + +/****************************************************************************\ +* Structures +\****************************************************************************/ + +#include "common/pack-start.h" + +/** + * MPAL global variables + */ +struct MpalVar { + uint32 _dwVal; // Variable value + char _lpszVarName[33]; // Variable name +} PACKED_STRUCT; +typedef MpalVar *LpMpalVar; + +/** + * MPAL Messages + */ +struct MpalMsg { + MpalHandle _hText; // Handle to the message text + uint16 _wNum; // Message number +} PACKED_STRUCT; +typedef MpalMsg *LpMpalMsg; + +/** + * MPAL Locations + */ +struct MpalLocation { + uint32 _nObj; // Location number + uint32 _dwXlen, _dwYlen; // Dimensions + uint32 _dwPicRes; // Resource that contains the image +} PACKED_STRUCT; +typedef MpalLocation *LpMpalLocation; + +/** + * All the data for a command, ie. tags used by OnAction in the item, the time + * in the script, and in the group dialog. + */ +struct Command { + /* + * Types of commands that are recognized + * + * #1 -> Custom function call (ITEM, SCRIPT, DIALOG) + * #2 -> Variable assignment (ITEM, SCRIPT, DIALOG) + * #3 -> Making a choice (DIALOG) + * + */ + byte _type; // Type of control + + union { + int32 _nCf; // Custom function call [#1] + char *_lpszVarName; // Variable name [#2] + int32 _nChoice; // Number of choice you make [#3] + }; + + union { + int32 _arg1; // Argument for custom function [#1] + MpalHandle _expr; // Expression to assign to a variable [#2] + }; + + int32 _arg2, _arg3, _arg4; // Arguments for custom function [#1] +} PACKED_STRUCT; + + +/** + * MPAL dialog + */ +struct MpalDialog { + uint32 _nObj; // Dialog number + + struct Command _command[MAX_COMMANDS_PER_DIALOG]; + + struct { + uint16 _num; + byte _nCmds; + uint16 _cmdNum[MAX_COMMANDS_PER_GROUP]; + + } _group[MAX_GROUPS_PER_DIALOG]; + + struct { + // The last choice has nChoice == 0 + uint16 _nChoice; + + // The select number (we're pretty stingy with RAM). The last select has dwData == 0 + struct { + MpalHandle _when; + uint32 _dwData; + uint16 _wPlayGroup[MAX_PLAYGROUPS_PER_SELECT]; + + // Bit 0=endchoice Bit 1=enddialog + byte _attr; + + // Modified at run-time: 0 if the select is currently disabled, + // and 1 if currently active + byte _curActive; + } _select[MAX_SELECTS_PER_CHOICE]; + + } _choice[MAX_CHOICES_PER_DIALOG]; + + uint16 _periodNums[MAX_PERIODS_PER_DIALOG]; + MpalHandle _periods[MAX_PERIODS_PER_DIALOG]; + +} PACKED_STRUCT; +typedef MpalDialog *LpMpalDialog; + +/** + * MPAL Item + */ +struct ItemAction { + byte _num; // Action number + uint16 _wTime; // If idle, the time which must pass + byte _perc; // Percentage of the idle run + MpalHandle _when; // Expression to compute. If != 0, then + // action can be done + uint16 _wParm; // Parameter for action + + byte _nCmds; // Number of commands to be executed + uint32 _cmdNum[MAX_COMMANDS_PER_ACTION]; // Commands to execute +} PACKED_STRUCT; + +struct MpalItem { + uint32 _nObj; // Item number + + byte _lpszDescribe[MAX_DESCRIBE_SIZE]; // Name + byte _nActions; // Number of managed actions + uint32 _dwRes; // Resource that contains frames and patterns + + struct Command _command[MAX_COMMANDS_PER_ITEM]; + + // Pointer to array of structures containing various managed activities. In practice, of + // every action we know what commands to run, including those defined in structures above + struct ItemAction *_action; + +} PACKED_STRUCT; +typedef MpalItem *LpMpalItem; + +/** + * MPAL Script + */ +struct MpalScript { + uint32 _nObj; + uint32 _nMoments; + + struct Command _command[MAX_COMMANDS_PER_SCRIPT]; + + struct { + int32 _dwTime; + byte _nCmds; + uint32 _cmdNum[MAX_COMMANDS_PER_MOMENT]; + + } _moment[MAX_MOMENTS_PER_SCRIPT]; + +} PACKED_STRUCT; +typedef MpalScript *LpMpalScript; + +#include "common/pack-end.h" + +/****************************************************************************\ +* Function prototypes +\****************************************************************************/ + +/** + * Returns the current value of a global variable + * + * @param lpszVarName Name of the variable + * @returns Current value + * @remarks Before using this method, you must call LockVar() to + * lock the global variablves for use. Then afterwards, you will + * need to remember to call UnlockVar() + */ +extern int32 varGetValue(const char *lpszVarName); + + +/** + * Sets the value of a MPAL global variable + * @param lpszVarName Name of the variable + * @param val Value to set + */ +extern void varSetValue(const char *lpszVarName, int32 val); + +} // end of namespace MPAL + +} // end of namespace Tony + +#endif + diff --git a/engines/tony/mpal/mpalutils.cpp b/engines/tony/mpal/mpalutils.cpp new file mode 100644 index 0000000000..0919aed5ac --- /dev/null +++ b/engines/tony/mpal/mpalutils.cpp @@ -0,0 +1,115 @@ +/* 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. + * + * + */ + +#include "tony/mpal/mpalutils.h" +#include "tony/tony.h" +#include "common/memstream.h" + +namespace Tony { + +namespace MPAL { + +/****************************************************************************\ +* RMRes methods +\****************************************************************************/ + +/** + * Constructor + * @param resId MPAL resource to open + */ +RMRes::RMRes(uint32 resID) { + _h = g_vm->_resUpdate.queryResource(resID); + if (_h == NULL) + _h = mpalQueryResource(resID); + if (_h != NULL) + _buf = (byte *)globalLock(_h); +} + +/** + * Destructor + */ +RMRes::~RMRes() { + if (_h != NULL) { + globalUnlock(_h); + globalFree(_h); + } +} + +/** + * Returns a pointer to the resource + */ +const byte *RMRes::dataPointer() { + return _buf; +} + +/** + * Returns a pointer to the resource + */ +RMRes::operator const byte *() { + return dataPointer(); +} + +/** + * Returns the size of the resource + */ +unsigned int RMRes::size() { + return globalSize(_h); +} + +Common::SeekableReadStream *RMRes::getReadStream() { + return new Common::MemoryReadStream(_buf, size()); +} + +bool RMRes::isValid() { + return _h != NULL; +} + +/****************************************************************************\ +* RMResRaw methods +\****************************************************************************/ + +RMResRaw::RMResRaw(uint32 resID) : RMRes(resID) { +} + +RMResRaw::~RMResRaw() { +} + +const byte *RMResRaw::dataPointer() { + return _buf + 8; +} + +RMResRaw::operator const byte *() { + return dataPointer(); +} + +int RMResRaw::width() { + return READ_LE_UINT16(_buf + 4); +} + +int RMResRaw::height() { + return READ_LE_UINT16(_buf + 6); +} + +} // end of namespace MPAL + +} // end of namespace Tony diff --git a/engines/tony/mpal/mpalutils.h b/engines/tony/mpal/mpalutils.h new file mode 100644 index 0000000000..d92bb6f9a2 --- /dev/null +++ b/engines/tony/mpal/mpalutils.h @@ -0,0 +1,74 @@ +/* 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. + * + * + */ + +#ifndef TONY_MPAL_MPALUTILS +#define TONY_MPAL_MPALUTILS + +#include "common/scummsys.h" +#include "tony/mpal/memory.h" + +namespace Common { + class SeekableReadStream; +} + +namespace Tony { + +namespace MPAL { + +class RMRes { +protected: + MpalHandle _h; + byte *_buf; + +public: + RMRes(uint32 resID); + virtual ~RMRes(); + + // Attributes + unsigned int size(); + const byte *dataPointer(); + bool isValid(); + + // Casting for access to data + operator const byte*(); + + Common::SeekableReadStream *getReadStream(); +}; + +class RMResRaw : public RMRes { +public: + RMResRaw(uint32 resID); + virtual ~RMResRaw(); + + const byte *dataPointer(); + operator const byte*(); + + int width(); + int height(); +}; + +} // end of namespace MPAL + +} // end of namespace Tony + +#endif diff --git a/engines/tony/resid.h b/engines/tony/resid.h new file mode 100644 index 0000000000..f4d2c9a4fa --- /dev/null +++ b/engines/tony/resid.h @@ -0,0 +1,71 @@ +/* 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 Tony Tough source code + * + * Copyright (c) 1997-2003 Nayma Software + */ + +// From 10500 onwards there are .OGG for inventory and scrap +#ifndef TONY_RESID_H +#define TONY_RESID_H + + +#define RES_I_INTERFACE 10300 +#define RES_I_INTERPAL 10301 +#define RES_I_INTERPPAL 10302 +#define RES_I_INTERP1 10303 +#define RES_I_INTERP2 10304 +#define RES_I_INTERP3 10305 +#define RES_I_INTERP4 10306 +#define RES_I_INTERP5 10307 + +#define RES_I_DLGTEXT 10350 +#define RES_I_DLGTEXTLINE 10351 +#define RES_I_DLGTEXTPAL 10352 + +#define RES_I_MINIINTER 10360 + +#define RES_P_PAL 10410 +#define RES_P_GO 10400 +#define RES_P_TAKE 10401 +#define RES_P_USE 10402 +#define RES_P_EXAM 10403 +#define RES_P_TALK 10404 + +#define RES_P_PAP1 10420 +#define RES_P_PAP2 10421 +#define RES_P_PAP3 10422 +#define RES_P_PAP4 10423 +#define RES_P_FRMAP 10424 + +#define RES_F_PAL 10700 +#define RES_F_PARL 10701 +#define RES_F_OBJ 10702 +#define RES_F_MACC 10703 +#define RES_F_CREDITS 10704 +#define RES_F_CPAL 10705 + +#define RES_W_CIRCLE 10800 + +#endif diff --git a/engines/tony/sound.cpp b/engines/tony/sound.cpp new file mode 100644 index 0000000000..2844e0d925 --- /dev/null +++ b/engines/tony/sound.cpp @@ -0,0 +1,688 @@ +/* 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 Tony Tough source code + * + * Copyright (c) 1997-2003 Nayma Software + */ + +#include "audio/audiostream.h" +#include "audio/decoders/adpcm.h" +#include "audio/decoders/raw.h" +#include "audio/decoders/wave.h" +#include "common/textconsole.h" +#include "tony/game.h" +#include "tony/tony.h" + +namespace Tony { + +/****************************************************************************\ +* FPSOUND Methods +\****************************************************************************/ + +/** + * Default constructor. Initializes the attributes. + * + */ +FPSound::FPSound() { + _soundSupported = false; +} + +/** + * Initializes the object, and prepare everything you need to create streams and sound effects. + * + * @returns True is everything is OK, False otherwise + */ +bool FPSound::init() { + _soundSupported = g_system->getMixer()->isReady(); + return _soundSupported; +} + +/** + * Destroy the object and free the memory + * + */ + +FPSound::~FPSound() { +} + +/** + * Allocates an object of type FPStream, and return its pointer + * + * @param streamPtr Will contain a pointer to the object you just created. + * + * @returns True is everything is OK, False otherwise + */ + +bool FPSound::createStream(FPStream **streamPtr) { + (*streamPtr) = new FPStream(_soundSupported); + + return (*streamPtr != NULL); +} + +/** + * Allocates an object of type FpSfx, and return its pointer + * + * @param soundPtr Will contain a pointer to the object you just created. + * + * @returns True is everything is OK, False otherwise + */ + +bool FPSound::createSfx(FPSfx **sfxPtr) { + (*sfxPtr) = new FPSfx(_soundSupported); + + return (*sfxPtr != NULL); +} + +/** + * Set the general volume + * + * @param volume Volume to set (0-63) + */ + +void FPSound::setMasterVolume(int volume) { + if (!_soundSupported) + return; + + g_system->getMixer()->setVolumeForSoundType(Audio::Mixer::kPlainSoundType, CLIP<int>(volume, 0, 63) * Audio::Mixer::kMaxChannelVolume / 63); +} + +/** + * Get the general volume + * + * @param volumePtr Variable that will contain the volume (0-63) + */ + +void FPSound::getMasterVolume(int *volumePtr) { + if (!_soundSupported) + return; + + *volumePtr = g_system->getMixer()->getVolumeForSoundType(Audio::Mixer::kPlainSoundType) * 63 / Audio::Mixer::kMaxChannelVolume; +} + +/** + * Default constructor. + * + * @remarks Do *NOT* declare an object directly, but rather + * create it using FPSound::CreateSfx() + * + */ + +FPSfx::FPSfx(bool soundOn) { + _soundSupported = soundOn; + _fileLoaded = false; + _lastVolume = 63; + _hEndOfBuffer = CoroScheduler.createEvent(true, false); + _isVoice = false; + _loopStream = 0; + _rewindableStream = 0; + _paused = false; + + g_vm->_activeSfx.push_back(this); +} + +/** + * Default Destructor. + * + * @remarks It is also stops the sound effect that may be + * currently played, and free the memory it uses. + * + */ + +FPSfx::~FPSfx() { + if (!_soundSupported) + return; + + g_system->getMixer()->stopHandle(_handle); + g_vm->_activeSfx.remove(this); + + if (_loopStream) + delete _loopStream; // _rewindableStream is deleted by deleting _loopStream + else + delete _rewindableStream; + + // Free the buffer end event + CoroScheduler.closeEvent(_hEndOfBuffer); +} + +/** + * Releases the memory used by the object. + * + * @remarks Must be called when the object is no longer used and + * **ONLY** if the object was created by + * FPSound::CreateStream(). + * Object pointers are no longer valid after this call. + */ + +void FPSfx::release() { + delete this; +} + +bool FPSfx::loadWave(Common::SeekableReadStream *stream) { + if (!stream) + return false; + + _rewindableStream = Audio::makeWAVStream(stream, DisposeAfterUse::YES); + + if (!_rewindableStream) + return false; + + _fileLoaded = true; + setVolume(_lastVolume); + return true; +} + +bool FPSfx::loadVoiceFromVDB(Common::File &vdbFP) { + if (!_soundSupported) + return true; + + uint32 size = vdbFP.readUint32LE(); + uint32 rate = vdbFP.readUint32LE(); + _isVoice = true; + + _rewindableStream = Audio::makeADPCMStream(vdbFP.readStream(size), DisposeAfterUse::YES, 0, Audio::kADPCMDVI, rate, 1); + + _fileLoaded = true; + setVolume(62); + return true; +} + +/** + * Opens a file and loads a sound effect. + * + * @param fileName Sfx filename + * @param codec CODEC used to uncompress the samples + * + * @returns True is everything is OK, False otherwise + */ + +bool FPSfx::loadFile(const char *fileName, uint32 codec) { + if (!_soundSupported) + return true; + + Common::File file; + if (!file.open(fileName)) { + warning("FPSfx::LoadFile(): Cannot open sfx file!"); + return false; + } + + if (file.readUint32BE() != MKTAG('A', 'D', 'P', 0x10)) { + warning("FPSfx::LoadFile(): Invalid ADP header!"); + return false; + } + + uint32 rate = file.readUint32LE(); + uint32 channels = file.readUint32LE(); + + Common::SeekableReadStream *buffer = file.readStream(file.size() - file.pos()); + + if (codec == FPCODEC_ADPCM) { + _rewindableStream = Audio::makeADPCMStream(buffer, DisposeAfterUse::YES, 0, Audio::kADPCMDVI, rate, channels); + } else { + byte flags = Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN; + + if (channels == 2) + flags |= Audio::FLAG_STEREO; + + _rewindableStream = Audio::makeRawStream(buffer, rate, flags, DisposeAfterUse::YES); + } + + _fileLoaded = true; + return true; +} + +/** + * Play the Sfx in memory. + * + * @returns True is everything is OK, False otherwise + */ + +bool FPSfx::play() { + stop(); // sanity check + + if (_fileLoaded) { + CoroScheduler.resetEvent(_hEndOfBuffer); + + _rewindableStream->rewind(); + + Audio::AudioStream *stream = _rewindableStream; + + if (_loop) { + if (!_loopStream) + _loopStream = Audio::makeLoopingAudioStream(_rewindableStream, 0); + + stream = _loopStream; + } + + g_system->getMixer()->playStream(Audio::Mixer::kPlainSoundType, &_handle, stream, -1, + Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO); + + setVolume(_lastVolume); + + if (_paused) + g_system->getMixer()->pauseHandle(_handle, true); + } + + return true; +} + +/** + * Stops a Sfx. + * + * @returns True is everything is OK, False otherwise + */ + +bool FPSfx::stop() { + if (_fileLoaded) { + g_system->getMixer()->stopHandle(_handle); + _paused = false; + } + + return true; +} + +/** + * Enables or disables the Sfx loop. + * + * @param loop True to enable the loop, False to disable + * + * @remarks The loop must be activated BEFORE the sfx starts + * playing. Any changes made during the play will have + * no effect until the sfx is stopped then played again. + */ + +void FPSfx::setLoop(bool loop) { + _loop = loop; +} + +/** + * Pauses a Sfx. + * + */ + +void FPSfx::setPause(bool pause) { + if (_fileLoaded) { + if (g_system->getMixer()->isSoundHandleActive(_handle) && (pause ^ _paused)) + g_system->getMixer()->pauseHandle(_handle, pause); + + _paused = pause; + } +} + +/** + * Change the volume of Sfx + * + * @param volume Volume to be set (0-63) + * + */ + +void FPSfx::setVolume(int volume) { + if (volume > 63) + volume = 63; + + if (volume < 0) + volume = 0; + + _lastVolume = volume; + + if (_isVoice) { + if (!GLOBALS._bCfgDubbing) + volume = 0; + else { + volume -= (10 - GLOBALS._nCfgDubbingVolume) * 2; + if (volume < 0) + volume = 0; + } + } else { + if (!GLOBALS._bCfgSFX) + volume = 0; + else { + volume -= (10 - GLOBALS._nCfgSFXVolume) * 2; + if (volume < 0) + volume = 0; + } + } + + if (g_system->getMixer()->isSoundHandleActive(_handle)) + g_system->getMixer()->setChannelVolume(_handle, volume * Audio::Mixer::kMaxChannelVolume / 63); +} + +/** + * Gets the Sfx volume + * + * @param volumePtr Will contain the current Sfx volume + * + */ + +void FPSfx::getVolume(int *volumePtr) { + if (g_system->getMixer()->isSoundHandleActive(_handle)) + *volumePtr = g_system->getMixer()->getChannelVolume(_handle) * 63 / Audio::Mixer::kMaxChannelVolume; + else + *volumePtr = 0; +} + +/** + * Returns true if the underlying sound has ended + */ + +bool FPSfx::endOfBuffer() const { + return !g_system->getMixer()->isSoundHandleActive(_handle) && (!_rewindableStream || _rewindableStream->endOfData()); +} + +/** + * Continually checks to see if active sounds have finished playing + * Sets the event signalling the sound has ended + */ +void FPSfx::soundCheckProcess(CORO_PARAM, const void *param) { + CORO_BEGIN_CONTEXT; + Common::List<FPSfx *>::iterator i; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + for (;;) { + // Check each active sound + for (_ctx->i = g_vm->_activeSfx.begin(); _ctx->i != g_vm->_activeSfx.end(); ++_ctx->i) { + FPSfx *sfx = *_ctx->i; + if (sfx->endOfBuffer()) + CoroScheduler.setEvent(sfx->_hEndOfBuffer); + } + + // Delay until the next check is done + CORO_INVOKE_1(CoroScheduler.sleep, 50); + } + + CORO_END_CODE; +} + +/** + * Default constructor. + * + * @remarks Do *NOT* declare an object directly, but rather + * create it using FPSound::CreateStream() + */ +FPStream::FPStream(bool soundOn) { + _soundSupported = soundOn; + _fileLoaded = false; + _paused = false; + _loop = false; + _doFadeOut = false; + _syncExit = false; + _bufferSize = _size = 0; + _lastVolume = 0; + _syncToPlay = NULL; + _loopStream = NULL; + _rewindableStream = NULL; +} + +/** + * Default destructor. + * + * @remarks It calls CloseFile() if needed. + */ + +FPStream::~FPStream() { + if (!_soundSupported) + return; + + if (g_system->getMixer()->isSoundHandleActive(_handle)) + stop(); + + if (_fileLoaded) + unloadFile(); + + _syncToPlay = NULL; +} + +/** + * Releases the memory object. + * + * @remarks Must be called when the object is no longer used + * and **ONLY** if the object was created by + * FPSound::CreateStream(). + * Object pointers are no longer valid after this call. + */ +void FPStream::release() { + delete this; +} + +/** + * Opens a file stream + * + * @param fileName Filename to be opened + * @param codec CODEC to be used to uncompress samples + * + * @returns True is everything is OK, False otherwise + */ +bool FPStream::loadFile(const Common::String &fileName, uint32 codec, int bufSize) { + if (!_soundSupported) + return true; + + if (_fileLoaded) + unloadFile(); + + // Save the codec type + _codec = codec; + + // Open the file stream for reading + if (!_file.open(fileName)) { + // Fallback: try with an extra '0' prefix + if (!_file.open("0" + fileName)) + return false; + } + + // Save the size of the stream + _size = _file.size(); + + switch (_codec) { + case FPCODEC_RAW: + _rewindableStream = Audio::makeRawStream(&_file, 44100, Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN | Audio::FLAG_STEREO, DisposeAfterUse::NO); + break; + + case FPCODEC_ADPCM: + _rewindableStream = Audio::makeADPCMStream(&_file, DisposeAfterUse::NO, 0, Audio::kADPCMDVI, 44100, 2); + break; + + default: + _file.close(); + return false; + } + + // All done + _fileLoaded = true; + _paused = false; + + setVolume(63); + + return true; +} + +/** + * Closes a file stream (opened or not). + * + * @returns For safety, the destructor calls unloadFile() if it has not + * been mentioned explicitly. + * + * @remarks It is necessary to call this function to free the + * memory used by the stream. + */ +bool FPStream::unloadFile() { + if (!_soundSupported || !_fileLoaded) + return true; + + assert(!g_system->getMixer()->isSoundHandleActive(_handle)); + + // Closes the file handle stream + delete _loopStream; + delete _rewindableStream; + _loopStream = NULL; + _rewindableStream = NULL; + _file.close(); + + // Flag that the file is no longer in memory + _fileLoaded = false; + + return true; +} + +/** + * Play the stream. + * + * @returns True is everything is OK, False otherwise + */ + +bool FPStream::play() { + if (!_soundSupported || !_fileLoaded) + return false; + + stop(); + + _rewindableStream->rewind(); + + Audio::AudioStream *stream = _rewindableStream; + + if (_loop) { + if (!_loopStream) + _loopStream = new Audio::LoopingAudioStream(_rewindableStream, 0, DisposeAfterUse::NO); + + stream = _loopStream; + } + + // FIXME: Should this be kMusicSoundType or KPlainSoundType? + g_system->getMixer()->playStream(Audio::Mixer::kMusicSoundType, &_handle, stream, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO); + setVolume(_lastVolume); + _paused = false; + + return true; +} + +/** + * Closes the stream. + * + * @returns True is everything is OK, False otherwise + * + */ + +bool FPStream::stop() { + if (!_soundSupported) + return true; + + if (!_fileLoaded) + return false; + + if (!g_system->getMixer()->isSoundHandleActive(_handle)) + return false; + + g_system->getMixer()->stopHandle(_handle); + + _paused = false; + + return true; +} + +void FPStream::waitForSync(FPStream *toPlay) { + // FIXME: The idea here is that you wait for this stream to reach + // a buffer which is a multiple of nBufSize/nSync, and then the + // thread stops it and immediately starts the 'toplay' stream. + + stop(); + toPlay->play(); +} + +/** + * Unables or disables stream loop. + * + * @param loop True enable loop, False disables it + * + * @remarks The loop must be activated BEFORE the stream starts + * playing. Any changes made during the play will have no + * effect until the stream is stopped then played again. + */ +void FPStream::setLoop(bool loop) { + _loop = loop; +} + +/** + * Pause sound effect + * + * @param pause True enables pause, False disables it + */ +void FPStream::setPause(bool pause) { + if (!_fileLoaded) + return; + + if (pause == _paused) + return; + + if (g_system->getMixer()->isSoundHandleActive(_handle)) + g_system->getMixer()->pauseHandle(_handle, pause); + + _paused = pause; + + // Trick to reset the volume after a possible new sound configuration + setVolume(_lastVolume); +} + +/** + * Change the volume of the stream + * + * @param volume Volume to be set (0-63) + * + */ + +void FPStream::setVolume(int volume) { + if (volume > 63) + volume = 63; + + if (volume < 0) + volume = 0; + + _lastVolume = volume; + + if (!GLOBALS._bCfgMusic) + volume = 0; + else { + volume -= (10 - GLOBALS._nCfgMusicVolume) * 2; + if (volume < 0) + volume = 0; + } + + if (g_system->getMixer()->isSoundHandleActive(_handle)) + g_system->getMixer()->setChannelVolume(_handle, volume * Audio::Mixer::kMaxChannelVolume / 63); +} + +/** + * Gets the volume of the stream + * + * @param volumePtr Variable that will contain the current volume + * + */ + +void FPStream::getVolume(int *volumePtr) { + if (g_system->getMixer()->isSoundHandleActive(_handle)) + *volumePtr = g_system->getMixer()->getChannelVolume(_handle) * 63 / Audio::Mixer::kMaxChannelVolume; + else + *volumePtr = 0; +} + +} // End of namespace Tony diff --git a/engines/tony/sound.h b/engines/tony/sound.h new file mode 100644 index 0000000000..7422de02b3 --- /dev/null +++ b/engines/tony/sound.h @@ -0,0 +1,377 @@ +/* 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 Tony Tough source code + * + * Copyright (c) 1997-2003 Nayma Software + */ + +#ifndef TONY_SOUND_H +#define TONY_SOUND_H + +#include "audio/mixer.h" +#include "common/file.h" +#include "tony/gfxcore.h" +#include "tony/loc.h" +#include "tony/utils.h" + +namespace Audio { +class RewindableAudioStream; +} + +namespace Tony { + +class FPStream; +class FPSfx; + +enum SoundCodecs { + FPCODEC_RAW, + FPCODEC_ADPCM +}; + +/** + * Sound driver For Tony Tough + */ + +class FPSound { +private: + bool _soundSupported; + +public: + /** + * Default constructor. Initializes the attributes. + * + */ + + FPSound(); + + /** + * Destroy the object and free the memory + * + */ + + ~FPSound(); + + /** + * Initializes the object, and prepare everything you need to create streams and sound effects. + * + * @returns True is everything is OK, False otherwise + */ + + bool init(); + + /** + * Allocates an object of type FPStream, and return its pointer + * + * @param streamPtr Will contain a pointer to the object you just created. + * + * @returns True is everything is OK, False otherwise + */ + + bool createStream(FPStream **streamPtr); + + /** + * Allocates an object of type FpSfx, and return its pointer + * + * @param sfxPtr Will contain a pointer to the object you just created. + * + * @returns True is everything is OK, False otherwise + */ + + bool createSfx(FPSfx **sfxPtr); + + /** + * Set the general volume + * + * @param volume Volume to set (0-63) + */ + + void setMasterVolume(int volume); + + /** + * Get the general volume + * + * @param volume Variable that will contain the volume (0-63) + */ + + void getMasterVolume(int *volume); +}; + +class FPSfx { +private: + bool _soundSupported; // True if the sound is active + bool _fileLoaded; // True is a file is opened + bool _loop; // True is sound effect should loop + int _lastVolume; + + bool _isVoice; + bool _paused; + + Audio::AudioStream *_loopStream; + Audio::RewindableAudioStream *_rewindableStream; + Audio::SoundHandle _handle; + +public: + uint32 _hEndOfBuffer; + + /** + * Check process for whether sounds have finished playing + */ + static void soundCheckProcess(CORO_PARAM, const void *param); + + /** + * Default constructor. + * + * @remarks Do *NOT* declare an object directly, but rather + * create it using FPSound::CreateSfx() + * + */ + + FPSfx(bool soundOn); + + /** + * Default Destructor. + * + * @remarks It is also stops the sound effect that may be + * currently played, and free the memory it uses. + * + */ + + ~FPSfx(); + + /** + * Releases the memory used by the object. + * + * @remarks Must be called when the object is no longer used and + * **ONLY** if the object was created by + * FPSound::CreateStream(). + * Object pointers are no longer valid after this call. + */ + + void release(); + + /** + * Opens a file and loads a sound effect. + * + * @param fileName Sfx filename + * @param codec CODEC used to uncompress the samples + * + * @returns True is everything is OK, False otherwise + */ + + bool loadFile(const char *fileName, uint32 codec = FPCODEC_RAW); + bool loadWave(Common::SeekableReadStream *stream); + bool loadVoiceFromVDB(Common::File &vdbFP); + + /** + * Play the Sfx in memory. + * + * @returns True is everything is OK, False otherwise + */ + + bool play(); + + /** + * Stops a Sfx. + * + * @returns True is everything is OK, False otherwise + */ + + bool stop(); + + /** + * Pauses a Sfx. + * + */ + + void setPause(bool pause); + + /** + * Enables or disables the Sfx loop. + * + * @param loop True to enable the loop, False to disable + * + * @remarks The loop must be activated BEFORE the sfx starts + * playing. Any changes made during the play will have + * no effect until the sfx is stopped then played again. + */ + + void setLoop(bool loop); + + /** + * Change the volume of Sfx + * + * @param volume Volume to be set (0-63) + * + */ + + void setVolume(int volume); + + /** + * Gets the Sfx volume + * + * @param volumePtr Will contain the current Sfx volume + * + */ + + void getVolume(int *volumePtr); + + /** + * Returns true if the underlying sound has ended + */ + bool endOfBuffer() const; +}; + +class FPStream { +private: + uint32 _bufferSize; // Buffer size (bytes) + uint32 _size; // Stream size (bytes) + uint32 _codec; // CODEC used + + Common::File _file; // File handle used for the stream + + bool _soundSupported; // True if the sound is active + bool _fileLoaded; // True if the file is open + bool _loop; // True if the stream should loop + bool _doFadeOut; // True if fade out is required + bool _syncExit; + bool _paused; + int _lastVolume; + FPStream *_syncToPlay; + + Audio::AudioStream *_loopStream; + Audio::RewindableAudioStream *_rewindableStream; + Audio::SoundHandle _handle; + +public: + + /** + * Default constructor. + * + * @remarks Do *NOT* declare an object directly, but rather + * create it using FPSound::CreateStream() + */ + + FPStream(bool soundOn); + + /** + * Default destructor. + * + * @remarks It calls CloseFile() if needed. + */ + + ~FPStream(); + + /** + * Releases the memory object. + * + * @remarks Must be called when the object is no longer used + * and **ONLY** if the object was created by + * FPSound::CreateStream(). + * Object pointers are no longer valid after this call. + */ + + void release(); + + /** + * Opens a file stream + * + * @param fileName Filename to be opened + * @param codec CODEC to be used to uncompress samples + * + * @returns True is everything is OK, False otherwise + */ + + bool loadFile(const Common::String &fileName, uint32 codec = FPCODEC_RAW, int sync = 2000); + + /** + * Closes a file stream (opened or not). + * + * @returns For safety, the destructor calls unloadFile() if it has not + * been mentioned explicitly. + * + * @remarks It is necessary to call this function to free the + * memory used by the stream. + */ + + bool unloadFile(); + + /** + * Play the stream. + * + * @returns True is everything is OK, False otherwise + */ + + bool play(); + void playFast(); + + /** + * Closes the stream. + * + * @returns True is everything is OK, False otherwise + */ + + bool stop(); + void waitForSync(FPStream *toPlay); + + /** + * Pause sound effect + * + * @param pause True enables pause, False disables it + */ + + void setPause(bool pause); + + /** + * Unables or disables stream loop. + * + * @param loop True enable loop, False disables it + * + * @remarks The loop must be activated BEFORE the stream starts + * playing. Any changes made during the play will have no + * effect until the stream is stopped then played again. + */ + + void setLoop(bool loop); + + /** + * Change the volume of the stream + * + * @param volume Volume to be set (0-63) + */ + + void setVolume(int volume); + + /** + * Gets the volume of the stream + * + * @param volumePtr Variable that will contain the current volume + * + */ + + void getVolume(int *volumePtr); +}; + +} // End of namespace Tony + +#endif diff --git a/engines/tony/tony.cpp b/engines/tony/tony.cpp new file mode 100644 index 0000000000..1c63096e92 --- /dev/null +++ b/engines/tony/tony.cpp @@ -0,0 +1,791 @@ +/* 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. + * + */ + +#include "common/scummsys.h" +#include "common/algorithm.h" +#include "common/config-manager.h" +#include "common/debug-channels.h" +#include "common/events.h" +#include "common/file.h" +#include "common/installshield_cab.h" +#include "tony/tony.h" +#include "tony/custom.h" +#include "tony/debugger.h" +#include "tony/game.h" +#include "tony/mpal/mpal.h" + +namespace Tony { + +TonyEngine *g_vm; + +TonyEngine::TonyEngine(OSystem *syst, const TonyGameDescription *gameDesc) : Engine(syst), + _gameDescription(gameDesc), _randomSource("tony") { + g_vm = this; + _loadSlotNumber = -1; + + // Set the up the debugger + _debugger = new Debugger(); + DebugMan.addDebugChannel(kTonyDebugAnimations, "animations", "Animations debugging"); + DebugMan.addDebugChannel(kTonyDebugActions, "actions", "Actions debugging"); + DebugMan.addDebugChannel(kTonyDebugSound, "sound", "Sound debugging"); + DebugMan.addDebugChannel(kTonyDebugMusic, "music", "Music debugging"); + + // Add folders to the search directory list + const Common::FSNode gameDataDir(ConfMan.get("path")); + SearchMan.addSubDirectoryMatching(gameDataDir, "Voices"); + SearchMan.addSubDirectoryMatching(gameDataDir, "Roasted"); + SearchMan.addSubDirectoryMatching(gameDataDir, "Music"); + SearchMan.addSubDirectoryMatching(gameDataDir, "Music/utilsfx"); + + // Set up load slot number + _initialLoadSlotNumber = -1; + if (ConfMan.hasKey("save_slot")) { + int slotNumber = ConfMan.getInt("save_slot"); + if (slotNumber >= 0 && slotNumber <= 99) + _initialLoadSlotNumber = slotNumber; + } + + // Load the ScummVM sound settings + syncSoundSettings(); + + _hEndOfFrame = 0; + for (int i = 0; i < 6; i++) + _stream[i] = NULL; + for (int i = 0; i < MAX_SFX_CHANNELS; i++) { + _sfx[i] = NULL; + _utilSfx[i] = NULL; + } + _bPaused = false; + _bDrawLocation = false; + _startTime = 0; + _curThumbnail = NULL; + _bQuitNow = false; + _bTimeFreezed = false; + _nTimeFreezed = 0; +} + +TonyEngine::~TonyEngine() { + // Close the voice database + closeVoiceDatabase(); + + // Reset the coroutine scheduler + CoroScheduler.reset(); + CoroScheduler.setResourceCallback(NULL); + + delete _debugger; +} + +/** + * Run the game + */ +Common::Error TonyEngine::run() { + Common::ErrorCode result = init(); + if (result != Common::kNoError) + return result; + + play(); + close(); + + return Common::kNoError; +} + +/** + * Initialize the game + */ +Common::ErrorCode TonyEngine::init() { + // Load DAT file (used by font manager) + if (!loadTonyDat()) + return Common::kUnknownError; + + if (isCompressed()) { + Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember("data1.cab"); + if (!stream) + error("Failed to open data1.cab"); + + Common::Archive *cabinet = Common::makeInstallShieldArchive(stream); + if (!cabinet) + error("Failed to parse data1.cab"); + + SearchMan.add("data1.cab", cabinet); + } + + _hEndOfFrame = CoroScheduler.createEvent(false, false); + + _bPaused = false; + _bDrawLocation = true; + _startTime = g_system->getMillis(); + + // Init static class fields + RMText::initStatics(); + RMTony::initStatics(); + + // Reset the scheduler + CoroScheduler.reset(); + + // Initialize the graphics window + _window.init(); + + // Initialize the function list + Common::fill(_funcList, _funcList + 300, (LPCUSTOMFUNCTION)NULL); + initCustomFunctionMap(); + + // Initializes MPAL system, passing the custom functions list + Common::File f; + if (!f.open("ROASTED.MPC")) + return Common::kReadingFailed; + f.close(); + + if (!mpalInit("ROASTED.MPC", "ROASTED.MPR", _funcList, _funcListStrings)) + return Common::kUnknownError; + + // Initialize the update resources + _resUpdate.init("ROASTED.MPU"); + + // Initialize the music + initMusic(); + + // Initialize the voices database + if (!openVoiceDatabase()) + return Common::kReadingFailed; + + // Initialize the boxes + _theBoxes.init(); + + // Link to the custom graphics engine + _theEngine.initCustomDll(); + _theEngine.init(); + + // Allocate space for thumbnails when saving the game + _curThumbnail = new uint16[160 * 120]; + + _bQuitNow = false; + + return Common::kNoError; +} + +bool TonyEngine::loadTonyDat() { + Common::String msg; + Common::File in; + + in.open("tony.dat"); + + if (!in.isOpen()) { + msg = "You're missing the 'tony.dat' file. Get it from the ScummVM website"; + GUIErrorMessage(msg); + warning("%s", msg.c_str()); + return false; + } + + // Read header + char buf[4+1]; + in.read(buf, 4); + buf[4] = '\0'; + + if (strcmp(buf, "TONY")) { + msg = "File 'tony.dat' is corrupt. Get it from the ScummVM website"; + GUIErrorMessage(msg); + warning("%s", msg.c_str()); + return false; + } + + int majVer = in.readByte(); + int minVer = in.readByte(); + + if ((majVer != TONY_DAT_VER_MAJ) || (minVer != TONY_DAT_VER_MIN)) { + msg = Common::String::format("File 'tony.dat' is wrong version. Expected %d.%d but got %d.%d. Get it from the ScummVM website", TONY_DAT_VER_MAJ, TONY_DAT_VER_MIN, majVer, minVer); + GUIErrorMessage(msg); + warning("%s", msg.c_str()); + + return false; + } + + int expectedLangVariant = -1; + switch (g_vm->getLanguage()) { + case Common::IT_ITA: + case Common::EN_ANY: + expectedLangVariant = 0; + break; + case Common::PL_POL: + expectedLangVariant = 1; + break; + case Common::RU_RUS: + expectedLangVariant = 2; + break; + case Common::CZ_CZE: + expectedLangVariant = 3; + break; + case Common::FR_FRA: + expectedLangVariant = 4; + break; + case Common::DE_DEU: + expectedLangVariant = 5; + break; + default: + warning("Unhandled language, falling back to English/Italian fonts."); + expectedLangVariant = 0; + break; + } + + int numVariant = in.readUint16BE(); + if (expectedLangVariant > numVariant - 1) { + msg = Common::String::format("Font variant not present in 'tony.dat'. Get it from the ScummVM website"); + GUIErrorMessage(msg); + warning("%s", msg.c_str()); + + return false; + } + + in.seek(in.pos() + (2 * 256 * 8 * expectedLangVariant)); + for (int i = 0; i < 256; i++) { + _cTableDialog[i] = in.readSint16BE(); + _lTableDialog[i] = in.readSint16BE(); + _cTableMacc[i] = in.readSint16BE(); + _lTableMacc[i] = in.readSint16BE(); + _cTableCred[i] = in.readSint16BE(); + _lTableCred[i] = in.readSint16BE(); + _cTableObj[i] = in.readSint16BE(); + _lTableObj[i] = in.readSint16BE(); + } + + return true; +} + +void TonyEngine::initCustomFunctionMap() { + INIT_CUSTOM_FUNCTION(_funcList, _funcListStrings); +} + +/** + * Display an error message + */ +void TonyEngine::GUIError(const Common::String &msg) { + GUIErrorMessage(msg); +} + +void TonyEngine::playMusic(int nChannel, const Common::String &fname, int nFX, bool bLoop, int nSync) { + if (nChannel < 4) { + if (GLOBALS._flipflop) + nChannel = nChannel + 1; + } + + switch (nFX) { + case 0: + case 1: + case 2: + _stream[nChannel]->stop(); + _stream[nChannel]->unloadFile(); + break; + + case 22: + break; + } + + if (nFX == 22) { // Sync a tempo + GLOBALS._curChannel = nChannel; + GLOBALS._nextLoop = bLoop; + GLOBALS._nextSync = nSync; + GLOBALS._nextMusic = fname; + + if (GLOBALS._flipflop) + GLOBALS._nextChannel = nChannel - 1; + else + GLOBALS._nextChannel = nChannel + 1; + + uint32 hThread = CoroScheduler.createProcess(doNextMusic, NULL, 0); + assert(hThread != CORO_INVALID_PID_VALUE); + + } else if (nFX == 44) { // Change the channel and let the first finish + if (GLOBALS._flipflop) + GLOBALS._nextChannel = nChannel - 1; + else + GLOBALS._nextChannel = nChannel + 1; + + _stream[GLOBALS._nextChannel]->stop(); + _stream[GLOBALS._nextChannel]->unloadFile(); + + if (!getIsDemo()) { + if (!_stream[GLOBALS._nextChannel]->loadFile(fname, FPCODEC_ADPCM, nSync)) + error("failed to open music file '%s'", fname.c_str()); + } else { + _stream[GLOBALS._nextChannel]->loadFile(fname, FPCODEC_ADPCM, nSync); + } + + _stream[GLOBALS._nextChannel]->setLoop(bLoop); + _stream[GLOBALS._nextChannel]->play(); + + GLOBALS._flipflop = 1 - GLOBALS._flipflop; + } else { + if (!getIsDemo()) { + if (!_stream[nChannel]->loadFile(fname, FPCODEC_ADPCM, nSync)) + error("failed to open music file '%s'", fname.c_str()); + } else { + _stream[nChannel]->loadFile(fname, FPCODEC_ADPCM, nSync); + } + + _stream[nChannel]->setLoop(bLoop); + _stream[nChannel]->play(); + } +} + +void TonyEngine::doNextMusic(CORO_PARAM, const void *param) { + CORO_BEGIN_CONTEXT; + Common::String fn; + CORO_END_CONTEXT(_ctx); + + FPStream **streams = g_vm->_stream; + + CORO_BEGIN_CODE(_ctx); + + if (!g_vm->getIsDemo()) { + if (!streams[GLOBALS._nextChannel]->loadFile(GLOBALS._nextMusic, FPCODEC_ADPCM, GLOBALS._nextSync)) + error("failed to open next music file '%s'", GLOBALS._nextMusic.c_str()); + } else { + streams[GLOBALS._nextChannel]->loadFile(GLOBALS._nextMusic, FPCODEC_ADPCM, GLOBALS._nextSync); + } + + streams[GLOBALS._nextChannel]->setLoop(GLOBALS._nextLoop); + //streams[GLOBALS._nextChannel]->prefetch(); + + streams[GLOBALS._curChannel]->waitForSync(streams[GLOBALS._nextChannel]); + + streams[GLOBALS._curChannel]->unloadFile(); + + GLOBALS._flipflop = 1 - GLOBALS._flipflop; + + CORO_END_CODE; +} + +void TonyEngine::playSFX(int nChannel, int nFX) { + if (_sfx[nChannel] == NULL) + return; + + switch (nFX) { + case 0: + _sfx[nChannel]->setLoop(false); + break; + + case 1: + _sfx[nChannel]->setLoop(true); + break; + } + + _sfx[nChannel]->play(); +} + +void TonyEngine::stopMusic(int nChannel) { + if (nChannel < 4) + _stream[nChannel + GLOBALS._flipflop]->stop(); + else + _stream[nChannel]->stop(); +} + +void TonyEngine::stopSFX(int nChannel) { + _sfx[nChannel]->stop(); +} + +void TonyEngine::playUtilSFX(int nChannel, int nFX) { + if (_utilSfx[nChannel] == NULL) + return; + + switch (nFX) { + case 0: + _utilSfx[nChannel]->setLoop(false); + break; + + case 1: + _utilSfx[nChannel]->setLoop(true); + break; + } + + _utilSfx[nChannel]->setVolume(52); + _utilSfx[nChannel]->play(); +} + +void TonyEngine::stopUtilSFX(int nChannel) { + _utilSfx[nChannel]->stop(); +} + +void TonyEngine::preloadSFX(int nChannel, const char *fn) { + if (_sfx[nChannel] != NULL) { + _sfx[nChannel]->stop(); + _sfx[nChannel]->release(); + _sfx[nChannel] = NULL; + } + + _theSound.createSfx(&_sfx[nChannel]); + + _sfx[nChannel]->loadFile(fn, FPCODEC_ADPCM); +} + +FPSfx *TonyEngine::createSFX(Common::SeekableReadStream *stream) { + FPSfx *sfx; + + _theSound.createSfx(&sfx); + sfx->loadWave(stream); + return sfx; +} + +void TonyEngine::preloadUtilSFX(int nChannel, const char *fn) { + if (_utilSfx[nChannel] != NULL) { + _utilSfx[nChannel]->stop(); + _utilSfx[nChannel]->release(); + _utilSfx[nChannel] = NULL; + } + + _theSound.createSfx(&_utilSfx[nChannel]); + + _utilSfx[nChannel]->loadFile(fn, FPCODEC_ADPCM); + _utilSfx[nChannel]->setVolume(63); +} + +void TonyEngine::unloadAllSFX() { + for (int i = 0; i < MAX_SFX_CHANNELS; i++) { + if (_sfx[i] != NULL) { + _sfx[i]->stop(); + _sfx[i]->release(); + _sfx[i] = NULL; + } + } +} + +void TonyEngine::unloadAllUtilSFX() { + for (int i = 0; i < MAX_SFX_CHANNELS; i++) { + if (_utilSfx[i] != NULL) { + _utilSfx[i]->stop(); + _utilSfx[i]->release(); + _utilSfx[i] = NULL; + } + } +} + +void TonyEngine::initMusic() { + int i; + + _theSound.init(); + _theSound.setMasterVolume(63); + + for (i = 0; i < 6; i++) + _theSound.createStream(&_stream[i]); + + for (i = 0; i < MAX_SFX_CHANNELS; i++) { + _sfx[i] = _utilSfx[i] = NULL; + } + + // Preload sound effects + preloadUtilSFX(0, "U01.ADP"); // Reversed!! + preloadUtilSFX(1, "U02.ADP"); + + // Start check processes for sound + CoroScheduler.createProcess(FPSfx::soundCheckProcess, NULL); +} + +void TonyEngine::closeMusic() { + for (int i = 0; i < 6; i++) { + _stream[i]->stop(); + _stream[i]->unloadFile(); + _stream[i]->release(); + } + + unloadAllSFX(); + unloadAllUtilSFX(); +} + +void TonyEngine::pauseSound(bool bPause) { + _theEngine.pauseSound(bPause); + + for (uint i = 0; i < 6; i++) + if (_stream[i]) + _stream[i]->setPause(bPause); + + for (uint i = 0; i < MAX_SFX_CHANNELS; i++) { + if (_sfx[i]) + _sfx[i]->setPause(bPause); + if (_utilSfx[i]) + _utilSfx[i]->setPause(bPause); + } +} + +void TonyEngine::setMusicVolume(int nChannel, int volume) { + _stream[nChannel + GLOBALS._flipflop]->setVolume(volume); +} + +int TonyEngine::getMusicVolume(int nChannel) { + int volume; + _stream[nChannel + GLOBALS._flipflop]->getVolume(&volume); + return volume; +} + +Common::String TonyEngine::getSaveStateFileName(int n) { + return Common::String::format("tony.%03d", n); +} + +void TonyEngine::autoSave(CORO_PARAM) { + CORO_BEGIN_CONTEXT; + Common::String buf; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + grabThumbnail(); + CORO_INVOKE_2(CoroScheduler.waitForSingleObject, g_vm->_hEndOfFrame, CORO_INFINITE); + CORO_INVOKE_2(CoroScheduler.waitForSingleObject, g_vm->_hEndOfFrame, CORO_INFINITE); + _ctx->buf = getSaveStateFileName(0); + _theEngine.saveState(_ctx->buf, (byte *)_curThumbnail, "Autosave"); + + CORO_END_CODE; +} + +void TonyEngine::saveState(int n, const char *name) { + Common::String buf = getSaveStateFileName(n); + _theEngine.saveState(buf.c_str(), (byte *)_curThumbnail, name); +} + +void TonyEngine::loadState(CORO_PARAM, int n) { + CORO_BEGIN_CONTEXT; + Common::String buf; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + _ctx->buf = getSaveStateFileName(n); + CORO_INVOKE_1(_theEngine.loadState, _ctx->buf.c_str()); + + CORO_END_CODE; +} + +bool TonyEngine::openVoiceDatabase() { + char id[4]; + uint32 numfiles; + + // Open the voices database + if (!_vdbFP.open("voices.vdb")) + return false; + + _vdbFP.seek(-8, SEEK_END); + numfiles = _vdbFP.readUint32LE(); + _vdbFP.read(id, 4); + + if (id[0] != 'V' || id[1] != 'D' || id[2] != 'B' || id[3] != '1') { + _vdbFP.close(); + return false; + } + + // Read in the index + _vdbFP.seek(-8 - (numfiles * VOICE_HEADER_SIZE), SEEK_END); + + for (uint32 i = 0; i < numfiles; ++i) { + VoiceHeader vh; + vh._offset = _vdbFP.readUint32LE(); + vh._code = _vdbFP.readUint32LE(); + vh._parts = _vdbFP.readUint32LE(); + + _voices.push_back(vh); + } + + return true; +} + +void TonyEngine::closeVoiceDatabase() { + if (_vdbFP.isOpen()) + _vdbFP.close(); + + if (_voices.size() > 0) + _voices.clear(); +} + +void TonyEngine::grabThumbnail() { + _window.grabThumbnail(_curThumbnail); +} + +uint16 *TonyEngine::getThumbnail() { + return _curThumbnail; +} + +void TonyEngine::quitGame() { + _bQuitNow = true; +} + +void TonyEngine::openInitLoadMenu(CORO_PARAM) { + _theEngine.openOptionScreen(coroParam, 1); +} + +void TonyEngine::openInitOptions(CORO_PARAM) { + _theEngine.openOptionScreen(coroParam, 2); +} + +/** + * Main process for playing the game. + * + * @remarks This needs to be in a separate process, since there are some things that can briefly + * block the execution of process. For now, all ScummVm event handling is dispatched to within the context of this + * process. If it ever proves a problem, we may have to look into whether it's feasible to have it still remain + * in the outer 'main' process. + */ +void TonyEngine::playProcess(CORO_PARAM, const void *param) { + CORO_BEGIN_CONTEXT; + Common::String fn; + CORO_END_CONTEXT(_ctx); + + + CORO_BEGIN_CODE(_ctx); + + // Game loop. We rely on the outer main process to detect if a shutdown is required, + // and kill the scheudler and all the processes, including this one + for (;;) { + // If a savegame needs to be loaded, then do so + if (g_vm->_loadSlotNumber != -1 && GLOBALS._gfxEngine != NULL) { + _ctx->fn = getSaveStateFileName(g_vm->_loadSlotNumber); + CORO_INVOKE_1(GLOBALS._gfxEngine->loadState, _ctx->fn); + g_vm->_loadSlotNumber = -1; + } + + // Wait for the next frame + CORO_INVOKE_1(CoroScheduler.sleep, 50); + + // Call the engine to handle the next frame + CORO_INVOKE_1(g_vm->_theEngine.doFrame, g_vm->_bDrawLocation); + + // Warns that a frame is finished + CoroScheduler.pulseEvent(g_vm->_hEndOfFrame); + + // Handle drawing the frame + if (!g_vm->_bPaused) { + if (!g_vm->_theEngine._bWiping) + g_vm->_window.getNewFrame(g_vm->_theEngine, NULL); + else + g_vm->_window.getNewFrame(g_vm->_theEngine, &g_vm->_theEngine._rcWipeEllipse); + } + + // Paint the frame onto the screen + g_vm->_window.repaint(); + + // Signal the ScummVM debugger + g_vm->_debugger->onFrame(); + } + + CORO_END_CODE; +} + +/** + * Play the game + */ +void TonyEngine::play() { + // Create the game player process + CoroScheduler.createProcess(playProcess, NULL); + + // Loop through calling the scheduler until it's time for the game to quit + while (!shouldQuit() && !_bQuitNow) { + // Delay for a brief amount + g_system->delayMillis(10); + + // Call any scheduled processes + CoroScheduler.schedule(); + } +} + +void TonyEngine::close() { + closeMusic(); + CoroScheduler.closeEvent(_hEndOfFrame); + _theBoxes.close(); + _theEngine.close(); + _window.close(); + mpalFree(); + freeMpc(); + delete[] _curThumbnail; +} + +void TonyEngine::freezeTime() { + _bTimeFreezed = true; + _nTimeFreezed = getTime() - _startTime; +} + +void TonyEngine::unfreezeTime() { + _bTimeFreezed = false; +} + +/** + * Returns the millisecond timer + */ +uint32 TonyEngine::getTime() { + return g_system->getMillis(); +} + +bool TonyEngine::canLoadGameStateCurrently() { + return GLOBALS._gfxEngine != NULL && GLOBALS._gfxEngine->canLoadSave(); +} +bool TonyEngine::canSaveGameStateCurrently() { + return GLOBALS._gfxEngine != NULL && GLOBALS._gfxEngine->canLoadSave(); +} + +Common::Error TonyEngine::loadGameState(int slot) { + _loadSlotNumber = slot; + return Common::kNoError; +} + +Common::Error TonyEngine::saveGameState(int slot, const Common::String &desc) { + if (!GLOBALS._gfxEngine) + return Common::kUnknownError; + + RMGfxTargetBuffer &bigBuf = *GLOBALS._gfxEngine; + RMSnapshot s; + s.grabScreenshot(bigBuf, 4, _curThumbnail); + + GLOBALS._gfxEngine->saveState(getSaveStateFileName(slot), (byte *)_curThumbnail, desc); + return Common::kNoError; +} + +void TonyEngine::syncSoundSettings() { + Engine::syncSoundSettings(); + + GLOBALS._bCfgDubbing = !ConfMan.getBool("mute") && !ConfMan.getBool("speech_mute"); + GLOBALS._bCfgSFX = !ConfMan.getBool("mute") && !ConfMan.getBool("sfx_mute"); + GLOBALS._bCfgMusic = !ConfMan.getBool("mute") && !ConfMan.getBool("music_mute"); + + GLOBALS._nCfgDubbingVolume = ConfMan.getInt("speech_volume") * 10 / 256; + GLOBALS._nCfgSFXVolume = ConfMan.getInt("sfx_volume") * 10 / 256; + GLOBALS._nCfgMusicVolume = ConfMan.getInt("music_volume") * 10 / 256; + + GLOBALS._bShowSubtitles = ConfMan.getBool("subtitles"); + GLOBALS._nCfgTextSpeed = ConfMan.getInt("talkspeed") * 10 / 256; +} + +void TonyEngine::saveSoundSettings() { + ConfMan.setBool("speech_mute", !GLOBALS._bCfgDubbing); + ConfMan.setBool("sfx_mute", !GLOBALS._bCfgSFX); + ConfMan.setBool("music_mute", !GLOBALS._bCfgMusic); + + ConfMan.setInt("speech_volume", GLOBALS._nCfgDubbingVolume * 256 / 10); + ConfMan.setInt("sfx_volume", GLOBALS._nCfgSFXVolume * 256 / 10); + ConfMan.setInt("music_volume", GLOBALS._nCfgMusicVolume * 256 / 10); + + ConfMan.setBool("subtitles", GLOBALS._bShowSubtitles); + ConfMan.setInt("talkspeed", GLOBALS._nCfgTextSpeed * 256 / 10); +} + +void TonyEngine::showLocation() { + _bDrawLocation = true; +} + +void TonyEngine::hideLocation() { + _bDrawLocation = false; +} + +} // End of namespace Tony diff --git a/engines/tony/tony.h b/engines/tony/tony.h new file mode 100644 index 0000000000..332b122923 --- /dev/null +++ b/engines/tony/tony.h @@ -0,0 +1,242 @@ +/* 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. + * + */ + +#ifndef TONY_H +#define TONY_H + +#include "common/scummsys.h" +#include "common/system.h" +#include "common/array.h" +#include "common/coroutines.h" +#include "common/error.h" +#include "common/random.h" +#include "common/util.h" +#include "engines/engine.h" + +#include "tony/mpal/mpal.h" +#include "tony/mpal/memory.h" +#include "tony/debugger.h" +#include "tony/gfxengine.h" +#include "tony/loc.h" +#include "tony/utils.h" +#include "tony/window.h" +#include "tony/globals.h" + +/** + * This is the namespace of the Tony engine. + * + * Status of this engine: In Development + * + * Games using this engine: + * - Tony Tough + */ +namespace Tony { + +using namespace MPAL; + +class Globals; + +enum { + kTonyDebugAnimations = 1 << 0, + kTonyDebugActions = 1 << 1, + kTonyDebugSound = 1 << 2, + kTonyDebugMusic = 1 << 3, + kTonyDebugMPAL = 1 << 4 +}; + +#define DEBUG_BASIC 1 +#define DEBUG_INTERMEDIATE 2 +#define DEBUG_DETAILED 3 + +struct TonyGameDescription; + +#define MAX_SFX_CHANNELS 32 +#define TONY_DAT_VER_MAJ 0 +#define TONY_DAT_VER_MIN 3 + +struct VoiceHeader { + int _offset; + int _code; + int _parts; +}; +#define VOICE_HEADER_SIZE 12 + +class TonyEngine : public Engine { +private: + Common::ErrorCode init(); + bool loadTonyDat(); + void initMusic(); + void closeMusic(); + bool openVoiceDatabase(); + void closeVoiceDatabase(); + void initCustomFunctionMap(); + static void playProcess(CORO_PARAM, const void *param); + static void doNextMusic(CORO_PARAM, const void *param); + +protected: + // Engine APIs + virtual Common::Error run(); + virtual bool hasFeature(EngineFeature f) const; +public: + LPCUSTOMFUNCTION _funcList[300]; + Common::String _funcListStrings[300]; + Common::RandomSource _randomSource; + RMResUpdate _resUpdate; + uint32 _hEndOfFrame; + Common::File _vdbFP; + Common::Array<VoiceHeader> _voices; + FPSound _theSound; + Common::List<FPSfx *> _activeSfx; + Globals _globals; + Debugger *_debugger; + + int16 _cTableDialog[256]; + int16 _lTableDialog[256]; + int16 _cTableMacc[256]; + int16 _lTableMacc[256]; + int16 _cTableCred[256]; + int16 _lTableCred[256]; + int16 _cTableObj[256]; + int16 _lTableObj[256]; + + enum DataDir { + DD_BASE = 1, + DD_SAVE, + DD_SHOTS, + DD_MUSIC, + DD_LAYER, + DD_UTILSFX, + DD_VOICES, + DD_BASE2 + }; + + FPStream *_stream[6]; + FPSfx *_sfx[MAX_SFX_CHANNELS]; + FPSfx *_utilSfx[MAX_SFX_CHANNELS]; + bool _bPaused; + bool _bDrawLocation; + int _startTime; + uint16 *_curThumbnail; + int _initialLoadSlotNumber; + int _loadSlotNumber; + + // Bounding box list manager + RMGameBoxes _theBoxes; + RMWindow _window; + RMGfxEngine _theEngine; + + bool _bQuitNow; + bool _bTimeFreezed; + int _nTimeFreezed; +public: + TonyEngine(OSystem *syst, const TonyGameDescription *gameDesc); + virtual ~TonyEngine(); + + const TonyGameDescription *_gameDescription; + uint32 getFeatures() const; + Common::Language getLanguage() const; + uint16 getVersion() const; + bool getIsDemo() const; + bool isCompressed() const; + RMGfxEngine *getEngine() { + return &_theEngine; + } + void GUIError(const Common::String &msg); + + virtual bool canLoadGameStateCurrently(); + virtual bool canSaveGameStateCurrently(); + Common::Error loadGameState(int slot); + Common::Error saveGameState(int slot, const Common::String &desc); + + void play(); + void close(); + + void getDataDirectory(DataDir dir, char *path); + + void showLocation(); + void hideLocation(); + + /** + * Reads the time + */ + uint32 getTime(); + void freezeTime(); + void unfreezeTime(); + + // Music + // ****** + void playMusic(int nChannel, const Common::String &fn, int nFX, bool bLoop, int nSync); + void stopMusic(int nChannel); + + void playSFX(int nSfx, int nFX = 0); + void stopSFX(int nSfx); + + void playUtilSFX(int nSfx, int nFX = 0); + void stopUtilSFX(int nSfx); + + FPSfx *createSFX(Common::SeekableReadStream *stream); + + void preloadSFX(int nSfx, const char *fn); + void unloadAllSFX(); + + void preloadUtilSFX(int nSfx, const char *fn); + void unloadAllUtilSFX(); + + /** + * Stop all the audio + */ + void pauseSound(bool bPause); + + void setMusicVolume(int nChannel, int volume); + int getMusicVolume(int nChannel); + + /** + * Handle saving + */ + void autoSave(CORO_PARAM); + void saveState(int n, const char *name); + void loadState(CORO_PARAM, int n); + static Common::String getSaveStateFileName(int n); + + /** + * Get a thumbnail + */ + void grabThumbnail(); + uint16 *getThumbnail(); + + void quitGame(); + + void openInitLoadMenu(CORO_PARAM); + void openInitOptions(CORO_PARAM); + + virtual void syncSoundSettings(); + void saveSoundSettings(); +}; + +// Global reference to the TonyEngine object +extern TonyEngine *g_vm; + +#define GLOBALS g_vm->_globals + +} // End of namespace Tony + +#endif /* TONY_H */ diff --git a/engines/tony/tonychar.cpp b/engines/tony/tonychar.cpp new file mode 100644 index 0000000000..c7fa1e4a7b --- /dev/null +++ b/engines/tony/tonychar.cpp @@ -0,0 +1,1945 @@ +/* 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 Tony Tough source code + * + * Copyright (c) 1997-2003 Nayma Software + */ + +#include "tony/mpal/memory.h" +#include "tony/mpal/mpalutils.h" +#include "tony/game.h" +#include "tony/tonychar.h" +#include "tony/tony.h" + +namespace Tony { + +bool RMTony::_bAction = false; + +void RMTony::initStatics() { + _bAction = false; +} + +RMTony::RMTony() { + _bShow = false; + _bShowShadow = false; + _bBodyFront = false; + _bActionPending = false; + _actionItem = NULL; + _action = 0; + _actionParm = 0; + _bShepherdess = false; + _bIsStaticTalk = false; + _bIsTalking = false; + _nPatB4Talking = 0; + _nTalkType = TALK_NORMAL; + _talkDirection = UP; + _nTimeLastStep = 0; + _hActionThread = CORO_INVALID_PID_VALUE; +} + +void RMTony::waitEndOfAction(CORO_PARAM, const void *param) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + uint32 pid = *(const uint32 *)param; + + CORO_BEGIN_CODE(_ctx); + + CORO_INVOKE_2(CoroScheduler.waitForSingleObject, pid, CORO_INFINITE); + + _bAction = false; + + CORO_END_CODE; +} + +RMGfxSourceBuffer *RMTony::newItemSpriteBuffer(int dimx, int dimy, bool bPreRLE) { + RMGfxSourceBuffer8RLE *spr; + + assert(_cm == CM_256); + spr = new RMGfxSourceBuffer8RLEByteAA; + spr->setAlphaBlendColor(1); + if (bPreRLE) + spr->setAlreadyCompressed(); + return spr; +} + +void RMTony::init() { + RMRes tony(0); + RMRes body(9999); + + // Tony is shown by default + _bShow = _bShowShadow = true; + + // No action pending + _bActionPending = false; + _bAction = false; + + _bShepherdess = false; + _bIsTalking = false; + _bIsStaticTalk = false; + + // Opens the buffer + Common::SeekableReadStream *ds = tony.getReadStream(); + + // Reads his details from the stream + readFromStream(*ds, true); + + // Closes the buffer + delete ds; + + // Reads Tony's body + ds = body.getReadStream(); + _body.readFromStream(*ds, true); + delete ds; + _body.setPattern(0); + + _nTimeLastStep = g_vm->getTime(); +} + + +void RMTony::close() { + // Deallocation of missing item +// _shadow.destroy(); +} + +void RMTony::doFrame(CORO_PARAM, RMGfxTargetBuffer *bigBuf, int curLoc) { + CORO_BEGIN_CONTEXT; + int time; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + if (!_nInList && _bShow) + bigBuf->addPrim(new RMGfxPrimitive(this)); + + setSpeed(GLOBALS._nCfgTonySpeed); + + // Runs the normal character movement + _ctx->time = g_vm->getTime(); + + do { + _nTimeLastStep += (1000 / 40); + CORO_INVOKE_2(RMCharacter::doFrame, bigBuf, curLoc); + } while (_ctx->time > _nTimeLastStep + (1000 / 40)); + + // Check if we are at the end of a path + if (endOfPath() && _bActionPending) { + // Must perform the action on which we clicked + _bActionPending = false; + } + + if (_bIsTalking || _bIsStaticTalk) + _body.doFrame(bigBuf, false); + + CORO_END_CODE; +} + +void RMTony::show() { + _bShow = true; + _bShowShadow = true; +} + +void RMTony::hide(bool bShowShadow) { + _bShow = false; + _bShowShadow = bShowShadow; +} + + +void RMTony::draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + // Call the Draw() of the parent class if Tony is visible + if (_bShow && _bDrawNow) { + if (_bBodyFront) { + prim->getDst().setEmpty(); + prim->getDst().offset(-44, -134); + if (_bShepherdess) + prim->getDst().offset(1, 4); + CORO_INVOKE_2(RMCharacter::draw, bigBuf, prim); + } + + if (_bIsTalking || _bIsStaticTalk) { + // Offest direction from scrolling + prim->getDst().setEmpty(); + prim->getDst().offset(-_curScroll); + prim->getDst().offset(_pos); + prim->getDst().offset(-44, -134); + prim->getDst() += _nBodyOffset; + CORO_INVOKE_2(_body.draw, bigBuf, prim); + } + + if (!_bBodyFront) { + prim->getDst().setEmpty(); + prim->getDst().offset(-44, -134); + if (_bShepherdess) + prim->getDst().offset(0, 3); + CORO_INVOKE_2(RMCharacter::draw, bigBuf, prim); + } + } + + CORO_END_CODE; +} + +void RMTony::moveAndDoAction(CORO_PARAM, RMPoint dst, RMItem *item, int nAction, int nActionParm) { + CORO_BEGIN_CONTEXT; + bool result; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + // Makes normal movement, but remember if you must then perform an action + if (item == NULL) { + _bActionPending = false; + _actionItem = NULL; + } else { + _actionItem = item; + _action = nAction; + _actionParm = nActionParm; + _bActionPending = true; + } + + CORO_INVOKE_2(RMCharacter::move, dst, &_ctx->result); + if (!_ctx->result) { + _bActionPending = false; + _actionItem = NULL; + } + + CORO_END_CODE; +} + + +void RMTony::executeAction(int nAction, int nActionItem, int nParm) { + uint32 pid; + + if (nAction == TA_COMBINE) { + pid = mpalQueryDoAction(TA_COMBINE, nParm, nActionItem); + + // If you failed the combine, we have RECEIVECOMBINE as a fallback + if (pid == CORO_INVALID_PID_VALUE) { + pid = mpalQueryDoAction(TA_RECEIVECOMBINE, nActionItem, nParm); + + // If you failed with that, go with the generic + // @@@ CombineGive! + if (pid == CORO_INVALID_PID_VALUE) { + pid = mpalQueryDoAction(TA_COMBINE, nParm, 0); + + if (pid == CORO_INVALID_PID_VALUE) { + pid = mpalQueryDoAction(TA_RECEIVECOMBINE, nActionItem, 0); + } + } + } + } else { + // Perform the action + pid = mpalQueryDoAction(nAction, nActionItem, 0); + } + + if (pid != CORO_INVALID_PID_VALUE) { + _bAction = true; + CoroScheduler.createProcess(waitEndOfAction, &pid, sizeof(uint32)); + _hActionThread = pid; + } else if (nAction != TA_GOTO) { + if (nAction == TA_TALK) { + pid = mpalQueryDoAction(6, 1, 0); + _bAction = true; + CoroScheduler.createProcess(waitEndOfAction, &pid, sizeof(uint32)); + _hActionThread = pid; + } else if (nAction == TA_PERORATE) { + pid = mpalQueryDoAction(7, 1, 0); + _bAction = true; + CoroScheduler.createProcess(waitEndOfAction, &pid, sizeof(uint32)); + _hActionThread = pid; + } else { + pid = mpalQueryDoAction(5, 1, 0); + _bAction = true; + CoroScheduler.createProcess(waitEndOfAction, &pid, sizeof(uint32)); + _hActionThread = pid; + } + } +} + + +void RMTony::stopNoAction(CORO_PARAM) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + if (_bAction) + CORO_INVOKE_2(CoroScheduler.waitForSingleObject, _hActionThread, CORO_INFINITE); + + _bActionPending = false; + _actionItem = NULL; + CORO_INVOKE_0(stop); + + CORO_END_CODE; +} + +void RMTony::stop(CORO_PARAM) { + CORO_BEGIN_CONTEXT; + uint32 pid; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + if (_actionItem != NULL) { + // Call MPAL to choose the direction + _ctx->pid = mpalQueryDoAction(21, _actionItem->mpalCode(), 0); + + if (_ctx->pid == CORO_INVALID_PID_VALUE) + CORO_INVOKE_0(RMCharacter::stop); + else { + _bNeedToStop = false; // If we make the OnWhichDirection, we don't need at least after the Stop(). + _bMoving = false; + CORO_INVOKE_2(CoroScheduler.waitForSingleObject, _ctx->pid, CORO_INFINITE); // @@@ Put an assert after 10 seconds + } + } else { + CORO_INVOKE_0(RMCharacter::stop); + } + + if (!_bActionPending) + return; + + _bActionPending = false; + + executeAction(_action, _actionItem->mpalCode(), _actionParm); + + _actionItem = NULL; + + CORO_END_CODE; +} + + +int RMTony::getCurPattern() { + int nPatt = RMCharacter::getCurPattern(); + + if (!_bShepherdess) + return nPatt; + + switch (nPatt) { + case PAT_PAST_STANDUP: + return PAT_STANDUP; + case PAT_PAST_STANDDOWN: + return PAT_STANDDOWN; + case PAT_PAST_STANDLEFT: + return PAT_STANDLEFT; + case PAT_PAST_STANDRIGHT: + return PAT_STANDRIGHT; + case PAT_PAST_WALKUP: + return PAT_WALKUP; + case PAT_PAST_WALKDOWN: + return PAT_WALKDOWN; + case PAT_PAST_WALKLEFT: + return PAT_WALKLEFT; + case PAT_PAST_WALKRIGHT: + return PAT_WALKRIGHT; + } + + return nPatt; +} + +void RMTony::setPattern(int nPatt, bool bPlayP0) { + if (_bShepherdess) { + switch (nPatt) { + case PAT_STANDUP: + nPatt = PAT_PAST_STANDUP; + break; + case PAT_STANDDOWN: + nPatt = PAT_PAST_STANDDOWN; + break; + case PAT_STANDLEFT: + nPatt = PAT_PAST_STANDLEFT; + break; + case PAT_STANDRIGHT: + nPatt = PAT_PAST_STANDRIGHT; + break; + case PAT_WALKUP: + nPatt = PAT_PAST_WALKUP; + break; + case PAT_WALKDOWN: + nPatt = PAT_PAST_WALKDOWN; + break; + case PAT_WALKLEFT: + nPatt = PAT_PAST_WALKLEFT; + break; + case PAT_WALKRIGHT: + nPatt = PAT_PAST_WALKRIGHT; + break; + } + } + + RMCharacter::setPattern(nPatt, bPlayP0); +} + + +void RMTony::take(int nWhere, int nPart) { + if (nPart == 0) { + switch (getCurPattern()) { + case PAT_STANDDOWN: + assert(0); // Not while you're doing a StandDown + break; + + case PAT_STANDUP: + switch (nWhere) { + case 0: + setPattern(PAT_TAKEUP_UP1); + break; + case 1: + setPattern(PAT_TAKEUP_MID1); + break; + case 2: + setPattern(PAT_TAKEUP_DOWN1); + break; + } + break; + + case PAT_STANDRIGHT: + switch (nWhere) { + case 0: + setPattern(PAT_TAKERIGHT_UP1); + break; + case 1: + setPattern(PAT_TAKERIGHT_MID1); + break; + case 2: + setPattern(PAT_TAKERIGHT_DOWN1); + break; + } + break; + + case PAT_STANDLEFT: + switch (nWhere) { + case 0: + setPattern(PAT_TAKELEFT_UP1); + break; + case 1: + setPattern(PAT_TAKELEFT_MID1); + break; + case 2: + setPattern(PAT_TAKELEFT_DOWN1); + break; + } + break; + } + } else if (nPart == 1) { + setPattern(getCurPattern() + 1); + } else if (nPart == 2) { + switch (getCurPattern()) { + case PAT_TAKEUP_UP2: + case PAT_TAKEUP_MID2: + case PAT_TAKEUP_DOWN2: + setPattern(PAT_STANDUP); + break; + + case PAT_TAKELEFT_UP2: + case PAT_TAKELEFT_MID2: + case PAT_TAKELEFT_DOWN2: + setPattern(PAT_STANDLEFT); + break; + + case PAT_TAKERIGHT_UP2: + case PAT_TAKERIGHT_MID2: + case PAT_TAKERIGHT_DOWN2: + setPattern(PAT_STANDRIGHT); + break; + } + } +} + + +void RMTony::put(int nWhere, int nPart) { + if (nPart == 0) { + switch (getCurPattern()) { + case PAT_STANDDOWN: + break; + + case PAT_STANDUP: + switch (nWhere) { + case 0: + setPattern(PAT_PUTUP_UP1); + break; + case 1: + setPattern(PAT_PUTUP_MID1); + break; + case 2: + setPattern(PAT_PUTUP_DOWN1); + break; + } + break; + + case PAT_STANDRIGHT: + switch (nWhere) { + case 0: + setPattern(PAT_PUTRIGHT_UP1); + break; + case 1: + setPattern(PAT_PUTRIGHT_MID1); + break; + case 2: + setPattern(PAT_PUTRIGHT_DOWN1); + break; + } + break; + + case PAT_STANDLEFT: + switch (nWhere) { + case 0: + setPattern(PAT_PUTLEFT_UP1); + break; + case 1: + setPattern(PAT_PUTLEFT_MID1); + break; + case 2: + setPattern(PAT_PUTLEFT_DOWN1); + break; + } + break; + } + } else if (nPart == 1) { + setPattern(getCurPattern() + 1); + } else if (nPart == 2) { + switch (getCurPattern()) { + case PAT_PUTUP_UP2: + case PAT_PUTUP_MID2: + case PAT_PUTUP_DOWN2: + setPattern(PAT_STANDUP); + break; + + case PAT_PUTLEFT_UP2: + case PAT_PUTLEFT_MID2: + case PAT_PUTLEFT_DOWN2: + setPattern(PAT_STANDLEFT); + break; + + case PAT_PUTRIGHT_UP2: + case PAT_PUTRIGHT_MID2: + case PAT_PUTRIGHT_DOWN2: + setPattern(PAT_STANDRIGHT); + break; + } + } +} + + +bool RMTony::startTalkCalculate(CharacterTalkType nTalkType, int &headStartPat, int &bodyStartPat, + int &headLoopPat, int &bodyLoopPat) { + assert(!_bIsTalking); + + _bIsTalking = true; + _nPatB4Talking = getCurPattern(); + _nTalkType = nTalkType; + + // Set the direction of speech ONLY if we are not in a static animation (since it would have already been done) + if (!_bIsStaticTalk) { + switch (_nPatB4Talking) { + case PAT_STANDDOWN: + _talkDirection = DOWN; + break; + + case PAT_TAKELEFT_UP2: + case PAT_TAKELEFT_MID2: + case PAT_TAKELEFT_DOWN2: + case PAT_GETUPLEFT: + case PAT_STANDLEFT: + _talkDirection = LEFT; + break; + + case PAT_TAKERIGHT_UP2: + case PAT_TAKERIGHT_MID2: + case PAT_TAKERIGHT_DOWN2: + case PAT_GETUPRIGHT: + case PAT_STANDRIGHT: + _talkDirection = RIGHT; + break; + + case PAT_TAKEUP_UP2: + case PAT_TAKEUP_MID2: + case PAT_TAKEUP_DOWN2: + case PAT_STANDUP: + _talkDirection = UP; + break; + } + + // Puts the body in front by default + _bBodyFront = true; + } + + if (_bShepherdess) { + // Talking whilst a shepherdess + switch (_talkDirection) { + case UP: + setPattern(PAT_PAST_TALKUP); + break; + + case DOWN: + setPattern(PAT_PAST_TALKDOWN); + break; + + case LEFT: + setPattern(PAT_PAST_TALKLEFT); + break; + + case RIGHT: + setPattern(PAT_PAST_TALKRIGHT); + break; + } + return false; + } + + headStartPat = bodyStartPat = 0; + bodyLoopPat = 0; + + switch (nTalkType) { + case TALK_NORMAL: + _bBodyFront = false; + headStartPat = 0; + bodyStartPat = 0; + + switch (_talkDirection) { + case DOWN: + headLoopPat = PAT_TALK_DOWN; + bodyLoopPat = BPAT_STANDDOWN; + _nBodyOffset.set(4, 53); + break; + + case LEFT: + headLoopPat = PAT_TALK_LEFT; + bodyLoopPat = BPAT_STANDLEFT; + _nBodyOffset.set(6, 56); + break; + + case RIGHT: + headLoopPat = PAT_TALK_RIGHT; + bodyLoopPat = BPAT_STANDRIGHT; + _nBodyOffset.set(6, 56); + break; + + case UP: + headLoopPat = PAT_TALK_UP; + bodyLoopPat = BPAT_STANDUP; + _nBodyOffset.set(6, 53); + break; + } + break; + + case TALK_HIPS: + _bBodyFront = false; + switch (_talkDirection) { + case UP: + _nBodyOffset.set(2, 42); + headStartPat = PAT_HEAD_UP; + bodyStartPat = BPAT_HIPSUP_START; + headLoopPat = PAT_TALK_UP; + bodyLoopPat = BPAT_HIPSUP_LOOP; + break; + + case DOWN: + _nBodyOffset.set(2, 48); + headStartPat = PAT_HEAD_DOWN; + bodyStartPat = BPAT_HIPSDOWN_START; + headLoopPat = PAT_TALK_DOWN; + bodyLoopPat = BPAT_HIPSDOWN_LOOP; + break; + + case LEFT: + _nBodyOffset.set(-3, 53); + headStartPat = PAT_HEAD_LEFT; + bodyStartPat = BPAT_HIPSLEFT_START; + headLoopPat = PAT_TALK_LEFT; + bodyLoopPat = BPAT_HIPSLEFT_LOOP; + break; + + case RIGHT: + _nBodyOffset.set(2, 53); + headStartPat = PAT_HEAD_RIGHT; + bodyStartPat = BPAT_HIPSRIGHT_START; + headLoopPat = PAT_TALK_RIGHT; + bodyLoopPat = BPAT_HIPSRIGHT_LOOP; + break; + } + break; + + case TALK_SING: + _nBodyOffset.set(-10, 25); + headStartPat = PAT_HEAD_LEFT; + bodyStartPat = BPAT_SINGLEFT_START; + headLoopPat = PAT_TALK_LEFT; + bodyLoopPat = BPAT_SINGLEFT_LOOP; + break; + + case TALK_LAUGH: + _bBodyFront = false; + switch (_talkDirection) { + case UP: + case DOWN: + case LEFT: + _nBodyOffset.set(6, 56); + headStartPat = PAT_LAUGHLEFT_START; + bodyStartPat = BPAT_STANDLEFT; + headLoopPat = PAT_LAUGHLEFT_LOOP; + bodyLoopPat = BPAT_LAUGHLEFT; + break; + + case RIGHT: + _nBodyOffset.set(6, 56); + headStartPat = PAT_LAUGHRIGHT_START; + bodyStartPat = BPAT_STANDRIGHT; + headLoopPat = PAT_LAUGHRIGHT_LOOP; + bodyLoopPat = BPAT_LAUGHRIGHT; + break; + } + break; + + case TALK_LAUGH2: + _bBodyFront = false; + switch (_talkDirection) { + case UP: + case DOWN: + case LEFT: + _nBodyOffset.set(6, 56); + headStartPat = PAT_LAUGHLEFT_START; + bodyStartPat = BPAT_STANDLEFT; + headLoopPat = PAT_LAUGHLEFT_LOOP; + break; + + case RIGHT: + _nBodyOffset.set(6, 56); + headStartPat = PAT_LAUGHRIGHT_START; + bodyStartPat = BPAT_STANDRIGHT; + headLoopPat = PAT_LAUGHRIGHT_LOOP; + bodyLoopPat = BPAT_LAUGHRIGHT; + break; + } + break; + + case TALK_INDICATE: + switch (_talkDirection) { + case UP: + case DOWN: + case LEFT: + _nBodyOffset.set(-4, 40); + headLoopPat = PAT_TALK_LEFT; + bodyLoopPat = BPAT_INDICATELEFT; + break; + + case RIGHT: + _nBodyOffset.set(5, 40); + headLoopPat = PAT_TALK_RIGHT; + bodyLoopPat = BPAT_INDICATERIGHT; + break; + } + break; + + case TALK_SCARED: + switch (_talkDirection) { + case UP: + _nBodyOffset.set(-4, -11); + headStartPat = PAT_HEAD_UP; + bodyStartPat = BPAT_SCAREDUP_START; + headLoopPat = PAT_TALK_UP; + bodyLoopPat = BPAT_SCAREDUP_LOOP; + break; + + case DOWN: + _nBodyOffset.set(-5, 45); + headStartPat = PAT_SCAREDDOWN_START; + bodyStartPat = BPAT_SCAREDDOWN_START; + headLoopPat = PAT_SCAREDDOWN_LOOP; + bodyLoopPat = BPAT_SCAREDDOWN_LOOP; + break; + + case RIGHT: + _nBodyOffset.set(-4, 41); + headStartPat = PAT_SCAREDRIGHT_START; + bodyStartPat = BPAT_SCAREDRIGHT_START; + headLoopPat = PAT_SCAREDRIGHT_LOOP; + bodyLoopPat = BPAT_SCAREDRIGHT_LOOP; + break; + + case LEFT: + _nBodyOffset.set(-10, 41); + headStartPat = PAT_SCAREDLEFT_START; + bodyStartPat = BPAT_SCAREDLEFT_START; + headLoopPat = PAT_SCAREDLEFT_LOOP; + bodyLoopPat = BPAT_SCAREDLEFT_LOOP; + break; + } + break; + + case TALK_SCARED2: + _bBodyFront = false; + switch (_talkDirection) { + case UP: + bodyStartPat = BPAT_STANDUP; + bodyLoopPat = BPAT_STANDUP; + _nBodyOffset.set(6, 53); + + headStartPat = PAT_HEAD_UP; + headLoopPat = PAT_TALK_UP; + break; + + case DOWN: + bodyStartPat = BPAT_STANDDOWN; + bodyLoopPat = BPAT_STANDDOWN; + _nBodyOffset.set(4, 53); + + headStartPat = PAT_SCAREDDOWN_START; + headLoopPat = PAT_SCAREDDOWN_LOOP; + break; + + case RIGHT: + bodyStartPat = BPAT_STANDRIGHT; + bodyLoopPat = BPAT_STANDRIGHT; + _nBodyOffset.set(6, 56); + + headStartPat = PAT_SCAREDRIGHT_START; + headLoopPat = PAT_SCAREDRIGHT_LOOP; + break; + + case LEFT: + bodyStartPat = BPAT_STANDLEFT; + bodyLoopPat = BPAT_STANDLEFT; + _nBodyOffset.set(6, 56); + + headStartPat = PAT_SCAREDLEFT_START; + headLoopPat = PAT_SCAREDLEFT_LOOP; + break; + } + break; + + case TALK_WITHGLASSES: + _nBodyOffset.set(4, 53); + headLoopPat = PAT_TALK_DOWN; + bodyLoopPat = BPAT_GLASS; + break; + case TALK_WITHWORM: + _nBodyOffset.set(9, 56); + headLoopPat = PAT_TALK_RIGHT; + bodyLoopPat = BPAT_WORM; + break; + case TALK_WITHHAMMER: + _nBodyOffset.set(6, 56); + headLoopPat = PAT_TALK_LEFT; + bodyLoopPat = BPAT_HAMMER; + break; + case TALK_WITHROPE: + _nBodyOffset.set(-3, 38); + headLoopPat = PAT_TALK_RIGHT; + bodyLoopPat = BPAT_ROPE; + break; + case TALK_WITHSECRETARY: + _nBodyOffset.set(-17, 12); + headLoopPat = PAT_TALK_RIGHT; + bodyLoopPat = BPAT_WITHSECRETARY; + break; + + case TALK_WITHRABBIT: + switch (_talkDirection) { + case LEFT: + case UP: + _nBodyOffset.set(-21, -5); + bodyStartPat = BPAT_WITHRABBITLEFT_START; + headLoopPat = PAT_TALK_LEFT; + bodyLoopPat = BPAT_WITHRABBITLEFT_LOOP; + break; + + case DOWN: + case RIGHT: + _nBodyOffset.set(-4, -5); + bodyStartPat = BPAT_WITHRABBITRIGHT_START; + headLoopPat = PAT_TALK_RIGHT; + bodyLoopPat = BPAT_WITHRABBITRIGHT_LOOP; + break; + } + break; + + case TALK_WITHRECIPE: + switch (_talkDirection) { + case LEFT: + case UP: + _nBodyOffset.set(-61, -7); + bodyStartPat = BPAT_WITHRECIPELEFT_START; + headLoopPat = PAT_TALK_LEFT; + bodyLoopPat = BPAT_WITHRECIPELEFT_LOOP; + break; + + case DOWN: + case RIGHT: + _nBodyOffset.set(-5, -7); + bodyStartPat = BPAT_WITHRECIPERIGHT_START; + headLoopPat = PAT_TALK_RIGHT; + bodyLoopPat = BPAT_WITHRECIPERIGHT_LOOP; + break; + } + break; + + case TALK_WITHCARDS: + switch (_talkDirection) { + case LEFT: + case UP: + _nBodyOffset.set(-34, -2); + bodyStartPat = BPAT_WITHCARDSLEFT_START; + headLoopPat = PAT_TALK_LEFT; + bodyLoopPat = BPAT_WITHCARDSLEFT_LOOP; + break; + + case DOWN: + case RIGHT: + _nBodyOffset.set(-4, -2); + bodyStartPat = BPAT_WITHCARDSRIGHT_START; + headLoopPat = PAT_TALK_RIGHT; + bodyLoopPat = BPAT_WITHCARDSRIGHT_LOOP; + break; + } + break; + + case TALK_WITHSNOWMAN: + switch (_talkDirection) { + case LEFT: + case UP: + _nBodyOffset.set(-35, 2); + bodyStartPat = BPAT_WITHSNOWMANLEFT_START; + headLoopPat = PAT_TALK_LEFT; + bodyLoopPat = BPAT_WITHSNOWMANLEFT_LOOP; + break; + + case DOWN: + case RIGHT: + _nBodyOffset.set(-14, 2); + bodyStartPat = BPAT_WITHSNOWMANRIGHT_START; + headLoopPat = PAT_TALK_RIGHT; + bodyLoopPat = BPAT_WITHSNOWMANRIGHT_LOOP; + break; + } + break; + + case TALK_WITHSNOWMANSTATIC: + case TALK_WITHRECIPESTATIC: + case TALK_WITHRABBITSTATIC: + case TALK_WITHCARDSSTATIC: + case TALK_WITH_NOTEBOOK: + case TALK_WITHMEGAPHONESTATIC: + switch (_talkDirection) { + case LEFT: + case UP: + headLoopPat = PAT_TALK_LEFT; + break; + + case DOWN: + case RIGHT: + headLoopPat = PAT_TALK_RIGHT; + break; + } + break; + + // The beard is the only case in which the head is animated separately while the body is the standard + case TALK_WITHBEARDSTATIC: + switch (_talkDirection) { + case LEFT: + case UP: + headLoopPat = PAT_TALKBEARD_LEFT; + bodyLoopPat = BPAT_STANDLEFT; + _nBodyOffset.set(6, 56); + break; + + case DOWN: + case RIGHT: + headLoopPat = PAT_TALKBEARD_RIGHT; + bodyLoopPat = BPAT_STANDRIGHT; + _nBodyOffset.set(6, 56); + break; + } + break; + + case TALK_DISGUSTED: + switch (_talkDirection) { + case LEFT: + case UP: + _nBodyOffset.set(6, 56); + headStartPat = PAT_DISGUSTEDLEFT_START; + bodyStartPat = BPAT_STANDLEFT; + headLoopPat = PAT_DISGUSTEDLEFT_LOOP; + break; + + case DOWN: + case RIGHT: + _nBodyOffset.set(6, 56); + headStartPat = PAT_DISGUSTEDRIGHT_START; + bodyStartPat = BPAT_STANDRIGHT; + headLoopPat = PAT_DISGUSTEDRIGHT_LOOP; + break; + } + break; + + case TALK_SARCASTIC: + switch (_talkDirection) { + case LEFT: + case UP: + _nBodyOffset.set(6, 56); + headStartPat = PAT_SARCASTICLEFT_START; + bodyStartPat = BPAT_STANDLEFT; + headLoopPat = PAT_SARCASTICLEFT_LOOP; + break; + + case DOWN: + case RIGHT: + _nBodyOffset.set(6, 56); + headStartPat = PAT_SARCASTICRIGHT_START; + bodyStartPat = BPAT_STANDRIGHT; + headLoopPat = PAT_SARCASTICRIGHT_LOOP; + break; + } + break; + + case TALK_MACBETH1: + _nBodyOffset.set(-33, -1); + headLoopPat = PAT_TALK_LEFT; + bodyLoopPat = BPAT_MACBETH1; + break; + case TALK_MACBETH2: + _nBodyOffset.set(-33, -1); + headLoopPat = PAT_TALK_LEFT; + bodyLoopPat = BPAT_MACBETH2; + break; + case TALK_MACBETH3: + _nBodyOffset.set(-33, -1); + headLoopPat = PAT_TALK_LEFT; + bodyLoopPat = BPAT_MACBETH3; + break; + case TALK_MACBETH4: + _nBodyOffset.set(-33, -1); + headLoopPat = PAT_TALK_LEFT; + bodyLoopPat = BPAT_MACBETH4; + break; + case TALK_MACBETH5: + _nBodyOffset.set(-33, -1); + headLoopPat = PAT_TALK_LEFT; + bodyLoopPat = BPAT_MACBETH5; + break; + case TALK_MACBETH6: + _nBodyOffset.set(-33, -1); + headLoopPat = PAT_TALK_LEFT; + bodyLoopPat = BPAT_MACBETH6; + break; + case TALK_MACBETH7: + _nBodyOffset.set(-33, -1); + headLoopPat = PAT_TALK_LEFT; + bodyLoopPat = BPAT_MACBETH7; + break; + case TALK_MACBETH8: + _nBodyOffset.set(-33, -1); + headLoopPat = PAT_TALK_LEFT; + bodyLoopPat = BPAT_MACBETH8; + break; + case TALK_MACBETH9: + _nBodyOffset.set(-33, -1); + headLoopPat = PAT_TALK_LEFT; + bodyLoopPat = BPAT_MACBETH9; + break; + + case TALK_SCAREDSTATIC: + _bBodyFront = false; + switch (_talkDirection) { + case DOWN: + bodyStartPat = BPAT_STANDDOWN; + bodyLoopPat = BPAT_STANDDOWN; + _nBodyOffset.set(4, 53); + + headStartPat = PAT_SCAREDDOWN_STAND; + headLoopPat = PAT_SCAREDDOWN_LOOP; + break; + + case RIGHT: + bodyStartPat = BPAT_STANDRIGHT; + bodyLoopPat = BPAT_STANDRIGHT; + _nBodyOffset.set(6, 56); + + headStartPat = PAT_SCAREDRIGHT_STAND; + headLoopPat = PAT_SCAREDRIGHT_LOOP; + break; + + case LEFT: + bodyStartPat = BPAT_STANDLEFT; + bodyLoopPat = BPAT_STANDLEFT; + _nBodyOffset.set(6, 56); + + headStartPat = PAT_SCAREDLEFT_STAND; + headLoopPat = PAT_SCAREDLEFT_LOOP; + break; + + default: + break; + } + break; + } + + return true; +} + +void RMTony::startTalk(CORO_PARAM, CharacterTalkType nTalkType) { + CORO_BEGIN_CONTEXT; + int headStartPat, bodyStartPat; + int headLoopPat, bodyLoopPat; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + _ctx->headStartPat = _ctx->bodyStartPat = 0; + _ctx->headLoopPat = _ctx->bodyLoopPat = 0; + + if (!startTalkCalculate(nTalkType, _ctx->headStartPat, _ctx->bodyStartPat, + _ctx->headLoopPat, _ctx->bodyLoopPat)) + return; + + // Perform the set pattern + if (_ctx->headStartPat != 0 || _ctx->bodyStartPat != 0) { + setPattern(_ctx->headStartPat); + _body.setPattern(_ctx->bodyStartPat); + + if (_ctx->bodyStartPat != 0) + CORO_INVOKE_0(_body.waitForEndPattern); + if (_ctx->headStartPat != 0) + CORO_INVOKE_0(waitForEndPattern); + } + + setPattern(_ctx->headLoopPat); + if (_ctx->bodyLoopPat) + _body.setPattern(_ctx->bodyLoopPat); + + CORO_END_CODE; +} + + +bool RMTony::endTalkCalculate(int &headStandPat, int &headEndPat, int &bodyEndPat, int &finalPat, bool &bStatic) { + bodyEndPat = 0; + headEndPat = 0; + + switch (_talkDirection) { + case UP: + finalPat = PAT_STANDUP; + headStandPat = PAT_HEAD_UP; + break; + + case DOWN: + finalPat = PAT_STANDDOWN; + headStandPat = PAT_HEAD_DOWN; + break; + + case LEFT: + finalPat = PAT_STANDLEFT; + headStandPat = PAT_HEAD_LEFT; + break; + + case RIGHT: + finalPat = PAT_STANDRIGHT; + headStandPat = PAT_HEAD_RIGHT; + break; + } + + if (_bShepherdess) { + setPattern(finalPat); + _bIsTalking = false; + return false; + } + + bStatic = false; + switch (_nTalkType) { + case TALK_NORMAL: + bodyEndPat = 0; + break; + + case TALK_HIPS: + switch (_talkDirection) { + case UP: + bodyEndPat = BPAT_HIPSUP_END; + break; + + case DOWN: + bodyEndPat = BPAT_HIPSDOWN_END; + break; + + case LEFT: + bodyEndPat = BPAT_HIPSLEFT_END; + break; + + case RIGHT: + bodyEndPat = BPAT_HIPSRIGHT_END; + break; + } + break; + + case TALK_SING: + bodyEndPat = BPAT_SINGLEFT_END; + break; + + case TALK_LAUGH: + case TALK_LAUGH2: + if (_talkDirection == LEFT) + headEndPat = PAT_LAUGHLEFT_END; + else if (_talkDirection == RIGHT) + headEndPat = PAT_LAUGHRIGHT_END; + + bodyEndPat = 0; + break; + + case TALK_DISGUSTED: + switch (_talkDirection) { + case UP: + case LEFT: + headEndPat = PAT_DISGUSTEDLEFT_END; + break; + + case DOWN: + case RIGHT: + headEndPat = PAT_DISGUSTEDRIGHT_END; + break; + } + + bodyEndPat = 0; + break; + + case TALK_SARCASTIC: + switch (_talkDirection) { + case UP: + case LEFT: + headEndPat = PAT_SARCASTICLEFT_END; + break; + + case DOWN: + case RIGHT: + headEndPat = PAT_SARCASTICRIGHT_END; + break; + } + + bodyEndPat = 0; + break; + + case TALK_INDICATE: + break; + + case TALK_SCARED: + switch (_talkDirection) { + case UP: + bodyEndPat = BPAT_SCAREDUP_END; + break; + + case DOWN: + headEndPat = PAT_SCAREDDOWN_END; + bodyEndPat = BPAT_SCAREDDOWN_END; + break; + + case RIGHT: + headEndPat = PAT_SCAREDRIGHT_END; + bodyEndPat = BPAT_SCAREDRIGHT_END; + break; + + case LEFT: + headEndPat = PAT_SCAREDLEFT_END; + bodyEndPat = BPAT_SCAREDLEFT_END; + break; + } + break; + + case TALK_SCARED2: + switch (_talkDirection) { + case UP: + bodyEndPat = 0; + break; + + case DOWN: + headEndPat = PAT_SCAREDDOWN_END; + bodyEndPat = 0; + break; + + case RIGHT: + headEndPat = PAT_SCAREDRIGHT_END; + bodyEndPat = 0; + break; + + case LEFT: + headEndPat = PAT_SCAREDLEFT_END; + bodyEndPat = 0; + break; + } + break; + + case TALK_WITHRABBIT: + switch (_talkDirection) { + case UP: + case LEFT: + finalPat = PAT_STANDLEFT; + bodyEndPat = BPAT_WITHRABBITLEFT_END; + break; + + case RIGHT: + case DOWN: + finalPat = PAT_STANDRIGHT; + bodyEndPat = BPAT_WITHRABBITRIGHT_END; + break; + } + break; + + case TALK_WITHRECIPE: + switch (_talkDirection) { + case UP: + case LEFT: + finalPat = PAT_STANDLEFT; + bodyEndPat = BPAT_WITHRECIPELEFT_END; + break; + + case RIGHT: + case DOWN: + finalPat = PAT_STANDRIGHT; + bodyEndPat = BPAT_WITHRECIPERIGHT_END; + break; + } + break; + + case TALK_WITHCARDS: + switch (_talkDirection) { + case UP: + case LEFT: + finalPat = PAT_STANDLEFT; + bodyEndPat = BPAT_WITHCARDSLEFT_END; + break; + + case RIGHT: + case DOWN: + finalPat = PAT_STANDRIGHT; + bodyEndPat = BPAT_WITHCARDSRIGHT_END; + break; + } + break; + + case TALK_WITHSNOWMAN: + switch (_talkDirection) { + case UP: + case LEFT: + finalPat = PAT_STANDLEFT; + bodyEndPat = BPAT_WITHSNOWMANLEFT_END; + break; + + case RIGHT: + case DOWN: + finalPat = PAT_STANDRIGHT; + bodyEndPat = BPAT_WITHSNOWMANRIGHT_END; + break; + } + break; + + case TALK_WITHWORM: + finalPat = PAT_WITHWORM; + break; + case TALK_WITHROPE: + finalPat = PAT_WITHROPE; + break; + case TALK_WITHSECRETARY: + finalPat = PAT_WITHSECRETARY; + break; + case TALK_WITHHAMMER: + finalPat = PAT_WITHHAMMER; + break; + case TALK_WITHGLASSES: + finalPat = PAT_WITHGLASSES; + break; + + case TALK_MACBETH1: + case TALK_MACBETH2: + case TALK_MACBETH3: + case TALK_MACBETH4: + case TALK_MACBETH5: + case TALK_MACBETH6: + case TALK_MACBETH7: + case TALK_MACBETH8: + finalPat = 0; + break; + + case TALK_SCAREDSTATIC: + switch (_talkDirection) { + case DOWN: + headStandPat = PAT_SCAREDDOWN_STAND; + bodyEndPat = 0; + break; + + case RIGHT: + headStandPat = PAT_SCAREDRIGHT_STAND; + bodyEndPat = 0; + break; + + case LEFT: + headStandPat = PAT_SCAREDLEFT_STAND; + bodyEndPat = 0; + break; + + + default: + break; + } + break; + + default: + break; + } + + return true; +} + +void RMTony::endTalk(CORO_PARAM) { + CORO_BEGIN_CONTEXT; + int headStandPat, headEndPat; + int bodyEndPat, finalPat; + bool bStatic; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + _ctx->headStandPat = _ctx->headEndPat = 0; + _ctx->bodyEndPat = _ctx->finalPat = 0; + _ctx->bStatic = false; + + _ctx->bodyEndPat = 0; + _ctx->headEndPat = 0; + + if (!endTalkCalculate(_ctx->headStandPat, _ctx->headEndPat, _ctx->bodyEndPat, _ctx->finalPat, _ctx->bStatic)) + return; + + // Handles the end of an animated and static, leaving everything unchanged + if (_bIsStaticTalk) { + if (_nTalkType == TALK_WITHBEARDSTATIC) { + setPattern(0); + if (_talkDirection == UP || _talkDirection == LEFT) { + _body.setPattern(BPAT_WITHBEARDLEFT_STATIC); + _nBodyOffset.set(-41, -14); + } else if (_talkDirection == DOWN || _talkDirection == RIGHT) { + _body.setPattern(BPAT_WITHBEARDRIGHT_STATIC); + _nBodyOffset.set(-26, -14); + } + } else { + setPattern(_ctx->headStandPat); + + CORO_INVOKE_0(_body.waitForEndPattern); + } + + _bIsTalking = false; + return; + } + + // Set the pattern + if (_ctx->headEndPat != 0 && _ctx->bodyEndPat != 0) { + setPattern(_ctx->headEndPat); + + CORO_INVOKE_0(_body.waitForEndPattern); + + _body.setPattern(_ctx->bodyEndPat); + + CORO_INVOKE_0(waitForEndPattern); + CORO_INVOKE_0(_body.waitForEndPattern); + } else if (_ctx->bodyEndPat != 0) { + setPattern(_ctx->headStandPat); + + CORO_INVOKE_0(_body.waitForEndPattern); + + _body.setPattern(_ctx->bodyEndPat); + + CORO_INVOKE_0(_body.waitForEndPattern); + } else if (_ctx->headEndPat != 0) { + CORO_INVOKE_0(_body.waitForEndPattern); + + setPattern(_ctx->headEndPat); + + CORO_INVOKE_0(waitForEndPattern); + } else { + CORO_INVOKE_0(_body.waitForEndPattern); + } + + if (_ctx->finalPat != 0) { + _body.setPattern(0); + setPattern(_ctx->finalPat); + } + + _bIsTalking = false; + + CORO_END_CODE; +} + +void RMTony::startStaticCalculate(CharacterTalkType nTalk, int &headPat, int &headLoopPat, + int &bodyStartPat, int &bodyLoopPat) { + int nPat = getCurPattern(); + + headLoopPat = -1; + + switch (nPat) { + case PAT_STANDDOWN: + _talkDirection = DOWN; + headPat = PAT_HEAD_RIGHT; + break; + + case PAT_TAKELEFT_UP2: + case PAT_TAKELEFT_MID2: + case PAT_TAKELEFT_DOWN2: + case PAT_GETUPLEFT: + case PAT_STANDLEFT: + _talkDirection = LEFT; + headPat = PAT_HEAD_LEFT; + break; + + case PAT_TAKERIGHT_UP2: + case PAT_TAKERIGHT_MID2: + case PAT_TAKERIGHT_DOWN2: + case PAT_GETUPRIGHT: + case PAT_STANDRIGHT: + _talkDirection = RIGHT; + headPat = PAT_HEAD_RIGHT; + break; + + case PAT_TAKEUP_UP2: + case PAT_TAKEUP_MID2: + case PAT_TAKEUP_DOWN2: + case PAT_STANDUP: + _talkDirection = UP; + headPat = PAT_HEAD_LEFT; + break; + } + + _bBodyFront = true; + + switch (nTalk) { + case TALK_WITHRABBITSTATIC: + switch (_talkDirection) { + case UP: + case LEFT: + _nBodyOffset.set(-21, -5); + bodyStartPat = BPAT_WITHRABBITLEFT_START; + bodyLoopPat = BPAT_WITHRABBITLEFT_LOOP; + break; + + case DOWN: + case RIGHT: + _nBodyOffset.set(-4, -5); + bodyStartPat = BPAT_WITHRABBITRIGHT_START; + bodyLoopPat = BPAT_WITHRABBITRIGHT_LOOP; + break; + } + break; + + case TALK_WITHCARDSSTATIC: + switch (_talkDirection) { + case UP: + case LEFT: + _nBodyOffset.set(-34, -2); + bodyStartPat = BPAT_WITHCARDSLEFT_START; + bodyLoopPat = BPAT_WITHCARDSLEFT_LOOP; + break; + + case DOWN: + case RIGHT: + _nBodyOffset.set(-4, -2); + bodyStartPat = BPAT_WITHCARDSRIGHT_START; + bodyLoopPat = BPAT_WITHCARDSRIGHT_LOOP; + break; + } + break; + + case TALK_WITHRECIPESTATIC: + switch (_talkDirection) { + case UP: + case LEFT: + _nBodyOffset.set(-61, -7); + bodyStartPat = BPAT_WITHRECIPELEFT_START; + bodyLoopPat = BPAT_WITHRECIPELEFT_LOOP; + break; + + case DOWN: + case RIGHT: + _nBodyOffset.set(-5, -7); + bodyStartPat = BPAT_WITHRECIPERIGHT_START; + bodyLoopPat = BPAT_WITHRECIPERIGHT_LOOP; + break; + } + break; + + case TALK_WITHSNOWMANSTATIC: + switch (_talkDirection) { + case UP: + case LEFT: + _nBodyOffset.set(-35, 2); + bodyStartPat = BPAT_WITHSNOWMANLEFT_START; + bodyLoopPat = BPAT_WITHSNOWMANLEFT_LOOP; + break; + + case DOWN: + case RIGHT: + _nBodyOffset.set(-14, 2); + bodyStartPat = BPAT_WITHSNOWMANRIGHT_START; + bodyLoopPat = BPAT_WITHSNOWMANRIGHT_LOOP; + break; + } + break; + + case TALK_WITH_NOTEBOOK: + switch (_talkDirection) { + case UP: + case LEFT: + _nBodyOffset.set(-16, -9); + bodyStartPat = BPAT_WITHNOTEBOOKLEFT_START; + bodyLoopPat = BPAT_WITHNOTEBOOKLEFT_LOOP; + break; + + case DOWN: + case RIGHT: + _nBodyOffset.set(-6, -9); + bodyStartPat = BPAT_WITHNOTEBOOKRIGHT_START; + bodyLoopPat = BPAT_WITHNOTEBOOKRIGHT_LOOP; + break; + } + break; + + case TALK_WITHMEGAPHONESTATIC: + switch (_talkDirection) { + case UP: + case LEFT: + _nBodyOffset.set(-41, -8); + bodyStartPat = BPAT_WITHMEGAPHONELEFT_START; + bodyLoopPat = BPAT_WITHMEGAPHONELEFT_LOOP; + break; + + case DOWN: + case RIGHT: + _nBodyOffset.set(-14, -8); + bodyStartPat = BPAT_WITHMEGAPHONERIGHT_START; + bodyLoopPat = BPAT_WITHMEGAPHONERIGHT_LOOP; + break; + } + break; + + case TALK_WITHBEARDSTATIC: + switch (_talkDirection) { + case UP: + case LEFT: + _nBodyOffset.set(-41, -14); + bodyStartPat = BPAT_WITHBEARDLEFT_START; + bodyLoopPat = BPAT_STANDLEFT; + headLoopPat = PAT_TALKBEARD_LEFT; + headPat = 0; + break; + + case DOWN: + case RIGHT: + _nBodyOffset.set(-26, -14); + bodyStartPat = BPAT_WITHBEARDRIGHT_START; + bodyLoopPat = BPAT_STANDRIGHT; + headLoopPat = PAT_TALKBEARD_RIGHT; + headPat = 0; + break; + } + break; + + case TALK_SCAREDSTATIC: + switch (_talkDirection) { + case DOWN: + headPat = PAT_SCAREDDOWN_START; + bodyLoopPat = BPAT_STANDDOWN; + bodyStartPat = BPAT_STANDDOWN; + headLoopPat = PAT_SCAREDDOWN_STAND; + _nBodyOffset.set(4, 53); + break; + + case LEFT: + headPat = PAT_SCAREDLEFT_START; + bodyLoopPat = BPAT_STANDLEFT; + bodyStartPat = BPAT_STANDLEFT; + headLoopPat = PAT_SCAREDLEFT_STAND; + _nBodyOffset.set(6, 56); + break; + + case RIGHT: + headPat = PAT_SCAREDRIGHT_START; + bodyLoopPat = BPAT_STANDRIGHT; + bodyStartPat = BPAT_STANDRIGHT; + headLoopPat = PAT_SCAREDRIGHT_STAND; + _nBodyOffset.set(6, 56); + break; + + default: + break; + } + + default: + break; + } +} + +void RMTony::startStatic(CORO_PARAM, CharacterTalkType nTalk) { + CORO_BEGIN_CONTEXT; + int headPat, headLoopPat; + int bodyStartPat, bodyLoopPat; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + _ctx->headPat = _ctx->headLoopPat = 0; + _ctx->bodyStartPat = _ctx->bodyLoopPat = 0; + + startStaticCalculate(nTalk, _ctx->headPat, _ctx->headLoopPat, + _ctx->bodyStartPat, _ctx->bodyLoopPat); + + // e vai con i pattern + _bIsStaticTalk = true; + + setPattern(_ctx->headPat); + _body.setPattern(_ctx->bodyStartPat); + + CORO_INVOKE_0(_body.waitForEndPattern); + CORO_INVOKE_0(waitForEndPattern); + + if (_ctx->headLoopPat != -1) + setPattern(_ctx->headLoopPat); + _body.setPattern(_ctx->bodyLoopPat); + + CORO_END_CODE; +} + +void RMTony::endStaticCalculate(CharacterTalkType nTalk, int &bodyEndPat, int &finalPat, int &headEndPat) { + switch (_talkDirection) { + case UP: + case LEFT: + finalPat = PAT_STANDLEFT; + break; + + case RIGHT: + case DOWN: + finalPat = PAT_STANDRIGHT; + break; + } + + switch (nTalk) { + case TALK_WITHSNOWMANSTATIC: + switch (_talkDirection) { + case UP: + case LEFT: + bodyEndPat = BPAT_WITHSNOWMANLEFT_END; + break; + + case DOWN: + case RIGHT: + bodyEndPat = BPAT_WITHSNOWMANRIGHT_END; + break; + } + break; + + case TALK_WITHRECIPESTATIC: + switch (_talkDirection) { + case UP: + case LEFT: + bodyEndPat = BPAT_WITHRECIPELEFT_END; + break; + + case DOWN: + case RIGHT: + bodyEndPat = BPAT_WITHRECIPERIGHT_END; + break; + } + break; + + case TALK_WITHRABBITSTATIC: + switch (_talkDirection) { + case UP: + case LEFT: + bodyEndPat = BPAT_WITHRABBITLEFT_END; + break; + + case DOWN: + case RIGHT: + bodyEndPat = BPAT_WITHRABBITRIGHT_END; + break; + } + break; + + case TALK_WITHCARDSSTATIC: + switch (_talkDirection) { + case UP: + case LEFT: + bodyEndPat = BPAT_WITHCARDSLEFT_END; + break; + + case DOWN: + case RIGHT: + bodyEndPat = BPAT_WITHCARDSRIGHT_END; + break; + } + break; + + case TALK_WITH_NOTEBOOK: + switch (_talkDirection) { + case UP: + case LEFT: + bodyEndPat = BPAT_WITHNOTEBOOKLEFT_END; + break; + + case DOWN: + case RIGHT: + bodyEndPat = BPAT_WITHNOTEBOOKRIGHT_END; + break; + } + break; + + case TALK_WITHMEGAPHONESTATIC: + switch (_talkDirection) { + case UP: + case LEFT: + bodyEndPat = BPAT_WITHMEGAPHONELEFT_END; + break; + + case DOWN: + case RIGHT: + bodyEndPat = BPAT_WITHMEGAPHONERIGHT_END; + break; + } + break; + + case TALK_WITHBEARDSTATIC: + switch (_talkDirection) { + case UP: + case LEFT: + bodyEndPat = BPAT_WITHBEARDLEFT_END; + break; + + case DOWN: + case RIGHT: + bodyEndPat = BPAT_WITHBEARDRIGHT_END; + break; + } + break; + + case TALK_SCAREDSTATIC: + switch (_talkDirection) { + case LEFT: + headEndPat = PAT_SCAREDLEFT_END; + break; + + case DOWN: + headEndPat = PAT_SCAREDDOWN_END; + break; + + case RIGHT: + headEndPat = PAT_SCAREDRIGHT_END; + break; + + default: + break; + } + break; + + default: + break; + } +} + +void RMTony::endStatic(CORO_PARAM, CharacterTalkType nTalk) { + CORO_BEGIN_CONTEXT; + int bodyEndPat; + int finalPat; + int headEndPat; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + _ctx->bodyEndPat = 0; + _ctx->finalPat = 0; + _ctx->headEndPat = 0; + + endStaticCalculate(nTalk, _ctx->bodyEndPat, _ctx->finalPat, _ctx->headEndPat); + + if (_ctx->headEndPat != 0) { + setPattern(_ctx->headEndPat); + + CORO_INVOKE_0(waitForEndPattern); + } else { + // Play please + _body.setPattern(_ctx->bodyEndPat); + + CORO_INVOKE_0(_body.waitForEndPattern); + } + + setPattern(_ctx->finalPat); + _body.setPattern(0); + + _bIsStaticTalk = false; + + CORO_END_CODE; +} + +/** + * Waits until the end of a pattern + */ +void RMTony::waitForEndPattern(CORO_PARAM, uint32 hCustomSkip) { + RMCharacter::waitForEndPattern(coroParam, hCustomSkip); +} + +/** + * Check if currently in an action + */ +bool RMTony::inAction() { + return (_bActionPending && _action != 0) | _bAction; +} + +/** + * Check if there needs to be an update for scrolling movement + */ +bool RMTony::mustUpdateScrolling() { + return ((!inAction()) || (isMoving())); +} + +/** + * Returns Tony's position + */ +RMPoint RMTony::position() { + return _pos; +} + +/** + * Set the scrolling position + */ +void RMTony::setScrollPosition(const RMPoint &pt) { + RMCharacter::setScrollPosition(pt); +} + +/** + * Tony disguises himself! + */ +void RMTony::setShepherdess(bool bIsPast) { + _bShepherdess = bIsPast; +} + +int RMTony::getShepherdess() { + return _bShepherdess; +} + +void RMTony::playSfx(int nSfx) { + RMItem::playSfx(nSfx); +} + +} // End of namespace Tony diff --git a/engines/tony/tonychar.h b/engines/tony/tonychar.h new file mode 100644 index 0000000000..d9f18f61ec --- /dev/null +++ b/engines/tony/tonychar.h @@ -0,0 +1,482 @@ +/* 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 Tony Tough source code + * + * Copyright (c) 1997-2003 Nayma Software + */ + +#ifndef TONY_TONYCHAR_H +#define TONY_TONYCHAR_H + +#include "common/coroutines.h" +#include "tony/loc.h" + +namespace Tony { + +class RMTony : public RMCharacter { +private: + enum CharacterDirection { + UP, DOWN, LEFT, RIGHT + }; + +public: + enum CharacterTalkType { + TALK_NORMAL, + TALK_HIPS, + TALK_SING, + TALK_LAUGH, + TALK_INDICATE, + TALK_SCARED, + TALK_SCARED2, + TALK_WITHGLASSES, + TALK_WITHHAMMER, + TALK_WITHWORM, + TALK_WITHROPE, + TALK_WITHRABBIT, + TALK_WITHRECIPE, + TALK_WITHCARDS, + TALK_WITHSNOWMAN, + TALK_WITHSNOWMANSTATIC, + TALK_WITHRABBITSTATIC, + TALK_WITHRECIPESTATIC, + TALK_WITHCARDSSTATIC, + TALK_WITH_NOTEBOOK, + TALK_WITHMEGAPHONESTATIC, + TALK_WITHBEARDSTATIC, + TALK_LAUGH2, + TALK_DISGUSTED, + TALK_SARCASTIC, + TALK_MACBETH1, + TALK_MACBETH2, + TALK_MACBETH3, + TALK_MACBETH4, + TALK_MACBETH5, + TALK_MACBETH6, + TALK_MACBETH7, + TALK_MACBETH8, + TALK_MACBETH9, + TALK_SCAREDSTATIC, + TALK_WITHSECRETARY + }; + +private: + bool _bShow; + bool _bShowShadow; + bool _bBodyFront; + // Useless variable? + // RMGfxSourceBuffer8AB _shadow; + bool _bActionPending; + RMItem *_actionItem; + int _action; + int _actionParm; + static bool _bAction; + + bool _bShepherdess; + + bool _bIsStaticTalk; + bool _bIsTalking; + int _nPatB4Talking; + CharacterTalkType _nTalkType; + CharacterDirection _talkDirection; + RMPoint _nBodyOffset; + + int _nTimeLastStep; + + RMItem _body; + uint32 _hActionThread; + +protected: + /** + * Overload of the allocation allocation of sprites + */ + virtual RMGfxSourceBuffer *newItemSpriteBuffer(int dimx, int dimy, bool bPreRLE); + + /** + * Watch thread which waits for the end of an action + */ + static void waitEndOfAction(CORO_PARAM, const void *param); + +public: + enum CharacterPatterns { + PAT_TAKEUP_UP1 = 9, + PAT_TAKEUP_UP2, + PAT_TAKEUP_MID1, + PAT_TAKEUP_MID2, + PAT_TAKEUP_DOWN1, + PAT_TAKEUP_DOWN2, + + PAT_TAKELEFT_UP1, + PAT_TAKELEFT_UP2, + PAT_TAKELEFT_MID1, + PAT_TAKELEFT_MID2, + PAT_TAKELEFT_DOWN1, + PAT_TAKELEFT_DOWN2, + + PAT_TAKERIGHT_UP1, + PAT_TAKERIGHT_UP2, + PAT_TAKERIGHT_MID1, + PAT_TAKERIGHT_MID2, + PAT_TAKERIGHT_DOWN1, + PAT_TAKERIGHT_DOWN2, + + PAT_GETUPLEFT, + PAT_ONTHEFLOORLEFT, + PAT_GETUPRIGHT, + PAT_ONTHEFLOORRIGHT, + + // Sheperdess! + PAT_PAST_WALKUP, + PAT_PAST_WALKDOWN, + PAT_PAST_WALKLEFT, + PAT_PAST_WALKRIGHT, + + PAT_PAST_STANDUP, + PAT_PAST_STANDDOWN, + PAT_PAST_STANDLEFT, + PAT_PAST_STANDRIGHT, + + // Speech + PAT_TALK_UP, + PAT_TALK_DOWN, + PAT_TALK_LEFT, + PAT_TALK_RIGHT, + + // Static head + PAT_HEAD_UP, + PAT_HEAD_DOWN, + PAT_HEAD_LEFT, + PAT_HEAD_RIGHT, + + // Laugh + PAT_LAUGHLEFT_START, + PAT_LAUGHLEFT_LOOP, + PAT_LAUGHLEFT_END, + PAT_LAUGHRIGHT_START, + PAT_LAUGHRIGHT_LOOP, + PAT_LAUGHRIGHT_END, + + // Speaking as a shepherdess + PAT_PAST_TALKUP, + PAT_PAST_TALKDOWN, + PAT_PAST_TALKLEFT, + PAT_PAST_TALKRIGHT, + + // Fear + PAT_SCAREDLEFT_START, + PAT_SCAREDLEFT_LOOP, + PAT_SCAREDLEFT_END, + PAT_SCAREDRIGHT_START, + PAT_SCAREDRIGHT_LOOP, + PAT_SCAREDRIGHT_END, + PAT_SCAREDDOWN_START, + PAT_SCAREDDOWN_LOOP, + PAT_SCAREDDOWN_END, + + // With objects: full body + PAT_WITHGLASSES, + PAT_WITHROPE, + PAT_WITHWORM, + PAT_WITHHAMMER, + + // Sound the whistle + PAT_WHISTLERIGHT, + + // Head with beard + PAT_TALKBEARD_LEFT, + PAT_TALKBEARD_RIGHT, + + // Sniff + PAT_SNIFF_LEFT, + PAT_SNIFF_RIGHT, + + // Disgusted + PAT_DISGUSTEDLEFT_START, + PAT_DISGUSTEDLEFT_LOOP, + PAT_DISGUSTEDLEFT_END, + PAT_DISGUSTEDRIGHT_START, + PAT_DISGUSTEDRIGHT_LOOP, + PAT_DISGUSTEDRIGHT_END, + PAT_SARCASTICLEFT_START, + PAT_SARCASTICLEFT_LOOP, + PAT_SARCASTICLEFT_END, + PAT_SARCASTICRIGHT_START, + PAT_SARCASTICRIGHT_LOOP, + PAT_SARCASTICRIGHT_END, + + // Stand scared + PAT_SCAREDLEFT_STAND, + PAT_SCAREDRIGHT_STAND, + PAT_SCAREDDOWN_STAND, + + PAT_PUTLEFT_UP1, + PAT_PUTLEFT_UP2, + PAT_PUTRIGHT_UP1, + PAT_PUTRIGHT_UP2, + PAT_PUTLEFT_MID1, + PAT_PUTLEFT_MID2, + PAT_PUTRIGHT_MID1, + PAT_PUTRIGHT_MID2, + PAT_PUTLEFT_DOWN1, + PAT_PUTLEFT_DOWN2, + PAT_PUTRIGHT_DOWN1, + PAT_PUTRIGHT_DOWN2, + PAT_PUTUP_UP1, + PAT_PUTUP_UP2, + PAT_PUTUP_MID1, + PAT_PUTUP_MID2, + PAT_PUTUP_DOWN1, + PAT_PUTUP_DOWN2, + + PAT_WITHSECRETARY + }; + + enum CharacterBodyPatterns { + BPAT_STANDUP = 1, + BPAT_STANDDOWN, + BPAT_STANDLEFT, + BPAT_STANDRIGHT, + + BPAT_HAMMER, + BPAT_SNOWMAN, + BPAT_WORM, + BPAT_GLASS, + + BPAT_SINGLEFT_START, + BPAT_SINGLEFT_LOOP, + BPAT_SINGLEFT_END, + + BPAT_HIPSLEFT_START, + BPAT_HIPSLEFT_LOOP, + BPAT_HIPSLEFT_END, + BPAT_HIPSRIGHT_START, + BPAT_HIPSRIGHT_LOOP, + BPAT_HIPSRIGHT_END, + BPAT_HIPSUP_START, + BPAT_HIPSUP_LOOP, + BPAT_HIPSUP_END, + BPAT_HIPSDOWN_START, + BPAT_HIPSDOWN_LOOP, + BPAT_HIPSDOWN_END, + + BPAT_LAUGHLEFT, + BPAT_LAUGHRIGHT, + + BPAT_INDICATELEFT, + BPAT_INDICATERIGHT, + + BPAT_SCAREDDOWN_START, + BPAT_SCAREDDOWN_LOOP, + BPAT_SCAREDDOWN_END, + BPAT_SCAREDLEFT_START, + BPAT_SCAREDLEFT_LOOP, + BPAT_SCAREDLEFT_END, + BPAT_SCAREDRIGHT_START, + BPAT_SCAREDRIGHT_LOOP, + BPAT_SCAREDRIGHT_END, + BPAT_SCAREDUP_START, + BPAT_SCAREDUP_LOOP, + BPAT_SCAREDUP_END, + + BPAT_ROPE, + + BPAT_WITHRABBITLEFT_START, + BPAT_WITHRABBITLEFT_LOOP, + BPAT_WITHRABBITLEFT_END, + BPAT_WITHRABBITRIGHT_START, + BPAT_WITHRABBITRIGHT_LOOP, + BPAT_WITHRABBITRIGHT_END, + + BPAT_WITHRECIPELEFT_START, + BPAT_WITHRECIPELEFT_LOOP, + BPAT_WITHRECIPELEFT_END, + BPAT_WITHRECIPERIGHT_START, + BPAT_WITHRECIPERIGHT_LOOP, + BPAT_WITHRECIPERIGHT_END, + + BPAT_WITHCARDSLEFT_START, + BPAT_WITHCARDSLEFT_LOOP, + BPAT_WITHCARDSLEFT_END, + BPAT_WITHCARDSRIGHT_START, + BPAT_WITHCARDSRIGHT_LOOP, + BPAT_WITHCARDSRIGHT_END, + + BPAT_WITHSNOWMANLEFT_START, + BPAT_WITHSNOWMANLEFT_LOOP, + BPAT_WITHSNOWMANLEFT_END, + BPAT_WITHSNOWMANRIGHT_START, + BPAT_WITHSNOWMANRIGHT_LOOP, + BPAT_WITHSNOWMANRIGHT_END, + + BPAT_WITHNOTEBOOKLEFT_START, + BPAT_WITHNOTEBOOKLEFT_LOOP, + BPAT_WITHNOTEBOOKLEFT_END, + BPAT_WITHNOTEBOOKRIGHT_START, + BPAT_WITHNOTEBOOKRIGHT_LOOP, + BPAT_WITHNOTEBOOKRIGHT_END, + + BPAT_WITHMEGAPHONELEFT_START, + BPAT_WITHMEGAPHONELEFT_LOOP, + BPAT_WITHMEGAPHONELEFT_END, + BPAT_WITHMEGAPHONERIGHT_START, + BPAT_WITHMEGAPHONERIGHT_LOOP, + BPAT_WITHMEGAPHONERIGHT_END, + + BPAT_WITHBEARDLEFT_START, + BPAT_WITHBEARDLEFT_END, + BPAT_WITHBEARDRIGHT_START, + BPAT_WITHBEARDRIGHT_END, + BPAT_WITHBEARDLEFT_STATIC, + BPAT_WITHBEARDRIGHT_STATIC, + + BPAT_MACBETH1, + BPAT_MACBETH2, + BPAT_MACBETH3, + BPAT_MACBETH4, + BPAT_MACBETH5, + BPAT_MACBETH6, + BPAT_MACBETH7, + BPAT_MACBETH8, + BPAT_MACBETH9, + + BPAT_WITHSECRETARY + }; + +public: + static void initStatics(); + RMTony(); + + /** + * Initialize Tony + */ + void init(); + + /** + * Free all memory + */ + void close(); + + /** + * Tony makes a frame, updating the movement, etc. + */ + void doFrame(CORO_PARAM, RMGfxTargetBuffer *bigBuf, int curLoc); + + /** + * Draw method, which controls chararacter display + */ + virtual void draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim); + + /** + * Show or hide + */ + void show(); + void hide(bool bShowShadow = false); + + /** + * Move and make an action, if necessary + */ + void moveAndDoAction(CORO_PARAM, RMPoint dst, RMItem *item, int nAction, int nActionParm = 0); + + /** + * Tony stops (on the right side with respect to any subject) + */ + virtual void stop(CORO_PARAM); + void stopNoAction(CORO_PARAM); + + /** + * Set a pattern + */ + void setPattern(int npatt, bool bPlayP0 = false); + + /** + * Reads the current pattern + */ + int getCurPattern(); + + /** + * Waits until the end of a pattern + */ + void waitForEndPattern(CORO_PARAM, uint32 hCustomSkip = CORO_INVALID_PID_VALUE); + + /** + * Check if currently in an action + */ + bool inAction(); + + /** + * Check if there needs to be an update for scrolling movement + */ + bool mustUpdateScrolling(); + + /** + * Returns Tony's position + */ + RMPoint position(); + + /** + * Set the scrolling position + */ + void setScrollPosition(const RMPoint &pt); + + /** + * Set the take animation + */ + void take(int nWhere, int nPart); + void put(int nWhere, int nPart); + + /** + * Start or End Talk + */ + bool startTalkCalculate(CharacterTalkType nTalkType, int &headStartPat, int &bodyStartPat, + int &headLoopPat, int &bodyLoopPat); + void startTalk(CORO_PARAM, CharacterTalkType nTalkType); + bool endTalkCalculate(int &headStandPat, int &headEndPat, int &bodyEndPat, int &finalPat, bool &bStatic); + void endTalk(CORO_PARAM); + + /** + * Start or End Static + */ + void startStaticCalculate(CharacterTalkType nTalk, int &headPat, int &headLoopPat, + int &bodyStartPat, int &bodyLoopPat); + void startStatic(CORO_PARAM, CharacterTalkType nTalkType); + void endStaticCalculate(CharacterTalkType nTalk, int &bodyEndPat, int &finalPat, int &headEndPat); + void endStatic(CORO_PARAM, CharacterTalkType nTalkType); + + /** + * Tony disguises himself! + */ + void setShepherdess(bool bIsPast); + + int getShepherdess(); + + /** + * Perform an action + */ + void executeAction(int nAction, int nActionItem, int nParm); + + void playSfx(int nSfx); +}; + +} // End of namespace Tony + +#endif diff --git a/engines/tony/utils.cpp b/engines/tony/utils.cpp new file mode 100644 index 0000000000..81060146b7 --- /dev/null +++ b/engines/tony/utils.cpp @@ -0,0 +1,448 @@ +/* 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 Tony Tough source code + * + * Copyright (c) 1997-2003 Nayma Software + */ + +#include "tony/utils.h" +#include "tony/tony.h" +#include "tony/mpal/lzo.h" + +namespace Tony { + +/** + * Extracts a string from a data stream + * @param df data stream + */ +Common::String readString(Common::ReadStream &df) { + Common::String var; + uint8 len = df.readByte(); + + for (int i = 0; i < len; i++) { + char c; + c = df.readByte(); + var += c; + } + + return var; +} + +/****************************************************************************\ +* RMPoint methods +\****************************************************************************/ + +/** + * Constructor + */ +RMPoint::RMPoint() { + _x = _y = 0; +} + +/** + * Copy constructor + */ +RMPoint::RMPoint(const RMPoint &p) { + _x = p._x; + _y = p._y; +} + +/** + * Constructor with integer parameters + */ +RMPoint::RMPoint(int x1, int y1) { + _x = x1; + _y = y1; +} + +/** + * Copy operator + */ +RMPoint &RMPoint::operator=(RMPoint p) { + _x = p._x; + _y = p._y; + + return *this; +} + +/** + * Set a point + */ +void RMPoint::set(int x1, int y1) { + _x = x1; + _y = y1; +} + +/** + * Offsets the point by another point + */ +void RMPoint::offset(const RMPoint &p) { + _x += p._x; + _y += p._y; +} + +/** + * Offsets the point by a specified offset + */ +void RMPoint::offset(int xOff, int yOff) { + _x += xOff; + _y += yOff; +} + +/** + * Sums together two points + */ +RMPoint operator+(RMPoint p1, RMPoint p2) { + RMPoint p(p1); + + return (p += p2); +} + +/** + * Subtracts two points + */ +RMPoint operator-(RMPoint p1, RMPoint p2) { + RMPoint p(p1); + + return (p -= p2); +} + +/** + * Sum (offset) of a point + */ +RMPoint &RMPoint::operator+=(RMPoint p) { + offset(p); + return *this; +} + +/** + * Subtract (offset) of a point + */ +RMPoint &RMPoint::operator-=(RMPoint p) { + offset(-p); + return *this; +} + +/** + * Inverts a point + */ +RMPoint RMPoint::operator-() { + RMPoint p; + + p._x = -_x; + p._y = -_y; + + return p; +} + +/** + * Equality operator + */ +bool RMPoint::operator==(RMPoint p) { + return ((_x == p._x) && (_y == p._y)); +} + +/** + * Not equal operator + */ +bool RMPoint::operator!=(RMPoint p) { + return ((_x != p._x) || (_y != p._y)); +} + +/** + * Reads a point from a stream + */ +void RMPoint::readFromStream(Common::ReadStream &ds) { + _x = ds.readSint32LE(); + _y = ds.readSint32LE(); +} + +/****************************************************************************\ +* RMPointReference methods +\****************************************************************************/ + +RMPointReference::RMPointReference(int &x, int &y): _x(x), _y(y) { +} + +RMPointReference &RMPointReference::operator=(const RMPoint &p) { + _x = p._x; _y = p._y; + return *this; +} + +RMPointReference &RMPointReference::operator-=(const RMPoint &p) { + _x -= p._x; _y -= p._y; + return *this; +} + +RMPointReference::operator RMPoint() const { + return RMPoint(_x, _y); +} + +/****************************************************************************\ +* RMRect methods +\****************************************************************************/ + +RMRect::RMRect(): _topLeft(_x1, _y1), _bottomRight(_x2, _y2) { + setEmpty(); +} + +void RMRect::setEmpty() { + _x1 = _y1 = _x2 = _y2 = 0; +} + +RMRect::RMRect(const RMPoint &p1, const RMPoint &p2): _topLeft(_x1, _y1), _bottomRight(_x2, _y2) { + setRect(p1, p2); +} + +RMRect::RMRect(int X1, int Y1, int X2, int Y2): _topLeft(_x1, _y1), _bottomRight(_x2, _y2) { + setRect(X1, Y1, X2, Y2); +} + +RMRect::RMRect(const RMRect &rc): _topLeft(_x1, _y1), _bottomRight(_x2, _y2) { + copyRect(rc); +} + +void RMRect::setRect(const RMPoint &p1, const RMPoint &p2) { + _x1 = p1._x; + _y1 = p1._y; + _x2 = p2._x; + _y2 = p2._y; +} + +void RMRect::setRect(int X1, int Y1, int X2, int Y2) { + _x1 = X1; + _y1 = Y1; + _x2 = X2; + _y2 = Y2; +} + +void RMRect::setRect(const RMRect &rc) { + copyRect(rc); +} + +void RMRect::copyRect(const RMRect &rc) { + _x1 = rc._x1; + _y1 = rc._y1; + _x2 = rc._x2; + _y2 = rc._y2; +} + +RMPointReference &RMRect::topLeft() { + return _topLeft; +} + +RMPointReference &RMRect::bottomRight() { + return _bottomRight; +} + +RMPoint RMRect::center() { + return RMPoint((_x2 - _x1) / 2, (_y2 - _y1) / 2); +} + +int RMRect::width() const { + return _x2 - _x1; +} + +int RMRect::height() const { + return _y2 - _y1; +} + +int RMRect::size() const { + return width() * height(); +} + +RMRect::operator Common::Rect() const { + return Common::Rect(_x1, _y1, _x2, _y2); +} + +bool RMRect::isEmpty() const { + return (_x1 == 0 && _y1 == 0 && _x2 == 0 && _y2 == 0); +} + +const RMRect &RMRect::operator=(const RMRect &rc) { + copyRect(rc); + return *this; +} + +void RMRect::offset(int xOff, int yOff) { + _x1 += xOff; + _y1 += yOff; + _x2 += xOff; + _y2 += yOff; +} + +void RMRect::offset(const RMPoint &p) { + _x1 += p._x; + _y1 += p._y; + _x2 += p._x; + _y2 += p._y; +} + +const RMRect &RMRect::operator+=(RMPoint p) { + offset(p); + return *this; +} + +const RMRect &RMRect::operator-=(RMPoint p) { + offset(-p); + return *this; +} + +RMRect operator+(const RMRect &rc, RMPoint p) { + RMRect r(rc); + return (r += p); +} + +RMRect operator-(const RMRect &rc, RMPoint p) { + RMRect r(rc); + + return (r -= p); +} + +RMRect operator+(RMPoint p, const RMRect &rc) { + RMRect r(rc); + + return (r += p); +} + +RMRect operator-(RMPoint p, const RMRect &rc) { + RMRect r(rc); + + return (r += p); +} + +bool RMRect::operator==(const RMRect &rc) { + return ((_x1 == rc._x1) && (_y1 == rc._y1) && (_x2 == rc._x2) && (_y2 == rc._y2)); +} + +bool RMRect::operator!=(const RMRect &rc) { + return ((_x1 != rc._x1) || (_y1 != rc._y1) || (_x2 != rc._x2) || (_y2 != rc._y2)); +} + +void RMRect::normalizeRect() { + setRect(MIN(_x1, _x2), MIN(_y1, _y2), MAX(_x1, _x2), MAX(_y1, _y2)); +} + +void RMRect::readFromStream(Common::ReadStream &ds) { + _x1 = ds.readSint32LE(); + _y1 = ds.readSint32LE(); + _x2 = ds.readSint32LE(); + _y2 = ds.readSint32LE(); +} + +/** + * Check if RMPoint is in RMRect + */ +bool RMRect::ptInRect(const RMPoint &pt) { + return (pt._x >= _x1 && pt._x <= _x2 && pt._y >= _y1 && pt._y <= _y2); +} + +/****************************************************************************\ +* Resource Update +\****************************************************************************/ + +RMResUpdate::RMResUpdate() { + _infos = NULL; + _numUpd = 0; +} + +RMResUpdate::~RMResUpdate() { + if (_infos) { + delete[] _infos; + _infos = NULL; + } + + if (_hFile.isOpen()) + _hFile.close(); +} + +void RMResUpdate::init(const Common::String &fileName) { + // Open the resource update file + if (!_hFile.open(fileName)) + // It doesn't exist, so exit immediately + return; + + _hFile.readByte(); // Version, unused + + _numUpd = _hFile.readUint32LE(); + + _infos = new ResUpdInfo[_numUpd]; + + // Load the index of the resources in the file + for (uint32 i = 0; i < _numUpd; ++i) { + ResUpdInfo &info = _infos[i]; + + info._dwRes = _hFile.readUint32LE(); + info._offset = _hFile.readUint32LE(); + info._size = _hFile.readUint32LE(); + info._cmpSize = _hFile.readUint32LE(); + } +} + +MpalHandle RMResUpdate::queryResource(uint32 dwRes) { + // If there isn't an update file, return NULL + if (!_hFile.isOpen()) + return NULL; + + uint32 i; + for (i = 0; i < _numUpd; ++i) + if (_infos[i]._dwRes == dwRes) + // Found the index + break; + + if (i == _numUpd) + // Couldn't find a matching resource, so return NULL + return NULL; + + const ResUpdInfo &info = _infos[i]; + byte *cmpBuf = new byte[info._cmpSize]; + uint32 dwRead; + + // Move to the correct offset and read in the compressed data + _hFile.seek(info._offset); + dwRead = _hFile.read(cmpBuf, info._cmpSize); + + if (info._cmpSize > dwRead) { + // Error occurred reading data, so return NULL + delete[] cmpBuf; + return NULL; + } + + // Allocate space for the output resource + MpalHandle destBuf = globalAllocate(0, info._size); + byte *lpDestBuf = (byte *)globalLock(destBuf); + uint32 dwSize; + + // Decompress the data + lzo1x_decompress(cmpBuf, info._cmpSize, lpDestBuf, &dwSize); + + // Delete buffer for compressed data + delete [] cmpBuf; + + // Return the resource + globalUnlock(destBuf); + return destBuf; +} + +} // End of namespace Tony diff --git a/engines/tony/utils.h b/engines/tony/utils.h new file mode 100644 index 0000000000..9f13e5f19b --- /dev/null +++ b/engines/tony/utils.h @@ -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. + * + * 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 Tony Tough source code + * + * Copyright (c) 1997-2003 Nayma Software + */ + +#ifndef TONY_UTILS_H +#define TONY_UTILS_H + +#include "common/scummsys.h" +#include "common/file.h" +#include "common/rect.h" +#include "common/str.h" +#include "tony/mpal/memory.h" + +namespace Tony { + +using namespace ::Tony::MPAL; + +Common::String readString(Common::ReadStream &ds); + +/** + * Point class + */ +class RMPoint { +public: + int _x, _y; + +public: + // Constructor + RMPoint(); + RMPoint(const RMPoint &p); + RMPoint(int x1, int y1); + + // Copy + RMPoint &operator=(RMPoint p); + + // Set + void set(int x1, int y1); + + // Offset + void offset(int xOff, int yOff); + void offset(const RMPoint &p); + friend RMPoint operator+(RMPoint p1, RMPoint p2); + friend RMPoint operator-(RMPoint p1, RMPoint p2); + RMPoint &operator+=(RMPoint p); + RMPoint &operator-=(RMPoint p); + RMPoint operator-(); + + // Comparison + bool operator==(RMPoint p); + bool operator!=(RMPoint p); + + // Casting a POINT + operator Common::Point() const; + + // Extraction from data streams + void readFromStream(Common::ReadStream &ds); +}; + +class RMPointReference { +public: + int &_x; + int &_y; + + RMPointReference(int &x, int &y); + RMPointReference &operator=(const RMPoint &p); + RMPointReference &operator-=(const RMPoint &p); + operator RMPoint() const; +}; + +class RMRect { +public: + int _x1, _y1; + int _x2, _y2; + RMPointReference _topLeft; + RMPointReference _bottomRight; + +public: + RMRect(); + RMRect(int x1, int y1, int x2, int y2); + RMRect(const RMPoint &p1, const RMPoint &p2); + RMRect(const RMRect &rc); + + // Attributes + RMPointReference &topLeft(); + RMPointReference &bottomRight(); + RMPoint center(); + int width() const; + int height() const; + bool isEmpty() const; + int size() const; + operator Common::Rect() const; + + // Set + void setRect(int x1, int y1, int x2, int y2); + void setRect(const RMPoint &p1, const RMPoint &p2); + void setEmpty(); + + // Copiers + void setRect(const RMRect &rc); + void copyRect(const RMRect &rc); + const RMRect &operator=(const RMRect &rc); + + // Offset + void offset(int xOff, int yOff); + void offset(const RMPoint &p); + friend RMRect operator+(const RMRect &rc, RMPoint p); + friend RMRect operator-(const RMRect &rc, RMPoint p); + friend RMRect operator+(RMPoint p, const RMRect &rc); + friend RMRect operator-(RMPoint p, const RMRect &rc); + const RMRect &operator+=(RMPoint p); + const RMRect &operator-=(RMPoint p); + + // Comparison + bool operator==(const RMRect &rc); + bool operator!=(const RMRect &rc); + + // Normalize + void normalizeRect(); + + // Point in rect + bool ptInRect(const RMPoint &pt); + + // Extract from data stream + void readFromStream(Common::ReadStream &ds); +}; + +/** + * Resource update manager + */ +class RMResUpdate { + struct ResUpdInfo { + uint32 _dwRes; + uint32 _offset; + uint32 _size; + uint32 _cmpSize; + }; + + uint32 _numUpd; + ResUpdInfo *_infos; + Common::File _hFile; + +public: + RMResUpdate(); + ~RMResUpdate(); + + void init(const Common::String &fileName); + MpalHandle queryResource(uint32 dwRes); +}; + +} // End of namespace Tony + +#endif /* TONY_H */ diff --git a/engines/tony/window.cpp b/engines/tony/window.cpp new file mode 100644 index 0000000000..c9c450424f --- /dev/null +++ b/engines/tony/window.cpp @@ -0,0 +1,336 @@ +/* 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 Tony Tough source code + * + * Copyright (c) 1997-2003 Nayma Software + */ + +#include "common/scummsys.h" +#include "graphics/surface.h" +#include "util.h" +#include "tony/window.h" +#include "tony/game.h" +#include "tony/tony.h" + +namespace Tony { + + +/****************************************************************************\ +* RMWindow Methods +\****************************************************************************/ + +RMWindow::RMWindow() { + _showDirtyRects = false; +} + +RMWindow::~RMWindow() { + close(); + RMText::unload(); + RMGfxTargetBuffer::freeBWPrecalcTable(); +} + +/** + * Initializes the graphics window + */ +void RMWindow::init() { + Graphics::PixelFormat pixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0); + initGraphics(RM_SX, RM_SY, true, &pixelFormat); + + _bGrabScreenshot = false; + _bGrabThumbnail = false; + _bGrabMovie = false; + _wiping = false; +} + +void RMWindow::copyRectToScreen(const byte *buf, int pitch, int x, int y, int w, int h) { + if (GLOBALS._bCfgAnni30) { + if (!RMGfxTargetBuffer::_precalcTable) { + RMGfxTargetBuffer::createBWPrecalcTable(); + g_vm->getEngine()->getPointer().updateCursor(); + } + Graphics::Surface *screen = g_system->lockScreen(); + const uint16 *src = (const uint16 *)buf; + for (int i = 0; i < h; i++) { + uint16 *dst = (uint16 *)screen->getBasePtr(x, y + i); + for (int j = 0; j < w; j++) { + dst[j] = RMGfxTargetBuffer::_precalcTable[src[j] & 0x7FFF]; + } + src += (pitch / 2); + } + g_system->unlockScreen(); + } else { + if (RMGfxTargetBuffer::_precalcTable) { + RMGfxTargetBuffer::freeBWPrecalcTable(); + g_vm->getEngine()->getPointer().updateCursor(); + } + g_system->copyRectToScreen(buf, pitch, x, y, w, h); + } + } + +/** + * Close the window + */ +void RMWindow::close() { +} + +void RMWindow::grabThumbnail(uint16 *thumbmem) { + _bGrabThumbnail = true; + _wThumbBuf = thumbmem; +} + +/** + * Repaint the screen + */ +void RMWindow::repaint() { + g_system->updateScreen(); +} + +/** + * Wipes an area of the screen + */ +void RMWindow::wipeEffect(Common::Rect &rcBoundEllipse) { + if ((rcBoundEllipse.left == 0) && (rcBoundEllipse.top == 0) && + (rcBoundEllipse.right == RM_SX) && (rcBoundEllipse.bottom == RM_SY)) { + // Full screen clear wanted, so use shortcut method + g_system->fillScreen(0); + } else { + // Clear the designated area a line at a time + uint16 line[RM_SX]; + Common::fill(line, line + RM_SX, 0); + + // Loop through each line + for (int yp = rcBoundEllipse.top; yp < rcBoundEllipse.bottom; ++yp) { + copyRectToScreen((const byte *)&line[0], RM_SX * 2, rcBoundEllipse.left, yp, rcBoundEllipse.width(), 1); + } + } +} + +void RMWindow::getNewFrame(RMGfxTargetBuffer &bigBuf, Common::Rect *rcBoundEllipse) { + // Get a pointer to the bytes of the source buffer + byte *lpBuf = bigBuf; + + if (rcBoundEllipse != NULL) { + // Circular wipe effect + getNewFrameWipe(lpBuf, *rcBoundEllipse); + _wiping = true; + } else if (_wiping) { + // Just finished a wiping effect, so copy the full screen + copyRectToScreen(lpBuf, RM_SX * 2, 0, 0, RM_SX, RM_SY); + _wiping = false; + + } else { + // Standard screen copy - iterate through the dirty rects + Common::List<Common::Rect> dirtyRects = bigBuf.getDirtyRects(); + Common::List<Common::Rect>::iterator i; + + // If showing dirty rects, copy the entire screen background and set up a surface pointer + Graphics::Surface *s = NULL; + if (_showDirtyRects) { + copyRectToScreen(lpBuf, RM_SX * 2, 0, 0, RM_SX, RM_SY); + s = g_system->lockScreen(); + } + + for (i = dirtyRects.begin(); i != dirtyRects.end(); ++i) { + Common::Rect &r = *i; + const byte *lpSrc = lpBuf + (RM_SX * 2) * r.top + (r.left * 2); + copyRectToScreen(lpSrc, RM_SX * 2, r.left, r.top, r.width(), r.height()); + } + + if (_showDirtyRects) { + for (i = dirtyRects.begin(); i != dirtyRects.end(); ++i) { + // Frame the copied area with a rectangle + s->frameRect(*i, 0xffffff); + } + + g_system->unlockScreen(); + } + } + + if (_bGrabThumbnail) { + // Need to generate a thumbnail + RMSnapshot s; + + s.grabScreenshot(lpBuf, 4, _wThumbBuf); + _bGrabThumbnail = false; + } + + // Clear the dirty rect list + bigBuf.clearDirtyRects(); +} + +/** + * Copies a section of the game frame in a circle bounded by the specified rectangle + */ +void RMWindow::getNewFrameWipe(byte *lpBuf, Common::Rect &rcBoundEllipse) { + // Clear the screen + g_system->fillScreen(0); + + if (!rcBoundEllipse.isValidRect()) + return; + + Common::Point center(rcBoundEllipse.left + rcBoundEllipse.width() / 2, + rcBoundEllipse.top + rcBoundEllipse.height() / 2); + + // The rectangle technically defines the area inside the ellipse, with the corners touching + // the ellipse boundary. Since we're currently simulating the ellipse using a plain circle, + // we need to calculate a necessary width using the hypotenuse of X/2 & Y/2 + int x2y2 = (rcBoundEllipse.width() / 2) * (rcBoundEllipse.width() / 2) + + (rcBoundEllipse.height() / 2) * (rcBoundEllipse.height() / 2); + int radius = 0; + while ((radius * radius) < x2y2) + ++radius; + + // Proceed copying a circular area of the frame with the calculated radius onto the screen + int error = -radius; + int x = radius; + int y = 0; + + while (x >= y) { + plotSplices(lpBuf, center, x, y); + + error += y; + ++y; + error += y; + + if (error >= 0) { + error -= x; + --x; + error -= x; + } + } +} + +/** + * Handles drawing the line splices for the circle of viewable area + */ +void RMWindow::plotSplices(const byte *lpBuf, const Common::Point ¢er, int x, int y) { + plotLines(lpBuf, center, x, y); + if (x != y) + plotLines(lpBuf, center, y, x); +} + +/** + * Handles drawing the line splices for the circle of viewable area + */ +void RMWindow::plotLines(const byte *lpBuf, const Common::Point ¢er, int x, int y) { + // Skips lines that have no width (i.e. at the top of the circle) + if ((x == 0) || (y > center.y)) + return; + + const byte *pSrc; + int xs = MAX(center.x - x, 0); + int width = MIN(RM_SX - xs, x * 2); + + if ((center.y - y) >= 0) { + // Draw line in top half of circle + pSrc = lpBuf + ((center.y - y) * RM_SX * 2) + xs * 2; + copyRectToScreen(pSrc, RM_SX * 2, xs, center.y - y, width, 1); + } + + if ((center.y + y) < RM_SY) { + // Draw line in bottom half of circle + pSrc = lpBuf + ((center.y + y) * RM_SX * 2) + xs * 2; + copyRectToScreen(pSrc, RM_SX * 2, xs, center.y + y, width, 1); + } +} + +void RMWindow::showDirtyRects(bool v) { + _showDirtyRects = v; +} + +/****************************************************************************\ +* RMSnapshot Methods +\****************************************************************************/ + +void RMSnapshot::grabScreenshot(byte *lpBuf, int dezoom, uint16 *lpDestBuf) { + uint16 *src = (uint16 *)lpBuf; + + int dimx = RM_SX / dezoom; + int dimy = RM_SY / dezoom; + + uint16 *cursrc; + + if (lpDestBuf == NULL) + src += (RM_SY - 1) * RM_BBX; + + if (dezoom == 1 && 0) { + byte *curOut = _rgb; + + for (int y = 0; y < dimy; y++) { + for (int x = 0; x < dimx; x++) { + cursrc = &src[RM_SKIPX + x]; + + *curOut++ = ((*cursrc) & 0x1F) << 3; + *curOut++ = (((*cursrc) >> 5) & 0x1F) << 3; + *curOut++ = (((*cursrc) >> 10) & 0x1F) << 3; + + if (lpDestBuf) + *lpDestBuf++ = *cursrc; + } + + if (lpDestBuf == NULL) + src -= RM_BBX; + else + src += RM_BBX; + } + } else { + uint32 k = 0; + for (int y = 0; y < dimy; y++) { + for (int x = 0; x < dimx; x++) { + cursrc = &src[RM_SKIPX + x * dezoom]; + int sommar, sommab, sommag, curv; + sommar = sommab = sommag = 0; + + for (int v = 0; v < dezoom; v++) { + for (int u = 0; u < dezoom; u++) { + if (lpDestBuf == NULL) + curv = -v; + else + curv = v; + + sommab += cursrc[curv * RM_BBX + u] & 0x1F; + sommag += (cursrc[curv * RM_BBX + u] >> 5) & 0x1F; + sommar += (cursrc[curv * RM_BBX + u] >> 10) & 0x1F; + } + } + _rgb[k + 0] = (byte)(sommab * 8 / (dezoom * dezoom)); + _rgb[k + 1] = (byte)(sommag * 8 / (dezoom * dezoom)); + _rgb[k + 2] = (byte)(sommar * 8 / (dezoom * dezoom)); + + if (lpDestBuf != NULL) + lpDestBuf[k / 3] = ((int)_rgb[k + 0] >> 3) | (((int)_rgb[k + 1] >> 3) << 5) | + (((int)_rgb[k + 2] >> 3) << 10); + + k += 3; + } + + if (lpDestBuf == NULL) + src -= RM_BBX * dezoom; + else + src += RM_BBX * dezoom; + } + } +} + +} // End of namespace Tony diff --git a/engines/tony/window.h b/engines/tony/window.h new file mode 100644 index 0000000000..2e8769707f --- /dev/null +++ b/engines/tony/window.h @@ -0,0 +1,98 @@ +/* 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 Tony Tough source code + * + * Copyright (c) 1997-2003 Nayma Software + */ + +#ifndef TONY_WINDOW_H +#define TONY_WINDOW_H + +#include "common/scummsys.h" +#include "common/rect.h" +#include "tony/game.h" + +namespace Tony { + +class RMSnapshot { +private: + // Buffer used to convert to RGB + byte _rgb[RM_SX *RM_SY * 3]; +public: + /** + * Take a screenshot + */ + void grabScreenshot(byte *lpBuf, int dezoom = 1, uint16 *lpDestBuf = NULL); +}; + + +class RMWindow { +private: + void plotSplices(const byte *lpBuf, const Common::Point ¢er, int x, int y); + void plotLines(const byte *lpBuf, const Common::Point ¢er, int x, int y); + +protected: + bool _wiping; + bool _showDirtyRects; + + bool _bGrabScreenshot; + bool _bGrabThumbnail; + bool _bGrabMovie; + uint16 *_wThumbBuf; + + void copyRectToScreen(const byte *buf, int pitch, int x, int y, int w, int h); + void wipeEffect(Common::Rect &rcBoundEllipse); + void getNewFrameWipe(byte *lpBuf, Common::Rect &rcBoundEllipse); + +public: + RMWindow(); + ~RMWindow(); + + /** + * Initialization + */ + void init(); + void close(); + + /** + * Drawing + */ + void repaint(); + + /** + * Reads the next frame + */ + void getNewFrame(RMGfxTargetBuffer &lpBuf, Common::Rect *rcBoundEllipse); + + /** + * Request a thumbnail be grabbed during the next frame + */ + void grabThumbnail(uint16 *buf); + + void showDirtyRects(bool v); +}; + +} // End of namespace Tony + +#endif /* TONY_WINDOW_H */ |