[Scummvm-git-logs] scummvm master -> d5568906739da23154cacd17aca93802901a9baf

LubomirR lubomirr at lubomirr.eu
Sun Oct 28 13:09:35 CET 2018


This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
d556890673 MUTATIONOFJB: Add support for combining items.


Commit: d5568906739da23154cacd17aca93802901a9baf
    https://github.com/scummvm/scummvm/commit/d5568906739da23154cacd17aca93802901a9baf
Author: Ľubomír Remák (lubomirr at lubomirr.eu)
Date: 2018-10-28T13:08:41+01:00

Commit Message:
MUTATIONOFJB: Add support for combining items.

Introduce game screen and game widget.
Add status bar to show currently hovered and picked items.
Load hardcoded strings from game executable.

Changed paths:
  A engines/mutationofjb/hardcodedstrings.cpp
  A engines/mutationofjb/hardcodedstrings.h
  A engines/mutationofjb/widgets/gamewidget.cpp
  A engines/mutationofjb/widgets/gamewidget.h
  A engines/mutationofjb/widgets/labelwidget.cpp
  A engines/mutationofjb/widgets/labelwidget.h
    engines/mutationofjb/assets.cpp
    engines/mutationofjb/assets.h
    engines/mutationofjb/detection.cpp
    engines/mutationofjb/game.cpp
    engines/mutationofjb/game.h
    engines/mutationofjb/gamedata.cpp
    engines/mutationofjb/gamedata.h
    engines/mutationofjb/gamescreen.cpp
    engines/mutationofjb/gamescreen.h
    engines/mutationofjb/guiscreen.cpp
    engines/mutationofjb/guiscreen.h
    engines/mutationofjb/module.mk
    engines/mutationofjb/mutationofjb.cpp
    engines/mutationofjb/mutationofjb.h
    engines/mutationofjb/tasks/conversationtask.cpp
    engines/mutationofjb/widgets/widget.cpp
    engines/mutationofjb/widgets/widget.h


diff --git a/engines/mutationofjb/assets.cpp b/engines/mutationofjb/assets.cpp
index 6f32361..02e0c85 100644
--- a/engines/mutationofjb/assets.cpp
+++ b/engines/mutationofjb/assets.cpp
@@ -24,7 +24,7 @@
 
 namespace MutationOfJB {
 
-Assets::Assets(Game &game) : _game(game), _toSayList("tosay.ger"), _responseList("response.ger") {}
+Assets::Assets(Game &game) : _game(game), _toSayList("tosay.ger"), _responseList("response.ger"), _hardcodedStrings(game) {}
 
 Font &Assets::getSystemFont() {
 	return _systemFont;
@@ -46,4 +46,8 @@ InventoryItemDefinitionList &Assets::getInventoryItemDefList() {
 	return _invItemDefList;
 }
 
+HardcodedStrings &Assets::getHardcodedStrings() {
+	return _hardcodedStrings;
+}
+
 }
diff --git a/engines/mutationofjb/assets.h b/engines/mutationofjb/assets.h
index 89ed678..6d9858e 100644
--- a/engines/mutationofjb/assets.h
+++ b/engines/mutationofjb/assets.h
@@ -26,6 +26,7 @@
 #include "mutationofjb/font.h"
 #include "mutationofjb/conversationlinelist.h"
 #include "mutationofjb/inventoryitemdefinitionlist.h"
+#include "mutationofjb/hardcodedstrings.h"
 
 namespace MutationOfJB {
 
@@ -38,10 +39,33 @@ public:
 	Font &getSystemFont();
 	Font &getSpeechFont();
 
+	/**
+	 * Access to "to say" list for conversations.
+	 *
+	 * @return Conversation line list.
+	 */
 	ConversationLineList &getToSayList();
+
+	/**
+	 * Access to "response" list for conversations.
+	 *
+	 * @return Conversation line list.
+	 */
 	ConversationLineList &getResponseList();
+
+	/**
+	 * Access to inventory definitions.
+	 *
+	 * @return Inventory item definiton list.
+	 */
 	InventoryItemDefinitionList &getInventoryItemDefList();
 
+	/**
+	 * Access to strings hardcoded in game executable.
+	 *
+	 * @return Hardcoded strings.
+	 */
+	HardcodedStrings &getHardcodedStrings();
 private:
 	Game &_game;
 	SystemFont _systemFont;
@@ -49,6 +73,7 @@ private:
 	ConversationLineList _toSayList;
 	ConversationLineList _responseList;
 	InventoryItemDefinitionList _invItemDefList;
+	HardcodedStrings _hardcodedStrings;
 };
 
 }
diff --git a/engines/mutationofjb/detection.cpp b/engines/mutationofjb/detection.cpp
index 0235f80..6ebfa13 100644
--- a/engines/mutationofjb/detection.cpp
+++ b/engines/mutationofjb/detection.cpp
@@ -102,7 +102,7 @@ public:
 
 	virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const override {
 		if (desc) {
-			*engine = new MutationOfJB::MutationOfJBEngine(syst);
+			*engine = new MutationOfJB::MutationOfJBEngine(syst, desc);
 		}
 		return desc != nullptr;
 	}
diff --git a/engines/mutationofjb/game.cpp b/engines/mutationofjb/game.cpp
index 86755b1..134dc72 100644
--- a/engines/mutationofjb/game.cpp
+++ b/engines/mutationofjb/game.cpp
@@ -34,6 +34,8 @@
 #include "common/str.h"
 #include "common/util.h"
 
