2016-05-03 14:51:18 +02:00

2098 lines
57 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
/*
* This code is based on original Tony Tough source code
*
* Copyright (c) 1997-2003 Nayma Software
*/
#include "common/scummsys.h"
#include "common/file.h"
#include "common/savefile.h"
#include "common/system.h"
#include "tony/tony.h"
#include "tony/mpal/lzo.h"
#include "tony/mpal/mpal.h"
#include "tony/mpal/mpaldll.h"
namespace Tony {
namespace MPAL {
/****************************************************************************\
* Internal functions
\****************************************************************************/
/**
* Locks the variables for access
*/
void lockVar() {
GLOBALS._lpmvVars = (LpMpalVar)globalLock(GLOBALS._hVars);
}
/**
* Unlocks variables after use
*/
void unlockVar() {
globalUnlock(GLOBALS._hVars);
}
/**
* Locks the messages for access
*/
static void LockMsg() {
#ifdef NEED_LOCK_MSGS
GLOBALS._lpmmMsgs = (LpMpalMsg)globalLock(GLOBALS._hMsgs);
#endif
}
/**
* Unlocks the messages after use
*/
static void UnlockMsg() {
#ifdef NEED_LOCK_MSGS
globalUnlock(GLOBALS._hMsgs);
#endif
}
/**
* Locks the dialogs for access
*/
static void lockDialogs() {
GLOBALS._lpmdDialogs = (LpMpalDialog)globalLock(GLOBALS._hDialogs);
}
/**
* Unlocks the dialogs after use
*/
static void unlockDialogs() {
globalUnlock(GLOBALS._hDialogs);
}
/**
* Locks the location data structures for access
*/
static void lockLocations() {
GLOBALS._lpmlLocations = (LpMpalLocation)globalLock(GLOBALS._hLocations);
}
/**
* Unlocks the location structures after use
*/
static void unlockLocations() {
globalUnlock(GLOBALS._hLocations);
}
/**
* Locks the items structures for use
*/
static void lockItems() {
GLOBALS._lpmiItems = (LpMpalItem)globalLock(GLOBALS._hItems);
}
/**
* Unlocks the items structures after use
*/
static void unlockItems() {
globalUnlock(GLOBALS._hItems);
}
/**
* Locks the script data structures for use
*/
static void LockScripts() {
GLOBALS._lpmsScripts = (LpMpalScript)globalLock(GLOBALS._hScripts);
}
/**
* Unlocks the script data structures after use
*/
static void unlockScripts() {
globalUnlock(GLOBALS._hScripts);
}
/**
* Returns the current value of a global variable
*
* @param lpszVarName Name of the variable
* @returns Current value
* @remarks Before using this method, you must call lockVar() to
* lock the global variablves for use. Then afterwards, you will
* need to remember to call UnlockVar()
*/
int32 varGetValue(const char *lpszVarName) {
LpMpalVar v = GLOBALS._lpmvVars;
for (int i = 0; i < GLOBALS._nVars; v++, i++)
if (strcmp(lpszVarName, v->_lpszVarName) == 0)
return v->_dwVal;
GLOBALS._mpalError = 1;
return 0;
}
/**
* Sets the value of a MPAL global variable
* @param lpszVarName Name of the variable
* @param val Value to set
*/
void varSetValue(const char *lpszVarName, int32 val) {
LpMpalVar v = GLOBALS._lpmvVars;
for (uint i = 0; i < GLOBALS._nVars; v++, i++)
if (strcmp(lpszVarName, v->_lpszVarName) == 0) {
v->_dwVal = val;
if (GLOBALS._lpiifCustom != NULL && strncmp(v->_lpszVarName, "Pattern.", 8) == 0) {
i = 0;
sscanf(v->_lpszVarName, "Pattern.%u", &i);
GLOBALS._lpiifCustom(i, val, -1);
} else if (GLOBALS._lpiifCustom != NULL && strncmp(v->_lpszVarName, "Status.", 7) == 0) {
i = 0;
sscanf(v->_lpszVarName,"Status.%u", &i);
GLOBALS._lpiifCustom(i, -1, val);
}
return;
}
GLOBALS._mpalError = 1;
return;
}
/**
* Find the index of a location within the location array. Remember to call LockLoc() beforehand.
*
* @param nLoc Location number to search for
* @returns Index, or -1 if the location is not present
* @remarks This function requires the location list to have
* first been locked with a call to LockLoc().
*/
static int locGetOrderFromNum(uint32 nLoc) {
LpMpalLocation loc = GLOBALS._lpmlLocations;
for (int i = 0; i < GLOBALS._nLocations; i++, loc++)
if (loc->_nObj == nLoc)
return i;
return -1;
}
/**
* Find the index of a message within the messages array
* @param nMsg Message number to search for
* @returns Index, or -1 if the message is not present
* @remarks This function requires the message list to have
* first been locked with a call to LockMsg()
*/
static int msgGetOrderFromNum(uint32 nMsg) {
LpMpalMsg msg = GLOBALS._lpmmMsgs;
for (int i = 0; i < GLOBALS._nMsgs; i++, msg++) {
if (msg->_wNum == nMsg)
return i;
}
return -1;
}
/**
* Find the index of an item within the items array
* @param nItem Item number to search for
* @returns Index, or -1 if the item is not present
* @remarks This function requires the item list to have
* first been locked with a call to LockItems()
*/
static int itemGetOrderFromNum(uint32 nItem) {
LpMpalItem item = GLOBALS._lpmiItems;
for (int i = 0; i < GLOBALS._nItems; i++, item++) {
if (item->_nObj == nItem)
return i;
}
return -1;
}
/**
* Find the index of a script within the scripts array
* @param nScript Script number to search for
* @returns Index, or -1 if the script is not present
* @remarks This function requires the script list to have
* first been locked with a call to LockScripts()
*/
static int scriptGetOrderFromNum(uint32 nScript) {
LpMpalScript script = GLOBALS._lpmsScripts;
for (int i = 0; i < GLOBALS._nScripts; i++, script++) {
if (script->_nObj == nScript)
return i;
}
return -1;
}
/**
* Find the index of a dialog within the dialogs array
* @param nDialog Dialog number to search for
* @returns Index, or -1 if the dialog is not present
* @remarks This function requires the dialog list to have
* first been locked with a call to LockDialogs()
*/
static int dialogGetOrderFromNum(uint32 nDialog) {
LpMpalDialog dialog = GLOBALS._lpmdDialogs;
for (int i = 0; i < GLOBALS._nDialogs; i++, dialog++) {
if (dialog->_nObj == nDialog)
return i;
}
return -1;
}
/**
* Duplicates a message
* @param nMsgOrd Index of the message inside the messages array
* @returns Pointer to the duplicated message.
* @remarks Remember to free the duplicated message when done with it.
*/
static char *DuplicateMessage(uint32 nMsgOrd) {
const char *origmsg;
char *clonemsg;
if (nMsgOrd == (uint32)-1)
return NULL;
origmsg = (const char *)globalLock(GLOBALS._lpmmMsgs[nMsgOrd]._hText);
int j = 0;
while (origmsg[j] != '\0' || origmsg[j + 1] != '\0')
j++;
j += 2;
clonemsg = (char *)globalAlloc(GMEM_FIXED | GMEM_ZEROINIT, j);
if (clonemsg == NULL)
return NULL;
memcpy(clonemsg, origmsg, j);
globalUnlock(GLOBALS._lpmmMsgs[nMsgOrd]._hText);
return clonemsg;
}
/**
* Duplicate a sentence of a dialog
* @param nDlgOrd Index of the dialog in the dialogs array
* @param nPeriod Sentence number to be duplicated.
* @returns Pointer to the duplicated phrase. Remember to free it
* when done with it.
*/
static char *duplicateDialogPeriod(uint32 nPeriod) {
const char *origmsg;
char *clonemsg;
LpMpalDialog dialog = GLOBALS._lpmdDialogs + GLOBALS._nExecutingDialog;
for (int j = 0; dialog->_periods[j] != NULL; j++) {
if (dialog->_periodNums[j] == nPeriod) {
// Found the phrase, it should be duplicated
origmsg = (const char *)globalLock(dialog->_periods[j]);
// Calculate the length and allocate memory
int i = 0;
while (origmsg[i] != '\0')
i++;
clonemsg = (char *)globalAlloc(GMEM_FIXED | GMEM_ZEROINIT, i + 1);
if (clonemsg == NULL)
return NULL;
memcpy(clonemsg, origmsg, i);
globalUnlock(dialog->_periods[j]);
return clonemsg;
}
}
return NULL;
}
/**
* Load a resource from the MPR file
*
* @param dwId ID of the resource to load
* @returns Handle to the loaded resource
*/
MpalHandle resLoad(uint32 dwId) {
MpalHandle h;
char head[4];
byte *temp, *buf;
for (int i = 0; i < GLOBALS._nResources; i++)
if (GLOBALS._lpResources[i * 2] == dwId) {
GLOBALS._hMpr.seek(GLOBALS._lpResources[i * 2 + 1]);
uint32 nBytesRead = GLOBALS._hMpr.read(head, 4);
if (nBytesRead != 4)
return NULL;
if (head[0] != 'R' || head[1] != 'E' || head[2] != 'S' || head[3] != 'D')
return NULL;
uint32 nSizeDecomp = GLOBALS._hMpr.readUint32LE();
if (GLOBALS._hMpr.err())
return NULL;
uint32 nSizeComp = GLOBALS._hMpr.readUint32LE();
if (GLOBALS._hMpr.err())
return NULL;
h = globalAllocate(GMEM_MOVEABLE | GMEM_ZEROINIT, nSizeDecomp + (nSizeDecomp / 1024) * 16);
buf = (byte *)globalLock(h);
temp = (byte *)globalAlloc(GMEM_FIXED | GMEM_ZEROINIT, nSizeComp);
nBytesRead = GLOBALS._hMpr.read(temp, nSizeComp);
if (nBytesRead != nSizeComp) {
globalDestroy(temp);
globalDestroy(h);
return NULL;
}
lzo1x_decompress(temp, nSizeComp, buf, &nBytesRead);
if (nBytesRead != nSizeDecomp) {
globalDestroy(temp);
globalDestroy(h);
return NULL;
}
globalDestroy(temp);
globalUnlock(h);
return h;
}
return NULL;
}
static uint32 *getSelectList(uint32 i) {
uint32 *sl;
LpMpalDialog dialog = GLOBALS._lpmdDialogs + GLOBALS._nExecutingDialog;
// Count how many are active selects
int num = 0;
for (int j = 0; dialog->_choice[i]._select[j]._dwData != 0; j++) {
if (dialog->_choice[i]._select[j]._curActive)
num++;
}
// If there are 0, it's a mistake
if (num == 0)
return NULL;
sl = (uint32 *)globalAlloc(GMEM_FIXED | GMEM_ZEROINIT, sizeof(uint32) * (num + 1));
if (sl == NULL)
return NULL;
// Copy all the data inside the active select list
int k = 0;
for (int j = 0; dialog->_choice[i]._select[j]._dwData != 0; j++) {
if (dialog->_choice[i]._select[j]._curActive)
sl[k++] = dialog->_choice[i]._select[j]._dwData;
}
sl[k] = 0;
return sl;
}
static uint32 *GetItemList(uint32 nLoc) {
uint32 *il;
LpMpalVar v = GLOBALS._lpmvVars;
uint32 num = 0;
for (uint32 i = 0; i < GLOBALS._nVars; i++, v++) {
if (strncmp(v->_lpszVarName, "Location", 8) == 0 && v->_dwVal == nLoc)
num++;
}
il = (uint32 *)globalAlloc(GMEM_FIXED | GMEM_ZEROINIT, sizeof(uint32) * (num + 1));
if (il == NULL)
return NULL;
v = GLOBALS._lpmvVars;
uint32 j = 0;
for (uint32 i = 0; i < GLOBALS._nVars; i++, v++) {
if (strncmp(v->_lpszVarName, "Location", 8) == 0 && v->_dwVal == nLoc) {
sscanf(v->_lpszVarName, "Location.%u", &il[j]);
j++;
}
}
il[j] = 0;
return il;
}
static LpItem getItemData(uint32 nOrdItem) {
LpMpalItem curitem = GLOBALS._lpmiItems + nOrdItem;
char *dat;
char *patlength;
// Zeroing out the allocated memory is required!!!
LpItem ret = (LpItem)globalAlloc(GMEM_FIXED | GMEM_ZEROINIT, sizeof(Item));
if (ret == NULL)
return NULL;
ret->_speed = 150;
MpalHandle hDat = resLoad(curitem->_dwRes);
dat = (char *)globalLock(hDat);
if (dat[0] == 'D' && dat[1] == 'A' && dat[2] == 'T') {
int i = dat[3]; // For version 1.0!!
dat += 4;
if (i >= 0x10) { // From 1.0, there's a destination point for each object
ret->_destX = (int16)READ_LE_UINT16(dat);
ret->_destY = (int16)READ_LE_UINT16(dat + 2);
dat += 4;
}
if (i >= 0x11) { // From 1.1, there's animation speed
ret->_speed = READ_LE_UINT16(dat);
dat += 2;
} else
ret->_speed = 150;
}
ret->_numframe = *dat++;
ret->_numpattern = *dat++;
ret->_destZ = *dat++;
// Upload the left & top co-ordinates of each frame
for (int i = 0; i < ret->_numframe; i++) {
ret->_frameslocations[i].left = (int16)READ_LE_UINT16(dat);
ret->_frameslocations[i].top = (int16)READ_LE_UINT16(dat + 2);
dat += 4;
}
// Upload the size of each frame and calculate the right & bottom
for (int i = 0; i < ret->_numframe; i++) {
ret->_frameslocations[i].right = (int16)READ_LE_UINT16(dat) + ret->_frameslocations[i].left;
ret->_frameslocations[i].bottom = (int16)READ_LE_UINT16(dat + 2) + ret->_frameslocations[i].top;
dat += 4;
}
// Upload the bounding boxes of each frame
for (int i = 0; i < ret->_numframe; i++) {
ret->_bbox[i].left = (int16)READ_LE_UINT16(dat);
ret->_bbox[i].top = (int16)READ_LE_UINT16(dat + 2);
ret->_bbox[i].right = (int16)READ_LE_UINT16(dat + 4);
ret->_bbox[i].bottom = (int16)READ_LE_UINT16(dat + 6);
dat += 8;
}
// Load the animation pattern
patlength = dat;
dat += ret->_numpattern;
for (int i = 1; i < ret->_numpattern; i++) {
for (int j = 0; j < patlength[i]; j++)
ret->_pattern[i][j] = dat[j];
ret->_pattern[i][(int)patlength[i]] = 255; // Terminate pattern
dat += patlength[i];
}
// Upload the individual frames of animations
for (int i = 1; i < ret->_numframe; i++) {
uint32 dim = (uint32)(ret->_frameslocations[i].right - ret->_frameslocations[i].left) *
(uint32)(ret->_frameslocations[i].bottom - ret->_frameslocations[i].top);
ret->_frames[i] = (char *)globalAlloc(GMEM_FIXED, dim);
if (ret->_frames[i] == NULL)
return NULL;
memcpy(ret->_frames[i], dat, dim);
dat += dim;
}
int i = READ_LE_UINT16(dat);
globalUnlock(hDat);
globalFree(hDat);
// Check if we've got to the end of the file
if (i != 0xABCD) {
globalDestroy(ret);
return NULL;
}
return ret;
}
/**
* Thread that calls a custom function. It is used in scripts, so that each script
* function is executed without delaying the others.
*
* @param param pointer to a pointer to the structure that defines the call.
* @remarks The passed structure is freed when the process finishes.
*/
void CustomThread(CORO_PARAM, const void *param) {
CORO_BEGIN_CONTEXT;
LpCfCall p;
CORO_END_CONTEXT(_ctx);
CORO_BEGIN_CODE(_ctx);
_ctx->p = *(const LpCfCall *)param;
CORO_INVOKE_4(GLOBALS._lplpFunctions[_ctx->p->_nCf], _ctx->p->_arg1, _ctx->p->_arg2, _ctx->p->_arg3, _ctx->p->_arg4);
globalFree(_ctx->p);
CORO_END_CODE;
}
/**
* Main process for running a script.
*
* @param param Pointer to a pointer to a structure containing the script data.
* @remarks The passed structure is freed when the process finishes.
*/
void ScriptThread(CORO_PARAM, const void *param) {
CORO_BEGIN_CONTEXT;
uint i, j, k;
uint32 dwStartTime;
uint32 dwCurTime;
uint32 dwId;
int numHandles;
LpCfCall p;
CORO_END_CONTEXT(_ctx);
static uint32 cfHandles[MAX_COMMANDS_PER_MOMENT];
LpMpalScript s = *(const LpMpalScript *)param;
CORO_BEGIN_CODE(_ctx);
_ctx->dwStartTime = g_vm->getTime();
_ctx->numHandles = 0;
//debugC(DEBUG_BASIC, kTonyDebugMPAL, "PlayScript(): Moments: %u\n", s->_nMoments);
for (_ctx->i = 0; _ctx->i < s->_nMoments; _ctx->i++) {
// Sleep for the required time
if (s->_moment[_ctx->i]._dwTime == -1) {
CORO_INVOKE_4(CoroScheduler.waitForMultipleObjects, _ctx->numHandles, cfHandles, true, CORO_INFINITE);
_ctx->dwStartTime = g_vm->getTime();
} else {
_ctx->dwCurTime = g_vm->getTime();
if (_ctx->dwCurTime < _ctx->dwStartTime + (s->_moment[_ctx->i]._dwTime * 100)) {
//debugC(DEBUG_BASIC, kTonyDebugMPAL, "PlayScript(): Sleeping %lums\n",_ctx->dwStartTime + (s->_moment[_ctx->i]._dwTime*100) - _ctx->dwCurTime);
CORO_INVOKE_1(CoroScheduler.sleep, _ctx->dwStartTime + (s->_moment[_ctx->i]._dwTime * 100) - _ctx->dwCurTime);
}
}
_ctx->numHandles = 0;
for (_ctx->j = 0; _ctx->j < s->_moment[_ctx->i]._nCmds; _ctx->j++) {
_ctx->k = s->_moment[_ctx->i]._cmdNum[_ctx->j];
if (s->_command[_ctx->k]._type == 1) {
_ctx->p = (LpCfCall)globalAlloc(GMEM_FIXED, sizeof(CfCall));
if (_ctx->p == NULL) {
GLOBALS._mpalError = 1;
CORO_KILL_SELF();
return;
}
_ctx->p->_nCf = s->_command[_ctx->k]._nCf;
_ctx->p->_arg1 = s->_command[_ctx->k]._arg1;
_ctx->p->_arg2 = s->_command[_ctx->k]._arg2;
_ctx->p->_arg3 = s->_command[_ctx->k]._arg3;
_ctx->p->_arg4 = s->_command[_ctx->k]._arg4;
// !!! New process management
if ((cfHandles[_ctx->numHandles++] = CoroScheduler.createProcess(CustomThread, &_ctx->p, sizeof(LpCfCall))) == 0) {
GLOBALS._mpalError = 1;
CORO_KILL_SELF();
return;
}
} else if (s->_command[_ctx->k]._type == 2) {
lockVar();
varSetValue(
s->_command[_ctx->k]._lpszVarName,
evaluateExpression(s->_command[_ctx->k]._expr)
);
unlockVar();
} else {
GLOBALS._mpalError = 1;
globalFree(s);
CORO_KILL_SELF();
return;
}
// WORKAROUND: Wait for events to pulse.
CORO_SLEEP(1);
}
}
globalFree(s);
CORO_KILL_SELF();
CORO_END_CODE;
}
/**
* Thread that performs an action on an item. the thread always executes the action,
* so it should create a new item in which the action is the one required.
* Furthermore, the expression is not checked, but it is always performed the action.
*
* @param param Pointer to a pointer to a structure containing the action.
*/
void ActionThread(CORO_PARAM, const void *param) {
// COROUTINE
CORO_BEGIN_CONTEXT;
int j, k;
LpMpalItem item;
~CoroContextTag() {
if (item)
globalDestroy(item);
}
CORO_END_CONTEXT(_ctx);
CORO_BEGIN_CODE(_ctx);
// The ActionThread owns the data block pointed to, so we need to make sure it's
// freed when the process exits
_ctx->item = *(const LpMpalItem *)param;
GLOBALS._mpalError = 0;
for (_ctx->j = 0; _ctx->j < _ctx->item->_action[_ctx->item->_dwRes]._nCmds; _ctx->j++) {
_ctx->k = _ctx->item->_action[_ctx->item->_dwRes]._cmdNum[_ctx->j];
if (_ctx->item->_command[_ctx->k]._type == 1) {
// Custom function
debugC(DEBUG_DETAILED, kTonyDebugActions, "Action Process %d Call=%s params=%d,%d,%d,%d",
CoroScheduler.getCurrentPID(), GLOBALS._lplpFunctionStrings[_ctx->item->_command[_ctx->k]._nCf].c_str(),
_ctx->item->_command[_ctx->k]._arg1, _ctx->item->_command[_ctx->k]._arg2,
_ctx->item->_command[_ctx->k]._arg3, _ctx->item->_command[_ctx->k]._arg4
);
CORO_INVOKE_4(GLOBALS._lplpFunctions[_ctx->item->_command[_ctx->k]._nCf],
_ctx->item->_command[_ctx->k]._arg1,
_ctx->item->_command[_ctx->k]._arg2,
_ctx->item->_command[_ctx->k]._arg3,
_ctx->item->_command[_ctx->k]._arg4
);
} else if (_ctx->item->_command[_ctx->k]._type == 2) {
// Variable assign
debugC(DEBUG_DETAILED, kTonyDebugActions, "Action Process %d Variable=%s",
CoroScheduler.getCurrentPID(), _ctx->item->_command[_ctx->k]._lpszVarName);
lockVar();
varSetValue(_ctx->item->_command[_ctx->k]._lpszVarName, evaluateExpression(_ctx->item->_command[_ctx->k]._expr));
unlockVar();
} else {
GLOBALS._mpalError = 1;
break;
}
// WORKAROUND: Wait for events to pulse.
CORO_SLEEP(1);
}
// WORKAROUND: User interface sometimes remaining disabled after capturing guard on Ferris wheel
if (_ctx->item->_nObj == 3601 && _ctx->item->_dwRes == 9)
g_vm->getEngine()->enableInput();
globalDestroy(_ctx->item);
_ctx->item = NULL;
debugC(DEBUG_DETAILED, kTonyDebugActions, "Action Process %d ended", CoroScheduler.getCurrentPID());
CORO_END_CODE;
}
/**
* This thread monitors a created action to detect when it ends.
* @remarks Since actions can spawn sub-actions, this needs to be a
* separate thread to determine when the outer action is done
*/
void ShutUpActionThread(CORO_PARAM, const void *param) {
// COROUTINE
CORO_BEGIN_CONTEXT;
int slotNumber;
CORO_END_CONTEXT(_ctx);
uint32 pid = *(const uint32 *)param;
CORO_BEGIN_CODE(_ctx);
CORO_INVOKE_2(CoroScheduler.waitForSingleObject, pid, CORO_INFINITE);
GLOBALS._bExecutingAction = false;
if (g_vm->_initialLoadSlotNumber != -1) {
_ctx->slotNumber = g_vm->_initialLoadSlotNumber;
g_vm->_initialLoadSlotNumber = -1;
CORO_INVOKE_1(g_vm->loadState, _ctx->slotNumber);
}
CORO_END_CODE;
}
/**
* Polls one location (starting point of a process)
*
* @param param Pointer to an index in the array of polling locations.
*/
void LocationPollThread(CORO_PARAM, const void *param) {
typedef struct {
uint32 _nItem, _nAction;
uint16 _wTime;
byte _perc;
MpalHandle _when;
byte _nCmds;
uint16 _cmdNum[MAX_COMMANDS_PER_ACTION];
uint32 _dwLastTime;
} MYACTION;
typedef struct {
uint32 _nItem;
uint32 _hThread;
} MYTHREAD;
CORO_BEGIN_CONTEXT;
uint32 *il;
int i, j, k;
int numitems;
int nRealItems;
LpMpalItem curItem, newItem;
int nIdleActions;
uint32 curTime;
uint32 dwSleepTime;
uint32 dwId;
int ord;
bool delayExpired;
bool expired;
MYACTION *myActions;
MYTHREAD *myThreads;
~CoroContextTag() {
// Free data blocks
if (myThreads)
globalDestroy(myThreads);
if (myActions)
globalDestroy(myActions);
}
CORO_END_CONTEXT(_ctx);
uint32 id = *((const uint32 *)param);
CORO_BEGIN_CODE(_ctx);
// Initialize data pointers
_ctx->myActions = NULL;
_ctx->myThreads = NULL;
// To begin with, we need to request the item list from the location
_ctx->il = mpalQueryItemList(GLOBALS._nPollingLocations[id]);
// Count the items
for (_ctx->numitems = 0; _ctx->il[_ctx->numitems] != 0; _ctx->numitems++)
;
// We look for items without idle actions, and eliminate them from the list
lockItems();
_ctx->nIdleActions = 0;
_ctx->nRealItems = 0;
for (_ctx->i = 0; _ctx->i < _ctx->numitems; _ctx->i++) {
_ctx->ord = itemGetOrderFromNum(_ctx->il[_ctx->i]);
if (_ctx->ord == -1)
continue;
_ctx->curItem = GLOBALS._lpmiItems + _ctx->ord;
_ctx->k = 0;
for (_ctx->j = 0; _ctx->j < _ctx->curItem->_nActions; _ctx->j++) {
if (_ctx->curItem->_action[_ctx->j]._num == 0xFF)
_ctx->k++;
}
_ctx->nIdleActions += _ctx->k;
if (_ctx->k == 0)
// We can remove this item from the list
_ctx->il[_ctx->i] = 0;
else
_ctx->nRealItems++;
}
unlockItems();
// If there is nothing left, we can exit
if (_ctx->nRealItems == 0) {
globalDestroy(_ctx->il);
CORO_KILL_SELF();
return;
}
_ctx->myThreads = (MYTHREAD *)globalAlloc(GMEM_FIXED | GMEM_ZEROINIT, _ctx->nRealItems * sizeof(MYTHREAD));
if (_ctx->myThreads == NULL) {
globalDestroy(_ctx->il);
CORO_KILL_SELF();
return;
}
// We have established that there is at least one item that contains idle actions.
// Now we created the mirrored copies of the idle actions.
_ctx->myActions = (MYACTION *)globalAlloc(GMEM_FIXED | GMEM_ZEROINIT, _ctx->nIdleActions * sizeof(MYACTION));
if (_ctx->myActions == NULL) {
globalDestroy(_ctx->myThreads);
globalDestroy(_ctx->il);
CORO_KILL_SELF();
return;
}
lockItems();
_ctx->k = 0;
for (_ctx->i = 0; _ctx->i < _ctx->numitems; _ctx->i++) {
if (_ctx->il[_ctx->i] == 0)
continue;
_ctx->curItem = GLOBALS._lpmiItems + itemGetOrderFromNum(_ctx->il[_ctx->i]);
for (_ctx->j = 0; _ctx->j < _ctx->curItem->_nActions; _ctx->j++) {
if (_ctx->curItem->_action[_ctx->j]._num == 0xFF) {
_ctx->myActions[_ctx->k]._nItem = _ctx->il[_ctx->i];
_ctx->myActions[_ctx->k]._nAction = _ctx->j;
_ctx->myActions[_ctx->k]._wTime = _ctx->curItem->_action[_ctx->j]._wTime;
_ctx->myActions[_ctx->k]._perc = _ctx->curItem->_action[_ctx->j]._perc;
_ctx->myActions[_ctx->k]._when = _ctx->curItem->_action[_ctx->j]._when;
_ctx->myActions[_ctx->k]._nCmds = _ctx->curItem->_action[_ctx->j]._nCmds;
memcpy(_ctx->myActions[_ctx->k]._cmdNum, _ctx->curItem->_action[_ctx->j]._cmdNum,
MAX_COMMANDS_PER_ACTION * sizeof(uint16));
_ctx->myActions[_ctx->k]._dwLastTime = g_vm->getTime();
_ctx->k++;
}
}
}
unlockItems();
// We don't need the item list anymore
globalDestroy(_ctx->il);
// Here's the main loop
while (1) {
// Searching for idle actions requiring time to execute
_ctx->curTime = g_vm->getTime();
_ctx->dwSleepTime = (uint32)-1L;
for (_ctx->k = 0;_ctx->k<_ctx->nIdleActions;_ctx->k++) {
if (_ctx->curTime >= _ctx->myActions[_ctx->k]._dwLastTime + _ctx->myActions[_ctx->k]._wTime) {
_ctx->dwSleepTime = 0;
break;
} else
_ctx->dwSleepTime = MIN(_ctx->dwSleepTime, _ctx->myActions[_ctx->k]._dwLastTime + _ctx->myActions[_ctx->k]._wTime - _ctx->curTime);
}
// We fall alseep, but always checking that the event is set when prompted for closure
CORO_INVOKE_3(CoroScheduler.waitForSingleObject, GLOBALS._hEndPollingLocations[id], _ctx->dwSleepTime, &_ctx->expired);
//if (_ctx->k == WAIT_OBJECT_0)
if (!_ctx->expired)
break;
for (_ctx->i = 0; _ctx->i < _ctx->nRealItems; _ctx->i++) {
if (_ctx->myThreads[_ctx->i]._nItem != 0) {
CORO_INVOKE_3(CoroScheduler.waitForSingleObject, _ctx->myThreads[_ctx->i]._hThread, 0, &_ctx->delayExpired);
// if result == WAIT_OBJECT_0)
if (!_ctx->delayExpired)
_ctx->myThreads[_ctx->i]._nItem = 0;
}
}
_ctx->curTime = g_vm->getTime();
// Loop through all the necessary idle actions
for (_ctx->k = 0; _ctx->k < _ctx->nIdleActions; _ctx->k++) {
if (_ctx->curTime >= _ctx->myActions[_ctx->k]._dwLastTime + _ctx->myActions[_ctx->k]._wTime) {
_ctx->myActions[_ctx->k]._dwLastTime += _ctx->myActions[_ctx->k]._wTime;
// It's time to check to see if fortune is on the side of the idle action
byte randomVal = (byte)g_vm->_randomSource.getRandomNumber(99);
if (randomVal < _ctx->myActions[_ctx->k]._perc) {
// Check if there is an action running on the item
if ((GLOBALS._bExecutingAction) && (GLOBALS._nExecutingAction == _ctx->myActions[_ctx->k]._nItem))
continue;
// Check to see if there already another idle funning running on the item
for (_ctx->i = 0; _ctx->i < _ctx->nRealItems; _ctx->i++) {
if (_ctx->myThreads[_ctx->i]._nItem == _ctx->myActions[_ctx->k]._nItem)
break;
}
if (_ctx->i < _ctx->nRealItems)
continue;
// Ok, we are the only ones :)
lockItems();
_ctx->curItem = GLOBALS._lpmiItems + itemGetOrderFromNum(_ctx->myActions[_ctx->k]._nItem);
// Check if there is a WhenExecute expression
_ctx->j=_ctx->myActions[_ctx->k]._nAction;
if (_ctx->curItem->_action[_ctx->j]._when != NULL) {
if (!evaluateExpression(_ctx->curItem->_action[_ctx->j]._when)) {
unlockItems();
continue;
}
}
// Ok, we can perform the action. For convenience, we do it in a new process
_ctx->newItem = (LpMpalItem)globalAlloc(GMEM_FIXED | GMEM_ZEROINIT, sizeof(MpalItem));
if (!_ctx->newItem) {
globalDestroy(_ctx->myThreads);
globalDestroy(_ctx->myActions);
CORO_KILL_SELF();
return;
}
memcpy(_ctx->newItem,_ctx->curItem, sizeof(MpalItem));
unlockItems();
// We copy the action in #0
//_ctx->newItem->Action[0].nCmds = _ctx->curItem->Action[_ctx->j].nCmds;
//memcpy(_ctx->newItem->Action[0].CmdNum,_ctx->curItem->Action[_ctx->j].CmdNum,_ctx->newItem->Action[0].nCmds*sizeof(_ctx->newItem->Action[0].CmdNum[0]));
_ctx->newItem->_dwRes = _ctx->j;
// We will create an action, and will provide the necessary details
for (_ctx->i = 0; _ctx->i < _ctx->nRealItems; _ctx->i++) {
if (_ctx->myThreads[_ctx->i]._nItem == 0)
break;
}
_ctx->myThreads[_ctx->i]._nItem = _ctx->myActions[_ctx->k]._nItem;
// Create the process
if ((_ctx->myThreads[_ctx->i]._hThread = CoroScheduler.createProcess(ActionThread, &_ctx->newItem, sizeof(LpMpalItem))) == CORO_INVALID_PID_VALUE) {
//if ((_ctx->myThreads[_ctx->i]._hThread = (void*)_beginthread(ActionThread, 10240, (void *)_ctx->newItem)) == (void*)-1)
globalDestroy(_ctx->newItem);
globalDestroy(_ctx->myThreads);
globalDestroy(_ctx->myActions);
CORO_KILL_SELF();
return;
}
// Skip all idle actions of the same item
}
}
}
}
// Set idle skip on
CORO_INVOKE_4(GLOBALS._lplpFunctions[200], 0, 0, 0, 0);
for (_ctx->i = 0; _ctx->i < _ctx->nRealItems; _ctx->i++) {
if (_ctx->myThreads[_ctx->i]._nItem != 0) {
CORO_INVOKE_3(CoroScheduler.waitForSingleObject, _ctx->myThreads[_ctx->i]._hThread, 5000, &_ctx->delayExpired);
//if (result != WAIT_OBJECT_0)
//if (_ctx->delayExpired)
// TerminateThread(_ctx->MyThreads[_ctx->i].hThread, 0);
CoroScheduler.killMatchingProcess(_ctx->myThreads[_ctx->i]._hThread);
}
}
// Set idle skip off
CORO_INVOKE_4(GLOBALS._lplpFunctions[201], 0, 0, 0, 0);
CORO_END_CODE;
}
/**
* Wait for the end of the dialog execution thread, and then restore global
* variables indicating that the dialogue has finished.
*
* @param param Pointer to a handle to the dialog
* @remarks This additional process is used, instead of clearing variables
* within the same dialog thread, because due to the recursive nature of a dialog,
* it would be difficult to know within it when the dialog is actually ending.
*/
void ShutUpDialogThread(CORO_PARAM, const void *param) {
CORO_BEGIN_CONTEXT;
CORO_END_CONTEXT(_ctx);
uint32 pid = *(const uint32 *)param;
CORO_BEGIN_CODE(_ctx);
CORO_INVOKE_2(CoroScheduler.waitForSingleObject, pid, CORO_INFINITE);
GLOBALS._bExecutingDialog = false;
GLOBALS._nExecutingDialog = 0;
GLOBALS._nExecutingChoice = 0;
CoroScheduler.setEvent(GLOBALS._hAskChoice);
CORO_KILL_SELF();
CORO_END_CODE;
}
void doChoice(CORO_PARAM, uint32 nChoice);
/**
* Executes a group of the current dialog. Can 'be the Starting point of a process.
* @parm nGroup Number of the group to perform
*/
void GroupThread(CORO_PARAM, const void *param) {
CORO_BEGIN_CONTEXT;
LpMpalDialog dialog;
int i, j, k;
int type;
CORO_END_CONTEXT(_ctx);
uint32 nGroup = *(const uint32 *)param;
CORO_BEGIN_CODE(_ctx);
// Lock the _ctx->dialog
lockDialogs();
// Find the pointer to the current _ctx->dialog
_ctx->dialog = GLOBALS._lpmdDialogs + GLOBALS._nExecutingDialog;
// Search inside the group requesting the _ctx->dialog
for (_ctx->i = 0; _ctx->dialog->_group[_ctx->i]._num != 0; _ctx->i++) {
if (_ctx->dialog->_group[_ctx->i]._num == nGroup) {
// Cycle through executing the commands of the group
for (_ctx->j = 0; _ctx->j < _ctx->dialog->_group[_ctx->i]._nCmds; _ctx->j++) {
_ctx->k = _ctx->dialog->_group[_ctx->i]._cmdNum[_ctx->j];
_ctx->type = _ctx->dialog->_command[_ctx->k]._type;
if (_ctx->type == 1) {
// Call custom function
CORO_INVOKE_4(GLOBALS._lplpFunctions[_ctx->dialog->_command[_ctx->k]._nCf],
_ctx->dialog->_command[_ctx->k]._arg1,
_ctx->dialog->_command[_ctx->k]._arg2,
_ctx->dialog->_command[_ctx->k]._arg3,
_ctx->dialog->_command[_ctx->k]._arg4
);
} else if (_ctx->type == 2) {
// Set a variable
lockVar();
varSetValue(_ctx->dialog->_command[_ctx->k]._lpszVarName, evaluateExpression(_ctx->dialog->_command[_ctx->k]._expr));
unlockVar();
} else if (_ctx->type == 3) {
// DoChoice: call the chosen function
CORO_INVOKE_1(doChoice, (uint32)_ctx->dialog->_command[_ctx->k]._nChoice);
} else {
GLOBALS._mpalError = 1;
unlockDialogs();
CORO_KILL_SELF();
return;
}
// WORKAROUND: Wait for events to pulse.
CORO_SLEEP(1);
}
// The gruop is finished, so we can return to the calling function.
// If the group was the first called, then the process will automatically
// end. Otherwise it returns to the caller method
return;
}
}
// If we are here, it means that we have not found the requested group
GLOBALS._mpalError = 1;
unlockDialogs();
CORO_KILL_SELF();
CORO_END_CODE;
}
/**
* Make a choice in the current dialog.
*
* @param nChoice Number of choice to perform
*/
void doChoice(CORO_PARAM, uint32 nChoice) {
CORO_BEGIN_CONTEXT;
LpMpalDialog dialog;
int i, j, k;
uint32 nGroup;
CORO_END_CONTEXT(_ctx);
CORO_BEGIN_CODE(_ctx);
// Lock the dialogs
lockDialogs();
// Get a pointer to the current dialog
_ctx->dialog = GLOBALS._lpmdDialogs + GLOBALS._nExecutingDialog;
// Search the choice between those required in the dialog
for (_ctx->i = 0; _ctx->dialog->_choice[_ctx->i]._nChoice != 0; _ctx->i++) {
if (_ctx->dialog->_choice[_ctx->i]._nChoice == nChoice)
break;
}
// If nothing has been found, exit with an error
if (_ctx->dialog->_choice[_ctx->i]._nChoice == 0) {
// If we're here, we did not find the required choice
GLOBALS._mpalError = 1;
unlockDialogs();
CORO_KILL_SELF();
return;
}
// We've found the requested choice. Remember what in global variables
GLOBALS._nExecutingChoice = _ctx->i;
while (1) {
GLOBALS._nExecutingChoice = _ctx->i;
_ctx->k = 0;
// Calculate the expression of each selection, to see if they're active or inactive
for (_ctx->j = 0; _ctx->dialog->_choice[_ctx->i]._select[_ctx->j]._dwData != 0; _ctx->j++) {
if (_ctx->dialog->_choice[_ctx->i]._select[_ctx->j]._when == NULL) {
_ctx->dialog->_choice[_ctx->i]._select[_ctx->j]._curActive = 1;
_ctx->k++;
} else if (evaluateExpression(_ctx->dialog->_choice[_ctx->i]._select[_ctx->j]._when)) {
_ctx->dialog->_choice[_ctx->i]._select[_ctx->j]._curActive = 1;
_ctx->k++;
} else
_ctx->dialog->_choice[_ctx->i]._select[_ctx->j]._curActive = 0;
}
// If there are no choices activated, then the dialog is finished.
if (_ctx->k == 0) {
unlockDialogs();
break;
}
// There are choices available to the user, so wait for them to make one
CoroScheduler.resetEvent(GLOBALS._hDoneChoice);
CoroScheduler.setEvent(GLOBALS._hAskChoice);
CORO_INVOKE_2(CoroScheduler.waitForSingleObject, GLOBALS._hDoneChoice, CORO_INFINITE);
// Now that the choice has been made, we can run the groups associated with the choice tbontbtitq
_ctx->j = GLOBALS._nSelectedChoice;
for (_ctx->k = 0; _ctx->dialog->_choice[_ctx->i]._select[_ctx->j]._wPlayGroup[_ctx->k] != 0; _ctx->k++) {
_ctx->nGroup = _ctx->dialog->_choice[_ctx->i]._select[_ctx->j]._wPlayGroup[_ctx->k];
CORO_INVOKE_1(GroupThread, &_ctx->nGroup);
}
// Control attribute
if (_ctx->dialog->_choice[_ctx->i]._select[_ctx->j]._attr & (1 << 0)) {
// Bit 0 set: the end of the choice
unlockDialogs();
break;
}
if (_ctx->dialog->_choice[_ctx->i]._select[_ctx->j]._attr & (1 << 1)) {
// Bit 1 set: the end of the dialog
unlockDialogs();
CORO_KILL_SELF();
return;
}
// End of choic ewithout attributes. We must do it again
}
// If we're here, we found an end choice. Return to the caller group
return;
CORO_END_CODE;
}
/**
* Perform an action on a certain item.
*
* @param nAction Action number
* @param ordItem Index of the item in the items list
* @param dwParam Any parameter for the action.
* @returns Id of the process that was launched to perform the action, or
* CORO_INVALID_PID_VALUE if the action was not defined, or the item was inactive.
* @remarks You can get the index of an item from its number by using
* the itemGetOrderFromNum() function. The items list must first be locked
* by calling LockItem().
*/
static uint32 doAction(uint32 nAction, uint32 ordItem, uint32 dwParam) {
LpMpalItem item = GLOBALS._lpmiItems;
LpMpalItem newitem;
item+=ordItem;
Common::String buf = Common::String::format("Status.%u", item->_nObj);
if (varGetValue(buf.c_str()) <= 0)
return CORO_INVALID_PID_VALUE;
for (int i = 0; i < item->_nActions; i++) {
if (item->_action[i]._num != nAction)
continue;
if (item->_action[i]._wParm != dwParam)
continue;
if (item->_action[i]._when != NULL) {
if (!evaluateExpression(item->_action[i]._when))
continue;
}
// Now we find the right action to be performed
// Duplicate the item and copy the current action in #i into #0
newitem = (LpMpalItem)globalAlloc(GMEM_FIXED | GMEM_ZEROINIT, sizeof(MpalItem));
if (newitem == NULL)
return CORO_INVALID_PID_VALUE;
// In the new version number of the action in writing dwRes
Common::copy((byte *)item, (byte *)item + sizeof(MpalItem), (byte *)newitem);
//newitem->_action[0]._nCmds=item->_action[i]._nCmds;
//memcpy(newitem->_action[0]._cmdNum, item->_action[i]._cmdNum, newitem->Action[0].nCmds * sizeof(newitem->_action[0]._cmdNum[0]));
newitem->_dwRes = i;
// And finally we can laucnh the process that will execute the action,
// and a second process to free up the memory when the action is finished.
// !!! New process management
uint32 h;
if ((h = CoroScheduler.createProcess(ActionThread, &newitem, sizeof(LpMpalItem))) == CORO_INVALID_PID_VALUE)
return CORO_INVALID_PID_VALUE;
if (CoroScheduler.createProcess(ShutUpActionThread, &h, sizeof(uint32)) == CORO_INVALID_PID_VALUE)
return CORO_INVALID_PID_VALUE;
GLOBALS._nExecutingAction = item->_nObj;
GLOBALS._bExecutingAction = true;
return h;
}
return CORO_INVALID_PID_VALUE;
}
/**
* Shows a dialog in a separate process.
*
* @param nDlgOrd The index of the dialog in the dialog list
* @param nGroup Number of the group to perform
* @returns The process Id of the process running the dialog
* or CORO_INVALID_PID_VALUE on error
* @remarks The dialogue runs in a thread created on purpose,
* so that must inform through an event and when 'necessary to you make a choice.
* The data on the choices may be obtained through various queries.
*/
static uint32 doDialog(uint32 nDlgOrd, uint32 nGroup) {
// Store the running dialog in a global variable
GLOBALS._nExecutingDialog = nDlgOrd;
// Enables the flag to indicate that there is' a running dialogue
GLOBALS._bExecutingDialog = true;
CoroScheduler.resetEvent(GLOBALS._hAskChoice);
CoroScheduler.resetEvent(GLOBALS._hDoneChoice);
// Create a thread that performs the dialogue group
// Create the process
uint32 h;
if ((h = CoroScheduler.createProcess(GroupThread, &nGroup, sizeof(uint32))) == CORO_INVALID_PID_VALUE)
return CORO_INVALID_PID_VALUE;
// Create a thread that waits until the end of the dialog process, and will restore the global variables
if (CoroScheduler.createProcess(ShutUpDialogThread, &h, sizeof(uint32)) == CORO_INVALID_PID_VALUE) {
// Something went wrong, so kill the previously started dialog process
CoroScheduler.killMatchingProcess(h);
return CORO_INVALID_PID_VALUE;
}
return h;
}
/**
* Takes note of the selection chosen by the user, and warns the process that was running
* the box that it can continue.
*
* @param nChoice Number of choice that was in progress
* @param dwData Since combined with select selection
* @returns True if everything is OK, false on failure
*/
bool doSelection(uint32 i, uint32 dwData) {
LpMpalDialog dialog = GLOBALS._lpmdDialogs + GLOBALS._nExecutingDialog;
int j;
for (j = 0; dialog->_choice[i]._select[j]._dwData != 0; j++) {
if (dialog->_choice[i]._select[j]._dwData == dwData && dialog->_choice[i]._select[j]._curActive != 0)
break;
}
if (dialog->_choice[i]._select[j]._dwData == 0)
return false;
GLOBALS._nSelectedChoice = j;
CoroScheduler.setEvent(GLOBALS._hDoneChoice);
return true;
}
/**
* @defgroup Exported functions
*/
//@{
/**
* Initializes the MPAL library and opens the .MPC file, which will be used for all queries.
*
* @param lpszMpcFileName Name of the MPC file
* @param lpszMprFileName Name of the MPR file
* @param lplpcfArray Array of pointers to custom functions.
* @returns True if everything is OK, false on failure
*/
bool mpalInit(const char *lpszMpcFileName, const char *lpszMprFileName,
LPLPCUSTOMFUNCTION lplpcfArray, Common::String *lpcfStrings) {
byte buf[5];
byte *cmpbuf;
// Save the array of custom functions
GLOBALS._lplpFunctions = lplpcfArray;
GLOBALS._lplpFunctionStrings = lpcfStrings;
// OPen the MPC file for reading
Common::File hMpc;
if (!hMpc.open(lpszMpcFileName))
return false;
// Read and check the header
uint32 nBytesRead = hMpc.read(buf, 5);
if (nBytesRead != 5)
return false;
if (buf[0] != 'M' || buf[1] != 'P' || buf[2] != 'C' || buf[3] != 0x20)
return false;
bool bCompress = buf[4];
// Reads the size of the uncompressed file, and allocate memory
uint32 dwSizeDecomp = hMpc.readUint32LE();
if (hMpc.err())
return false;
byte *lpMpcImage = (byte *)globalAlloc(GMEM_FIXED, dwSizeDecomp + 16);
if (lpMpcImage == NULL)
return false;
if (bCompress) {
// Get the compressed size and read the data in
uint32 dwSizeComp = hMpc.readUint32LE();
if (hMpc.err()) {
globalDestroy(lpMpcImage);
return false;
}
cmpbuf = (byte *)globalAlloc(GMEM_FIXED, dwSizeComp);
if (cmpbuf == NULL) {
globalDestroy(lpMpcImage);
return false;
}
nBytesRead = hMpc.read(cmpbuf, dwSizeComp);
if (nBytesRead != dwSizeComp) {
globalDestroy(cmpbuf);
globalDestroy(lpMpcImage);
return false;
}
// Decompress the data
lzo1x_decompress(cmpbuf, dwSizeComp, lpMpcImage, &nBytesRead);
if (nBytesRead != dwSizeDecomp) {
globalDestroy(cmpbuf);
globalDestroy(lpMpcImage);
return false;
}
globalDestroy(cmpbuf);
} else {
// If the file is not compressed, we directly read in the data
nBytesRead = hMpc.read(lpMpcImage, dwSizeDecomp);
if (nBytesRead != dwSizeDecomp) {
globalDestroy(lpMpcImage);
return false;
}
}
// Close the file
hMpc.close();
// Process the data
if (parseMpc(lpMpcImage) == false) {
globalDestroy(lpMpcImage);
return false;
}
globalDestroy(lpMpcImage);
// Open the MPR file
if (!GLOBALS._hMpr.open(lpszMprFileName))
return false;
// Seek to the end of the file to read overall information
GLOBALS._hMpr.seek(-12, SEEK_END);
uint32 dwSizeComp = GLOBALS._hMpr.readUint32LE();
if (GLOBALS._hMpr.err())
return false;
GLOBALS._nResources = GLOBALS._hMpr.readUint32LE();
if (GLOBALS._hMpr.err())
return false;
nBytesRead = GLOBALS._hMpr.read(buf, 4);
if (GLOBALS._hMpr.err())
return false;
if (buf[0] !='E' || buf[1] != 'N' || buf[2] != 'D' || buf[3] != '0')
return false;
// Move to the start of the resources header
GLOBALS._hMpr.seek(-(12 + (int)dwSizeComp), SEEK_END);
GLOBALS._lpResources = (uint32 *)globalAlloc(GMEM_FIXED | GMEM_ZEROINIT, GLOBALS._nResources * 8);
if (GLOBALS._lpResources == NULL)
return false;
cmpbuf = (byte *)globalAlloc(GMEM_FIXED | GMEM_ZEROINIT, dwSizeComp);
if (cmpbuf == NULL)
return false;
nBytesRead = GLOBALS._hMpr.read(cmpbuf, dwSizeComp);
if (nBytesRead != dwSizeComp)
return false;
lzo1x_decompress((const byte *)cmpbuf, dwSizeComp, (byte *)GLOBALS._lpResources, (uint32 *)&nBytesRead);
if (nBytesRead != (uint32)GLOBALS._nResources * 8)
return false;
for (int i = 0; i < 2*GLOBALS._nResources; ++i)
GLOBALS._lpResources[i] = FROM_LE_32(GLOBALS._lpResources[i]);
globalDestroy(cmpbuf);
// Reset back to the start of the file, leaving it open
GLOBALS._hMpr.seek(0, SEEK_SET);
// There is no action or dialog running by default
GLOBALS._bExecutingAction = false;
GLOBALS._bExecutingDialog = false;
// There's no polling location
Common::fill(GLOBALS._nPollingLocations, GLOBALS._nPollingLocations + MAXPOLLINGLOCATIONS, 0);
// Create the event that will be used to co-ordinate making choices and choices finishing
GLOBALS._hAskChoice = CoroScheduler.createEvent(true, false);
GLOBALS._hDoneChoice = CoroScheduler.createEvent(true, false);
return true;
}
/**
* Frees resources allocated by the MPAL subsystem
*/
void mpalFree() {
// Free the resource list
globalDestroy(GLOBALS._lpResources);
}
/**
* This is a general function to communicate with the library, to request information
* about what is in the .MPC file
*
* @param wQueryType Type of query. The list is in the QueryTypes enum.
* @returns 4 bytes depending on the type of query
* @remarks This is the specialized version of the original single mpalQuery
* method that returns numeric results.
*/
uint32 mpalQueryDWORD(uint16 wQueryType, ...) {
Common::String buf;
uint32 dwRet = 0;
va_list v;
va_start(v, wQueryType);
GLOBALS._mpalError = OK;
if (wQueryType == MPQ_VERSION) {
/*
* uint32 mpalQuery(MPQ_VERSION);
*/
dwRet = HEX_VERSION;
} else if (wQueryType == MPQ_GLOBAL_VAR) {
/*
* uint32 mpalQuery(MPQ_GLOBAL_VAR, char * lpszVarName);
*/
lockVar();
dwRet = (uint32)varGetValue(GETARG(char *));
unlockVar();
} else if (wQueryType == MPQ_MESSAGE) {
/*
* char * mpalQuery(MPQ_MESSAGE, uint32 nMsg);
*/
error("mpalQuery(MPQ_MESSAGE, uint32 nMsg) used incorrect method variant");
} else if (wQueryType == MPQ_ITEM_PATTERN) {
/*
* uint32 mpalQuery(MPQ_ITEM_PATTERN, uint32 nItem);
*/
lockVar();
buf = Common::String::format("Pattern.%u", GETARG(uint32));
dwRet = (uint32)varGetValue(buf.c_str());
unlockVar();
} else if (wQueryType == MPQ_LOCATION_SIZE) {
/*
* uint32 mpalQuery(MPQ_LOCATION_SIZE, uint32 nLoc, uint32 dwCoord);
*/
lockLocations();
int x = locGetOrderFromNum(GETARG(uint32));
int y = GETARG(uint32);
if (x != -1) {
if (y == MPQ_X)
dwRet = GLOBALS._lpmlLocations[x]._dwXlen;
else if (y == MPQ_Y)
dwRet = GLOBALS._lpmlLocations[x]._dwYlen;
else
GLOBALS._mpalError = 1;
} else
GLOBALS._mpalError = 1;
unlockLocations();
} else if (wQueryType == MPQ_LOCATION_IMAGE) {
/*
* HGLOBAL mpalQuery(MPQ_LOCATION_IMAGE, uint32 nLoc);
*/
error("mpalQuery(MPQ_LOCATION_IMAGE, uint32 nLoc) used incorrect variant");
} else if (wQueryType == MPQ_RESOURCE) {
/*
* HGLOBAL mpalQuery(MPQ_RESOURCE, uint32 dwRes);
*/
error("mpalQuery(MPQ_RESOURCE, uint32 dwRes) used incorrect variant");
} else if (wQueryType == MPQ_ITEM_LIST) {
/*
* uint32 mpalQuery(MPQ_ITEM_LIST, uint32 nLoc);
*/
error("mpalQuery(MPQ_ITEM_LIST, uint32 nLoc) used incorrect variant");
} else if (wQueryType == MPQ_ITEM_DATA) {
/*
* LpItem mpalQuery(MPQ_ITEM_DATA, uint32 nItem);
*/
error("mpalQuery(MPQ_ITEM_DATA, uint32 nItem) used incorrect variant");
} else if (wQueryType == MPQ_ITEM_IS_ACTIVE) {
/*
* bool mpalQuery(MPQ_ITEM_IS_ACTIVE, uint32 nItem);
*/
lockVar();
int x = GETARG(uint32);
buf = Common::String::format("Status.%u", x);
if (varGetValue(buf.c_str()) <= 0)
dwRet = (uint32)false;
else
dwRet = (uint32)true;
unlockVar();
} else if (wQueryType == MPQ_ITEM_NAME) {
/*
* uint32 mpalQuery(MPQ_ITEM_NAME, uint32 nItem, char * lpszName);
*/
lockVar();
int x = GETARG(uint32);
char *n = GETARG(char *);
buf = Common::String::format("Status.%u", x);
if (varGetValue(buf.c_str()) <= 0)
n[0]='\0';
else {
lockItems();
int y = itemGetOrderFromNum(x);
memcpy(n, (char *)(GLOBALS._lpmiItems + y)->_lpszDescribe, MAX_DESCRIBE_SIZE);
unlockItems();
}
unlockVar();
} else if (wQueryType == MPQ_DIALOG_PERIOD) {
/*
* char *mpalQuery(MPQ_DIALOG_PERIOD, uint32 nDialog, uint32 nPeriod);
*/
error("mpalQuery(MPQ_DIALOG_PERIOD, uint32 nDialog, uint32 nPeriod) used incorrect variant");
} else if (wQueryType == MPQ_DIALOG_WAITFORCHOICE) {
/*
* void mpalQuery(MPQ_DIALOG_WAITFORCHOICE);
*/
error("mpalQuery(MPQ_DIALOG_WAITFORCHOICE) used incorrect variant");
} else if (wQueryType == MPQ_DIALOG_SELECTLIST) {
/*
* uint32 *mpalQuery(MPQ_DIALOG_SELECTLIST, uint32 nChoice);
*/
error("mpalQuery(MPQ_DIALOG_SELECTLIST, uint32 nChoice) used incorrect variant");
} else if (wQueryType == MPQ_DIALOG_SELECTION) {
/*
* bool mpalQuery(MPQ_DIALOG_SELECTION, uint32 nChoice, uint32 dwData);
*/
lockDialogs();
int x = GETARG(uint32);
int y = GETARG(uint32);
dwRet = (uint32)doSelection(x, y);
unlockDialogs();
} else if (wQueryType == MPQ_DO_ACTION) {
/*
* int mpalQuery(MPQ_DO_ACTION, uint32 nAction, uint32 nItem, uint32 dwParam);
*/
lockItems();
lockVar();
int x = GETARG(uint32);
int z = GETARG(uint32);
int y = itemGetOrderFromNum(z);
if (y != -1) {
dwRet = doAction(x, y, GETARG(uint32));
} else {
dwRet = CORO_INVALID_PID_VALUE;
GLOBALS._mpalError = 1;
}
unlockVar();
unlockItems();
} else if (wQueryType == MPQ_DO_DIALOG) {
/*
* int mpalQuery(MPQ_DO_DIALOG, uint32 nDialog, uint32 nGroup);
*/
if (!GLOBALS._bExecutingDialog) {
lockDialogs();
int x = dialogGetOrderFromNum(GETARG(uint32));
int y = GETARG(uint32);
dwRet = doDialog(x, y);
unlockDialogs();
}
} else {
/*
* DEFAULT -> ERROR
*/
GLOBALS._mpalError = 1;
}
va_end(v);
return dwRet;
}
/**
* This is a general function to communicate with the library, to request information
* about what is in the .MPC file
*
* @param wQueryType Type of query. The list is in the QueryTypes enum.
* @returns 4 bytes depending on the type of query
* @remarks This is the specialized version of the original single mpalQuery
* method that returns a pointer or handle.
*/
MpalHandle mpalQueryHANDLE(uint16 wQueryType, ...) {
Common::String buf;
va_list v;
va_start(v, wQueryType);
void *hRet = NULL;
GLOBALS._mpalError = OK;
if (wQueryType == MPQ_VERSION) {
/*
* uint32 mpalQuery(MPQ_VERSION);
*/
error("mpalQuery(MPQ_VERSION) used incorrect variant");
} else if (wQueryType == MPQ_GLOBAL_VAR) {
/*
* uint32 mpalQuery(MPQ_GLOBAL_VAR, char * lpszVarName);
*/
error("mpalQuery(MPQ_GLOBAL_VAR, char * lpszVarName) used incorrect variant");
} else if (wQueryType == MPQ_MESSAGE) {
/*
* char * mpalQuery(MPQ_MESSAGE, uint32 nMsg);
*/
LockMsg();
hRet = DuplicateMessage(msgGetOrderFromNum(GETARG(uint32)));
UnlockMsg();
} else if (wQueryType == MPQ_ITEM_PATTERN) {
/*
* uint32 mpalQuery(MPQ_ITEM_PATTERN, uint32 nItem);
*/
error("mpalQuery(MPQ_ITEM_PATTERN, uint32 nItem) used incorrect variant");
} else if (wQueryType == MPQ_LOCATION_SIZE) {
/*
* uint32 mpalQuery(MPQ_LOCATION_SIZE, uint32 nLoc, uint32 dwCoord);
*/
error("mpalQuery(MPQ_LOCATION_SIZE, uint32 nLoc, uint32 dwCoord) used incorrect variant");
} else if (wQueryType == MPQ_LOCATION_IMAGE) {
/*
* HGLOBAL mpalQuery(MPQ_LOCATION_IMAGE, uint32 nLoc);
*/
lockLocations();
int x = locGetOrderFromNum(GETARG(uint32));
hRet = resLoad(GLOBALS._lpmlLocations[x]._dwPicRes);
unlockLocations();
} else if (wQueryType == MPQ_RESOURCE) {
/*
* HGLOBAL mpalQuery(MPQ_RESOURCE, uint32 dwRes);
*/
hRet = resLoad(GETARG(uint32));
} else if (wQueryType == MPQ_ITEM_LIST) {
/*
* uint32 mpalQuery(MPQ_ITEM_LIST, uint32 nLoc);
*/
lockVar();
hRet = GetItemList(GETARG(uint32));
lockVar();
} else if (wQueryType == MPQ_ITEM_DATA) {
/*
* LpItem mpalQuery(MPQ_ITEM_DATA, uint32 nItem);
*/
lockItems();
hRet = getItemData(itemGetOrderFromNum(GETARG(uint32)));
unlockItems();
} else if (wQueryType == MPQ_ITEM_IS_ACTIVE) {
/*
* bool mpalQuery(MPQ_ITEM_IS_ACTIVE, uint32 nItem);
*/
error("mpalQuery(MPQ_ITEM_IS_ACTIVE, uint32 nItem) used incorrect variant");
} else if (wQueryType == MPQ_ITEM_NAME) {
lockVar();
int x = GETARG(uint32);
char *n = GETARG(char *);
buf = Common::String::format("Status.%u", x);
if (varGetValue(buf.c_str()) <= 0)
n[0] = '\0';
else {
lockItems();
int y = itemGetOrderFromNum(x);
memcpy(n, (char *)(GLOBALS._lpmiItems + y)->_lpszDescribe, MAX_DESCRIBE_SIZE);
unlockItems();
}
unlockVar();
} else if (wQueryType == MPQ_DIALOG_PERIOD) {
/*
* char * mpalQuery(MPQ_DIALOG_PERIOD, uint32 nDialog, uint32 nPeriod);
*/
lockDialogs();
int y = GETARG(uint32);
hRet = duplicateDialogPeriod(y);
unlockDialogs();
} else if (wQueryType == MPQ_DIALOG_WAITFORCHOICE) {
/*
* void mpalQuery(MPQ_DIALOG_WAITFORCHOICE);
*/
error("mpalQuery(MPQ_DIALOG_WAITFORCHOICE) used incorrect variant");
} else if (wQueryType == MPQ_DIALOG_SELECTLIST) {
/*
* uint32 *mpalQuery(MPQ_DIALOG_SELECTLIST, uint32 nChoice);
*/
lockDialogs();
hRet = getSelectList(GETARG(uint32));
unlockDialogs();
} else if (wQueryType == MPQ_DIALOG_SELECTION) {
/*
* bool mpalQuery(MPQ_DIALOG_SELECTION, uint32 nChoice, uint32 dwData);
*/
error("mpalQuery(MPQ_DIALOG_SELECTION, uint32 nChoice, uint32 dwData) used incorrect variant");
} else if (wQueryType == MPQ_DO_ACTION) {
/*
* int mpalQuery(MPQ_DO_ACTION, uint32 nAction, uint32 nItem, uint32 dwParam);
*/
error("mpalQuery(MPQ_DO_ACTION, uint32 nAction, uint32 nItem, uint32 dwParam) used incorrect variant");
} else if (wQueryType == MPQ_DO_DIALOG) {
/*
* int mpalQuery(MPQ_DO_DIALOG, uint32 nDialog, uint32 nGroup);
*/
error("mpalQuery(MPQ_DO_DIALOG, uint32 nDialog, uint32 nGroup) used incorrect variant");
} else {
/*
* DEFAULT -> ERROR
*/
GLOBALS._mpalError = 1;
}
va_end(v);
return hRet;
}
/**
* This is a general function to communicate with the library, to request information
* about what is in the .MPC file
*
* @param wQueryType Type of query. The list is in the QueryTypes enum.
* @returns 4 bytes depending on the type of query
* @remarks This is the specialized version of the original single mpalQuery
* method that needs to run within a co-routine context.
*/
void mpalQueryCORO(CORO_PARAM, uint16 wQueryType, uint32 *dwRet) {
CORO_BEGIN_CONTEXT;
uint32 dwRet;
CORO_END_CONTEXT(_ctx);
CORO_BEGIN_CODE(_ctx);
if (wQueryType == MPQ_DIALOG_WAITFORCHOICE) {
/*
* void mpalQuery(MPQ_DIALOG_WAITFORCHOICE);
*/
CORO_INVOKE_2(CoroScheduler.waitForSingleObject, GLOBALS._hAskChoice, CORO_INFINITE);
// WORKAROUND: Introduce a single frame delay so that if there are multiple actions running,
// they all have time to be signalled before resetting the event. This fixes a problem where
// if you try to use the 'shrimp' on the parrot a second time after trying to first use it
// whilst the parrot was talking, the cursor wouldn't be re-enabled afterwards
CORO_SLEEP(1);
CoroScheduler.resetEvent(GLOBALS._hAskChoice);
if (GLOBALS._bExecutingDialog)
*dwRet = (uint32)GLOBALS._nExecutingChoice;
else
*dwRet = (uint32)((int)-1);
} else {
error("mpalQueryCORO called with unsupported query type");
}
CORO_END_CODE;
}
/**
* Returns the current MPAL error code
*
* @returns Error code
*/
uint32 mpalGetError() {
return GLOBALS._mpalError;
}
/**
* Execute a script. The script runs on multitasking by a thread.
*
* @param nScript Script number to run
* @returns TRUE if the script 'was launched, FALSE on failure
*/
bool mpalExecuteScript(int nScript) {
LockScripts();
int n = scriptGetOrderFromNum(nScript);
LpMpalScript s = (LpMpalScript)globalAlloc(GMEM_FIXED | GMEM_ZEROINIT, sizeof(MpalScript));
if (s == NULL)
return false;
memcpy(s, GLOBALS._lpmsScripts + n, sizeof(MpalScript));
unlockScripts();
// !!! New process management
if (CoroScheduler.createProcess(ScriptThread, &s, sizeof(LpMpalScript)) == CORO_INVALID_PID_VALUE)
return false;
return true;
}
/**
* Install a custom routine That will be called by MPAL every time the pattern
* of an item has been changed.
*
* @param lpiifCustom Custom function to install
*/
void mpalInstallItemIrq(LPITEMIRQFUNCTION lpiifCus) {
GLOBALS._lpiifCustom = lpiifCus;
}
/**
* Process the idle actions of the items on one location.
*
* @param nLoc Number of the location whose items must be processed
* for idle actions.
* @returns TRUE if all OK, and FALSE if it exceeded the maximum limit.
* @remarks The maximum number of locations that can be polled
* simultaneously is defined defined by MAXPOLLINGFUNCIONS
*/
bool mpalStartIdlePoll(int nLoc) {
for (uint32 i = 0; i < MAXPOLLINGLOCATIONS; i++) {
if (GLOBALS._nPollingLocations[i] == (uint32)nLoc)
return false;
}
for (uint32 i = 0; i < MAXPOLLINGLOCATIONS; i++) {
if (GLOBALS._nPollingLocations[i] == 0) {
GLOBALS._nPollingLocations[i] = nLoc;
GLOBALS._hEndPollingLocations[i] = CoroScheduler.createEvent(true, false);
// !!! New process management
if ((GLOBALS._pollingThreads[i] = CoroScheduler.createProcess(LocationPollThread, &i, sizeof(uint32))) == CORO_INVALID_PID_VALUE)
// if ((GLOBALS.hEndPollingLocations[i] = (void*)_beginthread(LocationPollThread, 10240, (void *)i))= = (void*)-1)
return false;
return true;
}
}
return false;
}
/**
* Stop processing the idle actions of the items on one location.
*
* @param nLo Number of the location
* @returns TRUE if all OK, FALSE if the specified location was not
* in the process of polling
*/
void mpalEndIdlePoll(CORO_PARAM, int nLoc, bool *result) {
CORO_BEGIN_CONTEXT;
int i;
CORO_END_CONTEXT(_ctx);
CORO_BEGIN_CODE(_ctx);
for (_ctx->i = 0; _ctx->i < MAXPOLLINGLOCATIONS; _ctx->i++) {
if (GLOBALS._nPollingLocations[_ctx->i] == (uint32)nLoc) {
CoroScheduler.setEvent(GLOBALS._hEndPollingLocations[_ctx->i]);
CORO_INVOKE_2(CoroScheduler.waitForSingleObject, GLOBALS._pollingThreads[_ctx->i], CORO_INFINITE);
CoroScheduler.closeEvent(GLOBALS._hEndPollingLocations[_ctx->i]);
GLOBALS._nPollingLocations[_ctx->i] = 0;
if (result)
*result = true;
return;
}
}
if (result)
*result = false;
CORO_END_CODE;
}
/**
* Retrieve the length of a save state
*
* @returns Length in bytes
*/
int mpalGetSaveStateSize() {
return GLOBALS._nVars * sizeof(MpalVar) + 4;
}
/**
* Store the save state into a buffer. The buffer must be
* length at least the size specified with mpalGetSaveStateSize
*
* @param buf Buffer where to store the state
*/
void mpalSaveState(byte *buf) {
lockVar();
WRITE_LE_UINT32(buf, GLOBALS._nVars);
buf += 4;
for (uint i = 0; i < GLOBALS._nVars; ++i) {
LpMpalVar var = &GLOBALS._lpmvVars[i];
WRITE_LE_UINT32(buf, var->_dwVal);
memcpy(buf + 4, var->_lpszVarName, sizeof(var->_lpszVarName));
buf += (4 + sizeof(var->_lpszVarName));
}
unlockVar();
}
/**
* Load a save state from a buffer.
*
* @param buf Buffer where to store the state
* @returns Length of the state buffer in bytes
*/
int mpalLoadState(byte *buf) {
// We must destroy and recreate all the variables
globalFree(GLOBALS._hVars);
GLOBALS._nVars = READ_LE_UINT32(buf);
buf += 4;
GLOBALS._hVars = globalAllocate(GMEM_ZEROINIT | GMEM_MOVEABLE, GLOBALS._nVars * sizeof(MpalVar));
lockVar();
for (uint i = 0; i < GLOBALS._nVars; ++i) {
LpMpalVar var = &GLOBALS._lpmvVars[i];
var->_dwVal = READ_LE_UINT32(buf);
memcpy(var->_lpszVarName, buf + 4, sizeof(var->_lpszVarName));
buf += (4 + sizeof(var->_lpszVarName));
}
unlockVar();
return GLOBALS._nVars * sizeof(MpalVar) + 4;
}
} // end of namespace MPAL
} // end of namespace Tony