1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #include "tsage/user_interface.h"
24 #include "tsage/core.h"
25 #include "tsage/tsage.h"
26 #include "tsage/blue_force/blueforce_dialogs.h"
27 #include "tsage/blue_force/blueforce_logic.h"
28 #include "tsage/ringworld2/ringworld2_logic.h"
29
30 namespace TsAGE {
31
process(Event & event)32 void StripProxy::process(Event &event) {
33 if (_action)
34 _action->process(event);
35 }
36
37 /*--------------------------------------------------------------------------*/
38
synchronize(Serializer & s)39 void UIElement::synchronize(Serializer &s) {
40 BackgroundSceneObject::synchronize(s);
41 if (s.getVersion() < 15) {
42 int useless = 0;
43 s.syncAsSint16LE(useless);
44 }
45 s.syncAsSint16LE(_enabled);
46 s.syncAsSint16LE(_frameNum);
47 }
48
setup(int visage,int stripNum,int frameNum,int posX,int posY,int priority)49 void UIElement::setup(int visage, int stripNum, int frameNum, int posX, int posY, int priority) {
50 _frameNum = frameNum;
51 _enabled = true;
52
53 SceneObject::setup(visage, stripNum, frameNum, posX, posY, priority);
54 }
55
setEnabled(bool flag)56 void UIElement::setEnabled(bool flag) {
57 if (_enabled != flag) {
58 _enabled = flag;
59 setFrame(_enabled ? _frameNum : _frameNum + 2);
60 }
61 }
62
63 /*--------------------------------------------------------------------------*/
64
process(Event & event)65 void UIQuestion::process(Event &event) {
66 if (event.eventType == EVENT_BUTTON_DOWN) {
67 CursorType currentCursor = GLOBALS._events.getCursor();
68 GLOBALS._events.hideCursor();
69 showDescription(currentCursor);
70
71 event.handled = true;
72 }
73 }
74
showDescription(CursorType cursor)75 void UIQuestion::showDescription(CursorType cursor) {
76 switch (g_vm->getGameID()) {
77 case GType_BlueForce:
78 if (cursor == INV_FOREST_RAP) {
79 // Forest rap item has a graphical display
80 showItem(5, 1, 1);
81 } else {
82 // Display object description
83 SceneItem::display2(9001, (int)cursor);
84 }
85 break;
86 case GType_Ringworld2:
87 if ((cursor == R2_COM_SCANNER) || (cursor == R2_COM_SCANNER_2)) {
88 // Show communicator
89 Ringworld2::SceneExt *scene = static_cast<Ringworld2::SceneExt *>
90 (R2_GLOBALS._sceneManager._scene);
91 if (!scene->_sceneAreas.contains(R2_GLOBALS._scannerDialog))
92 R2_GLOBALS._scannerDialog->setup2(4, 1, 1, 160, 125);
93 } else {
94 // Show object description
95 SceneItem::display2(3, (int)cursor);
96 }
97 break;
98 default:
99 break;
100 }
101 }
102
setEnabled(bool flag)103 void UIQuestion::setEnabled(bool flag) {
104 if (_enabled != flag) {
105 UIElement::setEnabled(flag);
106 T2_GLOBALS._uiElements.draw();
107 }
108 }
109
showItem(int resNum,int rlbNum,int frameNum)110 void UIQuestion::showItem(int resNum, int rlbNum, int frameNum) {
111 GfxDialog::setPalette();
112
113 // Get the item to display
114 GfxSurface objImage = surfaceFromRes(resNum, rlbNum, frameNum);
115 Rect imgRect;
116 imgRect.resize(objImage, 0, 0, 100);
117 imgRect.center(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2);
118
119 // Save the area behind where the image will be displayed
120 GfxSurface *savedArea = surfaceGetArea(GLOBALS.gfxManager().getSurface(), imgRect);
121
122 // Draw the image
123 GLOBALS.gfxManager().copyFrom(objImage, imgRect);
124
125 // Wait for a press
126 GLOBALS._events.waitForPress();
127
128 // Restore the old area
129 GLOBALS.gfxManager().copyFrom(*savedArea, imgRect);
130 delete savedArea;
131 }
132
133 /*--------------------------------------------------------------------------*/
134
postInit(SceneObjectList * OwnerList)135 void UIScore::postInit(SceneObjectList *OwnerList) {
136 int xp = 266;
137 _digit3.setup(1, 6, 1, xp, 180, 255);
138 _digit3.reposition();
139 xp += 7;
140 _digit2.setup(1, 6, 1, xp, 180, 255);
141 _digit2.reposition();
142 xp += 7;
143 _digit1.setup(1, 6, 1, xp, 180, 255);
144 _digit1.reposition();
145 xp += 7;
146 _digit0.setup(1, 6, 1, xp, 180, 255);
147 _digit0.reposition();
148 }
149
draw()150 void UIScore::draw() {
151 _digit3.draw();
152 _digit2.draw();
153 _digit1.draw();
154 _digit0.draw();
155 }
156
updateScore()157 void UIScore::updateScore() {
158 int score = T2_GLOBALS._uiElements._scoreValue;
159
160 _digit3.setFrame(score / 1000 + 1); score %= 1000;
161 _digit2.setFrame(score / 100 + 1); score %= 100;
162 _digit1.setFrame(score / 10 + 1); score %= 10;
163 _digit0.setFrame(score + 1);
164 }
165
166 /*--------------------------------------------------------------------------*/
167
UIInventorySlot()168 UIInventorySlot::UIInventorySlot(): UIElement() {
169 _objIndex = 0;
170 _object = NULL;
171 }
172
synchronize(Serializer & s)173 void UIInventorySlot::synchronize(Serializer &s) {
174 UIElement::synchronize(s);
175 s.syncAsSint16LE(_objIndex);
176 SYNC_POINTER(_object);
177 }
178
process(Event & event)179 void UIInventorySlot::process(Event &event) {
180 if (event.eventType == EVENT_BUTTON_DOWN) {
181 event.handled = true;
182
183 // Check if game has a select item handler, and if so, give it a chance to check
184 // whether something special happens when the item is selected
185 if (!T2_GLOBALS._onSelectItem || !T2_GLOBALS._onSelectItem((CursorType)_objIndex))
186 _object->setCursor();
187 }
188 }
189
190 /*--------------------------------------------------------------------------*/
191
UIInventoryScroll()192 UIInventoryScroll::UIInventoryScroll() {
193 _isLeft = false;
194 }
195
synchronize(Serializer & s)196 void UIInventoryScroll::synchronize(Serializer &s) {
197 UIElement::synchronize(s);
198 s.syncAsSint16LE(_isLeft);
199 }
200
process(Event & event)201 void UIInventoryScroll::process(Event &event) {
202 switch (event.eventType) {
203 case EVENT_BUTTON_DOWN:
204 // Draw the button as selected
205 toggle(true);
206
207 // Wait for the mouse to be released
208 g_globals->_events.waitForPress(EVENT_BUTTON_UP);
209
210 // Restore unselected version
211 toggle(false);
212
213 // Scroll the inventory as necessary
214 T2_GLOBALS._uiElements.scrollInventory(_isLeft);
215 event.handled = true;
216 break;
217 default:
218 break;
219 }
220 }
221
toggle(bool pressed)222 void UIInventoryScroll::toggle(bool pressed) {
223 if (_enabled) {
224 setFrame(pressed ? (_frameNum + 1) : _frameNum);
225 T2_GLOBALS._uiElements.draw();
226 }
227 }
228
229 /*--------------------------------------------------------------------------*/
230
UICollection()231 UICollection::UICollection(): EventHandler() {
232 _clearScreen = false;
233 _visible = false;
234 _cursorChanged = false;
235 }
236
setup(const Common::Point & pt)237 void UICollection::setup(const Common::Point &pt) {
238 _position = pt;
239 _bounds.left = _bounds.right = pt.x;
240 _bounds.top = _bounds.bottom = pt.y;
241 }
242
hide()243 void UICollection::hide() {
244 erase();
245 _visible = false;
246 }
247
show()248 void UICollection::show() {
249 _visible = true;
250 draw();
251 }
252
erase()253 void UICollection::erase() {
254 if (_clearScreen) {
255 Rect tempRect(0, UI_INTERFACE_Y, SCREEN_WIDTH, SCREEN_HEIGHT);
256 GLOBALS._screen.fillRect(tempRect, 0);
257 GLOBALS._sceneManager._scene->_backSurface.fillRect(tempRect, 0);
258 _clearScreen = false;
259 }
260 }
261
resetClear()262 void UICollection::resetClear() {
263 _clearScreen = false;
264 }
265
draw()266 void UICollection::draw() {
267 if (_visible) {
268 // Temporarily reset the sceneBounds when drawing UI elements to force them on-screen
269 Rect savedBounds = g_globals->_sceneManager._scene->_sceneBounds;
270 g_globals->_sceneManager._scene->_sceneBounds = Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
271
272 // Draw the elements onto the background
273 for (uint idx = 0; idx < _objList.size(); ++idx)
274 _objList[idx]->draw();
275
276 // Draw the resulting UI onto the screen
277 GLOBALS._screen.copyFrom(GLOBALS._sceneManager._scene->_backSurface,
278 Rect(0, UI_INTERFACE_Y, SCREEN_WIDTH, SCREEN_HEIGHT),
279 Rect(0, UI_INTERFACE_Y, SCREEN_WIDTH, SCREEN_HEIGHT));
280
281 if (g_vm->getGameID() == GType_Ringworld2)
282 r2rDrawFrame();
283
284 _clearScreen = 1;
285 g_globals->_sceneManager._scene->_sceneBounds = savedBounds;
286 }
287 }
288
r2rDrawFrame()289 void UICollection::r2rDrawFrame() {
290 Visage visage;
291 visage.setVisage(2, 1);
292 GfxSurface vertLineLeft = visage.getFrame(1);
293 GfxSurface vertLineRight = visage.getFrame(3);
294 GfxSurface horizLine = visage.getFrame(2);
295
296 GLOBALS._screen.copyFrom(horizLine, 0, 0);
297 GLOBALS._screen.copyFrom(vertLineLeft, 0, 3);
298 GLOBALS._screen.copyFrom(vertLineRight, SCREEN_WIDTH - 4, 3);
299
300 // Restrict drawing area to exclude the borders at the edge of the screen
301 R2_GLOBALS._screen._clipRect = Rect(4, 3, SCREEN_WIDTH - 4,
302 SCREEN_HEIGHT - 3);
303 }
304
305 /*--------------------------------------------------------------------------*/
306
UIElements()307 UIElements::UIElements(): UICollection() {
308 if (g_vm->getGameID() == GType_Ringworld2)
309 _cursorVisage.setVisage(5, 1);
310 else
311 _cursorVisage.setVisage(1, 5);
312 g_saver->addLoadNotifier(&UIElements::loadNotifierProc);
313
314 _slotStart = 0;
315 _scoreValue = 0;
316 _active = false;
317 }
318
synchronize(Serializer & s)319 void UIElements::synchronize(Serializer &s) {
320 UICollection::synchronize(s);
321
322 s.syncAsSint16LE(_slotStart);
323 s.syncAsSint16LE(_scoreValue);
324 s.syncAsByte(_active);
325
326 int count = _itemList.size();
327 s.syncAsSint16LE(count);
328 if (s.isLoading()) {
329 // Load in item list
330 _itemList.clear();
331
332 for (int idx = 0; idx < count; ++idx) {
333 int itemId;
334 s.syncAsSint16LE(itemId);
335 _itemList.push_back(itemId);
336 }
337 } else {
338 // Save item list
339 for (int idx = 0; idx < count; ++idx) {
340 int itemId = _itemList[idx];
341 s.syncAsSint16LE(itemId);
342 }
343 }
344 }
345
process(Event & event)346 void UIElements::process(Event &event) {
347 if (_clearScreen && GLOBALS._player._enabled &&
348 ((g_vm->getGameID() != GType_BlueForce) || (GLOBALS._sceneManager._sceneNumber != 50))) {
349 if (_bounds.contains(event.mousePos)) {
350 // Cursor inside UI area
351 if (!_cursorChanged) {
352 if (GLOBALS._events.isInventoryIcon()) {
353 // Inventory icon being displayed, so leave alone
354 } else {
355 // Change to the inventory use cursor
356 int cursorId = (g_vm->getGameID() == GType_Ringworld2) ? 11 : 6;
357 GfxSurface surface = _cursorVisage.getFrame(cursorId);
358 GLOBALS._events.setCursor(surface);
359 }
360 _cursorChanged = true;
361 }
362
363 // Pass event to any element that the cursor falls on
364 for (int idx = (int)_objList.size() - 1; idx >= 0; --idx) {
365 if (_objList[idx]->_bounds.contains(event.mousePos) && _objList[idx]->_enabled) {
366 _objList[idx]->process(event);
367 if (event.handled)
368 break;
369 }
370 }
371
372 if (event.eventType == EVENT_BUTTON_DOWN)
373 event.handled = true;
374
375 } else if (_cursorChanged) {
376 // Cursor outside UI area, so reset as necessary
377 GLOBALS._events.setCursor(GLOBALS._events.getCursor());
378 _cursorChanged = false;
379 /*
380 SceneExt *scene = (SceneExt *)GLOBALS._sceneManager._scene;
381 if (scene->_focusObject) {
382 GfxSurface surface = _cursorVisage.getFrame(7);
383 GLOBALS._events.setCursor(surface);
384 }
385 */
386 }
387 }
388 }
389
setup(const Common::Point & pt)390 void UIElements::setup(const Common::Point &pt) {
391 _slotStart = 0;
392 _itemList.clear();
393 _scoreValue = 0;
394 _active = true;
395 UICollection::setup(pt);
396 hide();
397
398 _background.setup(1, 3, 1, 0, 0, 255);
399 add(&_background);
400
401 // Set up the inventory slots
402 int xp = 0;
403 for (int idx = 0; idx < 4; ++idx) {
404 UIElement *item = NULL;
405 switch (idx) {
406 case 0:
407 item = &_slot1;
408 break;
409 case 1:
410 item = &_slot2;
411 break;
412 case 2:
413 item = &_slot3;
414 break;
415 case 3:
416 item = &_slot4;
417 break;
418 default:
419 break;
420 }
421
422 xp = idx * 63 + 2;
423 if (g_vm->getGameID() == GType_BlueForce) {
424 item->setup(9, 1, idx, xp, 4, 255);
425 } else {
426 item->setup(7, 1, idx, xp, 4, 255);
427 }
428 add(item);
429 }
430
431 // Setup bottom-right hand buttons
432 xp = (g_vm->getGameID() == GType_Ringworld2) ? 255 : 253;
433 int yp = (g_vm->getGameID() == GType_BlueForce) ? 16 : 17;
434 _question.setup(1, 4, 7, xp, yp, 255);
435 _question.setEnabled(false);
436 add(&_question);
437
438 xp += 21;
439 _scrollLeft.setup(1, 4, 1, xp, yp, 255);
440 add(&_scrollLeft);
441 _scrollLeft._isLeft = true;
442
443 xp += (g_vm->getGameID() == GType_Ringworld2) ? 21 : 22;
444 _scrollRight.setup(1, 4, 4, xp, yp, 255);
445 add(&_scrollRight);
446 _scrollRight._isLeft = false;
447
448 switch (g_vm->getGameID()) {
449 case GType_BlueForce:
450 // Set up the score
451 _score.postInit();
452 add(&_score);
453 break;
454 case GType_Ringworld2:
455 // Set up the character display
456 _character.setup(1, 5, R2_GLOBALS._player._characterIndex, 285, 11, 255);
457 add(&_character);
458 break;
459 default:
460 break;
461 }
462
463 // Set interface area
464 _bounds = Rect(0, UI_INTERFACE_Y - 1, SCREEN_WIDTH, SCREEN_HEIGHT);
465
466 updateInventory();
467 }
468
add(UIElement * obj)469 void UIElements::add(UIElement *obj) {
470 // Add object
471 assert(_objList.size() < 12);
472 _objList.push_back(obj);
473
474 obj->setPosition(Common::Point(_bounds.left + obj->_position.x, _bounds.top + obj->_position.y));
475 obj->reposition();
476
477 GfxSurface s = obj->getFrame();
478 s.draw(obj->_position);
479 }
480
481 /**
482 * Handles updating the visual inventory in the user interface
483 */
updateInventory(int objectNumber)484 void UIElements::updateInventory(int objectNumber) {
485 switch (g_vm->getGameID()) {
486 case GType_BlueForce:
487 // Update the score
488 _score.updateScore();
489 break;
490 case GType_Ringworld2:
491 _character.setFrame(R2_GLOBALS._player._characterIndex);
492 break;
493 default:
494 break;
495 }
496
497 updateInvList();
498
499 // Enable scroll buttons if the player has more than four items
500 if (_itemList.size() > 4) {
501 _scrollLeft.setEnabled(true);
502 _scrollRight.setEnabled(true);
503 } else {
504 _scrollLeft.setEnabled(false);
505 _scrollRight.setEnabled(false);
506 }
507
508 // Handle cropping the slots start within inventory
509 int lastPage = (_itemList.size() - 1) / 4 + 1;
510 if (_slotStart < 0)
511 _slotStart = lastPage - 1;
512 else if (_slotStart > (lastPage - 1))
513 _slotStart = 0;
514
515 // Handle changing the page, if necessary, to ensure an optionally supplied
516 // object number will be on-screen
517 if (objectNumber != 0) {
518 for (uint idx = 0; idx < _itemList.size(); ++idx) {
519 if (_itemList[idx] == objectNumber) {
520 _slotStart = idx / 4;
521 break;
522 }
523 }
524 }
525
526 // Handle refreshing slot graphics
527 UIInventorySlot *slotList[4] = { &_slot1, &_slot2, &_slot3, &_slot4 };
528
529 // Loop through the inventory objects
530 SynchronizedList<InvObject *>::iterator i;
531 int objIndex = 0;
532 for (i = GLOBALS._inventory->_itemList.begin(); i != GLOBALS._inventory->_itemList.end(); ++i, ++objIndex) {
533 InvObject *obj = *i;
534
535 // Check whether the object is in any of the four inventory slots
536 for (int slotIndex = 0; slotIndex < 4; ++slotIndex) {
537 int idx = _slotStart * 4 + slotIndex;
538 int objectIdx = (idx < (int)_itemList.size()) ? _itemList[idx] : 0;
539
540 if (objectIdx == objIndex) {
541 UIInventorySlot *slot = slotList[slotIndex];
542
543 slot->_objIndex = objIndex;
544 slot->_object = obj;
545 slot->setVisage(obj->_visage);
546 slot->setStrip(obj->_strip);
547 slot->setFrame(obj->_frame);
548
549 slot->reposition();
550 }
551 }
552 }
553
554 // Refresh the display if necessary
555 if (_active)
556 draw();
557 }
558
559 /**
560 * Update the list of the indexes of items in the player's inventory
561 */
updateInvList()562 void UIElements::updateInvList() {
563 // Update the index list of items in the player's inventory
564 _itemList.clear();
565
566 SynchronizedList<InvObject *>::iterator i;
567 int itemIndex = 0;
568 for (i = GLOBALS._inventory->_itemList.begin(); i != GLOBALS._inventory->_itemList.end(); ++i, ++itemIndex) {
569 InvObject *invObject = *i;
570 if (invObject->inInventory())
571 _itemList.push_back(itemIndex);
572 }
573 }
574
575 /**
576 * Set the game score
577 */
addScore(int amount)578 void UIElements::addScore(int amount) {
579 _scoreValue += amount;
580 T2_GLOBALS._inventorySound.play(0);
581 updateInventory();
582 }
583
584 /*
585 * Scroll the inventory slots
586 */
scrollInventory(bool isLeft)587 void UIElements::scrollInventory(bool isLeft) {
588 if (isLeft)
589 --_slotStart;
590 else
591 ++_slotStart;
592
593 updateInventory();
594 }
595
loadNotifierProc(bool postFlag)596 void UIElements::loadNotifierProc(bool postFlag) {
597 if (postFlag && T2_GLOBALS._uiElements._active)
598 T2_GLOBALS._uiElements.show();
599 }
600
601 } // End of namespace TsAGE
602