+#include "engines/advancedDetector.h"
+
 namespace MutationOfJB {
 
 Game::Game(MutationOfJBEngine *vm)
@@ -41,7 +43,6 @@ Game::Game(MutationOfJBEngine *vm)
 	  _randomSource("mutationofjb"),
 	  _delayedLocalScript(nullptr),
 	  _gui(*this, _vm->getScreen()),
-	  _currentAction(ActionInfo::Walk),
 	  _scriptExecCtx(*this),
 	  _taskManager(*this),
 	  _assets(*this) {
@@ -63,6 +64,10 @@ Game::Game(MutationOfJBEngine *vm)
 	_taskManager.startTask(TaskPtr(new ObjectAnimationTask));
 }
 
+MutationOfJBEngine &Game::getEngine() {
+	return *_vm;
+}
+
 Common::RandomSource &Game::getRandomSource() {
 	return _randomSource;
 }
@@ -109,7 +114,7 @@ Script *Game::changeSceneLoadScript(uint8 sceneId, bool partB) {
 	_gameData->_partB = partB;
 
 	_room->load(_gameData->_currentScene, partB);
-	_room->redraw();
+	_gui.refreshAfterSceneChanged();
 
 	EncryptedFile scriptFile;
 	Common::String fileName = Common::String::format("scrn%d%s.atn", sceneId, partB ? "b" : "");
@@ -128,8 +133,6 @@ Script *Game::changeSceneLoadScript(uint8 sceneId, bool partB) {
 	localScript->loadFromStream(scriptFile);
 	scriptFile.close();
 
-	_vm->updateCursor();
-
 	return localScript;
 }
 
@@ -190,7 +193,6 @@ void Game::update() {
 		_delayedLocalScript = nullptr;
 	}
 
-	_gui.update();
 	_taskManager.update();
 }
 
@@ -198,14 +200,6 @@ GameScreen &Game::getGameScreen() {
 	return _gui;
 }
 
-ActionInfo::Action Game::getCurrentAction() const {
-	return _currentAction;
-}
-
-void Game::setCurrentAction(ActionInfo::Action action) {
-	_currentAction = action;
-}
-
 uint8 Game::colorFromString(const char *colorStr) {
 	struct {
 		const char *str;
@@ -265,4 +259,8 @@ bool Game::loadSaveAllowed() const {
 	return true;
 }
 
+Common::Language Game::getLanguage() const {
+	return _vm->getGameDescription()->language;
+}
+
 }
diff --git a/engines/mutationofjb/game.h b/engines/mutationofjb/game.h
index adb5bf5..27804ea 100644
--- a/engines/mutationofjb/game.h
+++ b/engines/mutationofjb/game.h
@@ -25,9 +25,9 @@
 
 #include "mutationofjb/assets.h"
 #include "mutationofjb/gamescreen.h"
-#include "mutationofjb/script.h"
 #include "mutationofjb/tasks/taskmanager.h"
 
+#include "common/language.h"
 #include "common/ptr.h"
 #include "common/random.h"
 #include "common/scummsys.h"
@@ -51,6 +51,8 @@ struct Bitmap;
 class Game {
 public:
 	Game(MutationOfJBEngine *vm);
+	MutationOfJBEngine &getEngine();
+
 	Common::RandomSource &getRandomSource();
 	GameData &getGameData();
 	Room &getRoom();
@@ -68,9 +70,6 @@ public:
 
 	GameScreen &getGameScreen();
 
-	ActionInfo::Action getCurrentAction() const;
-	void setCurrentAction(ActionInfo::Action);
-
 	static uint8 colorFromString(const char *colorStr);
 
 	TaskManager &getTaskManager();
@@ -83,6 +82,8 @@ public:
 
 	bool loadSaveAllowed() const;
 
+	Common::Language getLanguage() const;
+
 private:
 	bool loadGameData(bool partB);
 	void runActiveCommand();
@@ -98,7 +99,6 @@ private:
 	Script *_delayedLocalScript;
 	Room *_room;
 	GameScreen _gui;
-	ActionInfo::Action _currentAction;
 
 	ScriptExecutionContext _scriptExecCtx;
 
diff --git a/engines/mutationofjb/gamedata.cpp b/engines/mutationofjb/gamedata.cpp
index 71f58c3..b0d3e7c 100644
--- a/engines/mutationofjb/gamedata.cpp
+++ b/engines/mutationofjb/gamedata.cpp
@@ -150,6 +150,14 @@ void Static::saveLoadWithSerializer(Common::Serializer &sz) {
 	sz.syncAsByte(_walkToFrame);
 }
 
+bool Static::isCombinable() const {
+	const size_t length = strlen(_name);
+	if (length == 0)
+		return false;
+
+	return _name[length - 1] == '[';
+}
+
 bool Bitmap::loadInitialState(Common::ReadStream &stream) {
 	_roomFrame = stream.readByte();
 	_isVisible = stream.readByte();
diff --git a/engines/mutationofjb/gamedata.h b/engines/mutationofjb/gamedata.h
index f42e84b..9daefd8 100644
--- a/engines/mutationofjb/gamedata.h
+++ b/engines/mutationofjb/gamedata.h
@@ -248,6 +248,13 @@ struct Static : public Common::Serializable {
 	 * @param sz Serializer.
 	 */
 	virtual void saveLoadWithSerializer(Common::Serializer &sz) override;
+
+	/**
+	 * Statics with names ending with '[' are allowed to be combined with other items.
+	 *
+	 * @return True if combinable, false otherwise.
+	 */
+	bool isCombinable() const;
 };
 
 /**
diff --git a/engines/mutationofjb/gamescreen.cpp b/engines/mutationofjb/gamescreen.cpp
index 3c007f4..ef7e50b 100644
--- a/engines/mutationofjb/gamescreen.cpp
+++ b/engines/mutationofjb/gamescreen.cpp
@@ -26,13 +26,16 @@
 #include "mutationofjb/encryptedfile.h"
 #include "mutationofjb/game.h"
 #include "mutationofjb/gamedata.h"
+#include "mutationofjb/mutationofjb.h"
 #include "mutationofjb/inventory.h"
 #include "mutationofjb/util.h"
-#include "mutationofjb/widgets/widget.h"
-#include "mutationofjb/widgets/inventorywidget.h"
-#include "mutationofjb/widgets/imagewidget.h"
 #include "mutationofjb/widgets/conversationwidget.h"
+#include "mutationofjb/widgets/gamewidget.h"
+#include "mutationofjb/widgets/imagewidget.h"
+#include "mutationofjb/widgets/inventorywidget.h"
+#include "mutationofjb/widgets/labelwidget.h"
 
+#include "common/events.h"
 #include "common/rect.h"
 
 #include "graphics/screen.h"
@@ -61,14 +64,20 @@ enum {
 	CONVERSATION_X = 0,
 	CONVERSATION_Y = 139,
 	CONVERSATION_WIDTH = 320,
-	CONVERSATION_HEIGHT = 61
+	CONVERSATION_HEIGHT = 61,
+	STATUS_BAR_X = 0,
+	STATUS_BAR_Y = 140,
+	STATUS_BAR_WIDTH = 320,
+	STATUS_BAR_HEIGHT = 8
 };
 
 
 GameScreen::GameScreen(Game &game, Graphics::Screen *screen)
 	: GuiScreen(game, screen),
 	  _inventoryWidget(nullptr),
-	  _conversationWidget(nullptr) {}
+	  _conversationWidget(nullptr),
+	  _statusBarWidget(nullptr),
+	  _currentAction(ActionInfo::Walk) {}
 
 GameScreen::~GameScreen() {}
 
@@ -111,22 +120,105 @@ bool GameScreen::init() {
 		ButtonWidget *button = new ButtonWidget(*this, ButtonRects[i], normalSurface, pressedSurface);
 		button->setId(i);
 		button->setCallback(this);
+		_buttons.push_back(button);
 		addWidget(button);
 	}
 
+	const Common::Rect statusBarRect(STATUS_BAR_X, STATUS_BAR_Y, STATUS_BAR_X + STATUS_BAR_WIDTH, STATUS_BAR_Y + STATUS_BAR_HEIGHT);
+	_statusBarWidget = new LabelWidget(*this, statusBarRect);
+	addWidget(_statusBarWidget);
+
 	const Common::Rect conversationRect(CONVERSATION_X, CONVERSATION_Y, CONVERSATION_X + CONVERSATION_WIDTH, CONVERSATION_Y + CONVERSATION_HEIGHT);
 	const Graphics::Surface conversationSurface = _hudSurfaces[2].getSubArea(conversationRect);
 	_conversationWidget = new ConversationWidget(*this, conversationRect, conversationSurface);
 	_conversationWidget->setVisible(false);
 	addWidget(_conversationWidget);
 
+	_gameWidget = new GameWidget(*this);
+	_gameWidget->setCallback(this);
+	addWidget(_gameWidget);
+
 	return true;
 }
 
+void GameScreen::handleEvent(const Common::Event &event) {
+	switch (event.type) {
+	case Common::EVENT_KEYUP: {
+		switch (event.kbd.ascii) {
+		case 'g':
+			_currentAction = ActionInfo::Walk;
+			break;
+		case 'r':
+			_currentAction = ActionInfo::Talk;
+			break;
+		case 's':
+			_currentAction = ActionInfo::Look;
+			break;
+		case 'b':
+			_currentAction = ActionInfo::Use;
+			break;
+		case 'n':
+			_currentAction = ActionInfo::PickUp;
+			break;
+		}
+		break;
+	}
+	default:
+		break;
+	}
+
+	GuiScreen::handleEvent(event);
+}
+
 ConversationWidget &GameScreen::getConversationWidget() {
 	return *_conversationWidget;
 }
 
+void GameScreen::showConversationWidget(bool show) {
+	_gameWidget->setEnabled(!show);
+	_conversationWidget->setVisible(show);
+	_statusBarWidget->setText(Common::String());
+
+	for (Common::Array<ButtonWidget *>::const_iterator it = _buttons.begin(); it != _buttons.end(); ++it) {
+		(*it)->setVisible(!show);
+	}
+	_inventoryWidget->setVisible(!show);
+}
+
+void GameScreen::refreshAfterSceneChanged() {
+	const Widgets &widgets = getWidgets();
+
+	if (!getGame().isCurrentSceneMap()) {
+		_gameWidget->setArea(Common::Rect(GameWidget::GAME_NORMAL_AREA_WIDTH, GameWidget::GAME_NORMAL_AREA_HEIGHT));
+
+		for (Widgets::const_iterator it = widgets.begin(); it != widgets.end(); ++it) {
+			if (*it == _gameWidget || *it == _conversationWidget)
+				continue;
+
+			(*it)->setVisible(true);
+		}
+	} else {
+		_gameWidget->setArea(Common::Rect(GameWidget::GAME_FULL_AREA_WIDTH, GameWidget::GAME_FULL_AREA_HEIGHT));
+		for (Widgets::const_iterator it = widgets.begin(); it != widgets.end(); ++it) {
+			if (*it == _gameWidget || *it == _conversationWidget)
+				continue;
+
+			(*it)->setVisible(false);
+		}
+	}
+
+	_gameWidget->clearState();
+
+	// Fake mouse move event to update the cursor.
+	Common::Event event;
+	event.type = Common::EVENT_MOUSEMOVE;
+	event.mouse = _game.getEngine().getEventManager()->getMousePos();
+	_gameWidget->handleEvent(event);
+
+	_gameWidget->markDirty();
+	_gameWidget->update(*_screen); // Force immediate update.
+}
+
 class InventoryAnimationDecoderCallback : public AnimationDecoderCallback {
 public:
 	InventoryAnimationDecoderCallback(GameScreen &gui) : _gui(gui) {}
@@ -180,6 +272,56 @@ bool GameScreen::loadHudGfx() {
 	return decoder.decode(&callback);
 }
 
+void GameScreen::updateStatusBarText(const Common::String &entity, bool inventory) {
+	const bool hasPrevPickedItem = !_currentPickedItem.empty();
+	const bool hasCurrentItem = !entity.empty();
+
+	if (!hasPrevPickedItem && !hasCurrentItem) {
+		_statusBarWidget->setText(Common::String());
+		return;
+	}
+
+	HardcodedStrings::StringType actionStringType = HardcodedStrings::LOOK;
+
+	if (inventory) {
+		switch (_currentAction) {
+		case ActionInfo::Use:
+			actionStringType = HardcodedStrings::USE;
+			break;
+		default:
+			actionStringType = HardcodedStrings::LOOK;
+			break;
+		}
+	} else {
+		switch (_currentAction) {
+		case ActionInfo::Look:
+			actionStringType = HardcodedStrings::LOOK;
+			break;
+		case ActionInfo::Walk:
+			actionStringType = HardcodedStrings::WALK;
+			break;
+		case ActionInfo::Talk:
+			actionStringType = HardcodedStrings::TALK;
+			break;
+		case ActionInfo::Use:
+			actionStringType = HardcodedStrings::USE;
+			break;
+		case ActionInfo::PickUp:
+			actionStringType = HardcodedStrings::PICKUP;
+			break;
+		}
+	}
+
+	Common::String text = _game.getAssets().getHardcodedStrings().getString(actionStringType);
+
+	if (hasPrevPickedItem)
+		text += " " + _currentPickedItem;
+	if (hasCurrentItem)
+		text += " " + entity;
+
+	_statusBarWidget->setText(text);
+}
+
 void GameScreen::onInventoryChanged() {
 	_inventoryWidget->markDirty();
 }
@@ -188,7 +330,8 @@ void GameScreen::onButtonClicked(ButtonWidget *button) {
 	const int buttonId = button->getId();
 	if (buttonId <= BUTTON_PICKUP) {
 		const ActionInfo::Action actions[] = {ActionInfo::Walk, ActionInfo::Talk, ActionInfo::Look, ActionInfo::Use, ActionInfo::PickUp};
-		_game.setCurrentAction(actions[buttonId]);
+		_currentAction = actions[buttonId];
+		_currentPickedItem.clear();
 	} else if (buttonId == BUTTON_SCROLL_LEFT) {
 		_game.getGameData().getInventory().scrollLeft();
 	} else if (buttonId == BUTTON_SCROLL_RIGHT) {
@@ -196,19 +339,64 @@ void GameScreen::onButtonClicked(ButtonWidget *button) {
 	}
 }
 
-void GameScreen::onInventoryItemHovered(InventoryWidget *widget, int posInWidget) {
-	// TODO
+void GameScreen::onInventoryItemHovered(InventoryWidget *, int posInWidget) {
+	if (posInWidget == -1) {
+		updateStatusBarText(Common::String(), true);
+	} else {
+		const Common::String &item = _game.getGameData().getInventory().getItems()[posInWidget];
+		updateStatusBarText(item, true);
+	}
 }
 
-void GameScreen::onInventoryItemClicked(InventoryWidget *widget, int posInWidget) {
+void GameScreen::onInventoryItemClicked(InventoryWidget *, int posInWidget) {
 	// Position in widget should match the position in inventory.
-	const Common::String &item = getGame().getGameData().getInventory().getItems()[posInWidget];
-
-	if (_game.getCurrentAction() == ActionInfo::Use) {
-		// TODO
+	const Common::String &item = _game.getGameData().getInventory().getItems()[posInWidget];
+
+	if (_currentAction == ActionInfo::Use) {
+		if (_currentPickedItem.empty()) {
+			// Inventory items ending with '[' aren't supposed to be combined (e.g. Fisher's mask).
+			if (item.lastChar() == '[')
+				_game.startActionSection(ActionInfo::Look, item);
+			else
+				_currentPickedItem = item;
+		} else {
+			_game.startActionSection(ActionInfo::Use, _currentPickedItem, item);
+			_currentPickedItem.clear();
+		}
 	} else {
 		_game.startActionSection(ActionInfo::Look, item);
 	}
 }
 
+void GameScreen::onGameDoorClicked(GameWidget *, const Door *door) {
+	if (!_currentPickedItem.empty()) {
+		_game.startActionSection(_currentAction, _currentPickedItem, door->_name);
+		return;
+	}
+
+	if (!_game.startActionSection(_currentAction, door->_name) && _currentAction == ActionInfo::Walk && door->_destSceneId != 0) {
+		_game.changeScene(door->_destSceneId, _game.getGameData()._partB);
+	}
+}
+
+void GameScreen::onGameStaticClicked(GameWidget *, const Static *stat) {
+	if (_currentAction == ActionInfo::Use) {
+		if (_currentPickedItem.empty()) {
+			if (stat->isCombinable())
+				_currentPickedItem = stat->_name;
+			else
+				_game.startActionSection(ActionInfo::Use, stat->_name);
+		} else {
+			_game.startActionSection(_currentAction, _currentPickedItem, stat->_name);
+			_currentPickedItem.clear();
+		}
+	} else {
+		_game.startActionSection(_currentAction, stat->_name);
+	}
+}
+
+void GameScreen::onGameEntityHovered(GameWidget *, const Common::String &entity) {
+	updateStatusBarText(entity, false);
+}
+
 }
diff --git a/engines/mutationofjb/gamescreen.h b/engines/mutationofjb/gamescreen.h
index bc70580..686a504 100644
--- a/engines/mutationofjb/gamescreen.h
+++ b/engines/mutationofjb/gamescreen.h
@@ -24,9 +24,11 @@
 #define MUTATIONOFJB_GUI_H
 
 #include "mutationofjb/inventory.h"
+#include "mutationofjb/script.h"
+#include "mutationofjb/guiscreen.h"
 #include "mutationofjb/widgets/buttonwidget.h"
 #include "mutationofjb/widgets/inventorywidget.h"
-#include "mutationofjb/guiscreen.h"
+#include "mutationofjb/widgets/gamewidget.h"
 
 #include "common/array.h"
 #include "common/hashmap.h"
@@ -48,8 +50,10 @@ class Game;
 class Widget;
 class InventoryWidget;
 class ConversationWidget;
+class LabelWidget;
+class GameWidget;
 
-class GameScreen : public GuiScreen, public InventoryObserver, public ButtonWidgetCallback, public InventoryWidgetCallback {
+class GameScreen : public GuiScreen, public InventoryObserver, public ButtonWidgetCallback, public InventoryWidgetCallback, public GameWidgetCallback {
 public:
 	friend class InventoryAnimationDecoderCallback;
 	friend class HudAnimationDecoderCallback;
@@ -59,24 +63,40 @@ public:
 
 	bool init();
 
+	virtual void handleEvent(const Common::Event &event) override;
+
 	virtual void onInventoryChanged() override;
 	virtual void onButtonClicked(ButtonWidget *) override;
 	virtual void onInventoryItemHovered(InventoryWidget *widget, int posInWidget) override;
 	virtual void onInventoryItemClicked(InventoryWidget *widget, int posInWidget) override;
+	virtual void onGameDoorClicked(GameWidget *, const Door *door) override;
+	virtual void onGameStaticClicked(GameWidget *, const Static *stat) override;
+	virtual void onGameEntityHovered(GameWidget *, const Common::String &entity) override;
 
 	ConversationWidget &getConversationWidget();
 
+	void showConversationWidget(bool show);
+	void refreshAfterSceneChanged();
+
 private:
 	bool loadInventoryGfx();
 	bool loadHudGfx();
 	void drawInventoryItem(const Common::String &item, int pos);
 	void drawInventory();
 
+	void updateStatusBarText(const Common::String &entity, bool inventory);
+
 	Common::Array<Graphics::Surface> _inventorySurfaces;
 	Common::Array<Graphics::Surface> _hudSurfaces;
 
+	Common::Array<ButtonWidget *> _buttons;
 	InventoryWidget *_inventoryWidget;
 	ConversationWidget *_conversationWidget;
+	LabelWidget *_statusBarWidget;
+	GameWidget *_gameWidget;
+
+	ActionInfo::Action _currentAction;
+	Common::String _currentPickedItem;
 };
 
 }
diff --git a/engines/mutationofjb/guiscreen.cpp b/engines/mutationofjb/guiscreen.cpp
index 5ad545d..891bcc2 100644
--- a/engines/mutationofjb/guiscreen.cpp
+++ b/engines/mutationofjb/guiscreen.cpp
@@ -68,6 +68,11 @@ void GuiScreen::update() {
 
 void GuiScreen::addWidget(Widget *widget) {
 	_widgets.push_back(widget);
+	widget->markDirty();
+}
+
+const GuiScreen::Widgets &GuiScreen::getWidgets() const {
+	return _widgets;
 }
 
 }
diff --git a/engines/mutationofjb/guiscreen.h b/engines/mutationofjb/guiscreen.h
index c1761be..3b1e8d4 100644
--- a/engines/mutationofjb/guiscreen.h
+++ b/engines/mutationofjb/guiscreen.h
@@ -60,7 +60,7 @@ public:
 	 *
 	 * @param event ScummVM event.
 	 */
-	void handleEvent(const Common::Event &event);
+	virtual void handleEvent(const Common::Event &event);
 
 	/**
 	 * Updates all visible widgets.
@@ -76,11 +76,15 @@ public:
 	void addWidget(Widget *widget);
 
 protected:
+	typedef Common::Array<Widget *> Widgets;
+
 	Game &_game;
 	Graphics::Screen *_screen;
 
+	const Widgets &getWidgets() const;
+
 private:
-	Common::Array<Widget *> _widgets;
+	Widgets _widgets;
 };
 
 }
diff --git a/engines/mutationofjb/hardcodedstrings.cpp b/engines/mutationofjb/hardcodedstrings.cpp
new file mode 100644
index 0000000..5635c36
--- /dev/null
+++ b/engines/mutationofjb/hardcodedstrings.cpp
@@ -0,0 +1,169 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "mutationofjb/hardcodedstrings.h"
+
+#include "mutationofjb/game.h"
+#include "mutationofjb/util.h"
+
+#include "common/file.h"
+
+namespace MutationOfJB {
+
+HardcodedStrings::HardcodedStrings(Game &game) : _strings(STRING_TYPES_TOTAL) {
+	loadStrings(game.getLanguage());
+}
+
+const Common::String &HardcodedStrings::getString(HardcodedStrings::StringType strType) const {
+	const StringArray::size_type index = static_cast<StringArray::size_type>(strType);
+	assert(index < _strings.size());
+
+	return _strings[index];
+}
+
+void HardcodedStrings::loadStrings(Common::Language lang) {
+	Common::File file;
+	const char *const fileName = "jb.ex_";
+	if (!file.open(fileName)) {
+		reportFileMissingError(fileName);
+		return;
+	}
+
+	if (lang == Common::SK_SVK)
+		file.seek(0xBAA8);
+	else if (lang == Common::DE_DEU)
+		file.seek(0xBC48);
+	else
+		return;
+
+	Common::String str;
+
+	file.readPascalString(); // WALK TO
+	file.readPascalString(); // TALK TO
+	file.readPascalString(); // PICK UP
+	file.readPascalString(); // LOOK AT
+
+	str = file.readPascalString();
+	if (lang == Common::SK_SVK)
+		_strings[WALK] = str;
+
+	str = file.readPascalString();
+	if (lang == Common::DE_DEU)
+		_strings[WALK] = str;
+
+	str = file.readPascalString();
+	if (lang == Common::SK_SVK)
+		_strings[LOOK] = str;
+
+	file.readPascalString();
+	file.readPascalString();
+
+	str = file.readPascalString();
+	if (lang == Common::DE_DEU)
+		_strings[LOOK] = str;
+
+	str = file.readPascalString();
+	if (lang == Common::SK_SVK)
+		_strings[PICKUP] = str;
+
+	str = file.readPascalString();
+	if (lang == Common::DE_DEU)
+		_strings[PICKUP] = str;
+
+	str = file.readPascalString();
+	if (lang == Common::SK_SVK)
+		_strings[TALK] = str;
+
+	str = file.readPascalString();
+	if (lang == Common::DE_DEU)
+		_strings[TALK] = str;
+
+	file.readPascalString(); // USE
+
+	str = file.readPascalString();
+	if (lang == Common::SK_SVK)
+		_strings[USE] = str;
+
+	str = file.readPascalString();
+	if (lang == Common::DE_DEU)
+		_strings[USE] = str;
+
+
+	if (lang == Common::SK_SVK)
+		file.seek(0x1982F);
+	else if (lang == Common::DE_DEU)
+		file.seek(0x199F0);
+	else
+		return;
+
+	_strings[JOHNNY_CANNOT_USE_1] = file.readPascalString();
+	_strings[SKEPTO_CANNOT_USE_1] = file.readPascalString();
+	file.readPascalString();
+	_strings[JOHNNY_CANNOT_USE_2] = file.readPascalString();
+	_strings[SKEPTO_CANNOT_USE_2] = file.readPascalString();
+	file.readPascalString();
+	_strings[JOHNNY_CANNOT_USE_3] = file.readPascalString();
+	_strings[SKEPTO_CANNOT_USE_3] = file.readPascalString();
+	file.readPascalString();
+	_strings[JOHNNY_CANNOT_USE_4] = file.readPascalString();
+	_strings[SKEPTO_CANNOT_USE_4] = file.readPascalString();
+	file.readPascalString();
+	_strings[JOHNNY_CANNOT_TALK_1] = file.readPascalString();
+	_strings[SKEPTO_CANNOT_TALK_1] = file.readPascalString();
+	file.readPascalString();
+	_strings[JOHNNY_CANNOT_TALK_2] = file.readPascalString();
+	_strings[SKEPTO_CANNOT_TALK_2] = file.readPascalString();
+	file.readPascalString();
+	_strings[JOHNNY_CANNOT_TALK_3] = file.readPascalString();
+	_strings[SKEPTO_CANNOT_TALK_3] = file.readPascalString();
+	file.readPascalString();
+	_strings[JOHNNY_CANNOT_TALK_4] = file.readPascalString();
+	_strings[SKEPTO_CANNOT_TALK_4] = file.readPascalString();
+	file.readPascalString();
+	_strings[JOHNNY_CANNOT_LOOK_1] = file.readPascalString();
+	_strings[SKEPTO_CANNOT_LOOK_1] = file.readPascalString();
+	file.readPascalString();
+	_strings[JOHNNY_CANNOT_LOOK_2] = file.readPascalString();
+	_strings[SKEPTO_CANNOT_LOOK_2] = file.readPascalString();
+	file.readPascalString();
+	if (lang != Common::SK_SVK) // This sentence seems to be missing from the Slovak executable.
+		_strings[JOHNNY_CANNOT_LOOK_3] = file.readPascalString();
+	_strings[SKEPTO_CANNOT_LOOK_3] = file.readPascalString();
+	file.readPascalString();
+	_strings[JOHNNY_CANNOT_LOOK_4] = file.readPascalString();
+	_strings[SKEPTO_CANNOT_LOOK_4] = file.readPascalString();
+	file.readPascalString();
+	_strings[JOHNNY_CANNOT_PICKUP_1] = file.readPascalString();
+	_strings[SKEPTO_CANNOT_PICKUP_1] = file.readPascalString();
+	file.readPascalString();
+	if (lang != Common::SK_SVK) // This sentence seems to be missing from the Slovak executable.
+		_strings[JOHNNY_CANNOT_PICKUP_2] = file.readPascalString();
+	_strings[SKEPTO_CANNOT_PICKUP_2] = file.readPascalString();
+	file.readPascalString();
+	_strings[JOHNNY_CANNOT_PICKUP_3] = file.readPascalString();
+	_strings[SKEPTO_CANNOT_PICKUP_3] = file.readPascalString();
+	file.readPascalString();
+	_strings[JOHNNY_CANNOT_PICKUP_4] = file.readPascalString();
+	_strings[SKEPTO_CANNOT_PICKUP_4] = file.readPascalString();
+}
+
+}
diff --git a/engines/mutationofjb/hardcodedstrings.h b/engines/mutationofjb/hardcodedstrings.h
new file mode 100644
index 0000000..3ee0103
--- /dev/null
+++ b/engines/mutationofjb/hardcodedstrings.h
@@ -0,0 +1,107 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef MUTATIONOFJB_HARDCODEDSTRINGS_H
+#define MUTATIONOFJB_HARDCODEDSTRINGS_H
+
+#include "common/language.h"
+#include "common/hashmap.h"
+
+namespace MutationOfJB {
+
+class Game;
+
+/**
+ * Provides access to hardcoded strings.
+ *
+ * Currently, we do not have any agreement with the original author of the game,
+ * so the strings are loaded from the gmae executable file.
+ */
+class HardcodedStrings {
+public:
+	enum StringType {
+		WALK,
+		TALK,
+		LOOK,
+		USE,
+		PICKUP,
+
+		JOHNNY_CANNOT_USE_1,
+		SKEPTO_CANNOT_USE_1,
+		JOHNNY_CANNOT_USE_2,
+		SKEPTO_CANNOT_USE_2,
+		JOHNNY_CANNOT_USE_3,
+		SKEPTO_CANNOT_USE_3,
+		JOHNNY_CANNOT_USE_4,
+		SKEPTO_CANNOT_USE_4,
+
+		JOHNNY_CANNOT_TALK_1,
+		SKEPTO_CANNOT_TALK_1,
+		JOHNNY_CANNOT_TALK_2,
+		SKEPTO_CANNOT_TALK_2,
+		JOHNNY_CANNOT_TALK_3,
+		SKEPTO_CANNOT_TALK_3,
+		JOHNNY_CANNOT_TALK_4,
+		SKEPTO_CANNOT_TALK_4,
+
+		JOHNNY_CANNOT_LOOK_1,
+		SKEPTO_CANNOT_LOOK_1,
+		JOHNNY_CANNOT_LOOK_2,
+		SKEPTO_CANNOT_LOOK_2,
+		JOHNNY_CANNOT_LOOK_3,
+		SKEPTO_CANNOT_LOOK_3,
+		JOHNNY_CANNOT_LOOK_4,
+		SKEPTO_CANNOT_LOOK_4,
+
+		JOHNNY_CANNOT_PICKUP_1,
+		SKEPTO_CANNOT_PICKUP_1,
+		JOHNNY_CANNOT_PICKUP_2,
+		SKEPTO_CANNOT_PICKUP_2,
+		JOHNNY_CANNOT_PICKUP_3,
+		SKEPTO_CANNOT_PICKUP_3,
+		JOHNNY_CANNOT_PICKUP_4,
+		SKEPTO_CANNOT_PICKUP_4,
+
+		STRING_TYPES_TOTAL
+	};
+
+	HardcodedStrings(Game &game);
+
+	/**
+	 * Get hardcoded string.
+	 *
+	 * @param strType String type.
+	 * @return Hardcoded string.
+	 */
+	const Common::String &getString(StringType strType) const;
+
+private:
+	typedef Common::Array<Common::String> StringArray;
+
+	void loadStrings(Common::Language lang);
+
+	StringArray _strings;
+};
+
+}
+
+#endif
diff --git a/engines/mutationofjb/module.mk b/engines/mutationofjb/module.mk
index c7de62b..5918536 100644
--- a/engines/mutationofjb/module.mk
+++ b/engines/mutationofjb/module.mk
@@ -30,8 +30,10 @@ MODULE_OBJS := \
 	tasks/taskmanager.o \
 	widgets/buttonwidget.o \
 	widgets/conversationwidget.o \
+	widgets/gamewidget.o \
 	widgets/imagewidget.o \
 	widgets/inventorywidget.o \
+	widgets/labelwidget.o \
 	widgets/widget.o \
 	animationdecoder.o \
 	assets.o \
@@ -44,6 +46,7 @@ MODULE_OBJS := \
 	gamedata.o \
 	gamescreen.o \
 	guiscreen.o \
+	hardcodedstrings.o \
 	inventory.o \
 	inventoryitemdefinitionlist.o \
 	mutationofjb.o \
diff --git a/engines/mutationofjb/mutationofjb.cpp b/engines/mutationofjb/mutationofjb.cpp
index ed4608b..ea99b32 100644
--- a/engines/mutationofjb/mutationofjb.cpp
+++ b/engines/mutationofjb/mutationofjb.cpp
@@ -37,18 +37,21 @@
 #include "mutationofjb/mutationofjb.h"
 #include "mutationofjb/game.h"
 #include "mutationofjb/gamedata.h"
+#include "mutationofjb/gamescreen.h"
 #include "mutationofjb/debug.h"
 #include "mutationofjb/room.h"
 
 namespace MutationOfJB {
 
-MutationOfJBEngine::MutationOfJBEngine(OSystem *syst)
+MutationOfJBEngine::MutationOfJBEngine(OSystem *syst, const ADGameDescription *gameDesc)
 	: Engine(syst),
+	  _gameDesc(gameDesc),
 	  _console(nullptr),
 	  _screen(nullptr),
 	  _game(nullptr),
 	  _mapObjectId(0),
-	  _cursorState(CURSOR_IDLE) {
+	  _cursorState(CURSOR_IDLE),
+	  _currentScreen(nullptr) {
 
 	const Common::FSNode gameDataDir(ConfMan.get("path"));
 	SearchMan.addSubDirectoryMatching(gameDataDir, "data");
@@ -110,22 +113,6 @@ void MutationOfJBEngine::setCursorState(CursorState cursorState) {
 	updateCursorPalette();
 }
 
-void MutationOfJBEngine::updateCursor() {
-	if (_cursorState == CURSOR_OFF) {
-		return;
-	}
-
-	if (_game->isCurrentSceneMap()) {
-		if (_cursorState != CURSOR_IDLE) {
-			_cursorState = CURSOR_IDLE;
-			updateCursorPalette();
-		}
-	} else {
-		const Common::Point point = _eventMan->getMousePos();
-		updateCursorHitTest(point.x, point.y);
-	}
-}
-
 bool MutationOfJBEngine::hasFeature(Engine::EngineFeature f) const {
 	if (f == kSupportsLoadingDuringRuntime || f == kSupportsSavingDuringRuntime) {
 		return true;
@@ -179,114 +166,8 @@ Common::Error MutationOfJBEngine::saveGameState(int slot, const Common::String &
 	return Common::kNoError;
 }
 
-void MutationOfJBEngine::handleNormalScene(const Common::Event &event) {
-	Scene *const scene = _game->getGameData().getCurrentScene();
-
-	switch (event.type) {
-	case Common::EVENT_LBUTTONDOWN: {
-		const int16 x = event.mouse.x;
-		const int16 y = event.mouse.y;
-
-		if (Door *const door = scene->findDoor(x, y)) {
-			if (!_game->startActionSection(_game->getCurrentAction(), door->_name) && _game->getCurrentAction() == ActionInfo::Walk && door->_destSceneId != 0) {
-				_game->changeScene(door->_destSceneId, _game->getGameData()._partB);
-			}
-		} else if (Static *const stat = scene->findStatic(x, y)) {
-			_game->startActionSection(_game->getCurrentAction(), stat->_name);
-		}
-		break;
-	}
-	case Common::EVENT_MOUSEMOVE: {
-		const int16 x = event.mouse.x;
-		const int16 y = event.mouse.y;
-
-		if (_cursorState != CURSOR_OFF) {
-			updateCursorHitTest(x, y);
-		}
-		break;
-	}
-	default:
-		break;
-	}
-	_game->getGameScreen().handleEvent(event);
-}
-
-void MutationOfJBEngine::handleMapScene(const Common::Event &event) {
-	Scene *const scene = _game->getGameData().getCurrentScene();
-
-	switch (event.type) {
-	case Common::EVENT_LBUTTONDOWN: {
-		const int16 x = event.mouse.x;
-		const int16 y = event.mouse.y;
-
-		int index = 0;
-		if (Bitmap *const bitmap = scene->findBitmap(x, y, &index)) {
-			Static *const stat = scene->getStatic(index);
-			if (stat && stat->_active == 1) {
-				_game->startActionSection(ActionInfo::Walk, stat->_name);
-			}
-		}
-		break;
-	}
-	case Common::EVENT_MOUSEMOVE: {
-		const int16 x = event.mouse.x;
-		const int16 y = event.mouse.y;
-
-		int index = 0;
-		bool found = false;
-		if (Bitmap *const bitmap = scene->findBitmap(x, y, &index)) {
-			Static *const stat = scene->getStatic(index);
-			if (stat && stat->_active == 1) {
-				Object *const object = scene->getObject(index);
-				if (object) {
-					found = true;
-					if (index != _mapObjectId) {
-						if (_mapObjectId) {
-							_game->getRoom().drawObjectAnimation(_mapObjectId, 1);
-							_mapObjectId = 0;
-						}
-
-						_mapObjectId = index;
-						_game->getRoom().drawObjectAnimation(_mapObjectId, 0);
-					}
-				}
-			}
-		}
-
-		if (!found && _mapObjectId != 0) {
-			_game->getRoom().drawObjectAnimation(_mapObjectId, 1);
-			_mapObjectId = 0;
-		}
-		break;
-	}
-	default:
-		break;
-	}
-}
-
-void MutationOfJBEngine::updateCursorHitTest(int16 x, int16 y) {
-	Scene *const scene = _game->getGameData().getCurrentScene();
-	if (!scene) {
-		return;
-	}
-
-	bool entityHit = false;
-	if (Door *const door = scene->findDoor(x, y)) {
-		if (door->_destSceneId != 0) {
-			entityHit = true;
-		}
-	} else if (Static *const stat = scene->findStatic(x, y)) {
-		entityHit = true;
-	}
-	bool cursorPaletteChange = false;
-	if ((_cursorState == CURSOR_ACTIVE && !entityHit) || (_cursorState == CURSOR_IDLE && entityHit)) {
-		cursorPaletteChange = true;
-	}
-	_cursorState = entityHit ? CURSOR_ACTIVE : CURSOR_IDLE;
-	if (cursorPaletteChange) {
-		updateCursorPalette();
-	}
-
+const ADGameDescription *MutationOfJBEngine::getGameDescription() const {
+	return _gameDesc;
 }
 
 Common::Error MutationOfJBEngine::run() {
@@ -295,6 +176,7 @@ Common::Error MutationOfJBEngine::run() {
 	_console = new Console(this);
 	_screen = new Graphics::Screen();
 	_game = new Game(this);
+	_currentScreen = &_game->getGameScreen();
 
 	setupCursor();
 
@@ -320,39 +202,19 @@ Common::Error MutationOfJBEngine::run() {
 				}
 				break;
 			}
-			case Common::EVENT_KEYUP: {
-				switch (event.kbd.ascii) {
-				case 'g':
-					_game->setCurrentAction(ActionInfo::Walk);
-					break;
-				case 'r':
-					_game->setCurrentAction(ActionInfo::Talk);
-					break;
-				case 's':
-					_game->setCurrentAction(ActionInfo::Look);
-					break;
-				case 'b':
-					_game->setCurrentAction(ActionInfo::Use);
-					break;
-				case 'n':
-					_game->setCurrentAction(ActionInfo::PickUp);
-					break;
-				}
-				break;
-			}
 			default:
 				break;
 			}
 
-			if (!_game->isCurrentSceneMap()) {
-				handleNormalScene(event);
-			} else {
-				handleMapScene(event);
-			}
+			if (_currentScreen)
+				_currentScreen->handleEvent(event);
 		}
 
 		_console->onFrame();
 		_game->update();
+		if (_currentScreen)
+			_currentScreen->update();
+
 		_system->delayMillis(10);
 		_screen->update();
 	}
diff --git a/engines/mutationofjb/mutationofjb.h b/engines/mutationofjb/mutationofjb.h
index 3430f13..a499769 100644
--- a/engines/mutationofjb/mutationofjb.h
+++ b/engines/mutationofjb/mutationofjb.h
@@ -26,6 +26,8 @@
 #include "engines/engine.h"
 #include "mutationofjb/script.h"
 
+struct ADGameDescription;
+
 namespace Common {
 struct Event;
 class Serializer;
@@ -39,6 +41,7 @@ namespace MutationOfJB {
 
 class Console;
 class Game;
+class GuiScreen;
 
 struct SaveHeader {
 	bool sync(Common::Serializer &sz);
@@ -54,14 +57,13 @@ public:
 		CURSOR_ACTIVE
 	};
 
-	MutationOfJBEngine(OSystem *syst);
+	MutationOfJBEngine(OSystem *syst, const ADGameDescription *gameDesc);
 	~MutationOfJBEngine();
 
 	virtual Common::Error run();
 	Graphics::Screen *getScreen() const;
 	Game &getGame();
 	void setCursorState(CursorState cursorState);
-	void updateCursor();
 
 	virtual bool hasFeature(EngineFeature f) const override;
 	virtual bool canLoadGameStateCurrently() override;
@@ -69,40 +71,21 @@ public:
 	virtual bool canSaveGameStateCurrently() override;
 	virtual Common::Error saveGameState(int slot, const Common::String &desc) override;
 
+	const ADGameDescription *getGameDescription() const;
+
 private:
 	bool loadGameData(bool partB);
 	void setupCursor();
-	void updateCursorHitTest(int16 x, int16 y);
 	void updateCursorPalette();
 
-	/**
-	 * Handling for normal (non-map) scenes.
-	 *
-	 * Statics and doors define mouse clickable areas.
-	 * Statics are used to start actions.
-	 * Doors are used to transition between scenes.
-	 *
-	 * @param event ScummVM event.
-	 */
-	void handleNormalScene(const Common::Event &event);
-
-	/**
-	 * Special handling for map scenes.
-	 *
-	 * Bitmaps define mouse clickable areas.
-	 * Statics are used to start actions.
-	 * Objects are used for showing labels.
-	 *
-	 * @param event ScummVM event.
-	 */
-	void handleMapScene(const Common::Event &event);
-
+	const ADGameDescription *_gameDesc;
 	Console *_console;
 	Graphics::Screen *_screen;
 	Game *_game;
 	uint8 _mapObjectId;
 
 	CursorState _cursorState;
+	GuiScreen *_currentScreen;
 };
 
 }
diff --git a/engines/mutationofjb/tasks/conversationtask.cpp b/engines/mutationofjb/tasks/conversationtask.cpp
index 5167ec5..ba90013 100644
--- a/engines/mutationofjb/tasks/conversationtask.cpp
+++ b/engines/mutationofjb/tasks/conversationtask.cpp
@@ -39,10 +39,9 @@ void ConversationTask::start() {
 	setState(RUNNING);
 
 	Game &game = getTaskManager()->getGame();
+	game.getGameScreen().showConversationWidget(true);
 	ConversationWidget &widget = game.getGameScreen().getConversationWidget();
-
 	widget.setCallback(this);
-	widget.setVisible(true);
 
 	_currentGroupIndex = 0;
 
@@ -219,9 +218,9 @@ void ConversationTask::finish() {
 	setState(FINISHED);
 
 	Game &game = getTaskManager()->getGame();
+	game.getGameScreen().showConversationWidget(false);
 	ConversationWidget &widget = game.getGameScreen().getConversationWidget();
-	widget.setVisible(false);
-	game.getGameScreen().markDirty(); // TODO: Handle automatically when changing visibility.
+	widget.setCallback(nullptr);
 }
 
 void ConversationTask::startExtra() {
diff --git a/engines/mutationofjb/widgets/gamewidget.cpp b/engines/mutationofjb/widgets/gamewidget.cpp
new file mode 100644
index 0000000..c481869
--- /dev/null
+++ b/engines/mutationofjb/widgets/gamewidget.cpp
@@ -0,0 +1,179 @@
+/* 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 "mutationofjb/widgets/gamewidget.h"
+
+#include "mutationofjb/game.h"
+#include "mutationofjb/gamedata.h"
+#include "mutationofjb/guiscreen.h"
+#include "mutationofjb/mutationofjb.h"
+#include "mutationofjb/room.h"
+
+#include "common/events.h"
+#include "graphics/screen.h"
+
+namespace MutationOfJB {
+
+GameWidget::GameWidget(GuiScreen &gui) :
+	Widget(gui, Common::Rect(GAME_NORMAL_AREA_WIDTH, GAME_NORMAL_AREA_HEIGHT)),
+	_currentMapObjectId(0),
+	_nextMapObjectId(0),
+	_callback(nullptr) {}
+
+void GameWidget::handleEvent(const Common::Event &event) {
+	if (!_enabled)
+		return;
+
+	if (!_gui.getGame().isCurrentSceneMap()) {
+		handleNormalScene(event);
+	} else {
+		handleMapScene(event);
+	}
+}
+
+void GameWidget::clearState() {
+	_currentMapObjectId = _nextMapObjectId = 0;
+}
+
+void GameWidget::draw(Graphics::ManagedSurface &) {
+	Room &room = _gui.getGame().getRoom();
+
+	// Only selection changed.
+	if (_dirtyBits & DIRTY_MAP_SELECTION) {
+		if (_currentMapObjectId != _nextMapObjectId) {
+			if (_currentMapObjectId) {
+				room.drawObjectAnimation(_currentMapObjectId, 1);
+			}
+			if (_nextMapObjectId) {
+				room.drawObjectAnimation(_nextMapObjectId, 0);
+			}
+			_currentMapObjectId = _nextMapObjectId;
+		}
+	}
+
+	// Full redraw.
+	if (_dirtyBits == DIRTY_ALL) {
+		room.redraw();
+		return;
+	}
+}
+
+void GameWidget::handleNormalScene(const Common::Event &event) {
+	Game &game = _gui.getGame();
+	Scene *const scene = game.getGameData().getCurrentScene();
+
+	switch (event.type) {
+	case Common::EVENT_LBUTTONDOWN: {
+		const int16 x = event.mouse.x;
+		const int16 y = event.mouse.y;
+
+		if (!_area.contains(x, y))
+			break;
+
+		if (Door *const door = scene->findDoor(x, y)) {
+			if (_callback)
+				_callback->onGameDoorClicked(this, door);
+		} else if (Static *const stat = scene->findStatic(x, y)) {
+			if (_callback)
+				_callback->onGameStaticClicked(this, stat);
+		}
+		break;
+	}
+	case Common::EVENT_MOUSEMOVE: {
+
+		const int16 x = event.mouse.x;
+		const int16 y = event.mouse.y;
+
+		if (!_area.contains(x, y))
+			break;
+
+		bool entityHit = false;
+		if (Door *const door = scene->findDoor(x, y)) {
+			if (door->_destSceneId != 0) {
+				if (_callback)
+					_callback->onGameEntityHovered(this, door->_name);
+				entityHit = true;
+			}
+		} else if (Static *const stat = scene->findStatic(x, y)) {
+			if (_callback)
+				_callback->onGameEntityHovered(this, stat->_name);
+			entityHit = true;
+		}
+
+		if (_callback && !entityHit)
+			_callback->onGameEntityHovered(this, Common::String());
+
+		_gui.getGame().getEngine().setCursorState(entityHit ? MutationOfJBEngine::CURSOR_ACTIVE : MutationOfJBEngine::CURSOR_IDLE);
+		break;
+	}
+	default:
+		break;
+	}
+}
+
+void GameWidget::handleMapScene(const Common::Event &event) {
+	Game &game = _gui.getGame();
+	Scene *const scene = game.getGameData().getCurrentScene();
+
+	switch (event.type) {
+	case Common::EVENT_LBUTTONDOWN: {
+		const int16 x = event.mouse.x;
+		const int16 y = event.mouse.y;
+
+		int index = 0;
+		if (Bitmap *const bitmap = scene->findBitmap(x, y, &index)) {
+			Static *const stat = scene->getStatic(index);
+			if (stat && stat->_active == 1) {
+				game.startActionSection(ActionInfo::Walk, stat->_name);
+			}
+		}
+		break;
+	}
+	case Common::EVENT_MOUSEMOVE: {
+		const int16 x = event.mouse.x;
+		const int16 y = event.mouse.y;
+
+		_nextMapObjectId = 0;
+
+		int index = 0;
+		//bool found = false;
+		if (Bitmap *const bitmap = scene->findBitmap(x, y, &index)) {
+			Static *const stat = scene->getStatic(index);
+			if (stat && stat->_active == 1) {
+				Object *const object = scene->getObject(index);
+				if (object) {
+					_nextMapObjectId = index;
+				}
+			}
+		}
+		if (_currentMapObjectId != _nextMapObjectId)
+			markDirty(DIRTY_MAP_SELECTION);
+
+		_gui.getGame().getEngine().setCursorState(_nextMapObjectId ? MutationOfJBEngine::CURSOR_ACTIVE : MutationOfJBEngine::CURSOR_IDLE);
+		break;
+	}
+	default:
+		break;
+	}
+}
+
+}
diff --git a/engines/mutationofjb/widgets/gamewidget.h b/engines/mutationofjb/widgets/gamewidget.h
new file mode 100644
index 0000000..e3fe08a
--- /dev/null
+++ b/engines/mutationofjb/widgets/gamewidget.h
@@ -0,0 +1,96 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef MUTATIONOFJB_GAMEWIDGET_H
+#define MUTATIONOFJB_GAMEWIDGET_H
+
+#include "mutationofjb/widgets/widget.h"
+
+namespace MutationOfJB {
+
+class GameWidget;
+struct Door;
+struct Static;
+
+class GameWidgetCallback {
+public:
+	virtual ~GameWidgetCallback() {}
+	virtual void onGameDoorClicked(GameWidget *, const Door *door) = 0;
+	virtual void onGameStaticClicked(GameWidget *, const Static *stat) = 0;
+	virtual void onGameEntityHovered(GameWidget *, const Common::String &entity) = 0;
+};
+
+class GameWidget : public Widget {
+public:
+	enum {
+		GAME_NORMAL_AREA_WIDTH = 320,
+		GAME_NORMAL_AREA_HEIGHT = 139,
+		GAME_FULL_AREA_WIDTH = 320,
+		GAME_FULL_AREA_HEIGHT = 200
+	};
+
+	GameWidget(GuiScreen &gui);
+	void setCallback(GameWidgetCallback *callback) {
+		_callback = callback;
+	}
+
+	virtual void handleEvent(const Common::Event &);
+
+	void clearState();
+protected:
+	virtual void draw(Graphics::ManagedSurface &);
+
+private:
+	enum {
+		DIRTY_MAP_SELECTION = 1 << 1
+	};
+
+	/**
+	 * Handling for normal (non-map) scenes.
+	 *
+	 * Statics and doors define mouse clickable areas.
+	 * Statics are used to start actions.
+	 * Doors are used to transition between scenes.
+	 *
+	 * @param event ScummVM event.
+	 */
+	void handleNormalScene(const Common::Event &event);
+
+	/**
+	 * Special handling for map scenes.
+	 *
+	 * Bitmaps define mouse clickable areas.
+	 * Statics are used to start actions.
+	 * Objects are used for showing labels.
+	 *
+	 * @param event ScummVM event.
+	 */
+	void handleMapScene(const Common::Event &event);
+
+	uint8 _currentMapObjectId;
+	uint8 _nextMapObjectId;
+	GameWidgetCallback *_callback;
+};
+
+}
+
+#endif
diff --git a/engines/mutationofjb/widgets/labelwidget.cpp b/engines/mutationofjb/widgets/labelwidget.cpp
new file mode 100644
index 0000000..0279cb2
--- /dev/null
+++ b/engines/mutationofjb/widgets/labelwidget.cpp
@@ -0,0 +1,64 @@
+/* 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 "mutationofjb/widgets/labelwidget.h"
+
+#include "mutationofjb/assets.h"
+#include "mutationofjb/game.h"
+#include "mutationofjb/gamedata.h"
+
+namespace MutationOfJB {
+
+LabelWidget::LabelWidget(GuiScreen &gui, const Common::Rect &area) :
+	Widget(gui, area),
+	_backgroundColor(0x00) {}
+
+uint8 LabelWidget::getBackgroundColor() const {
+	return _backgroundColor;
+}
+
+void LabelWidget::setBackgroundColor(uint8 color) {
+	if (_backgroundColor == color)
+		return;
+
+	_backgroundColor = color;
+	markDirty();
+}
+
+const Common::String &LabelWidget::getText() const {
+	return _text;
+}
+
+void LabelWidget::setText(const Common::String &text) {
+	if (_text == text)
+		return;
+
+	_text = text;
+	markDirty();
+}
+
+void LabelWidget::draw(Graphics::ManagedSurface &surface) {
+	surface.fillRect(_area, _backgroundColor);
+	_gui.getGame().getAssets().getSystemFont().drawString(&surface, _text, _area.left, _area.top, _area.width(), LIGHTGRAY, Graphics::kTextAlignCenter);
+}
+
+}
diff --git a/engines/mutationofjb/widgets/labelwidget.h b/engines/mutationofjb/widgets/labelwidget.h
new file mode 100644
index 0000000..c870996
--- /dev/null
+++ b/engines/mutationofjb/widgets/labelwidget.h
@@ -0,0 +1,50 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef MUTATIONOJFB_LABELWIDGET_H
+#define MUTATIONOJFB_LABELWIDGET_H
+
+#include "mutationofjb/widgets/widget.h"
+
+namespace MutationOfJB {
+
+class LabelWidget : public Widget {
+public:
+	LabelWidget(GuiScreen &gui, const Common::Rect &area);
+
+	uint8 getBackgroundColor() const;
+	void setBackgroundColor(uint8 color);
+
+	const Common::String &getText() const;
+	void setText(const Common::String &text);
+
+protected:
+	virtual void draw(Graphics::ManagedSurface &) override;
+
+private:
+	uint8 _backgroundColor;
+	Common::String _text;
+};
+
+}
+
+#endif
diff --git a/engines/mutationofjb/widgets/widget.cpp b/engines/mutationofjb/widgets/widget.cpp
index b030211..1b09fe0 100644
--- a/engines/mutationofjb/widgets/widget.cpp
+++ b/engines/mutationofjb/widgets/widget.cpp
@@ -43,20 +43,36 @@ void Widget::setVisible(bool visible) {
 	_visible = visible;
 }
 
-void Widget::markDirty() {
-	_dirty = true;
+bool Widget::isEnabled() const {
+	return _enabled;
+}
+
+void Widget::setEnabled(bool enabled) {
+	_enabled = enabled;
+}
+
+Common::Rect Widget::getArea() const {
+	return _area;
+}
+
+void Widget::setArea(const Common::Rect &area) {
+	_area = area;
+}
+
+void Widget::markDirty(uint32 dirtyBits) {
+	_dirtyBits = dirtyBits;
 }
 
 bool Widget::isDirty() const {
-	return _dirty;
+	return _dirtyBits != DIRTY_NONE;
 }
 
 void Widget::update(Graphics::ManagedSurface &surface) {
-	if (_dirty) {
+	if (isDirty()) {
 		if (_visible) {
 			draw(surface);
 		}
-		_dirty = false;
+		_dirtyBits = DIRTY_NONE;
 	}
 }
 
diff --git a/engines/mutationofjb/widgets/widget.h b/engines/mutationofjb/widgets/widget.h
index 86c912a..108d86c 100644
--- a/engines/mutationofjb/widgets/widget.h
+++ b/engines/mutationofjb/widgets/widget.h
@@ -40,7 +40,12 @@ class GuiScreen;
 
 class Widget {
 public:
-	Widget(GuiScreen &gui, const Common::Rect &area) : _gui(gui), _area(area), _id(0), _visible(true), _dirty(true) {}
+	enum {
+		DIRTY_NONE = 0,
+		DIRTY_ALL = 0xFFFFFFFF
+	};
+
+	Widget(GuiScreen &gui, const Common::Rect &area) : _gui(gui), _area(area), _id(0), _visible(true), _enabled(true), _dirtyBits(DIRTY_NONE) {}
 	virtual ~Widget() {}
 
 	int getId() const;
@@ -49,8 +54,14 @@ public:
 	bool isVisible() const;
 	void setVisible(bool visible);
 
+	bool isEnabled() const;
+	void setEnabled(bool enabled);
+
+	Common::Rect getArea() const;
+	void setArea(const Common::Rect &area);
+
 	bool isDirty() const;
-	void markDirty();
+	void markDirty(uint32 dirtyBits = DIRTY_ALL);
 	void update(Graphics::ManagedSurface &);
 
 	virtual void handleEvent(const Common::Event &) {}
@@ -61,7 +72,8 @@ protected:
 	Common::Rect _area;
 	int _id;
 	bool _visible;
-	bool _dirty;
+	bool _enabled;
+	uint32 _dirtyBits;
 };
 
 }





More information about the Scummvm-git-logs mailing list