diff options
Diffstat (limited to 'engines')
314 files changed, 40770 insertions, 4521 deletions
diff --git a/engines/agi/agi.cpp b/engines/agi/agi.cpp index 81aec3e351..9d88dd73ef 100644 --- a/engines/agi/agi.cpp +++ b/engines/agi/agi.cpp @@ -62,9 +62,7 @@ void AgiEngine::processEvents() {  	while (_eventMan->pollEvent(event)) {  		switch (event.type) {  		case Common::EVENT_QUIT: -			_gfx->deinitVideo(); -			_gfx->deinitMachine(); -			_system->quit(); +			_game.quitProgNow = true;  			break;  		case Common::EVENT_PREDICTIVE_DIALOG:  			if (_predictiveDialogRunning) @@ -766,12 +764,15 @@ AgiEngine::~AgiEngine() {  	}  	agiDeinit(); +	delete _loader;  	_sound->deinitSound();  	delete _sound;  	_gfx->deinitVideo();  	delete _sprites; +	delete _picture;  	free(_game.sbufOrig);  	_gfx->deinitMachine(); +	delete _gfx;  	delete _rnd;  	delete _console; diff --git a/engines/agos/agos.cpp b/engines/agos/agos.cpp index 9b22240f83..a9fd204d73 100644 --- a/engines/agos/agos.cpp +++ b/engines/agos/agos.cpp @@ -37,6 +37,7 @@  #include "sound/mididrv.h"  #include "sound/mods/protracker.h" +#include "sound/audiocd.h"  using Common::File; @@ -96,6 +97,8 @@ AGOSEngine::AGOSEngine(OSystem *syst)  	_vc_get_out_of_code = 0;  	_gameOffsetsPtr = 0; +	_quit = false; +  	_debugger = 0;  	_gameFile = 0; @@ -556,14 +559,17 @@ int AGOSEngine::init() {  		// Setup midi driver  		int midiDriver = MidiDriver::detectMusicDriver(MDT_ADLIB | MDT_MIDI);  		_nativeMT32 = ((midiDriver == MD_MT32) || ConfMan.getBool("native_mt32")); -		MidiDriver *driver = MidiDriver::createMidi(midiDriver); +		 +		_driver = MidiDriver::createMidi(midiDriver); +  		if (_nativeMT32) { -			driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE); +			_driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE);  		}  		_midi.mapMT32toGM (getGameType() != GType_SIMON2 && !_nativeMT32); -		_midi.setDriver(driver); +		_midi.setDriver(_driver); +  		int ret = _midi.open();  		if (ret)  			warning("MIDI Player init failed: \"%s\"", _midi.getErrorName (ret)); @@ -572,6 +578,8 @@ int AGOSEngine::init() {  		_midiEnabled = true; +	} else { +		_driver = NULL;  	}  	// allocate buffers @@ -875,6 +883,10 @@ AGOSEngine::~AGOSEngine() {  		delete _gameFile;  	_midi.close(); +	if (_driver) +		delete _driver; + +	AudioCD.destroy();  	for (uint i = 0; i < _itemHeap.size(); i++) {  		delete[] _itemHeap[i]; @@ -883,6 +895,8 @@ AGOSEngine::~AGOSEngine() {  	free(_tablesHeapPtr - _tablesHeapCurPos); +	free(_mouseData); +	  	free(_gameOffsetsPtr);  	free(_iconFilePtr);  	free(_itemArrayPtr); @@ -894,6 +908,7 @@ AGOSEngine::~AGOSEngine() {  	free(_backGroundBuf);  	free(_backBuf);  	free(_scaleBuf); +	free(_zoneBuffers);  	free(_window4BackScn);  	free(_window6BackScn); @@ -937,7 +952,7 @@ void AGOSEngine::pauseEngineIntern(bool pauseIt) {  void AGOSEngine::pause() {  	pauseEngine(true); -	while (_pause) { +	while (_pause && !_quit) {  		delay(1);  		if (_keyPressed.keycode == Common::KEYCODE_p)  			pauseEngine(false); @@ -974,7 +989,7 @@ int AGOSEngine::go() {  		(getFeatures() & GF_DEMO)) {  		int i; -		while (1) { +		while (!_quit) {  			for (i = 0; i < 4; i++) {  				setWindowImage(3, 9902 + i);  				debug(0, "Displaying image %d", 9902 + i); @@ -1003,7 +1018,7 @@ int AGOSEngine::go() {  	runSubroutine101();  	permitInput(); -	while (1) { +	while (!_quit) {  		waitForInput();  		handleVerbClicked(_verbHitArea);  		delay(100); @@ -1012,6 +1027,9 @@ int AGOSEngine::go() {  	return 0;  } + +/*  I do not think that this will be used + *    void AGOSEngine::shutdown() {  	// Sync with AGOSEngine::~AGOSEngine()  	// In Simon 2, this gets deleted along with _sound further down @@ -1019,6 +1037,7 @@ void AGOSEngine::shutdown() {  		delete _gameFile;  	_midi.close(); +	delete _driver;  	for (uint i = 0; i < _itemHeap.size(); i++) {  		delete[] _itemHeap[i]; @@ -1058,6 +1077,7 @@ void AGOSEngine::shutdown() {  	_system->quit();  } +*/  uint32 AGOSEngine::getTime() const {  	// FIXME: calling time() is not portable, use OSystem::getMillis instead diff --git a/engines/agos/agos.h b/engines/agos/agos.h index 448d26a9d0..8ad5487b35 100644 --- a/engines/agos/agos.h +++ b/engines/agos/agos.h @@ -269,6 +269,7 @@ protected:  	uint16 _marks; +	bool _quit;  	bool _scriptVar2;  	bool _runScriptReturn1;  	bool _runScriptCondition[40]; @@ -523,6 +524,7 @@ protected:  	byte _lettersToPrintBuf[80];  	MidiPlayer _midi; +	MidiDriver *_driver;  	bool _midiEnabled;  	bool _nativeMT32; @@ -1073,6 +1075,8 @@ protected:  	virtual void drawImage(VC10_state *state);  	void drawBackGroundImage(VC10_state *state);  	void drawVertImage(VC10_state *state); +	void drawVertImageCompressed(VC10_state *state); +	void drawVertImageUncompressed(VC10_state *state);  	void setMoveRect(uint16 x, uint16 y, uint16 width, uint16 height); diff --git a/engines/agos/animation.cpp b/engines/agos/animation.cpp index fd78c65002..c92f834a3b 100644 --- a/engines/agos/animation.cpp +++ b/engines/agos/animation.cpp @@ -280,7 +280,7 @@ void MoviePlayer::handleNextFrame() {  			_rightButtonDown = false;  			break;  		case Common::EVENT_QUIT: -			_vm->_system->quit(); +			_vm->_quit = true;  			break;  		default:  			break; diff --git a/engines/agos/debug.cpp b/engines/agos/debug.cpp index 76a0b8e76f..2cf285d56a 100644 --- a/engines/agos/debug.cpp +++ b/engines/agos/debug.cpp @@ -393,11 +393,11 @@ static const byte bmp_hdr[] = {  };  void dumpBMP(const char *filename, int w, int h, const byte *bytes, const uint32 *palette) { -	Common::File out; +	Common::DumpFile out;  	byte my_hdr[sizeof(bmp_hdr)];  	int i; -	out.open(filename, Common::File::kFileWriteMode); +	out.open(filename);  	if (!out.isOpen())  		return; diff --git a/engines/agos/draw.cpp b/engines/agos/draw.cpp index 737f5317af..d38a5ad33b 100644 --- a/engines/agos/draw.cpp +++ b/engines/agos/draw.cpp @@ -473,7 +473,7 @@ void AGOSEngine::restoreBackGround() {  		animTable = animTableTmp = _screenAnim1;  		while (animTable->srcPtr) {  			if (!(animTable->windowNum & 0x8000)) { -				memcpy(animTableTmp, animTable, sizeof(AnimTable)); +				memmove(animTableTmp, animTable, sizeof(AnimTable));  				animTableTmp++;  			}  			animTable++; diff --git a/engines/agos/event.cpp b/engines/agos/event.cpp index 250ff2fcfd..010b331cf8 100644 --- a/engines/agos/event.cpp +++ b/engines/agos/event.cpp @@ -142,7 +142,7 @@ bool AGOSEngine::kickoffTimeEvents() {  	cur_time = getTime() - _gameStoppedClock; -	while ((te = _firstTimeStruct) != NULL && te->time <= cur_time) { +	while ((te = _firstTimeStruct) != NULL && te->time <= cur_time && !_quit) {  		result = true;  		_pendingDeleteTimeEvent = te;  		invokeTimeEvent(te); @@ -521,7 +521,7 @@ void AGOSEngine::delay(uint amount) {  				_rightButtonDown++;  				break;  			case Common::EVENT_QUIT: -				shutdown(); +				_quit = true;  				return;  			default:  				break; @@ -544,7 +544,7 @@ void AGOSEngine::delay(uint amount) {  		_system->delayMillis(this_delay);  		cur = _system->getMillis(); -	} while (cur < start + amount); +	} while (cur < start + amount && !_quit);  }  void AGOSEngine::timer_callback() { diff --git a/engines/agos/gfx.cpp b/engines/agos/gfx.cpp index 193b7347d6..9a3962ea21 100644 --- a/engines/agos/gfx.cpp +++ b/engines/agos/gfx.cpp @@ -744,10 +744,6 @@ void AGOSEngine_Simon1::drawImage(VC10_state *state) {  }  void AGOSEngine::drawBackGroundImage(VC10_state *state) { -	const byte *src; -	byte *dst; -	uint h, i; -  	state->width = _screenWidth;  	if (_window3Flag == 1) {  		state->width = 0; @@ -755,15 +751,19 @@ void AGOSEngine::drawBackGroundImage(VC10_state *state) {  		state->y_skip = 0;  	} -	src = state->srcPtr + (state->width * state->y_skip) + (state->x_skip * 8); -	dst = state->surf_addr; +	const byte* src = state->srcPtr + (state->width * state->y_skip) + (state->x_skip * 8); +	byte* dst = state->surf_addr;  	state->draw_width *= 2; -	h = state->draw_height; +	uint h = state->draw_height; +	const uint w = state->draw_width; +	const byte paletteMod = state->paletteMod;  	do { -		for (i = 0; i != state->draw_width; i++) -			dst[i] = src[i] + state->paletteMod; +		for (uint i = 0; i != w; i+=2) { +			dst[i] = src[i] + paletteMod; +			dst[i+1] = src[i+1] + paletteMod; +		}  		dst += state->surf_pitch;  		src += state->width;  	} while (--h); @@ -771,63 +771,86 @@ void AGOSEngine::drawBackGroundImage(VC10_state *state) {  void AGOSEngine::drawVertImage(VC10_state *state) {  	if (state->flags & kDFCompressed) { -		uint w, h; -		byte *src, *dst, *dstPtr; +		drawVertImageCompressed(state); +	} else { +		drawVertImageUncompressed(state); +	} +} -		state->x_skip *= 4;				/* reached */ +void AGOSEngine::drawVertImageUncompressed(VC10_state *state) { +	assert ((state->flags & kDFCompressed) == 0) ; -		state->dl = state->width; -		state->dh = state->height; +	const byte *src; +	byte *dst; +	uint count; -		vc10_skip_cols(state); +	src = state->srcPtr + (state->width * state->y_skip) * 8; +	dst = state->surf_addr; +	state->x_skip *= 4; -		dstPtr = state->surf_addr; -		if (!(state->flags & kDFNonTrans) && (state->flags & 0x40)) { /* reached */ -			dstPtr += vcReadVar(252); -		} -		w = 0; -		do { +	do { +		for (count = 0; count != state->draw_width; count++) {  			byte color; +			color = (src[count + state->x_skip] / 16) + state->paletteMod; +			if ((state->flags & kDFNonTrans) || color) +				dst[count * 2] = color | state->palette; +			color = (src[count + state->x_skip] & 15) + state->paletteMod; +			if ((state->flags & kDFNonTrans) || color) +				dst[count * 2 + 1] = color | state->palette; +		} +		dst += state->surf_pitch; +		src += state->width * 8; +	} while (--state->draw_height); +} -			src = vc10_depackColumn(state); -			dst = dstPtr; +void AGOSEngine::drawVertImageCompressed(VC10_state *state) { +	assert (state->flags & kDFCompressed) ; +	uint w, h; + +	state->x_skip *= 4;				/* reached */ -			h = 0; +	state->dl = state->width; +	state->dh = state->height; + +	vc10_skip_cols(state); + +	byte *dstPtr = state->surf_addr; +	if (!(state->flags & kDFNonTrans) && (state->flags & 0x40)) { /* reached */ +		dstPtr += vcReadVar(252); +	} +	w = 0; +	do { +		byte color; + +		const byte *src = vc10_depackColumn(state); +		byte *dst = dstPtr; + +		h = 0; +		if (state->flags & kDFNonTrans)  { +			do { +				byte colors = *src; +				color = (colors / 16); +				dst[0] = color | state->palette; +				color = (colors & 15); +				dst[1] = color | state->palette; +				dst += state->surf_pitch; +				src++; +			} while (++h != state->draw_height); +		} else {  			do { -				color = (*src / 16); -				if ((state->flags & kDFNonTrans) || color != 0) +				byte colors = *src; +				color = (colors / 16); +				if (color != 0)  					dst[0] = color | state->palette; -				color = (*src & 15); -				if ((state->flags & kDFNonTrans) || color != 0) +				color = (colors & 15); +				if (color != 0)  					dst[1] = color | state->palette;  				dst += state->surf_pitch;  				src++;  			} while (++h != state->draw_height); -			dstPtr += 2; -		} while (++w != state->draw_width); -	} else { -		const byte *src; -		byte *dst; -		uint count; - -		src = state->srcPtr + (state->width * state->y_skip) * 8; -		dst = state->surf_addr; -		state->x_skip *= 4; - -		do { -			for (count = 0; count != state->draw_width; count++) { -				byte color; -				color = (src[count + state->x_skip] / 16) + state->paletteMod; -				if ((state->flags & kDFNonTrans) || color) -					dst[count * 2] = color | state->palette; -				color = (src[count + state->x_skip] & 15) + state->paletteMod; -				if ((state->flags & kDFNonTrans) || color) -					dst[count * 2 + 1] = color | state->palette; -			} -			dst += state->surf_pitch; -			src += state->width * 8; -		} while (--state->draw_height); -	} +		} +		dstPtr += 2; +	} while (++w != state->draw_width);  }  void AGOSEngine::drawImage(VC10_state *state) { @@ -1263,7 +1286,7 @@ void AGOSEngine::setWindowImageEx(uint16 mode, uint16 vga_res) {  		if (getGameType() == GType_WW && (mode == 6 || mode == 8 || mode == 9)) {  			setWindowImage(mode, vga_res);  		} else { -			while (_copyScnFlag) +			while (_copyScnFlag && !_quit)  				delay(1);  			setWindowImage(mode, vga_res); diff --git a/engines/agos/input.cpp b/engines/agos/input.cpp index add7eb96f0..d36549f187 100644 --- a/engines/agos/input.cpp +++ b/engines/agos/input.cpp @@ -189,12 +189,12 @@ void AGOSEngine::waitForInput() {  		resetVerbs();  	} -	for (;;) { +	while (!_quit) {  		_lastHitArea = NULL;  		_lastHitArea3 = NULL;  		_dragAccept = 1; -		for (;;) { +		while (!_quit) {  			if ((getGameType() == GType_SIMON1 || getGameType() == GType_SIMON2) &&  					_keyPressed.keycode == Common::KEYCODE_F10)  				displayBoxStars(); diff --git a/engines/agos/intern.h b/engines/agos/intern.h index 54cf4dba16..4479e2851e 100644 --- a/engines/agos/intern.h +++ b/engines/agos/intern.h @@ -161,6 +161,7 @@ struct WindowBlock {  	uint8 fill_color, text_color;  	IconBlock *iconPtr;  	WindowBlock() { memset(this, 0, sizeof(*this)); } +	~WindowBlock() { free (iconPtr); }  };  // note on text offset:  // the actual x-coordinate is: textColumn * 8 + textColumnOffset diff --git a/engines/agos/saveload.cpp b/engines/agos/saveload.cpp index 34e5f2cfeb..4a5c43e706 100644 --- a/engines/agos/saveload.cpp +++ b/engines/agos/saveload.cpp @@ -75,7 +75,7 @@ int AGOSEngine::countSaveGames() {  }  char *AGOSEngine::genSaveName(int slot) { -	static char buf[15]; +	static char buf[20];  	if (getGameId() == GID_DIMP) {  		sprintf(buf, "dimp.sav"); @@ -111,7 +111,7 @@ void AGOSEngine::quickLoadOrSave() {  	}  	bool success; -	char buf[50]; +	char buf[60];  	char *filename = genSaveName(_saveLoadSlot);  	if (_saveLoadType == 2) { @@ -978,7 +978,7 @@ bool AGOSEngine::loadGame(const char *filename, bool restartMode) {  	if (restartMode) {  		// Load restart state  		Common::File *file = new Common::File(); -		file->open(filename, Common::File::kFileReadMode); +		file->open(filename);  		f = file;  	} else {  		f = _saveFileMan->openForLoading(filename); @@ -1154,7 +1154,7 @@ bool AGOSEngine_Elvira2::loadGame(const char *filename, bool restartMode) {  	if (restartMode) {  		// Load restart state  		Common::File *file = new Common::File(); -		file->open(filename, Common::File::kFileReadMode); +		file->open(filename);  		f = file;  	} else {  		f = _saveFileMan->openForLoading(filename); diff --git a/engines/agos/script.cpp b/engines/agos/script.cpp index 44fbb4e9e0..6758aec511 100644 --- a/engines/agos/script.cpp +++ b/engines/agos/script.cpp @@ -410,7 +410,7 @@ void AGOSEngine::o_msg() {  void AGOSEngine::o_end() {  	// 68: exit interpreter -	shutdown(); +	_quit = true;  }  void AGOSEngine::o_done() { @@ -965,6 +965,9 @@ void AGOSEngine::writeVariable(uint16 variable, uint16 contents) {  int AGOSEngine::runScript() {  	bool flag; +	if (_quit) +		return 1; +  	do {  		if (_continousMainScript)  			dumpOpcode(_codePtr); @@ -1007,7 +1010,7 @@ int AGOSEngine::runScript() {  			error("Invalid opcode '%d' encountered", _opcode);  		executeOpcode(_opcode); -	} while (getScriptCondition() != flag && !getScriptReturn()); +	} while  (getScriptCondition() != flag && !getScriptReturn() && !_quit);  	return getScriptReturn();  } @@ -1063,7 +1066,7 @@ void AGOSEngine::waitForSync(uint a) {  	_exitCutscene = false;  	_rightButtonDown = false; -	while (_vgaWaitFor != 0) { +	while (_vgaWaitFor != 0 && !_quit) {  		if (_rightButtonDown) {  			if (_vgaWaitFor == 200 && (getGameType() == GType_FF || !getBitFlag(14))) {  				skipSpeech(); diff --git a/engines/agos/script_e1.cpp b/engines/agos/script_e1.cpp index 94df21979c..c7e1d6736e 100644 --- a/engines/agos/script_e1.cpp +++ b/engines/agos/script_e1.cpp @@ -565,7 +565,7 @@ void AGOSEngine_Elvira1::oe1_look() {  		lobjFunc(l, "You can see ");	/* Show objects */  	}  	if (r && (r->flags & 4) && levelOf(i) < 10000) { -		shutdown(); +		_quit = true;  	}  } @@ -944,7 +944,7 @@ restart:  			windowPutChar(window, *message2);  		if (confirmYesOrNo(120, 62) == 0x7FFF) { -			shutdown(); +			_quit = true;  		} else {  			goto restart;  		} diff --git a/engines/agos/script_s1.cpp b/engines/agos/script_s1.cpp index a1308b951d..51918b9515 100644 --- a/engines/agos/script_s1.cpp +++ b/engines/agos/script_s1.cpp @@ -345,14 +345,14 @@ void AGOSEngine_Simon1::os1_pauseGame() {  		if (isSmartphone()) {  			if (_keyPressed.keycode) {  				if (_keyPressed.keycode == Common::KEYCODE_RETURN) -					shutdown(); +					_quit = true;  				else  					break;  			}  		}  #endif  		if (_keyPressed.keycode == keyYes) -			shutdown(); +			_quit = true;  		else if (_keyPressed.keycode == keyNo)  			break;  	} diff --git a/engines/agos/subroutine.cpp b/engines/agos/subroutine.cpp index 44ada82585..cb71ed7efa 100644 --- a/engines/agos/subroutine.cpp +++ b/engines/agos/subroutine.cpp @@ -554,6 +554,10 @@ int AGOSEngine::startSubroutine(Subroutine *sub) {  	_currentTable = sub;  restart: + +	if (_quit) +		return result; +  	while ((byte *)sl != (byte *)sub) {  		_currentLine = sl;  		if (checkIfToRunSubroutineLine(sl, sub)) { diff --git a/engines/cine/anim.cpp b/engines/cine/anim.cpp index 055eb733c3..8dbccebedf 100644 --- a/engines/cine/anim.cpp +++ b/engines/cine/anim.cpp @@ -769,19 +769,18 @@ void loadAbs(const char *resourceName, uint16 idx) {  /*! \brief Load animDataTable from save   * \param fHandle Savefile open for reading - * \param broken Broken/correct file format switch + * \param saveGameFormat The used savegame format   * \todo Add Operation Stealth savefile support   *   * Unlike the old code, this one actually rebuilds the table one frame   * at a time.   */ -void loadResourcesFromSave(Common::InSaveFile &fHandle, bool broken) { +void loadResourcesFromSave(Common::SeekableReadStream &fHandle, enum CineSaveGameFormat saveGameFormat) {  	int16 currentAnim, foundFileIdx;  	int8 isMask = 0, isSpl = 0;  	byte *dataPtr, *ptr;  	char *animName, part[256];  	byte transparentColor = 0; -	AnimData *currentPtr;  	AnimHeaderStruct animHeader;  	uint16 width, height, bpp, var1; @@ -791,30 +790,46 @@ void loadResourcesFromSave(Common::InSaveFile &fHandle, bool broken) {  	strcpy(part, currentPartName); -	for (currentAnim = 0; currentAnim < NUM_MAX_ANIMDATA; currentAnim++) { -		currentPtr = &animDataTable[currentAnim]; +	// We only support these variations of the savegame format at the moment. +	assert(saveGameFormat == ANIMSIZE_23 || saveGameFormat == ANIMSIZE_30_PTRS_INTACT); +	const int entrySize = ((saveGameFormat == ANIMSIZE_23) ? 23 : 30); +	const int fileStartPos = fHandle.pos(); +	for (currentAnim = 0; currentAnim < NUM_MAX_ANIMDATA; currentAnim += animHeader.numFrames) { +		// Initialize the number of frames variable to a sane number. +		// This is needed when using continue later in this function. +		animHeader.numFrames = 1; + +		// Seek to the start of the current animation's entry +		fHandle.seek(fileStartPos + currentAnim * entrySize); +		// Read in the current animation entry  		width = fHandle.readUint16BE();  		var1 = fHandle.readUint16BE();  		bpp = fHandle.readUint16BE();  		height = fHandle.readUint16BE(); -		if (!broken) { -			if (!fHandle.readUint32BE()) { -				fHandle.skip(18); -				continue; -			} -			fHandle.readUint32BE(); +		bool validPtr = false; +		// Handle variables only present in animation entries of size 30 +		if (entrySize == 30) { +			validPtr = (fHandle.readUint32BE() != 0); // Read data pointer +			fHandle.readUint32BE(); // Discard mask pointer  		}  		foundFileIdx = fHandle.readSint16BE();  		frame = fHandle.readSint16BE();  		fHandle.read(name, 10); -		if (foundFileIdx < 0 || (broken && !fHandle.readByte())) { +		// Handle variables only present in animation entries of size 23 +		if (entrySize == 23) { +			validPtr = (fHandle.readByte() != 0); +		} + +		// Don't try to load invalid entries. +		if (foundFileIdx < 0 || !validPtr) {  			continue;  		} +		// Alright, the animation entry looks to be valid so let's start handling it...  		if (strcmp(currentPartName, name)) {  			closePart();  			loadPart(name); @@ -823,13 +838,14 @@ void loadResourcesFromSave(Common::InSaveFile &fHandle, bool broken) {  		animName = partBuffer[foundFileIdx].partName;  		ptr = dataPtr = readBundleFile(foundFileIdx); +		// isSpl and isMask are mutually exclusive cases  		isSpl  = (strstr(animName, ".SPL")) ? 1 : 0;  		isMask = (strstr(animName, ".MSK")) ? 1 : 0;  		if (isSpl) {  			width = (uint16) partBuffer[foundFileIdx].unpackedSize;  			height = 1; -			frame = 0; +			animHeader.numFrames = 1;  			type = ANIM_RAW;  		} else {  			Common::MemoryReadStream readS(ptr, 0x16); @@ -843,25 +859,35 @@ void loadResourcesFromSave(Common::InSaveFile &fHandle, bool broken) {  				type = ANIM_MASK;  			} else {  				type = ANIM_MASKSPRITE; +			} +		} -				loadRelatedPalette(animName); -				transparentColor = getAnimTransparentColor(animName); - -				// special case transparency handling -				if (!strcmp(animName, "L2202.ANI")) { -					transparentColor = (frame < 2) ? 0 : 7; -				} else if (!strcmp(animName, "L4601.ANI")) { -					transparentColor = (frame < 1) ? 0xE : 0; -				} +		loadRelatedPalette(animName); +		transparentColor = getAnimTransparentColor(animName); +		// Make sure we load at least one frame and also that we +		// don't overflow the animDataTable by writing beyond its end. +		animHeader.numFrames = CLIP<uint16>(animHeader.numFrames, 1, NUM_MAX_ANIMDATA - currentAnim); + +		// Load the frames +		for (frame = 0; frame < animHeader.numFrames; frame++) { +			// special case transparency handling +			if (!strcmp(animName, "L2202.ANI")) { +				transparentColor = (frame < 2) ? 0 : 7; +			} else if (!strcmp(animName, "L4601.ANI")) { +				transparentColor = (frame < 1) ? 0xE : 0;  			} + +			// Load a single frame +			animDataTable[currentAnim + frame].load(ptr + frame * width * height, type, width, height, foundFileIdx, frame, name, transparentColor);  		} -		ptr += frame * width * height; -		currentPtr->load(ptr, type, width, height, foundFileIdx, frame, name, transparentColor);  		free(dataPtr);  	}  	loadPart(part); + +	// Make sure we jump over all the animation entries +	fHandle.seek(fileStartPos + NUM_MAX_ANIMDATA * entrySize);  }  } // End of namespace Cine diff --git a/engines/cine/anim.h b/engines/cine/anim.h index d63033e670..b0ce55f7ee 100644 --- a/engines/cine/anim.h +++ b/engines/cine/anim.h @@ -26,8 +26,63 @@  #ifndef CINE_ANIM_H  #define CINE_ANIM_H +#include "common/endian.h" +  namespace Cine { +/** + * Cine engine's save game formats. + * Enumeration entries (Excluding the one used as an error) + * are sorted according to age (i.e. top one is oldest, last one newest etc). + * + * ANIMSIZE_UNKNOWN: + * - Animation data entry size is unknown (Used as an error). + * + * ANIMSIZE_23: + * - Animation data entry size is 23 bytes. + * - Used at least by 0.11.0 and 0.11.1 releases of ScummVM. + * - Introduced in revision 21772, stopped using in revision 31444. + * + * ANIMSIZE_30_PTRS_BROKEN: + * - Animation data entry size is 30 bytes. + * - Data and mask pointers in the saved structs are always NULL. + * - Introduced in revision 31453, stopped using in revision 32073. + * + * ANIMSIZE_30_PTRS_INTACT: + * - Animation data entry size is 30 bytes. + * - Data and mask pointers in the saved structs are intact, + *   so you can test them for equality or inequality with NULL + *   but don't try using them for anything else, it won't work. + * - Introduced in revision 31444, got broken in revision 31453, + *   got fixed in revision 32073 and used after that. + * + * TEMP_OS_FORMAT: + * - Temporary Operation Stealth savegame format. + * - NOT backward compatible and NOT to be supported in the future. + *   This format should ONLY be used during development and abandoned + *   later in favor of a better format! + */ +enum CineSaveGameFormat { +	ANIMSIZE_UNKNOWN, +	ANIMSIZE_23, +	ANIMSIZE_30_PTRS_BROKEN, +	ANIMSIZE_30_PTRS_INTACT, +	TEMP_OS_FORMAT +}; + +/** Identifier for the temporary Operation Stealth savegame format. */ +static const uint32 TEMP_OS_FORMAT_ID = MKID_BE('TEMP'); + +/** The current version number of Operation Stealth's savegame format. */ +static const uint32 CURRENT_OS_SAVE_VER = 0; + +/** Chunk header used by the temporary Operation Stealth savegame format. */ +struct ChunkHeader { +	uint32 id;      ///< Identifier (e.g. MKID_BE('TEMP')) +	uint32 version; ///< Version number +	uint32 size;    ///< Size of the chunk after this header in bytes +}; +  struct AnimHeaderStruct {  	byte field_0;  	byte field_1; @@ -101,7 +156,7 @@ void freeAnimDataTable(void);  void freeAnimDataRange(byte startIdx, byte numIdx);  void loadResource(const char *resourceName);  void loadAbs(const char *resourceName, uint16 idx); -void loadResourcesFromSave(Common::InSaveFile &fHandle, bool broken); +void loadResourcesFromSave(Common::SeekableReadStream &fHandle, enum CineSaveGameFormat saveGameFormat);  void generateMask(const byte *sprite, byte *mask, uint16 size, byte transparency);  } // End of namespace Cine diff --git a/engines/cine/bg.cpp b/engines/cine/bg.cpp index c5b7fb4e3d..2a4e7f0ab1 100644 --- a/engines/cine/bg.cpp +++ b/engines/cine/bg.cpp @@ -35,7 +35,7 @@ namespace Cine {  uint16 bgVar0;  byte *additionalBgTable[9]; -byte currentAdditionalBgIdx = 0, currentAdditionalBgIdx2 = 0; +int16 currentAdditionalBgIdx = 0, currentAdditionalBgIdx2 = 0;  byte loadCtFW(const char *ctName) {  	uint16 header[32]; diff --git a/engines/cine/bg.h b/engines/cine/bg.h index 5fa8209131..9f97bc467d 100644 --- a/engines/cine/bg.h +++ b/engines/cine/bg.h @@ -35,6 +35,9 @@ void addBackground(const char *bgName, uint16 bgIdx);  extern uint16 bgVar0; +extern int16 currentAdditionalBgIdx; +extern int16 currentAdditionalBgIdx2; +  } // End of namespace Cine  #endif diff --git a/engines/cine/bg_list.cpp b/engines/cine/bg_list.cpp index cf25f1d355..fddca078e5 100644 --- a/engines/cine/bg_list.cpp +++ b/engines/cine/bg_list.cpp @@ -63,6 +63,7 @@ void addSpriteFilledToBGList(int16 objIdx) {  void createBgIncrustListElement(int16 objIdx, int16 param) {  	BGIncrust tmp; +	tmp.unkPtr = 0;  	tmp.objIdx = objIdx;  	tmp.param = param;  	tmp.x = objectTable[objIdx].x; @@ -82,7 +83,7 @@ void resetBgIncrustList(void) {  /*! \brief Restore incrust list from savefile   * \param fHandle Savefile open for reading   */ -void loadBgIncrustFromSave(Common::InSaveFile &fHandle) { +void loadBgIncrustFromSave(Common::SeekableReadStream &fHandle) {  	BGIncrust tmp;  	int size = fHandle.readSint16BE(); @@ -90,6 +91,7 @@ void loadBgIncrustFromSave(Common::InSaveFile &fHandle) {  		fHandle.readUint32BE();  		fHandle.readUint32BE(); +		tmp.unkPtr = 0;  		tmp.objIdx = fHandle.readUint16BE();  		tmp.param = fHandle.readUint16BE();  		tmp.x = fHandle.readUint16BE(); diff --git a/engines/cine/bg_list.h b/engines/cine/bg_list.h index 1849d6ec3d..9a402baee8 100644 --- a/engines/cine/bg_list.h +++ b/engines/cine/bg_list.h @@ -51,7 +51,7 @@ void addSpriteFilledToBGList(int16 idx);  void createBgIncrustListElement(int16 objIdx, int16 param);  void resetBgIncrustList(void); -void loadBgIncrustFromSave(Common::InSaveFile &fHandle); +void loadBgIncrustFromSave(Common::SeekableReadStream &fHandle);  } // End of namespace Cine diff --git a/engines/cine/cine.cpp b/engines/cine/cine.cpp index efc33fadaf..f6778b6457 100644 --- a/engines/cine/cine.cpp +++ b/engines/cine/cine.cpp @@ -42,7 +42,6 @@  #include "cine/sound.h"  #include "cine/various.h" -  namespace Cine {  Sound *g_sound; @@ -70,6 +69,10 @@ CineEngine::~CineEngine() {  		freeErrmessDat();  	}  	Common::clearAllSpecialDebugLevels(); + +	free(palPtr); +	free(partBuffer); +	free(textDataPtr);  }  int CineEngine::init() { diff --git a/engines/cine/cine.h b/engines/cine/cine.h index 710840c17e..eaae555812 100644 --- a/engines/cine/cine.h +++ b/engines/cine/cine.h @@ -94,10 +94,17 @@ public:  	Common::StringList _volumeResourceFiles;  	StringPtrHashMap _volumeEntriesMap; +	TextHandler _textHandler;  private:  	void initialize(void); +	void resetEngine(); +	bool loadPlainSaveFW(Common::SeekableReadStream &in, CineSaveGameFormat saveGameFormat); +	bool loadTempSaveOS(Common::SeekableReadStream &in);  	bool makeLoad(char *saveName); +	void makeSaveFW(Common::OutSaveFile &out); +	void makeSaveOS(Common::OutSaveFile &out); +	void makeSave(char *saveFileName);  	void mainLoop(int bootScriptIdx);  	void readVolCnf(); @@ -107,6 +114,7 @@ private:  extern CineEngine *g_cine;  #define BOOT_PRC_NAME "AUTO00.PRC" +#define COPY_PROT_FAIL_PRC_NAME "L201.ANI"  enum {  	VAR_MOUSE_X_MODE = 253, diff --git a/engines/cine/gfx.cpp b/engines/cine/gfx.cpp index 47446f2410..cbddf0fc59 100644 --- a/engines/cine/gfx.cpp +++ b/engines/cine/gfx.cpp @@ -337,7 +337,7 @@ int FWRenderer::drawChar(char character, int x, int y) {  		x += 5;  	} else if ((width = fontParamTable[(unsigned char)character].characterWidth)) {  		idx = fontParamTable[(unsigned char)character].characterIdx; -		drawSpriteRaw(textTable[idx][0], textTable[idx][1], 16, 8, _backBuffer, x, y); +		drawSpriteRaw(g_cine->_textHandler.textTable[idx][0], g_cine->_textHandler.textTable[idx][1], 16, 8, _backBuffer, x, y);  		x += width + 1;  	} @@ -601,20 +601,26 @@ void FWRenderer::setScroll(unsigned int shift) {  	error("Future Wars renderer doesn't support multiple backgrounds");  } +/*! \brief Future Wars has no scrolling backgrounds so scroll value is always zero. + */ +uint FWRenderer::getScroll() const { +	return 0; +} +  /*! \brief Placeholder for Operation Stealth implementation   */  void FWRenderer::removeBg(unsigned int idx) {  	error("Future Wars renderer doesn't support multiple backgrounds");  } -void FWRenderer::saveBg(Common::OutSaveFile &fHandle) { +void FWRenderer::saveBgNames(Common::OutSaveFile &fHandle) {  	fHandle.write(_bgName, 13);  }  /*! \brief Restore active and backup palette from save   * \param fHandle Savefile open for reading   */ -void FWRenderer::restorePalette(Common::InSaveFile &fHandle) { +void FWRenderer::restorePalette(Common::SeekableReadStream &fHandle) {  	int i;  	if (!_palette) { @@ -655,6 +661,49 @@ void FWRenderer::savePalette(Common::OutSaveFile &fHandle) {  	}  } +/*! \brief Write active and backup palette to save + * \param fHandle Savefile open for writing + */ +void OSRenderer::savePalette(Common::OutSaveFile &fHandle) { +	int i; + +	assert(_activeHiPal); + +	// Write the active 256 color palette. +	for (i = 0; i < _hiPalSize; i++) { +		fHandle.writeByte(_activeHiPal[i]); +	} + +	// Write the active 256 color palette a second time. +	// FIXME: The backup 256 color palette should be saved here instead of the active one. +	for (i = 0; i < _hiPalSize; i++) { +		fHandle.writeByte(_activeHiPal[i]); +	} +} + +/*! \brief Restore active and backup palette from save + * \param fHandle Savefile open for reading + */ +void OSRenderer::restorePalette(Common::SeekableReadStream &fHandle) { +	int i; + +	if (!_activeHiPal) { +		_activeHiPal = new byte[_hiPalSize]; +	} + +	assert(_activeHiPal); + +	for (i = 0; i < _hiPalSize; i++) { +		_activeHiPal[i] = fHandle.readByte(); +	} + +	// Jump over the backup 256 color palette. +	// FIXME: Load the backup 256 color palette and use it properly. +	fHandle.seek(_hiPalSize, SEEK_CUR); + +	_changePal = 1; +} +  /*! \brief Rotate active palette   * \param a First color to rotate   * \param b Last color to rotate @@ -938,7 +987,7 @@ int OSRenderer::drawChar(char character, int x, int y) {  		x += 5;  	} else if ((width = fontParamTable[(unsigned char)character].characterWidth)) {  		idx = fontParamTable[(unsigned char)character].characterIdx; -		drawSpriteRaw2(textTable[idx][0], 0, 16, 8, _backBuffer, x, y); +		drawSpriteRaw2(g_cine->_textHandler.textTable[idx][0], 0, 16, 8, _backBuffer, x, y);  		x += width + 1;  	} @@ -969,6 +1018,7 @@ void OSRenderer::drawBackground() {  /*! \brief Draw one overlay   * \param it Overlay info + * \todo Add handling of type 22 overlays   */  void OSRenderer::renderOverlay(const Common::List<overlay>::iterator &it) {  	int len; @@ -979,6 +1029,9 @@ void OSRenderer::renderOverlay(const Common::List<overlay>::iterator &it) {  	switch (it->type) {  	// color sprite  	case 0: +		if (objectTable[it->objIdx].frame < 0) { +			break; +		}  		sprite = animDataTable + objectTable[it->objIdx].frame;  		len = sprite->_realWidth * sprite->_height;  		mask = new byte[len]; @@ -988,6 +1041,13 @@ void OSRenderer::renderOverlay(const Common::List<overlay>::iterator &it) {  		delete[] mask;  		break; +	// bitmap +	case 4: +		if (objectTable[it->objIdx].frame >= 0) { +			FWRenderer::renderOverlay(it); +		} +		break; +  	// masked background  	case 20:  		assert(it->objIdx < NUM_MAX_OBJECT); @@ -1236,6 +1296,13 @@ void OSRenderer::setScroll(unsigned int shift) {  	_bgShift = shift;  } +/*! \brief Get background scroll + * \return Background scroll in pixels + */ +uint OSRenderer::getScroll() const { +	return _bgShift; +} +  /*! \brief Unload background from renderer   * \param idx Background to unload   */ @@ -1259,6 +1326,12 @@ void OSRenderer::removeBg(unsigned int idx) {  	memset(_bgTable[idx].name, 0, sizeof (_bgTable[idx].name));  } +void OSRenderer::saveBgNames(Common::OutSaveFile &fHandle) { +	for (int i = 0; i < 8; i++) { +		fHandle.write(_bgTable[i].name, 13); +	} +} +  /*! \brief Fade to black   * \bug Operation Stealth sometimes seems to fade to black using   * transformPalette resulting in double fadeout diff --git a/engines/cine/gfx.h b/engines/cine/gfx.h index c63c79ac82..6a3aa1ef89 100644 --- a/engines/cine/gfx.h +++ b/engines/cine/gfx.h @@ -108,13 +108,14 @@ public:  	virtual void selectBg(unsigned int idx);  	virtual void selectScrollBg(unsigned int idx);  	virtual void setScroll(unsigned int shift); +	virtual uint getScroll() const;  	virtual void removeBg(unsigned int idx); -	void saveBg(Common::OutSaveFile &fHandle); +	virtual void saveBgNames(Common::OutSaveFile &fHandle);  	virtual void refreshPalette();  	virtual void reloadPalette(); -	void restorePalette(Common::InSaveFile &fHandle); -	void savePalette(Common::OutSaveFile &fHandle); +	virtual void restorePalette(Common::SeekableReadStream &fHandle); +	virtual void savePalette(Common::OutSaveFile &fHandle);  	virtual void rotatePalette(int a, int b, int c);  	virtual void transformPalette(int first, int last, int r, int g, int b); @@ -128,6 +129,7 @@ public:   */  class OSRenderer : public FWRenderer {  private: +	// FIXME: Background table's size is probably 8 instead of 9. Check to make sure and correct if necessary.  	palBg _bgTable[9]; ///< Table of backgrounds loaded into renderer  	byte *_activeHiPal; ///< Active 256 color palette  	unsigned int _currentBg; ///< Current background @@ -163,10 +165,14 @@ public:  	void selectBg(unsigned int idx);  	void selectScrollBg(unsigned int idx);  	void setScroll(unsigned int shift); +	uint getScroll() const;  	void removeBg(unsigned int idx); +	void saveBgNames(Common::OutSaveFile &fHandle);  	void refreshPalette();  	void reloadPalette(); +	void restorePalette(Common::SeekableReadStream &fHandle); +	void savePalette(Common::OutSaveFile &fHandle);  	void rotatePalette(int a, int b, int c);  	void transformPalette(int first, int last, int r, int g, int b); diff --git a/engines/cine/main_loop.cpp b/engines/cine/main_loop.cpp index cfb828cf3c..e5e670c973 100644 --- a/engines/cine/main_loop.cpp +++ b/engines/cine/main_loop.cpp @@ -179,6 +179,20 @@ int getKeyData() {  	return k;  } +/** Removes elements from seqList that have their member variable var4 set to value -1. */ +void purgeSeqList() { +	Common::List<SeqListElement>::iterator it = seqList.begin(); +	while (it != seqList.end()) { +		if (it->var4 == -1) { +			// Erase the element and jump to the next element +			it = seqList.erase(it); +		} else { +			// Let the element be and jump to the next element +			it++; +		} +	} +} +  void CineEngine::mainLoop(int bootScriptIdx) {  	bool playerAction;  	uint16 quitFlag; @@ -186,6 +200,7 @@ void CineEngine::mainLoop(int bootScriptIdx) {  	uint16 mouseButton;  	quitFlag = 0; +	exitEngine = 0;  	if (_preLoad == false) {  		resetBgIncrustList(); @@ -194,7 +209,7 @@ void CineEngine::mainLoop(int bootScriptIdx) {  		errorVar = 0; -		addScriptToList0(bootScriptIdx); +		addScriptToGlobalScripts(bootScriptIdx);	  		menuVar = 0; @@ -234,13 +249,25 @@ void CineEngine::mainLoop(int bootScriptIdx) {  	do {  		stopMusicAfterFadeOut();  		di = executePlayerInput(); +		 +		// Clear the zoneQuery table (Operation Stealth specific) +		if (g_cine->getGameType() == Cine::GType_OS) { +			for (uint i = 0; i < NUM_MAX_ZONE; i++) { +				zoneQuery[i] = 0; +			} +		} -		processSeqList(); -		executeList1(); -		executeList0(); +		if (g_cine->getGameType() == Cine::GType_OS) { +			processSeqList(); +		} +		executeObjectScripts(); +		executeGlobalScripts(); -		purgeList1(); -		purgeList0(); +		purgeObjectScripts(); +		purgeGlobalScripts(); +		if (g_cine->getGameType() == Cine::GType_OS) { +			purgeSeqList(); +		}  		if (playerCommand == -1) {  			setMouseCursor(MOUSE_CURSOR_NORMAL); diff --git a/engines/cine/object.cpp b/engines/cine/object.cpp index 7666f05352..c02e01c8ce 100644 --- a/engines/cine/object.cpp +++ b/engines/cine/object.cpp @@ -99,21 +99,36 @@ int removeOverlay(uint16 objIdx, uint16 param) {  /*! \brief Add new overlay sprite to the list   * \param objIdx Associate the overlay with this object - * \param param Type of new overlay + * \param type Type of new overlay   * \todo Why are x, y, width and color left uninitialized?   */ -void addOverlay(uint16 objIdx, uint16 param) { +void addOverlay(uint16 objIdx, uint16 type) {  	Common::List<overlay>::iterator it;  	overlay tmp;  	for (it = overlayList.begin(); it != overlayList.end(); ++it) { +		// This is done for both Future Wars and Operation Stealth  		if (objectTable[it->objIdx].mask >= objectTable[objIdx].mask) {  			break;  		} + +		// There are additional checks in Operation Stealth's implementation +		if (g_cine->getGameType() == Cine::GType_OS && (it->type == 2 || it->type == 3)) { +			break; +		} +	} + +	// In Operation Stealth's implementation we might bail out early +	if (g_cine->getGameType() == Cine::GType_OS && it != overlayList.end() && it->objIdx == objIdx && it->type == type) { +		return;  	}  	tmp.objIdx = objIdx; -	tmp.type = param; +	tmp.type = type; +	tmp.x = 0; +	tmp.y = 0; +	tmp.width = 0; +	tmp.color = 0;  	overlayList.insert(it, tmp);  } @@ -122,24 +137,22 @@ void addOverlay(uint16 objIdx, uint16 param) {   * \param objIdx Associate the overlay with this object   * \param param source background index   */ -void addGfxElementA0(int16 objIdx, int16 param) { +void addGfxElement(int16 objIdx, int16 param, int16 type) {  	Common::List<overlay>::iterator it;  	overlay tmp;  	for (it = overlayList.begin(); it != overlayList.end(); ++it) { -		// wtf?! -		if (objectTable[it->objIdx].mask == objectTable[objIdx].mask && -			(it->type == 2 || it->type == 3)) { +		if (objectTable[it->objIdx].mask >= objectTable[objIdx].mask || it->type == 2 || it->type == 3) {  			break;  		}  	} -	if (it != overlayList.end() && it->objIdx == objIdx && it->type == 20 && it->x == param) { +	if (it != overlayList.end() && it->objIdx == objIdx && it->type == type && it->x == param) {  		return;  	}  	tmp.objIdx = objIdx; -	tmp.type = 20; +	tmp.type = type;  	tmp.x = param;  	tmp.y = 0;  	tmp.width = 0; @@ -153,11 +166,11 @@ void addGfxElementA0(int16 objIdx, int16 param) {   * \param param Remove overlay using this background   * \todo Check that it works   */ -void removeGfxElementA0(int16 objIdx, int16 param) { +void removeGfxElement(int16 objIdx, int16 param, int16 type) {  	Common::List<overlay>::iterator it;  	for (it = overlayList.begin(); it != overlayList.end(); ++it) { -		if (it->objIdx == objIdx && it->type == 20 && it->x == param) { +		if (it->objIdx == objIdx && it->type == type && it->x == param) {  			overlayList.erase(it);  			return;  		} @@ -170,8 +183,12 @@ void setupObject(byte objIdx, uint16 param1, uint16 param2, uint16 param3, uint1  	objectTable[objIdx].mask = param3;  	objectTable[objIdx].frame = param4; -	if (removeOverlay(objIdx, 0)) { -		addOverlay(objIdx, 0); +	if (g_cine->getGameType() == Cine::GType_OS) { +		resetGfxEntityEntry(objIdx); +	} else { // Future Wars +		if (removeOverlay(objIdx, 0)) { +			addOverlay(objIdx, 0); +		}  	}  } @@ -199,9 +216,12 @@ void modifyObjectParam(byte objIdx, byte paramIdx, int16 newValue) {  	case 3:  		objectTable[objIdx].mask = newValue; -		// TODO: Check this part against disassembly -		if (removeOverlay(objIdx, 0)) { -			addOverlay(objIdx, 0); +		if (g_cine->getGameType() == Cine::GType_OS) { // Operation Stealth specific +			resetGfxEntityEntry(objIdx); +		} else { // Future Wars specific +			if (removeOverlay(objIdx, 0)) { +				addOverlay(objIdx, 0); +			}  		}  		break;  	case 4: @@ -221,6 +241,29 @@ void modifyObjectParam(byte objIdx, byte paramIdx, int16 newValue) {  	}  } +/** + * Check if at least one of the range B's endpoints is inside range A, + * not counting the starting and ending points of range A. + * Used at least by Operation Stealth's opcode 0x8D i.e. 141. + */ +bool compareRanges(uint16 aStart, uint16 aEnd, uint16 bStart, uint16 bEnd) { +	return (bStart > aStart && bStart < aEnd) || (bEnd > aStart && bEnd < aEnd); +} + +uint16 compareObjectParamRanges(uint16 objIdx1, uint16 xAdd1, uint16 yAdd1, uint16 maskAdd1, uint16 objIdx2, uint16 xAdd2, uint16 yAdd2, uint16 maskAdd2) { +	assert(objIdx1 < NUM_MAX_OBJECT && objIdx2 < NUM_MAX_OBJECT); +	const objectStruct &obj1 = objectTable[objIdx1]; +	const objectStruct &obj2 = objectTable[objIdx2]; + +	if (compareRanges(obj1.x,    obj1.x    + xAdd1,    obj2.x,    obj2.x    + xAdd2) && +		compareRanges(obj1.y,    obj1.y    + yAdd1,    obj2.y,    obj2.y    + yAdd2) && +		compareRanges(obj1.mask, obj1.mask + maskAdd1, obj2.mask, obj2.mask + maskAdd2)) { +		return kCmpEQ; +	} else { +		return 0; +	} +} +  uint16 compareObjectParam(byte objIdx, byte type, int16 value) {  	uint16 compareResult = 0;  	int16 objectParam = getObjectParam(objIdx, type); diff --git a/engines/cine/object.h b/engines/cine/object.h index e7de39649d..7ad65eb75f 100644 --- a/engines/cine/object.h +++ b/engines/cine/object.h @@ -50,7 +50,7 @@ struct overlay {  };  #define NUM_MAX_OBJECT 255 -#define NUM_MAX_VAR 256 +#define NUM_MAX_VAR 255  extern objectStruct objectTable[NUM_MAX_OBJECT]; @@ -60,15 +60,17 @@ void loadObject(char *pObjectName);  void setupObject(byte objIdx, uint16 param1, uint16 param2, uint16 param3, uint16 param4);  void modifyObjectParam(byte objIdx, byte paramIdx, int16 newValue); -void addOverlay(uint16 objIdx, uint16 param); +void addOverlay(uint16 objIdx, uint16 type);  int removeOverlay(uint16 objIdx, uint16 param); -void addGfxElementA0(int16 objIdx, int16 param); -void removeGfxElementA0(int16 objIdx, int16 param); +void addGfxElement(int16 objIdx, int16 param, int16 type); +void removeGfxElement(int16 objIdx, int16 param, int16 type);  int16 getObjectParam(uint16 objIdx, uint16 paramIdx);  void addObjectParam(byte objIdx, byte paramIdx, int16 newValue);  void subObjectParam(byte objIdx, byte paramIdx, int16 newValue); +bool compareRanges(uint16 aStart, uint16 aEnd, uint16 bStart, uint16 bEnd); +uint16 compareObjectParamRanges(uint16 objIdx1, uint16 xAdd1, uint16 yAdd1, uint16 maskAdd1, uint16 objIdx2, uint16 xAdd2, uint16 yAdd2, uint16 maskAdd2);  uint16 compareObjectParam(byte objIdx, byte param1, int16 param2);  } // End of namespace Cine diff --git a/engines/cine/pal.h b/engines/cine/pal.h index 70fcc0d98a..768cf0d27d 100644 --- a/engines/cine/pal.h +++ b/engines/cine/pal.h @@ -34,6 +34,8 @@ struct PalEntry {  	byte pal2[16];  }; +extern PalEntry *palPtr; +  void loadPal(const char *fileName);  void loadRelatedPalette(const char *fileName); diff --git a/engines/cine/part.cpp b/engines/cine/part.cpp index b39f1eff7d..88f2dcef52 100644 --- a/engines/cine/part.cpp +++ b/engines/cine/part.cpp @@ -289,8 +289,8 @@ void dumpBundle(const char *fileName) {  		debug(0, "%s", partBuffer[i].partName); -		Common::File out; -		if (out.open(Common::String("dumps/") + partBuffer[i].partName, Common::File::kFileWriteMode)) { +		Common::DumpFile out; +		if (out.open(Common::String("dumps/") + partBuffer[i].partName)) {  			out.write(data, partBuffer[i].unpackedSize);  			out.close();  		} diff --git a/engines/cine/prc.cpp b/engines/cine/prc.cpp index 402c97b1a6..27b1044620 100644 --- a/engines/cine/prc.cpp +++ b/engines/cine/prc.cpp @@ -40,8 +40,9 @@ ScriptList objectScripts;  /*! \todo Is script size of 0 valid?   * \todo Fix script dump code + * @return Was the loading successful?   */ -void loadPrc(const char *pPrcName) { +bool loadPrc(const char *pPrcName) {  	byte i;  	uint16 numScripts;  	byte *scriptPtr, *dataPtr; @@ -52,9 +53,9 @@ void loadPrc(const char *pPrcName) {  	scriptTable.clear();  	// This is copy protection. Used to hang the machine -	if (!scumm_stricmp(pPrcName, "L201.ANI")) { +	if (!scumm_stricmp(pPrcName, COPY_PROT_FAIL_PRC_NAME)) {  		exitEngine = 1; -		return; +		return false;  	}  	checkDataDisk(-1); @@ -107,6 +108,8 @@ void loadPrc(const char *pPrcName) {  		}  	}  #endif + +	return true;  }  } // End of namespace Cine diff --git a/engines/cine/prc.h b/engines/cine/prc.h index f5129d28b1..05bb240372 100644 --- a/engines/cine/prc.h +++ b/engines/cine/prc.h @@ -31,7 +31,7 @@ namespace Cine {  extern ScriptList globalScripts;  extern ScriptList objectScripts; -void loadPrc(const char *pPrcName); +bool loadPrc(const char *pPrcName);  } // End of namespace Cine diff --git a/engines/cine/script.h b/engines/cine/script.h index eeac0e8809..19576e4c1a 100644 --- a/engines/cine/script.h +++ b/engines/cine/script.h @@ -61,7 +61,7 @@ private:  public:  	// Explicit to prevent var=0 instead of var[i]=0 typos.  	explicit ScriptVars(unsigned int len = 50); -	ScriptVars(Common::InSaveFile &fHandle, unsigned int len = 50); +	ScriptVars(Common::SeekableReadStream &fHandle, unsigned int len = 50);  	ScriptVars(const ScriptVars &src);  	~ScriptVars(void); @@ -71,8 +71,8 @@ public:  	void save(Common::OutSaveFile &fHandle) const;  	void save(Common::OutSaveFile &fHandle, unsigned int len) const; -	void load(Common::InSaveFile &fHandle); -	void load(Common::InSaveFile &fHandle, unsigned int len); +	void load(Common::SeekableReadStream &fHandle); +	void load(Common::SeekableReadStream &fHandle, unsigned int len);  	void reset(void);  }; @@ -198,7 +198,7 @@ protected:  	int o1_blitAndFade();  	int o1_fadeToBlack();  	int o1_transformPaletteRange(); -	int o1_setDefaultMenuColor2(); +	int o1_setDefaultMenuBgColor();  	int o1_palRotate();  	int o1_break();  	int o1_endScript(); @@ -213,7 +213,7 @@ protected:  	int o1_initializeZoneData();  	int o1_setZoneDataEntry();  	int o1_getZoneDataEntry(); -	int o1_setDefaultMenuColor(); +	int o1_setPlayerCommandPosY();  	int o1_allowPlayerInput();  	int o1_disallowPlayerInput();  	int o1_changeDataDisk(); @@ -237,7 +237,7 @@ protected:  	int o2_playSample();  	int o2_playSampleAlt();  	int o2_op81(); -	int o2_op82(); +	int o2_modifySeqListElement();  	int o2_isSeqRunning();  	int o2_gotoIfSupNearest();  	int o2_gotoIfSupEquNearest(); @@ -258,10 +258,10 @@ protected:  	int o2_useBgScroll();  	int o2_setAdditionalBgVScroll();  	int o2_op9F(); -	int o2_addGfxElementA0(); -	int o2_removeGfxElementA0(); -	int o2_opA2(); -	int o2_opA3(); +	int o2_addGfxElementType20(); +	int o2_removeGfxElementType20(); +	int o2_addGfxElementType21(); +	int o2_removeGfxElementType21();  	int o2_loadMask22();  	int o2_unloadMask22(); @@ -371,16 +371,16 @@ void dumpScript(char *dumpName);  #define OP_requestCheckPendingDataLoad  0x42  #define OP_endScript                    0x50 -void addScriptToList0(uint16 idx); +void addScriptToGlobalScripts(uint16 idx);  int16 checkCollision(int16 objIdx, int16 x, int16 y, int16 numZones, int16 zoneIdx);  void runObjectScript(int16 entryIdx); -void executeList1(void); -void executeList0(void); +void executeObjectScripts(void); +void executeGlobalScripts(void); -void purgeList1(void); -void purgeList0(void); +void purgeObjectScripts(void); +void purgeGlobalScripts(void);  } // End of namespace Cine diff --git a/engines/cine/script_fw.cpp b/engines/cine/script_fw.cpp index 845120c99e..e761a0c8e4 100644 --- a/engines/cine/script_fw.cpp +++ b/engines/cine/script_fw.cpp @@ -38,7 +38,13 @@  namespace Cine { -ScriptVars globalVars(NUM_MAX_VAR); +/** + * Global variables. + * 255 of these are saved, but there's one more that's used for bypassing the copy protection. + * In CineEngine::mainLoop(int bootScriptIdx) there's this code: globalVars[VAR_BYPASS_PROTECTION] = 0; + * And as VAR_BYPASS_PROTECTION is 255 that's why we're allocating one more than we otherwise would. + */ +ScriptVars globalVars(NUM_MAX_VAR + 1);  uint16 compareVars(int16 a, int16 b); @@ -135,7 +141,7 @@ const Opcode FWScript::_opcodeTable[] = {  	{ &FWScript::o1_transformPaletteRange, "bbwww" },  	/* 48 */  	{ 0, 0 }, -	{ &FWScript::o1_setDefaultMenuColor2, "b" }, +	{ &FWScript::o1_setDefaultMenuBgColor, "b" },  	{ &FWScript::o1_palRotate, "bbb" },  	{ 0, 0 },  	/* 4C */ @@ -174,7 +180,7 @@ const Opcode FWScript::_opcodeTable[] = {  	{ &FWScript::o1_setZoneDataEntry, "bw" },  	{ &FWScript::o1_getZoneDataEntry, "bb" },  	/* 68 */ -	{ &FWScript::o1_setDefaultMenuColor, "b" }, +	{ &FWScript::o1_setPlayerCommandPosY, "b" },  	{ &FWScript::o1_allowPlayerInput, "" },  	{ &FWScript::o1_disallowPlayerInput, "" },  	{ &FWScript::o1_changeDataDisk, "b" }, @@ -230,7 +236,7 @@ ScriptVars::ScriptVars(unsigned int len) : _size(len), _vars(new int16[len]) {   * \param fHandle Savefile open for reading   * \param len Size of array   */ -ScriptVars::ScriptVars(Common::InSaveFile &fHandle, unsigned int len) +ScriptVars::ScriptVars(Common::SeekableReadStream &fHandle, unsigned int len)  	: _size(len), _vars(new int16[len]) {  	assert(_vars); @@ -306,7 +312,7 @@ void ScriptVars::save(Common::OutSaveFile &fHandle, unsigned int len) const {  /*! \brief Restore array from savefile   * \param fHandle Savefile open for reading   */ -void ScriptVars::load(Common::InSaveFile &fHandle) { +void ScriptVars::load(Common::SeekableReadStream &fHandle) {  	load(fHandle, _size);  } @@ -314,7 +320,7 @@ void ScriptVars::load(Common::InSaveFile &fHandle) {   * \param fHandle Savefile open for reading   * \param len Length of data to be read   */ -void ScriptVars::load(Common::InSaveFile &fHandle, unsigned int len) { +void ScriptVars::load(Common::SeekableReadStream &fHandle, unsigned int len) {  	debug(6, "assert(%d <= %d)", len, _size);  	assert(len <= _size);  	for (unsigned int i = 0; i < len; i++) { @@ -1019,6 +1025,20 @@ int FWScript::o1_divVar() {  }  int FWScript::o1_compareVar() { +	// WORKAROUND: A workaround for a script bug in script file CODE2.PRC +	// in at least some of the Amiga and Atari ST versions of Future Wars. +	// Fixes bug #2016647 (FW: crash with italian amiga version). A local +	// variable 251 is compared against value 0 although it's quite apparent +	// from the context in the script that instead global variable 251 should +	// be compared against value 0. So looks like someone made a typo when +	// making the scripts. Therefore we change that particular comparison +	// from using the local variable 251 to using the global variable 251. +	if (g_cine->getGameType() == Cine::GType_FW && scumm_stricmp(currentPrcName, "CODE2.PRC") == 0 && +		(g_cine->getPlatform() == Common::kPlatformAmiga || g_cine->getPlatform() == Common::kPlatformAtariST) && +		_script.getByte(_pos) == 251 && _script.getByte(_pos + 1) == 0 && _script.getWord(_pos + 2) == 0) { +		return o1_compareGlobalVar(); +	} +  	byte varIdx = getNextByte();  	byte varType = getNextByte(); @@ -1259,7 +1279,7 @@ int FWScript::o1_startGlobalScript() {  	assert(param < NUM_MAX_SCRIPT);  	debugC(5, kCineDebugScript, "Line: %d: startScript(%d)", _line, param); -	addScriptToList0(param); +	addScriptToGlobalScripts(param);  	return 0;  } @@ -1385,10 +1405,11 @@ int FWScript::o1_transformPaletteRange() {  	return 0;  } -int FWScript::o1_setDefaultMenuColor2() { +/** Set the default background color used for message boxes. */ +int FWScript::o1_setDefaultMenuBgColor() {  	byte param = getNextByte(); -	debugC(5, kCineDebugScript, "Line: %d: setDefaultMenuColor2(%d)", _line, param); +	debugC(5, kCineDebugScript, "Line: %d: setDefaultMenuBgColor(%d)", _line, param);  	renderer->_messageBg = param;  	return 0; @@ -1554,10 +1575,11 @@ int FWScript::o1_getZoneDataEntry() {  	return 0;  } -int FWScript::o1_setDefaultMenuColor() { +/** Set the player command string's vertical position on-screen. */ +int FWScript::o1_setPlayerCommandPosY() {  	byte param = getNextByte(); -	debugC(5, kCineDebugScript, "Line: %d: setDefaultMenuColor(%d)", _line, param); +	debugC(5, kCineDebugScript, "Line: %d: setPlayerCommandPosY(%d)", _line, param);  	renderer->_cmdY = param;  	return 0; @@ -1732,7 +1754,7 @@ int FWScript::o1_unloadMask5() {  //----------------------------------------------------------------------- -void addScriptToList0(uint16 idx) { +void addScriptToGlobalScripts(uint16 idx) {  	ScriptPtr tmp(scriptInfo->create(*scriptTable[idx], idx));  	assert(tmp);  	globalScripts.push_back(tmp); @@ -1764,18 +1786,32 @@ int16 checkCollision(int16 objIdx, int16 x, int16 y, int16 numZones, int16 zoneI  	int16 lx = objectTable[objIdx].x + x;  	int16 ly = objectTable[objIdx].y + y;  	int16 idx; +	int16 result = 0;  	for (int16 i = 0; i < numZones; i++) {  		idx = getZoneFromPositionRaw(page3Raw, lx + i, ly, 320); -		assert(idx >= 0 && idx <= NUM_MAX_ZONE); +		assert(idx >= 0 && idx < NUM_MAX_ZONE); + +		// The zoneQuery table is updated here only in Operation Stealth +		if (g_cine->getGameType() == Cine::GType_OS) { +			if (zoneData[idx] < NUM_MAX_ZONE) { +				zoneQuery[zoneData[idx]]++; +			} +		}  		if (zoneData[idx] == zoneIdx) { -			return 1; +			result = 1; +			// Future Wars breaks out early on the first match, but +			// Operation Stealth doesn't because it needs to update +			// the zoneQuery table for the whole loop's period. +			if (g_cine->getGameType() == Cine::GType_FW) { +				break; +			}  		}  	} -	return 0; +	return result;  }  uint16 compareVars(int16 a, int16 b) { @@ -1792,7 +1828,7 @@ uint16 compareVars(int16 a, int16 b) {  	return flag;  } -void executeList1(void) { +void executeObjectScripts(void) {  	ScriptList::iterator it = objectScripts.begin();  	for (; it != objectScripts.end();) {  		if ((*it)->_index < 0 || (*it)->execute() < 0) { @@ -1803,7 +1839,7 @@ void executeList1(void) {  	}  } -void executeList0(void) { +void executeGlobalScripts(void) {  	ScriptList::iterator it = globalScripts.begin();  	for (; it != globalScripts.end();) {  		if ((*it)->_index < 0 || (*it)->execute() < 0) { @@ -1814,12 +1850,16 @@ void executeList0(void) {  	}  } -/*! \todo objectScripts.clear()? +/*! \todo Remove object scripts with script index of -1 (Not script position, but script index!). + *        This would seem to be valid for both Future Wars and Operation Stealth.   */ -void purgeList1(void) { +void purgeObjectScripts(void) {  } -void purgeList0(void) { +/*! \todo Remove global scripts with script index of -1 (Not script position, but script index!). + *        This would seem to be valid for both Future Wars and Operation Stealth. + */ +void purgeGlobalScripts(void) {  }  //////////////////////////////////// @@ -2352,7 +2392,7 @@ void decompileScript(const byte *scriptPtr, uint16 scriptSize, uint16 scriptIdx)  				param = *(localScriptPtr + position);  				position++; -				sprintf(lineBuffer, "setDefaultMenuColor2(%d)\n", param); +				sprintf(lineBuffer, "setDefaultMenuBgColor(%d)\n", param);  				break;  			} @@ -2502,7 +2542,7 @@ void decompileScript(const byte *scriptPtr, uint16 scriptSize, uint16 scriptIdx)  				param = *(localScriptPtr + position);  				position++; -				sprintf(lineBuffer, "setDefaultMenuBoxColor(%d)\n", param); +				sprintf(lineBuffer, "setPlayerCommandPosY(%d)\n", param);  				break;  			} @@ -2917,10 +2957,10 @@ void decompileScript(const byte *scriptPtr, uint16 scriptSize, uint16 scriptIdx)  }  void dumpScript(char *dumpName) { -    Common::File fHandle; +    Common::DumpFile fHandle;  	uint16 i; -	fHandle.open(dumpName, Common::File::kFileWriteMode); +	fHandle.open(dumpName);  	for (i = 0; i < decompileBufferPosition; i++) {  		fHandle.writeString(Common::String(decompileBuffer[i])); diff --git a/engines/cine/script_os.cpp b/engines/cine/script_os.cpp index 319fca5d3c..a764281758 100644 --- a/engines/cine/script_os.cpp +++ b/engines/cine/script_os.cpp @@ -131,7 +131,7 @@ const Opcode OSScript::_opcodeTable[] = {  	{ &FWScript::o1_transformPaletteRange, "bbwww" },  	/* 48 */  	{ 0, 0 }, -	{ &FWScript::o1_setDefaultMenuColor2, "b" }, +	{ &FWScript::o1_setDefaultMenuBgColor, "b" },  	{ &FWScript::o1_palRotate, "bbb" },  	{ 0, 0 },  	/* 4C */ @@ -170,7 +170,7 @@ const Opcode OSScript::_opcodeTable[] = {  	{ &FWScript::o1_setZoneDataEntry, "bw" },  	{ &FWScript::o1_getZoneDataEntry, "bb" },  	/* 68 */ -	{ &FWScript::o1_setDefaultMenuColor, "b" }, +	{ &FWScript::o1_setPlayerCommandPosY, "b" },  	{ &FWScript::o1_allowPlayerInput, "" },  	{ &FWScript::o1_disallowPlayerInput, "" },  	{ &FWScript::o1_changeDataDisk, "b" }, /* Same as opcodes 0x95 and 0xA9. */ @@ -202,7 +202,7 @@ const Opcode OSScript::_opcodeTable[] = {  	/* 80 */  	{ &FWScript::o2_removeSeq, "bb" },  	{ &FWScript::o2_op81, "" }, /* TODO: Name this opcode properly. */ -	{ &FWScript::o2_op82, "bbwwb" }, /* TODO: Name this opcode properly. */ +	{ &FWScript::o2_modifySeqListElement, "bbwwb" },  	{ &FWScript::o2_isSeqRunning, "bb" },  	/* 84 */  	{ &FWScript::o2_gotoIfSupNearest, "b" }, @@ -240,10 +240,10 @@ const Opcode OSScript::_opcodeTable[] = {  	{ &FWScript::o2_setAdditionalBgVScroll, "c" },  	{ &FWScript::o2_op9F, "ww" }, /* TODO: Name this opcode properly. */  	/* A0 */ -	{ &FWScript::o2_addGfxElementA0, "ww" }, /* TODO: Name this opcode properly. */ -	{ &FWScript::o2_removeGfxElementA0, "ww" }, /* TODO: Name this opcode properly. */ -	{ &FWScript::o2_opA2, "ww" }, /* TODO: Name this opcode properly. */ -	{ &FWScript::o2_opA3, "ww" }, /* TODO: Name this opcode properly. */ +	{ &FWScript::o2_addGfxElementType20, "ww" }, /* TODO: Name this opcode properly. */ +	{ &FWScript::o2_removeGfxElementType20, "ww" }, /* TODO: Name this opcode properly. */ +	{ &FWScript::o2_addGfxElementType21, "ww" }, /* TODO: Name this opcode properly. */ +	{ &FWScript::o2_removeGfxElementType21, "ww" }, /* TODO: Name this opcode properly. */  	/* A4 */  	{ &FWScript::o2_loadMask22, "b" }, /* TODO: Name this opcode properly. */  	{ &FWScript::o2_unloadMask22, "b" }, /* TODO: Name this opcode properly. */ @@ -442,6 +442,7 @@ int FWScript::o2_removeSeq() {  }  /*! \todo Implement this instruction + * \note According to the scripts' opcode usage comparison this opcode isn't used at all.   */  int FWScript::o2_op81() {  	warning("STUB: o2_op81()"); @@ -449,23 +450,25 @@ int FWScript::o2_op81() {  	return 0;  } -/*! \todo Implement this instruction - */ -int FWScript::o2_op82() { +int FWScript::o2_modifySeqListElement() {  	byte a = getNextByte();  	byte b = getNextByte();  	uint16 c = getNextWord();  	uint16 d = getNextWord();  	byte e = getNextByte(); -	warning("STUB: o2_op82(%x, %x, %x, %x, %x)", a, b, c, d, e); +	debugC(5, kCineDebugScript, "Line: %d: o2_modifySeqListElement(%d,%d,%d,%d,%d)", _line, a, b, c, d, e); + +	modifySeqListElement(a, 0, b, c, d, e);	  	return 0;  } +/*! \todo Check whether this opcode's name is backwards (i.e. should it be o2_isSeqNotRunning?) + */  int FWScript::o2_isSeqRunning() {  	byte a = getNextByte();  	byte b = getNextByte(); -	debugC(5, kCineDebugScript, "Line: %d: OP83(%d,%d) -> TODO", _line, a, b); +	debugC(5, kCineDebugScript, "Line: %d: o2_isSeqRunning(%d,%d)", _line, a, b);  	if (isSeqRunning(a, 0, b)) {  		_compare = 1; @@ -593,19 +596,18 @@ int FWScript::o2_stopObjectScript() {  	return 0;  } -/*! \todo Implement this instruction - */  int FWScript::o2_op8D() { -	uint16 a = getNextWord(); -	uint16 b = getNextWord(); -	uint16 c = getNextWord(); -	uint16 d = getNextWord(); -	uint16 e = getNextWord(); -	uint16 f = getNextWord(); -	uint16 g = getNextWord(); -	uint16 h = getNextWord(); -	warning("STUB: o2_op8D(%x, %x, %x, %x, %x, %x, %x, %x)", a, b, c, d, e, f, g, h); -	// _currentScriptElement->compareResult = ... +	uint16 objIdx1  = getNextWord(); +	uint16 xAdd1    = getNextWord(); +	uint16 yAdd1    = getNextWord(); +	uint16 maskAdd1 = getNextWord(); +	uint16 objIdx2  = getNextWord(); +	uint16 xAdd2    = getNextWord(); +	uint16 yAdd2    = getNextWord(); +	uint16 maskAdd2 = getNextWord(); +	debugC(5, kCineDebugScript, "Line: %d: o2_op8D(%d, %d, %d, %d, %d, %d, %d, %d)", _line, objIdx1, xAdd1, yAdd1, maskAdd1, objIdx2, xAdd2, yAdd2, maskAdd2); + +	_compare = compareObjectParamRanges(objIdx1, xAdd1, yAdd1, maskAdd1, objIdx2, xAdd2, yAdd2, maskAdd2);  	return 0;  } @@ -649,16 +651,15 @@ int FWScript::o2_loadBg() {  	return 0;  } -/*! \todo Check the current implementation for correctness - */  int FWScript::o2_wasZoneChecked() {  	byte param = getNextByte(); -	_compare = (param < 16 && zoneData[param]); +	_compare = (param < NUM_MAX_ZONE && zoneQuery[param]) ? 1 : 0;  	debugC(5, kCineDebugScript, "Line: %d: o2_wasZoneChecked(%d)", _line, param);  	return 0;  }  /*! \todo Implement this instruction + * \note According to the scripts' opcode usage comparison this opcode isn't used at all.   */  int FWScript::o2_op9B() {  	uint16 a = getNextWord(); @@ -674,6 +675,7 @@ int FWScript::o2_op9B() {  }  /*! \todo Implement this instruction + * \note According to the scripts' opcode usage comparison this opcode isn't used at all.   */  int FWScript::o2_op9C() {  	uint16 a = getNextWord(); @@ -713,6 +715,7 @@ int FWScript::o2_setAdditionalBgVScroll() {  }  /*! \todo Implement this instruction + * \note According to the scripts' opcode usage comparison this opcode isn't used at all.   */  int FWScript::o2_op9F() {  	warning("o2_op9F()"); @@ -721,42 +724,36 @@ int FWScript::o2_op9F() {  	return 0;  } -int FWScript::o2_addGfxElementA0() { +int FWScript::o2_addGfxElementType20() {  	uint16 param1 = getNextWord();  	uint16 param2 = getNextWord(); -	debugC(5, kCineDebugScript, "Line: %d: addGfxElementA0(%d,%d)", _line, param1, param2); -	addGfxElementA0(param1, param2); +	debugC(5, kCineDebugScript, "Line: %d: o2_addGfxElementType20(%d,%d)", _line, param1, param2); +	addGfxElement(param1, param2, 20);  	return 0;  } -/*! \todo Implement this instruction - */ -int FWScript::o2_removeGfxElementA0() { +int FWScript::o2_removeGfxElementType20() {  	uint16 idx = getNextWord();  	uint16 param = getNextWord(); -	warning("STUB? o2_removeGfxElementA0(%x, %x)", idx, param); -	removeGfxElementA0(idx, param); +	debugC(5, kCineDebugScript, "Line: %d: o2_removeGfxElementType20(%d,%d)", _line, idx, param); +	removeGfxElement(idx, param, 20);  	return 0;  } -/*! \todo Implement this instruction - */ -int FWScript::o2_opA2() { +int FWScript::o2_addGfxElementType21() {  	uint16 a = getNextWord();  	uint16 b = getNextWord(); -	warning("STUB: o2_opA2(%x, %x)", a, b); -	// addGfxElementA2(); +	debugC(5, kCineDebugScript, "Line: %d: o2_addGfxElementType21(%d,%d)", _line, a, b); +	addGfxElement(a, b, 21);  	return 0;  } -/*! \todo Implement this instruction - */ -int FWScript::o2_opA3() { +int FWScript::o2_removeGfxElementType21() {  	uint16 a = getNextWord();  	uint16 b = getNextWord(); -	warning("STUB: o2_opA3(%x, %x)", a, b); -	// removeGfxElementA2(); +	debugC(5, kCineDebugScript, "Line: %d: o2_removeGfxElementType21(%d,%d)", _line, a, b); +	removeGfxElement(a, b, 21);  	return 0;  } diff --git a/engines/cine/sound.cpp b/engines/cine/sound.cpp index e808de6922..f26032fe98 100644 --- a/engines/cine/sound.cpp +++ b/engines/cine/sound.cpp @@ -249,6 +249,7 @@ AdlibSoundDriver::AdlibSoundDriver(Audio::Mixer *mixer)  AdlibSoundDriver::~AdlibSoundDriver() {  	_mixer->stopHandle(_soundHandle); +	OPLDestroy(_opl);  }  void AdlibSoundDriver::setupChannel(int channel, const byte *data, int instrument, int volume) { diff --git a/engines/cine/texte.cpp b/engines/cine/texte.cpp index 9b4b83f420..e4fd334926 100644 --- a/engines/cine/texte.cpp +++ b/engines/cine/texte.cpp @@ -31,8 +31,6 @@ namespace Cine {  byte *textDataPtr; -byte textTable[256][2][16 * 8]; -  const char **failureMessages;  const CommandeType *defaultActionCommand;  const CommandeType *systemMenu; @@ -77,14 +75,14 @@ void loadTextData(const char *pFileName, byte *pDestinationBuffer) {  		loadRelatedPalette(pFileName);  		for (i = 0; i < numCharacters; i++) { -			gfxConvertSpriteToRaw(textTable[i][0], tempBuffer, 16, 8); -			generateMask(textTable[i][0], textTable[i][1], 16 * 8, 0); +			gfxConvertSpriteToRaw(g_cine->_textHandler.textTable[i][0], tempBuffer, 16, 8); +			generateMask(g_cine->_textHandler.textTable[i][0], g_cine->_textHandler.textTable[i][1], 16 * 8, 0);  			tempBuffer += dataSize;  		}  	} else {  		for (i = 0; i < 90; i++) { -			gfxConvertSpriteToRaw(textTable[i][0], tempBuffer, 8, 8); -			generateMask(textTable[i][0], textTable[i][1], 8 * 8, 0); +			gfxConvertSpriteToRaw(g_cine->_textHandler.textTable[i][0], tempBuffer, 8, 8); +			generateMask(g_cine->_textHandler.textTable[i][0], g_cine->_textHandler.textTable[i][1], 8 * 8, 0);  			tempBuffer += 0x40;  		}  	} diff --git a/engines/cine/texte.h b/engines/cine/texte.h index ae82832aea..f471c3c49e 100644 --- a/engines/cine/texte.h +++ b/engines/cine/texte.h @@ -34,7 +34,10 @@ namespace Cine {  typedef char CommandeType[20];  extern byte *textDataPtr; -extern byte textTable[256][2][16 * 8]; + +struct TextHandler { +	byte textTable[256][2][16 * 8]; +};  extern const char **failureMessages;  extern const CommandeType *defaultActionCommand; diff --git a/engines/cine/unpack.cpp b/engines/cine/unpack.cpp index dcd3181242..5d85ff6cab 100644 --- a/engines/cine/unpack.cpp +++ b/engines/cine/unpack.cpp @@ -111,7 +111,7 @@ bool CineUnpacker::unpack(const byte *src, uint srcLen, byte *dst, uint dstLen)  	while (_dst >= _dstBegin && !_error) {  		/*  		Bits  => Action: -		0 0   => unpackRawBytes(3 bits + 1)              i.e. unpackRawBytes(1..9) +		0 0   => unpackRawBytes(3 bits + 1)              i.e. unpackRawBytes(1..8)  		1 1 1 => unpackRawBytes(8 bits + 9)              i.e. unpackRawBytes(9..264)  		0 1   => copyRelocatedBytes(8 bits, 2)           i.e. copyRelocatedBytes(0..255, 2)  		1 0 0 => copyRelocatedBytes(9 bits, 3)           i.e. copyRelocatedBytes(0..511, 3) diff --git a/engines/cine/various.cpp b/engines/cine/various.cpp index 9b98ddb253..2fcb015fcd 100644 --- a/engines/cine/various.cpp +++ b/engines/cine/various.cpp @@ -95,6 +95,9 @@ int16 saveVar2;  byte isInPause = 0; +// TODO: Implement inputVar0's changes in the program +// Currently inputVar0 isn't updated anywhere even though it's used at least in processSeqListElement. +uint16 inputVar0 = 0;  byte inputVar1 = 0;  uint16 inputVar2 = 0, inputVar3 = 0; @@ -112,6 +115,7 @@ int16 objListTab[20];  uint16 exitEngine;  uint16 zoneData[NUM_MAX_ZONE]; +uint16 zoneQuery[NUM_MAX_ZONE]; //!< Only exists in Operation Stealth  void stopMusicAfterFadeOut(void) { @@ -132,6 +136,7 @@ void runObjectScript(int16 entryIdx) {   */  void addPlayerCommandMessage(int16 cmd) {  	overlay tmp; +	memset(&tmp, 0, sizeof(tmp));  	tmp.objIdx = cmd;  	tmp.type = 3; @@ -224,6 +229,143 @@ int16 getObjectUnderCursor(uint16 x, uint16 y) {  	return -1;  } +bool writeChunkHeader(Common::OutSaveFile &out, const ChunkHeader &header) { +	out.writeUint32BE(header.id); +	out.writeUint32BE(header.version); +	out.writeUint32BE(header.size); +	return !out.ioFailed(); +} + +bool loadChunkHeader(Common::SeekableReadStream &in, ChunkHeader &header) { +	header.id      = in.readUint32BE(); +	header.version = in.readUint32BE(); +	header.size    = in.readUint32BE(); +	return !in.ioFailed(); +} + +void saveObjectTable(Common::OutSaveFile &out) { +	out.writeUint16BE(NUM_MAX_OBJECT); // Entry count +	out.writeUint16BE(0x20); // Entry size + +	for (int i = 0; i < NUM_MAX_OBJECT; i++) { +		out.writeUint16BE(objectTable[i].x); +		out.writeUint16BE(objectTable[i].y); +		out.writeUint16BE(objectTable[i].mask); +		out.writeUint16BE(objectTable[i].frame); +		out.writeUint16BE(objectTable[i].costume); +		out.write(objectTable[i].name, 20); +		out.writeUint16BE(objectTable[i].part); +	} +} + +void saveZoneData(Common::OutSaveFile &out) { +	for (int i = 0; i < 16; i++) { +		out.writeUint16BE(zoneData[i]); +	} +} + +void saveCommandVariables(Common::OutSaveFile &out) { +	for (int i = 0; i < 4; i++) { +		out.writeUint16BE(commandVar3[i]); +	} +} + +void saveAnimDataTable(Common::OutSaveFile &out) { +	out.writeUint16BE(NUM_MAX_ANIMDATA); // Entry count +	out.writeUint16BE(0x1E); // Entry size + +	for (int i = 0; i < NUM_MAX_ANIMDATA; i++) { +		animDataTable[i].save(out); +	} +} + +void saveScreenParams(Common::OutSaveFile &out) { +	// Screen parameters, unhandled +	out.writeUint16BE(0); +	out.writeUint16BE(0); +	out.writeUint16BE(0); +	out.writeUint16BE(0); +	out.writeUint16BE(0); +	out.writeUint16BE(0); +} + +void saveGlobalScripts(Common::OutSaveFile &out) { +	ScriptList::const_iterator it; +	out.writeUint16BE(globalScripts.size()); +	for (it = globalScripts.begin(); it != globalScripts.end(); ++it) { +		(*it)->save(out); +	} +} + +void saveObjectScripts(Common::OutSaveFile &out) { +	ScriptList::const_iterator it; +	out.writeUint16BE(objectScripts.size()); +	for (it = objectScripts.begin(); it != objectScripts.end(); ++it) { +		(*it)->save(out); +	} +} + +void saveOverlayList(Common::OutSaveFile &out) { +	Common::List<overlay>::const_iterator it; + +	out.writeUint16BE(overlayList.size()); + +	for (it = overlayList.begin(); it != overlayList.end(); ++it) { +		out.writeUint32BE(0); // next +		out.writeUint32BE(0); // previous? +		out.writeUint16BE(it->objIdx); +		out.writeUint16BE(it->type); +		out.writeSint16BE(it->x); +		out.writeSint16BE(it->y); +		out.writeSint16BE(it->width); +		out.writeSint16BE(it->color); +	} +} + +void saveBgIncrustList(Common::OutSaveFile &out) { +	Common::List<BGIncrust>::const_iterator it; +	out.writeUint16BE(bgIncrustList.size()); + +	for (it = bgIncrustList.begin(); it != bgIncrustList.end(); ++it) { +		out.writeUint32BE(0); // next +		out.writeUint32BE(0); // previous? +		out.writeUint16BE(it->objIdx); +		out.writeUint16BE(it->param); +		out.writeUint16BE(it->x); +		out.writeUint16BE(it->y); +		out.writeUint16BE(it->frame); +		out.writeUint16BE(it->part); +	} +} + +void saveZoneQuery(Common::OutSaveFile &out) { +	for (int i = 0; i < 16; i++) { +		out.writeUint16BE(zoneQuery[i]); +	} +} + +void saveSeqList(Common::OutSaveFile &out) { +	Common::List<SeqListElement>::const_iterator it; +	out.writeUint16BE(seqList.size()); + +	for (it = seqList.begin(); it != seqList.end(); ++it) { +		out.writeSint16BE(it->var4); +		out.writeUint16BE(it->objIdx); +		out.writeSint16BE(it->var8); +		out.writeSint16BE(it->frame); +		out.writeSint16BE(it->varC); +		out.writeSint16BE(it->varE); +		out.writeSint16BE(it->var10); +		out.writeSint16BE(it->var12); +		out.writeSint16BE(it->var14); +		out.writeSint16BE(it->var16); +		out.writeSint16BE(it->var18); +		out.writeSint16BE(it->var1A); +		out.writeSint16BE(it->var1C); +		out.writeSint16BE(it->var1E); +	} +} +  bool CineEngine::loadSaveDirectory(void) {  	Common::InSaveFile *fHandle;  	char tmp[80]; @@ -241,21 +383,143 @@ bool CineEngine::loadSaveDirectory(void) {  	return true;  } +/*! \brief Savegame format detector + * \param fHandle Savefile to check + * \return Savegame format on success, ANIMSIZE_UNKNOWN on failure + * + * This function seeks through the savefile and tries to determine the + * savegame format it uses. There's a miniscule chance that the detection + * algorithm could get confused and think that the file uses both the older + * and the newer format but that is such a remote possibility that I wouldn't + * worry about it at all. + * + * Also detects the temporary Operation Stealth savegame format now. + */ +enum CineSaveGameFormat detectSaveGameFormat(Common::SeekableReadStream &fHandle) { +	const uint32 prevStreamPos = fHandle.pos(); + +	// First check for the temporary Operation Stealth savegame format. +	fHandle.seek(0); +	ChunkHeader hdr; +	loadChunkHeader(fHandle, hdr); +	fHandle.seek(prevStreamPos); +	if (hdr.id == TEMP_OS_FORMAT_ID) { +		return TEMP_OS_FORMAT; +	} + +	// Ok, so the savegame isn't using the temporary Operation Stealth savegame format. +	// Let's check for the plain Future Wars savegame format and its different versions then. +	// The animDataTable begins at savefile position 0x2315. +	// Each animDataTable entry takes 23 bytes in older saves (Revisions 21772-31443) +	// and 30 bytes in the save format after that (Revision 31444 and onwards). +	// There are 255 entries in the animDataTable in both of the savefile formats. +	static const uint animDataTableStart = 0x2315; +	static const uint animEntriesCount = 255; +	static const uint oldAnimEntrySize = 23; +	static const uint newAnimEntrySize = 30; +	static const uint animEntrySizeChoices[] = {oldAnimEntrySize, newAnimEntrySize}; +	Common::Array<uint> animEntrySizeMatches; + +	// Try to walk through the savefile using different animDataTable entry sizes +	// and make a list of all the successful entry sizes. +	for (uint i = 0; i < ARRAYSIZE(animEntrySizeChoices); i++) { +		// 206 = 2 * 50 * 2 + 2 * 3 (Size of global and object script entries) +		// 20 = 4 * 2 + 2 * 6 (Size of overlay and background incrust entries) +		static const uint sizeofScreenParams = 2 * 6; +		static const uint globalScriptEntrySize = 206; +		static const uint objectScriptEntrySize = 206; +		static const uint overlayEntrySize = 20; +		static const uint bgIncrustEntrySize = 20; +		static const uint chainEntrySizes[] = { +			globalScriptEntrySize, +			objectScriptEntrySize, +			overlayEntrySize, +			bgIncrustEntrySize +		}; +		 +		uint animEntrySize = animEntrySizeChoices[i]; +		// Jump over the animDataTable entries and the screen parameters +		uint32 newPos = animDataTableStart + animEntrySize * animEntriesCount + sizeofScreenParams; +		// Check that there's data left after the point we're going to jump to +		if (newPos >= fHandle.size()) { +			continue; +		} +		fHandle.seek(newPos); + +		// Jump over the remaining items in the savegame file +		// (i.e. the global scripts, object scripts, overlays and background incrusts). +		bool chainWalkSuccess = true; +		for (uint chainIndex = 0; chainIndex < ARRAYSIZE(chainEntrySizes); chainIndex++) { +			// Read entry count and jump over the entries +			int entryCount = fHandle.readSint16BE(); +			newPos = fHandle.pos() + chainEntrySizes[chainIndex] * entryCount; +			// Check that we didn't go past the end of file. +			// Note that getting exactly to the end of file is acceptable. +			if (newPos > fHandle.size()) { +				chainWalkSuccess = false; +				break; +			} +			fHandle.seek(newPos); +		} +		 +		// If we could walk the chain successfully and +		// got exactly to the end of file then we've got a match. +		if (chainWalkSuccess && fHandle.pos() == fHandle.size()) { +			// We found a match, let's save it +			animEntrySizeMatches.push_back(animEntrySize); +		} +	} + +	// Check that we got only one entry size match. +	// If we didn't, then return an error. +	enum CineSaveGameFormat result = ANIMSIZE_UNKNOWN; +	if (animEntrySizeMatches.size() == 1) { +		const uint animEntrySize = animEntrySizeMatches[0]; +		assert(animEntrySize == oldAnimEntrySize || animEntrySize == newAnimEntrySize); +		if (animEntrySize == oldAnimEntrySize) { +			result = ANIMSIZE_23; +		} else { // animEntrySize == newAnimEntrySize		 +			// Check data and mask pointers in all of the animDataTable entries +			// to see whether we've got the version with the broken data and mask pointers or not. +			// In the broken format all data and mask pointers were always zero. +			static const uint relativeDataPos = 2 * 4; +			bool pointersIntact = false; +			for (uint i = 0; i < animEntriesCount; i++) { +				fHandle.seek(animDataTableStart + i * animEntrySize + relativeDataPos); +				uint32 data = fHandle.readUint32BE(); +				uint32 mask = fHandle.readUint32BE(); +				if ((data != 0) || (mask != 0)) { +					pointersIntact = true; +					break; +				} +			} +			result = (pointersIntact ? ANIMSIZE_30_PTRS_INTACT : ANIMSIZE_30_PTRS_BROKEN); +		} +	} else if (animEntrySizeMatches.size() > 1) { +		warning("Savegame format detector got confused by input data. Detecting savegame to be using an unknown format"); +	} else { // animEtrySizeMatches.size() == 0 +		debug(3, "Savegame format detector was unable to detect savegame's format"); +	} + +	fHandle.seek(prevStreamPos); +	return result; +} +  /*! \brief Restore script list item from savefile - * \param fHandle Savefile handlem open for reading + * \param fHandle Savefile handle open for reading   * \param isGlobal Restore object or global script?   */ -void loadScriptFromSave(Common::InSaveFile *fHandle, bool isGlobal) { +void loadScriptFromSave(Common::SeekableReadStream &fHandle, bool isGlobal) {  	ScriptVars localVars, labels;  	uint16 compare, pos;  	int16 idx; -	labels.load(*fHandle); -	localVars.load(*fHandle); +	labels.load(fHandle); +	localVars.load(fHandle); -	compare = fHandle->readUint16BE(); -	pos = fHandle->readUint16BE(); -	idx = fHandle->readUint16BE(); +	compare = fHandle.readUint16BE(); +	pos = fHandle.readUint16BE(); +	idx = fHandle.readUint16BE();  	// no way to reinitialize these  	if (idx < 0) { @@ -278,7 +542,7 @@ void loadScriptFromSave(Common::InSaveFile *fHandle, bool isGlobal) {  /*! \brief Restore overlay sprites from savefile   * \param fHandle Savefile open for reading   */ -void loadOverlayFromSave(Common::InSaveFile &fHandle) { +void loadOverlayFromSave(Common::SeekableReadStream &fHandle) {  	overlay tmp;  	fHandle.readUint32BE(); @@ -294,127 +558,10 @@ void loadOverlayFromSave(Common::InSaveFile &fHandle) {  	overlayList.push_back(tmp);  } -/*! \brief Savefile format tester - * \param fHandle Savefile to check - * - * This function seeks through savefile and tries to guess if it's the original - * savegame format or broken format from ScummVM 0.10/0.11 - * The test is incomplete but this should cover 99.99% of cases. - * If anyone makes a savefile which could confuse this test, assert will - * report it - */ -bool brokenSave(Common::InSaveFile &fHandle) { -	// Backward seeking not supported in compressed savefiles -	// if you really want it, finish it yourself -	return false; - -	// fixed size part: 14093 bytes (12308 bytes in broken save) -	// animDataTable begins at byte 6431 - -	int filesize = fHandle.size(); -	int startpos = fHandle.pos(); -	int pos, tmp; -	bool correct = false, broken = false; - -	// check for correct format -	while (filesize > 14093) { -		pos = 14093; - -		fHandle.seek(pos); -		tmp = fHandle.readUint16BE(); -		pos += 2 + tmp * 206; -		if (pos >= filesize) break; - -		fHandle.seek(pos); -		tmp = fHandle.readUint16BE(); -		pos += 2 + tmp * 206; -		if (pos >= filesize) break; - -		fHandle.seek(pos); -		tmp = fHandle.readUint16BE(); -		pos += 2 + tmp * 20; -		if (pos >= filesize) break; - -		fHandle.seek(pos); -		tmp = fHandle.readUint16BE(); -		pos += 2 + tmp * 20; - -		if (pos == filesize) correct = true; -		break; -	} -	debug(5, "brokenSave: correct format check %s: size=%d, pos=%d", -		correct ? "passed" : "failed", filesize, pos); - -	// check for broken format -	while (filesize > 12308) { -		pos = 12308; - -		fHandle.seek(pos); -		tmp = fHandle.readUint16BE(); -		pos += 2 + tmp * 206; -		if (pos >= filesize) break; - -		fHandle.seek(pos); -		tmp = fHandle.readUint16BE(); -		pos += 2 + tmp * 206; -		if (pos >= filesize) break; - -		fHandle.seek(pos); -		tmp = fHandle.readUint16BE(); -		pos += 2 + tmp * 20; -		if (pos >= filesize) break; - -		fHandle.seek(pos); -		tmp = fHandle.readUint16BE(); -		pos += 2 + tmp * 20; - -		if (pos == filesize) broken = true; -		break; -	} -	debug(5, "brokenSave: broken format check %s: size=%d, pos=%d", -		broken ? "passed" : "failed", filesize, pos); - -	// there's a very small chance that both cases will match -	// if anyone runs into it, you'll have to walk through -	// the animDataTable and try to open part file for each entry -	if (!correct && !broken) { -		error("brokenSave: file format check failed"); -	} else if (correct && broken) { -		error("brokenSave: both file formats seem to apply"); -	} - -	fHandle.seek(startpos); -	debug(5, "brokenSave: detected %s file format", -		correct ? "correct" : "broken"); - -	return broken; -} - -/*! \todo Implement Operation Stealth loading, this is obviously Future Wars only - */ -bool CineEngine::makeLoad(char *saveName) { -	int16 i; -	int16 size; -	bool broken; -	Common::InSaveFile *fHandle; -	char bgName[13]; - -	fHandle = g_saveFileMan->openForLoading(saveName); - -	if (!fHandle) { -		drawString(otherMessages[0], 0); -		waitPlayerInput(); -		// restoreScreen(); -		checkDataDisk(-1); -		return false; -	} - +void CineEngine::resetEngine() {  	g_sound->stopMusic();  	freeAnimDataTable();  	overlayList.clear(); -	// if (g_cine->getGameType() == Cine::GType_OS) { -	//	freeUnkList(); -	// }  	bgIncrustList.clear();  	closePart(); @@ -424,7 +571,9 @@ bool CineEngine::makeLoad(char *saveName) {  	scriptTable.clear();  	messageTable.clear(); -	for (i = 0; i < NUM_MAX_OBJECT; i++) { +	for (int i = 0; i < NUM_MAX_OBJECT; i++) { +		objectTable[i].x = 0; +		objectTable[i].y = 0;  		objectTable[i].part = 0;  		objectTable[i].name[0] = 0;  		objectTable[i].frame = 0; @@ -458,124 +607,381 @@ bool CineEngine::makeLoad(char *saveName) {  	checkForPendingDataLoadSwitch = 0; -	broken = brokenSave(*fHandle); +	if (g_cine->getGameType() == Cine::GType_OS) { +		seqList.clear(); +		currentAdditionalBgIdx = 0; +		currentAdditionalBgIdx2 = 0; +		// TODO: Add resetting of the following variables +		// adBgVar1 = 0; +		// adBgVar0 = 0;		 +		// gfxFadeOutCompleted = 0; +	} +} -	currentDisk = fHandle->readUint16BE(); +bool loadObjectTable(Common::SeekableReadStream &in) { +	in.readUint16BE(); // Entry count +	in.readUint16BE(); // Entry size -	fHandle->read(currentPartName, 13); -	fHandle->read(currentDatName, 13); +	for (int i = 0; i < NUM_MAX_OBJECT; i++) { +		objectTable[i].x = in.readSint16BE(); +		objectTable[i].y = in.readSint16BE(); +		objectTable[i].mask = in.readUint16BE(); +		objectTable[i].frame = in.readSint16BE(); +		objectTable[i].costume = in.readSint16BE(); +		in.read(objectTable[i].name, 20); +		objectTable[i].part = in.readUint16BE(); +	} +	return !in.ioFailed(); +} -	saveVar2 = fHandle->readSint16BE(); +bool loadZoneData(Common::SeekableReadStream &in) { +	for (int i = 0; i < 16; i++) { +		zoneData[i] = in.readUint16BE(); +	} +	return !in.ioFailed(); +} -	fHandle->read(currentPrcName, 13); -	fHandle->read(currentRelName, 13); -	fHandle->read(currentMsgName, 13); -	fHandle->read(bgName, 13); -	fHandle->read(currentCtName, 13); +bool loadCommandVariables(Common::SeekableReadStream &in) { +	for (int i = 0; i < 4; i++) { +		commandVar3[i] = in.readUint16BE(); +	} +	return !in.ioFailed(); +} -	checkDataDisk(currentDisk); +bool loadScreenParams(Common::SeekableReadStream &in) { +	// TODO: handle screen params (really required ?) +	in.readUint16BE(); +	in.readUint16BE(); +	in.readUint16BE(); +	in.readUint16BE(); +	in.readUint16BE(); +	in.readUint16BE(); +	return !in.ioFailed(); +} -	if (strlen(currentPartName)) { -		loadPart(currentPartName); +bool loadGlobalScripts(Common::SeekableReadStream &in) { +	int size = in.readSint16BE(); +	for (int i = 0; i < size; i++) { +		loadScriptFromSave(in, true);  	} +	return !in.ioFailed(); +} -	if (strlen(currentPrcName)) { -		loadPrc(currentPrcName); +bool loadObjectScripts(Common::SeekableReadStream &in) { +	int size = in.readSint16BE(); +	for (int i = 0; i < size; i++) { +		loadScriptFromSave(in, false);  	} +	return !in.ioFailed(); +} -	if (strlen(currentRelName)) { -		loadRel(currentRelName); +bool loadOverlayList(Common::SeekableReadStream &in) { +	int size = in.readSint16BE(); +	for (int i = 0; i < size; i++) { +		loadOverlayFromSave(in);  	} +	return !in.ioFailed(); +} -	if (strlen(bgName)) { -		loadBg(bgName); -	} +bool loadSeqList(Common::SeekableReadStream &in) { +	uint size = in.readUint16BE(); +	SeqListElement tmp; +	for (uint i = 0; i < size; i++) { +		tmp.var4   = in.readSint16BE(); +		tmp.objIdx = in.readUint16BE(); +		tmp.var8   = in.readSint16BE(); +		tmp.frame  = in.readSint16BE(); +		tmp.varC   = in.readSint16BE(); +		tmp.varE   = in.readSint16BE(); +		tmp.var10  = in.readSint16BE(); +		tmp.var12  = in.readSint16BE(); +		tmp.var14  = in.readSint16BE(); +		tmp.var16  = in.readSint16BE(); +		tmp.var18  = in.readSint16BE(); +		tmp.var1A  = in.readSint16BE(); +		tmp.var1C  = in.readSint16BE(); +		tmp.var1E  = in.readSint16BE(); +		seqList.push_back(tmp); +	} +	return !in.ioFailed(); +} -	if (strlen(currentCtName)) { -		loadCtFW(currentCtName); +bool loadZoneQuery(Common::SeekableReadStream &in) { +	for (int i = 0; i < 16; i++) { +		zoneQuery[i] = in.readUint16BE();  	} +	return !in.ioFailed(); +} -	fHandle->readUint16BE(); -	fHandle->readUint16BE(); +bool CineEngine::loadTempSaveOS(Common::SeekableReadStream &in) { +	char musicName[13]; +	char bgNames[8][13]; -	for (i = 0; i < 255; i++) { -		objectTable[i].x = fHandle->readSint16BE(); -		objectTable[i].y = fHandle->readSint16BE(); -		objectTable[i].mask = fHandle->readUint16BE(); -		objectTable[i].frame = fHandle->readSint16BE(); -		objectTable[i].costume = fHandle->readSint16BE(); -		fHandle->read(objectTable[i].name, 20); -		objectTable[i].part = fHandle->readUint16BE(); +	// First check the temporary Operation Stealth savegame format header. +	ChunkHeader hdr; +	loadChunkHeader(in, hdr); +	if (hdr.id != TEMP_OS_FORMAT_ID) { +		warning("loadTempSaveOS: File has incorrect identifier. Not loading savegame"); +		return false; +	} else if (hdr.version > CURRENT_OS_SAVE_VER) { +		warning("loadTempSaveOS: Detected newer format version. Not loading savegame"); +		return false;		 +	} else if ((int)hdr.version < (int)CURRENT_OS_SAVE_VER) { +		warning("loadTempSaveOS: Detected older format version. Trying to load nonetheless. Things may break"); +	} else { // hdr.id == TEMP_OS_FORMAT_ID && hdr.version == CURRENT_OS_SAVE_VER +		debug(3, "loadTempSaveOS: Found correct header (Both the identifier and version number match)."); +	} + +	// There shouldn't be any data in the header's chunk currently so it's an error if there is. +	if (hdr.size > 0) { +		warning("loadTempSaveOS: Format header's chunk seems to contain data so format is incorrect. Not loading savegame"); +		return false;  	} -	renderer->restorePalette(*fHandle); +	// Ok, so we've got a correct header for a temporary Operation Stealth savegame. +	// Let's start loading the plain savegame data then. +	currentDisk = in.readUint16BE();	 +	in.read(currentPartName, 13); +	in.read(currentPrcName, 13); +	in.read(currentRelName, 13); +	in.read(currentMsgName, 13); + +	// Load the 8 background names. +	for (uint i = 0; i < 8; i++) { +		in.read(bgNames[i], 13); +	} +	 +	in.read(currentCtName, 13); + +	// Moved the loading of current procedure, relation, +	// backgrounds and Ct here because if they were at the +	// end of this function then the global scripts loading +	// made an array out of bounds access. In the original +	// game's disassembly these aren't here but at the end. +	// The difference is probably in how we handle loading +	// the global scripts and some other things (i.e. the +	// loading routines aren't exactly the same and subtle +	// semantic differences result in having to do things +	// in a different order). +	{ +		// Not sure if this is needed with Operation Stealth... +		checkDataDisk(currentDisk); + +		if (strlen(currentPrcName)) { +			loadPrc(currentPrcName); +		} + +		if (strlen(currentRelName)) { +			loadRel(currentRelName); +		} + +		// Load first background (Uses loadBg) +		if (strlen(bgNames[0])) { +			loadBg(bgNames[0]); +		} -	globalVars.load(*fHandle, NUM_MAX_VAR - 1); +		// Add backgrounds 1-7 (Uses addBackground) +		for (int i = 1; i < 8; i++) { +			if (strlen(bgNames[i])) { +				addBackground(bgNames[i], i); +			} +		} -	for (i = 0; i < 16; i++) { -		zoneData[i] = fHandle->readUint16BE(); +		if (strlen(currentCtName)) { +			loadCtOS(currentCtName); +		}  	} -	for (i = 0; i < 4; i++) { -		commandVar3[i] = fHandle->readUint16BE(); +	loadObjectTable(in); +	renderer->restorePalette(in); +	globalVars.load(in, NUM_MAX_VAR); +	loadZoneData(in); +	loadCommandVariables(in); +	in.read(commandBuffer, 0x50); +	loadZoneQuery(in); + +	// TODO: Use the loaded string (Current music name (String, 13 bytes)). +	in.read(musicName, 13); + +	// TODO: Use the loaded value (Is music loaded? (Uint16BE, Boolean)). +	in.readUint16BE(); + +	// TODO: Use the loaded value (Is music playing? (Uint16BE, Boolean)). +	in.readUint16BE(); + +	renderer->_cmdY      = in.readUint16BE();	 +	in.readUint16BE(); // Some unknown variable that seems to always be zero +	allowPlayerInput     = in.readUint16BE(); +	playerCommand        = in.readUint16BE(); +	commandVar1          = in.readUint16BE(); +	isDrawCommandEnabled = in.readUint16BE(); +	var5                 = in.readUint16BE(); +	var4                 = in.readUint16BE(); +	var3                 = in.readUint16BE(); +	var2                 = in.readUint16BE(); +	commandVar2          = in.readUint16BE(); +	renderer->_messageBg = in.readUint16BE(); +	 +	// TODO: Use the loaded value (adBgVar1 (Uint16BE)). +	in.readUint16BE(); + +	currentAdditionalBgIdx = in.readSint16BE(); +	currentAdditionalBgIdx2 = in.readSint16BE(); + +	// TODO: Check whether the scroll value really gets used correctly after this. +	// Note that the backgrounds are loaded only later than this value is set. +	renderer->setScroll(in.readUint16BE()); + +	// TODO: Use the loaded value (adBgVar0 (Uint16BE). Maybe this means bgVar0?). +	in.readUint16BE(); + +	disableSystemMenu = in.readUint16BE(); + +	// TODO: adBgVar1 = 1 here + +	// Load the animDataTable entries +	in.readUint16BE(); // Entry count (255 in the PC version of Operation Stealth). +	in.readUint16BE(); // Entry size (36 in the PC version of Operation Stealth). +	loadResourcesFromSave(in, ANIMSIZE_30_PTRS_INTACT); + +	loadScreenParams(in); +	loadGlobalScripts(in); +	loadObjectScripts(in); +	loadSeqList(in); +	loadOverlayList(in); +	loadBgIncrustFromSave(in); + +	// Left this here instead of moving it earlier in this function with +	// the other current value loadings (e.g. loading of current procedure, +	// current backgrounds etc). Mostly emulating the way we've handled +	// Future Wars savegames and hoping that things work out. +	if (strlen(currentMsgName)) { +		loadMsg(currentMsgName);  	} -	fHandle->read(commandBuffer, 0x50); -	renderer->setCommand(commandBuffer); +	// TODO: Add current music loading and playing here +	// TODO: Palette handling? -	renderer->_cmdY = fHandle->readUint16BE(); +	if (in.pos() == in.size()) { +		debug(3, "loadTempSaveOS: Loaded the whole savefile.");		 +	} else { +		warning("loadTempSaveOS: Loaded the savefile but didn't exhaust it completely. Something was left over"); +	} -	bgVar0 = fHandle->readUint16BE(); -	allowPlayerInput = fHandle->readUint16BE(); -	playerCommand = fHandle->readSint16BE(); -	commandVar1 = fHandle->readSint16BE(); -	isDrawCommandEnabled = fHandle->readUint16BE(); -	var5 = fHandle->readUint16BE(); -	var4 = fHandle->readUint16BE(); -	var3 = fHandle->readUint16BE(); -	var2 = fHandle->readUint16BE(); -	commandVar2 = fHandle->readSint16BE(); +	return !in.ioFailed(); +} -	renderer->_messageBg = fHandle->readUint16BE(); +bool CineEngine::loadPlainSaveFW(Common::SeekableReadStream &in, CineSaveGameFormat saveGameFormat) { +	char bgName[13]; -	fHandle->readUint16BE(); -	fHandle->readUint16BE(); +	// At savefile position 0x0000: +	currentDisk = in.readUint16BE(); -	loadResourcesFromSave(*fHandle, broken); +	// At 0x0002: +	in.read(currentPartName, 13); +	// At 0x000F: +	in.read(currentDatName, 13); -	// TODO: handle screen params (really required ?) -	fHandle->readUint16BE(); -	fHandle->readUint16BE(); -	fHandle->readUint16BE(); -	fHandle->readUint16BE(); -	fHandle->readUint16BE(); -	fHandle->readUint16BE(); +	// At 0x001C: +	saveVar2 = in.readSint16BE(); -	size = fHandle->readSint16BE(); -	for (i = 0; i < size; i++) { -		loadScriptFromSave(fHandle, true); +	// At 0x001E: +	in.read(currentPrcName, 13); +	// At 0x002B: +	in.read(currentRelName, 13); +	// At 0x0038: +	in.read(currentMsgName, 13); +	// At 0x0045: +	in.read(bgName, 13); +	// At 0x0052: +	in.read(currentCtName, 13); + +	checkDataDisk(currentDisk); + +	if (strlen(currentPartName)) { +		loadPart(currentPartName);  	} -	size = fHandle->readSint16BE(); -	for (i = 0; i < size; i++) { -		loadScriptFromSave(fHandle, false); +	if (strlen(currentPrcName)) { +		loadPrc(currentPrcName);  	} -	size = fHandle->readSint16BE(); -	for (i = 0; i < size; i++) { -		loadOverlayFromSave(*fHandle); +	if (strlen(currentRelName)) { +		loadRel(currentRelName);  	} -	loadBgIncrustFromSave(*fHandle); +	if (strlen(bgName)) { +		loadBg(bgName); +	} -	delete fHandle; +	if (strlen(currentCtName)) { +		loadCtFW(currentCtName); +	} + +	// At 0x005F: +	loadObjectTable(in); + +	// At 0x2043 (i.e. 0x005F + 2 * 2 + 255 * 32): +	renderer->restorePalette(in); + +	// At 0x2083 (i.e. 0x2043 + 16 * 2 * 2): +	globalVars.load(in, NUM_MAX_VAR); + +	// At 0x2281 (i.e. 0x2083 + 255 * 2): +	loadZoneData(in); + +	// At 0x22A1 (i.e. 0x2281 + 16 * 2): +	loadCommandVariables(in); + +	// At 0x22A9 (i.e. 0x22A1 + 4 * 2): +	in.read(commandBuffer, 0x50); +	renderer->setCommand(commandBuffer); + +	// At 0x22F9 (i.e. 0x22A9 + 0x50): +	renderer->_cmdY = in.readUint16BE(); + +	// At 0x22FB: +	bgVar0 = in.readUint16BE(); +	// At 0x22FD: +	allowPlayerInput = in.readUint16BE(); +	// At 0x22FF: +	playerCommand = in.readSint16BE(); +	// At 0x2301: +	commandVar1 = in.readSint16BE(); +	// At 0x2303: +	isDrawCommandEnabled = in.readUint16BE(); +	// At 0x2305: +	var5 = in.readUint16BE(); +	// At 0x2307: +	var4 = in.readUint16BE(); +	// At 0x2309: +	var3 = in.readUint16BE(); +	// At 0x230B: +	var2 = in.readUint16BE(); +	// At 0x230D: +	commandVar2 = in.readSint16BE(); + +	// At 0x230F: +	renderer->_messageBg = in.readUint16BE(); + +	// At 0x2311: +	in.readUint16BE(); +	// At 0x2313: +	in.readUint16BE(); + +	// At 0x2315: +	loadResourcesFromSave(in, saveGameFormat); + +	loadScreenParams(in); +	loadGlobalScripts(in); +	loadObjectScripts(in); +	loadOverlayList(in); +	loadBgIncrustFromSave(in);  	if (strlen(currentMsgName)) {  		loadMsg(currentMsgName);  	} -	setMouseCursor(MOUSE_CURSOR_NORMAL); -  	if (strlen(currentDatName)) {  /*		i = saveVar2;  		saveVar2 = 0; @@ -585,135 +991,139 @@ bool CineEngine::makeLoad(char *saveName) {  		}*/  	} -	return true; +	return !in.ioFailed();  } -void makeSave(char *saveFileName) { -	int16 i; -	Common::OutSaveFile *fHandle; - -	fHandle = g_saveFileMan->openForSaving(saveFileName); +bool CineEngine::makeLoad(char *saveName) { +	Common::SharedPtr<Common::InSaveFile> saveFile(g_saveFileMan->openForLoading(saveName)); -	if (!fHandle) { -		drawString(otherMessages[1], 0); +	if (!saveFile) { +		drawString(otherMessages[0], 0);  		waitPlayerInput();  		// restoreScreen();  		checkDataDisk(-1); -		return; -	} - -	fHandle->writeUint16BE(currentDisk); -	fHandle->write(currentPartName, 13); -	fHandle->write(currentDatName, 13); -	fHandle->writeUint16BE(saveVar2); -	fHandle->write(currentPrcName, 13); -	fHandle->write(currentRelName, 13); -	fHandle->write(currentMsgName, 13); -	renderer->saveBg(*fHandle); -	fHandle->write(currentCtName, 13); - -	fHandle->writeUint16BE(0xFF); -	fHandle->writeUint16BE(0x20); - -	for (i = 0; i < 255; i++) { -		fHandle->writeUint16BE(objectTable[i].x); -		fHandle->writeUint16BE(objectTable[i].y); -		fHandle->writeUint16BE(objectTable[i].mask); -		fHandle->writeUint16BE(objectTable[i].frame); -		fHandle->writeUint16BE(objectTable[i].costume); -		fHandle->write(objectTable[i].name, 20); -		fHandle->writeUint16BE(objectTable[i].part); -	} - -	renderer->savePalette(*fHandle); - -	globalVars.save(*fHandle, NUM_MAX_VAR - 1); - -	for (i = 0; i < 16; i++) { -		fHandle->writeUint16BE(zoneData[i]); +		return false;  	} -	for (i = 0; i < 4; i++) { -		fHandle->writeUint16BE(commandVar3[i]); +	setMouseCursor(MOUSE_CURSOR_DISK); + +	uint32 saveSize = saveFile->size(); +	// TODO: Evaluate the maximum savegame size for the temporary Operation Stealth savegame format. +	if (saveSize == 0) { // Savefile's compressed using zlib format can't tell their unpacked size, test for it +		// Can't get information about the savefile's size so let's try +		// reading as much as we can from the file up to a predefined upper limit. +		// +		// Some estimates for maximum savefile sizes (All with 255 animDataTable entries of 30 bytes each): +		// With 256 global scripts, object scripts, overlays and background incrusts: +		// 0x2315 + (255 * 30) + (2 * 6) + (206 + 206 + 20 + 20) * 256 = ~129kB +		// With 512 global scripts, object scripts, overlays and background incrusts: +		// 0x2315 + (255 * 30) + (2 * 6) + (206 + 206 + 20 + 20) * 512 = ~242kB +		// +		// I think it extremely unlikely that there would be over 512 global scripts, object scripts, +		// overlays and background incrusts so 256kB seems like quite a safe upper limit.		 +		// NOTE: If the savegame format is changed then this value might have to be re-evaluated! +		// Hopefully devices with more limited memory can also cope with this memory allocation. +		saveSize = 256 * 1024; +	} +	Common::SharedPtr<Common::MemoryReadStream> in(saveFile->readStream(saveSize)); + +	// Try to detect the used savegame format +	enum CineSaveGameFormat saveGameFormat = detectSaveGameFormat(*in); + +	// Handle problematic savegame formats +	bool load = true; // Should we try to load the savegame? +	bool result = false; +	if (saveGameFormat == ANIMSIZE_30_PTRS_BROKEN) { +		// One might be able to load the ANIMSIZE_30_PTRS_BROKEN format but +		// that's not implemented here because it was never used in a stable +		// release of ScummVM but only during development (From revision 31453, +		// which introduced the problem, until revision 32073, which fixed it). +		// Therefore be bail out if we detect this particular savegame format. +		warning("Detected a known broken savegame format, not loading savegame"); +		load = false; // Don't load the savegame +	} else if (saveGameFormat == ANIMSIZE_UNKNOWN) { +		// If we can't detect the savegame format +		// then let's try the default format and hope for the best. +		warning("Couldn't detect the used savegame format, trying default savegame format. Things may break"); +		saveGameFormat = ANIMSIZE_30_PTRS_INTACT; +	} + +	if (load) { +		// Reset the engine's state +		resetEngine(); +		 +		if (saveGameFormat == TEMP_OS_FORMAT) { +			// Load the temporary Operation Stealth savegame format +			result = loadTempSaveOS(*in); +		} else { +			// Load the plain Future Wars savegame format +			result = loadPlainSaveFW(*in, saveGameFormat); +		}  	} -	fHandle->write(commandBuffer, 0x50); - -	fHandle->writeUint16BE(renderer->_cmdY); - -	fHandle->writeUint16BE(bgVar0); -	fHandle->writeUint16BE(allowPlayerInput); -	fHandle->writeUint16BE(playerCommand); -	fHandle->writeUint16BE(commandVar1); -	fHandle->writeUint16BE(isDrawCommandEnabled); -	fHandle->writeUint16BE(var5); -	fHandle->writeUint16BE(var4); -	fHandle->writeUint16BE(var3); -	fHandle->writeUint16BE(var2); -	fHandle->writeUint16BE(commandVar2); - -	fHandle->writeUint16BE(renderer->_messageBg); - -	fHandle->writeUint16BE(0xFF); -	fHandle->writeUint16BE(0x1E); +	setMouseCursor(MOUSE_CURSOR_NORMAL); -	for (i = 0; i < NUM_MAX_ANIMDATA; i++) { -		animDataTable[i].save(*fHandle); -	} +	return result; +} -	fHandle->writeUint16BE(0);  // Screen params, unhandled -	fHandle->writeUint16BE(0); -	fHandle->writeUint16BE(0); -	fHandle->writeUint16BE(0); -	fHandle->writeUint16BE(0); -	fHandle->writeUint16BE(0); +void CineEngine::makeSaveFW(Common::OutSaveFile &out) { +	out.writeUint16BE(currentDisk); +	out.write(currentPartName, 13); +	out.write(currentDatName, 13); +	out.writeUint16BE(saveVar2); +	out.write(currentPrcName, 13); +	out.write(currentRelName, 13); +	out.write(currentMsgName, 13); +	renderer->saveBgNames(out); +	out.write(currentCtName, 13); + +	saveObjectTable(out); +	renderer->savePalette(out); +	globalVars.save(out, NUM_MAX_VAR); +	saveZoneData(out); +	saveCommandVariables(out); +	out.write(commandBuffer, 0x50); + +	out.writeUint16BE(renderer->_cmdY); +	out.writeUint16BE(bgVar0); +	out.writeUint16BE(allowPlayerInput); +	out.writeUint16BE(playerCommand); +	out.writeUint16BE(commandVar1); +	out.writeUint16BE(isDrawCommandEnabled); +	out.writeUint16BE(var5); +	out.writeUint16BE(var4); +	out.writeUint16BE(var3); +	out.writeUint16BE(var2); +	out.writeUint16BE(commandVar2); +	out.writeUint16BE(renderer->_messageBg); + +	saveAnimDataTable(out); +	saveScreenParams(out); + +	saveGlobalScripts(out); +	saveObjectScripts(out); +	saveOverlayList(out); +	saveBgIncrustList(out); +} -	{ -		ScriptList::iterator it; -		fHandle->writeUint16BE(globalScripts.size()); -		for (it = globalScripts.begin(); it != globalScripts.end(); ++it) { -			(*it)->save(*fHandle); -		} +void CineEngine::makeSave(char *saveFileName) { +	Common::SharedPtr<Common::OutSaveFile> fHandle(g_saveFileMan->openForSaving(saveFileName)); -		fHandle->writeUint16BE(objectScripts.size()); -		for (it = objectScripts.begin(); it != objectScripts.end(); ++it) { -			(*it)->save(*fHandle); -		} -	} +	setMouseCursor(MOUSE_CURSOR_DISK); -	{ -		Common::List<overlay>::iterator it; - -		fHandle->writeUint16BE(overlayList.size()); - -		for (it = overlayList.begin(); it != overlayList.end(); ++it) { -			fHandle->writeUint32BE(0); -			fHandle->writeUint32BE(0); -			fHandle->writeUint16BE(it->objIdx); -			fHandle->writeUint16BE(it->type); -			fHandle->writeSint16BE(it->x); -			fHandle->writeSint16BE(it->y); -			fHandle->writeSint16BE(it->width); -			fHandle->writeSint16BE(it->color); +	if (!fHandle) { +		drawString(otherMessages[1], 0); +		waitPlayerInput(); +		// restoreScreen(); +		checkDataDisk(-1); +	} else { +		if (g_cine->getGameType() == GType_FW) { +			makeSaveFW(*fHandle); +		} else { +			makeSaveOS(*fHandle);  		}  	} -	Common::List<BGIncrust>::iterator it; -	fHandle->writeUint16BE(bgIncrustList.size()); - -	for (it = bgIncrustList.begin(); it != bgIncrustList.end(); ++it) { -		fHandle->writeUint32BE(0); // next -		fHandle->writeUint32BE(0); // unkPtr -		fHandle->writeUint16BE(it->objIdx); -		fHandle->writeUint16BE(it->param); -		fHandle->writeUint16BE(it->x); -		fHandle->writeUint16BE(it->y); -		fHandle->writeUint16BE(it->frame); -		fHandle->writeUint16BE(it->part); -	} - -	delete fHandle; -  	setMouseCursor(MOUSE_CURSOR_NORMAL);  } @@ -854,6 +1264,89 @@ void CineEngine::makeSystemMenu(void) {  	}  } +/** + * Save an Operation Stealth type savegame. WIP! + * + * NOTE: This is going to be very much a work in progress so the Operation Stealth's + *       savegame formats that are going to be tried are extremely probably not going + *       to be supported at all after Operation Stealth becomes officially supported. + *       This means that the savegame format will hopefully change to something nicer + *       when official support for Operation Stealth begins. + */ +void CineEngine::makeSaveOS(Common::OutSaveFile &out) { +	int i; + +	// Make a temporary Operation Stealth savegame format chunk header and save it.	 +	ChunkHeader header; +	header.id = TEMP_OS_FORMAT_ID; +	header.version = CURRENT_OS_SAVE_VER; +	header.size = 0; // No data is currently put inside the chunk, all the plain data comes right after it. +	writeChunkHeader(out, header); + +	// Start outputting the plain savegame data right after the chunk header. +	out.writeUint16BE(currentDisk); +	out.write(currentPartName, 13); +	out.write(currentPrcName, 13); +	out.write(currentRelName, 13); +	out.write(currentMsgName, 13); +	renderer->saveBgNames(out); +	out.write(currentCtName, 13); + +	saveObjectTable(out); +	renderer->savePalette(out); +	globalVars.save(out, NUM_MAX_VAR); +	saveZoneData(out); +	saveCommandVariables(out); +	out.write(commandBuffer, 0x50); +	saveZoneQuery(out); + +	// FIXME: Save a proper name here, saving an empty string currently. +	// 0x2925: Current music name (String, 13 bytes). +	for (i = 0; i < 13; i++) { +		out.writeByte(0); +	} +	// FIXME: Save proper value for this variable, currently writing zero +	// 0x2932: Is music loaded? (Uint16BE, Boolean). +	out.writeUint16BE(0); +	// FIXME: Save proper value for this variable, currently writing zero +	// 0x2934: Is music playing? (Uint16BE, Boolean). +	out.writeUint16BE(0); + +	out.writeUint16BE(renderer->_cmdY);	 +	out.writeUint16BE(0); // Some unknown variable that seems to always be zero +	out.writeUint16BE(allowPlayerInput); +	out.writeUint16BE(playerCommand); +	out.writeUint16BE(commandVar1); +	out.writeUint16BE(isDrawCommandEnabled); +	out.writeUint16BE(var5); +	out.writeUint16BE(var4); +	out.writeUint16BE(var3); +	out.writeUint16BE(var2); +	out.writeUint16BE(commandVar2); +	out.writeUint16BE(renderer->_messageBg); +	 +	// FIXME: Save proper value for this variable, currently writing zero. +	// An unknown variable at 0x295E: adBgVar1 (Uint16BE). +	out.writeUint16BE(0); +	out.writeSint16BE(currentAdditionalBgIdx); +	out.writeSint16BE(currentAdditionalBgIdx2); +	// FIXME: Save proper value for this variable, currently writing zero. +	// 0x2954: additionalBgVScroll (Uint16BE). This probably means renderer->_bgShift. +	out.writeUint16BE(0); +	// FIXME: Save proper value for this variable, currently writing zero. +	// An unknown variable at 0x2956: adBgVar0 (Uint16BE). Maybe this means bgVar0? +	out.writeUint16BE(0); +	out.writeUint16BE(disableSystemMenu); + +	saveAnimDataTable(out); +	saveScreenParams(out); +	saveGlobalScripts(out); +	saveObjectScripts(out); +	saveSeqList(out); +	saveOverlayList(out); +	saveBgIncrustList(out); +} +  void drawMessageBox(int16 x, int16 y, int16 width, int16 currentY, int16 offset, int16 color, byte* page) {  	gfxDrawLine(x + offset, y + offset, x + width - offset, y + offset, color, page);	// top  	gfxDrawLine(x + offset, currentY + 4 - offset, x + width - offset, currentY + 4 - offset, color, page);	// bottom @@ -1510,12 +2003,22 @@ void mainLoopSub6(void) {  void checkForPendingDataLoad(void) {  	if (newPrcName[0] != 0) { -		loadPrc(newPrcName); +		bool loadPrcOk = loadPrc(newPrcName);  		strcpy(currentPrcName, newPrcName);  		strcpy(newPrcName, ""); -		addScriptToList0(1); +		// Check that the loading of the script file was successful before +		// trying to add script 1 from it to the global scripts list. This +		// fixes a crash when failing copy protection in Amiga or Atari ST +		// versions of Future Wars. +		if (loadPrcOk) { +			addScriptToGlobalScripts(1); +		} else if (scumm_stricmp(currentPrcName, COPY_PROT_FAIL_PRC_NAME)) { +			// We only show an error here for other files than the file that +			// is loaded if copy protection fails (i.e. L201.ANI). +			warning("checkForPendingDataLoad: loadPrc(%s) failed", currentPrcName); +		}  	}  	if (newRelName[0] != 0) { @@ -1582,16 +2085,19 @@ void removeSeq(uint16 param1, uint16 param2, uint16 param3) {  	}  } -uint16 isSeqRunning(uint16 param1, uint16 param2, uint16 param3) { +bool isSeqRunning(uint16 param1, uint16 param2, uint16 param3) {  	Common::List<SeqListElement>::iterator it;  	for (it = seqList.begin(); it != seqList.end(); ++it) {  		if (it->objIdx == param1 && it->var4 == param2 && it->varE == param3) { -			return 1; +			// Just to be on the safe side there's a restriction of the +			// addition's result to 16-bit arithmetic here like in the +			// original. It's possible that it's not strictly needed. +			return ((it->var14 + it->var16) & 0xFFFF) == 0;  		}  	} -	return 0; +	return true;  }  void addSeqListElement(uint16 objIdx, int16 param1, int16 param2, int16 frame, int16 param4, int16 param5, int16 param6, int16 param7, int16 param8) { @@ -1618,6 +2124,19 @@ void addSeqListElement(uint16 objIdx, int16 param1, int16 param2, int16 frame, i  	seqList.insert(it, tmp);  } +void modifySeqListElement(uint16 objIdx, int16 var4Test, int16 param1, int16 param2, int16 param3, int16 param4) { +	// Find a suitable list element and modify it +	for (Common::List<SeqListElement>::iterator it = seqList.begin(); it != seqList.end(); ++it) { +		if (it->objIdx == objIdx && it->var4 == var4Test) { +			it->varC  = param1; +			it->var18 = param2; +			it->var1A = param3; +			it->var10 = it->var12 = param4; +			break; +		} +	} +} +  void computeMove1(SeqListElement &element, int16 x, int16 y, int16 param1,      int16 param2, int16 x2, int16 y2) {  	element.var16 = 0; @@ -1662,105 +2181,51 @@ uint16 computeMove2(SeqListElement &element) {  	return returnVar;  } -// sort all the gfx stuff... - -void resetGfxEntityEntry(uint16 objIdx) { -#if 0 -	overlayHeadElement* tempHead = &overlayHead; -	byte* var_16 = NULL; -	uint16 var_10 = 0; -	uint16 var_12 = 0; -	overlayHeadElement* currentHead = tempHead->next; -	byte* var_1A = NULL; -	overlayHeadElement* var1E = &overlayHead; - -	while (currentHead) { -		tempHead2 = currentHead->next; - -		if (currentHead->objIdx == objIdx && currentHead->type!=2 && currentHead->type!=3 && currentHead->type!=0x14) { -			tempHead->next = tempHead2; - -			if (tempHead2) { -				tempHead2->previous = currentHead->previous; -			} else { -				seqVar0 = currentHead->previous; -			} - -			var_22 = var_16; - -			if (!var_22) { -				// todo: goto? -			} - -			var_22->previous = currentHead; -		} else { -		} - -		if (currentHead->type == 0x14) { -		} else { -		} - -		if (currentHead->type == 0x2 || currentHead->type == 0x3) { -			si = 10000; -		} else { -			si = objectTable[currentHead->objIdx]; -		} - -		if (objectTable[objIdx]>si) { -			var1E = currentHead; -		} - -		tempHead = tempHead->next; - -	} - -	if (var_1A) { -		currentHead = var_16; -		var_22 = var_1E->next; -		var_1E->next = currentHead; -		var_1A->next = var_22; - -		if (var_1E != &gfxEntityHead) { -			currentHead->previous = var_1E; -		} - -		if (!var_22) { -			seqVar0 = var_1A; -		} else { -			var_22->previous = var_1A; -		} - -	} -#endif -} - -uint16 addAni(uint16 param1, uint16 objIdx, const byte *ptr, SeqListElement &element, uint16 param3, int16 *param4) { -	const byte *currentPtr = ptr; -	const byte *ptrData; -	const byte *ptr2; +uint16 addAni(uint16 param1, uint16 objIdx, const int8 *ptr, SeqListElement &element, uint16 param3, int16 *param4) { +	const int8 *ptrData; +	const int8 *ptr2;  	int16 di; -	assert(ptr); -	assert(param4); +	debug(5, "addAni: param1 = %d, objIdx = %d, ptr = %p, element.var8 = %d, element.var14 = %d param3 = %d", +		param1, objIdx, ptr, element.var8, element.var14, param3); -	dummyU16 = READ_BE_UINT16((currentPtr + param1 * 2) + 8); +	// In the original an error string is set and 0 is returned if the following doesn't hold +	assert(ptr); +	// We probably could just use a local variable here instead of the dummyU16 but +	// haven't checked if this has any side-effects so keeping it this way still. +	dummyU16 = READ_BE_UINT16(ptr + param1 * 2 + 8);  	ptrData = ptr + dummyU16; +	// In the original an error string is set and 0 is returned if the following doesn't hold  	assert(*ptrData);  	di = (objectTable[objIdx].costume + 1) % (*ptrData); -	ptr2 = (ptrData + (di * 8)) + 1; - +	++ptrData; // Jump over the just read byte +	// Here ptr2 seems to be indexing a table of structs (8 bytes per struct): +	//	struct { +	//		int8 x;			// 0 (Used with checkCollision) +	//		int8 y;			// 1 (Used with checkCollision) +	//		int8 numZones;	// 2 (Used with checkCollision) +	//		int8 var3;		// 3 (Not used in this function) +	//		int8 xAdd;		// 4 (Used with an object) +	//		int8 yAdd;		// 5 (Used with an object) +	//		int8 maskAdd;	// 6 (Used with an object) +	//		int8 frameAdd;	// 7 (Used with an object) +	//	}; +	ptr2 = ptrData + di * 8; + +	// We might probably safely discard the AND by 1 here because +	// at least in the original checkCollision returns always 0 or 1.  	if ((checkCollision(objIdx, ptr2[0], ptr2[1], ptr2[2], ptr[0]) & 1)) {  		return 0;  	} -	objectTable[objIdx].x += (int8)ptr2[4]; -	objectTable[objIdx].y += (int8)ptr2[5]; -	objectTable[objIdx].mask += (int8)ptr2[6]; +	objectTable[objIdx].x += ptr2[4]; +	objectTable[objIdx].y += ptr2[5]; +	objectTable[objIdx].mask += ptr2[6]; -	if (objectTable[objIdx].frame) { +	if (ptr2[6]) {  		resetGfxEntityEntry(objIdx);  	} @@ -1769,19 +2234,79 @@ uint16 addAni(uint16 param1, uint16 objIdx, const byte *ptr, SeqListElement &ele  	if (param3 || !element.var14) {  		objectTable[objIdx].costume = di;  	} else { +		assert(param4);  		*param4 = di;  	}  	return 1;  } +/*!  + * Permutates the overlay list into a different order according to some logic. + * \todo Check this function for correctness (Wasn't very easy to reverse engineer so there may be errors) + */ +void resetGfxEntityEntry(uint16 objIdx) { +	Common::List<overlay>::iterator it, bObjsCutPoint; +	Common::List<overlay> aReverseObjs, bObjs; +	bool foundCutPoint = false;	 + +	// Go through the overlay list and partition the whole list into two categories (Type A and type B objects) +	for (it = overlayList.begin(); it != overlayList.end(); ++it) { +		if (it->objIdx == objIdx && it->type != 2 && it->type != 3) { // Type A object +			aReverseObjs.push_front(*it); +		} else { // Type B object +			bObjs.push_back(*it); +			uint16 objectMask; +			if (it->type == 2 || it->type == 3) { +				objectMask = 10000; +			} else { +				objectMask = objectTable[it->objIdx].mask; +			} +	 +			if (objectTable[objIdx].mask > objectMask) { // Check for B objects' cut point +				bObjsCutPoint = bObjs.reverse_begin(); +				foundCutPoint = true; +			} +		} +	} +	 +	// Recreate the overlay list in a different order. +	overlayList.clear(); +	if (foundCutPoint) { +		// If a cut point was found the order is: +		// B objects before the cut point, the cut point, A objects in reverse order, B objects after cut point. +		++bObjsCutPoint; // Include the cut point in the first list insertion +		overlayList.insert(overlayList.end(), bObjs.begin(), bObjsCutPoint); +		overlayList.insert(overlayList.end(), aReverseObjs.begin(), aReverseObjs.end()); +		overlayList.insert(overlayList.end(), bObjsCutPoint, bObjs.end()); +	} else { +		// If no cut point was found the order is: +		// A objects in reverse order, B objects. +		overlayList.insert(overlayList.end(), aReverseObjs.begin(), aReverseObjs.end()); +		overlayList.insert(overlayList.end(), bObjs.begin(), bObjs.end()); +	} +} +  void processSeqListElement(SeqListElement &element) {  	int16 x = objectTable[element.objIdx].x;  	int16 y = objectTable[element.objIdx].y; -	const byte *ptr1 = animDataTable[element.frame].data(); +	const int8 *ptr1 = (const int8 *) animDataTable[element.frame].data();  	int16 var_10;  	int16 var_4;  	int16 var_2; +	 +	// Initial interpretations for variables addressed through ptr1 (8-bit addressing): +	// These may be inaccurate! +	// 0: ? +	// 1: xRadius +	// 2: yRadius +	// 3: ? +	// 4: xAdd +	// 5: yAdd +	// 6: ? +	// 7: ? +	// After this come (At least at positions 0, 1 and 3 in 16-bit addressing) +	// 16-bit big-endian values used for addressing through ptr1.  	if (element.var12 < element.var10) {  		element.var12++; @@ -1791,22 +2316,44 @@ void processSeqListElement(SeqListElement &element) {  	element.var12 = 0;  	if (ptr1) { -		uint16 param1 = ptr1[1]; -		uint16 param2 = ptr1[2]; +		int16 param1 = ptr1[1]; +		int16 param2 = ptr1[2];  		if (element.varC != 255) { -			// FIXME: Why is this here? Fingolfin gets lots of these -			// in his copy of Operation Stealth (value 0 or 236) under -			// Mac OS X. Maybe it's a endian issue? At least the graphics -			// in the copy protection screen are partially messed up. -			warning("processSeqListElement: varC = %d", element.varC); -		} - -		if (globalVars[VAR_MOUSE_X_POS] || globalVars[VAR_MOUSE_Y_POS]) { -			computeMove1(element, ptr1[4] + x, ptr1[5] + y, param1, param2, globalVars[VAR_MOUSE_X_POS], globalVars[VAR_MOUSE_Y_POS]); +			int16 x2 = element.var18; +			int16 y2 = element.var1A; +			if (element.varC) { +				x2 += objectTable[element.varC].x; +				y2 += objectTable[element.varC].y; +			} +			computeMove1(element, ptr1[4] + x, ptr1[5] + y, param1, param2, x2, y2);  		} else { -			element.var16 = 0; -			element.var14 = 0; +			if (inputVar0 && allowPlayerInput) { +				int16 adder = param1 + 1; +				if (inputVar0 != 1) { +					adder = -adder; +				} +				// FIXME: In Operation Stealth's disassembly global variable 251 is used here +				//        but it's named as VAR_MOUSE_Y_MODE in ScummVM. Is it correct or a +				//        left over from Future Wars's reverse engineering? +				globalVars[VAR_MOUSE_X_POS] = globalVars[251] = ptr1[4] + x + adder; +			} + +			if (inputVar1 && allowPlayerInput) { +				int16 adder = param2 + 1; +				if (inputVar1 != 1) { +					adder = -adder; +				} +				// TODO: Name currently unnamed global variable 252 +				globalVars[VAR_MOUSE_Y_POS] = globalVars[252] = ptr1[5] + y + adder; +			} + +			if (globalVars[VAR_MOUSE_X_POS] || globalVars[VAR_MOUSE_Y_POS]) { +				computeMove1(element, ptr1[4] + x, ptr1[5] + y, param1, param2, globalVars[VAR_MOUSE_X_POS], globalVars[VAR_MOUSE_Y_POS]); +			} else { +				element.var16 = 0; +				element.var14 = 0; +			}  		}  		var_10 = computeMove2(element); @@ -1847,14 +2394,14 @@ void processSeqListElement(SeqListElement &element) {  			}  		} -		if (element.var16 + element.var14) { +		if (element.var16 + element.var14 == 0) {  			if (element.var1C) {  				if (element.var1E) {  					objectTable[element.objIdx].costume = 0;  					element.var1E = 0;  				} -				addAni(element.var1C + 3, element.objIdx, ptr1, element, 1, (int16 *) & var2); +				addAni(element.var1C + 3, element.objIdx, ptr1, element, 1, &var_2);  			}  		} diff --git a/engines/cine/various.h b/engines/cine/various.h index 91662c16ff..d87679ca08 100644 --- a/engines/cine/various.h +++ b/engines/cine/various.h @@ -44,7 +44,7 @@ extern bool inMenu;  struct SeqListElement {  	int16 var4; -	uint16 objIdx; +	uint16 objIdx; ///< Is this really unsigned?  	int16 var8;  	int16 frame;  	int16 varC; @@ -130,16 +130,20 @@ struct SelectedObjStruct {  #define NUM_MAX_ZONE 16  extern uint16 zoneData[NUM_MAX_ZONE]; +extern uint16 zoneQuery[NUM_MAX_ZONE];  void addMessage(byte param1, int16 param2, int16 param3, int16 param4, int16 param5);  void removeMessages();  void removeSeq(uint16 param1, uint16 param2, uint16 param3); -uint16 isSeqRunning(uint16 param1, uint16 param2, uint16 param3); +bool isSeqRunning(uint16 param1, uint16 param2, uint16 param3);  void addSeqListElement(uint16 objIdx, int16 param1, int16 param2, int16 frame, int16 param4, int16 param5, int16 param6, int16 param7, int16 param8); +void modifySeqListElement(uint16 objIdx, int16 var4Test, int16 param1, int16 param2, int16 param3, int16 param4);  void processSeqList(void); +void resetGfxEntityEntry(uint16 objIdx); +  bool makeTextEntryMenu(const char *caption, char *string, int strLen, int y);  } // End of namespace Cine diff --git a/engines/cruise/cruise_main.h b/engines/cruise/cruise_main.h index 60afe5fa4c..c9c27ada49 100644 --- a/engines/cruise/cruise_main.h +++ b/engines/cruise/cruise_main.h @@ -28,7 +28,7 @@  #include <string.h>  #include <stdlib.h> -#include <assert.h> +#include <assert.h>	// FIXME: WINCE: this is not needed/not portable (probably applies to all above includes)  #include "common/scummsys.h" diff --git a/engines/cruise/volume.cpp b/engines/cruise/volume.cpp index e4a3dde78f..b2ff2631c0 100644 --- a/engines/cruise/volume.cpp +++ b/engines/cruise/volume.cpp @@ -456,8 +456,8 @@ int16 readVolCnf(void) {  			sprintf(nameBuffer, "%s", buffer[j].name);  			if (buffer[j].size == buffer[j].extSize) { -				Common::File fout; -				fout.open(nameBuffer, Common::File::kFileWriteMode); +				Common::DumpFile fout; +				fout.open(nameBuffer);  				if(fout.isOpen())  					fout.write(bufferLocal, buffer[j].size);  			} else { diff --git a/engines/drascula/animation.cpp b/engines/drascula/animation.cpp index feb6cb93ca..06868494b5 100644 --- a/engines/drascula/animation.cpp +++ b/engines/drascula/animation.cpp @@ -372,7 +372,11 @@ void DrasculaEngine::animation_1_1() {  			break;  		clearRoom(); -		playMusic(2); +		if (_lang == kSpanish) +			playMusic(31); +		else +			playMusic(2); +  		pause(5);  		playFLI("intro.bin", 12);  		term_int = 1; @@ -1669,7 +1673,7 @@ void DrasculaEngine::animation_12_5() {  	const int frusky_x[] = {100, 139, 178, 217, 100, 178, 217, 139, 100, 139};  	const int elfrusky_x[] = {1, 68, 135, 1, 68, 135, 1, 68, 135, 68, 1, 135, 68, 135, 68};  	int color, component; -	char fade; +	signed char fade;  	playMusic(26);  	updateRoom(); diff --git a/engines/drascula/drascula.h b/engines/drascula/drascula.h index ce67cc2c0e..8bb73d8dd1 100644 --- a/engines/drascula/drascula.h +++ b/engines/drascula/drascula.h @@ -245,7 +245,7 @@ public:  	void loadPic(const char *NamePcc, byte *targetSurface, int colorCount = 1); -	typedef char DacPalette256[256][3]; +	typedef signed char DacPalette256[256][3];  	void setRGB(byte *pal, int plt);  	void assignDefaultPalette(); @@ -328,7 +328,7 @@ public:  	int curHeight, curWidth, feetHeight;  	int talkHeight, talkWidth;  	int floorX1, floorY1, floorX2, floorY2; -	int near, far; +	int lowerLimit, upperLimit;  	int trackFinal, walkToObject;  	int objExit;  	int timeDiff, startTime; @@ -397,7 +397,7 @@ public:  	void playFLI(const char *filefli, int vel);  	void fadeFromBlack(int fadeSpeed);  	void fadeToBlack(int fadeSpeed); -	char adjustToVGA(char value); +	signed char adjustToVGA(signed char value);  	void color_abc(int cl);  	void centerText(const char *,int,int);  	void playSound(int soundNum); diff --git a/engines/drascula/graphics.cpp b/engines/drascula/graphics.cpp index 64591a856e..67993bfb6c 100644 --- a/engines/drascula/graphics.cpp +++ b/engines/drascula/graphics.cpp @@ -89,31 +89,21 @@ void DrasculaEngine::setCursorTable() {  }  void DrasculaEngine::loadPic(const char *NamePcc, byte *targetSurface, int colorCount) { -	unsigned int con, x = 0; -	unsigned int fExit = 0; -	byte ch, rep; +	uint dataSize = 0; +	byte *pcxData;  	_arj.open(NamePcc);  	if (!_arj.isOpen())  		error("missing game data %s %c", NamePcc, 7); -	_arj.seek(128); -	while (!fExit) { -		ch = _arj.readByte(); -		rep = 1; -		if ((ch & 192) == 192) { -			rep = (ch & 63); -			ch = _arj.readByte(); -		} -		for (con = 0; con < rep; con++) { -			x++; -			if (x > 64000) { -				fExit = 1; -				break; -			} -			*targetSurface++ = ch; -		} -	} +	dataSize = _arj.size() - 128 - (256 * 3); +	pcxData = (byte *)malloc(dataSize); + +	_arj.seek(128, SEEK_SET); +	_arj.read(pcxData, dataSize); + +	decodeRLE(pcxData, targetSurface); +	free(pcxData);  	for (int i = 0; i < 256; i++) {  		cPal[i * 3 + 0] = _arj.readByte(); @@ -141,7 +131,6 @@ void DrasculaEngine::showFrame(bool firstFrame) {  	memcpy(prevFrame, VGA, 64000);  	decodeRLE(pcxData, VGA); -  	free(pcxData);  	if (!firstFrame) diff --git a/engines/drascula/palette.cpp b/engines/drascula/palette.cpp index ad57bce618..6a93f21e55 100644 --- a/engines/drascula/palette.cpp +++ b/engines/drascula/palette.cpp @@ -87,12 +87,12 @@ void DrasculaEngine::color_abc(int cl) {  	setPalette((byte *)&gamePalette);  } -char DrasculaEngine::adjustToVGA(char value) { +signed char DrasculaEngine::adjustToVGA(signed char value) {  	return (value & 0x3F) * (value > 0);  }  void DrasculaEngine::fadeToBlack(int fadeSpeed) { -	char fade; +	signed char fade;  	unsigned int color, component;  	DacPalette256 palFade; @@ -110,7 +110,7 @@ void DrasculaEngine::fadeToBlack(int fadeSpeed) {  }  void DrasculaEngine::fadeFromBlack(int fadeSpeed) { -	char fade; +	signed char fade;  	unsigned int color, component;  	DacPalette256 palFade; @@ -186,7 +186,7 @@ void DrasculaEngine::setDarkPalette() {  }  void DrasculaEngine::setPaletteBase(int darkness) { -	char fade; +	signed char fade;  	unsigned int color, component;  	for (fade = darkness; fade >= 0; fade--) { diff --git a/engines/drascula/rooms.cpp b/engines/drascula/rooms.cpp index 6fe28bdbdc..37dddf4b7e 100644 --- a/engines/drascula/rooms.cpp +++ b/engines/drascula/rooms.cpp @@ -1672,8 +1672,8 @@ void DrasculaEngine::enterRoom(int roomIndex) {  	getIntFromLine(buffer, size, &floorY2);  	if (currentChapter != 2) { -		getIntFromLine(buffer, size, &far); -		getIntFromLine(buffer, size, &near); +		getIntFromLine(buffer, size, &upperLimit); +		getIntFromLine(buffer, size, &lowerLimit);  	}  	_arj.close(); @@ -1732,27 +1732,27 @@ void DrasculaEngine::enterRoom(int roomIndex) {  	if (currentChapter != 2) {  		for (l = 0; l <= floorY1; l++) -			factor_red[l] = far; +			factor_red[l] = upperLimit;  		for (l = floorY1; l <= 201; l++) -			factor_red[l] = near; +			factor_red[l] = lowerLimit; -		chiquez = (float)(near - far) / (float)(floorY2 - floorY1); +		chiquez = (float)(lowerLimit - upperLimit) / (float)(floorY2 - floorY1);  		for (l = floorY1; l <= floorY2; l++) { -			factor_red[l] = (int)(far + pequegnez); +			factor_red[l] = (int)(upperLimit + pequegnez);  			pequegnez = pequegnez + chiquez;  		}  	}  	if (roomNumber == 24) {  		for (l = floorY1 - 1; l > 74; l--) { -			factor_red[l] = (int)(far - pequegnez); +			factor_red[l] = (int)(upperLimit - pequegnez);  			pequegnez = pequegnez + chiquez;  		}  	}  	if (currentChapter == 5 && roomNumber == 54) {  		for (l = floorY1 - 1; l > 84; l--) { -			factor_red[l] = (int)(far - pequegnez); +			factor_red[l] = (int)(upperLimit - pequegnez);  			pequegnez = pequegnez + chiquez;  		}  	} diff --git a/engines/engines.mk b/engines/engines.mk index cfb8e69f3e..4dba913173 100644 --- a/engines/engines.mk +++ b/engines/engines.mk @@ -97,6 +97,11 @@ DEFINES += -DENABLE_SWORD2=$(ENABLE_SWORD2)  MODULES += engines/sword2  endif +ifdef ENABLE_TINSEL +DEFINES += -DENABLE_TINSEL=$(ENABLE_TINSEL) +MODULES += engines/tinsel +endif +  ifdef ENABLE_TOUCHE  DEFINES += -DENABLE_TOUCHE=$(ENABLE_TOUCHE)  MODULES += engines/touche diff --git a/engines/gob/dataio.cpp b/engines/gob/dataio.cpp index 8ae11b8755..bcf566d134 100644 --- a/engines/gob/dataio.cpp +++ b/engines/gob/dataio.cpp @@ -202,7 +202,7 @@ const Common::File *DataIO::file_getHandle(int16 handle) const {  	return &_filesHandles[handle];  } -int16 DataIO::file_open(const char *path, Common::File::AccessMode mode) { +int16 DataIO::file_open(const char *path) {  	int16 i;  	for (i = 0; i < MAX_FILES; i++) { @@ -212,7 +212,7 @@ int16 DataIO::file_open(const char *path, Common::File::AccessMode mode) {  	if (i == MAX_FILES)  		return -1; -	file_getHandle(i)->open(path, mode); +	file_getHandle(i)->open(path);  	if (file_getHandle(i)->isOpen())  		return i; @@ -467,17 +467,14 @@ void DataIO::closeData(int16 handle) {  		file_getHandle(handle)->close();  } -int16 DataIO::openData(const char *path, Common::File::AccessMode mode) { +int16 DataIO::openData(const char *path) {  	int16 handle; -	if (mode != Common::File::kFileReadMode) -		return file_open(path, mode); -  	handle = getChunk(path);  	if (handle >= 0)  		return handle; -	return file_open(path, mode); +	return file_open(path);  }  DataStream *DataIO::openAsStream(int16 handle, bool dispose) { diff --git a/engines/gob/dataio.h b/engines/gob/dataio.h index a990dbeda5..4b4c79d1eb 100644 --- a/engines/gob/dataio.h +++ b/engines/gob/dataio.h @@ -79,8 +79,7 @@ public:  	void closeDataFile(bool itk = 0);  	byte *getUnpackedData(const char *name);  	void closeData(int16 handle); -	int16 openData(const char *path, -			Common::File::AccessMode mode = Common::File::kFileReadMode); +	int16 openData(const char *path);  	DataStream *openAsStream(int16 handle, bool dispose = false);  	int32 getDataSize(const char *name); @@ -104,8 +103,7 @@ protected:  	class GobEngine *_vm; -	int16 file_open(const char *path, -			Common::File::AccessMode mode = Common::File::kFileReadMode); +	int16 file_open(const char *path);  	Common::File *file_getHandle(int16 handle);  	const Common::File *file_getHandle(int16 handle) const; diff --git a/engines/gob/detection.cpp b/engines/gob/detection.cpp index 8351f2ecfb..63a0f8f45b 100644 --- a/engines/gob/detection.cpp +++ b/engines/gob/detection.cpp @@ -277,6 +277,19 @@ static const GOBGameDescription gameDescriptions[] = {  		kFeaturesNone,  		"intro"  	}, +	{ // Supplied by raina in the forums +		{ +			"gob1", +			"", +			AD_ENTRY1s("intro.stk", "6d837c6380d8f4d984c9f6cc0026df4f", 192712), +			EN_ANY, +			kPlatformMacintosh, +			Common::ADGF_NO_FLAGS +		}, +		kGameTypeGob1, +		kFeaturesNone, +		"intro" +	},  	{ // Supplied by paul66 in bug report #1652352  		{  			"gob1", diff --git a/engines/gob/driver_vga.cpp b/engines/gob/driver_vga.cpp index f68ce47783..2d3a10b570 100644 --- a/engines/gob/driver_vga.cpp +++ b/engines/gob/driver_vga.cpp @@ -112,7 +112,7 @@ void VGAVideoDriver::drawSprite(SurfaceDesc *source, SurfaceDesc *dest,  	if ((width < 1) || (height < 1))  		return; -	byte *srcPos = source->getVidMem() + (top * source->getWidth()) + left; +	const byte *srcPos = source->getVidMem() + (top * source->getWidth()) + left;  	byte *destPos = dest->getVidMem() + (y * dest->getWidth()) + x;  	uint32 size = width * height; diff --git a/engines/gob/gob.cpp b/engines/gob/gob.cpp index a3fe0ebbe2..34443251d8 100644 --- a/engines/gob/gob.cpp +++ b/engines/gob/gob.cpp @@ -147,6 +147,15 @@ void GobEngine::validateVideoMode(int16 videoMode) {  		error("Video mode 0x%X is not supported!", videoMode);  } +Endianness GobEngine::getEndianness() const { +	if ((_vm->getPlatform() == Common::kPlatformAmiga) || +	    (_vm->getPlatform() == Common::kPlatformMacintosh) || +	    (_vm->getPlatform() == Common::kPlatformAtariST)) +		return kEndiannessBE; + +	return kEndiannessLE; +} +  Common::Platform GobEngine::getPlatform() const {  	return _platform;  } diff --git a/engines/gob/gob.h b/engines/gob/gob.h index ae2b53bc31..041658baea 100644 --- a/engines/gob/gob.h +++ b/engines/gob/gob.h @@ -79,6 +79,11 @@ class SaveLoad;  #define VAR(var)                    READ_VAR_UINT32(var) +enum Endianness { +	kEndiannessLE, +	kEndiannessBE +}; +  enum GameType {  	kGameTypeNone = 0,  	kGameTypeGob1, @@ -230,6 +235,7 @@ public:  	void validateLanguage();  	void validateVideoMode(int16 videoMode); +	Endianness getEndianness() const;  	Common::Platform getPlatform() const;  	GameType getGameType() const;  	bool isCD() const; diff --git a/engines/gob/goblin.cpp b/engines/gob/goblin.cpp index e7aed0790e..5add0b9cea 100644 --- a/engines/gob/goblin.cpp +++ b/engines/gob/goblin.cpp @@ -78,58 +78,6 @@ Goblin::Goblin(GobEngine *vm) : _vm(vm) {  	_pressedMapY = 0;  	_pathExistence = 0; -	_some0ValPtr = 0; - -	_gobRetVarPtr = 0; -	_curGobVarPtr = 0; -	_curGobXPosVarPtr = 0; -	_curGobYPosVarPtr = 0; -	_itemInPocketVarPtr = 0; - -	_curGobStateVarPtr = 0; -	_curGobFrameVarPtr = 0; -	_curGobMultStateVarPtr = 0; -	_curGobNextStateVarPtr = 0; -	_curGobScrXVarPtr = 0; -	_curGobScrYVarPtr = 0; -	_curGobLeftVarPtr = 0; -	_curGobTopVarPtr = 0; -	_curGobRightVarPtr = 0; -	_curGobBottomVarPtr = 0; -	_curGobDoAnimVarPtr = 0; -	_curGobOrderVarPtr = 0; -	_curGobNoTickVarPtr = 0; -	_curGobTypeVarPtr = 0; -	_curGobMaxTickVarPtr = 0; -	_curGobTickVarPtr = 0; -	_curGobActStartStateVarPtr = 0; -	_curGobLookDirVarPtr = 0; -	_curGobPickableVarPtr = 0; -	_curGobRelaxVarPtr = 0; -	_curGobMaxFrameVarPtr = 0; - -	_destItemStateVarPtr = 0; -	_destItemFrameVarPtr = 0; -	_destItemMultStateVarPtr = 0; -	_destItemNextStateVarPtr = 0; -	_destItemScrXVarPtr = 0; -	_destItemScrYVarPtr = 0; -	_destItemLeftVarPtr = 0; -	_destItemTopVarPtr = 0; -	_destItemRightVarPtr = 0; -	_destItemBottomVarPtr = 0; -	_destItemDoAnimVarPtr = 0; -	_destItemOrderVarPtr = 0; -	_destItemNoTickVarPtr = 0; -	_destItemTypeVarPtr = 0; -	_destItemMaxTickVarPtr = 0; -	_destItemTickVarPtr = 0; -	_destItemActStartStVarPtr = 0; -	_destItemLookDirVarPtr = 0; -	_destItemPickableVarPtr = 0; -	_destItemRelaxVarPtr = 0; -	_destItemMaxFrameVarPtr = 0; -  	_destItemType = 0;  	_destItemState = 0;  	for (int i = 0; i < 20; i++) { @@ -690,7 +638,7 @@ void Goblin::switchGoblin(int16 index) {  	_gobDestY = tmp;  	_vm->_map->_curGoblinY = tmp; -	*_curGobVarPtr = _currentGoblin; +	_curGobVarPtr = (uint32) _currentGoblin;  	_pathExistence = 0;  	_readyToAct = 0;  } @@ -1250,172 +1198,172 @@ void Goblin::loadObjects(const char *source) {  void Goblin::saveGobDataToVars(int16 xPos, int16 yPos, int16 someVal) {  	Gob_Object *obj; -	*_some0ValPtr = someVal; -	*_curGobXPosVarPtr = xPos; -	*_curGobYPosVarPtr = yPos; -	*_itemInPocketVarPtr = _itemIndInPocket; +	_some0ValPtr = (uint32) someVal; +	_curGobXPosVarPtr = (uint32) xPos; +	_curGobYPosVarPtr = (uint32) yPos; +	_itemInPocketVarPtr = (uint32) _itemIndInPocket;  	obj = _goblins[_currentGoblin]; -	*_curGobStateVarPtr = obj->state; -	*_curGobFrameVarPtr = obj->curFrame; -	*_curGobMultStateVarPtr = obj->multState; -	*_curGobNextStateVarPtr = obj->nextState; -	*_curGobScrXVarPtr = obj->xPos; -	*_curGobScrYVarPtr = obj->yPos; -	*_curGobLeftVarPtr = obj->left; -	*_curGobTopVarPtr = obj->top; -	*_curGobRightVarPtr = obj->right; -	*_curGobBottomVarPtr = obj->bottom; -	*_curGobDoAnimVarPtr = obj->doAnim; -	*_curGobOrderVarPtr = obj->order; -	*_curGobNoTickVarPtr = obj->noTick; -	*_curGobTypeVarPtr = obj->type; -	*_curGobMaxTickVarPtr = obj->maxTick; -	*_curGobTickVarPtr = obj->tick; -	*_curGobActStartStateVarPtr = obj->actionStartState; -	*_curGobLookDirVarPtr = obj->curLookDir; -	*_curGobPickableVarPtr = obj->pickable; -	*_curGobRelaxVarPtr = obj->relaxTime; -	*_curGobMaxFrameVarPtr = getObjMaxFrame(obj); +	_curGobStateVarPtr = (uint32) obj->state; +	_curGobFrameVarPtr = (uint32) obj->curFrame; +	_curGobMultStateVarPtr = (uint32) obj->multState; +	_curGobNextStateVarPtr = (uint32) obj->nextState; +	_curGobScrXVarPtr = (uint32) obj->xPos; +	_curGobScrYVarPtr = (uint32) obj->yPos; +	_curGobLeftVarPtr = (uint32) obj->left; +	_curGobTopVarPtr = (uint32) obj->top; +	_curGobRightVarPtr = (uint32) obj->right; +	_curGobBottomVarPtr = (uint32) obj->bottom; +	_curGobDoAnimVarPtr = (uint32) obj->doAnim; +	_curGobOrderVarPtr = (uint32) obj->order; +	_curGobNoTickVarPtr = (uint32) obj->noTick; +	_curGobTypeVarPtr = (uint32) obj->type; +	_curGobMaxTickVarPtr = (uint32) obj->maxTick; +	_curGobTickVarPtr = (uint32) obj->tick; +	_curGobActStartStateVarPtr = (uint32) obj->actionStartState; +	_curGobLookDirVarPtr = (uint32) obj->curLookDir; +	_curGobPickableVarPtr = (uint32) obj->pickable; +	_curGobRelaxVarPtr = (uint32) obj->relaxTime; +	_curGobMaxFrameVarPtr = (uint32) getObjMaxFrame(obj);  	if (_actDestItemDesc == 0)  		return;  	obj = _actDestItemDesc; -	*_destItemStateVarPtr = obj->state; -	*_destItemFrameVarPtr = obj->curFrame; -	*_destItemMultStateVarPtr = obj->multState; -	*_destItemNextStateVarPtr = obj->nextState; -	*_destItemScrXVarPtr = obj->xPos; -	*_destItemScrYVarPtr = obj->yPos; -	*_destItemLeftVarPtr = obj->left; -	*_destItemTopVarPtr = obj->top; -	*_destItemRightVarPtr = obj->right; -	*_destItemBottomVarPtr = obj->bottom; -	*_destItemDoAnimVarPtr = obj->doAnim; -	*_destItemOrderVarPtr = obj->order; -	*_destItemNoTickVarPtr = obj->noTick; -	*_destItemTypeVarPtr = obj->type; -	*_destItemMaxTickVarPtr = obj->maxTick; -	*_destItemTickVarPtr = obj->tick; -	*_destItemActStartStVarPtr = obj->actionStartState; -	*_destItemLookDirVarPtr = obj->curLookDir; -	*_destItemPickableVarPtr = obj->pickable; -	*_destItemRelaxVarPtr = obj->relaxTime; -	*_destItemMaxFrameVarPtr = getObjMaxFrame(obj); +	_destItemStateVarPtr = (uint32) obj->state; +	_destItemFrameVarPtr = (uint32) obj->curFrame; +	_destItemMultStateVarPtr = (uint32) obj->multState; +	_destItemNextStateVarPtr = (uint32) obj->nextState; +	_destItemScrXVarPtr = (uint32) obj->xPos; +	_destItemScrYVarPtr = (uint32) obj->yPos; +	_destItemLeftVarPtr = (uint32) obj->left; +	_destItemTopVarPtr = (uint32) obj->top; +	_destItemRightVarPtr = (uint32) obj->right; +	_destItemBottomVarPtr = (uint32) obj->bottom; +	_destItemDoAnimVarPtr = (uint32) obj->doAnim; +	_destItemOrderVarPtr = (uint32) obj->order; +	_destItemNoTickVarPtr = (uint32) obj->noTick; +	_destItemTypeVarPtr = (uint32) obj->type; +	_destItemMaxTickVarPtr = (uint32) obj->maxTick; +	_destItemTickVarPtr = (uint32) obj->tick; +	_destItemActStartStVarPtr = (uint32) obj->actionStartState; +	_destItemLookDirVarPtr = (uint32) obj->curLookDir; +	_destItemPickableVarPtr = (uint32) obj->pickable; +	_destItemRelaxVarPtr = (uint32) obj->relaxTime; +	_destItemMaxFrameVarPtr = (uint32) getObjMaxFrame(obj);  	_destItemState = obj->state;  	_destItemType = obj->type;  }  void Goblin::initVarPointers(void) { -	_gobRetVarPtr = (int32 *)VAR_ADDRESS(59); -	_curGobStateVarPtr = (int32 *)VAR_ADDRESS(60); -	_curGobFrameVarPtr = (int32 *)VAR_ADDRESS(61); -	_curGobMultStateVarPtr = (int32 *)VAR_ADDRESS(62); -	_curGobNextStateVarPtr = (int32 *)VAR_ADDRESS(63); -	_curGobScrXVarPtr = (int32 *)VAR_ADDRESS(64); -	_curGobScrYVarPtr = (int32 *)VAR_ADDRESS(65); -	_curGobLeftVarPtr = (int32 *)VAR_ADDRESS(66); -	_curGobTopVarPtr = (int32 *)VAR_ADDRESS(67); -	_curGobRightVarPtr = (int32 *)VAR_ADDRESS(68); -	_curGobBottomVarPtr = (int32 *)VAR_ADDRESS(69); -	_curGobDoAnimVarPtr = (int32 *)VAR_ADDRESS(70); -	_curGobOrderVarPtr = (int32 *)VAR_ADDRESS(71); -	_curGobNoTickVarPtr = (int32 *)VAR_ADDRESS(72); -	_curGobTypeVarPtr = (int32 *)VAR_ADDRESS(73); -	_curGobMaxTickVarPtr = (int32 *)VAR_ADDRESS(74); -	_curGobTickVarPtr = (int32 *)VAR_ADDRESS(75); -	_curGobActStartStateVarPtr = (int32 *)VAR_ADDRESS(76); -	_curGobLookDirVarPtr = (int32 *)VAR_ADDRESS(77); -	_curGobPickableVarPtr = (int32 *)VAR_ADDRESS(80); -	_curGobRelaxVarPtr = (int32 *)VAR_ADDRESS(81); -	_destItemStateVarPtr = (int32 *)VAR_ADDRESS(82); -	_destItemFrameVarPtr = (int32 *)VAR_ADDRESS(83); -	_destItemMultStateVarPtr = (int32 *)VAR_ADDRESS(84); -	_destItemNextStateVarPtr = (int32 *)VAR_ADDRESS(85); -	_destItemScrXVarPtr = (int32 *)VAR_ADDRESS(86); -	_destItemScrYVarPtr = (int32 *)VAR_ADDRESS(87); -	_destItemLeftVarPtr = (int32 *)VAR_ADDRESS(88); -	_destItemTopVarPtr = (int32 *)VAR_ADDRESS(89); -	_destItemRightVarPtr = (int32 *)VAR_ADDRESS(90); -	_destItemBottomVarPtr = (int32 *)VAR_ADDRESS(91); -	_destItemDoAnimVarPtr = (int32 *)VAR_ADDRESS(92); -	_destItemOrderVarPtr = (int32 *)VAR_ADDRESS(93); -	_destItemNoTickVarPtr = (int32 *)VAR_ADDRESS(94); -	_destItemTypeVarPtr = (int32 *)VAR_ADDRESS(95); -	_destItemMaxTickVarPtr = (int32 *)VAR_ADDRESS(96); -	_destItemTickVarPtr = (int32 *)VAR_ADDRESS(97); -	_destItemActStartStVarPtr = (int32 *)VAR_ADDRESS(98); -	_destItemLookDirVarPtr = (int32 *)VAR_ADDRESS(99); -	_destItemPickableVarPtr = (int32 *)VAR_ADDRESS(102); -	_destItemRelaxVarPtr = (int32 *)VAR_ADDRESS(103); -	_destItemMaxFrameVarPtr = (int32 *)VAR_ADDRESS(105); -	_curGobVarPtr = (int32 *)VAR_ADDRESS(106); -	_some0ValPtr = (int32 *)VAR_ADDRESS(107); -	_curGobXPosVarPtr = (int32 *)VAR_ADDRESS(108); -	_curGobYPosVarPtr = (int32 *)VAR_ADDRESS(109); -	_curGobMaxFrameVarPtr = (int32 *)VAR_ADDRESS(110); - -	_itemInPocketVarPtr = (int32 *)VAR_ADDRESS(114); - -	*_itemInPocketVarPtr = -2; +	_gobRetVarPtr.set(*_vm->_inter->_variables, 236); +	_curGobStateVarPtr.set(*_vm->_inter->_variables, 240); +	_curGobFrameVarPtr.set(*_vm->_inter->_variables, 244); +	_curGobMultStateVarPtr.set(*_vm->_inter->_variables, 248); +	_curGobNextStateVarPtr.set(*_vm->_inter->_variables, 252); +	_curGobScrXVarPtr.set(*_vm->_inter->_variables, 256); +	_curGobScrYVarPtr.set(*_vm->_inter->_variables, 260); +	_curGobLeftVarPtr.set(*_vm->_inter->_variables, 264); +	_curGobTopVarPtr.set(*_vm->_inter->_variables, 268); +	_curGobRightVarPtr.set(*_vm->_inter->_variables, 272); +	_curGobBottomVarPtr.set(*_vm->_inter->_variables, 276); +	_curGobDoAnimVarPtr.set(*_vm->_inter->_variables, 280); +	_curGobOrderVarPtr.set(*_vm->_inter->_variables, 284); +	_curGobNoTickVarPtr.set(*_vm->_inter->_variables, 288); +	_curGobTypeVarPtr.set(*_vm->_inter->_variables, 292); +	_curGobMaxTickVarPtr.set(*_vm->_inter->_variables, 296); +	_curGobTickVarPtr.set(*_vm->_inter->_variables, 300); +	_curGobActStartStateVarPtr.set(*_vm->_inter->_variables, 304); +	_curGobLookDirVarPtr.set(*_vm->_inter->_variables, 308); +	_curGobPickableVarPtr.set(*_vm->_inter->_variables, 320); +	_curGobRelaxVarPtr.set(*_vm->_inter->_variables, 324); +	_destItemStateVarPtr.set(*_vm->_inter->_variables, 328); +	_destItemFrameVarPtr.set(*_vm->_inter->_variables, 332); +	_destItemMultStateVarPtr.set(*_vm->_inter->_variables, 336); +	_destItemNextStateVarPtr.set(*_vm->_inter->_variables, 340); +	_destItemScrXVarPtr.set(*_vm->_inter->_variables, 344); +	_destItemScrYVarPtr.set(*_vm->_inter->_variables, 348); +	_destItemLeftVarPtr.set(*_vm->_inter->_variables, 352); +	_destItemTopVarPtr.set(*_vm->_inter->_variables, 356); +	_destItemRightVarPtr.set(*_vm->_inter->_variables, 360); +	_destItemBottomVarPtr.set(*_vm->_inter->_variables, 364); +	_destItemDoAnimVarPtr.set(*_vm->_inter->_variables, 368); +	_destItemOrderVarPtr.set(*_vm->_inter->_variables, 372); +	_destItemNoTickVarPtr.set(*_vm->_inter->_variables, 376); +	_destItemTypeVarPtr.set(*_vm->_inter->_variables, 380); +	_destItemMaxTickVarPtr.set(*_vm->_inter->_variables, 384); +	_destItemTickVarPtr.set(*_vm->_inter->_variables, 388); +	_destItemActStartStVarPtr.set(*_vm->_inter->_variables, 392); +	_destItemLookDirVarPtr.set(*_vm->_inter->_variables, 396); +	_destItemPickableVarPtr.set(*_vm->_inter->_variables, 408); +	_destItemRelaxVarPtr.set(*_vm->_inter->_variables, 412); +	_destItemMaxFrameVarPtr.set(*_vm->_inter->_variables, 420); +	_curGobVarPtr.set(*_vm->_inter->_variables, 424); +	_some0ValPtr.set(*_vm->_inter->_variables, 428); +	_curGobXPosVarPtr.set(*_vm->_inter->_variables, 432); +	_curGobYPosVarPtr.set(*_vm->_inter->_variables, 436); +	_curGobMaxFrameVarPtr.set(*_vm->_inter->_variables, 440); + +	_itemInPocketVarPtr.set(*_vm->_inter->_variables, 456); + +	_itemInPocketVarPtr = (uint32) -2;  }  void Goblin::loadGobDataFromVars(void) {  	Gob_Object *obj; -	_itemIndInPocket = *_itemInPocketVarPtr; +	_itemIndInPocket = (int32) _itemInPocketVarPtr;  	obj = _goblins[_currentGoblin]; -	obj->state = *_curGobStateVarPtr; -	obj->curFrame = *_curGobFrameVarPtr; -	obj->multState = *_curGobMultStateVarPtr; -	obj->nextState = *_curGobNextStateVarPtr; -	obj->xPos = *_curGobScrXVarPtr; -	obj->yPos = *_curGobScrYVarPtr; -	obj->left = *_curGobLeftVarPtr; -	obj->top = *_curGobTopVarPtr; -	obj->right = *_curGobRightVarPtr; -	obj->bottom = *_curGobBottomVarPtr; -	obj->doAnim = *_curGobDoAnimVarPtr; -	obj->order = *_curGobOrderVarPtr; -	obj->noTick = *_curGobNoTickVarPtr; -	obj->type = *_curGobTypeVarPtr; -	obj->maxTick = *_curGobMaxTickVarPtr; -	obj->tick = *_curGobTickVarPtr; -	obj->actionStartState = *_curGobActStartStateVarPtr; -	obj->curLookDir = *_curGobLookDirVarPtr; -	obj->pickable = *_curGobPickableVarPtr; -	obj->relaxTime = *_curGobRelaxVarPtr; +	obj->state = (int32) _curGobStateVarPtr; +	obj->curFrame = (int32) _curGobFrameVarPtr; +	obj->multState = (int32) _curGobMultStateVarPtr; +	obj->nextState = (int32) _curGobNextStateVarPtr; +	obj->xPos = (int32) _curGobScrXVarPtr; +	obj->yPos = (int32) _curGobScrYVarPtr; +	obj->left = (int32) _curGobLeftVarPtr; +	obj->top = (int32) _curGobTopVarPtr; +	obj->right = (int32) _curGobRightVarPtr; +	obj->bottom = (int32) _curGobBottomVarPtr; +	obj->doAnim = (int32) _curGobDoAnimVarPtr; +	obj->order = (int32) _curGobOrderVarPtr; +	obj->noTick = (int32) _curGobNoTickVarPtr; +	obj->type = (int32) _curGobTypeVarPtr; +	obj->maxTick = (int32) _curGobMaxTickVarPtr; +	obj->tick = (int32) _curGobTickVarPtr; +	obj->actionStartState = (int32) _curGobActStartStateVarPtr; +	obj->curLookDir = (int32) _curGobLookDirVarPtr; +	obj->pickable = (int32) _curGobPickableVarPtr; +	obj->relaxTime = (int32) _curGobRelaxVarPtr;  	if (_actDestItemDesc == 0)  		return;  	obj = _actDestItemDesc; -	obj->state = *_destItemStateVarPtr; -	obj->curFrame = *_destItemFrameVarPtr; -	obj->multState = *_destItemMultStateVarPtr; -	obj->nextState = *_destItemNextStateVarPtr; -	obj->xPos = *_destItemScrXVarPtr; -	obj->yPos = *_destItemScrYVarPtr; -	obj->left = *_destItemLeftVarPtr; -	obj->top = *_destItemTopVarPtr; -	obj->right = *_destItemRightVarPtr; -	obj->bottom = *_destItemBottomVarPtr; -	obj->doAnim = *_destItemDoAnimVarPtr; -	obj->order = *_destItemOrderVarPtr; -	obj->noTick = *_destItemNoTickVarPtr; -	obj->type = *_destItemTypeVarPtr; -	obj->maxTick = *_destItemMaxTickVarPtr; -	obj->tick = *_destItemTickVarPtr; -	obj->actionStartState = *_destItemActStartStVarPtr; -	obj->curLookDir = *_destItemLookDirVarPtr; -	obj->pickable = *_destItemPickableVarPtr; -	obj->relaxTime = *_destItemRelaxVarPtr; +	obj->state = (int32) _destItemStateVarPtr; +	obj->curFrame = (int32) _destItemFrameVarPtr; +	obj->multState = (int32) _destItemMultStateVarPtr; +	obj->nextState = (int32) _destItemNextStateVarPtr; +	obj->xPos = (int32) _destItemScrXVarPtr; +	obj->yPos = (int32) _destItemScrYVarPtr; +	obj->left = (int32) _destItemLeftVarPtr; +	obj->top = (int32) _destItemTopVarPtr; +	obj->right = (int32) _destItemRightVarPtr; +	obj->bottom = (int32) _destItemBottomVarPtr; +	obj->doAnim = (int32) _destItemDoAnimVarPtr; +	obj->order = (int32) _destItemOrderVarPtr; +	obj->noTick = (int32) _destItemNoTickVarPtr; +	obj->type = (int32) _destItemTypeVarPtr; +	obj->maxTick = (int32) _destItemMaxTickVarPtr; +	obj->tick = (int32) _destItemTickVarPtr; +	obj->actionStartState = (int32) _destItemActStartStVarPtr; +	obj->curLookDir = (int32) _destItemLookDirVarPtr; +	obj->pickable = (int32) _destItemPickableVarPtr; +	obj->relaxTime = (int32) _destItemRelaxVarPtr;  	if (obj->type != _destItemType)  		obj->toRedraw = 1; diff --git a/engines/gob/goblin.h b/engines/gob/goblin.h index 3fd8a9f93b..2100bcbdac 100644 --- a/engines/gob/goblin.h +++ b/engines/gob/goblin.h @@ -28,6 +28,7 @@  #include "gob/util.h"  #include "gob/mult.h" +#include "gob/variables.h"  #include "gob/sound/sounddesc.h"  namespace Gob { @@ -115,57 +116,57 @@ public:  	char _pathExistence;  	// Pointers to interpreter variables -	int32 *_some0ValPtr; - -	int32 *_gobRetVarPtr; -	int32 *_curGobVarPtr; -	int32 *_curGobXPosVarPtr; -	int32 *_curGobYPosVarPtr; -	int32 *_itemInPocketVarPtr; - -	int32 *_curGobStateVarPtr; -	int32 *_curGobFrameVarPtr; -	int32 *_curGobMultStateVarPtr; -	int32 *_curGobNextStateVarPtr; -	int32 *_curGobScrXVarPtr; -	int32 *_curGobScrYVarPtr; -	int32 *_curGobLeftVarPtr; -	int32 *_curGobTopVarPtr; -	int32 *_curGobRightVarPtr; -	int32 *_curGobBottomVarPtr; -	int32 *_curGobDoAnimVarPtr; -	int32 *_curGobOrderVarPtr; -	int32 *_curGobNoTickVarPtr; -	int32 *_curGobTypeVarPtr; -	int32 *_curGobMaxTickVarPtr; -	int32 *_curGobTickVarPtr; -	int32 *_curGobActStartStateVarPtr; -	int32 *_curGobLookDirVarPtr; -	int32 *_curGobPickableVarPtr; -	int32 *_curGobRelaxVarPtr; -	int32 *_curGobMaxFrameVarPtr; - -	int32 *_destItemStateVarPtr; -	int32 *_destItemFrameVarPtr; -	int32 *_destItemMultStateVarPtr; -	int32 *_destItemNextStateVarPtr; -	int32 *_destItemScrXVarPtr; -	int32 *_destItemScrYVarPtr; -	int32 *_destItemLeftVarPtr; -	int32 *_destItemTopVarPtr; -	int32 *_destItemRightVarPtr; -	int32 *_destItemBottomVarPtr; -	int32 *_destItemDoAnimVarPtr; -	int32 *_destItemOrderVarPtr; -	int32 *_destItemNoTickVarPtr; -	int32 *_destItemTypeVarPtr; -	int32 *_destItemMaxTickVarPtr; -	int32 *_destItemTickVarPtr; -	int32 *_destItemActStartStVarPtr; -	int32 *_destItemLookDirVarPtr; -	int32 *_destItemPickableVarPtr; -	int32 *_destItemRelaxVarPtr; -	int32 *_destItemMaxFrameVarPtr; +	VariableReference _some0ValPtr; + +	VariableReference _gobRetVarPtr; +	VariableReference _curGobVarPtr; +	VariableReference _curGobXPosVarPtr; +	VariableReference _curGobYPosVarPtr; +	VariableReference _itemInPocketVarPtr; + +	VariableReference _curGobStateVarPtr; +	VariableReference _curGobFrameVarPtr; +	VariableReference _curGobMultStateVarPtr; +	VariableReference _curGobNextStateVarPtr; +	VariableReference _curGobScrXVarPtr; +	VariableReference _curGobScrYVarPtr; +	VariableReference _curGobLeftVarPtr; +	VariableReference _curGobTopVarPtr; +	VariableReference _curGobRightVarPtr; +	VariableReference _curGobBottomVarPtr; +	VariableReference _curGobDoAnimVarPtr; +	VariableReference _curGobOrderVarPtr; +	VariableReference _curGobNoTickVarPtr; +	VariableReference _curGobTypeVarPtr; +	VariableReference _curGobMaxTickVarPtr; +	VariableReference _curGobTickVarPtr; +	VariableReference _curGobActStartStateVarPtr; +	VariableReference _curGobLookDirVarPtr; +	VariableReference _curGobPickableVarPtr; +	VariableReference _curGobRelaxVarPtr; +	VariableReference _curGobMaxFrameVarPtr; + +	VariableReference _destItemStateVarPtr; +	VariableReference _destItemFrameVarPtr; +	VariableReference _destItemMultStateVarPtr; +	VariableReference _destItemNextStateVarPtr; +	VariableReference _destItemScrXVarPtr; +	VariableReference _destItemScrYVarPtr; +	VariableReference _destItemLeftVarPtr; +	VariableReference _destItemTopVarPtr; +	VariableReference _destItemRightVarPtr; +	VariableReference _destItemBottomVarPtr; +	VariableReference _destItemDoAnimVarPtr; +	VariableReference _destItemOrderVarPtr; +	VariableReference _destItemNoTickVarPtr; +	VariableReference _destItemTypeVarPtr; +	VariableReference _destItemMaxTickVarPtr; +	VariableReference _destItemTickVarPtr; +	VariableReference _destItemActStartStVarPtr; +	VariableReference _destItemLookDirVarPtr; +	VariableReference _destItemPickableVarPtr; +	VariableReference _destItemRelaxVarPtr; +	VariableReference _destItemMaxFrameVarPtr;  	int16 _destItemType;  	int16 _destItemState; diff --git a/engines/gob/goblin_v2.cpp b/engines/gob/goblin_v2.cpp index 9144e35070..d763aeb01c 100644 --- a/engines/gob/goblin_v2.cpp +++ b/engines/gob/goblin_v2.cpp @@ -88,7 +88,7 @@ void Goblin_v2::placeObject(Gob_Object *objDesc, char animated,  				(_vm->_scenery->_animBottom - _vm->_scenery->_animTop) - (y + 1) / 2;  		*obj->pPosX = x * _vm->_map->_tilesWidth;  	} else { -		if (obj->goblinStates[state] != 0) { +		if ((obj->goblinStates != 0) && (obj->goblinStates[state] != 0)) {  			layer = obj->goblinStates[state][0].layer;  			animation = obj->goblinStates[state][0].animation;  			objAnim->state = state; diff --git a/engines/gob/inter.cpp b/engines/gob/inter.cpp index 9c39653a1d..02e7f99cbd 100644 --- a/engines/gob/inter.cpp +++ b/engines/gob/inter.cpp @@ -212,25 +212,35 @@ void Inter::funcBlock(int16 retFlag) {  			break;  		// WORKAROUND: -		// The EGA version of gob1 doesn't add a delay after showing +		// The EGA and Mac versions of gob1 doesn't add a delay after showing  		// images between levels. We manually add it here. -		if ((_vm->getGameType() == kGameTypeGob1) && _vm->isEGA()) { +		if ((_vm->getGameType() == kGameTypeGob1) && +		   (_vm->isEGA() || (_vm->getPlatform() == Common::kPlatformMacintosh))) { +  			int addr = _vm->_global->_inter_execPtr-_vm->_game->_totFileData; -			if ((startaddr == 0x18B4 && addr == 0x1A7F && // Zombie + +			if ((startaddr == 0x18B4 && addr == 0x1A7F && // Zombie, EGA +				 !strncmp(_vm->_game->_curTotFile, "avt005.tot", 10)) || +			  (startaddr == 0x188D && addr == 0x1A58 && // Zombie, Mac  				 !strncmp(_vm->_game->_curTotFile, "avt005.tot", 10)) ||  				(startaddr == 0x1299 && addr == 0x139A && // Dungeon  				 !strncmp(_vm->_game->_curTotFile, "avt006.tot", 10)) || -				(startaddr == 0x11C0 && addr == 0x12C9 && // Cauldron +				(startaddr == 0x11C0 && addr == 0x12C9 && // Cauldron, EGA +				 !strncmp(_vm->_game->_curTotFile, "avt012.tot", 10)) || +				(startaddr == 0x11C8 && addr == 0x1341 && // Cauldron, Mac  				 !strncmp(_vm->_game->_curTotFile, "avt012.tot", 10)) ||  				(startaddr == 0x09F2 && addr == 0x0AF3 && // Statue  				 !strncmp(_vm->_game->_curTotFile, "avt016.tot", 10)) ||  				(startaddr == 0x0B92 && addr == 0x0C93 && // Castle  				 !strncmp(_vm->_game->_curTotFile, "avt019.tot", 10)) || -				(startaddr == 0x17D9 && addr == 0x18DA && // Finale +				(startaddr == 0x17D9 && addr == 0x18DA && // Finale, EGA +				 !strncmp(_vm->_game->_curTotFile, "avt022.tot", 10)) || +				(startaddr == 0x17E9 && addr == 0x19A8 && // Finale, Mac  				 !strncmp(_vm->_game->_curTotFile, "avt022.tot", 10))) {  				_vm->_util->longDelay(5000);  			} +  		} // End of workaround  		cmd = *_vm->_global->_inter_execPtr; @@ -286,9 +296,7 @@ void Inter::callSub(int16 retFlag) {  }  void Inter::allocateVars(uint32 count) { -	if ((_vm->getPlatform() == Common::kPlatformAmiga) || -	    (_vm->getPlatform() == Common::kPlatformMacintosh) || -	    (_vm->getPlatform() == Common::kPlatformAtariST)) +	if (_vm->getEndianness() == kEndiannessBE)  		_variables = new VariablesBE(count * 4);  	else  		_variables = new VariablesLE(count * 4); diff --git a/engines/gob/inter.h b/engines/gob/inter.h index 60b3974d6d..b684be6c07 100644 --- a/engines/gob/inter.h +++ b/engines/gob/inter.h @@ -79,7 +79,7 @@ protected:  	};  	struct OpGobParams {  		int16 extraData; -		int32 *retVarPtr; +		VariableReference retVarPtr;  		Goblin::Gob_Object *objDesc;  	}; diff --git a/engines/gob/inter_v1.cpp b/engines/gob/inter_v1.cpp index e2b8d65112..865d188a2e 100644 --- a/engines/gob/inter_v1.cpp +++ b/engines/gob/inter_v1.cpp @@ -912,12 +912,21 @@ void Inter_v1::o1_initMult() {  	animDataVar = _vm->_parse->parseVarIndex();  	if (_vm->_mult->_objects && (oldObjCount != _vm->_mult->_objCount)) { +  		warning("Initializing new objects without having "  				"cleaned up the old ones at first"); + +		for (int i = 0; i < _vm->_mult->_objCount; i++) { +			delete _vm->_mult->_objects[i].pPosX; +			delete _vm->_mult->_objects[i].pPosY; +		} +  		delete[] _vm->_mult->_objects;  		delete[] _vm->_mult->_renderData; +  		_vm->_mult->_objects = 0;  		_vm->_mult->_renderObjs = 0; +  	}  	if (_vm->_mult->_objects == 0) { @@ -933,8 +942,8 @@ void Inter_v1::o1_initMult() {  			uint32 offPosY = i * 4 + (posYVar / 4) * 4;  			uint32 offAnim = animDataVar + i * 4 * _vm->_global->_inter_animDataSize; -			_vm->_mult->_objects[i].pPosX = (int32 *) _variables->getAddressOff32(offPosX); -			_vm->_mult->_objects[i].pPosY = (int32 *) _variables->getAddressOff32(offPosY); +			_vm->_mult->_objects[i].pPosX = new VariableReference(*_vm->_inter->_variables, offPosX); +			_vm->_mult->_objects[i].pPosY = new VariableReference(*_vm->_inter->_variables, offPosY);  			_vm->_mult->_objects[i].pAnimData =  				(Mult::Mult_AnimData *) _variables->getAddressOff8(offAnim, @@ -1774,7 +1783,7 @@ bool Inter_v1::o1_goblinFunc(OpFuncParams ¶ms) {  	gobParams.extraData = 0;  	gobParams.objDesc = 0; -	gobParams.retVarPtr = (int32 *) VAR_ADDRESS(59); +	gobParams.retVarPtr.set(*_vm->_inter->_variables, 236);  	cmd = load16();  	_vm->_global->_inter_execPtr += 2; @@ -2268,49 +2277,49 @@ bool Inter_v1::o1_manageDataFile(OpFuncParams ¶ms) {  void Inter_v1::o1_setState(OpGobParams ¶ms) {  	params.objDesc->state = params.extraData;  	if (params.objDesc == _vm->_goblin->_actDestItemDesc) -		*_vm->_goblin->_destItemStateVarPtr = params.extraData; +		_vm->_goblin->_destItemStateVarPtr = (uint32) params.extraData;  }  void Inter_v1::o1_setCurFrame(OpGobParams ¶ms) {  	params.objDesc->curFrame = params.extraData;  	if (params.objDesc == _vm->_goblin->_actDestItemDesc) -		*_vm->_goblin->_destItemFrameVarPtr = params.extraData; +		_vm->_goblin->_destItemFrameVarPtr = (uint32) params.extraData;  }  void Inter_v1::o1_setNextState(OpGobParams ¶ms) {  	params.objDesc->nextState = params.extraData;  	if (params.objDesc == _vm->_goblin->_actDestItemDesc) -		*_vm->_goblin->_destItemNextStateVarPtr = params.extraData; +		_vm->_goblin->_destItemNextStateVarPtr = (uint32) params.extraData;  }  void Inter_v1::o1_setMultState(OpGobParams ¶ms) {  	params.objDesc->multState = params.extraData;  	if (params.objDesc == _vm->_goblin->_actDestItemDesc) -		*_vm->_goblin->_destItemMultStateVarPtr = params.extraData; +		_vm->_goblin->_destItemMultStateVarPtr = (uint32) params.extraData;  }  void Inter_v1::o1_setOrder(OpGobParams ¶ms) {  	params.objDesc->order = params.extraData;  	if (params.objDesc == _vm->_goblin->_actDestItemDesc) -		*_vm->_goblin->_destItemOrderVarPtr = params.extraData; +		_vm->_goblin->_destItemOrderVarPtr = (uint32) params.extraData;  }  void Inter_v1::o1_setActionStartState(OpGobParams ¶ms) {  	params.objDesc->actionStartState = params.extraData;  	if (params.objDesc == _vm->_goblin->_actDestItemDesc) -		*_vm->_goblin->_destItemActStartStVarPtr = params.extraData; +		_vm->_goblin->_destItemActStartStVarPtr = (uint32) params.extraData;  }  void Inter_v1::o1_setCurLookDir(OpGobParams ¶ms) {  	params.objDesc->curLookDir = params.extraData;  	if (params.objDesc == _vm->_goblin->_actDestItemDesc) -		*_vm->_goblin->_destItemLookDirVarPtr = params.extraData; +		_vm->_goblin->_destItemLookDirVarPtr = (uint32) params.extraData;  }  void Inter_v1::o1_setType(OpGobParams ¶ms) {  	params.objDesc->type = params.extraData;  	if (params.objDesc == _vm->_goblin->_actDestItemDesc) -		*_vm->_goblin->_destItemTypeVarPtr = params.extraData; +		_vm->_goblin->_destItemTypeVarPtr = (uint32) params.extraData;  	if (params.extraData == 0)  		params.objDesc->toRedraw = 1; @@ -2319,107 +2328,107 @@ void Inter_v1::o1_setType(OpGobParams ¶ms) {  void Inter_v1::o1_setNoTick(OpGobParams ¶ms) {  	params.objDesc->noTick = params.extraData;  	if (params.objDesc == _vm->_goblin->_actDestItemDesc) -		*_vm->_goblin->_destItemNoTickVarPtr = params.extraData; +		_vm->_goblin->_destItemNoTickVarPtr = (uint32) params.extraData;  }  void Inter_v1::o1_setPickable(OpGobParams ¶ms) {  	params.objDesc->pickable = params.extraData;  	if (params.objDesc == _vm->_goblin->_actDestItemDesc) -		*_vm->_goblin->_destItemPickableVarPtr = params.extraData; +		_vm->_goblin->_destItemPickableVarPtr = (uint32) params.extraData;  }  void Inter_v1::o1_setXPos(OpGobParams ¶ms) {  	params.objDesc->xPos = params.extraData;  	if (params.objDesc == _vm->_goblin->_actDestItemDesc) -		*_vm->_goblin->_destItemScrXVarPtr = params.extraData; +		_vm->_goblin->_destItemScrXVarPtr = (uint32) params.extraData;  }  void Inter_v1::o1_setYPos(OpGobParams ¶ms) {  	params.objDesc->yPos = params.extraData;  	if (params.objDesc == _vm->_goblin->_actDestItemDesc) -		*_vm->_goblin->_destItemScrYVarPtr = params.extraData; +		_vm->_goblin->_destItemScrYVarPtr = (uint32) params.extraData;  }  void Inter_v1::o1_setDoAnim(OpGobParams ¶ms) {  	params.objDesc->doAnim = params.extraData;  	if (params.objDesc == _vm->_goblin->_actDestItemDesc) -		*_vm->_goblin->_destItemDoAnimVarPtr = params.extraData; +		_vm->_goblin->_destItemDoAnimVarPtr = (uint32) params.extraData;  }  void Inter_v1::o1_setRelaxTime(OpGobParams ¶ms) {  	params.objDesc->relaxTime = params.extraData;  	if (params.objDesc == _vm->_goblin->_actDestItemDesc) -		*_vm->_goblin->_destItemRelaxVarPtr = params.extraData; +		_vm->_goblin->_destItemRelaxVarPtr = (uint32) params.extraData;  }  void Inter_v1::o1_setMaxTick(OpGobParams ¶ms) {  	params.objDesc->maxTick = params.extraData;  	if (params.objDesc == _vm->_goblin->_actDestItemDesc) -		*_vm->_goblin->_destItemMaxTickVarPtr = params.extraData; +		_vm->_goblin->_destItemMaxTickVarPtr = (uint32) params.extraData;  }  void Inter_v1::o1_getState(OpGobParams ¶ms) { -	*params.retVarPtr = params.objDesc->state; +	params.retVarPtr = (uint32) params.objDesc->state;  }  void Inter_v1::o1_getCurFrame(OpGobParams ¶ms) { -	*params.retVarPtr = params.objDesc->curFrame; +	params.retVarPtr = (uint32) params.objDesc->curFrame;  }  void Inter_v1::o1_getNextState(OpGobParams ¶ms) { -	*params.retVarPtr = params.objDesc->nextState; +	params.retVarPtr = (uint32) params.objDesc->nextState;  }  void Inter_v1::o1_getMultState(OpGobParams ¶ms) { -	*params.retVarPtr = params.objDesc->multState; +	params.retVarPtr = (uint32) params.objDesc->multState;  }  void Inter_v1::o1_getOrder(OpGobParams ¶ms) { -	*params.retVarPtr = params.objDesc->order; +	params.retVarPtr = (uint32) params.objDesc->order;  }  void Inter_v1::o1_getActionStartState(OpGobParams ¶ms) { -	*params.retVarPtr = params.objDesc->actionStartState; +	params.retVarPtr = (uint32) params.objDesc->actionStartState;  }  void Inter_v1::o1_getCurLookDir(OpGobParams ¶ms) { -	*params.retVarPtr = params.objDesc->curLookDir; +	params.retVarPtr = (uint32) params.objDesc->curLookDir;  }  void Inter_v1::o1_getType(OpGobParams ¶ms) { -	*params.retVarPtr = params.objDesc->type; +	params.retVarPtr = (uint32) params.objDesc->type;  }  void Inter_v1::o1_getNoTick(OpGobParams ¶ms) { -	*params.retVarPtr = params.objDesc->noTick; +	params.retVarPtr = (uint32) params.objDesc->noTick;  }  void Inter_v1::o1_getPickable(OpGobParams ¶ms) { -	*params.retVarPtr = params.objDesc->pickable; +	params.retVarPtr = (uint32) params.objDesc->pickable;  }  void Inter_v1::o1_getObjMaxFrame(OpGobParams ¶ms) { -	*params.retVarPtr = _vm->_goblin->getObjMaxFrame(params.objDesc); +	params.retVarPtr = (uint32) _vm->_goblin->getObjMaxFrame(params.objDesc);  }  void Inter_v1::o1_getXPos(OpGobParams ¶ms) { -	*params.retVarPtr = params.objDesc->xPos; +	params.retVarPtr = (uint32) params.objDesc->xPos;  }  void Inter_v1::o1_getYPos(OpGobParams ¶ms) { -	*params.retVarPtr = params.objDesc->yPos; +	params.retVarPtr = (uint32) params.objDesc->yPos;  }  void Inter_v1::o1_getDoAnim(OpGobParams ¶ms) { -	*params.retVarPtr = params.objDesc->doAnim; +	params.retVarPtr = (uint32) params.objDesc->doAnim;  }  void Inter_v1::o1_getRelaxTime(OpGobParams ¶ms) { -	*params.retVarPtr = params.objDesc->relaxTime; +	params.retVarPtr = (uint32) params.objDesc->relaxTime;  }  void Inter_v1::o1_getMaxTick(OpGobParams ¶ms) { -	*params.retVarPtr = params.objDesc->maxTick; +	params.retVarPtr = (uint32) params.objDesc->maxTick;  }  void Inter_v1::o1_manipulateMap(OpGobParams ¶ms) { @@ -2435,9 +2444,9 @@ void Inter_v1::o1_getItem(OpGobParams ¶ms) {  	int16 yPos = load16();  	if ((_vm->_map->_itemsMap[yPos][xPos] & 0xFF00) != 0) -		*params.retVarPtr = (_vm->_map->_itemsMap[yPos][xPos] & 0xFF00) >> 8; +		params.retVarPtr = (uint32) ((_vm->_map->_itemsMap[yPos][xPos] & 0xFF00) >> 8);  	else -		*params.retVarPtr = _vm->_map->_itemsMap[yPos][xPos]; +		params.retVarPtr = (uint32) _vm->_map->_itemsMap[yPos][xPos];  }  void Inter_v1::o1_manipulateMapIndirect(OpGobParams ¶ms) { @@ -2460,9 +2469,9 @@ void Inter_v1::o1_getItemIndirect(OpGobParams ¶ms) {  	yPos = VAR(yPos);  	if ((_vm->_map->_itemsMap[yPos][xPos] & 0xFF00) != 0) -		*params.retVarPtr = (_vm->_map->_itemsMap[yPos][xPos] & 0xFF00) >> 8; +		params.retVarPtr = (uint32) ((_vm->_map->_itemsMap[yPos][xPos] & 0xFF00) >> 8);  	else -		*params.retVarPtr = _vm->_map->_itemsMap[yPos][xPos]; +		params.retVarPtr = (uint32) _vm->_map->_itemsMap[yPos][xPos];  }  void Inter_v1::o1_setPassMap(OpGobParams ¶ms) { @@ -2500,11 +2509,11 @@ void Inter_v1::o1_setGoblinPosH(OpGobParams ¶ms) {  	params.objDesc->curFrame = 0;  	params.objDesc->state = 21;  	if (_vm->_goblin->_currentGoblin == item) { -		*_vm->_goblin->_curGobScrXVarPtr = params.objDesc->xPos; -		*_vm->_goblin->_curGobScrYVarPtr = params.objDesc->yPos; +		_vm->_goblin->_curGobScrXVarPtr = (uint32) params.objDesc->xPos; +		_vm->_goblin->_curGobScrYVarPtr = (uint32) params.objDesc->yPos; -		*_vm->_goblin->_curGobFrameVarPtr = 0; -		*_vm->_goblin->_curGobStateVarPtr = 18; +		_vm->_goblin->_curGobFrameVarPtr = 0; +		_vm->_goblin->_curGobStateVarPtr = 18;  		_vm->_goblin->_pressedMapX = _vm->_goblin->_gobPositions[item].x;  		_vm->_goblin->_pressedMapY = _vm->_goblin->_gobPositions[item].y;  	} @@ -2512,12 +2521,12 @@ void Inter_v1::o1_setGoblinPosH(OpGobParams ¶ms) {  void Inter_v1::o1_getGoblinPosXH(OpGobParams ¶ms) {  	int16 item = load16(); -	*params.retVarPtr = _vm->_goblin->_gobPositions[item].x >> 1; +	params.retVarPtr = (uint32) (_vm->_goblin->_gobPositions[item].x >> 1);  }  void Inter_v1::o1_getGoblinPosYH(OpGobParams ¶ms) {  	int16 item = load16(); -	*params.retVarPtr = _vm->_goblin->_gobPositions[item].y >> 1; +	params.retVarPtr = (uint32) (_vm->_goblin->_gobPositions[item].y >> 1);  }  void Inter_v1::o1_setGoblinMultState(OpGobParams ¶ms) { @@ -2539,14 +2548,14 @@ void Inter_v1::o1_setGoblinMultState(OpGobParams ¶ms) {  		params.objDesc->xPos = animLayer->posX;  		params.objDesc->yPos = animLayer->posY; -		*_vm->_goblin->_curGobScrXVarPtr = params.objDesc->xPos; -		*_vm->_goblin->_curGobScrYVarPtr = params.objDesc->yPos; -		*_vm->_goblin->_curGobFrameVarPtr = 0; -		*_vm->_goblin->_curGobStateVarPtr = params.objDesc->state; -		*_vm->_goblin->_curGobNextStateVarPtr = params.objDesc->nextState; -		*_vm->_goblin->_curGobMultStateVarPtr = params.objDesc->multState; -		*_vm->_goblin->_curGobMaxFrameVarPtr = -				_vm->_goblin->getObjMaxFrame(params.objDesc); +		_vm->_goblin->_curGobScrXVarPtr = (uint32) params.objDesc->xPos; +		_vm->_goblin->_curGobScrYVarPtr = (uint32) params.objDesc->yPos; +		_vm->_goblin->_curGobFrameVarPtr = 0; +		_vm->_goblin->_curGobStateVarPtr = (uint32) params.objDesc->state; +		_vm->_goblin->_curGobNextStateVarPtr = (uint32) params.objDesc->nextState; +		_vm->_goblin->_curGobMultStateVarPtr = (uint32) params.objDesc->multState; +		_vm->_goblin->_curGobMaxFrameVarPtr = +				(uint32) _vm->_goblin->getObjMaxFrame(params.objDesc);  		_vm->_goblin->_noPick = 1;  		return;  	} @@ -2573,12 +2582,12 @@ void Inter_v1::o1_setGoblinMultState(OpGobParams ¶ms) {  	_vm->_goblin->_pressedMapY = yPos;  	_vm->_map->_curGoblinY = yPos; -	*_vm->_goblin->_curGobScrXVarPtr = params.objDesc->xPos; -	*_vm->_goblin->_curGobScrYVarPtr = params.objDesc->yPos; -	*_vm->_goblin->_curGobFrameVarPtr = 0; -	*_vm->_goblin->_curGobStateVarPtr = 21; -	*_vm->_goblin->_curGobNextStateVarPtr = 21; -	*_vm->_goblin->_curGobMultStateVarPtr = -1; +	_vm->_goblin->_curGobScrXVarPtr = (uint32) params.objDesc->xPos; +	_vm->_goblin->_curGobScrYVarPtr = (uint32) params.objDesc->yPos; +	_vm->_goblin->_curGobFrameVarPtr = 0; +	_vm->_goblin->_curGobStateVarPtr = 21; +	_vm->_goblin->_curGobNextStateVarPtr = 21; +	_vm->_goblin->_curGobMultStateVarPtr = (uint32) -1;  	_vm->_goblin->_noPick = 0;  } @@ -2598,11 +2607,11 @@ void Inter_v1::o1_setItemIndInPocket(OpGobParams ¶ms) {  }  void Inter_v1::o1_getItemIdInPocket(OpGobParams ¶ms) { -	*params.retVarPtr = _vm->_goblin->_itemIdInPocket; +	params.retVarPtr = (uint32) _vm->_goblin->_itemIdInPocket;  }  void Inter_v1::o1_getItemIndInPocket(OpGobParams ¶ms) { -	*params.retVarPtr = _vm->_goblin->_itemIndInPocket; +	params.retVarPtr = (uint32) _vm->_goblin->_itemIndInPocket;  }  void Inter_v1::o1_setGoblinPos(OpGobParams ¶ms) { @@ -2632,10 +2641,10 @@ void Inter_v1::o1_setGoblinPos(OpGobParams ¶ms) {  	params.objDesc->state = 21;  	if (_vm->_goblin->_currentGoblin == item) { -		*_vm->_goblin->_curGobScrXVarPtr = params.objDesc->xPos; -		*_vm->_goblin->_curGobScrYVarPtr = params.objDesc->yPos; -		*_vm->_goblin->_curGobFrameVarPtr = 0; -		*_vm->_goblin->_curGobStateVarPtr = 18; +		_vm->_goblin->_curGobScrXVarPtr = (uint32) params.objDesc->xPos; +		_vm->_goblin->_curGobScrYVarPtr = (uint32) params.objDesc->yPos; +		_vm->_goblin->_curGobFrameVarPtr = 0; +		_vm->_goblin->_curGobStateVarPtr = 18;  		_vm->_goblin->_pressedMapX = _vm->_goblin->_gobPositions[item].x;  		_vm->_goblin->_pressedMapY = _vm->_goblin->_gobPositions[item].y; @@ -2659,11 +2668,11 @@ void Inter_v1::o1_setGoblinState(OpGobParams ¶ms) {  	params.objDesc->yPos = animLayer->posY;  	if (item == _vm->_goblin->_currentGoblin) { -		*_vm->_goblin->_curGobScrXVarPtr = params.objDesc->xPos; -		*_vm->_goblin->_curGobScrYVarPtr = params.objDesc->yPos; -		*_vm->_goblin->_curGobFrameVarPtr = 0; -		*_vm->_goblin->_curGobStateVarPtr = params.objDesc->state; -		*_vm->_goblin->_curGobMultStateVarPtr = params.objDesc->multState; +		_vm->_goblin->_curGobScrXVarPtr = (uint32) params.objDesc->xPos; +		_vm->_goblin->_curGobScrYVarPtr = (uint32) params.objDesc->yPos; +		_vm->_goblin->_curGobFrameVarPtr = 0; +		_vm->_goblin->_curGobStateVarPtr = (uint32) params.objDesc->state; +		_vm->_goblin->_curGobMultStateVarPtr = (uint32) params.objDesc->multState;  	}  } @@ -2686,13 +2695,13 @@ void Inter_v1::o1_setGoblinStateRedraw(OpGobParams ¶ms) {  	params.objDesc->toRedraw = 1;  	params.objDesc->type = 0;  	if (params.objDesc == _vm->_goblin->_actDestItemDesc) { -		*_vm->_goblin->_destItemScrXVarPtr = params.objDesc->xPos; -		*_vm->_goblin->_destItemScrYVarPtr = params.objDesc->yPos; +		_vm->_goblin->_destItemScrXVarPtr = (uint32) params.objDesc->xPos; +		_vm->_goblin->_destItemScrYVarPtr = (uint32) params.objDesc->yPos; -		*_vm->_goblin->_destItemStateVarPtr = params.objDesc->state; -		*_vm->_goblin->_destItemNextStateVarPtr = -1; -		*_vm->_goblin->_destItemMultStateVarPtr = -1; -		*_vm->_goblin->_destItemFrameVarPtr = 0; +		_vm->_goblin->_destItemStateVarPtr = (uint32) params.objDesc->state; +		_vm->_goblin->_destItemNextStateVarPtr = (uint32) -1; +		_vm->_goblin->_destItemMultStateVarPtr = (uint32) -1; +		_vm->_goblin->_destItemFrameVarPtr = 0;  	}  } @@ -2712,12 +2721,12 @@ void Inter_v1::o1_decRelaxTime(OpGobParams ¶ms) {  void Inter_v1::o1_getGoblinPosX(OpGobParams ¶ms) {  	int16 item = load16(); -	*params.retVarPtr = _vm->_goblin->_gobPositions[item].x; +	params.retVarPtr = (uint32) _vm->_goblin->_gobPositions[item].x;  }  void Inter_v1::o1_getGoblinPosY(OpGobParams ¶ms) {  	int16 item = load16(); -	*params.retVarPtr = _vm->_goblin->_gobPositions[item].y; +	params.retVarPtr = (uint32) _vm->_goblin->_gobPositions[item].y;  }  void Inter_v1::o1_clearPathExistence(OpGobParams ¶ms) { @@ -2741,9 +2750,9 @@ void Inter_v1::o1_getObjectIntersect(OpGobParams ¶ms) {  	params.objDesc = _vm->_goblin->_objects[params.extraData];  	if (_vm->_goblin->objIntersected(params.objDesc,  				_vm->_goblin->_goblins[item])) -		*params.retVarPtr = 1; +		params.retVarPtr = 1;  	else -		*params.retVarPtr = 0; +		params.retVarPtr = 0;  }  void Inter_v1::o1_getGoblinIntersect(OpGobParams ¶ms) { @@ -2753,9 +2762,9 @@ void Inter_v1::o1_getGoblinIntersect(OpGobParams ¶ms) {  	params.objDesc = _vm->_goblin->_goblins[params.extraData];  	if (_vm->_goblin->objIntersected(params.objDesc,  				_vm->_goblin->_goblins[item])) -		*params.retVarPtr = 1; +		params.retVarPtr = 1;  	else -		*params.retVarPtr = 0; +		params.retVarPtr = 0;  }  void Inter_v1::o1_setItemPos(OpGobParams ¶ms) { @@ -2886,7 +2895,7 @@ void Inter_v1::o1_initGoblin(OpGobParams ¶ms) {  		_vm->_map->_destY = _vm->_goblin->_gobPositions[0].y;  		_vm->_goblin->_gobDestY = _vm->_goblin->_gobPositions[0].y; -		*_vm->_goblin->_curGobVarPtr = 0; +		_vm->_goblin->_curGobVarPtr = 0;  		_vm->_goblin->_pathExistence = 0;  		_vm->_goblin->_readyToAct = 0;  	} diff --git a/engines/gob/inter_v2.cpp b/engines/gob/inter_v2.cpp index d8c33fcce6..2f1d2ec0be 100644 --- a/engines/gob/inter_v2.cpp +++ b/engines/gob/inter_v2.cpp @@ -880,9 +880,15 @@ void Inter_v2::o2_initMult() {  		_vm->_mult->clearObjectVideos(); +		for (int i = 0; i < _vm->_mult->_objCount; i++) { +			delete _vm->_mult->_objects[i].pPosX; +			delete _vm->_mult->_objects[i].pPosY; +		} +  		delete[] _vm->_mult->_objects;  		delete[] _vm->_mult->_renderObjs;  		delete[] _vm->_mult->_orderArray; +  		_vm->_mult->_objects = 0;  		_vm->_mult->_renderObjs = 0;  		_vm->_mult->_orderArray = 0; @@ -907,8 +913,8 @@ void Inter_v2::o2_initMult() {  			uint32 offPosY = i * 4 + (posYVar / 4) * 4;  			uint32 offAnim = animDataVar + i * 4 * _vm->_global->_inter_animDataSize; -			_vm->_mult->_objects[i].pPosX = (int32 *) _variables->getAddressOff32(offPosX); -			_vm->_mult->_objects[i].pPosY = (int32 *) _variables->getAddressOff32(offPosY); +			_vm->_mult->_objects[i].pPosX = new VariableReference(*_vm->_inter->_variables, offPosX); +			_vm->_mult->_objects[i].pPosY = new VariableReference(*_vm->_inter->_variables, offPosY);  			_vm->_mult->_objects[i].pAnimData =  				(Mult::Mult_AnimData *) _variables->getAddressOff8(offAnim, @@ -1046,7 +1052,7 @@ void Inter_v2::o2_loadMultObject() {  	} else if ((objAnim.animType != 100) && (objAnim.animType != 101)) { -		if ((*(obj.pPosX) == -1234) && (*(obj.pPosY) == -4321)) { +		if ((((int32) *(obj.pPosX)) == -1234) && (((int32) *(obj.pPosY)) == -4321)) {  			if (obj.videoSlot > 0)  				_vm->_vidPlayer->slotClose(obj.videoSlot - 1); diff --git a/engines/gob/mult.cpp b/engines/gob/mult.cpp index 3d6a7942f9..b9373d48b3 100644 --- a/engines/gob/mult.cpp +++ b/engines/gob/mult.cpp @@ -93,12 +93,18 @@ Mult::Mult(GobEngine *vm) : _vm(vm) {  }  Mult::~Mult() { +	if (_objects) +		for (int i = 0; i < _objCount; i++) { +			delete _objects[i].pPosX; +			delete _objects[i].pPosY; +		} +  	delete[] _objects;  	delete[] _orderArray;  	delete[] _renderData;  	delete[] _renderObjs; -	delete[] _animArrayX; -	delete[] _animArrayY; +	delete _animArrayX; +	delete _animArrayY;  	delete[] _animArrayData;  	delete _multData;  } @@ -123,6 +129,12 @@ void Mult::freeAll(void) {  void Mult::freeMult() {  	clearObjectVideos(); +	if (_objects) +		for (int i = 0; i < _objCount; i++) { +			delete _objects[i].pPosX; +			delete _objects[i].pPosY; +		} +  	delete[] _objects;  	delete[] _renderData;  	delete[] _renderObjs; @@ -203,11 +215,17 @@ void Mult::playMult(int16 startFrame, int16 endFrame, char checkEscape,  		if (_animDataAllocated) {  			clearObjectVideos(); +			if (_objects) +				for (int i = 0; i < _objCount; i++) { +					delete _objects[i].pPosX; +					delete _objects[i].pPosY; +				} +  			delete[] _objects;  			delete[] _renderData;  			delete[] _renderObjs; -			delete[] _animArrayX; -			delete[] _animArrayY; +			delete _animArrayX; +			delete _animArrayY;  			delete[] _animArrayData;  			delete[] _orderArray; diff --git a/engines/gob/mult.h b/engines/gob/mult.h index aaf2e2826c..3bb3af17b3 100644 --- a/engines/gob/mult.h +++ b/engines/gob/mult.h @@ -27,6 +27,7 @@  #define GOB_MULT_H  #include "gob/video.h" +#include "gob/variables.h"  namespace Gob { @@ -77,8 +78,8 @@ public:  	} PACKED_STRUCT;  	struct Mult_Object { -		int32 *pPosX; -		int32 *pPosY; +		VariableReference *pPosX; +		VariableReference *pPosY;  		Mult_AnimData *pAnimData;  		int16 tick;  		int16 lastLeft; @@ -267,8 +268,8 @@ protected:  	bool _doPalSubst; -	int32 *_animArrayX; -	int32 *_animArrayY; +	Variables *_animArrayX; +	Variables *_animArrayY;  	Mult_AnimData *_animArrayData;  	int16 _palKeyIndex; diff --git a/engines/gob/mult_v1.cpp b/engines/gob/mult_v1.cpp index 22683437e7..a369e7d297 100644 --- a/engines/gob/mult_v1.cpp +++ b/engines/gob/mult_v1.cpp @@ -216,10 +216,16 @@ void Mult_v1::freeMultKeys() {  	if (_animDataAllocated) {  		clearObjectVideos(); +		if (_objects) +			for (int i = 0; i < _objCount; i++) { +				delete _objects[i].pPosX; +				delete _objects[i].pPosY; +			} +  		delete[] _objects;  		delete[] _renderData; -		delete[] _animArrayX; -		delete[] _animArrayY; +		delete _animArrayX; +		delete _animArrayY;  		delete[] _animArrayData;  		_objects = 0; @@ -263,6 +269,14 @@ void Mult_v1::playMultInit() {  	_oldPalette = _vm->_global->_pPaletteDesc->vgaPal;  	if (!_animSurf) { +		if (_objects) +			for (int i = 0; i < _objCount; i++) { +				delete _objects[i].pPosX; +				delete _objects[i].pPosY; +			} + +		delete[] _objects; +  		_vm->_util->setFrameRate(_multData->frameRate);  		_animTop = 0;  		_animLeft = 0; @@ -270,30 +284,27 @@ void Mult_v1::playMultInit() {  		_animHeight = 200;  		_objCount = 4; -		delete[] _objects;  		delete[] _renderData; -		delete[] _animArrayX; -		delete[] _animArrayY; +		delete _animArrayX; +		delete _animArrayY;  		delete[] _animArrayData;  		_objects = new Mult_Object[_objCount];  		_renderData = new int16[9 * _objCount]; -		_animArrayX = new int32[_objCount]; -		_animArrayY = new int32[_objCount]; +		_animArrayX = new VariablesLE(_objCount * 4); +		_animArrayY = new VariablesLE(_objCount * 4);  		_animArrayData = new Mult_AnimData[_objCount];  		memset(_objects, 0, _objCount * sizeof(Mult_Object));  		memset(_renderData, 0, _objCount * 9 * sizeof(int16)); -		memset(_animArrayX, 0, _objCount * sizeof(int32)); -		memset(_animArrayY, 0, _objCount * sizeof(int32));  		memset(_animArrayData, 0, _objCount * sizeof(Mult_AnimData));  		for (_counter = 0; _counter < _objCount; _counter++) {  			Mult_Object &multObj = _objects[_counter];  			Mult_AnimData &animData = _animArrayData[_counter]; -			multObj.pPosX = (int32 *) &_animArrayX[_counter]; -			multObj.pPosY = (int32 *) &_animArrayY[_counter]; +			multObj.pPosX = new VariableReference(*_animArrayX, _counter * 4); +			multObj.pPosY = new VariableReference(*_animArrayY, _counter * 4);  			multObj.pAnimData = &animData;  			animData.isStatic = 1; diff --git a/engines/gob/mult_v2.cpp b/engines/gob/mult_v2.cpp index 3a83ac1867..20a81174e5 100644 --- a/engines/gob/mult_v2.cpp +++ b/engines/gob/mult_v2.cpp @@ -329,8 +329,8 @@ void Mult_v2::freeMultKeys() {  	if (_animDataAllocated) {  		freeMult(); -		delete[] _animArrayX; -		delete[] _animArrayY; +		delete _animArrayX; +		delete _animArrayY;  		delete[] _animArrayData;  		_animArrayX = 0; @@ -510,6 +510,13 @@ void Mult_v2::playMultInit() {  	if (!_animSurf) {  		int16 width, height; +		for (int i = 0; i < _objCount; i++) { +			delete _objects[i].pPosX; +			delete _objects[i].pPosY; +		} + +		delete[] _objects; +	  		_vm->_util->setFrameRate(_multData->frameRate);  		_animTop = 0;  		_animLeft = 0; @@ -517,33 +524,30 @@ void Mult_v2::playMultInit() {  		_animHeight = _vm->_video->_surfHeight;  		_objCount = 4; -		delete[] _objects;  		delete[] _orderArray;  		delete[] _renderObjs; -		delete[] _animArrayX; -		delete[] _animArrayY; +		delete _animArrayX; +		delete _animArrayY;  		delete[] _animArrayData;  		_objects = new Mult_Object[_objCount];  		_orderArray = new int8[_objCount];  		_renderObjs = new Mult_Object*[_objCount]; -		_animArrayX = new int32[_objCount]; -		_animArrayY = new int32[_objCount]; +		_animArrayX = new VariablesLE(_objCount * 4); +		_animArrayY = new VariablesLE(_objCount * 4);  		_animArrayData = new Mult_AnimData[_objCount];  		memset(_objects, 0, _objCount * sizeof(Mult_Object));  		memset(_orderArray, 0, _objCount * sizeof(int8));  		memset(_renderObjs, 0, _objCount * sizeof(Mult_Object *)); -		memset(_animArrayX, 0, _objCount * sizeof(int32)); -		memset(_animArrayY, 0, _objCount * sizeof(int32));  		memset(_animArrayData, 0, _objCount * sizeof(Mult_AnimData));  		for (_counter = 0; _counter < _objCount; _counter++) {  			Mult_Object &multObj = _objects[_counter];  			Mult_AnimData &animData = _animArrayData[_counter]; -			multObj.pPosX = (int32 *) &_animArrayX[_counter]; -			multObj.pPosY = (int32 *) &_animArrayY[_counter]; +			multObj.pPosX = new VariableReference(*_animArrayX, _counter * 4); +			multObj.pPosY = new VariableReference(*_animArrayY, _counter * 4);  			multObj.pAnimData = &animData;  			animData.isStatic = 1; diff --git a/engines/gob/saveload.cpp b/engines/gob/saveload.cpp index 2788716858..fa9f8ea7a9 100644 --- a/engines/gob/saveload.cpp +++ b/engines/gob/saveload.cpp @@ -153,7 +153,7 @@ bool TempSprite::fromBuffer(const byte *buffer, int32 size, bool palette) {  } -PlainSave::PlainSave() { +PlainSave::PlainSave(Endianness endianness) : _endianness(endianness) {  }  PlainSave::~PlainSave() { @@ -230,7 +230,8 @@ bool PlainSave::save(int16 dataVar, int32 size, int32 offset, const char *name,  	}  	bool retVal; -	retVal = SaveLoad::saveDataEndian(*out, dataVar, size, variables, variableSizes); +	retVal = SaveLoad::saveDataEndian(*out, dataVar, size, +			variables, variableSizes, _endianness);  	out->finalize();  	if (out->ioFailed()) { @@ -258,13 +259,14 @@ bool PlainSave::load(int16 dataVar, int32 size, int32 offset, const char *name,  		return false;  	} -	bool retVal = SaveLoad::loadDataEndian(*in, dataVar, size, variables, variableSizes); +	bool retVal = SaveLoad::loadDataEndian(*in, dataVar, size, +			variables, variableSizes, _endianness);  	delete in;  	return retVal;  } -StagedSave::StagedSave() { +StagedSave::StagedSave(Endianness endianness) : _endianness(endianness) {  	_mode = kModeNone;  	_name = 0;  	_loaded = false; @@ -487,7 +489,7 @@ bool StagedSave::write() const {  		} else  			result = SaveLoad::saveDataEndian(*out, 0, _stages[i].size, -					_stages[i].bufVar, _stages[i].bufVarSizes); +					_stages[i].bufVar, _stages[i].bufVarSizes, _endianness);  	}  	if (result) { @@ -533,7 +535,7 @@ bool StagedSave::read() {  			_stages[i].bufVarSizes = new byte[_stages[i].size];  			result = SaveLoad::loadDataEndian(*in, 0, _stages[i].size, -					_stages[i].bufVar, _stages[i].bufVarSizes); +					_stages[i].bufVar, _stages[i].bufVarSizes, _endianness);  		}  	} @@ -734,12 +736,14 @@ void SaveLoad::buildIndex(byte *buffer, char *name, int n, int32 size, int32 off  	}  } -bool SaveLoad::fromEndian(byte *buf, const byte *sizes, uint32 count) { +bool SaveLoad::fromEndian(byte *buf, const byte *sizes, uint32 count, Endianness endianness) { +	bool LE = (endianness == kEndiannessLE); +  	while (count-- > 0) {  		if (*sizes == 3) -			*((uint32 *) buf) = READ_LE_UINT32(buf); +			*((uint32 *) buf) = LE ? READ_LE_UINT32(buf) : READ_BE_UINT32(buf);  		else if (*sizes == 1) -			*((uint16 *) buf) = READ_LE_UINT16(buf); +			*((uint16 *) buf) = LE ? READ_LE_UINT16(buf) : READ_BE_UINT16(buf);  		else if (*sizes != 0) {  			warning("SaveLoad::fromEndian(): Corrupted variables sizes");  			return false; @@ -753,12 +757,19 @@ bool SaveLoad::fromEndian(byte *buf, const byte *sizes, uint32 count) {  	return true;  } -bool SaveLoad::toEndian(byte *buf, const byte *sizes, uint32 count) { +bool SaveLoad::toEndian(byte *buf, const byte *sizes, uint32 count, Endianness endianness) {  	while (count-- > 0) { -		if (*sizes == 3) -			WRITE_LE_UINT32(buf, *((uint32 *) buf)); -		else if (*sizes == 1) -			WRITE_LE_UINT16(buf, *((uint16 *) buf)); +		if (*sizes == 3) { +			if (endianness == kEndiannessLE) +				WRITE_LE_UINT32(buf, *((uint32 *) buf)); +			else +				WRITE_BE_UINT32(buf, *((uint32 *) buf)); +		} else if (*sizes == 1) { +			if (endianness == kEndiannessLE) +				WRITE_LE_UINT16(buf, *((uint16 *) buf)); +			else +				WRITE_BE_UINT16(buf, *((uint16 *) buf)); +		}  		else if (*sizes != 0) {  			warning("SaveLoad::toEndian(): Corrupted variables sizes");  			return false; @@ -811,7 +822,8 @@ uint32 SaveLoad::write(Common::WriteStream &out,  }  bool SaveLoad::loadDataEndian(Common::ReadStream &in, -		int16 dataVar, uint32 size, byte *variables, byte *variableSizes) { +		int16 dataVar, uint32 size, +		byte *variables, byte *variableSizes, Endianness endianness) {  	bool retVal = false; @@ -821,7 +833,7 @@ bool SaveLoad::loadDataEndian(Common::ReadStream &in,  	assert(varBuf && sizeBuf);  	if (read(in, varBuf, sizeBuf, size) == size) { -		if (fromEndian(varBuf, sizeBuf, size)) { +		if (fromEndian(varBuf, sizeBuf, size, endianness)) {  			memcpy(variables + dataVar, varBuf, size);  			memcpy(variableSizes + dataVar, sizeBuf, size);  			retVal = true; @@ -835,7 +847,8 @@ bool SaveLoad::loadDataEndian(Common::ReadStream &in,  }  bool SaveLoad::saveDataEndian(Common::WriteStream &out, -		int16 dataVar, uint32 size, const byte *variables, const byte *variableSizes) { +		int16 dataVar, uint32 size, +		const byte *variables, const byte *variableSizes, Endianness endianness) {  	bool retVal = false; @@ -847,7 +860,7 @@ bool SaveLoad::saveDataEndian(Common::WriteStream &out,  	memcpy(varBuf, variables + dataVar, size);  	memcpy(sizeBuf, variableSizes + dataVar, size); -	if (toEndian(varBuf, sizeBuf, size)) +	if (toEndian(varBuf, sizeBuf, size, endianness))  		if (write(out, varBuf, sizeBuf, size) == size)  			retVal = true; diff --git a/engines/gob/saveload.h b/engines/gob/saveload.h index 29f7ee2594..52c3a9b260 100644 --- a/engines/gob/saveload.h +++ b/engines/gob/saveload.h @@ -65,7 +65,7 @@ private:  class PlainSave {  public: -	PlainSave(); +	PlainSave(Endianness endianness);  	~PlainSave();  	bool save(int16 dataVar, int32 size, int32 offset, const char *name, @@ -77,11 +77,14 @@ public:  			const byte *variables, const byte *variableSizes) const;  	bool load(int16 dataVar, int32 size, int32 offset, const char *name,  			byte *variables, byte *variableSizes) const; + +private: +	Endianness _endianness;  };  class StagedSave {  public: -	StagedSave(); +	StagedSave(Endianness endianness);  	~StagedSave();  	void addStage(int32 size, bool endianed = true); @@ -114,6 +117,8 @@ private:  		kModeLoad  	}; +	Endianness _endianness; +  	Common::Array<Stage> _stages;  	enum Mode _mode;  	char *_name; @@ -178,17 +183,19 @@ public:  	static const char *stripPath(const char *fileName); -	static bool fromEndian(byte *buf, const byte *sizes, uint32 count); -	static bool toEndian(byte *buf, const byte *sizes, uint32 count); +	static bool fromEndian(byte *buf, const byte *sizes, uint32 count, Endianness endianness); +	static bool toEndian(byte *buf, const byte *sizes, uint32 count, Endianness endianness);  	static uint32 read(Common::ReadStream &in,  			byte *buf, byte *sizes, uint32 count);  	static uint32 write(Common::WriteStream &out,  			const byte *buf, const byte *sizes, uint32 count);  	static bool loadDataEndian(Common::ReadStream &in, -			int16 dataVar, uint32 size, byte *variables, byte *variableSizes); +			int16 dataVar, uint32 size, +			byte *variables, byte *variableSizes, Endianness endianness);  	static bool saveDataEndian(Common::WriteStream &out, -			int16 dataVar, uint32 size, const byte *variables, const byte *variableSizes); +			int16 dataVar, uint32 size, +			const byte *variables, const byte *variableSizes, Endianness endianness);  protected:  	GobEngine *_vm; @@ -228,8 +235,8 @@ protected:  	int32 _varSize;  	TempSprite _tmpSprite; -	PlainSave _notes; -	StagedSave _save; +	PlainSave *_notes; +	StagedSave *_save;  	byte _indexBuffer[600];  	bool _hasIndex; @@ -306,8 +313,8 @@ protected:  	TempSprite _screenshot;  	TempSprite _tmpSprite; -	PlainSave _notes; -	StagedSave _save; +	PlainSave *_notes; +	StagedSave *_save;  	byte _propBuffer[1000];  	byte _indexBuffer[1200]; @@ -370,7 +377,7 @@ protected:  	int32 _varSize; -	StagedSave _save; +	StagedSave *_save;  	byte _propBuffer[1000];  	byte _indexBuffer[1200]; diff --git a/engines/gob/saveload_v2.cpp b/engines/gob/saveload_v2.cpp index a92fe8cf01..fc11950368 100644 --- a/engines/gob/saveload_v2.cpp +++ b/engines/gob/saveload_v2.cpp @@ -45,6 +45,9 @@ SaveLoad_v2::SaveFile SaveLoad_v2::_saveFiles[] = {  SaveLoad_v2::SaveLoad_v2(GobEngine *vm, const char *targetName) :  		SaveLoad(vm, targetName) { +	_notes = new PlainSave(_vm->getEndianness()); +	_save = new StagedSave(_vm->getEndianness()); +  	_saveFiles[0].destName = new char[strlen(targetName) + 5];  	_saveFiles[1].destName = _saveFiles[0].destName;  	_saveFiles[2].destName = 0; @@ -58,6 +61,9 @@ SaveLoad_v2::SaveLoad_v2(GobEngine *vm, const char *targetName) :  }  SaveLoad_v2::~SaveLoad_v2() { +	delete _notes; +	delete _save; +  	delete[] _saveFiles[0].destName;  	delete[] _saveFiles[3].destName;  } @@ -227,7 +233,7 @@ bool SaveLoad_v2::loadGame(SaveFile &saveFile,  			return false;  		} -		if (!_save.load(dataVar, size, 40, saveFile.destName, _vm->_inter->_variables)) +		if (!_save->load(dataVar, size, 40, saveFile.destName, _vm->_inter->_variables))  			return false;  	} @@ -268,7 +274,7 @@ bool SaveLoad_v2::loadNotes(SaveFile &saveFile,  	debugC(2, kDebugSaveLoad, "Loading the notes"); -	return _notes.load(dataVar, size, offset, saveFile.destName, _vm->_inter->_variables); +	return _notes->load(dataVar, size, offset, saveFile.destName, _vm->_inter->_variables);  }  bool SaveLoad_v2::saveGame(SaveFile &saveFile, @@ -313,10 +319,10 @@ bool SaveLoad_v2::saveGame(SaveFile &saveFile,  		byte sizes[40];  		memset(sizes, 0, 40); -		if(!_save.save(0, 40, 0, saveFile.destName, _indexBuffer + (slot * 40), sizes)) +		if(!_save->save(0, 40, 0, saveFile.destName, _indexBuffer + (slot * 40), sizes))  			return false; -		if (!_save.save(dataVar, size, 40, saveFile.destName, _vm->_inter->_variables)) +		if (!_save->save(dataVar, size, 40, saveFile.destName, _vm->_inter->_variables))  			return false;  	} @@ -350,7 +356,7 @@ bool SaveLoad_v2::saveNotes(SaveFile &saveFile,  	debugC(2, kDebugSaveLoad, "Saving the notes"); -	return _notes.save(dataVar, size, offset, saveFile.destName, _vm->_inter->_variables); +	return _notes->save(dataVar, size, offset, saveFile.destName, _vm->_inter->_variables);  	return false;  } @@ -360,8 +366,8 @@ void SaveLoad_v2::assertInited() {  	_varSize = READ_LE_UINT32(_vm->_game->_totFileData + 0x2C) * 4; -	_save.addStage(40); -	_save.addStage(_varSize); +	_save->addStage(40); +	_save->addStage(_varSize);  }  } // End of namespace Gob diff --git a/engines/gob/saveload_v3.cpp b/engines/gob/saveload_v3.cpp index 67879db3d1..dab5fd9385 100644 --- a/engines/gob/saveload_v3.cpp +++ b/engines/gob/saveload_v3.cpp @@ -48,6 +48,9 @@ SaveLoad_v3::SaveLoad_v3(GobEngine *vm, const char *targetName,  		uint32 screenshotSize, int32 indexOffset, int32 screenshotOffset) :  	SaveLoad(vm, targetName) { +	_notes = new PlainSave(_vm->getEndianness()); +	_save = new StagedSave(_vm->getEndianness()); +  	_screenshotSize = screenshotSize;  	_indexOffset = indexOffset;  	_screenshotOffset = screenshotOffset; @@ -71,6 +74,9 @@ SaveLoad_v3::SaveLoad_v3(GobEngine *vm, const char *targetName,  }  SaveLoad_v3::~SaveLoad_v3() { +	delete _notes; +	delete _save; +  	delete[] _saveFiles[0].destName;  	delete[] _saveFiles[3].destName;  } @@ -243,7 +249,7 @@ int32 SaveLoad_v3::getSizeNotes(SaveFile &saveFile) {  int32 SaveLoad_v3::getSizeScreenshot(SaveFile &saveFile) {  	if (!_useScreenshots) {  		_useScreenshots = true; -		_save.addStage(_screenshotSize, false); +		_save->addStage(_screenshotSize, false);  	}  	Common::SaveFileManager *saveMan = g_system->getSavefileManager(); @@ -312,7 +318,7 @@ bool SaveLoad_v3::loadGame(SaveFile &saveFile,  			return false;  		} -		if (!_save.load(dataVar, size, 540, saveFile.destName, _vm->_inter->_variables)) +		if (!_save->load(dataVar, size, 540, saveFile.destName, _vm->_inter->_variables))  			return false;  	} @@ -353,7 +359,7 @@ bool SaveLoad_v3::loadNotes(SaveFile &saveFile,  	debugC(2, kDebugSaveLoad, "Loading the notes"); -	return _notes.load(dataVar, size, offset, saveFile.destName, _vm->_inter->_variables); +	return _notes->load(dataVar, size, offset, saveFile.destName, _vm->_inter->_variables);  }  bool SaveLoad_v3::loadScreenshot(SaveFile &saveFile, @@ -363,7 +369,7 @@ bool SaveLoad_v3::loadScreenshot(SaveFile &saveFile,  	if (!_useScreenshots) {  		_useScreenshots = true; -		_save.addStage(_screenshotSize, false); +		_save->addStage(_screenshotSize, false);  	}  	if (offset == _indexOffset) { @@ -395,7 +401,7 @@ bool SaveLoad_v3::loadScreenshot(SaveFile &saveFile,  		byte *buffer = new byte[_screenshotSize]; -		if (!_save.load(0, _screenshotSize, _varSize + 540, saveFile.destName, buffer, 0)) { +		if (!_save->load(0, _screenshotSize, _varSize + 540, saveFile.destName, buffer, 0)) {  			delete[] buffer;  			return false;  		} @@ -483,13 +489,13 @@ bool SaveLoad_v3::saveGame(SaveFile &saveFile,  		_hasIndex = false; -		if(!_save.save(0, 500, 0, saveFile.destName, _propBuffer, _propBuffer + 500)) +		if(!_save->save(0, 500, 0, saveFile.destName, _propBuffer, _propBuffer + 500))  			return false; -		if(!_save.save(0, 40, 500, saveFile.destName, _indexBuffer + (saveFile.slot * 40), 0)) +		if(!_save->save(0, 40, 500, saveFile.destName, _indexBuffer + (saveFile.slot * 40), 0))  			return false; -		if (!_save.save(dataVar, size, 540, saveFile.destName, _vm->_inter->_variables)) +		if (!_save->save(dataVar, size, 540, saveFile.destName, _vm->_inter->_variables))  			return false;  	} @@ -523,7 +529,7 @@ bool SaveLoad_v3::saveNotes(SaveFile &saveFile,  	debugC(2, kDebugSaveLoad, "Saving the notes"); -	return _notes.save(dataVar, size - 160, offset, saveFile.destName, _vm->_inter->_variables); +	return _notes->save(dataVar, size - 160, offset, saveFile.destName, _vm->_inter->_variables);  	return false;  } @@ -534,7 +540,7 @@ bool SaveLoad_v3::saveScreenshot(SaveFile &saveFile,  	if (!_useScreenshots) {  		_useScreenshots = true; -		_save.addStage(_screenshotSize, false); +		_save->addStage(_screenshotSize, false);  	}  	if (offset >= _screenshotOffset) { @@ -571,7 +577,7 @@ bool SaveLoad_v3::saveScreenshot(SaveFile &saveFile,  			return false;  		} -		if (!_save.save(0, _screenshotSize, _varSize + 540, saveFile.destName, buffer, 0)) { +		if (!_save->save(0, _screenshotSize, _varSize + 540, saveFile.destName, buffer, 0)) {  			delete[] buffer;  			return false;  		} @@ -588,9 +594,9 @@ void SaveLoad_v3::assertInited() {  	_varSize = READ_LE_UINT32(_vm->_game->_totFileData + 0x2C) * 4; -	_save.addStage(500); -	_save.addStage(40, false); -	_save.addStage(_varSize); +	_save->addStage(500); +	_save->addStage(40, false); +	_save->addStage(_varSize);  }  void SaveLoad_v3::buildScreenshotIndex(byte *buffer, char *name, int n) { diff --git a/engines/gob/saveload_v4.cpp b/engines/gob/saveload_v4.cpp index a6548dd82d..0bd3dc03e6 100644 --- a/engines/gob/saveload_v4.cpp +++ b/engines/gob/saveload_v4.cpp @@ -50,6 +50,8 @@ SaveLoad_v4::SaveFile SaveLoad_v4::_saveFiles[] = {  SaveLoad_v4::SaveLoad_v4(GobEngine *vm, const char *targetName) :  	SaveLoad(vm, targetName) { +	_save = new StagedSave(_vm->getEndianness()); +  	_firstSizeGame = true;  	_saveFiles[0].destName = 0; @@ -76,6 +78,8 @@ SaveLoad_v4::SaveLoad_v4(GobEngine *vm, const char *targetName) :  }  SaveLoad_v4::~SaveLoad_v4() { +	delete _save; +  	delete[] _screenProps;  	delete[] _saveFiles[1].destName;  } @@ -297,7 +301,7 @@ bool SaveLoad_v4::loadGame(SaveFile &saveFile,  			return false;  		} -		if (!_save.load(dataVar, size, 540, saveFile.destName, _vm->_inter->_variables)) +		if (!_save->load(dataVar, size, 540, saveFile.destName, _vm->_inter->_variables))  			return false;  	} @@ -314,7 +318,7 @@ bool SaveLoad_v4::loadGameScreenProps(SaveFile &saveFile,  	setCurrentSlot(saveFile.destName, saveFile.sourceName[4] - '0'); -	if (!_save.load(0, 256000, _varSize + 540, saveFile.destName, +	if (!_save->load(0, 256000, _varSize + 540, saveFile.destName,  	                _screenProps, _screenProps + 256000))  		return false; @@ -393,13 +397,13 @@ bool SaveLoad_v4::saveGame(SaveFile &saveFile,  		_hasIndex = false; -		if(!_save.save(0, 500, 0, saveFile.destName, _propBuffer, _propBuffer + 500)) +		if(!_save->save(0, 500, 0, saveFile.destName, _propBuffer, _propBuffer + 500))  			return false; -		if(!_save.save(0, 40, 500, saveFile.destName, _indexBuffer + (slot * 40), 0)) +		if(!_save->save(0, 40, 500, saveFile.destName, _indexBuffer + (slot * 40), 0))  			return false; -		if (!_save.save(dataVar, size, 540, saveFile.destName, _vm->_inter->_variables)) +		if (!_save->save(dataVar, size, 540, saveFile.destName, _vm->_inter->_variables))  			return false;  	} @@ -417,7 +421,7 @@ bool SaveLoad_v4::saveGameScreenProps(SaveFile &saveFile,  	setCurrentSlot(saveFile.destName, saveFile.sourceName[4] - '0'); -	if (!_save.save(0, 256000, _varSize + 540, saveFile.destName, +	if (!_save->save(0, 256000, _varSize + 540, saveFile.destName,  	                _screenProps, _screenProps + 256000))  		return false; @@ -430,10 +434,10 @@ void SaveLoad_v4::assertInited() {  	_varSize = READ_LE_UINT32(_vm->_game->_totFileData + 0x2C) * 4; -	_save.addStage(500); -	_save.addStage(40, false); -	_save.addStage(_varSize); -	_save.addStage(256000); +	_save->addStage(500); +	_save->addStage(40, false); +	_save->addStage(_varSize); +	_save->addStage(256000);  }  } // End of namespace Gob diff --git a/engines/gob/scenery.cpp b/engines/gob/scenery.cpp index 6b52cdbbd6..33e540ace4 100644 --- a/engines/gob/scenery.cpp +++ b/engines/gob/scenery.cpp @@ -595,7 +595,7 @@ void Scenery::updateAnim(int16 layer, int16 frame, int16 animation, int16 flags,  	int16 destX;  	int16 destY; -	if (animation < 0) { +	if ((_vm->getGameType() == kGameTypeWoodruff) && (animation < 0)) {  		// Object video  		if (flags & 1) { // Do capture @@ -736,6 +736,8 @@ void Scenery::updateAnim(int16 layer, int16 frame, int16 animation, int16 flags,  		return;  	} +	if ((animation < 0) || (animation >= 10)) +		return;  	if ((_animPictCount[animation] == 0) || (layer < 0))  		return;  	if (layer >= _animations[animation].layersCount) diff --git a/engines/gob/sound/sound.h b/engines/gob/sound/sound.h index b59510e4bb..07b5a737db 100644 --- a/engines/gob/sound/sound.h +++ b/engines/gob/sound/sound.h @@ -144,4 +144,4 @@ private:  } // End of namespace Gob -#endif // GOB_SOUND_H +#endif // GOB_SOUND_SOUND_H diff --git a/engines/gob/sound/soundmixer.h b/engines/gob/sound/soundmixer.h index 5789885a99..3e8e6b5c1b 100644 --- a/engines/gob/sound/soundmixer.h +++ b/engines/gob/sound/soundmixer.h @@ -37,7 +37,7 @@ namespace Gob {  class SoundMixer : public Audio::AudioStream {  public: -	SoundMixer(Audio::Mixer &mixer, Audio::Mixer::SoundType type = Audio::Mixer::kPlainSoundType); +	SoundMixer(Audio::Mixer &mixer, Audio::Mixer::SoundType type);  	~SoundMixer();  	virtual void play(SoundDesc &sndDesc, int16 repCount, diff --git a/engines/gob/variables.cpp b/engines/gob/variables.cpp index 0eea2f6547..805aaeb839 100644 --- a/engines/gob/variables.cpp +++ b/engines/gob/variables.cpp @@ -308,4 +308,62 @@ uint32 VariablesBE::read32(const byte *buf) const {  	return READ_BE_UINT32(buf);  } +VariableReference::VariableReference() { +	_vars = 0; +	_offset = 0; +} + +VariableReference::VariableReference(Variables &vars, uint32 offset, Variables::Type type) { +	set(vars, offset, type); +} + +VariableReference::~VariableReference() { +} + +void VariableReference::set(Variables &vars, uint32 offset, Variables::Type type) { +	_vars = &vars; +	_offset = offset; +	_type = type; +} + +VariableReference &VariableReference::operator=(uint32 value) { +	if (_vars) { +		switch (_type) { +			case Variables::kVariableType8: +				_vars->writeOff8(_offset, (uint8) value); +				break; +			case Variables::kVariableType16: +				_vars->writeOff16(_offset, (uint16) value); +				break; +			case Variables::kVariableType32: +				_vars->writeOff32(_offset, value); +				break; +		} +	} +	return *this; +} + +VariableReference::operator uint32() { +	if (_vars) { +		switch (_type) { +			case Variables::kVariableType8: +				return (uint32) _vars->readOff8(_offset); +			case Variables::kVariableType16: +				return (uint32) _vars->readOff16(_offset); +			case Variables::kVariableType32: +				return _vars->readOff32(_offset); +		} +	} + +	return 0; +} + +VariableReference &VariableReference::operator+=(uint32 value) { +	return (*this = (*this + value)); +} + +VariableReference &VariableReference::operator*=(uint32 value) { +	return (*this = (*this * value)); +} +  } // End of namespace Gob diff --git a/engines/gob/variables.h b/engines/gob/variables.h index 5989ed38ee..32f160a6bd 100644 --- a/engines/gob/variables.h +++ b/engines/gob/variables.h @@ -30,6 +30,12 @@ namespace Gob {  class Variables {  public: +	enum Type { +		kVariableType8, +		kVariableType16, +		kVariableType32 +	}; +  	Variables(uint32 size);  	virtual ~Variables(); @@ -142,6 +148,26 @@ protected:  	uint32 read32(const byte *buf) const;  }; +class VariableReference { +	public: +		VariableReference(); +		VariableReference(Variables &vars, uint32 offset, +				Variables::Type type = Variables::kVariableType32); +		~VariableReference(); + +		void set(Variables &vars, uint32 offset, Variables::Type type = Variables::kVariableType32); + +		VariableReference &operator=(uint32 value); +		VariableReference &operator+=(uint32 value); +		VariableReference &operator*=(uint32 value); +		operator uint32(); + +	private: +		Variables *_vars; +		uint32 _offset; +		Variables::Type _type; +}; +  } // End of namespace Gob  #endif // GOB_VARIABLES_H diff --git a/engines/gob/videoplayer.cpp b/engines/gob/videoplayer.cpp index 909d39a63b..aa47e6cf84 100644 --- a/engines/gob/videoplayer.cpp +++ b/engines/gob/videoplayer.cpp @@ -588,12 +588,14 @@ bool VideoPlayer::doPlay(int16 frame, int16 breakKey,  }  void VideoPlayer::copyPalette(CoktelVideo &video, int16 palStart, int16 palEnd) { -	if ((palStart != -1) && (palEnd != -1)) -		memcpy(((char *) (_vm->_global->_pPaletteDesc->vgaPal)) + palStart * 3, -				video.getPalette() + palStart * 3, -				(palEnd - palStart + 1) * 3); -	else -		memcpy((char *) _vm->_global->_pPaletteDesc->vgaPal, video.getPalette(), 768); +	if (palStart < 0) +		palStart = 0; +	if (palEnd < 0) +		palEnd = 255; + +	memcpy(((char *) (_vm->_global->_pPaletteDesc->vgaPal)) + palStart * 3, +			video.getPalette() + palStart * 3, +			(palEnd - palStart + 1) * 3);  }  void VideoPlayer::writeVideoInfo(const char *videoFile, int16 varX, int16 varY, diff --git a/engines/igor/igor.cpp b/engines/igor/igor.cpp index 018709f34f..4d4fb97762 100644 --- a/engines/igor/igor.cpp +++ b/engines/igor/igor.cpp @@ -404,7 +404,7 @@ void IgorEngine::playSound(int num, int type) {  	debugC(9, kDebugEngine, "playSound() %d", num);  	--num;  	int soundOffset = -1; -	Audio::Mixer::SoundType soundType = Audio::Mixer::kPlainSoundType; +	Audio::Mixer::SoundType soundType;  	Audio::SoundHandle *soundHandle = 0;  	if (type == 1) {  		if (_mixer->isSoundHandleActive(_sfxHandle)) { diff --git a/engines/kyra/animator_lok.cpp b/engines/kyra/animator_lok.cpp index 1baa78b203..75d5537e4a 100644 --- a/engines/kyra/animator_lok.cpp +++ b/engines/kyra/animator_lok.cpp @@ -665,7 +665,7 @@ void Animator_LoK::animRefreshNPC(int character) {  void Animator_LoK::setCharacterDefaultFrame(int character) {  	debugC(9, kDebugLevelAnimator, "Animator_LoK::setCharacterDefaultFrame()"); -	static uint16 initFrameTable[] = { +	static const uint16 initFrameTable[] = {  		7, 41, 77, 0, 0  	};  	assert(character < ARRAYSIZE(initFrameTable)); @@ -678,7 +678,7 @@ void Animator_LoK::setCharacterDefaultFrame(int character) {  void Animator_LoK::setCharactersHeight() {  	debugC(9, kDebugLevelAnimator, "Animator_LoK::setCharactersHeight()"); -	static int8 initHeightTable[] = { +	static const int8 initHeightTable[] = {  		48, 40, 48, 47, 56,  		44, 42, 47, 38, 35,  		40 diff --git a/engines/kyra/detection.cpp b/engines/kyra/detection.cpp index 344121b503..2d592069d2 100644 --- a/engines/kyra/detection.cpp +++ b/engines/kyra/detection.cpp @@ -26,6 +26,7 @@  #include "kyra/kyra_lok.h"  #include "kyra/kyra_hof.h"  #include "kyra/kyra_mr.h" +#include "kyra/lol.h"  #include "common/config-manager.h"  #include "common/advancedDetector.h" @@ -41,9 +42,11 @@ struct KYRAGameDescription {  namespace { -#define FLAGS(x, y, z, a, b, c, id) { Common::UNK_LANG, Common::kPlatformUnknown, x, y, z, a, b, c, id } +#define FLAGS(x, y, z, a, b, c, id) { Common::UNK_LANG, Common::UNK_LANG, Common::UNK_LANG, Common::kPlatformUnknown, x, y, z, a, b, c, id } +#define FLAGS_FAN(fanLang, repLang, x, y, z, a, b, c, id) { Common::UNK_LANG, fanLang, repLang, Common::kPlatformUnknown, x, y, z, a, b, c, id }  #define KYRA1_FLOPPY_FLAGS FLAGS(false, false, false, false, false, false, Kyra::GI_KYRA1) +#define KYRA1_FLOPPY_CMP_FLAGS FLAGS(false, false, false, false, false, true, Kyra::GI_KYRA1)  #define KYRA1_AMIGA_FLAGS FLAGS(false, false, false, false, false, false, Kyra::GI_KYRA1)  #define KYRA1_TOWNS_FLAGS FLAGS(false, true, false, false, false, false, Kyra::GI_KYRA1)  #define KYRA1_TOWNS_SJIS_FLAGS FLAGS(false, true, false, true, false, false, Kyra::GI_KYRA1) @@ -59,13 +62,38 @@ namespace {  #define KYRA2_TOWNS_SJIS_FLAGS FLAGS(false, false, false, true, false, false, Kyra::GI_KYRA2)  #define KYRA3_CD_FLAGS FLAGS(false, false, true, false, true, true, Kyra::GI_KYRA3) -#define KYRA3_CD_INS_FLAGS FLAGS(false, false, true, false, true, true, Kyra::GI_KYRA3) +#define KYRA3_CD_INS_FLAGS FLAGS(false, false, true, false, true, false, Kyra::GI_KYRA3) +#define KYRA3_CD_FAN_FLAGS(x, y) FLAGS_FAN(x, y, false, false, true, false, true, false, Kyra::GI_KYRA3) + +#define LOL_CD_FLAGS FLAGS(false, false, true, false, false, false, Kyra::GI_LOL)  const KYRAGameDescription adGameDescs[] = {  	{  		{  			"kyra1",  			0, +			AD_ENTRY1("DISK1.EXE", "c8641d0414d6c966d0a3dad79db07bf4"), +			Common::EN_ANY, +			Common::kPlatformPC, +			Common::ADGF_NO_FLAGS +		}, +		KYRA1_FLOPPY_CMP_FLAGS +	}, +	{ +		{ +			"kyra1", +			0, +			AD_ENTRY1("DISK1.EXE", "5d5cee4c3d0b68d586788b74243d254a"), +			Common::DE_DEU, +			Common::kPlatformPC, +			Common::ADGF_NO_FLAGS +		}, +		KYRA1_FLOPPY_CMP_FLAGS +	}, +	{ +		{ +			"kyra1", +			"Extracted",  			AD_ENTRY1("GEMCUT.EMC", "3c244298395520bb62b5edfe41688879"),  			Common::EN_ANY,  			Common::kPlatformPC, @@ -76,7 +104,7 @@ const KYRAGameDescription adGameDescs[] = {  	{  		{  			"kyra1", -			0, +			"Extracted",  			AD_ENTRY1("GEMCUT.EMC", "796e44863dd22fa635b042df1bf16673"),  			Common::EN_ANY,  			Common::kPlatformPC, @@ -87,7 +115,7 @@ const KYRAGameDescription adGameDescs[] = {  	{  		{  			"kyra1", -			0, +			"Extracted",  			AD_ENTRY1("GEMCUT.EMC", "abf8eb360e79a6c2a837751fbd4d3d24"),  			Common::FR_FRA,  			Common::kPlatformPC, @@ -98,7 +126,7 @@ const KYRAGameDescription adGameDescs[] = {  	{  		{  			"kyra1", -			0, +			"Extracted",  			AD_ENTRY1("GEMCUT.EMC", "6018e1dfeaca7fe83f8d0b00eb0dd049"),  			Common::DE_DEU,  			Common::kPlatformPC, @@ -109,7 +137,7 @@ const KYRAGameDescription adGameDescs[] = {  	{ // from Arne.F  		{  			"kyra1", -			0, +			"Extracted",  			AD_ENTRY1("GEMCUT.EMC", "f0b276781f47c130f423ec9679fe9ed9"),  			Common::DE_DEU,  			Common::kPlatformPC, @@ -120,7 +148,7 @@ const KYRAGameDescription adGameDescs[] = {  	{ // from VooD  		{  			"kyra1", -			0, +			"Extracted",  			AD_ENTRY1("GEMCUT.EMC", "8909b41596913b3f5deaf3c9f1017b01"),  			Common::ES_ESP,  			Common::kPlatformPC, @@ -131,7 +159,7 @@ const KYRAGameDescription adGameDescs[] = {  	{ // floppy 1.8 from clemmy  		{  			"kyra1", -			0, +			"Extracted",  			AD_ENTRY1("GEMCUT.EMC", "747861d2a9c643c59fdab570df5b9093"),  			Common::ES_ESP,  			Common::kPlatformPC, @@ -142,7 +170,7 @@ const KYRAGameDescription adGameDescs[] = {  	{ // from gourry  		{  			"kyra1", -			0, +			"Extracted",  			AD_ENTRY1("GEMCUT.EMC", "ef08c8c237ee1473fd52578303fc36df"),  			Common::IT_ITA,  			Common::kPlatformPC, @@ -323,7 +351,7 @@ const KYRAGameDescription adGameDescs[] = {  		KYRA2_FLOPPY_CMP_FLAGS  	}, -	{ // // Floppy version extracted +	{ // Floppy version extracted  		{  			"kyra2",  			"Extracted", @@ -463,6 +491,28 @@ const KYRAGameDescription adGameDescs[] = {  		},  		KYRA2_TOWNS_SJIS_FLAGS  	}, +	{ // PC-9821 +		{ +			"kyra2", +			0, +			AD_ENTRY1("WSCORE.PAK", "c44de1302b67f27d4707409987b7a685"), +			Common::EN_ANY, +			Common::kPlatformPC98, +			Common::ADGF_NO_FLAGS +		}, +		KYRA2_TOWNS_FLAGS +	}, +	{ +		{ +			"kyra2", +			0, +			AD_ENTRY1("WSCORE.PAK", "c44de1302b67f27d4707409987b7a685"), +			Common::JA_JPN, +			Common::kPlatformPC98, +			Common::ADGF_NO_FLAGS +		}, +		KYRA2_TOWNS_SJIS_FLAGS +	},  	// Kyra3 @@ -480,7 +530,7 @@ const KYRAGameDescription adGameDescs[] = {  			Common::kPlatformPC,  			Common::ADGF_DROPLANGUAGE  		}, -		KYRA3_CD_INS_FLAGS +		KYRA3_CD_FLAGS  	},  	{  		{ @@ -495,7 +545,7 @@ const KYRAGameDescription adGameDescs[] = {  			Common::kPlatformPC,  			Common::ADGF_DROPLANGUAGE  		}, -		KYRA3_CD_INS_FLAGS +		KYRA3_CD_FLAGS  	},  	{  		{ @@ -510,7 +560,7 @@ const KYRAGameDescription adGameDescs[] = {  			Common::kPlatformPC,  			Common::ADGF_DROPLANGUAGE  		}, -		KYRA3_CD_INS_FLAGS +		KYRA3_CD_FLAGS  	},  	// installed version @@ -527,7 +577,7 @@ const KYRAGameDescription adGameDescs[] = {  			Common::kPlatformPC,  			Common::ADGF_DROPLANGUAGE  		}, -		KYRA3_CD_FLAGS +		KYRA3_CD_INS_FLAGS  	},  	{  		{ @@ -542,7 +592,7 @@ const KYRAGameDescription adGameDescs[] = {  			Common::kPlatformPC,  			Common::ADGF_DROPLANGUAGE  		}, -		KYRA3_CD_FLAGS +		KYRA3_CD_INS_FLAGS  	},  	{  		{ @@ -557,9 +607,152 @@ const KYRAGameDescription adGameDescs[] = {  			Common::kPlatformPC,  			Common::ADGF_DROPLANGUAGE  		}, -		KYRA3_CD_FLAGS +		KYRA3_CD_INS_FLAGS +	}, + +	// Spanish fan translation, see fr#1994040 "KYRA3: Add support for Spanish fan translation" +	{ +		{ +			"kyra3", +			0, +			{ +				{ "ONETIME.PAK", 0, "9aaca21d2a205ca02ec53132f2911794", -1 }, +				{ "AUD.PAK", 0, 0, -1 }, +				{ 0, 0, 0, 0 } +			}, +			Common::ES_ESP, +			Common::kPlatformPC, +			Common::ADGF_DROPLANGUAGE +		}, +		KYRA3_CD_FAN_FLAGS(Common::ES_ESP, Common::EN_ANY) +	}, +	{ +		{ +			"kyra3", +			0, +			{ +				{ "ONETIME.PAK", 0, "9aaca21d2a205ca02ec53132f2911794", -1 }, +				{ "AUD.PAK", 0, 0, -1 }, +				{ 0, 0, 0, 0 } +			}, +			Common::DE_DEU, +			Common::kPlatformPC, +			Common::ADGF_DROPLANGUAGE +		}, +		KYRA3_CD_FAN_FLAGS(Common::ES_ESP, Common::EN_ANY) +	}, +	{ +		{ +			"kyra3", +			0, +			{ +				{ "ONETIME.PAK", 0, "9aaca21d2a205ca02ec53132f2911794", -1 }, +				{ "AUD.PAK", 0, 0, -1 }, +				{ 0, 0, 0, 0 } +			}, +			Common::FR_FRA, +			Common::kPlatformPC, +			Common::ADGF_DROPLANGUAGE +		}, +		KYRA3_CD_FAN_FLAGS(Common::ES_ESP, Common::EN_ANY)  	}, +	// Itlian fan translation, see fr#2003504 "KYRA: add support for Italian version of Kyrandia 2&3" +	{ +		{ +			"kyra3", +			0, +			{ +				{ "ONETIME.PAK", 0, "ee2d4d056a5de5333a3c6bda055b3cb4", -1 }, +				{ "AUD.PAK", 0, 0, -1 }, +				{ 0, 0, 0, 0 } +			}, +			Common::EN_ANY, +			Common::kPlatformPC, +			Common::ADGF_DROPLANGUAGE +		}, +		KYRA3_CD_FAN_FLAGS(Common::IT_ITA, Common::FR_FRA) +	}, +	{ +		{ +			"kyra3", +			0, +			{ +				{ "ONETIME.PAK", 0, "ee2d4d056a5de5333a3c6bda055b3cb4", -1 }, +				{ "AUD.PAK", 0, 0, -1 }, +				{ 0, 0, 0, 0 } +			}, +			Common::DE_DEU, +			Common::kPlatformPC, +			Common::ADGF_DROPLANGUAGE +		}, +		KYRA3_CD_FAN_FLAGS(Common::IT_ITA, Common::FR_FRA) +	}, +	{ +		{ +			"kyra3", +			0, +			{ +				{ "ONETIME.PAK", 0, "ee2d4d056a5de5333a3c6bda055b3cb4", -1 }, +				{ "AUD.PAK", 0, 0, -1 }, +				{ 0, 0, 0, 0 } +			}, +			Common::IT_ITA, +			Common::kPlatformPC, +			Common::ADGF_DROPLANGUAGE +		}, +		KYRA3_CD_FAN_FLAGS(Common::IT_ITA, Common::FR_FRA) +	}, +	 +	// Lands of Lore CD +	{ +		{ +			"lol", +			"CD", +			{ +				{ "GENERAL.PAK", 0, "05a4f588fb81dc9c0ef1f2ec20d89e24", -1 }, +				{ "L01.PAK", 0, "759a0ac26808d77ea968bd392355ba1d", -1 }, +				{ 0, 0, 0, 0 } +			}, +			Common::EN_ANY, +			Common::kPlatformPC, +			Common::ADGF_DROPLANGUAGE | Common::ADGF_CD +		}, +		LOL_CD_FLAGS +	}, +	 +	{ +		{ +			"lol", +			"CD", +			{ +				{ "GENERAL.PAK", 0, "05a4f588fb81dc9c0ef1f2ec20d89e24", -1 }, +				{ "L01.PAK", 0, "759a0ac26808d77ea968bd392355ba1d", -1 }, +				{ 0, 0, 0, 0 } +			}, +			Common::DE_DEU, +			Common::kPlatformPC, +			Common::ADGF_DROPLANGUAGE | Common::ADGF_CD +		}, +		LOL_CD_FLAGS +	}, +	 +	{ +		{ +			"lol", +			"CD", +			{ +				{ "GENERAL.PAK", 0, "05a4f588fb81dc9c0ef1f2ec20d89e24", -1 }, +				{ "L01.PAK", 0, "759a0ac26808d77ea968bd392355ba1d", -1 }, +				{ 0, 0, 0, 0 } +			}, +			Common::FR_FRA, +			Common::kPlatformPC, +			Common::ADGF_DROPLANGUAGE | Common::ADGF_CD +		}, +		LOL_CD_FLAGS +	}, +	  	{ AD_TABLE_END_MARKER, FLAGS(0, 0, 0, 0, 0, 0, 0) }  }; @@ -567,6 +760,7 @@ const PlainGameDescriptor gameList[] = {  	{ "kyra1", "The Legend of Kyrandia" },  	{ "kyra2", "The Legend of Kyrandia: The Hand of Fate" },  	{ "kyra3", "The Legend of Kyrandia: Malcolm's Revenge" }, +	{ "lol", "Lands of Lore: The Throne of Chaos" },  	{ 0, 0 }  }; @@ -639,6 +833,9 @@ bool KyraMetaEngine::createInstance(OSystem *syst, Engine **engine, const Common  	case Kyra::GI_KYRA3:  		*engine = new Kyra::KyraEngine_MR(syst, flags);  		break; +	case Kyra::GI_LOL: +		*engine = new Kyra::LoLEngine(syst, flags); +		break;  	default:  		res = false;  		warning("Kyra engine: unknown gameID"); diff --git a/engines/kyra/gui_hof.cpp b/engines/kyra/gui_hof.cpp index 555934cb7f..7d56743af5 100644 --- a/engines/kyra/gui_hof.cpp +++ b/engines/kyra/gui_hof.cpp @@ -454,14 +454,26 @@ void KyraEngine_HoF::loadBookBkgd() {  void KyraEngine_HoF::showBookPage() {  	char filename[16]; -	sprintf(filename, "PAGE%.01X.", _bookCurPage); -	strcat(filename, (_flags.isTalkie || _flags.platform == Common::kPlatformFMTowns || _lang) ? _languageExtension[_lang] : "TXT"); +	sprintf(filename, "PAGE%.01X.%s", _bookCurPage, _languageExtension[_lang]);  	uint8 *leftPage = _res->fileData(filename, 0); +	if (!leftPage) { +		// some floppy version use a TXT extension +		sprintf(filename, "PAGE%.01X.TXT", _bookCurPage); +		leftPage = _res->fileData(filename, 0); +	} +  	int leftPageY = _bookPageYOffset[_bookCurPage]; -	sprintf(filename, "PAGE%.01X.", _bookCurPage+1); -	strcat(filename, (_flags.isTalkie || _flags.platform == Common::kPlatformFMTowns || _lang) ? _languageExtension[_lang] : "TXT"); -	uint8 *rightPage = (_bookCurPage != _bookMaxPage) ? _res->fileData(filename, 0) : 0; +	sprintf(filename, "PAGE%.01X.%s", _bookCurPage+1, _languageExtension[_lang]); +	uint8 *rightPage = 0; +	if (_bookCurPage != _bookMaxPage) { +		rightPage = _res->fileData(filename, 0); +		if (!rightPage) { +			sprintf(filename, "PAGE%.01X.TXT", _bookCurPage); +			rightPage = _res->fileData(filename, 0); +		} +	} +	  	int rightPageY = _bookPageYOffset[_bookCurPage+1];  	_screen->hideMouse(); diff --git a/engines/kyra/gui_lok.cpp b/engines/kyra/gui_lok.cpp index 6fa30c9e9a..35b343fc25 100644 --- a/engines/kyra/gui_lok.cpp +++ b/engines/kyra/gui_lok.cpp @@ -188,6 +188,7 @@ int KyraEngine_LoK::buttonAmuletCallback(Button *caller) {  #pragma mark -  GUI_LoK::GUI_LoK(KyraEngine_LoK *vm, Screen_LoK *screen) : GUI(vm), _vm(vm), _screen(screen) { +	_lastScreenUpdate = 0;  	_menu = 0;  	initStaticResource();  	_scrollUpFunctor = BUTTON_FUNCTOR(GUI_LoK, this, &GUI_LoK::scrollUp); @@ -479,7 +480,6 @@ int GUI_LoK::buttonMenuCallback(Button *caller) {  void GUI_LoK::getInput() {  	Common::Event event; -	static uint32 lastScreenUpdate = 0;  	uint32 now = _vm->_system->getMillis();  	_mouseWheel = 0; @@ -496,7 +496,7 @@ void GUI_LoK::getInput() {  			break;  		case Common::EVENT_MOUSEMOVE:  			_vm->_system->updateScreen(); -			lastScreenUpdate = now; +			_lastScreenUpdate = now;  			break;  		case Common::EVENT_WHEELUP:  			_mouseWheel = -1; @@ -512,9 +512,9 @@ void GUI_LoK::getInput() {  		}  	} -	if (now - lastScreenUpdate > 50) { +	if (now - _lastScreenUpdate > 50) {  		_vm->_system->updateScreen(); -		lastScreenUpdate = now; +		_lastScreenUpdate = now;  	}  	_vm->_system->delayMillis(3); @@ -1056,7 +1056,7 @@ void GUI_LoK::fadePalette() {  	if (_vm->gameFlags().platform == Common::kPlatformAmiga)  		return; -	static int16 menuPalIndexes[] = {248, 249, 250, 251, 252, 253, 254, -1}; +	static const int16 menuPalIndexes[] = {248, 249, 250, 251, 252, 253, 254, -1};  	int index = 0;  	memcpy(_screen->getPalette(2), _screen->_currentPalette, 768); diff --git a/engines/kyra/gui_lok.h b/engines/kyra/gui_lok.h index 607ef0b605..49081c7ae2 100644 --- a/engines/kyra/gui_lok.h +++ b/engines/kyra/gui_lok.h @@ -157,6 +157,8 @@ private:  	KyraEngine_LoK *_vm;  	Screen_LoK *_screen; +	uint32 _lastScreenUpdate; +  	bool _menuRestoreScreen;  	uint8 _toplevelMenu;  	int _savegameOffset; diff --git a/engines/kyra/items_lok.cpp b/engines/kyra/items_lok.cpp index 8eb62c20c2..2c2903d0b3 100644 --- a/engines/kyra/items_lok.cpp +++ b/engines/kyra/items_lok.cpp @@ -39,7 +39,7 @@  namespace Kyra {  int KyraEngine_LoK::findDuplicateItemShape(int shape) { -	static uint8 dupTable[] = { +	static const uint8 dupTable[] = {  		0x48, 0x46, 0x49, 0x47, 0x4a, 0x46, 0x4b, 0x47,  		0x4c, 0x46, 0x4d, 0x47, 0x5b, 0x5a, 0x5c, 0x5a,  		0x5d, 0x5a, 0x5e, 0x5a, 0xFF, 0xFF diff --git a/engines/kyra/kyra_hof.cpp b/engines/kyra/kyra_hof.cpp index 57f0dcc24a..d3de621707 100644 --- a/engines/kyra/kyra_hof.cpp +++ b/engines/kyra/kyra_hof.cpp @@ -230,7 +230,7 @@ int KyraEngine_HoF::init() {  	_gui = new GUI_HoF(this);  	assert(_gui);  	_gui->initStaticData(); -	_tim = new TIMInterpreter(this, _system); +	_tim = new TIMInterpreter(this, _screen, _system);  	assert(_tim);  	if (_flags.isDemo && !_flags.isTalkie) { @@ -296,6 +296,9 @@ int KyraEngine_HoF::go() {  			_res->loadFileList("FILEDATA.FDT");  		else  			_res->loadFileList(_ingamePakList, _ingamePakListSize); + +		if (_flags.platform == Common::kPlatformPC98) +			_res->loadPakFile("AUDIO.PAK");  	}  	_menuDirectlyToLoad = (_menuChoice == 3) ? true : false; @@ -1561,7 +1564,7 @@ void KyraEngine_HoF::snd_playSoundEffect(int track, int volume) {  	int16 vocIndex = (int16)READ_LE_UINT16(&_ingameSoundIndex[track * 2]);  	if (vocIndex != -1)  		_sound->voicePlay(_ingameSoundList[vocIndex], true); -	else if (_flags.platform == Common::kPlatformPC) +	else if (_flags.platform != Common::kPlatformFMTowns)  		// TODO ?? Maybe there is a way to let users select whether they want  		// voc, midi or adl sfx (even though it makes no sense to choose anything but voc).  		KyraEngine_v1::snd_playSoundEffect(track); @@ -2021,6 +2024,9 @@ void KyraEngine_HoF::writeSettings() {  		break;  	} +	if (_flags.lang == _flags.replacedLang && _flags.fanLang != Common::UNK_LANG) +		_flags.lang = _flags.fanLang; +  	ConfMan.set("language", Common::getLanguageCode(_flags.lang));  	KyraEngine_v1::writeSettings(); diff --git a/engines/kyra/kyra_mr.cpp b/engines/kyra/kyra_mr.cpp index 8a49b8e155..a4e5b58364 100644 --- a/engines/kyra/kyra_mr.cpp +++ b/engines/kyra/kyra_mr.cpp @@ -343,6 +343,14 @@ void KyraEngine_MR::initMainMenu() {  		0x80, 0xFF  	}; +	if (_flags.lang == Common::ES_ESP) { +		for (int i = 0; i < 4; ++i) +			data.strings[i] = _mainMenuSpanishFan[i]; +	} else if (_flags.lang == Common::IT_ITA) { +		for (int i = 0; i < 4; ++i) +			data.strings[i] = _mainMenuItalianFan[i]; +	} +  	MainMenu::Animation anim;  	anim.anim = _menuAnim;  	anim.startFrame = 29; @@ -1543,6 +1551,9 @@ void KyraEngine_MR::writeSettings() {  		break;  	} +	if (_flags.lang == _flags.replacedLang && _flags.fanLang != Common::UNK_LANG) +		_flags.lang = _flags.fanLang; +  	ConfMan.set("language", Common::getLanguageCode(_flags.lang));  	ConfMan.setBool("studio_audience", _configStudio); diff --git a/engines/kyra/kyra_mr.h b/engines/kyra/kyra_mr.h index 5af138373c..5f9f6f91a3 100644 --- a/engines/kyra/kyra_mr.h +++ b/engines/kyra/kyra_mr.h @@ -184,9 +184,12 @@ private:  private:  	// main menu -	const char *const *_mainMenuStrings; +	const char * const *_mainMenuStrings;  	int _mainMenuStringsSize; +	static const char * const _mainMenuSpanishFan[]; +	static const char * const _mainMenuItalianFan[]; +  	// animator  	uint8 *_gamePlayBuffer;  	void restorePage3(); diff --git a/engines/kyra/kyra_v1.cpp b/engines/kyra/kyra_v1.cpp index 1cc1d728bf..85c03dc1bb 100644 --- a/engines/kyra/kyra_v1.cpp +++ b/engines/kyra/kyra_v1.cpp @@ -107,14 +107,16 @@ int KyraEngine_v1::init() {  		// "KYRA1: Crash on exceeded polyphony" for more information).  		int midiDriver = MidiDriver::detectMusicDriver(MDT_MIDI | MDT_ADLIB/* | MDT_PREFER_MIDI*/); -		if (_flags.platform == Common::kPlatformFMTowns || _flags.platform == Common::kPlatformPC98) { -			// TODO: currently we don't support the PC98 sound data, -			// but since it has the FM-Towns data files, we just use the -			// FM-Towns driver +		if (_flags.platform == Common::kPlatformFMTowns) {  			if (_flags.gameID == GI_KYRA1)  				_sound = new SoundTowns(this, _mixer);  			else -				_sound = new SoundTowns_v2(this, _mixer); +				_sound = new SoundTownsPC98_v2(this, _mixer); +		} else if (_flags.platform == Common::kPlatformPC98) { +			if (_flags.gameID == GI_KYRA1) +				_sound = new SoundTowns/*SoundPC98*/(this, _mixer); +			else +				_sound = new SoundTownsPC98_v2(this, _mixer);  		} else if (midiDriver == MD_ADLIB) {  			_sound = new SoundAdlibPC(this, _mixer);  			assert(_sound); @@ -171,36 +173,6 @@ int KyraEngine_v1::init() {  			_gameToLoad = -1;  	} -	_lang = 0; -	Common::Language lang = Common::parseLanguage(ConfMan.get("language")); - -	if (_flags.gameID == GI_KYRA2 || _flags.gameID == GI_KYRA3) { -		switch (lang) { -		case Common::EN_ANY: -		case Common::EN_USA: -		case Common::EN_GRB: -			_lang = 0; -			break; - -		case Common::FR_FRA: -			_lang = 1; -			break; - -		case Common::DE_DEU: -			_lang = 2; -			break; - -		case Common::JA_JPN: -			_lang = 3; -			break; - -		default: -			warning("unsupported language, switching back to English"); -			_lang = 0; -			break; -		} -	} -  	return 0;  } @@ -275,6 +247,14 @@ void KyraEngine_v1::delayWithTicks(int ticks) {  void KyraEngine_v1::registerDefaultSettings() {  	if (_flags.gameID != GI_KYRA3)  		ConfMan.registerDefault("cdaudio", (_flags.platform == Common::kPlatformFMTowns || _flags.platform == Common::kPlatformPC98)); +	if (_flags.fanLang != Common::UNK_LANG) { +		// HACK/WORKAROUND: Since we can't use registerDefault here to overwrite +		// the global subtitles settings, we're using this hack to enable subtitles +		// for fan translations +		const Common::ConfigManager::Domain *cur = ConfMan.getActiveDomain(); +		if (!cur || (cur && cur->get("subtitles").empty())) +			ConfMan.setBool("subtitles", true); +	}  }  void KyraEngine_v1::readSettings() { diff --git a/engines/kyra/kyra_v1.h b/engines/kyra/kyra_v1.h index 4f38ceca98..50cabc421e 100644 --- a/engines/kyra/kyra_v1.h +++ b/engines/kyra/kyra_v1.h @@ -44,6 +44,11 @@ namespace Kyra {  struct GameFlags {  	Common::Language lang; + +	// language overwrites of fan translations (only needed for multilingual games) +	Common::Language fanLang; +	Common::Language replacedLang; +  	Common::Platform platform;  	bool isDemo					: 1; @@ -59,7 +64,8 @@ struct GameFlags {  enum {  	GI_KYRA1 = 0,  	GI_KYRA2 = 1, -	GI_KYRA3 = 2 +	GI_KYRA3 = 2, +	GI_LOL = 4  };  struct AudioDataStruct { @@ -212,7 +218,6 @@ protected:  	// detection  	GameFlags _flags; -	int _lang;  	// opcode  	virtual void setupOpcodeTable() = 0; diff --git a/engines/kyra/kyra_v2.cpp b/engines/kyra/kyra_v2.cpp index 12da338843..2e704f2aa2 100644 --- a/engines/kyra/kyra_v2.cpp +++ b/engines/kyra/kyra_v2.cpp @@ -23,6 +23,8 @@   *   */ +#include "common/config-manager.h" +  #include "kyra/kyra_v2.h"  #include "kyra/screen_v2.h"  #include "kyra/debugger.h" @@ -70,6 +72,36 @@ KyraEngine_v2::KyraEngine_v2(OSystem *system, const GameFlags &flags, const Engi  	memset(&_mainCharacter.inventory, -1, sizeof(_mainCharacter.inventory));  	_pauseStart = 0; + +	_lang = 0; +	Common::Language lang = Common::parseLanguage(ConfMan.get("language")); +	if (lang == _flags.fanLang && _flags.replacedLang != Common::UNK_LANG) +		lang = _flags.replacedLang; + +	switch (lang) { +	case Common::EN_ANY: +	case Common::EN_USA: +	case Common::EN_GRB: +		_lang = 0; +		break; + +	case Common::FR_FRA: +		_lang = 1; +		break; + +	case Common::DE_DEU: +		_lang = 2; +		break; + +	case Common::JA_JPN: +		_lang = 3; +		break; + +	default: +		warning("unsupported language, switching back to English"); +		_lang = 0; +		break; +	}  }  KyraEngine_v2::~KyraEngine_v2() { diff --git a/engines/kyra/kyra_v2.h b/engines/kyra/kyra_v2.h index 24f7aad614..6fdf30fff8 100644 --- a/engines/kyra/kyra_v2.h +++ b/engines/kyra/kyra_v2.h @@ -94,6 +94,9 @@ protected:  	virtual void update() = 0;  	virtual void updateWithText() = 0; +	// detection +	int _lang; +  	// MainMenu  	MainMenu *_menu; diff --git a/engines/kyra/lol.cpp b/engines/kyra/lol.cpp new file mode 100644 index 0000000000..ef1121baa0 --- /dev/null +++ b/engines/kyra/lol.cpp @@ -0,0 +1,806 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "kyra/lol.h" +#include "kyra/screen_lol.h" +#include "kyra/resource.h" +#include "kyra/sound.h" + +#include "common/endian.h" + +namespace Kyra { + +LoLEngine::LoLEngine(OSystem *system, const GameFlags &flags) : KyraEngine_v1(system, flags) { +	_screen = 0; +	 +	switch (_flags.lang) { +	case Common::EN_ANY: +	case Common::EN_USA: +	case Common::EN_GRB: +		_lang = 0; +		break; + +	case Common::FR_FRA: +		_lang = 1; +		break; + +	case Common::DE_DEU: +		_lang = 2; +		break; + +	default: +		warning("unsupported language, switching back to English"); +		_lang = 0; +		break; +	} +	 +	_chargenWSA = 0; +} + +LoLEngine::~LoLEngine() { +	setupPrologueData(false); + +	delete _screen; +	delete _tim; +	 +	for (Common::Array<const TIMOpcode*>::iterator i = _timIntroOpcodes.begin(); i != _timIntroOpcodes.end(); ++i) +		delete *i; +	_timIntroOpcodes.clear(); +} + +Screen *LoLEngine::screen() { +	return _screen; +} + +int LoLEngine::init() { +	_screen = new Screen_LoL(this, _system); +	assert(_screen); +	_screen->setResolution(); + +	KyraEngine_v1::init(); +	 +	_tim = new TIMInterpreter(this, _screen, _system); +	assert(_tim); +	 +	_screen->setAnimBlockPtr(10000); +	_screen->setScreenDim(0); + +	return 0; +} + +int LoLEngine::go() { +	setupPrologueData(true); +	showIntro(); +	_sound->playTrack(6); +	/*int character = */chooseCharacter(); +	_sound->playTrack(1); +	_screen->fadeToBlack(); +	setupPrologueData(false); + +	return 0; +} + +#pragma mark - Input + +int LoLEngine::checkInput(Button *buttonList, bool mainLoop) { +	debugC(9, kDebugLevelMain, "LoLEngine::checkInput(%p, %d)", (const void*)buttonList, mainLoop); +	updateInput(); + +	int keys = 0; +	int8 mouseWheel = 0; + +	while (_eventList.size()) { +		Common::Event event = *_eventList.begin(); +		bool breakLoop = false; + +		switch (event.type) { +		case Common::EVENT_KEYDOWN: +			/*if (event.kbd.keycode >= '1' && event.kbd.keycode <= '9' && +					(event.kbd.flags == Common::KBD_CTRL || event.kbd.flags == Common::KBD_ALT) && mainLoop) { +				const char *saveLoadSlot = getSavegameFilename(9 - (event.kbd.keycode - '0') + 990); + +				if (event.kbd.flags == Common::KBD_CTRL) { +					loadGame(saveLoadSlot); +					_eventList.clear(); +					breakLoop = true; +				} else { +					char savegameName[14]; +					sprintf(savegameName, "Quicksave %d", event.kbd.keycode - '0'); +					saveGame(saveLoadSlot, savegameName); +				} +			} else if (event.kbd.flags == Common::KBD_CTRL) { +				if (event.kbd.keycode == 'd') +					_debugger->attach(); +			}*/ +			break; + +		case Common::EVENT_MOUSEMOVE: { +			Common::Point pos = getMousePos(); +			_mouseX = pos.x; +			_mouseY = pos.y; +			} break; + +		case Common::EVENT_LBUTTONDOWN: +		case Common::EVENT_LBUTTONUP: { +			Common::Point pos = getMousePos(); +			_mouseX = pos.x; +			_mouseY = pos.y; +			keys = (event.type == Common::EVENT_LBUTTONDOWN ? 199 : (200 | 0x800)); +			breakLoop = true; +			} break; + +		case Common::EVENT_WHEELUP: +			mouseWheel = -1; +			break; + +		case Common::EVENT_WHEELDOWN: +			mouseWheel = 1; +			break; + +		default: +			break; +		} + +		//if (_debugger->isAttached()) +		//	_debugger->onFrame(); + +		if (breakLoop) +			break; + +		_eventList.erase(_eventList.begin()); +	} + +	return /*gui_v2()->processButtonList(buttonList, keys | 0x8000, mouseWheel)*/keys; +} + +void LoLEngine::updateInput() { +	Common::Event event; + +	while (_eventMan->pollEvent(event)) { +		switch (event.type) { +		case Common::EVENT_QUIT: +			_quitFlag = true; +			break; + +		case Common::EVENT_KEYDOWN: +			if (event.kbd.keycode == '.' || event.kbd.keycode == Common::KEYCODE_ESCAPE) +				_eventList.push_back(Event(event, true)); +			else if (event.kbd.keycode == 'q' && event.kbd.flags == Common::KBD_CTRL) +				_quitFlag = true; +			else +				_eventList.push_back(event); +			break; + +		case Common::EVENT_LBUTTONDOWN: +			_eventList.push_back(Event(event, true)); +			break; + +		case Common::EVENT_MOUSEMOVE: +			_screen->updateScreen(); +			// fall through + +		case Common::EVENT_LBUTTONUP: +		case Common::EVENT_WHEELUP: +		case Common::EVENT_WHEELDOWN: +			_eventList.push_back(event); +			break; + +		default: +			break; +		} +	} +} + +void LoLEngine::removeInputTop() { +	if (!_eventList.empty()) +		_eventList.erase(_eventList.begin()); +} + +bool LoLEngine::skipFlag() const { +	for (Common::List<Event>::const_iterator i = _eventList.begin(); i != _eventList.end(); ++i) { +		if (i->causedSkip) +			return true; +	} +	return false; +} + +void LoLEngine::resetSkipFlag(bool removeEvent) { +	for (Common::List<Event>::iterator i = _eventList.begin(); i != _eventList.end(); ++i) { +		if (i->causedSkip) { +			if (removeEvent) +				_eventList.erase(i); +			else +				i->causedSkip = false; +			return; +		} +	} +} + +#pragma mark - Intro + +void LoLEngine::setupPrologueData(bool load) { +	static const char * const fileList[] = { +		"xxx/general.pak", +		"xxx/introvoc.pak", +		"xxx/startup.pak", +		"xxx/intro1.pak", +		"xxx/intro2.pak", +		"xxx/intro3.pak", +		"xxx/intro4.pak", +		"xxx/intro5.pak", +		"xxx/intro6.pak", +		"xxx/intro7.pak", +		"xxx/intro8.pak", +		"xxx/intro9.pak" +	}; + +	char filename[32]; +	for (uint i = 0; i < ARRAYSIZE(fileList); ++i) { +		strcpy(filename, fileList[i]); +		memcpy(filename, _languageExt[_lang], 3); + +		if (load) { +			if (!_res->loadPakFile(filename)) +				error("Couldn't load file: '%s'", filename); +		} else { +			_res->unloadPakFile(filename); +		} +	} +	 +	if (load) { +		_chargenWSA = new WSAMovie_v2(this, _screen); +		assert(_chargenWSA); + +		_charSelection = -1; +		_charSelectionInfoResult = -1; + +		_selectionAnimFrames[0] = _selectionAnimFrames[2] = 0; +		_selectionAnimFrames[1] = _selectionAnimFrames[3] = 1; + +		memset(_selectionAnimTimers, 0, sizeof(_selectionAnimTimers)); +		memset(_screen->getPalette(1), 0, 768); +	} else { +		delete _chargenWSA; _chargenWSA = 0; +	} +} + +void LoLEngine::showIntro() { +	debugC(9, kDebugLevelMain, "LoLEngine::showIntro()"); + +	TIM *intro = _tim->load("LOLINTRO.TIM", &_timIntroOpcodes); + +	_screen->loadFont(Screen::FID_8_FNT, "NEW8P.FNT"); +	_screen->loadFont(Screen::FID_INTRO_FNT, "INTRO.FNT"); +	_screen->setFont(Screen::FID_8_FNT); + +	_tim->resetFinishedFlag(); +	_tim->setLangData("LOLINTRO.DIP"); + +	_screen->hideMouse(); + +	uint32 palNextFadeStep = 0; +	while (!_tim->finished() && !_quitFlag && !skipFlag()) { +		updateInput(); +		_tim->exec(intro, false); +		_screen->checkedPageUpdate(8, 4); + +		if (_tim->_palDiff) { +			if (palNextFadeStep < _system->getMillis()) { +				_tim->_palDelayAcc += _tim->_palDelayInc; +				palNextFadeStep = _system->getMillis() + ((_tim->_palDelayAcc >> 8) * _tickLength); +				_tim->_palDelayAcc &= 0xFF; + +				if (!_screen->fadePalStep(_screen->getPalette(0), _tim->_palDiff)) { +					_screen->setScreenPalette(_screen->getPalette(0)); +					_tim->_palDiff = 0; +				} +			} +		} + +		_system->delayMillis(10); +		_screen->updateScreen(); +	} +	_screen->showMouse(); +	_sound->voiceStop(); +	 +	// HACK: Remove all input events +	_eventList.clear(); +	 +	_tim->unload(intro); +	_tim->clearLangData(); +	 +	_screen->fadePalette(_screen->getPalette(1), 30, 0); +} + +int LoLEngine::chooseCharacter() { +	debugC(9, kDebugLevelMain, "LoLEngine::chooseCharacter()"); + +	_tim->setLangData("LOLINTRO.DIP"); +	 +	_screen->loadFont(Screen::FID_9_FNT, "FONT9P.FNT"); + +	_screen->loadBitmap("ITEMICN.SHP", 3, 3, 0); +	_screen->setMouseCursor(0, 0, _screen->getPtrToShape(_screen->getCPagePtr(3), 0)); + +	while (!_screen->isMouseVisible()) +		_screen->showMouse(); + +	_screen->loadBitmap("CHAR.CPS", 2, 2, _screen->getPalette(0)); +	_screen->loadBitmap("BACKGRND.CPS", 4, 4, _screen->getPalette(0)); +	 +	if (!_chargenWSA->open("CHARGEN.WSA", 1, 0)) +		error("Couldn't load CHARGEN.WSA"); +	_chargenWSA->setX(113); +	_chargenWSA->setY(0); +	_chargenWSA->setDrawPage(2); +	_chargenWSA->displayFrame(0, 0, 0, 0); + +	_screen->setFont(Screen::FID_9_FNT); +	_screen->_curPage = 2; +	 +	for (int i = 0; i < 4; ++i) +		_screen->fprintStringIntro(_charPreviews[i].name, _charPreviews[i].x + 16, _charPreviews[i].y + 36, 0xC0, 0x00, 0x9C, 0x120); + +	for (int i = 0; i < 4; ++i) { +		_screen->fprintStringIntro("%d", _charPreviews[i].x + 21, _charPreviews[i].y + 48, 0x98, 0x00, 0x9C, 0x220, _charPreviews[i].attrib[0]); +		_screen->fprintStringIntro("%d", _charPreviews[i].x + 21, _charPreviews[i].y + 56, 0x98, 0x00, 0x9C, 0x220, _charPreviews[i].attrib[1]); +		_screen->fprintStringIntro("%d", _charPreviews[i].x + 21, _charPreviews[i].y + 64, 0x98, 0x00, 0x9C, 0x220, _charPreviews[i].attrib[2]); +	} +	 +	_screen->fprintStringIntro(_tim->getCTableEntry(51), 36, 173, 0x98, 0x00, 0x9C, 0x20); +	_screen->fprintStringIntro(_tim->getCTableEntry(53), 36, 181, 0x98, 0x00, 0x9C, 0x20); +	_screen->fprintStringIntro(_tim->getCTableEntry(55), 36, 189, 0x98, 0x00, 0x9C, 0x20); +	 +	_screen->copyRegion(0, 0, 0, 0, 320, 200, 2, 0, Screen::CR_NO_P_CHECK); +	_screen->_curPage = 0; +	 +	_screen->fadePalette(_screen->getPalette(0), 30, 0); +	 +	bool kingIntro = true; +	while (!_quitFlag) { +		if (kingIntro) +			kingSelectionIntro(); + +		if (_charSelection < 0) +			processCharacterSelection(); + +		if (_quitFlag) +			break; + +		if (_charSelection == 100) { +			kingIntro = true; +			_charSelection = -1; +			continue; +		} + +		_screen->copyRegion(0, 0, 0, 0, 112, 120, 4, 0, Screen::CR_NO_P_CHECK); +		_screen->updateScreen(); +		_screen->showMouse(); + +		if (selectionCharInfo(_charSelection) == -1) { +			_charSelection = -1; +			kingIntro = false; +		} else { +			break; +		} +	} + +	if (_quitFlag) +		return -1; + +	uint32 waitTime = _system->getMillis() + 420 * _tickLength; +	while (waitTime > _system->getMillis() && !skipFlag() && !_quitFlag) { +		updateInput(); +		_system->delayMillis(10); +	} + +	// HACK: Remove all input events +	_eventList.clear(); + +	_tim->clearLangData(); + +	return _charSelection; +} + +void LoLEngine::kingSelectionIntro() { +	debugC(9, kDebugLevelMain, "LoLEngine::kingSelectionIntro()"); +	 +	_screen->copyRegion(0, 0, 0, 0, 112, 120, 4, 0, Screen::CR_NO_P_CHECK); +	int y = 38; +	 +	_screen->fprintStringIntro(_tim->getCTableEntry(57), 8, y, 0x32, 0x00, 0x9C, 0x20); +	_screen->fprintStringIntro(_tim->getCTableEntry(58), 8, y + 10, 0x32, 0x00, 0x9C, 0x20); +	_screen->fprintStringIntro(_tim->getCTableEntry(59), 8, y + 20, 0x32, 0x00, 0x9C, 0x20); +	_screen->fprintStringIntro(_tim->getCTableEntry(60), 8, y + 30, 0x32, 0x00, 0x9C, 0x20); +	_screen->fprintStringIntro(_tim->getCTableEntry(61), 8, y + 40, 0x32, 0x00, 0x9C, 0x20); + +	_sound->voicePlay("KING01"); +	 +	_chargenWSA->setX(113); +	_chargenWSA->setY(0); +	_chargenWSA->setDrawPage(0); +	 +	int index = 4; +	while (_sound->voiceIsPlaying("KING01") && _charSelection == -1 && !_quitFlag && !skipFlag()) { +		index = MAX(index, 4); + +		_chargenWSA->displayFrame(_chargenFrameTable[index], 0, 0, 0); +		_screen->copyRegion(_selectionPosTable[_selectionChar1IdxTable[index]*2+0], _selectionPosTable[_selectionChar1IdxTable[index]*2+1], _charPreviews[0].x, _charPreviews[0].y, 32, 32, 4, 0); +		_screen->copyRegion(_selectionPosTable[_selectionChar2IdxTable[index]*2+0], _selectionPosTable[_selectionChar2IdxTable[index]*2+1], _charPreviews[1].x, _charPreviews[1].y, 32, 32, 4, 0); +		_screen->copyRegion(_selectionPosTable[_selectionChar3IdxTable[index]*2+0], _selectionPosTable[_selectionChar3IdxTable[index]*2+1], _charPreviews[2].x, _charPreviews[2].y, 32, 32, 4, 0); +		_screen->copyRegion(_selectionPosTable[_selectionChar4IdxTable[index]*2+0], _selectionPosTable[_selectionChar4IdxTable[index]*2+1], _charPreviews[3].x, _charPreviews[3].y, 32, 32, 4, 0); +		_screen->updateScreen(); + +		uint32 waitEnd = _system->getMillis() + 7 * _tickLength; +		while (waitEnd > _system->getMillis() && _charSelection == -1 && !_quitFlag && !skipFlag()) { +			_charSelection = getCharSelection(); +			_system->delayMillis(10); +		} + +		index = (index + 1) % 22; +	} +	 +	resetSkipFlag(); +	 +	_chargenWSA->displayFrame(0x10, 0, 0, 0); +	_screen->updateScreen(); +	_sound->voiceStop("KING01"); +} + +void LoLEngine::kingSelectionReminder() { +	debugC(9, kDebugLevelMain, "LoLEngine::kingSelectionReminder()"); +	 +	_screen->copyRegion(0, 0, 0, 0, 112, 120, 4, 0, Screen::CR_NO_P_CHECK); +	int y = 48; +	 +	_screen->fprintStringIntro(_tim->getCTableEntry(62), 8, y, 0x32, 0x00, 0x9C, 0x20); +	_screen->fprintStringIntro(_tim->getCTableEntry(63), 8, y + 10, 0x32, 0x00, 0x9C, 0x20); +	 +	_sound->voicePlay("KING02"); +	 +	_chargenWSA->setX(113); +	_chargenWSA->setY(0); +	_chargenWSA->setDrawPage(0); +	 +	int index = 0; +	while (_sound->voiceIsPlaying("KING02") && _charSelection == -1 && !_quitFlag && index < 15) { +		_chargenWSA->displayFrame(_chargenFrameTable[index+9], 0, 0, 0); +		_screen->copyRegion(_selectionPosTable[_reminderChar1IdxTable[index]*2+0], _selectionPosTable[_reminderChar1IdxTable[index]*2+1], _charPreviews[0].x, _charPreviews[0].y, 32, 32, 4, 0); +		_screen->copyRegion(_selectionPosTable[_reminderChar2IdxTable[index]*2+0], _selectionPosTable[_reminderChar2IdxTable[index]*2+1], _charPreviews[1].x, _charPreviews[1].y, 32, 32, 4, 0); +		_screen->copyRegion(_selectionPosTable[_reminderChar3IdxTable[index]*2+0], _selectionPosTable[_reminderChar3IdxTable[index]*2+1], _charPreviews[2].x, _charPreviews[2].y, 32, 32, 4, 0); +		_screen->copyRegion(_selectionPosTable[_reminderChar4IdxTable[index]*2+0], _selectionPosTable[_reminderChar4IdxTable[index]*2+1], _charPreviews[3].x, _charPreviews[3].y, 32, 32, 4, 0); +		_screen->updateScreen(); + +		uint32 waitEnd = _system->getMillis() + 8 * _tickLength; +		while (waitEnd > _system->getMillis() && !_quitFlag) { +			_charSelection = getCharSelection(); +			_system->delayMillis(10); +		} + +		index = (index + 1) % 22; +	} + +	_sound->voiceStop("KING02"); +} + +void LoLEngine::kingSelectionOutro() { +	debugC(9, kDebugLevelMain, "LoLEngine::kingSelectionOutro()"); + +	_sound->voicePlay("KING03"); + +	_chargenWSA->setX(113); +	_chargenWSA->setY(0); +	_chargenWSA->setDrawPage(0); + +	int index = 0; +	while (_sound->voiceIsPlaying("KING03") && !_quitFlag && !skipFlag()) { +		index = MAX(index, 4); + +		_chargenWSA->displayFrame(_chargenFrameTable[index], 0, 0, 0); +		_screen->updateScreen(); + +		uint32 waitEnd = _system->getMillis() + 8 * _tickLength; +		while (waitEnd > _system->getMillis() && !_quitFlag && !skipFlag()) { +			updateInput(); +			_system->delayMillis(10); +		} + +		index = (index + 1) % 22; +	} + +	resetSkipFlag(); + +	_chargenWSA->displayFrame(0x10, 0, 0, 0); +	_screen->updateScreen(); +	_sound->voiceStop("KING03"); +} + +void LoLEngine::processCharacterSelection() { +	debugC(9, kDebugLevelMain, "LoLEngine::processCharacterSelection()"); +	 +	_charSelection = -1; +	while (!_quitFlag && _charSelection == -1) { +		uint32 nextKingMessage = _system->getMillis() + 900 * _tickLength; + +		while (nextKingMessage > _system->getMillis() && _charSelection == -1 && !_quitFlag) { +			updateSelectionAnims(); +			_charSelection = getCharSelection(); +			_system->delayMillis(10); +		} + +		if (_charSelection == -1) +			kingSelectionReminder(); +	} +} + +void LoLEngine::updateSelectionAnims() { +	debugC(9, kDebugLevelMain, "LoLEngine::updateSelectionAnims()"); + +	for (int i = 0; i < 4; ++i) { +		if (_system->getMillis() < _selectionAnimTimers[i]) +			continue; + +		const int index = _selectionAnimIndexTable[_selectionAnimFrames[i] + i * 2]; +		_screen->copyRegion(_selectionPosTable[index*2+0], _selectionPosTable[index*2+1], _charPreviews[i].x, _charPreviews[i].y, 32, 32, 4, 0); + +		int delayTime = 0; +		if (_selectionAnimFrames[i] == 1) +			delayTime = _rnd.getRandomNumberRng(0, 31) + 80; +		else +			delayTime = _rnd.getRandomNumberRng(0, 3) + 10; + +		_selectionAnimTimers[i] = _system->getMillis() + delayTime * _tickLength; +		_selectionAnimFrames[i] = (_selectionAnimFrames[i] + 1) % 2; +	} + +	_screen->updateScreen(); +} + +int LoLEngine::selectionCharInfo(int character) { +	debugC(9, kDebugLevelMain, "LoLEngine::selectionCharInfo(%d)", character); +	if (character < 0) +		return -1; + +	char filename[16]; +	char vocFilename[6]; +	strcpy(vocFilename, "000X0"); + +	switch (character) { +	case 0: +		strcpy(filename, "face09.shp"); +		vocFilename[3] = 'A'; +		break; +	 +	case 1: +		strcpy(filename, "face01.shp"); +		vocFilename[3] = 'M'; +		break; +	 +	case 2: +		strcpy(filename, "face08.shp"); +		vocFilename[3] = 'K'; +		break; +	 +	case 3: +		strcpy(filename, "face05.shp"); +		vocFilename[3] = 'C'; +		break; +	 +	default: +		break; +	}; + +	_screen->loadBitmap(filename, 9, 9, 0); +	_screen->copyRegion(0, 122, 0, 122, 320, 78, 4, 0, Screen::CR_NO_P_CHECK); +	_screen->copyRegion(_charPreviews[character].x - 3, _charPreviews[character].y - 3, 8, 127, 38, 38, 2, 0); + +	static const uint8 charSelectInfoIdx[] = { 0x1D, 0x22, 0x27, 0x2C }; +	const int idx = charSelectInfoIdx[character]; + +	_screen->fprintStringIntro(_tim->getCTableEntry(idx+0), 50, 127, 0x53, 0x00, 0xCF, 0x20); +	_screen->fprintStringIntro(_tim->getCTableEntry(idx+1), 50, 137, 0x53, 0x00, 0xCF, 0x20); +	_screen->fprintStringIntro(_tim->getCTableEntry(idx+2), 50, 147, 0x53, 0x00, 0xCF, 0x20); +	_screen->fprintStringIntro(_tim->getCTableEntry(idx+3), 50, 157, 0x53, 0x00, 0xCF, 0x20); +	_screen->fprintStringIntro(_tim->getCTableEntry(idx+4), 50, 167, 0x53, 0x00, 0xCF, 0x20); +	 +	_screen->fprintStringIntro(_tim->getCTableEntry(69), 100, 168, 0x32, 0x00, 0xCF, 0x20); +	 +	selectionCharInfoIntro(vocFilename); +	if (_charSelectionInfoResult == -1) { +		while (_charSelectionInfoResult == -1) { +			_charSelectionInfoResult = selectionCharAccept(); +			_system->delayMillis(10); +		} +	} +	 +	if (_charSelectionInfoResult != 1) { +		_charSelectionInfoResult = -1; +		_screen->copyRegion(0, 122, 0, 122, 320, 78, 2, 0, Screen::CR_NO_P_CHECK); +		_screen->updateScreen(); +		return -1; +	} + +	_screen->copyRegion(48, 127, 48, 127, 272, 60, 4, 0, Screen::CR_NO_P_CHECK); +	_screen->hideMouse(); +	_screen->copyRegion(48, 127, 48, 160, 272, 35, 4, 0, Screen::CR_NO_P_CHECK); +	_screen->copyRegion(0, 0, 0, 0, 112, 120, 4, 0, Screen::CR_NO_P_CHECK); + +	_screen->fprintStringIntro(_tim->getCTableEntry(64), 3, 28, 0x32, 0x00, 0x9C, 0x20); +	_screen->fprintStringIntro(_tim->getCTableEntry(65), 3, 38, 0x32, 0x00, 0x9C, 0x20); +	_screen->fprintStringIntro(_tim->getCTableEntry(66), 3, 48, 0x32, 0x00, 0x9C, 0x20); +	_screen->fprintStringIntro(_tim->getCTableEntry(67), 3, 58, 0x32, 0x00, 0x9C, 0x20); +	_screen->fprintStringIntro(_tim->getCTableEntry(68), 3, 68, 0x32, 0x00, 0x9C, 0x20); + +	resetSkipFlag(); +	kingSelectionOutro();	 +	return character; +} + +void LoLEngine::selectionCharInfoIntro(char *file) { +	debugC(9, kDebugLevelMain, "LoLEngine::selectionCharInfoIntro(%p)", (const void *)file); +	int index = 0; +	file[4] = '0'; +	 +	while (_charSelectionInfoResult == -1 && !_quitFlag) { +		if (!_sound->voicePlay(file)) +			break; + +		int i = 0; +		while (_sound->voiceIsPlaying(file) && _charSelectionInfoResult == -1 && !_quitFlag) { +			_screen->drawShape(0, _screen->getPtrToShape(_screen->getCPagePtr(9), _charInfoFrameTable[i]), 11, 130, 0, 0); +			_screen->updateScreen(); + +			uint32 nextFrame = _system->getMillis() + 8 * _tickLength; +			while (nextFrame > _system->getMillis() && _charSelectionInfoResult == -1) { +				_charSelectionInfoResult = selectionCharAccept(); +				_system->delayMillis(10); +			} + +			i = (i + 1) % 32; +		} + +		_sound->voiceStop(file); +		file[4] = ++index + '0'; +	} + +	_screen->drawShape(0, _screen->getPtrToShape(_screen->getCPagePtr(9), 0), 11, 130, 0, 0); +	_screen->updateScreen(); +} + +int LoLEngine::getCharSelection() { +	int inputFlag = checkInput() & 0xCF; +	removeInputTop(); + +	if (inputFlag == 200) { +		for (int i = 0; i < 4; ++i) { +			if (_charPreviews[i].x <= _mouseX && _mouseX <= _charPreviews[i].x + 31 && +				_charPreviews[i].y <= _mouseY && _mouseY <= _charPreviews[i].y + 31) +				return i; +		} +	} +	 +	return -1; +} + +int LoLEngine::selectionCharAccept() { +	int inputFlag = checkInput() & 0xCF; +	removeInputTop(); +	 +	if (inputFlag == 200) { +		if (88 <= _mouseX && _mouseX <= 128 && 180 <= _mouseY && _mouseY <= 194) +			return 1; +		if (196 <= _mouseX && _mouseX <= 236 && 180 <= _mouseY && _mouseY <= 194) +			return 0; +	} +	 +	return -1; +} + +#pragma mark - Opcodes + +typedef Common::Functor2Mem<const TIM *, const uint16 *, int, LoLEngine> TIMOpcodeLoL; +#define SetTimOpcodeTable(x) timTable = &x; +#define OpcodeTim(x) timTable->push_back(new TIMOpcodeLoL(this, &LoLEngine::x)) +#define OpcodeTimUnImpl() timTable->push_back(new TIMOpcodeLoL(this, 0)) + +void LoLEngine::setupOpcodeTable() { +	Common::Array<const TIMOpcode*> *timTable = 0; + +	SetTimOpcodeTable(_timIntroOpcodes); +	 +	// 0x00 +	OpcodeTim(tlol_setupPaletteFade); +	OpcodeTimUnImpl(); +	OpcodeTim(tlol_loadPalette); +	OpcodeTim(tlol_setupPaletteFadeEx); +	 +	// 0x04 +	OpcodeTim(tlol_processWsaFrame); +	OpcodeTim(tlol_displayText); +	OpcodeTimUnImpl(); +	OpcodeTimUnImpl(); +} + +#pragma mark - + +int LoLEngine::tlol_setupPaletteFade(const TIM *tim, const uint16 *param) { +	debugC(3, kDebugLevelScriptFuncs, "LoLEngine::t2_playSoundEffect(%p, %p) (%d)", (const void*)tim, (const void*)param, param[0]); +	_screen->getFadeParams(_screen->getPalette(0), param[0], _tim->_palDelayInc, _tim->_palDiff); +	_tim->_palDelayAcc = 0; +	return 1; +} + +int LoLEngine::tlol_loadPalette(const TIM *tim, const uint16 *param) { +	debugC(3, kDebugLevelScriptFuncs, "LoLEngine::tlol_loadPalette(%p, %p) (%d)", (const void*)tim, (const void*)param, param[0]); +	const char *palFile = (const char *)(tim->text + READ_LE_UINT16(tim->text + (param[0]<<1))); +	_res->loadFileToBuf(palFile, _screen->getPalette(0), 768); +	return 1; +} + +int LoLEngine::tlol_setupPaletteFadeEx(const TIM *tim, const uint16 *param) { +	debugC(3, kDebugLevelScriptFuncs, "LoLEngine::tlol_setupPaletteFadeEx(%p, %p) (%d)", (const void*)tim, (const void*)param, param[0]); +	memcpy(_screen->getPalette(0), _screen->getPalette(1), 768); + +	_screen->getFadeParams(_screen->getPalette(0), param[0], _tim->_palDelayInc, _tim->_palDiff); +	_tim->_palDelayAcc = 0; +	return 1; +} + +int LoLEngine::tlol_processWsaFrame(const TIM *tim, const uint16 *param) { +	debugC(3, kDebugLevelScriptFuncs, "LoLEngine::tlol_processWsaFrame(%p, %p) (%d, %d, %d, %d, %d)", +		(const void*)tim, (const void*)param, param[0], param[1], param[2], param[3], param[4]); +	TIMInterpreter::Animation *anim = (TIMInterpreter::Animation *)tim->wsa[param[0]].anim; +	const int frame = param[1]; +	const int x2 = param[2]; +	const int y2 = param[3]; +	const int factor = MAX<int>(0, (int16)param[4]); +	 +	const int x1 = anim->x; +	const int y1 = anim->y; +	 +	int w1 = anim->wsa->width(); +	int h1 = anim->wsa->height(); +	int w2 = (w1 * factor) / 100; +	int h2 = (h1 * factor) / 100; +	 +	anim->wsa->setDrawPage(2); +	anim->wsa->setX(x1); +	anim->wsa->setY(y1); +	anim->wsa->displayFrame(frame, anim->wsaCopyParams & 0xF0FF, 0, 0); +	_screen->wsaFrameAnimationStep(x1, y1, x2, y2, w1, h1, w2, h2, 2, 8, 0); +	_screen->checkedPageUpdate(8, 4); +	_screen->updateScreen(); + +	return 1; +} + +int LoLEngine::tlol_displayText(const TIM *tim, const uint16 *param) { +	debugC(3, kDebugLevelScriptFuncs, "LoLEngine::tlol_displayText(%p, %p) (%d, %d)", (const void*)tim, (const void*)param, param[0], (int16)param[1]); +	_tim->displayText(param[0], param[1]); +	return 1; +} + +} // end of namespace Kyra + diff --git a/engines/kyra/lol.h b/engines/kyra/lol.h new file mode 100644 index 0000000000..ee54f8abbb --- /dev/null +++ b/engines/kyra/lol.h @@ -0,0 +1,155 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ +  +#ifndef KYRA_LOL_H +#define KYRA_LOL_H + +#include "kyra/kyra_v1.h" +#include "kyra/script_tim.h" + +#include "common/list.h" + +namespace Kyra { + +class Screen_LoL; +class WSAMovie_v2; +struct Button; + +class LoLEngine : public KyraEngine_v1 { +public: +	LoLEngine(OSystem *system, const GameFlags &flags); +	~LoLEngine(); +	 +	Screen *screen(); +private: +	Screen_LoL *_screen; +	TIMInterpreter *_tim; + +	int init(); +	int go(); + +	// input +	void updateInput(); +	int checkInput(Button *buttonList = 0, bool mainLoop = false); +	void removeInputTop(); + +	int _mouseX, _mouseY; + +	struct Event { +		Common::Event event; +		bool causedSkip; + +		Event() : event(), causedSkip(false) {} +		Event(Common::Event e) : event(e), causedSkip(false) {} +		Event(Common::Event e, bool skip) : event(e), causedSkip(skip) {} + +		operator Common::Event() const { return event; } +	}; +	Common::List<Event> _eventList; + +	virtual bool skipFlag() const; +	virtual void resetSkipFlag(bool removeEvent = true); + +	// intro +	void setupPrologueData(bool load); + +	void showIntro(); +	 +	struct CharacterPrev { +		const char *name; +		int x, y; +		int attrib[3]; +	}; +	 +	static const CharacterPrev _charPreviews[]; + +	WSAMovie_v2 *_chargenWSA; +	static const uint8 _chargenFrameTable[]; +	int chooseCharacter(); + +	void kingSelectionIntro(); +	void kingSelectionReminder(); +	void kingSelectionOutro(); +	void processCharacterSelection(); +	void updateSelectionAnims(); +	int selectionCharInfo(int character); +	void selectionCharInfoIntro(char *file); +	 +	int getCharSelection(); +	int selectionCharAccept(); +	 +	int _charSelection; +	int _charSelectionInfoResult; +	 +	uint32 _selectionAnimTimers[4]; +	uint8 _selectionAnimFrames[4]; +	static const uint8 _selectionAnimIndexTable[]; +	 +	static const uint16 _selectionPosTable[]; + +	static const uint8 _selectionChar1IdxTable[]; +	static const uint8 _selectionChar2IdxTable[]; +	static const uint8 _selectionChar3IdxTable[]; +	static const uint8 _selectionChar4IdxTable[]; +	 +	static const uint8 _reminderChar1IdxTable[]; +	static const uint8 _reminderChar2IdxTable[]; +	static const uint8 _reminderChar3IdxTable[]; +	static const uint8 _reminderChar4IdxTable[]; +	 +	static const uint8 _charInfoFrameTable[]; + +	// timer +	void setupTimers() {} + +	// sound +	void snd_playVoiceFile(int) { /* XXX */ } + +	// opcode +	void setupOpcodeTable(); + +	Common::Array<const TIMOpcode*> _timIntroOpcodes; +	int tlol_setupPaletteFade(const TIM *tim, const uint16 *param); +	int tlol_loadPalette(const TIM *tim, const uint16 *param); +	int tlol_setupPaletteFadeEx(const TIM *tim, const uint16 *param); +	int tlol_processWsaFrame(const TIM *tim, const uint16 *param); +	int tlol_displayText(const TIM *tim, const uint16 *param); +	 +	// translation +	int _lang; + +	static const char * const _languageExt[]; +	 +	// unneeded +	void setWalkspeed(uint8) {} +	void setHandItem(uint16) {} +	void removeHandItem() {} +	bool lineIsPassable(int, int) { return false; } +}; + +} // end of namespace Kyra + +#endif + diff --git a/engines/kyra/module.mk b/engines/kyra/module.mk index ebb63b4b4e..e059a8ce4b 100644 --- a/engines/kyra/module.mk +++ b/engines/kyra/module.mk @@ -21,6 +21,7 @@ MODULE_OBJS := \  	kyra_v2.o \  	kyra_hof.o \  	kyra_mr.o \ +	lol.o \  	resource.o \  	saveload.o \  	saveload_lok.o \ @@ -33,6 +34,7 @@ MODULE_OBJS := \  	scene_mr.o \  	screen.o \  	screen_lok.o \ +	screen_lol.o \  	screen_v2.o \  	screen_hof.o \  	screen_mr.o \ diff --git a/engines/kyra/resource.cpp b/engines/kyra/resource.cpp index afd7eacfda..92818aafe1 100644 --- a/engines/kyra/resource.cpp +++ b/engines/kyra/resource.cpp @@ -55,10 +55,12 @@ bool Resource::reset() {  	if (!dir.exists() || !dir.isDirectory())  		error("invalid game path '%s'", dir.getPath().c_str()); -	if (!loadPakFile(StaticResource::staticDataFilename()) || !StaticResource::checkKyraDat()) { -		Common::String errorMessage = "You're missing the '" + StaticResource::staticDataFilename() + "' file or it got corrupted, (re)get it from the ScummVM website"; -		_vm->GUIErrorMessage(errorMessage); -		error(errorMessage.c_str()); +	if (_vm->game() != GI_LOL) { +		if (!loadPakFile(StaticResource::staticDataFilename()) || !StaticResource::checkKyraDat()) { +			Common::String errorMessage = "You're missing the '" + StaticResource::staticDataFilename() + "' file or it got corrupted, (re)get it from the ScummVM website"; +			_vm->GUIErrorMessage(errorMessage); +			error(errorMessage.c_str()); +		}  	}  	if (_vm->game() == GI_KYRA1) { @@ -99,6 +101,8 @@ bool Resource::reset() {  		loadFileList("FILEDATA.FDT");  		return true; +	} else if (_vm->game() == GI_LOL) { +		return true;  	}  	FSList fslist; @@ -1120,7 +1124,7 @@ bool FileExpander::process(uint8 *dst, const uint8 *src, uint32 outsize, uint32  void FileExpander::generateTables(uint8 srcIndex, uint8 dstIndex, uint8 dstIndex2, int cnt) {  	const uint8 *tbl1 = _tables[srcIndex]; -	const uint8 *tbl2 = _tables[dstIndex]; +	uint8 *tbl2 = _tables[dstIndex];  	const uint8 *tbl3 = dstIndex2 == 0xff ? 0 : _tables[dstIndex2];  	if (!cnt) @@ -1185,7 +1189,7 @@ void FileExpander::generateTables(uint8 srcIndex, uint8 dstIndex, uint8 dstIndex  		}		  	} -	memset((void*) tbl2, 0, 512); +	memset(tbl2, 0, 512);  	cnt--;  	s = tbl1 + cnt; diff --git a/engines/kyra/scene_hof.cpp b/engines/kyra/scene_hof.cpp index 1882386b03..62df683ea2 100644 --- a/engines/kyra/scene_hof.cpp +++ b/engines/kyra/scene_hof.cpp @@ -723,7 +723,7 @@ void KyraEngine_HoF::fadeScenePal(int srcIndex, int delayTime) {  bool KyraEngine_HoF::lineIsPassable(int x, int y) {  	debugC(9, kDebugLevelMain, "KyraEngine_HoF::lineIsPassable(%d, %d)", x, y); -	static int unkTable[] = { 1, 1, 1, 1, 1, 2, 4, 6, 8 }; +	static const int widthTable[] = { 1, 1, 1, 1, 1, 2, 4, 6, 8 };  	if (_pathfinderFlag & 2) {  		if (x >= 320) @@ -743,7 +743,7 @@ bool KyraEngine_HoF::lineIsPassable(int x, int y) {  	if (y > 143)  		return false; -	int unk1 = unkTable[getScale(x, y) >> 5]; +	int unk1 = widthTable[getScale(x, y) >> 5];  	if (y < 0)  		y = 0; diff --git a/engines/kyra/scene_lok.cpp b/engines/kyra/scene_lok.cpp index e4ae67f751..53c269a926 100644 --- a/engines/kyra/scene_lok.cpp +++ b/engines/kyra/scene_lok.cpp @@ -1144,11 +1144,11 @@ void KyraEngine_LoK::setCharactersInDefaultScene() {  }  void KyraEngine_LoK::setCharactersPositions(int character) { -	static uint16 initXPosTable[] = { +	static const uint16 initXPosTable[] = {  		0x3200, 0x0024, 0x2230, 0x2F00, 0x0020, 0x002B,  		0x00CA, 0x00F0, 0x0082, 0x00A2, 0x0042  	}; -	static uint8 initYPosTable[] = { +	static const uint8 initYPosTable[] = {  		0x00, 0xA2, 0x00, 0x42, 0x00,  		0x67, 0x67, 0x60, 0x5A, 0x71,  		0x76 diff --git a/engines/kyra/screen.cpp b/engines/kyra/screen.cpp index f00c47ceea..74f7bc6de9 100644 --- a/engines/kyra/screen.cpp +++ b/engines/kyra/screen.cpp @@ -92,9 +92,19 @@ bool Screen::init() {  		if (_useSJIS) {  			if (!_sjisFontData) { -				_sjisFontData = _vm->resource()->fileData("FMT_FNT.ROM", 0); -				if (!_sjisFontData) -					error("missing font rom ('FMT_FNT.ROM') required for this version"); +				// we use the FM-Towns font rom for PC-98, too, until we feel +				// like adding support for the PC-98 font +				//if (_vm->gameFlags().platform == Common::kPlatformFMTowns) { +					// FM-Towns +					_sjisFontData = _vm->resource()->fileData("FMT_FNT.ROM", 0); +					if (!_sjisFontData) +						error("missing font rom ('FMT_FNT.ROM') required for this version"); +				/*} else { +					// PC-98 +					_sjisFontData = _vm->resource()->fileData("FONT.ROM", 0); +					if (!_sjisFontData) +						error("missing font rom ('FONT.ROM') required for this version"); +				}*/  			}  			if (!_sjisTempPage) { @@ -370,61 +380,23 @@ void Screen::fadePalette(const uint8 *palData, int delay, const UpdateFunctor *u  	debugC(9, kDebugLevelScreen, "Screen::fadePalette(%p, %d, %p)", (const void *)palData, delay, (const void*)upFunc);  	updateScreen(); -	uint8 fadePal[768]; -	memcpy(fadePal, _screenPalette, 768); -	uint8 diff, maxDiff = 0; -	for (int i = 0; i < 768; ++i) { -		diff = ABS(palData[i] - fadePal[i]); -		if (diff > maxDiff) { -			maxDiff = diff; -		} -	} - -	int16 delayInc = delay << 8; -	if (maxDiff != 0) -		delayInc /= maxDiff; - -	delay = delayInc; -	for (diff = 1; diff <= maxDiff; ++diff) { -		if (delayInc >= 512) -			break; -		delayInc += delay; -	} +	int diff = 0, delayInc = 0; +	getFadeParams(palData, delay, delayInc, diff);  	int delayAcc = 0;  	while (!_vm->quit()) {  		delayAcc += delayInc; -		bool needRefresh = false; -		for (int i = 0; i < 768; ++i) { -			int c1 = palData[i]; -			int c2 = fadePal[i]; -			if (c1 != c2) { -				needRefresh = true; -				if (c1 > c2) { -					c2 += diff; -					if (c1 < c2) -						c2 = c1; -				} - -				if (c1 < c2) { -					c2 -= diff; -					if (c1 > c2) -						c2 = c1; -				} - -				fadePal[i] = (uint8)c2; -			} -		} -		if (!needRefresh) -			break; +		int refreshed = fadePalStep(palData, diff); -		setScreenPalette(fadePal);  		if (upFunc && upFunc->isValid())  			(*upFunc)();  		else  			_system->updateScreen(); -		//_system->delayMillis((delayAcc >> 8) * 1000 / 60); + +		if (!refreshed) +			break; +  		_vm->delay((delayAcc >> 8) * 1000 / 60);  		delayAcc &= 0xFF;  	} @@ -438,6 +410,61 @@ void Screen::fadePalette(const uint8 *palData, int delay, const UpdateFunctor *u  	}  } +void Screen::getFadeParams(const uint8 *palette, int delay, int &delayInc, int &diff) { +	debugC(9, kDebugLevelScreen, "Screen::getFadeParams(%p, %d, %p, %p)", (const void *)palette, delay, (const void *)&delayInc, (const void *)&diff); +	uint8 maxDiff = 0; +	for (int i = 0; i < 768; ++i) { +		diff = ABS(palette[i] - _screenPalette[i]); +		maxDiff = MAX<uint8>(maxDiff, diff); +	} + +	delayInc = delay << 8; +	if (maxDiff != 0) +		delayInc /= maxDiff; +	delayInc &= 0x7FFF; + +	delay = delayInc; +	for (diff = 1; diff <= maxDiff; ++diff) { +		if (delayInc >= 512) +			break; +		delayInc += delay; +	} +} + +int Screen::fadePalStep(const uint8 *palette, int diff) { +	debugC(9, kDebugLevelScreen, "Screen::fadePalStep(%p, %d)", (const void *)palette, diff); + +	uint8 fadePal[768]; +	memcpy(fadePal, _screenPalette, 768); +	 +	bool needRefresh = false; +	for (int i = 0; i < 768; ++i) { +		int c1 = palette[i]; +		int c2 = fadePal[i]; +		if (c1 != c2) { +			needRefresh = true; +			if (c1 > c2) { +				c2 += diff; +				if (c1 < c2) +					c2 = c1; +			} + +			if (c1 < c2) { +				c2 -= diff; +				if (c1 > c2) +					c2 = c1; +			} + +			fadePal[i] = (uint8)c2; +		} +	} +	 +	if (needRefresh) +		setScreenPalette(fadePal); + +	return needRefresh ? 1 : 0; +} +  void Screen::setPaletteIndex(uint8 index, uint8 red, uint8 green, uint8 blue) {  	debugC(9, kDebugLevelScreen, "Screen::setPaletteIndex(%u, %u, %u, %u)", index, red, green, blue);  	_currentPalette[index * 3 + 0] = red; @@ -2432,7 +2459,7 @@ void Screen::setShapePages(int page1, int page2, int minY, int maxY) {  	_maskMaxY = maxY;  } -void Screen::setMouseCursor(int x, int y, byte *shape) { +void Screen::setMouseCursor(int x, int y, const byte *shape) {  	debugC(9, kDebugLevelScreen, "Screen::setMouseCursor(%d, %d, %p)", x, y, (const void *)shape);  	if (!shape)  		return; diff --git a/engines/kyra/screen.h b/engines/kyra/screen.h index f8c85a2bac..99ba2d7c5f 100644 --- a/engines/kyra/screen.h +++ b/engines/kyra/screen.h @@ -89,10 +89,12 @@ public:  	enum FontId {  		FID_6_FNT = 0,  		FID_8_FNT, +		FID_9_FNT,  		FID_CRED6_FNT,  		FID_CRED8_FNT,  		FID_BOOKFONT_FNT,  		FID_GOLDFONT_FNT, +		FID_INTRO_FNT,  		FID_NUM  	}; @@ -145,6 +147,8 @@ public:  	void fadeToBlack(int delay=0x54, const UpdateFunctor *upFunc = 0);  	void fadePalette(const uint8 *palData, int delay, const UpdateFunctor *upFunc = 0); +	virtual void getFadeParams(const uint8 *palette, int delay, int &delayInc, int &diff); +	int fadePalStep(const uint8 *palette, int diff);  	void setPaletteIndex(uint8 index, uint8 red, uint8 green, uint8 blue);  	void setScreenPalette(const uint8 *palData); @@ -189,7 +193,7 @@ public:  	void hideMouse();  	void showMouse();  	bool isMouseVisible() const; -	void setMouseCursor(int x, int y, byte *shape); +	void setMouseCursor(int x, int y, const byte *shape);  	// rect handling  	virtual int getRectSize(int w, int h) = 0; diff --git a/engines/kyra/screen_lol.cpp b/engines/kyra/screen_lol.cpp new file mode 100644 index 0000000000..c6b47a9ca9 --- /dev/null +++ b/engines/kyra/screen_lol.cpp @@ -0,0 +1,69 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "kyra/screen_lol.h" +#include "kyra/lol.h" + +namespace Kyra { + +Screen_LoL::Screen_LoL(LoLEngine *vm, OSystem *system) : Screen_v2(vm, system), _vm(vm) { +} + +void Screen_LoL::setScreenDim(int dim) { +	debugC(9, kDebugLevelScreen, "Screen_LoL::setScreenDim(%d)", dim); +	assert(dim < _screenDimTableCount); +	_curDim = &_screenDimTable[dim]; +} + +const ScreenDim *Screen_LoL::getScreenDim(int dim) { +	debugC(9, kDebugLevelScreen, "Screen_LoL::getScreenDim(%d)", dim); +	assert(dim < _screenDimTableCount); +	return &_screenDimTable[dim]; +} + +void Screen_LoL::fprintStringIntro(const char *format, int x, int y, uint8 c1, uint8 c2, uint8 c3, uint16 flags, ...) { +	debugC(9, kDebugLevelScreen, "Screen_LoL::fprintStringIntro('%s', %d, %d, %d, %d, %d, %d, ...)", format, x, y, c1, c2, c3, flags); +	char buffer[400]; + +	va_list args; +	va_start(args, flags); +	vsprintf(buffer, format, args);	 +	va_end(args); +	 +	if ((flags & 0x0F00) == 0x100) +		x -= getTextWidth(buffer) >> 1; +	if ((flags & 0x0F00) == 0x200) +		x -= getTextWidth(buffer); + +	if ((flags & 0x00F0) == 0x20) { +		printText(buffer, x-1, y, c3, c2); +		printText(buffer, x, y+1, c3, c2); +	} + +	printText(buffer, x, y, c1, c2); +} + +} // end of namespace Kyra + diff --git a/engines/kyra/screen_lol.h b/engines/kyra/screen_lol.h new file mode 100644 index 0000000000..38df3ca897 --- /dev/null +++ b/engines/kyra/screen_lol.h @@ -0,0 +1,53 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef KYRA_SCREEN_LOL_H +#define KYRA_SCREEN_LOL_H + +#include "kyra/screen_v2.h" + +namespace Kyra { + +class LoLEngine; + +class Screen_LoL : public Screen_v2 { +public: +	Screen_LoL(LoLEngine *vm, OSystem *system); +	 +	void setScreenDim(int dim); +	const ScreenDim *getScreenDim(int dim); + +	void fprintStringIntro(const char *format, int x, int y, uint8 c1, uint8 c2, uint8 c3, uint16 flags, ...); +private: +	LoLEngine *_vm; + +	static const ScreenDim _screenDimTable[]; +	static const int _screenDimTableCount; +}; + +} // end of namespace Kyra + +#endif + diff --git a/engines/kyra/screen_v2.cpp b/engines/kyra/screen_v2.cpp index e26ef87bad..c6ea6a93e8 100644 --- a/engines/kyra/screen_v2.cpp +++ b/engines/kyra/screen_v2.cpp @@ -111,6 +111,30 @@ int Screen_v2::findLeastDifferentColor(const uint8 *paletteEntry, const uint8 *p  	return r;  } +void Screen_v2::getFadeParams(const uint8 *palette, int delay, int &delayInc, int &diff) { +	debugC(9, kDebugLevelScreen, "Screen_v2::getFadeParams(%p, %d, %p, %p)", (const void *)palette, delay, (const void *)&delayInc, (const void *)&diff); + +	int maxDiff = 0; +	diff = 0; +	for (int i = 0; i < 768; ++i) { +		diff = ABS(palette[i] - _screenPalette[i]); +		maxDiff = MAX(maxDiff, diff); +	} + +	delayInc = delay << 8; +	if (maxDiff != 0) { +		delayInc /= maxDiff; +		delayInc = MIN(delayInc, 0x7FFF); +	} + +	delay = delayInc; +	for (diff = 1; diff <= maxDiff; ++diff) { +		if (delayInc >= 256) +			break; +		delayInc += delay; +	} +} +  void Screen_v2::copyWsaRect(int x, int y, int w, int h, int dimState, int plotFunc, const uint8 *src,  							int unk1, const uint8 *unkPtr1, const uint8 *unkPtr2) {  	uint8 *dstPtr = getPagePtr(_curPage); @@ -369,7 +393,7 @@ void Screen_v2::wsaFrameAnimationStep(int x1, int y1, int x2, int y2,  		int t = (nb * h1) / h2;  		if (t != u) {  			u = t; -			const uint8 *s = src + (x1 + t) * 320; +			const uint8 *s = src + x1 + t * 320;  			uint8 *dt = (uint8 *)_wsaFrameAnimBuffer;  			t = w2 - w1; @@ -461,5 +485,27 @@ bool Screen_v2::calcBounds(int w0, int h0, int &x1, int &y1, int &w1, int &h1, i  	return (w1 == -1) ? false : true;  } +void Screen_v2::checkedPageUpdate(int srcPage, int dstPage) { +	debugC(9, kDebugLevelScreen, "Screen_v2::checkedPageUpdate(%d, %d)", srcPage, dstPage); +	 +	const uint32 *src = (const uint32 *)getPagePtr(srcPage); +	uint32 *dst = (uint32 *)getPagePtr(dstPage); +	uint32 *page0 = (uint32 *)getPagePtr(0); +	 +	bool updated = false; +	 +	for (int y = 0; y < 200; ++y) { +		for (int x = 0; x < 80; ++x, ++src, ++dst, ++page0) { +			if (*src != *dst) { +				updated = true; +				*dst = *page0 = *src; +			} +		} +	} + +	if (updated) +		addDirtyRect(0, 0, 320, 200); +} +  } // end of namespace Kyra diff --git a/engines/kyra/screen_v2.h b/engines/kyra/screen_v2.h index f624228445..7bbdc4b6c3 100644 --- a/engines/kyra/screen_v2.h +++ b/engines/kyra/screen_v2.h @@ -40,10 +40,14 @@ public:  	void copyWsaRect(int x, int y, int w, int h, int dimState, int plotFunc, const uint8 *src,  					int unk1, const uint8 *unkPtr1, const uint8 *unkPtr2); +	void checkedPageUpdate(int srcPage, int dstPage); +  	// palette handling  	uint8 *generateOverlay(const uint8 *palette, uint8 *buffer, int color, uint16 factor);  	void applyOverlay(int x, int y, int w, int h, int pageNum, const uint8 *overlay);  	int findLeastDifferentColor(const uint8 *paletteEntry, const uint8 *palette, uint16 numColors); +	 +	virtual void getFadeParams(const uint8 *palette, int delay, int &delayInc, int &diff);  	// shape handling  	uint8 *getPtrToShape(uint8 *shpFile, int shape); diff --git a/engines/kyra/script.cpp b/engines/kyra/script.cpp index ef3e259cfa..b10a4b32bf 100644 --- a/engines/kyra/script.cpp +++ b/engines/kyra/script.cpp @@ -35,7 +35,7 @@  namespace Kyra {  EMCInterpreter::EMCInterpreter(KyraEngine_v1 *vm) : _vm(vm) {  #define COMMAND(x) { &EMCInterpreter::x, #x } -	static CommandEntry commandProcs[] = { +	static const CommandEntry commandProcs[] = {  		// 0x00  		COMMAND(cmd_jmpTo),  		COMMAND(cmd_setRetValue), @@ -132,6 +132,8 @@ bool EMCInterpreter::load(const char *filename, EMCData *scriptData, const Commo  	scriptData->opcodes = opcodes; +	strncpy(scriptData->filename, filename, 13); +  	return true;  } @@ -205,7 +207,7 @@ bool EMCInterpreter::run(EMCState *script) {  	}  	if (opcode > 18) { -		error("Script unknown command: %d", opcode); +		error("Script unknown command: %d in file '%s' at offset 0x%.08X", opcode, script->dataPtr->filename, instOffset);  	} else {  		debugC(5, kDebugLevelScript, "[0x%.08X] EMCInterpreter::%s([%d/%u])", instOffset, _commands[opcode].desc, _parameter, (uint)_parameter);  		(this->*(_commands[opcode].proc))(script); @@ -388,7 +390,7 @@ void EMCInterpreter::cmd_execOpcode(EMCState* script) {  		script->retValue = (*(*script->dataPtr->opcodes)[opcode])(script);  	} else {  		script->retValue = 0; -		warning("calling unimplemented opcode(0x%.02X/%d)", opcode, opcode); +		warning("Calling unimplemented opcode(0x%.02X/%d) from file '%s'", opcode, opcode, script->dataPtr->filename);  	}  } diff --git a/engines/kyra/script.h b/engines/kyra/script.h index de52093f66..2b97a83289 100644 --- a/engines/kyra/script.h +++ b/engines/kyra/script.h @@ -36,6 +36,8 @@ struct EMCState;  typedef Common::Functor1<EMCState*, int> Opcode;  struct EMCData { +	char filename[13]; +  	byte *text;  	uint16 *data;  	uint16 *ordr; diff --git a/engines/kyra/script_hof.cpp b/engines/kyra/script_hof.cpp index 91fbfb3e49..e3a8bf95bc 100644 --- a/engines/kyra/script_hof.cpp +++ b/engines/kyra/script_hof.cpp @@ -799,10 +799,14 @@ int KyraEngine_HoF::o2_showLetter(EMCState *script) {  	_screen->fadeToBlack(0x14); -	sprintf(filename, "LETTER%.1d.", letter); -	strcat(filename, (_flags.isTalkie || _flags.platform == Common::kPlatformFMTowns || _lang) ? _languageExtension[_lang] : "TXT"); - +	sprintf(filename, "LETTER%.1d.%s", letter, _languageExtension[_lang]);  	uint8 *letterBuffer = _res->fileData(filename, 0); +	if (!letterBuffer) { +		// some floppy versions use a TXT extension +		sprintf(filename, "LETTER%.1d.TXT", letter); +		letterBuffer = _res->fileData(filename, 0); +	} +  	if (letterBuffer) {  		bookDecodeText(letterBuffer);  		bookPrintText(2, letterBuffer, 0xC, 0xA, 0x20); @@ -1488,7 +1492,7 @@ typedef Common::Functor1Mem<EMCState*, int, KyraEngine_HoF> OpcodeV2;  typedef Common::Functor2Mem<const TIM*, const uint16*, int, KyraEngine_HoF> TIMOpcodeV2;  #define OpcodeTim(x) _timOpcodes.push_back(new TIMOpcodeV2(this, &KyraEngine_HoF::x)) -#define OpcodeTimUnImpl() _timOpcodes.push_back(TIMOpcodeV2(this, 0)) +#define OpcodeTimUnImpl() _timOpcodes.push_back(new TIMOpcodeV2(this, 0))  void KyraEngine_HoF::setupOpcodeTable() {  	Common::Array<const Opcode*> *table = 0; diff --git a/engines/kyra/script_tim.cpp b/engines/kyra/script_tim.cpp index 4ad6464424..7993fb8de6 100644 --- a/engines/kyra/script_tim.cpp +++ b/engines/kyra/script_tim.cpp @@ -26,58 +26,75 @@  #include "kyra/script_tim.h"  #include "kyra/script.h"  #include "kyra/resource.h" +#include "kyra/sound.h" +#include "kyra/wsamovie.h"  #include "common/endian.h"  namespace Kyra { -TIMInterpreter::TIMInterpreter(KyraEngine_v1 *vm, OSystem *system) : _vm(vm), _system(system), _currentTim(0) { +TIMInterpreter::TIMInterpreter(KyraEngine_v1 *vm, Screen_v2 *screen, OSystem *system) : _vm(vm), _screen(screen), _system(system), _currentTim(0) {  #define COMMAND(x) { &TIMInterpreter::x, #x }  #define COMMAND_UNIMPL() { 0, 0 } -	static CommandEntry commandProcs[] = { +#define cmd_return(n) cmd_return_##n +	static const CommandEntry commandProcs[] = {  		// 0x00  		COMMAND(cmd_initFunc0),  		COMMAND(cmd_stopCurFunc), -		COMMAND_UNIMPL(), -		COMMAND_UNIMPL(), +		COMMAND(cmd_initWSA), +		COMMAND(cmd_uninitWSA),  		// 0x04  		COMMAND(cmd_initFunc),  		COMMAND(cmd_stopFunc), -		COMMAND_UNIMPL(), +		COMMAND(cmd_wsaDisplayFrame),  		COMMAND_UNIMPL(),  		// 0x08 -		COMMAND_UNIMPL(), -		COMMAND_UNIMPL(), -		COMMAND_UNIMPL(), +		COMMAND(cmd_loadVocFile), +		COMMAND(cmd_unloadVocFile), +		COMMAND(cmd_playVocFile),  		COMMAND_UNIMPL(),  		// 0x0C -		COMMAND_UNIMPL(), -		COMMAND_UNIMPL(), -		COMMAND_UNIMPL(), +		COMMAND(cmd_loadSoundFile), +		COMMAND(cmd_return(1)), +		COMMAND(cmd_playMusicTrack),  		COMMAND_UNIMPL(),  		// 0x10 -		COMMAND_UNIMPL(), -		COMMAND_UNIMPL(), +		COMMAND(cmd_return(1)), +		COMMAND(cmd_return(1)),  		COMMAND_UNIMPL(),  		COMMAND_UNIMPL(),  		// 0x14 -		COMMAND_UNIMPL(), -		COMMAND_UNIMPL(), -		COMMAND_UNIMPL(), +		COMMAND(cmd_setLoopIp), +		COMMAND(cmd_continueLoop), +		COMMAND(cmd_resetLoopIp),  		COMMAND(cmd_resetAllRuntimes),  		// 0x18 -		COMMAND(cmd_return<1>), +		COMMAND(cmd_return(1)),  		COMMAND(cmd_execOpcode),  		COMMAND(cmd_initFuncNow),  		COMMAND(cmd_stopFuncNow),  		// 0x1C -		COMMAND(cmd_return<1>), -		COMMAND(cmd_return<1>), -		COMMAND(cmd_return<-1>) +		COMMAND(cmd_return(1)), +		COMMAND(cmd_return(1)), +		COMMAND(cmd_return(n1))  	}; +#undef cmd_return  	_commands = commandProcs;  	_commandsSize = ARRAYSIZE(commandProcs); +	 +	memset(&_animations, 0, sizeof(_animations)); +	_langData = 0; +	_textDisplayed = false; +	_textAreaBuffer = new uint8[320*40]; +	assert(_textAreaBuffer); +	 +	_palDelayInc = _palDiff = _palDelayAcc = 0; +} + +TIMInterpreter::~TIMInterpreter() { +	delete[] _langData; +	delete[] _textAreaBuffer;  }  TIM *TIMInterpreter::load(const char *filename, const Common::Array<const TIMOpcode*> *opcodes) { @@ -122,6 +139,8 @@ TIM *TIMInterpreter::load(const char *filename, const Common::Array<const TIMOpc  	for (int i = 0; i < num; ++i)  		tim->func[i].avtl = tim->avtl + tim->avtl[i];	 +	strncpy(tim->filename, filename, 13); +  	return tim;  } @@ -135,6 +154,11 @@ void TIMInterpreter::unload(TIM *&tim) const {  	tim = 0;  } +void TIMInterpreter::setLangData(const char *filename) { +	delete[] _langData; +	_langData = _vm->resource()->fileData(filename, 0); +} +  void TIMInterpreter::exec(TIM *tim, bool loop) {  	if (!tim)  		return; @@ -171,6 +195,10 @@ void TIMInterpreter::exec(TIM *tim, bool loop) {  					_currentTim->procFunc = _currentFunc;  					break; +				case 22: +					cur.loopIp = 0; +					break; +  				default:  					break;  				} @@ -197,22 +225,224 @@ void TIMInterpreter::refreshTimersAfterPause(uint32 elapsedTime) {  	}  } +void TIMInterpreter::displayText(uint16 textId, int16 flags) { +	char *text = getTableEntry(textId); + +	if (_textDisplayed) { +		_screen->copyBlockToPage(0, 0, 160, 320, 40, _textAreaBuffer); +		_textDisplayed = false; +	} + +	if (!text) +		return; +	if (!text[0]) +		return; + +	char filename[16]; +	memset(filename, 0, sizeof(filename)); + +	if (text[0] == '$') { +		const char *end = strchr(text+1, '$'); +		if (end) +			memcpy(filename, text+1, end-1-text); +	} + +	if (filename[0]) +		_vm->sound()->voicePlay(filename); + +	if (text[0] == '$') +		text = strchr(text + 1, '$') + 1; + +	setupTextPalette((flags < 0) ? 1 : flags, 0); + +	if (flags < 0) { +		static const uint8 colorMap[] = { 0x00, 0xF0, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +		_screen->setFont(Screen::FID_8_FNT); +		_screen->setTextColorMap(colorMap); +		_screen->_charWidth = -2; +	} + +	_screen->_charOffset = -4; +	_screen->copyRegionToBuffer(0, 0, 160, 320, 40, _textAreaBuffer); +	_textDisplayed = true; + +	char backupChar = 0; +	char *str = text; +	int heightAdd = 0; + +	while (str[0]) { +		char *nextLine = strchr(str, '\r'); + +		backupChar = 0; +		if (nextLine) { +			backupChar = nextLine[0]; +			nextLine[0] = '\0'; +		} + +		int width = _screen->getTextWidth(str); + +		if (flags >= 0) +			_screen->printText(str, (320 - width) >> 1, 160 + heightAdd, 0xF0, 0x00); +		else +			_screen->printText(str, (320 - width) >> 1, 188, 0xF0, 0x00); + +		heightAdd += _screen->getFontHeight(); +		str += strlen(str); + +		if (backupChar) { +			nextLine[0] = backupChar; +			++str; +		} +	} + +	_screen->_charOffset = 0; + +	if (flags < 0) { +		static const uint8 colorMap[] = { 0x00, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0x00, 0x00, 0x00, 0x00 }; + +		_screen->setFont(Screen::FID_INTRO_FNT); +		_screen->setTextColorMap(colorMap); +		_screen->_charWidth = 0; +	} +} + +void TIMInterpreter::setupTextPalette(uint index, int fadePalette) { +	static const uint16 palTable[] = { +		0x00, 0x00, 0x00, +		0x64, 0x64, 0x64, +		0x61, 0x51, 0x30, +		0x29, 0x48, 0x64, +		0x00, 0x4B, 0x3B, +		0x64, 0x1E, 0x1E, +	}; + +	for (int i = 0; i < 15; ++i) { +		uint8 *palette = _screen->getPalette(0) + (240 + i) * 3; + +		uint8 c1 = (((15 - i) << 2) * palTable[index*3+0]) / 100; +		uint8 c2 = (((15 - i) << 2) * palTable[index*3+1]) / 100; +		uint8 c3 = (((15 - i) << 2) * palTable[index*3+2]) / 100; + +		palette[0] = c1; +		palette[1] = c2; +		palette[2] = c3; +	} +	 +	if (!fadePalette && !_palDiff) { +		_screen->setScreenPalette(_screen->getPalette(0)); +	} else { +		_screen->getFadeParams(_screen->getPalette(0), fadePalette, _palDelayInc, _palDiff); +		_palDelayAcc = 0; +	} +} + +TIMInterpreter::Animation *TIMInterpreter::initAnimStruct(int index, const char *filename, int x, int y, int, int offscreenBuffer, uint16 wsaFlags) { +	Animation *anim = &_animations[index]; +	anim->x = x; +	anim->y = y; +	anim->wsaCopyParams = wsaFlags; + +	uint16 wsaOpenFlags = ((wsaFlags & 0x10) != 0) ? 2 : 0; + +	char file[32]; +	snprintf(file, 32, "%s.WSA", filename); +	 +	if (_vm->resource()->exists(file)) { +		anim->wsa = new WSAMovie_v2(_vm, _screen); +		assert(anim->wsa); + +		anim->wsa->open(file, wsaOpenFlags, (index == 1) ? _screen->getPalette(0) : 0); +	} +	 +	if (anim->wsa && anim->wsa->opened()) { +		if (x == -1) +			anim->x = x = 0; +		if (y == -1) +			anim->y = y = 0; + +		if (wsaFlags & 2) { +			_screen->fadePalette(_screen->getPalette(1), 15, 0); +			_screen->clearPage(8); +			_screen->checkedPageUpdate(8, 4); +			_screen->updateScreen(); +		} + +		if (wsaFlags & 4) { +			snprintf(file, 32, "%s.CPS", filename); + +			if (_vm->resource()->exists(file)) { +				_screen->loadBitmap(file, 3, 3, _screen->getPalette(0)); +				_screen->copyRegion(0, 0, 0, 0, 320, 200, 2, 8, Screen::CR_NO_P_CHECK); +				_screen->checkedPageUpdate(8, 4); +				_screen->updateScreen(); +			} + +			anim->wsa->setX(x); +			anim->wsa->setY(y); +			anim->wsa->setDrawPage(0); +			anim->wsa->displayFrame(0, 0, 0, 0); +		}  + +		if (wsaFlags & 2) +			_screen->fadePalette(_screen->getPalette(0), 30, 0); +	} else { +		if (wsaFlags & 2) { +			_screen->fadePalette(_screen->getPalette(1), 15, 0); +			_screen->clearPage(8); +			_screen->checkedPageUpdate(8, 4); +			_screen->updateScreen(); +		} + +		snprintf(file, 32, "%s.CPS", filename); + +		if (_vm->resource()->exists(file)) { +			_screen->loadBitmap(file, 3, 3, _screen->getPalette(0)); +			_screen->copyRegion(0, 0, 0, 0, 320, 200, 2, 8, Screen::CR_NO_P_CHECK); +			_screen->checkedPageUpdate(8, 4); +			_screen->updateScreen(); +		} + +		if (wsaFlags & 2) +			_screen->fadePalette(_screen->getPalette(0), 30, 0); +	} +	 +	return anim; +} + +char *TIMInterpreter::getTableEntry(uint idx) { +	if (!_langData) +		return 0; +	else +		return (char *)(_langData + READ_LE_UINT16(_langData + (idx<<1))); +} + +const char *TIMInterpreter::getCTableEntry(uint idx) const { +	if (!_langData) +		return 0; +	else +		return (const char *)(_langData + READ_LE_UINT16(_langData + (idx<<1))); +} +  int TIMInterpreter::execCommand(int cmd, const uint16 *param) {  	if (cmd < 0 || cmd >= _commandsSize) { -		warning("Calling unimplemented TIM command %d", cmd); +		warning("Calling unimplemented TIM command %d from file '%s'", cmd, _currentTim->filename);  		return 0;  	}  	if (_commands[cmd].proc == 0) { -		warning("Calling unimplemented TIM command %d", cmd); +		warning("Calling unimplemented TIM command %d from file '%s'", cmd, _currentTim->filename);  		return 0;  	} -	debugC(5, kDebugLevelScript, "TIMInterpreter::%s(%p)", _commands[cmd].desc, (const void*)param); +	debugC(5, kDebugLevelScript, "TIMInterpreter::%s(%p)", _commands[cmd].desc, (const void* )param);  	return (this->*_commands[cmd].proc)(param);  }  int TIMInterpreter::cmd_initFunc0(const uint16 *param) { +	for (int i = 0; i < TIM::kWSASlots; ++i) +		memset(&_currentTim->wsa[i], 0, sizeof(TIM::WSASlot)); +  	_currentTim->func[0].ip = _currentTim->func[0].avtl;  	_currentTim->func[0].lastTime = _system->getMillis();  	return 1; @@ -226,6 +456,46 @@ int TIMInterpreter::cmd_stopCurFunc(const uint16 *param) {  	return -2;  } +int TIMInterpreter::cmd_initWSA(const uint16 *param) { +	const int index = param[0]; + +	TIM::WSASlot &slot = _currentTim->wsa[index]; +	 +	slot.x = int16(param[2]); +	slot.y = int16(param[3]); +	slot.offscreen = param[4]; +	slot.wsaFlags = param[5]; +	const char *filename = (const char *)(_currentTim->text + READ_LE_UINT16(_currentTim->text + (param[1]<<1))); +	 +	slot.anim = initAnimStruct(index, filename, slot.x, slot.y, 10, slot.offscreen, slot.wsaFlags); +	return 1; +} + +int TIMInterpreter::cmd_uninitWSA(const uint16 *param) { +	const int index = param[0]; + +	TIM::WSASlot &slot = _currentTim->wsa[index]; +	 +	if (!slot.anim) +		return 0; +	 +	Animation &anim = _animations[index]; +	 +	if (slot.offscreen) { +		delete anim.wsa; +		anim.wsa = 0; +		slot.anim = 0; +	} else { +		//XXX + +		delete anim.wsa; +		memset(&anim, 0, sizeof(Animation)); +		memset(&slot, 0, sizeof(TIM::WSASlot)); +	} +	 +	return 1; +} +  int TIMInterpreter::cmd_initFunc(const uint16 *param) {  	uint16 func = *param;  	assert(func < TIM::kCountFuncs); @@ -243,6 +513,97 @@ int TIMInterpreter::cmd_stopFunc(const uint16 *param) {  	return 1;  } +int TIMInterpreter::cmd_wsaDisplayFrame(const uint16 *param) { +	Animation &anim = _animations[param[0]]; +	const int frame = param[1]; +	 +	anim.wsa->setX(anim.x); +	anim.wsa->setY(anim.y); +	anim.wsa->setDrawPage((anim.wsaCopyParams & 0x4000) != 0 ? 2 : 8); +	anim.wsa->displayFrame(frame, anim.wsaCopyParams & 0xF0FF, 0, 0); +	return 1; +} + +int TIMInterpreter::cmd_displayText(const uint16 *param) { +	displayText(param[0], param[1]); +	return 1; +} + +int TIMInterpreter::cmd_loadVocFile(const uint16 *param) { +	const int stringId = param[0]; +	const int index = param[1]; + +	_vocFiles[index] = (const char *)(_currentTim->text + READ_LE_UINT16(_currentTim->text + (stringId << 1))); +	for (int i = 0; i < 4; ++i) +		_vocFiles[index].deleteLastChar(); +	return 1; +} + +int TIMInterpreter::cmd_unloadVocFile(const uint16 *param) { +	const int index = param[0]; +	_vocFiles[index].clear(); +	return 1; +} + +int TIMInterpreter::cmd_playVocFile(const uint16 *param) { +	const int index = param[0]; +	const int volume = (param[1] * 255) / 100; + +	if (index < ARRAYSIZE(_vocFiles) && !_vocFiles[index].empty()) +		_vm->sound()->voicePlay(_vocFiles[index].c_str()/*, volume*/, true); +	else +		_vm->snd_playSoundEffect(index, volume); +	 +	return 1; +} + +int TIMInterpreter::cmd_loadSoundFile(const uint16 *param) { +	const char *file = (const char *)(_currentTim->text + READ_LE_UINT16(_currentTim->text + (param[0]<<1))); +	 +	static char * fileList[] = { 0 }; +	fileList[0] = _audioFilename; +	static AudioDataStruct audioList = { fileList, 1, 0, 0 }; + +	strncpy(_audioFilename, file, sizeof(_audioFilename)); + +	_vm->sound()->setSoundList(&audioList); +	_vm->sound()->loadSoundFile(0); +	return 1; +} + +int TIMInterpreter::cmd_playMusicTrack(const uint16 *param) { +	_vm->sound()->playTrack(param[0]); +	return 1; +} + +int TIMInterpreter::cmd_setLoopIp(const uint16 *param) { +	_currentTim->func[_currentFunc].loopIp = _currentTim->func[_currentFunc].ip; +	return 1; +} + +int TIMInterpreter::cmd_continueLoop(const uint16 *param) { +	TIM::Function &func = _currentTim->func[_currentFunc]; +	 +	if (!func.loopIp) +		return -2; + +	func.ip = func.loopIp; + +	uint16 factor = param[0]; +	if (factor) { +		const uint32 random = _vm->_rnd.getRandomNumberRng(0, 0x8000); +		uint32 waitTime = (random * factor) / 0x8000; +		func.nextTime += waitTime * _vm->tickLength(); +	} +	 +	return 1; +} + +int TIMInterpreter::cmd_resetLoopIp(const uint16 *param) { +	_currentTim->func[_currentFunc].loopIp = 0; +	return 1; +} +  int TIMInterpreter::cmd_resetAllRuntimes(const uint16 *param) {  	for (int i = 0; i < TIM::kCountFuncs; ++i) {  		if (_currentTim->func[i].ip) @@ -252,14 +613,20 @@ int TIMInterpreter::cmd_resetAllRuntimes(const uint16 *param) {  }  int TIMInterpreter::cmd_execOpcode(const uint16 *param) { +	const uint16 opcode = *param++; +  	if (!_currentTim->opcodes) { -		warning("Trying to execute TIM opcode without opcode list"); +		warning("Trying to execute TIM opcode %d without opcode list (file '%s')", opcode, _currentTim->filename);  		return 0;  	} -	uint16 opcode = *param++;  	if (opcode > _currentTim->opcodes->size()) { -		warning("Calling unimplemented TIM opcode(0x%.02X/%d)", opcode, opcode); +		warning("Calling unimplemented TIM opcode(0x%.02X/%d) from file '%s'", opcode, opcode, _currentTim->filename); +		return 0; +	} + +	if (!(*_currentTim->opcodes)[opcode]->isValid()) { +		warning("Calling unimplemented TIM opcode(0x%.02X/%d) from file '%s'", opcode, opcode, _currentTim->filename);  		return 0;  	} diff --git a/engines/kyra/script_tim.h b/engines/kyra/script_tim.h index cd715ff4ef..68ef23fd6c 100644 --- a/engines/kyra/script_tim.h +++ b/engines/kyra/script_tim.h @@ -30,13 +30,18 @@  #include "common/array.h"  #include "common/func.h" +#include "common/str.h"  namespace Kyra { +class WSAMovie_v2; +class Screen_v2;  struct TIM;  typedef Common::Functor2<const TIM*, const uint16*, int> TIMOpcode;  struct TIM { +	char filename[13]; +  	int16 procFunc;  	uint16 procParam; @@ -50,9 +55,23 @@ struct TIM {  		uint32 lastTime;  		uint32 nextTime; +		const uint16 *loopIp; +  		const uint16 *avtl;  	} func[kCountFuncs]; +	enum { +		kWSASlots = 10 +	}; + +	struct WSASlot { +		void *anim; + +		int16 x, y; +		uint16 wsaFlags; +		uint16 offscreen; +	} wsa[kWSASlots]; +  	uint16 *avtl;  	uint8 *text; @@ -61,10 +80,22 @@ struct TIM {  class TIMInterpreter {  public: -	TIMInterpreter(KyraEngine_v1 *vm, OSystem *system); +	struct Animation { +		WSAMovie_v2 *wsa; +		int16 x, y; +		uint16 wsaCopyParams; +	}; + +	TIMInterpreter(KyraEngine_v1 *vm, Screen_v2 *screen, OSystem *system); +	~TIMInterpreter();  	TIM *load(const char *filename, const Common::Array<const TIMOpcode*> *opcodes);  	void unload(TIM *&tim) const; +	 +	void setLangData(const char *filename); +	void clearLangData() { delete[] _langData; _langData = 0; } +	 +	const char *getCTableEntry(uint idx) const;  	void resetFinishedFlag() { _finished = false; }  	bool finished() const { return _finished; } @@ -72,10 +103,15 @@ public:  	void exec(TIM *tim, bool loop);  	void stopCurFunc() { if (_currentTim) cmd_stopCurFunc(0); } -	void play(const char *filename);  	void refreshTimersAfterPause(uint32 elapsedTime); +	 +	void displayText(uint16 textId, int16 flags); +	void setupTextPalette(uint index, int fadePalette); + +	int _palDelayInc, _palDiff, _palDelayAcc;  private:  	KyraEngine_v1 *_vm; +	Screen_v2 *_screen;  	OSystem *_system;  	TIM *_currentTim; @@ -83,6 +119,19 @@ private:  	bool _finished; +	Common::String _vocFiles[120]; +	 +	Animation _animations[TIM::kWSASlots]; +	 +	Animation *initAnimStruct(int index, const char *filename, int x, int y, int, int offscreenBuffer, uint16 wsaFlags); +	 +	char _audioFilename[32]; +	 +	uint8 *_langData; +	char *getTableEntry(uint idx); +	bool _textDisplayed; +	uint8 *_textAreaBuffer; +  	int execCommand(int cmd, const uint16 *param);  	typedef int (TIMInterpreter::*CommandProc)(const uint16 *); @@ -96,14 +145,30 @@ private:  	int cmd_initFunc0(const uint16 *param);  	int cmd_stopCurFunc(const uint16 *param); +	int cmd_initWSA(const uint16 *param); +	int cmd_uninitWSA(const uint16 *param);  	int cmd_initFunc(const uint16 *param);  	int cmd_stopFunc(const uint16 *param); +	int cmd_wsaDisplayFrame(const uint16 *param); +	int cmd_displayText(const uint16 *param); +	int cmd_loadVocFile(const uint16 *param); +	int cmd_unloadVocFile(const uint16 *param); +	int cmd_playVocFile(const uint16 *param); +	int cmd_loadSoundFile(const uint16 *param); +	int cmd_playMusicTrack(const uint16 *param); +	int cmd_setLoopIp(const uint16 *param); +	int cmd_continueLoop(const uint16 *param); +	int cmd_resetLoopIp(const uint16 *param);  	int cmd_resetAllRuntimes(const uint16 *param);  	int cmd_execOpcode(const uint16 *param);  	int cmd_initFuncNow(const uint16 *param);  	int cmd_stopFuncNow(const uint16 *param); -	template<int T> -	int cmd_return(const uint16 *) { return T; } +#define cmd_return(n, v) \ +	int cmd_return_##n(const uint16 *) { return v; } + +	cmd_return( 1,  1); +	cmd_return(n1, -1); +#undef cmd_return  };  } // end of namespace Kyra diff --git a/engines/kyra/seqplayer.cpp b/engines/kyra/seqplayer.cpp index 73d69ef10c..dfda5bf859 100644 --- a/engines/kyra/seqplayer.cpp +++ b/engines/kyra/seqplayer.cpp @@ -500,7 +500,7 @@ bool SeqPlayer::playSequence(const uint8 *seqData, bool skipSeq) {  	debugC(9, kDebugLevelSequence, "SeqPlayer::seq_playSequence(%p, %d)", (const void *)seqData, skipSeq);  	assert(seqData); -	static SeqEntry floppySeqProcs[] = { +	static const SeqEntry floppySeqProcs[] = {  		// 0x00  		SEQOP(3, s1_wsaOpen),  		SEQOP(2, s1_wsaClose), @@ -541,7 +541,7 @@ bool SeqPlayer::playSequence(const uint8 *seqData, bool skipSeq) {  		SEQOP(1, s1_endOfScript)  	}; -	static SeqEntry cdromSeqProcs[] = { +	static const SeqEntry cdromSeqProcs[] = {  		// 0x00  		SEQOP(3, s1_wsaOpen),  		SEQOP(2, s1_wsaClose), diff --git a/engines/kyra/sequences_lok.cpp b/engines/kyra/sequences_lok.cpp index b30568c7e2..3a497a258f 100644 --- a/engines/kyra/sequences_lok.cpp +++ b/engines/kyra/sequences_lok.cpp @@ -1083,7 +1083,7 @@ void KyraEngine_LoK::seq_playCredits() {  	_screen->_charWidth = -1;  	// we only need this for the fm-towns version -	if (_flags.platform == Common::kPlatformFMTowns || _flags.platform == Common::kPlatformPC98) +	if (_flags.platform == Common::kPlatformFMTowns && _configMusic == 1)  		snd_playWanderScoreViaMap(53, 1);  	uint8 *buffer = 0; diff --git a/engines/kyra/sound.cpp b/engines/kyra/sound.cpp index f56c43aabd..c8749dc06b 100644 --- a/engines/kyra/sound.cpp +++ b/engines/kyra/sound.cpp @@ -202,8 +202,8 @@ bool SoundMidiPC::init() {  }  void SoundMidiPC::updateVolumeSettings() { -	_musicVolume = ConfMan.getInt("music_volume"); -	_sfxVolume = ConfMan.getInt("sfx_volume"); +	_musicVolume = CLIP(ConfMan.getInt("music_volume"), 0, 255); +	_sfxVolume = CLIP(ConfMan.getInt("sfx_volume"), 0, 255);  	updateChannelVolume(_musicVolume);  } @@ -243,27 +243,14 @@ int SoundMidiPC::open() {  }  void SoundMidiPC::close() { -	if (_driver) +	if (_driver) {  		_driver->close(); +		delete _driver; +	}  	_driver = 0;  }  void SoundMidiPC::send(uint32 b) { -	// HACK: For Kyrandia, we make the simplifying assumption that a song -	// either loops in its entirety, or not at all. So if we see a FOR_LOOP -	// controller event, we turn on looping even if there isn't any -	// corresponding NEXT_BREAK event. -	// -	// This is a gross over-simplification of how XMIDI handles loops. If -	// anyone feels like doing a proper implementation, please refer to -	// the Exult project, and do it in midiparser_xmidi.cpp - -	if ((b & 0xFFF0) == 0x74B0 && _eventFromMusic) { -		debugC(9, kDebugLevelMain | kDebugLevelSound, "SoundMidiPC: Looping song"); -		_musicParser->property(MidiParser::mpAutoLoop, true); -		return; -	} -  	if (_passThrough) {  		if ((b & 0xFFF0) == 0x007BB0)  			return; diff --git a/engines/kyra/sound.h b/engines/kyra/sound.h index 1baeb3064a..cebfdf491f 100644 --- a/engines/kyra/sound.h +++ b/engines/kyra/sound.h @@ -73,7 +73,8 @@ public:  		kAdlib,  		kMidiMT32,  		kMidiGM, -		kTowns +		kTowns, +		kPC98  	};  	virtual kType getMusicType() const = 0; @@ -382,7 +383,9 @@ private:  	Common::Mutex _mutex;  }; -class SoundTowns_EuphonyDriver; +class Towns_EuphonyDriver; +class TownsPC98_OpnDriver; +  class SoundTowns : public MidiDriver, public Sound {  public:  	SoundTowns(KyraEngine_v1 *vm, Audio::Mixer *mixer); @@ -417,6 +420,7 @@ public:  	static float semitoneAndSampleRate_to_sampleStep(int8 semiTone, int8 semiToneRootkey,  		uint32 sampleRate, uint32 outputRate, int32 pitchWheel); +  private:  	bool loadInstruments();  	void playEuphonyTrack(uint32 offset, int loop); @@ -430,7 +434,7 @@ private:  	uint _sfxFileIndex;  	uint8 *_sfxFileData; -	SoundTowns_EuphonyDriver * _driver; +	Towns_EuphonyDriver * _driver;  	MidiParser * _parser;  	Common::Mutex _mutex; @@ -439,13 +443,38 @@ private:  	const uint8 *_sfxWDTable;  }; -//class SoundTowns_v2_TwnDriver; -class SoundTowns_v2 : public Sound { +class SoundPC98 : public Sound {  public: -	SoundTowns_v2(KyraEngine_v1 *vm, Audio::Mixer *mixer); -	~SoundTowns_v2(); +	SoundPC98(KyraEngine_v1 *vm, Audio::Mixer *mixer); +	~SoundPC98(); -	kType getMusicType() const { return kTowns; } +	virtual kType getMusicType() const { return kPC98; } + +	bool init(); +	 +	void process() {} +	void loadSoundFile(uint file) {} + +	void playTrack(uint8 track); +	void haltTrack(); +	void beginFadeOut(); + +	int32 voicePlay(const char *file, bool isSfx = false) { return -1; } +	void playSoundEffect(uint8); + +protected: +	int _lastTrack; +	uint8 *_musicTrackData; +	uint8 *_sfxTrackData; +	TownsPC98_OpnDriver *_driver; +}; + +class SoundTownsPC98_v2 : public Sound { +public: +	SoundTownsPC98_v2(KyraEngine_v1 *vm, Audio::Mixer *mixer); +	~SoundTownsPC98_v2(); + +	kType getMusicType() const { return _vm->gameFlags().platform == Common::kPlatformFMTowns ? kTowns : kPC98; }  	bool init();  	void process(); @@ -457,15 +486,15 @@ public:  	void beginFadeOut();  	int32 voicePlay(const char *file, bool isSfx = false); -	void playSoundEffect(uint8) {} - -private: -	int _lastTrack; +	void playSoundEffect(uint8 track); +protected:  	Audio::AudioStream *_currentSFX; +	int _lastTrack; +	bool _useFmSfx; -	//SoundTowns_v2_TwnDriver *_driver; -	uint8 *_twnTrackData; +	uint8 *_musicTrackData; +	TownsPC98_OpnDriver *_driver;	  };  class MixedSoundDriver : public Sound { diff --git a/engines/kyra/sound_adlib.cpp b/engines/kyra/sound_adlib.cpp index 68a2f0be9c..0ceb288b8a 100644 --- a/engines/kyra/sound_adlib.cpp +++ b/engines/kyra/sound_adlib.cpp @@ -235,6 +235,10 @@ private:  	// * One for instruments, starting at offset 500.  	uint8 *getProgram(int progId) { +		uint16 offset = READ_LE_UINT16(_soundData + 2 * progId); +		//TODO: Check in LoL CD Adlib driver +		if (offset == 0xFFFF) +			return 0;  		return _soundData + READ_LE_UINT16(_soundData + 2 * progId);  	} @@ -1282,6 +1286,9 @@ int AdlibDriver::update_setupProgram(uint8 *&dataptr, Channel &channel, uint8 va  		return 0;  	uint8 *ptr = getProgram(value); +	//TODO: Check in LoL CD Adlib driver +	if (!ptr) +		return 0;  	uint8 chan = *ptr++;  	uint8 priority = *ptr++; @@ -2213,7 +2220,7 @@ const int SoundAdlibPC::_kyra1NumSoundTriggers = ARRAYSIZE(SoundAdlibPC::_kyra1S  SoundAdlibPC::SoundAdlibPC(KyraEngine_v1 *vm, Audio::Mixer *mixer)  	: Sound(vm, mixer), _driver(0), _trackEntries(), _soundDataPtr(0) {  	memset(_trackEntries, 0, sizeof(_trackEntries)); -	_v2 = (_vm->gameFlags().gameID == GI_KYRA2); +	_v2 = (_vm->gameFlags().gameID == GI_KYRA2) || (_vm->gameFlags().gameID == GI_LOL);  	_driver = new AdlibDriver(mixer, _v2);  	assert(_driver); diff --git a/engines/kyra/sound_lok.cpp b/engines/kyra/sound_lok.cpp index 8a1d16a6b1..b43d72ebce 100644 --- a/engines/kyra/sound_lok.cpp +++ b/engines/kyra/sound_lok.cpp @@ -43,19 +43,29 @@ void KyraEngine_LoK::snd_playWanderScoreViaMap(int command, int restart) {  	if (restart)  		_lastMusicCommand = -1; -	if (_flags.platform == Common::kPlatformFMTowns || _flags.platform == Common::kPlatformPC98) { +	if (_flags.platform == Common::kPlatformFMTowns) {  		if (command == 1) {  			_sound->beginFadeOut();  		} else if (command >= 35 && command <= 38) {  			snd_playSoundEffect(command-20);  		} else if (command >= 2) { -			if (_lastMusicCommand != command) { +			if (_lastMusicCommand != command)  				// the original does -2 here we handle this inside _sound->playTrack()  				_sound->playTrack(command); -			}  		} else {  			_sound->haltTrack();  		} +		_lastMusicCommand = command; +	} else if (_flags.platform == Common::kPlatformPC98) { +		if (command == 1) { +			_sound->beginFadeOut(); +		} else if (command >= 2) { +			if (_lastMusicCommand != command) +				_sound->playTrack(command); +		} else { +			_sound->haltTrack(); +		} +		_lastMusicCommand = command;  	} else {  		KyraEngine_v1::snd_playWanderScoreViaMap(command, restart);  	} diff --git a/engines/kyra/sound_towns.cpp b/engines/kyra/sound_towns.cpp index 4265533507..0f2b916c9d 100644 --- a/engines/kyra/sound_towns.cpp +++ b/engines/kyra/sound_towns.cpp @@ -34,18 +34,16 @@  #include "common/util.h" -#include <math.h> -  #define		EUPHONY_FADEOUT_TICKS		600  namespace Kyra { -enum ChannelState { _s_ready, _s_attacking, _s_decaying, _s_sustaining, _s_releasing }; +enum EnvelopeState { s_ready, s_attacking, s_decaying, s_sustaining, s_releasing }; -class MidiChannel_EuD : public MidiChannel { +class Towns_EuphonyChannel : public MidiChannel {  public: -	MidiChannel_EuD() {} -	~MidiChannel_EuD() {} +	Towns_EuphonyChannel() {} +	~Towns_EuphonyChannel() {}  	virtual void nextTick(int32 *outbuf, int buflen) = 0;  	virtual void rate(uint16 r) = 0; @@ -54,10 +52,10 @@ protected:  	uint16 _rate;  }; -class MidiChannel_EuD_FM : public MidiChannel_EuD { +class Towns_EuphonyFmChannel : public Towns_EuphonyChannel {  public: -	MidiChannel_EuD_FM(); -	virtual ~MidiChannel_EuD_FM(); +	Towns_EuphonyFmChannel(); +	virtual ~Towns_EuphonyFmChannel();  	void nextTick(int32 *outbuf, int buflen);  	void rate(uint16 r); @@ -79,13 +77,13 @@ protected:  	Voice2612 *_voice;  }; -class MidiChannel_EuD_WAVE : public MidiChannel_EuD { +class Towns_EuphonyPcmChannel : public Towns_EuphonyChannel {  public:  	void nextTick(int32 *outbuf, int buflen);  	void rate(uint16 r); -	MidiChannel_EuD_WAVE(); -	virtual ~MidiChannel_EuD_WAVE(); +	Towns_EuphonyPcmChannel(); +	virtual ~Towns_EuphonyPcmChannel();  	// MidiChannel interface  	MidiDriver *device() { return 0; } @@ -126,9 +124,9 @@ protected:  			int32 keyOffset;  			int32 keyNote;  			const int8 *_samples; -		} * _snd[8]; +		} *_snd[8];  		struct Env { -			ChannelState state; +			EnvelopeState state;  			int32 currentLevel;  			int32 rate;  			int32 tickCount; @@ -141,40 +139,39 @@ protected:  			int32 releaseRate;  			int32 rootKeyOffset;  			int32 size; -		} * _env[8]; -	} * _voice; +		} *_env[8]; +	} *_voice;  }; -class SoundTowns_EuphonyTrackQueue { +class Towns_EuphonyTrackQueue {  public: -	SoundTowns_EuphonyTrackQueue(SoundTowns_EuphonyDriver *driver, SoundTowns_EuphonyTrackQueue *last); -	~SoundTowns_EuphonyTrackQueue() {} +	Towns_EuphonyTrackQueue(Towns_EuphonyDriver *driver, Towns_EuphonyTrackQueue *last); +	~Towns_EuphonyTrackQueue() {} -	void release(); +	Towns_EuphonyTrackQueue *release();  	void initDriver(); -	void loadDataToCurrentPosition(uint8 * trackdata, uint32 size, bool loop = 0); -	void loadDataToEndOfQueue(uint8 * trackdata, uint32 size, bool loop = 0); +	void loadDataToCurrentPosition(uint8 *trackdata, uint32 size, bool loop = 0); +	void loadDataToEndOfQueue(uint8 *trackdata, uint32 size, bool loop = 0);  	void setPlayBackStatus(bool playing); -	SoundTowns_EuphonyTrackQueue * reset();  	bool isPlaying() {return _playing; } -	uint8 * trackData() {return _trackData; } +	uint8 *trackData() {return _trackData; }  	bool _loop; -	SoundTowns_EuphonyTrackQueue * _next; +	Towns_EuphonyTrackQueue *_next;  private: -	uint8 * _trackData; -	uint8 * _used; -	uint8 * _fchan; -	uint8 * _wchan; +	uint8 *_trackData; +	uint8 *_used; +	uint8 *_fchan; +	uint8 *_wchan;  	bool _playing; -	SoundTowns_EuphonyDriver * _driver; -	SoundTowns_EuphonyTrackQueue * _last; +	Towns_EuphonyDriver *_driver; +	Towns_EuphonyTrackQueue *_last;  }; -class MidiParser_EuD : public MidiParser { +class Towns_EuphonyParser : public MidiParser {  public: -	MidiParser_EuD(SoundTowns_EuphonyTrackQueue * queue); +	Towns_EuphonyParser(Towns_EuphonyTrackQueue * queue);  	bool loadMusic (byte *data, uint32 size);  	int32 calculateTempo(int16 val); @@ -183,11 +180,11 @@ protected:  	void resetTracking();  	void setup(); -	byte * _enable; -	byte * _mode; -	byte * _channel; -	byte * _adjVelo; -	int8 * _adjNote; +	byte *_enable; +	byte *_mode; +	byte *_channel; +	byte *_adjVelo; +	int8 *_adjNote;  	uint8 _firstBaseTickStep;  	uint8 _nextBaseTickStep; @@ -195,13 +192,13 @@ protected:  	uint32 _baseTick;  	byte _tempo[3]; -	SoundTowns_EuphonyTrackQueue * _queue; +	Towns_EuphonyTrackQueue *_queue;  }; -class SoundTowns_EuphonyDriver : public MidiDriver_Emulated { +class Towns_EuphonyDriver : public MidiDriver_Emulated {  public: -	SoundTowns_EuphonyDriver(Audio::Mixer *mixer); -	virtual ~SoundTowns_EuphonyDriver(); +	Towns_EuphonyDriver(Audio::Mixer *mixer); +	virtual ~Towns_EuphonyDriver();  	int open();  	void close(); @@ -213,7 +210,7 @@ public:  	void loadFmInstruments(const byte *instr);  	void loadWaveInstruments(const byte *instr); -	SoundTowns_EuphonyTrackQueue * queue() { return _queue; } +	Towns_EuphonyTrackQueue *queue() { return _queue; }  	MidiChannel *allocateChannel() { return 0; }  	MidiChannel *getPercussionChannel() { return 0; } @@ -237,10 +234,10 @@ protected:  	void generateSamples(int16 *buf, int len); -	MidiChannel_EuD_FM *_fChannel[6]; -	MidiChannel_EuD_WAVE *_wChannel[8]; -	MidiChannel_EuD * _channel[16]; -	SoundTowns_EuphonyTrackQueue * _queue; +	Towns_EuphonyFmChannel *_fChannel[6]; +	Towns_EuphonyPcmChannel *_wChannel[8]; +	Towns_EuphonyChannel *_channel[16]; +	Towns_EuphonyTrackQueue *_queue;  	int _volume;  	bool _fading; @@ -251,23 +248,23 @@ protected:  	int8 * _waveSounds[10];  }; -MidiChannel_EuD_FM::MidiChannel_EuD_FM() { +Towns_EuphonyFmChannel::Towns_EuphonyFmChannel() {  	_voice = new Voice2612;  } -MidiChannel_EuD_FM::~MidiChannel_EuD_FM() { +Towns_EuphonyFmChannel::~Towns_EuphonyFmChannel() {  	delete _voice;  } -void MidiChannel_EuD_FM::noteOn(byte note, byte onVelo) { +void Towns_EuphonyFmChannel::noteOn(byte note, byte onVelo) {  	_voice->noteOn(note, onVelo);  } -void MidiChannel_EuD_FM::noteOff(byte note) { +void Towns_EuphonyFmChannel::noteOff(byte note) {  	_voice->noteOff(note);  } -void MidiChannel_EuD_FM::controlChange(byte control, byte value) { +void Towns_EuphonyFmChannel::controlChange(byte control, byte value) {  	if (control == 121) {  		// Reset controller  		delete _voice; @@ -279,25 +276,25 @@ void MidiChannel_EuD_FM::controlChange(byte control, byte value) {  	}  } -void MidiChannel_EuD_FM::sysEx_customInstrument(uint32, const byte *fmInst) { +void Towns_EuphonyFmChannel::sysEx_customInstrument(uint32, const byte *fmInst) {  	_voice->_rate = _rate;  	_voice->setInstrument(fmInst);  } -void MidiChannel_EuD_FM::pitchBend(int16 value) { +void Towns_EuphonyFmChannel::pitchBend(int16 value) {  	_voice->pitchBend(value);  } -void MidiChannel_EuD_FM::nextTick(int32 *outbuf, int buflen) { +void Towns_EuphonyFmChannel::nextTick(int32 *outbuf, int buflen) {  	_voice->nextTick((int*) outbuf, buflen);  } -void MidiChannel_EuD_FM::rate(uint16 r) { +void Towns_EuphonyFmChannel::rate(uint16 r) {  	_rate = r;  	_voice->_rate = r;  } -MidiChannel_EuD_WAVE::MidiChannel_EuD_WAVE() { +Towns_EuphonyPcmChannel::Towns_EuphonyPcmChannel() {  	_voice = new Voice;  	for (uint8 i = 0; i < 8; i++) {  		_voice->_env[i] = new Voice::Env; @@ -310,7 +307,7 @@ MidiChannel_EuD_WAVE::MidiChannel_EuD_WAVE() {  	_current = -1;  } -MidiChannel_EuD_WAVE::~MidiChannel_EuD_WAVE() { +Towns_EuphonyPcmChannel::~Towns_EuphonyPcmChannel() {  	for (uint8 i = 0; i < 8; i++) {  		if (_voice->_snd[i])  			delete _voice->_snd[i]; @@ -319,7 +316,7 @@ MidiChannel_EuD_WAVE::~MidiChannel_EuD_WAVE() {  	delete _voice;  } -void MidiChannel_EuD_WAVE::noteOn(byte note, byte onVelo) { +void Towns_EuphonyPcmChannel::noteOn(byte note, byte onVelo) {  	_note = note;  	velocity(onVelo);  	_phase = 0; @@ -329,24 +326,24 @@ void MidiChannel_EuD_WAVE::noteOn(byte note, byte onVelo) {  			break;  	} -	_voice->_env[_current]->state = _s_attacking; +	_voice->_env[_current]->state = s_attacking;  	_voice->_env[_current]->currentLevel = 0;  	_voice->_env[_current]->rate = _rate;  	_voice->_env[_current]->tickCount = 0;  } -void MidiChannel_EuD_WAVE::noteOff(byte note) { +void Towns_EuphonyPcmChannel::noteOff(byte note) {      if (_current == -1)  		return; -	if (_voice->_env[_current]->state == _s_ready) +	if (_voice->_env[_current]->state == s_ready)  		return; -	_voice->_env[_current]->state = _s_releasing; +	_voice->_env[_current]->state = s_releasing;  	_voice->_env[_current]->releaseLevel = _voice->_env[_current]->currentLevel;  	_voice->_env[_current]->tickCount = 0;  } -void MidiChannel_EuD_WAVE::controlChange(byte control, byte value) { +void Towns_EuphonyPcmChannel::controlChange(byte control, byte value) {  	switch (control) {  		case 0x07:  			// volume @@ -377,7 +374,7 @@ void MidiChannel_EuD_WAVE::controlChange(byte control, byte value) {  	}  } -void MidiChannel_EuD_WAVE::sysEx_customInstrument(uint32 type, const byte *fmInst) { +void Towns_EuphonyPcmChannel::sysEx_customInstrument(uint32 type, const byte *fmInst) {  	if (type == 0x80) {  		for (uint8 i = 0; i < 8; i++) {  			const byte * const* pos = (const byte * const*) fmInst; @@ -406,7 +403,7 @@ void MidiChannel_EuD_WAVE::sysEx_customInstrument(uint32 type, const byte *fmIns  			_voice->split[i] = READ_LE_UINT16(fmInst + 16 + 2 * i);  			_voice->id[i] = READ_LE_UINT32(fmInst + 32 + 4 * i);  			_voice->_snd[i] = 0; -			_voice->_env[i]->state = _s_ready; +			_voice->_env[i]->state = s_ready;  			_voice->_env[i]->currentLevel = 0;  			_voice->_env[i]->totalLevel = *(fmInst + 64 + 8 * i);  			_voice->_env[i]->attackRate = *(fmInst + 65 + 8 * i) * 10; @@ -419,11 +416,11 @@ void MidiChannel_EuD_WAVE::sysEx_customInstrument(uint32 type, const byte *fmIns  	}  } -void MidiChannel_EuD_WAVE::pitchBend(int16 value) { +void Towns_EuphonyPcmChannel::pitchBend(int16 value) {  	_frequencyOffs = value;  } -void MidiChannel_EuD_WAVE::nextTick(int32 *outbuf, int buflen) { +void Towns_EuphonyPcmChannel::nextTick(int32 *outbuf, int buflen) {  	if (_current == -1 || !_voice->_snd[_current] || !_voice->_env[_current]->state || !_velocity) {  		velocity(0);  		_current = -1; @@ -475,13 +472,13 @@ void MidiChannel_EuD_WAVE::nextTick(int32 *outbuf, int buflen) {  	}  } -void MidiChannel_EuD_WAVE::evpNextTick() { +void Towns_EuphonyPcmChannel::evpNextTick() {  	switch (_voice->_env[_current]->state) { -		case _s_ready: +		case s_ready:  			_voice->_env[_current]->currentLevel = 0;  			return; -		case _s_attacking: +		case s_attacking:  			if (_voice->_env[_current]->attackRate == 0)  				_voice->_env[_current]->currentLevel = _voice->_env[_current]->totalLevel;  			else if (_voice->_env[_current]->attackRate >= 1270) @@ -493,12 +490,12 @@ void MidiChannel_EuD_WAVE::evpNextTick() {  			if (_voice->_env[_current]->currentLevel >= _voice->_env[_current]->totalLevel) {  				_voice->_env[_current]->currentLevel = _voice->_env[_current]->totalLevel; -				_voice->_env[_current]->state = _s_decaying; +				_voice->_env[_current]->state = s_decaying;  				_voice->_env[_current]->tickCount = 0;  			}  			break; -		case _s_decaying: +		case s_decaying:  			if (_voice->_env[_current]->decayRate == 0)  				_voice->_env[_current]->currentLevel = _voice->_env[_current]->sustainLevel;  			else if (_voice->_env[_current]->decayRate >= 1270) @@ -512,12 +509,12 @@ void MidiChannel_EuD_WAVE::evpNextTick() {  			if (_voice->_env[_current]->currentLevel <= _voice->_env[_current]->sustainLevel) {  				_voice->_env[_current]->currentLevel = _voice->_env[_current]->sustainLevel; -				_voice->_env[_current]->state = _s_sustaining; +				_voice->_env[_current]->state = s_sustaining;  				_voice->_env[_current]->tickCount = 0;  			}  			break; -			case _s_sustaining: +			case s_sustaining:  				if (_voice->_env[_current]->sustainRate == 0)  					_voice->_env[_current]->currentLevel = 0;  				else if (_voice->_env[_current]->sustainRate >= 2540) @@ -531,12 +528,12 @@ void MidiChannel_EuD_WAVE::evpNextTick() {  				if (_voice->_env[_current]->currentLevel <= 0) {  					_voice->_env[_current]->currentLevel = 0; -					_voice->_env[_current]->state = _s_ready; +					_voice->_env[_current]->state = s_ready;  					_voice->_env[_current]->tickCount = 0;  				}  				break; -			case _s_releasing: +			case s_releasing:  				if (_voice->_env[_current]->releaseRate == 0)  					_voice->_env[_current]->currentLevel = 0;  				else if (_voice->_env[_current]->releaseRate >= 1270) @@ -550,7 +547,7 @@ void MidiChannel_EuD_WAVE::evpNextTick() {  				if (_voice->_env[_current]->currentLevel <= 0) {  					_voice->_env[_current]->currentLevel = 0; -					_voice->_env[_current]->state = _s_ready; +					_voice->_env[_current]->state = s_ready;  				}  				break; @@ -559,15 +556,15 @@ void MidiChannel_EuD_WAVE::evpNextTick() {  	}  } -void MidiChannel_EuD_WAVE::rate(uint16 r) { +void Towns_EuphonyPcmChannel::rate(uint16 r) {  	_rate = r;  } -void MidiChannel_EuD_WAVE::velocity(int velo) { +void Towns_EuphonyPcmChannel::velocity(int velo) {  	_velocity = velo;  } -SoundTowns_EuphonyDriver::SoundTowns_EuphonyDriver(Audio::Mixer *mixer) +Towns_EuphonyDriver::Towns_EuphonyDriver(Audio::Mixer *mixer)  	: MidiDriver_Emulated(mixer) {  	_volume = 255;  	_fadestate = EUPHONY_FADEOUT_TICKS; @@ -576,9 +573,9 @@ SoundTowns_EuphonyDriver::SoundTowns_EuphonyDriver(Audio::Mixer *mixer)  	MidiDriver_YM2612::createLookupTables();  	for (uint8 i = 0; i < 6; i++) -		_channel[i] = _fChannel[i] = new MidiChannel_EuD_FM; +		_channel[i] = _fChannel[i] = new Towns_EuphonyFmChannel;  	for (uint8 i = 0; i < 8; i++) -		_channel[i + 6] = _wChannel[i] = new MidiChannel_EuD_WAVE; +		_channel[i + 6] = _wChannel[i] = new Towns_EuphonyPcmChannel;  	_channel[14] = _channel[15] = 0;  	_fmInstruments = _waveInstruments = 0; @@ -587,10 +584,10 @@ SoundTowns_EuphonyDriver::SoundTowns_EuphonyDriver(Audio::Mixer *mixer)  	rate(getRate());  	fading(0); -	_queue = new SoundTowns_EuphonyTrackQueue(this, 0); +	_queue = new Towns_EuphonyTrackQueue(this, 0);  } -SoundTowns_EuphonyDriver::~SoundTowns_EuphonyDriver() { +Towns_EuphonyDriver::~Towns_EuphonyDriver() {  	for (int i = 0; i < 6; i++)  		delete _fChannel[i];  	for (int i = 0; i < 8; i++) @@ -622,7 +619,7 @@ SoundTowns_EuphonyDriver::~SoundTowns_EuphonyDriver() {  	}  } -int SoundTowns_EuphonyDriver::open() { +int Towns_EuphonyDriver::open() {  	if (_isOpen)  		return MERR_ALREADY_OPEN;  	MidiDriver_Emulated::open(); @@ -633,18 +630,18 @@ int SoundTowns_EuphonyDriver::open() {  	return 0;  } -void SoundTowns_EuphonyDriver::close() { +void Towns_EuphonyDriver::close() {  	if (!_isOpen)  		return;  	_isOpen = false;  	_mixer->stopHandle(_mixerSoundHandle);  } -void SoundTowns_EuphonyDriver::send(uint32 b) { +void Towns_EuphonyDriver::send(uint32 b) {  	send(b & 0xF, b & 0xFFFFFFF0);  } -void SoundTowns_EuphonyDriver::send(byte chan, uint32 b) { +void Towns_EuphonyDriver::send(byte chan, uint32 b) {  	byte param2 = (byte) ((b >> 16) & 0xFF);  	byte param1 = (byte) ((b >>  8) & 0xFF);  	byte cmd    = (byte) (b & 0xF0); @@ -703,18 +700,18 @@ void SoundTowns_EuphonyDriver::send(byte chan, uint32 b) {  			_channel[chan]->pitchBend((param1 | (param2 << 7)) - 0x2000);  		break;  	default: -		warning("SoundTowns_EuphonyDriver: Unknown send() command 0x%02X", cmd); +		warning("Towns_EuphonyDriver: Unknown send() command 0x%02X", cmd);  	}  } -void SoundTowns_EuphonyDriver::loadFmInstruments(const byte *instr) { +void Towns_EuphonyDriver::loadFmInstruments(const byte *instr) {  	if (_fmInstruments)  		delete[] _fmInstruments;  	_fmInstruments = new uint8[0x1800];  	memcpy(_fmInstruments, instr, 0x1800);  } -void SoundTowns_EuphonyDriver::loadWaveInstruments(const byte *instr) { +void Towns_EuphonyDriver::loadWaveInstruments(const byte *instr) {  	if (_waveInstruments)  		delete[] _waveInstruments;  	_waveInstruments = new uint8[0x1000]; @@ -739,24 +736,24 @@ void SoundTowns_EuphonyDriver::loadWaveInstruments(const byte *instr) {  } -void SoundTowns_EuphonyDriver::assignFmChannel(uint8 midiChannelNumber, uint8 fmChannelNumber) { +void Towns_EuphonyDriver::assignFmChannel(uint8 midiChannelNumber, uint8 fmChannelNumber) {  	_channel[midiChannelNumber] = _fChannel[fmChannelNumber];  } -void SoundTowns_EuphonyDriver::assignWaveChannel(uint8 midiChannelNumber, uint8 waveChannelNumber) { +void Towns_EuphonyDriver::assignWaveChannel(uint8 midiChannelNumber, uint8 waveChannelNumber) {  	_channel[midiChannelNumber] = _wChannel[waveChannelNumber];  } -void SoundTowns_EuphonyDriver::removeChannel(uint8 midiChannelNumber) { +void Towns_EuphonyDriver::removeChannel(uint8 midiChannelNumber) {  	_channel[midiChannelNumber] = 0;  } -void SoundTowns_EuphonyDriver::generateSamples(int16 *data, int len) { +void Towns_EuphonyDriver::generateSamples(int16 *data, int len) {  	memset(data, 0, 2 * sizeof(int16) * len);  	nextTick(data, len);  } -void SoundTowns_EuphonyDriver::nextTick(int16 *buf1, int buflen) { +void Towns_EuphonyDriver::nextTick(int16 *buf1, int buflen) {  	int32 *buf0 = (int32 *)buf1;  	for (int i = 0; i < ARRAYSIZE(_channel); i++) { @@ -779,26 +776,26 @@ void SoundTowns_EuphonyDriver::nextTick(int16 *buf1, int buflen) {  	}  } -void SoundTowns_EuphonyDriver::rate(uint16 r) { +void Towns_EuphonyDriver::rate(uint16 r) {  	for (uint8 i = 0; i < 16; i++) {  		if (_channel[i])  			_channel[i]->rate(r);  	}  } -void SoundTowns_EuphonyDriver::fading(bool status) { +void Towns_EuphonyDriver::fading(bool status) {  	_fading = status;  	if (!_fading)  		_fadestate = EUPHONY_FADEOUT_TICKS;  } -MidiParser_EuD::MidiParser_EuD(SoundTowns_EuphonyTrackQueue * queue) : MidiParser(), +Towns_EuphonyParser::Towns_EuphonyParser(Towns_EuphonyTrackQueue * queue) : MidiParser(),  	_firstBaseTickStep(0x33), _nextBaseTickStep(0x33) {  		_initialTempo = calculateTempo(0x5a);  		_queue = queue;  } -void MidiParser_EuD::parseNextEvent(EventInfo &info) { +void Towns_EuphonyParser::parseNextEvent(EventInfo &info) {  	byte *pos = _position._play_pos;  	if (_queue->_next) { @@ -878,7 +875,7 @@ void MidiParser_EuD::parseNextEvent(EventInfo &info) {  				pos += 6;  			}  		} else if (cmd == 0xF2) { -			static uint16 tickTable [] = { 0x180, 0xC0, 0x80, 0x60, 0x40, 0x30, 0x20, 0x18 }; +			static const uint16 tickTable[] = { 0x180, 0xC0, 0x80, 0x60, 0x40, 0x30, 0x20, 0x18 };  			_baseTick += tickTable[_nextBaseTickStep >> 4] * ((_nextBaseTickStep & 0x0f) + 1);  			_nextBaseTickStep = pos[1];  			pos += 6; @@ -920,15 +917,14 @@ void MidiParser_EuD::parseNextEvent(EventInfo &info) {  	_position._play_pos = pos;  } -bool MidiParser_EuD::loadMusic(byte *data, uint32 size) { +bool Towns_EuphonyParser::loadMusic(byte *data, uint32 size) {  	bool loop = _autoLoop;  	if (_queue->isPlaying() && !_queue->_loop) {  		_queue->loadDataToEndOfQueue(data, size, loop);  	} else {  		unloadMusic(); -		_queue = _queue->reset(); -		_queue->release(); +		_queue = _queue->release();  		_queue->loadDataToCurrentPosition(data, size, loop);  		setup();  		setTrack(0); @@ -937,7 +933,7 @@ bool MidiParser_EuD::loadMusic(byte *data, uint32 size) {  	return true;  } -int32 MidiParser_EuD::calculateTempo(int16 val) { +int32 Towns_EuphonyParser::calculateTempo(int16 val) {  	int32 tempo = val;  	if (tempo < 0) @@ -953,7 +949,7 @@ int32 MidiParser_EuD::calculateTempo(int16 val) {  	return tempo;  } -void MidiParser_EuD::resetTracking() { +void Towns_EuphonyParser::resetTracking() {  	MidiParser::resetTracking();  	_nextBaseTickStep = _firstBaseTickStep; @@ -962,7 +958,7 @@ void MidiParser_EuD::resetTracking() {  	_queue->setPlayBackStatus(false);  } -void MidiParser_EuD::setup() { +void Towns_EuphonyParser::setup() {  	uint8 *data = _queue->trackData();  	if (!data)  		return; @@ -984,7 +980,7 @@ void MidiParser_EuD::setup() {  	_tracks[0] = data + 0x806;  } -SoundTowns_EuphonyTrackQueue::SoundTowns_EuphonyTrackQueue(SoundTowns_EuphonyDriver * driver, SoundTowns_EuphonyTrackQueue * last) { +Towns_EuphonyTrackQueue::Towns_EuphonyTrackQueue(Towns_EuphonyDriver * driver, Towns_EuphonyTrackQueue * last) {  	_trackData = 0;  	_next = 0;  	_driver = driver; @@ -993,22 +989,15 @@ SoundTowns_EuphonyTrackQueue::SoundTowns_EuphonyTrackQueue(SoundTowns_EuphonyDri  	_playing = false;  } -void SoundTowns_EuphonyTrackQueue::setPlayBackStatus(bool playing) { -	SoundTowns_EuphonyTrackQueue * i = this; +void Towns_EuphonyTrackQueue::setPlayBackStatus(bool playing) { +	Towns_EuphonyTrackQueue * i = this;  	do {  		i->_playing = playing;  		i = i->_next;  	} while (i);  } -SoundTowns_EuphonyTrackQueue * SoundTowns_EuphonyTrackQueue::reset() { -	SoundTowns_EuphonyTrackQueue * i = this; -	while (i->_last) -		i = i->_last; -	return i; -} - -void SoundTowns_EuphonyTrackQueue::loadDataToCurrentPosition(uint8 * trackdata, uint32 size, bool loop) { +void Towns_EuphonyTrackQueue::loadDataToCurrentPosition(uint8 * trackdata, uint32 size, bool loop) {  	if (_trackData)  		delete[] _trackData;  	_trackData = new uint8[0xC58A]; @@ -1022,17 +1011,17 @@ void SoundTowns_EuphonyTrackQueue::loadDataToCurrentPosition(uint8 * trackdata,  	_playing = false;  } -void SoundTowns_EuphonyTrackQueue::loadDataToEndOfQueue(uint8 * trackdata, uint32 size, bool loop) { +void Towns_EuphonyTrackQueue::loadDataToEndOfQueue(uint8 * trackdata, uint32 size, bool loop) {  	if (!_trackData) {  		loadDataToCurrentPosition(trackdata, size, loop);  		return;  	} -	SoundTowns_EuphonyTrackQueue * i = this; +	Towns_EuphonyTrackQueue * i = this;  	while (i->_next)  		i = i->_next; -	i = i->_next = new SoundTowns_EuphonyTrackQueue(_driver, i); +	i = i->_next = new Towns_EuphonyTrackQueue(_driver, i);  	i->_trackData = new uint8[0xC58A];  	memset(i->_trackData, 0, 0xC58A);  	Screen::decodeFrame4(trackdata, i->_trackData, size); @@ -1044,29 +1033,39 @@ void SoundTowns_EuphonyTrackQueue::loadDataToEndOfQueue(uint8 * trackdata, uint3  	i->_playing = _playing;  } -void SoundTowns_EuphonyTrackQueue::release() { -	SoundTowns_EuphonyTrackQueue * i = _next; -	_next = 0; -	_playing = false; -	_used = _fchan = _wchan = 0; +Towns_EuphonyTrackQueue *Towns_EuphonyTrackQueue::release() { +	Towns_EuphonyTrackQueue *i = this; +	while (i->_next) +		i = i->_next; -	if (_trackData) { -		delete[] _trackData; -		_trackData = 0; -	} +	Towns_EuphonyTrackQueue *res = i;  	while (i) { +		i->_playing = false; +		i->_used = i->_fchan = i->_wchan = 0;  		if (i->_trackData) {  			delete[] i->_trackData;  			i->_trackData = 0;  		} -		i = i->_next; -		if (i) -			delete i->_last; +		i = i->_last; +		if (i) { +			res = i; +			if (i->_next) { +				delete i->_next; +				i->_next = 0; +			} +		}  	} + +	if (res->_trackData) { +		delete[] res->_trackData; +		res->_trackData = 0; +	} + +	return res;  } -void SoundTowns_EuphonyTrackQueue::initDriver() { +void Towns_EuphonyTrackQueue::initDriver() {  	for (uint8 i = 0; i < 6; i++) {  		if (_used[_fchan[i]])  			_driver->assignFmChannel(_fchan[i], i); @@ -1084,11 +1083,1685 @@ void SoundTowns_EuphonyTrackQueue::initDriver() {  	_driver->send(0x79B0);  } +class TownsPC98_OpnOperator { +public: +	TownsPC98_OpnOperator(double rate, const uint8 *rateTable, +		const uint8 *shiftTable, const uint8 *attackDecayTable, const uint32 *frqTable, +		const uint32 *sineTable, const int32 *tlevelOut, const int32 *detuneTable); +	~TownsPC98_OpnOperator() {} + +	void keyOn(); +	void keyOff(); +	void frequency(int freq); +	void updatePhaseIncrement(); +	void recalculateRates(); +	void generateOutput(int phasebuf, int *_feedbuf, int &out); + +	void feedbackLevel(int32 level) {_feedbackLevel = level ? level + 6 : 0; } +	void detune(int value) { _detn = &_detnTbl[value << 5]; } +	void multiple(uint32 value) { _multiple = value ? (value << 1) : 1;	} +	void attackRate(uint32 value) { _specifiedAttackRate = value; } +	bool scaleRate(uint8 value); +	void decayRate(uint32 value) { _specifiedDecayRate = value;	recalculateRates();	} +	void sustainRate(uint32 value) { _specifiedSustainRate = value;	recalculateRates();	} +	void sustainLevel(uint32 value) { _sustainLevel = (value == 0x0f) ? 0x3e0 : value << 5; } +	void releaseRate(uint32 value) { _specifiedReleaseRate = value;	recalculateRates();	} +	void totalLevel(uint32 value) { _totalLevel = value << 3; } +	void reset(); + +protected: +	EnvelopeState _state; +	uint32 _feedbackLevel; +	uint32 _multiple; +	uint32 _totalLevel; +	uint8 _keyScale1; +	uint8 _keyScale2; +	uint32 _specifiedAttackRate; +	uint32 _specifiedDecayRate; +	uint32 _specifiedSustainRate; +	uint32 _specifiedReleaseRate; +	uint32 _tickCount; +	uint32 _sustainLevel; + +	uint32 _frequency; +	uint8 _kcode; +	uint32 _phase; +	uint32 _phaseIncrement; +	const int32 *_detn; + +	const uint8 *_rateTbl; +	const uint8 *_rshiftTbl; +	const uint8 *_adTbl; +	const uint32 *_fTbl; +	const uint32 *_sinTbl; +	const int32 *_tLvlTbl; +	const int32 *_detnTbl; + +	const double _tickLength; +	double _tick; +	int32 _currentLevel; + +	struct EvpState { +		uint8 rate; +		uint8 shift; +	} fs_a, fs_d, fs_s, fs_r; +}; + +TownsPC98_OpnOperator::TownsPC98_OpnOperator(double rate, const uint8 *rateTable,  +	const uint8 *shiftTable, const uint8 *attackDecayTable,	const uint32 *frqTable, +	const uint32 *sineTable, const int32 *tlevelOut, const int32 *detuneTable) : +	_rateTbl(rateTable), _rshiftTbl(shiftTable), _adTbl(attackDecayTable), _fTbl(frqTable), +	_sinTbl(sineTable), _tLvlTbl(tlevelOut), _detnTbl(detuneTable), _tickLength(rate * 65536.0), +	_specifiedAttackRate(0), _specifiedDecayRate(0), _specifiedReleaseRate(0), _specifiedSustainRate(0), +	_phase(0), _state(s_ready) { +	 +	reset(); +} + +void TownsPC98_OpnOperator::keyOn() { +	_state = s_attacking; +	_phase = 0; +} + +void TownsPC98_OpnOperator::keyOff() { +	if (_state != s_ready) +		_state = s_releasing; +} + +void TownsPC98_OpnOperator::frequency(int freq) { +	uint8 block = (freq >> 11); +	uint16 pos = (freq & 0x7ff); +	uint8 c = pos >> 7; +	_kcode = (block << 2) | ((c < 7) ? 0 : ((c > 8) ? 3 : c - 6 )); +	_frequency = _fTbl[pos << 1] >> (7 - block); +} + +void TownsPC98_OpnOperator::updatePhaseIncrement() { +	_phaseIncrement = ((_frequency + _detn[_kcode]) * _multiple) >> 1; +	uint8 keyscale = _kcode >> _keyScale1; +	if (_keyScale2 != keyscale) { +		_keyScale2 = keyscale; +		recalculateRates(); +	} +} + +void TownsPC98_OpnOperator::recalculateRates() { +	int k = _keyScale2; +	int r = _specifiedAttackRate ? (_specifiedAttackRate << 1) + 0x20 : 0; +	fs_a.rate = ((r + k) < 94) ? _rateTbl[r + k] : 136; +	fs_a.shift = ((r + k) < 94) ? _rshiftTbl[r + k] : 0; + +	r = _specifiedDecayRate ? (_specifiedDecayRate << 1) + 0x20 : 0; +	fs_d.rate = _rateTbl[r + k]; +	fs_d.shift = _rshiftTbl[r + k]; + +	r = _specifiedSustainRate ? (_specifiedSustainRate << 1) + 0x20 : 0; +	fs_s.rate = _rateTbl[r + k]; +	fs_s.shift = _rshiftTbl[r + k]; + +	r = (_specifiedReleaseRate << 2) + 0x22; +	fs_r.rate = _rateTbl[r + k]; +	fs_r.shift = _rshiftTbl[r + k]; +} + +void TownsPC98_OpnOperator::generateOutput(int phasebuf, int *_feedbuf, int &out) { +	if (_state == s_ready) +		return; + +	_tick += _tickLength; +	while (_tick > 0x30000) { +		_tick -= 0x30000; +		++_tickCount; + +		int32 levelIncrement = 0; +		uint32 targetTime = 0; +		int32 targetLevel = 0; +		EnvelopeState next_state = s_ready; + +		switch (_state) { +			case s_ready: +				return; +			case s_attacking: +				next_state = s_decaying; +				targetTime = (1 << fs_a.shift) - 1; +				targetLevel = 0; +				levelIncrement = (~_currentLevel * _adTbl[fs_a.rate + ((_tickCount >> fs_a.shift) & 7)]) >> 4; +				break; +			case s_decaying: +				targetTime = (1 << fs_d.shift) - 1; +				next_state = s_sustaining; +				targetLevel = _sustainLevel; +				levelIncrement = _adTbl[fs_d.rate + ((_tickCount >> fs_d.shift) & 7)]; +				break; +			case s_sustaining: +				targetTime = (1 << fs_s.shift) - 1; +				next_state = s_ready; +				targetLevel = 1023; +				levelIncrement = _adTbl[fs_s.rate + ((_tickCount >> fs_s.shift) & 7)]; +				break; +			case s_releasing: +				targetTime = (1 << fs_r.shift) - 1; +				next_state = s_ready; +				targetLevel = 1023; +				levelIncrement = _adTbl[fs_r.rate + ((_tickCount >> fs_r.shift) & 7)]; +				break; +		} + +		if (!(_tickCount & targetTime)) { +			_currentLevel += levelIncrement; +			if ((!targetLevel && _currentLevel <= targetLevel) || (targetLevel && _currentLevel >= targetLevel)) { +				if (_state != s_decaying) +					_currentLevel = targetLevel; +				if (_state != s_sustaining) +					_state = next_state; +			} +		} +	} + +	uint32 lvlout = _totalLevel + (uint32) _currentLevel; + +	int outp = 0; +	int *i = &outp, *o = &outp; +	int phaseShift = 0; + +	if (_feedbuf) { +		o = &_feedbuf[0]; +		i = &_feedbuf[1]; +		phaseShift = _feedbackLevel ? ((_feedbuf[0] + _feedbuf[1]) << _feedbackLevel) : 0; +		if (phasebuf == -1) +			*i = 0; +		*o = *i; +	} else { +		phaseShift = phasebuf << 15; +	}		 + +	if (lvlout < 832) { +		uint32 index = (lvlout << 3) + _sinTbl[(((int32)((_phase & 0xffff0000) +			+ phaseShift)) >> 16) & 0x3ff]; +		*i = ((index < 6656) ? _tLvlTbl[index] : 0); +	} else { +		*i = 0; +	} + +	_phase += _phaseIncrement; +	out += *o; +	if (out > 32767) +		out = 32767; +	if (out < -32767) +		out = -32767; +} + +void TownsPC98_OpnOperator::reset(){ +	keyOff(); +	_tick = 0; +	_keyScale2 = 0; +	_currentLevel = 1023; + +	frequency(0); +	detune(0); +	scaleRate(0); +	multiple(0); +	updatePhaseIncrement(); +	attackRate(0); +	decayRate(0); +	releaseRate(0); +	sustainRate(0); +	feedbackLevel(0);	 +	totalLevel(127); +} + +bool TownsPC98_OpnOperator::scaleRate(uint8 value) { +	value = 3 - value; +	if (_keyScale1 != value) { +		_keyScale1 = value; +		return true; +	} + +	int k = _keyScale2; +	int r = _specifiedAttackRate ? (_specifiedAttackRate << 1) + 0x20 : 0; +	fs_a.rate = ((r + k) < 94) ? _rateTbl[r + k] : 136; +	fs_a.shift = ((r + k) < 94) ? _rshiftTbl[r + k] : 0; +	return false; +} + +class TownsPC98_OpnDriver; +class TownsPC98_OpnChannel { +public: +	TownsPC98_OpnChannel(TownsPC98_OpnDriver *driver, uint8 regOffs, uint8 flgs, uint8 num, +		uint8 key, uint8 prt, uint8 id); +	virtual ~TownsPC98_OpnChannel(); +	virtual void init(); + +	typedef bool (TownsPC98_OpnChannel::*ControlEventFunc)(uint8 para); + +	typedef enum channelState { +		CHS_RECALCFREQ		=	0x01, +		CHS_KEYOFF			=	0x02, +		CHS_SSG				=	0x04, +		CHS_PITCHWHEELOFF	=	0x08, +		CHS_ALL_BUT_EOT		=	0x0f, +		CHS_EOT				=	0x80 +	} ChannelState; + +	virtual void loadData(uint8 *data); +	virtual void processEvents(); +	virtual void processFrequency(); +	bool processControlEvent(uint8 cmd); +	void writeReg(uint8 regAdress, uint8 value); + +	virtual void keyOn(); +	virtual void keyOff();	 +	 +	void setOutputLevel(); +	void fadeStep(); +	void reset(); + +	void updateEnv(); +	void generateOutput(int16 &leftSample, int16 &rightSample, int *del, int *feed); + +	bool _enableLeft; +	bool _enableRight; +	bool _updateEnvelopes; +	const uint8 _idFlag; +	int _feedbuf[3]; + +protected: +	bool control_dummy(uint8 para); +	bool control_f0_setPatch(uint8 para); +	bool control_f1_presetOutputLevel(uint8 para); +	bool control_f2_setKeyOffTime(uint8 para); +	bool control_f3_setFreqLSB(uint8 para); +	bool control_f4_setOutputLevel(uint8 para); +	bool control_f5_setTempo(uint8 para); +	bool control_f6_repeatSection(uint8 para); +	bool control_f7_setupPitchWheel(uint8 para); +	bool control_f8_togglePitchWheel(uint8 para); +	bool control_fa_writeReg(uint8 para); +	bool control_fb_incOutLevel(uint8 para); +	bool control_fc_decOutLevel(uint8 para); +	bool control_fd_jump(uint8 para); +	bool control_ff_endOfTrack(uint8 para); + +	bool control_f0_setPatchSSG(uint8 para); +	bool control_f1_setTotalLevel(uint8 para); +	bool control_f4_setAlgorithm(uint8 para); +	bool control_f9_unkSSG(uint8 para); +	bool control_fb_incOutLevelSSG(uint8 para); +	bool control_fc_decOutLevelSSG(uint8 para); +	bool control_ff_endOfTrackSSG(uint8 para); + +	uint8 _ticksLeft; +	uint8 _algorithm; +	uint8 _instrID; +	uint8 _totalLevel; +	uint8 _frqBlockMSB; +	int8 _frqLSB; +	uint8 _keyOffTime; +	bool _protect; +	uint8 *_dataPtr; +	uint8 _ptchWhlInitDelayLo; +	uint8 _ptchWhlInitDelayHi; +	int16 _ptchWhlModInitVal; +	uint8 _ptchWhlDuration; +	uint8 _ptchWhlCurDelay; +	int16 _ptchWhlModCurVal; +	uint8 _ptchWhlDurLeft; +	uint16 frequency; +	uint8 _regOffset; +	uint8 _flags; +	uint8 _ssg1; +	uint8 _ssg2; + +	const uint8 _chanNum; +	const uint8 _keyNum; +	const uint8 _part; + +	TownsPC98_OpnDriver *_drv; +	TownsPC98_OpnOperator **_opr; +	uint16 _frqTemp; + +	const ControlEventFunc *controlEvents; +}; + +class TownsPC98_OpnChannelSSG : public TownsPC98_OpnChannel { +public: +	TownsPC98_OpnChannelSSG(TownsPC98_OpnDriver *driver, uint8 regOffs, +		uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id); +	~TownsPC98_OpnChannelSSG() {} +	void init(); + +	void processEvents(); +	void processFrequency(); + +	void keyOn(); +	void keyOff(); +	void loadData(uint8 *data); + +private: +	void opn_SSG_UNK(uint8 a); +}; + + +class TownsPC98_OpnDriver : public Audio::AudioStream { +friend class TownsPC98_OpnChannel; +friend class TownsPC98_OpnChannelSSG; +public: +	enum OpnType { +		OD_TOWNS, +		OD_TYPE26, +		OD_TYPE86 +	}; + +	TownsPC98_OpnDriver(Audio::Mixer *mixer, OpnType type); +	~TownsPC98_OpnDriver(); + +	bool init(); +	void loadData(uint8 *data, bool loadPaused = false); +	void reset(); +	void fadeOut(); +	 +	void pause() { _playing = false; } +	void cont() { _playing = true; } + +	void callback(); +	void nextTick(int16 *buffer, uint32 bufferSize); + +	bool looping() { return _looping == _updateChannelsFlag ? true : false; } + +	// AudioStream interface +	int inline readBuffer(int16 *buffer, const int numSamples); +	bool isStereo() const { return true; } +	bool endOfData() const { return false; } +	int getRate() const { return _mixer->getOutputRate(); } + +protected: +	void generateTables(); + +	TownsPC98_OpnChannel **_channels; +	TownsPC98_OpnChannelSSG **_ssgChannels; +	//TownsPC98_OpnChannel *_adpcmChannel; + +	void setTempo(uint8 tempo); + +	void lock() { _mutex.lock(); } +	void unlock() { _mutex.unlock(); } + +	Audio::Mixer *_mixer; +	Common::Mutex _mutex; +	Audio::SoundHandle _soundHandle; + +	const uint8 *_opnCarrier; +	const uint8 *_opnFreqTable; +	const uint8 *_opnFxCmdLen; +	const uint8 *_opnLvlPresets; + +	uint8 *_oprRates; +	uint8 *_oprRateshift; +	uint8 *_oprAttackDecay; +	uint32 *_oprFrq; +	uint32 *_oprSinTbl; +	int32 *_oprLevelOut; +	int32 *_oprDetune; + +	uint8 *_trackData; +	uint8 *_patches; + +	uint8 _cbCounter; +	uint8 _updateChannelsFlag; +	uint8 _finishedChannelsFlag; +	uint16 _tempo; +	bool _playing; +	bool _fading; +	uint8 _looping; +	uint32 _tickCounter; + +	bool _updateEnvelopes; +	int _ssgFlag; + +	int32 _samplesTillCallback; +	int32 _samplesTillCallbackRemainder; +	int32 _samplesPerCallback; +	int32 _samplesPerCallbackRemainder; + +	const int _numChan; +	const int _numSSG; +	const bool _hasADPCM; +	const bool _hasStereo; + +	double _baserate; +	static const uint8 _drvTables[]; +	static const uint32 _adtStat[]; +	bool _ready; +}; + +TownsPC98_OpnChannel::TownsPC98_OpnChannel(TownsPC98_OpnDriver *driver, uint8 regOffs, uint8 flgs, uint8 num, +	uint8 key, uint8 prt, uint8 id) : _drv(driver), _regOffset(regOffs), _flags(flgs), _chanNum(num), _keyNum(key), +	_part(prt), _idFlag(id) { + +	_ticksLeft = _algorithm = _instrID = _totalLevel = _frqBlockMSB = _keyOffTime = _ssg1 = _ssg2 = 0; +	_ptchWhlInitDelayLo = _ptchWhlInitDelayHi = _ptchWhlDuration = _ptchWhlCurDelay = _ptchWhlDurLeft = 0; +	_frqLSB = 0; +	_protect = _updateEnvelopes = false; +	_enableLeft = _enableRight = true; +	_dataPtr = 0;		 +	_ptchWhlModInitVal = _ptchWhlModCurVal = 0; +	frequency = _frqTemp = 0; +	memset(&_feedbuf, 0, sizeof(int) * 3); +	_opr = 0; +} + +TownsPC98_OpnChannel::~TownsPC98_OpnChannel() { +	if (_opr) { +		for (int i = 0; i < 4; i++) +			delete _opr[i]; +		delete [] _opr; +	} +} + +void TownsPC98_OpnChannel::init() { +	 +	_opr = new TownsPC98_OpnOperator*[4]; +	for (int i = 0; i < 4; i++) +		_opr[i] = new TownsPC98_OpnOperator(_drv->_baserate, _drv->_oprRates, _drv->_oprRateshift, +			_drv->_oprAttackDecay, _drv->_oprFrq, _drv->_oprSinTbl, _drv->_oprLevelOut, _drv->_oprDetune); + +	#define Control(x)	&TownsPC98_OpnChannel::control_##x +	static const ControlEventFunc ctrlEvents[] = { +		Control(f0_setPatch), +		Control(f1_presetOutputLevel), +		Control(f2_setKeyOffTime), +		Control(f3_setFreqLSB), +		Control(f4_setOutputLevel), +		Control(f5_setTempo), +		Control(f6_repeatSection), +		Control(f7_setupPitchWheel), +		Control(f8_togglePitchWheel), +		Control(dummy), +		Control(fa_writeReg), +		Control(fb_incOutLevel), +		Control(fc_decOutLevel), +		Control(fd_jump), +		Control(dummy), +		Control(ff_endOfTrack) +	}; +	#undef Control + +	controlEvents = ctrlEvents; +} + +void TownsPC98_OpnChannel::keyOff() { +	// all operators off +	uint8 value = _keyNum & 0x0f; +	uint8 regAdress = 0x28; +	writeReg(regAdress, value); +	_flags |= CHS_KEYOFF; +} + +void TownsPC98_OpnChannel::keyOn() { +	// all operators on +	uint8 value = _keyNum | 0xf0; +	uint8 regAdress = 0x28; +	writeReg(regAdress, value); +} + +void TownsPC98_OpnChannel::loadData(uint8 *data) { +	_flags = (_flags & ~CHS_EOT) | CHS_ALL_BUT_EOT; +	_ticksLeft = 1; +	_dataPtr = data; +	_totalLevel = 0x7F; + +	uint8 *src_b = _dataPtr; +	int loop = 1; +	uint8 cmd = 0; +	while (loop) { +		if (loop == 1) { +			cmd = *src_b++; +			if (cmd < 0xf0) { +				src_b++; +				loop = 1; +			} else { +				if (cmd == 0xff) { +					loop = *src_b ? 2 : 0; +					if (READ_LE_UINT16(src_b)) +						_drv->_looping |= _idFlag; +				} else if (cmd == 0xf6) { +					loop = 3; +				} else { +					loop = 2; +				} +			} +		} else if (loop == 2) { +			src_b += _drv->_opnFxCmdLen[cmd - 240]; +			loop = 1; +		} else if (loop == 3) { +			src_b[0] = src_b[1]; +			src_b += 4; +			loop = 1; +		} +	} +} + +void TownsPC98_OpnChannel::processEvents() { +	if (_flags & CHS_EOT) +		return; + +	if (_protect == false && _ticksLeft == _keyOffTime) +		keyOff(); + +	if (--_ticksLeft) +		return; + +	if (_protect == false) +		keyOff(); + +	uint8 cmd = 0; +	bool loop = true; + +	while (loop) { +		cmd = *_dataPtr++; +		if (cmd < 0xf0) +			loop = false; +		else if (!processControlEvent(cmd)) +			return; +	} + +	uint8 para = *_dataPtr++; + +	if (cmd == 0x80) { +		keyOff(); +		_protect = false; +	} else { +		keyOn(); + +		if (_protect == false || cmd != _frqBlockMSB) +			_flags |= CHS_RECALCFREQ; +	 +		_protect = (para & 0x80) ? true : false; +		_frqBlockMSB = cmd; +	} + +	_ticksLeft = para & 0x7f; +} + +void TownsPC98_OpnChannel::processFrequency() { +	if (_flags & CHS_RECALCFREQ) { +		uint8 block = (_frqBlockMSB & 0x70) >> 1; +		uint16 bfreq = ((const uint16*)_drv->_opnFreqTable)[_frqBlockMSB & 0x0f]; +		frequency = (bfreq + _frqLSB) | (block << 8); + +		writeReg(_regOffset + 0xa4, (frequency >> 8)); +		writeReg(_regOffset + 0xa0, (frequency & 0xff)); + +		_ptchWhlCurDelay = _ptchWhlInitDelayHi; +		if (_flags & CHS_KEYOFF) { +			_ptchWhlModCurVal = _ptchWhlModInitVal; +			_ptchWhlCurDelay += _ptchWhlInitDelayLo; +		} + +		_ptchWhlDurLeft = (_ptchWhlDuration >> 1); +		_flags &= ~(CHS_KEYOFF | CHS_RECALCFREQ); +	} + +	if (!(_flags & CHS_PITCHWHEELOFF)) { +		if (--_ptchWhlCurDelay) +			return; +		_ptchWhlCurDelay = _ptchWhlInitDelayHi; +		frequency += _ptchWhlModCurVal; + +		writeReg(_regOffset + 0xa4, (frequency >> 8)); +		writeReg(_regOffset + 0xa0, (frequency & 0xff)); + +		if(!--_ptchWhlDurLeft) { +			_ptchWhlDurLeft = _ptchWhlDuration; +			_ptchWhlModCurVal = -_ptchWhlModCurVal; +		} +	} +} + +bool TownsPC98_OpnChannel::processControlEvent(uint8 cmd) { +	uint8 para = *_dataPtr++; +	return (this->*controlEvents[cmd & 0x0f])(para); +} + +void TownsPC98_OpnChannel::setOutputLevel() { +	uint8 outopr = _drv->_opnCarrier[_algorithm]; +	uint8 reg = 0x40 + _regOffset; + +	for (int i = 0; i < 4; i++) { +		if (outopr & 1) +			writeReg(reg, _totalLevel); +		outopr >>= 1; +		reg += 4; +	} +} + +void TownsPC98_OpnChannel::fadeStep() { +	_totalLevel += 3; +	if (_totalLevel > 0x7f) +		_totalLevel = 0x7f; +	setOutputLevel(); +} + +void TownsPC98_OpnChannel::reset() { +	for (int i = 0; i < 4; i++) +		_opr[i]->reset(); + +	_updateEnvelopes = false; +	_enableLeft = _enableRight = true; +	memset(&_feedbuf, 0, sizeof(int) * 3); +} + +void TownsPC98_OpnChannel::updateEnv() { +	for (int i = 0; i < 4 ; i++) +		_opr[i]->updatePhaseIncrement(); +} + +void TownsPC98_OpnChannel::generateOutput(int16 &leftSample, int16 &rightSample, int *del, int *feed) { +	int phbuf1, phbuf2, output; +	phbuf1 = phbuf2 = output = 0; +	 +	switch (_algorithm) { +		case 0: +			_opr[0]->generateOutput(0, feed, phbuf1); +			_opr[2]->generateOutput(*del, 0, phbuf2); +			*del = 0; +			_opr[1]->generateOutput(phbuf1, 0, *del); +			_opr[3]->generateOutput(phbuf2, 0, output); +			break; +		case 1: +			_opr[0]->generateOutput(0, feed, phbuf1); +			_opr[2]->generateOutput(*del, 0, phbuf2); +			_opr[1]->generateOutput(0, 0, phbuf1);					 +			_opr[3]->generateOutput(phbuf2, 0, output); +			*del = phbuf1; +			break; +		case 2: +			_opr[0]->generateOutput(0, feed, phbuf2); +			_opr[2]->generateOutput(*del, 0, phbuf2); +			_opr[1]->generateOutput(0, 0, phbuf1); +			_opr[3]->generateOutput(phbuf2, 0, output); +			*del = phbuf1; +			break; +		case 3: +			_opr[0]->generateOutput(0, feed, phbuf2); +			_opr[2]->generateOutput(0, 0, *del); +			_opr[1]->generateOutput(phbuf2, 0, phbuf1); +			_opr[3]->generateOutput(*del, 0, output); +			*del = phbuf1; +			break; +		case 4: +			_opr[0]->generateOutput(0, feed, phbuf1); +			_opr[2]->generateOutput(0, 0, phbuf2); +			_opr[1]->generateOutput(phbuf1, 0, output);					 +			_opr[3]->generateOutput(phbuf2, 0, output); +			*del = 0; +			break; +		case 5: +			*del = feed[1]; +			_opr[0]->generateOutput(-1, feed, phbuf1); +			_opr[2]->generateOutput(*del, 0, output); +			_opr[1]->generateOutput(*del, 0, output); +			_opr[3]->generateOutput(*del, 0, output); +			break; +		case 6: +			_opr[0]->generateOutput(0, feed, phbuf1); +			_opr[2]->generateOutput(0, 0, output); +			_opr[1]->generateOutput(phbuf1, 0, output); +			_opr[3]->generateOutput(0, 0, output); +			*del = 0; +			break; +		case 7: +			_opr[0]->generateOutput(0, feed, output); +			_opr[2]->generateOutput(0, 0, output); +			_opr[1]->generateOutput(0, 0, output); +			_opr[3]->generateOutput(0, 0, output); +			*del = 0; +			break; +		}; + +	if (_enableLeft) { +		int l = output + leftSample; +		if (l > 32767) +			l = 32767; +		if (l < -32767) +			l = -32767; +		leftSample = (int16) l; +	} + +	if (_enableRight) { +		int r = output + rightSample; +		if (r > 32767) +			r = 32767; +		if (r < -32767) +			r = -32767; +		rightSample = (int16) r; +	} +} + +void TownsPC98_OpnChannel::writeReg(uint8 regAdress, uint8 value) { +	uint8 h = regAdress & 0xf0; +	uint8 l = (regAdress & 0x0f); +	static const uint8 oprOrdr[] = { 0, 2, 1, 3 }; +	uint8 o = oprOrdr[(l - _regOffset) >> 2]; +	 +	switch (h) { +		case 0x00: +			// ssg +			warning("TownsPC98_OpnDriver: UNKNOWN ADDRESS %d", regAdress); +			break; +		case 0x10: +			// adpcm +			warning("TownsPC98_OpnDriver: UNKNOWN ADDRESS %d", regAdress); +			break; +		case 0x20: +			if (l == 8) { +				// Key on/off +				for (int i = 0; i < 4; i++) { +					if ((value >> (4 + i)) & 1) +						_opr[i]->keyOn(); +					else +						_opr[i]->keyOff(); +				} +			} else if (l == 2) { +				// LFO +				warning("TownsPC98_OpnDriver: TRYING TO USE LFO (NOT SUPPORTED)"); +			} else if (l == 7) { +				// Timers; Ch 3/6 special mode +				warning("TownsPC98_OpnDriver: TRYING TO USE CH 3/6 SPECIAL MODE (NOT SUPPORTED)"); +			} else if (l == 4 || l == 5) { +				// Timer A +				warning("TownsPC98_OpnDriver: TRYING TO USE TIMER_A (NOT SUPPORTED)"); +			} else if (l == 6) { +				// Timer B +				warning("TownsPC98_OpnDriver: TRYING TO USE TIMER_B (NOT SUPPORTED)"); +			} else if (l == 10 || l == 11) { +				// DAC +				warning("TownsPC98_OpnDriver: TRYING TO USE DAC (NOT SUPPORTED)"); +			} +			break; + +		case 0x30: +			// detune, multiple +			_opr[o]->detune((value >> 4) & 7); +			_opr[o]->multiple(value & 0x0f); +			_updateEnvelopes = true; +			break; + +		case 0x40: +			// total level +			_opr[o]->totalLevel(value & 0x7f); +			break; + +		case 0x50: +			// rate scaling, attack rate +			_opr[o]->attackRate(value & 0x1f); +			if (_opr[o]->scaleRate(value >> 6)) +				_updateEnvelopes = true; +			break; + +		case 0x60: +			// first decay rate, amplitude modulation +			_opr[o]->decayRate(value & 0x1f); +			if (value & 0x80) +				warning("TownsPC98_OpnDriver: TRYING TO USE AMP MODULATION (NOT SUPPORTED)"); + +			break; + +		case 0x70: +			// secondary decay rate +			_opr[o]->sustainRate(value & 0x1f); +			break; + +		case 0x80: +			// secondary amplitude, release rate; +			_opr[o]->sustainLevel(value >> 4); +			_opr[o]->releaseRate(value & 0x0f); +			break; + +		case 0x90: +			// ssg +			warning("TownsPC98_OpnDriver: UNKNOWN ADDRESS %d", regAdress); +			break; + +		case 0xa0: +			// frequency +			l -= _regOffset; +			if (l == 0) { +				_frqTemp = (_frqTemp & 0xff00) | value; +				_updateEnvelopes = true; +				for (int i = 0; i < 4; i++) +					_opr[i]->frequency(_frqTemp); +			} else if (l == 4) { +				_frqTemp = (_frqTemp & 0xff) | (value << 8); +			} else if (l == 8) { +				// Ch 3/6 special mode frq +				warning("TownsPC98_OpnDriver: TRYING TO USE CH 3/6 SPECIAL MODE FREQ (NOT SUPPORTED)"); +			} else if (l == 12) { +				// Ch 3/6 special mode frq +				warning("TownsPC98_OpnDriver: TRYING TO USE CH 3/6 SPECIAL MODE FREQ (NOT SUPPORTED)"); +			} +			break; + +		case 0xb0: +			l -= _regOffset; +			if (l == 0) { +				// feedback, _algorithm +				_opr[0]->feedbackLevel((value >> 3) & 7); +				_opr[1]->feedbackLevel(0); +				_opr[2]->feedbackLevel(0); +				_opr[3]->feedbackLevel(0); +			} else if (l == 4) { +				// stereo, LFO sensitivity +				_enableLeft = value & 0x80 ? true : false; +				_enableRight = value & 0x40 ? true : false; +				uint8 ams = (value & 0x3F) >> 3; +				if (ams) +					warning("TownsPC98_OpnDriver: TRYING TO USE AMP MODULATION SENSITIVITY (NOT SUPPORTED)"); +				uint8 fms = value & 3; +				if (fms) +					warning("TownsPC98_OpnDriver: TRYING TO USE FREQ MODULATION SENSITIVITY (NOT SUPPORTED)"); +			} +			break; + +		default: +			warning("TownsPC98_OpnDriver: UNKNOWN ADDRESS %d", regAdress); +			break; +	} +} + +bool TownsPC98_OpnChannel::control_f0_setPatch(uint8 para) { +	_instrID = para; +	uint8 reg = _regOffset + 0x80; + +	for (int i = 0; i < 4; i++) { +		// set release rate for each operator +		writeReg(reg, 0x0f); +		reg += 4; +	} + +	const uint8 *tptr = _drv->_patches + ((uint32)_instrID << 5); +	reg = _regOffset + 0x30; + +	// write registers 0x30 to 0x8f +	for (int i = 0; i < 6; i++) { +		writeReg(reg, tptr[0]); +		reg += 4; +		writeReg(reg, tptr[2]); +		reg += 4; +		writeReg(reg, tptr[1]); +		reg += 4; +		writeReg(reg, tptr[3]); +		reg += 4; +		tptr += 4; +	} + +	reg = _regOffset + 0xB0; +	_algorithm = tptr[0] & 7; +	// set feedback and algorithm +	writeReg(reg, tptr[0]); + +	setOutputLevel(); +	return true; +} + +bool TownsPC98_OpnChannel::control_f1_presetOutputLevel(uint8 para) { +	if (_drv->_fading) +		return true; + +	_totalLevel = _drv->_opnLvlPresets[para]; +	setOutputLevel(); +	return true; +} + +bool TownsPC98_OpnChannel::control_f2_setKeyOffTime(uint8 para) { +	_keyOffTime = para; +	return true; +} + +bool TownsPC98_OpnChannel::control_f3_setFreqLSB(uint8 para) { +	_frqLSB = (int8) para; +	return true; +} + +bool TownsPC98_OpnChannel::control_f4_setOutputLevel(uint8 para) { +	if (_drv->_fading) +		return true; + +	_totalLevel = para; +	setOutputLevel(); +	return true; +} + +bool TownsPC98_OpnChannel::control_f5_setTempo(uint8 para) { +	_drv->setTempo(para); +	return true; +} + +bool TownsPC98_OpnChannel::control_f6_repeatSection(uint8 para) { +	_dataPtr--; +	_dataPtr[0]--; + +	if (*_dataPtr) { +		// repeat section until counter has reached zero +		_dataPtr = _drv->_trackData + READ_LE_UINT16(_dataPtr + 2); +	} else { +		// reset counter, advance to next section +		_dataPtr[0] = _dataPtr[1]; +		_dataPtr += 4; +	} +	return true; +} + +bool TownsPC98_OpnChannel::control_f7_setupPitchWheel(uint8 para) { +	_ptchWhlInitDelayLo = _dataPtr[0]; +	_ptchWhlInitDelayHi = para; +	_ptchWhlModInitVal = (int16) READ_LE_UINT16(_dataPtr + 1); +	_ptchWhlDuration = _dataPtr[3]; +	_dataPtr += 4; +	_flags = (_flags & ~CHS_PITCHWHEELOFF) | CHS_KEYOFF | CHS_RECALCFREQ; +	return true; +} + +bool TownsPC98_OpnChannel::control_f8_togglePitchWheel(uint8 para) { +	if (para == 0x10) { +		if (*_dataPtr++) { +			_flags = (_flags & ~CHS_PITCHWHEELOFF) | CHS_KEYOFF; +		} else { +			_flags |= CHS_PITCHWHEELOFF; +		} +	} else { +		//uint8 skipChannels = para / 36; +		//uint8 entry = para % 36; +		//TownsPC98_OpnDriver::TownsPC98_OpnChannel *t = &chan[skipChannels]; +		////// NOT IMPLEMENTED +		//t->unnamedEntries[entry] = *_dataPtr++; +	} +	return true; +} + +bool TownsPC98_OpnChannel::control_fa_writeReg(uint8 para) { +	writeReg(para, *_dataPtr++); +	return true; +} + +bool TownsPC98_OpnChannel::control_fb_incOutLevel(uint8 para) { +	_dataPtr--; +	if (_drv->_fading) +		return true; + +	uint8 val = (_totalLevel + 3); +	if (val > 0x7f) +		val = 0x7f; + +	_totalLevel = val; +	setOutputLevel(); +	return true; +} + +bool TownsPC98_OpnChannel::control_fc_decOutLevel(uint8 para) { +	_dataPtr--; +	if (_drv->_fading) +		return true; + +	int8 val = (int8) (_totalLevel - 3); +	if (val < 0) +		val = 0; + +	_totalLevel = (uint8) val; +	setOutputLevel(); +	return true; +} + +bool TownsPC98_OpnChannel::control_fd_jump(uint8 para) { +	uint8 *tmp = _drv->_trackData + READ_LE_UINT16(_dataPtr - 1); +	_dataPtr = (tmp[1] == 1) ? tmp : ++_dataPtr; +	return true; +} + +bool TownsPC98_OpnChannel::control_dummy(uint8 para) { +	_dataPtr--; +	return true; +} + +bool TownsPC98_OpnChannel::control_ff_endOfTrack(uint8 para) { +	uint16 val = READ_LE_UINT16(--_dataPtr); +	if (val) { +		// loop +		_dataPtr = _drv->_trackData + val; +		return true; +	} else { +		// quit parsing for active channel +		--_dataPtr; +		_flags |= CHS_EOT; +		_drv->_finishedChannelsFlag |= _idFlag; +		keyOff(); +		return false; +	} +} + +bool TownsPC98_OpnChannel::control_f0_setPatchSSG(uint8 para) { +	_instrID = para << 4; +	para = (para >> 3) & 0x1e; +	if (para) +		return control_f4_setAlgorithm(para | 0x40); +	return true; +} + +bool TownsPC98_OpnChannel::control_f1_setTotalLevel(uint8 para) { +	if (!_drv->_fading) +		_totalLevel = para; +	return true; +} + +bool TownsPC98_OpnChannel::control_f4_setAlgorithm(uint8 para) { +	_algorithm = para; +	return true; +} + +bool TownsPC98_OpnChannel::control_f9_unkSSG(uint8 para) { +	_dataPtr += 5; +	return true; +} + +bool TownsPC98_OpnChannel::control_fb_incOutLevelSSG(uint8 para) { +	_dataPtr--; +	if (_drv->_fading) +		return true; + +	_totalLevel--; +	if ((int8)_totalLevel < 0) +		_totalLevel = 0; + +	return true; +} + +bool TownsPC98_OpnChannel::control_fc_decOutLevelSSG(uint8 para) { +	_dataPtr--; +	if (_drv->_fading) +		return true; + +	if(_totalLevel + 1 < 0x10) +		_totalLevel++; + +	return true; +} + +bool TownsPC98_OpnChannel::control_ff_endOfTrackSSG(uint8 para) { +	uint16 val = READ_LE_UINT16(--_dataPtr); +	if (val) { +		// loop +		_dataPtr = _drv->_trackData + val; +		return true; +	} else { +		// quit parsing for active channel +		--_dataPtr; +		_flags |= CHS_EOT; +		//_finishedChannelsFlag |= _idFlag; +		keyOff(); +		return false; +	} +} + +TownsPC98_OpnChannelSSG::TownsPC98_OpnChannelSSG(TownsPC98_OpnDriver *driver, uint8 regOffs,  +		uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id) : +		TownsPC98_OpnChannel(driver, regOffs, flgs, num, key, prt, id) { +} + +void TownsPC98_OpnChannelSSG::init() { +	_algorithm = 0x80; +	 +	_opr = new TownsPC98_OpnOperator*[4]; +	for (int i = 0; i < 4; i++) +		_opr[i] = new TownsPC98_OpnOperator(_drv->_baserate, _drv->_oprRates, _drv->_oprRateshift, +			_drv->_oprAttackDecay, _drv->_oprFrq, _drv->_oprSinTbl, _drv->_oprLevelOut, _drv->_oprDetune); + +	#define Control(x)	&TownsPC98_OpnChannelSSG::control_##x +	static const ControlEventFunc ctrlEventsSSG[] = { +		Control(f0_setPatchSSG), +		Control(f1_setTotalLevel), +		Control(f2_setKeyOffTime), +		Control(f3_setFreqLSB), +		Control(f4_setOutputLevel), +		Control(f5_setTempo), +		Control(f6_repeatSection), +		Control(f7_setupPitchWheel), +		Control(f8_togglePitchWheel), +		Control(f9_unkSSG), +		Control(fa_writeReg), +		Control(fb_incOutLevelSSG), +		Control(fc_decOutLevelSSG), +		Control(fd_jump), +		Control(dummy), +		Control(ff_endOfTrackSSG) +	}; +	#undef Control + +	controlEvents = ctrlEventsSSG; +} + +void TownsPC98_OpnChannelSSG::processEvents() { +	if (_flags & CHS_EOT) +		return; + +	_drv->_ssgFlag = (_flags & CHS_SSG) ? -1 : 0; + +	if (_protect == false && _ticksLeft == _keyOffTime) +		keyOff(); + +	if (--_ticksLeft) +		return; + +	if (_protect == false) +		keyOff(); + +	uint8 cmd = 0; +	bool loop = true; + +	while (loop) { +		cmd = *_dataPtr++; +		if (cmd < 0xf0) +			loop = false; +		else if (!processControlEvent(cmd)) +			return; +	} + +	uint8 para = *_dataPtr++; + +	if (cmd == 0x80) { +		keyOff(); +		_protect = false; +	} else { +		keyOn(); + +		if (_protect == false || cmd != _frqBlockMSB) +			_flags |= CHS_RECALCFREQ; +	 +		_protect = (para & 0x80) ? true : false; +		_frqBlockMSB = cmd; +	} + +	_ticksLeft = para & 0x7f; + +	if (!(_flags & CHS_SSG)) { + +	} +} + +void TownsPC98_OpnChannelSSG::processFrequency() { +	if (_flags & CHS_RECALCFREQ) { +		uint8 block = (_frqBlockMSB & 0x70) >> 1; +		uint16 bfreq = ((const uint16*)_drv->_opnFreqTable)[_frqBlockMSB & 0x0f]; +		frequency = (bfreq + _frqLSB) | (block << 8); + +		writeReg(_regOffset + 0xa4, (frequency >> 8)); +		writeReg(_regOffset + 0xa0, (frequency & 0xff)); + +		_ptchWhlCurDelay = _ptchWhlInitDelayHi; +		if (_flags & CHS_KEYOFF) { +			_ptchWhlModCurVal = _ptchWhlModInitVal; +			_ptchWhlCurDelay += _ptchWhlInitDelayLo; +		} + +		_ptchWhlDurLeft = (_ptchWhlDuration >> 1); +		_flags &= ~(CHS_KEYOFF | CHS_RECALCFREQ); +	} + +	if (!(_flags & CHS_PITCHWHEELOFF)) { +		if (--_ptchWhlCurDelay) +			return; +		_ptchWhlCurDelay = _ptchWhlInitDelayHi; +		frequency += _ptchWhlModCurVal; + +		writeReg(_regOffset + 0xa4, (frequency >> 8)); +		writeReg(_regOffset + 0xa0, (frequency & 0xff)); + +		if(!--_ptchWhlDurLeft) { +			_ptchWhlDurLeft = _ptchWhlDuration; +			_ptchWhlModCurVal = -_ptchWhlModCurVal; +		} +	} +} + +void TownsPC98_OpnChannelSSG::keyOff() { +	// all operators off +	uint8 value = _keyNum & 0x0f; +	uint8 regAdress = 0x28; +	writeReg(regAdress, value); +	_flags |= CHS_KEYOFF; +} + +void TownsPC98_OpnChannelSSG::keyOn() { +	// all operators on +	uint8 value = _keyNum | 0xf0; +	uint8 regAdress = 0x28; +	writeReg(regAdress, value); +} + +void TownsPC98_OpnChannelSSG::loadData(uint8 *data) { +	_drv->_ssgFlag = (_flags & CHS_SSG) ? -1 : 0; +	opn_SSG_UNK(0); +	TownsPC98_OpnChannel::loadData(data); +	_algorithm = 0x80; +} + +void TownsPC98_OpnChannelSSG::opn_SSG_UNK(uint8 a) { +	_ssg1 = a; +	uint16 h = (_totalLevel + 1) * a; +	if ((h >> 8) == _ssg2) +		return; +	_ssg2 = (h >> 8); +	writeReg(8 + _regOffset, _ssg2); +} + +TownsPC98_OpnDriver::TownsPC98_OpnDriver(Audio::Mixer *mixer, OpnType type) : +	_mixer(mixer), _trackData(0), _playing(false), _fading(false), _channels(0), _ssgChannels(0), +	_looping(0), _opnCarrier(_drvTables + 76), _opnFreqTable(_drvTables + 84), +	_opnFxCmdLen(_drvTables + 36), _opnLvlPresets(_drvTables + (type == OD_TOWNS ? 52 : 220)) , +	_oprRates(0), _oprRateshift(0), _oprAttackDecay(0), _oprFrq(0),	_oprSinTbl(0), _oprLevelOut(0), +	_oprDetune(0), _cbCounter(4), _tickCounter(0), _updateChannelsFlag(type == OD_TYPE26 ? 0x07 : 0x3F), +	_finishedChannelsFlag(0), _samplesTillCallback(0), _samplesTillCallbackRemainder(0), _ready(false), +	_numSSG(type == OD_TOWNS ? 0 : 3), _hasADPCM(type == OD_TYPE86 ? true : false), +	_numChan(type == OD_TYPE26 ? 3 : 6), _hasStereo(type == OD_TYPE26 ? false : true) {	 +	setTempo(84); +	_baserate = (double)getRate() / 10368.0; +} + +TownsPC98_OpnDriver::~TownsPC98_OpnDriver() { +	_mixer->stopHandle(_soundHandle); + +	if (_channels) { +		for (int i = 0; i < _numChan; i++) +			delete _channels[i]; +		delete [] _channels; +	} + +	if (_ssgChannels) { +		for (int i = 0; i < _numSSG; i++) +			delete _ssgChannels[i]; +		delete [] _ssgChannels; +	} + +	delete [] _oprRates; +	delete [] _oprRateshift; +	delete [] _oprFrq; +	delete [] _oprAttackDecay; +	delete [] _oprSinTbl; +	delete [] _oprLevelOut; +	delete [] _oprDetune;	 +} + +bool TownsPC98_OpnDriver::init() { +	if (_ready) { +		reset(); +		return true; +	} + +	generateTables(); + +	if (_channels) { +		for (int i = 0; i < _numChan; i++) { +			if (_channels[i]) +				delete _channels[i]; +		} +		delete [] _channels; +	} +	_channels = new TownsPC98_OpnChannel*[_numChan]; +	for (int i = 0; i < _numChan; i++) { +		int ii = i * 6; +		_channels[i] = new TownsPC98_OpnChannel(this, _drvTables[ii], _drvTables[ii + 1], +			_drvTables[ii + 2],	_drvTables[ii + 3],	_drvTables[ii + 4], _drvTables[ii + 5]); +		_channels[i]->init(); +	} + +	if (_ssgChannels) { +		for (int i = 0; i < _numSSG; i++) { +			if (_ssgChannels[i]) +				delete _ssgChannels[i]; +		} +		delete [] _ssgChannels; +	} +	if (_numSSG) { +		_ssgChannels = new TownsPC98_OpnChannelSSG*[_numSSG]; +		for (int i = 0; i < _numSSG; i++) { +			int ii = i * 6; +			_ssgChannels[i] = new TownsPC98_OpnChannelSSG(this, _drvTables[ii], _drvTables[ii + 1], +				_drvTables[ii + 2],	_drvTables[ii + 3],	_drvTables[ii + 4], _drvTables[ii + 5]); +			_ssgChannels[i]->init(); +		} +	} + +	_mixer->playInputStream(Audio::Mixer::kMusicSoundType, +		&_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, false, true); + +	_ready = true; +	return true; +} + +int inline TownsPC98_OpnDriver::readBuffer(int16 *buffer, const int numSamples) { +	memset(buffer, 0, sizeof(int16) * numSamples); +	int32 samplesLeft = numSamples >> 1; +	while (samplesLeft) { +		if (!_samplesTillCallback) { +			callback(); +			_samplesTillCallback = _samplesPerCallback; +			_samplesTillCallbackRemainder += _samplesPerCallbackRemainder; +			if (_samplesTillCallbackRemainder >= _tempo) { +				_samplesTillCallback++; +				_samplesTillCallbackRemainder -= _tempo; +			} +		} + +		int32 render = MIN(samplesLeft, _samplesTillCallback); +		samplesLeft -= render; +		_samplesTillCallback -= render; + +		nextTick(buffer, render); + +		for (int i = 0; i < render; ++i) { +			buffer[i << 1] <<= 2; +			buffer[(i << 1) + 1] <<= 2; +		} + +		buffer += (render << 1); +	} + +	return numSamples; +} + +void TownsPC98_OpnDriver::loadData(uint8 *data, bool loadPaused) { +	if (!_ready) { +		warning("TownsPC98_OpnDriver: Driver must be initialized before loading data"); +		return; +	} + +	if (!data) { +		warning("TownsPC98_OpnDriver: Invalid music file data"); +		return; +	} + +	lock(); +	_trackData = data; + +	reset(); +	 +	uint8 *src_a = data; + +	for (uint8 i = 0; i < 3; i++) { +		_channels[i]->loadData(data + READ_LE_UINT16(src_a)); +		src_a += 2; +	} + +	for (int i = 0; i < _numSSG; i++) { +		_ssgChannels[i]->loadData(data + READ_LE_UINT16(src_a)); +		src_a += 2; +	} + +	for (uint8 i = 3; i < _numChan; i++) { +		_channels[i]->loadData(data + READ_LE_UINT16(src_a)); +		src_a += 2; +	} + +	if (_hasADPCM) { +		//_adpcmChannel->loadData(data + READ_LE_UINT16(src_a)); +		src_a += 2; +	} + +	_ssgFlag = 0; + +	_patches = src_a + 4; +	_cbCounter = 4; +	_finishedChannelsFlag = 0; + +	// AH 0x17 +	unlock(); +	_playing = (loadPaused ? false : true); +} + +void TownsPC98_OpnDriver::reset() { +	for (int i = 0; i < (_numChan); i++) +		_channels[i]->reset(); +	for (int i = 0; i < (_numSSG); i++) +		_ssgChannels[i]->reset(); + +	_playing = _fading = false; +	_looping = 0; +	_tickCounter = 0; +} + +void TownsPC98_OpnDriver::fadeOut() { +	if (!_playing) +		return; + +	_fading = true; + +	for (int i = 0; i < 20; i++) {		 +		lock(); +		uint32 dTime = _tickCounter + 2; +		for (int j = 0; j < _numChan; j++) { +			if (_updateChannelsFlag & _channels[j]->_idFlag) +				_channels[j]->fadeStep(); +		} +		for (int j = 0; j < _numSSG; j++) +			_ssgChannels[j]->fadeStep(); + +		unlock(); + +		while (_playing) { +			if (_tickCounter >= dTime) +				break; +		} +	} + +	_fading = false; + +	reset(); +} + +void TownsPC98_OpnDriver::callback() { +	if (!_playing || --_cbCounter) +		return; + +	_cbCounter = 4; +	_tickCounter++; + +	lock(); + +	for (int i = 0; i < _numChan; i++) { +		if (_updateChannelsFlag & _channels[i]->_idFlag) { +			_channels[i]->processEvents(); +			_channels[i]->processFrequency(); +		} +	} + +	if (_numSSG) { +		for (int i = 0; i < _numSSG; i++) { +			_ssgChannels[i]->processEvents(); +			_ssgChannels[i]->processFrequency(); +		} +	} +	 +	_ssgFlag = 0; + +	unlock(); + +	if (_finishedChannelsFlag == _updateChannelsFlag) +		reset(); +} + +void TownsPC98_OpnDriver::nextTick(int16 *buffer, uint32 bufferSize) { +	if (!_playing) +		return;	 + +	for (int i = 0; i < _numChan ; i++) { +		if (_channels[i]->_updateEnvelopes) { +			_channels[i]->_updateEnvelopes = false; +			_channels[i]->updateEnv(); +		} +		 +		for (uint32 ii = 0; ii < bufferSize ; ii++) +			_channels[i]->generateOutput(buffer[ii * 2], +			buffer[ii * 2 + 1],	&_channels[i]->_feedbuf[2], _channels[i]->_feedbuf); +	} + +	for (int i = 0; i < _numSSG ; i++) { +		if (_ssgChannels[i]->_updateEnvelopes) { +			_ssgChannels[i]->_updateEnvelopes = false; +			_ssgChannels[i]->updateEnv(); +		} +		 +		for (uint32 ii = 0; ii < bufferSize ; ii++) +			_ssgChannels[i]->generateOutput(buffer[ii * 2], +			buffer[ii * 2 + 1],	&_ssgChannels[i]->_feedbuf[2], _ssgChannels[i]->_feedbuf); +	} +} + +void TownsPC98_OpnDriver::generateTables() { +	delete [] _oprRates; +	_oprRates = new uint8[128]; +	memset(_oprRates, 0x90, 32); +	uint8 *dst = (uint8*) _oprRates + 32; +	for (int i = 0; i < 48; i += 4) +		WRITE_BE_UINT32(dst + i, 0x00081018); +	dst += 48; +	for (uint8 i = 0; i < 16; i ++) { +		uint8 v = (i < 12) ? i : 12; +		*dst++ = ((4 + v) << 3); +	} +	memset(dst, 0x80, 32); + +	delete [] _oprRateshift; +	_oprRateshift = new uint8[128]; +	memset(_oprRateshift, 0, 128); +	dst = (uint8*) _oprRateshift + 32; +	for (int i = 11; i; i--) { +		memset(dst, i, 4); +		dst += 4; +	} + +	delete [] _oprFrq; +	_oprFrq = new uint32[0x1000]; +	for (uint32 i = 0; i < 0x1000; i++) +		_oprFrq[i] = (uint32)(_baserate * (double)(i << 11)); + +	delete [] _oprAttackDecay; +	_oprAttackDecay = new uint8[152]; +	memset(_oprAttackDecay, 0, 152); +	for (int i = 0; i < 36; i++) +		WRITE_BE_UINT32(_oprAttackDecay + (i << 2), _adtStat[i]); + +	delete [] _oprSinTbl; +	_oprSinTbl = new uint32[1024]; +	for (int i = 0; i < 1024; i++) { +		double val = sin((double) (((i << 1) + 1) * PI / 1024.0)); +		double d_dcb = log(1.0 / (double)ABS(val)) / log(2.0) * 256.0; +		int32 i_dcb = (int32)(2.0 * d_dcb); +		i_dcb = (i_dcb & 1) ? (i_dcb >> 1) + 1 : (i_dcb >> 1); +		_oprSinTbl[i] = (i_dcb << 1) + (val >= 0.0 ? 0 : 1); +	} + +	delete [] _oprLevelOut; +	_oprLevelOut = new int32[0x1a00]; +	for (int i = 0; i < 256; i++) { +		double val = floor(65536.0 / pow(2.0, 0.00390625 * (double)(1 + i))); +		int32 val_int = ((int32) val) >> 4; +		_oprLevelOut[i << 1] = (val_int & 1) ? ((val_int >> 1) + 1) << 2 : (val_int >> 1) << 2; +		_oprLevelOut[(i << 1) + 1] = -_oprLevelOut[i << 1]; +		for (int ii = 1; ii < 13; ii++) { +			_oprLevelOut[(i << 1) + (ii << 9)] =  _oprLevelOut[i << 1] >> ii; +			_oprLevelOut[(i << 1) + (ii << 9) + 1] = -_oprLevelOut[(i << 1) + (ii << 9)]; +		} +	} + +	uint8 *dtt = new uint8[128]; +	memset(dtt, 0, 36); +	memset(dtt + 36, 1, 8); +	memcpy(dtt + 44, _drvTables + 144, 84); + +	delete [] _oprDetune; +	_oprDetune = new int32[256]; +	for (int i = 0; i < 128; i++) { +		_oprDetune[i] = (int32)	((double)dtt[i] * _baserate * 64.0); +		_oprDetune[i + 128] = -_oprDetune[i]; +	} + +	delete [] dtt; +} + +void TownsPC98_OpnDriver::setTempo(uint8 tempo) { +	_tempo = tempo; +	_samplesPerCallback = getRate() / _tempo; +	_samplesPerCallbackRemainder = getRate() % _tempo; +} + +const uint8 TownsPC98_OpnDriver::_drvTables[] = { +	//	channel presets +	0x00, 0x80, 0x00, 0x00, 0x00, 0x01, +	0x01, 0x80, 0x01, 0x01, 0x00, 0x02, +	0x02, 0x80, 0x02, 0x02, 0x00, 0x04, +	0x00, 0x80, 0x03, 0x04, 0x01, 0x08, +	0x01, 0x80, 0x04, 0x05, 0x01, 0x10, +	0x02, 0x80, 0x05, 0x06, 0x01, 0x20, + +	//	control event size +	0x01, 0x01, 0x01, 0x01,	0x01, 0x01, 0x04, 0x05, +	0x02, 0x06, 0x02, 0x00, 0x00, 0x02, 0x00, 0x02, + +	//	fmt level presets  +	0x54, 0x50,	0x4C, 0x48,	0x44, 0x40, 0x3C, 0x38, +	0x34, 0x30, 0x2C, 0x28, 0x24, 0x20, 0x1C, 0x18, +	0x14, 0x10, 0x0C, 0x08,	0x04, 0x90, 0x90, 0x90, +	 +	//	carriers +	0x08, 0x08, 0x08, 0x08,	0x0C, 0x0E, 0x0E, 0x0F, + +	//	frequencies +	0x6A, 0x02, 0x8F, 0x02, 0xB6, 0x02,	0xDF, 0x02, +	0x0B, 0x03, 0x39, 0x03, 0x6A, 0x03, 0x9E, 0x03, +	0xD5, 0x03,	0x10, 0x04, 0x4E, 0x04, 0x8F, 0x04, +	0x00, 0x00, 0x00, 0x00, + +	//	unused +	0x01, 0x00,	0x00, 0x00, 0x03, 0x00, 0x00, 0x00, +	0x02, 0x00,	0x00, 0x00,	0x05, 0x00, 0x00, 0x00, +	0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, +	0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + +	//	detune +	0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, +	0x04, 0x04, 0x04, 0x05, 0x05, 0x06, 0x06, 0x07, +	0x08, 0x08, 0x08, 0x08, 0x01, 0x01,	0x01, 0x01, +	0x02, 0x02, 0x02, 0x02, 0x02, 0x03,	0x03, 0x03, +	0x04, 0x04, 0x04, 0x05, 0x05, 0x06, 0x06, 0x07, +	0x08, 0x08, 0x09, 0x0a,	0x0b, 0x0c, 0x0d, 0x0e, +	0x10, 0x10, 0x10, 0x10,	0x02, 0x02, 0x02, 0x02, +	0x02, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x05, +	0x05, 0x06,	0x06, 0x07, 0x08, 0x08, 0x09, 0x0a, +	0x0b, 0x0c,	0x0d, 0x0e, 0x10, 0x11, 0x13, 0x14, +	0x16, 0x16, 0x16, 0x16, + +	//	pc98 level presets  +	0x40, 0x3B, 0x38, 0x34, 0x30, 0x2A, 0x28, 0x25, +	0x22, 0x20, 0x1D, 0x1A, 0x18, 0x15, 0x12, 0x10, +	0x0D, 0x0A, 0x08, 0x05, 0x02, 0x90, 0x90, 0x90 +}; + +const uint32 TownsPC98_OpnDriver::_adtStat[] = { +	0x00010001, 0x00010001,	0x00010001, 0x01010001, +	0x00010101, 0x00010101, 0x00010101, 0x01010101, +	0x01010101, 0x01010101, 0x01010102, 0x01010102, +	0x01020102, 0x01020102, 0x01020202, 0x01020202, +	0x02020202, 0x02020202, 0x02020204, 0x02020204, +	0x02040204, 0x02040204, 0x02040404, 0x02040404, +	0x04040404, 0x04040404, 0x04040408, 0x04040408, +	0x04080408, 0x04080408, 0x04080808, 0x04080808, +	0x08080808, 0x08080808, 0x10101010, 0x10101010 +}; +  SoundTowns::SoundTowns(KyraEngine_v1 *vm, Audio::Mixer *mixer)  	: Sound(vm, mixer), _lastTrack(-1), _currentSFX(0), _sfxFileData(0),  	_sfxFileIndex((uint)-1), _sfxWDTable(0), _sfxBTTable(0), _parser(0) { -	_driver = new SoundTowns_EuphonyDriver(_mixer); +	_driver = new Towns_EuphonyDriver(_mixer);  	int ret = open();  	if (ret != MERR_ALREADY_OPEN && ret != 0)  		error("couldn't open midi driver"); @@ -1124,7 +2797,7 @@ void SoundTowns::playTrack(uint8 track) {  		return;  	track -= 2; -	const int32 * const tTable = (const int32 * const) cdaData(); +	const int32 *const tTable = (const int32 *const) cdaData();  	int tTableIndex = 3 * track;  	int trackNum = (int) READ_LE_UINT32(&tTable[tTableIndex + 2]); @@ -1195,12 +2868,12 @@ void SoundTowns::playSoundEffect(uint8 track) {  		}  	} -	uint8 * fileBody = _sfxFileData + 0x01b8; +	uint8 *fileBody = _sfxFileData + 0x01b8;  	int32 offset = (int32)READ_LE_UINT32(_sfxFileData + (track - 0x0b) * 4);  	if (offset == -1)  		return; -	uint32 * sfxHeader = (uint32*)(fileBody + offset); +	uint32 *sfxHeader = (uint32*)(fileBody + offset);  	uint32 sfxHeaderID = READ_LE_UINT32(sfxHeader);  	uint32 sfxHeaderInBufferSize = READ_LE_UINT32(&sfxHeader[1]); @@ -1220,7 +2893,7 @@ void SoundTowns::playSoundEffect(uint8 track) {  	} else if (sfxHeaderID == 1) {  		Screen::decodeFrame4(sfxBody, sfxPlaybackBuffer, playbackBufferSize);  	} else if (_sfxWDTable) { -		uint8 * tgt = sfxPlaybackBuffer; +		uint8 *tgt = sfxPlaybackBuffer;  		uint32 sfx_BtTable_Offset = 0;  		uint32 sfx_WdTable_Offset = 0;  		uint32 sfx_WdTable_Number = 5; @@ -1285,7 +2958,7 @@ uint32 SoundTowns::getBaseTempo(void) {  }  bool SoundTowns::loadInstruments() { -	uint8 * twm = _vm->resource()->fileData("twmusic.pak", 0); +	uint8 *twm = _vm->resource()->fileData("twmusic.pak", 0);  	if (!twm)  		return false;  	_driver->queue()->loadDataToCurrentPosition(twm, 0x8BF0); @@ -1300,11 +2973,11 @@ bool SoundTowns::loadInstruments() {  }  void SoundTowns::playEuphonyTrack(uint32 offset, int loop) { -	uint8 * twm = _vm->resource()->fileData("twmusic.pak", 0); +	uint8 *twm = _vm->resource()->fileData("twmusic.pak", 0);  	Common::StackLock lock(_mutex);  	if (!_parser) { -		_parser = new MidiParser_EuD(_driver->queue()); +		_parser = new Towns_EuphonyParser(_driver->queue());  		_parser->setMidiDriver(this);  		_parser->setTimerRate(getBaseTempo());  	} @@ -1315,7 +2988,7 @@ void SoundTowns::playEuphonyTrack(uint32 offset, int loop) {  	delete[] twm;  } -void SoundTowns::onTimer(void * data) { +void SoundTowns::onTimer(void *data) {  	SoundTowns *music = (SoundTowns *)data;  	Common::StackLock lock(music->_mutex);  	if (music->_parser) @@ -1356,22 +3029,75 @@ float SoundTowns::semitoneAndSampleRate_to_sampleStep(int8 semiTone, int8 semiTo  	return (float) sampleRate * 10.0f * rateshift / outputRate;  } +SoundPC98::SoundPC98(KyraEngine_v1 *vm, Audio::Mixer *mixer) : +	Sound(vm, mixer), _musicTrackData(0), _sfxTrackData(0), _lastTrack(-1), _driver(0) { +} + +SoundPC98::~SoundPC98() { +	delete[] _musicTrackData; +	delete[] _sfxTrackData; +	delete _driver; +} + +bool SoundPC98::init() { +	_driver = new TownsPC98_OpnDriver(_mixer, TownsPC98_OpnDriver::OD_TYPE26); +	_sfxTrackData = _vm->resource()->fileData("se.dat", 0); +	if (!_sfxTrackData) +		return false; +	return _driver->init(); +} + +void SoundPC98::playTrack(uint8 track) { +	if (--track >= 56) +		track -= 55; +  +	if (track == _lastTrack && _musicEnabled) +		return; + +	haltTrack(); + +	char musicfile[13]; +	sprintf(musicfile, fileListEntry(0), track); +	delete[] _musicTrackData; +	_musicTrackData = _vm->resource()->fileData(musicfile, 0); +	if (_musicEnabled) +		_driver->loadData(_musicTrackData); + +	_lastTrack = track; +} + +void SoundPC98::haltTrack() { +	_lastTrack = -1; +	AudioCD.stop(); +	AudioCD.updateCD(); +	_driver->reset(); +} + +void SoundPC98::beginFadeOut() { +	_driver->fadeOut(); +	haltTrack(); +} + +void SoundPC98::playSoundEffect(uint8) { +	/// TODO /// +} + +  //	KYRA 2 -SoundTowns_v2::SoundTowns_v2(KyraEngine_v1 *vm, Audio::Mixer *mixer) -	: Sound(vm, mixer), _lastTrack(-1), _currentSFX(0), /*_driver(0),*/ -	 _twnTrackData(0) { +SoundTownsPC98_v2::SoundTownsPC98_v2(KyraEngine_v1 *vm, Audio::Mixer *mixer) : +	Sound(vm, mixer), _currentSFX(0), _musicTrackData(0), _lastTrack(-1), _driver(0), _useFmSfx(false) {  } -SoundTowns_v2::~SoundTowns_v2() { -	/*if (_driver) -		delete _driver;*/ -	if (_twnTrackData) -		delete[] _twnTrackData; +SoundTownsPC98_v2::~SoundTownsPC98_v2() { +	delete[] _musicTrackData; +	delete _driver;  } -bool SoundTowns_v2::init() { -	//_driver = new SoundTowns_v2_TwnDriver(_mixer); +bool SoundTownsPC98_v2::init() { +	_driver = new TownsPC98_OpnDriver(_mixer, /*_vm->gameFlags().platform == Common::kPlatformPC98 ? +		TownsPC98_OpnDriver::OD_TYPE86 :*/ TownsPC98_OpnDriver::OD_TOWNS); +	_useFmSfx = _vm->gameFlags().platform == Common::kPlatformPC98 ? true : false;  	_vm->checkCD();  	// FIXME: While checking for 'track1.XXX(X)' looks like  	// a good idea, we should definitely not be doing this @@ -1384,55 +3110,61 @@ bool SoundTowns_v2::init() {  		(Common::File::exists("track1.mp3") || Common::File::exists("track1.ogg") ||  		 Common::File::exists("track1.flac") || Common::File::exists("track1.fla")))  			_musicEnabled = 2; -	return true;//_driver->init(); +	return _driver->init();  } -void SoundTowns_v2::process() { +void SoundTownsPC98_v2::process() {  	AudioCD.updateCD();  } -void SoundTowns_v2::playTrack(uint8 track) { +void SoundTownsPC98_v2::playTrack(uint8 track) {  	if (track == _lastTrack && _musicEnabled)  		return; -	const uint16 * const cdaTracks = (const uint16 * const) cdaData(); +	const uint16 *const cdaTracks = (const uint16 *const) cdaData();  	int trackNum = -1; -	for (int i = 0; i < cdaTrackNum(); i++) { -		if (track == (uint8) READ_LE_UINT16(&cdaTracks[i * 2])) { -			trackNum = (int) READ_LE_UINT16(&cdaTracks[i * 2 + 1]) - 1; -			break; +	if (_vm->gameFlags().platform == Common::kPlatformFMTowns) { +		for (int i = 0; i < cdaTrackNum(); i++) { +			if (track == (uint8) READ_LE_UINT16(&cdaTracks[i * 2])) { +				trackNum = (int) READ_LE_UINT16(&cdaTracks[i * 2 + 1]) - 1; +				break; +			}  		}  	} -	haltTrack(); +	beginFadeOut(); -	// TODO: figure out when to loop and when not for CD Audio -	bool loop = false; +	char musicfile[13]; +	sprintf(musicfile, fileListEntry(0), track); +	delete[] _musicTrackData; +	 +	_musicTrackData = _vm->resource()->fileData(musicfile, 0); +	_driver->loadData(_musicTrackData, true);  	if (_musicEnabled == 2 && trackNum != -1) { -		AudioCD.play(trackNum+1, loop ? -1 : 1, 0, 0); +		AudioCD.play(trackNum+1, _driver->looping() ? -1 : 1, 0, 0);  		AudioCD.updateCD();  	} else if (_musicEnabled) { -		char musicfile[13]; -		sprintf(musicfile, fileListEntry(0), track); -		if (_twnTrackData) -			delete[] _twnTrackData; -		_twnTrackData = _vm->resource()->fileData(musicfile, 0); -		//_driver->loadData(_twnTrackData); +		_driver->cont();  	}  	_lastTrack = track;  } -void SoundTowns_v2::haltTrack() { +void SoundTownsPC98_v2::haltTrack() {  	_lastTrack = -1;  	AudioCD.stop();  	AudioCD.updateCD(); -	//_driver->reset(); +	_driver->reset();  } -int32 SoundTowns_v2::voicePlay(const char *file, bool) { +void SoundTownsPC98_v2::beginFadeOut() { +	_driver->fadeOut(); +	haltTrack(); +} + +int32 SoundTownsPC98_v2::voicePlay(const char *file, bool) {  	static const uint16 rates[] =	{ 0x10E1, 0x0CA9, 0x0870, 0x0654, 0x0438, 0x032A, 0x021C, 0x0194 };  	int h = 0; @@ -1443,11 +3175,11 @@ int32 SoundTowns_v2::voicePlay(const char *file, bool) {  			return 0;  	} -	char filename [13]; +	char filename[13];  	sprintf(filename, "%s.PCM", file); -	uint8 * data = _vm->resource()->fileData(filename, 0); -	uint8 * src = data; +	uint8 *data = _vm->resource()->fileData(filename, 0); +	uint8 *src = data;  	uint16 sfxRate = rates[READ_LE_UINT16(src)];  	src += 2; @@ -1500,9 +3232,16 @@ int32 SoundTowns_v2::voicePlay(const char *file, bool) {  	return 1;  } -void SoundTowns_v2::beginFadeOut() { -	//_driver->fadeOut(); -	haltTrack(); +void SoundTownsPC98_v2::playSoundEffect(uint8 track) { +	if (!_useFmSfx) +		return; + +	uint8 *sd = _vm->resource()->fileData("sound.dat", 0); + + +	//TODO + +	delete [] sd;  }  } // end of namespace Kyra diff --git a/engines/kyra/staticres.cpp b/engines/kyra/staticres.cpp index abdf115c1e..9a4b40902e 100644 --- a/engines/kyra/staticres.cpp +++ b/engines/kyra/staticres.cpp @@ -23,16 +23,17 @@   *   */ -  #include "common/endian.h"  #include "common/md5.h"  #include "kyra/kyra_v1.h"  #include "kyra/kyra_lok.h" +#include "kyra/lol.h"  #include "kyra/kyra_v2.h"  #include "kyra/kyra_hof.h"  #include "kyra/kyra_mr.h"  #include "kyra/screen.h"  #include "kyra/screen_lok.h" +#include "kyra/screen_lol.h"  #include "kyra/screen_hof.h"  #include "kyra/screen_mr.h"  #include "kyra/resource.h" @@ -287,8 +288,10 @@ bool StaticResource::init() {  	} else if (_vm->game() == GI_KYRA3) {  		_builtIn = 0;  		_filenameTable = kyra3StaticRes; +	} else if (_vm->game() == GI_LOL) { +		return true;  	} else { -		error("unknown game ID"); +		error("StaticResource: Unknown game ID");  	}  	char errorBuffer[100]; @@ -1034,6 +1037,11 @@ void KyraEngine_LoK::initStaticResource() {  	}  	// audio data tables +#if 0 +	static const char *tIntro98[] = { "intro%d.dat" }; +	static const char *tIngame98[] = { "kyram%d.dat" }; +#endif +  	static const AudioDataStruct soundData_PC[] = {  		{ _soundFilesIntro, _soundFilesIntroSize, 0, 0 },  		{ _soundFiles, _soundFilesSize, 0, 0 }, @@ -1045,7 +1053,22 @@ void KyraEngine_LoK::initStaticResource() {  		{ _soundFiles, _soundFilesSize, _cdaTrackTable, _cdaTrackTableSize },  		{ 0, 0, 0, 0}  	}; -	_soundData = (_flags.platform == Common::kPlatformPC) ? soundData_PC : soundData_TOWNS; + +#if 0 +	static const AudioDataStruct soundData_PC98[] = { +		{ tIntro98, 1, 0, 0 }, +		{ tIngame98, 1, 0, 0 }, +		{ 0, 0, 0, 0} +	}; +#endif + +	if (_flags.platform == Common::kPlatformPC) +		_soundData = soundData_PC; +	else if (_flags.platform == Common::kPlatformFMTowns) +		_soundData = soundData_TOWNS; +	else if (_flags.platform == Common::kPlatformPC98) +		_soundData = soundData_TOWNS/*soundData_PC98*/; +  }  void KyraEngine_LoK::loadMouseShapes() { @@ -1243,6 +1266,12 @@ void KyraEngine_HoF::initStaticResource() {  	static const char *fmtMusicFileListFinale[] = { "finale%d.twn" };  	static const char *fmtMusicFileListIngame[] = { "km%02d.twn" }; +#if 0 +	static const char *pc98MusicFileListIntro[] = { "intro%d.86" }; +	static const char *pc98MusicFileListFinale[] = { "finale%d.86" }; +	static const char *pc98MusicFileListIngame[] = { "km%02d.86" }; +#endif +  	static const AudioDataStruct soundData_PC[] = {  		{ _musicFileListIntro, _musicFileListIntroSize, 0, 0 },  		{ _musicFileListIngame, _musicFileListIngameSize, 0, 0}, @@ -1254,7 +1283,21 @@ void KyraEngine_HoF::initStaticResource() {  		{ fmtMusicFileListIngame, 1, _cdaTrackTableIngame, _cdaTrackTableIngameSize >> 1 },  		{ fmtMusicFileListFinale, 1, _cdaTrackTableFinale, _cdaTrackTableFinaleSize >> 1 }  	}; -	_soundData = (_flags.platform == Common::kPlatformPC) ? soundData_PC : soundData_TOWNS; + +#if 0 +	static const AudioDataStruct soundData_PC98[] = { +		{ pc98MusicFileListIntro, 1, 0, 0 }, +		{ pc98MusicFileListIngame, 1, 0, 0 }, +		{ pc98MusicFileListFinale, 1, 0, 0 }		 +	}; +#endif + +	if (_flags.platform == Common::kPlatformPC) +		_soundData = soundData_PC; +	else if (_flags.platform == Common::kPlatformFMTowns) +		_soundData = soundData_TOWNS; +	else if (_flags.platform == Common::kPlatformPC98) +		_soundData = soundData_TOWNS/*soundData_PC98*/;  	// setup sequence data  	_sequences = _staticres->loadHofSequenceData(k2SeqplaySeqData, tmpSize); @@ -1944,12 +1987,26 @@ const char *KyraEngine_MR::_languageExtension[] = {  	"TRE",  	"TRF",  	"TRG"/*, -	"TRI",		Italian and Spanish were never included -	"TRS"*/ +	"TRI",		Italian and Spanish were never included, the supported fan translations are using +	"TRS"		English/French extensions thus overwriting these languages */		  };  const int KyraEngine_MR::_languageExtensionSize = ARRAYSIZE(KyraEngine_MR::_languageExtension); +const char * const KyraEngine_MR::_mainMenuSpanishFan[] = { +	"Nueva Partida", +	"Ver Intro", +	"Restaurar", +	"Finalizar" +}; + +const char * const KyraEngine_MR::_mainMenuItalianFan[] = { +	"Nuova Partita", +	"Introduzione", +	"Carica una partita", +	"Esci dal gioco" +}; +  const KyraEngine_MR::ShapeDesc KyraEngine_MR::_shapeDescs[] = {  	{ 57, 91, -31, -82 },  	{ 57, 91, -31, -82 }, @@ -2182,5 +2239,105 @@ const int8 KyraEngine_MR::_albumWSAY[] = {  	-1, -2, 2, 2, -6, -6, -6, 0  }; +// lands of lore static res + +const ScreenDim Screen_LoL::_screenDimTable[] = { +	{ 0x00, 0x00, 0x28, 0xC8, 0xC7, 0xCF, 0x00, 0x00 } +}; + +const int Screen_LoL::_screenDimTableCount = ARRAYSIZE(Screen_LoL::_screenDimTable); + +const char * const LoLEngine::_languageExt[] = { +	"ENG", +	"FRE", +	"GER" +}; + +const LoLEngine::CharacterPrev LoLEngine::_charPreviews[] = { +	{ "Ak\'shel", 0x060, 0x7F, { 0x0F, 0x08, 0x05 } }, +	{  "Michael", 0x09A, 0x7F, { 0x06, 0x0A, 0x0F } }, +	{   "Kieran", 0x0D4, 0x7F, { 0x08, 0x06, 0x08 } }, +	{   "Conrad", 0x10F, 0x7F, { 0x0A, 0x0C, 0x0A } } +}; + +const uint8 LoLEngine::_chargenFrameTable[] = { +	0x00, 0x01, 0x02, 0x03, 0x04, +	0x05, 0x04, 0x03, 0x02, 0x01, +	0x00, 0x00, 0x01, 0x02, 0x03, +	0x04, 0x05, 0x06, 0x07, 0x08, +	0x09, 0x0A, 0x0B, 0x0C, 0x0D, +	0x0E, 0x0F, 0x10, 0x11, 0x12 +}; + +const uint16 LoLEngine::_selectionPosTable[] = { +	0x6F, 0x00, 0x8F, 0x00, 0xAF, 0x00,  0xCF, 0x00, +	0xEF, 0x00, 0x6F, 0x20, 0x8F, 0x20,  0xAF, 0x20, +	0xCF, 0x20, 0xEF, 0x20, 0x6F, 0x40,  0x8F, 0x40, +	0xAF, 0x40, 0xCF, 0x40, 0xEF, 0x40, 0x10F, 0x00 +}; + +const uint8 LoLEngine::_selectionChar1IdxTable[] = { +	0, 0, 5, 5, 5, 5, 5, 5, +	5, 5, 5, 0, 0, 5, 5, 5, +	5, 5, 5, 5, 0, 0, 5, 5, +	5, 5, 5 +}; + +const uint8 LoLEngine::_selectionChar2IdxTable[] = { +	1, 1, 6, 6, 1, 1, 6, 6, +	6, 6, 6, 6, 6, 1, 1, 6, +	6, 6, 1, 1, 6, 6, 6, 6, +	6, 6, 6 +}; + +const uint8 LoLEngine::_selectionChar3IdxTable[] = { +	2, 2, 7, 7, 7, 7, 2, 2, +	7, 7, 7, 7, 7, 7, 7, 2, +	2, 7, 7, 7, 7, 2, 2, 7, +	7, 7, 7 +}; + +const uint8 LoLEngine::_selectionChar4IdxTable[] = { +	3, 3, 8, 8, 8, 8, 3, 3, +	8, 8, 3, 3, 8, 8, 8, 8, +	8, 8, 8, 8, 8, 3, 3, 8, +	8, 8, 8 +}; + +const uint8 LoLEngine::_reminderChar1IdxTable[] = { +	4, 4, 4, 5, 5, 5, 5, 5, +	5, 5, 5, 5, 5, 5, 5, 5, +	5 +}; + +const uint8 LoLEngine::_reminderChar2IdxTable[] = { +	9, 9, 9, 6, 6, 6, 6, 6, +	6, 6, 6, 6, 6, 6, 6, 6, +	6 +}; + +const uint8 LoLEngine::_reminderChar3IdxTable[] = { +	0xE, 0xE, 0xE, 0x7, 0x7, 0x7, 0x7, 0x7, +	0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, +	0x7 +}; + +const uint8 LoLEngine::_reminderChar4IdxTable[] = { +	0xF, 0xF, 0xF, 0x8, 0x8, 0x8, 0x8, 0x8, +	0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, +	0x8 +}; + +const uint8 LoLEngine::_selectionAnimIndexTable[] = { +	0, 5, 1, 6, 2, 7, 3, 8 +}; + +const uint8 LoLEngine::_charInfoFrameTable[] = { +	0x0, 0x7, 0x8, 0x9, 0xA, 0xB, 0xA, 0x9, +	0x8, 0x7, 0x0, 0x0, 0x7, 0x8, 0x9, 0xA, +	0xB, 0xA, 0x9, 0x8, 0x7, 0x0, 0x0, 0x7, +	0x8, 0x9, 0xA, 0xB, 0xA, 0x9, 0x8, 0x7 +}; +  } // End of namespace Kyra diff --git a/engines/kyra/wsamovie.h b/engines/kyra/wsamovie.h index 36cd75b1ab..1db8ee8474 100644 --- a/engines/kyra/wsamovie.h +++ b/engines/kyra/wsamovie.h @@ -109,7 +109,7 @@ private:  class WSAMovie_v2 : public WSAMovie_v1 {  public: -	WSAMovie_v2(KyraEngine_v1 *vm, Screen_v2 *scren); +	WSAMovie_v2(KyraEngine_v1 *vm, Screen_v2 *screen);  	int open(const char *filename, int unk1, uint8 *palette); diff --git a/engines/lure/lure.cpp b/engines/lure/lure.cpp index 06d3b1984e..ea760ddb4f 100644 --- a/engines/lure/lure.cpp +++ b/engines/lure/lure.cpp @@ -103,6 +103,7 @@ LureEngine::~LureEngine() {  	if (_initialised) {  		// Delete and deinitialise subsystems  		Surface::deinitialise(); +		Sound.destroy();  		delete _fights;  		delete _room;  		delete _menu; @@ -164,10 +165,6 @@ void LureEngine::pauseEngineIntern(bool pause) {  	}  } -void LureEngine::quitGame() { -	_system->quit(); -} -  const char *LureEngine::generateSaveName(int slotNumber) {  	static char buffer[15]; diff --git a/engines/lure/lure.h b/engines/lure/lure.h index d66f446247..1c5b40e54b 100644 --- a/engines/lure/lure.h +++ b/engines/lure/lure.h @@ -70,7 +70,6 @@ public:  	virtual int init();  	virtual int go();  	virtual void pauseEngineIntern(bool pause); -	void quitGame();  	Disk &disk() { return *_disk; } diff --git a/engines/lure/luredefs.h b/engines/lure/luredefs.h index 603102a099..922e1207d0 100644 --- a/engines/lure/luredefs.h +++ b/engines/lure/luredefs.h @@ -36,7 +36,7 @@ namespace Lure {  #define LURE_DAT_MAJOR 1  #define LURE_DAT_MINOR 29  #define LURE_MIN_SAVEGAME_MINOR 25 -#define LURE_SAVEGAME_MINOR 32 +#define LURE_SAVEGAME_MINOR 33  #define LURE_DEBUG 1 diff --git a/engines/lure/menu.cpp b/engines/lure/menu.cpp index cecc415499..0b4ef06081 100644 --- a/engines/lure/menu.cpp +++ b/engines/lure/menu.cpp @@ -57,6 +57,11 @@ MenuRecord::MenuRecord(const MenuRecordBounds *bounds, int numParams, ...) {  	_width = (bounds->contentsWidth + 3) << 3;  } +MenuRecord::~MenuRecord() { +	free(_entries); +	_entries = NULL; +} +  const char *MenuRecord::getEntry(uint8 index) {  	if (index >= _numEntries) error("Invalid menuitem index specified: %d", index);  	return _entries[index]; diff --git a/engines/lure/menu.h b/engines/lure/menu.h index b5b7769e34..fcc6308375 100644 --- a/engines/lure/menu.h +++ b/engines/lure/menu.h @@ -56,6 +56,7 @@ private:  	uint8 _numEntries;  public:  	MenuRecord(const MenuRecordBounds *bounds, int numParams, ...); +	~MenuRecord();  	uint16 xstart() { return _xstart; }  	uint16 width() { return _width; } diff --git a/engines/lure/palette.cpp b/engines/lure/palette.cpp index 03161032c0..badc3c96b0 100644 --- a/engines/lure/palette.cpp +++ b/engines/lure/palette.cpp @@ -106,6 +106,12 @@ Palette::Palette(uint16 resourceId, PaletteSource paletteSource) {  	delete srcData;  } +// Destructor + +Palette::~Palette() { +	delete _palette; +} +  void Palette::convertRgb64Palette(const byte *srcPalette, uint16 srcNumEntries) {  	byte *pDest = _palette->data();  	const byte *pSrc = srcPalette; diff --git a/engines/lure/palette.h b/engines/lure/palette.h index 1481e22775..9420079346 100644 --- a/engines/lure/palette.h +++ b/engines/lure/palette.h @@ -46,6 +46,7 @@ public:  	Palette(uint16 srcNumEntries, const byte *srcData, PaletteSource paletteSource);  	Palette(Palette &src);  	Palette(uint16 resourceId, PaletteSource paletteSource = DEFAULT); +	~Palette();  	uint8 *data() { return _palette->data(); }  	MemoryBlock *palette() { return _palette; } diff --git a/engines/lure/res.cpp b/engines/lure/res.cpp index f2997d5d17..68de260061 100644 --- a/engines/lure/res.cpp +++ b/engines/lure/res.cpp @@ -349,6 +349,7 @@ void Resources::reloadData() {  		_indexedRoomExitHospots.push_back(RoomExitIndexedHotspotList::value_type(new RoomExitIndexedHotspotData(indexedRec)));  		indexedRec++;  	} +	delete mb;  	// Initialise delay list  	_delayList.clear(true); diff --git a/engines/lure/res_struct.cpp b/engines/lure/res_struct.cpp index de09f982d1..92cea948f9 100644 --- a/engines/lure/res_struct.cpp +++ b/engines/lure/res_struct.cpp @@ -456,6 +456,8 @@ void HotspotData::saveToStream(WriteStream *stream) {  	stream->writeSint16LE(startY);  	stream->writeUint16LE(roomNumber);  	stream->writeByte(layer); +	stream->writeUint16LE(walkX); +	stream->writeUint16LE(walkY);  	stream->writeUint16LE(width);  	stream->writeUint16LE(height); @@ -503,6 +505,10 @@ void HotspotData::loadFromStream(ReadStream *stream) {  	uint8 saveVersion = LureEngine::getReference().saveVersion();  	if (saveVersion >= 29)  		layer = stream->readByte(); +	if (saveVersion >= 33) { +		walkX = stream->readUint16LE(); +		walkY = stream->readUint16LE(); +	}  	width = stream->readUint16LE();  	height = stream->readUint16LE(); diff --git a/engines/lure/sound.cpp b/engines/lure/sound.cpp index 839298d1c5..285f66e4e2 100644 --- a/engines/lure/sound.cpp +++ b/engines/lure/sound.cpp @@ -85,8 +85,10 @@ SoundManager::~SoundManager() {  	if (_soundData)  		delete _soundData; -	if (_driver) +	if (_driver) {  		_driver->close(); +		delete _driver; +	}  	_driver = NULL;  	g_system->deleteMutex(_soundMutex); @@ -143,7 +145,7 @@ void SoundManager::bellsBodge() {  	Room &room = Room::getReference();  	RoomData *roomData = res.getRoom(room.roomNumber()); -	if (roomData->areaFlag != res.fieldList().getField(AREA_FLAG)) { +	if (roomData && roomData->areaFlag != res.fieldList().getField(AREA_FLAG)) {  		res.fieldList().setField(AREA_FLAG, roomData->areaFlag);  		switch (roomData->areaFlag) { diff --git a/engines/m4/assets.cpp b/engines/m4/assets.cpp index 80b21119ff..0488f17d8f 100644 --- a/engines/m4/assets.cpp +++ b/engines/m4/assets.cpp @@ -201,6 +201,7 @@ void SpriteAsset::loadMadsSpriteAsset(M4Engine *vm, Common::SeekableReadStream*  	Common::SeekableReadStream *spriteDataStream = sprite.getItemStream(3);  	SpriteAssetFrame frame;  	for (curFrame = 0; curFrame < _frameCount; curFrame++) { +		frame.stream = 0;  		frame.comp = 0;  		frameOffset = spriteStream->readUint32LE();  		_frameOffsets.push_back(frameOffset); diff --git a/engines/m4/converse.cpp b/engines/m4/converse.cpp index 024cd591f5..5b8bdab9d6 100644 --- a/engines/m4/converse.cpp +++ b/engines/m4/converse.cpp @@ -153,7 +153,7 @@ void ConversationView::setNode(int32 nodeIndex) {  void ConversationView::onRefresh(RectList *rects, M4Surface *destSurface) {  	//if (!this->isVisible())  	//	return; -	empty(); +	clear();  	if (_entriesShown) {  		// Write out the conversation options diff --git a/engines/m4/globals.cpp b/engines/m4/globals.cpp index 12d9a24d37..58c68979d1 100644 --- a/engines/m4/globals.cpp +++ b/engines/m4/globals.cpp @@ -75,7 +75,7 @@ bool Kernel::sendTrigger(int32 triggerNum) {  bool Kernel::handleTrigger(int32 triggerNum) { -	printf("betweenRooms = %d; triggerNum = %08X\n", betweenRooms, triggerNum); +	printf("betweenRooms = %d; triggerNum = %08X\n", betweenRooms, (uint)triggerNum);  	if (betweenRooms)  		return true; @@ -271,11 +271,13 @@ Globals::Globals(M4Engine *vm): _vm(vm) {  }  Globals::~Globals() { -	for(uint32 i = 0; i < _madsVocab.size(); i++) +	uint32 i; + +	for(i = 0; i < _madsVocab.size(); i++)  		free(_madsVocab[i]);  	_madsVocab.clear(); -	for(uint32 i = 0; i < _madsQuotes.size(); i++) +	for(i = 0; i < _madsQuotes.size(); i++)  		free(_madsQuotes[i]);  	_madsQuotes.clear(); @@ -351,7 +353,7 @@ void Globals::loadMadsMessagesInfo() {  	_vm->res()->toss("messages.dat");  } -char* Globals::loadMessage(uint32 index) { +char* Globals::loadMessage(uint index) {  	if (index > _madsMessages.size() - 1) {  		warning("Invalid message index: %i", index);  		return NULL; diff --git a/engines/m4/globals.h b/engines/m4/globals.h index a0133db2d6..a80e8bf710 100644 --- a/engines/m4/globals.h +++ b/engines/m4/globals.h @@ -177,7 +177,7 @@ public:  	void loadMadsMessagesInfo();  	uint32 getMessagesSize() { return _madsMessages.size(); } -	char* loadMessage(uint32 index); +	char* loadMessage(uint index);  };  #define PLAYER_FIELD_LENGTH 40 diff --git a/engines/m4/graphics.cpp b/engines/m4/graphics.cpp index beda178344..1846f1c1e7 100644 --- a/engines/m4/graphics.cpp +++ b/engines/m4/graphics.cpp @@ -320,7 +320,7 @@ byte *M4Surface::getBasePtr(int x, int y) {  void M4Surface::freeData() {  } -void M4Surface::empty() { +void M4Surface::clear() {  	Common::set_to((byte *) pixels, (byte *) pixels + w * h, _vm->_palette->BLACK);  } @@ -389,7 +389,7 @@ void M4Surface::loadBackgroundRiddle(const char *sceneName) {  }  void M4Surface::loadBackground(int sceneNumber, RGBList **palData) { -	this->empty();		// clear previous scene +	clear();		// clear previous scene  	if (_vm->isM4() || (_vm->getGameType() == GType_RexNebular)) {  		char resourceName[20]; @@ -502,7 +502,7 @@ void M4Surface::madsLoadBackground(int roomNumber, RGBList **palData) {  		//printf("Tile: %i, compressed size: %i\n", i, compressedTileDataSize); -		newTile->empty(); +		newTile->clear();  		byte *compressedTileData = new byte[compressedTileDataSize]; diff --git a/engines/m4/graphics.h b/engines/m4/graphics.h index 60e608c148..84fc77656f 100644 --- a/engines/m4/graphics.h +++ b/engines/m4/graphics.h @@ -128,7 +128,7 @@ public:  	byte *getData();  	byte *getBasePtr(int x, int y);  	void freeData(); -	void empty(); +	void clear();  	void frameRect(const Common::Rect &r, uint8 color);  	void fillRect(const Common::Rect &r, uint8 color);  	void copyFrom(M4Surface *src, const Common::Rect &srcBounds, int destX, int destY, diff --git a/engines/m4/m4_views.cpp b/engines/m4/m4_views.cpp index 9bf964ee96..777356467b 100644 --- a/engines/m4/m4_views.cpp +++ b/engines/m4/m4_views.cpp @@ -331,7 +331,7 @@ bool GameInterfaceView::onEvent(M4EventType eventType, int param, int x, int y,  }  void GameInterfaceView::onRefresh(RectList *rects, M4Surface *destSurface) { -	empty(); +	clear();  	_statusText.onRefresh();  	_inventory.onRefresh(); diff --git a/engines/m4/mads_anim.cpp b/engines/m4/mads_anim.cpp index 3e80d0f1e0..c51daa84c4 100644 --- a/engines/m4/mads_anim.cpp +++ b/engines/m4/mads_anim.cpp @@ -61,9 +61,9 @@ TextviewView::TextviewView(M4Engine *vm):  	_vm->_font->setColors(5, 6, 4); -	empty(); -	_bgSurface.empty(); -	_textSurface.empty(); +	clear(); +	_bgSurface.clear(); +	_textSurface.clear();  	int y = (height() - MADS_SURFACE_HEIGHT) / 2;  	setColor(2); @@ -83,8 +83,8 @@ TextviewView::~TextviewView() {  }  void TextviewView::reset() { -	_bgSurface.empty(); -	_textSurface.empty(); +	_bgSurface.clear(); +	_textSurface.clear();  	_animating = false;  	_panX = 0;  	_panY = 0; @@ -456,8 +456,8 @@ AnimviewView::AnimviewView(M4Engine *vm):  	// Set up system palette colors  	_vm->_palette->setMadsSystemPalette(); -	empty(); -	_bgSurface.empty(); +	clear(); +	_bgSurface.clear();  	int y = (height() - MADS_SURFACE_HEIGHT) / 2;  	setColor(2); @@ -471,7 +471,7 @@ AnimviewView::~AnimviewView() {  }  void AnimviewView::reset() { -	_bgSurface.empty(); +	_bgSurface.clear();  	_soundDriverLoaded = false;  } diff --git a/engines/m4/viewmgr.cpp b/engines/m4/viewmgr.cpp index 3a8b5d24a8..b74e598c6c 100644 --- a/engines/m4/viewmgr.cpp +++ b/engines/m4/viewmgr.cpp @@ -380,7 +380,7 @@ void ViewManager::updateState() {  }  void ViewManager::refreshAll() { -	_vm->_screen->empty(); +	_vm->_screen->clear();  	for (ListIterator i = _views.begin(); i != _views.end(); ++i) {  		View *v = *i; diff --git a/engines/made/database.cpp b/engines/made/database.cpp index 55e0e90732..3497b5b46f 100644 --- a/engines/made/database.cpp +++ b/engines/made/database.cpp @@ -88,10 +88,7 @@ int16 Object::getVectorItem(int16 index) {  	if (getClass() == 0x7FFF) {  		byte *vector = (byte*)getData();  		return vector[index]; -	} else if (getClass() == 0x7FFE) { -		int16 *vector = (int16*)getData(); -		return READ_LE_UINT16(&vector[index]); -	} else if (getClass() < 0x7FFE) { +	} else if (getClass() <= 0x7FFE) {  		int16 *vector = (int16*)getData();  		return READ_LE_UINT16(&vector[index]);  	} else { @@ -372,7 +369,7 @@ void GameDatabaseV2::load(Common::SeekableReadStream &sourceS) {  	debug(2, "textOffs = %08X; textSize = %08X; objectCount = %d; varObjectCount = %d; gameStateSize = %d; objectsOffs = %08X; objectsSize = %d\n", textOffs, textSize, objectCount, varObjectCount, _gameStateSize, objectsOffs, objectsSize);  	_gameState = new byte[_gameStateSize + 2]; -	memset(_gameState, 0, _gameStateSize); +	memset(_gameState, 0, _gameStateSize + 2);  	setVar(1, objectCount);  	sourceS.seek(textOffs); @@ -441,7 +438,7 @@ int16 *GameDatabaseV2::findObjectProperty(int16 objectIndex, int16 propertyId, i  	int16 *propPtr2 = prop + count2;  	// First see if the property exists in the given object -	while (count2-- > 0) { +	while (count2--) {  		if ((READ_LE_UINT16(prop) & 0x7FFF) == propertyId) {  			propertyFlag = obj->getFlags() & 1;  			return propPtr1; @@ -467,8 +464,8 @@ int16 *GameDatabaseV2::findObjectProperty(int16 objectIndex, int16 propertyId, i  		propPtr1 = propPtr2 + count1 - count2;  		int16 *propertyPtr = prop + count1; -		while (count2-- > 0) { -			if (!(READ_LE_UINT16(prop) & 0x8000)) { +		while (count2--) { +			if ((READ_LE_UINT16(prop) & 0x8000) == 0) {  				if ((READ_LE_UINT16(prop) & 0x7FFF) == propertyId) {  					propertyFlag = obj->getFlags() & 1;  					return propPtr1; diff --git a/engines/made/detection.cpp b/engines/made/detection.cpp index dc7dbdee87..e5870bfeec 100644 --- a/engines/made/detection.cpp +++ b/engines/made/detection.cpp @@ -65,7 +65,7 @@ static const PlainGameDescriptor madeGames[] = {  	{"manhole", "The Manhole"},  	{"rtz", "Return to Zork"},  	{"lgop2", "Leather Goddesses of Phobos 2"}, -	{"rodney", "Rodney's Fun Screen"}, +	{"rodney", "Rodney's Funscreen"},  	{0, 0}  }; @@ -278,7 +278,7 @@ static const MadeGameDescription gameDescriptions[] = {  	},  	{ -		// Rodney's Fun Screen +		// Rodney's Funscreen  		{  			"rodney",  			"", diff --git a/engines/made/made.cpp b/engines/made/made.cpp index 59ec487c37..dc45dc4d2f 100644 --- a/engines/made/made.cpp +++ b/engines/made/made.cpp @@ -148,27 +148,31 @@ int MadeEngine::init() {  	return 0;  } +int16 MadeEngine::getTicks() { +	return g_system->getMillis() * 30 / 1000; +} +  int16 MadeEngine::getTimer(int16 timerNum) {  	if (timerNum > 0 && timerNum <= ARRAYSIZE(_timers) && _timers[timerNum - 1] != -1) -		return (_system->getMillis() - _timers[timerNum - 1]) / kTimerResolution; +		return (getTicks() - _timers[timerNum - 1]);  	else  		return 32000;  }  void MadeEngine::setTimer(int16 timerNum, int16 value) {  	if (timerNum > 0 && timerNum <= ARRAYSIZE(_timers)) -		_timers[timerNum - 1] = value * kTimerResolution; +		_timers[timerNum - 1] = value;  }  void MadeEngine::resetTimer(int16 timerNum) {  	if (timerNum > 0 && timerNum <= ARRAYSIZE(_timers)) -		_timers[timerNum - 1] = _system->getMillis(); +		_timers[timerNum - 1] = getTicks();  }  int16 MadeEngine::allocTimer() {  	for (int i = 0; i < ARRAYSIZE(_timers); i++) {  		if (_timers[i] == -1) { -			_timers[i] = _system->getMillis(); +			_timers[i] = getTicks();  			return i + 1;  		}  	} @@ -202,24 +206,20 @@ void MadeEngine::handleEvents() {  			break;  		case Common::EVENT_LBUTTONDOWN: -			_eventNum = 1; +			_eventNum = 2;  			break; -		/*  		case Common::EVENT_LBUTTONUP: -			_eventNum = 2; // TODO: Is this correct? +			_eventNum = 1;  			break; -		*/  		case Common::EVENT_RBUTTONDOWN: -			_eventNum = 3; +			_eventNum = 4;  			break; -		/*  		case Common::EVENT_RBUTTONUP: -			eventNum = 4; // TODO: Is this correct? +			_eventNum = 3;  			break; -		*/  		case Common::EVENT_KEYDOWN:  			_eventKey = event.kbd.ascii; @@ -239,7 +239,7 @@ void MadeEngine::handleEvents() {  		}  	} -	 +  	AudioCD.updateCD();  } diff --git a/engines/made/made.h b/engines/made/made.h index 461941e5cf..971961c867 100644 --- a/engines/made/made.h +++ b/engines/made/made.h @@ -120,6 +120,7 @@ public:  	int _engineVersion;  	int32 _timers[50]; +	int16 getTicks();  	int16 getTimer(int16 timerNum);  	void setTimer(int16 timerNum, int16 value);  	void resetTimer(int16 timerNum); diff --git a/engines/made/pmvplayer.cpp b/engines/made/pmvplayer.cpp index 1a8ca9c50a..831f1fab8e 100644 --- a/engines/made/pmvplayer.cpp +++ b/engines/made/pmvplayer.cpp @@ -40,7 +40,10 @@ void PmvPlayer::play(const char *filename) {  	_surface = NULL;  	_fd = new Common::File(); -	_fd->open(filename); +	if (!_fd->open(filename)) { +		delete _fd; +		return; +	}  	uint32 chunkType, chunkSize; diff --git a/engines/made/screen.cpp b/engines/made/screen.cpp index cecd0c8968..0c22d40259 100644 --- a/engines/made/screen.cpp +++ b/engines/made/screen.cpp @@ -688,7 +688,7 @@ void Screen::printText(const char *text) {  	for (int textPos = 0; textPos < textLen; textPos++) { -		uint c = text[textPos]; +		uint c = ((byte*)text)[textPos];  		int charWidth = _font->getCharWidth(c);  		if (c == 9) { @@ -822,6 +822,8 @@ SpriteListItem Screen::getFromSpriteList(int16 index) {  	if (((uint) index) > _spriteList.size()) {  		SpriteListItem emptyItem;  		emptyItem.index = 0; +		emptyItem.xofs = 0; +		emptyItem.yofs = 0;  		return emptyItem;  	} else {  		return _spriteList[index - 1]; diff --git a/engines/made/scriptfuncs.cpp b/engines/made/scriptfuncs.cpp index 932447a1eb..d697e24b04 100644 --- a/engines/made/scriptfuncs.cpp +++ b/engines/made/scriptfuncs.cpp @@ -106,7 +106,7 @@ void ScriptFunctions::setupExternalsTable() {  	External(sfStopSound);  	External(sfPlayVoice); -	if (_vm->getGameID() == GID_MANHOLE || _vm->getGameID() == GID_RTZ) { +	if (_vm->getGameID() == GID_MANHOLE || _vm->getGameID() == GID_RTZ || _vm->getGameID() == GID_RODNEY) {  		External(sfPlayCd);  		External(sfStopCd);  		External(sfGetCdStatus); @@ -332,7 +332,7 @@ int16 ScriptFunctions::sfAddSprite(int16 argc, int16 *argv) {  	if (_vm->getGameID() == GID_RTZ) {  		// Unused in RTZ  		return 0; -	} if (_vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_MANHOLE) { +	} if (_vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_MANHOLE || _vm->getGameID() == GID_RODNEY) {  		return _vm->_screen->addToSpriteList(argv[2], argv[1], argv[0]);  	} else {  		return 0; @@ -341,7 +341,7 @@ int16 ScriptFunctions::sfAddSprite(int16 argc, int16 *argv) {  int16 ScriptFunctions::sfFreeAnim(int16 argc, int16 *argv) {  	_vm->_screen->clearChannels(); -	if (_vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_MANHOLE) { +	if (_vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_MANHOLE || _vm->getGameID() == GID_RODNEY) {  		_vm->_screen->clearSpriteList();  	}  	return 0; @@ -350,7 +350,7 @@ int16 ScriptFunctions::sfFreeAnim(int16 argc, int16 *argv) {  int16 ScriptFunctions::sfDrawSprite(int16 argc, int16 *argv) {  	if (_vm->getGameID() == GID_RTZ) {  		return _vm->_screen->drawSprite(argv[2], argv[1], argv[0]); -	} if (_vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_MANHOLE) { +	} if (_vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_MANHOLE || _vm->getGameID() == GID_RODNEY) {  		SpriteListItem item = _vm->_screen->getFromSpriteList(argv[2]);  		int16 channelIndex = _vm->_screen->drawSprite(item.index, argv[1] - item.xofs, argv[0] - item.yofs);  		_vm->_screen->setChannelUseMask(channelIndex); @@ -409,7 +409,7 @@ int16 ScriptFunctions::sfDrawText(int16 argc, int16 *argv) {  	if (_vm->getGameID() == GID_RTZ) {  		text = _vm->_dat->getObjectString(argv[argc - 1]); -	} if (_vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_MANHOLE) { +	} if (_vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_MANHOLE || _vm->getGameID() == GID_RODNEY) {  		text = _vm->_dat->getString(argv[argc - 1]);  	} diff --git a/engines/parallaction/balloons.cpp b/engines/parallaction/balloons.cpp new file mode 100644 index 0000000000..81b32adb15 --- /dev/null +++ b/engines/parallaction/balloons.cpp @@ -0,0 +1,728 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "common/util.h" + +#include "parallaction/graphics.h" +#include "parallaction/parallaction.h" + +namespace Parallaction { + + +#define	BALLOON_TRANSPARENT_COLOR_NS 2 +#define BALLOON_TRANSPARENT_COLOR_BR 0 + +#define BALLOON_TAIL_WIDTH	12 +#define BALLOON_TAIL_HEIGHT	10 + + +byte _resBalloonTail[2][BALLOON_TAIL_WIDTH*BALLOON_TAIL_HEIGHT] = { +	{ +	  0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, +	  0x02, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, +	  0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, +	  0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, +	  0x02, 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, +	  0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, +	  0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, +	  0x02, 0x00, 0x01, 0x01, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, +	  0x00, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, +	  0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, +	}, +	{ +	  0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, +	  0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x02, 0x02, 0x02, +	  0x02, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, +	  0x02, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, +	  0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, +	  0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, +	  0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, +	  0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, +	  0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x00, 0x02, 0x02, +	  0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x02, 0x02 +	} +}; + +class BalloonManager_ns : public BalloonManager { + +	static int16 _dialogueBalloonX[5]; + +	struct Balloon { +		Common::Rect outerBox; +		Common::Rect innerBox; +		Graphics::Surface *surface; +		GfxObj	*obj; +	} _intBalloons[5]; + +	uint	_numBalloons; + +	void getStringExtent(Font *font, char *text, uint16 maxwidth, int16* width, int16* height); +	void drawWrappedText(Font *font, Graphics::Surface* surf, char *text, byte color, int16 wrapwidth); +	int createBalloon(int16 w, int16 h, int16 winding, uint16 borderThickness); +	Balloon *getBalloon(uint id); + +	Gfx *_gfx; + +public: +	BalloonManager_ns(Gfx *gfx); +	~BalloonManager_ns(); + +	void freeBalloons(); +	int setLocationBalloon(char *text, bool endGame); +	int setDialogueBalloon(char *text, uint16 winding, byte textColor); +	int setSingleBalloon(char *text, uint16 x, uint16 y, uint16 winding, byte textColor); +	void setBalloonText(uint id, char *text, byte textColor); +	int hitTestDialogueBalloon(int x, int y); +}; + +int16 BalloonManager_ns::_dialogueBalloonX[5] = { 80, 120, 150, 150, 150 }; + +BalloonManager_ns::BalloonManager_ns(Gfx *gfx) : _numBalloons(0), _gfx(gfx) { + +} + +BalloonManager_ns::~BalloonManager_ns() { + +} + + +BalloonManager_ns::Balloon* BalloonManager_ns::getBalloon(uint id) { +	assert(id < _numBalloons); +	return &_intBalloons[id]; +} + +int BalloonManager_ns::createBalloon(int16 w, int16 h, int16 winding, uint16 borderThickness) { +	assert(_numBalloons < 5); + +	int id = _numBalloons; + +	Balloon *balloon = &_intBalloons[id]; + +	int16 real_h = (winding == -1) ? h : h + 9; +	balloon->surface = new Graphics::Surface; +	balloon->surface->create(w, real_h, 1); +	balloon->surface->fillRect(Common::Rect(w, real_h), BALLOON_TRANSPARENT_COLOR_NS); + +	Common::Rect r(w, h); +	balloon->surface->fillRect(r, 0); +	balloon->outerBox = r; + +	r.grow(-borderThickness); +	balloon->surface->fillRect(r, 1); +	balloon->innerBox = r; + +	if (winding != -1) { +		// draws tail +		// TODO: this bitmap tail should only be used for Dos games. Amiga should use a polygon fill. +		winding = (winding == 0 ? 1 : 0); +		Common::Rect s(BALLOON_TAIL_WIDTH, BALLOON_TAIL_HEIGHT); +		s.moveTo(r.width()/2 - 5, r.bottom - 1); +		_gfx->blt(s, _resBalloonTail[winding], balloon->surface, LAYER_FOREGROUND, BALLOON_TRANSPARENT_COLOR_NS); +	} + +	_numBalloons++; + +	return id; +} + + +int BalloonManager_ns::setSingleBalloon(char *text, uint16 x, uint16 y, uint16 winding, byte textColor) { + +	int16 w, h; + +	getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &w, &h); + +	int id = createBalloon(w+5, h, winding, 1); +	Balloon *balloon = &_intBalloons[id]; + +	drawWrappedText(_vm->_dialogueFont, balloon->surface, text, textColor, MAX_BALLOON_WIDTH); + +	// TODO: extract some text to make a name for obj +	balloon->obj = _gfx->registerBalloon(new SurfaceToFrames(balloon->surface), 0); +	balloon->obj->x = x; +	balloon->obj->y = y; +	balloon->obj->transparentKey = BALLOON_TRANSPARENT_COLOR_NS; + +	return id; +} + +int BalloonManager_ns::setDialogueBalloon(char *text, uint16 winding, byte textColor) { + +	int16 w, h; + +	getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &w, &h); + +	int id = createBalloon(w+5, h, winding, 1); +	Balloon *balloon = &_intBalloons[id]; + +	drawWrappedText(_vm->_dialogueFont, balloon->surface, text, textColor, MAX_BALLOON_WIDTH); + +	// TODO: extract some text to make a name for obj +	balloon->obj = _gfx->registerBalloon(new SurfaceToFrames(balloon->surface), 0); +	balloon->obj->x = _dialogueBalloonX[id]; +	balloon->obj->y = 10; +	balloon->obj->transparentKey = BALLOON_TRANSPARENT_COLOR_NS; + +	if (id > 0) { +		balloon->obj->y += _intBalloons[id - 1].obj->y + _intBalloons[id - 1].outerBox.height(); +	} + + +	return id; +} + +void BalloonManager_ns::setBalloonText(uint id, char *text, byte textColor) { +	Balloon *balloon = getBalloon(id); +	balloon->surface->fillRect(balloon->innerBox, 1); +	drawWrappedText(_vm->_dialogueFont, balloon->surface, text, textColor, MAX_BALLOON_WIDTH); +} + + +int BalloonManager_ns::setLocationBalloon(char *text, bool endGame) { + +	int16 w, h; + +	getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &w, &h); + +	int id = createBalloon(w+(endGame ? 5 : 10), h+5, -1, BALLOON_TRANSPARENT_COLOR_NS); +	Balloon *balloon = &_intBalloons[id]; +	drawWrappedText(_vm->_dialogueFont, balloon->surface, text, 0, MAX_BALLOON_WIDTH); + +	// TODO: extract some text to make a name for obj +	balloon->obj = _gfx->registerBalloon(new SurfaceToFrames(balloon->surface), 0); +	balloon->obj->x = 5; +	balloon->obj->y = 5; +	balloon->obj->transparentKey = BALLOON_TRANSPARENT_COLOR_NS; + +	return id; +} + +int BalloonManager_ns::hitTestDialogueBalloon(int x, int y) { + +	Common::Point p; + +	for (uint i = 0; i < _numBalloons; i++) { +		p.x = x - _intBalloons[i].obj->x; +		p.y = y - _intBalloons[i].obj->y; + +		if (_intBalloons[i].innerBox.contains(p)) +			return i; +	} + +	return -1; +} + +void BalloonManager_ns::freeBalloons() { +	_gfx->destroyBalloons(); + +	for (uint i = 0; i < _numBalloons; i++) { +		_intBalloons[i].obj = 0; +		_intBalloons[i].surface = 0;	// no need to delete surface, since it is done by destroyBalloons +	} + +	_numBalloons = 0; +} + +void BalloonManager_ns::drawWrappedText(Font *font, Graphics::Surface* surf, char *text, byte color, int16 wrapwidth) { + +	uint16 lines = 0; +	uint16 linewidth = 0; + +	uint16 rx = 10; +	uint16 ry = 4; + +	uint16 blankWidth = font->getStringWidth(" "); +	uint16 tokenWidth = 0; + +	char token[MAX_TOKEN_LEN]; + +	if (wrapwidth == -1) +		wrapwidth = _vm->_screenWidth; + +	while (strlen(text) > 0) { + +		text = parseNextToken(text, token, MAX_TOKEN_LEN, "   ", true); + +		if (!scumm_stricmp(token, "%p")) { +			lines++; +			rx = 10; +			ry = 4 + lines*10;	// y + +			strcpy(token, "> ......."); +			strncpy(token+2, _password, strlen(_password)); +			tokenWidth = font->getStringWidth(token); +		} else { +			tokenWidth = font->getStringWidth(token); + +			linewidth += tokenWidth; + +			if (linewidth > wrapwidth) { +				// wrap line +				lines++; +				rx = 10;			// x +				ry = 4 + lines*10;	// y +				linewidth = tokenWidth; +			} + +			if (!scumm_stricmp(token, "%s")) { +				sprintf(token, "%d", _score); +			} + +		} + +		_gfx->drawText(font, surf, rx, ry, token, color); + +		rx += tokenWidth + blankWidth; +		linewidth += blankWidth; + +		text = Common::ltrim(text); +	} + +} + +void BalloonManager_ns::getStringExtent(Font *font, char *text, uint16 maxwidth, int16* width, int16* height) { + +	uint16 lines = 0; +	uint16 w = 0; +	*width = 0; + +	uint16 blankWidth = font->getStringWidth(" "); +	uint16 tokenWidth = 0; + +	char token[MAX_TOKEN_LEN]; + +	while (strlen(text) != 0) { + +		text = parseNextToken(text, token, MAX_TOKEN_LEN, "   ", true); +		tokenWidth = font->getStringWidth(token); + +		w += tokenWidth; + +		if (!scumm_stricmp(token, "%p")) { +			lines++; +		} else { +			if (w > maxwidth) { +				w -= tokenWidth; +				lines++; +				if (w > *width) +					*width = w; + +				w = tokenWidth; +			} +		} + +		w += blankWidth; +		text = Common::ltrim(text); +	} + +	if (*width < w) *width = w; +	*width += 10; + +	*height = lines * 10 + 20; + +	return; +} + + + + + +class BalloonManager_br : public BalloonManager { + +	struct Balloon { +		Common::Rect box; +		Graphics::Surface *surface; +		GfxObj	*obj; +	} _intBalloons[3]; + +	uint	_numBalloons; + +	Disk *_disk; +	Gfx *_gfx; + +	Frames *_leftBalloon; +	Frames *_rightBalloon; + +	void cacheAnims(); +	void getStringExtent(Font *font, const char *text, uint16 maxwidth, int16* width, int16* height); +	void drawWrappedText(Font *font, Graphics::Surface* surf, char *text, byte color, int16 wrapwidth); +	int createBalloon(int16 w, int16 h, int16 winding, uint16 borderThickness); +	Balloon *getBalloon(uint id); +	Graphics::Surface *expandBalloon(Frames *data, int frameNum); + +	void textSetupRendering(const Common::String &text, Graphics::Surface *dest, Font *font, byte color); +	void textEmitCenteredLine(); +	void textAccum(const Common::String &token, uint16 width); +	void textNewLine(); + +	Common::String _textLine; +	Graphics::Surface *_textSurf; +	Font *_textFont; +	uint16 _textX, _textY; +	byte _textColor; +	uint16 _textLines, _textWidth; + +	void extentSetup(Font *font, int16 *width, int16 *height); +	void extentAction(); + +	int16 *_extentWidth, *_extentHeight; + + +public: +	BalloonManager_br(Disk *disk, Gfx *gfx); +	~BalloonManager_br(); + +	void freeBalloons(); +	int setLocationBalloon(char *text, bool endGame); +	int setDialogueBalloon(char *text, uint16 winding, byte textColor); +	int setSingleBalloon(char *text, uint16 x, uint16 y, uint16 winding, byte textColor); +	void setBalloonText(uint id, char *text, byte textColor); +	int hitTestDialogueBalloon(int x, int y); +}; + + + +BalloonManager_br::Balloon* BalloonManager_br::getBalloon(uint id) { +	assert(id < _numBalloons); +	return &_intBalloons[id]; +} + +Graphics::Surface *BalloonManager_br::expandBalloon(Frames *data, int frameNum) { + +	Common::Rect rect; +	data->getRect(frameNum, rect); + +	rect.translate(-rect.left, -rect.top); + +	Graphics::Surface *surf = new Graphics::Surface; +	surf->create(rect.width(), rect.height(), 1); + +	_gfx->unpackBlt(rect, data->getData(frameNum), data->getRawSize(frameNum), surf, 0, BALLOON_TRANSPARENT_COLOR_BR); + +	return surf; +} + +int BalloonManager_br::setSingleBalloon(char *text, uint16 x, uint16 y, uint16 winding, byte textColor) { +	cacheAnims(); + +	int id = _numBalloons; +	Frames *src = 0; +	int srcFrame = 0; + +	Balloon *balloon = &_intBalloons[id]; + +	if (winding == 0) { +		src = _rightBalloon; +		srcFrame = 0; +	} else +	if (winding == 1) { +		src = _leftBalloon; +		srcFrame = 0; +	} + +	assert(src); + +	balloon->surface = expandBalloon(src, srcFrame); +	src->getRect(srcFrame, balloon->box); + +	drawWrappedText(_vm->_dialogueFont, balloon->surface, text, textColor, MAX_BALLOON_WIDTH); + +	// TODO: extract some text to make a name for obj +	balloon->obj = _gfx->registerBalloon(new SurfaceToFrames(balloon->surface), 0); +	balloon->obj->x = x + balloon->box.left; +	balloon->obj->y = y + balloon->box.top; +	balloon->obj->transparentKey = BALLOON_TRANSPARENT_COLOR_BR; + +	printf("balloon (%i, %i)\n", balloon->obj->x, balloon->obj->y); + +	_numBalloons++; + +	return id; +} + +int BalloonManager_br::setDialogueBalloon(char *text, uint16 winding, byte textColor) { +	cacheAnims(); + +	int id = _numBalloons; +	Frames *src = 0; +	int srcFrame = 0; + +	Balloon *balloon = &_intBalloons[id]; + +	if (winding == 0) { +		src = _rightBalloon; +		srcFrame = id; +	} else +	if (winding == 1) { +		src = _leftBalloon; +		srcFrame = 0; +	} + +	assert(src); + +	balloon->surface = expandBalloon(src, srcFrame); +	src->getRect(srcFrame, balloon->box); + +	drawWrappedText(_vm->_dialogueFont, balloon->surface, text, textColor, MAX_BALLOON_WIDTH); + +	// TODO: extract some text to make a name for obj +	balloon->obj = _gfx->registerBalloon(new SurfaceToFrames(balloon->surface), 0); +	balloon->obj->x = balloon->box.left; +	balloon->obj->y = balloon->box.top; +	balloon->obj->transparentKey = BALLOON_TRANSPARENT_COLOR_BR; + +	if (id > 0) { +		balloon->obj->y += _intBalloons[id - 1].obj->y + _intBalloons[id - 1].box.height(); +	} + +	_numBalloons++; + +	return id; +} + +void BalloonManager_br::setBalloonText(uint id, char *text, byte textColor) { } + +int BalloonManager_br::setLocationBalloon(char *text, bool endGame) { +/* +	int16 w, h; + +	getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &w, &h); + +	int id = createBalloon(w+(endGame ? 5 : 10), h+5, -1, BALLOON_TRANSPARENT_COLOR); +	Balloon *balloon = &_intBalloons[id]; +	drawWrappedText(_vm->_dialogueFont, balloon->surface, text, 0, MAX_BALLOON_WIDTH); + +	// TODO: extract some text to make a name for obj +	balloon->obj = _gfx->registerBalloon(new SurfaceToFrames(balloon->surface), 0); +	balloon->obj->x = 5; +	balloon->obj->y = 5; +*/ +	return 0; +} + +int BalloonManager_br::hitTestDialogueBalloon(int x, int y) { + +	Common::Point p; + +	for (uint i = 0; i < _numBalloons; i++) { +		p.x = x - _intBalloons[i].obj->x; +		p.y = y - _intBalloons[i].obj->y; + +		if (_intBalloons[i].box.contains(p)) +			return i; +	} + +	return -1; +} + +void BalloonManager_br::freeBalloons() { +	_gfx->destroyBalloons(); + +	for (uint i = 0; i < _numBalloons; i++) { +		_intBalloons[i].obj = 0; +		_intBalloons[i].surface = 0;	// no need to delete surface, since it is done by destroyBalloons +	} + +	_numBalloons = 0; +} + +void BalloonManager_br::cacheAnims() { +	if (!_leftBalloon) { +		_leftBalloon = _disk->loadFrames("fumetto.ani"); +		_rightBalloon = _disk->loadFrames("fumdx.ani"); +	} +} + + +void BalloonManager_br::extentSetup(Font *font, int16 *width, int16 *height) { +	_extentWidth = width; +	_extentHeight = height; + +	_textLine.clear(); +	_textLines = 0; +	_textWidth = 0; +	_textFont = font; +} + +void BalloonManager_br::extentAction() { +	if (_textWidth > *_extentWidth) { +		*_extentWidth = _textWidth; +	} +	*_extentHeight = _textLines * _textFont->height(); +} + +void BalloonManager_br::textSetupRendering(const Common::String &text, Graphics::Surface *dest, Font *font, byte color) { +	uint16 maxWidth = 216; + +	int16 w, h; +	getStringExtent(font, text.c_str(), maxWidth, &w, &h); + +	w += 10; +	h += 12; + +	_textLine.clear(); +	_textSurf = dest; +	_textFont = font; +	_textX = 0; +	_textY = (_textSurf->h - h) / 2; +	_textColor = color; +	_textLines = 0; +	_textWidth = 0; +} + +void BalloonManager_br::textEmitCenteredLine() { +	if (_textLine.empty()) { +		return; +	} +	uint16 rx = _textX + (_textSurf->w - _textWidth) / 2; +	uint16 ry = _textY + _textLines * _textFont->height();	// y +	_gfx->drawText(_textFont, _textSurf, rx, ry, _textLine.c_str(), _textColor); +} + +void BalloonManager_br::textAccum(const Common::String &token, uint16 width) { +	if (token.empty()) { +		return; +	} + +	_textWidth += width; +	_textLine += token; +} + +void BalloonManager_br::textNewLine() { +	_textLines++; +	_textWidth = 0; +	_textLine.clear(); +} + + +// TODO: really, base this and getStringExtent on some kind of LineTokenizer, instead of +// repeating the algorithm and changing a couple of lines. +void BalloonManager_br::drawWrappedText(Font *font, Graphics::Surface* surf, char *text, byte color, int16 wrapWidth) { +	textSetupRendering(text, surf, font, color); + +	wrapWidth = 216; + +	Common::StringTokenizer	tokenizer(text, " "); +	Common::String token; +	Common::String blank(" "); + +	uint16 blankWidth = font->getStringWidth(" "); +	uint16 tokenWidth = 0; + +	while (!tokenizer.empty()) { +		token = tokenizer.nextToken(); + +		if (token == '/') { +			tokenWidth = 0; +			textEmitCenteredLine(); +			textNewLine(); +		} else { +			// todo: expand '%' +			tokenWidth = font->getStringWidth(token.c_str()); + +			if (_textWidth == 0) { +				textAccum(token, tokenWidth); +			} else { +				if (_textWidth + blankWidth + tokenWidth <= wrapWidth) { +					textAccum(blank, blankWidth); +					textAccum(token, tokenWidth); +				} else { +					textEmitCenteredLine(); +					textNewLine(); +					textAccum(token, tokenWidth); +				} +			} +		} +	} + +	textEmitCenteredLine(); +} + + + +void BalloonManager_br::getStringExtent(Font *font, const char *text, uint16 maxwidth, int16* width, int16* height) { +	extentSetup(font, width, height); + +	Common::StringTokenizer	tokenizer(text, " "); +	Common::String token; +	Common::String blank(" "); + +	uint16 blankWidth = font->getStringWidth(" "); +	uint16 tokenWidth = 0; + +	while (!tokenizer.empty()) { +		token = tokenizer.nextToken(); + +		if (token == '/') { +			tokenWidth = 0; +			extentAction(); +			textNewLine(); +		} else { +			// todo: expand '%' +			tokenWidth = font->getStringWidth(token.c_str()); + +			if (_textWidth == 0) { +				textAccum(token, tokenWidth); +			} else { +				if (_textWidth + blankWidth + tokenWidth <= maxwidth) { +					textAccum(blank, blankWidth); +					textAccum(token, tokenWidth); +				} else { +					extentAction(); +					textNewLine(); +					textAccum(token, tokenWidth); +				} +			} +		} +	} + +	extentAction(); +} + + + + +BalloonManager_br::BalloonManager_br(Disk *disk, Gfx *gfx) : _numBalloons(0), _disk(disk), _gfx(gfx), _leftBalloon(0), _rightBalloon(0) { +} + +BalloonManager_br::~BalloonManager_br() { +	delete _leftBalloon; +	delete _rightBalloon; +} + +void Parallaction::setupBalloonManager() { +	if (_vm->getGameType() == GType_Nippon) { +		_balloonMan = new BalloonManager_ns(_vm->_gfx); +	} else +	if (_vm->getGameType() == GType_BRA) { +		_balloonMan = new BalloonManager_br(_vm->_disk, _vm->_gfx); +	} else { +		error("Unknown game type"); +	} +} + + + +} // namespace Parallaction diff --git a/engines/parallaction/callables_ns.cpp b/engines/parallaction/callables_ns.cpp index 68e6a70ffb..761e11dc7d 100644 --- a/engines/parallaction/callables_ns.cpp +++ b/engines/parallaction/callables_ns.cpp @@ -37,18 +37,6 @@  namespace Parallaction { -// part completion messages -static const char *endMsg0[] = {"COMPLIMENTI!", "BRAVO!", "CONGRATULATIONS!", "PRIMA!"}; -static const char *endMsg1[] = {"HAI FINITO QUESTA PARTE", "TU AS COMPLETE' CETTE AVENTURE", "YOU HAVE COMPLETED THIS PART", "DU HAST EIN ABENTEUER ERFOLGREICH"}; -static const char *endMsg2[] = {"ORA COMPLETA IL RESTO ", "AVEC SUCCES.",  "NOW GO ON WITH THE REST OF", "ZU ENDE GEFUHRT"}; -static const char *endMsg3[] = {"DELL' AVVENTURA",  "CONTINUE AVEC LES AUTRES", "THIS ADVENTURE", "MACH' MIT DEN ANDEREN WEITER"}; -// game completion messages -static const char *endMsg4[] = {"COMPLIMENTI!", "BRAVO!", "CONGRATULATIONS!", "PRIMA!"}; -static const char *endMsg5[] = {"HAI FINITO LE TRE PARTI", "TU AS COMPLETE' LES TROIS PARTIES", "YOU HAVE COMPLETED THE THREE PARTS", "DU HAST DREI ABENTEURE ERFOLGREICH"}; -static const char *endMsg6[] = {"DELL' AVVENTURA", "DE L'AVENTURE", "OF THIS ADVENTURE", "ZU ENDE GEFUHRT"}; -static const char *endMsg7[] = {"ED ORA IL GRAN FINALE ", "ET MAINTENANT LE GRAND FINAL", "NOW THE GREAT FINAL", "UND YETZT DER GROSSE SCHLUSS!"}; - -  /*  	intro callables data members  */ @@ -143,18 +131,6 @@ static uint16 _rightHandPositions[684] = {  	0x00e0, 0x007b, 0x00e0, 0x0077  }; -struct Credit { -	const char *_role; -	const char *_name; -} _credits[] = { -	{"Music and Sound Effects", "MARCO CAPRELLI"}, -	{"PC Version", "RICCARDO BALLARINO"}, -	{"Project Manager", "LOVRANO CANEPA"}, -	{"Production", "BRUNO BOZ"}, -	{"Special Thanks to", "LUIGI BENEDICENTI - GILDA and DANILO"}, -	{"Copyright 1992 Euclidea s.r.l ITALY", "All rights reserved"} -}; -  /*  	game callables  */ @@ -304,23 +280,19 @@ void Parallaction_ns::_c_trasformata(void *parm) {  }  void Parallaction_ns::_c_offMouse(void *parm) { -	_input->showCursor(false); -	_engineFlags |= kEngineBlockInput; -	return; +	_input->setMouseState(MOUSE_DISABLED);  }  void Parallaction_ns::_c_onMouse(void *parm) { -	_engineFlags &= ~kEngineBlockInput; -	_input->showCursor(true); -	return; +	_input->setMouseState(MOUSE_ENABLED_SHOW);  }  void Parallaction_ns::_c_setMask(void *parm) { -	memset(_gfx->_backgroundInfo.mask.data + 3600, 0, 3600); -	_gfx->_backgroundInfo.layers[1] = 500; +	memset(_gfx->_backgroundInfo->mask.data + 3600, 0, 3600); +	_gfx->_backgroundInfo->layers[1] = 500;  	return;  } @@ -340,8 +312,8 @@ void Parallaction_ns::_c_endComment(void *param) {  		g_system->delayMillis(20);  	} -	_input->waitUntilLeftClick(); -	_gfx->freeBalloons(); +	_input->waitForButtonEvent(kMouseLeftUp); +	_balloonMan->freeBalloons();  	return;  } @@ -376,37 +348,12 @@ void Parallaction_ns::_c_finito(void *parm) {  	setPartComplete(_char);  	cleanInventory(); -	_gfx->setPalette(_gfx->_palette); - -	uint id[4]; - -	if (allPartsComplete()) { -		id[0] = _gfx->createLabel(_menuFont, endMsg4[_language], 1); -		id[1] = _gfx->createLabel(_menuFont, endMsg5[_language], 1); -		id[2] = _gfx->createLabel(_menuFont, endMsg6[_language], 1); -		id[3] = _gfx->createLabel(_menuFont, endMsg7[_language], 1); -	} else { -		id[0] = _gfx->createLabel(_menuFont, endMsg0[_language], 1); -		id[1] = _gfx->createLabel(_menuFont, endMsg1[_language], 1); -		id[2] = _gfx->createLabel(_menuFont, endMsg2[_language], 1); -		id[3] = _gfx->createLabel(_menuFont, endMsg3[_language], 1); -	} - -	_gfx->showLabel(id[0], CENTER_LABEL_HORIZONTAL, 70); -	_gfx->showLabel(id[1], CENTER_LABEL_HORIZONTAL, 100); -	_gfx->showLabel(id[2], CENTER_LABEL_HORIZONTAL, 130); -	_gfx->showLabel(id[3], CENTER_LABEL_HORIZONTAL, 160); -	_input->waitUntilLeftClick(); +	cleanupGame(); -	_gfx->freeLabels(); +	_gfx->setPalette(_gfx->_palette); -	if (allPartsComplete()) { -		scheduleLocationSwitch("estgrotta.drki"); -	} else { -		selectStartLocation(); -	} +	startEndPartSequence(); -	cleanupGame();  	return;  } @@ -417,6 +364,14 @@ void Parallaction_ns::_c_ridux(void *parm) {  }  void Parallaction_ns::_c_testResult(void *parm) { +	if (_inTestResult) {		// NOTE: _inTestResult has been added because the scripts call _c_testResult multiple times to cope with +								// the multiple buffering that was used in the original engine. _inTestResult now prevents the engine +								// from crashing when the scripts are executed. +		return; +	} +	_inTestResult = true; + +	_gfx->freeLabels();  	_gfx->updateScreen();  	_disk->selectArchive("disk1"); @@ -459,52 +414,11 @@ void Parallaction_ns::_c_startIntro(void *parm) {  		_soundMan->playMusic();  	} -	_engineFlags |= kEngineBlockInput; - -	return; +	_input->setMouseState(MOUSE_DISABLED);  }  void Parallaction_ns::_c_endIntro(void *parm) { - -	debugC(1, kDebugExec, "endIntro()"); - -	uint id[2]; -	for (uint16 _si = 0; _si < 6; _si++) { -		id[0] = _gfx->createLabel(_menuFont, _credits[_si]._role, 1); -		id[1] = _gfx->createLabel(_menuFont, _credits[_si]._name, 1); - -		_gfx->showLabel(id[0], CENTER_LABEL_HORIZONTAL, 80); -		_gfx->showLabel(id[1], CENTER_LABEL_HORIZONTAL, 100); - -		_gfx->updateScreen(); - -		_input->waitForButtonEvent(kMouseLeftUp, 5500); - -		_gfx->freeLabels(); -	} -	debugC(1, kDebugExec, "endIntro(): done showing credits"); - -	_soundMan->stopMusic(); - -	if ((getFeatures() & GF_DEMO) == 0) { - -		id[0] = _gfx->createLabel(_menuFont, "CLICK MOUSE BUTTON TO START", 1); -		_gfx->showLabel(id[0], CENTER_LABEL_HORIZONTAL, 80); - -		_input->waitUntilLeftClick(); - -		_gfx->freeLabels(); - -		_engineFlags &= ~kEngineBlockInput; -		selectStartLocation(); - -		cleanupGame(); - -	} else { -		_input->waitUntilLeftClick(); -	} - -	return; +	startCreditSequence();  }  void Parallaction_ns::_c_moveSheet(void *parm) { @@ -588,11 +502,11 @@ void Parallaction_ns::_c_shade(void *parm) {  		_rightHandAnim->_top  	); -	uint16 _di = r.left/4 + r.top * _gfx->_backgroundInfo.mask.internalWidth; +	uint16 _di = r.left/4 + r.top * _gfx->_backgroundInfo->mask.internalWidth;  	for (uint16 _si = r.top; _si < r.bottom; _si++) { -		memset(_gfx->_backgroundInfo.mask.data + _di, 0, r.width()/4+1); -		_di += _gfx->_backgroundInfo.mask.internalWidth; +		memset(_gfx->_backgroundInfo->mask.data + _di, 0, r.width()/4+1); +		_di += _gfx->_backgroundInfo->mask.internalWidth;  	}  	return; diff --git a/engines/parallaction/debug.cpp b/engines/parallaction/debug.cpp index 3c90a76f61..f57976594e 100644 --- a/engines/parallaction/debug.cpp +++ b/engines/parallaction/debug.cpp @@ -188,17 +188,15 @@ bool Debugger::Cmd_GfxObjects(int argc, const char **argv) {  	const char *objType[] = { "DOOR", "GET", "ANIM" };  	DebugPrintf("+--------------------+-----+-----+-----+-----+--------+--------+\n" -				"| name               |  x  |  y  |  z  |  f  |  type  |  flag  |\n" +				"| name               |  x  |  y  |  z  |  f  |  type  |  visi  |\n"  				"+--------------------+-----+-----+-----+-----+--------+--------+\n"); -	for (uint i = 0; i < 3; i++) { -		GfxObjList::iterator b = _vm->_gfx->_gfxobjList[i].begin(); -		GfxObjList::iterator e = _vm->_gfx->_gfxobjList[i].end(); +	GfxObjList::iterator b = _vm->_gfx->_gfxobjList.begin(); +	GfxObjList::iterator e = _vm->_gfx->_gfxobjList.end(); -		for ( ; b != e; b++) { -			GfxObj *obj = *b; -			DebugPrintf("|%-20s|%5i|%5i|%5i|%5i|%8s|%8x|\n", obj->getName(), obj->x, obj->y, obj->z, obj->frame, objType[obj->type], 6 ); -		} +	for ( ; b != e; b++) { +		GfxObj *obj = *b; +		DebugPrintf("|%-20s|%5i|%5i|%5i|%5i|%8s|%8x|\n", obj->getName(), obj->x, obj->y, obj->z, obj->frame, objType[obj->type], obj->isVisible() );  	}  	DebugPrintf("+--------------------+-----+-----+-----+-----+--------+--------+\n"); diff --git a/engines/parallaction/detection.cpp b/engines/parallaction/detection.cpp index 8841b9ca40..0476b01454 100644 --- a/engines/parallaction/detection.cpp +++ b/engines/parallaction/detection.cpp @@ -154,7 +154,23 @@ static const PARALLACTIONGameDescription gameDescriptions[] = {  			Common::ADGF_NO_FLAGS  		},  		GType_BRA, -		GF_LANG_EN | GF_LANG_FR | GF_LANG_DE | GF_LANG_IT | GF_LANG_MULT +		GF_LANG_EN | GF_LANG_FR | GF_LANG_DE | GF_LANG_IT | GF_LANG_MULT, +	}, + +	{ +		{ +			"bra", +			"Demo", +			{ +				{ "russia.fnt",	0, "0dd55251d2886d6783718df2b184bf97", 10649 }, +				{ NULL, 0, NULL, 0} +			}, +			Common::UNK_LANG, +			Common::kPlatformPC, +			Common::ADGF_DEMO +		}, +		GType_BRA, +		GF_LANG_EN | GF_DEMO,  	},  	// TODO: Base the detection of Amiga BRA on actual data file, not executable file. @@ -171,9 +187,25 @@ static const PARALLACTIONGameDescription gameDescriptions[] = {  			Common::ADGF_NO_FLAGS  		},  		GType_BRA, -		GF_LANG_EN | GF_LANG_FR | GF_LANG_DE | GF_LANG_IT | GF_LANG_MULT +		GF_LANG_EN | GF_LANG_FR | GF_LANG_DE | GF_LANG_IT | GF_LANG_MULT,  	}, +	// TODO: Base the detection of Amiga BRA demo on actual data file, not executable file. +	{ +		{ +			"bra", +			"Demo", +			{ +				{ "bigred",	0, "b62a7b589fb5e9071f021227640893bf", 97004 }, +				{ NULL, 0, NULL, 0} +			}, +			Common::UNK_LANG, +			Common::kPlatformAmiga, +			Common::ADGF_DEMO +		}, +		GType_BRA, +		GF_LANG_EN | GF_DEMO, +	},  	{ AD_TABLE_END_MARKER, 0, 0 }  }; diff --git a/engines/parallaction/dialogue.cpp b/engines/parallaction/dialogue.cpp index 70db637699..21584a0525 100644 --- a/engines/parallaction/dialogue.cpp +++ b/engines/parallaction/dialogue.cpp @@ -33,7 +33,7 @@  namespace Parallaction {  #define MAX_PASSWORD_LENGTH			 7 - +/*  #define QUESTION_BALLOON_X			140  #define QUESTION_BALLOON_Y			10  #define QUESTION_CHARACTER_X		  190 @@ -41,118 +41,127 @@ namespace Parallaction {  #define ANSWER_CHARACTER_X			10  #define ANSWER_CHARACTER_Y			80 +*/ +struct BalloonPositions { +	Common::Point	_questionBalloon; +	Common::Point	_questionChar; + +	Common::Point	_answerChar; +}; + +BalloonPositions _balloonPositions_NS = { +	Common::Point(140, 10), +	Common::Point(190, 80), +	Common::Point(10, 80) +}; + +BalloonPositions _balloonPositions_BR = { +	Common::Point(0, 0), +	Common::Point(380, 80), +	Common::Point(10, 80) +}; +  class DialogueManager { +	enum { +		RUN_QUESTION, +		RUN_ANSWER, +		NEXT_QUESTION, +		NEXT_ANSWER, +		DIALOGUE_OVER +	} _state; +  	Parallaction	*_vm; -	SpeakData		*_data;  	Dialogue		*_dialogue;  	bool			_askPassword; +	int				_passwordLen; +	bool			_passwordChanged;  	bool			isNpc; -	Frames			*_questioner; -	Frames			*_answerer; +	GfxObj			*_questioner; +	GfxObj			*_answerer;  	Question		*_q;  	uint16			_visAnswers[5];  	int			_numVisAnswers; +	int			_answerId; + +	int		_selection, _oldSelection; + +	uint32			_mouseButtons; +	Common::Point	_mousePos; +	bool			_isKeyDown; +	uint16			_downKey; + +	BalloonPositions	_ballonPos; +  public: -	DialogueManager(Parallaction *vm, SpeakData *data) : _vm(vm), _data(data) { -		_dialogue = _data->_dialogue; -		isNpc = scumm_stricmp(_data->_name, "yourself") && _data->_name[0] != '\0'; -		_questioner = isNpc ? _vm->_disk->loadTalk(_data->_name) : _vm->_char._talk; -		_answerer = _vm->_char._talk; -	} +	DialogueManager(Parallaction *vm, ZonePtr z); +	~DialogueManager(); -	~DialogueManager() { -		if (isNpc) { -			delete _questioner; -		} +	bool isOver() { +		return _state == DIALOGUE_OVER;  	} -  	void run(); +	ZonePtr			_z; +	CommandList *_cmdList; +  protected: -	void displayQuestion(); +	bool displayQuestion();  	bool displayAnswers();  	bool displayAnswer(uint16 i); -	uint16 getAnswer(); -	int16 selectAnswer(); -	uint16 askPassword(); +	int16 selectAnswer1(); +	int16 selectAnswerN(); +	int16 askPassword();  	int16 getHoverAnswer(int16 x, int16 y); -}; - -uint16 DialogueManager::askPassword() { -	debugC(3, kDebugExec, "checkDialoguePassword()"); - -	uint16 passwordLen = 0; -	_password[0] = '\0'; - -	_vm->_gfx->setDialogueBalloon(_q->_answers[0]->_text, 1, 3); -	int id = _vm->_gfx->setItem(_answerer, ANSWER_CHARACTER_X, ANSWER_CHARACTER_Y); -	_vm->_gfx->setItemFrame(id, 0); - -	Common::Event e; -	bool changed = true;	// force first refresh - -	while (true) { -		e.kbd.ascii = 0; - -		if (g_system->getEventManager()->pollEvent(e)) { -			if (e.type == Common::EVENT_QUIT) { -				// TODO: don't quit() here, just have caller routines to check -				// on kEngineQuit and exit gracefully to allow the engine to shut down -				_engineFlags |= kEngineQuit; -				g_system->quit(); -			} - -			if ((e.type == Common::EVENT_KEYDOWN) && isdigit(e.kbd.ascii)) { -				_password[passwordLen] = e.kbd.ascii; -				passwordLen++; -				_password[passwordLen] = '\0'; -				changed = true; -			} -		} - -		if (changed) { -			_vm->_gfx->setBalloonText(0, _q->_answers[0]->_text, 3); -			_vm->_gfx->updateScreen(); -			changed = false; -		} - -		if ((passwordLen == MAX_PASSWORD_LENGTH) || (e.kbd.ascii == Common::KEYCODE_RETURN)) { +	void runQuestion(); +	void runAnswer(); +	void nextQuestion(); +	void nextAnswer(); -			if ((!scumm_stricmp(_vm->_char.getBaseName(), _doughName) && !scumm_strnicmp(_password, "1732461", 7)) || -			   (!scumm_stricmp(_vm->_char.getBaseName(), _donnaName) && !scumm_strnicmp(_password, "1622", 4)) || -			   (!scumm_stricmp(_vm->_char.getBaseName(), _dinoName) && !scumm_strnicmp(_password, "179", 3))) { +	bool checkPassword(); +	void resetPassword(); +	void accumPassword(uint16 ascii); +}; -				break; +DialogueManager::DialogueManager(Parallaction *vm, ZonePtr z) : _vm(vm), _z(z) { +	int gtype = vm->getGameType(); +	if (gtype == GType_Nippon) { +		_ballonPos = _balloonPositions_NS; +	} else +	if (gtype == GType_BRA) { +		_ballonPos = _balloonPositions_BR; +	} else +		error("unsupported game in DialogueManager"); + +	_dialogue = _z->u.speak->_dialogue; +	isNpc = scumm_stricmp(_z->u.speak->_name, "yourself") && _z->u.speak->_name[0] != '\0'; +	_questioner = isNpc ? _vm->_disk->loadTalk(_z->u.speak->_name) : _vm->_char._talk; +	_answerer = _vm->_char._talk; -			} else { -				passwordLen = 0; -				_password[0] = '\0'; -				changed = true; -			} +	_askPassword = false; +	_q = _dialogue->_questions[0]; -		} +	_cmdList = 0; +	_answerId = 0; -		g_system->delayMillis(20); +	_state = displayQuestion() ? RUN_QUESTION : NEXT_ANSWER; +} +DialogueManager::~DialogueManager() { +	if (isNpc) { +		delete _questioner;  	} - -	_vm->_gfx->hideDialogueStuff(); - -	return 0; - +	_z = nullZonePtr;  } - -  bool DialogueManager::displayAnswer(uint16 i) {  	Answer *a = _q->_answers[i]; @@ -164,11 +173,11 @@ bool DialogueManager::displayAnswer(uint16 i) {  	// display suitable answers  	if (((a->_yesFlags & flags) == a->_yesFlags) && ((a->_noFlags & ~flags) == a->_noFlags)) { -		int id = _vm->_gfx->setDialogueBalloon(a->_text, 1, 3); +		int id = _vm->_balloonMan->setDialogueBalloon(a->_text, 1, 3);  		assert(id >= 0);  		_visAnswers[id] = i; -		_askPassword = (strstr(a->_text, "%p") != NULL); +		_askPassword = (strstr(a->_text, "%P") != NULL);  		_numVisAnswers++;  		return true; @@ -185,126 +194,243 @@ bool DialogueManager::displayAnswers() {  		displayAnswer(i);  	} +	if (_askPassword) { +		resetPassword(); +//		_vm->_balloonMan->setDialogueBalloon(_q->_answers[0]->_text, 1, 3); +		int id = _vm->_gfx->setItem(_answerer, _ballonPos._answerChar.x, _ballonPos._answerChar.y); +		_vm->_gfx->setItemFrame(id, 0); +	} else +	if (_numVisAnswers == 1) { +		int id = _vm->_gfx->setItem(_answerer, _ballonPos._answerChar.x, _ballonPos._answerChar.y); +		_vm->_gfx->setItemFrame(id, _q->_answers[0]->_mood & 0xF); +		_vm->_balloonMan->setBalloonText(0, _q->_answers[_visAnswers[0]]->_text, 0); +	} else +	if (_numVisAnswers > 1) { +		int id = _vm->_gfx->setItem(_answerer, _ballonPos._answerChar.x, _ballonPos._answerChar.y); +		_vm->_gfx->setItemFrame(id, _q->_answers[_visAnswers[0]]->_mood & 0xF); +		_oldSelection = -1; +		_selection = 0; +	} +  	return _numVisAnswers > 0;  } -void DialogueManager::displayQuestion() { - -	if (!scumm_stricmp(_q->_text, "NULL")) return; +bool DialogueManager::displayQuestion() { +	if (!scumm_stricmp(_q->_text, "NULL")) return false; -	_vm->_gfx->setSingleBalloon(_q->_text, QUESTION_BALLOON_X, QUESTION_BALLOON_Y, _q->_mood & 0x10, 0); -	int id = _vm->_gfx->setItem(_questioner, QUESTION_CHARACTER_X, QUESTION_CHARACTER_Y); +	_vm->_balloonMan->setSingleBalloon(_q->_text, _ballonPos._questionBalloon.x, _ballonPos._questionBalloon.y, _q->_mood & 0x10, 0); +	int id = _vm->_gfx->setItem(_questioner, _ballonPos._questionChar.x, _ballonPos._questionChar.y);  	_vm->_gfx->setItemFrame(id, _q->_mood & 0xF); -	_vm->_gfx->updateScreen(); -	_vm->_input->waitUntilLeftClick(); -	_vm->_gfx->hideDialogueStuff(); +	return true; +} -	return; + +bool DialogueManager::checkPassword() { +	return ((!scumm_stricmp(_vm->_char.getBaseName(), _doughName) && !scumm_strnicmp(_password, "1732461", 7)) || +		   (!scumm_stricmp(_vm->_char.getBaseName(), _donnaName) && !scumm_strnicmp(_password, "1622", 4)) || +		   (!scumm_stricmp(_vm->_char.getBaseName(), _dinoName) && !scumm_strnicmp(_password, "179", 3)));  } -uint16 DialogueManager::getAnswer() { +void DialogueManager::resetPassword() { +	_passwordLen = 0; +	_password[0] = '\0'; +	_passwordChanged = true; +} + +void DialogueManager::accumPassword(uint16 ascii) { +	if (!isdigit(ascii)) { +		return; +	} -	uint16 answer = 0; +	_password[_passwordLen] = ascii; +	_passwordLen++; +	_password[_passwordLen] = '\0'; +	_passwordChanged = true; +} -	if (_askPassword == false) { -		answer = selectAnswer(); -	} else { -		answer = askPassword(); +int16 DialogueManager::askPassword() { + +	if (_isKeyDown) { +		accumPassword(_downKey); +	} + +	if (_passwordChanged) { +		_vm->_balloonMan->setBalloonText(0, _q->_answers[0]->_text, 3); +		_passwordChanged = false;  	} -	debugC(3, kDebugExec, "runDialogue: user selected answer #%i", answer); +	if ((_passwordLen == MAX_PASSWORD_LENGTH) || ((_isKeyDown) && (_downKey == Common::KEYCODE_RETURN))) { +		if (checkPassword()) { +			return 0; +		} else { +			resetPassword(); +		} +	} -	return answer; +	return -1;  } -void DialogueManager::run() { +int16 DialogueManager::selectAnswer1() { -	_askPassword = false; -	CommandList *cmdlist = NULL; +	if (_mouseButtons == kMouseLeftUp) { +		return 0; +	} -	_q = _dialogue->_questions[0]; -	int16 answer; +	return -1; +} -	while (_q) { +int16 DialogueManager::selectAnswerN() { -		answer = 0; +	_selection = _vm->_balloonMan->hitTestDialogueBalloon(_mousePos.x, _mousePos.y); -		displayQuestion(); -		if (_q->_answers[0] == NULL) break; +	if (_selection != _oldSelection) { +		if (_oldSelection != -1) { +			_vm->_balloonMan->setBalloonText(_oldSelection, _q->_answers[_visAnswers[_oldSelection]]->_text, 3); +		} -		if (scumm_stricmp(_q->_answers[0]->_text, "NULL")) { -			if (!displayAnswers()) break; -			answer = getAnswer(); -			cmdlist = &_q->_answers[answer]->_commands; +		if (_selection != -1) { +			_vm->_balloonMan->setBalloonText(_selection, _q->_answers[_visAnswers[_selection]]->_text, 0); +			_vm->_gfx->setItemFrame(0, _q->_answers[_visAnswers[_selection]]->_mood & 0xF);  		} +	} + +	_oldSelection = _selection; -		_q = _q->_answers[answer]->_following._question; +	if ((_mouseButtons == kMouseLeftUp) && (_selection != -1)) { +		return _visAnswers[_selection];  	} -	if (cmdlist) -		_vm->runCommands(*cmdlist); +	return -1; +} + +void DialogueManager::runQuestion() { +	debugC(9, kDebugDialogue, "runQuestion\n"); + +	if (_mouseButtons == kMouseLeftUp) { +		_vm->hideDialogueStuff(); +		_state = NEXT_ANSWER; +	}  } -int16 DialogueManager::selectAnswer() { -	int16 numAvailableAnswers = _numVisAnswers; +void DialogueManager::nextAnswer() { +	debugC(9, kDebugDialogue, "nextAnswer\n"); -	int id = _vm->_gfx->setItem(_answerer, ANSWER_CHARACTER_X, ANSWER_CHARACTER_Y); -	_vm->_gfx->setItemFrame(id, _q->_answers[0]->_mood & 0xF); +	if (_q->_answers[0] == NULL) { +		_state = DIALOGUE_OVER; +		return; +	} -	if (numAvailableAnswers == 1) { -		_vm->_gfx->setBalloonText(0, _q->_answers[0]->_text, 0); -		_vm->_input->waitUntilLeftClick(); -		_vm->_gfx->hideDialogueStuff(); -		return 0; +	if (!scumm_stricmp(_q->_answers[0]->_text, "NULL")) { +		_answerId = 0; +		_state = NEXT_QUESTION; +		return; +	} + +	_state = displayAnswers() ? RUN_ANSWER : DIALOGUE_OVER; +} + +void DialogueManager::runAnswer() { +	debugC(9, kDebugDialogue, "runAnswer\n"); + +	if (_askPassword) { +		_answerId = askPassword(); +	} else +	if (_numVisAnswers == 1) { +		_answerId = selectAnswer1(); +	} else { +		_answerId = selectAnswerN(); +	} + +	if (_answerId != -1) { +		_cmdList = &_q->_answers[_answerId]->_commands; +		_vm->hideDialogueStuff(); +		_state = NEXT_QUESTION;  	} +} + +void DialogueManager::nextQuestion() { +	debugC(9, kDebugDialogue, "nextQuestion\n"); -	int oldSelection = -1; -	int selection; +	_q = _q->_answers[_answerId]->_following._question; +	if (_q == 0) { +		_state = DIALOGUE_OVER; +	} else { +		_state = displayQuestion() ? RUN_QUESTION : NEXT_ANSWER; +	} +} -	uint32 event; -	Common::Point p; -	while (true) { -		_vm->_input->readInput(); -		_vm->_input->getCursorPos(p); -		event = _vm->_input->getLastButtonEvent(); -		selection = _vm->_gfx->hitTestDialogueBalloon(p.x, p.y); +void DialogueManager::run() { -		if (selection != oldSelection) { -			if (oldSelection != -1) { -				_vm->_gfx->setBalloonText(oldSelection, _q->_answers[_visAnswers[oldSelection]]->_text, 3); -			} +	// cache event data +	_mouseButtons = _vm->_input->getLastButtonEvent(); +	_vm->_input->getCursorPos(_mousePos); +	_isKeyDown = _vm->_input->getLastKeyDown(_downKey); -			if (selection != -1) { -				_vm->_gfx->setBalloonText(selection, _q->_answers[_visAnswers[selection]]->_text, 0); -				_vm->_gfx->setItemFrame(0, _q->_answers[_visAnswers[selection]]->_mood & 0xF); -			} -		} +	switch (_state) { +	case RUN_QUESTION: +		runQuestion(); +		break; -		if ((selection != -1) && (event == kMouseLeftUp)) { -			break; -		} +	case NEXT_ANSWER: +		nextAnswer(); +		break; -		_vm->_gfx->updateScreen(); -		g_system->delayMillis(20); +	case NEXT_QUESTION: +		nextQuestion(); +		break; -		oldSelection = selection; +	case RUN_ANSWER: +		runAnswer(); +		break; + +	case DIALOGUE_OVER: +		break; + +	default: +		error("unknown state in DialogueManager"); + +	} + +} + +void Parallaction::enterDialogueMode(ZonePtr z) { +	debugC(1, kDebugDialogue, "Parallaction::enterDialogueMode(%s)", z->u.speak->_name); +	_dialogueMan = new DialogueManager(this, z); +	_input->_inputMode = Input::kInputModeDialogue; +} + +void Parallaction::exitDialogueMode() { +	debugC(1, kDebugDialogue, "Parallaction::exitDialogueMode()"); +	_input->_inputMode = Input::kInputModeGame; + +	if (_dialogueMan->_cmdList) { +		_vm->_cmdExec->run(*_dialogueMan->_cmdList);  	} -	_vm->_gfx->hideDialogueStuff(); +	// The current instance of _dialogueMan must be destroyed before the zone commands +	// are executed, because they may create another instance of _dialogueMan that +	// overwrite the current one. This would cause headaches (and it did, actually). +	ZonePtr z = _dialogueMan->_z; +	delete _dialogueMan; +	_dialogueMan = 0; -	return _visAnswers[selection]; +	_cmdExec->run(z->_commands, z);  } +void Parallaction::runDialogueFrame() { +	if (_input->_inputMode != Input::kInputModeDialogue) { +		return; +	} -void Parallaction::runDialogue(SpeakData *data) { -	debugC(1, kDebugExec, "runDialogue: starting dialogue '%s'", data->_name); +	_dialogueMan->run(); -	DialogueManager man(this, data); -	man.run(); +	if (_dialogueMan->isOver()) { +		exitDialogueMode(); +	}  	return;  } diff --git a/engines/parallaction/disk.h b/engines/parallaction/disk.h index b76c66aead..341229a649 100644 --- a/engines/parallaction/disk.h +++ b/engines/parallaction/disk.h @@ -28,6 +28,8 @@  #define PATH_LEN 200 +#include "common/fs.h" +  #include "common/file.h"  #include "graphics/surface.h" @@ -55,12 +57,12 @@ public:  	virtual Script* loadLocation(const char *name) = 0;  	virtual Script* loadScript(const char* name) = 0; -	virtual Frames* loadTalk(const char *name) = 0; -	virtual Frames* loadObjects(const char *name) = 0; +	virtual GfxObj* loadTalk(const char *name) = 0; +	virtual GfxObj* loadObjects(const char *name) = 0;  	virtual Frames* loadPointer(const char *name) = 0; -	virtual Frames* loadHead(const char* name) = 0; +	virtual GfxObj* loadHead(const char* name) = 0;  	virtual Font* loadFont(const char* name) = 0; -	virtual Frames* loadStatic(const char* name) = 0; +	virtual GfxObj* loadStatic(const char* name) = 0;  	virtual Frames* loadFrames(const char* name) = 0;  	virtual void loadSlide(BackgroundInfo& info, const char *filename) = 0;  	virtual void loadScenery(BackgroundInfo& info, const char* background, const char* mask, const char* path) = 0; @@ -147,12 +149,12 @@ public:  	Script* loadLocation(const char *name);  	Script* loadScript(const char* name); -	Frames* loadTalk(const char *name); -	Frames* loadObjects(const char *name); +	GfxObj* loadTalk(const char *name); +	GfxObj* loadObjects(const char *name);  	Frames* loadPointer(const char *name); -	Frames* loadHead(const char* name); +	GfxObj* loadHead(const char* name);  	Font* loadFont(const char* name); -	Frames* loadStatic(const char* name); +	GfxObj* loadStatic(const char* name);  	Frames* loadFrames(const char* name);  	void loadSlide(BackgroundInfo& info, const char *filename);  	void loadScenery(BackgroundInfo& info, const char* background, const char* mask, const char* path); @@ -181,12 +183,12 @@ public:  	Script* loadLocation(const char *name);  	Script* loadScript(const char* name); -	Frames* loadTalk(const char *name); -	Frames* loadObjects(const char *name); +	GfxObj* loadTalk(const char *name); +	GfxObj* loadObjects(const char *name);  	Frames* loadPointer(const char *name); -	Frames* loadHead(const char* name); +	GfxObj* loadHead(const char* name);  	Font* loadFont(const char* name); -	Frames* loadStatic(const char* name); +	GfxObj* loadStatic(const char* name);  	Frames* loadFrames(const char* name);  	void loadSlide(BackgroundInfo& info, const char *filename);  	void loadScenery(BackgroundInfo& info, const char* background, const char* mask, const char* path); @@ -202,15 +204,29 @@ public:  class DosDisk_br : public Disk {  protected: +	uint16			_language; +  	Parallaction	*_vm; -	char			_partPath[PATH_LEN]; -	char			_languageDir[2]; + +	FilesystemNode	_baseDir; +	FilesystemNode	_partDir; + +	FilesystemNode	_aniDir; +	FilesystemNode	_bkgDir; +	FilesystemNode	_mscDir; +	FilesystemNode	_mskDir; +	FilesystemNode	_pthDir; +	FilesystemNode	_rasDir; +	FilesystemNode	_scrDir; +	FilesystemNode	_sfxDir; +	FilesystemNode	_talDir;  protected: -	void errorFileNotFound(const char *s); +	void errorFileNotFound(const FilesystemNode &dir, const Common::String &filename);  	Font *createFont(const char *name, Common::ReadStream &stream);  	Sprites*	createSprites(Common::ReadStream &stream);  	void loadBitmap(Common::SeekableReadStream &stream, Graphics::Surface &surf, byte *palette); +	GfxObj* createInventoryObjects(Common::SeekableReadStream &stream);  public:  	DosDisk_br(Parallaction *vm); @@ -220,12 +236,12 @@ public:  	void setLanguage(uint16 language);  	Script* loadLocation(const char *name);  	Script* loadScript(const char* name); -	Frames* loadTalk(const char *name); -	Frames* loadObjects(const char *name); +	GfxObj* loadTalk(const char *name); +	GfxObj* loadObjects(const char *name);  	Frames* loadPointer(const char *name); -	Frames* loadHead(const char* name); +	GfxObj* loadHead(const char* name);  	Font* loadFont(const char* name); -	Frames* loadStatic(const char* name); +	GfxObj* loadStatic(const char* name);  	Frames* loadFrames(const char* name);  	void loadSlide(BackgroundInfo& info, const char *filename);  	void loadScenery(BackgroundInfo& info, const char* name, const char* mask, const char* path); @@ -234,26 +250,49 @@ public:  	Common::ReadStream* loadSound(const char* name);  }; +class DosDemo_br : public DosDisk_br { + +public: +	DosDemo_br(Parallaction *vm); +	virtual ~DosDemo_br(); + +	Common::String selectArchive(const Common::String& name); + +}; +  class AmigaDisk_br : public DosDisk_br {  protected:  	BackgroundInfo	_backgroundTemp; -	Sprites*	createSprites(const char *name); +	Sprites*	createSprites(Common::ReadStream &stream);  	Font *createFont(const char *name, Common::SeekableReadStream &stream); -	void loadMask(BackgroundInfo& info, const char *name); -	void loadBackground(BackgroundInfo& info, const char *name); +	void loadBackground(BackgroundInfo& info, Common::SeekableReadStream &stream); + +	FilesystemNode	_baseBkgDir; +	FilesystemNode	_fntDir; +	FilesystemNode	_commonAniDir; +	FilesystemNode	_commonBkgDir; +	FilesystemNode	_commonMscDir; +	FilesystemNode	_commonMskDir; +	FilesystemNode	_commonPthDir; +	FilesystemNode	_commonTalDir;  public:  	AmigaDisk_br(Parallaction *vm);  	virtual ~AmigaDisk_br(); -	Frames* loadTalk(const char *name); +	GfxObj* loadTalk(const char *name);  	Font* loadFont(const char* name); -	Frames* loadStatic(const char* name); +	GfxObj* loadStatic(const char* name);  	Frames* loadFrames(const char* name);  	void loadSlide(BackgroundInfo& info, const char *filename);  	void loadScenery(BackgroundInfo& info, const char* name, const char* mask, const char* path); +	GfxObj* loadObjects(const char *name); +	Common::SeekableReadStream* loadMusic(const char* name); +	Common::ReadStream* loadSound(const char* name); +	Common::String selectArchive(const Common::String& name); +  };  } // namespace Parallaction diff --git a/engines/parallaction/disk_br.cpp b/engines/parallaction/disk_br.cpp index 5e88327879..cd57ec8822 100644 --- a/engines/parallaction/disk_br.cpp +++ b/engines/parallaction/disk_br.cpp @@ -25,6 +25,7 @@  #include "graphics/iff.h" +#include "common/config-manager.h"  #include "parallaction/parallaction.h" @@ -58,7 +59,7 @@ struct Sprites : public Frames {  	}  	~Sprites() { -		delete _sprites; +		delete[] _sprites;  	}  	uint16 getNum() { @@ -90,101 +91,110 @@ struct Sprites : public Frames { -void DosDisk_br::errorFileNotFound(const char *s) { -	error("File '%s' not found", s); +void DosDisk_br::errorFileNotFound(const FilesystemNode &dir, const Common::String &filename) { +	error("File '%s' not found in directory '%s'", filename.c_str(), dir.getDisplayName().c_str());  }  Common::String DosDisk_br::selectArchive(const Common::String& name) {  	debugC(5, kDebugDisk, "DosDisk_br::selectArchive"); -	Common::String oldPath(_partPath); -	strcpy(_partPath, name.c_str()); +	Common::String oldPath; +	if (_partDir.exists()) { +		oldPath = _partDir.getDisplayName(); +	} + +	_partDir = _baseDir.getChild(name); + +	_aniDir = _partDir.getChild("ani"); +	_bkgDir = _partDir.getChild("bkg"); +	_mscDir = _partDir.getChild("msc"); +	_mskDir = _partDir.getChild("msk"); +	_pthDir = _partDir.getChild("pth"); +	_rasDir = _partDir.getChild("ras"); +	_scrDir = _partDir.getChild("scripts"); +	_sfxDir = _partDir.getChild("sfx"); +	_talDir = _partDir.getChild("tal");  	return oldPath;  }  void DosDisk_br::setLanguage(uint16 language) {  	debugC(5, kDebugDisk, "DosDisk_br::setLanguage"); - -	switch (language) { -	case 0: -		strcpy(_languageDir, "it"); -		break; - -	case 1: -		strcpy(_languageDir, "fr"); -		break; - -	case 2: -		strcpy(_languageDir, "en"); -		break; - -	case 3: -		strcpy(_languageDir, "ge"); -		break; - -	default: -		error("unknown language"); - -	} - -	return; +	assert(language < 4); +	_language = language;  } -DosDisk_br::DosDisk_br(Parallaction* vm) : _vm(vm) { - +DosDisk_br::DosDisk_br(Parallaction* vm) : _vm(vm), _baseDir(ConfMan.get("path")) {  }  DosDisk_br::~DosDisk_br() {  } -Frames* DosDisk_br::loadTalk(const char *name) { +GfxObj* DosDisk_br::loadTalk(const char *name) {  	debugC(5, kDebugDisk, "DosDisk_br::loadTalk(%s)", name); -	Common::File stream; - -	char path[PATH_LEN]; -	sprintf(path, "%s/tal/%s", _partPath, name); -	if (!stream.open(path)) { -		sprintf(path, "%s/tal/%s.tal", _partPath, name); -		if (!stream.open(path)) -			errorFileNotFound(path); +	Common::String path(name); +	FilesystemNode node = _talDir.getChild(path); +	if (!node.exists()) { +		path += ".tal"; +		node = _talDir.getChild(path); +		if (!node.exists()) +			errorFileNotFound(_talDir, path);  	} -	return createSprites(stream); +	Common::File stream; +	stream.open(node); + +	// talk position is set to (0,0), because talks are always displayed at +	// absolute coordinates, set in the dialogue manager. The original used +	// to null out coordinates every time they were needed. We do it better! +	Sprites *spr = createSprites(stream); +	for (int i = 0; i < spr->getNum(); i++) { +		spr->_sprites[i].x = 0; +		spr->_sprites[i].y = 0; +	} +	return new GfxObj(0, spr, name);  }  Script* DosDisk_br::loadLocation(const char *name) {  	debugC(5, kDebugDisk, "DosDisk_br::loadLocation"); -	Common::File *stream = new Common::File; - -	char path[PATH_LEN]; -	sprintf(path, "%s/%s/%s.slf", _partPath, _languageDir, name); -	if (!stream->open(path)) { -		sprintf(path, "%s/%s/%s.loc", _partPath, _languageDir, name); -		if (!stream->open(path)) -			errorFileNotFound(path); +	Common::String langs[4] = { "it", "fr", "en", "ge" }; +	FilesystemNode locDir = _partDir.getChild(langs[_language]); + +	Common::String path(name); +	path += ".slf"; +	FilesystemNode node = locDir.getChild(path); +	if (!node.exists()) { +		path = Common::String(name) + ".loc"; +		node = locDir.getChild(path); +		if (!node.exists()) { +			errorFileNotFound(locDir, path); +		}  	} +	Common::File *stream = new Common::File; +	stream->open(node);  	return new Script(stream, true);  }  Script* DosDisk_br::loadScript(const char* name) {  	debugC(5, kDebugDisk, "DosDisk_br::loadScript"); -	Common::File *stream = new Common::File; - -	char path[PATH_LEN]; -	sprintf(path, "%s/scripts/%s.scr", _partPath, name); -	if (!stream->open(path)) -		errorFileNotFound(path); +	Common::String path(name); +	path += ".scr"; +	FilesystemNode node = _scrDir.getChild(path); +	if (!node.exists()) { +		errorFileNotFound(_scrDir, path); +	} +	Common::File *stream = new Common::File; +	stream->open(node);  	return new Script(stream, true);  }  //	there are no Head resources in Big Red Adventure -Frames* DosDisk_br::loadHead(const char* name) { +GfxObj* DosDisk_br::loadHead(const char* name) {  	debugC(5, kDebugDisk, "DosDisk_br::loadHead");  	return 0;  } @@ -192,6 +202,7 @@ Frames* DosDisk_br::loadHead(const char* name) {  void DosDisk_br::loadBitmap(Common::SeekableReadStream &stream, Graphics::Surface &surf, byte *palette) {  	stream.skip(4);  	uint width = stream.readUint32BE(); +	if (width & 1) width++;  	uint height = stream.readUint32BE();  	stream.skip(20); @@ -208,12 +219,15 @@ void DosDisk_br::loadBitmap(Common::SeekableReadStream &stream, Graphics::Surfac  Frames* DosDisk_br::loadPointer(const char *name) {  	debugC(5, kDebugDisk, "DosDisk_br::loadPointer"); -	char path[PATH_LEN]; -	sprintf(path, "%s.ras", name); +	Common::String path(name); +	path += ".ras"; +	FilesystemNode node = _baseDir.getChild(path); +	if (!node.exists()) { +		errorFileNotFound(_baseDir, path); +	}  	Common::File stream; -	if (!stream.open(path)) -		errorFileNotFound(path); +	stream.open(node);  	Graphics::Surface *surf = new Graphics::Surface;  	loadBitmap(stream, *surf, 0); @@ -224,39 +238,53 @@ Frames* DosDisk_br::loadPointer(const char *name) {  Font* DosDisk_br::loadFont(const char* name) {  	debugC(5, kDebugDisk, "DosDisk_br::loadFont"); -	char path[PATH_LEN]; -	sprintf(path, "%s.fnt", name); +	Common::String path(name); +	path += ".fnt"; +	FilesystemNode node = _baseDir.getChild(path); +	if (!node.exists()) { +		errorFileNotFound(_baseDir, path); +	}  	Common::File stream; -	if (!stream.open(path)) -		errorFileNotFound(path); - +	stream.open(node);  	return createFont(name, stream);  } -Frames* DosDisk_br::loadObjects(const char *name) { +GfxObj* DosDisk_br::loadObjects(const char *name) {  	debugC(5, kDebugDisk, "DosDisk_br::loadObjects"); -	return 0; + +	Common::String path(name); +	FilesystemNode node = _partDir.getChild(path); +	if (!node.exists()) { +		errorFileNotFound(_partDir, path); +	} + +	Common::File stream; +	stream.open(node); + +	return createInventoryObjects(stream);  }  void genSlidePath(char *path, const char* name) {  	sprintf(path, "%s.bmp", name);  } -Frames* DosDisk_br::loadStatic(const char* name) { +GfxObj* DosDisk_br::loadStatic(const char* name) {  	debugC(5, kDebugDisk, "DosDisk_br::loadStatic"); -	char path[PATH_LEN]; -	sprintf(path, "%s/ras/%s", _partPath, name); -	Common::File stream; -	if (!stream.open(path)) { -		errorFileNotFound(path); +	Common::String path(name); +	FilesystemNode node = _rasDir.getChild(path); +	if (!node.exists()) { +		errorFileNotFound(_rasDir, path);  	} +	Common::File stream; +	stream.open(node); +  	Graphics::Surface *surf = new Graphics::Surface;  	loadBitmap(stream, *surf, 0); -	return new SurfaceToFrames(surf); +	return new GfxObj(0, new SurfaceToFrames(surf), name);  }  Sprites* DosDisk_br::createSprites(Common::ReadStream &stream) { @@ -283,14 +311,18 @@ Sprites* DosDisk_br::createSprites(Common::ReadStream &stream) {  Frames* DosDisk_br::loadFrames(const char* name) {  	debugC(5, kDebugDisk, "DosDisk_br::loadFrames"); -	char path[PATH_LEN]; -	sprintf(path, "%s/ani/%s", _partPath, name); +	Common::String path(name); +	FilesystemNode node = _aniDir.getChild(path); +	if (!node.exists()) { +		path += ".ani"; +		node = _aniDir.getChild(path); +		if (!node.exists()) { +			errorFileNotFound(_aniDir, path); +		} +	}  	Common::File stream; -	if (!stream.open(path)) -		errorFileNotFound(path); - - +	stream.open(node);  	return createSprites(stream);  } @@ -302,12 +334,15 @@ Frames* DosDisk_br::loadFrames(const char* name) {  void DosDisk_br::loadSlide(BackgroundInfo& info, const char *name) {  	debugC(5, kDebugDisk, "DosDisk_br::loadSlide"); -	char path[PATH_LEN]; -	genSlidePath(path, name); +	Common::String path(name); +	path += ".bmp"; +	FilesystemNode node = _baseDir.getChild(path); +	if (!node.exists()) { +		errorFileNotFound(_baseDir, path); +	}  	Common::File stream; -	if (!stream.open(path)) -		errorFileNotFound(path); +	stream.open(node);  	byte rgb[768]; @@ -325,13 +360,17 @@ void DosDisk_br::loadSlide(BackgroundInfo& info, const char *name) {  void DosDisk_br::loadScenery(BackgroundInfo& info, const char *name, const char *mask, const char* path) {  	debugC(5, kDebugDisk, "DosDisk_br::loadScenery"); -	char filename[PATH_LEN]; +	Common::String filepath; +	FilesystemNode node;  	Common::File stream;  	if (name) { -		sprintf(filename, "%s/bkg/%s.bkg", _partPath, name); -		if (!stream.open(filename)) -			errorFileNotFound(filename); +		filepath = Common::String(name) + ".bkg"; +		node = _bkgDir.getChild(filepath); +		if (!node.exists()) { +			errorFileNotFound(_bkgDir, filepath); +		} +		stream.open(node);  		byte rgb[768]; @@ -347,9 +386,12 @@ void DosDisk_br::loadScenery(BackgroundInfo& info, const char *name, const char  	}  	if (mask) { -		sprintf(filename, "%s/msk/%s.msk", _partPath, mask); -		if (!stream.open(filename)) -			errorFileNotFound(filename); +		filepath = Common::String(mask) + ".msk"; +		node = _mskDir.getChild(filepath); +		if (!node.exists()) { +			errorFileNotFound(_mskDir, filepath); +		} +		stream.open(node);  		// NOTE: info.width and info.height are only valid if the background graphics  		// have already been loaded @@ -360,9 +402,12 @@ void DosDisk_br::loadScenery(BackgroundInfo& info, const char *name, const char  	}  	if (path) { -		sprintf(filename, "%s/pth/%s.pth", _partPath, path); -		if (!stream.open(filename)) -			errorFileNotFound(filename); +		filepath = Common::String(path) + ".pth"; +		node = _pthDir.getChild(filepath); +		if (!node.exists()) { +			errorFileNotFound(_pthDir, filepath); +		} +		stream.open(node);  		// NOTE: info.width and info.height are only valid if the background graphics  		// have already been loaded @@ -377,15 +422,16 @@ void DosDisk_br::loadScenery(BackgroundInfo& info, const char *name, const char  Table* DosDisk_br::loadTable(const char* name) {  	debugC(5, kDebugDisk, "DosDisk_br::loadTable"); -	char path[PATH_LEN]; -	sprintf(path, "%s/%s.tab", _partPath, name); - -	Common::File	stream; -	if (!stream.open(path)) -		errorFileNotFound(path); +	Common::String path(name); +	path += ".tab"; +	FilesystemNode node = _partDir.getChild(path); +	if (!node.exists()) { +		errorFileNotFound(_partDir, path); +	} +	Common::File stream; +	stream.open(node);  	Table *t = createTableFromStream(100, stream); -  	stream.close();  	return t; @@ -407,56 +453,68 @@ Common::ReadStream* DosDisk_br::loadSound(const char* name) { -AmigaDisk_br::AmigaDisk_br(Parallaction *vm) : DosDisk_br(vm) { +DosDemo_br::DosDemo_br(Parallaction *vm) : DosDisk_br(vm) {  } -AmigaDisk_br::~AmigaDisk_br() { +DosDemo_br::~DosDemo_br() {  } +Common::String DosDemo_br::selectArchive(const Common::String& name) { +	debugC(5, kDebugDisk, "DosDemo_br::selectArchive"); -/* -	FIXME: mask values are not computed correctly for level 1 and 2 +	Common::String oldPath; +	if (_partDir.exists()) { +		oldPath = _partDir.getDisplayName(); +	} -	NOTE: this routine is only able to build masks for Nippon Safes, since mask widths are hardcoded -	into the main loop. -*/ -void buildMask2(byte* buf) { +	_partDir = _baseDir; -	byte mask1[16] = { 0, 0x80, 0x20, 0xA0, 8, 0x88, 0x28, 0xA8, 2, 0x82, 0x22, 0xA2, 0xA, 0x8A, 0x2A, 0xAA }; -	byte mask0[16] = { 0, 0x40, 0x10, 0x50, 4, 0x44, 0x14, 0x54, 1, 0x41, 0x11, 0x51, 0x5, 0x45, 0x15, 0x55 }; +	_aniDir = _partDir.getChild("ani"); +	_bkgDir = _partDir.getChild("bkg"); +	_mscDir = _partDir.getChild("msc"); +	_mskDir = _partDir.getChild("msk"); +	_pthDir = _partDir.getChild("pth"); +	_rasDir = _partDir.getChild("ras"); +	_scrDir = _partDir.getChild("scripts"); +	_sfxDir = _partDir.getChild("sfx"); +	_talDir = _partDir.getChild("tal"); -	byte plane0[40]; -	byte plane1[40]; +	return oldPath; +} -	for (int32 i = 0; i < _vm->_screenHeight; i++) { -		memcpy(plane0, buf, 40); -		memcpy(plane1, buf+40, 40); -		for (uint32 j = 0; j < 40; j++) { -			*buf++ = mask0[(plane0[j] & 0xF0) >> 4] | mask1[(plane1[j] & 0xF0) >> 4]; -			*buf++ = mask0[plane0[j] & 0xF] | mask1[plane1[j] & 0xF]; -		} -	} + + +AmigaDisk_br::AmigaDisk_br(Parallaction *vm) : DosDisk_br(vm) { +	_fntDir = _baseDir.getChild("fonts"); + +	_baseBkgDir = _baseDir.getChild("backs"); + +	FilesystemNode commonDir = _baseDir.getChild("common"); +	_commonAniDir = commonDir.getChild("anims"); +	_commonBkgDir = commonDir.getChild("backs"); +	_commonMscDir = commonDir.getChild("msc"); +	_commonMskDir = commonDir.getChild("msk"); +	_commonPthDir = commonDir.getChild("pth"); +	_commonTalDir = commonDir.getChild("talks");  } -void AmigaDisk_br::loadBackground(BackgroundInfo& info, const char *name) { -	char path[PATH_LEN]; -	sprintf(path, "%s", name); +AmigaDisk_br::~AmigaDisk_br() { + +} -	Common::File s; -	if (!s.open(path)) -		errorFileNotFound(path); +void AmigaDisk_br::loadBackground(BackgroundInfo& info, Common::SeekableReadStream &stream) {  	byte *pal; -	Graphics::ILBMDecoder decoder(s, info.bg, pal); +	Graphics::ILBMDecoder decoder(stream, info.bg, pal);  	decoder.decode();  	uint i; @@ -480,58 +538,60 @@ void AmigaDisk_br::loadBackground(BackgroundInfo& info, const char *name) {  	return;  } -void AmigaDisk_br::loadMask(BackgroundInfo& info, const char *name) { -	debugC(5, kDebugDisk, "AmigaDisk_br::loadMask(%s)", name); - -	Common::File s; - -	if (!s.open(name)) -		return; - -	s.seek(0x30, SEEK_SET); - -	byte r, g, b; -	for (uint i = 0; i < 4; i++) { -		r = s.readByte(); -		g = s.readByte(); -		b = s.readByte(); - -		info.layers[i] = (((r << 4) & 0xF00) | (g & 0xF0) | (b >> 4)) & 0xFF; -	} - -	s.seek(0x126, SEEK_SET);	// HACK: skipping IFF/ILBM header should be done by analysis, not magic -	Graphics::PackBitsReadStream stream(s); - -	info.mask.create(info.width, info.height); -	stream.read(info.mask.data, info.mask.size); -	buildMask2(info.mask.data); - -	return; -}  void AmigaDisk_br::loadScenery(BackgroundInfo& info, const char* name, const char* mask, const char* path) {  	debugC(1, kDebugDisk, "AmigaDisk_br::loadScenery '%s', '%s' '%s'", name, mask, path); -	char filename[PATH_LEN]; +	Common::String filepath; +	FilesystemNode node;  	Common::File stream;  	if (name) { -		sprintf(filename, "%s/backs/%s.bkg", _partPath, name); - -		loadBackground(info, filename); +		filepath = Common::String(name) + ".bkg"; +		node = _bkgDir.getChild(filepath); +		if (!node.exists()) { +			filepath = Common::String(name) + ".bkg"; +			node = _commonBkgDir.getChild(filepath); +			if (!node.exists()) { +				errorFileNotFound(_bkgDir, filepath); +			} +		} +		stream.open(node); +		loadBackground(info, stream); +		stream.close();  	} +#if 0 +	if (mask && _mskDir.exists()) { +		filepath = Common::String(mask) + ".msk"; +		node = _mskDir.getChild(filepath); +		if (!node.exists()) { +			filepath = Common::String(mask) + ".msk"; +			node = _commonMskDir.getChild(filepath); +		} -	if (mask) { -		sprintf(filename, "%s/msk/%s.msk", _partPath, name); - -		loadMask(info, filename); +		if (node.exists()) { +			stream.open(node); +			stream.seek(0x30, SEEK_SET); +			Graphics::PackBitsReadStream unpackedStream(stream); +			info.mask.create(info.width, info.height); +			unpackedStream.read(info.mask.data, info.mask.size); +			// TODO: there is another step to do after decompression... +			loadMask(info, stream); +			stream.close(); +		}  	} - -	if (path) { -		sprintf(filename, "%s/pth/%s.pth", _partPath, path); -		if (!stream.open(filename)) -			errorFileNotFound(filename); - +#endif +	if (path && _pthDir.exists()) { +		filepath = Common::String(path) + ".pth"; +		node = _pthDir.getChild(filepath); +		if (!node.exists()) { +			filepath = Common::String(path) + ".pth"; +			node = _commonPthDir.getChild(filepath); +			if (!node.exists()) { +				errorFileNotFound(_pthDir, filepath); +			} +		} +		stream.open(node);  		// NOTE: info.width and info.height are only valid if the background graphics  		// have already been loaded  		info.path.create(info.width, info.height); @@ -545,22 +605,28 @@ void AmigaDisk_br::loadScenery(BackgroundInfo& info, const char* name, const cha  void AmigaDisk_br::loadSlide(BackgroundInfo& info, const char *name) {  	debugC(1, kDebugDisk, "AmigaDisk_br::loadSlide '%s'", name); -	char path[PATH_LEN]; -	sprintf(path, "backs/%s.bkg", name); - -	loadBackground(info, path); +	Common::String path(name); +	path += ".bkg"; +	FilesystemNode node = _baseBkgDir.getChild(path); +	if (!node.exists()) { +		errorFileNotFound(_baseBkgDir, path); +	} +	Common::File stream; +	stream.open(node); +	loadBackground(info, stream);  	return;  } -Frames* AmigaDisk_br::loadStatic(const char* name) { +GfxObj* AmigaDisk_br::loadStatic(const char* name) {  	debugC(1, kDebugDisk, "AmigaDisk_br::loadStatic '%s'", name); -	char path[PATH_LEN]; -	sprintf(path, "%s/ras/%s", _partPath, name); -	Common::File stream; -	if (!stream.open(path)) { -		errorFileNotFound(path); +	Common::String path(name); +	FilesystemNode node = _rasDir.getChild(path); +	if (!node.exists()) { +		errorFileNotFound(_rasDir, path);  	} +	Common::File stream; +	stream.open(node);  	byte *pal = 0;  	Graphics::Surface* surf = new Graphics::Surface; @@ -570,16 +636,10 @@ Frames* AmigaDisk_br::loadStatic(const char* name) {  	free(pal); -	return new SurfaceToFrames(surf); +	return new GfxObj(0, new SurfaceToFrames(surf));  } -Sprites* AmigaDisk_br::createSprites(const char *path) { - -	Common::File	stream; -	if (!stream.open(path)) { -		errorFileNotFound(path); -	} - +Sprites* AmigaDisk_br::createSprites(Common::ReadStream &stream) {  	uint16 num = stream.readUint16BE();  	Sprites *sprites = new Sprites(num); @@ -603,32 +663,165 @@ Sprites* AmigaDisk_br::createSprites(const char *path) {  Frames* AmigaDisk_br::loadFrames(const char* name) {  	debugC(1, kDebugDisk, "AmigaDisk_br::loadFrames '%s'", name); -	char path[PATH_LEN]; -	sprintf(path, "%s/anims/%s", _partPath, name); +	Common::String path(name); +	FilesystemNode node = _aniDir.getChild(path); +	if (!node.exists()) { +		path += ".ani"; +		node = _aniDir.getChild(path); +		if (!node.exists()) { +			path = Common::String(name); +			node = _commonAniDir.getChild(path); +			if (!node.exists()) { +				path += ".ani"; +				node = _commonAniDir.getChild(path); +				if (!node.exists()) { +					errorFileNotFound(_aniDir, path); +				} +			} +		} +	} -	return createSprites(path); +	Common::File stream; +	stream.open(node); +	return createSprites(stream);  } -Frames* AmigaDisk_br::loadTalk(const char *name) { +GfxObj* AmigaDisk_br::loadTalk(const char *name) {  	debugC(1, kDebugDisk, "AmigaDisk_br::loadTalk '%s'", name); -	char path[PATH_LEN]; -	sprintf(path, "%s/talks/%s.tal", _partPath, name); +	Common::String path(name); +	FilesystemNode node = _talDir.getChild(path); +	if (!node.exists()) { +		path += ".tal"; +		node = _talDir.getChild(path); +		if (!node.exists()) { +			path = Common::String(name); +			node = _commonTalDir.getChild(path); +			if (!node.exists()) { +				path += ".tal"; +				node = _commonTalDir.getChild(path); +				if (!node.exists()) { +					errorFileNotFound(_talDir, path); +				} +			} +		} +	} -	return createSprites(path); +	Common::File stream; +	stream.open(node); +	return new GfxObj(0, createSprites(stream));  }  Font* AmigaDisk_br::loadFont(const char* name) {  	debugC(1, kDebugDisk, "AmigaFullDisk::loadFont '%s'", name); -	char path[PATH_LEN]; -	sprintf(path, "%s", name); +	Common::String path(name); +	path += ".font"; +	FilesystemNode node = _fntDir.getChild(path); +	if (!node.exists()) { +		errorFileNotFound(_fntDir, path); +	} + +	Common::String fontDir; +	Common::String fontFile; +	byte ch;  	Common::File stream; -	if (!stream.open(path)) -		errorFileNotFound(path); +	stream.open(node); +	stream.seek(4, SEEK_SET); +	while ((ch = stream.readByte()) != 0x2F) fontDir += ch; +	while ((ch = stream.readByte()) != 0) fontFile += ch; +	stream.close(); + +	printf("fontDir = %s, fontFile = %s\n", fontDir.c_str(), fontFile.c_str()); + +	node = _fntDir.getChild(fontDir); +	if (!node.exists()) { +		errorFileNotFound(_fntDir, fontDir); +	} +	node = node.getChild(fontFile); +	if (!node.exists()) { +		errorFileNotFound(node, fontFile); +	} +	stream.open(node);  	return createFont(name, stream);  } +Common::SeekableReadStream* AmigaDisk_br::loadMusic(const char* name) { +	debugC(5, kDebugDisk, "AmigaDisk_br::loadMusic"); + +	Common::String path(name); +	FilesystemNode node = _mscDir.getChild(path); +	if (!node.exists()) { +		// TODO (Kirben): error out when music file is not found? +		return 0; +	} + +	Common::File *stream = new Common::File; +	stream->open(node); +	return stream; +} + + +Common::ReadStream* AmigaDisk_br::loadSound(const char* name) { +	debugC(5, kDebugDisk, "AmigaDisk_br::loadSound"); + +	Common::String path(name); +	FilesystemNode node = _sfxDir.getChild(path); +	if (!node.exists()) { +		errorFileNotFound(_sfxDir, path); +	} + +	Common::File *stream = new Common::File; +	stream->open(node); +	return stream; +} + +GfxObj* AmigaDisk_br::loadObjects(const char *name) { +	debugC(5, kDebugDisk, "AmigaDisk_br::loadObjects"); + +	Common::String path(name); +	FilesystemNode node = _partDir.getChild(path); +	if (!node.exists()) { +		errorFileNotFound(_partDir, path); +	} + +	Common::File stream; +	stream.open(node); + +	byte *pal = 0; +	Graphics::Surface* surf = new Graphics::Surface; + +	Graphics::ILBMDecoder decoder(stream, *surf, pal); +	decoder.decode(); + +	free(pal); + +	return new GfxObj(0, new SurfaceToFrames(surf)); +} + +Common::String AmigaDisk_br::selectArchive(const Common::String& name) { +	debugC(5, kDebugDisk, "AmigaDisk_br::selectArchive"); + +	Common::String oldPath; +	if (_partDir.exists()) { +		oldPath = _partDir.getDisplayName(); +	} + +	_partDir = _baseDir.getChild(name); + +	_aniDir = _partDir.getChild("anims"); +	_bkgDir = _partDir.getChild("backs"); +	_mscDir = _partDir.getChild("msc"); +	_mskDir = _partDir.getChild("msk"); +	_pthDir = _partDir.getChild("pth"); +	_rasDir = _partDir.getChild("ras"); +	_scrDir = _partDir.getChild("scripts"); +	_sfxDir = _partDir.getChild("sfx"); +	_talDir = _partDir.getChild("talks"); + +	return oldPath; +} +  } // namespace Parallaction diff --git a/engines/parallaction/disk_ns.cpp b/engines/parallaction/disk_ns.cpp index cdbe3458a7..55e6fc5e77 100644 --- a/engines/parallaction/disk_ns.cpp +++ b/engines/parallaction/disk_ns.cpp @@ -385,12 +385,12 @@ Cnv* DosDisk_ns::loadCnv(const char *filename) {  	return new Cnv(numFrames, width, height, data);  } -Frames* DosDisk_ns::loadTalk(const char *name) { +GfxObj* DosDisk_ns::loadTalk(const char *name) {  	const char *ext = strstr(name, ".talk");  	if (ext != NULL) {  		// npc talk -		return loadCnv(name); +		return new GfxObj(0, loadCnv(name), name);  	} @@ -401,7 +401,7 @@ Frames* DosDisk_ns::loadTalk(const char *name) {  		sprintf(v20, "%stal", name);  	} -	return loadExternalCnv(v20); +	return new GfxObj(0, loadExternalCnv(v20), name);  }  Script* DosDisk_ns::loadLocation(const char *name) { @@ -434,14 +434,14 @@ Script* DosDisk_ns::loadScript(const char* name) {  	return new Script(new DummyArchiveStream(_resArchive), true);  } -Frames* DosDisk_ns::loadHead(const char* name) { +GfxObj* DosDisk_ns::loadHead(const char* name) {  	char path[PATH_LEN];  	sprintf(path, "%shead", name);  	path[8] = '\0'; -	return loadExternalStaticCnv(path); +	return new GfxObj(0, loadExternalStaticCnv(path));  } @@ -457,15 +457,15 @@ Font* DosDisk_ns::loadFont(const char* name) {  } -Frames* DosDisk_ns::loadObjects(const char *name) { +GfxObj* DosDisk_ns::loadObjects(const char *name) {  	char path[PATH_LEN];  	sprintf(path, "%sobj", name); -	return loadExternalCnv(path); +	return new GfxObj(0, loadExternalCnv(path), name);  } -Frames* DosDisk_ns::loadStatic(const char* name) { +GfxObj* DosDisk_ns::loadStatic(const char* name) {  	char path[PATH_LEN]; @@ -487,7 +487,7 @@ Frames* DosDisk_ns::loadStatic(const char* name) {  	Graphics::PackBitsReadStream decoder(_resArchive);  	decoder.read(cnv->pixels, w*h); -	return new SurfaceToFrames(cnv); +	return new GfxObj(0, new SurfaceToFrames(cnv), name);  }  Frames* DosDisk_ns::loadFrames(const char* name) { @@ -1025,7 +1025,7 @@ Frames* AmigaDisk_ns::loadPointer(const char* name) {  	return makeStaticCnv(stream);  } -Frames* AmigaDisk_ns::loadStatic(const char* name) { +GfxObj* AmigaDisk_ns::loadStatic(const char* name) {  	debugC(1, kDebugDisk, "AmigaDisk_ns::loadStatic '%s'", name);  	Common::SeekableReadStream *s = openArchivedFile(name, true); @@ -1033,7 +1033,7 @@ Frames* AmigaDisk_ns::loadStatic(const char* name) {  	delete s; -	return cnv; +	return new GfxObj(0, cnv, name);  }  Common::SeekableReadStream *AmigaDisk_ns::openArchivedFile(const char* name, bool errorOnFileNotFound) { @@ -1276,7 +1276,7 @@ Frames* AmigaDisk_ns::loadFrames(const char* name) {  	return cnv;  } -Frames* AmigaDisk_ns::loadHead(const char* name) { +GfxObj* AmigaDisk_ns::loadHead(const char* name) {  	debugC(1, kDebugDisk, "AmigaDisk_ns::loadHead '%s'", name);  	char path[PATH_LEN]; @@ -1287,11 +1287,11 @@ Frames* AmigaDisk_ns::loadHead(const char* name) {  	delete s; -	return cnv; +	return new GfxObj(0, cnv, name);  } -Frames* AmigaDisk_ns::loadObjects(const char *name) { +GfxObj* AmigaDisk_ns::loadObjects(const char *name) {  	debugC(1, kDebugDisk, "AmigaDisk_ns::loadObjects");  	char path[PATH_LEN]; @@ -1305,11 +1305,11 @@ Frames* AmigaDisk_ns::loadObjects(const char *name) {  	Cnv *cnv = makeCnv(*s);  	delete s; -	return cnv; +	return new GfxObj(0, cnv, name);  } -Frames* AmigaDisk_ns::loadTalk(const char *name) { +GfxObj* AmigaDisk_ns::loadTalk(const char *name) {  	debugC(1, kDebugDisk, "AmigaDisk_ns::loadTalk '%s'", name);  	Common::SeekableReadStream *s; @@ -1328,7 +1328,7 @@ Frames* AmigaDisk_ns::loadTalk(const char *name) {  	Cnv *cnv = makeCnv(*s);  	delete s; -	return cnv; +	return new GfxObj(0, cnv, name);  }  Table* AmigaDisk_ns::loadTable(const char* name) { @@ -1395,9 +1395,7 @@ Common::ReadStream* AmigaDisk_ns::loadSound(const char* name) {  	char path[PATH_LEN];  	sprintf(path, "%s.snd", name); -	openArchivedFile(path); - -	return new DummyArchiveStream(_resArchive); +	return openArchivedFile(path);  }  } // namespace Parallaction diff --git a/engines/parallaction/exec.h b/engines/parallaction/exec.h new file mode 100644 index 0000000000..22e75744f1 --- /dev/null +++ b/engines/parallaction/exec.h @@ -0,0 +1,255 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + + +#ifndef PARALLACTION_EXEC_H +#define PARALLACTION_EXEC_H + +#include "common/util.h" +#include "parallaction/objects.h" + + +namespace Parallaction { + +typedef Common::Functor0<void> Opcode; +typedef Common::Array<const Opcode*>	OpcodeSet; + +#define DECLARE_UNQUALIFIED_COMMAND_OPCODE(op) void cmdOp_##op() +#define DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(op) void instOp_##op() + +class Parallaction_ns; +class Parallaction_br; + +class CommandExec { +protected: +	struct ParallactionStruct1 { +		CommandPtr cmd; +		ZonePtr	z; +		bool suspend; +	} _ctxt; + +	OpcodeSet	_opcodes; + +	struct SuspendedContext { +		bool valid; +		CommandList::iterator first; +		CommandList::iterator last; +		ZonePtr	zone; +	} _suspendedCtxt; + +	ZonePtr	_execZone; +	void runList(CommandList::iterator first, CommandList::iterator last); +	void createSuspendList(CommandList::iterator first, CommandList::iterator last); +	void cleanSuspendedList(); + +public: +	virtual void init() = 0; +	virtual void run(CommandList &list, ZonePtr z = nullZonePtr); +	void runSuspended(); + +	CommandExec() { +		_suspendedCtxt.valid = false; +	} +	virtual ~CommandExec() { +		for (Common::Array<const Opcode*>::iterator i = _opcodes.begin(); i != _opcodes.end(); ++i) +			delete *i; +		_opcodes.clear(); +	} +}; + +class CommandExec_ns : public CommandExec { + +	Parallaction_ns	*_vm; + +protected: +	void updateGetZone(ZonePtr z, bool visible); + +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(invalid); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(set); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(clear); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(start); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(speak); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(get); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(location); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(open); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(close); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(on); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(off); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(call); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(toggle); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(drop); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(quit); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(move); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(stop); + +public: +	void init(); + +	CommandExec_ns(Parallaction_ns* vm); +	~CommandExec_ns(); +}; + +class CommandExec_br : public CommandExec_ns { + +protected: +	Parallaction_br	*_vm; + +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(location); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(open); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(close); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(on); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(off); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(call); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(drop); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(move); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(start); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(stop); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(character); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(followme); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(onmouse); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(offmouse); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(add); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(leave); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(inc); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(dec); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(ifeq); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(iflt); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(ifgt); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(let); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(music); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(fix); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(unfix); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(zeta); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(scroll); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(swap); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(give); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(text); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(part); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(testsfx); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(ret); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(onsave); +	DECLARE_UNQUALIFIED_COMMAND_OPCODE(offsave); + +public: +	void		init(); + +	CommandExec_br(Parallaction_br* vm); +	~CommandExec_br(); +}; + +class ProgramExec { +protected: +	struct ParallactionStruct2 { +		AnimationPtr	anim; +		ProgramPtr		program; +		InstructionList::iterator inst; +		InstructionList::iterator ip; +		uint16		modCounter; +		bool		suspend; +	} _ctxt; + +	const char **_instructionNames; + +	OpcodeSet	_opcodes; + +	uint16	_modCounter; +	void runScript(ProgramPtr script, AnimationPtr a); + +public: +	virtual void init() = 0; +	virtual void runScripts(ProgramList::iterator first, ProgramList::iterator last); +	ProgramExec() : _modCounter(0) { +	} +	virtual ~ProgramExec() { +		for (Common::Array<const Opcode*>::iterator i = _opcodes.begin(); i != _opcodes.end(); ++i) +			delete *i; +		_opcodes.clear(); +	} +}; + +class ProgramExec_ns : public ProgramExec { + +	Parallaction_ns *_vm; + +protected: +	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(invalid); +	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(on); +	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(off); +	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(loop); +	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endloop); +	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(show); +	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(call); +	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(inc); +	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(set); +	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(put); +	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(wait); +	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(start); +	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(sound); +	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(move); +	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endscript); + +public: +	void init(); + +	ProgramExec_ns(Parallaction_ns *vm); +	~ProgramExec_ns(); +}; + +class ProgramExec_br : public ProgramExec_ns { + +	Parallaction_br *_vm; + +protected: +	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(on); + 	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(off); +	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(inc); +	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(dec); +	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(set); +	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(put); +	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(wait); +	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(start); +	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(process); +	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(move); +	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(color); +	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(mask); +	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(print); +	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(text); +	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(mul); +	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(div); +	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(ifeq); +	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(iflt); +	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(ifgt); +	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endif); +	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(stop); + +public: +	void init(); +	ProgramExec_br(Parallaction_br *vm); +	~ProgramExec_br(); +}; + +} // namespace Parallaction + +#endif diff --git a/engines/parallaction/exec_br.cpp b/engines/parallaction/exec_br.cpp index 3b67b4c370..0b7400f0f7 100644 --- a/engines/parallaction/exec_br.cpp +++ b/engines/parallaction/exec_br.cpp @@ -23,6 +23,7 @@   *   */ +#include "parallaction/exec.h"  #include "parallaction/input.h"  #include "parallaction/parallaction.h" @@ -60,16 +61,17 @@ namespace Parallaction {  #define INST_STOP		30  #define INST_ENDSCRIPT	31 - -  #define SetOpcodeTable(x) table = &x; -typedef Common::Functor0Mem<void, Parallaction_br> OpcodeV2; -#define COMMAND_OPCODE(op) table->push_back(new OpcodeV2(this, &Parallaction_br::cmdOp_##op)) -#define DECLARE_COMMAND_OPCODE(op) void Parallaction_br::cmdOp_##op() +typedef Common::Functor0Mem<void, CommandExec_br> OpcodeV1; +#define COMMAND_OPCODE(op) table->push_back(new OpcodeV1(this, &CommandExec_br::cmdOp_##op)) +#define DECLARE_COMMAND_OPCODE(op) void CommandExec_br::cmdOp_##op() -#define INSTRUCTION_OPCODE(op) table->push_back(new OpcodeV2(this, &Parallaction_br::instOp_##op)) -#define DECLARE_INSTRUCTION_OPCODE(op) void Parallaction_br::instOp_##op() +typedef Common::Functor0Mem<void, ProgramExec_br> OpcodeV2; +#define INSTRUCTION_OPCODE(op) table->push_back(new OpcodeV2(this, &ProgramExec_br::instOp_##op)) +#define DECLARE_INSTRUCTION_OPCODE(op) void ProgramExec_br::instOp_##op() + +extern const char *_instructionNamesRes_br[];  void Parallaction_br::setupSubtitles(char *s, char *s2, int y) {  	debugC(5, kDebugExec, "setupSubtitles(%s, %s, %i)", s, s2, y); @@ -100,8 +102,13 @@ void Parallaction_br::setupSubtitles(char *s, char *s2, int y) {  }  void Parallaction_br::clearSubtitles() { -	_gfx->freeLabels(); -	_subtitle[0] = _subtitle[1] = -1; +	if (_subtitle[0] != -1) { +		_gfx->hideLabel(_subtitle[0]); +	} + +	if (_subtitle[1] != -1) { +		_gfx->hideLabel(_subtitle[1]); +	}  } @@ -109,22 +116,30 @@ DECLARE_COMMAND_OPCODE(location) {  	warning("Parallaction_br::cmdOp_location command not yet implemented");  	// TODO: handle startPos and startPos2 -	scheduleLocationSwitch(_cmdRunCtxt.cmd->u._string); +	_vm->scheduleLocationSwitch(_ctxt.cmd->u._string);  }  DECLARE_COMMAND_OPCODE(open) {  	warning("Parallaction_br::cmdOp_open command not yet implemented"); +	_ctxt.cmd->u._zone->_flags &= ~kFlagsClosed; +	if (_ctxt.cmd->u._zone->u.door->gfxobj) { +		_vm->updateDoor(_ctxt.cmd->u._zone); +	}  }  DECLARE_COMMAND_OPCODE(close) {  	warning("Parallaction_br::cmdOp_close not yet implemented"); +	_ctxt.cmd->u._zone->_flags |= kFlagsClosed; +	if (_ctxt.cmd->u._zone->u.door->gfxobj) { +		_vm->updateDoor(_ctxt.cmd->u._zone); +	}  }  DECLARE_COMMAND_OPCODE(on) { -	CommandData *data = &_cmdRunCtxt.cmd->u; +	CommandData *data = &_ctxt.cmd->u;  	ZonePtr z = data->_zone;  	if (z) { @@ -132,52 +147,53 @@ DECLARE_COMMAND_OPCODE(on) {  		z->_flags &= ~kFlagsRemove;  		if ((z->_type & 0xFFFF) & kZoneGet) { -			_gfx->showGfxObj(z->u.get->gfxobj, true); +			_vm->_gfx->showGfxObj(z->u.get->gfxobj, true);  		}  	}  }  DECLARE_COMMAND_OPCODE(off) { -	CommandData *data = &_cmdRunCtxt.cmd->u; +	CommandData *data = &_ctxt.cmd->u;  	ZonePtr z = data->_zone;  	if (z) {  		z->_flags |= kFlagsRemove;  		if ((z->_type & 0xFFFF) & kZoneGet) { -			_gfx->showGfxObj(z->u.get->gfxobj, false); +			_vm->_gfx->showGfxObj(z->u.get->gfxobj, false);  		}  	}  }  DECLARE_COMMAND_OPCODE(call) { -	callFunction(_cmdRunCtxt.cmd->u._callable, &_cmdRunCtxt.z); +	_vm->callFunction(_ctxt.cmd->u._callable, &_ctxt.z);  }  DECLARE_COMMAND_OPCODE(drop) { -	warning("Parallaction_br::cmdOp_drop not yet implemented"); +	_vm->dropItem(_ctxt.cmd->u._object);  }  DECLARE_COMMAND_OPCODE(move) { -	warning("Parallaction_br::cmdOp_move not yet implemented"); +	_vm->_char.scheduleWalk(_ctxt.cmd->u._move.x, _ctxt.cmd->u._move.y); +	_ctxt.suspend = true;  }  DECLARE_COMMAND_OPCODE(start) { -	_cmdRunCtxt.cmd->u._zone->_flags |= kFlagsActing; +	_ctxt.cmd->u._zone->_flags |= kFlagsActing;  }  DECLARE_COMMAND_OPCODE(stop) { -	_cmdRunCtxt.cmd->u._zone->_flags &= ~kFlagsActing; +	_ctxt.cmd->u._zone->_flags &= ~kFlagsActing;  }  DECLARE_COMMAND_OPCODE(character) { -	debugC(9, kDebugExec, "Parallaction_br::cmdOp_character(%s)", _cmdRunCtxt.cmd->u._string); -	changeCharacter(_cmdRunCtxt.cmd->u._string); +	debugC(9, kDebugExec, "Parallaction_br::cmdOp_character(%s)", _ctxt.cmd->u._string); +	_vm->changeCharacter(_ctxt.cmd->u._string);  } @@ -187,17 +203,17 @@ DECLARE_COMMAND_OPCODE(followme) {  DECLARE_COMMAND_OPCODE(onmouse) { -	_input->showCursor(true); +	_vm->_input->setMouseState(MOUSE_ENABLED_SHOW);  }  DECLARE_COMMAND_OPCODE(offmouse) { -	_input->showCursor(false); +	_vm->_input->setMouseState(MOUSE_DISABLED);  }  DECLARE_COMMAND_OPCODE(add) { -	warning("Parallaction_br::cmdOp_add not yet implemented"); +	_vm->addInventoryItem(_ctxt.cmd->u._object);  } @@ -207,42 +223,42 @@ DECLARE_COMMAND_OPCODE(leave) {  DECLARE_COMMAND_OPCODE(inc) { -	_counters[_cmdRunCtxt.cmd->u._lvalue] += _cmdRunCtxt.cmd->u._rvalue; +	_vm->_counters[_ctxt.cmd->u._lvalue] += _ctxt.cmd->u._rvalue;  }  DECLARE_COMMAND_OPCODE(dec) { -	_counters[_cmdRunCtxt.cmd->u._lvalue] -= _cmdRunCtxt.cmd->u._rvalue; +	_vm->_counters[_ctxt.cmd->u._lvalue] -= _ctxt.cmd->u._rvalue;  }  DECLARE_COMMAND_OPCODE(ifeq) { -	if (_counters[_cmdRunCtxt.cmd->u._lvalue] == _cmdRunCtxt.cmd->u._rvalue) { -		setLocationFlags(kFlagsTestTrue); +	if (_vm->_counters[_ctxt.cmd->u._lvalue] == _ctxt.cmd->u._rvalue) { +		_vm->setLocationFlags(kFlagsTestTrue);  	} else { -		clearLocationFlags(kFlagsTestTrue); +		_vm->clearLocationFlags(kFlagsTestTrue);  	}  }  DECLARE_COMMAND_OPCODE(iflt) { -	if (_counters[_cmdRunCtxt.cmd->u._lvalue] < _cmdRunCtxt.cmd->u._rvalue) { -		setLocationFlags(kFlagsTestTrue); +	if (_vm->_counters[_ctxt.cmd->u._lvalue] < _ctxt.cmd->u._rvalue) { +		_vm->setLocationFlags(kFlagsTestTrue);  	} else { -		clearLocationFlags(kFlagsTestTrue); +		_vm->clearLocationFlags(kFlagsTestTrue);  	}  }  DECLARE_COMMAND_OPCODE(ifgt) { -	if (_counters[_cmdRunCtxt.cmd->u._lvalue] > _cmdRunCtxt.cmd->u._rvalue) { -		setLocationFlags(kFlagsTestTrue); +	if (_vm->_counters[_ctxt.cmd->u._lvalue] > _ctxt.cmd->u._rvalue) { +		_vm->setLocationFlags(kFlagsTestTrue);  	} else { -		clearLocationFlags(kFlagsTestTrue); +		_vm->clearLocationFlags(kFlagsTestTrue);  	}  }  DECLARE_COMMAND_OPCODE(let) { -	_counters[_cmdRunCtxt.cmd->u._lvalue] = _cmdRunCtxt.cmd->u._rvalue; +	_vm->_counters[_ctxt.cmd->u._lvalue] = _ctxt.cmd->u._rvalue;  } @@ -252,25 +268,25 @@ DECLARE_COMMAND_OPCODE(music) {  DECLARE_COMMAND_OPCODE(fix) { -	_cmdRunCtxt.cmd->u._zone->_flags |= kFlagsFixed; +	_ctxt.cmd->u._zone->_flags |= kFlagsFixed;  }  DECLARE_COMMAND_OPCODE(unfix) { -	_cmdRunCtxt.cmd->u._zone->_flags &= ~kFlagsFixed; +	_ctxt.cmd->u._zone->_flags &= ~kFlagsFixed;  }  DECLARE_COMMAND_OPCODE(zeta) { -	_location._zeta0 = _cmdRunCtxt.cmd->u._zeta0; -	_location._zeta1 = _cmdRunCtxt.cmd->u._zeta1; -	_location._zeta2 = _cmdRunCtxt.cmd->u._zeta2; +	_vm->_location._zeta0 = _ctxt.cmd->u._zeta0; +	_vm->_location._zeta1 = _ctxt.cmd->u._zeta1; +	_vm->_location._zeta2 = _ctxt.cmd->u._zeta2;  }  DECLARE_COMMAND_OPCODE(scroll) {  	warning("Parallaction_br::cmdOp_scroll not yet implemented"); -	_gfx->setVar("scroll_x", _cmdRunCtxt.cmd->u._rvalue ); +	_vm->_gfx->setVar("scroll_x", _ctxt.cmd->u._rvalue );  } @@ -285,8 +301,8 @@ DECLARE_COMMAND_OPCODE(give) {  DECLARE_COMMAND_OPCODE(text) { -	CommandData *data = &_cmdRunCtxt.cmd->u; -	setupSubtitles(data->_string, data->_string2, data->_zeta0); +	CommandData *data = &_ctxt.cmd->u; +	_vm->setupSubtitles(data->_string, data->_string2, data->_zeta0);  } @@ -297,7 +313,7 @@ DECLARE_COMMAND_OPCODE(part) {  DECLARE_COMMAND_OPCODE(testsfx) {  	warning("Parallaction_br::cmdOp_testsfx not completely implemented"); -	clearLocationFlags(kFlagsTestTrue);	// should test if sfx are enabled +	_vm->clearLocationFlags(kFlagsTestTrue);	// should test if sfx are enabled  } @@ -319,7 +335,7 @@ DECLARE_COMMAND_OPCODE(offsave) {  DECLARE_INSTRUCTION_OPCODE(on) { -	InstructionPtr inst = *_instRunCtxt.inst; +	InstructionPtr inst = *_ctxt.inst;  	ZonePtr z = inst->_z;  	if (z) { @@ -327,28 +343,28 @@ DECLARE_INSTRUCTION_OPCODE(on) {  		z->_flags &= ~kFlagsRemove;  		if ((z->_type & 0xFFFF) & kZoneGet) { -			_gfx->showGfxObj(z->u.get->gfxobj, true); +			_vm->_gfx->showGfxObj(z->u.get->gfxobj, true);  		}  	}  }  DECLARE_INSTRUCTION_OPCODE(off) { -	InstructionPtr inst = *_instRunCtxt.inst; +	InstructionPtr inst = *_ctxt.inst;  	ZonePtr z = inst->_z;  	if (z) {  		z->_flags |= kFlagsRemove;  		if ((z->_type & 0xFFFF) & kZoneGet) { -			_gfx->showGfxObj(z->u.get->gfxobj, false); +			_vm->_gfx->showGfxObj(z->u.get->gfxobj, false);  		}  	}  }  DECLARE_INSTRUCTION_OPCODE(set) { -	InstructionPtr inst = *_instRunCtxt.inst; +	InstructionPtr inst = *_ctxt.inst;  	int16 rvalue = inst->_opB.getRValue();  	int16* lvalue = inst->_opA.getLValue(); @@ -358,22 +374,15 @@ DECLARE_INSTRUCTION_OPCODE(set) {  } -DECLARE_INSTRUCTION_OPCODE(loop) { -	InstructionPtr inst = *_instRunCtxt.inst; - -	_instRunCtxt.program->_loopCounter = inst->_opB.getRValue(); -	_instRunCtxt.program->_loopStart = _instRunCtxt.inst; -} -  DECLARE_INSTRUCTION_OPCODE(inc) { -	InstructionPtr inst = *_instRunCtxt.inst; +	InstructionPtr inst = *_ctxt.inst;  	int16 rvalue = inst->_opB.getRValue();  	if (inst->_flags & kInstMod) {	// mod  		int16 _bx = (rvalue > 0 ? rvalue : -rvalue); -		if (_instRunCtxt.modCounter % _bx != 0) return; +		if (_ctxt.modCounter % _bx != 0) return;  		rvalue = (rvalue > 0 ?  1 : -1);  	} @@ -420,12 +429,12 @@ DECLARE_INSTRUCTION_OPCODE(wait) {  DECLARE_INSTRUCTION_OPCODE(start) { -	(*_instRunCtxt.inst)->_z->_flags |= kFlagsActing; +	(*_ctxt.inst)->_z->_flags |= kFlagsActing;  }  DECLARE_INSTRUCTION_OPCODE(process) { -	_activeZone2 = (*_instRunCtxt.inst)->_z; +	_vm->_activeZone2 = (*_ctxt.inst)->_z;  } @@ -435,18 +444,18 @@ DECLARE_INSTRUCTION_OPCODE(move) {  DECLARE_INSTRUCTION_OPCODE(color) { -	InstructionPtr inst = *_instRunCtxt.inst; +	InstructionPtr inst = *_ctxt.inst;  	int16 entry = inst->_opB.getRValue(); -	_gfx->_palette.setEntry(entry, inst->_colors[0], inst->_colors[1], inst->_colors[2]); +	_vm->_gfx->_palette.setEntry(entry, inst->_colors[0], inst->_colors[1], inst->_colors[2]);  }  DECLARE_INSTRUCTION_OPCODE(mask) {  #if 0 -	Instruction *inst = *_instRunCtxt.inst; +	Instruction *inst = *_ctxt.inst;  	_gfx->_bgLayers[0] = inst->_opA.getRValue();  	_gfx->_bgLayers[1] = inst->_opB.getRValue();  	_gfx->_bgLayers[2] = inst->_opC.getRValue(); @@ -459,8 +468,8 @@ DECLARE_INSTRUCTION_OPCODE(print) {  }  DECLARE_INSTRUCTION_OPCODE(text) { -	InstructionPtr inst = (*_instRunCtxt.inst); -	setupSubtitles(inst->_text, inst->_text2, inst->_y); +	InstructionPtr inst = (*_ctxt.inst); +	_vm->setupSubtitles(inst->_text, inst->_text2, inst->_y);  } @@ -488,22 +497,11 @@ DECLARE_INSTRUCTION_OPCODE(stop) {  	warning("Parallaction_br::instOp_stop not yet implemented");  } -DECLARE_INSTRUCTION_OPCODE(endscript) { -	if ((_instRunCtxt.anim->_flags & kFlagsLooping) == 0) { -		_instRunCtxt.anim->_flags &= ~kFlagsActing; -		runCommands(_instRunCtxt.anim->_commands, _instRunCtxt.anim); -		_instRunCtxt.program->_status = kProgramDone; -	} -	_instRunCtxt.program->_ip = _instRunCtxt.program->_instructions.begin(); - -	_instRunCtxt.suspend = true; -} - -void Parallaction_br::initOpcodes() { +void CommandExec_br::init() {  	Common::Array<const Opcode*> *table = 0; -	SetOpcodeTable(_commandOpcodes); +	SetOpcodeTable(_opcodes);  	COMMAND_OPCODE(invalid);  	COMMAND_OPCODE(set);  	COMMAND_OPCODE(clear); @@ -546,8 +544,21 @@ void Parallaction_br::initOpcodes() {  	COMMAND_OPCODE(ret);  	COMMAND_OPCODE(onsave);  	COMMAND_OPCODE(offsave); +} + +CommandExec_br::CommandExec_br(Parallaction_br* vm) : CommandExec_ns(vm), _vm(vm) { + +} + +CommandExec_br::~CommandExec_br() { + +} -	SetOpcodeTable(_instructionOpcodes); +void ProgramExec_br::init() { + +	Common::Array<const Opcode*> *table = 0; + +	SetOpcodeTable(_opcodes);  	INSTRUCTION_OPCODE(invalid);  	INSTRUCTION_OPCODE(on);  	INSTRUCTION_OPCODE(off); @@ -557,7 +568,7 @@ void Parallaction_br::initOpcodes() {  	INSTRUCTION_OPCODE(set);		// f  	INSTRUCTION_OPCODE(loop);  	INSTRUCTION_OPCODE(endloop); -	INSTRUCTION_OPCODE(null);		// show +	INSTRUCTION_OPCODE(show);		// show  	INSTRUCTION_OPCODE(inc);  	INSTRUCTION_OPCODE(inc);		// dec  	INSTRUCTION_OPCODE(set); @@ -582,6 +593,13 @@ void Parallaction_br::initOpcodes() {  	INSTRUCTION_OPCODE(endscript);  } +ProgramExec_br::ProgramExec_br(Parallaction_br *vm) : ProgramExec_ns(vm), _vm(vm) { +	_instructionNames = _instructionNamesRes_br; +} + +ProgramExec_br::~ProgramExec_br() { +} +  #if 0  void Parallaction_br::jobWaitRemoveLabelJob(void *parm, Job *job) { diff --git a/engines/parallaction/exec_ns.cpp b/engines/parallaction/exec_ns.cpp index a4b372f42a..99a492863b 100644 --- a/engines/parallaction/exec_ns.cpp +++ b/engines/parallaction/exec_ns.cpp @@ -23,6 +23,7 @@   *   */ +#include "parallaction/exec.h"  #include "parallaction/input.h"  #include "parallaction/parallaction.h"  #include "parallaction/sound.h" @@ -52,18 +53,19 @@ namespace Parallaction {  #define SetOpcodeTable(x) table = &x; -typedef Common::Functor0Mem<void, Parallaction_ns> OpcodeV2; -#define COMMAND_OPCODE(op) table->push_back(new OpcodeV2(this, &Parallaction_ns::cmdOp_##op)) -#define DECLARE_COMMAND_OPCODE(op) void Parallaction_ns::cmdOp_##op() - -#define INSTRUCTION_OPCODE(op) table->push_back(new OpcodeV2(this, &Parallaction_ns::instOp_##op)) -#define DECLARE_INSTRUCTION_OPCODE(op) void Parallaction_ns::instOp_##op() +typedef Common::Functor0Mem<void, CommandExec_ns> OpcodeV1; +#define COMMAND_OPCODE(op) table->push_back(new OpcodeV1(this, &CommandExec_ns::cmdOp_##op)) +#define DECLARE_COMMAND_OPCODE(op) void CommandExec_ns::cmdOp_##op() +typedef Common::Functor0Mem<void, ProgramExec_ns> OpcodeV2; +#define INSTRUCTION_OPCODE(op) table->push_back(new OpcodeV2(this, &ProgramExec_ns::instOp_##op)) +#define DECLARE_INSTRUCTION_OPCODE(op) void ProgramExec_ns::instOp_##op() +extern const char *_instructionNamesRes_ns[];  DECLARE_INSTRUCTION_OPCODE(on) { -	InstructionPtr inst = *_instRunCtxt.inst; +	InstructionPtr inst = *_ctxt.inst;  	inst->_a->_flags |= kFlagsActive;  	inst->_a->_flags &= ~kFlagsRemove; @@ -71,31 +73,31 @@ DECLARE_INSTRUCTION_OPCODE(on) {  DECLARE_INSTRUCTION_OPCODE(off) { -	(*_instRunCtxt.inst)->_a->_flags |= kFlagsRemove; +	(*_ctxt.inst)->_a->_flags |= kFlagsRemove;  }  DECLARE_INSTRUCTION_OPCODE(loop) { -	InstructionPtr inst = *_instRunCtxt.inst; +	InstructionPtr inst = *_ctxt.inst; -	_instRunCtxt.program->_loopCounter = inst->_opB.getRValue(); -	_instRunCtxt.program->_loopStart = _instRunCtxt.inst; +	_ctxt.program->_loopCounter = inst->_opB.getRValue(); +	_ctxt.program->_loopStart = _ctxt.ip;  }  DECLARE_INSTRUCTION_OPCODE(endloop) { -	if (--_instRunCtxt.program->_loopCounter > 0) { -		_instRunCtxt.inst = _instRunCtxt.program->_loopStart; +	if (--_ctxt.program->_loopCounter > 0) { +		_ctxt.ip = _ctxt.program->_loopStart;  	}  }  DECLARE_INSTRUCTION_OPCODE(inc) { -	InstructionPtr inst = *_instRunCtxt.inst; +	InstructionPtr inst = *_ctxt.inst;  	int16 _si = inst->_opB.getRValue();  	if (inst->_flags & kInstMod) {	// mod  		int16 _bx = (_si > 0 ? _si : -_si); -		if (_instRunCtxt.modCounter % _bx != 0) return; +		if (_ctxt.modCounter % _bx != 0) return;  		_si = (_si > 0 ?  1 : -1);  	} @@ -116,7 +118,7 @@ DECLARE_INSTRUCTION_OPCODE(inc) {  DECLARE_INSTRUCTION_OPCODE(set) { -	InstructionPtr inst = *_instRunCtxt.inst; +	InstructionPtr inst = *_ctxt.inst;  	int16 _si = inst->_opB.getRValue();  	int16 *lvalue = inst->_opA.getLValue(); @@ -127,7 +129,7 @@ DECLARE_INSTRUCTION_OPCODE(set) {  DECLARE_INSTRUCTION_OPCODE(put) { -	InstructionPtr inst = *_instRunCtxt.inst; +	InstructionPtr inst = *_ctxt.inst;  	Graphics::Surface v18;  	v18.w = inst->_a->width();  	v18.h = inst->_a->height(); @@ -137,162 +139,175 @@ DECLARE_INSTRUCTION_OPCODE(put) {  	int16 y = inst->_opB.getRValue();  	bool mask = (inst->_flags & kInstMaskedPut) == kInstMaskedPut; -	_gfx->patchBackground(v18, x, y, mask); +	_vm->_gfx->patchBackground(v18, x, y, mask);  } -DECLARE_INSTRUCTION_OPCODE(null) { - +DECLARE_INSTRUCTION_OPCODE(show) { +	_ctxt.suspend = true;  }  DECLARE_INSTRUCTION_OPCODE(invalid) { -	error("Can't execute invalid opcode %i", (*_instRunCtxt.inst)->_index); +	error("Can't execute invalid opcode %i", (*_ctxt.inst)->_index);  }  DECLARE_INSTRUCTION_OPCODE(call) { -	callFunction((*_instRunCtxt.inst)->_immediate, 0); +	_vm->callFunction((*_ctxt.inst)->_immediate, 0);  }  DECLARE_INSTRUCTION_OPCODE(wait) { -	if (_engineFlags & kEngineWalking) -		_instRunCtxt.suspend = true; +	if (_engineFlags & kEngineWalking) { +		_ctxt.ip--; +		_ctxt.suspend = true; +	}  }  DECLARE_INSTRUCTION_OPCODE(start) { -	(*_instRunCtxt.inst)->_a->_flags |= (kFlagsActing | kFlagsActive); +	(*_ctxt.inst)->_a->_flags |= (kFlagsActing | kFlagsActive);  }  DECLARE_INSTRUCTION_OPCODE(sound) { -	_activeZone = (*_instRunCtxt.inst)->_z; +	_vm->_activeZone = (*_ctxt.inst)->_z;  }  DECLARE_INSTRUCTION_OPCODE(move) { -	InstructionPtr inst = (*_instRunCtxt.inst); +	InstructionPtr inst = (*_ctxt.inst);  	int16 x = inst->_opA.getRValue();  	int16 y = inst->_opB.getRValue(); -	_char.scheduleWalk(x, y); +	_vm->_char.scheduleWalk(x, y);  }  DECLARE_INSTRUCTION_OPCODE(endscript) { -	if ((_instRunCtxt.anim->_flags & kFlagsLooping) == 0) { -		_instRunCtxt.anim->_flags &= ~kFlagsActing; -		runCommands(_instRunCtxt.anim->_commands, _instRunCtxt.anim); -		_instRunCtxt.program->_status = kProgramDone; +	if ((_ctxt.anim->_flags & kFlagsLooping) == 0) { +		_ctxt.anim->_flags &= ~kFlagsActing; +		_vm->_cmdExec->run(_ctxt.anim->_commands, _ctxt.anim); +		_ctxt.program->_status = kProgramDone;  	} -	_instRunCtxt.program->_ip = _instRunCtxt.program->_instructions.begin(); -	_instRunCtxt.suspend = true; +	_ctxt.ip = _ctxt.program->_instructions.begin(); +	_ctxt.suspend = true;  }  DECLARE_COMMAND_OPCODE(invalid) { -	error("Can't execute invalid command '%i'", _cmdRunCtxt.cmd->_id); +	error("Can't execute invalid command '%i'", _ctxt.cmd->_id);  }  DECLARE_COMMAND_OPCODE(set) { -	if (_cmdRunCtxt.cmd->u._flags & kFlagsGlobal) { -		_cmdRunCtxt.cmd->u._flags &= ~kFlagsGlobal; -		_commandFlags |= _cmdRunCtxt.cmd->u._flags; +	if (_ctxt.cmd->u._flags & kFlagsGlobal) { +		_ctxt.cmd->u._flags &= ~kFlagsGlobal; +		_commandFlags |= _ctxt.cmd->u._flags;  	} else { -		setLocationFlags(_cmdRunCtxt.cmd->u._flags); +		_vm->setLocationFlags(_ctxt.cmd->u._flags);  	}  }  DECLARE_COMMAND_OPCODE(clear) { -	if (_cmdRunCtxt.cmd->u._flags & kFlagsGlobal) { -		_cmdRunCtxt.cmd->u._flags &= ~kFlagsGlobal; -		_commandFlags &= ~_cmdRunCtxt.cmd->u._flags; +	if (_ctxt.cmd->u._flags & kFlagsGlobal) { +		_ctxt.cmd->u._flags &= ~kFlagsGlobal; +		_commandFlags &= ~_ctxt.cmd->u._flags;  	} else { -		clearLocationFlags(_cmdRunCtxt.cmd->u._flags); +		_vm->clearLocationFlags(_ctxt.cmd->u._flags);  	}  }  DECLARE_COMMAND_OPCODE(start) { -	_cmdRunCtxt.cmd->u._zone->_flags |= kFlagsActing; +	_ctxt.cmd->u._zone->_flags |= kFlagsActing;  }  DECLARE_COMMAND_OPCODE(speak) { -	_activeZone = _cmdRunCtxt.cmd->u._zone; +	if ((_ctxt.cmd->u._zone->_type & 0xFFFF) == kZoneSpeak) { +		_vm->enterDialogueMode(_ctxt.cmd->u._zone); +	} else { +		_vm->_activeZone = _ctxt.cmd->u._zone; +	}  }  DECLARE_COMMAND_OPCODE(get) { -	_cmdRunCtxt.cmd->u._zone->_flags &= ~kFlagsFixed; -	runZone(_cmdRunCtxt.cmd->u._zone); +	_ctxt.cmd->u._zone->_flags &= ~kFlagsFixed; +	_vm->runZone(_ctxt.cmd->u._zone);  }  DECLARE_COMMAND_OPCODE(location) { -	scheduleLocationSwitch(_cmdRunCtxt.cmd->u._string); +	_vm->scheduleLocationSwitch(_ctxt.cmd->u._string);  }  DECLARE_COMMAND_OPCODE(open) { -	_cmdRunCtxt.cmd->u._zone->_flags &= ~kFlagsClosed; -	if (_cmdRunCtxt.cmd->u._zone->u.door->gfxobj) { -		updateDoor(_cmdRunCtxt.cmd->u._zone); +	_ctxt.cmd->u._zone->_flags &= ~kFlagsClosed; +	if (_ctxt.cmd->u._zone->u.door->gfxobj) { +		_vm->updateDoor(_ctxt.cmd->u._zone);  	}  }  DECLARE_COMMAND_OPCODE(close) { -	_cmdRunCtxt.cmd->u._zone->_flags |= kFlagsClosed; -	if (_cmdRunCtxt.cmd->u._zone->u.door->gfxobj) { -		updateDoor(_cmdRunCtxt.cmd->u._zone); +	_ctxt.cmd->u._zone->_flags |= kFlagsClosed; +	if (_ctxt.cmd->u._zone->u.door->gfxobj) { +		_vm->updateDoor(_ctxt.cmd->u._zone);  	}  } +void CommandExec_ns::updateGetZone(ZonePtr z, bool visible) { +	if (!z) { +		return; +	} + +	if ((z->_type & 0xFFFF) == kZoneGet) { +		_vm->_gfx->showGfxObj(z->u.get->gfxobj, visible); +	} +}  DECLARE_COMMAND_OPCODE(on) { -	ZonePtr z = _cmdRunCtxt.cmd->u._zone; -	// WORKAROUND: the original DOS-based engine didn't check u->_zone before dereferencing -	// the pointer to get structure members, thus leading to crashes in systems with memory -	// protection. -	// As a side note, the overwritten address is the 5th entry in the DOS interrupt table -	// (print screen handler): this suggests that a system would hang when the print screen -	// key is pressed after playing Nippon Safes, provided that this code path is taken. +	ZonePtr z = _ctxt.cmd->u._zone; +  	if (z) {  		z->_flags &= ~kFlagsRemove;  		z->_flags |= kFlagsActive; -		if ((z->_type & 0xFFFF) == kZoneGet) { -			_gfx->showGfxObj(z->u.get->gfxobj, true); -		} +		updateGetZone(z, true);  	}  }  DECLARE_COMMAND_OPCODE(off) { -	_cmdRunCtxt.cmd->u._zone->_flags |= kFlagsRemove; +	ZonePtr z = _ctxt.cmd->u._zone; + +	if (z) { +		_ctxt.cmd->u._zone->_flags |= kFlagsRemove; +		updateGetZone(z, false); +	}  }  DECLARE_COMMAND_OPCODE(call) { -	callFunction(_cmdRunCtxt.cmd->u._callable, &_cmdRunCtxt.z); +	_vm->callFunction(_ctxt.cmd->u._callable, &_ctxt.z);  }  DECLARE_COMMAND_OPCODE(toggle) { -	if (_cmdRunCtxt.cmd->u._flags & kFlagsGlobal) { -		_cmdRunCtxt.cmd->u._flags &= ~kFlagsGlobal; -		_commandFlags ^= _cmdRunCtxt.cmd->u._flags; +	if (_ctxt.cmd->u._flags & kFlagsGlobal) { +		_ctxt.cmd->u._flags &= ~kFlagsGlobal; +		_commandFlags ^= _ctxt.cmd->u._flags;  	} else { -		toggleLocationFlags(_cmdRunCtxt.cmd->u._flags); +		_vm->toggleLocationFlags(_ctxt.cmd->u._flags);  	}  }  DECLARE_COMMAND_OPCODE(drop){ -	dropItem( _cmdRunCtxt.cmd->u._object ); +	_vm->dropItem( _ctxt.cmd->u._object );  } @@ -302,70 +317,103 @@ DECLARE_COMMAND_OPCODE(quit) {  DECLARE_COMMAND_OPCODE(move) { -	_char.scheduleWalk(_cmdRunCtxt.cmd->u._move.x, _cmdRunCtxt.cmd->u._move.y); +	_vm->_char.scheduleWalk(_ctxt.cmd->u._move.x, _ctxt.cmd->u._move.y);  }  DECLARE_COMMAND_OPCODE(stop) { -	_cmdRunCtxt.cmd->u._zone->_flags &= ~kFlagsActing; +	_ctxt.cmd->u._zone->_flags &= ~kFlagsActing;  }  void Parallaction_ns::drawAnimations() { +	debugC(9, kDebugExec, "Parallaction_ns::drawAnimations()\n");  	uint16 layer = 0;  	for (AnimationList::iterator it = _location._animations.begin(); it != _location._animations.end(); it++) { -		AnimationPtr v18 = *it; -		GfxObj *obj = v18->gfxobj; +		AnimationPtr anim = *it; +		GfxObj *obj = anim->gfxobj; -		if ((v18->_flags & kFlagsActive) && ((v18->_flags & kFlagsRemove) == 0))   { +		// Validation is performed here, so that every animation is affected, instead that only the ones +		// who *own* a script. In fact, some scripts can change values in other animations. +		// The right way to do this would be to enforce validation when any variable is modified from +		// a script. +		anim->validateScriptVars(); -			int16 frame = CLIP((int)v18->_frame, 0, v18->getFrameNum()-1); -			if (v18->_flags & kFlagsNoMasked) +		if ((anim->_flags & kFlagsActive) && ((anim->_flags & kFlagsRemove) == 0))   { + +			if (anim->_flags & kFlagsNoMasked)  				layer = 3;  			else -				layer = _gfx->_backgroundInfo.getLayer(v18->_top + v18->height()); +				layer = _gfx->_backgroundInfo->getLayer(anim->_top + anim->height());  			if (obj) {  				_gfx->showGfxObj(obj, true); -				obj->frame = frame; -				obj->x = v18->_left; -				obj->y = v18->_top; -				obj->z = v18->_z; +				obj->frame =  anim->_frame; +				obj->x = anim->_left; +				obj->y = anim->_top; +				obj->z = anim->_z;  				obj->layer = layer;  			}  		} -		if (((v18->_flags & kFlagsActive) == 0) && (v18->_flags & kFlagsRemove))   { -			v18->_flags &= ~kFlagsRemove; -			v18->_oldPos.x = -1000; +		if (((anim->_flags & kFlagsActive) == 0) && (anim->_flags & kFlagsRemove))   { +			anim->_flags &= ~kFlagsRemove; +			anim->_oldPos.x = -1000;  		} -		if ((v18->_flags & kFlagsActive) && (v18->_flags & kFlagsRemove))	{ -			v18->_flags &= ~kFlagsActive; -			v18->_flags |= kFlagsRemove; +		if ((anim->_flags & kFlagsActive) && (anim->_flags & kFlagsRemove))	{ +			anim->_flags &= ~kFlagsActive; +			anim->_flags |= kFlagsRemove;  			if (obj) {  				_gfx->showGfxObj(obj, false);  			}  		}  	} +	debugC(9, kDebugExec, "Parallaction_ns::drawAnimations done()\n"); +  	return;  } +void ProgramExec::runScript(ProgramPtr script, AnimationPtr a) { +	debugC(9, kDebugExec, "runScript(Animation = %s)", a->_name); + +	_ctxt.ip = script->_ip; +	_ctxt.anim = a; +	_ctxt.program = script; +	_ctxt.suspend = false; +	_ctxt.modCounter = _modCounter; + +	InstructionList::iterator inst; +	for ( ; (a->_flags & kFlagsActing) ; ) { + +		inst = _ctxt.ip; +		_ctxt.inst = inst; +		_ctxt.ip++; + +		debugC(9, kDebugExec, "inst [%02i] %s\n", (*inst)->_index, _instructionNames[(*inst)->_index - 1]); + +		script->_status = kProgramRunning; + +		(*_opcodes[(*inst)->_index])(); + +		if (_ctxt.suspend) +			break; -void Parallaction_ns::runScripts() { -	if (_engineFlags & kEnginePauseJobs) { -		return;  	} +	script->_ip = _ctxt.ip; -	debugC(9, kDebugExec, "runScripts"); +} -	static uint16 modCounter = 0; +void ProgramExec::runScripts(ProgramList::iterator first, ProgramList::iterator last) { +	if (_engineFlags & kEnginePauseJobs) { +		return; +	} -	for (ProgramList::iterator it = _location._programs.begin(); it != _location._programs.end(); it++) { +	for (ProgramList::iterator it = first; it != last; it++) {  		AnimationPtr a = (*it)->_anim; @@ -375,116 +423,182 @@ void Parallaction_ns::runScripts() {  		if ((a->_flags & kFlagsActing) == 0)  			continue; -		InstructionList::iterator inst = (*it)->_ip; -		while (((*inst)->_index != INST_SHOW) && (a->_flags & kFlagsActing)) { +		runScript(*it, a); -			(*it)->_status = kProgramRunning; +		if (a->_flags & kFlagsCharacter) +			a->_z = a->_top + a->height(); +	} -			debugC(9, kDebugExec, "Animation: %s, instruction: %i", a->_name, (*inst)->_index); //_instructionNamesRes[(*inst)->_index - 1]); +	_modCounter++; -			_instRunCtxt.inst = inst; -			_instRunCtxt.anim = AnimationPtr(a); -			_instRunCtxt.program = *it; -			_instRunCtxt.modCounter = modCounter; -			_instRunCtxt.suspend = false; +	return; +} + +void CommandExec::runList(CommandList::iterator first, CommandList::iterator last) { -			(*_instructionOpcodes[(*inst)->_index])(); +	uint32 useFlags = 0; +	bool useLocalFlags; -			inst = _instRunCtxt.inst;		// handles endloop correctly +	_ctxt.suspend = false; -			if (_instRunCtxt.suspend) -				goto label1; +	for ( ; first != last; first++) { +		if (_engineFlags & kEngineQuit) +			break; -			inst++; +		CommandPtr cmd = *first; + +		if (cmd->_flagsOn & kFlagsGlobal) { +			useFlags = _commandFlags | kFlagsGlobal; +			useLocalFlags = false; +		} else { +			useFlags = _vm->getLocationFlags(); +			useLocalFlags = true;  		} -		(*it)->_ip = ++inst; +		bool onMatch = (cmd->_flagsOn & useFlags) == cmd->_flagsOn; +		bool offMatch = (cmd->_flagsOff & ~useFlags) == cmd->_flagsOff; -label1: -		if (a->_flags & kFlagsCharacter) -			a->_z = a->_top + a->height(); -	} +		debugC(3, kDebugExec, "runCommands[%i] (on: %x, off: %x), (%s = %x)", cmd->_id,  cmd->_flagsOn, cmd->_flagsOff, +			useLocalFlags ? "LOCALFLAGS" : "GLOBALFLAGS", useFlags); + +		if (!onMatch || !offMatch) continue; + +		_ctxt.z = _execZone; +		_ctxt.cmd = cmd; + +		(*_opcodes[cmd->_id])(); -	_char._ani->_z = _char._ani->height() + _char._ani->_top; -	if (_char._ani->gfxobj) { -		_char._ani->gfxobj->z = _char._ani->_z; +		if (_ctxt.suspend) { +			createSuspendList(++first, last); +			return; +		}  	} -	modCounter++; -	return;  } - -void Parallaction::runCommands(CommandList& list, ZonePtr z) { -	if (list.size() == 0) +void CommandExec::run(CommandList& list, ZonePtr z) { +	if (list.size() == 0) { +		debugC(3, kDebugExec, "runCommands: nothing to do");  		return; +	} -	debugC(3, kDebugExec, "runCommands"); - -	CommandList::iterator it = list.begin(); -	for ( ; it != list.end(); it++) { +	_execZone = z; -		CommandPtr cmd = *it; -		uint32 v8 = getLocationFlags(); +	debugC(3, kDebugExec, "runCommands starting"); +	runList(list.begin(), list.end()); +	debugC(3, kDebugExec, "runCommands completed"); +} -		if (_engineFlags & kEngineQuit) -			break; +void CommandExec::createSuspendList(CommandList::iterator first, CommandList::iterator last) { +	if (first == last) { +		return; +	} -		if (cmd->_flagsOn & kFlagsGlobal) { -			v8 = _commandFlags | kFlagsGlobal; -		} +	debugC(3, kDebugExec, "CommandExec::createSuspendList()"); -		if ((cmd->_flagsOn & v8) != cmd->_flagsOn) continue; -		if ((cmd->_flagsOff & ~v8) != cmd->_flagsOff) continue; +	_suspendedCtxt.valid = true; +	_suspendedCtxt.first = first; +	_suspendedCtxt.last = last; +	_suspendedCtxt.zone = _execZone; +} -//		debugC(3, kDebugExec, "runCommands[%i]: %s (on: %x, off: %x)", cmd->_id, _commandsNamesRes[cmd->_id-1], cmd->_flagsOn, cmd->_flagsOff); +void CommandExec::cleanSuspendedList() { +	debugC(3, kDebugExec, "CommandExec::cleanSuspended()"); -		_cmdRunCtxt.z = z; -		_cmdRunCtxt.cmd = cmd; +	_suspendedCtxt.valid = false; +	_suspendedCtxt.first = _suspendedCtxt.last; +	_suspendedCtxt.zone = nullZonePtr; +} -		(*_commandOpcodes[cmd->_id])(); +void CommandExec::runSuspended() { +	if (_engineFlags & kEngineWalking) { +		return;  	} -	debugC(3, kDebugExec, "runCommands completed"); +	if (_suspendedCtxt.valid) { +		debugC(3, kDebugExec, "CommandExec::runSuspended()"); -	return; +		_execZone = _suspendedCtxt.zone; +		runList(_suspendedCtxt.first, _suspendedCtxt.last); +		cleanSuspendedList(); +	} +} + +CommandExec_ns::CommandExec_ns(Parallaction_ns* vm) : _vm(vm) {  } +CommandExec_ns::~CommandExec_ns() { + +}  //  //	ZONE TYPE: EXAMINE  // -void Parallaction::displayComment(ExamineData *data) { +void Parallaction::enterCommentMode(ZonePtr z) { +	if (!z) { +		return; +	} + +	_commentZone = z; + +	ExamineData *data = _commentZone->u.examine; +  	if (!data->_description) {  		return;  	} -	int id; +	// TODO: move this balloons stuff into DialogueManager and BalloonManager +	if (getGameType() == GType_Nippon) { +		int id; +		if (data->_filename) { +			if (data->_cnv == 0) { +				data->_cnv = _disk->loadStatic(data->_filename); +			} -	if (data->_filename) { -		if (data->_cnv == 0) { -			data->_cnv = _disk->loadStatic(data->_filename); +			_gfx->setHalfbriteMode(true); +			_balloonMan->setSingleBalloon(data->_description, 0, 90, 0, 0); +			Common::Rect r; +			data->_cnv->getRect(0, r); +			id = _gfx->setItem(data->_cnv, 140, (_screenHeight - r.height())/2); +			_gfx->setItemFrame(id, 0); +			id = _gfx->setItem(_char._head, 100, 152); +			_gfx->setItemFrame(id, 0); +		} else { +			_balloonMan->setSingleBalloon(data->_description, 140, 10, 0, 0); +			id = _gfx->setItem(_char._talk, 190, 80); +			_gfx->setItemFrame(id, 0);  		} - -		_gfx->setHalfbriteMode(true); -		_gfx->setSingleBalloon(data->_description, 0, 90, 0, 0); -		Common::Rect r; -		data->_cnv->getRect(0, r); -		id = _gfx->setItem(data->_cnv, 140, (_screenHeight - r.height())/2); -		_gfx->setItemFrame(id, 0); -		id = _gfx->setItem(_char._head, 100, 152); -		_gfx->setItemFrame(id, 0); -	} else { -		_gfx->setSingleBalloon(data->_description, 140, 10, 0, 0); -		id = _gfx->setItem(_char._talk, 190, 80); +	} else +	if (getGameType() == GType_BRA) { +		_balloonMan->setSingleBalloon(data->_description, 0, 0, 1, 0); +		int id = _gfx->setItem(_char._talk, 10, 80);  		_gfx->setItemFrame(id, 0);  	}  	_input->_inputMode = Input::kInputModeComment;  } +void Parallaction::exitCommentMode() { +	_input->_inputMode = Input::kInputModeGame; + +	hideDialogueStuff(); +	_gfx->setHalfbriteMode(false); + +	_cmdExec->run(_commentZone->_commands, _commentZone); +	_commentZone = nullZonePtr; +} + +void Parallaction::runCommentFrame() { +	if (_input->_inputMode != Input::kInputModeComment) { +		return; +	} + +	if (_input->getLastButtonEvent() == kMouseLeftUp) { +		exitCommentMode(); +	} +}  uint16 Parallaction::runZone(ZonePtr z) { @@ -496,8 +610,8 @@ uint16 Parallaction::runZone(ZonePtr z) {  	switch(subtype) {  	case kZoneExamine: -		displayComment(z->u.examine); -		break; +		enterCommentMode(z); +		return 0;  	case kZoneGet:  		if (z->_flags & kFlagsFixed) break; @@ -518,14 +632,13 @@ uint16 Parallaction::runZone(ZonePtr z) {  		break;  	case kZoneSpeak: -		runDialogue(z->u.speak); -		break; - +		enterDialogueMode(z); +		return 0;  	}  	debugC(3, kDebugExec, "runZone completed"); -	runCommands(z->_commands, z); +	_cmdExec->run(z->_commands, z);  	return 0;  } @@ -652,11 +765,34 @@ ZonePtr Parallaction::hitZone(uint32 type, uint16 x, uint16 y) {  } -void Parallaction_ns::initOpcodes() { +void CommandExec_ns::init() { +	Common::Array<const Opcode*> *table = 0; + +	SetOpcodeTable(_opcodes); +	COMMAND_OPCODE(invalid); +	COMMAND_OPCODE(set); +	COMMAND_OPCODE(clear); +	COMMAND_OPCODE(start); +	COMMAND_OPCODE(speak); +	COMMAND_OPCODE(get); +	COMMAND_OPCODE(location); +	COMMAND_OPCODE(open); +	COMMAND_OPCODE(close); +	COMMAND_OPCODE(on); +	COMMAND_OPCODE(off); +	COMMAND_OPCODE(call); +	COMMAND_OPCODE(toggle); +	COMMAND_OPCODE(drop); +	COMMAND_OPCODE(quit); +	COMMAND_OPCODE(move); +	COMMAND_OPCODE(stop); +} + +void ProgramExec_ns::init() {  	Common::Array<const Opcode*> *table = 0; -	SetOpcodeTable(_instructionOpcodes); +	SetOpcodeTable(_opcodes);  	INSTRUCTION_OPCODE(invalid);  	INSTRUCTION_OPCODE(on);  	INSTRUCTION_OPCODE(off); @@ -666,7 +802,7 @@ void Parallaction_ns::initOpcodes() {  	INSTRUCTION_OPCODE(set);		// f  	INSTRUCTION_OPCODE(loop);  	INSTRUCTION_OPCODE(endloop); -	INSTRUCTION_OPCODE(null); +	INSTRUCTION_OPCODE(show);  	INSTRUCTION_OPCODE(inc);  	INSTRUCTION_OPCODE(inc);		// dec  	INSTRUCTION_OPCODE(set); @@ -678,25 +814,13 @@ void Parallaction_ns::initOpcodes() {  	INSTRUCTION_OPCODE(move);  	INSTRUCTION_OPCODE(endscript); -	SetOpcodeTable(_commandOpcodes); -	COMMAND_OPCODE(invalid); -	COMMAND_OPCODE(set); -	COMMAND_OPCODE(clear); -	COMMAND_OPCODE(start); -	COMMAND_OPCODE(speak); -	COMMAND_OPCODE(get); -	COMMAND_OPCODE(location); -	COMMAND_OPCODE(open); -	COMMAND_OPCODE(close); -	COMMAND_OPCODE(on); -	COMMAND_OPCODE(off); -	COMMAND_OPCODE(call); -	COMMAND_OPCODE(toggle); -	COMMAND_OPCODE(drop); -	COMMAND_OPCODE(quit); -	COMMAND_OPCODE(move); -	COMMAND_OPCODE(stop);  } +ProgramExec_ns::ProgramExec_ns(Parallaction_ns *vm) : _vm(vm) { +	_instructionNames = _instructionNamesRes_ns; +} + +ProgramExec_ns::~ProgramExec_ns() { +}  }	// namespace Parallaction diff --git a/engines/parallaction/font.cpp b/engines/parallaction/font.cpp index 91848b30a4..e84dad34aa 100644 --- a/engines/parallaction/font.cpp +++ b/engines/parallaction/font.cpp @@ -35,6 +35,7 @@ extern byte _amigaTopazFont[];  class BraFont : public Font { +protected:  	byte	*_cp;  	uint	_bufPitch; @@ -45,15 +46,15 @@ class BraFont : public Font {  	uint	*_offsets;  	byte	*_data; - -	static  byte _charMap[]; +	const byte	*_charMap;  	byte mapChar(byte c) { -		return _charMap[c]; +		return (_charMap == 0) ? c : _charMap[c];  	}  public: -	BraFont(Common::ReadStream &stream) { +	BraFont(Common::ReadStream &stream, const byte *charMap = 0) { +		_charMap = charMap;  		_numGlyphs = stream.readByte();  		_height = stream.readUint32BE(); @@ -137,7 +138,7 @@ public:  }; -byte BraFont::_charMap[] = { +const byte _braDosFullCharMap[256] = {  // 0  	0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,  // 1 @@ -172,6 +173,111 @@ byte BraFont::_charMap[] = {  	0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34  }; +const byte _braDosDemoComicCharMap[] = { +// 0 +	0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// 1 +	0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// 2 +	0x34, 0x49, 0x48, 0x34, 0x34, 0x34, 0x34, 0x47, 0x34, 0x34, 0x34, 0x34, 0x40, 0x34, 0x3F, 0x34, +// 3 +	0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x46, 0x45, 0x34, 0x34, 0x34, 0x42, +// 4 +	0x34, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, +// 5 +	0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x34, 0x34, 0x34, 0x34, 0x34, +// 6 +	0x34, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, +// 7 +	0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x34, 0x34, 0x34, 0x34, +// 8 +	0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// 9 +	0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// A +	0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// B +	0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// C +	0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// D +	0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// E +	0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// F +	0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34 +}; + +const byte _braDosDemoRussiaCharMap[] = { +// 0 +	0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// 1 +	0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// 2 +	0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// 3 +	0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// 4 +	0x34, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, +// 5 +	0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x34, 0x34, 0x34, 0x34, 0x34, +// 6 +	0x34, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, +// 7 +	0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x34, 0x34, 0x34, 0x34, +// 8 +	0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// 9 +	0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// A +	0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// B +	0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// C +	0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// D +	0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// E +	0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// F +	0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34 +}; + +class BraInventoryObjects : public BraFont, public Frames { + +public: +	BraInventoryObjects(Common::ReadStream &stream) : BraFont(stream) { +	} + +	// Frames implementation +	uint16	getNum() { +		return _numGlyphs; +	} + +	byte*	getData(uint16 index) { +		assert(index < _numGlyphs); +		return _data + (_height * _widths[index]) * index;; +	} + +	void	getRect(uint16 index, Common::Rect &r) { +		assert(index < _numGlyphs); +		r.left = 0; +		r.top = 0; +		r.setWidth(_widths[index]); +		r.setHeight(_height); +	} + +	uint	getRawSize(uint16 index) { +		assert(index < _numGlyphs); +		return _widths[index] * _height; +	} + +	uint	getSize(uint16 index) { +		assert(index < _numGlyphs); +		return _widths[index] * _height; +	} + +};  class DosFont : public Font { @@ -537,7 +643,19 @@ Font *AmigaDisk_ns::createFont(const char *name, Common::SeekableReadStream &str  Font *DosDisk_br::createFont(const char *name, Common::ReadStream &stream) {  //	printf("DosDisk_br::createFont(%s)\n", name); -	return new BraFont(stream); +	Font *font; + +	if (_vm->getFeatures() & GF_DEMO) { +		if (!scumm_stricmp(name, "russia")) { +			font = new BraFont(stream, _braDosDemoRussiaCharMap); +		} else { +			font = new BraFont(stream, _braDosDemoComicCharMap); +		} +	} else { +		font = new BraFont(stream, _braDosFullCharMap); +	} + +	return font;  }  Font *AmigaDisk_br::createFont(const char *name, Common::SeekableReadStream &stream) { @@ -545,6 +663,12 @@ Font *AmigaDisk_br::createFont(const char *name, Common::SeekableReadStream &str  	return new AmigaFont(stream);  } +GfxObj* DosDisk_br::createInventoryObjects(Common::SeekableReadStream &stream) { +	Frames *frames = new BraInventoryObjects(stream); +	return new GfxObj(0, frames, "inventoryobjects"); +} + +  void Parallaction_ns::initFonts() {  	if (getPlatform() == Common::kPlatformPC) { @@ -573,8 +697,8 @@ void Parallaction_br::initFonts() {  		// fonts/sonya/18  		// fonts/vanya/16 -		_menuFont = _disk->loadFont("fonts/natasha/16"); -		_dialogueFont = _disk->loadFont("fonts/sonya/18"); +		_menuFont = _disk->loadFont("natasha"); +		_dialogueFont = _disk->loadFont("vanya");  		Common::MemoryReadStream stream(_amigaTopazFont, 2600, false);  		_labelFont = new AmigaFont(stream);  	} diff --git a/engines/parallaction/gfxbase.cpp b/engines/parallaction/gfxbase.cpp index 6599a1f81c..1c373dda44 100644 --- a/engines/parallaction/gfxbase.cpp +++ b/engines/parallaction/gfxbase.cpp @@ -32,7 +32,7 @@  namespace Parallaction { -GfxObj::GfxObj(uint objType, Frames *frames, const char* name) : type(objType), _frames(frames), x(0), y(0), z(0), frame(0), layer(3), _flags(0), _keep(true) { +GfxObj::GfxObj(uint objType, Frames *frames, const char* name) : _frames(frames), _keep(true), x(0), y(0), z(0), _flags(kGfxObjNormal), type(objType), frame(0), layer(3)  {  	if (name) {  		_name = strdup(name);  	} else { @@ -86,93 +86,265 @@ void GfxObj::clearFlags(uint32 flags) {  }  GfxObj* Gfx::loadAnim(const char *name) { -	Frames *frames = _disk->loadFrames(name); +	Frames* frames = _disk->loadFrames(name); +	assert(frames); +  	GfxObj *obj = new GfxObj(kGfxObjTypeAnim, frames, name);  	assert(obj); +	// animation Z is not set here, but controlled by game scripts and user interaction. +	// it is always >=0 and <screen height +	obj->transparentKey = 0; +	_gfxobjList.push_back(obj);  	return obj;  }  GfxObj* Gfx::loadGet(const char *name) { -	Frames *frames = _disk->loadStatic(name); -	GfxObj *obj = new GfxObj(kGfxObjTypeGet, frames, name); +	GfxObj *obj = _disk->loadStatic(name);  	assert(obj); +	obj->z = kGfxObjGetZ;	// this preset Z value ensures that get zones are drawn after doors but before animations +	obj->type = kGfxObjTypeGet; +	obj->transparentKey = 0; +	_gfxobjList.push_back(obj);  	return obj;  }  GfxObj* Gfx::loadDoor(const char *name) {  	Frames *frames = _disk->loadFrames(name); +	assert(frames); +  	GfxObj *obj = new GfxObj(kGfxObjTypeDoor, frames, name);  	assert(obj); +	obj->z = kGfxObjDoorZ;	// this preset Z value ensures that doors are drawn first +	obj->transparentKey = 0; +	_gfxobjList.push_back(obj);  	return obj;  } -void Gfx::clearGfxObjects() { -	_gfxobjList[0].clear(); -	_gfxobjList[1].clear(); -	_gfxobjList[2].clear(); +void Gfx::clearGfxObjects(uint filter) { + +	GfxObjList::iterator b = _gfxobjList.begin(); +	GfxObjList::iterator e = _gfxobjList.end(); + +	for ( ; b != e; ) { +		if (((*b)->_flags & filter) != 0) { +			b = _gfxobjList.erase(b); +		} else { +			b++; +		} +	} +  }  void Gfx::showGfxObj(GfxObj* obj, bool visible) { -	if (!obj || obj->isVisible() == visible) { +	if (!obj) {  		return;  	}  	if (visible) {  		obj->setFlags(kGfxObjVisible); -		_gfxobjList[obj->type].push_back(obj);  	} else {  		obj->clearFlags(kGfxObjVisible); -		_gfxobjList[obj->type].remove(obj);  	} -  } -bool compareAnimationZ(const GfxObj* a1, const GfxObj* a2) { +bool compareZ(const GfxObj* a1, const GfxObj* a2) {  	return a1->z < a2->z;  }  void Gfx::sortAnimations() { -	GfxObjList::iterator first = _gfxobjList[kGfxObjTypeAnim].begin(); -	GfxObjList::iterator last = _gfxobjList[kGfxObjTypeAnim].end(); +	GfxObjList::iterator first = _gfxobjList.begin(); +	GfxObjList::iterator last = _gfxobjList.end(); -	Common::sort(first, last, compareAnimationZ); +	Common::sort(first, last, compareZ);  } -void Gfx::drawGfxObjects(Graphics::Surface &surf) { + +void Gfx::drawGfxObject(GfxObj *obj, Graphics::Surface &surf, bool scene) { +	if (!obj->isVisible()) { +		return; +	}  	Common::Rect rect;  	byte *data; +	uint scrollX = (scene) ? -_varScrollX : 0; + +	obj->getRect(obj->frame, rect); +	rect.translate(obj->x + scrollX, obj->y); +	data = obj->getData(obj->frame); + +	if (obj->getSize(obj->frame) == obj->getRawSize(obj->frame)) { +		blt(rect, data, &surf, obj->layer, obj->transparentKey); +	} else { +		unpackBlt(rect, data, obj->getRawSize(obj->frame), &surf, obj->layer, obj->transparentKey); +	} + +} + + +void Gfx::drawGfxObjects(Graphics::Surface &surf) { +  	sortAnimations();  	// TODO: some zones don't appear because of wrong masking (3 or 0?) -	// TODO: Dr.Ki is not visible inside the club + +	GfxObjList::iterator b = _gfxobjList.begin(); +	GfxObjList::iterator e = _gfxobjList.end(); + +	for (; b != e; b++) { +		drawGfxObject(*b, surf, true); +	} +} -	for (uint i = 0; i < 3; i++) { -		GfxObjList::iterator b = _gfxobjList[i].begin(); -		GfxObjList::iterator e = _gfxobjList[i].end(); +void Gfx::drawText(Font *font, Graphics::Surface* surf, uint16 x, uint16 y, const char *text, byte color) { +	byte *dst = (byte*)surf->getBasePtr(x, y); +	font->setColor(color); +	font->drawString(dst, surf->w, text); +} + -		for (; b != e; b++) { -			GfxObj *obj = *b; -			if (obj->isVisible()) { -				obj->getRect(obj->frame, rect); -				rect.translate(obj->x - _varScrollX, obj->y); -				data = obj->getData(obj->frame); -				if (obj->getSize(obj->frame) == obj->getRawSize(obj->frame)) { -					blt(rect, data, &surf, obj->layer, 0); -				} else { -					unpackBlt(rect, data, obj->getRawSize(obj->frame), &surf, obj->layer, 0); +#if 0 +void Gfx::unpackBlt(const Common::Rect& r, byte *data, uint size, Graphics::Surface *surf, uint16 z, byte transparentColor) { + +	byte *d = _unpackedBitmap; + +	while (size > 0) { + +		uint8 p = *data++; +		size--; +		uint8 color = p & 0xF; +		uint8 repeat = (p & 0xF0) >> 4; +		if (repeat == 0) { +			repeat = *data++; +			size--; +		} + +		memset(d, color, repeat); +		d += repeat; +	} + +	blt(r, _unpackedBitmap, surf, z, transparentColor); +} +#endif +void Gfx::unpackBlt(const Common::Rect& r, byte *data, uint size, Graphics::Surface *surf, uint16 z, byte transparentColor) { + +	byte *d = _unpackedBitmap; +	uint pixelsLeftInLine = r.width(); + +	while (size > 0) { +		uint8 p = *data++; +		size--; +		uint8 color = p & 0xF; +		uint8 repeat = (p & 0xF0) >> 4; +		if (repeat == 0) { +			repeat = *data++; +			size--; +		} +		if (repeat == 0) { +			// end of line +			repeat = pixelsLeftInLine; +			pixelsLeftInLine = r.width(); +		} else { +			pixelsLeftInLine -= repeat; +		} + +		memset(d, color, repeat); +		d += repeat; +	} + +	blt(r, _unpackedBitmap, surf, z, transparentColor); +} + + +void Gfx::blt(const Common::Rect& r, byte *data, Graphics::Surface *surf, uint16 z, byte transparentColor) { + +	Common::Point dp; +	Common::Rect q(r); + +	Common::Rect clipper(surf->w, surf->h); + +	q.clip(clipper); +	if (!q.isValidRect()) return; + +	dp.x = q.left; +	dp.y = q.top; + +	q.translate(-r.left, -r.top); + +	byte *s = data + q.left + q.top * r.width(); +	byte *d = (byte*)surf->getBasePtr(dp.x, dp.y); + +	uint sPitch = r.width() - q.width(); +	uint dPitch = surf->w - q.width(); + + +	if (_varRenderMode == 2) { + +		for (uint16 i = 0; i < q.height(); i++) { + +			for (uint16 j = 0; j < q.width(); j++) { +				if (*s != transparentColor) { +					if (_backgroundInfo->mask.data && (z < LAYER_FOREGROUND)) { +						byte v = _backgroundInfo->mask.getValue(dp.x + j, dp.y + i); +						if (z >= v) *d = 5; +					} else { +						*d = 5; +					}  				} + +				s++; +				d++;  			} + +			s += sPitch; +			d += dPitch; +		} + +    } else { +		if (_backgroundInfo->mask.data && (z < LAYER_FOREGROUND)) { + +			for (uint16 i = 0; i < q.height(); i++) { + +				for (uint16 j = 0; j < q.width(); j++) { +					if (*s != transparentColor) { +						byte v = _backgroundInfo->mask.getValue(dp.x + j, dp.y + i); +						if (z >= v) *d = *s; +					} + +					s++; +					d++; +				} + +				s += sPitch; +				d += dPitch; +			} + +		} else { + +			for (uint16 i = q.top; i < q.bottom; i++) { +				for (uint16 j = q.left; j < q.right; j++) { +					if (*s != transparentColor) +						*d = *s; + +					s++; +					d++; +				} + +				s += sPitch; +				d += dPitch; +			} +  		}  	} +  } +  } // namespace Parallaction diff --git a/engines/parallaction/graphics.cpp b/engines/parallaction/graphics.cpp index 58fb02a750..c19d6ae5e5 100644 --- a/engines/parallaction/graphics.cpp +++ b/engines/parallaction/graphics.cpp @@ -33,6 +33,11 @@  namespace Parallaction { +// this is the size of the receiving buffer for unpacked frames, +// since BRA uses some insanely big animations. +#define MAXIMUM_UNPACKED_BITMAP_SIZE	640*401 + +  void Gfx::registerVar(const Common::String &name, int32 initialValue) {  	if (_vars.contains(name)) {  		warning("Variable '%s' already registered, ignoring initial value.\n", name.c_str()); @@ -64,10 +69,6 @@ int32 Gfx::getVar(const Common::String &name) {  #define	LABEL_TRANSPARENT_COLOR 0xFF -#define	BALLOON_TRANSPARENT_COLOR 2 - - -int16 Gfx::_dialogueBalloonX[5] = { 80, 120, 150, 150, 150 };  void halfbritePixel(int x, int y, int color, void *data) {  	byte *buffer = (byte*)data; @@ -152,6 +153,13 @@ void Palette::setEntry(uint index, int red, int green, int blue) {  		_data[index*3+2] = blue & 0xFF;  } +void Palette::getEntry(uint index, int &red, int &green, int &blue) { +	assert(index < _colors); +	red   = _data[index*3]; +	green = _data[index*3+1]; +	blue  = _data[index*3+2]; +} +  void Palette::makeGrayscale() {  	byte v;  	for (uint16 i = 0; i < _colors; i++) { @@ -238,37 +246,6 @@ void Palette::rotate(uint first, uint last, bool forward) {  } -#define BALLOON_TAIL_WIDTH	12 -#define BALLOON_TAIL_HEIGHT	10 - - -byte _resBalloonTail[2][BALLOON_TAIL_WIDTH*BALLOON_TAIL_HEIGHT] = { -	{ -	  0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, -	  0x02, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, -	  0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, -	  0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, -	  0x02, 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, -	  0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, -	  0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, -	  0x02, 0x00, 0x01, 0x01, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, -	  0x00, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, -	  0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, -	}, -	{ -	  0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, -	  0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x02, 0x02, 0x02, -	  0x02, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, -	  0x02, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, -	  0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, -	  0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, -	  0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, -	  0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, -	  0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x00, 0x02, 0x02, -	  0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x02, 0x02 -	} -}; -  void Gfx::setPalette(Palette pal) {  	byte sysPal[256*4]; @@ -292,7 +269,7 @@ void Gfx::animatePalette() {  	PaletteFxRange *range;  	for (uint16 i = 0; i < 4; i++) { -		range = &_backgroundInfo.ranges[i]; +		range = &_backgroundInfo->ranges[i];  		if ((range->_flags & 1) == 0) continue;		// animated palette  		range->_timer += range->_step * 2;	// update timer @@ -337,10 +314,14 @@ void Gfx::setProjectorProgram(int16 *data) {  }  void Gfx::drawInventory() { - +/*  	if ((_engineFlags & kEngineInventory) == 0) {  		return;  	} +*/ +	if (_vm->_input->_inputMode != Input::kInputModeInventory) { +		return; +	}  	Common::Rect r;  	_vm->_inventoryRenderer->getRect(r); @@ -356,21 +337,19 @@ void Gfx::drawItems() {  	Graphics::Surface *surf = g_system->lockScreen();  	for (uint i = 0; i < _numItems; i++) { -		blt(_items[i].rect, _items[i].data->getData(_items[i].frame), surf, LAYER_FOREGROUND, _items[i].transparentColor); +		drawGfxObject(_items[i].data, *surf, false);  	}  	g_system->unlockScreen();  }  void Gfx::drawBalloons() { -	if (_numBalloons == 0) { +	if (_balloons.size() == 0) {  		return;  	}  	Graphics::Surface *surf = g_system->lockScreen(); -	for (uint i = 0; i < _numBalloons; i++) { -		Common::Rect r(_balloons[i].surface.w, _balloons[i].surface.h); -		r.moveTo(_balloons[i].x, _balloons[i].y); -		blt(r, (byte*)_balloons[i].surface.getBasePtr(0, 0), surf, LAYER_FOREGROUND, BALLOON_TRANSPARENT_COLOR); +	for (uint i = 0; i < _balloons.size(); i++) { +		drawGfxObject(_balloons[i], *surf, false);  	}  	g_system->unlockScreen();  } @@ -380,29 +359,37 @@ void Gfx::clearScreen() {  }  void Gfx::beginFrame() { - -	int32 oldBackgroundMode = _varBackgroundMode; -	_varBackgroundMode = getVar("background_mode"); - -	if (oldBackgroundMode != _varBackgroundMode) { -		switch (_varBackgroundMode) { -		case 1: -			_bitmapMask.free(); -			break; -		case 2: -			_bitmapMask.create(_backgroundInfo.width, _backgroundInfo.height, 1); -			byte *data = (byte*)_bitmapMask.pixels; -			for (uint y = 0; y < _bitmapMask.h; y++) { -				for (uint x = 0; x < _bitmapMask.w; x++) { -					*data++ = _backgroundInfo.mask.getValue(x, y); +	_skipBackground = (_backgroundInfo->bg.pixels == 0);	// don't render frame if background is missing + +	if (!_skipBackground) { +		int32 oldBackgroundMode = _varBackgroundMode; +		_varBackgroundMode = getVar("background_mode"); +		if (oldBackgroundMode != _varBackgroundMode) { +			switch (_varBackgroundMode) { +			case 1: +				_bitmapMask.free(); +				break; +			case 2: +				_bitmapMask.create(_backgroundInfo->width, _backgroundInfo->height, 1); +				byte *data = (byte*)_bitmapMask.pixels; +				for (uint y = 0; y < _bitmapMask.h; y++) { +					for (uint x = 0; x < _bitmapMask.w; x++) { +						*data++ = _backgroundInfo->mask.getValue(x, y); +					}  				} +				break;  			} -			break;  		}  	} +	_varDrawPathZones = getVar("draw_path_zones"); +	if (_varDrawPathZones == 1 && _vm->getGameType() != GType_BRA) { +		setVar("draw_path_zones", 0); +		_varDrawPathZones = 0; +		warning("Path zones are supported only in Big Red Adventure"); +	} -	if (_vm->_screenWidth >= _backgroundInfo.width) { +	if (_skipBackground || (_vm->_screenWidth >= _backgroundInfo->width)) {  		_varScrollX = 0;  	} else {  		_varScrollX = getVar("scroll_x"); @@ -427,24 +414,38 @@ int32 Gfx::getRenderMode(const char *type) {  void Gfx::updateScreen() { -	// background may not cover the whole screen, so adjust bulk update size -	uint w = MIN(_vm->_screenWidth, (int32)_backgroundInfo.width); -	uint h = MIN(_vm->_screenHeight, (int32)_backgroundInfo.height); - -	byte *backgroundData = 0; -	uint16 backgroundPitch = 0; -	switch (_varBackgroundMode) { -	case 1: -		backgroundData = (byte*)_backgroundInfo.bg.getBasePtr(_varScrollX, 0); -		backgroundPitch = _backgroundInfo.bg.pitch; -		break; -	case 2: -		backgroundData = (byte*)_bitmapMask.getBasePtr(_varScrollX, 0); -		backgroundPitch = _bitmapMask.pitch; -		break; +	if (!_skipBackground) { +		// background may not cover the whole screen, so adjust bulk update size +		uint w = MIN(_vm->_screenWidth, (int32)_backgroundInfo->width); +		uint h = MIN(_vm->_screenHeight, (int32)_backgroundInfo->height); + +		byte *backgroundData = 0; +		uint16 backgroundPitch = 0; +		switch (_varBackgroundMode) { +		case 1: +			backgroundData = (byte*)_backgroundInfo->bg.getBasePtr(_varScrollX, 0); +			backgroundPitch = _backgroundInfo->bg.pitch; +			break; +		case 2: +			backgroundData = (byte*)_bitmapMask.getBasePtr(_varScrollX, 0); +			backgroundPitch = _bitmapMask.pitch; +			break; +		} +		g_system->copyRectToScreen(backgroundData, backgroundPitch, _backgroundInfo->x, _backgroundInfo->y, w, h);  	} -	g_system->copyRectToScreen(backgroundData, backgroundPitch, _backgroundInfo.x, _backgroundInfo.y, w, h); +	if (_varDrawPathZones == 1) { +		Graphics::Surface *surf = g_system->lockScreen(); +		ZoneList::iterator b = _vm->_location._zones.begin(); +		ZoneList::iterator e = _vm->_location._zones.end(); +		for (; b != e; b++) { +			ZonePtr z = *b; +			if (z->_type & kZonePath) { +				surf->frameRect(Common::Rect(z->_left, z->_top, z->_right, z->_bottom), 2); +			} +		} +		g_system->unlockScreen(); +	}  	_varRenderMode = _varAnimRenderMode; @@ -498,17 +499,17 @@ void Gfx::patchBackground(Graphics::Surface &surf, int16 x, int16 y, bool mask)  	Common::Rect r(surf.w, surf.h);  	r.moveTo(x, y); -	uint16 z = (mask) ? _backgroundInfo.getLayer(y) : LAYER_FOREGROUND; -	blt(r, (byte*)surf.pixels, &_backgroundInfo.bg, z, 0); +	uint16 z = (mask) ? _backgroundInfo->getLayer(y) : LAYER_FOREGROUND; +	blt(r, (byte*)surf.pixels, &_backgroundInfo->bg, z, 0);  }  void Gfx::fillBackground(const Common::Rect& r, byte color) { -	_backgroundInfo.bg.fillRect(r, color); +	_backgroundInfo->bg.fillRect(r, color);  }  void Gfx::invertBackground(const Common::Rect& r) { -	byte *d = (byte*)_backgroundInfo.bg.getBasePtr(r.left, r.top); +	byte *d = (byte*)_backgroundInfo->bg.getBasePtr(r.left, r.top);  	for (int i = 0; i < r.height(); i++) {  		for (int j = 0; j < r.width(); j++) { @@ -516,146 +517,7 @@ void Gfx::invertBackground(const Common::Rect& r) {  			d++;  		} -		d += (_backgroundInfo.bg.pitch - r.width()); -	} - -} - -// this is the maximum size of an unpacked frame in BRA -byte _unpackedBitmap[640*401]; - -#if 0 -void Gfx::unpackBlt(const Common::Rect& r, byte *data, uint size, Graphics::Surface *surf, uint16 z, byte transparentColor) { - -	byte *d = _unpackedBitmap; - -	while (size > 0) { - -		uint8 p = *data++; -		size--; -		uint8 color = p & 0xF; -		uint8 repeat = (p & 0xF0) >> 4; -		if (repeat == 0) { -			repeat = *data++; -			size--; -		} - -		memset(d, color, repeat); -		d += repeat; -	} - -	blt(r, _unpackedBitmap, surf, z, transparentColor); -} -#endif -void Gfx::unpackBlt(const Common::Rect& r, byte *data, uint size, Graphics::Surface *surf, uint16 z, byte transparentColor) { - -	byte *d = _unpackedBitmap; -	uint pixelsLeftInLine = r.width(); - -	while (size > 0) { -		uint8 p = *data++; -		size--; -		uint8 color = p & 0xF; -		uint8 repeat = (p & 0xF0) >> 4; -		if (repeat == 0) { -			repeat = *data++; -			size--; -		} -		if (repeat == 0) { -			// end of line -			repeat = pixelsLeftInLine; -			pixelsLeftInLine = r.width(); -		} else { -			pixelsLeftInLine -= repeat; -		} - -		memset(d, color, repeat); -		d += repeat; -	} - -	blt(r, _unpackedBitmap, surf, z, transparentColor); -} - - -void Gfx::blt(const Common::Rect& r, byte *data, Graphics::Surface *surf, uint16 z, byte transparentColor) { - -	Common::Point dp; -	Common::Rect q(r); - -	Common::Rect clipper(surf->w, surf->h); - -	q.clip(clipper); -	if (!q.isValidRect()) return; - -	dp.x = q.left; -	dp.y = q.top; - -	q.translate(-r.left, -r.top); - -	byte *s = data + q.left + q.top * r.width(); -	byte *d = (byte*)surf->getBasePtr(dp.x, dp.y); - -	uint sPitch = r.width() - q.width(); -	uint dPitch = surf->w - q.width(); - - -	if (_varRenderMode == 2) { - -		for (uint16 i = 0; i < q.height(); i++) { - -			for (uint16 j = 0; j < q.width(); j++) { -				if (*s != transparentColor) { -					if (_backgroundInfo.mask.data && (z < LAYER_FOREGROUND)) { -						byte v = _backgroundInfo.mask.getValue(dp.x + j, dp.y + i); -						if (z >= v) *d = 5; -					} else { -						*d = 5; -					} -				} - -				s++; -				d++; -			} - -			s += sPitch; -			d += dPitch; -		} - -    } else { -		if (_backgroundInfo.mask.data && (z < LAYER_FOREGROUND)) { - -			for (uint16 i = 0; i < q.height(); i++) { - -				for (uint16 j = 0; j < q.width(); j++) { -					if (*s != transparentColor) { -						byte v = _backgroundInfo.mask.getValue(dp.x + j, dp.y + i); -						if (z >= v) *d = *s; -					} - -					s++; -					d++; -				} - -				s += sPitch; -				d += dPitch; -			} - -		} else { - -			for (uint16 i = q.top; i < q.bottom; i++) { -				for (uint16 j = q.left; j < q.right; j++) { -					if (*s != transparentColor) -						*d = *s; - -					s++; -					d++; -				} - -				s += sPitch; -				d += dPitch; -			} - -		} +		d += (_backgroundInfo->bg.pitch - r.width());  	}  } @@ -669,10 +531,9 @@ void setupLabelSurface(Graphics::Surface &surf, uint w, uint h) {  	surf.fillRect(Common::Rect(w,h), LABEL_TRANSPARENT_COLOR);  } -Label *Gfx::renderFloatingLabel(Font *font, char *text) { +uint Gfx::renderFloatingLabel(Font *font, char *text) { -	Label *label = new Label; -	Graphics::Surface *cnv = &label->_cnv; +	Graphics::Surface *cnv = new Graphics::Surface;  	uint w, h; @@ -698,80 +559,38 @@ Label *Gfx::renderFloatingLabel(Font *font, char *text) {  		drawText(font, cnv, 0, 0, text, 0);  	} -	return label; -} +	GfxObj *obj = new GfxObj(kGfxObjTypeLabel, new SurfaceToFrames(cnv), "floatingLabel"); +	obj->transparentKey = LABEL_TRANSPARENT_COLOR; +	obj->layer = LAYER_FOREGROUND; -uint Gfx::createLabel(Font *font, const char *text, byte color) { -	assert(_numLabels < MAX_NUM_LABELS); - -	Label *label = new Label; -	Graphics::Surface *cnv = &label->_cnv; - -	uint w, h; - -	if (_vm->getPlatform() == Common::kPlatformAmiga) { -		w = font->getStringWidth(text) + 2; -		h = font->height() + 2; - -		setupLabelSurface(*cnv, w, h); - -		drawText(font, cnv, 0, 2, text, 0); -		drawText(font, cnv, 2, 0, text, color); -	} else { -		w = font->getStringWidth(text); -		h = font->height(); - -		setupLabelSurface(*cnv, w, h); - -		drawText(font, cnv, 0, 0, text, color); -	} - -	uint id = _numLabels; -	_labels[id] = label; -	_numLabels++; +	uint id = _labels.size(); +	_labels.insert_at(id, obj);  	return id;  } -void Gfx::showLabel(uint id, int16 x, int16 y) { -	assert(id < _numLabels); -	_labels[id]->_visible = true; +void Gfx::showFloatingLabel(uint label) { +	assert(label < _labels.size()); -	if (x == CENTER_LABEL_HORIZONTAL) { -		x = CLIP<int16>((_vm->_screenWidth - _labels[id]->_cnv.w) / 2, 0, _vm->_screenWidth/2); -	} - -	if (y == CENTER_LABEL_VERTICAL) { -		y = CLIP<int16>((_vm->_screenHeight - _labels[id]->_cnv.h) / 2, 0, _vm->_screenHeight/2); -	} +	hideFloatingLabel(); -	_labels[id]->_pos.x = x; -	_labels[id]->_pos.y = y; -} +	_labels[label]->x = -1000; +	_labels[label]->y = -1000; +	_labels[label]->setFlags(kGfxObjVisible); -void Gfx::hideLabel(uint id) { -	assert(id < _numLabels); -	_labels[id]->_visible = false; +	_floatingLabel = label;  } -void Gfx::freeLabels() { -	for (uint i = 0; i < _numLabels; i++) { -		delete _labels[i]; +void Gfx::hideFloatingLabel() { +	if (_floatingLabel != NO_FLOATING_LABEL) { +		_labels[_floatingLabel]->clearFlags(kGfxObjVisible);  	} -	_numLabels = 0; +	_floatingLabel = NO_FLOATING_LABEL;  } -void Gfx::setFloatingLabel(Label *label) { -	_floatingLabel = label; - -	if (_floatingLabel) { -		_floatingLabel->resetPosition(); -	} -} -  void Gfx::updateFloatingLabel() { -	if (!_floatingLabel) { +	if (_floatingLabel == NO_FLOATING_LABEL) {  		return;  	} @@ -780,113 +599,115 @@ void Gfx::updateFloatingLabel() {  	Common::Point	cursor;  	_vm->_input->getCursorPos(cursor); +	Common::Rect r; +	_labels[_floatingLabel]->getRect(0, r); +  	if (_vm->_input->_activeItem._id != 0) { -		_si = cursor.x + 16 - _floatingLabel->_cnv.w/2; +		_si = cursor.x + 16 - r.width()/2;  		_di = cursor.y + 34;  	} else { -		_si = cursor.x + 8 - _floatingLabel->_cnv.w/2; +		_si = cursor.x + 8 - r.width()/2;  		_di = cursor.y + 21;  	}  	if (_si < 0) _si = 0;  	if (_di > 190) _di = 190; -	if (_floatingLabel->_cnv.w + _si > _vm->_screenWidth) -		_si = _vm->_screenWidth - _floatingLabel->_cnv.w; +	if (r.width() + _si > _vm->_screenWidth) +		_si = _vm->_screenWidth - r.width(); -	_floatingLabel->_pos.x = _si; -	_floatingLabel->_pos.y = _di; +	_labels[_floatingLabel]->x = _si; +	_labels[_floatingLabel]->y = _di;  } -void Gfx::drawLabels() { -	if ((!_floatingLabel) && (_numLabels == 0)) { -		return; -	} -	updateFloatingLabel(); -	Graphics::Surface* surf = g_system->lockScreen(); -	for (uint i = 0; i < _numLabels; i++) { -		if (_labels[i]->_visible) { -			Common::Rect r(_labels[i]->_cnv.w, _labels[i]->_cnv.h); -			r.moveTo(_labels[i]->_pos); -			blt(r, (byte*)_labels[i]->_cnv.getBasePtr(0, 0), surf, LAYER_FOREGROUND, LABEL_TRANSPARENT_COLOR); -		} -	} -	if (_floatingLabel) { -		Common::Rect r(_floatingLabel->_cnv.w, _floatingLabel->_cnv.h); -		r.moveTo(_floatingLabel->_pos); -        blt(r, (byte*)_floatingLabel->_cnv.getBasePtr(0, 0), surf, LAYER_FOREGROUND, LABEL_TRANSPARENT_COLOR); -	} +uint Gfx::createLabel(Font *font, const char *text, byte color) { +	assert(_labels.size() < MAX_NUM_LABELS); -	g_system->unlockScreen(); -} +	Graphics::Surface *cnv = new Graphics::Surface; -Label::Label() { -	resetPosition(); -	_visible = false; -} +	uint w, h; -Label::~Label() { -	free(); -} +	if (_vm->getPlatform() == Common::kPlatformAmiga) { +		w = font->getStringWidth(text) + 2; +		h = font->height() + 2; -void Label::free() { -	_cnv.free(); -	resetPosition(); -} +		setupLabelSurface(*cnv, w, h); -void Label::resetPosition() { -	_pos.x = -1000; -	_pos.y = -1000; -} +		drawText(font, cnv, 0, 2, text, 0); +		drawText(font, cnv, 2, 0, text, color); +	} else { +		w = font->getStringWidth(text); +		h = font->height(); +		setupLabelSurface(*cnv, w, h); -void Gfx::getStringExtent(Font *font, char *text, uint16 maxwidth, int16* width, int16* height) { +		drawText(font, cnv, 0, 0, text, color); +	} -	uint16 lines = 0; -	uint16 w = 0; -	*width = 0; +	GfxObj *obj = new GfxObj(kGfxObjTypeLabel, new SurfaceToFrames(cnv), "label"); +	obj->transparentKey = LABEL_TRANSPARENT_COLOR; +	obj->layer = LAYER_FOREGROUND; -	uint16 blankWidth = font->getStringWidth(" "); -	uint16 tokenWidth = 0; +	int id = _labels.size(); -	char token[MAX_TOKEN_LEN]; +	_labels.insert_at(id, obj); -	while (strlen(text) != 0) { +	return id; +} -		text = parseNextToken(text, token, MAX_TOKEN_LEN, "   ", true); -		tokenWidth = font->getStringWidth(token); +void Gfx::showLabel(uint id, int16 x, int16 y) { +	assert(id < _labels.size()); +	_labels[id]->setFlags(kGfxObjVisible); -		w += tokenWidth; +	Common::Rect r; +	_labels[id]->getRect(0, r); -		if (!scumm_stricmp(token, "%p")) { -			lines++; -		} else { -			if (w > maxwidth) { -				w -= tokenWidth; -				lines++; -				if (w > *width) -					*width = w; +	if (x == CENTER_LABEL_HORIZONTAL) { +		x = CLIP<int16>((_vm->_screenWidth - r.width()) / 2, 0, _vm->_screenWidth/2); +	} -				w = tokenWidth; -			} -		} +	if (y == CENTER_LABEL_VERTICAL) { +		y = CLIP<int16>((_vm->_screenHeight - r.height()) / 2, 0, _vm->_screenHeight/2); +	} -		w += blankWidth; -		text = Common::ltrim(text); +	_labels[id]->x = x; +	_labels[id]->y = y; +} + +void Gfx::hideLabel(uint id) { +	assert(id < _labels.size()); +	_labels[id]->clearFlags(kGfxObjVisible); +} + +void Gfx::freeLabels() { +	for (uint i = 0; i < _labels.size(); i++) { +		delete _labels[i];  	} +	_labels.clear(); +	_floatingLabel = NO_FLOATING_LABEL; +} -	if (*width < w) *width = w; -	*width += 10; +void Gfx::drawLabels() { +	if (_labels.size() == 0) { +		return; +	} -	*height = lines * 10 + 20; +	updateFloatingLabel(); -	return; +	Graphics::Surface* surf = g_system->lockScreen(); + +	for (uint i = 0; i < _labels.size(); i++) { +		drawGfxObject(_labels[i], *surf, false); +	} + +	g_system->unlockScreen();  } +  void Gfx::copyRect(const Common::Rect &r, Graphics::Surface &src, Graphics::Surface &dst) {  	byte *s = (byte*)src.getBasePtr(r.left, r.top); @@ -903,7 +724,7 @@ void Gfx::copyRect(const Common::Rect &r, Graphics::Surface &src, Graphics::Surf  }  void Gfx::grabBackground(const Common::Rect& r, Graphics::Surface &dst) { -	copyRect(r, _backgroundInfo.bg, dst); +	copyRect(r, _backgroundInfo->bg, dst);  } @@ -917,17 +738,20 @@ Gfx::Gfx(Parallaction* vm) :  	setPalette(_palette); -	_numBalloons = 0;  	_numItems = 0; -	_numLabels = 0; -	_floatingLabel = 0; +	_floatingLabel = NO_FLOATING_LABEL;  	_screenX = 0;  	_screenY = 0; +	_backgroundInfo = 0; +  	_halfbrite = false;  	_hbCircleRadius = 0; +	_unpackedBitmap = new byte[MAXIMUM_UNPACKED_BITMAP_SIZE]; +	assert(_unpackedBitmap); +  	registerVar("background_mode", 1);  	_varBackgroundMode = 1; @@ -937,26 +761,39 @@ Gfx::Gfx(Parallaction* vm) :  	registerVar("anim_render_mode", 1);  	registerVar("misc_render_mode", 1); +	registerVar("draw_path_zones", 0); + +	if ((_vm->getGameType() == GType_BRA) && (_vm->getPlatform() == Common::kPlatformPC)) { +	// this loads the backup palette needed by the PC version of BRA (see setBackground()). +		BackgroundInfo	paletteInfo; +		_disk->loadSlide(paletteInfo, "pointer"); +		_backupPal.clone(paletteInfo.palette); +	} +  	return;  }  Gfx::~Gfx() { -	freeBackground(); +	delete _backgroundInfo; + +	freeLabels(); + +	delete []_unpackedBitmap;  	return;  } -int Gfx::setItem(Frames* frames, uint16 x, uint16 y, byte transparentColor) { +int Gfx::setItem(GfxObj* frames, uint16 x, uint16 y, byte transparentColor) {  	int id = _numItems;  	_items[id].data = frames; -	_items[id].x = x; -	_items[id].y = y; - -	_items[id].transparentColor = transparentColor; +	_items[id].data->x = x; +	_items[id].data->y = y; +	_items[id].data->layer = LAYER_FOREGROUND; +	_items[id].data->transparentKey = transparentColor;  	_numItems++; @@ -965,223 +802,58 @@ int Gfx::setItem(Frames* frames, uint16 x, uint16 y, byte transparentColor) {  void Gfx::setItemFrame(uint item, uint16 f) {  	assert(item < _numItems); -	_items[item].frame = f; -	_items[item].data->getRect(f, _items[item].rect); -	_items[item].rect.moveTo(_items[item].x, _items[item].y); -} - -Gfx::Balloon* Gfx::getBalloon(uint id) { -	assert(id < _numBalloons); -	return &_balloons[id]; -} - -int Gfx::createBalloon(int16 w, int16 h, int16 winding, uint16 borderThickness) { -	assert(_numBalloons < 5); - -	int id = _numBalloons; - -	Gfx::Balloon *balloon = &_balloons[id]; - -	int16 real_h = (winding == -1) ? h : h + 9; -	balloon->surface.create(w, real_h, 1); -	balloon->surface.fillRect(Common::Rect(w, real_h), BALLOON_TRANSPARENT_COLOR); - -	Common::Rect r(w, h); -	balloon->surface.fillRect(r, 0); -	balloon->outerBox = r; - -	r.grow(-borderThickness); -	balloon->surface.fillRect(r, 1); -	balloon->innerBox = r; - -	if (winding != -1) { -		// draws tail -		// TODO: this bitmap tail should only be used for Dos games. Amiga should use a polygon fill. -		winding = (winding == 0 ? 1 : 0); -		Common::Rect s(BALLOON_TAIL_WIDTH, BALLOON_TAIL_HEIGHT); -		s.moveTo(r.width()/2 - 5, r.bottom - 1); -		blt(s, _resBalloonTail[winding], &balloon->surface, LAYER_FOREGROUND, BALLOON_TRANSPARENT_COLOR); -	} - -	_numBalloons++; - -	return id; -} - -int Gfx::setSingleBalloon(char *text, uint16 x, uint16 y, uint16 winding, byte textColor) { - -	int16 w, h; - -	getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &w, &h); - -	int id = createBalloon(w+5, h, winding, 1); -	Gfx::Balloon *balloon = &_balloons[id]; - -	drawWrappedText(_vm->_dialogueFont, &balloon->surface, text, textColor, MAX_BALLOON_WIDTH); - -	balloon->x = x; -	balloon->y = y; - -	return id; -} - -int Gfx::setDialogueBalloon(char *text, uint16 winding, byte textColor) { - -	int16 w, h; - -	getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &w, &h); - -	int id = createBalloon(w+5, h, winding, 1); -	Gfx::Balloon *balloon = &_balloons[id]; - -	drawWrappedText(_vm->_dialogueFont, &balloon->surface, text, textColor, MAX_BALLOON_WIDTH); - -	balloon->x = _dialogueBalloonX[id]; -	balloon->y = 10; - -	if (id > 0) { -		balloon->y += _balloons[id - 1].y + _balloons[id - 1].outerBox.height(); -	} - - -	return id; +	_items[item].data->frame = f; +	_items[item].data->setFlags(kGfxObjVisible);  } -void Gfx::setBalloonText(uint id, char *text, byte textColor) { -	Gfx::Balloon *balloon = getBalloon(id); -	balloon->surface.fillRect(balloon->innerBox, 1); -	drawWrappedText(_vm->_dialogueFont, &balloon->surface, text, textColor, MAX_BALLOON_WIDTH); -} +GfxObj* Gfx::registerBalloon(Frames *frames, const char *text) { -int Gfx::setLocationBalloon(char *text, bool endGame) { +	GfxObj *obj = new GfxObj(kGfxObjTypeBalloon, frames, text); -	int16 w, h; +	obj->layer = LAYER_FOREGROUND; +	obj->frame = 0; +	obj->setFlags(kGfxObjVisible); -	getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &w, &h); +	_balloons.push_back(obj); -	int id = createBalloon(w+(endGame ? 5 : 10), h+5, -1, BALLOON_TRANSPARENT_COLOR); -	Gfx::Balloon *balloon = &_balloons[id]; -	drawWrappedText(_vm->_dialogueFont, &balloon->surface, text, 0, MAX_BALLOON_WIDTH); - -	balloon->x = 5; -	balloon->y = 5; - -	return id; +	return obj;  } -int Gfx::hitTestDialogueBalloon(int x, int y) { - -	Common::Point p; - -	for (uint i = 0; i < _numBalloons; i++) { -		p.x = x - _balloons[i].x; -		p.y = y - _balloons[i].y; - -		if (_balloons[i].innerBox.contains(p)) -			return i; +void Gfx::destroyBalloons() { +	for (uint i = 0; i < _balloons.size(); i++) { +		delete _balloons[i];  	} - -	return -1; -} - - -void Gfx::freeBalloons() { -	for (uint i = 0; i < _numBalloons; i++) { -		_balloons[i].surface.free(); -	} -	_numBalloons = 0; +	_balloons.clear();  }  void Gfx::freeItems() {  	_numItems = 0;  } -void Gfx::hideDialogueStuff() { -	freeItems(); -	freeBalloons(); -} - -void Gfx::drawText(Font *font, Graphics::Surface* surf, uint16 x, uint16 y, const char *text, byte color) { -	byte *dst = (byte*)surf->getBasePtr(x, y); -	font->setColor(color); -	font->drawString(dst, surf->w, text); -} - -void Gfx::drawWrappedText(Font *font, Graphics::Surface* surf, char *text, byte color, int16 wrapwidth) { - -	uint16 lines = 0; -	uint16 linewidth = 0; - -	uint16 rx = 10; -	uint16 ry = 4; - -	uint16 blankWidth = font->getStringWidth(" "); -	uint16 tokenWidth = 0; - -	char token[MAX_TOKEN_LEN]; - -	if (wrapwidth == -1) -		wrapwidth = _vm->_screenWidth; - -	while (strlen(text) > 0) { - -		text = parseNextToken(text, token, MAX_TOKEN_LEN, "   ", true); - -		if (!scumm_stricmp(token, "%p")) { -			lines++; -			rx = 10; -			ry = 4 + lines*10;	// y - -			strcpy(token, "> ......."); -			strncpy(token+2, _password, strlen(_password)); -			tokenWidth = font->getStringWidth(token); -		} else { -			tokenWidth = font->getStringWidth(token); - -			linewidth += tokenWidth; +void Gfx::setBackground(uint type, BackgroundInfo *info) { +	delete _backgroundInfo; +	_backgroundInfo = info; -			if (linewidth > wrapwidth) { -				// wrap line -				lines++; -				rx = 10;			// x -				ry = 4 + lines*10;	// y -				linewidth = tokenWidth; -			} - -			if (!scumm_stricmp(token, "%s")) { -				sprintf(token, "%d", _score); +	if (type == kBackgroundLocation) { +		// The PC version of BRA needs the entries 20-31 of the palette to be constant, but +		// the background resource files are screwed up. The right colors come from an unused +		// bitmap (pointer.bmp). Nothing is known about the Amiga version so far. +		if ((_vm->getGameType() == GType_BRA) && (_vm->getPlatform() == Common::kPlatformPC)) { +			int r, g, b; +			for (uint i = 16; i < 32; i++) { +				_backupPal.getEntry(i, r, g, b); +				_backgroundInfo->palette.setEntry(i, r, g, b);  			} -  		} -		drawText(font, surf, rx, ry, token, color); - -		rx += tokenWidth + blankWidth; -		linewidth += blankWidth; - -		text = Common::ltrim(text); -	} - -} - -void Gfx::freeBackground() { -	_backgroundInfo.free(); -} - -void Gfx::setBackground(uint type, const char* name, const char* mask, const char* path) { - -	freeBackground(); - -	if (type == kBackgroundLocation) { -		_disk->loadScenery(_backgroundInfo, name, mask, path); -		setPalette(_backgroundInfo.palette); -		_palette.clone(_backgroundInfo.palette); +		setPalette(_backgroundInfo->palette); +		_palette.clone(_backgroundInfo->palette);  	} else { -		_disk->loadSlide(_backgroundInfo, name); -		setPalette(_backgroundInfo.palette); +		for (uint i = 0; i < 6; i++) +			_backgroundInfo->ranges[i]._flags = 0;	// disable palette cycling for slides +		setPalette(_backgroundInfo->palette);  	} -  }  } // namespace Parallaction diff --git a/engines/parallaction/graphics.h b/engines/parallaction/graphics.h index 894e0fd678..23b4569c6a 100644 --- a/engines/parallaction/graphics.h +++ b/engines/parallaction/graphics.h @@ -95,6 +95,7 @@ public:  	}  	~SurfaceToFrames() { +		_surf->free();  		delete _surf;  	} @@ -156,11 +157,11 @@ struct SurfaceToMultiFrames : public Frames {  		r.setHeight(_height);  	}  	uint	getRawSize(uint16 index) { -		assert(index == 0); +		assert(index < _num);  		return getSize(index);  	}  	uint	getSize(uint16 index) { -		assert(index == 0); +		assert(index < _num);  		return _width * _height;  	} @@ -260,6 +261,7 @@ public:  	void makeBlack();  	void setEntries(byte* data, uint first, uint num); +	void getEntry(uint index, int &red, int &green, int &blue);  	void setEntry(uint index, int red, int green, int blue);  	void makeGrayscale();  	void fadeTo(const Palette& target, uint step); @@ -325,20 +327,6 @@ public:  #define CENTER_LABEL_HORIZONTAL	-1  #define CENTER_LABEL_VERTICAL	-1 -struct Label { -	Graphics::Surface	_cnv; - -	Common::Point		_pos; -	bool				_visible; - -	Label(); -	~Label(); - -	void free(); -	void resetPosition(); -}; - -  #define MAX_BALLOON_WIDTH 130 @@ -353,25 +341,39 @@ class Disk;  enum {  	kGfxObjVisible = 1, +	kGfxObjNormal = 2, +	kGfxObjCharacter = 4,  	kGfxObjTypeDoor = 0,  	kGfxObjTypeGet = 1, -	kGfxObjTypeAnim = 2 +	kGfxObjTypeAnim = 2, +	kGfxObjTypeLabel = 3, +	kGfxObjTypeBalloon = 4, +	kGfxObjTypeCharacter = 8 +}; + +enum { +	kGfxObjDoorZ = -200, +	kGfxObjGetZ = -100  };  class GfxObj {  	char *_name;  	Frames *_frames; -	uint32 _flags;  	bool _keep;  public:  	int16 x, y; -	uint16 z; + +	int32 z; + +	uint32 _flags; +  	uint type;  	uint frame;  	uint layer; +	uint transparentKey;  	GfxObj(uint type, Frames *frames, const char *name = NULL);  	virtual ~GfxObj(); @@ -434,7 +436,7 @@ struct BackgroundInfo {  		return LAYER_FOREGROUND;  	} -	void free() { +	~BackgroundInfo() {  		bg.free();  		mask.free();  		path.free(); @@ -452,49 +454,65 @@ enum {  	kBackgroundSlide = 2  }; + +class BalloonManager { +public: +	virtual ~BalloonManager() { } + +	virtual void freeBalloons() = 0; +	virtual int setLocationBalloon(char *text, bool endGame) = 0; +	virtual int setDialogueBalloon(char *text, uint16 winding, byte textColor) = 0; +	virtual int setSingleBalloon(char *text, uint16 x, uint16 y, uint16 winding, byte textColor) = 0; +	virtual void setBalloonText(uint id, char *text, byte textColor) = 0; +	virtual int hitTestDialogueBalloon(int x, int y) = 0; +}; + +  typedef Common::HashMap<Common::String, int32, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> VarMap;  class Gfx { +protected: +	Parallaction*		_vm; +  public:  	Disk *_disk;  	VarMap _vars; -	GfxObjList _gfxobjList[3]; +	GfxObjList _gfxobjList;  	GfxObj* loadAnim(const char *name);  	GfxObj* loadGet(const char *name);  	GfxObj* loadDoor(const char *name);  	void drawGfxObjects(Graphics::Surface &surf);  	void showGfxObj(GfxObj* obj, bool visible); -	void clearGfxObjects(); +	void clearGfxObjects(uint filter);  	void sortAnimations(); +  	// labels -	void setFloatingLabel(Label *label); -	Label *renderFloatingLabel(Font *font, char *text); +	void showFloatingLabel(uint label); +	void hideFloatingLabel(); + +	uint renderFloatingLabel(Font *font, char *text);  	uint createLabel(Font *font, const char *text, byte color);  	void showLabel(uint id, int16 x, int16 y);  	void hideLabel(uint id);  	void freeLabels();  	// dialogue balloons -	int setLocationBalloon(char *text, bool endGame); -	int setDialogueBalloon(char *text, uint16 winding, byte textColor); -	int setSingleBalloon(char *text, uint16 x, uint16 y, uint16 winding, byte textColor); -	void setBalloonText(uint id, char *text, byte textColor); -	int hitTestDialogueBalloon(int x, int y); -	void getStringExtent(Font *font, char *text, uint16 maxwidth, int16* width, int16* height); +	GfxObj* registerBalloon(Frames *frames, const char *text); +	void destroyBalloons();  	// other items -	int setItem(Frames* frames, uint16 x, uint16 y, byte transparentColor = 0); +	int setItem(GfxObj* obj, uint16 x, uint16 y, byte transparentColor = 0);  	void setItemFrame(uint item, uint16 f);  	void hideDialogueStuff();  	void freeBalloons();  	void freeItems();  	// background surface -	BackgroundInfo	_backgroundInfo; -	void setBackground(uint type, const char* name, const char* mask, const char* path); +	BackgroundInfo	*_backgroundInfo; +	void setBackground(uint type, BackgroundInfo *info);  	void patchBackground(Graphics::Surface &surf, int16 x, int16 y, bool mask = false);  	void grabBackground(const Common::Rect& r, Graphics::Surface &dst);  	void fillBackground(const Common::Rect& r, byte color); @@ -532,52 +550,45 @@ public:  	uint				_screenX;		// scrolling position  	uint				_screenY; +	byte				*_unpackedBitmap; +  protected: -	Parallaction*		_vm;  	bool				_halfbrite; +	bool 				_skipBackground; +  	Common::Point		_hbCirclePos;  	int				_hbCircleRadius; +	// BRA specific +	Palette				_backupPal; +  	// frame data stored in programmable variables  	int32				_varBackgroundMode;	// 1 = normal, 2 = only mask  	int32				_varScrollX;  	int32				_varAnimRenderMode;	// 1 = normal, 2 = flat  	int32				_varMiscRenderMode;	// 1 = normal, 2 = flat  	int32				_varRenderMode; +	int32				_varDrawPathZones;	// 0 = don't draw, 1 = draw  	Graphics::Surface 	_bitmapMask;  	int32 				getRenderMode(const char *type); -protected: -	static int16 _dialogueBalloonX[5]; - -	struct Balloon { -		uint16 x; -		uint16 y; -		Common::Rect outerBox; -		Common::Rect innerBox; -		uint16 winding; -		Graphics::Surface surface; -	} _balloons[5]; - -	uint	_numBalloons; +public:  	struct Item { -		uint16 x; -		uint16 y; -		uint16 frame; -		Frames *data; - -		byte transparentColor; -		Common::Rect rect; +		GfxObj *data;  	} _items[14];  	uint	_numItems; -	#define MAX_NUM_LABELS	5 -	Label*	_labels[MAX_NUM_LABELS]; -	uint	_numLabels; -	Label	*_floatingLabel; +	#define MAX_NUM_LABELS	20 +	#define NO_FLOATING_LABEL	1000 + +	typedef Common::Array<GfxObj*> GfxObjArray; +	GfxObjArray	_labels; +	GfxObjArray _balloons; + +	uint _floatingLabel;  	void drawInventory();  	void updateFloatingLabel(); @@ -587,13 +598,10 @@ protected:  	void copyRect(const Common::Rect &r, Graphics::Surface &src, Graphics::Surface &dst); -	int createBalloon(int16 w, int16 h, int16 winding, uint16 borderThickness); -	Balloon *getBalloon(uint id); -  	// low level text and patches  	void drawText(Font *font, Graphics::Surface* surf, uint16 x, uint16 y, const char *text, byte color); -	void drawWrappedText(Font *font, Graphics::Surface* surf, char *text, byte color, int16 wrapwidth); +	void drawGfxObject(GfxObj *obj, Graphics::Surface &surf, bool scene);      void blt(const Common::Rect& r, byte *data, Graphics::Surface *surf, uint16 z, byte transparentColor);  	void unpackBlt(const Common::Rect& r, byte *data, uint size, Graphics::Surface *surf, uint16 z, byte transparentColor);  }; diff --git a/engines/parallaction/gui.cpp b/engines/parallaction/gui.cpp new file mode 100644 index 0000000000..2dbe64fcf6 --- /dev/null +++ b/engines/parallaction/gui.cpp @@ -0,0 +1,92 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "parallaction/gui.h" + +namespace Parallaction { + +bool MenuInputHelper::run() { +	if (_newState == 0) { +		debugC(3, kDebugExec, "MenuInputHelper has set NULL state"); +		return false; +	} + +	if (_newState != _state) { +		debugC(3, kDebugExec, "MenuInputHelper changing state to '%s'", _newState->_name.c_str()); + +		_newState->enter(); +		_state = _newState; +	} + +	_newState = _state->run(); + +	return true; +} + +MenuInputHelper::~MenuInputHelper() { +	StateMap::iterator b = _map.begin(); +	for ( ; b != _map.end(); b++) { +		delete b->_value; +	} +	_map.clear(); +} + + +void Parallaction::runGuiFrame() { +	if (_input->_inputMode != Input::kInputModeMenu) { +		return; +	} + +	if (!_menuHelper) { +		error("No menu helper defined!"); +	} + +	bool res = _menuHelper->run(); + +	if (!res) { +		cleanupGui(); +		_input->_inputMode = Input::kInputModeGame; +	} + +} + +void Parallaction::cleanupGui() { +	delete _menuHelper; +	_menuHelper = 0; +} + +void Parallaction::setInternLanguage(uint id) { +	//TODO: assert id! + +	_language = id; +	_disk->setLanguage(id); +} + +uint Parallaction::getInternLanguage() { +	return _language; +} + + +} // namespace  Parallaction diff --git a/engines/parallaction/gui.h b/engines/parallaction/gui.h new file mode 100644 index 0000000000..dc6d1bc71b --- /dev/null +++ b/engines/parallaction/gui.h @@ -0,0 +1,93 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef PARALLACTION_GUI_H +#define PARALLACTION_GUI_H + +#include "common/system.h" +#include "common/hashmap.h" + +#include "parallaction/input.h" +#include "parallaction/parallaction.h" +#include "parallaction/sound.h" + + +namespace Parallaction { + +class MenuInputState; + +class MenuInputHelper { +	typedef	Common::HashMap<Common::String, MenuInputState*> StateMap; + +	StateMap	_map; +	MenuInputState	*_state; +	MenuInputState *_newState; + +public: +	MenuInputHelper() : _state(0) { +	} + +	~MenuInputHelper(); + +	void setState(const Common::String &name) { +		// bootstrap routine +		_newState = getState(name); +		assert(_newState); +	} + +	void addState(const Common::String &name, MenuInputState *state) { +		_map.setVal(name, state); +	} + +	MenuInputState *getState(const Common::String &name) { +		return _map[name]; +	} + +	bool run(); +}; + +class MenuInputState { + +protected: +	MenuInputHelper *_helper; + +public: +	MenuInputState(const Common::String &name, MenuInputHelper *helper) : _helper(helper), _name(name) { +		debugC(3, kDebugExec, "MenuInputState(%s)", name.c_str()); +		_helper->addState(name, this); +	} + +	Common::String	_name; + +	virtual ~MenuInputState() { } + +	virtual MenuInputState* run() = 0; +	virtual void enter() = 0; +}; + + +} // namespace Parallaction + +#endif diff --git a/engines/parallaction/gui_br.cpp b/engines/parallaction/gui_br.cpp index c515299a34..3315433762 100644 --- a/engines/parallaction/gui_br.cpp +++ b/engines/parallaction/gui_br.cpp @@ -25,184 +25,268 @@  #include "common/system.h" - +#include "parallaction/gui.h"  #include "parallaction/input.h"  #include "parallaction/parallaction.h"  namespace Parallaction { -enum MenuOptions { -	kMenuPart0 = 0, -	kMenuPart1 = 1, -	kMenuPart2 = 2, -	kMenuPart3 = 3, -	kMenuPart4 = 4, -	kMenuLoadGame = 5, -	kMenuQuit = 6 -}; - +class SplashInputState_BR : public MenuInputState { +protected: +	Common::String _slideName; +	uint32 _timeOut; +	Common::String _nextState; +	uint32	_startTime; +	Palette blackPal; +	Palette pal; -void Parallaction_br::guiStart() { +	Parallaction_br *_vm; +	int _fadeSteps; -	// TODO: load progress value from special save game -	_progress = 3; +public: +	SplashInputState_BR(Parallaction_br *vm, const Common::String &name, MenuInputHelper *helper) : MenuInputState(name, helper), _vm(vm)  { +	} -	int option = guiShowMenu(); -	switch (option) { -	case kMenuQuit: -		_engineFlags |= kEngineQuit; -		break; +	virtual MenuInputState* run() { +		if (_fadeSteps > 0) { +			pal.fadeTo(blackPal, 1); +			_vm->_gfx->setPalette(pal); +			_fadeSteps--; +			// TODO: properly implement timers to avoid delay calls +			_vm->_system->delayMillis(20); +			return this; +		} -	case kMenuLoadGame: -		warning("loadgame not yet implemented"); -		break; +		if (_fadeSteps == 0) { +			_vm->freeBackground(); +			return _helper->getState(_nextState); +		} -	default: -		_part = option; -		_disk->selectArchive(_partNames[_part]); -		startPart(); +		uint32 curTime = _vm->_system->getMillis(); +		if (curTime - _startTime > _timeOut) { +			_fadeSteps = 64; +			pal.clone(_vm->_gfx->_backgroundInfo->palette); +		} +		return this;  	} -} -void Parallaction_br::guiSplash(const char *name) { +	virtual void enter() { +		_vm->_gfx->clearScreen(); +		_vm->showSlide(_slideName.c_str(), CENTER_LABEL_HORIZONTAL, CENTER_LABEL_VERTICAL); +		_vm->_input->setMouseState(MOUSE_DISABLED); -	_gfx->clearScreen(); -	_gfx->setBackground(kBackgroundSlide, name, 0, 0); -	_gfx->_backgroundInfo.x = (_screenWidth - _gfx->_backgroundInfo.width) >> 1; -	_gfx->_backgroundInfo.y = (_screenHeight - _gfx->_backgroundInfo.height) >> 1; -	_gfx->updateScreen(); -	_system->delayMillis(600); +		_startTime = g_system->getMillis(); +		_fadeSteps = -1; +	} +}; -	Palette blackPal; -	Palette pal(_gfx->_backgroundInfo.palette); -	for (uint i = 0; i < 64; i++) { -		pal.fadeTo(blackPal, 1); -		_gfx->setPalette(pal); -		_gfx->updateScreen(); -		_system->delayMillis(20); +class SplashInputState0_BR : public SplashInputState_BR { + +public: +	SplashInputState0_BR(Parallaction_br *vm, MenuInputHelper *helper) : SplashInputState_BR(vm, "intro0", helper)  { +		_slideName = "dyna"; +		_timeOut = 600; +		_nextState = "intro1";  	} +}; -} +class SplashInputState1_BR : public SplashInputState_BR { -#define MENUITEMS_X			250 -#define MENUITEMS_Y			200 +public: +	SplashInputState1_BR(Parallaction_br *vm, MenuInputHelper *helper) : SplashInputState_BR(vm, "intro1", helper) { +		_slideName = "core"; +		_timeOut = 600; +		_nextState = "mainmenu"; +	} +}; -#define MENUITEM_WIDTH		190 -#define MENUITEM_HEIGHT		18 +class MainMenuInputState_BR : public MenuInputState { +	Parallaction_br *_vm; -Frames* Parallaction_br::guiRenderMenuItem(const char *text) { -	// this builds a surface containing two copies of the text. -	// one is in normal color, the other is inverted. -	// the two 'frames' are used to display selected/unselected menu items +	#define MENUITEMS_X			250 +	#define MENUITEMS_Y			200 -	Graphics::Surface *surf = new Graphics::Surface; -	surf->create(MENUITEM_WIDTH, MENUITEM_HEIGHT*2, 1); +	#define MENUITEM_WIDTH		190 +	#define MENUITEM_HEIGHT		18 -	// build first frame to be displayed when item is not selected -	if (getPlatform() == Common::kPlatformPC) { -		_menuFont->setColor(0); -	} else { -		_menuFont->setColor(7); -	} -	_menuFont->drawString((byte*)surf->getBasePtr(5, 2), MENUITEM_WIDTH, text); +	Frames* renderMenuItem(const char *text) { +		// this builds a surface containing two copies of the text. +		// one is in normal color, the other is inverted. +		// the two 'frames' are used to display selected/unselected menu items -	// build second frame to be displayed when item is selected -	_menuFont->drawString((byte*)surf->getBasePtr(5, 2 + MENUITEM_HEIGHT), MENUITEM_WIDTH, text); -	byte *s = (byte*)surf->getBasePtr(0, MENUITEM_HEIGHT); -	for (int i = 0; i < surf->w * MENUITEM_HEIGHT; i++) { -		*s++ ^= 0xD; -	} +		Graphics::Surface *surf = new Graphics::Surface; +		surf->create(MENUITEM_WIDTH, MENUITEM_HEIGHT*2, 1); -	// wrap the surface into the suitable Frames adapter -	return new SurfaceToMultiFrames(2, MENUITEM_WIDTH, MENUITEM_HEIGHT, surf); -} +		// build first frame to be displayed when item is not selected +		if (_vm->getPlatform() == Common::kPlatformPC) { +			_vm->_menuFont->setColor(0); +		} else { +			_vm->_menuFont->setColor(7); +		} +		_vm->_menuFont->drawString((byte*)surf->getBasePtr(5, 2), MENUITEM_WIDTH, text); +		// build second frame to be displayed when item is selected +		_vm->_menuFont->drawString((byte*)surf->getBasePtr(5, 2 + MENUITEM_HEIGHT), MENUITEM_WIDTH, text); +		byte *s = (byte*)surf->getBasePtr(0, MENUITEM_HEIGHT); +		for (int i = 0; i < surf->w * MENUITEM_HEIGHT; i++) { +			*s++ ^= 0xD; +		} -int Parallaction_br::guiShowMenu() { -	// TODO: filter menu entries according to progress in game +		// wrap the surface into the suitable Frames adapter +		return new SurfaceToMultiFrames(2, MENUITEM_WIDTH, MENUITEM_HEIGHT, surf); +	} -	#define NUM_MENULINES	7 -	Frames *_lines[NUM_MENULINES]; - -	const char *menuStrings[NUM_MENULINES] = { -		"SEE INTRO", -		"NEW GAME", -		"SAVED GAME", -		"EXIT TO DOS", -		"PART 2", -		"PART 3", -		"PART 4" +	enum MenuOptions { +		kMenuPart0 = 0, +		kMenuPart1 = 1, +		kMenuPart2 = 2, +		kMenuPart3 = 3, +		kMenuPart4 = 4, +		kMenuLoadGame = 5, +		kMenuQuit = 6  	}; -	MenuOptions options[NUM_MENULINES] = { -		kMenuPart0, -		kMenuPart1, -		kMenuLoadGame, -		kMenuQuit, -		kMenuPart2, -		kMenuPart3, -		kMenuPart4 -	}; +	#define NUM_MENULINES	7 +	GfxObj *_lines[NUM_MENULINES]; -	_gfx->clearScreen(); -	_gfx->setBackground(kBackgroundSlide, "tbra", 0, 0); -	if (getPlatform() == Common::kPlatformPC) { -		_gfx->_backgroundInfo.x = 20; -		_gfx->_backgroundInfo.y = 50; -	} +	static const char *_menuStrings[NUM_MENULINES]; +	static const MenuOptions _options[NUM_MENULINES]; -	int availItems = 4 + _progress; +	int _availItems; +	int _selection; -	// TODO: keep track of and destroy menu item frames/surfaces +	void cleanup() { +		_vm->_system->showMouse(false); +		_vm->hideDialogueStuff(); -	int i; -	for (i = 0; i < availItems; i++) { -		_lines[i] = guiRenderMenuItem(menuStrings[i]); -		uint id = _gfx->setItem(_lines[i], MENUITEMS_X, MENUITEMS_Y + MENUITEM_HEIGHT * i, 0xFF); -		_gfx->setItemFrame(id, 0); +		for (int i = 0; i < _availItems; i++) { +			delete _lines[i]; +		}  	} -	int selectedItem = -1; +	void performChoice(int selectedItem) { +		switch (selectedItem) { +		case kMenuQuit: +			_engineFlags |= kEngineQuit; +			break; -	setMousePointer(0); +		case kMenuLoadGame: +			warning("loadgame not yet implemented"); +			break; -	uint32 event; -	Common::Point p; -	while (true) { +		default: +			_vm->startPart(selectedItem); +		} +	} -		_input->readInput(); +public: +	MainMenuInputState_BR(Parallaction_br *vm, MenuInputHelper *helper) : MenuInputState("mainmenu", helper), _vm(vm)  { +	} -		event = _input->getLastButtonEvent(); -		if ((event == kMouseLeftUp) && selectedItem >= 0) -			break; +	virtual MenuInputState* run() { -		_input->getCursorPos(p); +		int event = _vm->_input->getLastButtonEvent(); +		if ((event == kMouseLeftUp) && _selection >= 0) { +			cleanup(); +			performChoice(_options[_selection]); +			return 0; +		} + +		Common::Point p; +		_vm->_input->getCursorPos(p);  		if ((p.x > MENUITEMS_X) && (p.x < (MENUITEMS_X+MENUITEM_WIDTH)) && (p.y > MENUITEMS_Y)) { -			selectedItem = (p.y - MENUITEMS_Y) / MENUITEM_HEIGHT; +			_selection = (p.y - MENUITEMS_Y) / MENUITEM_HEIGHT; -			if (!(selectedItem < availItems)) -				selectedItem = -1; +			if (!(_selection < _availItems)) +				_selection = -1;  		} else -			selectedItem = -1; +			_selection = -1; -		for (i = 0; i < availItems; i++) { -			_gfx->setItemFrame(i, selectedItem == i ? 1 : 0); +		for (int i = 0; i < _availItems; i++) { +			_vm->_gfx->setItemFrame(i, _selection == i ? 1 : 0);  		} -		_gfx->updateScreen(); -		_system->delayMillis(20); -	} -	_system->showMouse(false); -	_gfx->hideDialogueStuff(); +		return this; +	} -	for (i = 0; i < availItems; i++) { -		delete _lines[i]; +	virtual void enter() { +		_vm->_gfx->clearScreen(); +		int x = 0, y = 0; +		if (_vm->getPlatform() == Common::kPlatformPC) { +			x = 20; +			y = 50; +		} +		_vm->showSlide("tbra", x, y); + +		// TODO: load progress from savefile +		int progress = 3; +		_availItems = 4 + progress; + +		// TODO: keep track of and destroy menu item frames/surfaces +		int i; +		for (i = 0; i < _availItems; i++) { +			_lines[i] = new GfxObj(0, renderMenuItem(_menuStrings[i]), "MenuItem"); +			uint id = _vm->_gfx->setItem(_lines[i], MENUITEMS_X, MENUITEMS_Y + MENUITEM_HEIGHT * i, 0xFF); +			_vm->_gfx->setItemFrame(id, 0); +		} +		_selection = -1; +		_vm->setArrowCursor(); +		_vm->_input->setMouseState(MOUSE_ENABLED_SHOW);  	} -	return options[selectedItem]; +}; + +const char *MainMenuInputState_BR::_menuStrings[NUM_MENULINES] = { +	"SEE INTRO", +	"NEW GAME", +	"SAVED GAME", +	"EXIT TO DOS", +	"PART 2", +	"PART 3", +	"PART 4" +}; + +const MainMenuInputState_BR::MenuOptions MainMenuInputState_BR::_options[NUM_MENULINES] = { +	kMenuPart0, +	kMenuPart1, +	kMenuLoadGame, +	kMenuQuit, +	kMenuPart2, +	kMenuPart3, +	kMenuPart4 +}; + + + + + + + +void Parallaction_br::startGui() { +	_menuHelper = new MenuInputHelper; +	new SplashInputState0_BR(this, _menuHelper); +	new SplashInputState1_BR(this, _menuHelper); +	new MainMenuInputState_BR(this, _menuHelper); + +	_menuHelper->setState("intro0"); +	_input->_inputMode = Input::kInputModeMenu; + +	do { +		_input->readInput(); +		if (!_menuHelper->run()) break; +		_gfx->beginFrame(); +		_gfx->updateScreen(); +	} while (true); + +	delete _menuHelper; +	_menuHelper = 0; + +	_input->_inputMode = Input::kInputModeGame;  } + +  } // namespace Parallaction diff --git a/engines/parallaction/gui_ns.cpp b/engines/parallaction/gui_ns.cpp index 1d4d44fa46..815c27bd1c 100644 --- a/engines/parallaction/gui_ns.cpp +++ b/engines/parallaction/gui_ns.cpp @@ -24,7 +24,9 @@   */  #include "common/system.h" +#include "common/hashmap.h" +#include "parallaction/gui.h"  #include "parallaction/input.h"  #include "parallaction/parallaction.h"  #include "parallaction/sound.h" @@ -32,311 +34,567 @@  namespace Parallaction { -const char *introMsg1[] = { -	"INSERISCI IL CODICE", -	"ENTREZ CODE", -	"ENTER CODE", -	"GIB DEN KODE EIN" -}; +class SplashInputState_NS : public MenuInputState { +protected: +	Common::String _slideName; +	uint32 _timeOut; +	Common::String _nextState; +	uint32	_startTime; -const char *introMsg2[] = { -	"CODICE ERRATO", -	"CODE ERRONE", -	"WRONG CODE", -	"GIB DEN KODE EIN" -}; +	Parallaction_ns *_vm; -const char *introMsg3[] = { -	"PRESS LEFT MOUSE BUTTON", -	"TO SEE INTRO", -	"PRESS RIGHT MOUSE BUTTON", -	"TO START" -}; +public: +	SplashInputState_NS(Parallaction_ns *vm, const Common::String &name, MenuInputHelper *helper) : MenuInputState(name, helper), _vm(vm)  { +	} -const char *newGameMsg[] = { -	"NUOVO GIOCO", -	"NEUF JEU", -	"NEW GAME", -	"NEUES SPIEL" +	virtual MenuInputState* run() { +		uint32 curTime = g_system->getMillis(); +		if (curTime - _startTime > _timeOut) { +			_vm->freeBackground(); +			return _helper->getState(_nextState); +		} +		return this; +	} + +	virtual void enter() { +		_vm->_input->setMouseState(MOUSE_DISABLED); +		_vm->showSlide(_slideName.c_str()); +		_startTime = g_system->getMillis(); +	}  }; -const char *loadGameMsg[] = { -	"GIOCO SALVATO", -	"JEU SAUVE'", -	"SAVED GAME", -	"SPIEL GESPEICHERT" +class SplashInputState0_NS : public SplashInputState_NS { + +public: +	SplashInputState0_NS(Parallaction_ns *vm, MenuInputHelper *helper) : SplashInputState_NS(vm, "intro0", helper)  { +		_slideName = "intro"; +		_timeOut = 2000; +		_nextState = "intro1"; +	}  }; +class SplashInputState1_NS : public SplashInputState_NS { -#define BLOCK_WIDTH		16 -#define BLOCK_HEIGHT	24 +public: +	SplashInputState1_NS(Parallaction_ns *vm, MenuInputHelper *helper) : SplashInputState_NS(vm, "intro1", helper) { +		_slideName = "minintro"; +		_timeOut = 2000; +		_nextState = "chooselanguage"; +	} +}; -#define BLOCK_X			112 -#define BLOCK_Y			130 -#define BLOCK_SELECTION_X		  (BLOCK_X-1) -#define BLOCK_SELECTION_Y		  (BLOCK_Y-1) +class ChooseLanguageInputState_NS : public MenuInputState { +	#define BLOCK_WIDTH		16 +	#define BLOCK_HEIGHT	24 -#define BLOCK_X_OFFSET	(BLOCK_WIDTH+1) -#define BLOCK_Y_OFFSET	9 +	#define BLOCK_X			112 +	#define BLOCK_Y			130 -//	destination slots for code blocks -// -#define SLOT_X			61 -#define SLOT_Y			64 -#define SLOT_WIDTH		(BLOCK_WIDTH+2) +	#define BLOCK_SELECTION_X		  (BLOCK_X-1) +	#define BLOCK_SELECTION_Y		  (BLOCK_Y-1) -#define PASSWORD_LEN	6 +	#define BLOCK_X_OFFSET	(BLOCK_WIDTH+1) +	#define BLOCK_Y_OFFSET	9 -#define CHAR_DINO	0 -#define CHAR_DONNA	1 -#define CHAR_DOUGH	2 +	//	destination slots for code blocks +	// +	#define SLOT_X			61 +	#define SLOT_Y			64 +	#define SLOT_WIDTH		(BLOCK_WIDTH+2) -static const uint16 _amigaKeys[][PASSWORD_LEN] = { -	{ 5, 3, 6, 2, 2, 7 },		// dino -	{ 0, 3, 6, 2, 2, 6 },		// donna -	{ 1, 3 ,7, 2, 4, 6 }		// dough -}; +	int	_language; +	bool	_allowChoice; +	Common::String _nextState; -static const uint16 _pcKeys[][PASSWORD_LEN] = { -	{ 5, 3, 6, 1, 4, 7 },		// dino -	{ 0, 2, 8, 5, 5, 1 },		// donna -	{ 1, 7 ,7, 2, 2, 6 }		// dough -}; +	static const Common::Rect _dosLanguageSelectBlocks[4]; +	static const Common::Rect _amigaLanguageSelectBlocks[4]; +	const Common::Rect *_blocks; -static const char *_charStartLocation[] = { -	"test.dino", -	"test.donna", -	"test.dough" -}; +	Parallaction_ns *_vm; -enum { -	NEW_GAME, -	LOAD_GAME -}; +public: +	ChooseLanguageInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("chooselanguage", helper), _vm(vm) { +		_allowChoice = false; +		_nextState = "selectgame"; -enum { -	START_DEMO, -	START_INTRO, -	GAME_LOADED, -	SELECT_CHARACTER -}; +		if (_vm->getPlatform() == Common::kPlatformAmiga) { +			if (!(_vm->getFeatures() & GF_LANG_MULT)) { +				if (_vm->getFeatures() & GF_DEMO) { +					_language = 1;	// Amiga Demo supports English +					_nextState = "startdemo"; +					return; +				} else { +					_language = 0;	// The only other non multi-lingual version just supports Italian +					return; +				} +			} -void Parallaction_ns::guiStart() { +			_blocks = _amigaLanguageSelectBlocks; +		} else { +			_blocks = _dosLanguageSelectBlocks; +		} -	_disk->selectArchive((getFeatures() & GF_DEMO) ? "disk0" : "disk1"); +		_language = -1; +		_allowChoice = true; +	} -	guiSplash(); +	virtual MenuInputState* run() { +		if (!_allowChoice) { +			_vm->setInternLanguage(_language); +			return _helper->getState(_nextState); +		} -	_language = guiChooseLanguage(); -	_disk->setLanguage(_language); +		int event = _vm->_input->getLastButtonEvent(); +		if (event != kMouseLeftUp) { +			return this; +		} -	int event; +		Common::Point p; +		_vm->_input->getCursorPos(p); -	if (getFeatures() & GF_DEMO) { -		event = START_DEMO; -	} else { -		if (guiSelectGame() == NEW_GAME) { -			event = guiNewGame(); -		} else { -			event = loadGame() ? GAME_LOADED : START_INTRO; +		for (uint16 i = 0; i < 4; i++) { +			if (_blocks[i].contains(p)) { +				_vm->setInternLanguage(i); +				_vm->beep(); +				_vm->_gfx->freeLabels(); +				return _helper->getState(_nextState); +			}  		} + +		return this;  	} -	switch (event) { -	case START_DEMO: -		strcpy(_location._name, "fognedemo.dough"); -		break; +	virtual void enter() { +		if (!_allowChoice) { +			return; +		} -	case START_INTRO: -		strcpy(_location._name, "fogne.dough"); -		break; +		_vm->_input->setMouseState(MOUSE_ENABLED_SHOW); -	case GAME_LOADED: -		// nothing to do here -		return; +		// user can choose language in this version +		_vm->showSlide("lingua"); -	case SELECT_CHARACTER: -		selectStartLocation(); -		break; +		uint id = _vm->_gfx->createLabel(_vm->_introFont, "SELECT LANGUAGE", 1); +		_vm->_gfx->showLabel(id, 60, 30); +		_vm->setArrowCursor();  	} +}; -	return; -} +const Common::Rect ChooseLanguageInputState_NS::_dosLanguageSelectBlocks[4] = { +	Common::Rect(  80, 110, 128, 180 ),	// Italian +	Common::Rect( 129,  85, 177, 155 ),	// French +	Common::Rect( 178,  60, 226, 130 ),	// English +	Common::Rect( 227,  35, 275, 105 )	// German +}; -void Parallaction_ns::selectStartLocation() { -	int character = guiSelectCharacter(); -	if (character == -1) -		error("invalid character selected from menu screen"); +const Common::Rect ChooseLanguageInputState_NS::_amigaLanguageSelectBlocks[4] = { +	Common::Rect(  -1,  -1,  -1,  -1 ),	// Italian: not supported by Amiga multi-lingual version +	Common::Rect( 129,  85, 177, 155 ),	// French +	Common::Rect( 178,  60, 226, 130 ),	// English +	Common::Rect( 227,  35, 275, 105 )	// German +}; -	scheduleLocationSwitch(_charStartLocation[character]); -} +class SelectGameInputState_NS : public MenuInputState { +	int _choice, _oldChoice; +	Common::String _nextState[2]; -void Parallaction_ns::guiSplash() { +	uint	_labels[2]; -	showSlide("intro"); -	_gfx->updateScreen(); -	g_system->delayMillis(2000); -	freeBackground(); +	Parallaction_ns *_vm; -	showSlide("minintro"); -	_gfx->updateScreen(); -	g_system->delayMillis(2000); -	freeBackground(); -} +	static const char *newGameMsg[4]; +	static const char *loadGameMsg[4]; -int Parallaction_ns::guiNewGame() { +public: +	SelectGameInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("selectgame", helper), _vm(vm) { +		_choice = 0; +		_oldChoice = -1; -	const char **v14 = introMsg3; +		_nextState[0] = "newgame"; +		_nextState[1] = "loadgame"; +	} -	_disk->selectArchive("disk1"); -	setBackground("test", NULL, NULL); +	virtual MenuInputState *run() { +		int event = _vm->_input->getLastButtonEvent(); -	_gfx->updateScreen(); +		if (event == kMouseLeftUp) { +			_vm->_gfx->freeLabels(); +			return _helper->getState(_nextState[_choice]); +		} -	uint id[4]; -	id[0] = _gfx->createLabel(_menuFont, v14[0], 1); -	id[1] = _gfx->createLabel(_menuFont, v14[1], 1); -	id[2] = _gfx->createLabel(_menuFont, v14[2], 1); -	id[3] = _gfx->createLabel(_menuFont, v14[3], 1); -	_gfx->showLabel(id[0], CENTER_LABEL_HORIZONTAL, 50); -	_gfx->showLabel(id[1], CENTER_LABEL_HORIZONTAL, 70); -	_gfx->showLabel(id[2], CENTER_LABEL_HORIZONTAL, 100); -	_gfx->showLabel(id[3], CENTER_LABEL_HORIZONTAL, 120); +		Common::Point p; +		_vm->_input->getCursorPos(p); +		_choice = (p.x > 160) ? 1 : 0; -	_input->showCursor(false); +		if (_choice != _oldChoice) { +			if (_oldChoice != -1) +				_vm->_gfx->hideLabel(_labels[_oldChoice]); -	_gfx->updateScreen(); +			if (_choice != -1) +				_vm->_gfx->showLabel(_labels[_choice], 60, 30); -	_input->waitForButtonEvent(kMouseLeftUp | kMouseRightUp); -	uint32 event = _input->getLastButtonEvent(); +			_oldChoice = _choice; +		} -	_input->showCursor(true); +		return this; +	} -	_gfx->freeLabels(); +	virtual void enter() { +		_vm->showSlide("restore"); +		_vm->_input->setMouseState(MOUSE_ENABLED_SHOW); -	if (event != kMouseRightUp) { -		return START_INTRO; +		_labels[0] = _vm->_gfx->createLabel(_vm->_introFont, newGameMsg[_vm->getInternLanguage()], 1); +		_labels[1] = _vm->_gfx->createLabel(_vm->_introFont, loadGameMsg[_vm->getInternLanguage()], 1);  	} -	return SELECT_CHARACTER; -} +}; -static const Common::Rect _dosLanguageSelectBlocks[4] = { -	Common::Rect(  80, 110, 128, 180 ),	// Italian -	Common::Rect( 129,  85, 177, 155 ),	// French -	Common::Rect( 178,  60, 226, 130 ),	// English -	Common::Rect( 227,  35, 275, 105 )	// German +const char *SelectGameInputState_NS::newGameMsg[4] = { +	"NUOVO GIOCO", +	"NEUF JEU", +	"NEW GAME", +	"NEUES SPIEL"  }; -static const Common::Rect _amigaLanguageSelectBlocks[4] = { -	Common::Rect(  -1,  -1,  -1,  -1 ),	// Italian: not supported by Amiga multi-lingual version -	Common::Rect( 129,  85, 177, 155 ),	// French -	Common::Rect( 178,  60, 226, 130 ),	// English -	Common::Rect( 227,  35, 275, 105 )	// German +const char *SelectGameInputState_NS::loadGameMsg[4] = { +	"GIOCO SALVATO", +	"JEU SAUVE'", +	"SAVED GAME", +	"SPIEL GESPEICHERT"  }; -uint16 Parallaction_ns::guiChooseLanguage() { -	const Common::Rect *blocks; +class LoadGameInputState_NS : public MenuInputState { +	bool _result; +	Parallaction_ns *_vm; + +public: +	LoadGameInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("loadgame", helper), _vm(vm) { } + +	virtual MenuInputState* run() { +		if (!_result) { +			_vm->scheduleLocationSwitch("fogne.dough"); +		} +		return 0; +	} + +	virtual void enter() { +		_result = _vm->loadGame(); +	} +}; + + -	if (getPlatform() == Common::kPlatformAmiga) { -		if (!(getFeatures() & GF_LANG_MULT)) { -			if (getFeatures() & GF_DEMO) { -				return 1;		// Amiga Demo supports English -			} else { -				return 0;		// The only other non multi-lingual version just supports Italian +class NewGameInputState_NS : public MenuInputState { +	Parallaction_ns *_vm; + +	static const char *introMsg3[4]; + +public: +	NewGameInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("newgame", helper), _vm(vm) { +	} + +	virtual MenuInputState* run() { +		int event = _vm->_input->getLastButtonEvent(); + +		if (event == kMouseLeftUp || event == kMouseRightUp) { +			_vm->_input->setMouseState(MOUSE_ENABLED_SHOW); +			_vm->_gfx->freeLabels(); + +			if (event == kMouseLeftUp) { +				_vm->scheduleLocationSwitch("fogne.dough"); +				return 0;  			} + +			return _helper->getState("selectcharacter");  		} -		blocks = _amigaLanguageSelectBlocks; -	} else { -		blocks = _dosLanguageSelectBlocks; +		return this; +	} + +	virtual void enter() { +		_vm->_disk->selectArchive("disk1"); +		_vm->setBackground("test", NULL, NULL); +		_vm->_input->setMouseState(MOUSE_ENABLED_HIDE); + +		uint id[4]; +		id[0] = _vm->_gfx->createLabel(_vm->_menuFont, introMsg3[0], 1); +		id[1] = _vm->_gfx->createLabel(_vm->_menuFont, introMsg3[1], 1); +		id[2] = _vm->_gfx->createLabel(_vm->_menuFont, introMsg3[2], 1); +		id[3] = _vm->_gfx->createLabel(_vm->_menuFont, introMsg3[3], 1); +		_vm->_gfx->showLabel(id[0], CENTER_LABEL_HORIZONTAL, 50); +		_vm->_gfx->showLabel(id[1], CENTER_LABEL_HORIZONTAL, 70); +		_vm->_gfx->showLabel(id[2], CENTER_LABEL_HORIZONTAL, 100); +		_vm->_gfx->showLabel(id[3], CENTER_LABEL_HORIZONTAL, 120); +	} +}; + +const char *NewGameInputState_NS::introMsg3[4] = { +	"PRESS LEFT MOUSE BUTTON", +	"TO SEE INTRO", +	"PRESS RIGHT MOUSE BUTTON", +	"TO START" +}; + + + +class StartDemoInputState_NS : public MenuInputState { +	Parallaction_ns *_vm; + +public: +	StartDemoInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("startdemo", helper), _vm(vm) {  	} -	// user can choose language in dos version -	showSlide("lingua"); +	virtual MenuInputState* run() { +		_vm->scheduleLocationSwitch("fognedemo.dough"); +		return 0; +	} -	uint id = _gfx->createLabel(_introFont, "SELECT LANGUAGE", 1); -	_gfx->showLabel(id, 60, 30); +	virtual void enter() { +		_vm->_input->setMouseState(MOUSE_DISABLED); +	} +}; -	setArrowCursor(); +class SelectCharacterInputState_NS : public MenuInputState { -	Common::Point p; +	#define PASSWORD_LEN	6 -	int selection = -1; -	while (selection == -1) { -		_input->waitUntilLeftClick(); -		_input->getCursorPos(p); -		for (uint16 i = 0; i < 4; i++) { -			if (blocks[i].contains(p)) { +	#define CHAR_DINO	0 +	#define CHAR_DONNA	1 +	#define CHAR_DOUGH	2 + +	static const Common::Rect codeSelectBlocks[9]; +	static const Common::Rect codeTrueBlocks[9]; + +	Parallaction_ns *_vm; + +	int guiGetSelectedBlock(const Common::Point &p) { + +		int selection = -1; + +		for (uint16 i = 0; i < 9; i++) { +			if (codeSelectBlocks[i].contains(p)) {  				selection = i;  				break;  			}  		} + +		if ((selection != -1) && (_vm->getPlatform() == Common::kPlatformAmiga)) { +			_vm->_gfx->invertBackground(codeTrueBlocks[selection]); +			_vm->_gfx->updateScreen(); +			_vm->beep(); +			g_system->delayMillis(100); +			_vm->_gfx->invertBackground(codeTrueBlocks[selection]); +			_vm->_gfx->updateScreen(); +		} + +		return selection;  	} -	beep(); +	byte	_points[3]; +	bool 	_fail; +	const uint16 (*_keys)[PASSWORD_LEN]; +	Graphics::Surface _block; +	Graphics::Surface _emptySlots; -	_gfx->freeLabels(); +	uint	_labels[2]; +	uint	_len; +	uint32	_startTime; -	return selection; -} +	enum { +		CHOICE, +		FAIL, +		SUCCESS, +		DELAY +	}; +	uint	_state; +	static const char *introMsg1[4]; +	static const char *introMsg2[4]; -uint16 Parallaction_ns::guiSelectGame() { -//	  printf("selectGame()\n"); +	static const uint16 _amigaKeys[3][PASSWORD_LEN]; +	static const uint16 _pcKeys[3][PASSWORD_LEN]; +	static const char *_charStartLocation[3]; -	showSlide("restore"); -	uint16 _si = 0; -	uint16 _di = 3; +public: +	SelectCharacterInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("selectcharacter", helper), _vm(vm) { +		_keys = (_vm->getPlatform() == Common::kPlatformAmiga && (_vm->getFeatures() & GF_LANG_MULT)) ? _amigaKeys : _pcKeys; +		_block.create(BLOCK_WIDTH, BLOCK_HEIGHT, 1); +	} -	uint id0, id1; -	id0 = _gfx->createLabel(_introFont, loadGameMsg[_language], 1); -	id1 = _gfx->createLabel(_introFont, newGameMsg[_language], 1); +	~SelectCharacterInputState_NS() { +		_block.free(); +		_emptySlots.free(); +	} -	Common::Point p; +	void cleanup() { +		_points[0] = _points[1] = _points[2] = 0; +		_vm->_gfx->hideLabel(_labels[1]); +		_vm->_gfx->showLabel(_labels[0], 60, 30); +		_fail = false; +		_len = 0; +	} -	_input->readInput(); -	uint32 event = _input->getLastButtonEvent(); +	void delay() { +		if (g_system->getMillis() - _startTime < 2000) { +			return; +		} +		cleanup(); +		_state = CHOICE; +	} -	while (event != kMouseLeftUp) { +	void choice() { +		int event = _vm->_input->getLastButtonEvent(); +		if (event != kMouseLeftUp) { +			return; +		} -		_input->readInput(); -		_input->getCursorPos(p); -		event = _input->getLastButtonEvent(); +		Common::Point p; +		_vm->_input->getCursorPos(p); +		int _si = guiGetSelectedBlock(p); -		_si = (p.x > 160) ? 1 : 0; +		if (_si != -1) { +			_vm->_gfx->grabBackground(codeTrueBlocks[_si], _block); +			_vm->_gfx->patchBackground(_block, _len * SLOT_WIDTH + SLOT_X, SLOT_Y, false); -		if (_si != _di) { -			if (_si != 0) { -				// load a game -				_gfx->hideLabel(id1); -				_gfx->showLabel(id0, 60, 30); -			} else { -				// new game -				_gfx->hideLabel(id0); -				_gfx->showLabel(id1, 60, 30); +			if (_keys[0][_len] != _si && _keys[1][_len] != _si && _keys[2][_len] != _si) { +				_fail = true;  			} -			_di = _si; + +			// build user preference +			_points[0] += (_keys[0][_len] == _si); +			_points[1] += (_keys[1][_len] == _si); +			_points[2] += (_keys[2][_len] == _si); + +			_len++; +		} + +		if (_len == PASSWORD_LEN) { +			_state = _fail ? FAIL : SUCCESS;  		} +	} -		_gfx->updateScreen(); -		g_system->delayMillis(30); +	void fail() { +		_vm->_gfx->patchBackground(_emptySlots, SLOT_X, SLOT_Y, false); +		_vm->_gfx->hideLabel(_labels[0]); +		_vm->_gfx->showLabel(_labels[1], 60, 30); +		_startTime = g_system->getMillis(); +		_state = DELAY;  	} -	_gfx->freeLabels(); +	void success() { +		_vm->_gfx->freeLabels(); +		_vm->_gfx->setBlackPalette(); +		_emptySlots.free(); + +		// actually select character +		int character = -1; +		if (_points[0] >= _points[1] && _points[0] >= _points[2]) { +			character = CHAR_DINO; +		} else +		if (_points[1] >= _points[0] && _points[1] >= _points[2]) { +			character = CHAR_DONNA; +		} else +		if (_points[2] >= _points[0] && _points[2] >= _points[1]) { +			character = CHAR_DOUGH; +		} else { +			error("If you read this, either your CPU or transivity is broken (we believe the former)."); +		} -	return _si ? LOAD_GAME : NEW_GAME; -} +		_vm->_inTestResult = false; +		_vm->cleanupGame(); +		_vm->scheduleLocationSwitch(_charStartLocation[character]); +	} + +	virtual MenuInputState* run() { +		MenuInputState* nextState = this; + +		switch (_state) { +		case DELAY: +			delay(); +			break; + +		case CHOICE: +			choice(); +			break; + +		case FAIL: +			fail(); +			break; + +		case SUCCESS: +			success(); +			nextState = 0; +			break; + +		default: +			error("unknown state in SelectCharacterInputState"); +		} + +		return nextState; +	} + +	virtual void enter() { +		_vm->_soundMan->stopMusic(); +		_vm->_disk->selectArchive((_vm->getFeatures() & GF_DEMO) ? "disk0" : "disk1"); +		_vm->showSlide("password"); -static const Common::Rect codeSelectBlocks[9] = { +		_emptySlots.create(BLOCK_WIDTH * 8, BLOCK_HEIGHT, 1); +		Common::Rect rect(SLOT_X, SLOT_Y, SLOT_X + BLOCK_WIDTH * 8, SLOT_Y + BLOCK_HEIGHT); +		_vm->_gfx->grabBackground(rect, _emptySlots); + +		_labels[0] = _vm->_gfx->createLabel(_vm->_introFont, introMsg1[_vm->getInternLanguage()], 1); +		_labels[1] = _vm->_gfx->createLabel(_vm->_introFont, introMsg2[_vm->getInternLanguage()], 1); + +		cleanup(); + +		_vm->setArrowCursor(); +		_vm->_input->setMouseState(MOUSE_ENABLED_SHOW); +		_state = CHOICE; +	} +}; + +const char *SelectCharacterInputState_NS::introMsg1[4] = { +	"INSERISCI IL CODICE", +	"ENTREZ CODE", +	"ENTER CODE", +	"GIB DEN KODE EIN" +}; + +const char *SelectCharacterInputState_NS::introMsg2[4] = { +	"CODICE ERRATO", +	"CODE ERRONE", +	"WRONG CODE", +	"GIB DEN KODE EIN" +}; + +const uint16 SelectCharacterInputState_NS::_amigaKeys[][PASSWORD_LEN] = { +	{ 5, 3, 6, 2, 2, 7 },		// dino +	{ 0, 3, 6, 2, 2, 6 },		// donna +	{ 1, 3 ,7, 2, 4, 6 }		// dough +}; + +const uint16 SelectCharacterInputState_NS::_pcKeys[][PASSWORD_LEN] = { +	{ 5, 3, 6, 1, 4, 7 },		// dino +	{ 0, 2, 8, 5, 5, 1 },		// donna +	{ 1, 7 ,7, 2, 2, 6 }		// dough +}; + +const char *SelectCharacterInputState_NS::_charStartLocation[] = { +	"test.dino", +	"test.donna", +	"test.dough" +}; + + +const Common::Rect SelectCharacterInputState_NS::codeSelectBlocks[9] = {  	Common::Rect( 111, 129, 127, 153 ),		// na  	Common::Rect( 128, 120, 144, 144 ),		// wa  	Common::Rect( 145, 111, 161, 135 ),		// ra @@ -348,7 +606,7 @@ static const Common::Rect codeSelectBlocks[9] = {  	Common::Rect( 247, 57, 263, 81 )		// ka  }; -static const Common::Rect codeTrueBlocks[9] = { +const Common::Rect SelectCharacterInputState_NS::codeTrueBlocks[9] = {  	Common::Rect( 112, 130, 128, 154 ),  	Common::Rect( 129, 121, 145, 145 ),  	Common::Rect( 146, 112, 162, 136 ), @@ -361,146 +619,219 @@ static const Common::Rect codeTrueBlocks[9] = {  }; -int Parallaction_ns::guiGetSelectedBlock(const Common::Point &p) { +class ShowCreditsInputState_NS : public MenuInputState { +	Parallaction_ns *_vm; +	int	_current; +	uint32 _startTime; -	int selection = -1; +	struct Credit { +		const char *_role; +		const char *_name; +	}; -	for (uint16 i = 0; i < 9; i++) { -		if (codeSelectBlocks[i].contains(p)) { -			selection = i; -			break; -		} +	static const Credit _credits[6]; + +public: +	ShowCreditsInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("showcredits", helper), _vm(vm) {  	} -	if ((selection != -1) && (getPlatform() == Common::kPlatformAmiga)) { -		_gfx->invertBackground(codeTrueBlocks[selection]); -		_gfx->updateScreen(); -		beep(); -		g_system->delayMillis(100); -		_gfx->invertBackground(codeTrueBlocks[selection]); -		_gfx->updateScreen(); +	void drawCurrentLabel() { +		uint id[2]; +		id[0] = _vm->_gfx->createLabel(_vm->_menuFont, _credits[_current]._role, 1); +		id[1] = _vm->_gfx->createLabel(_vm->_menuFont, _credits[_current]._name, 1); +		_vm->_gfx->showLabel(id[0], CENTER_LABEL_HORIZONTAL, 80); +		_vm->_gfx->showLabel(id[1], CENTER_LABEL_HORIZONTAL, 100);  	} -	return selection; -} +	virtual MenuInputState* run() { +		if (_current == -1) { +			_startTime = g_system->getMillis(); +			_current = 0; +			drawCurrentLabel(); +			return this; +		} -// -//	character selection and protection -// -int Parallaction_ns::guiSelectCharacter() { -	debugC(1, kDebugMenu, "Parallaction_ns::guiselectCharacter()"); +		int event = _vm->_input->getLastButtonEvent(); +		uint32 curTime = g_system->getMillis(); +		if ((event == kMouseLeftUp) || (curTime - _startTime > 5500)) { +			_current++; +			_startTime = curTime; +			_vm->_gfx->freeLabels(); -	setArrowCursor(); -	_soundMan->stopMusic(); +			if (_current == 6) { +				return _helper->getState("endintro"); +			} -	_disk->selectArchive((getFeatures() & GF_DEMO) ? "disk0" : "disk1"); +			drawCurrentLabel(); +		} -	showSlide("password"); +		return this; +	} +	virtual void enter() { +		_current = -1; +		_vm->_input->setMouseState(MOUSE_DISABLED); +	} +}; -	const uint16 (*keys)[PASSWORD_LEN] = (getPlatform() == Common::kPlatformAmiga && (getFeatures() & GF_LANG_MULT)) ? _amigaKeys : _pcKeys; -	uint16 _di = 0; -	byte points[3] = { 0, 0, 0 }; +const ShowCreditsInputState_NS::Credit ShowCreditsInputState_NS::_credits[6] = { +	{"Music and Sound Effects", "MARCO CAPRELLI"}, +	{"PC Version", "RICCARDO BALLARINO"}, +	{"Project Manager", "LOVRANO CANEPA"}, +	{"Production", "BRUNO BOZ"}, +	{"Special Thanks to", "LUIGI BENEDICENTI - GILDA and DANILO"}, +	{"Copyright 1992 Euclidea s.r.l ITALY", "All rights reserved"} +}; -	bool fail; +class EndIntroInputState_NS : public MenuInputState { +	Parallaction_ns *_vm; +	bool _isDemo; -	uint id[2]; -	id[0] = _gfx->createLabel(_introFont, introMsg1[_language], 1); -	id[1] = _gfx->createLabel(_introFont, introMsg2[_language], 1); +public: +	EndIntroInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("endintro", helper), _vm(vm) { +		_isDemo = (_vm->getFeatures() & GF_DEMO) != 0; +	} -	Graphics::Surface v14; -	v14.create(BLOCK_WIDTH * 8, BLOCK_HEIGHT, 1); -	Common::Rect rect(SLOT_X, SLOT_Y, SLOT_X + BLOCK_WIDTH * 8, SLOT_Y + BLOCK_HEIGHT); -	_gfx->grabBackground(rect, v14); +	virtual MenuInputState* run() { -	Graphics::Surface block; -	block.create(BLOCK_WIDTH, BLOCK_HEIGHT, 1); +		int event = _vm->_input->getLastButtonEvent(); +		if (event != kMouseLeftUp) { +			return this; +		} -	Common::Point p; +		if (_isDemo) { +			_engineFlags |= kEngineQuit; +			return 0; +		} -	while (true) { +		_vm->_gfx->freeLabels(); +		return _helper->getState("selectcharacter"); +	} -		points[0] = 0; -		points[1] = 0; -		points[2] = 0; -		fail = false; +	virtual void enter() { +		_vm->_soundMan->stopMusic(); +		_vm->_input->setMouseState(MOUSE_DISABLED); -		_gfx->hideLabel(id[1]); -		_gfx->showLabel(id[0], 60, 30); +		if (!_isDemo) { +			int label = _vm->_gfx->createLabel(_vm->_menuFont, "CLICK MOUSE BUTTON TO START", 1); +			_vm->_gfx->showLabel(label, CENTER_LABEL_HORIZONTAL, 80); +		} +	} +}; -		_di = 0; -		while (_di < PASSWORD_LEN) { -			_input->waitUntilLeftClick(); -			_input->getCursorPos(p); +class EndPartInputState_NS : public MenuInputState { +	Parallaction_ns *_vm; +	bool _allPartsComplete; -			int _si = guiGetSelectedBlock(p); +	// part completion messages +	static const char *endMsg0[4]; +	static const char *endMsg1[4]; +	static const char *endMsg2[4]; +	static const char *endMsg3[4]; +	// game completion messages +	static const char *endMsg4[4]; +	static const char *endMsg5[4]; +	static const char *endMsg6[4]; +	static const char *endMsg7[4]; -			if (_si != -1) { -				_gfx->grabBackground(codeTrueBlocks[_si], block); -				_gfx->patchBackground(block, _di * SLOT_WIDTH + SLOT_X, SLOT_Y, false); -				if (keys[0][_di] == _si) { -					points[0]++; -				} else -				if (keys[1][_di] == _si) { -					points[1]++; -				} else -				if (keys[2][_di] == _si) { -					points[2]++; -				} else { -					fail = true; -				} +public: +	EndPartInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("endpart", helper), _vm(vm) { +	} -				// build user preference -				points[0] += (keys[0][_di] == _si); -				points[1] += (keys[1][_di] == _si); -				points[2] += (keys[2][_di] == _si); +	virtual MenuInputState* run() { +		int event = _vm->_input->getLastButtonEvent(); +		if (event != kMouseLeftUp) { +			return this; +		} -				_di++; -			} +		_vm->_gfx->freeLabels(); +		if (_allPartsComplete) { +			_vm->scheduleLocationSwitch("estgrotta.drki"); +			return 0;  		} -		if (!fail) { -			break; +		return _helper->getState("selectcharacter"); +	} + +	virtual void enter() { +		_allPartsComplete = _vm->allPartsComplete(); +		_vm->_input->setMouseState(MOUSE_DISABLED); + +		uint id[4]; +		if (_allPartsComplete) { +			id[0] = _vm->_gfx->createLabel(_vm->_menuFont, endMsg4[_language], 1); +			id[1] = _vm->_gfx->createLabel(_vm->_menuFont, endMsg5[_language], 1); +			id[2] = _vm->_gfx->createLabel(_vm->_menuFont, endMsg6[_language], 1); +			id[3] = _vm->_gfx->createLabel(_vm->_menuFont, endMsg7[_language], 1); +		} else { +			id[0] = _vm->_gfx->createLabel(_vm->_menuFont, endMsg0[_language], 1); +			id[1] = _vm->_gfx->createLabel(_vm->_menuFont, endMsg1[_language], 1); +			id[2] = _vm->_gfx->createLabel(_vm->_menuFont, endMsg2[_language], 1); +			id[3] = _vm->_gfx->createLabel(_vm->_menuFont, endMsg3[_language], 1);  		} -		_gfx->patchBackground(v14, SLOT_X, SLOT_Y, false); +		_vm->_gfx->showLabel(id[0], CENTER_LABEL_HORIZONTAL, 70); +		_vm->_gfx->showLabel(id[1], CENTER_LABEL_HORIZONTAL, 100); +		_vm->_gfx->showLabel(id[2], CENTER_LABEL_HORIZONTAL, 130); +		_vm->_gfx->showLabel(id[3], CENTER_LABEL_HORIZONTAL, 160); +	} +}; -		_gfx->hideLabel(id[0]); -		_gfx->showLabel(id[1], 60, 30); +// part completion messages +const char *EndPartInputState_NS::endMsg0[] = {"COMPLIMENTI!", "BRAVO!", "CONGRATULATIONS!", "PRIMA!"}; +const char *EndPartInputState_NS::endMsg1[] = {"HAI FINITO QUESTA PARTE", "TU AS COMPLETE' CETTE AVENTURE", "YOU HAVE COMPLETED THIS PART", "DU HAST EIN ABENTEUER ERFOLGREICH"}; +const char *EndPartInputState_NS::endMsg2[] = {"ORA COMPLETA IL RESTO ", "AVEC SUCCES.",  "NOW GO ON WITH THE REST OF", "ZU ENDE GEFUHRT"}; +const char *EndPartInputState_NS::endMsg3[] = {"DELL' AVVENTURA",  "CONTINUE AVEC LES AUTRES", "THIS ADVENTURE", "MACH' MIT DEN ANDEREN WEITER"}; +// game completion messages +const char *EndPartInputState_NS::endMsg4[] = {"COMPLIMENTI!", "BRAVO!", "CONGRATULATIONS!", "PRIMA!"}; +const char *EndPartInputState_NS::endMsg5[] = {"HAI FINITO LE TRE PARTI", "TU AS COMPLETE' LES TROIS PARTIES", "YOU HAVE COMPLETED THE THREE PARTS", "DU HAST DREI ABENTEURE ERFOLGREICH"}; +const char *EndPartInputState_NS::endMsg6[] = {"DELL' AVVENTURA", "DE L'AVENTURE", "OF THIS ADVENTURE", "ZU ENDE GEFUHRT"}; +const char *EndPartInputState_NS::endMsg7[] = {"ED ORA IL GRAN FINALE ", "ET MAINTENANT LE GRAND FINAL", "NOW THE GREAT FINAL", "UND YETZT DER GROSSE SCHLUSS!"}; + +void Parallaction_ns::startGui() { +	_disk->selectArchive((getFeatures() & GF_DEMO) ? "disk0" : "disk1"); -		_gfx->updateScreen(); +	_menuHelper = new MenuInputHelper; +	assert(_menuHelper); -		g_system->delayMillis(2000); -	} +	new SelectGameInputState_NS(this, _menuHelper); +	new LoadGameInputState_NS(this, _menuHelper); +	new NewGameInputState_NS(this, _menuHelper); +	new StartDemoInputState_NS(this, _menuHelper); +	new SelectCharacterInputState_NS(this, _menuHelper); +	new ChooseLanguageInputState_NS(this, _menuHelper); +	new SplashInputState1_NS(this, _menuHelper); +	new SplashInputState0_NS(this, _menuHelper); +	_menuHelper->setState("intro0"); -	_gfx->freeLabels(); +	_input->_inputMode = Input::kInputModeMenu; +} -	_gfx->setBlackPalette(); -	_gfx->updateScreen(); +void Parallaction_ns::startCreditSequence() { +	_menuHelper = new MenuInputHelper; +	assert(_menuHelper); -	v14.free(); +	new ShowCreditsInputState_NS(this, _menuHelper); +	new EndIntroInputState_NS(this, _menuHelper); +	new SelectCharacterInputState_NS(this, _menuHelper); +	_menuHelper->setState("showcredits"); +	_input->_inputMode = Input::kInputModeMenu; +} -	// actually select character +void Parallaction_ns::startEndPartSequence() { +	_menuHelper = new MenuInputHelper; +	assert(_menuHelper); -	int character = -1; -	if (points[0] >= points[1] && points[0] >= points[2]) { -		character = CHAR_DINO; -	} else -	if (points[1] >= points[0] && points[1] >= points[2]) { -		character = CHAR_DONNA; -	} else -	if (points[2] >= points[0] && points[2] >= points[1]) { -		character = CHAR_DOUGH; -	} else { -		error("If you read this, either your CPU or transivity is broken (we believe the former)."); -	} +	new EndPartInputState_NS(this, _menuHelper); +	new SelectCharacterInputState_NS(this, _menuHelper); +	_menuHelper->setState("endpart"); -	return character; +	_input->_inputMode = Input::kInputModeMenu;  } diff --git a/engines/parallaction/input.cpp b/engines/parallaction/input.cpp index 28d0ad888d..287618e803 100644 --- a/engines/parallaction/input.cpp +++ b/engines/parallaction/input.cpp @@ -36,23 +36,23 @@ namespace Parallaction {  // loops which could possibly be merged into this one with some effort in changing  // caller code, i.e. adding condition checks.  // -uint16 Input::readInput() { +void Input::readInput() {  	Common::Event e; -	uint16 KeyDown = 0;  	_mouseButtons = kMouseNone; +	_hasKeyPressEvent = false;  	Common::EventManager *eventMan = _vm->_system->getEventManager();  	while (eventMan->pollEvent(e)) {  		switch (e.type) {  		case Common::EVENT_KEYDOWN: +			_hasKeyPressEvent = true; +			_keyPressed = e.kbd; +  			if (e.kbd.flags == Common::KBD_CTRL && e.kbd.keycode == 'd')  				_vm->_debugger->attach(); -			if (_vm->getFeatures() & GF_DEMO) break; -			if (e.kbd.keycode == Common::KEYCODE_l) KeyDown = kEvLoadGame; -			if (e.kbd.keycode == Common::KEYCODE_s) KeyDown = kEvSaveGame;  			break;  		case Common::EVENT_LBUTTONDOWN: @@ -80,11 +80,8 @@ uint16 Input::readInput() {  			break;  		case Common::EVENT_QUIT: -			// TODO: don't quit() here, just have caller routines to check -			// on kEngineQuit and exit gracefully to allow the engine to shut down  			_engineFlags |= kEngineQuit; -			_vm->_system->quit(); -			break; +			return;  		default:  			break; @@ -96,10 +93,15 @@ uint16 Input::readInput() {  	if (_vm->_debugger->isAttached())  		_vm->_debugger->onFrame(); -	return KeyDown; +	return;  } +bool Input::getLastKeyDown(uint16 &ascii) { +	ascii = _keyPressed.ascii; +	return (_hasKeyPressEvent); +} +  // FIXME: see comment for readInput()  void Input::waitForButtonEvent(uint32 buttonEventMask, int32 timeout) { @@ -124,64 +126,36 @@ void Input::waitForButtonEvent(uint32 buttonEventMask, int32 timeout) {  } -// FIXME: see comment for readInput() -void Input::waitUntilLeftClick() { - -	do { -		readInput(); -		_vm->_gfx->updateScreen(); -		_vm->_system->delayMillis(30); -	} while (_mouseButtons != kMouseLeftUp); - -	return; -} -  void Input::updateGameInput() { -	int16 keyDown = readInput(); - -	debugC(3, kDebugInput, "translateInput: input flags (%i, %i, %i, %i)", -		!_mouseHidden, -		(_engineFlags & kEngineBlockInput) == 0, -		(_engineFlags & kEngineWalking) == 0, -		(_engineFlags & kEngineChangeLocation) == 0 -	); +	readInput(); -	if ((_mouseHidden) || -		(_engineFlags & kEngineBlockInput) || +	if (!isMouseEnabled() ||  		(_engineFlags & kEngineWalking) ||  		(_engineFlags & kEngineChangeLocation)) { +		debugC(3, kDebugInput, "updateGameInput: input flags (mouse: %i, walking: %i, changeloc: %i)", +			isMouseEnabled(), +			(_engineFlags & kEngineWalking) == 0, +			(_engineFlags & kEngineChangeLocation) == 0 +		); +  		return;  	} -	if (keyDown == kEvQuitGame) { -		_inputData._event = kEvQuitGame; -	} else -	if (keyDown == kEvSaveGame) { -		_inputData._event = kEvSaveGame; -	} else -	if (keyDown == kEvLoadGame) { -		_inputData._event = kEvLoadGame; -	} else { +	if (_hasKeyPressEvent && (_vm->getFeatures() & GF_DEMO) == 0) { +		if (_keyPressed.keycode == Common::KEYCODE_l) _inputData._event = kEvLoadGame; +		if (_keyPressed.keycode == Common::KEYCODE_s) _inputData._event = kEvSaveGame; +	} + +	if (_inputData._event == kEvNone) {  		_inputData._mousePos = _mousePos; -		_inputData._event = kEvNone; -		if (!translateGameInput()) { -			translateInventoryInput(); -		} +		translateGameInput();  	}  } -void Input::updateCommentInput() { -	waitUntilLeftClick(); - -	_vm->_gfx->hideDialogueStuff(); -	_vm->_gfx->setHalfbriteMode(false); - -	_inputMode = kInputModeGame; -}  InputData* Input::updateInput() { @@ -189,84 +163,109 @@ InputData* Input::updateInput() {  	switch (_inputMode) {  	case kInputModeComment: -		updateCommentInput(); +	case kInputModeDialogue: +	case kInputModeMenu: +		readInput();  		break;  	case kInputModeGame:  		updateGameInput();  		break; + +	case kInputModeInventory: +		readInput(); +		updateInventoryInput(); +		break;  	}  	return &_inputData;  } +void Input::trackMouse(ZonePtr z) { +	if ((z != _hoverZone) && (_hoverZone)) { +		stopHovering(); +		return; +	} + +	if (!z) { +		return; +	} + +	if ((!_hoverZone) && ((z->_flags & kFlagsNoName) == 0)) { +		_hoverZone = z; +		_vm->_gfx->showFloatingLabel(_hoverZone->_label); +		return; +	} +} + +void Input::stopHovering() { +	_hoverZone = nullZonePtr; +	_vm->_gfx->hideFloatingLabel(); +} + +void Input::takeAction(ZonePtr z) { +	stopHovering(); +	_vm->pauseJobs(); +	_vm->runZone(z); +	_vm->resumeJobs(); +} + +void Input::walkTo(const Common::Point &dest) { +	stopHovering(); +	_vm->setArrowCursor(); +	_vm->_char.scheduleWalk(dest.x, dest.y); +} +  bool Input::translateGameInput() { -	if ((_engineFlags & kEnginePauseJobs) || (_engineFlags & kEngineInventory)) { +	if (_engineFlags & kEnginePauseJobs) {  		return false;  	} -	if (_actionAfterWalk) { +	if (_hasDelayedAction) {  		// if walking is over, then take programmed action -		_inputData._event = kEvAction; -		_actionAfterWalk = false; +		takeAction(_delayedActionZone); +		_hasDelayedAction = false; +		_delayedActionZone = nullZonePtr;  		return true;  	}  	if (_mouseButtons == kMouseRightDown) {  		// right button down shows inventory - -		if (_vm->hitZone(kZoneYou, _mousePos.x, _mousePos.y) && (_activeItem._id != 0)) { -			_activeItem._index = (_activeItem._id >> 16) & 0xFFFF; -			_engineFlags |= kEngineDragging; -		} - -		_inputData._event = kEvOpenInventory; -		_transCurrentHoverItem = -1; +		enterInventoryMode();  		return true;  	}  	// test if mouse is hovering on an interactive zone for the currently selected inventory item  	ZonePtr z = _vm->hitZone(_activeItem._id, _mousePos.x, _mousePos.y); +	Common::Point dest(_mousePos);  	if (((_mouseButtons == kMouseLeftUp) && (_activeItem._id == 0) && ((_engineFlags & kEngineWalking) == 0)) && ((!z) || ((z->_type & 0xFFFF) != kZoneCommand))) { -		_inputData._event = kEvWalk; +		walkTo(dest);  		return true;  	} -	if ((z != _hoverZone) && (_hoverZone)) { -		_hoverZone = nullZonePtr; -		_inputData._event = kEvExitZone; -		return true; -	} - -	if (!z) { -		_inputData._event = kEvNone; -		return true; -	} - -	if ((!_hoverZone) && ((z->_flags & kFlagsNoName) == 0)) { -		_hoverZone = z; -		_inputData._event = kEvEnterZone; -		_inputData._label = z->_label; -		return true; -	} +	trackMouse(z); + 	if (!z) { + 		return true; + 	}  	if ((_mouseButtons == kMouseLeftUp) && ((_activeItem._id != 0) || ((z->_type & 0xFFFF) == kZoneCommand))) {  		_inputData._zone = z;  		if (z->_flags & kFlagsNoWalk) {  			// character doesn't need to walk to take specified action -			_inputData._event = kEvAction; - +			takeAction(z);  		} else {  			// action delayed: if Zone defined a moveto position the character is programmed to move there,  			// else it will move to the mouse position -			_inputData._event = kEvWalk; -			_actionAfterWalk = true; +			_delayedActionZone = z; +			_hasDelayedAction = true;  			if (z->_moveTo.y != 0) { -				_inputData._mousePos = z->_moveTo; +				dest = z->_moveTo;  			} + +			walkTo(dest);  		}  		_vm->beep(); @@ -275,58 +274,103 @@ bool Input::translateGameInput() {  	}  	return true; -  } -bool Input::translateInventoryInput() { -	if ((_engineFlags & kEngineInventory) == 0) { -		return false; +void Input::enterInventoryMode() { +	bool hitCharacter = _vm->hitZone(kZoneYou, _mousePos.x, _mousePos.y); + +	if (hitCharacter) { +		if (_activeItem._id != 0) { +			_activeItem._index = (_activeItem._id >> 16) & 0xFFFF; +			_engineFlags |= kEngineDragging; +		} else { +			_vm->setArrowCursor(); +		}  	} -	// in inventory -	int16 _si = _vm->getHoverInventoryItem(_mousePos.x, _mousePos.y); +	stopHovering(); +	_vm->pauseJobs(); +	_vm->openInventory(); -	if (_mouseButtons == kMouseRightUp) { -		// right up hides inventory +	_transCurrentHoverItem = -1; -		_inputData._event = kEvCloseInventory; -		_inputData._inventoryIndex = _vm->getHoverInventoryItem(_mousePos.x, _mousePos.y); -		_vm->highlightInventoryItem(-1);			// disable +	_inputMode = kInputModeInventory; +} -		if ((_engineFlags & kEngineDragging) == 0) { -			return true; -		} +void Input::exitInventoryMode() { +	// right up hides inventory + +	int pos = _vm->getHoverInventoryItem(_mousePos.x, _mousePos.y); +	_vm->highlightInventoryItem(-1);			// disable + +	if ((_engineFlags & kEngineDragging)) {  		_engineFlags &= ~kEngineDragging; -		ZonePtr z = _vm->hitZone(kZoneMerge, _activeItem._index, _vm->getInventoryItemIndex(_inputData._inventoryIndex)); +		ZonePtr z = _vm->hitZone(kZoneMerge, _activeItem._index, _vm->getInventoryItemIndex(pos));  		if (z) {  			_vm->dropItem(z->u.merge->_obj1);  			_vm->dropItem(z->u.merge->_obj2);  			_vm->addInventoryItem(z->u.merge->_obj3); -			_vm->runCommands(z->_commands); +			_vm->_cmdExec->run(z->_commands);  		} -		return true;  	} -	if (_si == _transCurrentHoverItem) { -		_inputData._event = kEvNone; +	_vm->closeInventory(); +	if (pos == -1) { +		_vm->setArrowCursor(); +	} else { +		const InventoryItem *item = _vm->getInventoryItem(pos); +		if (item->_index != 0) { +			_activeItem._id = item->_id; +			_vm->setInventoryCursor(item->_index); +		} +	} +	_vm->resumeJobs(); + +	_inputMode = kInputModeGame; +} + +bool Input::updateInventoryInput() { +	if (_mouseButtons == kMouseRightUp) { +		exitInventoryMode();  		return true;  	} -	_transCurrentHoverItem = _si; -	_inputData._event = kEvHoverInventory; -	_inputData._inventoryIndex = _si; +	int16 _si = _vm->getHoverInventoryItem(_mousePos.x, _mousePos.y); +	if (_si != _transCurrentHoverItem) { +		_transCurrentHoverItem = _si; +		_vm->highlightInventoryItem(_si);						// enable +	} +  	return true;  } -void Input::showCursor(bool visible) { -	_mouseHidden = !visible; -	_vm->_system->showMouse(visible); +void Input::setMouseState(MouseTriState state) { +	assert(state == MOUSE_ENABLED_SHOW || state == MOUSE_ENABLED_HIDE || state == MOUSE_DISABLED); +	_mouseState = state; + +	switch (_mouseState) { +	case MOUSE_ENABLED_HIDE: +	case MOUSE_DISABLED: +		_vm->_system->showMouse(false); +		break; + +	case MOUSE_ENABLED_SHOW: +		_vm->_system->showMouse(true); +		break; +	} +} + +MouseTriState Input::getMouseState() { +	return _mouseState;  } +bool Input::isMouseEnabled() { +	return (_mouseState == MOUSE_ENABLED_SHOW) || (_mouseState == MOUSE_ENABLED_HIDE); +}  } // namespace Parallaction diff --git a/engines/parallaction/input.h b/engines/parallaction/input.h index 46dbb08c4e..c1e912db74 100644 --- a/engines/parallaction/input.h +++ b/engines/parallaction/input.h @@ -26,6 +26,8 @@  #ifndef PARALLACTION_INPUT_H  #define PARALLACTION_INPUT_H +#include "common/keyboard.h" +  #include "parallaction/objects.h"  #include "parallaction/inventory.h" @@ -44,52 +46,68 @@ struct InputData {  	Common::Point	_mousePos;  	int16		_inventoryIndex;  	ZonePtr		_zone; -	Label*			_label; +	uint		_label; +}; + +enum MouseTriState { +	MOUSE_ENABLED_SHOW, +	MOUSE_ENABLED_HIDE, +	MOUSE_DISABLED  };  class Input {  	void updateGameInput(); -	void updateCommentInput();  	// input-only  	InputData	_inputData; -	bool		_actionAfterWalk;  // actived when the character needs to move before taking an action -	// these two could/should be merged as they carry on the same duty in two member functions, -	// respectively processInput and translateInput + +	bool		_hasKeyPressEvent; +	Common::KeyState _keyPressed; + +	bool		_hasDelayedAction;  // actived when the character needs to move before taking an action +	ZonePtr		_delayedActionZone; +  	int16		_transCurrentHoverItem;  	InputData	*translateInput();  	bool		translateGameInput(); -	bool		translateInventoryInput(); +	bool		updateInventoryInput(); +	void 		takeAction(ZonePtr z); +	void 		walkTo(const Common::Point &dest);  	Parallaction	*_vm;  	Common::Point	_mousePos;  	uint16	_mouseButtons; -	bool		_mouseHidden;  	ZonePtr			_hoverZone; +	void	enterInventoryMode(); +	void 	exitInventoryMode(); +  public:  	enum {  		kInputModeGame = 0, -		kInputModeComment = 1 +		kInputModeComment = 1, +		kInputModeDialogue = 2, +		kInputModeInventory = 3, +		kInputModeMenu = 4  	};  	Input(Parallaction *vm) : _vm(vm) {  		_transCurrentHoverItem = 0; -		_actionAfterWalk = false;  // actived when the character needs to move before taking an action -		_mouseHidden = false; +		_hasDelayedAction = false;  // actived when the character needs to move before taking an action +		_mouseState = MOUSE_DISABLED;  		_activeItem._index = 0;  		_activeItem._id = 0;  		_mouseButtons = 0; +		_delayedActionZone = nullZonePtr;  	}  	virtual ~Input() { } -	void		showCursor(bool visible);  	void			getCursorPos(Common::Point& p) {  		p = _mousePos;  	} @@ -97,16 +115,20 @@ public:  	int				_inputMode;  	InventoryItem	_activeItem; -	uint16	readInput(); +	void	readInput();  	InputData* 	updateInput(); -	void 	waitUntilLeftClick(); +	void	trackMouse(ZonePtr z);  	void	waitForButtonEvent(uint32 buttonEventMask, int32 timeout = -1);  	uint32	getLastButtonEvent() { return _mouseButtons; } +	bool  	getLastKeyDown(uint16 &ascii); -	void stopHovering() { -		_hoverZone = nullZonePtr; -	} +	void stopHovering(); + +	MouseTriState _mouseState; +	void setMouseState(MouseTriState state); +	MouseTriState getMouseState(); +	bool isMouseEnabled();  };  } // namespace Parallaction diff --git a/engines/parallaction/inventory.cpp b/engines/parallaction/inventory.cpp index 58848196d7..7b92974205 100644 --- a/engines/parallaction/inventory.cpp +++ b/engines/parallaction/inventory.cpp @@ -30,23 +30,58 @@  namespace Parallaction { -// -//	inventory is a grid made of (at most) 30 cells, 24x24 pixels each, -//	arranged in 6 lines -// -//	inventory items are stored in cnv files in a 32x24 grid -//	but only 24x24 pixels are actually copied to graphic memory -// + +/* +#define INVENTORYITEM_PITCH			32 +#define INVENTORYITEM_WIDTH			24 +#define INVENTORYITEM_HEIGHT		24  #define INVENTORY_MAX_ITEMS			30 -#define INVENTORY_FIRST_ITEM		4		// first four entries are used up by verbs  #define INVENTORY_ITEMS_PER_LINE	5  #define INVENTORY_LINES				6  #define INVENTORY_WIDTH				(INVENTORY_ITEMS_PER_LINE*INVENTORYITEM_WIDTH)  #define INVENTORY_HEIGHT			(INVENTORY_LINES*INVENTORYITEM_HEIGHT) - +*/ + +InventoryItem _verbs_NS[] = { +	{ 1, kZoneDoor }, +	{ 3, kZoneExamine }, +	{ 2, kZoneGet }, +	{ 4, kZoneSpeak }, +	{ 0, 0 } +}; + +InventoryItem _verbs_BR[] = { +	{ 1, kZoneBox }, +	{ 2, kZoneGet }, +	{ 3, kZoneExamine }, +	{ 4, kZoneSpeak }, +	{ 0, 0 } +}; + +InventoryProperties	_invProps_NS = { +	32, 		// INVENTORYITEM_PITCH +	24, 		// INVENTORYITEM_WIDTH +	24, 		// INVENTORYITEM_HEIGHT +	30, 		// INVENTORY_MAX_ITEMS +	5, 			// INVENTORY_ITEMS_PER_LINE +	6, 			// INVENTORY_LINES +	5 * 24, 	// INVENTORY_WIDTH =(INVENTORY_ITEMS_PER_LINE*INVENTORYITEM_WIDTH) +	6 * 24		// INVENTORY_HEIGHT = (INVENTORY_LINES*INVENTORYITEM_HEIGHT) +}; + +InventoryProperties	_invProps_BR = { +	51, 		// INVENTORYITEM_PITCH +	51, 		// INVENTORYITEM_WIDTH +	51, 		// INVENTORYITEM_HEIGHT +	48, 		// INVENTORY_MAX_ITEMS +	6, 			// INVENTORY_ITEMS_PER_LINE +	8, 			// INVENTORY_LINES +	6 * 51, 		// INVENTORY_WIDTH =(INVENTORY_ITEMS_PER_LINE*INVENTORYITEM_WIDTH) +	8 * 51			// INVENTORY_HEIGHT = (INVENTORY_LINES*INVENTORYITEM_HEIGHT) +};  int16 Parallaction::getHoverInventoryItem(int16 x, int16 y) {  	return _inventoryRenderer->hitTest(Common::Point(x,y)); @@ -91,8 +126,19 @@ int16 Parallaction::getInventoryItemIndex(int16 pos) {  }  void Parallaction::initInventory() { -	_inventory = new Inventory(INVENTORY_MAX_ITEMS); -	_inventoryRenderer = new InventoryRenderer(this); +	InventoryProperties *props; +	InventoryItem *verbs; + +	if (getGameType() == GType_Nippon) { +		props = &_invProps_NS; +		verbs = _verbs_NS; +	} else { +		props = &_invProps_BR; +		verbs = _verbs_BR; +	} + +	_inventory = new Inventory(props, verbs); +	_inventoryRenderer = new InventoryRenderer(this, props);  	_inventoryRenderer->bindInventory(_inventory);  } @@ -119,8 +165,8 @@ void Parallaction::closeInventory() { -InventoryRenderer::InventoryRenderer(Parallaction *vm) : _vm(vm) { -	_surf.create(INVENTORY_WIDTH, INVENTORY_HEIGHT, 1); +InventoryRenderer::InventoryRenderer(Parallaction *vm, InventoryProperties *props) : _vm(vm), _props(props) { +	_surf.create(_props->_width, _props->_height, 1);  }  InventoryRenderer::~InventoryRenderer() { @@ -131,15 +177,13 @@ void InventoryRenderer::showInventory() {  	if (!_inv)  		error("InventoryRenderer not bound to inventory"); -	_engineFlags |= kEngineInventory; -  	uint16 lines = getNumLines();  	Common::Point p;  	_vm->_input->getCursorPos(p); -	_pos.x = CLIP(p.x - (INVENTORY_WIDTH / 2), 0, (int)(_vm->_screenWidth - INVENTORY_WIDTH)); -	_pos.y = CLIP(p.y - 2 - (lines * INVENTORYITEM_HEIGHT), 0, (int)(_vm->_screenHeight - lines * INVENTORYITEM_HEIGHT)); +	_pos.x = CLIP((int)(p.x - (_props->_width / 2)), 0, (int)(_vm->_screenWidth - _props->_width)); +	_pos.y = CLIP((int)(p.y - 2 - (lines * _props->_itemHeight)), 0, (int)(_vm->_screenHeight - lines * _props->_itemHeight));  	refresh();  } @@ -147,13 +191,11 @@ void InventoryRenderer::showInventory() {  void InventoryRenderer::hideInventory() {  	if (!_inv)  		error("InventoryRenderer not bound to inventory"); - -	_engineFlags &= ~kEngineInventory;  }  void InventoryRenderer::getRect(Common::Rect& r) const { -	r.setWidth(INVENTORY_WIDTH); -	r.setHeight(INVENTORYITEM_HEIGHT * getNumLines()); +	r.setWidth(_props->_width); +	r.setHeight(_props->_itemHeight * getNumLines());  	r.moveTo(_pos);  } @@ -163,35 +205,36 @@ ItemPosition InventoryRenderer::hitTest(const Common::Point &p) const {  	if (!r.contains(p))  		return -1; -	return ((p.x - _pos.x) / INVENTORYITEM_WIDTH) + (INVENTORY_ITEMS_PER_LINE * ((p.y - _pos.y) / INVENTORYITEM_HEIGHT)); +	return ((p.x - _pos.x) / _props->_itemWidth) + (_props->_itemsPerLine * ((p.y - _pos.y) / _props->_itemHeight));  } -  void InventoryRenderer::drawItem(ItemPosition pos, ItemName name) { -  	Common::Rect r;  	getItemRect(pos, r); +	byte* d = (byte*)_surf.getBasePtr(r.left, r.top); +	drawItem(name, d, _surf.pitch); +} -	// FIXME: this will end up in a general blit function - +void InventoryRenderer::drawItem(ItemName name, byte *buffer, uint pitch) {  	byte* s = _vm->_char._objs->getData(name); -	byte* d = (byte*)_surf.getBasePtr(r.left, r.top); -	for (uint32 i = 0; i < INVENTORYITEM_HEIGHT; i++) { -		memcpy(d, s, INVENTORYITEM_WIDTH); +	byte* d = buffer; +	for (uint i = 0; i < _props->_itemHeight; i++) { +		memcpy(d, s, _props->_itemWidth); -		d += INVENTORY_WIDTH; -		s += INVENTORYITEM_PITCH; +		s += _props->_itemPitch; +		d += pitch;  	}  } +  int16 InventoryRenderer::getNumLines() const {  	int16 num = _inv->getNumItems(); -	return (num / INVENTORY_ITEMS_PER_LINE) + ((num % INVENTORY_ITEMS_PER_LINE) > 0 ? 1 : 0); +	return (num / _props->_itemsPerLine) + ((num % _props->_itemsPerLine) > 0 ? 1 : 0);  }  void InventoryRenderer::refresh() { -	for (uint16 i = 0; i < INVENTORY_MAX_ITEMS; i++) { +	for (uint16 i = 0; i < _props->_maxItems; i++) {  		ItemName name = _inv->getItemName(i);  		drawItem(i, name);  	} @@ -212,25 +255,24 @@ void InventoryRenderer::highlightItem(ItemPosition pos, byte color) {  void InventoryRenderer::getItemRect(ItemPosition pos, Common::Rect &r) { -	r.setHeight(INVENTORYITEM_HEIGHT); -	r.setWidth(INVENTORYITEM_WIDTH); +	r.setHeight(_props->_itemHeight); +	r.setWidth(_props->_itemWidth); -	uint16 line = pos / INVENTORY_ITEMS_PER_LINE; -	uint16 col = pos % INVENTORY_ITEMS_PER_LINE; +	uint16 line = pos / _props->_itemsPerLine; +	uint16 col = pos % _props->_itemsPerLine; -	r.moveTo(col * INVENTORYITEM_WIDTH, line * INVENTORYITEM_HEIGHT); +	r.moveTo(col * _props->_itemWidth, line * _props->_itemHeight);  } +Inventory::Inventory(InventoryProperties *props, InventoryItem *verbs) : _numItems(0), _props(props) { +	_items = (InventoryItem*)calloc(_props->_maxItems, sizeof(InventoryItem)); - -Inventory::Inventory(uint16 maxItems) : _maxItems(maxItems), _numItems(0) { -	_items = (InventoryItem*)calloc(_maxItems, sizeof(InventoryItem)); - -	addItem(1, kZoneDoor); -	addItem(3, kZoneExamine); -	addItem(2, kZoneGet); -	addItem(4, kZoneSpeak); +	int i = 0; +	for ( ; verbs[i]._id; i++) { +		addItem(verbs[i]._id, verbs[i]._index); +	} +	_numVerbs = i;  } @@ -241,7 +283,7 @@ Inventory::~Inventory() {  ItemPosition Inventory::addItem(ItemName name, uint32 value) {  	debugC(1, kDebugInventory, "addItem(%i, %i)", name, value); -	if (_numItems == INVENTORY_MAX_ITEMS) { +	if (_numItems == _props->_maxItems) {  		debugC(3, kDebugInventory, "addItem: inventory is full");  		return -1;  	} @@ -300,9 +342,9 @@ void Inventory::removeItem(ItemName name) {  void Inventory::clear(bool keepVerbs) {  	debugC(1, kDebugInventory, "clearInventory()"); -	uint first = (keepVerbs ? INVENTORY_FIRST_ITEM : 0); +	uint first = (keepVerbs ? _numVerbs : 0); -	for (uint16 slot = first; slot < _maxItems; slot++) { +	for (uint16 slot = first; slot < _numVerbs; slot++) {  		_items[slot]._id = 0;  		_items[slot]._index = 0;  	} @@ -312,7 +354,7 @@ void Inventory::clear(bool keepVerbs) {  ItemName Inventory::getItemName(ItemPosition pos) const { -	return (pos >= 0 && pos < INVENTORY_MAX_ITEMS) ? _items[pos]._index : 0; +	return (pos >= 0 && pos < _props->_maxItems) ? _items[pos]._index : 0;  }  const InventoryItem* Inventory::getItem(ItemPosition pos) const { diff --git a/engines/parallaction/inventory.h b/engines/parallaction/inventory.h index 8c32c09219..f041627810 100644 --- a/engines/parallaction/inventory.h +++ b/engines/parallaction/inventory.h @@ -38,9 +38,19 @@ struct InventoryItem {  	uint16		_index;			// index to frame in objs file  }; -#define INVENTORYITEM_PITCH			32 -#define INVENTORYITEM_WIDTH			24 -#define INVENTORYITEM_HEIGHT		24 +struct InventoryProperties { +	uint _itemPitch; +	uint _itemWidth; +	uint _itemHeight; + +	int _maxItems; + +	int _itemsPerLine; +	int _maxLines; + +	int _width; +	int _height; +};  #define MAKE_INVENTORY_ID(x) (((x) & 0xFFFF) << 16) @@ -50,12 +60,14 @@ typedef uint16 ItemName;  class Inventory {  protected: +	uint16			_numVerbs; +  	InventoryItem	*_items; -	uint16			_maxItems;  	uint16			_numItems; +	InventoryProperties *_props;  public: -	Inventory(uint16 maxItems); +	Inventory(InventoryProperties *props, InventoryItem *verbs);  	virtual ~Inventory();  	ItemPosition addItem(ItemName name, uint32 value); @@ -75,6 +87,8 @@ public:  class InventoryRenderer {  	Parallaction	*_vm; +	InventoryProperties *_props; +  	Inventory		*_inv;  	Common::Point	_pos; @@ -87,7 +101,7 @@ protected:  	void refresh();  public: -	InventoryRenderer(Parallaction *vm); +	InventoryRenderer(Parallaction *vm, InventoryProperties *props);  	virtual ~InventoryRenderer();  	void bindInventory(Inventory *inv) { _inv = inv; } @@ -97,6 +111,7 @@ public:  	ItemPosition hitTest(const Common::Point &p) const;  	void highlightItem(ItemPosition pos, byte color); +	void drawItem(ItemName name, byte *buffer, uint pitch);  	byte*	getData() const { return (byte*)_surf.pixels; } diff --git a/engines/parallaction/module.mk b/engines/parallaction/module.mk index 2478b4b2e1..9d44422541 100644 --- a/engines/parallaction/module.mk +++ b/engines/parallaction/module.mk @@ -1,6 +1,7 @@  MODULE := engines/parallaction  MODULE_OBJS := \ +	balloons.o \  	callables_br.o \  	callables_ns.o \  	debug.o \ @@ -13,6 +14,7 @@ MODULE_OBJS := \  	font.o \  	gfxbase.o \  	graphics.o \ +	gui.o \  	gui_br.o \  	gui_ns.o \  	input.o \ diff --git a/engines/parallaction/objects.cpp b/engines/parallaction/objects.cpp index cac31911f4..c387484de7 100644 --- a/engines/parallaction/objects.cpp +++ b/engines/parallaction/objects.cpp @@ -54,19 +54,20 @@ Animation::Animation() {  Animation::~Animation() {  	free(_scriptName); +	gfxobj->release();  }  uint16 Animation::width() const {  	if (!gfxobj) return 0;  	Common::Rect r; -	gfxobj->getRect(0, r); +	gfxobj->getRect(_frame, r);  	return r.width();  }  uint16 Animation::height() const {  	if (!gfxobj) return 0;  	Common::Rect r; -	gfxobj->getRect(0, r); +	gfxobj->getRect(_frame, r);  	return r.height();  } @@ -80,6 +81,12 @@ byte* Animation::getFrameData(uint32 index) const {  	return gfxobj->getData(index);  } +void Animation::validateScriptVars() { +	// this is used to clip values of _frame, _left and _top +	// which can be screwed up by buggy scripts. + +	_frame = CLIP(_frame, (int16)0, (int16)(getFrameNum() - 1)); +}  #define NUM_LOCALS	10  char	_localNames[NUM_LOCALS][10]; @@ -182,7 +189,8 @@ Zone::~Zone() {  		break;  	} -	delete _label; + +	free(_linkedName);  }  void Zone::getRect(Common::Rect& r) const { @@ -207,6 +215,16 @@ uint16 Zone::height() const {  	return _bottom - _top;  } +Dialogue::Dialogue() { +	memset(_questions, 0, sizeof(_questions)); +} + +Dialogue::~Dialogue() { +	for (int i = 0; i < NUM_QUESTIONS; i++) { +		delete _questions[i]; +	} +} +  Answer::Answer() {  	_text = NULL;  	_mood = 0; diff --git a/engines/parallaction/objects.h b/engines/parallaction/objects.h index c2c2c154b5..a3bf757bdb 100644 --- a/engines/parallaction/objects.h +++ b/engines/parallaction/objects.h @@ -54,6 +54,7 @@ typedef Common::SharedPtr<Instruction> InstructionPtr;  typedef Common::List<InstructionPtr> InstructionList;  extern InstructionPtr nullInstructionPtr; +typedef Common::List<Common::Point> PointList;  enum ZoneTypes {  	kZoneExamine	   = 1,					// zone displays comment if activated @@ -67,7 +68,11 @@ enum ZoneTypes {  	kZoneNone		   = 0x100,				// used to prevent parsing on peculiar Animations  	kZoneTrap		   = 0x200,				// zone activated when character enters  	kZoneYou		   = 0x400,				// marks the character -	kZoneCommand	   = 0x800 +	kZoneCommand	   = 0x800, + +	// BRA specific +	kZonePath          = 0x1000,			// defines nodes for assisting walk calculation routines +	kZoneBox           = 0x2000  }; @@ -89,6 +94,7 @@ enum ZoneFlags {  	kFlagsYourself		= 0x1000,  	kFlagsScaled		= 0x2000,  	kFlagsSelfuse		= 0x4000, +	kFlagsIsAnimation	= 0x1000000,		// BRA: used in walk code (trap check), to tell is a Zone is an Animation  	kFlagsAnimLinked	= 0x2000000  }; @@ -181,6 +187,9 @@ struct Question {  struct Dialogue {  	Question	*_questions[NUM_QUESTIONS]; + +	Dialogue(); +	~Dialogue();  };  struct GetData {	// size = 24 @@ -206,7 +215,7 @@ struct SpeakData {	// size = 36  	}  };  struct ExamineData {	// size = 28 -	Frames	*_cnv; +	GfxObj	*_cnv;  	uint16		_opBase;		   // unused  	uint16		field_12;			// unused  	char*		_description; @@ -253,6 +262,15 @@ struct MergeData {	// size = 12  		_obj1 = _obj2 = _obj3 = 0;  	}  }; +#define MAX_WALKPOINT_LISTS 20 +struct PathData { +	int	_numLists; +	PointList	_lists[MAX_WALKPOINT_LISTS]; + +	PathData() { +		_numLists = 0; +	} +};  struct TypeData {  	GetData		*get; @@ -261,6 +279,8 @@ struct TypeData {  	DoorData	*door;  	HearData	*hear;  	MergeData	*merge; +	// BRA specific field +	PathData	*path;  	TypeData() {  		get = NULL; @@ -269,6 +289,7 @@ struct TypeData {  		door = NULL;  		hear = NULL;  		merge = NULL; +		path = NULL;  	}  }; @@ -284,7 +305,7 @@ struct Zone {  	int16			_bottom;  	uint32			_type;  	uint32			_flags; -	Label			*_label; +	uint			_label;  	uint16			field_2C;		// unused  	uint16			field_2E;		// unused  	TypeData		u; @@ -429,6 +450,8 @@ struct Animation : public Zone {  	virtual uint16 height() const;  	uint16 getFrameNum() const;  	byte* getFrameData(uint32 index) const; + +	void validateScriptVars();  };  class Table { diff --git a/engines/parallaction/parallaction.cpp b/engines/parallaction/parallaction.cpp index d66b1af1f1..bb306c3299 100644 --- a/engines/parallaction/parallaction.cpp +++ b/engines/parallaction/parallaction.cpp @@ -85,20 +85,28 @@ Parallaction::Parallaction(OSystem *syst, const PARALLACTIONGameDescription *gam  Parallaction::~Parallaction() {  	delete _debugger; -  	delete _globalTable; -  	delete _callableNames; -	delete _localFlagNames; +	delete _cmdExec; +	delete _programExec; +	_gfx->clearGfxObjects(kGfxObjCharacter | kGfxObjNormal); +	hideDialogueStuff(); +	delete _balloonMan;  	freeLocation();  	freeCharacter();  	destroyInventory(); +	cleanupGui(); + +	delete _comboArrow; + +	delete _localFlagNames;  	delete _gfx;  	delete _soundMan;  	delete _disk; +	delete _input;  } @@ -132,18 +140,24 @@ int Parallaction::init() {  	_debugger = new Debugger(this); -	return 0; -} - +	_menuHelper = 0; +	setupBalloonManager(); +	return 0; +} +void Parallaction::clearSet(OpcodeSet &opcodes) { +	for (Common::Array<const Opcode*>::iterator i = opcodes.begin(); i != opcodes.end(); ++i) +		delete *i; +	opcodes.clear(); +}  void Parallaction::updateView() { -	if ((_engineFlags & kEnginePauseJobs) && (_engineFlags & kEngineInventory) == 0) { +	if ((_engineFlags & kEnginePauseJobs) && (_input->_inputMode != Input::kInputModeInventory)) {  		return;  	} @@ -153,6 +167,11 @@ void Parallaction::updateView() {  } +void Parallaction::hideDialogueStuff() { +	_gfx->freeItems(); +	_balloonMan->freeBalloons(); +} +  void Parallaction::freeCharacter() {  	debugC(1, kDebugExec, "freeCharacter()"); @@ -160,6 +179,8 @@ void Parallaction::freeCharacter() {  	delete _objectsNames;  	_objectsNames = 0; +	_gfx->clearGfxObjects(kGfxObjCharacter); +  	_char.free();  	return; @@ -189,6 +210,9 @@ AnimationPtr Parallaction::findAnimation(const char *name) {  }  void Parallaction::freeAnimations() { +	for (AnimationList::iterator it = _location._animations.begin(); it != _location._animations.end(); it++) { +		(*it)->_commands.clear();	// See comment for freeZones(), about circular references. +	}  	_location._animations.clear();  	return;  } @@ -237,10 +261,9 @@ void Parallaction::freeLocation() {  	_localFlagNames->clear(); -	_location._walkNodes.clear(); +	_location._walkPoints.clear(); -	_gfx->clearGfxObjects(); -	freeBackground(); +	_gfx->clearGfxObjects(kGfxObjNormal);  	_location._programs.clear();  	freeZones(); @@ -260,76 +283,32 @@ void Parallaction::freeLocation() {  void Parallaction::freeBackground() { -	_gfx->freeBackground();  	_pathBuffer = 0;  }  void Parallaction::setBackground(const char* name, const char* mask, const char* path) { -	_gfx->setBackground(kBackgroundLocation, name, mask, path); -	_pathBuffer = &_gfx->_backgroundInfo.path; +	BackgroundInfo *info = new BackgroundInfo; +	_disk->loadScenery(*info, name, mask, path); + +	_gfx->setBackground(kBackgroundLocation, info); +	_pathBuffer = &info->path;  	return;  }  void Parallaction::showLocationComment(const char *text, bool end) { -	_gfx->setLocationBalloon(const_cast<char*>(text), end); +	_balloonMan->setLocationBalloon(const_cast<char*>(text), end);  }  void Parallaction::processInput(InputData *data) { +	if (!data) { +		return; +	}  	switch (data->_event) { -	case kEvEnterZone: -		debugC(2, kDebugInput, "processInput: kEvEnterZone"); -		_gfx->setFloatingLabel(data->_label); -		break; - -	case kEvExitZone: -		debugC(2, kDebugInput, "processInput: kEvExitZone"); -		_gfx->setFloatingLabel(0); -		break; - -	case kEvAction: -		debugC(2, kDebugInput, "processInput: kEvAction"); -		_input->stopHovering(); -		pauseJobs(); -		runZone(data->_zone); -		resumeJobs(); -		break; - -	case kEvOpenInventory: -		_input->stopHovering(); -		_gfx->setFloatingLabel(0); -		if (hitZone(kZoneYou, data->_mousePos.x, data->_mousePos.y) == 0) { -			setArrowCursor(); -		} -		pauseJobs(); -		openInventory(); -		break; - -	case kEvCloseInventory: // closes inventory and possibly select item -		closeInventory(); -		setInventoryCursor(data->_inventoryIndex); -		resumeJobs(); -		break; - -	case kEvHoverInventory: -		highlightInventoryItem(data->_inventoryIndex);						// enable -		break; - -	case kEvWalk: -		debugC(2, kDebugInput, "processInput: kEvWalk"); -		_input->stopHovering(); -		setArrowCursor(); -		_char.scheduleWalk(data->_mousePos.x, data->_mousePos.y); -		break; - -	case kEvQuitGame: -		_engineFlags |= kEngineQuit; -		break; -  	case kEvSaveGame:  		_input->stopHovering();  		saveGame(); @@ -350,28 +329,39 @@ void Parallaction::processInput(InputData *data) {  void Parallaction::runGame() {  	InputData *data = _input->updateInput(); -	if (data->_event != kEvNone) { +	if (_engineFlags & kEngineQuit) +		return; + +	runGuiFrame(); +	runDialogueFrame(); +	runCommentFrame(); + +	if (_input->_inputMode == Input::kInputModeGame) {  		processInput(data); -	} +		runPendingZones(); -	runPendingZones(); +		if (_engineFlags & kEngineQuit) +			return; -	if (_engineFlags & kEngineChangeLocation) { -		changeLocation(_location._name); +		if (_engineFlags & kEngineChangeLocation) { +			changeLocation(_location._name); +		}  	} -  	_gfx->beginFrame();  	if (_input->_inputMode == Input::kInputModeGame) { -		runScripts(); -		walk(); +		_programExec->runScripts(_location._programs.begin(), _location._programs.end()); +		_char._ani->_z = _char._ani->height() + _char._ani->_top; +		if (_char._ani->gfxobj) { +			_char._ani->gfxobj->z = _char._ani->_z; +		} +		_char._walker->walk();  		drawAnimations();  	}  	// change this to endFrame?  	updateView(); -  } @@ -400,14 +390,13 @@ void Parallaction::doLocationEnterTransition() {  	pal.makeGrayscale();  	_gfx->setPalette(pal); -	runScripts(); +	_programExec->runScripts(_location._programs.begin(), _location._programs.end());  	drawAnimations(); - +	showLocationComment(_location._comment, false);  	_gfx->updateScreen(); -	showLocationComment(_location._comment, false); -	_input->waitUntilLeftClick(); -	_gfx->freeBalloons(); +	_input->waitForButtonEvent(kMouseLeftUp); +	_balloonMan->freeBalloons();  	// fades maximum intensity palette towards approximation of main palette  	for (uint16 _si = 0; _si<6; _si++) { @@ -467,6 +456,9 @@ void Parallaction::freeZones() {  			debugC(2, kDebugExec, "freeZones preserving zone '%s'", z->_name);  			it++;  		} else { +			(*it)->_commands.clear();	// Since commands may reference zones, and both commands and zones are kept stored into +										// SharedPtr's, we need to kill commands explicitly to destroy any potential circular +										// reference.  			it = _location._zones.erase(it);  		}  	} @@ -475,16 +467,47 @@ void Parallaction::freeZones() {  } +enum { +	WALK_LEFT = 0, +	WALK_RIGHT = 1, +	WALK_DOWN = 2, +	WALK_UP = 3 +}; + +struct WalkFrames { +	int16 stillFrame[4]; +	int16 firstWalkFrame[4]; +	int16 numWalkFrames[4]; +	int16 frameRepeat[4]; +}; + +WalkFrames _char20WalkFrames = { +	{  0,  7, 14, 17 }, +	{  1,  8, 15, 18 }, +	{  6,  6,  2,  2 }, +	{  2,  2,  4,  4 } +}; + +WalkFrames _char24WalkFrames = { +	{  0,  9, 18, 21 }, +	{  1, 10, 19, 22 }, +	{  8,  8,  2,  2 }, +	{  2,  2,  4,  4 } +}; +  const char Character::_prefixMini[] = "mini";  const char Character::_suffixTras[] = "tras";  const char Character::_empty[] = "\0"; -Character::Character(Parallaction *vm) : _vm(vm), _ani(new Animation), _builder(_ani) { +Character::Character(Parallaction *vm) : _vm(vm), _ani(new Animation) {  	_talk = NULL;  	_head = NULL;  	_objs = NULL; +	_direction = WALK_DOWN; +	_step = 0; +  	_dummy = false;  	_ani->_left = 150; @@ -496,24 +519,61 @@ Character::Character(Parallaction *vm) : _vm(vm), _ani(new Animation), _builder(  	_ani->_flags = kFlagsActive | kFlagsNoName;  	_ani->_type = kZoneYou;  	strncpy(_ani->_name, "yourself", ZONENAME_LENGTH); + +	// TODO: move creation into Parallaction. Needs to make Character a pointer first. +	if (_vm->getGameType() == GType_Nippon) { +		_builder = new PathBuilder_NS(this); +		_walker = new PathWalker_NS(this); +	} else { +		_builder = new PathBuilder_BR(this); +		_walker = new PathWalker_BR(this); +	} +} + +Character::~Character() { +	delete _builder; +	_builder = 0; + +	delete _walker; +	_walker = 0; + +	free();  }  void Character::getFoot(Common::Point &foot) { -	foot.x = _ani->_left + _ani->width() / 2; -	foot.y = _ani->_top + _ani->height(); +	Common::Rect rect; +	_ani->gfxobj->getRect(_ani->_frame, rect); + +	foot.x = _ani->_left + (rect.left + rect.width() / 2); +	foot.y = _ani->_top + (rect.top + rect.height());  }  void Character::setFoot(const Common::Point &foot) { -	_ani->_left = foot.x - _ani->width() / 2; -	_ani->_top = foot.y - _ani->height(); +	Common::Rect rect; +	_ani->gfxobj->getRect(_ani->_frame, rect); + +	_ani->_left = foot.x - (rect.left + rect.width() / 2); +	_ani->_top = foot.y - (rect.top + rect.height()); +} + +#if 0 +void dumpPath(const PointList &list, const char* text) { +	for (PointList::iterator it = list.begin(); it != list.end(); it++) +		printf("node (%i, %i)\n", it->x, it->y); + +	return;  } +#endif  void Character::scheduleWalk(int16 x, int16 y) {  	if ((_ani->_flags & kFlagsRemove) || (_ani->_flags & kFlagsActive) == 0) {  		return;  	} -	_walkPath = _builder.buildPath(x, y); +	_builder->buildPath(x, y); +#if 0 +	dumpPath(_walkPath, _name); +#endif  	_engineFlags |= kEngineWalking;  } @@ -522,11 +582,12 @@ void Character::free() {  	delete _talk;  	delete _head;  	delete _objs; +	delete _ani->gfxobj; -	_ani->gfxobj = NULL;  	_talk = NULL;  	_head = NULL;  	_objs = NULL; +	_ani->gfxobj = NULL;  	return;  } @@ -548,10 +609,14 @@ void Character::setName(const char *name) {  	const char *end = begin + strlen(name);  	_prefix = _empty; +	_suffix = _empty;  	_dummy = IS_DUMMY_CHARACTER(name);  	if (!_dummy) { +		if (!strstr(name, "donna")) { +			_engineFlags &= ~kEngineTransformedDonna; +		} else  		if (_engineFlags & kEngineTransformedDonna) {  			_suffix = _suffixTras;  		} else { @@ -560,8 +625,6 @@ void Character::setName(const char *name) {  				_engineFlags |= kEngineTransformedDonna;  				_suffix = _suffixTras;  				end = s; -			} else { -				_suffix = _empty;  			}  		}  		if (IS_MINI_CHARACTER(name)) { @@ -597,9 +660,35 @@ void Parallaction::beep() {  }  void Parallaction::scheduleLocationSwitch(const char *location) { +	debugC(9, kDebugExec, "scheduleLocationSwitch(%s)\n", location);  	strcpy(_location._name, location);  	_engineFlags |= kEngineChangeLocation;  } + + + +void Character::updateDirection(const Common::Point& pos, const Common::Point& to) { + +	Common::Point dist(to.x - pos.x, to.y - pos.y); +	WalkFrames *frames = (_ani->getFrameNum() == 20) ? &_char20WalkFrames : &_char24WalkFrames; + +	_step++; + +	if (dist.x == 0 && dist.y == 0) { +		_ani->_frame = frames->stillFrame[_direction]; +		return; +	} + +	if (dist.x < 0) +		dist.x = -dist.x; +	if (dist.y < 0) +		dist.y = -dist.y; + +	_direction = (dist.x > dist.y) ? ((to.x > pos.x) ? WALK_LEFT : WALK_RIGHT) : ((to.y > pos.y) ? WALK_DOWN : WALK_UP); +	_ani->_frame = frames->firstWalkFrame[_direction] + (_step / frames->frameRepeat[_direction]) % frames->numWalkFrames[_direction]; +} + +  } // namespace Parallaction diff --git a/engines/parallaction/parallaction.h b/engines/parallaction/parallaction.h index 6e5957d3cd..e5c5221414 100644 --- a/engines/parallaction/parallaction.h +++ b/engines/parallaction/parallaction.h @@ -33,6 +33,7 @@  #include "engines/engine.h" +#include "parallaction/exec.h"  #include "parallaction/input.h"  #include "parallaction/inventory.h"  #include "parallaction/parser.h" @@ -100,10 +101,8 @@ enum {  enum EngineFlags {  	kEngineQuit			= (1 << 0),  	kEnginePauseJobs	= (1 << 1), -	kEngineInventory	= (1 << 2),  	kEngineWalking		= (1 << 3),  	kEngineChangeLocation	= (1 << 4), -	kEngineBlockInput	= (1 << 5),  	kEngineDragging		= (1 << 6),  	kEngineTransformedDonna	= (1 << 7), @@ -113,14 +112,6 @@ enum EngineFlags {  enum {  	kEvNone			= 0, -	kEvEnterZone		= 1, -	kEvExitZone		= 2, -	kEvAction		= 3, -	kEvOpenInventory	= 4, -	kEvCloseInventory	= 5, -	kEvHoverInventory	= 6, -	kEvWalk			= 7, -	kEvQuitGame		= 1000,  	kEvSaveGame		= 2000,  	kEvLoadGame		= 4000  }; @@ -164,6 +155,8 @@ class Debugger;  class Gfx;  class SoundMan;  class Input; +class DialogueManager; +class MenuInputHelper;  struct Location { @@ -184,7 +177,7 @@ struct Location {  	char		_soundFile[50];  	// NS specific -	WalkNodeList	_walkNodes; +	PointList	_walkPoints;  	char _slideText[2][MAX_TOKEN_LEN];  	// BRA specific @@ -202,13 +195,16 @@ struct Character {  	AnimationPtr	_ani; -	Frames			*_head; -	Frames			*_talk; -	Frames			*_objs; -	PathBuilder		_builder; -	WalkNodeList	*_walkPath; +	GfxObj			*_head; +	GfxObj			*_talk; +	GfxObj			*_objs; +	PathBuilder		*_builder; +	PathWalker		*_walker; +	PointList		_walkPath;  	Character(Parallaction *vm); +	~Character(); +  	void getFoot(Common::Point &foot);  	void setFoot(const Common::Point &foot);  	void scheduleWalk(int16 x, int16 y); @@ -228,18 +224,19 @@ protected:  	static const char _suffixTras[];  	static const char _empty[]; +	int16		_direction, _step; +  public:  	void setName(const char *name);  	const char *getName() const;  	const char *getBaseName() const;  	const char *getFullName() const;  	bool dummy() const; -}; +	void updateDirection(const Common::Point& pos, const Common::Point& to); +}; -#define DECLARE_UNQUALIFIED_COMMAND_OPCODE(op) void cmdOp_##op() -#define DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(op) void instOp_##op()  #define NUM_LOCATIONS 120 @@ -259,41 +256,16 @@ public:  	Input	*_input; -	OpcodeSet	_commandOpcodes; - -	struct ParallactionStruct1 { -		CommandPtr cmd; -		ZonePtr	z; -	} _cmdRunCtxt; - -	OpcodeSet	_instructionOpcodes; - -	struct ParallactionStruct2 { -		AnimationPtr	anim; -		ProgramPtr		program; -		InstructionList::iterator inst; -		uint16		modCounter; -		bool		suspend; -	} _instRunCtxt; -  	void		processInput(InputData* data);  	void		pauseJobs();  	void		resumeJobs(); -	void		finalizeWalk(WalkNodeList *list); -	int16		selectWalkFrame(const Common::Point& pos, const WalkNodePtr from); -	void		clipMove(Common::Point& pos, const WalkNodePtr from); -  	ZonePtr		findZone(const char *name);  	ZonePtr		hitZone(uint32 type, uint16 x, uint16 y);  	uint16		runZone(ZonePtr z);  	void		freeZones(); -	void		runDialogue(SpeakData*); - -	void		runCommands(CommandList& list, ZonePtr z = nullZonePtr); -  	AnimationPtr findAnimation(const char *name);  	void		freeAnimations(); @@ -327,6 +299,8 @@ public:  	Gfx*			_gfx;  	Disk*			_disk; +	CommandExec*	_cmdExec; +	ProgramExec*	_programExec;  	Character		_char;  	void			setLocationFlags(uint32 flags); @@ -351,6 +325,7 @@ public:  	Common::RandomSource _rnd;  	Debugger	*_debugger; +	Frames	*_comboArrow;  protected:		// data @@ -367,10 +342,8 @@ protected:		// members  	void		runGame();  	void		updateView(); -	void		scheduleLocationSwitch(const char *location);  	void		doLocationEnterTransition();  	virtual void changeLocation(char *location) = 0; -	virtual void changeCharacter(const char *name) = 0;  	virtual void runPendingZones() = 0;  	void		allocateLocationSlot(const char *name);  	void		finalizeLocationParsing(); @@ -379,28 +352,33 @@ protected:		// members  	void		displayComment(ExamineData *data); -	uint16		checkDoor(); -  	void		freeCharacter();  	int16		pickupItem(ZonePtr z); +	void 		clearSet(OpcodeSet &opcodes); + +  public: +	void		scheduleLocationSwitch(const char *location); +	virtual void changeCharacter(const char *name) = 0; +  	virtual	void callFunction(uint index, void* parm) { }  	virtual void setArrowCursor() = 0; -	virtual void setInventoryCursor(int pos) = 0; +	virtual void setInventoryCursor(ItemName name) = 0;  	virtual void parseLocation(const char* name) = 0;  	void updateDoor(ZonePtr z); -	virtual void runScripts() = 0; -	virtual void walk() = 0;  	virtual void drawAnimations() = 0;  	void		beep(); +	ZonePtr		_zoneTrap; +	PathBuilder* getPathBuilder(Character *ch); +  public:  //	const char **_zoneFlagNamesRes;  //	const char **_zoneTypeNamesRes; @@ -425,6 +403,27 @@ public:  	Inventory *_inventory;  	InventoryRenderer *_inventoryRenderer; +	BalloonManager *_balloonMan; + +	void setupBalloonManager(); + +	void hideDialogueStuff(); +	DialogueManager	*_dialogueMan; +	void enterDialogueMode(ZonePtr z); +	void exitDialogueMode(); +	void runDialogueFrame(); + +	MenuInputHelper *_menuHelper; +	void runGuiFrame(); +	void cleanupGui(); + +	ZonePtr	_commentZone; +	void enterCommentMode(ZonePtr z); +	void exitCommentMode(); +	void runCommentFrame(); + +	void setInternLanguage(uint id); +	uint getInternLanguage();  }; @@ -483,12 +482,18 @@ public:  	typedef void (Parallaction_ns::*Callable)(void*);  	virtual	void callFunction(uint index, void* parm); -	void setMousePointer(uint32 value);  	bool loadGame();  	bool saveGame();  	void		switchBackground(const char* background, const char* mask); +	void		showSlide(const char *name, int x = 0, int y = 0); +	void 		setArrowCursor(); + +	// TODO: this should be private!!!!!!! +	bool	_inTestResult; +	void cleanupGame(); +	bool allPartsComplete();  private:  	LocationParser_ns		*_locationParser; @@ -500,17 +505,14 @@ private:  	Common::String genSaveFileName(uint slot, bool oldStyle = false);  	Common::InSaveFile *getInSaveFile(uint slot);  	Common::OutSaveFile *getOutSaveFile(uint slot); -	bool allPartsComplete();  	void setPartComplete(const Character& character);  private:  	void changeLocation(char *location);  	void changeCharacter(const char *name);  	void runPendingZones(); -	void cleanupGame(); -	void setArrowCursor(); -	void setInventoryCursor(int pos); +	void setInventoryCursor(ItemName name);  	void doLoadGame(uint16 slot); @@ -520,11 +522,9 @@ private:  	void initResources();  	void initCursors(); -	void initParsers();  	static byte _resMouseArrow[256];  	byte	*_mouseArrow; -	Frames	*_mouseComposedArrow;  	static const Callable _dosCallables[25];  	static const Callable _amigaCallables[25]; @@ -580,60 +580,16 @@ private:  	const Callable *_callables;  protected: -	void runScripts(); -	void walk();  	void drawAnimations();  	void		parseLocation(const char *filename);  	void		loadProgram(AnimationPtr a, const char *filename); -	void		initOpcodes(); - -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(invalid); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(set); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(clear); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(start); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(speak); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(get); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(location); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(open); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(close); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(on); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(off); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(call); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(toggle); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(drop); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(quit); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(move); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(stop); - -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(invalid); -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(on); -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(off); -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(loop); -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endloop); -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(null); -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(call); -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(inc); -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(set); -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(put); -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(wait); -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(start); -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(sound); -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(move); -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endscript); -  	void		selectStartLocation(); -	void		guiStart(); -	int			guiSelectCharacter(); -	void		guiSplash(); -	int			guiNewGame(); -	uint16		guiChooseLanguage(); -	uint16		guiSelectGame(); -	int			guiGetSelectedBlock(const Common::Point &p); - -	void		showSlide(const char *name); +	void		startGui(); +	void		startCreditSequence(); +	void		startEndPartSequence();  }; @@ -655,6 +611,9 @@ public:  	typedef void (Parallaction_br::*Callable)(void*);  	virtual	void callFunction(uint index, void* parm);  	void		changeCharacter(const char *name); +	void setupSubtitles(char *s, char *s2, int y); +	void clearSubtitles(); +  public:  	Table		*_countersNames; @@ -674,7 +633,8 @@ public:  	int32		_counters[32];  	uint32		_zoneFlags[NUM_LOCATIONS][NUM_ZONES]; - +	void		startPart(uint part); +	void 		setArrowCursor();  private:  	LocationParser_br		*_locationParser;  	ProgramParser_br		*_programParser; @@ -682,20 +642,15 @@ private:  	void		initResources();  	void		initFonts();  	void		freeFonts(); -	void		initOpcodes(); -	void		initParsers(); -	void setArrowCursor(); -	void setInventoryCursor(int pos); +	void setInventoryCursor(ItemName name);  	void		changeLocation(char *location);  	void 		runPendingZones();  	void		initPart();  	void		freePart(); -	void		startPart(); -	void setMousePointer(int16 index);  	void initCursors();  	Frames	*_dinoCursor; @@ -706,10 +661,7 @@ private:  	static const char *_partNames[]; -	void guiStart(); -	int guiShowMenu(); -	void guiSplash(const char *name); -	Frames* guiRenderMenuItem(const char *text); +	void startGui();  	static const Callable _dosCallables[6]; @@ -725,68 +677,6 @@ private:  	void parseLocation(const char* name);  	void loadProgram(AnimationPtr a, const char *filename); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(location); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(open); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(close); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(on); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(off); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(call); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(drop); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(move); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(start); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(stop); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(character); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(followme); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(onmouse); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(offmouse); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(add); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(leave); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(inc); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(dec); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(ifeq); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(iflt); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(ifgt); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(let); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(music); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(fix); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(unfix); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(zeta); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(scroll); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(swap); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(give); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(text); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(part); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(testsfx); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(ret); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(onsave); -	DECLARE_UNQUALIFIED_COMMAND_OPCODE(offsave); - -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(on); -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(off); -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(loop); -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(inc); -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(dec); -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(set); -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(put); -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(wait); -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(start); -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(process); -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(move); -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(color); -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(mask); -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(print); -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(text); -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(mul); -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(div); -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(ifeq); -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(iflt); -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(ifgt); -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endif); -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(stop); -	DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endscript); - -	void setupSubtitles(char *s, char *s2, int y); -	void clearSubtitles();  #if 0  	void jobWaitRemoveLabelJob(void *parm, Job *job);  	void jobPauseSfx(void *parm, Job *job); diff --git a/engines/parallaction/parallaction_br.cpp b/engines/parallaction/parallaction_br.cpp index 0f5cc2a0c4..761c8d1b74 100644 --- a/engines/parallaction/parallaction_br.cpp +++ b/engines/parallaction/parallaction_br.cpp @@ -32,6 +32,27 @@  namespace Parallaction { +struct MouseComboProperties { +	int	_xOffset; +	int	_yOffset; +	int	_width; +	int	_height; +}; +/* +// TODO: improve NS's handling of normal cursor before merging cursor code. +MouseComboProperties	_mouseComboProps_NS = { +	7,	// combo x offset (the icon from the inventory will be rendered from here) +	7,	// combo y offset (ditto) +	32,	// combo (arrow + icon) width +	32	// combo (arrow + icon) height +}; +*/ +MouseComboProperties	_mouseComboProps_BR = { +	8,	// combo x offset (the icon from the inventory will be rendered from here) +	8,	// combo y offset (ditto) +	68,	// combo (arrow + icon) width +	68	// combo (arrow + icon) height +};  const char *Parallaction_br::_partNames[] = {  	"PART0", @@ -56,7 +77,11 @@ int Parallaction_br::init() {  	if (getGameType() == GType_BRA) {  		if (getPlatform() == Common::kPlatformPC) { -			_disk = new DosDisk_br(this); +			if (getFeatures() & GF_DEMO) { +				_disk = new DosDemo_br(this); +			} else { +				_disk = new DosDisk_br(this); +			}  			_disk->setLanguage(2);					// NOTE: language is now hardcoded to English. Original used command-line parameters.  			_soundMan = new DummySoundMan(this);  		} else { @@ -72,14 +97,21 @@ int Parallaction_br::init() {  	initResources();  	initFonts();  	initCursors(); -	initOpcodes();  	_locationParser = new LocationParser_br(this);  	_locationParser->init();  	_programParser = new ProgramParser_br(this);  	_programParser->init(); +	_cmdExec = new CommandExec_br(this); +	_cmdExec->init(); +	_programExec = new ProgramExec_br(this); +	_programExec->init(); +  	_part = -1; +	_subtitle[0] = -1; +	_subtitle[1] = -1; +  	Parallaction::init();  	return 0; @@ -92,6 +124,7 @@ Parallaction_br::~Parallaction_br() {  	delete _dougCursor;  	delete _donnaCursor; +	delete _mouseArrow;  }  void Parallaction_br::callFunction(uint index, void* parm) { @@ -102,13 +135,14 @@ void Parallaction_br::callFunction(uint index, void* parm) {  int Parallaction_br::go() { -	guiSplash("dyna"); -	guiSplash("core"); +	if (getFeatures() & GF_DEMO) { +		startPart(1); +	} else { +		startGui(); +	}  	while ((_engineFlags & kEngineQuit) == 0) { -		guiStart(); -  //		initCharacter();  		_input->_inputMode = Input::kInputModeGame; @@ -142,6 +176,12 @@ void Parallaction_br::initCursors() {  		_dougCursor = _disk->loadPointer("pointer2");  		_donnaCursor = _disk->loadPointer("pointer3"); +		Graphics::Surface *surf = new Graphics::Surface; +		surf->create(_mouseComboProps_BR._width, _mouseComboProps_BR._height, 1); +		_comboArrow = new SurfaceToFrames(surf); + +		// TODO: choose the pointer depending on the active character +		// For now, we pick Donna's  		_mouseArrow = _donnaCursor;  	} else {  		// TODO: Where are the Amiga cursors? @@ -149,19 +189,6 @@ void Parallaction_br::initCursors() {  } -void Parallaction_br::setMousePointer(int16 index) { -	// FIXME: Where are the Amiga cursors? -	if (getPlatform() == Common::kPlatformAmiga) -		return; - -	Common::Rect r; -	_mouseArrow->getRect(0, r); - -	_system->setMouseCursor(_mouseArrow->getData(0), r.width(), r.height(), 0, 0, 0); -	_system->showMouse(true); - -} -  void Parallaction_br::initPart() {  	memset(_counters, 0, ARRAYSIZE(_counters)); @@ -170,7 +197,12 @@ void Parallaction_br::initPart() {  	_objectsNames = _disk->loadTable("objects");  	_countersNames = _disk->loadTable("counters"); -//	_disk->loadObjects("icone.ico"); +	// TODO: maybe handle this into Disk +	if (getPlatform() == Common::kPlatformPC) { +		_char._objs = _disk->loadObjects("icone.ico"); +	} else { +		_char._objs = _disk->loadObjects("icons.ico"); +	}  } @@ -185,11 +217,17 @@ void Parallaction_br::freePart() {  	_countersNames = 0;  } -void Parallaction_br::startPart() { +void Parallaction_br::startPart(uint part) { +	_part = part; +	_disk->selectArchive(_partNames[_part]);  	initPart(); -	strcpy(_location._name, partFirstLocation[_part]); +	if (getFeatures() & GF_DEMO) { +		strcpy(_location._name, "camalb"); +	} else { +		strcpy(_location._name, partFirstLocation[_part]); +	}  	parseLocation("common");  	changeLocation(_location._name); @@ -199,16 +237,26 @@ void Parallaction_br::startPart() {  void Parallaction_br::runPendingZones() {  	ZonePtr z; +	_cmdExec->runSuspended(); +  	if (_activeZone) {  		z = _activeZone;	// speak Zone or sound  		_activeZone = nullZonePtr; -		runZone(z);			// FIXME: BRA doesn't handle sound yet +		if ((z->_type & 0xFFFF) == kZoneSpeak) { +			enterDialogueMode(z); +		} else { +			runZone(z);			// FIXME: BRA doesn't handle sound yet +		}  	}  	if (_activeZone2) {  		z = _activeZone2;	// speak Zone or sound  		_activeZone2 = nullZonePtr; -		runZone(z); +		if ((z->_type & 0xFFFF) == kZoneSpeak) { +			enterDialogueMode(z); +		} else { +			runZone(z);			// FIXME: BRA doesn't handle sound yet +		}  	}  } @@ -218,21 +266,35 @@ void Parallaction_br::changeLocation(char *location) {  	// free open location stuff  	clearSubtitles();  	freeBackground(); -	_gfx->clearGfxObjects(); +	_gfx->clearGfxObjects(kGfxObjNormal); +	_gfx->freeLabels(); +	_subtitle[0] = _subtitle[1] = -1; +  	_location._programs.clear(); + +	_location._animations.remove(_char._ani); +  	freeZones();  	freeAnimations(); + +	_location._animations.push_front(_char._ani); +  //	free(_location._comment);  //	_location._comment = 0; -//	_location._commands.clear(); -//	_location._aCommands.clear(); - +	_location._commands.clear(); +	_location._aCommands.clear();  	// load new location  	parseLocation(location); -	runCommands(_location._commands); + +	// kFlagsRemove is cleared because the character defaults to visible on new locations +	// script command can hide the character, anyway, so that's why the flag is cleared +	// before _location._commands are executed +	_char._ani->_flags &= ~kFlagsRemove; + +	_cmdExec->run(_location._commands);  //	doLocationEnterTransition(); -	runCommands(_location._aCommands); +	_cmdExec->run(_location._aCommands);  	_engineFlags &= ~kEngineChangeLocation;  } @@ -282,25 +344,45 @@ void Parallaction_br::loadProgram(AnimationPtr a, const char *filename) {  void Parallaction_br::changeCharacter(const char *name) {  	const char *charName = _char.getName(); -	if (!scumm_stricmp(charName, name)) { -		return; + +	if (scumm_stricmp(charName, name)) { +		debugC(1, kDebugExec, "changeCharacter(%s)", name); + +		_char.setName(name); +		_char._ani->gfxobj = _gfx->loadAnim(name); +		_char._ani->gfxobj->setFlags(kGfxObjCharacter); +		_char._ani->gfxobj->clearFlags(kGfxObjNormal); +		_char._talk = _disk->loadTalk(name);  	} -	_char.setName(name); -	_char._talk = _disk->loadTalk(name); +	_char._ani->_flags |= kFlagsActive;  }  void Parallaction_br::setArrowCursor() { +	// FIXME: Where are the Amiga cursors? +	if (getPlatform() == Common::kPlatformAmiga) +		return; +	Common::Rect r; +	_mouseArrow->getRect(0, r); +	_system->setMouseCursor(_mouseArrow->getData(0), r.width(), r.height(), 0, 0, 0); +	_system->showMouse(true); +	_input->_activeItem._id = 0;  } -void Parallaction_br::setInventoryCursor(int pos) { - +void Parallaction_br::setInventoryCursor(ItemName name) { +	assert(name > 0); +	byte *src = _mouseArrow->getData(0); +	byte *dst = _comboArrow->getData(0); +	memcpy(dst, src, _comboArrow->getSize(0)); +	// FIXME: destination offseting is not clear +	_inventoryRenderer->drawItem(name, dst + _mouseComboProps_BR._yOffset * _mouseComboProps_BR._width + _mouseComboProps_BR._xOffset, _mouseComboProps_BR._width); +	_system->setMouseCursor(dst, _mouseComboProps_BR._width, _mouseComboProps_BR._height, 0, 0, 0);  }  } // namespace Parallaction diff --git a/engines/parallaction/parallaction_ns.cpp b/engines/parallaction/parallaction_ns.cpp index 2cca3a6a4a..e81492e655 100644 --- a/engines/parallaction/parallaction_ns.cpp +++ b/engines/parallaction/parallaction_ns.cpp @@ -34,6 +34,7 @@  namespace Parallaction { +  #define MOUSEARROW_WIDTH		16  #define MOUSEARROW_HEIGHT		16 @@ -135,18 +136,24 @@ int Parallaction_ns::init() {  	initResources();  	initFonts();  	initCursors(); -	initOpcodes();  	_locationParser = new LocationParser_ns(this);  	_locationParser->init();  	_programParser = new ProgramParser_ns(this);  	_programParser->init(); +	_cmdExec = new CommandExec_ns(this); +	_cmdExec->init(); +	_programExec = new ProgramExec_ns(this); +	_programExec->init(); +  	_introSarcData1 = 0;  	_introSarcData2 = 1;  	_introSarcData3 = 200;  	num_foglie = 0; +	_inTestResult = false; +  	_location._animations.push_front(_char._ani);  	Parallaction::init(); @@ -157,7 +164,8 @@ int Parallaction_ns::init() {  Parallaction_ns::~Parallaction_ns() {  	freeFonts(); -	delete _mouseComposedArrow; +	delete _locationParser; +	delete _programParser;  	_location._animations.remove(_char._ani); @@ -174,7 +182,7 @@ void Parallaction_ns::freeFonts() {  }  void Parallaction_ns::initCursors() { -	_mouseComposedArrow = _disk->loadPointer("pointer"); +	_comboArrow = _disk->loadPointer("pointer");  	_mouseArrow = _resMouseArrow;  } @@ -183,40 +191,20 @@ void Parallaction_ns::setArrowCursor() {  	debugC(1, kDebugInput, "setting mouse cursor to arrow");  	// this stuff is needed to avoid artifacts with labels and selected items when switching cursors -	_gfx->setFloatingLabel(0); +	_input->stopHovering();  	_input->_activeItem._id = 0;  	_system->setMouseCursor(_mouseArrow, MOUSEARROW_WIDTH, MOUSEARROW_HEIGHT, 0, 0, 0); -	_system->showMouse(true); -  } -void Parallaction_ns::setInventoryCursor(int pos) { - -	if (pos == -1) -		return; +void Parallaction_ns::setInventoryCursor(ItemName name) { +	assert(name > 0); -	const InventoryItem *item = getInventoryItem(pos); -	if (item->_index == 0) -		return; - -	_input->_activeItem._id = item->_id; - -	byte *v8 = _mouseComposedArrow->getData(0); +	byte *v8 = _comboArrow->getData(0);  	// FIXME: destination offseting is not clear -	byte* s = _char._objs->getData(item->_index); -	byte* d = v8 + 7 + MOUSECOMBO_WIDTH * 7; - -	for (uint i = 0; i < INVENTORYITEM_HEIGHT; i++) { -		memcpy(d, s, INVENTORYITEM_WIDTH); - -		s += INVENTORYITEM_PITCH; -		d += MOUSECOMBO_WIDTH; -	} - +	_inventoryRenderer->drawItem(name, v8 + 7 * MOUSECOMBO_WIDTH + 7, MOUSECOMBO_WIDTH);  	_system->setMouseCursor(v8, MOUSECOMBO_WIDTH, MOUSECOMBO_HEIGHT, 0, 0, 0); -  } @@ -232,11 +220,8 @@ int Parallaction_ns::go() {  	_globalTable = _disk->loadTable("global"); -	guiStart(); +	startGui(); -	changeLocation(_location._name); - -	_input->_inputMode = Input::kInputModeGame;  	while ((_engineFlags & kEngineQuit) == 0) {  		runGame();  	} @@ -268,8 +253,14 @@ void Parallaction_ns::switchBackground(const char* background, const char* mask)  } -void Parallaction_ns::showSlide(const char *name) { -	_gfx->setBackground(kBackgroundSlide, name, 0, 0); +void Parallaction_ns::showSlide(const char *name, int x, int y) { +	BackgroundInfo *info = new BackgroundInfo; +	_disk->loadSlide(*info, name); + +	info->x = (x == CENTER_LABEL_HORIZONTAL) ? ((_vm->_screenWidth - info->width) >> 1) : x; +	info->y = (y == CENTER_LABEL_VERTICAL) ? ((_vm->_screenHeight - info->height) >> 1) : y; + +	_gfx->setBackground(kBackgroundSlide, info);  }  void Parallaction_ns::runPendingZones() { @@ -286,16 +277,19 @@ void Parallaction_ns::runPendingZones() {  void Parallaction_ns::changeLocation(char *location) {  	debugC(1, kDebugExec, "changeLocation(%s)", location); +	MouseTriState oldMouseState = _input->getMouseState(); +	_input->setMouseState(MOUSE_DISABLED); +  	_soundMan->playLocationMusic(location); -	_gfx->setFloatingLabel(0); +	_input->stopHovering();  	_gfx->freeLabels(); -	_input->stopHovering(); -	if (_engineFlags & kEngineBlockInput) { -		setArrowCursor(); -	} +	_zoneTrap = nullZonePtr; +	setArrowCursor(); + +	_gfx->showGfxObj(_char._ani->gfxobj, false);  	_location._animations.remove(_char._ani);  	freeLocation(); @@ -307,7 +301,9 @@ void Parallaction_ns::changeLocation(char *location) {  		showSlide(locname.slide());  		uint id = _gfx->createLabel(_menuFont, _location._slideText[0], 1);  		_gfx->showLabel(id, CENTER_LABEL_HORIZONTAL, 14); -		_input->waitUntilLeftClick(); +		_gfx->updateScreen(); + +		_input->waitForButtonEvent(kMouseLeftUp);  		_gfx->freeLabels();  		freeBackground();  	} @@ -317,6 +313,7 @@ void Parallaction_ns::changeLocation(char *location) {  	}  	_location._animations.push_front(_char._ani); +	_gfx->showGfxObj(_char._ani->gfxobj, true);  	strcpy(_saveData1, locname.location());  	parseLocation(_saveData1); @@ -341,19 +338,18 @@ void Parallaction_ns::changeLocation(char *location) {  	// and acommands are executed, so that it can be set again if needed.  	_engineFlags &= ~kEngineChangeLocation; -	runCommands(_location._commands); +	_cmdExec->run(_location._commands);  	doLocationEnterTransition(); -	runCommands(_location._aCommands); +	_cmdExec->run(_location._aCommands);  	if (_location._hasSound)  		_soundMan->playSfx(_location._soundFile, 0, true); -	debugC(1, kDebugExec, "changeLocation() done"); - -	return; +	_input->setMouseState(oldMouseState); +	debugC(1, kDebugExec, "changeLocation() done");  } @@ -401,6 +397,8 @@ void Parallaction_ns::changeCharacter(const char *name) {  	Common::String oldArchive = _disk->selectArchive((getFeatures() & GF_DEMO) ? "disk0" : "disk1");  	_char._ani->gfxobj = _gfx->loadAnim(_char.getFullName()); +	_char._ani->gfxobj->setFlags(kGfxObjCharacter); +	_char._ani->gfxobj->clearFlags(kGfxObjNormal);  	if (!_char.dummy()) {  		if (getPlatform() == Common::kPlatformAmiga) { diff --git a/engines/parallaction/parser.cpp b/engines/parallaction/parser.cpp index f9de6eb4af..6de0a7d7f5 100644 --- a/engines/parallaction/parser.cpp +++ b/engines/parallaction/parser.cpp @@ -30,8 +30,7 @@ namespace Parallaction {  char			_tokens[20][MAX_TOKEN_LEN]; -Script::Script(Common::ReadStream *input, bool disposeSource) : _input(input), _disposeSource(disposeSource), _line(0) { -} +Script::Script(Common::ReadStream *input, bool disposeSource) : _input(input), _disposeSource(disposeSource), _line(0) {}  Script::~Script() {  	if (_disposeSource) diff --git a/engines/parallaction/parser.h b/engines/parallaction/parser.h index d488cf9b58..79e6cf6640 100644 --- a/engines/parallaction/parser.h +++ b/engines/parallaction/parser.h @@ -128,6 +128,7 @@ protected:  		// BRA specific  		int numZones; +		BackgroundInfo	*info;  		char *bgName;  		char *maskName;  		char *pathName; @@ -171,7 +172,7 @@ protected:  	DECLARE_UNQUALIFIED_COMMAND_PARSER(animation);  	DECLARE_UNQUALIFIED_COMMAND_PARSER(zone);  	DECLARE_UNQUALIFIED_COMMAND_PARSER(location); -	DECLARE_UNQUALIFIED_COMMAND_PARSER(drop); +	DECLARE_UNQUALIFIED_COMMAND_PARSER(invObject);  	DECLARE_UNQUALIFIED_COMMAND_PARSER(call);  	DECLARE_UNQUALIFIED_COMMAND_PARSER(simple);  	DECLARE_UNQUALIFIED_COMMAND_PARSER(move); @@ -192,8 +193,8 @@ protected:  	Question	*parseQuestion();  	void		parseZone(ZoneList &list, char *name); -	void		parseZoneTypeBlock(ZonePtr z); -	void		parseWalkNodes(WalkNodeList &list); +	virtual void parseZoneTypeBlock(ZonePtr z); +	void		parsePointList(PointList &list);  	void		parseAnimation(AnimationList &list, char *name);  	void		parseCommands(CommandList&);  	void		parseCommandFlags(); @@ -221,13 +222,14 @@ public:  	virtual void init();  	virtual ~LocationParser_ns() { +		delete _parser;  		delete _commandsNames;  		delete _locationStmt; +		delete _locationZoneStmt; +		delete _locationAnimStmt;  		delete _zoneTypeNames;  		delete _zoneFlagNames; -		delete _parser; -  		clearSet(_commandParsers);  		clearSet(_locationAnimParsers);  		clearSet(_locationZoneParsers); @@ -283,6 +285,9 @@ protected:  	DECLARE_UNQUALIFIED_ANIM_PARSER(moveto);  	DECLARE_UNQUALIFIED_ANIM_PARSER(endanimation); +	virtual void	parseZoneTypeBlock(ZonePtr z); +	void			parsePathData(ZonePtr z); +  public:  	LocationParser_br(Parallaction_br *vm) : LocationParser_ns((Parallaction_ns*)vm), _vm(vm) {  	} @@ -306,6 +311,7 @@ protected:  	Parser	*_parser;  	Parallaction_ns *_vm; +  	Script	*_script;  	ProgramPtr	_program; @@ -356,7 +362,9 @@ public:  	virtual void init();  	virtual ~ProgramParser_ns() { +		delete _parser;  		delete _instructionNames; +  		clearSet(_instructionParsers);  	} diff --git a/engines/parallaction/parser_br.cpp b/engines/parallaction/parser_br.cpp index 51da7eb396..3b446805d7 100644 --- a/engines/parallaction/parser_br.cpp +++ b/engines/parallaction/parser_br.cpp @@ -390,7 +390,7 @@ DECLARE_LOCATION_PARSER(flags)  {  	if ((_vm->getLocationFlags() & kFlagsVisited) == 0) {  		// only for 1st visit -		_vm->clearLocationFlags(kFlagsAll); +		_vm->clearLocationFlags((uint32)kFlagsAll);  		int _si = 1;  		do { @@ -442,7 +442,7 @@ DECLARE_LOCATION_PARSER(redundant)  {  DECLARE_LOCATION_PARSER(character)  {  	debugC(7, kDebugParser, "LOCATION_PARSER(character) "); -	ctxt.characterName = strdup(_tokens[0]); +	ctxt.characterName = strdup(_tokens[1]);  } @@ -464,9 +464,9 @@ DECLARE_LOCATION_PARSER(mask)  {  	debugC(7, kDebugParser, "LOCATION_PARSER(mask) ");  	ctxt.maskName = strdup(_tokens[1]); -	_vm->_gfx->_backgroundInfo.layers[0] = atoi(_tokens[2]); -	_vm->_gfx->_backgroundInfo.layers[1] = atoi(_tokens[3]); -	_vm->_gfx->_backgroundInfo.layers[2] = atoi(_tokens[4]); +	ctxt.info->layers[0] = atoi(_tokens[2]); +	ctxt.info->layers[1] = atoi(_tokens[3]); +	ctxt.info->layers[2] = atoi(_tokens[4]);  } @@ -750,6 +750,67 @@ DECLARE_ZONE_PARSER(type)  {  	_parser->popTables();  } +void LocationParser_br::parsePathData(ZonePtr z) { + +	PathData *data = new PathData; + +	do { + +		if (!scumm_stricmp("zone", _tokens[0])) { +			int id = atoi(_tokens[1]); +			parsePointList(data->_lists[id]); +			data->_numLists++; +		} + +		_script->readLineToken(true); +	} while (scumm_stricmp("endzone", _tokens[0])); + +	z->u.path = data; +} + +void LocationParser_br::parseZoneTypeBlock(ZonePtr z) { +	debugC(7, kDebugParser, "parseZoneTypeBlock(name: %s, type: %x)", z->_name, z->_type); + +	switch (z->_type & 0xFFFF) { +	case kZoneExamine:	// examine Zone alloc +		parseExamineData(z); +		break; + +	case kZoneDoor: // door Zone alloc +		parseDoorData(z); +		break; + +	case kZoneGet:	// get Zone alloc +		parseGetData(z); +		break; + +	case kZoneMerge:	// merge Zone alloc +		parseMergeData(z); +		break; + +	case kZoneHear: // hear Zone alloc +		parseHearData(z); +		break; + +	case kZoneSpeak:	// speak Zone alloc +		parseSpeakData(z); +		break; + +	// BRA specific zone +	case kZonePath: +		parsePathData(z); +		break; + +	default: +		// eats up 'ENDZONE' line for unprocessed zone types +		_script->readLineToken(true); +		break; +	} + +	debugC(7, kDebugParser, "parseZoneTypeBlock() done"); + +	return; +}  DECLARE_ANIM_PARSER(file)  {  	debugC(7, kDebugParser, "ANIM_PARSER(file) "); @@ -983,7 +1044,7 @@ void LocationParser_br::init() {  	COMMAND_PARSER(zone);		// off  	COMMAND_PARSER(call);  	COMMAND_PARSER(flags);		// toggle -	COMMAND_PARSER(drop); +	COMMAND_PARSER(invObject);	// drop  	COMMAND_PARSER(simple);		// quit  	COMMAND_PARSER(move);  	COMMAND_PARSER(zone);	// stop @@ -991,7 +1052,7 @@ void LocationParser_br::init() {  	COMMAND_PARSER(string);		// followme  	COMMAND_PARSER(simple);		// onmouse  	COMMAND_PARSER(simple);		// offmouse -	COMMAND_PARSER(drop);		// add +	COMMAND_PARSER(invObject);		// add  	COMMAND_PARSER(zone);		// leave  	COMMAND_PARSER(math);		// inc  	COMMAND_PARSER(math);		// dec @@ -1114,11 +1175,14 @@ void LocationParser_br::parse(Script *script) {  	ctxt.maskName = 0;  	ctxt.pathName = 0;  	ctxt.characterName = 0; +	ctxt.info = new BackgroundInfo;  	LocationParser_ns::parse(script); -	_vm->_gfx->setBackground(kBackgroundLocation, ctxt.bgName, ctxt.maskName, ctxt.pathName); -	_vm->_pathBuffer = &_vm->_gfx->_backgroundInfo.path; +	_vm->_disk->loadScenery(*ctxt.info, ctxt.bgName, ctxt.maskName, ctxt.pathName); +	_vm->_gfx->setBackground(kBackgroundLocation, ctxt.info); +	_vm->_pathBuffer = &ctxt.info->path; +  	if (ctxt.characterName) {  		_vm->changeCharacter(ctxt.characterName); diff --git a/engines/parallaction/parser_ns.cpp b/engines/parallaction/parser_ns.cpp index 2c4601c938..ad0f714fdc 100644 --- a/engines/parallaction/parser_ns.cpp +++ b/engines/parallaction/parser_ns.cpp @@ -299,6 +299,7 @@ void LocationParser_ns::parseAnimation(AnimationList &list, char *name) {  	AnimationPtr a(new Animation);  	strncpy(a->_name, name, ZONENAME_LENGTH); +	a->_flags |= kFlagsIsAnimation;  	list.push_front(AnimationPtr(a)); @@ -658,7 +659,7 @@ DECLARE_COMMAND_PARSER(location)  {  } -DECLARE_COMMAND_PARSER(drop)  { +DECLARE_COMMAND_PARSER(invObject)  {  	debugC(7, kDebugParser, "COMMAND_PARSER(drop) ");  	createCommand(_parser->_lookup); @@ -1011,7 +1012,7 @@ DECLARE_LOCATION_PARSER(disk)  {  DECLARE_LOCATION_PARSER(nodes)  {  	debugC(7, kDebugParser, "LOCATION_PARSER(nodes) "); -	parseWalkNodes(_vm->_location._walkNodes); +	parsePointList(_vm->_location._walkPoints);  } @@ -1059,7 +1060,7 @@ DECLARE_LOCATION_PARSER(flags)  {  	if ((_vm->getLocationFlags() & kFlagsVisited) == 0) {  		// only for 1st visit -		_vm->clearLocationFlags(kFlagsAll); +		_vm->clearLocationFlags((uint32)kFlagsAll);  		int _si = 1;  		do { @@ -1124,26 +1125,20 @@ void LocationParser_ns::parse(Script *script) {  	resolveCommandForwards();  } -void LocationParser_ns::parseWalkNodes(WalkNodeList &list) { -	debugC(5, kDebugParser, "parseWalkNodes()"); +void LocationParser_ns::parsePointList(PointList &list) { +	debugC(5, kDebugParser, "parsePointList()");  	_script->readLineToken(true);  	while (scumm_stricmp(_tokens[0], "ENDNODES")) {  		if (!scumm_stricmp(_tokens[0], "COORD")) { - -			WalkNodePtr v4(new WalkNode( -				atoi(_tokens[1]), -				atoi(_tokens[2]) -			)); - -			list.push_front(v4); +			list.push_front(Common::Point(atoi(_tokens[1]), atoi(_tokens[2])));  		}  		_script->readLineToken(true);  	} -	debugC(5, kDebugParser, "parseWalkNodes() done"); +	debugC(5, kDebugParser, "parsePointList() done");  	return;  } @@ -1203,7 +1198,7 @@ void LocationParser_ns::init() {  	COMMAND_PARSER(zone);			// off  	COMMAND_PARSER(call);			// call  	COMMAND_PARSER(flags);			// toggle -	COMMAND_PARSER(drop);			// drop +	COMMAND_PARSER(invObject);			// drop  	COMMAND_PARSER(simple);			// quit  	COMMAND_PARSER(move);			// move  	COMMAND_PARSER(zone);		// stop diff --git a/engines/parallaction/sound.cpp b/engines/parallaction/sound.cpp index dd74e8f7aa..df6867a90c 100644 --- a/engines/parallaction/sound.cpp +++ b/engines/parallaction/sound.cpp @@ -175,6 +175,7 @@ void MidiPlayer::close() {  	_mutex.lock();  	_driver->setTimerCallback(NULL, NULL);  	_driver->close(); +	delete _driver;  	_driver = 0;  	_parser->setMidiDriver(NULL);  	delete _parser; @@ -249,6 +250,9 @@ void DosSoundMan::stopMusic() {  }  void DosSoundMan::playCharacterMusic(const char *character) { +	if (character == NULL) { +		return; +	}  	if (!scumm_stricmp(_vm->_location._name, "night") ||  		!scumm_stricmp(_vm->_location._name, "intsushi")) { diff --git a/engines/parallaction/walk.cpp b/engines/parallaction/walk.cpp index 0a8ded9e29..bf8f423fd5 100644 --- a/engines/parallaction/walk.cpp +++ b/engines/parallaction/walk.cpp @@ -27,33 +27,43 @@  namespace Parallaction { -static uint16 _doorData1 = 1000; -static ZonePtr _zoneTrap; -static uint16	walkData1 = 0; -static uint16	walkData2 = 0;	// next walk frame +#define IS_PATH_CLEAR(x,y) _vm->_pathBuffer->getValue((x), (y))  inline byte PathBuffer::getValue(uint16 x, uint16 y) {  	byte m = data[(x >> 3) + y * internalWidth]; -	uint n = (_vm->getPlatform() == Common::kPlatformPC) ? (x & 7) : (7 - (x & 7)); -	return ((1 << n) & m) >> n; +	uint bit = 0; +	switch (_vm->getGameType()) { +	case GType_Nippon: +		bit = (_vm->getPlatform() == Common::kPlatformPC) ? (x & 7) : (7 - (x & 7)); +		break; + +	case GType_BRA: +		// Amiga and PC versions pack the path bits the same way in BRA +		bit = 7 - (x & 7); +		break; + +	default: +		error("path mask not yet implemented for this game type"); +	} +	return ((1 << bit) & m) >> bit;  }  // adjusts position towards nearest walkable point  // -void PathBuilder::correctPathPoint(Common::Point &to) { +void PathBuilder_NS::correctPathPoint(Common::Point &to) { -	if (_vm->_pathBuffer->getValue(to.x, to.y)) return; +	if (IS_PATH_CLEAR(to.x, to.y)) return;  	int16 right = to.x;  	int16 left = to.x;  	do {  		right++; -	} while ((_vm->_pathBuffer->getValue(right, to.y) == 0) && (right < _vm->_pathBuffer->w)); +	} while (!IS_PATH_CLEAR(right, to.y) && (right < _vm->_pathBuffer->w));  	do {  		left--; -	} while ((_vm->_pathBuffer->getValue(left, to.y) == 0) && (left > 0)); +	} while (!IS_PATH_CLEAR(left, to.y) && (left > 0));  	right = (right == _vm->_pathBuffer->w) ? 1000 : right - to.x;  	left = (left == 0) ? 1000 : to.x - left; @@ -62,10 +72,10 @@ void PathBuilder::correctPathPoint(Common::Point &to) {  	int16 bottom = to.y;  	do {  		top--; -	} while ((_vm->_pathBuffer->getValue(to.x, top) == 0) && (top > 0)); +	} while (!IS_PATH_CLEAR(to.x, top) && (top > 0));  	do {  		bottom++; -	} while ((_vm->_pathBuffer->getValue(to.x, bottom) == 0) && (bottom < _vm->_pathBuffer->h)); +	} while (!IS_PATH_CLEAR(to.x, bottom) && (bottom < _vm->_pathBuffer->h));  	top = (top == 0) ? 1000 : to.y - top;  	bottom = (bottom == _vm->_pathBuffer->h) ? 1000 : bottom - to.y; @@ -90,7 +100,7 @@ void PathBuilder::correctPathPoint(Common::Point &to) {  } -uint32 PathBuilder::buildSubPath(const Common::Point& pos, const Common::Point& stop) { +uint32 PathBuilder_NS::buildSubPath(const Common::Point& pos, const Common::Point& stop) {  	uint32 v28 = 0;  	uint32 v2C = 0; @@ -103,16 +113,15 @@ uint32 PathBuilder::buildSubPath(const Common::Point& pos, const Common::Point&  	while (true) { -		WalkNodeList::iterator nearest = _vm->_location._walkNodes.end(); -		WalkNodeList::iterator locNode = _vm->_location._walkNodes.begin(); +		PointList::iterator nearest = _vm->_location._walkPoints.end(); +		PointList::iterator locNode = _vm->_location._walkPoints.begin();  		// scans location path nodes searching for the nearest Node  		// which can't be farther than the target position  		// otherwise no _closest_node is selected -		while (locNode != _vm->_location._walkNodes.end()) { +		while (locNode != _vm->_location._walkPoints.end()) { -			Common::Point v8; -			(*locNode)->getPoint(v8); +			Common::Point v8 = *locNode;  			v2C = v8.sqrDist(stop);  			v28 = v8.sqrDist(v20); @@ -124,80 +133,59 @@ uint32 PathBuilder::buildSubPath(const Common::Point& pos, const Common::Point&  			locNode++;  		} -		if (nearest == _vm->_location._walkNodes.end()) break; +		if (nearest == _vm->_location._walkPoints.end()) break; -		(*nearest)->getPoint(v20); +		v20 = *nearest;  		v34 = v30 = v20.sqrDist(stop); -		_subPath.push_back(WalkNodePtr(new WalkNode(**nearest))); +		_subPath.push_back(*nearest);  	}  	return v34;  } -#if 0 -void printNodes(WalkNodeList *list, const char* text) { -	printf("%s\n-------------------\n", text); -	for (WalkNodeList::iterator it = list->begin(); it != list->end(); it++) -		printf("node [%p] (%i, %i)\n", *it, (*it)->_x, (*it)->_y); -	return; -} -#endif  //  //	x, y: mouse click (foot) coordinates  // -WalkNodeList *PathBuilder::buildPath(uint16 x, uint16 y) { +void PathBuilder_NS::buildPath(uint16 x, uint16 y) {  	debugC(1, kDebugWalk, "PathBuilder::buildPath to (%i, %i)", x, y); +	_ch->_walkPath.clear(); +  	Common::Point to(x, y);  	correctPathPoint(to);  	debugC(1, kDebugWalk, "found closest path point at (%i, %i)", to.x, to.y); -	WalkNodePtr v48(new WalkNode(to.x, to.y)); -	WalkNodePtr v44 = v48; +	Common::Point v48(to); +	Common::Point v44(to); -	uint16 v38 = walkFunc1(to.x, to.y, v44); +	uint16 v38 = walkFunc1(to, v44);  	if (v38 == 1) {  		// destination directly reachable  		debugC(1, kDebugWalk, "direct move to (%i, %i)", to.x, to.y); - -		_list = new WalkNodeList; -		_list->push_back(v48); -		return _list; +		_ch->_walkPath.push_back(v48); +		return;  	}  	// path is obstructed: look for alternative -	_list = new WalkNodeList; -	_list->push_back(v48); -#if 0 -	printNodes(_list, "start"); -#endif - -	Common::Point stop(v48->_x, v48->_y); +	_ch->_walkPath.push_back(v48);  	Common::Point pos; -	_vm->_char.getFoot(pos); +	_ch->getFoot(pos); -	uint32 v34 = buildSubPath(pos, stop); +	uint32 v34 = buildSubPath(pos, v48);  	if (v38 != 0 && v34 > v38) {  		// no alternative path (gap?) -		_list->clear(); -		_list->push_back(v44); -		return _list; +		_ch->_walkPath.clear(); +		_ch->_walkPath.push_back(v44); +		return;  	} -	_list->insert(_list->begin(), _subPath.begin(), _subPath.end()); -#if 0 -	printNodes(_list, "first segment"); -#endif +	_ch->_walkPath.insert(_ch->_walkPath.begin(), _subPath.begin(), _subPath.end()); -	(*_list->begin())->getPoint(stop); -	buildSubPath(pos, stop); -	_list->insert(_list->begin(), _subPath.begin(), _subPath.end()); -#if 0 -	printNodes(_list, "complete"); -#endif +	buildSubPath(pos, *_ch->_walkPath.begin()); +	_ch->_walkPath.insert(_ch->_walkPath.begin(), _subPath.begin(), _subPath.end()); -	return _list; +	return;  } @@ -208,23 +196,23 @@ WalkNodeList *PathBuilder::buildPath(uint16 x, uint16 y) {  //	1 : Point reachable in a straight line  //	other values: square distance to target (point not reachable in a straight line)  // -uint16 PathBuilder::walkFunc1(int16 x, int16 y, WalkNodePtr Node) { +uint16 PathBuilder_NS::walkFunc1(const Common::Point &to, Common::Point& node) { -	Common::Point arg(x, y); +	Common::Point arg(to); -	Common::Point v4(0, 0); +	Common::Point v4;  	Common::Point foot; -	_vm->_char.getFoot(foot); +	_ch->getFoot(foot);  	Common::Point v8(foot);  	while (foot != arg) { -		if (foot.x < x && _vm->_pathBuffer->getValue(foot.x + 1, foot.y) != 0) foot.x++; -		if (foot.x > x && _vm->_pathBuffer->getValue(foot.x - 1, foot.y) != 0) foot.x--; -		if (foot.y < y && _vm->_pathBuffer->getValue(foot.x, foot.y + 1) != 0) foot.y++; -		if (foot.y > y && _vm->_pathBuffer->getValue(foot.x, foot.y - 1) != 0) foot.y--; +		if (foot.x < to.x && IS_PATH_CLEAR(foot.x + 1, foot.y)) foot.x++; +		if (foot.x > to.x && IS_PATH_CLEAR(foot.x - 1, foot.y)) foot.x--; +		if (foot.y < to.y && IS_PATH_CLEAR(foot.x, foot.y + 1)) foot.y++; +		if (foot.y > to.y && IS_PATH_CLEAR(foot.x, foot.y - 1)) foot.y--;  		if (foot == v8 && foot != arg) { @@ -234,10 +222,10 @@ uint16 PathBuilder::walkFunc1(int16 x, int16 y, WalkNodePtr Node) {  			while (foot != arg) { -				if (foot.x < x && _vm->_pathBuffer->getValue(foot.x + 1, foot.y) == 0) foot.x++; -				if (foot.x > x && _vm->_pathBuffer->getValue(foot.x - 1, foot.y) == 0) foot.x--; -				if (foot.y < y && _vm->_pathBuffer->getValue(foot.x, foot.y + 1) == 0) foot.y++; -				if (foot.y > y && _vm->_pathBuffer->getValue(foot.x, foot.y - 1) == 0) foot.y--; +				if (foot.x < to.x && !IS_PATH_CLEAR(foot.x + 1, foot.y)) foot.x++; +				if (foot.x > to.x && !IS_PATH_CLEAR(foot.x - 1, foot.y)) foot.x--; +				if (foot.y < to.y && !IS_PATH_CLEAR(foot.x, foot.y + 1)) foot.y++; +				if (foot.y > to.y && !IS_PATH_CLEAR(foot.x, foot.y - 1)) foot.y--;  				if (foot == v8 && foot != arg)  					return 0; @@ -245,10 +233,8 @@ uint16 PathBuilder::walkFunc1(int16 x, int16 y, WalkNodePtr Node) {  				v8 = foot;  			} -			Node->_x = v4.x; -			Node->_y = v4.y; - -			return (x - v4.x) * (x - v4.x) + (y - v4.y) * (y - v4.y); +			node = v4; +			return v4.sqrDist(to);  		}  		v8 = foot; @@ -259,190 +245,390 @@ uint16 PathBuilder::walkFunc1(int16 x, int16 y, WalkNodePtr Node) {  	return 1;  } -void Parallaction::clipMove(Common::Point& pos, const WalkNodePtr from) { +void PathWalker_NS::clipMove(Common::Point& pos, const Common::Point& to) { -	if ((pos.x < from->_x) && (pos.x < _pathBuffer->w) && (_pathBuffer->getValue(pos.x + 2, pos.y) != 0)) { -		pos.x = (pos.x + 2 < from->_x) ? pos.x + 2 : from->_x; +	if ((pos.x < to.x) && (pos.x < _vm->_pathBuffer->w) && IS_PATH_CLEAR(pos.x + 2, pos.y)) { +		pos.x = (pos.x + 2 < to.x) ? pos.x + 2 : to.x;  	} -	if ((pos.x > from->_x) && (pos.x > 0) && (_pathBuffer->getValue(pos.x - 2, pos.y) != 0)) { -		pos.x = (pos.x - 2 > from->_x) ? pos.x - 2 : from->_x; +	if ((pos.x > to.x) && (pos.x > 0) && IS_PATH_CLEAR(pos.x - 2, pos.y)) { +		pos.x = (pos.x - 2 > to.x) ? pos.x - 2 : to.x;  	} -	if ((pos.y < from->_y) && (pos.y < _pathBuffer->h) && (_pathBuffer->getValue(pos.x, pos.y + 2) != 0)) { -		pos.y = (pos.y + 2 <= from->_y) ? pos.y + 2 : from->_y; +	if ((pos.y < to.y) && (pos.y < _vm->_pathBuffer->h) && IS_PATH_CLEAR(pos.x, pos.y + 2)) { +		pos.y = (pos.y + 2 <= to.y) ? pos.y + 2 : to.y;  	} -	if ((pos.y > from->_y) && (pos.y > 0) && (_pathBuffer->getValue(pos.x, pos.y - 2) != 0)) { -		pos.y = (pos.y - 2 >= from->_y) ? pos.y - 2 :from->_y; +	if ((pos.y > to.y) && (pos.y > 0) && IS_PATH_CLEAR(pos.x, pos.y - 2)) { +		pos.y = (pos.y - 2 >= to.y) ? pos.y - 2 : to.y;  	}  	return;  } -int16 Parallaction::selectWalkFrame(const Common::Point& pos, const WalkNodePtr from) { -	Common::Point dist(from->_x - pos.x, from->_y - pos.y); +void PathWalker_NS::checkDoor(const Common::Point &foot) { -	if (dist.x < 0) -		dist.x = -dist.x; -	if (dist.y < 0) -		dist.y = -dist.y; +	ZonePtr z = _vm->hitZone(kZoneDoor, foot.x, foot.y); +	if (z) { +		if ((z->_flags & kFlagsClosed) == 0) { +			_vm->_location._startPosition = z->u.door->_startPos; +			_vm->_location._startFrame = z->u.door->_startFrame; +			_vm->scheduleLocationSwitch(z->u.door->_location); +			_vm->_zoneTrap = nullZonePtr; +		} else { +			_vm->_cmdExec->run(z->_commands, z); +		} +	} -	walkData1++; +	z = _vm->hitZone(kZoneTrap, foot.x, foot.y); +	if (z) { +		_vm->setLocationFlags(kFlagsEnter); +		_vm->_cmdExec->run(z->_commands, z); +		_vm->clearLocationFlags(kFlagsEnter); +		_vm->_zoneTrap = z; +	} else +	if (_vm->_zoneTrap) { +		_vm->setLocationFlags(kFlagsExit); +		_vm->_cmdExec->run(_vm->_zoneTrap->_commands, _vm->_zoneTrap); +		_vm->clearLocationFlags(kFlagsExit); +		_vm->_zoneTrap = nullZonePtr; +	} -	// walk frame selection -	int16 v16; -	if (_char._ani->getFrameNum() == 20) { +} -		if (dist.x > dist.y) { -			walkData2 = (from->_x > pos.x) ? 0 : 7; -			walkData1 %= 12; -			v16 = walkData1 / 2; -		} else { -			walkData2 = (from->_y > pos.y) ? 14 : 17; -			walkData1 %= 8; -			v16 = walkData1 / 4; + +void PathWalker_NS::finalizeWalk() { +	_engineFlags &= ~kEngineWalking; + +	Common::Point foot; +	_ch->getFoot(foot); +	checkDoor(foot); + +	_ch->_walkPath.clear(); +} + +void PathWalker_NS::walk() { +	if ((_engineFlags & kEngineWalking) == 0) { +		return; +	} + +	Common::Point curPos; +	_ch->getFoot(curPos); + +	// update target, if previous was reached +	PointList::iterator it = _ch->_walkPath.begin(); +	if (it != _ch->_walkPath.end()) { +		if (*it == curPos) { +			debugC(1, kDebugWalk, "walk reached node (%i, %i)", (*it).x, (*it).y); +			it = _ch->_walkPath.erase(it);  		} +	} +	// advance character towards the target +	Common::Point targetPos; +	if (it == _ch->_walkPath.end()) { +		debugC(1, kDebugWalk, "walk reached last node"); +		finalizeWalk(); +		targetPos = curPos;  	} else { +		// targetPos is saved to help setting character direction +		targetPos = *it; -		if (dist.x > dist.y) { -			walkData2 = (from->_x > pos.x) ? 0 : 9; -			walkData1 %= 16; -			v16 = walkData1 / 2; -		} else { -			walkData2 = (from->_y > pos.y) ? 18 : 21; -			walkData1 %= 8; -			v16 = walkData1 / 4; -		} +		Common::Point newPos(curPos); +		clipMove(newPos, targetPos); +		_ch->setFoot(newPos); +		if (newPos == curPos) { +			debugC(1, kDebugWalk, "walk was blocked by an unforeseen obstacle"); +			finalizeWalk(); +			targetPos = newPos;	// when walking is interrupted, targetPos must be hacked so that a still frame can be selected +		}  	} -	return v16; +	// targetPos is used to select the direction (and the walkFrame) of a character, +	// since it doesn't cause the sudden changes in orientation that newPos would. +	// Since newPos is 'adjusted' according to walkable areas, an imaginary line drawn +	// from curPos to newPos is prone to abrutply change in direction, thus making the +	// code select 'too different' frames when walking diagonally against obstacles, +	// and yielding an annoying shaking effect in the character. +	_ch->updateDirection(curPos, targetPos);  } -uint16 Parallaction::checkDoor() { -//	printf("checkDoor()..."); -	if (_currentLocationIndex != _doorData1) { -		_doorData1 = _currentLocationIndex; -		_zoneTrap = nullZonePtr; -	} -	_engineFlags &= ~kEngineWalking; +PathBuilder_NS::PathBuilder_NS(Character *ch) : PathBuilder(ch), _list(0) { +} -	Common::Point foot; -	_char.getFoot(foot); -	ZonePtr z = hitZone(kZoneDoor, foot.x, foot.y); +bool PathBuilder_BR::directPathExists(const Common::Point &from, const Common::Point &to) { -	if (z) { +	Common::Point copy(from); +	Common::Point p(copy); -		if ((z->_flags & kFlagsClosed) == 0) { -			_location._startPosition = z->u.door->_startPos; -			_location._startFrame = z->u.door->_startFrame; +	while (p != to) { -			scheduleLocationSwitch(z->u.door->_location); -			_zoneTrap = nullZonePtr; +		if (p.x < to.x && IS_PATH_CLEAR(p.x + 1, p.y)) p.x++; +		if (p.x > to.x && IS_PATH_CLEAR(p.x - 1, p.y)) p.x--; +		if (p.y < to.y && IS_PATH_CLEAR(p.x, p.y + 1)) p.y++; +		if (p.y > to.y && IS_PATH_CLEAR(p.x, p.y - 1)) p.y--; -		} else { -			runCommands(z->_commands, z); +		if (p == copy && p != to) { +			return false;  		} + +		copy = p;  	} -	_char.getFoot(foot); -	z = hitZone(kZoneTrap, foot.x, foot.y); +	return true; +} -	if (z) { -		setLocationFlags(kFlagsEnter); -		runCommands(z->_commands, z); -		clearLocationFlags(kFlagsEnter); -		_zoneTrap = z; -	} else -	if (_zoneTrap) { -		setLocationFlags(kFlagsExit); -		runCommands(_zoneTrap->_commands, _zoneTrap); -		clearLocationFlags(kFlagsExit); -		_zoneTrap = nullZonePtr; +void PathBuilder_BR::buildPath(uint16 x, uint16 y) { +	Common::Point foot; +	_ch->getFoot(foot); + +	debugC(1, kDebugWalk, "buildPath: from (%i, %i) to (%i, %i)", foot.x, foot.y, x, y); +	_ch->_walkPath.clear(); + +	// look for easy path first +	Common::Point dest(x, y); +	if (directPathExists(foot, dest)) { +		_ch->_walkPath.push_back(dest); +		debugC(3, kDebugWalk, "buildPath: direct path found"); +		return; +	} + +	// look for short circuit cases +	ZonePtr z0 = _vm->hitZone(kZonePath, x, y); +	if (!z0) { +		_ch->_walkPath.push_back(dest); +		debugC(3, kDebugWalk, "buildPath: corner case 0"); +		return; +	} +	ZonePtr z1 = _vm->hitZone(kZonePath, foot.x, foot.y); +	if (!z1 || z1 == z0) { +		_ch->_walkPath.push_back(dest); +		debugC(3, kDebugWalk, "buildPath: corner case 1"); +		return; +	} + +	// build complex path +	int id = atoi(z0->_name); + +	if (z1->u.path->_lists[id].empty()) { +		_ch->_walkPath.clear(); +		debugC(3, kDebugWalk, "buildPath: no path"); +		return;  	} -//	printf("done\n"); +	PointList::iterator b = z1->u.path->_lists[id].begin(); +	PointList::iterator e = z1->u.path->_lists[id].end(); +	for ( ; b != e; b++) { +		_ch->_walkPath.push_front(*b); +	} +	_ch->_walkPath.push_back(dest); +	debugC(3, kDebugWalk, "buildPath: complex path"); -	_char._ani->_frame = walkData2; -	return _char._ani->_frame; +	return;  } +PathBuilder_BR::PathBuilder_BR(Character *ch) : PathBuilder(ch) { +} + +void PathWalker_BR::finalizeWalk() { +	_engineFlags &= ~kEngineWalking; +	_first = true; +	_fieldC = 1; + +	Common::Point foot; +	_ch->getFoot(foot); + +	ZonePtr z = _vm->hitZone(kZoneDoor, foot.x, foot.y); +	if (z && ((z->_flags & kFlagsClosed) == 0)) { +		_vm->_location._startPosition = z->u.door->_startPos; // foot pos +		_vm->_location._startFrame = z->u.door->_startFrame; + +#if 0 +		// TODO: implement working follower. Must find out a location in which the code is +		// used and which is stable enough. +		_followerFootInit.x = -1; +		if (_follower && z->u.door->startPos2.x != -1) { +			_followerFootInit.x = z->u.door->startPos2.x;	// foot pos +			_followerFootInit.y = z->u.door->startPos2.y;	// foot pos +		} +		_followerFootInit.z = -1; +		if (_follower && z->u.door->startPos2.z != -1) { +			_followerFootInit.z = z->u.door->startPos2.z;	// foot pos +		} +#endif + +		_vm->scheduleLocationSwitch(z->u.door->_location); +		_vm->_cmdExec->run(z->_commands, z); +	} + +#if 0 +	// TODO: Input::walkTo must be extended to support destination frame in addition to coordinates +	// TODO: the frame argument must be passed to PathWalker through PathBuilder, so probably +	// a merge between the two Path managers is the right solution +	if (_engineFlags & FINAL_WALK_FRAME) {	// this flag is set in readInput() +		_engineFlags &= ~FINAL_WALK_FRAME; +		_char.ani->_frame = _moveToF; 	// from readInput()... +	} else { +		_char.ani->_frame = _dirFrame;	// from walk() +	} +	_char.setFoot(foot); +#endif + +	_ch->_ani->_frame = _dirFrame;	// temporary solution + +#if 0 +	// TODO: support scrolling ;) +	if (foot.x > _gfx->hscroll + 600) _gfx->scrollRight(78); +	if (foot.x < _gfx->hscroll + 40) _gfx->scrollLeft(78); +	if (foot.y > 350) _gfx->scrollDown(100); +	if (foot.y < 80) _gfx->scrollUp(100); +#endif -void Parallaction::finalizeWalk(WalkNodeList *list) { -	checkDoor(); -	delete list; +	return;  } -void Parallaction_ns::walk() { + +void PathWalker_BR::walk() {  	if ((_engineFlags & kEngineWalking) == 0) {  		return;  	} -	WalkNodeList *list = _char._walkPath; +#if 0 +	// TODO: support delays in walking. This requires extending Input::walkIo(). +	if (ch._walkDelay > 0) { +		ch._walkDelay--; +		if (ch._walkDelay == 0 && _ch._ani->_scriptName) { +			// stop script and reset +			_ch._ani->_flags &= ~kFlagsActing; +			Script *script = findScript(_ch._ani->_scriptName); +			script->_nextCommand = script->firstCommand; +		} +		return; +	} +#endif -	_char._ani->_oldPos.x = _char._ani->_left; -	_char._ani->_oldPos.y = _char._ani->_top; +	GfxObj *obj = _ch->_ani->gfxobj; -	Common::Point pos; -	_char.getFoot(pos); +	Common::Rect rect; +	obj->getRect(_ch->_ani->_frame, rect); -	WalkNodeList::iterator it = list->begin(); +	uint scale; +	if (rect.bottom > _vm->_location._zeta0) { +		scale = 100; +	} else +	if (rect.bottom < _vm->_location._zeta1) { +		scale = _vm->_location._zeta2; +	} else { +		scale = _vm->_location._zeta2 + ((rect.bottom - _vm->_location._zeta1) * (100 - _vm->_location._zeta2)) / (_vm->_location._zeta0 - _vm->_location._zeta1); +	} +	int xStep = (scale * 16) / 100 + 1; +	int yStep = (scale * 10) / 100 + 1; + +	debugC(9, kDebugWalk, "calculated step: (%i, %i)\n", xStep, yStep); + +	if (_fieldC == 0) { +		_ch->_walkPath.erase(_ch->_walkPath.begin()); -	if (it != list->end()) { -		if ((*it)->_x == pos.x && (*it)->_y == pos.y) { -			debugC(1, kDebugWalk, "walk reached node (%i, %i)", (*it)->_x, (*it)->_y); -			it = list->erase(it); +		if (_ch->_walkPath.empty()) { +			finalizeWalk(); +			debugC(3, kDebugWalk, "PathWalker_BR::walk, case 0\n"); +			return; +		} else { +			debugC(3, kDebugWalk, "PathWalker_BR::walk, moving to next node\n");  		}  	} -	if (it == list->end()) { -		debugC(1, kDebugWalk, "walk reached last node"); -//		j->_finished = 1; -		finalizeWalk(list); -		return; -	} -	_char._walkPath = list; -	// selectWalkFrame must be performed before position is changed by clipMove -	int16 v16 = selectWalkFrame(pos, *it); -	clipMove(pos, *it); +	_ch->getFoot(_startFoot); -	_char.setFoot(pos); +	_fieldC = 0; +	_step++; +	_step %= 8; -	Common::Point newpos(_char._ani->_left, _char._ani->_top); +	int walkFrame = _step; +	_dirFrame = 0; +	Common::Point newpos(_startFoot), delta; -	if (newpos == _char._ani->_oldPos) { -		debugC(1, kDebugWalk, "walk was blocked by an unforeseen obstacle"); -//		j->_finished = 1; -		finalizeWalk(list); -	} else { -		_char._ani->_frame = v16 + walkData2 + 1; +	Common::Point p(*_ch->_walkPath.begin()); + +	if (_startFoot.y < p.y && _startFoot.y < 400 && IS_PATH_CLEAR(_startFoot.x, yStep + _startFoot.y)) { +		if (yStep + _startFoot.y <= p.y) { +			_fieldC = 1; +			delta.y = yStep; +			newpos.y = yStep + _startFoot.y; +		} else { +			delta.y = p.y - _startFoot.y; +			newpos.y = p.y; +		} +		_dirFrame = 9; +	} else +	if (_startFoot.y > p.y && _startFoot.y > 0 && IS_PATH_CLEAR(_startFoot.x, _startFoot.y - yStep)) { +		if (_startFoot.y - yStep >= p.y) { +			_fieldC = 1; +			delta.y = yStep; +			newpos.y = _startFoot.y - yStep; +		} else { +			delta.y = _startFoot.y - p.y; +			newpos.y = p.y; +		} +		_dirFrame = 0;  	} -	return; -} +	if (_startFoot.x < p.x && _startFoot.x < 640 && IS_PATH_CLEAR(_startFoot.x + xStep, _startFoot.y)) { +		if (_startFoot.x + xStep <= p.x) { +			_fieldC = 1; +			delta.x = xStep; +			newpos.x = xStep + _startFoot.x; +		} else { +			delta.x = p.x - _startFoot.x; +			newpos.x = p.x; +		} +		if (delta.y < delta.x) { +			_dirFrame = 18;	// right +		} +	} else +	if (_startFoot.x > p.x && _startFoot.x > 0 && IS_PATH_CLEAR(_startFoot.x - xStep, _startFoot.y)) { +		if (_startFoot.x - xStep >= p.x) { +			_fieldC = 1; +			delta.x = xStep; +			newpos.x = _startFoot.x - xStep; +		} else { +			delta.x = _startFoot.x - p.x; +			newpos.x = p.x; +		} +		if (delta.y < delta.x) { +			_dirFrame = 27;	// left +		} +	} +	debugC(9, kDebugWalk, "foot (%i, %i) dest (%i, %i) deltas = %i/%i \n", _startFoot.x, _startFoot.y, p.x, p.y, delta.x, delta.y); -WalkNode::WalkNode() : _x(0), _y(0) { -} +	if (_fieldC) { +		debugC(9, kDebugWalk, "PathWalker_BR::walk, foot moved from (%i, %i) to (%i, %i)\n", _startFoot.x, _startFoot.y, newpos.x, newpos.y); +		_ch->_ani->_frame = walkFrame + _dirFrame + 1; +		_startFoot.x = newpos.x; +		_startFoot.y = newpos.y; +		_ch->setFoot(_startFoot); +		_ch->_ani->_z = newpos.y; +	} -WalkNode::WalkNode(int16 x, int16 y) : _x(x), _y(y) { -} +	if (_fieldC || !_ch->_walkPath.empty()) { +//		checkTrap(); +		debugC(3, kDebugWalk, "PathWalker_BR::walk, case 1\n"); +		return; +	} -WalkNode::WalkNode(const WalkNode& w) : _x(w._x), _y(w._y) { +	debugC(3, kDebugWalk, "PathWalker_BR::walk, case 2\n"); +	finalizeWalk(); +	return;  } -void WalkNode::getPoint(Common::Point &p) const { -	p.x = _x; -	p.y = _y; -} +PathWalker_BR::PathWalker_BR(Character *ch) : PathWalker(ch), _fieldC(1), _first(true) { -PathBuilder::PathBuilder(AnimationPtr anim) : _anim(anim), _list(0) {  } diff --git a/engines/parallaction/walk.h b/engines/parallaction/walk.h index 788a6e1375..8d21e5ebbd 100644 --- a/engines/parallaction/walk.h +++ b/engines/parallaction/walk.h @@ -29,43 +29,89 @@  #include "common/ptr.h"  #include "common/list.h" +#include "parallaction/objects.h" + +  namespace Parallaction { -struct Animation; +struct Character; + +class PathBuilder { -struct WalkNode { -	int16	_x; -	int16	_y; +protected: +	Character *_ch;  public: -	WalkNode(); -	WalkNode(int16 x, int16 y); -	WalkNode(const WalkNode& w); +	PathBuilder(Character *ch) : _ch(ch) { } +	virtual ~PathBuilder() { } -	void getPoint(Common::Point &p) const; +	virtual void buildPath(uint16 x, uint16 y) = 0;  }; -typedef Common::SharedPtr<WalkNode> WalkNodePtr; -typedef Common::List<WalkNodePtr> WalkNodeList; +class PathBuilder_NS : public PathBuilder { -class PathBuilder { - -	AnimationPtr	_anim; - -	WalkNodeList	*_list; -	WalkNodeList	_subPath; +	PointList	*_list; +	PointList	_subPath;  	void correctPathPoint(Common::Point &to);  	uint32 buildSubPath(const Common::Point& pos, const Common::Point& stop); -	uint16 walkFunc1(int16 x, int16 y, WalkNodePtr Node); +	uint16 walkFunc1(const Common::Point &to, Common::Point& node);  public: -	PathBuilder(AnimationPtr anim); -	WalkNodeList* buildPath(uint16 x, uint16 y); +	PathBuilder_NS(Character *ch); +	void buildPath(uint16 x, uint16 y); +}; + +class PathBuilder_BR : public PathBuilder { + +	bool directPathExists(const Common::Point &from, const Common::Point &to); + +public: +	PathBuilder_BR(Character *ch); +	void buildPath(uint16 x, uint16 y); +}; + +class PathWalker { +protected: +	Character	*_ch; +public: +	PathWalker(Character *ch) : _ch(ch) { } +	virtual ~PathWalker() { } +	virtual void walk() = 0;  }; +class PathWalker_NS : public PathWalker { + + +	void finalizeWalk(); +	void clipMove(Common::Point& pos, const Common::Point& to); +	void checkDoor(const Common::Point &foot); + +public: +	PathWalker_NS(Character *ch) : PathWalker(ch) { } +	void walk(); +}; + + +class PathWalker_BR : public PathWalker { + + +	int			_walkDelay; +	int			_fieldC; +	Common::Point _startFoot; +	bool		_first; +	int			_step; + +	int			_dirFrame; + +	void finalizeWalk(); + +public: +	PathWalker_BR(Character *ch); +	void walk(); +};  } diff --git a/engines/queen/graphics.cpp b/engines/queen/graphics.cpp index f863f7663c..6d0a11ccfe 100644 --- a/engines/queen/graphics.cpp +++ b/engines/queen/graphics.cpp @@ -1175,15 +1175,8 @@ BamScene::BamScene(QueenEngine *vm)  }  void BamScene::playSfx() { -	// Don't try to play all the sounds. This is only necessary for the -	// fight bam, in which the number of 'sfx bam frames' is too much -	// important / too much closer. The original game does not have -	// this problem since its playSfx() function returns immediately -	// if a sound is already being played. -	if (_lastSoundIndex == 0 || _index - _lastSoundIndex >= SFX_SKIP) { -		_vm->sound()->playSfx(_vm->logic()->currentRoomSfx()); -		_lastSoundIndex = _index; -	} +	_vm->sound()->playSfx(_vm->logic()->currentRoomSfx()); +	_lastSoundIndex = _index;  }  void BamScene::prepareAnimation() { diff --git a/engines/queen/graphics.h b/engines/queen/graphics.h index 6f00111635..7eadf9a191 100644 --- a/engines/queen/graphics.h +++ b/engines/queen/graphics.h @@ -248,10 +248,6 @@ public:  		F_REQ_STOP = 2  	}; -	enum { -		SFX_SKIP = 8 -	}; -  	uint16 _flag, _index;  private: diff --git a/engines/queen/input.cpp b/engines/queen/input.cpp index 146e95bcef..9f03c341c9 100644 --- a/engines/queen/input.cpp +++ b/engines/queen/input.cpp @@ -27,6 +27,7 @@  #include "common/events.h"  #include "common/system.h" +#include "queen/queen.h"  #include "queen/input.h"  namespace Queen { @@ -51,12 +52,12 @@ const Verb Input::_verbKeys[] = {  	VERB_USE  }; -Input::Input(Common::Language language, OSystem *system) : +Input::Input(Common::Language language, OSystem *system, QueenEngine *vm) :  	_system(system), _eventMan(system->getEventManager()), _fastMode(false),  	_keyVerb(VERB_NONE), _cutawayRunning(false), _canQuit(false),  	_cutawayQuit(false), _dialogueRunning(false), _talkQuit(false),  	_quickSave(false), _quickLoad(false), _debugger(false), _inKey(Common::KEYCODE_INVALID), -	_mouseButton(0), _idleTime(0) { +	_mouseButton(0), _idleTime(0) , _vm(vm) {  	switch (language) {  	case Common::EN_ANY: @@ -119,8 +120,8 @@ void Input::delay(uint amount) {  				break;  			case Common::EVENT_QUIT: -				_system->quit(); -				break; +				_vm->quitGame(); +				return;  			default:  				break; diff --git a/engines/queen/input.h b/engines/queen/input.h index 86092aeed6..43a57729c6 100644 --- a/engines/queen/input.h +++ b/engines/queen/input.h @@ -49,7 +49,7 @@ public:  		MOUSE_RBUTTON = 2  	}; -	Input(Common::Language language, OSystem *system); +	Input(Common::Language language, OSystem *system, QueenEngine *vm);  	void delay(uint amount); @@ -99,6 +99,8 @@ private:  	Common::EventManager *_eventMan; +	QueenEngine *_vm; +  	//! some cutaways require update() run faster  	bool _fastMode; diff --git a/engines/queen/journal.cpp b/engines/queen/journal.cpp index bfbcfa4e59..0327fb74b8 100644 --- a/engines/queen/journal.cpp +++ b/engines/queen/journal.cpp @@ -85,8 +85,8 @@ void Journal::use() {  				handleMouseWheel(1);  				break;  			case Common::EVENT_QUIT: -				_system->quit(); -				break; +				_vm->quitGame(); +				return;  			default:  				break;  			} diff --git a/engines/queen/queen.cpp b/engines/queen/queen.cpp index d1a1247c46..c95e44b477 100644 --- a/engines/queen/queen.cpp +++ b/engines/queen/queen.cpp @@ -418,7 +418,7 @@ int QueenEngine::init() {  	_display = new Display(this, _system);  	_graphics = new Graphics(this);  	_grid = new Grid(this); -	_input = new Input(_resource->getLanguage(), _system); +	_input = new Input(_resource->getLanguage(), _system, this);  	if (_resource->isDemo()) {  		_logic = new LogicDemo(this); diff --git a/engines/queen/resource.cpp b/engines/queen/resource.cpp index 5a8db74e3b..b3bd663baf 100644 --- a/engines/queen/resource.cpp +++ b/engines/queen/resource.cpp @@ -106,7 +106,7 @@ ResourceEntry *Resource::resourceEntry(const char *filename) const {  			re = &_resourceTable[cur];  			break;  		} -	} while (cur++ < _resourceEntries); +	} while (++cur < _resourceEntries);  #endif  	return re;  } diff --git a/engines/queen/sound.cpp b/engines/queen/sound.cpp index d70fe7209d..27cf1bf6a2 100644 --- a/engines/queen/sound.cpp +++ b/engines/queen/sound.cpp @@ -35,6 +35,7 @@  #include "queen/queen.h"  #include "queen/resource.h" +#include "sound/audiostream.h"  #include "sound/flac.h"  #include "sound/mididrv.h"  #include "sound/mp3.h" @@ -45,6 +46,42 @@  namespace Queen { +// The sounds in the PC versions are all played at 11840 Hz. Unfortunately, we +// did not know that at the time, so there are plenty of compressed versions +// which claim that they should be played at 11025 Hz. This "wrapper" class +// works around that. + +class AudioStreamWrapper : public Audio::AudioStream { +protected: +	Audio::AudioStream *_stream; + +public: +	AudioStreamWrapper(Audio::AudioStream *stream) { +		_stream = stream; +	} +	~AudioStreamWrapper() { +		delete _stream; +	} +	int readBuffer(int16 *buffer, const int numSamples) { +		return _stream->readBuffer(buffer, numSamples); +	} +	bool isStereo() const { +		return _stream->isStereo(); +	} +	bool endOfData() const { +		return _stream->endOfData(); +	} +	bool endOfStream() { +		return _stream->endOfStream(); +	} +	int getRate() const { +		return 11840; +	} +	int32 getTotalPlayTime() { +		return _stream->getTotalPlayTime(); +	} +}; +  class SilentSound : public PCSound {  public:  	SilentSound(Audio::Mixer *mixer, QueenEngine *vm) : PCSound(mixer, vm) {} @@ -69,7 +106,7 @@ protected:  	void playSoundData(Common::File *f, uint32 size, Audio::SoundHandle *soundHandle) {  		Common::MemoryReadStream *tmp = f->readStream(size);  		assert(tmp); -		_mixer->playInputStream(Audio::Mixer::kSFXSoundType, soundHandle, Audio::makeMP3Stream(tmp, true)); +		_mixer->playInputStream(Audio::Mixer::kSFXSoundType, soundHandle, new AudioStreamWrapper(Audio::makeMP3Stream(tmp, true)));  	}  };  #endif @@ -82,7 +119,7 @@ protected:  	void playSoundData(Common::File *f, uint32 size, Audio::SoundHandle *soundHandle) {  		Common::MemoryReadStream *tmp = f->readStream(size);  		assert(tmp); -		_mixer->playInputStream(Audio::Mixer::kSFXSoundType, soundHandle, Audio::makeVorbisStream(tmp, true)); +		_mixer->playInputStream(Audio::Mixer::kSFXSoundType, soundHandle, new AudioStreamWrapper(Audio::makeVorbisStream(tmp, true)));  	}  };  #endif @@ -95,7 +132,7 @@ protected:  	void playSoundData(Common::File *f, uint32 size, Audio::SoundHandle *soundHandle) {  		Common::MemoryReadStream *tmp = f->readStream(size);  		assert(tmp); -		_mixer->playInputStream(Audio::Mixer::kSFXSoundType, soundHandle, Audio::makeFlacStream(tmp, true)); +		_mixer->playInputStream(Audio::Mixer::kSFXSoundType, soundHandle, new AudioStreamWrapper(Audio::makeFlacStream(tmp, true)));  	}  };  #endif // #ifdef USE_FLAC @@ -229,11 +266,6 @@ void PCSound::setVolume(int vol) {  	_music->setVolume(vol);  } -void PCSound::waitFinished(bool isSpeech) { -	while (_mixer->isSoundHandleActive(isSpeech ? _speechHandle : _sfxHandle)) -		_vm->input()->delay(10); -} -  void PCSound::playSound(const char *base, bool isSpeech) {  	char name[13];  	strcpy(name, base); @@ -243,7 +275,13 @@ void PCSound::playSound(const char *base, bool isSpeech) {  			name[i] = '0';  	}  	strcat(name, ".SB"); -	waitFinished(isSpeech); +	if (isSpeech) { +		while (_mixer->isSoundHandleActive(_speechHandle)) { +			_vm->input()->delay(10); +		} +	} else { +		_mixer->stopHandle(_sfxHandle); +	}  	uint32 size;  	Common::File *f = _vm->resource()->findSound(name, &size);  	if (f) { @@ -255,6 +293,8 @@ void PCSound::playSound(const char *base, bool isSpeech) {  }  void SBSound::playSoundData(Common::File *f, uint32 size, Audio::SoundHandle *soundHandle) { +	// In order to simplify the code, we don't parse the .sb header but hard-code the +	// values. Refer to tracker item #1876741 for details on the format/fields.  	int headerSize;  	f->seek(2, SEEK_CUR);  	uint16 version = f->readUint16LE(); @@ -276,7 +316,7 @@ void SBSound::playSoundData(Common::File *f, uint32 size, Audio::SoundHandle *so  	if (sound) {  		f->read(sound, size);  		byte flags = Audio::Mixer::FLAG_UNSIGNED | Audio::Mixer::FLAG_AUTOFREE; -		_mixer->playRaw(Audio::Mixer::kSFXSoundType, soundHandle, sound, size, 11025, flags); +		_mixer->playRaw(Audio::Mixer::kSFXSoundType, soundHandle, sound, size, 11840, flags);  	}  } diff --git a/engines/queen/sound.h b/engines/queen/sound.h index c2c1481cc6..331034f746 100644 --- a/engines/queen/sound.h +++ b/engines/queen/sound.h @@ -143,7 +143,6 @@ public:  	void setVolume(int vol);  protected: -	void waitFinished(bool isSpeech);  	void playSound(const char *base, bool isSpeech);  	virtual void playSoundData(Common::File *f, uint32 size, Audio::SoundHandle *soundHandle) = 0; diff --git a/engines/saga/animation.cpp b/engines/saga/animation.cpp index 3a1e510529..9fffb0f8bf 100644 --- a/engines/saga/animation.cpp +++ b/engines/saga/animation.cpp @@ -55,6 +55,7 @@ Anim::Anim(SagaEngine *vm) : _vm(vm) {  Anim::~Anim(void) {  	reset(); +	freeCutawayList();  }  void Anim::loadCutawayList(const byte *resourcePointer, size_t resourceLength) { diff --git a/engines/saga/font.cpp b/engines/saga/font.cpp index e936117894..482b3a4c82 100644 --- a/engines/saga/font.cpp +++ b/engines/saga/font.cpp @@ -63,6 +63,8 @@ Font::~Font(void) {  		free(_fonts[i]);  	} + +	free(_fonts);  } @@ -238,6 +240,13 @@ void Font::createOutline(FontData *font) {  	}  } +int Font::translateChar(int charId) { +	if (charId <= 127) +		return charId;					// normal character +	else +		return _charMap[charId - 128];	// extended character +} +  // Returns the horizontal length in pixels of the graphical representation  // of at most 'count' characters of the string 'text', taking  // into account any formatting options specified by 'flags'. @@ -257,7 +266,7 @@ int Font::getStringWidth(FontId fontId, const char *text, size_t count, FontEffe  	for (ct = count; *txt && (!count || ct > 0); txt++, ct--) {  		ch = *txt & 0xFFU;  		// Translate character -		ch = _charMap[ch]; +		ch = translateChar(ch);  		assert(ch < FONT_CHARCOUNT);  		width += font->normal.fontCharEntry[ch].tracking;  	} @@ -336,11 +345,11 @@ void Font::outFont(const FontStyle &drawFont, Surface *ds, const char *text, siz  				// Don't do any special font mapping for the Italian fan  				// translation of ITE  				if (_vm->getLanguage() != Common::IT_ITA) -					c_code = _charMap[c_code]; +					c_code = translateChar(c_code);  			}  		} else if (_fontMapping == 1) {  			// Force font mapping -			c_code = _charMap[c_code]; +			c_code = translateChar(c_code);  		} else {  			// In all other cases, ignore font mapping  		} diff --git a/engines/saga/font.h b/engines/saga/font.h index 6b930ddca0..76c0f06725 100644 --- a/engines/saga/font.h +++ b/engines/saga/font.h @@ -158,6 +158,7 @@ class Font {  	 };  	 Font::FontId knownFont2FontIdx(KnownFont font); +	 int translateChar(int charId);  	 int getStringWidth(FontId fontId, const char *text, size_t count, FontEffectFlags flags);  	 int getHeight(FontId fontId, const char *text, int width, FontEffectFlags flags); @@ -196,7 +197,7 @@ class Font {  		 return byteLength;  	 } -	static const int _charMap[256]; +	static const int _charMap[128];  	SagaEngine *_vm;  	bool _initialized; diff --git a/engines/saga/font_map.cpp b/engines/saga/font_map.cpp index 6246cb71da..6abaeea151 100644 --- a/engines/saga/font_map.cpp +++ b/engines/saga/font_map.cpp @@ -32,135 +32,8 @@  namespace Saga { -const int Font::_charMap[256] = { -	0,			//   0 -	1,			//   1 -	2,			//   2 -	3,			//   3 -	4,			//   4 -	5,			//   5 -	6,			//   6 -	7,			//   7 -	8,			//   8 -	9,			//   9 -	10,			//  10 -	11,			//  11 -	12,			//  12 -	13,			//  13 -	14,			//  14 -	15,			//  15 -	16,			//  16 -	17,			//  17 -	18,			//  18 -	19,			//  19 -	20,			//  20 -	21,			//  21 -	22,			//  22 -	23,			//  23 -	24,			//  24 -	25,			//  25 -	26,			//  26 -	27,			//  27 -	28,			//  28 -	29,			//  29 -	30,			//  30 -	31,			//  31 -	32,			//  32 -	33,			//  33 -	34,			//  34 -	35,			//  35 -	36,			//  36 -	37,			//  37 -	38,			//  38 -	39,			//  39 -	40,			//  40 -	41,			//  41 -	42,			//  42 -	43,			//  43 -	44,			//  44 -	45,			//  45 -	46,			//  46 -	47,			//  47 -	48,			//  48 -	49,			//  49 -	50,			//  50 -	51,			//  51 -	52,			//  52 -	53,			//  53 -	54,			//  54 -	55,			//  55 -	56,			//  56 -	57,			//  57 -	58,			//  58 -	59,			//  59 -	60,			//  60 -	61,			//  61 -	62,			//  62 -	63,			//  63 -	64,			//  64 -	65,			//  65 -	66,			//  66 -	67,			//  67 -	68,			//  68 -	69,			//  69 -	70,			//  70 -	71,			//  71 -	72,			//  72 -	73,			//  73 -	74,			//  74 -	75,			//  75 -	76,			//  76 -	77,			//  77 -	78,			//  78 -	79,			//  79 -	80,			//  80 -	81,			//  81 -	82,			//  82 -	83,			//  83 -	84,			//  84 -	85,			//  85 -	86,			//  86 -	87,			//  87 -	88,			//  88 -	89,			//  89 -	90,			//  90 -	91,			//  91 -	92,			//  92 -	93,			//  93 -	94,			//  94 -	95,			//  95 -	96,			//  96 -	97,			//  97 -	98,			//  98 -	99,			//  99 -	100,		// 100 -	101,		// 101 -	102,		// 102 -	103,		// 103 -	104,		// 104 -	105,		// 105 -	106,		// 106 -	107,		// 107 -	108,		// 108 -	109,		// 109 -	110,		// 110 -	111,		// 111 -	112,		// 112 -	113,		// 113 -	114,		// 114 -	115,		// 115 -	116,		// 116 -	117,		// 117 -	118,		// 118 -	119,		// 119 -	120,		// 120 -	121,		// 121 -	122,		// 122 -	123,		// 123 -	124,		// 124 -	125,		// 125 -	126,		// 126 -	127,		// 127 +const int Font::_charMap[128] = { +	// Characters 0 - 127 are mapped directly to ISO 8859-1  	199,		// 128 LATIN CAPITAL LETTER C WITH CEDILLA  	252,		// 129 LATIN SMALL LETTER U WITH DIAERESIS  	233,		// 130 LATIN SMALL LETTER E WITH ACUTE diff --git a/engines/saga/interface.cpp b/engines/saga/interface.cpp index 7380570a99..1d048baaad 100644 --- a/engines/saga/interface.cpp +++ b/engines/saga/interface.cpp @@ -334,7 +334,21 @@ Interface::Interface(SagaEngine *vm) : _vm(vm) {  Interface::~Interface(void) {  	free(_inventory); +	free(_mainPanel.image); +	free(_conversePanel.image); +	free(_optionPanel.image); +	free(_quitPanel.image); +	free(_loadPanel.image); +	free(_savePanel.image); +  	_mainPanel.sprites.freeMem(); +	_conversePanel.sprites.freeMem(); +	_optionPanel.sprites.freeMem(); +	_quitPanel.sprites.freeMem(); +	_loadPanel.sprites.freeMem(); +	_savePanel.sprites.freeMem(); +	_protectPanel.sprites.freeMem(); +  	_defPortraits.freeMem();  	_scenePortraits.freeMem();  } diff --git a/engines/saga/introproc_ihnm.cpp b/engines/saga/introproc_ihnm.cpp index 5f1d0157d5..6614f4098f 100644 --- a/engines/saga/introproc_ihnm.cpp +++ b/engines/saga/introproc_ihnm.cpp @@ -132,6 +132,8 @@ void Scene::IHNMLoadCutaways() {  	// Load the cutaways for the title screens  	_vm->_anim->loadCutawayList(resourcePointer, resourceLength); + +	free(resourcePointer);  }  bool Scene::checkKey() { diff --git a/engines/saga/rscfile.cpp b/engines/saga/rscfile.cpp index b7d4f4f1bd..e150caeca5 100644 --- a/engines/saga/rscfile.cpp +++ b/engines/saga/rscfile.cpp @@ -769,6 +769,7 @@ void Resource::loadGlobalResources(int chapter, int actorsEntrance) {  	_vm->_sprite->_mainSprites.freeMem();  	_vm->_sprite->loadList(_metaResource.mainSpritesID, _vm->_sprite->_mainSprites); +  	_vm->_actor->loadObjList(_metaResource.objectCount, _metaResource.objectsResourceID);  	_vm->_resource->loadResource(resourceContext, _metaResource.cutawayListResourceID, resourcePointer, resourceLength); @@ -806,6 +807,7 @@ void Resource::loadGlobalResources(int chapter, int actorsEntrance) {  		// The IHNM demo has a fixed music track and doesn't load a song table  		_vm->_music->setVolume(_vm->_musicVolume == 10 ? -1 : _vm->_musicVolume * 25, 1);  		_vm->_music->play(3, MUSIC_LOOP); +		free(resourcePointer);  	}  	int voiceLUTResourceID = 0; diff --git a/engines/saga/saga.cpp b/engines/saga/saga.cpp index 40eb32b276..fafbd02cec 100644 --- a/engines/saga/saga.cpp +++ b/engines/saga/saga.cpp @@ -79,6 +79,7 @@ SagaEngine::SagaEngine(OSystem *syst, const SAGAGameDescription *gameDesc)  	_scene = NULL;  	_isoMap = NULL;  	_gfx = NULL; +	_driver = NULL;  	_console = NULL;  	_render = NULL;  	_music = NULL; @@ -133,6 +134,7 @@ SagaEngine::~SagaEngine() {  	delete _render;  	delete _music;  	delete _sound; +	delete _driver;  	delete _gfx;  	delete _console; @@ -188,11 +190,11 @@ int SagaEngine::init() {  	bool native_mt32 = ((midiDriver == MD_MT32) || ConfMan.getBool("native_mt32"));  	bool adlib = (midiDriver == MD_ADLIB); -	MidiDriver *driver = MidiDriver::createMidi(midiDriver); +	_driver = MidiDriver::createMidi(midiDriver);  	if (native_mt32) -		driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE); +		_driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE); -	_music = new Music(this, _mixer, driver, _musicVolume); +	_music = new Music(this, _mixer, _driver, _musicVolume);  	_music->setNativeMT32(native_mt32);  	_music->setAdlib(adlib); diff --git a/engines/saga/saga.h b/engines/saga/saga.h index 4a5fae7ddb..6b6eb6b3fb 100644 --- a/engines/saga/saga.h +++ b/engines/saga/saga.h @@ -29,6 +29,7 @@  #include "engines/engine.h"  #include "common/stream.h" +#include "sound/mididrv.h"  #include "saga/gfx.h"  #include "saga/list.h" @@ -531,6 +532,7 @@ public:  	SndRes *_sndRes;  	Sound *_sound;  	Music *_music; +	MidiDriver *_driver;  	Anim *_anim;  	Render *_render;  	IsoMap *_isoMap; diff --git a/engines/saga/script.cpp b/engines/saga/script.cpp index 7664af314f..088be34c72 100644 --- a/engines/saga/script.cpp +++ b/engines/saga/script.cpp @@ -150,6 +150,7 @@ Script::~Script() {  	debug(8, "Shutting down scripting subsystem.");  	_mainStrings.freeMem(); +	_globalVoiceLUT.freeMem();  	freeModules();  	free(_modules); diff --git a/engines/saga/sprite.cpp b/engines/saga/sprite.cpp index e9d002819c..be4f2a423d 100644 --- a/engines/saga/sprite.cpp +++ b/engines/saga/sprite.cpp @@ -74,6 +74,9 @@ Sprite::Sprite(SagaEngine *vm) : _vm(vm) {  Sprite::~Sprite(void) {  	debug(8, "Shutting down sprite subsystem...");  	_mainSprites.freeMem(); +	_inventorySprites.freeMem(); +	_arrowSprites.freeMem(); +	_saveReminderSprites.freeMem();  	free(_decodeBuf);  } diff --git a/engines/scumm/charset.cpp b/engines/scumm/charset.cpp index 8f3175f098..5a45fb7da9 100644 --- a/engines/scumm/charset.cpp +++ b/engines/scumm/charset.cpp @@ -56,7 +56,7 @@ void ScummEngine::loadCJKFont() {  		_2byteWidth = 16;  		_2byteHeight = 16;  		// use FM-TOWNS font rom, since game files don't have kanji font resources -		if (fp.open("fmt_fnt.rom", Common::File::kFileReadMode)) { +		if (fp.open("fmt_fnt.rom")) {  			_useCJKMode = true;  			debug(2, "Loading FM-TOWNS Kanji rom");  			_2byteFontPtr = new byte[((_2byteWidth + 7) / 8) * _2byteHeight * numChar]; diff --git a/engines/scumm/debugger.cpp b/engines/scumm/debugger.cpp index 9f9115e207..23af1f9672 100644 --- a/engines/scumm/debugger.cpp +++ b/engines/scumm/debugger.cpp @@ -298,7 +298,7 @@ bool ScummDebugger::Cmd_ImportRes(int argc, const char** argv) {  	// FIXME add bounds check  	if (!strncmp(argv[1], "scr", 3)) { -		file.open(argv[2], Common::File::kFileReadMode); +		file.open(argv[2]);  		if (file.isOpen() == false) {  			DebugPrintf("Could not open file %s\n", argv[2]);  			return true; diff --git a/engines/scumm/detection.cpp b/engines/scumm/detection.cpp index 9359c6610c..68d3010199 100644 --- a/engines/scumm/detection.cpp +++ b/engines/scumm/detection.cpp @@ -492,7 +492,7 @@ static bool testGame(const GameSettings *g, const DescMap &fileMD5Map, const Com  			// Note that GF_OLD_BUNDLE is true if and only if GF_OLD256 is false.  			// Candidates: maniac enhanced, zak enhanced, indy3ega, loom -			if (g->version != 2 && g->version != 3  || (g->features & GF_OLD256)) +			if ((g->version != 2 && g->version != 3)  || (g->features & GF_OLD256))  				return false;  			/* We distinguish the games by the presence/absence of @@ -949,7 +949,7 @@ SaveStateList ScummMetaEngine::listSaves(const char *target) const {  	sort(filenames.begin(), filenames.end());	// Sort (hopefully ensuring we are sorted numerically..)  	SaveStateList saveList; -	for (Common::StringList::const_iterator file = filenames.begin(); file != filenames.end(); file++) { +	for (Common::StringList::const_iterator file = filenames.begin(); file != filenames.end(); ++file) {  		// Obtain the last 2 digits of the filename, since they correspond to the save slot  		int slotNum = atoi(file->c_str() + file->size() - 2); diff --git a/engines/scumm/dialogs.cpp b/engines/scumm/dialogs.cpp index 6d1cf1bbd8..e4e2b2b620 100644 --- a/engines/scumm/dialogs.cpp +++ b/engines/scumm/dialogs.cpp @@ -297,7 +297,7 @@ void SaveLoadChooser::handleCommand(CommandSender *sender, uint32 cmd, uint32 da  		break;  	case GUI::kListSelectionChangedCmd: {  		if (_gfxWidget) { -			updateInfos(); +			updateInfos(true);  		}  		if (_saveMode) { @@ -350,7 +350,7 @@ void SaveLoadChooser::reflowLayout() {  		_fillR = g_gui.evaluator()->getVar("scummsaveload_thumbnail.fillR");  		_fillG = g_gui.evaluator()->getVar("scummsaveload_thumbnail.fillG");  		_fillB = g_gui.evaluator()->getVar("scummsaveload_thumbnail.fillB"); -		updateInfos(); +		updateInfos(false);  	} else {  		_container->setFlags(GUI::WIDGET_INVISIBLE);  		_gfxWidget->setFlags(GUI::WIDGET_INVISIBLE); @@ -362,7 +362,7 @@ void SaveLoadChooser::reflowLayout() {  	Dialog::reflowLayout();  } -void SaveLoadChooser::updateInfos() { +void SaveLoadChooser::updateInfos(bool redraw) {  	int selItem = _list->getSelected();  	Graphics::Surface *thumb;  	thumb = _vm->loadThumbnailFromSlot(_saveMode ? selItem + 1 : selItem); @@ -376,7 +376,8 @@ void SaveLoadChooser::updateInfos() {  	}  	delete thumb; -	_gfxWidget->draw(); +	if (redraw) +		_gfxWidget->draw();  	InfoStuff infos;  	memset(&infos, 0, sizeof(InfoStuff)); @@ -386,12 +387,14 @@ void SaveLoadChooser::updateInfos() {  			(infos.date >> 24) & 0xFF, (infos.date >> 16) & 0xFF,  			infos.date & 0xFFFF);  		_date->setLabel(buffer); -		_date->draw(); +		if (redraw) +			_date->draw();  		snprintf(buffer, 32, "Time: %.2d:%.2d",  			(infos.time >> 8) & 0xFF, infos.time & 0xFF);  		_time->setLabel(buffer); -		_time->draw(); +		if (redraw) +			_time->draw();  		int minutes = infos.playtime / 60;  		int hours = minutes / 60; @@ -400,19 +403,23 @@ void SaveLoadChooser::updateInfos() {  		snprintf(buffer, 32, "Playtime: %.2d:%.2d",  			hours & 0xFF, minutes & 0xFF);  		_playtime->setLabel(buffer); -		_playtime->draw(); +		if (redraw) +			_playtime->draw();  	} else {  		snprintf(buffer, 32, "No date saved");  		_date->setLabel(buffer); -		_date->draw(); +		if (redraw) +			_date->draw();  		snprintf(buffer, 32, "No time saved");  		_time->setLabel(buffer); -		_time->draw(); +		if (redraw) +			_time->draw();  		snprintf(buffer, 32, "No playtime saved");  		_playtime->setLabel(buffer); -		_playtime->draw(); +		if (redraw) +			_playtime->draw();  	}  } diff --git a/engines/scumm/dialogs.h b/engines/scumm/dialogs.h index 7c99a0ebcc..0d04d8faea 100644 --- a/engines/scumm/dialogs.h +++ b/engines/scumm/dialogs.h @@ -69,7 +69,7 @@ protected:  	uint8 _fillR, _fillG, _fillB; -	void updateInfos(); +	void updateInfos(bool redraw);  public:  	SaveLoadChooser(const String &title, const String &buttonLabel, bool saveMode, ScummEngine *engine);  	~SaveLoadChooser(); diff --git a/engines/scumm/file.cpp b/engines/scumm/file.cpp index bc5fc38225..bf13308a0c 100644 --- a/engines/scumm/file.cpp +++ b/engines/scumm/file.cpp @@ -58,8 +58,8 @@ void ScummFile::resetSubfile() {  	seek(0, SEEK_SET);  } -bool ScummFile::open(const Common::String &filename, AccessMode mode) { -	if (File::open(filename, mode)) { +bool ScummFile::open(const Common::String &filename) { +	if (File::open(filename)) {  		resetSubfile();  		return true;  	} else { @@ -187,11 +187,6 @@ uint32 ScummFile::read(void *dataPtr, uint32 dataSize) {  	return realLen;  } -uint32 ScummFile::write(const void *, uint32) { -	error("ScummFile does not support writing!"); -	return 0; -} -  #pragma mark -  #pragma mark --- ScummDiskImage ---  #pragma mark - @@ -250,11 +245,6 @@ ScummDiskImage::ScummDiskImage(const char *disk1, const char *disk2, GameSetting  	}  } -uint32 ScummDiskImage::write(const void *, uint32) { -	error("ScummDiskImage does not support writing!"); -	return 0; -} -  void ScummDiskImage::setEnc(byte enc) {  	_stream->setEnc(enc);  } @@ -300,7 +290,7 @@ bool ScummDiskImage::openDisk(char num) {  	return true;  } -bool ScummDiskImage::open(const Common::String &filename, AccessMode mode) { +bool ScummDiskImage::open(const Common::String &filename) {  	uint16 signature;  	// check signature diff --git a/engines/scumm/file.h b/engines/scumm/file.h index 7064654f89..a2695cac59 100644 --- a/engines/scumm/file.h +++ b/engines/scumm/file.h @@ -36,7 +36,7 @@ class BaseScummFile : public Common::File {  public:  	virtual void setEnc(byte value) = 0; -	virtual bool open(const Common::String &filename, AccessMode mode = kFileReadMode) = 0; +	virtual bool open(const Common::String &filename) = 0;  	virtual bool openSubFile(const Common::String &filename) = 0;  	virtual bool eof() = 0; @@ -44,7 +44,6 @@ public:  	virtual uint32 size() = 0;  	virtual void seek(int32 offs, int whence = SEEK_SET) = 0;  	virtual uint32 read(void *dataPtr, uint32 dataSize) = 0; -	virtual uint32 write(const void *dataPtr, uint32 dataSize) = 0;  };  class ScummFile : public BaseScummFile { @@ -59,7 +58,7 @@ public:  	void setSubfileRange(uint32 start, uint32 len);  	void resetSubfile(); -	bool open(const Common::String &filename, AccessMode mode = kFileReadMode); +	bool open(const Common::String &filename);  	bool openSubFile(const Common::String &filename);  	bool eof(); @@ -67,7 +66,6 @@ public:  	uint32 size();  	void seek(int32 offs, int whence = SEEK_SET);  	uint32 read(void *dataPtr, uint32 dataSize); -	uint32 write(const void *dataPtr, uint32 dataSize);  };  class ScummDiskImage : public BaseScummFile { @@ -104,7 +102,7 @@ public:  	ScummDiskImage(const char *disk1, const char *disk2, GameSettings game);  	void setEnc(byte value); -	bool open(const Common::String &filename, AccessMode mode = kFileReadMode); +	bool open(const Common::String &filename);  	bool openSubFile(const Common::String &filename);  	void close(); @@ -113,7 +111,6 @@ public:  	uint32 size() { return _stream->size(); }  	void seek(int32 offs, int whence = SEEK_SET) { _stream->seek(offs, whence); }  	uint32 read(void *dataPtr, uint32 dataSize) { return _stream->read(dataPtr, dataSize); } -	uint32 write(const void *dataPtr, uint32 dataSize);  };  } // End of namespace Scumm diff --git a/engines/scumm/file_nes.cpp b/engines/scumm/file_nes.cpp index 95f5eec4ea..8325436f87 100644 --- a/engines/scumm/file_nes.cpp +++ b/engines/scumm/file_nes.cpp @@ -62,11 +62,6 @@ struct ScummNESFile::Resource {  ScummNESFile::ScummNESFile() : _stream(0), _buf(0), _ROMset(kROMsetNum) {  } -uint32 ScummNESFile::write(const void *, uint32) { -	error("ScummNESFile does not support writing!"); -	return 0; -} -  void ScummNESFile::setEnc(byte enc) {  	_stream->setEnc(enc);  } @@ -1234,7 +1229,7 @@ bool ScummNESFile::generateIndex() {  	return true;  } -bool ScummNESFile::open(const Common::String &filename, AccessMode mode) { +bool ScummNESFile::open(const Common::String &filename) {  	if (_ROMset == kROMsetNum) {  		char md5str[32+1]; @@ -1267,9 +1262,8 @@ bool ScummNESFile::open(const Common::String &filename, AccessMode mode) {  		}  	} -	if (File::open(filename, mode)) { -		if (_stream) -			delete _stream; +	if (File::open(filename)) { +		delete _stream;  		_stream = 0;  		free(_buf); @@ -1282,8 +1276,7 @@ bool ScummNESFile::open(const Common::String &filename, AccessMode mode) {  }  void ScummNESFile::close() { -	if (_stream) -		delete _stream; +	delete _stream;  	_stream = 0;  	free(_buf); diff --git a/engines/scumm/file_nes.h b/engines/scumm/file_nes.h index d601c2c496..4d2d6de275 100644 --- a/engines/scumm/file_nes.h +++ b/engines/scumm/file_nes.h @@ -64,7 +64,7 @@ public:  	ScummNESFile();  	void setEnc(byte value); -	bool open(const Common::String &filename, AccessMode mode = kFileReadMode); +	bool open(const Common::String &filename);  	bool openSubFile(const Common::String &filename);  	void close(); @@ -73,7 +73,6 @@ public:  	uint32 size() { return _stream->size(); }  	void seek(int32 offs, int whence = SEEK_SET) { _stream->seek(offs, whence); }  	uint32 read(void *dataPtr, uint32 dataSize) { return _stream->read(dataPtr, dataSize); } -	uint32 write(const void *dataPtr, uint32 dataSize);  };  } // End of namespace Scumm diff --git a/engines/scumm/gfxARM.s b/engines/scumm/gfxARM.s index cd3e5c7dad..83aaa78927 100644 --- a/engines/scumm/gfxARM.s +++ b/engines/scumm/gfxARM.s @@ -59,7 +59,7 @@ asmDrawStripToScreen:  	CMP	r1,#4			@ If width<4  	BLT	end			@    return -	@ Width &= ~4 ? What's that about then? Width &= ~3 I could have +	@ Width &= ~4 ? What''s that about then? Width &= ~3 I could have  	@ understood...  	BIC	r1,r1,#4 diff --git a/engines/scumm/he/resource_he.cpp b/engines/scumm/he/resource_he.cpp index 33e6748860..f8fb1efca2 100644 --- a/engines/scumm/he/resource_he.cpp +++ b/engines/scumm/he/resource_he.cpp @@ -522,12 +522,13 @@ int Win32ResExtractor::do_resources_recurs(WinLibrary *fi, WinResource *base,  	/* get a list of all resources at this level */  	wr = list_resources(fi, base, &rescnt); -	if (wr == NULL) +	if (wr == NULL) {  		if (size != 0)  			return size;  		else  			return 0; - +	} +	  	/* process each resource listed */  	for (c = 0 ; c < rescnt ; c++) {  		/* (over)write the corresponding WinResource holder with the current */ diff --git a/engines/scumm/he/script_v60he.cpp b/engines/scumm/he/script_v60he.cpp index 4d5ec668a0..9429f8d086 100644 --- a/engines/scumm/he/script_v60he.cpp +++ b/engines/scumm/he/script_v60he.cpp @@ -1012,7 +1012,7 @@ void ScummEngine_v60he::o60_openFile() {  			_hInFileTable[slot] = _saveFileMan->openForLoading(filename);  			if (_hInFileTable[slot] == 0) {  				Common::File *f = new Common::File(); -				f->open(filename, Common::File::kFileReadMode); +				f->open(filename);  				if (!f->isOpen())  					delete f;  				else diff --git a/engines/scumm/he/script_v72he.cpp b/engines/scumm/he/script_v72he.cpp index df3d857642..6c3d0023d8 100644 --- a/engines/scumm/he/script_v72he.cpp +++ b/engines/scumm/he/script_v72he.cpp @@ -1692,7 +1692,7 @@ void ScummEngine_v72he::o72_openFile() {  			_hInFileTable[slot] = _saveFileMan->openForLoading(filename);  			if (_hInFileTable[slot] == 0) {  				Common::File *f = new Common::File(); -				f->open(filename, Common::File::kFileReadMode); +				f->open(filename);  				if (!f->isOpen())  					delete f;  				else diff --git a/engines/scumm/he/script_v80he.cpp b/engines/scumm/he/script_v80he.cpp index 393e1d3a8f..39ec715d94 100644 --- a/engines/scumm/he/script_v80he.cpp +++ b/engines/scumm/he/script_v80he.cpp @@ -409,7 +409,7 @@ void ScummEngine_v80he::o80_getFileSize() {  	Common::SeekableReadStream *f = _saveFileMan->openForLoading((const char *)filename);  	if (!f) {  		Common::File *file = new Common::File(); -		file->open((const char *)filename, Common::File::kFileReadMode); +		file->open((const char *)filename);  		if (!file->isOpen())  			delete f;  		else diff --git a/engines/scumm/he/wiz_he.cpp b/engines/scumm/he/wiz_he.cpp index df472307eb..f514449bff 100644 --- a/engines/scumm/he/wiz_he.cpp +++ b/engines/scumm/he/wiz_he.cpp @@ -1881,7 +1881,7 @@ void Wiz::processWizImage(const WizParameters *params) {  			memcpy(filename, params->filename, 260);  			_vm->convertFilePath(filename); -			if (f.open((const char *)filename, Common::File::kFileReadMode)) { +			if (f.open((const char *)filename)) {  				uint32 id = f.readUint32BE();  				if (id == MKID_BE('AWIZ') || id == MKID_BE('MULT')) {  					uint32 size = f.readUint32BE(); @@ -1911,7 +1911,7 @@ void Wiz::processWizImage(const WizParameters *params) {  		break;  	case 4:  		if (params->processFlags & kWPFUseFile) { -			Common::File f; +			Common::DumpFile f;  			switch (params->fileWriteMode) {  			case 2: @@ -1924,7 +1924,7 @@ void Wiz::processWizImage(const WizParameters *params) {  				memcpy(filename, params->filename, 260);  				_vm->convertFilePath(filename); -				if (!f.open((const char *)filename, Common::File::kFileWriteMode)) { +				if (!f.open((const char *)filename)) {  					debug(0, "Unable to open for write '%s'", filename);  					_vm->VAR(119) = -3;  				} else { diff --git a/engines/scumm/imuse_digi/dimuse.cpp b/engines/scumm/imuse_digi/dimuse.cpp index fa50eca604..d3359fa33e 100644 --- a/engines/scumm/imuse_digi/dimuse.cpp +++ b/engines/scumm/imuse_digi/dimuse.cpp @@ -57,8 +57,8 @@ IMuseDigital::IMuseDigital(ScummEngine_v7 *scumm, Audio::Mixer *mixer, int fps)  	for (int l = 0; l < MAX_DIGITAL_TRACKS + MAX_DIGITAL_FADETRACKS; l++) {  		_track[l] = new Track;  		assert(_track[l]); +		memset(_track[l], 0, sizeof(Track));  		_track[l]->trackId = l; -		_track[l]->used = false;  	}  	_vm->_timer->installTimerProc(timer_handler, 1000000 / _callbackFps, this); diff --git a/engines/scumm/imuse_digi/dimuse_sndmgr.cpp b/engines/scumm/imuse_digi/dimuse_sndmgr.cpp index 1511b9aefc..b18b0ba70f 100644 --- a/engines/scumm/imuse_digi/dimuse_sndmgr.cpp +++ b/engines/scumm/imuse_digi/dimuse_sndmgr.cpp @@ -102,10 +102,10 @@ void ImuseDigiSndMgr::prepareSoundFromRMAP(Common::File *file, SoundDesc *sound,  	int32 version = file->readUint32BE();  	if (version != 3) {  		if (version == 2) { -			warning("ImuseDigiSndMgr::prepareSoundFromRMAP: Wrong version of compressed *.bun file, expected 3, but it's 2."); -			warning("Suggested to recompress with latest tool from daily builds."); +			warning("ImuseDigiSndMgr::prepareSoundFromRMAP: Wrong version of compressed *.bun file, expected 3, but it's 2"); +			warning("Suggested to recompress with latest tool from daily builds");  		} else -			error("ImuseDigiSndMgr::prepareSoundFromRMAP: Wrong version number, expected 3, but it's: %d.", version); +			error("ImuseDigiSndMgr::prepareSoundFromRMAP: Wrong version number, expected 3, but it's: %d", version);  	}  	sound->bits = file->readUint32BE();  	sound->freq = file->readUint32BE(); diff --git a/engines/scumm/imuse_digi/dimuse_track.h b/engines/scumm/imuse_digi/dimuse_track.h index 33147128cb..2d4c673cf6 100644 --- a/engines/scumm/imuse_digi/dimuse_track.h +++ b/engines/scumm/imuse_digi/dimuse_track.h @@ -85,13 +85,15 @@ struct Track {  	int getPan() const { return (pan != 64) ? 2 * pan - 127 : 0; }  	int getVol() const { return vol / 1000; }  	Audio::Mixer::SoundType getType() const { -		Audio::Mixer::SoundType type = Audio::Mixer::kPlainSoundType; +		Audio::Mixer::SoundType type;  		if (volGroupId == 1)  			type = Audio::Mixer::kSpeechSoundType;  		else if (volGroupId == 2)  			type = Audio::Mixer::kSFXSoundType;  		else if (volGroupId == 3)  			type = Audio::Mixer::kMusicSoundType; +		else +			error("Track::getType(): invalid sound type");  		return type;  	}  }; diff --git a/engines/scumm/resource.cpp b/engines/scumm/resource.cpp index acdc2bc222..6bd62c1761 100644 --- a/engines/scumm/resource.cpp +++ b/engines/scumm/resource.cpp @@ -1299,7 +1299,7 @@ void ScummEngine::allocateArrays() {  void ScummEngine::dumpResource(const char *tag, int idx, const byte *ptr, int length) {  	char buf[256]; -	Common::File out; +	Common::DumpFile out;  	uint32 size;  	if (length >= 0) @@ -1313,7 +1313,7 @@ void ScummEngine::dumpResource(const char *tag, int idx, const byte *ptr, int le  	sprintf(buf, "dumps/%s%d.dmp", tag, idx); -	out.open(buf, Common::File::kFileWriteMode); +	out.open(buf);  	if (out.isOpen() == false)  		return;  	out.write(ptr, size); diff --git a/engines/scumm/saveload.cpp b/engines/scumm/saveload.cpp index 36b82519e9..f9e4eb415c 100644 --- a/engines/scumm/saveload.cpp +++ b/engines/scumm/saveload.cpp @@ -411,15 +411,15 @@ void ScummEngine::listSavegames(bool *marks, int num) {  	char prefix[256];  	char slot[3];  	int slotNum; -	Common::StringList filenames; +	Common::StringList files;  	makeSavegameName(prefix, 99, false);  	prefix[strlen(prefix)-2] = '*';  	prefix[strlen(prefix)-1] = 0;  	memset(marks, false, num * sizeof(bool));	//assume no savegames for this title -	filenames = _saveFileMan->listSavefiles(prefix); +	files = _saveFileMan->listSavefiles(prefix); -	for (Common::StringList::const_iterator file = filenames.begin(); file != filenames.end(); file++){ +	for (Common::StringList::const_iterator file = files.begin(); file != files.end(); ++file) {  		//Obtain the last 2 digits of the filename, since they correspond to the save slot  		slot[0] = file->c_str()[file->size()-2];  		slot[1] = file->c_str()[file->size()-1]; diff --git a/engines/scumm/scumm-md5.h b/engines/scumm/scumm-md5.h index 62d777aa33..ce8f0a4d9a 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 Mon Jun 02 08:37:50 2008 +  This file was generated by the md5table tool on Mon Jul 28 00:13:01 2008    DO NOT EDIT MANUALLY!   */ @@ -75,7 +75,7 @@ static const MD5Table md5table[] = {  	{ "16effd200aa6b8abe9c569c3e578814d", "freddi4", "HE 99", "Demo", -1, Common::NL_NLD, Common::kPlatformWindows },  	{ "179879b6e35c1ead0d93aab26db0951b", "fbear", "HE 70", "", 13381, Common::EN_ANY, Common::kPlatformWindows },  	{ "17b5d5e6af4ae89d62631641d66d5a05", "indy3", "VGA", "VGA", -1, Common::IT_ITA, Common::kPlatformPC }, -	{ "17f7296f63c78642724f057fd8e736a7", "maniac", "NES", "extracted", -1, Common::EN_USA, Common::kPlatformNES }, +	{ "17f7296f63c78642724f057fd8e736a7", "maniac", "NES", "extracted", -1, Common::EN_GRB, Common::kPlatformNES },  	{ "17fa250eb72dae2dad511ba79c0b6b0a", "tentacle", "", "Demo", -1, Common::FR_FRA, Common::kPlatformPC },  	{ "182344899c2e2998fca0bebcd82aa81a", "atlantis", "", "CD", 12035, Common::EN_ANY, Common::kPlatformPC },  	{ "183d7464902d40d00800e8ee1f04117c", "maniac", "V2", "V2", 1988, Common::DE_DEU, Common::kPlatformPC }, @@ -149,7 +149,7 @@ static const MD5Table md5table[] = {  	{ "37ff1b308999c4cca7319edfcc1280a0", "puttputt", "HE 70", "Demo", 8269, Common::EN_ANY, Common::kPlatformWindows },  	{ "3824e60cdf639d22f6df92a03dc4b131", "fbear", "HE 61", "", 7732, Common::EN_ANY, Common::kPlatformPC },  	{ "387a544b8b10b26912d8413bab63a853", "monkey2", "", "Demo", -1, Common::EN_ANY, Common::kPlatformPC }, -	{ "3905799e081b80a61d4460b7b733c206", "maniac", "NES", "", 262144, Common::EN_GRB, Common::kPlatformNES }, +	{ "3905799e081b80a61d4460b7b733c206", "maniac", "NES", "", 262144, Common::EN_USA, Common::kPlatformNES },  	{ "3938ee1aa4433fca9d9308c9891172b1", "zak", "FM-TOWNS", "Demo", -1, Common::EN_ANY, Common::kPlatformFMTowns },  	{ "399b217b0c8d65d0398076da486363a9", "indy3", "VGA", "VGA", 6295, Common::DE_DEU, Common::kPlatformPC },  	{ "39cb9dec16fa16f38d79acd80effb059", "loom", "EGA", "EGA", -1, Common::FR_FRA, Common::kPlatformAmiga }, @@ -357,7 +357,7 @@ static const MD5Table md5table[] = {  	{ "90e2f0af4f779629695c6394a65bb702", "spyfox2", "", "", -1, Common::FR_FRA, Common::kPlatformUnknown },  	{ "910e31cffb28226bd68c569668a0d6b4", "monkey", "EGA", "EGA", -1, Common::ES_ESP, Common::kPlatformPC },  	{ "91469353f7be1b122fa88d23480a1320", "zak", "V2", "V2", -1, Common::FR_FRA, Common::kPlatformAmiga }, -	{ "91d5db93187fab54d823f73bd6441cb6", "maniac", "NES", "extracted", -1, Common::EN_GRB, Common::kPlatformNES }, +	{ "91d5db93187fab54d823f73bd6441cb6", "maniac", "NES", "extracted", -1, Common::EN_USA, Common::kPlatformNES },  	{ "927a764615c7fcdd72f591355e089d8c", "monkey", "No Adlib", "EGA", -1, Common::DE_DEU, Common::kPlatformAtariST },  	{ "92b078d9d6d9d751da9c26b8b3075779", "tentacle", "", "Floppy", -1, Common::FR_FRA, Common::kPlatformPC },  	{ "92e7727e67f5cd979d8a1070e4eb8cb3", "puttzoo", "HE 98.5", "Updated", -1, Common::EN_ANY, Common::kPlatformUnknown }, @@ -503,7 +503,7 @@ static const MD5Table md5table[] = {  	{ "d7b247c26bf1f01f8f7daf142be84de3", "balloon", "HE 99", "Updated", -1, Common::EN_ANY, Common::kPlatformWindows },  	{ "d831f7c048574dd9d5d85db2a1468099", "maniac", "C64", "", -1, Common::EN_ANY, Common::kPlatformC64 },  	{ "d8323015ecb8b10bf53474f6e6b0ae33", "dig", "", "", 16304, Common::UNK_LANG, Common::kPlatformUnknown }, -	{ "d8d07efcb88f396bee0b402b10c3b1c9", "maniac", "NES", "", 262144, Common::EN_USA, Common::kPlatformNES }, +	{ "d8d07efcb88f396bee0b402b10c3b1c9", "maniac", "NES", "", 262144, Common::EN_GRB, Common::kPlatformNES },  	{ "d917f311a448e3cc7239c31bddb00dd2", "samnmax", "", "CD", 9080, Common::EN_ANY, Common::kPlatformUnknown },  	{ "d9d0dd93d16ab4dec55cabc2b86bbd17", "samnmax", "", "Demo", 6478, Common::EN_ANY, Common::kPlatformPC },  	{ "da09e666fc8f5b78d7b0ac65d1a3b56e", "monkey2", "", "", 11135, Common::EN_ANY, Common::kPlatformFMTowns }, diff --git a/engines/scumm/smush/codec47ARM.s b/engines/scumm/smush/codec47ARM.s index 81bfdb2d22..73341c117f 100644 --- a/engines/scumm/smush/codec47ARM.s +++ b/engines/scumm/smush/codec47ARM.s @@ -80,7 +80,7 @@ level1codeFD:  	LDRB	r9,[r8,#384]		@ r9 = l = tmp_ptr[384]  	LDRB	r6,[r1],#1		@ r6 = val = *_d_src++  	ADD	r12,r8,#384		@ r12= &tmp_ptr[384] -	@ I don't really believe the next 2 lines are necessary, but... +	@ I don''t really believe the next 2 lines are necessary, but...  	CMP	r9,#0  	BEQ	level1codeFD_over1  level1codeFD_loop1: @@ -94,7 +94,7 @@ level1codeFD_over1:  	LDRB	r9,[r12,#1]		@ r9 = l = tmp_ptr[385]  	LDRB	r6,[r1],#1		@ r6 = val = *_d_src++  	SUB	r12,r12,#256		@ r12= &tmp_ptr[128] (256 = 384-128) -	@ I don't really believe the next 2 lines are necessary, but... +	@ I don''t really believe the next 2 lines are necessary, but...  	CMP	r9,#0  	BEQ	level1codeFD_over2  level1codeFD_loop2: @@ -219,7 +219,7 @@ level2codeFD:  	LDRB	r9,[r8,#96]		@ r9 = l = tmp_ptr[96]  	LDRB	r6,[r1],#1		@ r6 = val = *_d_src++  	ADD	r12,r8,#32		@ r12 = tmp_ptr + 32 -	@ I don't really believe the next 2 lines are necessary, but... +	@ I don''t really believe the next 2 lines are necessary, but...  	CMP	r9,#0  	BEQ	level2codeFD_over1  level2codeFD_loop1: @@ -232,7 +232,7 @@ level2codeFD_loop1:  level2codeFD_over1:  	LDRB	r9,[r12,#65]		@ r9 = l = tmp_ptr[97] (65 = 97-32)  	LDRB	r6,[r1],#1		@ r6 = val = *_d_src++ -	@ I don't really believe the next 2 lines are necessary, but... +	@ I don''t really believe the next 2 lines are necessary, but...  	CMP	r9,#0  	MOVEQ	PC,R14  level2codeFD_loop2: diff --git a/engines/scumm/sound.cpp b/engines/scumm/sound.cpp index fdd0598378..7500b16c87 100644 --- a/engines/scumm/sound.cpp +++ b/engines/scumm/sound.cpp @@ -89,6 +89,7 @@ Sound::Sound(ScummEngine *parent, Audio::Mixer *mixer)  Sound::~Sound() {  	stopCDTimer(); +	AudioCD.destroy();  	delete _sfxFile;  } diff --git a/engines/sky/disk.cpp b/engines/sky/disk.cpp index a2f7d57cb0..a30276f8be 100644 --- a/engines/sky/disk.cpp +++ b/engines/sky/disk.cpp @@ -326,14 +326,14 @@ void Disk::fnFlushBuffers(void) {  void Disk::dumpFile(uint16 fileNr) {  	char buf[128]; -	Common::File out; +	Common::DumpFile out;  	byte* filePtr;  	filePtr = loadFile(fileNr);  	sprintf(buf, "dumps/file-%d.dmp", fileNr);  	if (!Common::File::exists(buf)) { -		if (out.open(buf, Common::File::kFileWriteMode)) +		if (out.open(buf))  			out.write(filePtr, _lastLoadedFileSize);  	}  	free(filePtr); diff --git a/engines/sky/music/adlibmusic.cpp b/engines/sky/music/adlibmusic.cpp index 7c2b262d82..4434f4cd68 100644 --- a/engines/sky/music/adlibmusic.cpp +++ b/engines/sky/music/adlibmusic.cpp @@ -47,6 +47,7 @@ AdlibMusic::AdlibMusic(Audio::Mixer *pMixer, Disk *pDisk)  AdlibMusic::~AdlibMusic(void) { +	OPLDestroy(_opl);  	_mixer->stopHandle(_soundHandle);  } diff --git a/engines/sword1/resman.cpp b/engines/sword1/resman.cpp index d54e290b09..adb84eee83 100644 --- a/engines/sword1/resman.cpp +++ b/engines/sword1/resman.cpp @@ -212,8 +212,8 @@ void *ResMan::openFetchRes(uint32 id) {  void ResMan::dumpRes(uint32 id) {  	char outn[30];  	sprintf(outn, "DUMP%08X.BIN", id); -	Common::File outf; -	if (outf.open(outn, Common::File::kFileWriteMode)) { +	Common::DumpFile outf; +	if (outf.open(outn)) {  		resOpen(id);  		MemHandle *memHandle = resHandle(id);  		outf.write(memHandle->data, memHandle->size); diff --git a/engines/sword2/music.cpp b/engines/sword2/music.cpp index fd72ba8d52..3b5a09578b 100644 --- a/engines/sword2/music.cpp +++ b/engines/sword2/music.cpp @@ -52,9 +52,11 @@ namespace Sword2 {  static Audio::AudioStream *makeCLUStream(Common::File *fp, int size);  static Audio::AudioStream *getAudioStream(SoundFileHandle *fh, const char *base, int cd, uint32 id, uint32 *numSamples) { -	debug(3, "Playing %s from CD %d", base, cd); +	bool alreadyOpen;  	if (!fh->file.isOpen()) { +		alreadyOpen = false; +  		struct {  			const char *ext;  			int mode; @@ -75,16 +77,14 @@ static Audio::AudioStream *getAudioStream(SoundFileHandle *fh, const char *base,  		char filename[20];  		for (int i = 0; i < ARRAYSIZE(file_types); i++) { -			Common::File f; -  			sprintf(filename, "%s%d.%s", base, cd, file_types[i].ext); -			if (f.open(filename)) { +			if (Common::File::exists(filename)) {  				soundMode = file_types[i].mode;  				break;  			}  			sprintf(filename, "%s.%s", base, file_types[i].ext); -			if (f.open(filename)) { +			if (Common::File::exists(filename)) {  				soundMode = file_types[i].mode;  				break;  			} @@ -105,7 +105,8 @@ static Audio::AudioStream *getAudioStream(SoundFileHandle *fh, const char *base,  				fh->idxTab = NULL;  			}  		} -	} +	} else +		alreadyOpen = true;  	uint32 entrySize = (fh->fileType == kCLUMode) ? 2 : 3; @@ -134,7 +135,13 @@ static Audio::AudioStream *getAudioStream(SoundFileHandle *fh, const char *base,  		*numSamples = len;  	if (!pos || !len) { -		fh->file.close(); +		// We couldn't find the sound. Possibly as a result of a bad +		// installation (e.g. using the music file from CD 2 as the +		// first music file). Don't close the file if it was already +		// open though, because something is playing from it. +		warning("getAudioStream: Could not find %s ID %d! Possibly the wrong file", base, id); +		if (!alreadyOpen) +			fh->file.close();  		return NULL;  	} diff --git a/engines/sword2/resman.cpp b/engines/sword2/resman.cpp index d6b8025cda..8cddddff78 100644 --- a/engines/sword2/resman.cpp +++ b/engines/sword2/resman.cpp @@ -234,7 +234,6 @@ bool ResourceManager::init() {  /**   * Returns the address of a resource. Loads if not in memory. Retains a count.   */ -  byte *ResourceManager::openResource(uint32 res, bool dump) {  	assert(res < _totalResFiles); @@ -287,7 +286,6 @@ byte *ResourceManager::openResource(uint32 res, bool dump) {  		if (dump) {  			char buf[256];  			const char *tag; -			Common::File out;  			switch (fetchType(_resList[res].ptr)) {  			case ANIMATION_FILE: @@ -337,7 +335,8 @@ byte *ResourceManager::openResource(uint32 res, bool dump) {  			sprintf(buf, "dumps/%s-%d.dmp", tag, res);  			if (!Common::File::exists(buf)) { -				if (out.open(buf, Common::File::kFileWriteMode)) +				Common::DumpFile out; +				if (out.open(buf))  					out.write(_resList[res].ptr, len);  			}  		} diff --git a/engines/sword2/sound.h b/engines/sword2/sound.h index 70bae6f851..b89ef8f12b 100644 --- a/engines/sword2/sound.h +++ b/engines/sword2/sound.h @@ -106,7 +106,7 @@ private:  	void refill();  	inline bool eosIntern() const { -		return _pos >= _bufferEnd; +		return !_file->isOpen() || _pos >= _bufferEnd;  	}  public: diff --git a/engines/tinsel/actors.cpp b/engines/tinsel/actors.cpp new file mode 100644 index 0000000000..c2f01added --- /dev/null +++ b/engines/tinsel/actors.cpp @@ -0,0 +1,897 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Handles things to do with actors, delegates much moving actor stuff. + */ + +#include "tinsel/actors.h" +#include "tinsel/events.h" +#include "tinsel/film.h"	// for FREEL +#include "tinsel/handle.h" +#include "tinsel/inventory.h"	// INV_NOICON +#include "tinsel/move.h" +#include "tinsel/multiobj.h" +#include "tinsel/object.h"	// for POBJECT +#include "tinsel/pcode.h" +#include "tinsel/pid.h" +#include "tinsel/polygons.h" +#include "tinsel/rince.h" +#include "tinsel/sched.h" +#include "tinsel/serializer.h" +#include "tinsel/token.h" + +#include "common/util.h" + +namespace Tinsel { + + +//----------------- LOCAL DEFINES -------------------- + + +#include "common/pack-start.h"	// START STRUCT PACKING + +/** actor struct - one per actor */ +struct ACTOR_STRUC { +	int32 masking;			//!< type of actor masking +	SCNHANDLE hActorId;		//!< handle actor ID string index +	SCNHANDLE hActorCode;	//!< handle to actor script +} PACKED_STRUCT; + +#include "common/pack-end.h"	// END STRUCT PACKING + + + +//----------------- LOCAL GLOBAL DATA -------------------- + +static int LeadActorId = 0;		// The lead actor + +static int NumActors = 0;	// The total number of actors in the game + +struct ACTORINFO { +	bool		alive;		// TRUE == alive +	bool		hidden;		// TRUE == hidden +	bool		completed;	// TRUE == script played out + +	int			x, y, z; + +	int32		mtype;		// DEFAULT(b'ground), MASK, ALWAYS +	SCNHANDLE	actorCode;	// The actor's script + +	const FREEL	*presReel;	// the present reel +	int			presRnum;	// the present reel number +	SCNHANDLE 	presFilm;	// the film that reel belongs to +	OBJECT		*presObj;	// reference for position information +	int			presX, presY; + +	bool		tagged;		// actor tagged? +	SCNHANDLE	hTag;		// handle to tag text +	int			tType;		// e.g. TAG_Q1TO3 + +	bool		escOn; +	int			escEv; + +	COLORREF	tColour;	// Text colour + +	SCNHANDLE 	playFilm;	// revert to this after talks +	SCNHANDLE 	talkFilm;	// this be deleted in the future! +	SCNHANDLE 	latestFilm;	// the last film ordered +	bool		talking; + +	int			steps; + +}; + +static ACTORINFO *actorInfo = 0; + +static COLORREF defaultColour = 0;		// Text colour + +static bool bActorsOn = false; + +static int ti = 0; + +/** + * Called once at start-up time, and again at restart time. + * Registers the total number of actors in the game. + * @param num			Chunk Id + */ +void RegisterActors(int num) { +	if (actorInfo == NULL) 	{ +		// Store the total number of actors in the game +		NumActors = num; + +		// Check we can save so many +		assert(NumActors <= MAX_SAVED_ALIVES); + +		// Allocate RAM for actorInfo +		// FIXME: For now, we always allocate MAX_SAVED_ALIVES blocks, +		//   as this makes the save/load code simpler +		actorInfo = (ACTORINFO *)calloc(MAX_SAVED_ALIVES, sizeof(ACTORINFO)); + +		// make sure memory allocated +		if (actorInfo == NULL) { +			error("Cannot allocate memory for actors"); +		} +	} else { +		// Check the total number of actors is still the same +		assert(num == NumActors); + +		memset(actorInfo, 0, MAX_SAVED_ALIVES * sizeof(ACTORINFO)); +	} + +	// All actors start off alive. +	while (num--) +		actorInfo[num].alive = true; +} + +void FreeActors() { +	if (actorInfo) { +		free(actorInfo); +		actorInfo = NULL; +	} +} + +/** + * Called from dec_lead(), i.e. normally once at start of master script. + * @param leadID			Lead Id + */ +void setleadid(int leadID) { +	LeadActorId = leadID; +	actorInfo[leadID-1].mtype = ACT_MASK; +} + +/** + * No comment. + */ +int LeadId(void) { +	return LeadActorId; +} + +struct ATP_INIT { +	int		id;		// Actor number +	USER_EVENT	event;		// Event +	BUTEVENT	bev;		// Causal mouse event +}; + +/** + * Runs actor's glitter code. + */ +static void ActorTinselProcess(CORO_PARAM, const void *param) { +	// COROUTINE +	CORO_BEGIN_CONTEXT; +		INT_CONTEXT *pic; +	CORO_END_CONTEXT(_ctx); + +	// get the stuff copied to process when it was created +	ATP_INIT *atp = (ATP_INIT *)param; + +	CORO_BEGIN_CODE(_ctx); + +	CORO_INVOKE_1(AllowDclick, atp->bev);		// May kill us if single click + +	// Run the Glitter code +	assert(actorInfo[atp->id - 1].actorCode); // no code to run + +	_ctx->pic = InitInterpretContext(GS_ACTOR, actorInfo[atp->id - 1].actorCode, atp->event, NOPOLY, atp->id, NULL); +	CORO_INVOKE_1(Interpret, _ctx->pic); + +	// If it gets here, actor's code has run to completion +	actorInfo[atp->id - 1].completed = true; + +	CORO_END_CODE; +} + + +//--------------------------------------------------------------------------- + +struct RATP_INIT { +	INT_CONTEXT *pic; +	int		id;		// Actor number +}; + +static void ActorRestoredProcess(CORO_PARAM, const void *param) { +	// COROUTINE +	CORO_BEGIN_CONTEXT; +		INT_CONTEXT *pic; +	CORO_END_CONTEXT(_ctx); + +	// get the stuff copied to process when it was created +	RATP_INIT *r = (RATP_INIT *)param; + +	CORO_BEGIN_CODE(_ctx); + +	_ctx->pic = RestoreInterpretContext(r->pic); +	CORO_INVOKE_1(Interpret, _ctx->pic); + +	// If it gets here, actor's code has run to completion +	actorInfo[r->id - 1].completed = true; + +	CORO_END_CODE; +} + +void RestoreActorProcess(int id, INT_CONTEXT *pic) { +	RATP_INIT r = { pic, id }; + +	g_scheduler->createProcess(PID_TCODE, ActorRestoredProcess, &r, sizeof(r)); +} + +/** + * Starts up process to runs actor's glitter code. + * @param ano			Actor Id + * @param event			Event structure + * @param be			ButEvent + */ +void actorEvent(int ano, USER_EVENT event, BUTEVENT be) { +	ATP_INIT atp; + +	// Only if there is Glitter code associated with this actor. +	if (actorInfo[ano - 1].actorCode) { +		atp.id = ano; +		atp.event = event; +		atp.bev = be; +		g_scheduler->createProcess(PID_TCODE, ActorTinselProcess, &atp, sizeof(atp)); +	} +} + +/** + * Called at the start of each scene for each actor with a code block. + * @param as			Actor structure + * @param bRunScript	Flag for whether to run actor's script for the scene + */ +void StartActor(const ACTOR_STRUC *as, bool bRunScript) { +	SCNHANDLE hActorId = FROM_LE_32(as->hActorId); + +	// Zero-out many things +	actorInfo[hActorId - 1].hidden = false; +	actorInfo[hActorId - 1].completed = false; +	actorInfo[hActorId - 1].x = 0; +	actorInfo[hActorId - 1].y = 0; +	actorInfo[hActorId - 1].presReel = NULL; +	actorInfo[hActorId - 1].presFilm = 0; +	actorInfo[hActorId - 1].presObj = NULL; + +	// Store current scene's parameters for this actor +	actorInfo[hActorId - 1].mtype = FROM_LE_32(as->masking); +	actorInfo[hActorId - 1].actorCode = FROM_LE_32(as->hActorCode); + +	// Run actor's script for this scene +	if (bRunScript) { +		if (bActorsOn) +			actorInfo[hActorId - 1].alive = true; + +		if (actorInfo[hActorId - 1].alive && FROM_LE_32(as->hActorCode)) +			actorEvent(hActorId, STARTUP, BE_NONE); +	} +} + +/** + * Called at the start of each scene. Start each actor with a code block. + * @param ah			Scene handle + * @param numActors		Number of actors + * @param bRunScript	Flag for whether to run actor scene scripts + */ +void StartActors(SCNHANDLE ah, int numActors, bool bRunScript) { +	int	i; + +	// Only actors with code blocks got (x, y) re-initialised, so... +	for (i = 0; i < NumActors; i++) { +		actorInfo[i].x = actorInfo[i].y = 0; +		actorInfo[i].mtype = 0; +	} + +	const ACTOR_STRUC *as = (const ACTOR_STRUC *)LockMem(ah); +	for (i = 0; i < numActors; i++, as++) { +		StartActor(as, bRunScript); +	} +} + +/** + * Called between scenes, zeroises all actors. + */ +void DropActors(void) { +	for (int i = 0; i < NumActors; i++) { +		actorInfo[i].actorCode = 0;	// No script +		actorInfo[i].presReel = NULL;	// No reel running +		actorInfo[i].presFilm = 0;	//   ditto +		actorInfo[i].presObj = NULL;	// No object +		actorInfo[i].x = 0;		// No position +		actorInfo[i].y = 0;		//   ditto + +		actorInfo[i].talkFilm = 0; +		actorInfo[i].latestFilm = 0; +		actorInfo[i].playFilm = 0; +		actorInfo[i].talking = false; +	} +} + +/** + * Kill actors. + * @param ano			Actor Id + */ +void DisableActor(int ano) { +	PMACTOR	pActor; + +	assert(ano > 0 && ano <= NumActors); // illegal actor number + +	actorInfo[ano - 1].alive = false;	// Record as dead +	actorInfo[ano - 1].x = actorInfo[ano - 1].y = 0; + +	// Kill off moving actor properly +	pActor = GetMover(ano); +	if (pActor) +		KillMActor(pActor); +} + +/** + * Enable actors. + * @param ano			Actor Id + */ +void EnableActor(int ano) { +	assert(ano > 0 && ano <= NumActors); // illegal actor number + +	// Re-incarnate only if it's dead, or it's script ran to completion +	if (!actorInfo[ano - 1].alive || actorInfo[ano - 1].completed) { +		actorInfo[ano - 1].alive = true; +		actorInfo[ano - 1].hidden = false; +		actorInfo[ano - 1].completed = false; + +		// Re-run actor's script for this scene +		if (actorInfo[ano-1].actorCode) +			actorEvent(ano, STARTUP, BE_NONE); +	} +} + +/** + * Returns the aliveness (to coin a word) of the actor. + * @param ano			Actor Id + */ +bool actorAlive(int ano) { +	assert(ano > 0 && ano <= NumActors); // illegal actor number + +	return actorInfo[ano - 1].alive; +} + +/** + * Define an actor as being tagged. + * @param ano			Actor Id + * @param tagtext		Scene handle + * @param tp			tType + */ +void Tag_Actor(int ano, SCNHANDLE tagtext, int tp) { +	assert(ano > 0 && ano <= NumActors); // illegal actor number + +	actorInfo[ano-1].tagged = true; +	actorInfo[ano-1].hTag = tagtext; +	actorInfo[ano-1].tType = tp; +} + +/** + * Undefine  an actor as being tagged. + * @param ano			Actor Id + * @param tagtext		Scene handle + * @param tp			tType + */ +void UnTagActor(int ano) { +	assert(ano > 0 && ano <= NumActors); // illegal actor number + +	actorInfo[ano-1].tagged = false; +} + +/** + * Redefine an actor as being tagged. + * @param ano			Actor Id + * @param tagtext		Scene handle + * @param tp			tType + */ +void ReTagActor(int ano) { +	assert(ano > 0 && ano <= NumActors); // illegal actor number + +	if (actorInfo[ano-1].hTag) +		actorInfo[ano-1].tagged = true; +} + +/** + * Returns a tagged actor's tag type. e.g. TAG_Q1TO3 + * @param ano			Actor Id + */ +int TagType(int ano) { +	assert(ano > 0 && ano <= NumActors); // illegal actor number + +	return actorInfo[ano-1].tType; +} + +/** + * Returns handle to tagged actor's tag text + * @param ano			Actor Id + */ +SCNHANDLE GetActorTag(int ano) { +	assert(ano > 0 && ano <= NumActors); // illegal actor number + +	return actorInfo[ano - 1].hTag; +} + +/** + * Called from TagProcess, FirstTaggedActor() resets the index, then + * NextTagged Actor is repeatedly called until the caller gets fed up + * or there are no more tagged actors to look at. + */ +void FirstTaggedActor(void) { +	ti = 0; +} + +/** + * Called from TagProcess, FirstTaggedActor() resets the index, then + * NextTagged Actor is repeatedly called until the caller gets fed up + * or there are no more tagged actors to look at. + */ +int NextTaggedActor(void) { +	PMACTOR	pActor; +	bool	hid; + +	do { +		if (actorInfo[ti].tagged) { +			pActor = GetMover(ti+1); +			if (pActor) +				hid = getMActorHideState(pActor); +			else +				hid = actorInfo[ti].hidden; + +			if (!hid) { +				return ++ti; +			} +		} +	} while (++ti < NumActors); + +	return 0; +} + +/** + * Returns the masking type of the actor. + * @param ano			Actor Id + */ +int32 actorMaskType(int ano) { +	assert(ano > 0 && ano <= NumActors); // illegal actor number + +	return actorInfo[ano - 1].mtype; +} + +/** + * Store/Return the currently stored co-ordinates of the actor. + * Delegate the task for moving actors. + * @param ano			Actor Id + * @param x				X position + * @param y				Y position + */ +void storeActorPos(int ano, int x, int y) { +	assert(ano > 0 && ano <= NumActors); // illegal actor number + +	actorInfo[ano - 1].x = x; +	actorInfo[ano - 1].y = y; +} + +void storeActorSteps(int ano, int steps) { +	assert(ano > 0 && ano <= NumActors); // illegal actor number + +	actorInfo[ano - 1].steps = steps; +} + +int getActorSteps(int ano) { +	assert(ano > 0 && ano <= NumActors); // illegal actor number + +	return actorInfo[ano - 1].steps; +} + +void storeActorZpos(int ano, int z) { +	assert(ano > 0 && ano <= NumActors); // illegal actor number + +	actorInfo[ano - 1].z = z; +} + + +void GetActorPos(int ano, int *x, int *y) { +	PMACTOR pActor; + +	assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // unknown actor + +	pActor = GetMover(ano); + +	if (pActor) +		GetMActorPosition(pActor, x, y); +	else { +		*x = actorInfo[ano - 1].x; +		*y = actorInfo[ano - 1].y; +	} +} + +/** + * Returns the position of the mid-top of the actor. + * Delegate the task for moving actors. + * @param ano			Actor Id + * @param x				Output x + * @param y				Output y + */ +void GetActorMidTop(int ano, int *x, int *y) { +	// Not used in JAPAN version +	PMACTOR pActor; + +	assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // unknown actor + +	pActor = GetMover(ano); + +	if (pActor) +		GetMActorMidTopPosition(pActor, x, y); +	else if (actorInfo[ano - 1].presObj) { +		*x = (MultiLeftmost(actorInfo[ano - 1].presObj) +		      + MultiRightmost(actorInfo[ano - 1].presObj)) / 2; +		*y = MultiHighest(actorInfo[ano - 1].presObj); +	} else +		GetActorPos(ano, x, y);		// The best we can do! +} + +/** + * Return the appropriate co-ordinate of the actor. + * @param ano			Actor Id + */ +int GetActorLeft(int ano) { +	assert(ano > 0 && ano <= NumActors); // illegal actor number + +	if (!actorInfo[ano - 1].presObj) +		return 0; + +	return MultiLeftmost(actorInfo[ano - 1].presObj); +} + +/** + * Return the appropriate co-ordinate of the actor. + * @param ano			Actor Id + */ +int GetActorRight(int ano) { +	assert(ano > 0 && ano <= NumActors); // illegal actor number + +	if (!actorInfo[ano - 1].presObj) +		return 0; + +	return MultiRightmost(actorInfo[ano - 1].presObj); +} + +/** + * Return the appropriate co-ordinate of the actor. + * @param ano			Actor Id + */ +int GetActorTop(int ano) { +	assert(ano > 0 && ano <= NumActors); // illegal actor number + +	if (!actorInfo[ano - 1].presObj) +		return 0; + +	return MultiHighest(actorInfo[ano - 1].presObj); +} + +/** + * Return the appropriate co-ordinate of the actor. + */ +int GetActorBottom(int ano) { +	assert(ano > 0 && ano <= NumActors); // illegal actor number + +	if (!actorInfo[ano - 1].presObj) +		return 0; + +	return MultiLowest(actorInfo[ano - 1].presObj); +} + +/** + * Set actor hidden status to true. + * For a moving actor, actually hide it. + * @param ano			Actor Id + */ +void HideActor(int ano) { +	PMACTOR pActor; + +	assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // illegal actor + +	// Get moving actor involved +	pActor = GetMover(ano); + +	if (pActor) +		hideMActor(pActor, 0); +	else +		actorInfo[ano - 1].hidden = true; +} + +/** + * Hide an actor if it's a moving actor. + * @param ano			Actor Id + * @param sf			sf + */ +bool HideMovingActor(int ano, int sf) { +	PMACTOR pActor; + +	assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // illegal actor + +	// Get moving actor involved +	pActor = GetMover(ano); + +	if (pActor) { +		hideMActor(pActor, sf); +		return true; +	} else { +		if (actorInfo[ano - 1].presObj != NULL) +			MultiHideObject(actorInfo[ano - 1].presObj);	// Hidee object +		return false; +	} +} + +/** + * Unhide an actor if it's a moving actor. + * @param ano			Actor Id + */ +void unHideMovingActor(int ano) { +	PMACTOR pActor; + +	assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // illegal actor + +	// Get moving actor involved +	pActor = GetMover(ano); + +	assert(pActor); // not a moving actor + +	unhideMActor(pActor); +} + +/** + * Called after a moving actor had been replaced by an splay(). + * Moves the actor to where the splay() left it, and continues the + * actor's walk (if any) from the new co-ordinates. + */ +void restoreMovement(int ano) { +	PMACTOR pActor; + +	assert(ano > 0 && ano <= NumActors); // illegal actor number + +	// Get moving actor involved +	pActor = GetMover(ano); + +	assert(pActor); // not a moving actor + +	if (pActor->objx == actorInfo[ano - 1].x && pActor->objy == actorInfo[ano - 1].y) +		return; + +	pActor->objx = actorInfo[ano - 1].x; +	pActor->objy = actorInfo[ano - 1].y; + +	if (pActor->actorObj) +		SSetActorDest(pActor); +} + +/** + * More properly should be called: + * 'store_actor_reel_and/or_film_and/or_object()' + */ +void storeActorReel(int ano, const FREEL *reel, SCNHANDLE film, OBJECT *pobj, int reelnum, int x, int y) { +	PMACTOR pActor; + +	assert(ano > 0 && ano <= NumActors); // illegal actor number + +	pActor = GetMover(ano); + +	// Only store the reel and film for a moving actor if NOT called from MActorProcess() +	// (MActorProcess() calls with reel=film=NULL, pobj not NULL) +	if (!pActor +	|| !(reel == NULL && film == 0 && pobj != NULL)) { +		actorInfo[ano - 1].presReel = reel;	// Store reel +		actorInfo[ano - 1].presRnum = reelnum;	// Store reel number +		actorInfo[ano - 1].presFilm = film;	// Store film +		actorInfo[ano - 1].presX = x; +		actorInfo[ano - 1].presY = y; +	} + +	// Only store the object for a moving actor if called from MActorProcess() +	if (!pActor) { +		actorInfo[ano - 1].presObj = pobj;	// Store object +	} else if (reel == NULL && film == 0 && pobj != NULL) { +		actorInfo[ano - 1].presObj = pobj;	// Store object +	} +} + +/** + * Return the present reel/film of the actor. + */ +const FREEL *actorReel(int ano) { +	assert(ano > 0 && ano <= NumActors); // illegal actor number + +	return actorInfo[ano - 1].presReel;	// the present reel +} + +/***************************************************************************/ + +void setActorPlayFilm(int ano, SCNHANDLE film) { +	assert(ano > 0 && ano <= NumActors); // illegal actor number + +	actorInfo[ano - 1].playFilm = film; +} + +SCNHANDLE getActorPlayFilm(int ano) { +	assert(ano > 0 && ano <= NumActors); // illegal actor number + +	return actorInfo[ano - 1].playFilm; +} + +void setActorTalkFilm(int ano, SCNHANDLE film) { +	assert(ano > 0 && ano <= NumActors); // illegal actor number + +	actorInfo[ano - 1].talkFilm = film; +} + +SCNHANDLE getActorTalkFilm(int ano) { +	assert(ano > 0 && ano <= NumActors); // illegal actor number + +	return actorInfo[ano - 1].talkFilm; +} + +void setActorTalking(int ano, bool tf) { +	assert(ano > 0 && ano <= NumActors); // illegal actor number + +	actorInfo[ano - 1].talking = tf;; +} + +bool isActorTalking(int ano) { +	assert(ano > 0 && ano <= NumActors); // illegal actor number + +	return actorInfo[ano - 1].talking; +} + +void setActorLatestFilm(int ano, SCNHANDLE film) { +	assert(ano > 0 && ano <= NumActors); // illegal actor number + +	actorInfo[ano - 1].latestFilm = film; +	actorInfo[ano - 1].steps = 0; +} + +SCNHANDLE getActorLatestFilm(int ano) { +	assert(ano > 0 && ano <= NumActors); // illegal actor number + +	return actorInfo[ano - 1].latestFilm; +} + +/***************************************************************************/ + +void updateActorEsc(int ano, bool escOn, int escEvent) { +	assert(ano > 0 && ano <= NumActors); // illegal actor number + +	actorInfo[ano - 1].escOn = escOn; +	actorInfo[ano - 1].escEv = escEvent; +} + +bool actorEsc(int ano) { +	assert(ano > 0 && ano <= NumActors); // illegal actor number + +	return actorInfo[ano - 1].escOn; +} + +int actorEev(int ano) { +	assert(ano > 0 && ano <= NumActors); // illegal actor number + +	return actorInfo[ano - 1].escEv; +} + +/** + * Guess what these do. + */ +int AsetZPos(OBJECT *pObj, int y, int32 z) { +	int zPos; + +	z += z ? -1 : 0; + +	zPos = y + (z << 10); +	MultiSetZPosition(pObj, zPos); +	return zPos; +} + +/** + * Guess what these do. + */ +void MAsetZPos(PMACTOR pActor, int y, int32 zFactor) { +	if (!pActor->aHidden) +		AsetZPos(pActor->actorObj, y, zFactor); +} + +/** + * Stores actor's attributes. + * Currently only the speech colours. + */ +void storeActorAttr(int ano, int r1, int g1, int b1) { +	assert((ano > 0 && ano <= NumActors) || ano == -1); // illegal actor number + +	if (r1 > MAX_INTENSITY)	r1 = MAX_INTENSITY;	// } Ensure +	if (g1 > MAX_INTENSITY)	g1 = MAX_INTENSITY;	// } within limits +	if (b1 > MAX_INTENSITY)	b1 = MAX_INTENSITY;	// } + +	if (ano == -1) +		defaultColour = RGB(r1, g1, b1); +	else +		actorInfo[ano - 1].tColour = RGB(r1, g1, b1); +} + +/** + * Get the actor's stored speech colour. + * @param ano			Actor Id + */ +COLORREF getActorTcol(int ano) { +	// Not used in JAPAN version +	assert(ano > 0 && ano <= NumActors); // illegal actor number + +	if (actorInfo[ano - 1].tColour) +		return actorInfo[ano - 1].tColour; +	else +		return defaultColour; +} + +/** + * Store relevant information pertaining to currently existing actors. + */ +int SaveActors(SAVED_ACTOR *sActorInfo) { +	int	i, j; + +	for (i = 0, j = 0; i < NumActors; i++) { +		if (actorInfo[i].presObj != NULL) { +			assert(j < MAX_SAVED_ACTORS); // Saving too many actors + +//			sActorInfo[j].hidden	= actorInfo[i].hidden; +			sActorInfo[j].bAlive	= actorInfo[i].alive; +//			sActorInfo[j].x		= (short)actorInfo[i].x; +//			sActorInfo[j].y		= (short)actorInfo[i].y; +			sActorInfo[j].z		= (short)actorInfo[i].z; +//			sActorInfo[j].presReel	= actorInfo[i].presReel; +			sActorInfo[j].presRnum	= (short)actorInfo[i].presRnum; +			sActorInfo[j].presFilm	= actorInfo[i].presFilm; +			sActorInfo[j].presX	= (short)actorInfo[i].presX; +			sActorInfo[j].presY	= (short)actorInfo[i].presY; +			sActorInfo[j].actorID	= (short)(i+1); +			j++; +		} +	} + +	return j; +} + +void setactorson(void) { +	bActorsOn = true; +} + +void ActorsLife(int ano, bool bAlive) { +	assert((ano > 0 && ano <= NumActors) || ano == -1); // illegal actor number + +	actorInfo[ano-1].alive = bAlive; +} + + +void syncAllActorsAlive(Serializer &s) { +	for (int i = 0; i < MAX_SAVED_ALIVES; i++) { +		s.syncAsByte(actorInfo[i].alive); +		s.syncAsByte(actorInfo[i].tagged); +		s.syncAsByte(actorInfo[i].tType); +		s.syncAsUint32LE(actorInfo[i].hTag); +	} +} + + +} // end of namespace Tinsel diff --git a/engines/tinsel/actors.h b/engines/tinsel/actors.h new file mode 100644 index 0000000000..91f54519d5 --- /dev/null +++ b/engines/tinsel/actors.h @@ -0,0 +1,125 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Prototypes of actor functions + */ + +#ifndef TINSEL_ACTOR_H	// prevent multiple includes +#define TINSEL_ACTOR_H + + +#include "tinsel/dw.h"		// for SCNHANDLE +#include "tinsel/events.h"	// for USER_EVENT +#include "tinsel/palette.h"	// for COLORREF + +namespace Tinsel { + +struct FREEL; +struct INT_CONTEXT; +struct MACTOR; +struct OBJECT; + + +/*----------------------------------------------------------------------*/ + +void RegisterActors(int num); +void FreeActors(void); +void setleadid(int rid); +int LeadId(void); +void StartActors(SCNHANDLE ah, int numActors, bool bRunScript); +void DropActors(void);		// No actor reels running +void DisableActor(int actor); +void EnableActor(int actor); +void Tag_Actor(int ano, SCNHANDLE tagtext, int tp); +void UnTagActor(int ano); +void ReTagActor(int ano); +int TagType(int ano); +bool actorAlive(int ano); +int32 actorMaskType(int ano); +void GetActorPos(int ano, int *x, int *y); +void SetActorPos(int ano, int x, int y); +void GetActorMidTop(int ano, int *x, int *y); +int GetActorLeft(int ano); +int GetActorRight(int ano); +int GetActorTop(int ano); +int GetActorBottom(int ano); +void HideActor(int ano); +bool HideMovingActor(int id, int sf); +void unHideMovingActor(int id); +void restoreMovement(int id); +void storeActorReel(int ano, const FREEL *reel, SCNHANDLE film, OBJECT *pobj, int reelnum, int x, int y); +const FREEL *actorReel(int ano); +SCNHANDLE actorFilm(int ano); + +void setActorPlayFilm(int ano, SCNHANDLE film); +SCNHANDLE getActorPlayFilm(int ano); +void setActorTalkFilm(int ano, SCNHANDLE film); +SCNHANDLE getActorTalkFilm(int ano); +void setActorTalking(int ano, bool tf); +bool isActorTalking(int ano); +void setActorLatestFilm(int ano, SCNHANDLE film); +SCNHANDLE getActorLatestFilm(int ano); + +void updateActorEsc(int ano, bool escOn, int escEv); +bool actorEsc(int ano); +int actorEev(int ano); +void storeActorPos(int ano, int x, int y); +void storeActorSteps(int ano, int steps); +int getActorSteps(int ano); +void storeActorZpos(int ano, int z); +SCNHANDLE GetActorTag(int ano); +void FirstTaggedActor(void); +int NextTaggedActor(void); +int AsetZPos(OBJECT *pObj, int y, int32 zFactor); +void MAsetZPos(MACTOR *pActor, int y, int32 zFactor); +void actorEvent(int ano, USER_EVENT event, BUTEVENT be); + +void storeActorAttr(int ano, int r1, int g1, int b1); +COLORREF getActorTcol(int ano); + +void setactorson(void); + +void ActorsLife(int id, bool bAlive); + +/*----------------------------------------------------------------------*/ + +struct SAVED_ACTOR { +	short		actorID; +	short		z; +	bool		bAlive; +	SCNHANDLE 	presFilm;	//!< the film that reel belongs to +	short		presRnum;	//!< the present reel number +	short		presX, presY; +}; + +int SaveActors(SAVED_ACTOR *sActorInfo); + +	 +void RestoreActorProcess(int id, INT_CONTEXT *pic); + + +/*----------------------------------------------------------------------*/ + +} // end of namespace Tinsel + +#endif /* TINSEL_ACTOR_H */ diff --git a/engines/tinsel/anim.cpp b/engines/tinsel/anim.cpp new file mode 100644 index 0000000000..95d834d88a --- /dev/null +++ b/engines/tinsel/anim.cpp @@ -0,0 +1,404 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * This file contains utilities to handle object animation. + */ + +#include "tinsel/anim.h" +#include "tinsel/handle.h" +#include "tinsel/multiobj.h"	// multi-part object defintions etc. +#include "tinsel/object.h" +#include "tinsel/sched.h" + +#include "common/util.h" + +namespace Tinsel { + +/** Animation script commands */ +enum { +	ANI_END			= 0,	//!< end of animation script +	ANI_JUMP		= 1,	//!< animation script jump +	ANI_HFLIP		= 2,	//!< flip animated object horizontally +	ANI_VFLIP		= 3,	//!< flip animated object vertically +	ANI_HVFLIP		= 4,	//!< flip animated object in both directions +	ANI_ADJUSTX		= 5,	//!< adjust animated object x animation point +	ANI_ADJUSTY		= 6,	//!< adjust animated object y animation point +	ANI_ADJUSTXY	= 7,	//!< adjust animated object x & y animation points +	ANI_NOSLEEP		= 8,	//!< do not sleep for this frame +	ANI_CALL		= 9,	//!< call routine +	ANI_HIDE		= 10	//!< hide animated object +}; + +/** animation script command possibilities */ +union ANI_SCRIPT { +	int32 op;		//!< treat as an opcode or operand +	uint32 hFrame;	//!< treat as a animation frame handle +}; + +/** + * Advance to next frame routine. + * @param pAnim			Animation data structure + */ +SCRIPTSTATE DoNextFrame(ANIM *pAnim) { +	// get a pointer to the script +	const ANI_SCRIPT *pAni = (const ANI_SCRIPT *)LockMem(pAnim->hScript); + +	while (1) {	// repeat until a real image + +		switch ((int32)FROM_LE_32(pAni[pAnim->scriptIndex].op)) { +		case ANI_END:	// end of animation script + +			// move to next opcode +			pAnim->scriptIndex++; + +			// indicate script has finished +			return ScriptFinished; + +		case ANI_JUMP:	// do animation jump + +			// move to jump address +			pAnim->scriptIndex++; + +			// jump to new frame position +			pAnim->scriptIndex += (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op); + +			// go fetch a real image +			break; + +		case ANI_HFLIP:	// flip animated object horizontally + +			// next opcode +			pAnim->scriptIndex++; + +			MultiHorizontalFlip(pAnim->pObject); + +			// go fetch a real image +			break; + +		case ANI_VFLIP:	// flip animated object vertically + +			// next opcode +			pAnim->scriptIndex++; + +			MultiVerticalFlip(pAnim->pObject); + +			// go fetch a real image +			break; + +		case ANI_HVFLIP:	// flip animated object in both directions + +			// next opcode +			pAnim->scriptIndex++; + +			MultiHorizontalFlip(pAnim->pObject); +			MultiVerticalFlip(pAnim->pObject); + +			// go fetch a real image +			break; + +		case ANI_ADJUSTX:	// adjust animated object x animation point + +			// move to x adjustment operand +			pAnim->scriptIndex++; + +			MultiAdjustXY(pAnim->pObject, (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op), 0); + +			// next opcode +			pAnim->scriptIndex++; + +			// go fetch a real image +			break; + +		case ANI_ADJUSTY:	// adjust animated object y animation point + +			// move to y adjustment operand +			pAnim->scriptIndex++; + +			MultiAdjustXY(pAnim->pObject, 0, (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op)); + +			// next opcode +			pAnim->scriptIndex++; + +			// go fetch a real image +			break; + +		case ANI_ADJUSTXY:	// adjust animated object x & y animation points +		{ +			int x, y; + +			// move to x adjustment operand +			pAnim->scriptIndex++; +			x = (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op); + +			// move to y adjustment operand +			pAnim->scriptIndex++; +			y = (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op); + +			MultiAdjustXY(pAnim->pObject, x, y); + +			// next opcode +			pAnim->scriptIndex++; + +			// go fetch a real image +			break; +		} + +		case ANI_NOSLEEP:	// do not sleep for this frame + +			// next opcode +			pAnim->scriptIndex++; + +			// indicate not to sleep +			return ScriptNoSleep; + +		case ANI_CALL:		// call routine + +			// move to function address +			pAnim->scriptIndex++; + +			// make function call + +			// REMOVED BUGGY CODE +			// pFunc is a function pointer that's part of a union and is assumed to be 32-bits. +			// There is no known place where a function pointer is stored inside the animation +			// scripts, something which wouldn't have worked anyway. Having played through the +			// entire game, there hasn't been any occurence of this case, so just error out here +			// in case we missed something (highly unlikely though) +			error("ANI_CALL opcode encountered! Please report this error to the ScummVM team"); +			//(*pAni[pAnim->scriptIndex].pFunc)(pAnim); + +			// next opcode +			pAnim->scriptIndex++; + +			// go fetch a real image +			break; + +		case ANI_HIDE:		// hide animated object + +			MultiHideObject(pAnim->pObject); + +			// next opcode +			pAnim->scriptIndex++; + +			// dont skip a sleep +			return ScriptSleep; + +		default:	// must be an actual animation frame handle + +			// set objects new animation frame +			pAnim->pObject->hShape = FROM_LE_32(pAni[pAnim->scriptIndex].hFrame); + +			// re-shape the object +			MultiReshape(pAnim->pObject); + +			// next opcode +			pAnim->scriptIndex++; + +			// dont skip a sleep +			return ScriptSleep; +		} +	} +} + +/** + * Init a ANIM structure for single stepping through a animation script. + * @param pAnim				Animation data structure + * @param pAniObj			Object to animate + * @param hNewScript		Script of multipart frames + * @param aniSpeed			Sets speed of animation in frames + */ +void InitStepAnimScript(ANIM *pAnim, OBJECT *pAniObj, SCNHANDLE hNewScript, int aniSpeed) { +	OBJECT *pObj;			// multi-object list iterator + +	pAnim->aniDelta = 1;		// will animate on next call to NextAnimRate +	pAnim->pObject = pAniObj;	// set object to animate +	pAnim->hScript = hNewScript;	// set animation script +	pAnim->scriptIndex = 0;		// start of script +	pAnim->aniRate = aniSpeed;	// set speed of animation + +	// reset flip flags for the object - let the script do the flipping +	for (pObj = pAniObj; pObj != NULL; pObj = pObj->pSlave) { +		AnimateObjectFlags(pObj, pObj->flags & ~(DMA_FLIPH | DMA_FLIPV), +			pObj->hImg); +	} +} + +/** + * Execute the next command in a animation script. + * @param pAnim			Animation data structure + */ +SCRIPTSTATE StepAnimScript(ANIM *pAnim) { +	SCRIPTSTATE state; + +	if (--pAnim->aniDelta == 0) { +		// re-init animation delta counter +		pAnim->aniDelta = pAnim->aniRate; + +		// move to next frame +		while ((state = DoNextFrame(pAnim)) == ScriptNoSleep) +			; + +		return state; +	} + +	// indicate calling task should sleep +	return ScriptSleep; +} + +/** + * Skip the specified number of frames. + * @param pAnim			Animation data structure + * @param numFrames		Number of frames to skip + */ +void SkipFrames(ANIM *pAnim, int numFrames) { +	// get a pointer to the script +	const ANI_SCRIPT *pAni = (const ANI_SCRIPT *)LockMem(pAnim->hScript); + +	if (numFrames <= 0) +		// do nothing +		return; + +	while (1) {	// repeat until a real image + +		switch ((int32)FROM_LE_32(pAni[pAnim->scriptIndex].op)) { +		case ANI_END:	// end of animation script +			// going off the end is probably a error +			error("SkipFrames(): formally 'assert(0)!'"); +			break; + +		case ANI_JUMP:	// do animation jump + +			// move to jump address +			pAnim->scriptIndex++; + +			// jump to new frame position +			pAnim->scriptIndex += (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op); +			break; + +		case ANI_HFLIP:	// flip animated object horizontally + +			// next opcode +			pAnim->scriptIndex++; + +			MultiHorizontalFlip(pAnim->pObject); +			break; + +		case ANI_VFLIP:	// flip animated object vertically + +			// next opcode +			pAnim->scriptIndex++; + +			MultiVerticalFlip(pAnim->pObject); +			break; + +		case ANI_HVFLIP:	// flip animated object in both directions + +			// next opcode +			pAnim->scriptIndex++; + +			MultiHorizontalFlip(pAnim->pObject); +			MultiVerticalFlip(pAnim->pObject); +			break; + +		case ANI_ADJUSTX:	// adjust animated object x animation point + +			// move to x adjustment operand +			pAnim->scriptIndex++; + +			MultiAdjustXY(pAnim->pObject, (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op), 0); + +			// next opcode +			pAnim->scriptIndex++; +			break; + +		case ANI_ADJUSTY:	// adjust animated object y animation point + +			// move to y adjustment operand +			pAnim->scriptIndex++; + +			MultiAdjustXY(pAnim->pObject, 0, (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op)); + +			// next opcode +			pAnim->scriptIndex++; +			break; + +		case ANI_ADJUSTXY:	// adjust animated object x & y animation points +		{ +			int x, y; + +			// move to x adjustment operand +			pAnim->scriptIndex++; +			x = (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op); + +			// move to y adjustment operand +			pAnim->scriptIndex++; +			y = (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op); + +			MultiAdjustXY(pAnim->pObject, x, y); + +			// next opcode +			pAnim->scriptIndex++; + +			break; +		} + +		case ANI_NOSLEEP:	// do not sleep for this frame + +			// next opcode +			pAnim->scriptIndex++; +			break; + +		case ANI_CALL:		// call routine + +			// skip function address +			pAnim->scriptIndex += 2; +			break; + +		case ANI_HIDE:		// hide animated object + +			// next opcode +			pAnim->scriptIndex++; +			break; + +		default:	// must be an actual animation frame handle + +			// one less frame +			if (numFrames-- > 0) { +				// next opcode +				pAnim->scriptIndex++; +			} else { +				// set objects new animation frame +				pAnim->pObject->hShape = FROM_LE_32(pAni[pAnim->scriptIndex].hFrame); + +				// re-shape the object +				MultiReshape(pAnim->pObject); + +				// we have skipped to the correct place +				return; +			} +			break; +		} +	} +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/anim.h b/engines/tinsel/anim.h new file mode 100644 index 0000000000..5b25292a6b --- /dev/null +++ b/engines/tinsel/anim.h @@ -0,0 +1,71 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Object animation definitions + */ + +#ifndef TINSEL_ANIM_H     // prevent multiple includes +#define TINSEL_ANIM_H + +#include "tinsel/dw.h"	// for SCNHANDLE + +namespace Tinsel { + +struct OBJECT; + +/** animation structure */ +struct ANIM { +	int aniRate;		//!< animation speed +	int aniDelta;		//!< animation speed delta counter +	OBJECT *pObject;	//!< object to animate (assumed to be multi-part) +	uint32 hScript;		//!< animation script handle +	int scriptIndex;	//!< current position in animation script +}; + + +/*----------------------------------------------------------------------*\ +|*			Anim Function Prototypes			*| +\*----------------------------------------------------------------------*/ + +/** states for DoNextFrame */ +enum SCRIPTSTATE {ScriptFinished, ScriptNoSleep, ScriptSleep}; + +SCRIPTSTATE DoNextFrame(	// Execute the next animation frame of a animation script +	ANIM *pAnim);		// animation data structure + +void InitStepAnimScript(	// Init a ANIM struct for single stepping through a animation script +	ANIM *pAnim,		// animation data structure +	OBJECT *pAniObj,	// object to animate +	SCNHANDLE hNewScript,	// handle to script of multipart frames +	int aniSpeed);		// sets speed of animation in frames + +SCRIPTSTATE StepAnimScript(	// Execute the next command in a animation script +	ANIM *pAnim);		// animation data structure + +void SkipFrames(		// Skip the specified number of frames +	ANIM *pAnim,		// animation data structure +	int numFrames);		// number of frames to skip + +} // end of namespace Tinsel + +#endif		// TINSEL_ANIM_H diff --git a/engines/tinsel/background.cpp b/engines/tinsel/background.cpp new file mode 100644 index 0000000000..91d21b4e0b --- /dev/null +++ b/engines/tinsel/background.cpp @@ -0,0 +1,232 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Background handling code. + */ + +#include "tinsel/background.h" +#include "tinsel/cliprect.h"	// object clip rect defs +#include "tinsel/graphics.h" +#include "tinsel/sched.h"	// process sheduler defs +#include "tinsel/object.h" +#include "tinsel/pid.h"	// process identifiers +#include "tinsel/tinsel.h" + +namespace Tinsel { + +// current background +BACKGND *pCurBgnd = NULL; + +/** + * Called to initialise a background. + * @param pBgnd			Pointer to data struct for current background + */ + +void InitBackground(BACKGND *pBgnd) { +	int i;			// playfield counter +	PLAYFIELD *pPlayfield;	// pointer to current playfield + +	// set current background +	pCurBgnd = pBgnd; + +	// init background sky colour +	SetBgndColour(pBgnd->rgbSkyColour); + +	// start of playfield array +	pPlayfield = pBgnd->fieldArray; + +	// for each background playfield +	for (i = 0; i < pBgnd->numPlayfields; i++, pPlayfield++) { +		// init playfield pos +		pPlayfield->fieldX = intToFrac(pBgnd->ptInitWorld.x); +		pPlayfield->fieldY = intToFrac(pBgnd->ptInitWorld.y); + +		// no scrolling +		pPlayfield->fieldXvel = intToFrac(0); +		pPlayfield->fieldYvel = intToFrac(0); + +		// clear playfield display list +		pPlayfield->pDispList = NULL; + +		// clear playfield moved flag +		pPlayfield->bMoved = false; +	} +} + +/** + * Sets the xy position of the specified playfield in the current background. + * @param which			Which playfield + * @param newXpos		New x position + * @param newYpos		New y position + */ + +void PlayfieldSetPos(int which, int newXpos, int newYpos) { +	PLAYFIELD *pPlayfield;	// pointer to relavent playfield + +	// make sure there is a background +	assert(pCurBgnd != NULL); + +	// make sure the playfield number is in range +	assert(which >= 0 && which < pCurBgnd->numPlayfields); + +	// get playfield pointer +	pPlayfield = pCurBgnd->fieldArray + which; + +	// set new integer position +	pPlayfield->fieldX = intToFrac(newXpos); +	pPlayfield->fieldY = intToFrac(newYpos); + +	// set moved flag +	pPlayfield->bMoved = true; +} + +/** + * Returns the xy position of the specified playfield in the current background. + * @param which			Which playfield + * @param pXpos			Returns current x position + * @param pYpos			Returns current y position + */ + +void PlayfieldGetPos(int which, int *pXpos, int *pYpos) { +	PLAYFIELD *pPlayfield;	// pointer to relavent playfield + +	// make sure there is a background +	assert(pCurBgnd != NULL); + +	// make sure the playfield number is in range +	assert(which >= 0 && which < pCurBgnd->numPlayfields); + +	// get playfield pointer +	pPlayfield = pCurBgnd->fieldArray + which; + +	// get current integer position +	*pXpos = fracToInt(pPlayfield->fieldX); +	*pYpos = fracToInt(pPlayfield->fieldY); +} + +/** + * Returns the display list for the specified playfield. + * @param which			Which playfield + */ + +OBJECT *GetPlayfieldList(int which) { +	PLAYFIELD *pPlayfield;	// pointer to relavent playfield + +	// make sure there is a background +	assert(pCurBgnd != NULL); + +	// make sure the playfield number is in range +	assert(which >= 0 && which < pCurBgnd->numPlayfields); + +	// get playfield pointer +	pPlayfield = pCurBgnd->fieldArray + which; + +	// return the display list pointer for this playfield +	return (OBJECT *)&pPlayfield->pDispList; +} + +/** + * Draws all the playfield object lists for the current background. + * The playfield velocity is added to the playfield position in order + * to scroll each playfield before it is drawn. + */ + +void DrawBackgnd(void) { +	int i;			// playfield counter +	PLAYFIELD *pPlay;	// playfield pointer +	int prevX, prevY;	// save interger part of position +	Common::Point ptWin;	// window top left + +	if (pCurBgnd == NULL) +		return;		// no current background + +	// scroll each background playfield +	for (i = 0; i < pCurBgnd->numPlayfields; i++) { +		// get pointer to correct playfield +		pPlay = pCurBgnd->fieldArray + i; + +		// save integer part of position +		prevX = fracToInt(pPlay->fieldX); +		prevY = fracToInt(pPlay->fieldY); + +		// update scrolling +		pPlay->fieldX += pPlay->fieldXvel; +		pPlay->fieldY += pPlay->fieldYvel; + +		// convert fixed point window pos to a int +		ptWin.x = fracToInt(pPlay->fieldX); +		ptWin.y = fracToInt(pPlay->fieldY); + +		// set the moved flag if the playfield has moved +		if (prevX != ptWin.x || prevY != ptWin.y) +			pPlay->bMoved = true; + +		// sort the display list for this background - just in case somebody has changed object Z positions +		SortObjectList((OBJECT *)&pPlay->pDispList); + +		// generate clipping rects for all objects that have moved etc. +		FindMovingObjects((OBJECT *)&pPlay->pDispList, &ptWin, +			&pPlay->rcClip,	false, pPlay->bMoved); + +		// clear playfield moved flag +		pPlay->bMoved = false; +	} + +	// merge the clipping rectangles +	MergeClipRect(); + +	// redraw all playfields within the clipping rectangles +	const RectList &clipRects = GetClipRects(); +	for (RectList::const_iterator r = clipRects.begin(); r != clipRects.end(); ++r) { +		// clear the clip rectangle on the virtual screen +		// for each background playfield +		for (i = 0; i < pCurBgnd->numPlayfields; i++) { +			Common::Rect rcPlayClip;	// clip rect for this playfield + +			// get pointer to correct playfield +			pPlay = pCurBgnd->fieldArray + i; + +			// convert fixed point window pos to a int +			ptWin.x = fracToInt(pPlay->fieldX); +			ptWin.y = fracToInt(pPlay->fieldY); + +			if (IntersectRectangle(rcPlayClip, pPlay->rcClip, *r)) +				// redraw all objects within this clipping rect +				UpdateClipRect((OBJECT *)&pPlay->pDispList, +						&ptWin,	&rcPlayClip); +		} +	} + +	// transfer any new palettes to the video DAC +	PalettesToVideoDAC(); + +	// update the screen within the clipping rectangles +	for (RectList::const_iterator r = clipRects.begin(); r != clipRects.end(); ++r) { +		UpdateScreenRect(*r); +	} + +	// delete all the clipping rectangles +	ResetClipRect(); +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/background.h b/engines/tinsel/background.h new file mode 100644 index 0000000000..7b8d099446 --- /dev/null +++ b/engines/tinsel/background.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. + * + * $URL$ + * $Id$ + * + * Data structures used for handling backgrounds + */ + +#ifndef TINSEL_BACKGND_H     // prevent multiple includes +#define TINSEL_BACKGND_H + +#include "tinsel/dw.h"	// for SCNHANDLE +#include "tinsel/palette.h"	// palette definitions +#include "common/frac.h" +#include "common/rect.h" + +namespace Tinsel { + +struct OBJECT; + + +/** Scrolling padding. Needed because scroll process does not normally run on every frame */ +enum { +	SCROLLX_PAD	= 64, +	SCROLLY_PAD	= 64 +}; + +/** When module BLK_INFO list is this long, switch from a binary to linear search */ +#define	LINEAR_SEARCH	5 + +/** background playfield structure - a playfield is a container for modules */ +struct PLAYFIELD { +	OBJECT *pDispList;	//!< object display list for this playfield +	frac_t fieldX;		//!< current world x position of playfield +	frac_t fieldY;		//!< current world y position of playfield +	frac_t fieldXvel;	//!< current x velocity of playfield +	frac_t fieldYvel;	//!< current y velocity of playfield +	Common::Rect rcClip;	//!< clip rectangle for this playfield +	bool bMoved;		//!< set when playfield has moved +}; + +/** multi-playfield background structure - a backgnd is a container of playfields */ +struct BACKGND { +	COLORREF rgbSkyColour;	//!< background sky colour +	Common::Point ptInitWorld;		//!< initial world position +	Common::Rect rcScrollLimits;	//!< scroll limits +	int refreshRate;		//!< background update process refresh rate +	frac_t *pXscrollTable;	//!< pointer to x direction scroll table for this background +	frac_t *pYscrollTable;	//!< pointer to y direction scroll table for this background +	int numPlayfields;		//!< number of playfields for this background +	PLAYFIELD *fieldArray;	//!< pointer to array of all playfields for this background +	bool bAutoErase;		//!< when set - screen is cleared before anything is plotted (unused) +}; + + +/*----------------------------------------------------------------------*\ +|*			Background Function Prototypes			*| +\*----------------------------------------------------------------------*/ + +void InitBackground(		// called to initialise a background +	BACKGND *pBgnd);	// pointer to data struct for current background + +void StopBgndScrolling(void);	// Stops all background playfields from scrolling + +void PlayfieldSetPos(		// Sets the xy position of the specified playfield in the current background +	int which,		// which playfield +	int newXpos,		// new x position +	int newYpos);		// new y position + +void PlayfieldGetPos(		// Returns the xy position of the specified playfield in the current background +	int which,		// which playfield +	int *pXpos,		// returns current x position +	int *pYpos);		// returns current y position + +OBJECT *GetPlayfieldList(	// Returns the display list for the specified playfield +	int which);		// which playfield + +void KillPlayfieldList(		// Kills all the objects on the display list for the specified playfield +	int which);		// which playfield + +void DrawBackgnd(void);		// Draws all playfields for the current background + +void RedrawBackgnd(void);	// Completely redraws all the playfield object lists for the current background + +SCNHANDLE BackPal(void); + +} // end of namespace Tinsel + +#endif	// TINSEL_BACKGND_H diff --git a/engines/tinsel/bg.cpp b/engines/tinsel/bg.cpp new file mode 100644 index 0000000000..9c1e5f1540 --- /dev/null +++ b/engines/tinsel/bg.cpp @@ -0,0 +1,189 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Plays the background film of a scene. + */ + +#include "tinsel/anim.h" +#include "tinsel/background.h" +#include "tinsel/dw.h" +#include "tinsel/faders.h" +#include "tinsel/film.h" +#include "tinsel/font.h" +#include "tinsel/handle.h" +#include "tinsel/multiobj.h" +#include "tinsel/object.h" +#include "tinsel/pcode.h"		// CONTROL_STARTOFF +#include "tinsel/pid.h" +#include "tinsel/sched.h" +#include "tinsel/timers.h"		// For ONE_SECOND constant +#include "tinsel/tinlib.h"		// For control() + +#include "common/util.h" + +namespace Tinsel { + +//----------------- LOCAL GLOBAL DATA -------------------- + +static SCNHANDLE BackPalette = 0;	// Background's palette +static OBJECT *pBG = 0;		// The main picture's object. +static int BGspeed = 0; +static SCNHANDLE BgroundHandle = 0;	// Current scene handle - stored in case of Save_Scene() +static bool DoFadeIn = false; +static ANIM	thisAnim;	// used by BGmainProcess() + +/** + * BackPal + */ +SCNHANDLE BackPal(void) { +	return BackPalette; +} + +/** + * SetDoFadeIn +*/ +void SetDoFadeIn(bool tf) { +	DoFadeIn = tf; +} + +/** + * Called before scene change. + */ +void DropBackground(void) { +	pBG = NULL;		// No background +	BackPalette = 0;	// No background palette +} + +/** + * Return the width of the current background. + */ +int BackgroundWidth(void) { +	assert(pBG); +	return MultiRightmost(pBG) + 1; +} + +/** + * Return the height of the current background. + */ +int BackgroundHeight(void) { +	assert(pBG); +	return MultiLowest(pBG) + 1; +} + +/** + * Run main animation that comprises the scene background. + */ +static void BGmainProcess(CORO_PARAM, const void *param) { +	// COROUTINE +	CORO_BEGIN_CONTEXT; +	CORO_END_CONTEXT(_ctx); + +	CORO_BEGIN_CODE(_ctx); + +	const FREEL *pfr; +	const MULTI_INIT *pmi; + +	// get the stuff copied to process when it was created +	pfr = (const FREEL *)param; + +	if (pBG == NULL) { +		/*** At start of scene ***/ + +		// Get the MULTI_INIT structure +		pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(pfr->mobj)); + +		// Initialise and insert the object, and initialise its script. +		pBG = MultiInitObject(pmi); +		MultiInsertObject(GetPlayfieldList(FIELD_WORLD), pBG); +		InitStepAnimScript(&thisAnim, pBG, FROM_LE_32(pfr->script), BGspeed); + +		if (DoFadeIn) { +			FadeInFast(NULL); +			DoFadeIn = false; +		} + +		while (StepAnimScript(&thisAnim) != ScriptFinished) +			CORO_SLEEP(1); + +		error("Background animation has finished!"); +	} else { +		// New background during scene + +		// Just re-initialise the script. +		InitStepAnimScript(&thisAnim, pBG, FROM_LE_32(pfr->script), BGspeed); +		StepAnimScript(&thisAnim); +	} + +	CORO_END_CODE; +} + +/** + * setBackPal() + */ +void setBackPal(SCNHANDLE hPal) { +	BackPalette = hPal; + +	fettleFontPal(BackPalette); +	CreateTranslucentPalette(BackPalette); +} + +void ChangePalette(SCNHANDLE hPal) { +	SwapPalette(FindPalette(BackPalette), hPal); + +	setBackPal(hPal); +} + +/** + * Given the scene background film, extracts the palette handle for + * everything else's use, then starts a display process for each reel + * in the film. + * @param bfilm			Scene background film + */ +void startupBackground(SCNHANDLE bfilm) { +	const FILM *pfilm; +	IMAGE *pim; + +	BgroundHandle = bfilm;		// Save handle in case of Save_Scene() + +	pim = GetImageFromFilm(bfilm, 0, NULL, NULL, &pfilm); +	setBackPal(FROM_LE_32(pim->hImgPal)); + +	// Extract the film speed +	BGspeed = ONE_SECOND / FROM_LE_32(pfilm->frate); + +	if (pBG == NULL) +		control(CONTROL_STARTOFF);	// New feature - start scene with control off + +	// Start display process for each reel in the film +	assert(FROM_LE_32(pfilm->numreels) == 1); // Multi-reeled backgrounds withdrawn +	g_scheduler->createProcess(PID_REEL, BGmainProcess, &pfilm->reels[0], sizeof(FREEL)); +} + +/** + * Return the current scene handle. + */ +SCNHANDLE GetBgroundHandle(void) { +	return BgroundHandle; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/cliprect.cpp b/engines/tinsel/cliprect.cpp new file mode 100644 index 0000000000..b67ae7b17f --- /dev/null +++ b/engines/tinsel/cliprect.cpp @@ -0,0 +1,313 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * This file contains the clipping rectangle code. + */ + +#include "tinsel/cliprect.h"	// object clip rect defs +#include "tinsel/graphics.h"	// normal object drawing +#include "tinsel/object.h" +#include "tinsel/palette.h" + +namespace Tinsel { + +/** list of all clip rectangles */ +static RectList s_rectList; + +/** + * Resets the clipping rectangle allocator. + */ +void ResetClipRect(void) { +	s_rectList.clear(); +} + +/** + * Allocate a clipping rectangle from the free list. + * @param pClip			clip rectangle dimensions to allocate + */ +void AddClipRect(const Common::Rect &pClip) { +	s_rectList.push_back(pClip); +} + +const RectList &GetClipRects() { +	return s_rectList; +} + +/** + * Creates the intersection of two rectangles. + * Returns True if there is a intersection. + * @param pDest			Pointer to destination rectangle that is to receive the intersection + * @param pSrc1			Pointer to a source rectangle + * @param pSrc2			Pointer to a source rectangle + */ +bool IntersectRectangle(Common::Rect &pDest, const Common::Rect &pSrc1, const Common::Rect &pSrc2) { +	pDest.left   = MAX(pSrc1.left, pSrc2.left); +	pDest.top    = MAX(pSrc1.top, pSrc2.top); +	pDest.right  = MIN(pSrc1.right, pSrc2.right); +	pDest.bottom = MIN(pSrc1.bottom, pSrc2.bottom); + +	return !pDest.isEmpty(); +} + +/** + * Creates the union of two rectangles. + * Returns True if there is a union. + * @param pDest			destination rectangle that is to receive the new union + * @param pSrc1			a source rectangle + * @param pSrc2			a source rectangle + */ +bool UnionRectangle(Common::Rect &pDest, const Common::Rect &pSrc1, const Common::Rect &pSrc2) { +	pDest.left   = MIN(pSrc1.left, pSrc2.left); +	pDest.top    = MIN(pSrc1.top, pSrc2.top); +	pDest.right  = MAX(pSrc1.right, pSrc2.right); +	pDest.bottom = MAX(pSrc1.bottom, pSrc2.bottom); + +	return !pDest.isEmpty(); +} + +/** + * Check if the two rectangles are next to each other. + * @param pSrc1			a source rectangle + * @param pSrc2			a source rectangle + */ +static bool LooseIntersectRectangle(const Common::Rect &pSrc1, const Common::Rect &pSrc2) { +	Common::Rect pDest; + +	pDest.left   = MAX(pSrc1.left, pSrc2.left); +	pDest.top    = MAX(pSrc1.top, pSrc2.top); +	pDest.right  = MIN(pSrc1.right, pSrc2.right); +	pDest.bottom = MIN(pSrc1.bottom, pSrc2.bottom); + +	return pDest.isValidRect(); +} + +/** + * Adds velocities and creates clipping rectangles for all the + * objects that have moved on the specified object list. + * @param pObjList			Playfield display list to draw + * @param pWin				Playfield window top left position + * @param pClip				Playfield clipping rectangle + * @param bNoVelocity		When reset, objects pos is updated with velocity + * @param bScrolled)		When set, playfield has scrolled + */ +void FindMovingObjects(OBJECT *pObjList, Common::Point *pWin, Common::Rect *pClip, bool bNoVelocity, bool bScrolled) { +	OBJECT *pObj;			// object list traversal pointer + +	for (pObj = pObjList->pNext; pObj != NULL; pObj = pObj->pNext) { +		if (!bNoVelocity) { +			// we want to add velocities to objects position + +			if (bScrolled) { +				// this playfield has scrolled + +				// indicate change +				pObj->flags |= DMA_CHANGED; +			} +		} + +		if ((pObj->flags & DMA_CHANGED) ||	// object changed +			HasPalMoved(pObj->pPal)) {	// or palette moved +			// object has changed in some way +			 +			Common::Rect rcClip;	// objects clipped bounding rectangle +			Common::Rect rcObj;	// objects bounding rectangle + +			// calc intersection of objects previous bounding rectangle +			// NOTE: previous position is in screen co-ords +			if (IntersectRectangle(rcClip, pObj->rcPrev, *pClip)) { +				// previous position is within clipping rect +				AddClipRect(rcClip); +			} + +			// calc objects current bounding rectangle +			if (pObj->flags & DMA_ABS) { +				// object position is absolute +				rcObj.left = fracToInt(pObj->xPos); +				rcObj.top  = fracToInt(pObj->yPos); +			} else { +				// object position is relative to window +				rcObj.left = fracToInt(pObj->xPos) - pWin->x; +				rcObj.top  = fracToInt(pObj->yPos) - pWin->y; +			} +			rcObj.right  = rcObj.left + pObj->width; +			rcObj.bottom = rcObj.top  + pObj->height; + +			// calc intersection of object with clipping rect			 +			if (IntersectRectangle(rcClip, rcObj, *pClip)) { +				// current position is within clipping rect +				AddClipRect(rcClip); + +				// update previous position +				pObj->rcPrev = rcClip; +			} else { +				// clear previous position +				pObj->rcPrev = Common::Rect(); +			} + +			// clear changed flag +			pObj->flags &= ~DMA_CHANGED; +		} +	} +} + +/** + * Merges any clipping rectangles that overlap to try and reduce + * the total number of clip rectangles. + */ +void MergeClipRect() { +	if (s_rectList.size() <= 1) +		return; + +	RectList::iterator rOuter, rInner; + +	for (rOuter = s_rectList.begin(); rOuter != s_rectList.end(); ++rOuter) { +		rInner = rOuter; +		while (++rInner != s_rectList.end()) { + +			if (LooseIntersectRectangle(*rOuter, *rInner)) { +				// these two rectangles overlap or +				// are next to each other - merge them + +				UnionRectangle(*rOuter, *rOuter, *rInner); + +				// remove the inner rect from the list +				s_rectList.erase(rInner); + +				// move back to beginning of list +				rInner = rOuter; +			} +		} +	} +} + +/** + * Redraws all objects within this clipping rectangle. + * @param pObjList		Object list to draw + * @param pWin			Window top left position + * @param pClip			Pointer to clip rectangle + */ +void UpdateClipRect(OBJECT *pObjList, Common::Point *pWin, Common::Rect *pClip) { +	int x, y, right, bottom;	// object corners +	int hclip, vclip;			// total size of object clipping +	DRAWOBJECT currentObj;		// filled in to draw the current object in list +	OBJECT *pObj;				// object list iterator + +	// Initialise the fields of the drawing object to empty +	memset(¤tObj, 0, sizeof(DRAWOBJECT)); + +	for (pObj = pObjList->pNext; pObj != NULL; pObj = pObj->pNext) { +		if (pObj->flags & DMA_ABS) { +			// object position is absolute +			x = fracToInt(pObj->xPos); +			y = fracToInt(pObj->yPos); +		} else { +			// object position is relative to window +			x = fracToInt(pObj->xPos) - pWin->x; +			y = fracToInt(pObj->yPos) - pWin->y; +		} + +		// calc object right +		right = x + pObj->width; +		if (right < 0) +			// totally clipped if negative +			continue; + +		// calc object bottom +		bottom = y + pObj->height; +		if (bottom < 0) +			// totally clipped if negative +			continue; + +		// bottom clip = low right y - clip low right y +		currentObj.botClip = bottom - pClip->bottom; +		if (currentObj.botClip < 0) { +			// negative - object is not clipped +			currentObj.botClip = 0; +		} + +		// right clip = low right x - clip low right x +		currentObj.rightClip = right - pClip->right; +		if (currentObj.rightClip < 0) { +			// negative - object is not clipped +			currentObj.rightClip = 0; +		} + +		// top clip = clip top left y - top left y +		currentObj.topClip = pClip->top - y; +		if (currentObj.topClip < 0) { +			// negative - object is not clipped +			currentObj.topClip = 0; +		} else {	// clipped - adjust start position to top of clip rect +			y = pClip->top; +		} + +		// left clip = clip top left x - top left x +		currentObj.leftClip = pClip->left - x; +		if (currentObj.leftClip < 0) { +			// negative - object is not clipped +			currentObj.leftClip = 0; +		} +		else +		// NOTE: This else statement is disabled in tinsel v1 +		{	// clipped - adjust start position to left of clip rect +			x = pClip->left; +		} + +		// calc object total horizontal clipping +		hclip = currentObj.leftClip + currentObj.rightClip; + +		// calc object total vertical clipping +		vclip = currentObj.topClip + currentObj.botClip; + +		if (hclip + vclip != 0) { +			// object is clipped in some way + +			if (pObj->width <= hclip) +				// object totally clipped horizontally - ignore +				continue; + +			if (pObj->height <= vclip) +				// object totally clipped vertically - ignore +				continue; + +			// set clip bit in objects flags +			currentObj.flags = pObj->flags | DMA_CLIP; +		} else {	// object is not clipped - copy flags +			currentObj.flags = pObj->flags; +		} + +		// copy objects properties to local object +		currentObj.width    = pObj->width; +		currentObj.height   = pObj->height; +		currentObj.xPos     = (short)x; +		currentObj.yPos     = (short)y; +		currentObj.pPal     = pObj->pPal; +		currentObj.constant = pObj->constant; +		currentObj.hBits    = pObj->hBits; + +		// draw the object +		DrawObject(¤tObj); +	} +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/cliprect.h b/engines/tinsel/cliprect.h new file mode 100644 index 0000000000..28a66c312c --- /dev/null +++ b/engines/tinsel/cliprect.h @@ -0,0 +1,76 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Clipping rectangle defines + */ + +#ifndef TINSEL_CLIPRECT_H     // prevent multiple includes +#define TINSEL_CLIPRECT_H + +#include "common/list.h" +#include "common/rect.h" + +namespace Tinsel { + +struct OBJECT; + +typedef Common::List<Common::Rect> RectList; + +/*----------------------------------------------------------------------*\ +|*			Clip Rect Function Prototypes			*| +\*----------------------------------------------------------------------*/ + +void ResetClipRect();	// Resets the clipping rectangle allocator + +void AddClipRect(		// Allocate a clipping rectangle from the free list +	const Common::Rect &pClip);		// clip rectangle dimensions to allocate + +const RectList &GetClipRects(); + +bool IntersectRectangle(	// Creates the intersection of two rectangles +	Common::Rect &pDest,		// pointer to destination rectangle that is to receive the intersection +	const Common::Rect &pSrc1,		// pointer to a source rectangle +	const Common::Rect &pSrc2);		// pointer to a source rectangle + +bool UnionRectangle(		// Creates the union of two rectangles +	Common::Rect &pDest,		// destination rectangle that is to receive the new union +	const Common::Rect &pSrc1,		// a source rectangle +	const Common::Rect &pSrc2);		// a source rectangle + +void FindMovingObjects(		// Creates clipping rectangles for all the objects that have moved on the specified object list +	OBJECT *pObjList,	// playfield display list to draw +	Common::Point *pWin,		// playfield window top left position +	Common::Rect *pClip,		// playfield clipping rectangle +	bool bVelocity,		// when set, objects pos is updated with velocity +	bool bScrolled);	// when set, playfield has scrolled + +void MergeClipRect();	// Merges any clipping rectangles that overlap + +void UpdateClipRect(		// Redraws all objects within this clipping rectangle +	OBJECT *pObjList,	// object list to draw +	Common::Point *pWin,		// window top left position +	Common::Rect *pClip);		// pointer to clip rectangle + +} // end of namespace Tinsel + +#endif	// TINSEL_CLIPRECT_H diff --git a/engines/tinsel/config.cpp b/engines/tinsel/config.cpp new file mode 100644 index 0000000000..4c143f1b8d --- /dev/null +++ b/engines/tinsel/config.cpp @@ -0,0 +1,125 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * This file contains configuration functionality + */ + +//#define USE_3FLAGS 1 + +#include "tinsel/config.h" +#include "tinsel/dw.h" +#include "tinsel/sound.h" +#include "tinsel/music.h" + +#include "common/file.h" +#include "common/config-manager.h" + +#include "sound/mixer.h" + +namespace Tinsel { + +//----------------- GLOBAL GLOBAL DATA -------------------- + +int dclickSpeed = DOUBLE_CLICK_TIME; +int volMidi = MAXMIDIVOL; +int volSound = MAXSAMPVOL; +int volVoice = MAXSAMPVOL; +int speedText = DEFTEXTSPEED; +int bSubtitles = false; +int bSwapButtons = 0; +LANGUAGE language = TXT_ENGLISH; +int bAmerica = 0; + + +// Shouldn't really be here, but time is short... +bool bNoBlocking; + +/** + * WriteConfig() + */ + +void WriteConfig(void) { +	ConfMan.setInt("dclick_speed", dclickSpeed); +	ConfMan.setInt("music_volume", (volMidi * Audio::Mixer::kMaxChannelVolume) / MAXMIDIVOL); +	ConfMan.setInt("sfx_volume", (volSound * Audio::Mixer::kMaxChannelVolume) / MAXSAMPVOL); +	ConfMan.setInt("speech_volume", (volVoice * Audio::Mixer::kMaxChannelVolume) / MAXSAMPVOL); +	ConfMan.setInt("talkspeed", (speedText * 255) / 100); +	ConfMan.setBool("subtitles", bSubtitles); +	//ConfMan.setBool("swap_buttons", bSwapButtons ? 1 : 0); +	//ConfigData.language = language;	// not necessary, as language has been set in the launcher +	//ConfigData.bAmerica = bAmerica;		// EN_USA / EN_GRB +} + +/*---------------------------------------------------------------------*\ +|	ReadConfig()							| +|-----------------------------------------------------------------------| +| +\*---------------------------------------------------------------------*/ +void ReadConfig(void) { +	if (ConfMan.hasKey("dclick_speed")) +		dclickSpeed = ConfMan.getInt("dclick_speed"); + +	volMidi = (ConfMan.getInt("music_volume") * MAXMIDIVOL) / Audio::Mixer::kMaxChannelVolume; +	volSound = (ConfMan.getInt("sfx_volume") * MAXSAMPVOL) / Audio::Mixer::kMaxChannelVolume; +	volVoice = (ConfMan.getInt("speech_volume") * MAXSAMPVOL) / Audio::Mixer::kMaxChannelVolume; + +	if (ConfMan.hasKey("talkspeed")) +		speedText = (ConfMan.getInt("talkspeed") * 100) / 255; +	if (ConfMan.hasKey("subtitles")) +		bSubtitles = ConfMan.getBool("subtitles"); + +	// FIXME: If JAPAN is set, subtitles are forced OFF in the original engine + +	//bSwapButtons = ConfMan.getBool("swap_buttons") == 1 ? true : false; +	//ConfigData.language = language;	// not necessary, as language has been set in the launcher +	//ConfigData.bAmerica = bAmerica;		// EN_USA / EN_GRB + +// The flags here control how many country flags are displayed in one of the option dialogs. +#if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS) +	language = ConfigData.language; + #ifdef USE_3FLAGS +	if (language == TXT_ENGLISH || language == TXT_ITALIAN) { +		language = TXT_GERMAN; +		bSubtitles = true; +	} + #endif + #ifdef USE_4FLAGS +	if (language == TXT_ENGLISH) { +		language = TXT_GERMAN; +		bSubtitles = true; +	} + #endif +#else +	language = TXT_ENGLISH; +#endif +} + +bool isJapanMode() { +#ifdef JAPAN +	return true; +#else +	return false; +#endif +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/config.h b/engines/tinsel/config.h new file mode 100644 index 0000000000..73cc411cb6 --- /dev/null +++ b/engines/tinsel/config.h @@ -0,0 +1,72 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef TINSEL_CONFIG_H +#define TINSEL_CONFIG_H + +#include "tinsel/dw.h" + +namespace Tinsel { + +// None of these defined -> 1 language, in ENGLISH.TXT +//#define USE_5FLAGS	1	// All 5 flags +//#define USE_4FLAGS	1	// French, German, Italian, Spanish +//#define USE_3FLAGS	1	// French, German, Spanish + +// The Hebrew version appears to the software as being English +// but it needs to have subtitles on... +//#define HEBREW	1 + +//#define JAPAN	1 + + +// double click timer initial value +#define	DOUBLE_CLICK_TIME	6	// 6 @ 18Hz = .33 sec + +#define DEFTEXTSPEED	0 + + +extern int dclickSpeed; +extern int volMidi; +extern int volSound; +extern int volVoice; +extern int speedText; +extern int bSubtitles; +extern int bSwapButtons; +extern LANGUAGE language; +extern int bAmerica; + +void WriteConfig(void); +void ReadConfig(void); + +extern bool isJapanMode(); + + +// Shouldn't really be here, but time is short... +extern bool bNoBlocking; + +} // end of namespace Tinsel + +#endif diff --git a/engines/tinsel/coroutine.h b/engines/tinsel/coroutine.h new file mode 100644 index 0000000000..e0292735bb --- /dev/null +++ b/engines/tinsel/coroutine.h @@ -0,0 +1,147 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef TINSEL_COROUTINE_H +#define TINSEL_COROUTINE_H + +#include "common/scummsys.h" + +namespace Tinsel { + +/* + * The following is loosely based on an article by Simon Tatham: + *   <http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html>. + * However, many improvements and tweaks have been made, in particular + * by taking advantage of C++ features not available in C. + *  + * Why is this code here? Well, the Tinsel engine apparently used + * setjmp/longjmp based coroutines as a core tool from the start, and + * so they are deeply ingrained into the whole code base. When we + * started to get Tinsel ready for ScummVM, we had to deal with that. + * It soon got clear that we could not simply rewrite the code to work + * without some form of coroutines. While possible in principle, it + * would have meant a major restructuring of the entire code base, a + * rather daunting task. Also, it would have very likely introduced + * tons of regressons. + *  + * So instead of getting rid of the coroutines, we chose to implement + * them in an alternate way, using Simon Tatham's trick as described + * above. While the trick is dirty, the result seems to be clear enough, + * we hope; plus, it allowed us to stay relatively close to the + * original structure of the code, which made it easier to avoid + * regressions, and will be helpful in the future when comparing things + * against the original code base. + */ + + +/** + * The core of any coroutine context which captures the 'state' of a coroutine. + * Private use only + */ +struct CoroBaseContext { +	int _line; +	int _sleep; +	CoroBaseContext *_subctx; +	CoroBaseContext() : _line(0), _sleep(0), _subctx(0) {} +	~CoroBaseContext() { delete _subctx; } +}; + +typedef CoroBaseContext *CoroContext; + + +/** + * Wrapper class which holds a pointer to a pointer to a CoroBaseContext. + * The interesting part is the destructor, which kills the context being held, + * but ONLY if the _sleep val of that context is zero. This way, a coroutine + * can just 'return' w/o having to worry about freeing the allocated context + * (in Simon Tatham's original code, one had to use a special macro to + * return from a coroutine). + */ +class CoroContextHolder { +	CoroContext &_ctx; +public: +	CoroContextHolder(CoroContext &ctx) : _ctx(ctx) {} +	~CoroContextHolder() { +		if (_ctx && _ctx->_sleep == 0) { +			delete _ctx; +			_ctx = 0; +		} +	} +}; + + +#define CORO_PARAM     CoroContext &coroParam + +#define CORO_SUBCTX   coroParam->_subctx + + +#define CORO_BEGIN_CONTEXT  struct CoroContextTag : CoroBaseContext { int DUMMY +#define CORO_END_CONTEXT(x)    } *x = (CoroContextTag *)coroParam + +#define CORO_BEGIN_CODE(x) \ +		if (!x) {coroParam = x = new CoroContextTag();}\ +		assert(coroParam);\ +		assert(coroParam->_sleep >= 0);\ +		coroParam->_sleep = 0;\ +		CoroContextHolder tmpHolder(coroParam);\ +		switch(coroParam->_line) { case 0:; + +#define CORO_END_CODE \ +		} + +#define CORO_SLEEP(delay) \ +		do {\ +			coroParam->_line = __LINE__;\ +			coroParam->_sleep = delay;\ +			return; case __LINE__:;\ +		} while (0) + +/** Stop the currently running coroutine */ +#define CORO_KILL_SELF()         do { coroParam->_sleep = -1; return; } while(0) + +/** Invoke another coroutine */ +#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;\ +				return; case __LINE__:;\ +			} while(1);\ +		} while (0) + +#define CORO_INVOKE_0(subCoroutine) \ +			CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX)) +#define CORO_INVOKE_1(subCoroutine, a0) \ +			CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX,a0)) +#define CORO_INVOKE_2(subCoroutine, a0,a1) \ +			CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX,a0,a1)) + + +} // end of namespace Tinsel + +#endif		// TINSEL_COROUTINE_H diff --git a/engines/tinsel/cursor.cpp b/engines/tinsel/cursor.cpp new file mode 100644 index 0000000000..b95662cbfe --- /dev/null +++ b/engines/tinsel/cursor.cpp @@ -0,0 +1,647 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Cursor and cursor trails. + */ + +#include "tinsel/cursor.h" + +#include "tinsel/anim.h" +#include "tinsel/background.h" +#include "tinsel/cursor.h" +#include "tinsel/dw.h" +#include "tinsel/events.h"		// For EventsManager class +#include "tinsel/film.h" +#include "tinsel/graphics.h" +#include "tinsel/handle.h" +#include "tinsel/inventory.h" +#include "tinsel/multiobj.h"	// multi-part object defintions etc. +#include "tinsel/object.h" +#include "tinsel/pid.h" +#include "tinsel/sched.h" +#include "tinsel/timers.h"		// For ONE_SECOND constant +#include "tinsel/tinlib.h"		// resetidletime() +#include "tinsel/tinsel.h"		// For engine access + + +namespace Tinsel { + +//----------------- LOCAL DEFINES -------------------- + +#define ITERATION_BASE		FRAC_ONE +#define ITER_ACCELLERATION	(10L << (FRAC_BITS - 4)) + + +//----------------- LOCAL GLOBAL DATA -------------------- + +static OBJECT *McurObj = 0;		// Main cursor object +static OBJECT *AcurObj = 0;		// Auxiliary cursor object + +static ANIM McurAnim = {0,0,0,0,0};		// Main cursor animation structure +static ANIM AcurAnim = {0,0,0,0,0};		// Auxiliary cursor animation structure + +static bool bHiddenCursor = false;	// Set when cursor is hidden +static bool bTempNoTrailers = false;	// Set when cursor trails are hidden + +static bool bFrozenCursor = false;	// Set when cursor position is frozen + +static frac_t IterationSize = 0; + +static SCNHANDLE CursorHandle = 0;	// Handle to cursor reel data + +static int numTrails = 0; +static int nextTrail = 0; + +static bool bWhoa = false;		// Set by DropCursor() at the end of a scene +				// - causes cursor processes to do nothing +				// Reset when main cursor has re-initialised + +static bool restart = false;		// When main cursor has been bWhoa-ed, it waits +				// for this to be set to true. + +static short ACoX = 0, ACoY = 0;	// Auxillary cursor image's animation offsets + + + +#define MAX_TRAILERS	10 + +static struct { + +	ANIM	trailAnim;	// Animation structure +	OBJECT *trailObj;	// This trailer's object + +} ntrailData [MAX_TRAILERS]; + +static int lastCursorX = 0, lastCursorY = 0; + + +//----------------- FORWARD REFERENCES -------------------- + +static void MoveCursor(void); + +/** + * Initialise and insert a cursor trail object, set its Z-pos, and hide + * it. Also initialise its animation script. + */ +static void InitCurTrailObj(int i, int x, int y) { +	const FREEL *pfr;		// pointer to reel +	IMAGE *pim;		// pointer to image +	const MULTI_INIT *pmi;		// MULTI_INIT structure + +	const FILM *pfilm; + +	if (!numTrails) +		return; + +	// Get rid of old object +	if (ntrailData[i].trailObj != NULL) +		MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), ntrailData[i].trailObj); + +	pim = GetImageFromFilm(CursorHandle, i+1, &pfr, &pmi, &pfilm);// Get pointer to image +	assert(BackPal()); // No background palette +	pim->hImgPal = TO_LE_32(BackPal()); + +	// Initialise and insert the object, set its Z-pos, and hide it +	ntrailData[i].trailObj = MultiInitObject(pmi); +	MultiInsertObject(GetPlayfieldList(FIELD_STATUS), ntrailData[i].trailObj); +	MultiSetZPosition(ntrailData[i].trailObj, Z_CURSORTRAIL); +	MultiSetAniXY(ntrailData[i].trailObj, x, y); + +	// Initialise the animation script +	InitStepAnimScript(&ntrailData[i].trailAnim, ntrailData[i].trailObj, FROM_LE_32(pfr->script), ONE_SECOND / FROM_LE_32(pfilm->frate)); +	StepAnimScript(&ntrailData[i].trailAnim); +} + +/** + * Get the cursor position from the mouse driver. + */ +static bool GetDriverPosition(int *x, int *y) { +	Common::Point ptMouse = _vm->getMousePosition(); +	*x = ptMouse.x; +	*y = ptMouse.y; + +	return(*x >= 0 && *x <= SCREEN_WIDTH-1 && +		*y >= 0 && *y <= SCREEN_HEIGHT-1); +} + +/** + * Move the cursor relative to current position. + */ +void AdjustCursorXY(int deltaX, int deltaY) { +	int x, y; + +	if (deltaX || deltaY) { +		if (GetDriverPosition(&x, &y)) +			_vm->setMousePosition(Common::Point(x + deltaX, y + deltaY)); +	} +	MoveCursor(); +} + +/** + * Move the cursor to an absolute position. + */ +void SetCursorXY(int newx, int newy) { +	int	x, y; +	int	Loffset, Toffset;	// Screen offset + +	PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); +	newx -= Loffset;  +	newy -= Toffset; + +	if (GetDriverPosition(&x, &y)) +		_vm->setMousePosition(Common::Point(newx, newy)); +	MoveCursor(); +} + +/** + * Move the cursor to a screen position. + */ +void SetCursorScreenXY(int newx, int newy) { +	int	x, y; + +	if (GetDriverPosition(&x, &y)) +		_vm->setMousePosition(Common::Point(newx, newy)); +	MoveCursor(); +} + +/** + * Called by the world and his brother. + * Returns the cursor's animation position in (x,y). + * Returns false if there is no cursor object. + */ +bool GetCursorXYNoWait(int *x, int *y, bool absolute) { +	if (McurObj == NULL) { +		*x = *y = 0; +		return false; +	} + +	GetAniPosition(McurObj, x, y); + +	if (absolute) { +		int	Loffset, Toffset;	// Screen offset +		PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); +		*x += Loffset; +		*y += Toffset; +	} +	 +	return true; +} + +/** + * Called by the world and his brother. + * Returns the cursor's animation position. + * If called while there is no cursor object, the calling process ends + * up waiting until there is. + */ +void GetCursorXY(int *x, int *y, bool absolute) { +	//while (McurObj == NULL) +	//	ProcessSleepSelf(); +	assert(McurObj); +	GetCursorXYNoWait(x, y, absolute); +} + +/** + * Re-initialise the main cursor to use the main cursor reel. + * Called from TINLIB.C to restore cursor after hiding it. + * Called from INVENTRY.C to restore cursor after customising it. + */ +void RestoreMainCursor(void) { +	const FILM *pfilm; + +	if (McurObj != NULL) { +		pfilm = (const FILM *)LockMem(CursorHandle); + +		InitStepAnimScript(&McurAnim, McurObj, FROM_LE_32(pfilm->reels->script), ONE_SECOND / FROM_LE_32(pfilm->frate)); +		StepAnimScript(&McurAnim); +	} +	bHiddenCursor = false; +	bFrozenCursor = false; +} + +/** + * Called from INVENTRY.C to customise the main cursor. + */ +void SetTempCursor(SCNHANDLE pScript) { +	if (McurObj != NULL) +		InitStepAnimScript(&McurAnim, McurObj, pScript, 2); +} + +/** + * Hide the cursor. + */ +void DwHideCursor(void) { +	int i; + +	bHiddenCursor = true; + +	if (McurObj) +		MultiHideObject(McurObj); +	if (AcurObj) +		MultiHideObject(AcurObj); + +	for (i = 0; i < numTrails; i++) { +		if (ntrailData[i].trailObj != NULL) { +			MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), ntrailData[i].trailObj); +			ntrailData[i].trailObj = NULL; +		} +	} +} + +/** + * Unhide the cursor. + */ +void UnHideCursor(void) { +	bHiddenCursor = false; +} + +/** + * Freeze the cursor. + */ +void FreezeCursor(void) { +	bFrozenCursor = true; +} + +/** + * HideCursorTrails + */ +void HideCursorTrails(void) { +	int i; + +	bTempNoTrailers = true; + +	for (i = 0; i < numTrails; i++) 	{ +		if (ntrailData[i].trailObj != NULL) { +			MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), ntrailData[i].trailObj); +			ntrailData[i].trailObj = NULL; +		} +	} +} + +/** + * UnHideCursorTrails + */ +void UnHideCursorTrails(void) { +	bTempNoTrailers = false; +} + +/** + * Get pointer to image from a film reel. And the rest. + */ +IMAGE *GetImageFromReel(const FREEL *pfr, const MULTI_INIT **ppmi) { +	const MULTI_INIT *pmi; +	const FRAME *pFrame; + +	pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(pfr->mobj)); +	if (ppmi) +		*ppmi = pmi; + +	pFrame = (const FRAME *)LockMem(FROM_LE_32(pmi->hMulFrame)); + +	// get pointer to image +	return (IMAGE *)LockMem(READ_LE_UINT32(pFrame)); +} + +/** + * Get pointer to image from a film. And the rest. + */ +IMAGE *GetImageFromFilm(SCNHANDLE hFilm, int reel, const FREEL **ppfr, const MULTI_INIT **ppmi, const FILM **ppfilm) { +	const FILM *pfilm; +	const FREEL *pfr; + +	pfilm = (const FILM *)LockMem(hFilm); +	if (ppfilm) +		*ppfilm = pfilm; + +	pfr = &pfilm->reels[reel]; +	if (ppfr) +		*ppfr = pfr; + +	return GetImageFromReel(pfr, ppmi); +} + +/** + * Delete auxillary cursor. Restore animation offsets in the image. + */ +void DelAuxCursor(void) { +	if (AcurObj != NULL) { +		MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), AcurObj); +		AcurObj = NULL; +	} +} + +/** + * Set auxillary cursor. + * Save animation offsets from the image if required. + */ +void SetAuxCursor(SCNHANDLE hFilm) { +	IMAGE *pim;		// Pointer to auxillary cursor's image +	const FREEL *pfr; +	const MULTI_INIT *pmi; +	const FILM *pfilm; +	int	x, y;		// Cursor position + +	DelAuxCursor();		// Get rid of previous + +	GetCursorXY(&x, &y, false);	// Note: also waits for cursor to appear + +	pim = GetImageFromFilm(hFilm, 0, &pfr, &pmi, &pfilm);// Get pointer to image +	assert(BackPal()); // no background palette +	pim->hImgPal = TO_LE_32(BackPal());			// Poke in the background palette + +	ACoX = (short)(FROM_LE_16(pim->imgWidth)/2 - ((int16) FROM_LE_16(pim->anioffX))); +	ACoY = (short)(FROM_LE_16(pim->imgHeight)/2 - ((int16) FROM_LE_16(pim->anioffY))); + +	// Initialise and insert the auxillary cursor object +	AcurObj = MultiInitObject(pmi); +	MultiInsertObject(GetPlayfieldList(FIELD_STATUS), AcurObj); + +	// Initialise the animation and set its position +	InitStepAnimScript(&AcurAnim, AcurObj, FROM_LE_32(pfr->script), ONE_SECOND / FROM_LE_32(pfilm->frate)); +	MultiSetAniXY(AcurObj, x - ACoX, y - ACoY); +	MultiSetZPosition(AcurObj, Z_ACURSOR); + +	if (bHiddenCursor) +		MultiHideObject(AcurObj); +} + +/** + * MoveCursor + */ +static void MoveCursor(void) { +	int	startX, startY; +	Common::Point ptMouse; +	frac_t newX, newY; +	unsigned dir; + +	// get cursors start animation position +	GetCursorXYNoWait(&startX, &startY, false); + +	// get mouse drivers current position +	ptMouse = _vm->getMousePosition(); + +	// convert to fixed point +	newX = intToFrac(ptMouse.x); +	newY = intToFrac(ptMouse.y); + +	// modify mouse driver position depending on cursor keys +	if ((dir = _vm->getKeyDirection()) != 0) { +		if (dir & MSK_LEFT) +			newX -= IterationSize; + +		if (dir & MSK_RIGHT) +			newX += IterationSize; + +		if (dir & MSK_UP) +			newY -= IterationSize; + +		if (dir & MSK_DOWN) +			newY += IterationSize; + +		IterationSize += ITER_ACCELLERATION; + +		// set new mouse driver position +		_vm->setMousePosition(Common::Point(fracToInt(newX), fracToInt(newY))); +	} else + +		IterationSize = ITERATION_BASE; + +	// get new mouse driver position - could have been modified +	ptMouse = _vm->getMousePosition(); +	 +	if (lastCursorX != ptMouse.x || lastCursorY != ptMouse.y) { +		resetUserEventTime(); + +		if (!bTempNoTrailers && !bHiddenCursor) { +			InitCurTrailObj(nextTrail++, lastCursorX, lastCursorY); +			if (nextTrail == numTrails) +				nextTrail = 0; +		} +	} + +	// adjust cursor to new mouse position +	if (McurObj) +		MultiSetAniXY(McurObj, ptMouse.x, ptMouse.y); +	if (AcurObj != NULL) +		MultiSetAniXY(AcurObj, ptMouse.x - ACoX, ptMouse.y - ACoY); + +	if (InventoryActive() && McurObj) { +		// Notify the inventory +		Xmovement(ptMouse.x - startX); +		Ymovement(ptMouse.y - startY); +	} + +	lastCursorX = ptMouse.x; +	lastCursorY = ptMouse.y; +} + +/** + * Initialise cursor object. + */ +static void InitCurObj(void) { +	const FILM *pfilm; +	const FREEL *pfr; +	const MULTI_INIT *pmi; +	IMAGE *pim; + +	pim = GetImageFromFilm(CursorHandle, 0, &pfr, &pmi, &pfilm);// Get pointer to image +	assert(BackPal()); // no background palette +	pim->hImgPal = TO_LE_32(BackPal()); +//--- + +	AcurObj = NULL;		// No auxillary cursor + +	McurObj = MultiInitObject(pmi); +	MultiInsertObject(GetPlayfieldList(FIELD_STATUS), McurObj); + +	InitStepAnimScript(&McurAnim, McurObj, FROM_LE_32(pfr->script), ONE_SECOND / FROM_LE_32(pfilm->frate)); +} + +/** + * Initialise the cursor position. + */ +static void InitCurPos(void) { +	Common::Point ptMouse = _vm->getMousePosition(); +	lastCursorX = ptMouse.x; +	lastCursorY = ptMouse.y; + +	MultiSetZPosition(McurObj, Z_CURSOR); +	MoveCursor(); +	MultiHideObject(McurObj); + + 	IterationSize = ITERATION_BASE; +} + +/** + * CursorStoppedCheck + */ +static void CursorStoppedCheck(CORO_PARAM) { +	// COROUTINE +	CORO_BEGIN_CONTEXT; +	CORO_END_CONTEXT(_ctx); + +	CORO_BEGIN_CODE(_ctx); + +	// If scene is closing down +	if (bWhoa) { +		// ...wait for next scene start-up +		while (!restart) +			CORO_SLEEP(1); + +		// Re-initialise +		InitCurObj(); +		InitCurPos(); +		InventoryIconCursor();	// May be holding something + +		// Re-start the cursor trails +		restart = false;		// set all bits +		bWhoa = false; +	} +	CORO_END_CODE; +} + +/** + * The main cursor process. + */ +void CursorProcess(CORO_PARAM, const void *) { +	// COROUTINE +	CORO_BEGIN_CONTEXT; +	CORO_END_CONTEXT(_ctx); + +	CORO_BEGIN_CODE(_ctx); + +	while (!CursorHandle || !BackPal()) +		CORO_SLEEP(1); + +	InitCurObj(); +	InitCurPos(); +	InventoryIconCursor();		// May be holding something + +	bWhoa = false; +	restart = false; + +	while (1) { +		// allow rescheduling +		CORO_SLEEP(1); + +		// Stop/start between scenes +		CORO_INVOKE_0(CursorStoppedCheck); + +		// Step the animation script(s) +		StepAnimScript(&McurAnim); +		if (AcurObj != NULL) +			StepAnimScript(&AcurAnim); +		for (int i = 0; i < numTrails; i++) { +			if (ntrailData[i].trailObj != NULL) { +				if (StepAnimScript(&ntrailData[i].trailAnim) == ScriptFinished) { +					MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), ntrailData[i].trailObj); +					ntrailData[i].trailObj = NULL; +				} +			} +		} + +		// Move the cursor as appropriate +		if (!bFrozenCursor) +			MoveCursor(); + +		// If the cursor should be hidden... +		if (bHiddenCursor) { +			// ...hide the cursor object(s) +			MultiHideObject(McurObj); +			if (AcurObj) +				MultiHideObject(AcurObj); + +			for (int i = 0; i < numTrails; i++) { +				if (ntrailData[i].trailObj != NULL) +					MultiHideObject(ntrailData[i].trailObj); +			} + +			// Wait 'til cursor is again required. +			while (bHiddenCursor) { +				CORO_SLEEP(1); + +				// Stop/start between scenes +				CORO_INVOKE_0(CursorStoppedCheck); +			} +		} +	} +	CORO_END_CODE; +} + +/** + * Called from dec_cursor() Glitter function. + * Register the handle to cursor reel data. + */ +void DwInitCursor(SCNHANDLE bfilm) { +	const FILM *pfilm; + +	CursorHandle = bfilm; + +	pfilm = (const FILM *)LockMem(CursorHandle); +	numTrails = FROM_LE_32(pfilm->numreels) - 1; + +	assert(numTrails <= MAX_TRAILERS); +} + +/** + * DropCursor is called when a scene is closing down. + */ +void DropCursor(void) { +	AcurObj = NULL;		// No auxillary cursor +	McurObj = NULL;		// No cursor object (imminently deleted elsewhere) +	bHiddenCursor = false;	// Not hidden in next scene +	bTempNoTrailers = false;	// Trailers not hidden in next scene +	bWhoa = true;		// Suspend cursor processes + +	for (int i = 0; i < numTrails; i++) { +		if (ntrailData[i].trailObj != NULL) 		{ +			MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), ntrailData[i].trailObj); +			ntrailData[i].trailObj = NULL; +		} +	} +} + +/** + * RestartCursor is called when a new scene is starting up. + */ +void RestartCursor(void) { +	restart = true;	// Get the main cursor to re-initialise +} + +/** + * Called when restarting the game, ensures correct re-start with NULL + * pointers etc. + */ +void RebootCursor(void) { +	McurObj = AcurObj = NULL; +	for (int i = 0; i < MAX_TRAILERS; i++) +		ntrailData[i].trailObj = NULL; + +	bHiddenCursor = bTempNoTrailers = bFrozenCursor = false; + +	CursorHandle = 0; + +	bWhoa = false; +	restart = false; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/cursor.h b/engines/tinsel/cursor.h new file mode 100644 index 0000000000..15349dda26 --- /dev/null +++ b/engines/tinsel/cursor.h @@ -0,0 +1,56 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Clipping rectangle defines + */ + +#ifndef TINSEL_CURSOR_H	// prevent multiple includes +#define TINSEL_CURSOR_H + +#include "tinsel/dw.h"	// for SCNHANDLE + +namespace Tinsel { + +void AdjustCursorXY(int deltaX, int deltaY); +void SetCursorXY(int x, int y); +void SetCursorScreenXY(int newx, int newy); +void GetCursorXY(int *x, int *y, bool absolute); +bool GetCursorXYNoWait(int *x, int *y, bool absolute); + +void RestoreMainCursor(void); +void SetTempCursor(SCNHANDLE pScript); +void DwHideCursor(void); +void UnHideCursor(void); +void FreezeCursor(void); +void HideCursorTrails(void); +void UnHideCursorTrails(void); +void DelAuxCursor(void); +void SetAuxCursor(SCNHANDLE hFilm); +void DwInitCursor(SCNHANDLE bfilm); +void DropCursor(void); +void RestartCursor(void); +void RebootCursor(void); + +} // end of namespace Tinsel + +#endif	// TINSEL_CURSOR_H diff --git a/engines/tinsel/debugger.cpp b/engines/tinsel/debugger.cpp new file mode 100644 index 0000000000..dc37e6a9a1 --- /dev/null +++ b/engines/tinsel/debugger.cpp @@ -0,0 +1,162 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "tinsel/tinsel.h" +#include "tinsel/debugger.h" +#include "tinsel/inventory.h" +#include "tinsel/pcode.h" +#include "tinsel/scene.h" +#include "tinsel/sound.h" +#include "tinsel/music.h" +#include "tinsel/font.h" +#include "tinsel/strres.h" + +namespace Tinsel { + +//----------------- EXTERNAL FUNCTIONS --------------------- + +// In PDISPLAY.CPP +extern void TogglePathDisplay(void); +// In tinsel.cpp +extern void SetNewScene(SCNHANDLE scene, int entrance, int transition); +// In scene.cpp +extern SCNHANDLE GetSceneHandle(void); + +//----------------- SUPPORT FUNCTIONS --------------------- + +//static  +int strToInt(const char *s) { +	if (!*s) +		// No string at all +		return 0; +	else if (toupper(s[strlen(s) - 1]) != 'H') +		// Standard decimal string +		return atoi(s); + +	// Hexadecimal string +	uint tmp; +	sscanf(s, "%xh", &tmp); +	return (int)tmp; +} + +//----------------- CONSOLE CLASS  --------------------- + +Console::Console() : GUI::Debugger() { +	DCmd_Register("item",		WRAP_METHOD(Console, cmd_item)); +	DCmd_Register("scene",		WRAP_METHOD(Console, cmd_scene)); +	DCmd_Register("music",		WRAP_METHOD(Console, cmd_music)); +	DCmd_Register("sound",		WRAP_METHOD(Console, cmd_sound)); +	DCmd_Register("string",		WRAP_METHOD(Console, cmd_string)); +} + +Console::~Console() { +} + +bool Console::cmd_item(int argc, const char **argv) { +	if (argc < 2) { +		DebugPrintf("%s item_number\n", argv[0]); +		DebugPrintf("Sets the currently active 'held' item\n"); +		return true; +	} + +	HoldItem(INV_NOICON); +	HoldItem(strToInt(argv[1])); +	return false; +} + +bool Console::cmd_scene(int argc, const char **argv) { +	if (argc < 1 || argc > 3) { +		DebugPrintf("%s [scene_number [entry number]]\n", argv[0]); +		DebugPrintf("If no parameters are given, prints the current scene.\n"); +		DebugPrintf("Otherwise changes to the specified scene number. Entry number defaults to 1 if none provided\n"); +		return true; +	} + +	if (argc == 1) { +		DebugPrintf("Current scene is %d\n", GetSceneHandle() >> SCNHANDLE_SHIFT); +		return true; +	} + +	uint32 sceneNumber = (uint32)strToInt(argv[1]) << SCNHANDLE_SHIFT; +	int entryNumber = (argc >= 3) ? strToInt(argv[2]) : 1; + +	SetNewScene(sceneNumber, entryNumber, TRANS_CUT); +	return false; +} + +bool Console::cmd_music(int argc, const char **argv) { +	if (argc < 2) { +		DebugPrintf("%s track_number or %s -offset\n", argv[0], argv[0]); +		DebugPrintf("Plays the MIDI track number provided, or the offset inside midi.dat\n"); +		DebugPrintf("A positive number signifies a track number, whereas a negative signifies an offset\n"); +		return true; +	} + +	int param = strToInt(argv[1]); +	if (param == 0) { +		DebugPrintf("Track number/offset can't be 0!\n", argv[0]); +	} else if (param > 0) { +		// Track provided +		PlayMidiSequence(GetTrackOffset(param - 1), false); +	} else if (param < 0) { +		// Offset provided +		param = param * -1; +		PlayMidiSequence(param, false); +	} +	return true; +} + +bool Console::cmd_sound(int argc, const char **argv) { +	if (argc < 2) { +		DebugPrintf("%s id\n", argv[0]); +		DebugPrintf("Plays the sound with the given ID\n"); +		return true; +	} + +	int id = strToInt(argv[1]); +	if (_vm->_sound->sampleExists(id)) +		_vm->_sound->playSample(id, Audio::Mixer::kSpeechSoundType); +	else +		DebugPrintf("Sample %d does not exist!\n", id); + +	return true; +} + +bool Console::cmd_string(int argc, const char **argv) { +	if (argc < 2) { +		DebugPrintf("%s id\n", argv[0]); +		DebugPrintf("Prints the string with the given ID\n"); +		return true; +	} + +	char tmp[TBUFSZ]; +	int id = strToInt(argv[1]); +	LoadStringRes(id, tmp, TBUFSZ); +	DebugPrintf("%s\n", tmp); + +	return true; +} + +} // End of namespace Tinsel diff --git a/engines/tinsel/debugger.h b/engines/tinsel/debugger.h new file mode 100644 index 0000000000..219bc71224 --- /dev/null +++ b/engines/tinsel/debugger.h @@ -0,0 +1,49 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef TINSEL_DEBUGGER_H +#define TINSEL_DEBUGGER_H + +#include "gui/debugger.h" + +namespace Tinsel { + +class TinselEngine; + +class Console: public GUI::Debugger { +protected: +	bool cmd_item(int argc, const char **argv); +	bool cmd_scene(int argc, const char **argv); +	bool cmd_music(int argc, const char **argv); +	bool cmd_sound(int argc, const char **argv); +	bool cmd_string(int argc, const char **argv); +public: +	Console(); +	virtual ~Console(void); +}; + +} // End of namespace Tinsel + +#endif diff --git a/engines/tinsel/detection.cpp b/engines/tinsel/detection.cpp new file mode 100644 index 0000000000..a638dde2c5 --- /dev/null +++ b/engines/tinsel/detection.cpp @@ -0,0 +1,278 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "base/plugins.h" + +#include "common/advancedDetector.h" +#include "common/file.h" + +#include "tinsel/tinsel.h" + + +namespace Tinsel { + +struct TinselGameDescription { +	Common::ADGameDescription desc; + +	int gameID; +	int gameType; +	uint32 features; +	uint16 version; +}; + +uint32 TinselEngine::getGameID() const { +	return _gameDescription->gameID; +} + +uint32 TinselEngine::getFeatures() const { +	return _gameDescription->features; +} + +Common::Language TinselEngine::getLanguage() const {  +	return _gameDescription->desc.language;  +} + +Common::Platform TinselEngine::getPlatform() const { +	return _gameDescription->desc.platform; +} + +uint16 TinselEngine::getVersion() const { +	return _gameDescription->version; +} + +} + +static const PlainGameDescriptor tinselGames[] = { +	{"tinsel", "Tinsel engine game"}, +	{"dw", "Discworld"}, +	{"dw2", "Discworld 2: Mortality Bytes!"}, +	{0, 0} +}; + + +namespace Tinsel { + +static const TinselGameDescription gameDescriptions[] = { + +	// Note: versions with *.gra files use tinsel v1 (28/2/1995), whereas +	// versions with *.scn files tinsel v2 (7/5/1995) +	// Update: this is not entirely true, there were some versions released +	// with *.gra files and used tinsel v2 + +	{ +		{	// This version has *.gra files but uses tinsel v2 +			"dw", +			"Floppy", +			AD_ENTRY1s("dw.gra", "c8808ccd988d603dd35dff42013ae7fd", 781656), +			Common::EN_ANY, +			Common::kPlatformPC, +			Common::ADGF_NO_FLAGS +		}, +		GID_DW1, +		0, +		GF_FLOPPY, +		TINSEL_V2, +	}, + +	{	// English CD v1. This version has *.gra files but uses tinsel v2 +		{ +			"dw", +			"CD", +			{ +				{"dw.gra", 0, "c8808ccd988d603dd35dff42013ae7fd", 781656}, +				{"english.smp", 0, NULL, -1}, +				{NULL, 0, NULL, 0} +			}, +			Common::EN_ANY, +			Common::kPlatformPC, +			Common::ADGF_NO_FLAGS +		}, +		GID_DW1, +		0, +		GF_CD, +		TINSEL_V2, +	}, + +	{	// English CD v2 +		{ +			"dw", +			"CD", +			{ +				{"dw.scn", 0, "70955425870c7720d6eebed903b2ef41", 776188}, +				{"english.smp", 0, NULL, -1}, +				{NULL, 0, NULL, 0} +			}, +			Common::EN_ANY, +			Common::kPlatformPC, +			Common::ADGF_NO_FLAGS +		}, +		GID_DW1, +		0, +		GF_CD | GF_SCNFILES, +		TINSEL_V2, +	}, + +#if 0 +	{	// English Saturn CD +		{ +			"dw", +			"CD", +			{ +				{"dw.scn", 0, "6803f293c88758057cc685b9437f7637", 382248}, +				{"english.smp", 0, NULL, -1}, +				{NULL, 0, NULL, 0} +			}, +			Common::EN_ANY, +			Common::kPlatformPC, +			Common::ADGF_NO_FLAGS +		}, +		GID_DW1, +		0, +		GF_CD, +		TINSEL_V2, +	}, +#endif + +	{	// Demo from http://www.adventure-treff.de/specials/dl_demos.php +		{ +			"dw", +			"Demo", +			AD_ENTRY1s("dw.gra", "ce1b57761ba705221bcf70955b827b97", 441192), +			//AD_ENTRY1s("dw.scn", "ccd72f02183d0e96b6e7d8df9492cda8", 23308), +			Common::EN_ANY, +			Common::kPlatformPC, +			Common::ADGF_DEMO +		}, +		GID_DW1, +		0, +		GF_DEMO, +		TINSEL_V1, +	}, + +	{	// German CD re-release "Neon Edition" +		// Note: This release has ENGLISH.TXT (with german content) instead of GERMAN.TXT +		{ +			"dw", +			"CD", +			AD_ENTRY1s("dw.scn", "6182c7986eaec893c62fb6ea13a9f225", 774556), +			Common::DE_DEU, +			Common::kPlatformPC, +			Common::ADGF_NO_FLAGS +		}, +		GID_DW1, +		0, +		GF_CD | GF_SCNFILES, +		TINSEL_V2, +	}, +	 +	{ AD_TABLE_END_MARKER, 0, 0, 0, 0 } +}; + +/** + * The fallback game descriptor used by the Tinsel engine's fallbackDetector. + * Contents of this struct are to be overwritten by the fallbackDetector. + */ +static TinselGameDescription g_fallbackDesc = { +	{ +		"", +		"", +		AD_ENTRY1(0, 0), // This should always be AD_ENTRY1(0, 0) in the fallback descriptor +		Common::UNK_LANG, +		Common::kPlatformPC, +		Common::ADGF_NO_FLAGS +	}, +	0, +	0, +	0, +	0, +}; + +} // End of namespace Tinsel + +static const Common::ADParams detectionParams = { +	// Pointer to ADGameDescription or its superset structure +	(const byte *)Tinsel::gameDescriptions, +	// Size of that superset structure +	sizeof(Tinsel::TinselGameDescription), +	// Number of bytes to compute MD5 sum for +	5000, +	// List of all engine targets +	tinselGames, +	// Structure for autoupgrading obsolete targets +	0, +	// Name of single gameid (optional) +	"tinsel", +	// List of files for file-based fallback detection (optional) +	0, +	// Flags +	0 +}; + +class TinselMetaEngine : public Common::AdvancedMetaEngine { +public: +	TinselMetaEngine() : Common::AdvancedMetaEngine(detectionParams) {} + +	virtual const char *getName() const { +		return "Tinsel Engine"; +	} + +	virtual const char *getCopyright() const { +		return "Tinsel Engine"; +	} + +	virtual bool createInstance(OSystem *syst, Engine **engine, const Common::ADGameDescription *desc) const; + +	const Common::ADGameDescription *fallbackDetect(const FSList *fslist) const; + +}; + +bool TinselMetaEngine::createInstance(OSystem *syst, Engine **engine, const Common::ADGameDescription *desc) const { +	const Tinsel::TinselGameDescription *gd = (const Tinsel::TinselGameDescription *)desc; +	if (gd) { +		*engine = new Tinsel::TinselEngine(syst, gd); +	} +	return gd != 0; +} + +const Common::ADGameDescription *TinselMetaEngine::fallbackDetect(const FSList *fslist) const { +	// Set the default values for the fallback descriptor's ADGameDescription part. +	Tinsel::g_fallbackDesc.desc.language = Common::UNK_LANG; +	Tinsel::g_fallbackDesc.desc.platform = Common::kPlatformPC; +	Tinsel::g_fallbackDesc.desc.flags = Common::ADGF_NO_FLAGS; + +	// Set default values for the fallback descriptor's TinselGameDescription part. +	Tinsel::g_fallbackDesc.gameID = 0; +	Tinsel::g_fallbackDesc.features = 0; +	Tinsel::g_fallbackDesc.version = 0; + +	//return (const Common::ADGameDescription *)&Tinsel::g_fallbackDesc; +	return NULL; +} + +#if PLUGIN_ENABLED_DYNAMIC(TINSEL) +	REGISTER_PLUGIN_DYNAMIC(TINSEL, PLUGIN_TYPE_ENGINE, TinselMetaEngine); +#else +	REGISTER_PLUGIN_STATIC(TINSEL, PLUGIN_TYPE_ENGINE, TinselMetaEngine); +#endif diff --git a/engines/tinsel/dw.h b/engines/tinsel/dw.h new file mode 100644 index 0000000000..d14dd43fa2 --- /dev/null +++ b/engines/tinsel/dw.h @@ -0,0 +1,119 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef TINSEL_DW_H +#define TINSEL_DW_H + +#include "common/scummsys.h" +#include "common/endian.h" + +namespace Tinsel { + +/** scene handle data type */ +typedef uint32		SCNHANDLE; + +/** polygon handle */ +typedef int HPOLYGON; + + +#define	EOS_CHAR		'\0'		// string terminator +#define	LF_CHAR			'\x0a'		// line feed + +// file names +#define	SAMPLE_FILE		"english.smp"	// all samples +#define	SAMPLE_INDEX		"english.idx"	// sample index filename +#define	MIDI_FILE		"midi.dat"	// all MIDI sequences +#define	INDEX_FILENAME		"index"		// name of index file + +#define	SCNHANDLE_SHIFT		23		// amount to shift scene handles by +#define	NO_SCNHANDLES		300		// number of memory handles for scenes +#define	MASTER_SCNHANDLE	(0 << SCNHANDLE_SHIFT)	// master scene memory handle + +// the minimum value a integer number can have +#define	MIN_INT   (1 << (8*sizeof(int) - 1)) +#define	MIN_INT16 (-32767) + +// the maximum value a integer number can have +#define	MAX_INT	(~MIN_INT) + +// TODO: v1->v2 scene files +#ifdef FILE_SPLIT +// each scene is split into 2 files +#define	INV_OBJ_SCNHANDLE	(2 << SCNHANDLE_SHIFT)	// inventory object handle (if there are inventory objects) +#else +#define	INV_OBJ_SCNHANDLE	(1 << SCNHANDLE_SHIFT)	// inventory object handle (if there are inventory objects) +#endif + + +#define FIELD_WORLD	0 +#define FIELD_STATUS	1 + + + + +// We don't set the Z position for print and talk text +// i.e. it gets a Z position of 0 + +#define Z_INV_BRECT	10	// Inventory background rectangle +#define Z_INV_MFRAME	15	// Inventory window frame +#define Z_INV_HTEXT	15	// Inventory heading text +#define Z_INV_ICONS	16	// Icons in inventory +#define Z_INV_ITEXT	995	// Icon text + +#define Z_INV_RFRAME	22	// Re-sizing frame + +#define Z_CURSOR	1000	// Cursor +#define Z_CURSORTRAIL	999	// Cursor trails +#define Z_ACURSOR	990	// Auxillary cursor + +#define Z_TAG_TEXT	995	// In front of auxillary cursor + +#define Z_MDGROOVE	20 +#define Z_MDSLIDER	21 + +#define Z_TOPPLAY	100 + +#define Z_TOPW_TEXT	Z_TAG_TEXT + +// Started a collection of assorted maximum numbers here: +#define MAX_MOVERS	6	// Moving actors using path system +#define MAX_SAVED_ACTORS 32	// Saved 'Normal' actors +#define MAX_SAVED_ALIVES 512	// Saves actors'lives + +// Legal non-existant entrance number for LoadScene() +#define NO_ENTRY_NUM	(-3458)	// Magic unlikely number + + +#define SAMPLETIMEOUT	(15*ONE_SECOND) + +// Language for the resource strings +enum LANGUAGE { +	TXT_ENGLISH, TXT_FRENCH, TXT_GERMAN, +		TXT_ITALIAN, TXT_SPANISH +}; + +} // end of namespace Tinsel + +#endif	// TINSEL_DW_H diff --git a/engines/tinsel/effect.cpp b/engines/tinsel/effect.cpp new file mode 100644 index 0000000000..91645da71b --- /dev/null +++ b/engines/tinsel/effect.cpp @@ -0,0 +1,134 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +// Handles effect polygons. +// +// EffectPolyProcess() monitors triggering of effect code (i.e. a moving +// actor entering an effect polygon). +// EffectProcess() runs the appropriate effect code. +// +// NOTE: Currently will only run one effect process at a time, i.e. +// effect polygons will not currently nest. It won't be very difficult +// to fix this if required. + +#include "tinsel/actors.h" +#include "tinsel/dw.h" +#include "tinsel/events.h" +#include "tinsel/pid.h" +#include "tinsel/pcode.h"		// LEAD_ACTOR +#include "tinsel/polygons.h" +#include "tinsel/rince.h" +#include "tinsel/sched.h" + + +namespace Tinsel { + +struct EP_INIT { +	HPOLYGON	hEpoly; +	PMACTOR		pActor; +	int		index; +}; + +/** + * Runs an effect polygon's Glitter code with ENTER event, waits for the + * actor to leave that polygon. Then runs the polygon's Glitter code + * with LEAVE event. + */ +static void EffectProcess(CORO_PARAM, const void *param) { +	// COROUTINE +	CORO_BEGIN_CONTEXT; +	CORO_END_CONTEXT(_ctx); + +	EP_INIT *to = (EP_INIT *)param;		// get the stuff copied to process when it was created + +	CORO_BEGIN_CODE(_ctx); + +	int		x, y;		// Lead actor position + +	// Run effect poly enter script +	effRunPolyTinselCode(to->hEpoly, ENTER, to->pActor->actorID); + +	do { +		CORO_SLEEP(1); +		GetMActorPosition(to->pActor, &x, &y); +	} while (InPolygon(x, y, EFFECT) == to->hEpoly); + +	// Run effect poly leave script +	effRunPolyTinselCode(to->hEpoly, LEAVE, to->pActor->actorID); + +	SetMAinEffectPoly(to->index, false); + +	CORO_END_CODE; +} + +/** + * If the actor was not already in an effect polygon, checks to see if + * it has just entered one. If it has, a process is started up to run + * the polygon's Glitter code. + */ +static void FettleEffectPolys(int x, int y, int index, PMACTOR pActor) { +	HPOLYGON	hPoly; +	EP_INIT		epi; + +	// If just entered an effect polygon, the effect should be triggered. +	if (!IsMAinEffectPoly(index)) { +		hPoly = InPolygon(x, y, EFFECT); +		if (hPoly != NOPOLY) { +			//Just entered effect polygon +			SetMAinEffectPoly(index, true); + +			epi.hEpoly = hPoly; +			epi.pActor = pActor; +			epi.index = index; +			g_scheduler->createProcess(PID_TCODE, EffectProcess, &epi, sizeof(epi)); +		} +	} +} + +/** + * Just calls FettleEffectPolys() every clock tick. + */ +void EffectPolyProcess(CORO_PARAM, const void *param) { +	// COROUTINE +	CORO_BEGIN_CONTEXT; +	CORO_END_CONTEXT(_ctx); + +	CORO_BEGIN_CODE(_ctx); +	while (1) { +		for (int i = 0; i < MAX_MOVERS; i++) { +			PMACTOR pActor = GetLiveMover(i); +			if (pActor != NULL) { +				int	x, y; +				GetMActorPosition(pActor, &x, &y); +				FettleEffectPolys(x, y, i, pActor); +			} +		} + +		CORO_SLEEP(1);		// allow re-scheduling +	} +	CORO_END_CODE; +} + +} // End of namespace Tinsel diff --git a/engines/tinsel/events.cpp b/engines/tinsel/events.cpp new file mode 100644 index 0000000000..bf9f428fd4 --- /dev/null +++ b/engines/tinsel/events.cpp @@ -0,0 +1,439 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Main purpose is to process user events. + * Also provides a couple of utility functions. + */ + +#include "tinsel/actors.h" +#include "tinsel/config.h" +#include "tinsel/cursor.h" +#include "tinsel/dw.h" +#include "tinsel/events.h" +#include "tinsel/handle.h"	// For LockMem() +#include "tinsel/inventory.h" +#include "tinsel/move.h"	// For walking lead actor +#include "tinsel/pcode.h"	// For Interpret() +#include "tinsel/pid.h" +#include "tinsel/polygons.h" +#include "tinsel/rince.h"	// For walking lead actor +#include "tinsel/sched.h" +#include "tinsel/scroll.h"	// For DontScrollCursor() +#include "tinsel/timers.h"	// DwGetCurrentTime() +#include "tinsel/tinlib.h"	// For control() +#include "tinsel/token.h" + +namespace Tinsel { + +//----------------- EXTERNAL FUNCTIONS --------------------- + +// in PDISPLAY.C +extern int GetTaggedActor(void); +extern HPOLYGON GetTaggedPoly(void); + + +//----------------- EXTERNAL GLOBAL DATA --------------------- + +extern bool bEnableF1; + + +//----------------- LOCAL GLOBAL DATA -------------------- + +static int userEvents = 0;		// Whenever a button or a key comes in +static uint32 lastUserEvent = 0;	// Time it hapenned +static int butEvents = 0;	// Single or double, left or right. Or escape key. +static int escEvents = 0;	// Escape key + + +static int eCount = 0; + +/** + * Gets called before each schedule, only 1 user action per schedule + * is allowed. + */ +void ResetEcount(void) { +	eCount = 0; +} + + +void IncUserEvents(void) { +	userEvents++; +	lastUserEvent = DwGetCurrentTime(); +} + +/** + * If this is a single click, wait to check it's not the first half of a + * double click. + * If this is a double click, the process from the waiting single click + * gets killed. + */ +void AllowDclick(CORO_PARAM, BUTEVENT be) { +	CORO_BEGIN_CONTEXT; +	CORO_END_CONTEXT(_ctx); + +	CORO_BEGIN_CODE(_ctx); +	if (be == BE_SLEFT) { +		GetToken(TOKEN_LEFT_BUT); +		CORO_SLEEP(dclickSpeed+1); +		FreeToken(TOKEN_LEFT_BUT); + +		// Prevent activation of 2 events on the same tick +		if (++eCount != 1) +			CORO_KILL_SELF(); + +		break; + +	} else if (be == BE_DLEFT) { +		GetToken(TOKEN_LEFT_BUT); +		FreeToken(TOKEN_LEFT_BUT); +	} +	CORO_END_CODE; +} + +/** + * Take control from player, if the player has it. + * Return TRUE if control taken, FALSE if not. + */ + +bool GetControl(int param) { +	if (TestToken(TOKEN_CONTROL)) { +		control(param); +		return true; +	} else +		return false; +} + +struct TP_INIT { +	HPOLYGON	hPoly;		// Polygon +	USER_EVENT	event;		// Trigerring event +	BUTEVENT	bev;		// To allow for double clicks +	bool		take_control;	// Set if control should be taken +					// while code is running. +	int		actor; +}; + +/** + * Runs glitter code associated with a polygon. + */ +static void PolyTinselProcess(CORO_PARAM, const void *param) { +	// COROUTINE +	CORO_BEGIN_CONTEXT; +		INT_CONTEXT *pic; +		bool took_control;	// Set if this function takes control +	CORO_END_CONTEXT(_ctx); + +	TP_INIT *to = (TP_INIT *)param;	// get the stuff copied to process when it was created + +	CORO_BEGIN_CODE(_ctx); + +	CORO_INVOKE_1(AllowDclick, to->bev);	// May kill us if single click + +	// Control may have gone off during AllowDclick() +	if (!TestToken(TOKEN_CONTROL) +	    && (to->event == WALKTO || to->event == ACTION || to->event == LOOK)) +		CORO_KILL_SELF(); + +	// Take control, if requested +	if (to->take_control) +		_ctx->took_control = GetControl(CONTROL_OFF); +	else +		_ctx->took_control = false; + +	// Hide conversation if appropriate +	if (to->event == CONVERSE) +		convHide(true); + +	// Run the code +	_ctx->pic = InitInterpretContext(GS_POLYGON, getPolyScript(to->hPoly), to->event, to->hPoly, to->actor, NULL); +	CORO_INVOKE_1(Interpret, _ctx->pic); + +	// Free control if we took it +	if (_ctx->took_control) +		control(CONTROL_ON); + +	// Restore conv window if applicable +	if (to->event == CONVERSE) +		convHide(false); + +	CORO_END_CODE; +} + +/** + * Runs glitter code associated with a polygon. + */ +void RunPolyTinselCode(HPOLYGON hPoly, USER_EVENT event, BUTEVENT be, bool tc) { +	TP_INIT to = { hPoly, event, be, tc, 0 }; + +	g_scheduler->createProcess(PID_TCODE, PolyTinselProcess, &to, sizeof(to)); +} + +void effRunPolyTinselCode(HPOLYGON hPoly, USER_EVENT event, int actor) { +	TP_INIT to = { hPoly, event, BE_NONE, false, actor }; + +	g_scheduler->createProcess(PID_TCODE, PolyTinselProcess, &to, sizeof(to)); +} + +//----------------------------------------------------------------------- + +struct WP_INIT { +	int	x;	// } Where to walk to +	int	y;	// } +}; + +/** + * Perform a walk directly initiated by a click. + */ +static void WalkProcess(CORO_PARAM, const void *param) { +	// COROUTINE +	CORO_BEGIN_CONTEXT; +		PMACTOR pActor; +	CORO_END_CONTEXT(_ctx); + +	WP_INIT *to = (WP_INIT *)param;	// get the co-ordinates - copied to process when it was created + +	CORO_BEGIN_CODE(_ctx); + +	_ctx->pActor = GetMover(LEAD_ACTOR); +	if (_ctx->pActor->MActorState == NORM_MACTOR) { +		assert(_ctx->pActor->hCpath != NOPOLY); // Lead actor is not in a path + +		GetToken(TOKEN_LEAD); +		SetActorDest(_ctx->pActor, to->x, to->y, false, 0); +		DontScrollCursor(); + +		while (MAmoving(_ctx->pActor)) +			CORO_SLEEP(1); + +		FreeToken(TOKEN_LEAD); +	} + +	CORO_END_CODE; +} + +void walkto(int x, int y) { +	WP_INIT to = { x, y }; + +	g_scheduler->createProcess(PID_TCODE, WalkProcess, &to, sizeof(to)); +} + +/** + * Run appropriate actor or polygon glitter code. + * If none, and it's a WALKTO event, do a walk. + */ +static void User_Event(USER_EVENT uEvent, BUTEVENT be) { +	int	actor; +	int	aniX, aniY; +	HPOLYGON hPoly; + +	// Prevent activation of 2 events on the same tick +	if (++eCount != 1) +		return; + +	if ((actor = GetTaggedActor()) != 0) +		actorEvent(actor, uEvent, be); +	else if ((hPoly = GetTaggedPoly()) != NOPOLY) +		RunPolyTinselCode(hPoly, uEvent, be, false); +	else { +		GetCursorXY(&aniX, &aniY, true); + +		// There could be a poly involved which has no tag. +		if ((hPoly = InPolygon(aniX, aniY, TAG)) != NOPOLY +			|| (hPoly = InPolygon(aniX, aniY, EXIT)) != NOPOLY) { +			RunPolyTinselCode(hPoly, uEvent, be, false); +		} else if (uEvent == WALKTO) +			walkto(aniX, aniY); +	} +} + + +/** + * ProcessButEvent + */ +void ProcessButEvent(BUTEVENT be) { +	IncUserEvents(); + +	if (bSwapButtons) { +		switch (be) { +		case BE_SLEFT: +			be = BE_SRIGHT; +			break; +		case BE_DLEFT: +			be = BE_DRIGHT; +			break; +		case BE_SRIGHT: +			be = BE_SLEFT; +			break; +		case BE_DRIGHT: +			be = BE_DLEFT; +			break; +		case BE_LDSTART: +			be = BE_RDSTART; +			break; +		case BE_LDEND: +			be = BE_RDEND; +			break; +		case BE_RDSTART: +			be = BE_LDSTART; +			break; +		case BE_RDEND: +			be = BE_LDEND; +			break; +		default: +			break; +		} +	} + +//	if (be == BE_SLEFT || be == BE_DLEFT || be == BE_SRIGHT || be == BE_DRIGHT) +	if (be == BE_SLEFT || be == BE_SRIGHT) +		butEvents++; + +	if (!TestToken(TOKEN_CONTROL) && be != BE_LDEND) +		return; + +	if (InventoryActive()) { +		ButtonToInventory(be); +	} else { +		switch (be) { +		case BE_SLEFT: +			User_Event(WALKTO, BE_SLEFT); +			break; +	 +		case BE_DLEFT: +			User_Event(ACTION, BE_DLEFT); +			break; +	 +		case BE_SRIGHT: +			User_Event(LOOK, BE_SRIGHT); +			break; +	 +		default: +			break; +		} +	} +} + +/** + * ProcessKeyEvent + */ + +void ProcessKeyEvent(KEYEVENT ke) { +	// This stuff to allow F1 key during startup. +	if (bEnableF1 && ke == OPTION_KEY) +		control(CONTROL_ON); +	else +		IncUserEvents(); + +	if (ke == ESC_KEY) { +		escEvents++; +		butEvents++;		// Yes, I do mean this +	} + +	// FIXME: This comparison is weird - I added (BUTEVENT) cast for now to suppress warning +	if (!TestToken(TOKEN_CONTROL) && (BUTEVENT)ke != BE_LDEND) +		return; + +	switch (ke) { +	case QUIT_KEY: +		PopUpConf(QUIT); +		break; + +	case OPTION_KEY: +		PopUpConf(OPTION); +		break; + +	case SAVE_KEY: +		PopUpConf(SAVE); +		break; + +	case LOAD_KEY: +		PopUpConf(LOAD); +		break; + +	case WALKTO_KEY: +		if (InventoryActive()) +			ButtonToInventory(BE_SLEFT); +		else +			User_Event(WALKTO, BE_NONE); +		break; + +	case ACTION_KEY: +		if (InventoryActive()) +			ButtonToInventory(BE_DLEFT); +		else +			User_Event(ACTION, BE_NONE); +		break; + +	case LOOK_KEY: +		if (InventoryActive()) +			ButtonToInventory(BE_SRIGHT); +		else +			User_Event(LOOK, BE_NONE); +		break; + +	case ESC_KEY: +	case PGUP_KEY: +	case PGDN_KEY: +	case HOME_KEY: +	case END_KEY: +		if (InventoryActive()) +			KeyToInventory(ke); +		break; + +	default: +		break; +	} +} + +/** + * For ESCapable Glitter sequences + */ + +int GetEscEvents(void) { +	return escEvents; +} + +/** + * For cutting short talk()s etc. + */ + +int GetLeftEvents(void) { +	return butEvents; +} + +/** + * For waitkey() Glitter function + */ + +int getUserEvents(void) { +	return userEvents; +} + +uint32 getUserEventTime(void) { +	return DwGetCurrentTime() - lastUserEvent; +} + +void resetUserEventTime(void) { +	lastUserEvent = DwGetCurrentTime(); +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/events.h b/engines/tinsel/events.h new file mode 100644 index 0000000000..bc49d68717 --- /dev/null +++ b/engines/tinsel/events.h @@ -0,0 +1,84 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * User events processing and utility functions + */ + +#ifndef TINSEL_EVENTS_H +#define TINSEL_EVENTS_H + +#include "tinsel/dw.h" +#include "tinsel/coroutine.h" + +namespace Tinsel { + +enum BUTEVENT { +	BE_NONE, BE_SLEFT, BE_DLEFT, BE_SRIGHT, BE_DRIGHT, +	BE_LDSTART, BE_LDEND, BE_RDSTART, BE_RDEND, +	BE_UNKNOWN +}; + + +enum KEYEVENT { +	ESC_KEY, QUIT_KEY, SAVE_KEY, LOAD_KEY, OPTION_KEY, +	PGUP_KEY, PGDN_KEY, HOME_KEY, END_KEY, +	WALKTO_KEY, ACTION_KEY, LOOK_KEY, +	NOEVENT_KEY +}; + + +/** + * Reasons for running Glitter code. + * Do not re-order these as equivalent CONSTs are defined in the master + * scene Glitter source file for testing against the event() library function. + */ +enum USER_EVENT { +	POINTED, WALKTO, ACTION, LOOK, +	ENTER, LEAVE, STARTUP, CONVERSE, +	UNPOINT, PUTDOWN, +	NOEVENT +}; + + +void AllowDclick(CORO_PARAM, BUTEVENT be); +bool GetControl(int param); + +void RunPolyTinselCode(HPOLYGON hPoly, USER_EVENT event, BUTEVENT be, bool tc); +void effRunPolyTinselCode(HPOLYGON hPoly, USER_EVENT event, int actor); + +void ProcessButEvent(BUTEVENT be); +void ProcessKeyEvent(KEYEVENT ke); + + +int GetEscEvents(void); +int GetLeftEvents(void); +int getUserEvents(void); + +uint32 getUserEventTime(void); +void resetUserEventTime(void); + +void ResetEcount(void); + +} // end of namespace Tinsel + +#endif /* TINSEL_EVENTS_H */ diff --git a/engines/tinsel/faders.cpp b/engines/tinsel/faders.cpp new file mode 100644 index 0000000000..0018727ccb --- /dev/null +++ b/engines/tinsel/faders.cpp @@ -0,0 +1,175 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Palette Fader and Flasher processes. + */ + +#include "tinsel/pid.h"	// list of all process IDs +#include "tinsel/sched.h"	// scheduler defs +#include "tinsel/faders.h"	// fader defs +#include "tinsel/handle.h" +#include "tinsel/palette.h"	// Palette Manager defs + +namespace Tinsel { + +/** structure used by the "FadeProcess" process */ +struct FADE { +	const long *pColourMultTable;	// list of fixed point colour multipliers - terminated with negative entry +	PALQ *pPalQ;		// palette queue entry to fade +}; + +// fixed point fade multiplier tables +//const long fadeout[] = {0xf000, 0xd000, 0xb000, 0x9000, 0x7000, 0x5000, 0x3000, 0x1000, 0, -1}; +const long fadeout[] = {0xd000, 0xa000, 0x7000, 0x4000, 0x1000, 0, -1}; +//const long fadein[] = {0, 0x1000, 0x3000, 0x5000, 0x7000, 0x9000, 0xb000, 0xd000, 0x10000L, -1}; +const long fadein[] = {0, 0x1000, 0x4000, 0x7000, 0xa000, 0xd000, 0x10000L, -1}; + +/** + * Scale 'colour' by the fixed point colour multiplier 'colourMult' + * @param colour			Colour to scale + * @param colourMult		Fixed point multiplier + */ +static COLORREF ScaleColour(COLORREF colour, uint32 colourMult)	{ +	// apply multiplier to RGB components +	uint32 red   = ((GetRValue(colour) * colourMult) << 8) >> 24; +	uint32 green = ((GetGValue(colour) * colourMult) << 8) >> 24; +	uint32 blue  = ((GetBValue(colour) * colourMult) << 8) >> 24; + +	// return new colour +	return RGB(red, green, blue); +} + +/** + * Applies the fixed point multiplier 'mult' to all colours in + * 'pOrig' to produce 'pNew'. Each colour in the palette will be + * multiplied by 'mult'. + * @param pNew				Pointer to new palette + * @param pOrig				Pointer to original palette + * @param numColours		Number of colours in the above palettes + * @param mult				Fixed point multiplier + */ +static void FadePalette(COLORREF *pNew, COLORREF *pOrig, int numColours, uint32 mult) { +	for (int i = 0; i < numColours; i++, pNew++, pOrig++) { +		// apply multiplier to RGB components +		*pNew = ScaleColour(*pOrig, mult); +	} +} + +/** + * Process to fade one palette. + * A pointer to a 'FADE' structure must be passed to this process when + * it is created. + */ +static void FadeProcess(CORO_PARAM, const void *param) { +	// COROUTINE +	CORO_BEGIN_CONTEXT; +		COLORREF fadeRGB[MAX_COLOURS];	// local copy of palette +		const long *pColMult;			// pointer to colour multiplier table +		PALETTE *pPalette;		// pointer to palette +	CORO_END_CONTEXT(_ctx); + +	// get the fade data structure - copied to process when it was created +	FADE *pFade = (FADE *)param; + +	CORO_BEGIN_CODE(_ctx); + +	// get pointer to palette - reduce pointer indirection a bit +	_ctx->pPalette = (PALETTE *)LockMem(pFade->pPalQ->hPal); + +	for (_ctx->pColMult = pFade->pColourMultTable; *_ctx->pColMult >= 0; _ctx->pColMult++) { +		// go through all multipliers in table - until a negative entry + +		// fade palette using next multiplier +		FadePalette(_ctx->fadeRGB, _ctx->pPalette->palRGB, +			FROM_LE_32(_ctx->pPalette->numColours), (uint32) *_ctx->pColMult); + +		// send new palette to video DAC +		UpdateDACqueue(pFade->pPalQ->posInDAC, FROM_LE_32(_ctx->pPalette->numColours), _ctx->fadeRGB); + +		// allow time for video DAC to be updated +		CORO_SLEEP(1); +	} + +	CORO_END_CODE; +} + +/** + * Generic palette fader/unfader. Creates a 'FadeProcess' process + * for each palette that is to fade. + * @param multTable			Fixed point colour multiplier table + * @param noFadeTable		List of palettes not to fade + */ +static void Fader(const long multTable[], SCNHANDLE noFadeTable[]) { +	PALQ *pPal;	// palette manager iterator + +	// create a process for each palette in the palette queue +	for (pPal = GetNextPalette(NULL); pPal != NULL; pPal = GetNextPalette(pPal)) { +		bool bFade = true; +			// assume we want to fade this palette + +		// is palette in the list of palettes not to fade +		if (noFadeTable != NULL) { +			// there is a list of palettes not to fade +			for (int i = 0; noFadeTable[i] != 0; i++) { +				if (pPal->hPal == noFadeTable[i]) { +					// palette is in the list - dont fade it +					bFade = false; + +					// leave loop prematurely +					break; +				} +			} +		} + +		if (bFade) { +			FADE fade; + +			// fill in FADE struct +			fade.pColourMultTable	= multTable; +			fade.pPalQ		= pPal; + +			// create a fader process for this palette +			g_scheduler->createProcess(PID_FADER, FadeProcess, (void *)&fade, sizeof(FADE)); +		} +	} +} + +/** + * Fades a list of palettes down to black. + * @param noFadeTable		A NULL terminated list of palettes not to fade. + */ +void FadeOutFast(SCNHANDLE noFadeTable[]) { +	// call generic fader +	Fader(fadeout, noFadeTable); +} + +/** + * Fades a list of palettes from black to their current colours. + * @param noFadeTable		A NULL terminated list of palettes not to fade. + */ +void FadeInFast(SCNHANDLE noFadeTable[]) { +	// call generic fader +	Fader(fadein, noFadeTable); +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/faders.h b/engines/tinsel/faders.h new file mode 100644 index 0000000000..1e9336fae8 --- /dev/null +++ b/engines/tinsel/faders.h @@ -0,0 +1,55 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Data structures used by the fader and flasher processes + */ + +#ifndef TINSEL_FADERS_H		// prevent multiple includes +#define TINSEL_FADERS_H + +#include "tinsel/dw.h"	// for SCNHANDLE + +namespace Tinsel { + +enum { +	/** +	 * Number of iterations in a fade out. +	 * Must match which FadeOut() is in use. +	 */ +	COUNTOUT_COUNT  = 6 +}; + +/*----------------------------------------------------------------------*\ +|*                      Fader Function Prototypes                       *| +\*----------------------------------------------------------------------*/ + +// usefull palette faders - they all need a list of palettes that +//				should not be faded. This parameter can be +//				NULL - fade all palettes. + +void FadeOutFast(SCNHANDLE noFadeTable[]); +void FadeInFast(SCNHANDLE noFadeTable[]); + +} // end of namespace Tinsel + +#endif		// TINSEL_FADERS_H diff --git a/engines/tinsel/film.h b/engines/tinsel/film.h new file mode 100644 index 0000000000..c8bf4604bc --- /dev/null +++ b/engines/tinsel/film.h @@ -0,0 +1,50 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef TINSEL_FILM_H	// prevent multiple includes +#define TINSEL_FILM_H + +#include "tinsel/dw.h" + +namespace Tinsel { + +#include "common/pack-start.h"	// START STRUCT PACKING + +struct FREEL { +	SCNHANDLE mobj; +	SCNHANDLE script; +} PACKED_STRUCT; + +struct FILM { +	int32	frate; +	int32	numreels; +	FREEL	reels[1]; +} PACKED_STRUCT; + +#include "common/pack-end.h"	// END STRUCT PACKING + +} // end of namespace Tinsel + +#endif diff --git a/engines/tinsel/font.cpp b/engines/tinsel/font.cpp new file mode 100644 index 0000000000..620298867e --- /dev/null +++ b/engines/tinsel/font.cpp @@ -0,0 +1,96 @@ +/* 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. + * + * $URL$ + * $Id$ + */ + +#include "tinsel/dw.h" +#include "tinsel/font.h" +#include "tinsel/handle.h" +#include "tinsel/object.h" +#include "tinsel/text.h" + +namespace Tinsel { + +//----------------- LOCAL GLOBAL DATA -------------------- + +static char tBuffer[TBUFSZ]; + +static SCNHANDLE hTagFont = 0, hTalkFont = 0; + + +/** + * Return address of tBuffer + */ +char *tBufferAddr() { +	return tBuffer; +} + +/** + * Return hTagFont handle. + */ +SCNHANDLE hTagFontHandle() { +	return hTagFont; +} + +/** + * Return hTalkFont handle. + */ +SCNHANDLE hTalkFontHandle() { +	return hTalkFont; +} + +/** + * Called from dec_tagfont() Glitter function. Store the tag font handle. + */ +void TagFontHandle(SCNHANDLE hf) { +	hTagFont = hf;		// Store the font handle +} + +/** + * Called from dec_talkfont() Glitter function. + * Store the talk font handle. + */ +void TalkFontHandle(SCNHANDLE hf) { +	hTalkFont = hf;		// Store the font handle +} + +/** + * Poke the background palette into character 0's images. + */ +void fettleFontPal(SCNHANDLE fontPal) { +	const FONT *pFont; +	IMAGE *pImg; + +	assert(fontPal); +	assert(hTagFont); // Tag font not declared +	assert(hTalkFont); // Talk font not declared + +	pFont = (const FONT *)LockMem(hTagFont); +	pImg = (IMAGE *)LockMem(FROM_LE_32(pFont->fontInit.hObjImg));	// get image for char 0 +	pImg->hImgPal = TO_LE_32(fontPal); + +	pFont = (const FONT *)LockMem(hTalkFont); +	pImg = (IMAGE *)LockMem(FROM_LE_32(pFont->fontInit.hObjImg));	// get image for char 0 +	pImg->hImgPal = TO_LE_32(fontPal); +} + +} // End of namespace Tinsel diff --git a/engines/tinsel/font.h b/engines/tinsel/font.h new file mode 100644 index 0000000000..b75c36191c --- /dev/null +++ b/engines/tinsel/font.h @@ -0,0 +1,48 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef TINSEL_FONT_H	// prevent multiple includes +#define TINSEL_FONT_H + +#include "tinsel/dw.h" + +namespace Tinsel { + +// A temporary buffer for extracting text into is defined in font.c +// Accessed using tBufferAddr(), this is how big it is: +#define TBUFSZ	512 + + +char *tBufferAddr(void); +SCNHANDLE hTagFontHandle(void); +SCNHANDLE hTalkFontHandle(void); + +void TagFontHandle(SCNHANDLE hf); +void TalkFontHandle(SCNHANDLE hf); +void fettleFontPal(SCNHANDLE fontPal); + +} // end of namespace Tinsel + +#endif		// TINSEL_FONT_H diff --git a/engines/tinsel/graphics.cpp b/engines/tinsel/graphics.cpp new file mode 100644 index 0000000000..cd0937d944 --- /dev/null +++ b/engines/tinsel/graphics.cpp @@ -0,0 +1,440 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Low level graphics interface. + */ + +#include "tinsel/graphics.h" +#include "tinsel/handle.h"	// LockMem() +#include "tinsel/object.h" +#include "tinsel/palette.h" +#include "tinsel/tinsel.h" + +namespace Tinsel { + +//----------------- LOCAL DEFINES -------------------- + +// Defines used in graphic drawing +#define CHARPTR_OFFSET 16 +#define CHAR_WIDTH 4 +#define CHAR_HEIGHT 4 + +extern uint8 transPalette[MAX_COLOURS]; + +//----------------- SUPPORT FUNCTIONS --------------------- + +/** + * Straight rendering with transparency support + */ + +static void WrtNonZero(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyClipping) { +	// Set up the offset between destination blocks +	int rightClip = applyClipping ? pObj->rightClip : 0; +	Common::Rect boxBounds; + +	if (applyClipping) { +		// Adjust the height down to skip any bottom clipping +		pObj->height -= pObj->botClip; + +		// Make adjustment for the top clipping row +		srcP += sizeof(uint16) * ((pObj->width + 3) >> 2) * (pObj->topClip >> 2); +		pObj->height -= pObj->topClip; +		pObj->topClip %= 4; +	} + +	// Vertical loop +	while (pObj->height > 0) { +		// Get the start of the next line output +		uint8 *tempDest = destP; + +		// Get the line width, and figure out which row range within the 4 row high blocks +		// will be displayed if clipping is to be taken into account +		int width = pObj->width; + +		if (!applyClipping) { +			// No clipping, so so set box bounding area for drawing full 4x4 pixel blocks +			boxBounds.top = 0; +			boxBounds.bottom = 3; +			boxBounds.left = 0; +		} else { +			// Handle any possible clipping at the top of the char block. +			// We already handled topClip partially at the beginning of this function. +			// Hence the only non-zero values it can assume at this point are 1,2,3, +			// and that only during the very first iteration (i.e. when the top char +			// block is drawn only partially). In particular, we set topClip to zero, +			// as all following blocks are not to be top clipped. +			boxBounds.top = pObj->topClip; +			pObj->topClip = 0; + +			boxBounds.bottom = MIN(boxBounds.top + pObj->height - 1, 3); + +			// Handle any possible clipping at the start of the line +			boxBounds.left = pObj->leftClip; +			if (boxBounds.left >= 4) { +				srcP += sizeof(uint16) * (boxBounds.left >> 2); +				width -= boxBounds.left & 0xfffc; +				boxBounds.left %= 4; +			} + +			width -= boxBounds.left; +		} + +		// Horizontal loop +		while (width > rightClip) { +			boxBounds.right = MIN(boxBounds.left + width - rightClip - 1, 3); +			assert(boxBounds.bottom >= boxBounds.top); +			assert(boxBounds.right >= boxBounds.left); + +			int16 indexVal = READ_LE_UINT16(srcP); +			srcP += sizeof(uint16); + +			if (indexVal >= 0) { +				// Draw a 4x4 block based on the opcode as in index into the block list +				const uint8 *p = (uint8 *)pObj->charBase + (indexVal << 4); +				p += boxBounds.top * sizeof(uint32); +				for (int yp = boxBounds.top; yp <= boxBounds.bottom; ++yp, p += sizeof(uint32)) { +					Common::copy(p + boxBounds.left, p + boxBounds.right + 1, tempDest + (SCREEN_WIDTH * (yp - boxBounds.top))); +				} + +			} else { +				// Draw a 4x4 block with transparency support +				indexVal &= 0x7fff; + +				// If index is zero, then skip drawing the block completely +				if (indexVal > 0) { +					// Use the index along with the object's translation offset +					const uint8 *p = (uint8 *)pObj->charBase + ((pObj->transOffset + indexVal) << 4); + +					// Loop through each pixel - only draw a pixel if it's non-zero +					p += boxBounds.top * sizeof(uint32); +					for (int yp = boxBounds.top; yp <= boxBounds.bottom; ++yp) { +						p += boxBounds.left; +						for (int xp = boxBounds.left; xp <= boxBounds.right; ++xp, ++p) { +							if (*p) +								*(tempDest + SCREEN_WIDTH * (yp - boxBounds.top) + (xp - boxBounds.left)) = *p; +						} +						p += 3 - boxBounds.right; +					} +				} +			} + +			tempDest += boxBounds.right - boxBounds.left + 1; +			width -= 3 - boxBounds.left + 1; +			 +			// None of the remaining horizontal blocks should be left clipped +			boxBounds.left = 0; +		} + +		// If there is any width remaining, there must be a right edge clipping +		if (width >= 0) +			srcP += sizeof(uint16) * ((width + 3) >> 2); + +		// Move to next line line +		pObj->height -= boxBounds.bottom - boxBounds.top + 1; +		destP += (boxBounds.bottom - boxBounds.top + 1) * SCREEN_WIDTH; +	} +} + +/** + * Fill the destination area with a constant colour + */ + +static void WrtConst(DRAWOBJECT *pObj, uint8 *destP, bool applyClipping) { +	if (applyClipping) { +		pObj->height -= pObj->topClip + pObj->botClip; +		pObj->width -= pObj->leftClip + pObj->rightClip; + +		if (pObj->width <= 0) +			return; +	} + +	// Loop through any remaining lines +	while (pObj->height > 0) { +		Common::set_to(destP, destP + pObj->width, pObj->constant); + +		--pObj->height; +		destP += SCREEN_WIDTH; +	} +} + +/** + * Translates the destination surface within the object's bounds using the transparency + * lookup table from transpal.cpp (the contents of which have been moved into palette.cpp) + */ + +static void WrtTrans(DRAWOBJECT *pObj, uint8 *destP, bool applyClipping) { +	if (applyClipping) { +		pObj->height -= pObj->topClip + pObj->botClip; +		pObj->width -= pObj->leftClip + pObj->rightClip; + +		if (pObj->width <= 0) +			return; +	} + +	// Set up the offset between destination lines  +	int lineOffset = SCREEN_WIDTH - pObj->width; + +	// Loop through any remaining lines +	while (pObj->height > 0) { +		for (int i = 0; i < pObj->width; ++i, ++destP) +			*destP = transPalette[*destP]; + +		--pObj->height; +		destP += lineOffset; +	}		 +} + + +#if 0 +// This commented out code is the untested original WrtNonZero/ClpWrtNonZero combo method +// from the v1 source. It may be needed to be included later on to support v1 gfx files + +/** + * Straight rendering with transparency support + * Possibly only used in the Discworld Demo + */ + +static void DemoWrtNonZero(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyClipping) { +	// FIXME: If this method is used for the demo, it still needs to be made Endian safe + +	// Set up the offset between destination lines +	pObj->lineoffset = SCREEN_WIDTH - pObj->width - (applyClipping ? pObj->leftClip - pObj->rightClip : 0); + +	// Top clipped line handling +	while (applyClipping && (pObj->topClip > 0)) { +		// Loop through discarding the data for the line +		int width = pObj->width; +		while (width > 0) { +			int32 opcodeOrLen = (int32)READ_LE_UINT32(srcP); +			srcP += sizeof(uint32); + +			if (opcodeOrLen >= 0) { +				// Dump the data +				srcP += ((opcodeOrLen + 3) / 4) * 4; +				width -= opcodeOrLen; +			} else { +				// Dump the run-length opcode +				width -= -opcodeOrLen; +			} +		} + +		--pObj->height; +		--pObj->topClip; +	} + +	// Loop for the required number of rows +	while (pObj->height > 0) { + +		int width = pObj->width; +		 +		// Handling for left edge clipping - this basically involves dumping data until we reach +		// the part of the line to be displayed +		int clipLeft = pObj->leftClip; +		while (applyClipping && (clipLeft > 0)) { +			int32 opcodeOrLen = (int32)READ_LE_UINT32(srcP); +			srcP += sizeof(uint32); + +			if (opcodeOrLen >= 0) { +				// Copy a specified number of bytes +				// Make adjustments for past the clipping width +				int remainder = 4 - (opcodeOrLen % 4); +				srcP += MIN(clipLeft, opcodeOrLen); +				opcodeOrLen -= MIN(clipLeft, opcodeOrLen); +				clipLeft -= MIN(clipLeft, opcodeOrLen); +				width -= opcodeOrLen; +				 + +				// Handle any right edge clipping (if run length covers entire width) +				if (width < pObj->rightClip) { +					remainder += (pObj->rightClip - width); +					opcodeOrLen -= (pObj->rightClip - width); +				} +				 +				if (opcodeOrLen > 0)  +					Common::copy(srcP, srcP + opcodeOrLen, destP); + +			} else { +				// Output a run length number of bytes +				// Get data for byte value and run length +				opcodeOrLen = -opcodeOrLen; +				int runLength = opcodeOrLen & 0xff; +				uint8 colourVal = (opcodeOrLen >> 8) & 0xff; + +				// Make adjustments for past the clipping width +				runLength -= MIN(clipLeft, runLength); +				clipLeft -= MIN(clipLeft, runLength); +				width -= runLength; + +				// Handle any right edge clipping (if run length covers entire width) +				if (width < pObj->rightClip)  +					runLength -= (pObj->rightClip - width); + +				if (runLength > 0) { +					// Displayable part starts partway through the slice +					if (colourVal != 0) +						Common::set_to(destP, destP + runLength, colourVal); +					destP += runLength; +				} +			} + +			if (width < pObj->rightClip) +				width = 0; +		} + +		// Handling for the visible part of the line +		int endWidth = applyClipping ? pObj->rightClip : 0; +		while (width > endWidth) { +			int32 opcodeOrLen = (int32)READ_LE_UINT32(srcP); +			srcP += sizeof(uint32); + +			if (opcodeOrLen >= 0) { +				// Copy the specified number of bytes +				int remainder = 4 - (opcodeOrLen % 4); + +				if (width < endWidth) { +					// Shorten run length by right clipping +					remainder += (pObj->rightClip - width); +					opcodeOrLen -= (pObj->rightClip - width); +				} + +				Common::copy(srcP, srcP + opcodeOrLen, destP); +				srcP += opcodeOrLen + remainder; +				destP += opcodeOrLen; +				width -= opcodeOrLen; + +			} else { +				// Handle a given run length +				opcodeOrLen = -opcodeOrLen; +				int runLength = opcodeOrLen & 0xff; +				uint8 colourVal = (opcodeOrLen >> 8) & 0xff; + +				if (width < endWidth)  +					// Shorten run length by right clipping +					runLength -= (pObj->rightClip - width); + +				// Only set pixels if colourVal non-zero (0 signifies transparency) +				if (colourVal != 0)  +					// Fill out a run length of a specified colour +					Common::set_to(destP, destP + runLength, colourVal); + +				destP += runLength; +				width -= runLength; +			} +		} + +		// If right edge clipping is being applied, then width may still be non-zero - in +		// that case all remaining line data until the end of the line must be ignored +		while (width > 0) { +			int32 opcodeOrLen = (int32)READ_LE_UINT32(srcP); +			srcP += sizeof(uint32); + +			if (opcodeOrLen >= 0) { +				// Dump the data +				srcP += ((opcodeOrLen + 3) / 4) * 4; +				width -= opcodeOrLen; +			} else { +				// Dump the run-length opcode +				width -= -opcodeOrLen; +			} +		} + +		--pObj->height; +		destP += pObj->lineoffset; +	} +} +#endif + +//----------------- MAIN FUNCTIONS --------------------- + +/** + * Clears both the screen surface buffer and screen to the specified value + */ +void ClearScreen() { +	void *pDest = _vm->screen().getBasePtr(0, 0); +	memset(pDest, 0, SCREEN_WIDTH * SCREEN_HEIGHT); +	g_system->clearScreen(); +	g_system->updateScreen();  +} + +/** + * Updates the screen surface within the following rectangle + */ +void UpdateScreenRect(const Common::Rect &pClip) { +	byte *pDest = (byte *)_vm->screen().getBasePtr(pClip.left, pClip.top); +	g_system->copyRectToScreen(pDest, _vm->screen().pitch, pClip.left, pClip.top, pClip.width(), pClip.height()); +	g_system->updateScreen();  +} + +/** + * Draws the specified object onto the screen surface buffer + */ +void DrawObject(DRAWOBJECT *pObj) { +	uint8 *srcPtr = NULL; +	uint8 *destPtr; + +	if ((pObj->width <= 0) || (pObj->height <= 0))  +		// Empty image, so return immediately +		return; + +	// If writing constant data, don't bother locking the data pointer and reading src details +	if ((pObj->flags & DMA_CONST) == 0) { +		byte *p = (byte *)LockMem(pObj->hBits & 0xFF800000); +		 +		srcPtr = p + (pObj->hBits & 0x7FFFFF); +		pObj->charBase = (char *)p + READ_LE_UINT32(p + 0x10); +		pObj->transOffset = READ_LE_UINT32(p + 0x14); +	} + +	// Get destination starting point +	destPtr = (byte *)_vm->screen().getBasePtr(pObj->xPos, pObj->yPos); +	 +	// Handle various draw types +	uint8 typeId = pObj->flags & 0xff; +	switch (typeId) { +	case 0x01: +	case 0x08: +	case 0x41: +	case 0x48: +		WrtNonZero(pObj, srcPtr, destPtr, typeId >= 0x40); +		break; + +	case 0x04: +	case 0x44: +		// ClpWrtConst with/without clipping +		WrtConst(pObj,destPtr, typeId == 0x44); +		break; + +	case 0x84: +	case 0xC4: +		// WrtTrans with/without clipping +		WrtTrans(pObj, destPtr, typeId == 0xC4); +		break; + +	default: +		// NoOp +		error("Unknown drawing type %d", typeId); +		break; +	} +} + +} // End of namespace Tinsel diff --git a/engines/tinsel/graphics.h b/engines/tinsel/graphics.h new file mode 100644 index 0000000000..85299d4873 --- /dev/null +++ b/engines/tinsel/graphics.h @@ -0,0 +1,78 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Low level graphics interface. + */ + +#ifndef TINSEL_GRAPHICS_H		// prevent multiple includes +#define TINSEL_GRAPHICS_H + +#include "tinsel/dw.h" + +#include "common/rect.h" +#include "common/system.h" +#include "graphics/surface.h" + +namespace Tinsel { + +struct PALQ; + + +#define	SCREEN_WIDTH	320			// PC screen dimensions +#define	SCREEN_HEIGHT	200 +#define	SCRN_CENTRE_X	((SCREEN_WIDTH  - 1) / 2)	// screen centre x +#define	SCRN_CENTRE_Y	((SCREEN_HEIGHT - 1) / 2)	// screen centre y + +/** draw object structure - only used when drawing objects */ +struct DRAWOBJECT { +	char *charBase;		// character set base address +	int transOffset;	// transparent character offset +	int flags;		// object flags - see above for list +	PALQ *pPal;		// objects palette Q position +	int constant;		// which colour in palette for monochrome objects +	int width;		// width of object +	int height;		// height of object +	SCNHANDLE hBits;	// image bitmap handle +	int lineoffset;		// offset to next line +	int leftClip;		// amount to clip off object left +	int rightClip;		// amount to clip off object right +	int topClip;		// amount to clip off object top +	int botClip;		// amount to clip off object bottom +	short xPos;		// x position of object +	short yPos;		// y position of object +}; + + +/*----------------------------------------------------------------------*\ +|*			    Function Prototypes				*| +\*----------------------------------------------------------------------*/ + +void ClearScreen(); +void DrawObject(DRAWOBJECT *pObj); + +// called to update a rectangle on the video screen from a video page +void UpdateScreenRect(const Common::Rect &pClip); + +} // end of namespace Tinsel + +#endif diff --git a/engines/tinsel/handle.cpp b/engines/tinsel/handle.cpp new file mode 100644 index 0000000000..11623516ec --- /dev/null +++ b/engines/tinsel/handle.cpp @@ -0,0 +1,366 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * This file contains the handle based Memory Manager code + */ + +#define BODGE + +#include "common/file.h" + +#include "tinsel/dw.h" +#include "tinsel/scn.h"			// name of "index" file +#include "tinsel/handle.h" +#include "tinsel/heapmem.h"			// heap memory manager + + +// these are included only so the relevant structs can be used in convertLEStructToNative() +#include "tinsel/anim.h" +#include "tinsel/multiobj.h" +#include "tinsel/film.h" +#include "tinsel/object.h" +#include "tinsel/palette.h" +#include "tinsel/text.h" +#include "tinsel/scene.h" + +namespace Tinsel { + +//----------------- EXTERNAL GLOBAL DATA -------------------- + +#ifdef DEBUG +bool bLockedScene = 0; +#endif + + +//----------------- LOCAL DEFINES -------------------- + +struct MEMHANDLE { +	char szName[12];	//!< 00 - file name of graphics file +	int32 filesize;		//!< 12 - file size and flags +	MEM_NODE *pNode;	//!< 16 - memory node for the graphics +}; + + +/** memory allocation flags - stored in the top bits of the filesize field */ +enum { +	fPreload	= 0x01000000L,	//!< preload memory +	fDiscard	= 0x02000000L,	//!< discard memory +	fSound		= 0x04000000L,	//!< sound data +	fGraphic	= 0x08000000L,	//!< graphic data +	fCompressed	= 0x10000000L,	//!< compressed data +	fLoaded		= 0x20000000L	//!< set when file data has been loaded +}; +#define	FSIZE_MASK	0x00FFFFFFL	//!< mask to isolate the filesize +#define	MALLOC_MASK	0xFF000000L	//!< mask to isolate the memory allocation flags +#define	OFFSETMASK	0x007fffffL	//!< get offset of address +//#define	HANDLEMASK		0xFF800000L	//!< get handle of address + +//----------------- LOCAL GLOBAL DATA -------------------- + +// handle table gets loaded from index file at runtime +static MEMHANDLE *handleTable = 0; + +// number of handles in the handle table +static uint numHandles = 0; + + +//----------------- FORWARD REFERENCES -------------------- + +static void LoadFile(MEMHANDLE *pH, bool bWarn);	// load a memory block as a file + + +/** + * Loads the graphics handle table index file and preloads all the + * permanent graphics etc. + */ +void SetupHandleTable(void) { +	enum { RECORD_SIZE = 20 }; + +	int len; +	uint i; +	MEMHANDLE *pH; +	Common::File f; + +	if (f.open(INDEX_FILENAME)) { +		// get size of index file +		len = f.size(); + +		if (len > 0) { +			if ((len % RECORD_SIZE) != 0) { +				// index file is corrupt +				error("File %s is corrupt", INDEX_FILENAME); +			} + +			// calc number of handles +			numHandles = len / RECORD_SIZE; + +			// allocate memory for the index file +			handleTable = (MEMHANDLE *)calloc(numHandles, sizeof(struct MEMHANDLE)); + +			// make sure memory allocated +			assert(handleTable); + +			// load data +			for (i = 0; i < numHandles; i++) { +				f.read(handleTable[i].szName, 12); +				handleTable[i].filesize = f.readUint32LE(); +				// The pointer should always be NULL. We don't +				// need to read that from the file. +				handleTable[i].pNode = NULL; +				f.seek(4, SEEK_CUR); +			} + +			if (f.ioFailed()) { +				// index file is corrupt +				error("File %s is corrupt", INDEX_FILENAME); +			} + +			// close the file +			f.close(); +		} else {	// index file is corrupt +			error("File %s is corrupt", INDEX_FILENAME); +		} +	} else {	// cannot find the index file +		error("Cannot find file %s", INDEX_FILENAME); +	} + +	// allocate memory nodes and load all permanent graphics +	for (i = 0, pH = handleTable; i < numHandles; i++, pH++) { +		if (pH->filesize & fPreload) { +			// allocate a fixed memory node for permanent files +			pH->pNode = MemoryAlloc(DWM_FIXED, pH->filesize & FSIZE_MASK); + +			// make sure memory allocated +			assert(pH->pNode); + +			// load the data +			LoadFile(pH, true); +		} +#ifdef BODGE +		else if ((pH->filesize & FSIZE_MASK) == 8) { +			pH->pNode = NULL; +		} +#endif +		else { +			// allocate a discarded memory node for other files +			pH->pNode = MemoryAlloc( +				DWM_MOVEABLE | DWM_DISCARDABLE | DWM_NOALLOC, +				pH->filesize & FSIZE_MASK); + +			// make sure memory allocated +			assert(pH->pNode); +		} +	} +} + +void FreeHandleTable(void) { +	if (handleTable) { +		free(handleTable); +		handleTable = NULL; +	} +} + +/** + * Loads a memory block as a file. + * @param pH			Memory block pointer + * @param bWarn			If set, treat warnings as errors + */ +void LoadFile(MEMHANDLE *pH, bool bWarn) { +	Common::File f; +	char szFilename[sizeof(pH->szName) + 1]; + +	if (pH->filesize & fCompressed) { +		error("Compression handling has been removed!"); +	} + +	// extract and zero terminate the filename +	strncpy(szFilename, pH->szName, sizeof(pH->szName)); +	szFilename[sizeof(pH->szName)] = 0; + +	if (f.open(szFilename)) { +		// read the data +		int bytes; +		uint8 *addr; + +		if (pH->filesize & fPreload) +			// preload - no need to lock the memory +			addr = (uint8 *)pH->pNode; +		else { +			// discardable - lock the memory +			addr = (uint8 *)MemoryLock(pH->pNode); +		} +#ifdef DEBUG +		if (addr == NULL) { +			if (pH->filesize & fPreload) +				// preload - no need to lock the memory +				addr = (uint8 *)pH->pNode; +			else { +				// discardable - lock the memory +				addr = (uint8 *)MemoryLock(pH->pNode); +			} +		} +#endif + +		// make sure address is valid +		assert(addr); + +		bytes = f.read(addr, pH->filesize & FSIZE_MASK); + +		// close the file +		f.close(); + +		if ((pH->filesize & fPreload) == 0) { +			// discardable - unlock the memory +			MemoryUnlock(pH->pNode); +		} + +		// set the loaded flag +		pH->filesize |= fLoaded; + +		if (bytes == (pH->filesize & FSIZE_MASK)) { +			return; +		} + +		if (bWarn) +			// file is corrupt +			error("File %s is corrupt", szFilename); +	} + +	if (bWarn) +		// cannot find file +		error("Cannot find file %s", szFilename); +} + +/** + * Returns the address of a image, given its memory handle. + * @param offset			Handle and offset to data + */ +uint8 *LockMem(SCNHANDLE offset) { +	uint32 handle = offset >> SCNHANDLE_SHIFT;	// calc memory handle to use +	MEMHANDLE *pH;			// points to table entry + +	// range check the memory handle +	assert(handle < numHandles); + +	pH = handleTable + handle; + +	if (pH->filesize & fPreload) { +		// permanent files are already loaded +		return (uint8 *)pH->pNode + (offset & OFFSETMASK); +	} else { +		if (pH->pNode->pBaseAddr && (pH->filesize & fLoaded)) +			// already allocated and loaded +			return pH->pNode->pBaseAddr + (offset & OFFSETMASK); +		 +		if (pH->pNode->pBaseAddr == NULL) +			// must have been discarded - reallocate the memory +			MemoryReAlloc(pH->pNode, pH->filesize & FSIZE_MASK, +				DWM_MOVEABLE | DWM_DISCARDABLE); + +		if (pH->pNode->pBaseAddr == NULL) +			error("Out of memory"); + +		LoadFile(pH, true); + +		// make sure address is valid +		assert(pH->pNode->pBaseAddr); + +		return pH->pNode->pBaseAddr + (offset & OFFSETMASK); +	} +} + +/** + * Called to make the current scene non-discardable. + * @param offset			Handle and offset to data + */ +void LockScene(SCNHANDLE offset) { + +	uint32 handle = offset >> SCNHANDLE_SHIFT;	// calc memory handle to use +	MEMHANDLE *pH;					// points to table entry + +#ifdef DEBUG +	assert(!bLockedScene); // Trying to lock more than one scene +#endif + +	// range check the memory handle +	assert(handle < numHandles); + +	pH = handleTable + handle; + +	// compact the heap to avoid fragmentation while scene is non-discardable +	HeapCompact(MAX_INT, false); + +	if ((pH->filesize & fPreload) == 0) { +		// change the flags for the node +		MemoryReAlloc(pH->pNode, pH->filesize & FSIZE_MASK, DWM_MOVEABLE); +#ifdef DEBUG +		bLockedScene = true; +#endif +	} +} + +/** + * Called to make the current scene discardable again. + * @param offset			Handle and offset to data + */ +void UnlockScene(SCNHANDLE offset) { + +	uint32 handle = offset >> SCNHANDLE_SHIFT;	// calc memory handle to use +	MEMHANDLE *pH;					// points to table entry + +	// range check the memory handle +	assert(handle < numHandles); + +	pH = handleTable + handle; + +	if ((pH->filesize & fPreload) == 0) { +		// change the flags for the node +		MemoryReAlloc(pH->pNode, pH->filesize & FSIZE_MASK, DWM_MOVEABLE | DWM_DISCARDABLE); +#ifdef DEBUG +		bLockedScene = false; +#endif +	} +} + +/*----------------------------------------------------------------------*/ + +#ifdef BODGE + +/** + * Validates that a specified handle pointer is valid + * @param offset			Handle and offset to data + */ +bool ValidHandle(SCNHANDLE offset) { +	uint32 handle = offset >> SCNHANDLE_SHIFT;	// calc memory handle to use +	MEMHANDLE *pH;					// points to table entry + +	// range check the memory handle +	assert(handle < numHandles); + +	pH = handleTable + handle; + +	return (pH->filesize & FSIZE_MASK) != 8; +} +#endif + +} // end of namespace Tinsel diff --git a/engines/tinsel/handle.h b/engines/tinsel/handle.h new file mode 100644 index 0000000000..2cb1638d9d --- /dev/null +++ b/engines/tinsel/handle.h @@ -0,0 +1,53 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Graphics Memory Manager data structures + * TODO: This should really be named dos_hand.h, or the dos_hand.cpp should be renamed + */ + +#ifndef TINSEL_HANDLE_H			// prevent multiple includes +#define TINSEL_HANDLE_H + +#include "tinsel/dw.h"			// new data types + +namespace Tinsel { + +/*----------------------------------------------------------------------*\ +|*                              Function Prototypes                     *| +\*----------------------------------------------------------------------*/ + +void SetupHandleTable(void);			// Loads the graphics handle table index file and preloads all the permanent graphics etc. +void FreeHandleTable(void); + +uint8 *LockMem(			// returns the addr of a image, given its memory handle +	SCNHANDLE offset);			// handle and offset to data + +void LockScene(					// Called to make the current scene non-discardable +	SCNHANDLE offset);			// handle and offset to data + +void UnlockScene(				// Called to make the current scene discardable again +	SCNHANDLE offset);			// handle and offset to data + +} // end of namespace Tinsel + +#endif	// TINSEL_HANDLE_H diff --git a/engines/tinsel/heapmem.cpp b/engines/tinsel/heapmem.cpp new file mode 100644 index 0000000000..aff085d003 --- /dev/null +++ b/engines/tinsel/heapmem.cpp @@ -0,0 +1,594 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * This file contains the handle based Memory Manager code. + */ + +#include "tinsel/heapmem.h" +#include "tinsel/timers.h"	// For DwGetCurrentTime + +namespace Tinsel { + +// minimum memory required for MS-DOS version of game +#define	MIN_MEM 2506752L + +// list of all memory nodes +MEM_NODE mnodeList[NUM_MNODES]; + +// pointer to the linked list of free mnodes +static MEM_NODE *pFreeMemNodes; + +#ifdef DEBUG +// diagnostic mnode counters +static int numNodes; +static int maxNodes; +#endif + +// the mnode heap sentinel +static MEM_NODE heapSentinel; + +// +static MEM_NODE *AllocMemNode(void); + + +/** + * Initialises the memory manager. + */ +void MemoryInit(void) { +	MEM_NODE *pNode; + +#ifdef DEBUG +	// clear number of nodes in use +	numNodes = 0; +#endif + +	// place first node on free list +	pFreeMemNodes = mnodeList; + +	// link all other objects after first +	for (int i = 1; i < NUM_MNODES; i++) { +		mnodeList[i - 1].pNext = mnodeList + i; +	} + +	// null the last mnode +	mnodeList[NUM_MNODES - 1].pNext = NULL; + +	// allocatea big chunk of memory +	const uint32 size = 2*MIN_MEM+655360L; +	uint8 *mem = (uint8 *)malloc(size); +	assert(mem); + +	// allocate a mnode for this memory +	pNode = AllocMemNode(); + +	// make sure mnode was allocated +	assert(pNode); + +	// convert segment to memory address +	pNode->pBaseAddr = mem; + +	// set size of the memory heap +	pNode->size = size; + +	// clear the memory +	memset(pNode->pBaseAddr, 0, size); + +	// set cyclic links to the sentinel +	heapSentinel.pPrev = pNode; +	heapSentinel.pNext = pNode; +	pNode->pPrev = &heapSentinel; +	pNode->pNext = &heapSentinel; + +	// flag sentinel as locked +	heapSentinel.flags = DWM_LOCKED | DWM_SENTINEL; +} + + +#ifdef DEBUG +/** + * Shows the maximum number of mnodes used at once. + */ + +void MemoryStats(void) { +	printf("%i mnodes of %i used.\n", maxNodes, NUM_MNODES); +} +#endif + +/** + * Allocate a mnode from the free list. + */ +static MEM_NODE *AllocMemNode(void) { +	// get the first free mnode +	MEM_NODE *pMemNode = pFreeMemNodes; + +	// make sure a mnode is available +	assert(pMemNode); // Out of memory nodes + +	// the next free mnode +	pFreeMemNodes = pMemNode->pNext; + +	// wipe out the mnode +	memset(pMemNode, 0, sizeof(MEM_NODE)); + +#ifdef DEBUG +	// one more mnode in use +	if (++numNodes > maxNodes) +		maxNodes = numNodes; +#endif + +	// return new mnode +	return pMemNode; +} + +/** + * Return a mnode back to the free list. + * @param pMemNode			Node of the memory object + */ +void FreeMemNode(MEM_NODE *pMemNode) { +	// validate mnode pointer +	assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1); + +#ifdef DEBUG +	// one less mnode in use +	--numNodes; +	assert(numNodes >= 0); +#endif + +	// place free list in mnode next +	pMemNode->pNext = pFreeMemNodes; + +	// add mnode to top of free list +	pFreeMemNodes = pMemNode; +} + + +/** + * Tries to make space for the specified number of bytes on the specified heap. + * @param size			Number of bytes to free up + * @param bDiscard		When set - will discard blocks to fullfill the request + */ +bool HeapCompact(long size, bool bDiscard) { +	MEM_NODE *pHeap = &heapSentinel; +	MEM_NODE *pPrev, *pCur, *pOldest; +	long largest;		// size of largest free block +	uint32 oldest;		// time of the oldest discardable block + +	while (true) { +		bool bChanged; + +		do { +			bChanged = false; +			for (pPrev = pHeap->pNext, pCur = pPrev->pNext; +				pCur != pHeap; pPrev = pCur, pCur = pCur->pNext) { +				if (pPrev->flags == 0 && pCur->flags == 0) { +					// set the changed flag +					bChanged = true; + +					// both blocks are free - merge them +					pPrev->size += pCur->size; + +					// unlink the current mnode +					pPrev->pNext = pCur->pNext; +					pCur->pNext->pPrev = pPrev; + +					// free the current mnode +					FreeMemNode(pCur); + +					// leave the loop +					break; +				} else if ((pPrev->flags & (DWM_MOVEABLE | DWM_LOCKED | DWM_DISCARDED)) == DWM_MOVEABLE +						&& pCur->flags == 0) { +					// a free block after a moveable block - swap them + +					// set the changed flag +					bChanged = true; + +					// move the unlocked blocks data up (can overlap) +					memmove(pPrev->pBaseAddr + pCur->size, +						pPrev->pBaseAddr, pPrev->size); + +					// swap the order in the linked list +					pPrev->pPrev->pNext = pCur; +					pCur->pNext->pPrev = pPrev; + +					pCur->pPrev = pPrev->pPrev; +					pPrev->pPrev = pCur; + +					pPrev->pNext = pCur->pNext; +					pCur->pNext = pPrev; + +					pCur->pBaseAddr = pPrev->pBaseAddr; +					pPrev->pBaseAddr += pCur->size; + +					// leave the loop +					break; +				} +			} +		} while (bChanged); + +		// find the largest free block +		for (largest = 0, pCur = pHeap->pNext; pCur != pHeap; pCur = pCur->pNext) { +			if (pCur->flags == 0 && pCur->size > largest) +				largest = pCur->size; +		} + +		if (largest >= size) +			// we have freed enough memory +			return true; + +		if (!bDiscard) +			// we cannot free enough without discarding blocks +			return false; + +		// find the oldest discardable block +		oldest = DwGetCurrentTime(); +		pOldest = NULL; +		for (pCur = pHeap->pNext; pCur != pHeap; pCur = pCur->pNext) { +			if ((pCur->flags & (DWM_DISCARDABLE | DWM_DISCARDED | DWM_LOCKED)) +				== DWM_DISCARDABLE) { +				// found a non-discarded discardable block +				if (pCur->lruTime < oldest) { +					oldest = pCur->lruTime; +					pOldest = pCur; +				} +			} +		} +		 +		if (pOldest) +			// discard the oldest block +			MemoryDiscard(pOldest); +		else +			// cannot discard any blocks +			return false; +	} +} + +/** + * Allocates the specified number of bytes from the heap. + * @param flags			Allocation attributes + * @param size			Number of bytes to allocate + */ +MEM_NODE *MemoryAlloc(int flags, long size) { +	MEM_NODE *pHeap = &heapSentinel; +	MEM_NODE *pNode; +	bool bCompacted = true;	// set when heap has been compacted + +	// compact the heap if we are allocating fixed memory +	if (flags & DWM_FIXED) +		HeapCompact(MAX_INT, false); + +	while ((flags & DWM_NOALLOC) == 0 && bCompacted) { +		// search the heap for a free block + +		for (pNode = pHeap->pNext; pNode != pHeap; pNode = pNode->pNext) { +			if (pNode->flags == 0 && pNode->size >= size) { +				// a free block of the required size +				pNode->flags = flags; + +				// update the LRU time +				pNode->lruTime = DwGetCurrentTime() + 1; + +				if (pNode->size == size) { +					// an exact fit + +					// check for zeroing the block +					if (flags & DWM_ZEROINIT) +						memset(pNode->pBaseAddr, 0, size); + +					if (flags & DWM_FIXED) +						// lock the memory +						return (MEM_NODE *)MemoryLock(pNode); +					else +						// just return the node +						return pNode; +				} else { +					// allocate a node for the remainder of the free block +					MEM_NODE *pTemp = AllocMemNode(); + +					// calc size of the free block +					long freeSize = pNode->size - size; + +					// set size of free block +					pTemp->size = freeSize; + +					// set size of node +					pNode->size = size; + +					if (flags & DWM_FIXED) { +						// place the free node after pNode +						pTemp->pBaseAddr = pNode->pBaseAddr + size; +						pTemp->pNext = pNode->pNext; +						pTemp->pPrev = pNode; +						pNode->pNext->pPrev = pTemp; +						pNode->pNext = pTemp; + +						// check for zeroing the block +						if (flags & DWM_ZEROINIT) +							memset(pNode->pBaseAddr, 0, size); + +						return (MEM_NODE *)MemoryLock(pNode); +					} else { +						// place the free node before pNode +						pTemp->pBaseAddr = pNode->pBaseAddr; +						pNode->pBaseAddr += freeSize; +						pTemp->pNext = pNode; +						pTemp->pPrev = pNode->pPrev; +						pNode->pPrev->pNext = pTemp; +						pNode->pPrev = pTemp; + +						// check for zeroing the block +						if (flags & DWM_ZEROINIT) +							memset(pNode->pBaseAddr, 0, size); + +						return pNode; +					} +				} +			} +		} +		// compact the heap if we get to here +		bCompacted = HeapCompact(size, (flags & DWM_NOCOMPACT) ? false : true); +	} + +	// not allocated a block if we get to here +	if (flags & DWM_DISCARDABLE) { +		// chain a discarded node onto the end of the heap +		pNode = AllocMemNode(); +		pNode->flags = flags | DWM_DISCARDED; + +		// set mnode at the end of the list +		pNode->pPrev = pHeap->pPrev; +		pNode->pNext = pHeap; + +		// fix links to this mnode +		pHeap->pPrev->pNext = pNode; +		pHeap->pPrev = pNode; + +		// return the discarded node +		return pNode; +	} + +	// could not allocate a block +	return NULL; +} + +/** + * Discards the specified memory object. + * @param pMemNode			Node of the memory object + */ +void MemoryDiscard(MEM_NODE *pMemNode) { +	// validate mnode pointer +	assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1); + +	// object must be discardable +	assert(pMemNode->flags & DWM_DISCARDABLE); + +	// object cannot be locked +	assert((pMemNode->flags & DWM_LOCKED) == 0); + +	if ((pMemNode->flags & DWM_DISCARDED) == 0) { +		// allocate a free node to replace this node +		MEM_NODE *pTemp = AllocMemNode(); + +		// copy node data +		memcpy(pTemp, pMemNode, sizeof(MEM_NODE)); + +		// flag as a free block +		pTemp->flags = 0; + +		// link in the free node +		pTemp->pPrev->pNext = pTemp; +		pTemp->pNext->pPrev = pTemp; + +		// discard the node +		pMemNode->flags |= DWM_DISCARDED; +		pMemNode->pBaseAddr = NULL; +		pMemNode->size = 0; + +		// and place it at the end of the heap +		while ((pTemp->flags & DWM_SENTINEL) != DWM_SENTINEL) +			pTemp = pTemp->pNext; + +		// pTemp now points to the heap sentinel +		// set mnode at the end of the list +		pMemNode->pPrev = pTemp->pPrev; +		pMemNode->pNext = pTemp; + +		// fix links to this mnode +		pTemp->pPrev->pNext = pMemNode; +		pTemp->pPrev = pMemNode; +	} +} + +/** + * Frees the specified memory object and invalidates its node. + * @param pMemNode			Node of the memory object + */ +void MemoryFree(MEM_NODE *pMemNode) { +	MEM_NODE *pPrev, *pNext; + +	// validate mnode pointer +	assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1); + +	// get pointer to the next mnode +	pNext = pMemNode->pNext; + +	// get pointer to the previous mnode +	pPrev = pMemNode->pPrev; + +	if (pPrev->flags == 0) { +		// there is a previous free mnode +		pPrev->size += pMemNode->size; + +		// unlink this mnode +		pPrev->pNext = pNext;	// previous to next +		pNext->pPrev = pPrev;	// next to previous + +		// free this mnode +		FreeMemNode(pMemNode); + +		pMemNode = pPrev; +	} +	if (pNext->flags == 0) { +		// the next mnode is free +		pMemNode->size += pNext->size; + +		// flag as a free block +		pMemNode->flags = 0; + +		// unlink the next mnode +		pMemNode->pNext = pNext->pNext; +		pNext->pNext->pPrev = pMemNode; + +		// free the next mnode +		FreeMemNode(pNext); +	} +} + +/** + * Locks a memory object and returns a pointer to the first byte + * of the objects memory block. + * @param pMemNode			Node of the memory object + */ +void *MemoryLock(MEM_NODE *pMemNode) { +	// validate mnode pointer +	assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1); + +	// make sure memory object is not already locked +	assert((pMemNode->flags & DWM_LOCKED) == 0); + +	// check for a discarded or null memory object +	if ((pMemNode->flags & DWM_DISCARDED) || pMemNode->size == 0) +		return NULL; + +	// set the lock flag +	pMemNode->flags |= DWM_LOCKED; + +	// return memory objects base address +	return pMemNode->pBaseAddr; +} + +/** + * Changes the size or attributes of a specified memory object. + * @param pMemNode		Node of the memory object + * @param size			New size of block + * @param flags			How to reallocate the object + */ +MEM_NODE *MemoryReAlloc(MEM_NODE *pMemNode, long size, int flags) { +	MEM_NODE *pNew; + +	// validate mnode pointer +	assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1); + +	// validate the flags +	// cannot be fixed and moveable +	assert((flags & (DWM_FIXED | DWM_MOVEABLE)) != (DWM_FIXED | DWM_MOVEABLE)); + +	// cannot be fixed and discardable +	assert((flags & (DWM_FIXED | DWM_DISCARDABLE)) != (DWM_FIXED | DWM_DISCARDABLE)); + +	// must be fixed or moveable +	assert(flags & (DWM_FIXED | DWM_MOVEABLE)); + +	// align the size to machine boundary requirements +	size = (size + sizeof(int) - 1) & ~(sizeof(int) - 1); + +	// validate the size +	assert(size); + +	// make sure we want the node on the same heap +	assert((flags & (DWM_SOUND | DWM_GRAPHIC)) == (pMemNode->flags & (DWM_SOUND | DWM_GRAPHIC))); + +	if (size == pMemNode->size) { +		// must be just a change in flags + +		// update the nodes flags +		pMemNode->flags = flags; +	} else { +		// unlink the mnode from the current heap +		pMemNode->pNext->pPrev = pMemNode->pPrev; +		pMemNode->pPrev->pNext = pMemNode->pNext; + +		// allocate a new node +		pNew = MemoryAlloc((flags & ~DWM_FIXED) | DWM_MOVEABLE, size); + +		// make sure memory allocated +		assert(pNew != NULL); + +		// update the nodes flags +		pNew->flags = flags; + +		// copy the node to the current node +		memcpy(pMemNode, pNew, sizeof(MEM_NODE)); + +		// relink the mnode into the list +		pMemNode->pPrev->pNext = pMemNode; +		pMemNode->pNext->pPrev = pMemNode; + +		// free the new node +		FreeMemNode(pNew); +	} + +	if (flags & DWM_FIXED) +		// lock the memory +		return (MEM_NODE *)MemoryLock(pMemNode); +	else +		// just return the node +		return pMemNode; +} + +/** + * Unlocks a memory object. + * @param pMemNode		Node of the memory object + */ +void MemoryUnlock(MEM_NODE *pMemNode) { +	// validate mnode pointer +	assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1); + +	// make sure memory object is already locked +	assert(pMemNode->flags & DWM_LOCKED); + +	// clear the lock flag +	pMemNode->flags &= ~DWM_LOCKED; + +	// update the LRU time +	pMemNode->lruTime = DwGetCurrentTime(); +} + +/** + * Retrieves the mnode associated with the specified pointer to a memory object. + * @param pMem			Address of memory object + */ +MEM_NODE *MemoryHandle(void *pMem) { +	MEM_NODE *pNode; +	// search the DOS heap +	for (pNode = heapSentinel.pNext; pNode != &heapSentinel; pNode = pNode->pNext) { +		if (pNode->pBaseAddr == pMem) +			// found it +			return pNode; +	} + +	// not found if we get to here +	return NULL; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/heapmem.h b/engines/tinsel/heapmem.h new file mode 100644 index 0000000000..7fb85985a9 --- /dev/null +++ b/engines/tinsel/heapmem.h @@ -0,0 +1,109 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * This file contains the handle based Memory Manager defines + */ + +#ifndef TINSEL_HEAPMEM_H +#define TINSEL_HEAPMEM_H + +#include "tinsel/dw.h"		// new data types + +namespace Tinsel { + +#define	NUM_MNODES	192	// the number of memory management nodes (was 128, then 192) + +struct MEM_NODE { +	MEM_NODE *pNext;	// link to the next node in the list +	MEM_NODE *pPrev;	// link to the previous node in the list +	uint8 *pBaseAddr;	// base address of the memory object +	long size;		// size of the memory object +	uint32 lruTime;		// time when memory object was last accessed +	int flags;		// allocation attributes +}; + +// allocation flags for the MemoryAlloc function +#define	DWM_FIXED	0x0001	// allocates fixed memory +#define	DWM_MOVEABLE	0x0002	// allocates movable memory +#define	DWM_DISCARDABLE	0x0004	// allocates discardable memory +#define	DWM_NOALLOC	0x0008	// when used with discardable memory - allocates a discarded block +#define	DWM_NOCOMPACT	0x0010	// does not discard memory to satisfy the allocation request +#define	DWM_ZEROINIT	0x0020	// initialises memory contents to zero +#define	DWM_SOUND	0x0040	// allocate from the sound pool +#define	DWM_GRAPHIC	0x0080	// allocate from the graphics pool + +// return value from the MemoryFlags function +#define	DWM_DISCARDED	0x0100	// the objects memory block has been discarded + +// internal allocation flags +#define	DWM_LOCKED	0x0200	// the objects memory block is locked +#define	DWM_SENTINEL	0x0400	// the objects memory block is a sentinel + + +/*----------------------------------------------------------------------*\ +|*			Memory Function Prototypes			*| +\*----------------------------------------------------------------------*/ + +void MemoryInit(void);		// initialises the memory manager + +#ifdef	DEBUG +void MemoryStats(void);		// Shows the maximum number of mnodes used at once +#endif + +MEM_NODE *MemoryAlloc(		// allocates the specified number of bytes from the heap +	int flags,		// allocation attributes +	long size);		// number of bytes to allocate + +void MemoryDiscard(		// discards the specified memory object +	MEM_NODE *pMemNode);	// node of the memory object + +int MemoryFlags(		// returns information about the specified memory object +	MEM_NODE *pMemNode);	// node of the memory object + +void MemoryFree(		// frees the specified memory object and invalidates its node +	MEM_NODE *pMemNode);	// node of the memory object + +MEM_NODE *MemoryHandle(		// Retrieves the mnode associated with the specified pointer to a memory object +	void *pMem);		// address of memory object + +void *MemoryLock(		// locks a memory object and returns a pointer to the first byte of the objects memory block +	MEM_NODE *pMemNode);	// node of the memory object + +MEM_NODE *MemoryReAlloc(	// changes the size or attributes of a specified memory object +	MEM_NODE *pMemNode,	// node of the memory object +	long size,		// new size of block +	int flags);		// how to reallocate the object + +long MemorySize(		// returns the size, in bytes, of the specified memory object +	MEM_NODE *pMemNode);	// node of the memory object + +void MemoryUnlock(		// unlocks a memory object +	MEM_NODE *pMemNode);	// node of the memory object + +bool HeapCompact(		// Allocates the specified number of bytes from the specified heap +	long size,		// number of bytes to free up +	bool bDiscard);		// when set - will discard blocks to fullfill the request + +} // end of namespace Tinsel + +#endif diff --git a/engines/tinsel/inventory.cpp b/engines/tinsel/inventory.cpp new file mode 100644 index 0000000000..2a0f3695c0 --- /dev/null +++ b/engines/tinsel/inventory.cpp @@ -0,0 +1,4535 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Handles the inventory and conversation windows. + * + * And the save/load game windows. Some of this will be platform + * specific - I'll try to separate this ASAP. + * + * And there's still a bit of tidying and commenting to do yet. + */ + +//#define USE_3FLAGS 1 + +#include "tinsel/actors.h" +#include "tinsel/anim.h" +#include "tinsel/background.h" +#include "tinsel/config.h" +#include "tinsel/cursor.h" +#include "tinsel/dw.h" +#include "tinsel/film.h" +#include "tinsel/font.h" +#include "tinsel/graphics.h" +#include "tinsel/handle.h" +#include "tinsel/inventory.h" +#include "tinsel/multiobj.h" +#include "tinsel/music.h" +#include "tinsel/polygons.h" +#include "tinsel/savescn.h" +#include "tinsel/sched.h" +#include "tinsel/serializer.h" +#include "tinsel/sound.h" +#include "tinsel/strres.h" +#include "tinsel/text.h" +#include "tinsel/timers.h"		// For ONE_SECOND constant +#include "tinsel/tinsel.h"		// For engine access +#include "tinsel/token.h" +#include "tinsel/pcode.h" +#include "tinsel/pid.h" + +namespace Tinsel { + +//----------------- EXTERNAL GLOBAL DATA -------------------- + +// In DOS_DW.C +extern bool bRestart;		// restart flag - set to restart the game + +#ifdef MAC_OPTIONS +// In MAC_SOUND.C +extern int volMaster; +#endif + + +//----------------- EXTERNAL FUNCTIONS --------------------- + +// Tag functions in PDISPLAY.C +extern void EnableTags(void); +extern void DisableTags(void); + + +//----------------- LOCAL DEFINES -------------------- + +//#define ALL_CURSORS + +#define INV_PICKUP	BE_SLEFT		// Local names +#define INV_LOOK	BE_SRIGHT		//	for button events +#define INV_ACTION	BE_DLEFT		// + + +// For SlideSlider() and similar +enum SSFN { +	S_START, S_SLIDE, S_END, S_TIMEUP, S_TIMEDN +}; + +/** attribute values - may become bit field if further attributes are added */ +enum { + IO_ONLYINV1	= 0x01, + IO_ONLYINV2	= 0x02, + IO_DROPCODE	= 0x04 +}; + +//----------------------- +// Moveable window translucent rectangle position limits +enum { +	MAXLEFT		= 315,		// +	MINRIGHT	= 3,		// These values keep 2 pixcells +	MINTOP		= -13,		// of header on the screen. +	MAXTOP		= 195		// +}; + +//----------------------- +// Indices into winPartsf's reels +#define IX_SLIDE 0		// Slider +#define IX_V26	1 +#define IX_V52	2 +#define IX_V78	3 +#define IX_V104	4 +#define IX_V130	5 +#define IX_H26	6 +#define IX_H52	7 +#define IX_H78	8 +#define IX_H104	9 +#define IX_H130	10 +#define IX_H156	11 +#define IX_H182	12 +#define IX_H208	13 +#define IX_H234	14 +#define IX_TL	15		// Top left corner +#define IX_TR	16		// Top right corner +#define IX_BL	17		// Bottom left corner +#define IX_BR	18		// Bottom right corner +#define IX_H25	19 +#define IX_V11	20 +#define IX_RTL	21		// Re-sizing top left corner +#define IX_RTR	22		// Re-sizing top right corner +#define IX_RBR	23		// Re-sizing bottom right corner +#define IX_CURLR	24		// } +#define IX_CURUD	25		// } +#define IX_CURDU	26		// } Custom cursors +#define IX_CURDD	27		// } +#define IX_CURUP	28		// } +#define IX_CURDOWN	29		// } +#define IX_MDGROOVE	30	// 'Mixing desk' slider background +#define IX_MDSLIDER	34	// 'Mixing desk' slider + +#define IX_BLANK1	35		// +#define IX_BLANK2	36		// +#define IX_BLANK3	37		// +#define IX_CIRCLE1	38	// +#define IX_CIRCLE2	39	// +#define IX_CROSS1	40		// +#define IX_CROSS2	41		// +#define IX_CROSS3	42		// +#define IX_QUIT1	43	// +#define IX_QUIT2	44	// +#define IX_QUIT3	45	// +#define IX_TICK1	46		// +#define IX_TICK2	47		// +#define IX_TICK3	48		// +#define IX_NTR		49		// New top right corner +#define HOPEDFORREELS	50 + +#define NORMGRAPH	0 +#define DOWNGRAPH	1 +#define HIGRAPH		2 +//----------------------- +#define FIX_UK		0 +#define FIX_FR		1 +#define FIX_GR		2 +#define FIX_IT		3 +#define FIX_SP		4 +#define FIX_USA		5 +#define HOPEDFORFREELS	6	// Expected flag reels +//----------------------- + +#define MAX_ININV	150	// Max in an inventory +#define MAX_CONVBASIC	10	// Max permanent conversation icons + +#define MAXHICONS	10	// Max dimensions of +#define MAXVICONS	6	// an inventory window + +#define ITEM_WIDTH	25	// Dimensions of an icon +#define ITEM_HEIGHT	25	// + +// Number of objects that makes up an empty window +#define MAX_WCOMP	21		// 4 corners + (3+3) sides + (2+2) extra sides +					// + Bground + title + slider +					// + more Needed for save game window + +#define MAX_ICONS	MAXHICONS*MAXVICONS + + + +//----------------- LOCAL GLOBAL DATA -------------------- + +//----- Permanent data (compiled in) ----- + +// Save game name editing cursor + +#define CURSOR_CHAR	'_' +char sCursor[2]	= { CURSOR_CHAR, 0 }; +static const int hFillers[MAXHICONS] = { +	IX_H26,			// 2 icons wide +	IX_H52,			// 3 +	IX_H78,			// 4 +	IX_H104,		// 5 +	IX_H130,		// 6 +	IX_H156,		// 7 +	IX_H182,		// 8 +	IX_H208,		// 9 +	IX_H234			// 10 icons wide +}; +static const int vFillers[MAXVICONS] = { +	IX_V26,			// 2 icons high +	IX_V52,			// 3 +	IX_V78,			// 4 +	IX_V104,		// 5 +	IX_V130			// 6 icons high +}; + + +//----- Permanent data (set once) ----- + +static SCNHANDLE winPartsf = 0;	// Window members and cursors' graphic data +static SCNHANDLE flagFilm = 0;	// Window members and cursors' graphic data +static SCNHANDLE configStrings[20]; + +static INV_OBJECT *pio = 0;		// Inventory objects' data +static int numObjects = 0;		// Number of inventory objects + + +//----- Permanent data (updated, valid while inventory closed) ----- + +static enum {NO_INV, IDLE_INV, ACTIVE_INV, BOGUS_INV} InventoryState; + +static int HeldItem = INV_NOICON;	// Current held item + +struct INV_DEF { + +	int MinHicons;		// } +	int MinVicons;		// } Dimension limits +	int MaxHicons;		// } +	int MaxVicons;		// } + +	int NoofHicons;		// } +	int NoofVicons;		// } Current dimentsions + +	int ItemOrder[MAX_ININV];	// Contained items +	int NoofItems;			// Current number of held items + +	int FirstDisp;		// Index to first item currently displayed + +	int inventoryX;		// } Display position +	int inventoryY;		// }  +	int otherX;		// } Display position +	int otherY;		// }  + +	int MaxInvObj;		// Max. allowed contents + +	SCNHANDLE hInvTitle;	// Window heading + +	bool resizable;		// Re-sizable window? +	bool moveable;		// Moveable window? + +	int sNoofHicons;	// } +	int sNoofVicons;	// } Current dimensions + +	bool bMax;		// Maximised last time open? + +}; + +static INV_DEF InvD[NUM_INV];		// Conversation + 2 inventories + ... + + +// Permanent contents of conversation inventory +static int Inv0Order[MAX_CONVBASIC];	// Basic items i.e. permanent contents +static int Num0Order = 0;			// - copy to conv. inventory at pop-up time + + + +//----- Data pertinant to current active inventory ----- + +static int ino = 0;		// Which inventory is currently active + +static bool InventoryHidden = false; +static bool InventoryMaximised = false; + +static enum {	ID_NONE, ID_MOVE, ID_SLIDE, +		ID_BOTTOM, ID_TOP, ID_LEFT, ID_RIGHT, +		ID_TLEFT, ID_TRIGHT, ID_BLEFT, ID_BRIGHT, +		ID_CSLIDE, ID_MDCONT } InvDragging; + +static int SuppH = 0;		// 'Linear' element of +static int SuppV = 0;		// dimensions during re-sizing + +static int Ychange = 0;		// +static int Ycompensate = 0;		// All to do with re-sizing. +static int Xchange = 0;		// +static int Xcompensate = 0;		// + +static bool ItemsChanged = 0;	// When set, causes items to be re-drawn + +static bool bOpenConf = 0; + +static int TL = 0, TR = 0, BL = 0, BR = 0;	// Used during window construction +static int TLwidth = 0, TLheight = 0;	// +static int TRwidth = 0;		// +static int BLheight = 0;		// + + + +static OBJECT	*objArray[MAX_WCOMP];	// Current display objects (window) +static OBJECT	*iconArray[MAX_ICONS];	// Current display objects (icons) +static ANIM		iconAnims[MAX_ICONS]; +static OBJECT	*DobjArray[MAX_WCOMP];	// Current display objects (re-sizing window) + +static OBJECT *RectObject = 0, *SlideObject = 0;	// Current display objects, for reference +					// objects are in objArray. + +static int slideY = 0;			// For positioning the slider +static int slideYmax = 0, slideYmin = 0;	//  + +// Also to do with the slider +static struct { int n; int y; } slideStuff[MAX_ININV+1]; + +#define MAXSLIDES 4 +struct MDSLIDES { +	int	num; +	OBJECT	*obj; +	int	min, max; +}; +static MDSLIDES mdSlides[MAXSLIDES]; +static int numMdSlides = 0; + +static int GlitterIndex = 0; + +static HPOLYGON thisConvPoly = 0;			// Conversation code is in a polygon code block +static int thisConvIcon = 0;				// Passed to polygon code via convIcon() +static int pointedIcon = INV_NOICON;		// used by InvLabels - icon pointed to on last call +static volatile int PointedWaitCount = 0;	// used by InvTinselProcess - fix the 'repeated pressing bug' +static int sX = 0;							// used by SlideMSlider() - current x-coordinate +static int lX = 0;							// used by SlideMSlider() - last x-coordinate + +//----- Data pertinant to configure (incl. load/save game) ----- + +#define COL_MAINBOX	TBLUE1		// Base blue colour +#define COL_BOX		TBLUE1 +#define COL_HILIGHT	TBLUE4 + +#ifdef JAPAN +#define BOX_HEIGHT	17 +#define EDIT_BOX1_WIDTH	149 +#else +#define BOX_HEIGHT	13 +#define EDIT_BOX1_WIDTH	145 +#endif +#define EDIT_BOX2_WIDTH	166 + +// RGROUP	Radio button group - 1 is selectable at a time. Action on double click +// ARSBUT	Action if a radio button is selected +// AABUT	Action always +// AATBUT	Action always, text box +// AAGBUT	Action always, graphic button +// SLIDER	Not a button at all +enum BTYPE { +	RGROUP, ARSBUT, AABUT, AATBUT, ARSGBUT, AAGBUT, SLIDER, +	TOGGLE, DCTEST, FLIP, FRGROUP, NOTHING +}; + +enum BFUNC { +	NOFUNC, SAVEGAME, LOADGAME, IQUITGAME, CLOSEWIN, +	OPENLOAD, OPENSAVE, OPENREST, +	OPENSOUND, OPENCONT, +#ifndef JAPAN +	OPENSUBT, +#endif +	OPENQUIT, +	INITGAME, MIDIVOL, +	CLANG, RLANG +#ifdef MAC_OPTIONS +	, MASTERVOL, SAMPVOL  +#endif +}; + +struct CONFBOX { +	BTYPE	boxType; +	BFUNC	boxFunc; +	char	*boxText; +	int	ixText; +	int	xpos; +	int	ypos; +	int	w;		// Doubles as max value for SLIDERs +	int	h;		// Doubles as iteration size for SLIDERs +	int	*ival; +	int	bi;		// Base index for AAGBUTs +}; + + +#define NO_HEADING		(-1) +#define USE_POINTER		(-1) +#define SIX_LOAD_OPTION		0 +#define SIX_SAVE_OPTION		1 +#define SIX_RESTART_OPTION	2 +#define SIX_SOUND_OPTION	3 +#define SIX_CONTROL_OPTION	4 +#ifndef JAPAN +#define SIX_SUBTITLES_OPTION	5 +#endif +#define SIX_QUIT_OPTION		6 +#define SIX_RESUME_OPTION	7 +#define SIX_LOAD_HEADING	8 +#define SIX_SAVE_HEADING	9 +#define SIX_RESTART_HEADING	10 +#define SIX_MVOL_SLIDER		11 +#define SIX_SVOL_SLIDER		12 +#define SIX_VVOL_SLIDER		13 +#define SIX_DCLICK_SLIDER	14 +#define SIX_DCLICK_TEST		15 +#define SIX_SWAP_TOGGLE		16 +#define SIX_TSPEED_SLIDER	17 +#define SIX_STITLE_TOGGLE	18 +#define SIX_QUIT_HEADING	19 + + +/*-------------------------------------------------------------*\ +| This is the main menu (that comes up when you hit F1 on a PC)	| +\*-------------------------------------------------------------*/ + +#ifdef JAPAN +#define FBY	11	// y-offset of first button +#define FBX	13	// x-offset of first button +#else +#define FBY	20	// y-offset of first button +#define FBX	15	// x-offset of first button +#endif + +CONFBOX optionBox[] = { + + { AATBUT, OPENLOAD, NULL, SIX_LOAD_OPTION,	FBX, FBY,			EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, + { AATBUT, OPENSAVE, NULL, SIX_SAVE_OPTION,	FBX, FBY + (BOX_HEIGHT + 2),	EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, + { AATBUT, OPENREST, NULL, SIX_RESTART_OPTION,	FBX, FBY + 2*(BOX_HEIGHT + 2),	EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, + { AATBUT, OPENSOUND, NULL, SIX_SOUND_OPTION,	FBX, FBY + 3*(BOX_HEIGHT + 2),	EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, + { AATBUT, OPENCONT, NULL, SIX_CONTROL_OPTION,	FBX, FBY + 4*(BOX_HEIGHT + 2),	EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, +#ifdef JAPAN +// TODO: If in JAPAN mode, simply disable the subtitles button? + { AATBUT, OPENQUIT, NULL, SIX_QUIT_OPTION,	FBX, FBY + 5*(BOX_HEIGHT + 2),	EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, + { AATBUT, CLOSEWIN, NULL, SIX_RESUME_OPTION,	FBX, FBY + 6*(BOX_HEIGHT + 2),	EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 } +#else + { AATBUT, OPENSUBT, NULL, SIX_SUBTITLES_OPTION,FBX, FBY + 5*(BOX_HEIGHT + 2),	EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, + { AATBUT, OPENQUIT, NULL, SIX_QUIT_OPTION,	FBX, FBY + 6*(BOX_HEIGHT + 2),	EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, + { AATBUT, CLOSEWIN, NULL, SIX_RESUME_OPTION,	FBX, FBY + 7*(BOX_HEIGHT + 2),	EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 } +#endif + +}; + +/*-------------------------------------------------------------*\ +| These are the load and save game menus.			| +\*-------------------------------------------------------------*/ + +#ifdef JAPAN +#define NUM_SL_RGROUP	7	// number of visible slots +#define SY		32	// y-position of first slot +#else +#define NUM_SL_RGROUP	9	// number of visible slots +#define SY		31	// y-position of first slot +#endif + +CONFBOX loadBox[NUM_SL_RGROUP+2] = { + + { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY,				EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + (BOX_HEIGHT + 2),	EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 2*(BOX_HEIGHT + 2),	EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 3*(BOX_HEIGHT + 2),	EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 4*(BOX_HEIGHT + 2),	EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 5*(BOX_HEIGHT + 2),	EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 6*(BOX_HEIGHT + 2),	EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, +#ifndef JAPAN + { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 7*(BOX_HEIGHT + 2),	EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 8*(BOX_HEIGHT + 2),	EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, +#endif + { ARSGBUT, LOADGAME, NULL, USE_POINTER, 230, 44,	23, 19, NULL, IX_TICK1 }, + { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 230, 44+47,	23, 19, NULL, IX_CROSS1 } + +}; + +CONFBOX saveBox[NUM_SL_RGROUP+2] = { + + { RGROUP, SAVEGAME, NULL, USE_POINTER, 28,	SY,			EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, SAVEGAME, NULL, USE_POINTER, 28,	SY + (BOX_HEIGHT + 2),	EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, SAVEGAME, NULL, USE_POINTER, 28,	SY + 2*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, SAVEGAME, NULL, USE_POINTER, 28,	SY + 3*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, SAVEGAME, NULL, USE_POINTER, 28,	SY + 4*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, SAVEGAME, NULL, USE_POINTER, 28,	SY + 5*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, SAVEGAME, NULL, USE_POINTER, 28,	SY + 6*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, +#ifndef JAPAN + { RGROUP, SAVEGAME, NULL, USE_POINTER, 28,	SY + 7*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, SAVEGAME, NULL, USE_POINTER, 28,	SY + 8*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, +#endif + { ARSGBUT, SAVEGAME, NULL,USE_POINTER, 230, 44,	23, 19, NULL, IX_TICK1 }, + { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 230, 44+47,	23, 19, NULL, IX_CROSS1 } + +}; + + +/*-------------------------------------------------------------*\ +| This is the restart confirmation 'menu'.			| +\*-------------------------------------------------------------*/ + +CONFBOX restartBox[] = { + +#ifdef JAPAN + { AAGBUT, INITGAME, NULL, USE_POINTER, 96, 44,	23, 19, NULL, IX_TICK1 }, + { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 56, 44,	23, 19, NULL, IX_CROSS1 } +#else + { AAGBUT, INITGAME, NULL, USE_POINTER, 70, 28,	23, 19, NULL, IX_TICK1 }, + { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 30, 28,	23, 19, NULL, IX_CROSS1 } +#endif + +}; + + +/*-------------------------------------------------------------*\ +| This is the sound control 'menu'.				| +\*-------------------------------------------------------------*/ + +#ifdef MAC_OPTIONS +	CONFBOX soundBox[] = { +		{ SLIDER, MASTERVOL, NULL, SIX_MVOL_SLIDER,	142, 20, 100, 2, &volMaster, 0 },	 + 		{ SLIDER, MIDIVOL, NULL, SIX_MVOL_SLIDER,	142, 20+40,	100, 2, &volMidi, 0 }, + 		{ SLIDER, SAMPVOL, NULL, SIX_SVOL_SLIDER,	142, 20+2*40,	100, 2, &volSound, 0 }, + 		{ SLIDER, SAMPVOL, NULL, SIX_VVOL_SLIDER,	142, 20+3*40,	100, 2, &volVoice, 0 } +	}; +#else +CONFBOX soundBox[] = { +	{ SLIDER, MIDIVOL, NULL, SIX_MVOL_SLIDER,	142, 25,	MAXMIDIVOL, 2, &volMidi, 0 }, +	{ SLIDER, NOFUNC, NULL, SIX_SVOL_SLIDER,	142, 25+40,	MAXSAMPVOL, 2, &volSound, 0 }, +	{ SLIDER, NOFUNC, NULL, SIX_VVOL_SLIDER,	142, 25+2*40,	MAXSAMPVOL, 2, &volVoice, 0 } +}; +#endif + + +/*-------------------------------------------------------------*\ +| This is the (mouse) control 'menu'.				| +\*-------------------------------------------------------------*/ + +int bFlipped;	// looks like this is just so the code has something to alter! + + +#ifdef MAC_OPTIONS +CONFBOX controlBox[] = { + + { SLIDER, NOFUNC, NULL, SIX_DCLICK_SLIDER,	142, 25,	3*DOUBLE_CLICK_TIME, 1, &dclickSpeed, 0 }, + { FLIP, NOFUNC, NULL, SIX_DCLICK_TEST,		142, 25+30,	23, 19, &bFlipped, IX_CIRCLE1 } + +}; +#else +CONFBOX controlBox[] = { + + { SLIDER, NOFUNC, NULL, SIX_DCLICK_SLIDER,	142, 25,	3*DOUBLE_CLICK_TIME, 1, &dclickSpeed, 0 }, + { FLIP, NOFUNC, NULL, SIX_DCLICK_TEST,		142, 25+30,	23, 19, &bFlipped, IX_CIRCLE1 }, +#ifdef JAPAN + { TOGGLE, NOFUNC, NULL, SIX_SWAP_TOGGLE,	205, 25+70,	23, 19, &bSwapButtons, 0 } +#else + { TOGGLE, NOFUNC, NULL, SIX_SWAP_TOGGLE,	155, 25+70,	23, 19, &bSwapButtons, 0 } +#endif + +}; +#endif + + +/*-------------------------------------------------------------*\ +| This is the subtitles 'menu'.					| +\*-------------------------------------------------------------*/ + +#ifndef JAPAN +CONFBOX subtitlesBox[] = { + +#ifdef USE_5FLAGS + { FRGROUP, NOFUNC, NULL, USE_POINTER,	15, 100,	56, 32, NULL, FIX_UK }, + { FRGROUP, NOFUNC, NULL, USE_POINTER,	85, 100,	56, 32, NULL, FIX_FR }, + { FRGROUP, NOFUNC, NULL, USE_POINTER,	155, 100,	56, 32, NULL, FIX_GR }, + { FRGROUP, NOFUNC, NULL, USE_POINTER,	50, 137,	56, 32, NULL, FIX_IT }, + { FRGROUP, NOFUNC, NULL, USE_POINTER,	120, 137,	56, 32, NULL, FIX_SP }, +#endif +#ifdef USE_4FLAGS + { FRGROUP, NOFUNC, NULL, USE_POINTER,	20, 100,	56, 32, NULL, FIX_FR }, + { FRGROUP, NOFUNC, NULL, USE_POINTER,	108, 100,	56, 32, NULL, FIX_GR }, + { FRGROUP, NOFUNC, NULL, USE_POINTER,	64, 137,	56, 32, NULL, FIX_IT }, + { FRGROUP, NOFUNC, NULL, USE_POINTER,	152, 137,	56, 32, NULL, FIX_SP }, +#endif +#ifdef USE_3FLAGS + { FRGROUP, NOFUNC, NULL, USE_POINTER,	15, 118,	56, 32, NULL, FIX_FR }, + { FRGROUP, NOFUNC, NULL, USE_POINTER,	85, 118,	56, 32, NULL, FIX_GR }, + { FRGROUP, NOFUNC, NULL, USE_POINTER,	155, 118,	56, 32, NULL, FIX_SP }, +#endif + + { SLIDER, NOFUNC, NULL, SIX_TSPEED_SLIDER,	142, 20,	100, 2, &speedText, 0 }, + { TOGGLE, NOFUNC, NULL, SIX_STITLE_TOGGLE,	142, 20+40,	23, 19, &bSubtitles, 0 }, + +#if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS) + { ARSGBUT, CLANG, NULL, USE_POINTER,	230, 110,	23, 19, NULL, IX_TICK1 }, + { AAGBUT, RLANG, NULL, USE_POINTER,	230, 140,	23, 19, NULL, IX_CROSS1 } +#endif + +}; +#endif + + +/*-------------------------------------------------------------*\ +| This is the quit confirmation 'menu'.				| +\*-------------------------------------------------------------*/ + +CONFBOX quitBox[] = { +#ifdef JAPAN + { AAGBUT, IQUITGAME, NULL, USE_POINTER,70, 44,	23, 19, NULL, IX_TICK1 }, + { AAGBUT, CLOSEWIN, NULL, USE_POINTER,	30, 44,	23, 19, NULL, IX_CROSS1 } +#else + { AAGBUT, IQUITGAME, NULL, USE_POINTER,70, 28,	23, 19, NULL, IX_TICK1 }, + { AAGBUT, CLOSEWIN, NULL, USE_POINTER,	30, 28,	23, 19, NULL, IX_CROSS1 } +#endif +}; + + +CONFBOX topwinBox[] = { + { NOTHING, NOFUNC, NULL, USE_POINTER, 0, 0, 0, 0, NULL, 0 } +}; + + + +struct CONFINIT { +	int	h; +	int	v; +	int	x; +	int	y; +	bool bExtraWin; +	CONFBOX *Box; +	int	NumBoxes; +	int	ixHeading; +}; + +CONFINIT ciOption	= { 6, 5, 72, 23, false, optionBox,	ARRAYSIZE(optionBox),	NO_HEADING }; + +CONFINIT ciLoad		= { 10, 6, 20, 16, true, loadBox,	ARRAYSIZE(loadBox),	SIX_LOAD_HEADING }; +CONFINIT ciSave		= { 10, 6, 20, 16, true, saveBox,	ARRAYSIZE(saveBox),	SIX_SAVE_HEADING }; +#ifdef JAPAN +CONFINIT ciRestart	= { 6, 2, 72, 53, false, restartBox,	ARRAYSIZE(restartBox),	SIX_RESTART_HEADING }; +#else +CONFINIT ciRestart	= { 4, 2, 98, 53, false, restartBox,	ARRAYSIZE(restartBox),	SIX_RESTART_HEADING }; +#endif +CONFINIT ciSound	= { 10, 5, 20, 16, false, soundBox,	ARRAYSIZE(soundBox),	NO_HEADING }; +#ifdef MAC_OPTIONS +	CONFINIT ciControl	= { 10, 3, 20, 40, false, controlBox,	ARRAYSIZE(controlBox),	NO_HEADING }; +#else +	CONFINIT ciControl	= { 10, 5, 20, 16, false, controlBox,	ARRAYSIZE(controlBox),	NO_HEADING }; +#endif +#ifndef JAPAN +#if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS) +CONFINIT ciSubtitles	= { 10, 6, 20, 16, false, subtitlesBox,	ARRAYSIZE(subtitlesBox),	NO_HEADING }; +#else +CONFINIT ciSubtitles	= { 10, 3, 20, 16, false, subtitlesBox,	ARRAYSIZE(subtitlesBox),	NO_HEADING }; +#endif +#endif +CONFINIT ciQuit		= { 4, 2, 98, 53, false, quitBox,	ARRAYSIZE(quitBox),	SIX_QUIT_HEADING }; + +CONFINIT ciTopWin	= { 6, 5, 72, 23, false, topwinBox,	0,					NO_HEADING }; + +#define NOBOX (-1) + +// Conf window globals +static struct { +	CONFBOX *Box; +	int	NumBoxes; +	bool bExtraWin; +	int	ixHeading; +	bool editableRgroup; + +	int	selBox; +	int	pointBox;	// Box pointed to on last call +	int	saveModifier; +	int	fileBase; +	int	numSaved; +} cd = { +	NULL, 0, false, 0, false, +	NOBOX, NOBOX, 0, 0, 0 +}; + +// For editing save game names +char sedit[SG_DESC_LEN+2]; + +#define HL1	0	// Hilight that moves with the cursor +#define HL2	1	// Hilight on selected RGROUP box +#define HL3	2	// Text on selected RGROUP box +#define NUMHL	3 + + +// Data for button press/toggle effects +static struct { +	bool bButAnim; +	CONFBOX *box; +	bool press;		// true = button press; false = button toggle +} g_buttonEffect = { false, 0, false }; + + +//----- LOCAL FORWARD REFERENCES ----- + +enum { +	IB_NONE			= -1,	// +	IB_UP			= -2,	// negative numbers returned +	IB_DOWN			= -3,	// by WhichInvBox() +	IB_SLIDE		= -4,	// +	IB_SLIDE_UP		= -5,	// +	IB_SLIDE_DOWN	= -6	// +}; + +enum { +	HI_BIT		= ((uint)MIN_INT >> 1),	// The next to top bit +	IS_LEFT		= HI_BIT, +	IS_SLIDER	= (IS_LEFT >> 1), +	IS_RIGHT	= (IS_SLIDER >> 1), +	IS_MASK		= (IS_LEFT | IS_SLIDER | IS_RIGHT) +}; + +static int WhichInvBox(int curX, int curY, bool bSlides); +static void SlideMSlider(int x, SSFN fn); +static OBJECT *AddObject(const FREEL *pfreel, int num); +static void AddBoxes(bool posnSlide); + +static void ConfActionSpecial(int i); + + +/*-------------------------------------------------------------------------*/ +/***	Magic numbers	***/ + +#define M_SW	5	// Side width +#define M_TH	5	// Top height +#ifdef JAPAN +#define M_TOFF	6	// Title text Y offset from top +#define M_TBB	20	// Title box bottom Y offset +#else +#define M_TOFF	4	// Title text Y offset from top +#define M_TBB	14	// Title box bottom Y offset +#endif +#define M_SBL	26	// Scroll bar left X offset +#define M_SH	5	// Slider height (*) +#define M_SW	5	// Slider width (*) +#define M_SXOFF	9	// Slider X offset from right-hand side +#ifdef JAPAN +#define M_IUT	22	// Y offset of top of up arrow +#define M_IUB	30	// Y offset of bottom of up arrow +#else +#define M_IUT	16	// Y offset of top of up arrow +#define M_IUB	24	// Y offset of bottom of up arrow +#endif +#define M_IDT	10	// Y offset (from bottom) of top of down arrow +#define M_IDB	3	// Y offset (from bottom) of bottom of down arrow +#define M_IAL	12	// X offset (from right) of left of scroll arrows +#define M_IAR	3	// X offset (from right) of right of scroll arrows + +#define START_ICONX	(M_SW+1)	// } Relative offset of first icon +#define START_ICONY	(M_TBB+M_TH+1)	// } within the inventory window + +/*-------------------------------------------------------------------------*/ + + + +#ifndef JAPAN +bool LanguageChange(void) { +	LANGUAGE nLang; + +#ifdef USE_3FLAGS +	// VERY quick dodgy bodge +	if (cd.selBox == 0) +		nLang = TXT_FRENCH; +	else if (cd.selBox == 1) +		nLang = TXT_GERMAN; +	else +		nLang = TXT_SPANISH; +	if (nLang != language) { +#elif defined(USE_4FLAGS) +	nLang = (LANGUAGE)(cd.selBox + 1); +	if (nLang != language) { +#else +	if (cd.selBox != language) { +		nLang = (LANGUAGE)cd.selBox; +#endif +		KillInventory(); +		ChangeLanguage(nLang); +		language = nLang; +		return true; +	} +	else +		return false; +} +#endif + +/**************************************************************************/ +/******************** Some miscellaneous functions ************************/ +/**************************************************************************/ + +/*---------------------------------------------------------------------*\ +|	DumpIconArray()/DumpDobjArray()/DumpObjArray()			| +|-----------------------------------------------------------------------| +| Delete all the objects in iconArray[]/DobjArray[]/objArray[]		| +\*---------------------------------------------------------------------*/ +static void DumpIconArray(void){ +	for (int i = 0; i < MAX_ICONS; i++) { +		if (iconArray[i] != NULL) { +			MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[i]); +			iconArray[i] = NULL; +		} +	} +} + +/** + * Delete all the objects in DobjArray[] + */ + +static void DumpDobjArray(void) { +	for (int i = 0; i < MAX_WCOMP; i++) { +		if (DobjArray[i] != NULL) { +			MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), DobjArray[i]); +			DobjArray[i] = NULL; +		} +	} +} + +/** + * Delete all the objects in objArray[] + */ + +static void DumpObjArray(void) { +	for (int i = 0; i < MAX_WCOMP; i++) { +		if (objArray[i] != NULL) { +			MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), objArray[i]); +			objArray[i] = NULL; +		} +	} +} + +/** + * Convert item ID number to pointer to item's compiled data + * i.e. Image data and Glitter code. + */ +INV_OBJECT *findInvObject(int num) { +	INV_OBJECT *retval = pio; + +	for (int i = 0; i < numObjects; i++, retval++) { +		if (retval->id == num) +			return retval; +	} + +	error("Trying to manipulate undefined inventory icon"); +} + +/** + * Returns position of an item in one of the inventories. + * The actual position is not important for the uses that this is put to. + */ + +int InventoryPos(int num) { +	int	i; + +	for (i = 0; i < InvD[INV_1].NoofItems; i++)	// First inventory +		if (InvD[INV_1].ItemOrder[i] == num) +			return i; + +	for (i = 0; i < InvD[INV_2].NoofItems; i++)	// Second inventory +		if (InvD[INV_2].ItemOrder[i] == num) +			return i; + +	if (HeldItem == num) +		return INV_HELDNOTIN;	// Held, but not in either inventory + +	return INV_NOICON;		// Not held, not in either inventory +} + +bool IsInInventory(int object, int invnum) { +	assert(invnum == INV_1 || invnum == INV_2); + +	for (int i = 0; i < InvD[invnum].NoofItems; i++)	// First inventory +		if (InvD[invnum].ItemOrder[i] == object) +			return true; + +	return false; +} + +/** + * Returns which item is held (INV_NOICON (-1) if none) + */ + +int WhichItemHeld(void) { +	return HeldItem; +} + +/** + * Called from the cursor module when it re-initialises (at the start of + * a new scene). For if we are holding something at scene-change time. + */ + +void InventoryIconCursor(void) { +	INV_OBJECT *invObj; + +	if (HeldItem != INV_NOICON) { +		invObj = findInvObject(HeldItem); +		SetAuxCursor(invObj->hFilm); +	} +} + +/** + * Returns TRUE if the inventory is active. + */ + +bool InventoryActive(void) { +	return (InventoryState == ACTIVE_INV); +} + +int WhichInventoryOpen(void) { +	if (InventoryState != ACTIVE_INV) +		return 0; +	else +		return ino; +} + + +/**************************************************************************/ +/************** Running inventory item's Glitter code *********************/ +/**************************************************************************/ + +struct ITP_INIT { +	INV_OBJECT *pinvo; +	USER_EVENT	event; +	BUTEVENT	bev; +}; + +/** + * Run inventory item's Glitter code + */ +static void InvTinselProcess(CORO_PARAM, const void *param) { +	// COROUTINE +	CORO_BEGIN_CONTEXT; +		INT_CONTEXT *pic; +		int	ThisPointedWait;			//	Fix the 'repeated pressing bug' +	CORO_END_CONTEXT(_ctx); + +	// get the stuff copied to process when it was created +	ITP_INIT *to = (ITP_INIT *)param; + +	CORO_BEGIN_CODE(_ctx); + +	CORO_INVOKE_1(AllowDclick, to->bev); + +	_ctx->pic = InitInterpretContext(GS_INVENTORY, to->pinvo->hScript, to->event, NOPOLY, 0, to->pinvo); +	CORO_INVOKE_1(Interpret, _ctx->pic); + +	if (to->event == POINTED) { +		_ctx->ThisPointedWait = ++PointedWaitCount; +		while (1) { +			CORO_SLEEP(1); +			int	x, y; +			GetCursorXY(&x, &y, false); +			if (InvItemId(x, y) != to->pinvo->id) +				break; + +			// Fix the 'repeated pressing bug' +			if (_ctx->ThisPointedWait != PointedWaitCount) +				CORO_KILL_SELF(); +		} + +		_ctx->pic = InitInterpretContext(GS_INVENTORY, to->pinvo->hScript, UNPOINT, NOPOLY, 0, to->pinvo); +		CORO_INVOKE_1(Interpret, _ctx->pic); +	} + +	CORO_END_CODE; +} + +/** + * Run inventory item's Glitter code + */ +void RunInvTinselCode(INV_OBJECT *pinvo, USER_EVENT event, BUTEVENT be, int index) { +	ITP_INIT to = { pinvo, event, be }; +	 +	if (InventoryHidden) +		return; + +	GlitterIndex = index; +	g_scheduler->createProcess(PID_TCODE, InvTinselProcess, &to, sizeof(to)); +} + +/**************************************************************************/ +/****************** Load/Save game specific functions *********************/ +/**************************************************************************/ + +/** + * Set first load/save file entry displayed. + * Point Box[] text pointers to appropriate file descriptions. + */ + +void firstFile(int first) { +	int	i, j; + +	i = getList(); + +	cd.numSaved = i; + +	if (first < 0) +		first = 0; +	else if (first > MAX_SFILES-NUM_SL_RGROUP) +		first = MAX_SFILES-NUM_SL_RGROUP; + +	if (first == 0 && i < MAX_SFILES && cd.Box == saveBox) { +		// Blank first entry for new save +		cd.Box[0].boxText = NULL; +		cd.saveModifier = j = 1; +	} else { +		cd.saveModifier = j = 0; +	} + +	for (i = first; j < NUM_SL_RGROUP; j++, i++) { +		cd.Box[j].boxText = ListEntry(i, LE_DESC); +	} + +	cd.fileBase = first; +} + +/** + * Save the game using filename from selected slot & current description. + */ + +void InvSaveGame(void) { +	if (cd.selBox != NOBOX) { +#ifndef JAPAN +		sedit[strlen(sedit)-1] = 0;	// Don't include the cursor! +#endif +		SaveGame(ListEntry(cd.selBox-cd.saveModifier+cd.fileBase, LE_NAME), sedit); +	} +} + +/** + * Load the selected saved game. + */ +void InvLoadGame(void) { +	int	rGame; + +	if (cd.selBox != NOBOX && (cd.selBox+cd.fileBase < cd.numSaved)) { +		rGame = cd.selBox; +		cd.selBox = NOBOX; +		if (iconArray[HL3] != NULL) { +			MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL3]); +			iconArray[HL3] = NULL; +		} +		if (iconArray[HL2] != NULL) { +			MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL2]); +			iconArray[HL2] = NULL; +		} +		if (iconArray[HL1] != NULL) { +			MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); +			iconArray[HL1] = NULL; +		}	 +		RestoreGame(rGame+cd.fileBase); +	} +} + +/** + * Edit the string in sedit[] + * Returns TRUE if the string was altered. + */ +#ifndef JAPAN +bool UpdateString(const Common::KeyState &kbd) { +	int	cpos; + +	if (!cd.editableRgroup) +		return false; + +	cpos = strlen(sedit)-1; + +	if (kbd.keycode == Common::KEYCODE_BACKSPACE) { +		if (!cpos) +			return false; +		sedit[cpos] = 0; +		cpos--; +		sedit[cpos] = CURSOR_CHAR; +		return true; +//	} else if (isalnum(c) || c == ',' || c == '.' || c == '\'' || (c == ' ' && cpos != 0)) { +	} else if (IsCharImage(hTagFontHandle(), kbd.ascii) || (kbd.ascii == ' ' && cpos != 0)) { +		if (cpos == SG_DESC_LEN) +			return false; +		sedit[cpos] = kbd.ascii; +		cpos++; +		sedit[cpos] = CURSOR_CHAR; +		sedit[cpos+1] = 0; +		return true; +	} +	return false; +} +#endif + +/** + * Keystrokes get sent here when load/save screen is up. + */ +bool InvKeyIn(const Common::KeyState &kbd) { +	if (kbd.keycode == Common::KEYCODE_PAGEUP || +	    kbd.keycode == Common::KEYCODE_PAGEDOWN || +	    kbd.keycode == Common::KEYCODE_HOME || +	    kbd.keycode == Common::KEYCODE_END) +		return true;	// Key needs processing + +	if (kbd.keycode == 0 && kbd.ascii == 0) { +		; +	} else if (kbd.keycode == Common::KEYCODE_RETURN) { +		return true;	// Key needs processing +	} else if (kbd.keycode == Common::KEYCODE_ESCAPE) { +		return true;	// Key needs processing +	} else { +#ifndef JAPAN +		if (UpdateString(kbd)) { +			/* +			* Delete display of text currently being edited, +			* and replace it with freshly edited text. +			*/ +			if (iconArray[HL3] != NULL) { +				MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL3]); +				iconArray[HL3] = NULL; +			} +			iconArray[HL3] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), sedit, 0, +				InvD[ino].inventoryX + cd.Box[cd.selBox].xpos + 2, +				InvD[ino].inventoryY + cd.Box[cd.selBox].ypos, +				hTagFontHandle(), 0); +			if (MultiRightmost(iconArray[HL3]) > 213) { +				MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL3]); +				UpdateString(Common::KeyState(Common::KEYCODE_BACKSPACE)); +				iconArray[HL3] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), sedit, 0, +					InvD[ino].inventoryX + cd.Box[cd.selBox].xpos + 2, +					InvD[ino].inventoryY + cd.Box[cd.selBox].ypos, +					hTagFontHandle(), 0); +			} +			MultiSetZPosition(iconArray[HL3], Z_INV_ITEXT + 2); +		} +#endif +	} +	return false; +} + +/*---------------------------------------------------------------------*\ +|	Select()							| +|-----------------------------------------------------------------------| +| Highlights selected box.						| +| If it's editable (save game), copy existing description and add a	| +| cursor.								| +\*---------------------------------------------------------------------*/ +void Select(int i, bool force) { +#ifdef JAPAN +	time_t		secs_now; +	struct tm	*time_now; +#endif + +	i &= ~IS_MASK; + +	if (cd.selBox == i && !force) +		return; + +	cd.selBox = i; + +	// Clear previous selected highlight and text +	if (iconArray[HL2] != NULL) { +		MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL2]); +		iconArray[HL2] = NULL; +	} +	if (iconArray[HL3] != NULL) { +		MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL3]); +		iconArray[HL3] = NULL; +	} + +	// New highlight box +	switch (cd.Box[i].boxType) { +	case RGROUP: +		iconArray[HL2] = RectangleObject(BackPal(), COL_HILIGHT, cd.Box[i].w, cd.Box[i].h); +		MultiInsertObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL2]); +		MultiSetAniXY(iconArray[HL2], +		InvD[ino].inventoryX + cd.Box[i].xpos, +		InvD[ino].inventoryY + cd.Box[i].ypos); + +		// Z-position of box, and add edit text if appropriate +		if (cd.editableRgroup) { +			MultiSetZPosition(iconArray[HL2], Z_INV_ITEXT+1); + +			assert(cd.Box[i].ixText == USE_POINTER); +#ifdef JAPAN +			// Current date and time +			time(&secs_now); +			time_now = localtime(&secs_now); +			strftime(sedit, SG_DESC_LEN, "%D %H:%M", time_now); +#else +			// Current description with cursor appended +			if (cd.Box[i].boxText != NULL) { +				strcpy(sedit, cd.Box[i].boxText); +				strcat(sedit, sCursor); +			} else { +				strcpy(sedit, sCursor); +			} +#endif +			iconArray[HL3] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), sedit, 0, +				InvD[ino].inventoryX + cd.Box[i].xpos + 2, +#ifdef JAPAN +				InvD[ino].inventoryY + cd.Box[i].ypos + 2, +#else +				InvD[ino].inventoryY + cd.Box[i].ypos, +#endif +				hTagFontHandle(), 0); +			MultiSetZPosition(iconArray[HL3], Z_INV_ITEXT + 2); +		} else { +			MultiSetZPosition(iconArray[HL2], Z_INV_ICONS + 1); +		} + +		_vm->divertKeyInput(InvKeyIn); + +		break; + +#if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS) +	case FRGROUP: +		iconArray[HL2] = RectangleObject(BackPal(), COL_HILIGHT, cd.Box[i].w+6, cd.Box[i].h+6); +		MultiInsertObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL2]); +		MultiSetAniXY(iconArray[HL2], +		InvD[ino].inventoryX + cd.Box[i].xpos - 2, +		InvD[ino].inventoryY + cd.Box[i].ypos - 2); +		MultiSetZPosition(iconArray[HL2], Z_INV_BRECT+1); + +		break; +#endif +	default: +		break; +	} +} + + +/**************************************************************************/ +/***/ +/**************************************************************************/ + +/** + * If the item is not already held, hold it. + */ + +void HoldItem(int item) { +	INV_OBJECT *invObj; + +	if (HeldItem != item) { +		if (item == INV_NOICON && HeldItem != INV_NOICON) +			DelAuxCursor();			// no longer aux cursor + +		if (item != INV_NOICON) { +			invObj = findInvObject(item); +			SetAuxCursor(invObj->hFilm);	// and is aux. cursor +		} + +		HeldItem = item;			// Item held +	} + +	// Redraw contents - held item not displayed as a content. +	ItemsChanged = true; +} + +/** + * Stop holding an item. + */ + +void DropItem(int item) { +	if (HeldItem == item) { +		HeldItem = INV_NOICON;		// Item not held +		DelAuxCursor();			// no longer aux cursor +	} + +	// Redraw contents - held item was not displayed as a content. +	ItemsChanged = true; +} + +/** + * Stick the item into an inventory list (ItemOrder[]), and hold the + * item if requested. + */ + +void AddToInventory(int invno, int icon, bool hold) { +	int	i; +	bool	bOpen; +#ifdef DEBUG +	INV_OBJECT *invObj; +#endif + +	assert((invno == INV_1 || invno == INV_2 || invno == INV_CONV || invno == INV_OPEN)); // Trying to add to illegal inventory + +	if (invno == INV_OPEN) { +		assert(InventoryState == ACTIVE_INV && (ino == INV_1 || ino == INV_2)); // addopeninv() with inventry not open +		invno = ino; +		bOpen = true; + +		// Make sure it doesn't get in both! +		RemFromInventory(ino == INV_1 ? INV_2 : INV_1, icon); +	} else +		bOpen = false; + +#ifdef DEBUG +	invObj = findInvObject(icon); +	if ((invObj->attribute & IO_ONLYINV1 && invno != INV_1) +	|| (invObj->attribute & IO_ONLYINV2 && invno != INV_2)) +		error("Trying to add resticted object to wrong inventory"); +#endif + +	if (invno == INV_1) +		RemFromInventory(INV_2, icon); +	else if (invno == INV_2) +		RemFromInventory(INV_1, icon); + +	// See if it's already there +	for (i = 0; i < InvD[invno].NoofItems; i++) { +		if (InvD[invno].ItemOrder[i] == icon) +			break; +	} + +	// Add it if it isn't already there +	if (i == InvD[invno].NoofItems) { +		if (!bOpen) { +			if (invno == INV_CONV) { +				// For conversation, insert before last icon +				// which will always be the goodbye icon +				InvD[invno].ItemOrder[InvD[invno].NoofItems] = InvD[invno].ItemOrder[InvD[invno].NoofItems-1]; +				InvD[invno].ItemOrder[InvD[invno].NoofItems-1] = icon; +				InvD[invno].NoofItems++; +			} else { +				InvD[invno].ItemOrder[InvD[invno].NoofItems++] = icon; +			} +			ItemsChanged = true; +		} else { +			// It could be that the index is beyond what you'd expect +			// as delinv may well have been called +			if (GlitterIndex < InvD[invno].NoofItems) { +				memmove(&InvD[invno].ItemOrder[GlitterIndex + 1], +					&InvD[invno].ItemOrder[GlitterIndex], +					(InvD[invno].NoofItems-GlitterIndex)*sizeof(int)); +				InvD[invno].ItemOrder[GlitterIndex] = icon; +			} else { +				InvD[invno].ItemOrder[InvD[invno].NoofItems] = icon; +			} +			InvD[invno].NoofItems++; +		} +	} + +	// Hold it if requested +	if (hold) +		HoldItem(icon); +} + +/** + * Take the item from the inventory list (ItemOrder[]). + * Return FALSE if item wasn't present, true if it was. + */ + +bool RemFromInventory(int invno, int icon) { +	int i; + +	assert(invno == INV_1 || invno == INV_2 || invno == INV_CONV); // Trying to delete from illegal inventory + +	// See if it's there +	for (i = 0; i < InvD[invno].NoofItems; i++) { +		if (InvD[invno].ItemOrder[i] == icon) +			break; +	} + +	if (i == InvD[invno].NoofItems) +		return false;			// Item wasn't there +	else { +		memmove(&InvD[invno].ItemOrder[i], &InvD[invno].ItemOrder[i+1], (InvD[invno].NoofItems-i)*sizeof(int)); +		InvD[invno].NoofItems--; +		ItemsChanged = true; +		return true;			// Item removed +	} +} + + +/**************************************************************************/ +/***/ +/**************************************************************************/ + +/*---------------------------------------------------------------------*\ +|	InvArea()							| +|-----------------------------------------------------------------------| +| Work out which area of the inventory window the cursor is in.		| +|-----------------------------------------------------------------------| +| This used to be worked out with appropriately defined magic numbers.	| +| Then the graphic changed and I got it right again. Then the graphic	| +| changed and I got fed up of faffing about. It's probably easier just	| +| to rework all this.							| +\*---------------------------------------------------------------------*/ +enum {	I_NOTIN, I_MOVE, I_BODY, +	I_TLEFT, I_TRIGHT, I_BLEFT, I_BRIGHT, +	I_TOP, I_BOTTOM, I_LEFT, I_RIGHT, +	I_UP, I_SLIDE_UP, I_SLIDE, I_SLIDE_DOWN, I_DOWN, +	I_ENDCHANGE +}; + +#define EXTRA	1	// This was introduced when we decided to increase +			// the active area of the borders for re-sizing. + +/*---------------------------------*/ +#define LeftX	InvD[ino].inventoryX +#define TopY	InvD[ino].inventoryY +/*---------------------------------*/ + +int InvArea(int x, int y) { +	int RightX = MultiRightmost(RectObject) + 1; +	int BottomY = MultiLowest(RectObject) + 1; + +// Outside the whole rectangle? +	if (x <= LeftX - EXTRA || x > RightX + EXTRA +	|| y <= TopY - EXTRA || y > BottomY + EXTRA) +		return I_NOTIN; + +// The bottom line +	if (y > BottomY - 2 - EXTRA) {		// Below top of bottom line? +		if (x <= LeftX + 2 + EXTRA) +			return I_BLEFT;		// Bottom left corner +		else if (x > RightX - 2 - EXTRA) +			return I_BRIGHT;	// Bottom right corner +		else +			return I_BOTTOM;	// Just plain bottom +	} + +// The top line +	if (y <= TopY + 2 + EXTRA) {		// Above bottom of top line? +		if (x <= LeftX + 2 + EXTRA) +			return I_TLEFT;		// Top left corner +		else if (x > RightX - 2 - EXTRA) +			return I_TRIGHT;	// Top right corner +		else +			return I_TOP;		// Just plain top +	} + +// Sides +	if (x <= LeftX + 2 + EXTRA)		// Left of right of left side? +		return I_LEFT; +	else if (x > RightX - 2 - EXTRA)		// Right of left of right side? +		return I_RIGHT; + +// From here down still needs fixing up properly +/* +* In the move area? +*/ +	if (ino != INV_CONF +	&& x >= LeftX + M_SW - 2 && x <= RightX - M_SW + 3 && +	   y >= TopY + M_TH - 2  && y < TopY + M_TBB + 2) +		return I_MOVE; + +/* +* Scroll bits +*/ +	if (ino == INV_CONF && cd.bExtraWin) { +	} else { +		if (x > RightX - M_IAL + 3 && x <= RightX - M_IAR + 1) { +			if (y > TopY + M_IUT + 1 && y < TopY + M_IUB - 1) +				return I_UP; +			if (y > BottomY - M_IDT + 4 && y <= BottomY - M_IDB + 1) +				return I_DOWN; + +			if (y >= TopY + slideYmin && y < TopY + slideYmax + M_SH) { +				if (y < TopY + slideY) +					return I_SLIDE_UP; +				if (y < TopY + slideY + M_SH) +					return I_SLIDE; +				else +					return I_SLIDE_DOWN; +			} +		} +	} + +	return I_BODY; +} + +/** + * Returns the id of the icon displayed under the given position. + * Also return co-ordinates of items tag display position, if requested. + */ + +int InvItem(int *x, int *y, bool update) { +	int itop, ileft; +	int row, col; +	int item; +	int IconsX; + +	itop = InvD[ino].inventoryY + START_ICONY; + +	IconsX = InvD[ino].inventoryX + START_ICONX; + +	for (item = InvD[ino].FirstDisp, row = 0; row < InvD[ino].NoofVicons; row++) { +		ileft = IconsX; + +		for (col = 0; col < InvD[ino].NoofHicons; col++, item++) { +			if (*x >= ileft && *x < ileft + ITEM_WIDTH && +			   *y >= itop  && *y < itop + ITEM_HEIGHT) { +				if (update) { +					*x = ileft + ITEM_WIDTH/2; +					*y = itop /*+ ITEM_HEIGHT/4*/; +				} +				return item; +			} + +			ileft += ITEM_WIDTH + 1; +		} +		itop += ITEM_HEIGHT + 1; +	} +	return INV_NOICON; +} + +/** + * Returns the id of the icon displayed under the given position. + */ + +int InvItemId(int x, int y) { +	int itop, ileft; +	int row, col; +	int item; + +	if (InventoryHidden || InventoryState == IDLE_INV) +		return INV_NOICON; + +	itop = InvD[ino].inventoryY + START_ICONY; + +	int IconsX = InvD[ino].inventoryX + START_ICONX; + +	for (item = InvD[ino].FirstDisp, row = 0; row < InvD[ino].NoofVicons; row++) { +		ileft = IconsX; + +		for (col = 0; col < InvD[ino].NoofHicons; col++, item++) { +			if (x >= ileft && x < ileft + ITEM_WIDTH && +			   y >= itop  && y < itop + ITEM_HEIGHT) { +				return InvD[ino].ItemOrder[item]; +			} + +			ileft += ITEM_WIDTH + 1; +		} +		itop += ITEM_HEIGHT + 1; +	} +	return INV_NOICON; +} + +/*---------------------------------------------------------------------*\ +|	WhichInvBox()							| +|-----------------------------------------------------------------------| +| Finds which box the cursor is in.					| +\*---------------------------------------------------------------------*/ +#define MD_YSLIDTOP	7 +#define MD_YSLIDBOT	18 +#define MD_YBUTTOP	9 +#define MD_YBUTBOT	16 +#define MD_XLBUTL	1 +#define MD_XLBUTR	10 +#define MD_XRBUTL	105 +#define MD_XRBUTR	114 + +static int WhichInvBox(int curX, int curY, bool bSlides) { +	if (bSlides) { +		for (int i = 0; i < numMdSlides; i++) { +			if (curY > MultiHighest(mdSlides[i].obj) && curY < MultiLowest(mdSlides[i].obj) +			&& curX > MultiLeftmost(mdSlides[i].obj) && curX < MultiRightmost(mdSlides[i].obj)) +				return mdSlides[i].num | IS_SLIDER; +		} +	} + +	curX -= InvD[ino].inventoryX; +	curY -= InvD[ino].inventoryY; + +	for (int i = 0; i < cd.NumBoxes; i++) { +		switch (cd.Box[i].boxType) { +		case SLIDER: +			if (bSlides) { +				if (curY >= cd.Box[i].ypos+MD_YBUTTOP && curY < cd.Box[i].ypos+MD_YBUTBOT) { +					if (curX >= cd.Box[i].xpos+MD_XLBUTL && curX < cd.Box[i].xpos+MD_XLBUTR) +						return i | IS_LEFT; +					if (curX >= cd.Box[i].xpos+MD_XRBUTL && curX < cd.Box[i].xpos+MD_XRBUTR) +						return i | IS_RIGHT; +				} +			} +			break; + +		case AAGBUT: +		case ARSGBUT: +		case TOGGLE: +		case FLIP: +			if (curY > cd.Box[i].ypos && curY < cd.Box[i].ypos + cd.Box[i].h +			&& curX > cd.Box[i].xpos && curX < cd.Box[i].xpos + cd.Box[i].w) +				return i; +			break; + +		default: +			// 'Normal' box +			if (curY >= cd.Box[i].ypos && curY < cd.Box[i].ypos + cd.Box[i].h +			&& curX >= cd.Box[i].xpos && curX < cd.Box[i].xpos + cd.Box[i].w) +				return i; +			break; +		} +	} + +	if (cd.bExtraWin) { +		if (curX > 20 + 181 && curX < 20 + 181 + 8 && +			curY > 24 + 2   && curY < 24 + 139 + 5) { + +			if (curY < 24 + 2 + 5) { +				return IB_UP; +			} else if (curY > 24 + 139) { +				return IB_DOWN; +			} else if (curY+InvD[ino].inventoryY >= slideY && curY+InvD[ino].inventoryY < slideY + 5) { +				return IB_SLIDE; +			} else if (curY+InvD[ino].inventoryY < slideY) { +				return IB_SLIDE_UP; +			} else if (curY+InvD[ino].inventoryY >= slideY + 5) { +				return IB_SLIDE_DOWN; +			} +		} +	} + +	return IB_NONE; +} + +/**************************************************************************/ +/***/ +/**************************************************************************/ + +/** + * InBoxes + */ +void InvBoxes(bool InBody, int curX, int curY) { +	int	index;			// Box pointed to on this call +	const FILM *pfilm; + +	// Find out which icon is currently pointed to +	if (!InBody) +		index = -1; +	else { +		index = WhichInvBox(curX, curY, false); +	} + +	// If no icon pointed to, or points to (logical position of) +	// currently held icon, then no icon is pointed to! +	if (index < 0) { +		// unhigh-light box (if one was) +		cd.pointBox = NOBOX; +		if (iconArray[HL1] != NULL) { +			MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); +			iconArray[HL1] = NULL; +		}	 +	} else if (index != cd.pointBox) { +		cd.pointBox = index; +		// A new box is pointed to - high-light it +		if (iconArray[HL1] != NULL) { +			MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); +			iconArray[HL1] = NULL; +		} +		if ((cd.Box[cd.pointBox].boxType == ARSBUT && cd.selBox != NOBOX) || +///* I don't agree */ cd.Box[cd.pointBox].boxType == RGROUP || +		    cd.Box[cd.pointBox].boxType == AATBUT || +		    cd.Box[cd.pointBox].boxType == AABUT) { +			iconArray[HL1] = RectangleObject(BackPal(), COL_HILIGHT, cd.Box[cd.pointBox].w, cd.Box[cd.pointBox].h); +			MultiInsertObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); +			MultiSetAniXY(iconArray[HL1], +				InvD[ino].inventoryX + cd.Box[cd.pointBox].xpos, +				InvD[ino].inventoryY + cd.Box[cd.pointBox].ypos); +			MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1); +		} +		else if (cd.Box[cd.pointBox].boxType == AAGBUT || +				cd.Box[cd.pointBox].boxType == ARSGBUT || +				cd.Box[cd.pointBox].boxType == TOGGLE) { +			pfilm = (const FILM *)LockMem(winPartsf); + +			iconArray[HL1] = AddObject(&pfilm->reels[cd.Box[cd.pointBox].bi+HIGRAPH], -1); +			MultiSetAniXY(iconArray[HL1], +				InvD[ino].inventoryX + cd.Box[cd.pointBox].xpos, +				InvD[ino].inventoryY + cd.Box[cd.pointBox].ypos); +			MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1); +		} +	} +} + +static void ButtonPress(CORO_PARAM, CONFBOX *box) { +	CORO_BEGIN_CONTEXT; +	CORO_END_CONTEXT(_ctx); + +	CORO_BEGIN_CODE(_ctx); + +	const FILM *pfilm; + +	assert(box->boxType == AAGBUT || box->boxType == ARSGBUT); + +	// Replace highlight image with normal image +	pfilm = (const FILM *)LockMem(winPartsf); +	if (iconArray[HL1] != NULL) +		MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); +	pfilm = (const FILM *)LockMem(winPartsf); +	iconArray[HL1] = AddObject(&pfilm->reels[box->bi+NORMGRAPH], -1); +	MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos); +	MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1); + +	// Hold normal image for 1 frame +	CORO_SLEEP(1); +	if (iconArray[HL1] == NULL) +		return; + +	// Replace normal image with depresses image +	pfilm = (const FILM *)LockMem(winPartsf); +	MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); +	iconArray[HL1] = AddObject(&pfilm->reels[box->bi+DOWNGRAPH], -1); +	MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos); +	MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1); + +	// Hold depressed image for 2 frames +	CORO_SLEEP(2); +	if (iconArray[HL1] == NULL) +		return; + +	// Replace depressed image with normal image +	pfilm = (const FILM *)LockMem(winPartsf); +	MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); +	iconArray[HL1] = AddObject(&pfilm->reels[box->bi+NORMGRAPH], -1); +	MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos); +	MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1); + +	CORO_SLEEP(1); + +	CORO_END_CODE; +} + +static void ButtonToggle(CORO_PARAM, CONFBOX *box) { +	CORO_BEGIN_CONTEXT; +	CORO_END_CONTEXT(_ctx); + +	CORO_BEGIN_CODE(_ctx); + +	const FILM *pfilm; + +	assert(box->boxType == TOGGLE); + +	// Remove hilight image +	if (iconArray[HL1] != NULL) { +		MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); +		iconArray[HL1] = NULL; +	} + +	// Hold normal image for 1 frame +	CORO_SLEEP(1); +	if (InventoryState != ACTIVE_INV) +		return; + +	// Add depressed image +	pfilm = (const FILM *)LockMem(winPartsf); +	iconArray[HL1] = AddObject(&pfilm->reels[box->bi+DOWNGRAPH], -1); +	MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos); +	MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1); + +	// Hold depressed image for 1 frame +	CORO_SLEEP(1); +	if (iconArray[HL1] == NULL) +		return; + +	// Toggle state +	(*box->ival) = *(box->ival) ^ 1;	// XOR with true +	box->bi = *(box->ival) ? IX_TICK1 : IX_CROSS1; +	AddBoxes(false); +	// Keep highlight (e.g. flag) +	if (cd.selBox != NOBOX) +		Select(cd.selBox, true); + +	// New state, depressed image +	pfilm = (const FILM *)LockMem(winPartsf); +	if (iconArray[HL1] != NULL) +		MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); +	iconArray[HL1] = AddObject(&pfilm->reels[box->bi+DOWNGRAPH], -1); +	MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos); +	MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1); + +	// Hold new depressed image for 1 frame +	CORO_SLEEP(1); +	if (iconArray[HL1] == NULL) +		return; + +	// New state, normal +	MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); +	iconArray[HL1] = NULL; + +	// Hold normal image for 1 frame +	CORO_SLEEP(1); +	if (InventoryState != ACTIVE_INV) +		return; + +	// New state, highlighted +	pfilm = (const FILM *)LockMem(winPartsf); +	if (iconArray[HL1] != NULL) +		MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); +	iconArray[HL1] = AddObject(&pfilm->reels[box->bi+HIGRAPH], -1); +	MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos); +	MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1); + +	CORO_END_CODE; +} + +/** + * Monitors for POINTED event for inventory icons. + */ + +void InvLabels(bool InBody, int aniX, int aniY) { +	int	index;				// Icon pointed to on this call +	INV_OBJECT *invObj; + +	// Find out which icon is currently pointed to +	if (!InBody) +		index = INV_NOICON; +	else { +		index = InvItem(&aniX, &aniY, false); +		if (index != INV_NOICON) { +			if (index >= InvD[ino].NoofItems) +				index = INV_NOICON; +			else +				index = InvD[ino].ItemOrder[index]; +		} +	} + +	// If no icon pointed to, or points to (logical position of) +	// currently held icon, then no icon is pointed to! +	if (index == INV_NOICON || index == HeldItem) { +		pointedIcon = INV_NOICON; +	} else if (index != pointedIcon) { +		// A new icon is pointed to - run its script with POINTED event +		invObj = findInvObject(index); +		if (invObj->hScript) +			RunInvTinselCode(invObj, POINTED, BE_NONE, index); +		pointedIcon = index; +	} +} + +/**************************************************************************/ +/***/ +/**************************************************************************/ + +/** + * All to do with the slider. + * I can't remember how it works - or, indeed, what it does. + * It seems to set up slideStuff[], an array of possible first-displayed + * icons set against the matching y-positions of the slider. + */ + +void AdjustTop(void) { +	int tMissing, bMissing, nMissing; +	int nslideY; +	int rowsWanted; +	int slideRange; +	int n, i; + +	// Only do this if there's a slider +	if (!SlideObject) +		return; + +	rowsWanted = (InvD[ino].NoofItems - InvD[ino].FirstDisp + InvD[ino].NoofHicons-1) / InvD[ino].NoofHicons; + +	while (rowsWanted < InvD[ino].NoofVicons) { +		if (InvD[ino].FirstDisp) { +			InvD[ino].FirstDisp -= InvD[ino].NoofHicons; +			if (InvD[ino].FirstDisp < 0) +				InvD[ino].FirstDisp = 0; +			rowsWanted++; +		} else +			break; +	} +	tMissing = InvD[ino].FirstDisp ? (InvD[ino].FirstDisp + InvD[ino].NoofHicons-1)/InvD[ino].NoofHicons : 0; +	bMissing = (rowsWanted > InvD[ino].NoofVicons) ? rowsWanted - InvD[ino].NoofVicons : 0; + +	nMissing = tMissing + bMissing; +	slideRange = slideYmax - slideYmin; + +	if (!tMissing) +		nslideY = slideYmin; +	else if (!bMissing) +		nslideY = slideYmax; +	else { +		nslideY = tMissing*slideRange/nMissing; +		nslideY += slideYmin; +	} + +	if (nMissing) { +		n = InvD[ino].FirstDisp - tMissing*InvD[ino].NoofHicons; +		for (i = 0; i <= nMissing; i++, n += InvD[ino].NoofHicons) { +			slideStuff[i].n = n; +			slideStuff[i].y = (i*slideRange/nMissing) + slideYmin; +		} +		if (slideStuff[0].n < 0) +			slideStuff[0].n = 0; +		assert(i < MAX_ININV + 1); +		slideStuff[i].n = -1; +	} else { +		slideStuff[0].n = 0; +		slideStuff[0].y = slideYmin; +		slideStuff[1].n = -1; +	} + +	if (nslideY != slideY) { +		MultiMoveRelXY(SlideObject, 0, nslideY - slideY); +		slideY = nslideY; +	} +} + +/** + * Insert an inventory icon object onto the display list. + */ + +OBJECT *AddInvObject(int num, const FREEL **pfreel, const FILM **pfilm) { +	INV_OBJECT *invObj;		// Icon data +	const MULTI_INIT *pmi;		// Its INIT structure - from the reel +	IMAGE *pim;		// ... you get the picture +	OBJECT *pPlayObj;	// The object we insert + +	invObj = findInvObject(num); + +	// Get pointer to image +	pim = GetImageFromFilm(invObj->hFilm, 0, pfreel, &pmi, pfilm); + +	// Poke in the background palette +	pim->hImgPal = TO_LE_32(BackPal()); + +	// Set up the multi-object +	pPlayObj = MultiInitObject(pmi); +	MultiInsertObject(GetPlayfieldList(FIELD_STATUS), pPlayObj); + +	return pPlayObj; +} + +/** + * Create display objects for the displayed icons in an inventory window. + */ + +void FillInInventory(void) { +	int	Index;		// Index into ItemOrder[] +	int	n = 0;		// index into iconArray[] +	int	xpos, ypos; +	int	row, col; +	const FREEL *pfr; +	const FILM *pfilm; + +	DumpIconArray(); + +	if (InvDragging != ID_SLIDE) +		AdjustTop();		// Set up slideStuff[] + +	Index = InvD[ino].FirstDisp;	// Start from first displayed object +	n = 0; +	ypos = START_ICONY;		// Y-offset of first display row + +	for (row = 0; row < InvD[ino].NoofVicons; row++,	ypos += ITEM_HEIGHT + 1) { +		xpos = START_ICONX;		// X-offset of first display column + +		for (col = 0; col < InvD[ino].NoofHicons; col++) { +			if (Index >= InvD[ino].NoofItems) +				break; +			else if (InvD[ino].ItemOrder[Index] != HeldItem) { +				// Create a display object and position it +				iconArray[n] = AddInvObject(InvD[ino].ItemOrder[Index], &pfr, &pfilm); +				MultiSetAniXY(iconArray[n], InvD[ino].inventoryX + xpos , InvD[ino].inventoryY + ypos); +				MultiSetZPosition(iconArray[n], Z_INV_ICONS); + +				InitStepAnimScript(&iconAnims[n], iconArray[n], FROM_LE_32(pfr->script), ONE_SECOND / FROM_LE_32(pfilm->frate)); + +				n++; +			} +			Index++; +			xpos += ITEM_WIDTH + 1;	// X-offset of next display column +		} +	} +} + +/** + * Set up a rectangle as the background to the inventory window. + *  Additionally, sticks the window title up. + */ + +enum {FROM_HANDLE, FROM_STRING}; + +void AddBackground(OBJECT **rect, OBJECT **title, int extraH, int extraV, int textFrom) { +	// Why not 2 ???? +	int width = TLwidth + extraH + TRwidth - 3; +	int height = TLheight + extraV + BLheight - 3; + +	// Create a rectangle object +	RectObject = *rect = TranslucentObject(width, height); + +	// add it to display list and position it +	MultiInsertObject(GetPlayfieldList(FIELD_STATUS), *rect); +	MultiSetAniXY(*rect, InvD[ino].inventoryX + 1, InvD[ino].inventoryY + 1); +	MultiSetZPosition(*rect, Z_INV_BRECT); + +	// Create text object using title string +	if (textFrom == FROM_HANDLE) { +		LoadStringRes(InvD[ino].hInvTitle, tBufferAddr(), TBUFSZ); +		*title = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0, +					InvD[ino].inventoryX + width/2, InvD[ino].inventoryY + M_TOFF, +					hTagFontHandle(), TXT_CENTRE); +		assert(*title); // Inventory title string produced NULL text +		MultiSetZPosition(*title, Z_INV_HTEXT); +	} else if (textFrom == FROM_STRING && cd.ixHeading != NO_HEADING) { +		LoadStringRes(configStrings[cd.ixHeading], tBufferAddr(), TBUFSZ); +		*title = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0, +					InvD[ino].inventoryX + width/2, InvD[ino].inventoryY + M_TOFF, +					hTagFontHandle(), TXT_CENTRE); +		assert(*title); // Inventory title string produced NULL text +		MultiSetZPosition(*title, Z_INV_HTEXT); +	} +} + +/** + * Insert a part of the inventory window frame onto the display list. + */ + +static OBJECT *AddObject(const FREEL *pfreel, int num) { +	const MULTI_INIT *pmi;	// Get the MULTI_INIT structure +	IMAGE *pim; +	OBJECT *pPlayObj; + +	// Get pointer to image +	pim = GetImageFromReel(pfreel, &pmi); + +	// Poke in the background palette +	pim->hImgPal = TO_LE_32(BackPal()); + +	// Horrible bodge involving global variables to save +	// width and/or height of some window frame components +	if (num == TL) { +		TLwidth = FROM_LE_16(pim->imgWidth); +		TLheight = FROM_LE_16(pim->imgHeight); +	} else if (num == TR) { +		TRwidth = FROM_LE_16(pim->imgWidth); +	} else if (num == BL) { +		BLheight = FROM_LE_16(pim->imgHeight); +	} + +	// Set up and insert the multi-object +	pPlayObj = MultiInitObject(pmi); +	MultiInsertObject(GetPlayfieldList(FIELD_STATUS), pPlayObj); + +	return pPlayObj; +} + +/** + * Display the scroll bar slider. + */ + +void AddSlider(OBJECT **slide, const FILM *pfilm) { +	SlideObject = *slide = AddObject(&pfilm->reels[IX_SLIDE], -1); +	MultiSetAniXY(*slide, MultiRightmost(RectObject)-M_SXOFF+2, InvD[ino].inventoryY + slideY); +	MultiSetZPosition(*slide, Z_INV_MFRAME); +} + +enum { +	SLIDE_RANGE	= 81, +	SLIDE_MINX	= 8, +	SLIDE_MAXX	= 8+SLIDE_RANGE, + +	MDTEXT_YOFF	= 6, +	MDTEXT_XOFF	= -4 +}; + +/** + * Display a box with some text in it. + */ + +void AddBox(int *pi, int i) { +	int x	= InvD[ino].inventoryX + cd.Box[i].xpos; +	int y	= InvD[ino].inventoryY + cd.Box[i].ypos; +	int *pival = cd.Box[i].ival; +	int	xdisp; +	const FILM *pfilm; + +	switch (cd.Box[i].boxType) { +	default: +		// Give us a box +		iconArray[*pi] = RectangleObject(BackPal(), COL_BOX, cd.Box[i].w, cd.Box[i].h); +		MultiInsertObject(GetPlayfieldList(FIELD_STATUS), iconArray[*pi]); +		MultiSetAniXY(iconArray[*pi], x, y); +		MultiSetZPosition(iconArray[*pi], Z_INV_BRECT+1); +		*pi += 1; + +		// Stick in the text +		if (cd.Box[i].ixText == USE_POINTER) { +			if (cd.Box[i].boxText != NULL) { +				if (cd.Box[i].boxType == RGROUP) { +					iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), cd.Box[i].boxText, 0, +#ifdef JAPAN +							x+2, y+2, hTagFontHandle(), 0); +#else +							x+2, y, hTagFontHandle(), 0); +#endif +				} else { +					iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), cd.Box[i].boxText, 0, +#ifdef JAPAN +// Note: it never seems to go here! +							x + cd.Box[i].w/2, y+2, hTagFontHandle(), TXT_CENTRE); +#else +							x + cd.Box[i].w/2, y, hTagFontHandle(), TXT_CENTRE); +#endif +				} +				MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT); +				*pi += 1; +			} +		} else { +			LoadStringRes(configStrings[cd.Box[i].ixText], tBufferAddr(), TBUFSZ); +			assert(cd.Box[i].boxType != RGROUP); // You'll need to add some code! +			iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0, +#ifdef JAPAN +					x + cd.Box[i].w/2, y+2, hTagFontHandle(), TXT_CENTRE); +#else +					x + cd.Box[i].w/2, y, hTagFontHandle(), TXT_CENTRE); +#endif +			MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT); +			*pi += 1; +		} +		break; + +	case AAGBUT: +	case ARSGBUT: +		pfilm = (const FILM *)LockMem(winPartsf); + +		iconArray[*pi] = AddObject(&pfilm->reels[cd.Box[i].bi+NORMGRAPH], -1); +		MultiSetAniXY(iconArray[*pi], x, y); +		MultiSetZPosition(iconArray[*pi], Z_INV_BRECT+1); +		*pi += 1; + +		break; + +#if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS) +	case FRGROUP: +		assert(flagFilm != 0); // Language flags not declared! + +		pfilm = (const FILM *)LockMem(flagFilm); + +		if (bAmerica && cd.Box[i].bi == FIX_UK) +			cd.Box[i].bi = FIX_USA; + +		iconArray[*pi] = AddObject(&pfilm->reels[cd.Box[i].bi], -1); +		MultiSetAniXY(iconArray[*pi], x, y); +		MultiSetZPosition(iconArray[*pi], Z_INV_BRECT+2); +		*pi += 1; + +		break; +#endif +	case FLIP: +		pfilm = (const FILM *)LockMem(winPartsf); + +		if (*(cd.Box[i].ival)) +			iconArray[*pi] = AddObject(&pfilm->reels[cd.Box[i].bi], -1); +		else +			iconArray[*pi] = AddObject(&pfilm->reels[cd.Box[i].bi+1], -1); +		MultiSetAniXY(iconArray[*pi], x, y); +		MultiSetZPosition(iconArray[*pi], Z_INV_BRECT+1); +		*pi += 1; + +		// Stick in the text +		assert(cd.Box[i].ixText != USE_POINTER); +		LoadStringRes(configStrings[cd.Box[i].ixText], tBufferAddr(), TBUFSZ); +		iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0, +				x+MDTEXT_XOFF, y+MDTEXT_YOFF, hTagFontHandle(), TXT_RIGHT); +		MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT); +		*pi += 1; +		break; + +	case TOGGLE: +		pfilm = (const FILM *)LockMem(winPartsf); + +		cd.Box[i].bi = *(cd.Box[i].ival) ? IX_TICK1 : IX_CROSS1; +		iconArray[*pi] = AddObject(&pfilm->reels[cd.Box[i].bi+NORMGRAPH], -1); +		MultiSetAniXY(iconArray[*pi], x, y); +		MultiSetZPosition(iconArray[*pi], Z_INV_BRECT+1); +		*pi += 1; + +		// Stick in the text +		assert(cd.Box[i].ixText != USE_POINTER); +		LoadStringRes(configStrings[cd.Box[i].ixText], tBufferAddr(), TBUFSZ); +		iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0, +				x+MDTEXT_XOFF, y+MDTEXT_YOFF, hTagFontHandle(), TXT_RIGHT); +		MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT); +		*pi += 1; +		break; + +	case SLIDER: +		pfilm = (const FILM *)LockMem(winPartsf); +		xdisp = SLIDE_RANGE*(*pival)/cd.Box[i].w; + +		iconArray[*pi] = AddObject(&pfilm->reels[IX_MDGROOVE], -1); +		MultiSetAniXY(iconArray[*pi], x, y); +		MultiSetZPosition(iconArray[*pi], Z_MDGROOVE); +		*pi += 1; +		iconArray[*pi] = AddObject(&pfilm->reels[IX_MDSLIDER], -1); +		MultiSetAniXY(iconArray[*pi], x+SLIDE_MINX+xdisp, y); +		MultiSetZPosition(iconArray[*pi], Z_MDSLIDER); +		assert(numMdSlides < MAXSLIDES); +		mdSlides[numMdSlides].num = i; +		mdSlides[numMdSlides].min = x+SLIDE_MINX; +		mdSlides[numMdSlides].max = x+SLIDE_MAXX; +		mdSlides[numMdSlides++].obj = iconArray[*pi]; +		*pi += 1; + +		// Stick in the text +		assert(cd.Box[i].ixText != USE_POINTER); +		LoadStringRes(configStrings[cd.Box[i].ixText], tBufferAddr(), TBUFSZ); +		iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0, +				x+MDTEXT_XOFF, y+MDTEXT_YOFF, hTagFontHandle(), TXT_RIGHT); +		MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT); +		*pi += 1; +		break; +	} +} + +/** + * Display some boxes. + */ +static void AddBoxes(bool posnSlide) { +	int	oCount = NUMHL;	// Object count - allow for HL1, HL2 etc. + +	DumpIconArray(); +	numMdSlides = 0; + +	for (int i = 0; i < cd.NumBoxes; i++) { +		AddBox(&oCount, i); +	} + +	if (cd.bExtraWin) { +		if (posnSlide) +			slideY = slideYmin + (cd.fileBase*(slideYmax-slideYmin))/(MAX_SFILES-NUM_SL_RGROUP); +		MultiSetAniXY(SlideObject, InvD[ino].inventoryX + 24 + 179, slideY); +	} + +	assert(oCount < MAX_ICONS); // added too many icons +} + +/** + * Display the scroll bar slider. + */ + +void AddEWSlider(OBJECT **slide, const FILM *pfilm) { +	SlideObject = *slide = AddObject(&pfilm->reels[IX_SLIDE], -1); +	MultiSetAniXY(*slide, InvD[ino].inventoryX + 24 + 127, slideY); +	MultiSetZPosition(*slide, Z_INV_MFRAME); +} + +/** + * AddExtraWindow + */ + +int AddExtraWindow(int x, int y, OBJECT **retObj) { +	int	n = 0; +	const FILM *pfilm; + +	// Get the frame's data +	pfilm = (const FILM *)LockMem(winPartsf); + +	x += 20; +	y += 24; + +// Draw the four corners +	retObj[n] = AddObject(&pfilm->reels[IX_RTL], -1);	// Top left +	MultiSetAniXY(retObj[n], x, y); +	MultiSetZPosition(retObj[n], Z_INV_MFRAME); +	n++; +	retObj[n] = AddObject(&pfilm->reels[IX_NTR], -1);	// Top right +	MultiSetAniXY(retObj[n], x + 152, y); +	MultiSetZPosition(retObj[n], Z_INV_MFRAME); +	n++; +	retObj[n] = AddObject(&pfilm->reels[IX_BL], -1);	// Bottom left +	MultiSetAniXY(retObj[n], x, y + 124); +	MultiSetZPosition(retObj[n], Z_INV_MFRAME); +	n++; +	retObj[n] = AddObject(&pfilm->reels[IX_BR], -1);	// Bottom right +	MultiSetAniXY(retObj[n], x + 152, y + 124); +	MultiSetZPosition(retObj[n], Z_INV_MFRAME); +	n++; + +// Draw the edges +	retObj[n] = AddObject(&pfilm->reels[IX_H156], -1);	// Top +	MultiSetAniXY(retObj[n], x + 6, y); +	MultiSetZPosition(retObj[n], Z_INV_MFRAME); +	n++; +	retObj[n] = AddObject(&pfilm->reels[IX_H156], -1);	// Bottom +	MultiSetAniXY(retObj[n], x + 6, y + 143); +	MultiSetZPosition(retObj[n], Z_INV_MFRAME); +	n++; +	retObj[n] = AddObject(&pfilm->reels[IX_V104], -1);	// Left +	MultiSetAniXY(retObj[n], x, y + 20); +	MultiSetZPosition(retObj[n], Z_INV_MFRAME); +	n++; +	retObj[n] = AddObject(&pfilm->reels[IX_V104], -1);	// Right 1 +	MultiSetAniXY(retObj[n], x + 179, y + 20); +	MultiSetZPosition(retObj[n], Z_INV_MFRAME); +	n++; +	retObj[n] = AddObject(&pfilm->reels[IX_V104], -1);	// Right 2 +	MultiSetAniXY(retObj[n], x + 188, y + 20); +	MultiSetZPosition(retObj[n], Z_INV_MFRAME); +	n++; + +	slideY = slideYmin = y + 9; +	slideYmax = y + 134; +	AddEWSlider(&retObj[n++], pfilm); + +	return n; +} + + +enum InventoryType { EMPTY, FULL, CONF }; + +/** + * Construct an inventory window - either a standard one, with + * background, slider and icons, or a re-sizing window. + */ +void ConstructInventory(InventoryType filling) { +	int	eH, eV;		// Extra width and height +	int	n = 0;		// Index into object array +	int	zpos;		// Z-position of frame +	int	invX = InvD[ino].inventoryX; +	int	invY = InvD[ino].inventoryY; +	OBJECT **retObj; +	const FILM *pfilm; + +	extern bool RePosition(void);	// Forward reference +	// Select the object array to use +	if (filling == FULL || filling == CONF) { +		retObj = objArray;		// Standard window +		zpos = Z_INV_MFRAME; +	} else { +		retObj = DobjArray;		// Re-sizing window +		zpos = Z_INV_RFRAME; +	} + +	// Dispose of anything it may be replacing +	for (int i = 0; i < MAX_WCOMP; i++) { +		if (retObj[i] != NULL) { +			MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), retObj[i]); +			retObj[i] = NULL; +		} +	} + +	// Get the frame's data +	pfilm = (const FILM *)LockMem(winPartsf); + +	// Standard window is of granular dimensions +	if (filling == FULL) { +		// Round-up/down to nearest number of icons +		if (SuppH > ITEM_WIDTH / 2) +			InvD[ino].NoofHicons++; +		if (SuppV > ITEM_HEIGHT / 2) +			InvD[ino].NoofVicons++; +		SuppH = SuppV = 0; +	} + +	// Extra width and height +	eH = (InvD[ino].NoofHicons - 1) * (ITEM_WIDTH+1) + SuppH; +	eV = (InvD[ino].NoofVicons - 1) * (ITEM_HEIGHT+1) + SuppV; + +	// Which window frame corners to use +	if (filling == FULL && ino != INV_CONV) { +		TL = IX_TL; +		TR = IX_TR; +		BL = IX_BL; +		BR = IX_BR; +	} else { +		TL = IX_RTL; +		TR = IX_RTR; +		BL = IX_BL; +		BR = IX_RBR; +	} + +// Draw the four corners +	retObj[n] = AddObject(&pfilm->reels[TL], TL); +	MultiSetAniXY(retObj[n], invX, invY); +	MultiSetZPosition(retObj[n], zpos); +	n++; +	retObj[n] = AddObject(&pfilm->reels[TR], TR); +	MultiSetAniXY(retObj[n], invX + TLwidth + eH, invY); +	MultiSetZPosition(retObj[n], zpos); +	n++; +	retObj[n] = AddObject(&pfilm->reels[BL], BL); +	MultiSetAniXY(retObj[n], invX, invY + TLheight + eV); +	MultiSetZPosition(retObj[n], zpos); +	n++; +	retObj[n] = AddObject(&pfilm->reels[BR], BR); +	MultiSetAniXY(retObj[n], invX + TLwidth + eH, invY + TLheight + eV); +	MultiSetZPosition(retObj[n], zpos); +	n++; + +// Draw extra Top and bottom parts +	if (InvD[ino].NoofHicons > 1) { +		// Top side +		retObj[n] = AddObject(&pfilm->reels[hFillers[InvD[ino].NoofHicons-2]], -1); +		MultiSetAniXY(retObj[n], invX + TLwidth, invY); +		MultiSetZPosition(retObj[n], zpos); +		n++; + +		// Bottom of header box +		if (filling == FULL) { +			retObj[n] = AddObject(&pfilm->reels[hFillers[InvD[ino].NoofHicons-2]], -1); +			MultiSetAniXY(retObj[n], invX + TLwidth, invY + M_TBB + 1); +			MultiSetZPosition(retObj[n], zpos); +			n++; + +			// Extra bits for conversation - hopefully temporary +			if (ino == INV_CONV) { +				retObj[n] = AddObject(&pfilm->reels[IX_H26], -1); +				MultiSetAniXY(retObj[n], invX + TLwidth - 2, invY + M_TBB + 1); +				MultiSetZPosition(retObj[n], zpos); +				n++; + +				retObj[n] = AddObject(&pfilm->reels[IX_H52], -1); +				MultiSetAniXY(retObj[n], invX + eH - 10, invY + M_TBB + 1); +				MultiSetZPosition(retObj[n], zpos); +				n++; +			} +		} + +		// Bottom side +		retObj[n] = AddObject(&pfilm->reels[hFillers[InvD[ino].NoofHicons-2]], -1); +		MultiSetAniXY(retObj[n], invX + TLwidth, invY + TLheight + eV + BLheight - M_TH + 1); +		MultiSetZPosition(retObj[n], zpos); +		n++; +	} +	if (SuppH) { +		int offx = TLwidth + eH - 26;  +		if (offx < TLwidth)	// Not too far! +			offx = TLwidth; + +		// Top side extra +		retObj[n] = AddObject(&pfilm->reels[IX_H26], -1); +		MultiSetAniXY(retObj[n], invX + offx, invY); +		MultiSetZPosition(retObj[n], zpos); +		n++; + +		// Bottom side extra +		retObj[n] = AddObject(&pfilm->reels[IX_H26], -1); +		MultiSetAniXY(retObj[n], invX + offx, invY + TLheight + eV + BLheight - M_TH + 1); +		MultiSetZPosition(retObj[n], zpos); +		n++; +	} + +// Draw extra side parts +	if (InvD[ino].NoofVicons > 1) { +		// Left side +		retObj[n] = AddObject(&pfilm->reels[vFillers[InvD[ino].NoofVicons-2]], -1); +		MultiSetAniXY(retObj[n], invX, invY + TLheight); +		MultiSetZPosition(retObj[n], zpos); +		n++; + +		// Left side of scroll bar +		if (filling == FULL && ino != INV_CONV) { +			retObj[n] = AddObject(&pfilm->reels[vFillers[InvD[ino].NoofVicons-2]], -1); +			MultiSetAniXY(retObj[n], invX + TLwidth + eH + M_SBL + 1, invY + TLheight); +			MultiSetZPosition(retObj[n], zpos); +			n++; +		} + +		// Right side +		retObj[n] = AddObject(&pfilm->reels[vFillers[InvD[ino].NoofVicons-2]], -1); +		MultiSetAniXY(retObj[n], invX + TLwidth + eH + TRwidth - M_SW + 1, invY + TLheight); +		MultiSetZPosition(retObj[n], zpos); +		n++; +	} +	if (SuppV) { +		int offy = TLheight + eV - 26; +		if (offy < 5) +			offy = 5; + +		// Left side extra +		retObj[n] = AddObject(&pfilm->reels[IX_V26], -1); +		MultiSetAniXY(retObj[n], invX, invY + offy); +		MultiSetZPosition(retObj[n], zpos); +		n++; + +		// Right side extra +		retObj[n] = AddObject(&pfilm->reels[IX_V26], -1); +		MultiSetAniXY(retObj[n], invX + TLwidth + eH + TRwidth - M_SW + 1, invY + offy); +		MultiSetZPosition(retObj[n], zpos); +		n++; +	} + +	OBJECT **rect, **title; + +// Draw background, slider and icons +	if (filling == FULL) { +		rect = &retObj[n++]; +		title = &retObj[n++]; + +		AddBackground(rect, title, eH, eV, FROM_HANDLE); + +		if (ino == INV_CONV) +			SlideObject = NULL; +		else if (InvD[ino].NoofItems > InvD[ino].NoofHicons*InvD[ino].NoofVicons) { +			slideYmin = TLheight - 2; +			slideYmax = TLheight + eV + 10; +			AddSlider(&retObj[n++], pfilm); +		} + +		FillInInventory(); +	} +	else if (filling == CONF) { +		rect = &retObj[n++]; +		title = &retObj[n++]; + +		AddBackground(rect, title, eH, eV, FROM_STRING); +		if (cd.bExtraWin) +			n += AddExtraWindow(invX, invY, &retObj[n]); +		AddBoxes(true); +	} + +	assert(n < MAX_WCOMP); // added more parts than we can handle! + +	// Reposition returns TRUE if needs to move +	if (InvD[ino].moveable && filling == FULL && RePosition()) { +		ConstructInventory(FULL); +	} +} + + +/** + * Call this when drawing a 'FULL', movable inventory. Checks that the + * position of the Translucent object is within limits. If it isn't, + * adjusts the x/y position of the current inventory and returns TRUE. + */ +bool RePosition(void) { +	int	p; +	bool	bMoveitMoveit = false; + +	assert(RectObject); // no recangle object! + +	// Test for off-screen horizontally +	p = MultiLeftmost(RectObject); +	if (p > MAXLEFT) { +		// Too far to the right +		InvD[ino].inventoryX += MAXLEFT - p; +		bMoveitMoveit = true;			// I like to.... +	} else { +		// Too far to the left? +		p = MultiRightmost(RectObject); +		if (p < MINRIGHT) { +			InvD[ino].inventoryX += MINRIGHT - p; +			bMoveitMoveit = true;		// I like to.... +		} +	} + +	// Test for off-screen vertically +	p = MultiHighest(RectObject); +	if (p < MINTOP) { +		// Too high +		InvD[ino].inventoryY += MINTOP - p; +		bMoveitMoveit = true;			// I like to.... +	} else if (p > MAXTOP) { +		// Too low +		InvD[ino].inventoryY += MAXTOP - p; +		bMoveitMoveit = true;			// I like to.... +	} +		 +	return bMoveitMoveit; +} + +/**************************************************************************/ +/***/ +/**************************************************************************/ + +/** + * Get the cursor's reel, poke in the background palette, + * and customise the cursor. + */ +void AlterCursor(int num) { +	const FREEL *pfreel; +	IMAGE *pim; + +	// Get pointer to image +	pim = GetImageFromFilm(winPartsf, num, &pfreel); + +	// Poke in the background palette +	pim->hImgPal = TO_LE_32(BackPal()); + +	SetTempCursor(FROM_LE_32(pfreel->script)); +} + +enum InvCursorFN {IC_AREA, IC_DROP}; + +/** + * InvCursor + */ +void InvCursor(InvCursorFN fn, int CurX, int CurY) { +	static enum { IC_NORMAL, IC_DR, IC_UR, IC_TB, IC_LR, +		IC_INV, IC_UP, IC_DN } ICursor = IC_NORMAL;	// FIXME: local static var + +	int	area;		// The part of the window the cursor is over +	bool	restoreMain = false; + +	// If currently dragging, don't be messing about with the cursor shape +	if (InvDragging != ID_NONE) +		return; + +	switch (fn) { +	case IC_DROP: +		ICursor = IC_NORMAL; +		InvCursor(IC_AREA, CurX, CurY); +		break; + +	case IC_AREA: +		area = InvArea(CurX, CurY); + +		// Check for POINTED events +		if (ino == INV_CONF) +			InvBoxes(area == I_BODY, CurX, CurY); +		else +			InvLabels(area == I_BODY, CurX, CurY); + +		// No cursor trails while within inventory window +		if (area == I_NOTIN) +			UnHideCursorTrails(); +		else +			HideCursorTrails(); + +		switch (area) { +		case I_NOTIN: +			restoreMain = true; +			break; + +		case I_TLEFT: +		case I_BRIGHT: +			if (!InvD[ino].resizable) +				restoreMain = true; +			else if (ICursor != IC_DR) { +				AlterCursor(IX_CURDD); +				ICursor = IC_DR; +			} +			break; + +		case I_TRIGHT: +		case I_BLEFT: +			if (!InvD[ino].resizable) +				restoreMain = true; +			else if (ICursor != IC_UR) { +				AlterCursor(IX_CURDU); +				ICursor = IC_UR; +			} +			break; + +		case I_TOP: +		case I_BOTTOM: +			if (!InvD[ino].resizable) { +				restoreMain = true; +				break; +			} +			if (ICursor != IC_TB) { +				AlterCursor(IX_CURUD); +				ICursor = IC_TB; +			} +			break; + +		case I_LEFT: +		case I_RIGHT: +			if (!InvD[ino].resizable) +				restoreMain = true; +			else if (ICursor != IC_LR) { +				AlterCursor(IX_CURLR); +				ICursor = IC_LR; +			} +			break; + +		case I_UP: +		case I_SLIDE_UP: +		case I_DOWN: +		case I_SLIDE_DOWN: +		case I_SLIDE: +		case I_MOVE: +		case I_BODY: +			restoreMain = true; +			break; +		} +		break; +	} + +	if (restoreMain && ICursor != IC_NORMAL) { +		RestoreMainCursor(); +		ICursor = IC_NORMAL; +	} +} + + + + +/*-------------------------------------------------------------------------*/ + + +/**************************************************************************/ +/******************** Conversation specific functions *********************/ +/**************************************************************************/ + + +void ConvAction(int index) { +	assert(ino == INV_CONV); // not conv. window! + +	switch (index) { +	case INV_NOICON: +		return; + +	case INV_CLOSEICON: +		thisConvIcon = -1;	// Postamble +		break; + +	case INV_OPENICON: +		thisConvIcon = -2;	// Preamble +		break; + +	default: +		thisConvIcon = InvD[ino].ItemOrder[index]; +		break; +	} + +	RunPolyTinselCode(thisConvPoly, CONVERSE, BE_NONE, true); +} +/*-------------------------------------------------------------------------*/ + +void AddIconToPermanentDefaultList(int icon) { +	int i; + +	// See if it's already there +	for (i = 0; i < Num0Order; i++) { +		if (Inv0Order[i] == icon) +			break; +	} + +	// Add it if it isn't already there +	if (i == Num0Order) { +		Inv0Order[Num0Order++] = icon; +	} +} + +/*-------------------------------------------------------------------------*/ + +void convPos(int fn) { +	if (fn == CONV_DEF) +		InvD[INV_CONV].inventoryY = 8; +	else if (fn == CONV_BOTTOM) +		InvD[INV_CONV].inventoryY = 150; +} + +void ConvPoly(HPOLYGON hPoly) { +	thisConvPoly = hPoly; +} + +int convIcon(void) { +	return thisConvIcon; +} + +void CloseDownConv(void) { +	if (InventoryState == ACTIVE_INV && ino == INV_CONV) { +		KillInventory(); +	} +} + +void convHide(bool hide) { +	int aniX, aniY; +	int i; + +	if (InventoryState == ACTIVE_INV && ino == INV_CONV) { +		if (hide) { +			for (i = 0; objArray[i] && i < MAX_WCOMP; i++) { +				MultiAdjustXY(objArray[i], 2*SCREEN_WIDTH, 0); +			} +			for (i = 0; iconArray[i] && i < MAX_ICONS; i++) { +				MultiAdjustXY(iconArray[i], 2*SCREEN_WIDTH, 0); +			} +			InventoryHidden = true; + +			InvLabels(false, 0, 0); +		} else { +			InventoryHidden = false; + +			for (i = 0; objArray[i] && i < MAX_WCOMP; i++) { +				MultiAdjustXY(objArray[i], -2*SCREEN_WIDTH, 0); +			} + +			// Don't flash if items changed. If they have, will be redrawn anyway. +			if (!ItemsChanged) { +				for (i = 0; iconArray[i] && i < MAX_ICONS; i++) { +					MultiAdjustXY(iconArray[i], -2*SCREEN_WIDTH, 0); +				} +			} + +			GetCursorXY(&aniX, &aniY, false); +			InvLabels(true, aniX, aniY); +		} +	} +} + +bool convHid(void) { +	return InventoryHidden; +} + + +/**************************************************************************/ +/******************* Open and closing functions ***************************/ +/**************************************************************************/ + +/** + * Start up an inventory window. + */ + +void PopUpInventory(int invno) { +	assert((invno == INV_1 || invno == INV_2 || invno == INV_CONV || invno == INV_CONF)); // Trying to open illegal inventory + +	if (InventoryState == IDLE_INV) { +		bOpenConf = false;	// Better safe than sorry... + +		DisableTags();		// Tags disabled during inventory + +		if (invno == INV_CONV) {	// Conversation window? +			// Start conversation with permanent contents +			memset(InvD[INV_CONV].ItemOrder, 0, MAX_ININV*sizeof(int)); +			memcpy(InvD[INV_CONV].ItemOrder, Inv0Order, Num0Order*sizeof(int)); +			InvD[INV_CONV].NoofItems = Num0Order; +			thisConvIcon = 0; +		} else if (invno == INV_CONF) {	// Configuration window? +			cd.selBox = NOBOX; +			cd.pointBox = NOBOX; +		} + +		ino = invno;			// The open inventory + +		ItemsChanged = false;		// Nothing changed +		InvDragging = ID_NONE;		// Not dragging +		InventoryState = ACTIVE_INV;	// Inventory actiive +		InventoryHidden = false;	// Not hidden +		InventoryMaximised = InvD[ino].bMax; +		if (invno != INV_CONF)	// Configuration window? +			ConstructInventory(FULL);	// Draw it up +		else { +			ConstructInventory(CONF);	// Draw it up +		} +	} +} + +void SetConfGlobals(CONFINIT *ci) { +	InvD[INV_CONF].MinHicons = InvD[INV_CONF].MaxHicons = InvD[INV_CONF].NoofHicons = ci->h; +	InvD[INV_CONF].MaxVicons = InvD[INV_CONF].MinVicons = InvD[INV_CONF].NoofVicons = ci->v; +	InvD[INV_CONF].inventoryX = ci->x; +	InvD[INV_CONF].inventoryY = ci->y; +	cd.bExtraWin = ci->bExtraWin; +	cd.Box = ci->Box; +	cd.NumBoxes = ci->NumBoxes; +	cd.ixHeading = ci->ixHeading; +} + +/** + * PopupConf + */ + +void PopUpConf(CONFTYPE type) { +	int curX, curY; + +	if (InventoryState != IDLE_INV) +		return; + +	InvD[INV_CONF].resizable = false; +	InvD[INV_CONF].moveable = false; + +	switch (type) { +	case SAVE: +	case LOAD: +		if (type == SAVE) { +			SetCursorScreenXY(262, 91); +			SetConfGlobals(&ciSave); +			cd.editableRgroup = true; +		} else { +			SetConfGlobals(&ciLoad); +			cd.editableRgroup = false; +		} +		firstFile(0); +		break; + +	case QUIT: +#ifdef JAPAN +		SetCursorScreenXY(180, 106); +#else +		SetCursorScreenXY(180, 90); +#endif +		SetConfGlobals(&ciQuit); +		break; + +	case RESTART: +#ifdef JAPAN +		SetCursorScreenXY(180, 106); +#else +		SetCursorScreenXY(180, 90); +#endif +		SetConfGlobals(&ciRestart); +		break; + +	case OPTION: +		SetConfGlobals(&ciOption); +		break; + +	case CONTROLS: +		SetConfGlobals(&ciControl); +		break; + +	case SOUND: +		SetConfGlobals(&ciSound); +		break; + +#ifndef JAPAN +	case SUBT: +		SetConfGlobals(&ciSubtitles); +		break; +#endif + +	case TOPWIN: +		SetConfGlobals(&ciTopWin); +		ino = INV_CONF; +		ConstructInventory(CONF);	// Draw it up +		InventoryState = BOGUS_INV; +		return; + +	default: +		return; +	} + +	if (HeldItem != INV_NOICON) +		DelAuxCursor();			// no longer aux cursor + +	PopUpInventory(INV_CONF); + +	if (type == SAVE || type == LOAD) +		Select(0, false); +#ifndef JAPAN +#if !defined(USE_3FLAGS) || !defined(USE_4FLAGS) || !defined(USE_5FLAGS) +	else if (type == SUBT) { +#ifdef USE_3FLAGS +		// VERY quick dirty bodges +		if (language == TXT_FRENCH) +			Select(0, false); +		else if (language == TXT_GERMAN) +			Select(1, false); +		else +			Select(2, false); +#elif defined(USE_4FLAGS) +		Select(language-1, false); +#else +		Select(language, false); +#endif +	} +#endif +#endif // JAPAN + +	GetCursorXY(&curX, &curY, false); +	InvCursor(IC_AREA, curX, curY); +} + +/** + * Close down an inventory window. + */ + +void KillInventory(void) { +	if (objArray[0] != NULL) { +		DumpObjArray(); +		DumpDobjArray(); +		DumpIconArray(); +	} + +	if (InventoryState == ACTIVE_INV) { +		EnableTags(); + +		InvD[ino].bMax = InventoryMaximised; + +		UnHideCursorTrails(); +		_vm->divertKeyInput(NULL); +	} + +	InventoryState = IDLE_INV; + +	if (bOpenConf) { +		bOpenConf = false; +		PopUpConf(OPTION); +	} else if (ino == INV_CONF) +		InventoryIconCursor(); +} + +void CloseInventory(void) { +	// If not active, ignore this +	if (InventoryState != ACTIVE_INV) +		return; + +	// If hidden, a conversation action is still underway - ignore this +	if (InventoryHidden) +		return; + +	// If conversation, this is a closeing event +	if (ino == INV_CONV) +		ConvAction(INV_CLOSEICON); + +	KillInventory(); + +	RestoreMainCursor(); +} + + + +/**************************************************************************/ +/************************ The inventory process ***************************/ +/**************************************************************************/ + +/** + * Redraws the icons if appropriate. Also handle button press/toggle effects + */ +void InventoryProcess(CORO_PARAM, const void *) { +	// COROUTINE +	CORO_BEGIN_CONTEXT; +	CORO_END_CONTEXT(_ctx); + +	CORO_BEGIN_CODE(_ctx); + +	while (1) { +		CORO_SLEEP(1);		// allow scheduling + +		if (objArray[0] != NULL) { +			if (ItemsChanged && ino != INV_CONF && !InventoryHidden) { +				FillInInventory(); + +				// Needed when clicking on scroll bar. +				int	curX, curY; +				GetCursorXY(&curX, &curY, false); +				InvCursor(IC_AREA, curX, curY); + +				ItemsChanged = false; +			} +			if (ino != INV_CONF) { +				for (int i = 0; i < MAX_ICONS; i++) { +					if (iconArray[i] != NULL) +						StepAnimScript(&iconAnims[i]); +				} +			} +			if (InvDragging == ID_MDCONT) { +				// Mixing desk control +				int sval, index, *pival; + +				index = cd.selBox & ~IS_MASK; +				pival = cd.Box[index].ival; +				sval = *pival; + +				if (cd.selBox & IS_LEFT) { +					*pival -= cd.Box[index].h; +					if (*pival < 0) +						*pival = 0; +				} else if (cd.selBox & IS_RIGHT) { +					*pival += cd.Box[index].h; +					if (*pival > cd.Box[index].w) +						*pival = cd.Box[index].w; +				} + +				if (sval != *pival) { +					SlideMSlider(0, (cd.selBox & IS_RIGHT) ? S_TIMEUP : S_TIMEDN); +				} +			} +		} + +		if (g_buttonEffect.bButAnim) { +			assert(g_buttonEffect.box); +			if (g_buttonEffect.press) { +				if (g_buttonEffect.box->boxType == AAGBUT || g_buttonEffect.box->boxType == ARSGBUT) +					CORO_INVOKE_1(ButtonPress, g_buttonEffect.box); +				switch (g_buttonEffect.box->boxFunc) { +				case SAVEGAME: +					KillInventory(); +					InvSaveGame(); +					break; +				case LOADGAME: +					KillInventory(); +					InvLoadGame(); +					break; +				case IQUITGAME: +					_vm->quitFlag = true; +					break; +				case CLOSEWIN: +					KillInventory(); +					break; +				case OPENLOAD: +					KillInventory(); +					PopUpConf(LOAD); +					break; +				case OPENSAVE: +					KillInventory(); +					PopUpConf(SAVE); +					break; +				case OPENREST: +					KillInventory(); +					PopUpConf(RESTART); +					break; +				case OPENSOUND: +					KillInventory(); +					PopUpConf(SOUND); +					break; +				case OPENCONT: +					KillInventory(); +					PopUpConf(CONTROLS); +					break; +	#ifndef JAPAN +				case OPENSUBT: +					KillInventory(); +					PopUpConf(SUBT); +					break; +	#endif +				case OPENQUIT: +					KillInventory(); +					PopUpConf(QUIT); +					break; +				case INITGAME: +					KillInventory(); +					bRestart = true; +					break; +	#if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS) +				case CLANG: +					if (!LanguageChange()) +						KillInventory(); +					break; +				case RLANG: +					KillInventory(); +					break; +	#endif +				default: +					break; +				} +			} else +				CORO_INVOKE_1(ButtonToggle, g_buttonEffect.box); +		 +			g_buttonEffect.bButAnim = false; +		} + +	} +	CORO_END_CODE; +} + +/**************************************************************************/ +/*************** Drag stuff - Resizing and moving window ******************/ +/**************************************************************************/ + +/** + * Appears to find the nearest entry in slideStuff[] to the supplied + * y-coordinate. + */ +int NearestSlideY(int fity) { +	int nearDist = 1000; +	int thisDist; +	int nearI = 0;	// Index of nearest fit +	int i = 0; + +	do { +		thisDist = ABS(slideStuff[i].y - fity); +		if (thisDist < nearDist) { +			nearDist = thisDist; +			nearI = i; +		} +	} while (slideStuff[++i].n != -1); +	return nearI; +} + +/** + * Gets called at the start and end of a drag on the slider, and upon + * y-movement during such a drag. + */ +void SlideSlider(int y, SSFN fn) { +	static int newY = 0, lasti = 0;	// FIXME: local static var +	int gotoY, ati; + +	// Only do this if there's a slider +	if (!SlideObject) +		return; + +	switch (fn) { +	case S_START:			// Start of a drag on the slider +		newY = slideY; +		lasti = NearestSlideY(slideY); +		break; + +	case S_SLIDE:			// Y-movement during drag +		newY = newY + y;		// New y-position + +		if (newY < slideYmin) +			gotoY = slideYmin;	// Above top limit +		else if (newY > slideYmax) +			gotoY = slideYmax;	// Below bottom limit +		else +			gotoY = newY;		// Hunky-Dory + +		// Move slider to new position +		MultiMoveRelXY(SlideObject, 0, gotoY - slideY); +		slideY = gotoY; + +		// Re-draw icons if necessary +		ati = NearestSlideY(slideY); +		if (ati != lasti) { +			InvD[ino].FirstDisp = slideStuff[ati].n; +			assert(InvD[ino].FirstDisp >= 0); // negative first displayed +			ItemsChanged = true; +			lasti = ati; +		} +		break; + +	case S_END:			// End of a drag on the slider +		// Draw icons from new start icon +		ati = NearestSlideY(slideY); +		InvD[ino].FirstDisp = slideStuff[ati].n; +		ItemsChanged = true; +		break; + +	default: +		break; +	} +} + +/** + * Gets called at the start and end of a drag on the slider, and upon + * y-movement during such a drag. + */ + +void SlideCSlider(int y, SSFN fn) { +	static int newY = 0;	// FIXME: local static var +	int	gotoY; +	int	fc; + +	// Only do this if there's a slider +	if (!SlideObject) +		return; + +	switch (fn) { +	case S_START:			// Start of a drag on the slider +		newY = slideY; +		break; + +	case S_SLIDE:			// Y-movement during drag +		newY = newY + y;		// New y-position + +		if (newY < slideYmin) +			gotoY = slideYmin;	// Above top limit +		else if (newY > slideYmax) +			gotoY = slideYmax;	// Below bottom limit +		else +			gotoY = newY;		// Hunky-Dory + +		slideY = gotoY; + +		fc = cd.fileBase; +		firstFile((slideY-slideYmin)*(MAX_SFILES-NUM_SL_RGROUP)/(slideYmax-slideYmin)); +		if (fc != cd.fileBase) { +			AddBoxes(false); +				fc -= cd.fileBase; +			cd.selBox += fc; +			if (cd.selBox < 0) +				cd.selBox = 0; +			else if (cd.selBox >= NUM_SL_RGROUP) +				cd.selBox = NUM_SL_RGROUP-1; +			Select(cd.selBox, true); +		} +		break; + +	case S_END:			// End of a drag on the slider +		break; + +	default: +		break; +	} +} + +/** + * Gets called at the start and end of a drag on a mixing desk slider, + * and upon x-movement during such a drag. + */ + +static void SlideMSlider(int x, SSFN fn) { +	static int newX = 0;	// FIXME: local static var +	int gotoX; +	int index, i; + +	if (fn == S_END || fn == S_TIMEUP || fn == S_TIMEDN) +		; +	else if (!(cd.selBox & IS_SLIDER)) +		return; + +	// Work out the indices +	index = cd.selBox & ~IS_MASK; +	for (i = 0; i < numMdSlides; i++) +		if (mdSlides[i].num == index) +			break; +	assert(i < numMdSlides); + +	switch (fn) { +	case S_START:			// Start of a drag on the slider +		// can use index as a throw-away value +		GetAniPosition(mdSlides[i].obj, &newX, &index); +		lX = sX = newX; +		break; + +	case S_SLIDE:			// X-movement during drag +		if (x == 0) +			return; + +		newX = newX + x;	// New x-position + +		if (newX < mdSlides[i].min) +			gotoX = mdSlides[i].min;	// Below bottom limit +		else if (newX > mdSlides[i].max) +			gotoX = mdSlides[i].max;	// Above top limit +		else +			gotoX = newX;		// Hunky-Dory + +		// Move slider to new position +		MultiMoveRelXY(mdSlides[i].obj, gotoX - sX, 0); +		sX = gotoX; + +		if (lX != sX) { +			*cd.Box[index].ival = (sX - mdSlides[i].min)*cd.Box[index].w/SLIDE_RANGE; +			if (cd.Box[index].boxFunc == MIDIVOL) +				SetMidiVolume(*cd.Box[index].ival); +#ifdef MAC_OPTIONS +			if (cd.Box[index].boxFunc == MASTERVOL) +				SetSystemVolume(*cd.Box[index].ival); + +			if (cd.Box[index].boxFunc == SAMPVOL) +				SetSampleVolume(*cd.Box[index].ival); +#endif +			lX = sX; +		} +		break; + +	case S_TIMEUP: +	case S_TIMEDN: +		gotoX = SLIDE_RANGE*(*cd.Box[index].ival)/cd.Box[index].w; +		MultiSetAniX(mdSlides[i].obj, mdSlides[i].min+gotoX); + +		if (cd.Box[index].boxFunc == MIDIVOL) +			SetMidiVolume(*cd.Box[index].ival); +#ifdef MAC_OPTIONS +			if (cd.Box[index].boxFunc == MASTERVOL) +				SetSystemVolume(*cd.Box[index].ival); + +			if (cd.Box[index].boxFunc == SAMPVOL) +				SetSampleVolume(*cd.Box[index].ival); +#endif +		break; + +	case S_END:			// End of a drag on the slider +		AddBoxes(false);	// Might change position slightly +#ifndef JAPAN +		if (ino == INV_CONF && cd.Box == subtitlesBox) +			Select(language, false); +#endif +		break; +	} +} + +/** + * Called from ChangeingSize() during re-sizing. + */ + +void GettingTaller(void) { +	if (SuppV) { +		Ychange += SuppV; +		if (Ycompensate == 'T') +			InvD[ino].inventoryY += SuppV; +		SuppV = 0; +	} +	while (Ychange > (ITEM_HEIGHT+1) && InvD[ino].NoofVicons < InvD[ino].MaxVicons) { +		Ychange -= (ITEM_HEIGHT+1); +		InvD[ino].NoofVicons++; +		if (Ycompensate == 'T') +			InvD[ino].inventoryY -= (ITEM_HEIGHT+1); +	} +	if (InvD[ino].NoofVicons < InvD[ino].MaxVicons) { +		SuppV = Ychange; +		Ychange = 0; +		if (Ycompensate == 'T') +			InvD[ino].inventoryY -= SuppV; +	} +} + +/** + * Called from ChangeingSize() during re-sizing. + */ + +void GettingShorter(void) { +	int StartNvi = InvD[ino].NoofVicons; +	int StartUv = SuppV; + +	if (SuppV) { +		Ychange += (SuppV - (ITEM_HEIGHT+1)); +		InvD[ino].NoofVicons++; +		SuppV = 0; +	} +	while (Ychange < -(ITEM_HEIGHT+1) && InvD[ino].NoofVicons > InvD[ino].MinVicons) { +		Ychange += (ITEM_HEIGHT+1); +		InvD[ino].NoofVicons--; +	} +	if (InvD[ino].NoofVicons > InvD[ino].MinVicons && Ychange) { +		SuppV = (ITEM_HEIGHT+1) + Ychange; +		InvD[ino].NoofVicons--; +		Ychange = 0; +	} +	if (Ycompensate == 'T') +		InvD[ino].inventoryY += (ITEM_HEIGHT+1)*(StartNvi - InvD[ino].NoofVicons) - (SuppV - StartUv); +} + +/** + * Called from ChangeingSize() during re-sizing. + */ + +void GettingWider(void) { +	int StartNhi = InvD[ino].NoofHicons; +	int StartUh = SuppH; + +	if (SuppH) { +		Xchange += SuppH; +		SuppH = 0; +	} +	while (Xchange > (ITEM_WIDTH+1) && InvD[ino].NoofHicons < InvD[ino].MaxHicons) { +		Xchange -= (ITEM_WIDTH+1); +		InvD[ino].NoofHicons++; +	} +	if (InvD[ino].NoofHicons < InvD[ino].MaxHicons) { +		SuppH = Xchange; +		Xchange = 0; +	} +	if (Xcompensate == 'L') +		InvD[ino].inventoryX += (ITEM_WIDTH+1)*(StartNhi - InvD[ino].NoofHicons) - (SuppH - StartUh); +} + +/** + * Called from ChangeingSize() during re-sizing. + */ + +void GettingNarrower(void) { +	int StartNhi = InvD[ino].NoofHicons; +	int StartUh = SuppH; + +	if (SuppH) { +		Xchange += (SuppH - (ITEM_WIDTH+1)); +		InvD[ino].NoofHicons++; +		SuppH = 0; +	} +	while (Xchange < -(ITEM_WIDTH+1) && InvD[ino].NoofHicons > InvD[ino].MinHicons) { +		Xchange += (ITEM_WIDTH+1); +		InvD[ino].NoofHicons--; +	} +	if (InvD[ino].NoofHicons > InvD[ino].MinHicons && Xchange) { +		SuppH = (ITEM_WIDTH+1) + Xchange; +		InvD[ino].NoofHicons--; +		Xchange = 0; +	} +	if (Xcompensate == 'L') +		InvD[ino].inventoryX += (ITEM_WIDTH+1)*(StartNhi - InvD[ino].NoofHicons) - (SuppH - StartUh); +} + + +/** + * Called from Xmovement()/Ymovement() during re-sizing. + */ + +void ChangeingSize(void) { +	/* Make it taller or shorter if necessary. */ +	if (Ychange > 0) +		GettingTaller(); +	else if (Ychange < 0) +		GettingShorter(); + +	/* Make it wider or narrower if necessary. */ +	if (Xchange > 0) +		GettingWider(); +	else if (Xchange < 0) +		GettingNarrower(); + +	ConstructInventory(EMPTY); +} + +/** + * Called from cursor module when cursor moves while inventory is up. + */ + +void Xmovement(int x) { +	int aniX, aniY; +	int i; + +	if (x && objArray[0] != NULL) { +		switch (InvDragging) { +		case ID_MOVE: +			GetAniPosition(objArray[0], &InvD[ino].inventoryX, &aniY); +			InvD[ino].inventoryX +=x; +			MultiSetAniX(objArray[0], InvD[ino].inventoryX); +			for (i = 1; objArray[i] && i < MAX_WCOMP; i++) +				MultiMoveRelXY(objArray[i], x, 0); +			for (i = 0; iconArray[i] && i < MAX_ICONS; i++) +				MultiMoveRelXY(iconArray[i], x, 0); +			break; + +		case ID_LEFT: +		case ID_TLEFT: +		case ID_BLEFT: +			Xchange -= x; +			ChangeingSize(); +			break; + +		case ID_RIGHT: +		case ID_TRIGHT: +		case ID_BRIGHT: +			Xchange += x; +			ChangeingSize(); +			break; + +		case ID_NONE: +			GetCursorXY(&aniX, &aniY, false); +			InvCursor(IC_AREA, aniX, aniY); +			break; + +		case ID_MDCONT: +			SlideMSlider(x, S_SLIDE); +			break; + +		default: +			break; +		} +	} +} + +/** + * Called from cursor module when cursor moves while inventory is up. + */ + +void Ymovement(int y) { +	int aniX, aniY; +	int i; + +	if (y && objArray[0] != NULL) { +		switch (InvDragging) { +		case ID_MOVE: +			GetAniPosition(objArray[0], &aniX, &InvD[ino].inventoryY); +			InvD[ino].inventoryY +=y; +			MultiSetAniY(objArray[0], InvD[ino].inventoryY); +			for (i = 1; objArray[i] && i < MAX_WCOMP; i++) +				MultiMoveRelXY(objArray[i], 0, y); +			for (i = 0; iconArray[i] && i < MAX_ICONS; i++) +				MultiMoveRelXY(iconArray[i], 0, y); +			break; + +		case ID_SLIDE: +			SlideSlider(y, S_SLIDE); +			break; + +		case ID_CSLIDE: +			SlideCSlider(y, S_SLIDE); +			break; + +		case ID_BOTTOM: +		case ID_BLEFT: +		case ID_BRIGHT: +			Ychange += y; +			ChangeingSize(); +			break; + +		case ID_TOP: +		case ID_TLEFT: +		case ID_TRIGHT: +			Ychange -= y; +			ChangeingSize(); +			break; + +		case ID_NONE: +			GetCursorXY(&aniX, &aniY, false); +			InvCursor(IC_AREA, aniX, aniY); +			break; + +		default: +			break; +		} +	} +} + +/** + * Called when a drag is commencing. + */ + +void InvDragStart(void) { +	int curX, curY;		// cursor's animation position + +	GetCursorXY(&curX, &curY, false); + +/* +* Do something different for Save/Restore screens +*/ +	if (ino == INV_CONF) { +		int	whichbox; + +		whichbox = WhichInvBox(curX, curY, true); + +		if (whichbox == IB_SLIDE) { +			InvDragging = ID_CSLIDE; +			SlideCSlider(0, S_START); +		} else if (whichbox > 0 && (whichbox & IS_MASK)) { +			InvDragging = ID_MDCONT;	// Mixing desk control +			cd.selBox = whichbox; +			SlideMSlider(0, S_START); +		} +		return; +	} + +/* +* Normal operation +*/ +	switch (InvArea(curX, curY)) { +	case I_MOVE: +		if (InvD[ino].moveable) { +			InvDragging = ID_MOVE; +		} +		break; + +	case I_SLIDE: +		InvDragging = ID_SLIDE; +		SlideSlider(0, S_START); +		break; + +	case I_BOTTOM: +		if (InvD[ino].resizable) { +			Ychange = 0; +			InvDragging = ID_BOTTOM; +			Ycompensate = 'B'; +		} +		break; + +	case I_TOP: +		if (InvD[ino].resizable) { +			Ychange = 0; +			InvDragging = ID_TOP; +			Ycompensate = 'T'; +		} +		break; + +	case I_LEFT: +		if (InvD[ino].resizable) { +			Xchange = 0; +			InvDragging = ID_LEFT; +			Xcompensate = 'L'; +		} +		break; + +	case I_RIGHT: +		if (InvD[ino].resizable) { +			Xchange = 0; +			InvDragging = ID_RIGHT; +			Xcompensate = 'R'; +		} +		break; + +	case I_TLEFT: +		if (InvD[ino].resizable) { +			Ychange = 0; +			Ycompensate = 'T'; +			Xchange = 0; +			Xcompensate = 'L'; +			InvDragging = ID_TLEFT; +		} +		break; + +	case I_TRIGHT: +		if (InvD[ino].resizable) { +			Ychange = 0; +			Ycompensate = 'T'; +			Xchange = 0; +			Xcompensate = 'R'; +			InvDragging = ID_TRIGHT; +		} +		break; + +	case I_BLEFT: +		if (InvD[ino].resizable) { +			Ychange = 0; +			Ycompensate = 'B'; +			Xchange = 0; +			Xcompensate = 'L'; +			InvDragging = ID_BLEFT; +		} +		break; + +	case I_BRIGHT: +		if (InvD[ino].resizable) { +			Ychange = 0; +			Ycompensate = 'B'; +			Xchange = 0; +			Xcompensate = 'R'; +			InvDragging = ID_BRIGHT; +		} +		break; +	} +} + +/** + * Called when a drag is over. + */ + +void InvDragEnd(void) { +	int curX, curY;		// cursor's animation position + +	GetCursorXY(&curX, &curY, false); + +	if (InvDragging != ID_NONE) { +		if (InvDragging == ID_SLIDE) { +			SlideSlider(0, S_END); +		} else if (InvDragging == ID_CSLIDE) { +			;	// No action +		} else if (InvDragging == ID_MDCONT) { +			SlideMSlider(0, S_END); +		} else if (InvDragging == ID_MOVE) { +			;	// No action +		} else { +			// Were re-sizing. Redraw the whole thing. +			DumpDobjArray(); +			DumpObjArray(); +			ConstructInventory(FULL); + +			// If this was the maximised, it no longer is! +			if (InventoryMaximised) { +				InventoryMaximised = false; +				InvD[ino].otherX = InvD[ino].inventoryX; +				InvD[ino].otherY = InvD[ino].inventoryY; +			} +		} +		InvDragging = ID_NONE; +	} + +	// Cursor could well now be inappropriate +	InvCursor(IC_AREA, curX, curY); + +	Xchange = Ychange = 0;		// Probably no need, but does no harm! +} + + +/**************************************************************************/ +/************** Incoming events - further processing **********************/ +/**************************************************************************/ + +/** + * ConfAction + */ +void ConfAction(int i, bool dbl) { + +	if (i >= 0) { +		switch (cd.Box[i].boxType) { +		case FLIP: +			if (dbl) { +				*(cd.Box[i].ival) ^= 1;	// XOR with true +				AddBoxes(false); +			} +			break; + +		case TOGGLE: +			if (!g_buttonEffect.bButAnim) { +				g_buttonEffect.bButAnim = true; +				g_buttonEffect.box = &cd.Box[i]; +				g_buttonEffect.press = false; +			} +			break; + +		case RGROUP: +			if (dbl) { +				// Already highlighted +				switch (cd.Box[i].boxFunc) { +				case SAVEGAME: +					KillInventory(); +					InvSaveGame(); +					break; +				case LOADGAME: +					KillInventory(); +					InvLoadGame(); +					break; +				default: +					break; +				} +			} else { +				Select(i, false); +			} +			break; + +#if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS) +		case FRGROUP: +			if (dbl) { +				Select(i, false); +				LanguageChange(); +			} else { +				Select(i, false); +			} +			break; +#endif + +		case AAGBUT: +		case ARSGBUT: +		case ARSBUT: +		case AABUT: +		case AATBUT: +			if (g_buttonEffect.bButAnim) +				break; + +			g_buttonEffect.bButAnim = true; +			g_buttonEffect.box = &cd.Box[i]; +			g_buttonEffect.press = true; +			break; +		default: +			break; +		} +	} else { +		ConfActionSpecial(i); +	} +} + +static void ConfActionSpecial(int i) { +	switch (i) { +	case IB_NONE: +		break; +	case IB_UP:	// Scroll up +		if (cd.fileBase > 0) { +			firstFile(cd.fileBase-1); +			AddBoxes(true); +			if (cd.selBox < NUM_SL_RGROUP-1) +				cd.selBox += 1; +			Select(cd.selBox, true); +		} +		break; +	case IB_DOWN:	// Scroll down +		if (cd.fileBase < MAX_SFILES-NUM_SL_RGROUP) { +			firstFile(cd.fileBase+1); +			AddBoxes(true); +			if (cd.selBox) +				cd.selBox -= 1; +			Select(cd.selBox, true); +		} +		break; +	case IB_SLIDE_UP: +		if (cd.fileBase > 0) { +			firstFile(cd.fileBase-(NUM_SL_RGROUP-1)); +			AddBoxes(true); +			cd.selBox = 0; +			Select(cd.selBox, true); +		} +		break; +	case IB_SLIDE_DOWN:	// Scroll down +		if (cd.fileBase < MAX_SFILES-NUM_SL_RGROUP) { +			firstFile(cd.fileBase+(NUM_SL_RGROUP-1)); +			AddBoxes(true); +			cd.selBox = NUM_SL_RGROUP-1; +			Select(cd.selBox, true); +		} +		break; +	} +} +// SLIDE_UP and SLIDE_DOWN on d click?????? + +void InvPutDown(int index) { +	int aniX, aniY; +			// index is the drop position +	int hiIndex;	// Current position of held item (if in) + +	// Find where the held item is positioned in this inventory (if it is) +	for (hiIndex = 0; hiIndex < InvD[ino].NoofItems; hiIndex++) +		if (InvD[ino].ItemOrder[hiIndex] == HeldItem) +			break; + +	// If drop position would leave a gap, move it up +	if (index >= InvD[ino].NoofItems) { +		if (hiIndex == InvD[ino].NoofItems)	// Not in, add it +			index = InvD[ino].NoofItems; +		else +			index = InvD[ino].NoofItems - 1; +	} + +	if (hiIndex == InvD[ino].NoofItems) {	// Not in, add it +		if (InvD[ino].NoofItems < InvD[ino].MaxInvObj) { +			InvD[ino].NoofItems++; + +			// Don't leave it in the other inventory! +			if (InventoryPos(HeldItem) != INV_HELDNOTIN) +				RemFromInventory(ino == INV_1 ? INV_2 : INV_1, HeldItem); +		} else { +			// No room at the inn! +			return; +		} +	} + +	// Position it in the inventory +	if (index < hiIndex) { +		memmove(&InvD[ino].ItemOrder[index + 1], &InvD[ino].ItemOrder[index], (hiIndex-index)*sizeof(int)); +		InvD[ino].ItemOrder[index] = HeldItem; +	} else if (index > hiIndex) { +		memmove(&InvD[ino].ItemOrder[hiIndex], &InvD[ino].ItemOrder[hiIndex+1], (index-hiIndex)*sizeof(int)); +		InvD[ino].ItemOrder[index] = HeldItem; +	} else { +		InvD[ino].ItemOrder[index] = HeldItem; +	} + +	HeldItem = INV_NOICON; +	ItemsChanged = true; +	DelAuxCursor(); +	RestoreMainCursor(); +	GetCursorXY(&aniX, &aniY, false); +	InvCursor(IC_DROP, aniX, aniY); +} + +void InvPdProcess(CORO_PARAM, const void *param) { +	// COROUTINE +	CORO_BEGIN_CONTEXT; +	CORO_END_CONTEXT(_ctx); + +	CORO_BEGIN_CODE(_ctx); + +	GetToken(TOKEN_LEFT_BUT); +	CORO_SLEEP(dclickSpeed+1); +	FreeToken(TOKEN_LEFT_BUT); + +	// get the stuff copied to process when it was created +	int	*pindex = (int *)param; + +	InvPutDown(*pindex); + +	CORO_END_CODE; +} + +void InvPickup(int index) { +	INV_OBJECT *invObj; + +	if (index != INV_NOICON) { +		if (HeldItem == INV_NOICON && InvD[ino].ItemOrder[index] && InvD[ino].ItemOrder[index] != HeldItem) { +			// Pick-up +			invObj = findInvObject(InvD[ino].ItemOrder[index]); +			if (invObj->hScript) +				RunInvTinselCode(invObj, WALKTO, INV_PICKUP, index); +		} else if (HeldItem != INV_NOICON) {			// Put icon down +			// Put-down +			invObj = findInvObject(HeldItem); + +			if (invObj->attribute & IO_DROPCODE && invObj->hScript) +				RunInvTinselCode(invObj, PUTDOWN, INV_PICKUP, index); + +			else if (!(invObj->attribute & IO_ONLYINV1 && ino !=INV_1) +			     && !(invObj->attribute & IO_ONLYINV2 && ino !=INV_2)) +				g_scheduler->createProcess(PID_TCODE, InvPdProcess, &index, sizeof(index)); +		} +	} +} + +/** + * Pick up/put down icon + */ +void InvSLClick(void) { +	int i; +	int aniX, aniY;		// Cursor's animation position + +	GetCursorXY(&aniX, &aniY, false); + +	switch (InvArea(aniX, aniY)) { +	case I_NOTIN: +		if (ino == INV_CONV) +			ConvAction(INV_CLOSEICON); +		KillInventory(); +		break; + +	case I_SLIDE_UP: +		if (InvD[ino].NoofVicons == 1) +			InvD[ino].FirstDisp -= InvD[ino].NoofHicons; +		for (i = 1; i < InvD[ino].NoofVicons; i++) +			InvD[ino].FirstDisp -= InvD[ino].NoofHicons; +		if (InvD[ino].FirstDisp < 0) +			InvD[ino].FirstDisp = 0; +		ItemsChanged = true; +		break; + +	case I_UP: +		InvD[ino].FirstDisp -= InvD[ino].NoofHicons; +		if (InvD[ino].FirstDisp < 0) +			InvD[ino].FirstDisp = 0; +		ItemsChanged = true; +		break; + +	case I_SLIDE_DOWN: +		if (InvD[ino].NoofVicons == 1) +			if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems) +				InvD[ino].FirstDisp += InvD[ino].NoofHicons; +		for (i = 1; i < InvD[ino].NoofVicons; i++) { +			if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems) +				InvD[ino].FirstDisp += InvD[ino].NoofHicons; +		} +		ItemsChanged = true; +		break; + +	case I_DOWN: +		if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems) { +			InvD[ino].FirstDisp += InvD[ino].NoofHicons; +			ItemsChanged = true; +		} +		break; + +	case I_BODY: +		if (ino == INV_CONF) { +			if (!InventoryHidden) +				ConfAction(WhichInvBox(aniX, aniY, false), false); +		} else { +			i = InvItem(&aniX, &aniY, false); + +			// Special bodge for David, to +			// cater for drop in dead space between icons +			if (i == INV_NOICON && HeldItem != INV_NOICON && (ino == INV_1 || ino == INV_2)) { +				aniX += 1;				// 1 to the right +				i = InvItem(&aniX, &aniY, false); +				if (i == INV_NOICON) { +					aniX -= 1;			// 1 down +					aniY += 1; +					i = InvItem(&aniX, &aniY, false); +					if (i == INV_NOICON) { +						aniX += 1;		// 1 down-right +						i = InvItem(&aniX, &aniY, false); +					} +				} +			} + +			if (ino == INV_CONV) { +				ConvAction(i); +			} else  +				InvPickup(i); +		} +		break; +	} +} + +void InvAction(void) { +	int index; +	INV_OBJECT *invObj; +	int aniX, aniY; +	int i; + +	GetCursorXY(&aniX, &aniY, false); + +	switch (InvArea(aniX, aniY)) { +	case I_BODY: +		if (ino == INV_CONF) { +			if (!InventoryHidden) +				ConfAction(WhichInvBox(aniX, aniY, false), true); +		} else if (ino == INV_CONV) { +			index = InvItem(&aniX, &aniY, false); +			ConvAction(index); +		} else { +			index = InvItem(&aniX, &aniY, false); +			if (index != INV_NOICON) { +				if (InvD[ino].ItemOrder[index] && InvD[ino].ItemOrder[index] != HeldItem) { +					invObj = findInvObject(InvD[ino].ItemOrder[index]); +					if (invObj->hScript) +						RunInvTinselCode(invObj, ACTION, INV_ACTION, index); +				} +			} +		} +		break; + +	case I_MOVE:	// Maximise/unmaximise inventory +		if (!InvD[ino].resizable) +			break; + +		if (!InventoryMaximised) { +			InvD[ino].sNoofHicons = InvD[ino].NoofHicons; +			InvD[ino].sNoofVicons = InvD[ino].NoofVicons; +			InvD[ino].NoofHicons = InvD[ino].MaxHicons; +			InvD[ino].NoofVicons = InvD[ino].MaxVicons; +			InventoryMaximised = true; + +			i = InvD[ino].inventoryX; +			InvD[ino].inventoryX = InvD[ino].otherX; +			InvD[ino].otherX = i; +			i = InvD[ino].inventoryY; +			InvD[ino].inventoryY = InvD[ino].otherY; +			InvD[ino].otherY = i; +		} else { +			InvD[ino].NoofHicons = InvD[ino].sNoofHicons; +			InvD[ino].NoofVicons = InvD[ino].sNoofVicons; +			InventoryMaximised = false; + +			i = InvD[ino].inventoryX; +			InvD[ino].inventoryX = InvD[ino].otherX; +			InvD[ino].otherX = i; +			i = InvD[ino].inventoryY; +			InvD[ino].inventoryY = InvD[ino].otherY; +			InvD[ino].otherY = i; +		} + +		// Delete current, and re-draw +		DumpDobjArray(); +		DumpObjArray(); +		ConstructInventory(FULL); +		break; + +	case I_UP: +		InvD[ino].FirstDisp -= InvD[ino].NoofHicons; +		if (InvD[ino].FirstDisp < 0) +			InvD[ino].FirstDisp = 0; +		ItemsChanged = true; +		break; +	case I_DOWN: +		if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems) { +			InvD[ino].FirstDisp += InvD[ino].NoofHicons; +			ItemsChanged = true; +		} +		break; +	} + +} + + +void InvLook(void) { +	int index; +	INV_OBJECT *invObj; +	int aniX, aniY; + +	GetCursorXY(&aniX, &aniY, false); + +	switch (InvArea(aniX, aniY)) { +	case I_BODY: +		index = InvItem(&aniX, &aniY, false); +		if (index != INV_NOICON) { +			if (InvD[ino].ItemOrder[index] && InvD[ino].ItemOrder[index] != HeldItem) { +				invObj = findInvObject(InvD[ino].ItemOrder[index]); +				if (invObj->hScript) +					RunInvTinselCode(invObj, LOOK, INV_LOOK, index); +			} +		} +		break; + +	case I_NOTIN: +		if (ino == INV_CONV) +			ConvAction(INV_CLOSEICON); +		KillInventory(); +		break; +	} +} + + +/**************************************************************************/ +/********************* Incoming events ************************************/ +/**************************************************************************/ + + +void ButtonToInventory(BUTEVENT be) { +	if (InventoryHidden) +		return; + +	switch (be) { +	case INV_PICKUP:		// BE_SLEFT +		InvSLClick(); +		break; + +	case INV_LOOK:			// BE_SRIGHT +		if (IsConfWindow()) +			InvSLClick(); +		else +			InvLook(); +		break; + +	case INV_ACTION:		// BE_DLEFT +		if (InvDragging != ID_MDCONT) +			InvDragEnd(); +		InvAction(); +		break; + +	case BE_LDSTART:		// Left drag start +		InvDragStart(); +		break; + +	case BE_LDEND:		// Left drag end +		InvDragEnd(); +		break; + +//	case BE_DLEFT:		// Double click left (also ends left drag) +//		ButtonToInventory(LDEND); +//		break; + +	case BE_RDSTART: +	case BE_RDEND: +	case BE_UNKNOWN: +		break; +	default: +		break; +	} +} + +void KeyToInventory(KEYEVENT ke) { +	int i; + +	switch (ke) { +	case ESC_KEY: +		if (InventoryState == ACTIVE_INV && ino == INV_CONF && cd.Box != optionBox) +			bOpenConf = true; +		CloseInventory(); +		break; + +	case PGDN_KEY: +		if (ino == INV_CONF) { +			// Only act if load or save screen +			if (cd.Box != loadBox && cd.Box != saveBox) +				break; + +			ConfActionSpecial(IB_SLIDE_DOWN); +		} else { +			// This code is a copy of SLClick on IB_SLIDE_DOWN +			// TODO: So share this duplicate code +			if (InvD[ino].NoofVicons == 1) +				if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems) +					InvD[ino].FirstDisp += InvD[ino].NoofHicons; +			for (i = 1; i < InvD[ino].NoofVicons; i++) { +				if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems) +					InvD[ino].FirstDisp += InvD[ino].NoofHicons; +			} +			ItemsChanged = true; +		} +		break; + +	case PGUP_KEY: +		if (ino == INV_CONF) { +			// Only act if load or save screen +			if (cd.Box != loadBox && cd.Box != saveBox) +				break; + +			ConfActionSpecial(IB_SLIDE_UP); +		} else { +			// This code is a copy of SLClick on I_SLIDE_UP +			// TODO: So share this duplicate code +			if (InvD[ino].NoofVicons == 1) +				InvD[ino].FirstDisp -= InvD[ino].NoofHicons; +			for (i = 1; i < InvD[ino].NoofVicons; i++) +				InvD[ino].FirstDisp -= InvD[ino].NoofHicons; +			if (InvD[ino].FirstDisp < 0) +				InvD[ino].FirstDisp = 0; +			ItemsChanged = true; +		} +		break; + +	case HOME_KEY: +		if (ino == INV_CONF) { +			// Only act if load or save screen +			if (cd.Box != loadBox && cd.Box != saveBox) +				break; + +			firstFile(0); +			AddBoxes(true); +			cd.selBox = 0; +			Select(cd.selBox, true); +		} else { +			InvD[ino].FirstDisp = 0; +			ItemsChanged = true; +		} +		break; + +	case END_KEY: +		if (ino == INV_CONF) { +			// Only act if load or save screen +			if (cd.Box != loadBox && cd.Box != saveBox) +				break; + +			firstFile(MAX_SFILES);	// Will get reduced to appropriate value +			AddBoxes(true); +			cd.selBox = 0; +			Select(cd.selBox, true); +		} else { +			InvD[ino].FirstDisp = InvD[ino].NoofItems - InvD[ino].NoofHicons*InvD[ino].NoofVicons; +			if (InvD[ino].FirstDisp < 0) +				InvD[ino].FirstDisp = 0; +			ItemsChanged = true; +		} +		break; + +	default: +		error("We're at KeyToInventory(), with default"); +	} +} + +/**************************************************************************/ +/************************* Odds and Ends **********************************/ +/**************************************************************************/ + +/** + * Called from Glitter function invdepict() + * Changes (permanently) the animation film for that object. + */ + +void invObjectFilm(int object, SCNHANDLE hFilm) { +	INV_OBJECT *invObj; + +	invObj = findInvObject(object); +	invObj->hFilm = hFilm; + +	if (HeldItem != object) +		ItemsChanged = true; +} + +/** + * (Un)serialize the inventory data for save/restore game. + */ + +void syncInvInfo(Serializer &s) { +	for (int i = 0; i < NUM_INV; i++) { +		s.syncAsSint32LE(InvD[i].MinHicons); +		s.syncAsSint32LE(InvD[i].MinVicons); +		s.syncAsSint32LE(InvD[i].MaxHicons); +		s.syncAsSint32LE(InvD[i].MaxVicons); +		s.syncAsSint32LE(InvD[i].NoofHicons); +		s.syncAsSint32LE(InvD[i].NoofVicons); +		for (int j = 0; j < MAX_ININV; j++) { +			s.syncAsSint32LE(InvD[i].ItemOrder[j]); +		} +		s.syncAsSint32LE(InvD[i].NoofItems); +		s.syncAsSint32LE(InvD[i].FirstDisp); +		s.syncAsSint32LE(InvD[i].inventoryX); +		s.syncAsSint32LE(InvD[i].inventoryY); +		s.syncAsSint32LE(InvD[i].otherX); +		s.syncAsSint32LE(InvD[i].otherY); +		s.syncAsSint32LE(InvD[i].MaxInvObj); +		s.syncAsSint32LE(InvD[i].hInvTitle); +		s.syncAsSint32LE(InvD[i].resizable); +		s.syncAsSint32LE(InvD[i].moveable); +		s.syncAsSint32LE(InvD[i].sNoofHicons); +		s.syncAsSint32LE(InvD[i].sNoofVicons); +		s.syncAsSint32LE(InvD[i].bMax); +	} +} + +/**************************************************************************/ +/************************ Initialisation stuff ****************************/ +/**************************************************************************/ + +/** + * Called from PlayGame(), stores handle to inventory objects' data - + * its id, animation film and Glitter script. + */ +// Note: the SCHANDLE type here has been changed to a void* +void RegisterIcons(void *cptr, int num) { +	numObjects = num; +	pio = (INV_OBJECT *) cptr; +} + +/** + * Called from Glitter function 'dec_invw()' - Declare the bits that the + * inventory windows are constructed from, and special cursors. + */ + +void setInvWinParts(SCNHANDLE hf) { +#ifdef DEBUG +	const FILM *pfilm; +#endif + +	winPartsf = hf; + +#ifdef DEBUG +	pfilm = (const FILM *)LockMem(hf); +	assert(FROM_LE_32(pfilm->numreels) >= HOPEDFORREELS); // not as many reels as expected +#endif +} + +/** + * Called from Glitter function 'dec_flags()' - Declare the language + * flag films + */ + +void setFlagFilms(SCNHANDLE hf) { +#ifdef DEBUG +	const FILM *pfilm; +#endif + +	flagFilm = hf; + +#ifdef DEBUG +	pfilm = (const FILM *)LockMem(hf); +	assert(FROM_LE_32(pfilm->numreels) >= HOPEDFORFREELS); // not as many reels as expected +#endif +} + +void setConfigStrings(SCNHANDLE *tp) { +	memcpy(configStrings, tp, sizeof(configStrings)); +} + +/** + * Called from Glitter functions: dec_convw()/dec_inv1()/dec_inv2() + * - Declare the heading text and dimensions etc. + */ + +void idec_inv(int num, SCNHANDLE text, int MaxContents, +		int MinWidth, int MinHeight, +		int StartWidth, int StartHeight, +		int MaxWidth, int MaxHeight, +		int startx, int starty, bool moveable) { +	if (MaxWidth > MAXHICONS) +		MaxWidth = MAXHICONS;		// Max window width +	if (MaxHeight > MAXVICONS) +		MaxHeight = MAXVICONS;		// Max window height +	if (MaxContents > MAX_ININV) +		MaxContents = MAX_ININV;	// Max contents + +	if (StartWidth > MaxWidth) +		StartWidth = MaxWidth; +	if (StartHeight > MaxHeight) +		StartHeight = MaxHeight; + +	InventoryState = IDLE_INV; + +	InvD[num].MaxHicons = MaxWidth; +	InvD[num].MinHicons = MinWidth; +	InvD[num].MaxVicons = MaxHeight; +	InvD[num].MinVicons = MinHeight; + +	InvD[num].NoofHicons = StartWidth; +	InvD[num].NoofVicons = StartHeight; + +	memset(InvD[num].ItemOrder, 0, sizeof(InvD[num].ItemOrder)); +	InvD[num].NoofItems = 0; + +	InvD[num].FirstDisp = 0; + +	InvD[num].inventoryX = startx; +	InvD[num].inventoryY = starty; +	InvD[num].otherX = 21; +	InvD[num].otherY = 15; + +	InvD[num].MaxInvObj = MaxContents; + +	InvD[num].hInvTitle = text; + +	if (MaxWidth != MinWidth && MaxHeight != MinHeight) +		InvD[num].resizable = true; + +	InvD[num].moveable = moveable; + +	InvD[num].bMax = false; +} + +/** + * Called from Glitter functions: dec_convw()/dec_inv1()/dec_inv2() + * - Declare the heading text and dimensions etc. + */ + +void idec_convw(SCNHANDLE text, int MaxContents, +		int MinWidth, int MinHeight, +		int StartWidth, int StartHeight, +		int MaxWidth, int MaxHeight) { +	idec_inv(INV_CONV, text, MaxContents, MinWidth, MinHeight, +			StartWidth, StartHeight, MaxWidth, MaxHeight, +			20, 8, true); +} + +/** + * Called from Glitter functions: dec_convw()/dec_inv1()/dec_inv2() + * - Declare the heading text and dimensions etc. + */ + +void idec_inv1(SCNHANDLE text, int MaxContents, +		int MinWidth, int MinHeight, +		int StartWidth, int StartHeight, +		int MaxWidth, int MaxHeight) { +	idec_inv(INV_1, text, MaxContents, MinWidth, MinHeight, +			StartWidth, StartHeight, MaxWidth, MaxHeight, +			100, 100, true); +} + +/** + * Called from Glitter functions: dec_convw()/dec_inv1()/dec_inv2() + * - Declare the heading text and dimensions etc. + */ + +void idec_inv2(SCNHANDLE text, int MaxContents, +		int MinWidth, int MinHeight, +		int StartWidth, int StartHeight, +		int MaxWidth, int MaxHeight) { +	idec_inv(INV_2, text, MaxContents, MinWidth, MinHeight, +			StartWidth, StartHeight, MaxWidth, MaxHeight, +			100, 100, true); +} + +int InvGetLimit(int invno) { +	assert(invno == INV_1 || invno == INV_2); // only INV_1 and INV_2 supported + +	return InvD[invno].MaxInvObj; +} + +void InvSetLimit(int invno, int MaxContents) { +	assert(invno == INV_1 || invno == INV_2); // only INV_1 and INV_2 supported +	assert(MaxContents >= InvD[invno].NoofItems); // can't reduce maximum contents below current contents + +	if (MaxContents > MAX_ININV) +		MaxContents = MAX_ININV;	// Max contents + +	InvD[invno].MaxInvObj = MaxContents; +} + +void InvSetSize(int invno, int MinWidth, int MinHeight, +		int StartWidth, int StartHeight, int MaxWidth, int MaxHeight) { +	assert(invno == INV_1 || invno == INV_2); // only INV_1 and INV_2 supported + +	if (StartWidth > MaxWidth) +		StartWidth = MaxWidth; +	if (StartHeight > MaxHeight) +		StartHeight = MaxHeight; + +	InvD[invno].MaxHicons = MaxWidth; +	InvD[invno].MinHicons = MinWidth; +	InvD[invno].MaxVicons = MaxHeight; +	InvD[invno].MinVicons = MinHeight; + +	InvD[invno].NoofHicons = StartWidth; +	InvD[invno].NoofVicons = StartHeight; + +	if (MaxWidth != MinWidth && MaxHeight != MinHeight) +		InvD[invno].resizable = true; +	else +		InvD[invno].resizable = false; + +	InvD[invno].bMax = false; +} + +/**************************************************************************/ + +bool IsTopWindow(void) { +	return (InventoryState == BOGUS_INV); +} + + +bool IsConfWindow(void) { +	return (InventoryState == ACTIVE_INV && ino == INV_CONF); +} + + +bool IsConvWindow(void) { +	return (InventoryState == ACTIVE_INV && ino == INV_CONV); +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/inventory.h b/engines/tinsel/inventory.h new file mode 100644 index 0000000000..d83439c68f --- /dev/null +++ b/engines/tinsel/inventory.h @@ -0,0 +1,142 @@ + +/* 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. + * + * $URL$ + * $Id$ + * + * Inventory related functions + */ + +#ifndef TINSEL_INVENTORY_H	// prevent multiple includes +#define TINSEL_INVENTORY_H + +#include "tinsel/dw.h" +#include "tinsel/events.h"	// for KEYEVENT, BUTEVENT + +namespace Tinsel { + +class Serializer; + +enum { +	INV_OPEN	= -1, +	INV_CONV	= 0, +	INV_1		= 1, +	INV_2		= 2, +	INV_CONF	= 3, + +	NUM_INV		= 4 +}; + +/** structure of each inventory object */ +struct INV_OBJECT { +	int32 id;		// inventory objects id +	SCNHANDLE hFilm;	// inventory objects animation film +	SCNHANDLE hScript;	// inventory objects event handling script +	int32 attribute;		// inventory object's attribute +}; + +void PopUpInventory(int invno); + +enum CONFTYPE { +	SAVE, LOAD, QUIT, OPTION, RESTART, SOUND, CONTROLS, SUBT, TOPWIN +}; + +void PopUpConf(CONFTYPE type); + + +void Xmovement(int x); +void Ymovement(int y); + +void ButtonToInventory(BUTEVENT be); + +void KeyToInventory(KEYEVENT ke); + + +int WhichItemHeld(void); + +void HoldItem(int item); +void DropItem(int item); +void AddToInventory(int invno, int icon, bool hold); +bool RemFromInventory(int invno, int icon); + + +void RegisterIcons(void *cptr, int num); + +void idec_convw(SCNHANDLE text, int MaxContents, int MinWidth, int MinHeight, +			int StartWidth, int StartHeight, int MaxWidth, int MaxHeight); +void idec_inv1(SCNHANDLE text, int MaxContents, int MinWidth, int MinHeight, +			int StartWidth, int StartHeight, int MaxWidth, int MaxHeight); +void idec_inv2(SCNHANDLE text, int MaxContents, int MinWidth, int MinHeight, +			int StartWidth, int StartHeight, int MaxWidth, int MaxHeight); + +bool InventoryActive(void); + +void AddIconToPermanentDefaultList(int icon); + +void convPos(int bpos); +void ConvPoly(HPOLYGON hp); +int convIcon(void); +void CloseDownConv(void); +void convHide(bool hide); +bool convHid(void); + +enum { +	INV_NOICON		= -1, +	INV_CLOSEICON	= -2, +	INV_OPENICON	= -3, +	INV_HELDNOTIN	= -4 +}; + +void ConvAction(int index); + +void InventoryIconCursor(void); + +void setInvWinParts(SCNHANDLE hf); +void setFlagFilms(SCNHANDLE hf); +void setConfigStrings(SCNHANDLE *tp); + +int InvItem(int *x, int *y, bool update); +int InvItemId(int x, int y); + +int InventoryPos(int num); + +bool IsInInventory(int object, int invnum); + +void KillInventory(void); + +void invObjectFilm(int object, SCNHANDLE hFilm); + +void syncInvInfo(Serializer &s); + +int InvGetLimit(int invno); +void InvSetLimit(int invno, int n); +void InvSetSize(int invno, int MinWidth, int MinHeight, +		int StartWidth, int StartHeight, int MaxWidth, int MaxHeight); + +int WhichInventoryOpen(void); + +bool IsTopWindow(void); +bool IsConfWindow(void); +bool IsConvWindow(void); + +} // end of namespace Tinsel + +#endif /* TINSEL_INVENTRY_H */ diff --git a/engines/tinsel/mareels.cpp b/engines/tinsel/mareels.cpp new file mode 100644 index 0000000000..4c64eaf091 --- /dev/null +++ b/engines/tinsel/mareels.cpp @@ -0,0 +1,132 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Functions to set up moving actors' reels. + */ + +#include "tinsel/pcode.h"	// For D_UP, D_DOWN +#include "tinsel/rince.h" + +#include "common/util.h" + +namespace Tinsel { + +//----------------- LOCAL GLOBAL DATA -------------------- + +enum { +	NUM_INTERVALS = NUM_MAINSCALES - 1, + +	// 2 for up and down, 3 allow enough entries for 3 fully subscribed moving actors' worth +	MAX_SCRENTRIES = NUM_INTERVALS*2*3 +}; + +struct SCIdataStruct { +	int	actor; +	int	scale; +	int	direction; +	SCNHANDLE reels[4]; +}; + +static SCIdataStruct SCIdata[MAX_SCRENTRIES]; + +static int scrEntries = 0; + +/** + * Return handle to actor's talk reel at present scale and direction. + */ +SCNHANDLE GetMactorTalkReel(PMACTOR pActor, TFTYPE dirn) { +	assert(1 <= pActor->scale && pActor->scale <= TOTAL_SCALES); +	switch (dirn) { +	case TF_NONE: +		return pActor->TalkReels[pActor->scale-1][pActor->dirn]; + +	case TF_UP: +		return pActor->TalkReels[pActor->scale-1][AWAY]; + +	case TF_DOWN: +		return pActor->TalkReels[pActor->scale-1][FORWARD]; + +	case TF_LEFT: +		return pActor->TalkReels[pActor->scale-1][LEFTREEL]; + +	case TF_RIGHT: +		return pActor->TalkReels[pActor->scale-1][RIGHTREEL]; + +	default: +		error("GetMactorTalkReel() - illegal direction!"); +	} +} + +/** + * scalingreels + */ +void setscalingreels(int actor, int scale, int direction, +		SCNHANDLE left, SCNHANDLE right, SCNHANDLE forward, SCNHANDLE away) { +	assert(scale >= 1 && scale <= NUM_MAINSCALES); // invalid scale +	assert(!(scale == 1 && direction == D_UP) && +		!(scale == NUM_MAINSCALES && direction == D_DOWN)); // illegal direction from scale + +	assert(scrEntries < MAX_SCRENTRIES); // Scaling reels limit reached! + +	SCIdata[scrEntries].actor = actor; +	SCIdata[scrEntries].scale = scale; +	SCIdata[scrEntries].direction = direction; +	SCIdata[scrEntries].reels[LEFTREEL]	= left; +	SCIdata[scrEntries].reels[RIGHTREEL]	= right; +	SCIdata[scrEntries].reels[FORWARD]	= forward; +	SCIdata[scrEntries].reels[AWAY]		= away; +	scrEntries++; +} + +/** + * ScalingReel + */ +SCNHANDLE ScalingReel(int ano, int scale1, int scale2, DIRREEL reel) { +	int d;	// Direction + +	// The smaller the number, the bigger the scale +	if (scale1 < scale2) +		d = D_DOWN; +	else +		d = D_UP; + +	for (int i = 0; i < scrEntries; i++) 	{ +		if (SCIdata[i].actor == ano && SCIdata[i].scale == scale1 && SCIdata[i].direction == d) { +			if (SCIdata[i].reels[reel] == TF_NONE) +				return 0; +			else +				return SCIdata[i].reels[reel]; +		} +	} +	return 0; +} + +/** + * RebootScalingReels + */ +void RebootScalingReels(void) { +	scrEntries = 0; +	memset(SCIdata, 0, sizeof(SCIdata)); +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/module.mk b/engines/tinsel/module.mk new file mode 100644 index 0000000000..b00afcddbc --- /dev/null +++ b/engines/tinsel/module.mk @@ -0,0 +1,52 @@ +MODULE := engines/tinsel + +MODULE_OBJS = \ +	actors.o \ +	anim.o \ +	background.o \ +	bg.o \ +	cliprect.o \ +	config.o \ +	cursor.o \ +	debugger.o \ +	detection.o \ +	effect.o \ +	events.o \ +	faders.o \ +	font.o \ +	graphics.o \ +	handle.o \ +	heapmem.o \ +	inventory.o \ +	mareels.o \ +	move.o \ +	multiobj.o \ +	music.o \ +	object.o \ +	palette.o \ +	pcode.o \ +	pdisplay.o \ +	play.o \ +	polygons.o \ +	rince.o \ +	saveload.o \ +	savescn.o \ +	scene.o \ +	sched.o \ +	scn.o \ +	scroll.o \ +	sound.o \ +	strres.o \ +	text.o \ +	timers.o \ +	tinlib.o \ +	tinsel.o \ +	token.o + +# This module can be built as a plugin +ifeq ($(ENABLE_TINSEL), DYNAMIC_PLUGIN) +PLUGIN := 1 +endif + +# Include common rules +include $(srcdir)/rules.mk diff --git a/engines/tinsel/move.cpp b/engines/tinsel/move.cpp new file mode 100644 index 0000000000..803bc5fd7b --- /dev/null +++ b/engines/tinsel/move.cpp @@ -0,0 +1,1618 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Handles walking and use of the path system. + * + * Contains the dodgiest code in the whole system. + */ + +#include "tinsel/actors.h" +#include "tinsel/anim.h" +#include "tinsel/background.h" +#include "tinsel/cursor.h" +#include "tinsel/dw.h" +#include "tinsel/graphics.h" +#include "tinsel/move.h" +#include "tinsel/multiobj.h"	// multi-part object defintions etc. +#include "tinsel/object.h" +#include "tinsel/polygons.h" +#include "tinsel/rince.h" +#include "tinsel/scroll.h" +#include "tinsel/tinlib.h"	// For stand() + +namespace Tinsel { + +//----------------- DEVELOPMENT OPTIONS -------------------- + +#define SLOW_RINCE_DOWN		0 + +//----------------- EXTERNAL FUNCTIONS --------------------- + +// in BG.C +extern int BackgroundWidth(void); +extern int BackgroundHeight(void); + + +// in POLYGONS.C +// Deliberatley defined here, and not in polygons.h +HPOLYGON InitExtraBlock(PMACTOR ca, PMACTOR ta); + +//----------------- LOCAL DEFINES -------------------- + +#define XMDIST	4 +#define XHMDIST	2 +#define YMDIST	2 +#define YHMDIST	2 + +#define XTHERE		1 +#define XRESTRICT	2 +#define YTHERE		4 +#define YRESTRICT	8 +#define STUCK		16 + +#define LEAVING_PATH	0x100 +#define ENTERING_BLOCK	0x200 +#define ENTERING_MBLOCK	0x400 + +#define ALL_SORTED	1 +#define NOT_SORTED	0 + + +//----------------- LOCAL GLOBAL DATA -------------------- + +#if SLOW_RINCE_DOWN +static int Interlude = 0;	// For slowing down walking, for testing +static int BogusVar = 0;	// For slowing down walking, for testing +#endif + +static int32 DefaultRefer = 0; +static int hSlowVar = 0;	// used by MoveActor() + + +//----------------- FORWARD REFERENCES -------------------- + +static void NewCoOrdinates(int fromx, int fromy, int *targetX, int *targetY, +			int *newx, int *newy, int *s1, int *s2, HPOLYGON *hS2p, +			bool bOver, bool bBodge, +			PMACTOR pActor, PMACTOR *collisionActor = 0); + + +#if SLOW_RINCE_DOWN +/** + * AddInterlude + */ + +void AddInterlude(int n) { +	Interlude += n; +	if (Interlude < 0) +		Interlude = 0; +} +#endif + +/** + * Given (x, y) of a click within a path polygon, checks that the + * co-ordinates are not within a blocking polygon. If it is not, the + * destination is the click point, otherwise tries to find a legal point + * below or above the click point. + * Returns: + *	NOT_SORTED - if a destination is worked out (movement required) + *	ALL_SORTED - no destination found (so no movement required) + */ +static int ClickedOnPath(int clickX, int clickY, int *ptgtX, int *ptgtY) { +	int Loffset, Toffset; +	int i; + +	/*-------------------------------------- +	 Clicked within a path, +	 go to where requested unless blocked. +	 --------------------------------------*/ +	if (InPolygon(clickX, clickY, BLOCKING) == NOPOLY) { +		// Not in a blocking polygon - go to where requested. +		*ptgtX = clickX; +		*ptgtY = clickY; +	} else { +		/*------------------------------------------------------ +		 In a Blocking polygon - try searching down and up. +		 If still nowhere (for now) give up! +		 ------------------------------------------------------*/ +		PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + +		for (i = clickY+1; i < SCREEN_HEIGHT + Toffset; i++) { +			// Don't leave the path system +			if (InPolygon(clickX, i, PATH) == NOPOLY) { +				i = SCREEN_HEIGHT; +				break; +			} +			if (InPolygon(clickX, i, BLOCKING) == NOPOLY) { +				*ptgtX = clickX; +				*ptgtY = i; +				break; +			} +		} +		if (i == SCREEN_HEIGHT) { +			for (i = clickY-1; i >= Toffset; i--) { +				// Don't leave the path system +				if (InPolygon(clickX, i, PATH) == NOPOLY) { +					i = -1; +					break; +				} +				if (InPolygon(clickX, i, BLOCKING) == NOPOLY) { +					*ptgtX = clickX; +					*ptgtY = i; +					break; +				} +			} +		} +		if (i < 0) { +			return ALL_SORTED; +		} +	} +	return NOT_SORTED; +} + +/** + * Given (x, y) of a click within a referral polygon, works out the + * destination according to the referral type. + * Returns: + *   NOT_SORTED - if a destination is worked out (movement required) + *   ALL_SORTED - no destination found (so no movement required) + */ +static int ClickedOnRefer(HPOLYGON hRefpoly, int clickX, int clickY, int *ptgtX, int *ptgtY) { +	int	i; +	int	end;		// Extreme of the scene +	int	Loffset, Toffset; + +	PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); +	*ptgtX = *ptgtY = -1; + +	switch (PolySubtype(hRefpoly)) { +	case REF_POINT:				// Go to specified node +		getPolyNode(hRefpoly, ptgtX, ptgtY); +		assert(InPolygon(*ptgtX, *ptgtY, PATH) != NOPOLY); // POINT Referral to illegal point +		break; + +	case REF_DOWN:				// Search downwards +		end = BackgroundHeight(); +		for (i = clickY+1; i < end; i++) +			if (InPolygon(clickX, i, PATH) != NOPOLY +					&& InPolygon(clickX, i, BLOCKING) == NOPOLY) { +				*ptgtX = clickX; +				*ptgtY = i; +				break; +			} +		break; + +	case REF_UP:				// Search upwards +		for (i = clickY-1; i >= 0; i--) +			if (InPolygon(clickX, i, PATH) != NOPOLY +					&& InPolygon(clickX, i, BLOCKING) == NOPOLY) { +				*ptgtX = clickX; +				*ptgtY = i; +				break; +			} +		break; + +	case REF_RIGHT:				// Search to the right +		end = BackgroundWidth(); +		for (i = clickX+1; i < end; i++) +			if (InPolygon(i, clickY, PATH) != NOPOLY +			&& InPolygon(i, clickY, BLOCKING) == NOPOLY) { +				*ptgtX = i; +				*ptgtY = clickY; +				break; +			} +		break; + +	case REF_LEFT:				// Search to the left +		for (i = clickX-1; i >= 0; i--) +			if (InPolygon(i, clickY, PATH) != NOPOLY +			&& InPolygon(i, clickY, BLOCKING) == NOPOLY) { +				*ptgtX = i; +				*ptgtY = clickY; +				break; +			} +		break; +	} +	if (*ptgtX != -1 && *ptgtY != -1) { +		return NOT_SORTED; +	} else +		return ALL_SORTED; +} + +/** + * Given (x, y) of a click, works out the destination according to the + * default referral type. + * Returns: + *   NOT_SORTED - if a destination is worked out (movement required) + *   ALL_SORTED - no destination found (so no movement required) + */ +static int ClickedOnNothing(int clickX, int clickY, int *ptgtX, int *ptgtY) { +	int	i; +	int	end;		// Extreme of the scene +	int	Loffset, Toffset; + +	PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + +	switch (DefaultRefer) { +	case REF_DEFAULT: +		// Try searching down and up (onscreen). +		for (i = clickY+1; i < SCREEN_HEIGHT+Toffset; i++) +			if (InPolygon(clickX, i, PATH) != NOPOLY) { +				return ClickedOnPath(clickX, i, ptgtX, ptgtY); +			} +		for (i = clickY-1; i >= Toffset; i--) +			if (InPolygon(clickX, i, PATH) != NOPOLY) { +				return ClickedOnPath(clickX, i, ptgtX, ptgtY); +			} +		// Try searching down and up (offscreen). +		end = BackgroundHeight(); +		for (i = clickY+1; i < end; i++) +			if (InPolygon(clickX, i, PATH) != NOPOLY) { +				return ClickedOnPath(clickX, i, ptgtX, ptgtY); +			} +		for (i = clickY-1; i >= 0; i--) +			if (InPolygon(clickX, i, PATH) != NOPOLY) { +				return ClickedOnPath(clickX, i, ptgtX, ptgtY); +			} +		break; + +	case REF_UP: +		for (i = clickY-1; i >= 0; i--) +			if (InPolygon(clickX, i, PATH) != NOPOLY) { +				return ClickedOnPath(clickX, i, ptgtX, ptgtY); +			} +		break; + +	case REF_DOWN: +		end = BackgroundHeight(); +		for (i = clickY+1; i < end; i++) +			if (InPolygon(clickX, i, PATH) != NOPOLY) { +				return ClickedOnPath(clickX, i, ptgtX, ptgtY); +			} +		break; + +	case REF_LEFT: +		for (i = clickX-1; i >= 0; i--) +			if (InPolygon(i, clickY, PATH) != NOPOLY) { +				return ClickedOnPath(i, clickY, ptgtX, ptgtY); +			} +		break; + +	case REF_RIGHT: +		end = BackgroundWidth(); +		for (i = clickX + 1; i < end; i++) +			if (InPolygon(i, clickY, PATH) != NOPOLY) { +				return ClickedOnPath(i, clickY, ptgtX, ptgtY); +			} +		break; +	} + +	// Going nowhere! +	return ALL_SORTED; +} + +/** + * Given (x, y) of the click, ascertains whether the click is within a + * path, within a referral poly, or niether. The appropriate function + * then gets called to give us a revised destination. + * Returns: + *   NOT_SORTED - if a destination is worked out (movement required) + *   ALL_SORTED - no destination found (so no movement required) + */ +static int WorkOutDestination(int clickX, int clickY, int *ptgtX, int *ptgtY) { +	HPOLYGON hPoly; + +	/*-------------------------------------- +	 Clicked within a path? +	 if not, within a referral poly? +	 if not, try and sort something out. +	---------------------------------------*/ +	if (InPolygon(clickX, clickY, PATH) != NOPOLY) { +		return ClickedOnPath(clickX, clickY, ptgtX, ptgtY); +	} else if ((hPoly = InPolygon(clickX, clickY, REFER)) != NOPOLY) { +		return ClickedOnRefer(hPoly, clickX, clickY, ptgtX, ptgtY); +	} else { +		return ClickedOnNothing(clickX, clickY, ptgtX, ptgtY); +	} +} + +/** + * Work out which reel to adopt for a section of movement. + */ +static DIRREEL GetDirectionReel(int fromx, int fromy, int tox, int toy, DIRREEL lastreel, HPOLYGON hPath) { +	int	xchange = 0, ychange = 0; +	enum {X_NONE, X_LEFT, X_RIGHT, X_NO} xdir; +	enum {Y_NONE, Y_UP, Y_DOWN, Y_NO} ydir; + +	DIRREEL	reel = lastreel;	// Leave alone if can't decide + +	/* +	 * Determine size and direction of X movement. +	 * i.e. left, right, none or not allowed. +	 */ +	if (getPolyReelType(hPath) == REEL_VERT) +		xdir = X_NO; +	else if (tox == -1) +		xdir = X_NONE; +	else { +		xchange = tox - fromx; +		if (xchange > 0) +			xdir = X_RIGHT; +		else if (xchange < 0) { +			xchange = -xchange; +			xdir = X_LEFT; +		} else +			xdir = X_NONE; +	} + +	/* +	 * Determine size and direction of Y movement. +	 * i.e. up, down, none or not allowed. +	 */ +	if (getPolyReelType(hPath) == REEL_HORIZ) +		ydir = Y_NO; +	else if (toy == -1) +		ydir = Y_NONE; +	else { +		ychange = toy - fromy; +		if (ychange > 0) +			ydir = Y_DOWN; +		else if (ychange < 0) { +			ychange = -ychange; +			ydir = Y_UP; +		} else +			ydir = Y_NONE; +	} + +	/* +	 * Some adjustment to allow for different x and y pixell sizes. +	 */ +	ychange += ychange;	// Double y distance to cover + +	/* +	 * Determine which reel to use. +	 */ +	if (xdir == X_NO) { +		// Forced to be FORWARD or AWAY +		switch (ydir) { +		case Y_DOWN: +			reel = FORWARD; +			break; +		case Y_UP: +			reel = AWAY; +			break; +		default: +			if (reel != AWAY)	// No gratuitous turn +				reel = FORWARD; +			break; +		} +	} else if (ydir == Y_NO) { +		// Forced to be LEFTREEL or RIGHTREEL +		switch (xdir) { +		case X_LEFT: +			reel = LEFTREEL; +			break; +		case X_RIGHT: +			reel = RIGHTREEL; +			break; +		default: +			if (reel != LEFTREEL)	// No gratuitous turn +				reel = RIGHTREEL; +			break; +		} +	} else if (xdir != X_NONE || ydir != Y_NONE) { +		if (xdir == X_NONE) +			reel = (ydir == Y_DOWN) ? FORWARD : AWAY; +		else if (ydir == Y_NONE) +			reel = (xdir == X_LEFT) ? LEFTREEL : RIGHTREEL; +		else { +			bool DontBother = false; + +			if (xchange <= 4 && ychange <= 4) { +				switch (reel) { +				case LEFTREEL: +					if (xdir == X_LEFT) +						DontBother = true; +					break; +				case RIGHTREEL: +					if (xdir == X_RIGHT) +						DontBother = true; +					break; +				case FORWARD: +					if (ydir == Y_DOWN) +						DontBother = true; +					break; +				case AWAY: +					if (ydir == Y_UP) +						DontBother = true; +					break; +				} +			} +			if (!DontBother) { +				if (xchange > ychange) +					reel = (xdir == X_LEFT) ? LEFTREEL : RIGHTREEL; +				else +					reel = (ydir == Y_DOWN) ? FORWARD : AWAY; +			} +		} +	} +	return reel; +} + +/** + * Haven't moved, look towards the cursor. + */ +static void GotThereWithoutMoving(PMACTOR pActor) { +	int	curX, curY; +	DIRREEL	reel; + +	if (!pActor->TagReelRunning) { +		GetCursorXYNoWait(&curX, &curY, true); + +		reel = GetDirectionReel(pActor->objx, pActor->objy, curX, curY, pActor->dirn, pActor->hCpath); + +		if (reel != pActor->dirn) +			SetMActorWalkReel(pActor, reel, pActor->scale, false); +	} +} + +/** + * Arrived at final destination. + */ +static void GotThere(PMACTOR pActor) { +	pActor->targetX = pActor->targetY = -1;		// 4/1/95 +	pActor->ItargetX = pActor->ItargetY = -1; +	pActor->UtargetX = pActor->UtargetY = -1; + +	// Perhaps we have'nt moved. +	if (pActor->objx == (int)pActor->fromx && pActor->objy == (int)pActor->fromy) +		GotThereWithoutMoving(pActor); + +	ReTagActor(pActor->actorID);	// Tag allowed while stationary + +	SetMActorStanding(pActor); + +	pActor->bMoving = false; +} + +enum cgt { GT_NOTL, GT_NOTB, GT_NOT2, GT_OK, GT_MAY }; + +/** + * Can we get straight there? + */ +static cgt CanGetThere(PMACTOR pActor, int tx, int ty) { +	int s1, s2;		// s2 not used here! +	HPOLYGON hS2p;		// nor is s2p! +	int nextx, nexty; + +	int targetX = tx; +	int targetY = ty;		// Ultimate destination +	int x = pActor->objx; +	int y = pActor->objy;		// Present position + +	while (targetX != -1 || targetY != -1) { +		NewCoOrdinates(x, y, &targetX, &targetY, &nextx, &nexty, +				&s1, &s2, &hS2p, pActor->over, false, pActor); + +		if (s1 == (XTHERE | YTHERE)) { +			return GT_OK;	// Can get there directly. +		} else if (s1 == (XTHERE | YRESTRICT) || s1 == (YTHERE | XRESTRICT)) { +			return GT_MAY;	// Can't get there directly. +		} else if (s1 & STUCK) { +			if (s2 == LEAVING_PATH) +				return GT_NOTL;	// Can't get there. +			else +				return GT_NOTB;	// Can't get there. +		} else if (x == nextx && y == nexty) { +			return GT_NOT2;	// Can't get there. +		} +		x = nextx; +		y = nexty; +	} +	return GT_MAY; +} + + +/** + * Set final destination. + */ +static void SetMoverUltDest(PMACTOR pActor, int x, int y) { +	pActor->UtargetX = x; +	pActor->UtargetY = y; +	pActor->hUpath = InPolygon(x, y, PATH); + +	assert(pActor->hUpath != NOPOLY || pActor->bIgPath); // Invalid ultimate destination +} + +/** + * Set intermediate destination. + * + * If in final destination path, go straight to target. + * If in a neighbouring path to the final destination, if the target path + * is a follow nodes path, head for the end node, otherwise head straight + * for the target. + * Otherwise, head towards the pseudo-centre or end node of the first + * en-route path. + */ +static void SetMoverIntDest(PMACTOR pActor, int x, int y) { +	HPOLYGON hIpath, hTpath; +	int	node; + +	hTpath = InPolygon(x, y, PATH);		// Target path +#ifdef DEBUG +	if (!pActor->bIgPath) +		assert(hTpath != NOPOLY); // SetMoverIntDest() - target not in path +#endif + +	if (pActor->hCpath == hTpath || pActor->bIgPath +		|| IsInPolygon(pActor->objx, pActor->objy, hTpath)) { +		// In destination path - head straight for the target. +		pActor->ItargetX = x; +		pActor->ItargetY = y; +		pActor->hIpath = hTpath; +	} else if (IsAdjacentPath(pActor->hCpath, hTpath)) { +		// In path adjacent to target +		if (PolySubtype(hTpath) != NODE) { +			// Target path is normal - head for target. +			// Added 26/01/95, innroom +			if (CanGetThere(pActor, x, y) == GT_NOTL) { +				NearestCorner(&x, &y, pActor->hCpath, hTpath); +			} +			pActor->ItargetX = x; +			pActor->ItargetY = y; +		} else { +			// Target path is node - head for end node. +			node = NearestEndNode(hTpath, pActor->objx, pActor->objy); +			getNpathNode(hTpath, node, &pActor->ItargetX, &pActor->ItargetY); + +		} +		pActor->hIpath = hTpath; +	} else { +		assert(hTpath != NOPOLY); // Error 701 +		hIpath = getPathOnTheWay(pActor->hCpath, hTpath); + +		if (hIpath != NOPOLY) { +			/* Head for an en-route path */ +			if (PolySubtype(hIpath) != NODE) { +				/* En-route path is normal - head for pseudo centre. */ +				if (CanGetThere(pActor, x, y) == GT_OK) { +					pActor->ItargetX = x; +					pActor->ItargetY = y; +				} else { +					pActor->ItargetX = PolyCentreX(hIpath); +					pActor->ItargetY = PolyCentreY(hIpath); +				} +			} else { +				/* En-route path is node - head for end node. */ +				node = NearestEndNode(hIpath, pActor->objx, pActor->objy); +				getNpathNode(hIpath, node, &pActor->ItargetX, &pActor->ItargetY); +			} +			pActor->hIpath = hIpath; +		} +	} + +	pActor->InDifficulty = NO_PROB; +} + +/** + * Set short-term destination and adopt the appropriate reel. + */ +static void SetMoverDest(PMACTOR pActor, int x, int y) { +	int	scale; +	DIRREEL	reel; + +	// Set the co-ordinates requested. +	pActor->targetX = x; +	pActor->targetY = y; +	pActor->InDifficulty = NO_PROB; + +	reel = GetDirectionReel(pActor->objx, pActor->objy, x, y, pActor->dirn, pActor->hCpath); +	scale = GetScale(pActor->hCpath, pActor->objy); +	if (scale != pActor->scale || reel != pActor->dirn) { +		SetMActorWalkReel(pActor, reel, scale, false); +	} +} + +/** + * SetNextDest + */ +static void SetNextDest(PMACTOR pActor) { +	int	targetX, targetY;		// Ultimate destination +	int	x, y;				// Present position +	int	nextx, nexty; +	int	s1, lstatus = 0; +	int	s2; +	HPOLYGON	hS2p; +	int	i; +	HPOLYGON	hNpoly; +	HPOLYGON	hPath; +	int	znode; +	int	nx, ny; +	int	sx, sy; +	HPOLYGON	hEb; + +	int	ss1, ss2; +	HPOLYGON shS2p; +	PMACTOR collisionActor; +#if 1 +	int	sTargetX, sTargetY; +#endif + +	/* +	 * Desired destination (Itarget) is already set +	 */ +	x = pActor->objx;		// Current position +	y = pActor->objy; +	targetX = pActor->ItargetX;	// Desired position +	targetY = pActor->ItargetY; + +	/* +	 * If we're where we're headed, end it all (the moving). +	 */ +//	if (x == targetX && y == targetY) +	if (ABS(x - targetX) < XMDIST && ABS(y - targetY) < YMDIST) { +		if (targetX == pActor->UtargetX && targetY == pActor->UtargetY) { +			// Desired position +			GotThere(pActor); +			return; +		} else { + 			assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5001 +			SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY); +		} +	} + +	if (pActor->bNoPath || pActor->bIgPath) { +		/* Can get there directly. */ +		SetMoverDest(pActor, targetX, targetY); +		pActor->over = false; +		return; +	} + +	/*---------------------------------------------------------------------- +	| Some work to do here if we're in a follow nodes polygon - basically +	| head for the next node. +	----------------------------------------------------------------------*/ +	hNpoly = pActor->hFnpath;		// The node path we're in (if any) +	switch (pActor->npstatus) { +	case NOT_IN: +		break; + +	case ENTERING: +		znode = NearestEndNode(hNpoly, x, y); +		/* Hang on, we're probably here already! */ +		if (znode) { +			pActor->npstatus = GOING_DOWN; +			pActor->line = znode-1; +			getNpathNode(hNpoly, znode - 1, &nx, &ny); +		} else { +			pActor->npstatus = GOING_UP; +			pActor->line = znode; +			getNpathNode(hNpoly, 1, &nx, &ny); +		} +		SetMoverDest(pActor, nx, ny); + +		// Test for pseudo-one-node npaths +		if (numNodes(hNpoly) == 2 && +				ABS(pActor->objx - pActor->targetX) < XMDIST && +				ABS(pActor->objy - pActor->targetY) < YMDIST) { +			// That's enough, we're leaving +			pActor->npstatus = LEAVING; +		} else { +			// Normal situation +			pActor->over = true; +			return; +		} +		// Fall through for LEAVING + +	case LEAVING: + 		assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5002 +		SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY); +		targetX = pActor->ItargetX;	// Desired position +		targetY = pActor->ItargetY; +		break; + +	case GOING_UP: +		i = pActor->line;		// The line we're on + +		// Is this the final target line? +		if (i+1 == pActor->Tline && hNpoly == pActor->hUpath) { +			// The final leg of the journey +			pActor->line = i+1; +			SetMoverDest(pActor, pActor->UtargetX, pActor->UtargetY); +			pActor->over = false; +			return; +		} else { +			// Go to the next node unless we're at the last one +			i++;				// The node we're at +			if (++i < numNodes(hNpoly)) { +				getNpathNode(hNpoly, i, &nx, &ny); +				SetMoverDest(pActor, nx, ny); +				pActor->line = i-1; +				if (ABS(pActor->UtargetX - pActor->targetX) < XMDIST && +				   ABS(pActor->UtargetY - pActor->targetY) < YMDIST) +					pActor->over = false; +				else +					pActor->over = true; +				return; +			} else { +				// Last node - we're off +				pActor->npstatus = LEAVING; +		 		assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5003 +				SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY); +				targetX = pActor->ItargetX;	// Desired position +				targetY = pActor->ItargetY; +				break; +			} +		} + +	case GOING_DOWN: +		i = pActor->line;		// The line we're on and the node we're at + +		// Is this the final target line? +		if (i - 1 == pActor->Tline && hNpoly == pActor->hUpath) { +			// The final leg of the journey +			SetMoverDest(pActor, pActor->UtargetX, pActor->UtargetY); +			pActor->line = i-1; +			pActor->over = false; +			return; +		} else { +			// Go to the next node unless we're at the last one +			if (--i >= 0) { +				getNpathNode(hNpoly, i, &nx, &ny); +				SetMoverDest(pActor, nx, ny); +				pActor->line--;		/* The next node to head for */ +				if (ABS(pActor->UtargetX - pActor->targetX) < XMDIST && +				   ABS(pActor->UtargetY - pActor->targetY) < YMDIST) +					pActor->over = false; +				else +					pActor->over = true; +				return; +			} else { +				// Last node - we're off +				pActor->npstatus = LEAVING; +		 		assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5004 +				SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY); +				targetX = pActor->ItargetX;	// Desired position +				targetY = pActor->ItargetY; +				break; +			} +		} +	} + + + + +	/*------------------------------------------------------ +	| See if it can get there directly. There may be an +	| intermediate destination to head for. +	------------------------------------------------------*/ + +	while (targetX != -1 || targetY != -1) { +#if 1 +		// 'push' the target +		sTargetX = targetX; +		sTargetY = targetY; +#endif +		NewCoOrdinates(x, y, &targetX, &targetY, &nextx, &nexty, +					&s1, &s2, &hS2p, pActor->over, false, pActor, &collisionActor); + +		if (s1 != (XTHERE | YTHERE) && x == nextx && y == nexty) { +			ss1 = s1; +			ss2 = s2; +			shS2p = hS2p; +#if 1 +			// 'pop' the target +			targetX = sTargetX; +			targetY = sTargetY; +#endif +			// Note: this aint right - targetX/Y (may) have been +			// nobbled by that last call to NewCoOrdinates() +			// Re-instating them (can) leads to oscillation +			NewCoOrdinates(x, y, &targetX, &targetY, &nextx, &nexty, +						&s1, &s2, &hS2p, pActor->over, true, pActor, &collisionActor); + +			if (x == nextx && y == nexty) { +				s1 = ss1; +				s2 = ss2; +				hS2p = shS2p; +			} +		} + +		if (s1 == (XTHERE | YTHERE)) { +			/* Can get there directly. */ +			SetMoverDest(pActor, nextx, nexty); +			pActor->over = false; +			break; +		} else if ((s1 & STUCK) || s1 == (XRESTRICT + YRESTRICT) +		     || s1 == (XTHERE | YRESTRICT) || s1 == (YTHERE | XRESTRICT)) { +			/*------------------------------------------------- +			 Can't go any further in this direction.	   | +			 If it's because of a blocking polygon, try to do | +			 something about it.				   | +			 -------------------------------------------------*/ +			if (s2 & ENTERING_BLOCK) { +				x = pActor->objx;	// Current position +				y = pActor->objy; +				// Go to the nearest corner of the blocking polygon concerned +				BlockingCorner(hS2p, &x, &y, pActor->ItargetX, pActor->ItargetY); +				SetMoverDest(pActor, x, y); +				pActor->over = false; +			} else if (s2 & ENTERING_MBLOCK) { +				if (InMActorBlock(pActor, pActor->UtargetX, pActor->UtargetY)) { +					// The best we're going to achieve +					SetMoverUltDest(pActor, x, y); +					SetMoverDest(pActor, x, y); +				} else { +					sx = pActor->objx; +					sy = pActor->objy; +//					pActor->objx = x; +//					pActor->objy = y; + +					hEb = InitExtraBlock(pActor, collisionActor); +					x = pActor->objx; +					y = pActor->objy; +					BlockingCorner(hEb, &x, &y, pActor->ItargetX, pActor->ItargetY); + +					pActor->objx = sx; +					pActor->objy = sy; +					SetMoverDest(pActor, x, y); +					pActor->over = false; +				} +			} else { +				/*---------------------------------------- +				 Currently, this is as far as we can go. | +				 Definitely room for improvement here!   | +				 ----------------------------------------*/ +				hPath = InPolygon(pActor->ItargetX, pActor->ItargetY, PATH); +				if (hPath != pActor->hIpath) { +					if (IsInPolygon(pActor->ItargetX, pActor->ItargetY, pActor->hIpath)) +						hPath = pActor->hIpath; +				} +				assert(hPath == pActor->hIpath); + +				if (pActor->InDifficulty == NO_PROB) { +					x = PolyCentreX(hPath); +					y = PolyCentreY(hPath); +					SetMoverDest(pActor, x, y); +					pActor->InDifficulty = TRY_CENTRE; +					pActor->over = false; +				} else if (pActor->InDifficulty == TRY_CENTRE) { +					NearestCorner(&x, &y, pActor->hCpath, pActor->hIpath); +					SetMoverDest(pActor, x, y); +					pActor->InDifficulty = TRY_CORNER; +					pActor->over = false; +				} else if (pActor->InDifficulty == TRY_CORNER) { +					NearestCorner(&x, &y, pActor->hCpath, pActor->hIpath); +					SetMoverDest(pActor, x, y); +					pActor->InDifficulty = TRY_NEXTCORNER; +					pActor->over = false; +				} +			} +			break; +		} +		else if (((lstatus & YRESTRICT) && !(s1 & YRESTRICT)) +		     ||  ((lstatus & XRESTRICT) && !(s1 & XRESTRICT))) { +			/*----------------------------------------------- +			 A restriction in a direction has been removed. | +			 Use this as an intermediate destination.	 | +			 -----------------------------------------------*/ +			SetMoverDest(pActor, nextx, nexty); +			pActor->over = false; +			break; +		} + +		x = nextx; +		y = nexty; + +		/*------------------------- +		 Change of path polygon?  | +		 -------------------------*/ +		hPath = InPolygon(x, y, PATH); +		if (pActor->hCpath != hPath && +		   !IsInPolygon(x, y, pActor->hCpath) && +		   !IsAdjacentPath(pActor->hCpath, pActor->hIpath)) { +			/*---------------------------------------------------------- +			 If just entering a follow nodes polygon, go to first node.| +			 Else if just going to pass through, go to pseudo-centre.  | +			 ----------------------------------------------------------*/ +			if (PolySubtype(hPath) == NODE && pActor->hFnpath != hPath && pActor->npstatus != LEAVING) { +				int node = NearestEndNode(hPath, x, y); +				getNpathNode(hPath, node, &nx, &ny); +				SetMoverDest(pActor, nx, ny); +				pActor->over = true; +			} else if (!IsInPolygon(pActor->ItargetX, pActor->ItargetY, hPath) && +				!IsInPolygon(pActor->ItargetX, pActor->ItargetY, pActor->hCpath)) { +				SetMoverDest(pActor, PolyCentreX(hPath), PolyCentreY(hPath)); +				pActor->over = true; +			} else { +				SetMoverDest(pActor, pActor->ItargetX, pActor->ItargetY); +			} +			break; +		} + +		lstatus = s1; +	} +} + +/** + * Work out where the next position should be. + * Check that it's in a path and not in a blocking polygon. + */ +static void NewCoOrdinates(int fromx, int fromy, int *targetX, int *targetY, +				int *newx, int *newy, int *s1, int *s2, +				HPOLYGON *hS2p, bool bOver, bool bBodge, +				PMACTOR pActor, PMACTOR *collisionActor) { +	HPOLYGON hPoly; +	int sidem, depthm; +	int sidesteps, depthsteps; +	PMACTOR	ma; + +	*s1 = *s2 = 0; + +	/*------------------------------------------------ +	 Don't overrun if this is the final destination. | +	 ------------------------------------------------*/ +	if (*targetX == pActor->UtargetX && (*targetY == -1 || *targetY == pActor->UtargetY) || +			*targetY == pActor->UtargetY && (*targetX == -1 || *targetX == pActor->UtargetX)) +		bOver = false; + +	/*---------------------------------------------------- +	 Decide how big a step to attempt in each direction. | +	 ----------------------------------------------------*/ +	sidesteps = *targetX == -1 ? 0 : *targetX - fromx; +	sidesteps = ABS(sidesteps); + +	depthsteps = *targetY == -1 ? 0 : *targetY - fromy; +	depthsteps = ABS(depthsteps); + +	if (sidesteps && depthsteps > sidesteps) { +		depthm = YMDIST; +		sidem = depthm * sidesteps/depthsteps; + +		if (!sidem) +			sidem = 1; +	} else if (depthsteps && sidesteps > depthsteps) { +		sidem = XMDIST; +		depthm = sidem * depthsteps/sidesteps; + +		if (!depthm) { +			if (bBodge) +				depthm = 1; +		} else if (depthm > YMDIST) +			depthm = YMDIST; +	} else { +		sidem = sidesteps ? XMDIST : 0; +		depthm = depthsteps ? YMDIST : 0; +	} + +	*newx = fromx; +	*newy = fromy; + +	/*------------------------------------------------------------ +	 If Left-Right movement is required - then make the move,    | +	 but don't overshoot, and do notice when we're already there | +	 ------------------------------------------------------------*/ +	if (*targetX == -1) +		*s1 |= XTHERE; +	else { +		if (*targetX > fromx) {		/* To the right?	*/ +			*newx += sidem;		// Move to the right... +			if (*newx == *targetX) +				*s1 |= XTHERE; +			else if (*newx > *targetX) {	// ...but don't overshoot +				if (!bOver) +					*newx = *targetX; +				else +					*targetX = *newx; +				*s1 |= XTHERE; +			} +		} else if (*targetX < fromx) {	/* To the left?		*/ +			*newx -= sidem;		// Move to the left... +			if (*newx == *targetX) +				*s1 |= XTHERE; +			else if (*newx < *targetX) {	// ...but don't overshoot +				if (!bOver) +					*newx = *targetX; +				else +					*targetX = *newx; +				*s1 |= XTHERE; +			} +		} else { +			*targetX = -1;		// We're already there! +			*s1 |= XTHERE; +		} +	} + +	/*-------------------------------------------------------------- +	 If Up-Down movement is required - then make the move, +	 but don't overshoot, and do notice when we're already there +	 --------------------------------------------------------------*/ +	if (*targetY == -1) +		*s1 |= YTHERE; +	else { +		if (*targetY > fromy) {		/* Downwards?		*/ +			*newy += depthm;	// Move down... +			if (*newy == *targetY)	// ...but don't overshoot +				*s1 |= YTHERE; +			else if (*newy > *targetY) {	// ...but don't overshoot +				if (!bOver) +					*newy = *targetY; +				else +					*targetY = *newy; +				*s1 |= YTHERE; +			} +		} else if (*targetY < fromy) {	/* Upwards?		*/ +			*newy -= depthm;	// Move up... +			if (*newy == *targetY)	// ...but don't overshoot +				*s1 |= YTHERE; +			else if (*newy < *targetY) {	// ...but don't overshoot +				if (!bOver) +					*newy = *targetY; +				else +					*targetY = *newy; +				*s1 |= YTHERE; +			} +		} else { +			*targetY = -1;		// We're already there! +			*s1 |= YTHERE; +		} +	} + +	/* Give over if this is it */ +	if (*s1 == (XTHERE | YTHERE)) +		return; + +	/*------------------------------------------------------ +	 Have worked out where an optimum step would take us. +	 Must now check if it's in a legal spot. +	 ------------------------------------------------------*/ + +	if (!pActor->bNoPath && !pActor->bIgPath) { +		/*------------------------------ +		 Must stay in a path polygon. +		-------------------------------*/ +		hPoly = InPolygon(*newx, *newy, PATH); +		if (hPoly == NOPOLY) { +			*s2 = LEAVING_PATH;	// Trying to leave the path polygons + +			if (*newx != fromx && InPolygon(*newx, fromy, PATH) != NOPOLY && InPolygon(*newx, fromy, BLOCKING) == NOPOLY) { +				*newy = fromy; +				*s1 |= YRESTRICT; +			} else if (*newy != fromy && InPolygon(fromx, *newy, PATH) != NOPOLY && InPolygon(fromx, *newy, BLOCKING) == NOPOLY) { +				*newx = fromx; +				*s1 |= XRESTRICT; +			} else { +				*newx = fromx; +				*newy = fromy; +#if 1 +				*targetX = *targetY = -1; +#endif +				*s1 |= STUCK; +				return; +			} +		} + +		/*-------------------------------------- +		 Must stay out of blocking polygons. +		 --------------------------------------*/ +		hPoly = InPolygon(*newx, *newy, BLOCKING); +		if (hPoly != NOPOLY) { +			*s2 = ENTERING_BLOCK;	// Trying to enter a blocking poly +			*hS2p = hPoly; + +			if (*newx != fromx && InPolygon(*newx, fromy, BLOCKING) == NOPOLY && InPolygon(*newx, fromy, PATH) != NOPOLY) { +				*newy = fromy; +				*s1 |= YRESTRICT; +			} else if (*newy != fromy && InPolygon(fromx, *newy, BLOCKING) == NOPOLY && InPolygon(fromx, *newy, PATH) != NOPOLY) { +				*newx = fromx; +				*s1 |= XRESTRICT; +			} else { +				*newx = fromx; +				*newy = fromy; +#if 1 +				*targetX = *targetY = -1; +#endif +				*s1 |= STUCK; +			} +		} +		/*------------------------------------------------------ +		 Must stay out of moving actors' blocking polygons. +		 ------------------------------------------------------*/ +		ma = InMActorBlock(pActor, *newx, *newy); +		if (ma != NULL) { +			// Ignore if already in it (it may have just appeared) +			if (!InMActorBlock(pActor, pActor->objx, pActor->objy)) { +				*s2 = ENTERING_MBLOCK;	// Trying to walk through an actor +				 +				*hS2p = -1; +				if (collisionActor) +					*collisionActor = ma; + +				if (*newx != fromx && InMActorBlock(pActor, *newx, fromy) == NULL +				    && InPolygon(*newx, fromy, BLOCKING) == NOPOLY && InPolygon(*newx, fromy, PATH) != NOPOLY) { +					*newy = fromy; +					*s1 |= YRESTRICT; +				} else if (*newy != fromy && InMActorBlock(pActor, fromx, *newy) == NULL +				           && InPolygon(fromx, *newy, BLOCKING) == NOPOLY && InPolygon(fromx, *newy, PATH) != NOPOLY) { +					*newx = fromx; +					*s1 |= XRESTRICT; +				} else { +					*newx = fromx; +					*newy = fromy; +#if 1 +					*targetX = *targetY = -1; +#endif +					*s1 |= STUCK; +				} +			} +		} +	} +} + +/** + * SetOffWithinNodePath + */ +static void SetOffWithinNodePath(PMACTOR pActor, HPOLYGON StartPath, HPOLYGON DestPath, +								 int targetX, int targetY) { +	int endnode; +	HPOLYGON hIpath; +	int	nx, ny; +	int	x, y; + +	if (StartPath == DestPath) { +		if (pActor->line == pActor->Tline) { +			SetMoverDest(pActor, pActor->UtargetX, pActor->UtargetY); +			pActor->over = false; +		} else if (pActor->line < pActor->Tline) { +			getNpathNode(StartPath, pActor->line+1, &nx, &ny); +			SetMoverDest(pActor, nx, ny); +			pActor->npstatus = GOING_UP; +		} else if (pActor->line > pActor->Tline) { +			getNpathNode(StartPath, pActor->line, &nx, &ny); +			SetMoverDest(pActor, nx, ny); +			pActor->npstatus = GOING_DOWN; +		} +	} else { +		/* +		 * Leaving this path - work out +		 * which end of this path to head for. +		 */ +		assert(DestPath != NOPOLY); // Error 702 +		if ((hIpath = getPathOnTheWay(StartPath, DestPath)) == NOPOLY) { +			// This should never happen! +			// It's the old code that didn't always work. +			endnode = NearestEndNode(StartPath, targetX, targetY); +		} else { +			if (PolySubtype(hIpath) != NODE) { +				x = PolyCentreX(hIpath); +				y = PolyCentreY(hIpath); +				endnode = NearestEndNode(StartPath, x, y); +			} else { +				endnode = NearEndNode(StartPath, hIpath); +			} +		} + +#if 1 +		if ((pActor->npstatus == LEAVING) && +			endnode == NearestEndNode(StartPath, pActor->objx, pActor->objy)) { +			// Leave it be +		} else +#endif +		{ +			if (endnode) { +				getNpathNode(StartPath, pActor->line+1, &nx, &ny); +				SetMoverDest(pActor, nx, ny); +				pActor->npstatus = GOING_UP; +			} else { +				getNpathNode(StartPath, pActor->line, &nx, &ny); +				SetMoverDest(pActor, nx, ny); +				pActor->npstatus = GOING_DOWN; +			} +		} +	} +} + +/** + * Restore a movement, called from restoreMovement() in ACTORS.CPP + */ +void SSetActorDest(PMACTOR pActor) { +	if (pActor->UtargetX != -1 && pActor->UtargetY != -1) { +		stand(pActor->actorID, pActor->objx, pActor->objy, 0); + +		if (pActor->UtargetX != -1 && pActor->UtargetY != -1) { +			SetActorDest(pActor, pActor->UtargetX, pActor->UtargetY, +					pActor->bIgPath, 0); +		} +	} else { +		stand(pActor->actorID, pActor->objx, pActor->objy, 0); +	} +} + +/** + * Initiate a movement, called from WalkTo_Event() + */ +void SetActorDest(PMACTOR pActor, int clickX, int clickY, bool igPath, SCNHANDLE film) { +	HPOLYGON StartPath, DestPath = 0; +	int targetX, targetY; + +	if (pActor->actorID == LeadId())		// Now only for lead actor +		UnTagActor(pActor->actorID);	// Tag not allowed while moving +	pActor->ticket++; +	pActor->stop = false; +	pActor->over = false; +	pActor->fromx = pActor->objx; +	pActor->fromy = pActor->objy; +	pActor->bMoving = true; +	pActor->bIgPath = igPath; + +	// Use the supplied reel or restore the normal actor. +	if (film != 0) +		AlterMActor(pActor, film, AR_WALKREEL); +	else +		AlterMActor(pActor, 0, AR_NORMAL); + +	if (igPath) { +		targetX = clickX; +		targetY = clickY; +	} else { +		if (WorkOutDestination(clickX, clickY, &targetX, &targetY) == ALL_SORTED) { +			GotThere(pActor); +			return; +		} +		assert(InPolygon(targetX, targetY, PATH) != NOPOLY); // illegal destination! +		assert(InPolygon(targetX, targetY, BLOCKING) == NOPOLY); // illegal destination! +	} + + +	/***** Now have a destination to aim for. *****/ + +	/*---------------------------------- +	| Don't move if it's not worth it. +	----------------------------------*/ +	if (ABS(targetX - pActor->objx) < XMDIST && ABS(targetY - pActor->objy) < YMDIST) { +		GotThere(pActor); +		return; +	} + +	/*------------------------------------------------------ +	| If the destiation is within a follow nodes polygon, +	| set destination as the nearest node. +	------------------------------------------------------*/ +	if (!igPath) { +		DestPath = InPolygon(targetX, targetY, PATH); +		if (PolySubtype(DestPath) == NODE) { +			// Find the nearest point on a line, or nearest node +			FindBestPoint(DestPath, &targetX, &targetY, &pActor->Tline); +		} +	} + + 	assert(pActor->bIgPath || InPolygon(targetX, targetY, PATH) != NOPOLY); // Error 5005 +	SetMoverUltDest(pActor, targetX, targetY); +	SetMoverIntDest(pActor, targetX, targetY); + +	/*------------------------------------------------------------------- +	| If in a follow nodes path, need to set off in the right direction! | +	-------------------------------------------------------------------*/ +	if ((StartPath = pActor->hFnpath) != NOPOLY && !igPath) { +		SetOffWithinNodePath(pActor, StartPath, DestPath, targetX, targetY); +	} else { +		// Set off! +		SetNextDest(pActor); +	} +} + +/** + * Change scale if appropriate. + */ +static void CheckScale(PMACTOR pActor, HPOLYGON hPath, int ypos) { +	int scale; + +	scale = GetScale(hPath, ypos); +	if (scale != pActor->scale) { +		SetMActorWalkReel(pActor, pActor->dirn, scale, false); +	} +} + +/** + * Not going anywhere - Kick off again if not at final destination. + */ +static void NotMoving(PMACTOR pActor, int x, int y) { +	pActor->targetX = pActor->targetY = -1; + +//	if (x == pActor->UtargetX && y == pActor->UtargetY) +	if (ABS(x - pActor->UtargetX) < XMDIST && ABS(y - pActor->UtargetY) < YMDIST) { +		GotThere(pActor); +		return; +	} + +	if (pActor->ItargetX != -1 || pActor->ItargetY != -1) { +		SetNextDest(pActor); +	} else if (pActor->UtargetX != -1 || pActor->UtargetY != -1) { + 		assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5006 +		SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY); +		SetNextDest(pActor); +	} +} + +/** + * Does the necessary business when entering a different path polygon. + */ +static void EnteringNewPath(PMACTOR pActor, HPOLYGON hPath, int x, int y) { +	int	firstnode;	// First node to go to +	int	lastnode;	// Last node to go to +	HPOLYGON hIpath; +	int	nx, ny; +	int	nxl, nyl; + +	pActor->hCpath = hPath;		// current path + +	if (hPath == NOPOLY) { +		// Not proved this ever happens, but just in case +		pActor->hFnpath = NOPOLY; +		pActor->npstatus = NOT_IN; +		return; +	} + +	// Is new path a node path? +	if (PolySubtype(hPath) == NODE) { +		// Node path - usually go to nearest end node +		firstnode = NearestEndNode(hPath, x, y); +		lastnode = -1; + +		// If this is not the destination path, +		// find which end nodfe we wish to leave via +		if (hPath != pActor->hUpath) { +			if (pActor->bIgPath) { +				lastnode = NearestEndNode(hPath, pActor->UtargetX, pActor->UtargetY); +			} else { +				assert(pActor->hUpath != NOPOLY); // Error 703 +				hIpath = getPathOnTheWay(hPath, pActor->hUpath); +				assert(hIpath != NOPOLY); // No path on the way + +				if (PolySubtype(hIpath) != NODE) { +					lastnode = NearestEndNode(hPath, PolyCentreX(hIpath), PolyCentreY(hIpath)); +				} else { +					lastnode = NearEndNode(hPath, hIpath); +				} +			} +		} +		// Test for pseudo-one-node npaths +		if (lastnode != -1 && numNodes(hPath) == 2) { +			getNpathNode(hPath, firstnode, &nx, &ny); +			getNpathNode(hPath, lastnode, &nxl, &nyl); +			if (nxl == nx && nyl == ny) +				firstnode = lastnode; +		} + +		// If leaving by same node as entering, don't bother. +		if (lastnode == firstnode) { +			pActor->hFnpath = NOPOLY; +			pActor->npstatus = NOT_IN; +	 		assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5007 +			SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY); +			SetNextDest(pActor); +		} else { +			// Head for first node +			pActor->over = true; +			pActor->npstatus = ENTERING; +			pActor->hFnpath = hPath; +			pActor->line = firstnode ? firstnode - 1 : firstnode; +			if (pActor->line == pActor->Tline && hPath == pActor->hUpath) { +		 		assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5008 +				SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY); +				SetMoverDest(pActor, pActor->UtargetX, pActor->UtargetY); +			} else { +				// This doesn't seem right +				getNpathNode(hPath, firstnode, &nx, &ny); +				if (ABS(pActor->objx - nx) < XMDIST +						&& ABS(pActor->objy - ny) < YMDIST) { +					pActor->npstatus = ENTERING; +					pActor->hFnpath = hPath; +					SetNextDest(pActor); +				} else { +					getNpathNode(hPath, firstnode, &nx, &ny); +					SetMoverDest(pActor, nx, ny); +				} +			} +		} +		return; +	} else { +		pActor->hFnpath = NOPOLY; +		pActor->npstatus = NOT_IN; + 		assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5009 +// Added 26/01/95 +		if (IsPolyCorner(hPath, pActor->ItargetX, pActor->ItargetY)) +			return; + +		SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY); +		SetNextDest(pActor); +	} +} + +/** + * Move + */ +void Move(PMACTOR pActor, int newx, int newy, HPOLYGON hPath) { +	MultiSetAniXY(pActor->actorObj, newx, newy); +	MAsetZPos(pActor, newy, getPolyZfactor(hPath)); +	if (StepAnimScript(&pActor->actorAnim) == ScriptFinished) { +		// The end of a scale-change reel +		// Revert to normal walking reel +		pActor->walkReel = false; +		pActor->scount = 0; +		SetMActorWalkReel(pActor, pActor->dirn, pActor->scale, true); +	} +	pActor->objx = newx; +	pActor->objy = newy; + +	// Synchronised walking reels +	if (++pActor->scount >= 6) +		pActor->scount = 0; +} + +/** + * Called from MActorProcess() on every tick. + * + * Moves the actor as appropriate. + */ +void MoveActor(PMACTOR pActor) { +	int newx, newy; +	HPOLYGON hPath; +	int status, s2;		// s2 not used here! +	HPOLYGON hS2p;		// nor is s2p! +	HPOLYGON hEb; +	PMACTOR ma; +	int	sTargetX, sTargetY; +	bool bNewPath = false; + +	// Only do anything if the actor needs to move! +	if (pActor->targetX == -1 && pActor->targetY == -1) +		return; + +	if (pActor->stop) { +		GotThere(pActor); +		pActor->stop = false; +		SetMActorStanding(pActor); +		return; +	} + +#if SLOW_RINCE_DOWN +/**/	if (BogusVar++ < Interlude)	// Temporary slow-down-the-action code +/**/		return;			// +/**/	BogusVar = 0;			// +#endif + +	// During swalk()s, movement while hidden may be slowed down. +	if (pActor->aHidden) { +		if (++hSlowVar < pActor->SlowFactor) +			return; +		hSlowVar = 0; +	} + +	// 'push' the target +	sTargetX = pActor->targetX; +	sTargetY = pActor->targetY; + +	NewCoOrdinates(pActor->objx, pActor->objy, &pActor->targetX, &pActor->targetY, +			&newx, &newy, &status, &s2, &hS2p, pActor->over, false, pActor); + +	if (newx == pActor->objx && newy == pActor->objy) { +		// 'pop' the target +		pActor->targetX = sTargetX; +		pActor->targetY = sTargetY; + +		NewCoOrdinates(pActor->objx, pActor->objy, &pActor->targetX, &pActor->targetY, &newx, &newy, +				&status, &s2, &hS2p, pActor->over, true, pActor); +		if (newx == pActor->objx && newy == pActor->objy) { +			NotMoving(pActor, newx, newy); +			return; +		} +	} + +	// Find out which path we're in now +	hPath = InPolygon(newx, newy, PATH); +	if (hPath == NOPOLY) { +		if (pActor->bNoPath) { +			Move(pActor, newx, newy, pActor->hCpath); +			return; +		} else { +			// May be marginally outside! +			// OR bIgPath may be set. +			hPath = pActor->hCpath; +		} +	} else if (pActor->bNoPath) { +		pActor->bNoPath = false; +		bNewPath = true; +	} else if (hPath != pActor->hCpath) { +		if (IsInPolygon(newx, newy, pActor->hCpath)) +			hPath = pActor->hCpath; +	} + +	CheckScale(pActor, hPath, newy); + +	/* +	* Must stay out of moving actors' blocking polygons. +	*/ +	ma = InMActorBlock(pActor, newx, newy); +	if (ma != NULL) { +		// Stop if there's no chance of arriving +		if (InMActorBlock(pActor, pActor->UtargetX, pActor->UtargetY)) { +			GotThere(pActor); +			return; +		} + +		if (InMActorBlock(pActor, pActor->objx, pActor->objy)) +			; +		else { +			hEb = InitExtraBlock(pActor, ma); +			newx = pActor->objx; +			newy = pActor->objy; +			BlockingCorner(hEb, &newx, &newy, pActor->ItargetX, pActor->ItargetY); +			SetMoverDest(pActor, newx, newy); +			return; +		} +	} + +	/*-------------------------------------- +	 This is where it actually gets moved. +	 --------------------------------------*/ +	Move(pActor, newx, newy, hPath); + +	// Entering a new path polygon? +	if (hPath != pActor->hCpath || bNewPath) +		EnteringNewPath(pActor, hPath, newx, newy); +} + +/** + * Store the default refer type for the current scene. + */ +void SetDefaultRefer(int32 defRefer) { +	DefaultRefer = defRefer; +} + +/** + * DoMoveActor + */ +void DoMoveActor(PMACTOR pActor) { +	int wasx, wasy; +	int i; + +#define NUMBER 1 + +	wasx = pActor->objx; +	wasy = pActor->objy; + +	MoveActor(pActor); + +	if ((pActor->targetX != -1 || pActor->targetY != -1) +	&&  (wasx == pActor->objx && wasy == pActor->objy)) 	{ +		for (i=0; i < NUMBER; i++) { +			MoveActor(pActor); +			if (wasx != pActor->objx || wasy != pActor->objy) +				break; +		} +//		assert(i<NUMBER); +	} +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/move.h b/engines/tinsel/move.h new file mode 100644 index 0000000000..2c5f2cfe73 --- /dev/null +++ b/engines/tinsel/move.h @@ -0,0 +1,43 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef TINSEL_MOVE_H	// prevent multiple includes +#define TINSEL_MOVE_H + +#include "tinsel/dw.h"	// for SCNHANDLE + +namespace Tinsel { + +struct MACTOR; + +void SetActorDest(MACTOR *pActor, int x, int y, bool igPath, SCNHANDLE film); +void SSetActorDest(MACTOR *pActor); +void DoMoveActor(MACTOR *pActor); + +void SetDefaultRefer(int32 defRefer); + +} // end of namespace Tinsel + +#endif /* TINSEL_MOVE_H */ diff --git a/engines/tinsel/multiobj.cpp b/engines/tinsel/multiobj.cpp new file mode 100644 index 0000000000..c60592069f --- /dev/null +++ b/engines/tinsel/multiobj.cpp @@ -0,0 +1,533 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * This file contains utilities to handle multi-part objects. + */ + +#include "tinsel/multiobj.h" +#include "tinsel/handle.h" +#include "tinsel/object.h" + +namespace Tinsel { + +// from object.c +extern OBJECT *objectList; + +/** + * Initialise a multi-part object using a list of images to init + * each object piece. One object is created for each image in the list. + * All objects are given the same palette as the first image. A pointer + * to the first (master) object created is returned. + * @param pInitTbl			Pointer to multi-object initialisation table + */ +OBJECT *MultiInitObject(const MULTI_INIT *pInitTbl) { +	OBJ_INIT obj_init;	// object init table +	OBJECT *pFirst, *pObj;	// object pointers +	FRAME *pFrame;		// list of images for the multi-part object + +	if (pInitTbl->hMulFrame) { +		// we have a frame handle +		pFrame = (FRAME *)LockMem(FROM_LE_32(pInitTbl->hMulFrame)); + +		obj_init.hObjImg  = READ_LE_UINT32(pFrame);	// first objects shape +	} else {	// this must be a animation list for a NULL object +		pFrame = NULL; +		obj_init.hObjImg = 0;	// first objects shape +	} + +	// init the object init table +	obj_init.objFlags = (int)FROM_LE_32(pInitTbl->mulFlags);	// all objects have same flags +	obj_init.objID    = (int)FROM_LE_32(pInitTbl->mulID);	// all objects have same ID +	obj_init.objX     = (int)FROM_LE_32(pInitTbl->mulX);	// all objects have same X ani pos +	obj_init.objY     = (int)FROM_LE_32(pInitTbl->mulY);	// all objects have same Y ani pos +	obj_init.objZ     = (int)FROM_LE_32(pInitTbl->mulZ);	// all objects have same Z pos + +	// create and init the first object +	pObj = pFirst = InitObject(&obj_init); + +	if (pFrame) { +		// if we have any animation frames + +		pFrame++; + +		while (READ_LE_UINT32(pFrame) != 0) { +			// set next objects shape +			obj_init.hObjImg = READ_LE_UINT32(pFrame); + +			// create next object and link to previous +			pObj = pObj->pSlave = InitObject(&obj_init); + +			pFrame++; +		} +	} + +	// null end of list for final object +	pObj->pSlave = NULL; + +	// return master object +	return pFirst; +} + +/** + * Inserts the multi-part object onto the specified object list. + * @param pObjList			List to insert multi-part object onto +* @param  pInsObj			Head of multi-part object to insert + + */ + +void MultiInsertObject(OBJECT *pObjList, OBJECT *pInsObj) { +	// validate object pointer +	assert(pInsObj >= objectList && pInsObj <= objectList + NUM_OBJECTS - 1); + +	// for all the objects that make up this multi-part +	do { +		// add next part to the specified list +		InsertObject(pObjList, pInsObj); + +		// next obj in list +		pInsObj = pInsObj->pSlave; +	} while (pInsObj != NULL); +} + +/** + * Deletes all the pieces of a multi-part object from the + * specified object list. + * @param pObjList			List to delete multi-part object from + * @param pMultiObj			Multi-part object to be deleted + */ + +void MultiDeleteObject(OBJECT *pObjList, OBJECT *pMultiObj) { +	// validate object pointer +	assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1); + +	// for all the objects that make up this multi-part +	do { +		// delete object +		DelObject(pObjList, pMultiObj); + +		// next obj in list +		pMultiObj = pMultiObj->pSlave; +	} +	while (pMultiObj != NULL); +} + +/** + * Hides a multi-part object by giving each object a "NullImage" + * image pointer. + * @param pMultiObj			Multi-part object to be hidden + */ + +void MultiHideObject(OBJECT *pMultiObj) { +	// validate object pointer +	assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1); + +	// set master shape to null animation frame +	pMultiObj->hShape = 0; + +	// change all objects +	MultiReshape(pMultiObj); +} + +/** + * Horizontally flip a multi-part object. + * @param pFlipObj			Head of multi-part object to flip + */ + +void MultiHorizontalFlip(OBJECT *pFlipObj) { +	// validate object pointer +	assert(pFlipObj >= objectList && pFlipObj <= objectList + NUM_OBJECTS - 1); + +	// for all the objects that make up this multi-part +	do { +		// horizontally flip the next part +		AnimateObjectFlags(pFlipObj, pFlipObj->flags ^ DMA_FLIPH, +			pFlipObj->hImg); + +		// next obj in list +		pFlipObj = pFlipObj->pSlave; +	} while (pFlipObj != NULL); +} + +/** + * Vertically flip a multi-part object. + * @param pFlipObj			Head of multi-part object to flip + */ + +void MultiVerticalFlip(OBJECT *pFlipObj) { +	// validate object pointer +	assert(pFlipObj >= objectList && pFlipObj <= objectList + NUM_OBJECTS - 1); + +	// for all the objects that make up this multi-part +	do { +		// vertically flip the next part +		AnimateObjectFlags(pFlipObj, pFlipObj->flags ^ DMA_FLIPV, +			pFlipObj->hImg); + +		// next obj in list +		pFlipObj = pFlipObj->pSlave; +	} +	while (pFlipObj != NULL); +} + +/** + * Adjusts the coordinates of a multi-part object.	The adjustments + * take into account the orientation of the object. + * @param pMultiObj			Multi-part object to be adjusted + * @param deltaX			X adjustment + * @param deltaY			Y adjustment + */ + +void MultiAdjustXY(OBJECT *pMultiObj, int deltaX, int deltaY) { +	// validate object pointer +	assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1); + +	if (deltaX == 0 && deltaY == 0) +		return;		// ignore no change + +	if (pMultiObj->flags & DMA_FLIPH) { +		// image is flipped horizontally - flip the x direction +		deltaX = -deltaX; +	} + +	if (pMultiObj->flags & DMA_FLIPV) { +		// image is flipped vertically - flip the y direction +		deltaY = -deltaY; +	} + +	// for all the objects that make up this multi-part +	do { +		// signal a change in the object +		pMultiObj->flags |= DMA_CHANGED; + +		// adjust the x position +		pMultiObj->xPos += intToFrac(deltaX); + +		// adjust the y position +		pMultiObj->yPos += intToFrac(deltaY); + +		// next obj in list +		pMultiObj = pMultiObj->pSlave; + +	} while (pMultiObj != NULL); +} + +/** + * Moves all the pieces of a multi-part object by the specified + * amount. Does not take into account the objects orientation. + * @param pMultiObj			Multi-part object to be adjusted + * @param deltaX			X movement + * @param deltaY			Y movement + */ + +void MultiMoveRelXY(OBJECT *pMultiObj, int deltaX, int deltaY) { +	// validate object pointer +	assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1); + +	if (deltaX == 0 && deltaY == 0) +		return;		// ignore no change + +	// for all the objects that make up this multi-part +	do { +		// signal a change in the object +		pMultiObj->flags |= DMA_CHANGED; + +		// adjust the x position +		pMultiObj->xPos += intToFrac(deltaX); + +		// adjust the y position +		pMultiObj->yPos += intToFrac(deltaY); + +		// next obj in list +		pMultiObj = pMultiObj->pSlave; + +	} while (pMultiObj != NULL); +} + +/** + * Sets the x & y anim position of all pieces of a multi-part object. + * @param pMultiObj			Multi-part object whose position is to be changed + * @param newAniX			New x animation position + * @param newAniY			New y animation position + */ + +void MultiSetAniXY(OBJECT *pMultiObj, int newAniX, int newAniY) { +	int curAniX, curAniY;	// objects current animation position + +	// validate object pointer +	assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1); + +	// get master objects current animation position +	GetAniPosition(pMultiObj, &curAniX, &curAniY); + +	// calc difference between current and new positions +	newAniX -= curAniX; +	newAniY -= curAniY; + +	// move all pieces by the difference +	MultiMoveRelXY(pMultiObj, newAniX, newAniY); +} + +/** + * Sets the x anim position of all pieces of a multi-part object. + * @param pMultiObj			Multi-part object whose x position is to be changed + * @param newAniX			New x animation position + */ + +void MultiSetAniX(OBJECT *pMultiObj, int newAniX) { +	int curAniX, curAniY;	// objects current animation position + +	// validate object pointer +	assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1); + +	// get master objects current animation position +	GetAniPosition(pMultiObj, &curAniX, &curAniY); + +	// calc x difference between current and new positions +	newAniX -= curAniX; +	curAniY = 0; + +	// move all pieces by the difference +	MultiMoveRelXY(pMultiObj, newAniX, curAniY); +} + +/** + * Sets the y anim position of all pieces of a multi-part object. + * @param pMultiObj			Multi-part object whose x position is to be changed + * @param newAniX			New y animation position + */ + +void MultiSetAniY(OBJECT *pMultiObj, int newAniY) { +	int curAniX, curAniY;	// objects current animation position + +	// validate object pointer +	assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1); + +	// get master objects current animation position +	GetAniPosition(pMultiObj, &curAniX, &curAniY); + +	// calc y difference between current and new positions +	curAniX = 0; +	newAniY -= curAniY; + +	// move all pieces by the difference +	MultiMoveRelXY(pMultiObj, curAniX, newAniY); +} + +/** + * Sets the Z position of all pieces of a multi-part object. + * @param pMultiObj		Multi-part object to be adjusted + * @param newZ			New Z order + */ + +void MultiSetZPosition(OBJECT *pMultiObj, int newZ) { +	// validate object pointer +	assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1); + +	// for all the objects that make up this multi-part +	do { +		// signal a change in the object +		pMultiObj->flags |= DMA_CHANGED; + +		// set the new z position +		pMultiObj->zPos = newZ; + +		// next obj in list +		pMultiObj = pMultiObj->pSlave; +	} +	while (pMultiObj != NULL); +} + +/** + * Reshape a multi-part object. + * @param pMultiObj			Multi-part object to re-shape + */ + +void MultiReshape(OBJECT *pMultiObj) { +	SCNHANDLE hFrame; + +	// validate object pointer +	assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1); + +	// get objects current anim frame +	hFrame = pMultiObj->hShape; + +	if (hFrame != 0 && hFrame != pMultiObj->hMirror) { +		// a valid shape frame which is different from previous + +		// get pointer to frame +		const FRAME *pFrame = (const FRAME *)LockMem(hFrame); + +		// update previous +		pMultiObj->hMirror = hFrame; + +		while (READ_LE_UINT32(pFrame) != 0 && pMultiObj != NULL) { +			// a normal image - update the current object with this image +			AnimateObject(pMultiObj, READ_LE_UINT32(pFrame)); + +			// move to next image for this frame +			pFrame++; + +			// move to next part of object +			pMultiObj = pMultiObj->pSlave; +		} + +		// null the remaining object parts +		while (pMultiObj != NULL) { +			// set a null image for this object part +			AnimateObject(pMultiObj, 0); + +			// move to next part of object +			pMultiObj = pMultiObj->pSlave; +		} +	} else if (hFrame == 0) { +		// update previous +		pMultiObj->hMirror = hFrame; + +		// null all the object parts +		while (pMultiObj != NULL) { +			// set a null image for this object part +			AnimateObject(pMultiObj, 0); + +			// move to next part of object +			pMultiObj = pMultiObj->pSlave; +		} +	} +} + +/** + * Returns the left-most point of a multi-part object. + * @param pMulti			Multi-part object + */ + +int MultiLeftmost(OBJECT *pMulti) { +	int left; + +	// validate object pointer +	assert(pMulti >= objectList && pMulti <= objectList + NUM_OBJECTS - 1); + +	// init leftmost point to first object +	left = fracToInt(pMulti->xPos); + +	// for all the objects in this multi +	while ((pMulti = pMulti->pSlave) != NULL) { +		if (pMulti->hImg != 0) { +			// non null object part + +			if (fracToInt(pMulti->xPos) < left) +				// this object is further left +				left = fracToInt(pMulti->xPos); +		} +	} + +	// return left-most point +	return left; +} + +/** + * Returns the right-most point of a multi-part object. + * @param pMulti			Multi-part object + */ + +int MultiRightmost(OBJECT *pMulti) { +	int right; + +	// validate object pointer +	assert(pMulti >= objectList && pMulti <= objectList + NUM_OBJECTS - 1); + +	// init right-most point to first object +	right = fracToInt(pMulti->xPos) + pMulti->width; + +	// for all the objects in this multi +	while ((pMulti = pMulti->pSlave) != NULL) { +		if (pMulti->hImg != 0) { +			// non null object part + +			if (fracToInt(pMulti->xPos) + pMulti->width > right) +				// this object is further right +				right = fracToInt(pMulti->xPos) + pMulti->width; +		} +	} + +	// return right-most point +	return right - 1; +} + +/** + * Returns the highest point of a multi-part object. + * @param pMulti			Multi-part object + */ + +int MultiHighest(OBJECT *pMulti) { +	int highest; + +	// validate object pointer +	assert(pMulti >= objectList && pMulti <= objectList + NUM_OBJECTS - 1); + +	// init highest point to first object +	highest = fracToInt(pMulti->yPos); + +	// for all the objects in this multi +	while ((pMulti = pMulti->pSlave) != NULL) { +		if (pMulti->hImg != 0) { +			// non null object part + +			if (fracToInt(pMulti->yPos) < highest) +				// this object is higher +				highest = fracToInt(pMulti->yPos); +		} +	} + +	// return highest point +	return highest; +} + +/** + * Returns the lowest point of a multi-part object. + * @param pMulti			Multi-part object + */ + +int MultiLowest(OBJECT *pMulti) { +	int lowest; + +	// validate object pointer +	assert(pMulti >= objectList && pMulti <= objectList + NUM_OBJECTS - 1); + +	// init lowest point to first object +	lowest = fracToInt(pMulti->yPos) + pMulti->height; + +	// for all the objects in this multi +	while ((pMulti = pMulti->pSlave) != NULL) { +		if (pMulti->hImg != 0) { +			// non null object part + +			if (fracToInt(pMulti->yPos) + pMulti->height > lowest) +				// this object is lower +				lowest = fracToInt(pMulti->yPos) + pMulti->height; +		} +	} + +	// return lowest point +	return lowest - 1; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/multiobj.h b/engines/tinsel/multiobj.h new file mode 100644 index 0000000000..6d25600ea2 --- /dev/null +++ b/engines/tinsel/multiobj.h @@ -0,0 +1,124 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Multi-part object definitions + */ + +#ifndef TINSEL_MULTIOBJ_H     // prevent multiple includes +#define TINSEL_MULTIOBJ_H + +#include "tinsel/dw.h" + +namespace Tinsel { + +struct OBJECT; + +#include "common/pack-start.h"	// START STRUCT PACKING + +/** + * multi-object initialisation structure (parallels OBJ_INIT struct) + */ +struct MULTI_INIT { +	SCNHANDLE hMulFrame;	//!< multi-objects shape - NULL terminated list of IMAGE structures +	int32 mulFlags;			//!< multi-objects flags +	int32 mulID;			//!< multi-objects id +	int32 mulX;				//!< multi-objects initial x ani position +	int32 mulY;				//!< multi-objects initial y ani position +	int32 mulZ;				//!< multi-objects initial z position +} PACKED_STRUCT; + +#include "common/pack-end.h"	// END STRUCT PACKING + +/*----------------------------------------------------------------------*\ +|*			Multi Object Function Prototypes		*| +\*----------------------------------------------------------------------*/ + +OBJECT *MultiInitObject(	// Initialise a multi-part object +	const MULTI_INIT *pInitTbl);	// pointer to multi-object initialisation table + +void MultiInsertObject(		// Insert a multi-part object onto a object list +	OBJECT *pObjList,	// list to insert multi-part object onto +	OBJECT *pInsObj);	// head of multi-part object to insert + +void MultiDeleteObject(		// Delete all the pieces of a multi-part object +	OBJECT *pObjList,	// list to delete multi-part object from +	OBJECT *pMultiObj);	// multi-part object to be deleted + +void MultiHideObject(		// Hide a multi-part object +	OBJECT *pMultiObj);	// multi-part object to be hidden + +void MultiHorizontalFlip(	// Hortizontally flip a multi-part object +	OBJECT *pFlipObj);	// head of multi-part object to flip + +void MultiVerticalFlip(		// Vertically flip a multi-part object +	OBJECT *pFlipObj);	// head of multi-part object to flip + +void MultiAdjustXY(		// Adjust coords of a multi-part object. Takes into account the orientation +	OBJECT *pMultiObj,	// multi-part object to be adjusted +	int deltaX,		// x adjustment +	int deltaY);		// y adjustment + +void MultiMoveRelXY(		// Move multi-part object relative. Does not take into account the orientation +	OBJECT *pMultiObj,	// multi-part object to be moved +	int deltaX,		// x movement +	int deltaY);		// y movement + +void MultiSetAniXY(		// Set the x & y anim position of a multi-part object +	OBJECT *pMultiObj,	// multi-part object whose position is to be changed +	int newAniX,		// new x animation position +	int newAniY);		// new y animation position + +void MultiSetAniX(		// Set the x anim position of a multi-part object +	OBJECT *pMultiObj,	// multi-part object whose x position is to be changed +	int newAniX);		// new x animation position + +void MultiSetAniY(		// Set the y anim position of a multi-part object +	OBJECT *pMultiObj,	// multi-part object whose y position is to be adjusted +	int newAniY);		// new y animation position + +void MultiSetZPosition(		// Sets the z position of a multi-part object +	OBJECT *pMultiObj,	// multi-part object to be adjusted +	int newZ);		// new Z order + +void MultiMatchAniPoints(	// Matches a multi-parts pos and orientation to be the same as a reference object +	OBJECT *pMoveObj,	// multi-part object to be moved +	OBJECT *pRefObj);	// multi-part object to match with + +void MultiReshape(		// Reshape a multi-part object +	OBJECT *pMultiObj);	// multi-part object to re-shape + +int MultiLeftmost(		// Returns the left-most point of a multi-part object +	OBJECT *pMulti);	// multi-part object + +int MultiRightmost(		// Returns the right-most point of a multi-part object +	OBJECT *pMulti);	// multi-part object + +int MultiHighest(		// Returns the highest point of a multi-part object +	OBJECT *pMulti);	// multi-part object + +int MultiLowest(		// Returns the lowest point of a multi-part object +	OBJECT *pMulti);	// multi-part object + +} // end of namespace Tinsel + +#endif	// TINSEL_MULTIOBJ_H diff --git a/engines/tinsel/music.cpp b/engines/tinsel/music.cpp new file mode 100644 index 0000000000..7d4efd8079 --- /dev/null +++ b/engines/tinsel/music.cpp @@ -0,0 +1,551 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +// FIXME: This code is taken from MADE and may need more work (e.g. setVolume). + +// MIDI and digital music class + +#include "sound/audiostream.h" +#include "sound/mididrv.h" +#include "sound/midiparser.h" +#include "sound/audiocd.h" +#include "common/config-manager.h" +#include "common/file.h" + +#include "tinsel/config.h" +#include "tinsel/sound.h" +#include "tinsel/music.h" + +namespace Tinsel { + +//--------------------------- Midi data ------------------------------------- + +// sound buffer structure used for MIDI data and samples +struct SOUND_BUFFER { +	uint8 *pDat;		// pointer to actual buffer +	uint32 size;		// size of the buffer +}; + +// get set when music driver is installed +//static MDI_DRIVER *mDriver; +//static HSEQUENCE mSeqHandle; + +// if non-zero this is the index position of the next MIDI sequence to play +static uint32 dwMidiIndex = 0; + +// MIDI buffer +static SOUND_BUFFER midiBuffer = { 0, 0 }; + +static SCNHANDLE	currentMidi = 0; +static bool		currentLoop = false; + +const SCNHANDLE midiOffsetsGRAVersion[] = { +		 4,	  4534,	 14298,	 18828,	 23358,	 38888,	 54418,	 57172,	 59926,	 62450, +	 62952,	 67482,	 72258,	 74538,	 79314,	 87722,	103252,	115176,	127100,	127898, +	130256,	132614,	134972,	137330,	139688,	150196,	152554,	154912,	167422,	174762, +	182102,	194612,	198880,	199536,	206128,	206380,	216372,	226364,	235676,	244988, +	249098,	249606,	251160,	252714,	263116,	268706,	274296,	283562,	297986,	304566, +	312028,	313524,	319192,	324860,	331772,	336548,	336838,	339950,	343062,	346174, +	349286,	356246,	359358,	360434,	361510,	369966,	374366,	382822,	384202,	394946, +	396022,	396730,	399524,	401020,	403814,	418364,	419466,	420568,	425132,	433540, +	434384,	441504,	452132,	462760,	472804,	486772,	491302,	497722,	501260,	507680, +	509726,	521858,	524136,	525452,	533480,	538236,	549018,	559870,	564626,	565306, +	566734,	567616,	570144,	574102,	574900,	582518,	586350,	600736,	604734,	613812, +	616566,	619626,	623460,	627294,	631128,	634188,	648738,	663288,	667864,	681832, +	682048,	683014,	688908,	689124,	698888,	708652,	718416,	728180,	737944,	747708, +	752238,	765522,	766554,	772944,	774546,	776148,	776994,	781698,	786262,	789016, +	794630,	796422,	798998 +}; + +const SCNHANDLE midiOffsetsSCNVersion[] = { +		 4,	  4504,	 11762,	 21532,	 26070,	 28754,	 33254,	 40512,	 56310,	 72108, +	 74864,	 77620,	 80152,	 80662,	 85200,	 89982,	 92268,	 97050,	105466,	121264, +	133194,	145124,	145928,	148294,	150660,	153026,	155392,	157758,	168272,	170638, +	173004,	185522,	192866,	200210,	212728,	217000,	217662,	224254,	224756,	234754, +	244752,	245256,	245950,	255256,	264562,	268678,	269192,	270752,	272312,	282712, +	288312,	293912,	303186,	317624,	324210,	331680,	333208,	338884,	344560,	351478, +	356262,	356552,	359670,	362788,	365906,	369024,	376014,	379132,	380214,	381296, +	389758,	394164,	402626,	404012,	414762,	415844,	416552,	419352,	420880,	423680, +	438236,	439338,	440440,	445010,	453426,	454276,	461398,	472032,	482666,	492716, +	506690,	511226,	517654,	521198,	527626,	529676,	541814,	546210,	547532,	555562, +	560316,	571104,	581962,	586716,	587402,	588836,	589718,	592246,	596212,	597016, +	604636,	608474,	622862,	626860,	635944,	638700,	641456,	645298,	649140,	652982, +	655738,	670294,	684850,	689432,	703628,	703850,	704816,	706350,	706572,	716342, +	726112,	735882,	745652,	755422,	765192,	774962,	784732,	794502,	804272,	814042, +	823812,	832996,	846286,	847324,	853714,	855324,	856934,	857786,	862496,	867066, +	869822,	875436,	877234,	879818 +}; + +// TODO: finish this (currently unmapped tracks are 0) +const int enhancedAudioSCNVersion[] = { +	 0,  0,  2,  0,  0,  0,  0,  3,  3,  4, +	 4,  0,  0,  0,  0,  0,  0, 10,  3, 11, +	11,  0, 13, 13, 13, 13, 13,  0, 13, 13, +	 0,  0,  0,  0,  0,  0,  0,  0,  0,  0, +	 0,  0,  0,  0,  0, 24,  0,  0, 27,  0, +	 0,  0,  0,  0,  0,  0,  0,  0,  0,  0, +	 0,  0,  0,  0,  0,  0,  0,  0,  0,  0, +	 0,  0,  0,  0,  0,  0,  0,  0,  0,  0, +	 0,  0,  0,  0,  0, 55, 56, 56,  0,  0, +	 0,  0,  0,  0,  0,  0,  0,  0,  0,  0, +	 0,  0,  0,  0,  0,  0,  0,  0,  0,  0, +	 0,  0,  0,  0,  4,  4, 83, 83, 83,  4, +	 0,  0,  0,  0,  0,  0,  0,  0,  2,  2, +	 2,  2,  2,  2,  2,  2,  2,  2,  2,  2, +	 0,  0,  0,  0,  0,  0,  0,  0, 52,  4, +	 0,  0,  0,  0	 +}; + +int GetTrackNumber(SCNHANDLE hMidi) { +	if (_vm->getFeatures() & GF_SCNFILES) { +		for (int i = 0; i < ARRAYSIZE(midiOffsetsSCNVersion); i++) { +			if (midiOffsetsSCNVersion[i] == hMidi) +				return i; +		} +	} else { +		for (int i = 0; i < ARRAYSIZE(midiOffsetsGRAVersion); i++) { +			if (midiOffsetsGRAVersion[i] == hMidi) +				return i; +		} +	} + +	return -1; +} + +SCNHANDLE GetTrackOffset(int trackNumber) { +	if (_vm->getFeatures() & GF_SCNFILES) { +		assert(trackNumber < ARRAYSIZE(midiOffsetsSCNVersion)); +		return midiOffsetsSCNVersion[trackNumber]; +	} else { +		assert(trackNumber < ARRAYSIZE(midiOffsetsGRAVersion)); +		return midiOffsetsGRAVersion[trackNumber]; +	} +} + +/** + * Plays the specified MIDI sequence through the sound driver. + * @param dwFileOffset		File offset of MIDI sequence data + * @param bLoop				Whether to loop the sequence + */ +bool PlayMidiSequence(uint32 dwFileOffset, bool bLoop) { +	currentMidi = dwFileOffset; +	currentLoop = bLoop; + +	if (volMidi != 0) { +		SetMidiVolume(volMidi); +		// Support for compressed music from the music enhancement project +		AudioCD.stop(); + +		int trackNumber = GetTrackNumber(dwFileOffset); +		if (trackNumber >= 0) { +#if 0 +			// TODO: GRA version +			int track = enhancedAudioSCNVersion[trackNumber]; +			if (track > 0) +				AudioCD.play(track, -1, 0, 0); +#endif +		} else { +			warning("Unknown MIDI offset %d", dwFileOffset); +		} + +		if (AudioCD.isPlaying()) +			return true; +	} + +	// set file offset for this sequence +	dwMidiIndex = dwFileOffset; + +	// the index and length of the last tune loaded +	static uint32 dwLastMidiIndex; +	static uint32 dwLastSeqLen; + +	uint32 dwSeqLen = 0;	// length of the sequence + +	if (dwMidiIndex == 0) +		return true; + +	if (dwMidiIndex != dwLastMidiIndex) { +		Common::File midiStream; + +		// open MIDI sequence file in binary mode +		if (!midiStream.open(MIDI_FILE)) +			error("Cannot find file %s", MIDI_FILE); + +		// update index of last tune loaded +		dwLastMidiIndex = dwMidiIndex; + +		// move to correct position in the file +		midiStream.seek(dwMidiIndex, SEEK_SET); + +		// read the length of the sequence +		dwSeqLen = midiStream.readUint32LE(); + +		// make sure buffer is large enough for this sequence +		assert(dwSeqLen > 0 && dwSeqLen <= midiBuffer.size); + +		// stop any currently playing tune +		_vm->_music->stop(); + +		// read the sequence +		if (midiStream.read(midiBuffer.pDat, dwSeqLen) != dwSeqLen) +			error("File %s is corrupt", MIDI_FILE); + +		midiStream.close(); + +		_vm->_music->playXMIDI(midiBuffer.pDat, dwSeqLen, bLoop); + +		// Store the length +		dwLastSeqLen = dwSeqLen; +	} else { +	  	// dwMidiIndex == dwLastMidiIndex +		_vm->_music->stop(); +		_vm->_music->playXMIDI(midiBuffer.pDat, dwSeqLen, bLoop); +	} + +	// allow another sequence to play +	dwMidiIndex = 0; + +	return true; +} + +/** + * Returns TRUE if a Midi tune is currently playing. + */ +bool MidiPlaying(void) { +	if (AudioCD.isPlaying()) return true; +	return _vm->_music->isPlaying(); +} + +/** + * Stops any currently playing midi. + */ +bool StopMidi(void) { +	currentMidi = 0; +	currentLoop = false; + +	AudioCD.stop(); +	_vm->_music->stop(); +	return true; +} + + +/** + * Gets the volume of the MIDI music. + */ +int GetMidiVolume() { +	return volMidi; +} + +/** + * Sets the volume of the MIDI music. + * @param vol			New volume - 0..MAXMIDIVOL + */ +void SetMidiVolume(int vol)	{ +	assert(vol >= 0 && vol <= MAXMIDIVOL); + +	if (vol == 0 && volMidi == 0) 	{ +		// Nothing to do +	} else if (vol == 0 && volMidi != 0) { +		// Stop current midi sequence +		AudioCD.stop(); +		StopMidi(); +	} else if (vol != 0 && volMidi == 0) { +		// Perhaps restart last midi sequence +		if (currentLoop) { +			PlayMidiSequence(currentMidi, true); +			_vm->_music->setVolume(vol); +		} +	} else if (vol != 0 && volMidi != 0) { +		// Alter current volume +		_vm->_music->setVolume(vol); +	} + +	volMidi = vol; +} + +/** + * Opens and inits all MIDI sequence files. + */ +void OpenMidiFiles(void) { +	Common::File midiStream; + +	// Demo version has no midi file +	if (_vm->getFeatures() & GF_DEMO) +		return; + +	if (midiBuffer.pDat) +		// already allocated +		return; + +	// open MIDI sequence file in binary mode +	if (!midiStream.open(MIDI_FILE)) +		error("Cannot find file %s", MIDI_FILE); + +	// gen length of the largest sequence +	midiBuffer.size = midiStream.readUint32LE(); +	if (midiStream.ioFailed()) +		error("File %s is corrupt", MIDI_FILE); + +	if (midiBuffer.size) { +		// allocate a buffer big enough for the largest MIDI sequence +		if ((midiBuffer.pDat = (uint8 *)malloc(midiBuffer.size)) != NULL) { +			// clear out the buffer +			memset(midiBuffer.pDat, 0, midiBuffer.size); +//			VMM_lock(midiBuffer.pDat, midiBuffer.size); +		} else { +			//mSeqHandle = NULL; +		} +	} + +	midiStream.close(); +} + +void DeleteMidiBuffer() { +	free(midiBuffer.pDat); +	midiBuffer.pDat = NULL; +} + +MusicPlayer::MusicPlayer(MidiDriver *driver) : _parser(0), _driver(driver), _looping(false), _isPlaying(false) { +	memset(_channel, 0, sizeof(_channel)); +	_masterVolume = 0; +	this->open(); +	_xmidiParser = MidiParser::createParser_XMIDI(); +} + +MusicPlayer::~MusicPlayer() { +	_driver->setTimerCallback(NULL, NULL); +	stop(); +	this->close(); +	_xmidiParser->setMidiDriver(NULL); +	delete _xmidiParser; +} + +void MusicPlayer::setVolume(int volume) { +	Common::StackLock lock(_mutex); + +	// FIXME: Could we simply change MAXMIDIVOL to match ScummVM's range? +	volume = CLIP((255 * volume) / MAXMIDIVOL, 0, 255); +	_vm->_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, volume); + +	if (_masterVolume == volume) +		return; + +	_masterVolume = volume; + +	for (int i = 0; i < 16; ++i) { +		if (_channel[i]) { +			_channel[i]->volume(_channelVolume[i] * _masterVolume / 255); +		} +	} +} + +int MusicPlayer::open() { +	// Don't ever call open without first setting the output driver! +	if (!_driver) +		return 255; + +	int ret = _driver->open(); +	if (ret) +		return ret; + +	_driver->setTimerCallback(this, &onTimer); +	return 0; +} + +void MusicPlayer::close() { +	stop(); +	if (_driver) +		_driver->close(); +	_driver = 0; +} + +void MusicPlayer::send(uint32 b) { +	byte channel = (byte)(b & 0x0F); +	if ((b & 0xFFF0) == 0x07B0) { +		// Adjust volume changes by master volume +		byte volume = (byte)((b >> 16) & 0x7F); +		_channelVolume[channel] = volume; +		volume = volume * _masterVolume / 255; +		b = (b & 0xFF00FFFF) | (volume << 16); +	} else if ((b & 0xFFF0) == 0x007BB0) { +		//Only respond to All Notes Off if this channel +		//has currently been allocated +		if (_channel[b & 0x0F]) +			return; +	} + +	if (!_channel[channel]) +		_channel[channel] = (channel == 9) ? _driver->getPercussionChannel() : _driver->allocateChannel(); + +	if (_channel[channel]) { +		_channel[channel]->send(b); + +		if ((b & 0xFFF0) == 0x0079B0) { +			// We've just Reset All Controllers, so we need to +			// re-adjust the volume. Otherwise, volume is reset to +			// default whenever the music changes. +			_channel[channel]->send(0x000007B0 | (((_channelVolume[channel] * _masterVolume) / 255) << 16) | channel); +		} +	} +} + +void MusicPlayer::metaEvent(byte type, byte *data, uint16 length) { +	switch (type) { +	case 0x2F:	// End of Track +		if (_looping) +			_parser->jumpToTick(0); +		else +			stop(); +		break; +	default: +		//warning("Unhandled meta event: %02x", type); +		break; +	} +} + +void MusicPlayer::onTimer(void *refCon) { +	MusicPlayer *music = (MusicPlayer *)refCon; +	Common::StackLock lock(music->_mutex); + +	if (music->_isPlaying) +		music->_parser->onTimer(); +} + +void MusicPlayer::playXMIDI(byte *midiData, uint32 size, bool loop) { +	if (_isPlaying) +		return; + +	stop(); + +	// It seems like not all music (the main menu music, for instance) set +	// all the instruments explicitly. That means the music will sound +	// different, depending on which music played before it. This appears +	// to be a genuine glitch in the original. For consistency, reset all +	// instruments to the default one (piano). + +	for (int i = 0; i < 16; i++) { +		_driver->send(0xC0 | i, 0, 0); +	} + +	// Load XMID resource data + +	if (_xmidiParser->loadMusic(midiData, size)) { +		MidiParser *parser = _xmidiParser; +		parser->setTrack(0); +		parser->setMidiDriver(this); +		parser->setTimerRate(getBaseTempo()); +		parser->property(MidiParser::mpCenterPitchWheelOnUnload, 1); + +		_parser = parser; + +		_looping = loop; +		_isPlaying = true; +	} +} + +void MusicPlayer::stop() { +	Common::StackLock lock(_mutex); + +	_isPlaying = false; +	if (_parser) { +		_parser->unloadMusic(); +		_parser = NULL; +	} +} + +void MusicPlayer::pause() { +	setVolume(-1); +	_isPlaying = false; +} + +void MusicPlayer::resume() { +	setVolume(GetMidiVolume()); +	_isPlaying = true; +} + +void CurrentMidiFacts(SCNHANDLE	*pMidi, bool *pLoop) { +	*pMidi = currentMidi; +	*pLoop = currentLoop; +} + +void RestoreMidiFacts(SCNHANDLE	Midi, bool Loop) { +	AudioCD.stop(); +	StopMidi(); + +	currentMidi = Midi; +	currentLoop = Loop; + +	if (volMidi != 0 && Loop) { +		PlayMidiSequence(currentMidi, true); +		SetMidiVolume(volMidi); +	} +} + +#if 0 +// Dumps all of the game's music in external XMIDI *.xmi files +void dumpMusic() { +	Common::File midiFile; +	Common::DumpFile outFile; +	char outName[20]; +	midiFile.open(MIDI_FILE); +	int outFileSize = 0; +	char buffer[20000]; + +	int total = (_vm->getFeatures() & GF_SCNFILES) ?  +				ARRAYSIZE(midiOffsetsSCNVersion) :  +				ARRAYSIZE(midiOffsetsGRAVersion); + +	for (int i = 0; i < total; i++) { +		sprintf(outName, "track%03d.xmi", i + 1); +		outFile.open(outName); + +		if (_vm->getFeatures() & GF_SCNFILES) { +			if (i < total - 1) +				outFileSize = midiOffsetsSCNVersion[i + 1] - midiOffsetsSCNVersion[i] - 4; +			else +				outFileSize = midiFile.size() - midiOffsetsSCNVersion[i] - 4; + +			midiFile.seek(midiOffsetsSCNVersion[i] + 4, SEEK_SET); +		} else { +			if (i < total - 1) +				outFileSize = midiOffsetsGRAVersion[i + 1] - midiOffsetsGRAVersion[i] - 4; +			else +				outFileSize = midiFile.size() - midiOffsetsGRAVersion[i] - 4; + +			midiFile.seek(midiOffsetsGRAVersion[i] + 4, SEEK_SET); +		} + +		assert(outFileSize < 20000); +		midiFile.read(buffer, outFileSize); +		outFile.write(buffer, outFileSize); + +		outFile.close(); +	} + +	midiFile.close(); +} +#endif + +} // End of namespace Made diff --git a/engines/tinsel/music.h b/engines/tinsel/music.h new file mode 100644 index 0000000000..80456e2a76 --- /dev/null +++ b/engines/tinsel/music.h @@ -0,0 +1,118 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +// Music class + +#ifndef TINSEL_MUSIC_H +#define TINSEL_MUSIC_H + +#include "sound/mididrv.h" +#include "sound/midiparser.h" +#include "common/mutex.h" + +namespace Tinsel { + +#define MAXMIDIVOL 127 + +bool PlayMidiSequence(		// Plays the specified MIDI sequence through the sound driver +	uint32 dwFileOffset,		// handle of MIDI sequence data +	bool bLoop);			// Whether to loop the sequence + +bool MidiPlaying(void);		// Returns TRUE if a Midi tune is currently playing + +bool StopMidi(void);		// Stops any currently playing midi + +void SetMidiVolume(		// Sets the volume of the MIDI music. Returns the old volume +	int vol);		// new volume - 0..MAXMIDIVOL + +int GetMidiVolume(); + +void OpenMidiFiles(); +void DeleteMidiBuffer(); + +void CurrentMidiFacts(SCNHANDLE	*pMidi, bool *pLoop); +void RestoreMidiFacts(SCNHANDLE	Midi, bool Loop); + +int GetTrackNumber(SCNHANDLE hMidi); +SCNHANDLE GetTrackOffset(int trackNumber); + +void dumpMusic(); + + +class MusicPlayer : public MidiDriver { +public: +	MusicPlayer(MidiDriver *driver); +	~MusicPlayer(); + +	bool isPlaying() { return _isPlaying; } +	void setPlaying(bool playing) { _isPlaying = playing; } + +	void setVolume(int volume); +	int getVolume() { return _masterVolume; } + +	void playXMIDI(byte *midiData, uint32 size, bool loop); +	void stop(); +	void pause(); +	void resume(); +	void setLoop(bool loop) { _looping = loop; } + +	//MidiDriver interface implementation +	int open(); +	void close(); +	void send(uint32 b); + +	void metaEvent(byte type, byte *data, uint16 length); + +	void setTimerCallback(void *timerParam, void (*timerProc)(void *)) { } + +	// The original sets the "sequence timing" to 109 Hz, whatever that +	// means. The default is 120. + +	uint32 getBaseTempo(void)	{ return _driver ? (109 * _driver->getBaseTempo()) / 120 : 0; } + +	//Channel allocation functions +	MidiChannel *allocateChannel()		{ return 0; } +	MidiChannel *getPercussionChannel()	{ return 0; } + +	MidiParser *_parser; +	Common::Mutex _mutex; + +protected: + +	static void onTimer(void *data); + +	MidiChannel *_channel[16]; +	MidiDriver *_driver; +	MidiParser *_xmidiParser; +	byte _channelVolume[16]; + +	bool _isPlaying; +	bool _looping; +	byte _masterVolume; +}; + +} // End of namespace Made + +#endif diff --git a/engines/tinsel/object.cpp b/engines/tinsel/object.cpp new file mode 100644 index 0000000000..709fa4fad9 --- /dev/null +++ b/engines/tinsel/object.cpp @@ -0,0 +1,530 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * This file contains the Object Manager code. + */ + +#include "tinsel/object.h" +#include "tinsel/background.h" +#include "tinsel/cliprect.h"	// object clip rect defs +#include "tinsel/graphics.h"	// low level interface +#include "tinsel/handle.h" + +#define	OID_EFFECTS	0x2000			// generic special effects object id + +namespace Tinsel { + +/** screen clipping rectangle */ +static const Common::Rect rcScreen(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); + +// list of all objects +OBJECT *objectList = 0; + +// pointer to free object list +static OBJECT *pFreeObjects = 0; + +#ifdef DEBUG +// diagnostic object counters +static int numObj = 0; +static int maxObj = 0; +#endif + +void FreeObjectList(void) { +	if (objectList) { +		free(objectList); +		objectList = NULL; +	} +} + +/** + * Kills all objects and places them on the free list. + */ + +void KillAllObjects(void) { +	int i; + +#ifdef DEBUG +	// clear number of objects in use +	numObj = 0; +#endif + +	if (objectList == NULL) { +		// first time - allocate memory for object list +		objectList = (OBJECT *)calloc(NUM_OBJECTS, sizeof(OBJECT)); + +		// make sure memory allocated +		if (objectList == NULL) { +			error("Cannot allocate memory for object data"); +		} +	} + +	// place first object on free list +	pFreeObjects = objectList; + +	// link all other objects after first +	for (i = 1; i < NUM_OBJECTS; i++) { +		objectList[i - 1].pNext = objectList + i; +	} + +	// null the last object +	objectList[NUM_OBJECTS - 1].pNext = NULL; +} + + +#ifdef	DEBUG +/** + * Shows the maximum number of objects used at once. + */ + +void ObjectStats(void) { +	printf("%i objects of %i used.\n", maxObj, NUM_OBJECTS); +} +#endif + +/** + * Allocate a object from the free list. + */ +OBJECT *AllocObject(void) { +	OBJECT *pObj = pFreeObjects;	// get a free object + +	// check for no free objects +	assert(pObj != NULL); + +	// a free object exists + +	// get link to next free object +	pFreeObjects = pObj->pNext; + +	// clear out object +	memset(pObj, 0, sizeof(OBJECT)); + +	// set default drawing mode and set changed bit +	pObj->flags = DMA_WNZ | DMA_CHANGED; + +#ifdef DEBUG +	// one more object in use +	if (++numObj > maxObj) +		maxObj = numObj; +#endif + +	// return new object +	return pObj; +} + +/** + * Copy one object to another. + * @param pDest			Destination object + * @param pSrc			Source object + */ +void CopyObject(OBJECT *pDest, OBJECT *pSrc) { +	// save previous dimensions etc. +	Common::Rect rcSave = pDest->rcPrev; + +	// make a copy +	memcpy(pDest, pSrc, sizeof(OBJECT)); + +	// restore previous dimensions etc. +	pDest->rcPrev = rcSave; + +	// set changed flag in destination +	pDest->flags |= DMA_CHANGED; + +	// null the links +	pDest->pNext = pDest->pSlave = NULL; +} + +/** + * Inserts an object onto the specified object list. The object + * lists are sorted in Z Y order. + * @param pObjList			List to insert object onto + * @param pInsObj			Object to insert + */ + +void InsertObject(OBJECT *pObjList, OBJECT *pInsObj) { +	OBJECT *pPrev, *pObj;	// object list traversal pointers + +	// validate object pointer +	assert(pInsObj >= objectList && pInsObj <= objectList + NUM_OBJECTS - 1); + +	for (pPrev = pObjList, pObj = pObjList->pNext; pObj != NULL; pPrev = pObj, pObj = pObj->pNext) { +		// check Z order +		if (pInsObj->zPos < pObj->zPos) { +			// object Z is lower than list Z - insert here +			break; +		} else if (pInsObj->zPos == pObj->zPos) { +			// Z values are the same - sort on Y +			if (fracToDouble(pInsObj->yPos) <= fracToDouble(pObj->yPos)) { +				// object Y is lower than or same as list Y - insert here +				break; +			} +		} +	} + +	// insert obj between pPrev and pObj +	pInsObj->pNext = pObj; +	pPrev->pNext = pInsObj; +} + + +/** + * Deletes an object from the specified object list and places it + * on the free list. + * @param pObjList			List to delete object from + * @param pDelObj			Object to delete + */ +void DelObject(OBJECT *pObjList, OBJECT *pDelObj) { +	OBJECT *pPrev, *pObj;	// object list traversal pointers + +	// validate object pointer +	assert(pDelObj >= objectList && pDelObj <= objectList + NUM_OBJECTS - 1); + +#ifdef DEBUG +	// one less object in use +	--numObj; +	assert(numObj >= 0); +#endif + +	for (pPrev = pObjList, pObj = pObjList->pNext; pObj != NULL; pPrev = pObj, pObj = pObj->pNext) { +		if (pObj == pDelObj) { +			// found object to delete + +			if (IntersectRectangle(pDelObj->rcPrev, pDelObj->rcPrev, rcScreen)) { +				// allocate a clipping rect for objects previous pos +				AddClipRect(pDelObj->rcPrev); +			} + +			// make PREV next = OBJ next - removes OBJ from list +			pPrev->pNext = pObj->pNext; + +			// place free list in OBJ next +			pObj->pNext = pFreeObjects; + +			// add OBJ to top of free list +			pFreeObjects = pObj; + +			// delete objects palette +			if (pObj->pPal) +				FreePalette(pObj->pPal); + +			// quit +			return; +		} +	} + +	// if we get to here - object has not been found on the list +	error("DelObject(): formally 'assert(0)!'"); +} + + +/** + * Sort the specified object list in Z Y order. + * @param pObjList			List to sort + */ +void SortObjectList(OBJECT *pObjList) { +	OBJECT *pPrev, *pObj;	// object list traversal pointers +	OBJECT head;		// temporary head of list - because pObjList is not usually a OBJECT + +	// put at head of list +	head.pNext = pObjList->pNext; + +	// set head of list dummy OBJ Z Y values to lowest possible +	head.yPos = intToFrac(MIN_INT16); +	head.zPos = MIN_INT; +	 +	for (pPrev = &head, pObj = head.pNext; pObj != NULL; pPrev = pObj, pObj = pObj->pNext) { +		// check Z order +		if (pObj->zPos < pPrev->zPos) { +			// object Z is lower than previous Z + +			// remove object from list +			pPrev->pNext = pObj->pNext; + +			// re-insert object on list +			InsertObject(pObjList, pObj); + +			// back to beginning of list +			pPrev = &head; +			pObj  = head.pNext; +		} else if (pObj->zPos == pPrev->zPos) { +			// Z values are the same - sort on Y +			if (fracToDouble(pObj->yPos) < fracToDouble(pPrev->yPos)) { +				// object Y is lower than previous Y + +				// remove object from list +				pPrev->pNext = pObj->pNext; + +				// re-insert object on list +				InsertObject(pObjList, pObj); + +				// back to beginning of list +				pPrev = &head; +				pObj  = head.pNext; +			} +		} +	} +} + +/** + * Returns the animation offsets of a image, dependent on the + * images orientation flags. + * @param hImg			Iimage to get animation offset of + * @param flags			Images current flags + * @param pAniX			Gets set to new X animation offset + * @param pAniY			Gets set to new Y animation offset + */ +void GetAniOffset(SCNHANDLE hImg, int flags, int *pAniX, int *pAniY) { +	if (hImg) { +		const IMAGE *pImg = (const IMAGE *)LockMem(hImg); + +		// set ani X +		*pAniX = (int16) FROM_LE_16(pImg->anioffX); + +		// set ani Y +		*pAniY = (int16) FROM_LE_16(pImg->anioffY); + +		if (flags & DMA_FLIPH) { +			// we are flipped horizontally + +			// set ani X = -ani X + width - 1 +			*pAniX = -*pAniX + FROM_LE_16(pImg->imgWidth) - 1; +		} + +		if (flags & DMA_FLIPV) { +			// we are flipped vertically + +			// set ani Y = -ani Y + height - 1 +			*pAniY = -*pAniY + FROM_LE_16(pImg->imgHeight) - 1; +		} +	} else +		// null image +		*pAniX = *pAniY = 0; +} + + +/** + * Returns the x,y position of an objects animation point. + * @param pObj			Pointer to object + * @param pPosX			Gets set to objects X animation position + * @param pPosY			Gets set to objects Y animation position + */ +void GetAniPosition(OBJECT *pObj, int *pPosX, int *pPosY) { +	// validate object pointer +	assert(pObj >= objectList && pObj <= objectList + NUM_OBJECTS - 1); + +	// get the animation offset of the object +	GetAniOffset(pObj->hImg, pObj->flags, pPosX, pPosY); + +	// from animation offset and objects position - determine objects animation point +	*pPosX += fracToInt(pObj->xPos); +	*pPosY += fracToInt(pObj->yPos); +} + +/** + * Initialise a object using a OBJ_INIT structure to supply parameters. + * @param pInitTbl			Pointer to object initialisation table + */ +OBJECT *InitObject(const OBJ_INIT *pInitTbl) { +	// allocate a new object +	OBJECT *pObj = AllocObject(); + +	// make sure object created +	assert(pObj != NULL); + +	// set objects shape +	pObj->hImg = pInitTbl->hObjImg; + +	// set objects ID +	pObj->oid = pInitTbl->objID; + +	// set objects flags +	pObj->flags = DMA_CHANGED | pInitTbl->objFlags; + +	// set objects Z position +	pObj->zPos = pInitTbl->objZ; + +	// get pointer to image +	if (pInitTbl->hObjImg) { +		int aniX, aniY;		// objects animation offsets +		PALQ *pPalQ;		// palette queue pointer +		const IMAGE *pImg = (const IMAGE *)LockMem(pInitTbl->hObjImg);	// handle to image + +		// allocate a palette for this object +		pPalQ = AllocPalette(FROM_LE_32(pImg->hImgPal)); + +		// make sure palette allocated +		assert(pPalQ != NULL); + +		// assign palette to object +		pObj->pPal = pPalQ; + +		// set objects size +		pObj->width  = FROM_LE_16(pImg->imgWidth); +		pObj->height = FROM_LE_16(pImg->imgHeight); + +		// set objects bitmap definition +		pObj->hBits = FROM_LE_32(pImg->hImgBits); + +		// get animation offset of object +		GetAniOffset(pObj->hImg, pInitTbl->objFlags, &aniX, &aniY); + +		// set objects X position - subtract ani offset +		pObj->xPos = intToFrac(pInitTbl->objX - aniX); + +		// set objects Y position - subtract ani offset +		pObj->yPos = intToFrac(pInitTbl->objY - aniY); +	} else {	// no image handle - null image + +		// set objects X position +		pObj->xPos = intToFrac(pInitTbl->objX); + +		// set objects Y position +		pObj->yPos = intToFrac(pInitTbl->objY); +	} + +	// return new object +	return pObj; +} + +/** + * Give a object a new image and new orientation flags. + * @param pAniObj			Object to be updated + * @param newflags			Objects new flags + * @param hNewImg			Objects new image + */ +void AnimateObjectFlags(OBJECT *pAniObj, int newflags, SCNHANDLE hNewImg) { +	// validate object pointer +	assert(pAniObj >= objectList && pAniObj <= objectList + NUM_OBJECTS - 1); + +	if (pAniObj->hImg != hNewImg +		|| (pAniObj->flags & DMA_HARDFLAGS) != (newflags & DMA_HARDFLAGS)) { +		// something has changed + +		int oldAniX, oldAniY;	// objects old animation offsets +		int newAniX, newAniY;	// objects new animation offsets + +		// get objects old animation offsets +		GetAniOffset(pAniObj->hImg, pAniObj->flags, &oldAniX, &oldAniY); + +		// get objects new animation offsets +		GetAniOffset(hNewImg, newflags, &newAniX, &newAniY); + +		if (hNewImg) { +			// get pointer to image +			const IMAGE *pNewImg = (IMAGE *)LockMem(hNewImg); + +			// setup new shape +			pAniObj->width  = FROM_LE_16(pNewImg->imgWidth); +			pAniObj->height = FROM_LE_16(pNewImg->imgHeight); + +			// set objects bitmap definition +			pAniObj->hBits  = FROM_LE_32(pNewImg->hImgBits); +		} else {	// null image +			pAniObj->width  = 0; +			pAniObj->height = 0; +			pAniObj->hBits  = 0; +		} + +		// set objects flags and signal a change +		pAniObj->flags = newflags | DMA_CHANGED; + +		// set objects image +		pAniObj->hImg = hNewImg; + +		// adjust objects position - subtract new from old for difference +		pAniObj->xPos += intToFrac(oldAniX - newAniX); +		pAniObj->yPos += intToFrac(oldAniY - newAniY); +	} +} + +/** + * Give an object a new image. + * @param pAniObj			Object to animate + * @param hNewImg			Objects new image + */ +void AnimateObject(OBJECT *pAniObj, SCNHANDLE hNewImg) { +	// dont change the objects flags +	AnimateObjectFlags(pAniObj, pAniObj->flags, hNewImg); +} + +/** + * Creates a rectangle object of the given dimensions and returns + * a pointer to the object. + * @param hPal			Palette for the rectangle object + * @param colour		Which colour offset from the above palette + * @param width			Width of rectangle + * @param height		Height of rectangle + */ +OBJECT *RectangleObject(SCNHANDLE hPal, int colour, int width, int height) { +	// template for initialising the rectangle object +	static const OBJ_INIT rectObj = {0, DMA_CONST, OID_EFFECTS, 0, 0, 0}; +	PALQ *pPalQ;		// palette queue pointer + +	// allocate and init a new object +	OBJECT *pRect = InitObject(&rectObj); + +	// allocate a palette for this object +	pPalQ = AllocPalette(hPal); + +	// make sure palette allocated +	assert(pPalQ != NULL); + +	// assign palette to object +	pRect->pPal = pPalQ; + +	// set colour in the palette +	pRect->constant = colour; + +	// set rectangle width +	pRect->width = width; + +	// set rectangle height +	pRect->height = height; + +	// return pointer to rectangle object +	return pRect; +} + +/** + * Creates a translucent rectangle object of the given dimensions + * and returns a pointer to the object. + * @param width			Width of rectangle + * @param height		Height of rectangle + */ +OBJECT *TranslucentObject(int width, int height) { +	// template for initialising the rectangle object +	static const OBJ_INIT rectObj = {0, DMA_TRANS, OID_EFFECTS, 0, 0, 0}; + +	// allocate and init a new object +	OBJECT *pRect = InitObject(&rectObj); + +	// set rectangle width +	pRect->width = width; + +	// set rectangle height +	pRect->height = height; + +	// return pointer to rectangle object +	return pRect; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/object.h b/engines/tinsel/object.h new file mode 100644 index 0000000000..8b61571a3e --- /dev/null +++ b/engines/tinsel/object.h @@ -0,0 +1,206 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Object Manager data structures + */ + +#ifndef TINSEL_OBJECT_H	// prevent multiple includes +#define TINSEL_OBJECT_H + +#include "tinsel/dw.h" +#include "common/frac.h" +#include "common/rect.h" + +namespace Tinsel { + +struct PALQ; + +enum { +	/** the maximum number of objects */ +	NUM_OBJECTS	= 256, + +	// object flags +	DMA_WNZ		= 0x0001,	//!< write non-zero data +	DMA_CNZ		= 0x0002,	//!< write constant on non-zero data +	DMA_CONST	= 0x0004,	//!< write constant on both zero & non-zero data +	DMA_WA		= 0x0008,	//!< write all data +	DMA_FLIPH	= 0x0010,	//!< flip object horizontally +	DMA_FLIPV	= 0x0020,	//!< flip object vertically +	DMA_CLIP	= 0x0040,	//!< clip object +	DMA_TRANS	= 0x0084,	//!< translucent rectangle object +	DMA_ABS		= 0x0100,	//!< position of object is absolute +	DMA_CHANGED	= 0x0200,	//!< object has changed in some way since the last frame +	DMA_USERDEF	= 0x0400,	//!< user defined flags start here + +	/** flags that effect an objects appearance */ +	DMA_HARDFLAGS	= (DMA_WNZ | DMA_CNZ | DMA_CONST | DMA_WA | DMA_FLIPH | DMA_FLIPV | DMA_TRANS) +}; + +/** structure for image */ +struct IMAGE { +	short imgWidth;		//!< image width +	short imgHeight;	//!< image height +	short anioffX;		//!< image x animation offset +	short anioffY;		//!< image y animation offset +	SCNHANDLE hImgBits;	//!< image bitmap handle +	SCNHANDLE hImgPal;	//!< image palette handle +}; + + +/** a multi-object animation frame is a list of multi-image handles */ +typedef uint32 FRAME; + + +// object structure +struct OBJECT { +	OBJECT *pNext;	//!< pointer to next object in list +	OBJECT *pSlave;	//!< pointer to slave object (multi-part objects) +//	char *pOnDispList;	//!< pointer to display list byte for background objects +//	frac_t xVel;		//!< x velocity of object +//	frac_t yVel;		//!< y velocity of object +	frac_t xPos;		//!< x position of object +	frac_t yPos;		//!< y position of object +	int zPos;			//!< z position of object +	Common::Rect rcPrev;		//!< previous screen coordinates of object bounding rectangle +	int flags;			//!< object flags - see above for list +	PALQ *pPal;			//!< objects palette Q position +	int constant;		//!< which colour in palette for monochrome objects +	int width;			//!< width of object +	int height;			//!< height of object +	SCNHANDLE hBits;	//!< image bitmap handle +	SCNHANDLE hImg;		//!< handle to object image definition +	SCNHANDLE hShape;	//!< objects current animation frame +	SCNHANDLE hMirror;	//!< objects previous animation frame +	int oid;			//!< object identifier +}; + +#include "common/pack-start.h"	// START STRUCT PACKING + +// object initialisation structure +struct OBJ_INIT { +	SCNHANDLE hObjImg;	// objects shape - handle to IMAGE structure +	int32 objFlags;		// objects flags +	int32 objID;		// objects id +	int32 objX;		// objects initial x position +	int32 objY;		// objects initial y position +	int32 objZ;		// objects initial z position +} PACKED_STRUCT; + +#include "common/pack-end.h"	// END STRUCT PACKING + + +/*----------------------------------------------------------------------*\ +|*			Object Function Prototypes			*| +\*----------------------------------------------------------------------*/ + +void KillAllObjects(void);	// kill all objects and place them on free list + +void FreeObjectList(void);	// free the object list + +#ifdef	DEBUG +void ObjectStats(void);		// Shows the maximum number of objects used at once +#endif + +OBJECT *AllocObject(void);	// allocate a object from the free list + +void FreeObject(		// place a object back on the free list +	OBJECT *pFreeObj);	// object to free + +void CopyObject(		// copy one object to another +	OBJECT *pDest,		// destination object +	OBJECT *pSrc);		// source object + +void InsertObject(		// insert a object onto a sorted object list +	OBJECT *pObjList,	// list to insert object onto +	OBJECT *pInsObj);	// object to insert + +void DelObject(			// delete a object from a object list and add to free list +	OBJECT *pObjList,	// list to delete object from +	OBJECT *pDelObj);	// object to delete + +void SortObjectList(		// re-sort an object list +	OBJECT *pObjList);	// list to sort + +OBJECT *GetNextObject(		// object list iterator - returns next obj in list +	OBJECT *pObjList,	// which object list +	OBJECT *pStrtObj);	// object to start from - when NULL will start from beginning of list + +OBJECT *FindObject(		// Searches the specified obj list for a object matching the specified OID +	OBJECT *pObjList,	// object list to search +	int oidDesired,		// object identifer of object to find +	int oidMask);		// mask to apply to object identifiers before comparison + +void GetAniOffset(	// returns the anim offsets of a image, takes into account orientation +	SCNHANDLE hImg,	// image to get animation offset of +	int flags,	// images current flags +	int *pAniX,	// gets set to new X animation offset +	int *pAniY);	// gets set to new Y animation offset + +void GetAniPosition(	// Returns a objects x,y animation point +	OBJECT *pObj,	// pointer to object +	int *pPosX,	// gets set to objects X animation position +	int *pPosY);	// gets set to objects Y animation position + +OBJECT *InitObject(		// Init a object using a OBJ_INIT struct +	const OBJ_INIT *pInitTbl);	// pointer to object initialisation table + +void AnimateObjectFlags(	// Give a object a new image and new orientation flags +	OBJECT *pAniObj,	// object to be updated +	int newflags,		// objects new flags +	SCNHANDLE hNewImg);	// objects new image + +void AnimateObject(		// give a object a new image +	OBJECT *pAniObj,	// object to animate +	SCNHANDLE hNewImg);	// objects new image + +void HideObject(		// Hides a object by giving it a "NullImage" image pointer +	OBJECT *pObj);		// object to be hidden + +OBJECT *RectangleObject(	// create a rectangle object of the given dimensions +	SCNHANDLE hPal,		// palette for the rectangle object +	int colour,		// which colour offset from the above palette +	int width,		// width of rectangle +	int height);		// height of rectangle + +OBJECT *TranslucentObject(	// create a translucent rectangle object of the given dimensions +	int width,		// width of rectangle +	int height);		// height of rectangle + +void ResizeRectangle(		// resizes a rectangle object +	OBJECT *pRect,		// rectangle object pointer +	int width,		// new width of rectangle +	int height);		// new height of rectangle + + +// FIXME: This does not belong here +struct FILM; +struct FREEL; +struct MULTI_INIT; +IMAGE *GetImageFromReel(const FREEL *pfreel, const MULTI_INIT **ppmi = 0); +IMAGE *GetImageFromFilm(SCNHANDLE hFilm, int reel, const FREEL **ppfr = 0, +					const MULTI_INIT **ppmi = 0, const FILM **ppfilm = 0); + + +} // end of namespace Tinsel + +#endif	// TINSEL_OBJECT_H diff --git a/engines/tinsel/palette.cpp b/engines/tinsel/palette.cpp new file mode 100644 index 0000000000..3bc2b514b5 --- /dev/null +++ b/engines/tinsel/palette.cpp @@ -0,0 +1,440 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Palette Allocator for IBM PC. + */ + +#include "tinsel/dw.h"		// TBLUE1 definition +#include "tinsel/graphics.h" +#include "tinsel/handle.h"	// LockMem definition +#include "tinsel/palette.h"	// palette allocator structures etc. +#include "tinsel/tinsel.h" + +#include "common/system.h" + +namespace Tinsel { + +//----------------- LOCAL DEFINES -------------------- + +/** video DAC transfer Q structure */ +struct VIDEO_DAC_Q { +	union { +		SCNHANDLE hRGBarray;	//!< handle of palette or +		COLORREF *pRGBarray;	//!< list of palette colours +	} pal; +	bool bHandle;		//!< when set - use handle of palette +	int destDACindex;	//!< start index of palette in video DAC +	int numColours;		//!< number of colours in "hRGBarray" +}; + + +//----------------- LOCAL GLOBAL DATA -------------------- + +/** background colour */ +static COLORREF bgndColour = BLACK; + +/** palette allocator data */ +static PALQ palAllocData[NUM_PALETTES]; + + +/** video DAC transfer Q length */ +#define VDACQLENGTH (NUM_PALETTES+2) + +/** video DAC transfer Q */ +static VIDEO_DAC_Q vidDACdata[VDACQLENGTH]; + +/** video DAC transfer Q head pointer */ +static VIDEO_DAC_Q *pDAChead; + +/** colour index of the 4 colours used for the translucent palette */ +#define COL_HILIGHT	TBLUE1 + +/** the translucent palette lookup table */ +uint8 transPalette[MAX_COLOURS];	// used in graphics.cpp + +#ifdef DEBUG +// diagnostic palette counters +static int numPals = 0; +static int maxPals = 0; +static int maxDACQ = 0; +#endif + +/** + * Transfer palettes in the palette Q to Video DAC. + */ +void PalettesToVideoDAC(void) { +	PALQ *pPalQ;				// palette Q iterator +	VIDEO_DAC_Q *pDACtail = vidDACdata;	// set tail pointer +	bool needUpdate = false; + +	// while Q is not empty +	while (pDAChead != pDACtail) { +		PALETTE *pPalette;	// pointer to hardware palette +		COLORREF *pColours;	// pointer to list of RGB triples + +#ifdef	DEBUG +		// make sure palette does not overlap +		assert(pDACtail->destDACindex + pDACtail->numColours <= MAX_COLOURS); +#else +		// make sure palette does not overlap +		if (pDACtail->destDACindex + pDACtail->numColours > MAX_COLOURS) +			pDACtail->numColours = MAX_COLOURS - pDACtail->destDACindex; +#endif + +		if (pDACtail->bHandle) { +			// we are using a palette handle + +			// get hardware palette pointer +			pPalette = (PALETTE *)LockMem(pDACtail->pal.hRGBarray); + +			// get RGB pointer +			pColours = pPalette->palRGB; +		} else { +			// we are using a palette pointer +			pColours = pDACtail->pal.pRGBarray; +		} + +		if (pDACtail->numColours > 0) +			needUpdate = true; + +		// update the system palette +		g_system->setPalette((byte *)pColours, pDACtail->destDACindex, pDACtail->numColours); + +		// update tail pointer +		pDACtail++; + +	} + +	// reset video DAC transfer Q head pointer +	pDAChead = vidDACdata; + +	// clear all palette moved bits +	for (pPalQ = palAllocData; pPalQ < palAllocData + NUM_PALETTES; pPalQ++) +		pPalQ->posInDAC &= ~PALETTE_MOVED; + +	if (needUpdate) +		g_system->updateScreen(); +} + +/** + * Commpletely reset the palette allocator. + */ +void ResetPalAllocator(void) { +#ifdef DEBUG +	// clear number of palettes in use +	numPals = 0; +#endif + +	// wipe out the palette allocator data +	memset(palAllocData, 0, sizeof(palAllocData)); + +	// reset video DAC transfer Q head pointer +	pDAChead = vidDACdata; +} + +#ifdef	DEBUG +/** + * Shows the maximum number of palettes used at once. + */ +void PaletteStats(void) { +	printf("%i palettes of %i used.\n", maxPals, NUM_PALETTES); +	printf("%i DAC queue entries of %i used.\n", maxDACQ, VDACQLENGTH); +} +#endif + +/** + * Places a palette in the video DAC queue. + * @param posInDAC			Position in video DAC + * @param numColours		Number of colours in palette + * @param hPalette			Handle to palette + */ +void UpdateDACqueueHandle(int posInDAC, int numColours, SCNHANDLE hPalette) { +	// check Q overflow +	assert(pDAChead < vidDACdata + VDACQLENGTH); + +	pDAChead->destDACindex = posInDAC & ~PALETTE_MOVED;	// set index in video DAC +	pDAChead->numColours = numColours;	// set number of colours +	pDAChead->pal.hRGBarray = hPalette;	// set handle of palette +	pDAChead->bHandle = true;		// we are using a palette handle + +	// update head pointer +	++pDAChead; + +#ifdef DEBUG +	if ((pDAChead-vidDACdata) > maxDACQ) +		maxDACQ = pDAChead-vidDACdata; +#endif +} + +/** + * Places a palette in the video DAC queue. + * @param posInDAC			Position in video DAC + * @param numColours,		Number of colours in palette + * @param pColours			List of RGB triples + */ +void UpdateDACqueue(int posInDAC, int numColours, COLORREF *pColours) { +	// check Q overflow +	assert(pDAChead < vidDACdata + NUM_PALETTES); + +	pDAChead->destDACindex = posInDAC & ~PALETTE_MOVED;	// set index in video DAC +	pDAChead->numColours = numColours;	// set number of colours +	pDAChead->pal.pRGBarray = pColours;	// set addr of palette +	pDAChead->bHandle = false;		// we are not using a palette handle + +	// update head pointer +	++pDAChead; + +#ifdef DEBUG +	if ((pDAChead-vidDACdata) > maxDACQ) +		maxDACQ = pDAChead-vidDACdata; +#endif +} + +/** + * Allocate a palette. + * @param hNewPal			Palette to allocate + */ +PALQ *AllocPalette(SCNHANDLE hNewPal) { +	PALQ *pPrev, *p;		// walks palAllocData +	int iDAC;		// colour index in video DAC +	PALQ *pNxtPal;		// next PALQ struct in palette allocator +	PALETTE *pNewPal; + +	// get pointer to new palette +	pNewPal = (PALETTE *)LockMem(hNewPal); + +	// search all structs in palette allocator - see if palette already allocated +	for (p = palAllocData; p < palAllocData + NUM_PALETTES; p++) { +		if (p->hPal == hNewPal) { +			// found the desired palette in palette allocator +			p->objCount++;	// update number of objects using palette +			return p;	// return palette queue position +		} +	} + +	// search all structs in palette allocator - find a free slot +	iDAC = FGND_DAC_INDEX;	// init DAC index to first available foreground colour + +	for (p = palAllocData; p < palAllocData + NUM_PALETTES; p++) { +		if (p->hPal == 0) { +			// found a free slot in palette allocator +			p->objCount = 1;	// init number of objects using palette +			p->posInDAC = iDAC;	// set palettes start pos in video DAC +			p->hPal = hNewPal;	// set hardware palette data +			p->numColours = FROM_LE_32(pNewPal->numColours);	// set number of colours in palette + +#ifdef DEBUG +			// one more palette in use +			if (++numPals > maxPals) +				maxPals = numPals; +#endif + +			// Q the change to the video DAC +			UpdateDACqueueHandle(p->posInDAC, p->numColours, p->hPal); + +			// move all palettes after this one down (if necessary) +			for (pPrev = p, pNxtPal = pPrev + 1; pNxtPal < palAllocData + NUM_PALETTES; pNxtPal++) { +				if (pNxtPal->hPal != 0) { +					// palette slot is in use +					if (pNxtPal->posInDAC >= pPrev->posInDAC + pPrev->numColours) +						// no need to move palettes down +						break; + +					// move palette down - indicate change +					pNxtPal->posInDAC = pPrev->posInDAC +						+ pPrev->numColours | PALETTE_MOVED; + +					// Q the palette change in position to the video DAC +					UpdateDACqueueHandle(pNxtPal->posInDAC, +						pNxtPal->numColours, +						pNxtPal->hPal); + +					// update previous palette to current palette +					pPrev = pNxtPal; +				} +			} + +			// return palette pointer +			return p; +		} + +		// set new DAC index +		iDAC = p->posInDAC + p->numColours; +	} + +	// no free palettes +	error("AllocPalette(): formally 'assert(0)!'"); +} + +/** + * Free a palette allocated with "AllocPalette". + * @param pFreePal			Palette queue entry to free + */ +void FreePalette(PALQ *pFreePal) { +	// validate palette Q pointer +	assert(pFreePal >= palAllocData && pFreePal <= palAllocData + NUM_PALETTES - 1); + +	// reduce the palettes object reference count +	pFreePal->objCount--; + +	// make sure palette has not been deallocated too many times +	assert(pFreePal->objCount >= 0); + +	if (pFreePal->objCount == 0) { +		pFreePal->hPal = 0;	// palette is no longer in use + +#ifdef DEBUG +		// one less palette in use +		--numPals; +		assert(numPals >= 0); +#endif +	} +} + +/** + * Find the specified palette. + * @param hSrchPal			Hardware palette to search for + */ +PALQ *FindPalette(SCNHANDLE hSrchPal) { +	PALQ *pPal;		// palette allocator iterator + +	// search all structs in palette allocator +	for (pPal = palAllocData; pPal < palAllocData + NUM_PALETTES; pPal++) { +		if (pPal->hPal == hSrchPal) +			// found palette in palette allocator +			return pPal; +	} + +	// palette not found +	return NULL; +} + +/** + * Swaps the palettes at the specified palette queue position. + * @param pPalQ			Palette queue position + * @param hNewPal		New palette + */ +void SwapPalette(PALQ *pPalQ, SCNHANDLE hNewPal) { +	// convert handle to palette pointer +	PALETTE *pNewPal = (PALETTE *)LockMem(hNewPal); + +	// validate palette Q pointer +	assert(pPalQ >= palAllocData && pPalQ <= palAllocData + NUM_PALETTES - 1); + +	if (pPalQ->numColours >= (int)FROM_LE_32(pNewPal->numColours)) { +		// new palette will fit the slot + +		// install new palette +		pPalQ->hPal = hNewPal; + +		// Q the change to the video DAC +		UpdateDACqueueHandle(pPalQ->posInDAC, FROM_LE_32(pNewPal->numColours), hNewPal); +	} else { +		// # colours are different - will have to update all following palette entries + +		PALQ *pNxtPalQ;		// next palette queue position + +		for (pNxtPalQ = pPalQ + 1; pNxtPalQ < palAllocData + NUM_PALETTES; pNxtPalQ++) { +			if (pNxtPalQ->posInDAC >= pPalQ->posInDAC + pPalQ->numColours) +				// no need to move palettes down +				break; + +			// move palette down +			pNxtPalQ->posInDAC = pPalQ->posInDAC +				+ pPalQ->numColours | PALETTE_MOVED; + +			// Q the palette change in position to the video DAC +			UpdateDACqueueHandle(pNxtPalQ->posInDAC, +				pNxtPalQ->numColours, +				pNxtPalQ->hPal); + +			// update previous palette to current palette +			pPalQ = pNxtPalQ; +		} +	} +} + +/** + * Statless palette iterator. Returns the next palette in the list + * @param pStrtPal			Palette to start from - when NULL will start from beginning of list + */ +PALQ *GetNextPalette(PALQ *pStrtPal) { +	if (pStrtPal == NULL) { +		// start of palette iteration - return 1st palette +		return (palAllocData[0].objCount) ? palAllocData : NULL; +	} + +	// validate palette Q pointer +	assert(pStrtPal >= palAllocData && pStrtPal <= palAllocData + NUM_PALETTES - 1); + +	// return next active palette in list +	while (++pStrtPal < palAllocData + NUM_PALETTES) { +		if (pStrtPal->objCount) +			// active palette found +			return pStrtPal; +	} + +	// non found +	return NULL; +} + +/** + * Sets the current background colour. + * @param colour			Colour to set the background to + */ +void SetBgndColour(COLORREF colour) { +	// update background colour struct +	bgndColour = colour; + +	// Q the change to the video DAC +	UpdateDACqueue(BGND_DAC_INDEX, 1, &bgndColour); +} + +/** + * Builds the translucent palette from the current backgrounds palette. + * @param hPalette			Handle to current background palette + */ +void CreateTranslucentPalette(SCNHANDLE hPalette) { +	// get a pointer to the palette +	PALETTE *pPal = (PALETTE *)LockMem(hPalette); + +	// leave background colour alone +	transPalette[0] = 0; + +	for (uint i = 0; i < FROM_LE_32(pPal->numColours); i++) { +		// get the RGB colour model values +		uint8 red   = GetRValue(pPal->palRGB[i]); +		uint8 green = GetGValue(pPal->palRGB[i]); +		uint8 blue  = GetBValue(pPal->palRGB[i]); + +		// calculate the Value field of the HSV colour model +		unsigned val = (red > green) ? red : green; +		val = (val > blue) ? val : blue; + +		// map the Value field to one of the 4 colours reserved for the translucent palette +		val /= 63; +		transPalette[i + 1] = (uint8)((val == 0) ? 0 : val + COL_HILIGHT - 1); +	} +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/palette.h b/engines/tinsel/palette.h new file mode 100644 index 0000000000..fdc4826dbd --- /dev/null +++ b/engines/tinsel/palette.h @@ -0,0 +1,144 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Palette Allocator Definitions + */ + +#ifndef TINSEL_PALETTE_H	// prevent multiple includes +#define TINSEL_PALETTE_H + +#include "tinsel/dw.h" + +namespace Tinsel { + +typedef	uint32	COLORREF; + +#define RGB(r,g,b)	((COLORREF)TO_LE_32(((uint8)(r)|((uint16)(g)<<8))|(((uint32)(uint8)(b))<<16))) + +#define GetRValue(rgb)	((uint8)(FROM_LE_32(rgb))) +#define GetGValue(rgb)	((uint8)(((uint16)(FROM_LE_32(rgb))) >> 8)) +#define GetBValue(rgb)	((uint8)((FROM_LE_32(rgb))>>16)) + +enum { +	MAX_COLOURS		= 256,	//!< maximum number of colours - for VGA 256 +	BITS_PER_PIXEL	= 8,	//!< number of bits per pixel for VGA 256 +	MAX_INTENSITY	= 255,	//!< the biggest value R, G or B can have +	NUM_PALETTES	= 3,	//!< number of palettes + +	// Discworld has some fixed apportioned bits in the palette. +	BGND_DAC_INDEX	= 0,	//!< index of background colour in Video DAC +	FGND_DAC_INDEX	= 1,	//!< index of first foreground colour in Video DAC +	TBLUE1			= 228,	//!< Blue used in translucent rectangles +	TBLUE2			= 229,	//!< Blue used in translucent rectangles +	TBLUE3			= 230,	//!< Blue used in translucent rectangles +	TBLUE4			= 231,	//!< Blue used in translucent rectangles +	TALKFONT_COL	= 233 +}; + +// some common colours + +#define	BLACK	(RGB(0, 0, 0)) +#define	WHITE	(RGB(MAX_INTENSITY, MAX_INTENSITY, MAX_INTENSITY)) +#define	RED		(RGB(MAX_INTENSITY, 0, 0)) +#define	GREEN	(RGB(0, MAX_INTENSITY, 0)) +#define	BLUE	(RGB(0, 0, MAX_INTENSITY)) +#define	YELLOW	(RGB(MAX_INTENSITY, MAX_INTENSITY, 0)) +#define	MAGENTA	(RGB(MAX_INTENSITY, 0, MAX_INTENSITY)) +#define	CYAN	(RGB(0, MAX_INTENSITY, MAX_INTENSITY)) + + +#include "common/pack-start.h"	// START STRUCT PACKING + +/** hardware palette structure */ +struct PALETTE { +	int32 numColours;		//!< number of colours in the palette +	COLORREF palRGB[MAX_COLOURS];	//!< actual palette colours +} PACKED_STRUCT; + +#include "common/pack-end.h"	// END STRUCT PACKING + + +/** palette queue structure */ +struct PALQ { +	SCNHANDLE hPal;		//!< handle to palette data struct +	int objCount;		//!< number of objects using this palette +	int posInDAC;		//!< palette position in the video DAC +	int numColours;		//!< number of colours in the palette +}; + + +#define	PALETTE_MOVED	0x8000	// when this bit is set in the "posInDAC" +				// field - the palette entry has moved + +// Translucent objects have NULL pPal +#define	HasPalMoved(pPal) (((pPal) != NULL) && ((pPal)->posInDAC & PALETTE_MOVED)) + + +/*----------------------------------------------------------------------*\ +|*			Palette Manager Function Prototypes		*| +\*----------------------------------------------------------------------*/ + +void ResetPalAllocator(void);	// wipe out all palettes + +#ifdef	DEBUG +void PaletteStats(void);	// Shows the maximum number of palettes used at once +#endif + +void PalettesToVideoDAC(void);	// Update the video DAC with palettes currently the the DAC queue + +void UpdateDACqueueHandle( +	int posInDAC,		// position in video DAC +	int numColours,		// number of colours in palette +	SCNHANDLE hPalette);	// handle to palette + +void UpdateDACqueue(		// places a palette in the video DAC queue +	int posInDAC,		// position in video DAC +	int numColours,		// number of colours in palette +	COLORREF *pColours);	// list of RGB tripples + +PALQ *AllocPalette(		// allocate a new palette +	SCNHANDLE hNewPal);	// palette to allocate + +void FreePalette(		// free a palette allocated with "AllocPalette" +	PALQ *pFreePal);	// palette queue entry to free + +PALQ *FindPalette(		// find a palette in the palette queue +	SCNHANDLE hSrchPal);	// palette to search for + +void SwapPalette(		// swaps palettes at the specified palette queue position +	PALQ *pPalQ,		// palette queue position +	SCNHANDLE hNewPal);	// new palette + +PALQ *GetNextPalette(		// returns the next palette in the queue +	PALQ *pStrtPal);	// queue position to start from - when NULL will start from beginning of queue + +COLORREF GetBgndColour(void);	// returns current background colour + +void SetBgndColour(		// sets current background colour +	COLORREF colour);	// colour to set the background to + +void CreateTranslucentPalette(SCNHANDLE BackPal); + +} // end of namespace Tinsel + +#endif	// TINSEL_PALETTE_H diff --git a/engines/tinsel/pcode.cpp b/engines/tinsel/pcode.cpp new file mode 100644 index 0000000000..023417fe3c --- /dev/null +++ b/engines/tinsel/pcode.cpp @@ -0,0 +1,593 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Virtual processor. + */ + +#include "tinsel/dw.h" +#include "tinsel/events.h"	// 'POINTED' etc. +#include "tinsel/handle.h"	// LockMem() +#include "tinsel/inventory.h"	// for inventory id's +#include "tinsel/pcode.h"	// opcodes etc. +#include "tinsel/scn.h"	// FindChunk() +#include "tinsel/serializer.h" +#include "tinsel/tinlib.h"	// Library routines + +#include "common/util.h" + +namespace Tinsel { + +//----------------- EXTERN FUNCTIONS -------------------- + +extern int CallLibraryRoutine(CORO_PARAM, int operand, int32 *pp, const INT_CONTEXT *pic, RESUME_STATE *pResumeState); + +//----------------- LOCAL DEFINES -------------------- + +/** list of all opcodes */ +enum OPCODE { +	OP_HALT = 0,	//!< end of program +	OP_IMM = 1,		//!< loads signed immediate onto stack +	OP_ZERO = 2,	//!< loads zero onto stack +	OP_ONE = 3,		//!< loads one onto stack +	OP_MINUSONE = 4,	//!< loads minus one onto stack +	OP_STR = 5,		//!< loads string offset onto stack +	OP_FILM = 6,	//!< loads film offset onto stack +	OP_FONT = 7,	//!< loads font offset onto stack +	OP_PAL = 8,		//!< loads palette offset onto stack +	OP_LOAD = 9,	//!< loads local variable onto stack +	OP_GLOAD = 10,	//!< loads global variable onto stack - long offset to variable +	OP_STORE = 11,	//!< pops stack and stores in local variable - long offset to variable +	OP_GSTORE = 12,	//!< pops stack and stores in global variable - long offset to variable +	OP_CALL = 13,	//!< procedure call +	OP_LIBCALL = 14,	//!< library procedure call - long offset to procedure +	OP_RET = 15,		//!< procedure return +	OP_ALLOC = 16,	//!< allocate storage on stack +	OP_JUMP = 17,	//!< unconditional jump	- signed word offset +	OP_JMPFALSE = 18,	//!< conditional jump	- signed word offset +	OP_JMPTRUE = 19,	//!< conditional jump	- signed word offset +	OP_EQUAL = 20,	//!< tests top two items on stack for equality +	OP_LESS,	//!< tests top two items on stack +	OP_LEQUAL,	//!< tests top two items on stack +	OP_NEQUAL,	//!< tests top two items on stack +	OP_GEQUAL,	//!< tests top two items on stack +	OP_GREAT = 25,	//!< tests top two items on stack +	OP_PLUS,	//!< adds top two items on stack and replaces with result +	OP_MINUS,	//!< subs top two items on stack and replaces with result +	OP_LOR,		//!< logical or of top two items on stack and replaces with result +	OP_MULT,	//!< multiplies top two items on stack and replaces with result +	OP_DIV = 30,		//!< divides top two items on stack and replaces with result +	OP_MOD,		//!< divides top two items on stack and replaces with modulus +	OP_AND,		//!< bitwise ands top two items on stack and replaces with result +	OP_OR,		//!< bitwise ors top two items on stack and replaces with result +	OP_EOR,		//!< bitwise exclusive ors top two items on stack and replaces with result +	OP_LAND = 35,	//!< logical ands top two items on stack and replaces with result +	OP_NOT,		//!< logical nots top item on stack +	OP_COMP,	//!< complements top item on stack +	OP_NEG,		//!< negates top item on stack +	OP_DUP,		//!< duplicates top item on stack +	OP_ESCON = 40,	//!< start of escapable sequence +	OP_ESCOFF = 41,	//!< end of escapable sequence +	OP_CIMM,	//!< loads signed immediate onto stack (special to case statements) +	OP_CDFILM	//!< loads film offset onto stack but not in current scene +}; + +// modifiers for the above opcodes +#define	OPSIZE8		0x40	//!< when this bit is set - the operand size is 8 bits +#define	OPSIZE16	0x80	//!< when this bit is set - the operand size is 16 bits + +#define	OPMASK		0x3F	//!< mask to isolate the opcode + + + +//----------------- LOCAL GLOBAL DATA -------------------- + +static int32 *pGlobals = 0;		// global vars + +static int numGlobals = 0;		// How many global variables to save/restore + +static INT_CONTEXT *icList = 0; + +/** + * Keeps the code array pointer up to date. + */ +void LockCode(INT_CONTEXT *ic) { +	if (ic->GSort == GS_MASTER) +		ic->code = (byte *)FindChunk(MASTER_SCNHANDLE, CHUNK_PCODE); +	else +		ic->code = (byte *)LockMem(ic->hCode); +} + +/** + * Find a free interpret context and allocate it to the calling process. + */ +static INT_CONTEXT *AllocateInterpretContext(GSORT gsort) { +	INT_CONTEXT *pic; +	int	i; + +	for (i = 0, pic = icList; i < MAX_INTERPRET; i++, pic++) { +		if (pic->GSort == GS_NONE) { +			pic->pProc = g_scheduler->getCurrentProcess(); +			pic->GSort = gsort; +			return pic; +		} +#ifdef DEBUG +		else { +			if (pic->pProc == g_scheduler->getCurrentProcess()) +				error("Found unreleased interpret context"); +		} +#endif +	} + +	error("Out of interpret contexts"); +} + +/** + * Normal release of an interpret context. + * Called from the end of Interpret(). + */ +static void FreeInterpretContextPi(INT_CONTEXT *pic) { +	pic->GSort = GS_NONE; +} + +/** + * Free interpret context owned by a dying process. + * Ensures that interpret contexts don't get lost when an Interpret() + * call doesn't complete. + */ +void FreeInterpretContextPr(PROCESS *pProc) { +	INT_CONTEXT *pic; +	int	i; + +	for (i = 0, pic = icList; i < MAX_INTERPRET; i++, pic++) { +		if (pic->GSort != GS_NONE && pic->pProc == pProc) { +			pic->GSort = GS_NONE; +			break; +		} +	} +} + +/** + * Free all interpret contexts except for the master script's + */ +void FreeMostInterpretContexts(void) { +	INT_CONTEXT *pic; +	int	i; + +	for (i = 0, pic = icList; i < MAX_INTERPRET; i++, pic++) { +		if (pic->GSort != GS_MASTER) { +			pic->GSort = GS_NONE; +		} +	} +} + +/** + * Free the master script's interpret context. + */ +void FreeMasterInterpretContext(void) { +	INT_CONTEXT *pic; +	int	i; + +	for (i = 0, pic = icList; i < MAX_INTERPRET; i++, pic++) 	{ +		if (pic->GSort == GS_MASTER) { +			pic->GSort = GS_NONE; +			return; +		} +	} +} + +/** + * Allocate and initialise an interpret context. + * Called from a process prior to Interpret(). + * @param gsort			which sort of code + * @param hCode			Handle to code to execute + * @param event			Causal event + * @param hpoly			Associated polygon (if any) + * @param actorId		Associated actor (if any) + * @param pinvo			Associated inventory object + */ +INT_CONTEXT *InitInterpretContext(GSORT gsort, SCNHANDLE hCode,	USER_EVENT event,  +		HPOLYGON hpoly, int actorid, INV_OBJECT *pinvo) { +	INT_CONTEXT *ic; + +	ic = AllocateInterpretContext(gsort); + +	// Previously parameters to Interpret() +	ic->hCode = hCode; +	LockCode(ic); +	ic->event = event; +	ic->hpoly = hpoly; +	ic->actorid = actorid; +	ic->pinvo = pinvo; + +	// Previously local variables in Interpret() +	ic->bHalt = false;		// set to exit interpeter +	ic->escOn = false; +	ic->myescEvent = 0;		// only initialised to prevent compiler warning! +	ic->sp = 0; +	ic->bp = ic->sp + 1; +	ic->ip = 0;			// start of code + +	ic->resumeState = RES_NOT; + +	return ic; +} + +/** + * Allocate and initialise an interpret context with restored data. + */ +INT_CONTEXT *RestoreInterpretContext(INT_CONTEXT *ric) { +	INT_CONTEXT *ic; + +	ic = AllocateInterpretContext(GS_NONE);	// Sort will soon be overridden + +	memcpy(ic, ric, sizeof(INT_CONTEXT)); +	ic->pProc = g_scheduler->getCurrentProcess(); +	ic->resumeState = RES_1; + +	LockCode(ic); + +	return ic; +} + +/** + * Allocates enough RAM to hold the global Glitter variables. + */ +void RegisterGlobals(int num) { +	if (pGlobals == NULL) { +		numGlobals = num; + +		// Allocate RAM for pGlobals and make sure it's allocated +		pGlobals = (int32 *)calloc(numGlobals, sizeof(int32)); +		if (pGlobals == NULL) { +			error("Cannot allocate memory for global data"); +		} + +		// Allocate RAM for interpret contexts and make sure it's allocated +		icList = (INT_CONTEXT *)calloc(MAX_INTERPRET, sizeof(INT_CONTEXT)); +		if (icList == NULL) { +			error("Cannot allocate memory for interpret contexts"); +		} + +		g_scheduler->setResourceCallback(FreeInterpretContextPr); +	} else { +		// Check size is still the same +		assert(numGlobals == num); + +		memset(pGlobals, 0, numGlobals * sizeof(int32)); +		memset(icList, 0, MAX_INTERPRET * sizeof(INT_CONTEXT)); +	} +} + +void FreeGlobals(void) { +	free(pGlobals); +	pGlobals = NULL; + +	free(icList); +	icList = NULL; +} + +/** + * (Un)serialize the global data for save/restore game. + */ +void syncGlobInfo(Serializer &s) { +	for (int i = 0; i < numGlobals; i++) { +		s.syncAsSint32LE(pGlobals[i]); +	} +} + +/** + * (Un)serialize an interpreter context for save/restore game. + */ +void INT_CONTEXT::syncWithSerializer(Serializer &s) { +	if (s.isLoading()) { +		// Null out the pointer fields +		pProc = NULL; +		code = NULL; +		pinvo = NULL; +	} +	// Write out used fields +	s.syncAsUint32LE(GSort); +	s.syncAsUint32LE(hCode); +	s.syncAsUint32LE(event); +	s.syncAsSint32LE(hpoly); +	s.syncAsSint32LE(actorid); + +	for (int i = 0; i < PCODE_STACK_SIZE; ++i) +		s.syncAsSint32LE(stack[i]); + +	s.syncAsSint32LE(sp); +	s.syncAsSint32LE(bp); +	s.syncAsSint32LE(ip); +	s.syncAsUint32LE(bHalt); +	s.syncAsUint32LE(escOn); +	s.syncAsSint32LE(myescEvent); +} + +/** + * Return pointer to and size of global data for save/restore game. + */ +void SaveInterpretContexts(INT_CONTEXT *sICInfo) { +	memcpy(sICInfo, icList, MAX_INTERPRET * sizeof(INT_CONTEXT)); +} + +/** + * Fetch (and sign extend, if necessary) a 8/16/32 bit value from the code + * stream and advance the instruction pointer accordingly. + */ +static int32 Fetch(byte opcode, byte *code, int &ip) { +	int32 tmp; +	if (opcode & OPSIZE8) { +		// Fetch and sign extend a 8 bit value to 32 bits. +		tmp = *(int8 *)(code + ip); +		ip += 1; +	} else if (opcode & OPSIZE16) { +		// Fetch and sign extend a 16 bit value to 32 bits. +		tmp = (int16)READ_LE_UINT16(code + ip); +		ip += 2; +	} else { +		// Fetch a 32 bit value. +		tmp = (int32)READ_LE_UINT32(code + ip); +		ip += 4; +	} +	return tmp; +} + +/** + * Interprets the PCODE instructions in the code array. + */ +void Interpret(CORO_PARAM, INT_CONTEXT *ic) { +	do { +		int tmp, tmp2; +		int ip = ic->ip; +		byte opcode = ic->code[ip++]; +		debug(7, "  Opcode %d (-> %d)", opcode, opcode & OPMASK); +		switch (opcode & OPMASK) { +		case OP_HALT:			// end of program + +			ic->bHalt = true; +			break; + +		case OP_IMM:			// loads immediate data onto stack +		case OP_STR:			// loads string handle onto stack +		case OP_FILM:			// loads film handle onto stack +		case OP_CDFILM:			// loads film handle onto stack +		case OP_FONT:			// loads font handle onto stack +		case OP_PAL:			// loads palette handle onto stack + +			ic->stack[++ic->sp] = Fetch(opcode, ic->code, ip); +			break; + +		case OP_ZERO:			// loads zero onto stack +			ic->stack[++ic->sp] = 0; +			break; + +		case OP_ONE:			// loads one onto stack +			ic->stack[++ic->sp] = 1; +			break; + +		case OP_MINUSONE:		// loads minus one onto stack +			ic->stack[++ic->sp] = -1; +			break; + +		case OP_LOAD:			// loads local variable onto stack + +			ic->stack[++ic->sp] = ic->stack[ic->bp + Fetch(opcode, ic->code, ip)]; +			break; + +		case OP_GLOAD:				// loads global variable onto stack + +			tmp = Fetch(opcode, ic->code, ip); +			assert(0 <= tmp && tmp < numGlobals); +			ic->stack[++ic->sp] = pGlobals[tmp]; +			break; + +		case OP_STORE:				// pops stack and stores in local variable + +			ic->stack[ic->bp + Fetch(opcode, ic->code, ip)] = ic->stack[ic->sp--]; +			break; + +		case OP_GSTORE:				// pops stack and stores in global variable + +			tmp = Fetch(opcode, ic->code, ip); +			assert(0 <= tmp && tmp < numGlobals); +			pGlobals[tmp] = ic->stack[ic->sp--]; +			break; + +		case OP_CALL:				// procedure call + +			tmp = Fetch(opcode, ic->code, ip); +			//assert(0 <= tmp && tmp < codeSize);	// TODO: Verify jumps are not out of bounds +			ic->stack[ic->sp + 1] = 0;	// static link +			ic->stack[ic->sp + 2] = ic->bp;	// dynamic link +			ic->stack[ic->sp + 3] = ip;	// return address +			ic->bp = ic->sp + 1;		// set new base pointer +			ip = tmp;	// set ip to procedure address +			break; + +		case OP_LIBCALL:		// library procedure or function call + +			tmp = Fetch(opcode, ic->code, ip); +			// NOTE: Interpret() itself is not using the coroutine facilities, +			// but still accepts a CORO_PARAM, so from the outside it looks +			// like a coroutine. In fact it may still acts as a kind of "proxy" +			// for some underlying coroutine. To enable this, we just pass on +			// 'coroParam' to CallLibraryRoutine(). If we then detect that +			// coroParam was set to a non-zero value, this means that some +			// coroutine code did run at some point, and we are now supposed +			// to sleep or die -- hence, we 'return' if coroParam != 0. +			// +			// This works because Interpret() is fully re-entrant: If we return +			// now and are later called again, then we will end up in the very +			// same spot (i.e. here). +			// +			// The reasons we do it this way, instead of turning Interpret into +			// a 'proper' coroutine are (1) we avoid implementation problems  +			// (CORO_INVOKE involves adding 'case' statements, but Interpret +			// already has a huge switch/case, so that would not work out of the +			// box), (2) we incurr less overhead, (3) it's easier to debug, +			// (4) it's simply cool ;). +			tmp2 = CallLibraryRoutine(coroParam, tmp, &ic->stack[ic->sp], ic, &ic->resumeState); +			if (coroParam) +				return; +			ic->sp += tmp2; +			LockCode(ic); +			break; + +		case OP_RET:			// procedure return + +			ic->sp = ic->bp - 1;		// restore stack +			ip = ic->stack[ic->sp + 3];	// return address +			ic->bp = ic->stack[ic->sp + 2];	// restore previous base pointer +			break; + +		case OP_ALLOC:			// allocate storage on stack + +			ic->sp += Fetch(opcode, ic->code, ip); +			break; + +		case OP_JUMP:	// unconditional jump + +			ip = Fetch(opcode, ic->code, ip); +			break; + +		case OP_JMPFALSE:	// conditional jump + +			tmp = Fetch(opcode, ic->code, ip); +			if (ic->stack[ic->sp--] == 0) { +				// condition satisfied - do the jump +				ip = tmp; +			} +			break; + +		case OP_JMPTRUE:	// conditional jump + +			tmp = Fetch(opcode, ic->code, ip); +			if (ic->stack[ic->sp--] != 0) { +				// condition satisfied - do the jump +				ip = tmp; +			} +			break; + +		case OP_EQUAL:			// tests top two items on stack for equality +		case OP_LESS:			// tests top two items on stack +		case OP_LEQUAL:			// tests top two items on stack +		case OP_NEQUAL:			// tests top two items on stack +		case OP_GEQUAL:			// tests top two items on stack +		case OP_GREAT:			// tests top two items on stack +		case OP_LOR:			// logical or of top two items on stack and replaces with result +		case OP_LAND:			// logical ands top two items on stack and replaces with result + +			// pop one operand +			ic->sp--; +			assert(ic->sp >= 0); +			tmp = ic->stack[ic->sp]; +			tmp2 = ic->stack[ic->sp + 1]; + +			// replace other operand with result of operation +			switch (opcode) { +			case OP_EQUAL:  tmp = (tmp == tmp2); break; +			case OP_LESS:   tmp = (tmp <  tmp2); break; +			case OP_LEQUAL: tmp = (tmp <= tmp2); break; +			case OP_NEQUAL: tmp = (tmp != tmp2); break; +			case OP_GEQUAL: tmp = (tmp >= tmp2); break; +			case OP_GREAT:  tmp = (tmp >  tmp2); break; + +			case OP_LOR:    tmp = (tmp || tmp2); break; +			case OP_LAND:   tmp = (tmp && tmp2); break; +			} + +			ic->stack[ic->sp] = tmp; +			break; + +		case OP_PLUS:			// adds top two items on stack and replaces with result +		case OP_MINUS:			// subs top two items on stack and replaces with result +		case OP_MULT:			// multiplies top two items on stack and replaces with result +		case OP_DIV:			// divides top two items on stack and replaces with result +		case OP_MOD:			// divides top two items on stack and replaces with modulus +		case OP_AND:			// bitwise ands top two items on stack and replaces with result +		case OP_OR:				// bitwise ors top two items on stack and replaces with result +		case OP_EOR:			// bitwise exclusive ors top two items on stack and replaces with result + +			// pop one operand +			ic->sp--; +			assert(ic->sp >= 0); +			tmp = ic->stack[ic->sp]; +			tmp2 = ic->stack[ic->sp + 1]; + +			// replace other operand with result of operation +			switch (opcode) { +			case OP_PLUS:   tmp += tmp2; break; +			case OP_MINUS:  tmp -= tmp2; break; +			case OP_MULT:   tmp *= tmp2; break; +			case OP_DIV:    tmp /= tmp2; break; +			case OP_MOD:    tmp %= tmp2; break; +			case OP_AND:    tmp &= tmp2; break; +			case OP_OR:     tmp |= tmp2; break; +			case OP_EOR:    tmp ^= tmp2; break; +			} +			ic->stack[ic->sp] = tmp; +			break; + +		case OP_NOT:			// logical nots top item on stack + +			ic->stack[ic->sp] = !ic->stack[ic->sp]; +			break; + +		case OP_COMP:			// complements top item on stack +			ic->stack[ic->sp] = ~ic->stack[ic->sp]; +			break; + +		case OP_NEG:			// negates top item on stack +			ic->stack[ic->sp] = -ic->stack[ic->sp]; +			break; + +		case OP_DUP:			// duplicates top item on stack +			ic->stack[ic->sp + 1] = ic->stack[ic->sp]; +			ic->sp++; +			break; + +		case OP_ESCON: +			ic->escOn = true; +			ic->myescEvent = GetEscEvents(); +			break; + +		case OP_ESCOFF: +			ic->escOn = false; +			break; + +		default: +			error("Interpret() - Unknown opcode"); +		} + +		// check for stack under-overflow +		assert(ic->sp >= 0 && ic->sp < PCODE_STACK_SIZE); +		ic->ip = ip; +	} while (!ic->bHalt); + +	// make sure stack is unwound +	assert(ic->sp == 0); + +	FreeInterpretContextPi(ic); +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/pcode.h b/engines/tinsel/pcode.h new file mode 100644 index 0000000000..1c7e0a942c --- /dev/null +++ b/engines/tinsel/pcode.h @@ -0,0 +1,155 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Virtual processor definitions + */ + +#ifndef TINSEL_PCODE_H     // prevent multiple includes +#define TINSEL_PCODE_H + +#include "tinsel/events.h"	// for USER_EVENT +#include "tinsel/sched.h"	// for PROCESS + +namespace Tinsel { + +// forward declaration +class Serializer; +struct INV_OBJECT; + +enum RESUME_STATE { +	RES_NOT, RES_1, RES_2 +}; + +enum { +	PCODE_STACK_SIZE	= 128	//!< interpeters stack size  +}; + +enum GSORT { +	GS_NONE, GS_ACTOR, GS_MASTER, GS_POLYGON, GS_INVENTORY, GS_SCENE +}; + +struct INT_CONTEXT { + +	// Elements for interpret context management +	PROCESS *pProc;			//!< processes owning this context +	GSORT	GSort;			//!< sort of this context + +	// Previously parameters to Interpret() +	SCNHANDLE	hCode;		//!< scene handle of the code to execute +	byte		*code;		//!< pointer to the code to execute +	USER_EVENT	event;		//!< causal event +	HPOLYGON	hpoly;		//!< associated polygon (if any) +	int			actorid;	//!< associated actor (if any) +	INV_OBJECT	*pinvo;		//!< associated inventory object + +	// Previously local variables in Interpret() +	int32 stack[PCODE_STACK_SIZE];	//!< interpeters run time stack +	int sp;				//!< stack pointer +	int bp;				//!< base pointer +	int ip;				//!< instruction pointer +	bool bHalt;			//!< set to exit interpeter +	bool escOn; +	int myescEvent;		//!< only initialised to prevent compiler warning! + +	RESUME_STATE resumeState; + +	void syncWithSerializer(Serializer &s); +}; + + +/*----------------------------------------------------------------------*\ +|*			Interpreter Function Prototypes			*| +\*----------------------------------------------------------------------*/ + +void Interpret(CORO_PARAM, INT_CONTEXT *ic);	// Interprets the PCODE instructions in the code array + +INT_CONTEXT *InitInterpretContext( +	GSORT		gsort, +	SCNHANDLE	hCode,		// code to execute +	USER_EVENT	event,		// causal event +	HPOLYGON	hpoly,		// associated polygon (if any) +	int		actorid,	// associated actor (if any) +	INV_OBJECT	*pinvo);		// associated inventory object + +INT_CONTEXT *RestoreInterpretContext(INT_CONTEXT *ric); + +void FreeMostInterpretContexts(void); +void FreeMasterInterpretContext(void); + +void SaveInterpretContexts(INT_CONTEXT *sICInfo); + +void RegisterGlobals(int num); +void FreeGlobals(void); + + +#define MAX_INTERPRET	(NUM_PROCESS - 20) + +/*----------------------------------------------------------------------*\ +|*	Library Procedure and Function codes parameter enums		*| +\*----------------------------------------------------------------------*/ + +#define TAG_DEF		0	// For tagactor() +#define TAG_Q1TO3	1	//	tag types +#define TAG_Q1TO4	2	//	tag types + +#define CONV_DEF	0	// +#define CONV_BOTTOM	1	// conversation() parameter +#define CONV_END	2	// + +#define CONTROL_OFF	0	// control()  +#define CONTROL_ON	1	// 	parameter +#define CONTROL_OFFV	2	// +#define CONTROL_OFFV2	3	// +#define CONTROL_STARTOFF 4	// + +#define NULL_ACTOR (-1)		// For actor parameters +#define LEAD_ACTOR (-2)		// + +#define RAND_NORM	0	// For random() frills +#define RAND_NORPT	1	// + +#define D_UP		1 +#define D_DOWN		0 + +#define TW_START	1	// topwindow() parameter +#define TW_END		2	// + +#define MIDI_DEF	0 +#define MIDI_LOOP	1 + +#define TRANS_DEF	0 +#define TRANS_CUT	1 +#define TRANS_FADE	2 + +#define FM_IN		0	// +#define FM_OUT		1	// fademidi() + +#define FG_ON		0	// +#define FG_OFF		1	// FrameGrab() + +#define ST_ON		0	// +#define ST_OFF		1	// SubTitles() + +} // end of namespace Tinsel + +#endif		// TINSEL_PCODE_H diff --git a/engines/tinsel/pdisplay.cpp b/engines/tinsel/pdisplay.cpp new file mode 100644 index 0000000000..b5488da3e8 --- /dev/null +++ b/engines/tinsel/pdisplay.cpp @@ -0,0 +1,652 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * CursorPositionProcess() + * TagProcess() + * PointProcess() + */ + +#include "tinsel/actors.h" +#include "tinsel/background.h" +#include "tinsel/cursor.h" +#include "tinsel/dw.h" +#include "tinsel/events.h" +#include "tinsel/font.h" +#include "tinsel/graphics.h" +#include "tinsel/multiobj.h" +#include "tinsel/object.h" +#include "tinsel/pcode.h" +#include "tinsel/polygons.h" +#include "tinsel/rince.h" +#include "tinsel/sched.h" +#include "tinsel/strres.h" +#include "tinsel/text.h" + +namespace Tinsel { + +//----------------- EXTERNAL GLOBAL DATA -------------------- + +#ifdef DEBUG +//extern int Overrun;		// The overrun counter, in DOS_DW.C + +extern int newestString;	// The overrun counter, in STRRES.C +#endif + + +//----------------- EXTERNAL FUNCTIONS --------------------- + +// in BG.C +extern int BackgroundWidth(void); +extern int BackgroundHeight(void); + + + +//----------------- LOCAL DEFINES -------------------- + +#define LPOSX	295		// X-co-ord of lead actor's position display +#define CPOSX	24		// X-co-ord of cursor's position display +#define OPOSX	SCRN_CENTRE_X	// X-co-ord of overrun counter's display +#define SPOSX	SCRN_CENTRE_X	// X-co-ord of string numbner's display + +#define POSY	0		// Y-co-ord of these position displays + +enum HotSpotTag { +	NO_HOTSPOT_TAG, +	POLY_HOTSPOT_TAG, +	ACTOR_HOTSPOT_TAG +}; + +//----------------- LOCAL GLOBAL DATA -------------------- + +static bool DispPath = false; +static bool bShowString = false; + +static int	TaggedActor = 0; +static HPOLYGON	hTaggedPolygon = NOPOLY; + +static enum { TAGS_OFF, TAGS_ON } TagsActive = TAGS_ON; + + +#ifdef DEBUG +/** + * Displays the cursor and lead actor's co-ordinates and the overrun + * counter. Also which path polygon the cursor is in, if required. + * + * This process is only started up if a Glitter showpos() call is made. + * Obviously, this is for testing purposes only... + */ +void CursorPositionProcess(CORO_PARAM, const void *) { +	// COROUTINE +	CORO_BEGIN_CONTEXT; +		int prevsX, prevsY;	// Last screen top left +		int prevcX, prevcY;	// Last displayed cursor position +		int prevlX, prevlY;	// Last displayed lead actor position +//		int prevOver;		// Last displayed overrun +		int prevString;		// Last displayed string number + +		OBJECT *cpText;		// cursor position text object pointer +		OBJECT *cpathText;	// cursor path text object pointer +		OBJECT *rpText;		// text object pointer +//		OBJECT *opText;		// text object pointer +		OBJECT *spText;		// string number text object pointer +	CORO_END_CONTEXT(_ctx); + +	CORO_BEGIN_CODE(_ctx); + +	_ctx->prevsX = -1; +	_ctx->prevsY = -1; +	_ctx->prevcX = -1; +	_ctx->prevcY = -1; +	_ctx->prevlX = -1; +	_ctx->prevlY = -1; +//	_ctx->prevOver = -1; +	_ctx->prevString = -1; + +	_ctx->cpText = NULL; +	_ctx->cpathText = NULL; +	_ctx->rpText = NULL; +//	_ctx->opText = NULL; +	_ctx->spText = NULL; + + +	int aniX, aniY;			// cursor/lead actor position +	int Loffset, Toffset;		// Screen top left + +	char PositionString[64];	// sprintf() things into here + +	PMACTOR pActor;		// Lead actor + +	while (1) { +		PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + +		/*-----------------------------------*\ +		| Cursor's position and path display. | +		\*-----------------------------------*/ +		GetCursorXY(&aniX, &aniY, false); + +		// Change in cursor position? +		if (aniX != _ctx->prevcX || aniY != _ctx->prevcY || +				Loffset != _ctx->prevsX || Toffset != _ctx->prevsY) { +			// kill current text objects +			if (_ctx->cpText) { +				MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->cpText); +			} +			if (_ctx->cpathText) { +				MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->cpathText); +				_ctx->cpathText = NULL; +			} + +			// New text objects +			sprintf(PositionString, "%d %d", aniX + Loffset, aniY + Toffset); +			_ctx->cpText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString, +						0, CPOSX, POSY, hTagFontHandle(), TXT_CENTRE); +			if (DispPath) { +				HPOLYGON hp = InPolygon(aniX + Loffset, aniY + Toffset, PATH); +				if (hp == NOPOLY) +					sprintf(PositionString, "No path"); +				else +					sprintf(PositionString, "%d,%d %d,%d %d,%d %d,%d", +						PolyCornerX(hp, 0), PolyCornerY(hp, 0), +						PolyCornerX(hp, 1), PolyCornerY(hp, 1), +						PolyCornerX(hp, 2), PolyCornerY(hp, 2), +						PolyCornerX(hp, 3), PolyCornerY(hp, 3)); +				_ctx->cpathText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString, +							0, 4, POSY+ 10, hTagFontHandle(), 0); +			} + +			// update previous position +			_ctx->prevcX = aniX; +			_ctx->prevcY = aniY; +		} + +#if 0 +		/*------------------------*\ +		| Overrun counter display. | +		\*------------------------*/ +		if (Overrun != _ctx->prevOver) { +			// kill current text objects +			if (_ctx->opText) { +				MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->opText); +			} + +			sprintf(PositionString, "%d", Overrun); +			_ctx->opText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString, +						0, OPOSX, POSY, hTagFontHandle(), TXT_CENTRE); + +			// update previous value +			_ctx->prevOver = Overrun; +		} +#endif + +		/*----------------------*\ +		| Lead actor's position. | +		\*----------------------*/ +		pActor = GetMover(LEAD_ACTOR); +		if (pActor && pActor->MActorState == NORM_MACTOR) { +			// get lead's animation position +			GetActorPos(LEAD_ACTOR, &aniX, &aniY); + +			// Change in position? +			if (aniX != _ctx->prevlX || aniY != _ctx->prevlY || +					Loffset != _ctx->prevsX || Toffset != _ctx->prevsY) { +				// Kill current text objects +				if (_ctx->rpText) { +					MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->rpText); +				} + +				// create new text object list +				sprintf(PositionString, "%d %d", aniX, aniY); +				_ctx->rpText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString, +								0, LPOSX, POSY,	hTagFontHandle(), TXT_CENTRE); + +				// update previous position +				_ctx->prevlX = aniX; +				_ctx->prevlY = aniY; +			} +		} + +		/*-------------*\ +		| String number	| +		\*-------------*/ +		if (bShowString && newestString != _ctx->prevString) { +			// kill current text objects +			if (_ctx->spText) { +				MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->spText); +			} + +			sprintf(PositionString, "String: %d", newestString); +			_ctx->spText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString, +						0, SPOSX, POSY+10, hTalkFontHandle(), TXT_CENTRE); + +			// update previous value +			_ctx->prevString = newestString; +		} + +		// update previous playfield position +		_ctx->prevsX = Loffset; +		_ctx->prevsY = Toffset; + +		CORO_SLEEP(1);		// allow re-scheduling +	} +	CORO_END_CODE; +} +#endif + +/** + * Tag process keeps us updated as to which tagged actor is currently tagged + * (if one is). Tag process asks us for this information, as does User_Event(). + */ +static void SaveTaggedActor(int ano) { +	TaggedActor = ano; +} + +/** + * Tag process keeps us updated as to which tagged actor is currently tagged + * (if one is). Tag process asks us for this information, as does User_Event(). + */ +int GetTaggedActor(void) { +	return TaggedActor; +} + +/** + * Tag process keeps us updated as to which polygon is currently tagged  + * (if one is). Tag process asks us for this information, as does User_Event(). + */ +static void SaveTaggedPoly(HPOLYGON hp) { +	hTaggedPolygon = hp; +} + +HPOLYGON GetTaggedPoly(void) { +	return hTaggedPolygon; +} + +/** + * Given cursor position and an actor number, ascertains whether the + * cursor is within the actor's tag area. + * Returns TRUE for a positive result, FALSE for negative. + * If TRUE, the mid-top co-ordinates of the actor's tag area are also + * returned. + */ +static bool InHotSpot(int ano, int aniX, int aniY, int *pxtext, int *pytext) { +	int	Top, Bot;		// Top and bottom limits of active area +	int	left, right;	// left and right of active area +	int	qrt = 0;		// 1/4 of height (sometimes 1/2) + +	// First check if within x-range +	if (aniX > (left = GetActorLeft(ano)) && aniX < (right = GetActorRight(ano))) { +		Top = GetActorTop(ano); +		Bot = GetActorBottom(ano); + +		// y-range varies according to tag-type +		switch (TagType(ano)) { +		case TAG_DEF: +			// Next to bottom 1/4 of the actor's area +			qrt = (Bot - Top) >> 1;		// Half actor's height +			Top += qrt;			// Top = mid-height + +			qrt = qrt >> 1;			// Quarter height +			Bot -= qrt;			// Bot = 1/4 way up +			break; + +		case TAG_Q1TO3: +			// Top 3/4 of the actor's area +			qrt = (Bot - Top) >> 2;		// 1/4 actor's height +			Bot -= qrt;			// Bot = 1/4 way up +			break; + +		case TAG_Q1TO4: +			// All the actor's area +			break; + +		default: +			error("illegal tag area type"); +		} + +		// Now check if within y-range +		if (aniY >= Top && aniY <= Bot) { +			if (TagType(ano) == TAG_Q1TO3) +				*pytext = Top + qrt; +			else +				*pytext = Top; +			*pxtext = (left + right) / 2; +			return true; +		} +	} +	return false; +} + +/** + * See if the cursor is over a tagged actor's hot-spot. If so, display + * the tag or, if tag already displayed, maintain the tag's position on + * the screen. + */ +static bool ActorTag(int curX, int curY, HotSpotTag *pTag, OBJECT **ppText) { +	static int Loffset = 0, Toffset = 0;	// Values when tag was displayed +	int	nLoff, nToff;		// new values, to keep tag in place +	int	ano; +	int	xtext, ytext; +	bool	newActor; + +	// For each actor with a tag.... +	FirstTaggedActor(); +	while ((ano = NextTaggedActor()) != 0) { +		if (InHotSpot(ano, curX, curY, &xtext, &ytext)) { +			// Put up or maintain actor tag +			if (*pTag != ACTOR_HOTSPOT_TAG) +				newActor = true; +			else if (ano != GetTaggedActor()) +				newActor = true;	// Different actor +			else +				newActor = false;	// Same actor + +			if (newActor) { +				// Display actor's tag + +				if (*ppText) +					MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), *ppText); + +				*pTag = ACTOR_HOTSPOT_TAG; +				SaveTaggedActor(ano);	// This actor tagged +				SaveTaggedPoly(NOPOLY);	// No tagged polygon + +				PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); +				LoadStringRes(GetActorTag(ano), tBufferAddr(), TBUFSZ); +				*ppText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), +							0, xtext - Loffset, ytext - Toffset, hTagFontHandle(), TXT_CENTRE); +				assert(*ppText); // Actor tag string produced NULL text +				MultiSetZPosition(*ppText, Z_TAG_TEXT); +			} else { +				// Maintain actor tag's position + +				PlayfieldGetPos(FIELD_WORLD, &nLoff, &nToff); +				if (nLoff != Loffset || nToff != Toffset) { +					MultiMoveRelXY(*ppText, Loffset - nLoff, Toffset - nToff); +					Loffset = nLoff; +					Toffset = nToff; +				} +			} +			return true; +		} +	} + +	// No tagged actor +	if (*pTag == ACTOR_HOTSPOT_TAG) { +		*pTag = NO_HOTSPOT_TAG; +		SaveTaggedActor(0); +	} +	return false; +} + +/** + * Perhaps some comment in due course. + * + * Under control of PointProcess(), when the cursor is over a TAG or + * EXIT polygon, its pointState flag is set to POINTING. If its Glitter + * code contains a printtag() call, its tagState flag gets set to TAG_ON. + */ +static bool PolyTag(HotSpotTag *pTag, OBJECT **ppText) { +	static int	Loffset = 0, Toffset = 0;	// Values when tag was displayed +	int		nLoff, nToff;		// new values, to keep tag in place +	HPOLYGON	hp; +	bool	newPoly; +	int	shift; + +	int	tagx, tagy;	// Tag display co-ordinates +	SCNHANDLE hTagtext;	// Tag text + +	// For each polgon with a tag.... +	for (int i = 0; i < MAX_POLY; i++) { +		hp = GetPolyHandle(i); + +		// Added code for un-tagged tags +		if (hp != NOPOLY && PolyPointState(hp) == POINTING && PolyTagState(hp) != TAG_ON) { +			// This poly is entitled to be tagged +			if (hp != GetTaggedPoly()) { +				if (*ppText) { +					MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), *ppText); +					*ppText = NULL; +				} +				*pTag = POLY_HOTSPOT_TAG; +				SaveTaggedActor(0);	// No tagged actor +				SaveTaggedPoly(hp);	// This polygon tagged +			} +			return true; +		} else if (hp != NOPOLY && PolyTagState(hp) == TAG_ON) { +			// Put up or maintain polygon tag +			if (*pTag != POLY_HOTSPOT_TAG) +				newPoly = true;		// A new polygon (no current) +			else if (hp != GetTaggedPoly()) +				newPoly = true;		// Different polygon +			else +				newPoly = false;	// Same polygon + +			if (newPoly) { +				if (*ppText) +					MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), *ppText); + +				*pTag = POLY_HOTSPOT_TAG; +				SaveTaggedActor(0);	// No tagged actor +				SaveTaggedPoly(hp);	// This polygon tagged + +				PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); +				getPolyTagInfo(hp, &hTagtext, &tagx, &tagy); + +				int strLen; +				if (PolyTagHandle(hp) != 0) +					strLen = LoadStringRes(PolyTagHandle(hp), tBufferAddr(), TBUFSZ); +				else  +					strLen = LoadStringRes(hTagtext, tBufferAddr(), TBUFSZ); + +				if (strLen == 0) +					// No valid string returned, so leave ppText as NULL +					ppText = NULL; +				else { +					// Handle displaying the tag text on-screen +					*ppText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), +							0, tagx - Loffset, tagy - Toffset, +							hTagFontHandle(), TXT_CENTRE); +					assert(*ppText); // Polygon tag string produced NULL text +					MultiSetZPosition(*ppText, Z_TAG_TEXT); +				 + +					/* +					* New feature: Don't go off the side of the background +					*/ +					shift = MultiRightmost(*ppText) + Loffset + 2; +					if (shift >= BackgroundWidth())			// Not off right +						MultiMoveRelXY(*ppText, BackgroundWidth() - shift, 0); +					shift = MultiLeftmost(*ppText) + Loffset - 1; +					if (shift <= 0)					// Not off left +						MultiMoveRelXY(*ppText, -shift, 0); +					shift = MultiLowest(*ppText) + Toffset; +					if (shift > BackgroundHeight())			// Not off bottom +						MultiMoveRelXY(*ppText, 0, BackgroundHeight() - shift); +				} +			} else { +				PlayfieldGetPos(FIELD_WORLD, &nLoff, &nToff); +				if (nLoff != Loffset || nToff != Toffset) { +					MultiMoveRelXY(*ppText, Loffset - nLoff, Toffset - nToff); +					Loffset = nLoff; +					Toffset = nToff; +				} +			} +			return true; +		} +	} + +	// No tagged polygon +	if (*pTag == POLY_HOTSPOT_TAG) { +		*pTag = NO_HOTSPOT_TAG; +		SaveTaggedPoly(NOPOLY); +	} +	return false; +} + +/** + * Handle display of tagged actor and polygon tags. + * Tagged actor's get priority over polygons. + */ +void TagProcess(CORO_PARAM, const void *) { +	// COROUTINE +	CORO_BEGIN_CONTEXT; +		OBJECT	*pText;	// text object pointer +		HotSpotTag Tag; +	CORO_END_CONTEXT(_ctx); + +	CORO_BEGIN_CODE(_ctx); +	 +	_ctx->pText = NULL; +	_ctx->Tag = NO_HOTSPOT_TAG; + +	SaveTaggedActor(0);		// No tagged actor yet +	SaveTaggedPoly(NOPOLY);		// No tagged polygon yet + +	while (1) { +		if (TagsActive == TAGS_ON) { +			int	curX, curY;	// cursor position +			while (!GetCursorXYNoWait(&curX, &curY, true)) +				CORO_SLEEP(1); + +			if (!ActorTag(curX, curY, &_ctx->Tag, &_ctx->pText) +					&& !PolyTag(&_ctx->Tag, &_ctx->pText)) { +				// Nothing tagged. Remove tag, if there is one +				if (_ctx->pText) { +					MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText); +					_ctx->pText = NULL; +				} +			} +		} else { +			SaveTaggedActor(0); +			SaveTaggedPoly(NOPOLY); + +			// Remove tag, if there is one +			if (_ctx->pText) { +				// kill current text objects +				MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText); +				_ctx->pText = NULL; +				_ctx->Tag = NO_HOTSPOT_TAG; +			} +		} + +		CORO_SLEEP(1);		// allow re-scheduling +	} + +	CORO_END_CODE; +} + +/** + * Called from PointProcess() as appropriate. + */ +static void enteringpoly(HPOLYGON hp) { +	SetPolyPointState(hp, POINTING); + +	RunPolyTinselCode(hp, POINTED, BE_NONE, false); +} + +/** + * Called from PointProcess() as appropriate. + */ +static void leavingpoly(HPOLYGON hp) { +	SetPolyPointState(hp, NOT_POINTING); + +	if (PolyTagState(hp) == TAG_ON) { +		// Delete this tag entry  +		SetPolyTagState(hp, TAG_OFF); +	} +} + +/** + * For TAG and EXIT polygons, monitor cursor entering and leaving. + * Maintain the polygons' pointState and tagState flags accordingly. + * Also run the polygon's Glitter code when the cursor enters. + */ +void PointProcess(CORO_PARAM, const void *) { +	// COROUTINE +	CORO_BEGIN_CONTEXT; +	CORO_END_CONTEXT(_ctx); + +	CORO_BEGIN_CODE(_ctx); + +	while (1) { +		int	aniX, aniY;	// cursor/tagged actor position +		while (!GetCursorXYNoWait(&aniX, &aniY, true)) +			CORO_SLEEP(1); + +		/*----------------------------------*\ +		| For polygons of type TAG and EXIT. | +		\*----------------------------------*/ +		for (int i = 0; i < MAX_POLY; i++) { +			HPOLYGON hp = GetPolyHandle(i); + +			if (hp != NOPOLY && (PolyType(hp) == TAG || PolyType(hp) == EXIT)) { +				if (PolyPointState(hp) == NOT_POINTING) { +					if (IsInPolygon(aniX, aniY, hp)) { +						enteringpoly(hp); +					} +				} else if (PolyPointState(hp) == POINTING) { +					if (!IsInPolygon(aniX, aniY, hp)) { +						leavingpoly(hp); +					} +				} +			} +		} + +		// allow re-scheduling +		CORO_SLEEP(1); +	} + +	CORO_END_CODE; +} + +void DisableTags(void) { +	TagsActive = TAGS_OFF; +} + +void EnableTags(void) { +	TagsActive = TAGS_ON; +} + +bool DisableTagsIfEnabled(void) { +	if (TagsActive == TAGS_OFF) +		return false; +	else { +		TagsActive = TAGS_OFF; +		return true; +	} +} + +/** + * For testing purposes only. + * Causes CursorPositionProcess() to display, or not, the path that the + * cursor is in. + */ +void TogglePathDisplay(void) { +	DispPath ^= 1;	// Toggle path display (XOR with true) +} + + +void setshowstring(void) { +	bShowString = true; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/pid.h b/engines/tinsel/pid.h new file mode 100644 index 0000000000..c2af1a5fcb --- /dev/null +++ b/engines/tinsel/pid.h @@ -0,0 +1,72 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * List of all process identifiers + */ + +#ifndef TINSEL_PID_H	// prevent multiple includes +#define TINSEL_PID_H + +namespace Tinsel { + +#define	PID_DESTROY	0x8000			// process id of any process that is to be destroyed between scenes + +#define	PID_EFFECTS	(0x0010 | PID_DESTROY)	// generic special effects process id +#define	PID_FLASH	(PID_EFFECTS + 1)	// flash colour process +#define	PID_CYCLE	(PID_EFFECTS + 2)	// cycle colour range process +#define	PID_MORPH	(PID_EFFECTS + 3)	// morph process +#define	PID_FADER	(PID_EFFECTS + 4)	// fader process +#define	PID_FADE_BGND	(PID_EFFECTS + 5)	// fade background colour process + +#define	PID_BACKGND	(0x0020 | PID_DESTROY)	// background update process id + +#define	PID_MOUSE	0x0030			// mouse button checking process id + +#define	PID_JOYSTICK	0x0040			// joystick button checking process id + +#define	PID_KEYBOARD	0x0050			// keyboard scanning process + +#define	PID_CURSOR	0x0060			// cursor process +#define	PID_CUR_TRAIL	(PID_CURSOR + 1)	// cursor trail process + +#define	PID_SCROLL	(0x0070 | PID_DESTROY)	// scroll process + +#define	PID_INVENTORY	0x0080			// inventory process + +#define	PID_POSITION	(0x0090 | PID_DESTROY)	// cursor position process + +#define	PID_TAG		(0x00A0 | PID_DESTROY)	// tag process + +#define	PID_TCODE	(0x00B0 | PID_DESTROY)	// tinsel code process + +#define	PID_MASTER_SCR	0x00C0			// tinsel master script process + +#define	PID_MACTOR	(0x00D0 | PID_DESTROY)	// moving actor process + +#define	PID_REEL	(0x00E0 | PID_DESTROY)	// process for each film reel + +#define	PID_MIDI	(0x00F0 | PID_DESTROY)	// process to poll MIDI sound driver + +} // end of namespace Tinsel + +#endif	// TINSEL_PID_H diff --git a/engines/tinsel/play.cpp b/engines/tinsel/play.cpp new file mode 100644 index 0000000000..e32fc88d3d --- /dev/null +++ b/engines/tinsel/play.cpp @@ -0,0 +1,507 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Plays films within a scene, takes into account the actor in each 'column'.								| + */ + +#include "tinsel/actors.h" +#include "tinsel/background.h" +#include "tinsel/dw.h" +#include "tinsel/film.h" +#include "tinsel/handle.h" +#include "tinsel/multiobj.h" +#include "tinsel/object.h" +#include "tinsel/pid.h" +#include "tinsel/polygons.h" +#include "tinsel/rince.h" +#include "tinsel/sched.h" +#include "tinsel/timers.h" +#include "tinsel/tinlib.h"	// stand() + +namespace Tinsel { + +/** + * Poke the background palette into an image. + */ +static void PokeInPalette(SCNHANDLE hMulFrame) { +	const FRAME *pFrame;		// Pointer to frame +	IMAGE *pim;		// Pointer to image + +	// Could be an empty column +	if (hMulFrame) { +		pFrame = (const FRAME *)LockMem(hMulFrame); + +		// get pointer to image +		pim = (IMAGE *)LockMem(READ_LE_UINT32(pFrame));	// handle to image + +		pim->hImgPal = TO_LE_32(BackPal()); +	} +} + + +int32 NoNameFunc(int actorID, bool bNewMover) { +	PMACTOR	pActor; +	int32	retval; + +	pActor = GetMover(actorID); + +	if (pActor != NULL && !bNewMover) { +		// If no path, just use first path in the scene +		if (pActor->hCpath == NOPOLY) +			retval = getPolyZfactor(FirstPathPoly()); +		else +			retval = getPolyZfactor(pActor->hCpath); +	} else { +		switch (actorMaskType(actorID)) { +		case ACT_DEFAULT: +			retval = 0; +			break; +		case ACT_MASK: +			retval = 0; +			break; +		case ACT_ALWAYS: +			retval = 10; +			break; +		default: +			retval = actorMaskType(actorID); +			break; +		} +	} + +	return retval; +} + +struct PPINIT { +	SCNHANDLE hFilm;	// The 'film' +	int16	x;			// } Co-ordinates from the play() +	int16	y;			// } - set to (-1, -1) if none. +	int16	z;			// normally 0, set if from restore +	int16	speed;		// Film speed +	int16 	actorid;	// Set if called from an actor code block +	uint8	splay;		// Set if called from splay() +	uint8	bTop;		// Set if called from topplay() +	int16	sf;			// SlowFactor - only used for moving actors +	int16	column;		// Column number, first column = 0 + +	uint8	escOn; +	int32	myescEvent; +}; + + +/** + * - Don't bother if this reel is already playing for this actor. + * - If explicit co-ordinates, use these, If embedded co-ordinates, + * leave alone, otherwise use actor's current position. + * - Moving actors get hidden during this play, other actors get + * _ctx->replaced by this play. + * - Column 0 of a film gets its appropriate Z-position, slave columns + * get slightly bigger Z-positions, in column order. + * - Play proceeds until the script finishes, another reel starts up for + * this actor, or the actor gets killed. + * - If called from an splay(), moving actor's co-ordinates are updated + * after the play, any walk still in progress will go on from there. + */ +void PlayReel(CORO_PARAM, const PPINIT *ppi) { +	CORO_BEGIN_CONTEXT; +		OBJECT	*pPlayObj;	// Object +		ANIM	thisAnim;	// Animation structure +	 +		bool	mActor;		// Gets set if this is a moving actor +		bool	lifeNoMatter; +		bool	replaced; +	 +		const FREEL *pfreel;	// The 'column' to play +		int		stepCount; +		int		frameCount; +		int		reelActor; +	CORO_END_CONTEXT(_ctx); + +	static int	firstColZ = 0;	// Z-position of column zero +	static int32	fColZfactor = 0;	// Z-factor of column zero's actor + +	CORO_BEGIN_CODE(_ctx); + +	const MULTI_INIT *pmi;		// MULTI_INIT structure +	PMACTOR	pActor; +	bool	bNewMover;	// Gets set if a moving actor that isn't in scene yet + +	const FILM *pfilm; + +	_ctx->lifeNoMatter = false; +	_ctx->replaced = false; +	pActor = NULL; +	bNewMover = false; + +	pfilm = (const FILM *)LockMem(ppi->hFilm); +	_ctx->pfreel = &pfilm->reels[ppi->column]; + +	// Get the MULTI_INIT structure +	pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(_ctx->pfreel->mobj)); + +	// Save actor's ID +	_ctx->reelActor = (int32)FROM_LE_32(pmi->mulID); + +	/**** New (experimental? bit 5/1/95 ****/ +	if (!actorAlive(_ctx->reelActor)) +		return; +	/**** Delete a bit down there if this stays ****/ + +	updateActorEsc(_ctx->reelActor, ppi->escOn, ppi->myescEvent); + +	// To handle the play()-talk(), talk()-play(), talk()-talk() and play()-play() scenarios +	if (ppi->hFilm != getActorLatestFilm(_ctx->reelActor)) { +		// This in not the last film scheduled for this actor + +		// It may be the last non-talk film though +		if (isActorTalking(_ctx->reelActor)) +			setActorPlayFilm(_ctx->reelActor, ppi->hFilm);	// Revert to this film after talk + +		return; +	} +	if (isActorTalking(_ctx->reelActor)) { +		// Note: will delete this and there'll be no need to store the talk film! +		if (ppi->hFilm != getActorTalkFilm(_ctx->reelActor)) { +			setActorPlayFilm(_ctx->reelActor, ppi->hFilm);	// Revert to this film after talk +			return; +		} +	} else { +		setActorPlayFilm(_ctx->reelActor, ppi->hFilm); +	} + +	// If this reel is already playing for this actor, just forget it. +	if (actorReel(_ctx->reelActor) == _ctx->pfreel) +		return; + +	// Poke in the background palette +	PokeInPalette(FROM_LE_32(pmi->hMulFrame)); + +	// Set up and insert the multi-object +	_ctx->pPlayObj = MultiInitObject(pmi); +	if (!ppi->bTop) +		MultiInsertObject(GetPlayfieldList(FIELD_WORLD), _ctx->pPlayObj); +	else +		MultiInsertObject(GetPlayfieldList(FIELD_STATUS), _ctx->pPlayObj); + +	// If co-ordinates are specified, use specified. +	// Otherwise, use actor's position if there are not embedded co-ords. +	// Add this first test for nth columns with offsets +	// in plays with (x,y) +	int tmpX, tmpY; +	tmpX = ppi->x; +	tmpY = ppi->y; +	if (ppi->column != 0 && (pmi->mulX || pmi->mulY)) { +	} else if (tmpX != -1 || tmpY != -1) { +		MultiSetAniXY(_ctx->pPlayObj, tmpX, tmpY); +	} else if (!pmi->mulX && !pmi->mulY) { +		GetActorPos(_ctx->reelActor, &tmpX, &tmpY); +		MultiSetAniXY(_ctx->pPlayObj, tmpX, tmpY); +	} + +	// If it's a moving actor, this hides the moving actor +	// used to do this only if (actorid == 0) - I don't know why +	_ctx->mActor = HideMovingActor(_ctx->reelActor, ppi->sf); + +	// If it's a moving actor, get its MACTOR structure. +	// If it isn't in the scene yet, get its task running - using +	// stand() - to prevent a glitch at the end of the play. +	if (_ctx->mActor) { +		pActor = GetMover(_ctx->reelActor); +		if (getMActorState(pActor) == NO_MACTOR) { +			stand(_ctx->reelActor, MAGICX, MAGICY, 0); +			bNewMover = true; +		} +	} + +	// Register the fact that we're playing this for this actor +	storeActorReel(_ctx->reelActor, _ctx->pfreel, ppi->hFilm, _ctx->pPlayObj, ppi->column, tmpX, tmpY); + +	/**** Will get rid of this if the above is kept ****/ +	// We may be temporarily resuscitating a dead actor +	if (ppi->actorid == 0 && !actorAlive(_ctx->reelActor)) +		_ctx->lifeNoMatter = true; + +	InitStepAnimScript(&_ctx->thisAnim, _ctx->pPlayObj,  FROM_LE_32(_ctx->pfreel->script), ppi->speed); + +	// If first column, set Z position as per +	// Otherwise, column 0's + column number +	// N.B. It HAS been ensured that the first column gets here first +	if (ppi->z != 0) { +		MultiSetZPosition(_ctx->pPlayObj, ppi->z); +		storeActorZpos(_ctx->reelActor, ppi->z); +	} else if (ppi->bTop) { +		if (ppi->column == 0) { +			firstColZ = Z_TOPPLAY + actorMaskType(_ctx->reelActor); +			MultiSetZPosition(_ctx->pPlayObj, firstColZ); +			storeActorZpos(_ctx->reelActor, firstColZ); +		} else { +			MultiSetZPosition(_ctx->pPlayObj, firstColZ + ppi->column); +			storeActorZpos(_ctx->reelActor, firstColZ + ppi->column); +		} +	} else if (ppi->column == 0) { +		if (_ctx->mActor && !bNewMover) { +			// If no path, just use first path in the scene +			if (pActor->hCpath == NOPOLY) +				fColZfactor = getPolyZfactor(FirstPathPoly()); +			else +				fColZfactor = getPolyZfactor(pActor->hCpath); +			firstColZ = AsetZPos(_ctx->pPlayObj, MultiLowest(_ctx->pPlayObj), fColZfactor); +		} else { +			switch (actorMaskType(_ctx->reelActor)) { +			case ACT_DEFAULT: +				fColZfactor = 0; +				firstColZ = 2; +				MultiSetZPosition(_ctx->pPlayObj, firstColZ); +				break; +			case ACT_MASK: +				fColZfactor = 0; +				firstColZ = MultiLowest(_ctx->pPlayObj); +				MultiSetZPosition(_ctx->pPlayObj, firstColZ); +				break; +			case ACT_ALWAYS: +				fColZfactor = 10; +				firstColZ = 10000; +				MultiSetZPosition(_ctx->pPlayObj, firstColZ); +				break; +			default: +				fColZfactor = actorMaskType(_ctx->reelActor); +				firstColZ = AsetZPos(_ctx->pPlayObj, MultiLowest(_ctx->pPlayObj), fColZfactor); +				if (firstColZ < 2) { +					// This is an experiment! +					firstColZ = 2; +					MultiSetZPosition(_ctx->pPlayObj, firstColZ); +				} +				break; +			} +		} +		storeActorZpos(_ctx->reelActor, firstColZ); +	} else { +		if (NoNameFunc(_ctx->reelActor, bNewMover) > fColZfactor) { +			fColZfactor = NoNameFunc(_ctx->reelActor, bNewMover); +			firstColZ = fColZfactor << 10; +		} +		MultiSetZPosition(_ctx->pPlayObj, firstColZ + ppi->column); +		storeActorZpos(_ctx->reelActor, firstColZ + ppi->column); +	} + +	/* +	 * Play until the script finishes, +	 * another reel starts up for this actor, +	 * or the actor gets killed. +	 */ +	_ctx->stepCount = 0; +	_ctx->frameCount = 0; +	do { +		if (_ctx->stepCount++ == 0) { +			_ctx->frameCount++; +			storeActorSteps(_ctx->reelActor, _ctx->frameCount); +		} +		if (_ctx->stepCount == ppi->speed) +			_ctx->stepCount = 0; + +		if (StepAnimScript(&_ctx->thisAnim) == ScriptFinished) +			break; + +		int x, y; +		GetAniPosition(_ctx->pPlayObj, &x, &y); +		storeActorPos(_ctx->reelActor, x, y); + +		CORO_SLEEP(1); + +		if (actorReel(_ctx->reelActor) != _ctx->pfreel) { +			_ctx->replaced = true; +			break; +		} + +		if (actorEsc(_ctx->reelActor) && actorEev(_ctx->reelActor) != GetEscEvents()) +			break; + +	} while (_ctx->lifeNoMatter || actorAlive(_ctx->reelActor)); + +	// Register the fact that we're NOT playing this for this actor +	if (actorReel(_ctx->reelActor) == _ctx->pfreel) +		storeActorReel(_ctx->reelActor, NULL, 0, NULL, 0, 0, 0); + +	// Ditch the object +	if (!ppi->bTop) +		MultiDeleteObject(GetPlayfieldList(FIELD_WORLD), _ctx->pPlayObj); +	else +		MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pPlayObj); + +	if (_ctx->mActor) { +		if (!_ctx->replaced) +			unHideMovingActor(_ctx->reelActor);	// Restore moving actor + +		// Update it's co-ordinates if this is an splay() +		if (ppi->splay) +			restoreMovement(_ctx->reelActor); +	} +	CORO_END_CODE; +} + +/** + * Run all animations that comprise the play film. + */ +static void playProcess(CORO_PARAM, const void *param) { +	// get the stuff copied to process when it was created +	PPINIT *ppi = (PPINIT *)param; + +	PlayReel(coroParam, ppi); +} + +// ******************************************************* + + +// To handle the play()-talk(), talk()-play(), talk()-talk() and play()-play() scenarios +void newestFilm(SCNHANDLE film, const FREEL *reel) { +	const MULTI_INIT *pmi;		// MULTI_INIT structure + +	// Get the MULTI_INIT structure +	pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(reel->mobj)); + +	setActorLatestFilm((int32)FROM_LE_32(pmi->mulID), film); +} + +// ******************************************************* + +/** + * Start up a play process for each column in a film. + * + * NOTE: The processes are started in reverse order so that the first + *   column's process kicks in first. + */ +void playFilm(SCNHANDLE film, int x, int y, int actorid, bool splay, int sfact, bool escOn,  +			  int myescEvent, bool bTop) { +	const FILM *pfilm = (const FILM *)LockMem(film); +	PPINIT ppi; + +	assert(film != 0); // Trying to play NULL film + +	// Now allowed empty films! +	if (pfilm->numreels == 0) +		return;                 // Nothing to do! + +	ppi.hFilm = film; +	ppi.x = x; +	ppi.y = y; +	ppi.z = 0; +	ppi.speed = (ONE_SECOND / FROM_LE_32(pfilm->frate)); +	ppi.actorid = actorid; +	ppi.splay = splay; +	ppi.bTop = bTop; +	ppi.sf = sfact; +	ppi.escOn = escOn; +	ppi.myescEvent = myescEvent; + +	// Start display process for each reel in the film +	for (int i = FROM_LE_32(pfilm->numreels) - 1; i >= 0; i--) { +		newestFilm(film, &pfilm->reels[i]); + +		ppi.column = i; +		g_scheduler->createProcess(PID_REEL, playProcess, &ppi, sizeof(PPINIT)); +	} +} + +/** + * Start up a play process for each slave column in a film. + * Play the first column directly from the parent process. + */ +void playFilmc(CORO_PARAM, SCNHANDLE film, int x, int y, int actorid, bool splay, int sfact,  +			   bool escOn, int myescEvent, bool bTop) { +	CORO_BEGIN_CONTEXT; +		PPINIT ppi; +	CORO_END_CONTEXT(_ctx); + +	CORO_BEGIN_CODE(_ctx); + +	assert(film != 0); // Trying to play NULL film +	const FILM *pfilm; +	 +	pfilm = (const FILM *)LockMem(film); + +	// Now allowed empty films! +	if (pfilm->numreels == 0) +		return;                 //  Already played to completion! + +	_ctx->ppi.hFilm = film; +	_ctx->ppi.x = x; +	_ctx->ppi.y = y; +	_ctx->ppi.z = 0; +	_ctx->ppi.speed = (ONE_SECOND / FROM_LE_32(pfilm->frate)); +	_ctx->ppi.actorid = actorid; +	_ctx->ppi.splay = splay; +	_ctx->ppi.bTop = bTop; +	_ctx->ppi.sf = sfact; +	_ctx->ppi.escOn = escOn; +	_ctx->ppi.myescEvent = myescEvent; + +	// Start display process for each secondary reel in the film +	for (int i = FROM_LE_32(pfilm->numreels) - 1; i > 0; i--) { +		newestFilm(film, &pfilm->reels[i]); + +		_ctx->ppi.column = i; +		g_scheduler->createProcess(PID_REEL, playProcess, &_ctx->ppi, sizeof(PPINIT)); +	} + +	newestFilm(film, &pfilm->reels[0]); + +	_ctx->ppi.column = 0; +	CORO_INVOKE_1(PlayReel, &_ctx->ppi); + +	CORO_END_CODE; +} + +/** + * Start up a play process for a particular column in a film. + * + * NOTE: This is specifically for actors during a restore scene. + */ +void playThisReel(SCNHANDLE film, short reelnum, short z, int x, int y) { +	const FILM *pfilm = (const FILM *)LockMem(film); +	PPINIT ppi; + +	ppi.hFilm = film; +	ppi.x = x; +	ppi.y = y; +	ppi.z = z; +	ppi.speed = (ONE_SECOND / FROM_LE_32(pfilm->frate)); +	ppi.actorid = 0; +	ppi.splay = false; +	ppi.bTop = false; +	ppi.sf = 0; +	ppi.column = reelnum; + +	// FIXME: The PlayReel play loop was previously breaking out, and then deleting objects, when  +	// returning to a scene because escOn and myescEvent were undefined. Need to make sure whether  +	// restored objects should have any particular combination of these two values +	ppi.escOn = false; +	ppi.myescEvent = GetEscEvents(); + +	assert(pfilm->numreels); + +	newestFilm(film, &pfilm->reels[reelnum]); + +	// Start display process for the reel +	g_scheduler->createProcess(PID_REEL, playProcess, &ppi, sizeof(ppi)); +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/polygons.cpp b/engines/tinsel/polygons.cpp new file mode 100644 index 0000000000..d73e290277 --- /dev/null +++ b/engines/tinsel/polygons.cpp @@ -0,0 +1,1862 @@ +/* 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. + * + * $URL$ + * $Id$ + */ + +#include "tinsel/actors.h" +#include "tinsel/font.h" +#include "tinsel/handle.h" +#include "tinsel/polygons.h" +#include "tinsel/rince.h" +#include "tinsel/serializer.h" +#include "tinsel/token.h" + +#include "common/util.h" + +namespace Tinsel { + + +//----------------- LOCAL DEFINES -------------------- + +/** different types of polygon */ +enum POLY_TYPE { +	POLY_PATH, POLY_NPATH, POLY_BLOCK, POLY_REFER, POLY_EFFECT, +	POLY_EXIT, POLY_TAG +}; + + +// Note 7/10/94, with adjacency reduction ANKHMAP max is 3, UNSEEN max is 4 +// so reduced this back to 6 (from 12) for now. +#define MAXADJ	6	// Max number of known adjacent paths + +struct POLYGON { + +	PTYPE	polytype;	// Polygon type + +	int	subtype;	// refer type in REFER polygons +				// NODE/NORMAL in PATH polygons + +	int	pIndex;		// Index into compiled polygon data + +	/* +	 * Data duplicated from compiled polygon data +	 */ +	short	cx[4];		// Corners (clockwise direction) +	short	cy[4]; +	int	polyID; + +	/* For TAG and EXIT (and EFFECT in future?) polygons only   */ +	TSTATE	tagState; +	PSTATE	pointState; +	SCNHANDLE oTagHandle;	// Override tag. + +	/* For Path polygons only  */ +	bool	tried; + +	/* +	 * Internal derived data for speed and conveniance +	 * set up by FiddlyBit() +	 */ +	short	ptop;		// +	short	pbottom;	// Enclosing external rectangle +	short	pleft;		// +	short	pright;		// + +	short	ltop[4];	// +	short	lbottom[4];	// Rectangles enclosing each side +	short	lleft[4];	// +	short	lright[4];	// + +	int	a[4];		// y1-y2       } +	int	b[4];		// x2-x1       } See IsInPolygon() +	long	c[4];		// y1x2 - x1y2 } +       +	/* +	 * Internal derived data for speed and conveniance +	 * set up by PseudoCentre() +	 */ +	int	pcentrex;	// Pseudo-centre +	int	pcentrey;	// + +	/** +	 * List of adjacent polygons. For Path polygons only. +	 * set up by SetPathAdjacencies() +	 */ +	POLYGON *adjpaths[MAXADJ]; + +}; + + +#define MAXONROUTE 40 + +#include "common/pack-start.h"	// START STRUCT PACKING + +/** lineinfo struct - one per (node-1) in a node path */ +struct LINEINFO { + +	int32	a; +	int32	b; +	int32	c; + +	int32	a2;             //!< a squared +	int32	b2;             //!< b squared +	int32	a2pb2;          //!< a squared + b squared +	int32	ra2pb2;         //!< root(a squared + b squared) + +	int32	ab; +	int32	ac; +	int32	bc; +} PACKED_STRUCT; + +/** polygon struct - one per polygon */ +struct POLY { +	int32 type;			//!< type of polygon +	int32 x[4], y[4];	// Polygon definition + +	int32 tagx, tagy;	// } For tagged polygons +	SCNHANDLE hTagtext;	// } i.e. EXIT, TAG, EFFECT + +	int32 nodex, nodey;	// EXIT, TAG, REFER +	SCNHANDLE hFilm;	//!< film reel handle for EXIT, TAG + +	int32 reftype;		//!< Type of REFER + +	int32 id;			// } EXIT and TAG + +	int32 scale1, scale2;	// } +	int32 reel;			// } PATH and NPATH +	int32 zFactor;		// } + +	//The arrays now stored externally +	int32 nodecount;		//!<The number of nodes in this polygon +	int32 pnodelistx,pnodelisty;	//!<offset in chunk to this array if present +	int32 plinelist; +	 +	SCNHANDLE hScript;	//!< handle of code segment for polygon events +} PACKED_STRUCT; + +#include "common/pack-end.h"	// END STRUCT PACKING + + +//----------------- LOCAL GLOBAL DATA -------------------- + +static int MaxPolys = MAX_POLY; + +static POLYGON *Polys[MAX_POLY+1]; + +static POLYGON *Polygons = 0; + +static SCNHANDLE pHandle = 0;	// } Set at start of each scene +static int noofPolys = 0;		// } + +static POLYGON extraBlock;	// Used for dynamic blocking + +static int pathsOnRoute = 0; +static const POLYGON *RoutePaths[MAXONROUTE]; + +static POLYGON *RouteEnd = 0; + +#ifdef DEBUG +int highestYet = 0; +#endif + + + +//----------------- LOCAL MACROS -------------------- + +// The str parameter is no longer used +#define CHECK_HP_OR(mvar, str) assert((mvar >= 0 && mvar <= noofPolys) || mvar == MAX_POLY); +#define CHECK_HP(mvar, str)	assert(mvar >= 0 && mvar <= noofPolys); + +static HPOLYGON PolyIndex(const POLYGON *pp) { +	for (int j = 0; j <= MAX_POLY; j++) { +		if (Polys[j] == pp) +			return j; +	} + +	error("PolyIndex(): polygon not found"); +	return NOPOLY; +} + +/** + * Returns TRUE if the point is within the polygon supplied. + * + * Firstly, the point must be within the smallest imaginary rectangle + * which encloses the polygon. + * + * Then, from each corner of the polygon, if the point is within an + * imaginary rectangle enclosing the clockwise-going side from that + * corner, the gradient of a line from the corner to the point must be + * less than (or more negative than) the gradient of that side: + * + * If the corners' coordinates are designated (x1, y1) and (x2, y2), and + * the point in question's (xt, yt), then: + *     gradient (x1,y1)->(x2,y2) > gradient (x1,y1)->(xt,yt) + *               (y1-y2)/(x2-x1) > (y1-yt)/(xt-x1) + *               (y1-y2)*(xt-x1) > (y1-yt)*(x2-x1) + *        xt(y1-y2) -x1y1 + x1y2 > -yt(x2-x1) + y1x2 - x1y1 + *         xt(y1-y2) + yt(x2-x1) > y1x2 - x1y2 + * + * If the point passed one of the four 'side tests', and failed none, + * then it must be within the polygon. If the point was not tested, it + * may be within the internal rectangle not covered by the above tests. + * + * Most polygons contain an internal rectangle which does not fall into + * any of the above side-related tests. Such a rectangle will always + * have two polygon corners above it and two corners to the left of it. + */ +bool IsInPolygon(int xt, int yt, HPOLYGON hp) { +	const POLYGON *pp; +	int	i; +	bool BeenTested = false; +	int	pl = 0, pa = 0; + +	CHECK_HP_OR(hp, "Out of range polygon handle (1)"); +	pp = Polys[hp]; +	assert(pp != NULL); // Testing whether in a NULL polygon + +	/* Is point within the external rectangle? */ +	if (xt < pp->pleft || xt > pp->pright || yt < pp->ptop || yt > pp->pbottom) +		return false; + +	// For each corner/side +	for (i = 0; i < 4; i++)	{ +		// If within this side's 'testable' area +		// i.e. within the width of the line in y direction of end of line +		// or within the height of the line in x direction of end of line +		if ((xt >= pp->lleft[i] && xt <= pp->lright[i]  && ((yt > pp->cy[i]) == (pp->cy[(i+1)%4] > pp->cy[i]))) +		 || (yt >= pp->ltop[i]  && yt <= pp->lbottom[i] && ((xt > pp->cx[i]) == (pp->cx[(i+1)%4] > pp->cx[i])))) { +			if (((long)xt*pp->a[i] + (long)yt*pp->b[i]) < pp->c[i]) +				return false; +			else +				BeenTested = true; +		} +	} + +	if (BeenTested) { +		// New dodgy code 29/12/94 +		if (pp->polytype == BLOCKING) { +			// For each corner/side +			for (i = 0; i < 4; i++) { +				// Pretend the corners of blocking polys are not in the poly. +				if (xt == pp->cx[i] && yt == pp->cy[i]) +					return false; +			} +		} +		return true; +	} else { +		// Is point within the internal rectangle? +		for (i = 0; i < 4; i++) { +			if (pp->cx[i] < xt) +				pl++; +			if (pp->cy[i] < yt) +				pa++; +		} + +		if (pa == 2 && pl == 2) +			return true; +		else +			return false; +	} +} + +/** + * Finds a polygon of the specified type containing the supplied point. + */ + +HPOLYGON InPolygon(int xt, int yt, PTYPE type) { +	for (int j = 0; j <= MAX_POLY; j++)	{ +		if (Polys[j] && Polys[j]->polytype == type) { +			if (IsInPolygon(xt, yt, j)) +				return j; +		} +	} +	return NOPOLY; +} + +/** + * Given a blocking polygon, current co-ordinates of an actor, and the + * co-ordinates of where the actor is heading, works out which corner of + * the blocking polygon to head around. + */ + +void BlockingCorner(HPOLYGON hp, int *x, int *y, int tarx, int tary) { +	const POLYGON *pp; +	int	i; +	int	xd, yd;		// distance per axis +	int	ThisD, SmallestD = 1000; +	int	D1, D2; +	int	NearestToHere = 1000, NearestToTarget; +	unsigned At = 10;	// Corner already at + +	int	bcx[4], bcy[4];	// Bogus corners + +	CHECK_HP_OR(hp, "Out of range polygon handle (2)"); +	pp = Polys[hp]; + +	// Work out a point outside each corner +	for (i = 0; i < 4; i++)	{ +		int	next, prev; + +		// X-direction +		next = pp->cx[i] - pp->cx[(i+1)%4]; +		prev = pp->cx[i] - pp->cx[(i+3)%4]; +		if (next <= 0 && prev <= 0) +			bcx[i] = pp->cx[i] - 4;		// Both points to the right +		else if (next >= 0 && prev >= 0) +			bcx[i] = pp->cx[i] + 4;		// Both points to the left +		else +			bcx[i] = pp->cx[i]; + +		// Y-direction +		next = pp->cy[i] - pp->cy[(i+1)%4]; +		prev = pp->cy[i] - pp->cy[(i+3)%4]; +		if (next <= 0 && prev <= 0) +			bcy[i] = pp->cy[i] - 4;		// Both points below +		else if (next >= 0 && prev >= 0) +			bcy[i] = pp->cy[i] + 4;		// Both points above +		else +			bcy[i] = pp->cy[i]; +	} + +	// Find nearest corner to where we are, +	// but not the one we're stood at. + +	for (i = 0; i < 4; i++) {		// For 4 corners +//		ThisD = ABS(*x - pp->cx[i]) + ABS(*y - pp->cy[i]); +		ThisD = ABS(*x - bcx[i]) + ABS(*y - bcy[i]); +		if (ThisD < SmallestD) { +			// Ignore this corner if it's not in a path +			if (InPolygon(pp->cx[i], pp->cy[i], PATH) == NOPOLY || +			    InPolygon(bcx[i], bcy[i], PATH) == NOPOLY) +				continue; + +			// Are we stood at this corner? +			if (ThisD > 4) { +				// No - it's the nearest we've found yet. +				NearestToHere = i; +				SmallestD = ThisD; +			} else { +				// Stood at/next to this corner +				At = i; +			} +		} +	} + +	// If we're not already at a corner, go to the nearest corner + +	if (At == 10) { +		// Not stood at a corner +//		assert(NearestToHere != 1000); // At blocking corner, not found near corner! +		// Better to give up than to assert fail! +		if (NearestToHere == 1000) { +			// Send it to where it is now +			// i.e. leave x and y alone +		} else { +			*x = bcx[NearestToHere]; +			*y = bcy[NearestToHere]; +		} +	} else { +		// Already at a corner. Go to an adjacent corner. +		// First, find out which adjacent corner is nearest the target. +			xd = ABS(tarx - pp->cx[(At + 1) % 4]); +			yd = ABS(tary - pp->cy[(At + 1) % 4]); +			D1 = xd + yd; +			xd = ABS(tarx - pp->cx[(At + 3) % 4]); +			yd = ABS(tary - pp->cy[(At + 3) % 4]); +			D2 = xd + yd; +			NearestToTarget = (D2 > D1) ? (At + 1) % 4 : (At + 3) % 4; +		if (NearestToTarget == NearestToHere) { +			*x = bcx[NearestToHere]; +			*y = bcy[NearestToHere]; +		} else { +			// Need to decide whether it's better to go to the nearest to +			// here and then on to the target, or to the nearest to the +			// target and on from there. +			xd = ABS(pp->cx[At] - pp->cx[NearestToHere]); +			D1 = xd; +			xd = ABS(pp->cx[NearestToHere] - tarx); +			D1 += xd; + +			yd = ABS(pp->cy[At] - pp->cy[NearestToHere]); +			D1 += yd; +			yd = ABS(pp->cy[NearestToHere] - tary); +			D1 += yd; + +			xd = ABS(pp->cx[At] - pp->cx[NearestToTarget]); +			D2 = xd; +			xd = ABS(pp->cx[NearestToTarget] - tarx); +			D2 += xd; + +			yd = ABS(pp->cy[At] - pp->cy[NearestToTarget]); +			D2 += yd; +			yd = ABS(pp->cy[NearestToTarget] - tary); +			D2 += yd; + +			if (D2 > D1) { +				*x = bcx[NearestToHere]; +				*y = bcy[NearestToHere]; +			} else { +				*x = bcx[NearestToTarget]; +				*y = bcy[NearestToTarget]; +			} +		} +	} +} + + +/** + * Try do drop a perpendicular to each inter-node line from the point + * and remember the shortest (if any). + * Find which node is nearest to the point. + * The shortest of these gives the best point in the node path. +*/ +void FindBestPoint(HPOLYGON hp, int *x, int *y, int *pline) { +	const POLYGON *pp; + +	uint8	*pps;		// Compiled polygon data +	const POLY *ptp;		// Compiled polygon data +	int	dropD;		// length of perpendicular (i.e. distance of point from line) +	int	dropX, dropY;	// (X, Y) where dropped perpendicular intersects the line +	int	d1, d2;		// distance from perpendicular intersect to line's end nodes +	int32	*nlistx, *nlisty; + +	int	shortestD = 10000;	// Shortest distance found +	int	nearestL = -1;		// Nearest line +	int	nearestN;		// Nearest Node + +	int	h = *x;		// For readability/conveniance +	int	k = *y;		//	- why aren't these #defines? +	LINEINFO *llist;		// Inter-node line structure + +	CHECK_HP(hp, "Out of range polygon handle (3)"); +	pp = Polys[hp]; + +	// Pointer to polygon data +	pps = LockMem(pHandle);	// All polygons +	ptp = (const POLY *)pps + pp->pIndex;	// This polygon + +	nlistx = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelistx)); +	nlisty = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelisty)); +	llist = (LINEINFO *)(pps + (int)FROM_LE_32(ptp->plinelist)); + +	// Look for fit of perpendicular to lines between nodes +	for (int i = 0; i < (int)FROM_LE_32(ptp->nodecount) - 1; i++) { +		const int32	a = (int)FROM_LE_32(llist[i].a); +		const int32	b = (int)FROM_LE_32(llist[i].b); +		const int32	c = (int)FROM_LE_32(llist[i].c); + +#if 1 +		if (true) { +			//printf("a %d, b %d, c %d,  a^2+b^2 = %d\n", a, b, c, a*a+b*b); + +			// TODO: If the comments of the LINEINFO struct are correct, then it contains mostly +			// duplicate data, probably in an effort to safe CPU cycles. Even on the slowest devices +			// we support, calculatin a product of two ints is not an issue. +			// So we can just load & endian convert a,b,c, then replace stuff like +			//   (int)FROM_LE_32(line->ab) +			// by simply a*b, which makes it easier to understand what the code does, too. +			// Just in case there is some bugged data, I leave this code here for verifying it. +			// Let's leave it in for some time. +			// +			// One bad thing: We use sqrt to compute a square root. Might not be a good idea, +			// speed wise. Maybe we should take Vicent's fp_sqroot. But that's a problem for later. + +			LINEINFO *line = &llist[i]; +			int32	a2 = (int)FROM_LE_32(line->a2);             //!< a squared +			int32	b2 = (int)FROM_LE_32(line->b2);             //!< b squared +			int32	a2pb2 = (int)FROM_LE_32(line->a2pb2);          //!< a squared + b squared +			int32	ra2pb2 = (int)FROM_LE_32(line->ra2pb2);         //!< root(a squared + b squared) +		 +			int32	ab = (int)FROM_LE_32(line->ab); +			int32	ac = (int)FROM_LE_32(line->ac); +			int32	bc = (int)FROM_LE_32(line->bc); + +			assert(a*a == a2); +			assert(b*b == b2); +			assert(a*b == ab); +			assert(a*c == ac); +			assert(b*c == bc); + +			assert(a2pb2 == a*a + b*b); +			assert(ra2pb2 == (int)sqrt((float)a*a + (float)b*b)); +		} +#endif + + +		if (a == 0 && b == 0) +			continue;		// Line is just a point! + +		// X position of dropped perpendicular intersection with line +		dropX = ((b*b * h) - (a*b * k) - a*c) / (a*a + b*b); + +		// X distances from intersection to end nodes +		d1 = dropX - (int)FROM_LE_32(nlistx[i]); +		d2 = dropX - (int)FROM_LE_32(nlistx[i+1]); + +		// if both -ve or both +ve, no fit +		if ((d1 < 0 && d2 < 0) || (d1 > 0 && d2 > 0)) +			continue; +//#if 0 +		// Y position of sidweays perpendicular intersection with line +		dropY = ((a*a * k) - (a*b * h) - b*c) / (a*a + b*b); + +		// Y distances from intersection to end nodes +		d1 = dropY - (int)FROM_LE_32(nlisty[i]); +		d2 = dropY - (int)FROM_LE_32(nlisty[i+1]); + +		// if both -ve or both +ve, no fit +		if ((d1 < 0 && d2 < 0) || (d1 > 0 && d2 > 0)) +			continue; +//#endif +		dropD = ((a * h) + (b * k) + c) / (int)sqrt((float)a*a + (float)b*b); +		dropD = ABS(dropD); +		if (dropD < shortestD) { +			shortestD = dropD; +			nearestL = i; +		} +	} + +	// Distance to nearest node +	nearestN = NearestNodeWithin(hp, h, k); +	dropD = ABS(h - (int)FROM_LE_32(nlistx[nearestN])) + ABS(k - (int)FROM_LE_32(nlisty[nearestN])); + +	// Go to a node or a point on a line +	if (dropD < shortestD) { +		// A node is nearest +		*x = (int)FROM_LE_32(nlistx[nearestN]); +		*y = (int)FROM_LE_32(nlisty[nearestN]); +		*pline = nearestN; +	} else { +		assert(nearestL != -1); + +		// A point on a line is nearest +		const int32	a = (int)FROM_LE_32(llist[nearestL].a); +		const int32	b = (int)FROM_LE_32(llist[nearestL].b); +		const int32	c = (int)FROM_LE_32(llist[nearestL].c); +		dropX = ((b*b * h) - (a*b * k) - a*c) / (a*a + b*b); +		dropY = ((a*a * k) - (a*b * h) - b*c) / (a*a + b*b); +		*x = dropX; +		*y = dropY; +		*pline = nearestL; +	} + +	assert(IsInPolygon(*x, *y, hp)); // Nearest point is not in polygon(!) +} + +/** + * Returns TRUE if two paths are asdjacent. + */ +bool IsAdjacentPath(HPOLYGON hPath1, HPOLYGON hPath2) { +	const POLYGON *pp1, *pp2; + +	CHECK_HP(hPath1, "Out of range polygon handle (4)"); +	CHECK_HP(hPath2, "Out of range polygon handle (500)"); + +	if (hPath1 == hPath2) +		return true; + +	pp1 = Polys[hPath1]; +	pp2 = Polys[hPath2]; + +	for (int j = 0; j < MAXADJ; j++) +		if (pp1->adjpaths[j] == pp2) +			return true; + +	return false; +} + +static const POLYGON *TryPath(POLYGON *last, POLYGON *whereto, POLYGON *current) { +	POLYGON *x; + +	// For each path adjacent to this one +	for (int j = 0; j < MAXADJ; j++) { +		x = current->adjpaths[j];	// call the adj. path x +		if (x == whereto) { +			RoutePaths[pathsOnRoute++] = x; +			return x;		// Got there! +		} + +		if (x == NULL) +			break;			// no more adj. paths to look at + +		if (x->tried) +			continue;		// don't double back + +		if (x == last) +			continue;		// don't double back + +		x->tried = true; +		if (TryPath(current, whereto, x) != NULL) { +			RoutePaths[pathsOnRoute++] = x; +			assert(pathsOnRoute < MAXONROUTE); +			return x;		// Got there in this direction +		} else +			x->tried = false; +	} + +	return NULL; +} + + +/** + * Sort out the first path to head to for the imminent leg of a walk. + */ +static HPOLYGON PathOnTheWay(HPOLYGON from, HPOLYGON to) { +	// TODO: Fingolfin says: This code currently uses DFS (depth first search), +	// in the TryPath function, to compute a path between 'from' and 'to'.  +	// However, a BFS (breadth first search) might yield more natural results, +	// at least in cases where there are multiple possible paths. +	// There is a small risk of regressions caused by such a change, though. +	// +	// Also, the overhead of computing a DFS again and again could be avoided +	// by computing a path matrix (like we do in the SCUMM engine). +	int	i; + +	CHECK_HP(from, "Out of range polygon handle (501a)"); +	CHECK_HP(to, "Out of range polygon handle (501b)"); + +	if (IsAdjacentPath(from, to)) +		return to; + +	for (i = 0; i < MAX_POLY; i++) {		// For each polygon.. +		POLYGON *p = Polys[i]; +		if (p && p->polytype == PATH)	//...if it's a path  +			p->tried = false; +	} +	Polys[from]->tried = true; +	pathsOnRoute = 0; + +	const POLYGON *p = TryPath(Polys[from], Polys[to], Polys[from]); + +	assert(p != NULL); // Trying to find route between unconnected paths + +	// Don't go a roundabout way to an adjacent path. +	for (i = 0; i < pathsOnRoute; i++) { +		CHECK_HP(PolyIndex(RoutePaths[i]), "Out of range polygon handle (502)"); +		if (IsAdjacentPath(from, PolyIndex(RoutePaths[i]))) +			return PolyIndex(RoutePaths[i]); +	} +	return PolyIndex(p); +} + +/** + * Indirect method of calling PathOnTheWay(), to put the burden of + * recursion onto the main stack. + */ +HPOLYGON getPathOnTheWay(HPOLYGON hFrom, HPOLYGON hTo) { +	CHECK_HP(hFrom, "Out of range polygon handle (6)"); +	CHECK_HP(hTo, "Out of range polygon handle (7)"); + +	// Reuse already computed result +	if (RouteEnd == Polys[hTo]) { +		for (int i = 0; i < pathsOnRoute; i++) { +			CHECK_HP(PolyIndex(RoutePaths[i]), "Out of range polygon handle (503)"); +			if (IsAdjacentPath(hFrom, PolyIndex(RoutePaths[i]))) { +				return PolyIndex(RoutePaths[i]); +			} +		} +	} + +	RouteEnd = Polys[hTo]; +	return PathOnTheWay(hFrom, hTo); +} + + +/** + * Given a node path, work out which end node is nearest the given point. + */ + +int NearestEndNode(HPOLYGON hPath, int x, int y) { +	const POLYGON *pp; + +	int	d1, d2; +	uint8	*pps;		// Compiled polygon data +	const POLY *ptp;		// Pointer to compiled polygon data +	int32	*nlistx, *nlisty; + +	CHECK_HP(hPath, "Out of range polygon handle (8)"); +	pp = Polys[hPath]; + +	pps = LockMem(pHandle);		// All polygons +	ptp = (const POLY *)pps + pp->pIndex;		// This polygon + +	nlistx = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelistx)); +	nlisty = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelisty)); +	 +	const int nodecount = (int)FROM_LE_32(ptp->nodecount); + +	d1 = ABS(x - (int)FROM_LE_32(nlistx[0])) + ABS(y - (int)FROM_LE_32(nlisty[0])); +	d2 = ABS(x - (int)FROM_LE_32(nlistx[nodecount - 1])) + ABS(y - (int)FROM_LE_32(nlisty[nodecount - 1])); + +	return (d2 > d1) ? 0 : nodecount - 1; +} + + +/** + * Given a start path and a destination path, find which pair of end + * nodes is nearest together. + * Return which node in the start path is part of the closest pair. + */ + +int NearEndNode(HPOLYGON hSpath, HPOLYGON hDpath) { +	const POLYGON *pSpath, *pDpath; + +	int	ns, nd;		// 'top' nodes in each path +	int	dist, NearDist; +	int	NearNode; +	uint8	*pps;		// Compiled polygon data +	const POLY *ps, *pd;		// Pointer to compiled polygon data +	int32	*snlistx, *snlisty; +	int32	*dnlistx, *dnlisty; + +	CHECK_HP(hSpath, "Out of range polygon handle (9)"); +	CHECK_HP(hDpath, "Out of range polygon handle (10)"); +	pSpath = Polys[hSpath]; +	pDpath = Polys[hDpath]; + +	pps = LockMem(pHandle);		// All polygons +	ps = (const POLY *)pps + pSpath->pIndex;	// Start polygon +	pd = (const POLY *)pps + pDpath->pIndex;	// Dest polygon + +	ns = (int)FROM_LE_32(ps->nodecount) - 1; +	nd = (int)FROM_LE_32(pd->nodecount) - 1; + +	snlistx = (int32 *)(pps + (int)FROM_LE_32(ps->pnodelistx)); +	snlisty = (int32 *)(pps + (int)FROM_LE_32(ps->pnodelisty)); +	dnlistx = (int32 *)(pps + (int)FROM_LE_32(pd->pnodelistx)); +	dnlisty = (int32 *)(pps + (int)FROM_LE_32(pd->pnodelisty)); + +	// start[0] to dest[0] +	NearDist = ABS((int)FROM_LE_32(snlistx[0]) - (int)FROM_LE_32(dnlistx[0])) + ABS((int)FROM_LE_32(snlisty[0]) - (int)FROM_LE_32(dnlisty[0])); +	NearNode = 0; + +	// start[0] to dest[top] +	dist = ABS((int)FROM_LE_32(snlistx[0]) - (int)FROM_LE_32(dnlistx[nd])) + ABS((int)FROM_LE_32(snlisty[0]) - (int)FROM_LE_32(dnlisty[nd])); +	if (dist < NearDist) +		NearDist = dist; + +	// start[top] to dest[0] +	dist = ABS((int)FROM_LE_32(snlistx[ns]) - (int)FROM_LE_32(dnlistx[0])) + ABS((int)FROM_LE_32(snlisty[ns]) - (int)FROM_LE_32(dnlisty[0])); +	if (dist < NearDist) { +		NearDist = dist; +		NearNode = ns; +	} + +	// start[top] to dest[top] +	dist = ABS((int)FROM_LE_32(snlistx[ns]) - (int)FROM_LE_32(dnlistx[nd])) + ABS((int)FROM_LE_32(snlisty[ns]) - (int)FROM_LE_32(dnlisty[nd])); +	if (dist < NearDist) { +		NearNode = ns; +	} + +	return NearNode; +} + +/** + * Given a follow nodes path and a co-ordinate, finds which node in the + * path is nearest to the co-ordinate. + */ +int NearestNodeWithin(HPOLYGON hNpath, int x, int y) { +	int	ThisDistance, SmallestDistance = 1000; +	int	NumNodes;	// Number of nodes in this follow nodes path +	int	NearestYet = 0;	// Number of nearest node +	uint8	*pps;		// Compiled polygon data +	const POLY *ptp;		// Pointer to compiled polygon data +	int32	*nlistx, *nlisty; + +	CHECK_HP(hNpath, "Out of range polygon handle (11)"); + +	pps = LockMem(pHandle);		// All polygons +	ptp = (const POLY *)pps + Polys[hNpath]->pIndex;	// This polygon + +	nlistx = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelistx)); +	nlisty = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelisty)); + +	NumNodes = (int)FROM_LE_32(ptp->nodecount); + +	for (int i = 0; i < NumNodes; i++) { +		ThisDistance = ABS(x - (int)FROM_LE_32(nlistx[i])) + ABS(y - (int)FROM_LE_32(nlisty[i])); + +		if (ThisDistance < SmallestDistance) { +			NearestYet = i; +			SmallestDistance = ThisDistance; +		} +	} + +	return NearestYet; +} + +/** + * Given a point and start and destination paths, find the nearest + * corner (if any) of the start path which is within the destination + * path. If there is no such corner, find the nearest corner of the + * destination path which falls within the source path. + */ +void NearestCorner(int *x, int *y, HPOLYGON hStartPoly, HPOLYGON hDestPoly) { +	const POLYGON *psp, *pdp; +	int	j;	 +	int	ncorn = 0;			// nearest corner +	HPOLYGON hNpath = NOPOLY;	// path containing nearest corner +	int ThisD, SmallestD = 1000; + +	CHECK_HP(hStartPoly, "Out of range polygon handle (12)"); +	CHECK_HP(hDestPoly, "Out of range polygon handle (13)"); + +	psp = Polys[hStartPoly]; +	pdp = Polys[hDestPoly]; + +	// Nearest corner of start path in destination path. + +	for (j = 0; j < 4; j++)	{ +		if (IsInPolygon(psp->cx[j], psp->cy[j], hDestPoly)) { +			ThisD = ABS(*x - psp->cx[j]) + ABS(*y - psp->cy[j]); +			if (ThisD < SmallestD) {   +				hNpath = hStartPoly; +				ncorn = j; +				// Try to ignore it if virtually stood on it +				if (ThisD > 4) +					SmallestD = ThisD; +			} +		} +	} +	if (SmallestD == 1000) { +		// Nearest corner of destination path in start path. +		for (j = 0; j < 4; j++) { +			if (IsInPolygon(pdp->cx[j], pdp->cy[j], hStartPoly)) { +				ThisD = ABS(*x - pdp->cx[j]) + ABS(*y - pdp->cy[j]); +				if (ThisD < SmallestD) {   +					hNpath = hDestPoly; +					ncorn = j; +					// Try to ignore it if virtually stood on it +					if (ThisD > 4) +						SmallestD = ThisD; +				} +			} +		} +	} + +	if (hNpath != NOPOLY) { +		*x = Polys[hNpath]->cx[ncorn]; +		*y = Polys[hNpath]->cy[ncorn]; +	} else +		error("NearestCorner() failure"); +} + +bool IsPolyCorner(HPOLYGON hPath, int x, int y) { +	CHECK_HP(hPath, "Out of range polygon handle (37)"); + +	for (int i = 0; i < 4; i++)	{ +		if (Polys[hPath]->cx[i] == x && Polys[hPath]->cy[i] == y) +			return true; +	} +	return false; +} + +/** + * Given a path polygon and a Y co-ordinate, return a scale value. + */ +int GetScale(HPOLYGON hPath, int y) { +	const POLY *ptp;	// Pointer to compiled polygon data +	int	zones;		// Number of different scales +	int	zlen;		// Depth of each scale zone +	int	scale; +	int	top; + +	// To try and fix some unknown potential bug +	if (hPath == NOPOLY) +		return SCALE_LARGE; + +	CHECK_HP(hPath, "Out of range polygon handle (14)"); + +	ptp = (const POLY *)LockMem(pHandle) + Polys[hPath]->pIndex; + +	// Path is of a constant scale? +	if (FROM_LE_32(ptp->scale2) == 0) +		return FROM_LE_32(ptp->scale1); + +	assert(FROM_LE_32(ptp->scale1) >= FROM_LE_32(ptp->scale2)); + +	zones = FROM_LE_32(ptp->scale1) - FROM_LE_32(ptp->scale2) + 1; +	zlen = (Polys[hPath]->pbottom - Polys[hPath]->ptop) / zones; + +	scale = FROM_LE_32(ptp->scale1); +	top = Polys[hPath]->ptop; + +	do { +		top += zlen; +		if (y < top) +			return scale; +	} while (--scale); + +	return FROM_LE_32(ptp->scale2); +} + +/** + * Give the co-ordinates of a node in a node path. + */ +void getNpathNode(HPOLYGON hNpath, int node, int *px, int *py) { +	uint8	*pps;		// Compiled polygon data +	const POLY *ptp;		// Pointer to compiled polygon data +	int32	*nlistx, *nlisty; + +	CHECK_HP(hNpath, "Out of range polygon handle (15)"); +	assert(Polys[hNpath] != NULL && Polys[hNpath]->polytype == PATH && Polys[hNpath]->subtype == NODE); // must be given a node path! + +	pps = LockMem(pHandle);		// All polygons +	ptp = (const POLY *)pps + Polys[hNpath]->pIndex;	// This polygon + +	nlistx = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelistx)); +	nlisty = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelisty)); + +	// Might have just walked to the node from above. +	if (node == (int)FROM_LE_32(ptp->nodecount)) +		node -= 1; + +	*px = (int)FROM_LE_32(nlistx[node]); +	*py = (int)FROM_LE_32(nlisty[node]); +} + +/** + * Get tag text handle and tag co-ordinates of a polygon. + */ + +void getPolyTagInfo(HPOLYGON hp, SCNHANDLE *hTagText, int *tagx, int *tagy) { +	const POLY *pp;	// Pointer to compiled polygon data + +	CHECK_HP(hp, "Out of range polygon handle (16)"); + +	pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex; + +	*tagx = (int)FROM_LE_32(pp->tagx); +	*tagy = (int)FROM_LE_32(pp->tagy); +	*hTagText = FROM_LE_32(pp->hTagtext); +} + +/** + * Get polygon's film reel handle. + */ + +SCNHANDLE getPolyFilm(HPOLYGON hp) { +	const POLY *pp;	// Pointer to compiled polygon data + +	CHECK_HP(hp, "Out of range polygon handle (17)"); + +	pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex; + +	return FROM_LE_32(pp->hFilm); +} + +/** + * Get polygon's associated node. + */ + +void getPolyNode(HPOLYGON hp, int *px, int *py) { +	const POLY *pp;	// Pointer to compiled polygon data + +	CHECK_HP(hp, "Out of range polygon handle (18)"); + +	pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex; + +	*px = (int)FROM_LE_32(pp->nodex); +	*py = (int)FROM_LE_32(pp->nodey); +} + +/** + * Get handle to polygon's glitter code. + */ + +SCNHANDLE getPolyScript(HPOLYGON hp) { +	const POLY *pp;	// Pointer to compiled polygon data + +	CHECK_HP(hp, "Out of range polygon handle (19)"); + +	pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex; + +	return FROM_LE_32(pp->hScript); +} + +REEL getPolyReelType(HPOLYGON hp) { +	const POLY *pp;	// Pointer to compiled polygon data + +	// To try and fix some unknown potential bug (toyshop entrance) +	if (hp == NOPOLY) +		return REEL_ALL; + +	CHECK_HP(hp, "Out of range polygon handle (20)"); + +	pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex; + +	return (REEL)FROM_LE_32(pp->reel); +} + +int32 getPolyZfactor(HPOLYGON hp) { +	const POLY *pp;	// Pointer to compiled polygon data + +	CHECK_HP(hp, "Out of range polygon handle (21)"); +	assert(Polys[hp] != NULL); + +	pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex; + +	return (int)FROM_LE_32(pp->zFactor); +} + +int numNodes(HPOLYGON hp) { +	const POLY *pp;	// Pointer to compiled polygon data + +	CHECK_HP(hp, "Out of range polygon handle (22)"); +	assert(Polys[hp] != NULL); + +	pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex; + +	return (int)FROM_LE_32(pp->nodecount); +} + +// ************************************************************************* +// +// Code concerned with killing and reviving TAG and EXIT polygons. +// And code to enable this information to be saved and restored. +// +// ************************************************************************* + +struct TAGSTATE	{ +	int tid; +	bool enabled; +}; + +#define MAX_SCENES	256 +#define MAX_TAGS	2048 +#define MAX_EXITS	512 + +static struct { +	SCNHANDLE sid; +	int	nooftags; +	int	offset; +} SceneTags[MAX_SCENES], SceneExits[MAX_SCENES]; + +static TAGSTATE TagStates[MAX_TAGS]; +static TAGSTATE ExitStates[MAX_EXITS]; + +static int nextfreeT = 0, numScenesT = 0; +static int nextfreeE = 0, numScenesE = 0; + +static int currentTScene = 0; +static int currentEScene = 0; + +bool deadPolys[MAX_POLY];	// Currently just for dead blocks + +void RebootDeadTags(void) { +	nextfreeT = numScenesT = 0; +	nextfreeE = numScenesE = 0; +	 +	memset(SceneTags, 0, sizeof(SceneTags)); +	memset(SceneExits, 0, sizeof(SceneExits)); +	memset(TagStates, 0, sizeof(TagStates)); +	memset(ExitStates, 0, sizeof(ExitStates)); +	memset(deadPolys, 0, sizeof(deadPolys)); +} + +/** + * (Un)serialize the dead tag and exit data for save/restore game. + */ +void syncPolyInfo(Serializer &s) { +	int i; + +	for (i = 0; i < MAX_SCENES; i++) { +		s.syncAsUint32LE(SceneTags[i].sid); +		s.syncAsSint32LE(SceneTags[i].nooftags); +		s.syncAsSint32LE(SceneTags[i].offset); +	} + +	for (i = 0; i < MAX_SCENES; i++) { +		s.syncAsUint32LE(SceneExits[i].sid); +		s.syncAsSint32LE(SceneExits[i].nooftags); +		s.syncAsSint32LE(SceneExits[i].offset); +	} + +	for (i = 0; i < MAX_TAGS; i++) { +		s.syncAsUint32LE(TagStates[i].tid); +		s.syncAsSint32LE(TagStates[i].enabled); +	} + +	for (i = 0; i < MAX_EXITS; i++) { +		s.syncAsUint32LE(ExitStates[i].tid); +		s.syncAsSint32LE(ExitStates[i].enabled); +	} + +	s.syncAsSint32LE(nextfreeT); +	s.syncAsSint32LE(numScenesT); +	s.syncAsSint32LE(nextfreeE); +	s.syncAsSint32LE(numScenesE); +} + +/** + * This is all totally different to the way the rest of the way polygon + * data is stored and restored, more specifically, different to how dead + * tags and exits are handled, because of the piecemeal design-by-just- + * thought-of-this approach employed. + */ + +void SaveDeadPolys(bool *sdp) { +	memcpy(sdp, deadPolys, MAX_POLY*sizeof(bool)); +} + +void RestoreDeadPolys(bool *sdp) { +	memcpy(deadPolys, sdp, MAX_POLY*sizeof(bool)); +} + +/** + * Convert a BLOCKING to an EX_BLOCK poly. + */ +void DisableBlock(int blockno) { +	for (int i = 0; i < MAX_POLY; i++) { +		if (Polys[i] && Polys[i]->polytype == BLOCKING && Polys[i]->polyID == blockno) { +			Polys[i]->polytype = EX_BLOCK; +			deadPolys[i] = true; +		} +	} +} + +/** + * Convert an EX_BLOCK to a BLOCKING poly. + */ +void EnableBlock(int blockno) { +	for (int i = 0; i < MAX_POLY; i++) { +		if (Polys[i] && Polys[i]->polytype == EX_BLOCK && Polys[i]->polyID == blockno) { +			Polys[i]->polytype = BLOCKING; +			deadPolys[i] = false; +		} +	} +} + +/** + * Convert an EX_TAG to a TAG poly. + */ +void EnableTag(int tagno) { +	for (int i = 0; i < MAX_POLY; i++) { +		if (Polys[i] && Polys[i]->polytype == EX_TAG && Polys[i]->polyID == tagno) { +			Polys[i]->polytype = TAG; +		} +	} + +	TAGSTATE *pts; +	pts = &TagStates[SceneTags[currentTScene].offset]; +	for (int j = 0; j < SceneTags[currentTScene].nooftags; j++, pts++) { +		if (pts->tid == tagno) { +			pts->enabled = true; +			break; +		} +	} +} + +/** + * Convert an EX_EXIT to a EXIT poly. + */ +void EnableExit(int exitno) { +	for (int i = 0; i < MAX_POLY; i++) { +		if (Polys[i] && Polys[i]->polytype == EX_EXIT && Polys[i]->polyID == exitno) { +			Polys[i]->polytype = EXIT; +		} +	} + +	TAGSTATE *pts; +	pts = &ExitStates[SceneExits[currentEScene].offset]; +	for (int j = 0; j < SceneExits[currentEScene].nooftags; j++, pts++) { +		if (pts->tid == exitno) { +			pts->enabled = true; +			break; +		} +	} +} + +/** + * Convert a TAG to an EX_TAG poly. + */ +void DisableTag(int tagno) { +	TAGSTATE *pts; + +	for (int i = 0; i < MAX_POLY; i++) { +		if (Polys[i] && Polys[i]->polytype == TAG && Polys[i]->polyID == tagno) { +			Polys[i]->polytype = EX_TAG; +			Polys[i]->tagState = TAG_OFF; +			Polys[i]->pointState = NOT_POINTING; +		} +	} + +	pts = &TagStates[SceneTags[currentTScene].offset]; +	for (int j = 0; j < SceneTags[currentTScene].nooftags; j++, pts++) { +		if (pts->tid == tagno) { +			pts->enabled = false; +			break; +		} +	} +} + +/** + * Convert a EXIT to an EX_EXIT poly. + */ +void DisableExit(int exitno) { +	TAGSTATE *pts; + +	for (int i = 0; i < MAX_POLY; i++) { +		if (Polys[i] && Polys[i]->polytype == EXIT && Polys[i]->polyID == exitno) { +			Polys[i]->polytype = EX_EXIT; +			Polys[i]->tagState = TAG_OFF; +			Polys[i]->pointState = NOT_POINTING; +		} +	} + +	pts = &ExitStates[SceneExits[currentEScene].offset]; +	for (int j = 0; j < SceneExits[currentEScene].nooftags; j++, pts++) { +		if (pts->tid == exitno) { +			pts->enabled = false; +			break; +		} +	} +} + +HPOLYGON FirstPathPoly(void) { +	for (int i = 0; i < noofPolys; i++) { +		if (Polys[i]->polytype == PATH) +			return i; +	} +	error("FirstPathPoly() - no PATH polygons!"); +	return NOPOLY; +} + +HPOLYGON GetPolyHandle(int i) { +	assert(i >= 0 && i <= MAX_POLY); + +	return (Polys[i] != NULL) ? i : NOPOLY; +} + +// ************************************************************************** +// +// Code called to initialise or wrap up a scene: +// +// ************************************************************************** + +/** + * Called at the start of a scene, when all polygons have been + * initialised, to work out which paths are adjacent to which. + */ +static int DistinctCorners(HPOLYGON hp1, HPOLYGON hp2) { +	const POLYGON *pp1, *pp2; +	int	i, j; +	int	retval = 0; + +	CHECK_HP(hp1, "Out of range polygon handle (23)"); +	CHECK_HP(hp2, "Out of range polygon handle (24)"); +	pp1 = Polys[hp1]; +	pp2 = Polys[hp2]; + +	// Work out (how many of p1's corners is in p2) + (how many of p2's corners is in p1) +	for (i = 0; i < 4; i++) { +		if (IsInPolygon(pp1->cx[i], pp1->cy[i], hp2)) +			retval++; +		if (IsInPolygon(pp2->cx[i], pp2->cy[i], hp1)) +			retval++; +	} + +	// Common corners only count once +	for (i = 0; i < 4; i++) { +		for (j = 0; j < 4; j++) { +			if (pp1->cx[i] == pp2->cx[j] && pp1->cy[i] == pp2->cy[j]) +				retval--; +		} +	} +	return retval; +} + +static void SetPathAdjacencies() { +	POLYGON *p1, *p2;		// Polygon pointers + +	// For each polygon.. +	for (int i1 = 0; i1 < MAX_POLY-1; i1++) { +		// Get polygon, but only carry on if it's a path  +		p1 = Polys[i1]; +		if (!p1 || p1->polytype != PATH) +			continue; + +		// For each subsequent polygon.. +		for (int i2 = i1 + 1; i2 < MAX_POLY; i2++) { +			// Get polygon, but only carry on if it's a path  +			p2 = Polys[i2]; +			if (!p2 || p2->polytype != PATH) +				continue; + +			int j = DistinctCorners(i1, i2); + +			if (j >= 2) { +				// Paths are adjacent +				for (j = 0; j < MAXADJ; j++) +					if (p1->adjpaths[j] == NULL) { +						p1->adjpaths[j] = p2; +						break; +					} +#ifdef DEBUG +				if (j > highestYet) +					highestYet = j; +#endif +				assert(j < MAXADJ); // Number of adjacent paths limit +				for (j = 0; j < MAXADJ; j++) { +					if (p2->adjpaths[j] == NULL) { +						p2->adjpaths[j] = p1; +						break; +					} +				} +#ifdef DEBUG +				if (j > highestYet) +					highestYet = j; +#endif +				assert(j < MAXADJ); // Number of adjacent paths limit +			} +		} +	} +} + +/** + * Ensure NPATH nodes are not inside another PATH/NPATH polygon. + * Only bother with end nodes for now. + */ +#ifdef DEBUG +void CheckNPathIntegrity() { +	uint8		*pps;	// Compiled polygon data +	const POLYGON *rp;	// Run-time polygon structure +	HPOLYGON	hp; +	const POLY *cp;	// Compiled polygon structure +	int		i, j;	// Loop counters +	int		n;	// Last node in current path +	int32	*nlistx, *nlisty; + +	pps = LockMem(pHandle);		// All polygons + +	for (i = 0; i < MAX_POLY; i++) {		// For each polygon.. +		rp = Polys[i]; +		if (rp && rp->polytype == PATH && rp->subtype == NODE) { //...if it's a node path  +			// Get compiled polygon structure +			cp = (const POLY *)pps + rp->pIndex;	// This polygon +			nlistx = (int32 *)(pps + (int)FROM_LE_32(cp->pnodelistx)); +			nlisty = (int32 *)(pps + (int)FROM_LE_32(cp->pnodelisty)); + +			n = (int)FROM_LE_32(cp->nodecount) - 1;		// Last node +			assert(n >= 1); // Node paths must have at least 2 nodes + +			hp = PolyIndex(rp); +			for (j = 0; j <= n; j++) { +				if (!IsInPolygon((int)FROM_LE_32(nlistx[j]), (int)FROM_LE_32(nlisty[j]), hp)) { +					sprintf(tBufferAddr(), "Node (%d, %d) is not in its own path (starting (%d, %d))", +						 (int)FROM_LE_32(nlistx[j]), (int)FROM_LE_32(nlisty[j]), rp->cx[0], rp->cy[0]); +					error(tBufferAddr()); +				} +			} + +			// Check end nodes are not in adjacent path +			for (j = 0; j < MAXADJ; j++) {	// For each adjacent path +				if (rp->adjpaths[j] == NULL) +					break; + +				if (IsInPolygon((int)FROM_LE_32(nlistx[0]), (int)FROM_LE_32(nlisty[0]), PolyIndex(rp->adjpaths[j]))) { +					sprintf(tBufferAddr(), "Node (%d, %d) is in another path (starting (%d, %d))", +						 (int)FROM_LE_32(nlistx[0]), (int)FROM_LE_32(nlisty[0]), rp->adjpaths[j]->cx[0], rp->adjpaths[j]->cy[0]); +					error(tBufferAddr()) +				} +				if (IsInPolygon((int)FROM_LE_32(nlistx[n]), (int)FROM_LE_32(nlisty[n]), PolyIndex(rp->adjpaths[j]))) { +					sprintf(tBufferAddr(), "Node (%d, %d) is in another path (starting (%d, %d))", +						 (int)FROM_LE_32(nlistx[n]), (int)FROM_LE_32(nlisty[n]), rp->adjpaths[j]->cx[0], rp->adjpaths[j]->cy[0]); +					error(tBufferAddr()) +				} +			} +		} +	} +} +#endif + +/** + * Called at the start of a scene, nobbles TAG polygons which should be dead. + */ +static void SetExBlocks() { +	for (int i = 0; i < MAX_POLY; i++) { +		if (deadPolys[i]) { +			if (Polys[i] && Polys[i]->polytype == BLOCKING) +				Polys[i]->polytype = EX_BLOCK; +#ifdef DEBUG +			else +				error("Impossible message!"); +#endif +		} +	} +} + +/** + * Called at the start of a scene, nobbles TAG polygons which should be dead. + */ +static void SetExTags(SCNHANDLE ph) { +	TAGSTATE *pts; +	int i, j; + +	for (i = 0; i < numScenesT; i++) { +		if (SceneTags[i].sid == ph) { +			currentTScene = i; + +			pts = &TagStates[SceneTags[i].offset]; +			for (j = 0; j < SceneTags[i].nooftags; j++, pts++) { +				if (!pts->enabled) +					DisableTag(pts->tid); +			} +			return; +		} +	} +	 +	i = numScenesT++; +	currentTScene = i; +	assert(numScenesT < MAX_SCENES); // Dead tag remembering: scene limit + +	SceneTags[i].sid = ph; +	SceneTags[i].offset = nextfreeT; +	SceneTags[i].nooftags = 0; + +	for (j = 0; j < MAX_POLY; j++) { +		if (Polys[j] && Polys[j]->polytype == TAG) { +			TagStates[nextfreeT].tid = Polys[j]->polyID; +			TagStates[nextfreeT].enabled = true; +			nextfreeT++; +			assert(nextfreeT < MAX_TAGS); // Dead tag remembering: tag limit +			SceneTags[i].nooftags++; +		} +	} +} + +/** + * Called at the start of a scene, nobbles EXIT polygons which should be dead. + */ +static void SetExExits(SCNHANDLE ph) { +	TAGSTATE *pts; +	int i, j; + +	for (i = 0; i < numScenesE; i++) { +		if (SceneExits[i].sid == ph) { +			currentEScene = i; + +			pts = &ExitStates[SceneExits[i].offset]; +			for (j = 0; j < SceneExits[i].nooftags; j++, pts++) { +				if (!pts->enabled) +					DisableExit(pts->tid); +			} +			return; +		} +	} + +	i = numScenesE++; +	currentEScene = i; +	assert(numScenesE < MAX_SCENES); // Dead exit remembering: scene limit + +	SceneExits[i].sid = ph; +	SceneExits[i].offset = nextfreeE; +	SceneExits[i].nooftags = 0; + +	for (j = 0; j < MAX_POLY; j++) { +		if (Polys[j] && Polys[j]->polytype == EXIT) { +			ExitStates[nextfreeE].tid = Polys[j]->polyID; +			ExitStates[nextfreeE].enabled = true; +			nextfreeE++; +			assert(nextfreeE < MAX_EXITS); // Dead exit remembering: exit limit +			SceneExits[i].nooftags++; +		} +	} +} + +/** + * Works out some fixed numbers for a polygon. + */ +static void FiddlyBit(POLYGON *p) { +	int	t1, t2;		// General purpose temp. variables + +	// Enclosing external rectangle +	t1 = MAX(p->cx[0], p->cx[1]); +	t2 = MAX(p->cx[2], p->cx[3]); +	p->pright = MAX(t1, t2); + +	t1 = MIN(p->cx[0], p->cx[1]); +	t2 = MIN(p->cx[2], p->cx[3]); +	p->pleft = MIN(t1, t2); + +	t1 = MAX(p->cy[0], p->cy[1]); +	t2 = MAX(p->cy[2], p->cy[3]); +	p->pbottom = MAX(t1, t2); + +	t1 = MIN(p->cy[0], p->cy[1]); +	t2 = MIN(p->cy[2], p->cy[3]); +	p->ptop = MIN(t1, t2); + +	// Rectangles enclosing each side and each side's magic numbers +	for (t1 = 0; t1 < 4; t1++) { +		p->lright[t1]   = MAX(p->cx[t1], p->cx[(t1+1)%4]); +		p->lleft[t1]    = MIN(p->cx[t1], p->cx[(t1+1)%4]); + +		p->ltop[t1]     = MIN(p->cy[t1], p->cy[(t1+1)%4]);     +		p->lbottom[t1]  = MAX(p->cy[t1], p->cy[(t1+1)%4]); + +		p->a[t1] = p->cy[t1] - p->cy[(t1+1)%4]; +		p->b[t1] = p->cx[(t1+1)%4] - p->cx[t1]; +		p->c[t1] = (long)p->cy[t1]*p->cx[(t1+1)%4] - (long)p->cx[t1]*p->cy[(t1+1)%4]; +	} +} + +/** + * Calculate a point approximating to the centre of a polygon. + * Not very sophisticated. + */ +static void PseudoCentre(POLYGON *p) { +	p->pcentrex = (p->cx[0] + p->cx[1] + p->cx[2] + p->cx[3])/4; +	p->pcentrey = (p->cy[0] + p->cy[1] + p->cy[2] + p->cy[3])/4; + +	if (!IsInPolygon(p->pcentrex, p->pcentrey, PolyIndex(p))) { +		int i, top = 0, bot = 0; + +		for (i = p->ptop; i <= p->pbottom; i++) { +			if (IsInPolygon(p->pcentrex, i, PolyIndex(p))) { +				top = i; +				break; +			} +		} +		for (i = p->pbottom; i >= p->ptop; i--) { +			if (IsInPolygon(p->pcentrex, i, PolyIndex(p))) { +				bot = i; +				break; +			} +		} +		p->pcentrex = (top+bot)/2; +	} +#ifdef DEBUG +	//	assert(IsInPolygon(p->pcentrex, p->pcentrey, PolyIndex(p)));  // Pseudo-centre is not in path +	if (!IsInPolygon(p->pcentrex, p->pcentrey, PolyIndex(p))) { +		sprintf(tBufferAddr(), "Pseudo-centre is not in path (starting (%d, %d)) - polygon reversed?", +			p->cx[0], p->cy[0]); +		error(tBufferAddr()); +	} +#endif +} + +/** + * Allocate a POLYGON structure. + */ +static POLYGON *GetPolyEntry(PTYPE type, const POLY *pp, int pno) { +	for (int i = 0; i < MaxPolys; i++) { +		if (!Polys[i]) { +			POLYGON	*p = Polys[i] = &Polygons[i]; +			memset(p, 0, sizeof(POLYGON)); + +			p->polytype = type;		// Polygon type +			p->pIndex = pno; +			p->tagState = TAG_OFF; +			p->pointState = NOT_POINTING; +			p->polyID = FROM_LE_32(pp->id);		// Identifier + +			for (int j = 0; j < 4; j++)	{	// Polygon definition +				p->cx[j] = (short)FROM_LE_32(pp->x[j]); +				p->cy[j] = (short)FROM_LE_32(pp->y[j]); +			} + +			return p; +		} +	} + +	error("Exceeded MaxPolys"); +	return NULL; +} + +/** + * Initialise an EXIT polygon. + */ +static void InitExit(const POLY *pp, int pno) { +	FiddlyBit(GetPolyEntry(EXIT, pp, pno)); +} + +/** + * Initialise a PATH or NPATH polygon. + */ +static void InitPath(const POLY *pp, bool NodePath, int pno) { +	POLYGON	*p; + +	p = GetPolyEntry(PATH, pp, pno);	// Obtain a slot + +	if (NodePath) { +		p->subtype = NODE; +	} else { +		p->subtype = NORMAL; +	} + +	// Clear out ajacent path pointers +	memset(p->adjpaths, 0, MAXADJ*sizeof(POLYGON *)); + +	FiddlyBit(p); +	PseudoCentre(p); +} + + +/** + * Initialise a BLOCKING polygon. + */ +static void InitBlock(const POLY *pp, int pno) { +	FiddlyBit(GetPolyEntry(BLOCKING, pp, pno)); +} + +/** + * Initialise an extra BLOCKING polygon related to a moving actor. + * The width of the polygon depends on the width of the actor which is + * trying to walk through the actor you first thought of. + * This is for dynamic blocking. + */ +HPOLYGON InitExtraBlock(PMACTOR ca, PMACTOR ta) { +	int	caX, caY;	// Calling actor co-ords +	int	taX, taY;	// Test actor co-ords +	int	left, right; + +	GetMActorPosition(ca, &caX, &caY);	// Calling actor co-ords +	GetMActorPosition(ta, &taX, &taY);	// Test actor co-ords + +	left = GetMActorLeft(ta) - (GetMActorRight(ca) - caX); +	right = GetMActorRight(ta) + (caX - GetMActorLeft(ca)); + +	memset(&extraBlock, 0, sizeof(extraBlock)); + +	// The 3s on the y co-ordinates used to be 10s +	extraBlock.cx[0] = (short)(left - 2); +	extraBlock.cy[0] = (short)(taY - 3); +	extraBlock.cx[1] = (short)(right + 2); +	extraBlock.cy[1] = (short)(taY - 3); +	extraBlock.cx[2] = (short)(right + 2); +	extraBlock.cy[2] = (short)(taY + 3); +	extraBlock.cx[3] = (short)(left - 2); +	extraBlock.cy[3] = (short)(taY + 3); + +	FiddlyBit(&extraBlock);		// Is this necessary? + +	Polys[MAX_POLY] = &extraBlock; +	return MAX_POLY; +} + +/** + * Initialise an EFFECT polygon. + */ +static void InitEffect(const POLY *pp, int pno) { +	FiddlyBit(GetPolyEntry(EFFECT, pp, pno)); +} + + +/** + * Initialise a REFER polygon. + */ +static void InitRefer(const POLY *pp, int pno) { +	POLYGON	*p = GetPolyEntry(REFER, pp, pno);	// Obtain a slot + +	p->subtype = FROM_LE_32(pp->reftype);	// Refer type + +	FiddlyBit(p); +} + + +/** + * Initialise a TAG polygon. + */ +static void InitTag(const POLY *pp, int pno) { +	FiddlyBit(GetPolyEntry(TAG, pp, pno)); +} + + +/** + * Called at the start of a scene to initialise the polys in that scene. + */ +void InitPolygons(SCNHANDLE ph, int numPoly, bool bRestart) { +	const POLY *pp;	// Pointer to compiled data polygon structure + +	pHandle = ph; +	noofPolys = numPoly; + +	if (Polygons == NULL) { +		// first time - allocate memory for process list +		Polygons = (POLYGON *)calloc(MaxPolys, sizeof(POLYGON)); + +		// make sure memory allocated +		if (Polygons == NULL) { +			error("Cannot allocate memory for polygon data"); +		} +	} + +	for (int i = 0; i < noofPolys; i++)	{ +		if (Polys[i]) { +			Polys[i]->pointState = NOT_POINTING; +			Polys[i] = NULL; +		} +	} + +	memset(RoutePaths, 0, sizeof(RoutePaths)); + +	if (!bRestart) +		memset(deadPolys, 0, sizeof(deadPolys)); + +	pp = (const POLY *)LockMem(ph); +	for (int i = 0; i < numPoly; i++, pp++) { +		switch (FROM_LE_32(pp->type)) { +		case POLY_PATH: +			InitPath(pp, false, i); +			break; + +		case POLY_NPATH: +			InitPath(pp, true, i); +			break; + +		case POLY_BLOCK: +			InitBlock(pp, i); +			break; + +		case POLY_REFER: +			InitRefer(pp, i); +			break; + +		case POLY_EFFECT: +			InitEffect(pp, i); +			break; + +		case POLY_EXIT: +			InitExit(pp, i); +			break; + +		case POLY_TAG: +			InitTag(pp, i); +			break; + +		default: +			error("Unknown polygon type"); +		} +	} +	SetPathAdjacencies();		// Paths need to know the facts +#ifdef DEBUG +	CheckNPathIntegrity(); +#endif +	SetExTags(ph);			// Some tags may have been killed +	SetExExits(ph);			// Some exits may have been killed + +	if (bRestart) +		SetExBlocks();		// Some blocks may have been killed +} + +/** + * Called at the end of a scene to ditch all polygons. + */ +void DropPolygons() { +	pathsOnRoute = 0; +	memset(RoutePaths, 0, sizeof(RoutePaths)); +	RouteEnd = NULL; + +	for (int i = 0; i < noofPolys; i++)	{ +		if (Polys[i]) { +			Polys[i]->pointState = NOT_POINTING; +			Polys[i] = NULL; +		} +	} +	noofPolys = 0; +	free(Polygons); +	Polygons = NULL; +} + + + +PTYPE PolyType(HPOLYGON hp) { +	CHECK_HP(hp, "Out of range polygon handle (25)"); + +	return Polys[hp]->polytype; +} + +int PolySubtype(HPOLYGON hp) { +	CHECK_HP(hp, "Out of range polygon handle (26)"); + +	return Polys[hp]->subtype; +} + +int PolyCentreX(HPOLYGON hp) { +	CHECK_HP(hp, "Out of range polygon handle (27)"); + +	return Polys[hp]->pcentrex; +} + +int PolyCentreY(HPOLYGON hp) { +	CHECK_HP(hp, "Out of range polygon handle (28)"); + +	return Polys[hp]->pcentrey; +} + +int PolyCornerX(HPOLYGON hp, int n) { +	CHECK_HP(hp, "Out of range polygon handle (29)"); + +	return Polys[hp]->cx[n]; +} + +int PolyCornerY(HPOLYGON hp, int n) { +	CHECK_HP(hp, "Out of range polygon handle (30)"); + +	return Polys[hp]->cy[n]; +} + +PSTATE PolyPointState(HPOLYGON hp) { +	CHECK_HP(hp, "Out of range polygon handle (31)"); + +	return Polys[hp]->pointState; +} + +TSTATE PolyTagState(HPOLYGON hp) { +	CHECK_HP(hp, "Out of range polygon handle (32)"); + +	return Polys[hp]->tagState; +} + +SCNHANDLE PolyTagHandle(HPOLYGON hp) { +	CHECK_HP(hp, "Out of range polygon handle (33)"); + +	return Polys[hp]->oTagHandle; +} + +void SetPolyPointState(HPOLYGON hp, PSTATE ps) { +	CHECK_HP(hp, "Out of range polygon handle (34)"); + +	Polys[hp]->pointState = ps; +} + +void SetPolyTagState(HPOLYGON hp, TSTATE ts) { +	CHECK_HP(hp, "Out of range polygon handle (35)"); + +	Polys[hp]->tagState = ts; +} + +void SetPolyTagHandle(HPOLYGON hp, SCNHANDLE th) { +	CHECK_HP(hp, "Out of range polygon handle (36)"); + +	Polys[hp]->oTagHandle = th; +} + +void MaxPolygons(int numPolys) { +	assert(numPolys <= MAX_POLY); + +	MaxPolys = numPolys; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/polygons.h b/engines/tinsel/polygons.h new file mode 100644 index 0000000000..90c57d5f99 --- /dev/null +++ b/engines/tinsel/polygons.h @@ -0,0 +1,125 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Definition of POLYGON structure and functions in POLYGONS.C + */ + +#ifndef TINSEL_POLYGONS_H     // prevent multiple includes +#define TINSEL_POLYGONS_H + +#include "tinsel/dw.h"	// for SCNHANDLE +#include "tinsel/scene.h"	// for PPOLY and REEL + +namespace Tinsel { + + +// Polygon Types +enum PTYPE { +	TEST, PATH, EXIT, BLOCKING, +	EFFECT, REFER, TAG, EX_TAG, EX_EXIT, EX_BLOCK +}; + +// subtype +enum { +	NORMAL = 0, +	NODE   = 1       // For paths +}; + +// tagState +enum TSTATE { +	TAG_OFF, TAG_ON +}; + +// pointState +enum PSTATE { +	NO_POINT, NOT_POINTING, POINTING +}; + + +enum { +	NOPOLY = -1 +}; + + + +/*-------------------------------------------------------------------------*/ + +bool IsInPolygon(int xt, int yt, HPOLYGON p); +HPOLYGON InPolygon(int xt, int yt, PTYPE type); +void BlockingCorner(HPOLYGON poly, int *x, int *y, int tarx, int tary); +void FindBestPoint(HPOLYGON path, int *x, int *y, int *line); +bool IsAdjacentPath(HPOLYGON path1, HPOLYGON path2); +HPOLYGON getPathOnTheWay(HPOLYGON from, HPOLYGON to); +int NearestEndNode(HPOLYGON path, int x, int y); +int NearEndNode(HPOLYGON spath, HPOLYGON dpath); +int NearestNodeWithin(HPOLYGON npath, int x, int y); +void NearestCorner(int *x, int *y, HPOLYGON spath, HPOLYGON dpath); +bool IsPolyCorner(HPOLYGON hPath, int x, int y); +int GetScale(HPOLYGON path, int y); +void getNpathNode(HPOLYGON npath, int node, int *px, int *py); +void getPolyTagInfo(HPOLYGON p, SCNHANDLE *hTagText, int *tagx, int *tagy); +SCNHANDLE getPolyFilm(HPOLYGON p); +void getPolyNode(HPOLYGON p, int *px, int *py); +SCNHANDLE getPolyScript(HPOLYGON p); +REEL getPolyReelType(HPOLYGON p); +int32 getPolyZfactor(HPOLYGON p); +int numNodes(HPOLYGON pp); +void RebootDeadTags(void); +void DisableBlock(int blockno); +void EnableBlock(int blockno); +void DisableTag(int tagno); +void EnableTag(int tagno); +void DisableExit(int exitno); +void EnableExit(int exitno); +HPOLYGON FirstPathPoly(void); +HPOLYGON GetPolyHandle(int i); +void InitPolygons(SCNHANDLE ph, int numPoly, bool bRestart); +void DropPolygons(void); + + +void SaveDeadPolys(bool *sdp); +void RestoreDeadPolys(bool *sdp); + +/*-------------------------------------------------------------------------*/ + +PTYPE PolyType(HPOLYGON hp);		// ->type +int PolySubtype(HPOLYGON hp);		// ->subtype +int PolyCentreX(HPOLYGON hp);		// ->pcentrex +int PolyCentreY(HPOLYGON hp);		// ->pcentrey +int PolyCornerX(HPOLYGON hp, int n);	// ->cx[n] +int PolyCornerY(HPOLYGON hp, int n);	// ->cy[n] +PSTATE PolyPointState(HPOLYGON hp);	// ->pointState +TSTATE PolyTagState(HPOLYGON hp);	// ->tagState +SCNHANDLE PolyTagHandle(HPOLYGON hp);	// ->oTagHandle + +void SetPolyPointState(HPOLYGON hp, PSTATE ps);	// ->pointState +void SetPolyTagState(HPOLYGON hp, TSTATE ts);	// ->tagState +void SetPolyTagHandle(HPOLYGON hp, SCNHANDLE th);// ->oTagHandle + +void MaxPolygons(int maxPolys); + +/*-------------------------------------------------------------------------*/ + +} // end of namespace Tinsel + +#endif		/* TINSEL_POLYGONS_H */ diff --git a/engines/tinsel/rince.cpp b/engines/tinsel/rince.cpp new file mode 100644 index 0000000000..a9b24bcac9 --- /dev/null +++ b/engines/tinsel/rince.cpp @@ -0,0 +1,709 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Should really be called "moving actors.c" + */ + +#include "tinsel/actors.h" +#include "tinsel/anim.h" +#include "tinsel/background.h" +#include "tinsel/config.h" +#include "tinsel/dw.h" +#include "tinsel/film.h" +#include "tinsel/handle.h" +#include "tinsel/inventory.h" +#include "tinsel/move.h" +#include "tinsel/multiobj.h"	// multi-part object defintions etc. +#include "tinsel/object.h" +#include "tinsel/pcode.h" +#include "tinsel/pid.h" +#include "tinsel/polygons.h" +#include "tinsel/rince.h" +#include "tinsel/sched.h" +#include "tinsel/timers.h" +#include "tinsel/token.h" + +#include "common/util.h" + +namespace Tinsel { + +//----------------- LOCAL GLOBAL DATA -------------------- + +static MACTOR Movers[MAX_MOVERS]; + + +/** + * RebootMovers + */ +void RebootMovers(void) { +	memset(Movers, 0, sizeof(Movers)); +} + +/** + * Given an actor number, return pointer to its moving actor structure, + * if it is a moving actor. + */ +PMACTOR GetMover(int ano) { +	int i; + +	// Slot 0 is reserved for lead actor +	if (ano == LeadId() || ano == LEAD_ACTOR) +		return &Movers[0]; + +	for (i = 1; i < MAX_MOVERS; i++) +		if (Movers[i].actorID == ano) +			return &Movers[i]; + +	return NULL; +} + +/** + * Register an actor as being a moving one. + */ +PMACTOR SetMover(int ano) { +	int i; + +	// Slot 0 is reserved for lead actor +	if (ano == LeadId() || ano == LEAD_ACTOR) { +		Movers[0].actorToken = TOKEN_LEAD; +		Movers[0].actorID = LeadId(); +		return &Movers[0]; +	} + +	// Check it hasn't already been declared +	for (i = 1; i < MAX_MOVERS; i++) { +		if (Movers[i].actorID == ano) { +			// Actor is already a moving actor +			return &Movers[i]; +		} +	} + +	// Find an empty slot +	for (i = 1; i < MAX_MOVERS; i++) +		if (!Movers[i].actorID) { +			Movers[i].actorToken = TOKEN_LEAD + i; +			Movers[i].actorID = ano; +			return &Movers[i]; +		} + +	error("Too many moving actors"); +} + +/** + * Given an index, returns the associated moving actor. + * + * At the time of writing, used by the effect process. + */ +PMACTOR GetLiveMover(int index) { +	assert(index >= 0 && index < MAX_MOVERS); // out of range + +	if (Movers[index].MActorState == NORM_MACTOR) +		return &Movers[index]; +	else +		return NULL; +} + +bool IsMAinEffectPoly(int index) { +	assert(index >= 0 && index < MAX_MOVERS); // out of range + +	return Movers[index].InEffect; +} + +void SetMAinEffectPoly(int index, bool tf) { +	assert(index >= 0 && index < MAX_MOVERS); // out of range + +	Movers[index].InEffect = tf; +} + +/** + * Remove a moving actor from the current scene. + */ +void KillMActor(PMACTOR pActor) { +	if (pActor->MActorState == NORM_MACTOR) { +		pActor->MActorState = NO_MACTOR; +		MultiDeleteObject(GetPlayfieldList(FIELD_WORLD), pActor->actorObj); +		pActor->actorObj = NULL; +		assert(g_scheduler->getCurrentProcess() != pActor->pProc); +		g_scheduler->killProcess(pActor->pProc); +	} +} + +/** + * getMActorState + */ +MAS getMActorState(PMACTOR pActor) { +	return pActor->MActorState; +} + +/** + * If the actor's object exists, move it behind the background. + * MultiHideObject() is deliberately not used, as StepAnimScript() calls + * cause the object to re-appear. + */ +void hideMActor(PMACTOR pActor, int sf) { +	assert(pActor); // Hiding null moving actor + +	pActor->aHidden = true; +	pActor->SlowFactor = sf; + +	if (pActor->actorObj) +		MultiSetZPosition(pActor->actorObj, -1); +} + +/** + * getMActorHideState + */ +bool getMActorHideState(PMACTOR pActor) { +	if (pActor) +		return pActor->aHidden; +	else +		return false; +} + +/** + * unhideMActor + */ +void unhideMActor(PMACTOR pActor) { +	assert(pActor); // unHiding null moving actor + +	pActor->aHidden = false; + +	// Make visible on the screen +	if (pActor->actorObj) { +		// If no path, just use first path in the scene +		if (pActor->hCpath != NOPOLY) +			MAsetZPos(pActor, pActor->objy, getPolyZfactor(pActor->hCpath)); +		else +			MAsetZPos(pActor, pActor->objy, getPolyZfactor(FirstPathPoly())); +	} +} + +/** + * Get it into our heads that there's nothing doing. + * Called at the end of a scene. + */ +void DropMActors(void) { +	for (int i = 0; i < MAX_MOVERS; i++) { +		Movers[i].MActorState = NO_MACTOR; +		Movers[i].objx = 0; +		Movers[i].objy = 0; +		Movers[i].actorObj = NULL;	// No moving actor objects + +		Movers[i].hCpath = NOPOLY;	// No moving actor path +	} +} + + +/** + * Reposition a moving actor. + */ +void MoveMActor(PMACTOR pActor, int x, int y) { +	int	z; +	int	node; +	HPOLYGON hPath; + +	assert(pActor); // Moving null moving actor +	assert(pActor->actorObj); + +	pActor->objx = x; +	pActor->objy = y; +	MultiSetAniXY(pActor->actorObj, x, y); + +	hPath = InPolygon(x, y, PATH); +	if (hPath != NOPOLY) { +		pActor->hCpath = hPath; +		if (PolySubtype(hPath) == NODE) { +			node = NearestNodeWithin(hPath, x, y); +			getNpathNode(hPath, node, &pActor->objx, &pActor->objy); +			pActor->hFnpath = hPath; +			pActor->line = node; +			pActor->npstatus = GOING_UP; +		} else { +			pActor->hFnpath = NOPOLY; +			pActor->npstatus = NOT_IN; +		} + +		z = GetScale(hPath, pActor->objy); +		pActor->scale = z; +		SetMActorStanding(pActor); +	} else { +		pActor->bNoPath = true; + +		pActor->hFnpath = NOPOLY;	// Ain't in one +		pActor->npstatus = NOT_IN; + +		// Ensure legal reel and scale +		if (pActor->dirn < 0 || pActor->dirn > 3) +			pActor->dirn = FORWARD; +		if (pActor->scale < 0 || pActor->scale > TOTAL_SCALES) +			pActor->scale = 1; +	} +} + +/** + * Get position of a moving actor. + */ +void GetMActorPosition(PMACTOR pActor, int *paniX, int *paniY) { +	assert(pActor); // Getting null moving actor's position + +	if (pActor->actorObj != NULL) +		GetAniPosition(pActor->actorObj, paniX, paniY); +	else { +		*paniX = 0; +		*paniY = 0; +	} +} + +/** + * Moving actor's mid-top position. + */ +void GetMActorMidTopPosition(PMACTOR pActor, int *aniX, int *aniY) { +	assert(pActor); // Getting null moving actor's mid-top position +	assert(pActor->actorObj); // Getting null moving actor's mid-top position + +	*aniX = (MultiLeftmost(pActor->actorObj) + MultiRightmost(pActor->actorObj))/2; +	*aniY = MultiHighest(pActor->actorObj); +} + +/** + * Moving actor's left-most co-ordinate. + */ +int GetMActorLeft(PMACTOR pActor) { +	assert(pActor); // Getting null moving actor's leftmost position +	assert(pActor->actorObj); // Getting null moving actor's leftmost position + +	return MultiLeftmost(pActor->actorObj); +} + +/** + * Moving actor's right-most co-ordinate. + */ +int GetMActorRight(PMACTOR pActor) { +	assert(pActor); // Getting null moving actor's rightmost position +	assert(pActor->actorObj); // Getting null moving actor's rightmost position + +	return MultiRightmost(pActor->actorObj); +} + +/** + * See if moving actor is stood within a polygon. + */ +bool MActorIsInPolygon(PMACTOR pActor, HPOLYGON hp) { +	assert(pActor); // Checking if null moving actor is in polygon +	assert(pActor->actorObj); // Checking if null moving actor is in polygon + +	int aniX, aniY; +	GetAniPosition(pActor->actorObj, &aniX, &aniY); + +	return IsInPolygon(aniX, aniY, hp); +} + +/** + * Change which reel is playing for a moving actor. + */ +void AlterMActor(PMACTOR pActor, SCNHANDLE film, AR_FUNCTION fn) { +	const FILM *pfilm; + +	assert(pActor->actorObj); // Altering null moving actor's animation script + +	if (fn == AR_POPREEL) { +		film = pActor->pushedfilm;	// Use the saved film +	} +	if (fn == AR_PUSHREEL) { +		// Save the one we're replacing +		pActor->pushedfilm = (pActor->TagReelRunning) ? pActor->lastfilm : 0; +	} + +	if (film == 0) { +		if (pActor->TagReelRunning) { +			// Revert to 'normal' actor +			SetMActorWalkReel(pActor, pActor->dirn, pActor->scale, true); +			pActor->TagReelRunning = false; +		} +	} else { +		pActor->lastfilm = film;	// Remember this one + +		pfilm = (const FILM *)LockMem(film); +		assert(pfilm != NULL); + +		InitStepAnimScript(&pActor->actorAnim, pActor->actorObj, FROM_LE_32(pfilm->reels[0].script), ONE_SECOND / FROM_LE_32(pfilm->frate)); +		pActor->scount = 0; + +		// If no path, just use first path in the scene +		if (pActor->hCpath != NOPOLY) +			MAsetZPos(pActor, pActor->objy, getPolyZfactor(pActor->hCpath)); +		else +			MAsetZPos(pActor, pActor->objy, getPolyZfactor(FirstPathPoly())); + +		if (fn == AR_WALKREEL) { +			pActor->TagReelRunning = false; +			pActor->walkReel = true; +		} else { +			pActor->TagReelRunning = true; +			pActor->walkReel = false; + +#ifdef DEBUG +			assert(StepAnimScript(&pActor->actorAnim) != ScriptFinished); // Actor reel has finished! +#else +			StepAnimScript(&pActor->actorAnim);	// 04/01/95 +#endif +		} +			 +		// Hang on, we may not want him yet! 04/01/95 +		if (pActor->aHidden) +			MultiSetZPosition(pActor->actorObj, -1); +	} +} + +/** + * Return the actor's direction. + */ +DIRREEL GetMActorDirection(PMACTOR pActor) { +	return pActor->dirn; +} + +/** + * Return the actor's scale. + */ +int GetMActorScale(PMACTOR pActor) { +	return pActor->scale; +} + +/** + * Point actor in specified derection + */ +void SetMActorDirection(PMACTOR pActor, DIRREEL dirn) { +	pActor->dirn = dirn; +} + +/** + * MAmoving + */ +bool MAmoving(PMACTOR pActor) { +	return pActor->bMoving; +} + +/** + * Return an actor's walk ticket. + */ +int GetActorTicket(PMACTOR pActor) { +	return pActor->ticket; +} + +/** + * Get actor to adopt its appropriate standing reel. + */ +void SetMActorStanding(PMACTOR pActor) { +	assert(pActor->actorObj); +	AlterMActor(pActor, pActor->StandReels[pActor->scale-1][pActor->dirn], AR_NORMAL); +} + +/** + * Get actor to adopt its appropriate walking reel. + */ +void SetMActorWalkReel(PMACTOR pActor, DIRREEL reel, int scale, bool force) { +	SCNHANDLE	whichReel; +	const FILM *pfilm; + +	// Kill off any play that may be going on for this actor +	// and restore the real actor +	storeActorReel(pActor->actorID, NULL, 0, NULL, 0, 0, 0); +	unhideMActor(pActor); + +	// Don't do it if using a special walk reel +	if (pActor->walkReel) +		return; + +	if (force || pActor->scale != scale || pActor->dirn != reel) { +		assert(reel >= 0 && reel <= 3 && scale > 0 && scale <= TOTAL_SCALES); // out of range scale or reel + +		// If scale change and both are regular scales +		// and there's a scaling reel in the right direction +		if (pActor->scale != scale +				&& scale <= NUM_MAINSCALES && pActor->scale <= NUM_MAINSCALES +				&& (whichReel = ScalingReel(pActor->actorID, pActor->scale, scale, reel)) != 0) { +//			error("Cripes!"); +			;	// Use what is now in 'whichReel' +		} else { +			whichReel = pActor->WalkReels[scale-1][reel]; +			assert(whichReel); // no reel +		} + +		pfilm = (const FILM *)LockMem(whichReel); +		assert(pfilm != NULL); // no film + +		InitStepAnimScript(&pActor->actorAnim, pActor->actorObj, FROM_LE_32(pfilm->reels[0].script), 1); + +		// Synchronised walking reels +		SkipFrames(&pActor->actorAnim, pActor->scount); + +		pActor->scale = scale; +		pActor->dirn = reel; +	} +} + +/** + * Sort some stuff out at actor start-up time. + */ +static void InitialPathChecks(PMACTOR pActor, int xpos, int ypos) { +	HPOLYGON hPath; +	int	node; +	int	z; + +	pActor->objx = xpos; +	pActor->objy = ypos; + +	/*-------------------------------------- +	| If Actor is in a follow nodes path,	| +	| position it at the nearest node.	| +	 --------------------------------------*/ +	hPath = InPolygon(xpos, ypos, PATH); + +	if (hPath != NOPOLY) { +		pActor->hCpath = hPath; +		if (PolySubtype(hPath) == NODE) { +			node = NearestNodeWithin(hPath, xpos, ypos); +			getNpathNode(hPath, node, &pActor->objx, &pActor->objy); +			pActor->hFnpath = hPath; +			pActor->line = node; +			pActor->npstatus = GOING_UP; +		} + +		z = GetScale(hPath, pActor->objy); +	} else { +		pActor->bNoPath = true; + +		z = GetScale(FirstPathPoly(), pActor->objy); +	} +	SetMActorWalkReel(pActor, FORWARD, z, false);	 +} + +/** + * Clear everything out at actor start-up time. + */ +static void InitMActor(PMACTOR pActor) { +	 +	pActor->objx = pActor->objy = 0; +	pActor->targetX = pActor->targetY = -1; +	pActor->ItargetX = pActor->ItargetY = -1; +	pActor->hIpath = NOPOLY; +	pActor->UtargetX = pActor->UtargetY = -1; +	pActor->hUpath = NOPOLY; +	pActor->hCpath = NOPOLY; + +	pActor->over = false; +	pActor->InDifficulty = NO_PROB; + +	pActor->hFnpath = NOPOLY; +	pActor->npstatus = NOT_IN; +	pActor->line = 0; + +	pActor->Tline = 0; + +	pActor->TagReelRunning = false; + +	if (pActor->dirn != FORWARD || pActor->dirn != AWAY +			|| pActor->dirn != LEFTREEL || pActor->dirn != RIGHTREEL) +		pActor->dirn = FORWARD; + +	if (pActor->scale < 0 || pActor->scale > TOTAL_SCALES) +		pActor->scale = 1; + +	pActor->scount = 0; + +	pActor->fromx = pActor->fromy = 0; + +	pActor->bMoving = false; +	pActor->bNoPath = false; +	pActor->bIgPath = false; +	pActor->walkReel = false; + +	pActor->actorObj = NULL; + +	pActor->lastfilm = 0; +	pActor->pushedfilm = 0; + +	pActor->InEffect = false; +	pActor->aHidden = false;	// 20/2/95 +} + +static void MActorProcessHelper(int X, int Y, int id, PMACTOR pActor) { +	const FILM *pfilm; +	const MULTI_INIT *pmi; +	const FRAME *pFrame; +	IMAGE *pim; + + +	assert(BackPal()); // Can't start actor without a background palette +	assert(pActor->WalkReels[0][FORWARD]); // Starting actor process without walk reels + +	InitMActor(pActor); +	InitialPathChecks(pActor, X, Y); + +	pfilm = (const FILM *)LockMem(pActor->WalkReels[0][FORWARD]); +	pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(pfilm->reels[0].mobj)); + +//--- +	pFrame = (const FRAME *)LockMem(FROM_LE_32(pmi->hMulFrame)); + +	// get pointer to image +	pim = (IMAGE *)LockMem(READ_LE_UINT32(pFrame));	// handle to image +	pim->hImgPal = TO_LE_32(BackPal()); +//--- +	pActor->actorObj = MultiInitObject(pmi); + +/**/	assert(pActor->actorID == id); +	pActor->actorID = id; + +	// add it to display list +	MultiInsertObject(GetPlayfieldList(FIELD_WORLD), pActor->actorObj); +	storeActorReel(id, NULL, 0, pActor->actorObj, 0, 0, 0); + +	InitStepAnimScript(&pActor->actorAnim, pActor->actorObj, FROM_LE_32(pfilm->reels[0].script), ONE_SECOND / FROM_LE_32(pfilm->frate)); +	pActor->scount = 0; + +	MultiSetAniXY(pActor->actorObj, pActor->objx, pActor->objy); + +	// If no path, just use first path in the scene +	if (pActor->hCpath != NOPOLY) +		MAsetZPos(pActor, pActor->objy, getPolyZfactor(pActor->hCpath)); +	else +		MAsetZPos(pActor, pActor->objy, getPolyZfactor(FirstPathPoly())); + +	// Make him the right size +	SetMActorStanding(pActor); + +//**** if added 18/11/94, am +	if (X != MAGICX && Y != MAGICY) { +		hideMActor(pActor, 0);		// Allows a play to come in before this appears +		pActor->aHidden = false;	// ...but don't stay hidden +	} + +	pActor->MActorState = NORM_MACTOR; +} + +/** + * Moving actor process - 1 per moving actor in current scene. + */ +void MActorProcess(CORO_PARAM, const void *param) { +	// COROUTINE +	CORO_BEGIN_CONTEXT; +	CORO_END_CONTEXT(_ctx); + +	PMACTOR pActor = *(PMACTOR *)param; + +	CORO_BEGIN_CODE(_ctx); +	 +	while (1) { +		if (pActor->TagReelRunning) { + 			if (!pActor->aHidden) +#ifdef DEBUG +			assert(StepAnimScript(&pActor->actorAnim) != ScriptFinished); // Actor reel has finished! +#else +			StepAnimScript(&pActor->actorAnim); +#endif +		} else +			DoMoveActor(pActor); + +		CORO_SLEEP(1);		// allow rescheduling + +	} + +	CORO_END_CODE; +} + +void MActorProcessCreate(int X, int Y, int id, PMACTOR pActor) { +	MActorProcessHelper(X, Y, id, pActor); +	pActor->pProc = g_scheduler->createProcess(PID_MACTOR, MActorProcess, &pActor, sizeof(PMACTOR)); +} + + +/** + * Check for moving actor collision. + */ +PMACTOR InMActorBlock(PMACTOR pActor, int x, int y) { +	int	caX;		// Calling actor's pos'n +	int	caL, caR;	// Calling actor's left and right +	int	taX, taY;	// Test actor's pos'n +	int	taL, taR;	// Test actor's left and right + +	caX = pActor->objx; +	if (pActor->hFnpath != NOPOLY || bNoBlocking) +		return NULL; + +	caL = GetMActorLeft(pActor) + x - caX; +	caR = GetMActorRight(pActor) + x - caX; + +	for (int i = 0; i < MAX_MOVERS; i++) { +		if (pActor == &Movers[i] || Movers[i].MActorState == NO_MACTOR) +			continue; + +		// At around the same height? +		GetMActorPosition(&Movers[i], &taX, &taY); +		if (Movers[i].hFnpath != NOPOLY) +			continue; + +		if (ABS(y - taY) > 2)	// 2 was 8 +			continue; + +		// To the left? +		taL = GetMActorLeft(&Movers[i]); +		if (caR <= taL) +			continue; + +		// To the right? +		taR = GetMActorRight(&Movers[i]); +		if (caL >= taR) +			continue; + +		return &Movers[i]; +	} +	return NULL; +} + +/** + * Copies key information for savescn.c to store away. + */ +void SaveMovers(SAVED_MOVER *sMoverInfo) { +	for (int i = 0; i < MAX_MOVERS; i++) { +		sMoverInfo[i].MActorState= Movers[i].MActorState; +		sMoverInfo[i].actorID	= Movers[i].actorID; +		sMoverInfo[i].objx	= Movers[i].objx; +		sMoverInfo[i].objy	= Movers[i].objy; +		sMoverInfo[i].lastfilm	= Movers[i].lastfilm; + +		memcpy(sMoverInfo[i].WalkReels, Movers[i].WalkReels, TOTAL_SCALES*4*sizeof(SCNHANDLE)); +		memcpy(sMoverInfo[i].StandReels, Movers[i].StandReels, TOTAL_SCALES*4*sizeof(SCNHANDLE)); +		memcpy(sMoverInfo[i].TalkReels, Movers[i].TalkReels, TOTAL_SCALES*4*sizeof(SCNHANDLE)); +	} +} + +void RestoreAuxScales(SAVED_MOVER *sMoverInfo) { +	for (int i = 0; i < MAX_MOVERS; i++) { +		memcpy(Movers[i].WalkReels, sMoverInfo[i].WalkReels, TOTAL_SCALES*4*sizeof(SCNHANDLE)); +		memcpy(Movers[i].StandReels, sMoverInfo[i].StandReels, TOTAL_SCALES*4*sizeof(SCNHANDLE)); +		memcpy(Movers[i].TalkReels, sMoverInfo[i].TalkReels, TOTAL_SCALES*4*sizeof(SCNHANDLE)); +	} +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/rince.h b/engines/tinsel/rince.h new file mode 100644 index 0000000000..7ccf017c65 --- /dev/null +++ b/engines/tinsel/rince.h @@ -0,0 +1,209 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Should really be called "moving actors.h" + */ + +#ifndef TINSEL_RINCE_H	// prevent multiple includes +#define TINSEL_RINCE_H + +#include "tinsel/anim.h"	// for ANIM +#include "tinsel/scene.h"	// for TFTYPE + +namespace Tinsel { + +struct OBJECT; +struct PROCESS; + +enum NPS {NOT_IN, GOING_UP, GOING_DOWN, LEAVING, ENTERING}; + +enum IND {NO_PROB, TRY_CENTRE, TRY_CORNER, TRY_NEXTCORNER}; + +enum MAS {NO_MACTOR, NORM_MACTOR}; + +enum DIRREEL{ LEFTREEL, RIGHTREEL, FORWARD, AWAY }; + +enum { +	NUM_MAINSCALES	= 5, +	NUM_AUXSCALES	= 5, +	TOTAL_SCALES	= NUM_MAINSCALES + NUM_AUXSCALES +}; + +struct MACTOR { + +	int     objx;           /* Co-ordinates object  */ +	int     objy; + +	int     targetX, targetY; +	int     ItargetX, ItargetY;     /* Intermediate destination */ +	HPOLYGON hIpath; +	int     UtargetX, UtargetY;     /* Ultimate destination */ +	HPOLYGON hUpath; + +	HPOLYGON hCpath; + +	bool over; +	int	ticket; + +	IND	InDifficulty; + +	/* For use in 'follow nodes' polygons   */ +	HPOLYGON hFnpath; +	NPS	npstatus; +	int     line; + +	int     Tline;                   // NEW + +	bool	TagReelRunning; + + +	/* Used internally */ +	DIRREEL	dirn;		// Current reel +	int	scale;		// Current scale +	int	scount;		// Step count for walking reel synchronisation + +    unsigned    fromx; +    unsigned    fromy; + +	bool		bMoving;		// Set this to TRUE during a walk + +	bool		bNoPath; +	bool		bIgPath; +	bool		walkReel; + +	OBJECT		*actorObj;	// Actor's object +	ANIM		actorAnim;	// Actor's animation script + +	SCNHANDLE	lastfilm;	// } Used by AlterActor() +	SCNHANDLE	pushedfilm;	// } + +	int			actorID; +	int			actorToken; + +	SCNHANDLE	WalkReels[TOTAL_SCALES][4]; +	SCNHANDLE	StandReels[TOTAL_SCALES][4]; +	SCNHANDLE	TalkReels[TOTAL_SCALES][4]; + +	MAS			MActorState; + +	bool		aHidden; +	int			SlowFactor;	// Slow down movement while hidden + +	bool		stop; + +	/* NOTE: If effect polys can overlap, this needs improving */ +	bool		InEffect; + +	PROCESS		*pProc; +}; +typedef MACTOR *PMACTOR; + +//--------------------------------------------------------------------------- + + +void MActorProcessCreate(int X, int Y, int id, MACTOR *pActor); + + +enum AR_FUNCTION { AR_NORMAL, AR_PUSHREEL, AR_POPREEL, AR_WALKREEL }; + + +MACTOR *GetMover(int ano); +MACTOR *SetMover(int ano); +void KillMActor(MACTOR *pActor); +MACTOR *GetLiveMover(int index); + +MAS getMActorState(MACTOR *psActor); + +void hideMActor(MACTOR *pActor, int sf); +bool getMActorHideState(MACTOR *pActor); +void unhideMActor(MACTOR *pActor); +void DropMActors(void); +void MoveMActor(MACTOR *pActor, int x, int y); + +void GetMActorPosition(MACTOR *pActor, int *aniX, int *aniY); +void GetMActorMidTopPosition(MACTOR *pActor, int *aniX, int *aniY); +int GetMActorLeft(MACTOR *pActor); +int GetMActorRight(MACTOR *pActor); + +bool MActorIsInPolygon(MACTOR *pActor, HPOLYGON hPoly); +void AlterMActor(MACTOR *actor, SCNHANDLE film, AR_FUNCTION fn); +DIRREEL GetMActorDirection(MACTOR *pActor); +int GetMActorScale(MACTOR *pActor); +void SetMActorDirection(MACTOR *pActor, DIRREEL dirn); +void SetMActorStanding(MACTOR *actor); +void SetMActorWalkReel(MACTOR *actor, DIRREEL reel, int scale, bool force); + +MACTOR *InMActorBlock(MACTOR *pActor, int x, int y); + +void RebootMovers(void); + +bool IsMAinEffectPoly(int index); +void SetMAinEffectPoly(int index, bool tf); + +bool MAmoving(MACTOR *pActor); + +int GetActorTicket(MACTOR *pActor); + +/*----------------------------------------------------------------------*/ + +struct SAVED_MOVER { + +	MAS	MActorState; +	int	actorID; +	int     objx; +	int     objy; +	SCNHANDLE lastfilm; + +	SCNHANDLE	WalkReels[TOTAL_SCALES][4]; +	SCNHANDLE	StandReels[TOTAL_SCALES][4]; +	SCNHANDLE	TalkReels[TOTAL_SCALES][4]; + +}; + +void SaveMovers(SAVED_MOVER *sMoverInfo); +void RestoreAuxScales(SAVED_MOVER *sMoverInfo); + +/*----------------------------------------------------------------------*/ + +/* +* Dodgy bit... +* These functions are now in MAREELS.C, but I can't be bothered to +* create a new header file. +*/ +SCNHANDLE GetMactorTalkReel(MACTOR *pAactor, TFTYPE dirn); + +void setscalingreels(int actor, int scale, int direction, +		SCNHANDLE left, SCNHANDLE right, SCNHANDLE forward, SCNHANDLE away); +SCNHANDLE ScalingReel(int ano, int scale1, int scale2, DIRREEL reel); +void RebootScalingReels(void); + +enum { +	MAGICX	= -101, +	MAGICY	= -102 +}; + +/*----------------------------------------------------------------------*/ + +} // end of namespace Tinsel + +#endif /* TINSEL_RINCE_H */ diff --git a/engines/tinsel/saveload.cpp b/engines/tinsel/saveload.cpp new file mode 100644 index 0000000000..1a6cc1202a --- /dev/null +++ b/engines/tinsel/saveload.cpp @@ -0,0 +1,475 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Save and restore scene and game. + */ + +#include "tinsel/actors.h" +#include "tinsel/dw.h" +#include "tinsel/inventory.h" +#include "tinsel/rince.h" +#include "tinsel/savescn.h" +#include "tinsel/serializer.h" +#include "tinsel/timers.h" +#include "tinsel/tinlib.h" +#include "tinsel/tinsel.h" + +#include "common/savefile.h" + +namespace Tinsel { + + +/** + * The current savegame format version. + * Our save/load system uses an elaborate scheme to allow us to modify the + * savegame while keeping full backward compatibility, in the sense that newer + * ScummVM versions always are able to load old savegames. + * In order to achieve that, we store a version in the savegame files, and whenever + * the savegame layout is modified, the version is incremented. + * + * This roughly works by marking each savegame entry with a range of versions + * for which it is valid; the save/load code iterates over all entries, but + * only saves/loads those which are valid for the version of the savegame + * which is being loaded/saved currently. + */ +#define CURRENT_VER 1 +// TODO: Not yet used + +/** + * An auxillary macro, used to specify savegame versions. We use this instead + * of just writing the raw version, because this way they stand out more to + * the reading eye, making it a bit easier to navigate through the code. + */ +#define VER(x) x + + + + +//----------------- EXTERN FUNCTIONS -------------------- + +// in DOS_DW.C +extern void syncSCdata(Serializer &s); + +// in DOS_MAIN.C +//char HardDriveLetter(void); + +// in PCODE.C +extern void syncGlobInfo(Serializer &s); + +// in POLYGONS.C +extern void syncPolyInfo(Serializer &s); + +// in SAVESCN.CPP +extern void RestoreScene(SAVED_DATA *sd, bool bFadeOut); + +//----------------- LOCAL DEFINES -------------------- + +struct SaveGameHeader { +	uint32 id; +	uint32 size; +	uint32 ver; +	char desc[SG_DESC_LEN]; +	struct tm dateTime; +}; + +enum { +	SAVEGAME_ID = 0x44575399,	// = 'DWSc' = "DiscWorld ScummVM" +	SAVEGAME_HEADER_SIZE = 4 + 4 + 4 + SG_DESC_LEN + 7 +}; + + +//----------------- LOCAL GLOBAL DATA -------------------- + +static int	numSfiles = 0; +static SFILES	savedFiles[MAX_SFILES]; + +static bool NeedLoad = true; + +static SAVED_DATA *srsd = 0; +static int RestoreGameNumber = 0; +static char *SaveSceneName = 0; +static const char *SaveSceneDesc = 0; +static int *SaveSceneSsCount = 0; +static char *SaveSceneSsData = 0;	// points to 'SAVED_DATA ssdata[MAX_NEST]' + +static SRSTATE SRstate = SR_IDLE; + +//------------- SAVE/LOAD SUPPORT METHODS ---------------- + +static void syncTime(Serializer &s, struct tm &t) { +	s.syncAsUint16LE(t.tm_year); +	s.syncAsByte(t.tm_mon); +	s.syncAsByte(t.tm_mday); +	s.syncAsByte(t.tm_hour); +	s.syncAsByte(t.tm_min); +	s.syncAsByte(t.tm_sec); +	if (s.isLoading()) { +		t.tm_wday = 0; +		t.tm_yday = 0; +		t.tm_isdst = 0; +	} +} + +static bool syncSaveGameHeader(Serializer &s, SaveGameHeader &hdr) { +	s.syncAsUint32LE(hdr.id); +	s.syncAsUint32LE(hdr.size); +	s.syncAsUint32LE(hdr.ver); + +	s.syncBytes((byte *)hdr.desc, SG_DESC_LEN); +	hdr.desc[SG_DESC_LEN - 1] = 0; + +	syncTime(s, hdr.dateTime); + +	int tmp = hdr.size - s.bytesSynced(); +	// Perform sanity check +	if (tmp < 0 || hdr.id != SAVEGAME_ID || hdr.ver > CURRENT_VER || hdr.size > 1024) +		return false; +	// Skip over any extra bytes +	while (tmp-- > 0) { +		byte b = 0; +		s.syncAsByte(b); +	} +	return true; +} + +static void syncSavedMover(Serializer &s, SAVED_MOVER &sm) { +	SCNHANDLE *pList[3] = { (SCNHANDLE *)&sm.WalkReels, (SCNHANDLE *)&sm.StandReels, (SCNHANDLE *)&sm.TalkReels }; + +	s.syncAsUint32LE(sm.MActorState); +	s.syncAsSint32LE(sm.actorID); +	s.syncAsSint32LE(sm.objx); +	s.syncAsSint32LE(sm.objy); +	s.syncAsUint32LE(sm.lastfilm); +	 +	for (int pIndex = 0; pIndex < 3; ++pIndex) { +		SCNHANDLE *p = pList[pIndex]; +		for (int i = 0; i < TOTAL_SCALES * 4; ++i) +			s.syncAsUint32LE(*p++); +	} +} + +static void syncSavedActor(Serializer &s, SAVED_ACTOR &sa) { +	s.syncAsUint16LE(sa.actorID); +	s.syncAsUint16LE(sa.z); +	s.syncAsUint32LE(sa.bAlive); +	s.syncAsUint32LE(sa.presFilm); +	s.syncAsUint16LE(sa.presRnum); +	s.syncAsUint16LE(sa.presX); +	s.syncAsUint16LE(sa.presY); +} + +extern void syncAllActorsAlive(Serializer &s); + +static void syncNoScrollB(Serializer &s, NOSCROLLB &ns) { +	s.syncAsSint32LE(ns.ln); +	s.syncAsSint32LE(ns.c1); +	s.syncAsSint32LE(ns.c2); +} + +static void syncSavedData(Serializer &s, SAVED_DATA &sd) { +	s.syncAsUint32LE(sd.SavedSceneHandle); +	s.syncAsUint32LE(sd.SavedBgroundHandle); +	for (int i = 0; i < MAX_MOVERS; ++i) +		syncSavedMover(s, sd.SavedMoverInfo[i]); +	for (int i = 0; i < MAX_SAVED_ACTORS; ++i) +		syncSavedActor(s, sd.SavedActorInfo[i]); + +	s.syncAsSint32LE(sd.NumSavedActors); +	s.syncAsSint32LE(sd.SavedLoffset); +	s.syncAsSint32LE(sd.SavedToffset); +	for (int i = 0; i < MAX_INTERPRET; ++i) +		sd.SavedICInfo[i].syncWithSerializer(s); +	for (int i = 0; i < MAX_POLY; ++i) +		s.syncAsUint32LE(sd.SavedDeadPolys[i]); +	s.syncAsUint32LE(sd.SavedControl); +	s.syncAsUint32LE(sd.SavedMidi); +	s.syncAsUint32LE(sd.SavedLoop); +	s.syncAsUint32LE(sd.SavedNoBlocking); + +	// SavedNoScrollData +	for (int i = 0; i < MAX_VNOSCROLL; ++i) +		syncNoScrollB(s, sd.SavedNoScrollData.NoVScroll[i]); +	for (int i = 0; i < MAX_HNOSCROLL; ++i) +		syncNoScrollB(s, sd.SavedNoScrollData.NoHScroll[i]); +	s.syncAsUint32LE(sd.SavedNoScrollData.NumNoV); +	s.syncAsUint32LE(sd.SavedNoScrollData.NumNoH); +} + + +/** + * Called when saving a game to a new file. + * Generates a new, unique, filename. + */ +static char *NewName(void) { +	static char result[FNAMELEN]; +	int	i; +	int	ano = 1;	// Allocated number +	 +	while (1) { +		Common::String fname = _vm->getSavegameFilename(ano); +		strcpy(result, fname.c_str()); + +		for (i = 0; i < numSfiles; i++) +			if (!strcmp(savedFiles[i].name, result)) +				break; + +		if (i == numSfiles) +			break; +		ano++; +	} + +	return result; +} + +/** + * Interrogate the current DOS directory for saved game files. + * Store the file details, ordered by time, in savedFiles[] and return + * the number of files found). + */ +int getList(void) { +	// No change since last call? +	// TODO/FIXME: Just always reload this data? Be careful about slow downs!!! +	if (!NeedLoad) +		return numSfiles; + +	int i; + +	const Common::String pattern = _vm->getSavegamePattern(); +	Common::StringList files = _vm->getSaveFileMan()->listSavefiles(pattern.c_str()); + +	numSfiles = 0; + +	for (Common::StringList::const_iterator file = files.begin(); file != files.end(); ++file) { +		if (numSfiles >= MAX_SFILES) +			break; + +		const Common::String &fname = *file; +		Common::InSaveFile *f = _vm->getSaveFileMan()->openForLoading(fname.c_str()); +		if (f == NULL) { +			continue; +		} + +		// Try to load save game header +		Serializer s(f, 0); +		SaveGameHeader hdr; +		bool validHeader = syncSaveGameHeader(s, hdr); +		delete f; +		if (!validHeader) { +			continue;	// Invalid header, or savegame too new -> skip it +			// TODO: In SCUMM, we still show an entry for the save, but with description +			// "incompatible version". +		} + +		i = numSfiles; +#ifndef DISABLE_SAVEGAME_SORTING +		for (i = 0; i < numSfiles; i++) { +			if (difftime(mktime(&hdr.dateTime), mktime(&savedFiles[i].dateTime)) > 0) { +				Common::copy_backward(&savedFiles[i], &savedFiles[numSfiles], &savedFiles[numSfiles + 1]); +				break; +			}  +		} +#endif + +		strncpy(savedFiles[i].name, fname.c_str(), FNAMELEN); +		strncpy(savedFiles[i].desc, hdr.desc, SG_DESC_LEN); +		savedFiles[i].desc[SG_DESC_LEN - 1] = 0; +		savedFiles[i].dateTime = hdr.dateTime; + +		++numSfiles; +	} + +	// Next getList() needn't do its stuff again +	NeedLoad = false; + +	return numSfiles; +} + + +char *ListEntry(int i, letype which) { +	if (i == -1) +		i = numSfiles; + +	assert(i >= 0); + +	if (i < numSfiles) +		return which == LE_NAME ? savedFiles[i].name : savedFiles[i].desc; +	else +		return NULL; +} + +static void DoSync(Serializer &s) { +	int	sg; + +	syncSavedData(s, *srsd); +	syncGlobInfo(s);		// Glitter globals +	syncInvInfo(s);			// Inventory data + +	// Held object +	if (s.isSaving()) +		sg = WhichItemHeld(); +	s.syncAsSint32LE(sg); +	if (s.isLoading()) +		HoldItem(sg); + +	syncTimerInfo(s);		// Timer data +	syncPolyInfo(s);		// Dead polygon data +	syncSCdata(s);			// Hook Scene and delayed scene + +	s.syncAsSint32LE(*SaveSceneSsCount); + +	if (*SaveSceneSsCount != 0) { +		SAVED_DATA *sdPtr = (SAVED_DATA *)SaveSceneSsData; +		for (int i = 0; i < *SaveSceneSsCount; ++i, ++sdPtr) +			syncSavedData(s, *sdPtr); +	} + +	syncAllActorsAlive(s); +} + +/** + * DoRestore + */ +static bool DoRestore(void) { +	Common::InSaveFile *f; +	uint32 id; + +	f = _vm->getSaveFileMan()->openForLoading(savedFiles[RestoreGameNumber].name); +	if (f == NULL) { +		return false; +	} + +	Serializer s(f, 0); +	SaveGameHeader hdr; +	if (!syncSaveGameHeader(s, hdr)) { +		delete f;	// Invalid header, or savegame too new -> skip it +		return false; +	} + +	DoSync(s); + +	id = f->readSint32LE(); +	if (id != (uint32)0xFEEDFACE) +		error("Incompatible saved game"); + +	bool failed = f->ioFailed(); + +	delete f; + +	return !failed; +} + +/** + * DoSave + */ +static void DoSave(void) { +	Common::OutSaveFile *f; +	const char *fname; + +	// Next getList() must do its stuff again +	NeedLoad = true; + +	if (SaveSceneName == NULL) +		SaveSceneName = NewName(); +	if (SaveSceneDesc[0] == 0) +		SaveSceneDesc = "unnamed"; + +	fname = SaveSceneName; + +	f = _vm->getSaveFileMan()->openForSaving(fname); +	if (f == NULL) +		return; + +	Serializer s(0, f); +	 +	// Write out a savegame header +	SaveGameHeader hdr; +	hdr.id = SAVEGAME_ID; +	hdr.size = SAVEGAME_HEADER_SIZE; +	hdr.ver = CURRENT_VER; +	memcpy(hdr.desc, SaveSceneDesc, SG_DESC_LEN); +	hdr.desc[SG_DESC_LEN - 1] = 0; +	g_system->getTimeAndDate(hdr.dateTime); +	if (!syncSaveGameHeader(s, hdr) || f->ioFailed()) { +		goto save_failure; +	} + +	DoSync(s); + +	// Write out the special Id for Discworld savegames +	f->writeUint32LE(0xFEEDFACE); +	if (f->ioFailed()) +		goto save_failure; + +	f->finalize(); +	delete f; +	return; + +save_failure: +	delete f; +	_vm->getSaveFileMan()->removeSavefile(fname); +} + +/** + * ProcessSRQueue + */ +void ProcessSRQueue(void) { +	switch (SRstate) { +	case SR_DORESTORE: +		if (DoRestore()) { +			RestoreScene(srsd, false); +		} +		SRstate = SR_IDLE; +		break; + +	case SR_DOSAVE: +		DoSave(); +		SRstate = SR_IDLE; +		break; +	default: +		break; +	} +} + + +void RequestSaveGame(char *name, char *desc, SAVED_DATA *sd, int *pSsCount, SAVED_DATA *pSsData) { +	assert(SRstate == SR_IDLE); + +	SaveSceneName = name; +	SaveSceneDesc = desc; +	SaveSceneSsCount = pSsCount; +	SaveSceneSsData = (char *)pSsData; +	srsd = sd; +	SRstate = SR_DOSAVE; +} + +void RequestRestoreGame(int num, SAVED_DATA *sd, int *pSsCount, SAVED_DATA *pSsData) { +	assert(num >= 0); + +	RestoreGameNumber = num; +	SaveSceneSsCount = pSsCount; +	SaveSceneSsData = (char *)pSsData; +	srsd = sd; +	SRstate = SR_DORESTORE; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/savescn.cpp b/engines/tinsel/savescn.cpp new file mode 100644 index 0000000000..9f0d7b9039 --- /dev/null +++ b/engines/tinsel/savescn.cpp @@ -0,0 +1,336 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Save and restore scene and game. + */ + + +#include "tinsel/actors.h" +#include "tinsel/background.h" +#include "tinsel/config.h" +#include "tinsel/dw.h" +#include "tinsel/faders.h"		// FadeOutFast() +#include "tinsel/graphics.h"		// ClearScreen() +#include "tinsel/handle.h" +#include "tinsel/inventory.h" +#include "tinsel/music.h" +#include "tinsel/pid.h" +#include "tinsel/polygons.h" +#include "tinsel/rince.h" +#include "tinsel/savescn.h" +#include "tinsel/sched.h" +#include "tinsel/scroll.h" +#include "tinsel/sound.h" +#include "tinsel/tinlib.h" +#include "tinsel/token.h" + +namespace Tinsel { + +//----------------- EXTERN FUNCTIONS -------------------- + +// in BG.C +extern void startupBackground(SCNHANDLE bfilm); +extern SCNHANDLE GetBgroundHandle(void); +extern void SetDoFadeIn(bool tf); + +// In DOS_DW.C +void RestoreMasterProcess(INT_CONTEXT *pic); + +// in EVENTS.C (declared here and not in events.h because of strange goings-on) +void RestoreProcess(INT_CONTEXT *pic); + +// in PLAY.C +extern void playThisReel(SCNHANDLE film, short reelnum, short z, int x, int y); + +// in SCENE.C +extern SCNHANDLE GetSceneHandle(void); +extern void NewScene(SCNHANDLE scene, int entry); + + + + +//----------------- LOCAL DEFINES -------------------- + +enum { +	RS_COUNT = 5,	// Restore scene count + +	MAX_NEST = 4 +}; + + +//----------------- LOCAL GLOBAL DATA -------------------- + +static bool ASceneIsSaved = false; + +static int savedSceneCount = 0; + +//static SAVED_DATA s_ssData[MAX_NEST]; +static SAVED_DATA *s_ssData = 0; +static SAVED_DATA sgData; + +static SAVED_DATA *s_rsd = 0; + +static int s_restoreSceneCount = 0; + +static bool bNoFade = false; + +//----------------- FORWARD REFERENCES -------------------- + + + +void InitialiseSs(void) { +	if (s_ssData == NULL) { +		s_ssData = (SAVED_DATA *)calloc(MAX_NEST, sizeof(SAVED_DATA)); +		if (s_ssData == NULL) { +			error("Cannot allocate memory for scene changes"); +		} +	} else +		savedSceneCount = 0; +} + +void FreeSs(void) { +	if (s_ssData) { +		free(s_ssData); +		s_ssData = NULL; +	} +} + +/** + * Save current scene. + * @param sd			Pointer to the scene data + */ +void SaveScene(SAVED_DATA *sd) { +	sd->SavedSceneHandle = GetSceneHandle(); +	sd->SavedBgroundHandle = GetBgroundHandle(); +	SaveMovers(sd->SavedMoverInfo); +	sd->NumSavedActors = SaveActors(sd->SavedActorInfo); +	PlayfieldGetPos(FIELD_WORLD, &sd->SavedLoffset, &sd->SavedToffset); +	SaveInterpretContexts(sd->SavedICInfo); +	SaveDeadPolys(sd->SavedDeadPolys); +	sd->SavedControl = TestToken(TOKEN_CONTROL); +	CurrentMidiFacts(&sd->SavedMidi, &sd->SavedLoop); +	sd->SavedNoBlocking = bNoBlocking; +	GetNoScrollData(&sd->SavedNoScrollData); + +	ASceneIsSaved = true; +} + +/** + * Initiate restoration of the saved scene. + * @param sd			Pointer to the scene data + * @param bFadeOut		Flag to perform a fade out + */ +void RestoreScene(SAVED_DATA *sd, bool bFadeOut) { +	s_rsd = sd; + +	if (bFadeOut) +		s_restoreSceneCount = RS_COUNT + COUNTOUT_COUNT;	// Set restore scene count +	else +		s_restoreSceneCount = RS_COUNT;	// Set restore scene count +} + +/** + * Checks that all non-moving actors are playing the same reel as when + * the scene was saved. + * Also 'stand' all the moving actors at their saved positions. + */ +void sortActors(SAVED_DATA *rsd) { +	for (int i = 0; i < rsd->NumSavedActors; i++) { +		ActorsLife(rsd->SavedActorInfo[i].actorID, rsd->SavedActorInfo[i].bAlive); + +		// Should be playing the same reel. +		if (rsd->SavedActorInfo[i].presFilm != 0) { +			if (!actorAlive(rsd->SavedActorInfo[i].actorID)) +				continue; + +			playThisReel(rsd->SavedActorInfo[i].presFilm, rsd->SavedActorInfo[i].presRnum, rsd->SavedActorInfo[i].z, +					rsd->SavedActorInfo[i].presX, rsd->SavedActorInfo[i].presY); +		} +	} + +	RestoreAuxScales(rsd->SavedMoverInfo); +	for (int i = 0; i < MAX_MOVERS; i++) { +		if (rsd->SavedMoverInfo[i].MActorState == NORM_MACTOR) +			stand(rsd->SavedMoverInfo[i].actorID, rsd->SavedMoverInfo[i].objx, +				rsd->SavedMoverInfo[i].objy, rsd->SavedMoverInfo[i].lastfilm); +	} +} + + +//--------------------------------------------------------------------------- + +void ResumeInterprets(SAVED_DATA *rsd) { +	// Master script only affected on restore game, not restore scene +	if (rsd == &sgData) { +		g_scheduler->killMatchingProcess(PID_MASTER_SCR, -1); +		FreeMasterInterpretContext(); +	} + +	for (int i = 0; i < MAX_INTERPRET; i++) { +		switch (rsd->SavedICInfo[i].GSort) { +		case GS_NONE: +			break; + +		case GS_INVENTORY: +			if (rsd->SavedICInfo[i].event != POINTED) { +				RestoreProcess(&rsd->SavedICInfo[i]); +			} +			break; + +		case GS_MASTER: +			// Master script only affected on restore game, not restore scene +			if (rsd == &sgData) +				RestoreMasterProcess(&rsd->SavedICInfo[i]); +			break; + +		case GS_ACTOR: +			RestoreActorProcess(rsd->SavedICInfo[i].actorid, &rsd->SavedICInfo[i]); +			break; + +		case GS_POLYGON: +		case GS_SCENE: +			RestoreProcess(&rsd->SavedICInfo[i]); +			break; +		} +	} +} + +/** + * Do restore scene + * @param n			Id + */ +static int DoRestoreScene(SAVED_DATA *rsd, int n) { +	switch (n) { +	case RS_COUNT + COUNTOUT_COUNT: +		// Trigger pre-load and fade and start countdown +		FadeOutFast(NULL); +		break; + +	case RS_COUNT: +		_vm->_sound->stopAllSamples(); +		ClearScreen(); +		RestoreDeadPolys(rsd->SavedDeadPolys); +		NewScene(rsd->SavedSceneHandle, NO_ENTRY_NUM); +		SetDoFadeIn(!bNoFade); +		bNoFade = false; +		startupBackground(rsd->SavedBgroundHandle); +		KillScroll(); +		PlayfieldSetPos(FIELD_WORLD, rsd->SavedLoffset, rsd->SavedToffset); +		bNoBlocking = rsd->SavedNoBlocking; +		RestoreNoScrollData(&rsd->SavedNoScrollData); +/* +		break; + +	case RS_COUNT - 1: +*/ +		sortActors(rsd); +		break; + +	case 2: +		break; + +	case 1: +		RestoreMidiFacts(rsd->SavedMidi, rsd->SavedLoop); +		if (rsd->SavedControl) +			control(CONTROL_ON);	// TOKEN_CONTROL was free +		ResumeInterprets(rsd); +	} + +	return n - 1; +} + +/** + * Restore game + * @param num			num + */ +void RestoreGame(int num) { +	KillInventory(); + +	RequestRestoreGame(num, &sgData, &savedSceneCount, s_ssData); +	 +	// Actual restoring is performed by ProcessSRQueue +} + +/** + * Save game + * @param name			Name of savegame + * @param desc			Description of savegame + */ +void SaveGame(char *name, char *desc) { +	// Get current scene data +	SaveScene(&sgData); + +	RequestSaveGame(name, desc, &sgData, &savedSceneCount, s_ssData); +	 +	// Actual saving is performed by ProcessSRQueue +} + + +//--------------------------------------------------------------------------------- + +bool IsRestoringScene() { +	if (s_restoreSceneCount) { +		s_restoreSceneCount = DoRestoreScene(s_rsd, s_restoreSceneCount); +	} + +	return s_restoreSceneCount ? true : false; +} + +/** + * Please Restore Scene + */ +void PleaseRestoreScene(bool bFade) { +	// only called by restore_scene PCODE +	if (s_restoreSceneCount == 0) { +		assert(savedSceneCount >= 1); // No saved scene to restore + +		if (ASceneIsSaved) +			RestoreScene(&s_ssData[--savedSceneCount], bFade); +		if (!bFade) +			bNoFade = true; +	} +} + +/** + * Please Save Scene + */ +void PleaseSaveScene(CORO_PARAM) { +	// only called by save_scene PCODE +	CORO_BEGIN_CONTEXT; +	CORO_END_CONTEXT(_ctx); + +	CORO_BEGIN_CODE(_ctx); + +	assert(savedSceneCount < MAX_NEST); // nesting limit reached + +	// Don't save the same thing multiple times! +	// FIXME/TODO: Maybe this can be changed to an assert? +	if (savedSceneCount && s_ssData[savedSceneCount-1].SavedSceneHandle == GetSceneHandle()) +		CORO_KILL_SELF(); + +	SaveScene(&s_ssData[savedSceneCount++]); + +	CORO_END_CODE; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/savescn.h b/engines/tinsel/savescn.h new file mode 100644 index 0000000000..a999c9bffa --- /dev/null +++ b/engines/tinsel/savescn.h @@ -0,0 +1,103 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Should really be called "moving actors.h" + */ + +#ifndef TINSEL_SAVESCN_H +#define TINSEL_SAVESCN_H + +#include <time.h>	// for time_t struct + +#include "tinsel/actors.h"	// SAVED_ACTOR +#include "tinsel/dw.h"	// SCNHANDLE +#include "tinsel/rince.h"	// SAVED_MOVER +#include "tinsel/pcode.h"	// INT_CONTEXT +#include "tinsel/scroll.h"	// SCROLLDATA + +namespace Tinsel { + +enum { +	SG_DESC_LEN	= 40,	// Max. saved game description length +	MAX_SFILES	= 30, + +	// FIXME: Save file names in ScummVM can be longer than 8.3, overflowing the +	// name field in savedFiles. Raising it to 256 as a preliminary fix. +	FNAMELEN	= 256 // 8.3 +}; + +struct SFILES { +	char	name[FNAMELEN]; +	char	desc[SG_DESC_LEN + 2]; +	struct tm dateTime; +}; + +struct SAVED_DATA { +	SCNHANDLE	SavedSceneHandle;		// Scene handle +	SCNHANDLE	SavedBgroundHandle;		// Background handle +	SAVED_MOVER	SavedMoverInfo[MAX_MOVERS];	// Moving actors +	SAVED_ACTOR	SavedActorInfo[MAX_SAVED_ACTORS];	// } Actors +	int			NumSavedActors;				// } +	int			SavedLoffset, SavedToffset;	// Screen offsets +	INT_CONTEXT	SavedICInfo[MAX_INTERPRET];	// Interpret contexts +	bool		SavedDeadPolys[MAX_POLY]; +	bool		SavedControl; +	SCNHANDLE	SavedMidi;			// } +	bool		SavedLoop;			// } Midi +	bool		SavedNoBlocking; +	SCROLLDATA	SavedNoScrollData; +}; + + +enum SRSTATE { +	SR_IDLE, SR_DORESTORE, SR_DONERESTORE, +	SR_DOSAVE, SR_DONESAVE,	SR_ABORTED +}; + +void PleaseRestoreScene(bool bFade); +void PleaseSaveScene(CORO_PARAM); + +bool IsRestoringScene(); + + +enum letype{ +	LE_NAME, LE_DESC +}; + +char *ListEntry(int i, letype which); +int getList(void); + +void RestoreGame(int num); +void SaveGame(char *name, char *desc); + +void ProcessSRQueue(void); + +void RequestSaveGame(char *name, char *desc, SAVED_DATA *sd, int *ssCount, SAVED_DATA *ssData); +void RequestRestoreGame(int num, SAVED_DATA *sd, int *ssCount, SAVED_DATA *ssData); + +void InitialiseSs(void); +void FreeSs(void); + +} // end of namespace Tinsel + +#endif	/* TINSEL_SAVESCN_H */ diff --git a/engines/tinsel/scene.cpp b/engines/tinsel/scene.cpp new file mode 100644 index 0000000000..70700c16a3 --- /dev/null +++ b/engines/tinsel/scene.cpp @@ -0,0 +1,306 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Starts up new scenes. + */ + +#include "tinsel/actors.h" +#include "tinsel/anim.h" +#include "tinsel/background.h" +#include "tinsel/config.h" +#include "tinsel/cursor.h" +#include "tinsel/dw.h" +#include "tinsel/graphics.h" +#include "tinsel/handle.h" +#include "tinsel/inventory.h" +#include "tinsel/film.h" +#include "tinsel/move.h" +#include "tinsel/rince.h" +#include "tinsel/sched.h" +#include "tinsel/scn.h" +#include "tinsel/scroll.h" +#include "tinsel/sound.h"	// stopAllSamples() +#include "tinsel/object.h" +#include "tinsel/pcode.h" +#include "tinsel/pid.h"	// process IDs +#include "tinsel/polygons.h" +#include "tinsel/token.h" + + +namespace Tinsel { + +//----------------- EXTERNAL FUNCTIONS --------------------- + +// in BG.C +extern void DropBackground(void); + +// in EFFECT.C +extern void EffectPolyProcess(CORO_PARAM, const void *); + +// in PDISPLAY.C +#ifdef DEBUG +extern void CursorPositionProcess(CORO_PARAM, const void *); +#endif +extern void TagProcess(CORO_PARAM, const void *); +extern void PointProcess(CORO_PARAM, const void *); +extern void EnableTags(void); + + +//----------------- LOCAL DEFINES -------------------- + +#include "common/pack-start.h"	// START STRUCT PACKING + +/** scene structure - one per scene */ +struct SCENE_STRUC { +	int32 numEntrance;	//!< number of entrances in this scene +	int32 numPoly;		//!< number of various polygons in this scene +	int32 numActor;		//!< number of actors in this scene +	int32 defRefer;		//!< Default refer direction +	SCNHANDLE hSceneScript;	//!< handle to scene script +	SCNHANDLE hEntrance;	//!< handle to table of entrances +	SCNHANDLE hPoly;	//!< handle to table of polygons +	SCNHANDLE hActor;	//!< handle to table of actors +} PACKED_STRUCT; + +/** entrance structure - one per entrance */ +struct ENTRANCE_STRUC { +	int32 eNumber;		//!< entrance number +	SCNHANDLE hScript;	//!< handle to entrance script +} PACKED_STRUCT; + +#include "common/pack-end.h"	// END STRUCT PACKING + + +//----------------- LOCAL GLOBAL DATA -------------------- + +#ifdef DEBUG +static bool ShowPosition = false;	// Set when showpos() has been called +#endif + +static SCNHANDLE SceneHandle = 0;	// Current scene handle - stored in case of Save_Scene() + + +/** + * Started up for scene script and entrance script. + */ +static void SceneTinselProcess(CORO_PARAM, const void *param) { +	// COROUTINE +	CORO_BEGIN_CONTEXT; +		INT_CONTEXT *pic; +	CORO_END_CONTEXT(_ctx); + +	// get the stuff copied to process when it was created +	SCNHANDLE *ss = (SCNHANDLE *)param; +	assert(*ss);		// Must have some code to run + +	CORO_BEGIN_CODE(_ctx); + +	_ctx->pic = InitInterpretContext(GS_SCENE, READ_LE_UINT32(ss), NOEVENT, NOPOLY, 0, NULL); +	CORO_INVOKE_1(Interpret, _ctx->pic); + +	CORO_END_CODE; +} + +/** + * Get the SCENE_STRUC + * Initialise polygons for the scene + * Initialise the actors for this scene + * Run the appropriate entrance code (if any) + * Get the default refer type + */ +static void LoadScene(SCNHANDLE scene, int entry) { +	const SCENE_STRUC	*ss; +	const ENTRANCE_STRUC	*es; +	uint	i; + +	// Scene structure +	SceneHandle = scene;	// Save scene handle in case of Save_Scene() + +	LockMem(SceneHandle);		// Make sure scene is loaded +	LockScene(SceneHandle);		// Prevent current scene from being discarded + +	ss = (const SCENE_STRUC *)FindChunk(scene, CHUNK_SCENE); +	assert(ss != NULL); + +	// Initialise all the polygons for this scene +	InitPolygons(FROM_LE_32(ss->hPoly), FROM_LE_32(ss->numPoly), (entry == NO_ENTRY_NUM)); + +	// Initialise the actors for this scene +	StartActors(FROM_LE_32(ss->hActor), FROM_LE_32(ss->numActor), (entry != NO_ENTRY_NUM)); + +	if (entry != NO_ENTRY_NUM) { + +		// Run the appropriate entrance code (if any) +		es = (const ENTRANCE_STRUC *)LockMem(FROM_LE_32(ss->hEntrance)); +		for (i = 0; i < FROM_LE_32(ss->numEntrance); i++, es++) { +			if (FROM_LE_32(es->eNumber) == (uint)entry) { +				if (es->hScript) +					g_scheduler->createProcess(PID_TCODE, SceneTinselProcess, &es->hScript, sizeof(es->hScript)); +				break; +			} +		} + +		if (i == FROM_LE_32(ss->numEntrance)) +			error("Non-existant scene entry number"); + +		if (ss->hSceneScript) +			g_scheduler->createProcess(PID_TCODE, SceneTinselProcess, &ss->hSceneScript, sizeof(ss->hSceneScript)); +	} + +	// Default refer type +	SetDefaultRefer(FROM_LE_32(ss->defRefer)); +} + + +/** + * Wrap up the last scene. + */ +void EndScene(void) { +	if (SceneHandle != 0) { +		UnlockScene(SceneHandle); +		SceneHandle = 0; +	} + +	KillInventory();	// Close down any open inventory + +	DropPolygons();		// No polygons +	DropNoScrolls();	// No no-scrolls +	DropBackground();	// No background +	DropMActors();		// No moving actors +	DropCursor();		// No cursor +	DropActors();		// No actor reels running +	FreeAllTokens();	// No-one has tokens +	FreeMostInterpretContexts();	// Only master script still interpreting + +	_vm->_sound->stopAllSamples();		// Kill off any still-running sample + +	// init the palette manager +	ResetPalAllocator(); + +	// init the object manager +	KillAllObjects(); + +	// kill all destructable process +	g_scheduler->killMatchingProcess(PID_DESTROY, PID_DESTROY); +} + +/** + * + */ +void PrimeBackground(void) { +	// structure for playfields +	static PLAYFIELD playfield[] = { +		{	// FIELD WORLD +			NULL,		// display list +			0,			// init field x +			0,			// init field y +			0,			// x vel +			0,			// y vel +			Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT),	// clip rect +			false		// moved flag +		}, +		{	// FIELD STATUS +			NULL,		// display list +			0,			// init field x +			0,			// init field y +			0,			// x vel +			0,			// y vel +			Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT),	// clip rect +			false		// moved flag +		} +	}; + +	// structure for background +	static BACKGND backgnd = { +		BLACK,			// sky colour +		Common::Point(0, 0),	// initial world pos +		Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT),	// scroll limits +		0,				// no background update process +		NULL,			// no x scroll table +		NULL,			// no y scroll table +		2,				// 2 playfields +		playfield,		// playfield pointer +		false			// no auto-erase +	}; + +	InitBackground(&backgnd); +} + +/** + * Start up the standard stuff for the next scene. + */ + +void PrimeScene(void) { + +	bNoBlocking = false; + +	RestartCursor();	// Restart the cursor +	EnableTags();		// Next scene with tags enabled + +	g_scheduler->createProcess(PID_SCROLL, ScrollProcess, NULL, 0); +	g_scheduler->createProcess(PID_SCROLL, EffectPolyProcess, NULL, 0); + +#ifdef DEBUG +	if (ShowPosition) +		g_scheduler->createProcess(PID_POSITION, CursorPositionProcess, NULL, 0); +#endif + +	g_scheduler->createProcess(PID_TAG, TagProcess, NULL, 0); +	g_scheduler->createProcess(PID_TAG, PointProcess, NULL, 0); + +	// init the current background +	PrimeBackground(); +} + +/** + * Wrap up the last scene and start up the next scene. + */ + +void NewScene(SCNHANDLE scene, int entry) { +	EndScene();	// Wrap up the last scene. + +	PrimeScene();	// Start up the standard stuff for the next scene. + +	LoadScene(scene, entry); +} + +#ifdef DEBUG +/** + * Sets the ShowPosition flag, causing the cursor position process to be + * created in each scene. + */ + +void setshowpos(void) { +	ShowPosition = true; +} +#endif + +/** + * Return the current scene handle. + */ + +SCNHANDLE GetSceneHandle(void) { +	return SceneHandle; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/scene.h b/engines/tinsel/scene.h new file mode 100644 index 0000000000..d0fc6e1ae3 --- /dev/null +++ b/engines/tinsel/scene.h @@ -0,0 +1,73 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Scene parsing defines + */ + +#ifndef	TINSEL_SCENE_H +#define	TINSEL_SCENE_H + +#include "tinsel/dw.h" + +namespace Tinsel { + +enum { +	MAX_NODES		= 32,	//!< maximum nodes in a Node Path +	MAX_NOSCROLL	= 16,	//!< maximum number of NoScroll commands in a scene +	MAX_ENTRANCE	= 25,	//!< maximum number of entrances in a scene +	MAX_POLY		= 256,	//!< maximum number of polygons in a scene +	MAX_ACTOR		= 32	//!< maximum number of actors in a scene +}; + +/** reference direction */ +enum REFTYPE { +	REF_DEFAULT, REF_UP, REF_DOWN, REF_LEFT, REF_RIGHT, REF_POINT +}; + +enum TFTYPE { +	TF_NONE, TF_UP, TF_DOWN, TF_LEFT, TF_RIGHT, TF_BOGUS +}; + +/** different actor masks */ +enum MASK_TYPE{ +	ACT_DEFAULT, +	ACT_MASK = -1, +	ACT_ALWAYS = -2 +}; + +/** different scales */ +enum SCALE { +	SCALE_DEFAULT, SCALE_LARGE, SCALE_MEDIUM, SCALE_SMALL, +	SCALE_COMPACT, SCALE_TINY, +	SCALE_AUX1, SCALE_AUX2, SCALE_AUX3, +	SCALE_AUX4, SCALE_AUX5 +}; + +/** different reels */ +enum REEL { +	REEL_DEFAULT, REEL_ALL, REEL_HORIZ, REEL_VERT +}; + +} // end of namespace Tinsel + +#endif	// TINSEL_SCENE_H diff --git a/engines/tinsel/sched.cpp b/engines/tinsel/sched.cpp new file mode 100644 index 0000000000..72cfeaf6b0 --- /dev/null +++ b/engines/tinsel/sched.cpp @@ -0,0 +1,345 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Process scheduler. + */ + +#include "tinsel/sched.h" + +#include "common/util.h" + +namespace Tinsel { + +Scheduler *g_scheduler = 0; + +/** process structure */ +struct PROCESS { +	PROCESS *pNext;	//!< pointer to next process in active or free list + +	CoroContext state;		//!< the state of the coroutine +	CORO_ADDR  coroAddr;	//!< the entry point of the coroutine  + +	int sleepTime;		//!< number of scheduler cycles to sleep +	int pid;		//!< process ID +	char param[PARAM_SIZE];	//!< process specific info +}; + + +Scheduler::Scheduler() { +	processList = 0; +	pFreeProcesses = 0; +	pCurrent = 0; + +#ifdef DEBUG +	// diagnostic process counters +	numProcs = 0; +	maxProcs = 0; +#endif + +	pRCfunction = 0; +	 +	active = new PROCESS; +	 +	g_scheduler = this;	// FIXME HACK +} + +Scheduler::~Scheduler() { +	free(processList); +	processList = NULL; +	 +	delete active; +	active = 0; +} + +/** + * Kills all processes and places them on the free list. + */ +void Scheduler::reset() { + +#ifdef DEBUG +	// clear number of process in use +	numProcs = 0; +#endif + +	if (processList == NULL) { +		// first time - allocate memory for process list +		processList = (PROCESS *)calloc(NUM_PROCESS, sizeof(PROCESS)); + +		// make sure memory allocated +		if (processList == NULL) { +			error("Cannot allocate memory for process data"); +		} + +		// fill with garbage +		memset(processList, 'S', NUM_PROCESS * sizeof(PROCESS)); +	} + +	// no active processes +	pCurrent = active->pNext = NULL; + +	// place first process on free list +	pFreeProcesses = processList; + +	// link all other processes after first +	for (int i = 1; i < NUM_PROCESS; i++) { +		processList[i - 1].pNext = processList + i; +	} + +	// null the last process +	processList[NUM_PROCESS - 1].pNext = NULL; +} + + +#ifdef	DEBUG +/** + * Shows the maximum number of process used at once. + */ +void Scheduler::printStats(void) { +	printf("%i process of %i used.\n", maxProcs, NUM_PROCESS); +} +#endif + + +/** + * Give all active processes a chance to run + */ +void Scheduler::schedule(void) { +	// start dispatching active process list +	PROCESS *pPrevProc = active; +	PROCESS *pProc = active->pNext; +	while (pProc != NULL) { +		if (--pProc->sleepTime <= 0) { +			// process is ready for dispatch, activate it +			pCurrent = pProc; +			pProc->coroAddr(pProc->state, pProc->param); +			pCurrent = NULL; +			if (!pProc->state || pProc->state->_sleep <= 0) { +				// Coroutine finished +				killProcess(pProc); +				pProc = pPrevProc; +			} else { +				pProc->sleepTime = pProc->state->_sleep; +			} +		} +		pPrevProc = pProc; +		pProc = pProc->pNext; +	} +} + + +/** + * 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 *Scheduler::createProcess(int pid, CORO_ADDR coroAddr, const void *pParam, int sizeParam) { +	PROCESS *pProc; + +	// get a free process +	pProc = pFreeProcesses; + +	// trap no free process +	assert(pProc != NULL); // Out of processes + +#ifdef DEBUG +	// one more process in use +	if (++numProcs > maxProcs) +		maxProcs = numProcs; +#endif + +	// get link to next free process +	pFreeProcesses = pProc->pNext; + +	if (pCurrent != NULL) { +		// place new process before the next active process +		pProc->pNext = pCurrent->pNext; + +		// make this new process the next active process +		pCurrent->pNext = pProc; +	} else {	// no active processes, place process at head of list +		pProc->pNext = active->pNext; +		active->pNext = pProc; +	} + +	// set coroutine entry point +	pProc->coroAddr = coroAddr; + +	// clear coroutine state +	pProc->state = 0; + +	// wake process up as soon as possible +	pProc->sleepTime = 1; + +	// set new process id +	pProc->pid = pid; + +	// set new process specific info +	if (sizeParam) { +		assert(sizeParam > 0 && sizeParam <= PARAM_SIZE); + +		// set new process specific info +		memcpy(pProc->param, pParam, sizeParam); +	} + + +	// return created process +	return pProc; +} + +/** + * Kills the specified process. + * + * @param pKillProc	which process to kill + */ +void Scheduler::killProcess(PROCESS *pKillProc) { +	PROCESS *pProc, *pPrev;	// process list pointers + +	// make sure a valid process pointer +	assert(pKillProc >= processList && pKillProc <= processList + NUM_PROCESS - 1); +	 +	// can not kill the current process using killProcess ! +	assert(pCurrent != pKillProc); + +#ifdef DEBUG +	// one less process in use +	--numProcs; +	assert(numProcs >= 0); +#endif + +	// search the active list for the process +	for (pProc = active->pNext, pPrev = active; pProc != NULL; pPrev = pProc, pProc = pProc->pNext) { +		if (pProc == pKillProc) { +			// found process in active list + +			// Free process' resources +			if (pRCfunction != NULL) +				(pRCfunction)(pProc); + +			delete pProc->state; + +			// make prev point to next to unlink pProc +			pPrev->pNext = pProc->pNext; + +			// link first free process after pProc +			pProc->pNext = pFreeProcesses; + +			// make pProc the first free process +			pFreeProcesses = pProc; + +			return; +		} +	} + +	// process not found in active list if we get to here +	error("killProcess(): tried to kill a process not in the list of active processes"); +} + + + +/** + * Returns a pointer to the currently running process. + */ +PROCESS *Scheduler::getCurrentProcess(void) { +	return pCurrent; +} + +/** + * Returns the process identifier of the specified process. + * + * @param pProc	which process + */ +int Scheduler::getCurrentPID() const { +	PROCESS *pProc = pCurrent; + +	// make sure a valid process pointer +	assert(pProc >= processList && pProc <= processList + NUM_PROCESS - 1); + +	// return processes PID +	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 Scheduler::killMatchingProcess(int pidKill, int pidMask) { +	int numKilled = 0; +	PROCESS *pProc, *pPrev;	// process list pointers + +	for (pProc = active->pNext, pPrev = active; pProc != NULL; pPrev = pProc, pProc = pProc->pNext) { +		if ((pProc->pid & pidMask) == pidKill) { +			// found a matching process + +			// dont kill the current process +			if (pProc != pCurrent) { +				// kill this process +				numKilled++; + +				// make prev point to next to unlink pProc +				pPrev->pNext = pProc->pNext; + +				// link first free process after pProc +				pProc->pNext = pFreeProcesses; + +				// make pProc the first free process +				pFreeProcesses = pProc; + +				// set to a process on the active list +				pProc = pPrev; +			} +		} +	} + +#ifdef DEBUG +	// adjust process in use +	numProcs -= numKilled; +	assert(numProcs >= 0); +#endif + +	// return number of processes killed +	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 Scheduler::setResourceCallback(VFPTRPP pFunc) { +	pRCfunction = pFunc; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/sched.h b/engines/tinsel/sched.h new file mode 100644 index 0000000000..0d90b3bb9f --- /dev/null +++ b/engines/tinsel/sched.h @@ -0,0 +1,110 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Data structures used by the process scheduler + */ + +#ifndef TINSEL_SCHED_H     // prevent multiple includes +#define TINSEL_SCHED_H + +#include "tinsel/dw.h"	// new data types +#include "tinsel/coroutine.h" + +namespace Tinsel { + +// the size of process specific info +#define	PARAM_SIZE	32 + +// the maximum number of processes +#define	NUM_PROCESS	64 + +typedef void (*CORO_ADDR)(CoroContext &, const void *); + + +struct PROCESS; + +/** + * Create and manage "processes" (really coroutines). + */ +class Scheduler { +public: +	/** Pointer to a function of the form "void function(PPROCESS)" */ +	typedef void (*VFPTRPP)(PROCESS *); +	 +private: +	 +	/** list of all processes */ +	PROCESS *processList; +	 +	/** active process list - also saves scheduler state */ +	PROCESS *active; +	 +	/** pointer to free process list */ +	PROCESS *pFreeProcesses; +	 +	/** the currently active process */ +	PROCESS *pCurrent; +	 +#ifdef DEBUG +	// diagnostic process counters +	int numProcs; +	int maxProcs; +#endif +	 +	/** +	 * Called from killProcess() to enable other resources +	 * a process may be allocated to be released. +	 */ +	VFPTRPP pRCfunction; + + +public: + +	Scheduler(); +	~Scheduler(); + +	void reset(); +	 +	#ifdef	DEBUG +	void printStats(); +	#endif +	 +	void schedule(); +	 +	PROCESS *createProcess(int pid, CORO_ADDR coroAddr, const void *pParam, int sizeParam); +	void killProcess(PROCESS *pKillProc); +	 +	PROCESS *getCurrentProcess(); +	int getCurrentPID() const; +	int killMatchingProcess(int pidKill, int pidMask); +	 +	 +	void setResourceCallback(VFPTRPP pFunc); + +}; + +extern Scheduler *g_scheduler;	// FIXME: Temporary global var, to be used until everything has been OOifyied + +} // end of namespace Tinsel + +#endif		// TINSEL_SCHED_H diff --git a/engines/tinsel/scn.cpp b/engines/tinsel/scn.cpp new file mode 100644 index 0000000000..b14b1c5962 --- /dev/null +++ b/engines/tinsel/scn.cpp @@ -0,0 +1,80 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * A (some would say very) small collection of utility functions. + */ + +#include "common/endian.h" +#include "common/util.h" + +#include "tinsel/dw.h" +#include "tinsel/film.h" +#include "tinsel/handle.h" +#include "tinsel/multiobj.h" +#include "tinsel/scn.h" +#include "tinsel/tinsel.h"	// for _vm + +namespace Tinsel { + +/** + * Given a scene handle and a chunk id, gets the scene in RAM and + * locates the requested chunk. + * @param handle			Scene handle + * @param chunk				Chunk Id + */ +byte *FindChunk(SCNHANDLE handle, uint32 chunk) { +	byte *bptr = LockMem(handle); +	uint32 *lptr = (uint32 *)bptr; +	uint32 add; + +	// V1 chunk types can be found by substracting 2 from the +	// chunk type. Note that CHUNK_STRING and CHUNK_BITMAP are +	// the same in V1 and V2 +	if (_vm->getVersion() == TINSEL_V1 &&  +		chunk != CHUNK_STRING && chunk != CHUNK_BITMAP) +		chunk -= 0x2L; + +	while (1) { +		if (READ_LE_UINT32(lptr) == chunk) +			return (byte *)(lptr + 2); + +		++lptr; +		add = READ_LE_UINT32(lptr); +		if (!add) +			return NULL; + +		lptr = (uint32 *)(bptr + add); +	} +} + +/** + * Get the actor id from a film (column 0) + */ +int extractActor(SCNHANDLE film) { +	const FILM *pfilm = (const FILM *)LockMem(film); +	const FREEL *preel = &pfilm->reels[0]; +	const MULTI_INIT *pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(preel->mobj)); +	return (int)FROM_LE_32(pmi->mulID); +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/scn.h b/engines/tinsel/scn.h new file mode 100644 index 0000000000..29f3dc51fc --- /dev/null +++ b/engines/tinsel/scn.h @@ -0,0 +1,68 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef TINSEL_SCN_H	// prevent multiple includes +#define TINSEL_SCN_H + +#include "tinsel/dw.h" + +namespace Tinsel { + + +// chunk identifier numbers + +// V2 chunks + +#define	CHUNK_STRING			0x33340001L	// same in V1 and V2 +#define	CHUNK_BITMAP			0x33340002L	// same in V1 and V2 +#define	CHUNK_CHARPTR			0x33340003L	// not used! +#define	CHUNK_CHARMATRIX		0x33340004L	// not used! +#define	CHUNK_PALETTE			0x33340005L	// not used! +#define	CHUNK_IMAGE				0x33340006L	// not used! +#define	CHUNK_ANI_FRAME			0x33340007L	// not used! +#define	CHUNK_FILM				0x33340008L	// not used! +#define	CHUNK_FONT				0x33340009L	// not used! +#define	CHUNK_PCODE				0x3334000AL +#define	CHUNK_ENTRANCE			0x3334000BL	// not used! +#define	CHUNK_POLYGONS			0x3334000CL	// not used! +#define	CHUNK_ACTORS			0x3334000DL	// not used! +#define	CHUNK_SCENE				0x3334000EL +#define	CHUNK_TOTAL_ACTORS		0x3334000FL +#define	CHUNK_TOTAL_GLOBALS		0x33340010L +#define	CHUNK_TOTAL_OBJECTS		0x33340011L +#define	CHUNK_OBJECTS			0x33340012L +#define	CHUNK_MIDI				0x33340013L	// not used! +#define	CHUNK_SAMPLE			0x33340014L	// not used! +#define	CHUNK_TOTAL_POLY		0x33340015L +#define	CHUNK_MBSTRING			0x33340022L	// Multi-byte characters + +#define	INDEX_FILENAME		"index"	// name of index file + +byte *FindChunk(SCNHANDLE handle, uint32 chunk); +int extractActor(SCNHANDLE film); + +} // end of namespace Tinsel + +#endif /* TINSEL_SCN_H */ diff --git a/engines/tinsel/scroll.cpp b/engines/tinsel/scroll.cpp new file mode 100644 index 0000000000..aa1bc67298 --- /dev/null +++ b/engines/tinsel/scroll.cpp @@ -0,0 +1,432 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Handles scrolling + */ + +#include "tinsel/actors.h" +#include "tinsel/background.h" +#include "tinsel/cursor.h" +#include "tinsel/dw.h" +#include "tinsel/graphics.h" +#include "tinsel/polygons.h" +#include "tinsel/rince.h" +#include "tinsel/scroll.h" +#include "tinsel/sched.h" + +namespace Tinsel { + +//----------------- EXTERNAL FUNCTIONS --------------------- + +// in BG.C +extern int BackgroundWidth(void); +extern int BackgroundHeight(void); + + + +//----------------- LOCAL DEFINES -------------------- + +#define LEFT	'L' +#define RIGHT	'R' +#define UP	'U' +#define DOWN	'D' + + + +//----------------- LOCAL GLOBAL DATA -------------------- + +static int LeftScroll = 0, DownScroll = 0;	// Number of iterations outstanding + +static int scrollActor = 0; +static PMACTOR psActor = 0; +static int oldx = 0, oldy = 0; + +/** Boundaries and numbers of boundaries */ +static SCROLLDATA sd = { +		{ +			{0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0},  +			{0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0} +		}, +		{ +			{0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0},  +			{0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0} +		}, +		0, +		0 +	}; + +static int ImageH = 0, ImageW = 0; + +static bool ScrollCursor = 0;	// If a TAG or EXIT polygon is clicked on, +				// the cursor is kept over that polygon +				// whilst scrolling + +static int scrollPixels = SCROLLPIXELS; + + +/** + * Reset the ScrollCursor flag + */ +void DontScrollCursor(void) { +	ScrollCursor = false; +} + +/** + * Set the ScrollCursor flag + */ +void DoScrollCursor(void) { +	ScrollCursor = true; +} + +/** + * Configure a no-scroll boundary for a scene. + */ +void SetNoScroll(int x1, int y1, int x2, int y2) { +	if (x1 == x2) { +		/* Vertical line */ +		assert(sd.NumNoH < MAX_HNOSCROLL); + +		sd.NoHScroll[sd.NumNoH].ln = x1;	// X pos of vertical line +		sd.NoHScroll[sd.NumNoH].c1 = y1; +		sd.NoHScroll[sd.NumNoH].c2 = y2; +		sd.NumNoH++; +	} else if (y1 == y2) { +		/* Horizontal line */ +		assert(sd.NumNoV < MAX_VNOSCROLL); + +		sd.NoVScroll[sd.NumNoV].ln = y1;	// Y pos of horizontal line +		sd.NoVScroll[sd.NumNoV].c1 = x1; +		sd.NoVScroll[sd.NumNoV].c2 = x2; +		sd.NumNoV++; +	} else { +		/* No-scroll lines must be horizontal or vertical */ +	} +} + +/** + * Does the obvious - called at the end of a scene. + */ +void DropNoScrolls(void) { +	sd.NumNoH = sd.NumNoV = 0; +} + +/** + * Called from scroll process when it thinks that a scroll is in order. + * Checks for no-scroll boundaries and sets off a scroll if allowed. + */ +static void NeedScroll(int direction) { +	uint	i; +	int	BottomLine, RightCol; +	int	Loffset, Toffset; + +	// get background offsets +	PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + +	switch (direction) { +	case LEFT:	  /* Picture will go left, 'camera' right */ + +		BottomLine = Toffset + (SCREEN_HEIGHT - 1); +		RightCol = Loffset + (SCREEN_WIDTH - 1); + +		for (i = 0; i < sd.NumNoH; i++) { +			if (RightCol >= sd.NoHScroll[i].ln - 1 && RightCol <= sd.NoHScroll[i].ln + 1 && +					((sd.NoHScroll[i].c1 >= Toffset && sd.NoHScroll[i].c1 <= BottomLine) || +					(sd.NoHScroll[i].c2 >= Toffset && sd.NoHScroll[i].c2 <= BottomLine) || +					(sd.NoHScroll[i].c1 < Toffset && sd.NoHScroll[i].c2 > BottomLine))) +				return; +		} + +		if (LeftScroll <= 0) { +			scrollPixels = SCROLLPIXELS; +			LeftScroll = RLSCROLL; +		} +		break; + +	case RIGHT:	 /* Picture will go right, 'camera' left */ + +		BottomLine = Toffset + (SCREEN_HEIGHT - 1); + +		for (i = 0; i < sd.NumNoH; i++) { +			if (Loffset >= sd.NoHScroll[i].ln - 1 && Loffset <= sd.NoHScroll[i].ln + 1 && +					((sd.NoHScroll[i].c1 >= Toffset && sd.NoHScroll[i].c1 <= BottomLine) || +					(sd.NoHScroll[i].c2 >= Toffset && sd.NoHScroll[i].c2 <= BottomLine) || +					(sd.NoHScroll[i].c1 < Toffset && sd.NoHScroll[i].c2 > BottomLine))) +				return; +		} + +		if (LeftScroll >= 0) { +			scrollPixels = SCROLLPIXELS; +			LeftScroll = -RLSCROLL; +		} +		break; + +	case UP:		/* Picture will go upwards, 'camera' downwards  */ + +		BottomLine = Toffset + (SCREEN_HEIGHT - 1); +		RightCol = Loffset + (SCREEN_WIDTH - 1); + +		for (i = 0; i < sd.NumNoV; i++) { +			if ((BottomLine >= sd.NoVScroll[i].ln - 1 && BottomLine <= sd.NoVScroll[i].ln + 1) && +					((sd.NoVScroll[i].c1 >= Loffset && sd.NoVScroll[i].c1 <= RightCol) || +					(sd.NoVScroll[i].c2 >= Loffset && sd.NoVScroll[i].c2 <= RightCol) || +					(sd.NoVScroll[i].c1 < Loffset && sd.NoVScroll[i].c2 > RightCol))) +				return; +			} + +		if (DownScroll <= 0) { +			scrollPixels = SCROLLPIXELS; +			DownScroll = UDSCROLL; +		} +		break; + +	case DOWN:	  /* Picture will go downwards, 'camera' upwards  */ + +		RightCol = Loffset + (SCREEN_WIDTH - 1); + +		for (i = 0; i < sd.NumNoV; i++) { +			if (Toffset >= sd.NoVScroll[i].ln - 1  && Toffset <= sd.NoVScroll[i].ln + 1  && +					((sd.NoVScroll[i].c1 >= Loffset && sd.NoVScroll[i].c1 <= RightCol) || +					(sd.NoVScroll[i].c2 >= Loffset && sd.NoVScroll[i].c2 <= RightCol) || +					(sd.NoVScroll[i].c1 < Loffset && sd.NoVScroll[i].c2 > RightCol))) +				return; +		} + +		if (DownScroll >= 0) { +			scrollPixels = SCROLLPIXELS; +			DownScroll = -UDSCROLL; +		} +		break; +	} +} + +/** + * Called from scroll process - Scrolls the image as appropriate. + */ +static void ScrollImage(void) { +	int OldLoffset = 0, OldToffset = 0;	// Used when keeping cursor on a tag +	int Loffset, Toffset; +	int curX, curY; + +	// get background offsets +	PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + +	/* +	 * Keeping cursor on a tag? +	 */ +	if (ScrollCursor) { +		GetCursorXY(&curX, &curY, true); +		if (InPolygon(curX, curY, TAG) != NOPOLY || InPolygon(curX, curY, EXIT) != NOPOLY) { +			OldLoffset = Loffset; +			OldToffset = Toffset; +		} else +			ScrollCursor = false; +	} + +	/* +	 * Horizontal scrolling +	 */ +	if (LeftScroll > 0) { +		LeftScroll -= scrollPixels; +		if (LeftScroll < 0) { +			Loffset += LeftScroll; +			LeftScroll = 0; +		} +		Loffset += scrollPixels;		// Move right +		if (Loffset > ImageW - SCREEN_WIDTH) +			Loffset = ImageW - SCREEN_WIDTH;// Now at extreme right +	} else if (LeftScroll < 0) { +		LeftScroll += scrollPixels; +		if (LeftScroll > 0) { +			Loffset += LeftScroll; +			LeftScroll = 0; +		} +		Loffset -= scrollPixels;	// Move left +		if (Loffset < 0) +			Loffset = 0;		// Now at extreme left +	} + +	/* +	 * Vertical scrolling +	 */ +	if (DownScroll > 0) { +		DownScroll -= scrollPixels; +		if (DownScroll < 0) { +			Toffset += DownScroll; +			DownScroll = 0; +		} +		Toffset += scrollPixels;		// Move down + +		if (Toffset > ImageH - SCREEN_HEIGHT) +			Toffset = ImageH - SCREEN_HEIGHT;// Now at extreme bottom + +	} else if (DownScroll < 0) { +		DownScroll += scrollPixels; +		if (DownScroll > 0) { +			Toffset += DownScroll; +			DownScroll = 0; +		} +		Toffset -= scrollPixels;		// Move up + +		if (Toffset < 0) +			Toffset = 0;			// Now at extreme top +	} + +	/* +	 * Move cursor if keeping cursor on a tag. +	 */ +	if (ScrollCursor) +		AdjustCursorXY(OldLoffset - Loffset, OldToffset - Toffset); + +	PlayfieldSetPos(FIELD_WORLD, Loffset, Toffset); +} + + +/** + * See if the actor on whom the camera is is approaching an edge. + * Request a scroll if he is. + */ +static void MonitorScroll(void) { +	int newx, newy; +	int Loffset, Toffset; + +	/* +	 * Only do it if the actor is there and is visible +	 */ +	if (!psActor || getMActorHideState(psActor) +			|| getMActorState(psActor) == NO_MACTOR) +		return; + +	GetActorPos(scrollActor, &newx, &newy); + +	if (oldx == newx && oldy == newy) +		return; + +	PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + +	/* +	 * Approaching right side or left side of the screen? +	 */ +	if (newx > Loffset+SCREEN_WIDTH-RLDISTANCE && Loffset < ImageW-SCREEN_WIDTH) { +		if (newx > oldx) +				NeedScroll(LEFT); +	} else if (newx < Loffset + RLDISTANCE  &&  Loffset) { +		if (newx < oldx) +				NeedScroll(RIGHT); +	} + +	/* +	 * Approaching bottom or top of the screen? +	 */ +	if (newy > Toffset+SCREEN_HEIGHT-UDDISTANCE && Toffset < ImageH-SCREEN_HEIGHT) { +		if (newy > oldy) +				NeedScroll(UP); +	} else if (Toffset && newy < Toffset + UDDISTANCE + GetActorBottom(scrollActor) - GetActorTop(scrollActor)) { +		if (newy < oldy) +				NeedScroll(DOWN); +	} + +	oldx = newx; +	oldy = newy; +} + +/** + * Decide when to scroll and scroll when decided to. + */ +void ScrollProcess(CORO_PARAM, const void *) { +	// COROUTINE +	CORO_BEGIN_CONTEXT; +	CORO_END_CONTEXT(_ctx); + +	CORO_BEGIN_CODE(_ctx); + +	ImageH = BackgroundHeight();		// Dimensions +	ImageW = BackgroundWidth();		//  of this scene. + +	// Give up if there'll be no purpose in this process +	if (ImageW == SCREEN_WIDTH  &&  ImageH == SCREEN_HEIGHT) +		CORO_KILL_SELF(); + +	LeftScroll = DownScroll = 0;		// No iterations outstanding +	oldx = oldy = 0; +	scrollPixels = SCROLLPIXELS; + +	if (!scrollActor) +		scrollActor = LeadId(); + +	psActor = GetMover(scrollActor); + +	while (1) { +		MonitorScroll();		// Set scroll requirement + +		if (LeftScroll || DownScroll)	// Scroll if required +			ScrollImage(); + +		CORO_SLEEP(1);		// allow re-scheduling +	} + +	CORO_END_CODE; +} + +/** + * Change which actor the camera is following. + */ +void ScrollFocus(int ano) { +	if (scrollActor != ano) { +		oldx = oldy = 0; +		scrollActor = ano; + +		psActor = ano ? GetMover(scrollActor) : NULL; +	} +} + +/** + * Scroll to abslote position. + */ +void ScrollTo(int x, int y, int iter) { +	int Loffset, Toffset;		// for background offsets + +	scrollPixels = iter != 0 ? iter : SCROLLPIXELS; + +	PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);	// get background offsets + +	LeftScroll = x - Loffset; +	DownScroll = y - Toffset; +} + +/** + * Kill of any current scroll. + */ +void KillScroll(void) { +	LeftScroll = DownScroll = 0; +} + + +void GetNoScrollData(SCROLLDATA *ssd) { +	memcpy(ssd, &sd, sizeof(SCROLLDATA)); +} + +void RestoreNoScrollData(SCROLLDATA *ssd) { +	memcpy(&sd, ssd, sizeof(SCROLLDATA)); +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/scroll.h b/engines/tinsel/scroll.h new file mode 100644 index 0000000000..ac903157f2 --- /dev/null +++ b/engines/tinsel/scroll.h @@ -0,0 +1,77 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef TINSEL_SCROLL_H	// prevent multiple includes +#define TINSEL_SCROLL_H + +namespace Tinsel { + +#define SCROLLPIXELS 8	// Number of pixels to scroll per iteration + +#define RLDISTANCE 50	// Distance from edge that triggers a scroll +#define UDDISTANCE 20 + +// Number of iterations to make +#define RLSCROLL 160	// 20*8 = 160 = half a screen +#define UDSCROLL 100	// 12.5*8 = 100 = half a screen + + +// These structures defined here so boundaries can be saved +struct NOSCROLLB { +	int ln; +	int c1; +	int c2; +}; + +#define MAX_HNOSCROLL	10 +#define MAX_VNOSCROLL	10 + +struct SCROLLDATA{ +	NOSCROLLB NoVScroll[MAX_VNOSCROLL];	// Vertical no-scroll boundaries +	NOSCROLLB NoHScroll[MAX_HNOSCROLL];	// Horizontal no-scroll boundaries +	unsigned NumNoV, NumNoH;		// Counts of no-scroll boundaries +}; + + + +void DontScrollCursor(void); +void DoScrollCursor(void); + +void SetNoScroll(int x1, int y1, int x2, int y2); +void DropNoScrolls(void); + +void ScrollProcess(CORO_PARAM, const void *); + +void ScrollFocus(int actor); +void ScrollTo(int x, int y, int iter); + +void KillScroll(void); + +void GetNoScrollData(SCROLLDATA *ssd); +void RestoreNoScrollData(SCROLLDATA *ssd); + +} // end of namespace Tinsel + +#endif /* TINSEL_SCROLL_H */ diff --git a/engines/tinsel/serializer.h b/engines/tinsel/serializer.h new file mode 100644 index 0000000000..98ee398ef8 --- /dev/null +++ b/engines/tinsel/serializer.h @@ -0,0 +1,131 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Handles timers. + */ + +#ifndef TINSEL_SERIALIZER_H +#define TINSEL_SERIALIZER_H + +#include "common/scummsys.h" +#include "common/savefile.h" + + +namespace Tinsel { + + +#define SYNC_AS(SUFFIX,TYPE,SIZE) \ +	template <class T> \ +	void syncAs ## SUFFIX(T &val) { \ +		if (_loadStream) \ +			val = static_cast<T>(_loadStream->read ## SUFFIX()); \ +		else { \ +			TYPE tmp = val; \ +			_saveStream->write ## SUFFIX(tmp); \ +		} \ +		_bytesSynced += SIZE; \ +	} +	 + +// TODO: Write comment for this +// TODO: Inspired by the SCUMM engine -- move to common/ code and use in more engines? +class Serializer { +public: +	Serializer(Common::SeekableReadStream *in, Common::OutSaveFile *out) +		: _loadStream(in), _saveStream(out), _bytesSynced(0) { +		assert(in || out); +	} + +	bool isSaving() { return (_saveStream != 0); } +	bool isLoading() { return (_loadStream != 0); } +	 +	uint bytesSynced() const { return _bytesSynced; } +	 +	void syncBytes(byte *buf, uint16 size) { +		if (_loadStream) +			_loadStream->read(buf, size); +		else +			_saveStream->write(buf, size); +		_bytesSynced += size; +	} + +	SYNC_AS(Byte, byte, 1) + +	SYNC_AS(Uint16LE, uint16, 2) +	SYNC_AS(Uint16BE, uint16, 2) +	SYNC_AS(Sint16LE, int16, 2) +	SYNC_AS(Sint16BE, int16, 2) + +	SYNC_AS(Uint32LE, uint32, 4) +	SYNC_AS(Uint32BE, uint32, 4) +	SYNC_AS(Sint32LE, int32, 4) +	SYNC_AS(Sint32BE, int32, 4) + +protected: +	Common::SeekableReadStream *_loadStream; +	Common::OutSaveFile *_saveStream; + +	uint _bytesSynced; +}; + +#undef SYNC_AS + +// TODO: Make a subclass "VersionedSerializer", which makes it easy to support  +//       multiple versions of a savegame format (again inspired by SCUMM). +/* +class VersionedSerializer : public Serializer { +public: +	// "version" is the version of the savegame we are loading/creating +	VersionedSerializer(Common::SeekableReadStream *in, Common::OutSaveFile *out, int version) +		: Serializer(in, out), _version(version) { +		assert(in || out); +	} + +	void syncBytes(byte *buf, uint16 size, int minVersion = 0, int maxVersion = INF) { +		if (_version < minVersion || _version > maxVersion) +			return;	// Do nothing if too old or too new +		if (_loadStream) { +			_loadStream->read(buf, size); +		} else { +			_saveStream->write(buf, size); +		} +	} +	... + +}; + +*/ + +// Mixin class / interface +// TODO Maybe call it ISerializable or SerializableMixin ? +// TODO: Taken from SCUMM engine -- move to common/ code? +class Serializable { +public: +	virtual ~Serializable() {} +	virtual void saveLoadWithSerializer(Serializer *ser) = 0; +}; + + +} // end of namespace Tinsel + +#endif diff --git a/engines/tinsel/sound.cpp b/engines/tinsel/sound.cpp new file mode 100644 index 0000000000..e2a24dbd47 --- /dev/null +++ b/engines/tinsel/sound.cpp @@ -0,0 +1,211 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * sound functionality + */ + +#include "tinsel/sound.h" + +#include "tinsel/dw.h" +#include "tinsel/config.h" +#include "tinsel/music.h" +#include "tinsel/strres.h" +#include "tinsel/tinsel.h" + +#include "common/endian.h" +#include "common/file.h" +#include "common/system.h" + +#include "sound/mixer.h" +#include "sound/audiocd.h" + +namespace Tinsel { + +//--------------------------- General data ---------------------------------- + +SoundManager::SoundManager(TinselEngine *vm) : +	//_vm(vm),	// TODO: Enable this once global _vm var is gone +	_sampleIndex(0), _sampleIndexLen(0) { +} + +SoundManager::~SoundManager() { +	free(_sampleIndex); +} + +/** + * Plays the specified sample through the sound driver. + * @param id			Identifier of sample to be played + * @param type			type of sound (voice or sfx) + * @param handle		sound handle + */ +bool SoundManager::playSample(int id, Audio::Mixer::SoundType type, Audio::SoundHandle *handle) { +	// Floppy version has no sample file +	if (_vm->getFeatures() & GF_FLOPPY) +		return false; + +	// no sample driver? +	if (!_vm->_mixer->isReady()) +		return false; + +	// stop any currently playing sample +	_vm->_mixer->stopHandle(_handle); + +	// make sure id is in range +	assert(id > 0 && id < _sampleIndexLen); + +	// get file offset for this sample +	uint32 dwSampleIndex = _sampleIndex[id]; +	 +	// move to correct position in the sample file +	_sampleStream.seek(dwSampleIndex); +	if (_sampleStream.ioFailed() || _sampleStream.pos() != dwSampleIndex) +		error("File %s is corrupt", SAMPLE_FILE); + +	// read the length of the sample +	uint32 sampleLen = _sampleStream.readUint32LE(); +	if (_sampleStream.ioFailed()) +		error("File %s is corrupt", SAMPLE_FILE); + +	// allocate a buffer +	void *sampleBuf = malloc(sampleLen); +	assert(sampleBuf); + +	// read all of the sample +	if (_sampleStream.read(sampleBuf, sampleLen) != sampleLen) +		error("File %s is corrupt", SAMPLE_FILE); + +	// FIXME: Should set this in a different place ;) +	_vm->_mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, volSound); +	//_vm->_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, soundVolumeMusic); +	_vm->_mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, volVoice); + + +	// play it +	_vm->_mixer->playRaw(type, &_handle, sampleBuf, sampleLen, 22050, +						 Audio::Mixer::FLAG_AUTOFREE | Audio::Mixer::FLAG_UNSIGNED); + +	if (handle) +		*handle = _handle; + +	return true; +} + +/** + * Returns TRUE if there is a sample for the specified sample identifier. + * @param id			Identifier of sample to be checked + */ +bool SoundManager::sampleExists(int id) { +	if (_vm->_mixer->isReady()) 	{ +		// make sure id is in range +		if (id > 0 && id < _sampleIndexLen) { +			// check for a sample index +			if (_sampleIndex[id]) +				return true; +		} +	} + +	// no sample driver or no sample +	return false; +} + +/** + * Returns true if a sample is currently playing. + */ +bool SoundManager::sampleIsPlaying(void) { +	return _vm->_mixer->isSoundHandleActive(_handle); +} + +/** + * Stops any currently playing sample. + */ +void SoundManager::stopAllSamples(void) { +	// stop currently playing sample +	_vm->_mixer->stopHandle(_handle); +} + +/** + * Opens and inits all sound sample files. + */ +void SoundManager::openSampleFiles(void) { +	// Floppy and demo versions have no sample files +	if (_vm->getFeatures() & GF_FLOPPY || _vm->getFeatures() & GF_DEMO) +		return; + +	Common::File f; + +	if (_sampleIndex) +		// already allocated +		return; + +	// open sample index file in binary mode +	if (f.open(SAMPLE_INDEX)) 	{ +		// get length of index file +		f.seek(0, SEEK_END);		// move to end of file +		_sampleIndexLen = f.pos();	// get file pointer +		f.seek(0, SEEK_SET);		// back to beginning + +		if (_sampleIndex == NULL) { +			// allocate a buffer for the indices +			_sampleIndex = (uint32 *)malloc(_sampleIndexLen); + +			// make sure memory allocated +			if (_sampleIndex == NULL) { +				// disable samples if cannot alloc buffer for indices +				// TODO: Disabled sound if we can't load the sample index? +				return; +			} +		} + +		// load data +		if (f.read(_sampleIndex, _sampleIndexLen) != (uint32)_sampleIndexLen) +			// file must be corrupt if we get to here +			error("File %s is corrupt", SAMPLE_FILE); + +#ifdef SCUMM_BIG_ENDIAN +		// Convert all ids from LE to native format +		for (uint i = 0; i < _sampleIndexLen / sizeof(uint32); ++i) { +			_sampleIndex[i] = READ_LE_UINT32(_sampleIndex + i); +		} +#endif + +		// close the file +		f.close(); + +		// convert file size to size in DWORDs +		_sampleIndexLen /= sizeof(uint32); +	} else +		error("Cannot find file %s", SAMPLE_INDEX); + +	// open sample file in binary mode +	if (!_sampleStream.open(SAMPLE_FILE)) +		error("Cannot find file %s", SAMPLE_FILE); + +/* +	// gen length of the largest sample +	sampleBuffer.size = _sampleStream.readUint32LE(); +	if (_sampleStream.ioFailed()) +		error("File %s is corrupt", SAMPLE_FILE); +*/ +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/sound.h b/engines/tinsel/sound.h new file mode 100644 index 0000000000..56618eeb8e --- /dev/null +++ b/engines/tinsel/sound.h @@ -0,0 +1,80 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * This file contains the Sound Driver data structures etc. + */ + +#ifndef TINSEL_SOUND_H +#define TINSEL_SOUND_H + +#include "common/file.h" +#include "common/file.h" + +#include "sound/mixer.h" + +#include "tinsel/dw.h" +#include "tinsel/tinsel.h" + +namespace Tinsel { + +#define MAXSAMPVOL 127 + +/*----------------------------------------------------------------------*\ +|*				Function Prototypes			*| +\*----------------------------------------------------------------------*/ + +class SoundManager { +protected: + +	//TinselEngine *_vm;	// TODO: Enable this once global _vm var is gone + +	/** Sample handle */ +	Audio::SoundHandle _handle; + +	/** Sample index buffer and number of entries */ +	uint32 *_sampleIndex; + +	/** Number of entries in the sample index */ +	long _sampleIndexLen; + +	/** file stream for sample file */ +	Common::File _sampleStream; + +public: + +	SoundManager(TinselEngine *vm); +	~SoundManager(); + +	bool playSample(int id, Audio::Mixer::SoundType type, Audio::SoundHandle *handle = 0); +	void stopAllSamples(void);		// Stops any currently playing sample +	 +	bool sampleExists(int id); +	bool sampleIsPlaying(void); +	 +	// TODO: Internal method, make this protected? +	void openSampleFiles(void); +}; + +} // end of namespace Tinsel + +#endif	// TINSEL_SOUND_H diff --git a/engines/tinsel/strres.cpp b/engines/tinsel/strres.cpp new file mode 100644 index 0000000000..abf5a880f6 --- /dev/null +++ b/engines/tinsel/strres.cpp @@ -0,0 +1,209 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * String resource managment routines + */ + +#include "tinsel/dw.h" +#include "tinsel/sound.h" +#include "tinsel/strres.h" +#include "common/file.h" +#include "common/endian.h" + +namespace Tinsel { + +#ifdef DEBUG +// Diagnostic number +int newestString; +#endif + +// buffer for resource strings +static uint8 *textBuffer = 0; + +// language resource string filenames +static const char *languageFiles[] = { +	"english.txt", +	"french.txt", +	"german.txt", +	"italian.txt", +	"spanish.txt" +}; + +// Set if we're handling 2-byte characters. +bool bMultiByte = false; + +/** + * Called to load a resource file for a different language + * @param newLang			The new language + */ +void ChangeLanguage(LANGUAGE newLang) { +	Common::File f; +	uint32 textLen = 0;	// length of buffer + +	if (textBuffer) { +		// free the previous buffer +		free(textBuffer); +		textBuffer = NULL; +	} + +	// Try and open the specified language file. If it fails, and the language +	// isn't English, try falling back on opening 'english.txt' - some foreign +	// language versions reused it rather than their proper filename +	if (!f.open(languageFiles[newLang])) { +		if ((newLang == TXT_ENGLISH) || !f.open(languageFiles[TXT_ENGLISH])) +			error("Cannot find file %s", languageFiles[newLang]); +	} + +	// Check whether the file is compressed or not -  for compressed files the  +	// first long is the filelength and for uncompressed files it is the chunk  +	// identifier +	textLen = f.readUint32LE(); +	if (f.ioFailed()) +		error("File %s is corrupt", languageFiles[newLang]); + +	if (textLen == CHUNK_STRING || textLen == CHUNK_MBSTRING) { +		// the file is uncompressed + +		bMultiByte = (textLen == CHUNK_MBSTRING); + +		// get length of uncompressed file +		textLen = f.size(); +		f.seek(0, SEEK_SET);	// Set to beginning of file + +		if (textBuffer == NULL) { +			// allocate a text buffer for the strings +			textBuffer = (uint8 *)malloc(textLen); + +			// make sure memory allocated +			assert(textBuffer); +		} + +		// load data +		if (f.read(textBuffer, textLen) != textLen) +			// file must be corrupt if we get to here +			error("File %s is corrupt", languageFiles[newLang]); + +		// close the file +		f.close(); +	} else {	// the file must be compressed +		error("Compression handling has been removed!"); +	} +} + +/** + * Loads a string resource identified by id. + * @param id			identifier of string to be loaded + * @param pBuffer		points to buffer that receives the string + * @param bufferMax		maximum number of chars to be copied to the buffer + */ +int LoadStringRes(int id, char *pBuffer, int bufferMax) { +#ifdef DEBUG +	// For diagnostics +	newestString = id; +#endif + +	// base of string resource table +	uint8 *pText = textBuffer; + +	// index into text resource file +	uint32 index = 0; + +	// number of chunks to skip +	int chunkSkip = id / STRINGS_PER_CHUNK; + +	// number of strings to skip when in the correct chunk +	int strSkip = id % STRINGS_PER_CHUNK; + +	// length of string +	int len; + +	// skip to the correct chunk +	while (chunkSkip-- != 0) { +		// make sure chunk id is correct +		assert(READ_LE_UINT32(pText + index) == CHUNK_STRING || READ_LE_UINT32(pText + index) == CHUNK_MBSTRING); + +		if (READ_LE_UINT32(pText + index + sizeof(uint32)) == 0) { +			// TEMPORARY DIRTY BODGE +			strcpy(pBuffer, "!! HIGH STRING !!"); + +			// string does not exist +			return 0; +		} + +		// get index to next chunk +		index = READ_LE_UINT32(pText + index + sizeof(uint32)); +	} + +	// skip over chunk id and offset +	index += (2 * sizeof(uint32)); + +	// pointer to strings +	pText = pText + index; + +	// skip to the correct string +	while (strSkip-- != 0) { +		// skip to next string +		pText += *pText + 1; +	} + +	// get length of string +	len = *pText; + +	if (len) { +		// the string exists + +		// copy the string to the buffer +		if (len < bufferMax) { +			memcpy(pBuffer, pText + 1, len); + +			// null terminate +			pBuffer[len] = 0; + +			// number of chars copied +			return len + 1; +		} else { +			memcpy(pBuffer, pText + 1, bufferMax - 1); + +			// null terminate +			pBuffer[bufferMax - 1] = 0; + +			// number of chars copied +			return bufferMax; +		} +	} + +	// TEMPORARY DIRTY BODGE +	strcpy(pBuffer, "!! NULL STRING !!"); + +	// string does not exist +	return 0; +} + +void FreeTextBuffer() { +	if (textBuffer) { +		free(textBuffer); +		textBuffer = NULL; +	} +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/strres.h b/engines/tinsel/strres.h new file mode 100644 index 0000000000..fac287492b --- /dev/null +++ b/engines/tinsel/strres.h @@ -0,0 +1,69 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * String resource managment routines + */ + +#ifndef TINSEL_STRRES_H +#define TINSEL_STRRES_H + +#include "common/scummsys.h" +#include "tinsel/scn.h" + +namespace Tinsel { + +#define	STRINGS_PER_CHUNK	64	// number of strings per chunk in the language text files +#define	FIRST_STR_ID		1	// id number of first string in string table +#define	MAX_STRING_SIZE		255	// maximum size of a string in the resource table +#define	MAX_STRRES_SIZE		300000	// maximum size of string resource file + +// Set if we're handling 2-byte characters. +extern bool bMultiByte; + +/*----------------------------------------------------------------------*\ +|*				Function Prototypes			*| +\*----------------------------------------------------------------------*/ + +/** + * Called to load a resource file for a different language + * @param newLang			The new language + */ +void ChangeLanguage(LANGUAGE newLang); + +/** + * Loads a string resource identified by id. + * @param id			identifier of string to be loaded + * @param pBuffer		points to buffer that receives the string + * @param bufferMax		maximum number of chars to be copied to the buffer + */ +int LoadStringRes(int id, char *pBuffer, int bufferMax); + +/** + * Frees the text buffer allocated from ChangeLanguage() + */ +void FreeTextBuffer(); + +} // end of namespace Tinsel + +#endif + diff --git a/engines/tinsel/text.cpp b/engines/tinsel/text.cpp new file mode 100644 index 0000000000..dab97f7fdf --- /dev/null +++ b/engines/tinsel/text.cpp @@ -0,0 +1,279 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Text utilities. + */ + +#include "tinsel/dw.h" +#include "tinsel/graphics.h"	// object plotting +#include "tinsel/handle.h" +#include "tinsel/sched.h"	// process scheduler defines +#include "tinsel/strres.h"	// bMultiByte +#include "tinsel/text.h"	// text defines + +namespace Tinsel { + +/** + * Returns the length of one line of a string in pixels. + * @param szStr			String + * @param pFont			Which font to use for dimensions + */ +int StringLengthPix(char *szStr, const FONT *pFont) { +	int strLen;	// accumulated length of string +	byte	c; +	SCNHANDLE	hImg; + +	// while not end of string or end of line +	for (strLen = 0; (c = *szStr) != EOS_CHAR && c != LF_CHAR; szStr++) { +		if (bMultiByte) { +			if (c & 0x80) +				c = ((c & ~0x80) << 8) + *++szStr; +		} +		hImg = FROM_LE_32(pFont->fontDef[c]); + +		if (hImg) { +			// there is a IMAGE for this character +			const IMAGE *pChar = (const IMAGE *)LockMem(hImg); + +			// add width of font bitmap +			strLen += FROM_LE_16(pChar->imgWidth); +		} else +			// use width of space character +			strLen += FROM_LE_32(pFont->spaceSize); + +		// finally add the inter-character spacing +		strLen += FROM_LE_32(pFont->xSpacing); +	} + +	// return length of line in pixels - minus inter-char spacing for last character +	strLen -= FROM_LE_32(pFont->xSpacing); +	return (strLen > 0) ? strLen : 0; +} + +/** + * Returns the justified x start position of a line of text. + * @param szStr			String to output + * @param xPos			X position of string + * @param pFont			Which font to use + * @param mode			Mode flags for the string + */ +int JustifyText(char *szStr, int xPos, const FONT *pFont, int mode) { +	if (mode & TXT_CENTRE) { +		// centre justify the text + +		// adjust x positioning by half the length of line in pixels +		xPos -= StringLengthPix(szStr, pFont) / 2; +	} else if (mode & TXT_RIGHT) { +		// right justify the text + +		// adjust x positioning by length of line in pixels +		xPos -= StringLengthPix(szStr, pFont); +	} + +	// return text line x start position +	return xPos; +} + +/** + * Main text outputting routine. If a object list is specified a + * multi-object is created for the whole text and a pointer to the head + * of the list is returned. + * @param pList			Object list to add text to + * @param szStr			String to output + * @param colour		Colour for monochrome text + * @param xPos			X position of string + * @param yPos			Y position of string + * @param hFont			Which font to use + * @param mode			Mode flags for the string + */ +OBJECT *ObjectTextOut(OBJECT *pList, char *szStr, int colour, int xPos, int yPos, +		SCNHANDLE hFont, int mode) { +	int xJustify;	// x position of text after justification +	int yOffset;	// offset to next line of text +	OBJECT *pFirst;	// head of multi-object text list +	OBJECT *pChar = 0;	// object ptr for the character +	byte c; +	SCNHANDLE hImg; +	const IMAGE *pImg; + +	// make sure there is a linked list to add text to +	assert(pList); + +	// get font pointer +	const FONT *pFont = (const FONT *)LockMem(hFont); + +	// init head of text list +	pFirst = NULL; + +	// get image for capital W +	assert(pFont->fontDef[(int)'W']); +	pImg = (const IMAGE *)LockMem(FROM_LE_32(pFont->fontDef[(int)'W'])); + +	// get height of capital W for offset to next line +	yOffset = FROM_LE_16(pImg->imgHeight); + +	while (*szStr) { +		// x justify the text according to the mode flags +		xJustify = JustifyText(szStr, xPos, pFont, mode); + +		// repeat until end of string or end of line +		while ((c = *szStr) != EOS_CHAR && c != LF_CHAR) { +			if (bMultiByte) { +				if (c & 0x80) +					c = ((c & ~0x80) << 8) + *++szStr; +			} +			hImg = FROM_LE_32(pFont->fontDef[c]); + +			if (hImg == 0) { +				// no image for this character + +				// add font spacing for a space character +				xJustify += FROM_LE_32(pFont->spaceSize); +			} else {	// printable character + +				int aniX, aniY;		// char image animation offsets +				 +				OBJ_INIT oi; +				oi.hObjImg  = FROM_LE_32(pFont->fontInit.hObjImg); +				oi.objFlags = FROM_LE_32(pFont->fontInit.objFlags); +				oi.objID    = FROM_LE_32(pFont->fontInit.objID); +				oi.objX     = FROM_LE_32(pFont->fontInit.objX); +				oi.objY     = FROM_LE_32(pFont->fontInit.objY); +				oi.objZ     = FROM_LE_32(pFont->fontInit.objZ); + +				// allocate and init a character object +				if (pFirst == NULL) +					// first time - init head of list +					pFirst = pChar = InitObject(&oi);	// FIXME: endian issue using fontInit!!! +				else +					// chain to multi-char list +					pChar = pChar->pSlave = InitObject(&oi);	// FIXME: endian issue using fontInit!!! + +				// convert image handle to pointer +				pImg = (const IMAGE *)LockMem(hImg); + +				// fill in character object +				pChar->hImg   = hImg;			// image def +				pChar->width  = FROM_LE_16(pImg->imgWidth);		// width of chars bitmap +				pChar->height = FROM_LE_16(pImg->imgHeight);	// height of chars bitmap +				pChar->hBits  = FROM_LE_32(pImg->hImgBits);		// bitmap + +				// check for absolute positioning +				if (mode & TXT_ABSOLUTE) +					pChar->flags |= DMA_ABS; + +				// set characters colour - only effective for mono fonts +				pChar->constant = colour; + +				// get Y animation offset +				GetAniOffset(hImg, pChar->flags, &aniX, &aniY); + +				// set x position - ignore animation point +				pChar->xPos = intToFrac(xJustify); + +				// set y position - adjust for animation point +				pChar->yPos = intToFrac(yPos - aniY); + +				if (mode & TXT_SHADOW) { +					// we want to shadow the character +					OBJECT *pShad; + +					// allocate a object for the shadow and chain to multi-char list +					pShad = pChar->pSlave = AllocObject(); + +					// copy the character for a shadow +					CopyObject(pShad, pChar); + +					// add shadow offsets to characters position +					pShad->xPos += intToFrac(FROM_LE_32(pFont->xShadow)); +					pShad->yPos += intToFrac(FROM_LE_32(pFont->yShadow)); + +					// shadow is behind the character +					pShad->zPos--; + +					// shadow is always mono +					pShad->flags = DMA_CNZ | DMA_CHANGED; + +					// check for absolute positioning +					if (mode & TXT_ABSOLUTE) +						pShad->flags |= DMA_ABS; + +					// shadow always uses first palette entry +					// should really alloc a palette here also ???? +					pShad->constant = 1; + +					// add shadow to object list +					InsertObject(pList, pShad); +				} + +				// add character to object list +				InsertObject(pList, pChar); + +				// move to end of list +				if (pChar->pSlave) +					pChar = pChar->pSlave; + +				// add character spacing +				xJustify += FROM_LE_16(pImg->imgWidth); +			} + +			// finally add the inter-character spacing +			xJustify += FROM_LE_32(pFont->xSpacing); + +			// next character in string +			++szStr; +		} + +		// adjust the text y position and add the inter-line spacing +		yPos += yOffset + FROM_LE_32(pFont->ySpacing); + +		// check for newline +		if (c == LF_CHAR) +			// next character in string +			++szStr; +	} + +	// return head of list +	return pFirst; +} + +/** + * Is there an image for this character in this font? + * @param hFont	which font to use + * @param c		character to test + */ +bool IsCharImage(SCNHANDLE hFont, char c) { +	byte c2 = (byte)c; +	 +	// Inventory save game name editor needs to be more clever for +	// multi-byte characters. This bodge will stop it erring. +	if (bMultiByte && (c2 & 0x80)) +		return false; + +	// get font pointer +	const FONT *pFont = (const FONT *)LockMem(hFont); + +	return pFont->fontDef[c2] != 0; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/text.h b/engines/tinsel/text.h new file mode 100644 index 0000000000..78998831a1 --- /dev/null +++ b/engines/tinsel/text.h @@ -0,0 +1,101 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Text utility defines + */ + +#ifndef TINSEL_TEXT_H     // prevent multiple includes +#define TINSEL_TEXT_H + +#include "tinsel/object.h"	// object manager defines + +namespace Tinsel { + +/** text mode flags - defaults to left justify */ +enum { +	TXT_CENTRE		= 0x0001,	//!< centre justify text +	TXT_RIGHT		= 0x0002,	//!< right justify text +	TXT_SHADOW		= 0x0004,	//!< shadow each character +	TXT_ABSOLUTE	= 0x0008	//!< position of text is absolute (only for object text) +}; + +/** maximum number of characters in a font */ +#define	MAX_FONT_CHARS	256 + + +#include "common/pack-start.h"	// START STRUCT PACKING + +/** + * Text font data structure. + * @note only the pointer is used so the size of fontDef[] is not important. + * It is currently set at 300 because it suited me for debugging. + */ +struct FONT { +	int xSpacing;			//!< x spacing between characters +	int ySpacing;			//!< y spacing between characters +	int xShadow;			//!< x shadow offset +	int yShadow;			//!< y shadow offset +	int spaceSize;			//!< x spacing to use for a space character +	OBJ_INIT fontInit;		//!< structure used to init text objects +	SCNHANDLE fontDef[300];	//!< image handle array for all characters in the font +} PACKED_STRUCT; + +#include "common/pack-end.h"	// END STRUCT PACKING + + +/** structure for passing the correct parameters to ObjectTextOut */ +struct TEXTOUT { +	OBJECT *pList;		//!< object list to add text to +	char *szStr;		//!< string to output +	int colour;			//!< colour for monochrome text +	int xPos;			//!< x position of string +	int yPos;			//!< y position of string +	SCNHANDLE hFont;	//!< which font to use +	int mode;			//!< mode flags for the string +	int sleepTime;		//!< sleep time between each character (if non-zero) +}; + + +/*----------------------------------------------------------------------*\ +|*			Text Function Prototypes			*| +\*----------------------------------------------------------------------*/ + +OBJECT *ObjectTextOut(		// output a string of text +	OBJECT *pList,		// object list to add text to +	char *szStr,		// string to output +	int colour,		// colour for monochrome text +	int xPos,		// x position of string +	int yPos,		// y position of string +	SCNHANDLE hFont,	// which font to use +	int mode);		// mode flags for the string + +OBJECT *ObjectTextOutIndirect(	// output a string of text +	TEXTOUT *pText);	// pointer to TextOut struct with all parameters + +bool IsCharImage(		// Is there an image for this character in this font? +	SCNHANDLE hFont,	// which font to use +	char c);		// character to test + +} // end of namespace Tinsel + +#endif	// TINSEL_TEXT_H diff --git a/engines/tinsel/timers.cpp b/engines/tinsel/timers.cpp new file mode 100644 index 0000000000..c7b9d3708b --- /dev/null +++ b/engines/tinsel/timers.cpp @@ -0,0 +1,192 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Handles timers. + * + * Note: As part of the transition to ScummVM, the ticks field of a timer has been changed  + * to a millisecond value, rather than ticks at 24Hz. Most places should be able to use + * the timers without change, since the ONE_SECOND constant has been set to be in MILLISECONDS + */ + +#include "tinsel/timers.h" +#include "tinsel/dw.h" +#include "tinsel/serializer.h" + +#include "common/system.h" + +namespace Tinsel { + +//----------------- LOCAL DEFINES -------------------- + +#define MAX_TIMERS 16 + +struct TIMER { +	int 	tno;		/**< Timer number */ +	int		ticks;		/**< Tick count */ +	int		secs;		/**< Second count */ +	int		delta;		/**< Increment/decrement value */ +	bool	frame;		/**< If set, in ticks, otherwise in seconds */ +}; + + + +//----------------- LOCAL GLOBAL DATA -------------------- + +static TIMER timers[MAX_TIMERS]; + + +//-------------------------------------------------------- + +/** + * Gets the current time in number of ticks. + * + * DOS timer ticks is the number of 54.9254ms since midnight. Converting the + * millisecond count won't give the exact same 'since midnight' count, but I + * figure that as long as the timing interval is more or less accurate, it + * shouldn't be a problem. + */ + +uint32 DwGetCurrentTime() { +	return g_system->getMillis() * 55 / 1000; +} + +/** + * Resets all of the timer slots + */ + +void RebootTimers(void) { +	memset(timers, 0, sizeof(timers)); +} + +/** + * (Un)serialize the timer data for save/restore game. + */ +void syncTimerInfo(Serializer &s) { +	for (int i = 0; i < MAX_TIMERS; i++) { +		s.syncAsSint32LE(timers[i].tno); +		s.syncAsSint32LE(timers[i].ticks); +		s.syncAsSint32LE(timers[i].secs); +		s.syncAsSint32LE(timers[i].delta); +		s.syncAsSint32LE(timers[i].frame); +	} +} + +/** + * Find the timer numbered thus, if one is thus numbered. + * @param num	number of the timer + * @return the timer with the specified number, or NULL if there is none + */ +static TIMER *findTimer(int num) { +	for (int i = 0; i < MAX_TIMERS; i++) { +		if (timers[i].tno == num) +			return &timers[i]; +	} +	return NULL; +} + +/** + * Find an empty timer slot. + */ +static TIMER *allocateTimer(int num) { +	assert(num); // zero is not allowed as a timer number +	assert(!findTimer(num)); // Allocating already existant timer + +	for (int i = 0; i < MAX_TIMERS; i++) { +		if (!timers[i].tno) { +			timers[i].tno = num; +			return &timers[i]; +		} +	} + +	error("Too many timers"); +} + +/** + * Update all timers, as appropriate. + */ +void FettleTimers(void) { +	for (int i = 0; i < MAX_TIMERS; i++) { +		if (!timers[i].tno) +			continue; + +		timers[i].ticks += timers[i].delta;	// Update tick value + +		if (timers[i].frame) { +			if (timers[i].ticks < 0) +				timers[i].ticks = 0;	// Have reached zero +		} else { +			if (timers[i].ticks < 0) { +				timers[i].ticks = ONE_SECOND; +				timers[i].secs--; +				if (timers[i].secs < 0) +					timers[i].secs = 0;	// Have reached zero +			} else if (timers[i].ticks == ONE_SECOND) { +				timers[i].ticks = 0; +				timers[i].secs++;		// Another second has passed +			} +		} +	} +} + +/** + * Start a timer up. + */ +void DwSetTimer(int num, int sval, bool up, bool frame) { +	TIMER *pt; + +	assert(num); // zero is not allowed as a timer number + +	pt = findTimer(num); +	if (pt == NULL) +		pt = allocateTimer(num); + +	pt->delta = up ? 1 : -1;	// Increment/decrement value +	pt->frame = frame; + +	if (frame) { +		pt->secs = 0; +		pt->ticks = sval; +	} else { +		pt->secs = sval; +		pt->ticks = 0; +	} +} + +/** + * Return the current count of a timer. + */ +int Timer(int num) { +	TIMER *pt; + +	pt = findTimer(num); + +	if (pt == NULL) +		return -1; + +	if (pt->frame) +		return pt->ticks; +	else +		return pt->secs; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/timers.h b/engines/tinsel/timers.h new file mode 100644 index 0000000000..75eb87ee2b --- /dev/null +++ b/engines/tinsel/timers.h @@ -0,0 +1,53 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Handles timers. + */ + +#ifndef TINSEL_TIMERS_H	// prevent multiple includes +#define TINSEL_TIMERS_H + +#include "common/scummsys.h" +#include "tinsel/dw.h" + +namespace Tinsel { + +class Serializer; + +#define ONE_SECOND 24 + +uint32 DwGetCurrentTime(void); + +void RebootTimers(void); + +void syncTimerInfo(Serializer &s); + +void FettleTimers(void); + +void DwSetTimer(int num, int sval, bool up, bool frame); + +int Timer(int num); + +} // end of namespace Tinsel + +#endif diff --git a/engines/tinsel/tinlib.cpp b/engines/tinsel/tinlib.cpp new file mode 100644 index 0000000000..e8364e20dd --- /dev/null +++ b/engines/tinsel/tinlib.cpp @@ -0,0 +1,2980 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Glitter library functions. + * + * In the main called only from PCODE.C + * Function names are the same as Glitter code function names. + * + * To ensure exclusive use of resources and exclusive control responsibilities. + */ + +#define BODGE + +#include "tinsel/actors.h" +#include "tinsel/background.h" +#include "tinsel/config.h" +#include "tinsel/coroutine.h" +#include "tinsel/cursor.h" +#include "tinsel/dw.h" +#include "tinsel/film.h" +#include "tinsel/font.h" +#include "tinsel/graphics.h" +#include "tinsel/handle.h" +#include "tinsel/inventory.h" +#include "tinsel/move.h" +#include "tinsel/multiobj.h" +#include "tinsel/music.h" +#include "tinsel/object.h" +#include "tinsel/palette.h" +#include "tinsel/pcode.h" +#include "tinsel/pid.h" +#include "tinsel/polygons.h" +#include "tinsel/rince.h" +#include "tinsel/savescn.h" +#include "tinsel/sched.h" +#include "tinsel/scn.h" +#include "tinsel/scroll.h" +#include "tinsel/sound.h" +#include "tinsel/strres.h" +#include "tinsel/text.h" +#include "tinsel/timers.h"		// For ONE_SECOND constant +#include "tinsel/tinlib.h" +#include "tinsel/tinsel.h" +#include "tinsel/token.h" + + +namespace Tinsel { + +//----------------- EXTERNAL GLOBAL DATA -------------------- + +// In DOS_DW.C +extern bool bRestart;		// restart flag - set to restart the game +extern bool bHasRestarted;	// Set after a restart + +// In DOS_MAIN.C +// TODO/FIXME: From dos_main.c: "Only used on PSX so far" +int clRunMode = 0; + +//----------------- EXTERNAL FUNCTIONS --------------------- + +// in BG.C +extern void startupBackground(SCNHANDLE bfilm); +extern void ChangePalette(SCNHANDLE hPal); +extern int BackgroundWidth(void); +extern int BackgroundHeight(void); + +// in DOS_DW.C +extern void SetHookScene(SCNHANDLE scene, int entrance, int transition); +extern void SetNewScene(SCNHANDLE scene, int entrance, int transition); +extern void UnHookScene(void); +extern void SuspendHook(void); +extern void UnSuspendHook(void); + +// in PDISPLAY.C +extern void EnableTags(void); +extern void DisableTags(void); +bool DisableTagsIfEnabled(void); +extern void setshowstring(void); + +// in PLAY.C +extern void playFilm(SCNHANDLE film, int x, int y, int actorid, bool splay, int sfact, bool escOn, int myescEvent, bool bTop); +extern void playFilmc(CORO_PARAM, SCNHANDLE film, int x, int y, int actorid, bool splay, int sfact, bool escOn, int myescEvent, bool bTop); + +// in SCENE.C +extern void setshowpos(void); + +#ifdef BODGE +// In DOS_HAND.C +bool ValidHandle(SCNHANDLE offset); + +// In SCENE.C +SCNHANDLE GetSceneHandle(void); +#endif + +//----------------- GLOBAL GLOBAL DATA -------------------- + +bool bEnableF1; + + +//----------------- LOCAL DEFINES -------------------- + +#define JAP_TEXT_TIME	(2*ONE_SECOND) + +/*----------------------------------------------------------------------*\ +|*                      Library Procedure and Function codes            *| +\*----------------------------------------------------------------------*/ + +enum LIB_CODE { +	ACTORATTR = 0, ACTORDIRECTION, ACTORREF, ACTORSCALE, ACTORXPOS = 4, +	ACTORYPOS, ADDICON, ADDINV1, ADDINV2, ADDOPENINV, AUXSCALE = 10, +	BACKGROUND, CAMERA, CLOSEINVENTORY, CONTROL, CONVERSATION = 15, +	CONVICON, CURSORXPOS, CURSORYPOS, DEC_CONVW, DEC_CURSOR = 20, +	DEC_INV1, DEC_INV2, DEC_INVW, DEC_LEAD, DEC_TAGFONT = 25, +	DEC_TALKFONT, DELICON, DELINV, EFFECTACTOR, ESCAPE, EVENT = 31, +	GETINVLIMIT, HELDOBJECT, HIDE, ININVENTORY, INVDEPICT = 36, +	INVENTORY, KILLACTOR, KILLBLOCK, KILLEXIT, KILLTAG, LEFTOFFSET = 42, +	MOVECURSOR, NEWSCENE, NOSCROLL, OBJECTHELD, OFFSET, PAUSE = 48, +	PLAY, PLAYMIDI, PLAYSAMPLE, PREPARESCENE, PRINT, PRINTOBJ = 54, +	PRINTTAG, RANDOM, RESTORE_SCENE, SAVE_SCENE, SCALINGREELS = 59, +	SCANICON, SCROLL, SETACTOR, SETBLOCK, SETEXIT, SETINVLIMIT = 65, +	SETPALETTE, SETTAG, SETTIMER, SHOWPOS, SHOWSTRING, SPLAY = 71, +	STAND, STANDTAG, STOP, SWALK, TAGACTOR, TALK, TALKATTR, TIMER = 79, +	TOPOFFSET, TOPPLAY, TOPWINDOW, UNTAGACTOR, VIBRATE, WAITKEY = 85, +	WAITTIME, WALK, WALKED, WALKINGACTOR, WALKPOLY, WALKTAG = 91, +	WHICHINVENTORY = 92, +	ACTORSON, CUTSCENE, HOOKSCENE, IDLETIME, RESETIDLETIME = 97, +	TALKAT, UNHOOKSCENE, WAITFRAME,	DEC_CSTRINGS, STOPMIDI, STOPSAMPLE = 103, +	TALKATS = 104, +	DEC_FLAGS, FADEMIDI, CLEARHOOKSCENE, SETINVSIZE, INWHICHINV = 109, +	NOBLOCKING, SAMPLEPLAYING, TRYPLAYSAMPLE, ENABLEF1 = 113, +	RESTARTGAME, QUITGAME, FRAMEGRAB, PLAYRTF, CDPLAY, CDLOAD = 119, +	HASRESTARTED, RESTORE_CUT, RUNMODE, SUBTITLES, SETLANGUAGE = 124 +}; + + + +//----------------- LOCAL GLOBAL DATA -------------------- + +// Saved cursor co-ordinates for control(on) to restore cursor position +// as it was at control(off). +// They are global so that movecursor(..) has a net effect if it +// precedes control(on). +static int controlX = 0, controlY = 0; + +static int offtype = 0;			// used by control() +static uint32 lastValue = 0;	// used by dw_random() +static int scrollCount = 0;		// used by scroll() + +static bool NotPointedRunning = false;	// Used in printobj and printobjPointed + +static COLORREF s_talkfontColor = 0; + +//----------------- FORWARD REFERENCES -------------------- + +void resetidletime(void); +void stopmidi(void); +void stopsample(void); +void walk(CORO_PARAM, int actor, int x, int y, SCNHANDLE film, int hold, bool igPath, bool escOn, int myescTime); + + +/** + * NOT A LIBRARY FUNCTION + * + * Poke supplied colours into the DAC queue. + */ +static void setTextPal(COLORREF col) { +	s_talkfontColor = col; +	UpdateDACqueue(TALKFONT_COL, 1, &s_talkfontColor); +} + + +static int TextTime(char *pTstring) { +	if (isJapanMode()) +		return JAP_TEXT_TIME; +	else if (!speedText) +		return strlen(pTstring) + ONE_SECOND; +	else +		return strlen(pTstring) + ONE_SECOND + (speedText * 5 * ONE_SECOND) / 100; +} + +/*--------------------------------------------------------------------------*/ + + +/** + * Set actor's attributes. + * - currently only the text colour. + */ +void actorattr(int actor, int r1, int g1, int b1) { +	storeActorAttr(actor, r1, g1, b1); +} + +/** + * Return the actor's direction. + */ +int actordirection(int actor) { +	PMACTOR pActor; + +	pActor = GetMover(actor); +	assert(pActor != NULL); // not a moving actor + +	return (int)GetMActorDirection(pActor); +} + +/** + * Return the actor's scale. + */ +int actorscale(int actor) { +	PMACTOR pActor; + +	pActor = GetMover(actor); +	assert(pActor != NULL); // not a moving actor + +	return (int)GetMActorScale(pActor); +} + +/** + * Returns the x or y position of an actor. + */ +int actorpos(int xory, int actor) { +	int x, y; + +	GetActorPos(actor, &x, &y); +	return (xory == ACTORXPOS) ? x : y; +} + +/** + * Make all actors alive at the start of each scene. + */ +void actorson(void) { +	setactorson(); +} + +/** + * Adds an icon to the conversation window. + */ +void addicon(int icon) { +	AddToInventory(INV_CONV, icon, false); +} + +/** + * Place the object in inventory 1 or 2. + */ +void addinv(int invno, int object) { +	assert(invno == INV_1 || invno == INV_2 || invno == INV_OPEN); // illegal inventory number + +	AddToInventory(invno, object, false); +} + +/** + * Define an actor's walk and stand reels for an auxilliary scale. + */ +void auxscale(int actor, int scale, SCNHANDLE *rp) { +	PMACTOR pActor; + +	pActor = GetMover(actor); +	assert(pActor); // Can't set aux scale for a non-moving actor + +	int j; +	for (j = 0; j < 4; ++j) +		pActor->WalkReels[scale-1][j] = *rp++; +	for (j = 0; j < 4; ++j) +		pActor->StandReels[scale-1][j] = *rp++; +	for (j = 0; j < 4; ++j) +		pActor->TalkReels[scale-1][j] = *rp++; +} + +/** + * Defines the background image for a scene. + */ +void background(SCNHANDLE bfilm) { +	startupBackground(bfilm); +} + +/** + * Sets focus of the scroll process. + */ +void camera(int actor) { +	ScrollFocus(actor); +} + +/** + * A CDPLAY() is imminent. + */ +void cdload(SCNHANDLE start, SCNHANDLE next) { +	assert(start && next && start != next); // cdload() fault + +// TODO/FIXME +//	LoadExtraGraphData(start, next); +} + +/** + * Clear the hooked scene (if any) + */ + +void clearhookscene() { +	SetHookScene(0, 0, 0); +} + +/** + * Guess what. + */ + +void closeinventory(void) { +	KillInventory(); +} + +/** + * Turn off cursor and take control from player - and variations on the	 theme. + *  OR Restore cursor and return control to the player. + */ + +void control(int param) { +	bEnableF1 = false; + +	switch (param) { +	case CONTROL_STARTOFF: +		GetControlToken();	// Take control +		DisableTags();			// Switch off tags +		DwHideCursor();			// Blank out cursor +		offtype = param; +		break; + +	case CONTROL_OFF: +	case CONTROL_OFFV: +	case CONTROL_OFFV2: +		if (TestToken(TOKEN_CONTROL)) { +			GetControlToken();	// Take control + +			DisableTags();			// Switch off tags +			GetCursorXYNoWait(&controlX, &controlY, true);	// Store cursor position + +			// There may be a button timing out +			GetToken(TOKEN_LEFT_BUT); +			FreeToken(TOKEN_LEFT_BUT); +		} + +		if (offtype == CONTROL_STARTOFF) +			GetCursorXYNoWait(&controlX, &controlY, true);	// Store cursor position + +		offtype = param; + +		if (param == CONTROL_OFF) +			DwHideCursor();		// Blank out cursor +		else if (param == CONTROL_OFFV) { +			UnHideCursor(); +			FreezeCursor(); +		} else if (param == CONTROL_OFFV2) { +			UnHideCursor(); +		} +		break; + +	case CONTROL_ON: +		if (offtype != CONTROL_OFFV2 && offtype != CONTROL_STARTOFF) +			SetCursorXY(controlX, controlY);// ... where it was + +		FreeControlToken();	// Release control + +		if (!InventoryActive()) +			EnableTags();		// Tags back on + +		RestoreMainCursor();		// Re-instate cursor... +	} +} + +/** + * Open or close the conversation window. + */ + +void conversation(int fn, HPOLYGON hp, bool escOn, int myescEvent) { +	assert(hp != NOPOLY); // conversation() must (currently) be called from a polygon code block + +	switch (fn) { +	case CONV_END:			// Close down conversation +		CloseDownConv(); +		break; + +	case CONV_DEF:			// Default (i.e. TOP of screen) +	case CONV_BOTTOM:		// BOTTOM of screen +		// Don't do it if it's not wanted +		if (escOn && myescEvent != GetEscEvents()) +			break; + +		if (IsConvWindow()) +			break; + +		KillInventory(); +		convPos(fn); +		ConvPoly(hp); +		PopUpInventory(INV_CONV);	// Conversation window +		ConvAction(INV_OPENICON);	// CONVERSATION event +		break; +	} +} + +/** + * Add icon to conversation window's permanent default list. + */ + +void convicon(int icon) { +	AddIconToPermanentDefaultList(icon); +} + +/** + * Returns the x or y position of the cursor. + */ + +int cursorpos(int xory) { +	int x, y; + +	GetCursorXY(&x, &y, true); +	return (xory == CURSORXPOS) ? x : y; +} + +/** + * Declare conversation window. + */ + +void dec_convw(SCNHANDLE text, int MaxContents, int MinWidth, int MinHeight, +			int StartWidth, int StartHeight, int MaxWidth, int MaxHeight) { +	idec_convw(text, MaxContents, MinWidth, MinHeight, +			StartWidth, StartHeight, MaxWidth, MaxHeight); +} + +/** + * Declare config strings. + */ + +void dec_cstrings(SCNHANDLE *tp) { +	setConfigStrings(tp); +} + +/** + * Declare cursor's reels. + */ + +void dec_cursor(SCNHANDLE bfilm) { +	DwInitCursor(bfilm); +} + +/** + * Declare the language flags. + */ + +void dec_flags(SCNHANDLE hf) { +	setFlagFilms(hf); +} + +/** + * Declare inventory 1's parameters. + */ + +void dec_inv1(SCNHANDLE text, int MaxContents, +		int MinWidth, int MinHeight, +		int StartWidth, int StartHeight, +		int MaxWidth, int MaxHeight) { +	idec_inv1(text, MaxContents, MinWidth, MinHeight, +			StartWidth, StartHeight, MaxWidth, MaxHeight); +} + +/** + * Declare inventory 2's parameters. + */ + +void dec_inv2(SCNHANDLE text, int MaxContents, +		int MinWidth, int MinHeight, +		int StartWidth, int StartHeight, +		int MaxWidth, int MaxHeight) { +	idec_inv2(text, MaxContents, MinWidth, MinHeight, +			StartWidth, StartHeight, MaxWidth, MaxHeight); +} + +/** + * Declare the bits that the inventory windows are constructed from. + */ + +void dec_invw(SCNHANDLE hf) { +	setInvWinParts(hf); +} + +/** + * Declare lead actor. + * - the actor's id, walk and stand reels for all the regular scales, + * and the tag text. + */ + +void dec_lead(uint32 id, SCNHANDLE *rp, SCNHANDLE text) { +	PMACTOR	pActor;		// Moving actor structure + +	Tag_Actor(id, text, TAG_DEF);	// The lead actor is automatically tagged +	setleadid(id);			// Establish this as the lead +	SetMover(id);			// Establish as a moving actor + +	pActor = GetMover(id);		// Get moving actor structure +	assert(pActor); + +	// Store all those reels +	int i, j; +	for (i = 0; i < 5; ++i) { +		for (j = 0; j < 4; ++j) +			pActor->WalkReels[i][j] = *rp++; +		for (j = 0; j < 4; ++j) +			pActor->StandReels[i][j] = *rp++; +		for (j = 0; j < 4; ++j) +			pActor->TalkReels[i][j] = *rp++; +	} + + +	for (i = NUM_MAINSCALES; i < TOTAL_SCALES; i++) { +		for (j = 0; j < 4; ++j) { +			pActor->WalkReels[i][j] = pActor->WalkReels[4][j]; +			pActor->StandReels[i][j] = pActor->StandReels[2][j]; +			pActor->TalkReels[i][j] = pActor->TalkReels[4][j]; +		} +	} +} + +/** + * Declare the text font. + */ + +void dec_tagfont(SCNHANDLE hf) { +	TagFontHandle(hf);		// Store the font handle +} + +/** + * Declare the text font. + */ + +void dec_talkfont(SCNHANDLE hf) { +	TalkFontHandle(hf);		// Store the font handle +} + +/** + * Remove an icon from the conversation window. + */ + +void delicon(int icon) { +	RemFromInventory(INV_CONV, icon); +} + +/** + * Delete the object from inventory 1 or 2. + */ + +void delinv(int object) { +	if (!RemFromInventory(INV_1, object))		// Remove from inventory 1... +		RemFromInventory(INV_2, object);		// ...or 2 (whichever) + +	DropItem(object);			// Stop holding it +} + +/** + * enablef1 + */ + +void enablef1(void) { +	bEnableF1 = true; +} + +/** + * fademidi(in/out) + */ + +void fademidi(CORO_PARAM, int inout) { +	CORO_BEGIN_CONTEXT; +	CORO_END_CONTEXT(_ctx); + +	CORO_BEGIN_CODE(_ctx); +	assert(inout == FM_IN || inout == FM_OUT); + +	// To prevent compiler complaining +	if (inout == FM_IN || inout == FM_OUT) +		CORO_SLEEP(1); +	CORO_END_CODE; +} + +/** + * Guess what. + */ + +int getinvlimit(int invno) { +	return InvGetLimit(invno); +} + +/** + * Returns TRUE if the game has been restarted, FALSE if not. + */ +bool hasrestarted(void) { +	return bHasRestarted; +} + +/** + * Returns which object is currently held. + */ + +int heldobject(void) { +	return WhichItemHeld(); +} + +/** + * Removes a player from the screen, probably when he's about to be + * replaced by an animation. + * + * Not believed to work anymore! (hide() is not used). + */ + +void hide(int actor) { +	HideActor(actor); +} + +/** + * hookscene(scene, entrance, transition) + */ + +void hookscene(SCNHANDLE scene, int entrance, int transition) { +	SetHookScene(scene, entrance, transition); +} + +/** + * idletime + */ + +int idletime(void) { +	uint32	x; + +	x = getUserEventTime() / ONE_SECOND; +	 +	if (!TestToken(TOKEN_CONTROL)) +		resetidletime(); + +	return (int)x; +} + +/** + * invdepict + */ +void invdepict(int object, SCNHANDLE hFilm) { +	invObjectFilm(object, hFilm); +} + +/** + * See if an object is in the inventory. + */ +int ininventory(int object) { +	return (InventoryPos(object) != INV_NOICON); +} + +/** + * Open an inventory. + */ +void inventory(int invno, bool escOn, int myescEvent) { +	// Don't do it if it's not wanted +	if (escOn && myescEvent != GetEscEvents()) +		return; + +	assert((invno == INV_1 || invno == INV_2)); // Trying to open illegal inventory + +	PopUpInventory(invno); +} + +/** + * See if an object is in the inventory. + */ +int inwhichinv(int object) { +	if (WhichItemHeld() == object) +		return 0; + +	if (IsInInventory(object, INV_1)) +		return 1; + +	if (IsInInventory(object, INV_2)) +		return 2; + +	return -1; +} + +/** + * Kill an actor. + */ +void killactor(int actor) { +	DisableActor(actor); +} + +/** + * Turn a blocking polygon off. + */ +void killblock(int block) { +	DisableBlock(block); +} + +/** + * Turn an exit off. + */ +void killexit(int exit) { +	DisableExit(exit); +} + +/** + * Turn a tag off. + */ +void killtag(int tagno) { +	DisableTag(tagno); +} + +/** + * Returns the left or top offset of the screen. + */ +int ltoffset(int lort) { +	int Loffset, Toffset; + +	PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); +	return (lort == LEFTOFFSET) ? Loffset : Toffset; +} + +/** + * Set new cursor position. + */ +void movecursor(int x, int y) { +	SetCursorXY(x, y); + +	controlX = x;		// Save these values so that +	controlY = y;		// control(on) doesn't undo this +} + +/** + * Triggers change to a new scene. + */ +void newscene(CORO_PARAM, SCNHANDLE scene, int entrance, int transition) { +	// COROUTINE +	CORO_BEGIN_CONTEXT; +	CORO_END_CONTEXT(_ctx); + +	CORO_BEGIN_CODE(_ctx); + +#ifdef BODGE +	if (!ValidHandle(scene)) { +		scene = GetSceneHandle(); +		entrance = 1; +	} +	assert(scene); // Non-existant first scene! +#endif + +	SetNewScene(scene, entrance, transition); + +#if 1 +	// Prevent tags and cursor re-appearing +	GetControl(CONTROL_STARTOFF); +#endif + +	// Prevent code subsequent to this call running before scene changes +	if (g_scheduler->getCurrentPID() != PID_MASTER_SCR) +		CORO_KILL_SELF(); +	CORO_END_CODE; +} + +/** + * Disable dynamic blocking for current scene. + */ +void noblocking(void) { +	bNoBlocking = true; +} + +/** + * Define a no-scroll boundary for the current scene. + */ +void noscroll(int x1, int y1, int x2, int y2) { +	SetNoScroll(x1, y1, x2, y2); +} + +/** + * Hold the specified object. + */ +void objectheld(int object) { +	HoldItem(object); +} + +/** + * Set the top left offset of the screen. + */ +void offset(int x, int y) { +	KillScroll(); +	PlayfieldSetPos(FIELD_WORLD, x, y); +} + +/** + * Play a film. + */ +void play(CORO_PARAM, SCNHANDLE film, int x, int y, int compit, int actorid, bool splay, int sfact, +		  bool escOn, int myescEvent, bool bTop) { +	// COROUTINE +	CORO_BEGIN_CONTEXT; +	CORO_END_CONTEXT(_ctx); + +	CORO_BEGIN_CODE(_ctx); + +	assert(film != 0); // play(): Trying to play NULL film + +	// Don't do it if it's not wanted +	if (escOn && myescEvent != GetEscEvents()) +		return; + +	// If this actor is dead, call a stop to the calling process +	if (actorid && !actorAlive(actorid)) +		CORO_KILL_SELF(); + +	// 7/4/95 +	if (!escOn) +		myescEvent = GetEscEvents(); + +	if (compit == 1) { +		// Play to completion before returning +		CORO_INVOKE_ARGS(playFilmc, (CORO_SUBCTX, film, x, y, actorid, splay, sfact, escOn, myescEvent, bTop)); +	} else if (compit == 2) { +		error("play(): compit == 2 - please advise John"); +	} else { +		// Kick off the play and return. +		playFilm(film, x, y, actorid, splay, sfact, escOn, myescEvent, bTop); +	} +	CORO_END_CODE; +} + +/** + * Play a midi file. + */ +void playmidi(CORO_PARAM, SCNHANDLE hMidi, int loop, bool complete) { +	// FIXME: This is a workaround for the FIXME below +	if (GetMidiVolume() == 0) +		return; + +	CORO_BEGIN_CONTEXT; +	CORO_END_CONTEXT(_ctx); + +	CORO_BEGIN_CODE(_ctx); +	assert(loop == MIDI_DEF || loop == MIDI_LOOP); + +	PlayMidiSequence(hMidi, loop == MIDI_LOOP); + +	// FIXME: The following check messes up the script arguments when +	// entering the secret door in the bookshelf in the library, +	// leading to a crash, when the music volume is set to 0 (MidiPlaying() +	// always false then). +	// +	// Why exactly this happens is unclear. An analysis of the involved +	// script(s) might reveal more. +	// +	// Note: This check&sleep was added in DW v2. It was most likely added +	// to ensure that the MIDI song started playing before the next opcode +	// is executed. +	if (!MidiPlaying()) +		CORO_SLEEP(1); + +	if (complete) { +		while (MidiPlaying()) +			CORO_SLEEP(1); +	} +	CORO_END_CODE; +} + +/** + * Play a sample. + */ +void playsample(CORO_PARAM, int sample, bool complete, bool escOn, int myescEvent) { +	CORO_BEGIN_CONTEXT; +		Audio::SoundHandle handle; +	CORO_END_CONTEXT(_ctx); + +	CORO_BEGIN_CODE(_ctx); +	// Don't play SFX if voice is already playing +	if (_vm->_mixer->hasActiveChannelOfType(Audio::Mixer::kSpeechSoundType)) +		return; + +	// Don't do it if it's not wanted +	if (escOn && myescEvent != GetEscEvents()) { +		_vm->_sound->stopAllSamples();		// Stop any currently playing sample +		return; +	} + +	if (volSound != 0 && _vm->_sound->sampleExists(sample)) { +		_vm->_sound->playSample(sample, Audio::Mixer::kSFXSoundType, &_ctx->handle); + +		if (complete) { +			while (_vm->_mixer->isSoundHandleActive(_ctx->handle)) { +				// Abort if escapable and ESCAPE is pressed +				if (escOn && myescEvent != GetEscEvents()) { +					_vm->_mixer->stopHandle(_ctx->handle); +					break; +				} + +				CORO_SLEEP(1); +			} +		} +	} else { +		// Prevent Glitter lock-up +		CORO_SLEEP(1); +	} +	CORO_END_CODE; +} + +/** + * Play a sample. + */ +void tryplaysample(CORO_PARAM, int sample, bool complete, bool escOn, int myescEvent) { +	CORO_BEGIN_CONTEXT; +	CORO_END_CONTEXT(_ctx); + +	CORO_BEGIN_CODE(_ctx); +	// Don't do it if it's not appropriate +	if (_vm->_sound->sampleIsPlaying()) { +		// return, but prevent Glitter lock-up +		CORO_SLEEP(1); +		return; +	} + +	CORO_INVOKE_ARGS(playsample, (CORO_SUBCTX, sample, complete, escOn, myescEvent)); +	CORO_END_CODE; +} + +/** + * Trigger pre-loading of a scene's data. + */ +void preparescene(SCNHANDLE scene) { +#ifdef BODGE +	if (!ValidHandle(scene)) +		return; +#endif +} + +/** + * Print the given text at the given place for the given time. + * + * Print(....., h) -> hold = 1 (not used) + * Print(....., s) -> hold = 2 (sustain) + */ +void print(CORO_PARAM, int x, int y, SCNHANDLE text, int time, int hold, bool escOn, int myescEvent) { +	CORO_BEGIN_CONTEXT; +		OBJECT *pText;			// text object pointer +		int	myleftEvent; +		bool bSample;			// Set if a sample is playing +		Audio::SoundHandle handle; +		int timeout; +		int time; +	CORO_END_CONTEXT(_ctx); + +	bool	bJapDoPrintText;	// Bodge to get-around Japanese bodge + +	CORO_BEGIN_CODE(_ctx); + +	_ctx->pText = NULL; +	_ctx->bSample = false; + +	// Don't do it if it's not wanted +	if (escOn && myescEvent != GetEscEvents()) +		return; + +	// Kick off the voice sample +	if (volVoice != 0 && _vm->_sound->sampleExists(text)) { +		_vm->_sound->playSample(text, Audio::Mixer::kSpeechSoundType, &_ctx->handle); +		_ctx->bSample = _vm->_mixer->isSoundHandleActive(_ctx->handle); +	} + +	// Calculate display time +	LoadStringRes(text, tBufferAddr(), TBUFSZ); +	bJapDoPrintText = false; +	if (time == 0) { +		// This is a 'talky' print +		_ctx->time = TextTime(tBufferAddr()); +		 +		// Cut short-able if sustain was not set +		_ctx->myleftEvent = (hold == 2) ? 0 : GetLeftEvents(); +	} else { +		_ctx->time = time * ONE_SECOND; +		_ctx->myleftEvent = 0; +		if (isJapanMode()) +			bJapDoPrintText = true; +	} + +	// Print the text +	if (bJapDoPrintText || (!isJapanMode() && (bSubtitles || !_ctx->bSample))) { +		int Loffset, Toffset;	// Screen position +		PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); +		_ctx->pText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), +					0, x - Loffset, y - Toffset, hTalkFontHandle(), TXT_CENTRE); +		assert(_ctx->pText); // string produced NULL text +		if (IsTopWindow()) +			MultiSetZPosition(_ctx->pText, Z_TOPW_TEXT); + +		/* +		 * New feature: Don't go off the side of the background +		 */ +		int	shift; +		shift = MultiRightmost(_ctx->pText) + 2; +		if (shift >= BackgroundWidth())			// Not off right +			MultiMoveRelXY(_ctx->pText, BackgroundWidth() - shift, 0); +		shift = MultiLeftmost(_ctx->pText) - 1; +		if (shift <= 0)					// Not off left +			MultiMoveRelXY(_ctx->pText, -shift, 0); +		shift = MultiLowest(_ctx->pText); +		if (shift > BackgroundHeight())			// Not off bottom +			MultiMoveRelXY(_ctx->pText, 0, BackgroundHeight() - shift); +	} + +	// Give up if nothing printed and no sample +	if (_ctx->pText == NULL && !_ctx->bSample) +		return; + +	// Leave it up until time runs out or whatever +	_ctx->timeout = SAMPLETIMEOUT; +	do { +		CORO_SLEEP(1); + +		// Abort if escapable and ESCAPE is pressed +		// Abort if left click - hardwired feature for talky-print! +		// Will be ignored if myleftevent happens to be 0! +		// Abort if sample times out +		if ((escOn && myescEvent != GetEscEvents()) +		|| (_ctx->myleftEvent && _ctx->myleftEvent != GetLeftEvents()) +		|| (_ctx->bSample && --_ctx->timeout <= 0)) +			break; + +		if (_ctx->bSample) { +			// Wait for sample to end whether or not +			if (!_vm->_mixer->isSoundHandleActive(_ctx->handle)) { +				if (_ctx->pText == NULL || speedText == DEFTEXTSPEED) 				{ +					// No text or speed modification - just depends on sample +					break; +				} else { +					// Must wait for time +					_ctx->bSample = false; +				} +			} +		} else { +			// No sample - just depends on time +			if (_ctx->time-- <= 0) +				break; +		} + +	} while (1); + +	// Delete the text +	if (_ctx->pText != NULL) +		MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText); +	_vm->_mixer->stopHandle(_ctx->handle); + +	CORO_END_CODE; +} + + +static void printobjPointed(CORO_PARAM, const SCNHANDLE text, const INV_OBJECT *pinvo, OBJECT *&pText, const int textx, const int texty, const int item); +static void printobjNonPointed(CORO_PARAM, const SCNHANDLE text, const OBJECT *pText); + +/** + * Print the given inventory object's name or whatever. + */ +void printobj(CORO_PARAM, const SCNHANDLE text, const INV_OBJECT *pinvo, const int event) { +	CORO_BEGIN_CONTEXT; +		OBJECT *pText;		// text object pointer +		int	textx, texty; +		int	item; +	CORO_END_CONTEXT(_ctx); + +	CORO_BEGIN_CODE(_ctx); + +	assert(pinvo != 0); // printobj() may only be called from an object code block + +	if (text == (SCNHANDLE)-1) {	// 'OFF' +		NotPointedRunning = true; +		return; +	} +	if (text == (SCNHANDLE)-2) {	// 'ON' +		NotPointedRunning = false; +		return; +	} + +	GetCursorXY(&_ctx->textx, &_ctx->texty, false);	// Cursor position.. +	_ctx->item = InvItem(&_ctx->textx, &_ctx->texty, true);	// ..to text position + +	if (_ctx->item == INV_NOICON) +		return; + +	if (event != POINTED) { +		NotPointedRunning = true;	// Get POINTED text to die +		CORO_SLEEP(1);		// Give it chance to +	} else +		NotPointedRunning = false;	// There may have been an OFF without an ON + +	// Display the text and set it's Z position +	if (event == POINTED || (!isJapanMode() && (bSubtitles || !_vm->_sound->sampleExists(text)))) { +		int	xshift; + +		LoadStringRes(text, tBufferAddr(), TBUFSZ);	// The text string +		_ctx->pText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), +					0, _ctx->textx, _ctx->texty, hTagFontHandle(), TXT_CENTRE); +		assert(_ctx->pText); // printobj() string produced NULL text +		MultiSetZPosition(_ctx->pText, Z_INV_ITEXT); + +		// Don't go off the side of the screen +		xshift = MultiLeftmost(_ctx->pText); +		if (xshift < 0) { +			MultiMoveRelXY(_ctx->pText, - xshift, 0); +			_ctx->textx -= xshift; +		} +		xshift = MultiRightmost(_ctx->pText); +		if (xshift > SCREEN_WIDTH) { +			MultiMoveRelXY(_ctx->pText, SCREEN_WIDTH - xshift, 0); +			_ctx->textx += SCREEN_WIDTH - xshift; +		} +	} else +		_ctx->pText = NULL; + +	if (event == POINTED) { +		// FIXME: Is there ever an associated sound if in POINTED mode??? +		assert(!_vm->_sound->sampleExists(text)); +		CORO_INVOKE_ARGS(printobjPointed, (CORO_SUBCTX, text, pinvo, _ctx->pText, _ctx->textx, _ctx->texty, _ctx->item)); +	} else { +		CORO_INVOKE_2(printobjNonPointed, text, _ctx->pText); +	} + +	// Delete the text, if haven't already +	if (_ctx->pText) +		MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText); + +	CORO_END_CODE; +} + +static void printobjPointed(CORO_PARAM, const SCNHANDLE text, const INV_OBJECT *pinvo, OBJECT *&pText, const int textx, const int texty, const int item) { +	CORO_BEGIN_CONTEXT; +	CORO_END_CONTEXT(_ctx); + +	CORO_BEGIN_CODE(_ctx); +		// Have to give way to non-POINTED-generated text +		// and go away if the item gets picked up +		int	x, y; +		do { +			// Give up if this item gets picked up +			if (WhichItemHeld() == pinvo->id) +				break; + +			// Give way to non-POINTED-generated text +			if (NotPointedRunning) { +				// Delete the text, and wait for the all-clear +				MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), pText); +				pText = NULL; +				while (NotPointedRunning) +					CORO_SLEEP(1); + +				GetCursorXY(&x, &y, false); +				if (InvItem(&x, &y, false) != item) +					break; + +				// Re-display in the same place +				LoadStringRes(text, tBufferAddr(), TBUFSZ); +				pText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), +							0, textx, texty, hTagFontHandle(), TXT_CENTRE); +				assert(pText); // printobj() string produced NULL text +				MultiSetZPosition(pText, Z_INV_ITEXT); +			} + +			CORO_SLEEP(1); + +			// Carry on until the cursor leaves this icon +			GetCursorXY(&x, &y, false); +		} while (InvItemId(x, y) == pinvo->id); + +	CORO_END_CODE; +} + +static void printobjNonPointed(CORO_PARAM, const SCNHANDLE text, const OBJECT *pText) { +	CORO_BEGIN_CONTEXT; +		bool bSample;		// Set if a sample is playing +		Audio::SoundHandle handle; + +		int myleftEvent; +		bool took_control; +		int	ticks; +		int	timeout; +	CORO_END_CONTEXT(_ctx); + +	CORO_BEGIN_CODE(_ctx); +		// Kick off the voice sample +		if (volVoice != 0 && _vm->_sound->sampleExists(text)) { +			_vm->_sound->playSample(text, Audio::Mixer::kSpeechSoundType, &_ctx->handle); +			_ctx->bSample = _vm->_mixer->isSoundHandleActive(_ctx->handle); +		} else +			_ctx->bSample = false; + +		_ctx->myleftEvent = GetLeftEvents(); +		_ctx->took_control = GetControl(CONTROL_OFF); + +		// Display for a time, but abort if conversation gets hidden +		if (isJapanMode()) +			_ctx->ticks = JAP_TEXT_TIME; +		else if (pText) +			_ctx->ticks = TextTime(tBufferAddr()); +		else +			_ctx->ticks = 0; + +		_ctx->timeout = SAMPLETIMEOUT; +		do { +			CORO_SLEEP(1); +			--_ctx->timeout; + +			// Abort if left click - hardwired feature for talky-print! +			// Abort if sample times out +			// Abort if conversation hidden +			if (_ctx->myleftEvent != GetLeftEvents() || _ctx->timeout <= 0 || convHid()) +				break; + +			if (_ctx->bSample) { +				// Wait for sample to end whether or not +				if (!_vm->_mixer->isSoundHandleActive(_ctx->handle)) { +					if (pText == NULL || speedText == DEFTEXTSPEED) { +						// No text or speed modification - just depends on sample +						break; +					} else { +						// Must wait for time +						_ctx->bSample = false; +					} +				} +			} else { +				// No sample - just depends on time +				if (_ctx->ticks-- <= 0) +					break; +			} +		} while (1); + +		NotPointedRunning = false;	// Let POINTED text back in + +		if (_ctx->took_control) +			control(CONTROL_ON);	// Free control if we took it + +		_vm->_mixer->stopHandle(_ctx->handle); + +	CORO_END_CODE; +} + +/** + * Register the fact that this poly would like its tag displayed. + */ +void printtag(HPOLYGON hp, SCNHANDLE text) { +	assert(hp != NOPOLY); // printtag() may only be called from a polygon code block + +	if (PolyTagState(hp) == TAG_OFF) { +		SetPolyTagState(hp, TAG_ON); +		SetPolyTagHandle(hp, text); +	} +} + +/** + * quitgame + */ +void quitgame(void) { +	stopmidi(); +	stopsample(); +	_vm->quitFlag = true; +} + +/** + * Return a random number between optional limits. + */ +int dw_random(int n1, int n2, int norpt) { +	int i = 0; +	uint32 value; + +	do { +		value = n1 + _vm->getRandomNumber(n2 - n1); +	} while ((lastValue == value) && (norpt == RAND_NORPT) && (++i <= 10)); + +	lastValue = value; +	return value; +} + +/** + * resetidletime + */ +void resetidletime(void) { +	resetUserEventTime(); +} + +/** + * restartgame + */ +void restartgame(void) { +	stopmidi(); +	stopsample(); +	bRestart = true; +} + +/** + * Restore saved scene. + */ +void restore_scene(bool bFade) { +	UnSuspendHook(); +	PleaseRestoreScene(bFade); +} + +/** + * runmode + */ +int runmode(void) { +	return clRunMode; +} + +/** + * sampleplaying + */ +bool sampleplaying(bool escOn, int myescEvent) { +	// escape effects introduced 14/12/95 to fix +	//	 while (sampleplaying()) pause; + +	if (escOn && myescEvent != GetEscEvents()) +		return false; + +	return _vm->_sound->sampleIsPlaying(); +} + +/** + * Save current scene. + */ +void save_scene(CORO_PARAM) { +	PleaseSaveScene(coroParam); +	SuspendHook(); +} + +/** + * scalingreels + */ +void scalingreels(int actor, int scale, int direction, +		SCNHANDLE left, SCNHANDLE right, SCNHANDLE forward, SCNHANDLE away) { + +	setscalingreels(actor, scale, direction, left, right, forward, away); +} + +/** + * Return the icon that caused the CONVERSE event. + */ + +int scanicon(void) { +	return convIcon(); +} + +/** + * Scroll the screen to target co-ordinates. + */ + +void scroll(CORO_PARAM, int x, int y, int iter, bool comp, bool escOn, int myescEvent) { +	CORO_BEGIN_CONTEXT; +		int	mycount; +	CORO_END_CONTEXT(_ctx); + +	CORO_BEGIN_CODE(_ctx); +	if (escOn && myescEvent != GetEscEvents()) { +		// Instant completion! +		offset(x, y); +	} else { +		_ctx->mycount = ++scrollCount; + +		ScrollTo(x, y, iter); + +		if (comp) { +			int	Loffset, Toffset; +			do { +				CORO_SLEEP(1); + +				// If escapable and ESCAPE is pressed... +				if (escOn && myescEvent != GetEscEvents()) { +					// Instant completion! +					offset(x, y); +					break; +				} + +				// give up if have been superseded +				if (_ctx->mycount != scrollCount) +					CORO_KILL_SELF(); + +				PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); +			} while (Loffset != x || Toffset != y); +		} +	} +	CORO_END_CODE; +} + +/** + * Un-kill an actor. + */ +void setactor(int actor) { +	EnableActor(actor); +} + +/** + * Turn a blocking polygon on. + */ + +void setblock(int blockno) { +	EnableBlock(blockno); +} + +/** + * Turn an exit on. + */ + +void setexit(int exitno) { +	EnableExit(exitno); +} + +/** + * Guess what. + */ +void setinvlimit(int invno, int n) { +	InvSetLimit(invno, n); +} + +/** + * Guess what. + */ +void setinvsize(int invno, int MinWidth, int MinHeight, +		int StartWidth, int StartHeight, int MaxWidth, int MaxHeight) { +	InvSetSize(invno, MinWidth, MinHeight, StartWidth, StartHeight, MaxWidth, MaxHeight); +} + +/** + * Guess what. + */ +void setlanguage(LANGUAGE lang) { +	assert(lang == TXT_ENGLISH || lang == TXT_FRENCH +	     || lang == TXT_GERMAN  || lang == TXT_ITALIAN +	     || lang == TXT_SPANISH); // ensure language is valid + +	ChangeLanguage(lang); +} + +/** + * Set palette + */ +void setpalette(SCNHANDLE hPal, bool escOn, int myescEvent) { +	// Don't do it if it's not wanted +	if (escOn && myescEvent != GetEscEvents()) +		return; + +	ChangePalette(hPal); +} + +/** + * Turn a tag on. + */ +void settag(int tagno) { +	EnableTag(tagno); +} + +/** + * Initialise a timer. + */ +void settimer(int timerno, int start, bool up, bool frame) { +	DwSetTimer(timerno, start, up != 0, frame != 0); +} + +#ifdef DEBUG +/** + * Enable display of diagnostic co-ordinates. + */ +void showpos(void) { +	setshowpos(); +} + +/** + * Enable display of diagnostic co-ordinates. + */ +void showstring(void) { +	setshowstring(); +} +#endif + +/** + * Special play - slow down associated actor's movement while the play + * is running. After the play, position the actor where the play left + * it and continue walking, if the actor still is. + */ + +void splay(CORO_PARAM, int sf, SCNHANDLE film, int x, int y, bool complete, int actorid, bool escOn, int myescEvent) { +	// Don't do it if it's not wanted +	if (escOn && myescEvent != GetEscEvents()) +		return; + +	play(coroParam, film, x, y, complete, actorid, true, sf, escOn, myescEvent, false); +} + +/** + * (Re)Position an actor. + * If moving actor is not around yet in this scene, start it up. + */ + +void stand(int actor, int x, int y, SCNHANDLE film) { +	PMACTOR pActor;		// Moving actor structure + +	pActor = GetMover(actor); +	if (pActor) { +		if (pActor->MActorState == NO_MACTOR) { +			// create a moving actor process +			MActorProcessCreate(x, y, (actor == LEAD_ACTOR) ? LeadId() : actor, pActor); + +			if (film == TF_NONE) { +				SetMActorStanding(pActor); +			} else { +				switch (film) { +				case TF_NONE: +					break; +	 +				case TF_UP: +					SetMActorDirection(pActor, AWAY); +					SetMActorStanding(pActor); +					break; +				case TF_DOWN: +					SetMActorDirection(pActor, FORWARD); +					SetMActorStanding(pActor); +					break; +				case TF_LEFT: +					SetMActorDirection(pActor, LEFTREEL); +					SetMActorStanding(pActor); +					break; +				case TF_RIGHT: +					SetMActorDirection(pActor, RIGHTREEL); +					SetMActorStanding(pActor); +					break; +	 +				default: +					AlterMActor(pActor, film, AR_NORMAL); +					break; +				} +			} +		} else { +			switch (film) { +			case TF_NONE: +				if (x != -1 && y != -1) +					MoveMActor(pActor, x, y); +				break; + +			case TF_UP: +				SetMActorDirection(pActor, AWAY); +				if (x != -1 && y != -1) +					MoveMActor(pActor, x, y); +				SetMActorStanding(pActor); +				break; +			case TF_DOWN: +				SetMActorDirection(pActor, FORWARD); +				if (x != -1 && y != -1) +					MoveMActor(pActor, x, y); +				SetMActorStanding(pActor); +				break; +			case TF_LEFT: +				SetMActorDirection(pActor, LEFTREEL); +				if (x != -1 && y != -1) +					MoveMActor(pActor, x, y); +				SetMActorStanding(pActor); +				break; +			case TF_RIGHT: +				SetMActorDirection(pActor, RIGHTREEL); +				if (x != -1 && y != -1) +					MoveMActor(pActor, x, y); +				SetMActorStanding(pActor); +				break; + +			default: +				if (x != -1 && y != -1) +					MoveMActor(pActor, x, y); +				AlterMActor(pActor, film, AR_NORMAL); +				break; +			} +		} +	} else if (actor == NULL_ACTOR) { +		// +	} else { +		assert(film != 0); // Trying to play NULL film + +		// Kick off the play and return. +		playFilm(film, x, y, actor, false, 0, false, 0, false); +	} +} + +/** + * Position the actor at the polygon's tag node. + */ +void standtag(int actor, HPOLYGON hp) { +	SCNHANDLE film; +	int	pnodex, pnodey; + +	assert(hp != NOPOLY); // standtag() may only be called from a polygon code block + +	// Lead actor uses tag node film +	film = getPolyFilm(hp); +	getPolyNode(hp, &pnodex, &pnodey); +	if (film && (actor == LEAD_ACTOR || actor == LeadId())) +		stand(actor, pnodex, pnodey, film); +	else +		stand(actor, pnodex, pnodey, 0); +} + +/** + * Kill a moving actor's walk. + */ +void stop(int actor) { +	PMACTOR pActor; + +	pActor = GetMover(actor); +	assert(pActor); // Trying to stop a null actor + +	GetToken(pActor->actorToken);	// Kill the walk process +	pActor->stop = true;			// Cause the actor to stop +	FreeToken(pActor->actorToken); +} + +void stopmidi(void) { +	StopMidi();		// Stop any currently playing midi +} + +void stopsample(void) { +	_vm->_sound->stopAllSamples();		// Stop any currently playing sample +} + +void subtitles(int onoff) { +	assert (onoff == ST_ON || onoff == ST_OFF); + +	if (isJapanMode()) +		return;	// Subtitles are always off in JAPAN version (?) + +	if (onoff == ST_ON) +		bSubtitles = true; +	else +		bSubtitles = false; +} + +/** + * Special walk. + * Walk into or out of a legal path. + */ +void swalk(CORO_PARAM, int actor, int x1, int y1, int x2, int y2, SCNHANDLE film, bool escOn, int myescEvent) { +	CORO_BEGIN_CONTEXT; +		bool	took_control;			// Set if this function takes control +	CORO_END_CONTEXT(_ctx); + +	CORO_BEGIN_CODE(_ctx); + +	// Don't do it if it's not wanted +	if (escOn && myescEvent != GetEscEvents()) +		return; + +	// For lead actor, lock out the user (if not already locked out) +	if (actor == LeadId() || actor == LEAD_ACTOR) +		_ctx->took_control = GetControl(CONTROL_OFFV2); +	else +		_ctx->took_control = false; + +	HPOLYGON hPath; + +	hPath = InPolygon(x1, y1, PATH); +	if (hPath != NOPOLY) { +		// Walking out of a path +		stand(actor, x1, y1, 0); +	} else { +		hPath = InPolygon(x2, y2, PATH); +		// One of them has to be in a path +		assert(hPath != NOPOLY); //one co-ordinate must be in a legal path + +		// Walking into a path +		stand(actor, x2, y2, 0);	// Get path's characteristics +		stand(actor, x1, y1, 0); +	} + +	CORO_INVOKE_ARGS(walk, (CORO_SUBCTX, actor, x2, y2, film, 0, true, escOn, myescEvent)); + +	// Free control if we took it +	if (_ctx->took_control) +		control(CONTROL_ON); + +	CORO_END_CODE; +} + +/** + * Define a tagged actor. + */ + +void tagactor(int actor, SCNHANDLE text, int tp) { +	Tag_Actor(actor, text, tp); +} + +/** + * Text goes over actor's head while actor plays the talk reel. + */ + +void FinishTalkingReel(PMACTOR pActor, int actor) { +	if (pActor) { +		SetMActorStanding(pActor); +		AlterMActor(pActor, 0, AR_POPREEL); +	} else { +		setActorTalking(actor, false); +		playFilm(getActorPlayFilm(actor), -1, -1, 0, false, 0, false, 0, false); +	} +} + +void talk(CORO_PARAM, SCNHANDLE film, const SCNHANDLE text, int actorid, bool escOn, int myescEvent) { +	CORO_BEGIN_CONTEXT; +		int		Loffset, Toffset;	// Top left of display +		int		actor;			// The speaking actor +		PMACTOR	pActor;			// For moving actors +		int		myleftEvent; +		int		ticks; +		bool	bTookControl;	// Set if this function takes control +		bool	bTookTags;		// Set if this function disables tags +		OBJECT	*pText;			// text object pointer +		bool	bSample;		// Set if a sample is playing +		bool	bTalkReel;		// Set while talk reel is playing +		Audio::SoundHandle handle; +		int	timeout; +	CORO_END_CONTEXT(_ctx); + +	CORO_BEGIN_CODE(_ctx); + +	_ctx->Loffset = 0; +	_ctx->Toffset = 0; +	_ctx->ticks = 0; + +	// Don't do it if it's not wanted +	if (escOn && myescEvent != GetEscEvents()) +		return; + +	_ctx->myleftEvent = GetLeftEvents(); + +	// If this actor is dead, call a stop to the calling process +	if (actorid && !actorAlive(actorid)) +		CORO_KILL_SELF(); + +	/* +	 * Find out which actor is talking +	 * and with which direction if no film supplied +	 */ +	TFTYPE	direction; +	switch (film) { +	case TF_NONE: +	case TF_UP: +	case TF_DOWN: +	case TF_LEFT: +	case TF_RIGHT: +		_ctx->actor = LeadId();	// If no film, actor is lead actor +		direction = (TFTYPE)film; +		break; + +	default: +		_ctx->actor = extractActor(film); +		assert(_ctx->actor); // talk() - no actor ID in the reel +		direction = TF_BOGUS; +		break; +	} + +	/* +	 * Lock out the user (for lead actor, if not already locked out) +	 * May need to disable tags for other actors +	 */ +	if (_ctx->actor == LeadId()) +		_ctx->bTookControl = GetControl(CONTROL_OFF); +	else +		_ctx->bTookControl = false; +	_ctx->bTookTags = DisableTagsIfEnabled(); + +	/* +	 * Kick off the voice sample +	 */ +	if (volVoice != 0 && _vm->_sound->sampleExists(text)) { +		_vm->_sound->playSample(text, Audio::Mixer::kSpeechSoundType, &_ctx->handle); +		_ctx->bSample = _vm->_mixer->isSoundHandleActive(_ctx->handle); +	} else +		_ctx->bSample = false; + +	/* +	 * Replace actor with the talk reel, saving the current one +	 */ +	_ctx->pActor = GetMover(_ctx->actor); +	if (_ctx->pActor) { +		if (direction != TF_BOGUS) +			film = GetMactorTalkReel(_ctx->pActor, direction); +		AlterMActor(_ctx->pActor, film, AR_PUSHREEL); +	} else { +		setActorTalking(_ctx->actor, true); +		setActorTalkFilm(_ctx->actor, film); +		playFilm(film, -1, -1, 0, false, 0, escOn, myescEvent, false); +	} +	_ctx->bTalkReel = true; +	CORO_SLEEP(1);		// Allow the play to come in + +	/* +	 * Display the text. +	 */ +	_ctx->pText = NULL; +	if (isJapanMode()) { +		_ctx->ticks = JAP_TEXT_TIME; +	} else if (bSubtitles || !_ctx->bSample) { +		int	aniX, aniY;		// actor position +		int	xshift, yshift; +		/* +		 * Work out where to display the text +		 */ +		PlayfieldGetPos(FIELD_WORLD, &_ctx->Loffset, &_ctx->Toffset); +		GetActorMidTop(_ctx->actor, &aniX, &aniY); +		aniY -= _ctx->Toffset; + +		setTextPal(getActorTcol(_ctx->actor)); +		LoadStringRes(text, tBufferAddr(), TBUFSZ); +		_ctx->pText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), +					0, aniX - _ctx->Loffset, aniY, hTalkFontHandle(), TXT_CENTRE); +		assert(_ctx->pText); // talk() string produced NULL text; +		if (IsTopWindow()) +			MultiSetZPosition(_ctx->pText, Z_TOPW_TEXT); + +		/* +		 * Set bottom of text just above the speaker's head +		 * But don't go off the top of the screen +		 */ +		yshift = aniY - MultiLowest(_ctx->pText) - 2;		// Just above head +		MultiMoveRelXY(_ctx->pText, 0, yshift);		// +		yshift = MultiHighest(_ctx->pText); +		if (yshift < 4) +			MultiMoveRelXY(_ctx->pText, 0, 4 - yshift);	// Not off top + +		/* +		 * Don't go off the side of the screen +		 */ +		xshift = MultiRightmost(_ctx->pText) + 2; +		if (xshift >= SCREEN_WIDTH)			// Not off right +			MultiMoveRelXY(_ctx->pText, SCREEN_WIDTH - xshift, 0); +		xshift = MultiLeftmost(_ctx->pText) - 1; +		if (xshift <= 0)					// Not off left +			MultiMoveRelXY(_ctx->pText, -xshift, 0); +		/* +		 * Work out how long to talk. +		 * During this time, reposition the text if the screen scrolls. +		 */ +		_ctx->ticks = TextTime(tBufferAddr()); +	} + +	_ctx->timeout = SAMPLETIMEOUT; +	do { +		// Keep text in place if scrolling +		if (_ctx->pText != NULL) { +			int	nLoff, nToff; + +			PlayfieldGetPos(FIELD_WORLD, &nLoff, &nToff); +			if (nLoff != _ctx->Loffset || nToff != _ctx->Toffset) { +				MultiMoveRelXY(_ctx->pText, _ctx->Loffset - nLoff, _ctx->Toffset - nToff); +				_ctx->Loffset = nLoff; +				_ctx->Toffset = nToff; +			} +		} + +		CORO_SLEEP(1); +		--_ctx->timeout; + +		// Abort if escapable and ESCAPE is pressed +		// Abort if left click - hardwired feature for talk! +		// Abort if sample times out +		if ((escOn && myescEvent != GetEscEvents()) +				|| (_ctx->myleftEvent != GetLeftEvents()) +				|| (_ctx->timeout <= 0)) +			break; + +		if (_ctx->bSample) { +			// Wait for sample to end whether or not +			if (!_vm->_mixer->isSoundHandleActive(_ctx->handle)) { +				if (_ctx->pText == NULL || speedText == DEFTEXTSPEED) { +					// No text or speed modification - just depends on sample +					break; +				} else { +					// Talk reel stops at end of speech +					FinishTalkingReel(_ctx->pActor, _ctx->actor); +					_ctx->bTalkReel = false; +					_ctx->bSample = false; +				} +			} +		} else { +			// No sample - just depends on time +			if (_ctx->ticks-- <= 0) +				break; +		} +	} while (1); + +	/* +	 * The talk is over now - dump the text +	 * Stop the sample +	 * Restore the actor's film or standing reel +	 */ +	if (_ctx->pText != NULL) +		MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText); +	_vm->_mixer->stopHandle(_ctx->handle); +	if (_ctx->bTalkReel) +		FinishTalkingReel(_ctx->pActor, _ctx->actor); + +	/* +	 * Restore user control and tags, as appropriate +	 * And, finally, release the talk token. +	 */ +	if (_ctx->bTookControl) +		control(CONTROL_ON); +	if (_ctx->bTookTags) +		EnableTags(); + +	CORO_END_CODE; +} + +/** + * talkat(actor, x, y, text) + */ +void talkat(CORO_PARAM, int actor, int x, int y, SCNHANDLE text, bool escOn, int myescEvent) { +	if (!coroParam) { +		// Don't do it if it's not wanted +		if (escOn && myescEvent != GetEscEvents()) +			return; + +		if (!isJapanMode() && (bSubtitles || !_vm->_sound->sampleExists(text))) +			setTextPal(getActorTcol(actor)); +	} + +	print(coroParam, x, y, text, 0, 0, escOn, myescEvent); +} + +/** + * talkats(actor, x, y, text, sustain) + */ +void talkats(CORO_PARAM, int actor, int x, int y, SCNHANDLE text, int sustain, bool escOn, int myescEvent) { +	if (!coroParam) { +		assert(sustain == 2); + +		// Don't do it if it's not wanted +		if (escOn && myescEvent != GetEscEvents()) +			return; + +		if (!isJapanMode()) +			setTextPal(getActorTcol(actor)); +	} + +	print(coroParam, x, y, text, 0, sustain, escOn, myescEvent); +} + +/** + * Set talk font's palette entry. + */ +void talkattr(int r1, int g1, int b1, bool escOn, int myescEvent) { +	if (isJapanMode()) +		return; + +	// Don't do it if it's not wanted +	if (escOn && myescEvent != GetEscEvents()) +		return; + +	if (r1 > MAX_INTENSITY)	r1 = MAX_INTENSITY;	// } Ensure +	if (g1 > MAX_INTENSITY)	g1 = MAX_INTENSITY;	// } within limits +	if (b1 > MAX_INTENSITY)	b1 = MAX_INTENSITY;	// } + +	setTextPal(RGB(r1, g1, b1)); +} + +/** + * Get a timer's current count. + */ +int timer(int timerno) { +	return Timer(timerno); +} + +/** + * topplay(film, x, y, actor, hold, complete) + */ +void topplay(CORO_PARAM, SCNHANDLE film, int x, int y, int complete, int actorid, bool splay, int sfact, bool escOn, int myescTime) { +	play(coroParam, film, x, y, complete, actorid, splay, sfact, escOn, myescTime, true); +} + +/** + * Open or close the 'top window' + */ + +void topwindow(int bpos) { +	assert(bpos == TW_START || bpos == TW_END); + +	switch (bpos) { +	case TW_END: +		KillInventory(); +		break; + +	case TW_START: +		KillInventory(); +		PopUpConf(TOPWIN); +		break; +	} +} + +/** + * unhookscene + */ + +void unhookscene(void) { +	UnHookScene(); +} + +/** + * Un-define an actor as tagged. + */ + +void untagactor(int actor) { +	UnTagActor(actor); +} + +/** + * vibrate + */ + +void vibrate(void) { +} + +/** + * waitframe(int actor, int frameNumber) + */ + +void waitframe(CORO_PARAM, int actor, int frameNumber, bool escOn, int myescEvent) { +	CORO_BEGIN_CONTEXT; +	CORO_END_CONTEXT(_ctx); + +	CORO_BEGIN_CODE(_ctx); +	// Don't do it if it's not wanted +	if (escOn && myescEvent != GetEscEvents()) +		return; + +	while (getActorSteps(actor) < frameNumber) { +		CORO_SLEEP(1); + +		// Abort if escapable and ESCAPE is pressed +		if (escOn && myescEvent != GetEscEvents()) +			break; +	} +	CORO_END_CODE; +} + +/** + * Return when a key pressed or button pushed. + */ + +void waitkey(CORO_PARAM, bool escOn, int myescEvent) { +	CORO_BEGIN_CONTEXT; +		int	startEvent; +		int startX, startY; +	CORO_END_CONTEXT(_ctx); + +	CORO_BEGIN_CODE(_ctx); + +	// Don't do it if it's not wanted +	if (escOn && myescEvent != GetEscEvents()) +		return; + +	while (1) { +		_ctx->startEvent = getUserEvents(); +		// Store cursor position +		while (!GetCursorXYNoWait(&_ctx->startX, &_ctx->startY, false)) +			CORO_SLEEP(1); + +		while (_ctx->startEvent == getUserEvents()) { +			CORO_SLEEP(1); + +			// Not necessary to monitor escape as it's an event anyway + +			int curX, curY; +			GetCursorXY(&curX, &curY, false);	// Store cursor position +			if (curX != _ctx->startX || curY != _ctx->startY) +				break; + +			if (IsConfWindow()) +				break; +		} + +		if (!IsConfWindow()) +			return; + +		do { +			CORO_SLEEP(1); +		} while (IsConfWindow()); + +		CORO_SLEEP(ONE_SECOND / 2);		// Let it die down +	} +	CORO_END_CODE; +} + +/** + * Pause for requested time. + */ + +void waittime(CORO_PARAM, int time, bool frame, bool escOn, int myescEvent) { +	CORO_BEGIN_CONTEXT; +		int time; +	CORO_END_CONTEXT(_ctx); + +	CORO_BEGIN_CODE(_ctx); +	// Don't do it if it's not wanted +	if (escOn && myescEvent != GetEscEvents()) +		return; + +	if (!frame) +		time *= ONE_SECOND; + +	_ctx->time = time; +	do { +		CORO_SLEEP(1); + +		// Abort if escapable and ESCAPE is pressed +		if (escOn && myescEvent != GetEscEvents()) +			break; +	} while (_ctx->time--); +	CORO_END_CODE; +} + +/** + * Set a moving actor off on a walk. + */ +void walk(CORO_PARAM, int actor, int x, int y, SCNHANDLE film, int hold, bool igPath, bool escOn, int myescEvent) { +	CORO_BEGIN_CONTEXT; +	CORO_END_CONTEXT(_ctx); + +	PMACTOR pActor = GetMover(actor); +	assert(pActor); // Can't walk a non-moving actor + +	CORO_BEGIN_CODE(_ctx); + +	// Straight there if escaped +	if (escOn && myescEvent != GetEscEvents()) { +		stand(actor, x, y, 0); +		return; +	} + +	assert(pActor->hCpath != NOPOLY); // moving actor not in path + +	GetToken(pActor->actorToken); +	SetActorDest(pActor, x, y, igPath, film); +	DontScrollCursor(); + +	if (hold == 2) { +		; +	} else { +		while (MAmoving(pActor)) { +			CORO_SLEEP(1); + +			// Straight there if escaped +			if (escOn && myescEvent != GetEscEvents()) { +				stand(actor, x, y, 0); +				FreeToken(pActor->actorToken); +				return; +			} +		} +	} +	FreeToken(pActor->actorToken); +	CORO_END_CODE; +} + +/** + * Set a moving actor off on a walk. + * Wait to see if its aborted or completed. + */ +void walked(CORO_PARAM, int actor, int x, int y, SCNHANDLE film, bool escOn, int myescEvent, bool &retVal) { +	// COROUTINE +	CORO_BEGIN_CONTEXT; +		int	ticket; +	CORO_END_CONTEXT(_ctx); + +	PMACTOR pActor = GetMover(actor); +	assert(pActor); // Can't walk a non-moving actor + +	CORO_BEGIN_CODE(_ctx); + +	// Straight there if escaped +	if (escOn && myescEvent != GetEscEvents()) { +		stand(actor, x, y, 0); +		retVal = true; +		return; +	} + +	CORO_SLEEP(ONE_SECOND); + +	assert(pActor->hCpath != NOPOLY); // moving actor not in path + +	// Briefly aquire token to kill off any other normal walk +	GetToken(pActor->actorToken); +	FreeToken(pActor->actorToken); + +	SetActorDest(pActor, x, y, false, film); +	DontScrollCursor(); + +	_ctx->ticket = GetActorTicket(pActor); + +	while (MAmoving(pActor)) { +		CORO_SLEEP(1); + +		if (_ctx->ticket != GetActorTicket(pActor)) { +			retVal = false; +			return; +		} + +		// Straight there if escaped +		if (escOn && myescEvent != GetEscEvents()) { +			stand(actor, x, y, 0); +			retVal = true; +			return; +		} +	} + +	int	endx, endy; +	GetMActorPosition(pActor, &endx, &endy); +	retVal = (_ctx->ticket == GetActorTicket(pActor) && endx == x && endy == y); + +	CORO_END_CODE; +} + +/** + * Declare a moving actor. + */ +void walkingactor(uint32 id, SCNHANDLE *rp) { +	PMACTOR	pActor;		// Moving actor structure + +	SetMover(id);		// Establish as a moving actor +	pActor = GetMover(id); +	assert(pActor); + +	// Store all those reels +	int i, j; +	for (i = 0; i < 5; ++i) { +		for (j = 0; j < 4; ++j) +			pActor->WalkReels[i][j] = *rp++; +		for (j = 0; j < 4; ++j) +			pActor->StandReels[i][j] = *rp++; +	} + + +	for (i = NUM_MAINSCALES; i < TOTAL_SCALES; i++) { +		for (j = 0; j < 4; ++j) { +			pActor->WalkReels[i][j] = pActor->WalkReels[4][j]; +			pActor->StandReels[i][j] = pActor->StandReels[2][j]; +		} +	} +} + +/** + * Walk a moving actor towards the polygon's tag, but return when the + * actor enters the polygon. + */ + +void walkpoly(CORO_PARAM, int actor, SCNHANDLE film, HPOLYGON hp, bool escOn, int myescEvent) { +	// COROUTINE +	CORO_BEGIN_CONTEXT; +	CORO_END_CONTEXT(_ctx); + +	PMACTOR pActor = GetMover(actor); +	assert(pActor); // Can't walk a non-moving actor + +	CORO_BEGIN_CODE(_ctx); + +	int	aniX, aniY;		// cursor/actor position +	int	pnodex, pnodey; + +	assert(hp != NOPOLY); // walkpoly() may only be called from a polygon code block + +	// Straight there if escaped +	if (escOn && myescEvent != GetEscEvents()) { +		standtag(actor, hp); +		return; +	} + +	GetToken(pActor->actorToken); +	getPolyNode(hp, &pnodex, &pnodey); +	SetActorDest(pActor, pnodex, pnodey, false, film); +	DoScrollCursor(); + +	do { +		CORO_SLEEP(1); + +		if (escOn && myescEvent != GetEscEvents()) { +			// Straight there if escaped +			standtag(actor, hp); +			FreeToken(pActor->actorToken); +			return; +		} + +		GetMActorPosition(pActor, &aniX, &aniY); +	} while (!MActorIsInPolygon(pActor, hp) && MAmoving(pActor)); + +	FreeToken(pActor->actorToken); + +	CORO_END_CODE; +} + +/** + * walktag(actor, reel, hold) + */ + +void walktag(CORO_PARAM, int actor, SCNHANDLE film, HPOLYGON hp, bool escOn, int myescEvent) { +	// COROUTINE +	CORO_BEGIN_CONTEXT; +	CORO_END_CONTEXT(_ctx); + +	PMACTOR pActor = GetMover(actor); +	assert(pActor); // Can't walk a non-moving actor + +	CORO_BEGIN_CODE(_ctx); + +	int	pnodex, pnodey; + +	assert(hp != NOPOLY); // walkpoly() may only be called from a polygon code block + +	// Straight there if escaped +	if (escOn && myescEvent != GetEscEvents()) { +		standtag(actor, hp); +		return; +	} + +	GetToken(pActor->actorToken); +	getPolyNode(hp, &pnodex, &pnodey); +	SetActorDest(pActor, pnodex, pnodey, false, film); +	DoScrollCursor(); + +	while (MAmoving(pActor)) { +		CORO_SLEEP(1); + +		if (escOn && myescEvent != GetEscEvents()) { +			// Straight there if escaped +			standtag(actor, hp); +			FreeToken(pActor->actorToken); +			return; +		} +	} + +	// Adopt the tag-related reel +	SCNHANDLE pfilm = getPolyFilm(hp); + +	switch (pfilm) { +	case TF_NONE: +		break; + +	case TF_UP: +		SetMActorDirection(pActor, AWAY); +		SetMActorStanding(pActor); +		break; +	case TF_DOWN: +		SetMActorDirection(pActor, FORWARD); +		SetMActorStanding(pActor); +		break; +	case TF_LEFT: +		SetMActorDirection(pActor, LEFTREEL); +		SetMActorStanding(pActor); +		break; +	case TF_RIGHT: +		SetMActorDirection(pActor, RIGHTREEL); +		SetMActorStanding(pActor); +		break; + +	default: +		if (actor == LEAD_ACTOR || actor == LeadId()) +			AlterMActor(pActor, pfilm, AR_NORMAL); +		else +			SetMActorStanding(pActor); +		break; +	} + +	FreeToken(pActor->actorToken); +	CORO_END_CODE; +} + +/** + * whichinventory + */ + +int whichinventory(void) { +	return WhichInventoryOpen(); +} + + +/** + * Subtract one less that the number of parameters from pp + * pp then points to the first parameter. + * + * If the library function has no return value: + * return -(the number of parameters) to pop them from the stack + * + * If the library function has a return value: + * return -(the number of parameters - 1) to pop most of them from + * the stack, and stick the return value in pp[0] + * @param operand			Library function + * @param pp				Top of parameter stack + */ +int CallLibraryRoutine(CORO_PARAM, int operand, int32 *pp, const INT_CONTEXT *pic, RESUME_STATE *pResumeState) { +	debug(7, "CallLibraryRoutine op %d (escOn %d, myescEvent %d)", operand, pic->escOn, pic->myescEvent); +	switch (operand) { +	case ACTORATTR: +		pp -= 3;			// 4 parameters +		actorattr(pp[0], pp[1], pp[2], pp[3]); +		return -4; + +	case ACTORDIRECTION: +		pp[0] = actordirection(pp[0]); +		return 0; + +	case ACTORREF: +		error("actorref isn't a real function!"); + +	case ACTORSCALE: +		pp[0] = actorscale(pp[0]); +		return 0; + +	case ACTORSON: +		actorson(); +		return 0; + +	case ACTORXPOS: +		pp[0] = actorpos(ACTORXPOS, pp[0]); +		return 0; + +	case ACTORYPOS: +		pp[0] = actorpos(ACTORYPOS, pp[0]); +		return 0; + +	case ADDICON: +		addicon(pp[0]); +		return -1; + +	case ADDINV1: +		addinv(INV_1, pp[0]); +		return -1; + +	case ADDINV2: +		addinv(INV_2, pp[0]); +		return -1; + +	case ADDOPENINV: +		addinv(INV_OPEN, pp[0]); +		return -1; + +	case AUXSCALE: +		pp -= 13;			// 14 parameters +		auxscale(pp[0], pp[1], (SCNHANDLE *)(pp+2)); +		return -14; + +	case BACKGROUND: +		background(pp[0]); +		return -1; + +	case CAMERA: +		camera(pp[0]); +		return -1; + +	case CDLOAD: +		pp -= 1;                // 2 parameters +		cdload(pp[0], pp[1]); +		return -2; + +	case CDPLAY: +		error("cdplay isn't a real function!"); + +	case CLEARHOOKSCENE: +		clearhookscene(); +		return 0; + +	case CLOSEINVENTORY: +		closeinventory(); +		return 0; + +	case CONTROL: +		control(pp[0]); +		return -1; + +	case CONVERSATION: +		conversation(pp[0], pic->hpoly, pic->escOn, pic->myescEvent); +		return -1; + +	case CONVICON: +		convicon(pp[0]); +		return -1; + +	case CURSORXPOS: +		pp[0] = cursorpos(CURSORXPOS); +		return 0; + +	case CURSORYPOS: +		pp[0] = cursorpos(CURSORYPOS); +		return 0; + +	case CUTSCENE: +		error("cutscene isn't a real function!"); + +	case DEC_CONVW: +		pp -= 7;			// 8 parameters +		dec_convw(pp[0], pp[1], pp[2], pp[3], +			 pp[4], pp[5], pp[6], pp[7]); +		return -8; + +	case DEC_CSTRINGS: +		pp -= 19;			// 20 parameters +		dec_cstrings((SCNHANDLE *)pp); +		return -20; + +	case DEC_CURSOR: +		dec_cursor(pp[0]); +		return -1; + +	case DEC_FLAGS: +		dec_flags(pp[0]); +		return -1; + +	case DEC_INV1: +		pp -= 7;			// 8 parameters +		dec_inv1(pp[0], pp[1], pp[2], pp[3], +			 pp[4], pp[5], pp[6], pp[7]); +		return -8; + +	case DEC_INV2: +		pp -= 7;			// 8 parameters +		dec_inv2(pp[0], pp[1], pp[2], pp[3], +			 pp[4], pp[5], pp[6], pp[7]); +		return -8; + +	case DEC_INVW: +		dec_invw(pp[0]); +		return -1; + +	case DEC_LEAD: +		pp -= 61;			// 62 parameters +		dec_lead(pp[0], (SCNHANDLE *)&pp[1], pp[61]); +		return -62; + +	case DEC_TAGFONT: +		dec_tagfont(pp[0]); +		return -1; + +	case DEC_TALKFONT: +		dec_talkfont(pp[0]); +		return -1; + +	case DELICON: +		delicon(pp[0]); +		return -1; + +	case DELINV: +		delinv(pp[0]); +		return -1; + +	case EFFECTACTOR: +		assert(pic->event == ENTER || pic->event == LEAVE); // effectactor() must be from effect poly code + +		pp[0] = pic->actorid; +		return 0; + +	case ENABLEF1: +		enablef1(); +		return 0; + +	case EVENT: +		pp[0] = pic->event; +		return 0; + +	case FADEMIDI: +		fademidi(coroParam, pp[0]); +		return -1; + +	case FRAMEGRAB: +		return -1; + +	case GETINVLIMIT: +		pp[0] = getinvlimit(pp[0]); +		return 0; + +	case HASRESTARTED: +		pp[0] = hasrestarted(); +		return 0; + +	case HELDOBJECT: +		pp[0] = heldobject(); +		return 0; + +	case HIDE: +		hide(pp[0]); +		return -1; + +	case HOOKSCENE: +		pp -= 2;			// 3 parameters +		hookscene(pp[0], pp[1], pp[2]); +		return -3; + +	case IDLETIME: +		pp[0] = idletime(); +		return 0; + +	case ININVENTORY: +		pp[0] = ininventory(pp[0]); +		return 0;			// using return value + +	case INVDEPICT: +		pp -= 1;			// 2 parameters +		invdepict(pp[0], pp[1]); +		return -2; + +	case INVENTORY: +		inventory(pp[0], pic->escOn, pic->myescEvent); +		return -1; + +	case INWHICHINV: +		pp[0] = inwhichinv(pp[0]); +		return 0;			// using return value + +	case KILLACTOR: +		killactor(pp[0]); +		return -1; + +	case KILLBLOCK: +		killblock(pp[0]); +		return -1; + +	case KILLEXIT: +		killexit(pp[0]); +		return -1; + +	case KILLTAG: +		killtag(pp[0]); +		return -1; + +	case LEFTOFFSET: +		pp[0] = ltoffset(LEFTOFFSET); +		return 0; + +	case MOVECURSOR: +		pp -= 1;			// 2 parameters +		movecursor(pp[0], pp[1]); +		return -2; + +	case NEWSCENE: +		pp -= 2;			// 3 parameters +		if (*pResumeState == RES_2) +			*pResumeState = RES_NOT; +		else +			newscene(coroParam, pp[0], pp[1], pp[2]); +		return -3; + +	case NOBLOCKING: +		noblocking(); +		return 0; + +	case NOSCROLL: +		pp -= 3;			// 4 parameters +		noscroll(pp[0], pp[1], pp[2], pp[3]); +		return -4; + +	case OBJECTHELD: +		objectheld(pp[0]); +		return -1; + +	case OFFSET: +		pp -= 1;			// 2 parameters +		offset(pp[0], pp[1]); +		return -2; + +	case PLAY: +		pp -= 5;			// 6 parameters + +		if (pic->event == ENTER || pic->event == LEAVE) +			play(coroParam, pp[0], pp[1], pp[2], pp[5], 0, false, 0, pic->escOn, pic->myescEvent, false); +		else +			play(coroParam, pp[0], pp[1], pp[2], pp[5], pic->actorid, false, 0, pic->escOn, pic->myescEvent, false); +		return -6; + +	case PLAYMIDI: +		pp -= 2;			// 3 parameters +		playmidi(coroParam, pp[0], pp[1], pp[2]); +		return -3; + +	case PLAYRTF: +		error("playrtf only applies to cdi!"); + +	case PLAYSAMPLE: +		pp -= 1;			// 2 parameters +		playsample(coroParam, pp[0], pp[1], pic->escOn, pic->myescEvent); +		return -2; + +	case PREPARESCENE: +		preparescene(pp[0]); +		return -1; + +	case PRINT: +		pp -= 5;			// 6 parameters +		/* pp[2] was intended to be attribute */ +		print(coroParam, pp[0], pp[1], pp[3], pp[4], pp[5], pic->escOn, pic->myescEvent); +		return -6; + +	case PRINTOBJ: +		printobj(coroParam, pp[0], pic->pinvo, pic->event); +		return -1; + +	case PRINTTAG: +		printtag(pic->hpoly, pp[0]); +		return -1; + +	case QUITGAME: +		quitgame(); +		return 0; + +	case RANDOM: +		pp -= 2;			// 3 parameters +		pp[0] = dw_random(pp[0], pp[1], pp[2]); +		return -2;		// One holds return value + +	case RESETIDLETIME: +		resetidletime(); +		return 0; + +	case RESTARTGAME: +		restartgame(); +		return 0; + +	case RESTORE_CUT: +		restore_scene(false); +		return 0; + +	case RESTORE_SCENE: +		restore_scene(true); +		return 0; + +	case RUNMODE: +		pp[0] = runmode(); +		return 0; + +	case SAMPLEPLAYING: +		pp[0] = sampleplaying(pic->escOn, pic->myescEvent); +		return 0; + +	case SAVE_SCENE: +		if (*pResumeState == RES_1) +			*pResumeState = RES_2; +		else +			save_scene(coroParam); +		return 0; + +	case SCALINGREELS: +		pp -= 6;			// 7 parameters +		scalingreels(pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pp[6]); +		return -7; + +	case SCANICON: +		pp[0] = scanicon(); +		return 0; + +	case SCROLL: +		pp -= 3;			// 4 parameters +		scroll(coroParam, pp[0], pp[1], pp[2], pp[3], pic->escOn, pic->myescEvent); +		return -4; + +	case SETACTOR: +		setactor(pp[0]); +		return -1; + +	case SETBLOCK: +		setblock(pp[0]); +		return -1; + +	case SETEXIT: +		setexit(pp[0]); +		return -1; + +	case SETINVLIMIT: +		pp -= 1;			// 2 parameters +		setinvlimit(pp[0], pp[1]); +		return -2; + +	case SETINVSIZE: +		pp -= 6;			// 7 parameters +		setinvsize(pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pp[6]); +		return -7; + +	case SETLANGUAGE: +		setlanguage((LANGUAGE)pp[0]); +		return -1; + +	case SETPALETTE: +		setpalette(pp[0], pic->escOn, pic->myescEvent); +		return -1; + +	case SETTAG: +		settag(pp[0]); +		return -1; + +	case SETTIMER: +		pp -= 3;			// 4 parameters +		settimer(pp[0], pp[1], pp[2], pp[3]); +		return -4; + +	case SHOWPOS: +#ifdef DEBUG +		showpos(); +#endif +		return 0; + +	case SHOWSTRING: +#ifdef DEBUG +		showstring(); +#endif +		return 0; + +	case SPLAY: +		pp -= 6;			// 7 parameters + +		if (pic->event == ENTER || pic->event == LEAVE) +			splay(coroParam, pp[0], pp[1], pp[2], pp[3], pp[6], 0, pic->escOn, pic->myescEvent); +		else +			splay(coroParam, pp[0], pp[1], pp[2], pp[3], pp[6], pic->actorid, pic->escOn, pic->myescEvent); +		return -7; + +	case STAND: +		pp -= 3;			// 4 parameters +		stand(pp[0], pp[1], pp[2], pp[3]); +		return -4; + +	case STANDTAG: +		standtag(pp[0], pic->hpoly); +		return -1; + +	case STOP: +		stop(pp[0]); +		return -1; + +	case STOPMIDI: +		stopmidi(); +		return 0; + +	case STOPSAMPLE: +		stopsample(); +		return 0; + +	case SUBTITLES: +		subtitles(pp[0]); +		return -1; + +	case SWALK: +		pp -= 5;			// 6 parameters +		swalk(coroParam, pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pic->escOn, pic->myescEvent); +		return -6; + +	case TAGACTOR: +		pp -= 2;			// 3 parameters +		tagactor(pp[0], pp[1], pp[2]); +		return -3; + +	case TALK: +		pp -= 1;			// 2 parameters + +		if (pic->event == ENTER || pic->event == LEAVE) +			talk(coroParam, pp[0], pp[1], 0, pic->escOn, pic->myescEvent); +		else +			talk(coroParam, pp[0], pp[1], pic->actorid, pic->escOn, pic->myescEvent); +		return -2; + +	case TALKAT: +		pp -= 3;			// 4 parameters +		talkat(coroParam, pp[0], pp[1], pp[2], pp[3], pic->escOn, pic->myescEvent); +		return -4; + +	case TALKATS: +		pp -= 4;			// 5 parameters +		talkats(coroParam, pp[0], pp[1], pp[2], pp[3], pp[4], pic->escOn, pic->myescEvent); +		return -5; + +	case TALKATTR: +		pp -= 2;			// 3 parameters +		talkattr(pp[0], pp[1], pp[2], pic->escOn, pic->myescEvent); +		return -3; + +	case TIMER: +		pp[0] = timer(pp[0]); +		return 0; + +	case TOPOFFSET: +		pp[0] = ltoffset(TOPOFFSET); +		return 0; + +	case TOPPLAY: +		pp -= 5;			// 6 parameters +		topplay(coroParam, pp[0], pp[1], pp[2], pp[5], pic->actorid, false, 0, pic->escOn, pic->myescEvent); +		return -6; + +	case TOPWINDOW: +		topwindow(pp[0]); +		return -1; + +	case TRYPLAYSAMPLE: +		pp -= 1;			// 2 parameters +		tryplaysample(coroParam, pp[0], pp[1], pic->escOn, pic->myescEvent); +		return -2; + +	case UNHOOKSCENE: +		unhookscene(); +		return 0; + +	case UNTAGACTOR: +		untagactor(pp[0]); +		return -1; + +	case VIBRATE: +		vibrate(); +		return 0; + +	case WAITKEY: +		waitkey(coroParam, pic->escOn, pic->myescEvent); +		return 0; + +	case WAITFRAME: +		pp -= 1;			// 2 parameters +		waitframe(coroParam, pp[0], pp[1], pic->escOn, pic->myescEvent); +		return -2; + +	case WAITTIME: +		pp -= 1;			// 2 parameters +		waittime(coroParam, pp[0], pp[1], pic->escOn, pic->myescEvent); +		return -2; + +	case WALK: +		pp -= 4;			// 5 parameters +		walk(coroParam, pp[0], pp[1], pp[2], pp[3], pp[4], false, pic->escOn, pic->myescEvent); +		return -5; + +	case WALKED: { +		pp -= 3;			// 4 parameters +		bool tmp; +		walked(coroParam, pp[0], pp[1], pp[2], pp[3], pic->escOn, pic->myescEvent, tmp); +		if (!coroParam) { +			// Only write the result to the stack if walked actually completed running. +			pp[0] = tmp; +		} +		} +		return -3; + +	case WALKINGACTOR: +		pp -= 40;			// 41 parameters +		walkingactor(pp[0], (SCNHANDLE *)&pp[1]); +		return -41; + +	case WALKPOLY: +		pp -= 2;			// 3 parameters +		walkpoly(coroParam, pp[0], pp[1], pic->hpoly, pic->escOn, pic->myescEvent); +		return -3; + +	case WALKTAG: +		pp -= 2;			// 3 parameters +		walktag(coroParam, pp[0], pp[1], pic->hpoly, pic->escOn, pic->myescEvent); +		return -3; + +	case WHICHINVENTORY: +		pp[0] = whichinventory(); +		return 0; + +	default: +		error("Unsupported library function"); +	} + +	error("Can't possibly get here"); +} + + +} // end of namespace Tinsel diff --git a/engines/tinsel/tinlib.h b/engines/tinsel/tinlib.h new file mode 100644 index 0000000000..001de70896 --- /dev/null +++ b/engines/tinsel/tinlib.h @@ -0,0 +1,41 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * Text utility defines + */ + +#ifndef TINSEL_TINLIB_H	// prevent multiple includes +#define TINSEL_TINLIB_H + +#include "tinsel/dw.h" + +namespace Tinsel { + +// Library functions in TINLIB.C + +void control(int param); +void stand(int actor, int x, int y, SCNHANDLE film); + +} // end of namespace Tinsel + +#endif		// TINSEL_TINLIB_H diff --git a/engines/tinsel/tinsel.cpp b/engines/tinsel/tinsel.cpp new file mode 100644 index 0000000000..1f56385283 --- /dev/null +++ b/engines/tinsel/tinsel.cpp @@ -0,0 +1,999 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "common/endian.h" +#include "common/events.h" +#include "common/keyboard.h" +#include "common/file.h" +#include "common/savefile.h" +#include "common/config-manager.h" +#include "common/stream.h" + +#include "graphics/cursorman.h" + +#include "base/plugins.h" +#include "base/version.h" + +#include "sound/mididrv.h" +#include "sound/mixer.h" +#include "sound/audiocd.h" + +#include "tinsel/actors.h" +#include "tinsel/background.h" +#include "tinsel/config.h" +#include "tinsel/cursor.h" +#include "tinsel/dw.h" +#include "tinsel/events.h" +#include "tinsel/faders.h" +#include "tinsel/film.h" +#include "tinsel/handle.h" +#include "tinsel/heapmem.h"			// MemoryInit +#include "tinsel/inventory.h" +#include "tinsel/music.h" +#include "tinsel/object.h" +#include "tinsel/pid.h" +#include "tinsel/polygons.h" +#include "tinsel/savescn.h" +#include "tinsel/scn.h" +#include "tinsel/serializer.h" +#include "tinsel/sound.h" +#include "tinsel/strres.h" +#include "tinsel/timers.h" +#include "tinsel/tinsel.h" + +namespace Tinsel { + +//----------------- EXTERNAL FUNCTIONS --------------------- + +// In BG.CPP +extern void SetDoFadeIn(bool tf); +extern void DropBackground(void); + +// In CURSOR.CPP +extern void CursorProcess(CORO_PARAM, const void *); + +// In INVENTORY.CPP +extern void InventoryProcess(CORO_PARAM, const void *); + +// In SCENE.CPP +extern void PrimeBackground(); +extern void NewScene(SCNHANDLE scene, int entry); +extern SCNHANDLE GetSceneHandle(void); + +// In TIMER.CPP +extern void FettleTimers(void); +extern void RebootTimers(void); + +//----------------- FORWARD DECLARATIONS  --------------------- +void SetNewScene(SCNHANDLE scene, int entrance, int transition); + +//----------------- GLOBAL GLOBAL DATA -------------------- + +bool bRestart = false; +bool bHasRestarted = false; + +#ifdef DEBUG +bool bFast;		// set to make it go ludicrously fast +#endif + +//----------------- LOCAL GLOBAL DATA -------------------- + +struct Scene { +	SCNHANDLE scene;	// Memory handle for scene +	int	entry;		// Entrance number +	int	trans;		// Transition - not yet used +}; + +static Scene NextScene = { 0, 0, 0 }; +static Scene HookScene = { 0, 0, 0 }; +static Scene DelayedScene = { 0, 0, 0 }; + +static bool bHookSuspend = false; + +static PROCESS *pMouseProcess = 0; +static PROCESS *pKeyboardProcess = 0; + +// Stack of pending mouse button events +Common::List<Common::EventType> mouseButtons; + +// Stack of pending keypresses +Common::List<Common::Event> keypresses; + +//----------------- LOCAL PROCEDURES -------------------- + +/** + * Process to handle keypresses + */ +void KeyboardProcess(CORO_PARAM, const void *) { +	// COROUTINE +	CORO_BEGIN_CONTEXT; +	CORO_END_CONTEXT(_ctx); + +	CORO_BEGIN_CODE(_ctx); +	while (true) { +		if (keypresses.empty()) { +			// allow scheduling +			CORO_SLEEP(1); +			continue; +		} + +		// Get the next keyboard event off the stack +		Common::Event evt = *keypresses.begin(); +		keypresses.erase(keypresses.begin()); + +		// Switch for special keys  +		switch (evt.kbd.keycode) { +		// Drag action +		case Common::KEYCODE_LALT: +		case Common::KEYCODE_RALT: +			if (evt.type == Common::EVENT_KEYDOWN) { +				if (!bSwapButtons) +					ProcessButEvent(BE_RDSTART); +				else +					ProcessButEvent(BE_LDSTART); +			} else { +				if (!bSwapButtons) +					ProcessButEvent(BE_LDEND); +				else +					ProcessButEvent(BE_RDEND); +			} +			continue; + +		case Common::KEYCODE_LCTRL: +		case Common::KEYCODE_RCTRL: +			if (evt.type == Common::EVENT_KEYDOWN) { +				ProcessKeyEvent(LOOK_KEY); +			} else { +				// Control key release +			} +			continue; + +		default: +			break; +		} + +		// At this point only key down events need processing +		if (evt.type == Common::EVENT_KEYUP) +			continue; + +		if (_vm->_keyHandler != NULL)  +			// Keyboard is hooked, so pass it on to that handler first +			if (!_vm->_keyHandler(evt.kbd)) +				continue; + +		switch (evt.kbd.keycode) { +		/*** SPACE = WALKTO ***/ +		case Common::KEYCODE_SPACE: +			ProcessKeyEvent(WALKTO_KEY); +			continue; + +		/*** RETURN = ACTION ***/ +		case Common::KEYCODE_RETURN: +		case Common::KEYCODE_KP_ENTER: +			ProcessKeyEvent(ACTION_KEY); +			continue; + +		/*** l = LOOK ***/ +		case Common::KEYCODE_l:		// LOOK +			ProcessKeyEvent(LOOK_KEY); +			continue; + +		case Common::KEYCODE_ESCAPE: +			// WORKAROUND: Check if any of the starting logo screens are active, and if so +			// manually skip to the title screen, allowing them to be bypassed +			{ +				int sceneOffset = (_vm->getFeatures() & GF_SCNFILES) ? 1 : 0; +				int sceneNumber = (GetSceneHandle() >> SCNHANDLE_SHIFT) - sceneOffset; +				if ((language == TXT_GERMAN) &&  +					((sceneNumber >= 25 && sceneNumber <= 27) || (sceneNumber == 17))) { +					// Skip to title screen +					// It seems the German CD version uses scenes 25,26,27,17 for the intro, +					// instead of 13,14,15,11;  also, the title screen is 11 instead of 10 +					SetNewScene((11 + sceneOffset) << SCNHANDLE_SHIFT, 1, TRANS_CUT); +				} else if ((sceneNumber >= 13) && (sceneNumber <= 15) || (sceneNumber == 11)) { +					// Skip to title screen +					SetNewScene((10 + sceneOffset) << SCNHANDLE_SHIFT, 1, TRANS_CUT); +				} else { +					// Not on an intro screen, so process the key normally +					ProcessKeyEvent(ESC_KEY); +				} +			} +			continue; + +#ifdef SLOW_RINCE_DOWN +		case '>': +			AddInterlude(1); +			continue; +		case '<': +			AddInterlude(-1); +			continue; +#endif + +		case Common::KEYCODE_F1: +			// Options dialog +			ProcessKeyEvent(OPTION_KEY); +			continue; +		case Common::KEYCODE_F5: +			// Save game +			ProcessKeyEvent(SAVE_KEY); +			continue; +		case Common::KEYCODE_F7: +			// Load game +			ProcessKeyEvent(LOAD_KEY); +			continue; +		case Common::KEYCODE_q: +			if ((evt.kbd.flags == Common::KBD_CTRL) || (evt.kbd.flags == Common::KBD_ALT)) +				ProcessKeyEvent(QUIT_KEY); +			continue; +		case Common::KEYCODE_PAGEUP: +		case Common::KEYCODE_KP9: +			ProcessKeyEvent(PGUP_KEY); +			continue; +		case Common::KEYCODE_PAGEDOWN: +		case Common::KEYCODE_KP3: +			ProcessKeyEvent(PGDN_KEY); +			continue; +		case Common::KEYCODE_HOME: +		case Common::KEYCODE_KP7: +			ProcessKeyEvent(HOME_KEY); +			continue; +		case Common::KEYCODE_END: +		case Common::KEYCODE_KP1: +			ProcessKeyEvent(END_KEY); +			continue; +		default: +			ProcessKeyEvent(NOEVENT_KEY); +			break; +		} +	} +	CORO_END_CODE; +} + +/** + * Process to handle changes in the mouse buttons. + */ +void MouseProcess(CORO_PARAM, const void *) { +	// COROUTINE +	CORO_BEGIN_CONTEXT; +		bool lastLWasDouble; +		bool lastRWasDouble; +		uint32 lastLeftClick, lastRightClick; +	CORO_END_CONTEXT(_ctx); + +	CORO_BEGIN_CODE(_ctx); + +	_ctx->lastLWasDouble = false; +	_ctx->lastRWasDouble = false; +	_ctx->lastLeftClick = _ctx->lastRightClick = DwGetCurrentTime(); + +	while (true) { +		// FIXME: I'm still keeping the ctrl/Alt handling in the ProcessKeyEvent method. +		// Need to make sure that this works correctly +		//DragKeys(); + +		if (mouseButtons.empty()) { +			// allow scheduling +			CORO_SLEEP(1); +			continue; +		} + +		// get next mouse button event +		Common::EventType type = *mouseButtons.begin(); +		mouseButtons.erase(mouseButtons.begin()); + +		switch (type) { +		case Common::EVENT_LBUTTONDOWN: +			// left button press +			if (DwGetCurrentTime() - _ctx->lastLeftClick < (uint32)dclickSpeed) { +				// signal left drag start +				ProcessButEvent(BE_LDSTART); + +				// signal left double click event +				ProcessButEvent(BE_DLEFT); + +				_ctx->lastLWasDouble = true; +			} else { +				// signal left drag start +				ProcessButEvent(BE_LDSTART); + +				// signal left single click event +				ProcessButEvent(BE_SLEFT); + +				_ctx->lastLWasDouble = false; +			} +			break; + +		case Common::EVENT_LBUTTONUP: +			// left button release + +			// update click timer +			if (_ctx->lastLWasDouble == false) +				_ctx->lastLeftClick = DwGetCurrentTime(); +			else +				_ctx->lastLeftClick -= dclickSpeed; + +			// signal left drag end +			ProcessButEvent(BE_LDEND); +			break; + +		case Common::EVENT_RBUTTONDOWN: +			// right button press + +			if (DwGetCurrentTime() - _ctx->lastRightClick < (uint32)dclickSpeed) { +				// signal right drag start +				ProcessButEvent(BE_RDSTART); + +				// signal right double click event +				ProcessButEvent(BE_DRIGHT); + +				_ctx->lastRWasDouble = true; +			} else { +				// signal right drag start +				ProcessButEvent(BE_RDSTART); + +				// signal right single click event +				ProcessButEvent(BE_SRIGHT); + +				_ctx->lastRWasDouble = false; +			} +			break; + +		case Common::EVENT_RBUTTONUP: +			// right button release + +			// update click timer +			if (_ctx->lastRWasDouble == false) +				_ctx->lastRightClick = DwGetCurrentTime(); +			else +				_ctx->lastRightClick -= dclickSpeed; + +			// signal right drag end +			ProcessButEvent(BE_RDEND); +			break; + +		default: +			break; +		} +	} +	CORO_END_CODE; +} + +/** + * Run the master script. + * Continues between scenes, or until Interpret() returns. + */ +static void MasterScriptProcess(CORO_PARAM, const void *) { +	// COROUTINE +	CORO_BEGIN_CONTEXT; +		INT_CONTEXT *pic; +	CORO_END_CONTEXT(_ctx); + +	CORO_BEGIN_CODE(_ctx); +	_ctx->pic = InitInterpretContext(GS_MASTER, 0, NOEVENT, NOPOLY, 0, NULL); +	CORO_INVOKE_1(Interpret, _ctx->pic); +	CORO_END_CODE; +} + +/** + * Store the facts pertaining to a scene change. + */ + +void SetNewScene(SCNHANDLE scene, int entrance, int transition) { +	if (HookScene.scene == 0 || bHookSuspend) { +		// This scene comes next +		NextScene.scene = scene; +		NextScene.entry = entrance; +		NextScene.trans = transition; +	} else { +		// This scene gets delayed +		DelayedScene.scene = scene; +		DelayedScene.entry = entrance; +		DelayedScene.trans = transition; + +		// The hooked scene comes next +		NextScene.scene = HookScene.scene; +		NextScene.entry = HookScene.entry; +		NextScene.trans = HookScene.trans; + +		HookScene.scene = 0; +	} +} + +void SetHookScene(SCNHANDLE scene, int entrance, int transition) { +	assert(HookScene.scene == 0); // scene already hooked + +	HookScene.scene = scene; +	HookScene.entry = entrance; +	HookScene.trans = transition; +} + +void UnHookScene(void) { +	assert(DelayedScene.scene != 0); // no scene delayed + +	// The delayed scene can go now +	NextScene.scene = DelayedScene.scene; +	NextScene.entry = DelayedScene.entry; +	NextScene.trans = DelayedScene.trans; + +	DelayedScene.scene = 0; +} + +void SuspendHook(void) { +	bHookSuspend = true; +} + +void UnSuspendHook(void) { +	bHookSuspend = false; +} + +void syncSCdata(Serializer &s) { +	s.syncAsUint32LE(HookScene.scene); +	s.syncAsSint32LE(HookScene.entry); +	s.syncAsSint32LE(HookScene.trans); + +	s.syncAsUint32LE(DelayedScene.scene); +	s.syncAsSint32LE(DelayedScene.entry); +	s.syncAsSint32LE(DelayedScene.trans); +} + + +//----------------------------------------------------------------------- + +static void RestoredProcess(CORO_PARAM, const void *param) { +	// COROUTINE +	CORO_BEGIN_CONTEXT; +		INT_CONTEXT *pic; +	CORO_END_CONTEXT(_ctx); + +	CORO_BEGIN_CODE(_ctx); + +	// get the stuff copied to process when it was created +	_ctx->pic = *((INT_CONTEXT **)param); + +	_ctx->pic = RestoreInterpretContext(_ctx->pic); +	CORO_INVOKE_1(Interpret, _ctx->pic); + +	CORO_END_CODE; +} + +void RestoreProcess(INT_CONTEXT *pic) { +	g_scheduler->createProcess(PID_TCODE, RestoredProcess, &pic, sizeof(pic)); +} + +void RestoreMasterProcess(INT_CONTEXT *pic) { +	g_scheduler->createProcess(PID_MASTER_SCR, RestoredProcess, &pic, sizeof(pic)); +} + +// FIXME: CountOut is used by ChangeScene +static int CountOut = 1;	// == 1 for immediate start of first scene + +/** + * If a scene restore is going on, just return (we don't update the + * screen during this time). + * If a scene change is required, 'order' the data for the new scene and + * start a fade out and a countdown. + * When the count expires, the screen will have faded. Ensure the scene	| + * is loaded, clear the screen, and start the new scene. + */ +void ChangeScene() { + +	if (IsRestoringScene()) +		return; + +	if (NextScene.scene != 0) { +		if (!CountOut) { +			switch (NextScene.trans) { +			case TRANS_CUT: +				CountOut = 1; +				break; + +			case TRANS_FADE: +			default: +				// Trigger pre-load and fade and start countdown +				CountOut = COUNTOUT_COUNT; +				FadeOutFast(NULL); +				break; +			} +		} else if (--CountOut == 0) { +			ClearScreen(); +			 +			NewScene(NextScene.scene, NextScene.entry); +			NextScene.scene = 0; + +			switch (NextScene.trans) { +			case TRANS_CUT: +				SetDoFadeIn(false); +				break; + +			case TRANS_FADE: +			default: +				SetDoFadeIn(true); +				break; +			} +		} +	} +} + +/** + * LoadBasicChunks + */ + +void LoadBasicChunks(void) { +	byte *cptr; +	int numObjects; + +	// Allocate RAM for savescene data +	InitialiseSs(); + +	// CHUNK_TOTAL_ACTORS seems to be missing in the released version, hard coding a value +	// TODO: Would be nice to just change 511 to MAX_SAVED_ALIVES +	cptr = FindChunk(MASTER_SCNHANDLE, CHUNK_TOTAL_ACTORS); +	RegisterActors((cptr != NULL) ? READ_LE_UINT32(cptr) : 511); + +	// CHUNK_TOTAL_GLOBALS seems to be missing in some versions. +	// So if it is missing, set a reasonably high value for the number of globals. +	cptr = FindChunk(MASTER_SCNHANDLE, CHUNK_TOTAL_GLOBALS); +	RegisterGlobals((cptr != NULL) ? READ_LE_UINT32(cptr) : 512); + +	cptr = FindChunk(INV_OBJ_SCNHANDLE, CHUNK_TOTAL_OBJECTS); +	numObjects = (cptr != NULL) ? READ_LE_UINT32(cptr) : 0; + +	cptr = FindChunk(INV_OBJ_SCNHANDLE, CHUNK_OBJECTS); +	 +#ifdef SCUMM_BIG_ENDIAN +	//convert to native endianness +	INV_OBJECT *io = (INV_OBJECT *)cptr; +	for (int i = 0; i < numObjects; i++, io++) { +		io->id        = FROM_LE_32(io->id); +		io->hFilm     = FROM_LE_32(io->hFilm); +		io->hScript   = FROM_LE_32(io->hScript); +		io->attribute = FROM_LE_32(io->attribute); +	} +#endif +	 +	RegisterIcons(cptr, numObjects); + +	cptr = FindChunk(MASTER_SCNHANDLE, CHUNK_TOTAL_POLY); +	if (cptr != NULL) +		MaxPolygons(*cptr); +} + +//----------------- TinselEngine -------------------- + +// Global pointer to engine +TinselEngine *_vm; + +struct GameSettings { +	const char *gameid; +	const char *description; +	byte id; +	uint32 features; +	const char *detectname; +}; + +static const GameSettings tinselSettings[] = { +	{"tinsel", "Tinsel game", 0, 0, 0}, + +	{NULL, NULL, 0, 0, NULL} +}; + +TinselEngine::TinselEngine(OSystem *syst, const TinselGameDescription *gameDesc) :  +		Engine(syst), _gameDescription(gameDesc) { +	_vm = this; + +	// Setup mixer +	_mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, ConfMan.getInt("sfx_volume")); +	_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, ConfMan.getInt("music_volume")); + +	const GameSettings *g; + +	const char *gameid = ConfMan.get("gameid").c_str(); +	for (g = tinselSettings; g->gameid; ++g) +		if (!scumm_stricmp(g->gameid, gameid)) +			_gameId = g->id; + +	int cd_num = ConfMan.getInt("cdrom"); +	if (cd_num >= 0) +		_system->openCD(cd_num); +		 +	int midiDriver = MidiDriver::detectMusicDriver(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MIDI); +	bool native_mt32 = ((midiDriver == MD_MT32) || ConfMan.getBool("native_mt32")); +	//bool adlib = (midiDriver == MD_ADLIB); + +	MidiDriver *driver = MidiDriver::createMidi(midiDriver); +	if (native_mt32) +		driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE); + +	_music = new MusicPlayer(driver); +	//_music->setNativeMT32(native_mt32); +	//_music->setAdlib(adlib); + +	_musicVolume = ConfMan.getInt("music_volume"); +	 +	_sound = new SoundManager(this); + +	_mousePos.x = 0; +	_mousePos.y = 0; +	_keyHandler = NULL; +	_dosPlayerDir = 0; +	quitFlag = false; +} + +TinselEngine::~TinselEngine() { +	delete _sound; +	delete _music; +	delete _console; +	FreeSs(); +	FreeTextBuffer(); +	FreeHandleTable(); +	FreeActors(); +	FreeObjectList(); +	FreeGlobals(); +	delete _scheduler; +} + +int TinselEngine::init() { +	// Initialize backend +	_system->beginGFXTransaction(); +		initCommonGFX(false); +		_system->initSize(SCREEN_WIDTH, SCREEN_HEIGHT); +	_system->endGFXTransaction(); + +	_screenSurface.create(SCREEN_WIDTH, SCREEN_HEIGHT, 1); + +	g_system->getEventManager()->registerRandomSource(_random, "tinsel"); + +	_console = new Console(); +	 +	_scheduler = new Scheduler(); + +	// init memory manager +	MemoryInit(); + +	// load user configuration +	ReadConfig(); + +#if 1 +	// FIXME: The following is taken from RestartGame(). +	// It may have to be adjusted a bit +	RebootCursor(); +	RebootDeadTags(); +	RebootMovers(); +	RebootTimers(); +	RebootScalingReels(); + +	DelayedScene.scene = HookScene.scene = 0; +#endif + +	// Init palette and object managers, scheduler, keyboard and mouse +	RestartDrivers(); + +	// TODO: More stuff from dos_main.c may have to be added here + +	// Set language - we'll be clever here and use the ScummVM language setting +	language = TXT_ENGLISH; +	switch (getLanguage()) { +	case Common::FR_FRA: +		language = TXT_FRENCH; +		break; +	case Common::DE_DEU: +		language = TXT_GERMAN; +		break; +	case Common::IT_ITA: +		language = TXT_ITALIAN; +		break; +	case Common::ES_ESP: +		language = TXT_SPANISH; +		break; +	default: +		language = TXT_ENGLISH; +	} +	ChangeLanguage(language); + +	// load in graphics info +	SetupHandleTable(); + +	// Actors, globals and inventory icons +	LoadBasicChunks(); + +	return 0; +} + +Common::String TinselEngine::getSavegamePattern() const { +	return _targetName + ".???"; +} + +Common::String TinselEngine::getSavegameFilename(int16 saveNum) const { +	char filename[256]; +	snprintf(filename, 256, "%s.%03d", getTargetName().c_str(), saveNum); +	return filename; +} + +#define GAME_FRAME_DELAY (1000 / ONE_SECOND) + +int TinselEngine::go() { +	uint32 timerVal = 0; + +	// Continuous game processes +	CreateConstProcesses(); + +	// allow game to run in the background +	//RestartBackgroundProcess();	// FIXME: is this still needed? + +	//dumpMusic();	// dumps all of the game's music in external XMIDI files + +#if 0 +	// Load game from specified slot, if any +	// FIXME: Not working correctly right now +	if (ConfMan.hasKey("save_slot")) { +		getList(); +		RestoreGame(ConfMan.getInt("save_slot")); +	} +#endif + +	// Foreground loop + +	while (!quitFlag) { +		assert(_console); +		if (_console->isAttached()) +			_console->onFrame(); + +		// Check for time to do next game cycle +		if ((g_system->getMillis() > timerVal + GAME_FRAME_DELAY)) { +			timerVal = g_system->getMillis(); +			AudioCD.updateCD(); +			NextGameCycle(); +		} + +		if (bRestart) { +			RestartGame(); +			bRestart = false; +			bHasRestarted = true;	// Set restarted flag +		} + +		// Save/Restore scene file transfers +		ProcessSRQueue(); + +#ifdef DEBUG +		if (bFast) +			continue;		// run flat-out +#endif +		// Loop processing events while there are any pending +		while (pollEvent()); + +		g_system->delayMillis(10); +	} + +	// Write configuration +	WriteConfig(); + +	return 0; +} + + +void TinselEngine::NextGameCycle(void) { +	// +	ChangeScene(); + +	// Allow a user event for this schedule +	ResetEcount(); + +	// schedule process +	_scheduler->schedule(); + +	// redraw background +	DrawBackgnd(); + +	// Why waste resources on yet another process? +	FettleTimers(); +} + + +bool TinselEngine::pollEvent() { +	Common::Event event; +	 +	if (!g_system->getEventManager()->pollEvent(event))  +		return false; + +	// Handle the various kind of events +	switch (event.type) { +	case Common::EVENT_QUIT: +		quitFlag = true; +		break; + +	case Common::EVENT_LBUTTONDOWN: +	case Common::EVENT_LBUTTONUP: +	case Common::EVENT_RBUTTONDOWN: +	case Common::EVENT_RBUTTONUP: +		// Add button to queue for the mouse process +		mouseButtons.push_back(event.type); +		break; + +	case Common::EVENT_MOUSEMOVE: +		_mousePos = event.mouse; +		break; + +	case Common::EVENT_KEYDOWN: +	case Common::EVENT_KEYUP: +		ProcessKeyEvent(event); +		break; +			 +	default: +		break; +	} + +	return true; +} + +/** + * Start the processes that continue between scenes. + */ + +void TinselEngine::CreateConstProcesses(void) { +	// Process to run the master script +	_scheduler->createProcess(PID_MASTER_SCR, MasterScriptProcess, NULL, 0); + +	// Processes to run the cursor and inventory, +	_scheduler->createProcess(PID_CURSOR, CursorProcess, NULL, 0); +	_scheduler->createProcess(PID_INVENTORY, InventoryProcess, NULL, 0); +} + +/** + * Restart the game + */ + +void TinselEngine::RestartGame(void) { +	HoldItem(INV_NOICON);	// Holding nothing + +	DropBackground();	// No background + +	// Ditches existing infrastructure background +	PrimeBackground(); + +	// Next scene change won't need to fade out +	// -> reset the count used by ChangeScene +	CountOut = 1; + +	RebootCursor(); +	RebootDeadTags(); +	RebootMovers(); +	RebootTimers(); +	RebootScalingReels(); + +	DelayedScene.scene = HookScene.scene = 0; + +	// remove keyboard, mouse and joystick drivers +	ChopDrivers(); + +	// Init palette and object managers, scheduler, keyboard and mouse +	RestartDrivers(); + +	// Actors, globals and inventory icons +	LoadBasicChunks(); + +	// Continuous game processes +	CreateConstProcesses(); +} + +/** + * Init palette and object managers, scheduler, keyboard and mouse. + */ + +void TinselEngine::RestartDrivers(void) { +	// init the palette manager +	ResetPalAllocator(); + +	// init the object manager +	KillAllObjects(); + +	// init the process scheduler +	_scheduler->reset(); + +	// init the event handlers +	pMouseProcess = _scheduler->createProcess(PID_MOUSE, MouseProcess, NULL, 0);	 +	pKeyboardProcess = _scheduler->createProcess(PID_KEYBOARD, KeyboardProcess, NULL, 0);	 + +	// open MIDI files +	OpenMidiFiles(); + +	// open sample files (only if mixer is ready) +	if (_mixer->isReady()) { +		_sound->openSampleFiles(); +	} + +	// Set midi volume +	SetMidiVolume(volMidi); +} + +/** + * Remove keyboard, mouse and joystick drivers. + */ + +void TinselEngine::ChopDrivers(void) { +	// remove sound driver +	StopMidi(); +	_sound->stopAllSamples(); +	DeleteMidiBuffer(); + +	// remove event drivers +	_scheduler->killProcess(pMouseProcess); +	_scheduler->killProcess(pKeyboardProcess); +} + +/** + * Process a keyboard event + */ + +void TinselEngine::ProcessKeyEvent(const Common::Event &event) { + +	// Handle any special keys immediately +	switch (event.kbd.keycode) { +	case Common::KEYCODE_d: +		if ((event.kbd.flags == Common::KBD_CTRL) && (event.type == Common::EVENT_KEYDOWN)) { +			// Activate the debugger +			assert(_console); +			_console->attach(); +			return; +		} +		break; +	default: +		break; +	} + +	// Check for movement keys +	int idx = 0; +	switch (event.kbd.keycode) { +	case Common::KEYCODE_UP: +	case Common::KEYCODE_KP8: +		idx = MSK_UP; +		break; +	case Common::KEYCODE_DOWN: +	case Common::KEYCODE_KP2: +		idx = MSK_DOWN; +		break; +	case Common::KEYCODE_LEFT: +	case Common::KEYCODE_KP4: +		idx = MSK_LEFT; +		break; +	case Common::KEYCODE_RIGHT: +	case Common::KEYCODE_KP6: +		idx = MSK_RIGHT; +		break; +	default: +		break; +	} +	if (idx != 0) { +		if (event.type == Common::EVENT_KEYDOWN) +			_dosPlayerDir |= idx; +		else +			_dosPlayerDir &= ~idx; +		return; +	} + +	// All other keypresses add to the queue for processing in KeyboardProcess  +	keypresses.push_back(event); +} + +} // End of namespace Tinsel diff --git a/engines/tinsel/tinsel.h b/engines/tinsel/tinsel.h new file mode 100644 index 0000000000..9ffadfe8c1 --- /dev/null +++ b/engines/tinsel/tinsel.h @@ -0,0 +1,143 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef TINSEL_H +#define TINSEL_H + +#include "common/scummsys.h" +#include "common/system.h" +#include "common/events.h" +#include "common/keyboard.h" +#include "common/util.h" + +#include "sound/mididrv.h" +#include "sound/mixer.h" + +#include "engines/engine.h" +#include "tinsel/debugger.h" +#include "tinsel/graphics.h" +#include "tinsel/sound.h" + +namespace Tinsel { + +class MusicPlayer; +class Scheduler; +class SoundManager; + +enum TinselGameID { +	GID_DW1 = 0, +	GID_DW2 = 1 +}; + +enum TinselGameFeatures { +	GF_DEMO = 1 << 0, +	GF_CD = 1 << 1, +	GF_FLOPPY = 1 << 2, +	GF_SCNFILES = 1 << 3 +}; + +enum TinselEngineVersion { +	TINSEL_V1 = 1 << 0, +	TINSEL_V2 = 1 << 1 +}; + +struct TinselGameDescription; + +enum TinselKeyDirection { +	MSK_LEFT = 1, MSK_RIGHT = 2, MSK_UP = 4, MSK_DOWN = 8, +	MSK_DIRECTION = MSK_LEFT | MSK_RIGHT | MSK_UP | MSK_DOWN +}; + +typedef bool (*KEYFPTR)(const Common::KeyState &); + +class TinselEngine : public ::Engine { +	int _gameId; +	Common::KeyState _keyPressed; +	Common::RandomSource _random; +	Graphics::Surface _screenSurface; +	Common::Point _mousePos; +	uint8 _dosPlayerDir; +	Console *_console; +	Scheduler *_scheduler; + +protected: + +	int init(); +	int go(); + +public: +	TinselEngine(OSystem *syst, const TinselGameDescription *gameDesc); +	virtual ~TinselEngine(); +	int getGameId() { +		return _gameId; +	} + +	const TinselGameDescription *_gameDescription; +	uint32 getGameID() const; +	uint32 getFeatures() const; +	Common::Language getLanguage() const; +	uint16 getVersion() const; +	Common::Platform getPlatform() const; +	bool quitFlag; + +	SoundManager *_sound; +	MusicPlayer *_music; + +	KEYFPTR _keyHandler; +private: +	//MusicPlayer *_music; +	int _musicVolume; + +	void NextGameCycle(void); +	void CreateConstProcesses(void); +	void RestartGame(void); +	void RestartDrivers(void); +	void ChopDrivers(void); +	void ProcessKeyEvent(const Common::Event &event); +	bool pollEvent(); + +public: +	const Common::String getTargetName() const { return _targetName; } +	Common::String getSavegamePattern() const; +	Common::String getSavegameFilename(int16 saveNum) const; +	Common::SaveFileManager *getSaveFileMan() { return _saveFileMan; } +	Graphics::Surface &screen() { return _screenSurface; } + +	Common::Point getMousePosition() const { return _mousePos; } +	void setMousePosition(const Common::Point &pt) { +		g_system->warpMouse(pt.x, pt.y); +		_mousePos = pt; +	} +	void divertKeyInput(KEYFPTR fptr) { _keyHandler = fptr; } +	int getRandomNumber(int maxNumber) { return _random.getRandomNumber(maxNumber); } +	uint8 getKeyDirection() const { return _dosPlayerDir; } +}; + +// Global reference to the TinselEngine object +extern TinselEngine *_vm; + +} // End of namespace Tinsel + +#endif /* TINSEL_H */ diff --git a/engines/tinsel/token.cpp b/engines/tinsel/token.cpp new file mode 100644 index 0000000000..0bdac0d6eb --- /dev/null +++ b/engines/tinsel/token.cpp @@ -0,0 +1,129 @@ +/* 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. + * + * $URL$ + * $Id$ + * + * To ensure exclusive use of resources and exclusive control responsibilities. + */ + +#include "common/util.h" + +#include "tinsel/sched.h" +#include "tinsel/token.h" + +namespace Tinsel { + +//----------------- LOCAL GLOBAL DATA -------------------- + +struct Token { +	PROCESS		*proc; +}; + +static Token tokens[NUMTOKENS]; + + +/** + * Release all tokens held by this process, and kill the process. + */ +static void TerminateProcess(PROCESS *tProc) { + +	// Release tokens held by the process +	for (int i = 0; i < NUMTOKENS; i++) { +		if (tokens[i].proc == tProc) { +			tokens[i].proc = NULL; +		} +	} + +	// Kill the process +	g_scheduler->killProcess(tProc); +} + +/** + * Gain control of the CONTROL token if it is free. + */ +void GetControlToken() { +	const int which = TOKEN_CONTROL; + +	if (tokens[which].proc == NULL) { +		tokens[which].proc = g_scheduler->getCurrentProcess(); +	} +} + +/** + * Release control of the CONTROL token. + */ +void FreeControlToken() { +	// Allow anyone to free TOKEN_CONTROL +	tokens[TOKEN_CONTROL].proc = NULL; +} + + +/** + * Gain control of a token. If the requested token is out of range, or + * is already held by the calling process, then the calling process + * will be killed off. + * + * Otherwise, the calling process will gain the token. If the token was + * held by another process, then the previous holder is killed off. + */ +void GetToken(int which) { +	assert(TOKEN_LEAD <= which && which < NUMTOKENS); + +	if (tokens[which].proc != NULL) { +		assert(tokens[which].proc != g_scheduler->getCurrentProcess()); +		TerminateProcess(tokens[which].proc); +	} + +	tokens[which].proc = g_scheduler->getCurrentProcess(); +} + +/** + * Release control of a token. If the requested token is not owned by + * the calling process, then the calling process will be killed off. + */ +void FreeToken(int which) { +	assert(TOKEN_LEAD <= which && which < NUMTOKENS); + +	assert(tokens[which].proc == g_scheduler->getCurrentProcess());	// we'd have been killed if some other proc had taken this token + +	tokens[which].proc = NULL; +} + +/** + * If it's a valid token and it's free, returns true. + */ +bool TestToken(int which) { +	if (which < 0 || which >= NUMTOKENS) +		return false; + +	return (tokens[which].proc == NULL); +} + +/** + * Call at the start of each scene. + */ +void FreeAllTokens(void) { +	for (int i = 0; i < NUMTOKENS; i++) { +		tokens[i].proc = NULL; +	} +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/token.h b/engines/tinsel/token.h new file mode 100644 index 0000000000..4ab4775bfb --- /dev/null +++ b/engines/tinsel/token.h @@ -0,0 +1,57 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef TINSEL_TOKEN_H +#define TINSEL_TOKEN_H + +#include "tinsel/dw.h" + +namespace Tinsel { + +// Fixed tokens + +enum { +	TOKEN_CONTROL = 0, +	TOKEN_LEAD, // = TOKEN_CONTROL + 1 +	TOKEN_LEFT_BUT = TOKEN_LEAD + MAX_MOVERS, + +	NUMTOKENS // = TOKEN_LEFT_BUT + 1 +}; + +// Token functions + +void GetControlToken(); +void FreeControlToken(); + +void GetToken(int which); +void FreeToken(int which); + +void FreeAllTokens(void); +bool TestToken(int which); + + +} // end of namespace Tinsel + +#endif		// TINSEL_TOKEN_H diff --git a/engines/touche/midi.cpp b/engines/touche/midi.cpp index 14cb85912a..d77dbf5bfa 100644 --- a/engines/touche/midi.cpp +++ b/engines/touche/midi.cpp @@ -23,6 +23,7 @@   *   */ +#include "common/config-manager.h"  #include "common/stream.h"  #include "sound/midiparser.h" @@ -31,9 +32,8 @@  namespace Touche { -MidiPlayer::MidiPlayer(MidiDriver *driver, bool nativeMT32) -	: _driver(driver), _parser(0), _midiData(0), _isLooping(false), _isPlaying(false), _masterVolume(0), _nativeMT32(nativeMT32) { -	assert(_driver); +MidiPlayer::MidiPlayer() +	: _driver(0), _parser(0), _midiData(0), _isLooping(false), _isPlaying(false), _masterVolume(0) {  	memset(_channelsTable, 0, sizeof(_channelsTable));  	memset(_channelsVolume, 0, sizeof(_channelsVolume));  	open(); @@ -92,6 +92,9 @@ void MidiPlayer::setVolume(int volume) {  }  int MidiPlayer::open() { +	int midiDriver = MidiDriver::detectMusicDriver(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MIDI); +	_nativeMT32 = ((midiDriver == MD_MT32) || ConfMan.getBool("native_mt32")); +	_driver = MidiDriver::createMidi(midiDriver);  	int ret = _driver->open();  	if (ret == 0) {  		_parser = MidiParser::createParser_SMF(); @@ -107,6 +110,7 @@ void MidiPlayer::close() {  	_mutex.lock();  	_driver->setTimerCallback(NULL, NULL);  	_driver->close(); +	delete _driver;  	_driver = 0;  	_parser->setMidiDriver(NULL);  	delete _parser; diff --git a/engines/touche/midi.h b/engines/touche/midi.h index 3b128593db..a518a4bb29 100644 --- a/engines/touche/midi.h +++ b/engines/touche/midi.h @@ -46,7 +46,7 @@ public:  		NUM_CHANNELS = 16  	}; -	MidiPlayer(MidiDriver *driver, bool nativeMT32); +	MidiPlayer();  	~MidiPlayer();  	void play(Common::ReadStream &stream, int size, bool loop = false); diff --git a/engines/touche/saveload.cpp b/engines/touche/saveload.cpp index eb647a1b42..4fcf6e114d 100644 --- a/engines/touche/saveload.cpp +++ b/engines/touche/saveload.cpp @@ -200,9 +200,6 @@ static void saveOrLoad(S &s, ProgramPointData &data) {  	saveOrLoad(s, data.order);  } -template <class S, class A> -static void saveOrLoadCommonArray(S &s, A &array); -  template <class A>  static void saveOrLoadCommonArray(Common::WriteStream &stream, A &array) {  	uint count = array.size(); diff --git a/engines/touche/touche.cpp b/engines/touche/touche.cpp index 6520fb5e4a..ac8e8a786a 100644 --- a/engines/touche/touche.cpp +++ b/engines/touche/touche.cpp @@ -91,10 +91,7 @@ int ToucheEngine::init() {  	setupOpcodes(); -	int midiDriver = MidiDriver::detectMusicDriver(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MIDI); -	bool native_mt32 = ((midiDriver == MD_MT32) || ConfMan.getBool("native_mt32")); -	MidiDriver *driver = MidiDriver::createMidi(midiDriver); -	_midiPlayer = new MidiPlayer(driver, native_mt32); +	_midiPlayer = new MidiPlayer;  	_mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, ConfMan.getInt("sfx_volume"));  	_mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, ConfMan.getInt("speech_volume"));  | 
