diff options
52 files changed, 2984 insertions, 518 deletions
@@ -7,6 +7,7 @@ For a more comprehensive changelog of the latest experimental code, see: - Added support for Blue Force. - Added support for Darby the Dragon. - Added support for Dreamweb. + - Added support for Geisha. - Added support for Gregory and the Hot Air Balloon. - Added support for Magic Tales: Baba Yaga and the Magic Geese. - Added support for Magic Tales: Imo and the King. @@ -28,7 +29,7 @@ For a more comprehensive changelog of the latest experimental code, see: Engine tab when adding or editing a configuration for a game. In most cases, you will have to run each game once or readd them all in ScummVM's launcher in order to get the custom options tab. - - Improved predicitve dialog look. + - Improved predictive dialog look. - Various GUI improvements. SDL ports: diff --git a/common/coroutines.cpp b/common/coroutines.cpp index d511ab4b35..241d31e0d7 100644 --- a/common/coroutines.cpp +++ b/common/coroutines.cpp @@ -34,14 +34,17 @@ CoroContext nullContext = NULL; DECLARE_SINGLETON(CoroutineScheduler); - #ifdef COROUTINE_DEBUG namespace { +/** Count of active coroutines */ static int s_coroCount = 0; typedef Common::HashMap<Common::String, int> CoroHashMap; static CoroHashMap *s_coroFuncs = 0; +/** + * Change the current coroutine status + */ static void changeCoroStats(const char *func, int change) { if (!s_coroFuncs) s_coroFuncs = new CoroHashMap(); @@ -49,6 +52,9 @@ static void changeCoroStats(const char *func, int change) { (*s_coroFuncs)[func] += change; } +/** + * Display the details of active coroutines + */ static void displayCoroStats() { debug("%d active coros", s_coroCount); @@ -56,13 +62,13 @@ static void displayCoroStats() { if (!s_coroFuncs) return; for (CoroHashMap::const_iterator it = s_coroFuncs->begin(); - it != s_coroFuncs->end(); ++it) { + it != s_coroFuncs->end(); ++it) { if (it->_value != 0) debug(" %3d x %s", it->_value, it->_key.c_str()); } } -} +} // End of anonymous namespace #endif CoroBaseContext::CoroBaseContext(const char *func) @@ -79,7 +85,7 @@ CoroBaseContext::~CoroBaseContext() { s_coroCount--; changeCoroStats(_funcName, -1); debug("Deleting coro in %s at %p (subctx %p)", - _funcName, (void *)this, (void *)_subctx); + _funcName, (void *)this, (void *)_subctx); displayCoroStats(); #endif delete _subctx; @@ -87,9 +93,6 @@ CoroBaseContext::~CoroBaseContext() { //--------------------- Scheduler Class ------------------------ -/** - * Constructor - */ CoroutineScheduler::CoroutineScheduler() { processList = NULL; pFreeProcesses = NULL; @@ -111,9 +114,6 @@ CoroutineScheduler::CoroutineScheduler() { reset(); } -/** - * Destructor - */ CoroutineScheduler::~CoroutineScheduler() { // Kill all running processes (i.e. free memory allocated for their state). PROCESS *pProc = active->pNext; @@ -132,14 +132,10 @@ CoroutineScheduler::~CoroutineScheduler() { // Clear the event list Common::List<EVENT *>::iterator i; for (i = _events.begin(); i != _events.end(); ++i) - delete (*i); + delete *i; } -/** - * Kills all processes and places them on the free list. - */ void CoroutineScheduler::reset() { - #ifdef DEBUG // clear number of process in use numProcs = 0; @@ -181,21 +177,14 @@ void CoroutineScheduler::reset() { } -#ifdef DEBUG -/** - * Shows the maximum number of process used at once. - */ +#ifdef DEBUG void CoroutineScheduler::printStats() { debug("%i process of %i used", maxProcs, CORO_NUM_PROCESS); } #endif #ifdef DEBUG -/** - * Checks both the active and free process list to insure all the links are valid, - * and that no processes have been lost - */ -void CoroutineScheduler::CheckStack() { +void CoroutineScheduler::checkStack() { Common::List<PROCESS *> pList; // Check both the active and free process lists @@ -229,9 +218,6 @@ void CoroutineScheduler::CheckStack() { } #endif -/** - * Give all active processes a chance to run - */ void CoroutineScheduler::schedule() { // start dispatching active process list PROCESS *pNext; @@ -261,9 +247,6 @@ void CoroutineScheduler::schedule() { } } -/** - * Reschedules all the processes to run again this query - */ void CoroutineScheduler::rescheduleAll() { assert(pCurrent); @@ -279,10 +262,6 @@ void CoroutineScheduler::rescheduleAll() { pCurrent->pPrevious = active; } -/** - * If the specified process has already run on this tick, make it run - * again on the current tick. - */ void CoroutineScheduler::reschedule(PPROCESS pReSchedProc) { // If not currently processing the schedule list, then no action is needed if (!pCurrent) @@ -320,11 +299,6 @@ void CoroutineScheduler::reschedule(PPROCESS pReSchedProc) { pReSchedProc->pNext = NULL; } -/** - * Moves the specified process to the end of the dispatch queue - * allowing it to run again within the current game cycle. - * @param pGiveProc Which process - */ void CoroutineScheduler::giveWay(PPROCESS pReSchedProc) { // If not currently processing the schedule list, then no action is needed if (!pCurrent) @@ -358,13 +332,6 @@ void CoroutineScheduler::giveWay(PPROCESS pReSchedProc) { pReSchedProc->pNext = NULL; } -/** - * Continously makes a given process wait for another process to finish or event to signal. - * - * @param pid Process/Event identifier - * @param duration Duration in milliseconds - * @param expired If specified, set to true if delay period expired - */ void CoroutineScheduler::waitForSingleObject(CORO_PARAM, int pid, uint32 duration, bool *expired) { if (!pCurrent) error("Called CoroutineScheduler::waitForSingleObject from the main process"); @@ -385,8 +352,8 @@ void CoroutineScheduler::waitForSingleObject(CORO_PARAM, int pid, uint32 duratio // Presume it will expire *expired = true; - // Outer loop for doing checks until expiry - while (g_system->getMillis() < _ctx->endTime) { + // Outer loop for doing checks until expiry + while (g_system->getMillis() <= _ctx->endTime) { // Check to see if a process or event with the given Id exists _ctx->pProcess = getProcess(pid); _ctx->pEvent = !_ctx->pProcess ? getEvent(pid) : NULL; @@ -399,7 +366,7 @@ void CoroutineScheduler::waitForSingleObject(CORO_PARAM, int pid, uint32 duratio break; } - // If a process was found, don't go into the if statement, and keep waiting. + // If a process was found, don't go into the if statement, and keep waiting. // Likewise if it's an event that's not yet signalled if ((_ctx->pEvent != NULL) && _ctx->pEvent->signalled) { // Unless the event is flagged for manual reset, reset it now @@ -421,19 +388,10 @@ void CoroutineScheduler::waitForSingleObject(CORO_PARAM, int pid, uint32 duratio CORO_END_CODE; } -/** - * Continously makes a given process wait for given prcesses to finished or events to be set - * - * @param nCount Number of Id's being passed - * @param evtList List of pids to wait for - * @param bWaitAll Specifies whether all or any of the processes/events - * @param duration Duration in milliseconds - * @param expired Set to true if delay period expired - */ -void CoroutineScheduler::waitForMultipleObjects(CORO_PARAM, int nCount, uint32 *pidList, bool bWaitAll, - uint32 duration, bool *expired) { +void CoroutineScheduler::waitForMultipleObjects(CORO_PARAM, int nCount, uint32 *pidList, bool bWaitAll, + uint32 duration, bool *expired) { if (!pCurrent) - error("Called CoroutineScheduler::waitForMultipleEvents from the main process"); + error("Called CoroutineScheduler::waitForMultipleObjects from the main process"); CORO_BEGIN_CONTEXT; uint32 endTime; @@ -455,8 +413,8 @@ void CoroutineScheduler::waitForMultipleObjects(CORO_PARAM, int nCount, uint32 * // Presume that delay will expire *expired = true; - // Outer loop for doing checks until expiry - while (g_system->getMillis() < _ctx->endTime) { + // Outer loop for doing checks until expiry + while (g_system->getMillis() <= _ctx->endTime) { _ctx->signalled = bWaitAll; for (_ctx->i = 0; _ctx->i < nCount; ++_ctx->i) { @@ -497,15 +455,9 @@ void CoroutineScheduler::waitForMultipleObjects(CORO_PARAM, int nCount, uint32 * CORO_END_CODE; } -/** - * Make the active process sleep for the given duration in milliseconds - * @param duration Duration in milliseconds - * @remarks This duration won't be precise, since it relies on the frequency the - * scheduler is called. - */ void CoroutineScheduler::sleep(CORO_PARAM, uint32 duration) { if (!pCurrent) - error("Called CoroutineScheduler::waitForSingleObject from the main process"); + error("Called CoroutineScheduler::sleep from the main process"); CORO_BEGIN_CONTEXT; uint32 endTime; @@ -517,7 +469,7 @@ void CoroutineScheduler::sleep(CORO_PARAM, uint32 duration) { _ctx->endTime = g_system->getMillis() + duration; - // Outer loop for doing checks until expiry + // Outer loop for doing checks until expiry while (g_system->getMillis() < _ctx->endTime) { // Sleep until the next cycle CORO_SLEEP(1); @@ -526,14 +478,6 @@ void CoroutineScheduler::sleep(CORO_PARAM, uint32 duration) { CORO_END_CODE; } -/** - * Creates a new process. - * - * @param pid process identifier - * @param CORO_ADDR coroutine start address - * @param pParam process specific info - * @param sizeParam size of process specific info - */ PROCESS *CoroutineScheduler::createProcess(uint32 pid, CORO_ADDR coroAddr, const void *pParam, int sizeParam) { PROCESS *pProc; @@ -564,7 +508,7 @@ PROCESS *CoroutineScheduler::createProcess(uint32 pid, CORO_ADDR coroAddr, const pCurrent->pNext = pProc; pProc->pPrevious = pCurrent; - } else { // no active processes, place process at head of list + } else { // no active processes, place process at head of list pProc->pNext = active->pNext; pProc->pPrevious = active; @@ -598,35 +542,15 @@ PROCESS *CoroutineScheduler::createProcess(uint32 pid, CORO_ADDR coroAddr, const return pProc; } -/** - * Creates a new process with an auto-incrementing Process Id. - * - * @param CORO_ADDR coroutine start address - * @param pParam process specific info - * @param sizeParam size of process specific info - */ uint32 CoroutineScheduler::createProcess(CORO_ADDR coroAddr, const void *pParam, int sizeParam) { PROCESS *pProc = createProcess(++pidCounter, coroAddr, pParam, sizeParam); return pProc->pid; } -/** - * Creates a new process with an auto-incrementing Process Id, and a single pointer parameter. - * - * @param CORO_ADDR coroutine start address - * @param pParam process specific info - * @param sizeParam size of process specific info - */ uint32 CoroutineScheduler::createProcess(CORO_ADDR coroAddr, const void *pParam) { return createProcess(coroAddr, &pParam, sizeof(void *)); } - -/** - * Kills the specified process. - * - * @param pKillProc which process to kill - */ void CoroutineScheduler::killProcess(PROCESS *pKillProc) { // make sure a valid process pointer assert(pKillProc >= processList && pKillProc <= processList + CORO_NUM_PROCESS - 1); @@ -662,20 +586,10 @@ void CoroutineScheduler::killProcess(PROCESS *pKillProc) { pFreeProcesses = pKillProc; } - - -/** - * Returns a pointer to the currently running process. - */ PROCESS *CoroutineScheduler::getCurrentProcess() { return pCurrent; } -/** - * Returns the process identifier of the specified process. - * - * @param pProc which process - */ int CoroutineScheduler::getCurrentPID() const { PROCESS *pProc = pCurrent; @@ -686,17 +600,9 @@ int CoroutineScheduler::getCurrentPID() const { return pProc->pid; } -/** - * Kills any process matching the specified PID. The current - * process cannot be killed. - * - * @param pidKill process identifier of process to kill - * @param pidMask mask to apply to process identifiers before comparison - * @return The number of processes killed is returned. - */ int CoroutineScheduler::killMatchingProcess(uint32 pidKill, int pidMask) { int numKilled = 0; - PROCESS *pProc, *pPrev; // process list pointers + PROCESS *pProc, *pPrev; // process list pointers for (pProc = active->pNext, pPrev = active; pProc != NULL; pPrev = pProc, pProc = pProc->pNext) { if ((pProc->pid & (uint32)pidMask) == pidKill) { @@ -743,15 +649,6 @@ int CoroutineScheduler::killMatchingProcess(uint32 pidKill, int pidMask) { return numKilled; } -/** - * Set pointer to a function to be called by killProcess(). - * - * May be called by a resource allocator, the function supplied is - * called by killProcess() to allow the resource allocator to free - * resources allocated to the dying process. - * - * @param pFunc Function to be called by killProcess() - */ void CoroutineScheduler::setResourceCallback(VFPTRPP pFunc) { pRCfunction = pFunc; } @@ -776,12 +673,6 @@ EVENT *CoroutineScheduler::getEvent(uint32 pid) { } -/** - * Creates a new event object - * @param bManualReset Events needs to be manually reset. Otherwise, events - * will be automatically reset after a process waits on the event finishes - * @param bInitialState Specifies whether the event is signalled or not initially - */ uint32 CoroutineScheduler::createEvent(bool bManualReset, bool bInitialState) { EVENT *evt = new EVENT(); evt->pid = ++pidCounter; @@ -792,10 +683,6 @@ uint32 CoroutineScheduler::createEvent(bool bManualReset, bool bInitialState) { return evt->pid; } -/** - * Destroys the given event - * @param pidEvent Event PID - */ void CoroutineScheduler::closeEvent(uint32 pidEvent) { EVENT *evt = getEvent(pidEvent); if (evt) { @@ -804,41 +691,26 @@ void CoroutineScheduler::closeEvent(uint32 pidEvent) { } } -/** - * Sets the event - * @param pidEvent Event PID - */ void CoroutineScheduler::setEvent(uint32 pidEvent) { EVENT *evt = getEvent(pidEvent); if (evt) evt->signalled = true; } -/** - * Resets the event - * @param pidEvent Event PID - */ void CoroutineScheduler::resetEvent(uint32 pidEvent) { EVENT *evt = getEvent(pidEvent); if (evt) evt->signalled = false; } -/** - * Temporarily sets a given event to true, and then runs all waiting processes, allowing any - * processes waiting on the event to be fired. It then immediately resets the event again. - * @param pidEvent Event PID - * - * @remarks Should not be run inside of another process - */ void CoroutineScheduler::pulseEvent(uint32 pidEvent) { EVENT *evt = getEvent(pidEvent); if (!evt) return; - + // Set the event as true evt->signalled = true; - + // start dispatching active process list for any processes that are currently waiting PROCESS *pOriginal = pCurrent; PROCESS *pNext; @@ -880,5 +752,4 @@ void CoroutineScheduler::pulseEvent(uint32 pidEvent) { evt->signalled = false; } - } // end of namespace Common diff --git a/common/coroutines.h b/common/coroutines.h index 6df843887c..64eabbf8f4 100644 --- a/common/coroutines.h +++ b/common/coroutines.h @@ -23,7 +23,7 @@ #define COMMON_COROUTINES_H #include "common/scummsys.h" -#include "common/util.h" // for SCUMMVM_CURRENT_FUNCTION +#include "common/util.h" // for SCUMMVM_CURRENT_FUNCTION #include "common/list.h" #include "common/singleton.h" @@ -56,7 +56,14 @@ struct CoroBaseContext { #ifdef COROUTINE_DEBUG const char *_funcName; #endif + /** + * Creates a coroutine context + */ CoroBaseContext(const char *func); + + /** + * Destructor for coroutine context + */ virtual ~CoroBaseContext(); }; @@ -64,8 +71,9 @@ typedef CoroBaseContext *CoroContext; /** This is a special constant that can be temporarily used as a parameter to call coroutine-ised - * from methods from methods that haven't yet been converted to being a coroutine, so code at least - * compiles correctly. Be aware, though, that if you use this, you will get runtime errors. + * methods from code that haven't yet been converted to being a coroutine, so code at least + * compiles correctly. Be aware, though, that an error will occur if a coroutine that was passed + * the nullContext tries to sleep or yield control. */ extern CoroContext nullContext; @@ -125,42 +133,43 @@ public: /** * End the declaration of a coroutine context. - * @param x name of the coroutine context + * @param x name of the coroutine context * @see CORO_BEGIN_CONTEXT */ #define CORO_END_CONTEXT(x) } *x = (CoroContextTag *)coroParam /** * Begin the code section of a coroutine. - * @param x name of the coroutine context + * @param x name of the coroutine context * @see CORO_BEGIN_CODE */ #define CORO_BEGIN_CODE(x) \ - if (&coroParam == &Common::nullContext) assert(!Common::nullContext);\ - if (!x) {coroParam = x = new CoroContextTag();}\ - Common::CoroContextHolder tmpHolder(coroParam);\ - switch (coroParam->_line) { case 0:; + if (&coroParam == &Common::nullContext) assert(!Common::nullContext); \ + if (!x) { coroParam = x = new CoroContextTag(); } \ + Common::CoroContextHolder tmpHolder(coroParam); \ + switch (coroParam->_line) { case 0:; /** * End the code section of a coroutine. * @see CORO_END_CODE */ #define CORO_END_CODE \ - if (&coroParam == &Common::nullContext) { \ - delete Common::nullContext; \ - Common::nullContext = NULL; \ - } \ - } + if (&coroParam == &Common::nullContext) { \ + delete Common::nullContext; \ + Common::nullContext = NULL; \ + } \ + } /** * Sleep for the specified number of scheduler cycles. */ -#define CORO_SLEEP(delay) do {\ - coroParam->_line = __LINE__;\ - coroParam->_sleep = delay;\ - assert(&coroParam != &Common::nullContext);\ - return; case __LINE__:;\ - } while (0) +#define CORO_SLEEP(delay) \ + do { \ + coroParam->_line = __LINE__; \ + coroParam->_sleep = delay; \ + assert(&coroParam != &Common::nullContext); \ + return; case __LINE__:; \ + } while (0) #define CORO_GIVE_WAY do { CoroScheduler.giveWay(); CORO_SLEEP(1); } while (0) #define CORO_RESCHEDULE do { CoroScheduler.reschedule(); CORO_SLEEP(1); } while (0) @@ -174,7 +183,7 @@ public: * then delete the entire coroutine's state, including all subcontexts). */ #define CORO_KILL_SELF() \ - do { if (&coroParam != &Common::nullContext) { coroParam->_sleep = -1; } return; } while (0) + do { if (&coroParam != &Common::nullContext) { coroParam->_sleep = -1; } return; } while (0) /** @@ -193,8 +202,8 @@ public: * If the subcontext is null, the coroutine ended normally, and we can * simply break out of the loop and continue execution. * - * @param subCoro name of the coroutine-enabled function to invoke - * @param ARGS list of arguments to pass to subCoro + * @param subCoro name of the coroutine-enabled function to invoke + * @param ARGS list of arguments to pass to subCoro * * @note ARGS must be surrounded by parentheses, and the first argument * in this list must always be CORO_SUBCTX. For example, the @@ -203,18 +212,18 @@ public: * becomes the following: * CORO_INVOKE_ARGS(myFunc, (CORO_SUBCTX, a, b)); */ -#define CORO_INVOKE_ARGS(subCoro, ARGS) \ - do {\ - coroParam->_line = __LINE__;\ - coroParam->_subctx = 0;\ - do {\ - subCoro ARGS;\ - if (!coroParam->_subctx) break;\ - coroParam->_sleep = coroParam->_subctx->_sleep;\ - assert(&coroParam != &Common::nullContext);\ - return; case __LINE__:;\ - } while (1);\ - } while (0) +#define CORO_INVOKE_ARGS(subCoro, ARGS) \ + do { \ + coroParam->_line = __LINE__; \ + coroParam->_subctx = 0; \ + do { \ + subCoro ARGS; \ + if (!coroParam->_subctx) break; \ + coroParam->_sleep = coroParam->_subctx->_sleep; \ + assert(&coroParam != &Common::nullContext); \ + return; case __LINE__:; \ + } while (1); \ + } while (0) /** * Invoke another coroutine. Similar to CORO_INVOKE_ARGS, @@ -222,81 +231,82 @@ public: * if invoked coroutine yields (thus causing the current * coroutine to yield, too). */ -#define CORO_INVOKE_ARGS_V(subCoro, RESULT, ARGS) \ - do {\ - coroParam->_line = __LINE__;\ - coroParam->_subctx = 0;\ - do {\ - subCoro ARGS;\ - if (!coroParam->_subctx) break;\ - coroParam->_sleep = coroParam->_subctx->_sleep;\ - assert(&coroParam != &Common::nullContext);\ - return RESULT; case __LINE__:;\ - } while (1);\ - } while (0) +#define CORO_INVOKE_ARGS_V(subCoro, RESULT, ARGS) \ + do { \ + coroParam->_line = __LINE__; \ + coroParam->_subctx = 0; \ + do { \ + subCoro ARGS; \ + if (!coroParam->_subctx) break; \ + coroParam->_sleep = coroParam->_subctx->_sleep; \ + assert(&coroParam != &Common::nullContext); \ + return RESULT; case __LINE__:; \ + } while (1); \ + } while (0) /** * Convenience wrapper for CORO_INVOKE_ARGS for invoking a coroutine * with no parameters. */ #define CORO_INVOKE_0(subCoroutine) \ - CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX)) + CORO_INVOKE_ARGS(subCoroutine, (CORO_SUBCTX)) /** * Convenience wrapper for CORO_INVOKE_ARGS for invoking a coroutine * with one parameter. */ #define CORO_INVOKE_1(subCoroutine, a0) \ - CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX,a0)) + CORO_INVOKE_ARGS(subCoroutine, (CORO_SUBCTX, a0)) /** * Convenience wrapper for CORO_INVOKE_ARGS for invoking a coroutine * with two parameters. */ #define CORO_INVOKE_2(subCoroutine, a0,a1) \ - CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX,a0,a1)) + CORO_INVOKE_ARGS(subCoroutine, (CORO_SUBCTX, a0, a1)) /** * Convenience wrapper for CORO_INVOKE_ARGS for invoking a coroutine * with three parameters. */ #define CORO_INVOKE_3(subCoroutine, a0,a1,a2) \ - CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX,a0,a1,a2)) + CORO_INVOKE_ARGS(subCoroutine, (CORO_SUBCTX, a0, a1, a2)) /** * Convenience wrapper for CORO_INVOKE_ARGS for invoking a coroutine * with four parameters. */ #define CORO_INVOKE_4(subCoroutine, a0,a1,a2,a3) \ - CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX,a0,a1,a2,a3)) + CORO_INVOKE_ARGS(subCoroutine, (CORO_SUBCTX, a0, a1, a2, a3)) // the size of process specific info -#define CORO_PARAM_SIZE 32 +#define CORO_PARAM_SIZE 32 // the maximum number of processes -#define CORO_NUM_PROCESS 100 -#define CORO_MAX_PROCESSES 100 +#define CORO_NUM_PROCESS 100 +#define CORO_MAX_PROCESSES 100 #define CORO_MAX_PID_WAITING 5 #define CORO_INFINITE 0xffffffff #define CORO_INVALID_PID_VALUE 0 +/** Coroutine parameter for methods converted to coroutines */ typedef void (*CORO_ADDR)(CoroContext &, const void *); /** process structure */ struct PROCESS { - PROCESS *pNext; ///< pointer to next process in active or free list - PROCESS *pPrevious; ///< pointer to previous process in active or free list + PROCESS *pNext; ///< pointer to next process in active or free list + PROCESS *pPrevious; ///< pointer to previous process in active or free list - CoroContext state; ///< the state of the coroutine - CORO_ADDR coroAddr; ///< the entry point of the coroutine + CoroContext state; ///< the state of the coroutine + CORO_ADDR coroAddr; ///< the entry point of the coroutine - int sleepTime; ///< number of scheduler cycles to sleep - uint32 pid; ///< process ID - uint32 pidWaiting[CORO_MAX_PID_WAITING]; ///< Process ID(s) process is currently waiting on - char param[CORO_PARAM_SIZE]; ///< process specific info + int sleepTime; ///< number of scheduler cycles to sleep + uint32 pid; ///< process ID + uint32 pidWaiting[CORO_MAX_PID_WAITING]; ///< Process ID(s) process is currently waiting on + char param[CORO_PARAM_SIZE]; ///< process specific info }; typedef PROCESS *PPROCESS; @@ -312,12 +322,24 @@ struct EVENT { /** * Creates and manages "processes" (really coroutines). */ -class CoroutineScheduler: public Singleton<CoroutineScheduler> { +class CoroutineScheduler : public Singleton<CoroutineScheduler> { public: /** Pointer to a function of the form "void function(PPROCESS)" */ typedef void (*VFPTRPP)(PROCESS *); private: + friend class Singleton<CoroutineScheduler>; + + /** + * Constructor + */ + CoroutineScheduler(); + + /** + * Destructor + */ + ~CoroutineScheduler(); + /** list of all processes */ PROCESS *processList; @@ -342,7 +364,11 @@ private: int numProcs; int maxProcs; - void CheckStack(); + /** + * Checks both the active and free process list to insure all the links are valid, + * and that no processes have been lost + */ + void checkStack(); #endif /** @@ -354,41 +380,175 @@ private: PROCESS *getProcess(uint32 pid); EVENT *getEvent(uint32 pid); public: - - CoroutineScheduler(); - ~CoroutineScheduler(); - + /** + * Kills all processes and places them on the free list. + */ void reset(); - #ifdef DEBUG +#ifdef DEBUG + /** + * Shows the maximum number of process used at once. + */ void printStats(); - #endif +#endif + /** + * Give all active processes a chance to run + */ void schedule(); + + /** + * Reschedules all the processes to run again this tick + */ void rescheduleAll(); + + /** + * If the specified process has already run on this tick, make it run + * again on the current tick. + */ void reschedule(PPROCESS pReSchedProc = NULL); + + /** + * Moves the specified process to the end of the dispatch queue + * allowing it to run again within the current game cycle. + * @param pGiveProc Which process + */ void giveWay(PPROCESS pReSchedProc = NULL); + + /** + * Continously makes a given process wait for another process to finish or event to signal. + * + * @param pid Process/Event identifier + * @param duration Duration in milliseconds + * @param expired If specified, set to true if delay period expired + */ void waitForSingleObject(CORO_PARAM, int pid, uint32 duration, bool *expired = NULL); - void waitForMultipleObjects(CORO_PARAM, int nCount, uint32 *pidList, bool bWaitAll, - uint32 duration, bool *expired = NULL); + + /** + * Continously makes a given process wait for given prcesses to finished or events to be set + * + * @param nCount Number of Id's being passed + * @param evtList List of pids to wait for + * @param bWaitAll Specifies whether all or any of the processes/events + * @param duration Duration in milliseconds + * @param expired Set to true if delay period expired + */ + void waitForMultipleObjects(CORO_PARAM, int nCount, uint32 *pidList, bool bWaitAll, + uint32 duration, bool *expired = NULL); + + /** + * Make the active process sleep for the given duration in milliseconds + * + * @param duration Duration in milliseconds + * @remarks This duration won't be precise, since it relies on the frequency the + * scheduler is called. + */ void sleep(CORO_PARAM, uint32 duration); + /** + * Creates a new process. + * + * @param pid process identifier + * @param coroAddr Coroutine start address + * @param pParam Process specific info + * @param sizeParam Size of process specific info + */ PROCESS *createProcess(uint32 pid, CORO_ADDR coroAddr, const void *pParam, int sizeParam); + + /** + * Creates a new process with an auto-incrementing Process Id. + * + * @param coroAddr Coroutine start address + * @param pParam Process specific info + * @param sizeParam Size of process specific info + */ uint32 createProcess(CORO_ADDR coroAddr, const void *pParam, int sizeParam); + + /** + * Creates a new process with an auto-incrementing Process Id, and a single pointer parameter. + * + * @param coroAddr Coroutine start address + * @param pParam Process specific info + */ uint32 createProcess(CORO_ADDR coroAddr, const void *pParam); + + /** + * Kills the specified process. + * + * @param pKillProc Which process to kill + */ void killProcess(PROCESS *pKillProc); + /** + * Returns a pointer to the currently running process. + */ PROCESS *getCurrentProcess(); + + /** + * Returns the process identifier of the currently running process. + */ int getCurrentPID() const; + + /** + * Kills any process matching the specified PID. The current + * process cannot be killed. + * + * @param pidKill Process identifier of process to kill + * @param pidMask Mask to apply to process identifiers before comparison + * @return The number of processes killed is returned. + */ int killMatchingProcess(uint32 pidKill, int pidMask = -1); + /** + * Set pointer to a function to be called by killProcess(). + * + * May be called by a resource allocator, the function supplied is + * called by killProcess() to allow the resource allocator to free + * resources allocated to the dying process. + * + * @param pFunc Function to be called by killProcess() + */ void setResourceCallback(VFPTRPP pFunc); /* Event methods */ + /** + * Creates a new event (semaphore) object + * + * @param bManualReset Events needs to be manually reset. Otherwise, + * events will be automatically reset after a + * process waits on the event finishes + * @param bInitialState Specifies whether the event is signalled or not + * initially + */ uint32 createEvent(bool bManualReset, bool bInitialState); + + /** + * Destroys the given event + * @param pidEvent Event Process Id + */ void closeEvent(uint32 pidEvent); + + /** + * Sets the event + * @param pidEvent Event Process Id + */ void setEvent(uint32 pidEvent); + + /** + * Resets the event + * @param pidEvent Event Process Id + */ void resetEvent(uint32 pidEvent); + + /** + * Temporarily sets a given event to true, and then runs all waiting + * processes,allowing any processes waiting on the event to be fired. It + * then immediately resets the event again. + * + * @param pidEvent Event Process Id + * + * @remarks Should not be run inside of another process + */ void pulseEvent(uint32 pidEvent); }; @@ -396,4 +556,4 @@ public: } // end of namespace Common -#endif // COMMON_COROUTINES_H +#endif // COMMON_COROUTINES_H diff --git a/devtools/scumm-md5.txt b/devtools/scumm-md5.txt index f08b7d29d2..5223d6785d 100644 --- a/devtools/scumm-md5.txt +++ b/devtools/scumm-md5.txt @@ -776,6 +776,7 @@ puttzoo Putt-Putt Saves the Zoo f3d55aea441e260e9e9c7d2a187097e0 14337 en Windows - Demo - khalek 65fa23d6884e8ca23d5d2406d70de7e8 -1 fr Windows - Demo - gist974 2a446817ffcabfef8716e0c456ecaf81 -1 de Windows - Demo - Joachim Eberhard + 4e859d3ef1e146b41e7d93c35cd6cc62 -1 en iOS HE 100 Lite - clone2727 PuttTime Putt-Putt Travels Through Time fcb78ebecab2757264c590890c319cc5 -1 nl All HE 85 - - adutchguy, daniel9 diff --git a/engines/agos/charset-fontdata.cpp b/engines/agos/charset-fontdata.cpp index 87f51cfad2..262ae44f01 100644 --- a/engines/agos/charset-fontdata.cpp +++ b/engines/agos/charset-fontdata.cpp @@ -681,6 +681,51 @@ static const byte feeble_windowFont[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; +void AGOSEngine_Feeble::windowDrawChar(WindowBlock *window, uint x, uint y, byte chr) { + const byte *src; + byte color, *dst; + uint dstPitch, h, w, i; + + if (_noOracleScroll) + return; + + _videoLockOut |= 0x8000; + + dst = getBackGround(); + dstPitch = _backGroundBuf->pitch; + h = 13; + w = getFeebleFontSize(chr); + + if (_language == Common::PL_POL) { + if (!strcmp(getExtra(), "4CD")) + src = polish4CD_feeble_windowFont + (chr - 32) * 13; + else + src = polish2CD_feeble_windowFont + (chr - 32) * 13; + } else { + src = feeble_windowFont + (chr - 32) * 13; + } + dst += y * dstPitch + x + window->textColumnOffset; + + color = window->textColor; + + do { + int8 b = *src++; + i = 0; + do { + if (b < 0) { + if (dst[i] == 0) + dst[i] = color; + } + + b <<= 1; + } while (++i != w); + dst += dstPitch; + } while (--h); + + _videoLockOut &= ~0x8000; +} +#endif + static const byte english_simon1AGAFontData[] = { 0x00,0x00,0x00,0x20,0x00,0x00,0x20,0x50,0x20,0x10,0x40,0x88,0x30,0x40,0x00,0x88,0x20,0x00,0x00,0x50,0x20,0x00,0x00,0x50,0x00,0x00,0x00,0x20,0x00,0x00,0x20,0x50,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x00,0x05, 0x00,0x00,0x00,0x30,0x00,0x10,0x20,0x48,0x10,0x20,0x00,0x48,0x20,0x40,0x00,0x90,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05, @@ -1253,51 +1298,6 @@ void AGOSEngine::renderString(uint vgaSpriteId, uint color, uint width, uint hei } } -void AGOSEngine_Feeble::windowDrawChar(WindowBlock *window, uint x, uint y, byte chr) { - const byte *src; - byte color, *dst; - uint dstPitch, h, w, i; - - if (_noOracleScroll) - return; - - _videoLockOut |= 0x8000; - - dst = getBackGround(); - dstPitch = _backGroundBuf->pitch; - h = 13; - w = getFeebleFontSize(chr); - - if (_language == Common::PL_POL) { - if (!strcmp(getExtra(), "4CD")) - src = polish4CD_feeble_windowFont + (chr - 32) * 13; - else - src = polish2CD_feeble_windowFont + (chr - 32) * 13; - } else { - src = feeble_windowFont + (chr - 32) * 13; - } - dst += y * dstPitch + x + window->textColumnOffset; - - color = window->textColor; - - do { - int8 b = *src++; - i = 0; - do { - if (b < 0) { - if (dst[i] == 0) - dst[i] = color; - } - - b <<= 1; - } while (++i != w); - dst += dstPitch; - } while (--h); - - _videoLockOut &= ~0x8000; -} -#endif - static const byte czech_simonFont[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x70, 0x70, 0x20, 0x20, 0x00, 0x20, 0x00, diff --git a/engines/dreamweb/sound.cpp b/engines/dreamweb/sound.cpp index 76c734e932..570f76f2f9 100644 --- a/engines/dreamweb/sound.cpp +++ b/engines/dreamweb/sound.cpp @@ -35,14 +35,12 @@ DreamWebSound::DreamWebSound(DreamWebEngine *vm) : _vm(vm) { _vm->_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, ConfMan.getInt("music_volume")); _vm->_mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, ConfMan.getInt("speech_volume")); - _channel0 = 0; - _channel1 = 0; - _currentSample = 0xff; - _channel0Playing = 0; + _channel0Playing = 255; _channel0Repeat = 0; _channel0NewSound = false; _channel1Playing = 255; + _channel1NewSound = false; _volume = 0; _volumeTo = 0; @@ -82,13 +80,9 @@ void DreamWebSound::volumeAdjust() { void DreamWebSound::playChannel0(uint8 index, uint8 repeat) { debug(1, "playChannel0(index:%d, repeat:%d)", index, repeat); - if (index == _channel0Playing) { - warning("playChannel0(index: %d) already playing! Forcing restart...", index); - _channel0NewSound = true; - } - _channel0Playing = index; _channel0Repeat = repeat; + _channel0NewSound = true; } void DreamWebSound::playChannel1(uint8 index) { @@ -97,6 +91,7 @@ void DreamWebSound::playChannel1(uint8 index) { return; _channel1Playing = index; + _channel1NewSound = true; } void DreamWebSound::cancelCh0() { @@ -186,10 +181,6 @@ void DreamWebSound::stopSound(uint8 channel) { debug(1, "stopSound(%u)", channel); assert(channel == 0 || channel == 1); _vm->_mixer->stopHandle(_channelHandle[channel]); - if (channel == 0) - _channel0 = 0; - else - _channel1 = 0; } bool DreamWebSound::loadSpeech(const Common::String &filename) { @@ -229,35 +220,24 @@ void DreamWebSound::soundHandler() { volume = (8 - volume) * Audio::Mixer::kMaxChannelVolume / 8; _vm->_mixer->setChannelVolume(_channelHandle[0], volume); - uint8 ch0 = _channel0Playing; - if (ch0 == 255) - ch0 = 0; - uint8 ch1 = _channel1Playing; - if (ch1 == 255) - ch1 = 0; - uint8 ch0loop = _channel0Repeat; - - if (_channel0 != ch0 || _channel0NewSound) { - _channel0 = ch0; + if (_channel0NewSound) { _channel0NewSound = false; - if (ch0) { - playSound(0, ch0, ch0loop); + if (_channel0Playing != 255) { + playSound(0, _channel0Playing, _channel0Repeat); } } - if (_channel1 != ch1) { - _channel1 = ch1; - if (ch1) { - playSound(1, ch1, 1); + if (_channel1NewSound) { + _channel1NewSound = false; + if (_channel1Playing != 255) { + playSound(1, _channel1Playing, 1); } } if (!_vm->_mixer->isSoundHandleActive(_channelHandle[0])) { _channel0Playing = 255; - _channel0 = 0; } if (!_vm->_mixer->isSoundHandleActive(_channelHandle[1])) { _channel1Playing = 255; - _channel1 = 0; } } diff --git a/engines/dreamweb/sound.h b/engines/dreamweb/sound.h index a38dbf3c1a..1ab06dc694 100644 --- a/engines/dreamweb/sound.h +++ b/engines/dreamweb/sound.h @@ -68,13 +68,12 @@ private: Audio::SoundHandle _channelHandle[2]; - uint8 _channel0, _channel1; - uint8 _currentSample; uint8 _channel0Playing; uint8 _channel0Repeat; bool _channel0NewSound; uint8 _channel1Playing; + bool _channel1NewSound; uint8 _volume; uint8 _volumeTo; diff --git a/engines/gob/aniobject.cpp b/engines/gob/aniobject.cpp index 154f8e04ed..8d739fb3a4 100644 --- a/engines/gob/aniobject.cpp +++ b/engines/gob/aniobject.cpp @@ -76,6 +76,10 @@ void ANIObject::rewind() { _frame = 0; } +void ANIObject::setFrame(uint16 frame) { + _frame = frame % _ani->getAnimationInfo(_animation).frameCount; +} + void ANIObject::setPosition() { // CMP "animations" have no default position if (_cmp) @@ -167,19 +171,21 @@ bool ANIObject::isIn(const ANIObject &obj) const { obj.isIn(frameX + frameWidth - 1, frameY + frameHeight - 1); } -void ANIObject::draw(Surface &dest, int16 &left, int16 &top, +bool ANIObject::draw(Surface &dest, int16 &left, int16 &top, int16 &right, int16 &bottom) { if (!_visible) - return; + return false; if (_cmp) - drawCMP(dest, left, top, right, bottom); + return drawCMP(dest, left, top, right, bottom); else if (_ani) - drawANI(dest, left, top, right, bottom); + return drawANI(dest, left, top, right, bottom); + + return false; } -void ANIObject::drawCMP(Surface &dest, int16 &left, int16 &top, +bool ANIObject::drawCMP(Surface &dest, int16 &left, int16 &top, int16 &right, int16 &bottom) { if (!_background) { @@ -209,9 +215,11 @@ void ANIObject::drawCMP(Surface &dest, int16 &left, int16 &top, top = _backgroundTop; right = _backgroundRight; bottom = _backgroundBottom; + + return true; } -void ANIObject::drawANI(Surface &dest, int16 &left, int16 &top, +bool ANIObject::drawANI(Surface &dest, int16 &left, int16 &top, int16 &right, int16 &bottom) { if (!_background) { @@ -224,7 +232,7 @@ void ANIObject::drawANI(Surface &dest, int16 &left, int16 &top, const ANIFile::Animation &animation = _ani->getAnimationInfo(_animation); if (_frame >= animation.frameCount) - return; + return false; const ANIFile::FrameArea &area = animation.frameAreas[_frame]; @@ -244,13 +252,15 @@ void ANIObject::drawANI(Surface &dest, int16 &left, int16 &top, top = _backgroundTop; right = _backgroundRight; bottom = _backgroundBottom; + + return true; } -void ANIObject::clear(Surface &dest, int16 &left, int16 &top, +bool ANIObject::clear(Surface &dest, int16 &left, int16 &top, int16 &right, int16 &bottom) { if (!_drawn) - return; + return false; const int16 bgRight = _backgroundRight - _backgroundLeft; const int16 bgBottom = _backgroundBottom - _backgroundTop; @@ -263,6 +273,8 @@ void ANIObject::clear(Surface &dest, int16 &left, int16 &top, top = _backgroundTop; right = _backgroundRight; bottom = _backgroundBottom; + + return true; } void ANIObject::advance() { diff --git a/engines/gob/aniobject.h b/engines/gob/aniobject.h index c101d747b7..00f42b43ce 100644 --- a/engines/gob/aniobject.h +++ b/engines/gob/aniobject.h @@ -61,9 +61,9 @@ public: void setMode(Mode mode); /** Set the current position to the animation's default. */ - void setPosition(); + virtual void setPosition(); /** Set the current position. */ - void setPosition(int16 x, int16 y); + virtual void setPosition(int16 x, int16 y); /** Return the current position. */ void getPosition(int16 &x, int16 &y) const; @@ -84,6 +84,9 @@ public: /** Rewind the current animation to the first frame. */ void rewind(); + /** Set the animation to a specific frame. */ + void setFrame(uint16 frame); + /** Return the current animation number. */ uint16 getAnimation() const; /** Return the current frame number. */ @@ -93,9 +96,9 @@ public: bool lastFrame() const; /** Draw the current frame onto the surface and return the affected rectangle. */ - void draw(Surface &dest, int16 &left, int16 &top, int16 &right, int16 &bottom); + virtual bool draw(Surface &dest, int16 &left, int16 &top, int16 &right, int16 &bottom); /** Draw the current frame from the surface and return the affected rectangle. */ - void clear(Surface &dest, int16 &left , int16 &top, int16 &right, int16 &bottom); + virtual bool clear(Surface &dest, int16 &left, int16 &top, int16 &right, int16 &bottom); /** Advance the animation to the next frame. */ virtual void advance(); @@ -123,8 +126,8 @@ private: int16 _backgroundRight; ///< The right position of the saved background. int16 _backgroundBottom; ///< The bottom position of the saved background. - void drawCMP(Surface &dest, int16 &left, int16 &top, int16 &right, int16 &bottom); - void drawANI(Surface &dest, int16 &left, int16 &top, int16 &right, int16 &bottom); + bool drawCMP(Surface &dest, int16 &left, int16 &top, int16 &right, int16 &bottom); + bool drawANI(Surface &dest, int16 &left, int16 &top, int16 &right, int16 &bottom); }; } // End of namespace Gob diff --git a/engines/gob/cheater.h b/engines/gob/cheater.h index 334a5e88eb..bf6c1372fb 100644 --- a/engines/gob/cheater.h +++ b/engines/gob/cheater.h @@ -31,6 +31,7 @@ namespace Gob { namespace Geisha { class Diving; + class Penetration; } class GobEngine; @@ -48,13 +49,14 @@ protected: class Cheater_Geisha : public Cheater { public: - Cheater_Geisha(GobEngine *vm, Geisha::Diving *diving); + Cheater_Geisha(GobEngine *vm, Geisha::Diving *diving, Geisha::Penetration *penetration); ~Cheater_Geisha(); bool cheat(GUI::Debugger &console); private: - Geisha::Diving *_diving; + Geisha::Diving *_diving; + Geisha::Penetration *_penetration; }; } // End of namespace Gob diff --git a/engines/gob/cheater_geisha.cpp b/engines/gob/cheater_geisha.cpp index 3d8c56707d..567333c12f 100644 --- a/engines/gob/cheater_geisha.cpp +++ b/engines/gob/cheater_geisha.cpp @@ -27,11 +27,12 @@ #include "gob/inter.h" #include "gob/minigames/geisha/diving.h" +#include "gob/minigames/geisha/penetration.h" namespace Gob { -Cheater_Geisha::Cheater_Geisha(GobEngine *vm, Geisha::Diving *diving) : - Cheater(vm), _diving(diving) { +Cheater_Geisha::Cheater_Geisha(GobEngine *vm, Geisha::Diving *diving, Geisha::Penetration *penetration) : + Cheater(vm), _diving(diving), _penetration(penetration) { } @@ -45,6 +46,12 @@ bool Cheater_Geisha::cheat(GUI::Debugger &console) { return false; } + // A cheat to get around the Penetration minigame + if (_penetration->isPlaying()) { + _penetration->cheatWin(); + return false; + } + // A cheat to get around the mastermind puzzle if (_vm->isCurrentTot("hard.tot") && _vm->_inter->_variables) { uint32 digit1 = READ_VARO_UINT32(0x768); diff --git a/engines/gob/inter_geisha.cpp b/engines/gob/inter_geisha.cpp index 99f834d4d7..75204a3f55 100644 --- a/engines/gob/inter_geisha.cpp +++ b/engines/gob/inter_geisha.cpp @@ -55,7 +55,7 @@ Inter_Geisha::Inter_Geisha(GobEngine *vm) : Inter_v1(vm), _diving = new Geisha::Diving(vm); _penetration = new Geisha::Penetration(vm); - _cheater = new Cheater_Geisha(vm, _diving); + _cheater = new Cheater_Geisha(vm, _diving, _penetration); _vm->_console->registerCheater(_cheater); } diff --git a/engines/gob/inter_v1.cpp b/engines/gob/inter_v1.cpp index 9aa190a456..4aa54f720b 100644 --- a/engines/gob/inter_v1.cpp +++ b/engines/gob/inter_v1.cpp @@ -1744,10 +1744,15 @@ void Inter_v1::o1_writeData(OpFuncParams ¶ms) { void Inter_v1::o1_manageDataFile(OpFuncParams ¶ms) { Common::String file = _vm->_game->_script->evalString(); - if (!file.empty()) + if (!file.empty()) { _vm->_dataIO->openArchive(file, true); - else + } else { _vm->_dataIO->closeArchive(true); + + // NOTE: Lost in Time might close a data file without explicitely closing a video in it. + // So we make sure that all open videos are still available. + _vm->_vidPlayer->reopenAll(); + } } void Inter_v1::o1_setState(OpGobParams ¶ms) { diff --git a/engines/gob/inter_v2.cpp b/engines/gob/inter_v2.cpp index 1e5b7bb24c..54f6a1acc1 100644 --- a/engines/gob/inter_v2.cpp +++ b/engines/gob/inter_v2.cpp @@ -1002,6 +1002,10 @@ void Inter_v2::o2_openItk() { void Inter_v2::o2_closeItk() { _vm->_dataIO->closeArchive(false); + + // NOTE: Lost in Time might close a data file without explicitely closing a video in it. + // So we make sure that all open videos are still available. + _vm->_vidPlayer->reopenAll(); } void Inter_v2::o2_setImdFrontSurf() { diff --git a/engines/gob/minigames/geisha/diving.cpp b/engines/gob/minigames/geisha/diving.cpp index 6f4c6e168a..56c7b5213c 100644 --- a/engines/gob/minigames/geisha/diving.cpp +++ b/engines/gob/minigames/geisha/diving.cpp @@ -706,16 +706,16 @@ void Diving::updateAnims() { for (Common::List<ANIObject *>::iterator a = _anims.reverse_begin(); a != _anims.end(); --a) { - (*a)->clear(*_vm->_draw->_backSurface, left, top, right, bottom); - _vm->_draw->dirtiedRect(_vm->_draw->_backSurface, left, top, right, bottom); + if ((*a)->clear(*_vm->_draw->_backSurface, left, top, right, bottom)) + _vm->_draw->dirtiedRect(_vm->_draw->_backSurface, left, top, right, bottom); } // Draw the current animation frames for (Common::List<ANIObject *>::iterator a = _anims.begin(); a != _anims.end(); ++a) { - (*a)->draw(*_vm->_draw->_backSurface, left, top, right, bottom); - _vm->_draw->dirtiedRect(_vm->_draw->_backSurface, left, top, right, bottom); + if ((*a)->draw(*_vm->_draw->_backSurface, left, top, right, bottom)) + _vm->_draw->dirtiedRect(_vm->_draw->_backSurface, left, top, right, bottom); (*a)->advance(); } diff --git a/engines/gob/minigames/geisha/evilfish.cpp b/engines/gob/minigames/geisha/evilfish.cpp index c7ef9d5622..05ae9d0ad4 100644 --- a/engines/gob/minigames/geisha/evilfish.cpp +++ b/engines/gob/minigames/geisha/evilfish.cpp @@ -171,7 +171,7 @@ void EvilFish::mutate(uint16 animSwimLeft, uint16 animSwimRight, } } -bool EvilFish::isDead() { +bool EvilFish::isDead() const { return !isVisible() || (_state == kStateNone) || (_state == kStateDie); } diff --git a/engines/gob/minigames/geisha/evilfish.h b/engines/gob/minigames/geisha/evilfish.h index 81efb676e2..4c82629461 100644 --- a/engines/gob/minigames/geisha/evilfish.h +++ b/engines/gob/minigames/geisha/evilfish.h @@ -58,7 +58,7 @@ public: uint16 animTurnLeft, uint16 animTurnRight, uint16 animDie); /** Is the fish dead? */ - bool isDead(); + bool isDead() const; private: enum State { diff --git a/engines/gob/minigames/geisha/meter.cpp b/engines/gob/minigames/geisha/meter.cpp index 9dcc717e48..719ecf3d18 100644 --- a/engines/gob/minigames/geisha/meter.cpp +++ b/engines/gob/minigames/geisha/meter.cpp @@ -63,22 +63,36 @@ void Meter::setMaxValue() { setValue(_maxValue); } -void Meter::increase(int32 n) { +int32 Meter::increase(int32 n) { + if (n < 0) + return decrease(-n); + + int32 overflow = MAX(0, (_value + n) - _maxValue); + int32 value = CLIP<int32>(_value + n, 0, _maxValue); if (_value == value) - return; + return overflow; _value = value; _needUpdate = true; + + return overflow; } -void Meter::decrease(int32 n) { +int32 Meter::decrease(int32 n) { + if (n < 0) + return increase(-n); + + int32 underflow = -MIN(0, _value - n); + int32 value = CLIP<int32>(_value - n, 0, _maxValue); if (_value == value) - return; + return underflow; _value = value; _needUpdate = true; + + return underflow; } void Meter::draw(Surface &dest, int16 &left, int16 &top, int16 &right, int16 &bottom) { diff --git a/engines/gob/minigames/geisha/meter.h b/engines/gob/minigames/geisha/meter.h index d3e82cb32e..30dc826de0 100644 --- a/engines/gob/minigames/geisha/meter.h +++ b/engines/gob/minigames/geisha/meter.h @@ -55,10 +55,10 @@ public: /** Set the current value the meter is measuring to the max value. */ void setMaxValue(); - /** Increase the current value the meter is measuring. */ - void increase(int32 n = 1); - /** Decrease the current value the meter is measuring. */ - void decrease(int32 n = 1); + /** Increase the current value the meter is measuring, returning the overflow. */ + int32 increase(int32 n = 1); + /** Decrease the current value the meter is measuring, returning the underflow. */ + int32 decrease(int32 n = 1); /** Draw the meter onto the surface and return the affected rectangle. */ void draw(Surface &dest, int16 &left, int16 &top, int16 &right, int16 &bottom); diff --git a/engines/gob/minigames/geisha/mouth.cpp b/engines/gob/minigames/geisha/mouth.cpp new file mode 100644 index 0000000000..7ba9f86f8c --- /dev/null +++ b/engines/gob/minigames/geisha/mouth.cpp @@ -0,0 +1,169 @@ +/* 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/util.h" + +#include "gob/minigames/geisha/mouth.h" + +namespace Gob { + +namespace Geisha { + +Mouth::Mouth(const ANIFile &ani, const CMPFile &cmp, + uint16 mouthAnim, uint16 mouthSprite, uint16 floorSprite) : ANIObject(ani) { + + _sprite = new ANIObject(cmp); + _sprite->setAnimation(mouthSprite); + _sprite->setVisible(true); + + for (int i = 0; i < kFloorCount; i++) { + _floor[i] = new ANIObject(cmp); + _floor[i]->setAnimation(floorSprite); + _floor[i]->setVisible(true); + } + + _state = kStateDeactivated; + + setAnimation(mouthAnim); + setMode(kModeOnce); + setPause(true); + setVisible(true); +} + +Mouth::~Mouth() { + for (int i = 0; i < kFloorCount; i++) + delete _floor[i]; + + delete _sprite; +} + +void Mouth::advance() { + if (_state != kStateActivated) + return; + + // Animation finished, set state to dead + if (isPaused()) { + _state = kStateDead; + return; + } + + ANIObject::advance(); +} + +void Mouth::activate() { + if (_state != kStateDeactivated) + return; + + _state = kStateActivated; + + setPause(false); +} + +bool Mouth::isDeactivated() const { + return _state == kStateDeactivated; +} + +void Mouth::setPosition(int16 x, int16 y) { + ANIObject::setPosition(x, y); + + int16 floorWidth, floorHeight; + _floor[0]->getFrameSize(floorWidth, floorHeight); + + _sprite->setPosition(x, y); + + for (int i = 0; i < kFloorCount; i++) + _floor[i]->setPosition(x + (i * floorWidth), y); +} + +bool Mouth::draw(Surface &dest, int16 &left, int16 &top, int16 &right, int16 &bottom) { + // If the mouth is deactivated, draw the default mouth sprite + if (_state == kStateDeactivated) + return _sprite->draw(dest, left, top, right, bottom); + + // If the mouth is activated, draw the current mouth animation sprite + if (_state == kStateActivated) + return ANIObject::draw(dest, left, top, right, bottom); + + // If the mouth is dead, draw the floor tiles + if (_state == kStateDead) { + int16 fLeft, fRight, fTop, fBottom; + bool drawn = false; + + left = 0x7FFF; + top = 0x7FFF; + right = 0; + bottom = 0; + + for (int i = 0; i < kFloorCount; i++) { + if (_floor[i]->draw(dest, fLeft, fTop, fRight, fBottom)) { + drawn = true; + left = MIN(left , fLeft); + top = MIN(top , fTop); + right = MAX(right , fRight); + bottom = MAX(bottom, fBottom); + } + } + + return drawn; + } + + return false; +} + +bool Mouth::clear(Surface &dest, int16 &left , int16 &top, int16 &right, int16 &bottom) { + // If the mouth is deactivated, clear the default mouth sprite + if (_state == kStateDeactivated) + return _sprite->clear(dest, left, top, right, bottom); + + // If the mouth is activated, clear the current mouth animation sprite + if (_state == kStateActivated) + return ANIObject::clear(dest, left, top, right, bottom); + + // If the mouth is clear, draw the floor tiles + if (_state == kStateDead) { + int16 fLeft, fRight, fTop, fBottom; + bool cleared = false; + + left = 0x7FFF; + top = 0x7FFF; + right = 0; + bottom = 0; + + for (int i = 0; i < kFloorCount; i++) { + if (_floor[i]->clear(dest, fLeft, fTop, fRight, fBottom)) { + cleared = true; + left = MIN(left , fLeft); + top = MIN(top , fTop); + right = MAX(right , fRight); + bottom = MAX(bottom, fBottom); + } + } + + return cleared; + } + + return false; +} + +} // End of namespace Geisha + +} // End of namespace Gob diff --git a/engines/gob/minigames/geisha/mouth.h b/engines/gob/minigames/geisha/mouth.h new file mode 100644 index 0000000000..2e0cfcd5d0 --- /dev/null +++ b/engines/gob/minigames/geisha/mouth.h @@ -0,0 +1,75 @@ +/* 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 GOB_MINIGAMES_GEISHA_MOUTH_H +#define GOB_MINIGAMES_GEISHA_MOUTH_H + +#include "gob/aniobject.h" + +namespace Gob { + +namespace Geisha { + +/** A kissing/biting mouth in Geisha's "Penetration" minigame. */ +class Mouth : public ANIObject { +public: + Mouth(const ANIFile &ani, const CMPFile &cmp, + uint16 mouthAnim, uint16 mouthSprite, uint16 floorSprite); + ~Mouth(); + + /** Advance the animation to the next frame. */ + void advance(); + + /** Active the mouth's animation. */ + void activate(); + + /** Is the mouth deactivated? */ + bool isDeactivated() const; + + /** Set the current position. */ + void setPosition(int16 x, int16 y); + + /** Draw the current frame onto the surface and return the affected rectangle. */ + bool draw(Surface &dest, int16 &left, int16 &top, int16 &right, int16 &bottom); + /** Draw the current frame from the surface and return the affected rectangle. */ + bool clear(Surface &dest, int16 &left, int16 &top, int16 &right, int16 &bottom); + +private: + static const int kFloorCount = 2; + + enum State { + kStateDeactivated, + kStateActivated, + kStateDead + }; + + ANIObject *_sprite; + ANIObject *_floor[kFloorCount]; + + State _state; +}; + +} // End of namespace Geisha + +} // End of namespace Gob + +#endif // GOB_MINIGAMES_GEISHA_MOUTH_H diff --git a/engines/gob/minigames/geisha/penetration.cpp b/engines/gob/minigames/geisha/penetration.cpp index 8b5de27ad2..3be9f1f651 100644 --- a/engines/gob/minigames/geisha/penetration.cpp +++ b/engines/gob/minigames/geisha/penetration.cpp @@ -20,8 +20,11 @@ * */ +#include "common/events.h" + #include "gob/global.h" #include "gob/util.h" +#include "gob/palanim.h" #include "gob/draw.h" #include "gob/video.h" #include "gob/decfile.h" @@ -29,44 +32,450 @@ #include "gob/anifile.h" #include "gob/aniobject.h" +#include "gob/sound/sound.h" + #include "gob/minigames/geisha/penetration.h" #include "gob/minigames/geisha/meter.h" +#include "gob/minigames/geisha/mouth.h" namespace Gob { namespace Geisha { -static const byte kPalette[48] = { - 0x16, 0x16, 0x16, - 0x12, 0x14, 0x16, - 0x34, 0x00, 0x25, - 0x1D, 0x1F, 0x22, - 0x24, 0x27, 0x2A, - 0x2C, 0x0D, 0x22, - 0x2B, 0x2E, 0x32, - 0x12, 0x09, 0x20, - 0x3D, 0x3F, 0x00, - 0x3F, 0x3F, 0x3F, - 0x00, 0x00, 0x00, - 0x15, 0x15, 0x3F, - 0x25, 0x22, 0x2F, - 0x1A, 0x14, 0x28, - 0x3F, 0x00, 0x00, - 0x15, 0x3F, 0x15 +static const int kColorShield = 11; +static const int kColorHealth = 15; +static const int kColorBlack = 10; +static const int kColorFloor = 13; +static const int kColorFloorText = 14; +static const int kColorExitText = 15; + +enum Sprite { + kSpriteFloorShield = 25, + kSpriteExit = 29, + kSpriteFloor = 30, + kSpriteWall = 31, + kSpriteMouthBite = 32, + kSpriteMouthKiss = 33, + kSpriteBulletN = 65, + kSpriteBulletS = 66, + kSpriteBulletW = 67, + kSpriteBulletE = 68, + kSpriteBulletSW = 85, + kSpriteBulletSE = 86, + kSpriteBulletNW = 87, + kSpriteBulletNE = 88 +}; + +enum Animation { + kAnimationEnemyRound = 0, + kAnimationEnemyRoundExplode = 1, + kAnimationEnemySquare = 2, + kAnimationEnemySquareExplode = 3, + kAnimationMouthKiss = 33, + kAnimationMouthBite = 34 +}; + +static const int kMapTileWidth = 24; +static const int kMapTileHeight = 24; + +static const int kPlayAreaX = 120; +static const int kPlayAreaY = 7; +static const int kPlayAreaWidth = 192; +static const int kPlayAreaHeight = 113; + +static const int kPlayAreaBorderWidth = kPlayAreaWidth / 2; +static const int kPlayAreaBorderHeight = kPlayAreaHeight / 2; + +static const int kTextAreaLeft = 9; +static const int kTextAreaTop = 7; +static const int kTextAreaRight = 104; +static const int kTextAreaBottom = 107; + +static const int kTextAreaBigBottom = 142; + +const byte Penetration::kPalettes[kFloorCount][3 * kPaletteSize] = { + { + 0x16, 0x16, 0x16, + 0x12, 0x14, 0x16, + 0x34, 0x00, 0x25, + 0x1D, 0x1F, 0x22, + 0x24, 0x27, 0x2A, + 0x2C, 0x0D, 0x22, + 0x2B, 0x2E, 0x32, + 0x12, 0x09, 0x20, + 0x3D, 0x3F, 0x00, + 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, + 0x15, 0x15, 0x3F, + 0x25, 0x22, 0x2F, + 0x1A, 0x14, 0x28, + 0x3F, 0x00, 0x00, + 0x15, 0x3F, 0x15 + }, + { + 0x16, 0x16, 0x16, + 0x12, 0x14, 0x16, + 0x37, 0x00, 0x24, + 0x1D, 0x1F, 0x22, + 0x24, 0x27, 0x2A, + 0x30, 0x0E, 0x16, + 0x2B, 0x2E, 0x32, + 0x22, 0x0E, 0x26, + 0x3D, 0x3F, 0x00, + 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, + 0x15, 0x15, 0x3F, + 0x36, 0x28, 0x36, + 0x30, 0x1E, 0x2A, + 0x3F, 0x00, 0x00, + 0x15, 0x3F, 0x15 + }, + { + 0x16, 0x16, 0x16, + 0x12, 0x14, 0x16, + 0x3F, 0x14, 0x22, + 0x1D, 0x1F, 0x22, + 0x24, 0x27, 0x2A, + 0x30, 0x10, 0x10, + 0x2B, 0x2E, 0x32, + 0x2A, 0x12, 0x12, + 0x3D, 0x3F, 0x00, + 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, + 0x15, 0x15, 0x3F, + 0x3F, 0x23, 0x31, + 0x39, 0x20, 0x2A, + 0x3F, 0x00, 0x00, + 0x15, 0x3F, 0x15 + } +}; + +const byte Penetration::kMaps[kModeCount][kFloorCount][kMapWidth * kMapHeight] = { + { + { // Real mode, floor 0 + 0, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 0, + 50, 50, 0, 0, 52, 53, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 50, + 50, 0, 0, 0, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 0, 0, 50, + 50, 0, 0, 50, 0, 0, 52, 53, 0, 0, 0, 0, 0, 0, 50, 0, 50, + 50, 0, 50, 0, 0, 50, 50, 50, 50, 0, 54, 55, 0, 0, 50, 0, 50, + 50, 0, 50, 49, 0, 50, 0, 52, 53, 0, 50, 50, 50, 0, 0, 0, 50, + 50, 57, 0, 50, 0, 0, 0, 50, 50, 50, 0, 0, 56, 50, 54, 55, 50, + 50, 50, 0, 0, 50, 50, 50, 0, 0, 0, 0, 50, 0, 0, 50, 0, 50, + 50, 51, 50, 0, 54, 55, 0, 0, 50, 50, 50, 50, 52, 53, 50, 0, 50, + 50, 0, 50, 0, 0, 0, 0, 0, 54, 55, 0, 0, 0, 50, 0, 0, 50, + 50, 0, 0, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 0, 0, 0, 50, + 50, 50, 0, 52, 53, 0, 0, 0, 0, 0, 0, 52, 53, 0, 0, 50, 50, + 0, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 0 + }, + { // Real mode, floor 1 + 0, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 0, + 50, 0, 52, 53, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50, + 50, 0, 0, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 0, 0, 50, + 50, 0, 50, 51, 52, 53, 0, 0, 52, 53, 0, 0, 0, 0, 50, 0, 50, + 50, 0, 50, 0, 50, 50, 0, 50, 0, 50, 0, 50, 50, 0, 50, 0, 50, + 50, 0, 50, 0, 52, 53, 0, 0, 0, 0, 0, 52, 53, 0, 52, 53, 50, + 50, 57, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 0, 0, 50, + 50, 0, 50, 52, 53, 0, 0, 52, 53, 0, 0, 0, 0, 0, 54, 55, 50, + 50, 0, 50, 0, 50, 0, 50, 50, 0, 50, 50, 0, 50, 0, 50, 50, 50, + 50, 0, 50, 49, 0, 0, 52, 53, 0, 52, 53, 0, 0, 0, 50, 56, 50, + 50, 0, 0, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 0, 0, 50, + 50, 0, 0, 0, 0, 0, 0, 0, 54, 55, 0, 0, 0, 0, 0, 0, 50, + 0, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 0 + }, + { // Real mode, floor 2 + 0, 50, 50, 50, 50, 50, 50, 50, 0, 50, 50, 50, 50, 50, 50, 50, 0, + 50, 52, 53, 0, 0, 0, 0, 50, 50, 50, 0, 0, 0, 0, 52, 53, 50, + 50, 0, 50, 50, 50, 0, 0, 0, 50, 0, 0, 0, 50, 50, 50, 0, 50, + 50, 0, 50, 52, 53, 50, 50, 52, 53, 0, 50, 50, 54, 55, 50, 0, 50, + 50, 0, 50, 0, 0, 0, 0, 50, 0, 50, 0, 0, 0, 0, 50, 0, 50, + 50, 0, 0, 0, 50, 0, 0, 0, 50, 0, 0, 0, 50, 0, 52, 53, 50, + 0, 50, 0, 50, 50, 50, 0, 57, 50, 51, 0, 50, 50, 50, 0, 50, 0, + 50, 0, 0, 0, 50, 0, 0, 0, 50, 0, 52, 53, 50, 0, 0, 0, 50, + 50, 0, 50, 0, 0, 0, 0, 50, 56, 50, 0, 0, 0, 0, 50, 0, 50, + 50, 0, 50, 54, 55, 50, 50, 0, 0, 0, 50, 50, 54, 55, 50, 0, 50, + 50, 0, 50, 50, 50, 0, 0, 0, 50, 0, 0, 0, 50, 50, 50, 0, 50, + 50, 52, 53, 0, 0, 0, 0, 50, 50, 50, 0, 0, 0, 0, 52, 53, 50, + 0, 50, 50, 50, 50, 50, 50, 50, 0, 50, 50, 50, 50, 50, 50, 50, 0 + } + }, + { + { // Test mode, floor 0 + 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, + 50, 56, 0, 50, 0, 0, 52, 53, 0, 0, 0, 0, 52, 53, 0, 51, 50, + 50, 0, 0, 50, 0, 0, 0, 50, 0, 54, 55, 50, 0, 50, 50, 50, 50, + 50, 52, 53, 50, 50, 0, 0, 50, 50, 50, 50, 50, 0, 50, 0, 0, 50, + 50, 0, 0, 0, 0, 56, 0, 0, 0, 0, 0, 50, 49, 50, 0, 0, 50, + 50, 0, 54, 55, 0, 50, 50, 54, 55, 0, 50, 50, 50, 0, 0, 0, 50, + 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 53, 0, 0, 54, 55, 50, + 50, 0, 50, 0, 50, 0, 0, 50, 0, 0, 0, 50, 0, 0, 0, 0, 50, + 50, 0, 50, 0, 50, 54, 55, 50, 0, 50, 50, 50, 0, 50, 0, 0, 50, + 50, 50, 50, 50, 50, 0, 0, 50, 0, 0, 0, 0, 0, 50, 54, 55, 50, + 50, 0, 0, 0, 0, 0, 0, 0, 50, 50, 50, 50, 50, 0, 0, 0, 50, + 50, 57, 0, 52, 53, 0, 0, 0, 0, 54, 55, 0, 0, 0, 0, 56, 50, + 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50 + }, + { // Test mode, floor 1 + 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, + 50, 52, 53, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50, + 50, 0, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 54, 55, 0, 50, + 50, 0, 50, 52, 53, 0, 0, 50, 0, 0, 54, 55, 50, 0, 50, 0, 50, + 50, 0, 50, 0, 50, 0, 0, 52, 53, 0, 50, 0, 50, 0, 50, 0, 50, + 50, 0, 50, 0, 50, 50, 50, 50, 50, 49, 50, 0, 50, 0, 50, 0, 50, + 50, 0, 50, 0, 50, 0, 50, 0, 0, 50, 50, 0, 50, 0, 50, 0, 50, + 50, 0, 50, 0, 50, 0, 50, 51, 0, 0, 52, 53, 50, 0, 50, 0, 50, + 50, 57, 50, 0, 50, 0, 50, 50, 50, 50, 50, 50, 50, 0, 50, 0, 50, + 50, 50, 50, 0, 50, 56, 0, 0, 0, 54, 55, 0, 0, 0, 50, 0, 50, + 50, 56, 0, 0, 0, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 0, 50, + 50, 50, 50, 50, 0, 0, 0, 0, 52, 53, 0, 0, 0, 0, 0, 0, 50, + 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50 + }, + { // Test mode, floor 2 + 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, + 50, 57, 50, 54, 55, 0, 50, 54, 55, 0, 50, 0, 52, 53, 50, 51, 50, + 50, 0, 50, 0, 50, 0, 50, 0, 0, 0, 50, 0, 50, 0, 50, 0, 50, + 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, + 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, + 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, + 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, + 50, 0, 50, 52, 53, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, + 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, + 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, + 50, 0, 0, 0, 50, 0, 50, 0, 50, 0, 0, 0, 50, 0, 50, 0, 50, + 50, 0, 0, 0, 50, 52, 53, 0, 50, 52, 53, 56, 50, 0, 54, 55, 50, + 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50 + } + } +}; + +static const int kLanguageCount = 5; +static const int kFallbackLanguage = 2; // English + +enum String { + kString3rdBasement = 0, + kString2ndBasement, + kString1stBasement, + kStringNoExit, + kStringYouHave, + kString2Exits, + kString1Exit, + kStringToReach, + kStringUpperLevel1, + kStringUpperLevel2, + kStringLevel0, + kStringPenetration, + kStringSuccessful, + kStringDanger, + kStringGynoides, + kStringActivated, + kStringCount +}; + +static const char *kStrings[kLanguageCount][kStringCount] = { + { // French + "3EME SOUS-SOL", + "2EME SOUS-SOL", + "1ER SOUS-SOL", + "SORTIE REFUSEE", + "Vous disposez", + "de deux sorties", + "d\'une sortie", + "pour l\'acc\212s au", + "niveau", + "sup\202rieur", + "- NIVEAU 0 -", + "PENETRATION", + "REUSSIE", + "DANGER", + "GYNOIDES", + "ACTIVEES" + }, + { // German + // NOTE: The original had very broken German there. We provide proper(ish) German instead. + // B0rken text in the comments after each line + "3. UNTERGESCHOSS", // "3. U.-GESCHOSS"" + "2. UNTERGESCHOSS", // "2. U.-GESCHOSS" + "1. UNTERGESCHOSS", // "1. U.-GESCHOSS" + "AUSGANG GESPERRT", + "Sie haben", + "zwei Ausg\204nge", // "zwei Ausgang" + "einen Ausgang", // "Fortsetztung" + "um das obere", // "" + "Stockwerk zu", // "" + "erreichen", // "" + "- STOCKWERK 0 -", // "0 - HOHE" + "PENETRATION", // "DURCHDRIGEN" + "ERFOLGREICH", // "ERFOLG" + "GEFAHR", + "GYNOIDE", + "AKTIVIERT", + }, + { // English + "3RD BASEMENT", + "2ND BASEMENT", + "1ST BASEMENT", + "NO EXIT", + "You have", + "2 exits", + "1 exit", + "to reach upper", + "level", + "", + "- 0 LEVEL -", + "PENETRATION", + "SUCCESSFUL", + "DANGER", + "GYNOIDES", + "ACTIVATED", + }, + { // Spanish + "3ER. SUBSUELO", + "2D. SUBSUELO", + "1ER. SUBSUELO", + "SALIDA RECHAZADA", + "Dispones", + "de dos salidas", + "de una salida", + "para acceso al", + "nivel", + "superior", + "- NIVEL 0 -", + "PENETRACION", + "CONSEGUIDA", + "PELIGRO", + "GYNOIDAS", + "ACTIVADAS", + }, + { // Italian + "SOTTOSUOLO 3", + "SOTTOSUOLO 2", + "SOTTOSUOLO 1", + "NON USCITA", + "avete", + "due uscite", + "un\' uscita", + "per accedere al", + "livello", + "superiore", + "- LIVELLO 0 -", + "PENETRAZIONE", + "RIUSCITA", + "PERICOLO", + "GYNOIDI", + "ATTIVATE", + } }; -Penetration::Penetration(GobEngine *vm) : _vm(vm), _background(0), _sprites(0), _objects(0), - _shieldMeter(0), _healthMeter(0) { + +Penetration::MapObject::MapObject(uint16 tX, uint16 tY, uint16 mX, uint16 mY, uint16 w, uint16 h) : + tileX(tX), tileY(tY), mapX(mX), mapY(mY), width(w), height(h) { + + isBlocking = true; +} + +Penetration::MapObject::MapObject(uint16 tX, uint16 tY, uint16 w, uint16 h) : + tileX(tX), tileY(tY), width(w), height(h) { + + isBlocking = true; + + setMapFromTilePosition(); +} + +void Penetration::MapObject::setTileFromMapPosition() { + tileX = (mapX + (width / 2)) / kMapTileWidth; + tileY = (mapY + (height / 2)) / kMapTileHeight; +} + +void Penetration::MapObject::setMapFromTilePosition() { + mapX = tileX * kMapTileWidth; + mapY = tileY * kMapTileHeight; +} + +bool Penetration::MapObject::isIn(uint16 mX, uint16 mY) const { + if ((mX < mapX) || (mY < mapY)) + return false; + if ((mX > (mapX + width - 1)) || (mY > (mapY + height - 1))) + return false; + + return true; +} + +bool Penetration::MapObject::isIn(uint16 mX, uint16 mY, uint16 w, uint16 h) const { + return isIn(mX , mY ) || + isIn(mX + w - 1, mY ) || + isIn(mX , mY + h - 1) || + isIn(mX + w - 1, mY + h - 1); +} + +bool Penetration::MapObject::isIn(const MapObject &obj) const { + return isIn(obj.mapX, obj.mapY, obj.width, obj.height); +} + + +Penetration::ManagedMouth::ManagedMouth(uint16 tX, uint16 tY, MouthType t) : + MapObject(tX, tY, 0, 0), mouth(0), type(t) { + +} + +Penetration::ManagedMouth::~ManagedMouth() { + delete mouth; +} + + +Penetration::ManagedSub::ManagedSub(uint16 tX, uint16 tY) : + MapObject(tX, tY, kMapTileWidth, kMapTileHeight), sub(0) { + +} + +Penetration::ManagedSub::~ManagedSub() { + delete sub; +} + + +Penetration::ManagedEnemy::ManagedEnemy() : MapObject(0, 0, 0, 0), enemy(0), dead(false) { +} + +Penetration::ManagedEnemy::~ManagedEnemy() { + delete enemy; +} + +void Penetration::ManagedEnemy::clear() { + delete enemy; + + enemy = 0; +} + + +Penetration::ManagedBullet::ManagedBullet() : MapObject(0, 0, 0, 0), bullet(0) { +} + +Penetration::ManagedBullet::~ManagedBullet() { + delete bullet; +} + +void Penetration::ManagedBullet::clear() { + delete bullet; + + bullet = 0; +} + + +Penetration::Penetration(GobEngine *vm) : _vm(vm), _background(0), _sprites(0), _objects(0), _sub(0), + _shieldMeter(0), _healthMeter(0), _floor(0), _isPlaying(false) { _background = new Surface(320, 200, 1); - _shieldMeter = new Meter(11, 119, 92, 3, 11, 10, 1020, Meter::kFillToRight); - _healthMeter = new Meter(11, 137, 92, 3, 15, 10, 1020, Meter::kFillToRight); + _shieldMeter = new Meter(11, 119, 92, 3, kColorShield, kColorBlack, 920, Meter::kFillToRight); + _healthMeter = new Meter(11, 137, 92, 3, kColorHealth, kColorBlack, 920, Meter::kFillToRight); + + _map = new Surface(kMapWidth * kMapTileWidth + kPlayAreaWidth , + kMapHeight * kMapTileHeight + kPlayAreaHeight, 1); } Penetration::~Penetration() { deinit(); + delete _map; + delete _shieldMeter; delete _healthMeter; @@ -78,36 +487,70 @@ bool Penetration::play(bool hasAccessPass, bool hasMaxEnergy, bool testMode) { _hasMaxEnergy = hasMaxEnergy; _testMode = testMode; + _isPlaying = true; + init(); initScreen(); + drawFloorText(); + _vm->_draw->blitInvalidated(); _vm->_video->retrace(); - while (!_vm->shouldQuit()) { + while (!_vm->shouldQuit() && !_quit && !isDead() && !hasWon()) { + enemiesCreate(); + bulletsMove(); updateAnims(); - // Draw and wait for the end of the frame + // Draw, fade in if necessary and wait for the end of the frame _vm->_draw->blitInvalidated(); + fadeIn(); _vm->_util->waitEndFrame(); - // Handle input - _vm->_util->processInput(); + // Handle the input + checkInput(); - int16 mouseX, mouseY; - MouseButtons mouseButtons; + // Handle the sub movement + handleSub(); - int16 key = checkInput(mouseX, mouseY, mouseButtons); - // Aborting the game - if (key == kKeyEscape) - break; + // Handle the enemies movement + enemiesMove(); + + checkExited(); + + if (_shotCoolDown > 0) + _shotCoolDown--; } deinit(); - return false; + drawEndText(); + + _isPlaying = false; + + return hasWon(); +} + +bool Penetration::isPlaying() const { + return _isPlaying; +} + +void Penetration::cheatWin() { + _floor = 3; } void Penetration::init() { + // Load sounds + _vm->_sound->sampleLoad(&_soundShield , SOUND_SND, "boucl.snd"); + _vm->_sound->sampleLoad(&_soundBite , SOUND_SND, "pervet.snd"); + _vm->_sound->sampleLoad(&_soundKiss , SOUND_SND, "baise.snd"); + _vm->_sound->sampleLoad(&_soundShoot , SOUND_SND, "tirgim.snd"); + _vm->_sound->sampleLoad(&_soundExit , SOUND_SND, "trouve.snd"); + _vm->_sound->sampleLoad(&_soundExplode, SOUND_SND, "virmor.snd"); + + _quit = false; + for (int i = 0; i < kKeyCount; i++) + _keys[i] = false; + _background->clear(); _vm->_video->drawPackedSprite("hyprmef2.cmp", *_background); @@ -115,14 +558,6 @@ void Penetration::init() { _sprites = new CMPFile(_vm, "tcifplai.cmp", 320, 200); _objects = new ANIFile(_vm, "tcite.ani", 320); - // Draw the shield meter - _sprites->draw(*_background, 0, 0, 95, 6, 9, 117, 0); // Meter frame - _sprites->draw(*_background, 271, 176, 282, 183, 9, 108, 0); // Shield - - // Draw the health meter - _sprites->draw(*_background, 0, 0, 95, 6, 9, 135, 0); // Meter frame - _sprites->draw(*_background, 283, 176, 292, 184, 9, 126, 0); // Heart - // The shield starts down _shieldMeter->setValue(0); @@ -131,10 +566,23 @@ void Penetration::init() { _healthMeter->setMaxValue(); else _healthMeter->setValue(_healthMeter->getMaxValue() / 3); + + _floor = 0; + + _shotCoolDown = 0; + + createMap(); } void Penetration::deinit() { - _anims.clear(); + _soundShield.free(); + _soundBite.free(); + _soundKiss.free(); + _soundShoot.free(); + _soundExit.free(); + _soundExplode.free(); + + clearMap(); delete _objects; delete _sprites; @@ -143,41 +591,875 @@ void Penetration::deinit() { _sprites = 0; } +void Penetration::clearMap() { + _mapAnims.clear(); + _anims.clear(); + + _blockingObjects.clear(); + + _walls.clear(); + _exits.clear(); + _shields.clear(); + _mouths.clear(); + + for (int i = 0; i < kEnemyCount; i++) + _enemies[i].clear(); + for (int i = 0; i < kMaxBulletCount; i++) + _bullets[i].clear(); + + delete _sub; + + _sub = 0; + + _map->fill(kColorBlack); +} + +void Penetration::createMap() { + if (_floor >= kFloorCount) + error("Geisha: Invalid floor %d in minigame penetration", _floor); + + clearMap(); + + const byte *mapTiles = kMaps[_testMode ? 1 : 0][_floor]; + + bool exitWorks; + + // Draw the map tiles + for (int y = 0; y < kMapHeight; y++) { + for (int x = 0; x < kMapWidth; x++) { + const byte mapTile = mapTiles[y * kMapWidth + x]; + + const int posX = kPlayAreaBorderWidth + x * kMapTileWidth; + const int posY = kPlayAreaBorderHeight + y * kMapTileHeight; + + switch (mapTile) { + case 0: // Floor + _sprites->draw(*_map, kSpriteFloor, posX, posY); + break; + + case 49: // Emergency exit (needs access pass) + + exitWorks = _hasAccessPass; + if (exitWorks) { + _sprites->draw(*_map, kSpriteExit, posX, posY); + _exits.push_back(MapObject(x, y, 0, 0)); + } else { + _sprites->draw(*_map, kSpriteWall, posX, posY); + _walls.push_back(MapObject(x, y, kMapTileWidth, kMapTileHeight)); + } + + break; + + case 50: // Wall + _sprites->draw(*_map, kSpriteWall, posX, posY); + _walls.push_back(MapObject(x, y, kMapTileWidth, kMapTileHeight)); + break; + + case 51: // Regular exit + + // A regular exit works always in test mode. + // But if we're in real mode, and on the last floor, it needs an access pass + exitWorks = _testMode || (_floor < 2) || _hasAccessPass; + + if (exitWorks) { + _sprites->draw(*_map, kSpriteExit, posX, posY); + _exits.push_back(MapObject(x, y, 0, 0)); + } else { + _sprites->draw(*_map, kSpriteWall, posX, posY); + _walls.push_back(MapObject(x, y, kMapTileWidth, kMapTileHeight)); + } + + break; + + case 52: // Left side of biting mouth + _mouths.push_back(ManagedMouth(x, y, kMouthTypeBite)); + + _mouths.back().mouth = + new Mouth(*_objects, *_sprites, kAnimationMouthBite, kSpriteMouthBite, kSpriteFloor); + + _mouths.back().mouth->setPosition(posX, posY); + break; + + case 53: // Right side of biting mouth + break; + + case 54: // Left side of kissing mouth + _mouths.push_back(ManagedMouth(x, y, kMouthTypeKiss)); + + _mouths.back().mouth = + new Mouth(*_objects, *_sprites, kAnimationMouthKiss, kSpriteMouthKiss, kSpriteFloor); + + _mouths.back().mouth->setPosition(posX, posY); + break; + + case 55: // Right side of kissing mouth + break; + + case 56: // Shield lying on the floor + _sprites->draw(*_map, kSpriteFloor , posX , posY ); // Floor + _sprites->draw(*_map, kSpriteFloorShield, posX + 4, posY + 8); // Shield + + _map->fillRect(posX + 4, posY + 8, posX + 7, posY + 18, kColorFloor); // Area left to shield + _map->fillRect(posX + 17, posY + 8, posX + 20, posY + 18, kColorFloor); // Area right to shield + + _shields.push_back(MapObject(x, y, 0, 0)); + break; + + case 57: // Start position + _sprites->draw(*_map, kSpriteFloor, posX, posY); + + delete _sub; + + _sub = new ManagedSub(x, y); + + _sub->sub = new Submarine(*_objects); + _sub->sub->setPosition(kPlayAreaX + kPlayAreaBorderWidth, kPlayAreaY + kPlayAreaBorderHeight); + break; + } + } + } + + if (!_sub) + error("Geisha: No starting position in floor %d (testmode: %d)", _floor, _testMode); + + // Walls + for (Common::List<MapObject>::iterator w = _walls.begin(); w != _walls.end(); ++w) + _blockingObjects.push_back(&*w); + + // Mouths + for (Common::List<ManagedMouth>::iterator m = _mouths.begin(); m != _mouths.end(); ++m) + _mapAnims.push_back(m->mouth); + + // Sub + _blockingObjects.push_back(_sub); + _anims.push_back(_sub->sub); + + // Moving enemies + for (int i = 0; i < kEnemyCount; i++) { + _enemies[i].enemy = new ANIObject(*_objects); + + _enemies[i].enemy->setPause(true); + _enemies[i].enemy->setVisible(false); + + _enemies[i].isBlocking = false; + + _blockingObjects.push_back(&_enemies[i]); + _mapAnims.push_back(_enemies[i].enemy); + } + + // Bullets + for (int i = 0; i < kMaxBulletCount; i++) { + _bullets[i].bullet = new ANIObject(*_sprites); + + _bullets[i].bullet->setPause(true); + _bullets[i].bullet->setVisible(false); + + _bullets[i].isBlocking = false; + + _mapAnims.push_back(_bullets[i].bullet); + } +} + +void Penetration::drawFloorText() { + _vm->_draw->_backSurface->fillRect(kTextAreaLeft, kTextAreaTop, kTextAreaRight, kTextAreaBottom, kColorBlack); + _vm->_draw->dirtiedRect(_vm->_draw->_backSurface, kTextAreaLeft, kTextAreaTop, kTextAreaRight, kTextAreaBottom); + + const Font *font = _vm->_draw->_fonts[2]; + if (!font) + return; + + const char **strings = kStrings[getLanguage()]; + + const char *floorString = 0; + if (_floor == 0) + floorString = strings[kString3rdBasement]; + else if (_floor == 1) + floorString = strings[kString2ndBasement]; + else if (_floor == 2) + floorString = strings[kString1stBasement]; + + if (floorString) + _vm->_draw->drawString(floorString, 10, 15, kColorFloorText, kColorBlack, 1, + *_vm->_draw->_backSurface, *font); + + if (_exits.size() > 0) { + int exitCount = kString2Exits; + if (_exits.size() == 1) + exitCount = kString1Exit; + + _vm->_draw->drawString(strings[kStringYouHave] , 10, 38, kColorExitText, kColorBlack, 1, + *_vm->_draw->_backSurface, *font); + _vm->_draw->drawString(strings[exitCount] , 10, 53, kColorExitText, kColorBlack, 1, + *_vm->_draw->_backSurface, *font); + _vm->_draw->drawString(strings[kStringToReach] , 10, 68, kColorExitText, kColorBlack, 1, + *_vm->_draw->_backSurface, *font); + _vm->_draw->drawString(strings[kStringUpperLevel1], 10, 84, kColorExitText, kColorBlack, 1, + *_vm->_draw->_backSurface, *font); + _vm->_draw->drawString(strings[kStringUpperLevel2], 10, 98, kColorExitText, kColorBlack, 1, + *_vm->_draw->_backSurface, *font); + + } else + _vm->_draw->drawString(strings[kStringNoExit], 10, 53, kColorExitText, kColorBlack, 1, + *_vm->_draw->_backSurface, *font); +} + +void Penetration::drawEndText() { + // Only draw the end text when we've won and this isn't a test run + if (!hasWon() || _testMode) + return; + + _vm->_draw->_backSurface->fillRect(kTextAreaLeft, kTextAreaTop, kTextAreaRight, kTextAreaBigBottom, kColorBlack); + + const Font *font = _vm->_draw->_fonts[2]; + if (!font) + return; + + const char **strings = kStrings[getLanguage()]; + + _vm->_draw->drawString(strings[kStringLevel0] , 11, 21, kColorExitText, kColorBlack, 1, + *_vm->_draw->_backSurface, *font); + _vm->_draw->drawString(strings[kStringPenetration], 11, 42, kColorExitText, kColorBlack, 1, + *_vm->_draw->_backSurface, *font); + _vm->_draw->drawString(strings[kStringSuccessful] , 11, 58, kColorExitText, kColorBlack, 1, + *_vm->_draw->_backSurface, *font); + + _vm->_draw->drawString(strings[kStringDanger] , 11, 82, kColorFloorText, kColorBlack, 1, + *_vm->_draw->_backSurface, *font); + _vm->_draw->drawString(strings[kStringGynoides] , 11, 98, kColorFloorText, kColorBlack, 1, + *_vm->_draw->_backSurface, *font); + _vm->_draw->drawString(strings[kStringActivated], 11, 113, kColorFloorText, kColorBlack, 1, + *_vm->_draw->_backSurface, *font); + + _vm->_draw->dirtiedRect(_vm->_draw->_backSurface, kTextAreaLeft, kTextAreaTop, kTextAreaRight, kTextAreaBigBottom); + _vm->_draw->blitInvalidated(); + _vm->_video->retrace(); +} + +void Penetration::fadeIn() { + if (!_needFadeIn) + return; + + // Fade to palette + _vm->_palAnim->fade(_vm->_global->_pPaletteDesc, 0, 0); + _needFadeIn = false; +} + +void Penetration::setPalette() { + // Fade to black + _vm->_palAnim->fade(0, 0, 0); + + // Set palette + memcpy(_vm->_draw->_vgaPalette , kPalettes[_floor], 3 * kPaletteSize); + memcpy(_vm->_draw->_vgaSmallPalette, kPalettes[_floor], 3 * kPaletteSize); + + _needFadeIn = true; +} + void Penetration::initScreen() { _vm->_util->setFrameRate(15); - memcpy(_vm->_draw->_vgaPalette , kPalette, 48); - memcpy(_vm->_draw->_vgaSmallPalette, kPalette, 48); + setPalette(); - _vm->_video->setFullPalette(_vm->_global->_pPaletteDesc); + // Draw the shield meter + _sprites->draw(*_background, 0, 0, 95, 6, 9, 117, 0); // Meter frame + _sprites->draw(*_background, 271, 176, 282, 183, 9, 108, 0); // Shield + + // Draw the health meter + _sprites->draw(*_background, 0, 0, 95, 6, 9, 135, 0); // Meter frame + _sprites->draw(*_background, 283, 176, 292, 184, 9, 126, 0); // Heart _vm->_draw->_backSurface->blit(*_background); _vm->_draw->dirtiedRect(_vm->_draw->_backSurface, 0, 0, 319, 199); } -int16 Penetration::checkInput(int16 &mouseX, int16 &mouseY, MouseButtons &mouseButtons) { - _vm->_util->getMouseState(&mouseX, &mouseY, &mouseButtons); +void Penetration::enemiesCreate() { + for (int i = 0; i < kEnemyCount; i++) { + ManagedEnemy &enemy = _enemies[i]; - return _vm->_util->checkKey(); + if (enemy.enemy->isVisible()) + continue; + + enemy.enemy->setAnimation((i & 1) ? kAnimationEnemySquare : kAnimationEnemyRound); + enemy.enemy->setMode(ANIObject::kModeContinuous); + enemy.enemy->setPause(false); + enemy.enemy->setVisible(true); + + int16 width, height; + enemy.enemy->getFrameSize(width, height); + + enemy.width = width; + enemy.height = height; + + do { + enemy.mapX = _vm->_util->getRandom(kMapWidth) * kMapTileWidth + 2; + enemy.mapY = _vm->_util->getRandom(kMapHeight) * kMapTileHeight + 4; + enemy.setTileFromMapPosition(); + } while (isBlocked(enemy, enemy.mapX, enemy.mapY)); + + const int posX = kPlayAreaBorderWidth + enemy.mapX; + const int posY = kPlayAreaBorderHeight + enemy.mapY; + + enemy.enemy->setPosition(posX, posY); + + enemy.isBlocking = true; + enemy.dead = false; + } +} + +void Penetration::enemyMove(ManagedEnemy &enemy, int x, int y) { + if ((x == 0) && (y == 0)) + return; + + MapObject *blockedBy; + findPath(enemy, x, y, &blockedBy); + + enemy.setTileFromMapPosition(); + + const int posX = kPlayAreaBorderWidth + enemy.mapX; + const int posY = kPlayAreaBorderHeight + enemy.mapY; + + enemy.enemy->setPosition(posX, posY); + + if (blockedBy == _sub) + enemyAttack(enemy); +} + +void Penetration::enemiesMove() { + for (int i = 0; i < kEnemyCount; i++) { + ManagedEnemy &enemy = _enemies[i]; + + if (!enemy.enemy->isVisible() || enemy.dead) + continue; + + int x = 0, y = 0; + + if (enemy.mapX > _sub->mapX) + x = -8; + else if (enemy.mapX < _sub->mapX) + x = 8; + + if (enemy.mapY > _sub->mapY) + y = -8; + else if (enemy.mapY < _sub->mapY) + y = 8; + + enemyMove(enemy, x, y); + } +} + +void Penetration::enemyAttack(ManagedEnemy &enemy) { + // If we have shields, the enemy explodes at them, taking a huge chunk of energy with it. + // Otherwise, the enemy nibbles a small amount of health away. + + if (_shieldMeter->getValue() > 0) { + enemyExplode(enemy); + + healthLose(80); + } else + healthLose(5); +} + +void Penetration::enemyExplode(ManagedEnemy &enemy) { + enemy.dead = true; + enemy.isBlocking = false; + + bool isSquare = enemy.enemy->getAnimation() == kAnimationEnemySquare; + + enemy.enemy->setAnimation(isSquare ? kAnimationEnemySquareExplode : kAnimationEnemyRoundExplode); + enemy.enemy->setMode(ANIObject::kModeOnce); + + _vm->_sound->blasterPlay(&_soundExplode, 1, 0); +} + +void Penetration::checkInput() { + Common::Event event; + Common::EventManager *eventMan = g_system->getEventManager(); + + while (eventMan->pollEvent(event)) { + switch (event.type) { + case Common::EVENT_KEYDOWN: + if (event.kbd.keycode == Common::KEYCODE_ESCAPE) + _quit = true; + else if (event.kbd.keycode == Common::KEYCODE_UP) + _keys[kKeyUp ] = true; + else if (event.kbd.keycode == Common::KEYCODE_DOWN) + _keys[kKeyDown ] = true; + else if (event.kbd.keycode == Common::KEYCODE_LEFT) + _keys[kKeyLeft ] = true; + else if (event.kbd.keycode == Common::KEYCODE_RIGHT) + _keys[kKeyRight] = true; + else if (event.kbd.keycode == Common::KEYCODE_SPACE) + _keys[kKeySpace] = true; + else if (event.kbd.keycode == Common::KEYCODE_d) { + _vm->getDebugger()->attach(); + _vm->getDebugger()->onFrame(); + } + break; + + case Common::EVENT_KEYUP: + if (event.kbd.keycode == Common::KEYCODE_UP) + _keys[kKeyUp ] = false; + else if (event.kbd.keycode == Common::KEYCODE_DOWN) + _keys[kKeyDown ] = false; + else if (event.kbd.keycode == Common::KEYCODE_LEFT) + _keys[kKeyLeft ] = false; + else if (event.kbd.keycode == Common::KEYCODE_RIGHT) + _keys[kKeyRight] = false; + else if (event.kbd.keycode == Common::KEYCODE_SPACE) + _keys[kKeySpace] = false; + break; + + default: + break; + } + } +} + +void Penetration::handleSub() { + int x, y; + Submarine::Direction direction = getDirection(x, y); + + subMove(x, y, direction); + + if (_keys[kKeySpace]) + subShoot(); +} + +bool Penetration::isBlocked(const MapObject &self, int16 x, int16 y, MapObject **blockedBy) { + + if ((x < 0) || (y < 0)) + return true; + if (((x + self.width - 1) >= (kMapWidth * kMapTileWidth)) || + ((y + self.height - 1) >= (kMapHeight * kMapTileHeight))) + return true; + + MapObject checkSelf(0, 0, self.width, self.height); + + checkSelf.mapX = x; + checkSelf.mapY = y; + + for (Common::List<MapObject *>::iterator o = _blockingObjects.begin(); o != _blockingObjects.end(); ++o) { + MapObject &obj = **o; + + if (&obj == &self) + continue; + + if (!obj.isBlocking) + continue; + + if (obj.isIn(checkSelf) || checkSelf.isIn(obj)) { + if (blockedBy && !*blockedBy) + *blockedBy = &obj; + + return true; + } + } + + return false; +} + +void Penetration::findPath(MapObject &obj, int x, int y, MapObject **blockedBy) { + if (blockedBy) + *blockedBy = 0; + + while ((x != 0) || (y != 0)) { + uint16 oldX = obj.mapX; + uint16 oldY = obj.mapY; + + uint16 newX = obj.mapX; + if (x > 0) { + newX++; + x--; + } else if (x < 0) { + newX--; + x++; + } + + if (!isBlocked(obj, newX, obj.mapY, blockedBy)) + obj.mapX = newX; + + uint16 newY = obj.mapY; + if (y > 0) { + newY++; + y--; + } else if (y < 0) { + newY--; + y++; + } + + if (!isBlocked(obj, obj.mapX, newY, blockedBy)) + obj.mapY = newY; + + if ((obj.mapX == oldX) && (obj.mapY == oldY)) + break; + } +} + +void Penetration::subMove(int x, int y, Submarine::Direction direction) { + if (!_sub->sub->canMove()) + return; + + if ((x == 0) && (y == 0)) + return; + + findPath(*_sub, x, y); + + _sub->setTileFromMapPosition(); + + _sub->sub->turn(direction); + + checkShields(); + checkMouths(); + checkExits(); +} + +void Penetration::subShoot() { + if (!_sub->sub->canMove() || _sub->sub->isShooting()) + return; + + if (_shotCoolDown > 0) + return; + + // Creating a bullet + int slot = findEmptyBulletSlot(); + if (slot < 0) + return; + + ManagedBullet &bullet = _bullets[slot]; + + bullet.bullet->setAnimation(directionToBullet(_sub->sub->getDirection())); + + setBulletPosition(*_sub, bullet); + + const int posX = kPlayAreaBorderWidth + bullet.mapX; + const int posY = kPlayAreaBorderHeight + bullet.mapY; + + bullet.bullet->setPosition(posX, posY); + bullet.bullet->setVisible(true); + + // Shooting + _sub->sub->shoot(); + _vm->_sound->blasterPlay(&_soundShoot, 1, 0); + + _shotCoolDown = 3; +} + +void Penetration::setBulletPosition(const ManagedSub &sub, ManagedBullet &bullet) const { + bullet.mapX = sub.mapX; + bullet.mapY= sub.mapY; + + int16 sWidth, sHeight; + sub.sub->getFrameSize(sWidth, sHeight); + + int16 bWidth, bHeight; + bullet.bullet->getFrameSize(bWidth, bHeight); + + switch (sub.sub->getDirection()) { + case Submarine::kDirectionN: + bullet.mapX += sWidth / 2; + bullet.mapY -= bHeight; + + bullet.deltaX = 0; + bullet.deltaY = -8; + break; + + case Submarine::kDirectionNE: + bullet.mapX += sWidth; + bullet.mapY -= bHeight * 2; + + bullet.deltaX = 8; + bullet.deltaY = -8; + break; + + case Submarine::kDirectionE: + bullet.mapX += sWidth; + bullet.mapY += sHeight / 2 - bHeight; + + bullet.deltaX = 8; + bullet.deltaY = 0; + break; + + case Submarine::kDirectionSE: + bullet.mapX += sWidth; + bullet.mapY += sHeight; + + bullet.deltaX = 8; + bullet.deltaY = 8; + break; + + case Submarine::kDirectionS: + bullet.mapX += sWidth / 2; + bullet.mapY += sHeight; + + bullet.deltaX = 0; + bullet.deltaY = 8; + break; + + case Submarine::kDirectionSW: + bullet.mapX -= bWidth; + bullet.mapY += sHeight; + + bullet.deltaX = -8; + bullet.deltaY = 8; + break; + + case Submarine::kDirectionW: + bullet.mapX -= bWidth; + bullet.mapY += sHeight / 2 - bHeight; + + bullet.deltaX = -8; + bullet.deltaY = 0; + break; + + case Submarine::kDirectionNW: + bullet.mapX -= bWidth; + bullet.mapY -= bHeight; + + bullet.deltaX = -8; + bullet.deltaY = -8; + break; + + default: + break; + } +} + +uint16 Penetration::directionToBullet(Submarine::Direction direction) const { + switch (direction) { + case Submarine::kDirectionN: + return kSpriteBulletN; + + case Submarine::kDirectionNE: + return kSpriteBulletNE; + + case Submarine::kDirectionE: + return kSpriteBulletE; + + case Submarine::kDirectionSE: + return kSpriteBulletSE; + + case Submarine::kDirectionS: + return kSpriteBulletS; + + case Submarine::kDirectionSW: + return kSpriteBulletSW; + + case Submarine::kDirectionW: + return kSpriteBulletW; + + case Submarine::kDirectionNW: + return kSpriteBulletNW; + + default: + break; + } + + return 0; +} + +int Penetration::findEmptyBulletSlot() const { + for (int i = 0; i < kMaxBulletCount; i++) + if (!_bullets[i].bullet->isVisible()) + return i; + + return -1; +} + +void Penetration::bulletsMove() { + for (int i = 0; i < kMaxBulletCount; i++) + if (_bullets[i].bullet->isVisible()) + bulletMove(_bullets[i]); +} + +void Penetration::bulletMove(ManagedBullet &bullet) { + MapObject *blockedBy; + findPath(bullet, bullet.deltaX, bullet.deltaY, &blockedBy); + + if (blockedBy) { + checkShotEnemy(*blockedBy); + bullet.bullet->setVisible(false); + return; + } + + const int posX = kPlayAreaBorderWidth + bullet.mapX; + const int posY = kPlayAreaBorderHeight + bullet.mapY; + + bullet.bullet->setPosition(posX, posY); +} + +void Penetration::checkShotEnemy(MapObject &shotObject) { + for (int i = 0; i < kEnemyCount; i++) { + ManagedEnemy &enemy = _enemies[i]; + + if ((&enemy == &shotObject) && !enemy.dead && enemy.enemy->isVisible()) { + enemyExplode(enemy); + return; + } + } +} + +Submarine::Direction Penetration::getDirection(int &x, int &y) const { + x = _keys[kKeyRight] ? 3 : (_keys[kKeyLeft] ? -3 : 0); + y = _keys[kKeyDown ] ? 3 : (_keys[kKeyUp ] ? -3 : 0); + + if ((x > 0) && (y > 0)) + return Submarine::kDirectionSE; + if ((x > 0) && (y < 0)) + return Submarine::kDirectionNE; + if ((x < 0) && (y > 0)) + return Submarine::kDirectionSW; + if ((x < 0) && (y < 0)) + return Submarine::kDirectionNW; + if (x > 0) + return Submarine::kDirectionE; + if (x < 0) + return Submarine::kDirectionW; + if (y > 0) + return Submarine::kDirectionS; + if (y < 0) + return Submarine::kDirectionN; + + return Submarine::kDirectionNone; +} + +void Penetration::checkShields() { + for (Common::List<MapObject>::iterator s = _shields.begin(); s != _shields.end(); ++s) { + if ((s->tileX == _sub->tileX) && (s->tileY == _sub->tileY)) { + // Charge shields + _shieldMeter->setMaxValue(); + + // Play the shield sound + _vm->_sound->blasterPlay(&_soundShield, 1, 0); + + // Erase the shield from the map + _sprites->draw(*_map, 30, s->mapX + kPlayAreaBorderWidth, s->mapY + kPlayAreaBorderHeight); + _shields.erase(s); + break; + } + } +} + +void Penetration::checkMouths() { + for (Common::List<ManagedMouth>::iterator m = _mouths.begin(); m != _mouths.end(); ++m) { + if (!m->mouth->isDeactivated()) + continue; + + if ((( m->tileX == _sub->tileX) && (m->tileY == _sub->tileY)) || + (((m->tileX + 1) == _sub->tileX) && (m->tileY == _sub->tileY))) { + + m->mouth->activate(); + + // Play the mouth sound and do health gain/loss + if (m->type == kMouthTypeBite) { + _vm->_sound->blasterPlay(&_soundBite, 1, 0); + healthLose(230); + } else if (m->type == kMouthTypeKiss) { + _vm->_sound->blasterPlay(&_soundKiss, 1, 0); + healthGain(120); + } + } + } +} + +void Penetration::checkExits() { + if (!_sub->sub->canMove()) + return; + + for (Common::List<MapObject>::iterator e = _exits.begin(); e != _exits.end(); ++e) { + if ((e->tileX == _sub->tileX) && (e->tileY == _sub->tileY)) { + _sub->setMapFromTilePosition(); + + _sub->sub->leave(); + + _vm->_sound->blasterPlay(&_soundExit, 1, 0); + break; + } + } +} + +void Penetration::healthGain(int amount) { + if (_shieldMeter->getValue() > 0) + _healthMeter->increase(_shieldMeter->increase(amount)); + else + _healthMeter->increase(amount); +} + +void Penetration::healthLose(int amount) { + _healthMeter->decrease(_shieldMeter->decrease(amount)); + + if (_healthMeter->getValue() == 0) + _sub->sub->die(); +} + +void Penetration::checkExited() { + if (_sub->sub->hasExited()) { + _floor++; + + if (_floor >= kFloorCount) + return; + + setPalette(); + createMap(); + drawFloorText(); + } +} + +bool Penetration::isDead() const { + return _sub && _sub->sub->isDead(); +} + +bool Penetration::hasWon() const { + return _floor >= kFloorCount; +} + +int Penetration::getLanguage() const { + if (_vm->_global->_language < kLanguageCount) + return _vm->_global->_language; + + return kFallbackLanguage; } void Penetration::updateAnims() { - int16 left, top, right, bottom; + int16 left = 0, top = 0, right = 0, bottom = 0; + + // Clear the previous map animation frames + for (Common::List<ANIObject *>::iterator a = _mapAnims.reverse_begin(); + a != _mapAnims.end(); --a) { + + (*a)->clear(*_map, left, top, right, bottom); + } + + // Draw the current map animation frames + for (Common::List<ANIObject *>::iterator a = _mapAnims.begin(); + a != _mapAnims.end(); ++a) { + + (*a)->draw(*_map, left, top, right, bottom); + (*a)->advance(); + } // Clear the previous animation frames for (Common::List<ANIObject *>::iterator a = _anims.reverse_begin(); a != _anims.end(); --a) { - (*a)->clear(*_vm->_draw->_backSurface, left, top, right, bottom); - _vm->_draw->dirtiedRect(_vm->_draw->_backSurface, left, top, right, bottom); + if ((*a)->clear(*_vm->_draw->_backSurface, left, top, right, bottom)) + _vm->_draw->dirtiedRect(_vm->_draw->_backSurface, left, top, right, bottom); + } + + if (_sub) { + // Draw the map + + _vm->_draw->_backSurface->blit(*_map, _sub->mapX, _sub->mapY, + _sub->mapX + kPlayAreaWidth - 1, _sub->mapY + kPlayAreaHeight - 1, kPlayAreaX, kPlayAreaY); + _vm->_draw->dirtiedRect(_vm->_draw->_backSurface, kPlayAreaX, kPlayAreaY, + kPlayAreaX + kPlayAreaWidth - 1, kPlayAreaY + kPlayAreaHeight - 1); } // Draw the current animation frames for (Common::List<ANIObject *>::iterator a = _anims.begin(); a != _anims.end(); ++a) { - (*a)->draw(*_vm->_draw->_backSurface, left, top, right, bottom); - _vm->_draw->dirtiedRect(_vm->_draw->_backSurface, left, top, right, bottom); + if ((*a)->draw(*_vm->_draw->_backSurface, left, top, right, bottom)) + _vm->_draw->dirtiedRect(_vm->_draw->_backSurface, left, top, right, bottom); (*a)->advance(); } diff --git a/engines/gob/minigames/geisha/penetration.h b/engines/gob/minigames/geisha/penetration.h index 6c32d28942..50004eba8e 100644 --- a/engines/gob/minigames/geisha/penetration.h +++ b/engines/gob/minigames/geisha/penetration.h @@ -26,6 +26,10 @@ #include "common/system.h" #include "common/list.h" +#include "gob/sound/sounddesc.h" + +#include "gob/minigames/geisha/submarine.h" + namespace Gob { class GobEngine; @@ -36,6 +40,7 @@ class ANIFile; namespace Geisha { class Meter; +class Mouth; /** Geisha's "Penetration" minigame. */ class Penetration { @@ -45,30 +50,203 @@ public: bool play(bool hasAccessPass, bool hasMaxEnergy, bool testMode); + bool isPlaying() const; + void cheatWin(); + private: + static const int kModeCount = 2; + static const int kFloorCount = 3; + + static const int kMapWidth = 17; + static const int kMapHeight = 13; + + static const int kPaletteSize = 16; + + static const byte kPalettes[kFloorCount][3 * kPaletteSize]; + static const byte kMaps[kModeCount][kFloorCount][kMapWidth * kMapHeight]; + + static const int kEnemyCount = 9; + static const int kMaxBulletCount = 10; + + struct MapObject { + uint16 tileX; + uint16 tileY; + + uint16 mapX; + uint16 mapY; + + uint16 width; + uint16 height; + + bool isBlocking; + + MapObject(uint16 tX, uint16 tY, uint16 mX, uint16 mY, uint16 w, uint16 h); + MapObject(uint16 tX, uint16 tY, uint16 w, uint16 h); + + void setTileFromMapPosition(); + void setMapFromTilePosition(); + + bool isIn(uint16 mX, uint16 mY) const; + bool isIn(uint16 mX, uint16 mY, uint16 w, uint16 h) const; + bool isIn(const MapObject &obj) const; + }; + + enum MouthType { + kMouthTypeBite, + kMouthTypeKiss + }; + + struct ManagedMouth : public MapObject { + Mouth *mouth; + + MouthType type; + + ManagedMouth(uint16 tX, uint16 tY, MouthType t); + ~ManagedMouth(); + }; + + struct ManagedSub : public MapObject { + Submarine *sub; + + ManagedSub(uint16 tX, uint16 tY); + ~ManagedSub(); + }; + + struct ManagedEnemy : public MapObject { + ANIObject *enemy; + + bool dead; + + ManagedEnemy(); + ~ManagedEnemy(); + + void clear(); + }; + + struct ManagedBullet : public MapObject { + ANIObject *bullet; + + int16 deltaX; + int16 deltaY; + + ManagedBullet(); + ~ManagedBullet(); + + void clear(); + }; + + enum Keys { + kKeyUp = 0, + kKeyDown, + kKeyLeft, + kKeyRight, + kKeySpace, + kKeyCount + }; + GobEngine *_vm; bool _hasAccessPass; bool _hasMaxEnergy; bool _testMode; + bool _needFadeIn; + + bool _quit; + bool _keys[kKeyCount]; + Surface *_background; CMPFile *_sprites; ANIFile *_objects; Common::List<ANIObject *> _anims; + Common::List<ANIObject *> _mapAnims; Meter *_shieldMeter; Meter *_healthMeter; + uint8 _floor; + + Surface *_map; + + ManagedSub *_sub; + + Common::List<MapObject> _walls; + Common::List<MapObject> _exits; + Common::List<MapObject> _shields; + Common::List<ManagedMouth> _mouths; + + ManagedEnemy _enemies[kEnemyCount]; + ManagedBullet _bullets[kMaxBulletCount]; + + Common::List<MapObject *> _blockingObjects; + + uint8 _shotCoolDown; + + SoundDesc _soundShield; + SoundDesc _soundBite; + SoundDesc _soundKiss; + SoundDesc _soundShoot; + SoundDesc _soundExit; + SoundDesc _soundExplode; + + bool _isPlaying; + + void init(); void deinit(); + void clearMap(); + void createMap(); + void initScreen(); + void setPalette(); + void fadeIn(); + + void drawFloorText(); + void drawEndText(); + + bool isBlocked(const MapObject &self, int16 x, int16 y, MapObject **blockedBy = 0); + void findPath(MapObject &obj, int x, int y, MapObject **blockedBy = 0); + void updateAnims(); - int16 checkInput(int16 &mouseX, int16 &mouseY, MouseButtons &mouseButtons); + void checkInput(); + + Submarine::Direction getDirection(int &x, int &y) const; + + void handleSub(); + void subMove(int x, int y, Submarine::Direction direction); + void subShoot(); + + int findEmptyBulletSlot() const; + uint16 directionToBullet(Submarine::Direction direction) const; + void setBulletPosition(const ManagedSub &sub, ManagedBullet &bullet) const; + + void bulletsMove(); + void bulletMove(ManagedBullet &bullet); + void checkShotEnemy(MapObject &shotObject); + + void checkExits(); + void checkShields(); + void checkMouths(); + + void healthGain(int amount); + void healthLose(int amount); + + void checkExited(); + + void enemiesCreate(); + void enemiesMove(); + void enemyMove(ManagedEnemy &enemy, int x, int y); + void enemyAttack(ManagedEnemy &enemy); + void enemyExplode(ManagedEnemy &enemy); + + bool isDead() const; + bool hasWon() const; + + int getLanguage() const; }; } // End of namespace Geisha diff --git a/engines/gob/minigames/geisha/submarine.cpp b/engines/gob/minigames/geisha/submarine.cpp new file mode 100644 index 0000000000..d16761cb7c --- /dev/null +++ b/engines/gob/minigames/geisha/submarine.cpp @@ -0,0 +1,256 @@ +/* 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 "gob/minigames/geisha/submarine.h" + +namespace Gob { + +namespace Geisha { + +enum Animation { + kAnimationDriveS = 4, + kAnimationDriveE = 5, + kAnimationDriveN = 6, + kAnimationDriveW = 7, + kAnimationDriveSE = 8, + kAnimationDriveNE = 9, + kAnimationDriveSW = 10, + kAnimationDriveNW = 11, + kAnimationShootS = 12, + kAnimationShootN = 13, + kAnimationShootW = 14, + kAnimationShootE = 15, + kAnimationShootNE = 16, + kAnimationShootSE = 17, + kAnimationShootSW = 18, + kAnimationShootNW = 19, + kAnimationExplodeN = 28, + kAnimationExplodeS = 29, + kAnimationExplodeW = 30, + kAnimationExplodeE = 31, + kAnimationExit = 32 +}; + + +Submarine::Submarine(const ANIFile &ani) : ANIObject(ani), _state(kStateMove) { + turn(kDirectionN); +} + +Submarine::~Submarine() { +} + +Submarine::Direction Submarine::getDirection() const { + return _direction; +} + +void Submarine::turn(Direction to) { + // Nothing to do + if ((to == kDirectionNone) || ((_state == kStateMove) && (_direction == to))) + return; + + _direction = to; + + move(); +} + +void Submarine::move() { + uint16 frame = getFrame(); + uint16 anim = (_state == kStateShoot) ? directionToShoot(_direction) : directionToMove(_direction); + + setAnimation(anim); + setFrame(frame); + setPause(false); + setVisible(true); + + setMode((_state == kStateShoot) ? kModeOnce : kModeContinuous); +} + +void Submarine::shoot() { + _state = kStateShoot; + + setAnimation(directionToShoot(_direction)); + setMode(kModeOnce); + setPause(false); + setVisible(true); +} + +void Submarine::die() { + if (!canMove()) + return; + + _state = kStateDie; + + setAnimation(directionToExplode(_direction)); + setMode(kModeOnce); + setPause(false); + setVisible(true); +} + +void Submarine::leave() { + _state = kStateExit; + + setAnimation(kAnimationExit); + setMode(kModeOnce); + setPause(false); + setVisible(true); +} + +void Submarine::advance() { + ANIObject::advance(); + + switch (_state) { + case kStateShoot: + if (isPaused()) { + _state = kStateMove; + + move(); + } + break; + + case kStateExit: + if (isPaused()) + _state = kStateExited; + + break; + + case kStateDie: + if (isPaused()) + _state = kStateDead; + break; + + default: + break; + } +} + +bool Submarine::canMove() const { + return (_state == kStateMove) || (_state == kStateShoot); +} + +bool Submarine::isDead() const { + return _state == kStateDead; +} + +bool Submarine::isShooting() const { + return _state == kStateShoot; +} + +bool Submarine::hasExited() const { + return _state == kStateExited; +} + +uint16 Submarine::directionToMove(Direction direction) const { + switch (direction) { + case kDirectionN: + return kAnimationDriveN; + + case kDirectionNE: + return kAnimationDriveNE; + + case kDirectionE: + return kAnimationDriveE; + + case kDirectionSE: + return kAnimationDriveSE; + + case kDirectionS: + return kAnimationDriveS; + + case kDirectionSW: + return kAnimationDriveSW; + + case kDirectionW: + return kAnimationDriveW; + + case kDirectionNW: + return kAnimationDriveNW; + + default: + break; + } + + return 0; +} + +uint16 Submarine::directionToShoot(Direction direction) const { + switch (direction) { + case kDirectionN: + return kAnimationShootN; + + case kDirectionNE: + return kAnimationShootNE; + + case kDirectionE: + return kAnimationShootE; + + case kDirectionSE: + return kAnimationShootSE; + + case kDirectionS: + return kAnimationShootS; + + case kDirectionSW: + return kAnimationShootSW; + + case kDirectionW: + return kAnimationShootW; + + case kDirectionNW: + return kAnimationShootNW; + + default: + break; + } + + return 0; +} + +uint16 Submarine::directionToExplode(Direction direction) const { + // Only 4 exploding animations (spinning clockwise) + + switch (direction) { + case kDirectionNW: + case kDirectionN: + return kAnimationExplodeN; + + case kDirectionNE: + case kDirectionE: + return kAnimationExplodeE; + + case kDirectionSE: + case kDirectionS: + return kAnimationExplodeS; + + case kDirectionSW: + case kDirectionW: + return kAnimationExplodeW; + + default: + break; + } + + return 0; +} + +} // End of namespace Geisha + +} // End of namespace Gob diff --git a/engines/gob/minigames/geisha/submarine.h b/engines/gob/minigames/geisha/submarine.h new file mode 100644 index 0000000000..a6eae57095 --- /dev/null +++ b/engines/gob/minigames/geisha/submarine.h @@ -0,0 +1,107 @@ +/* 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 GOB_MINIGAMES_GEISHA_SUBMARINE_H +#define GOB_MINIGAMES_GEISHA_SUBMARINE_H + +#include "gob/aniobject.h" + +namespace Gob { + +namespace Geisha { + +/** The submarine Geisha's "Penetration" minigame. */ +class Submarine : public ANIObject { +public: + enum Direction { + kDirectionNone, + kDirectionN, + kDirectionNE, + kDirectionE, + kDirectionSE, + kDirectionS, + kDirectionSW, + kDirectionW, + kDirectionNW + }; + + Submarine(const ANIFile &ani); + ~Submarine(); + + Direction getDirection() const; + + /** Turn to the specified direction. */ + void turn(Direction to); + + /** Play the shoot animation. */ + void shoot(); + + /** Play the exploding animation. */ + void die(); + + /** Play the exiting animation. */ + void leave(); + + /** Advance the animation to the next frame. */ + void advance(); + + /** Can the submarine move at the moment? */ + bool canMove() const; + + /** Is the submarine dead? */ + bool isDead() const; + + /** Is the submarine shooting? */ + bool isShooting() const; + + /** Has the submarine finished exiting the level? */ + bool hasExited() const; + +private: + enum State { + kStateNone = 0, + kStateMove, + kStateShoot, + kStateExit, + kStateExited, + kStateDie, + kStateDead + }; + + State _state; + Direction _direction; + + /** Map the directions to move animation indices. */ + uint16 directionToMove(Direction direction) const; + /** Map the directions to shoot animation indices. */ + uint16 directionToShoot(Direction direction) const; + /** Map the directions to explode animation indices. */ + uint16 directionToExplode(Direction direction) const; + + void move(); +}; + +} // End of namespace Geisha + +} // End of namespace Gob + +#endif // GOB_MINIGAMES_GEISHA_SUBMARINE_H diff --git a/engines/gob/module.mk b/engines/gob/module.mk index 9da5a82de2..b9680fad6b 100644 --- a/engines/gob/module.mk +++ b/engines/gob/module.mk @@ -80,6 +80,8 @@ MODULE_OBJS := \ minigames/geisha/oko.o \ minigames/geisha/meter.o \ minigames/geisha/diving.o \ + minigames/geisha/mouth.o \ + minigames/geisha/submarine.o \ minigames/geisha/penetration.o \ save/savefile.o \ save/savehandler.o \ diff --git a/engines/gob/video.cpp b/engines/gob/video.cpp index c865b2b40e..3b1c6423bb 100644 --- a/engines/gob/video.cpp +++ b/engines/gob/video.cpp @@ -25,7 +25,6 @@ #include "engines/util.h" #include "graphics/cursorman.h" -#include "graphics/fontman.h" #include "graphics/palette.h" #include "graphics/surface.h" diff --git a/engines/gob/videoplayer.cpp b/engines/gob/videoplayer.cpp index 221f5ab3c9..a478492ccc 100644 --- a/engines/gob/videoplayer.cpp +++ b/engines/gob/videoplayer.cpp @@ -234,6 +234,23 @@ void VideoPlayer::closeAll() { closeVideo(i); } +bool VideoPlayer::reopenVideo(int slot) { + Video *video = getVideoBySlot(slot); + if (!video) + return true; + + return reopenVideo(*video); +} + +bool VideoPlayer::reopenAll() { + bool all = true; + for (int i = 0; i < kVideoSlotCount; i++) + if (!reopenVideo(i)) + all = false; + + return all; +} + void VideoPlayer::pauseVideo(int slot, bool pause) { Video *video = getVideoBySlot(slot); if (!video || !video->decoder) @@ -850,6 +867,39 @@ Common::String VideoPlayer::findFile(const Common::String &file, Properties &pro return video; } +bool VideoPlayer::reopenVideo(Video &video) { + if (video.isEmpty()) + return true; + + if (video.fileName.empty()) { + video.close(); + return false; + } + + Properties properties; + + properties.type = video.properties.type; + + Common::String fileName = findFile(video.fileName, properties); + if (fileName.empty()) { + video.close(); + return false; + } + + Common::SeekableReadStream *stream = _vm->_dataIO->getFile(fileName); + if (!stream) { + video.close(); + return false; + } + + if (!video.decoder->reloadStream(stream)) { + delete stream; + return false; + } + + return true; +} + void VideoPlayer::copyPalette(const Video &video, int16 palStart, int16 palEnd) { if (!video.decoder->hasPalette() || !video.decoder->isPaletted()) return; diff --git a/engines/gob/videoplayer.h b/engines/gob/videoplayer.h index bc7cb48768..129ccef67a 100644 --- a/engines/gob/videoplayer.h +++ b/engines/gob/videoplayer.h @@ -110,6 +110,9 @@ public: void closeLiveSound(); void closeAll(); + bool reopenVideo(int slot = 0); + bool reopenAll(); + void pauseVideo(int slot, bool pause); void pauseAll(bool pause); @@ -163,6 +166,8 @@ private: bool isEmpty() const; void close(); + + void reopen(); }; static const int kVideoSlotCount = 32; @@ -188,6 +193,8 @@ private: ::Video::CoktelDecoder *openVideo(const Common::String &file, Properties &properties); + bool reopenVideo(Video &video); + bool playFrame(int slot, Properties &properties); void checkAbort(Video &video, Properties &properties); diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h index 4ddf0534ea..254a479e65 100644 --- a/engines/sci/engine/kernel_tables.h +++ b/engines/sci/engine/kernel_tables.h @@ -564,7 +564,7 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(GetSierraProfileInt), SIG_EVERYWHERE, "rri", NULL, NULL }, { MAP_CALL(CelInfo), SIG_EVERYWHERE, "iiiiii", NULL, NULL }, { MAP_CALL(SetLanguage), SIG_EVERYWHERE, "r", NULL, NULL }, - { MAP_CALL(ScrollWindow), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(ScrollWindow), SIG_EVERYWHERE, "io(.*)", NULL, NULL }, { MAP_CALL(SetFontRes), SIG_EVERYWHERE, "ii", NULL, NULL }, { MAP_CALL(Font), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, { MAP_CALL(Bitmap), SIG_EVERYWHERE, "(.*)", NULL, NULL }, diff --git a/engines/sci/engine/kgraphics32.cpp b/engines/sci/engine/kgraphics32.cpp index 2bb8288cb7..71c4949d65 100644 --- a/engines/sci/engine/kgraphics32.cpp +++ b/engines/sci/engine/kgraphics32.cpp @@ -308,103 +308,91 @@ reg_t kCelInfo(EngineState *s, int argc, reg_t *argv) { } reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv) { - // Used by Phantasmagoria 1 and SQ6. In SQ6, it is used for the messages - // shown in the scroll window at the bottom of the screen. - - // TODO: This is all a stub/skeleton, thus we're invoking kStub() for now - kStub(s, argc, argv); - - switch (argv[0].toUint16()) { + // Used by SQ6 and LSL6 hires for the text area in the bottom of the + // screen. The relevant scripts also exist in Phantasmagoria 1, but they're + // unused. This is always called by scripts 64906 (ScrollerWindow) and + // 64907 (ScrollableWindow). + + reg_t kWindow = argv[1]; + uint16 op = argv[0].toUint16(); + switch (op) { case 0: // Init - // 2 parameters - // argv[1] points to the scroll object (e.g. textScroller in SQ6) - // argv[2] is an integer (e.g. 0x32) - break; - case 1: // Show message + g_sci->_gfxFrameout->initScrollText(argv[2].toUint16()); // maxItems + g_sci->_gfxFrameout->clearScrollTexts(); + return argv[1]; // kWindow + case 1: // Show message, called by ScrollableWindow::addString + case 14: // Modify message, called by ScrollableWindow::modifyString // 5 or 6 parameters // Seems to be called with 5 parameters when the narrator speaks, and // with 6 when Roger speaks - // argv[1] unknown (usually 0) - // argv[2] the text to show - // argv[3] a small integer (e.g. 0x32) - // argv[4] a small integer (e.g. 0x54) - // argv[5] optional, unknown (usually 0) - warning("kScrollWindow: '%s'", s->_segMan->getString(argv[2]).c_str()); - break; - case 2: // Clear - // 2 parameters - // TODO - break; - case 3: // Page up - // 2 parameters - // TODO - break; - case 4: // Page down - // 2 parameters - // TODO + { + Common::String text = s->_segMan->getString(argv[2]); + uint16 x = 0;//argv[3].toUint16(); // TODO: can't be x (values are all wrong) + uint16 y = 0;//argv[4].toUint16(); // TODO: can't be y (values are all wrong) + // TODO: argv[5] is an optional unknown parameter (an integer set to 0) + g_sci->_gfxFrameout->addScrollTextEntry(text, kWindow, x, y, (op == 14)); + } break; - case 5: // Up arrow - // 2 parameters - // TODO + case 2: // Clear, called by ScrollableWindow::erase + g_sci->_gfxFrameout->clearScrollTexts(); break; - case 6: // Down arrow - // 2 parameters + case 3: // Page up, called by ScrollableWindow::scrollTo // TODO + kStub(s, argc, argv); break; - case 7: // Home - // 2 parameters + case 4: // Page down, called by ScrollableWindow::scrollTo // TODO + kStub(s, argc, argv); break; - case 8: // End - // 2 parameters - // TODO + case 5: // Up arrow, called by ScrollableWindow::scrollTo + g_sci->_gfxFrameout->prevScrollText(); break; - case 9: // Resize - // 3 parameters - // TODO + case 6: // Down arrow, called by ScrollableWindow::scrollTo + g_sci->_gfxFrameout->nextScrollText(); break; - case 10: // Where - // 3 parameters - // TODO + case 7: // Home, called by ScrollableWindow::scrollTo + g_sci->_gfxFrameout->firstScrollText(); break; - case 11: // Go - // 4 parameters - // TODO + case 8: // End, called by ScrollableWindow::scrollTo + g_sci->_gfxFrameout->lastScrollText(); break; - case 12: // Insert - // 7 parameters + case 9: // Resize, called by ScrollableWindow::resize and ScrollerWindow::resize // TODO + kStub(s, argc, argv); break; - case 13: // Delete - // 3 parameters + case 10: // Where, called by ScrollableWindow::where // TODO + // argv[2] is an unknown integer + kStub(s, argc, argv); break; - case 14: // Modify - // 7 or 8 parameters + case 11: // Go, called by ScrollableWindow::scrollTo + // 2 extra parameters here // TODO + kStub(s, argc, argv); break; - case 15: // Hide - // 2 parameters + case 12: // Insert, called by ScrollableWindow::insertString + // 3 extra parameters here // TODO + kStub(s, argc, argv); break; - case 16: // Show - // 2 parameters - // TODO + // case 13 (Delete) is handled below + // case 14 (Modify) is handled above + case 15: // Hide, called by ScrollableWindow::hide + g_sci->_gfxFrameout->toggleScrollText(false); break; - case 17: // Destroy - // 2 parameters - // TODO + case 16: // Show, called by ScrollableWindow::show + g_sci->_gfxFrameout->toggleScrollText(true); break; - case 18: // Text - // 2 parameters - // TODO + case 17: // Destroy, called by ScrollableWindow::dispose + g_sci->_gfxFrameout->clearScrollTexts(); break; - case 19: // Reconstruct - // 3 parameters - // TODO + case 13: // Delete, unused + case 18: // Text, unused + case 19: // Reconstruct, unused + error("kScrollWindow: Unused subop %d invoked", op); break; default: - error("kScrollWindow: unknown subop %d", argv[0].toUint16()); + error("kScrollWindow: unknown subop %d", op); break; } diff --git a/engines/sci/event.cpp b/engines/sci/event.cpp index 378e88b7df..14443db1e2 100644 --- a/engines/sci/event.cpp +++ b/engines/sci/event.cpp @@ -102,8 +102,8 @@ const MouseEventConversion mouseEventMappings[] = { { Common::EVENT_RBUTTONDOWN, SCI_EVENT_MOUSE_PRESS, 2 }, { Common::EVENT_MBUTTONDOWN, SCI_EVENT_MOUSE_PRESS, 3 }, { Common::EVENT_LBUTTONUP, SCI_EVENT_MOUSE_RELEASE, 1 }, - { Common::EVENT_LBUTTONUP, SCI_EVENT_MOUSE_RELEASE, 2 }, - { Common::EVENT_LBUTTONUP, SCI_EVENT_MOUSE_RELEASE, 3 } + { Common::EVENT_RBUTTONUP, SCI_EVENT_MOUSE_RELEASE, 2 }, + { Common::EVENT_MBUTTONUP, SCI_EVENT_MOUSE_RELEASE, 3 } }; EventManager::EventManager(bool fontIsExtended) : _fontIsExtended(fontIsExtended) { diff --git a/engines/sci/graphics/frameout.cpp b/engines/sci/graphics/frameout.cpp index 709a708d8b..450581000b 100644 --- a/engines/sci/graphics/frameout.cpp +++ b/engines/sci/graphics/frameout.cpp @@ -59,6 +59,9 @@ GfxFrameout::GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAd _coordAdjuster = (GfxCoordAdjuster32 *)coordAdjuster; _scriptsRunningWidth = 320; _scriptsRunningHeight = 200; + _curScrollText = -1; + _showScrollText = false; + _maxScrollTexts = 0; } GfxFrameout::~GfxFrameout() { @@ -69,6 +72,46 @@ void GfxFrameout::clear() { deletePlaneItems(NULL_REG); _planes.clear(); deletePlanePictures(NULL_REG); + clearScrollTexts(); +} + +void GfxFrameout::clearScrollTexts() { + _scrollTexts.clear(); + _curScrollText = -1; +} + +void GfxFrameout::addScrollTextEntry(Common::String &text, reg_t kWindow, uint16 x, uint16 y, bool replace) { + //reg_t bitmapHandle = g_sci->_gfxText32->createScrollTextBitmap(text, kWindow); + // HACK: We set the container dimensions manually + reg_t bitmapHandle = g_sci->_gfxText32->createScrollTextBitmap(text, kWindow, 480, 70); + ScrollTextEntry textEntry; + textEntry.bitmapHandle = bitmapHandle; + textEntry.kWindow = kWindow; + textEntry.x = x; + textEntry.y = y; + if (!replace || _scrollTexts.size() == 0) { + if (_scrollTexts.size() > _maxScrollTexts) { + _scrollTexts.remove_at(0); + _curScrollText--; + } + _scrollTexts.push_back(textEntry); + _curScrollText++; + } else { + _scrollTexts.pop_back(); + _scrollTexts.push_back(textEntry); + } +} + +void GfxFrameout::showCurrentScrollText() { + if (!_showScrollText || _curScrollText < 0) + return; + + uint16 size = (uint16)_scrollTexts.size(); + if (size > 0) { + assert(_curScrollText < size); + ScrollTextEntry textEntry = _scrollTexts[_curScrollText]; + g_sci->_gfxText32->drawScrollTextBitmap(textEntry.kWindow, textEntry.bitmapHandle, textEntry.x, textEntry.y); + } } void GfxFrameout::kernelAddPlane(reg_t object) { @@ -673,6 +716,8 @@ void GfxFrameout::kernelFrameout() { } } + showCurrentScrollText(); + _screen->copyToScreen(); g_sci->getEngineState()->_throttleTrigger = true; diff --git a/engines/sci/graphics/frameout.h b/engines/sci/graphics/frameout.h index ec4de62c0a..2d2ca6546c 100644 --- a/engines/sci/graphics/frameout.h +++ b/engines/sci/graphics/frameout.h @@ -76,6 +76,15 @@ struct PlanePictureEntry { typedef Common::List<PlanePictureEntry> PlanePictureList; +struct ScrollTextEntry { + reg_t bitmapHandle; + reg_t kWindow; + uint16 x; + uint16 y; +}; + +typedef Common::Array<ScrollTextEntry> ScrollTextList; + class GfxCache; class GfxCoordAdjuster32; class GfxPaint32; @@ -104,6 +113,18 @@ public: void addPlanePicture(reg_t object, GuiResourceId pictureId, uint16 startX, uint16 startY = 0); void deletePlanePictures(reg_t object); void clear(); + + // Scroll text functions + void addScrollTextEntry(Common::String &text, reg_t kWindow, uint16 x, uint16 y, bool replace); + void showCurrentScrollText(); + void initScrollText(uint16 maxItems) { _maxScrollTexts = maxItems; } + void clearScrollTexts(); + void firstScrollText() { if (_scrollTexts.size() > 0) _curScrollText = 0; } + void lastScrollText() { if (_scrollTexts.size() > 0) _curScrollText = _scrollTexts.size() - 1; } + void prevScrollText() { if (_curScrollText > 0) _curScrollText--; } + void nextScrollText() { if (_curScrollText + 1 < (uint16)_scrollTexts.size()) _curScrollText++; } + void toggleScrollText(bool show) { _showScrollText = show; } + void printPlaneList(Console *con); void printPlaneItemList(Console *con, reg_t planeObject); @@ -127,6 +148,10 @@ private: FrameoutList _screenItems; PlaneList _planes; PlanePictureList _planePictures; + ScrollTextList _scrollTexts; + int16 _curScrollText; + bool _showScrollText; + uint16 _maxScrollTexts; void sortPlanes(); diff --git a/engines/sci/graphics/palette.cpp b/engines/sci/graphics/palette.cpp index 47d1647c6c..ea154c5037 100644 --- a/engines/sci/graphics/palette.cpp +++ b/engines/sci/graphics/palette.cpp @@ -698,7 +698,7 @@ void GfxPalette::palVaryInit() { } bool GfxPalette::palVaryLoadTargetPalette(GuiResourceId resourceId) { - _palVaryResourceId = resourceId; + _palVaryResourceId = (resourceId != 65535) ? resourceId : -1; Resource *palResource = _resMan->findResource(ResourceId(kResourceTypePalette, resourceId), false); if (palResource) { // Load and initialize destination palette diff --git a/engines/sci/graphics/text32.cpp b/engines/sci/graphics/text32.cpp index cd24ca5a99..8ac9582535 100644 --- a/engines/sci/graphics/text32.cpp +++ b/engines/sci/graphics/text32.cpp @@ -49,9 +49,12 @@ GfxText32::GfxText32(SegManager *segMan, GfxCache *fonts, GfxScreen *screen) GfxText32::~GfxText32() { } +reg_t GfxText32::createScrollTextBitmap(Common::String text, reg_t textObject, uint16 maxWidth, uint16 maxHeight, reg_t prevHunk) { + return createTextBitmapInternal(text, textObject, maxWidth, maxHeight, prevHunk); + +} reg_t GfxText32::createTextBitmap(reg_t textObject, uint16 maxWidth, uint16 maxHeight, reg_t prevHunk) { reg_t stringObject = readSelector(_segMan, textObject, SELECTOR(text)); - // The object in the text selector of the item can be either a raw string // or a Str object. In the latter case, we need to access the object's data // selector to get the raw string. @@ -59,6 +62,11 @@ reg_t GfxText32::createTextBitmap(reg_t textObject, uint16 maxWidth, uint16 maxH stringObject = readSelector(_segMan, stringObject, SELECTOR(data)); Common::String text = _segMan->getString(stringObject); + + return createTextBitmapInternal(text, textObject, maxWidth, maxHeight, prevHunk); +} + +reg_t GfxText32::createTextBitmapInternal(Common::String &text, reg_t textObject, uint16 maxWidth, uint16 maxHeight, reg_t prevHunk) { // HACK: The character offsets of the up and down arrow buttons are off by one // in GK1, for some unknown reason. Fix them here. if (text.size() == 1 && (text[0] == 29 || text[0] == 30)) { @@ -91,7 +99,11 @@ reg_t GfxText32::createTextBitmap(reg_t textObject, uint16 maxWidth, uint16 maxH reg_t memoryId = NULL_REG; if (prevHunk.isNull()) { memoryId = _segMan->allocateHunkEntry("TextBitmap()", entrySize); - writeSelector(_segMan, textObject, SELECTOR(bitmap), memoryId); + + // Scroll text objects have no bitmap selector! + ObjVarRef varp; + if (lookupSelector(_segMan, textObject, SELECTOR(bitmap), &varp, NULL) == kSelectorVariable) + writeSelector(_segMan, textObject, SELECTOR(bitmap), memoryId); } else { memoryId = prevHunk; } @@ -175,6 +187,24 @@ void GfxText32::disposeTextBitmap(reg_t hunkId) { void GfxText32::drawTextBitmap(int16 x, int16 y, Common::Rect planeRect, reg_t textObject) { reg_t hunkId = readSelector(_segMan, textObject, SELECTOR(bitmap)); + drawTextBitmapInternal(x, y, planeRect, textObject, hunkId); +} + +void GfxText32::drawScrollTextBitmap(reg_t textObject, reg_t hunkId, uint16 x, uint16 y) { + /*reg_t plane = readSelector(_segMan, textObject, SELECTOR(plane)); + Common::Rect planeRect; + planeRect.top = readSelectorValue(_segMan, plane, SELECTOR(top)); + planeRect.left = readSelectorValue(_segMan, plane, SELECTOR(left)); + planeRect.bottom = readSelectorValue(_segMan, plane, SELECTOR(bottom)); + planeRect.right = readSelectorValue(_segMan, plane, SELECTOR(right)); + + drawTextBitmapInternal(x, y, planeRect, textObject, hunkId);*/ + + // HACK: we pretty much ignore the plane rect and x, y... + drawTextBitmapInternal(0, 0, Common::Rect(20, 390, 600, 460), textObject, hunkId); +} + +void GfxText32::drawTextBitmapInternal(int16 x, int16 y, Common::Rect planeRect, reg_t textObject, reg_t hunkId) { uint16 backColor = readSelectorValue(_segMan, textObject, SELECTOR(back)); // Sanity check: Check if the hunk is set. If not, either the game scripts // didn't set it, or an old saved game has been loaded, where it wasn't set. @@ -188,8 +218,9 @@ void GfxText32::drawTextBitmap(int16 x, int16 y, Common::Rect planeRect, reg_t t byte *memoryPtr = _segMan->getHunkPointer(hunkId); if (!memoryPtr) { - // Happens when restoring in some SCI32 games - warning("Attempt to draw an invalid text bitmap"); + // Happens when restoring in some SCI32 games (e.g. SQ6). + // Commented out to reduce console spam + //warning("Attempt to draw an invalid text bitmap"); return; } diff --git a/engines/sci/graphics/text32.h b/engines/sci/graphics/text32.h index 3505de85eb..ce78003fdf 100644 --- a/engines/sci/graphics/text32.h +++ b/engines/sci/graphics/text32.h @@ -33,13 +33,17 @@ public: GfxText32(SegManager *segMan, GfxCache *fonts, GfxScreen *screen); ~GfxText32(); reg_t createTextBitmap(reg_t textObject, uint16 maxWidth = 0, uint16 maxHeight = 0, reg_t prevHunk = NULL_REG); - void disposeTextBitmap(reg_t hunkId); + reg_t createScrollTextBitmap(Common::String text, reg_t textObject, uint16 maxWidth = 0, uint16 maxHeight = 0, reg_t prevHunk = NULL_REG); void drawTextBitmap(int16 x, int16 y, Common::Rect planeRect, reg_t textObject); + void drawScrollTextBitmap(reg_t textObject, reg_t hunkId, uint16 x, uint16 y); + void disposeTextBitmap(reg_t hunkId); int16 GetLongest(const char *text, int16 maxWidth, GfxFont *font); void kernelTextSize(const char *text, int16 font, int16 maxWidth, int16 *textWidth, int16 *textHeight); private: + reg_t createTextBitmapInternal(Common::String &text, reg_t textObject, uint16 maxWidth, uint16 maxHeight, reg_t hunkId); + void drawTextBitmapInternal(int16 x, int16 y, Common::Rect planeRect, reg_t textObject, reg_t hunkId); int16 Size(Common::Rect &rect, const char *text, GuiResourceId fontId, int16 maxWidth); void Width(const char *text, int16 from, int16 len, GuiResourceId orgFontId, int16 &textWidth, int16 &textHeight, bool restoreFont); void StringWidth(const char *str, GuiResourceId orgFontId, int16 &textWidth, int16 &textHeight); diff --git a/engines/scumm/detection.cpp b/engines/scumm/detection.cpp index cd878b49ae..95de1a8706 100644 --- a/engines/scumm/detection.cpp +++ b/engines/scumm/detection.cpp @@ -1079,6 +1079,14 @@ Common::Error ScummMetaEngine::createInstance(OSystem *syst, Engine **engine) co debug(1, "Using MD5 '%s'", res.md5.c_str()); } + // We don't support the "Lite" version off puttzoo iOS because it contains + // the full game. + if (!strcmp(res.game.gameid, "puttzoo") && !strcmp(res.extra, "Lite")) { + GUIErrorMessage("The Lite version of Putt-Putt Saves the Zoo iOS is not supported to avoid piracy.\n" + "The full version is available for purchase from the iTunes Store."); + return Common::kUnsupportedGameidError; + } + // If the GUI options were updated, we catch this here and update them in the users config // file transparently. Common::updateGameGUIOptions(res.game.guioptions, getGameGUIOptionsDescriptionLanguage(res.language)); diff --git a/engines/scumm/detection_tables.h b/engines/scumm/detection_tables.h index 452a6f0960..be1b90e356 100644 --- a/engines/scumm/detection_tables.h +++ b/engines/scumm/detection_tables.h @@ -382,14 +382,16 @@ static const GameSettings gameVariantsTable[] = { {"pjgames", 0, 0, GID_HEGAME, 6, 100, MDT_NONE, GF_USE_KEY | GF_HE_LOCALIZED | GF_16BIT_COLOR, UNK, GUIO3(GUIO_NOLAUNCHLOAD, GUIO_NOMIDI, GUIO_NOASPECT)}, // Added the use of bink videos - {"Baseball2003", 0, 0, GID_BASEBALL2003, 6, 100, MDT_NONE, GF_USE_KEY | GF_16BIT_COLOR, UNK, GUIO3(GUIO_NOLAUNCHLOAD, GUIO_NOMIDI, GUIO_NOASPECT)}, - {"basketball", 0, 0, GID_BASKETBALL, 6, 100, MDT_NONE, GF_USE_KEY| GF_16BIT_COLOR, UNK, GUIO3(GUIO_NOLAUNCHLOAD, GUIO_NOMIDI, GUIO_NOASPECT)}, - {"football2002", 0, 0, GID_FOOTBALL, 6, 100, MDT_NONE, GF_USE_KEY | GF_16BIT_COLOR, UNK, GUIO3(GUIO_NOLAUNCHLOAD, GUIO_NOMIDI, GUIO_NOASPECT)}, {"Soccer2004", 0, 0, GID_SOCCER2004, 6, 100, MDT_NONE, GF_USE_KEY | GF_16BIT_COLOR, UNK, GUIO3(GUIO_NOLAUNCHLOAD, GUIO_NOMIDI, GUIO_NOASPECT)}, // U32 code required, for testing only {"moonbase", 0, 0, GID_MOONBASE, 6, 100, MDT_NONE, GF_USE_KEY | GF_16BIT_COLOR, UNK, GUIO3(GUIO_NOLAUNCHLOAD, GUIO_NOMIDI, GUIO_NOASPECT)}, {"moonbase", "Demo", 0, GID_MOONBASE, 6, 100, MDT_NONE, GF_USE_KEY | GF_16BIT_COLOR | GF_DEMO, UNK, GUIO3(GUIO_NOLAUNCHLOAD, GUIO_NOMIDI, GUIO_NOASPECT)}, + + // HE100 games, which use older o72_debugInput code + {"Baseball2003", 0, 0, GID_BASEBALL2003, 6, 101, MDT_NONE, GF_USE_KEY | GF_16BIT_COLOR, UNK, GUIO3(GUIO_NOLAUNCHLOAD, GUIO_NOMIDI, GUIO_NOASPECT)}, + {"basketball", 0, 0, GID_BASKETBALL, 6, 101, MDT_NONE, GF_USE_KEY| GF_16BIT_COLOR, UNK, GUIO3(GUIO_NOLAUNCHLOAD, GUIO_NOMIDI, GUIO_NOASPECT)}, + {"football2002", 0, 0, GID_FOOTBALL2002, 6, 101, MDT_NONE, GF_USE_KEY | GF_16BIT_COLOR, UNK, GUIO3(GUIO_NOLAUNCHLOAD, GUIO_NOMIDI, GUIO_NOASPECT)}, #endif // The following are meant to be generic HE game variants and as such do @@ -407,6 +409,7 @@ static const GameSettings gameVariantsTable[] = { {"", "HE 98.5", 0, GID_HEGAME, 6, 98, MDT_NONE, GF_USE_KEY | GF_HE_985, UNK, GUIO3(GUIO_NOLAUNCHLOAD, GUIO_NOMIDI, GUIO_NOASPECT)}, {"", "HE 99", 0, GID_HEGAME, 6, 99, MDT_NONE, GF_USE_KEY, UNK, GUIO3(GUIO_NOLAUNCHLOAD, GUIO_NOMIDI, GUIO_NOASPECT)}, {"", "HE 100", 0, GID_HEGAME, 6, 100, MDT_NONE, GF_USE_KEY, UNK, GUIO3(GUIO_NOLAUNCHLOAD, GUIO_NOMIDI, GUIO_NOASPECT)}, + {"", "HE 101", 0, GID_HEGAME, 6, 100, MDT_NONE, GF_USE_KEY, UNK, GUIO3(GUIO_NOLAUNCHLOAD, GUIO_NOMIDI, GUIO_NOASPECT)}, #endif {NULL, NULL, 0, 0, 0, MDT_NONE, 0, 0, UNK, 0} }; diff --git a/engines/scumm/he/intern_he.h b/engines/scumm/he/intern_he.h index cdc5faa084..fc5e4bcdf0 100644 --- a/engines/scumm/he/intern_he.h +++ b/engines/scumm/he/intern_he.h @@ -187,6 +187,8 @@ public: Wiz *_wiz; + virtual int setupStringArray(int size); + protected: virtual void setupOpcodes(); @@ -201,7 +203,6 @@ protected: virtual void clearDrawQueues(); int getStringCharWidth(byte chr); - virtual int setupStringArray(int size); void appendSubstring(int dst, int src, int len2, int len); void adjustRect(Common::Rect &rect); @@ -258,6 +259,9 @@ public: virtual void resetScumm(); + virtual byte *getStringAddress(ResId idx); + virtual int setupStringArray(int size); + protected: virtual void setupOpcodes(); @@ -265,7 +269,6 @@ protected: virtual void resetScummVars(); virtual void readArrayFromIndexFile(); - virtual byte *getStringAddress(ResId idx); virtual void readMAXS(int blockSize); virtual void redrawBGAreas(); @@ -280,7 +283,6 @@ protected: void copyArray(int array1, int a1_dim2start, int a1_dim2end, int a1_dim1start, int a1_dim1end, int array2, int a2_dim2start, int a2_dim2end, int a2_dim1start, int a2_dim1end); void copyArrayHelper(ArrayHeader *ah, int idx2, int idx1, int len1, byte **data, int *size, int *num); - virtual int setupStringArray(int size); int readFileToArray(int slot, int32 size); void writeFileFromArray(int slot, int32 resID); diff --git a/engines/scumm/he/logic/football.cpp b/engines/scumm/he/logic/football.cpp index f86f97eaf7..f67e07c475 100644 --- a/engines/scumm/he/logic/football.cpp +++ b/engines/scumm/he/logic/football.cpp @@ -20,6 +20,8 @@ * */ +#include "common/savefile.h" + #include "scumm/he/intern_he.h" #include "scumm/he/logic_he.h" @@ -35,16 +37,16 @@ public: LogicHEfootball(ScummEngine_v90he *vm) : LogicHE(vm) {} int versionID(); - int32 dispatch(int op, int numArgs, int32 *args); - -private: - int op_1004(int32 *args); - int op_1006(int32 *args); - int op_1007(int32 *args); - int op_1010(int32 *args); - int op_1022(int32 *args); - int op_1023(int32 *args); - int op_1024(int32 *args); + virtual int32 dispatch(int op, int numArgs, int32 *args); + +protected: + int lineEquation3D(int32 *args); + virtual int translateWorldToScreen(int32 *args); + int fieldGoalScreenTranslation(int32 *args); + virtual int translateScreenToWorld(int32 *args); + int nextPoint(int32 *args); + int computePlayerBallIntercepts(int32 *args); + int computeTwoCircleIntercepts(int32 *args); }; int LogicHEfootball::versionID() { @@ -56,31 +58,31 @@ int32 LogicHEfootball::dispatch(int op, int numArgs, int32 *args) { switch (op) { case 1004: - res = op_1004(args); + res = lineEquation3D(args); break; case 1006: - res = op_1006(args); + res = translateWorldToScreen(args); break; case 1007: - res = op_1007(args); + res = fieldGoalScreenTranslation(args); break; case 1010: - res = op_1010(args); + res = translateScreenToWorld(args); break; case 1022: - res = op_1022(args); + res = nextPoint(args); break; case 1023: - res = op_1023(args); + res = computePlayerBallIntercepts(args); break; case 1024: - res = op_1024(args); + res = computeTwoCircleIntercepts(args); break; case 8221968: @@ -123,8 +125,8 @@ int32 LogicHEfootball::dispatch(int op, int numArgs, int32 *args) { return res; } -int LogicHEfootball::op_1004(int32 *args) { - // Identical to LogicHEsoccer::op_1004 +int LogicHEfootball::lineEquation3D(int32 *args) { + // Identical to soccer's 1004 opcode double res, a2, a4, a5; a5 = ((double)args[4] - (double)args[1]) / ((double)args[5] - (double)args[2]); @@ -141,8 +143,8 @@ int LogicHEfootball::op_1004(int32 *args) { return 1; } -int LogicHEfootball::op_1006(int32 *args) { - // This seems to be more or less the inverse of op_1010 +int LogicHEfootball::translateWorldToScreen(int32 *args) { + // This is more or less the inverse of translateScreenToWorld const double a1 = args[1]; double res; @@ -167,7 +169,7 @@ int LogicHEfootball::op_1006(int32 *args) { return 1; } -int LogicHEfootball::op_1007(int32 *args) { +int LogicHEfootball::fieldGoalScreenTranslation(int32 *args) { double res, temp; temp = (double)args[1] * 0.32; @@ -188,8 +190,8 @@ int LogicHEfootball::op_1007(int32 *args) { return 1; } -int LogicHEfootball::op_1010(int32 *args) { - // This seems to be more or less the inverse of op_1006 +int LogicHEfootball::translateScreenToWorld(int32 *args) { + // This is more or less the inverse of translateWorldToScreen double a1 = (640.0 - (double)args[1] - 26.0) / 1.1588235e-1; // 2.9411764e-4 = 1/3400 @@ -205,7 +207,7 @@ int LogicHEfootball::op_1010(int32 *args) { return 1; } -int LogicHEfootball::op_1022(int32 *args) { +int LogicHEfootball::nextPoint(int32 *args) { double res; double var10 = args[4] - args[1]; double var8 = args[5] - args[2]; @@ -226,7 +228,7 @@ int LogicHEfootball::op_1022(int32 *args) { return 1; } -int LogicHEfootball::op_1023(int32 *args) { +int LogicHEfootball::computePlayerBallIntercepts(int32 *args) { double var10, var18, var20, var28, var30, var30_; double argf[7]; @@ -272,7 +274,8 @@ int LogicHEfootball::op_1023(int32 *args) { return 1; } -int LogicHEfootball::op_1024(int32 *args) { +int LogicHEfootball::computeTwoCircleIntercepts(int32 *args) { + // Looks like this was just dummied out writeScummVar(108, 0); writeScummVar(109, 0); writeScummVar(110, 0); @@ -281,8 +284,120 @@ int LogicHEfootball::op_1024(int32 *args) { return 1; } +class LogicHEfootball2002 : public LogicHEfootball { +public: + LogicHEfootball2002(ScummEngine_v90he *vm) : LogicHEfootball(vm) {} + + int32 dispatch(int op, int numArgs, int32 *args); + +private: + int translateWorldToScreen(int32 *args); + int translateScreenToWorld(int32 *args); + int getDayOfWeek(); + int initScreenTranslations(); + int getPlaybookFiles(int32 *args); + int largestFreeBlock(); +}; + +int32 LogicHEfootball2002::dispatch(int op, int numArgs, int32 *args) { + int32 res = 0; + + switch (op) { + case 1025: + res = getDayOfWeek(); + break; + + case 1026: + res = initScreenTranslations(); + break; + + case 1027: + res = getPlaybookFiles(args); + break; + + case 1028: + res = largestFreeBlock(); + break; + + case 1029: + // Clean-up off heap + // Dummied in the Windows U32 + res = 1; + break; + + case 1516: + // Start auto LAN game + break; + + default: + res = LogicHEfootball::dispatch(op, numArgs, args); + break; + } + + return res; +} + +int LogicHEfootball2002::translateWorldToScreen(int32 *args) { + // TODO: Implement modified 2002 version + return LogicHEfootball::translateWorldToScreen(args); +} + +int LogicHEfootball2002::translateScreenToWorld(int32 *args) { + // TODO: Implement modified 2002 version + return LogicHEfootball::translateScreenToWorld(args); +} + +int LogicHEfootball2002::getDayOfWeek() { + // TODO: Get day of week, store in var 108 + return 1; +} + +int LogicHEfootball2002::initScreenTranslations() { + // TODO: Set values used by translateWorldToScreen/translateScreenToWorld + return 1; +} + +int LogicHEfootball2002::getPlaybookFiles(int32 *args) { + // Get the pattern and then skip over the directory prefix ("*\" or "*:") + Common::String pattern = (const char *)_vm->getStringAddress(args[0] & ~0x33539000) + 2; + + // Prepare a buffer to hold the file names + char buffer[1000]; + buffer[0] = 0; + + // Get the list of file names that match the pattern and iterate over it + Common::StringArray fileList = _vm->getSaveFileManager()->listSavefiles(pattern); + + for (uint32 i = 0; i < fileList.size() && strlen(buffer) < 970; i++) { + // Isolate the base part of the filename and concatenate it to our buffer + Common::String fileName = Common::String(fileList[i].c_str(), fileList[i].size() - (pattern.size() - 1)); + strcat(buffer, fileName.c_str()); + strcat(buffer, ">"); // names separated by '>' + } + + // Now store the result in an array + int array = _vm->setupStringArray(strlen(buffer)); + strcpy((char *)_vm->getStringAddress(array), buffer); + + // And store the array index in variable 108 + writeScummVar(108, array); + + return 1; +} + +int LogicHEfootball2002::largestFreeBlock() { + // The Windows version always sets the variable to this + // The Mac version actually checks for the largest free block + writeScummVar(108, 100000000); + return 1; +} + LogicHE *makeLogicHEfootball(ScummEngine_v90he *vm) { return new LogicHEfootball(vm); } +LogicHE *makeLogicHEfootball2002(ScummEngine_v90he *vm) { + return new LogicHEfootball2002(vm); +} + } // End of namespace Scumm diff --git a/engines/scumm/he/logic_he.cpp b/engines/scumm/he/logic_he.cpp index a76c393e13..0f9454ba28 100644 --- a/engines/scumm/he/logic_he.cpp +++ b/engines/scumm/he/logic_he.cpp @@ -87,6 +87,9 @@ LogicHE *LogicHE::makeLogicHE(ScummEngine_v90he *vm) { case GID_FOOTBALL: return makeLogicHEfootball(vm); + case GID_FOOTBALL2002: + return makeLogicHEfootball2002(vm); + case GID_SOCCER: case GID_SOCCERMLS: case GID_SOCCER2004: diff --git a/engines/scumm/he/logic_he.h b/engines/scumm/he/logic_he.h index 893dc81b87..93c0569a4f 100644 --- a/engines/scumm/he/logic_he.h +++ b/engines/scumm/he/logic_he.h @@ -61,6 +61,7 @@ protected: LogicHE *makeLogicHErace(ScummEngine_v90he *vm); LogicHE *makeLogicHEfunshop(ScummEngine_v90he *vm); LogicHE *makeLogicHEfootball(ScummEngine_v90he *vm); +LogicHE *makeLogicHEfootball2002(ScummEngine_v90he *vm); LogicHE *makeLogicHEsoccer(ScummEngine_v90he *vm); LogicHE *makeLogicHEbaseball2001(ScummEngine_v90he *vm); LogicHE *makeLogicHEbasketball(ScummEngine_v90he *vm); diff --git a/engines/scumm/he/script_v100he.cpp b/engines/scumm/he/script_v100he.cpp index d2e01a6564..3e2053790e 100644 --- a/engines/scumm/he/script_v100he.cpp +++ b/engines/scumm/he/script_v100he.cpp @@ -2339,8 +2339,9 @@ void ScummEngine_v100he::o100_writeFile() { } void ScummEngine_v100he::o100_debugInput() { - // Backyard Basketball uses older code for this opcode - if (_game.id == GID_BASKETBALL) { + // Backyard Baseball 2003 / Basketball / Football 2002 + // use older o72_debugInput code + if (_game.heversion == 101) { ScummEngine_v72he::o72_debugInput(); return; } diff --git a/engines/scumm/he/sound_he.cpp b/engines/scumm/he/sound_he.cpp index 1007d2a7b0..f94b74ac45 100644 --- a/engines/scumm/he/sound_he.cpp +++ b/engines/scumm/he/sound_he.cpp @@ -65,7 +65,7 @@ void SoundHE::addSoundToQueue(int sound, int heOffset, int heChannel, int heFlag if (_vm->VAR_LAST_SOUND != 0xFF) _vm->VAR(_vm->VAR_LAST_SOUND) = sound; - if ((_vm->_game.heversion <= 99 && (heFlags & 16)) || (_vm->_game.heversion == 100 && (heFlags & 8))) { + if ((_vm->_game.heversion <= 99 && (heFlags & 16)) || (_vm->_game.heversion >= 100 && (heFlags & 8))) { playHESound(sound, heOffset, heChannel, heFlags); return; } else { diff --git a/engines/scumm/scumm-md5.h b/engines/scumm/scumm-md5.h index 946e954a46..9aac4a082f 100644 --- a/engines/scumm/scumm-md5.h +++ b/engines/scumm/scumm-md5.h @@ -1,5 +1,5 @@ /* - This file was generated by the md5table tool on Tue May 29 00:49:03 2012 + This file was generated by the md5table tool on Tue Jun 5 16:56:40 2012 DO NOT EDIT MANUALLY! */ @@ -215,6 +215,7 @@ static const MD5Table md5table[] = { { "4dbff3787aedcd96b0b325f2d92d7ad9", "maze", "HE 100", "Updated", -1, Common::EN_USA, Common::kPlatformUnknown }, { "4dc780f1bc587a193ce8a97652791438", "loom", "EGA", "EGA", -1, Common::EN_ANY, Common::kPlatformAmiga }, { "4e5867848ee61bc30d157e2c94eee9b4", "PuttTime", "HE 90", "Demo", 18394, Common::EN_USA, Common::kPlatformUnknown }, + { "4e859d3ef1e146b41e7d93c35cd6cc62", "puttzoo", "HE 100", "Lite", -1, Common::EN_ANY, Common::kPlatformIOS }, { "4edbf9d03550f7ba01e7f34d69b678dd", "spyfox", "HE 98.5", "Demo", -1, Common::NL_NLD, Common::kPlatformUnknown }, { "4f04b321a95d4315ce6d65f8e1dd0368", "maze", "HE 80", "", -1, Common::EN_USA, Common::kPlatformUnknown }, { "4f138ac6f9b2ac5a41bc68b2c3296064", "freddi4", "HE 99", "", -1, Common::FR_FRA, Common::kPlatformWindows }, diff --git a/engines/scumm/scumm.h b/engines/scumm/scumm.h index 8c0070b5f5..c8cf096a19 100644 --- a/engines/scumm/scumm.h +++ b/engines/scumm/scumm.h @@ -241,6 +241,7 @@ enum ScummGameId { GID_PUTTRACE, GID_FUNSHOP, // Used for all three funshops GID_FOOTBALL, + GID_FOOTBALL2002, GID_SOCCER, GID_SOCCERMLS, GID_SOCCER2004, diff --git a/engines/sword1/objectman.cpp b/engines/sword1/objectman.cpp index ed994a97fa..d0803590a7 100644 --- a/engines/sword1/objectman.cpp +++ b/engines/sword1/objectman.cpp @@ -105,7 +105,7 @@ char *ObjectMan::lockText(uint32 textId) { addr += sizeof(Header); if ((textId & ITM_ID) >= _resMan->readUint32(addr)) { warning("ObjectMan::lockText(%d): only %d texts in file", textId & ITM_ID, _resMan->readUint32(addr)); - textId = 0; // get first line instead + return _missingSubTitleStr; } uint32 offset = _resMan->readUint32(addr + ((textId & ITM_ID) + 1) * 4); if (offset == 0) { diff --git a/engines/sword1/text.cpp b/engines/sword1/text.cpp index 3bd2fdb2e6..f23ac5f182 100644 --- a/engines/sword1/text.cpp +++ b/engines/sword1/text.cpp @@ -156,6 +156,8 @@ uint16 Text::analyzeSentence(const uint8 *text, uint16 maxWidth, LineInfo *line) } uint16 Text::copyChar(uint8 ch, uint8 *sprPtr, uint16 sprWidth, uint8 pen) { + if (ch < SPACE) + ch = 64; FrameHeader *chFrame = _resMan->fetchFrame(_font, ch - SPACE); uint8 *chData = ((uint8 *)chFrame) + sizeof(FrameHeader); uint8 *dest = sprPtr; diff --git a/engines/tinsel/tinsel.h b/engines/tinsel/tinsel.h index 59344c44f4..bac7ef6efb 100644 --- a/engines/tinsel/tinsel.h +++ b/engines/tinsel/tinsel.h @@ -53,7 +53,6 @@ class Config; class MidiDriver; class MidiMusicPlayer; class PCMMusicPlayer; -class Scheduler; class SoundManager; typedef Common::List<Common::Rect> RectList; @@ -154,7 +153,6 @@ class TinselEngine : public Engine { Common::Point _mousePos; uint8 _dosPlayerDir; Console *_console; - Scheduler *_scheduler; static const char *const _sampleIndices[][3]; static const char *const _sampleFiles[][3]; diff --git a/video/coktel_decoder.cpp b/video/coktel_decoder.cpp index be36874db4..0c7ade1b8a 100644 --- a/video/coktel_decoder.cpp +++ b/video/coktel_decoder.cpp @@ -646,6 +646,21 @@ PreIMDDecoder::~PreIMDDecoder() { close(); } +bool PreIMDDecoder::reloadStream(Common::SeekableReadStream *stream) { + if (!_stream) + return false; + + if (!stream->seek(_stream->pos())) { + close(); + return false; + } + + delete _stream; + _stream = stream; + + return true; +} + bool PreIMDDecoder::seek(int32 frame, int whence, bool restart) { if (!evaluateSeekFrame(frame, whence)) return false; @@ -840,6 +855,21 @@ IMDDecoder::~IMDDecoder() { close(); } +bool IMDDecoder::reloadStream(Common::SeekableReadStream *stream) { + if (!_stream) + return false; + + if (!stream->seek(_stream->pos())) { + close(); + return false; + } + + delete _stream; + _stream = stream; + + return true; +} + bool IMDDecoder::seek(int32 frame, int whence, bool restart) { if (!evaluateSeekFrame(frame, whence)) return false; @@ -1536,6 +1566,21 @@ VMDDecoder::~VMDDecoder() { close(); } +bool VMDDecoder::reloadStream(Common::SeekableReadStream *stream) { + if (!_stream) + return false; + + if (!stream->seek(_stream->pos())) { + close(); + return false; + } + + delete _stream; + _stream = stream; + + return true; +} + bool VMDDecoder::seek(int32 frame, int whence, bool restart) { if (!evaluateSeekFrame(frame, whence)) return false; diff --git a/video/coktel_decoder.h b/video/coktel_decoder.h index 68696d5ff3..c88d982191 100644 --- a/video/coktel_decoder.h +++ b/video/coktel_decoder.h @@ -79,6 +79,9 @@ public: Audio::Mixer::SoundType soundType = Audio::Mixer::kPlainSoundType); ~CoktelDecoder(); + /** Replace the current video stream with this identical one. */ + virtual bool reloadStream(Common::SeekableReadStream *stream) = 0; + virtual bool seek(int32 frame, int whence = SEEK_SET, bool restart = false) = 0; /** Draw directly onto the specified video memory. */ @@ -237,6 +240,8 @@ public: Audio::Mixer::SoundType soundType = Audio::Mixer::kPlainSoundType); ~PreIMDDecoder(); + bool reloadStream(Common::SeekableReadStream *stream); + bool seek(int32 frame, int whence = SEEK_SET, bool restart = false); @@ -268,6 +273,8 @@ public: IMDDecoder(Audio::Mixer *mixer, Audio::Mixer::SoundType soundType = Audio::Mixer::kPlainSoundType); ~IMDDecoder(); + bool reloadStream(Common::SeekableReadStream *stream); + bool seek(int32 frame, int whence = SEEK_SET, bool restart = false); void setXY(uint16 x, uint16 y); @@ -364,6 +371,8 @@ public: VMDDecoder(Audio::Mixer *mixer, Audio::Mixer::SoundType soundType = Audio::Mixer::kPlainSoundType); ~VMDDecoder(); + bool reloadStream(Common::SeekableReadStream *stream); + bool seek(int32 frame, int whence = SEEK_SET, bool restart = false); void setXY(uint16 x, uint16 y); |