scummvm/engines/tsage/user_interface.cpp

598 lines
15 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "tsage/user_interface.h"
#include "tsage/core.h"
#include "tsage/tsage.h"
#include "tsage/blue_force/blueforce_dialogs.h"
#include "tsage/blue_force/blueforce_logic.h"
#include "tsage/ringworld2/ringworld2_logic.h"
namespace TsAGE {
void StripProxy::process(Event &event) {
if (_action)
_action->process(event);
}
/*--------------------------------------------------------------------------*/
void UIElement::synchronize(Serializer &s) {
BackgroundSceneObject::synchronize(s);
if (s.getVersion() < 15) {
int useless = 0;
s.syncAsSint16LE(useless);
}
s.syncAsSint16LE(_enabled);
s.syncAsSint16LE(_frameNum);
}
void UIElement::setup(int visage, int stripNum, int frameNum, int posX, int posY, int priority) {
_frameNum = frameNum;
_enabled = true;
SceneObject::setup(visage, stripNum, frameNum, posX, posY, priority);
}
void UIElement::setEnabled(bool flag) {
if (_enabled != flag) {
_enabled = flag;
setFrame(_enabled ? _frameNum : _frameNum + 2);
}
}
/*--------------------------------------------------------------------------*/
void UIQuestion::process(Event &event) {
if (event.eventType == EVENT_BUTTON_DOWN) {
CursorType currentCursor = GLOBALS._events.getCursor();
GLOBALS._events.hideCursor();
showDescription(currentCursor);
event.handled = true;
}
}
void UIQuestion::showDescription(CursorType cursor) {
switch (g_vm->getGameID()) {
case GType_BlueForce:
if (cursor == INV_FOREST_RAP) {
// Forest rap item has a graphical display
showItem(5, 1, 1);
} else {
// Display object description
SceneItem::display2(9001, (int)cursor);
}
break;
case GType_Ringworld2:
if ((cursor == R2_COM_SCANNER) || (cursor == R2_COM_SCANNER_2)) {
// Show communicator
Ringworld2::SceneExt *scene = static_cast<Ringworld2::SceneExt *>
(R2_GLOBALS._sceneManager._scene);
if (!scene->_sceneAreas.contains(R2_GLOBALS._scannerDialog))
R2_GLOBALS._scannerDialog->setup2(4, 1, 1, 160, 125);
} else {
// Show object description
SceneItem::display2(3, (int)cursor);
}
break;
default:
break;
}
}
void UIQuestion::setEnabled(bool flag) {
if (_enabled != flag) {
UIElement::setEnabled(flag);
T2_GLOBALS._uiElements.draw();
}
}
void UIQuestion::showItem(int resNum, int rlbNum, int frameNum) {
GfxDialog::setPalette();
// Get the item to display
GfxSurface objImage = surfaceFromRes(resNum, rlbNum, frameNum);
Rect imgRect;
imgRect.resize(objImage, 0, 0, 100);
imgRect.center(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2);
// Save the area behind where the image will be displayed
GfxSurface *savedArea = surfaceGetArea(GLOBALS.gfxManager().getSurface(), imgRect);
// Draw the image
GLOBALS.gfxManager().copyFrom(objImage, imgRect);
// Wait for a press
GLOBALS._events.waitForPress();
// Restore the old area
GLOBALS.gfxManager().copyFrom(*savedArea, imgRect);
delete savedArea;
}
/*--------------------------------------------------------------------------*/
void UIScore::postInit(SceneObjectList *OwnerList) {
int xp = 266;
_digit3.setup(1, 6, 1, xp, 180, 255);
_digit3.reposition();
xp += 7;
_digit2.setup(1, 6, 1, xp, 180, 255);
_digit2.reposition();
xp += 7;
_digit1.setup(1, 6, 1, xp, 180, 255);
_digit1.reposition();
xp += 7;
_digit0.setup(1, 6, 1, xp, 180, 255);
_digit0.reposition();
}
void UIScore::draw() {
_digit3.draw();
_digit2.draw();
_digit1.draw();
_digit0.draw();
}
void UIScore::updateScore() {
int score = T2_GLOBALS._uiElements._scoreValue;
_digit3.setFrame(score / 1000 + 1); score %= 1000;
_digit2.setFrame(score / 100 + 1); score %= 100;
_digit1.setFrame(score / 10 + 1); score %= 10;
_digit0.setFrame(score + 1);
}
/*--------------------------------------------------------------------------*/
UIInventorySlot::UIInventorySlot(): UIElement() {
_objIndex = 0;
_object = NULL;
}
void UIInventorySlot::synchronize(Serializer &s) {
UIElement::synchronize(s);
s.syncAsSint16LE(_objIndex);
SYNC_POINTER(_object);
}
void UIInventorySlot::process(Event &event) {
if (event.eventType == EVENT_BUTTON_DOWN) {
event.handled = true;
// Check if game has a select item handler, and if so, give it a chance to check
// whether something special happens when the item is selected
if (!T2_GLOBALS._onSelectItem || !T2_GLOBALS._onSelectItem((CursorType)_objIndex))
_object->setCursor();
}
}
/*--------------------------------------------------------------------------*/
UIInventoryScroll::UIInventoryScroll() {
_isLeft = false;
}
void UIInventoryScroll::synchronize(Serializer &s) {
UIElement::synchronize(s);
s.syncAsSint16LE(_isLeft);
}
void UIInventoryScroll::process(Event &event) {
switch (event.eventType) {
case EVENT_BUTTON_DOWN:
// Draw the button as selected
toggle(true);
// Wait for the mouse to be released
BF_GLOBALS._events.waitForPress(EVENT_BUTTON_UP);
// Restore unselected version
toggle(false);
// Scroll the inventory as necessary
T2_GLOBALS._uiElements.scrollInventory(_isLeft);
event.handled = true;
break;
default:
break;
}
}
void UIInventoryScroll::toggle(bool pressed) {
if (_enabled) {
setFrame(pressed ? (_frameNum + 1) : _frameNum);
T2_GLOBALS._uiElements.draw();
}
}
/*--------------------------------------------------------------------------*/
UICollection::UICollection(): EventHandler() {
_clearScreen = false;
_visible = false;
_cursorChanged = false;
}
void UICollection::setup(const Common::Point &pt) {
_position = pt;
_bounds.left = _bounds.right = pt.x;
_bounds.top = _bounds.bottom = pt.y;
}
void UICollection::hide() {
erase();
_visible = false;
}
void UICollection::show() {
_visible = true;
draw();
}
void UICollection::erase() {
if (_clearScreen) {
Rect tempRect(0, UI_INTERFACE_Y, SCREEN_WIDTH, SCREEN_HEIGHT);
GLOBALS._screen.fillRect(tempRect, 0);
GLOBALS._sceneManager._scene->_backSurface.fillRect(tempRect, 0);
_clearScreen = false;
}
}
void UICollection::resetClear() {
_clearScreen = false;
}
void UICollection::draw() {
if (_visible) {
// Temporarily reset the sceneBounds when drawing UI elements to force them on-screen
Rect savedBounds = g_globals->_sceneManager._scene->_sceneBounds;
g_globals->_sceneManager._scene->_sceneBounds = Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
// Draw the elements onto the background
for (uint idx = 0; idx < _objList.size(); ++idx)
_objList[idx]->draw();
// Draw the resulting UI onto the screen
GLOBALS._screen.copyFrom(GLOBALS._sceneManager._scene->_backSurface,
Rect(0, UI_INTERFACE_Y, SCREEN_WIDTH, SCREEN_HEIGHT),
Rect(0, UI_INTERFACE_Y, SCREEN_WIDTH, SCREEN_HEIGHT));
if (g_vm->getGameID() == GType_Ringworld2)
r2rDrawFrame();
_clearScreen = 1;
g_globals->_sceneManager._scene->_sceneBounds = savedBounds;
}
}
void UICollection::r2rDrawFrame() {
Visage visage;
visage.setVisage(2, 1);
GfxSurface vertLineLeft = visage.getFrame(1);
GfxSurface vertLineRight = visage.getFrame(3);
GfxSurface horizLine = visage.getFrame(2);
GLOBALS._screen.copyFrom(horizLine, 0, 0);
GLOBALS._screen.copyFrom(vertLineLeft, 0, 3);
GLOBALS._screen.copyFrom(vertLineRight, SCREEN_WIDTH - 4, 3);
// Restrict drawing area to exclude the borders at the edge of the screen
R2_GLOBALS._screen._clipRect = Rect(4, 3, SCREEN_WIDTH - 4,
SCREEN_HEIGHT - 3);
}
/*--------------------------------------------------------------------------*/
UIElements::UIElements(): UICollection() {
if (g_vm->getGameID() == GType_Ringworld2)
_cursorVisage.setVisage(5, 1);
else
_cursorVisage.setVisage(1, 5);
g_saver->addLoadNotifier(&UIElements::loadNotifierProc);
_slotStart = 0;
_scoreValue = 0;
_active = false;
}
void UIElements::synchronize(Serializer &s) {
UICollection::synchronize(s);
s.syncAsSint16LE(_slotStart);
s.syncAsSint16LE(_scoreValue);
s.syncAsByte(_active);
int count = _itemList.size();
s.syncAsSint16LE(count);
if (s.isLoading()) {
// Load in item list
_itemList.clear();
for (int idx = 0; idx < count; ++idx) {
int itemId;
s.syncAsSint16LE(itemId);
_itemList.push_back(itemId);
}
} else {
// Save item list
for (int idx = 0; idx < count; ++idx) {
int itemId = _itemList[idx];
s.syncAsSint16LE(itemId);
}
}
}
void UIElements::process(Event &event) {
if (_clearScreen && GLOBALS._player._enabled &&
((g_vm->getGameID() != GType_BlueForce) || (GLOBALS._sceneManager._sceneNumber != 50))) {
if (_bounds.contains(event.mousePos)) {
// Cursor inside UI area
if (!_cursorChanged) {
if (GLOBALS._events.isInventoryIcon()) {
// Inventory icon being displayed, so leave alone
} else {
// Change to the inventory use cursor
int cursorId = (g_vm->getGameID() == GType_Ringworld2) ? 11 : 6;
GfxSurface surface = _cursorVisage.getFrame(cursorId);
GLOBALS._events.setCursor(surface);
}
_cursorChanged = true;
}
// Pass event to any element that the cursor falls on
for (int idx = (int)_objList.size() - 1; idx >= 0; --idx) {
if (_objList[idx]->_bounds.contains(event.mousePos) && _objList[idx]->_enabled) {
_objList[idx]->process(event);
if (event.handled)
break;
}
}
if (event.eventType == EVENT_BUTTON_DOWN)
event.handled = true;
} else if (_cursorChanged) {
// Cursor outside UI area, so reset as necessary
GLOBALS._events.setCursor(GLOBALS._events.getCursor());
_cursorChanged = false;
/*
SceneExt *scene = (SceneExt *)GLOBALS._sceneManager._scene;
if (scene->_focusObject) {
GfxSurface surface = _cursorVisage.getFrame(7);
GLOBALS._events.setCursor(surface);
}
*/
}
}
}
void UIElements::setup(const Common::Point &pt) {
_slotStart = 0;
_itemList.clear();
_scoreValue = 0;
_active = true;
UICollection::setup(pt);
hide();
_background.setup(1, 3, 1, 0, 0, 255);
add(&_background);
// Set up the inventory slots
int xp = 0;
for (int idx = 0; idx < 4; ++idx) {
UIElement *item = NULL;
switch (idx) {
case 0:
item = &_slot1;
break;
case 1:
item = &_slot2;
break;
case 2:
item = &_slot3;
break;
case 3:
item = &_slot4;
break;
}
xp = idx * 63 + 2;
if (g_vm->getGameID() == GType_BlueForce) {
item->setup(9, 1, idx, xp, 4, 255);
} else {
item->setup(7, 1, idx, xp, 4, 255);
}
add(item);
}
// Setup bottom-right hand buttons
xp = (g_vm->getGameID() == GType_Ringworld2) ? 255 : 253;
int yp = (g_vm->getGameID() == GType_BlueForce) ? 16 : 17;
_question.setup(1, 4, 7, xp, yp, 255);
_question.setEnabled(false);
add(&_question);
xp += 21;
_scrollLeft.setup(1, 4, 1, xp, yp, 255);
add(&_scrollLeft);
_scrollLeft._isLeft = true;
xp += (g_vm->getGameID() == GType_Ringworld2) ? 21 : 22;
_scrollRight.setup(1, 4, 4, xp, yp, 255);
add(&_scrollRight);
_scrollRight._isLeft = false;
switch (g_vm->getGameID()) {
case GType_BlueForce:
// Set up the score
_score.postInit();
add(&_score);
break;
case GType_Ringworld2:
// Set up the character display
_character.setup(1, 5, R2_GLOBALS._player._characterIndex, 285, 11, 255);
add(&_character);
break;
default:
break;
}
// Set interface area
_bounds = Rect(0, UI_INTERFACE_Y - 1, SCREEN_WIDTH, SCREEN_HEIGHT);
updateInventory();
}
void UIElements::add(UIElement *obj) {
// Add object
assert(_objList.size() < 12);
_objList.push_back(obj);
obj->setPosition(Common::Point(_bounds.left + obj->_position.x, _bounds.top + obj->_position.y));
obj->reposition();
GfxSurface s = obj->getFrame();
s.draw(obj->_position);
}
/**
* Handles updating the visual inventory in the user interface
*/
void UIElements::updateInventory(int objectNumber) {
switch (g_vm->getGameID()) {
case GType_BlueForce:
// Update the score
_score.updateScore();
break;
case GType_Ringworld2:
_character.setFrame(R2_GLOBALS._player._characterIndex);
break;
}
updateInvList();
// Enable scroll buttons if the player has more than four items
if (_itemList.size() > 4) {
_scrollLeft.setEnabled(true);
_scrollRight.setEnabled(true);
} else {
_scrollLeft.setEnabled(false);
_scrollRight.setEnabled(false);
}
// Handle cropping the slots start within inventory
int lastPage = (_itemList.size() - 1) / 4 + 1;
if (_slotStart < 0)
_slotStart = lastPage - 1;
else if (_slotStart > (lastPage - 1))
_slotStart = 0;
// Handle changing the page, if necessary, to ensure an optionally supplied
// object number will be on-screen
if (objectNumber != 0) {
for (uint idx = 0; idx < _itemList.size(); ++idx) {
if (_itemList[idx] == objectNumber) {
_slotStart = idx / 4;
break;
}
}
}
// Handle refreshing slot graphics
UIInventorySlot *slotList[4] = { &_slot1, &_slot2, &_slot3, &_slot4 };
// Loop through the inventory objects
SynchronizedList<InvObject *>::iterator i;
int objIndex = 0;
for (i = GLOBALS._inventory->_itemList.begin(); i != GLOBALS._inventory->_itemList.end(); ++i, ++objIndex) {
InvObject *obj = *i;
// Check whether the object is in any of the four inventory slots
for (int slotIndex = 0; slotIndex < 4; ++slotIndex) {
int idx = _slotStart * 4 + slotIndex;
int objectIdx = (idx < (int)_itemList.size()) ? _itemList[idx] : 0;
if (objectIdx == objIndex) {
UIInventorySlot *slot = slotList[slotIndex];
slot->_objIndex = objIndex;
slot->_object = obj;
slot->setVisage(obj->_visage);
slot->setStrip(obj->_strip);
slot->setFrame(obj->_frame);
slot->reposition();
}
}
}
// Refresh the display if necessary
if (_active)
draw();
}
/**
* Update the list of the indexes of items in the player's inventory
*/
void UIElements::updateInvList() {
// Update the index list of items in the player's inventory
_itemList.clear();
SynchronizedList<InvObject *>::iterator i;
int itemIndex = 0;
for (i = GLOBALS._inventory->_itemList.begin(); i != GLOBALS._inventory->_itemList.end(); ++i, ++itemIndex) {
InvObject *invObject = *i;
if (invObject->inInventory())
_itemList.push_back(itemIndex);
}
}
/**
* Set the game score
*/
void UIElements::addScore(int amount) {
_scoreValue += amount;
T2_GLOBALS._inventorySound.play(0);
updateInventory();
}
/*
* Scroll the inventory slots
*/
void UIElements::scrollInventory(bool isLeft) {
if (isLeft)
--_slotStart;
else
++_slotStart;
updateInventory();
}
void UIElements::loadNotifierProc(bool postFlag) {
if (postFlag && T2_GLOBALS._uiElements._active)
T2_GLOBALS._uiElements.show();
}
} // End of namespace TsAGE