diff options
| author | Martin Kiewitz | 2016-01-29 13:13:40 +0100 | 
|---|---|---|
| committer | Martin Kiewitz | 2016-01-29 13:22:22 +0100 | 
| commit | 8a595e7771aa89d06876e13d7ab6751e26da8982 (patch) | |
| tree | dfc61e112c9f7e5b1d9e295fbb60edf4ae1b65f3 | |
| parent | 1e73796bd0b17740ca4c35b9a7bd1882f9de6a37 (diff) | |
| download | scummvm-rg350-8a595e7771aa89d06876e13d7ab6751e26da8982.tar.gz scummvm-rg350-8a595e7771aa89d06876e13d7ab6751e26da8982.tar.bz2 scummvm-rg350-8a595e7771aa89d06876e13d7ab6751e26da8982.zip  | |
AGI: graphics rewrite + cleanup
- graphics code fully rewritten
- Apple IIgs font support
- Amiga Topaz support
- Word parser rewritten
- menu code rewritten
- removed forced 2 second delay on all room changes
  replaced with heuristic to detect situations, where it's required
- lots of naming cleanup
- new console commands show_map, screenobj, vmvars and vmflags
- all sorts of hacks/workarounds removed
- added SCI wait mouse cursor
- added Apple IIgs mouse cursor
- added Atari ST mouse cursor
- added Amiga/Apple IIgs transition
- added Atari ST transition
- user can select another render mode and
  use Apple IIgs palette + transition for PC versions
- inventory screen rewritten
- SetSimple command now properly implemented
- PreAGI Mickey: Sierra logo now shown
- saved games: now saving controller key mapping
  also saving automatic save data (SetSimple command)
- fixed invalid memory access when saving games (31 bytes were saved
  using Common::String c_ptr()
Special Thanks to:
- fuzzie for helping out with the Apple IIgs font + valgrind
- eriktorbjorn for helping out with valgrind
- LordHoto for figuring out the code, that caused invalid memory
  access in the original code, when saving a game
- sev for help out with reversing the Amiga transition
currently missing:
- mouse support for menu
- mouse support for system dialogs
- predictive dialog support
49 files changed, 10174 insertions, 7159 deletions
diff --git a/engines/agi/agi.cpp b/engines/agi/agi.cpp index e907d3835e..19e05959dc 100644 --- a/engines/agi/agi.cpp +++ b/engines/agi/agi.cpp @@ -21,7 +21,6 @@   */  #include "common/md5.h" -#include "common/events.h"  #include "common/file.h"  #include "common/memstream.h"  #include "common/savefile.h" @@ -42,9 +41,13 @@  #include "agi/agi.h"  #include "agi/graphics.h" +#include "agi/inv.h"  #include "agi/sprite.h" +#include "agi/text.h"  #include "agi/keyboard.h"  #include "agi/menu.h" +#include "agi/systemui.h" +#include "agi/words.h"  #include "gui/predictivedialog.h" @@ -54,241 +57,6 @@ void AgiEngine::allowSynthetic(bool allow) {  	_allowSynthetic = allow;  } -void AgiEngine::processEvents() { -	Common::Event event; -	int key = 0; - -	while (_eventMan->pollEvent(event)) { -		switch (event.type) { -		case Common::EVENT_PREDICTIVE_DIALOG: { -			GUI::PredictiveDialog _predictiveDialog; -			_predictiveDialog.runModal(); -			strcpy(_predictiveResult, _predictiveDialog.getResult()); -			if (strcmp(_predictiveResult, "")) { -				if (_game.inputMode == INPUT_NORMAL) { -					strcpy((char *)_game.inputBuffer, _predictiveResult); -					handleKeys(KEY_ENTER); -				} else if (_game.inputMode == INPUT_GETSTRING) { -					strcpy(_game.strings[_stringdata.str], _predictiveResult); -					newInputMode(INPUT_NORMAL); -					_gfx->printCharacter(_stringdata.x + strlen(_game.strings[_stringdata.str]) + 1, -							_stringdata.y, ' ', _game.colorFg, _game.colorBg); -				} else if (_game.inputMode == INPUT_NONE) { -					for (int n = 0; _predictiveResult[n]; n++) -						keyEnqueue(_predictiveResult[n]); -				} -			} -			/* -			if (predictiveDialog()) { -				if (_game.inputMode == INPUT_NORMAL) { -					strcpy((char *)_game.inputBuffer, _predictiveResult); -					handleKeys(KEY_ENTER); -				} else if (_game.inputMode == INPUT_GETSTRING) { -					strcpy(_game.strings[_stringdata.str], _predictiveResult); -					newInputMode(INPUT_NORMAL); -					_gfx->printCharacter(_stringdata.x + strlen(_game.strings[_stringdata.str]) + 1, -							_stringdata.y, ' ', _game.colorFg, _game.colorBg); -				} else if (_game.inputMode == INPUT_NONE) { -					for (int n = 0; _predictiveResult[n]; n++) -						keyEnqueue(_predictiveResult[n]); -				} -			} -			*/ -			} -			break; -		case Common::EVENT_LBUTTONDOWN: -			if (_game.mouseEnabled) { -				key = BUTTON_LEFT; -				_mouse.button = kAgiMouseButtonLeft; -				keyEnqueue(key); -				_mouse.x = event.mouse.x; -				_mouse.y = event.mouse.y; -			} -			break; -		case Common::EVENT_RBUTTONDOWN: -			if (_game.mouseEnabled) { -				key = BUTTON_RIGHT; -				_mouse.button = kAgiMouseButtonRight; -				keyEnqueue(key); -				_mouse.x = event.mouse.x; -				_mouse.y = event.mouse.y; -			} -			break; -		case Common::EVENT_WHEELUP: -			if (_game.mouseEnabled) { -				key = WHEEL_UP; -				keyEnqueue(key); -			} -			break; -		case Common::EVENT_WHEELDOWN: -			if (_game.mouseEnabled) { -				key = WHEEL_DOWN; -				keyEnqueue(key); -			} -			break; -		case Common::EVENT_MOUSEMOVE: -			if (_game.mouseEnabled) { -				_mouse.x = event.mouse.x; -				_mouse.y = event.mouse.y; - -				if (!_game.mouseFence.isEmpty()) { -					if (_mouse.x < _game.mouseFence.left) -						_mouse.x = _game.mouseFence.left; -					if (_mouse.x > _game.mouseFence.right) -						_mouse.x = _game.mouseFence.right; -					if (_mouse.y < _game.mouseFence.top) -						_mouse.y = _game.mouseFence.top; -					if (_mouse.y > _game.mouseFence.bottom) -						_mouse.y = _game.mouseFence.bottom; - -					g_system->warpMouse(_mouse.x, _mouse.y); -				} -			} - -			break; -		case Common::EVENT_LBUTTONUP: -		case Common::EVENT_RBUTTONUP: -			if (_game.mouseEnabled) { -				_mouse.button = kAgiMouseButtonUp; -				_mouse.x = event.mouse.x; -				_mouse.y = event.mouse.y; -			} -			break; -		case Common::EVENT_KEYDOWN: -			if (event.kbd.hasFlags(Common::KBD_CTRL) && event.kbd.keycode == Common::KEYCODE_d) { -				_console->attach(); -				break; -			} - -			switch (key = event.kbd.keycode) { -			case Common::KEYCODE_LEFT: -			case Common::KEYCODE_KP4: -				if (_allowSynthetic || !event.synthetic) -					key = KEY_LEFT; -				break; -			case Common::KEYCODE_RIGHT: -			case Common::KEYCODE_KP6: -				if (_allowSynthetic || !event.synthetic) -					key = KEY_RIGHT; -				break; -			case Common::KEYCODE_UP: -			case Common::KEYCODE_KP8: -				if (_allowSynthetic || !event.synthetic) -					key = KEY_UP; -				break; -			case Common::KEYCODE_DOWN: -			case Common::KEYCODE_KP2: -				if (_allowSynthetic || !event.synthetic) -					key = KEY_DOWN; -				break; -			case Common::KEYCODE_PAGEUP: -			case Common::KEYCODE_KP9: -				if (_allowSynthetic || !event.synthetic) -					key = KEY_UP_RIGHT; -				break; -			case Common::KEYCODE_PAGEDOWN: -			case Common::KEYCODE_KP3: -				if (_allowSynthetic || !event.synthetic) -					key = KEY_DOWN_RIGHT; -				break; -			case Common::KEYCODE_HOME: -			case Common::KEYCODE_KP7: -				if (_allowSynthetic || !event.synthetic) -					key = KEY_UP_LEFT; -				break; -			case Common::KEYCODE_END: -			case Common::KEYCODE_KP1: -				if (_allowSynthetic || !event.synthetic) -					key = KEY_DOWN_LEFT; -				break; -			case Common::KEYCODE_KP5: -				key = KEY_STATIONARY; -				break; -			case Common::KEYCODE_PLUS: -				key = '+'; -				break; -			case Common::KEYCODE_MINUS: -				key = '-'; -				break; -			case Common::KEYCODE_TAB: -				key = 0x0009; -				break; -			case Common::KEYCODE_F1: -				key = 0x3b00; -				break; -			case Common::KEYCODE_F2: -				key = 0x3c00; -				break; -			case Common::KEYCODE_F3: -				key = 0x3d00; -				break; -			case Common::KEYCODE_F4: -				key = 0x3e00; -				break; -			case Common::KEYCODE_F5: -				key = 0x3f00; -				break; -			case Common::KEYCODE_F6: -				key = 0x4000; -				break; -			case Common::KEYCODE_F7: -				key = 0x4100; -				break; -			case Common::KEYCODE_F8: -				key = 0x4200; -				break; -			case Common::KEYCODE_F9: -				key = 0x4300; -				break; -			case Common::KEYCODE_F10: -				key = 0x4400; -				break; -			case Common::KEYCODE_F11: -				key = KEY_STATUSLN; -				break; -			case Common::KEYCODE_F12: -				key = KEY_PRIORITY; -				break; -			case Common::KEYCODE_ESCAPE: -				key = 0x1b; -				break; -			case Common::KEYCODE_RETURN: -			case Common::KEYCODE_KP_ENTER: -				key = KEY_ENTER; -				break; -			case Common::KEYCODE_BACKSPACE: -				key = KEY_BACKSPACE; -				break; -			default: -				// Not a special key, so get the ASCII code for it -				key = event.kbd.ascii; - -				if (Common::isAlpha(key)) { -					// Key is A-Z. -					// Map Ctrl-A to 1, Ctrl-B to 2, etc. -					if (event.kbd.flags & Common::KBD_CTRL) { -						key = toupper(key) - 'A' + 1; -					} else if (event.kbd.flags & Common::KBD_ALT) { -						// Map Alt-A, Alt-B etc. to special scancode values according to an internal scancode table. -						key = scancodeTable[toupper(key) - 'A'] << 8; -					} -				} -				break; -			} -			if (key) -				keyEnqueue(key); -			break; - -		case Common::EVENT_KEYUP: -			if (_egoHoldKey) -				_game.viewTable[0].direction = 0; - -		default: -			break; -		} -	} -} -  void AgiEngine::pollTimer() {  	_lastTick += 50; @@ -305,24 +73,14 @@ void AgiEngine::pollTimer() {  void AgiEngine::pause(uint32 msec) {  	uint32 endTime = _system->getMillis() + msec; -	_gfx->setCursor(_renderMode == Common::kRenderAmiga, true); +	_gfx->setMouseCursor(true); // Busy mouse cursor  	while (_system->getMillis() < endTime) {  		processEvents();  		_system->updateScreen();  		_system->delayMillis(10);  	} -	_gfx->setCursor(_renderMode == Common::kRenderAmiga); -} - -void AgiEngine::initPriTable() { -	int i, p, y = 0; - -	for (p = 1; p < 15; p++) { -		for (i = 0; i < 12; i++) { -			_game.priTable[y++] = p < 4 ? 4 : p; -		} -	} +	_gfx->setMouseCursor(); // regular mouse cursor  }  int AgiEngine::agiInit() { @@ -341,7 +99,7 @@ int AgiEngine::agiInit() {  		_game.vars[i] = 0;  	// clear all resources and events -	for (i = 0; i < MAX_DIRS; i++) { +	for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++) {  		memset(&_game.views[i], 0, sizeof(struct AgiView));  		memset(&_game.pictures[i], 0, sizeof(struct AgiPicture));  		memset(&_game.logics[i], 0, sizeof(struct AgiLogic)); @@ -353,15 +111,17 @@ int AgiEngine::agiInit() {  	}  	// clear view table -	for (i = 0; i < MAX_VIEWTABLE; i++) -		memset(&_game.viewTable[i], 0, sizeof(struct VtEntry)); +	for (i = 0; i < SCREENOBJECTS_MAX; i++) +		memset(&_game.screenObjTable[i], 0, sizeof(struct ScreenObjEntry)); + +	memset(&_game.addToPicView, 0, sizeof(struct ScreenObjEntry)); -	initWords(); +	_words->clearEgoWords();  	if (!_menu) -		_menu = new Menu(this, _gfx, _picture); +		_menu = new GfxMenu(this, _gfx, _picture, _text); -	initPriTable(); +	_gfx->initPriorityTable();  	// Clear the string buffer on startup, but not when the game restarts, as  	// some scripts expect that the game strings remain unaffected after a @@ -394,10 +154,6 @@ int AgiEngine::agiInit() {  	if (getFeatures() & GF_AGDS)  		_game.gameFlags |= ID_AGDS; -	// Make the 256 color AGI screen the default AGI screen when AGI256 or AGI256-2 is used -	if (getFeatures() & (GF_AGI256 | GF_AGI256_2)) -		_game.sbuf = _game.sbuf256c; -  	if (_game.gameFlags & ID_AMIGA)  		debug(1, "Amiga padded game detected."); @@ -413,12 +169,9 @@ int AgiEngine::agiInit() {  	if (ec == errOK)  		ec = _loader->loadWords(WORDS); -	// FIXME: load IIgs instruments and samples -	// load_instruments("kq.sys16"); -  	// Load logic 0 into memory  	if (ec == errOK) -		ec = _loader->loadResource(rLOGIC, 0); +		ec = _loader->loadResource(RESOURCETYPE_LOGIC, 0);  #ifdef __DS__  	// Normally, the engine loads the predictive text dictionary when the predictive dialog @@ -443,42 +196,42 @@ void AgiEngine::agiUnloadResources() {  	int i;  	// Make sure logic 0 is always loaded -	for (i = 1; i < MAX_DIRS; i++) { -		_loader->unloadResource(rLOGIC, i); +	for (i = 1; i < MAX_DIRECTORY_ENTRIES; i++) { +		_loader->unloadResource(RESOURCETYPE_LOGIC, i);  	} -	for (i = 0; i < MAX_DIRS; i++) { -		_loader->unloadResource(rVIEW, i); -		_loader->unloadResource(rPICTURE, i); -		_loader->unloadResource(rSOUND, i); +	for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++) { +		_loader->unloadResource(RESOURCETYPE_VIEW, i); +		_loader->unloadResource(RESOURCETYPE_PICTURE, i); +		_loader->unloadResource(RESOURCETYPE_SOUND, i);  	}  }  int AgiEngine::agiDeinit() {  	int ec; -	cleanInput();		// remove all words from memory +	_words->clearEgoWords();		// remove all words from memory  	agiUnloadResources();	// unload resources in memory -	_loader->unloadResource(rLOGIC, 0); +	_loader->unloadResource(RESOURCETYPE_LOGIC, 0);  	ec = _loader->deinit();  	unloadObjects(); -	unloadWords(); +	_words->unloadDictionary();  	clearImageStack();  	return ec;  } -int AgiEngine::agiLoadResource(int r, int n) { +int AgiEngine::agiLoadResource(int16 resourceType, int16 resourceNr) {  	int i; -	i = _loader->loadResource(r, n); +	i = _loader->loadResource(resourceType, resourceNr);  	// WORKAROUND: Patches broken picture 147 in a corrupted Amiga version of Gold Rush! (v2.05 1989-03-09).  	// The picture can be seen in room 147 after dropping through the outhouse's hole in room 146. -	if (i == errOK && getGameID() == GID_GOLDRUSH && r == rPICTURE && n == 147 && _game.dirPic[n].len == 1982) { -		uint8 *pic = _game.pictures[n].rdata; -		Common::MemoryReadStream picStream(pic, _game.dirPic[n].len); -		Common::String md5str = Common::computeStreamMD5AsString(picStream, _game.dirPic[n].len); +	if (i == errOK && getGameID() == GID_GOLDRUSH && resourceType == RESOURCETYPE_PICTURE && resourceNr == 147 && _game.dirPic[resourceNr].len == 1982) { +		uint8 *pic = _game.pictures[resourceNr].rdata; +		Common::MemoryReadStream picStream(pic, _game.dirPic[resourceNr].len); +		Common::String md5str = Common::computeStreamMD5AsString(picStream, _game.dirPic[resourceNr].len);  		if (md5str == "1c685eb048656cedcee4eb6eca2cecea") {  			pic[0x042] = 0x4B; // 0x49 -> 0x4B  			pic[0x043] = 0x66; // 0x26 -> 0x66 @@ -492,8 +245,8 @@ int AgiEngine::agiLoadResource(int r, int n) {  	return i;  } -int AgiEngine::agiUnloadResource(int r, int n) { -	return _loader->unloadResource(r, n); +int AgiEngine::agiUnloadResource(int16 resourceType, int16 resourceNr) { +	return _loader->unloadResource(resourceType, resourceNr);  }  struct GameSettings { @@ -510,7 +263,8 @@ AgiBase::AgiBase(OSystem *syst, const AGIGameDescription *gameDesc) : Engine(sys  	_rnd = new Common::RandomSource("agi");  	_sound = 0; -	_fontData = NULL; +	_fontData = nullptr; +	_fontDataAllocated = nullptr;  	initFeatures();  	initVersion(); @@ -520,30 +274,419 @@ AgiBase::~AgiBase() {  	delete _rnd;  	delete _sound; + +	if (_fontDataAllocated) { +		free(_fontDataAllocated); +	}  }  void AgiBase::initRenderMode() { -	_renderMode = Common::kRenderEGA; +	Common::Platform platform = Common::parsePlatform(ConfMan.get("platform")); +	Common::RenderMode configRenderMode = Common::parseRenderMode(ConfMan.get("render_mode").c_str()); + +	// Default to EGA PC rendering +	_renderMode = RENDERMODE_EGA; + +	switch (platform) { +	case Common::kPlatformDOS: +		switch (configRenderMode) { +		case Common::kRenderCGA: +			_renderMode = RENDERMODE_CGA; +			break; +		// Hercules is not supported atm +		//case Common::kRenderHercA: +		//case Common::kRenderHercG: +		//	_renderMode = RENDERMODE_HERCULES; +		//	break; +		default: +			break; +		} +		break; +	case Common::kPlatformAmiga: +		_renderMode = RENDERMODE_AMIGA; +		break; +	case Common::kPlatformApple2GS: +		_renderMode = RENDERMODE_APPLE_II_GS; +		break; +	case Common::kPlatformAtariST: +		_renderMode = RENDERMODE_ATARI_ST; +		break; +	default: +		break; +	} -	if (ConfMan.hasKey("platform")) { -		Common::Platform platform = Common::parsePlatform(ConfMan.get("platform")); -		_renderMode = (platform == Common::kPlatformAmiga) ? Common::kRenderAmiga : Common::kRenderEGA; +	// If render mode is explicitly set, force rendermode +	switch (configRenderMode) { +	case Common::kRenderAmiga: +		_renderMode = RENDERMODE_AMIGA; +		break; +	case Common::kRenderApple2GS: +		_renderMode = RENDERMODE_APPLE_II_GS; +		break; +	case Common::kRenderAtariST: +		_renderMode = RENDERMODE_ATARI_ST; +		break; +	default: +		break;  	} -	if (ConfMan.hasKey("render_mode")) { -		Common::RenderMode tmpMode = Common::parseRenderMode(ConfMan.get("render_mode").c_str()); -		if (tmpMode != Common::kRenderDefault) -			_renderMode = tmpMode; +	if (getFeatures() & (GF_AGI256 | GF_AGI256_2)) { +		// If current game is AGI256, switch (force) to VGA render mode +		_renderMode = RENDERMODE_VGA;  	}  } +void AgiBase::initFont() { +	// We are currently using the custom font for all fanmade games +	if (getFeatures() & (GF_FANMADE | GF_AGDS)) { +		// fanmade game, use custom font for now +		_fontData = fontData_FanGames; // our (own?) custom font, that supports umlauts etc. +		return; +	} + +	switch (_renderMode) { +	case RENDERMODE_AMIGA: +		loadFontAmigaPseudoTopaz(); +		//_fontData = fontData_Amiga; // use Amiga Topaz font +		break; +	case RENDERMODE_APPLE_II_GS: +		// Special font, stored in file AGIFONT +		loadFontAppleIIgs(); +		break; +	case RENDERMODE_ATARI_ST: +		// TODO: Atari ST uses another font +		// Seems to be the standard Atari ST 8x8 system font + +	case RENDERMODE_CGA: +	case RENDERMODE_EGA: +	case RENDERMODE_VGA: +		switch (getGameID()) { +		case GID_MICKEY: +			// load mickey mouse font from interpreter file +			loadFontMickey(); +			break; +		default: +			break; +		} +		break; + +	default: +		break; +	} + +	if (!_fontData) { +		// no font asigned? +		switch (getGameID()) { +		case GID_MICKEY: +		case GID_TROLL: +		case GID_WINNIE: +			// use IBM font for pre-AGI games as standard +			_fontData = fontData_IBM; +			break; +		default: +			// for everything else use Sierra PC font +			_fontData = fontData_Sierra; +			break; +		} +	} +} + +// We load the Mickey Mouse font from MICKEY.EXE +void AgiBase::loadFontMickey() { +	Common::File interpreterFile; +	int32 interpreterFileSize = 0; +	byte *fontData = nullptr; + +	if (!interpreterFile.open("mickey.exe")) { +		// Continue, if file not found +		return; +	} + +	interpreterFileSize = interpreterFile.size(); +	if (interpreterFileSize != 55136) { +		// unexpected file size +		interpreterFile.close(); +		warning("mickey.exe unexpected file size"); +		return; +	} +	interpreterFile.seek(32476); // offset of font data + +	// allocate space for font bitmap data +	fontData = (uint8 *)calloc(256, 8); +	_fontData = fontData; +	_fontDataAllocated = fontData; +  +	// read font data, is already in the format that we need (plain bitmap 8x8) +	interpreterFile.read(fontData, 256 * 8); +	interpreterFile.close(); +} + +// we create a bitmap out of the topaz data used in parallaction (which is normally found in staticres.cpp) +// it's a recreation of the Amiga Topaz font but not really accurate +void AgiBase::loadFontAmigaPseudoTopaz() { +	const byte *topazStart = fontData_AmigaPseudoTopaz + 32; +	const byte *topazHeader = topazStart + 78; +	const byte *topazData = nullptr; +	const byte *topazLocations = nullptr; +	byte *fontData = nullptr; +	uint16 topazHeight = 0; +	uint16 topazWidth = 0; +	uint16 topazModulo = 0; +	uint32 topazDataOffset = 0; +	uint32 topazLocationOffset = 0; +	byte   topazLowChar = 0; +	byte   topazHighChar = 0; +	uint16 topazTotalChars = 0; +	uint16 topazBitLength = 0; +	uint16 topazBitOffset = 0; +	uint16 topazByteOffset = 0; + +	// allocate space for font bitmap data +	fontData = (uint8 *)calloc(256, 8); +	_fontData = fontData; +	_fontDataAllocated = fontData; + +	topazHeight = READ_BE_UINT16(topazHeader + 0); +	topazWidth = READ_BE_UINT16(topazHeader + 4); + +	// we expect 8x8 +	assert(topazHeight == 8); +	assert(topazWidth == 8); + +	topazLowChar = topazHeader[12]; +	topazHighChar = topazHeader[13]; +	topazTotalChars = topazHighChar - topazLowChar + 1; +	topazDataOffset = READ_BE_UINT32(topazHeader + 14); +	topazModulo = READ_BE_UINT16(topazHeader + 18); +	topazLocationOffset = READ_BE_UINT32(topazHeader + 20); + +	// Security checks +	assert(topazLowChar == ' '); +	assert(topazHighChar == 0xFF); + +	// copy first 32 characters over +	memcpy(fontData, fontData_Sierra, FONT_DISPLAY_WIDTH * 32); +	fontData += FONT_DISPLAY_WIDTH * 32; + +	// now actually convert from topaz data +	topazData = topazStart + topazDataOffset; +	topazLocations = topazStart + topazLocationOffset; + +	for (uint16 curChar = 0; curChar < topazTotalChars; curChar++) { +		topazBitOffset = READ_BE_UINT16(topazLocations + (curChar * 4) + 0); +		topazBitLength = READ_BE_UINT16(topazLocations + (curChar * 4) + 2); + +		if (topazBitLength == 8) { +			assert((topazBitOffset & 7) == 0); + +			topazByteOffset = topazBitOffset >> 3; +			for (uint16 curHeight = 0; curHeight < topazHeight; curHeight++) { +				*fontData = topazData[topazByteOffset]; +				fontData++; +				topazByteOffset += topazModulo; +			} +		} else { +			memset(fontData, 0, 8); +			fontData += 8; +		} +	} +} + +void AgiBase::loadFontAppleIIgs() { +	Common::File fontFile; +	uint16 headerIIgs_OffsetMacHeader = 0; +	uint16 headerIIgs_Version = 0; +	uint16 macRecord_FirstChar = 0; +	uint16 macRecord_LastChar = 0; +	int16 macRecord_MaxKern = 0; +	uint16 macRecord_RectHeight = 0; +	uint16 macRecord_StrikeWidth = 0; +	uint16 strikeDataLen = 0; +	byte *strikeDataPtr = nullptr; +	uint16 actualCharacterCount = 0; +	uint16 totalCharacterCount = 0; +	uint16 *locationTablePtr = nullptr; +	uint16 *offsetWidthTablePtr = nullptr; + +	uint16 curCharNr = 0; +	uint16 curRow = 0; +	uint16 curLocation = 0; +	uint16 curLocationBytes = 0; +	uint16 curLocationBits = 0; +	uint16 curCharOffsetWidth = 0; +	uint16 curCharOffset = 0; +	uint16 curCharWidth = 0; +	uint16 curStrikeWidth = 0; + +	uint16 curPixelNr = 0; +	uint16 curBitMask = 0; +	int16 positionAdjust = 0; +	byte curByte = 0; +	byte fontByte = 0; + +	uint16 strikeRowOffset = 0; +	uint16 strikeCurOffset = 0; + +	byte *fontData = nullptr; + +	if (!fontFile.open("agifont")) { +		// Continue, +		// This also happens when the user selected Apple IIgs as render for the palette for non-AppleIIgs games +		warning("could not open agifont for Apple IIgs font"); +		return; +	} + +	// Apple IIgs header +	headerIIgs_OffsetMacHeader = fontFile.readUint16LE(); +	fontFile.skip(2); // font family +	fontFile.skip(2); // font style +	fontFile.skip(2); // point size +	headerIIgs_Version = fontFile.readUint16LE(); +	fontFile.skip(2); // bounds type +	// end of Apple IIgs header +	// Macintosh font record +	fontFile.skip(2); // font type +	macRecord_FirstChar = fontFile.readUint16LE(); +	macRecord_LastChar = fontFile.readUint16LE(); +	fontFile.skip(2); // max width +	macRecord_MaxKern = fontFile.readSint16LE(); +	fontFile.skip(2); // negative descent +	fontFile.skip(2); // rect width +	macRecord_RectHeight = fontFile.readUint16LE(); +	fontFile.skip(2); // low word ptr table +	fontFile.skip(2); // font ascent +	fontFile.skip(2); // font descent +	fontFile.skip(2); // leading +	macRecord_StrikeWidth = fontFile.readUint16LE(); + +	// security-checks +	if (headerIIgs_OffsetMacHeader != 6) +		error("AppleIIgs-font: unexpected header"); +	if (headerIIgs_Version != 0x0101) +		error("AppleIIgs-font: not a 1.1 font"); +	if ((macRecord_FirstChar != 0) || (macRecord_LastChar != 255)) +		error("AppleIIgs-font: unexpected characters"); +	if (macRecord_RectHeight != 8) +		error("AppleIIgs-font: expected 8x8 font"); + +	// Calculate table sizes +	strikeDataLen = macRecord_StrikeWidth * macRecord_RectHeight * 2; +	actualCharacterCount = (macRecord_LastChar - macRecord_FirstChar + 1); +	totalCharacterCount = actualCharacterCount + 2; // replacement-char + extra character + +	// Allocate memory for tables +	strikeDataPtr = (byte *)calloc(strikeDataLen, 1); +	locationTablePtr = (uint16 *)calloc(totalCharacterCount, 2); // 1 word per character +	offsetWidthTablePtr = (uint16 *)calloc(totalCharacterCount, 2); // ditto + +	// read tables +	fontFile.read(strikeDataPtr, strikeDataLen); +	for (curCharNr = 0; curCharNr < totalCharacterCount; curCharNr++) { +		locationTablePtr[curCharNr] = fontFile.readUint16LE(); +	} +	for (curCharNr = 0; curCharNr < totalCharacterCount; curCharNr++) { +		offsetWidthTablePtr[curCharNr] = fontFile.readUint16LE(); +	} +	fontFile.close(); + +	// allocate space for font bitmap data +	fontData = (uint8 *)calloc(256, 8); +	_fontData = fontData; +	_fontDataAllocated = fontData; + +	// extract font bitmap data +	for (curCharNr = 0; curCharNr < actualCharacterCount; curCharNr++) { +		curCharOffsetWidth = offsetWidthTablePtr[curCharNr]; +		curLocation = locationTablePtr[curCharNr]; +		if (curCharOffsetWidth == 0xFFFF) { +			// character does not exist in font, use replacement character instead +			curCharOffsetWidth = offsetWidthTablePtr[actualCharacterCount]; +			curLocation = locationTablePtr[actualCharacterCount]; +			curStrikeWidth = locationTablePtr[actualCharacterCount + 1] - curLocation; +		} else { +			curStrikeWidth = locationTablePtr[curCharNr + 1] - curLocation; +		} + +		// Figure out bytes + bits location +		curLocationBytes = curLocation >> 3; +		curLocationBits = curLocation & 0x0007; +		curCharWidth = curCharOffsetWidth & 0x00FF; // isolate width +		curCharOffset = curCharOffsetWidth >> 8; // isolate offset + +		if (!curCharWidth) { +			fontData += 8; // skip over this character +			continue; +		} + +		if (curCharWidth != 8) { +			if (curCharNr != 0x3B) +				error("AppleIIgs-font: expected 8x8 font"); +		} + +		// Get all rows of the current character +		strikeRowOffset = 0; +		for (curRow = 0; curRow < macRecord_RectHeight; curRow++) { +			strikeCurOffset = strikeRowOffset + curLocationBytes; + +			// Copy over bits +			fontByte = 0; +			curByte = strikeDataPtr[strikeCurOffset]; +			curBitMask = 0x80 >> curLocationBits; + +			for (curPixelNr = 0; curPixelNr < curStrikeWidth; curPixelNr++) { +				fontByte = fontByte << 1; +				if (curByte & curBitMask) { +					fontByte |= 0x01; +				} +				curBitMask = curBitMask >> 1; +				if (!curBitMask) { +					curByte = strikeDataPtr[strikeCurOffset + 1]; +					curBitMask = 0x80; +				} +			} + +			// adjust, so that it's aligned to the left (starting at 0x80 bit) +			fontByte = fontByte << (8 - curStrikeWidth); + +			// now adjust according to offset + MaxKern +			positionAdjust = macRecord_MaxKern + curCharOffset; + +			// adjust may be negative for space, or 8 for "empty" characters +			if (positionAdjust > 8) +				error("AppleIIgs-font: invalid character spacing"); + +			if (positionAdjust < 0) { +				// negative adjust strangely happens for empty characters like space +				if (curStrikeWidth) +					error("AppleIIgs-font: invalid character spacing"); +			} + +			if (positionAdjust > 0) { +				// move the amount of pixels to the right +				fontByte = fontByte >> positionAdjust; +			} + +			*fontData = fontByte; +			fontData++; + +			strikeRowOffset += macRecord_StrikeWidth * 2; +		} +	} + +	free(offsetWidthTablePtr); +	free(locationTablePtr); +	free(strikeDataPtr); + +	// overwrite character 0x1A with the standard Sierra arrow to the right character +	// required for the original save/restore dialogs +	memcpy(_fontDataAllocated + (0x1A * 8), fontData_ArrowRightCharacter, sizeof(fontData_ArrowRightCharacter)); +} +  AgiEngine::AgiEngine(OSystem *syst, const AGIGameDescription *gameDesc) : AgiBase(syst, gameDesc) {  	// Setup mixer  	syncSoundSettings(); -	parseFeatures(); -  	DebugMan.addDebugChannel(kDebugLevelMain, "Main", "Generic debug level");  	DebugMan.addDebugChannel(kDebugLevelResources, "Resources", "Resources debugging");  	DebugMan.addDebugChannel(kDebugLevelSprites, "Sprites", "Sprites debugging"); @@ -561,20 +704,21 @@ AgiEngine::AgiEngine(OSystem *syst, const AGIGameDescription *gameDesc) : AgiBas  	memset(&_mouse, 0, sizeof(struct Mouse));  	_game.mouseEnabled = true; +	_game.mouseHidden = false; +	// don't check for Amiga, Amiga doesn't allow disabling mouse support. It's mandatory.  	if (!ConfMan.getBool("mousesupport")) {  		// we effectively disable the mouse for games, that explicitly do not want mouse support to be enabled  		_game.mouseEnabled = false; +		_game.mouseHidden = true;  	} -	// We are currently using the custom font for all fanmade games -	if (!(getFeatures() & (GF_FANMADE | GF_AGDS))) { -		_fontData = fontData_Sierra; // original Sierra font -	} else { -		_fontData = fontData_FanGames; // our (own?) custom font, that supports umlauts etc. -	} +	_fontData = nullptr; +	_fontDataAllocated = nullptr;  	_game._vm = this; +	_game.gfxMode = true; +  	_game.clockEnabled = false;  	_game.state = STATE_INIT; @@ -585,17 +729,13 @@ AgiEngine::AgiEngine(OSystem *syst, const AGIGameDescription *gameDesc) : AgiBas  	_intobj = NULL; -	_menu = NULL; -	_menuSelected = false; - -	_lastSentence[0] = 0;  	memset(&_stringdata, 0, sizeof(struct StringData));  	_objects = NULL;  	_restartGame = false; -	_oldMode = INPUT_NONE; +	_oldMode = INPUTMODE_NONE;  	_firstSlot = 0; @@ -609,16 +749,17 @@ AgiEngine::AgiEngine(OSystem *syst, const AGIGameDescription *gameDesc) : AgiBas  	_lastTick = 0;  	memset(_keyQueue, 0, sizeof(_keyQueue)); -	memset(_predictiveResult, 0, sizeof(_predictiveResult)); +	_text = NULL;  	_sprites = NULL;  	_picture = NULL;  	_loader = NULL;  	_console = NULL; +	_menu = NULL; +	_gfx = NULL; +	_systemUI = NULL;  	_egoHoldKey = false; - -  }  void AgiEngine::initialize() { @@ -657,29 +798,28 @@ void AgiEngine::initialize() {  	}  	initRenderMode(); +	initFont(); -	_buttonStyle = AgiButtonStyle(_renderMode); -	_defaultButtonStyle = AgiButtonStyle();  	_console = new Console(this); +	_words = new Words(this);  	_gfx = new GfxMgr(this);  	_sound = new SoundMgr(this, _mixer);  	_picture = new PictureMgr(this, _gfx);  	_sprites = new SpritesMgr(this, _gfx); +	_text = new TextMgr(this, _words, _gfx); +	_systemUI = new SystemUI(this, _gfx, _text); +	_inventory = new InventoryMgr(this, _gfx, _text, _systemUI); + +	_text->init(_systemUI);  	_gfx->initMachine();  	_game.gameFlags = 0; -	_game.colorFg = 15; -	_game.colorBg = 0; +	_text->charAttrib_Set(15, 0);  	_game.name[0] = '\0'; -	_game.sbufOrig = (uint8 *)calloc(_WIDTH, _HEIGHT * 2); // Allocate space for two AGI screens vertically -	_game.sbuf16c  = _game.sbufOrig + SBUF16_OFFSET; // Make sbuf16c point to the 16 color (+control line & priority info) AGI screen -	_game.sbuf256c = _game.sbufOrig + SBUF256_OFFSET; // Make sbuf256c point to the 256 color AGI screen -	_game.sbuf     = _game.sbuf16c; // Make sbuf point to the 16 color (+control line & priority info) AGI screen by default -  	_gfx->initVideo();  	_lastSaveTime = 0; @@ -698,6 +838,30 @@ void AgiEngine::initialize() {  	debugC(2, kDebugLevelMain, "Init sound");  } +bool AgiEngine::promptIsEnabled() { +	return _text->promptIsEnabled(); +} + +void AgiEngine::redrawScreen() { +	_game.gfxMode = true; // enable graphics mode +	_gfx->setPalette(true); // set graphics mode palette +	_text->charAttrib_Set(_text->_textAttrib.foreground, _text->_textAttrib.background); +	_gfx->clearDisplay(0); +	_picture->showPic(); +	_text->statusDraw(); +	_text->promptRedraw(); +} + +// Adjust a given coordinate to the local game screen +// Used on mouse cursor coordinates before passing them to scripts +void AgiEngine::adjustPosToGameScreen(int16 &x, int16 &y) { +	x = x / 2; // 320 -> 160 +	y = y - 8; // remove status bar line +	if (y >= SCRIPT_HEIGHT) { +		y = SCRIPT_HEIGHT + 1; // 1 beyond +	} +} +  AgiEngine::~AgiEngine() {  	// If the engine hasn't been initialized yet via  	// AgiEngine::initialize(), don't attempt to free any resources, as @@ -710,22 +874,25 @@ AgiEngine::~AgiEngine() {  	agiDeinit();  	delete _loader;  	_gfx->deinitVideo(); +	delete _inventory; +	delete _systemUI; +	delete _text;  	delete _sprites;  	delete _picture; -	free(_game.sbufOrig);  	_gfx->deinitMachine();  	delete _gfx; +	delete _words;  	delete _console;  }  Common::Error AgiBase::init() {  	// Initialize backend -	initGraphics(320, 200, false); +	//initGraphics(320, 200, false);  	initialize(); -	_gfx->gfxSetPalette(); +	_gfx->setPalette(true);  	return Common::kNoError;  } @@ -747,74 +914,68 @@ Common::Error AgiEngine::go() {  	return Common::kNoError;  } -void AgiEngine::parseFeatures() { - -	/* FIXME: Seems this method doesn't really do anything. It might -	   be a leftover that could be removed, except that some of its -	   intended purpose may still need to be reimplemented. - -	[0:29] <Fingolfin> can you tell me what the point behind AgiEngine::parseFeatures() is? -	[0:30] <_sev> when games are created with WAGI studio -	[0:31] <_sev> it creates .wag site with game-specific features such as full game title, whether to use AGIMOUSE etc -	[0:32] <Fingolfin> ... and the "features" config key is created by our detector based on the wag file, I guess? -	[0:33] <_sev> yes -	[0:33] <Fingolfin> it's just that I cant seem to find a place we do that -	[0:33] <_sev> it is used for fallback -	[0:34] <_sev> ah, perhaps it was not updated -	[0:34] <Fingolfin> I only see us check the value, but never set it -	[0:34] <Fingolfin> maybe I am grepping wrong, who knows :) -	[0:44] <Fingolfin> _sev: so, unless I miss something, it seem that function does nothing right now -	[0:45] <_sev> Fingolfin: it could be unfinished. It was part of GSoC 3 years ago -	[0:45] <Fingolfin> well -	[0:45] <_sev> I just don't remember -	[0:45] <Fingolfin> but don't we just re-parse the wag when the game is loaded anyway? -	[0:45] <_sev> but it documents the format -	[0:45] <Fingolfin> the advanced meta engine would re-run the detector, wouldn't it? -	[0:45] <_sev> yep -	[0:47] <Fingolfin> so... shouldn't we at least add a comment to the function explaining what it does and that it's unfinished etc.? maybe add a TODO to the wiki? -	[0:47] <Fingolfin> otherwise it might stay as it is for another 3 years :) -	*/ - -	if (!ConfMan.hasKey("features")) -		return; +// Scenes that need this: +// +// Manhunter 1: +//  - intro text screen (DrawPic) +//  - MAD "zooming in..." during intro and other scenes, for example room 124 (NewRoom) +//     The NewRoom call is not done during the same cycle as the "zooming in..." print call. +// Space Quest 1: +//  - right at the start of the game (NewRoom) +// Space Quest 2 +//  - right at the start of the game (NewRoom) +//  - after exiting the very first room, a message pops up, that isn't readable without it (NewRoom) + + +// Games, that must not be triggered: +// +// Fanmade Voodoo Girl: +//  - waterfall (DrawPic, room 17) +//  - inside shop (NewRoom, changes to same room every new button, room 4) + +void AgiEngine::nonBlockingText_IsShown() { +	_game.nonBlockingTextShown = true; +	_game.nonBlockingTextCyclesLeft = 2; // 1 additional script cycle is counted too +} +void AgiEngine::nonBlockingText_Forget() { +	_game.nonBlockingTextShown = false; +	_game.nonBlockingTextCyclesLeft = 0; +} +void AgiEngine::nonBlockingText_CycleDone() { +	if (_game.nonBlockingTextCyclesLeft) { +		_game.nonBlockingTextCyclesLeft--; + +		if (!_game.nonBlockingTextCyclesLeft) { +			// cycle count expired, we assume that non-blocking text won't be a problem for room / pic change +			_game.nonBlockingTextShown = false; +		} +	} +} -	char *features = strdup(ConfMan.get("features").c_str()); -	const char *feature[100]; -	int numFeatures = 0; +void AgiEngine::loadingTrigger_NewRoom(int16 newRoomNr) { +	if (_game.nonBlockingTextShown) { +		_game.nonBlockingTextShown = false; -	char *tok = strtok(features, " "); -	if (tok) { -		do { -			feature[numFeatures++] = tok; -		} while ((tok = strtok(NULL, " ")) != NULL); -	} else { -		feature[numFeatures++] = features; -	} - -	const struct Flags { -		const char *name; -		uint32 flag; -	} flags[] = { -		{ "agimouse", GF_AGIMOUSE }, -		{ "agds", GF_AGDS }, -		{ "agi256", GF_AGI256 }, -		{ "agi256-2", GF_AGI256_2 }, -		{ "agipal", GF_AGIPAL }, -		{ 0, 0 } -	}; - -	for (int i = 0; i < numFeatures; i++) { -		for (const Flags *flag = flags; flag->name; flag++) { -			if (!scumm_stricmp(feature[i], flag->name)) { -				debug(2, "Added feature: %s", flag->name); - -				setFeature(flag->flag); -				break; +		int16 curRoomNr = _game.vars[VM_VAR_CURRENT_ROOM]; + +		if (newRoomNr != curRoomNr) { +			if (!_game.automaticRestoreGame) { +				// wait a bit, we detected non-blocking text +				pause(2000); // 2 seconds  			}  		}  	} +} + +void AgiEngine::loadingTrigger_DrawPicture() { +	if (_game.nonBlockingTextShown) { +		_game.nonBlockingTextShown = false; -	free(features); +		if (!_game.automaticRestoreGame) { +			// wait a bit, we detected non-blocking text +			pause(2000); // 2 seconds +		} +	}  }  } // End of namespace Agi diff --git a/engines/agi/agi.h b/engines/agi/agi.h index 04e02dcf87..fd44afd910 100644 --- a/engines/agi/agi.h +++ b/engines/agi/agi.h @@ -79,14 +79,18 @@ typedef signed int Err;  #define OBJECTS		"object"  #define WORDS		"words.tok" -#define	MAX_DIRS	256 +#define MAX_DIRECTORY_ENTRIES 256 +#define	MAX_CONTROLLERS	256  #define	MAX_VARS	256  #define	MAX_FLAGS	(256 >> 3) -#define MAX_VIEWTABLE	255	// KQ3 uses o255! +#define SCREENOBJECTS_MAX	255	// KQ3 uses o255! +#define SCREENOBJECTS_EGO_ENTRY 0 // first entry is ego  #define MAX_WORDS	20  #define	MAX_STRINGS	24		// MAX_STRINGS + 1 used for get.num  #define MAX_STRINGLEN	40 -#define MAX_CONTROLLERS 39 +#define MAX_CONTROLLER_KEYMAPPINGS 39 + +#define SAVEDGAME_DESCRIPTION_LEN 30  #define	_EMPTY		0xfffff  #define	EGO_OWNED	0xff @@ -95,13 +99,6 @@ typedef signed int Err;  #define	CRYPT_KEY_SIERRA	"Avis Durgan"  #define CRYPT_KEY_AGDS		"Alex Simkin" -#define	MSG_BOX_COLOR	0x0f	// White -#define MSG_BOX_TEXT	0x00	// Black -#define MSG_BOX_LINE	0x04	// Red -#define BUTTON_BORDER	0x00	// Black -#define STATUS_FG	0x00		// Black -#define	STATUS_BG	0x0f		// White -  #define ADD_PIC 1  #define ADD_VIEW 2 @@ -128,7 +125,7 @@ enum AgiGameID {  	GID_GETOUTTASQ,	// Fanmade  	GID_MICKEY,			// PreAGI  	GID_WINNIE,			// PreAGI -	GID_TROLL				// PreAGI +	GID_TROLL			// PreAGI  };  enum AgiGameType { @@ -143,6 +140,16 @@ enum AgiGameType {  	 BooterDisk2 = 1   }; +enum AgiRenderMode { +	RENDERMODE_EGA = 0, +	RENDERMODE_CGA = 1, +	RENDERMODE_VGA = 2, +	RENDERMODE_HERCULES = 3, +	RENDERMODE_AMIGA = 4, +	RENDERMODE_APPLE_II_GS = 5, +	RENDERMODE_ATARI_ST = 6 +}; +  //  // GF_OLDAMIGAV20 means that the interpreter is an old Amiga AGI interpreter that  // uses value 20 for the computer type (v20 i.e. vComputer) rather than the usual value 5. @@ -206,15 +213,17 @@ enum kDebugLevels {   * AGI resources.   */  enum { -	rLOGIC = 1, -	rSOUND, -	rVIEW, -	rPICTURE +	RESOURCETYPE_LOGIC = 1, +	RESOURCETYPE_SOUND, +	RESOURCETYPE_VIEW, +	RESOURCETYPE_PICTURE  };  enum { -	RES_LOADED = 1, -	RES_COMPRESSED = 0x40 +	RES_LOADED                 = 0x01, +	RES_COMPRESSED             = 0x40, +	RES_PICTURE_V3_NIBBLE_PARM = 0x80  // Flag that gets set for picture resources, +	                                   // which use a nibble instead of a byte as F0+F2 parameters  };  enum { @@ -244,44 +253,37 @@ enum AgiMouseButton {  	kAgiMouseButtonMiddle // Middle mouse button  }; -enum GameId { -	GID_AGI = 1 -}; - -#define WIN_TO_PIC_X(x) ((x) / 2) -#define WIN_TO_PIC_Y(y) ((y) < 8 ? 999 : (y) >= (8 + _HEIGHT) ? 999 : (y) - 8) -  /**   * AGI variables.   */  enum { -	vCurRoom = 0,		// 0 -	vPrevRoom, -	vBorderTouchEgo, -	vScore, -	vBorderCode, -	vBorderTouchObj,	// 5 -	vEgoDir, -	vMaxScore, -	vFreePages, -	vWordNotFound, -	vTimeDelay,		// 10 -	vSeconds, -	vMinutes, -	vHours, -	vDays, -	vJoystickSensitivity,	// 15 -	vEgoViewResource, -	vAgiErrCode, -	vAgiErrCodeInfo, -	vKey, -	vComputer,		// 20 -	vWindowReset, -	vSoundgen, -	vVolume, -	vMaxInputChars, -	vSelItem,		// 25 -	vMonitor +	VM_VAR_CURRENT_ROOM = 0,		// 0 +	VM_VAR_PREVIOUS_ROOM,			// 1 +	VM_VAR_BORDER_TOUCH_EGO,		// 2 +	VM_VAR_SCORE,					// 3 +	VM_VAR_BORDER_CODE,				// 4 +	VM_VAR_BORDER_TOUCH_OBJECT,		// 5 +	VM_VAR_EGO_DIRECTION,			// 6 +	VM_VAR_MAX_SCORE,				// 7 +	VM_VAR_FREE_PAGES,				// 8 +	VM_VAR_WORD_NOT_FOUND,			// 9 +	VM_VAR_TIME_DELAY,				// 10 +	VM_VAR_SECONDS,					// 11 +	VM_VAR_MINUTES,					// 12 +	VM_VAR_HOURS,					// 13 +	VM_VAR_DAYS,					// 14 +	VM_VAR_JOYSTICK_SENSITIVITY,	// 15 +	VM_VAR_EGO_VIEW_RESOURCE,		// 16 +	VM_VAR_AGI_ERROR_CODE,			// 17 +	VM_VAR_AGI_ERROR_INFO,			// 18 +	VM_VAR_KEY,						// 19 +	VM_VAR_COMPUTER,				// 20 +	VM_VAR_WINDOW_RESET,			// 21 +	VM_VAR_SOUNDGENERATOR,			// 22 +	VM_VAR_VOLUME,					// 23 +	VM_VAR_MAX_INPUT_CHARACTERS,	// 24 +	VM_VAR_SELECTED_INVENTORY_ITEM,	// 25 +	VM_VAR_MONITOR					// 26  };  /** @@ -335,33 +337,28 @@ enum AgiSoundType {   * AGI flags   */  enum { -	fEgoWater = 0,	// 0 -	fEgoInvisible, -	fEnteredCli, -	fEgoTouchedP2, -	fSaidAcceptedInput, -	fNewRoomExec,	// 5 -	fRestartGame, -	fScriptBlocked, -	fJoySensitivity, -	fSoundOn, -	fDebuggerOn,		// 10 -	fLogicZeroFirsttime, -	fRestoreJustRan, -	fStatusSelectsItems, -	fMenusWork, -	fOutputMode,		// 15 -	fAutoRestart -}; - -enum AgiSlowliness { -	kPauseRoom = 1500, -	kPausePicture = 500 -}; - -struct AgiController { +	VM_FLAG_EGO_WATER = 0,	// 0 +	VM_FLAG_EGO_INVISIBLE, +	VM_FLAG_ENTERED_CLI, +	VM_FLAG_EGO_TOUCHED_P2, +	VM_FLAG_SAID_ACCEPTED_INPUT, +	VM_FLAG_NEW_ROOM_EXEC,	// 5 +	VM_FLAG_RESTART_GAME, +	VM_FLAG_SCRIPT_BLOCKED, +	VM_FLAG_JOY_SENSITIVITY, +	VM_FLAG_SOUND_ON, +	VM_FLAG_DEBUGGER_ON,		// 10 +	VM_FLAG_LOGIC_ZERO_FIRST_TIME, +	VM_FLAG_RESTORE_JUST_RAN, +	VM_FLAG_STATUS_SELECTS_ITEMS, +	VM_FLAG_MENUS_WORK, +	VM_FLAG_OUTPUT_MODE,		// 15 +	VM_FLAG_AUTO_RESTART +}; + +struct AgiControllerKeyMapping {  	uint16 keycode; -	uint8 controller; +	byte   controllerSlot;  };  struct AgiObject { @@ -369,11 +366,6 @@ struct AgiObject {  	char *name;  }; -struct AgiWord { -	int id; -	char *word; -}; -  struct AgiDir {  	uint8 volume;  	uint32 offset; @@ -389,122 +381,9 @@ struct AgiDir {  };  struct AgiBlock { -	int active; -	int x1, y1; -	int x2, y2; -	uint8 *buffer;		// used for window background -}; - -/** AGI text color (Background and foreground color). */ -struct AgiTextColor { -	/** Creates an AGI text color. Uses black text on white background by default. */ -	AgiTextColor(int fgColor = 0x00, int bgColor = 0x0F) : fg(fgColor), bg(bgColor) {} - -	/** Get an AGI text color with swapped foreground and background color. */ -	AgiTextColor swap() const { return AgiTextColor(bg, fg); } - -	int fg; ///< Foreground color (Used for text). -	int bg; ///< Background color (Used for text's background). -}; - -/** - * AGI button style (Amiga or PC). - * - * Supports positive and negative button types (Used with Amiga-style only): - * Positive buttons do what the dialog was opened for. - * Negative buttons cancel what the dialog was opened for. - * Restart-dialog example: Restart-button is positive, Cancel-button negative. - * Paused-dialog example: Continue-button is positive. - */ -struct AgiButtonStyle { -// Public constants etc -public: -	static const int -		// Amiga colors (Indexes into the Amiga-ish palette) -		amigaBlack  = 0x00, ///< Accurate,                   is          #000000 (24-bit RGB) -		amigaWhite  = 0x0F, ///< Practically accurate,       is close to #FFFFFF (24-bit RGB) -		amigaGreen  = 0x02, ///< Quite accurate,             should be   #008A00 (24-bit RGB) -		amigaOrange = 0x0C, ///< Inaccurate, too much blue,  should be   #FF7500 (24-bit RGB) -		amigaPurple = 0x0D, ///< Inaccurate, too much green, should be   #FF00FF (24-bit RGB) -		amigaRed    = 0x04, ///< Quite accurate,             should be   #BD0000 (24-bit RGB) -		amigaCyan   = 0x0B, ///< Inaccurate, too much red,   should be   #00FFDE (24-bit RGB) -		// PC colors (Indexes into the EGA-palette) -		pcBlack     = 0x00, -		pcWhite     = 0x0F; - -// Public methods -public: -	/** -	 * Get the color of the button with the given state and type using current style. -	 * -	 * @param hasFocus True if button has focus, false otherwise. -	 * @param pressed True if button is being pressed, false otherwise. -	 * @param positive True if button is positive, false if button is negative. Only matters for Amiga-style buttons. -	 */ -	AgiTextColor getColor(bool hasFocus, bool pressed, bool positive = true) const; - -	/** -	 * Get the color of a button with the given base color and state ignoring current style. -	 * Swaps foreground and background color when the button has focus or is being pressed. -	 * -	 * @param hasFocus True if button has focus, false otherwise. -	 * @param pressed True if button is being pressed, false otherwise. -	 * @param baseFgColor Foreground color of the button when it has no focus and is not being pressed. -	 * @param baseBgColor Background color of the button when it has no focus and is not being pressed. -	 */ -	AgiTextColor getColor(bool hasFocus, bool pressed, int baseFgColor, int baseBgColor) const; - -	/** -	 * Get the color of a button with the given base color and state ignoring current style. -	 * Swaps foreground and background color when the button has focus or is being pressed. -	 * -	 * @param hasFocus True if button has focus, false otherwise. -	 * @param pressed True if button is being pressed, false otherwise. -	 * @param baseColor Color of the button when it has no focus and is not being pressed. -	 */ -	AgiTextColor getColor(bool hasFocus, bool pressed, const AgiTextColor &baseColor) const; - -	/** -	 * How many pixels to offset the shown text diagonally down and to the right. -	 * Currently only used for pressed PC-style buttons. -	 */ -	int getTextOffset(bool hasFocus, bool pressed) const; - -	/** -	 * Show border around the button? -	 * Currently border is only used for in focus or pressed Amiga-style buttons -	 * when in inauthentic Amiga-style mode. -	 */ -	bool getBorder(bool hasFocus, bool pressed) const; - -	/** -	 * Set Amiga-button style. -	 * -	 * @param amigaStyle Set Amiga-button style if true, otherwise set PC-button style. -	 * @param olderAgi If true then use older AGI style in Amiga-mode, otherwise use newer. -	 * @param authenticAmiga If true then don't use a border around buttons in Amiga-mode, otherwise use. -	 */ -	void setAmigaStyle(bool amigaStyle = true, bool olderAgi = false, bool authenticAmiga = false); - -	/** -	 * Set PC-button style. -	 * @param pcStyle Set PC-button style if true, otherwise set default Amiga-button style. -	 */ -	void setPcStyle(bool pcStyle = true); - -// Public constructors -public: -	/** -	 * Create a button style based on the given rendering mode. -	 * @param renderMode If Common::kRenderAmiga then creates default Amiga-button style, otherwise PC-style. -	 */ -	AgiButtonStyle(Common::RenderMode renderMode = Common::kRenderDefault); - -// Private member variables -private: -	bool _amigaStyle;     ///< Use Amiga-style buttons if true, otherwise use PC-style buttons. -	bool _olderAgi;       ///< Use older AGI style in Amiga-style mode. -	bool _authenticAmiga; ///< Don't use border around buttons in Amiga-style mode. +	bool active; +	int16 x1, y1; +	int16 x2, y2;  };  struct ScriptPos { @@ -512,18 +391,17 @@ struct ScriptPos {  	int curIP;  }; -enum { -	EGO_VIEW_TABLE	= 0, -	HORIZON			= 36, -	_WIDTH			= 160, -	_HEIGHT			= 168 +enum InputMode { +	INPUTMODE_NONE		= 0x04, +	INPUTMODE_NORMAL	= 0x01 // prompt active  }; -enum InputMode { -	INPUT_NORMAL	= 0x01, -	INPUT_GETSTRING	= 0x02, -	INPUT_MENU		= 0x03, -	INPUT_NONE		= 0x04 +enum CycleInnerLoopType { +	CYCLE_INNERLOOP_GETSTRING                    = 0, +	CYCLE_INNERLOOP_GETNUMBER                    = 1, +	CYCLE_INNERLOOP_INVENTORY                    = 2, +	CYCLE_INNERLOOP_MENU                         = 3, +	CYCLE_INNERLOOP_SYSTEMUI_SELECTSAVEDGAMESLOT = 4  };  enum State { @@ -532,12 +410,7 @@ enum State {  	STATE_RUNNING	= 0x02  }; -enum { -	SBUF16_OFFSET = 0, -	SBUF256_OFFSET = ((_WIDTH) * (_HEIGHT)), -	FROM_SBUF16_TO_SBUF256_OFFSET = ((SBUF256_OFFSET) - (SBUF16_OFFSET)), -	FROM_SBUF256_TO_SBUF16_OFFSET = ((SBUF16_OFFSET) - (SBUF256_OFFSET)) -}; +typedef Common::Array<int16> SavedGameSlotIdArray;  /**   * AGI game structure. @@ -563,94 +436,89 @@ struct AgiGame {  	uint8 vars[MAX_VARS];   /**< 256 variables */  	// internal variables -	int horizon;			/**< horizon y coordinate */ -	int lineStatus;		/**< line number to put status on */ -	int lineUserInput;	/**< line to put user input on */ -	int lineMinPrint;		/**< num lines to print on */ -	int cursorPos;			/**< column where the input cursor is */ -	byte inputBuffer[40]; /**< buffer for user input */ -	byte echoBuffer[40];	/**< buffer for echo.line */ +	int16 horizon;			/**< horizon y coordinate */ +  	int keypress; +	bool  cycleInnerLoopActive; +	int16 cycleInnerLoopType; +  	InputMode inputMode;			/**< keyboard input mode */ -	bool inputEnabled;		/**< keyboard input enabled */ + +	uint16 specialMenuTriggerKey;	/**< key to trigger menu for platforms except PC */ +  	int lognum;				/**< current logic number */  	Common::Array<ScriptPos> execStack;  	// internal flags  	int playerControl;		/**< player is in control */ -	int statusLine;		/**< status line on/off */  	int clockEnabled;		/**< clock is on/off */  	int exitAllLogics;	/**< break cycle after new.room */ -	int pictureShown;		/**< show.pic has been issued */ +	bool pictureShown;		/**< show.pic has been issued */  	int hasPrompt;			/**< input prompt has been printed */  #define ID_AGDS		0x00000001  #define ID_AMIGA	0x00000002  	int gameFlags;			/**< agi options flags */ -	uint8 priTable[_HEIGHT];/**< priority table */ -  	// windows  	uint32 msgBoxTicks;	/**< timed message box tick counter */  	AgiBlock block; -	AgiBlock window; -	int hasWindow;  	// graphics & text -	int gfxMode; -	char cursorChar; -	unsigned int colorFg; -	unsigned int colorBg; - -	uint8 *sbufOrig;		/**< Pointer to the 160x336 AGI screen buffer that contains vertically two 160x168 screens (16 color and 256 color). */ -	uint8 *sbuf16c;			/**< 160x168 16 color (+control line & priority information) AGI screen buffer. Points at sbufOrig + SBUF16_OFFSET. */ -	uint8 *sbuf256c;		/**< 160x168 256 color AGI screen buffer (For AGI256 and AGI256-2 support). Points at sbufOrig + SBUF256_OFFSET. */ -	uint8 *sbuf;			/**< Currently chosen AGI screen buffer (sbuf256c if AGI256 or AGI256-2 is used, otherwise sbuf16c). */ - -	// player command line -	AgiWord egoWords[MAX_WORDS]; -	int numEgoWords; +	bool gfxMode;  	unsigned int numObjects; -	bool controllerOccured[MAX_DIRS];  /**< keyboard keypress events */ -	AgiController controllers[MAX_CONTROLLERS]; +	bool controllerOccured[MAX_CONTROLLERS];  /**< keyboard keypress events */ +	AgiControllerKeyMapping controllerKeyMapping[MAX_CONTROLLER_KEYMAPPINGS];  	char strings[MAX_STRINGS + 1][MAX_STRINGLEN]; /**< strings */  	// directory entries for resources -	AgiDir dirLogic[MAX_DIRS]; -	AgiDir dirPic[MAX_DIRS]; -	AgiDir dirView[MAX_DIRS]; -	AgiDir dirSound[MAX_DIRS]; +	AgiDir dirLogic[MAX_DIRECTORY_ENTRIES]; +	AgiDir dirPic[MAX_DIRECTORY_ENTRIES]; +	AgiDir dirView[MAX_DIRECTORY_ENTRIES]; +	AgiDir dirSound[MAX_DIRECTORY_ENTRIES];  	// resources -	AgiPicture pictures[MAX_DIRS];	/**< AGI picture resources */ -	AgiLogic logics[MAX_DIRS];		/**< AGI logic resources */ -	AgiView views[MAX_DIRS];		/**< AGI view resources */ -	AgiSound *sounds[MAX_DIRS];		/**< Pointers to AGI sound resources */ +	AgiPicture pictures[MAX_DIRECTORY_ENTRIES];	/**< AGI picture resources */ +	AgiLogic logics[MAX_DIRECTORY_ENTRIES];		/**< AGI logic resources */ +	AgiView views[MAX_DIRECTORY_ENTRIES];		/**< AGI view resources */ +	AgiSound *sounds[MAX_DIRECTORY_ENTRIES];	/**< Pointers to AGI sound resources */  	AgiLogic *_curLogic; -	// words -	Common::Array<AgiWord *> words[26]; -  	// view table -	VtEntry viewTable[MAX_VIEWTABLE]; +	ScreenObjEntry screenObjTable[SCREENOBJECTS_MAX]; + +	ScreenObjEntry addToPicView;  	int32 ver;						/**< detected game version */ -	int simpleSave;					/**< select simple savegames */ +	bool automaticSave;             /**< set by CmdSetSimple() */ +	char automaticSaveDescription[SAVEDGAME_DESCRIPTION_LEN + 1];  	Common::Rect mouseFence;		/**< rectangle set by fence.mouse command */  	bool mouseEnabled;              /**< if mouse is supposed to be active */ +	bool mouseHidden;               /**< if mouse is currently hidden */  	// IF condition handling  	int testResult; -  	int max_logics;  	int logic_list[256]; + +	// used to detect situations, where the game shows some text and changes rooms right afterwards +	// for example Space Quest 2 intro right at the start +	// or Space Quest 2, when entering the vent also right at the start +	// The developers assumed that loading the new room would take a bit. +	// In ScummVM it's basically done in an instant, which means that +	// the text would only get shown for a split second. +	// We delay a bit as soon as such situations get detected. +	bool nonBlockingTextShown; +	int16 nonBlockingTextCyclesLeft; + +	bool automaticRestoreGame;  };  class AgiLoader { @@ -662,8 +530,8 @@ public:  	virtual int init() = 0;  	virtual int deinit() = 0;  	virtual int detectGame() = 0; -	virtual int loadResource(int, int) = 0; -	virtual int unloadResource(int, int) = 0; +	virtual int loadResource(int16 resourceType, int16 resourceNr) = 0; +	virtual int unloadResource(int16 resourceType, int16 resourceNr) = 0;  	virtual int loadObjects(const char *) = 0;  	virtual int loadWords(const char *) = 0;  }; @@ -684,8 +552,8 @@ public:  	virtual int init();  	virtual int deinit();  	virtual int detectGame(); -	virtual int loadResource(int, int); -	virtual int unloadResource(int, int); +	virtual int loadResource(int16 resourceType, int16 resourceNr); +	virtual int unloadResource(int16 resourceType, int16 resourceNr);  	virtual int loadObjects(const char *);  	virtual int loadWords(const char *);  }; @@ -706,8 +574,8 @@ public:  	virtual int init();  	virtual int deinit();  	virtual int detectGame(); -	virtual int loadResource(int, int); -	virtual int unloadResource(int, int); +	virtual int loadResource(int16 resourceType, int16 resourceNr); +	virtual int unloadResource(int16 resourceType, int16 resourceNr);  	virtual int loadObjects(const char *);  	virtual int loadWords(const char *);  }; @@ -728,8 +596,8 @@ public:  	virtual int init();  	virtual int deinit();  	virtual int detectGame(); -	virtual int loadResource(int, int); -	virtual int unloadResource(int, int); +	virtual int loadResource(int16 resourceType, int16 resourceNr); +	virtual int unloadResource(int16 resourceType, int16 resourceNr);  	virtual int loadObjects(const char *);  	virtual int loadWords(const char *);  }; @@ -737,7 +605,11 @@ public:  class GfxMgr;  class SpritesMgr; -class Menu; +class InventoryMgr; +class TextMgr; +class GfxMenu; +class SystemUI; +class Words;  // Image stack support  struct ImageStackElement { @@ -780,15 +652,21 @@ protected:  	virtual void initialize() = 0;  	void initRenderMode(); +	void initFont(); + +	void loadFontMickey(); +	void loadFontAmigaPseudoTopaz(); +	void loadFontAppleIIgs(); -	const uint8 *_fontData; +	const uint8 *_fontData; // pointer to the currently used font +	uint8 *_fontDataAllocated;  public: +	Words *_words; +  	GfxMgr *_gfx; -	AgiButtonStyle _defaultButtonStyle; -	AgiButtonStyle _buttonStyle; -	Common::RenderMode _renderMode; +	AgiRenderMode _renderMode;  	volatile uint32 _clockCount;  	AgiDebug _debug;  	AgiGame _game; @@ -800,6 +678,10 @@ public:  	bool _noSaveLoadAllowed; +	virtual bool promptIsEnabled() { +		return false; +	} +  	virtual void pollTimer() = 0;  	virtual int getKeypress() = 0;  	virtual bool isKeypress() = 0; @@ -844,6 +726,17 @@ public:  	bool canSaveGameStateCurrently();  	const uint8 *getFontData() { return _fontData; }; + +	void cycleInnerLoopActive(int16 loopType) { +		_game.cycleInnerLoopActive = true; +		_game.cycleInnerLoopType = loopType; +	}; +	void cycleInnerLoopInactive() { +		_game.cycleInnerLoopActive = false; +	}; +	bool cycleInnerLoopIsActive() { +		return _game.cycleInnerLoopActive; +	}  };  typedef void (*AgiCommand)(AgiGame *state, uint8 *p); @@ -861,8 +754,12 @@ public:  	AgiEngine(OSystem *syst, const AGIGameDescription *gameDesc);  	virtual ~AgiEngine(); +	bool promptIsEnabled(); +  	Common::Error loadGameState(int slot); -	Common::Error saveGameState(int slot, const Common::String &desc); +	Common::Error saveGameState(int slot, const Common::String &description); + +	void adjustPosToGameScreen(int16 &x, int16 &y);  private:  	uint32 _lastTick; @@ -873,11 +770,9 @@ private:  	bool _allowSynthetic; -	int checkPriority(VtEntry *v); -	int checkCollision(VtEntry *v); -	int checkPosition(VtEntry *v); - -	void parseFeatures(); +	bool checkPriority(ScreenObjEntry *v); +	bool checkCollision(ScreenObjEntry *v); +	bool checkPosition(ScreenObjEntry *v);  	int _firstSlot; @@ -886,15 +781,16 @@ public:  	StringData _stringdata; -	Common::String getSavegameFilename(int num) const; -	void getSavegameDescription(int num, char *buf, bool showEmpty = true); -	int selectSlot(); -	int saveGame(const Common::String &fileName, const Common::String &saveName); +	SavedGameSlotIdArray getSavegameSlotIds(); +	Common::String getSavegameFilename(int16 slotId) const; +	bool getSavegameInformation(int16 slotId, Common::String &saveDescription, uint32 &saveDate, uint16 &saveTime, bool &saveIsValid); + +	int saveGame(const Common::String &fileName, const Common::String &descriptionString);  	int loadGame(const Common::String &fileName, bool checkId = true); -	int saveGameDialog(); -	int saveGameSimple(); -	int loadGameDialog(); -	int loadGameSimple(); +	bool saveGameDialog(); +	bool saveGameAutomatic(); +	bool loadGameDialog(); +	bool loadGameAutomatic();  	int doSave(int slot, const Common::String &desc);  	int doLoad(int slot, bool showMessages);  	int scummVMSaveLoadDialog(bool isSave); @@ -903,14 +799,13 @@ public:  	InputMode _oldMode;  	bool _restartGame; -	Menu* _menu; -	bool _menuSelected; - -	char _lastSentence[40]; -  	SpritesMgr *_sprites; +	TextMgr *_text; +	InventoryMgr *_inventory;  	PictureMgr *_picture;  	AgiLoader *_loader;	// loader +	GfxMenu *_menu; +	SystemUI *_systemUI;  	Common::Stack<ImageStackElement> _imageStack; @@ -929,8 +824,8 @@ public:  	int agiInit();  	int agiDeinit();  	int agiDetectGame(); -	int agiLoadResource(int, int); -	int agiUnloadResource(int, int); +	int agiLoadResource(int16 resourceType, int16 resourceNr); +	int agiUnloadResource(int16 resourceType, int16 resourceNr);  	void agiUnloadResources();  	virtual void pollTimer(); @@ -938,35 +833,27 @@ public:  	virtual bool isKeypress();  	virtual void clearKeyQueue(); -	void initPriTable(); -  	void newInputMode(InputMode mode);  	void oldInputMode(); -	int getvar(int); -	void setvar(int, int); +	int getVar(int16 varNr); +	void setVar(int16 varNr, int);  	void decrypt(uint8 *mem, int len);  	void releaseSprites();  	int mainCycle(bool onlyCheckForEvents = false);  	int viewPictures();  	int runGame(); -	void inventory();  	void updateTimer();  	int getAppDir(char *appDir, unsigned int size);  	int setupV2Game(int ver);  	int setupV3Game(int ver); -	void newRoom(int n); +	void newRoom(int16 newRoomNr);  	void resetControllers();  	void interpretCycle();  	int playGame(); -	void printItem(int n, int fg, int bg); -	int findItem(); -	int showItems(); -	void selectItems(int n); -  	void allowSynthetic(bool);  	void processEvents();  	void checkQuickLoad(); @@ -977,9 +864,9 @@ public:  	int loadObjects(const char *fname);  	int loadObjects(Common::File &fp);  	void unloadObjects(); -	const char *objectName(unsigned int); -	int objectGetLocation(unsigned int); -	void objectSetLocation(unsigned int, int); +	const char *objectName(uint16 objectNr); +	int objectGetLocation(uint16 objectNr); +	void objectSetLocation(uint16 objectNr, int);  private:  	int decodeObjects(uint8 *mem, uint32 flen);  	int readObjects(Common::File &fp, int flen); @@ -987,8 +874,8 @@ private:  	// Logic  public: -	int decodeLogic(int); -	void unloadLogic(int); +	int decodeLogic(int16 logicNr); +	void unloadLogic(int16 logicNr);  	int runLogic(int);  	void debugConsole(int, int, const char *);  	int testIfCode(int); @@ -1010,93 +897,72 @@ public:  	// View  private: -	void lSetCel(VtEntry *v, int n); -	void lSetLoop(VtEntry *v, int n); -	void updateView(VtEntry *v); +	void lSetLoop(ScreenObjEntry *screenObj, int16 loopNr); +	void updateView(ScreenObjEntry *screenObj);  public: +	void setView(ScreenObjEntry *screenObj, int16 viewNr); +	void setLoop(ScreenObjEntry *screenObj, int16 loopNr); +	void setCel(ScreenObjEntry *screenObj, int16 celNr); -	void setCel(VtEntry *, int); -	void clipViewCoordinates(VtEntry *v); -	void setLoop(VtEntry *, int); -	void setView(VtEntry *, int); -	void startUpdate(VtEntry *); -	void stopUpdate(VtEntry *); -	void updateViewtable(); -	void unloadView(int); -	int decodeView(int); -	void addToPic(int, int, int, int, int, int, int); -	void drawObj(int); -	bool isEgoView(const VtEntry *v); +	void clipViewCoordinates(ScreenObjEntry *screenObj); + +	void startUpdate(ScreenObjEntry *); +	void stopUpdate(ScreenObjEntry *); +	void updateScreenObjTable(); +	void unloadView(int16 viewNr); +	int decodeView(byte *resourceData, uint16 resourceSize, int16 viewNr); + +private: +	void unpackViewCelData(AgiViewCel *celData, byte *compressedData, uint16 compressedSize); +	void unpackViewCelDataAGI256(AgiViewCel *celData, byte *compressedData, uint16 compressedSize); -	// Words  public: -	int showWords(); -	int loadWords(const char *); -	int loadWords_v1(Common::File &f); -	void unloadWords(); -	int findWord(const char *word, int *flen); -	void dictionaryWords(char *); +	void addToPic(int, int, int, int, int, int, int); +	void drawObj(int); +	bool isEgoView(const ScreenObjEntry *screenObj);  	// Motion  private:  	int checkStep(int delta, int step); -	int checkBlock(int x, int y); -	void changePos(VtEntry *v); -	void motionWander(VtEntry *v); -	void motionFollowEgo(VtEntry *v); -	void motionMoveObj(VtEntry *v); -	void checkMotion(VtEntry *v); +	bool checkBlock(int16 x, int16 y); +	void changePos(ScreenObjEntry *screenObj); +	void motionWander(ScreenObjEntry *screenObj); +	void motionFollowEgo(ScreenObjEntry *screenObj); +	void motionMoveObj(ScreenObjEntry *screenObj); +	void motionMoveObjStop(ScreenObjEntry *screenObj); +	void checkMotion(ScreenObjEntry *screenObj);  public:  	void checkAllMotions(); -	void moveObj(VtEntry *); -	void inDestination(VtEntry *); -	void fixPosition(int); +	void moveObj(ScreenObjEntry *screenObj); +	void inDestination(ScreenObjEntry *screenObj); +	void fixPosition(int16 screenObjNr); +	void fixPosition(ScreenObjEntry *screenObj);  	void updatePosition(); -	int getDirection(int x0, int y0, int x, int y, int s); +	int getDirection(int16 objX, int16 objY, int16 destX, int16 destY, int16 stepSize);  	bool _egoHoldKey;  	// Keyboard -	void initWords(); -	void cleanInput();  	int doPollKeyboard();  	void cleanKeyboard(); -	void handleKeys(int); -	void handleGetstring(int); -	int handleController(int); -	void getString(int, int, int, int); + +	int16 getSpecialMenuControllerSlot(); +	bool handleController(uint16 key);  	uint16 agiGetKeypress();  	int waitKey();  	int waitAnyKey(); -	// Text -public: -	int messageBox(const char *); -	int selectionBox(const char *, const char **); -	void closeWindow(); -	void drawWindow(int, int, int, int); -	void printText(const char *, int, int, int, int, int, int, bool checkerboard = false); -	void printTextConsole(const char *, int, int, int, int, int); -	int print(const char *, int, int, int); -	char *wordWrapString(const char *, int *); -	char *agiSprintf(const char *); -	void writeStatus(); -	void writePrompt(); -	void clearPrompt(bool useBlackBg = false); -	void clearLines(int, int, int); -	void flushLines(int, int); +	void nonBlockingText_IsShown(); +	void nonBlockingText_Forget(); +	void nonBlockingText_CycleDone(); -private: -	void printStatus(const char *message, ...) GCC_PRINTF(2, 3); -	void printText2(int l, const char *msg, int foff, int xoff, int yoff, int len, int fg, int bg, bool checkerboard = false); -	void blitTextbox(const char *p, int y, int x, int len); -	void eraseTextbox(); -	bool matchWord(); +	void loadingTrigger_NewRoom(int16 newRoomNr); +	void loadingTrigger_DrawPicture();  public: -	char _predictiveResult[40]; +	void redrawScreen();  private:  	AgiCommand _agiCommands[183]; diff --git a/engines/agi/checks.cpp b/engines/agi/checks.cpp index e61146e901..8399c9834a 100644 --- a/engines/agi/checks.cpp +++ b/engines/agi/checks.cpp @@ -21,159 +21,156 @@   */  #include "agi/agi.h" +#include "agi/graphics.h"  namespace Agi { -int AgiEngine::checkPosition(VtEntry *v) { -	debugC(4, kDebugLevelSprites, "check position @ %d, %d", v->xPos, v->yPos); +bool AgiEngine::checkPosition(ScreenObjEntry *screenObj) { +	bool result = true; // position is fine +	debugC(4, kDebugLevelSprites, "check position @ %d, %d", screenObj->xPos, screenObj->yPos); -	if (v->xPos < 0 || -			v->xPos + v->xSize > _WIDTH || -			v->yPos - v->ySize + 1 < 0 || -			v->yPos >= _HEIGHT || -			((~v->flags & fIgnoreHorizon) && v->yPos <= _game.horizon)) { -		debugC(4, kDebugLevelSprites, "check position failed: x=%d, y=%d, h=%d, w=%d", -				v->xPos, v->yPos, v->xSize, v->ySize); -		return 0; -	} +	do { +		if (screenObj->xPos < 0) { +			result = false; +			break; +		} +		if (screenObj->xPos + screenObj->xSize > SCRIPT_WIDTH) { +			result = false; +			break; +		} +		if (screenObj->yPos - screenObj->ySize < -1) { +			result = false; +			break; +		} +		if (screenObj->yPos >= SCRIPT_HEIGHT) { +			result = false; +			break; +		} +		if (((!(screenObj->flags & fIgnoreHorizon)) && screenObj->yPos <= _game.horizon)) { +			result = false; +			break; +		} +	} while (0);  	// MH1 needs this, but it breaks LSL1 -	if (getVersion() >= 0x3000) { -		if (v->yPos < v->ySize) -			return 0; -	} +// TODO: *NOT* in disassembly of AGI3 .149, why was this needed? +//	if (getVersion() >= 0x3000) { +//		if (screenObj->yPos < screenObj->ySize) +//			result = false; +//	} -	return 1; +	if (!result) { +		debugC(4, kDebugLevelSprites, "check position failed: x=%d, y=%d, h=%d, w=%d", +				screenObj->xPos, screenObj->yPos, screenObj->xSize, screenObj->ySize); +	} +	return result;  }  /**   * Check if there's another object on the way   */ -int AgiEngine::checkCollision(VtEntry *v) { -	VtEntry *u; +bool AgiEngine::checkCollision(ScreenObjEntry *screenObj) { +	ScreenObjEntry *checkObj; -	if (v->flags & fIgnoreObjects) -		return 0; +	if (screenObj->flags & fIgnoreObjects) +		return false; -	for (u = _game.viewTable; u < &_game.viewTable[MAX_VIEWTABLE]; u++) { -		if ((u->flags & (fAnimated | fDrawn)) != (fAnimated | fDrawn)) +	for (checkObj = _game.screenObjTable; checkObj < &_game.screenObjTable[SCREENOBJECTS_MAX]; checkObj++) { +		if ((checkObj->flags & (fAnimated | fDrawn)) != (fAnimated | fDrawn))  			continue; -		if (u->flags & fIgnoreObjects) +		if (checkObj->flags & fIgnoreObjects)  			continue;  		// Same object, check next -		if (v->entry == u->entry) +		if (screenObj->objectNr == checkObj->objectNr)  			continue;  		// No horizontal overlap, check next -		if (v->xPos + v->xSize < u->xPos || v->xPos > u->xPos + u->xSize) +		if (screenObj->xPos + screenObj->xSize < checkObj->xPos || screenObj->xPos > checkObj->xPos + checkObj->xSize)  			continue;  		// Same y, return error! -		if (v->yPos == u->yPos) { -			debugC(4, kDebugLevelSprites, "check returns 1 (object %d)", v->entry); -			return 1; +		if (screenObj->yPos == checkObj->yPos) { +			debugC(4, kDebugLevelSprites, "check returns 1 (object %d)", screenObj->objectNr); +			return true;  		}  		// Crossed the baseline, return error! -		if ((v->yPos > u->yPos && v->yPos2 < u->yPos2) || -				(v->yPos < u->yPos && v->yPos2 > u->yPos2)) { -			debugC(4, kDebugLevelSprites, "check returns 1 (object %d)", v->entry); -			return 1; +		if ((screenObj->yPos > checkObj->yPos && screenObj->yPos_prev < checkObj->yPos_prev) || +				(screenObj->yPos < checkObj->yPos && screenObj->yPos_prev > checkObj->yPos_prev)) { +			debugC(4, kDebugLevelSprites, "check returns 1 (object %d)", screenObj->objectNr); +			return true;  		}  	} - -	return 0; - +	 +	return false;  } -int AgiEngine::checkPriority(VtEntry *v) { -	int i, trigger, water, pass, pri; -	uint8 *p0; +bool AgiEngine::checkPriority(ScreenObjEntry *screenObj) { +	bool touchedWater = false; +	bool touchedTrigger = false; +	bool touchedControl = true; +	int16 curX; +	int16 curY; +	int16 celX; +	byte screenPriority = 0; -	if (~v->flags & fFixedPriority) { +	if (!(screenObj->flags & fFixedPriority)) {  		// Priority bands -		v->priority = _game.priTable[v->yPos]; -	} - -	trigger = 0; -	water = 0; -	pass = 1; - -	if (v->priority == 0x0f) { -		// Check ego -		if (v->entry == 0) { -			setflag(fEgoTouchedP2, trigger ? true : false); -			setflag(fEgoWater, water ? true : false); -		} - -		return pass; -	} - -	water = 1; - -	// Check if any picture is loaded before checking for priority below. -	// If no picture has been loaded, the priority buffer won't be initialized, -	// thus the check below will always fail. This case causes an infinite loop -	// in the fanmade game Nick's Quest (bug #3451122), as the game attempts to -	// draw a sprite (view 4, floating Nick) before it loads any picture. This -	// causes the checks below to always fail, and the engine keeps readjusting -	// the sprite's position in fixPosition() forever, as there is no valid -	// position to place it (the default visual and priority screen is set to -	// zero, i.e. unconditional black). To remedy this situation, we always -	// return true here if no picture has been loaded and no priority screen -	// has been set up. -	if (!_game._vm->_picture->isPictureLoaded()) { -		warning("checkPriority: no picture loaded"); -		return pass; +		screenObj->priority = _gfx->priorityFromY(screenObj->yPos);  	} -	p0 = &_game.sbuf16c[v->xPos + v->yPos * _WIDTH]; - -	for (i = 0; i < v->xSize; i++, p0++) { -		pri = *p0 >> 4; +	if (screenObj->priority != 0x0f) { -		if (pri == 0) {	// unconditional black. no go at all! -			pass = 0; -			break; -		} +		touchedWater = true; -		if (pri == 3)	// water surface -			continue; +		curX = screenObj->xPos; +		curY = screenObj->yPos; -		water = 0; +		for (celX = 0; celX < screenObj->xSize; celX++, curX++) { +			screenPriority = _gfx->getPriority(curX, curY); -		if (pri == 1) {	// conditional blue -			if (v->flags & fIgnoreBlocks) -				continue; +			if (screenPriority == 0) {	// unconditional black. no go at all! +				touchedControl = 0; +				break; +			} -			debugC(4, kDebugLevelSprites, "Blocks observed!"); -			pass = 0; -			break; +			if (screenPriority != 3) {	// not water surface +				touchedWater = false; + +				if (screenPriority == 1) {	// conditional blue +					if (!(screenObj->flags & fIgnoreBlocks)) { +						debugC(4, kDebugLevelSprites, "Blocks observed!"); +						touchedControl = false; +						break; +					} +				} else if (screenPriority == 2) { +					debugC(4, kDebugLevelSprites, "stepped on trigger"); +					if (!_debug.ignoretriggers) +						touchedTrigger = true; +				} +			}  		} -		if (pri == 2) {	// trigger -			debugC(4, kDebugLevelSprites, "stepped on trigger"); -			if (!_debug.ignoretriggers) -				trigger = 1; +		if (touchedControl) { +			if (!touchedWater) { +				if (screenObj->flags & fOnWater) +					touchedControl = false; +			} else { +				if (screenObj->flags & fOnLand) +					touchedControl = false; +			}  		}  	} -	if (pass) { -		if (!water && v->flags & fOnWater) -			pass = 0; -		if (water && v->flags & fOnLand) -			pass = 0; -	} -  	// Check ego -	if (v->entry == 0) { -		setflag(fEgoTouchedP2, trigger ? true : false); -		setflag(fEgoWater, water ? true : false); +	if (screenObj->objectNr == 0) { +		setflag(VM_FLAG_EGO_TOUCHED_P2, touchedTrigger ? true : false); +		setflag(VM_FLAG_EGO_WATER, touchedWater ? true : false);  	} -	return pass; +	return touchedControl;  }  /* @@ -188,91 +185,106 @@ int AgiEngine::checkPriority(VtEntry *v) {   * rules, otherwise the previous position will be kept.   */  void AgiEngine::updatePosition() { -	VtEntry *v; +	ScreenObjEntry *screenObj;  	int x, y, oldX, oldY, border; -	_game.vars[vBorderCode] = 0; -	_game.vars[vBorderTouchEgo] = 0; -	_game.vars[vBorderTouchObj] = 0; +	_game.vars[VM_VAR_BORDER_CODE] = 0; +	_game.vars[VM_VAR_BORDER_TOUCH_EGO] = 0; +	_game.vars[VM_VAR_BORDER_TOUCH_OBJECT] = 0; -	for (v = _game.viewTable; v < &_game.viewTable[MAX_VIEWTABLE]; v++) { -		if ((v->flags & (fAnimated | fUpdate | fDrawn)) != (fAnimated | fUpdate | fDrawn)) { +	for (screenObj = _game.screenObjTable; screenObj < &_game.screenObjTable[SCREENOBJECTS_MAX]; screenObj++) { +		if ((screenObj->flags & (fAnimated | fUpdate | fDrawn)) != (fAnimated | fUpdate | fDrawn)) {  			continue;  		} -		if (v->stepTimeCount != 0) { -			if (--v->stepTimeCount != 0) -				continue; +		if (screenObj->stepTimeCount > 1) { +			screenObj->stepTimeCount--; +			continue;  		} -		v->stepTimeCount = v->stepTime; +		screenObj->stepTimeCount = screenObj->stepTime; -		x = oldX = v->xPos; -		y = oldY = v->yPos; +		x = oldX = screenObj->xPos; +		y = oldY = screenObj->yPos;  		// If object has moved, update its position -		if (~v->flags & fUpdatePos) { +		if (!(screenObj->flags & fUpdatePos)) {  			int dx[9] = { 0, 0, 1, 1, 1, 0, -1, -1, -1 };  			int dy[9] = { 0, -1, -1, 0, 1, 1, 1, 0, -1 }; -			x += v->stepSize * dx[v->direction]; -			y += v->stepSize * dy[v->direction]; +			x += screenObj->stepSize * dx[screenObj->direction]; +			y += screenObj->stepSize * dy[screenObj->direction];  		}  		// Now check if it touched the borders  		border = 0;  		// Check left/right borders -		if (x < 0) { -			x = 0; -			border = 4; -		} else if (x <= 0 && getVersion() == 0x3086) {	// KQ4 -			x = 0;	// See Sarien bug #590462 -			border = 4; -		} else if (v->entry == 0 && x == 0 && v->flags & fAdjEgoXY) { -			// Extra test to walk west clicking the mouse -			x = 0; -			border = 4; -		} else if (x + v->xSize > _WIDTH) { -			x = _WIDTH - v->xSize; -			border = 2; +		if (getVersion() == 0x3086) { +			// KQ4 interpreter does a different comparison on x +			// The interpreter before (2.917) and after that (3.098) don't do them that way +			// This difference is required for at least Sarien bug #192 +			// KQ4: room 135, hen moves from the center of the screen to the left border, +			// but it doesn't disappear. +			if (x <= 0) { +				x = 0; +				border = 4; +			} +		} else { +			// regular comparison +			if (x < 0) { +				x = 0; +				border = 4; +			} +		} + +//		} else if (v->entry == 0 && x == 0 && v->flags & fAdjEgoXY) { // should not be required +//			// Extra test to walk west clicking the mouse +//			x = 0; +//			border = 4; + +		if (!border) { +			if (x + screenObj->xSize > SCRIPT_WIDTH) { +				x = SCRIPT_WIDTH - screenObj->xSize; +				border = 2; +			}  		}  		// Check top/bottom borders. -		if (y - v->ySize + 1 < 0) { -			y = v->ySize - 1; +		if (y - screenObj->ySize < -1) { +			y = screenObj->ySize - 1;  			border = 1; -		} else if (y > _HEIGHT - 1) { -			y = _HEIGHT - 1; +		} else if (y > SCRIPT_HEIGHT - 1) { +			y = SCRIPT_HEIGHT - 1;  			border = 3; -		} else if ((~v->flags & fIgnoreHorizon) && y <= _game.horizon) { +		} else if ((!(screenObj->flags & fIgnoreHorizon)) && y <= _game.horizon) {  			debugC(4, kDebugLevelSprites, "y = %d, horizon = %d", y, _game.horizon);  			y = _game.horizon + 1;  			border = 1;  		}  		// Test new position. rollback if test fails -		v->xPos = x; -		v->yPos = y; -		if (checkCollision(v) || !checkPriority(v)) { -			v->xPos = oldX; -			v->yPos = oldY; +		screenObj->xPos = x; +		screenObj->yPos = y; +		if (checkCollision(screenObj) || !checkPriority(screenObj)) { +			screenObj->xPos = oldX; +			screenObj->yPos = oldY;  			border = 0; -			fixPosition(v->entry); +			fixPosition(screenObj->objectNr);  		} -		if (border != 0) { -			if (isEgoView(v)) { -				_game.vars[vBorderTouchEgo] = border; +		if (border) { +			if (isEgoView(screenObj)) { +				_game.vars[VM_VAR_BORDER_TOUCH_EGO] = border;  			} else { -				_game.vars[vBorderCode] = v->entry; -				_game.vars[vBorderTouchObj] = border; +				_game.vars[VM_VAR_BORDER_CODE] = screenObj->objectNr; +				_game.vars[VM_VAR_BORDER_TOUCH_OBJECT] = border;  			} -			if (v->motion == kMotionMoveObj) { -				inDestination(v); +			if (screenObj->motionType == kMotionMoveObj) { // ANGEPASST +				motionMoveObjStop(screenObj);  			}  		} -		v->flags &= ~fUpdatePos; +		screenObj->flags &= ~fUpdatePos;  	}  } @@ -285,42 +297,46 @@ void AgiEngine::updatePosition() {   *   * @param n view table entry number   */ -void AgiEngine::fixPosition(int n) { -	VtEntry *v = &_game.viewTable[n]; +void AgiEngine::fixPosition(int16 screenObjNr) { +	ScreenObjEntry *screenObj = &_game.screenObjTable[screenObjNr]; +	fixPosition(screenObj); +} + +void AgiEngine::fixPosition(ScreenObjEntry *screenObj) {  	int count, dir, size; -	debugC(4, kDebugLevelSprites, "adjusting view table entry #%d (%d,%d)", n, v->xPos, v->yPos); +	debugC(4, kDebugLevelSprites, "adjusting view table entry #%d (%d,%d)", screenObj->objectNr, screenObj->xPos, screenObj->yPos);  	// test horizon -	if ((~v->flags & fIgnoreHorizon) && v->yPos <= _game.horizon) -		v->yPos = _game.horizon + 1; +	if ((!(screenObj->flags & fIgnoreHorizon)) && screenObj->yPos <= _game.horizon) +		screenObj->yPos = _game.horizon + 1;  	dir = 0;  	count = size = 1; -	while (!checkPosition(v) || checkCollision(v) || !checkPriority(v)) { +	while (!checkPosition(screenObj) || checkCollision(screenObj) || !checkPriority(screenObj)) {  		switch (dir) {  		case 0:	// west -			v->xPos--; +			screenObj->xPos--;  			if (--count)  				continue;  			dir = 1;  			break;  		case 1:	// south -			v->yPos++; +			screenObj->yPos++;  			if (--count)  				continue;  			dir = 2;  			size++;  			break;  		case 2:	// east -			v->xPos++; +			screenObj->xPos++;  			if (--count)  				continue;  			dir = 3;  			break;  		case 3:	// north -			v->yPos--; +			screenObj->yPos--;  			if (--count)  				continue;  			dir = 0; @@ -331,7 +347,7 @@ void AgiEngine::fixPosition(int n) {  		count = size;  	} -	debugC(4, kDebugLevelSprites, "view table entry #%d position adjusted to (%d,%d)", n, v->xPos, v->yPos); +	debugC(4, kDebugLevelSprites, "view table entry #%d position adjusted to (%d,%d)", screenObj->objectNr, screenObj->xPos, screenObj->yPos);  }  } // End of namespace Agi diff --git a/engines/agi/console.cpp b/engines/agi/console.cpp index 89838b3be9..d8ecb2a0d6 100644 --- a/engines/agi/console.cpp +++ b/engines/agi/console.cpp @@ -22,6 +22,7 @@  #include "agi/agi.h"  #include "agi/opcodes.h" +#include "agi/graphics.h"  #include "agi/preagi.h"  #include "agi/preagi_mickey.h" @@ -49,6 +50,10 @@ Console::Console(AgiEngine *vm) : GUI::Debugger() {  	registerCmd("setobj",     WRAP_METHOD(Console, Cmd_SetObj));  	registerCmd("room",       WRAP_METHOD(Console, Cmd_Room));  	registerCmd("bt",         WRAP_METHOD(Console, Cmd_BT)); +	registerCmd("show_map",   WRAP_METHOD(Console, Cmd_ShowMap)); +	registerCmd("screenobj",  WRAP_METHOD(Console, Cmd_ScreenObj)); +	registerCmd("vmvars",     WRAP_METHOD(Console, Cmd_VmVars)); +	registerCmd("vmflags",    WRAP_METHOD(Console, Cmd_VmFlags));  }  bool Console::Cmd_SetVar(int argc, const char **argv) { @@ -58,7 +63,7 @@ bool Console::Cmd_SetVar(int argc, const char **argv) {  	}  	int p1 = (int)atoi(argv[1]);  	int p2 = (int)atoi(argv[2]); -	_vm->setvar(p1, p2); +	_vm->setVar(p1, p2);  	return true;  } @@ -158,13 +163,13 @@ bool Console::Cmd_Version(int argc, const char **argv) {  	// We do this by scanning through all script texts  	// This is the best we can do about it. There is no special location for the game version number.  	// There are multiple variations, like "ver. X.XX", "ver X.XX" and even "verion X.XX". -	for (scriptNr = 0; scriptNr < MAX_DIRS; scriptNr++) { +	for (scriptNr = 0; scriptNr < MAX_DIRECTORY_ENTRIES; scriptNr++) {  		if (game->dirLogic[scriptNr].offset != _EMPTY) {  			// Script is supposed to exist?  			scriptLoadedByUs = false;  			if (!(game->dirLogic[scriptNr].flags & RES_LOADED)) {  				// But not currently loaded? -> load it now -				if (_vm->agiLoadResource(rLOGIC, scriptNr) != errOK) { +				if (_vm->agiLoadResource(RESOURCETYPE_LOGIC, scriptNr) != errOK) {  					// In case we can't load the source, skip it  					continue;  				} @@ -263,7 +268,7 @@ bool Console::Cmd_Version(int argc, const char **argv) {  			}  			if (scriptLoadedByUs) { -				_vm->agiUnloadResource(rLOGIC, scriptNr); +				_vm->agiUnloadResource(RESOURCETYPE_LOGIC, scriptNr);  			}  		}  	} @@ -298,7 +303,7 @@ bool Console::Cmd_Vars(int argc, const char **argv) {  	for (i = 0; i < 255;) {  		for (j = 0; j < 5; j++, i++) { -			debugPrintf("%03d:%3d ", i, _vm->getvar(i)); +			debugPrintf("%03d:%3d ", i, _vm->getVar(i));  		}  		debugPrintf("\n");  	} @@ -380,7 +385,7 @@ bool Console::Cmd_Room(int argc, const char **argv) {  		_vm->newRoom(strtoul(argv[1], NULL, 0));  	} -	debugPrintf("Current room: %d\n", _vm->getvar(0)); +	debugPrintf("Current room: %d\n", _vm->getVar(0));  	return true;  } @@ -412,6 +417,182 @@ bool Console::Cmd_BT(int argc, const char **argv) {  	return true;  } +bool Console::Cmd_ShowMap(int argc, const char **argv) { +	if (argc != 2) { +		debugPrintf("Switches to one of the following screen maps\n"); +		debugPrintf("Usage: %s <screen map>\n", argv[0]); +		debugPrintf("Screen maps:\n"); +		debugPrintf("- 0: visual map\n"); +		debugPrintf("- 1: priority map\n"); +		return true; +	} + +	int map = atoi(argv[1]); + +	switch (map) { +	case 0: +	case 1: +		_vm->_gfx->debugShowMap(map); +		break; + +	default: +		debugPrintf("Map %d is not available.\n", map); +		return true; +	} +	return cmdExit(0, 0); +} + +bool Console::Cmd_ScreenObj(int argc, const char **argv) { +	if (argc != 2) { +		debugPrintf("Shows information about a specific screen object\n"); +		debugPrintf("Usage: %s <screenobj number>\n", argv[0]); +		return true; +	} + +	int16 screenObjNr = atoi(argv[1]); + +	if ((screenObjNr >= 0) && (screenObjNr < SCREENOBJECTS_MAX)) { +		ScreenObjEntry *screenObj = &_vm->_game.screenObjTable[screenObjNr]; + +		debugPrintf("Screen Object ID %d\n", screenObj->objectNr); +		debugPrintf("current view: %d, loop: %d, cel: %d\n", screenObj->currentViewNr, screenObj->currentLoopNr, screenObj->currentCelNr); +		debugPrintf("flags: %x\n", screenObj->flags); +		debugPrintf("\n"); +		debugPrintf("xPos: %d, yPos: %d, xSize: %d, ySize: %d\n", screenObj->xPos, screenObj->yPos, screenObj->xSize, screenObj->ySize); +		debugPrintf("previous: xPos: %d, yPos: %d, xSize: %d, ySize: %d\n", screenObj->xPos_prev, screenObj->yPos_prev, screenObj->xSize_prev, screenObj->ySize_prev); +		debugPrintf("direction: %d, priority: %d\n", screenObj->direction, screenObj->priority); +		debugPrintf("stepTime: %d, timeCount: %d, size: %d\n", screenObj->stepTime, screenObj->stepTimeCount, screenObj->stepSize); +		debugPrintf("cycleTime: %d, timeCount: %d\n", screenObj->cycleTime, screenObj->cycleTimeCount); + +		switch(screenObj->motionType) { +		case kMotionNormal: +			debugPrintf("motion: normal\n"); +			break; +		case kMotionWander: +			debugPrintf("motion: wander\n"); +			debugPrintf("wanderCount: %d\n", screenObj->wander_count); +			break; +		case kMotionFollowEgo: +			debugPrintf("motion: follow ego\n"); +			debugPrintf("stepSize: %d, flag: %x, count: %d", screenObj->follow_stepSize, screenObj->follow_flag, screenObj->follow_count); +			break; +		case kMotionMoveObj: +		case kMotionEgo: +			if (screenObj->motionType == kMotionMoveObj) { +				debugPrintf("motion: move obj\n"); +			} else { +				debugPrintf("motion: ego\n"); +			} +			debugPrintf("x: %d, y: %d, stepSize: %d, flag: %x\n", screenObj->move_x, screenObj->move_y, screenObj->move_stepSize, screenObj->move_flag); +			break; +		} +	} +#if 0 +	CycleType cycle; +#endif  +	return true; +} + +bool Console::Cmd_VmVars(int argc, const char **argv) { +	if (argc < 2) { +		debugPrintf("Shows the content of a VM variable / sets it\n"); +		debugPrintf("Usage: %s <variable number> [<value>]\n", argv[0]); +		return true; +	} + +	int varNr = 0; +	int newValue = 0; + +	if (!parseInteger(argv[1], varNr)) +		return true; + +	if ((varNr < 0) || (varNr > 255)) { +		debugPrintf("invalid variable number\n"); +		return true; +	} + +	if (argc < 3) { +		// show contents +		debugPrintf("variable %d == %d\n", varNr, _vm->getVar(varNr)); +	} else { +		if (!parseInteger(argv[2], newValue)) +			return true; + +		_vm->setVar(varNr, newValue); + +		debugPrintf("value set.\n"); +	} +	return true; +} + +bool Console::Cmd_VmFlags(int argc, const char **argv) { +	if (argc < 2) { +		debugPrintf("Shows the content of a VM flag / sets it\n"); +		debugPrintf("Usage: %s <flag number> [<value>]\n", argv[0]); +		return true; +	} + +	int flagNr = 0; +	int newFlagState = 0; + +	if (!parseInteger(argv[1], flagNr)) +		return true; + +	if ((flagNr < 0) || (flagNr > 255)) { +		debugPrintf("invalid flag number\n"); +		return true; +	} + +	if (argc < 3) { +		// show contents +		if (_vm->getflag(flagNr)) { +			debugPrintf("flag %d == set\n", flagNr); +		} else { +			debugPrintf("flag %d == not set\n", flagNr); +		} +	} else { +		if (!parseInteger(argv[2], newFlagState)) +			return true; + +		if ((newFlagState != 0) && (newFlagState != 1)) { +			debugPrintf("new state must bei either 0 or 1\n"); +			return true; +		} + +		if (!newFlagState) { +			_vm->setflag(flagNr, 0); +			debugPrintf("flag %d reset.\n", flagNr); +		} else { +			_vm->setflag(flagNr, 1); +			debugPrintf("flag %d set.\n", flagNr); +		} +	} +	return true; +} + +bool Console::parseInteger(const char *argument, int &result) { +	char *endPtr = 0; +	int idxLen = strlen(argument); +	const char *lastChar = argument + idxLen - (idxLen == 0 ? 0 : 1); + +	if ((strncmp(argument, "0x", 2) == 0) || (*lastChar == 'h')) { +		// hexadecimal number +		result = strtol(argument, &endPtr, 16); +		if ((*endPtr != 0) && (*endPtr != 'h')) { +			debugPrintf("Invalid hexadecimal number '%s'\n", argument); +			return false; +		} +	} else { +		// decimal number +		result = strtol(argument, &endPtr, 10); +		if (*endPtr != 0) { +			debugPrintf("Invalid decimal number '%s'\n", argument); +			return false; +		} +	} +	return true; +} +  MickeyConsole::MickeyConsole(MickeyEngine *mickey) : GUI::Debugger() {  	_mickey = mickey; diff --git a/engines/agi/console.h b/engines/agi/console.h index c650e143a0..41dc9ddabc 100644 --- a/engines/agi/console.h +++ b/engines/agi/console.h @@ -62,6 +62,12 @@ private:  	bool Cmd_Cont(int argc, const char **argv);  	bool Cmd_Room(int argc, const char **argv);  	bool Cmd_BT(int argc, const char **argv); +	bool Cmd_ShowMap(int argc, const char **argv); +	bool Cmd_ScreenObj(int argc, const char **argv); +	bool Cmd_VmVars(int argc, const char **argv); +	bool Cmd_VmFlags(int argc, const char **argv); + +	bool parseInteger(const char *argument, int &result);  private:  	AgiEngine *_vm; diff --git a/engines/agi/cycle.cpp b/engines/agi/cycle.cpp index 145b827160..98f29ec0e1 100644 --- a/engines/agi/cycle.cpp +++ b/engines/agi/cycle.cpp @@ -20,11 +20,16 @@   *   */ +#include "common/config-manager.h" +  #include "agi/agi.h"  #include "agi/sprite.h"  #include "agi/graphics.h" +#include "agi/inv.h" +#include "agi/text.h"  #include "agi/keyboard.h"  #include "agi/menu.h" +#include "agi/systemui.h"  namespace Agi { @@ -33,124 +38,133 @@ namespace Agi {   * This function is called when ego enters a new room.   * @param n room number   */ -void AgiEngine::newRoom(int n) { -	VtEntry *v; +void AgiEngine::newRoom(int16 newRoomNr) { +	ScreenObjEntry *screenObj; +	ScreenObjEntry *screenObjEgo = &_game.screenObjTable[SCREENOBJECTS_EGO_ENTRY];  	int i; -	// Simulate slowww computer. -	// Many effects rely on it. -	pause(kPauseRoom); +	// Loading trigger +	loadingTrigger_NewRoom(newRoomNr); -	debugC(4, kDebugLevelMain, "*** room %d ***", n); +	debugC(4, kDebugLevelMain, "*** room %d ***", newRoomNr);  	_sound->stopSound();  	i = 0; -	for (v = _game.viewTable; v < &_game.viewTable[MAX_VIEWTABLE]; v++) { -		v->entry = i++; -		v->flags &= ~(fAnimated | fDrawn); -		v->flags |= fUpdate; -		v->stepTime = 1; -		v->stepTimeCount = 1; -		v->cycleTime = 1; -		v->cycleTimeCount = 1; -		v->stepSize = 1; +	for (screenObj = _game.screenObjTable; screenObj < &_game.screenObjTable[SCREENOBJECTS_MAX]; screenObj++) { +		screenObj->objectNr = i++; +		screenObj->flags &= ~(fAnimated | fDrawn); +		screenObj->flags |= fUpdate; +		screenObj->stepTime = 1; +		screenObj->stepTimeCount = 1; +		screenObj->cycleTime = 1; +		screenObj->cycleTimeCount = 1; +		screenObj->stepSize = 1;  	}  	agiUnloadResources();  	_game.playerControl = true;  	_game.block.active = false;  	_game.horizon = 36; -	_game.vars[vPrevRoom] = _game.vars[vCurRoom]; -	_game.vars[vCurRoom] = n; -	_game.vars[vBorderTouchObj] = 0; -	_game.vars[vBorderCode] = 0; -	_game.vars[vEgoViewResource] = _game.viewTable[0].currentView; +	_game.vars[VM_VAR_PREVIOUS_ROOM] = _game.vars[VM_VAR_CURRENT_ROOM]; +	_game.vars[VM_VAR_CURRENT_ROOM] = newRoomNr; +	_game.vars[VM_VAR_BORDER_TOUCH_OBJECT] = 0; +	_game.vars[VM_VAR_BORDER_CODE] = 0; +	_game.vars[VM_VAR_EGO_VIEW_RESOURCE] = screenObjEgo->currentViewNr; -	agiLoadResource(rLOGIC, n); +	agiLoadResource(RESOURCETYPE_LOGIC, newRoomNr);  	// Reposition ego in the new room -	switch (_game.vars[vBorderTouchEgo]) { +	switch (_game.vars[VM_VAR_BORDER_TOUCH_EGO]) {  	case 1: -		_game.viewTable[0].yPos = _HEIGHT - 1; +		screenObjEgo->yPos = SCRIPT_HEIGHT - 1;  		break;  	case 2: -		_game.viewTable[0].xPos = 0; +		screenObjEgo->xPos = 0;  		break;  	case 3: -		_game.viewTable[0].yPos = HORIZON + 1; +		screenObjEgo->yPos = _game.horizon + 1;  		break;  	case 4: -		_game.viewTable[0].xPos = _WIDTH - _game.viewTable[0].xSize; +		screenObjEgo->xPos = SCRIPT_WIDTH - screenObjEgo->xSize;  		break;  	}  	if (getVersion() < 0x2000) { -		warning("STUB: NewRoom(%d)", n); +		warning("STUB: NewRoom(%d)", newRoomNr); -		v->flags &= ~fDidntMove; +		screenObjEgo->flags &= ~fDidntMove;  		// animateObject(0); -		agiLoadResource(rVIEW, _game.viewTable[0].currentView); -		setView(&_game.viewTable[0], _game.viewTable[0].currentView); +		agiLoadResource(RESOURCETYPE_VIEW, screenObjEgo->currentViewNr); +		setView(screenObjEgo, screenObjEgo->currentViewNr);  	} else { -		_game.vars[vBorderTouchEgo] = 0; -		setflag(fNewRoomExec, true); +		if (screenObjEgo->motionType == kMotionEgo) { +			screenObjEgo->motionType = kMotionNormal; +			_game.vars[VM_VAR_EGO_DIRECTION] = 0; +		} + +		_game.vars[VM_VAR_BORDER_TOUCH_EGO] = 0; +		setflag(VM_FLAG_NEW_ROOM_EXEC, true);  		_game.exitAllLogics = true; -		writeStatus(); -		writePrompt(); +		_game._vm->_text->statusDraw(); +		_game._vm->_text->promptRedraw();  	}  }  void AgiEngine::resetControllers() {  	int i; -	for (i = 0; i < MAX_DIRS; i++) { +	for (i = 0; i < MAX_CONTROLLERS; i++) {  		_game.controllerOccured[i] = false;  	}  }  void AgiEngine::interpretCycle() { +	ScreenObjEntry *screenObjEgo = &_game.screenObjTable[SCREENOBJECTS_EGO_ENTRY];  	int oldSound, oldScore;  	if (_game.playerControl) -		_game.vars[vEgoDir] = _game.viewTable[0].direction; +		_game.vars[VM_VAR_EGO_DIRECTION] = screenObjEgo->direction;  	else -		_game.viewTable[0].direction = _game.vars[vEgoDir]; +		screenObjEgo->direction = _game.vars[VM_VAR_EGO_DIRECTION];  	checkAllMotions(); -	oldScore = _game.vars[vScore]; -	oldSound = getflag(fSoundOn); +	oldScore = _game.vars[VM_VAR_SCORE]; +	oldSound = getflag(VM_FLAG_SOUND_ON);  	_game.exitAllLogics = false;  	while (runLogic(0) == 0 && !(shouldQuit() || _restartGame)) { -		_game.vars[vWordNotFound] = 0; -		_game.vars[vBorderTouchObj] = 0; -		_game.vars[vBorderCode] = 0; -		oldScore = _game.vars[vScore]; -		setflag(fEnteredCli, false); +		_game.vars[VM_VAR_WORD_NOT_FOUND] = 0; +		_game.vars[VM_VAR_BORDER_TOUCH_OBJECT] = 0; +		_game.vars[VM_VAR_BORDER_CODE] = 0; +		oldScore = _game.vars[VM_VAR_SCORE]; +		setflag(VM_FLAG_ENTERED_CLI, false);  		_game.exitAllLogics = false; +		nonBlockingText_CycleDone();  		resetControllers();  	} +	nonBlockingText_CycleDone();  	resetControllers(); -	_game.viewTable[0].direction = _game.vars[vEgoDir]; +	screenObjEgo->direction = _game.vars[VM_VAR_EGO_DIRECTION]; -	if (_game.vars[vScore] != oldScore || getflag(fSoundOn) != oldSound) -		writeStatus(); +	if (_game.vars[VM_VAR_SCORE] != oldScore || getflag(VM_FLAG_SOUND_ON) != oldSound) +		_game._vm->_text->statusDraw(); -	_game.vars[vBorderTouchObj] = 0; -	_game.vars[vBorderCode] = 0; -	setflag(fNewRoomExec, false); -	setflag(fRestartGame, false); -	setflag(fRestoreJustRan, false); +	_game.vars[VM_VAR_BORDER_TOUCH_OBJECT] = 0; +	_game.vars[VM_VAR_BORDER_CODE] = 0; +	setflag(VM_FLAG_NEW_ROOM_EXEC, false); +	setflag(VM_FLAG_RESTART_GAME, false); +	setflag(VM_FLAG_RESTORE_JUST_RAN, false);  	if (_game.gfxMode) { -		updateViewtable(); -		_gfx->doUpdate(); +		updateScreenObjTable();  	} +	_gfx->updateScreen(); +	//_gfx->doUpdate();  }  /** @@ -166,27 +180,27 @@ void AgiEngine::updateTimer() {  	if (!_game.clockEnabled)  		return; -	setvar(vSeconds, getvar(vSeconds) + 1); -	if (getvar(vSeconds) < 60) +	setVar(VM_VAR_SECONDS, getVar(VM_VAR_SECONDS) + 1); +	if (getVar(VM_VAR_SECONDS) < 60)  		return; -	setvar(vSeconds, 0); -	setvar(vMinutes, getvar(vMinutes) + 1); -	if (getvar(vMinutes) < 60) +	setVar(VM_VAR_SECONDS, 0); +	setVar(VM_VAR_MINUTES, getVar(VM_VAR_MINUTES) + 1); +	if (getVar(VM_VAR_MINUTES) < 60)  		return; -	setvar(vMinutes, 0); -	setvar(vHours, getvar(vHours) + 1); -	if (getvar(vHours) < 24) +	setVar(VM_VAR_MINUTES, 0); +	setVar(VM_VAR_HOURS, getVar(VM_VAR_HOURS) + 1); +	if (getVar(VM_VAR_HOURS) < 24)  		return; -	setvar(vHours, 0); -	setvar(vDays, getvar(vDays) + 1); +	setVar(VM_VAR_HOURS, 0); +	setVar(VM_VAR_DAYS, getVar(VM_VAR_DAYS) + 1);  }  void AgiEngine::newInputMode(InputMode mode) { -	if (mode == INPUT_MENU && !getflag(fMenusWork) && !(getFeatures() & GF_MENUS)) -		return; +	//if (mode == INPUTMODE_MENU && !getflag(VM_FLAG_MENUS_WORK) && !(getFeatures() & GF_MENUS)) +	//	return;  	_oldMode = _game.inputMode;  	_game.inputMode = mode; @@ -198,14 +212,19 @@ void AgiEngine::oldInputMode() {  // If main_cycle returns false, don't process more events!  int AgiEngine::mainCycle(bool onlyCheckForEvents) { -	unsigned int key, kascii; -	VtEntry *v = &_game.viewTable[0]; +	uint16 key; +	byte   keyAscii; +	ScreenObjEntry *screenObjEgo = &_game.screenObjTable[SCREENOBJECTS_EGO_ENTRY];  	if (!onlyCheckForEvents) {  		pollTimer();  		updateTimer();  	} +	if (_menu->delayedExecuteActive()) { +		_menu->execute(); +	} +  	key = doPollKeyboard();  	// In AGI Mouse emulation mode we must update the mouse-related @@ -217,98 +236,106 @@ int AgiEngine::mainCycle(bool onlyCheckForEvents) {  		_game.vars[29] = _mouse.y;  	//} -	if (key == KEY_STATUSLN) {	// F11 -		_debug.statusline = !_debug.statusline; -		writeStatus(); -		key = 0; -	} - -	if (key == KEY_PRIORITY) {	// F12 -		_sprites->eraseBoth(); -		_debug.priority = !_debug.priority; -		_picture->showPic(); -		_sprites->blitBoth(); -		_sprites->commitBoth(); -		key = 0; +	switch (_game.inputMode) { +	case INPUTMODE_NORMAL: +	case INPUTMODE_NONE: +		// Click-to-walk mouse interface +		if (_game.playerControl && (screenObjEgo->flags & fAdjEgoXY)) { +			int toX = screenObjEgo->move_x; +			int toY = screenObjEgo->move_y; + +			// AGI Mouse games use ego's sprite's bottom left corner for mouse walking target. +			// Amiga games use ego's sprite's bottom center for mouse walking target. +			// Atari ST and Apple II GS seem to use the bottom left +			if (getPlatform() == Common::kPlatformAmiga) +				toX -= (screenObjEgo->xSize / 2); // Center ego's sprite horizontally + +			// Adjust ego's sprite's mouse walking target position (These parameters are +			// controlled with the adj.ego.move.to.x.y-command). Note that these values rely +			// on the horizontal centering of the ego's sprite at least on the Amiga platform. +			toX += _game.adjMouseX; +			toY += _game.adjMouseY; + +			screenObjEgo->direction = getDirection(screenObjEgo->xPos, screenObjEgo->yPos, toX, toY, screenObjEgo->stepSize); + +			if (screenObjEgo->direction == 0) +				inDestination(screenObjEgo); +		} +		break; +	default: +		break;  	} -	// Click-to-walk mouse interface -	if (_game.playerControl && (v->flags & fAdjEgoXY)) { -		int toX = v->parm1; -		int toY = v->parm2; - -		// AGI Mouse games use ego's sprite's bottom left corner for mouse walking target. -		// Amiga games use ego's sprite's bottom center for mouse walking target. -		// TODO: Check what Atari ST AGI and Apple IIGS AGI use for mouse walking target. -		if (getPlatform() == Common::kPlatformAmiga) -			toX -= (v->xSize / 2); // Center ego's sprite horizontally - -		// Adjust ego's sprite's mouse walking target position (These parameters are -		// controlled with the adj.ego.move.to.x.y-command). Note that these values rely -		// on the horizontal centering of the ego's sprite at least on the Amiga platform. -		toX += _game.adjMouseX; -		toY += _game.adjMouseY; - -		v->direction = getDirection(v->xPos, v->yPos, toX, toY, v->stepSize); - -		if (v->direction == 0) -			inDestination(v); +	keyAscii = key & 0xFF; +	if (keyAscii) { +		setVar(VM_VAR_KEY, keyAscii);  	} -	kascii = KEY_ASCII(key); - -	if (kascii) -		setvar(vKey, kascii); - -	bool restartProcessKey; -	do { -		restartProcessKey = false; - +	if (!cycleInnerLoopIsActive()) { +		// no inner loop active at the moment, regular processing  		switch (_game.inputMode) { -		case INPUT_NORMAL: +		case INPUTMODE_NORMAL:  			if (!handleController(key)) { -				if (key == 0 || !_game.inputEnabled) +				if (key == 0 || (!_text->promptIsEnabled()))  					break; -				handleKeys(key); - -				// if ESC pressed, activate menu before -				// accept.input from the interpreter cycle -				// sets the input mode to normal again -				// (closes: #540856) -				if (key == KEY_ESCAPE) { -					key = 0; -					restartProcessKey = true; -				} - -				// commented out to close Sarien bug #438872 -				//if (key) -				//	_game.keypress = key; + +				_text->promptCharPress(key);  			}  			break; -		case INPUT_GETSTRING: -			handleController(key); -			handleGetstring(key); -			setvar(vKey, 0);	// clear ENTER key -			break; -		case INPUT_MENU: -			_menu->keyhandler(key); -			_gfx->doUpdate(); -			return false; -		case INPUT_NONE: +		case INPUTMODE_NONE:  			handleController(key);  			if (key)  				_game.keypress = key;  			break; +		default: +			break;  		} -	} while (restartProcessKey); -	if (!onlyCheckForEvents) { -		_gfx->doUpdate(); +	} else { +		// inner loop active +		// call specific workers +		setVar(VM_VAR_KEY, 0);	// clear keys, they must not be passed to the scripts +		_game.keypress = 0; + +		switch (_game.cycleInnerLoopType) { +		case CYCLE_INNERLOOP_GETSTRING: // loop called from TextMgr::stringEdit() +		case CYCLE_INNERLOOP_GETNUMBER: +			//handleController(key); +			if (key) { +				_text->stringCharPress(key); +			} +			break; + +		case CYCLE_INNERLOOP_INVENTORY: // loop called from InventoryMgr::show() +			if (key) { +				_inventory->charPress(key); +			} +			break; + +		case CYCLE_INNERLOOP_MENU: +			if (key) { +				_menu->charPress(key); +			} +			return false; + +		case CYCLE_INNERLOOP_SYSTEMUI_SELECTSAVEDGAMESLOT: +			if (key) { +				_systemUI->savedGameSlot_CharPress(key); +			} +			break; + +		default: +			break; +		} +	} +	if (!onlyCheckForEvents) {  		if (_game.msgBoxTicks > 0)  			_game.msgBoxTicks--;  	} +	_gfx->updateScreen(); +  	return true;  } @@ -319,19 +346,24 @@ int AgiEngine::playGame() {  	debugC(2, kDebugLevelMain, "game version = 0x%x", getVersion());  	_sound->stopSound(); -	_gfx->clearScreen(0); -	_game.horizon = HORIZON; +	// We need to do this accurately and reset the AGI priorityscreen to 4 +	// otherwise at least the fan game Nick's Quest will go into an endless +	// loop, because the game draws views before it draws the first background picture. +	// For further study see bug #3451122 +	_gfx->clear(0, 4); + +	_game.horizon = 36;  	_game.playerControl = false; -	setflag(fLogicZeroFirsttime, true);	// not in 2.917 -	setflag(fNewRoomExec, true);	// needed for MUMG and SQ2! -	setflag(fSoundOn, true);	// enable sound -	setvar(vTimeDelay, 2);	// "normal" speed +	setflag(VM_FLAG_LOGIC_ZERO_FIRST_TIME, true);	// not in 2.917 +	setflag(VM_FLAG_NEW_ROOM_EXEC, true);	// needed for MUMG and SQ2! +	setflag(VM_FLAG_SOUND_ON, true);	// enable sound +	setVar(VM_VAR_TIME_DELAY, 2);	// "normal" speed  	_game.gfxMode = true;  	_game.clockEnabled = true; -	_game.lineUserInput = 22; +	_text->promptRow_Set(22);  	// We run AGIMOUSE always as a side effect  	//if (getFeatures() & GF_AGIMOUSE) @@ -342,25 +374,34 @@ int AgiEngine::playGame() {  	debug(0, "Running AGI script.\n"); -	setflag(fEnteredCli, false); -	setflag(fSaidAcceptedInput, false); -	_game.vars[vWordNotFound] = 0; -	_game.vars[vKey] = 0; +	setflag(VM_FLAG_ENTERED_CLI, false); +	setflag(VM_FLAG_SAID_ACCEPTED_INPUT, false); +	_game.vars[VM_VAR_WORD_NOT_FOUND] = 0; +	_game.vars[VM_VAR_KEY] = 0;  	debugC(2, kDebugLevelMain, "Entering main loop"); -	bool firstLoop = !getflag(fRestartGame); // Do not restore on game restart +	bool firstLoop = !getflag(VM_FLAG_RESTART_GAME); // Do not restore on game restart + +	if (firstLoop) { +		if (ConfMan.hasKey("save_slot")) { +			// quick restore enabled +			_game.automaticRestoreGame = true; +		} +	} + +	nonBlockingText_Forget();  	do {  		if (!mainCycle())  			continue; -		if (getvar(vTimeDelay) == 0 || (1 + _clockCount) % getvar(vTimeDelay) == 0) { -			if (!_game.hasPrompt && _game.inputMode == INPUT_NORMAL) { -				writePrompt(); +		if (getVar(VM_VAR_TIME_DELAY) == 0 || (1 + _clockCount) % getVar(VM_VAR_TIME_DELAY) == 0) { +			if (!_game.hasPrompt && _game.inputMode == INPUTMODE_NORMAL) { +				_text->promptRedraw();  				_game.hasPrompt = 1; -			} else if (_game.hasPrompt && _game.inputMode == INPUT_NONE) { -				writePrompt(); +			} else if (_game.hasPrompt && _game.inputMode == INPUTMODE_NONE) { +				_text->promptRedraw();  				_game.hasPrompt = 0;  			} @@ -368,15 +409,15 @@ int AgiEngine::playGame() {  			// Check if the user has asked to load a game from the command line  			// or the launcher -			if (firstLoop) { +			if (_game.automaticRestoreGame) { +				_game.automaticRestoreGame = false;  				checkQuickLoad(); -				firstLoop = false;  			} -			setflag(fEnteredCli, false); -			setflag(fSaidAcceptedInput, false); -			_game.vars[vWordNotFound] = 0; -			_game.vars[vKey] = 0; +			setflag(VM_FLAG_ENTERED_CLI, false); +			setflag(VM_FLAG_SAID_ACCEPTED_INPUT, false); +			_game.vars[VM_VAR_WORD_NOT_FOUND] = 0; +			_game.vars[VM_VAR_KEY] = 0;  		}  		if (shouldPerformAutoSave(_lastSaveTime)) { @@ -393,6 +434,23 @@ int AgiEngine::playGame() {  int AgiEngine::runGame() {  	int ec = errOK; +	// figure out the expected menu trigger for the current platform +	// need to trigger the menu via mouse and via keyboard for platforms except PC +	if (!(getFeatures() & GF_ESCPAUSE)) { +		switch (getPlatform()) { +		case Common::kPlatformAmiga: +		case Common::kPlatformApple2GS: +			_game.specialMenuTriggerKey = AGI_MENU_TRIGGER_APPLE2GS; +			break; +		case Common::kPlatformAtariST: +			_game.specialMenuTriggerKey = AGI_MENU_TRIGGER_ATARIST; +			break; +		// Macintosh games seem to use ESC key just like PC versions do +		default: +			break; +		} +	} +  	// Execute the game  	do {  		debugC(2, kDebugLevelMain, "game loop"); @@ -402,61 +460,62 @@ int AgiEngine::runGame() {  			break;  		if (_restartGame) { -			setflag(fRestartGame, true); -			setvar(vTimeDelay, 2);	// "normal" speed +			setflag(VM_FLAG_RESTART_GAME, true); +			setVar(VM_VAR_TIME_DELAY, 2);	// "normal" speed  			_restartGame = false;  		}  		// Set computer type (v20 i.e. vComputer) and sound type  		switch (getPlatform()) {  		case Common::kPlatformAtariST: -			setvar(vComputer, kAgiComputerAtariST); -			setvar(vSoundgen, kAgiSoundPC); +			setVar(VM_VAR_COMPUTER, kAgiComputerAtariST); +			setVar(VM_VAR_SOUNDGENERATOR, kAgiSoundPC);  			break;  		case Common::kPlatformAmiga:  			if (getFeatures() & GF_OLDAMIGAV20) -				setvar(vComputer, kAgiComputerAmigaOld); +				setVar(VM_VAR_COMPUTER, kAgiComputerAmigaOld);  			else -				setvar(vComputer, kAgiComputerAmiga); -			setvar(vSoundgen, kAgiSoundTandy); +				setVar(VM_VAR_COMPUTER, kAgiComputerAmiga); +			setVar(VM_VAR_SOUNDGENERATOR, kAgiSoundTandy);  			break;  		case Common::kPlatformApple2GS: -			setvar(vComputer, kAgiComputerApple2GS); +			setVar(VM_VAR_COMPUTER, kAgiComputerApple2GS);  			if (getFeatures() & GF_2GSOLDSOUND) -				setvar(vSoundgen, kAgiSound2GSOld); +				setVar(VM_VAR_SOUNDGENERATOR, kAgiSound2GSOld);  			else -				setvar(vSoundgen, kAgiSoundTandy); +				setVar(VM_VAR_SOUNDGENERATOR, kAgiSoundTandy);  			break;  		case Common::kPlatformDOS:  		default: -			setvar(vComputer, kAgiComputerPC); -			setvar(vSoundgen, kAgiSoundPC); +			setVar(VM_VAR_COMPUTER, kAgiComputerPC); +			setVar(VM_VAR_SOUNDGENERATOR, kAgiSoundPC);  			break;  		}  		// Set monitor type (v26 i.e. vMonitor)  		switch (_renderMode) { -		case Common::kRenderCGA: -			setvar(vMonitor, kAgiMonitorCga); +		case RENDERMODE_CGA: +			setVar(VM_VAR_MONITOR, kAgiMonitorCga);  			break; -		case Common::kRenderHercG: -		case Common::kRenderHercA: -			setvar(vMonitor, kAgiMonitorHercules); +		case RENDERMODE_HERCULES: +			setVar(VM_VAR_MONITOR, kAgiMonitorHercules);  			break;  		// Don't know if Amiga AGI games use a different value than kAgiMonitorEga  		// for vMonitor so I just use kAgiMonitorEga for them (As was done before too). -		case Common::kRenderAmiga: -		case Common::kRenderDefault: -		case Common::kRenderEGA: +		case RENDERMODE_AMIGA: +		case RENDERMODE_APPLE_II_GS: +		case RENDERMODE_ATARI_ST: +		case RENDERMODE_EGA: +		case RENDERMODE_VGA:  		default: -			setvar(vMonitor, kAgiMonitorEga); +			setVar(VM_VAR_MONITOR, kAgiMonitorEga);  			break;  		} -		setvar(vFreePages, 180); // Set amount of free memory to realistic value -		setvar(vMaxInputChars, 38); -		_game.inputMode = INPUT_NONE; -		_game.inputEnabled = false; +		setVar(VM_VAR_FREE_PAGES, 180); // Set amount of free memory to realistic value +		setVar(VM_VAR_MAX_INPUT_CHARACTERS, 38); +		_game.inputMode = INPUTMODE_NONE; +		_text->promptDisable();  		_game.hasPrompt = 0;  		_game.state = STATE_RUNNING; diff --git a/engines/agi/detection.cpp b/engines/agi/detection.cpp index 971b562aec..17358e0a40 100644 --- a/engines/agi/detection.cpp +++ b/engines/agi/detection.cpp @@ -257,7 +257,6 @@ SaveStateList AgiMetaEngine::listSaves(const char *target) const {  	const uint32 AGIflag = MKTAG('A','G','I',':');  	Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();  	Common::StringArray filenames; -	char saveDesc[31];  	Common::String pattern = target;  	pattern += ".###"; @@ -267,16 +266,35 @@ SaveStateList AgiMetaEngine::listSaves(const char *target) const {  	SaveStateList saveList;  	for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) {  		// Obtain the last 3 digits of the filename, since they correspond to the save slot -		int slotNum = atoi(file->c_str() + file->size() - 3); +		int slotNr = atoi(file->c_str() + file->size() - 3); -		if (slotNum >= 0 && slotNum <= 999) { +		if (slotNr >= 0 && slotNr <= 999) {  			Common::InSaveFile *in = saveFileMan->openForLoading(*file);  			if (in) {  				uint32 type = in->readUint32BE(); -				if (type == AGIflag) -					in->read(saveDesc, 31); -				saveList.push_back(SaveStateDescriptor(slotNum, saveDesc)); +				char description[31]; + +				if (type == AGIflag) { +					uint16 descriptionPos = 0; + +					in->read(description, 31); + +					// Security-check, if saveDescription has a terminating NUL +					while (description[descriptionPos]) { +						descriptionPos++; +						if (descriptionPos >= sizeof(description)) +							break; +					} +					if (descriptionPos >= sizeof(description)) { +						strcpy(description, "[broken saved game]"); +					} +				} else { +					strcpy(description, "[not an AGI saved game]"); +				} +  				delete in; + +				saveList.push_back(SaveStateDescriptor(slotNr, description));  			}  		}  	} @@ -291,9 +309,9 @@ void AgiMetaEngine::removeSaveState(const char *target, int slot) const {  	g_system->getSavefileManager()->removeSavefile(fileName);  } -SaveStateDescriptor AgiMetaEngine::querySaveMetaInfos(const char *target, int slot) const { +SaveStateDescriptor AgiMetaEngine::querySaveMetaInfos(const char *target, int slotNr) const {  	const uint32 AGIflag = MKTAG('A','G','I',':'); -	Common::String fileName = Common::String::format("%s.%03d", target, slot); +	Common::String fileName = Common::String::format("%s.%03d", target, slotNr);  	Common::InSaveFile *in = g_system->getSavefileManager()->openForLoading(fileName); @@ -303,49 +321,73 @@ SaveStateDescriptor AgiMetaEngine::querySaveMetaInfos(const char *target, int sl  			return SaveStateDescriptor();  		} -		char name[32]; -		in->read(name, 31); +		char description[31]; +		uint16 descriptionPos = 0; + +		in->read(description, 31); + +		while (description[descriptionPos]) { +			descriptionPos++; +			if (descriptionPos >= sizeof(description)) +				break; +		} +		if (descriptionPos >= sizeof(description)) { +			// broken description, ignore it +			delete in; + +			SaveStateDescriptor descriptor(slotNr, "[broken saved game]"); +			return descriptor; +		} -		SaveStateDescriptor desc(slot, name); +		SaveStateDescriptor descriptor(slotNr, description);  		// Do not allow save slot 0 (used for auto-saving) to be deleted or  		// overwritten. -		desc.setDeletableFlag(slot != 0); -		desc.setWriteProtectedFlag(slot == 0); +		if (slotNr == 0) { +			descriptor.setWriteProtectedFlag(true); +			descriptor.setDeletableFlag(false); +		} else { +			descriptor.setWriteProtectedFlag(false); +			descriptor.setDeletableFlag(true); +		}  		char saveVersion = in->readByte();  		if (saveVersion >= 4) {  			Graphics::Surface *const thumbnail = Graphics::loadThumbnail(*in); -			desc.setThumbnail(thumbnail); +			descriptor.setThumbnail(thumbnail);  			uint32 saveDate = in->readUint32BE();  			uint16 saveTime = in->readUint16BE();  			if (saveVersion >= 6) {  				uint32 playTime = in->readUint32BE(); -				desc.setPlayTime(playTime * 1000); +				descriptor.setPlayTime(playTime * 1000);  			}  			int day = (saveDate >> 24) & 0xFF;  			int month = (saveDate >> 16) & 0xFF;  			int year = saveDate & 0xFFFF; -			desc.setSaveDate(year, month, day); +			descriptor.setSaveDate(year, month, day);  			int hour = (saveTime >> 8) & 0xFF;  			int minutes = saveTime & 0xFF; -			desc.setSaveTime(hour, minutes); +			descriptor.setSaveTime(hour, minutes);  		} -  		delete in; -		return desc; +		return descriptor; +  	} else {  		SaveStateDescriptor emptySave;  		// Do not allow save slot 0 (used for auto-saving) to be overwritten. -		emptySave.setWriteProtectedFlag(slot == 0); +		if (slotNr == 0) { +			emptySave.setWriteProtectedFlag(true); +		} else { +			emptySave.setWriteProtectedFlag(false); +		}  		return emptySave;  	}  } @@ -528,14 +570,17 @@ const ADGameDescription *AgiMetaEngine::fallbackDetect(const FileMap &allFilesXX  namespace Agi {  bool AgiBase::canLoadGameStateCurrently() { -	return (!(getGameType() == GType_PreAGI) && getflag(fMenusWork) && !_noSaveLoadAllowed); +	return (!(getGameType() == GType_PreAGI) && getflag(VM_FLAG_MENUS_WORK) && !_noSaveLoadAllowed);  }  bool AgiBase::canSaveGameStateCurrently() { +	bool promptEnabled = false; +  	if (getGameID() == GID_BC) // Technically in Black Cauldron we may save anytime  		return true; -	return (!(getGameType() == GType_PreAGI) && getflag(fMenusWork) && !_noSaveLoadAllowed && _game.inputEnabled); +	promptEnabled = promptIsEnabled(); +	return (!(getGameType() == GType_PreAGI) && getflag(VM_FLAG_MENUS_WORK) && !_noSaveLoadAllowed && promptEnabled);  }  int AgiEngine::agiDetectGame() { diff --git a/engines/agi/font.h b/engines/agi/font.h index 0e6b15f06b..6b6381ccd7 100644 --- a/engines/agi/font.h +++ b/engines/agi/font.h @@ -25,6 +25,181 @@  namespace Agi { +// Arrow to the right character, used for original saved game dialogs +// Needs to get patched into at least the Apple IIgs font, because the font didn't support +// that character and original AGI on Apple IIgs used Apple II menus for saving/restoring. +static const uint8 fontData_ArrowRightCharacter[8] = { +	0x00, 0x18, 0x0C, 0xFE, 0x0C, 0x18, 0x00, 0x00, +}; + +// topaz data, same as in engines\parallaction\staticres.cpp +// seems to have been recreated and is not the original amiga font +static const uint8 fontData_AmigaPseudoTopaz[2600] = { +	0x00, 0x00, 0x03, 0xf3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x79, 0x00, 0x00, 0x03, 0xe9, 0x00, 0x00, 0x02, 0x79, +	0x70, 0xff, 0x4e, 0x75, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, +	0x00, 0x1a, 0x0f, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x45, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x09, 0x74, 0x00, 0x08, +	0x00, 0x40, 0x00, 0x08, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x20, 0xff, 0x00, 0x00, 0x00, 0x6e, +	0x00, 0xbe, 0x00, 0x00, 0x06, 0x5e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, +	0x6c, 0x6c, 0x18, 0x00, 0x38, 0x18, 0x0c, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x3c, 0x18, +	0x3c, 0x3c, 0x1c, 0x7e, 0x1c, 0x7e, 0x3c, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x7c, 0x3c, +	0x7c, 0x1e, 0x78, 0x7e, 0x7e, 0x3c, 0x66, 0x3c, 0x06, 0xc6, 0x60, 0xc6, 0xc6, 0x3c, 0x7c, 0x78, +	0x7c, 0x3c, 0x7e, 0x66, 0x66, 0xc6, 0xc3, 0xc3, 0xfe, 0x3c, 0xc0, 0x3c, 0x10, 0x00, 0x18, 0x00, +	0x60, 0x00, 0x06, 0x00, 0x1c, 0x00, 0x60, 0x18, 0x0c, 0x60, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x18, 0x70, 0x72, 0x0f, 0x00, 0x18, +	0x00, 0x1c, 0x42, 0xc3, 0x18, 0x3c, 0x66, 0x7e, 0x1c, 0x00, 0x3e, 0x7e, 0x7e, 0x3c, 0x18, 0x78, +	0x78, 0x18, 0x00, 0x3e, 0x00, 0x00, 0x30, 0x38, 0x00, 0x40, 0x40, 0xc0, 0x18, 0x3c, 0x3c, 0x7e, +	0x06, 0x66, 0x18, 0x7e, 0x7e, 0x36, 0x0c, 0x0c, 0x18, 0x3c, 0xc6, 0x3c, 0x60, 0x76, 0x18, 0x00, +	0x0c, 0x7e, 0x71, 0x66, 0x00, 0x66, 0x60, 0x0e, 0x7e, 0x66, 0x18, 0x6e, 0x3c, 0x00, 0x18, 0x7e, +	0x06, 0x66, 0x18, 0x00, 0x7e, 0x34, 0x0c, 0x0c, 0x18, 0x0c, 0x60, 0x00, 0x18, 0x3c, 0x0c, 0x00, +	0x0c, 0x00, 0x71, 0x00, 0x00, 0x00, 0x18, 0x0c, 0x7e, 0x00, 0x18, 0x3c, 0x00, 0x18, 0x6c, 0x6c, +	0x3e, 0x66, 0x6c, 0x18, 0x18, 0x18, 0x66, 0x18, 0x00, 0x00, 0x00, 0x06, 0x66, 0x38, 0x66, 0x66, +	0x3c, 0x60, 0x30, 0x06, 0x66, 0x66, 0x18, 0x18, 0x06, 0x00, 0x60, 0x66, 0xc6, 0x66, 0x66, 0x30, +	0x6c, 0x60, 0x60, 0x66, 0x66, 0x18, 0x06, 0xcc, 0x60, 0xee, 0xe6, 0x66, 0x66, 0xcc, 0x66, 0x66, +	0x18, 0x66, 0x66, 0xc6, 0x66, 0x66, 0x0c, 0x30, 0x60, 0x0c, 0x38, 0x00, 0x18, 0x00, 0x60, 0x00, +	0x06, 0x00, 0x30, 0x00, 0x60, 0x00, 0x00, 0x60, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x9c, 0x3c, 0x7e, 0x00, 0x0c, 0x36, +	0x3c, 0x66, 0x18, 0x60, 0x66, 0x81, 0x24, 0x33, 0x06, 0x81, 0x00, 0x66, 0x18, 0x0c, 0x0c, 0x30, +	0x00, 0x7a, 0x00, 0x00, 0x70, 0x44, 0xcc, 0xc6, 0xc6, 0x23, 0x00, 0x66, 0x18, 0x00, 0x1c, 0x00, +	0x24, 0x60, 0x00, 0x1c, 0x18, 0x18, 0x00, 0x66, 0xcc, 0x00, 0x60, 0x3c, 0x30, 0xc6, 0x18, 0x00, +	0x8e, 0x00, 0xc6, 0x66, 0x60, 0x38, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x00, 0x00, 0x00, 0x0c, 0x00, +	0x24, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x18, 0x66, 0x3c, 0x18, 0x18, 0x18, 0x00, 0x18, 0x7e, +	0x8e, 0x66, 0x18, 0x00, 0x18, 0x18, 0x00, 0x66, 0x00, 0x18, 0x00, 0x18, 0x00, 0xfe, 0x60, 0xac, +	0x68, 0x30, 0x30, 0x0c, 0x3c, 0x18, 0x00, 0x00, 0x00, 0x0c, 0x6e, 0x78, 0x06, 0x06, 0x6c, 0x7c, +	0x60, 0x06, 0x66, 0x66, 0x18, 0x18, 0x18, 0x7e, 0x18, 0x06, 0xde, 0x66, 0x66, 0x60, 0x66, 0x60, +	0x60, 0x60, 0x66, 0x18, 0x06, 0xd8, 0x60, 0xfe, 0xf6, 0x66, 0x66, 0xcc, 0x66, 0x70, 0x18, 0x66, +	0x66, 0xc6, 0x3c, 0x3c, 0x18, 0x30, 0x30, 0x0c, 0x6c, 0x00, 0x0c, 0x3c, 0x7c, 0x3c, 0x3e, 0x3c, +	0x7c, 0x3e, 0x7c, 0x18, 0x0c, 0x66, 0x18, 0xec, 0x7c, 0x3c, 0x7c, 0x3e, 0x7c, 0x3c, 0x7c, 0x66, +	0x66, 0xc6, 0xc6, 0x66, 0x7e, 0x18, 0x18, 0x18, 0x00, 0xf0, 0x66, 0x18, 0x3e, 0x30, 0x66, 0x3c, +	0x18, 0x3c, 0x00, 0x9d, 0x44, 0x66, 0x00, 0xb9, 0x00, 0x3c, 0x7e, 0x18, 0x18, 0x60, 0x66, 0x7a, +	0x18, 0x00, 0x30, 0x44, 0x66, 0x4c, 0x4c, 0x66, 0x18, 0x66, 0x18, 0x3c, 0x3c, 0x3c, 0x3c, 0x60, +	0x7e, 0x3c, 0x7e, 0x7e, 0x7e, 0x60, 0xd8, 0x3c, 0x60, 0x66, 0xc6, 0xe6, 0x3c, 0x3c, 0x3c, 0x3c, +	0x6c, 0x66, 0x6c, 0x66, 0x66, 0x66, 0x7e, 0x7e, 0x66, 0x3c, 0x18, 0x3c, 0x18, 0x3c, 0x3c, 0x3c, +	0x3c, 0x18, 0x3c, 0x7e, 0x3c, 0x3e, 0x6c, 0x00, 0x18, 0x3c, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x66, 0x1e, 0x3c, 0x66, 0x00, 0x7e, 0x7e, 0x00, 0x18, 0x00, 0x6c, 0x3c, 0xd8, 0x76, 0x00, +	0x30, 0x0c, 0xff, 0x7e, 0x00, 0x7e, 0x00, 0x18, 0x7e, 0x18, 0x0c, 0x1c, 0xcc, 0x06, 0x7c, 0x0c, +	0x3c, 0x3e, 0x00, 0x00, 0x60, 0x00, 0x06, 0x0c, 0xd6, 0x7e, 0x7c, 0x60, 0x66, 0x78, 0x78, 0x6e, +	0x7e, 0x18, 0x06, 0xf0, 0x60, 0xd6, 0xde, 0x66, 0x7c, 0xcc, 0x7c, 0x3c, 0x18, 0x66, 0x66, 0xd6, +	0x18, 0x18, 0x30, 0x30, 0x18, 0x0c, 0xc6, 0x00, 0x00, 0x06, 0x66, 0x60, 0x66, 0x66, 0x30, 0x66, +	0x66, 0x18, 0x0c, 0x6c, 0x18, 0xfe, 0x66, 0x66, 0x66, 0x66, 0x66, 0x60, 0x30, 0x66, 0x66, 0xc6, +	0x6c, 0x66, 0x0c, 0x70, 0x18, 0x0e, 0x00, 0xc3, 0x66, 0x18, 0x6c, 0x78, 0x3c, 0x18, 0x00, 0x66, +	0x00, 0xb1, 0x3c, 0xcc, 0x00, 0xa5, 0x00, 0x00, 0x18, 0x30, 0x0c, 0x00, 0x66, 0x3a, 0x18, 0x00, +	0x30, 0x38, 0x33, 0x58, 0x58, 0x2c, 0x30, 0x7e, 0x18, 0x66, 0x66, 0x66, 0x66, 0x78, 0x60, 0x66, +	0x60, 0x4c, 0x60, 0x6e, 0xf0, 0x18, 0x60, 0x30, 0xe6, 0xf6, 0x66, 0x66, 0x66, 0x66, 0x38, 0x66, +	0x70, 0x30, 0x66, 0x66, 0x4c, 0x4c, 0x6c, 0x06, 0x18, 0x06, 0x3c, 0x06, 0x06, 0x66, 0x66, 0x3c, +	0x66, 0x0c, 0x66, 0x66, 0x78, 0x18, 0x18, 0x60, 0x7c, 0x66, 0x3c, 0x3c, 0x3c, 0x3c, 0x7e, 0x66, +	0x78, 0x60, 0x66, 0x66, 0x0c, 0x0c, 0x00, 0x18, 0x00, 0xfe, 0x06, 0x36, 0xdc, 0x00, 0x30, 0x0c, +	0x3c, 0x18, 0x00, 0x00, 0x00, 0x30, 0x76, 0x18, 0x18, 0x06, 0xfe, 0x06, 0x66, 0x18, 0x66, 0x06, +	0x00, 0x00, 0x18, 0x7e, 0x18, 0x18, 0xde, 0x66, 0x66, 0x60, 0x66, 0x60, 0x60, 0x66, 0x66, 0x18, +	0x06, 0xd8, 0x60, 0xc6, 0xce, 0x66, 0x60, 0xcc, 0x6c, 0x0e, 0x18, 0x66, 0x3c, 0xfe, 0x3c, 0x18, +	0x60, 0x30, 0x0c, 0x0c, 0x00, 0x00, 0x00, 0x3e, 0x66, 0x60, 0x66, 0x7e, 0x30, 0x66, 0x66, 0x18, +	0x0c, 0x78, 0x18, 0xd6, 0x66, 0x66, 0x66, 0x66, 0x60, 0x3c, 0x30, 0x66, 0x66, 0xd6, 0x38, 0x66, +	0x18, 0x18, 0x18, 0x18, 0x00, 0x0f, 0x66, 0x18, 0x3e, 0x30, 0x42, 0x3c, 0x18, 0x3c, 0x00, 0x9d, +	0x00, 0x66, 0x00, 0xb9, 0x00, 0x00, 0x18, 0x7c, 0x78, 0x00, 0x66, 0x0a, 0x00, 0x00, 0x30, 0x00, +	0x66, 0x32, 0x3e, 0xd9, 0x60, 0x66, 0x18, 0x7e, 0x40, 0x7e, 0x7e, 0x60, 0x78, 0x40, 0x78, 0x18, +	0x78, 0x66, 0xd8, 0x18, 0x60, 0x0c, 0xf6, 0xde, 0x66, 0x66, 0x66, 0x66, 0x6c, 0x66, 0xe0, 0x0c, +	0x66, 0x66, 0x18, 0x18, 0x66, 0x3e, 0x18, 0x3e, 0x60, 0x3e, 0x3e, 0x7e, 0x7e, 0x60, 0x7e, 0x18, +	0x7e, 0x66, 0x6c, 0x18, 0x18, 0x3c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x66, 0x18, 0x3c, +	0x66, 0x66, 0x18, 0x18, 0x00, 0x00, 0x00, 0x6c, 0x7c, 0x6a, 0xce, 0x00, 0x18, 0x18, 0x66, 0x18, +	0x18, 0x00, 0x18, 0x60, 0x66, 0x18, 0x30, 0x66, 0x0c, 0x66, 0x66, 0x18, 0x66, 0x0c, 0x18, 0x18, +	0x06, 0x00, 0x60, 0x00, 0xc0, 0x66, 0x66, 0x30, 0x6c, 0x60, 0x60, 0x66, 0x66, 0x18, 0x66, 0xcc, +	0x60, 0xc6, 0xc6, 0x66, 0x60, 0xdc, 0x66, 0x66, 0x18, 0x66, 0x3c, 0xee, 0x66, 0x18, 0xc0, 0x30, +	0x06, 0x0c, 0x00, 0x00, 0x00, 0x66, 0x66, 0x60, 0x66, 0x60, 0x30, 0x3e, 0x66, 0x18, 0x0c, 0x6c, +	0x18, 0xc6, 0x66, 0x66, 0x7c, 0x3e, 0x60, 0x06, 0x30, 0x66, 0x3c, 0xfe, 0x6c, 0x3c, 0x30, 0x18, +	0x18, 0x18, 0x00, 0x3c, 0x66, 0x18, 0x0c, 0x30, 0x00, 0x18, 0x18, 0x06, 0x00, 0x81, 0x7e, 0x33, +	0x00, 0xa5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x0a, 0x00, 0x00, 0x00, 0x7c, 0xcc, 0x66, +	0x62, 0x33, 0x66, 0x66, 0x18, 0x66, 0x66, 0x66, 0x66, 0x60, 0x60, 0x66, 0x60, 0x32, 0x60, 0x3e, +	0xcc, 0x18, 0x7e, 0x66, 0xde, 0xce, 0x66, 0x66, 0x66, 0x66, 0xc6, 0x66, 0x60, 0x66, 0x66, 0x66, +	0x32, 0x32, 0x66, 0x66, 0x18, 0x66, 0x60, 0x66, 0x66, 0x60, 0x60, 0x60, 0x60, 0x30, 0x60, 0x3e, +	0x66, 0x18, 0x18, 0x06, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x18, 0x66, 0x18, 0x06, 0x66, 0x66, +	0x30, 0x30, 0x00, 0x18, 0x00, 0x6c, 0x18, 0xcc, 0x7b, 0x00, 0x0c, 0x30, 0x00, 0x00, 0x18, 0x00, +	0x18, 0xc0, 0x3c, 0x18, 0x7e, 0x3c, 0x0c, 0x3c, 0x3c, 0x18, 0x3c, 0x38, 0x18, 0x18, 0x00, 0x00, +	0x00, 0x18, 0x78, 0x66, 0x7c, 0x1e, 0x78, 0x7e, 0x60, 0x3e, 0x66, 0x3c, 0x3c, 0xc6, 0x7e, 0xc6, +	0xc6, 0x3c, 0x60, 0x7e, 0x66, 0x3c, 0x18, 0x3c, 0x18, 0xc6, 0xc3, 0x18, 0xfe, 0x3c, 0x03, 0x3c, +	0x00, 0x00, 0x00, 0x3e, 0x7c, 0x3c, 0x3e, 0x3c, 0x30, 0x06, 0x66, 0x0c, 0x0c, 0x66, 0x0c, 0xc6, +	0x66, 0x3c, 0x60, 0x06, 0x60, 0x7c, 0x1c, 0x3e, 0x18, 0x6c, 0xc6, 0x18, 0x7e, 0x0e, 0x18, 0x70, +	0x00, 0xf0, 0x7e, 0x18, 0x00, 0x7e, 0x00, 0x18, 0x18, 0x3c, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x81, +	0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x7f, 0x0a, 0x00, 0x18, 0x00, 0x00, 0x00, 0xcf, 0xc4, 0x67, +	0x3c, 0x67, 0x3e, 0x66, 0x3c, 0x66, 0x66, 0x7f, 0x7e, 0x3c, 0x7e, 0x7e, 0x7e, 0x18, 0x30, 0x3c, +	0x18, 0x3c, 0xce, 0x18, 0x3c, 0x3c, 0x3c, 0x3c, 0x00, 0x3f, 0x7e, 0x3c, 0x3c, 0x3c, 0x7e, 0x7e, +	0x6c, 0x3f, 0x1e, 0x3e, 0x3c, 0x3e, 0x3e, 0x3c, 0x3c, 0x3c, 0x3c, 0x7e, 0x3c, 0x06, 0x18, 0x0c, +	0x0c, 0x7c, 0x66, 0x18, 0x3c, 0x3c, 0x3c, 0x3c, 0x00, 0x3f, 0x0c, 0x7c, 0x3e, 0x3e, 0x7e, 0x7e, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x60, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x02, 0x0e, 0x01, 0x00, 0x03, +	0x06, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x60, 0x00, 0x30, 0x00, +	0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x03, +	0x06, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x30, 0x00, 0x18, 0x00, +	0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x10, 0x00, 0x08, 0x00, 0x18, 0x00, 0x08, 0x00, 0x20, +	0x00, 0x08, 0x00, 0x28, 0x00, 0x08, 0x00, 0x30, 0x00, 0x08, 0x00, 0x38, 0x00, 0x08, 0x00, 0x40, +	0x00, 0x08, 0x00, 0x48, 0x00, 0x08, 0x00, 0x50, 0x00, 0x08, 0x00, 0x58, 0x00, 0x08, 0x00, 0x60, +	0x00, 0x08, 0x00, 0x68, 0x00, 0x08, 0x00, 0x70, 0x00, 0x08, 0x00, 0x78, 0x00, 0x08, 0x00, 0x80, +	0x00, 0x08, 0x00, 0x88, 0x00, 0x08, 0x00, 0x90, 0x00, 0x08, 0x00, 0x98, 0x00, 0x08, 0x00, 0xa0, +	0x00, 0x08, 0x00, 0xa8, 0x00, 0x08, 0x00, 0xb0, 0x00, 0x08, 0x00, 0xb8, 0x00, 0x08, 0x00, 0xc0, +	0x00, 0x08, 0x00, 0xc8, 0x00, 0x08, 0x00, 0xd0, 0x00, 0x08, 0x00, 0xd8, 0x00, 0x08, 0x00, 0xe0, +	0x00, 0x08, 0x00, 0xe8, 0x00, 0x08, 0x00, 0xf0, 0x00, 0x08, 0x00, 0xf8, 0x00, 0x08, 0x01, 0x00, +	0x00, 0x08, 0x01, 0x08, 0x00, 0x08, 0x01, 0x10, 0x00, 0x08, 0x01, 0x18, 0x00, 0x08, 0x01, 0x20, +	0x00, 0x08, 0x01, 0x28, 0x00, 0x08, 0x01, 0x30, 0x00, 0x08, 0x01, 0x38, 0x00, 0x08, 0x01, 0x40, +	0x00, 0x08, 0x01, 0x48, 0x00, 0x08, 0x01, 0x50, 0x00, 0x08, 0x01, 0x58, 0x00, 0x08, 0x01, 0x60, +	0x00, 0x08, 0x01, 0x68, 0x00, 0x08, 0x01, 0x70, 0x00, 0x08, 0x01, 0x78, 0x00, 0x08, 0x01, 0x80, +	0x00, 0x08, 0x01, 0x88, 0x00, 0x08, 0x01, 0x90, 0x00, 0x08, 0x01, 0x98, 0x00, 0x08, 0x01, 0xa0, +	0x00, 0x08, 0x01, 0xa8, 0x00, 0x08, 0x01, 0xb0, 0x00, 0x08, 0x01, 0xb8, 0x00, 0x08, 0x01, 0xc0, +	0x00, 0x08, 0x01, 0xc8, 0x00, 0x08, 0x01, 0xd0, 0x00, 0x08, 0x01, 0xd8, 0x00, 0x08, 0x01, 0xe0, +	0x00, 0x08, 0x01, 0xe8, 0x00, 0x08, 0x01, 0xf0, 0x00, 0x08, 0x01, 0xf8, 0x00, 0x08, 0x02, 0x00, +	0x00, 0x08, 0x02, 0x08, 0x00, 0x08, 0x02, 0x10, 0x00, 0x08, 0x02, 0x18, 0x00, 0x08, 0x02, 0x20, +	0x00, 0x08, 0x02, 0x28, 0x00, 0x08, 0x02, 0x30, 0x00, 0x08, 0x02, 0x38, 0x00, 0x08, 0x02, 0x40, +	0x00, 0x08, 0x02, 0x48, 0x00, 0x08, 0x02, 0x50, 0x00, 0x08, 0x02, 0x58, 0x00, 0x08, 0x02, 0x60, +	0x00, 0x08, 0x02, 0x68, 0x00, 0x08, 0x02, 0x70, 0x00, 0x08, 0x02, 0x78, 0x00, 0x08, 0x02, 0x80, +	0x00, 0x08, 0x02, 0x88, 0x00, 0x08, 0x02, 0x90, 0x00, 0x08, 0x02, 0x98, 0x00, 0x08, 0x02, 0xa0, +	0x00, 0x08, 0x02, 0xa8, 0x00, 0x08, 0x02, 0xb0, 0x00, 0x08, 0x02, 0xb8, 0x00, 0x08, 0x02, 0xc0, +	0x00, 0x08, 0x02, 0xc8, 0x00, 0x08, 0x02, 0xd0, 0x00, 0x08, 0x02, 0xd8, 0x00, 0x08, 0x02, 0xe0, +	0x00, 0x08, 0x02, 0xe8, 0x00, 0x08, 0x02, 0xf0, 0x00, 0x08, 0x02, 0xf8, 0x00, 0x08, 0x03, 0x00, +	0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, +	0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, +	0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, +	0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, +	0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, +	0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, +	0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, +	0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x08, 0x00, 0x00, +	0x00, 0x08, 0x03, 0x08, 0x00, 0x08, 0x03, 0x10, 0x00, 0x08, 0x03, 0x18, 0x00, 0x08, 0x03, 0x20, +	0x00, 0x08, 0x03, 0x28, 0x00, 0x08, 0x03, 0x30, 0x00, 0x08, 0x03, 0x38, 0x00, 0x08, 0x03, 0x40, +	0x00, 0x08, 0x03, 0x48, 0x00, 0x08, 0x03, 0x50, 0x00, 0x08, 0x03, 0x58, 0x00, 0x08, 0x03, 0x60, +	0x00, 0x08, 0x00, 0x68, 0x00, 0x08, 0x03, 0x68, 0x00, 0x08, 0x03, 0x70, 0x00, 0x08, 0x03, 0x78, +	0x00, 0x08, 0x03, 0x80, 0x00, 0x08, 0x03, 0x88, 0x00, 0x08, 0x03, 0x90, 0x00, 0x08, 0x03, 0x98, +	0x00, 0x08, 0x03, 0xa0, 0x00, 0x08, 0x03, 0xa8, 0x00, 0x08, 0x03, 0xb0, 0x00, 0x08, 0x03, 0xb8, +	0x00, 0x08, 0x03, 0xc0, 0x00, 0x08, 0x03, 0xc8, 0x00, 0x08, 0x03, 0xd0, 0x00, 0x08, 0x03, 0xd8, +	0x00, 0x08, 0x03, 0xe0, 0x00, 0x08, 0x03, 0xe8, 0x00, 0x08, 0x03, 0xf0, 0x00, 0x08, 0x03, 0xf8, +	0x00, 0x08, 0x04, 0x00, 0x00, 0x08, 0x04, 0x08, 0x00, 0x08, 0x04, 0x10, 0x00, 0x08, 0x04, 0x18, +	0x00, 0x08, 0x04, 0x20, 0x00, 0x08, 0x04, 0x28, 0x00, 0x08, 0x04, 0x30, 0x00, 0x08, 0x04, 0x38, +	0x00, 0x08, 0x04, 0x40, 0x00, 0x08, 0x04, 0x48, 0x00, 0x08, 0x04, 0x50, 0x00, 0x08, 0x04, 0x58, +	0x00, 0x08, 0x04, 0x60, 0x00, 0x08, 0x04, 0x68, 0x00, 0x08, 0x04, 0x70, 0x00, 0x08, 0x04, 0x78, +	0x00, 0x08, 0x04, 0x80, 0x00, 0x08, 0x04, 0x88, 0x00, 0x08, 0x04, 0x90, 0x00, 0x08, 0x04, 0x98, +	0x00, 0x08, 0x04, 0xa0, 0x00, 0x08, 0x04, 0xa8, 0x00, 0x08, 0x04, 0xb0, 0x00, 0x08, 0x04, 0xb8, +	0x00, 0x08, 0x04, 0xc0, 0x00, 0x08, 0x04, 0xc8, 0x00, 0x08, 0x04, 0xd0, 0x00, 0x08, 0x04, 0xd8, +	0x00, 0x08, 0x04, 0xe0, 0x00, 0x08, 0x04, 0xe8, 0x00, 0x08, 0x04, 0xf0, 0x00, 0x08, 0x04, 0xf8, +	0x00, 0x08, 0x05, 0x00, 0x00, 0x08, 0x05, 0x08, 0x00, 0x08, 0x05, 0x10, 0x00, 0x08, 0x05, 0x18, +	0x00, 0x08, 0x05, 0x20, 0x00, 0x08, 0x05, 0x28, 0x00, 0x08, 0x05, 0x30, 0x00, 0x08, 0x05, 0x38, +	0x00, 0x08, 0x05, 0x40, 0x00, 0x08, 0x05, 0x48, 0x00, 0x08, 0x05, 0x50, 0x00, 0x08, 0x05, 0x58, +	0x00, 0x08, 0x05, 0x60, 0x00, 0x08, 0x05, 0x68, 0x00, 0x08, 0x05, 0x70, 0x00, 0x08, 0x05, 0x78, +	0x00, 0x08, 0x05, 0x80, 0x00, 0x08, 0x05, 0x88, 0x00, 0x08, 0x05, 0x90, 0x00, 0x08, 0x05, 0x98, +	0x00, 0x08, 0x05, 0xa0, 0x00, 0x08, 0x05, 0xa8, 0x00, 0x08, 0x05, 0xb0, 0x00, 0x08, 0x05, 0xb8, +	0x00, 0x08, 0x05, 0xc0, 0x00, 0x08, 0x05, 0xc8, 0x00, 0x08, 0x05, 0xd0, 0x00, 0x08, 0x05, 0xd8, +	0x00, 0x08, 0x05, 0xe0, 0x00, 0x08, 0x05, 0xe8, 0x00, 0x08, 0x00, 0x38, 0x00, 0x08, 0x03, 0x00, +	0x00, 0x08, 0x03, 0x00, 0x00, 0x00, 0x03, 0xec, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x62, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf2 +}; +  // 8x8 font patterns  static const uint8 fontData_Sierra[] = {  	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -546,6 +721,7 @@ static const uint8 fontData_FanGames[] = {  	0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x7E, 0x00  }; +#if 0  static const uint8 fontData_Mickey[] = {  	0x00, 0x36, 0x7F, 0x7F, 0x3E, 0x1C, 0x08, 0x00,  	0x00, 0x00, 0x3F, 0x20, 0x2F, 0x28, 0x28, 0x28, @@ -804,6 +980,7 @@ static const uint8 fontData_Mickey[] = {  	0x08, 0x18, 0x38, 0x78, 0x38, 0x18, 0x08, 0x00,  	0x10, 0x18, 0x1C, 0x1E, 0x1C, 0x18, 0x10, 0x00,  }; +#endif  static const uint8 fontData_IBM[] = {  	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, diff --git a/engines/agi/global.cpp b/engines/agi/global.cpp index 7d55316d7b..bf6356dbcd 100644 --- a/engines/agi/global.cpp +++ b/engines/agi/global.cpp @@ -48,17 +48,17 @@ void AgiBase::flipflag(int n) {  	*set ^= 1 << (n & 0x07);	// flip bit  } -void AgiEngine::setvar(int var, int val) { -	_game.vars[var] = val; +void AgiEngine::setVar(int16 varNr, int val) { +	_game.vars[varNr] = val; -	if (var == vVolume) { +	if (varNr == VM_VAR_VOLUME) {  		_mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, val * 17);  		_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, val * 17);  	}  } -int AgiEngine::getvar(int var) { -	return _game.vars[var]; +int AgiEngine::getVar(int16 varNr) { +	return _game.vars[varNr];  }  void AgiEngine::decrypt(uint8 *mem, int len) { diff --git a/engines/agi/graphics.cpp b/engines/agi/graphics.cpp index b46e6f726c..e9ae1295e9 100644 --- a/engines/agi/graphics.cpp +++ b/engines/agi/graphics.cpp @@ -23,773 +23,739 @@  #include "common/config-manager.h"  #include "common/file.h"  #include "common/textconsole.h" +#include "engines/util.h"  #include "graphics/cursorman.h"  #include "graphics/palette.h"  #include "agi/agi.h"  #include "agi/graphics.h" +#include "agi/mouse_cursor.h" +#include "agi/palette.h" +#include "agi/picture.h" +#include "agi/text.h"  namespace Agi { -#define DEV_X0(x) ((x) << 1) -#define DEV_X1(x) (((x) << 1) + 1) -#define DEV_Y(x) (x) +#include "agi/font.h" -#ifndef MAX_INT -#  define MAX_INT (int)((unsigned)~0 >> 1) -#endif +GfxMgr::GfxMgr(AgiBase *vm) : _vm(vm) { +	_agipalFileNum = 0; -#include "agi/font.h" +	memset(&_paletteGfxMode, 0, sizeof(_paletteGfxMode)); +	memset(&_paletteTextMode, 0, sizeof(_paletteTextMode)); -/** - * 16 color RGB palette. - * This array contains the 6-bit RGB values of the EGA palette exported - * to the console drivers. - */ -static const uint8 egaPalette[16 * 3] = { -	0x00, 0x00, 0x00, -	0x00, 0x00, 0x2a, -	0x00, 0x2a, 0x00, -	0x00, 0x2a, 0x2a, -	0x2a, 0x00, 0x00, -	0x2a, 0x00, 0x2a, -	0x2a, 0x15, 0x00, -	0x2a, 0x2a, 0x2a, -	0x15, 0x15, 0x15, -	0x15, 0x15, 0x3f, -	0x15, 0x3f, 0x15, -	0x15, 0x3f, 0x3f, -	0x3f, 0x15, 0x15, -	0x3f, 0x15, 0x3f, -	0x3f, 0x3f, 0x15, -	0x3f, 0x3f, 0x3f -}; +	memset(&_mouseCursor, 0, sizeof(_mouseCursor)); +	memset(&_mouseCursorBusy, 0, sizeof(_mouseCursorBusy)); -/** - * Atari ST AGI palette. - * Used by all of the tested Atari ST AGI games - * from Donald Duck's Playground (1986) to Manhunter II (1989). - * 16 RGB colors. 3 bits per color component. - */ -#if 0 -static const uint8 atariStAgiPalette[16 * 3] = { -	0x0, 0x0, 0x0, -	0x0, 0x0, 0x7, -	0x0, 0x4, 0x0, -	0x0, 0x5, 0x4, -	0x5, 0x0, 0x0, -	0x5, 0x3, 0x6, -	0x4, 0x3, 0x0, -	0x5, 0x5, 0x5, -	0x3, 0x3, 0x2, -	0x0, 0x5, 0x7, -	0x0, 0x6, 0x0, -	0x0, 0x7, 0x6, -	0x7, 0x2, 0x3, -	0x7, 0x4, 0x7, -	0x7, 0x7, 0x4, -	0x7, 0x7, 0x7 -}; -#endif +	initPriorityTable(); -/** - * Second generation Apple IIGS AGI palette. - * A 16-color, 12-bit RGB palette. - * - * Used by at least the following Apple IIGS AGI versions: - * 1.003 (Leisure Suit Larry I  v1.0E, intro says 1987) - * 1.005 (AGI Demo 2            1987-06-30?) - * 1.006 (King's Quest I        v1.0S 1988-02-23) - * 1.007 (Police Quest I        v2.0B 1988-04-21 8:00am) - * 1.013 (King's Quest II       v2.0A 1988-06-16 (CE)) - * 1.013 (Mixed-Up Mother Goose v2.0A 1988-05-31 10:00am) - * 1.014 (King's Quest III      v2.0A 1988-08-28 (CE)) - * 1.014 (Space Quest II        v2.0A, LOGIC.141 says 1988) - * 2.004 (Manhunter I           v2.0E 1988-10-05 (CE)) - * 2.006 (King's Quest IV       v1.0K 1988-11-22 (CE)) - * 3.001 (Black Cauldron        v1.0O 1989-02-24 (CE)) - * 3.003 (Gold Rush!            v1.0M 1989-02-28 (CE)) - */ -#if 0 -// FIXME: Identical to amigaAgiPaletteV2 -static const uint8 appleIIgsAgiPaletteV2[16 * 3] = { -	0x0, 0x0, 0x0, -	0x0, 0x0, 0xF, -	0x0, 0x8, 0x0, -	0x0, 0xD, 0xB, -	0xC, 0x0, 0x0, -	0xB, 0x7, 0xD, -	0x8, 0x5, 0x0, -	0xB, 0xB, 0xB, -	0x7, 0x7, 0x7, -	0x0, 0xB, 0xF, -	0x0, 0xE, 0x0, -	0x0, 0xF, 0xD, -	0xF, 0x9, 0x8, -	0xD, 0x9, 0xF, // Only this differs from the 1st generation palette -	0xE, 0xE, 0x0, -	0xF, 0xF, 0xF -}; -#endif +	_renderStartOffsetY = 0; +}  /** - * First generation Amiga & Apple IIGS AGI palette. - * A 16-color, 12-bit RGB palette. - * - * Used by at least the following Amiga AGI versions: - * 2.082 (King's Quest I   v1.0U 1986) - * 2.082 (Space Quest I    v1.2  1986) - * 2.090 (King's Quest III v1.01 1986-11-08) - * 2.107 (King's Quest II  v2.0J 1987-01-29) - * x.yyy (Black Cauldron   v2.00 1987-06-14) - * x.yyy (Larry I          v1.05 1987-06-26) + * Initialize graphics device.   * - * Also used by at least the following Apple IIGS AGI versions: - * 1.002 (Space Quest I, intro says v2.2 1987) + * @see deinit_video()   */ -static const uint8 amigaAgiPaletteV1[16 * 3] = { -	0x0, 0x0, 0x0, -	0x0, 0x0, 0xF, -	0x0, 0x8, 0x0, -	0x0, 0xD, 0xB, -	0xC, 0x0, 0x0, -	0xB, 0x7, 0xD, -	0x8, 0x5, 0x0, -	0xB, 0xB, 0xB, -	0x7, 0x7, 0x7, -	0x0, 0xB, 0xF, -	0x0, 0xE, 0x0, -	0x0, 0xF, 0xD, -	0xF, 0x9, 0x8, -	0xF, 0x7, 0x0, -	0xE, 0xE, 0x0, -	0xF, 0xF, 0xF -}; +int GfxMgr::initVideo() { +	// Set up palettes +	initPalette(_paletteTextMode, PALETTE_EGA); + +	switch (_vm->_renderMode) { +	case RENDERMODE_EGA: +		initPalette(_paletteGfxMode, PALETTE_EGA); +		break; +	case RENDERMODE_CGA: +		initPalette(_paletteGfxMode, PALETTE_CGA, 4, 8); +		break; +	case RENDERMODE_VGA: +		initPalette(_paletteGfxMode, PALETTE_VGA, 256, 8); +		break; +	case RENDERMODE_AMIGA: +		if (!ConfMan.getBool("altamigapalette")) { +			// Set the correct Amiga palette depending on AGI interpreter version +			if (_vm->getVersion() < 0x2936) +				initPalette(_paletteGfxMode, PALETTE_AMIGA_V1, 16, 4); +			else if (_vm->getVersion() == 0x2936) +				initPalette(_paletteGfxMode, PALETTE_AMIGA_V2, 16, 4); +			else if (_vm->getVersion() > 0x2936) +				initPalette(_paletteGfxMode, PALETTE_AMIGA_V3, 16, 4); +		} else { +			// Set the old common alternative Amiga palette +			initPalette(_paletteGfxMode, PALETTE_AMIGA_ALT); +		} +		break; +	case RENDERMODE_APPLE_II_GS: +		initPalette(_paletteGfxMode, PALETTE_APPLE_II_GS, 16, 4); +		break; +	case RENDERMODE_ATARI_ST: +		initPalette(_paletteGfxMode, PALETTE_ATARI_ST, 16, 3); +		break; +	default: +		error("initVideo: unsupported render mode"); +		break; +	} -/** - * Second generation Amiga AGI palette. - * A 16-color, 12-bit RGB palette. - * - * Used by at least the following Amiga AGI versions: - * 2.202 (Space Quest II v2.0F. Intro says 1988. ScummVM 0.10.0 detects as 1986-12-09) - */ -static const uint8 amigaAgiPaletteV2[16 * 3] = { -	0x0, 0x0, 0x0, -	0x0, 0x0, 0xF, -	0x0, 0x8, 0x0, -	0x0, 0xD, 0xB, -	0xC, 0x0, 0x0, -	0xB, 0x7, 0xD, -	0x8, 0x5, 0x0, -	0xB, 0xB, 0xB, -	0x7, 0x7, 0x7, -	0x0, 0xB, 0xF, -	0x0, 0xE, 0x0, -	0x0, 0xF, 0xD, -	0xF, 0x9, 0x8, -	0xD, 0x0, 0xF, -	0xE, 0xE, 0x0, -	0xF, 0xF, 0xF -}; +	// set up mouse cursors +	switch (_vm->_renderMode) { +	case RENDERMODE_EGA: +	case RENDERMODE_CGA: +	case RENDERMODE_VGA: +		initMouseCursor(&_mouseCursor, MOUSECURSOR_SCI, 11, 16, 1, 1); +		initMouseCursor(&_mouseCursorBusy, MOUSECURSOR_SCI_BUSY, 15, 16, 7, 8); +		break; +	case RENDERMODE_AMIGA: +		initMouseCursor(&_mouseCursor, MOUSECURSOR_AMIGA, 8, 11, 1, 1); +		initMouseCursor(&_mouseCursorBusy, MOUSECURSOR_AMIGA_BUSY, 13, 16, 7, 8); +		break; +	case RENDERMODE_APPLE_II_GS: +		// had no special busy mouse cursor +		initMouseCursor(&_mouseCursor, MOUSECURSOR_APPLE_II_GS, 9, 11, 1, 1); +		initMouseCursor(&_mouseCursorBusy, MOUSECURSOR_SCI_BUSY, 15, 16, 7, 8); +		break; +	case RENDERMODE_ATARI_ST: +		initMouseCursor(&_mouseCursor, MOUSECURSOR_ATARI_ST, 11, 16, 1, 1); +		initMouseCursor(&_mouseCursorBusy, MOUSECURSOR_SCI_BUSY, 15, 16, 7, 8); +		break; +	default: +		error("initVideo: unsupported render mode"); +		break; +	} -/** - * Third generation Amiga AGI palette. - * A 16-color, 12-bit RGB palette. - * - * Used by at least the following Amiga AGI versions: - * 2.310 (Police Quest I   v2.0B 1989-02-22) - * 2.316 (Gold Rush!       v2.05 1989-03-09) - * x.yyy (Manhunter I      v1.06 1989-03-18) - * 2.333 (Manhunter II     v3.06 1989-08-17) - * 2.333 (King's Quest III v2.15 1989-11-15) - */ -static const uint8 amigaAgiPaletteV3[16 * 3] = { -	0x0, 0x0, 0x0, -	0x0, 0x0, 0xB, -	0x0, 0xB, 0x0, -	0x0, 0xB, 0xB, -	0xB, 0x0, 0x0, -	0xB, 0x0, 0xB, -	0xC, 0x7, 0x0, -	0xB, 0xB, 0xB, -	0x7, 0x7, 0x7, -	0x0, 0x0, 0xF, -	0x0, 0xF, 0x0, -	0x0, 0xF, 0xF, -	0xF, 0x0, 0x0, -	0xF, 0x0, 0xF, -	0xF, 0xF, 0x0, -	0xF, 0xF, 0xF -}; +	_pixels = SCRIPT_WIDTH * SCRIPT_HEIGHT; +	_visualScreen = (byte *)calloc(_pixels, 1); +	_priorityScreen = (byte *)calloc(_pixels, 1); +	_activeScreen = _visualScreen; +	//_activeScreen = _priorityScreen; -/** - * 16 color amiga-ish palette. - */ -static const uint8 altAmigaPalette[16 * 3] = { -	0x00, 0x00, 0x00, -	0x00, 0x00, 0x3f, -	0x00, 0x2A, 0x00, -	0x00, 0x2A, 0x2A, -	0x33, 0x00, 0x00, -	0x2f, 0x1c, 0x37, -	0x23, 0x14, 0x00, -	0x2f, 0x2f, 0x2f, -	0x15, 0x15, 0x15, -	0x00, 0x2f, 0x3f, -	0x00, 0x33, 0x15, -	0x15, 0x3F, 0x3F, -	0x3f, 0x27, 0x23, -	0x3f, 0x15, 0x3f, -	0x3b, 0x3b, 0x00, -	0x3F, 0x3F, 0x3F -}; +	_displayPixels = DISPLAY_WIDTH * DISPLAY_HEIGHT; +	_displayScreen = (byte *)calloc(_displayPixels, 1); + +	initGraphics(DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_WIDTH > 320); + +	setPalette(true); // set gfx-mode palette + +	// set up mouse cursor palette +	CursorMan.replaceCursorPalette(MOUSECURSOR_PALETTE, 1, ARRAYSIZE(MOUSECURSOR_PALETTE) / 3); +	setMouseCursor(); + +	return errOK; +}  /** - * 256 color palette used with AGI256 and AGI256-2 games. - * Uses full 8 bits per color component. + * Deinitialize graphics device. + * + * @see init_video()   */ -static const uint8 vgaPalette[256 * 3] = { -	0x00, 0x00, 0x00, -	0x00, 0x00, 0xA8, -	0x00, 0xA8, 0x00, -	0x00, 0xA8, 0xA8, -	0xA8, 0x00, 0x00, -	0xA8, 0x00, 0xA8, -	0xA8, 0x54, 0x00, -	0xA8, 0xA8, 0xA8, -	0x54, 0x54, 0x54, -	0x54, 0x54, 0xFC, -	0x54, 0xFC, 0x54, -	0x54, 0xFC, 0xFC, -	0xFC, 0x54, 0x54, -	0xFC, 0x54, 0xFC, -	0xFC, 0xFC, 0x54, -	0xFC, 0xFC, 0xFC, -	0x00, 0x00, 0x00, -	0x14, 0x14, 0x14, -	0x20, 0x20, 0x20, -	0x2C, 0x2C, 0x2C, -	0x38, 0x38, 0x38, -	0x44, 0x44, 0x44, -	0x50, 0x50, 0x50, -	0x60, 0x60, 0x60, -	0x70, 0x70, 0x70, -	0x80, 0x80, 0x80, -	0x90, 0x90, 0x90, -	0xA0, 0xA0, 0xA0, -	0xB4, 0xB4, 0xB4, -	0xC8, 0xC8, 0xC8, -	0xE0, 0xE0, 0xE0, -	0xFC, 0xFC, 0xFC, -	0x00, 0x00, 0xFC, -	0x40, 0x00, 0xFC, -	0x7C, 0x00, 0xFC, -	0xBC, 0x00, 0xFC, -	0xFC, 0x00, 0xFC, -	0xFC, 0x00, 0xBC, -	0xFC, 0x00, 0x7C, -	0xFC, 0x00, 0x40, -	0xFC, 0x00, 0x00, -	0xFC, 0x40, 0x00, -	0xFC, 0x7C, 0x00, -	0xFC, 0xBC, 0x00, -	0xFC, 0xFC, 0x00, -	0xBC, 0xFC, 0x00, -	0x7C, 0xFC, 0x00, -	0x40, 0xFC, 0x00, -	0x00, 0xFC, 0x00, -	0x00, 0xFC, 0x40, -	0x00, 0xFC, 0x7C, -	0x00, 0xFC, 0xBC, -	0x00, 0xFC, 0xFC, -	0x00, 0xBC, 0xFC, -	0x00, 0x7C, 0xFC, -	0x00, 0x40, 0xFC, -	0x7C, 0x7C, 0xFC, -	0x9C, 0x7C, 0xFC, -	0xBC, 0x7C, 0xFC, -	0xDC, 0x7C, 0xFC, -	0xFC, 0x7C, 0xFC, -	0xFC, 0x7C, 0xDC, -	0xFC, 0x7C, 0xBC, -	0xFC, 0x7C, 0x9C, -	0xFC, 0x7C, 0x7C, -	0xFC, 0x9C, 0x7C, -	0xFC, 0xBC, 0x7C, -	0xFC, 0xDC, 0x7C, -	0xFC, 0xFC, 0x7C, -	0xDC, 0xFC, 0x7C, -	0xBC, 0xFC, 0x7C, -	0x9C, 0xFC, 0x7C, -	0x7C, 0xFC, 0x7C, -	0x7C, 0xFC, 0x9C, -	0x7C, 0xFC, 0xBC, -	0x7C, 0xFC, 0xDC, -	0x7C, 0xFC, 0xFC, -	0x7C, 0xDC, 0xFC, -	0x7C, 0xBC, 0xFC, -	0x7C, 0x9C, 0xFC, -	0xB4, 0xB4, 0xFC, -	0xC4, 0xB4, 0xFC, -	0xD8, 0xB4, 0xFC, -	0xE8, 0xB4, 0xFC, -	0xFC, 0xB4, 0xFC, -	0xFC, 0xB4, 0xE8, -	0xFC, 0xB4, 0xD8, -	0xFC, 0xB4, 0xC4, -	0xFC, 0xB4, 0xB4, -	0xFC, 0xC4, 0xB4, -	0xFC, 0xD8, 0xB4, -	0xFC, 0xE8, 0xB4, -	0xFC, 0xFC, 0xB4, -	0xE8, 0xFC, 0xB4, -	0xD8, 0xFC, 0xB4, -	0xC4, 0xFC, 0xB4, -	0xB4, 0xFC, 0xB4, -	0xB4, 0xFC, 0xC4, -	0xB4, 0xFC, 0xD8, -	0xB4, 0xFC, 0xE8, -	0xB4, 0xFC, 0xFC, -	0xB4, 0xE8, 0xFC, -	0xB4, 0xD8, 0xFC, -	0xB4, 0xC4, 0xFC, -	0x00, 0x00, 0x70, -	0x1C, 0x00, 0x70, -	0x38, 0x00, 0x70, -	0x54, 0x00, 0x70, -	0x70, 0x00, 0x70, -	0x70, 0x00, 0x54, -	0x70, 0x00, 0x38, -	0x70, 0x00, 0x1C, -	0x70, 0x00, 0x00, -	0x70, 0x1C, 0x00, -	0x70, 0x38, 0x00, -	0x70, 0x54, 0x00, -	0x70, 0x70, 0x00, -	0x54, 0x70, 0x00, -	0x38, 0x70, 0x00, -	0x1C, 0x70, 0x00, -	0x00, 0x70, 0x00, -	0x00, 0x70, 0x1C, -	0x00, 0x70, 0x38, -	0x00, 0x70, 0x54, -	0x00, 0x70, 0x70, -	0x00, 0x54, 0x70, -	0x00, 0x38, 0x70, -	0x00, 0x1C, 0x70, -	0x38, 0x38, 0x70, -	0x44, 0x38, 0x70, -	0x54, 0x38, 0x70, -	0x60, 0x38, 0x70, -	0x70, 0x38, 0x70, -	0x70, 0x38, 0x60, -	0x70, 0x38, 0x54, -	0x70, 0x38, 0x44, -	0x70, 0x38, 0x38, -	0x70, 0x44, 0x38, -	0x70, 0x54, 0x38, -	0x70, 0x60, 0x38, -	0x70, 0x70, 0x38, -	0x60, 0x70, 0x38, -	0x54, 0x70, 0x38, -	0x44, 0x70, 0x38, -	0x38, 0x70, 0x38, -	0x38, 0x70, 0x44, -	0x38, 0x70, 0x54, -	0x38, 0x70, 0x60, -	0x38, 0x70, 0x70, -	0x38, 0x60, 0x70, -	0x38, 0x54, 0x70, -	0x38, 0x44, 0x70, -	0x50, 0x50, 0x70, -	0x58, 0x50, 0x70, -	0x60, 0x50, 0x70, -	0x68, 0x50, 0x70, -	0x70, 0x50, 0x70, -	0x70, 0x50, 0x68, -	0x70, 0x50, 0x60, -	0x70, 0x50, 0x58, -	0x70, 0x50, 0x50, -	0x70, 0x58, 0x50, -	0x70, 0x60, 0x50, -	0x70, 0x68, 0x50, -	0x70, 0x70, 0x50, -	0x68, 0x70, 0x50, -	0x60, 0x70, 0x50, -	0x58, 0x70, 0x50, -	0x50, 0x70, 0x50, -	0x50, 0x70, 0x58, -	0x50, 0x70, 0x60, -	0x50, 0x70, 0x68, -	0x50, 0x70, 0x70, -	0x50, 0x68, 0x70, -	0x50, 0x60, 0x70, -	0x50, 0x58, 0x70, -	0x00, 0x00, 0x40, -	0x10, 0x00, 0x40, -	0x20, 0x00, 0x40, -	0x30, 0x00, 0x40, -	0x40, 0x00, 0x40, -	0x40, 0x00, 0x30, -	0x40, 0x00, 0x20, -	0x40, 0x00, 0x10, -	0x40, 0x00, 0x00, -	0x40, 0x10, 0x00, -	0x40, 0x20, 0x00, -	0x40, 0x30, 0x00, -	0x40, 0x40, 0x00, -	0x30, 0x40, 0x00, -	0x20, 0x40, 0x00, -	0x10, 0x40, 0x00, -	0x00, 0x40, 0x00, -	0x00, 0x40, 0x10, -	0x00, 0x40, 0x20, -	0x00, 0x40, 0x30, -	0x00, 0x40, 0x40, -	0x00, 0x30, 0x40, -	0x00, 0x20, 0x40, -	0x00, 0x10, 0x40, -	0x20, 0x20, 0x40, -	0x28, 0x20, 0x40, -	0x30, 0x20, 0x40, -	0x38, 0x20, 0x40, -	0x40, 0x20, 0x40, -	0x40, 0x20, 0x38, -	0x40, 0x20, 0x30, -	0x40, 0x20, 0x28, -	0x40, 0x20, 0x20, -	0x40, 0x28, 0x20, -	0x40, 0x30, 0x20, -	0x40, 0x38, 0x20, -	0x40, 0x40, 0x20, -	0x38, 0x40, 0x20, -	0x30, 0x40, 0x20, -	0x28, 0x40, 0x20, -	0x20, 0x40, 0x20, -	0x20, 0x40, 0x28, -	0x20, 0x40, 0x30, -	0x20, 0x40, 0x38, -	0x20, 0x40, 0x40, -	0x20, 0x38, 0x40, -	0x20, 0x30, 0x40, -	0x20, 0x28, 0x40, -	0x2C, 0x2C, 0x40, -	0x30, 0x2C, 0x40, -	0x34, 0x2C, 0x40, -	0x3C, 0x2C, 0x40, -	0x40, 0x2C, 0x40, -	0x40, 0x2C, 0x3C, -	0x40, 0x2C, 0x34, -	0x40, 0x2C, 0x30, -	0x40, 0x2C, 0x2C, -	0x40, 0x30, 0x2C, -	0x40, 0x34, 0x2C, -	0x40, 0x3C, 0x2C, -	0x40, 0x40, 0x2C, -	0x3C, 0x40, 0x2C, -	0x34, 0x40, 0x2C, -	0x30, 0x40, 0x2C, -	0x2C, 0x40, 0x2C, -	0x2C, 0x40, 0x30, -	0x2C, 0x40, 0x34, -	0x2C, 0x40, 0x3C, -	0x2C, 0x40, 0x40, -	0x2C, 0x3C, 0x40, -	0x2C, 0x34, 0x40, -	0x2C, 0x30, 0x40, -	0x40, 0x40, 0x40, -	0x38, 0x38, 0x38, -	0x30, 0x30, 0x30, -	0x28, 0x28, 0x28, -	0x24, 0x24, 0x24, -	0x1C, 0x1C, 0x1C, -	0x14, 0x14, 0x14, -	0x0C, 0x0C, 0x0C -}; +int GfxMgr::deinitVideo() { +	free(_displayScreen); +	free(_visualScreen); +	free(_priorityScreen); -static const uint16 cgaMap[16] = { -	0x0000,			//  0 - black -	0x0d00,			//  1 - blue -	0x0b00,			//  2 - green -	0x0f00,			//  3 - cyan -	0x000b,			//  4 - red -	0x0b0d,			//  5 - magenta -	0x000d,			//  6 - brown -	0x0b0b,			//  7 - gray -	0x0d0d,			//  8 - dark gray -	0x0b0f,			//  9 - light blue -	0x0b0d,			// 10 - light green -	0x0f0d,			// 11 - light cyan -	0x0f0d,			// 12 - light red -	0x0f00,			// 13 - light magenta -	0x0f0b,			// 14 - yellow -	0x0f0f			// 15 - white -}; +	return errOK; +} -struct UpdateBlock { -	int x1, y1; -	int x2, y2; -}; +int GfxMgr::initMachine() { +	_vm->_clockCount = 0; -static struct UpdateBlock update = { -	MAX_INT, MAX_INT, 0, 0 -}; +	return errOK; +} -GfxMgr::GfxMgr(AgiBase *vm) : _vm(vm) { -	_shakeH = NULL; -	_shakeV = NULL; -	_agipalFileNum = 0; -	_currentCursorPalette = 0;	// cursor palette not set +int GfxMgr::deinitMachine() { +	return errOK;  } +void GfxMgr::setRenderStartOffset(uint16 offsetY) { +	if (offsetY >= (DISPLAY_HEIGHT - SCRIPT_HEIGHT)) +		error("invalid render start offset"); -// -//  Layer 4:  640x480?  ==================  User display -//                              ^ -//                              |  do_update(), put_block() -//                              | -//  Layer 3:  640x480?  ==================  Framebuffer -//                              ^ -//                              |  flush_block(), put_pixels() -//                              | -//  Layer 2:  320x200   ==================  AGI engine screen (console), put_pixel() -//                              | -//  Layer 1:  160x336   ==================  AGI screen -// -//  Upper half (160x168) of Layer 1 is for the normal 16 color & control line/priority info. -//  Lower half (160x168) of Layer 1 is for the 256 color information (Used with AGI256 & AGI256-2). -// - -#define SHAKE_MAG 3 - -void GfxMgr::shakeStart() { -	int i; - -	if ((_shakeH = (uint8 *)malloc(GFX_WIDTH * SHAKE_MAG)) == NULL) -		return; +	_renderStartOffsetY = offsetY; +} -	if ((_shakeV = (uint8 *)malloc(SHAKE_MAG * (GFX_HEIGHT - SHAKE_MAG))) == NULL) { -		free(_shakeH); -		return; +void GfxMgr::debugShowMap(int mapNr) { +	switch (mapNr) { +	case 0: +		_activeScreen = _visualScreen; +		break; +	case 1: +		_activeScreen = _priorityScreen; +		break; +	default: +		break;  	} -	for (i = 0; i < GFX_HEIGHT - SHAKE_MAG; i++) { -		memcpy(_shakeV + i * SHAKE_MAG, _agiScreen + i * GFX_WIDTH, SHAKE_MAG); +	render_Block(0, 167, SCRIPT_WIDTH, SCRIPT_HEIGHT); +} + +void GfxMgr::clear(byte color, byte priority) { +	memset(_visualScreen, color, _pixels); +	memset(_priorityScreen, priority, _pixels); +} + +void GfxMgr::clearDisplay(byte color, bool copyToScreen) { +	memset(_displayScreen, color, _displayPixels); + +	if (copyToScreen) { +		g_system->copyRectToScreen(_displayScreen, DISPLAY_WIDTH, 0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT);  	} +} + +void GfxMgr::putPixel(int16 x, int16 y, byte drawMask, byte color, byte priority) { +	int offset = y * SCRIPT_WIDTH + x; -	for (i = 0; i < SHAKE_MAG; i++) { -		memcpy(_shakeH + i * GFX_WIDTH, _agiScreen + i * GFX_WIDTH, GFX_WIDTH); +	if (drawMask & GFX_SCREEN_MASK_VISUAL) { +		_visualScreen[offset] = color; +	} +	if (drawMask & GFX_SCREEN_MASK_PRIORITY) { +		_priorityScreen[offset] = priority;  	}  } -void GfxMgr::shakeScreen(int n) { -	int i; +void GfxMgr::putPixelOnDisplay(int16 x, int16 y, byte color) { +	int offset = y * DISPLAY_WIDTH + x; -	if (n == 0) { -		for (i = 0; i < (GFX_HEIGHT - SHAKE_MAG); i++) { -			memmove(&_agiScreen[GFX_WIDTH * i], -					&_agiScreen[GFX_WIDTH * (i + SHAKE_MAG) + SHAKE_MAG], -					GFX_WIDTH - SHAKE_MAG); -		} -	} else { -		for (i = GFX_HEIGHT - SHAKE_MAG - 1; i >= 0; i--) { -			memmove(&_agiScreen[GFX_WIDTH * (i + SHAKE_MAG) + SHAKE_MAG], -					&_agiScreen[GFX_WIDTH * i], GFX_WIDTH - SHAKE_MAG); +	_displayScreen[offset] = color; +} + +byte GfxMgr::getColor(int16 x, int16 y) { +	int offset = y * SCRIPT_WIDTH + x; + +	return _visualScreen[offset]; +} + +byte GfxMgr::getPriority(int16 x, int16 y) { +	int offset = y * SCRIPT_WIDTH + x; + +	return _priorityScreen[offset]; +} + +// used, when a control pixel is found +// will search downwards and compare priority in case any is found +bool GfxMgr::checkControlPixel(int16 x, int16 y, byte viewPriority) { +	int offset = y * SCRIPT_WIDTH + x; +	byte curPriority; + +	while (1) { +		y++; +		offset += SCRIPT_WIDTH; +		if (y >= SCRIPT_HEIGHT) { +			// end of screen, nothing but control pixels found +			return true; // draw view pixel  		} +		curPriority = _priorityScreen[offset]; +		if (curPriority > 2) // valid priority found? +			break; +	} +	if (curPriority <= viewPriority) +		return true; // view priority is higher, draw +	return false; // view priority is lower, don't draw +} + +static byte CGA_MixtureColorTable[] = { +	0x00, 0x08, 0x04, 0x0C, 0x01, 0x09, 0x02, 0x05, +	0x0A, 0x0D, 0x06, 0x0E, 0x0B, 0x03, 0x07, 0x0F +}; + +byte GfxMgr::getCGAMixtureColor(byte color) { +	return CGA_MixtureColorTable[color & 0x0F]; +} + +// Attention: y-coordinate points to the LOWER left! +void GfxMgr::render_Block(int16 x, int16 y, int16 width, int16 height, bool copyToScreen) { +	if (!render_Clip(x, y, width, height)) +		return; + +	switch (_vm->_renderMode) { +	case RENDERMODE_CGA: +		render_BlockCGA(x, y, width, height, copyToScreen); +		break; +	case RENDERMODE_EGA: +	default: +		render_BlockEGA(x, y, width, height, copyToScreen); +		break; +	} + +	if (copyToScreen) { +		int16 upperY = y - height + 1 + _renderStartOffsetY; +		g_system->copyRectToScreen(_displayScreen + upperY * DISPLAY_WIDTH + x * 2, DISPLAY_WIDTH, x * 2, upperY, width * 2, height);  	}  } -void GfxMgr::shakeEnd() { -	int i; +bool GfxMgr::render_Clip(int16 &x, int16 &y, int16 &width, int16 &height, int16 clipAgainstWidth, int16 clipAgainstHeight) { +	if ((x >= clipAgainstWidth) || ((x + width - 1) < 0) || +		(y < 0) || ((y - (height - 1)) >= clipAgainstHeight)) { +		return false; +	} + +	if ((y - height + 1) < 0) +		height = y + 1; -	for (i = 0; i < GFX_HEIGHT - SHAKE_MAG; i++) { -		memcpy(_agiScreen + i * GFX_WIDTH, _shakeV + i * SHAKE_MAG, SHAKE_MAG); +	if (y >= clipAgainstHeight) { +		height -= y - (clipAgainstHeight - 1); +		y = clipAgainstHeight - 1;  	} -	for (i = 0; i < SHAKE_MAG; i++) { -		memcpy(_agiScreen + i * GFX_WIDTH, _shakeH + i * GFX_WIDTH, GFX_WIDTH); +	if (x < 0) { +		width += x; +		x = 0;  	} -	flushBlock(0, 0, GFX_WIDTH - 1, GFX_HEIGHT - 1); +	if ((x + width - 1) >= clipAgainstWidth) { +		width = clipAgainstWidth - x; +	} +	return true; +} + +void GfxMgr::render_BlockEGA(int16 x, int16 y, int16 width, int16 height, bool copyToScreen) { +	int offsetVisual = SCRIPT_WIDTH * y + x; +	int offsetDisplay = (DISPLAY_WIDTH * (y + _renderStartOffsetY)) + x * 2; +	int16 remainingWidth = width; +	int16 remainingHeight = height; +	byte curColor = 0; + +	while (remainingHeight) { +		remainingWidth = width; +		while (remainingWidth) { +			curColor = _activeScreen[offsetVisual++]; +			_displayScreen[offsetDisplay++] = curColor; +			_displayScreen[offsetDisplay++] = curColor; +			remainingWidth--; +		} +		offsetVisual -= SCRIPT_WIDTH + width; +		offsetDisplay -= DISPLAY_WIDTH + width * 2; -	free(_shakeV); -	free(_shakeH); +		remainingHeight--; +	}  } -void GfxMgr::putTextCharacter(int l, int x, int y, unsigned char c, int fg, int bg, bool checkerboard, const uint8 *font) { -	int x1, y1, xx, yy, cc; -	const uint8 *p; +void GfxMgr::render_BlockCGA(int16 x, int16 y, int16 width, int16 height, bool copyToScreen) { +	int offsetVisual = SCRIPT_WIDTH * y + x; +	int offsetDisplay = (DISPLAY_WIDTH * (y + _renderStartOffsetY)) + x * 2; +	int16 remainingWidth = width; +	int16 remainingHeight = height; +	byte curColor = 0; + +	while (remainingHeight) { +		remainingWidth = width; +		while (remainingWidth) { +			curColor = _activeScreen[offsetVisual++]; +			_displayScreen[offsetDisplay++] = curColor & 0x03; // we process CGA mixture +			_displayScreen[offsetDisplay++] = curColor >> 2; +			remainingWidth--; +		} +		offsetVisual -= SCRIPT_WIDTH + width; +		offsetDisplay -= DISPLAY_WIDTH + width * 2; + +		remainingHeight--; +	} +} -	assert(font); +void GfxMgr::transition_Amiga() { +	uint16 screenPos = 1; +	uint16 screenStepPos = 1; +	int16  posY = 0, posX = 0; +	int16  stepCount = 0; + +	// disable mouse while transition is taking place +	if (_vm->_game.mouseEnabled) { +		CursorMan.showMouse(false); +	} + +	do { +		if (screenPos & 1) { +			screenPos = screenPos >> 1; +			screenPos = screenPos ^ 0x3500; // 13568d +		} else { +			screenPos = screenPos >> 1; +		} -	p = font + ((unsigned int)c * CHAR_LINES); -	for (y1 = 0; y1 < CHAR_LINES; y1++) { -		for (x1 = 0; x1 < CHAR_COLS; x1++) { -			xx = x + x1; -			yy = y + y1; -			cc = (*p & (1 << (7 - x1))) ? fg : bg; -			_agiScreen[xx + yy * GFX_WIDTH] = cc; +		if ((screenPos < 13440) && (screenPos & 1)) { +			screenStepPos = screenPos >> 1; +			posY = screenStepPos / SCRIPT_WIDTH; +			posX = screenStepPos - (posY * SCRIPT_WIDTH); +		 +			posY += _renderStartOffsetY; // adjust to only update the main area, not the status bar +			posX *= 2; // adjust for display screen + +			screenStepPos = (screenStepPos * 2) + (_renderStartOffsetY * DISPLAY_WIDTH); // adjust here too for display screen +			for (int16 multiPixel = 0; multiPixel < 4; multiPixel++) { +				g_system->copyRectToScreen(_displayScreen + screenStepPos, DISPLAY_WIDTH, posX, posY, 2, 1); +				screenStepPos += (0x1A40 * 2); // 6720d +				posY += 42; +			} +			stepCount++; +			if (stepCount == 220) { +				// 30 times for the whole transition, so should take around 0.5 seconds +				g_system->updateScreen(); +				g_system->delayMillis(16); +				stepCount = 0; +			}  		} +	} while (screenPos != 1); -		p++; +	// Enable mouse again +	if (_vm->_game.mouseEnabled) { +		CursorMan.showMouse(true);  	} -	// Simple checkerboard effect to simulate "greyed out" text. -	// This is what Sierra's interpreter does for things like menu items -	// that aren't selectable (such as separators). -- dsymonds -	if (checkerboard) { -		for (yy = y; yy < y + CHAR_LINES; yy++) -			for (xx = x + (~yy & 1); xx < x + CHAR_COLS; xx += 2) -				_agiScreen[xx + yy * GFX_WIDTH] = 15; +	g_system->updateScreen(); +} + +// This transition code was not reverse engineered, but created based on the Amiga transition code +// Atari ST definitely had a hi-res transition using the full resolution unlike the Amiga transition. +void GfxMgr::transition_AtariSt() { +	uint16 screenPos = 1; +	uint16 screenStepPos = 1; +	int16  posY = 0, posX = 0; +	int16  stepCount = 0; + +	// disable mouse while transition is taking place +	if (_vm->_game.mouseEnabled) { +		CursorMan.showMouse(false); +	} + +	do { +		if (screenPos & 1) { +			screenPos = screenPos >> 1; +			screenPos = screenPos ^ 0x3500; // 13568d +		} else { +			screenPos = screenPos >> 1; +		} + +		if ((screenPos < 13440) && (screenPos & 1)) { +			screenStepPos = screenPos >> 1; +			posY = screenStepPos / DISPLAY_WIDTH; +			posX = screenStepPos - (posY * DISPLAY_WIDTH); + +			posY += _renderStartOffsetY; // adjust to only update the main area, not the status bar + +			screenStepPos = screenStepPos + (_renderStartOffsetY * DISPLAY_WIDTH); // adjust here too for display screen +			for (int16 multiPixel = 0; multiPixel < 8; multiPixel++) { +				g_system->copyRectToScreen(_displayScreen + screenStepPos, DISPLAY_WIDTH, posX, posY, 1, 1); +				screenStepPos += 0x1a40; // 6720d +				posY += 21; +			} +			stepCount++; +			if (stepCount == 168) { +				// 40 times for the whole transition, so should take around 0.7 seconds +				// When using an Atari ST emulator, the transition seems to be even slower than this +				// TODO: should get checked on real hardware +				g_system->updateScreen(); +				g_system->delayMillis(16); +				stepCount = 0; +			} +		} +	} while (screenPos != 1); + +	// Enable mouse again +	if (_vm->_game.mouseEnabled) { +		CursorMan.showMouse(true);  	} -	// FIXME: we don't want this when we're writing on the -	//        console! -	flushBlock(x, y, x + CHAR_COLS - 1, y + CHAR_LINES - 1); +	g_system->updateScreen();  } -void GfxMgr::drawRectangle(int x1, int y1, int x2, int y2, int c) { -	int y, w, h; -	uint8 *p0; - -	if (x1 >= GFX_WIDTH) -		x1 = GFX_WIDTH - 1; -	if (y1 >= GFX_HEIGHT) -		y1 = GFX_HEIGHT - 1; -	if (x2 >= GFX_WIDTH) -		x2 = GFX_WIDTH - 1; -	if (y2 >= GFX_HEIGHT) -		y2 = GFX_HEIGHT - 1; - -	w = x2 - x1 + 1; -	h = y2 - y1 + 1; -	p0 = &_agiScreen[x1 + y1 * GFX_WIDTH]; -	for (y = 0; y < h; y++) { -		memset(p0, c, w); -		p0 += GFX_WIDTH; +// Attention: y coordinate is here supposed to be the upper one! +void GfxMgr::block_save(int16 x, int16 y, int16 width, int16 height, byte *bufferPtr) { +	int16 startOffset = y * SCRIPT_WIDTH + x; +	int16 offset = startOffset; +	int16 remainingHeight = height; +	byte *curBufferPtr = bufferPtr; + +	//warning("block_save: %d, %d -> %d, %d", x, y, width, height); + +	while (remainingHeight) { +		memcpy(curBufferPtr, _visualScreen + offset, width); +		offset += SCRIPT_WIDTH; +		curBufferPtr += width; +		remainingHeight--; +	} + +	remainingHeight = height; +	offset = startOffset; +	while (remainingHeight) { +		memcpy(curBufferPtr, _priorityScreen + offset, width); +		offset += SCRIPT_WIDTH; +		curBufferPtr += width; +		remainingHeight--;  	}  } -void GfxMgr::drawFrame(int x1, int y1, int x2, int y2, int c1, int c2) { -	int y, w; -	uint8 *p0; +// Attention: y coordinate is here supposed to be the upper one! +void GfxMgr::block_restore(int16 x, int16 y, int16 width, int16 height, byte *bufferPtr) { +	int16 startOffset = y * SCRIPT_WIDTH + x; +	int16 offset = startOffset; +	int16 remainingHeight = height; +	byte *curBufferPtr = bufferPtr; -	// top line -	w = x2 - x1 + 1; -	p0 = &_agiScreen[x1 + y1 * GFX_WIDTH]; -	memset(p0, c1, w); +	//warning("block_restore: %d, %d -> %d, %d", x, y, width, height); -	// bottom line -	p0 = &_agiScreen[x1 + y2 * GFX_WIDTH]; -	memset(p0, c2, w); +	while (remainingHeight) { +		memcpy(_visualScreen + offset, curBufferPtr, width); +		offset += SCRIPT_WIDTH; +		curBufferPtr += width; +		remainingHeight--; +	} -	// side lines -	for (y = y1; y <= y2; y++) { -		_agiScreen[x1 + y * GFX_WIDTH] = c1; -		_agiScreen[x2 + y * GFX_WIDTH] = c2; +	remainingHeight = height; +	offset = startOffset; +	while (remainingHeight) { +		memcpy(_priorityScreen + offset, curBufferPtr, width); +		offset += SCRIPT_WIDTH; +		curBufferPtr += width; +		remainingHeight--;  	}  } -void GfxMgr::drawBox(int x1, int y1, int x2, int y2, int color1, int color2, int m) { -	x1 += m; -	y1 += m; -	x2 -= m; -	y2 -= m; +// Attention: uses visual screen coordinates! +void GfxMgr::copyDisplayRectToScreen(int16 x, int16 y, int16 width, int16 height) { +	g_system->copyRectToScreen(_displayScreen + y * DISPLAY_WIDTH + x, DISPLAY_WIDTH, x, y, width, height); +} + +// coordinates are for visual screen, but are supposed to point somewhere inside the playscreen +void GfxMgr::drawBox(int16 x, int16 y, int16 width, int16 height, byte backgroundColor, byte lineColor) { +	drawRect(x, y, width, height, backgroundColor); +	drawRect(x + 1, y - 1, width - 2, 1, lineColor); +	drawRect(x + width - 2, y - 2, 1, height - 4, lineColor); +	drawRect(x + 1, y - height + 2, width - 2, 1, lineColor); +	drawRect(x + 1, y - 2, 1, height - 4, lineColor); +} + +// coordinates for visual screen +// attention: Clipping is done here against 160x200 instead of 160x168 +//            Original interpreter didn't do any clipping, we do it for security. +//            Clipping against the regular script width/height must not be done, +//            because at least during the intro one message box goes beyond playscreen +//            Going beyond 160x168 will result in messageboxes not getting fully removed +//            In KQ4's case, the scripts clear the screen that's why it works. +void GfxMgr::drawRect(int16 x, int16 y, int16 width, int16 height, byte color) { +	if (!render_Clip(x, y, width, height, SCRIPT_WIDTH, DISPLAY_HEIGHT - _renderStartOffsetY)) +		return; -	drawRectangle(x1, y1, x2, y2, color1); -	drawFrame(x1 + 2, y1 + 2, x2 - 2, y2 - 2, color2, color2); -	flushBlock(x1, y1, x2, y2); +	// coordinate translation: visual-screen -> display-screen +	x = x * 2; +	y = y + _renderStartOffsetY; // drawDisplayRect paints anywhere on the whole screen, our coordinate is within playscreen +	width = width * 2; // width was given as visual width, we need display width +	drawDisplayRect(x, y, width, height, color); +} + +// coordinates are directly for display screen +void GfxMgr::drawDisplayRect(int16 x, int16 y, int16 width, int16 height, byte color) { +	switch (_vm->_renderMode) { +	case RENDERMODE_CGA: +		drawDisplayRectCGA(x, y, width, height, color); +		break; +	case RENDERMODE_EGA: +	default: +		drawDisplayRectEGA(x, y, width, height, color); +		break; +	} +	int16 upperY = y - height + 1; +	g_system->copyRectToScreen(_displayScreen + upperY * DISPLAY_WIDTH + x, DISPLAY_WIDTH, x, upperY, width, height);  } -void GfxMgr::printCharacter(int x, int y, char c, int fg, int bg) { -	x *= CHAR_COLS; -	y *= CHAR_LINES; +void GfxMgr::drawDisplayRectEGA(int16 x, int16 y, int16 width, int16 height, byte color) { +	int offsetDisplay = (DISPLAY_WIDTH * y) + x; +	int16 remainingHeight = height; -	putTextCharacter(0, x, y, c, fg, bg, false, _vm->getFontData()); -	// redundant! already inside put_text_character! -	// flush_block (x, y, x + CHAR_COLS - 1, y + CHAR_LINES - 1); +	while (remainingHeight) { +		memset(_displayScreen + offsetDisplay, color, width); + +		offsetDisplay -= DISPLAY_WIDTH; +		remainingHeight--; +	}  } -/** - * Draw a default style button. - * Swaps background and foreground color if button is in focus or being pressed. - * @param x  x coordinate of the button - * @param y  y coordinate of the button - * @param a  set if the button has focus - * @param p  set if the button is pressed - * @param fgcolor foreground color of the button when it is neither in focus nor being pressed - * @param bgcolor background color of the button when it is neither in focus nor being pressed - */ -void GfxMgr::drawDefaultStyleButton(int x, int y, const char *s, int a, int p, int fgcolor, int bgcolor) { -	int textOffset     = _vm->_defaultButtonStyle.getTextOffset(a > 0, p > 0); -	AgiTextColor color = _vm->_defaultButtonStyle.getColor     (a > 0, p > 0, fgcolor, bgcolor); -	bool border        = _vm->_defaultButtonStyle.getBorder    (a > 0, p > 0); +void GfxMgr::drawDisplayRectCGA(int16 x, int16 y, int16 width, int16 height, byte color) { +	int offsetDisplay = (DISPLAY_WIDTH * y) + x; +	int16 remainingHeight = height; +	int16 remainingWidth = width; +	byte CGAMixtureColor = getCGAMixtureColor(color); +	byte *displayScreen = nullptr; + +	// we should never get an uneven width +	assert((width & 1) == 0); + +	while (remainingHeight) { +		remainingWidth = width; + +		// set up pointer +		displayScreen = _displayScreen + offsetDisplay; -	rawDrawButton(x, y, s, color.fg, color.bg, border, textOffset); +		while (remainingWidth) { +			*displayScreen++ = CGAMixtureColor & 0x03; +			*displayScreen++ = CGAMixtureColor >> 2; +			remainingWidth -= 2; +		} + +		offsetDisplay -= DISPLAY_WIDTH; +		remainingHeight--; +	}  } -/** - * Draw a button using the currently chosen style. - * Amiga-style is used for the Amiga-rendering mode, PC-style is used otherwise. - * @param x  x coordinate of the button - * @param y  y coordinate of the button - * @param hasFocus  set if the button has focus - * @param pressed  set if the button is pressed - * @param positive  set if button is positive, otherwise button is negative (Only matters with Amiga-style buttons) - * TODO: Make Amiga-style buttons a bit wider as they were in Amiga AGI games. - */ -void GfxMgr::drawCurrentStyleButton(int x, int y, const char *label, bool hasFocus, bool pressed, bool positive) { -	int textOffset     = _vm->_buttonStyle.getTextOffset(hasFocus, pressed); -	AgiTextColor color = _vm->_buttonStyle.getColor(hasFocus, pressed, positive); -	bool border        = _vm->_buttonStyle.getBorder(hasFocus, pressed); +// row + column are text-coordinates +void GfxMgr::drawCharacter(int16 row, int16 column, byte character, byte foreground, byte background, bool disabledLook) { +	int16       startX, startY; +	int16       curX, curY; +	const byte *fontData; +	byte        curByte = 0; +	uint16      curBit; +	byte        curTransformXOR = 0; +	byte        curTransformOR = 0; + +	startX = column * FONT_DISPLAY_HEIGHT; +	startY = row * FONT_DISPLAY_WIDTH; + +	// get font data of specified character +	fontData = _vm->getFontData() + character * FONT_BYTES_PER_CHARACTER; +	 +	// Now figure out, if special handling needs to be done (for graphical mode only) +	if (_vm->_game.gfxMode) { +		if (background & 0x08) { +			// invert enabled +			background &= 0x07; // remove invert bit +			curTransformXOR = 0xFF; // inverse all bits of the font +		} +		if (disabledLook) { +			curTransformOR = 0xAA; +		} +	} + +	curBit = 0; +	for (curY = 0; curY < FONT_DISPLAY_HEIGHT; curY++) { +		for (curX = 0; curX < FONT_DISPLAY_WIDTH; curX++) { +			if (!curBit) { +				curByte = *fontData; +				// do transformations in case they are needed (invert/disabled look) +				curByte ^= curTransformXOR; +				curByte |= curTransformOR; +				fontData++; +				curBit  = 0x80; +			} +			if (curByte & curBit) { +				putPixelOnDisplay(startX + curX, startY + curY, foreground); +			} else { +				putPixelOnDisplay(startX + curX, startY + curY, background); +			} +			curBit = curBit >> 1; +		} +	} -	rawDrawButton(x, y, label, color.fg, color.bg, border, textOffset); +	copyDisplayRectToScreen(startX, startY, FONT_DISPLAY_WIDTH, FONT_DISPLAY_HEIGHT);  } -void GfxMgr::rawDrawButton(int x, int y, const char *s, int fgcolor, int bgcolor, bool border, int textOffset) { -	int len = strlen(s); -	int x1, y1, x2, y2; +#define SHAKE_VERTICAL_PIXELS 4 +#define SHAKE_HORIZONTAL_PIXELS 8 -	x1 = x - 3; -	y1 = y - 3; -	x2 = x + CHAR_COLS * len + 2; -	y2 = y + CHAR_LINES + 2; +// Sierra used some EGA port trickery to do it, we have to do it by copying pixels around +void GfxMgr::shakeScreen(int16 repeatCount) { +	int shakeNr, shakeCount; +	uint8 *blackSpace; -	// Draw a filled rectangle that's larger than the button. Used for drawing -	// a border around the button as the button itself is drawn after this. -	drawRectangle(x1, y1, x2, y2, border ? BUTTON_BORDER : MSG_BOX_COLOR); +	if ((blackSpace = (uint8 *)calloc(SHAKE_HORIZONTAL_PIXELS * DISPLAY_WIDTH, 1)) == NULL) +		return; -	while (*s) { -		putTextCharacter(0, x + textOffset, y + textOffset, *s++, fgcolor, bgcolor, false, _vm->getFontData()); -		x += CHAR_COLS; +	shakeCount = repeatCount * 8; // effectively 4 shakes per repeat + +	// it's 4 pixels down and 8 pixels to the right +	// and it's also filling the remaining space with black +	for (shakeNr = 0; shakeNr < shakeCount; shakeNr++) { +		if (shakeNr & 1) { +			// move back +			copyDisplayRectToScreen(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT); +		} else { +			g_system->copyRectToScreen(_displayScreen, DISPLAY_WIDTH, SHAKE_HORIZONTAL_PIXELS, SHAKE_VERTICAL_PIXELS, DISPLAY_WIDTH - SHAKE_HORIZONTAL_PIXELS, DISPLAY_HEIGHT - SHAKE_VERTICAL_PIXELS); +			// additionally fill the remaining space with black +			g_system->copyRectToScreen(blackSpace, DISPLAY_WIDTH, 0, 0, DISPLAY_WIDTH, SHAKE_VERTICAL_PIXELS); +			g_system->copyRectToScreen(blackSpace, SHAKE_HORIZONTAL_PIXELS, 0, 0, SHAKE_HORIZONTAL_PIXELS, DISPLAY_HEIGHT); +		} +		g_system->updateScreen(); +		g_system->delayMillis(66); // Sierra waited for 4 V'Syncs, which is around 66 milliseconds  	} -	x1 -= 2; -	y1 -= 2; -	x2 += 2; -	y2 += 2; +	free(blackSpace); +} -	flushBlock(x1, y1, x2, y2); +void GfxMgr::updateScreen() { +	g_system->updateScreen();  } -int GfxMgr::testButton(int x, int y, const char *s) { -	int len = strlen(s); -	Common::Rect rect(x - 3, y - 3, x + CHAR_COLS * len + 3, y + CHAR_LINES + 3); -	return rect.contains(_vm->_mouse.x, _vm->_mouse.y); +void GfxMgr::initPriorityTable() { +	int16 priority, step; +	int16 yPos = 0; + +	_priorityTableSet = false; +	for (priority = 1; priority < 15; priority++) { +		for (step = 0; step < 12; step++) { +			_priorityTable[yPos++] = priority < 4 ? 4 : priority; +		} +	}  } -void GfxMgr::putBlock(int x1, int y1, int x2, int y2) { -	gfxPutBlock(x1, y1, x2, y2); +void GfxMgr::setPriorityTable(int16 priorityBase) { +	int16 x, priorityY, priority; + +	_priorityTableSet = true; +	x = (SCRIPT_HEIGHT - priorityBase) * SCRIPT_HEIGHT / 10; + +	for (priorityY = 0; priorityY < SCRIPT_HEIGHT; priorityY++) { +		priority = (priorityY - priorityBase) < 0 ? 4 : (priorityY - priorityBase) * SCRIPT_HEIGHT / x + 5; +		if (priority > 15) +			priority = 15; +		_priorityTable[priorityY] = priority; +	}  } -void GfxMgr::putScreen() { -	putBlock(0, 0, GFX_WIDTH - 1, GFX_HEIGHT - 1); +// used for restoring +void GfxMgr::setPriority(int16 yPos, int16 priority) { +	assert(yPos < SCRIPT_HEIGHT); +	_priorityTable[yPos] = priority;  } -/* - * Public functions +/** + * Convert sprite priority to y value.   */ +int16 GfxMgr::priorityToY(int16 priority) { +	int16 currentY; + +	if (!_priorityTableSet) { +		// priority table wasn't set by scripts? calculate directly +		return (priority - 5) * 12 + 48; +	} + +	// dynamic priority bands were introduced in 3.002.086 (effectively AGI3) +	// It seems there was a glitch, that caused priority bands to not get calculated properly. +	// It was caused by this function starting with Y = 168 instead of 167, which meant it always +	// returned with 168 as result. +	// This glitch is required in King's Quest 4 2.0, otherwise in room 54 ego will get drawn over +	//  the last dwarf, that enters the house. +	//  Dwarf is screen object 13 (view 152), gets fixed priority of 8, which would normally +	//  result in a Y of 101. Ego is priority (non-fixed) 8, which would mean that dwarf is +	//  drawn first, followed by ego, which would then draw ego over the dwarf. +	//  For more information see bug #1712585 (dwarf sprite priority) +	// +	// Priority bands were working properly in: 3.001.098 (Black Cauldron) +	uint16 agiVersion = _vm->getVersion(); +	 +	if (agiVersion <= 0x3086) { +		return 168; // Buggy behavior, see above +	} + +	currentY = 167; +	while (_priorityTable[currentY] >= priority) { +		currentY--; +		if (currentY < 0) // Original AGI didn't do this, we abort in that case and return -1 +			break; +	} +	return currentY; +} + +int16 GfxMgr::priorityFromY(int16 yPos) { +	assert(yPos < SCRIPT_HEIGHT); +	return _priorityTable[yPos]; +} +  /**   * Initialize the color palette @@ -800,18 +766,22 @@ void GfxMgr::putScreen() {   * @param fromBits    Bits per source color component.   * @param toBits      Bits per destination color component.   */ -void GfxMgr::initPalette(const uint8 *p, uint colorCount, uint fromBits, uint toBits) { +void GfxMgr::initPalette(uint8 *destPalette, const uint8 *paletteData, uint colorCount, uint fromBits, uint toBits) {  	const uint srcMax  = (1 << fromBits) - 1;  	const uint destMax = (1 << toBits) - 1;  	for (uint col = 0; col < colorCount; col++) {  		for (uint comp = 0; comp < 3; comp++) { // Convert RGB components -			_palette[col * 3 + comp] = (p[col * 3 + comp] * destMax) / srcMax; +			destPalette[col * 3 + comp] = (paletteData[col * 3 + comp] * destMax) / srcMax;  		}  	}  } -void GfxMgr::gfxSetPalette() { -	g_system->getPaletteManager()->setPalette(_palette, 0, 256); +void GfxMgr::setPalette(bool gfxModePalette) { +	if (gfxModePalette) { +		g_system->getPaletteManager()->setPalette(_paletteGfxMode, 0, 256); +	} else { +		g_system->getPaletteManager()->setPalette(_paletteTextMode, 0, 256); +	}  }  //Gets AGIPAL Data @@ -863,8 +833,8 @@ void GfxMgr::setAGIPal(int p0) {  	_agipalFileNum = p0; -	initPalette(_agipalPalette); -	gfxSetPalette(); +	initPalette(_paletteGfxMode, _agipalPalette); +	setPalette(true); // set gfx-mode palette  	debug(1, "Using AGIPAL palette from '%s'", filename);  } @@ -873,137 +843,33 @@ int GfxMgr::getAGIPalFileNum() {  	return _agipalFileNum;  } -// put a block onto the screen -void GfxMgr::gfxPutBlock(int x1, int y1, int x2, int y2) { -	if (x1 >= GFX_WIDTH) -		x1 = GFX_WIDTH - 1; -	if (y1 >= GFX_HEIGHT) -		y1 = GFX_HEIGHT - 1; -	if (x2 >= GFX_WIDTH) -		x2 = GFX_WIDTH - 1; -	if (y2 >= GFX_HEIGHT) -		y2 = GFX_HEIGHT - 1; - -	g_system->copyRectToScreen(_screen + y1 * 320 + x1, 320, x1, y1, x2 - x1 + 1, y2 - y1 + 1); +void GfxMgr::initMouseCursor(MouseCursorData *mouseCursor, const byte *bitmapData, uint16 width, uint16 height, int hotspotX, int hotspotY) { +	mouseCursor->bitmapData = bitmapData; +	mouseCursor->width = width; +	mouseCursor->height = height; +	mouseCursor->hotspotX = hotspotX; +	mouseCursor->hotspotY = hotspotY;  } -/** - * A black and white SCI-style arrow cursor (11x16). - * 0 = Transparent. - * 1 = Black (#000000 in 24-bit RGB). - * 2 = White (#FFFFFF in 24-bit RGB). - */ -static const byte sciMouseCursor[] = { -	1,1,0,0,0,0,0,0,0,0,0, -	1,2,1,0,0,0,0,0,0,0,0, -	1,2,2,1,0,0,0,0,0,0,0, -	1,2,2,2,1,0,0,0,0,0,0, -	1,2,2,2,2,1,0,0,0,0,0, -	1,2,2,2,2,2,1,0,0,0,0, -	1,2,2,2,2,2,2,1,0,0,0, -	1,2,2,2,2,2,2,2,1,0,0, -	1,2,2,2,2,2,2,2,2,1,0, -	1,2,2,2,2,2,2,2,2,2,1, -	1,2,2,2,2,2,1,0,0,0,0, -	1,2,1,0,1,2,2,1,0,0,0, -	1,1,0,0,1,2,2,1,0,0,0, -	0,0,0,0,0,1,2,2,1,0,0, -	0,0,0,0,0,1,2,2,1,0,0, -	0,0,0,0,0,0,1,2,2,1,0 -}; - -#if 0 -/** - * A black and white Apple IIGS style arrow cursor (9x11). - * 0 = Transparent. - * 1 = Black (#000000 in 24-bit RGB). - * 2 = White (#FFFFFF in 24-bit RGB). - */ -static const byte appleIIgsMouseCursor[] = { -	2,2,0,0,0,0,0,0,0, -	2,1,2,0,0,0,0,0,0, -	2,1,1,2,0,0,0,0,0, -	2,1,1,1,2,0,0,0,0, -	2,1,1,1,1,2,0,0,0, -	2,1,1,1,1,1,2,0,0, -	2,1,1,1,1,1,1,2,0, -	2,1,1,1,1,1,1,1,2, -	2,1,1,2,1,1,2,2,0, -	2,2,2,0,2,1,1,2,0, -	0,0,0,0,0,2,2,2,0 -}; -#endif - -/** - * RGB-palette for the black and white SCI and Apple IIGS arrow cursors. - */ -static const byte sciMouseCursorPalette[] = { -	0x00, 0x00, 0x00, // Black -	0xFF, 0xFF, 0xFF  // White -}; - -/** - * An Amiga-style arrow cursor (8x11). - * 0 = Transparent. - * 1 = Black     (#000000 in 24-bit RGB). - * 2 = Red       (#DE2021 in 24-bit RGB). - * 3 = Light red (#FFCFAD in 24-bit RGB). - */ -static const byte amigaMouseCursor[] = { -	2,3,1,0,0,0,0,0, -	2,2,3,1,0,0,0,0, -	2,2,2,3,1,0,0,0, -	2,2,2,2,3,1,0,0, -	2,2,2,2,2,3,1,0, -	2,2,2,2,2,2,3,1, -	2,0,2,2,3,1,0,0, -	0,0,0,2,3,1,0,0, -	0,0,0,2,2,3,1,0, -	0,0,0,0,2,3,1,0, -	0,0,0,0,2,2,3,1 -}; +void GfxMgr::setMouseCursor(bool busy) { +	MouseCursorData *mouseCursor = nullptr; -/** - * RGB-palette for the Amiga-style arrow cursor - * and the Amiga-style busy cursor. - */ -static const byte amigaMouseCursorPalette[] = { -	0x00, 0x00, 0x00, // Black -	0xDE, 0x20, 0x21, // Red -	0xFF, 0xCF, 0xAD  // Light red -}; +	if (!busy) { +		mouseCursor = &_mouseCursor; +	} else { +		mouseCursor = &_mouseCursorBusy; +	} -/** - * An Amiga-style busy cursor showing an hourglass (13x16). - * 0 = Transparent. - * 1 = Black     (#000000 in 24-bit RGB). - * 2 = Red       (#DE2021 in 24-bit RGB). - * 3 = Light red (#FFCFAD in 24-bit RGB). - */ -static const byte busyAmigaMouseCursor[] = { -	1,1,1,1,1,1,1,1,1,1,1,1,1, -	1,2,2,2,2,2,2,2,2,2,2,2,1, -	1,2,2,2,2,2,2,2,2,2,2,2,1, -	0,1,3,3,3,3,3,3,3,3,3,1,0, -	0,0,1,3,3,3,3,3,3,3,1,0,0, -	0,0,0,1,3,3,3,3,3,1,0,0,0, -	0,0,0,0,1,3,3,3,1,0,0,0,0, -	0,0,0,0,0,1,3,1,0,0,0,0,0, -	0,0,0,0,0,1,3,1,0,0,0,0,0, -	0,0,0,0,1,2,3,2,1,0,0,0,0, -	0,0,0,1,2,2,3,2,2,1,0,0,0, -	0,0,1,2,2,2,3,2,2,2,1,0,0, -	0,1,2,2,2,3,3,3,2,2,2,1,0, -	1,3,3,3,3,3,3,3,3,3,3,3,1, -	1,3,3,3,3,3,3,3,3,3,3,3,1, -	1,1,1,1,1,1,1,1,1,1,1,1,1 -}; +	if (mouseCursor) { +		CursorMan.replaceCursor(mouseCursor->bitmapData, mouseCursor->width, mouseCursor->height, mouseCursor->hotspotX, mouseCursor->hotspotY, 0); +	} +} +#if 0  void GfxMgr::setCursor(bool amigaStyleCursor, bool busy) {  	if (busy) { -		CursorMan.replaceCursorPalette(amigaMouseCursorPalette, 1, ARRAYSIZE(amigaMouseCursorPalette) / 3); -		CursorMan.replaceCursor(busyAmigaMouseCursor, 13, 16, 7, 8, 0); - +		CursorMan.replaceCursorPalette(MOUSECURSOR_AMIGA_PALETTE, 1, ARRAYSIZE(MOUSECURSOR_AMIGA_PALETTE) / 3); +		CursorMan.replaceCursor(MOUSECURSOR_AMIGA_BUSY, 13, 16, 7, 8, 0);  		return;  	} @@ -1029,241 +895,6 @@ void GfxMgr::setCursorPalette(bool amigaStyleCursor) {  		}  	}  } - -/** - * Initialize graphics device. - * - * @see deinit_video() - */ -int GfxMgr::initVideo() { -	if (_vm->getFeatures() & (GF_AGI256 | GF_AGI256_2)) -		initPalette(vgaPalette, 256, 8); -	else if (_vm->_renderMode == Common::kRenderEGA) -		initPalette(egaPalette); -	else if (_vm->_renderMode == Common::kRenderAmiga) { -		if (!ConfMan.getBool("altamigapalette")) { -			// Set the correct Amiga palette -			if (_vm->getVersion() < 0x2936) -				// TODO: This palette isn't used for Apple IIGS games yet, as -				// we don't set a separate render mode for them yet -				initPalette(amigaAgiPaletteV1, 16, 4); -			else if (_vm->getVersion() == 0x2936) -				initPalette(amigaAgiPaletteV2, 16, 4); -			else if (_vm->getVersion() > 0x2936) -				initPalette(amigaAgiPaletteV3, 16, 4); -		} else -			// Set the old common alternative Amiga palette -			initPalette(altAmigaPalette); -	} else -		error("initVideo: Unhandled render mode \"%s\"", Common::getRenderModeDescription(_vm->_renderMode)); - -	if ((_agiScreen = (uint8 *)calloc(GFX_WIDTH, GFX_HEIGHT)) == NULL) -		return errNotEnoughMemory; - -	gfxSetPalette(); - -	setCursor(_vm->_renderMode == Common::kRenderAmiga); - -	return errOK; -} - -/** - * Deinitialize graphics device. - * - * @see init_video() - */ -int GfxMgr::deinitVideo() { -	free(_agiScreen); - -	return errOK; -} - -int GfxMgr::initMachine() { -	_screen = (unsigned char *)malloc(320 * 200); -	_vm->_clockCount = 0; - -	return errOK; -} - -int GfxMgr::deinitMachine() { -	free(_screen); - -	return errOK; -} - -/** - * Write pixels on the output device. - * This function writes a row of pixels on the output device. Only the - * lower 4 bits of each pixel in the row will be used, making this - * function suitable for use with rows from the AGI screen. - * @param x x coordinate of the row start (AGI coord.) - * @param y y coordinate of the row start (AGI coord.) - * @param n number of pixels in the row - * @param p pointer to the row start in the AGI screen (Always use sbuf16c as base, not sbuf256c) - * FIXME: CGA rendering doesn't work correctly with AGI256 or AGI256-2. - */ -void GfxMgr::putPixelsA(int x, int y, int n, uint8 *p) { -	const uint rShift = _vm->_debug.priority ? 4 : 0; // Priority information is in the top 4 bits of a byte taken from sbuf16c. - -	// Choose the correct screen to read from. If AGI256 or AGI256-2 is used and we're not trying to show the priority information, -	// then choose the 256 color screen, otherwise choose the 16 color screen (Which also has the priority information). -	p += ((_vm->getFeatures() & (GF_AGI256 | GF_AGI256_2)) && !_vm->_debug.priority) ? FROM_SBUF16_TO_SBUF256_OFFSET : 0; - -	if (_vm->_renderMode == Common::kRenderCGA) { -		for (x *= 2; n--; p++, x += 2) { -			register uint16 q = (cgaMap[(*p & 0xf0) >> 4] << 4) | cgaMap[*p & 0x0f]; -			*(uint16 *)&_agiScreen[x + y * GFX_WIDTH] = (q >> rShift) & 0x0f0f; -		} -	} else { -		const uint16 mask = ((_vm->getFeatures() & (GF_AGI256 | GF_AGI256_2)) && !_vm->_debug.priority) ? 0xffff : 0x0f0f; -		for (x *= 2; n--; p++, x += 2) { -			register uint16 q = ((uint16)*p << 8) | *p; -			*(uint16 *)&_agiScreen[x + y * GFX_WIDTH] = (q >> rShift) & mask; -		} -	} -} - -/** - * Schedule blocks for blitting on the output device. - * This function gets the coordinates of a block in the AGI screen and - * schedule it to be updated in the output device. - * @param x1 x coordinate of the upper left corner of the block (AGI coord.) - * @param y1 y coordinate of the upper left corner of the block (AGI coord.) - * @param x2 x coordinate of the lower right corner of the block (AGI coord.) - * @param y2 y coordinate of the lower right corner of the block (AGI coord.) - * - * @see do_update() - */ -void GfxMgr::scheduleUpdate(int x1, int y1, int x2, int y2) { -	if (x1 < update.x1) -		update.x1 = x1; -	if (y1 < update.y1) -		update.y1 = y1; -	if (x2 > update.x2) -		update.x2 = x2; -	if (y2 > update.y2) -		update.y2 = y2; -} - -/** - * Update scheduled blocks on the output device. - * This function exposes the blocks scheduled for updating to the output - * device. Blocks can be scheduled at any point of the AGI cycle. - * - * @see schedule_update() - */ -void GfxMgr::doUpdate() { -	if (update.x1 <= update.x2 && update.y1 <= update.y2) { -		gfxPutBlock(update.x1, update.y1, update.x2, update.y2); -	} - -	// reset update block variables -	update.x1 = MAX_INT; -	update.y1 = MAX_INT; -	update.x2 = 0; -	update.y2 = 0; - -	g_system->updateScreen(); -} - -/** - * Updates a block of the framebuffer with contents of the AGI engine screen. - * This function updates a block in the output device with the contents of - * the AGI engine screen, handling console transparency. - * @param x1 x coordinate of the upper left corner of the block - * @param y1 y coordinate of the upper left corner of the block - * @param x2 x coordinate of the lower right corner of the block - * @param y2 y coordinate of the lower right corner of the block - * - * @see flush_block_a() - */ -void GfxMgr::flushBlock(int x1, int y1, int x2, int y2) { -	int y, w; -	uint8 *p0; - -	scheduleUpdate(x1, y1, x2, y2); - -	p0 = &_agiScreen[x1 + y1 * GFX_WIDTH]; -	w = x2 - x1 + 1; - -	for (y = y1; y <= y2; y++) { -		memcpy(_screen + 320 * y + x1, p0, w); -		p0 += GFX_WIDTH; -	} -} - -/** - * Updates a block of the framebuffer receiving AGI picture coordinates. - * @param x1 x AGI picture coordinate of the upper left corner of the block - * @param y1 y AGI picture coordinate of the upper left corner of the block - * @param x2 x AGI picture coordinate of the lower right corner of the block - * @param y2 y AGI picture coordinate of the lower right corner of the block - * - * @see flush_block() - */ -void GfxMgr::flushBlockA(int x1, int y1, int x2, int y2) { -	//y1 += 8; -	//y2 += 8; -	flushBlock(DEV_X0(x1), DEV_Y(y1), DEV_X1(x2), DEV_Y(y2)); -} - -/** - * Updates the framebuffer with contents of the AGI engine screen (console-aware). - * This function updates the output device with the contents of the AGI - * screen, handling console transparency. - */ -void GfxMgr::flushScreen() { -	flushBlock(0, 0, GFX_WIDTH - 1, GFX_HEIGHT - 1); - -	doUpdate(); -} - -/** - * Clear the output device screen (console-aware). - * This function clears the output device screen and updates the - * output device. Contents of the AGI screen are left untouched. This - * function can be used to simulate a switch to a text mode screen in - * a graphic-only device. - * @param c  color to clear the screen - */ -void GfxMgr::clearScreen(int c) { -	memset(_agiScreen, c, GFX_WIDTH * GFX_HEIGHT); -	flushScreen(); -} - -/** - * Save a block of the AGI engine screen - */ -void GfxMgr::saveBlock(int x1, int y1, int x2, int y2, uint8 *b) { -	uint8 *p0; -	int w, h; - -	p0 = &_agiScreen[x1 + GFX_WIDTH * y1]; -	w = x2 - x1 + 1; -	h = y2 - y1 + 1; -	while (h--) { -		memcpy(b, p0, w); -		b += w; -		p0 += GFX_WIDTH; -	} -} - -/** - * Restore a block of the AGI engine screen - */ -void GfxMgr::restoreBlock(int x1, int y1, int x2, int y2, uint8 *b) { -	uint8 *p0; -	int w, h; - -	p0 = &_agiScreen[x1 + GFX_WIDTH * y1]; -	w = x2 - x1 + 1; -	h = y2 - y1 + 1; -	while (h--) { -		memcpy(p0, b, w); -		b += w; -		p0 += GFX_WIDTH; -	} -	flushBlock(x1, y1, x2, y2); -} +#endif  } // End of namespace Agi diff --git a/engines/agi/graphics.h b/engines/agi/graphics.h index 506a9d93d6..a89a7fdabc 100644 --- a/engines/agi/graphics.h +++ b/engines/agi/graphics.h @@ -27,6 +27,11 @@  namespace Agi { +#define SCRIPT_WIDTH	160 +#define SCRIPT_HEIGHT	168 +#define DISPLAY_WIDTH	320 +#define DISPLAY_HEIGHT	200 +  #define GFX_WIDTH	320  #define GFX_HEIGHT	200  #define CHAR_COLS	8 @@ -34,66 +39,115 @@ namespace Agi {  class AgiEngine; +enum GfxScreenMasks { +	GFX_SCREEN_MASK_VISUAL		= 1, +	GFX_SCREEN_MASK_PRIORITY	= 2, +	GFX_SCREEN_MASK_ALL			= GFX_SCREEN_MASK_VISUAL|GFX_SCREEN_MASK_PRIORITY +}; + +struct MouseCursorData { +	const byte *bitmapData; +	uint16 width; +	uint16 height; +	int hotspotX; +	int hotspotY; +}; +  class GfxMgr {  private:  	AgiBase *_vm; -	uint8 _palette[256 * 3]; -	uint8 *_agiScreen; -	unsigned char *_screen; - -	uint8 *_shakeH, *_shakeV; +	uint8 _paletteGfxMode[256 * 3]; +	uint8 _paletteTextMode[256 * 3];  	uint8 _agipalPalette[16 * 3];  	int _agipalFileNum; -	int _currentCursorPalette;	// 0 - palette not set, 1 - PC, 2 - Amiga - -private: -	void rawDrawButton(int x, int y, const char *s, int fgcolor, int bgcolor, bool border, int textOffset);  public:  	GfxMgr(AgiBase *vm); -	void gfxPutBlock(int x1, int y1, int x2, int y2); - -	void putTextCharacter(int, int, int, unsigned char, int, int, bool checkerboard = false, const uint8 *font = fontData_Sierra); -	void shakeScreen(int); -	void shakeStart(); -	void shakeEnd(); -	void saveScreen(); -	void restoreScreen(); -  	int initVideo();  	int deinitVideo(); -	void scheduleUpdate(int, int, int, int); -	void doUpdate(); -	void putScreen(); -	void flushBlock(int, int, int, int); -	void flushBlockA(int, int, int, int); -	void putPixelsA(int, int, int, uint8 *); -	void flushScreen(); -	void clearScreen(int); -	void clearConsoleScreen(int); -	void drawBox(int, int, int, int, int, int, int); -	void drawDefaultStyleButton(int, int, const char *, int, int, int fgcolor = 0, int bgcolor = 0); -	void drawCurrentStyleButton(int x, int y, const char *label, bool hasFocus, bool pressed = false, bool positive = true); -	int testButton(int, int, const char *); -	void drawRectangle(int, int, int, int, int); -	void saveBlock(int, int, int, int, uint8 *); -	void restoreBlock(int, int, int, int, uint8 *); -	void initPalette(const uint8 *p, uint colorCount = 16, uint fromBits = 6, uint toBits = 8); +	void initPalette(uint8 *destPalette, const uint8 *paletteData, uint colorCount = 16, uint fromBits = 6, uint toBits = 8);  	void setAGIPal(int);  	int getAGIPalFileNum(); -	void drawFrame(int x1, int y1, int x2, int y2, int c1, int c2); +	void setPalette(bool GfxModePalette); -	void putBlock(int x1, int y1, int x2, int y2); -	void gfxSetPalette(); -	void setCursor(bool amigaStyleCursor = false, bool busy = false); -	void setCursorPalette(bool amigaStylePalette = false); +	void initMouseCursor(MouseCursorData *mouseCursor, const byte *bitmapData, uint16 width, uint16 height, int hotspotX, int hotspotY); +	void setMouseCursor(bool busy = false); -	void printCharacter(int, int, char, int, int);  	int initMachine();  	int deinitMachine(); + +	void setRenderStartOffset(uint16 offsetY); + +private: +	uint _pixels; +	//uint16 _displayWidth; +	//uint16 _displayHeight; +	uint _displayPixels; + +	byte *_activeScreen; +	byte *_visualScreen;   // 160x168 +	byte *_priorityScreen; // 160x168 +	byte *_displayScreen;  // 320x200 + +	bool  _priorityTableSet; +	uint8 _priorityTable[SCRIPT_HEIGHT]; /**< priority table */ + +	MouseCursorData _mouseCursor; +	MouseCursorData _mouseCursorBusy; + +	uint16 _renderStartOffsetY; + +public: +	void debugShowMap(int mapNr); + +	void clear(byte color, byte priority); +	void clearDisplay(byte color, bool copyToScreen = true); +	void putPixel(int16 x, int16 y, byte drawMask, byte color, byte priority); +	void putPixelOnDisplay(int16 x, int16 y, byte color); + +	byte getColor(int16 x, int16 y); +	byte getPriority(int16 x, int16 y); +	bool checkControlPixel(int16 x, int16 y, byte newPriority); + +	byte getCGAMixtureColor(byte color); + +	void render_Block(int16 x, int16 y, int16 width, int16 height, bool copyToScreen = true); +	bool render_Clip(int16 &x, int16 &y, int16 &width, int16 &height, int16 clipAgainstWidth = SCRIPT_WIDTH, int16 clipAgainstHeight = SCRIPT_HEIGHT); + +private: +	void render_BlockEGA(int16 x, int16 y, int16 width, int16 height, bool copyToScreen); +	void render_BlockCGA(int16 x, int16 y, int16 width, int16 height, bool copyToScreen); + +public: +	void transition_Amiga(); +	void transition_AtariSt(); + +	void block_save(int16 x, int16 y, int16 width, int16 height, byte *bufferPtr); +	void block_restore(int16 x, int16 y, int16 width, int16 height, byte *bufferPtr); + +	void copyDisplayRectToScreen(int16 x, int16 y, int16 width, int16 height); + +	void drawBox(int16 x, int16 y, int16 width, int16 height, byte backgroundColor, byte lineColor); +	void drawRect(int16 x, int16 y, int16 width, int16 height, byte color); +	void drawDisplayRect(int16 x, int16 y, int16 width, int16 height, byte color); +private: +	void drawDisplayRectEGA(int16 x, int16 y, int16 width, int16 height, byte color); +	void drawDisplayRectCGA(int16 x, int16 y, int16 width, int16 height, byte color); + +public: +	void drawCharacter(int16 row, int16 column, byte character, byte foreground, byte background, bool disabledLook); + +	void shakeScreen(int16 repeatCount); +	void updateScreen(); + +	void initPriorityTable(); +	void setPriorityTable(int16 priorityBase); +	void setPriority(int16 yPos, int16 priority); +	int16 priorityToY(int16 priority); +	int16 priorityFromY(int16 yPos);  };  } // End of namespace Agi diff --git a/engines/agi/inv.cpp b/engines/agi/inv.cpp index f1e4e5094b..614b016463 100644 --- a/engines/agi/inv.cpp +++ b/engines/agi/inv.cpp @@ -22,221 +22,193 @@  #include "agi/agi.h"  #include "agi/graphics.h" +#include "agi/inv.h" +#include "agi/text.h"  #include "agi/keyboard.h" +#include "agi/systemui.h"  namespace Agi { -// -// Messages and coordinates -// +InventoryMgr::InventoryMgr(AgiEngine *agi, GfxMgr *gfx, TextMgr *text, SystemUI *systemUI) { +	_vm = agi; +	_gfx = gfx; +	_text = text; +	_systemUI = systemUI; +} -#define NOTHING_X	16 -#define NOTHING_Y	3 -#define NOTHING_MSG	"nothing" +InventoryMgr::~InventoryMgr() { +} -#define ANY_KEY_X	4 -#define ANY_KEY_Y	24 -#define ANY_KEY_MSG	"Press a key to return to the game" +void InventoryMgr::getPlayerInventory() { +	AgiGame game = _vm->_game; +	int16 selectedInventoryItem = _vm->getVar(VM_VAR_SELECTED_INVENTORY_ITEM); +	uint16 objectNr = 0; +	int16 curRow = 2; // starting at position 2,1 +	int16 curColumn = 1; + +	_array.clear(); +	_activeItemNr = 0; + +	for (objectNr = 0; objectNr < game.numObjects; objectNr++) { +		if (_vm->objectGetLocation(objectNr) == EGO_OWNED) { +			// item is in the possession of ego, so add it to our internal list +			if (objectNr == selectedInventoryItem) { +				// it's the currently selected inventory item, remember that +				_activeItemNr = _array.size(); +			} -#define YOUHAVE_X	11 -#define YOUHAVE_Y	0 -#define YOUHAVE_MSG	"You are carrying:" +			InventoryEntry inventoryEntry; -#define SELECT_X	2 -#define SELECT_Y	24 -#define SELECT_MSG	"Press ENTER to select, ESC to cancel" +			inventoryEntry.objectNr = objectNr; +			inventoryEntry.name = _vm->objectName(objectNr); +			inventoryEntry.row = curRow; +			inventoryEntry.column = curColumn; +			if (inventoryEntry.column > 1) { +				// right side, adjust column accordingly +				inventoryEntry.column -= strlen( inventoryEntry.name ); +			} +			_array.push_back(inventoryEntry); + +			// go to next position +			if (curColumn == 1) { +				// current position is left side, go to right side +				curColumn = 39; +			} else { +				// current position is right side, so go to left side again and new row +				curColumn = 1; +				curRow++; +			} +		} +	} -#define NOTHING_X_RU	16 -#define NOTHING_Y_RU	3 -#define NOTHING_MSG_RU	"\xad\xa8\xe7\xa5\xa3\xae" +	if (_array.size() == 0) { +		// empty inventory +		InventoryEntry inventoryEntry; -#define ANY_KEY_X_RU	4 -#define ANY_KEY_Y_RU	24 -#define ANY_KEY_MSG_RU	"\x8b\xee\xa1\xa0\xef \xaa\xab\xa0\xa2\xa8\xe8\xa0 - \xa2\xae\xa7\xa2\xe0\xa0\xe2 \xa2 \xa8\xa3\xe0\xe3." +		inventoryEntry.objectNr = 0; +		inventoryEntry.name = _systemUI->getInventoryTextNothing(); +		inventoryEntry.row = 2; +		inventoryEntry.column = 19 - (strlen(inventoryEntry.name) / 2); +		_array.push_back(inventoryEntry); +	} +} -#define YOUHAVE_X_RU	11 -#define YOUHAVE_Y_RU	0 -#define YOUHAVE_MSG_RU	"   \x93 \xa2\xa0\xe1 \xa5\xe1\xe2\xec:   " +void InventoryMgr::drawAll() { +	int16 inventoryCount = _array.size(); +	int16 inventoryNr = 0; -#define SELECT_X_RU	2 -#define SELECT_Y_RU	24 -#define SELECT_MSG_RU	"ENTER - \xa2\xeb\xa1\xe0\xa0\xe2\xec, ESC - \xae\xe2\xac\xa5\xad\xa8\xe2\xec." +	_text->charPos_Set(0, 11); +	_text->displayText(_systemUI->getInventoryTextYouAreCarrying()); -void AgiEngine::printItem(int n, int fg, int bg) { -	printText(objectName(_intobj[n]), 0, ((n % 2) ? 39 - strlen(objectName(_intobj[n])) : 1), -			(n / 2) + 2, 40, fg, bg); +	for (inventoryNr = 0; inventoryNr < inventoryCount; inventoryNr++) { +		drawItem(inventoryNr); +	}  } -int AgiEngine::findItem() { -	int r, c; +void InventoryMgr::drawItem(int16 itemNr) { +	if (itemNr == _activeItemNr) { +		_text->charAttrib_Set(15, 0); +	} else { +		_text->charAttrib_Set(0, 15); +	} -	r = _mouse.y / CHAR_LINES; -	c = _mouse.x / CHAR_COLS; +	_text->charPos_Set(_array[itemNr].row, _array[itemNr].column); +	// original interpreter used printf here +	// this doesn't really make sense, because for length calculation it's using strlen without printf +	// which means right-aligned inventory items on the right side would not be displayed properly +	// in case printf-formatting was actually used +	// I have to assume that no game uses this, because behavior in original interpreter would have been buggy. +	_text->displayText(_array[itemNr].name); +} -	debugC(6, kDebugLevelInventory, "r = %d, c = %d", r, c); +void InventoryMgr::show() { +	bool selectItems = false; -	if (r < 2) -		return -1; +	// figure out current inventory of the player +	getPlayerInventory(); -	return (r - 2) * 2 + (c > 20); -} +	if (_vm->getflag(VM_FLAG_STATUS_SELECTS_ITEMS)) { +		selectItems = true; +	} else{ +		_activeItemNr = -1; // so that none is shown as active +	} -int AgiEngine::showItems() { -	unsigned int x, i; +	drawAll(); -	for (x = i = 0; x < _game.numObjects; x++) { -		if (objectGetLocation(x) == EGO_OWNED) { -			// add object to our list! -			_intobj[i] = x; -			printItem(i, STATUS_FG, STATUS_BG); -			i++; -		} +	_text->charAttrib_Set(0, 15); +	if (selectItems) { +		_text->charPos_Set(24, 2); +		_text->displayText(_systemUI->getInventoryTextSelectItems()); +	} else { +		_text->charPos_Set(24, 4); +		_text->displayText(_systemUI->getInventoryTextReturnToGame());  	} -	if (i == 0) { -		switch (getLanguage()) { -		case Common::RU_RUS: -			printText(NOTHING_MSG_RU, 0, NOTHING_X_RU, NOTHING_Y_RU, 40, STATUS_FG, STATUS_BG); -			break; -		default: -			printText(NOTHING_MSG, 0, NOTHING_X, NOTHING_Y, 40, STATUS_FG, STATUS_BG); -			break; -		} -	} +	if (selectItems) { +		_vm->cycleInnerLoopActive(CYCLE_INNERLOOP_INVENTORY); -	return i; -} +		do { +			_vm->mainCycle(); +		} while (_vm->cycleInnerLoopIsActive() && !(_vm->shouldQuit() || _vm->_restartGame)); -void AgiEngine::selectItems(int n) { -	int fsel = 0; -	bool exit_select = false; - -	while (!exit_select && !(shouldQuit() || _restartGame)) { -		if (n > 0) -			printItem(fsel, STATUS_BG, STATUS_FG); - -		switch (waitAnyKey()) { -		case KEY_ENTER: -			setvar(vSelItem, _intobj[fsel]); -			exit_select = true; -			break; -		case KEY_ESCAPE: -			setvar(vSelItem, 0xff); -			exit_select = true; -			break; -		case KEY_UP: -			if (fsel >= 2) -				fsel -= 2; -			break; -		case KEY_DOWN: -			if (fsel + 2 < n) -				fsel += 2; -			break; -		case KEY_LEFT: -			if (fsel % 2 == 1) -				fsel--; -			break; -		case KEY_RIGHT: -			if (fsel % 2 == 0 && fsel + 1 < n) -				fsel++; -			break; -		case BUTTON_LEFT:{ -				int i = findItem(); -				if (i >= 0 && i < n) { -					setvar(vSelItem, _intobj[fsel = i]); -					debugC(6, kDebugLevelInventory, "item found: %d", fsel); -					showItems(); -					printItem(fsel, STATUS_BG, STATUS_FG); -					_gfx->doUpdate(); -					exit_select = true; -				} -				break; -			} -		default: -			break; +		if (_activeItemNr >= 0) { +			// pass selected object number +			_vm->setVar(VM_VAR_SELECTED_INVENTORY_ITEM, _array[_activeItemNr].objectNr); +		} else { +			// nothing was selected +			_vm->setVar(VM_VAR_SELECTED_INVENTORY_ITEM, 0xff);  		} -		if (!exit_select) { -			showItems(); -			_gfx->doUpdate(); -		} +	} else { +		// no selection is supposed to be possible, just wait for key and exit +		_vm->waitAnyKey();  	} - -	debugC(6, kDebugLevelInventory, "selected: %d", fsel);  } -/* - * Public functions - */ - -/** - * Display inventory items. - */ -void AgiEngine::inventory() { -	int oldFg, oldBg; -	int n; - -	// screen is white with black text -	oldFg = _game.colorFg; -	oldBg = _game.colorBg; -	_game.colorFg = 0; -	_game.colorBg = 15; -	_gfx->clearScreen(_game.colorBg); - -	switch (getLanguage()) { -	case Common::RU_RUS: -		printText(YOUHAVE_MSG_RU, 0, YOUHAVE_X_RU, YOUHAVE_Y_RU, 40, STATUS_FG, STATUS_BG); -		break; -	default: -		printText(YOUHAVE_MSG, 0, YOUHAVE_X, YOUHAVE_Y, 40, STATUS_FG, STATUS_BG); +void InventoryMgr::charPress(int16 newChar) { +	switch (newChar) { +	case AGI_KEY_ENTER: { +		_vm->cycleInnerLoopInactive(); // exit show-loop  		break;  	} -	// FIXME: doesn't check if objects overflow off screen... - -	_intobj = (uint8 *)malloc(4 + _game.numObjects); -	memset(_intobj, 0, (4 + _game.numObjects)); - -	n = showItems(); +	case AGI_KEY_ESCAPE: { +		_vm->cycleInnerLoopInactive(); // exit show-loop +		_activeItemNr = -1; // no item selected +		break; +	} -	switch (getLanguage()) { -	case Common::RU_RUS: -		if (getflag(fStatusSelectsItems)) { -			printText(SELECT_MSG_RU, 0, SELECT_X_RU, SELECT_Y_RU, 40, STATUS_FG, STATUS_BG); -		} else { -			printText(ANY_KEY_MSG_RU, 0, ANY_KEY_X_RU, ANY_KEY_Y_RU, 40, STATUS_FG, STATUS_BG); -		} +	case AGI_KEY_UP: +		changeActiveItem(-2);  		break; +	case AGI_KEY_DOWN: +		changeActiveItem(+2); +		break; +	case AGI_KEY_LEFT: +		changeActiveItem(-1); +		break; +	case AGI_KEY_RIGHT: +		changeActiveItem(+1); +		break; +  	default: -		if (getflag(fStatusSelectsItems)) { -			printText(SELECT_MSG, 0, SELECT_X, SELECT_Y, 40, STATUS_FG, STATUS_BG); -		} else { -			printText(ANY_KEY_MSG, 0, ANY_KEY_X, ANY_KEY_Y, 40, STATUS_FG, STATUS_BG); -		}  		break;  	} +} -	_gfx->flushScreen(); - -	// If flag 13 is set, we want to highlight & select an item. -	// opon selection, put objnum in var 25. Then on esc put in -	// var 25 = 0xff. - -	if (getflag(fStatusSelectsItems)) -		selectItems(n); - -	free(_intobj); +void InventoryMgr::changeActiveItem(int16 direction) { +	int16 orgItemNr = _activeItemNr; -	if (!getflag(fStatusSelectsItems)) -		waitAnyKey(); +	_activeItemNr += direction; -	_gfx->clearScreen(0); -	writeStatus(); -	_picture->showPic(); -	_game.colorFg = oldFg; -	_game.colorBg = oldBg; -	_game.hasPrompt = 0; -	flushLines(_game.lineUserInput, 24); +	if ((_activeItemNr >= 0) && (_activeItemNr < (int16)_array.size())) { +		// within bounds +		drawItem(orgItemNr); +		drawItem(_activeItemNr); +	} else { +		// out of bounds, revert change +		_activeItemNr = orgItemNr; +	}  }  } // End of namespace Agi diff --git a/engines/agi/inv.h b/engines/agi/inv.h new file mode 100644 index 0000000000..0c89275b7c --- /dev/null +++ b/engines/agi/inv.h @@ -0,0 +1,61 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef AGI_INV_H +#define AGI_INV_H + +namespace Agi { + +struct InventoryEntry { +	uint16 objectNr; +	int16  row; +	int16  column; +	const char *name; +}; +typedef Common::Array<InventoryEntry> InventoryArray; + +class InventoryMgr { +private: +	GfxMgr *_gfx; +	TextMgr *_text; +	AgiEngine *_vm; +	SystemUI *_systemUI; + +	InventoryArray _array; +	int16 _activeItemNr; + +public: +	InventoryMgr(AgiEngine *agi, GfxMgr *gfx, TextMgr *text, SystemUI *systemUI); +	~InventoryMgr(); + +	void getPlayerInventory(); +	void drawAll(); +	void drawItem(int16 itemNr); +	void show(); + +	void charPress(int16 newChar); +	void changeActiveItem(int16 direction); +}; + +} // End of namespace Agi + +#endif /* AGI_INV_H */ diff --git a/engines/agi/keyboard.cpp b/engines/agi/keyboard.cpp index 0aa521bdc8..c307b077b1 100644 --- a/engines/agi/keyboard.cpp +++ b/engines/agi/keyboard.cpp @@ -20,12 +20,13 @@   *   */ +#include "common/events.h" +  #include "agi/agi.h"  #include "agi/graphics.h"  #include "agi/keyboard.h" -#ifdef __DS__ -#include "wordcompletion.h" -#endif +#include "agi/menu.h" +#include "agi/text.h"  namespace Agi { @@ -61,21 +62,230 @@ const uint8 scancodeTable[26] = {  	44			// Z  }; -void AgiEngine::initWords() { -	_game.numEgoWords = 0; -} +void AgiEngine::processEvents() { +	Common::Event event; +	int key = 0; -void AgiEngine::cleanInput() { -	while (_game.numEgoWords) -		free(_game.egoWords[--_game.numEgoWords].word); -} +	while (_eventMan->pollEvent(event)) { +		switch (event.type) { +		case Common::EVENT_PREDICTIVE_DIALOG: { +			GUI::PredictiveDialog _predictiveDialog; +			_predictiveDialog.runModal(); +#if 0 +			strcpy(_predictiveResult, _predictiveDialog.getResult()); +			if (strcmp(_predictiveResult, "")) { +				if (_game.inputMode == INPUTMODE_NORMAL) { +					//strcpy((char *)_game.inputBuffer, _predictiveResult); +					//handleKeys(KEY_ENTER); +					// TODO: repair predictive +				} else if (_game.inputMode == INPUTMODE_GETSTRING) { +					strcpy(_game.strings[_stringdata.str], _predictiveResult); +					newInputMode(INPUTMODE_NORMAL); +					//_gfx->printCharacter(_stringdata.x + strlen(_game.strings[_stringdata.str]) + 1, +					//		_stringdata.y, ' ', 15, 0); +				} else if (_game.inputMode == INPUTMODE_NONE) { +					for (int n = 0; _predictiveResult[n]; n++) +						keyEnqueue(_predictiveResult[n]); +				} +			} +#endif +			/* +			if (predictiveDialog()) { +				if (_game.inputMode == INPUT_NORMAL) { +					strcpy((char *)_game.inputBuffer, _predictiveResult); +					handleKeys(KEY_ENTER); +				} else if (_game.inputMode == INPUT_GETSTRING) { +					strcpy(_game.strings[_stringdata.str], _predictiveResult); +					newInputMode(INPUT_NORMAL); +					_gfx->printCharacter(_stringdata.x + strlen(_game.strings[_stringdata.str]) + 1, +							_stringdata.y, ' ', _game.colorFg, _game.colorBg); +				} else if (_game.inputMode == INPUT_NONE) { +					for (int n = 0; _predictiveResult[n]; n++) +						keyEnqueue(_predictiveResult[n]); +				} +			} +			*/ +			} +			break; +		case Common::EVENT_LBUTTONDOWN: +			if (_game.mouseEnabled) { +				key = AGI_MOUSE_BUTTON_LEFT; +				_mouse.button = kAgiMouseButtonLeft; +				keyEnqueue(key); +				_mouse.x = event.mouse.x; +				_mouse.y = event.mouse.y; +			} +			break; +		case Common::EVENT_RBUTTONDOWN: +			if (_game.mouseEnabled) { +				key = AGI_MOUSE_BUTTON_RIGHT; +				_mouse.button = kAgiMouseButtonRight; +				keyEnqueue(key); +				_mouse.x = event.mouse.x; +				_mouse.y = event.mouse.y; +			} +			break; +		case Common::EVENT_WHEELUP: +			if (_game.mouseEnabled) { +				key = AGI_MOUSE_WHEEL_UP; +				keyEnqueue(key); +			} +			break; +		case Common::EVENT_WHEELDOWN: +			if (_game.mouseEnabled) { +				key = AGI_MOUSE_WHEEL_DOWN; +				keyEnqueue(key); +			} +			break; +		case Common::EVENT_MOUSEMOVE: +			if (_game.mouseEnabled) { +				_mouse.x = event.mouse.x; +				_mouse.y = event.mouse.y; + +				if (!_game.mouseFence.isEmpty()) { +					if (_mouse.x < _game.mouseFence.left) +						_mouse.x = _game.mouseFence.left; +					if (_mouse.x > _game.mouseFence.right) +						_mouse.x = _game.mouseFence.right; +					if (_mouse.y < _game.mouseFence.top) +						_mouse.y = _game.mouseFence.top; +					if (_mouse.y > _game.mouseFence.bottom) +						_mouse.y = _game.mouseFence.bottom; + +					g_system->warpMouse(_mouse.x, _mouse.y); +				} +			} + +			break; +		case Common::EVENT_LBUTTONUP: +		case Common::EVENT_RBUTTONUP: +			if (_game.mouseEnabled) { +				_mouse.button = kAgiMouseButtonUp; +				_mouse.x = event.mouse.x; +				_mouse.y = event.mouse.y; +			} +			break; +		case Common::EVENT_KEYDOWN: +			if (event.kbd.hasFlags(Common::KBD_CTRL) && event.kbd.keycode == Common::KEYCODE_d) { +				_console->attach(); +				break; +			} -void AgiEngine::getString(int x, int y, int len, int str) { -	newInputMode(INPUT_GETSTRING); -	_stringdata.x = x; -	_stringdata.y = y; -	_stringdata.len = len; -	_stringdata.str = str; +			if ((event.kbd.ascii) && (event.kbd.ascii <= 0xFF)) { +				// No special key, directly accept it +				// Is ISO-8859-1, we need lower 128 characters only, which is plain ASCII, so no mapping required +				key = event.kbd.ascii; + +				if (Common::isAlpha(key)) { +					// Key is A-Z. +					// Map Ctrl-A to 1, Ctrl-B to 2, etc. +					if (event.kbd.flags & Common::KBD_CTRL) { +						key = toupper(key) - 'A' + 1; +					} else if (event.kbd.flags & Common::KBD_ALT) { +						// Map Alt-A, Alt-B etc. to special scancode values according to an internal scancode table. +						key = scancodeTable[toupper(key) - 'A'] << 8; +					} +				} +			} else { +				switch (key = event.kbd.keycode) { +				case Common::KEYCODE_LEFT: +				case Common::KEYCODE_KP4: +					if (_allowSynthetic || !event.synthetic) +						key = AGI_KEY_LEFT; +					break; +				case Common::KEYCODE_RIGHT: +				case Common::KEYCODE_KP6: +					if (_allowSynthetic || !event.synthetic) +						key = AGI_KEY_RIGHT; +					break; +				case Common::KEYCODE_UP: +				case Common::KEYCODE_KP8: +					if (_allowSynthetic || !event.synthetic) +						key = AGI_KEY_UP; +					break; +				case Common::KEYCODE_DOWN: +				case Common::KEYCODE_KP2: +					if (_allowSynthetic || !event.synthetic) +						key = AGI_KEY_DOWN; +					break; +				case Common::KEYCODE_PAGEUP: +				case Common::KEYCODE_KP9: +					if (_allowSynthetic || !event.synthetic) +						key = AGI_KEY_UP_RIGHT; +					break; +				case Common::KEYCODE_PAGEDOWN: +				case Common::KEYCODE_KP3: +					if (_allowSynthetic || !event.synthetic) +						key = AGI_KEY_DOWN_RIGHT; +					break; +				case Common::KEYCODE_HOME: +				case Common::KEYCODE_KP7: +					if (_allowSynthetic || !event.synthetic) +						key = AGI_KEY_UP_LEFT; +					break; +				case Common::KEYCODE_END: +				case Common::KEYCODE_KP1: +					if (_allowSynthetic || !event.synthetic) +						key = AGI_KEY_DOWN_LEFT; +					break; +				case Common::KEYCODE_KP5: +					key = AGI_KEY_STATIONARY; +					break; +				case Common::KEYCODE_F1: +					key = AGI_KEY_F1; +					break; +				case Common::KEYCODE_F2: +					key = AGI_KEY_F2; +					break; +				case Common::KEYCODE_F3: +					key = AGI_KEY_F3; +					break; +				case Common::KEYCODE_F4: +					key = AGI_KEY_F4; +					break; +				case Common::KEYCODE_F5: +					key = AGI_KEY_F5; +					break; +				case Common::KEYCODE_F6: +					key = AGI_KEY_F6; +					break; +				case Common::KEYCODE_F7: +					key = AGI_KEY_F7; +					break; +				case Common::KEYCODE_F8: +					key = AGI_KEY_F8; +					break; +				case Common::KEYCODE_F9: +					key = AGI_KEY_F9; +					break; +				case Common::KEYCODE_F10: +					key = AGI_KEY_F10; +					break; +				case Common::KEYCODE_F11: +					key = AGI_KEY_F11; +					break; +				case Common::KEYCODE_F12: +					key = AGI_KEY_F12; +					break; +				case Common::KEYCODE_KP_ENTER: +					key = AGI_KEY_ENTER; +					break; +				default: +					break; +				} +			} +			if (key) +				keyEnqueue(key); +			break; + +		case Common::EVENT_KEYUP: +			if (_egoHoldKey) +				_game.screenObjTable[SCREENOBJECTS_EGO_ENTRY].direction = 0; + +		default: +			break; +		} +	}  }  /** @@ -97,54 +307,128 @@ int AgiEngine::doPollKeyboard() {  	return key;  } -int AgiEngine::handleController(int key) { -	VtEntry *v = &_game.viewTable[0]; -	int i; +int16 AgiEngine::getSpecialMenuControllerSlot() { +	int16 controllerSlotESC = -1; +	int16 controllerSlotSpecial = -1; + +	for (uint16 curMapping = 0; curMapping < MAX_CONTROLLER_KEYMAPPINGS; curMapping++) { +		if (_game.controllerKeyMapping[curMapping].keycode == _game.specialMenuTriggerKey) { +			if (controllerSlotSpecial < 0) { +				controllerSlotSpecial = _game.controllerKeyMapping[curMapping].controllerSlot; +			} +		} +		if (_game.controllerKeyMapping[curMapping].keycode == AGI_MENU_TRIGGER_PC) { +			if (controllerSlotESC < 0) { +				controllerSlotESC = _game.controllerKeyMapping[curMapping].controllerSlot; +			} +		} +	} +	if (controllerSlotSpecial >= 0) { +		// special menu controller slot found +		if (controllerSlotSpecial != controllerSlotESC) { +			// not the same as the ESC slot (is the same in Manhunter AppleIIgs, we need to replace "pause" +			if (controllerSlotSpecial >= 10) { +				// slot needs to be at least 10 +				// Atari ST SQ1 maps the special key, but doesn't trigger any menu with it +				// the controller slot in this case is 8. +				return controllerSlotSpecial; +			} +		} +	} +	return -1; +} + +bool AgiEngine::handleController(uint16 key) { +	ScreenObjEntry *screenObjEgo = &_game.screenObjTable[SCREENOBJECTS_EGO_ENTRY]; + +	if (key == 0) // nothing pressed +		return false; + +	// This previously skipped processing, when ESC was pressed and called menu directly. +	// This original approach was bad, because games check different flags before actually allowing the +	//  user to enter the menu. We checked a few common flags, like for example the availability of the prompt. +	//  But this stopped the user being able to enter the menu, when the original interpreter actually allowed it. +	//  We now instead implement this feature using another way for those platforms. +	if (key == AGI_KEY_ESCAPE) { +		// Escape pressed, user probably wants to trigger the menu +		// For PC, just passing ASCII code for ESC will normally trigger a controller +		//  and the scripts will then trigger the menu +		// For other platforms, ESC was handled by platforms to trigger "pause game" instead +		// We need to change ESC to a platform specific code to make it work properly. +		// +		// There are exceptions though. Mixed Up Mother Goose on AppleIIgs for example actually sets up +		//  ESC for pause only. That's why we also check, if the key is actually mapped to a controller. +		// For this special case, we actually replace the pause function with a menu trigger. +		// Replacing "pause" all the time wouldn't work out as well, becaue games like KQ1 on Apple IIgs +		//  actually disable "pause" when ego has been killed, which means we wouldn't be able to access +		//  the menu anymore in that case. +		if (_menu->isAvailable()) { +			// menu is actually available +			if (_game.specialMenuTriggerKey) { +				int16 specialMenuControllerSlot = getSpecialMenuControllerSlot(); + +				if (specialMenuControllerSlot >= 0) { +					// menu trigger found, trigger it now +					_game.controllerOccured[specialMenuControllerSlot] = true; +					return true; +				} +			} +			// Otherwise go on and look for the ESC controller +		} +	} +  	// AGI 3.149 games, The Black Cauldron and King's Quest 4 need KEY_ESCAPE to use menus  	// Games with the GF_ESCPAUSE flag need KEY_ESCAPE to pause the game -	if (key == 0 || -		(key == KEY_ESCAPE && getVersion() != 0x3149 && getGameID() != GID_BC && getGameID() != GID_KQ4 && !(getFeatures() & GF_ESCPAUSE)) ) -		return false; +	//		(key == KEY_ESCAPE && getVersion() != 0x3149 && getGameID() != GID_BC && getGameID() != GID_KQ4 && !(getFeatures() & GF_ESCPAUSE)) ) +	//		return false; -	if ((getGameID() == GID_MH1 || getGameID() == GID_MH2) && (key == KEY_ENTER) && -			(_game.inputMode == INPUT_NONE)) { +	if ((getGameID() == GID_MH1 || getGameID() == GID_MH2) && (key == AGI_KEY_ENTER) && +			(_game.inputMode == INPUTMODE_NONE)) {  		key = 0x20; // Set Enter key to Space in Manhunter when there's no text input  	}  	debugC(3, kDebugLevelInput, "key = %04x", key); -	for (i = 0; i < MAX_CONTROLLERS; i++) { -		if (_game.controllers[i].keycode == key) { -			debugC(3, kDebugLevelInput, "event %d: key press", _game.controllers[i].controller); -			_game.controllerOccured[_game.controllers[i].controller] = true; +	for (uint16 curMapping = 0; curMapping < MAX_CONTROLLER_KEYMAPPINGS; curMapping++) { +		if (_game.controllerKeyMapping[curMapping].keycode == key) { +			debugC(3, kDebugLevelInput, "event %d: key press", _game.controllerKeyMapping[curMapping].controllerSlot); +			_game.controllerOccured[_game.controllerKeyMapping[curMapping].controllerSlot] = true;  			return true;  		}  	} -	if (key == BUTTON_LEFT) { -		if ((getflag(fMenusWork) || (getFeatures() & GF_MENUS)) && _mouse.y <= CHAR_LINES) { -			newInputMode(INPUT_MENU); -			return true; -		} +	if (key == AGI_MOUSE_BUTTON_LEFT) { +		// call mouse when click is done on status bar +		// TODO +		// This should be done in a better way as in simulate ESC key +		// Sierra seems to have hardcoded it in some way, but we would have to verify, what flags +		// they checked. The previous way wasn't accurate. Mouse support for menu is missing atm anyway. +		//if ((getflag(VM_FLAG_MENUS_WORK) || (getFeatures() & GF_MENUS)) && _mouse.y <= CHAR_LINES) { +		//	newInputMode(INPUTMODE_MENU); +		//	return true; +		//}  	}  	// Show predictive dialog if the user clicks on input area -	if (key == BUTTON_LEFT && -			(int)_mouse.y >= _game.lineUserInput * CHAR_LINES && -			(int)_mouse.y <= (_game.lineUserInput + 1) * CHAR_LINES) { +	if (key == AGI_MOUSE_BUTTON_LEFT && +			(int)_mouse.y >= _text->promptRow_Get() * FONT_DISPLAY_HEIGHT && +			(int)_mouse.y <= (_text->promptRow_Get() + 1) * FONT_DISPLAY_HEIGHT) {  		GUI::PredictiveDialog _predictiveDialog;  		_predictiveDialog.runModal(); +#if 0  		strcpy(_predictiveResult, _predictiveDialog.getResult());  		if (strcmp(_predictiveResult, "")) { -			if (_game.inputMode == INPUT_NONE) { +			if (_game.inputMode == INPUTMODE_NONE) {  				for (int n = 0; _predictiveResult[n]; n++)  					keyEnqueue(_predictiveResult[n]);  			} else { -				strcpy((char *)_game.inputBuffer, _predictiveResult); -				handleKeys(KEY_ENTER); +				//strcpy((char *)_game.inputBuffer, _predictiveResult); +				//handleKeys(KEY_ENTER); +				// TODO  			}  		} +#endif  		/*  		if (predictiveDialog()) {  			if (_game.inputMode == INPUT_NONE) { @@ -160,57 +444,73 @@ int AgiEngine::handleController(int key) {  	}  	if (_game.playerControl) { -		int d = 0; +		int16 newDirection = 0; -		if (!KEY_ASCII(key)) { -			switch (key) { -			case KEY_UP: -				d = 1; -				break; -			case KEY_DOWN: -				d = 5; -				break; -			case KEY_LEFT: -				d = 7; -				break; -			case KEY_RIGHT: -				d = 3; -				break; -			case KEY_UP_RIGHT: -				d = 2; -				break; -			case KEY_DOWN_RIGHT: -				d = 4; -				break; -			case KEY_UP_LEFT: -				d = 8; -				break; -			case KEY_DOWN_LEFT: -				d = 6; -				break; -			} +		switch (key) { +		case AGI_KEY_UP: +			newDirection = 1; +			break; +		case AGI_KEY_DOWN: +			newDirection = 5; +			break; +		case AGI_KEY_LEFT: +			newDirection = 7; +			break; +		case AGI_KEY_RIGHT: +			newDirection = 3; +			break; +		case AGI_KEY_UP_RIGHT: +			newDirection = 2; +			break; +		case AGI_KEY_DOWN_RIGHT: +			newDirection = 4; +			break; +		case AGI_KEY_UP_LEFT: +			newDirection = 8; +			break; +		case AGI_KEY_DOWN_LEFT: +			newDirection = 6; +			break; +		default: +			break;  		}  		if (!(getFeatures() & GF_AGIMOUSE)) {  			// Handle mouse button events -			if (key == BUTTON_LEFT) { -				if (getGameID() == GID_PQ1 && _game.vars[vCurRoom] == 116) { -					// WORKAROUND: Special handling for mouse clicks in the newspaper -					// screen of PQ1. Fixes bug #3018770. -					d = 3;	// fake a right arrow key (next page) -				} else { -					// Click-to-walk mouse interface -					v->flags |= fAdjEgoXY; -					v->parm1 = WIN_TO_PIC_X(_mouse.x); -					v->parm2 = WIN_TO_PIC_Y(_mouse.y); -					return true; +			if (!_game.mouseHidden) { +				if (key == AGI_MOUSE_BUTTON_LEFT) { +					if (getGameID() == GID_PQ1 && _game.vars[VM_VAR_CURRENT_ROOM] == 116) { +						// WORKAROUND: Special handling for mouse clicks in the newspaper +						// screen of PQ1. Fixes bug #3018770. +						newDirection = 3;	// fake a right arrow key (next page) + +					} else { +						// Click-to-walk mouse interface +						//v->flags |= fAdjEgoXY; +						// setting fAdjEgoXY here will at least break "climbing the log" in SQ2 +						// in case you walked to the log by using the mouse, so don't!!! +						int16 egoDestinationX = _mouse.x; +						int16 egoDestinationY = _mouse.y; +						adjustPosToGameScreen(egoDestinationX, egoDestinationY); + +						screenObjEgo->motionType = kMotionEgo; +						if (egoDestinationX < (screenObjEgo->xSize / 2)) { +							screenObjEgo->move_x = -1; +						} else { +							screenObjEgo->move_x = egoDestinationX - (screenObjEgo->xSize / 2); +						} +						screenObjEgo->move_y        = egoDestinationY; +						screenObjEgo->move_stepSize = screenObjEgo->stepSize; +						return true; +					}  				}  			}  		} -		if (d || key == KEY_STATIONARY) { -			v->flags &= ~fAdjEgoXY; -			v->direction = v->direction == d ? 0 : d; +		if (newDirection || key == AGI_KEY_STATIONARY) { +			screenObjEgo->flags &= ~fAdjEgoXY; +			screenObjEgo->direction = screenObjEgo->direction == newDirection ? 0 : newDirection; +			screenObjEgo->motionType = kMotionNormal;  			return true;  		}  	} @@ -218,205 +518,22 @@ int AgiEngine::handleController(int key) {  	return false;  } -void AgiEngine::handleGetstring(int key) { -	static int pos = 0;	// Cursor position -	static char buf[40]; - -	if (KEY_ASCII(key) == 0) -		return; - -	debugC(3, kDebugLevelInput, "handling key: %02x", key); - -	switch (key) { -	case BUTTON_LEFT: -		if ((int)_mouse.y >= _stringdata.y * CHAR_LINES && -				(int)_mouse.y <= (_stringdata.y + 1) * CHAR_LINES) { -			GUI::PredictiveDialog _predictiveDialog; -			_predictiveDialog.runModal(); -			strcpy(_predictiveResult, _predictiveDialog.getResult()); -			if (strcmp(_predictiveResult, "")) { -				strcpy(_game.strings[_stringdata.str], _predictiveResult); -				newInputMode(INPUT_NORMAL); -				_gfx->printCharacter(_stringdata.x + strlen(_game.strings[_stringdata.str]) + 1, -								_stringdata.y, ' ', _game.colorFg, _game.colorBg); -				return; -			} -			/* -			if (predictiveDialog()) { -				strcpy(_game.strings[_stringdata.str], _predictiveResult); -				newInputMode(INPUT_NORMAL); -				_gfx->printCharacter(_stringdata.x + strlen(_game.strings[_stringdata.str]) + 1, -								_stringdata.y, ' ', _game.colorFg, _game.colorBg); -				return; -			} -			*/ -		} -		break; -	case KEY_ENTER: -		debugC(3, kDebugLevelInput, "KEY_ENTER"); -		_game.hasPrompt = 0; -		buf[pos] = 0; - -		strcpy(_game.strings[_stringdata.str], buf); -		debugC(3, kDebugLevelInput, "buffer=[%s]", buf); -		buf[pos = 0] = 0; - -		newInputMode(INPUT_NORMAL); -		_gfx->printCharacter(_stringdata.x + strlen(_game.strings[_stringdata.str]) + 1, -				_stringdata.y, ' ', _game.colorFg, _game.colorBg); -		return; -	case KEY_ESCAPE: -		debugC(3, kDebugLevelInput, "KEY_ESCAPE"); -		_game.hasPrompt = 0; -		buf[pos = 0] = 0; - -		strcpy(_game.strings[_stringdata.str], buf); -		newInputMode(INPUT_NORMAL); - -		// newInputMode(INPUT_MENU); -		break; -	case KEY_BACKSPACE:	// 0x08 -		if (!pos) -			break; - -		_gfx->printCharacter(_stringdata.x + (pos + 1), _stringdata.y, -				' ', _game.colorFg, _game.colorBg); -		pos--; -		buf[pos] = 0; -		break; -	default: -		if (key < 0x20 || key > 0x7f) -			break; - -		if (pos >= _stringdata.len) -			break; - -		buf[pos++] = key; -		buf[pos] = 0; - -		// Echo -		_gfx->printCharacter(_stringdata.x + pos, _stringdata.y, buf[pos - 1], -				_game.colorFg, _game.colorBg); - -		break; -	} - -	// print cursor -	_gfx->printCharacter(_stringdata.x + pos + 1, _stringdata.y, -			(char)_game.cursorChar, _game.colorFg, _game.colorBg); -} - -void AgiEngine::handleKeys(int key) { -	uint8 *p = NULL; -	int c = 0; -	static uint8 formattedEntry[40]; -	int l = _game.lineUserInput; -	int fg = _game.colorFg, bg = _game.colorBg; -	int promptLength = strlen(agiSprintf(_game.strings[0])); - -	setvar(vWordNotFound, 0); - -	debugC(3, kDebugLevelInput, "handling key: %02x", key); - -	switch (key) { -	case KEY_ENTER: -		debugC(3, kDebugLevelInput, "KEY_ENTER"); -		_game.keypress = 0; - -		// Remove all leading spaces -		for (p = _game.inputBuffer; *p && *p == 0x20; p++) -			; - -		// Copy to internal buffer -		for (; *p && c < 40-1; p++) { -			// Squash spaces -			if (*p == 0x20 && *(p + 1) == 0x20) { -				p++; -				continue; -			} -			formattedEntry[c++] = tolower(*p); -		} -		formattedEntry[c++] = 0; - -		// Handle string only if it's not empty -		if (formattedEntry[0]) { -			strcpy((char *)_game.echoBuffer, (const char *)_game.inputBuffer); -			strcpy(_lastSentence, (const char *)formattedEntry); -			dictionaryWords(_lastSentence); -		} - -		// Clear to start a new line -		_game.hasPrompt = 0; -		_game.inputBuffer[_game.cursorPos = 0] = 0; -		debugC(3, kDebugLevelInput | kDebugLevelText, "clear lines"); -		clearLines(l, l + 1, bg); -		flushLines(l, l + 1); -#ifdef __DS__ -		DS::findWordCompletions((char *) _game.inputBuffer); -#endif - -		break; -	case KEY_ESCAPE: -		debugC(3, kDebugLevelInput, "KEY_ESCAPE"); -		newInputMode(INPUT_MENU); -		break; -	case KEY_BACKSPACE: -		// Ignore backspace at start of line -		if (_game.cursorPos == 0) -			break; - -		// erase cursor -		_gfx->printCharacter(_game.cursorPos + promptLength, l, ' ', fg, bg); -		_game.inputBuffer[--_game.cursorPos] = 0; - -		// Print cursor -		_gfx->printCharacter(_game.cursorPos + promptLength, l, _game.cursorChar, fg, bg); - -#ifdef __DS__ -		DS::findWordCompletions((char *) _game.inputBuffer); -#endif -		break; -	default: -		// Ignore invalid keystrokes -		if (key < 0x20 || key > 0x7f) -			break; - -		// Maximum input size reached -		if (_game.cursorPos >= getvar(vMaxInputChars)) -			break; - -		_game.inputBuffer[_game.cursorPos++] = key; -		_game.inputBuffer[_game.cursorPos] = 0; - -#ifdef __DS__ -		DS::findWordCompletions((char *) _game.inputBuffer); -#endif - -		// echo -		_gfx->printCharacter(_game.cursorPos + promptLength - 1, l, _game.inputBuffer[_game.cursorPos - 1], fg, bg); - -		// Print cursor -		_gfx->printCharacter(_game.cursorPos + promptLength, l, _game.cursorChar, fg, bg); -		break; -	} -} -  int AgiEngine::waitKey() {  	int key = 0;  	clearKeyQueue();  	debugC(3, kDebugLevelInput, "waiting..."); -	while (!(shouldQuit() || _restartGame || getflag(fRestoreJustRan))) { +	while (!(shouldQuit() || _restartGame || getflag(VM_FLAG_RESTORE_JUST_RAN))) {  		pollTimer();  		key = doPollKeyboard(); -		if (key == KEY_ENTER || key == KEY_ESCAPE || key == BUTTON_LEFT) +		if (key == AGI_KEY_ENTER || key == AGI_KEY_ESCAPE || key == AGI_MOUSE_BUTTON_LEFT)  			break;  		pollTimer();  		updateTimer(); -		_gfx->doUpdate(); +		g_system->updateScreen();  	}  	// Have to clear it as original did not set this variable, and we do it in doPollKeyboard() @@ -437,7 +554,7 @@ int AgiEngine::waitAnyKey() {  		key = doPollKeyboard();  		if (key)  			break; -		_gfx->doUpdate(); +		g_system->updateScreen();  	}  	// Have to clear it as original did not set this variable, and we do it in doPollKeyboard() diff --git a/engines/agi/keyboard.h b/engines/agi/keyboard.h index 89d6a89ce3..e5cf955a07 100644 --- a/engines/agi/keyboard.h +++ b/engines/agi/keyboard.h @@ -46,49 +46,50 @@ public:  	}  }; -// QNX4 has a KEY_DOWN defined which we don't need to care about -#undef KEY_DOWN - -// Allegro defines these -#undef KEY_BACKSPACE -#undef KEY_ENTER -#undef KEY_LEFT -#undef KEY_RIGHT -#undef KEY_UP -#undef KEY_PGUP -#undef KEY_PGDN -#undef KEY_HOME -#undef KEY_END - -#define KEY_BACKSPACE	0x08 -#define	KEY_ESCAPE	0x1B -#define KEY_ENTER	0x0D -#define KEY_UP		0x4800 -#define	KEY_DOWN	0x5000 -#define KEY_LEFT	0x4B00 -#define KEY_STATIONARY	0x4C00 -#define KEY_RIGHT	0x4D00 - -#define KEY_DOWN_LEFT	0x4F00 -#define KEY_DOWN_RIGHT	0x5100 -#define KEY_UP_LEFT	0x4700 -#define KEY_UP_RIGHT	0x4900 - -#define KEY_STATUSLN	0xd900	// F11 -#define KEY_PRIORITY	0xda00	// F12 - -#define KEY_PGUP	0x4900	// Page Up (fixed by Ziv Barber) -#define KEY_PGDN	0x5100	// Page Down -#define KEY_HOME	0x4700	// Home -#define KEY_END		0x4f00	// End * - -#define BUTTON_LEFT	0xF101	// Left mouse button -#define BUTTON_RIGHT	0xF202	// Right mouse button -#define WHEEL_UP	0xF203	// Mouse wheel up -#define WHEEL_DOWN	0xF204	// Mouse wheel down - -#define KEY_SCAN(k)	(k >> 8) -#define KEY_ASCII(k)	(k & 0xff) +#define AGI_KEY_BACKSPACE	0x08 +#define	AGI_KEY_ESCAPE	0x1B +#define AGI_KEY_ENTER	0x0D +#define AGI_KEY_UP		0x4800 +#define	AGI_KEY_DOWN	0x5000 +#define AGI_KEY_LEFT	0x4B00 +#define AGI_KEY_STATIONARY	0x4C00 +#define AGI_KEY_RIGHT	0x4D00 + +#define AGI_KEY_DOWN_LEFT	0x4F00 +#define AGI_KEY_DOWN_RIGHT	0x5100 +#define AGI_KEY_UP_LEFT	0x4700 +#define AGI_KEY_UP_RIGHT	0x4900 + +#define AGI_KEY_F1  0x3B00 +#define AGI_KEY_F2  0x3C00 +#define AGI_KEY_F3  0x3D00 +#define AGI_KEY_F4  0x3E00 +#define AGI_KEY_F5  0x3F00 +#define AGI_KEY_F6  0x4000 +#define AGI_KEY_F7  0x4000 +#define AGI_KEY_F8  0x4100 +#define AGI_KEY_F9  0x4200 +#define AGI_KEY_F10 0x4300 +#define AGI_KEY_F11 0xd900	// F11 +#define AGI_KEY_F12 0xda00	// F12 + +#define AGI_KEY_PAGE_UP		0x4900	// Page Up (fixed by Ziv Barber) +#define AGI_KEY_PAGE_DOWN	0x5100	// Page Down +#define AGI_KEY_HOME		0x4700	// Home +#define AGI_KEY_END			0x4f00	// End * + +#define AGI_MOUSE_BUTTON_LEFT	0xF101	// Left mouse button +#define AGI_MOUSE_BUTTON_RIGHT	0xF202	// Right mouse button +#define AGI_MOUSE_WHEEL_UP	0xF203	// Mouse wheel up +#define AGI_MOUSE_WHEEL_DOWN	0xF204	// Mouse wheel down + +// special menu triggers +// Attention: at least Mixed Up Mother Goose on Apple IIgs actually hooks ESC for menu only +// Which is why we have to check, if the corresponding trigger is hooked before changing it +// And otherwise simply use the regular ESC. +#define AGI_MENU_TRIGGER_PC       0x001B // will trigger menu for PC +#define AGI_MENU_TRIGGER_APPLE2GS 0x0301 // will trigger menu for AppleIIgs + Amiga +#define AGI_MENU_TRIGGER_ATARIST  0x0101 // will trigger menu for Atari ST  extern const uint8 scancodeTable[]; diff --git a/engines/agi/loader_v1.cpp b/engines/agi/loader_v1.cpp index 404fb6ee0e..fd418f3d59 100644 --- a/engines/agi/loader_v1.cpp +++ b/engines/agi/loader_v1.cpp @@ -21,6 +21,8 @@   */  #include "agi/agi.h" +#include "agi/words.h" +  #include "common/md5.h"  #define IMAGE_SIZE 368640 // = 40 * 2 * 9 * 512 = tracks * sides * sectors * sector size @@ -69,7 +71,7 @@ int AgiLoader_v1::loadDir_DDP(AgiDir *agid, int offset, int max) {  		return errBadFileOpen;  	// Cleanup -	for (int i = 0; i < MAX_DIRS; i++) { +	for (int i = 0; i < MAX_DIRECTORY_ENTRIES; i++) {  		agid[i].volume = 0xFF;  		agid[i].offset = _EMPTY;  	} @@ -103,7 +105,7 @@ int AgiLoader_v1::loadDir_BC(AgiDir *agid, int offset, int max) {  		return errBadFileOpen;  	// Cleanup -	for (int i = 0; i < MAX_DIRS; i++) { +	for (int i = 0; i < MAX_DIRECTORY_ENTRIES; i++) {  		agid[i].volume = 0xFF;  		agid[i].offset = _EMPTY;  	} @@ -197,84 +199,84 @@ uint8 *AgiLoader_v1::loadVolRes(struct AgiDir *agid) {  	return data;  } -int AgiLoader_v1::loadResource(int t, int n) { +int AgiLoader_v1::loadResource(int16 resourceType, int16 resourceNr) {  	int ec = errOK;  	uint8 *data = NULL; -	debugC(3, kDebugLevelResources, "(t = %d, n = %d)", t, n); -	if (n >= MAX_DIRS) +	debugC(3, kDebugLevelResources, "(t = %d, n = %d)", resourceType, resourceNr); +	if (resourceNr >= MAX_DIRECTORY_ENTRIES)  		return errBadResource; -	switch (t) { -	case rLOGIC: -		if (~_vm->_game.dirLogic[n].flags & RES_LOADED) { -			debugC(3, kDebugLevelResources, "loading logic resource %d", n); -			unloadResource(rLOGIC, n); +	switch (resourceType) { +	case RESOURCETYPE_LOGIC: +		if (~_vm->_game.dirLogic[resourceNr].flags & RES_LOADED) { +			debugC(3, kDebugLevelResources, "loading logic resource %d", resourceNr); +			unloadResource(RESOURCETYPE_LOGIC, resourceNr);  			// load raw resource into data -			data = loadVolRes(&_vm->_game.dirLogic[n]); +			data = loadVolRes(&_vm->_game.dirLogic[resourceNr]); -			_vm->_game.logics[n].data = data; -			ec = data ? _vm->decodeLogic(n) : errBadResource; +			_vm->_game.logics[resourceNr].data = data; +			ec = data ? _vm->decodeLogic(resourceNr) : errBadResource; -			_vm->_game.logics[n].sIP = 2; +			_vm->_game.logics[resourceNr].sIP = 2;  		}  		// if logic was cached, we get here  		// reset code pointers incase it was cached -		_vm->_game.logics[n].cIP = _vm->_game.logics[n].sIP; +		_vm->_game.logics[resourceNr].cIP = _vm->_game.logics[resourceNr].sIP;  		break; -	case rPICTURE: +	case RESOURCETYPE_PICTURE:  		// if picture is currently NOT loaded *OR* cacheing is off,  		// unload the resource (caching == off) and reload it -		debugC(3, kDebugLevelResources, "loading picture resource %d", n); -		if (_vm->_game.dirPic[n].flags & RES_LOADED) +		debugC(3, kDebugLevelResources, "loading picture resource %d", resourceNr); +		if (_vm->_game.dirPic[resourceNr].flags & RES_LOADED)  			break;  		// if loaded but not cached, unload it  		// if cached but not loaded, etc -		unloadResource(rPICTURE, n); -		data = loadVolRes(&_vm->_game.dirPic[n]); +		unloadResource(RESOURCETYPE_PICTURE, resourceNr); +		data = loadVolRes(&_vm->_game.dirPic[resourceNr]);  		if (data != NULL) { -			_vm->_game.pictures[n].rdata = data; -			_vm->_game.dirPic[n].flags |= RES_LOADED; +			_vm->_game.pictures[resourceNr].rdata = data; +			_vm->_game.dirPic[resourceNr].flags |= RES_LOADED;  		} else {  			ec = errBadResource;  		}  		break; -	case rSOUND: -		debugC(3, kDebugLevelResources, "loading sound resource %d", n); -		if (_vm->_game.dirSound[n].flags & RES_LOADED) +	case RESOURCETYPE_SOUND: +		debugC(3, kDebugLevelResources, "loading sound resource %d", resourceNr); +		if (_vm->_game.dirSound[resourceNr].flags & RES_LOADED)  			break; -		data = loadVolRes(&_vm->_game.dirSound[n]); +		data = loadVolRes(&_vm->_game.dirSound[resourceNr]);  		if (data != NULL) {  			// Freeing of the raw resource from memory is delegated to the createFromRawResource-function -			_vm->_game.sounds[n] = AgiSound::createFromRawResource(data, _vm->_game.dirSound[n].len, n, _vm->_soundemu); -			_vm->_game.dirSound[n].flags |= RES_LOADED; +			_vm->_game.sounds[resourceNr] = AgiSound::createFromRawResource(data, _vm->_game.dirSound[resourceNr].len, resourceNr, _vm->_soundemu); +			_vm->_game.dirSound[resourceNr].flags |= RES_LOADED;  		} else {  			ec = errBadResource;  		}  		break; -	case rVIEW: +	case RESOURCETYPE_VIEW:  		// Load a VIEW resource into memory...  		// Since VIEWS alter the view table ALL the time  		// can we cache the view? or must we reload it all  		// the time? -		if (_vm->_game.dirView[n].flags & RES_LOADED) +		if (_vm->_game.dirView[resourceNr].flags & RES_LOADED)  			break; -		debugC(3, kDebugLevelResources, "loading view resource %d", n); -		unloadResource(rVIEW, n); -		data = loadVolRes(&_vm->_game.dirView[n]); +		debugC(3, kDebugLevelResources, "loading view resource %d", resourceNr); +		unloadResource(RESOURCETYPE_VIEW, resourceNr); +		data = loadVolRes(&_vm->_game.dirView[resourceNr]);  		if (data) { -			_vm->_game.views[n].rdata = data; -			_vm->_game.dirView[n].flags |= RES_LOADED; -			ec = _vm->decodeView(n); +			_vm->_game.dirView[resourceNr].flags |= RES_LOADED; +			ec = _vm->decodeView(data, _vm->_game.dirView[resourceNr].len, resourceNr); +			free(data);  		} else {  			ec = errBadResource;  		} @@ -287,19 +289,19 @@ int AgiLoader_v1::loadResource(int t, int n) {  	return ec;  } -int AgiLoader_v1::unloadResource(int t, int n) { -	switch (t) { -	case rLOGIC: -		_vm->unloadLogic(n); +int AgiLoader_v1::unloadResource(int16 resourceType, int16 resourceNr) { +	switch (resourceType) { +	case RESOURCETYPE_LOGIC: +		_vm->unloadLogic(resourceNr);  		break; -	case rPICTURE: -		_vm->_picture->unloadPicture(n); +	case RESOURCETYPE_PICTURE: +		_vm->_picture->unloadPicture(resourceNr);  		break; -	case rVIEW: -		_vm->unloadView(n); +	case RESOURCETYPE_VIEW: +		_vm->unloadView(resourceNr);  		break; -	case rSOUND: -		_vm->_sound->unloadSound(n); +	case RESOURCETYPE_SOUND: +		_vm->_sound->unloadSound(resourceNr);  		break;  	} @@ -321,7 +323,7 @@ int AgiLoader_v1::loadWords(const char *fname) {  		Common::File f;  		f.open(_filenameDisk0);  		f.seek(BC_WORDS, SEEK_SET); -		return _vm->loadWords_v1(f); +		return _vm->_words->loadDictionary_v1(f);  	}  	return errOK;  } diff --git a/engines/agi/loader_v2.cpp b/engines/agi/loader_v2.cpp index 693c53c2bf..76f6900e44 100644 --- a/engines/agi/loader_v2.cpp +++ b/engines/agi/loader_v2.cpp @@ -23,6 +23,7 @@  #include "common/textconsole.h"  #include "agi/agi.h" +#include "agi/words.h"  namespace Agi { @@ -60,7 +61,7 @@ int AgiLoader_v2::loadDir(AgiDir *agid, const char *fname) {  	fp.read(mem, flen);  	// set all directory resources to gone -	for (i = 0; i < MAX_DIRS; i++) { +	for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++) {  		agid[i].volume = 0xff;  		agid[i].offset = _EMPTY;  	} @@ -107,21 +108,21 @@ int AgiLoader_v2::deinit() {  	return ec;  } -int AgiLoader_v2::unloadResource(int t, int n) { +int AgiLoader_v2::unloadResource(int16 resourceType, int16 resourceNr) {  	debugC(3, kDebugLevelResources, "unload resource"); -	switch (t) { -	case rLOGIC: -		_vm->unloadLogic(n); +	switch (resourceType) { +	case RESOURCETYPE_LOGIC: +		_vm->unloadLogic(resourceNr);  		break; -	case rPICTURE: -		_vm->_picture->unloadPicture(n); +	case RESOURCETYPE_PICTURE: +		_vm->_picture->unloadPicture(resourceNr);  		break; -	case rVIEW: -		_vm->unloadView(n); +	case RESOURCETYPE_VIEW: +		_vm->unloadView(resourceNr);  		break; -	case rSOUND: -		_vm->_sound->unloadSound(n); +	case RESOURCETYPE_SOUND: +		_vm->_sound->unloadSound(resourceNr);  		break;  	} @@ -129,7 +130,7 @@ int AgiLoader_v2::unloadResource(int t, int n) {  }  /** - * This function does noting but load a raw resource into memory, + * This function loads a raw resource into memory,   * if further decoding is required, it must be done by another   * routine. NULL is returned if unsucsessfull.   */ @@ -173,84 +174,84 @@ uint8 *AgiLoader_v2::loadVolRes(struct AgiDir *agid) {   * Loads a resource into memory, a raw resource is loaded in   * with above routine, then further decoded here.   */ -int AgiLoader_v2::loadResource(int t, int n) { +int AgiLoader_v2::loadResource(int16 resourceType, int16 resourceNr) {  	int ec = errOK;  	uint8 *data = NULL; -	debugC(3, kDebugLevelResources, "(t = %d, n = %d)", t, n); -	if (n >= MAX_DIRS) +	debugC(3, kDebugLevelResources, "(t = %d, n = %d)", resourceType, resourceNr); +	if (resourceNr >= MAX_DIRECTORY_ENTRIES)  		return errBadResource; -	switch (t) { -	case rLOGIC: -		if (~_vm->_game.dirLogic[n].flags & RES_LOADED) { -			debugC(3, kDebugLevelResources, "loading logic resource %d", n); -			unloadResource(rLOGIC, n); +	switch (resourceType) { +	case RESOURCETYPE_LOGIC: +		if (~_vm->_game.dirLogic[resourceNr].flags & RES_LOADED) { +			debugC(3, kDebugLevelResources, "loading logic resource %d", resourceNr); +			unloadResource(RESOURCETYPE_LOGIC, resourceNr);  			// load raw resource into data -			data = loadVolRes(&_vm->_game.dirLogic[n]); +			data = loadVolRes(&_vm->_game.dirLogic[resourceNr]); -			_vm->_game.logics[n].data = data; -			ec = data ? _vm->decodeLogic(n) : errBadResource; +			_vm->_game.logics[resourceNr].data = data; +			ec = data ? _vm->decodeLogic(resourceNr) : errBadResource; -			_vm->_game.logics[n].sIP = 2; +			_vm->_game.logics[resourceNr].sIP = 2;  		}  		// if logic was cached, we get here  		// reset code pointers incase it was cached -		_vm->_game.logics[n].cIP = _vm->_game.logics[n].sIP; +		_vm->_game.logics[resourceNr].cIP = _vm->_game.logics[resourceNr].sIP;  		break; -	case rPICTURE: +	case RESOURCETYPE_PICTURE:  		// if picture is currently NOT loaded *OR* cacheing is off,  		// unload the resource (caching == off) and reload it -		debugC(3, kDebugLevelResources, "loading picture resource %d", n); -		if (_vm->_game.dirPic[n].flags & RES_LOADED) +		debugC(3, kDebugLevelResources, "loading picture resource %d", resourceNr); +		if (_vm->_game.dirPic[resourceNr].flags & RES_LOADED)  			break;  		// if loaded but not cached, unload it  		// if cached but not loaded, etc -		unloadResource(rPICTURE, n); -		data = loadVolRes(&_vm->_game.dirPic[n]); +		unloadResource(RESOURCETYPE_PICTURE, resourceNr); +		data = loadVolRes(&_vm->_game.dirPic[resourceNr]);  		if (data != NULL) { -			_vm->_game.pictures[n].rdata = data; -			_vm->_game.dirPic[n].flags |= RES_LOADED; +			_vm->_game.pictures[resourceNr].rdata = data; +			_vm->_game.dirPic[resourceNr].flags |= RES_LOADED;  		} else {  			ec = errBadResource;  		}  		break; -	case rSOUND: -		debugC(3, kDebugLevelResources, "loading sound resource %d", n); -		if (_vm->_game.dirSound[n].flags & RES_LOADED) +	case RESOURCETYPE_SOUND: +		debugC(3, kDebugLevelResources, "loading sound resource %d", resourceNr); +		if (_vm->_game.dirSound[resourceNr].flags & RES_LOADED)  			break; -		data = loadVolRes(&_vm->_game.dirSound[n]); +		data = loadVolRes(&_vm->_game.dirSound[resourceNr]);  		if (data != NULL) {  			// Freeing of the raw resource from memory is delegated to the createFromRawResource-function -			_vm->_game.sounds[n] = AgiSound::createFromRawResource(data, _vm->_game.dirSound[n].len, n, _vm->_soundemu); -			_vm->_game.dirSound[n].flags |= RES_LOADED; +			_vm->_game.sounds[resourceNr] = AgiSound::createFromRawResource(data, _vm->_game.dirSound[resourceNr].len, resourceNr, _vm->_soundemu); +			_vm->_game.dirSound[resourceNr].flags |= RES_LOADED;  		} else {  			ec = errBadResource;  		}  		break; -	case rVIEW: +	case RESOURCETYPE_VIEW:  		// Load a VIEW resource into memory...  		// Since VIEWS alter the view table ALL the time  		// can we cache the view? or must we reload it all  		// the time? -		if (_vm->_game.dirView[n].flags & RES_LOADED) +		if (_vm->_game.dirView[resourceNr].flags & RES_LOADED)  			break; -		debugC(3, kDebugLevelResources, "loading view resource %d", n); -		unloadResource(rVIEW, n); -		data = loadVolRes(&_vm->_game.dirView[n]); +		debugC(3, kDebugLevelResources, "loading view resource %d", resourceNr); +		unloadResource(RESOURCETYPE_VIEW, resourceNr); +		data = loadVolRes(&_vm->_game.dirView[resourceNr]);  		if (data) { -			_vm->_game.views[n].rdata = data; -			_vm->_game.dirView[n].flags |= RES_LOADED; -			ec = _vm->decodeView(n); +			_vm->_game.dirView[resourceNr].flags |= RES_LOADED; +			ec = _vm->decodeView(data, _vm->_game.dirView[resourceNr].len, resourceNr); +			free(data);  		} else {  			ec = errBadResource;  		} @@ -268,7 +269,7 @@ int AgiLoader_v2::loadObjects(const char *fname) {  }  int AgiLoader_v2::loadWords(const char *fname) { -	return _vm->loadWords(fname); +	return _vm->_words->loadDictionary(fname);  }  } // End of namespace Agi diff --git a/engines/agi/loader_v3.cpp b/engines/agi/loader_v3.cpp index 39759b4649..d7abbac0bd 100644 --- a/engines/agi/loader_v3.cpp +++ b/engines/agi/loader_v3.cpp @@ -22,6 +22,7 @@  #include "agi/agi.h"  #include "agi/lzw.h" +#include "agi/words.h"  #include "common/config-manager.h"  #include "common/fs.h" @@ -76,7 +77,7 @@ int AgiLoader_v3::loadDir(struct AgiDir *agid, Common::File *fp,  		fp->read(mem, len);  		// set all directory resources to gone -		for (i = 0; i < MAX_DIRS; i++) { +		for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++) {  			agid[i].volume = 0xff;  			agid[i].offset = _EMPTY;  		} @@ -171,19 +172,19 @@ int AgiLoader_v3::deinit() {  	return ec;  } -int AgiLoader_v3::unloadResource(int t, int n) { -	switch (t) { -	case rLOGIC: -		_vm->unloadLogic(n); +int AgiLoader_v3::unloadResource(int16 resourceType, int16 resourceNr) { +	switch (resourceType) { +	case RESOURCETYPE_LOGIC: +		_vm->unloadLogic(resourceNr);  		break; -	case rPICTURE: -		_vm->_picture->unloadPicture(n); +	case RESOURCETYPE_PICTURE: +		_vm->_picture->unloadPicture(resourceNr);  		break; -	case rVIEW: -		_vm->unloadView(n); +	case RESOURCETYPE_VIEW: +		_vm->unloadView(resourceNr);  		break; -	case rSOUND: -		_vm->_sound->unloadSound(n); +	case RESOURCETYPE_SOUND: +		_vm->_sound->unloadSound(resourceNr);  		break;  	} @@ -191,7 +192,7 @@ int AgiLoader_v3::unloadResource(int t, int n) {  }  /** - * This function does noting but load a raw resource into memory. + * This function loads a raw resource into memory.   * If further decoding is required, it must be done by another   * routine.   * @@ -225,7 +226,11 @@ uint8 *AgiLoader_v3::loadVolRes(AgiDir *agid) {  		fp.read(compBuffer, agid->clen);  		if (x[2] & 0x80) { // compressed pic -			data = _vm->_picture->convertV3Pic(compBuffer, agid->clen); +			// effectively uncompressed, but having only 4-bit parameters for F0 / F2 commands +			// Manhunter 2 uses such pictures +			data = compBuffer; +			agid->flags |= RES_PICTURE_V3_NIBBLE_PARM; +			//data = _vm->_picture->convertV3Pic(compBuffer, agid->clen);  			// compBuffer has been freed inside convertV3Pic()  		} else if (agid->len == agid->clen) {  			// do not decompress @@ -252,31 +257,31 @@ uint8 *AgiLoader_v3::loadVolRes(AgiDir *agid) {   * Loads a resource into memory, a raw resource is loaded in   * with above routine, then further decoded here.   */ -int AgiLoader_v3::loadResource(int t, int n) { +int AgiLoader_v3::loadResource(int16 resourceType, int16 resourceNr) {  	int ec = errOK;  	uint8 *data = NULL; -	if (n >= MAX_DIRS) +	if (resourceNr >= MAX_DIRECTORY_ENTRIES)  		return errBadResource; -	switch (t) { -	case rLOGIC: +	switch (resourceType) { +	case RESOURCETYPE_LOGIC:  		// load resource into memory, decrypt messages at the end  		// and build the message list (if logic is in memory) -		if (~_vm->_game.dirLogic[n].flags & RES_LOADED) { +		if (~_vm->_game.dirLogic[resourceNr].flags & RES_LOADED) {  			// if logic is already in memory, unload it -			unloadResource(rLOGIC, n); +			unloadResource(RESOURCETYPE_LOGIC, resourceNr);  			// load raw resource into data -			data = loadVolRes(&_vm->_game.dirLogic[n]); -			_vm->_game.logics[n].data = data; +			data = loadVolRes(&_vm->_game.dirLogic[resourceNr]); +			_vm->_game.logics[resourceNr].data = data;  			// uncompressed logic files need to be decrypted  			if (data != NULL) {  				// resloaded flag gets set by decode logic  				// needed to build string table -				ec = _vm->decodeLogic(n); -				_vm->_game.logics[n].sIP = 2; +				ec = _vm->decodeLogic(resourceNr); +				_vm->_game.logics[resourceNr].sIP = 2;  			} else {  				ec = errBadResource;  			} @@ -284,56 +289,56 @@ int AgiLoader_v3::loadResource(int t, int n) {  			// logics[n].sIP=2; // saved IP = 2  			// logics[n].cIP=2; // current IP = 2 -			_vm->_game.logics[n].cIP = _vm->_game.logics[n].sIP; +			_vm->_game.logics[resourceNr].cIP = _vm->_game.logics[resourceNr].sIP;  		}  		// if logic was cached, we get here  		// reset code pointers incase it was cached -		_vm->_game.logics[n].cIP = _vm->_game.logics[n].sIP; +		_vm->_game.logics[resourceNr].cIP = _vm->_game.logics[resourceNr].sIP;  		break; -	case rPICTURE: +	case RESOURCETYPE_PICTURE:  		// if picture is currently NOT loaded *OR* cacheing is off,  		// unload the resource (caching==off) and reload it -		if (~_vm->_game.dirPic[n].flags & RES_LOADED) { -			unloadResource(rPICTURE, n); -			data = loadVolRes(&_vm->_game.dirPic[n]); +		if (~_vm->_game.dirPic[resourceNr].flags & RES_LOADED) { +			unloadResource(RESOURCETYPE_PICTURE, resourceNr); +			data = loadVolRes(&_vm->_game.dirPic[resourceNr]);  			if (data != NULL) { -				_vm->_game.pictures[n].rdata = data; -				_vm->_game.dirPic[n].flags |= RES_LOADED; +				_vm->_game.pictures[resourceNr].rdata = data; +				_vm->_game.dirPic[resourceNr].flags |= RES_LOADED;  			} else {  				ec = errBadResource;  			}  		}  		break; -	case rSOUND: -		if (_vm->_game.dirSound[n].flags & RES_LOADED) +	case RESOURCETYPE_SOUND: +		if (_vm->_game.dirSound[resourceNr].flags & RES_LOADED)  			break; -		data = loadVolRes(&_vm->_game.dirSound[n]); +		data = loadVolRes(&_vm->_game.dirSound[resourceNr]);  		if (data != NULL) {  			// Freeing of the raw resource from memory is delegated to the createFromRawResource-function -			_vm->_game.sounds[n] = AgiSound::createFromRawResource(data, _vm->_game.dirSound[n].len, n, _vm->_soundemu); -			_vm->_game.dirSound[n].flags |= RES_LOADED; +			_vm->_game.sounds[resourceNr] = AgiSound::createFromRawResource(data, _vm->_game.dirSound[resourceNr].len, resourceNr, _vm->_soundemu); +			_vm->_game.dirSound[resourceNr].flags |= RES_LOADED;  		} else {  			ec = errBadResource;  		}  		break; -	case rVIEW: +	case RESOURCETYPE_VIEW:  		// Load a VIEW resource into memory...  		// Since VIEWS alter the view table ALL the time can we  		// cache the view? or must we reload it all the time?  		//  		// load a raw view from a VOL file into data -		if (_vm->_game.dirView[n].flags & RES_LOADED) +		if (_vm->_game.dirView[resourceNr].flags & RES_LOADED)  			break; -		unloadResource(rVIEW, n); -		data = loadVolRes(&_vm->_game.dirView[n]); +		unloadResource(RESOURCETYPE_VIEW, resourceNr); +		data = loadVolRes(&_vm->_game.dirView[resourceNr]);  		if (data != NULL) { -			_vm->_game.views[n].rdata = data; -			_vm->_game.dirView[n].flags |= RES_LOADED; -			ec = _vm->decodeView(n); +			_vm->_game.dirView[resourceNr].flags |= RES_LOADED; +			ec = _vm->decodeView(data, _vm->_game.dirView[resourceNr].len, resourceNr); +			free(data);  		} else {  			ec = errBadResource;  		} @@ -351,7 +356,7 @@ int AgiLoader_v3::loadObjects(const char *fname) {  }  int AgiLoader_v3::loadWords(const char *fname) { -	return _vm->loadWords(fname); +	return _vm->_words->loadDictionary(fname);  }  } // End of namespace Agi diff --git a/engines/agi/logic.cpp b/engines/agi/logic.cpp index 7429b117c8..1aeb7cc3de 100644 --- a/engines/agi/logic.cpp +++ b/engines/agi/logic.cpp @@ -30,15 +30,16 @@ namespace Agi {   * into a message list.   * @param n  The number of the logic resource to decode.   */ -int AgiEngine::decodeLogic(int n) { +int AgiEngine::decodeLogic(int16 logicNr) {  	int ec = errOK;  	int mstart, mend, mc;  	uint8 *m0; +	AgiLogic *curLogic = &_game.logics[logicNr];  	// decrypt messages at end of logic + build message list  	// report ("decoding logic #%d\n", n); -	m0 = _game.logics[n].data; +	m0 = curLogic->data;  	mstart = READ_LE_UINT16(m0) + 2;  	mc = *(m0 + mstart); @@ -48,38 +49,38 @@ int AgiEngine::decodeLogic(int n) {  	// if the logic was not compressed, decrypt the text messages  	// only if there are more than 0 messages -	if ((~_game.dirLogic[n].flags & RES_COMPRESSED) && mc > 0) +	if ((~_game.dirLogic[logicNr].flags & RES_COMPRESSED) && mc > 0)  		decrypt(m0 + mstart, mend - mstart);	// decrypt messages  	// build message list -	m0 = _game.logics[n].data; +	m0 = curLogic->data;  	mstart = READ_LE_UINT16(m0) + 2;	// +2 covers pointer -	_game.logics[n].numTexts = *(m0 + mstart); +	_game.logics[logicNr].numTexts = *(m0 + mstart);  	// resetp logic pointers -	_game.logics[n].sIP = 2; -	_game.logics[n].cIP = 2; -	_game.logics[n].size = READ_LE_UINT16(m0) + 2;	// logic end pointer +	curLogic->sIP = 2; +	curLogic->cIP = 2; +	curLogic->size = READ_LE_UINT16(m0) + 2;	// logic end pointer  	// allocate list of pointers to point into our data -	_game.logics[n].texts = (const char **)calloc(1 + _game.logics[n].numTexts, sizeof(char *)); +	curLogic->texts = (const char **)calloc(1 + curLogic->numTexts, sizeof(char *));  	// cover header info  	m0 += mstart + 3; -	if (_game.logics[n].texts != NULL) { +	if (curLogic->texts != NULL) {  		// move list of strings into list to make real pointers -		for (mc = 0; mc < _game.logics[n].numTexts; mc++) { +		for (mc = 0; mc < curLogic->numTexts; mc++) {  			mend = READ_LE_UINT16(m0 + mc * 2); -			_game.logics[n].texts[mc] = mend ? (const char *)m0 + mend - 2 : (const char *)""; +			_game.logics[logicNr].texts[mc] = mend ? (const char *)m0 + mend - 2 : (const char *)"";  		}  		// set loaded flag now its all completly loaded -		_game.dirLogic[n].flags |= RES_LOADED; +		_game.dirLogic[logicNr].flags |= RES_LOADED;  	} else {  		// unload data  		// Note that not every logic has text -		free(_game.logics[n].data); +		free(curLogic->data);  		ec = errNotEnoughMemory;  	} @@ -92,18 +93,17 @@ int AgiEngine::decodeLogic(int n) {   * memory chunks allocated for this resource.   * @param n  The number of the logic resource to unload   */ -void AgiEngine::unloadLogic(int n) { -	if (_game.dirLogic[n].flags & RES_LOADED) { -		free(_game.logics[n].data); -		if (_game.logics[n].numTexts) -			free(_game.logics[n].texts); -		_game.logics[n].numTexts = 0; -		_game.dirLogic[n].flags &= ~RES_LOADED; +void AgiEngine::unloadLogic(int16 logicNr) { +	if (_game.dirLogic[logicNr].flags & RES_LOADED) { +		free(_game.logics[logicNr].data); +		free(_game.logics[logicNr].texts); +		_game.logics[logicNr].numTexts = 0; +		_game.dirLogic[logicNr].flags &= ~RES_LOADED;  	}  	// if cached, we end up here -	_game.logics[n].sIP = 2; -	_game.logics[n].cIP = 2; +	_game.logics[logicNr].sIP = 2; +	_game.logics[logicNr].cIP = 2;  }  } // End of namespace Agi diff --git a/engines/agi/menu.cpp b/engines/agi/menu.cpp index 008c208c82..411cd002fd 100644 --- a/engines/agi/menu.cpp +++ b/engines/agi/menu.cpp @@ -22,534 +22,456 @@  #include "agi/agi.h"  #include "agi/graphics.h" +#include "agi/text.h"  #include "agi/keyboard.h"  #include "agi/menu.h"  namespace Agi { -// TODO: add constructor/destructor for agi_menu, agi_menu_option - -struct AgiMenuOption { -	int enabled;			/**< option is enabled or disabled */ -	int event;			/**< menu event */ -	int index;			/**< number of option in this menu */ -	char *text;			/**< text of menu option */ -}; - -struct AgiMenu { -	MenuOptionList down;		/**< list head for menu options */ -	int index;			/**< number of menu in menubar */ -	int width;			/**< width of menu in characters */ -	int height;			/**< height of menu in characters */ -	int col;			/**< column of menubar entry */ -	int wincol;			/**< column of menu window */ -	char *text;			/**< menu name */ -}; - -AgiMenu *Menu::getMenu(int i) { -	MenuList::iterator iter; -	for (iter = _menubar.begin(); iter != _menubar.end(); ++iter) { -		AgiMenu *m = *iter; -		if (m->index == i) -			return m; -	} -	return NULL; -} - -AgiMenuOption *Menu::getMenuOption(int i, int j) { -	AgiMenu *m = getMenu(i); -	MenuOptionList::iterator iter; - -	for (iter = m->down.begin(); iter != m->down.end(); ++iter) { -		AgiMenuOption* d = *iter; -		if (d->index == j) -			return d; -	} - -	return NULL; -} +GfxMenu::GfxMenu(AgiEngine *vm, GfxMgr *gfx, PictureMgr *picture, TextMgr *text) { +	_vm = vm; +	_gfx = gfx; +	_picture = picture; +	_text = text; -void Menu::drawMenuBar() { -	_vm->clearLines(0, 0, MENU_BG); -	_vm->flushLines(0, 0); +	_allowed = true; +	_submitted = false; +	_delayedExecute = false; -	MenuList::iterator iter; -	for (iter = _menubar.begin(); iter != _menubar.end(); ++iter) { -		AgiMenu *m = *iter; - -		_vm->printText(m->text, 0, m->col, 0, 40, MENU_FG, MENU_BG); -	} +	_setupMenuColumn = 1; +	_setupMenuItemColumn = 1; +	_selectedMenuNr = 0; +	_selectedMenuHeight = 0; +	_selectedMenuWidth = 0; +	_selectedMenuRow = 0; +	_selectedMenuColumn = 0;  } -void Menu::drawMenuHilite(int curMenu) { -	AgiMenu *m = getMenu(curMenu); - -	debugC(6, kDebugLevelMenu, "[%s]", m->text); +GfxMenu::~GfxMenu() { +	for (GuiMenuArray::iterator itemIter = _array.begin(); itemIter != _array.end(); ++itemIter) +		delete *itemIter; +	_array.clear(); -	_vm->printText(m->text, 0, m->col, 0, 40, MENU_BG, MENU_FG); -	_vm->flushLines(0, 0); +	for (GuiMenuItemArray::iterator menuIter = _itemArray.begin(); menuIter != _itemArray.end(); ++menuIter) +		delete *menuIter; +	_itemArray.clear();  } -// draw box and pulldowns. -void Menu::drawMenuOption(int hMenu) { -	// find which vertical menu it is -	AgiMenu *m = getMenu(hMenu); - -	_gfx->drawBox(m->wincol * CHAR_COLS, 1 * CHAR_LINES, (m->wincol + m->width + 2) * CHAR_COLS, -			(1 + m->height + 2) * CHAR_LINES, MENU_BG, MENU_LINE, 0); +void GfxMenu::addMenu(const char *menuText) { +	// already submitted? in that case no further changes possible +	if (_submitted) +		return; -	MenuOptionList::iterator iter; +	GuiMenuEntry *menuEntry = new GuiMenuEntry(); -	for (iter = m->down.begin(); iter != m->down.end(); ++iter) { -		AgiMenuOption* d = *iter; +	menuEntry->text = menuText; +	menuEntry->textLen = menuEntry->text.size(); +	menuEntry->row = 0; +	menuEntry->column = _setupMenuColumn; +	menuEntry->itemCount = 0; +	menuEntry->firstItemNr = _itemArray.size(); +	menuEntry->selectedItemNr = menuEntry->firstItemNr; +	menuEntry->maxItemTextLen = 0; +	_array.push_back(menuEntry); -		_vm->printText(d->text, 0, m->wincol + 1, d->index + 2, m->width + 2, -				MENU_FG, MENU_BG, !d->enabled); -	} +	_setupMenuColumn += menuEntry->textLen + 1;  } -void Menu::drawMenuOptionHilite(int hMenu, int vMenu) { -	AgiMenu *m = getMenu(hMenu); -	AgiMenuOption *d = getMenuOption(hMenu, vMenu); +void GfxMenu::addMenuItem(const char *menuItemText, uint16 controllerSlot) { +	int16 arrayCount = _array.size(); -	// Disabled menu items are "greyed out" with a checkerboard effect, -	// rather than having a different color. -- dsymonds -	_vm->printText(d->text, 0, m->wincol + 1, vMenu + 2, m->width + 2, -			MENU_BG, MENU_FG, !d->enabled); -} +	// already submitted? in that case no further changes possible +	if (_submitted) +		return; -void Menu::newMenuSelected(int i) { -	_picture->showPic(); -	drawMenuBar(); -	drawMenuHilite(i); -	drawMenuOption(i); -} +	if (arrayCount == 0) +		error("tried to add a menu item before adding an actual menu"); -bool Menu::mouseOverText(int line, int col, char *s) { -	if (_vm->_mouse.x < col * CHAR_COLS) -		return false; +	// go to latest menu entry +	GuiMenuEntry *curMenuEntry = _array.back(); -	if (_vm->_mouse.x > (int)(col + strlen(s)) * CHAR_COLS) -		return false; +	GuiMenuItemEntry *menuItemEntry = new GuiMenuItemEntry(); -	if (_vm->_mouse.y < line * CHAR_LINES) -		return false; +	menuItemEntry->enabled = true; +	menuItemEntry->text = menuItemText; +	menuItemEntry->textLen = menuItemEntry->text.size(); +	menuItemEntry->controllerSlot = controllerSlot; -	if (_vm->_mouse.y >= (line + 1) * CHAR_LINES) -		return false; +	// Original interpreter on PC used the length of the first item for drawing +	// At least in KQ2 on Apple IIgs follow-up items are longer, which would result in graphic glitches. +	// That's why we remember the longest item and draw according to that +	if (curMenuEntry->maxItemTextLen < menuItemEntry->textLen) { +		curMenuEntry->maxItemTextLen = menuItemEntry->textLen; +	} -	return true; -} +	if (curMenuEntry->itemCount == 0) { +		// for first menu item of menu calculated column +		if (menuItemEntry->textLen + curMenuEntry->column < (FONT_COLUMN_CHARACTERS - 1)) { +			_setupMenuItemColumn = curMenuEntry->column; +		} else { +			_setupMenuItemColumn = ( FONT_COLUMN_CHARACTERS - 1 ) - menuItemEntry->textLen; +		} +	} -#if 0 -static void add_about_option() { -	const char *text = "About AGI engine"; - -	agi_menu_option *d = new agi_menu_option; -	d->text = strdup(text); -	d->enabled = true; -	d->event = 255; -	d->index = (v_max_menu[0] += 1); - -	agi_menu *m = *menubar.begin(); -	m->down.push_back(d); -	m->height++; -	if (m->width < (int)strlen(text)) -		m->width = strlen(text); -} -#endif +	menuItemEntry->row = 2 + curMenuEntry->itemCount; +	menuItemEntry->column = _setupMenuItemColumn; -/* - * Public functions - */ +	_itemArray.push_back(menuItemEntry); -Menu::Menu(AgiEngine *vm, GfxMgr *gfx, PictureMgr *picture) { -	_vm = vm; -	_gfx = gfx; -	_picture = picture; -	_hIndex = 0; -	_hCol = 1; -	_hMaxMenu = 0; -	_hCurMenu = 0; -	_vCurMenu = 0; +	curMenuEntry->itemCount++;  } -Menu::~Menu() { -	MenuList::iterator iterh; -	for (iterh = _menubar.reverse_begin(); iterh != _menubar.end(); ) { -		AgiMenu *m = *iterh; - -		debugC(3, kDebugLevelMenu, "deiniting hmenu %s", m->text); - -		MenuOptionList::iterator iterv; - -		for (iterv = m->down.reverse_begin(); iterv != m->down.end(); ) { -			AgiMenuOption *d = *iterv; - -			debugC(3, kDebugLevelMenu, "  deiniting vmenu %s", d->text); - -			free(d->text); -			delete d; +void GfxMenu::submit() { +	GuiMenuEntry *menuEntry = nullptr; +	GuiMenuItemEntry *menuItemEntry = nullptr; +	int16 menuCount = _array.size(); +	int16 menuNr = 0; +	int16 menuItemNr = 0; +	int16 menuItemLastNr = 0; + +	if ((_array.size() == 0) || (_itemArray.size() == 0)) +		return; + +	_submitted = true; + +	// WORKAROUND: For Apple II gs we try to fix the menu text +	// On this platform it seems a system font was used and the menu was drawn differently (probably system menu?) +	// Still the text was misaligned anyway, but it looks worse in our (the original PC) implementation +	// Atari ST SQ1 had one bad menu entry as well, we fix that too. +	switch (_vm->getPlatform()) { +	case Common::kPlatformApple2GS: +	case Common::kPlatformAtariST: +		// Go through all menus +		for (menuNr = 0; menuNr < menuCount; menuNr++) { +			menuEntry = _array[menuNr]; +			menuItemLastNr = menuEntry->firstItemNr + menuEntry->itemCount; + +			// Go through all items of current menu +			for (menuItemNr = menuEntry->firstItemNr; menuItemNr < menuItemLastNr; menuItemNr++) { +				menuItemEntry = _itemArray[menuItemNr]; + +				if (menuItemEntry->textLen < menuEntry->maxItemTextLen) { +					// current item text is shorter than the maximum? +					int16 missingCharCount = menuEntry->maxItemTextLen - menuItemEntry->textLen; + +					if (menuItemEntry->text.contains('>')) { +						// text contains '>', we now try to find a '<' +						// and then add spaces in case this item is shorter than the first item +						int16 textPos = menuItemEntry->textLen - 1; + +						while (textPos > 0) { +							if (menuItemEntry->text[textPos] == '<') +								break; +							textPos--; +						} + +						if (textPos > 0) { +							while (missingCharCount) { +								menuItemEntry->text.insertChar(' ', textPos); +								missingCharCount--; +							} +						} +					} else { +						// Also check if text consists only of '-', which is the separator +						// These were sometimes also too small +						int16 separatorCount = 0; +						int16 charPos = 0; + +						while (charPos < menuItemEntry->textLen) { +							if (menuItemEntry->text[charPos] != '-') +								break; +							separatorCount++; +							charPos++; +						} + +						if (separatorCount == menuItemEntry->textLen) { +							// Separator detected +							while (missingCharCount) { +								menuItemEntry->text.insertChar('-', 0); +								missingCharCount--; +							} +						} else { +							// Append spaces to the end to fill it up +							int16 textPos = menuItemEntry->textLen; +							while (missingCharCount) { +								menuItemEntry->text.insertChar(' ', textPos); +								textPos++; +								missingCharCount--; +							} +						} +					} -			iterv = m->down.reverse_erase(iterv); +					menuItemEntry->textLen = menuItemEntry->text.size(); +				} +			}  		} -		free(m->text); -		delete m; - -		iterh = _menubar.reverse_erase(iterh); +		break; +	default: +		break;  	}  } -void Menu::add(const char *s) { -	AgiMenu *m = new AgiMenu; -	m->text = strdup(s); - -	while (m->text[strlen(m->text) - 1] == ' ') -		m->text[strlen(m->text) - 1] = 0; - -	m->width = 0; -	m->height = 0; -	m->index = _hIndex++; -	m->col = _hCol; -	m->wincol = _hCol - 1; -	_vIndex = 0; -	_vMaxMenu[m->index] = 0; -	_hCol += strlen(m->text) + 1; -	_hMaxMenu = m->index; - -	debugC(3, kDebugLevelMenu, "add menu: '%s' %02x", s, m->text[strlen(m->text)]); -	_menubar.push_back(m); +void GfxMenu::itemEnable(uint16 controllerSlot) { +	itemEnableDisable(controllerSlot, true);  } -void Menu::addItem(const char *s, int code) { -	int l; - -	AgiMenuOption* d = new AgiMenuOption; - -	d->text = strdup(s); -	d->enabled = true; -	d->event = code; -	d->index = _vIndex++; - -	// add to last menu in list -	assert(_menubar.reverse_begin() != _menubar.end()); -	AgiMenu *m = *_menubar.reverse_begin(); -	m->height++; - -	_vMaxMenu[m->index] = d->index; - -	l = strlen(d->text); -	if (l > 40) -		l = 38; -	if (m->wincol + l > 38) -		m->wincol = 38 - l; -	if (l > m->width) -		m->width = l; - -	debugC(3, kDebugLevelMenu, "Adding menu item: %s (size = %d)", s, m->height); - -	m->down.push_back(d); +void GfxMenu::itemDisable(uint16 controllerSlot) { +	itemEnableDisable(controllerSlot, false);  } -void Menu::submit() { -	debugC(3, kDebugLevelMenu, "Submitting menu"); - -	// add_about_option (); - -	// If a menu has no options, delete it -	MenuList::iterator iter; -	for (iter = _menubar.reverse_begin(); iter != _menubar.end(); ) { -		AgiMenu *m = *iter; +void GfxMenu::itemEnableDisable(uint16 controllerSlot, bool enabled) { +	GuiMenuItemArray::iterator listIterator; +	GuiMenuItemArray::iterator listEnd = _itemArray.end(); +	GuiMenuItemEntry *menuItemEntry; -		if (m->down.empty()) { -			free(m->text); -			delete m; - -			_hMaxMenu--; - -			iter = _menubar.reverse_erase(iter); -		} else { -			--iter; +	listIterator = _itemArray.begin(); +	while (listIterator != listEnd) { +		menuItemEntry = *listIterator; +		if (menuItemEntry->controllerSlot == controllerSlot) { +			menuItemEntry->enabled = enabled;  		} + +		listIterator++;  	}  } -bool Menu::keyhandler(int key) { -	static int clockVal; -	static int menuActive = false; -	static int buttonUsed = 0; -	bool exitMenu = false; +void GfxMenu::itemEnableAll() { +	GuiMenuItemArray::iterator listIterator; +	GuiMenuItemArray::iterator listEnd = _itemArray.end(); +	GuiMenuItemEntry *menuItemEntry; -	if (!_vm->getflag(fMenusWork) && !(_vm->getFeatures() & GF_MENUS)) -		return false; +	listIterator = _itemArray.begin(); +	while (listIterator != listEnd) { +		menuItemEntry = *listIterator; +		menuItemEntry->enabled = true; -	if (!menuActive) { -		clockVal = _vm->_game.clockEnabled; -		_vm->_game.clockEnabled = false; -		drawMenuBar(); +		listIterator++;  	} +} -	// Mouse handling -	if (_vm->_mouse.button) { -		int hmenu, vmenu; - -		buttonUsed = 1;	// Button has been used at least once - -		if (_vm->_mouse.y <= CHAR_LINES) { -			// on the menubar -			hmenu = 0; +// return true, in case a menu was actually created and submitted by the scripts +bool GfxMenu::isAvailable() { +	return _submitted; +} -			MenuList::iterator iterh; +void GfxMenu::accessAllow() { +	_allowed = true; +} -			for (iterh = _menubar.begin(); iterh != _menubar.end(); ++iterh) { -				AgiMenu *m = *iterh; +void GfxMenu::accessDeny() { +	_allowed = false; +} -				if (mouseOverText(0, m->col, m->text)) { -					break; -				} else { -					hmenu++; -				} -			} +void GfxMenu::delayedExecute() { +	_delayedExecute = true; +} -			if (hmenu <= _hMaxMenu) { -				if (_hCurMenu != hmenu) { -					_vCurMenu = -1; -					newMenuSelected(hmenu); -				} -				_hCurMenu = hmenu; -			} -		} else { -			// not in menubar -			vmenu = 0; +bool GfxMenu::delayedExecuteActive() { +	return _delayedExecute; +} -			AgiMenu *m = getMenu(_hCurMenu); +void GfxMenu::execute() { +	_delayedExecute = false; -			MenuOptionList::iterator iterv; +	// got submitted? -> safety check +	if (!_submitted) +		return; -			for (iterv = m->down.begin(); iterv != m->down.end(); ++iterv) { -				AgiMenuOption *do1 = *iterv; +	// access allowed at the moment? +	if (!_allowed) +		return; -				if (mouseOverText(2 + do1->index, m->wincol + 1, do1->text)) { -					break; -				} else { -					vmenu++; -				} -			} +	_text->charPos_Push(); +	_text->charAttrib_Push(); +	_text->clearLine(0, _text->calculateTextBackground(15)); -			if (vmenu <= _vMaxMenu[_hCurMenu]) { -				if (_vCurMenu != vmenu) { -					drawMenuOption(_hCurMenu); -					drawMenuOptionHilite(_hCurMenu, vmenu); -				} -				_vCurMenu = vmenu; -			} -		} -	} else if (buttonUsed) { -		// Button released -		buttonUsed = 0; +	// Draw all menus +	for (uint16 menuNr = 0; menuNr < _array.size(); menuNr++) { +		drawMenuName(menuNr, false); +	} +	drawActiveMenu(); -		debugC(6, kDebugLevelMenu | kDebugLevelInput, "button released!"); +	_vm->cycleInnerLoopActive(CYCLE_INNERLOOP_MENU); +	do { +		_vm->mainCycle(); +	} while (_vm->cycleInnerLoopIsActive() && !(_vm->shouldQuit() || _vm->_restartGame)); -		if (_vCurMenu < 0) -			_vCurMenu = 0; +	removeActiveMenu(); -		drawMenuOptionHilite(_hCurMenu, _vCurMenu); +	_text->charAttrib_Pop(); +	_text->charPos_Pop(); -		if (_vm->_mouse.y <= CHAR_LINES) { -			// on the menubar -		} else { -			// see which option we selected -			AgiMenu *m = getMenu(_hCurMenu); -			MenuOptionList::iterator iterv; - -			for (iterv = m->down.begin(); iterv != m->down.end(); ++iterv) { -				AgiMenuOption *d = *iterv; - -				if (mouseOverText(2 + d->index, m->wincol + 1, d->text)) { -					// activate that option -					if (d->enabled) { -						debugC(6, kDebugLevelMenu | kDebugLevelInput, "event %d registered", d->event); -						_vm->_game.controllerOccured[d->event] = true; -						_vm->_menuSelected = true; -						break; -					} -				} -			} -			exitMenu = true; -		} +	// Restore status line +	if (_text->statusEnabled()) { +		_text->statusDraw(); +	} else { +		_text->clearLine(0, 0);  	} +} -	if (!exitMenu) { -		if (!menuActive) { -			if (_hCurMenu >= 0) { -				drawMenuHilite(_hCurMenu); -				drawMenuOption(_hCurMenu); -				if (!buttonUsed && _vCurMenu >= 0) -					drawMenuOptionHilite(_hCurMenu, _vCurMenu); -			} -			menuActive = true; -		} +void GfxMenu::drawMenuName(int16 menuNr, bool inverted) { +	GuiMenuEntry *menuEntry = _array[menuNr]; +	bool disabledLook = false; -		switch (key) { -		case KEY_ESCAPE: -			debugC(6, kDebugLevelMenu | kDebugLevelInput, "KEY_ESCAPE"); -			exitMenu = true; -			break; -		case KEY_ENTER: -		{ -			debugC(6, kDebugLevelMenu | kDebugLevelInput, "KEY_ENTER"); -			AgiMenuOption* d = getMenuOption(_hCurMenu, _vCurMenu); - -			if (d->enabled) { -				debugC(6, kDebugLevelMenu | kDebugLevelInput, "event %d registered", d->event); -				_vm->_game.controllerOccured[d->event] = true; -				_vm->_menuSelected = true; -				exitMenu = true; -			} -			break; -		} -		case KEY_DOWN: -		case KEY_UP: -			_vCurMenu += key == KEY_DOWN ? 1 : -1; - -			if (_vCurMenu < 0) -				_vCurMenu = _vMaxMenu[_hCurMenu]; -			if (_vCurMenu > _vMaxMenu[_hCurMenu]) -				_vCurMenu = 0; - -			drawMenuOption(_hCurMenu); -			drawMenuOptionHilite(_hCurMenu, _vCurMenu); -			break; -		case KEY_RIGHT: -		case KEY_LEFT: -			_hCurMenu += key == KEY_RIGHT ? 1 : -1; - -			if (_hCurMenu < 0) -				_hCurMenu = _hMaxMenu; -			if (_hCurMenu > _hMaxMenu) -				_hCurMenu = 0; - -			_vCurMenu = 0; -			newMenuSelected(_hCurMenu); -			drawMenuOptionHilite(_hCurMenu, _vCurMenu); -			break; -		} +	if (!inverted) { +		_text->charAttrib_Set(0, _text->calculateTextBackground(15)); +	} else { +		_text->charAttrib_Set(15, _text->calculateTextBackground(0));  	} -	if (exitMenu) { -		buttonUsed = 0; -		_picture->showPic(); -		_vm->writeStatus(); - -		_vm->setvar(vKey, 0); -		_vm->_game.keypress = 0; -		_vm->_game.clockEnabled = clockVal; -		_vm->oldInputMode(); +	_text->charPos_Set(menuEntry->row, menuEntry->column); -		debugC(3, kDebugLevelMenu, "exit_menu: input mode reset to %d", _vm->_game.inputMode); -		menuActive = false; -	} +	if (menuEntry->itemCount == 0) +		disabledLook = true; -	return true; +	_text->displayText(menuEntry->text.c_str(), disabledLook);  } -void Menu::setItem(int event, int state) { -	// scan all menus for event number # +void GfxMenu::drawItemName(int16 itemNr, bool inverted) { +	GuiMenuItemEntry *itemEntry = _itemArray[itemNr]; +	bool disabledLook = false; -	debugC(6, kDebugLevelMenu, "event = %d, state = %d", event, state); -	MenuList::iterator iterh; +	if (!inverted) { +		_text->charAttrib_Set(0, _text->calculateTextBackground(15)); +	} else { +		_text->charAttrib_Set(15, _text->calculateTextBackground(0)); +	} -	for (iterh = _menubar.begin(); iterh != _menubar.end(); ++iterh) { -		AgiMenu *m = *iterh; -		MenuOptionList::iterator iterv; +	_text->charPos_Set(itemEntry->row, itemEntry->column); -		for (iterv = m->down.begin(); iterv != m->down.end(); ++iterv) { -			AgiMenuOption *d = *iterv; +	if (itemEntry->enabled == false) +		disabledLook = true; -			if (d->event == event) { -				d->enabled = state; -				// keep going; we need to set the state of every menu item -				// with this event code. -- dsymonds -			} -		} -	} +	_text->displayText(itemEntry->text.c_str(), disabledLook);  } -void Menu::enableAll() { -	MenuList::iterator iterh; -	for (iterh = _menubar.begin(); iterh != _menubar.end(); ++iterh) { -		AgiMenu *m = *iterh; -		MenuOptionList::iterator iterv; - -		for (iterv = m->down.begin(); iterv != m->down.end(); ++iterv) { -			AgiMenuOption *d = *iterv; - -			d->enabled = true; +void GfxMenu::drawActiveMenu() { +	GuiMenuEntry *menuEntry = _array[_selectedMenuNr]; +	GuiMenuItemEntry *itemEntry = _itemArray[menuEntry->firstItemNr]; +	int16 itemNr = menuEntry->firstItemNr; +	int16 itemCount = menuEntry->itemCount; +	 +	// draw menu name as inverted +	drawMenuName(_selectedMenuNr, true); + +	// calculate active menu dimensions +	_selectedMenuHeight = (menuEntry->itemCount + 2) * FONT_VISUAL_HEIGHT; +	_selectedMenuWidth  = (menuEntry->maxItemTextLen * FONT_VISUAL_WIDTH) + 8; +	_selectedMenuRow    = (menuEntry->itemCount + 3 - _text->getWindowRowMin()) * FONT_VISUAL_HEIGHT - 1; +	_selectedMenuColumn = (itemEntry->column - 1) * FONT_VISUAL_WIDTH; + +	_gfx->drawBox(_selectedMenuColumn, _selectedMenuRow, _selectedMenuWidth, _selectedMenuHeight, 15, 0); + +	while (itemCount) { +		if (itemNr == menuEntry->selectedItemNr) { +			drawItemName(itemNr, true); +		} else { +			drawItemName(itemNr, false);  		} +		itemNr++; +		itemCount--;  	}  } +void GfxMenu::removeActiveMenu() { +	// draw menu name normally again +	drawMenuName(_selectedMenuNr, false); -AgiTextColor AgiButtonStyle::getColor(bool hasFocus, bool pressed, bool positive) const { -	if (_amigaStyle) { -		if (positive) { -			if (pressed) { // Positive pressed Amiga-style button -				if (_olderAgi) { -					return AgiTextColor(amigaBlack, amigaOrange); -				} else { -					return AgiTextColor(amigaBlack, amigaPurple); -				} -			} else { // Positive unpressed Amiga-style button -				return AgiTextColor(amigaWhite, amigaGreen); -			} -		} else { // _amigaStyle && !positive -			if (pressed) { // Negative pressed Amiga-style button -				return AgiTextColor(amigaBlack, amigaCyan); -			} else { // Negative unpressed Amiga-style button -				return AgiTextColor(amigaWhite, amigaRed); -			} -		} -	} else { // PC-style button -		if (hasFocus || pressed) { // A pressed or in focus PC-style button -			return AgiTextColor(pcWhite, pcBlack); -		} else { // An unpressed PC-style button without focus -			return AgiTextColor(pcBlack, pcWhite); -		} -	} +	// overwrite actual menu items by rendering play screen +	_gfx->render_Block(_selectedMenuColumn, _selectedMenuRow, _selectedMenuWidth, _selectedMenuHeight);  } -AgiTextColor AgiButtonStyle::getColor(bool hasFocus, bool pressed, int baseFgColor, int baseBgColor) const { -	return getColor(hasFocus, pressed, AgiTextColor(baseFgColor, baseBgColor)); -} +void GfxMenu::charPress(int16 newChar) { +	GuiMenuEntry *menuEntry = _array[_selectedMenuNr]; +	GuiMenuItemEntry *itemEntry = _itemArray[menuEntry->selectedItemNr]; +	int16 newMenuNr = _selectedMenuNr; +	int16 newItemNr = menuEntry->selectedItemNr; + +	switch (newChar) { +	case AGI_KEY_ENTER: +		// check, if current item is actually enabled +		if (!itemEntry->enabled) +			return; + +		// Trigger controller +		_vm->_game.controllerOccured[itemEntry->controllerSlot] = true; + +		_vm->cycleInnerLoopInactive(); // exit execute-loop +		break; +	case AGI_KEY_ESCAPE: +		_vm->cycleInnerLoopInactive(); // exit execute-loop +		break; + +	// these here change menu item +	case AGI_KEY_UP: +		newItemNr--; +		break; +	case AGI_KEY_DOWN: +		newItemNr++; +		break; +	case AGI_KEY_PAGE_UP: +		// select first item of current menu +		newItemNr = menuEntry->firstItemNr; +		break; +	case AGI_KEY_PAGE_DOWN: +		// select last item of current menu +		newItemNr = menuEntry->firstItemNr + menuEntry->itemCount - 1; +		break; + +	case AGI_KEY_LEFT: +		newMenuNr--; +		break; +	case AGI_KEY_RIGHT: +		newMenuNr++; +		break; +	case AGI_KEY_HOME: +		// select first menu +		newMenuNr = 0; +		break; +	case AGI_KEY_END: +		// select last menu +		newMenuNr = _array.size() - 1; +		break; + +	default: +		break; +	} -AgiTextColor AgiButtonStyle::getColor(bool hasFocus, bool pressed, const AgiTextColor &baseColor) const { -	if (hasFocus || pressed) -		return baseColor.swap(); -	else -		return baseColor; -} +	if (newMenuNr != _selectedMenuNr) { +		// selected menu was changed +		int16 lastMenuNr = _array.size() - 1; -int AgiButtonStyle::getTextOffset(bool hasFocus, bool pressed) const { -	return (pressed && !_amigaStyle) ? 1 : 0; -} +		if (newMenuNr < 0) { +			newMenuNr = lastMenuNr; +		} else if (newMenuNr > lastMenuNr) { +			newMenuNr = 0; +		} -bool AgiButtonStyle::getBorder(bool hasFocus, bool pressed) const { -	return _amigaStyle && !_authenticAmiga && (hasFocus || pressed); -} +		if (newMenuNr != _selectedMenuNr) { +			removeActiveMenu(); +			_selectedMenuNr = newMenuNr; +			drawActiveMenu(); +		} +	} -void AgiButtonStyle::setAmigaStyle(bool amigaStyle, bool olderAgi, bool authenticAmiga) { -	_amigaStyle		= amigaStyle; -	_olderAgi		= olderAgi; -	_authenticAmiga	= authenticAmiga; -} +	if (newItemNr != menuEntry->selectedItemNr) { +		// selected item was changed +		int16 lastItemNr = menuEntry->firstItemNr + menuEntry->itemCount - 1; -void AgiButtonStyle::setPcStyle(bool pcStyle) { -	setAmigaStyle(!pcStyle); -} +		if (newItemNr < menuEntry->firstItemNr) { +			newItemNr = lastItemNr; +		} else if (newItemNr > lastItemNr) { +			newItemNr = menuEntry->firstItemNr; +		} -AgiButtonStyle::AgiButtonStyle(Common::RenderMode renderMode) { -	setAmigaStyle(renderMode == Common::kRenderAmiga); +		if (newItemNr != menuEntry->selectedItemNr) { +			// still changed after clip -> draw changes +			drawItemName(menuEntry->selectedItemNr, false); +			drawItemName(newItemNr, true); +			menuEntry->selectedItemNr = newItemNr; +		} +	}  }  } // End of namespace Agi diff --git a/engines/agi/menu.h b/engines/agi/menu.h index 000973db23..1781704996 100644 --- a/engines/agi/menu.h +++ b/engines/agi/menu.h @@ -25,6 +25,88 @@  namespace Agi { +struct GuiMenuEntry { +	Common::String text; +	int16 textLen; + +	int16 row; +	int16 column; + +	int16 itemCount; +	int16 firstItemNr; +	int16 selectedItemNr; + +	int16 maxItemTextLen; +}; +typedef Common::Array<GuiMenuEntry *> GuiMenuArray; + +struct GuiMenuItemEntry { +	Common::String text; +	int16 textLen; + +	int16 row; +	int16 column; + +	bool enabled; +	uint16 controllerSlot; +}; +typedef Common::Array<GuiMenuItemEntry *> GuiMenuItemArray; + +class GfxMenu { +public: +	GfxMenu(AgiEngine *vm, GfxMgr *gfx, PictureMgr *picture, TextMgr *text); +	~GfxMenu(); + +	void addMenu(const char *menuText); +	void addMenuItem(const char *menuText, uint16 controlCode); +	void submit(); +	void itemEnable(uint16 controllerSlot); +	void itemDisable(uint16 controllerSlot); +	void itemEnableAll(); +	void charPress(int16 newChar); + +	bool isAvailable(); + +	void accessAllow(); +	void accessDeny(); + +	void delayedExecute(); +	bool delayedExecuteActive(); +	void execute(); + +private: +	void itemEnableDisable(uint16 controllerSlot, bool enabled); + +	void drawMenuName(int16 menuNr, bool inverted); +	void drawItemName(int16 itemNr, bool inverted); +	void drawActiveMenu(); +	void removeActiveMenu(); + +	AgiEngine *_vm; +	GfxMgr *_gfx; +	PictureMgr *_picture; +	TextMgr *_text; + +	bool _allowed; +	bool _submitted; +	bool _delayedExecute; + +	// for initial setup of the menu +	int16 _setupMenuColumn; +	int16 _setupMenuItemColumn; + +	GuiMenuArray _array; +	GuiMenuItemArray _itemArray; + +	int16 _selectedMenuNr; + +	uint16 _selectedMenuHeight; +	uint16 _selectedMenuWidth; +	int16  _selectedMenuRow; +	int16  _selectedMenuColumn; +}; + +#if 0  #define MENU_BG		0x0f	// White  #define MENU_DISABLED	0x07	// Grey @@ -44,6 +126,7 @@ private:  	AgiEngine *_vm;  	GfxMgr *_gfx;  	PictureMgr *_picture; +	TextMgr *_text;  public:  	Menu(AgiEngine *vm, GfxMgr *gfx, PictureMgr *picture); @@ -78,6 +161,7 @@ private:  	bool mouseOverText(int line, int col, char *s);  }; +#endif  } // End of namespace Agi diff --git a/engines/agi/module.mk b/engines/agi/module.mk index 331a10c16e..b6cf6fc531 100644 --- a/engines/agi/module.mk +++ b/engines/agi/module.mk @@ -36,6 +36,7 @@ MODULE_OBJS := \  	sound_pcjr.o \  	sound_sarien.o \  	sprite.o \ +	systemui.o \  	text.o \  	view.o \  	wagparser.o \ diff --git a/engines/agi/motion.cpp b/engines/agi/motion.cpp index 363291ac0b..3f85059b56 100644 --- a/engines/agi/motion.cpp +++ b/engines/agi/motion.cpp @@ -29,7 +29,7 @@ int AgiEngine::checkStep(int delta, int step) {  	return (-step >= delta) ? 0 : (step <= delta) ? 2 : 1;  } -int AgiEngine::checkBlock(int x, int y) { +bool AgiEngine::checkBlock(int16 x, int16 y) {  	if (x <= _game.block.x1 || x >= _game.block.x2)  		return false; @@ -39,87 +39,90 @@ int AgiEngine::checkBlock(int x, int y) {  	return true;  } -void AgiEngine::changePos(VtEntry *v) { -	int b, x, y; +void AgiEngine::changePos(ScreenObjEntry *screenObj) { +	bool insideBlock; +	int16 x, y;  	int dx[9] = { 0, 0, 1, 1, 1, 0, -1, -1, -1 };  	int dy[9] = { 0, -1, -1, 0, 1, 1, 1, 0, -1 }; -	x = v->xPos; -	y = v->yPos; -	b = checkBlock(x, y); +	x = screenObj->xPos; +	y = screenObj->yPos; +	insideBlock = checkBlock(x, y); -	x += v->stepSize * dx[v->direction]; -	y += v->stepSize * dy[v->direction]; +	x += screenObj->stepSize * dx[screenObj->direction]; +	y += screenObj->stepSize * dy[screenObj->direction]; -	if (checkBlock(x, y) == b) { -		v->flags &= ~fMotion; +	if (checkBlock(x, y) == insideBlock) { +		screenObj->flags &= ~fMotion;  	} else { -		v->flags |= fMotion; -		v->direction = 0; -		if (isEgoView(v)) -			_game.vars[vEgoDir] = 0; +		screenObj->flags |= fMotion; +		screenObj->direction = 0; +		if (isEgoView(screenObj)) +			_game.vars[VM_VAR_EGO_DIRECTION] = 0;  	}  } -void AgiEngine::motionWander(VtEntry *v) { -	if (v->parm1--) { -		if (~v->flags & fDidntMove) -			return; -	} +void AgiEngine::motionWander(ScreenObjEntry *screenObj) { +	uint8 originalWanderCount = screenObj->wander_count; -	v->direction = _rnd->getRandomNumber(8); +	screenObj->wander_count--; +	if ((originalWanderCount == 0) || (screenObj->flags & fDidntMove)) { +		screenObj->direction = _rnd->getRandomNumber(8); -	if (isEgoView(v)) { -		_game.vars[vEgoDir] = v->direction; -		while (v->parm1 < 6) { -			v->parm1 = _rnd->getRandomNumber(50);	// huh? +		if (isEgoView(screenObj)) { +			_game.vars[VM_VAR_EGO_DIRECTION] = screenObj->direction; +		} + +		while (screenObj->wander_count < 6) { +			screenObj->wander_count = _rnd->getRandomNumber(50);	// huh?  		}  	}  } -void AgiEngine::motionFollowEgo(VtEntry *v) { +void AgiEngine::motionFollowEgo(ScreenObjEntry *screenObj) { +	ScreenObjEntry *screenObjEgo = &_game.screenObjTable[SCREENOBJECTS_EGO_ENTRY];  	int egoX, egoY;  	int objX, objY;  	int dir; -	egoX = _game.viewTable[0].xPos + _game.viewTable[0].xSize / 2; -	egoY = _game.viewTable[0].yPos; +	egoX = screenObjEgo->xPos + screenObjEgo->xSize / 2; +	egoY = screenObjEgo->yPos; -	objX = v->xPos + v->xSize / 2; -	objY = v->yPos; +	objX = screenObj->xPos + screenObj->xSize / 2; +	objY = screenObj->yPos;  	// Get direction to reach ego -	dir = getDirection(objX, objY, egoX, egoY, v->parm1); +	dir = getDirection(objX, objY, egoX, egoY, screenObj->follow_stepSize);  	// Already at ego coordinates  	if (dir == 0) { -		v->direction = 0; -		v->motion = kMotionNormal; -		setflag(v->parm2, true); +		screenObj->direction = 0; +		screenObj->motionType = kMotionNormal; +		setflag(screenObj->follow_flag, true);  		return;  	} -	if (v->parm3 == 0xff) { -		v->parm3 = 0; -	} else if (v->flags & fDidntMove) { +	if (screenObj->follow_count == 0xff) { +		screenObj->follow_count = 0; +	} else if (screenObj->flags & fDidntMove) {  		int d; -		while ((v->direction = _rnd->getRandomNumber(8)) == 0) { +		while ((screenObj->direction = _rnd->getRandomNumber(8)) == 0) {  		}  		d = (ABS(egoY - objY) + ABS(egoX - objX)) / 2; -		if (d < v->stepSize) { -			v->parm3 = v->stepSize; +		if (d < screenObj->stepSize) { +			screenObj->follow_count = screenObj->stepSize;  			return;  		} -		while ((v->parm3 = _rnd->getRandomNumber(d)) < v->stepSize) { +		while ((screenObj->follow_count = _rnd->getRandomNumber(d)) < screenObj->stepSize) {  		}  		return;  	} -	if (v->parm3 != 0) { +	if (screenObj->follow_count != 0) {  		int k;  		// DF: this is ugly and I dont know why this works, but @@ -128,45 +131,46 @@ void AgiEngine::motionFollowEgo(VtEntry *v) {  		// if (((int8)v->parm3 -= v->step_size) < 0)  		//      v->parm3 = 0; -		k = v->parm3; -		k -= v->stepSize; -		v->parm3 = k; +		k = screenObj->follow_count; +		k -= screenObj->stepSize; +		screenObj->follow_count = k; -		if ((int8) v->parm3 < 0) -			v->parm3 = 0; +		if ((int8) screenObj->follow_count < 0) +			screenObj->follow_count = 0;  	} else { -		v->direction = dir; +		screenObj->direction = dir;  	}  } -void AgiEngine::motionMoveObj(VtEntry *v) { -	v->direction = getDirection(v->xPos, v->yPos, v->parm1, v->parm2, v->stepSize); +void AgiEngine::motionMoveObj(ScreenObjEntry *screenObj) { +	screenObj->direction = getDirection(screenObj->xPos, screenObj->yPos, screenObj->move_x, screenObj->move_y, screenObj->stepSize);  	// Update V6 if ego -	if (isEgoView(v)) -		_game.vars[vEgoDir] = v->direction; +	if (isEgoView(screenObj)) +		_game.vars[VM_VAR_EGO_DIRECTION] = screenObj->direction; -	if (v->direction == 0) -		inDestination(v); +	if (screenObj->direction == 0) +		motionMoveObjStop(screenObj);  } -void AgiEngine::checkMotion(VtEntry *v) { -	switch (v->motion) { +void AgiEngine::checkMotion(ScreenObjEntry *screenObj) { +	switch (screenObj->motionType) {  	case kMotionNormal:  		break;  	case kMotionWander: -		motionWander(v); +		motionWander(screenObj);  		break;  	case kMotionFollowEgo: -		motionFollowEgo(v); +		motionFollowEgo(screenObj);  		break; +	case kMotionEgo:  	case kMotionMoveObj: -		motionMoveObj(v); +		motionMoveObj(screenObj);  		break;  	} -	if ((_game.block.active && (~v->flags & fIgnoreBlocks)) && v->direction) -		changePos(v); +	if ((_game.block.active && (~screenObj->flags & fIgnoreBlocks)) && screenObj->direction) +		changePos(screenObj);  }  /* @@ -177,12 +181,12 @@ void AgiEngine::checkMotion(VtEntry *v) {   *   */  void AgiEngine::checkAllMotions() { -	VtEntry *v; +	ScreenObjEntry *screenObj; -	for (v = _game.viewTable; v < &_game.viewTable[MAX_VIEWTABLE]; v++) { -		if ((v->flags & (fAnimated | fUpdate | fDrawn)) == (fAnimated | fUpdate | fDrawn) -				&& v->stepTimeCount == 1) { -			checkMotion(v); +	for (screenObj = _game.screenObjTable; screenObj < &_game.screenObjTable[SCREENOBJECTS_MAX]; screenObj++) { +		if ((screenObj->flags & (fAnimated | fUpdate | fDrawn)) == (fAnimated | fUpdate | fDrawn) +				&& screenObj->stepTimeCount == 1) { +			checkMotion(screenObj);  		}  	}  } @@ -193,14 +197,27 @@ void AgiEngine::checkAllMotions() {   * type motion that * has reached its final destination coordinates.   * @param  v  Pointer to view table entry   */ -void AgiEngine::inDestination(VtEntry *v) { -	if (v->motion == kMotionMoveObj) { -		v->stepSize = v->parm3; -		setflag(v->parm4, true); +void AgiEngine::inDestination(ScreenObjEntry *screenObj) { +	if (screenObj->motionType == kMotionMoveObj) { +		screenObj->stepSize = screenObj->move_stepSize; +		setflag(screenObj->move_flag, true); +	} +	screenObj->motionType = kMotionNormal; +	if (isEgoView(screenObj)) +		_game.playerControl = true; +} + +void AgiEngine::motionMoveObjStop(ScreenObjEntry *screenObj) { +	screenObj->stepSize = screenObj->move_stepSize; +	if (screenObj->motionType != kMotionEgo) { +		setflag(screenObj->move_flag, true);  	} -	v->motion = kMotionNormal; -	if (isEgoView(v)) + +	screenObj->motionType = kMotionNormal; +	if (isEgoView(screenObj)) {  		_game.playerControl = true; +		_game.vars[VM_VAR_EGO_DIRECTION] = 0; +	}  }  /** @@ -209,8 +226,8 @@ void AgiEngine::inDestination(VtEntry *v) {   * after setting the motion mode to kMotionMoveObj.   * @param  v  Pointer to view table entry   */ -void AgiEngine::moveObj(VtEntry *v) { -	motionMoveObj(v); +void AgiEngine::moveObj(ScreenObjEntry *screenObj) { +	motionMoveObj(screenObj);  }  /** @@ -223,9 +240,9 @@ void AgiEngine::moveObj(VtEntry *v) {   * @param  y   y coordinate of the object   * @param  s   step size   */ -int AgiEngine::getDirection(int x0, int y0, int x, int y, int s) { +int AgiEngine::getDirection(int16 objX, int16 objY, int16 destX, int16 destY, int16 stepSize) {  	int dirTable[9] = { 8, 1, 2, 7, 0, 3, 6, 5, 4 }; -	return dirTable[checkStep(x - x0, s) + 3 * checkStep(y - y0, s)]; +	return dirTable[checkStep(destX - objX, stepSize) + 3 * checkStep(destY - objY, stepSize)];  }  } // End of namespace Agi diff --git a/engines/agi/mouse_cursor.h b/engines/agi/mouse_cursor.h new file mode 100644 index 0000000000..1883960451 --- /dev/null +++ b/engines/agi/mouse_cursor.h @@ -0,0 +1,183 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef AGI_MOUSE_CURSOR_H +#define AGI_MOUSE_CURSOR_H + +namespace Agi { + +/** + * RGB-palette for the Amiga-style arrow cursor + * and the Amiga-style busy cursor. + */ +static const byte MOUSECURSOR_PALETTE[] = { +	0x00, 0x00, 0x00, // Black +	0xFF, 0xFF, 0xFF, // White +	0xDE, 0x20, 0x21, // Red +	0xFF, 0xCF, 0xAD  // Light red +}; + +/** + * A black and white SCI-style arrow cursor (11x16). + * 0 = Transparent. + * 1 = Black (#000000 in 24-bit RGB). + * 2 = White (#FFFFFF in 24-bit RGB). + */ +static const byte MOUSECURSOR_SCI[] = { +	1,1,0,0,0,0,0,0,0,0,0, +	1,2,1,0,0,0,0,0,0,0,0, +	1,2,2,1,0,0,0,0,0,0,0, +	1,2,2,2,1,0,0,0,0,0,0, +	1,2,2,2,2,1,0,0,0,0,0, +	1,2,2,2,2,2,1,0,0,0,0, +	1,2,2,2,2,2,2,1,0,0,0, +	1,2,2,2,2,2,2,2,1,0,0, +	1,2,2,2,2,2,2,2,2,1,0, +	1,2,2,2,2,2,2,2,2,2,1, +	1,2,2,2,2,2,1,0,0,0,0, +	1,2,1,0,1,2,2,1,0,0,0, +	1,1,0,0,1,2,2,1,0,0,0, +	0,0,0,0,0,1,2,2,1,0,0, +	0,0,0,0,0,1,2,2,1,0,0, +	0,0,0,0,0,0,1,2,2,1,0 +}; + +/** + * A black and white SCI-style busy cursor (15x16). + * 0 = Transparent. + * 1 = Black (#000000 in 24-bit RGB). + * 2 = White (#FFFFFF in 24-bit RGB). + */ +static const byte MOUSECURSOR_SCI_BUSY[] = { +	0,0,0,0,0,0,0,1,1,1,0,0,0,0,0, +	0,0,0,0,1,1,1,2,2,1,1,1,0,0,0, +	0,0,0,1,2,2,1,2,2,1,2,2,1,0,0, +	0,1,1,1,2,2,1,2,2,1,2,2,1,0,0, +	1,2,2,1,2,2,1,2,2,1,2,2,1,0,0, +	1,2,2,1,2,2,1,2,2,1,2,2,1,0,0, +	1,2,2,1,2,2,1,2,2,1,2,1,2,1,0, +	1,2,2,1,2,2,1,2,2,1,1,2,2,1,1, +	1,2,2,1,2,2,1,2,2,1,1,2,2,2,1, +	1,2,2,2,2,2,2,2,2,1,1,2,2,2,1, +	1,2,2,2,2,2,2,2,1,2,2,2,2,1,0, +	1,2,2,2,2,2,2,1,2,2,2,2,2,1,0, +	0,1,2,2,2,2,2,1,2,2,2,2,1,0,0, +	0,1,2,2,2,2,2,2,2,2,2,2,1,0,0, +	0,0,1,2,2,2,2,2,2,2,2,1,0,0,0, +	0,0,0,1,1,1,1,1,1,1,1,0,0,0,0 +}; + +/** + * A black and white Atari ST style arrow cursor (11x16). + * 0 = Transparent. + * 1 = Black (#000000 in 24-bit RGB). + * 2 = White (#FFFFFF in 24-bit RGB). + */ +static const byte MOUSECURSOR_ATARI_ST[] = { +	2,2,0,0,0,0,0,0,0,0,0, +	2,1,2,0,0,0,0,0,0,0,0, +	2,1,1,2,0,0,0,0,0,0,0, +	2,1,1,1,2,0,0,0,0,0,0, +	2,1,1,1,1,2,0,0,0,0,0, +	2,1,1,1,1,1,2,0,0,0,0, +	2,1,1,1,1,1,1,2,0,0,0, +	2,1,1,1,1,1,1,1,2,0,0, +	2,1,1,1,1,1,1,1,1,2,0, +	2,1,1,1,1,1,2,2,2,2,2, +	2,1,1,2,1,1,2,0,0,0,0, +	2,1,2,0,2,1,1,2,0,0,0, +	2,2,0,0,2,1,1,2,0,0,0, +	2,0,0,0,0,2,1,1,2,0,0, +	0,0,0,0,0,2,1,1,2,0,0, +	0,0,0,0,0,0,2,2,2,0,0 +}; + +/** + * A black and white Apple IIGS style arrow cursor (9x11). + * 0 = Transparent. + * 1 = Black (#000000 in 24-bit RGB). + * 2 = White (#FFFFFF in 24-bit RGB). + */ +static const byte MOUSECURSOR_APPLE_II_GS[] = { +	2,2,0,0,0,0,0,0,0, +	2,1,2,0,0,0,0,0,0, +	2,1,1,2,0,0,0,0,0, +	2,1,1,1,2,0,0,0,0, +	2,1,1,1,1,2,0,0,0, +	2,1,1,1,1,1,2,0,0, +	2,1,1,1,1,1,1,2,0, +	2,1,1,1,1,1,1,1,2, +	2,1,1,2,1,1,2,2,0, +	2,2,2,0,2,1,1,2,0, +	0,0,0,0,0,2,2,2,0 +}; + +/** + * An Amiga-style arrow cursor (8x11). + * 0 = Transparent. + * 1 = Black     (#000000 in 24-bit RGB). + * 3 = Red       (#DE2021 in 24-bit RGB). + * 4 = Light red (#FFCFAD in 24-bit RGB). + */ +static const byte MOUSECURSOR_AMIGA[] = { +	3,4,1,0,0,0,0,0, +	3,3,4,1,0,0,0,0, +	3,3,3,4,1,0,0,0, +	3,3,3,3,4,1,0,0, +	3,3,3,3,3,4,1,0, +	3,3,3,3,3,3,4,1, +	3,0,3,3,4,1,0,0, +	0,0,0,3,4,1,0,0, +	0,0,0,3,3,4,1,0, +	0,0,0,0,3,4,1,0, +	0,0,0,0,3,3,4,1 +}; + +/** + * An Amiga-style busy cursor showing an hourglass (13x16). + * 0 = Transparent. + * 1 = Black     (#000000 in 24-bit RGB). + * 2 = Red       (#DE2021 in 24-bit RGB). + * 3 = Light red (#FFCFAD in 24-bit RGB). + */ +static const byte MOUSECURSOR_AMIGA_BUSY[] = { +	1,1,1,1,1,1,1,1,1,1,1,1,1, +	1,3,3,3,3,3,3,3,3,3,3,3,1, +	1,3,3,3,3,3,3,3,3,3,3,3,1, +	0,1,4,4,4,4,4,4,4,4,4,1,0, +	0,0,1,4,4,4,4,4,4,4,1,0,0, +	0,0,0,1,4,4,4,4,4,1,0,0,0, +	0,0,0,0,1,4,4,4,1,0,0,0,0, +	0,0,0,0,0,1,4,1,0,0,0,0,0, +	0,0,0,0,0,1,4,1,0,0,0,0,0, +	0,0,0,0,1,3,4,3,1,0,0,0,0, +	0,0,0,1,3,3,4,3,3,1,0,0,0, +	0,0,1,3,3,3,4,3,3,3,1,0,0, +	0,1,3,3,3,4,4,4,3,3,3,1,0, +	1,4,4,4,4,4,4,4,4,4,4,4,1, +	1,4,4,4,4,4,4,4,4,4,4,4,1, +	1,1,1,1,1,1,1,1,1,1,1,1,1 +}; + +} // End of namespace Agi + +#endif /* AGI_MOUSE_CURSOR_H */ diff --git a/engines/agi/objects.cpp b/engines/agi/objects.cpp index 27cde61065..b6c628c32d 100644 --- a/engines/agi/objects.cpp +++ b/engines/agi/objects.cpp @@ -144,28 +144,28 @@ void AgiEngine::unloadObjects() {  	}  } -void AgiEngine::objectSetLocation(unsigned int n, int i) { -	if (n >= _game.numObjects) { -		warning("AgiEngine::objectSetLocation: Can't access object %d.\n", n); +void AgiEngine::objectSetLocation(uint16 objectNr, int i) { +	if (objectNr >= _game.numObjects) { +		warning("AgiEngine::objectSetLocation: Can't access object %d.\n", objectNr);  		return;  	} -	_objects[n].location = i; +	_objects[objectNr].location = i;  } -int AgiEngine::objectGetLocation(unsigned int n) { -	if (n >= _game.numObjects) { -		warning("AgiEngine::objectGetLocation: Can't access object %d.\n", n); +int AgiEngine::objectGetLocation(uint16 objectNr) { +	if (objectNr >= _game.numObjects) { +		warning("AgiEngine::objectGetLocation: Can't access object %d.\n", objectNr);  		return 0;  	} -	return _objects[n].location; +	return _objects[objectNr].location;  } -const char *AgiEngine::objectName(unsigned int n) { -	if (n >= _game.numObjects) { -		warning("AgiEngine::objectName: Can't access object %d.\n", n); +const char *AgiEngine::objectName(uint16 objectNr) { +	if (objectNr >= _game.numObjects) { +		warning("AgiEngine::objectName: Can't access object %d.\n", objectNr);  		return "";  	} -	return _objects[n].name; +	return _objects[objectNr].name;  }  } // End of namespace Agi diff --git a/engines/agi/op_cmd.cpp b/engines/agi/op_cmd.cpp index bf2a2ed77b..bb10c27d6e 100644 --- a/engines/agi/op_cmd.cpp +++ b/engines/agi/op_cmd.cpp @@ -23,55 +23,49 @@  #include "base/version.h"  #include "agi/agi.h" +#include "agi/inv.h"  #include "agi/sprite.h" +#include "agi/text.h"  #include "agi/graphics.h"  #include "agi/opcodes.h"  #include "agi/menu.h" +#include "agi/systemui.h" +#include "agi/words.h"  #include "common/random.h"  #include "common/textconsole.h"  namespace Agi { -#define p0	(p[0]) -#define p1	(p[1]) -#define p2	(p[2]) -#define p3	(p[3]) -#define p4	(p[4]) -#define p5	(p[5]) -#define p6	(p[6]) - -#define code state->_curLogic->data -#define ip	state->_curLogic->cIP -#define vt	state->viewTable[p0] -#define vt_v state->viewTable[state->vars[p0]] - -#define _v state->vars -  #define getGameID() state->_vm->getGameID()  #define getFeatures() state->_vm->getFeatures()  #define getVersion() state->_vm->getVersion()  #define getLanguage() state->_vm->getLanguage() -#define setflag(a,b) state->_vm->setflag(a,b) -#define getflag(a) state->_vm->getflag(a) -void cmdIncrement(AgiGame *state, uint8 *p) { +void cmdIncrement(AgiGame *state, uint8 *parameter) { +	uint16 varNr = parameter[0]; +  	if (getVersion() < 0x2000) { -		if (_v[p0] < 0xf0) -			++_v[p0]; +		if (state->vars[varNr] < 0xf0) +			++state->vars[varNr];  	} else { -		if (_v[p0] != 0xff) -			++_v[p0]; +		if (state->vars[varNr] != 0xff) +			++state->vars[varNr];  	}  } -void cmdDecrement(AgiGame *state, uint8 *p) { -	if (_v[p0] != 0) -		--_v[p0]; +void cmdDecrement(AgiGame *state, uint8 *parameter) { +	uint16 varNr = parameter[0]; + +	if (state->vars[varNr] != 0) +		--state->vars[varNr];  } -void cmdAssignN(AgiGame *state, uint8 *p) { -	_v[p0] = p1; +void cmdAssignN(AgiGame *state, uint8 *parameter) { +	uint16 varNr = parameter[0]; +	uint16 value = parameter[1]; + +	state->vars[varNr] = value;  	// WORKAROUND for a bug in fan game "Get outta SQ"  	// Total number of points is stored in variable 7, which @@ -80,104 +74,170 @@ void cmdAssignN(AgiGame *state, uint8 *p) {  	// variable to the correct value here  	// Fixes bug #1942476 - "AGI: Fan(Get Outta SQ) - Score  	// is lost on restart" -	if (getGameID() == GID_GETOUTTASQ && p0 == 7) -		_v[p0] = 8; +	if (getGameID() == GID_GETOUTTASQ && varNr == 7) +		state->vars[varNr] = 8;  } -void cmdAddN(AgiGame *state, uint8 *p) { -	_v[p0] += p1; +void cmdAddN(AgiGame *state, uint8 *parameter) { +	uint16 varNr = parameter[0]; +	uint16 value = parameter[1]; + +	state->vars[varNr] += value;  } -void cmdSubN(AgiGame *state, uint8 *p) { -	_v[p0] -= p1; +void cmdSubN(AgiGame *state, uint8 *parameter) { +	uint16 varNr = parameter[0]; +	uint16 value = parameter[1]; + +	state->vars[varNr] -= value;  } -void cmdAssignV(AgiGame *state, uint8 *p) { -	_v[p0] = _v[p1]; +void cmdAssignV(AgiGame *state, uint8 *parameter) { +	uint16 varNr1 = parameter[0]; +	uint16 varNr2 = parameter[1]; + +	state->vars[varNr1] = state->vars[varNr2];  } -void cmdAddV(AgiGame *state, uint8 *p) { -	_v[p0] += _v[p1]; +void cmdAddV(AgiGame *state, uint8 *parameter) { +	uint16 varNr1 = parameter[0]; +	uint16 varNr2 = parameter[1]; + +	state->vars[varNr1] += state->vars[varNr2];  } -void cmdSubV(AgiGame *state, uint8 *p) { -	_v[p0] -= _v[p1]; +void cmdSubV(AgiGame *state, uint8 *parameter) { +	uint16 varNr1 = parameter[0]; +	uint16 varNr2 = parameter[1]; + +	state->vars[varNr1] -= state->vars[varNr2];  } -void cmdMulN(AgiGame *state, uint8 *p) { -	_v[p0] *= p1; +void cmdMulN(AgiGame *state, uint8 *parameter) { +	uint16 varNr = parameter[0]; +	uint16 value = parameter[1]; + +	state->vars[varNr] *= value;  } -void cmdMulV(AgiGame *state, uint8 *p) { -	_v[p0] *= _v[p1]; +void cmdMulV(AgiGame *state, uint8 *parameter) { +	uint16 varNr1 = parameter[0]; +	uint16 varNr2 = parameter[1]; + +	state->vars[varNr1] *= state->vars[varNr2];  } -void cmdDivN(AgiGame *state, uint8 *p) { -	_v[p0] /= p1; +void cmdDivN(AgiGame *state, uint8 *parameter) { +	uint16 varNr = parameter[0]; +	uint16 value = parameter[1]; + +	state->vars[varNr] /= value;  } -void cmdDivV(AgiGame *state, uint8 *p) { -	_v[p0] /= _v[p1]; +void cmdDivV(AgiGame *state, uint8 *parameter) { +	uint16 varNr1 = parameter[0]; +	uint16 varNr2 = parameter[1]; + +	state->vars[varNr1] /= state->vars[varNr2];  } -void cmdRandomV1(AgiGame *state, uint8 *p) { -	_v[p0] = state->_vm->_rnd->getRandomNumber(250); +void cmdRandomV1(AgiGame *state, uint8 *parameter) { +	uint16 varNr = parameter[0]; + +	state->vars[varNr] = state->_vm->_rnd->getRandomNumber(250);  } -void cmdRandom(AgiGame *state, uint8 *p) { -	_v[p2] = state->_vm->_rnd->getRandomNumber(p1 - p0) + p0; +void cmdRandom(AgiGame *state, uint8 *parameter) { +	uint16 valueMin = parameter[0]; +	uint16 valueMax = parameter[1]; +	uint16 varNr = parameter[2]; + +	state->vars[varNr] = state->_vm->_rnd->getRandomNumber(valueMax - valueMin) + valueMin;  } -void cmdLindirectN(AgiGame *state, uint8 *p) { -	_v[_v[p0]] = p1; +void cmdLindirectN(AgiGame *state, uint8 *parameter) { +	uint16 varNr = parameter[0]; +	uint16 value = parameter[1]; + +	state->vars[state->vars[varNr]] = value;  } -void cmdLindirectV(AgiGame *state, uint8 *p) { -	_v[_v[p0]] = _v[p1]; +void cmdLindirectV(AgiGame *state, uint8 *parameter) { +	uint16 varNr1 = parameter[0]; +	uint16 varNr2 = parameter[1]; + +	state->vars[state->vars[varNr1]] = state->vars[varNr2];  } -void cmdRindirect(AgiGame *state, uint8 *p) { -	_v[p0] = _v[_v[p1]]; +void cmdRindirect(AgiGame *state, uint8 *parameter) { +	uint16 varNr1 = parameter[0]; +	uint16 varNr2 = parameter[1]; + +	state->vars[varNr1] = state->vars[state->vars[varNr2]];  } -void cmdSet(AgiGame *state, uint8 *p) { -	setflag(*p, true); +void cmdSet(AgiGame *state, uint8 *parameter) { +	uint16 flagNr = parameter[0]; + +	state->_vm->setflag(flagNr, true);  } -void cmdReset(AgiGame *state, uint8 *p) { -	setflag(*p, false); +void cmdReset(AgiGame *state, uint8 *parameter) { +	uint16 flagNr = parameter[0]; + +	state->_vm->setflag(flagNr, false);  } -void cmdToggle(AgiGame *state, uint8 *p) { -	setflag(*p, !getflag(*p)); +void cmdToggle(AgiGame *state, uint8 *parameter) { +	AgiEngine *vm = state->_vm; +	uint16 flagNr = parameter[0]; +	bool curFlagState = vm->getflag(flagNr); + +	vm->setflag(flagNr, !curFlagState);  } -void cmdSetV(AgiGame *state, uint8 *p) { +void cmdSetV(AgiGame *state, uint8 *parameter) { +	uint16 flagNr = parameter[0]; +  	if (getVersion() < 0x2000) { -		_v[p0] = 1; +		state->vars[flagNr] = 1;  	} else { -		setflag(_v[p0], true); +		flagNr = state->vars[flagNr]; +		 +		state->_vm->setflag(flagNr, true);  	}  } -void cmdResetV(AgiGame *state, uint8 *p) { +void cmdResetV(AgiGame *state, uint8 *parameter) { +	uint16 flagNr = parameter[0]; +  	if (getVersion() < 0x2000) { -		_v[p0] = 0; +		state->vars[flagNr] = 0;  	} else { -		setflag(_v[p0], false); +		flagNr = state->vars[flagNr]; + +		state->_vm->setflag(flagNr, false);  	}  } -void cmdToggleV(AgiGame *state, uint8 *p) { +void cmdToggleV(AgiGame *state, uint8 *parameter) { +	uint16 flagNr = parameter[0]; +  	if (getVersion() < 0x2000) { -		_v[p0] ^= 1; +		state->vars[flagNr] ^= 1;  	} else { -		setflag(_v[p0], !getflag(_v[p0])); +		AgiEngine *vm = state->_vm; +		flagNr = state->vars[flagNr]; +		bool curFlagState = vm->getflag(flagNr); + +		vm->setflag(flagNr, !curFlagState);  	}  } -void cmdNewRoom(AgiGame *state, uint8 *p) { -	state->_vm->newRoom(p0); +void cmdNewRoom(AgiGame *state, uint8 *parameter) { +	uint16 newRoomNr = parameter[0]; + +	state->_vm->newRoom(newRoomNr);  	// WORKAROUND: Works around intro skipping bug (#1737343) in Gold Rush.  	// Intro was skipped because the enter-keypress finalizing the entering @@ -188,351 +248,524 @@ void cmdNewRoom(AgiGame *state, uint8 *p) {  	// keyboard buffer when the intro sequence's first room (Room 73) is  	// loaded so that no keys from the copy protection scene can be left  	// over to cause the intro to skip to the game's start. -	if (getGameID() == GID_GOLDRUSH && p0 == 73) +	if (getGameID() == GID_GOLDRUSH && newRoomNr == 73)  		state->keypress = 0;  } -void cmdNewRoomF(AgiGame *state, uint8 *p) { -	state->_vm->newRoom(_v[p0]); +void cmdNewRoomF(AgiGame *state, uint8 *parameter) { +	uint16 varNr = parameter[0]; + +	state->_vm->newRoom(state->vars[varNr]);  } -void cmdLoadView(AgiGame *state, uint8 *p) { -	state->_vm->agiLoadResource(rVIEW, p0); +void cmdLoadView(AgiGame *state, uint8 *parameter) { +	uint16 resourceNr = parameter[0]; + +	state->_vm->agiLoadResource(RESOURCETYPE_VIEW, resourceNr);  } -void cmdLoadLogic(AgiGame *state, uint8 *p) { -	state->_vm->agiLoadResource(rLOGIC, p0); +void cmdLoadLogic(AgiGame *state, uint8 *parameter) { +	uint16 resourceNr = parameter[0]; + +	state->_vm->agiLoadResource(RESOURCETYPE_LOGIC, resourceNr);  } -void cmdLoadSound(AgiGame *state, uint8 *p) { -	state->_vm->agiLoadResource(rSOUND, p0); +void cmdLoadSound(AgiGame *state, uint8 *parameter) { +	uint16 resourceNr = parameter[0]; + +	state->_vm->agiLoadResource(RESOURCETYPE_SOUND, resourceNr);  } -void cmdLoadViewF(AgiGame *state, uint8 *p) { -	state->_vm->agiLoadResource(rVIEW, _v[p0]); +void cmdLoadViewF(AgiGame *state, uint8 *parameter) { +	uint16 varNr = parameter[0]; + +	state->_vm->agiLoadResource(RESOURCETYPE_VIEW, state->vars[varNr]);  } -void cmdLoadLogicF(AgiGame *state, uint8 *p) { -	state->_vm->agiLoadResource(rLOGIC, _v[p0]); +void cmdLoadLogicF(AgiGame *state, uint8 *parameter) { +	uint16 varNr = parameter[0]; + +	state->_vm->agiLoadResource(RESOURCETYPE_LOGIC, state->vars[varNr]);  } -void cmdDiscardView(AgiGame *state, uint8 *p) { -	state->_vm->agiUnloadResource(rVIEW, p0); +void cmdDiscardView(AgiGame *state, uint8 *parameter) { +	uint16 resourceNr = parameter[0]; + +	state->_vm->agiUnloadResource(RESOURCETYPE_VIEW, resourceNr);  } -void cmdObjectOnAnything(AgiGame *state, uint8 *p) { -	vt.flags &= ~(fOnWater | fOnLand); +void cmdObjectOnAnything(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	screenObj->flags &= ~(fOnWater | fOnLand);  } -void cmdObjectOnLand(AgiGame *state, uint8 *p) { -	vt.flags |= fOnLand; +void cmdObjectOnLand(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	screenObj->flags |= fOnLand;  } -void cmdObjectOnWater(AgiGame *state, uint8 *p) { -	vt.flags |= fOnWater; +void cmdObjectOnWater(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	screenObj->flags |= fOnWater;  } -void cmdObserveHorizon(AgiGame *state, uint8 *p) { -	vt.flags &= ~fIgnoreHorizon; +void cmdObserveHorizon(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	screenObj->flags &= ~fIgnoreHorizon;  } -void cmdIgnoreHorizon(AgiGame *state, uint8 *p) { -	vt.flags |= fIgnoreHorizon; +void cmdIgnoreHorizon(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	screenObj->flags |= fIgnoreHorizon;  } -void cmdObserveObjs(AgiGame *state, uint8 *p) { -	vt.flags &= ~fIgnoreObjects; +void cmdObserveObjs(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	screenObj->flags &= ~fIgnoreObjects;  } -void cmdIgnoreObjs(AgiGame *state, uint8 *p) { -	vt.flags |= fIgnoreObjects; +void cmdIgnoreObjs(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	screenObj->flags |= fIgnoreObjects;  } -void cmdObserveBlocks(AgiGame *state, uint8 *p) { -	vt.flags &= ~fIgnoreBlocks; +void cmdObserveBlocks(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	screenObj->flags &= ~fIgnoreBlocks;  } -void cmdIgnoreBlocks(AgiGame *state, uint8 *p) { -	vt.flags |= fIgnoreBlocks; +void cmdIgnoreBlocks(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	screenObj->flags |= fIgnoreBlocks;  } -void cmdSetHorizon(AgiGame *state, uint8 *p) { -	state->horizon = p0; +void cmdSetHorizon(AgiGame *state, uint8 *parameter) { +	uint16 horizonValue = parameter[0]; + +	state->horizon = horizonValue;  } -void cmdGetPriority(AgiGame *state, uint8 *p) { -	_v[p1] = vt.priority; +void cmdGetPriority(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	uint16 varNr = parameter[1]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	state->vars[varNr] = screenObj->priority;  } -void cmdSetPriority(AgiGame *state, uint8 *p) { -	vt.flags |= fFixedPriority; -	vt.priority = p1; +void cmdSetPriority(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	uint16 priority = parameter[1]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; -	// WORKAROUND: this fixes bug #1712585 in KQ4 (dwarf sprite priority) -	// For this scene, ego (Rosella) hasn't got a fixed priority till script 54 -	// explicitly sets priority 8 for her, so that she can walk back to the table -	// without being drawn over the other dwarfs -	// It seems that in this scene, ego's priority is set to 8, but the priority of -	// the last dwarf with the soup bowls (view 152) is also set to 8, which causes -	// the dwarf to be drawn behind ego -	// With this workaround, when the game scripts set the priority of view 152 -	// (seventh dwarf with soup bowls), ego's priority is set to 7 -	// The game script itself sets priotity 8 for ego before she starts walking, -	// and then releases the fixed priority set on ego after ego is seated -	// Therefore, this workaround only affects that specific part of this scene -	// Ego is set to object 19 by script 54 -	if (getGameID() == GID_KQ4 && vt.currentView == 152) { -		state->viewTable[19].flags |= fFixedPriority; -		state->viewTable[19].priority = 7; -	} +	screenObj->flags |= fFixedPriority; +	screenObj->priority = priority;  } -void cmdSetPriorityF(AgiGame *state, uint8 *p) { -	vt.flags |= fFixedPriority; -	vt.priority = _v[p1]; +void cmdSetPriorityF(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	uint16 varNr = parameter[1]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	screenObj->flags |= fFixedPriority; +	screenObj->priority = state->vars[varNr];  } -void cmdReleasePriority(AgiGame *state, uint8 *p) { -	vt.flags &= ~fFixedPriority; +void cmdReleasePriority(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	screenObj->flags &= ~fFixedPriority;  } -void cmdSetUpperLeft(AgiGame *state, uint8 *p) {				// do nothing (AGI 2.917) +void cmdSetUpperLeft(AgiGame *state, uint8 *parameter) {				// do nothing (AGI 2.917)  } -void cmdStartUpdate(AgiGame *state, uint8 *p) { -	state->_vm->startUpdate(&vt); +void cmdStartUpdate(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	state->_vm->startUpdate(screenObj);  } -void cmdStopUpdate(AgiGame *state, uint8 *p) { -	state->_vm->stopUpdate(&vt); +void cmdStopUpdate(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	state->_vm->stopUpdate(screenObj);  } -void cmdCurrentView(AgiGame *state, uint8 *p) { -	_v[p1] = vt.currentView; +void cmdCurrentView(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	uint16 varNr = parameter[1]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	state->vars[varNr] = screenObj->currentViewNr;  } -void cmdCurrentCel(AgiGame *state, uint8 *p) { -	_v[p1] = vt.currentCel; -	debugC(4, kDebugLevelScripts, "v%d=%d", p1, _v[p1]); +void cmdCurrentCel(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	uint16 varNr = parameter[1]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	state->vars[varNr] = screenObj->currentCelNr; +	debugC(4, kDebugLevelScripts, "v%d=%d", varNr, state->vars[varNr]);  } -void cmdCurrentLoop(AgiGame *state, uint8 *p) { -	_v[p1] = vt.currentLoop; +void cmdCurrentLoop(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	uint16 varNr = parameter[1]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	state->vars[varNr] = screenObj->currentLoopNr;  } -void cmdLastCel(AgiGame *state, uint8 *p) { -	_v[p1] = vt.loopData->numCels - 1; +void cmdLastCel(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	uint16 varNr = parameter[1]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	state->vars[varNr] = screenObj->loopData->celCount - 1;  } -void cmdSetCel(AgiGame *state, uint8 *p) { -	state->_vm->setCel(&vt, p1); +void cmdSetCel(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	uint16 celNr = parameter[1]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; +	state->_vm->setCel(screenObj, celNr);  	if (getVersion() >= 0x2000) { -		vt.flags &= ~fDontupdate; +		screenObj->flags &= ~fDontupdate;  	}  } -void cmdSetCelF(AgiGame *state, uint8 *p) { -	state->_vm->setCel(&vt, _v[p1]); -	vt.flags &= ~fDontupdate; +void cmdSetCelF(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	uint16 varNr = parameter[1]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	state->_vm->setCel(screenObj, state->vars[varNr]); +	screenObj->flags &= ~fDontupdate;  } -void cmdSetView(AgiGame *state, uint8 *p) { -	state->_vm->setView(&vt, p1); +void cmdSetView(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	uint16 viewNr = parameter[1]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	state->_vm->setView(screenObj, viewNr);  } -void cmdSetViewF(AgiGame *state, uint8 *p) { -	state->_vm->setView(&vt, _v[p1]); +void cmdSetViewF(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	uint16 varNr = parameter[1]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	state->_vm->setView(screenObj, state->vars[varNr]);  } -void cmdSetLoop(AgiGame *state, uint8 *p) { -	state->_vm->setLoop(&vt, p1); +void cmdSetLoop(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	uint16 loopNr = parameter[1]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	state->_vm->setLoop(screenObj, loopNr);  } -void cmdSetLoopF(AgiGame *state, uint8 *p) { -	state->_vm->setLoop(&vt, _v[p1]); +void cmdSetLoopF(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	uint16 varNr = parameter[1]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	state->_vm->setLoop(screenObj, state->vars[varNr]);  } -void cmdNumberOfLoops(AgiGame *state, uint8 *p) { -	_v[p1] = vt.numLoops; +void cmdNumberOfLoops(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	uint16 varNr = parameter[1]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	state->vars[varNr] = screenObj->loopCount;  } -void cmdFixLoop(AgiGame *state, uint8 *p) { -	vt.flags |= fFixLoop; +void cmdFixLoop(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	screenObj->flags |= fFixLoop;  } -void cmdReleaseLoop(AgiGame *state, uint8 *p) { -	vt.flags &= ~fFixLoop; +void cmdReleaseLoop(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	screenObj->flags &= ~fFixLoop;  } -void cmdStepSize(AgiGame *state, uint8 *p) { -	vt.stepSize = _v[p1]; +void cmdStepSize(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	uint16 varNr = parameter[1]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	screenObj->stepSize = state->vars[varNr];  } -void cmdStepTime(AgiGame *state, uint8 *p) { -	vt.stepTime = vt.stepTimeCount = _v[p1]; +void cmdStepTime(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	uint16 varNr = parameter[1]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	screenObj->stepTime = screenObj->stepTimeCount = state->vars[varNr];  } -void cmdCycleTime(AgiGame *state, uint8 *p) { -	vt.cycleTime = vt.cycleTimeCount = _v[p1]; +void cmdCycleTime(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	uint16 varNr = parameter[1]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	screenObj->cycleTime = screenObj->cycleTimeCount = state->vars[varNr];  } -void cmdStopCycling(AgiGame *state, uint8 *p) { -	vt.flags &= ~fCycling; +void cmdStopCycling(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	screenObj->flags &= ~fCycling;  } -void cmdStartCycling(AgiGame *state, uint8 *p) { -	vt.flags |= fCycling; +void cmdStartCycling(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	screenObj->flags |= fCycling;  } -void cmdNormalCycle(AgiGame *state, uint8 *p) { -	vt.cycle = kCycleNormal; -	vt.flags |= fCycling; +void cmdNormalCycle(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	screenObj->cycle = kCycleNormal; +	screenObj->flags |= fCycling;  } -void cmdReverseCycle(AgiGame *state, uint8 *p) { -	vt.cycle = kCycleReverse; -	vt.flags |= fCycling; +void cmdReverseCycle(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	screenObj->cycle = kCycleReverse; +	screenObj->flags |= fCycling;  } -void cmdSetDir(AgiGame *state, uint8 *p) { -	vt.direction = _v[p1]; +void cmdSetDir(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	uint16 varNr = parameter[1]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	screenObj->direction = state->vars[varNr];  } -void cmdGetDir(AgiGame *state, uint8 *p) { -	_v[p1] = vt.direction; +void cmdGetDir(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	uint16 varNr = parameter[1]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	state->vars[varNr] = screenObj->direction;  } -void cmdGetRoomF(AgiGame *state, uint8 *p) { -	_v[p1] = state->_vm->objectGetLocation(_v[p0]); +void cmdGetRoomF(AgiGame *state, uint8 *parameter) { +	uint16 varNr1 = parameter[0]; +	uint16 varNr2 = parameter[1]; + +	state->vars[varNr2] = state->_vm->objectGetLocation(state->vars[varNr1]);  } -void cmdPut(AgiGame *state, uint8 *p) { -	state->_vm->objectSetLocation(p0, _v[p1]); +void cmdPut(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	uint16 varNr = parameter[1]; + +	state->_vm->objectSetLocation(objectNr, state->vars[varNr]);  } -void cmdPutF(AgiGame *state, uint8 *p) { -	state->_vm->objectSetLocation(_v[p0], _v[p1]); +void cmdPutF(AgiGame *state, uint8 *parameter) { +	uint16 varNr1 = parameter[0]; +	uint16 varNr2 = parameter[1]; + +	state->_vm->objectSetLocation(state->vars[varNr1], state->vars[varNr2]);  } -void cmdDrop(AgiGame *state, uint8 *p) { -	state->_vm->objectSetLocation(p0, 0); +void cmdDrop(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; + +	state->_vm->objectSetLocation(objectNr, 0);  } -void cmdGet(AgiGame *state, uint8 *p) { -	state->_vm->objectSetLocation(p0, EGO_OWNED); +void cmdGet(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; + +	state->_vm->objectSetLocation(objectNr, EGO_OWNED);  } -void cmdGetV1(AgiGame *state, uint8 *p) { -	state->_vm->objectSetLocation(p0, EGO_OWNED_V1); +void cmdGetV1(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; + +	state->_vm->objectSetLocation(objectNr, EGO_OWNED_V1);  } -void cmdGetF(AgiGame *state, uint8 *p) { -	state->_vm->objectSetLocation(_v[p0], EGO_OWNED); +void cmdGetF(AgiGame *state, uint8 *parameter) { +	uint16 varNr = parameter[0]; + +	state->_vm->objectSetLocation(state->vars[varNr], EGO_OWNED);  } -void cmdWordToString(AgiGame *state, uint8 *p) { -	strcpy(state->strings[p0], state->egoWords[p1].word); +void cmdWordToString(AgiGame *state, uint8 *parameter) { +	uint16 stringNr = parameter[0]; +	uint16 wordNr = parameter[1]; + +	strcpy(state->strings[stringNr], state->_vm->_words->getEgoWord(wordNr));  } -void cmdOpenDialogue(AgiGame *state, uint8 *p) { -	state->hasWindow = true; +void cmdOpenDialogue(AgiGame *state, uint8 *parameter) { +	state->_vm->_text->dialogueOpen();  } -void cmdCloseDialogue(AgiGame *state, uint8 *p) { -	state->hasWindow = false; +void cmdCloseDialogue(AgiGame *state, uint8 *parameter) { +	state->_vm->_text->dialogueClose();  } -void cmdCloseWindow(AgiGame *state, uint8 *p) { -	state->_vm->closeWindow(); +void cmdCloseWindow(AgiGame *state, uint8 *parameter) { +	state->_vm->_text->closeWindow();  } -void cmdStatusLineOn(AgiGame *state, uint8 *p) { -	state->statusLine = true; -	state->_vm->writeStatus(); +void cmdStatusLineOn(AgiGame *state, uint8 *parameter) { +	TextMgr *text = state->_vm->_text; + +	text->statusEnable(); +	text->statusDraw();  } -void cmdStatusLineOff(AgiGame *state, uint8 *p) { -	state->statusLine = false; -	state->_vm->writeStatus(); +void cmdStatusLineOff(AgiGame *state, uint8 *parameter) { +	TextMgr *text = state->_vm->_text; + +	text->statusDisable(); +	state->_vm->_text->statusClear();  } -void cmdShowObj(AgiGame *state, uint8 *p) { -	state->_vm->_sprites->showObj(p0); +void cmdShowObj(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; + +	state->_vm->_sprites->showObject(objectNr);  } -void cmdShowObjV(AgiGame *state, uint8 *p) { -	state->_vm->_sprites->showObj(_v[p0]); +void cmdShowObjV(AgiGame *state, uint8 *parameter) { +	uint16 varNr = parameter[0]; + +	state->_vm->_sprites->showObject(state->vars[varNr]);  } -void cmdSound(AgiGame *state, uint8 *p) { -	state->_vm->_sound->startSound(p0, p1); +void cmdSound(AgiGame *state, uint8 *parameter) { +	uint16 resourceNr = parameter[0]; +	uint16 flagNr = parameter[1]; + +	state->_vm->_sound->startSound(resourceNr, flagNr);  } -void cmdStopSound(AgiGame *state, uint8 *p) { +void cmdStopSound(AgiGame *state, uint8 *parameter) {  	state->_vm->_sound->stopSound();  } -void cmdMenuInput(AgiGame *state, uint8 *p) { -	state->_vm->newInputMode(INPUT_MENU); +void cmdMenuInput(AgiGame *state, uint8 *parameter) { +	AgiEngine *vm = state->_vm; + +	if (vm->getflag(VM_FLAG_MENUS_WORK)) { +		vm->_menu->delayedExecute(); +	}  } -void cmdEnableItem(AgiGame *state, uint8 *p) { -	state->_vm->_menu->setItem(p0, true); +void cmdEnableItem(AgiGame *state, uint8 *parameter) { +	uint16 controlCode = parameter[0]; + +	state->_vm->_menu->itemEnable(controlCode);  } -void cmdDisableItem(AgiGame *state, uint8 *p) { -	state->_vm->_menu->setItem(p0, false); +void cmdDisableItem(AgiGame *state, uint8 *parameter) { +	uint16 controlCode = parameter[0]; + +	state->_vm->_menu->itemDisable(controlCode);  } -void cmdSubmitMenu(AgiGame *state, uint8 *p) { +void cmdSubmitMenu(AgiGame *state, uint8 *parameter) {  	state->_vm->_menu->submit();  } -void cmdSetScanStart(AgiGame *state, uint8 *p) { +void cmdSetScanStart(AgiGame *state, uint8 *parameter) {  	state->_curLogic->sIP = state->_curLogic->cIP;  } -void cmdResetScanStart(AgiGame *state, uint8 *p) { +void cmdResetScanStart(AgiGame *state, uint8 *parameter) {  	state->_curLogic->sIP = 2;  } -void cmdSaveGame(AgiGame *state, uint8 *p) { -	state->simpleSave ? state->_vm->saveGameSimple() : state->_vm->saveGameDialog(); -} - -void cmdLoadGame(AgiGame *state, uint8 *p) { -	assert(1); -	state->simpleSave ? state->_vm->loadGameSimple() : state->_vm->loadGameDialog(); +void cmdSaveGame(AgiGame *state, uint8 *parameter) { +	if (state->automaticSave) { +		if (state->_vm->saveGameAutomatic()) { +			// automatic save succeded +			return; +		} +		// fall back to regular dialog otherwise +	} +	state->_vm->saveGameDialog();  } -void cmdInitDisk(AgiGame *state, uint8 *p) {				// do nothing +void cmdLoadGame(AgiGame *state, uint8 *parameter) { +	if (state->automaticSave) { +		if (state->_vm->loadGameAutomatic()) { +			// automatic restore succeded +			return; +		} +		// fall back to regular dialog otherwise +	} +	state->_vm->loadGameDialog();  } -void cmdLog(AgiGame *state, uint8 *p) {				// do nothing +void cmdInitDisk(AgiGame *state, uint8 *parameter) {				// do nothing  } -void cmdTraceOn(AgiGame *state, uint8 *p) {				// do nothing +void cmdLog(AgiGame *state, uint8 *parameter) {				// do nothing  } -void cmdTraceInfo(AgiGame *state, uint8 *p) {				// do nothing +void cmdTraceOn(AgiGame *state, uint8 *parameter) {				// do nothing  } -void cmdShowMem(AgiGame *state, uint8 *p) { -	state->_vm->messageBox("Enough memory"); +void cmdTraceInfo(AgiGame *state, uint8 *parameter) {				// do nothing  } -void cmdInitJoy(AgiGame *state, uint8 *p) { // do nothing +void cmdShowMem(AgiGame *state, uint8 *parameter) { +	state->_vm->_text->messageBox("Enough memory");  } -void cmdScriptSize(AgiGame *state, uint8 *p) { -	debug(0, "script.size(%d)", p0); +void cmdInitJoy(AgiGame *state, uint8 *parameter) { // do nothing  } -void cmdCancelLine(AgiGame *state, uint8 *p) { -	state->inputBuffer[0] = 0; -	state->cursorPos = 0; -	state->_vm->writePrompt(); +void cmdScriptSize(AgiGame *state, uint8 *parameter) { +	debug(0, "script.size(%d)", parameter[0]);  }  // This implementation is based on observations of Amiga's Gold Rush. @@ -545,13 +778,16 @@ void cmdCancelLine(AgiGame *state, uint8 *p) {  // 4051 (When ego is stationary),  // 471 (When walking on the first screen's bridge),  // 71 (When walking around, using the mouse or the keyboard). -void cmdObjStatusF(AgiGame *state, uint8 *p) { +void cmdObjStatusF(AgiGame *state, uint8 *parameter) { +	uint16 varNr = parameter[0]; +	ScreenObjEntry *screenObj = &state->screenObjTable[state->vars[varNr]]; +  	const char *cycleDesc;  // Object's cycle description line  	const char *motionDesc; // Object's motion description line  	char msg[256];          // The whole object status message  	// Generate cycle description line -	switch (vt_v.cycle) { +	switch (screenObj->cycle) {  	case kCycleNormal:  		cycleDesc = "normal cycle";  		break; @@ -570,7 +806,7 @@ void cmdObjStatusF(AgiGame *state, uint8 *p) {  	}  	// Generate motion description line -	switch (vt_v.motion) { +	switch (screenObj->motionType) {  	case kMotionNormal:  		motionDesc = "normal motion";  		break; @@ -599,14 +835,14 @@ void cmdObjStatusF(AgiGame *state, uint8 *p) {  		"stepsize: %d\n" \  		"%s\n" \  		"%s", -		_v[p0], -		vt_v.xPos, vt_v.xSize, -		vt_v.yPos, vt_v.ySize, -		vt_v.priority, -		vt_v.stepSize, +		state->vars[varNr], +		screenObj->xPos, screenObj->xSize, +		screenObj->yPos, screenObj->ySize, +		screenObj->priority, +		screenObj->stepSize,  		cycleDesc,  		motionDesc); -	state->_vm->messageBox(msg); +	state->_vm->_text->messageBox(msg);  }  // unknown commands: @@ -617,49 +853,67 @@ void cmdObjStatusF(AgiGame *state, uint8 *p) {  // unk_174: Change priority table (used in KQ4) -- j5  // unk_177: Disable menus completely -- j5  // unk_181: Deactivate keypressed control (default control of ego) -void cmdSetSimple(AgiGame *state, uint8 *p) { +void cmdSetSimple(AgiGame *state, uint8 *parameter) {  	if (!(getFeatures() & (GF_AGI256 | GF_AGI256_2))) { -		state->simpleSave = true; +		int16 stringNr = parameter[0]; +		const char *textPtr = nullptr; + +		state->automaticSave = false; + +		// Try to get description for automatic saves +//		if (state->strings_curLogic->texts && state->_curLogic->numTexts >= textNr) { +			textPtr = state->strings[stringNr]; +			strncpy(state->automaticSaveDescription, textPtr, sizeof(state->automaticSaveDescription)); +			if (state->automaticSaveDescription[0]) { +				// We got it and it's set, so enable automatic saving +				state->automaticSave = true; +			} +//		} +  	} else { // AGI256 and AGI256-2 use this unknown170 command to load 256 color pictures.  		// Load the picture. Similar to void cmdLoad_pic(AgiGame *state, uint8 *p). -		state->_vm->_sprites->eraseBoth(); -		state->_vm->agiLoadResource(rPICTURE, _v[p0]); +		SpritesMgr *spritesMgr = state->_vm->_sprites; +		uint16 varNr = parameter[0]; +		uint16 resourceNr = state->vars[varNr]; + +		spritesMgr->eraseSprites(); +		state->_vm->agiLoadResource(RESOURCETYPE_PICTURE, resourceNr);  		// Draw the picture. Similar to void cmdDraw_pic(AgiGame *state, uint8 *p). -		state->_vm->_picture->decodePicture(_v[p0], false, true); -		state->_vm->_sprites->blitBoth(); -		state->pictureShown = 0; +		state->_vm->_picture->decodePicture(resourceNr, false, true); +		spritesMgr->drawAllSpriteLists(); +		state->pictureShown = false;  		// Show the picture. Similar to void cmdShow_pic(AgiGame *state, uint8 *p). -		setflag(fOutputMode, false); -		state->_vm->closeWindow(); +		state->_vm->setflag(VM_FLAG_OUTPUT_MODE, false); +		state->_vm->_text->closeWindow();  		state->_vm->_picture->showPic(); -		state->pictureShown = 1; +		state->pictureShown = true; -		// Simulate slowww computer. Many effects rely on this -		state->_vm->pause(kPausePicture); +		// Loading trigger +		state->_vm->loadingTrigger_DrawPicture();  	}  } -void cmdPopScript(AgiGame *state, uint8 *p) { +void cmdPopScript(AgiGame *state, uint8 *parameter) {  	if (getVersion() >= 0x2915) {  		debug(0, "pop.script");  	}  } -void cmdHoldKey(AgiGame *state, uint8 *p) { +void cmdHoldKey(AgiGame *state, uint8 *parameter) {  	if (getVersion() >= 0x3098) {  		state->_vm->_egoHoldKey = true;  	}  } -void cmdDiscardSound(AgiGame *state, uint8 *p) { +void cmdDiscardSound(AgiGame *state, uint8 *parameter) {  	if (getVersion() >= 0x2936) {  		debug(0, "discard.sound");  	}  } -void cmdHideMouse(AgiGame *state, uint8 *p) { +void cmdHideMouse(AgiGame *state, uint8 *parameter) {  	// WORKAROUND: Turns off current movement that's being caused with the mouse.  	// This fixes problems with too many popup boxes appearing in the Amiga  	// Gold Rush's copy protection failure scene (i.e. the hanging scene, logic.192). @@ -667,34 +921,48 @@ void cmdHideMouse(AgiGame *state, uint8 *p) {  	// to walk somewhere else than to the right using the mouse.  	// FIXME: Write a proper implementation using disassembly and  	//        apply it to other games as well if applicable. -	state->viewTable[0].flags &= ~fAdjEgoXY; +	//state->screenObjTable[SCREENOBJECTS_EGO_ENTRY].flags &= ~fAdjEgoXY; +	state->_vm->_game.mouseHidden = true;  	g_system->showMouse(false);  } -void cmdAllowMenu(AgiGame *state, uint8 *p) { +void cmdAllowMenu(AgiGame *state, uint8 *parameter) { +	uint16 allowed = parameter[0]; +  	if (getVersion() >= 0x3098) { -		setflag(fMenusWork, ((p0 != 0) ? true : false)); +		if (allowed) { +			state->_vm->_menu->accessAllow(); +		} else { +			state->_vm->_menu->accessDeny(); +		}  	}  } -void cmdShowMouse(AgiGame *state, uint8 *p) { +void cmdShowMouse(AgiGame *state, uint8 *parameter) { +	state->_vm->_game.mouseHidden = false; +  	g_system->showMouse(true);  } -void cmdFenceMouse(AgiGame *state, uint8 *p) { -	state->mouseFence.moveTo(p0, p1); -	state->mouseFence.setWidth(p2 - p0); -	state->mouseFence.setHeight(p3 - p1); +void cmdFenceMouse(AgiGame *state, uint8 *parameter) { +	uint16 varNr1 = parameter[0]; +	uint16 varNr2 = parameter[1]; +	uint16 varNr3 = parameter[2]; +	uint16 varNr4 = parameter[3]; + +	state->mouseFence.moveTo(varNr1, varNr2); +	state->mouseFence.setWidth(varNr3 - varNr1); +	state->mouseFence.setHeight(varNr4 - varNr1);  } -void cmdReleaseKey(AgiGame *state, uint8 *p) { +void cmdReleaseKey(AgiGame *state, uint8 *parameter) {  	if (getVersion() >= 0x3098) {  		state->_vm->_egoHoldKey = false;  	}  } -void cmdAdjEgoMoveToXY(AgiGame *state, uint8 *p) { +void cmdAdjEgoMoveToXY(AgiGame *state, uint8 *parameter) {  	int8 x, y;  	switch (logicNamesCmd[182].argumentsLength()) { @@ -703,8 +971,8 @@ void cmdAdjEgoMoveToXY(AgiGame *state, uint8 *p) {  	// (Using arguments (0, 0), (0, 7), (0, 8), (9, 9) and (-9, 9)).  	case 2:  		// Both arguments are signed 8-bit (i.e. in range -128 to +127). -		x = (int8) p0; -		y = (int8) p1; +		x = (int8) parameter[0]; +		y = (int8) parameter[1];  		// Turn off ego's current movement caused with the mouse if  		// adj.ego.move.to.x.y is called with other arguments than previously. @@ -717,7 +985,7 @@ void cmdAdjEgoMoveToXY(AgiGame *state, uint8 *p) {  		// by something else because this command doesn't do any flag manipulations  		// in the Amiga version - checked it with disassembly).  		if (x != state->adjMouseX || y != state->adjMouseY) -			state->viewTable[EGO_VIEW_TABLE].flags &= ~fAdjEgoXY; +			state->screenObjTable[SCREENOBJECTS_EGO_ENTRY].flags &= ~fAdjEgoXY;  		state->adjMouseX = x;  		state->adjMouseY = y; @@ -727,20 +995,24 @@ void cmdAdjEgoMoveToXY(AgiGame *state, uint8 *p) {  	// TODO: Check where (if anywhere) the 0 arguments version is used  	case 0:  	default: -		state->viewTable[0].flags |= fAdjEgoXY; +		state->screenObjTable[SCREENOBJECTS_EGO_ENTRY].flags |= fAdjEgoXY;  		break;  	}  } -void cmdParse(AgiGame *state, uint8 *p) { -	_v[vWordNotFound] = 0; -	setflag(fEnteredCli, false); -	setflag(fSaidAcceptedInput, false); +void cmdParse(AgiGame *state, uint8 *parameter) { +	TextMgr *text = state->_vm->_text; +	uint16 stringNr = parameter[0]; + +	state->vars[VM_VAR_WORD_NOT_FOUND] = 0; +	state->_vm->setflag(VM_FLAG_ENTERED_CLI, false); +	state->_vm->setflag(VM_FLAG_SAID_ACCEPTED_INPUT, false); -	state->_vm->dictionaryWords(state->_vm->agiSprintf(state->strings[p0])); +	state->_vm->_words->parseUsingDictionary(text->stringPrintf(state->strings[stringNr]));  } -void cmdCall(AgiGame *state, uint8 *p) { +void cmdCall(AgiGame *state, uint8 *parameter) { +	uint16 logicNr = parameter[0];  	int oldCIP;  	int oldLognum; @@ -749,35 +1021,48 @@ void cmdCall(AgiGame *state, uint8 *p) {  	oldCIP = state->_curLogic->cIP;  	oldLognum = state->lognum; -	state->_vm->runLogic(p0); +	state->_vm->runLogic(logicNr);  	state->lognum = oldLognum;  	state->_curLogic = &state->logics[state->lognum];  	state->_curLogic->cIP = oldCIP;  } -void cmdCallF(AgiGame *state, uint8 *p) { -	cmdCall(state, &_v[p0]); +void cmdCallF(AgiGame *state, uint8 *parameter) { +	uint16 varNr = parameter[0]; + +	cmdCall(state, &state->vars[varNr]);  } -void cmdDrawPicV1(AgiGame *state, uint8 *p) { -	debugC(6, kDebugLevelScripts, "=== draw pic V1 %d ===", _v[p0]); -	state->_vm->_picture->decodePicture(_v[p0], true); +void cmdDrawPicV1(AgiGame *state, uint8 *parameter) { +	uint16 varNr = parameter[0]; +	uint16 resourceNr = state->vars[varNr]; + +	debugC(6, kDebugLevelScripts, "=== draw pic V1 %d ===", resourceNr); +	state->_vm->_picture->decodePicture(resourceNr, true); -	state->_vm->clearPrompt(); +	// TODO: check, if this was really done +	state->_vm->_text->promptClear(); -	// Simulate slowww computer. Many effects rely on this -	state->_vm->pause(kPausePicture); +	// Loading trigger +	state->_vm->loadingTrigger_DrawPicture();  } -void cmdDrawPic(AgiGame *state, uint8 *p) { -	debugC(6, kDebugLevelScripts, "=== draw pic %d ===", _v[p0]); -	state->_vm->_sprites->eraseBoth(); -	state->_vm->_picture->decodePicture(_v[p0], true); -	state->_vm->_sprites->blitBoth(); -	state->_vm->_sprites->commitBoth(); -	state->pictureShown = 0; -	debugC(6, kDebugLevelScripts, "--- end of draw pic %d ---", _v[p0]); +void cmdDrawPic(AgiGame *state, uint8 *parameter) { +	SpritesMgr *spritesMgr = state->_vm->_sprites; +	uint16 varNr = parameter[0]; +	uint16 resourceNr = state->vars[varNr]; + +	debugC(6, kDebugLevelScripts, "=== draw pic %d ===", resourceNr); + +	spritesMgr->eraseSprites(); // === CHANGED === +	state->_vm->_picture->decodePicture(resourceNr, true); +	spritesMgr->buildAllSpriteLists(); +	spritesMgr->drawAllSpriteLists(); // === CHANGED === +	//state->_vm->_sprites->blitBoth(); +	//state->_vm->_sprites->commitBoth(); +	state->pictureShown = false; +	debugC(6, kDebugLevelScripts, "--- end of draw pic %d ---", resourceNr);  	// WORKAROUND for a script bug which exists in SQ1, logic scripts  	// 20 and 110. Flag 103 is not reset correctly, which leads to erroneous @@ -790,484 +1075,640 @@ void cmdDrawPic(AgiGame *state, uint8 *p) {  	// With this workaround, when the player goes back to picture 20 (1 screen  	// above the ground), flag 103 is reset, thereby fixing this issue. Note  	// that this is a script bug and occurs in the original interpreter as well. -	// Fixes bug #1658514: AGI: SQ1 (2.2 DOS ENG) bizzare exploding roger -	if (getGameID() == GID_SQ1 && _v[p0] == 20) -		setflag(103, false); +	// Fixes bug #3056: AGI: SQ1 (2.2 DOS ENG) bizzare exploding roger +	if (getGameID() == GID_SQ1 && resourceNr == 20) +		state->_vm->setflag(103, false); -	// Simulate slowww computer. Many effects rely on this -	state->_vm->pause(kPausePicture); +	// Loading trigger +	state->_vm->loadingTrigger_DrawPicture();  } -void cmdShowPic(AgiGame *state, uint8 *p) { +void cmdShowPic(AgiGame *state, uint8 *parameter) {  	debugC(6, kDebugLevelScripts, "=== show pic ==="); -	setflag(fOutputMode, false); -	state->_vm->closeWindow(); -	state->_vm->_picture->showPic(); -	state->pictureShown = 1; +	state->_vm->setflag(VM_FLAG_OUTPUT_MODE, false); +	state->_vm->_text->closeWindow(); +	state->_vm->_picture->showPicWithTransition(); +	state->pictureShown = true;  	debugC(6, kDebugLevelScripts, "--- end of show pic ---");  } -void cmdLoadPic(AgiGame *state, uint8 *p) { -	state->_vm->_sprites->eraseBoth(); -	state->_vm->agiLoadResource(rPICTURE, _v[p0]); -	state->_vm->_sprites->blitBoth(); -	state->_vm->_sprites->commitBoth(); +void cmdLoadPic(AgiGame *state, uint8 *parameter) { +	SpritesMgr *spritesMgr = state->_vm->_sprites; +	uint16 varNr = parameter[0]; +	uint16 resourceNr = state->vars[varNr]; + +	spritesMgr->eraseSprites(); +	state->_vm->agiLoadResource(RESOURCETYPE_PICTURE, resourceNr); +	spritesMgr->buildAllSpriteLists(); +	spritesMgr->drawAllSpriteLists();  } -void cmdLoadPicV1(AgiGame *state, uint8 *p) { -	state->_vm->agiLoadResource(rPICTURE, _v[p0]); +void cmdLoadPicV1(AgiGame *state, uint8 *parameter) { +	uint16 varNr = parameter[0]; +	uint16 resourceNr = state->vars[varNr]; + +	state->_vm->agiLoadResource(RESOURCETYPE_PICTURE, resourceNr);  } -void cmdDiscardPic(AgiGame *state, uint8 *p) { +void cmdDiscardPic(AgiGame *state, uint8 *parameter) {  	debugC(6, kDebugLevelScripts, "--- discard pic ---");  	// do nothing  } -void cmdOverlayPic(AgiGame *state, uint8 *p) { +void cmdOverlayPic(AgiGame *state, uint8 *parameter) { +	SpritesMgr *spritesMgr = state->_vm->_sprites; +	uint16 varNr = parameter[0]; +	uint16 resourceNr = state->vars[varNr]; +  	debugC(6, kDebugLevelScripts, "--- overlay pic ---"); -	state->_vm->_sprites->eraseBoth(); -	state->_vm->_picture->decodePicture(_v[p0], false); -	state->_vm->_sprites->blitBoth(); -	state->pictureShown = 0; -	state->_vm->_sprites->commitBoth(); +	spritesMgr->eraseSprites(); +	state->_vm->_picture->decodePicture(resourceNr, false); +	spritesMgr->buildAllSpriteLists(); +	spritesMgr->drawAllSpriteLists(); +	spritesMgr->showAllSpriteLists(); +	state->pictureShown = false; -	// Simulate slowww computer. Many effects rely on this -	state->_vm->pause(kPausePicture); +	// Loading trigger +	state->_vm->loadingTrigger_DrawPicture();  } -void cmdShowPriScreen(AgiGame *state, uint8 *p) { -	state->_vm->_debug.priority = 1; -	state->_vm->_sprites->eraseBoth(); -	state->_vm->_picture->showPic(); -	state->_vm->_sprites->blitBoth(); +void cmdShowPriScreen(AgiGame *state, uint8 *parameter) { +	GfxMgr *gfx = state->_vm->_gfx; + +	gfx->debugShowMap(1); // switch to priority map  	state->_vm->waitKey(); -	state->_vm->_debug.priority = 0; -	state->_vm->_sprites->eraseBoth(); -	state->_vm->_picture->showPic(); -	state->_vm->_sprites->blitBoth(); +	gfx->debugShowMap(0); // switch back to visual map  } -void cmdAnimateObj(AgiGame *state, uint8 *p) { +void cmdAnimateObj(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; +  	if (getVersion() < 0x2000) { -		if (vt.flags & fDidntMove) +		if (screenObj->flags & fDidntMove)  			return;  	} else { -		if (vt.flags & fAnimated) +		if (screenObj->flags & fAnimated)  			return;  	} -	debugC(4, kDebugLevelScripts, "animate vt entry #%d", p0); -	vt.flags = fAnimated | fUpdate | fCycling; +	debugC(4, kDebugLevelScripts, "animate vt entry #%d", objectNr); +	screenObj->flags = fAnimated | fUpdate | fCycling;  	if (getVersion() < 0x2000) { -		vt.flags |= fDidntMove; +		screenObj->flags |= fDidntMove;  	} -	vt.motion = kMotionNormal; -	vt.cycle = kCycleNormal; -	vt.direction = 0; +	screenObj->motionType = kMotionNormal; +	screenObj->cycle = kCycleNormal; +	screenObj->direction = 0;  } -void cmdUnanimateAll(AgiGame *state, uint8 *p) { +void cmdUnanimateAll(AgiGame *state, uint8 *parameter) {  	int i; -	for (i = 0; i < MAX_VIEWTABLE; i++) -		state->viewTable[i].flags &= ~(fAnimated | fDrawn); +	state->_vm->_sprites->eraseSprites(); + +	for (i = 0; i < SCREENOBJECTS_MAX; i++) +		state->screenObjTable[i].flags &= ~(fAnimated | fDrawn);  } -void cmdDraw(AgiGame *state, uint8 *p) { -	if (vt.flags & fDrawn) -		return; +void cmdDraw(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; -	if (vt.ySize <= 0 || vt.xSize <= 0) +	if (screenObj->flags & fDrawn)  		return; -	debugC(4, kDebugLevelScripts, "draw entry %d", vt.entry); +//	if (vt.ySize <= 0 || vt.xSize <= 0) +//		return; + +	debugC(4, kDebugLevelScripts, "draw entry %d", screenObj->objectNr); -	vt.flags |= fUpdate; +	screenObj->flags |= fUpdate;  	if (getVersion() >= 0x3000) { -		state->_vm->setLoop(&vt, vt.currentLoop); -		state->_vm->setCel(&vt, vt.currentCel); +		state->_vm->setLoop(screenObj, screenObj->currentLoopNr); +		state->_vm->setCel(screenObj, screenObj->currentCelNr);  	} -	state->_vm->fixPosition(p0); -	vt.xPos2 = vt.xPos; -	vt.yPos2 = vt.yPos; -	vt.celData2 = vt.celData; -	state->_vm->_sprites->eraseUpdSprites(); -	vt.flags |= fDrawn; - -	// WORKAROUND: This fixes a bug with AGI Fanmade game Space Trek. -	// The original workaround checked if AGI version was <= 2.440, which could -	// cause regressions with some AGI games. The original workaround no longer -	// works for Space Trek in ScummVM, as all fanmade games are set to use -	// AGI version 2.917, but it applies to all other games where AGI version is -	// <= 2.440, which was not the original purpose of this workaround. It is -	// assumed that this bug is caused by AGI Studio, so this applies to all -	// fanmade games only. -	// TODO: Investigate this further and check if any other fanmade AGI -	// games are affected. If yes, then it'd be best to set this for Space -	// Trek only -	if (getFeatures() & GF_FANMADE)	// See Sarien bug #546562 -		vt.flags |= fAnimated; - -	state->_vm->_sprites->blitUpdSprites(); -	vt.flags &= ~fDontupdate; - -	state->_vm->_sprites->commitBlock(vt.xPos, vt.yPos - vt.ySize + 1, vt.xPos + vt.xSize - 1, vt.yPos, true); - -	debugC(4, kDebugLevelScripts, "vt entry #%d flags = %02x", p0, vt.flags); -} - -void cmdErase(AgiGame *state, uint8 *p) { -	if (~vt.flags & fDrawn) -		return; +	SpritesMgr *sprites = state->_vm->_sprites; -	state->_vm->_sprites->eraseUpdSprites(); +	state->_vm->fixPosition(objectNr); +	screenObj->xPos_prev = screenObj->xPos; +	screenObj->yPos_prev = screenObj->yPos; +	screenObj->xSize_prev = screenObj->xSize; +	screenObj->ySize_prev = screenObj->ySize; +	//screenObj->celData2 = screenObj->celData; +	sprites->eraseRegularSprites(); +	screenObj->flags |= fDrawn; +	sprites->buildRegularSpriteList(); +	sprites->drawRegularSpriteList(); +	sprites->showSprite(screenObj); +	screenObj->flags &= ~fDontupdate; -	if (vt.flags & fUpdate) { -		vt.flags &= ~fDrawn; -	} else { -		state->_vm->_sprites->eraseNonupdSprites(); -		vt.flags &= ~fDrawn; -		state->_vm->_sprites->blitNonupdSprites(); -	} -	state->_vm->_sprites->blitUpdSprites(); +	debugC(4, kDebugLevelScripts, "vt entry #%d flags = %02x", objectNr, screenObj->flags); +} + +void cmdErase(AgiGame *state, uint8 *parameter) { +	SpritesMgr *sprites = state->_vm->_sprites; +	uint16 objectNr = parameter[0]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; -	int x1, y1, x2, y2; +	bool noUpdateFlag = false; + +	if (!(screenObj->flags & fDrawn)) +		return; + +	sprites->eraseRegularSprites(); +	if ((screenObj->flags & fUpdate) == 0) { +		noUpdateFlag = true; +		sprites->eraseStaticSprites(); +	} -	x1 = MIN((int)MIN(vt.xPos, vt.xPos2), MIN(vt.xPos + vt.celData->width, vt.xPos2 + vt.celData2->width)); -	x2 = MAX((int)MAX(vt.xPos, vt.xPos2), MAX(vt.xPos + vt.celData->width, vt.xPos2 + vt.celData2->width)); -	y1 = MIN((int)MIN(vt.yPos, vt.yPos2), MIN(vt.yPos - vt.celData->height, vt.yPos2 - vt.celData2->height)); -	y2 = MAX((int)MAX(vt.yPos, vt.yPos2), MAX(vt.yPos - vt.celData->height, vt.yPos2 - vt.celData2->height)); +	screenObj->flags &= ~fDrawn; -	state->_vm->_sprites->commitBlock(x1, y1, x2, y2, true); +	if (noUpdateFlag) { +		sprites->buildStaticSpriteList(); +		sprites->drawStaticSpriteList(); +	} +	sprites->buildRegularSpriteList(); +	sprites->drawRegularSpriteList(); +	sprites->showSprite(screenObj);  } -void cmdPosition(AgiGame *state, uint8 *p) { -	vt.xPos = vt.xPos2 = p1; -	vt.yPos = vt.yPos2 = p2; +void cmdPosition(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	uint16 xPos = parameter[1]; +	uint16 yPos = parameter[2]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; -	// WORKAROUND: Part of the fix for bug #1659209 "AGI: Space Trek sprite duplication" -	// with an accompanying identical workaround in position.v-command (i.e. command 0x26). -	// These two workarounds together make up the whole fix. The bug was caused by -	// wrongly written script data in Space Trek v1.0's scripts (At least logics 4 and 11). -	// Position-command was called with horizontal values over 200 (Outside the screen!). -	// Clipping the coordinates so the views stay wholly on-screen seems to fix the problems. -	//   It is probable (Would have to check better with disassembly to be completely sure) -	// that AGI 2.440 clipped its coordinates in its position and position.v-commands -	// although AGI 2.917 certainly doesn't (Checked that with disassembly) and that's why -	// Space Trek may have worked better with AGI 2.440 than with some other AGI versions. -	//   I haven't checked but if Space Trek solely abuses the position-command we wouldn't -	// strictly need the identical workaround in the position.v-command but it does make -	// for a nice symmetry. -	if (getFeatures() & GF_CLIPCOORDS) -		state->_vm->clipViewCoordinates(&vt); +	screenObj->xPos = screenObj->xPos_prev = xPos; +	screenObj->yPos = screenObj->yPos_prev = yPos;  } -void cmdPositionV1(AgiGame *state, uint8 *p) { -	vt.xPos = p1; -	vt.yPos = p2; +void cmdPositionV1(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	uint16 xPos = parameter[1]; +	uint16 yPos = parameter[2]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	screenObj->xPos = xPos; +	screenObj->yPos = yPos;  } -void cmdPositionF(AgiGame *state, uint8 *p) { -	vt.xPos = vt.xPos2 = _v[p1]; -	vt.yPos = vt.yPos2 = _v[p2]; +void cmdPositionF(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	uint16 varNr1 = parameter[1]; +	uint16 varNr2 = parameter[2]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; -	// WORKAROUND: Part of the fix for bug #1659209 "AGI: Space Trek sprite duplication" -	// with an accompanying identical workaround in position-command (i.e. command 0x25). -	// See that workaround's comment for more in-depth information. -	if (getFeatures() & GF_CLIPCOORDS) -		state->_vm->clipViewCoordinates(&vt); +	screenObj->xPos = screenObj->xPos_prev = state->vars[varNr1]; +	screenObj->yPos = screenObj->yPos_prev = state->vars[varNr2];  } -void cmdPositionFV1(AgiGame *state, uint8 *p) { -	vt.xPos = _v[p1]; -	vt.yPos = _v[p2]; +void cmdPositionFV1(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	uint16 varNr1 = parameter[1]; +	uint16 varNr2 = parameter[2]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	screenObj->xPos = state->vars[varNr1]; +	screenObj->yPos = state->vars[varNr2];  } -void cmdGetPosn(AgiGame *state, uint8 *p) { -	state->vars[p1] = (unsigned char)vt.xPos; -	state->vars[p2] = (unsigned char)vt.yPos; +void cmdGetPosn(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	uint16 varNr1 = parameter[1]; +	uint16 varNr2 = parameter[2]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	state->vars[varNr1] = (unsigned char)screenObj->xPos; +	state->vars[varNr2] = (unsigned char)screenObj->yPos;  } -void cmdReposition(AgiGame *state, uint8 *p) { -	int dx = (int8) _v[p1], dy = (int8) _v[p2]; +void cmdReposition(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	uint16 varNr1 = parameter[1]; +	uint16 varNr2 = parameter[2]; +	int16 dx = (int8) state->vars[varNr1]; +	int16 dy = (int8) state->vars[varNr2]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];  	debugC(4, kDebugLevelScripts, "dx=%d, dy=%d", dx, dy); -	vt.flags |= fUpdatePos; +	screenObj->flags |= fUpdatePos; -	if (dx < 0 && vt.xPos < -dx) -		vt.xPos = 0; +	if (dx < 0 && screenObj->xPos < -dx) +		screenObj->xPos = 0;  	else -		vt.xPos += dx; +		screenObj->xPos += dx; -	if (dy < 0 && vt.yPos < -dy) -		vt.yPos = 0; +	if (dy < 0 && screenObj->yPos < -dy) +		screenObj->yPos = 0;  	else -		vt.yPos += dy; +		screenObj->yPos += dy; -	state->_vm->fixPosition(p0); +	state->_vm->fixPosition(objectNr);  } -void cmdRepositionV1(AgiGame *state, uint8 *p) { -	vt.xPos2 = vt.xPos; -	vt.yPos2 = vt.yPos; -	vt.flags |= fUpdatePos; +void cmdRepositionV1(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	uint16 xPosPlus = parameter[1]; +	uint16 yPosPlus = parameter[2]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; -	vt.xPos = (vt.xPos + p1) & 0xff; -	vt.yPos = (vt.yPos + p2) & 0xff; +	screenObj->xPos_prev = screenObj->xPos; +	screenObj->yPos_prev = screenObj->yPos; +	screenObj->flags |= fUpdatePos; + +	screenObj->xPos = (screenObj->xPos + xPosPlus) & 0xff; +	screenObj->yPos = (screenObj->yPos + yPosPlus) & 0xff;  } -void cmdRepositionTo(AgiGame *state, uint8 *p) { -	vt.xPos = p1; -	vt.yPos = p2; -	vt.flags |= fUpdatePos; -	state->_vm->fixPosition(p0); +void cmdRepositionTo(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	uint16 xPos = parameter[1]; +	uint16 yPos = parameter[2]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	screenObj->xPos = xPos; +	screenObj->yPos = yPos; +	screenObj->flags |= fUpdatePos; +	state->_vm->fixPosition(objectNr);  } -void cmdRepositionToF(AgiGame *state, uint8 *p) { -	vt.xPos = _v[p1]; -	vt.yPos = _v[p2]; -	vt.flags |= fUpdatePos; -	state->_vm->fixPosition(p0); +void cmdRepositionToF(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	uint16 varNr1 = parameter[1]; +	uint16 varNr2 = parameter[2]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	screenObj->xPos = state->vars[varNr1]; +	screenObj->yPos = state->vars[varNr2]; +	screenObj->flags |= fUpdatePos; +	state->_vm->fixPosition(objectNr);  } -void cmdAddToPic(AgiGame *state, uint8 *p) { -	state->_vm->_sprites->addToPic(p0, p1, p2, p3, p4, p5, p6); +void cmdAddToPic(AgiGame *state, uint8 *parameter) { +	uint16 viewNr = parameter[0]; +	uint16 loopNr = parameter[1]; +	uint16 celNr = parameter[2]; +	uint16 xPos = parameter[3]; +	uint16 yPos = parameter[4]; +	uint16 priority = parameter[5]; +	uint16 border = parameter[6]; + +	state->_vm->_sprites->addToPic(viewNr, loopNr, celNr, xPos, yPos, priority, border);  } -void cmdAddToPicV1(AgiGame *state, uint8 *p) { -	state->_vm->_sprites->addToPic(p0, p1, p2, p3, p4, p5, -1); +void cmdAddToPicV1(AgiGame *state, uint8 *parameter) { +	uint16 viewNr = parameter[0]; +	uint16 loopNr = parameter[1]; +	uint16 celNr = parameter[2]; +	uint16 xPos = parameter[3]; +	uint16 yPos = parameter[4]; +	uint16 priority = parameter[5]; + +	state->_vm->_sprites->addToPic(viewNr, loopNr, celNr, xPos, yPos, priority, -1);  } -void cmdAddToPicF(AgiGame *state, uint8 *p) { -	state->_vm->_sprites->addToPic(_v[p0], _v[p1], _v[p2], _v[p3], _v[p4], _v[p5], _v[p6]); +void cmdAddToPicF(AgiGame *state, uint8 *parameter) { +	uint16 viewNr = state->vars[parameter[0]]; +	uint16 loopNr = state->vars[parameter[1]]; +	uint16 celNr = state->vars[parameter[2]]; +	uint16 xPos = state->vars[parameter[3]]; +	uint16 yPos = state->vars[parameter[4]]; +	uint16 priority = state->vars[parameter[5]]; +	uint16 border = state->vars[parameter[6]]; + +	state->_vm->_sprites->addToPic(viewNr, loopNr, celNr, xPos, yPos, priority, border);  } -void cmdForceUpdate(AgiGame *state, uint8 *p) { -	state->_vm->_sprites->eraseBoth(); -	state->_vm->_sprites->blitBoth(); -	state->_vm->_sprites->commitBoth(); +void cmdForceUpdate(AgiGame *state, uint8 *parameter) { +	SpritesMgr *spritesMgr = state->_vm->_sprites; + +	spritesMgr->eraseSprites(); +	spritesMgr->buildAllSpriteLists(); +	spritesMgr->drawAllSpriteLists(); +	spritesMgr->showAllSpriteLists();  } -void cmdReverseLoop(AgiGame *state, uint8 *p) { -	debugC(4, kDebugLevelScripts, "o%d, f%d", p0, p1); -	vt.cycle = kCycleRevLoop; -	vt.flags |= (fDontupdate | fUpdate | fCycling); -	vt.parm1 = p1; -	setflag(p1, false); +void cmdReverseLoop(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	uint16 loopFlag = parameter[1]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	debugC(4, kDebugLevelScripts, "o%d, f%d", objectNr, loopFlag); +	screenObj->cycle = kCycleRevLoop; +	screenObj->flags |= (fDontupdate | fUpdate | fCycling); +	screenObj->loop_flag = loopFlag; +	state->_vm->setflag(screenObj->loop_flag, false);  } -void cmdReverseLoopV1(AgiGame *state, uint8 *p) { -	debugC(4, kDebugLevelScripts, "o%d, f%d", p0, p1); -	vt.cycle = kCycleRevLoop; -	state->_vm->setCel(&vt, 0); -	vt.flags |= (fDontupdate | fUpdate | fCycling); -	vt.parm1 = p1; -	vt.parm3 = 0; +void cmdReverseLoopV1(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	uint16 loopFlag = parameter[1]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	debugC(4, kDebugLevelScripts, "o%d, f%d", objectNr, loopFlag); +	screenObj->cycle = kCycleRevLoop; +	state->_vm->setCel(screenObj, 0); +	screenObj->flags |= (fDontupdate | fUpdate | fCycling); +	screenObj->loop_flag = loopFlag; +	//screenObj->parm3 = 0;  } -void cmdEndOfLoop(AgiGame *state, uint8 *p) { -	debugC(4, kDebugLevelScripts, "o%d, f%d", p0, p1); -	vt.cycle = kCycleEndOfLoop; -	vt.flags |= (fDontupdate | fUpdate | fCycling); -	vt.parm1 = p1; -	setflag(p1, false); +void cmdEndOfLoop(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	uint16 loopFlag = parameter[1]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	debugC(4, kDebugLevelScripts, "o%d, f%d", objectNr, loopFlag); +	screenObj->cycle = kCycleEndOfLoop; +	screenObj->flags |= (fDontupdate | fUpdate | fCycling); +	screenObj->loop_flag = loopFlag; +	state->_vm->setflag(screenObj->loop_flag, false);  } -void cmdEndOfLoopV1(AgiGame *state, uint8 *p) { -	debugC(4, kDebugLevelScripts, "o%d, f%d", p0, p1); -	vt.cycle = kCycleEndOfLoop; -	state->_vm->setCel(&vt, 0); -	vt.flags |= (fDontupdate | fUpdate | fCycling); -	vt.parm1 = p1; -	vt.parm3 = 0; +void cmdEndOfLoopV1(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	uint16 loopFlag = parameter[1]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	debugC(4, kDebugLevelScripts, "o%d, f%d", objectNr, loopFlag); +	screenObj->cycle = kCycleEndOfLoop; +	state->_vm->setCel(screenObj, 0); +	screenObj->flags |= (fDontupdate | fUpdate | fCycling); +	screenObj->loop_flag = loopFlag; +	//screenObj->parm3 = 0;  } -void cmdBlock(AgiGame *state, uint8 *p) { -	debugC(4, kDebugLevelScripts, "x1=%d, y1=%d, x2=%d, y2=%d", p0, p1, p2, p3); +void cmdBlock(AgiGame *state, uint8 *parameter) { +	uint16 x1 = parameter[0]; +	uint16 y1 = parameter[1]; +	uint16 x2 = parameter[2]; +	uint16 y2 = parameter[3]; + +	debugC(4, kDebugLevelScripts, "x1=%d, y1=%d, x2=%d, y2=%d", x1, y1, x2, y2);  	state->block.active = true; -	state->block.x1 = p0; -	state->block.y1 = p1; -	state->block.x2 = p2; -	state->block.y2 = p3; +	state->block.x1 = x1; +	state->block.y1 = y1; +	state->block.x2 = x2; +	state->block.y2 = y2;  } -void cmdUnblock(AgiGame *state, uint8 *p) { +void cmdUnblock(AgiGame *state, uint8 *parameter) {  	state->block.active = false;  } -void cmdNormalMotion(AgiGame *state, uint8 *p) { -	vt.motion = kMotionNormal; +void cmdNormalMotion(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	screenObj->motionType = kMotionNormal;  } -void cmdStopMotion(AgiGame *state, uint8 *p) { -	vt.direction = 0; -	vt.motion = kMotionNormal; -	if (p0 == 0) {		// ego only -		_v[vEgoDir] = 0; +void cmdStopMotion(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	screenObj->direction = 0; +	screenObj->motionType = kMotionNormal; +	if (objectNr == 0) {		// ego only +		state->vars[VM_VAR_EGO_DIRECTION] = 0;  		state->playerControl = false;  	}  } -void cmdStopMotionV1(AgiGame *state, uint8 *p) { -	vt.flags &= ~fAnimated; +void cmdStopMotionV1(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	screenObj->flags &= ~fAnimated;  } -void cmdStartMotion(AgiGame *state, uint8 *p) { -	vt.motion = kMotionNormal; -	if (p0 == 0) {		// ego only -		_v[vEgoDir] = 0; +void cmdStartMotion(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	screenObj->motionType = kMotionNormal; +	if (objectNr == 0) {		// ego only +		state->vars[VM_VAR_EGO_DIRECTION] = 0;  		state->playerControl = true;  	}  } -void cmdStartMotionV1(AgiGame *state, uint8 *p) { -	vt.flags |= fAnimated; +void cmdStartMotionV1(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	screenObj->flags |= fAnimated;  } -void cmdPlayerControl(AgiGame *state, uint8 *p) { +void cmdPlayerControl(AgiGame *state, uint8 *parameter) { +	ScreenObjEntry *screenObjEgo = &state->screenObjTable[SCREENOBJECTS_EGO_ENTRY]; +  	state->playerControl = true; -	state->viewTable[0].motion = kMotionNormal; + +	if (screenObjEgo->motionType != kMotionEgo) +		screenObjEgo->motionType = kMotionNormal;  } -void cmdProgramControl(AgiGame *state, uint8 *p) { +void cmdProgramControl(AgiGame *state, uint8 *parameter) {  	state->playerControl = false;  } -void cmdFollowEgo(AgiGame *state, uint8 *p) { -	vt.motion = kMotionFollowEgo; -	vt.parm1 = p1 > vt.stepSize ? p1 : vt.stepSize; -	vt.parm2 = p2; -	vt.parm3 = 0xff; +void cmdFollowEgo(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	uint16 followStepSize = parameter[1]; +	uint16 followFlag = parameter[2]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	screenObj->motionType = kMotionFollowEgo; +	if (followStepSize <= screenObj->stepSize) { +		screenObj->follow_stepSize = screenObj->stepSize; +	} else { +		screenObj->follow_stepSize = followStepSize; +	} +	screenObj->follow_flag = followFlag; +	screenObj->follow_count = 255;  	if (getVersion() < 0x2000) { -		_v[p2] = 0; -		vt.flags |= fUpdate | fAnimated; +		state->vars[screenObj->follow_flag] = 0; +		screenObj->flags |= fUpdate | fAnimated;  	} else { -		setflag(p2, false); -		vt.flags |= fUpdate; +		state->_vm->setflag(screenObj->follow_flag, false); +		screenObj->flags |= fUpdate;  	}  } -void cmdMoveObj(AgiGame *state, uint8 *p) { +void cmdMoveObj(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	uint16 moveX = parameter[1]; +	uint16 moveY = parameter[2]; +	uint16 stepSize = parameter[3]; +	uint16 moveFlag = parameter[4]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr];  	// _D (_D_WARN "o=%d, x=%d, y=%d, s=%d, f=%d", p0, p1, p2, p3, p4); -	vt.motion = kMotionMoveObj; -	vt.parm1 = p1; -	vt.parm2 = p2; -	vt.parm3 = vt.stepSize; -	vt.parm4 = p4; +	screenObj->motionType = kMotionMoveObj; +	screenObj->move_x = moveX; +	screenObj->move_y = moveY; +	screenObj->move_stepSize = screenObj->stepSize; +	screenObj->move_flag = moveFlag; -	if (p3 != 0) -		vt.stepSize = p3; +	if (stepSize != 0) +		screenObj->stepSize = stepSize;  	if (getVersion() < 0x2000) { -		_v[p4] = 0; -		vt.flags |= fUpdate | fAnimated; +		state->vars[moveFlag] = 0; +		screenObj->flags |= fUpdate | fAnimated;  	} else { -		setflag(p4, false); -		vt.flags |= fUpdate; +		state->_vm->setflag(screenObj->move_flag, false); +		screenObj->flags |= fUpdate;  	} -	if (p0 == 0) +	if (objectNr == 0)  		state->playerControl = false;  	// AGI 2.272 (ddp, xmas) doesn't call move_obj!  	if (getVersion() > 0x2272) -		state->_vm->moveObj(&vt); +		state->_vm->moveObj(screenObj);  } -void cmdMoveObjF(AgiGame *state, uint8 *p) { -	vt.motion = kMotionMoveObj; -	vt.parm1 = _v[p1]; -	vt.parm2 = _v[p2]; -	vt.parm3 = vt.stepSize; -	vt.parm4 = p4; +void cmdMoveObjF(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	uint16 moveX = state->vars[parameter[1]]; +	uint16 moveY = state->vars[parameter[2]]; +	uint16 stepSize = state->vars[parameter[3]]; +	uint16 moveFlag = parameter[4]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	screenObj->motionType = kMotionMoveObj; +	screenObj->move_x = moveX; +	screenObj->move_y = moveY; +	screenObj->move_stepSize = screenObj->stepSize; +	screenObj->move_flag = moveFlag; -	if (_v[p3] != 0) -		vt.stepSize = _v[p3]; +	if (stepSize != 0) +		screenObj->stepSize = stepSize; -	setflag(p4, false); -	vt.flags |= fUpdate; +	state->_vm->setflag(screenObj->move_flag, false); +	screenObj->flags |= fUpdate; -	if (p0 == 0) +	if (objectNr == 0)  		state->playerControl = false;  	// AGI 2.272 (ddp, xmas) doesn't call move_obj!  	if (getVersion() > 0x2272) -		state->_vm->moveObj(&vt); +		state->_vm->moveObj(screenObj);  } -void cmdWander(AgiGame *state, uint8 *p) { -	if (p0 == 0) +void cmdWander(AgiGame *state, uint8 *parameter) { +	uint16 objectNr = parameter[0]; +	ScreenObjEntry *screenObj = &state->screenObjTable[objectNr]; + +	if (objectNr == 0)  		state->playerControl = false; -	vt.motion = kMotionWander; +	screenObj->motionType = kMotionWander;  	if (getVersion() < 0x2000) { -		vt.flags |= fUpdate | fAnimated; +		screenObj->flags |= fUpdate | fAnimated;  	} else { -		vt.flags |= fUpdate; +		screenObj->flags |= fUpdate;  	}  } -void cmdSetGameID(AgiGame *state, uint8 *p) { -	if (state->_curLogic->texts && (p0 - 1) <= state->_curLogic->numTexts) -		Common::strlcpy(state->id, state->_curLogic->texts[p0 - 1], 8); +void cmdSetGameID(AgiGame *state, uint8 *parameter) { +	uint16 textNr = parameter[0]; + +	if (state->_curLogic->texts && (textNr - 1) <= state->_curLogic->numTexts) +		Common::strlcpy(state->id, state->_curLogic->texts[textNr - 1], 8);  	else  		state->id[0] = 0;  	debug(0, "Game ID: \"%s\"", state->id);  } -void cmdPause(AgiGame *state, uint8 *p) { -	int tmp = state->clockEnabled; -	const char *b[] = { "Continue", NULL }; -	const char *b_ru[] = { "\x8f\xe0\xae\xa4\xae\xab\xa6\xa8\xe2\xec", NULL }; +void cmdPause(AgiGame *state, uint8 *parameter) { +	AgiEngine *vm = state->_vm; +	int originalClockState = state->clockEnabled; +	bool skipPause = false;  	state->clockEnabled = false; -	switch (getLanguage()) { -	case Common::RU_RUS: -		state->_vm->selectionBox("  \x88\xa3\xe0\xa0 \xae\xe1\xe2\xa0\xad\xae\xa2\xab\xa5\xad\xa0.  \n\n\n", b_ru); -		break; -	default: -		state->_vm->selectionBox("  Game is paused.  \n\n\n", b); -		break; +	// We check in here, if a special key was specified to trigger menus. +	// If that's the case, normally triggering the menu should be handled inside handleController() +	// For the rare cases, where this approach doesn't work because the trigger is not mapped to a controller, +	//  we trigger the menu in here. +	// +	// for further study read the comments for handleController() +	// +	// This is needed for at least Mixed Up Mother Goose for Apple IIgs. +	if (state->specialMenuTriggerKey) { +		if (vm->_menu->isAvailable()) { +			// Pulldown-menu is actually available (was submitted) +			skipPause = true; + +			// Check, if special trigger key is currently NOT mapped. +			if (vm->getSpecialMenuControllerSlot() < 0) { +				// menu trigger is not mapped, trigger menu +				vm->_menu->delayedExecute(); +			} else { +				// menu trigger is mapped, do not replace "pause" +				skipPause = false; +			} +		} else { +			warning("menu is not available, doing regular pause game instead"); +		} +	} + +	if (!skipPause) { +		// Show pause message box +		state->_vm->_systemUI->pauseDialog();  	} -	state->clockEnabled = tmp; + +	state->clockEnabled = originalClockState;  } -void cmdSetMenu(AgiGame *state, uint8 *p) { -	debugC(4, kDebugLevelScripts, "text %02x of %02x", p0, state->_curLogic->numTexts); +void cmdSetMenu(AgiGame *state, uint8 *parameter) { +	uint16 textNr = parameter[0]; + +	debugC(4, kDebugLevelScripts, "text %02x of %02x", textNr, state->_curLogic->numTexts); -	if (state->_curLogic->texts != NULL && p0 <= state->_curLogic->numTexts) -		state->_vm->_menu->add(state->_curLogic->texts[p0 - 1]); +	if (state->_curLogic->texts != NULL && (textNr - 1) <= state->_curLogic->numTexts) { +		const char *menuText = state->_curLogic->texts[textNr - 1]; + +		state->_vm->_menu->addMenu(menuText); +	}  } -void cmdSetMenuItem(AgiGame *state, uint8 *p) { -	debugC(4, kDebugLevelScripts, "text %02x of %02x", p0, state->_curLogic->numTexts); +void cmdSetMenuItem(AgiGame *state, uint8 *parameter) { +	uint16 textNr = parameter[0] - 1; +	uint16 controllerSlot = parameter[1]; -	if (state->_curLogic->texts != NULL && p0 <= state->_curLogic->numTexts) -		state->_vm->_menu->addItem(state->_curLogic->texts[p0 - 1], p1); +	debugC(4, kDebugLevelScripts, "text %02x of %02x", textNr, state->_curLogic->numTexts); + +	if (state->_curLogic->texts != NULL && textNr <= state->_curLogic->numTexts) { +		const char *menuItemText = state->_curLogic->texts[textNr]; + +		state->_vm->_menu->addMenuItem(menuItemText, controllerSlot); +	}  } -void cmdVersion(AgiGame *state, uint8 *p) { +void cmdVersion(AgiGame *state, uint8 *parameter) {  	char ver2Msg[] =  	    "\n"  	    "                               \n\n" -	    "  Emulating Sierra AGI v%x.%03x\n"; +	    "  ScummVM Sierra AGI v%x.%03x";  	char ver3Msg[] =  	    "\n"  	    "                             \n\n" -	    "  Emulating AGI v%x.002.%03x\n"; -	// no Sierra as it wraps textbox +	    "ScummVM Sierra AGI v%x.002.%03x";  	Common::String verMsg = TITLE " v%s"; @@ -1278,98 +1719,113 @@ void cmdVersion(AgiGame *state, uint8 *p) {  	verMsg += (maj == 2 ? ver2Msg : ver3Msg);  	verMsg = Common::String::format(verMsg.c_str(), gScummVMVersion, maj, min); -	state->_vm->messageBox(verMsg.c_str()); +	state->_vm->_text->messageBox(verMsg.c_str());  } -void cmdConfigureScreen(AgiGame *state, uint8 *p) { -	state->lineMinPrint = p0; -	state->lineUserInput = p1; -	state->lineStatus = p2; +void cmdConfigureScreen(AgiGame *state, uint8 *parameter) { +	TextMgr *textMgr = state->_vm->_text; +	uint16 lineMinPrint = parameter[0]; +	uint16 promptRow = parameter[1]; +	uint16 statusRow = parameter[2]; + +	state->_vm->_text->configureScreen(lineMinPrint); + +	textMgr->statusRow_Set(statusRow); +	textMgr->promptRow_Set(promptRow);  } -void cmdTextScreen(AgiGame *state, uint8 *p) { -	debugC(4, kDebugLevelScripts, "switching to text mode"); -	state->gfxMode = false; +void cmdTextScreen(AgiGame *state, uint8 *parameter) { +	GfxMgr  *gfxMgr = state->_vm->_gfx; +	TextMgr *textMgr = state->_vm->_text; -	// Simulates the "bright background bit" of the PC video -	// controller. -	if (state->colorBg) -		state->colorBg |= 0x08; +	debugC(4, kDebugLevelScripts, "switching to text mode"); -	state->_vm->_gfx->clearScreen(state->colorBg); +	state->gfxMode = false; +	gfxMgr->setPalette(false); // set text-mode palette +	textMgr->charAttrib_Set(textMgr->_textAttrib.foreground, textMgr->_textAttrib.background); +	gfxMgr->clearDisplay(0); +	textMgr->clearLines(0, 24, textMgr->_textAttrib.combinedBackground);  } -void cmdGraphics(AgiGame *state, uint8 *p) { +void cmdGraphics(AgiGame *state, uint8 *parameter) {  	debugC(4, kDebugLevelScripts, "switching to graphics mode"); -	if (!state->gfxMode) { -		state->gfxMode = true; -		state->_vm->_gfx->clearScreen(0); -		state->_vm->_picture->showPic(); -		state->_vm->writeStatus(); -		state->_vm->writePrompt(); -	} +	state->_vm->redrawScreen();  } -void cmdSetTextAttribute(AgiGame *state, uint8 *p) { -	state->colorFg = p0; -	state->colorBg = p1; - -	if (state->gfxMode) { -		if (state->colorBg != 0) { -			state->colorFg = 0; -			state->colorBg = 15; -		} -	} +void cmdSetTextAttribute(AgiGame *state, uint8 *parameter) { +	int16 foreground = parameter[0]; +	int16 background = parameter[1]; +	state->_vm->_text->charAttrib_Set(foreground, background);  } -void cmdStatus(AgiGame *state, uint8 *p) { -	state->_vm->inventory(); +void cmdStatus(AgiGame *state, uint8 *parameter) { +	TextMgr *textMgr = state->_vm->_text; +	InventoryMgr *inventoryMgr = state->_vm->_inventory; + +	textMgr->inputEditOn(); +	textMgr->charAttrib_Push(); +	textMgr->charAttrib_Set(0, 15); + +	cmdTextScreen(state, parameter); + +	inventoryMgr->show(); + +	//invent_state = 0; +	textMgr->charAttrib_Pop(); +	state->_vm->redrawScreen();  } -void cmdQuit(AgiGame *state, uint8 *p) { -	const char *buttons[] = { "Quit", "Continue", NULL }; +void cmdQuit(AgiGame *state, uint8 *parameter) { +	uint16 withoutPrompt = parameter[0]; +//	const char *buttons[] = { "Quit", "Continue", NULL };  	state->_vm->_sound->stopSound(); -	if (p0) { +	if (withoutPrompt) {  		state->_vm->quitGame();  	} else { -		if (state->_vm->selectionBox(" Quit the game, or continue? \n\n\n", buttons) == 0) { +		if (state->_vm->_systemUI->quitDialog()) {  			state->_vm->quitGame();  		}  	}  } -void cmdQuitV1(AgiGame *state, uint8 *p) { +void cmdQuitV1(AgiGame *state, uint8 *parameter) {  	state->_vm->_sound->stopSound();  	state->_vm->quitGame();  } -void cmdRestartGame(AgiGame *state, uint8 *p) { -	const char *buttons[] = { "Restart", "Continue", NULL }; -	int sel; +void cmdRestartGame(AgiGame *state, uint8 *parameter) { +	bool doRestart = false;  	state->_vm->_sound->stopSound(); -	sel = getflag(fAutoRestart) ? 0 : -		state->_vm->selectionBox(" Restart game, or continue? \n\n\n", buttons); -	if (sel == 0) { +	if (state->_vm->getflag(VM_FLAG_AUTO_RESTART)) { +		doRestart = true; +	} else { +		doRestart = state->_vm->_systemUI->restartDialog(); +	} + +	if (doRestart) {  		state->_vm->_restartGame = true; -		setflag(fRestartGame, true); -		state->_vm->_menu->enableAll(); +		state->_vm->setflag(VM_FLAG_RESTART_GAME, true); +		state->_vm->_menu->itemEnableAll();  	}  } -void cmdDistance(AgiGame *state, uint8 *p) { +void cmdDistance(AgiGame *state, uint8 *parameter) { +	uint16 objectNr1 = parameter[0]; +	uint16 objectNr2 = parameter[1]; +	uint16 destVarNr = parameter[2];  	int16 x1, y1, x2, y2, d; -	VtEntry *v0 = &state->viewTable[p0]; -	VtEntry *v1 = &state->viewTable[p1]; - -	if (v0->flags & fDrawn && v1->flags & fDrawn) { -		x1 = v0->xPos + v0->xSize / 2; -		y1 = v0->yPos; -		x2 = v1->xPos + v1->xSize / 2; -		y2 = v1->yPos; +	ScreenObjEntry *screenObj1 = &state->screenObjTable[objectNr1]; +	ScreenObjEntry *screenObj2 = &state->screenObjTable[objectNr2]; + +	if (screenObj1->flags & fDrawn && screenObj2->flags & fDrawn) { +		x1 = screenObj1->xPos + screenObj1->xSize / 2; +		y1 = screenObj1->yPos; +		x2 = screenObj2->xPos + screenObj2->xSize / 2; +		y2 = screenObj2->yPos;  		d = ABS(x1 - x2) + ABS(y1 - y2);  		if (d > 0xfe)  			d = 0xfe; @@ -1377,7 +1833,8 @@ void cmdDistance(AgiGame *state, uint8 *p) {  		d = 0xff;  	} -	// WORKAROUND: Fixes King's Quest IV's script bug #1660424 (KQ4: Zombie bug). +	// WORKAROUND: Fixes King's Quest IV's script bug #3067 (KQ4: Zombie bug). +	// This bug also happens in the original interpreter.  	// In the graveyard (Rooms 16 and 18) at night if you had the Obsidian Scarab (Item 4)  	// and you were very close to a spot where a zombie was going to rise up from the  	// ground you could reproduce the bug. Just standing there and letting the zombie @@ -1386,7 +1843,7 @@ void cmdDistance(AgiGame *state, uint8 *p) {  	// wouldn't chase Rosella around anymore. If it had worked correctly the zombie  	// wouldn't have come up at all or it would have come up and gone back down  	// immediately. The latter approach is the one implemented here. -	if (getGameID() == GID_KQ4 && (_v[vCurRoom] == 16 || _v[vCurRoom] == 18) && p2 >= 221 && p2 <= 223) { +	if (getGameID() == GID_KQ4 && (state->vars[VM_VAR_CURRENT_ROOM] == 16 || state->vars[VM_VAR_CURRENT_ROOM] == 18) && destVarNr >= 221 && destVarNr <= 223) {  		// Rooms 16 and 18 are graveyards where three zombies come up at night. They use logics 16 and 18.  		// Variables 221-223 are used to save the distance between each zombie and Rosella.  		// Variables 155, 156 and 162 are used to save the state of each zombie in room 16. @@ -1399,228 +1856,271 @@ void cmdDistance(AgiGame *state, uint8 *p) {  		// a zombie or the zombie getting turned away by the scarab) we make it appear the  		// zombie is far away from Rosella if the zombie is not already up and chasing her.  		enum zombieStates {ZOMBIE_SET_TO_RISE_UP, ZOMBIE_RISING_UP, ZOMBIE_CHASING_EGO}; -		uint8 zombieStateVarNumList[] = {155, 156, (uint8)((_v[vCurRoom] == 16) ? 162 : 158)}; -		uint8 zombieNum         = p2 - 221;                         // Zombie's number (In range 0-2) +		uint8 zombieStateVarNumList[] = {155, 156, (uint8)((state->vars[VM_VAR_CURRENT_ROOM] == 16) ? 162 : 158)}; +		uint8 zombieNum         = destVarNr - 221;                         // Zombie's number (In range 0-2)  		uint8 zombieStateVarNum = zombieStateVarNumList[zombieNum]; // Number of the variable containing zombie's state -		uint8 zombieState       = _v[zombieStateVarNum];            // Zombie's state +		uint8 zombieState       = state->vars[zombieStateVarNum];   // Zombie's state  		// If zombie is not chasing Rosella then set its distance from Rosella to the maximum  		if (zombieState != ZOMBIE_CHASING_EGO)  			d = 0xff;  	} -	_v[p2] = (unsigned char)d; +	state->vars[destVarNr] = (unsigned char)d;  } -void cmdAcceptInput(AgiGame *state, uint8 *p) { +void cmdAcceptInput(AgiGame *state, uint8 *parameter) { +	TextMgr *textMgr = state->_vm->_text; +  	debugC(4, kDebugLevelScripts | kDebugLevelInput, "input normal"); -	state->_vm->newInputMode(INPUT_NORMAL); -	state->inputEnabled = true; -	state->_vm->writePrompt(); +	state->_vm->newInputMode(INPUTMODE_NORMAL); + +	textMgr->promptEnable(); +	textMgr->promptRedraw();  } -void cmdPreventInput(AgiGame *state, uint8 *p) { +void cmdPreventInput(AgiGame *state, uint8 *parameter) { +	TextMgr *textMgr = state->_vm->_text; +  	debugC(4, kDebugLevelScripts | kDebugLevelInput, "no input"); -	state->_vm->newInputMode(INPUT_NONE); -	state->inputEnabled = false; +	state->_vm->newInputMode(INPUTMODE_NONE); + +	textMgr->promptDisable(); + +	textMgr->inputEditOn(); +	textMgr->clearLine(textMgr->promptRow_Get(), 0); +} + +void cmdCancelLine(AgiGame *state, uint8 *parameter) { +	state->_vm->_text->promptCancelLine(); +} + +void cmdEchoLine(AgiGame *state, uint8 *parameter) { +	TextMgr *textMgr = state->_vm->_text; -	// Always clear with black background. Fixes bug #3080041. -	state->_vm->clearPrompt(true); +	if (textMgr->promptIsEnabled()) { +		textMgr->promptEchoLine(); +	}  } -void cmdGetString(AgiGame *state, uint8 *p) { -	int tex, row, col; +void cmdGetString(AgiGame *state, uint8 *parameter) { +	TextMgr *textMgr = state->_vm->_text; +	int16 stringDestNr = parameter[0]; +	int16 leadInTextNr = parameter[1] - 1; +	int16 stringRow = parameter[2]; +	int16 stringColumn = parameter[3]; +	int16 stringMaxLen = parameter[4]; +	bool previousEditState = false; +	const char *leadInTextPtr = nullptr; + +	if (stringMaxLen > TEXT_STRING_MAX_SIZE) +		stringMaxLen = TEXT_STRING_MAX_SIZE; -	debugC(4, kDebugLevelScripts, "%d %d %d %d %d", p0, p1, p2, p3, p4); +	debugC(4, kDebugLevelScripts, "%d %d %d %d %d", stringDestNr, leadInTextNr, stringRow, stringColumn, stringMaxLen); -	tex = p1 - 1; -	row = p2; -	col = p3; +	previousEditState = textMgr->inputGetEditStatus(); + +	textMgr->charPos_Push(); +	textMgr->inputEditOn();  	// Workaround for SQLC bug.  	// See Sarien bug #792125 for details -	if (row > 24) -		row = 24; -	if (col > 39) -		col = 39; +//	if (promptRow > 24) +//		promptRow = 24; +//	if (promptColumn > 39) +//		promptColumn = 39; -	state->_vm->newInputMode(INPUT_GETSTRING); +	if (stringRow < 25) { +		textMgr->charPos_Set(stringRow, stringColumn); +	} -	if (state->_curLogic->texts != NULL && state->_curLogic->numTexts >= tex) { -		int len = strlen(state->_curLogic->texts[tex]); +	if (state->_curLogic->texts && state->_curLogic->numTexts >= leadInTextNr) { +		leadInTextPtr = state->_curLogic->texts[leadInTextNr]; -		state->_vm->printText(state->_curLogic->texts[tex], 0, col, row, len, state->colorFg, state->colorBg); -		state->_vm->getString(col + len - 1, row, p4, p0); +		leadInTextPtr = textMgr->stringPrintf(leadInTextPtr); +		leadInTextPtr = textMgr->stringWordWrap(leadInTextPtr, 40); // ?? not absolutely sure -		// SGEO: display input char -		state->_vm->_gfx->printCharacter((col + len), row, state->cursorChar, state->colorFg, state->colorBg); +		textMgr->displayText(leadInTextPtr);  	} -	do { -		state->_vm->mainCycle(); -	} while (state->inputMode == INPUT_GETSTRING && !(state->_vm->shouldQuit() || state->_vm->_restartGame)); +	state->_vm->cycleInnerLoopActive(CYCLE_INNERLOOP_GETSTRING); + +	textMgr->stringSet(""); +	textMgr->stringEdit(stringMaxLen); + +	// copy string to destination +	// TODO: not sure if set all the time or only when ENTER is pressed +	strcpy(&state->_vm->_game.strings[stringDestNr][0], (char *)textMgr->_inputString); + +	textMgr->charPos_Pop(); + +	if (!previousEditState) { +		textMgr->inputEditOff(); +	}  } -void cmdGetNum(AgiGame *state, uint8 *p) { -	debugC(4, kDebugLevelScripts, "%d %d", p0, p1); +void cmdGetNum(AgiGame *state, uint8 *parameter) { +	TextMgr *textMgr = state->_vm->_text; +	int16 leadInTextNr = parameter[0] - 1; +	int16 numberDestVarNr = parameter[1]; +	const char *leadInTextPtr = nullptr; + +	debugC(4, kDebugLevelScripts, "%d %d", leadInTextNr, numberDestVarNr); -	state->_vm->newInputMode(INPUT_GETSTRING); +	textMgr->inputEditOn(); +	textMgr->charPos_Set(textMgr->promptRow_Get(), 0); -	if (state->_curLogic->texts != NULL && state->_curLogic->numTexts >= (p0 - 1)) { -		int len = strlen(state->_curLogic->texts[p0 - 1]); +	if (state->_curLogic->texts && state->_curLogic->numTexts >= leadInTextNr) { +		leadInTextPtr = state->_curLogic->texts[leadInTextNr]; -		state->_vm->printText(state->_curLogic->texts[p0 - 1], 0, 0, 22, len, state->colorFg, state->colorBg); -		state->_vm->getString(len - 1, 22, 3, MAX_STRINGS); +		leadInTextPtr = textMgr->stringPrintf(leadInTextPtr); +		leadInTextPtr = textMgr->stringWordWrap(leadInTextPtr, 40); // ?? not absolutely sure -		// CM: display input char -		state->_vm->_gfx->printCharacter((p3 + len), 22, state->cursorChar, state->colorFg, state->colorBg); +		textMgr->displayText(leadInTextPtr);  	} -	do { -		state->_vm->mainCycle(); -	} while (state->inputMode == INPUT_GETSTRING && !(state->_vm->shouldQuit() || state->_vm->_restartGame)); +	textMgr->inputEditOff(); + +	state->_vm->cycleInnerLoopActive(CYCLE_INNERLOOP_GETNUMBER); -	_v[p1] = atoi(state->strings[MAX_STRINGS]); +	textMgr->stringSet(""); +	textMgr->stringEdit(3); -	debugC(4, kDebugLevelScripts, "[%s] -> %d", state->strings[MAX_STRINGS], _v[p1]); +	textMgr->promptRedraw(); -	state->_vm->clearLines(22, 22, state->colorBg); -	state->_vm->flushLines(22, 22); +	state->vars[numberDestVarNr] = atoi((char *)textMgr->_inputString); + +	debugC(4, kDebugLevelScripts, "[%s] -> %d", state->strings[MAX_STRINGS], state->vars[numberDestVarNr]);  } -void cmdSetCursorChar(AgiGame *state, uint8 *p) { -	if (state->_curLogic->texts != NULL && (p0 - 1) <= state->_curLogic->numTexts) { -		state->cursorChar = *state->_curLogic->texts[p0 - 1]; +void cmdSetCursorChar(AgiGame *state, uint8 *parameter) { +	TextMgr *textMgr = state->_vm->_text; +	uint16 textNr = parameter[0] - 1; + +	if (state->_curLogic->texts != NULL && textNr <= state->_curLogic->numTexts) { +		textMgr->inputSetCursorChar(*state->_curLogic->texts[textNr]);  	} else {  		// default -		state->cursorChar = '_'; +		textMgr->inputSetCursorChar('_');  	}  } -void cmdSetKey(AgiGame *state, uint8 *p) { -	int key = 256 * p1 + p0; -	int slot = -1; +void cmdSetKey(AgiGame *state, uint8 *parameter) { +	uint16 key = parameter[0] + (parameter[1] << 8); +	uint16 controllerSlot = parameter[2]; +	int16 keyMappingSlot = -1; -	for (int i = 0; i < MAX_CONTROLLERS; i++) { -		if (slot == -1 && !state->controllers[i].keycode) -			slot = i; +	for (int i = 0; i < MAX_CONTROLLER_KEYMAPPINGS; i++) { +		if (keyMappingSlot == -1 && !state->controllerKeyMapping[i].keycode) +			keyMappingSlot = i; -		if (state->controllers[i].keycode == key && state->controllers[i].controller == p2) +		if (state->controllerKeyMapping[i].keycode == key && state->controllerKeyMapping[i].controllerSlot == controllerSlot)  			return;  	} -	if (slot == -1) { -		warning("Number of set.keys exceeded %d", MAX_CONTROLLERS); +	if (keyMappingSlot == -1) { +		warning("Number of set.keys exceeded %d", MAX_CONTROLLER_KEYMAPPINGS);  		return;  	} -	debugC(4, kDebugLevelScripts, "cmdSetKey: %d %d %d", p0, p1, p2); -	state->controllers[slot].keycode = key; -	state->controllers[slot].controller = p2; +	debugC(4, kDebugLevelScripts, "cmdSetKey: %d %d %d", parameter[0], parameter[1], controllerSlot); +	state->controllerKeyMapping[keyMappingSlot].keycode = key; +	state->controllerKeyMapping[keyMappingSlot].controllerSlot = controllerSlot; -	state->controllerOccured[p2] = false; +	state->controllerOccured[controllerSlot] = false;  } -void cmdSetString(AgiGame *state, uint8 *p) { +void cmdSetString(AgiGame *state, uint8 *parameter) { +	uint16 stringNr = parameter[0]; +	uint16 textNr = parameter[1] - 1;  	// CM: to avoid crash in Groza (str = 150) -	if (p0 > MAX_STRINGS) +	if (stringNr > MAX_STRINGS)  		return; -	strcpy(state->strings[p0], state->_curLogic->texts[p1 - 1]); +	strcpy(state->strings[stringNr], state->_curLogic->texts[textNr]);  } -void cmdDisplay(AgiGame *state, uint8 *p) { +void cmdDisplay(AgiGame *state, uint8 *parameter) {  	// V1 has 4 args -	int t = (getVersion() >= 0x2000 ? p2 : p3); -	int len = 40; - -	char *s = state->_vm->wordWrapString(state->_curLogic->texts[t - 1], &len); - -	state->_vm->printText(s, p1, 0, p0, 40, state->colorFg, state->colorBg); +	int16 textNr = (getVersion() >= 0x2000 ? parameter[2] : parameter[3]); +	int16 textRow = parameter[0]; +	int16 textColumn = parameter[1]; -	free(s); +	state->_vm->_text->display(textNr, textRow, textColumn);  } -void cmdDisplayF(AgiGame *state, uint8 *p) { -	state->_vm->printText(state->_curLogic->texts[_v[p2] - 1], _v[p1], 0, _v[p0], 40, state->colorFg, state->colorBg); -} - -void cmdClearTextRect(AgiGame *state, uint8 *p) { -	int c, x1, y1, x2, y2; +void cmdDisplayF(AgiGame *state, uint8 *parameter) { +	int16 textRow = state->vars[parameter[0]]; +	int16 textColumn = state->vars[parameter[1]]; +	int16 textNr = state->vars[parameter[2]]; -	if ((c = p4) != 0) -		c = 15; - -	x1 = p1 * CHAR_COLS; -	y1 = p0 * CHAR_LINES; -	x2 = (p3 + 1) * CHAR_COLS - 1; -	y2 = (p2 + 1) * CHAR_LINES - 1; +	state->_vm->_text->display(textNr, textRow, textColumn); +} -	// Added to prevent crash with x2 = 40 in the iigs demo -	if (x1 > GFX_WIDTH) -		x1 = GFX_WIDTH - 1; -	if (x2 > GFX_WIDTH) -		x2 = GFX_WIDTH - 1; -	if (y1 > GFX_HEIGHT) -		y1 = GFX_HEIGHT - 1; -	if (y2 > GFX_HEIGHT) -		y2 = GFX_HEIGHT - 1; +void cmdClearTextRect(AgiGame *state, uint8 *parameter) { +	int16 textUpperRow = parameter[0]; +	int16 textUpperColumn = parameter[1]; +	int16 textLowerRow = parameter[2]; +	int16 textLowerColumn = parameter[3]; +	int16 color = state->_vm->_text->calculateTextBackground(parameter[4]); -	state->_vm->_gfx->drawRectangle(x1, y1, x2, y2, c); -	state->_vm->_gfx->flushBlock(x1, y1, x2, y2); +	state->_vm->_text->clearBlock(textUpperRow, textUpperColumn, textLowerRow, textLowerColumn, color);  } -void cmdToggleMonitor(AgiGame *state, uint8 *p) { +void cmdToggleMonitor(AgiGame *state, uint8 *parameter) {  	debug(0, "toggle.monitor");  } -void cmdEchoLine(AgiGame *state, uint8 *p) { -	strcpy((char *)state->inputBuffer, (const char *)state->echoBuffer); -	state->cursorPos = strlen((char *)state->inputBuffer); -	state->hasPrompt = 0; -} - -void cmdClearLines(AgiGame *state, uint8 *p) { -	uint8 l; +void cmdClearLines(AgiGame *state, uint8 *parameter) { +	int16 textRowUpper = parameter[0]; +	int16 textRowLower = parameter[1]; +	int16 color = state->_vm->_text->calculateTextBackground(parameter[2]);  	// Residence 44 calls clear.lines(24,0,0), see Sarien bug #558423 -	l = p1 ? p1 : p0; -  	// Agent06 incorrectly calls clear.lines(1,150,0), see ScummVM bugs  	// #1935838 and #1935842 -	l = (l <= 24) ? l : 24; - -	state->_vm->clearLines(p0, l, p2); -	state->_vm->flushLines(p0, l); +	if (textRowUpper > textRowLower) { +		warning("cmdClearLines: RowUpper higher than RowLower"); +		textRowLower = textRowUpper; +	} +	state->_vm->_text->clearLines(textRowUpper, textRowLower, color);  } -void cmdPrint(AgiGame *state, uint8 *p) { -	int n = p0 < 1 ? 1 : p0; +void cmdPrint(AgiGame *state, uint8 *parameter) { +	int16 textNr = parameter[0]; -	state->_vm->print(state->_curLogic->texts[n - 1], 0, 0, 0); +	state->_vm->_text->print(textNr);  } -void cmdPrintF(AgiGame *state, uint8 *p) { -	int n = _v[p0] < 1 ? 1 : _v[p0]; +void cmdPrintF(AgiGame *state, uint8 *parameter) { +	int16 textNr = state->vars[parameter[0]]; -	state->_vm->print(state->_curLogic->texts[n - 1], 0, 0, 0); +	state->_vm->_text->print(textNr);  } -void cmdPrintAt(AgiGame *state, uint8 *p) { -	int n = p0 < 1 ? 1 : p0; +void cmdPrintAt(AgiGame *state, uint8 *parameter) { +	int16 textNr = parameter[0]; +	int16 textRow = parameter[1]; +	int16 textColumn = parameter[2]; +	int16 textWidth = parameter[3]; -	debugC(4, kDebugLevelScripts, "%d %d %d %d", p0, p1, p2, p3); +	debugC(4, kDebugLevelScripts, "%d %d %d %d", textNr, textRow, textColumn, textWidth); -	state->_vm->print(state->_curLogic->texts[n - 1], p1, p2, p3); +	state->_vm->_text->printAt(textNr, textRow, textColumn, textWidth);  } -void cmdPrintAtV(AgiGame *state, uint8 *p) { -	int n = _v[p0] < 1 ? 1 : _v[p0]; +void cmdPrintAtV(AgiGame *state, uint8 *parameter) { +	int16 textNr = state->vars[parameter[0]]; +	int16 textRow = parameter[1]; +	int16 textColumn = parameter[2]; +	int16 textWidth = parameter[3]; + +	debugC(4, kDebugLevelScripts, "%d %d %d %d", textNr, textRow, textColumn, textWidth); -	state->_vm->print(state->_curLogic->texts[n - 1], p1, p2, p3); +	state->_vm->_text->printAt(textNr, textRow, textColumn, textWidth);  } -void cmdPushScript(AgiGame *state, uint8 *p) { +void cmdPushScript(AgiGame *state, uint8 *parameter) {  	// We run AGIMOUSE always as a side effect  	//if (getFeatures() & GF_AGIMOUSE || true) {  		state->vars[27] = state->_vm->_mouse.button; @@ -1633,86 +2133,91 @@ void cmdPushScript(AgiGame *state, uint8 *p) {  	}*/  } -void cmdSetPriBase(AgiGame *state, uint8 *p) { -	int i, x, pri; - -	debug(0, "Priority base set to %d", p0); +void cmdSetPriBase(AgiGame *state, uint8 *parameter) { +	uint16 priorityBase = parameter[0]; -	// state->alt_pri = true; -	x = (_HEIGHT - p0) * _HEIGHT / 10; +	debug(0, "Priority base set to %d", priorityBase); -	for (i = 0; i < _HEIGHT; i++) { -		pri = (i - p0) < 0 ? 4 : (i - p0) * _HEIGHT / x + 5; -		if (pri > 15) -			pri = 15; -		state->priTable[i] = pri; -	} +	state->_vm->_gfx->setPriorityTable(priorityBase);  } -void cmdMousePosn(AgiGame *state, uint8 *p) { -	_v[p0] = WIN_TO_PIC_X(state->_vm->_mouse.x); -	_v[p1] = WIN_TO_PIC_Y(state->_vm->_mouse.y); +void cmdMousePosn(AgiGame *state, uint8 *parameter) { +	uint16 destVarNr1 = parameter[0]; +	uint16 destVarNr2 = parameter[1]; +	int16 mouseX = state->_vm->_mouse.x; +	int16 mouseY = state->_vm->_mouse.y; + +	state->_vm->adjustPosToGameScreen(mouseX, mouseY); + +	state->vars[destVarNr1] = mouseX; +	state->vars[destVarNr2] = mouseY;  } -void cmdShakeScreen(AgiGame *state, uint8 *p) { -	int i; +void cmdShakeScreen(AgiGame *state, uint8 *parameter) { +	uint16 shakeCount = parameter[0];  	// AGIPAL uses shake.screen values between 100 and 109 to set the palette  	// (Checked the original AGIPAL-hack's shake.screen-routine's disassembly). -	if (p0 >= 100 && p0 < 110) { +	if (shakeCount >= 100 && shakeCount < 110) {  		if (getFeatures() & GF_AGIPAL) { -			state->_vm->_gfx->setAGIPal(p0); +			state->_vm->_gfx->setAGIPal(shakeCount);  			return;  		} else {  			warning("It looks like GF_AGIPAL flag is missing");  		}  	} -	// Disables input while shaking to prevent bug -	// #1678230: AGI: Entering text while screen is shaking -	bool originalValue = state->inputEnabled; -	state->inputEnabled = false; - -	state->_vm->_gfx->shakeStart(); - -	state->_vm->_sprites->commitBoth();		// Fixes SQ1 demo -	for (i = 4 * p0; i; i--) { -		state->_vm->_gfx->shakeScreen(i & 1); -		state->_vm->_gfx->flushBlock(0, 0, GFX_WIDTH - 1, GFX_HEIGHT - 1); -		state->_vm->mainCycle(); -	} -	state->_vm->_gfx->shakeEnd(); - -	// Sets input back to what it was -	state->inputEnabled = originalValue; +	state->_vm->_gfx->shakeScreen(shakeCount);  } -void cmdSetSpeed(AgiGame *state, uint8 *p) { +void cmdSetSpeed(AgiGame *state, uint8 *parameter) {  	// V1 command  	(void)state; -	(void)p; +	(void)parameter;  	// speed = _v[p0];  } -void cmdSetItemView(AgiGame *state, uint8 *p) { +void cmdSetItemView(AgiGame *state, uint8 *parameter) {  	// V1 command  	(void)state; -	(void)p; +	(void)parameter;  } -void cmdCallV1(AgiGame *state, uint8 *p) { -	state->_vm->agiLoadResource(rLOGIC, p0); +void cmdCallV1(AgiGame *state, uint8 *parameter) { +	uint16 resourceNr = parameter[0]; + +	state->_vm->agiLoadResource(RESOURCETYPE_LOGIC, resourceNr);  	// FIXME: The following instruction looks incomplete.  	// Maybe something is meant to be assigned to, or read from,  	// the logic_list entry?  //	state->logic_list[++state->max_logics];  	// For now, just do the increment, to silence a clang warning  	++state->max_logics; -	_v[13] = 1; +	state->vars[13] = 1; +} + +void cmdNewRoomV1(AgiGame *state, uint8 *parameter) { +	uint16 resourceNr = parameter[0]; + +	warning("cmdNewRoomV1()"); +	state->_vm->agiLoadResource(RESOURCETYPE_LOGIC, resourceNr); +	state->max_logics = 1; +	state->logic_list[1] = resourceNr; +	state->vars[13] = 1; +} + +void cmdNewRoomVV1(AgiGame *state, uint8 *parameter) { +	uint16 resourceNr = state->vars[parameter[0]]; + +	warning("cmdNewRoomVV1()"); +	state->_vm->agiLoadResource(RESOURCETYPE_LOGIC, resourceNr); +	state->max_logics = 1; +	state->logic_list[1] = resourceNr; +	state->vars[13] = 1;  } -void cmdUnknown(AgiGame *state, uint8 *p) { -	warning("Skipping unknown opcode %2X", *(code + ip - 1)); +void cmdUnknown(AgiGame *state, uint8 *parameter) { +	warning("Skipping unknown opcode %2X", *(state->_curLogic->data + state->_curLogic->cIP - 1));  }  /** @@ -1740,7 +2245,7 @@ int AgiEngine::runLogic(int n) {  	// If logic not loaded, load it  	if (~_game.dirLogic[n].flags & RES_LOADED) {  		debugC(4, kDebugLevelScripts, "logic %d not loaded!", n); -		agiLoadResource(rLOGIC, n); +		agiLoadResource(RESOURCETYPE_LOGIC, n);  	}  	_game.lognum = n; @@ -1749,7 +2254,9 @@ int AgiEngine::runLogic(int n) {  	_game._curLogic->cIP = _game._curLogic->sIP;  	_timerHack = 0; -	while (ip < _game.logics[n].size && !(shouldQuit() || _restartGame)) { +	while (state->_curLogic->cIP < _game.logics[n].size && !(shouldQuit() || _restartGame)) { +		// TODO: old code, needs to be adjusted +#if 0  		if (_debug.enabled) {  			if (_debug.steps > 0) {  				if (_debug.logic0 || n) { @@ -1762,24 +2269,25 @@ int AgiEngine::runLogic(int n) {  				do {  					mainCycle();  				} while (!_debug.steps && _debug.enabled); -				_sprites->eraseBoth(); +				_sprites->eraseAllSprites();  			}  		} +#endif -		_game.execStack.back().curIP = ip; +		_game.execStack.back().curIP = state->_curLogic->cIP;  		char st[101];  		int sz = MIN(_game.execStack.size(), 100u);  		memset(st, '.', sz);  		st[sz] = 0; -		switch (op = *(code + ip++)) { +		switch (op = *(state->_curLogic->data + state->_curLogic->cIP++)) {  		case 0xff:	// if (open/close)  			testIfCode(n);  			break;  		case 0xfe:	// goto  			// +2 covers goto size -			ip += 2 + ((int16)READ_LE_UINT16(code + ip)); +			state->_curLogic->cIP += 2 + ((int16)READ_LE_UINT16(state->_curLogic->data + state->_curLogic->cIP));  			// timer must keep running even in goto loops,  			// but AGI engine can't do that :( @@ -1809,20 +2317,20 @@ int AgiEngine::runLogic(int n) {  			return 1;  		default:  			num = logicNamesCmd[op].argumentsLength(); -			memmove(p, code + ip, num); +			memmove(p, state->_curLogic->data + state->_curLogic->cIP, num);  			memset(p + num, 0, CMD_BSIZE - num);  			debugC(2, kDebugLevelScripts, "%s%s(%d %d %d)", st, logicNamesCmd[op].name, p[0], p[1], p[2]);  			_agiCommands[op](&_game, p); -			ip += num; +			state->_curLogic->cIP += num;  		}  //		if ((op == 0x0B || op == 0x3F || op == 0x40) && logic_index < state->max_logics) {  //			n = state->logic_list[++logic_index];  //			state->_curLogic = &state->logics[n];  //			state->lognum = n; -//			ip = 2; +//			state->_curLogic_cIP = 2;  //			warning("running logic %d\n", n);  //		} diff --git a/engines/agi/op_dbg.cpp b/engines/agi/op_dbg.cpp index 997da9db7d..92af2c63f1 100644 --- a/engines/agi/op_dbg.cpp +++ b/engines/agi/op_dbg.cpp @@ -93,7 +93,7 @@ void AgiEngine::debugConsole(int lognum, int mode, const char *str) {  			if (*c == 'n') {  				debugN(0, "%d", *(code + (ip + z)));  			} else { -				debugN(0, "v%d[%d]", *(code + (ip + z)), getvar(*(code + (ip + z)))); +				debugN(0, "v%d[%d]", *(code + (ip + z)), getVar(*(code + (ip + z))));  			}  			c++;  			z++; diff --git a/engines/agi/op_test.cpp b/engines/agi/op_test.cpp index 9839f0ec90..afb1ddb820 100644 --- a/engines/agi/op_test.cpp +++ b/engines/agi/op_test.cpp @@ -23,6 +23,8 @@  #include "agi/agi.h"  #include "agi/opcodes.h" +#include "agi/words.h" +  #include "common/endian.h"  namespace Agi { @@ -30,16 +32,14 @@ namespace Agi {  #define ip (state->_curLogic->cIP)  #define code (state->_curLogic->data) -#define getvar(a) state->_vm->getvar(a) -#define getflag(a) state->_vm->getflag(a) +#define getVar(a) state->_vm->getVar(a) -#define testEqual(v1, v2)		(getvar(v1) == (v2)) -#define testLess(v1, v2)		(getvar(v1) < (v2)) -#define testGreater(v1, v2)	(getvar(v1) > (v2)) -#define testIsSet(flag)		(getflag(flag)) +#define testEqual(v1, v2)		(getVar(v1) == (v2)) +#define testLess(v1, v2)		(getVar(v1) < (v2)) +#define testGreater(v1, v2)	(getVar(v1) > (v2))  #define testHas(obj)			(state->_vm->objectGetLocation(obj) == EGO_OWNED)  #define testHasV1(obj)			(state->_vm->objectGetLocation(obj) == EGO_OWNED_V1) -#define testObjInRoom(obj, v)	(state->_vm->objectGetLocation(obj) == getvar(v)) +#define testObjInRoom(obj, v)	(state->_vm->objectGetLocation(obj) == getVar(v))  void condEqual(AgiGame *state, uint8 *p) {  	if (p[0] == 11) @@ -50,7 +50,7 @@ void condEqual(AgiGame *state, uint8 *p) {  void condEqualV(AgiGame *state, uint8 *p) {  	if (p[0] == 11 || p[1] == 11)  		state->_vm->_timerHack++; -	state->testResult = testEqual(p[0], getvar(p[1])); +	state->testResult = testEqual(p[0], getVar(p[1]));  }  void condLess(AgiGame *state, uint8 *p) { @@ -62,7 +62,7 @@ void condLess(AgiGame *state, uint8 *p) {  void condLessV(AgiGame *state, uint8 *p) {  	if (p[0] == 11 || p[1] == 11)  		state->_vm->_timerHack++; -	state->testResult = testLess(p[0], getvar(p[1])); +	state->testResult = testLess(p[0], getVar(p[1]));  }  void condGreater(AgiGame *state, uint8 *p) { @@ -74,19 +74,19 @@ void condGreater(AgiGame *state, uint8 *p) {  void condGreaterV(AgiGame *state, uint8 *p) {  	if (p[0] == 11 || p[1] == 11)  		state->_vm->_timerHack++; -	state->testResult = testGreater(p[0], getvar(p[1])); +	state->testResult = testGreater(p[0], getVar(p[1]));  }  void condIsSet(AgiGame *state, uint8 *p) { -	state->testResult = testIsSet(p[0]); +	state->testResult = state->_vm->getflag(p[0]);  }  void condIsSetV(AgiGame *state, uint8 *p) { -	state->testResult = testIsSet(getvar(p[0])); +	state->testResult = state->_vm->getflag(getVar(p[0]));  }  void condIsSetV1(AgiGame *state, uint8 *p) { -	state->testResult = getvar(p[0]) > 0; +	state->testResult = getVar(p[0]) > 0;  }  void condHas(AgiGame *state, uint8 *p) { @@ -121,47 +121,47 @@ void condSaid(AgiGame *state, uint8 *p) {  void condSaid1(AgiGame *state, uint8 *p) {  	state->testResult = false; -	if (!getflag(fEnteredCli)) +	if (!state->_vm->getflag(VM_FLAG_ENTERED_CLI))  		return;  	int id0 = READ_LE_UINT16(p); -	if ((id0 == 1 || id0 == state->egoWords[0].id)) +	if ((id0 == 1 || id0 == state->_vm->_words->getEgoWordId(0)))  		state->testResult = true;  }  void condSaid2(AgiGame *state, uint8 *p) {  	state->testResult = false; -	if (!getflag(fEnteredCli)) +	if (!state->_vm->getflag(VM_FLAG_ENTERED_CLI))  		return;  	int id0 = READ_LE_UINT16(p);  	int id1 = READ_LE_UINT16(p + 2); -	if ((id0 == 1 || id0 == state->egoWords[0].id) && -		(id1 == 1 || id1 == state->egoWords[1].id)) +	if ((id0 == 1 || id0 == state->_vm->_words->getEgoWordId(0)) && +		(id1 == 1 || id1 == state->_vm->_words->getEgoWordId(1)))  		state->testResult = true;  }  void condSaid3(AgiGame *state, uint8 *p) {  	state->testResult = false; -	if (!getflag(fEnteredCli)) +	if (!state->_vm->getflag(VM_FLAG_ENTERED_CLI))  		return;  	int id0 = READ_LE_UINT16(p);  	int id1 = READ_LE_UINT16(p + 2);  	int id2 = READ_LE_UINT16(p + 4); -	if ((id0 == 1 || id0 == state->egoWords[0].id) && -		(id1 == 1 || id1 == state->egoWords[1].id) && -		(id2 == 1 || id2 == state->egoWords[2].id)) +	if ((id0 == 1 || id0 == state->_vm->_words->getEgoWordId(0)) && +		(id1 == 1 || id1 == state->_vm->_words->getEgoWordId(1)) && +		(id2 == 1 || id2 == state->_vm->_words->getEgoWordId(2)))  		state->testResult = true;  }  void condBit(AgiGame *state, uint8 *p) { -	state->testResult = (getvar(p[1]) >> p[0]) & 1; +	state->testResult = (getVar(p[1]) >> p[0]) & 1;  }  void condCompareStrings(AgiGame *state, uint8 *p) { @@ -188,7 +188,7 @@ void condUnknown13(AgiGame *state, uint8 *p) {  	// This command is used at least in the Amiga version of Gold Rush! v2.05 1989-03-09  	// (AGI 2.316) in logics 1, 3, 5, 6, 137 and 192 (Logic.192 revealed this command's nature).  	// TODO: Check this command's implementation using disassembly just to be sure. -	int ec = state->viewTable[0].flags & fAdjEgoXY; +	int ec = state->screenObjTable[SCREENOBJECTS_EGO_ENTRY].flags & fAdjEgoXY;  	debugC(7, kDebugLevelScripts, "op_test: in.motion.using.mouse = %s (Amiga-specific testcase 19)", ec ? "true" : "false");  	state->testResult = ec;  } @@ -221,7 +221,7 @@ uint8 AgiEngine::testCompareStrings(uint8 s1, uint8 s2) {  			break;  		default: -			ms1[j++] = toupper(ms1[k]); +			ms1[j++] = tolower(ms1[k]);  			break;  		}  	} @@ -242,7 +242,7 @@ uint8 AgiEngine::testCompareStrings(uint8 s1, uint8 s2) {  			break;  		default: -			ms2[j++] = toupper(ms2[k]); +			ms2[j++] = tolower(ms2[k]);  			break;  		}  	} @@ -258,7 +258,7 @@ uint8 AgiEngine::testKeypressed() {  	if (!x) {  		InputMode mode = _game.inputMode; -		_game.inputMode = INPUT_NONE; +		_game.inputMode = INPUTMODE_NONE;  		// Only check for events here, without updating the game cycle,  		// otherwise the animations in some games are drawn too quickly  		// like, for example, Manannan's lightnings in the intro of KQ3 @@ -275,11 +275,11 @@ uint8 AgiEngine::testKeypressed() {  }  uint8 AgiEngine::testController(uint8 cont) { -	return (_game.controllerOccured[cont] ? 1 : 0); +	return (_game.controllerOccured[cont] ? true : false);  }  uint8 AgiEngine::testPosn(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2) { -	VtEntry *v = &_game.viewTable[n]; +	ScreenObjEntry *v = &_game.screenObjTable[n];  	uint8 r;  	r = v->xPos >= x1 && v->yPos >= y1 && v->xPos <= x2 && v->yPos <= y2; @@ -290,7 +290,7 @@ uint8 AgiEngine::testPosn(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2) {  }  uint8 AgiEngine::testObjInBox(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2) { -	VtEntry *v = &_game.viewTable[n]; +	ScreenObjEntry *v = &_game.screenObjTable[n];  	return v->xPos >= x1 &&  	    v->yPos >= y1 && v->xPos + v->xSize - 1 <= x2 && v->yPos <= y2; @@ -298,7 +298,7 @@ uint8 AgiEngine::testObjInBox(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2) {  // if n is in center of box  uint8 AgiEngine::testObjCenter(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2) { -	VtEntry *v = &_game.viewTable[n]; +	ScreenObjEntry *v = &_game.screenObjTable[n];  	return v->xPos + v->xSize / 2 >= x1 &&  			v->xPos + v->xSize / 2 <= x2 && v->yPos >= y1 && v->yPos <= y2; @@ -306,7 +306,7 @@ uint8 AgiEngine::testObjCenter(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2)  // if nect N is in right corner  uint8 AgiEngine::testObjRight(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2) { -	VtEntry *v = &_game.viewTable[n]; +	ScreenObjEntry *v = &_game.screenObjTable[n];  	return v->xPos + v->xSize - 1 >= x1 &&  			v->xPos + v->xSize - 1 <= x2 && v->yPos >= y1 && v->yPos <= y2; @@ -315,10 +315,12 @@ uint8 AgiEngine::testObjRight(uint8 n, uint8 x1, uint8 y1, uint8 x2, uint8 y2) {  // When player has entered something, it is parsed elsewhere  uint8 AgiEngine::testSaid(uint8 nwords, uint8 *cc) {  	AgiGame *state = &_game; -	int c, n = _game.numEgoWords; +	AgiEngine *vm = state->_vm; +	Words *words = vm->_words; +	int c, n = words->getEgoWordCount();  	int z = 0; -	if (getflag(fSaidAcceptedInput) || !getflag(fEnteredCli)) +	if (vm->getflag(VM_FLAG_SAID_ACCEPTED_INPUT) || !vm->getflag(VM_FLAG_ENTERED_CLI))  		return false;  	// FR: @@ -349,7 +351,7 @@ uint8 AgiEngine::testSaid(uint8 nwords, uint8 *cc) {  		case 1:	// any word  			break;  		default: -			if (_game.egoWords[c].id != z) +			if (words->getEgoWordId(c) != z)  				return false;  			break;  		} @@ -364,7 +366,7 @@ uint8 AgiEngine::testSaid(uint8 nwords, uint8 *cc) {  	if (nwords != 0 && READ_LE_UINT16(cc) != 9999)  		return false; -	setflag(fSaidAcceptedInput, true); +	setflag(VM_FLAG_SAID_ACCEPTED_INPUT, true);  	return true;  } diff --git a/engines/agi/opcodes.cpp b/engines/agi/opcodes.cpp index 0d7d180ec9..a20e51a5fc 100644 --- a/engines/agi/opcodes.cpp +++ b/engines/agi/opcodes.cpp @@ -173,189 +173,189 @@ AgiInstruction insV2Test[] = {  };  AgiInstruction insV2[] = { -	{ "return",				"",			NULL }, -	{ "increment",			"v",		&cmdIncrement }, -	{ "decrement",			"v",		&cmdDecrement }, -	{ "assignn",			"vn",		&cmdAssignN }, -	{ "assignv",			"vv",		&cmdAssignV }, -	{ "addn",				"vn",		&cmdAddN }, -	{ "addv",				"vv",		&cmdAddV }, -	{ "subn",				"vn",		&cmdSubN }, -	{ "subv",				"vv",		&cmdSubV }, -	{ "lindirectv",			"vv",		&cmdLindirectV }, -	{ "lindirect",			"vv",		&cmdRindirect }, -	{ "lindirectn",			"vn",		&cmdLindirectN }, -	{ "set",				"n",		&cmdSet }, -	{ "reset",				"n",		&cmdReset }, -	{ "toggle",				"n",		&cmdToggle }, -	{ "set.v",				"v",		&cmdSetV }, -	{ "reset.v",			"v",		&cmdResetV }, -	{ "toggle.v",			"v",		&cmdToggleV }, -	{ "new.room",			"n",		&cmdNewRoom }, -	{ "new.room.v",			"v",		&cmdNewRoomF }, -	{ "load.logics",		"n",		&cmdLoadLogic }, -	{ "load.logics.v",		"v",		&cmdLoadLogicF }, -	{ "call",				"n",		&cmdCall }, -	{ "call.v",				"v",		&cmdCallF }, -	{ "load.pic",			"v",		&cmdLoadPic }, -	{ "draw.pic",			"v",		&cmdDrawPic }, -	{ "show.pic",			"",			&cmdShowPic }, -	{ "discard.pic",		"v",		&cmdDiscardPic }, -	{ "overlay.pic",		"v",		&cmdOverlayPic }, -	{ "show.pri.screen",	"",			&cmdShowPriScreen }, -	{ "load.view",			"n",		&cmdLoadView }, -	{ "load.view.v",		"v",		&cmdLoadViewF }, -	{ "discard.view",		"n",		&cmdDiscardView }, -	{ "animate.obj",		"n",		&cmdAnimateObj }, -	{ "unanimate.all",		"",			&cmdUnanimateAll }, -	{ "draw",				"n",		&cmdDraw }, -	{ "erase",				"n",		&cmdErase }, -	{ "position",			"nnn",		&cmdPosition }, -	{ "position.v",			"nvv",		&cmdPositionF }, -	{ "get.posn",			"nvv",		&cmdGetPosn }, -	{ "reposition",			"nvv",		&cmdReposition }, -	{ "set.view",			"nn",		&cmdSetView }, -	{ "set.view.v",			"nv",		&cmdSetViewF }, -	{ "set.loop",			"nn",		&cmdSetLoop }, -	{ "set.loop.v",			"nv",		&cmdSetLoopF }, -	{ "fix.loop",			"n",		&cmdFixLoop }, -	{ "release.loop",		"n",		&cmdReleaseLoop }, -	{ "set.cel",			"nn",		&cmdSetCel }, -	{ "set.cel.v",			"nv",		&cmdSetCelF }, -	{ "last.cel",			"nv",		&cmdLastCel }, -	{ "current.cel",		"nv",		&cmdCurrentCel }, -	{ "current.loop",		"nv",		&cmdCurrentLoop }, -	{ "current.view",		"nv",		&cmdCurrentView }, -	{ "number.of.loops",	"nv",		&cmdNumberOfLoops }, -	{ "set.priority",		"nn",		&cmdSetPriority }, -	{ "set.priority.v",		"nv",		&cmdSetPriorityF }, -	{ "release.priority",	"n",		&cmdReleasePriority }, -	{ "get.priority",		"nn",		&cmdGetPriority }, -	{ "stop.update",		"n",		&cmdStopUpdate }, -	{ "start.update",		"n",		&cmdStartUpdate }, -	{ "force.update",		"n",		&cmdForceUpdate }, -	{ "ignore.horizon",		"n",		&cmdIgnoreHorizon }, -	{ "observe.horizon",	"n",		&cmdObserveHorizon }, -	{ "set.horizon",		"n",		&cmdSetHorizon }, -	{ "object.on.water",	"n",		&cmdObjectOnWater }, -	{ "object.on.land",		"n",		&cmdObjectOnLand }, -	{ "object.on.anything",	"n",		&cmdObjectOnAnything }, -	{ "ignore.objs",		"n",		&cmdIgnoreObjs }, -	{ "observe.objs",		"n",		&cmdObserveObjs }, -	{ "distance",			"nnv",		&cmdDistance }, -	{ "stop.cycling",		"n",		&cmdStopCycling }, -	{ "start.cycling",		"n",		&cmdStartCycling }, -	{ "normal.cycle",		"n",		&cmdNormalCycle }, -	{ "end.of.loop",		"nn",		&cmdEndOfLoop }, -	{ "reverse.cycle",		"n",		&cmdReverseCycle }, -	{ "reverse.loop",		"nn",		&cmdReverseLoop }, -	{ "cycle.time",			"nv",		&cmdCycleTime }, -	{ "stop.motion",		"n",		&cmdStopMotion }, -	{ "start.motion",		"n",		&cmdStartMotion }, -	{ "step.size",			"nv",		&cmdStepSize }, -	{ "step.time",			"nv",		&cmdStepTime }, -	{ "move.obj",			"nnnnn",	&cmdMoveObj }, -	{ "move.obj.v",			"nvvvv",	&cmdMoveObjF }, -	{ "follow.ego",			"nnn",		&cmdFollowEgo }, -	{ "wander",				"n",		&cmdWander }, -	{ "normal.motion",		"n",		&cmdNormalMotion }, -	{ "set.dir",			"nv",		&cmdSetDir }, -	{ "get.dir",			"nv",		&cmdGetDir }, -	{ "ignore.blocks",		"n",		&cmdIgnoreBlocks }, -	{ "observe.blocks",		"n",		&cmdObserveBlocks }, -	{ "block",				"nnnn",		&cmdBlock }, -	{ "unblock",			"",			&cmdUnblock }, -	{ "get",				"n",		&cmdGet }, -	{ "get.v",				"v",		&cmdGetF }, -	{ "drop",				"n",		&cmdDrop }, -	{ "put",				"nn",		&cmdPut }, -	{ "put.v",				"vv",		&cmdPutF }, -	{ "get.room.v",			"vv",		&cmdGetRoomF }, -	{ "load.sound",			"n",		&cmdLoadSound }, -	{ "sound",				"nn",		&cmdSound }, -	{ "stop.sound",			"",			&cmdStopSound }, -	{ "print",				"s",		&cmdPrint }, -	{ "print.v",			"v",		&cmdPrintF }, -	{ "display",			"nns",		&cmdDisplay }, -	{ "display.v",			"vvv",		&cmdDisplayF }, -	{ "clear.lines",		"nns",		&cmdClearLines }, -	{ "text.screen",		"",			&cmdTextScreen }, -	{ "graphics",			"",			&cmdGraphics }, -	{ "set.cursor.char",	"s",		&cmdSetCursorChar }, -	{ "set.text.attribute",	"nn",		&cmdSetTextAttribute }, -	{ "shake.screen",		"n",		&cmdShakeScreen }, -	{ "configure.screen",	"nnn",		&cmdConfigureScreen }, -	{ "status.line.on",		"",			&cmdStatusLineOn }, -	{ "status.line.off",	"",			&cmdStatusLineOff }, -	{ "set.string",			"ns",		&cmdSetString }, -	{ "get.string",			"nsnnn",		&cmdGetString }, -	{ "word.to.string",		"nn",		&cmdWordToString }, -	{ "parse",				"n",		&cmdParse }, -	{ "get.num",			"nv",		&cmdGetNum }, -	{ "prevent.input",		"",			&cmdPreventInput }, -	{ "accept.input",		"",			&cmdAcceptInput }, -	{ "set.key",			"nnn",		&cmdSetKey }, -	{ "add.to.pic",			"nnnnnnn",	&cmdAddToPic }, -	{ "add.to.pic.v",		"vvvvvvv",	&cmdAddToPicF }, -	{ "status",				"",			&cmdStatus }, -	{ "save.game",			"",			&cmdSaveGame }, -	{ "restore.game",		"",			&cmdLoadGame }, -	{ "init.disk",			"",			&cmdInitDisk }, -	{ "restart.game",		"",			&cmdRestartGame }, -	{ "show.obj",			"n",		&cmdShowObj }, -	{ "random",				"nnv",		&cmdRandom }, -	{ "program.control",	"",			&cmdProgramControl }, -	{ "player.control",		"",			&cmdPlayerControl }, -	{ "obj.status.v",		"v",		&cmdObjStatusF }, -	{ "quit",				"n",		&cmdQuit },  // 0 args for AGI version 2.089 -	{ "show.mem",			"",			&cmdShowMem }, -	{ "pause",				"",			&cmdPause }, -	{ "echo.line",			"",			&cmdEchoLine }, -	{ "cancel.line",		"",			&cmdCancelLine }, -	{ "init.joy",			"",			&cmdInitJoy }, -	{ "toggle.monitor",		"",			&cmdToggleMonitor }, -	{ "version",			"",			&cmdVersion }, -	{ "script.size",		"n",		&cmdScriptSize }, -	{ "set.game.id",		"s",		&cmdSetGameID }, -	{ "log",				"s",		&cmdLog }, -	{ "set.scan.start",		"",			&cmdSetScanStart }, -	{ "reset.scan.start",	"",			&cmdResetScanStart }, -	{ "reposition.to",		"nnn",		&cmdRepositionTo }, -	{ "reposition.to.v",	"nvv",		&cmdRepositionToF }, -	{ "trace.on",			"",			&cmdTraceOn }, -	{ "trace.info", 		"nnn",		&cmdTraceInfo }, +	{ "return",				"",			NULL },					// 00 +	{ "increment",			"v",		&cmdIncrement },		// 01 +	{ "decrement",			"v",		&cmdDecrement },		// 02 +	{ "assignn",			"vn",		&cmdAssignN },			// 03 +	{ "assignv",			"vv",		&cmdAssignV },			// 04 +	{ "addn",				"vn",		&cmdAddN },				// 05 +	{ "addv",				"vv",		&cmdAddV },				// 06 +	{ "subn",				"vn",		&cmdSubN },				// 07 +	{ "subv",				"vv",		&cmdSubV },				// 08 +	{ "lindirectv",			"vv",		&cmdLindirectV },		// 09 +	{ "lindirect",			"vv",		&cmdRindirect },		// 0A +	{ "lindirectn",			"vn",		&cmdLindirectN },		// 0B +	{ "set",				"n",		&cmdSet },				// 0C +	{ "reset",				"n",		&cmdReset },			// 0D +	{ "toggle",				"n",		&cmdToggle },			// 0E +	{ "set.v",				"v",		&cmdSetV },				// 0F +	{ "reset.v",			"v",		&cmdResetV },			// 10 +	{ "toggle.v",			"v",		&cmdToggleV },			// 11 +	{ "new.room",			"n",		&cmdNewRoom },			// 12 +	{ "new.room.v",			"v",		&cmdNewRoomF },			// 13 +	{ "load.logics",		"n",		&cmdLoadLogic },		// 14 +	{ "load.logics.v",		"v",		&cmdLoadLogicF },		// 15 +	{ "call",				"n",		&cmdCall },				// 16 +	{ "call.v",				"v",		&cmdCallF },			// 17 +	{ "load.pic",			"v",		&cmdLoadPic },			// 18 +	{ "draw.pic",			"v",		&cmdDrawPic },			// 19 +	{ "show.pic",			"",			&cmdShowPic },			// 1A +	{ "discard.pic",		"v",		&cmdDiscardPic },		// 1B +	{ "overlay.pic",		"v",		&cmdOverlayPic },		// 1C +	{ "show.pri.screen",	"",			&cmdShowPriScreen },	// 1D +	{ "load.view",			"n",		&cmdLoadView },			// 1E +	{ "load.view.v",		"v",		&cmdLoadViewF },		// 1F +	{ "discard.view",		"n",		&cmdDiscardView },		// 20 +	{ "animate.obj",		"n",		&cmdAnimateObj },		// 21 +	{ "unanimate.all",		"",			&cmdUnanimateAll },		// 22 +	{ "draw",				"n",		&cmdDraw },				// 23 +	{ "erase",				"n",		&cmdErase },			// 24 +	{ "position",			"nnn",		&cmdPosition },			// 25 +	{ "position.v",			"nvv",		&cmdPositionF },		// 26 +	{ "get.posn",			"nvv",		&cmdGetPosn },			// 27 +	{ "reposition",			"nvv",		&cmdReposition },		// 28 +	{ "set.view",			"nn",		&cmdSetView },			// 29 +	{ "set.view.v",			"nv",		&cmdSetViewF },			// 2A +	{ "set.loop",			"nn",		&cmdSetLoop },			// 2B +	{ "set.loop.v",			"nv",		&cmdSetLoopF },			// 2C +	{ "fix.loop",			"n",		&cmdFixLoop },			// 2D +	{ "release.loop",		"n",		&cmdReleaseLoop },		// 2E +	{ "set.cel",			"nn",		&cmdSetCel },			// 2F +	{ "set.cel.v",			"nv",		&cmdSetCelF },			// 30 +	{ "last.cel",			"nv",		&cmdLastCel },			// 31 +	{ "current.cel",		"nv",		&cmdCurrentCel },		// 32 +	{ "current.loop",		"nv",		&cmdCurrentLoop },		// 33 +	{ "current.view",		"nv",		&cmdCurrentView },		// 34 +	{ "number.of.loops",	"nv",		&cmdNumberOfLoops },	// 35 +	{ "set.priority",		"nn",		&cmdSetPriority },		// 36 +	{ "set.priority.v",		"nv",		&cmdSetPriorityF },		// 37 +	{ "release.priority",	"n",		&cmdReleasePriority },	// 38 +	{ "get.priority",		"nn",		&cmdGetPriority },		// 39 +	{ "stop.update",		"n",		&cmdStopUpdate },		// 3A +	{ "start.update",		"n",		&cmdStartUpdate },		// 3B +	{ "force.update",		"n",		&cmdForceUpdate },		// 3C +	{ "ignore.horizon",		"n",		&cmdIgnoreHorizon },	// 3D +	{ "observe.horizon",	"n",		&cmdObserveHorizon },	// 3E +	{ "set.horizon",		"n",		&cmdSetHorizon },		// 3F +	{ "object.on.water",	"n",		&cmdObjectOnWater },	// 40 +	{ "object.on.land",		"n",		&cmdObjectOnLand },		// 41 +	{ "object.on.anything",	"n",		&cmdObjectOnAnything },	// 42 +	{ "ignore.objs",		"n",		&cmdIgnoreObjs },		// 43 +	{ "observe.objs",		"n",		&cmdObserveObjs },		// 44 +	{ "distance",			"nnv",		&cmdDistance },			// 45 +	{ "stop.cycling",		"n",		&cmdStopCycling },		// 46 +	{ "start.cycling",		"n",		&cmdStartCycling },		// 47 +	{ "normal.cycle",		"n",		&cmdNormalCycle },		// 48 +	{ "end.of.loop",		"nn",		&cmdEndOfLoop },		// 49 +	{ "reverse.cycle",		"n",		&cmdReverseCycle },		// 5A +	{ "reverse.loop",		"nn",		&cmdReverseLoop },		// 5B +	{ "cycle.time",			"nv",		&cmdCycleTime },		// 5C +	{ "stop.motion",		"n",		&cmdStopMotion },		// 5D +	{ "start.motion",		"n",		&cmdStartMotion },		// 5E +	{ "step.size",			"nv",		&cmdStepSize },			// 5F +	{ "step.time",			"nv",		&cmdStepTime },			// 60 +	{ "move.obj",			"nnnnn",	&cmdMoveObj },			// 61 +	{ "move.obj.v",			"nvvvv",	&cmdMoveObjF },			// 62 +	{ "follow.ego",			"nnn",		&cmdFollowEgo },		// 63 +	{ "wander",				"n",		&cmdWander },			// 64 +	{ "normal.motion",		"n",		&cmdNormalMotion },		// 65 +	{ "set.dir",			"nv",		&cmdSetDir },			// 66 +	{ "get.dir",			"nv",		&cmdGetDir },			// 67 +	{ "ignore.blocks",		"n",		&cmdIgnoreBlocks },		// 68 +	{ "observe.blocks",		"n",		&cmdObserveBlocks },	// 69 +	{ "block",				"nnnn",		&cmdBlock },			// 6A +	{ "unblock",			"",			&cmdUnblock },			// 6B +	{ "get",				"n",		&cmdGet },				// 6C +	{ "get.v",				"v",		&cmdGetF },				// 6D +	{ "drop",				"n",		&cmdDrop },				// 6E +	{ "put",				"nn",		&cmdPut },				// 6F +	{ "put.v",				"vv",		&cmdPutF },				// 70 +	{ "get.room.v",			"vv",		&cmdGetRoomF },			// 71 +	{ "load.sound",			"n",		&cmdLoadSound },		// 72 +	{ "sound",				"nn",		&cmdSound },			// 73 +	{ "stop.sound",			"",			&cmdStopSound },		// 74 +	{ "print",				"s",		&cmdPrint },			// 75 +	{ "print.v",			"v",		&cmdPrintF },			// 76 +	{ "display",			"nns",		&cmdDisplay },			// 77 +	{ "display.v",			"vvv",		&cmdDisplayF },			// 78 +	{ "clear.lines",		"nns",		&cmdClearLines },		// 79 +	{ "text.screen",		"",			&cmdTextScreen },		// 7A +	{ "graphics",			"",			&cmdGraphics },			// 7B +	{ "set.cursor.char",	"s",		&cmdSetCursorChar },	// 7C +	{ "set.text.attribute",	"nn",		&cmdSetTextAttribute },	// 7D +	{ "shake.screen",		"n",		&cmdShakeScreen },		// 7E +	{ "configure.screen",	"nnn",		&cmdConfigureScreen },	// 7F +	{ "status.line.on",		"",			&cmdStatusLineOn },		// 80 +	{ "status.line.off",	"",			&cmdStatusLineOff },	// 81 +	{ "set.string",			"ns",		&cmdSetString },		// 82 +	{ "get.string",			"nsnnn",	&cmdGetString },		// 83 +	{ "word.to.string",		"nn",		&cmdWordToString },		// 84 +	{ "parse",				"n",		&cmdParse },			// 85 +	{ "get.num",			"nv",		&cmdGetNum },			// 86 +	{ "prevent.input",		"",			&cmdPreventInput },		// 87 +	{ "accept.input",		"",			&cmdAcceptInput },		// 88 +	{ "set.key",			"nnn",		&cmdSetKey },			// 89 +	{ "add.to.pic",			"nnnnnnn",	&cmdAddToPic },			// 8A +	{ "add.to.pic.v",		"vvvvvvv",	&cmdAddToPicF },		// 8B +	{ "status",				"",			&cmdStatus },			// 8C +	{ "save.game",			"",			&cmdSaveGame },			// 8D +	{ "restore.game",		"",			&cmdLoadGame },			// 8E +	{ "init.disk",			"",			&cmdInitDisk },			// 8F +	{ "restart.game",		"",			&cmdRestartGame },		// 90 +	{ "show.obj",			"n",		&cmdShowObj },			// 91 +	{ "random",				"nnv",		&cmdRandom },			// 92 +	{ "program.control",	"",			&cmdProgramControl },	// 93 +	{ "player.control",		"",			&cmdPlayerControl },	// 94 +	{ "obj.status.v",		"v",		&cmdObjStatusF },		// 95 +	{ "quit",				"n",		&cmdQuit },				// 96 0 args for AGI version 2.089 +	{ "show.mem",			"",			&cmdShowMem },			// 97 +	{ "pause",				"",			&cmdPause },			// 98 +	{ "echo.line",			"",			&cmdEchoLine },			// 99 +	{ "cancel.line",		"",			&cmdCancelLine },		// 9A +	{ "init.joy",			"",			&cmdInitJoy },			// 9B +	{ "toggle.monitor",		"",			&cmdToggleMonitor },	// 9C +	{ "version",			"",			&cmdVersion },			// 9D +	{ "script.size",		"n",		&cmdScriptSize },		// 9E +	{ "set.game.id",		"s",		&cmdSetGameID },		// 9F +	{ "log",				"s",		&cmdLog },				// A0 +	{ "set.scan.start",		"",			&cmdSetScanStart },		// A1 +	{ "reset.scan.start",	"",			&cmdResetScanStart },	// A2 +	{ "reposition.to",		"nnn",		&cmdRepositionTo },		// A3 +	{ "reposition.to.v",	"nvv",		&cmdRepositionToF },	// A4 +	{ "trace.on",			"",			&cmdTraceOn },			// A5 +	{ "trace.info", 		"nnn",		&cmdTraceInfo },		// A6  	{ "print.at",			"snnn",		&cmdPrintAt }, // 3 args for AGI versions before 2.440 -	{ "print.at.v",			"vnnn",		&cmdPrintAtV }, -	{ "discard.view.v",		"v",		&cmdDiscardView}, -	{ "clear.text.rect",	"nnnnn",	&cmdClearTextRect }, -	{ "set.upper.left",		"nn",		&cmdSetUpperLeft }, -	{ "set.menu",			"s",		&cmdSetMenu }, -	{ "set.menu.item",		"sn",		&cmdSetMenuItem }, -	{ "submit.menu",		"",			&cmdSubmitMenu }, -	{ "enable.item",		"n",		&cmdEnableItem }, -	{ "disable.item",		"n",		&cmdDisableItem }, -	{ "menu.input",			"",			&cmdMenuInput }, -	{ "show.obj.v",			"v",		&cmdShowObjV }, -	{ "open.dialogue",		"",			&cmdOpenDialogue }, -	{ "close.dialogue",		"",			&cmdCloseDialogue }, -	{ "mul.n",				"vn",		&cmdMulN }, -	{ "mul.v",				"vv",		&cmdMulV }, -	{ "div.n",				"vn",		&cmdDivN }, -	{ "div.v",				"vv",		&cmdDivV }, -	{ "close.window",		"",			&cmdCloseWindow }, -	{ "set.simple",			"n",		&cmdSetSimple }, -	{ "push.script",		"",			&cmdPushScript }, -	{ "pop.script",			"",			&cmdPopScript }, -	{ "hold.key",			"",			&cmdHoldKey }, -	{ "set.pri.base",		"n",		&cmdSetPriBase }, -	{ "discard.sound",		"n",		&cmdDiscardSound }, +	{ "print.at.v",			"vnnn",		&cmdPrintAtV },			// A8 +	{ "discard.view.v",		"v",		&cmdDiscardView},		// A9 +	{ "clear.text.rect",	"nnnnn",	&cmdClearTextRect },	// AA +	{ "set.upper.left",		"nn",		&cmdSetUpperLeft },		// AB +	{ "set.menu",			"s",		&cmdSetMenu },			// AC +	{ "set.menu.item",		"sn",		&cmdSetMenuItem },		// AD +	{ "submit.menu",		"",			&cmdSubmitMenu },		// AE +	{ "enable.item",		"n",		&cmdEnableItem },		// AF +	{ "disable.item",		"n",		&cmdDisableItem },		// B0 +	{ "menu.input",			"",			&cmdMenuInput },		// B1 +	{ "show.obj.v",			"v",		&cmdShowObjV },			// B2 +	{ "open.dialogue",		"",			&cmdOpenDialogue },		// B3 +	{ "close.dialogue",		"",			&cmdCloseDialogue },	// B4 +	{ "mul.n",				"vn",		&cmdMulN },				// B5 +	{ "mul.v",				"vv",		&cmdMulV },				// B6 +	{ "div.n",				"vn",		&cmdDivN },				// B7 +	{ "div.v",				"vv",		&cmdDivV },				// B8 +	{ "close.window",		"",			&cmdCloseWindow },		// B9 +	{ "set.simple",			"n",		&cmdSetSimple },		// BA +	{ "push.script",		"",			&cmdPushScript },		// BB +	{ "pop.script",			"",			&cmdPopScript },		// BC +	{ "hold.key",			"",			&cmdHoldKey },			// BD +	{ "set.pri.base",		"n",		&cmdSetPriBase },		// BE +	{ "discard.sound",		"n",		&cmdDiscardSound },		// BF  	{ "hide.mouse",			"",			&cmdHideMouse }, // 1 arg for AGI version 3.002.086 -	{ "allow.menu",			"n",		&cmdAllowMenu }, -	{ "show.mouse",			"",			&cmdShowMouse }, -	{ "fence.mouse",		"nnnn",		&cmdFenceMouse }, -	{ "mouse.posn",			"vv",		&cmdMousePosn }, +	{ "allow.menu",			"n",		&cmdAllowMenu },		// C1 +	{ "show.mouse",			"",			&cmdShowMouse },		// C2 +	{ "fence.mouse",		"nnnn",		&cmdFenceMouse },		// C3 +	{ "mouse.posn",			"vv",		&cmdMousePosn },		// C4  	{ "release.key",		"",			&cmdReleaseKey }, // 2 args for at least the Amiga GR (v2.05 1989-03-09) using AGI 2.316 -	{ "adj.ego.move.to.xy",	"",			&cmdAdjEgoMoveToXY } +	{ "adj.ego.move.to.xy",	"",			&cmdAdjEgoMoveToXY }	// C6  };  void AgiEngine::setupOpcodes() { diff --git a/engines/agi/palette.h b/engines/agi/palette.h new file mode 100644 index 0000000000..88ccce7441 --- /dev/null +++ b/engines/agi/palette.h @@ -0,0 +1,311 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef AGI_PALETTE_H +#define AGI_PALETTE_H + +namespace Agi { + +/** + * 16 color RGB palette. + * This array contains the 6-bit RGB values of the EGA palette exported + * to the console drivers. + */ +static const uint8 PALETTE_EGA[16 * 3] = { +	0x00, 0x00, 0x00, +	0x00, 0x00, 0x2a, +	0x00, 0x2a, 0x00, +	0x00, 0x2a, 0x2a, +	0x2a, 0x00, 0x00, +	0x2a, 0x00, 0x2a, +	0x2a, 0x15, 0x00, +	0x2a, 0x2a, 0x2a, +	0x15, 0x15, 0x15, +	0x15, 0x15, 0x3f, +	0x15, 0x3f, 0x15, +	0x15, 0x3f, 0x3f, +	0x3f, 0x15, 0x15, +	0x3f, 0x15, 0x3f, +	0x3f, 0x3f, 0x15, +	0x3f, 0x3f, 0x3f +}; + +/** + * 4 color CGA palette. + */ +static const uint8 PALETTE_CGA[4 * 3] = { +	0x00, 0x00, 0x00, // black +	0x55, 0xff, 0xff, // cyan +	0xff, 0x55, 0xff, // magenta +	0xff, 0xff, 0xff +}; + +/** + * Atari ST AGI palette. + * Used by all of the tested Atari ST AGI games + * from Donald Duck's Playground (1986) to Manhunter II (1989). + * 16 RGB colors. 3 bits per color component. + */ +static const uint8 PALETTE_ATARI_ST[16 * 3] = { +	0x0, 0x0, 0x0, +	0x0, 0x0, 0x7, +	0x0, 0x4, 0x0, +	0x0, 0x5, 0x4, +	0x5, 0x0, 0x0, +	0x5, 0x3, 0x6, +	0x4, 0x3, 0x0, +	0x5, 0x5, 0x5, +	0x3, 0x3, 0x2, +	0x0, 0x5, 0x7, +	0x0, 0x6, 0x0, +	0x0, 0x7, 0x6, +	0x7, 0x2, 0x3, +	0x7, 0x4, 0x7, +	0x7, 0x7, 0x4, +	0x7, 0x7, 0x7 +}; + +/** + * Second generation Apple IIGS AGI palette. + * A 16-color, 12-bit RGB palette. + * + * Used by at least the following Apple IIGS AGI versions: + * 1.003 (Leisure Suit Larry I  v1.0E, intro says 1987) + * 1.005 (AGI Demo 2            1987-06-30?) + * 1.006 (King's Quest I        v1.0S 1988-02-23) + * 1.007 (Police Quest I        v2.0B 1988-04-21 8:00am) + * 1.013 (King's Quest II       v2.0A 1988-06-16 (CE)) + * 1.013 (Mixed-Up Mother Goose v2.0A 1988-05-31 10:00am) + * 1.014 (King's Quest III      v2.0A 1988-08-28 (CE)) + * 1.014 (Space Quest II        v2.0A, LOGIC.141 says 1988) + * 2.004 (Manhunter I           v2.0E 1988-10-05 (CE)) + * 2.006 (King's Quest IV       v1.0K 1988-11-22 (CE)) + * 3.001 (Black Cauldron        v1.0O 1989-02-24 (CE)) + * 3.003 (Gold Rush!            v1.0M 1989-02-28 (CE)) + */ +// *NOT* identical to Amiga generation 2 palette +static const uint8 PALETTE_APPLE_II_GS[16 * 3] = { +	0x0, 0x0, 0x0, +	0x0, 0x0, 0xF, +	0x0, 0x8, 0x0, +	0x0, 0xD, 0xB, +	0xC, 0x0, 0x0, +	0xB, 0x7, 0xD, +	0x8, 0x5, 0x0, +	0xB, 0xB, 0xB, +	0x7, 0x7, 0x7, +	0x0, 0xB, 0xF, +	0x0, 0xE, 0x0, +	0x0, 0xF, 0xD, +	0xF, 0x9, 0x8, +	0xD, 0x9, 0xF, // difference between Amiga v2 palette and Apple II GS palette, gotten from emulator (SQ2) +	0xE, 0xE, 0x0, +	0xF, 0xF, 0xF +}; + +/** + * First generation Amiga & Apple IIGS AGI palette. + * A 16-color, 12-bit RGB palette. + * + * Used by at least the following Amiga AGI versions: + * 2.082 (King's Quest I   v1.0U 1986) + * 2.082 (Space Quest I    v1.2  1986) + * 2.090 (King's Quest III v1.01 1986-11-08) + * 2.107 (King's Quest II  v2.0J 1987-01-29) + * x.yyy (Black Cauldron   v2.00 1987-06-14) + * x.yyy (Larry I          v1.05 1987-06-26) + * + * Also used by at least the following Apple IIGS AGI versions: + * 1.002 (Space Quest I, intro says v2.2 1987) + */ +static const uint8 PALETTE_AMIGA_V1[16 * 3] = { +	0x0, 0x0, 0x0, +	0x0, 0x0, 0xF, +	0x0, 0x8, 0x0, +	0x0, 0xD, 0xB, +	0xC, 0x0, 0x0, +	0xB, 0x7, 0xD, +	0x8, 0x5, 0x0, +	0xB, 0xB, 0xB, +	0x7, 0x7, 0x7, +	0x0, 0xB, 0xF, +	0x0, 0xE, 0x0, +	0x0, 0xF, 0xD, +	0xF, 0x9, 0x8, +	0xF, 0x7, 0x0, +	0xE, 0xE, 0x0, +	0xF, 0xF, 0xF +}; + +/** + * Second generation Amiga AGI palette. + * A 16-color, 12-bit RGB palette. + * + * Used by at least the following Amiga AGI versions: + * 2.202 (Space Quest II v2.0F. Intro says 1988. ScummVM 0.10.0 detects as 1986-12-09) + */ +static const uint8 PALETTE_AMIGA_V2[16 * 3] = { +	0x0, 0x0, 0x0, +	0x0, 0x0, 0xF, +	0x0, 0x8, 0x0, +	0x0, 0xD, 0xB, +	0xC, 0x0, 0x0, +	0xB, 0x7, 0xD, +	0x8, 0x5, 0x0, +	0xB, 0xB, 0xB, +	0x7, 0x7, 0x7, +	0x0, 0xB, 0xF, +	0x0, 0xE, 0x0, +	0x0, 0xF, 0xD, +	0xF, 0x9, 0x8, +	0xD, 0x0, 0xF, +	0xE, 0xE, 0x0, +	0xF, 0xF, 0xF +}; + +/** + * Third generation Amiga AGI palette. + * A 16-color, 12-bit RGB palette. + * + * Used by at least the following Amiga AGI versions: + * 2.310 (Police Quest I   v2.0B 1989-02-22) + * 2.316 (Gold Rush!       v2.05 1989-03-09) + * x.yyy (Manhunter I      v1.06 1989-03-18) + * 2.333 (Manhunter II     v3.06 1989-08-17) + * 2.333 (King's Quest III v2.15 1989-11-15) + */ +static const uint8 PALETTE_AMIGA_V3[16 * 3] = { +	0x0, 0x0, 0x0, +	0x0, 0x0, 0xB, +	0x0, 0xB, 0x0, +	0x0, 0xB, 0xB, +	0xB, 0x0, 0x0, +	0xB, 0x0, 0xB, +	0xC, 0x7, 0x0, +	0xB, 0xB, 0xB, +	0x7, 0x7, 0x7, +	0x0, 0x0, 0xF, +	0x0, 0xF, 0x0, +	0x0, 0xF, 0xF, +	0xF, 0x0, 0x0, +	0xF, 0x0, 0xF, +	0xF, 0xF, 0x0, +	0xF, 0xF, 0xF +}; + +/** + * 16 color amiga-ish palette. + */ +static const uint8 PALETTE_AMIGA_ALT[16 * 3] = { +	0x00, 0x00, 0x00, +	0x00, 0x00, 0x3f, +	0x00, 0x2A, 0x00, +	0x00, 0x2A, 0x2A, +	0x33, 0x00, 0x00, +	0x2f, 0x1c, 0x37, +	0x23, 0x14, 0x00, +	0x2f, 0x2f, 0x2f, +	0x15, 0x15, 0x15, +	0x00, 0x2f, 0x3f, +	0x00, 0x33, 0x15, +	0x15, 0x3F, 0x3F, +	0x3f, 0x27, 0x23, +	0x3f, 0x15, 0x3f, +	0x3b, 0x3b, 0x00, +	0x3F, 0x3F, 0x3F +}; + +/** + * 256 color palette used with AGI256 and AGI256-2 games. + * Uses full 8 bits per color component. + * This is NOT the standard VGA palette. + */ +static const uint8 PALETTE_VGA[256 * 3] = { +	0x00, 0x00, 0x00, 0x00, 0x00, 0xA8, 0x00, 0xA8, 0x00, 0x00, 0xA8, 0xA8, +	0xA8, 0x00, 0x00, 0xA8, 0x00, 0xA8, 0xA8, 0x54, 0x00, 0xA8, 0xA8, 0xA8, +	0x54, 0x54, 0x54, 0x54, 0x54, 0xFC, 0x54, 0xFC, 0x54, 0x54, 0xFC, 0xFC, +	0xFC, 0x54, 0x54, 0xFC, 0x54, 0xFC, 0xFC, 0xFC, 0x54, 0xFC, 0xFC, 0xFC, +	0x00, 0x00, 0x00, 0x14, 0x14, 0x14, 0x20, 0x20, 0x20, 0x2C, 0x2C, 0x2C, +	0x38, 0x38, 0x38, 0x44, 0x44, 0x44, 0x50, 0x50, 0x50, 0x60, 0x60, 0x60, +	0x70, 0x70, 0x70, 0x80, 0x80, 0x80, 0x90, 0x90, 0x90, 0xA0, 0xA0, 0xA0, +	0xB4, 0xB4, 0xB4, 0xC8, 0xC8, 0xC8, 0xE0, 0xE0, 0xE0, 0xFC, 0xFC, 0xFC, +	0x00, 0x00, 0xFC, 0x40, 0x00, 0xFC, 0x7C, 0x00, 0xFC, 0xBC, 0x00, 0xFC, +	0xFC, 0x00, 0xFC, 0xFC, 0x00, 0xBC, 0xFC, 0x00, 0x7C, 0xFC, 0x00, 0x40, +	0xFC, 0x00, 0x00, 0xFC, 0x40, 0x00, 0xFC, 0x7C, 0x00, 0xFC, 0xBC, 0x00, +	0xFC, 0xFC, 0x00, 0xBC, 0xFC, 0x00, 0x7C, 0xFC, 0x00, 0x40, 0xFC, 0x00, +	0x00, 0xFC, 0x00, 0x00, 0xFC, 0x40, 0x00, 0xFC, 0x7C, 0x00, 0xFC, 0xBC, +	0x00, 0xFC, 0xFC, 0x00, 0xBC, 0xFC, 0x00, 0x7C, 0xFC, 0x00, 0x40, 0xFC, +	0x7C, 0x7C, 0xFC, 0x9C, 0x7C, 0xFC, 0xBC, 0x7C, 0xFC, 0xDC, 0x7C, 0xFC, +	0xFC, 0x7C, 0xFC, 0xFC, 0x7C, 0xDC, 0xFC, 0x7C, 0xBC, 0xFC, 0x7C, 0x9C, +	0xFC, 0x7C, 0x7C, 0xFC, 0x9C, 0x7C, 0xFC, 0xBC, 0x7C, 0xFC, 0xDC, 0x7C, +	0xFC, 0xFC, 0x7C, 0xDC, 0xFC, 0x7C, 0xBC, 0xFC, 0x7C, 0x9C, 0xFC, 0x7C, +	0x7C, 0xFC, 0x7C, 0x7C, 0xFC, 0x9C, 0x7C, 0xFC, 0xBC, 0x7C, 0xFC, 0xDC, +	0x7C, 0xFC, 0xFC, 0x7C, 0xDC, 0xFC, 0x7C, 0xBC, 0xFC, 0x7C, 0x9C, 0xFC, +	0xB4, 0xB4, 0xFC, 0xC4, 0xB4, 0xFC, 0xD8, 0xB4, 0xFC, 0xE8, 0xB4, 0xFC, +	0xFC, 0xB4, 0xFC, 0xFC, 0xB4, 0xE8, 0xFC, 0xB4, 0xD8, 0xFC, 0xB4, 0xC4, +	0xFC, 0xB4, 0xB4, 0xFC, 0xC4, 0xB4, 0xFC, 0xD8, 0xB4, 0xFC, 0xE8, 0xB4, +	0xFC, 0xFC, 0xB4, 0xE8, 0xFC, 0xB4, 0xD8, 0xFC, 0xB4, 0xC4, 0xFC, 0xB4, +	0xB4, 0xFC, 0xB4, 0xB4, 0xFC, 0xC4, 0xB4, 0xFC, 0xD8, 0xB4, 0xFC, 0xE8, +	0xB4, 0xFC, 0xFC, 0xB4, 0xE8, 0xFC, 0xB4, 0xD8, 0xFC, 0xB4, 0xC4, 0xFC, +	0x00, 0x00, 0x70, 0x1C, 0x00, 0x70, 0x38, 0x00, 0x70, 0x54, 0x00, 0x70, +	0x70, 0x00, 0x70, 0x70, 0x00, 0x54, 0x70, 0x00, 0x38, 0x70, 0x00, 0x1C, +	0x70, 0x00, 0x00, 0x70, 0x1C, 0x00, 0x70, 0x38, 0x00, 0x70, 0x54, 0x00, +	0x70, 0x70, 0x00, 0x54, 0x70, 0x00, 0x38, 0x70, 0x00, 0x1C, 0x70, 0x00, +	0x00, 0x70, 0x00, 0x00, 0x70, 0x1C, 0x00, 0x70, 0x38, 0x00, 0x70, 0x54, +	0x00, 0x70, 0x70, 0x00, 0x54, 0x70, 0x00, 0x38, 0x70, 0x00, 0x1C, 0x70, +	0x38, 0x38, 0x70, 0x44, 0x38, 0x70, 0x54, 0x38, 0x70, 0x60, 0x38, 0x70, +	0x70, 0x38, 0x70, 0x70, 0x38, 0x60, 0x70, 0x38, 0x54, 0x70, 0x38, 0x44, +	0x70, 0x38, 0x38, 0x70, 0x44, 0x38, 0x70, 0x54, 0x38, 0x70, 0x60, 0x38, +	0x70, 0x70, 0x38, 0x60, 0x70, 0x38, 0x54, 0x70, 0x38, 0x44, 0x70, 0x38, +	0x38, 0x70, 0x38, 0x38, 0x70, 0x44, 0x38, 0x70, 0x54, 0x38, 0x70, 0x60, +	0x38, 0x70, 0x70, 0x38, 0x60, 0x70, 0x38, 0x54, 0x70, 0x38, 0x44, 0x70, +	0x50, 0x50, 0x70, 0x58, 0x50, 0x70, 0x60, 0x50, 0x70, 0x68, 0x50, 0x70, +	0x70, 0x50, 0x70, 0x70, 0x50, 0x68, 0x70, 0x50, 0x60, 0x70, 0x50, 0x58, +	0x70, 0x50, 0x50, 0x70, 0x58, 0x50, 0x70, 0x60, 0x50, 0x70, 0x68, 0x50, +	0x70, 0x70, 0x50, 0x68, 0x70, 0x50, 0x60, 0x70, 0x50, 0x58, 0x70, 0x50, +	0x50, 0x70, 0x50, 0x50, 0x70, 0x58, 0x50, 0x70, 0x60, 0x50, 0x70, 0x68, +	0x50, 0x70, 0x70, 0x50, 0x68, 0x70, 0x50, 0x60, 0x70, 0x50, 0x58, 0x70, +	0x00, 0x00, 0x40, 0x10, 0x00, 0x40, 0x20, 0x00, 0x40, 0x30, 0x00, 0x40, +	0x40, 0x00, 0x40, 0x40, 0x00, 0x30, 0x40, 0x00, 0x20, 0x40, 0x00, 0x10, +	0x40, 0x00, 0x00, 0x40, 0x10, 0x00, 0x40, 0x20, 0x00, 0x40, 0x30, 0x00, +	0x40, 0x40, 0x00, 0x30, 0x40, 0x00, 0x20, 0x40, 0x00, 0x10, 0x40, 0x00, +	0x00, 0x40, 0x00, 0x00, 0x40, 0x10, 0x00, 0x40, 0x20, 0x00, 0x40, 0x30, +	0x00, 0x40, 0x40, 0x00, 0x30, 0x40, 0x00, 0x20, 0x40, 0x00, 0x10, 0x40, +	0x20, 0x20, 0x40, 0x28, 0x20, 0x40, 0x30, 0x20, 0x40, 0x38, 0x20, 0x40, +	0x40, 0x20, 0x40, 0x40, 0x20, 0x38, 0x40, 0x20, 0x30, 0x40, 0x20, 0x28, +	0x40, 0x20, 0x20, 0x40, 0x28, 0x20, 0x40, 0x30, 0x20, 0x40, 0x38, 0x20, +	0x40, 0x40, 0x20, 0x38, 0x40, 0x20, 0x30, 0x40, 0x20, 0x28, 0x40, 0x20, +	0x20, 0x40, 0x20, 0x20, 0x40, 0x28, 0x20, 0x40, 0x30, 0x20, 0x40, 0x38, +	0x20, 0x40, 0x40, 0x20, 0x38, 0x40, 0x20, 0x30, 0x40, 0x20, 0x28, 0x40, +	0x2C, 0x2C, 0x40, 0x30, 0x2C, 0x40, 0x34, 0x2C, 0x40, 0x3C, 0x2C, 0x40, +	0x40, 0x2C, 0x40, 0x40, 0x2C, 0x3C, 0x40, 0x2C, 0x34, 0x40, 0x2C, 0x30, +	0x40, 0x2C, 0x2C, 0x40, 0x30, 0x2C, 0x40, 0x34, 0x2C, 0x40, 0x3C, 0x2C, +	0x40, 0x40, 0x2C, 0x3C, 0x40, 0x2C, 0x34, 0x40, 0x2C, 0x30, 0x40, 0x2C, +	0x2C, 0x40, 0x2C, 0x2C, 0x40, 0x30, 0x2C, 0x40, 0x34, 0x2C, 0x40, 0x3C, +	0x2C, 0x40, 0x40, 0x2C, 0x3C, 0x40, 0x2C, 0x34, 0x40, 0x2C, 0x30, 0x40, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +} // End of namespace Agi + +#endif /* AGI_PALETTE_H */ diff --git a/engines/agi/picture.cpp b/engines/agi/picture.cpp index 58dfb9db68..951d67a0c0 100644 --- a/engines/agi/picture.cpp +++ b/engines/agi/picture.cpp @@ -31,8 +31,11 @@ PictureMgr::PictureMgr(AgiBase *agi, GfxMgr *gfx) {  	_vm = agi;  	_gfx = gfx; +	_resourceNr = 0;  	_data = NULL; -	_flen = _foffs = 0; +	_dataSize = 0; +	_dataOffset = 0; +	_dataOffsetNibble = false;  	_patCode = _patNum = _priOn = _scrOn = _scrColor = _priColor = 0;  	_xOffset = _yOffset = 0; @@ -44,257 +47,38 @@ PictureMgr::PictureMgr(AgiBase *agi, GfxMgr *gfx) {  }  void PictureMgr::putVirtPixel(int x, int y) { -	uint8 *p; - -	x += _xOffset; -	y += _yOffset; +	byte drawMask = 0;  	if (x < 0 || y < 0 || x >= _width || y >= _height)  		return; -	p = &_vm->_game.sbuf16c[y * _width + x]; +	x += _xOffset; +	y += _yOffset;  	if (_priOn) -		*p = (_priColor << 4) | (*p & 0x0f); +		drawMask |= GFX_SCREEN_MASK_PRIORITY;  	if (_scrOn) -		*p = _scrColor | (*p & 0xf0); -} +		drawMask |= GFX_SCREEN_MASK_VISUAL; -#if 0 -static void drawProc(int x, int y, int c, void *data) { -	((PictureMgr *)data)->putVirtPixel(x, y); +	_gfx->putPixel(x, y, drawMask, _scrColor, _priColor);  } -#endif - -/** - * Draw an AGI line. - * A line drawing routine sent by Joshua Neal, modified by Stuart George - * (fixed >>2 to >>1 and some other bugs like x1 instead of y1, etc.) - * @param x1  x coordinate of start point - * @param y1  y coordinate of start point - * @param x2  x coordinate of end point - * @param y2  y coordinate of end point - */ -void PictureMgr::drawLine(int x1, int y1, int x2, int y2) { -	x1 = CLIP(x1, 0, _width - 1); -	x2 = CLIP(x2, 0, _width - 1); -	y1 = CLIP(y1, 0, _height - 1); -	y2 = CLIP(y2, 0, _height - 1); - -#if 0 -	Graphics::drawLine(x1, y1, x2, y2, 0, drawProc, this); -#else -	int i, x, y, deltaX, deltaY, stepX, stepY, errorX, errorY, detdelta; - -	// Vertical line - -	if (x1 == x2) { -		if (y1 > y2) { -			SWAP(y1, y2); -		} - -		for (; y1 <= y2; y1++) -			putVirtPixel(x1, y1); - -		return; -	} - -	// Horizontal line - -	if (y1 == y2) { -		if (x1 > x2) { -			SWAP(x1, x2); -		} -		for (; x1 <= x2; x1++) -			putVirtPixel(x1, y1); -		return; -	} - -	y = y1; -	x = x1; -	stepY = 1; -	deltaY = y2 - y1; -	if (deltaY < 0) { -		stepY = -1; -		deltaY = -deltaY; -	} - -	stepX = 1; -	deltaX = x2 - x1; -	if (deltaX < 0) { -		stepX = -1; -		deltaX = -deltaX; -	} - -	if (deltaY > deltaX) { -		i = deltaY; -		detdelta = deltaY; -		errorX = deltaY / 2; -		errorY = 0; +byte PictureMgr::getNextByte() { +	if (!_dataOffsetNibble) { +		return _data[_dataOffset++];  	} else { -		i = deltaX; -		detdelta = deltaX; -		errorX = 0; -		errorY = deltaX / 2; +		byte curByte = _data[_dataOffset++] << 4; +		return (_data[_dataOffset] >> 4) | curByte;  	} - -	putVirtPixel(x, y); - -	do { -		errorY += deltaY; -		if (errorY >= detdelta) { -			errorY -= detdelta; -			y += stepY; -		} - -		errorX += deltaX; -		if (errorX >= detdelta) { -			errorX -= detdelta; -			x += stepX; -		} - -		putVirtPixel(x, y); -		i--; -	} while (i > 0); -#endif  } -/** - * Draw a relative AGI line. - * Draws short lines relative to last position. (drawing action 0xF7) - */ -void PictureMgr::dynamicDrawLine() { -	int x1, y1, disp, dx, dy; - -	if ((x1 = nextByte()) >= _minCommand || -		(y1 = nextByte()) >= _minCommand) { -		_foffs--; -		return; -	} - -	putVirtPixel(x1, y1); - -	for (;;) { -		if ((disp = nextByte()) >= _minCommand) -			break; - -		dx = ((disp & 0xf0) >> 4) & 0x0f; -		dy = (disp & 0x0f); - -		if (dx & 0x08) -			dx = -(dx & 0x07); -		if (dy & 0x08) -			dy = -(dy & 0x07); - -		drawLine(x1, y1, x1 + dx, y1 + dy); -		x1 += dx; -		y1 += dy; -	} -	_foffs--; -} - -/************************************************************************** -** absoluteLine -** -** Draws long lines to actual locations (cf. relative) (drawing action 0xF6) -**************************************************************************/ -void PictureMgr::absoluteDrawLine() { -	int x1, y1, x2, y2; - -	if ((x1 = nextByte()) >= _minCommand || -		(y1 = nextByte()) >= _minCommand) { -		_foffs--; -		return; -	} - -	putVirtPixel(x1, y1); - -	for (;;) { -		if ((x2 = nextByte()) >= _minCommand) -			break; - -		if ((y2 = nextByte()) >= _minCommand) -			break; - -		drawLine(x1, y1, x2, y2); -		x1 = x2; -		y1 = y2; -	} -	_foffs--; -} - -/************************************************************************** -** okToFill -**************************************************************************/ -int PictureMgr::isOkFillHere(int x, int y) { -	uint8 p; - -	x += _xOffset; -	y += _yOffset; - -	if (x < 0 || x >= _width || y < 0 || y >= _height) -		return false; - -	p = _vm->_game.sbuf16c[y * _width + x]; - -	if (_flags & kPicFTrollMode) -		return ((p & 0x0f) != 11 && (p & 0x0f) != _scrColor); - -	if (!_priOn && _scrOn && _scrColor != 15) -		return (p & 0x0f) == 15; - -	if (_priOn && !_scrOn && _priColor != 4) -		return (p >> 4) == 4; - -	return (_scrOn && (p & 0x0f) == 15 && _scrColor != 15); -} - -/************************************************************************** -** agi_fill -**************************************************************************/ -void PictureMgr::agiFill(unsigned int x, unsigned int y) { -	if (!_scrOn && !_priOn) -		return; - -	// Push initial pixel on the stack -	Common::Stack<Common::Point> stack; -	stack.push(Common::Point(x,y)); - -	// Exit if stack is empty -	while (!stack.empty()) { -		Common::Point p = stack.pop(); -		unsigned int c; -		int newspanUp, newspanDown; - -		if (!isOkFillHere(p.x, p.y)) -			continue; - -		// Scan for left border -		for (c = p.x - 1; isOkFillHere(c, p.y); c--) -			; - -		newspanUp = newspanDown = 1; -		for (c++; isOkFillHere(c, p.y); c++) { -			putVirtPixel(c, p.y); -			if (isOkFillHere(c, p.y - 1)) { -				if (newspanUp) { -					stack.push(Common::Point(c,p.y-1)); -					newspanUp = 0; -				} -			} else { -				newspanUp = 1; -			} - -			if (isOkFillHere(c, p.y + 1)) { -				if (newspanDown) { -					stack.push(Common::Point(c,p.y+1)); -					newspanDown = 0; -				} -			} else { -				newspanDown = 1; -			} -		} +byte PictureMgr::getNextNibble() { +	if (!_dataOffsetNibble) { +		_dataOffsetNibble = true; +		return _data[_dataOffset] >> 4; +	} else { +		_dataOffsetNibble = false; +		return _data[_dataOffset++] & 0x0F;  	}  } @@ -303,43 +87,43 @@ void PictureMgr::agiFill(unsigned int x, unsigned int y) {  **  ** Draws an xCorner  (drawing action 0xF5)  **************************************************************************/ -void PictureMgr::xCorner(bool skipOtherCoords) { +void PictureMgr::draw_xCorner(bool skipOtherCoords) {  	int x1, x2, y1, y2; -	if ((x1 = nextByte()) >= _minCommand || -		(y1 = nextByte()) >= _minCommand) { -		_foffs--; +	if ((x1 = getNextByte()) >= _minCommand || +		(y1 = getNextByte()) >= _minCommand) { +		_dataOffset--;  		return;  	}  	putVirtPixel(x1, y1);  	for (;;) { -		x2 = nextByte(); +		x2 = getNextByte();  		if (x2 >= _minCommand)  			break;  		if (skipOtherCoords) -			if (nextByte() >= _minCommand) +			if (getNextByte() >= _minCommand)  				break; -		drawLine(x1, y1, x2, y1); +		draw_Line(x1, y1, x2, y1);  		x1 = x2;  		if (skipOtherCoords) -			if (nextByte() >= _minCommand) +			if (getNextByte() >= _minCommand)  				break; -		y2 = nextByte(); +		y2 = getNextByte();  		if (y2 >= _minCommand)  			break; -		drawLine(x1, y1, x1, y2); +		draw_Line(x1, y1, x1, y2);  		y1 = y2;  	} -	_foffs--; +	_dataOffset--;  }  /************************************************************************** @@ -350,9 +134,9 @@ void PictureMgr::xCorner(bool skipOtherCoords) {  void PictureMgr::yCorner(bool skipOtherCoords) {  	int x1, x2, y1, y2; -	if ((x1 = nextByte()) >= _minCommand || -		(y1 = nextByte()) >= _minCommand) { -		_foffs--; +	if ((x1 = getNextByte()) >= _minCommand || +		(y1 = getNextByte()) >= _minCommand) { +		_dataOffset--;  		return;  	} @@ -360,44 +144,30 @@ void PictureMgr::yCorner(bool skipOtherCoords) {  	for (;;) {  		if (skipOtherCoords) -			if (nextByte() >= _minCommand) +			if (getNextByte() >= _minCommand)  				break; -		y2 = nextByte(); +		y2 = getNextByte();  		if (y2 >= _minCommand)  			break; -		drawLine(x1, y1, x1, y2); +		draw_Line(x1, y1, x1, y2);  		y1 = y2; -		x2 = nextByte(); +		x2 = getNextByte();  		if (x2 >= _minCommand)  			break;  		if (skipOtherCoords) -			if (nextByte() >= _minCommand) +			if (getNextByte() >= _minCommand)  				break; -		drawLine(x1, y1, x2, y1); +		draw_Line(x1, y1, x2, y1);  		x1 = x2;  	} -	_foffs--; -} - -/************************************************************************** -** fill -** -** AGI flood fill.  (drawing action 0xF8) -**************************************************************************/ -void PictureMgr::fill() { -	int x1, y1; - -	while ((x1 = nextByte()) < _minCommand && (y1 = nextByte()) < _minCommand) -		agiFill(x1, y1); - -	_foffs--; +	_dataOffset--;  }  /************************************************************************** @@ -406,7 +176,6 @@ void PictureMgr::fill() {  ** Draws pixels, circles, squares, or splatter brush patterns depending  ** on the pattern code.  **************************************************************************/ -  void PictureMgr::plotPattern(int x, int y) {  	static const uint16 binary_list[] = {0x8000, 0x4000, 0x2000, 0x1000, 0x800, 0x400, 0x200, 0x100,  		0x80, 0x40, 0x20, 0x10, 0x8, 0x4, 0x2, 0x1}; @@ -532,267 +301,600 @@ void PictureMgr::plotBrush() {  	for (;;) {  		if (_patCode & 0x20) { -			if ((_patNum = nextByte()) >= _minCommand) +			if ((_patNum = getNextByte()) >= _minCommand)  				break;  			_patNum = (_patNum >> 1) & 0x7f;  		} -		if ((x1 = nextByte()) >= _minCommand) +		if ((x1 = getNextByte()) >= _minCommand)  			break; -		if ((y1 = nextByte()) >= _minCommand) +		if ((y1 = getNextByte()) >= _minCommand)  			break;  		plotPattern(x1, y1);  	} -	_foffs--; +	_dataOffset--;  }  /**************************************************************************  ** Draw AGI picture  **************************************************************************/ -  void PictureMgr::drawPicture() { -	uint8 act; -	int drawing; -	int iteration = 0; -  	_patCode = 0;  	_patNum = 0; -	_priOn = _scrOn = false; -	_scrColor = (_pictureVersion == AGIPIC_C64) ? 0x0 : 0xf; -	_priColor = 0x4; +	_priOn = false; +	_scrOn = false; +	_scrColor = 15; +	_priColor = 4; + +	switch (_pictureVersion) { +	case AGIPIC_C64: +		drawPictureC64(); +		break; +	case AGIPIC_V1: +		drawPictureV1(); +		break; +	case AGIPIC_V15: +		drawPictureV15(); +		break; +	case AGIPIC_V2: +		drawPictureV2(); +		break; +	case AGIPIC_256: +		drawPictureAGI256(); +		break; +	default: +		break; +	} +} -	drawing = 1; +void PictureMgr::drawPictureC64() { +	byte curByte; -	debugC(8, kDebugLevelMain, "Drawing v2 picture"); -	for (drawing = 1; drawing && _foffs < _flen;) { -		act = nextByte(); +	debugC(8, kDebugLevelMain, "Drawing C64 picture"); -		if (_pictureVersion == AGIPIC_C64 && act >= 0xf0 && act <= 0xfe) { -			_scrColor = act - 0xf0; +	_scrColor = 0x0; + +	while (_dataOffset < _dataSize) { +		curByte = getNextByte(); + +		if ((curByte >= 0xF0) && (curByte <= 0xFE)) { +			_scrColor = curByte & 0x0F;  			continue;  		} -		switch (act) { -		case 0xe0:	// x-corner (C64) -			xCorner(); +		switch (curByte) { +		case 0xe0:	// x-corner +			draw_xCorner();  			break; -		case 0xe1:	// y-corner (C64) +		case 0xe1:	// y-corner  			yCorner();  			break; -		case 0xe2:	// dynamic draw lines (C64) -			dynamicDrawLine(); +		case 0xe2:	// dynamic draw lines +			draw_LineShort();  			break; -		case 0xe3:	// absolute draw lines (C64) -			absoluteDrawLine(); +		case 0xe3:	// absolute draw lines +			draw_LineAbsolute();  			break; -		case 0xe4:	// fill (C64) -			_scrColor = nextByte(); -			_scrColor &= 0xF;	// for v3 drawing diff -			fill(); +		case 0xe4:	// fill +			draw_SetColor(); +			draw_Fill();  			break; -		case 0xe5:	// enable screen drawing (C64) +		case 0xe5:	// enable screen drawing  			_scrOn = true;  			break; -		case 0xe6:	// plot brush (C64) -			_patCode = nextByte(); +		case 0xe6:	// plot brush +			_patCode = getNextByte();  			plotBrush();  			break; -		case 0xf0:	// set color on screen (AGI pic v2) -			if (_pictureVersion == AGIPIC_V15) -				break; +		case 0xfb: +			draw_LineShort(); +			break; +		case 0xff: // end of data +			return; +		default: +			warning("Unknown picture opcode (%x) at (%x)", curByte, _dataOffset - 1); +			break; +		} +	} +} + +void PictureMgr::drawPictureV1() { +	byte curByte; + +	debugC(8, kDebugLevelMain, "Drawing V1 picture"); -			_scrColor = nextByte(); -			_scrColor &= 0xF;	// for v3 drawing diff +	while (_dataOffset < _dataSize) { +		curByte = getNextByte(); + +		switch (curByte) { +		case 0xf1: +			draw_SetColor();  			_scrOn = true; +			_priOn = false;  			break; -		case 0xf1: -			if (_pictureVersion == AGIPIC_V1) { -				_scrColor = nextByte(); -				_scrColor &= 0xF;	// for v3 drawing diff -				_scrOn = true; -				_priOn = false; -			} else if (_pictureVersion == AGIPIC_V15) {	// set color on screen -				_scrColor = nextByte(); -				_scrColor &= 0xF; -				_scrOn = true; -			} else if (_pictureVersion == AGIPIC_V2) {	// disable screen drawing -				_scrOn = false; -			} +		case 0xf3: +			draw_SetColor(); +			_scrOn = true; +			draw_SetPriority(); +			_priOn = true;  			break; -		case 0xf2:	// set color on priority (AGI pic v2) -			if (_pictureVersion == AGIPIC_V15) -				break; - -			_priColor = nextByte(); -			_priColor &= 0xf;	// for v3 drawing diff +		case 0xfa: +			_scrOn = false;  			_priOn = true; +			draw_LineAbsolute(); +			_scrOn = true; +			_priOn = false;  			break; -		case 0xf3: -			if (_pictureVersion == AGIPIC_V1) { -				_scrColor = nextByte(); -				_scrColor &= 0xF;	// for v3 drawing diff -				_scrOn = true; -				_priColor = nextByte(); -				_priColor &= 0xf;	// for v3 drawing diff -				_priOn = true; -			} +		case 0xff: // end of data +			return; +		default: +			warning("Unknown picture opcode (%x) at (%x)", curByte, _dataOffset - 1); +			break; +		} +	} +} -			if (_pictureVersion == AGIPIC_V15 && (_flags & kPicFf3Stop)) -				drawing = 0; +void PictureMgr::drawPictureV15() { +	byte curByte; -			if (_pictureVersion == AGIPIC_V2)	// disable priority screen -				_priOn = false; -			break; -		case 0xf4:	// y-corner -			if (_pictureVersion == AGIPIC_V15) -				break; +	debugC(8, kDebugLevelMain, "Drawing V1.5 picture"); -			yCorner(); -			break; -		case 0xf5:	// x-corner -			if (_pictureVersion == AGIPIC_V15) -				break; +	while (_dataOffset < _dataSize) { +		curByte = getNextByte(); -			xCorner(); +		switch (curByte) { +		case 0xf0: +			// happens in all Troll's Tale pictures +			// TODO: figure out what it was meant for  			break; -		case 0xf6:	// absolute draw lines -			if (_pictureVersion == AGIPIC_V15) -				break; - -			absoluteDrawLine(); +		case 0xf1: +			draw_SetColor(); +			_scrOn = true;  			break; -		case 0xf7:	// dynamic draw lines -			if (_pictureVersion == AGIPIC_V15) -				break; - -			dynamicDrawLine(); +		case 0xf3: +			if (_flags & kPicFf3Stop) +				return;  			break; -		case 0xf8:	// fill -			if (_pictureVersion == AGIPIC_V15) { -				yCorner(true); -			} else if (_pictureVersion == AGIPIC_V2) { -				fill(); -			} +		case 0xf8: +			yCorner(true);  			break; -		case 0xf9:	// set pattern -			if (_pictureVersion == AGIPIC_V15) { -				xCorner(true); -			} else if (_pictureVersion == AGIPIC_V2) { -				_patCode = nextByte(); +		case 0xf9: +			draw_xCorner(true); +			break; +		case 0xfa: +			// TODO: is this really correct? +			draw_LineAbsolute(); +			break; +		case 0xfb: +			// TODO: is this really correct? +			draw_LineAbsolute(); +			break; +		case 0xfe: +			draw_SetColor(); +			_scrOn = true; +			draw_Fill(); +			break; +		case 0xff: // end of data +			return; +		default: +			warning("Unknown picture opcode (%x) at (%x)", curByte, _dataOffset - 1); +			break; +		} +	} +} -				if (_vm->getGameType() == GType_PreAGI) -					plotBrush(); +void PictureMgr::drawPictureV2() { +	byte curByte; +	bool nibbleMode = false; +	bool mickeyCrystalAnimation = false; +	int  mickeyIteration = 0; + +	debugC(8, kDebugLevelMain, "Drawing V2/V3 picture"); + +	if (_vm->_game.dirPic[_resourceNr].flags & RES_PICTURE_V3_NIBBLE_PARM) { +		// check, if this resource uses nibble mode (0xF0 + 0xF2 commands take nibbles instead of bytes) +		nibbleMode = true; +	} + +	if ((_flags & kPicFStep) && _vm->getGameType() == GType_PreAGI) { +		mickeyCrystalAnimation = true; +	} + +	while (_dataOffset < _dataSize) { +		curByte = getNextByte(); + +		switch (curByte) { +		case 0xf0: +			if (!nibbleMode) { +				draw_SetColor(); +			} else { +				draw_SetNibbleColor();  			} +			_scrOn = true;  			break; -		case 0xfa:	// plot brush -			if (_pictureVersion == AGIPIC_V1) { -				_scrOn = false; -				_priOn = true; -				absoluteDrawLine(); -				_scrOn = true; -				_priOn = false; -			} else if (_pictureVersion == AGIPIC_V15) { -				absoluteDrawLine(); -			} else if (_pictureVersion == AGIPIC_V2) { -				plotBrush(); -			} +		case 0xf1: +			_scrOn = false;  			break; -		case 0xfb: -			if (_pictureVersion == AGIPIC_V1) { -				dynamicDrawLine(); -			} else if (_pictureVersion == AGIPIC_V15) { -				absoluteDrawLine(); +		case 0xf2: +			if (!nibbleMode) { +				draw_SetPriority(); +			} else { +				draw_SetNibblePriority();  			} +			_priOn = true;  			break; -		case 0xfc:	// fill (AGI pic v1) -			if (_pictureVersion == AGIPIC_V15) -				break; +		case 0xf3: +			_priOn = false; +			break; +		case 0xf4: +			yCorner(); +			break; +		case 0xf5: +			draw_xCorner(); +			break; +		case 0xf6: +			draw_LineAbsolute(); +			break; +		case 0xf7: +			draw_LineShort(); +			break; +		case 0xf8: +			draw_Fill(); +			break; +		case 0xf9: +			_patCode = getNextByte(); -			_scrColor = nextByte(); -			_scrColor &= 0xF; -			_priColor = nextByte(); -			_priColor &= 0xf; -			fill(); +			if (_vm->getGameType() == GType_PreAGI) +				plotBrush();  			break; -		case 0xfe:	// fill (AGI pic v1.5) -			_scrColor = nextByte(); -			_scrColor &= 0xF; -			_scrOn = true; -			fill(); +		case 0xfa: +			plotBrush();  			break; -		case 0xff:	// end of pic data -			drawing = 0; +		case 0xfc: +			draw_SetColor(); +			draw_SetPriority(); +			draw_Fill();  			break; +		case 0xff: // end of data +			return;  		default: -			warning("Unknown picture opcode (%x) at (%x)", act, _foffs - 1); +			warning("Unknown picture opcode (%x) at (%x)", curByte, _dataOffset - 1); +			break;  		}  		// This is used by Mickey for the crystal animation  		// One frame of the crystal animation is shown on each iteration, based on _currentStep -		if ((_flags & kPicFStep) && _vm->getGameType() == GType_PreAGI && _currentStep == iteration) { -			int storedXOffset = _xOffset; -			int storedYOffset = _yOffset; -			// Note that picture coordinates are correct for Mickey only -			showPic(10, 0, _width, _height); -			_xOffset = storedXOffset; -			_yOffset = storedYOffset; -			_currentStep++; -			if (_currentStep > 14)	// crystal animation is 15 frames -				_currentStep = 0; -			// reset the picture step flag - it will be set when the next frame of the crystal animation is drawn -			_flags &= ~kPicFStep; -			return;		// return back to the game loop +		if (mickeyCrystalAnimation) { +			if (_currentStep == mickeyIteration) { +				int storedXOffset = _xOffset; +				int storedYOffset = _yOffset; +				// Note that picture coordinates are correct for Mickey only +				showPic(10, 0, _width, _height); +				_xOffset = storedXOffset; +				_yOffset = storedYOffset; +				_currentStep++; +				if (_currentStep > 14)	// crystal animation is 15 frames +					_currentStep = 0; +				// reset the picture step flag - it will be set when the next frame of the crystal animation is drawn +				_flags &= ~kPicFStep; +				return;		// return back to the game loop +			} +			mickeyIteration++; +		} +	} +} + +void PictureMgr::drawPictureAGI256() { +	const uint32 maxFlen = _width * _height; +	int16 x = 0; +	int16 y = 0; +	byte *dataPtr = _data; +	byte *dataEndPtr = _data + _dataSize; +	byte color = 0; + +	debugC(8, kDebugLevelMain, "Drawing AGI256 picture"); + +	while (dataPtr < dataEndPtr) { +		color = *dataPtr++; +		_gfx->putPixel(x, y, GFX_SCREEN_MASK_VISUAL, color, 0); + +		x++; +		if (x >= _width) { +			x = 0; +			y++; +			if (y >= _height) { +				break; +			} +		} +	} + +	if (_dataSize < maxFlen) { +		warning("Undersized AGI256 picture resource %d, using it anyway. Filling rest with white.", _resourceNr); +		while (_dataSize < maxFlen) { +			x++; +			if (x >= _width) { +				x = 0; +				y++; +				if (y >= _height) +					break; +			} +			_gfx->putPixel(x, y, GFX_SCREEN_MASK_VISUAL, 15, 0);  		} +	} else if (_dataSize > maxFlen) +		warning("Oversized AGI256 picture resource %d, decoding only %ux%u part of it", _resourceNr, _width, _height); +} + +void PictureMgr::draw_SetColor() { +	_scrColor = getNextByte(); + +	// For CGA, replace the color with its mixture color +	switch (_vm->_renderMode) { +	case RENDERMODE_CGA: +		_scrColor = _gfx->getCGAMixtureColor(_scrColor); +		break; +	default: +		break; +	} +} + +void PictureMgr::draw_SetPriority() { +	_priColor = getNextByte(); +} -		iteration++; +// this gets a nibble instead of a full byte +// used by some V3 games, special resource flag RES_PICTURE_V3_NIBBLE_PARM is set +void PictureMgr::draw_SetNibbleColor() { +	_scrColor = getNextNibble(); + +	// For CGA, replace the color with its mixture color +	switch (_vm->_renderMode) { +	case RENDERMODE_CGA: +		_scrColor = _gfx->getCGAMixtureColor(_scrColor); +		break; +	default: +		break;  	}  } +void PictureMgr::draw_SetNibblePriority() { +	_priColor = getNextNibble(); +} +  /** - * convert AGI v3 format picture to AGI v2 format + * Draw an AGI line. + * A line drawing routine sent by Joshua Neal, modified by Stuart George + * (fixed >>2 to >>1 and some other bugs like x1 instead of y1, etc.) + * @param x1  x coordinate of start point + * @param y1  y coordinate of start point + * @param x2  x coordinate of end point + * @param y2  y coordinate of end point   */ -uint8 *PictureMgr::convertV3Pic(uint8 *src, uint32 len) { -	uint8 d, old = 0, x, *in, *xdata, *out, mode = 0; -	uint32 i, ulen; +void PictureMgr::draw_Line(int16 x1, int16 y1, int16 x2, int16 y2) { +	x1 = CLIP<int16>(x1, 0, _width - 1); +	x2 = CLIP<int16>(x2, 0, _width - 1); +	y1 = CLIP<int16>(y1, 0, _height - 1); +	y2 = CLIP<int16>(y2, 0, _height - 1); + +	int i, x, y, deltaX, deltaY, stepX, stepY, errorX, errorY, detdelta; -	xdata = (uint8 *)malloc(len + len / 2); +	// Vertical line + +	if (x1 == x2) { +		if (y1 > y2) { +			SWAP(y1, y2); +		} -	out = xdata; -	in = src; +		for (; y1 <= y2; y1++) +			putVirtPixel(x1, y1); -	for (i = ulen = 0; i < len; i++, ulen++) { -		d = *in++; +		return; +	} -		*out++ = x = mode ? ((d & 0xF0) >> 4) + ((old & 0x0F) << 4) : d; +	// Horizontal line -		if (x == 0xFF) { -			ulen++; -			break; +	if (y1 == y2) { +		if (x1 > x2) { +			SWAP(x1, x2); +		} +		for (; x1 <= x2; x1++) +			putVirtPixel(x1, y1); +		return; +	} + +	y = y1; +	x = x1; + +	stepY = 1; +	deltaY = y2 - y1; +	if (deltaY < 0) { +		stepY = -1; +		deltaY = -deltaY; +	} + +	stepX = 1; +	deltaX = x2 - x1; +	if (deltaX < 0) { +		stepX = -1; +		deltaX = -deltaX; +	} + +	if (deltaY > deltaX) { +		i = deltaY; +		detdelta = deltaY; +		errorX = deltaY / 2; +		errorY = 0; +	} else { +		i = deltaX; +		detdelta = deltaX; +		errorX = 0; +		errorY = deltaX / 2; +	} + +	putVirtPixel(x, y); + +	do { +		errorY += deltaY; +		if (errorY >= detdelta) { +			errorY -= detdelta; +			y += stepY;  		} -		if (x == 0xf0 || x == 0xf2) { -			if (mode) { -				*out++ = d & 0x0F; -				ulen++; +		errorX += deltaX; +		if (errorX >= detdelta) { +			errorX -= detdelta; +			x += stepX; +		} + +		putVirtPixel(x, y); +		i--; +	} while (i > 0); +} + +/** + * Draw a relative AGI line. + * Draws short lines relative to last position. (drawing action 0xF7) + */ +void PictureMgr::draw_LineShort() { +	int x1, y1, disp, dx, dy; + +	if ((x1 = getNextByte()) >= _minCommand || +		(y1 = getNextByte()) >= _minCommand) { +		_dataOffset--; +		return; +	} + +	putVirtPixel(x1, y1); + +	for (;;) { +		if ((disp = getNextByte()) >= _minCommand) +			break; + +		dx = ((disp & 0xf0) >> 4) & 0x0f; +		dy = (disp & 0x0f); + +		if (dx & 0x08) +			dx = -(dx & 0x07); +		if (dy & 0x08) +			dy = -(dy & 0x07); + +		draw_Line(x1, y1, x1 + dx, y1 + dy); +		x1 += dx; +		y1 += dy; +	} +	_dataOffset--; +} + +/************************************************************************** +** absoluteLine +** +** Draws long lines to actual locations (cf. relative) (drawing action 0xF6) +**************************************************************************/ +void PictureMgr::draw_LineAbsolute() { +	int16 x1, y1, x2, y2; + +	if ((x1 = getNextByte()) >= _minCommand || +		(y1 = getNextByte()) >= _minCommand) { +		_dataOffset--; +		return; +	} + +	putVirtPixel(x1, y1); + +	for (;;) { +		if ((x2 = getNextByte()) >= _minCommand) +			break; + +		if ((y2 = getNextByte()) >= _minCommand) +			break; + +		draw_Line(x1, y1, x2, y2); +		x1 = x2; +		y1 = y2; +	} +	_dataOffset--; +} + +// flood fill +void PictureMgr::draw_Fill() { +	int16 x1, y1; + +	while ((x1 = getNextByte()) < _minCommand && (y1 = getNextByte()) < _minCommand) +		draw_Fill(x1, y1); + +	_dataOffset--; +} + +void PictureMgr::draw_Fill(int16 x, int16 y) { +	if (!_scrOn && !_priOn) +		return; + +	// Push initial pixel on the stack +	Common::Stack<Common::Point> stack; +	stack.push(Common::Point(x,y)); + +	// Exit if stack is empty +	while (!stack.empty()) { +		Common::Point p = stack.pop(); +		unsigned int c; +		bool newspanUp, newspanDown; + +		if (!draw_FillCheck(p.x, p.y)) +			continue; + +		// Scan for left border +		for (c = p.x - 1; draw_FillCheck(c, p.y); c--) +			; + +		newspanUp = newspanDown = true; +		for (c++; draw_FillCheck(c, p.y); c++) { +			putVirtPixel(c, p.y); +			if (draw_FillCheck(c, p.y - 1)) { +				if (newspanUp) { +					stack.push(Common::Point(c,p.y-1)); +					newspanUp = false; +				}  			} else { -				d = *in++; -				*out++ = (d & 0xF0) >> 4; -				i++, ulen++; +				newspanUp = true;  			} -			mode = !mode; +			if (draw_FillCheck(c, p.y + 1)) { +				if (newspanDown) { +					stack.push(Common::Point(c,p.y+1)); +					newspanDown = false; +				} +			} else { +				newspanDown = true; +			}  		} - -		old = d;  	} +} -	free(src); -	xdata = (uint8 *)realloc(xdata, ulen); +int PictureMgr::draw_FillCheck(int16 x, int16 y) { +	byte screenColor; +	byte screenPriority; -	return xdata; +	if (x < 0 || x >= _width || y < 0 || y >= _height) +		return false; + +	x += _xOffset; +	y += _yOffset; + +	screenColor = _gfx->getColor(x, y); +	screenPriority = _gfx->getPriority(x, y); + +	if (_flags & kPicFTrollMode) +		return ((screenColor != 11) && (screenColor != _scrColor)); + +	if (!_priOn && _scrOn && _scrColor != 15) +		return (screenColor == 15); + +	if (_priOn && !_scrOn && _priColor != 4) +		return screenPriority == 4; + +	return (_scrOn && screenColor == 15 && _scrColor != 15);  }  /** @@ -804,8 +906,8 @@ uint8 *PictureMgr::convertV3Pic(uint8 *src, uint32 len) {   * @param clear  clear AGI screen before drawing   * @param agi256 load an AGI256 picture resource   */ -int PictureMgr::decodePicture(int n, int clr, bool agi256, int pic_width, int pic_height) { -	debugC(8, kDebugLevelResources, "(%d)", n); +int PictureMgr::decodePicture(int16 resourceNr, bool clear, bool agi256, int16 pic_width, int16 pic_height) { +	debugC(8, kDebugLevelResources, "(%d)", resourceNr);  	_patCode = 0;  	_patNum = 0; @@ -813,32 +915,28 @@ int PictureMgr::decodePicture(int n, int clr, bool agi256, int pic_width, int pi  	_scrColor = 0xF;  	_priColor = 0x4; -	_data = _vm->_game.pictures[n].rdata; -	_flen = _vm->_game.dirPic[n].len; -	_foffs = 0; +	_resourceNr = resourceNr; +	_data = _vm->_game.pictures[resourceNr].rdata; +	_dataSize = _vm->_game.dirPic[resourceNr].len; +	_dataOffset = 0; +	_dataOffsetNibble = false;  	_width = pic_width;  	_height = pic_height; -	if (clr && !agi256) // 256 color pictures should always fill the whole screen, so no clearing for them. -		memset(_vm->_game.sbuf16c, 0x4f, _width * _height); // Clear 16 color AGI screen (Priority 4, color white). +	if (clear && !agi256) { // 256 color pictures should always fill the whole screen, so no clearing for them. +		_gfx->clear(15, 4); // Clear 16 color AGI screen (Priority 4, color white). +	}  	if (!agi256) {  		drawPicture(); // Draw 16 color picture.  	} else { -		const uint32 maxFlen = _width * _height; -		memcpy(_vm->_game.sbuf256c, _data, MIN(_flen, maxFlen)); // Draw 256 color picture. - -		if (_flen < maxFlen) { -			warning("Undersized AGI256 picture resource %d, using it anyway. Filling rest with white.", n); -			memset(_vm->_game.sbuf256c + _flen, 0x0f, maxFlen - _flen); // Fill missing area with white. -		} else if (_flen > maxFlen) -			warning("Oversized AGI256 picture resource %d, decoding only %ux%u part of it", n, _width, _height); +		drawPictureAGI256();  	} -	if (clr) +	if (clear)  		_vm->clearImageStack(); -	_vm->recordImageStackCall(ADD_PIC, n, clr, agi256, 0, 0, 0, 0); +	_vm->recordImageStackCall(ADD_PIC, resourceNr, clear, agi256, 0, 0, 0, 0);  	return errOK;  } @@ -860,14 +958,15 @@ int PictureMgr::decodePicture(byte* data, uint32 length, int clr, int pic_width,  	_priColor = 0x4;  	_data = data; -	_flen = length; -	_foffs = 0; +	_dataSize = length; +	_dataOffset = 0; +	_dataOffsetNibble = false;  	_width = pic_width;  	_height = pic_height;  	if (clr) // 256 color pictures should always fill the whole screen, so no clearing for them. -		memset(_vm->_game.sbuf16c, 0x4f, _width * _height); // Clear 16 color AGI screen (Priority 4, color white). +		clear();  	drawPicture(); // Draw 16 color picture. @@ -891,29 +990,66 @@ int PictureMgr::unloadPicture(int n) {  }  void PictureMgr::clear() { -	memset(_vm->_game.sbuf16c, 0x4f, _width * _height); +	_gfx->clear(15, 4); // Clear 16 color AGI screen (Priority 4, color white). +} + +void PictureMgr::showPic() { +	debugC(8, kDebugLevelMain, "Show picture!"); + +	_gfx->render_Block(0, 167, SCRIPT_WIDTH, SCRIPT_HEIGHT);  }  /**   * Show AGI picture.   * This function copies a ``hidden'' AGI picture to the output device.   */ -void PictureMgr::showPic(int x, int y, int pic_width, int pic_height) { -	int i, y1; -	int offset; +void PictureMgr::showPic(int16 x, int16 y, int16 pic_width, int16 pic_height) {  	_width = pic_width;  	_height = pic_height;  	debugC(8, kDebugLevelMain, "Show picture!"); -	i = 0; -	offset = _vm->_game.lineMinPrint * CHAR_LINES; -	for (y1 = y; y1 < y + _height; y1++) { -		_gfx->putPixelsA(x, y1 + offset, _width, &_vm->_game.sbuf16c[i]); -		i += _width; +	// render block requires lower left coordinate! +	_gfx->render_Block(x, pic_height + y - 1, pic_width, pic_height); +} + +void PictureMgr::showPicWithTransition() { +	_width = SCRIPT_WIDTH; +	_height = SCRIPT_HEIGHT; + +	debugC(8, kDebugLevelMain, "Show picture!"); + +	if (!_vm->_game.automaticRestoreGame) { +		// only do transitions when we are not restoring a saved game + +		if (!_vm->_game.gfxMode) { +			// if we are not yet in graphics mode, set graphics mode palette now +			// TODO: maybe change text mode to use different colors for drawing +			// so that we don't have to change palettes at all +			_gfx->setPalette(true); +		} + +		switch (_vm->_renderMode) { +		case RENDERMODE_AMIGA: +		case RENDERMODE_APPLE_II_GS: +			// Platform Amiga/Apple II GS -> render and do Amiga transition +			_gfx->render_Block(0, 167, SCRIPT_WIDTH, SCRIPT_HEIGHT, false); +			_gfx->transition_Amiga(); +			return; +			break; +		case RENDERMODE_ATARI_ST: +			// Platform Atari ST used a different transition, looks "high-res" (full 320x168) +			_gfx->render_Block(0, 167, SCRIPT_WIDTH, SCRIPT_HEIGHT, false); +			_gfx->transition_AtariSt(); +			return; +		default: +			// Platform PC -> render directly +			// Macintosh AGI also doesn't seem to have any transitions +			break; +		}  	} -	_gfx->flushScreen(); +	_gfx->render_Block(0, 167, SCRIPT_WIDTH, SCRIPT_HEIGHT);  }  // preagi needed functions (for plotPattern) @@ -933,8 +1069,9 @@ void PictureMgr::setPictureVersion(AgiPictureVersion version) {  void PictureMgr::setPictureData(uint8 *data, int len) {  	_data = data; -	_flen = len; -	_foffs = 0; +	_dataSize = len; +	_dataOffset = 0; +	_dataOffsetNibble = false;  	_flags = 0;  } diff --git a/engines/agi/picture.h b/engines/agi/picture.h index 9ff1e742f4..b1a272bedb 100644 --- a/engines/agi/picture.h +++ b/engines/agi/picture.h @@ -41,7 +41,8 @@ enum AgiPictureVersion {  	AGIPIC_C64,  	AGIPIC_V1,  	AGIPIC_V15, -	AGIPIC_V2 +	AGIPIC_V2, +	AGIPIC_256  };  enum AgiPictureFlags { @@ -60,31 +61,49 @@ class PictureMgr {  	AgiBase *_vm;  	GfxMgr *_gfx; -private: +public: +	PictureMgr(AgiBase *agi, GfxMgr *gfx); -	void drawLine(int x1, int y1, int x2, int y2); -	void dynamicDrawLine(); -	void absoluteDrawLine(); -	int isOkFillHere(int x, int y); -	void agiFill(unsigned int x, unsigned int y); -	void xCorner(bool skipOtherCoords = false); +private: +	void draw_xCorner(bool skipOtherCoords = false);  	void yCorner(bool skipOtherCoords = false); -	void fill();  	int plotPatternPoint(int x, int y, int bitpos);  	void plotBrush(); -	uint8 nextByte() { return _data[_foffs++]; } +	byte getNextByte(); +	byte getNextNibble();  public: -	PictureMgr(AgiBase *agi, GfxMgr *gfx); -  	void putVirtPixel(int x, int y); -	int decodePicture(int n, int clear, bool agi256 = false, int pic_width = _DEFAULT_WIDTH, int pic_height = _DEFAULT_HEIGHT); +	int decodePicture(int16 resourceNr, bool clear, bool agi256 = false, int16 pic_width = _DEFAULT_WIDTH, int16 pic_height = _DEFAULT_HEIGHT);  	int decodePicture(byte* data, uint32 length, int clear, int pic_width = _DEFAULT_WIDTH, int pic_height = _DEFAULT_HEIGHT);  	int unloadPicture(int);  	void drawPicture(); -	void showPic(int x = 0, int y = 0, int pic_width = _DEFAULT_WIDTH, int pic_height = _DEFAULT_HEIGHT); +private: +	void drawPictureC64(); +	void drawPictureV1(); +	void drawPictureV15(); +	void drawPictureV2(); +	void drawPictureAGI256(); + +	void draw_SetColor(); +	void draw_SetPriority(); +	void draw_SetNibbleColor(); +	void draw_SetNibblePriority(); + +	void draw_Line(int16 x1, int16 y1, int16 x2, int16 y2); +	void draw_LineShort(); +	void draw_LineAbsolute(); + +	int  draw_FillCheck(int16 x, int16 y); +	void draw_Fill(int16 x, int16 y); +	void draw_Fill(); + +public: +	void showPic(); // <-- for regular AGI games +	void showPic(int16 x, int16 y, int16 pic_width, int16 pic_height); // <-- for preAGI games +	void showPicWithTransition();  	uint8 *convertV3Pic(uint8 *src, uint32 len);  	void plotPattern(int x, int y);		// public because it's used directly by preagi @@ -108,19 +127,12 @@ public:  		_height = h;  	} -	void putPixel(int x, int y, uint8 color) { -		_scrColor = color; -		_priOn = false; -		_scrOn = true; -		putVirtPixel(x, y); -	} - -	bool isPictureLoaded() { return _data != NULL; } -  private: +	int16  _resourceNr;  	uint8 *_data; -	uint32 _flen; -	uint32 _foffs; +	uint32 _dataSize; +	uint32 _dataOffset; +	bool   _dataOffsetNibble;  	uint8 _patCode;  	uint8 _patNum; @@ -132,8 +144,8 @@ private:  	uint8 _minCommand;  	AgiPictureVersion _pictureVersion; -	int _width, _height; -	int _xOffset, _yOffset; +	int16 _width, _height; +	int16 _xOffset, _yOffset;  	int _flags;  	int _currentStep; diff --git a/engines/agi/preagi.cpp b/engines/agi/preagi.cpp index c368c7b195..c0a53e7608 100644 --- a/engines/agi/preagi.cpp +++ b/engines/agi/preagi.cpp @@ -29,6 +29,7 @@  #include "agi/preagi.h"  #include "agi/graphics.h"  #include "agi/keyboard.h" +#include "agi/text.h"  namespace Agi { @@ -55,32 +56,22 @@ PreAgiEngine::PreAgiEngine(OSystem *syst, const AGIGameDescription *gameDesc) :  void PreAgiEngine::initialize() {  	initRenderMode(); +	initFont();  	_gfx = new GfxMgr(this);  	_picture = new PictureMgr(this, _gfx); -	if (getGameID() == GID_MICKEY) { -		_fontData = fontData_Mickey; -	} else { -		_fontData = fontData_IBM; -	} -  	_gfx->initMachine();  	_game.gameFlags = 0; -	_game.colorFg = 15; -	_game.colorBg = 0; +	//_game._vm->_text->charAttrib_Set(15, 0);  	_defaultColor = 0xF;  	_game.name[0] = '\0'; -	_game.sbufOrig = (uint8 *)calloc(_WIDTH, _HEIGHT * 2); // Allocate space for two AGI screens vertically -	_game.sbuf16c  = _game.sbufOrig + SBUF16_OFFSET; // Make sbuf16c point to the 16 color (+control line & priority info) AGI screen -	_game.sbuf     = _game.sbuf16c; // Make sbuf point to the 16 color (+control line & priority info) AGI screen by default - -	_game.lineMinPrint = 0; // hardcoded +	//_game._vm->_text->configureScreen(0); // hardcoded  	_gfx->initVideo(); @@ -91,7 +82,7 @@ void PreAgiEngine::initialize() {  	debugC(2, kDebugLevelMain, "Detect game");  	// clear all resources and events -	for (int i = 0; i < MAX_DIRS; i++) { +	for (int i = 0; i < MAX_DIRECTORY_ENTRIES; i++) {  		memset(&_game.pictures[i], 0, sizeof(struct AgiPicture));  		memset(&_game.sounds[i], 0, sizeof(class AgiSound *)); // _game.sounds contains pointers now  		memset(&_game.dirPic[i], 0, sizeof(struct AgiDir)); @@ -113,11 +104,11 @@ void PreAgiEngine::clearScreen(int attr, bool overrideDefault) {  	if (overrideDefault)  		_defaultColor = attr; -	_gfx->clearScreen((attr & 0xF0) / 0x10); +	_gfx->clearDisplay((attr & 0xF0) / 0x10);  }  void PreAgiEngine::clearGfxScreen(int attr) { -	_gfx->drawRectangle(0, 0, GFX_WIDTH - 1, IDI_MAX_ROW_PIC * 8 -1, (attr & 0xF0) / 0x10); +	_gfx->drawDisplayRect(0, 0, GFX_WIDTH - 1, IDI_MAX_ROW_PIC * 8 -1, (attr & 0xF0) / 0x10);  }  // String functions @@ -143,7 +134,7 @@ void PreAgiEngine::drawStr(int row, int col, int attr, const char *buffer) {  			break;  		default: -			_gfx->putTextCharacter(1, col * 8 , row * 8, static_cast<char>(code), attr & 0x0f, (attr & 0xf0) / 0x10, false, _fontData); +			_gfx->drawCharacter(row, col, code, attr & 0x0f, attr >> 4, false);  			if (++col == 320 / 8) {  				col = 0; @@ -176,7 +167,7 @@ void PreAgiEngine::clearRow(int row) {  void PreAgiEngine::printStr(const char* szMsg) {  	clearTextArea();  	drawStr(21, 0, IDA_DEFAULT, szMsg); -	_gfx->doUpdate(); +	g_system->updateScreen();  }  void PreAgiEngine::XOR80(char *buffer) { @@ -274,7 +265,7 @@ void PreAgiEngine::waitForTimer(int msec_delay) {  	uint32 start_time = _system->getMillis();  	while (_system->getMillis() < start_time + msec_delay) { -		_gfx->doUpdate(); +		g_system->updateScreen();  		_system->delayMillis(10);  	}  } diff --git a/engines/agi/preagi_mickey.cpp b/engines/agi/preagi_mickey.cpp index 883107a957..02c48b090b 100644 --- a/engines/agi/preagi_mickey.cpp +++ b/engines/agi/preagi_mickey.cpp @@ -149,7 +149,7 @@ void MickeyEngine::printStr(char *buffer) {  	}  	// Show the string on screen -	_gfx->doUpdate(); +	_gfx->updateScreen();  }  void MickeyEngine::printLine(const char *buffer) { @@ -158,7 +158,7 @@ void MickeyEngine::printLine(const char *buffer) {  	drawStr(22, 18 - strlen(buffer) / 2, IDA_DEFAULT, buffer);  	// Show the string on screen -	_gfx->doUpdate(); +	_gfx->updateScreen();  	waitAnyKey(true);  } @@ -281,7 +281,7 @@ void MickeyEngine::drawMenu(MSA_MENU menu, int sel0, int sel1) {  	}  	// Menu created, show it on screen -	_gfx->doUpdate(); +	_gfx->updateScreen();  }  void MickeyEngine::getMouseMenuSelRow(MSA_MENU menu, int *sel0, int *sel1, int iRow, int x, int y) { @@ -373,18 +373,19 @@ bool MickeyEngine::getMenuSelRow(MSA_MENU menu, int *sel0, int *sel1, int iRow)  					// Change cursor  					if (northIndex >= 0 && (event.mouse.x >= 20 && event.mouse.x <= (IDI_MSA_PIC_WIDTH + 10) * 2) &&  							(event.mouse.y >= 0 && event.mouse.y <= 10)) { -						_gfx->setCursorPalette(true); +						//_gfx->setCursorPalette(true); +						// TODO:?????  					} else if (southIndex >= 0 && (event.mouse.x >= 20 && event.mouse.x <= (IDI_MSA_PIC_WIDTH + 10) * 2) &&  								(event.mouse.y >= IDI_MSA_PIC_HEIGHT - 10 && event.mouse.y <= IDI_MSA_PIC_HEIGHT)) { -						_gfx->setCursorPalette(true); +						//_gfx->setCursorPalette(true);  					} else if (westIndex >= 0 && (event.mouse.y >= 0  && event.mouse.y <= IDI_MSA_PIC_HEIGHT) &&  								(event.mouse.x >= 20 && event.mouse.x <= 30)) { -						_gfx->setCursorPalette(true); +						//_gfx->setCursorPalette(true);  					} else if (eastIndex >= 0 && (event.mouse.y >= 0  && event.mouse.y <= IDI_MSA_PIC_HEIGHT) &&  								(event.mouse.x >= IDI_MSA_PIC_WIDTH * 2 && event.mouse.x <= (IDI_MSA_PIC_WIDTH + 10) * 2)) { -						_gfx->setCursorPalette(true); +						//_gfx->setCursorPalette(true);  					} else { -						_gfx->setCursorPalette(false); +						//_gfx->setCursorPalette(false);  					}  				}  				break; @@ -397,7 +398,8 @@ bool MickeyEngine::getMenuSelRow(MSA_MENU menu, int *sel0, int *sel1, int iRow)  					drawMenu(menu, *sel0, *sel1); -					_gfx->setCursorPalette(false); +					//_gfx->setCursorPalette(false); +					// TODO???  					_clickToMove = true;  				} else if (southIndex >= 0 && (event.mouse.x >= 20 && event.mouse.x <= (IDI_MSA_PIC_WIDTH + 10) * 2) &&  							(event.mouse.y >= IDI_MSA_PIC_HEIGHT - 10 && event.mouse.y <= IDI_MSA_PIC_HEIGHT)) { @@ -406,7 +408,8 @@ bool MickeyEngine::getMenuSelRow(MSA_MENU menu, int *sel0, int *sel1, int iRow)  					drawMenu(menu, *sel0, *sel1); -					_gfx->setCursorPalette(false); +					//_gfx->setCursorPalette(false); +					// TODO???  					_clickToMove = true;  				} else if (westIndex >= 0 && (event.mouse.y >= 0  && event.mouse.y <= IDI_MSA_PIC_HEIGHT) &&  							(event.mouse.x >= 20 && event.mouse.x <= 30)) { @@ -415,7 +418,8 @@ bool MickeyEngine::getMenuSelRow(MSA_MENU menu, int *sel0, int *sel1, int iRow)  					drawMenu(menu, *sel0, *sel1); -					_gfx->setCursorPalette(false); +					//_gfx->setCursorPalette(false); +					// TODO???  					_clickToMove = true;  				} else if (eastIndex >= 0 && (event.mouse.y >= 0  && event.mouse.y <= IDI_MSA_PIC_HEIGHT) &&  							(event.mouse.x >= IDI_MSA_PIC_WIDTH * 2 && event.mouse.x <= (IDI_MSA_PIC_WIDTH + 10) * 2)) { @@ -424,10 +428,12 @@ bool MickeyEngine::getMenuSelRow(MSA_MENU menu, int *sel0, int *sel1, int iRow)  					drawMenu(menu, *sel0, *sel1); -					_gfx->setCursorPalette(false); +					//_gfx->setCursorPalette(false); +					// TODO???  					_clickToMove = true;  				} else { -					_gfx->setCursorPalette(false); +					//_gfx->setCursorPalette(false); +					// TODO???  				}  				return true;  			case Common::EVENT_RBUTTONUP: @@ -486,7 +492,7 @@ bool MickeyEngine::getMenuSelRow(MSA_MENU menu, int *sel0, int *sel1, int iRow)  					return false;  				case Common::KEYCODE_s: -					flipflag(fSoundOn); +					flipflag(VM_FLAG_SOUND_ON);  					break;  				case Common::KEYCODE_c:  					inventory(); @@ -666,7 +672,7 @@ void MickeyEngine::playNote(MSA_SND_NOTE note) {  }  void MickeyEngine::playSound(ENUM_MSA_SOUND iSound) { -	if (!getflag(fSoundOn)) +	if (!getflag(VM_FLAG_SOUND_ON))  		return;  	Common::Event event; @@ -755,7 +761,9 @@ void MickeyEngine::drawPic(int iPic) {  	file.close();  	// Note that decodePicture clears the screen +	_picture->setOffset(10, 0);  	_picture->decodePicture(buffer, size, true, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT); +	_picture->setOffset(0, 0);  	_picture->showPic(10, 0, IDI_MSA_PIC_WIDTH, IDI_MSA_PIC_HEIGHT);  } @@ -892,75 +900,65 @@ void MickeyEngine::drawRoom() {  	drawRoomAnimation();  } -#if 0 -const uint8 colorBCG[16][2] = { -	{ 0x00,	0x00 },	// 0 (black, black) -	{ 0, 0 }, -	{ 0x00,	0x0D },	// 2 (black, purple) -	{ 0x00,	0xFF },	// 3 (black, white) -	{ 0, 0 }, -	{ 0, 0 }, -	{ 0, 0 }, -	{ 0, 0 }, -	{ 0x0D,	0x00 },	// 8 (purple, black) -	{ 0, 0 }, -	{ 0x0D,	0x0D },	// A (purple, purple) -	{ 0, 0 }, -	{ 0xFF,	0x00 },	// C (white, black) -	{ 0, 0 }, -	{ 0, 0 }, -	{ 0xFF,	0xFF }	// F (white, white) +// Straight mapping, CGA colors to CGA +const byte BCGColorMappingCGAToCGA[4] = { +	0, 1, 2, 3 +}; + +// Mapping table to map CGA colors to EGA +const byte BCGColorMappingCGAToEGA[4] = { +	0, 11, 13, 15  }; -#endif  void MickeyEngine::drawLogo() { -	// TODO: clean this up and make it work properly, the logo is drawn way off to the right -#if 0 -	char szFile[256] = {0}; -	uint8 *buffer = new uint8[16384]; -	const int w = 150; -	const int h = 80; -	const int xoffset = 30;	// FIXME: remove this -	uint8 bitmap[w][h]; -	uint8 color, color2, color3, color4; - -	// read in logos.bcg -	sprintf(szFile, IDS_MSA_PATH_LOGO); +	const int width = 80; +	const int height = 85 * 2; +	byte  color1, color2, color3, color4; +	byte  *fileBuffer = nullptr; +	uint32 fileBufferSize = 0; +	byte  *dataBuffer; +	byte   curByte; +	const byte *BCGColorMapping = BCGColorMappingCGAToEGA; + +	// disable color mapping in case we are in CGA mode +	if (_renderMode == RENDERMODE_CGA) +		BCGColorMapping = BCGColorMappingCGAToCGA; + +	// read logos.bcg  	Common::File infile; -	if (!infile.open(szFile)) +	if (!infile.open(IDS_MSA_PATH_LOGO))  		return; -	infile.read(buffer, infile.size()); +	fileBufferSize = infile.size(); +	fileBuffer = new byte[fileBufferSize]; +	infile.read(fileBuffer, fileBufferSize);  	infile.close(); -	// draw logo bitmap -	memcpy(bitmap, buffer, sizeof(bitmap)); - -	_picture->setDimensions(w, h); +	if (fileBufferSize < (width * height / 4)) +		error("logos.bcg: unexpected end of file");  	// Show BCG picture -	for (int y = 0; y < h; y++) { -		for (int x = xoffset; x < w; x++) { -			color  = colorBCG[(bitmap[y][x] & 0xf0) / 0x10][0];	// background -			color2 = colorBCG[(bitmap[y][x] & 0xf0) / 0x10][1];	// background -			color3 = colorBCG[ bitmap[y][x] & 0x0f][0];			// foreground -			color4 = colorBCG[ bitmap[y][x] & 0x0f][1];			// foreground - -			_picture->putPixel(x * 4 - xoffset,			y,		color); -			_picture->putPixel(x * 4 + 1 - xoffset,		y,		color2); -			_picture->putPixel(x * 4 + 2 - xoffset,		y,		color3); -			_picture->putPixel(x * 4 + 3 - xoffset,		y,		color4); -			_picture->putPixel(x * 4 - xoffset,			y + 1,	color); -			_picture->putPixel(x * 4 + 1 - xoffset,		y + 1,	color2); -			_picture->putPixel(x * 4 + 2 - xoffset,		y + 1,	color3); -			_picture->putPixel(x * 4 + 3 - xoffset,		y + 1,	color4); +	// It's basically uncompressed CGA 4-color data (4 pixels per byte) +	dataBuffer = fileBuffer; +	for (int y = 0; y < height; y++) { +		for (int x = 0; x < width; x++) { +			curByte = *dataBuffer++; + +			color1 = BCGColorMapping[(curByte >> 6) & 0x03]; +			color2 = BCGColorMapping[(curByte >> 4) & 0x03]; +			color3 = BCGColorMapping[(curByte >> 2) & 0x03]; +			color4 = BCGColorMapping[(curByte >> 0) & 0x03]; + +			_gfx->putPixelOnDisplay(x * 4 + 0, y, color1); +			_gfx->putPixelOnDisplay(x * 4 + 1, y, color2); +			_gfx->putPixelOnDisplay(x * 4 + 2, y, color3); +			_gfx->putPixelOnDisplay(x * 4 + 3, y, color4);  		}  	} -	_picture->showPic(10, 10, w, h); +	_gfx->copyDisplayRectToScreen(0, 0, DISPLAY_WIDTH, height); -	delete[] buffer; -#endif +	delete[] fileBuffer;  }  void MickeyEngine::animate() { @@ -1221,8 +1219,8 @@ void MickeyEngine::printStory() {  	waitAnyKey();  	//Set back to black -	_gfx->clearScreen(0); -	_gfx->doUpdate(); +	_gfx->clearDisplay(0); +	_gfx->updateScreen();  	drawRoom(); @@ -1399,8 +1397,8 @@ void MickeyEngine::inventory() {  void MickeyEngine::intro() {  	// Draw Sierra logo -	//drawLogo();		// Original does not even show this, so we skip it too -	//waitAnyKey();		// Not in the original, but needed so that the logo is visible +	drawLogo();		// Original does not even show this, so we skip it too +	waitAnyKey();		// Not in the original, but needed so that the logo is visible  	// draw title picture  	_gameStateMickey.iRoom = IDI_MSA_PIC_TITLE; @@ -1448,14 +1446,14 @@ void MickeyEngine::intro() {  		playSound(IDI_MSA_SND_PRESS_BLUE);  		//Set screen to white -		_gfx->clearScreen(15); -		_gfx->doUpdate(); +		_gfx->clearDisplay(15); +		_gfx->updateScreen();  		_system->delayMillis(IDI_MSA_ANIM_DELAY);  		//Set back to black -		_gfx->clearScreen(0); -		_gfx->doUpdate(); +		_gfx->clearDisplay(0); +		_gfx->updateScreen();  		drawRoom();  		printDesc(_gameStateMickey.iRoom); @@ -2201,7 +2199,7 @@ void MickeyEngine::waitAnyKey(bool anim) {  	Common::Event event;  	if (!anim) -		_gfx->doUpdate(); +		_gfx->updateScreen();  	while (!shouldQuit()) {  		while (_system->getEventManager()->pollEvent(event)) { @@ -2219,10 +2217,9 @@ void MickeyEngine::waitAnyKey(bool anim) {  		if (anim) {  			animate(); -			_gfx->doUpdate();  		} -		_system->updateScreen(); +		_gfx->updateScreen();  		_system->delayMillis(10);  	}  } @@ -2300,7 +2297,7 @@ void MickeyEngine::init() {  #endif -	setflag(fSoundOn, true); // enable sound +	setflag(VM_FLAG_SOUND_ON, true); // enable sound  }  Common::Error MickeyEngine::go() { diff --git a/engines/agi/preagi_troll.cpp b/engines/agi/preagi_troll.cpp index 2889407c85..6d82c62987 100644 --- a/engines/agi/preagi_troll.cpp +++ b/engines/agi/preagi_troll.cpp @@ -41,7 +41,7 @@ TrollEngine::~TrollEngine() {  void TrollEngine::pressAnyKey(int col) {  	drawStr(24, col, kColorDefault, IDS_TRO_PRESSANYKEY); -	_gfx->doUpdate(); +	g_system->updateScreen();  	getSelection(kSelAnyKey);  } @@ -49,7 +49,7 @@ void TrollEngine::drawMenu(const char *szMenu, int iSel) {  	clearTextArea();  	drawStr(21, 0, kColorDefault, szMenu);  	drawStr(22 + iSel, 0, kColorDefault, " *"); -	_gfx->doUpdate(); +	g_system->updateScreen();  }  bool TrollEngine::getMenuSel(const char *szMenu, int *iSel, int nSel) { @@ -155,8 +155,8 @@ void TrollEngine::drawPic(int iPic, bool f3IsCont, bool clr, bool troll) {  	_picture->drawPicture(); -	_picture->showPic(); -	_gfx->doUpdate(); +	_picture->showPic(); // TODO: *HAVE* to add coordinates + height/width!! +	g_system->updateScreen();  }  // Game Logic @@ -223,11 +223,11 @@ void TrollEngine::waitAnyKeyIntro() {  			// fall through  		case 0:  			drawStr(22, 3, kColorDefault, IDS_TRO_INTRO_2); -			_gfx->doUpdate(); +			g_system->updateScreen();  			break;  		case 100:  			drawStr(22, 3, kColorDefault, IDS_TRO_INTRO_3); -			_gfx->doUpdate(); +			g_system->updateScreen();  			break;  		} @@ -262,7 +262,7 @@ void TrollEngine::credits() {  	drawStr(17, 7, 12, IDS_TRO_CREDITS_5);  	drawStr(19, 2, 14, IDS_TRO_CREDITS_6); -	_gfx->doUpdate(); +	g_system->updateScreen();  	pressAnyKey();  } @@ -288,11 +288,11 @@ void TrollEngine::tutorial() {  			switch (iSel) {  			case IDI_TRO_SEL_OPTION_1:  				clearScreen(0x22, false); -				_gfx->doUpdate(); +				g_system->updateScreen();  				break;  			case IDI_TRO_SEL_OPTION_2:  				clearScreen(0x00, false); -				_gfx->doUpdate(); +				g_system->updateScreen();  				break;  			case IDI_TRO_SEL_OPTION_3:  				done = true; @@ -304,7 +304,7 @@ void TrollEngine::tutorial() {  		clearScreen(0x4F);  		drawStr(7, 4, kColorDefault, IDS_TRO_TUTORIAL_5);  		drawStr(9, 4, kColorDefault, IDS_TRO_TUTORIAL_6); -		_gfx->doUpdate(); +		g_system->updateScreen();  		if (!getSelection(kSelYesNo))  			break; @@ -314,37 +314,37 @@ void TrollEngine::tutorial() {  	clearScreen(0x5F);  	drawStr(4, 1, kColorDefault, IDS_TRO_TUTORIAL_7);  	drawStr(5, 1, kColorDefault, IDS_TRO_TUTORIAL_8); -	_gfx->doUpdate(); +	g_system->updateScreen();  	pressAnyKey();  	clearScreen(0x2F);  	drawStr(6, 1, kColorDefault, IDS_TRO_TUTORIAL_9); -	_gfx->doUpdate(); +	g_system->updateScreen();  	pressAnyKey();  	clearScreen(0x19);  	drawStr(7, 1, kColorDefault, IDS_TRO_TUTORIAL_10);  	drawStr(8, 1, kColorDefault, IDS_TRO_TUTORIAL_11); -	_gfx->doUpdate(); +	g_system->updateScreen();  	pressAnyKey();  	clearScreen(0x6E);  	drawStr(9, 1, kColorDefault, IDS_TRO_TUTORIAL_12);  	drawStr(10, 1, kColorDefault, IDS_TRO_TUTORIAL_13); -	_gfx->doUpdate(); +	g_system->updateScreen();  	pressAnyKey();  	clearScreen(0x4C);  	drawStr(11, 1, kColorDefault, IDS_TRO_TUTORIAL_14);  	drawStr(12, 1, kColorDefault, IDS_TRO_TUTORIAL_15); -	_gfx->doUpdate(); +	g_system->updateScreen();  	pressAnyKey();  	clearScreen(0x5D);  	drawStr(13, 1, kColorDefault, IDS_TRO_TUTORIAL_16);  	drawStr(14, 1, kColorDefault, IDS_TRO_TUTORIAL_17);  	drawStr(15, 1, kColorDefault, IDS_TRO_TUTORIAL_18); -	_gfx->doUpdate(); +	g_system->updateScreen();  	pressAnyKey();  	// show treasures @@ -353,7 +353,7 @@ void TrollEngine::tutorial() {  	for (int i = 0; i < IDI_TRO_MAX_TREASURE; i++)  		drawStr(19 - i, 11, kColorDefault, _items[i].name); -	_gfx->doUpdate(); +	g_system->updateScreen();  	pressAnyKey();  } @@ -363,7 +363,7 @@ void TrollEngine::intro() {  	clearScreen(0x2F);  	drawStr(9, 10, kColorDefault, IDS_TRO_INTRO_0);  	drawStr(14, 15, kColorDefault, IDS_TRO_INTRO_1); -	_gfx->doUpdate(); +	g_system->updateScreen();  	_system->delayMillis(3200);  	CursorMan.showMouse(true); @@ -371,7 +371,7 @@ void TrollEngine::intro() {  	// Draw logo  	setDefaultTextColor(0x0f);  	drawPic(45, false, true); -	_gfx->doUpdate(); +	g_system->updateScreen();  	// wait for keypress and alternate message  	waitAnyKeyIntro(); @@ -379,7 +379,7 @@ void TrollEngine::intro() {  	// have you played this game before?  	drawStr(22, 3, kColorDefault, IDS_TRO_INTRO_4);  	drawStr(23, 6, kColorDefault, IDS_TRO_INTRO_5); -	_gfx->doUpdate(); +	g_system->updateScreen();  	if (!getSelection(kSelYesNo))  		tutorial(); @@ -411,7 +411,7 @@ void TrollEngine::gameOver() {  	sprintf(szMoves, IDS_TRO_GAMEOVER_0, _moves);  	drawStr(21, 1, kColorDefault, szMoves);  	drawStr(22, 1, kColorDefault, IDS_TRO_GAMEOVER_1); -	_gfx->doUpdate(); +	g_system->updateScreen();  	pressAnyKey();  } @@ -443,7 +443,7 @@ int TrollEngine::drawRoom(char *menu) {  		}  		drawPic(_currentRoom, contFlag, true); -		_gfx->doUpdate(); +		g_system->updateScreen();  		if (_currentRoom == 42) {  			drawPic(44, false, false); // don't clear @@ -454,7 +454,7 @@ int TrollEngine::drawRoom(char *menu) {  		}  	} -	_gfx->doUpdate(); +	g_system->updateScreen();  	char tmp[10];  	strncat(menu, (char *)_gameData + _locMessagesIdx[_currentRoom], 39); @@ -498,7 +498,7 @@ void TrollEngine::pickupTreasure(int treasureId) {  	if (_currentRoom != 24) {  		clearTextArea();  		drawPic(_currentRoom, false, true); -		_gfx->doUpdate(); +		g_system->updateScreen();  	}  	printUserMessage(treasureId + 16); diff --git a/engines/agi/preagi_winnie.cpp b/engines/agi/preagi_winnie.cpp index a91ad24fc6..596f417140 100644 --- a/engines/agi/preagi_winnie.cpp +++ b/engines/agi/preagi_winnie.cpp @@ -192,16 +192,16 @@ void WinnieEngine::randomize() {  void WinnieEngine::intro() {  	drawPic(IDS_WTP_FILE_LOGO);  	printStr(IDS_WTP_INTRO_0); -	_gfx->doUpdate(); +	g_system->updateScreen();  	_system->delayMillis(0x640);  	if (getPlatform() == Common::kPlatformAmiga) -		_gfx->clearScreen(0); +		_gfx->clearDisplay(0);  	drawPic(IDS_WTP_FILE_TITLE);  	printStr(IDS_WTP_INTRO_1); -	_gfx->doUpdate(); +	g_system->updateScreen();  	_system->delayMillis(0x640);  	if (!playSound(IDI_WTP_SND_POOH_0)) @@ -452,7 +452,7 @@ int WinnieEngine::parser(int pc, int index, uint8 *buffer) {  		if (iBlock == 1)  			return IDI_WTP_PAR_OK; -		_gfx->doUpdate(); +		g_system->updateScreen();  	}  	return IDI_WTP_PAR_OK; @@ -477,7 +477,7 @@ void WinnieEngine::inventory() {  	Common::String missing = Common::String::format(IDS_WTP_INVENTORY_1, _gameStateWinnie.nObjMiss);  	drawStr(IDI_WTP_ROW_OPTION_4, IDI_WTP_COL_MENU, IDA_DEFAULT, missing.c_str()); -	_gfx->doUpdate(); +	g_system->updateScreen();  	getSelection(kSelAnyKey);  } @@ -755,7 +755,7 @@ void WinnieEngine::drawMenu(char *szMenu, int iSel, int fCanSel[]) {  		break;  	}  	drawStr(iRow, iCol - 1, IDA_DEFAULT, ">"); -	_gfx->doUpdate(); +	g_system->updateScreen();  }  void WinnieEngine::incMenuSel(int *iSel, int fCanSel[]) { @@ -821,15 +821,16 @@ void WinnieEngine::getMenuSel(char *szMenu, int *iSel, int fCanSel[]) {  				// Change cursor  				if (fCanSel[IDI_WTP_SEL_NORTH] && hotspotNorth.contains(event.mouse.x, event.mouse.y)) { -					_gfx->setCursorPalette(true); +					//_gfx->setCursorPalette(true); +					// ????  				} else if (fCanSel[IDI_WTP_SEL_SOUTH] && hotspotSouth.contains(event.mouse.x, event.mouse.y)) { -					_gfx->setCursorPalette(true); +					//_gfx->setCursorPalette(true);  				} else if (fCanSel[IDI_WTP_SEL_WEST] && hotspotWest.contains(event.mouse.x, event.mouse.y)) { -					_gfx->setCursorPalette(true); +					//_gfx->setCursorPalette(true);  				} else if (fCanSel[IDI_WTP_SEL_EAST] && hotspotEast.contains(event.mouse.x, event.mouse.y)) { -					_gfx->setCursorPalette(true); +					//_gfx->setCursorPalette(true);  				} else { -					_gfx->setCursorPalette(false); +					//_gfx->setCursorPalette(false);  				}  				break; @@ -838,25 +839,30 @@ void WinnieEngine::getMenuSel(char *szMenu, int *iSel, int fCanSel[]) {  				if (fCanSel[IDI_WTP_SEL_NORTH] && hotspotNorth.contains(event.mouse.x, event.mouse.y)) {  					*iSel = IDI_WTP_SEL_NORTH;  					makeSel(iSel, fCanSel); -					_gfx->setCursorPalette(false); +					//_gfx->setCursorPalette(false); +					// TODO???  					return;  				} else if (fCanSel[IDI_WTP_SEL_SOUTH] && hotspotSouth.contains(event.mouse.x, event.mouse.y)) {  					*iSel = IDI_WTP_SEL_SOUTH;  					makeSel(iSel, fCanSel); -					_gfx->setCursorPalette(false); +					//_gfx->setCursorPalette(false); +					// TODO???  					return;  				} else if (fCanSel[IDI_WTP_SEL_WEST] && hotspotWest.contains(event.mouse.x, event.mouse.y)) {  					*iSel = IDI_WTP_SEL_WEST;  					makeSel(iSel, fCanSel); -					_gfx->setCursorPalette(false); +					//_gfx->setCursorPalette(false); +					// TODO???  					return;  				} else if (fCanSel[IDI_WTP_SEL_EAST] && hotspotEast.contains(event.mouse.x, event.mouse.y)) {  					*iSel = IDI_WTP_SEL_EAST;  					makeSel(iSel, fCanSel); -					_gfx->setCursorPalette(false); +					//_gfx->setCursorPalette(false); +					// TODO???  					return;  				} else { -					_gfx->setCursorPalette(false); +					//_gfx->setCursorPalette(false); +					// TODO???  				}  				switch (*iSel) { @@ -941,7 +947,7 @@ void WinnieEngine::getMenuSel(char *szMenu, int *iSel, int fCanSel[]) {  					break;  				case Common::KEYCODE_s:  					if (event.kbd.flags & Common::KBD_CTRL) { -						flipflag(fSoundOn); +						flipflag(VM_FLAG_SOUND_ON);  					} else {  						*iSel = IDI_WTP_SEL_SOUTH;  						makeSel(iSel, fCanSel); @@ -1016,7 +1022,7 @@ void WinnieEngine::gameLoop() {  			readRoom(_room, roomdata, hdr);  			drawRoomPic(); -			_gfx->doUpdate(); +			g_system->updateScreen();  			decodePhase = 1;  		} @@ -1102,7 +1108,7 @@ void WinnieEngine::drawRoomPic() {  	int iObj = getObjInRoom(_room);  	// clear gfx screen -	_gfx->clearScreen(0); +	_gfx->clearDisplay(0);  	// read room picture  	readRoom(_room, buffer, roomhdr); @@ -1175,7 +1181,8 @@ void WinnieEngine::clrMenuSel(int *iSel, int fCanSel[]) {  	while (!fCanSel[*iSel]) {  		*iSel += 1;  	} -	_gfx->setCursorPalette(false); +	//_gfx->setCursorPalette(false); +	// TODO???  }  void WinnieEngine::printRoomStr(int iRoom, int iStr) { @@ -1335,7 +1342,7 @@ void WinnieEngine::init() {  	}  	_sound = new SoundMgr(this, _mixer); -	setflag(fSoundOn, true); // enable sound +	setflag(VM_FLAG_SOUND_ON, true); // enable sound  	memset(&_gameStateWinnie, 0, sizeof(_gameStateWinnie));  	_gameStateWinnie.fSound = 1; diff --git a/engines/agi/saveload.cpp b/engines/agi/saveload.cpp index 41a7a943ff..b11927c542 100644 --- a/engines/agi/saveload.cpp +++ b/engines/agi/saveload.cpp @@ -38,11 +38,14 @@  #include "agi/agi.h"  #include "agi/graphics.h" +#include "agi/text.h"  #include "agi/sprite.h"  #include "agi/keyboard.h"  #include "agi/menu.h" +#include "agi/systemui.h" +#include "agi/words.h" -#define SAVEGAME_VERSION 6 +#define SAVEGAME_CURRENT_VERSION 7  //  // Version 0 (Sarien): view table has 64 entries @@ -52,19 +55,23 @@  // Version 4 (ScummVM): added thumbnails and save creation date/time  // Version 5 (ScummVM): Added game md5  // Version 6 (ScummVM): Added game played time +// Version 7 (ScummVM): Added controller key mappings +//                       required for some games for quick-loading from ScummVM main menu +//                       for games, that do not set all key mappings right at the start +//                      Added automatic save data (for command SetSimple)  //  namespace Agi {  static const uint32 AGIflag = MKTAG('A','G','I',':'); -int AgiEngine::saveGame(const Common::String &fileName, const Common::String &description) { +int AgiEngine::saveGame(const Common::String &fileName, const Common::String &descriptionString) {  	char gameIDstring[8] = "gameIDX";  	int i;  	Common::OutSaveFile *out;  	int result = errOK; -	debugC(3, kDebugLevelMain | kDebugLevelSavegame, "AgiEngine::saveGame(%s, %s)", fileName.c_str(), description.c_str()); +	debugC(3, kDebugLevelMain | kDebugLevelSavegame, "AgiEngine::saveGame(%s, %s)", fileName.c_str(), descriptionString.c_str());  	if (!(out = _saveFileMan->openForSaving(fileName))) {  		warning("Can't create file '%s', game not saved", fileName.c_str());  		return errBadFileOpen; @@ -73,10 +80,17 @@ int AgiEngine::saveGame(const Common::String &fileName, const Common::String &de  	}  	out->writeUint32BE(AGIflag); -	out->write(description.c_str(), 31); -	out->writeByte(SAVEGAME_VERSION); -	debugC(5, kDebugLevelMain | kDebugLevelSavegame, "Writing save game version (%d)", SAVEGAME_VERSION); +	// Write description of saved game, limited to SAVEDGAME_DESCRIPTION_LEN characters + terminating NUL +	char description[SAVEDGAME_DESCRIPTION_LEN + 1]; + +	memset(description, 0, sizeof(description)); +	strncpy(description, descriptionString.c_str(), SAVEDGAME_DESCRIPTION_LEN); +	assert(SAVEDGAME_DESCRIPTION_LEN + 1 == 31); // safety +	out->write(description, 31); + +	out->writeByte(SAVEGAME_CURRENT_VERSION); +	debugC(5, kDebugLevelMain | kDebugLevelSavegame, "Writing save game version (%d)", SAVEGAME_CURRENT_VERSION);  	// Thumbnail  	Graphics::saveThumbnail(*out); @@ -116,37 +130,50 @@ int AgiEngine::saveGame(const Common::String &fileName, const Common::String &de  			out->writeByte(tmp[i]);  	} +	// Version 7+: Save automatic saving state (set.simple opcode) +	out->writeByte(_game.automaticSave); +	out->write(_game.automaticSaveDescription, 31); +  	for (i = 0; i < MAX_FLAGS; i++)  		out->writeByte(_game.flags[i]);  	for (i = 0; i < MAX_VARS; i++)  		out->writeByte(_game.vars[i]);  	out->writeSint16BE((int8)_game.horizon); -	out->writeSint16BE((int16)_game.lineStatus); -	out->writeSint16BE((int16)_game.lineUserInput); -	out->writeSint16BE((int16)_game.lineMinPrint); +	out->writeSint16BE((int16)_text->statusRow_Get()); +	out->writeSint16BE((int16)_text->promptRow_Get()); +	out->writeSint16BE((int16)_text->getWindowRowMin());  	out->writeSint16BE((int16)_game.inputMode);  	out->writeSint16BE((int16)_game.lognum);  	out->writeSint16BE((int16)_game.playerControl);  	out->writeSint16BE((int16)shouldQuit()); -	out->writeSint16BE((int16)_game.statusLine); +	if (_text->statusEnabled()) { +		out->writeSint16BE(0x7FFF); +	} else { +		out->writeSint16BE(0); +	}  	out->writeSint16BE((int16)_game.clockEnabled);  	out->writeSint16BE((int16)_game.exitAllLogics);  	out->writeSint16BE((int16)_game.pictureShown);  	out->writeSint16BE((int16)_game.hasPrompt);  	out->writeSint16BE((int16)_game.gameFlags); -	out->writeSint16BE(_game.inputEnabled); +	if (_text->promptIsEnabled()) { +		out->writeSint16BE(0x7FFF); +	} else { +		out->writeSint16BE(0); +	} -	for (i = 0; i < _HEIGHT; i++) -		out->writeByte(_game.priTable[i]); +	// TODO: save if priority table was modified +	for (i = 0; i < SCRIPT_HEIGHT; i++) +		out->writeByte(_gfx->priorityFromY(i));  	out->writeSint16BE((int16)_game.gfxMode); -	out->writeByte(_game.cursorChar); -	out->writeSint16BE((int16)_game.colorFg); -	out->writeSint16BE((int16)_game.colorBg); +	out->writeByte(_text->inputGetCursorChar()); +	out->writeSint16BE((int16)_text->charAttrib_GetForeground()); +	out->writeSint16BE((int16)_text->charAttrib_GetBackground());  	// game.hires  	// game.sbuf @@ -157,21 +184,29 @@ int AgiEngine::saveGame(const Common::String &fileName, const Common::String &de  	for (i = 0; i < (int16)_game.numObjects; i++)  		out->writeSint16BE((int16)objectGetLocation(i)); +	// Version 7+: save controller key mappings +	//  required for games, that do not set all key mappings right at the start +	//  when quick restoring is used from ScummVM menu, only 1 cycle is executed +	for (i = 0; i < MAX_CONTROLLER_KEYMAPPINGS; i++) { +		out->writeUint16BE(_game.controllerKeyMapping[i].keycode); +		out->writeByte(_game.controllerKeyMapping[i].controllerSlot); +	} +  	// game.ev_keyp  	for (i = 0; i < MAX_STRINGS; i++)  		out->write(_game.strings[i], MAX_STRINGLEN);  	// record info about loaded resources -	for (i = 0; i < MAX_DIRS; i++) { +	for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++) {  		out->writeByte(_game.dirLogic[i].flags);  		out->writeSint16BE((int16)_game.logics[i].sIP);  		out->writeSint16BE((int16)_game.logics[i].cIP);  	} -	for (i = 0; i < MAX_DIRS; i++) +	for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++)  		out->writeByte(_game.dirPic[i].flags); -	for (i = 0; i < MAX_DIRS; i++) +	for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++)  		out->writeByte(_game.dirView[i].flags); -	for (i = 0; i < MAX_DIRS; i++) +	for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++)  		out->writeByte(_game.dirSound[i].flags);  	// game.pictures @@ -179,51 +214,77 @@ int AgiEngine::saveGame(const Common::String &fileName, const Common::String &de  	// game.views  	// game.sounds -	for (i = 0; i < MAX_VIEWTABLE; i++) { -		VtEntry *v = &_game.viewTable[i]; +	for (i = 0; i < SCREENOBJECTS_MAX; i++) { +		ScreenObjEntry *screenObj = &_game.screenObjTable[i]; -		out->writeByte(v->stepTime); -		out->writeByte(v->stepTimeCount); -		out->writeByte(v->entry); -		out->writeSint16BE(v->xPos); -		out->writeSint16BE(v->yPos); -		out->writeByte(v->currentView); +		out->writeByte(screenObj->stepTime); +		out->writeByte(screenObj->stepTimeCount); +		out->writeByte(screenObj->objectNr); +		out->writeSint16BE(screenObj->xPos); +		out->writeSint16BE(screenObj->yPos); +		out->writeByte(screenObj->currentViewNr);  		// v->view_data -		out->writeByte(v->currentLoop); -		out->writeByte(v->numLoops); +		out->writeByte(screenObj->currentLoopNr); +		out->writeByte(screenObj->loopCount);  		// v->loop_data -		out->writeByte(v->currentCel); -		out->writeByte(v->numCels); +		out->writeByte(screenObj->currentCelNr); +		out->writeByte(screenObj->celCount);  		// v->cel_data  		// v->cel_data_2 -		out->writeSint16BE(v->xPos2); -		out->writeSint16BE(v->yPos2); +		out->writeSint16BE(screenObj->xPos_prev); +		out->writeSint16BE(screenObj->yPos_prev);  		// v->s -		out->writeSint16BE(v->xSize); -		out->writeSint16BE(v->ySize); -		out->writeByte(v->stepSize); -		out->writeByte(v->cycleTime); -		out->writeByte(v->cycleTimeCount); -		out->writeByte(v->direction); +		out->writeSint16BE(screenObj->xSize); +		out->writeSint16BE(screenObj->ySize); +		out->writeByte(screenObj->stepSize); +		out->writeByte(screenObj->cycleTime); +		out->writeByte(screenObj->cycleTimeCount); +		out->writeByte(screenObj->direction); -		out->writeByte(v->motion); -		out->writeByte(v->cycle); -		out->writeByte(v->priority); +		out->writeByte(screenObj->motionType); +		out->writeByte(screenObj->cycle); +		out->writeByte(screenObj->priority); -		out->writeUint16BE(v->flags); +		out->writeUint16BE(screenObj->flags); -		out->writeByte(v->parm1); -		out->writeByte(v->parm2); -		out->writeByte(v->parm3); -		out->writeByte(v->parm4); +		// this was done so that saved games compatibility isn't broken +		switch (screenObj->motionType) { +		case kMotionNormal: +			out->writeByte(0); +			out->writeByte(0); +			out->writeByte(0); +			out->writeByte(0); +			break; +		case kMotionWander: +			out->writeByte(screenObj->wander_count); +			out->writeByte(0); +			out->writeByte(0); +			out->writeByte(0); +			break; +		case kMotionFollowEgo: +			out->writeByte(screenObj->follow_stepSize); +			out->writeByte(screenObj->follow_flag); +			out->writeByte(screenObj->follow_count); +			out->writeByte(0); +			break; +		case kMotionEgo: +		case kMotionMoveObj: +			out->writeByte((byte)screenObj->move_x); // problematic! int16 -> byte +			out->writeByte((byte)screenObj->move_y); +			out->writeByte(screenObj->move_stepSize); +			out->writeByte(screenObj->move_flag); +			break; +		default: +			error("unknown motion-type"); +		}  	}  	// Save image stack @@ -249,7 +310,7 @@ int AgiEngine::saveGame(const Common::String &fileName, const Common::String &de  		warning("Can't write file '%s'. (Disk full?)", fileName.c_str());  		result = errIOError;  	} else -		debugC(1, kDebugLevelMain | kDebugLevelSavegame, "Saved game %s in file %s", description.c_str(), fileName.c_str()); +		debugC(1, kDebugLevelMain | kDebugLevelSavegame, "Saved game %s in file %s", descriptionString.c_str(), fileName.c_str());  	delete out;  	debugC(3, kDebugLevelMain | kDebugLevelSavegame, "Closed %s", fileName.c_str()); @@ -260,8 +321,10 @@ int AgiEngine::saveGame(const Common::String &fileName, const Common::String &de  }  int AgiEngine::loadGame(const Common::String &fileName, bool checkId) { -	char description[31], saveVersion, loadId[8]; -	int i, vtEntries = MAX_VIEWTABLE; +	char  description[SAVEDGAME_DESCRIPTION_LEN + 1]; +	byte  saveVersion = 0; +	char  loadId[8]; +	int   i, vtEntries = SCREENOBJECTS_MAX;  	uint8 t;  	int16 parm[7];  	Common::InSaveFile *in; @@ -284,17 +347,28 @@ int AgiEngine::loadGame(const Common::String &fileName, bool checkId) {  		return errOK;  	} -	in->read(description, 31); +	assert(SAVEDGAME_DESCRIPTION_LEN + 1 == 31); // safety +	in->read(description, 31); // skip description +	// check, if there is a terminating NUL inside description +	uint16 descriptionPos = 0; +	while (description[descriptionPos]) { +		descriptionPos++; +		if (descriptionPos >= sizeof(description)) +			error("saved game description is corrupt"); +	}  	debugC(6, kDebugLevelMain | kDebugLevelSavegame, "Description is: %s", description);  	saveVersion = in->readByte();  	if (saveVersion < 2)	// is the save game pre-ScummVM? -		warning("Old save game version (%d, current version is %d). Will try and read anyway, but don't be surprised if bad things happen", saveVersion, SAVEGAME_VERSION); +		warning("Old save game version (%d, current version is %d). Will try and read anyway, but don't be surprised if bad things happen", saveVersion, SAVEGAME_CURRENT_VERSION);  	if (saveVersion < 3)  		warning("This save game contains no AGIPAL data, if the game is using the AGIPAL hack, it won't work correctly"); +	if (saveVersion > SAVEGAME_CURRENT_VERSION) +		error("Saved game was created with a newer version of ScummVM. Unable to load."); +  	if (saveVersion >= 4) {  		// We don't need the thumbnail here, so just read it and discard it  		Graphics::skipThumbnail(*in); @@ -348,53 +422,74 @@ int AgiEngine::loadGame(const Common::String &fileName, bool checkId) {  		}  	} +	if (saveVersion >= 7) { +		// Restore automatic saving state (set.simple opcode) +		_game.automaticSave = in->readByte(); +		in->read(_game.automaticSaveDescription, 31); +	} else { +		_game.automaticSave = false; +		_game.automaticSaveDescription[0] = 0; +	} +  	for (i = 0; i < MAX_FLAGS; i++)  		_game.flags[i] = in->readByte();  	for (i = 0; i < MAX_VARS; i++)  		_game.vars[i] = in->readByte(); -	setvar(vFreePages, 180); // Set amount of free memory to realistic value (Overwriting the just loaded value) +	setVar(VM_VAR_FREE_PAGES, 180); // Set amount of free memory to realistic value (Overwriting the just loaded value)  	_game.horizon = in->readSint16BE(); -	_game.lineStatus = in->readSint16BE(); -	_game.lineUserInput = in->readSint16BE(); -	_game.lineMinPrint = in->readSint16BE(); +	_text->statusRow_Set(in->readSint16BE()); +	_text->promptRow_Set(in->readSint16BE()); +	_text->configureScreen(in->readSint16BE());  	// These are never saved -	_game.cursorPos = 0; -	_game.inputBuffer[0] = 0; -	_game.echoBuffer[0] = 0; +	_text->promptReset(); +  	_game.keypress = 0;  	_game.inputMode = (InputMode)in->readSint16BE(); +	if ((_game.inputMode != INPUTMODE_NORMAL) && (_game.inputMode != INPUTMODE_NONE)) { +		// other input modes were removed +		_game.inputMode = INPUTMODE_NORMAL; +	} +  	_game.lognum = in->readSint16BE();  	_game.playerControl = in->readSint16BE();  	if (in->readSint16BE())  		quitGame(); -	_game.statusLine = in->readSint16BE(); +	if (in->readSint16BE()) { +		_text->statusEnable(); +	} else { +		_text->statusDisable(); +	}  	_game.clockEnabled = in->readSint16BE();  	_game.exitAllLogics = in->readSint16BE(); -	_game.pictureShown = in->readSint16BE(); +	in->readSint16BE(); // was _game.pictureShown +	//_game.pictureShown = in->readSint16BE();  	_game.hasPrompt = in->readSint16BE();  	_game.gameFlags = in->readSint16BE(); -	_game.inputEnabled = in->readSint16BE(); +	if (in->readSint16BE()) { +		_text->promptEnable(); +	} else { +		_text->promptDisable(); +	} -	for (i = 0; i < _HEIGHT; i++) -		_game.priTable[i] = in->readByte(); +	for (i = 0; i < SCRIPT_HEIGHT; i++) +		_gfx->setPriority(i, in->readByte()); -	if (_game.hasWindow) -		closeWindow(); +	_text->closeWindow();  	_game.msgBoxTicks = 0;  	_game.block.active = false; -	// game.window - fixed by close_window() -	// game.has_window - fixed by close_window()  	_game.gfxMode = in->readSint16BE(); -	_game.cursorChar = in->readByte(); -	_game.colorFg = in->readSint16BE(); -	_game.colorBg = in->readSint16BE(); +	_text->inputSetCursorChar(in->readByte()); + +	int16 textForeground = in->readSint16BE(); +	int16 textBackground = in->readSint16BE(); +	_text->charAttrib_Set(textForeground, textBackground);  	// game.hires - rebuilt from image stack  	// game.sbuf - rebuilt from image stack @@ -407,41 +502,49 @@ int AgiEngine::loadGame(const Common::String &fileName, bool checkId) {  		objectSetLocation(i, in->readSint16BE());  	// Those are not serialized -	for (i = 0; i < MAX_DIRS; i++) { +	for (i = 0; i < MAX_CONTROLLERS; i++) {  		_game.controllerOccured[i] = false;  	} +	if (saveVersion >= 7) { +		// For old saves, we just keep the current controllers +		for (i = 0; i < MAX_CONTROLLER_KEYMAPPINGS; i++) { +			_game.controllerKeyMapping[i].keycode = in->readUint16BE(); +			_game.controllerKeyMapping[i].controllerSlot = in->readByte(); +		} +	} +  	for (i = 0; i < MAX_STRINGS; i++)  		in->read(_game.strings[i], MAX_STRINGLEN); -	for (i = 0; i < MAX_DIRS; i++) { +	for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++) {  		if (in->readByte() & RES_LOADED) -			agiLoadResource(rLOGIC, i); +			agiLoadResource(RESOURCETYPE_LOGIC, i);  		else -			agiUnloadResource(rLOGIC, i); +			agiUnloadResource(RESOURCETYPE_LOGIC, i);  		_game.logics[i].sIP = in->readSint16BE();  		_game.logics[i].cIP = in->readSint16BE();  	} -	for (i = 0; i < MAX_DIRS; i++) { +	for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++) {  		if (in->readByte() & RES_LOADED) -			agiLoadResource(rPICTURE, i); +			agiLoadResource(RESOURCETYPE_PICTURE, i);  		else -			agiUnloadResource(rPICTURE, i); +			agiUnloadResource(RESOURCETYPE_PICTURE, i);  	} -	for (i = 0; i < MAX_DIRS; i++) { +	for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++) {  		if (in->readByte() & RES_LOADED) -			agiLoadResource(rVIEW, i); +			agiLoadResource(RESOURCETYPE_VIEW, i);  		else -			agiUnloadResource(rVIEW, i); +			agiUnloadResource(RESOURCETYPE_VIEW, i);  	} -	for (i = 0; i < MAX_DIRS; i++) { +	for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++) {  		if (in->readByte() & RES_LOADED) -			agiLoadResource(rSOUND, i); +			agiLoadResource(RESOURCETYPE_SOUND, i);  		else -			agiUnloadResource(rSOUND, i); +			agiUnloadResource(RESOURCETYPE_SOUND, i);  	}  	// game.pictures - loaded above @@ -450,78 +553,102 @@ int AgiEngine::loadGame(const Common::String &fileName, bool checkId) {  	// game.sounds - loaded above  	for (i = 0; i < vtEntries; i++) { -		VtEntry *v = &_game.viewTable[i]; - -		v->stepTime = in->readByte(); -		v->stepTimeCount = in->readByte(); -		v->entry = in->readByte(); -		v->xPos = in->readSint16BE(); -		v->yPos = in->readSint16BE(); -		v->currentView = in->readByte(); - -		// v->view_data - fixed below - -		v->currentLoop = in->readByte(); -		v->numLoops = in->readByte(); - -		// v->loop_data - fixed below - -		v->currentCel = in->readByte(); -		v->numCels = in->readByte(); - -		// v->cel_data - fixed below -		// v->cel_data_2 - fixed below - -		v->xPos2 = in->readSint16BE(); -		v->yPos2 = in->readSint16BE(); - -		// v->s - fixed below - -		v->xSize = in->readSint16BE(); -		v->ySize = in->readSint16BE(); -		v->stepSize = in->readByte(); -		v->cycleTime = in->readByte(); -		v->cycleTimeCount = in->readByte(); -		v->direction = in->readByte(); - -		v->motion = (MotionType)in->readByte(); -		v->cycle = (CycleType)in->readByte(); -		v->priority = in->readByte(); - -		v->flags = in->readUint16BE(); - -		v->parm1 = in->readByte(); -		v->parm2 = in->readByte(); -		v->parm3 = in->readByte(); -		v->parm4 = in->readByte(); +		ScreenObjEntry *screenObj = &_game.screenObjTable[i]; + +		screenObj->stepTime = in->readByte(); +		screenObj->stepTimeCount = in->readByte(); +		screenObj->objectNr = in->readByte(); +		screenObj->xPos = in->readSint16BE(); +		screenObj->yPos = in->readSint16BE(); +		screenObj->currentViewNr = in->readByte(); + +		// screenObj->view_data - fixed below + +		screenObj->currentLoopNr = in->readByte(); +		screenObj->loopCount = in->readByte(); + +		// screenObj->loop_data - fixed below + +		screenObj->currentCelNr = in->readByte(); +		screenObj->celCount = in->readByte(); + +		// screenObj->cel_data - fixed below +		// screenObj->cel_data_2 - fixed below + +		screenObj->xPos_prev = in->readSint16BE(); +		screenObj->yPos_prev = in->readSint16BE(); + +		// screenObj->s - fixed below + +		screenObj->xSize = in->readSint16BE(); +		screenObj->ySize = in->readSint16BE(); +		screenObj->stepSize = in->readByte(); +		screenObj->cycleTime = in->readByte(); +		screenObj->cycleTimeCount = in->readByte(); +		screenObj->direction = in->readByte(); + +		screenObj->motionType = (MotionType)in->readByte(); +		screenObj->cycle = (CycleType)in->readByte(); +		screenObj->priority = in->readByte(); + +		screenObj->flags = in->readUint16BE(); + +		// this was done so that saved games compatibility isn't broken +		switch (screenObj->motionType) { +		case kMotionNormal: +			in->readByte(); +			in->readByte(); +			in->readByte(); +			in->readByte(); +			break; +		case kMotionWander: +			screenObj->wander_count = in->readByte(); +			in->readByte(); +			in->readByte(); +			in->readByte(); +			break; +		case kMotionFollowEgo: +			screenObj->follow_stepSize = in->readByte(); +			screenObj->follow_flag = in->readByte(); +			screenObj->follow_count = in->readByte(); +			in->readByte(); +			break; +		case kMotionEgo: +		case kMotionMoveObj: +			screenObj->move_x = in->readByte(); // problematic! int16 -> byte +			screenObj->move_y = in->readByte(); +			screenObj->move_stepSize = in->readByte(); +			screenObj->move_flag = in->readByte(); +			break; +		default: +			error("unknown motion-type"); +		}  	} -	for (i = vtEntries; i < MAX_VIEWTABLE; i++) { -		memset(&_game.viewTable[i], 0, sizeof(VtEntry)); +	for (i = vtEntries; i < SCREENOBJECTS_MAX; i++) { +		memset(&_game.screenObjTable[i], 0, sizeof(ScreenObjEntry));  	} -	// Fix some pointers in viewtable +	// Fix some pointers in screenObjTable -	for (i = 0; i < MAX_VIEWTABLE; i++) { -		VtEntry *v = &_game.viewTable[i]; +	for (i = 0; i < SCREENOBJECTS_MAX; i++) { +		ScreenObjEntry *screenObj = &_game.screenObjTable[i]; -		if (_game.dirView[v->currentView].offset == _EMPTY) +		if (_game.dirView[screenObj->currentViewNr].offset == _EMPTY)  			continue; -		if (!(_game.dirView[v->currentView].flags & RES_LOADED)) -			agiLoadResource(rVIEW, v->currentView); +		if (!(_game.dirView[screenObj->currentViewNr].flags & RES_LOADED)) +			agiLoadResource(RESOURCETYPE_VIEW, screenObj->currentViewNr); -		setView(v, v->currentView);	// Fix v->view_data -		setLoop(v, v->currentLoop);	// Fix v->loop_data -		setCel(v, v->currentCel);	// Fix v->cel_data -		v->celData2 = v->celData; -		v->s = NULL;	// not sure if it is used... +		setView(screenObj, screenObj->currentViewNr);	// Fix v->view_data +		setLoop(screenObj, screenObj->currentLoopNr);	// Fix v->loop_data +		setCel(screenObj, screenObj->currentCelNr);	// Fix v->cel_data  	} -	_sprites->eraseBoth(); +	_sprites->eraseSprites(); + +	_game.pictureShown = false; -	// Clear input line -	_gfx->clearScreen(0); -	writeStatus(); +	_gfx->clearDisplay(0, false); // clear display screen, but not copy it to actual screen for now b/c transition  	// Recreate background from saved image stack  	clearImageStack(); @@ -539,259 +666,26 @@ int AgiEngine::loadGame(const Common::String &fileName, bool checkId) {  	delete in;  	debugC(3, kDebugLevelMain | kDebugLevelSavegame, "Closed %s", fileName.c_str()); -	setflag(fRestoreJustRan, true); +	setflag(VM_FLAG_RESTORE_JUST_RAN, true);  	_game.hasPrompt = 0;	// force input line repaint if necessary -	cleanInput(); - -	_sprites->eraseBoth(); -	_sprites->blitBoth(); -	_sprites->commitBoth(); -	_picture->showPic(); -	_gfx->doUpdate(); - -	return errOK; -} - -#define NUM_SLOTS 100 -#define NUM_VISIBLE_SLOTS 12 - -Common::String AgiEngine::getSavegameFilename(int num) const { -	Common::String saveLoadSlot = _targetName; -	saveLoadSlot += Common::String::format(".%.3d", num); -	return saveLoadSlot; -} - -void AgiEngine::getSavegameDescription(int num, char *buf, bool showEmpty) { -	Common::InSaveFile *in; -	Common::String fileName = getSavegameFilename(num); - -	debugC(4, kDebugLevelMain | kDebugLevelSavegame, "Current game id is %s", _targetName.c_str()); - -	if (!(in = _saveFileMan->openForLoading(fileName))) { -		debugC(4, kDebugLevelMain | kDebugLevelSavegame, "File %s does not exist", fileName.c_str()); - -		if (showEmpty) -			strcpy(buf, "        (empty slot)"); -		else -			*buf = 0; -	} else { -		debugC(4, kDebugLevelMain | kDebugLevelSavegame, "Successfully opened %s for reading", fileName.c_str()); - -		uint32 type = in->readUint32BE(); - -		if (type == AGIflag) { -			debugC(6, kDebugLevelMain | kDebugLevelSavegame, "Has AGI flag, good start"); -			in->read(buf, 31); -		} else { -			warning("This doesn't appear to be an AGI savegame"); -			strcpy(buf, "(corrupt file)"); -		} - -		delete in; -	} -} - -int AgiEngine::selectSlot() { -	int i, key, active = 0; -	int rc = -1; -	int hm = 1, vm = 3;	// box margins -	int xmin, xmax, slotClicked; -	char desc[NUM_VISIBLE_SLOTS][40]; -	int textCenter, buttonLength, buttonX[2], buttonY; -	const char *buttonText[] = { "  OK  ", "Cancel", NULL }; - -	_noSaveLoadAllowed = true; - -	for (i = 0; i < NUM_VISIBLE_SLOTS; i++) { -		getSavegameDescription(_firstSlot + i, desc[i]); -	} - -	textCenter = GFX_WIDTH / CHAR_LINES / 2; -	buttonLength = 6; -	buttonX[0] = (textCenter - 3 * buttonLength / 2) * CHAR_COLS; -	buttonX[1] = (textCenter + buttonLength / 2) * CHAR_COLS; -	buttonY = (vm + 17) * CHAR_LINES; - -	for (i = 0; i < 2; i++) -		_gfx->drawCurrentStyleButton(buttonX[i], buttonY, buttonText[i], false, false, i == 0); - -	AllowSyntheticEvents on(this); -	int oldFirstSlot = _firstSlot + 1; -	int oldActive = active + 1; -	bool exitSelectSlot = false; -	while (!exitSelectSlot && !(shouldQuit() || _restartGame)) { -		int sbPos = 0; - -		// Use the extreme scrollbar positions only if the extreme -		// slots are in sight. (We have to calculate this even if we -		// don't redraw the save slots, because it's also used for -		// clicking in the scrollbar. - -		if (_firstSlot == 0) -			sbPos = 1; -		else if (_firstSlot == NUM_SLOTS - NUM_VISIBLE_SLOTS) -			sbPos = NUM_VISIBLE_SLOTS - 2; -		else { -			sbPos = 2 + (_firstSlot * (NUM_VISIBLE_SLOTS - 4)) / (NUM_SLOTS - NUM_VISIBLE_SLOTS - 1); -			if (sbPos >= NUM_VISIBLE_SLOTS - 3) -				sbPos = NUM_VISIBLE_SLOTS - 3; -		} - -		if (oldFirstSlot != _firstSlot || oldActive != active) { -			char dstr[64]; -			for (i = 0; i < NUM_VISIBLE_SLOTS; i++) { -				sprintf(dstr, "[%2d. %-28.28s]", i + _firstSlot, desc[i]); -				printText(dstr, 0, hm + 1, vm + 4 + i, -						(40 - 2 * hm) - 1, i == active ? MSG_BOX_COLOR : MSG_BOX_TEXT, -						i == active ? MSG_BOX_TEXT : MSG_BOX_COLOR); -			} - -			char upArrow[] = "^"; -			char downArrow[] = "v"; -			char scrollBar[] = " "; - -			for (i = 1; i < NUM_VISIBLE_SLOTS - 1; i++) -				printText(scrollBar, 35, hm + 1, vm + 4 + i, 1, MSG_BOX_COLOR, 7, true); - -			printText(upArrow, 35, hm + 1, vm + 4, 1, 8, 7); -			printText(downArrow, 35, hm + 1, vm + 4 + NUM_VISIBLE_SLOTS - 1, 1, 8, 7); -			printText(scrollBar, 35, hm + 1, vm + 4 + sbPos, 1, MSG_BOX_COLOR, MSG_BOX_TEXT); - -			oldActive = active; -			oldFirstSlot = _firstSlot; -		} - -		pollTimer(); -		key = doPollKeyboard(); - -		// It may happen that somebody will open GMM while -		// this dialog is open, and load a game -		// We are processing it here, effectively jumping -		// out of the dead loop -		if (getflag(fRestoreJustRan)) { -			rc = -2; -			exitSelectSlot = true; -		} +	_words->clearEgoWords(); -		if (!exitSelectSlot) { -			switch (key) { -			case KEY_ENTER: -				rc = active; -				Common::strlcpy(_game.strings[MAX_STRINGS], desc[i], MAX_STRINGLEN); -				debugC(8, kDebugLevelMain | kDebugLevelInput, "Button pressed: %d", rc); -				exitSelectSlot = true; -				break; -			case KEY_ESCAPE: -				rc = -1; -				exitSelectSlot = true; -				break; -			case BUTTON_LEFT: -				if (_gfx->testButton(buttonX[0], buttonY, buttonText[0])) { -					rc = active; -					strncpy(_game.strings[MAX_STRINGS], desc[i], MAX_STRINGLEN); -					debugC(8, kDebugLevelMain | kDebugLevelInput, "Button pressed: %d", rc); -					exitSelectSlot = true; -				} else if (_gfx->testButton(buttonX[1], buttonY, buttonText[1])) { -					rc = -1; -					exitSelectSlot = true; -				} else { -					slotClicked = ((int)_mouse.y - 1) / CHAR_COLS - (vm + 4); -					xmin = (hm + 1) * CHAR_COLS; -					xmax = xmin + CHAR_COLS * 34; -					if ((int)_mouse.x >= xmin && (int)_mouse.x <= xmax) { -						if (slotClicked >= 0 && slotClicked < NUM_VISIBLE_SLOTS) -							active = slotClicked; -					} -					xmin = (hm + 36) * CHAR_COLS; -					xmax = xmin + CHAR_COLS; -					if ((int)_mouse.x >= xmin && (int)_mouse.x <= xmax) { -						if (slotClicked >= 0 && slotClicked < NUM_VISIBLE_SLOTS) { -							if (slotClicked == 0) -								keyEnqueue(KEY_UP); -							else if (slotClicked == NUM_VISIBLE_SLOTS - 1) -								keyEnqueue(KEY_DOWN); -							else if (slotClicked < sbPos) -								keyEnqueue(KEY_UP_RIGHT); -							else if (slotClicked > sbPos) -								keyEnqueue(KEY_DOWN_RIGHT); -						} -					} -				} -				break; +	// don't delay anything right after restoring a game +	nonBlockingText_Forget(); -			case KEY_DOWN: -				active++; -				if (active >= NUM_VISIBLE_SLOTS) { -					if (_firstSlot + NUM_VISIBLE_SLOTS < NUM_SLOTS) { -						_firstSlot++; -						for (i = 1; i < NUM_VISIBLE_SLOTS; i++) -							memcpy(desc[i - 1], desc[i], sizeof(desc[0])); -						getSavegameDescription(_firstSlot + NUM_VISIBLE_SLOTS - 1, desc[NUM_VISIBLE_SLOTS - 1]); -					} -					active = NUM_VISIBLE_SLOTS - 1; -				} -				break; -			case KEY_UP: -				active--; -				if (active < 0) { -					active = 0; -					if (_firstSlot > 0) { -						_firstSlot--; -						for (i = NUM_VISIBLE_SLOTS - 1; i > 0; i--) -							memcpy(desc[i], desc[i - 1], sizeof(desc[0])); -						getSavegameDescription(_firstSlot, desc[0]); -					} -				} -				break; +	_sprites->eraseSprites(); +	_sprites->buildAllSpriteLists(); +	_sprites->drawAllSpriteLists(); +	_picture->showPicWithTransition(); +	_game.pictureShown = true; +	_text->statusDraw(); +	_text->promptRedraw(); -			// Page Up/Down and mouse wheel scrolling all leave 'active' -			// unchanged so that a visible slot will remain selected. +	// copy everything over (we should probably only copy over the remaining parts of the screen w/o play screen +	_gfx->copyDisplayRectToScreen(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT); -			case WHEEL_DOWN: -				if (_firstSlot < NUM_SLOTS - NUM_VISIBLE_SLOTS) { -					_firstSlot++; -					for (i = 1; i < NUM_VISIBLE_SLOTS; i++) -						memcpy(desc[i - 1], desc[i], sizeof(desc[0])); -					getSavegameDescription(_firstSlot + NUM_VISIBLE_SLOTS - 1, desc[NUM_VISIBLE_SLOTS - 1]); -				} -				break; -			case WHEEL_UP: -				if (_firstSlot > 0) { -					_firstSlot--; -					for (i = NUM_VISIBLE_SLOTS - 1; i > 0; i--) -						memcpy(desc[i], desc[i - 1], sizeof(desc[0])); -					getSavegameDescription(_firstSlot, desc[0]); -				} -				break; -			case KEY_DOWN_RIGHT: -				// This is probably triggered by Page Down. -				_firstSlot += NUM_VISIBLE_SLOTS; -				if (_firstSlot > NUM_SLOTS - NUM_VISIBLE_SLOTS) { -					_firstSlot = NUM_SLOTS - NUM_VISIBLE_SLOTS; -				} -				for (i = 0; i < NUM_VISIBLE_SLOTS; i++) -					getSavegameDescription(_firstSlot + i, desc[i]); -				break; -			case KEY_UP_RIGHT: -				// This is probably triggered by Page Up. -				_firstSlot -= NUM_VISIBLE_SLOTS; -				if (_firstSlot < 0) { -					_firstSlot = 0; -				} -				for (i = 0; i < NUM_VISIBLE_SLOTS; i++) -					getSavegameDescription(_firstSlot + i, desc[i]); -				break; -			} -		} -		_gfx->doUpdate(); -	} - -	closeWindow(); - -	_noSaveLoadAllowed = false; - -	return rc; +	return errOK;  }  int AgiEngine::scummVMSaveLoadDialog(bool isSave) { @@ -834,7 +728,8 @@ int AgiEngine::doSave(int slot, const Common::String &desc) {  	// Make sure all graphics was blitted to screen. This fixes bug  	// #2960567: "AGI: Ego partly erased in Load/Save thumbnails" -	_gfx->doUpdate(); +	_gfx->updateScreen(); +//	_gfx->doUpdate();  	return saveGame(fileName, desc);  } @@ -843,160 +738,213 @@ int AgiEngine::doLoad(int slot, bool showMessages) {  	Common::String fileName = getSavegameFilename(slot);  	debugC(8, kDebugLevelMain | kDebugLevelResources, "file is [%s]", fileName.c_str()); -	_sprites->eraseBoth(); +	_sprites->eraseSprites();  	_sound->stopSound(); -	closeWindow(); +	_text->closeWindow();  	int result = loadGame(fileName);  	if (result == errOK) { -		if (showMessages) -			messageBox("Game restored.");  		_game.exitAllLogics = 1; -		_menu->enableAll(); +		_menu->itemEnableAll();  	} else {  		if (showMessages) -			messageBox("Error restoring game."); +			_text->messageBox("Error restoring game.");  	}  	return result;  } -int AgiEngine::saveGameDialog() { -	if (!ConfMan.getBool("originalsaveload")) -		return scummVMSaveLoadDialog(true); +SavedGameSlotIdArray AgiEngine::getSavegameSlotIds() { +	Common::StringArray filenames; +	int16 numberPos = _targetName.size() + 1; +	int16 slotId = 0; +	SavedGameSlotIdArray slotIdArray; -	char *desc; -	const char *buttons[] = { "Do as I say!", "I regret", NULL }; -	char dstr[200]; -	int rc, slot = 0; -	int hm, vm, hp, vp; -	int w; - -	hm = 1; -	vm = 3; -	hp = hm * CHAR_COLS; -	vp = vm * CHAR_LINES; -	w = (40 - 2 * hm) - 1; - -	do { -		drawWindow(hp, vp, GFX_WIDTH - hp, GFX_HEIGHT - vp); -		printText("Select a slot in which you wish to\nsave the game:", -				0, hm + 1, vm + 1, w, MSG_BOX_TEXT, MSG_BOX_COLOR); -		slot = selectSlot(); -		if (slot + _firstSlot == 0) -			messageBox("That slot is for Autosave only."); -		else if (slot < 0) -			return errOK; -	} while (slot + _firstSlot == 0); - -	drawWindow(hp, vp + 5 * CHAR_LINES, GFX_WIDTH - hp, -			GFX_HEIGHT - vp - 9 * CHAR_LINES); -	printText("Enter a description for this game:", -			0, hm + 1, vm + 6, w, MSG_BOX_TEXT, MSG_BOX_COLOR); -	_gfx->drawRectangle(3 * CHAR_COLS, 11 * CHAR_LINES - 1, -			37 * CHAR_COLS, 12 * CHAR_LINES, MSG_BOX_TEXT); -	_gfx->flushBlock(3 * CHAR_COLS, 11 * CHAR_LINES - 1, -			37 * CHAR_COLS, 12 * CHAR_LINES); - -	// The description field of the save/restore dialog holds 32 characters -	// but we use four of them for the slot number. The input field is a -	// bit wider than that, so we don't have to worry about leaving space -	// for the cursor. - -	getString(2, 11, 28, MAX_STRINGS); - -	// If we're saving over an old slot, show the old description. We can't -	// access that buffer directly, so we have to feed the characters to -	// the input handler one at a time. - -	char name[40]; -	int numChars; - -	getSavegameDescription(_firstSlot + slot, name, false); - -	for (numChars = 0; numChars < 28 && name[numChars]; numChars++) -		handleGetstring(name[numChars]); - -	_gfx->printCharacter(numChars + 3, 11, _game.cursorChar, MSG_BOX_COLOR, MSG_BOX_TEXT); -	do { -		mainCycle(); -	} while (_game.inputMode == INPUT_GETSTRING); -	closeWindow(); - -	desc = _game.strings[MAX_STRINGS]; -	sprintf(dstr, "Are you sure you want to save the game " -			"described as:\n\n%s\n\nin slot %d?\n\n\n", desc, _firstSlot + slot); - -	rc = selectionBox(dstr, buttons); - -	if (rc != 0) { -		messageBox("Game NOT saved."); -		return errOK; +	// search for saved game filenames... +	filenames = _saveFileMan->listSavefiles(_targetName + ".###"); + +	Common::StringArray::iterator it; +	Common::StringArray::iterator end = filenames.end();; + +	// convert to lower-case, just to be sure +	for (it = filenames.begin(); it != end; it++) { +		it->toLowercase();  	} +	// sort +	Common::sort(filenames.begin(), filenames.end()); -	int result = doSave(_firstSlot + slot, desc); +	// now extract slot-Ids +	for (it = filenames.begin(); it != end; it++) { +		slotId = atoi(it->c_str() + numberPos); -	if (result == errOK) -		messageBox("Game saved."); -	else -		messageBox("Error saving game."); +		slotIdArray.push_back(slotId); +	} +	return slotIdArray; +} -	return result; +Common::String AgiEngine::getSavegameFilename(int16 slotId) const { +	Common::String saveLoadSlot = _targetName; +	saveLoadSlot += Common::String::format(".%.3d", slotId); +	return saveLoadSlot;  } -int AgiEngine::saveGameSimple() { -	if (!ConfMan.getBool("originalsaveload")) -		return scummVMSaveLoadDialog(true); +bool AgiEngine::getSavegameInformation(int16 slotId, Common::String &saveDescription, uint32 &saveDate, uint16 &saveTime, bool &saveIsValid) { +	Common::InSaveFile *in; +	Common::String fileName = getSavegameFilename(slotId); +	char saveGameDescription[31]; +	int16 curPos = 0; +	byte  saveVersion = 0; -	Common::String fileName = getSavegameFilename(0); +	saveDescription.clear(); +	saveDate = 0; +	saveTime = 0; +	saveIsValid = false; -	int result = saveGame(fileName, "Default savegame"); -	if (result != errOK) -		messageBox("Error saving game."); -	return result; -} +	debugC(4, kDebugLevelMain | kDebugLevelSavegame, "Current game id is %s", _targetName.c_str()); -int AgiEngine::loadGameDialog() { -	if (!ConfMan.getBool("originalsaveload")) -		return scummVMSaveLoadDialog(false); +	if (!(in = _saveFileMan->openForLoading(fileName))) { +		debugC(4, kDebugLevelMain | kDebugLevelSavegame, "File %s does not exist", fileName.c_str()); +		return false; + +	} else { +		debugC(4, kDebugLevelMain | kDebugLevelSavegame, "Successfully opened %s for reading", fileName.c_str()); -	int slot = 0; -	int hm, vm, hp, vp;	// box margins -	int w; +		uint32 type = in->readUint32BE(); -	hm = 1; -	vm = 3; -	hp = hm * CHAR_COLS; -	vp = vm * CHAR_LINES; -	w = (40 - 2 * hm) - 1; +		if (type != AGIflag) { +			warning("This doesn't appear to be an AGI savegame"); +			saveDescription += "[ScummVM: not an AGI save]"; +			delete in; +			return true; +		} -	_sprites->eraseBoth(); -	_sound->stopSound(); +		debugC(6, kDebugLevelMain | kDebugLevelSavegame, "Has AGI flag, good start"); +		if (in->read(saveGameDescription, 31) != 31) { +			warning("unexpected EOF"); +			delete in; +			saveDescription += "[ScummVM: invalid save]"; +			return true; +		} -	drawWindow(hp, vp, GFX_WIDTH - hp, GFX_HEIGHT - vp); -	printText("Select a game which you wish to\nrestore:", -			0, hm + 1, vm + 1, w, MSG_BOX_TEXT, MSG_BOX_COLOR); +		for (curPos = 0; curPos < 31; curPos++) { +			if (!saveGameDescription[curPos]) +				break; +		} +		if (curPos >= 31) { +			warning("corrupted description"); +			delete in; +			saveDescription += "[ScummVM: invalid save]"; +			return true; +		} -	slot = selectSlot(); +		saveVersion = in->readByte(); +		if (saveVersion > SAVEGAME_CURRENT_VERSION) { +			warning("save from a future ScummVM, not supported"); +			delete in; +			saveDescription += "[ScummVM: not supported]"; +			return true; +		} -	if (slot < 0) { -		if (slot == -1) // slot = -2 when GMM was launched -			messageBox("Game NOT restored."); +		if (saveVersion >= 4) { +			// We don't need the thumbnail here, so just read it and discard it +			Graphics::skipThumbnail(*in); -		return errOK; +			saveDate = in->readUint32BE(); +			saveTime = in->readUint16BE(); + +			// save date is DDMMYYYY, we need a proper format +			byte saveDateDay = saveDate >> 24; +			byte saveDateMonth = (saveDate >> 16) & 0xFF; +			uint16 saveDateYear = saveDate & 0xFFFF; + +			saveDate = (saveDateYear << 16) | (saveDateMonth << 8) | saveDateDay; + +		} else { +			saveDate = 0; +			saveTime = 0; +		} + +		saveDescription += saveGameDescription; +		saveIsValid = true; + +		delete in; +		return true;  	} +} -	return doLoad(_firstSlot + slot, true); +bool AgiEngine::loadGameAutomatic() { +	int16 automaticRestoreGameSlotId = 0; + +	automaticRestoreGameSlotId = _systemUI->figureOutAutomaticRestoreGameSlot(_game.automaticSaveDescription); +	if (automaticRestoreGameSlotId >= 0) { +		if (doLoad(automaticRestoreGameSlotId, true) == errOK) { +			return true; +		} +	} +	return false;  } -int AgiEngine::loadGameSimple() { +bool AgiEngine::loadGameDialog() { +	int16 restoreGameSlotId = 0; +  	if (!ConfMan.getBool("originalsaveload"))  		return scummVMSaveLoadDialog(false); -	else -		return doLoad(0, true); + +	restoreGameSlotId = _systemUI->askForRestoreGameSlot(); +	if (restoreGameSlotId >= 0) { +		if (doLoad(restoreGameSlotId, true) == errOK) { +			return true; +		} +	} +	return errOK; +} + +// Try to figure out either the slot, that is currently using the automatic saved game description +// or get a new slot. +// If we fail, return false, so that the regular saved game dialog is called +// Original AGI was limited to 12 saves, we are effectively limited to 100 saves at the moment. +// +// btw. this also means that entering an existant name in Mixed Up Mother Goose will effectively overwrite +// that saved game. This is also what original AGI did. +bool AgiEngine::saveGameAutomatic() { +	int16 automaticSaveGameSlotId = 0; + +	automaticSaveGameSlotId = _systemUI->figureOutAutomaticSaveGameSlot(_game.automaticSaveDescription); +	if (automaticSaveGameSlotId >= 0) { +		Common::String slotDescription(_game.automaticSaveDescription); + +		// WORKAROUND: Remove window in case one is currently shown, otherwise it would get saved in the thumbnail +		// Happens for Mixed Up Mother Goose. The scripts close the window after saving. +		// Original interpreter obviously did not do this, but original interpreter also did not save thumbnails. +		_text->closeWindow(); + +		if (doSave(automaticSaveGameSlotId, slotDescription) == errOK) { +			return true; +		} +	} +	return false;  } +bool AgiEngine::saveGameDialog() { +	int16 saveGameSlotId = 0; +	Common::String slotDescription; + +	if (!ConfMan.getBool("originalsaveload")) +		return scummVMSaveLoadDialog(true); + +	saveGameSlotId = _systemUI->askForSaveGameSlot(); +	if (saveGameSlotId >= 0) { +		if (_systemUI->askForSaveGameDescription(saveGameSlotId, slotDescription)) { +			if (doSave(saveGameSlotId, slotDescription) == errOK) { +				return true; +			} +		} +	} +	return false; +} + +  void AgiEngine::recordImageStackCall(uint8 type, int16 p1, int16 p2, int16 p3,  		int16 p4, int16 p5, int16 p6, int16 p7) {  	ImageStackElement pnew; @@ -1019,11 +967,11 @@ void AgiEngine::replayImageStackCall(uint8 type, int16 p1, int16 p2, int16 p3,  	switch (type) {  	case ADD_PIC:  		debugC(8, kDebugLevelMain, "--- decoding picture %d ---", p1); -		agiLoadResource(rPICTURE, p1); +		agiLoadResource(RESOURCETYPE_PICTURE, p1);  		_picture->decodePicture(p1, p2, p3 != 0);  		break;  	case ADD_VIEW: -		agiLoadResource(rVIEW, p1); +		agiLoadResource(RESOURCETYPE_VIEW, p1);  		_sprites->addToPic(p1, p2, p3, p4, p5, p6, p7);  		break;  	} @@ -1041,12 +989,12 @@ void AgiEngine::checkQuickLoad() {  	if (ConfMan.hasKey("save_slot")) {  		Common::String saveNameBuffer = getSavegameFilename(ConfMan.getInt("save_slot")); -		_sprites->eraseBoth(); +		_sprites->eraseSprites();  		_sound->stopSound();  		if (loadGame(saveNameBuffer, false) == errOK) {	 // Do not check game id  			_game.exitAllLogics = 1; -			_menu->enableAll(); +			_menu->itemEnableAll();  		}  	}  } @@ -1054,21 +1002,21 @@ void AgiEngine::checkQuickLoad() {  Common::Error AgiEngine::loadGameState(int slot) {  	Common::String saveLoadSlot = getSavegameFilename(slot); -	_sprites->eraseBoth(); +	_sprites->eraseSprites();  	_sound->stopSound();  	if (loadGame(saveLoadSlot) == errOK) {  		_game.exitAllLogics = 1; -		_menu->enableAll(); +		_menu->itemEnableAll();  		return Common::kNoError;  	} else {  		return Common::kUnknownError;  	}  } -Common::Error AgiEngine::saveGameState(int slot, const Common::String &desc) { +Common::Error AgiEngine::saveGameState(int slot, const Common::String &description) {  	Common::String saveLoadSlot = getSavegameFilename(slot); -	if (saveGame(saveLoadSlot, desc) == errOK) +	if (saveGame(saveLoadSlot, description) == errOK)  		return Common::kNoError;  	else  		return Common::kUnknownError; diff --git a/engines/agi/sound_pcjr.cpp b/engines/agi/sound_pcjr.cpp index ea7a2789e0..d875fa0b97 100644 --- a/engines/agi/sound_pcjr.cpp +++ b/engines/agi/sound_pcjr.cpp @@ -236,7 +236,7 @@ int SoundGenPCJr::getNextNote_v2(int ch) {  	assert(ch < CHAN_MAX); -	if (!_vm->getflag(fSoundOn)) +	if (!_vm->getflag(VM_FLAG_SOUND_ON))  		return -1;  	tpcm = &_tchannel[ch]; diff --git a/engines/agi/sound_sarien.cpp b/engines/agi/sound_sarien.cpp index 98479f3edc..d197f85832 100644 --- a/engines/agi/sound_sarien.cpp +++ b/engines/agi/sound_sarien.cpp @@ -161,7 +161,7 @@ void SoundGenSarien::stopNote(int i) {  }  void SoundGenSarien::playNote(int i, int freq, int vol) { -	if (!_vm->getflag(fSoundOn)) +	if (!_vm->getflag(VM_FLAG_SOUND_ON))  		vol = 0;  	else if (vol && _vm->_soundemu == SOUND_EMU_PC)  		vol = 160; diff --git a/engines/agi/sprite.cpp b/engines/agi/sprite.cpp index 92f88d8fcb..1124aef742 100644 --- a/engines/agi/sprite.cpp +++ b/engines/agi/sprite.cpp @@ -23,530 +23,392 @@  #include "agi/agi.h"  #include "agi/sprite.h"  #include "agi/graphics.h" +#include "agi/text.h"  namespace Agi { -/** - * Sprite structure. - * This structure holds information on visible and priority data of - * a rectangular area of the AGI screen. Sprites are chained in two - * circular lists, one for updating and other for non-updating sprites. - */ -struct Sprite { -	VtEntry *v;		/**< pointer to view table entry */ -	int16 xPos;			/**< x coordinate of the sprite */ -	int16 yPos;			/**< y coordinate of the sprite */ -	int16 xSize;			/**< width of the sprite */ -	int16 ySize;			/**< height of the sprite */ -	uint8 *buffer;			/**< buffer to store background data */ -}; - -/* - * Sprite pool replaces dynamic allocation - */ -#undef ALLOC_DEBUG - - -#define POOL_SIZE 68000		// Gold Rush mine room needs > 50000 -							// Speeder bike challenge needs > 67000 - -void *SpritesMgr::poolAlloc(int size) { -	uint8 *x; - -	// Adjust size to sizeof(void *) boundary to prevent data misalignment -	// errors. -	const int alignPadding = sizeof(void *) - 1; -	size = (size + alignPadding) & ~alignPadding; - -	x = _poolTop; -	_poolTop += size; - -	if (_poolTop >= (uint8 *)_spritePool + POOL_SIZE) { -		debugC(1, kDebugLevelMain | kDebugLevelResources, "not enough memory"); -		_poolTop = x; -		return NULL; -	} - -	return x; +SpritesMgr::SpritesMgr(AgiEngine *agi, GfxMgr *gfx) { +	_vm = agi; +	_gfx = gfx;  } -// Note: it's critical that pool_release() is called in the exact -//         reverse order of pool_alloc() -void SpritesMgr::poolRelease(void *s) { -	_poolTop = (uint8 *)s; +SpritesMgr::~SpritesMgr() { +	_spriteRegularList.clear(); +	_spriteStaticList.clear();  } -/* - * Blitter functions - */ - -// Blit one pixel considering the priorities -void SpritesMgr::blitPixel(uint8 *p, uint8 *end, uint8 col, int spr, int width, int *hidden) { -	int epr = 0, pr = 0;	// effective and real priorities - -	// CM: priority 15 overrides control lines and is ignored when -	//     tracking effective priority. This tweak is needed to fix -	//     Sarien bug #451768, and should not affect Sierra games because -	//     sprites shouldn't have priority 15 (like the AGI Mouse -	//     demo "mouse pointer") -	// -	// Update: this solution breaks other games, and can't be used. - -	if (p >= end) -		return; - -	// Check if we're on a control line -	if ((pr = *p & 0xf0) < 0x30) { -		uint8 *p1; -		// Yes, get effective priority going down -		for (p1 = p; p1 < end && (epr = *p1 & 0xf0) < 0x30; p1 += width) -			; -		if (p1 >= end) -			epr = 0x40; -	} else { -		epr = pr; -	} - -	if (spr >= epr) { -		// Keep control line information visible, but put our -		// priority over water (0x30) surface -		if (_vm->getFeatures() & (GF_AGI256 | GF_AGI256_2)) -			*(p + FROM_SBUF16_TO_SBUF256_OFFSET) = col; // Write to 256 color buffer -		else -			*p = (pr < 0x30 ? pr : spr) | col; // Write to 16 color (+control line/priority info) buffer - -		*hidden = false; - -		// Except if our priority is 15, which should never happen -		// (fixes Sarien bug #451768) -		// -		// Update: breaks other games, can't be used -		// -		// if (spr == 0xf0) -		//      *p = spr | col; +static bool sortSpriteHelper(const Sprite &entry1, const Sprite &entry2) { +	if (entry1.sortOrder == entry2.sortOrder) { +		// If sort-order is the same, we sort according to given order +		// which makes this sort stable. +		return entry1.givenOrderNr < entry2.givenOrderNr;  	} +	return entry1.sortOrder < entry2.sortOrder;  } +void SpritesMgr::buildRegularSpriteList() { +	ScreenObjEntry *screenObj = NULL; +	uint16 givenOrderNr = 0; -int SpritesMgr::blitCel(int x, int y, int spr, ViewCel *c, bool agi256_2) { -	uint8 *p0, *p, *q = NULL, *end; -	int i, j, t, m, col; -	int hidden = true; - -	// Fixes Sarien bug #477841 (crash in PQ1 map C4 when y == -2) -	if (y < 0) -		y = 0; -	if (x < 0) -		x = 0; -	if (y >= _HEIGHT) -		y = _HEIGHT - 1; -	if (x >= _WIDTH) -		x = _WIDTH - 1; - -	q = c->data; -	t = c->transparency; -	m = c->mirror; -	spr <<= 4; -	p0 = &_vm->_game.sbuf16c[x + y * _WIDTH + m * (c->width - 1)]; - -	end = _vm->_game.sbuf16c + _WIDTH * _HEIGHT; - -	for (i = 0; i < c->height; i++) { -		p = p0; -		while (*q) { -			col = agi256_2 ? *q : (*q & 0xf0) >> 4; // Uses whole byte for color info with AGI256-2 -			for (j = agi256_2 ? 1 : *q & 0x0f; j; j--, p += 1 - 2 * m) { // No RLE with AGI256-2 -				if (col != t) { -					blitPixel(p, end, col, spr, _WIDTH, &hidden); -				} -			} -			q++; +	freeList(_spriteRegularList); +	for (screenObj = _vm->_game.screenObjTable; screenObj < &_vm->_game.screenObjTable[SCREENOBJECTS_MAX]; screenObj++) { +		if ((screenObj->flags & (fAnimated | fUpdate | fDrawn)) == (fAnimated | fUpdate | fDrawn)) { +			buildSpriteListAdd(givenOrderNr, screenObj, _spriteRegularList); +			givenOrderNr++;  		} -		p0 += _WIDTH; -		q++;  	} -	return hidden; +	// Now sort this list +	Common::sort(_spriteRegularList.begin(), _spriteRegularList.end(), sortSpriteHelper); +//	warning("buildRegular: %d", _spriteRegularList.size());  } -void SpritesMgr::objsSaveArea(Sprite *s) { -	int y; -	int16 xPos = s->xPos, yPos = s->yPos; -	int16 xSize = s->xSize, ySize = s->ySize; -	uint8 *p0, *q; +void SpritesMgr::buildStaticSpriteList() { +	ScreenObjEntry *screenObj = NULL; +	uint16 givenOrderNr = 0; -	if (xPos + xSize > _WIDTH) -		xSize = _WIDTH - xPos; - -	if (xPos < 0) { -		xSize += xPos; -		xPos = 0; +	freeList(_spriteStaticList); +	for (screenObj = _vm->_game.screenObjTable; screenObj < &_vm->_game.screenObjTable[SCREENOBJECTS_MAX]; screenObj++) { +		if ((screenObj->flags & (fAnimated | fUpdate | fDrawn)) == (fAnimated | fDrawn)) { // DIFFERENCE IN HERE! +			buildSpriteListAdd(givenOrderNr, screenObj, _spriteStaticList); +			givenOrderNr++; +		}  	} -	if (yPos + ySize > _HEIGHT) -		ySize = _HEIGHT - yPos; +	// Now sort this list +	Common::sort(_spriteStaticList.begin(), _spriteStaticList.end(), sortSpriteHelper); +} -	if (yPos < 0) { -		ySize += yPos; -		yPos = 0; -	} +void SpritesMgr::buildAllSpriteLists() { +	buildStaticSpriteList(); +	buildRegularSpriteList(); +} + +void SpritesMgr::buildSpriteListAdd(uint16 givenOrderNr, ScreenObjEntry *screenObj, SpriteList &spriteList) { +	Sprite spriteEntry; -	if (xSize <= 0 || ySize <= 0) +	// Check, if screen object points to currently loaded view, if not don't add it +	if (!(_vm->_game.dirView[screenObj->currentViewNr].flags & RES_LOADED))  		return; -	p0 = &_vm->_game.sbuf[xPos + yPos * _WIDTH]; -	q = s->buffer; -	for (y = 0; y < ySize; y++) { -		memcpy(q, p0, xSize); -		q += xSize; -		p0 += _WIDTH; +	spriteEntry.givenOrderNr = givenOrderNr; +//	warning("sprite add objNr %d", screenObjPtr->objectNr); +	if (screenObj->flags & fFixedPriority) { +		spriteEntry.sortOrder = _gfx->priorityToY(screenObj->priority); +//		warning(" - priorityToY (fixed) %d -> %d", screenObj->priority, spriteEntry.sortOrder); +	} else { +		spriteEntry.sortOrder = screenObj->yPos; +//		warning(" - Ypos %d -> %d", screenObjPtr->yPos, spriteEntry.sortOrder);  	} -} -void SpritesMgr::objsRestoreArea(Sprite *s) { -	int y, offset; -	int16 xPos = s->xPos, yPos = s->yPos; -	int16 xSize = s->xSize, ySize = s->ySize; -	uint8 *q; -	uint32 pos0; +	spriteEntry.screenObjPtr = screenObj; +	spriteEntry.xPos = screenObj->xPos; +	spriteEntry.yPos = (screenObj->yPos) - (screenObj->ySize) + 1; +	spriteEntry.xSize = screenObj->xSize; +	spriteEntry.ySize = screenObj->ySize; +//	warning("list-add: %d, %d, original yPos: %d, ySize: %d", spriteEntry.xPos, spriteEntry.yPos, screenObj->yPos, screenObj->ySize); +	spriteEntry.backgroundBuffer = (uint8 *)malloc(spriteEntry.xSize * spriteEntry.ySize * 2); // for visual + priority data +	assert(spriteEntry.backgroundBuffer); +	spriteList.push_back(spriteEntry); +} -	if (xPos + xSize > _WIDTH) -		xSize = _WIDTH - xPos; +void SpritesMgr::freeList(SpriteList &spriteList) { +	SpriteList::iterator iter; +	for (iter = spriteList.reverse_begin(); iter != spriteList.end(); iter--) { +		Sprite &sprite = *iter; -	if (xPos < 0) { -		xSize += xPos; -		xPos = 0; +		free(sprite.backgroundBuffer);  	} +	spriteList.clear(); +} -	if (yPos + ySize > _HEIGHT) -		ySize = _HEIGHT - yPos; +void SpritesMgr::freeRegularSprites() { +	freeList(_spriteRegularList); +} -	if (yPos < 0) { -		ySize += yPos; -		yPos = 0; -	} +void SpritesMgr::freeStaticSprites() { +	freeList(_spriteStaticList); +} -	if (xSize <= 0 || ySize <= 0) -		return; +void SpritesMgr::freeAllSprites() { +	freeList(_spriteRegularList); +	freeList(_spriteStaticList); +} -	pos0 = xPos + yPos * _WIDTH; -	q = s->buffer; -	offset = _vm->_game.lineMinPrint * CHAR_LINES; -	for (y = 0; y < ySize; y++) { -		memcpy(&_vm->_game.sbuf[pos0], q, xSize); -		_gfx->putPixelsA(xPos, yPos + y + offset, xSize, &_vm->_game.sbuf16c[pos0]); -		q += xSize; -		pos0 += _WIDTH; +void SpritesMgr::eraseSprites(SpriteList &spriteList) { +	SpriteList::iterator iter; +//	warning("eraseSprites - count %d", spriteList.size()); +	for (iter = spriteList.reverse_begin(); iter != spriteList.end(); iter--) { +		Sprite &sprite = *iter; +		_gfx->block_restore(sprite.xPos, sprite.yPos, sprite.xSize, sprite.ySize, sprite.backgroundBuffer);  	} -	// WORKAROUND (see ScummVM bug #1945716) -	// When set.view command is called, current code cannot detect  this situation while updating -	// Thus we force removal of the old sprite -	if (s->v && s->v->viewReplaced) { -		commitBlock(xPos, yPos, xPos + xSize, yPos + ySize); -		s->v->viewReplaced = false; -	} +	freeList(spriteList);  } -  /** - * Condition to determine whether a sprite will be in the 'updating' list. + * Erase updating sprites. + * This function follows the list of all updating sprites and restores + * the visible and priority data of their background buffers back to + * the AGI screen. + * + * @see erase_nonupd_sprites() + * @see erase_both()   */ -bool SpritesMgr::testUpdating(VtEntry *v, AgiEngine *agi) { -	// Sanity check (see Sarien bug #779302) -	if (~agi->_game.dirView[v->currentView].flags & RES_LOADED) -		return false; - -	return (v->flags & (fAnimated | fUpdate | fDrawn)) == (fAnimated | fUpdate | fDrawn); +void SpritesMgr::eraseRegularSprites() { +	eraseSprites(_spriteRegularList);  } -/** - * Condition to determine whether a sprite will be in the 'non-updating' list. - */ -bool SpritesMgr::testNotUpdating(VtEntry *v, AgiEngine *vm) { -	// Sanity check (see Sarien bug #779302) -	if (~vm->_game.dirView[v->currentView].flags & RES_LOADED) -		return false; +void SpritesMgr::eraseStaticSprites() { +	eraseSprites(_spriteStaticList); +} -	return (v->flags & (fAnimated | fUpdate | fDrawn)) == (fAnimated | fDrawn); +void SpritesMgr::eraseSprites() { +	eraseSprites(_spriteRegularList); +	eraseSprites(_spriteStaticList);  }  /** - * Convert sprite priority to y value. + * Draw all sprites in the given list.   */ -int SpritesMgr::prioToY(int p) { -	int i; +void SpritesMgr::drawSprites(SpriteList &spriteList) { +	SpriteList::iterator iter; +//	warning("drawSprites"); -	if (p == 0) -		return -1; +	for (iter = spriteList.begin(); iter != spriteList.end(); ++iter) { +		Sprite &sprite = *iter; +		ScreenObjEntry *screenObj = sprite.screenObjPtr; -	for (i = 167; i >= 0; i--) { -		if (_vm->_game.priTable[i] < p) -			return i; +		_gfx->block_save(sprite.xPos, sprite.yPos, sprite.xSize, sprite.ySize, sprite.backgroundBuffer); +		//debugC(8, kDebugLevelSprites, "drawSprites(): s->v->entry = %d (prio %d)", s->viewPtr->entry, s->viewPtr->priority); +//		warning("sprite %d (view %d), priority %d, sort %d, givenOrder %d", screenObj->objectNr, screenObj->currentView, screenObj->priority, sprite.sortOrder, sprite.givenOrderNr); +		drawCel(screenObj);  	} - -	return -1;		// (p - 5) * 12 + 48;  }  /** - * Create and initialize a new sprite structure. - */ -Sprite *SpritesMgr::newSprite(VtEntry *v) { -	Sprite *s; -	s = (Sprite *)poolAlloc(sizeof(Sprite)); -	if (s == NULL) -		return NULL; - -	s->v = v;		// link sprite to associated view table entry -	s->xPos = v->xPos; -	s->yPos = v->yPos - v->ySize + 1; -	s->xSize = v->xSize; -	s->ySize = v->ySize; -	s->buffer = (uint8 *)poolAlloc(s->xSize * s->ySize); -	v->s = s;		// link view table entry to this sprite - -	return s; -} - -/** - * Insert sprite in the specified sprite list. + * Blit updating sprites. + * This function follows the list of all updating sprites and blits + * them on the AGI screen. + * + * @see blit_nonupd_sprites() + * @see blit_both()   */ -void SpritesMgr::sprAddlist(SpriteList &l, VtEntry *v) { -	Sprite *s = newSprite(v); -	l.push_back(s); -} +void SpritesMgr::drawRegularSpriteList() { +	debugC(7, kDebugLevelSprites, "drawRegularSpriteList()"); +	drawSprites(_spriteRegularList); +} + +void SpritesMgr::drawStaticSpriteList() { +	//debugC(7, kDebugLevelSprites, "drawRegularSpriteList()"); +	drawSprites(_spriteStaticList); +} + +void SpritesMgr::drawAllSpriteLists() { +	drawSprites(_spriteStaticList); +	drawSprites(_spriteRegularList); +} + +void SpritesMgr::drawCel(ScreenObjEntry *screenObj) { +	int16 curX = screenObj->xPos; +	int16 baseX = screenObj->xPos; +	int16 curY = screenObj->yPos; +	AgiViewCel *celPtr = screenObj->celData; +	byte *celDataPtr = celPtr->rawBitmap; +	uint8 remainingCelHeight = celPtr->height; +	uint8 celWidth = celPtr->width; +	byte celClearKey = celPtr->clearKey; +	byte viewPriority = screenObj->priority; +	byte screenPriority = 0; +	byte curColor = 0; +	byte isViewHidden = true; + +	// Adjust vertical position, given yPos is lower left, but we need upper left +	curY = curY - celPtr->height + 1; + +	while (remainingCelHeight) { +		for (int16 loopX = 0; loopX < celWidth; loopX++) { +			curColor = *celDataPtr++; + +			if (curColor != celClearKey) { +				screenPriority = _gfx->getPriority(curX, curY); +				if (screenPriority <= 2) { +					// control data found +					if (_gfx->checkControlPixel(curX, curY, viewPriority)) { +						_gfx->putPixel(curX, curY, GFX_SCREEN_MASK_VISUAL, curColor, 0); +						isViewHidden = false; +					} +				} else if (screenPriority <= viewPriority) { +					_gfx->putPixel(curX, curY, GFX_SCREEN_MASK_ALL, curColor, viewPriority); +					isViewHidden = false; +				} -/** - * Sort sprites from lower y values to build a sprite list. - */ -void SpritesMgr::buildList(SpriteList &l, bool (*test)(VtEntry *, AgiEngine *)) { -	int i, j, k; -	VtEntry *v; -	VtEntry *entry[0x100]; -	int yVal[0x100]; -	int minY = 0xff, minIndex = 0; - -	// fill the arrays with all sprites that satisfy the 'test' -	// condition and their y values -	i = 0; -	for (v = _vm->_game.viewTable; v < &_vm->_game.viewTable[MAX_VIEWTABLE]; v++) { -		if ((*test)(v, _vm)) { -			entry[i] = v; -			yVal[i] = v->flags & fFixedPriority ? prioToY(v->priority) : v->yPos; -			i++; +			} +			curX++;  		} + +		// go to next vertical position +		remainingCelHeight--; +		curX = baseX; +		curY++;  	} -	debugC(5, kDebugLevelSprites, "buildList() --> entries %d", i); +	if (screenObj->objectNr == 0) {	// if ego, update if ego is visible at the moment +		_vm->setflag(VM_FLAG_EGO_INVISIBLE, isViewHidden); +	} +} -	// now look for the smallest y value in the array and put that -	// sprite in the list -	for (j = 0; j < i; j++) { -		minY = 0xff; -		for (k = 0; k < i; k++) { -			if (yVal[k] < minY) { -				minIndex = k; -				minY = yVal[k]; -			} -		} +void SpritesMgr::showSprite(ScreenObjEntry *screenObj) { +	int16 x = 0; +	int16 y = 0; +	int16 width = 0; +	int16 height = 0; -		yVal[minIndex] = 0xff; -		sprAddlist(l, entry[minIndex]); -	} -} +	int16 view_height_prev = 0; +	int16 view_width_prev = 0; -/** - * Build list of updating sprites. - */ -void SpritesMgr::buildUpdBlitlist() { -	buildList(_sprUpd, testUpdating); -} +	int16 y2 = 0; +	int16 height1 = 0; +	int16 height2 = 0; -/** - * Build list of non-updating sprites. - */ -void SpritesMgr::buildNonupdBlitlist() { -	buildList(_sprNonupd, testNotUpdating); -} +	int16 x2 = 0; +	int16 width1 = 0; +	int16 width2 = 0; -/** - * Clear the given sprite list. - */ -void SpritesMgr::freeList(SpriteList &l) { -	SpriteList::iterator iter; -	for (iter = l.reverse_begin(); iter != l.end(); ) { -		Sprite* s = *iter; +	if (!_vm->_game.pictureShown) +		return; -		poolRelease(s->buffer); -		poolRelease(s); -		iter = l.reverse_erase(iter); -	} -} +	view_height_prev = screenObj->ySize_prev; +	view_width_prev  = screenObj->xSize_prev; -/** - * Copy sprites from the pic buffer to the screen buffer, and check if - * sprites of the given list have moved. - */ -void SpritesMgr::commitSprites(SpriteList &l, bool immediate) { -	SpriteList::iterator iter; -	for (iter = l.begin(); iter != l.end(); ++iter) { -		Sprite *s = *iter; -		int x1, y1, x2, y2; +	screenObj->ySize_prev = screenObj->ySize; +	screenObj->xSize_prev = screenObj->xSize; -		x1 = MIN((int)MIN(s->v->xPos, s->v->xPos2), MIN(s->v->xPos + s->v->celData->width, s->v->xPos2 + s->v->celData2->width)); -		x2 = MAX((int)MAX(s->v->xPos, s->v->xPos2), MAX(s->v->xPos + s->v->celData->width, s->v->xPos2 + s->v->celData2->width)); -		y1 = MIN((int)MIN(s->v->yPos, s->v->yPos2), MIN(s->v->yPos - s->v->celData->height, s->v->yPos2 - s->v->celData2->height)); -		y2 = MAX((int)MAX(s->v->yPos, s->v->yPos2), MAX(s->v->yPos - s->v->celData->height, s->v->yPos2 - s->v->celData2->height)); +	if (screenObj->yPos < screenObj->yPos_prev) { +		y = screenObj->yPos_prev; +		y2 = screenObj->yPos; -		s->v->celData2 = s->v->celData; +		height1 = view_height_prev; +		height2 = screenObj->ySize; +	} else { +		y = screenObj->yPos; +		y2 = screenObj->yPos_prev; -		commitBlock(x1, y1, x2, y2, immediate); +		height1 = screenObj->ySize; +		height2 = view_height_prev; +	} -		if (s->v->stepTimeCount != s->v->stepTime) -			continue; +	if ((y2 - height2) > (y - height1)) { +		height = height1; +	} else { +		height = y - y2 + height2; +	} -		if (s->v->xPos == s->v->xPos2 && s->v->yPos == s->v->yPos2) { -			s->v->flags |= fDidntMove; -			continue; -		} +	if (screenObj->xPos > screenObj->xPos_prev) { +		x = screenObj->xPos_prev; +		x2 = screenObj->xPos; +		width1 = view_width_prev; +		width2 = screenObj->xSize; +	} else { +		x = screenObj->xPos; +		x2 = screenObj->xPos_prev; +		width1 = screenObj->xSize; +		width2 = view_width_prev; +	} -		s->v->xPos2 = s->v->xPos; -		s->v->yPos2 = s->v->yPos; -		s->v->flags &= ~fDidntMove; +	if ((x2 + width2) < (x + width1)) { +		width = width1; +	} else { +		width = width2 + x2 - x;  	} -} -/** - * Erase all sprites in the given list. - */ -void SpritesMgr::eraseSprites(SpriteList &l) { -	SpriteList::iterator iter; -	for (iter = l.reverse_begin(); iter != l.end(); --iter) { -		Sprite *s = *iter; -		objsRestoreArea(s); +	if ((x + width) > 161) { +		width = 161 - x; +	} + +	if (1 < (height - y)) { +		height = y + 1;  	} -	freeList(l); +	// render this block +	_gfx->render_Block(x, y, width, height);  } -/** - * Blit all sprites in the given list. - */ -void SpritesMgr::blitSprites(SpriteList& l) { -	int hidden; +void SpritesMgr::showSprites(SpriteList &spriteList) {  	SpriteList::iterator iter; +	ScreenObjEntry *screenObjPtr = NULL; -	for (iter = l.begin(); iter != l.end(); ++iter) { -		Sprite *s = *iter; +	for (iter = spriteList.begin(); iter != spriteList.end(); ++iter) { +		Sprite &sprite = *iter; +		screenObjPtr = sprite.screenObjPtr; -		objsSaveArea(s); -		debugC(8, kDebugLevelSprites, "blitSprites(): s->v->entry = %d (prio %d)", s->v->entry, s->v->priority); -		hidden = blitCel(s->xPos, s->yPos, s->v->priority, s->v->celData, s->v->viewData->agi256_2); +		showSprite(screenObjPtr); -		if (s->v->entry == 0) {	// if ego, update f1 -			_vm->setflag(fEgoInvisible, hidden); +		if (screenObjPtr->stepTimeCount == screenObjPtr->stepTime) { +			if ((screenObjPtr->xPos == screenObjPtr->xPos_prev) && (screenObjPtr->yPos == screenObjPtr->yPos_prev)) { +				screenObjPtr->flags |= fDidntMove; +			} else { +				screenObjPtr->xPos_prev = screenObjPtr->xPos; +				screenObjPtr->yPos_prev = screenObjPtr->yPos; +				screenObjPtr->flags &= ~fDidntMove; +			}  		}  	} +	g_system->updateScreen(); +	//g_system->delayMillis(20);  } -/* - * Public functions - */ - -void SpritesMgr::commitUpdSprites() { -	commitSprites(_sprUpd); +void SpritesMgr::showRegularSpriteList() { +	debugC(7, kDebugLevelSprites, "showRegularSpriteList()"); +	showSprites(_spriteRegularList);  } -void SpritesMgr::commitNonupdSprites() { -	commitSprites(_sprNonupd); +void SpritesMgr::showStaticSpriteList() { +	debugC(7, kDebugLevelSprites, "showStaticSpriteList()"); +	showSprites(_spriteStaticList);  } -// check moves in both lists -void SpritesMgr::commitBoth() { -	commitUpdSprites(); -	commitNonupdSprites(); +void SpritesMgr::showAllSpriteLists() { +	showSprites(_spriteStaticList); +	showSprites(_spriteRegularList);  }  /** - * Erase updating sprites. - * This function follows the list of all updating sprites and restores - * the visible and priority data of their background buffers back to - * the AGI screen. - * - * @see erase_nonupd_sprites() - * @see erase_both() + * Show object and description + * This function shows an object from the player's inventory, displaying + * a message box with the object description. + * @param n  Number of the object to show   */ -void SpritesMgr::eraseUpdSprites() { -	eraseSprites(_sprUpd); -} +void SpritesMgr::showObject(int16 viewNr) { +	ScreenObjEntry screenObj; +	uint8 *backgroundBuffer = NULL; -/** - * Erase non-updating sprites. - * This function follows the list of all non-updating sprites and restores - * the visible and priority data of their background buffers back to - * the AGI screen. - * - * @see erase_upd_sprites() - * @see erase_both() - */ -void SpritesMgr::eraseNonupdSprites() { -	eraseSprites(_sprNonupd); -} +	_vm->agiLoadResource(RESOURCETYPE_VIEW, viewNr); +	_vm->setView(&screenObj, viewNr); -/** - * Erase all sprites. - * This function follows the lists of all updating and non-updating - * sprites and restores the visible and priority data of their background - * buffers back to the AGI screen. - * - * @see erase_upd_sprites() - * @see erase_nonupd_sprites() - */ -void SpritesMgr::eraseBoth() { -	eraseUpdSprites(); -	eraseNonupdSprites(); -} +	screenObj.ySize_prev = screenObj.celData->height; +	screenObj.xSize_prev = screenObj.celData->width; +	screenObj.xPos_prev = ((SCRIPT_WIDTH - 1) - screenObj.xSize) / 2; +	screenObj.xPos = screenObj.xPos_prev; +	screenObj.yPos_prev = SCRIPT_HEIGHT - 1; +	screenObj.yPos = screenObj.yPos_prev; +	screenObj.priority = 15; +	screenObj.flags |= fFixedPriority; +	screenObj.objectNr = 255; // ??? -/** - * Blit updating sprites. - * This function follows the list of all updating sprites and blits - * them on the AGI screen. - * - * @see blit_nonupd_sprites() - * @see blit_both() - */ -void SpritesMgr::blitUpdSprites() { -	debugC(7, kDebugLevelSprites, "blitUpdSprites()"); -	buildUpdBlitlist(); -	blitSprites(_sprUpd); -} +	backgroundBuffer = (uint8 *)malloc(screenObj.xSize * screenObj.ySize * 2); // for visual + priority data -/** - * Blit non-updating sprites. - * This function follows the list of all non-updating sprites and blits - * them on the AGI screen. - * - * @see blit_upd_sprites() - * @see blit_both() - */ -void SpritesMgr::blitNonupdSprites() { -	debugC(7, kDebugLevelSprites, "blitNonupdSprites()"); -	buildNonupdBlitlist(); -	blitSprites(_sprNonupd); -} +	_gfx->block_save(screenObj.xPos, (screenObj.yPos - screenObj.ySize + 1), screenObj.xSize, screenObj.ySize, backgroundBuffer); +	drawCel(&screenObj); +	showSprite(&screenObj); -/** - * Blit all sprites. - * This function follows the lists of all updating and non-updating - * sprites and blits them on the AGI screen. - * - * @see blit_upd_sprites() - * @see blit_nonupd_sprites() - */ -void SpritesMgr::blitBoth() { -	blitNonupdSprites(); -	blitUpdSprites(); +	_vm->_text->messageBox((char *)_vm->_game.views[viewNr].description); + +	_gfx->block_restore(screenObj.xPos, (screenObj.yPos - screenObj.ySize + 1), screenObj.xSize, screenObj.ySize, backgroundBuffer); +	showSprite(&screenObj); + +	free(backgroundBuffer);  }  /** @@ -562,188 +424,107 @@ void SpritesMgr::blitBoth() {   * @param pri   priority to use   * @param mar   if < 4, create a margin around the the base of the cel   */ -void SpritesMgr::addToPic(int view, int loop, int cel, int x, int y, int pri, int mar) { -	ViewCel *c = NULL; -	int x1, y1, x2, y2, y3; -	uint8 *p1, *p2; - -	debugC(3, kDebugLevelSprites, "addToPic(view=%d, loop=%d, cel=%d, x=%d, y=%d, pri=%d, mar=%d)", view, loop, cel, x, y, pri, mar); - -	_vm->recordImageStackCall(ADD_VIEW, view, loop, cel, x, y, pri, mar); - -	// Was hardcoded to 8, changed to pri_table[y] to fix Gold -	// Rush (see Sarien bug #587558) -	if (pri == 0) -		pri = _vm->_game.priTable[y]; - -	c = &_vm->_game.views[view].loop[loop].cel[cel]; - -	x1 = x; -	y1 = y - c->height + 1; -	x2 = x + c->width - 1; -	y2 = y; - -	if (x1 < 0) { -		x2 -= x1; -		x1 = 0; -	} -	if (y1 < 0) { -		y2 -= y1; -		y1 = 0; +void SpritesMgr::addToPic(int16 viewNr, int16 loopNr, int16 celNr, int16 xPos, int16 yPos, int16 priority, int16 border) { +	debugC(3, kDebugLevelSprites, "addToPic(view=%d, loop=%d, cel=%d, x=%d, y=%d, pri=%d, border=%d)", viewNr, loopNr, celNr, xPos, yPos, priority, border); + +	_vm->recordImageStackCall(ADD_VIEW, viewNr, loopNr, celNr, xPos, yPos, priority, border); + +	ScreenObjEntry *screenObj = &_vm->_game.addToPicView; +	screenObj->objectNr = -1; // addToPic-view + +	_vm->setView(screenObj, viewNr); +	_vm->setLoop(screenObj, loopNr); +	_vm->setCel(screenObj, celNr); + +	screenObj->xSize_prev = screenObj->xSize; +	screenObj->ySize_prev = screenObj->ySize; +	screenObj->xPos_prev = xPos; +	screenObj->xPos = xPos; +	screenObj->yPos_prev = yPos; +	screenObj->yPos = yPos; +	screenObj->flags = fIgnoreObjects | fIgnoreHorizon | fFixedPriority; +	screenObj->priority = 15; +	_vm->fixPosition(screenObj); +	if (priority == 0) { +		screenObj->flags = fIgnoreHorizon;  	} -	if (x2 >= _WIDTH) -		x2 = _WIDTH - 1; -	if (y2 >= _HEIGHT) -		y2 = _HEIGHT - 1; - -	eraseBoth(); - -	debugC(4, kDebugLevelSprites, "blitCel(%d, %d, %d, c)", x, y, pri); -	blitCel(x1, y1, pri, c, _vm->_game.views[view].agi256_2); - -	// If margin is 0, 1, 2, or 3, the base of the cel is -	// surrounded with a rectangle of the corresponding priority. -	// If margin >= 4, this extra margin is not shown. -	// -	// -1 indicates ignore and is set for V1 -	if (mar < 4 && mar != -1) { -		// add rectangle around object, don't clobber control -		// info in priority data. The box extends to the end of -		// its priority band! -		y3 = (y2 / 12) * 12; - -		// SQ1 needs +1 (see Sarien bug #810331) -		if (_vm->getGameID() == GID_SQ1) -			y3++; - -		// don't let box extend below y. -		if (y3 > y2) y3 = y2; - -		p1 = &_vm->_game.sbuf16c[x1 + y3 * _WIDTH]; -		p2 = &_vm->_game.sbuf16c[x2 + y3 * _WIDTH]; - -		for (y = y3; y <= y2; y++) { -			if ((*p1 >> 4) >= 4) -				*p1 = (mar << 4) | (*p1 & 0x0f); - -			if ((*p2 >> 4) >= 4) -				*p2 = (mar << 4) | (*p2 & 0x0f); - -			p1 += _WIDTH; -			p2 += _WIDTH; -		} - -		debugC(4, kDebugLevelSprites, "pri box: %d %d %d %d (%d)", x1, y3, x2, y2, mar); -		p1 = &_vm->_game.sbuf16c[x1 + y3 * _WIDTH]; -		p2 = &_vm->_game.sbuf16c[x1 + y2 * _WIDTH]; -		for (x = x1; x <= x2; x++) { -			if ((*p1 >> 4) >= 4) -				*p1 = (mar << 4) | (*p1 & 0x0f); +	screenObj->priority = priority; -			if ((*p2 >> 4) >= 4) -				*p2 = (mar << 4) | (*p2 & 0x0f); - -			p1++; -			p2++; -		} +	eraseSprites(); +	 +	// bugs related to this code: required by Gold Rush (see Sarien bug #587558) +	if (screenObj->priority == 0) { +		screenObj->priority = _gfx->priorityFromY(screenObj->yPos);  	} +	drawCel(screenObj); -	blitBoth(); - -	commitBlock(x1, y1, x2, y2, true); -} - -/** - * Show object and description - * This function shows an object from the player's inventory, displaying - * a message box with the object description. - * @param n  Number of the object to show - */ -void SpritesMgr::showObj(int n) { -	ViewCel *c; -	Sprite s; -	int x1, y1, x2, y2; - -	_vm->agiLoadResource(rVIEW, n); -	if (!(c = &_vm->_game.views[n].loop[0].cel[0])) -		return; - -	x1 = (_WIDTH - c->width) / 2; -	y1 = 112; -	x2 = x1 + c->width - 1; -	y2 = y1 + c->height - 1; - -	s.xPos = x1; -	s.yPos = y1; -	s.xSize = c->width; -	s.ySize = c->height; -	s.buffer = (uint8 *)malloc(s.xSize * s.ySize); -	s.v = 0; - -	objsSaveArea(&s); -	blitCel(x1, y1, 15, c, _vm->_game.views[n].agi256_2); -	commitBlock(x1, y1, x2, y2, true); -	_vm->messageBox(_vm->_game.views[n].descr); -	objsRestoreArea(&s); -	commitBlock(x1, y1, x2, y2, true); - -	free(s.buffer); -} - -void SpritesMgr::commitBlock(int x1, int y1, int x2, int y2, bool immediate) { -	int i, w, offset; -	uint8 *q; - -	if (!_vm->_game.pictureShown) -		return; +	if (border <= 3) { +		// Create priority-box +		addToPicDrawPriorityBox(screenObj, border); +	} +	buildAllSpriteLists(); +	drawAllSpriteLists(); +	showSprite(screenObj); +} + +// bugs previously related to this: +// Sarien bug #247) +void SpritesMgr::addToPicDrawPriorityBox(ScreenObjEntry *screenObj, int16 border) { +	int16 priorityFromY = _gfx->priorityFromY(screenObj->yPos); +	int16 priorityHeight = 0; +	int16 curY = 0; +	int16 curX = 0; +	int16 height = 0; +	int16 width = 0; +	int16 offsetX = 0; +	 +	// Figure out the height of the box +	curY = screenObj->yPos; +	do { +		priorityHeight++; +		if (curY <= 0) +			break; +		curY--; +	} while (_gfx->priorityFromY(curY) == priorityFromY); + +	// box height may not be larger than the actual view +	if (screenObj->ySize < priorityHeight) +		priorityHeight = screenObj->ySize; + +	// now actually draw lower horizontal line +	curY = screenObj->yPos; +	curX = screenObj->xPos; +	 +	width = screenObj->xSize; +	while (width) { +		_gfx->putPixel(curX, curY, GFX_SCREEN_MASK_PRIORITY, 0, border); +		curX++; +		width--; +	} -	x1 = CLIP(x1, 0, _WIDTH - 1); -	x2 = CLIP(x2, 0, _WIDTH - 1); -	y1 = CLIP(y1, 0, _HEIGHT - 1); -	y2 = CLIP(y2, 0, _HEIGHT - 1); - -	// Check if a window is active, and clip the block commited to exclude the -	// window's contents. Fixes bug #3295652, and partially fixes bug #3080415. -	AgiBlock &window = _vm->_game.window; -	if (window.active) { -		if (y1 < window.y2 && y2 > window.y2 && (x1 < window.x2 || x2 > window.x1)) { -			// The top of the block covers the bottom of the window -			y1 = window.y2; +	if (priorityHeight > 1) { +		// Actual rectangle is needed +		curY = screenObj->yPos; +		curX = screenObj->xPos; +		offsetX = screenObj->xSize - 1; + +		height = priorityHeight - 1; +		while (height) { +			curY--; +			height--; +			_gfx->putPixel(curX, curY, GFX_SCREEN_MASK_PRIORITY, 0, border); // left line +			_gfx->putPixel(curX + offsetX, curY, GFX_SCREEN_MASK_PRIORITY, 0, border); // right line  		} -		if (y1 < window.y1 && y2 > window.y1 && (x1 < window.x2 || x2 > window.x1)) { -			// The bottom of the block covers the top of the window -			y2 = window.y1; +		// and finally the upper horizontal line +		width = screenObj->xSize - 2; +		curX++; +		while (width > 0) { +			_gfx->putPixel(curX, curY, GFX_SCREEN_MASK_PRIORITY, 0, border); +			curX++; +			width--;  		}  	} - -	debugC(7, kDebugLevelSprites, "commitBlock(%d, %d, %d, %d)", x1, y1, x2, y2); - -	w = x2 - x1 + 1; -	q = &_vm->_game.sbuf16c[x1 + _WIDTH * y1]; -	offset = _vm->_game.lineMinPrint * CHAR_LINES; - -	for (i = y1; i <= y2; i++) { -		_gfx->putPixelsA(x1, i + offset, w, q); -		q += _WIDTH; -	} - -	_gfx->flushBlockA(x1, y1 + offset, x2, y2 + offset); - -	if (immediate) -		_gfx->doUpdate(); -} - -SpritesMgr::SpritesMgr(AgiEngine *agi, GfxMgr *gfx) { -	_vm = agi; -	_gfx = gfx; - -	_spritePool = (uint8 *)malloc(POOL_SIZE); -	_poolTop = _spritePool; -} - -SpritesMgr::~SpritesMgr() { -	free(_spritePool);  }  } // End of namespace Agi diff --git a/engines/agi/sprite.h b/engines/agi/sprite.h index 549eb59832..0bb0547650 100644 --- a/engines/agi/sprite.h +++ b/engines/agi/sprite.h @@ -25,9 +25,25 @@  namespace Agi { +/** + * Sprite structure. + * This structure holds information on visible and priority data of + * a rectangular area of the AGI screen. Sprites are chained in two + * circular lists, one for updating and other for non-updating sprites. + */ +struct Sprite { +	uint16 givenOrderNr; +	uint16 sortOrder; +	ScreenObjEntry *screenObjPtr;	/**< pointer to view table entry */ +	int16 xPos;					/**< x coordinate of the sprite */ +	int16 yPos;					/**< y coordinate of the sprite */ +	int16 xSize;				/**< width of the sprite */ +	int16 ySize;				/**< height of the sprite */ +	byte *backgroundBuffer;		/**< buffer to store background data */ +}; -struct Sprite; -typedef Common::List<Sprite *> SpriteList; +typedef Common::List<Sprite> SpriteList; +typedef Common::List<Sprite *> SpritePtrList;  class AgiEngine;  class GfxMgr; @@ -38,35 +54,42 @@ private:  	GfxMgr *_gfx;  	AgiEngine *_vm; -	uint8 *_spritePool; -	uint8 *_poolTop; -  	//  	// Sprite management functions  	// -	SpriteList _sprUpd; -	SpriteList _sprNonupd; - -	void *poolAlloc(int size); -	void poolRelease(void *s); -	void blitPixel(uint8 *p, uint8 *end, uint8 col, int spr, int width, int *hidden); -	int blitCel(int x, int y, int spr, ViewCel *c, bool agi256_2); -	void objsSaveArea(Sprite *s); -	void objsRestoreArea(Sprite *s); - -	int prioToY(int p); -	Sprite *newSprite(VtEntry *v); -	void sprAddlist(SpriteList &l, VtEntry *v); -	void buildList(SpriteList &l, bool (*test)(VtEntry *, AgiEngine *)); -	void buildUpdBlitlist(); -	void buildNonupdBlitlist(); -	void freeList(SpriteList &l); -	void commitSprites(SpriteList &l, bool immediate = false); -	void eraseSprites(SpriteList &l); -	void blitSprites(SpriteList &l); -	static bool testUpdating(VtEntry *v, AgiEngine *); -	static bool testNotUpdating(VtEntry *v, AgiEngine *); +	SpriteList _spriteRegularList; +	SpriteList _spriteStaticList; + +public: +	void buildRegularSpriteList(); +	void buildStaticSpriteList(); +	void buildAllSpriteLists(); +	void buildSpriteListAdd(uint16 givenOrderNr, ScreenObjEntry *screenObj, SpriteList &spriteList); +	void freeList(SpriteList &spriteList); +	void freeRegularSprites(); +	void freeStaticSprites(); +	void freeAllSprites(); + +	void eraseSprites(SpriteList &spriteList); +	void eraseRegularSprites(); +	void eraseStaticSprites(); +	void eraseSprites(); + +	void drawSprites(SpriteList &spriteList); +	void drawRegularSpriteList(); +	void drawStaticSpriteList(); +	void drawAllSpriteLists(); + +	void drawCel(ScreenObjEntry *screenObj); + +	void showSprite(ScreenObjEntry *screenObj); +	void showSprites(SpriteList &spriteList); +	void showRegularSpriteList(); +	void showStaticSpriteList(); +	void showAllSpriteLists(); + +	void showObject(int16 viewNr);  public:  	SpritesMgr(AgiEngine *agi, GfxMgr *gfx); @@ -74,18 +97,8 @@ public:  	int initSprites();  	void deinitSprites(); -	void eraseUpdSprites(); -	void eraseNonupdSprites(); -	void eraseBoth(); -	void blitUpdSprites(); -	void blitNonupdSprites(); -	void blitBoth(); -	void commitUpdSprites(); -	void commitNonupdSprites(); -	void commitBoth(); -	void addToPic(int, int, int, int, int, int, int); -	void showObj(int); -	void commitBlock(int x1, int y1, int x2, int y2, bool immediate = false); +	void addToPic(int16 viewNr, int16 loopNr, int16 celNr, int16 xPos, int16 yPos, int16 priority, int16 border); +	void addToPicDrawPriorityBox(ScreenObjEntry *screenObj, int16 border);  };  } // End of namespace Agi diff --git a/engines/agi/systemui.cpp b/engines/agi/systemui.cpp new file mode 100644 index 0000000000..dc83c4d393 --- /dev/null +++ b/engines/agi/systemui.cpp @@ -0,0 +1,661 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "agi/agi.h" +#include "agi/graphics.h" +#include "agi/text.h" +#include "agi/keyboard.h" +#include "agi/systemui.h" + +namespace Agi { + +SystemUI::SystemUI(AgiEngine *vm, GfxMgr *gfx, TextMgr *text) { +	_vm = vm; +	_gfx = gfx; +	_text = text; + +	_textStatusScore = "Score:%v3 of %v7"; +	_textStatusSoundOn = "Sound:on"; +	_textStatusSoundOff = "Sound:off"; + +	_textPause = "      Game paused.\nPress Enter to continue."; +	_textRestart = "Press ENTER to restart\nthe game.\n\nPress ESC to continue\nthis game."; +	_textQuit = "Press ENTER to quit.\nPress ESC to keep playing."; + +	_textInventoryYouAreCarrying = "You are carrying:"; +	_textInventoryNothing = "nothing"; +	_textInventorySelectItems = "Press ENTER to select, ESC to cancel"; +	_textInventoryReturnToGame = "Press a key to return to the game"; + +	_textSaveGameSelectSlot = "Use the arrow keys to select the slot in which you wish to save the game. Press ENTER to save in the slot, ESC to not save a game."; +	_textSaveGameEnterDescription = "How would you like to describe this saved game?\n\n"; +	_textSaveGameVerify = "About to save the game\ndescribed as:\n\n%s\n\nPress ENTER to continue.\nPress ESC to cancel."; +	 +	_textRestoreGameNoSlots = "There are no games to\nrestore in\n\n ScummVM saved game directory\n\nPress ENTER to continue."; +	_textRestoreGameSelectSlot = "Use the arrow keys to select the game which you wish to restore. Press ENTER to restore the game, ESC to not restore a game."; +	_textRestoreGameError = "Error in restoring game.\nPress ENTER to quit."; +	_textRestoreGameVerify = "About to restore the game\ndescribed as:\n\n%s\n\nPress ENTER to continue.\nPress ESC to cancel."; + +	// Replace with translated text, when needed +	switch (_vm->getLanguage()) { +	case Common::RU_RUS: +		_textStatusScore = "\x91\xE7\xA5\xE2: %v3 \xA8\xA7 %v7"; +		_textStatusSoundOn = "\x87\xA2\xE3\xAA: \xA2\xAA\xAB"; +		_textStatusSoundOff = "\x87\xA2\xE3\xAA: \xA2\xEB\xAA\xAB"; + +		_textPause = "  \x88\xA3\xE0\xA0 \xAE\xE1\xE2\xA0\xAD\xAE\xA2\xAB\xA5\xAD\xA0\nENTER - \xAF\xE0\xAE\xA4\xAE\xAB\xA6\xA5\xAD\xA8\xA5."; +		//_textPause = "  \x88\xa3\xe0\xa0 \xae\xe1\xe2\xa0\xad\xae\xa2\xab\xa5\xad\xa0.  \n\n\n"; <- mouse +		// pause button text "\x8f\xe0\xae\xa4\xae\xab\xa6\xa8\xe2\xec" +		_textRestart = "ENTER - \xAF\xA5\xE0\xA5\xA7\xA0\xAF\xE3\xE1\xE2\xA8\xE2\xEC \xA8\xA3\xE0\xE3.\n\nESC - \xAF\xE0\xAE\xA4\xAE\xAB\xA6\xA5\xAD\xA8\xA5 \xA8\xA3\xE0\xEB."; +		_textQuit = "ENTER-\xA2\xEB\xE5\xAE\xA4 \xA8\xA7 \xA8\xA3\xE0\xEB.\nESC - \xA8\xA3\xE0\xA0\xE2\xEC \xA4\xA0\xAB\xEC\xE8\xA5."; + +		_textInventoryYouAreCarrying = "   \x93 \xA2\xA0\xE1 \xA5\xE1\xE2\xEC\x3A   "; +		_textInventoryNothing = "\xAD\xA8\xE7\xA5\xA3\xAE"; +		_textInventorySelectItems = "ENTER - \xA2\xEB\xA1\xAE\xE0, ESC - \xAE\xE2\xAC\xA5\xAD\xA0."; +		_textInventoryReturnToGame = "\x8B\xEE\xA1\xA0\xEF \xAA\xAB\xA0\xA2\xA8\xE8\xA0 - \xA2\xAE\xA7\xA2\xE0\xA0\xE2 \xA2 \xA8\xA3\xE0\xE3"; + +		_textSaveGameSelectSlot ="\x91 \xAF\xAE\xAC\xAE\xE9\xEC\xEE \xAA\xAB\xA0\xA2\xA8\xE8 \xAA\xE3\xE0\xE1\xAE\xE0\xA0 \xA2\xEB\xA1\xA5\xE0\xA8\xE2\xA5 \xE1\xE2\xE0\xAE\xAA\xE3, \xA2 \xAA\xAE\xE2\xAE\xE0\xE3\xEE \xA2\xEB \xA6\xA5\xAB\xA0\xA5\xE2\xA5 \xA7\xA0\xAF\xA8\xE1\xA0\xE2\xEC \xA8\xA3\xE0\xE3. \x8D\xA0\xA6\xAC\xA8\xE2\xA5 ENTER \xA4\xAB\xEF \xA7\xA0\xAF\xA8\xE1\xA8 \xA8\xA3\xE0\xEB, ESC - \xAE\xE2\xAC\xA5\xAD\xA0 \xA7\xA0\xAF\xA8\xE1\xA8."; +		_textSaveGameEnterDescription = "\x8A\xA0\xAA \xA2\xEB \xA6\xA5\xAB\xA0\xA5\xE2\xA5 \xAD\xA0\xA7\xA2\xA0\xE2\xEC \xED\xE2\xE3 \xA7\xA0\xAF\xA8\xE1\xEB\xA2\xA0\xA5\xAC\xE3\xEE \xA8\xA3\xE0\xE3?\n\n"; +		_textSaveGameVerify = "\x83\xAE\xE2\xAE\xA2 \xAA \xA7\xA0\xAF\xA8\xE1\xA8 \xA8\xA3\xE0\xEB, \n\xAE\xAF\xA8\xE1\xA0\xAD\xAD\xAE\xA9 \xAA\xA0\xAA:\n\n%s\n\n\x84\xAB\xEF \xAF\xE0\xAE\xA4\xAE\xAB\xA6\xA5\xAD\xA8\xEF \xAD\xA0\xA6\xAC\xA8\xE2\xA5 ENTER.\nESC - \xAE\xE2\xAC\xA5\xAD\xA0."; + +		_textRestoreGameNoSlots ="\x82 \xAA\xA0\xE2\xA0\xAB\xAE\xA3\xA5\n\n ScummVM saved game directory\n\n\xAD\xA5\xE2 \xA7\xA0\xAF\xA8\xE1\xA0\xAD\xAD\xEB\xE5 \xA8\xA3\xE0.\n\nENTER - \xAF\xE0\xAE\xA4\xAE\xAB\xA6\xA5\xAD\xA8\xA5."; +		_textRestoreGameSelectSlot = "\x91 \xAF\xAE\xAC\xAE\xE9\xEC\xEE \xAA\xAB\xA0\xA2\xA8\xE8 \xAA\xE3\xE0\xE1\xAE\xE0\xA0 \xA2\xEB\xA1\xA5\xE0\xA8\xE2\xA5 \xA8\xA3\xE0\xE3, \xAA\xAE\xE2\xAE\xE0\xE3\xEE \xA2\xEB \xA6\xA5\xAB\xA0\xA5\xE2\xA5 \xE1\xE7\xA8\xE2\xA0\xE2\xEC. \x8D\xA0\xA6\xAC\xA8\xE2\xA5 ENTER \xA4\xAB\xEF \xE1\xE7\xA8\xE2\xEB\xA2\xA0\xAD\xA8\xEF \xA8\xA3\xE0\xEB, ESC - \xA4\xAB\xEF \xAE\xE2\xAC\xA5\xAD\xEB."; +		_textRestoreGameError ="\x8E\xE8\xA8\xA1\xAA\xA0 \xA2 \xA7\xA0\xAF\xA8\xE1\xA0\xAD\xAD\xAE\xA9 \xA8\xA3\xE0\xA5.\nENTER - \xA2\xEB\xE5\xAE\xA4."; +		_textRestoreGameVerify = "\x83\xAE\xE2\xAE\xA2 \xAA \xE1\xE7\xA8\xE2\xEB\xA2\xA0\xAD\xA8\xEE \xA8\xA3\xE0\xEB\x2C\n\xAE\xAF\xA8\xE1\xA0\xAD\xAD\xAE\xA9 \xAA\xA0\xAA.\n\n%s\n\n\x84\xAB\xEF \xAF\xE0\xAE\xA4\xAE\xAB\xA6\xA5\xAD\xA8\xEF \xAD\xA0\xA6\xAC\xA8\xE2\xA5 ENTER.\nESC - \xAE\xE2\xAC\xA5\xAD\xA0."; + +//Press ENTER to continue... +//"\x84\xAB\xEF \xAF\xE0\xAE\xA4\xAE\xAB\xA6\xA5\xAD\xA8\xEF \xAD\xA0\xA6\xAC\xA8\xE2\xA5 ENTER\nESC - \xAE\xE2\xAC\xA5\xAD\xA0."; + +// nothing inventory +// "\xAD\xA8\xE7\xA5\xA3\xAE + +// you are carrying +// "   \x93 \xA2\xA0\xE1 \xA5\xE1\xE2\xEC\x3A   " + +// Press enter to select +// "ENTER - \xA2\xEB\xA1\xAE\xE0, ESC - \xAE\xE2\xAC\xA5\xAD\xA0."; + +// Press a key to return +// "\x8B\xEE\xA1\xA0\xEF \xAA\xAB\xA0\xA2\xA8\xE8\xA0 - \xA2\xAE\xA7\xA2\xE0\xA0\xE2 \xA2 \xA8\xA3\xE0\xE3"; + +// Score %d %d +// "\x91\xE7\xA5\xE2\x3A %d \xA8\xA7 %d  " + +// Sound: on/off +// "\x87\xA2\xE3\xAA\x3A\xA2%s" +// "\xAA\xAB " +// "\xEB\xAA\xAB" + + +		break; +	default: +		break; +	} +} + +SystemUI::~SystemUI() { +	clearSavedGameSlots(); +} + +const char *SystemUI::getStatusTextScore() { +	return _textStatusScore; +} +const char *SystemUI::getStatusTextSoundOn() { +	return _textStatusSoundOn; +} +const char *SystemUI::getStatusTextSoundOff() { +	return _textStatusSoundOff; +} + +void SystemUI::pauseDialog() { +	_vm->_text->messageBox(_textPause); +} + +bool SystemUI::restartDialog() { +	return _vm->_text->messageBox(_textRestart); +} + +bool SystemUI::quitDialog() { +	return _vm->_text->messageBox(_textQuit); +} + +const char *SystemUI::getInventoryTextNothing() { +	return _textInventoryNothing; +} +const char *SystemUI::getInventoryTextYouAreCarrying() { +	return _textInventoryYouAreCarrying; +} +const char *SystemUI::getInventoryTextSelectItems() { +	return _textInventorySelectItems; +} +const char *SystemUI::getInventoryTextReturnToGame() { +	return _textInventoryReturnToGame; +} + +int16 SystemUI::figureOutAutomaticSaveGameSlot(const char *automaticSaveDescription) { +	int16 matchedGameSlotId = -1; +	int16 freshGameSlotId   = -1; + +	// Fill saved game slot cache +	readSavedGameSlots(false, false); // don't filter, but also don't include auto-save slot + +	// Walk through saved game slots +	// check, if description matches and return the slot +	// if no match can be found, return the first non-existant slot +	// if all slots exist, return -1 +	figureOutAutomaticSavedGameSlot(automaticSaveDescription, matchedGameSlotId, freshGameSlotId); + +	if (matchedGameSlotId >= 0) +		return matchedGameSlotId; // return matched slot + +	if (freshGameSlotId >= 0) +		return freshGameSlotId; // return first non-existant slot + +	return -1; // no slots exist, not match found +} + +int16 SystemUI::figureOutAutomaticRestoreGameSlot(const char *automaticSaveDescription) { +	int16 matchedGameSlotId = -1; +	int16 freshGameSlotId   = -1; + +	// Fill saved game slot cache +	readSavedGameSlots(true, false); // filter non-existant/invalid saves, also don't include auto-save slot + +	// Walk through saved game slots +	// check, if description matches and return the slot. Otherwise return -1 +	figureOutAutomaticSavedGameSlot(automaticSaveDescription, matchedGameSlotId, freshGameSlotId); + +	if (matchedGameSlotId >= 0) +		return matchedGameSlotId; // return matched slot +	return -1; // no match found +} + +int16 SystemUI::askForSaveGameSlot() { +	int16 saveGameSlotNr = -1; + +	// Fill saved game slot cache +	readSavedGameSlots(false, false); // don't filter, but also don't include auto-save slot + +	saveGameSlotNr = askForSavedGameSlot(_textSaveGameSelectSlot); + +	if (saveGameSlotNr < 0) { +		// User cancelled? exit now +		return -1; +	} + +	// return actual slot number of the saved game +	return _savedGameArray[saveGameSlotNr].slotId; +} + +bool SystemUI::askForSaveGameDescription(int16 slotId, Common::String &newDescription) { +	// Let user enter new description +	bool previousEditState = _text->inputGetEditStatus(); +	byte previousEditCursor = _text->inputGetCursorChar(); + +	_text->drawMessageBox(_textSaveGameEnterDescription, 0, 31, true); + +	_text->charPos_Push(); +	_text->charAttrib_Push(); +	_text->inputEditOn(); + +	_text->charPos_SetInsideWindow(3, 0); +	_text->charAttrib_Set(15, 0); +	_text->clearBlockInsideWindow(3, 0, 31, 0); // input line is supposed to be black +	_text->inputSetCursorChar('_'); + +	// figure out the current description of the slot +	_text->stringSet(""); +	for (uint16 slotNr = 0; slotNr < _savedGameArray.size(); slotNr++) { +		if (_savedGameArray[slotNr].slotId == slotId) { +			// found slotId +			if (_savedGameArray[slotNr].isValid) { +				// and also valid, so use its description +				_text->stringSet(_savedGameArray[slotNr].description); +			} +		} +	} + +	_vm->cycleInnerLoopActive(CYCLE_INNERLOOP_GETSTRING); +	_text->stringEdit(30); // only allow up to 30 characters + +	_text->charAttrib_Pop(); +	_text->charPos_Pop(); +	_text->inputSetCursorChar(previousEditCursor); +	if (!previousEditState) { +		_text->inputEditOff(); +	} + +	_text->closeWindow(); + +	if (!_text->stringWasEntered()) { +		// User cancelled? exit now +		return false; +	} + +	// Now verify that the user really wants to do this +	char userInput[SYSTEMUI_SAVEDGAME_DISPLAYTEXT_LEN + 1]; + +	createSavedGameDisplayText(userInput, (char *)_text->_inputString, slotId); + +	if (!askForSavedGameVerification(_textSaveGameVerify, userInput)) { +		return false; +	} + +	newDescription.clear(); +	newDescription += (char *)_text->_inputString; +	return true; +} + +int16 SystemUI::askForRestoreGameSlot() { +	int16 restoreGameSlotNr = -1; + +	// Fill saved game slot cache +	readSavedGameSlots(true, true); // filter empty/corrupt slots, but including auto-save slot + +	if (_savedGameArray.size() == 0) { +		// no saved games +		_vm->_text->messageBox(_textRestoreGameNoSlots); +		return -1; +	} + +	restoreGameSlotNr = askForSavedGameSlot(_textRestoreGameSelectSlot); + +	// User cancelled? exit now +	if (restoreGameSlotNr < 0) +		return -1; + +	// Check, if selected saved game was marked as valid +	if (!_savedGameArray[restoreGameSlotNr].isValid) { +		_vm->_text->messageBox(_textRestoreGameError); +		return -1; +	} + +	// Now ask user about this specific saved game +	char userActionVerify[200]; + +	sprintf(userActionVerify, _textRestoreGameVerify, _savedGameArray[restoreGameSlotNr].displayText); +	if (!_vm->_text->messageBox(userActionVerify)) { +		return -1; +	} +	// return actual slot number of the saved game +	return _savedGameArray[restoreGameSlotNr].slotId; +} + +int16 SystemUI::askForSavedGameSlot(const char *slotListText) { +	int16 messageBoxHeight = 0; +	int16 slotsCount = _savedGameArray.size(); + +	if (slotsCount > SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN) { +		messageBoxHeight = 5 + SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN; +	} else { +		messageBoxHeight = 5 + slotsCount; +	} +	_text->drawMessageBox(slotListText, messageBoxHeight, 34, true); + +	drawSavedGameSlots(); +	drawSavedGameSlotSelector(true); + +	_vm->cycleInnerLoopActive(CYCLE_INNERLOOP_SYSTEMUI_SELECTSAVEDGAMESLOT); +	do { +		_vm->mainCycle(); +	} while (_vm->cycleInnerLoopIsActive() && !(_vm->shouldQuit() || _vm->_restartGame)); + +	_text->closeWindow(); + +	return _savedGameSelectedSlotNr; +} + +void SystemUI::savedGameSlot_CharPress(int16 newChar) { +	int16 slotCount = _savedGameArray.size(); +	int16 newUpmostSlotNr = _savedGameUpmostSlotNr; +	int16 newSelectedSlotNr = _savedGameSelectedSlotNr; +	bool  slotsScrolled = false; + +	switch (newChar) { +	case AGI_KEY_ENTER: +		_vm->cycleInnerLoopInactive(); // exit savedGameSlot-loop +		return; +		break; + +	case AGI_KEY_ESCAPE: +		_savedGameSelectedSlotNr = -1; +		_vm->cycleInnerLoopInactive(); // exit savedGameSlot-loop +		return; +		break; +	case AGI_KEY_UP: // previous slot +		newSelectedSlotNr--; +		break; +	case AGI_KEY_DOWN: // next slot +		newSelectedSlotNr++; +		break; +	// FEATURE: any scroll functionality was not available in original AGI +	//  Original AGI was in fact limited to a total of 12 save slots +	case AGI_KEY_PAGE_UP: // scroll up +		newUpmostSlotNr -= SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN; +		break; +	case AGI_KEY_PAGE_DOWN: // scroll down +		newUpmostSlotNr += SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN; +		break; +	case AGI_KEY_HOME: // scroll all the way up +		newUpmostSlotNr = 0; +		break; +	case AGI_KEY_END: // scroll all the way down +		newUpmostSlotNr = (slotCount - 1) - (SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN - 1); +		break; + +	default: +		break; +	} + +	if (newUpmostSlotNr != _savedGameUpmostSlotNr) { +		// Make sure, upmost slot number is valid +		if (newUpmostSlotNr < 0) { +			newUpmostSlotNr = 0; +		} +		if (newUpmostSlotNr + (SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN - 1) >= slotCount) { +			newUpmostSlotNr = (slotCount - 1) - (SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN - 1); +			if (newUpmostSlotNr < 0) +				newUpmostSlotNr = 0; +		} + +		if (newUpmostSlotNr != _savedGameUpmostSlotNr) { +			// Still different? then actually force a slot number change in any case +			slotsScrolled = true; + +			// also adjust selected slot number now +			int16 slotDifference = _savedGameSelectedSlotNr - _savedGameUpmostSlotNr; +			newSelectedSlotNr = newUpmostSlotNr + slotDifference; +		} +	} + +	if ((newSelectedSlotNr != _savedGameSelectedSlotNr) || slotsScrolled) { +		// slot number was changed + +		// Make slot number valid and scroll in case it's needed +		if (newSelectedSlotNr < 0) { +			newSelectedSlotNr = slotCount - 1; +			newUpmostSlotNr = newSelectedSlotNr - SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN; +			if (newUpmostSlotNr < 0) +				newUpmostSlotNr = 0; +		} +		if (newSelectedSlotNr >= slotCount) { +			newSelectedSlotNr = 0; +			newUpmostSlotNr = 0; +		} + +		if (newSelectedSlotNr < newUpmostSlotNr) { +			// scroll up when needed +			newUpmostSlotNr = newSelectedSlotNr; +		} + +		if (newSelectedSlotNr >= newUpmostSlotNr + SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN) { +			// scroll down when needed +			newUpmostSlotNr = newSelectedSlotNr - (SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN - 1); +		} + +		bool drawSlots = false; + +		// remove selector +		drawSavedGameSlotSelector(false); + +		if (newUpmostSlotNr != _savedGameUpmostSlotNr) { +			drawSlots = true; +		} + +		_savedGameUpmostSlotNr   = newUpmostSlotNr; +		_savedGameSelectedSlotNr = newSelectedSlotNr; +		if (drawSlots) { +			drawSavedGameSlots(); +		} +		drawSavedGameSlotSelector(true); +	} +} + +void SystemUI::clearSavedGameSlots() { +	_savedGameArray.clear(); +	_savedGameUpmostSlotNr = 0; +	_savedGameSelectedSlotNr = 0; +} + +void SystemUI::createSavedGameDisplayText(char *destDisplayText, const char *actualDescription, int16 slotId) { +	char  slotIdChar[3]; +	int16 actualDescriptionLen = 0; + +	// clear with spaces +	memset(destDisplayText, ' ', SYSTEMUI_SAVEDGAME_DISPLAYTEXT_LEN); + +	// create fixed prefix (" 1:", "10:", etc.) +	sprintf(slotIdChar, "%02d", slotId); +	memcpy(destDisplayText, slotIdChar, 2); +	destDisplayText[2] = ':'; + +	actualDescriptionLen = strlen(actualDescription); +	if (actualDescriptionLen > (SYSTEMUI_SAVEDGAME_DISPLAYTEXT_LEN - SYSTEMUI_SAVEDGAME_DISPLAYTEXT_PREFIX_LEN)) { +		actualDescriptionLen = SYSTEMUI_SAVEDGAME_DISPLAYTEXT_LEN - SYSTEMUI_SAVEDGAME_DISPLAYTEXT_PREFIX_LEN; +	} + +	if (actualDescriptionLen > 0) { +		memcpy(destDisplayText + SYSTEMUI_SAVEDGAME_DISPLAYTEXT_PREFIX_LEN, actualDescription, actualDescriptionLen); +	} +	destDisplayText[SYSTEMUI_SAVEDGAME_DISPLAYTEXT_LEN] = 0; // terminator +} + +void SystemUI::readSavedGameSlots(bool filterNonexistant, bool withAutoSaveSlot) { +	SavedGameSlotIdArray slotIdArray; +	int16 lastSlotId = -1; +	int16 curSlotId = 0; +	int16 loopSlotId = 0; +	SystemUISavedGameEntry savedGameEntry; +	Common::String saveDescription; +	uint32         saveDate = 0; +	uint16         saveTime = 0; +	bool           saveIsValid = false; + +	int16  mostRecentSlotNr = -1; +	uint32 mostRecentSlotSaveDate = 0; +	uint16 mostRecentSlotSaveTime = 0; + +	clearSavedGameSlots(); + +	// we assume that the Slot-Ids are in order +	slotIdArray = _vm->getSavegameSlotIds(); +	slotIdArray.push_back(SYSTEMUI_SAVEDGAME_MAXIMUM_SLOTS); // so that the loop will process all slots + +	SavedGameSlotIdArray::iterator it; +	SavedGameSlotIdArray::iterator end = slotIdArray.end();; + +	for (it = slotIdArray.begin(); it != end; it++) { +		curSlotId = *it; + +		assert(curSlotId > lastSlotId); // safety check + +		if (curSlotId == 0) { +			// Skip over auto-save slot, if not requested +			if (!withAutoSaveSlot) +				continue; +		} + +		// only allow slot-ids 000 up to 099 +		if (curSlotId >= SYSTEMUI_SAVEDGAME_MAXIMUM_SLOTS) +			curSlotId = SYSTEMUI_SAVEDGAME_MAXIMUM_SLOTS; + +		if (!filterNonexistant) { +			// add slot-ids from last one up to current one (not including the current one) +			lastSlotId++; +			for (loopSlotId = lastSlotId; loopSlotId < curSlotId; loopSlotId++) { +				if (loopSlotId == 0) { +					// Skip over auto-save slot, if not requested +					if (!withAutoSaveSlot) +						continue; +				} + +				savedGameEntry.slotId  = loopSlotId; +				savedGameEntry.exists  = false; +				savedGameEntry.isValid = false; +				memset(savedGameEntry.description, 0, sizeof(savedGameEntry.description)); +				createSavedGameDisplayText(savedGameEntry.displayText, "", loopSlotId); + +				_savedGameArray.push_back(savedGameEntry); +			} +		} + +		if (curSlotId >= SYSTEMUI_SAVEDGAME_MAXIMUM_SLOTS) +			break; // force an exit, limit reached + +		savedGameEntry.slotId = curSlotId; +		if (_vm->getSavegameInformation(curSlotId, saveDescription, saveDate, saveTime, saveIsValid)) { +			if (saveIsValid) { +				// saved game is valid +				// check, if this is the latest slot +				if (saveDate > mostRecentSlotSaveDate) { +					mostRecentSlotNr = _savedGameArray.size(); +					mostRecentSlotSaveDate = saveDate; +					mostRecentSlotSaveTime = saveTime; +				} else if ((saveDate == mostRecentSlotSaveDate) && (saveTime >= mostRecentSlotSaveTime)) { +					// larger or equal is on purpose, so that we use the last slot in case there are multiple saves +					// with the exact same date+time +					mostRecentSlotNr = _savedGameArray.size(); +					mostRecentSlotSaveDate = saveDate; +					mostRecentSlotSaveTime = saveTime; +				} +			} +		} else { +			// slot doesn't exist +			if (filterNonexistant) { +				continue; +			} +		} + +		savedGameEntry.exists  = true; +		savedGameEntry.isValid = saveIsValid; +		memset(savedGameEntry.description, 0, sizeof(savedGameEntry.description)); +		strncpy(savedGameEntry.description, saveDescription.c_str(), SYSTEMUI_SAVEDGAME_DESCRIPTION_LEN); +		createSavedGameDisplayText(savedGameEntry.displayText, saveDescription.c_str(), curSlotId); + +		_savedGameArray.push_back(savedGameEntry); + +		lastSlotId = curSlotId; +	} + +	if (mostRecentSlotNr >= 0) { +		// valid slot found, we use it as default +		// Sierra AGI seems to have done the same +		_savedGameSelectedSlotNr = mostRecentSlotNr; +		if (mostRecentSlotNr < SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN) { +			// available without scrolling, so keep upmost slot 0 +			_savedGameUpmostSlotNr = 0; +		} else { +			// we need to scroll, try to have the slot in the middle +			int16 slotCount = _savedGameArray.size(); + +			_savedGameUpmostSlotNr = mostRecentSlotNr - (SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN / 2); +			if ((_savedGameUpmostSlotNr + (SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN - 1)) >= slotCount) { +				// current upmost would result in empty lines, because it's at the end, push it upwards +				_savedGameUpmostSlotNr = (slotCount - 1) - (SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN - 1); +			} +		} +	} +} + +void SystemUI::figureOutAutomaticSavedGameSlot(const char *automaticSaveDescription, int16 &matchedGameSlotId, int16 &freshGameSlotId) { +	bool foundFresh = false; + +	for (uint16 slotNr = 0; slotNr < _savedGameArray.size(); slotNr++) { +		SystemUISavedGameEntry *savedGameEntry = &_savedGameArray[slotNr]; + +		if (savedGameEntry->isValid) { +			// valid saved game +			if (strcmp(savedGameEntry->description, automaticSaveDescription) == 0) { +				// we got a match +				matchedGameSlotId = savedGameEntry->slotId; +				return; +			} +		} +		if (!foundFresh) { +			// no new slot found yet +			if (!savedGameEntry->exists) { +				// and current slot doesn't exist +				if (slotNr) { +					// and slot is not the auto-save slot -> remember this slot +					freshGameSlotId = savedGameEntry->slotId; +					foundFresh = true; +				} +			} +		} +	} +	return; +} + +void SystemUI::drawSavedGameSlots() { +	int16 slotsToDrawCount = _savedGameArray.size() - _savedGameUpmostSlotNr; +	int16 slotNr = 0; + +	if (slotsToDrawCount > SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN) { +		slotsToDrawCount = SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN; +	} +	_text->charAttrib_Push(); +	_text->charAttrib_Set(0, 15); + +	for (slotNr = 0; slotNr < slotsToDrawCount; slotNr++) { +		_text->displayTextInsideWindow("-", 5 + slotNr, 1); +		_text->displayTextInsideWindow(_savedGameArray[_savedGameUpmostSlotNr + slotNr].displayText, 5 + slotNr, 3); +	} +	_text->charAttrib_Pop(); +} + +void SystemUI::drawSavedGameSlotSelector(bool active) { +	int16 windowRow = 5 + (_savedGameSelectedSlotNr - _savedGameUpmostSlotNr); + +	_text->charAttrib_Push(); +	_text->charAttrib_Set(0, 15); +	if (active) { +		_text->displayTextInsideWindow("\x1a", windowRow, 0); +	} else { +		_text->displayTextInsideWindow(" ", windowRow, 0); +	} +	_text->charAttrib_Pop(); +} + +bool SystemUI::askForSavedGameVerification(const char *verifyText, const char *description) { +	char userActionVerify[200]; +	int16 userKey = 0; + +	sprintf(userActionVerify, verifyText, description); + +	_text->drawMessageBox(userActionVerify, 0, 35); + +	userKey = _vm->waitKey(); + +	_text->closeWindow(); + +	switch (userKey) { +	case AGI_KEY_ENTER: +	case AGI_KEY_LEFT: +		return true; +		break; +	default: +		return false; +		break; +	} +} + +} // End of namespace Agi diff --git a/engines/agi/systemui.h b/engines/agi/systemui.h new file mode 100644 index 0000000000..da9f09f5be --- /dev/null +++ b/engines/agi/systemui.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. + * + */ + +#ifndef AGI_SYSTEMUI_H +#define AGI_SYSTEMUI_H + +namespace Agi { + +#define SYSTEMUI_SAVEDGAME_MAXIMUM_SLOTS 100 +#define SYSTEMUI_SAVEDGAME_SLOTS_ON_SCREEN 12 +#define SYSTEMUI_SAVEDGAME_DESCRIPTION_LEN 30 +#define SYSTEMUI_SAVEDGAME_DISPLAYTEXT_LEN 31 +#define SYSTEMUI_SAVEDGAME_DISPLAYTEXT_PREFIX_LEN 3 + +struct SystemUISavedGameEntry { +	int16 slotId; +	bool  exists; +	bool  isValid; +	char description[SYSTEMUI_SAVEDGAME_DESCRIPTION_LEN + 1]; // actual description +	char displayText[SYSTEMUI_SAVEDGAME_DISPLAYTEXT_LEN + 1]; // modified description, meant for display purposes only +}; +typedef Common::Array<SystemUISavedGameEntry> SystemUISavedGameArray; + +class SystemUI { +public: +	SystemUI(AgiEngine *vm, GfxMgr *gfx, TextMgr *text); +	~SystemUI(); + +private: +	AgiEngine *_vm; +	GfxMgr *_gfx; +	PictureMgr *_picture; +	TextMgr *_text; + +public: +	const char *getStatusTextScore(); +	const char *getStatusTextSoundOn(); +	const char *getStatusTextSoundOff(); + +	void pauseDialog(); +	bool restartDialog(); +	bool quitDialog(); + +	const char *getInventoryTextNothing(); +	const char *getInventoryTextYouAreCarrying(); +	const char *getInventoryTextSelectItems(); +	const char *getInventoryTextReturnToGame(); + +	int16 figureOutAutomaticSaveGameSlot(const char *automaticSaveDescription); +	int16 figureOutAutomaticRestoreGameSlot(const char *automaticSaveDescription); + +	int16 askForSaveGameSlot(); +	int16 askForRestoreGameSlot(); +	bool  askForSaveGameDescription(int16 slotId, Common::String &newDescription); + +	void  savedGameSlot_CharPress(int16 newChar); + +private: +	int16 askForSavedGameSlot(const char *slotListText); +	bool  askForSavedGameVerification(const char *verifyText, const char *description); + +	void  createSavedGameDisplayText(char *destDisplayText, const char *actualDescription, int16 slotId); +	void  clearSavedGameSlots(); +	void  readSavedGameSlots(bool filterNonexistant, bool withAutoSaveSlot); +	void  figureOutAutomaticSavedGameSlot(const char *automaticSaveDescription, int16 &matchedGameSlotId, int16 &freshGameSlotId); + +	void  drawSavedGameSlots(); +	void  drawSavedGameSlotSelector(bool active); + +	SystemUISavedGameArray _savedGameArray; +	int16 _savedGameUpmostSlotNr; +	int16 _savedGameSelectedSlotNr; + +private: +	const char *_textStatusScore; +	const char *_textStatusSoundOn; +	const char *_textStatusSoundOff; + +	const char *_textPause; +	const char *_textRestart; +	const char *_textQuit; + +	const char *_textInventoryNothing; +	const char *_textInventoryYouAreCarrying; +	const char *_textInventorySelectItems; +	const char *_textInventoryReturnToGame; + +	const char *_textSaveGameSelectSlot; +	const char *_textSaveGameEnterDescription; +	const char *_textSaveGameVerify; + +	const char *_textRestoreGameNoSlots; +	const char *_textRestoreGameSelectSlot; +	const char *_textRestoreGameError; +	const char *_textRestoreGameVerify; +}; + +} // End of namespace Agi + +#endif /* AGI_SYSTEMUI_H */ diff --git a/engines/agi/text.cpp b/engines/agi/text.cpp index 16c8284ce0..8367929932 100644 --- a/engines/agi/text.cpp +++ b/engines/agi/text.cpp @@ -24,493 +24,922 @@  #include "agi/sprite.h"		// for commit_both()  #include "agi/graphics.h"  #include "agi/keyboard.h" +#include "agi/text.h" +#include "agi/systemui.h" +#include "agi/words.h" +#ifdef __DS__ +#include "wordcompletion.h" +#endif  namespace Agi { -void AgiEngine::printText2(int l, const char *msg, int foff, int xoff, int yoff, -				int len, int fg, int bg, bool checkerboard) { -	int x1, y1; -	int maxx, minx, ofoff; -	int update; -	// Note: Must be unsigned to use AGDS cyrillic characters! -#ifdef __DS__ -	// On the DS, a compiler bug causes the text to render incorrectly, because -	// GCC tries to optimisie out writes to this pointer (tested on DevkitARM v19b and v20) -	// Making this pointer volatile fixes this. -	volatile const unsigned char *m; -#else -	const unsigned char *m; -#endif +TextMgr::TextMgr(AgiEngine *vm, Words *words, GfxMgr *gfx) { +	_vm = vm; +	_words = words; +	_gfx = gfx; -	// kludge! -	update = 1; -	if (l == 2) { -		update = l = 0; -	} +	memset(&_messageState, 0, sizeof(_messageState)); +	_textPos.row = 0; +	_textPos.column = 0; +	_reset_Column = 0; -	// FR: strings with len == 1 were not printed -	if (len == 1) { -		_gfx->putTextCharacter(l, xoff + foff, yoff, *msg, fg, bg, checkerboard, _fontData); -		maxx = 1; -		minx = 0; -		ofoff = foff; -		y1 = 0;		// Check this -	} else { -		maxx = 0; -		minx = GFX_WIDTH; -		ofoff = foff; +	charAttrib_Set(15, 0); -		for (m = (const unsigned char *)msg, x1 = y1 = 0; *m; m++) { +	_messageState.wanted_TextPos.row = -1; +	_messageState.wanted_TextPos.column = -1; +	_messageState.wanted_Text_Width = -1; +	 +	_textPosArrayCount = 0; +	memset(&_textPosArray, 0, sizeof(_textPosArray)); +	_textAttribArrayCount = 0; +	memset(&_textAttribArray, 0, sizeof(_textAttribArray)); -			// Note: there were extra checks for *m being a cursor character -			// here (1, 2 or 3), which have been removed, as the cursor -			// character is no longer printed via this function. -			if (*m >= 0x20) { -				int ypos = (y1 * CHAR_LINES) + yoff; +	_inputEditEnabled = false; +	_inputCursorChar = 0; -				if ((x1 != (len - 1) || x1 == 39) && (ypos <= (GFX_HEIGHT - CHAR_LINES))) { -					int xpos = (x1 * CHAR_COLS) + xoff + foff; +	_statusEnabled = false; +	_statusRow = 0; -					if (xpos >= GFX_WIDTH) -						continue; +	_promptRow = 0; +	promptDisable(); +	promptReset(); -					_gfx->putTextCharacter(l, xpos, ypos, *m, fg, bg, checkerboard, _fontData); +	_inputStringMaxLen = 0; +	_inputStringCursorPos = 0; +	_inputString[0] = 0; -					if (x1 > maxx) -						maxx = x1; -					if (x1 < minx) -						minx = x1; -				} +	configureScreen(2); +} + +TextMgr::~TextMgr() { +} + +void TextMgr::init(SystemUI *systemUI) { +	_systemUI = systemUI; +} + +void TextMgr::configureScreen(uint16 row_Min) { +	_window_Row_Min = row_Min; +	_window_Row_Max = row_Min + 21; + +	// forward data to GfxMgr as well +	_gfx->setRenderStartOffset(row_Min * FONT_DISPLAY_HEIGHT); +} +uint16 TextMgr::getWindowRowMin() { +	return _window_Row_Min; +} -				x1++; +void TextMgr::dialogueOpen() { +	_messageState.dialogue_Open = true; +} -				// Change line if we've reached the end of this one, unless the next -				// character is a new line itself, or the end of the string -				if (x1 == len && m[1] != '\n' && m[1] != 0) { -					y1++; -					x1 = foff = 0; +void TextMgr::dialogueClose() { +	_messageState.dialogue_Open = false; +} + +void TextMgr::charPos_Clip(int16 &row, int16 &column) { +	row = CLIP<int16>(row, 0, FONT_ROW_CHARACTERS - 1); +	column = CLIP<int16>(column, 0, FONT_COLUMN_CHARACTERS - 1); +} + +void TextMgr::charPos_Set(int16 row, int16 column) { +	_textPos.row = row; +	_textPos.column = column; +} + +void TextMgr::charPos_Get(TextPos_Struct *posPtr) { +	posPtr->row    = _textPos.row; +	posPtr->column = _textPos.column; +} + +void TextMgr::charPos_Set(TextPos_Struct *posPtr) { +	_textPos.row    = posPtr->row; +	_textPos.column = posPtr->column; +} + +void TextMgr::charPos_Push() { +	if (_textPosArrayCount < TEXTPOSARRAY_MAX) { +		charPos_Get(&_textPosArray[_textPosArrayCount]); +		_textPosArrayCount++; +	} +} + +void TextMgr::charPos_Pop() { +	if (_textPosArrayCount > 0) { +		_textPosArrayCount--; +		charPos_Set(&_textPosArray[_textPosArrayCount]); +	} +} + +void TextMgr::charPos_SetInsideWindow(int16 windowRow, int16 windowColumn) { +	if (!_messageState.window_Active) +		return; + +	_textPos.row = _messageState.textPos.row + windowRow; +	_textPos.column = _messageState.textPos.column + windowColumn; +} + +static byte charAttrib_CGA_Conversion[] = { +	0, 1, 1, 1, 2, 2, 2, 3, 3, 1, 1, 1, 2, 2, 2 +}; + +void TextMgr::charAttrib_Set(byte foreground, byte background) { +	_textAttrib.foreground = foreground; +	_textAttrib.background = calculateTextBackground(background); + +	if (!_vm->_game.gfxMode) { +		// Text-mode: +		// just use the given colors directly +		_textAttrib.combinedForeground = foreground; +		_textAttrib.combinedBackground = background; +	} else { +		// Graphics-mode: +		switch (_vm->_renderMode) { +		case RENDERMODE_CGA: +			// CGA +			if (background) { +				_textAttrib.combinedForeground = 3; +				_textAttrib.combinedBackground = 8; // enable invert of colors +			} else if (foreground > 14) { +				if (foreground > 14) { +					_textAttrib.combinedForeground = 3; +				} else { +					_textAttrib.combinedForeground = charAttrib_CGA_Conversion[foreground & 0x0F];  				} +				_textAttrib.combinedBackground = 0; +			} +			break; +		default: +			// EGA-handling: +			if (background) { +				_textAttrib.combinedForeground = 15; +				_textAttrib.combinedBackground = 8; // enable invert of colors  			} else { -				if (m[1] != 0) { -					y1++; -					x1 = foff = 0; -				} +				_textAttrib.combinedForeground = foreground; +				_textAttrib.combinedBackground = 0;  			} +			break;  		}  	} +} -	if (l) -		return; - -	if (maxx < minx) -		return; +byte TextMgr::charAttrib_GetForeground() { +	return _textAttrib.foreground; +} +byte TextMgr::charAttrib_GetBackground() { +	return _textAttrib.background; +} -	maxx *= CHAR_COLS; -	minx *= CHAR_COLS; +void TextMgr::charAttrib_Push() { +	if (_textAttribArrayCount < TEXTATTRIBARRAY_MAX) { +		memcpy(&_textAttribArray[_textAttribArrayCount], &_textAttrib, sizeof(_textAttrib)); +		_textAttribArrayCount++; +	} +} -	if (update) { -		_gfx->scheduleUpdate(foff + xoff + minx, yoff, ofoff + xoff + maxx + CHAR_COLS - 1, -				yoff + y1 * CHAR_LINES + CHAR_LINES + 1); +void TextMgr::charAttrib_Pop() { +	if (_textAttribArrayCount > 0) { +		_textAttribArrayCount--; +		memcpy(&_textAttrib, &_textAttribArray[_textAttribArrayCount], sizeof(_textAttrib)); +	} +} -		// Making synchronous text updates reduces CPU load -		// when updating status line and input area -		_gfx->doUpdate(); +byte TextMgr::calculateTextBackground(byte background) { +	if ((_vm->_game.gfxMode) && (background)) { +		return 15; // interpreter sets 0xFF, but drawClearCharacter() would use upper 4 bits by shift  	} +	return 0;  } -// -// len is in characters, not pixels!! -// -void AgiEngine::blitTextbox(const char *p, int y, int x, int len) { -	// if x | y = -1, then center the box -	int xoff, yoff, lin, h, w; -	char *msg, *m; +void TextMgr::display(int16 textNr, int16 textRow, int16 textColumn) { +	const char *logicTextPtr = NULL; +	char *processedTextPtr   = NULL; -	debugC(3, kDebugLevelText, "blitTextbox(): x=%d, y=%d, len=%d", x, y, len); -	if (_game.window.active) -		closeWindow(); +	charPos_Push(); +	charPos_Set(textRow, textColumn); + +	if (textNr >= 1 && textNr <= _vm->_game._curLogic->numTexts) { +		logicTextPtr = _vm->_game._curLogic->texts[textNr - 1]; +		processedTextPtr = stringPrintf(logicTextPtr); +		processedTextPtr = stringWordWrap(processedTextPtr, 40); +		displayText(processedTextPtr); + +		// Signal, that non-blocking text is shown at the moment +		if (textRow > 0) { +			// only signal, when it's not the status line (kq3) +			_vm->nonBlockingText_IsShown(); +		} +	} +	charPos_Pop(); +} + +void TextMgr::displayTextInsideWindow(const char *textPtr, int16 windowRow, int16 windowColumn) { +	int16 textRow = 0; +	int16 textColumn = 0; -	if (x == 0 && y == 0 && len == 0) -		x = y = -1; +	if (!_messageState.window_Active) +		return; -	if (len <= 0) -		len = 30; +	charPos_Push(); +	textRow = _messageState.textPos.row + windowRow; +	textColumn = _messageState.textPos.column + windowColumn; +	charPos_Set(textRow, textColumn); +	displayText(textPtr); +	charPos_Pop(); +} -	xoff = x * CHAR_COLS; -	yoff = y * CHAR_LINES; +void TextMgr::displayText(const char *textPtr, bool disabledLook) { +	const char *curTextPtr = textPtr; +	byte  curCharacter = 0; -	m = msg = wordWrapString(agiSprintf(p), &len); +	while (1) { +		curCharacter = *curTextPtr; +		if (!curCharacter) +			break; -	for (lin = 1; *m; m++) { -		// Test \r for MacOS 8 -		if (*m == '\n' || *m == '\r') -			lin++; +		curTextPtr++; +		displayCharacter(curCharacter, disabledLook);  	} +} -	if (lin * CHAR_LINES > GFX_HEIGHT) -		lin = (GFX_HEIGHT / CHAR_LINES); +void TextMgr::displayCharacter(byte character, bool disabledLook) { +	TextPos_Struct charCurPos; -	w = (len + 2) * CHAR_COLS; -	h = (lin + 2) * CHAR_LINES; +	charPos_Get(&charCurPos); -	if (xoff < 0) -		xoff = (GFX_WIDTH - w - CHAR_COLS) / 2; -	else -		xoff -= CHAR_COLS; +	switch (character) { +	case 0x08: // backspace +		if (charCurPos.column) { +			charCurPos.column--; +		} else if (charCurPos.row > 21) { +			charCurPos.column = (FONT_COLUMN_CHARACTERS - 1); +			charCurPos.row--; +		} +		clearBlock(charCurPos.row, charCurPos.column, charCurPos.row, charCurPos.column, _textAttrib.background); +		charPos_Set(&charCurPos); +		break; -	if (yoff < 0) -		yoff = (GFX_HEIGHT - 3 * CHAR_LINES - h) / 2; +	case 0x0D: +	case 0x0A: // CR/LF +		if (charCurPos.row < (FONT_ROW_CHARACTERS - 1)) +			charCurPos.row++; +		charCurPos.column = _reset_Column; +		charPos_Set(&charCurPos); +		break; +	default: +		// ch_attrib(state.text_comb, conversion); +		_gfx->drawCharacter(charCurPos.row, charCurPos.column, character, _textAttrib.combinedForeground, _textAttrib.combinedBackground, disabledLook); + +		charCurPos.column++; +		if (charCurPos.column <= (FONT_COLUMN_CHARACTERS - 1)) { +			charPos_Set(&charCurPos); +		} else { +			displayCharacter(0x0D); // go to next line +		} +	}  +} -	drawWindow(xoff, yoff, xoff + w - 1, yoff + h - 1); +void TextMgr::print(int16 textNr) { +	const char *logicTextPtr = NULL; +	if (textNr >= 1 && textNr <= _vm->_game._curLogic->numTexts) { +		logicTextPtr = _vm->_game._curLogic->texts[textNr - 1]; +		messageBox(logicTextPtr); +	} +} + +void TextMgr::printAt(int16 textNr, int16 textPos_Row, int16 textPos_Column, int16 text_Width) { +	// Sierra didn't do clipping, we do it for security +	charPos_Clip(textPos_Row, textPos_Column); -	printText2(2, msg, 0, CHAR_COLS + xoff, CHAR_LINES + yoff, -			len + 1, MSG_BOX_TEXT, MSG_BOX_COLOR); +	_messageState.wanted_TextPos.row = textPos_Row; +	_messageState.wanted_TextPos.column = textPos_Column; +	_messageState.wanted_Text_Width = text_Width; -	free(msg); +	if (_messageState.wanted_Text_Width == 0) { +		_messageState.wanted_Text_Width = 30; +	} +	print(textNr); -	_gfx->doUpdate(); +	_messageState.wanted_TextPos.row = -1; +	_messageState.wanted_TextPos.column = -1; +	_messageState.wanted_Text_Width = -1;  } -void AgiEngine::eraseTextbox() { -	if (!_game.window.active) { -		debugC(3, kDebugLevelText, "eraseTextbox(): no window active"); -		return; +bool TextMgr::messageBox(const char *textPtr) { +	drawMessageBox(textPtr); + +	if (_vm->getflag(VM_FLAG_OUTPUT_MODE)) { +		// non-blocking window +		_vm->setflag(VM_FLAG_OUTPUT_MODE, false); + +		// Signal, that non-blocking text is shown at the moment +		_vm->nonBlockingText_IsShown(); +		return true;  	} -	debugC(4, kDebugLevelText, "eraseTextbox(): x1=%d, y1=%d, x2=%d, y2=%d", _game.window.x1, -			_game.window.y1, _game.window.x2, _game.window.y2); +	// blocking window +	_vm->_noSaveLoadAllowed = true; +	_vm->nonBlockingText_Forget(); -	_gfx->restoreBlock(_game.window.x1, _game.window.y1, -			_game.window.x2, _game.window.y2, _game.window.buffer); +	if (_vm->_game.vars[VM_VAR_WINDOW_RESET] == 0) { +		int userKey; +		_vm->setVar(VM_VAR_KEY, 0); +		userKey = _vm->waitKey(); +		closeWindow(); -	free(_game.window.buffer); -	_game.window.active = false; +		_vm->_noSaveLoadAllowed = false; +		if (userKey == AGI_KEY_ENTER) +			return true; +		return false; +	} -	_gfx->doUpdate(); -} +	// timed window +	debugC(3, kDebugLevelText, "f15==0, v21==%d => timed", _vm->getVar(VM_VAR_WINDOW_RESET)); +	_vm->_game.msgBoxTicks = _vm->getVar(VM_VAR_WINDOW_RESET) * 10; +	_vm->setVar(VM_VAR_KEY, 0); -/* - * Public functions - */ +	do { +		if (_vm->getflag(VM_FLAG_RESTORE_JUST_RAN)) +			break; -/** - * Print text in the AGI engine screen. - */ -void AgiEngine::printText(const char *msg, int f, int x, int y, int len, int fg, int bg, bool checkerboard) { -	f *= CHAR_COLS; -	x *= CHAR_COLS; -	y *= CHAR_LINES; +		_vm->mainCycle(); +		if (_vm->_game.keypress == AGI_KEY_ENTER) { +			debugC(4, kDebugLevelText, "KEY_ENTER"); +			_vm->setVar(VM_VAR_WINDOW_RESET, 0); +			_vm->_game.keypress = 0; +			break; +		} +	} while (_vm->_game.msgBoxTicks > 0 && !(_vm->shouldQuit() || _vm->_restartGame)); -	debugC(4, kDebugLevelText, "printText(): %s, %d, %d, %d, %d, %d, %d", msg, f, x, y, len, fg, bg); -	printText2(0, agiSprintf(msg), f, x, y, len, fg, bg, checkerboard); +	_vm->setVar(VM_VAR_WINDOW_RESET, 0); +	closeWindow(); +	_vm->_noSaveLoadAllowed = false; +	return true;  } -/** - * Print text in the AGI engine console. - */ -void AgiEngine::printTextConsole(const char *msg, int x, int y, int len, int fg, int bg) { -	x *= CHAR_COLS; -	y *= 10; +void TextMgr::drawMessageBox(const char *textPtr, int16 wantedHeight, int16 wantedWidth, bool wantedForced) { +	int16 maxWidth = wantedWidth; +	int16 startingRow = 0; +	char *processedTextPtr; -	debugC(4, kDebugLevelText, "printTextConsole(): %s, %d, %d, %d, %d, %d", msg, x, y, len, fg, bg); -	printText2(1, msg, 0, x, y, len, fg, bg); -} +	if (_messageState.window_Active) { +		closeWindow(); +	} +	charAttrib_Push(); +	charPos_Push(); +	charAttrib_Set(0, 15); + +	if ((_messageState.wanted_Text_Width == -1) && (maxWidth == 0)) { +		maxWidth = 30; +	} else if (_messageState.wanted_Text_Width != -1) { +		maxWidth = _messageState.wanted_Text_Width; +	} -/** - * Wrap text line to the specified width. - * @param str  String to wrap. - * @param len  Length of line. - * - * Based on GBAGI implementation with permission from the author - */ -char *AgiEngine::wordWrapString(const char *s, int *len) { -	char *outStr, *msgBuf; -	int maxWidth = *len; -	const char *pWord; -	int lnLen, wLen; +	processedTextPtr = stringPrintf(textPtr); -	// Allocate some extra space for the final buffer, as -	// outStr may end up being longer than s -	// 26 = 200 (screen height) / 8 (font height) + 1 -	msgBuf = outStr = (char *)malloc(strlen(s) + 26); +	int16 calculatedWidth = 0; +	int16 calculatedHeight = 0; -	int msgWidth = 0; +	processedTextPtr = stringWordWrap(processedTextPtr, maxWidth, &calculatedWidth, &calculatedHeight); +	_messageState.textSize_Width  = calculatedWidth; +	_messageState.textSize_Height = calculatedHeight; -	lnLen = 0; +	_messageState.printed_Height = _messageState.textSize_Height; -	while (*s) { -		pWord = s; +	// Caller wants to force specified width/height? set it +	if (wantedForced) { +		if (wantedHeight) +			_messageState.textSize_Height = wantedHeight; +		if (wantedWidth) +			_messageState.textSize_Width = wantedWidth; +	} -		while (*s != '\0' && *s != ' ' && *s != '\n' && *s != '\r') -			s++; +	if (_messageState.wanted_TextPos.row == -1) { +		startingRow = ((HEIGHT_MAX - _messageState.textSize_Height - 1) / 2) + 1; +	} else { +		startingRow = _messageState.wanted_TextPos.row; +	} +	_messageState.textPos.row = startingRow + _window_Row_Min; +	_messageState.textPos_Edge.row = _messageState.textSize_Height + _messageState.textPos.row - 1; -		wLen = (int)(s - pWord); +	if (_messageState.wanted_TextPos.column == -1) { +		_messageState.textPos.column = (FONT_COLUMN_CHARACTERS - _messageState.textSize_Width) / 2; +	} else { +		_messageState.textPos.column = _messageState.wanted_TextPos.column; +	} +	_messageState.textPos_Edge.column = _messageState.textPos.column + _messageState.textSize_Width; -		if (wLen && *s == '\n' && s[-1] == ' ') -			wLen--; +	charPos_Set(_messageState.textPos.row, _messageState.textPos.column); -		if (wLen + lnLen >= maxWidth) { -			// Check if outStr isn't msgBuf. If this is the case, outStr hasn't advanced -			// yet, so no output has been written yet -			if (outStr != msgBuf) { -				if (outStr[-1] == ' ') -					outStr[-1] = '\n'; -				else -					*outStr++ = '\n'; -			} +	_messageState.backgroundSize_Width = (_messageState.textSize_Width * FONT_VISUAL_WIDTH) + 10; +	_messageState.backgroundSize_Height = (_messageState.textSize_Height * FONT_VISUAL_HEIGHT) + 10; +	_messageState.backgroundPos_x = (_messageState.textPos.column * FONT_VISUAL_WIDTH) - 5; +	_messageState.backgroundPos_y = (_messageState.textPos_Edge.row - _window_Row_Min + 1 ) * FONT_VISUAL_HEIGHT + 4; -			lnLen = 0; +	// Hardcoded colors: white background and red lines +	_gfx->drawBox(_messageState.backgroundPos_x, _messageState.backgroundPos_y, _messageState.backgroundSize_Width, _messageState.backgroundSize_Height, 15, 4); -			while (wLen >= maxWidth) { -				msgWidth = maxWidth; +	_messageState.window_Active = true; -				memcpy(outStr, pWord, maxWidth); +	_reset_Column = _messageState.textPos.column; +	displayText(processedTextPtr); +	_reset_Column = 0; -				wLen -= maxWidth; -				outStr += maxWidth; -				pWord  += maxWidth; -				*outStr++ = '\n'; -			} -		} +	charPos_Pop(); +	charAttrib_Pop(); -		if (wLen) { -			memcpy(outStr, pWord, wLen); -			outStr += wLen; -		} -		lnLen += wLen+1; +	_messageState.dialogue_Open = true; +} + +void TextMgr::closeWindow() { +	if (_messageState.window_Active) { +		_gfx->render_Block(_messageState.backgroundPos_x, _messageState.backgroundPos_y, _messageState.backgroundSize_Width, _messageState.backgroundSize_Height); +	} +	_messageState.dialogue_Open = false; +	_messageState.window_Active = false; +} -		if (lnLen > msgWidth) { -			msgWidth = lnLen; +void TextMgr::statusRow_Set(int16 row) { +	_statusRow = row; +} +int16 TextMgr::statusRow_Get() { +	return _statusRow; +} -			if (*s == '\0' || *s == ' ' || *s == '\n' || *s == '\r') -				msgWidth--; -		} +void TextMgr::statusEnable() { +	_statusEnabled = true; +} +void TextMgr::statusDisable() { +	_statusEnabled = false; +} +bool TextMgr::statusEnabled() { +	return _statusEnabled; +} + +void TextMgr::statusDraw() { +	char *statusTextPtr = NULL; + +	charAttrib_Push(); +	charPos_Push(); + +	if (_statusEnabled) { +		clearLine(_statusRow, 15); -		if (*s == '\n') -			lnLen = 0; +		charAttrib_Set(0, 15); +		charPos_Set(_statusRow, 1); +		statusTextPtr = stringPrintf(_systemUI->getStatusTextScore()); +		displayText(statusTextPtr); -		if (*s) -			*outStr++ = *s++; +		charPos_Set(_statusRow, 30); +		if (_vm->getflag(VM_FLAG_SOUND_ON)) { +			statusTextPtr = stringPrintf(_systemUI->getStatusTextSoundOn()); +		} else { +			statusTextPtr = stringPrintf(_systemUI->getStatusTextSoundOff()); +		} +		displayText(statusTextPtr);  	} -	*outStr = '\0'; -	*len = msgWidth; -	return msgBuf; +	charPos_Pop(); +	charAttrib_Pop();  } -/** - * Remove existing window, if any. - */ -void AgiEngine::closeWindow() { -	debugC(4, kDebugLevelText, "closeWindow()"); +void TextMgr::statusClear() { +	clearLine(_statusRow, 0); +} -	_sprites->eraseBoth(); -	eraseTextbox();	// remove window, if any -	_sprites->blitBoth(); -	_sprites->commitBoth();		// redraw sprites -	_game.hasWindow = false; +void TextMgr::clearLine(int16 row, byte color) { +	clearLines(row, row, color);  } -/** - * Display a message box. - * This function displays the specified message in a text box - * centered in the screen and waits until a key is pressed. - * @param p The text to be displayed - */ -int AgiEngine::messageBox(const char *s) { -	int k; - -	_sprites->eraseBoth(); -	blitTextbox(s, -1, -1, -1); -	_sprites->blitBoth(); -	k = waitKey(); -	debugC(4, kDebugLevelText, "messageBox(): wait_key returned %02x", k); -	closeWindow(); +void TextMgr::clearLines(int16 row_Upper, int16 row_Lower, byte color) { +	clearBlock(row_Upper, 0, row_Lower, FONT_COLUMN_CHARACTERS - 1, color); +} + +void TextMgr::clearBlock(int16 row_Upper, int16 column_Upper, int16 row_Lower, int16 column_Lower, byte color) { +	// Sierra didn't do clipping of the coordinates, we do it for security +	//  and b/c there actually are some games, that call commands with invalid coordinates +	//  see cmdClearLines() comments. +	charPos_Clip(row_Upper, column_Upper); +	charPos_Clip(row_Lower, column_Lower); -	return k; +	int16 x = column_Upper * FONT_DISPLAY_WIDTH; +	int16 y = row_Upper * FONT_DISPLAY_HEIGHT; +	int16 width = (column_Lower + 1 - column_Upper) * FONT_DISPLAY_WIDTH; +	int16 height = (row_Lower + 1 - row_Upper) * FONT_DISPLAY_HEIGHT; + +	y = y + height - 1; // drawDisplayRect wants lower Y-coordinate +	_gfx->drawDisplayRect(x, y, width, height, color);  } -/** - * Display a message box with buttons. - * This function displays the specified message in a text box - * centered in the screen and waits until a button is pressed. - * @param p The text to be displayed - * @param b NULL-terminated list of button labels - */ -int AgiEngine::selectionBox(const char *m, const char **b) { -	int numButtons = 0; -	int x, y, i, s; -	int bx[5], by[5]; +void TextMgr::clearBlockInsideWindow(int16 windowRow, int16 windowColumn, int16 width, byte color) { +	int16 row; +	int16 column; +	if (!_messageState.window_Active) +		return; -	_noSaveLoadAllowed = true; +	row = _messageState.textPos.row + windowRow; +	column = _messageState.textPos.column + windowColumn; +	clearBlock(row, column, row, column + width - 1, color); +} -	_sprites->eraseBoth(); -	blitTextbox(m, -1, -1, -1); +bool TextMgr::inputGetEditStatus() { +	return _inputEditEnabled; +} -	x = _game.window.x1 + 5 * CHAR_COLS / 2; -	y = _game.window.y2 - 5 * CHAR_LINES / 2; -	s = _game.window.x2 - _game.window.x1 + 1 - 5 * CHAR_COLS; -	debugC(3, kDebugLevelText, "selectionBox(): s = %d", s); +void TextMgr::inputEditOn() { +	if (!_inputEditEnabled) { +		_inputEditEnabled = true; +		if (_inputCursorChar) { +			displayCharacter(0x08); // backspace +		} +	} +} -	// Automatically position buttons -	for (i = 0; b[i]; i++) { -		numButtons++; -		s -= CHAR_COLS * strlen(b[i]); +void TextMgr::inputEditOff() { +	if (_inputEditEnabled) { +		_inputEditEnabled = false; +		if (_inputCursorChar) { +			displayCharacter(_inputCursorChar); +		}  	} +} + +void TextMgr::inputSetCursorChar(int16 cursorChar) { +	_inputCursorChar = cursorChar; +} + +byte TextMgr::inputGetCursorChar() { +	return _inputCursorChar; +} + +void TextMgr::promptRow_Set(int16 row) { +	_promptRow = row; +} + +int16 TextMgr::promptRow_Get() { +	return _promptRow; +} + +void TextMgr::promptReset() { +	_promptCursorPos = 0; +	memset(_prompt, 0, sizeof(_prompt)); +	memset(_promptPrevious, 0, sizeof(_promptPrevious)); +} -	if (i > 1) { -		debugC(3, kDebugLevelText, "selectionBox(): s / %d = %d", i - 1, s / (i - 1)); -		s /= (i - 1); +void TextMgr::promptEnable() { +	_promptEnabled = true; +} +void TextMgr::promptDisable() { +	_promptEnabled = false; +} +bool TextMgr::promptIsEnabled() { +	return _promptEnabled; +} + +void TextMgr::promptCharPress(int16 newChar) { +	int16 maxChars = 0; +	int16 scriptsInputLen = _vm->getVar(VM_VAR_MAX_INPUT_CHARACTERS); + +	if (_messageState.dialogue_Open) { +		maxChars = TEXT_STRING_MAX_SIZE - 4;  	} else { -		x += s / 2; +		maxChars = TEXT_STRING_MAX_SIZE - strlen(_vm->_game.strings[0]); // string 0 is the prompt string prefix  	} -	for (i = 0; b[i]; i++) { -		bx[i] = x; -		by[i] = y; -		x += CHAR_COLS * strlen(b[i]) + s; -	} +	if (_promptCursorPos) +		maxChars--; -	_sprites->blitBoth(); +	if (scriptsInputLen < maxChars) +		maxChars = scriptsInputLen; -	clearKeyQueue(); +	inputEditOn(); -	AllowSyntheticEvents on(this); +	switch (newChar) { +	case AGI_KEY_BACKSPACE: { +		if (_promptCursorPos) { +			_promptCursorPos--; +			_prompt[_promptCursorPos] = 0; +			displayCharacter(newChar); + +			promptRememberForAutoComplete(); +		} +		break; +	} +	case 0x0A: // LF +		break; +	case AGI_KEY_ENTER: { +		if (_promptCursorPos) { +			// something got entered? -> process it and pass it to the scripts +			promptRememberForAutoComplete(true); + +			memcpy(&_promptPrevious, &_prompt, sizeof(_prompt)); +			// parse text +			_vm->_words->parseUsingDictionary((char *)&_prompt); + +			_promptCursorPos = 0; +			_prompt[0] = 0; +			promptRedraw(); +		} +		break; +	} +	default: +		if (maxChars > _promptCursorPos) { +			bool acceptableInput = false; + +			// FEATURE: Sierra didn't check for valid characters (filtered out umlauts etc.) +			// In text-mode this sort of worked at least with the DOS interpreter +			// but as soon as invalid characters were used in graphics mode they weren't properly shown +			switch (_vm->getLanguage()) { +			case Common::RU_RUS: +				if (newChar >= 0x20) +					acceptableInput = true; +				break; +			default: +				if ((newChar >= 0x20) && (newChar <= 0x7f)) +					acceptableInput = true; +				break; +			} -	debugC(4, kDebugLevelText, "selectionBox(): waiting..."); -	int key, active = 0; -	int rc = -1; -	while (rc == -1 && !(shouldQuit() || _restartGame)) { -		for (i = 0; b[i]; i++) -			_gfx->drawCurrentStyleButton(bx[i], by[i], b[i], i == active, false, i == 0); +			if (acceptableInput) { +				_prompt[_promptCursorPos] = newChar; +				_promptCursorPos++; +				_prompt[_promptCursorPos] = 0; +				displayCharacter(newChar); -		pollTimer(); -		key = doPollKeyboard(); -		switch (key) { -		case KEY_ENTER: -			rc = active; -			debugC(4, kDebugLevelText, "selectionBox(): Button pressed: %d", rc); -			break; -		case KEY_RIGHT: -			active++; -			if (active >= numButtons) -				active = 0; -			break; -		case KEY_LEFT: -			active--; -			if (active < 0) -				active = numButtons - 1; -			break; -		case BUTTON_LEFT: -			for (i = 0; b[i]; i++) { -				if (_gfx->testButton(bx[i], by[i], b[i])) { -					rc = active = i; -					debugC(4, kDebugLevelText, "selectionBox(): Button pressed: %d", rc); -					break; -				} +				promptRememberForAutoComplete();  			} -			break; -		case 0x09:	// Tab -			debugC(3, kDebugLevelText, "selectionBox(): Focus change"); -			active++; -			active %= i; -			break;  		} -		_gfx->doUpdate(); +		break; +	} -		if (key == KEY_ESCAPE) -			break; +	inputEditOff(); +} + +void TextMgr::promptCancelLine() { +	while (_promptCursorPos) { +		promptCharPress(0x08); // Backspace until prompt is empty  	} +} -	closeWindow(); -	debugC(2, kDebugLevelText, "selectionBox(): Result = %d", rc); +void TextMgr::promptEchoLine() { +	int16 previousLen = strlen((char *)_promptPrevious); -	_noSaveLoadAllowed = false; +	if (_promptCursorPos < previousLen) { +		inputEditOn(); + +		while (_promptPrevious[_promptCursorPos]) { +			promptCharPress(_promptPrevious[_promptCursorPos]); +		} +		promptRememberForAutoComplete(); -	return rc; +		inputEditOff(); +	}  } -/** - * - */ -int AgiEngine::print(const char *p, int lin, int col, int len) { -	if (p == NULL) -		return 0; +void TextMgr::promptRedraw() { +	char *textPtr = nullptr; -	debugC(4, kDebugLevelText, "print(): lin = %d, col = %d, len = %d", lin, col, len); +	if (_promptEnabled) { +		inputEditOn(); +		clearLine(_promptRow, _textAttrib.background); +		charPos_Set(_promptRow, 0); +		// agi_printf(str_wordwrap(msg, state.string[0], 40) ); -	blitTextbox(p, lin, col, len); +		textPtr = _vm->_game.strings[0]; +		textPtr = stringPrintf(textPtr); +		textPtr = stringWordWrap(textPtr, 40); -	if (getflag(fOutputMode)) { -		// non-blocking window -		setflag(fOutputMode, false); -		return 1; +		displayText(textPtr); +		displayText((char *)&_prompt); +		inputEditOff();  	} +} -	// blocking +// for AGI1 +void TextMgr::promptClear() { +	clearLine(_promptRow, _textAttrib.background); +} -	_noSaveLoadAllowed = true; +void TextMgr::promptRememberForAutoComplete(bool entered) { +#ifdef __DS__ +	DS::findWordCompletions((char *)_prompt); +#endif +} -	if (_game.vars[vWindowReset] == 0) { -		int k; -		setvar(vKey, 0); -		k = waitKey(); -		closeWindow(); +bool TextMgr::stringWasEntered() { +	return _inputStringEntered; +} + +void TextMgr::stringSet(const char *text) { +	strncpy((char *)_inputString, text, sizeof(_inputString)); +	_inputString[sizeof(_inputString) - 1] = 0; // terminator +} -		_noSaveLoadAllowed = false; +void TextMgr::stringEdit(int16 stringMaxLen) { +	int16 inputStringLen = strlen((const char *)_inputString); -		return k; +	// Caller can set the input string +	_inputStringCursorPos = 0; +	while (_inputStringCursorPos < inputStringLen) { +		displayCharacter(_inputString[_inputStringCursorPos]); +		_inputStringCursorPos++;  	} -	// timed window +	// should never happen unless there is a coding glitch +	assert(_inputStringCursorPos <= stringMaxLen); -	debugC(3, kDebugLevelText, "f15==0, v21==%d => timed", getvar(21)); -	_game.msgBoxTicks = getvar(vWindowReset) * 10; -	setvar(vKey, 0); +	_inputStringMaxLen = stringMaxLen; +	_inputStringEntered = false; -	_menuSelected = false; +	inputEditOff();  	do { -		if (getflag(fRestoreJustRan)) -			break; +		_vm->mainCycle(); +	} while (_vm->cycleInnerLoopIsActive() && !(_vm->shouldQuit() || _vm->_restartGame)); -		if (_menuSelected) -			break; +	inputEditOn(); -		mainCycle(); -		if (_game.keypress == KEY_ENTER) { -			debugC(4, kDebugLevelText, "KEY_ENTER"); -			setvar(vWindowReset, 0); -			_game.keypress = 0; -			break; +	// Forget non-blocking text, user was asked to enter something +	_vm->nonBlockingText_Forget(); +} + +void TextMgr::stringCharPress(int16 newChar) { +	inputEditOn(); + +	switch (newChar) { +	case 0x3:		// ctrl-c +	case 0x18: {	// ctrl-x +		// clear string +		while (_inputStringCursorPos) { +			_inputStringCursorPos--; +			_inputString[_inputStringCursorPos] = 0; +			displayCharacter(0x08);  		} -	} while (_game.msgBoxTicks > 0 && !(shouldQuit() || _restartGame)); +		break; +	} -	setvar(vWindowReset, 0); +	case AGI_KEY_BACKSPACE: { +		if (_inputStringCursorPos) { +			_inputStringCursorPos--; +			_inputString[_inputStringCursorPos] = 0; +			displayCharacter(newChar); -	closeWindow(); +			stringRememberForAutoComplete(); +		} +		break; +	} -	_noSaveLoadAllowed = false; +	case AGI_KEY_ENTER: { +		stringRememberForAutoComplete(true); -	return 0; +		_inputStringEntered = true; + +		_vm->cycleInnerLoopInactive(); // exit GetString-loop +		break; +	} + +	case AGI_KEY_ESCAPE: { +		_inputString[0] = 0; +		_inputStringCursorPos = 0; +		_inputStringEntered = false; + +		_vm->cycleInnerLoopInactive(); // exit GetString-loop +		break; +	} + +	default: +		if (_inputStringMaxLen > _inputStringCursorPos) { +			bool acceptableInput = false; + +			// FEATURE: Sierra didn't check for valid characters (filtered out umlauts etc.) +			// In text-mode this sort of worked at least with the DOS interpreter +			// but as soon as invalid characters were used in graphics mode they weren't properly shown +			switch (_vm->getLanguage()) { +			case Common::RU_RUS: +				if (newChar >= 0x20) +					acceptableInput = true; +				break; +			default: +				if ((newChar >= 0x20) && (newChar <= 0x7f)) +					acceptableInput = true; +				break; +			} + +			if (acceptableInput) { +				if ((_vm->_game.cycleInnerLoopType == CYCLE_INNERLOOP_GETSTRING) || ((newChar >= '0') && (newChar <= '9'))) { +					// Additionally check for GETNUMBER-mode, if character is a number +					// Sierra also did not do this +					_inputString[_inputStringCursorPos] = newChar; +					_inputStringCursorPos++; +					_inputString[_inputStringCursorPos] = 0; +					displayCharacter(newChar); + +					stringRememberForAutoComplete(); +				} +			} +		} +		break; +	} + +	inputEditOff(); +} + +void TextMgr::stringRememberForAutoComplete(bool entered) { +#ifdef __DS__ +	DS::findWordCompletions((char *)_inputString); +#endif  }  /** + * Wrap text line to the specified width. + * @param str  String to wrap. + * @param len  Length of line.   * + * Based on GBAGI implementation with permission from the author   */ -void AgiEngine::printStatus(const char *message, ...) { -	va_list args; +char *TextMgr::stringWordWrap(const char *originalText, int16 maxWidth, int16 *calculatedWidthPtr, int16 *calculatedHeightPtr) { +	static char resultWrapBuffer[2000]; +	char *outStr = resultWrapBuffer; +	const char *wordStartPtr; +	int16 lineLen = 0; +	int16 wordLen = 0; +	int curMaxWidth = 0; +	int curHeight = 0; + +	assert(maxWidth > 0); // this routine would create heap corruption in case maxWidth <= 0 + +	while (*originalText) { +		wordStartPtr = originalText; + +		while (*originalText != '\0' && *originalText != ' ' && *originalText != '\n' && *originalText != '\r') +			originalText++; + +		wordLen = originalText - wordStartPtr; + +		if (wordLen && *originalText == '\n' && originalText[-1] == ' ') +			wordLen--; + +		if (wordLen + lineLen >= maxWidth) { +			// Check if outStr isn't msgBuf. If this is the case, outStr hasn't advanced +			// yet, so no output has been written yet +			if (outStr != resultWrapBuffer) { +				if (outStr[-1] == ' ') +					outStr[-1] = '\n'; +				else +					*outStr++ = '\n'; +			} +			curHeight++; + +			lineLen = 0; + +			while (wordLen >= maxWidth) { +				curMaxWidth = maxWidth; + +				memcpy(outStr, wordStartPtr, maxWidth); + +				wordLen -= maxWidth; +				outStr += maxWidth; +				wordStartPtr  += maxWidth; +				*outStr++ = '\n'; +				curHeight++; +			} +		} -	va_start(args, message); +		if (wordLen) { +			memcpy(outStr, wordStartPtr, wordLen); +			outStr += wordLen; +		} +		lineLen += wordLen + 1; -	Common::String x = Common::String::vformat(message, args); +		if (lineLen > curMaxWidth) { +			curMaxWidth = lineLen; -	va_end(args); +			if (*originalText == '\0' || *originalText == ' ' || *originalText == '\n' || *originalText == '\r') +				curMaxWidth--; +		} + +		if (*originalText == '\n') { +			lineLen = 0; +			curHeight++; +		} + +		if (*originalText) +			*outStr++ = *originalText++; +	} +	*outStr = '\0'; +	curHeight++; -	debugC(4, kDebugLevelText, "fg=%d, bg=%d", STATUS_FG, STATUS_BG); -	printText(x.c_str(), 0, 0, _game.lineStatus, 40, STATUS_FG, STATUS_BG); +	if (calculatedWidthPtr) { +		*calculatedWidthPtr = curMaxWidth; +	} +	if (calculatedHeightPtr) { +		*calculatedHeightPtr = curHeight; +	} +	return resultWrapBuffer;  } +// =============================================================== +  static void safeStrcat(Common::String &p, const char *t) {  	if (t != NULL)  		p += t; @@ -523,31 +952,31 @@ static void safeStrcat(Common::String &p, const char *t) {   * @param s  string containing the format specifier   * @param n  logic number   */ -char *AgiEngine::agiSprintf(const char *s) { -	static char agiSprintf_buf[768]; -	Common::String p; +char *TextMgr::stringPrintf(const char *originalText) { +	static char resultPrintfBuffer[2000]; +	Common::String resultString;  	char z[16]; -	debugC(3, kDebugLevelText, "logic %d, '%s'", _game.lognum, s); +	debugC(3, kDebugLevelText, "logic %d, '%s'", _vm->_game.lognum, originalText); -	while (*s) { -		switch (*s) { +	while (*originalText) { +		switch (*originalText) {  		case '%': -			s++; -			switch (*s++) { +			originalText++; +			switch (*originalText++) {  				int i;  			case 'v': -				i = strtoul(s, NULL, 10); -				while (*s >= '0' && *s <= '9') -					s++; -				sprintf(z, "%015i", getvar(i)); +				i = strtoul(originalText, NULL, 10); +				while (*originalText >= '0' && *originalText <= '9') +					originalText++; +				sprintf(z, "%015i", _vm->getVar(i));  				i = 99; -				if (*s == '|') { -					s++; -					i = strtoul(s, NULL, 10); -					while (*s >= '0' && *s <= '9') -						s++; +				if (*originalText == '|') { +					originalText++; +					i = strtoul(originalText, NULL, 10); +					while (*originalText >= '0' && *originalText <= '9') +						originalText++;  				}  				if (i == 99) { @@ -558,173 +987,48 @@ char *AgiEngine::agiSprintf(const char *s) {  				} else {  					i = 15 - i;  				} -				safeStrcat(p, z + i); +				safeStrcat(resultString, z + i);  				break;  			case '0': -				i = strtoul(s, NULL, 10) - 1; -				safeStrcat(p, objectName(i)); +				i = strtoul(originalText, NULL, 10) - 1; +				safeStrcat(resultString, _vm->objectName(i));  				break;  			case 'g': -				i = strtoul(s, NULL, 10) - 1; -				safeStrcat(p, _game.logics[0].texts[i]); +				i = strtoul(originalText, NULL, 10) - 1; +				safeStrcat(resultString, _vm->_game.logics[0].texts[i]);  				break;  			case 'w': -				i = strtoul(s, NULL, 10) - 1; -				safeStrcat(p, _game.egoWords[i].word); +				i = strtoul(originalText, NULL, 10) - 1; +				safeStrcat(resultString, _vm->_words->getEgoWord(i));  				break;  			case 's': -				i = strtoul(s, NULL, 10); -				safeStrcat(p, agiSprintf(_game.strings[i])); +				i = strtoul(originalText, NULL, 10); +				safeStrcat(resultString, stringPrintf(_vm->_game.strings[i]));  				break;  			case 'm': -				i = strtoul(s, NULL, 10) - 1; -				if (_game.logics[_game.lognum].numTexts > i) -					safeStrcat(p, agiSprintf(_game.logics[_game.lognum].texts[i])); +				i = strtoul(originalText, NULL, 10) - 1; +				if (_vm->_game.logics[_vm->_game.lognum].numTexts > i) +					safeStrcat(resultString, stringPrintf(_vm->_game.logics[_vm->_game.lognum].texts[i]));  				break;  			} -			while (*s >= '0' && *s <= '9') -				s++; +			while (*originalText >= '0' && *originalText <= '9') +				originalText++;  			break;  		case '\\': -			s++; +			originalText++;  			// FALL THROUGH  		default: -			p += *s++; +			resultString += *originalText++;  			break;  		}  	} -	assert(p.size() < sizeof(agiSprintf_buf)); -	strcpy(agiSprintf_buf, p.c_str()); -	return agiSprintf_buf; -} - -/** - * Write the status line. - */ -void AgiEngine::writeStatus() { -	char x[64]; - -	if (_debug.statusline) { -		printStatus("%3d(%03d) %3d,%3d(%3d,%3d)               ", -				getvar(0), getvar(1), _game.viewTable[0].xPos, -				_game.viewTable[0].yPos, WIN_TO_PIC_X(_mouse.x), -				WIN_TO_PIC_Y(_mouse.y)); -		return; -	} - -	if (!_game.statusLine) { -		clearLines(_game.lineStatus, _game.lineStatus, 0); -		flushLines(_game.lineStatus, _game.lineStatus); - -#if 0 -		// FIXME: Breaks wrist watch prompt in SQ2 - -		// Clear the user input line as well when clearing the status line -		// Fixes bug #1893564 - AGI: Texts messed out in Naturette 1 -		clearLines(_game.lineUserInput, _game.lineUserInput, 0); -		flushLines(_game.lineUserInput, _game.lineUserInput); -#endif -		return; -	} - -	switch (getLanguage()) { -	case Common::RU_RUS: -		sprintf(x, " \x91\xe7\xa5\xe2: %i \xa8\xa7 %-3i", _game.vars[vScore], _game.vars[vMaxScore]); -		printStatus("%-17s              \x87\xa2\xe3\xaa:%s", x, getflag(fSoundOn) ? "\xa2\xaa\xab " : "\xa2\xeb\xaa\xab"); -		break; -	default: -		sprintf(x, " Score:%i of %-3i", _game.vars[vScore], _game.vars[vMaxScore]); -		printStatus("%-17s             Sound:%s ", x, getflag(fSoundOn) ? "on " : "off"); -		break; -	} -} - -/** - * Print user input prompt. - */ -void AgiEngine::writePrompt() { -	int l, fg, bg, pos; -	int promptLength = strlen(agiSprintf(_game.strings[0])); - -	if (!_game.inputEnabled || _game.inputMode != INPUT_NORMAL) -		return; - -	l = _game.lineUserInput; -	fg = _game.colorFg; -	bg = _game.colorBg; -	pos = _game.cursorPos; - -	debugC(4, kDebugLevelText, "erase line %d", l); -	clearLines(l, l, _game.colorBg); - -	debugC(4, kDebugLevelText, "prompt = '%s'", agiSprintf(_game.strings[0])); -	printText(_game.strings[0], 0, 0, l, promptLength + 1, fg, bg); -	printText((char *)_game.inputBuffer, 0, promptLength, l, pos + 1, fg, bg); -	_gfx->printCharacter(pos + promptLength, l, _game.cursorChar, fg, bg); - -	flushLines(l, l); -	_gfx->doUpdate(); -} - -void AgiEngine::clearPrompt(bool useBlackBg) { -	int l; - -	l = _game.lineUserInput; -	clearLines(l, l, useBlackBg ? 0 : _game.colorBg); -	flushLines(l, l); - -	_gfx->doUpdate(); -} - -/** - * Clear text lines in the screen. - * @param l1  start line - * @param l2  end line - * @param c   color - */ -void AgiEngine::clearLines(int l1, int l2, int c) { -	// do we need to adjust for +8 on topline? -	// inc for endline so it matches the correct num -	// ie, from 22 to 24 is 3 lines, not 2 lines. - -	debugC(4, kDebugLevelText, "clearLines(%d, %d, %d)", l1, l2, c); - -	l1 *= CHAR_LINES; -	l2 *= CHAR_LINES; -	l2 += CHAR_LINES - 1; - -	_gfx->drawRectangle(0, l1, GFX_WIDTH - 1, l2, c); -} - -/** - * - */ -void AgiEngine::flushLines(int l1, int l2) { -	l1 *= CHAR_LINES; -	l2 *= CHAR_LINES; -	l2 += CHAR_LINES - 1; - -	_gfx->flushBlock(0, l1, GFX_WIDTH - 1, l2); -} - -/** - * - */ -void AgiEngine::drawWindow(int x1, int y1, int x2, int y2) { -	_game.window.active = true; -	_game.window.x1 = x1; -	_game.window.y1 = y1; -	_game.window.x2 = x2; -	_game.window.y2 = y2; -	_game.window.buffer = (uint8 *)malloc((x2 - x1 + 1) * (y2 - y1 + 1)); - -	debugC(4, kDebugLevelText, "x1=%d, y1=%d, x2=%d, y2=%d", x1, y1, x2, y2); -	_gfx->saveBlock(x1, y1, x2, y2, _game.window.buffer); -	_gfx->drawBox(x1, y1, x2, y2, MSG_BOX_COLOR, MSG_BOX_LINE, 2); +	assert(resultString.size() < sizeof(resultPrintfBuffer)); +	strcpy(resultPrintfBuffer, resultString.c_str()); +	return resultPrintfBuffer;  }  } // End of namespace Agi diff --git a/engines/agi/text.h b/engines/agi/text.h new file mode 100644 index 0000000000..b42f0982d5 --- /dev/null +++ b/engines/agi/text.h @@ -0,0 +1,201 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef AGI_TEXT_H +#define AGI_TEXT_H + +namespace Agi { + +struct TextPos_Struct { +	int16 row; +	int16 column; +}; + +#define TEXTPOSARRAY_MAX 5 + +struct TextAttrib_Struct { +	byte foreground; +	byte background; +	byte combinedForeground; +	byte combinedBackground; +}; + +#define TEXTATTRIBARRAY_MAX 5 + +struct MessageState_Struct { +	uint8 type; +	int16 wanted_Text_Width; +	TextPos_Struct wanted_TextPos; +	bool dialogue_Open; +	uint8 newline_Char; +	bool  window_Active; +	TextPos_Struct textPos; +	TextPos_Struct textPos_Edge; +	int16 textSize_Width; +	int16 textSize_Height; +	uint16 printed_Height; +	 +	int16 backgroundPos_x; +	int16 backgroundPos_y; +	int16 backgroundSize_Width; +	int16 backgroundSize_Height; +}; + +// this defines here are for calculating character-size inside the visual-screen! +#define FONT_VISUAL_WIDTH			4 +#define FONT_VISUAL_HEIGHT			8 + +#define FONT_DISPLAY_WIDTH			8 +#define FONT_DISPLAY_HEIGHT			8 +#define FONT_ROW_CHARACTERS			25 +#define FONT_COLUMN_CHARACTERS		40 +#define FONT_BYTES_PER_CHARACTER	8 + +#define HEIGHT_MAX					20 + +#define TEXT_STRING_MAX_SIZE        40 + +class TextMgr { +private: +	Words *_words; +	GfxMgr *_gfx; +	AgiEngine *_vm; +	SystemUI *_systemUI; + +public: +	TextMgr(AgiEngine *vm, Words *words, GfxMgr *gfx); +	~TextMgr(); + +	void init(SystemUI *systemUI); + +	TextPos_Struct _textPos; +	int16          _textPosArrayCount; +	TextPos_Struct _textPosArray[TEXTPOSARRAY_MAX]; + +	TextAttrib_Struct _textAttrib; +	int16             _textAttribArrayCount; +	TextAttrib_Struct _textAttribArray[TEXTATTRIBARRAY_MAX]; + +	uint16 _window_Row_Min; +	uint16 _window_Row_Max; +	int16 _reset_Column; + +	void configureScreen(uint16 row_Min); +	uint16 getWindowRowMin(); + +	void dialogueOpen(); +	void dialogueClose(); + +	void charPos_Clip(int16 &row, int16 &column); +	void charPos_Set(int16 row, int16 column); +	void charPos_Get(TextPos_Struct *posPtr); +	void charPos_Set(TextPos_Struct *posPtr); +	void charPos_Push(); +	void charPos_Pop(); +	void charPos_SetInsideWindow(int16 windowRow, int16 windowColumn); +	void charAttrib_Set(byte foreground, byte background); +	byte charAttrib_GetForeground(); +	byte charAttrib_GetBackground(); +	void charAttrib_Push(); +	void charAttrib_Pop(); +	byte calculateTextBackground(byte background); + +	void display(int16 textNr, int16 textRow, int16 textColumn); +	void displayText(const char *textPtr, bool disabledLook = false); +	void displayCharacter(byte character, bool disabledLook = false); + +	void displayTextInsideWindow(const char *textPtr, int16 windowRow, int16 windowColumn); + +	MessageState_Struct _messageState; + +	void printAt(int16 textNr, int16 textPos_Row, int16 textPos_Column, int16 text_Width); +	void print(int16 textNr); +	bool messageBox(const char *textPtr); +	void drawMessageBox(const char *textPtr, int16 wantedHeight = 0, int16 wantedWidth = 0, bool wantedForced = false); +	void closeWindow(); + +	void statusRow_Set(int16 row); +	int16 statusRow_Get(); + +	void statusEnable(); +	void statusDisable(); +	bool statusEnabled(); + +	void statusDraw(); +	void statusClear(); + +	bool _statusEnabled; +	int16 _statusRow; + +	void clearLine(int16 row, byte color); +	void clearLines(int16 row_Upper, int16 row_Lower, byte color); +	void clearBlock(int16 row_Upper, int16 column_Upper, int16 row_Lower, int16 column_Lower, byte color); + +	void clearBlockInsideWindow(int16 windowRow, int16 windowColumn, int16 width, byte color); + +	bool  _inputEditEnabled; +	byte  _inputCursorChar; + +	bool  _promptEnabled; +	int16 _promptRow; +	int16 _promptCursorPos; +	byte  _prompt[42]; +	byte  _promptPrevious[42]; + +	bool inputGetEditStatus(); +	void inputEditOn(); +	void inputEditOff(); +	void inputSetCursorChar(int16 cursorChar); +	byte inputGetCursorChar(); + +	void promptReset(); +	void promptEnable(); +	void promptDisable(); +	bool promptIsEnabled(); + +	void promptRow_Set(int16 row); +	int16 promptRow_Get(); +	void promptCharPress(int16 newChar); +	void promptCancelLine(); +	void promptEchoLine(); +	void promptRedraw(); +	void promptClear(); // for AGI1 +	void promptRememberForAutoComplete(bool entered = false); // for auto-completion + +	bool  _inputStringEntered; +	int16 _inputStringMaxLen; +	int16 _inputStringCursorPos; +	byte  _inputString[42]; + +	bool stringWasEntered(); +	void stringSet(const char *text); +	void stringEdit(int16 stringMaxLen); +	void stringCharPress(int16 newChar); +	void stringRememberForAutoComplete(bool entered = false); // for auto-completion + +	char *stringPrintf(const char *originalText); +	char *stringWordWrap(const char *originalText, int16 maxWidth, int16 *calculatedWidthPtr = nullptr, int16 *calculatedHeightPtr = nullptr); +}; + +} // End of namespace Agi + +#endif /* AGI_TEXT_H */ diff --git a/engines/agi/view.cpp b/engines/agi/view.cpp index 6a274a1b73..66508a6c27 100644 --- a/engines/agi/view.cpp +++ b/engines/agi/view.cpp @@ -21,107 +21,60 @@   */  #include "agi/agi.h" +#include "agi/graphics.h"  #include "agi/sprite.h"  namespace Agi { -void AgiEngine::lSetCel(VtEntry *v, int n) { -	ViewLoop *currentVl; -	ViewCel *currentVc; +void AgiEngine::updateView(ScreenObjEntry *screenObj) { +	int16 celNr, lastCelNr; -	v->currentCel = n; - -	currentVl = &_game.views[v->currentView].loop[v->currentLoop]; - -	// Added by Amit Vainsencher <amitv@subdimension.com> to prevent -	// crash in KQ1 -- not in the Sierra interpreter -	if (currentVl->numCels == 0) -		return; - -	// WORKAROUND: This is a very nasty hack to fix a bug in the KQ4 introduction -	// In its original form, it caused a lot of regressions, including KQ4 bugs and crashes -	// Refer to Sarien bug #588899 for the original issue -	// Modifying this workaround to only work for a specific view in the KQ4 intro fixes several -	// ScummVM bugs. Refer to bugs #1660486, #1660169, #1660192, #1660162 and #1660354 -	// FIXME: Remove this workaround and investigate the reason for the erroneous actor behavior -	// in the KQ4 introduction -	// It seems there's either a bug with KQ4's logic script 120 (the intro script) -	// or flag 64 is not set correctly, which causes the erroneous behavior from the actors -	if (getGameID() == GID_KQ4 && !(v->flags & fUpdate) && (v->currentView == 172)) -		return; - -	currentVc = ¤tVl->cel[n]; -	v->celData = currentVc; -	v->xSize = currentVc->width; -	v->ySize = currentVc->height; -} - -void AgiEngine::lSetLoop(VtEntry *v, int n) { -	ViewLoop *currentVl; -	debugC(7, kDebugLevelResources, "vt entry #%d, loop = %d", v->entry, n); - -	// Added to avoid crash when leaving the arcade machine in MH1 -	// -- not in AGI 2.917 -	if (n >= v->numLoops) -		n = 0; - -	v->currentLoop = n; -	currentVl = &_game.views[v->currentView].loop[v->currentLoop]; - -	v->numCels = currentVl->numCels; -	if (v->currentCel >= v->numCels) -		v->currentCel = 0; - -	v->loopData = &_game.views[v->currentView].loop[n]; -} - -void AgiEngine::updateView(VtEntry *v) { -	int cel, lastCel; - -	if (v->flags & fDontupdate) { -		v->flags &= ~fDontupdate; +	if (screenObj->flags & fDontupdate) { +		screenObj->flags &= ~fDontupdate;  		return;  	} -	cel = v->currentCel; -	lastCel = v->numCels - 1; +	celNr = screenObj->currentCelNr; +	lastCelNr = screenObj->celCount - 1; -	switch (v->cycle) { +	switch (screenObj->cycle) {  	case kCycleNormal: -		if (++cel > lastCel) -			cel = 0; +		celNr++; +		if (celNr > lastCelNr) +			celNr = 0;  		break;  	case kCycleEndOfLoop: -		if (cel < lastCel) { -			debugC(5, kDebugLevelResources, "cel %d (last = %d)", cel + 1, lastCel); -			if (++cel != lastCel) +		if (celNr < lastCelNr) { +			debugC(5, kDebugLevelResources, "cel %d (last = %d)", celNr + 1, lastCelNr); +			if (++celNr != lastCelNr)  				break;  		} -		setflag(v->parm1, true); -		v->flags &= ~fCycling; -		v->direction = 0; -		v->cycle = kCycleNormal; +		setflag(screenObj->loop_flag, true); +		screenObj->flags &= ~fCycling; +		screenObj->direction = 0; +		screenObj->cycle = kCycleNormal;  		break;  	case kCycleRevLoop: -		if (cel) { -			if (--cel) +		if (celNr) { +			celNr--; +			if (celNr)  				break;  		} -		setflag(v->parm1, true); -		v->flags &= ~fCycling; -		v->direction = 0; -		v->cycle = kCycleNormal; +		setflag(screenObj->loop_flag, true); +		screenObj->flags &= ~fCycling; +		screenObj->direction = 0; +		screenObj->cycle = kCycleNormal;  		break;  	case kCycleReverse: -		if (cel == 0) { -			cel = lastCel; +		if (celNr == 0) { +			celNr = lastCelNr;  		} else { -			cel--; +			celNr--;  		}  		break;  	} -	setCel(v, cel); +	setCel(screenObj, celNr);  }  /* @@ -134,191 +87,475 @@ void AgiEngine::updateView(VtEntry *v) {   * and fills the corresponding views array element.   * @param n number of view resource to decode   */ -int AgiEngine::decodeView(int n) { -	int loop, cel; -	uint8 *v, *lptr; -	uint16 lofs, cofs; -	ViewLoop *vl; -	ViewCel *vc; +int AgiEngine::decodeView(byte *resourceData, uint16 resourceSize, int16 viewNr) { +	AgiView *viewData = &_game.views[viewNr]; +	uint16 headerId = 0; +	byte   headerStepSize = 0; +	byte   headerCycleTime = 0; +	byte   headerLoopCount = 0; +	uint16 headerDescriptionOffset = 0; +	bool   isAGI256Data = false; + +	AgiViewLoop *loopData = nullptr; +	uint16 loopOffset = 0; +	byte   loopHeaderCelCount = 0; + +	AgiViewCel *celData = nullptr; +	uint16 celOffset = 0; +	byte   celHeaderWidth = 0; +	byte   celHeaderHeight = 0; +	byte   celHeaderTransparencyMirror = 0; +	byte   celHeaderClearKey = 0; +	bool   celHeaderMirrored = false; +	byte   celHeaderMirrorLoop = 0; + +	byte  *celCompressedData = nullptr; +	uint16 celCompressedSize = 0; +//	byte  *rawBitmap = nullptr; + +	debugC(5, kDebugLevelResources, "decode_view(%d)", viewNr); + +	if (resourceSize < 5) +		error("unexpected end of view data for view %d", viewNr); + +	headerId = READ_LE_UINT16(resourceData); +	if (getVersion() < 0x2000) { +		headerStepSize = resourceData[0]; +		headerCycleTime = resourceData[1]; +	} +	headerLoopCount = resourceData[2]; +	headerDescriptionOffset = READ_LE_UINT16(resourceData + 3); + +	if (headerId == 0xF00F) +		isAGI256Data = true; // AGI 256-2 view detected, 256 color view + +	viewData->headerStepSize = headerStepSize; +	viewData->headerCycleTime = headerCycleTime; +	viewData->loopCount = headerLoopCount; +	viewData->description = nullptr; +	viewData->loop = nullptr; + +	if (headerDescriptionOffset) { +		// Figure out length of description +		uint16 descriptionPos = headerDescriptionOffset; +		uint16 descriptionLen = 0; +		while (descriptionPos < resourceSize) { +			if (resourceData[descriptionPos] == 0) +				break; +			descriptionPos++; +			descriptionLen++; +		} +		// Allocate memory for description +		viewData->description = new byte[descriptionLen + 1]; +		// Copy description over +		memcpy(viewData->description, resourceData + headerDescriptionOffset, descriptionLen); +		viewData->description[descriptionLen] = 0; // set terminator +	} + +	if (!viewData->loopCount) // no loops, exit now +		return errOK; + +	// Check, if at least the loop-offsets are available +	if (resourceSize < 5 + (headerLoopCount * 2)) +		error("unexpected end of view data for view %d", viewNr); + +	// Allocate space for loop-information +	loopData = new AgiViewLoop[headerLoopCount]; +	viewData->loop = loopData; + +	for (int16 loopNr = 0; loopNr < headerLoopCount; loopNr++) { +		loopOffset = READ_LE_UINT16(resourceData + 5 + (loopNr * 2)); + +		// Check, if at least the loop-header is available +		if (resourceSize < (loopOffset + 1)) +			error("unexpected end of view data for view %d", viewNr); + +		// loop-header: +		//  celCount:BYTE +		//  relativeCelOffset[0]:WORD +		//  relativeCelOffset[1]:WORD +		//  etc. +		loopHeaderCelCount = resourceData[loopOffset]; + +		loopData->celCount = loopHeaderCelCount; +		loopData->cel = nullptr; + +		// Check, if at least the cel-offsets for current loop are available +		if (resourceSize < (loopOffset + 1 + (loopHeaderCelCount * 2))) +			error("unexpected end of view data for view %d", viewNr); + +		if (loopHeaderCelCount) { +			// Allocate space for cel-information of current loop +			celData = new AgiViewCel[loopHeaderCelCount]; +			loopData->cel = celData; + +			for (int16 celNr = 0; celNr < loopHeaderCelCount; celNr++) { +				celOffset = READ_LE_UINT16(resourceData + loopOffset + 1 + (celNr * 2)); +				celOffset += loopOffset; // cel offset is relative to loop offset, so adjust accordingly + +				// Check, if at least the cel-header is available +				if (resourceSize < (celOffset + 3)) +					error("unexpected end of view data for view %d", viewNr); + +				// cel-header: +				//  width:BYTE +				//  height:BYTE +				//  Transparency + Mirroring:BYTE +				//  celData follows +				celHeaderWidth = resourceData[celOffset + 0]; +				celHeaderHeight = resourceData[celOffset + 1]; +				celHeaderTransparencyMirror = resourceData[celOffset + 2]; + +				if (!isAGI256Data) { +					// regular AGI view data +					// Transparency + Mirroring byte is as follows: +					//  Bit 0-3 - clear key +					//  Bit 4-6 - original loop, that is not supposed to be mirrored in any case +					//  Bit 7   - apply mirroring +					celHeaderClearKey = celHeaderTransparencyMirror & 0x0F; // bit 0-3 is the clear key +					celHeaderMirrored = false; +					if (celHeaderTransparencyMirror & 0x80) { +						// mirror bit is set +						celHeaderMirrorLoop = (celHeaderTransparencyMirror >> 4) & 0x07; +						if (celHeaderMirrorLoop != loopNr) { +							// only set to mirror'd in case we are not the original loop +							celHeaderMirrored = true; +						} +					} +				} else { +					// AGI256-2 view data +					celHeaderClearKey = celHeaderTransparencyMirror; // full 8 bits for clear key +					celHeaderMirrored = false; +				} + +				celData->width = celHeaderWidth; +				celData->height = celHeaderHeight; +				celData->clearKey = celHeaderClearKey; +				celData->mirrored = celHeaderMirrored; + +				// Now decompress cel-data +				if ((celHeaderWidth == 0) && (celHeaderHeight == 0)) +					error("view cel is 0x0"); + +				celCompressedData = resourceData + celOffset + 3; +				celCompressedSize = resourceSize - (celOffset + 3); + +				if (!isAGI256Data) { +					unpackViewCelData(celData, celCompressedData, celCompressedSize); +				} else { +					unpackViewCelDataAGI256(celData, celCompressedData, celCompressedSize); +				} +				celData++; +			} +		} +		 +		loopData++; +	} + +	return errOK; +} -	debugC(5, kDebugLevelResources, "decode_view(%d)", n); -	v = _game.views[n].rdata; +void AgiEngine::unpackViewCelData(AgiViewCel *celData, byte *compressedData, uint16 compressedSize) { +	byte *rawBitmap = new byte[celData->width * celData->height]; +	int16 remainingHeight = celData->height; +	int16 remainingWidth = celData->width; +	bool  isMirrored = celData->mirrored; +	byte curByte; +	byte curColor; +	byte curChunkLen; +	int16 adjustPreChangeSingle = 0; +	int16 adjustAfterChangeSingle = +1; + +	celData->rawBitmap = rawBitmap; + +	if (isMirrored) { +		adjustPreChangeSingle = -1; +		adjustAfterChangeSingle = 0; +		rawBitmap += celData->width; +	} -	assert(v != NULL); +	while (remainingHeight) { +		if (!compressedSize) +			error("unexpected end of data, while unpacking AGI256 data"); -	_game.views[n].agi256_2 = (READ_LE_UINT16(v) == 0xf00f); // Detect AGI256-2 views by their header bytes -	_game.views[n].descr = READ_LE_UINT16(v + 3) ? (char *)(v + READ_LE_UINT16(v + 3)) : (char *)(v + 3); +		curByte = *compressedData++; +		compressedSize--; -	// if no loops exist, return! -	if ((_game.views[n].numLoops = *(v + 2)) == 0) -		return errNoLoopsInView; +		if (curByte == 0) { +			curColor = celData->clearKey; +			curChunkLen = remainingWidth; +		} else { +			curColor = curByte >> 4; +			curChunkLen = curByte & 0x0F; +			if (curChunkLen > remainingWidth) +				error("invalid chunk in view data"); +		} -	// allocate memory for all views -	_game.views[n].loop = (ViewLoop *)calloc(_game.views[n].numLoops, sizeof(ViewLoop)); +		switch (curChunkLen) { +		case 0: +			break; +		case 1: +			rawBitmap += adjustPreChangeSingle; +			*rawBitmap = curColor; +			rawBitmap += adjustAfterChangeSingle; +			break; +		default: +			if (isMirrored) +				rawBitmap -= curChunkLen; +			memset(rawBitmap, curColor, curChunkLen); +			if (!isMirrored) +				rawBitmap += curChunkLen; +			break; +		} -	if (_game.views[n].loop == NULL) -		return errNotEnoughMemory; +		remainingWidth -= curChunkLen; +			 +		if (curByte == 0) { +			remainingWidth = celData->width; +			remainingHeight--; -	// decode all of the loops in this view -	lptr = v + 5;		// first loop address +			if (isMirrored) +				rawBitmap += celData->width * 2; +		} +	} -	for (loop = 0; loop < _game.views[n].numLoops; loop++, lptr += 2) { -		lofs = READ_LE_UINT16(lptr);	// loop header offset -		vl = &_game.views[n].loop[loop];	// the loop struct +	// for CGA rendering, apply dithering +	switch (_renderMode) { +	case RENDERMODE_CGA: { +		uint16 totalPixels = celData->width * celData->height; -		vl->numCels = *(v + lofs); -		debugC(6, kDebugLevelResources, "view %d, num_cels = %d", n, vl->numCels); -		vl->cel = (ViewCel *)calloc(vl->numCels, sizeof(ViewCel)); +		// dither clear key +		celData->clearKey = _gfx->getCGAMixtureColor(celData->clearKey); -		if (vl->cel == NULL) { -			free(_game.views[n].loop); -			_game.views[n].numLoops = 0; -			return errNotEnoughMemory; +		rawBitmap = celData->rawBitmap; +		for (uint16 pixelNr = 0; pixelNr < totalPixels; pixelNr++) { +			curColor = *rawBitmap; +			*rawBitmap = _gfx->getCGAMixtureColor(curColor); +			rawBitmap++;  		} +		break; +	} +	default: +		break; +	} +} -		// decode the cells -		for (cel = 0; cel < vl->numCels; cel++) { -			cofs = lofs + READ_LE_UINT16(v + lofs + 1 + (cel * 2)); -			vc = &vl->cel[cel]; - -			vc->width = *(v + cofs); -			vc->height = *(v + cofs + 1); - -			if (!_game.views[n].agi256_2) { -				vc->transparency = *(v + cofs + 2) & 0xf; -				vc->mirrorLoop = (*(v + cofs + 2) >> 4) & 0x7; -				vc->mirror = (*(v + cofs + 2) >> 7) & 0x1; -			} else { -				// Mirroring is disabled for AGI256-2 views because -				// AGI256-2 uses whole 8 bits for the transparency variable. -				vc->transparency = *(v + cofs + 2); -				vc->mirrorLoop = 0; -				vc->mirror = 0; -			} +void AgiEngine::unpackViewCelDataAGI256(AgiViewCel *celData, byte *compressedData, uint16 compressedSize) { +	byte *rawBitmap = new byte[celData->width * celData->height]; +	int16 remainingHeight = celData->height; +	int16 remainingWidth = celData->width; +	byte curByte; -			// skip over width/height/trans|mirror data -			cofs += 3; +	celData->rawBitmap = rawBitmap; -			vc->data = v + cofs; +	while (remainingHeight) { +		if (!compressedSize) +			error("unexpected end of data, while unpacking AGI256 data"); -			// If mirror_loop is pointing to the current loop, -			// then this is the original. -			if (vc->mirrorLoop == loop) -				vc->mirror = 0; -		}		// cel -	}			// loop +		curByte = *compressedData++; +		compressedSize--; -	return errOK; +		if (curByte == 0) { +			// Go to next vertical position +			if (remainingWidth) { +				// fill remaining bytes with clear key +				memset(rawBitmap, celData->clearKey, remainingWidth); +				rawBitmap += remainingWidth; +				remainingWidth = 0; +			} +		} else { +			*rawBitmap = curByte; +			rawBitmap++; +		} + +		if (curByte == 0) { +			remainingWidth = celData->width; +			remainingHeight--; +		} +	}  }  /**   * Unloads all data in a view resource   * @param n number of view resource   */ -void AgiEngine::unloadView(int n) { -	int x; +void AgiEngine::unloadView(int16 viewNr) { +	AgiView *viewData = &_game.views[viewNr]; -	debugC(5, kDebugLevelResources, "discard view %d", n); -	if (~_game.dirView[n].flags & RES_LOADED) +	debugC(5, kDebugLevelResources, "discard view %d", viewNr); +	if (!(_game.dirView[viewNr].flags & RES_LOADED))  		return;  	// Rebuild sprite list, see Sarien bug #779302 -	_sprites->eraseBoth(); -	_sprites->blitBoth(); -	_sprites->commitBoth(); +	_sprites->eraseSprites(); -	// free all the loops -	for (x = 0; x < _game.views[n].numLoops; x++) -		free(_game.views[n].loop[x].cel); +	// free data +	for (int16 loopNr = 0; loopNr < viewData->loopCount; loopNr++) { +		AgiViewLoop *loopData = &viewData->loop[loopNr]; +		for (int16 celNr = 0; celNr < loopData->celCount; celNr++) { +			AgiViewCel *celData = &loopData->cel[celNr]; -	free(_game.views[n].loop); -	free(_game.views[n].rdata); +			delete[] celData->rawBitmap; +		} +		delete[] loopData->cel; +	} +	delete[] viewData->loop; -	_game.dirView[n].flags &= ~RES_LOADED; -} +	if (viewData->description) +		delete[] viewData->description; -/** - * Set a view table entry to use the specified cel of the current loop. - * @param v pointer to view table entry - * @param n number of cel - */ -void AgiEngine::setCel(VtEntry *v, int n) { -	assert(v->viewData != NULL); -	assert(v->numCels >= n); +	viewData->headerCycleTime = 0; +	viewData->headerStepSize = 0; +	viewData->description = nullptr; +	viewData->loop = nullptr; +	viewData->loopCount = 0; -	lSetCel(v, n); +	// Mark this view as not loaded anymore +	_game.dirView[viewNr].flags &= ~RES_LOADED; -	// If position isn't appropriate, update it accordingly -	clipViewCoordinates(v); +	_sprites->buildAllSpriteLists(); +	_sprites->drawAllSpriteLists();  }  /** - * Restrict view table entry's position so it stays wholly inside the screen. - * Also take horizon into account when clipping if not set to ignore it. - * @param v pointer to view table entry + * Set a view table entry to use the specified view resource. + * @param screenObj pointer to screen object + * @param viewNr number of AGI view resource   */ -void AgiEngine::clipViewCoordinates(VtEntry *v) { -	if (v->xPos + v->xSize > _WIDTH) { -		v->flags |= fUpdatePos; -		v->xPos = _WIDTH - v->xSize; +void AgiEngine::setView(ScreenObjEntry *screenObj, int16 viewNr) { +	screenObj->viewData = &_game.views[viewNr]; +	screenObj->currentViewNr = viewNr; +	screenObj->loopCount = screenObj->viewData->loopCount; +	screenObj->viewReplaced = true; + +	if (getVersion() < 0x2000) { +		screenObj->stepSize = screenObj->viewData->headerStepSize; +		screenObj->cycleTime = screenObj->viewData->headerCycleTime; +		screenObj->cycleTimeCount = 0;  	} -	if (v->yPos - v->ySize + 1 < 0) { -		v->flags |= fUpdatePos; -		v->yPos = v->ySize - 1; +	if (screenObj->currentLoopNr >= screenObj->loopCount) { +		setLoop(screenObj, 0); +	} else { +		setLoop(screenObj, screenObj->currentLoopNr);  	} -	if (v->yPos <= _game.horizon && (~v->flags & fIgnoreHorizon)) { -		v->flags |= fUpdatePos; -		v->yPos = _game.horizon + 1; +} + +/** + * Set a view table entry to use the specified loop of the current view. + * @param screenObj pointer to screen object + * @param loopNr number of loop + */ +void AgiEngine::setLoop(ScreenObjEntry *screenObj, int16 loopNr) { +	assert(screenObj->viewData != NULL); + +	if (screenObj->loopCount == 0) { +		warning("setLoop() called on screen object %d, which has no loops (view %d)", screenObj->objectNr, screenObj->currentViewNr); +		return;  	} -	if (getVersion() < 0x2000) { -		v->flags |= fDontupdate; +	if (loopNr >= screenObj->loopCount) { +		// requested loop not existant +		// instead of error()ing out, we instead clip it +		// At least required for possibly Manhunter 1 according to previous comment when leaving the arcade machine +		// TODO: check MH1 +		int16 requestedLoopNr = loopNr; + +		loopNr = screenObj->loopCount - 1; + +		warning("Non-existant loop requested for screen object %d", screenObj->objectNr); +		warning("view %d, requested loop %d -> clipped to loop %d", screenObj->currentViewNr, requestedLoopNr, loopNr);  	} +	AgiViewLoop *curViewLoop = &_game.views[screenObj->currentViewNr].loop[loopNr]; + +	screenObj->currentLoopNr = loopNr; +	screenObj->loopData = curViewLoop; +	screenObj->celCount = curViewLoop->celCount; + +	if (screenObj->currentCelNr >= screenObj->celCount) { +		setCel(screenObj, 0); +	} else { +		setCel(screenObj, screenObj->currentCelNr); +	}  }  /** - * Set a view table entry to use the specified loop of the current view. - * @param v pointer to view table entry - * @param n number of loop + * Set a view table entry to use the specified cel of the current loop. + * @param screenObj pointer to screen object + * @param celNr number of cel   */ -void AgiEngine::setLoop(VtEntry *v, int n) { -	assert(v->viewData != NULL); -	assert(v->numLoops >= n); -	lSetLoop(v, n); -	setCel(v, v->currentCel); +void AgiEngine::setCel(ScreenObjEntry *screenObj, int16 celNr) { +	assert(screenObj->viewData != NULL); + +	AgiViewLoop *curViewLoop = &_game.views[screenObj->currentViewNr].loop[screenObj->currentLoopNr]; + +	// Added by Amit Vainsencher <amitv@subdimension.com> to prevent +	// crash in KQ1 -- not in the Sierra interpreter +	if (curViewLoop->celCount == 0) { +		warning("setCel() called on screen object %d, which has no cels (view %d)", screenObj->objectNr, screenObj->currentViewNr); +		return; +	} + +	if (celNr >= screenObj->celCount) { +		// requested cel not existant +		// instead of error()ing out, we instead clip it +		// At least required for King's Quest 3 on Apple IIgs - walking the planks death cutscene +		// see bug #5832, which is a game bug! +		int16 requestedCelNr = celNr; + +		celNr = screenObj->celCount - 1; + +		warning("Non-existant cel requested for screen object %d", screenObj->objectNr); +		warning("view %d, loop %d, requested cel %d -> clipped to cel %d", screenObj->currentViewNr, screenObj->currentLoopNr, requestedCelNr, celNr); +	} + +	screenObj->currentCelNr = celNr; + +	AgiViewCel *curViewCel; +	curViewCel         = &curViewLoop->cel[celNr]; +	screenObj->celData = curViewCel; +	screenObj->xSize   = curViewCel->width; +	screenObj->ySize   = curViewCel->height; + +	// If position isn't appropriate, update it accordingly +	clipViewCoordinates(screenObj);  }  /** - * Set a view table entry to use the specified view resource. + * Restrict view table entry's position so it stays wholly inside the screen. + * Also take horizon into account when clipping if not set to ignore it.   * @param v pointer to view table entry - * @param n number of AGI view resource   */ -void AgiEngine::setView(VtEntry *v, int n) { -	v->viewData = &_game.views[n]; -	v->currentView = n; -	v->numLoops = v->viewData->numLoops; -	v->viewReplaced = true; +void AgiEngine::clipViewCoordinates(ScreenObjEntry *screenObj) { +	if (screenObj->xPos + screenObj->xSize > SCRIPT_WIDTH) { +		screenObj->flags |= fUpdatePos; +		screenObj->xPos = SCRIPT_WIDTH - screenObj->xSize; +	} +	if (screenObj->yPos - screenObj->ySize + 1 < 0) { +		screenObj->flags |= fUpdatePos; +		screenObj->yPos = screenObj->ySize - 1; +	} +	if (screenObj->yPos <= _game.horizon && (~screenObj->flags & fIgnoreHorizon)) { +		screenObj->flags |= fUpdatePos; +		screenObj->yPos = _game.horizon + 1; +	}  	if (getVersion() < 0x2000) { -		v->stepSize = v->viewData->rdata[0]; -		v->cycleTime = v->viewData->rdata[1]; -		v->cycleTimeCount = 0; +		screenObj->flags |= fDontupdate;  	} -	setLoop(v, v->currentLoop >= v->numLoops ? 0 : v->currentLoop); +  }  /**   * Set the view table entry as updating.   * @param v pointer to view table entry   */ -void AgiEngine::startUpdate(VtEntry *v) { +void AgiEngine::startUpdate(ScreenObjEntry *v) {  	if (~v->flags & fUpdate) { -		_sprites->eraseBoth(); - +		_sprites->eraseSprites();  		v->flags |= fUpdate; -		_sprites->blitBoth(); -		_sprites->commitBoth(); +		_sprites->buildAllSpriteLists(); +		_sprites->drawAllSpriteLists();  	}  } @@ -326,13 +563,12 @@ void AgiEngine::startUpdate(VtEntry *v) {   * Set the view table entry as non-updating.   * @param v pointer to view table entry   */ -void AgiEngine::stopUpdate(VtEntry *v) { -	if (v->flags & fUpdate) { -		_sprites->eraseBoth(); - -		v->flags &= ~fUpdate; -		_sprites->blitBoth(); -		_sprites->commitBoth(); +void AgiEngine::stopUpdate(ScreenObjEntry *viewPtr) { +	if (viewPtr->flags & fUpdate) { +		_sprites->eraseSprites(); +		viewPtr->flags &= ~fUpdate; +		_sprites->buildAllSpriteLists(); +		_sprites->drawAllSpriteLists();  	}  } @@ -351,67 +587,68 @@ static int loopTable4[] = {   * This function is called at the end of each interpreter cycle   * to update the view table entries and blit the sprites.   */ -void AgiEngine::updateViewtable() { -	VtEntry *v; -	int i, loop; +void AgiEngine::updateScreenObjTable() { +	ScreenObjEntry *screenObj; +	int16 changeCount, loopNr; -	i = 0; -	for (v = _game.viewTable; v < &_game.viewTable[MAX_VIEWTABLE]; v++) { -		if ((v->flags & (fAnimated | fUpdate | fDrawn)) != (fAnimated | fUpdate | fDrawn)) { +	changeCount = 0; +	for (screenObj = _game.screenObjTable; screenObj < &_game.screenObjTable[SCREENOBJECTS_MAX]; screenObj++) { +		if ((screenObj->flags & (fAnimated | fUpdate | fDrawn)) != (fAnimated | fUpdate | fDrawn)) {  			continue;  		} -		i++; +		changeCount++; -		loop = 4; -		if (~v->flags & fFixLoop) { -			switch (v->numLoops) { +		loopNr = 4; +		if (!(screenObj->flags & fFixLoop)) { +			switch (screenObj->loopCount) {  			case 2:  			case 3: -				loop = loopTable2[v->direction]; +				loopNr = loopTable2[screenObj->direction];  				break;  			case 4: -				loop = loopTable4[v->direction]; +				loopNr = loopTable4[screenObj->direction];  				break;  			default:  				// for KQ4  				if (getVersion() == 0x3086 || getGameID() == GID_KQ4) -					loop = loopTable4[v->direction]; +					loopNr = loopTable4[screenObj->direction];  				break;  			}  		}  		// AGI 2.272 (ddp, xmas) doesn't test step_time_count! -		if (loop != 4 && loop != v->currentLoop) { +		if (loopNr != 4 && loopNr != screenObj->currentLoopNr) {  			if (getVersion() <= 0x2272 || -			    v->stepTimeCount == 1) { -				setLoop(v, loop); +			    screenObj->stepTimeCount == 1) { +				setLoop(screenObj, loopNr);  			}  		} -		if (~v->flags & fCycling) -			continue; - -		if (v->cycleTimeCount == 0) -			continue; - -		if (--v->cycleTimeCount == 0) { -			updateView(v); -			v->cycleTimeCount = v->cycleTime; +		if (screenObj->flags & fCycling) { +			if (screenObj->cycleTimeCount) { +				screenObj->cycleTimeCount--; +				if (screenObj->cycleTimeCount == 0) { +					updateView(screenObj); +					screenObj->cycleTimeCount = screenObj->cycleTime; +				} +			}  		}  	} -	if (i) { -		_sprites->eraseUpdSprites(); +	if (changeCount) { +		_sprites->eraseRegularSprites();  		updatePosition(); -		_sprites->blitUpdSprites(); -		_sprites->commitUpdSprites(); -		_game.viewTable[0].flags &= ~(fOnWater | fOnLand); +		_sprites->buildRegularSpriteList(); +		_sprites->drawRegularSpriteList(); +		_sprites->showRegularSpriteList(); + +		_game.screenObjTable[SCREENOBJECTS_EGO_ENTRY].flags &= ~(fOnWater | fOnLand);  	}  } -bool AgiEngine::isEgoView(const VtEntry* v) { -	return v == _game.viewTable; +bool AgiEngine::isEgoView(const ScreenObjEntry* screenObj) { +	return screenObj == _game.screenObjTable;  }  } // End of namespace Agi diff --git a/engines/agi/view.h b/engines/agi/view.h index b82fbe04d7..3afe3dc84b 100644 --- a/engines/agi/view.h +++ b/engines/agi/view.h @@ -25,36 +25,40 @@  namespace Agi { -struct ViewCel { +struct AgiViewCel {  	uint8 height;  	uint8 width; -	uint8 transparency; -	uint8 mirrorLoop; -	uint8 mirror; -	uint8 *data; +	uint8 clearKey; +	bool  mirrored; +	byte *rawBitmap;  }; -struct ViewLoop { -	int numCels; -	struct ViewCel *cel; +struct AgiViewLoop { +	int16 celCount; +	AgiViewCel *cel;  };  /**   * AGI view resource structure.   */  struct AgiView { -	int numLoops; -	struct ViewLoop *loop; -	bool agi256_2; -	char *descr; -	uint8 *rdata; +	byte  headerStepSize; +	byte  headerCycleTime; +	byte *description; +	int16 loopCount; +	AgiViewLoop *loop; + +	//struct ViewLoop *loop; +	//bool agi256_2; +	//byte *resourceData;  };  enum MotionType {  	kMotionNormal = 0,  	kMotionWander = 1,  	kMotionFollowEgo = 2, -	kMotionMoveObj = 3 +	kMotionMoveObj = 3, +	kMotionEgo = 4  };  enum CycleType { @@ -65,60 +69,73 @@ enum CycleType {   };  enum ViewFlags { -	fDrawn 			= (1 << 0), -	fIgnoreBlocks 	= (1 << 1), -	fFixedPriority	= (1 << 2), -	fIgnoreHorizon	= (1 << 3), -	fUpdate			= (1 << 4), -	fCycling		= (1 << 5), -	fAnimated		= (1 << 6), -	fMotion			= (1 << 7), -	fOnWater		= (1 << 8), -	fIgnoreObjects	= (1 << 9), -	fUpdatePos		= (1 << 10), -	fOnLand			= (1 << 11), -	fDontupdate		= (1 << 12), -	fFixLoop		= (1 << 13), -	fDidntMove		= (1 << 14), -	fAdjEgoXY		= (1 << 15) +	fDrawn 			= (1 << 0),		// 0x0001 +	fIgnoreBlocks 	= (1 << 1),		// 0x0002 +	fFixedPriority	= (1 << 2),		// 0x0004 +	fIgnoreHorizon	= (1 << 3),		// 0x0008 +	fUpdate			= (1 << 4),		// 0x0010 +	fCycling		= (1 << 5),		// 0x0020 +	fAnimated		= (1 << 6),		// 0x0040 +	fMotion			= (1 << 7),		// 0x0080 +	fOnWater		= (1 << 8),		// 0x0100 +	fIgnoreObjects	= (1 << 9),		// 0x0200 +	fUpdatePos		= (1 << 10),	// 0x0400 +	fOnLand			= (1 << 11),	// 0x0800 +	fDontupdate		= (1 << 12),	// 0x1000 +	fFixLoop		= (1 << 13),	// 0x2000 +	fDidntMove		= (1 << 14),	// 0x4000 +	fAdjEgoXY		= (1 << 15)		// 0x8000  };  /** - * AGI view table entry + * AGI screen object table entry   */ -struct VtEntry { +struct ScreenObjEntry { +	int16 objectNr; // 0-255 -> regular screenObjTable, -1 -> addToPic-view  	uint8 stepTime;  	uint8 stepTimeCount; -	uint8 entry;  	int16 xPos;  	int16 yPos; -	uint8 currentView; +	uint8 currentViewNr;  	bool viewReplaced;  	struct AgiView *viewData; -	uint8 currentLoop; -	uint8 numLoops; -	struct ViewLoop *loopData; -	uint8 currentCel; -	uint8 numCels; -	struct ViewCel *celData; -	struct ViewCel *celData2; -	int16 xPos2; -	int16 yPos2; -	void *s; +	uint8 currentLoopNr; +	uint8 loopCount; +	struct AgiViewLoop *loopData; +	uint8 currentCelNr; +	uint8 celCount; +	struct AgiViewCel *celData; +	//int16 xPos2; +	//int16 yPos2;  	int16 xSize;  	int16 ySize; + +	int16 xPos_prev; +	int16 yPos_prev; +	int16 xSize_prev; +	int16 ySize_prev; +  	uint8 stepSize;  	uint8 cycleTime;  	uint8 cycleTimeCount;  	uint8 direction; -	MotionType motion; +	MotionType motionType;  	CycleType cycle;  	uint8 priority;  	uint16 flags; -	uint8 parm1; -	uint8 parm2; -	uint8 parm3; -	uint8 parm4; +	// kMotionMoveObj +	int16 move_x; +	int16 move_y; +	uint8 move_stepSize; +	uint8 move_flag; +	// kMotionFollowEgo +	uint8 follow_stepSize; +	uint8 follow_flag; +	uint8 follow_count; +	// kMotionWander +	uint8 wander_count; +	// end of motion related variables +	uint8 loop_flag;  }; // struct vt_entry  } // End of namespace Agi diff --git a/engines/agi/words.cpp b/engines/agi/words.cpp index 438c1ce354..7eca2b82c8 100644 --- a/engines/agi/words.cpp +++ b/engines/agi/words.cpp @@ -21,22 +21,23 @@   */  #include "agi/agi.h" +#include "agi/words.h"  #include "common/textconsole.h"  namespace Agi { -// -// Local implementation to avoid problems with strndup() used by -// gcc 3.2 Cygwin (see #635984) -// -static char *myStrndup(const char *src, int n) { -	char *tmp = strncpy((char *)malloc(n + 1), src, n); -	tmp[n] = 0; -	return tmp; +Words::Words(AgiEngine *vm) { +	_vm = vm; + +	clearEgoWords(); +} + +Words::~Words() { +	clearEgoWords();  } -int AgiEngine::loadWords_v1(Common::File &f) { +int Words::loadDictionary_v1(Common::File &f) {  	char str[64];  	int k; @@ -55,18 +56,21 @@ int AgiEngine::loadWords_v1(Common::File &f) {  		// And store it in our internal dictionary  		if (k > 0) { -			AgiWord *w = new AgiWord; -			w->word = myStrndup(str, k + 1); -			w->id = f.readUint16LE(); -			_game.words[str[0] - 'a'].push_back(w); -			debug(3, "'%s' (%d)", w->word, w->id); +			WordEntry *newWord = new WordEntry; +			byte firstCharNr = str[0] - 'a'; + +			newWord->word = Common::String(str, k + 1); // myStrndup(str, k + 1); +			newWord->id = f.readUint16LE(); +			 +			_dictionaryWords[firstCharNr].push_back(newWord); +			debug(3, "'%s' (%d)", newWord->word.c_str(), newWord->id);  		}  	} while((uint8)str[0] != 0xFF);  	return errOK;  } -int AgiEngine::loadWords(const char *fname) { +int Words::loadDictionary(const char *fname) {  	Common::File fp;  	if (!fp.open(fname)) { @@ -99,10 +103,10 @@ int AgiEngine::loadWords(const char *fname) {  			// See bug #3615061  			if (str[0] == 'a' + i) {  				// And store it in our internal dictionary -				AgiWord *w = new AgiWord; -				w->word = myStrndup(str, k); -				w->id = fp.readUint16BE(); -				_game.words[i].push_back(w); +				WordEntry *newWord = new WordEntry; +				newWord->word = Common::String(str, k); +				newWord->id = fp.readUint16BE(); +				_dictionaryWords[i].push_back(newWord);  			}  			k = fp.readByte(); @@ -119,115 +123,249 @@ int AgiEngine::loadWords(const char *fname) {  	return errOK;  } -void AgiEngine::unloadWords() { -	for (int i = 0; i < 26; i++) -		_game.words[i].clear(); -} +void Words::unloadDictionary() { +	for (int16 firstCharNr = 0; firstCharNr < 26; firstCharNr++) { +		Common::Array<WordEntry *> &dictionary = _dictionaryWords[firstCharNr]; +		int16 dictionarySize = dictionary.size(); -/** - * Find a word in the dictionary - * Uses an algorithm hopefully like the one Sierra used. Returns the ID - * of the word and the length in flen. Returns -1 if not found. - */ -int AgiEngine::findWord(const char *word, int *flen) { -	int c; -	int result = -1; - -	debugC(2, kDebugLevelScripts, "find_word(%s)", word); - -	if (word[0] >= 'a' && word[0] <= 'z') -		c = word[0] - 'a'; -	else -		return -1; - -	*flen = 0; -	Common::Array<AgiWord *> &a = _game.words[c]; - -	for (int i = 0; i < (int)a.size(); i++) { -		int wlen = strlen(a[i]->word); -		// Keep looking till we find the word itself, or the whole phrase. -		// Try to find the best match (i.e. the longest matching phrase). -		if (!strncmp(a[i]->word, word, wlen) && (word[wlen] == 0 || word[wlen] == 0x20) && wlen >= *flen) { -			*flen = wlen; -			result = a[i]->id; +		for (int16 dictionaryWordNr = 0; dictionaryWordNr < dictionarySize; dictionaryWordNr++) { +			delete dictionary[dictionaryWordNr];  		} -	} -	return result; +		_dictionaryWords[firstCharNr].clear(); +	}  } -void AgiEngine::dictionaryWords(char *msg) { -	char *p = NULL; -	char *q = NULL; -	int wid, wlen; - -	assert(msg); - -	debugC(2, kDebugLevelScripts, "msg = \"%s\"", msg); +void Words::clearEgoWords() { +	for (int16 wordNr = 0; wordNr < MAX_WORDS; wordNr++) { +		_egoWords[wordNr].id = 0; +		_egoWords[wordNr].word.clear(); +	} +	_egoWordCount = 0; +} -	cleanInput(); -	for (p = msg; *p && getvar(vWordNotFound) == 0;) { -		if (*p == 0x20) -			p++; +static bool isCharSeparator(const char curChar) { +	switch (curChar) { +	case ' ': +	case ',': +	case '.': +	case '?': +	case '!': +	case '(': +	case ')': +	case ';': +	case ':': +	case '[': +	case ']': +	case '{': +	case '}': +		return true; +		break; +	default: +		break; +	} +	return false; +} -		if (*p == 0) -			break; +static bool isCharInvalid(const char curChar) { +	switch (curChar) { +	case 0x27: // ' +	case 0x60: // ` +	case '-': +	case '\\': +	case '"': +		return true; +		break; +	default: +		break; +	} +	return false; +} -		wid = findWord(p, &wlen); -		debugC(2, kDebugLevelScripts, "find_word(p) == %d", wid); +void Words::cleanUpInput(const char *rawUserInput, Common::String &cleanInput) { +	byte curChar = 0; -		switch (wid) { -		case -1: -			debugC(2, kDebugLevelScripts, "unknown word"); -			_game.egoWords[_game.numEgoWords].word = strdup(p); +	cleanInput.clear(); -			q = _game.egoWords[_game.numEgoWords].word; +	curChar = *rawUserInput; +	while (curChar) { +		// skip separators / invalid characters +		if (isCharSeparator(curChar) || isCharInvalid(curChar)) { +			rawUserInput++; +			curChar = *rawUserInput; +		} else { +			do { +				if (!isCharInvalid(curChar)) { +					// not invalid char, add it to the cleaned up input +					cleanInput += curChar; +				} + +				rawUserInput++; +				curChar = *rawUserInput; + +				if (isCharSeparator(curChar)) { +					cleanInput += ' '; +					break; +				} +			} while (curChar); +		} +	} +	if (cleanInput.hasSuffix(" ")) { +		// ends with a space? remove it +		cleanInput.deleteLastChar(); +	} -			_game.egoWords[_game.numEgoWords].id = 19999; -			setvar(vWordNotFound, 1 + _game.numEgoWords); +	// Sierra compared independent of upper case and lower case +	cleanInput.toLowercase(); +} -			_game.numEgoWords++; +int16 Words::findWordInDictionary(const Common::String &userInput, uint16 userInputLen, uint16 userInputPos, uint16 &foundWordLen) { +	uint16 userInputLeft = userInputLen - userInputPos; +	uint16 wordStartPos = userInputPos; +	int16 wordId = DICTIONARY_RESULT_UNKNOWN; +	byte  firstChar = userInput[userInputPos]; +	byte  curUserInputChar = 0; + +	foundWordLen = 0; + +	if ((firstChar >= 'a') && (firstChar <= 'z')) { +		// word has to start with a letter +		if (((userInputPos + 1) == userInputLen) || (userInput[1] == ' ')) { +			// current word is 1 char only? +			if ((firstChar == 'a') || (firstChar == 'i')) { +				// and it's "a" or "i"? -> then set current type to ignore +				wordId = DICTIONARY_RESULT_IGNORE; +			} +		} -			p += strlen(p); -			break; -		case 0: -			// ignore this word -			debugC(2, kDebugLevelScripts, "ignore word"); -			p += wlen; -			q = NULL; -			break; -		default: -			// an OK word -			debugC(3, kDebugLevelScripts, "ok word (%d)", wid); -			_game.egoWords[_game.numEgoWords].id = wid; -			_game.egoWords[_game.numEgoWords].word = myStrndup(p, wlen); -			_game.numEgoWords++; -			p += wlen; -			break; +		Common::Array<WordEntry *> &dictionary = _dictionaryWords[firstChar - 'a']; +		int16 dictionarySize = dictionary.size(); + +		for (int16 dictionaryWordNr = 0; dictionaryWordNr < dictionarySize; dictionaryWordNr++) { +			WordEntry *dictionaryEntry = dictionary[dictionaryWordNr]; +			uint16 dictionaryWordLen = dictionaryEntry->word.size(); + +			if (dictionaryWordLen <= userInputLeft) { +				// dictionary word is longer or same length as the remaining user input +				uint16 curCompareLeft = dictionaryWordLen; +				uint16 dictionaryWordPos = 0; +				byte   curDictionaryChar = 0; + +				userInputPos = wordStartPos; +				while (curCompareLeft) { +					curUserInputChar = userInput[userInputPos]; +					curDictionaryChar = dictionaryEntry->word[dictionaryWordPos]; + +					if (curUserInputChar != curDictionaryChar) +						break; + +					userInputPos++; +					dictionaryWordPos++; +					curCompareLeft--; +				} + +				if (!curCompareLeft) { +					// fully matched, remember match +					wordId = dictionaryEntry->id; +					foundWordLen = dictionaryWordLen; + +					// follow-up character in user-input is a space? add that to the word length +					if (userInputLeft == foundWordLen) { +						// perfect match -> break +						break; +					} +				} +			}  		} +	} -		if (*p) { -			debugC(2, kDebugLevelScripts, "p = %s", p); -			*p = 0; -			p++; +	if (foundWordLen == 0) { +		userInputPos = wordStartPos; +		while (userInputPos < userInputLen) { +			if (userInput[userInputPos] == ' ') { +				break; +			} +			userInputPos++;  		} +		foundWordLen = userInputPos - wordStartPos; +	} +	return wordId; +} -		if (q != NULL) { -			for (; (*q != 0 && *q != 0x20); q++) -				; -			if (*q) { -				*q = 0; -				q++; +void Words::parseUsingDictionary(char *rawUserInput) { +	Common::String userInput; +	const char *userInputPtr = nullptr; +	uint16 userInputLen; +	uint16 userInputPos = 0; +	uint16 foundWordPos; +	int16  foundWordId; +	uint16 foundWordLen = 0; +	uint16 wordCount = 0; + +	assert(rawUserInput); +	debugC(2, kDebugLevelScripts, "parse: userinput = \"%s\"", rawUserInput); + +	// Reset result +	clearEgoWords(); + +	// clean up user input +	cleanUpInput(rawUserInput, userInput); + +	userInputLen = userInput.size(); +	userInputPtr = userInput.c_str(); + +	while (userInputPos < userInputLen) { +		// Skip trailing space +		if (userInput[userInputPos] == ' ') +			userInputPos++; + +		foundWordPos = userInputPos; +		foundWordId = findWordInDictionary(userInput, userInputLen, userInputPos, foundWordLen); + +		if (foundWordId != DICTIONARY_RESULT_IGNORE) { +			// word not supposed to get ignored +			// add it now +			if (foundWordId != DICTIONARY_RESULT_UNKNOWN) { +				// known word +				_egoWords[wordCount].id = foundWordId; +			} + +			_egoWords[wordCount].word = Common::String(userInputPtr + foundWordPos, foundWordLen); +			debugC(2, kDebugLevelScripts, "found word %s (id %d)", _egoWords[wordCount].word.c_str(), _egoWords[wordCount].id); +			wordCount++; + +			if (foundWordId == DICTIONARY_RESULT_UNKNOWN) { +				// unknown word +				_vm->setVar(VM_VAR_WORD_NOT_FOUND, wordCount); +				break; // and exit now  			}  		} + +		userInputPos += foundWordLen;  	} -	debugC(4, kDebugLevelScripts, "num_ego_words = %d", _game.numEgoWords); -	if (_game.numEgoWords > 0) { -		setflag(fEnteredCli, true); -		setflag(fSaidAcceptedInput, false); +	_egoWordCount = wordCount; + +	debugC(4, kDebugLevelScripts, "ego word count = %d", _egoWordCount); +	if (_egoWordCount > 0) { +		_vm->setflag(VM_FLAG_ENTERED_CLI, true); +	} else { +		_vm->setflag(VM_FLAG_ENTERED_CLI, false);  	} +	_vm->setflag(VM_FLAG_SAID_ACCEPTED_INPUT, false); +} + +uint16 Words::getEgoWordCount() { +	return _egoWordCount; +} +const char *Words::getEgoWord(int16 wordNr) { +	assert(wordNr >= 0 && wordNr < MAX_WORDS); +	return _egoWords[wordNr].word.c_str(); +} +uint16 Words::getEgoWordId(int16 wordNr) { +	assert(wordNr >= 0 && wordNr < MAX_WORDS); +	return _egoWords[wordNr].id;  }  } // End of namespace Agi diff --git a/engines/agi/words.h b/engines/agi/words.h new file mode 100644 index 0000000000..c7bf4829c3 --- /dev/null +++ b/engines/agi/words.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. + * + */ + +#ifndef AGI_WORDS_H +#define AGI_WORDS_H + +namespace Agi { + +#define DICTIONARY_RESULT_UNKNOWN -1 +#define DICTIONARY_RESULT_IGNORE   0 + +struct WordEntry { +	uint16 id; +	Common::String word; +}; + +class Words { +public: +	Words(AgiEngine *vm); +	~Words(); + +private: +	AgiEngine *_vm; + +	// Dictionary +	Common::Array<WordEntry *> _dictionaryWords[26]; + +	WordEntry _egoWords[MAX_WORDS]; +	uint16  _egoWordCount; + +public: +	uint16 getEgoWordCount(); +	const char *getEgoWord(int16 wordNr); +	uint16 getEgoWordId(int16 wordNr); + +	int  loadDictionary_v1(Common::File &f); +	int  loadDictionary(const char *fname); +	void unloadDictionary(); + +	void clearEgoWords(); +	void parseUsingDictionary(char *rawUserInput); + +private: +	void  cleanUpInput(const char *userInput, Common::String &cleanInput); +	int16 findWordInDictionary(const Common::String &userInput, uint16 userInputLen, uint16 userInputPos, uint16 &foundWordLen); +}; + +} // End of namespace Agi + +#endif /* AGI_WORDS_H */  | 
