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