aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS3
-rw-r--r--common/coroutines.cpp183
-rw-r--r--common/coroutines.h308
-rw-r--r--devtools/scumm-md5.txt1
-rw-r--r--engines/agos/charset-fontdata.cpp90
-rw-r--r--engines/dreamweb/sound.cpp42
-rw-r--r--engines/dreamweb/sound.h3
-rw-r--r--engines/gob/aniobject.cpp30
-rw-r--r--engines/gob/aniobject.h15
-rw-r--r--engines/gob/cheater.h6
-rw-r--r--engines/gob/cheater_geisha.cpp11
-rw-r--r--engines/gob/inter_geisha.cpp2
-rw-r--r--engines/gob/inter_v1.cpp9
-rw-r--r--engines/gob/inter_v2.cpp4
-rw-r--r--engines/gob/minigames/geisha/diving.cpp8
-rw-r--r--engines/gob/minigames/geisha/evilfish.cpp2
-rw-r--r--engines/gob/minigames/geisha/evilfish.h2
-rw-r--r--engines/gob/minigames/geisha/meter.cpp22
-rw-r--r--engines/gob/minigames/geisha/meter.h8
-rw-r--r--engines/gob/minigames/geisha/mouth.cpp169
-rw-r--r--engines/gob/minigames/geisha/mouth.h75
-rw-r--r--engines/gob/minigames/geisha/penetration.cpp1386
-rw-r--r--engines/gob/minigames/geisha/penetration.h180
-rw-r--r--engines/gob/minigames/geisha/submarine.cpp256
-rw-r--r--engines/gob/minigames/geisha/submarine.h107
-rw-r--r--engines/gob/module.mk2
-rw-r--r--engines/gob/video.cpp1
-rw-r--r--engines/gob/videoplayer.cpp50
-rw-r--r--engines/gob/videoplayer.h7
-rw-r--r--engines/sci/engine/kernel_tables.h2
-rw-r--r--engines/sci/engine/kgraphics32.cpp128
-rw-r--r--engines/sci/event.cpp4
-rw-r--r--engines/sci/graphics/frameout.cpp45
-rw-r--r--engines/sci/graphics/frameout.h25
-rw-r--r--engines/sci/graphics/palette.cpp2
-rw-r--r--engines/sci/graphics/text32.cpp39
-rw-r--r--engines/sci/graphics/text32.h6
-rw-r--r--engines/scumm/detection.cpp8
-rw-r--r--engines/scumm/detection_tables.h9
-rw-r--r--engines/scumm/he/intern_he.h8
-rw-r--r--engines/scumm/he/logic/football.cpp169
-rw-r--r--engines/scumm/he/logic_he.cpp3
-rw-r--r--engines/scumm/he/logic_he.h1
-rw-r--r--engines/scumm/he/script_v100he.cpp5
-rw-r--r--engines/scumm/he/sound_he.cpp2
-rw-r--r--engines/scumm/scumm-md5.h3
-rw-r--r--engines/scumm/scumm.h1
-rw-r--r--engines/sword1/objectman.cpp2
-rw-r--r--engines/sword1/text.cpp2
-rw-r--r--engines/tinsel/tinsel.h2
-rw-r--r--video/coktel_decoder.cpp45
-rw-r--r--video/coktel_decoder.h9
52 files changed, 2984 insertions, 518 deletions
diff --git a/NEWS b/NEWS
index ed98cdc83f..7ce4f255dd 100644
--- a/NEWS
+++ b/NEWS
@@ -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 &params) {
void Inter_v1::o1_manageDataFile(OpFuncParams &params) {
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 &params) {
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);