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 "common/scummsys.h"
24 #include "mads/mads.h"
25 #include "mads/compression.h"
26 #include "mads/user_interface.h"
27 #include "mads/nebular/game_nebular.h"
28 
29 namespace MADS {
30 
UISlot()31 UISlot::UISlot() {
32 	_flags = IMG_STATIC;
33 	_segmentId = 0;
34 	_spritesIndex = 0;
35 	_frameNumber = 0;
36 	_width = _height = 0;
37 }
38 
39 /*------------------------------------------------------------------------*/
40 
fullRefresh()41 void UISlots::fullRefresh() {
42 	UISlot slot;
43 	slot._flags = IMG_REFRESH;
44 	slot._segmentId = -1;
45 
46 	push_back(slot);
47 }
48 
add(const Common::Rect & bounds)49 void UISlots::add(const Common::Rect &bounds) {
50 	assert(size() < 50);
51 
52 	UISlot ie;
53 	ie._flags = IMG_OVERPRINT;
54 	ie._segmentId = IMG_TEXT_UPDATE;
55 	ie._position = Common::Point(bounds.left, bounds.top);
56 	ie._width = bounds.width();
57 	ie._height = bounds.height();
58 
59 	push_back(ie);
60 }
61 
add(const AnimFrameEntry & frameEntry)62 void UISlots::add(const AnimFrameEntry &frameEntry) {
63 	assert(size() < 50);
64 
65 	UISlot ie;
66 	ie._flags = IMG_UPDATE;
67 	ie._segmentId = frameEntry._seqIndex;
68 	ie._spritesIndex = frameEntry._spriteSlot._spritesIndex;
69 	ie._frameNumber = frameEntry._spriteSlot._frameNumber;
70 	ie._position = frameEntry._spriteSlot._position;
71 
72 	push_back(ie);
73 }
74 
draw(bool updateFlag,bool delFlag)75 void UISlots::draw(bool updateFlag, bool delFlag) {
76 	Scene &scene = _vm->_game->_scene;
77 	UserInterface &userInterface = scene._userInterface;
78 	DirtyArea *dirtyAreaPtr = nullptr;
79 
80 	// Loop through setting up the dirty areas
81 	for (uint idx = 0; idx < size(); ++idx) {
82 		DirtyArea &dirtyArea = userInterface._dirtyAreas[idx];
83 		UISlot &slot = (*this)[idx];
84 
85 		if (slot._flags >= IMG_STATIC) {
86 			dirtyArea._active = false;
87 		} else {
88 			dirtyArea.setUISlot(&slot);
89 			dirtyArea._textActive = true;
90 			if (slot._segmentId == IMG_SPINNING_OBJECT && slot._flags == IMG_FULL_UPDATE) {
91 				dirtyArea._active = false;
92 				dirtyAreaPtr = &dirtyArea;
93 			}
94 		}
95 	}
96 
97 	userInterface._dirtyAreas.merge(1, userInterface._uiSlots.size());
98 	if (dirtyAreaPtr)
99 		dirtyAreaPtr->_active = true;
100 
101 	// Copy parts of the user interface background that need to be erased
102 	for (uint idx = 0; idx < size(); ++idx) {
103 		DirtyArea &dirtyArea = userInterface._dirtyAreas[idx];
104 		UISlot &slot = (*this)[idx];
105 
106 		if (dirtyArea._active && dirtyArea._bounds.width() > 0
107 				&& dirtyArea._bounds.height() > 0 && slot._flags > -20) {
108 
109 			if (slot._flags >= IMG_ERASE) {
110 				// Merge area
111 				userInterface.mergeFrom(&userInterface._surface, dirtyArea._bounds,
112 					Common::Point(dirtyArea._bounds.left, dirtyArea._bounds.top));
113 			} else {
114 				// Copy area
115 				userInterface.blitFrom(userInterface._surface, dirtyArea._bounds,
116 					Common::Point(dirtyArea._bounds.left, dirtyArea._bounds.top));
117 			}
118 		}
119 	}
120 
121 	for (uint idx = 0; idx < size(); ++idx) {
122 		DirtyArea &dirtyArea = userInterface._dirtyAreas[idx];
123 		UISlot &slot = (*this)[idx];
124 
125 		int slotType = slot._flags;
126 		if (slotType >= IMG_STATIC) {
127 			dirtyArea.setUISlot(&slot);
128 			if (!updateFlag)
129 				slotType &= ~0x40;
130 
131 			dirtyArea._textActive = slotType > 0;
132 			slot._flags &= 0x40;
133 		}
134 	}
135 
136 	userInterface._dirtyAreas.merge(1, userInterface._uiSlots.size());
137 
138 	for (uint idx = 0; idx < size(); ++idx) {
139 		DirtyArea *dirtyArea = &userInterface._dirtyAreas[idx];
140 		UISlot &slot = (*this)[idx];
141 
142 		if (slot._flags >= IMG_STATIC && !(slot._flags & 0x40)) {
143 			if (!dirtyArea->_active) {
144 				do {
145 					dirtyArea = dirtyArea->_mergedArea;
146 				} while (!dirtyArea->_active);
147 			}
148 
149 			if (dirtyArea->_textActive) {
150 				SpriteAsset *asset = scene._sprites[slot._spritesIndex];
151 
152 				// Get the frame details
153 				int frameNumber = ABS(slot._frameNumber);
154 				bool flipped = slot._frameNumber < 0;
155 
156 				if (slot._segmentId == IMG_SPINNING_OBJECT) {
157 					MSprite *sprite = asset->getFrame(frameNumber - 1);
158 					userInterface.transBlitFrom(*sprite, slot._position,
159 						sprite->getTransparencyIndex());
160 				} else {
161 					MSprite *sprite = asset->getFrame(frameNumber - 1);
162 
163 					if (flipped) {
164 						BaseSurface *spr = sprite->flipHorizontal();
165 						userInterface.mergeFrom(spr, spr->getBounds(), slot._position,
166 							sprite->getTransparencyIndex());
167 						spr->free();
168 						delete spr;
169 					} else {
170 						userInterface.mergeFrom(sprite, sprite->getBounds(), slot._position,
171 							sprite->getTransparencyIndex());
172 					}
173 				}
174 			}
175 		}
176 	}
177 
178 	// Mark areas of the screen surface for updating
179 	if (updateFlag) {
180 		for (uint idx = 0; idx < size(); ++idx) {
181 			DirtyArea &dirtyArea = userInterface._dirtyAreas[idx];
182 
183 			if (dirtyArea._active && dirtyArea._textActive &&
184 				dirtyArea._bounds.width() > 0 && dirtyArea._bounds.height() > 0) {
185 				// Flag area of screen as needing update
186 				Common::Rect r = dirtyArea._bounds;
187 				r.translate(0, scene._interfaceY);
188 				//_vm->_screen->copyRectToScreen(r);
189 			}
190 		}
191 	}
192 
193 	// Post-processing to remove slots no longer needed
194 	for (int idx = (int)size() - 1; idx >= 0; --idx) {
195 		UISlot &slot = (*this)[idx];
196 
197 		if (slot._flags < IMG_STATIC) {
198 			if (delFlag || updateFlag)
199 				remove_at(idx);
200 			else if (slot._flags > -20)
201 				slot._flags -= 20;
202 		} else {
203 			if (updateFlag)
204 				slot._flags &= ~0x40;
205 			else
206 				slot._flags |= 0x40;
207 		}
208 	}
209 }
210 
211 /*------------------------------------------------------------------------*/
212 
213 MADSEngine *Conversation::_vm;
214 
init(MADSEngine * vm)215 void Conversation::init(MADSEngine *vm) {
216 	_vm = vm;
217 }
218 
setup(int globalId,...)219 void Conversation::setup(int globalId, ...) {
220 	va_list va;
221 	va_start(va, globalId);
222 
223 	// Load the list of conversation quotes
224 	_quotes.clear();
225 	int quoteId = va_arg(va, int);
226 	while (quoteId > 0) {
227 		_quotes.push_back(quoteId);
228 		quoteId = va_arg(va, int);
229 	}
230 	va_end(va);
231 
232 	if (quoteId < 0) {
233 		// For an ending value of -1, also initial the bitflags for the global
234 		// associated with the conversation entry, which enables all the quote Ids
235 		_vm->_game->globals()[globalId] = (int16)0xffff;
236 	}
237 
238 	_globalId = globalId;
239 }
240 
set(int quoteId,...)241 void Conversation::set(int quoteId, ...) {
242 	_vm->_game->globals()[_globalId] = 0;
243 
244 	va_list va;
245 	va_start(va, quoteId);
246 
247 	// Loop through handling each quote
248 	while (quoteId > 0) {
249 		for (uint idx = 0; idx < _quotes.size(); ++idx) {
250 			if (_quotes[idx] == quoteId) {
251 				// Found index, so set that bit in the global keeping track of conversation state
252 				_vm->_game->globals()[_globalId] |= 1 << idx;
253 				break;
254 			}
255 		}
256 
257 		quoteId = va_arg(va, int);
258 	}
259 	va_end(va);
260 }
261 
read(int quoteId)262 int Conversation::read(int quoteId) {
263 	uint16 flags = _vm->_game->globals()[_globalId];
264 	int count = 0;
265 
266 	for (uint idx = 0; idx < _quotes.size(); ++idx) {
267 		if (flags & (1 << idx))
268 			++count;
269 
270 		if (_quotes[idx] == quoteId)
271 			return flags & (1 << idx);
272 	}
273 
274 	// Could not find it, simply return number of active quotes
275 	return count;
276 }
277 
write(int quoteId,bool flag)278 void Conversation::write(int quoteId, bool flag) {
279 	for (uint idx = 0; idx < _quotes.size(); ++idx) {
280 		if (_quotes[idx] == quoteId) {
281 			// Found index, so set or clear the flag
282 			if (flag) {
283 				// Set bit
284 				_vm->_game->globals()[_globalId] |= 1 << idx;
285 			} else {
286 				// Clear bit
287 				_vm->_game->globals()[_globalId] &= ~(1 << idx);
288 			}
289 			return;
290 		}
291 	}
292 }
293 
start()294 void Conversation::start() {
295 	UserInterface &userInterface = _vm->_game->_scene._userInterface;
296 	userInterface.emptyConversationList();
297 
298 	// Loop through each of the quotes loaded into the conversation
299 	for (uint idx = 0; idx < _quotes.size(); ++idx) {
300 		// Check whether the given quote is enabled or not
301 		if (_vm->_game->globals()[_globalId] & (1 << idx)) {
302 			// Quote enabled, so add it to the list of talk selections
303 			Common::String msg = _vm->_game->getQuote(_quotes[idx]);
304 			userInterface.addConversationMessage(_quotes[idx], msg);
305 		}
306 	}
307 
308 	userInterface.setup(kInputConversation);
309 }
310 
311 /*------------------------------------------------------------------------*/
312 
UserInterface(MADSEngine * vm)313 UserInterface::UserInterface(MADSEngine *vm) : _vm(vm), _dirtyAreas(vm),
314 		_uiSlots(vm) {
315 	_invSpritesIndex = -1;
316 	_invFrameNumber = 1;
317 	_scrollMilli = 0;
318 	_scrollFlag = false;
319 	_category = CAT_NONE;
320 	_inventoryTopIndex = 0;
321 	_selectedInvIndex = -1;
322 	_selectedActionIndex = 0;
323 	_selectedItemVocabIdx = -1;
324 	_scrollbarActive = SCROLLBAR_NONE;
325 	_scrollbarOldActive = SCROLLBAR_NONE;
326 	_scrollbarStrokeType = SCROLLBAR_NONE;
327 	_scrollbarQuickly = false;
328 	_scrollbarMilliTime = 0;
329 	_scrollbarElevator = _scrollbarOldElevator = 0;
330 	_highlightedCommandIndex = -1;
331 	_highlightedInvIndex = -1;
332 	_highlightedItemVocabIndex = -1;
333 	_dirtyAreas.resize(50);
334 	_inventoryChanged = false;
335 	_noSegmentsActive = 0;
336 	_someSegmentsActive = 0;
337 	_rectP = nullptr;
338 
339 	Common::fill(&_categoryIndexes[0], &_categoryIndexes[7], 0);
340 
341 	// Map the user interface to the bottom of the game's screen surface
342 	create(*_vm->_screen, Common::Rect(0, MADS_SCENE_HEIGHT,  MADS_SCREEN_WIDTH,
343 		MADS_SCREEN_HEIGHT));
344 
345 	_surface.create(MADS_SCREEN_WIDTH, MADS_INTERFACE_HEIGHT);
346 }
347 
load(const Common::String & resName)348 void UserInterface::load(const Common::String &resName) {
349 	File f(resName);
350 	MadsPack madsPack(&f);
351 
352 	// Load in the palette
353 	Common::SeekableReadStream *palStream = madsPack.getItemStream(0);
354 
355 	uint32 *gamePalP = &_vm->_palette->_palFlags[0];
356 	byte *palP = &_vm->_palette->_mainPalette[0];
357 
358 	for (int i = 0; i < 16; ++i, gamePalP++, palP += 3) {
359 		RGB6 rgb;
360 		rgb.load(palStream);
361 		palP[0] = rgb.r;
362 		palP[1] = rgb.g;
363 		palP[2] = rgb.b;
364 		*gamePalP |= 1;
365 	}
366 	delete palStream;
367 
368 	// Read in the surface data
369 	Common::SeekableReadStream *pixelsStream = madsPack.getItemStream(1);
370 	pixelsStream->read(_surface.getPixels(), MADS_SCREEN_WIDTH * MADS_INTERFACE_HEIGHT);
371 	delete pixelsStream;
372 }
373 
setup(InputMode inputMode)374 void UserInterface::setup(InputMode inputMode) {
375 	Scene &scene = _vm->_game->_scene;
376 
377 	if (_vm->_game->_screenObjects._inputMode != inputMode) {
378 		Common::String resName = _vm->_game->_aaName;
379 
380 		// Strip off any extension
381 		const char *p = strchr(resName.c_str(), '.');
382 		if (p) {
383 			resName = Common::String(resName.c_str(), p);
384 		}
385 
386 		// Add on suffix if necessary
387 		if (inputMode != kInputBuildingSentences)
388 			resName += "A";
389 
390 		resName += ".INT";
391 
392 		load(resName);
393 		blitFrom(_surface);
394 	}
395 	_vm->_game->_screenObjects._inputMode = inputMode;
396 
397 	scene._userInterface._uiSlots.clear();
398 	scene._userInterface._uiSlots.fullRefresh();
399 	_vm->_game->_screenObjects._baseTime = _vm->_events->getFrameCounter();
400 	_highlightedCommandIndex = -1;
401 	_highlightedItemVocabIndex = -1;
402 	_highlightedInvIndex = -1;
403 
404 	if (_vm->_game->_kernelMode == KERNEL_ACTIVE_CODE)
405 		scene._userInterface._uiSlots.draw(false, false);
406 
407 	scene._action.clear();
408 	drawTextElements();
409 	loadElements();
410 	scene._dynamicHotspots.refresh();
411 }
412 
drawTextElements()413 void UserInterface::drawTextElements() {
414 	switch (_vm->_game->_screenObjects._inputMode) {
415 	case kInputBuildingSentences:
416 		// Draw the actions
417 		drawActions();
418 		drawInventoryList();
419 		drawItemVocabList();
420 		break;
421 
422 	case kInputConversation:
423 		drawConversationList();
424 		break;
425 
426 	case kInputLimitedSentences:
427 	default:
428 		break;
429 	}
430 }
431 
mergeFrom(BaseSurface * src,const Common::Rect & srcBounds,const Common::Point & destPos,int transparencyIndex)432 void UserInterface::mergeFrom(BaseSurface *src, const Common::Rect &srcBounds,
433 	const Common::Point &destPos, int transparencyIndex) {
434 	// Validation of the rectangle and position
435 	int destX = destPos.x, destY = destPos.y;
436 	if ((destX >= w) || (destY >= h))
437 		return;
438 
439 	Common::Rect copyRect = srcBounds;
440 	if (destX < 0) {
441 		copyRect.left += -destX;
442 		destX = 0;
443 	} else if (destX + copyRect.width() > w) {
444 		copyRect.right -= destX + copyRect.width() - w;
445 	}
446 	if (destY < 0) {
447 		copyRect.top += -destY;
448 		destY = 0;
449 	} else if (destY + copyRect.height() > h) {
450 		copyRect.bottom -= destY + copyRect.height() - h;
451 	}
452 
453 	if (!copyRect.isValidRect())
454 		return;
455 
456 	// Copy the specified area
457 
458 	byte *data = src->getPixels();
459 	byte *srcPtr = data + (src->w * copyRect.top + copyRect.left);
460 	byte *destPtr = (byte *)getPixels() + (destY * this->w) + destX;
461 
462 	for (int rowCtr = 0; rowCtr < copyRect.height(); ++rowCtr) {
463 		// Process each line of the area
464 		for (int xCtr = 0; xCtr < copyRect.width(); ++xCtr) {
465 			// Check for the range used for the user interface background,
466 			// which are the only pixels that can be replaced
467 			if ((destPtr[xCtr] >= 8 && destPtr[xCtr] <= 15) && (int)srcPtr[xCtr] != transparencyIndex)
468 				destPtr[xCtr] = srcPtr[xCtr];
469 		}
470 
471 		srcPtr += src->w;
472 		destPtr += this->w;
473 	}
474 }
475 
drawActions()476 void UserInterface::drawActions() {
477 	for (int idx = 0; idx < 10; ++idx) {
478 		writeVocab(CAT_COMMAND, idx);
479 	}
480 }
481 
drawInventoryList()482 void UserInterface::drawInventoryList() {
483 	int endIndex = MIN((int)_vm->_game->_objects._inventoryList.size(), _inventoryTopIndex + 5);
484 	for (int idx = _inventoryTopIndex; idx < endIndex; ++idx) {
485 		writeVocab(CAT_INV_LIST, idx);
486 	}
487 }
488 
drawItemVocabList()489 void UserInterface::drawItemVocabList() {
490 	if (_selectedInvIndex >= 0) {
491 		InventoryObject &io = _vm->_game->_objects[
492 			_vm->_game->_objects._inventoryList[_selectedInvIndex]];
493 		for (int idx = 0; idx < io._vocabCount; ++idx) {
494 			writeVocab(CAT_INV_VOCAB, idx);
495 		}
496 	}
497 }
498 
drawScroller()499 void UserInterface::drawScroller() {
500 	if (_scrollbarActive)
501 		writeVocab(CAT_INV_SCROLLER, _scrollbarActive);
502 	writeVocab(CAT_INV_SCROLLER, 4);
503 }
504 
updateInventoryScroller()505 void UserInterface::updateInventoryScroller() {
506 	ScreenObjects &screenObjects = _vm->_game->_screenObjects;
507 
508 	if (screenObjects._inputMode != kInputBuildingSentences)
509 		return;
510 
511 	_scrollbarActive = SCROLLBAR_NONE;
512 
513 	if ((screenObjects._category == CAT_INV_SCROLLER) || (screenObjects._category != CAT_INV_SCROLLER
514 			&& _scrollbarOldActive == SCROLLBAR_ELEVATOR && _vm->_events->_mouseStatusCopy)) {
515 		if (_vm->_events->_mouseStatusCopy || _vm->_easyMouse) {
516 			if ((_vm->_events->_mouseClicked || (_vm->_easyMouse && !_vm->_events->_mouseStatusCopy))
517 					&& (screenObjects._category == CAT_INV_SCROLLER))
518 				_scrollbarStrokeType = (ScrollbarActive)screenObjects._spotId;
519 
520 			if (screenObjects._spotId == _scrollbarStrokeType || _scrollbarOldActive == SCROLLBAR_ELEVATOR) {
521 				_scrollbarActive = _scrollbarStrokeType;
522 				uint32 currentMilli = g_system->getMillis();
523 				uint32 timeInc = _scrollbarQuickly ? 100 : 380;
524 
525 				if (_vm->_events->_mouseStatus && (_scrollbarMilliTime + timeInc) <= currentMilli) {
526 					_scrollbarQuickly = _vm->_events->_strokeGoing < 1;
527 					_scrollbarMilliTime = currentMilli;
528 
529 					// Change the scrollbar and visible inventory list
530 					changeScrollBar();
531 				}
532 			}
533 		}
534 	}
535 
536 	if (_scrollbarActive != _scrollbarOldActive || _scrollbarElevator != _scrollbarOldElevator)
537 		scrollbarChanged();
538 
539 	_scrollbarOldActive = _scrollbarActive;
540 	_scrollbarOldElevator = _scrollbarElevator;
541 }
542 
changeScrollBar()543 void UserInterface::changeScrollBar() {
544 	Common::Array<int> &inventoryList = _vm->_game->_objects._inventoryList;
545 	ScreenObjects &screenObjects = _vm->_game->_screenObjects;
546 
547 	if (screenObjects._inputMode != kInputBuildingSentences)
548 		return;
549 
550 	switch (_scrollbarStrokeType) {
551 	case SCROLLBAR_UP:
552 		// Scroll up
553 		if (_inventoryTopIndex > 0 && inventoryList.size() > 0) {
554 			--_inventoryTopIndex;
555 			_inventoryChanged = true;
556 		}
557 		break;
558 
559 	case SCROLLBAR_DOWN:
560 		// Scroll down
561 		if (_inventoryTopIndex < ((int)inventoryList.size() - 1) && inventoryList.size() > 1) {
562 			++_inventoryTopIndex;
563 			_inventoryChanged = true;
564 		}
565 		break;
566 
567 	case SCROLLBAR_ELEVATOR: {
568 		// Inventory slider
569 		int newIndex = CLIP((int)_vm->_events->currentPos().y - 170, 0, 17)
570 			* inventoryList.size() / 10;
571 		if (newIndex >= (int)inventoryList.size())
572 			newIndex = inventoryList.size() - 1;
573 
574 		if (inventoryList.size() > 0) {
575 			_inventoryChanged = newIndex != _inventoryTopIndex;
576 			_inventoryTopIndex = newIndex;
577 		}
578 		break;
579 	}
580 
581 	default:
582 		break;
583 	}
584 
585 	if (_inventoryChanged) {
586 		int dummy;
587 		updateSelection(CAT_INV_LIST, 0, &dummy);
588 	}
589 }
590 
scrollbarChanged()591 void UserInterface::scrollbarChanged() {
592 	Common::Rect r(73, 4, 73 + 9, 4 + 38);
593 	_uiSlots.add(r);
594 	_uiSlots.draw(false, false);
595 	drawScroller();
596 //	updateRect(r);
597 }
598 
writeVocab(ScrCategory category,int id)599 void UserInterface::writeVocab(ScrCategory category, int id) {
600 	Common::Rect bounds;
601 	if (!getBounds(category, id, bounds))
602 		return;
603 
604 	Scene &scene = _vm->_game->_scene;
605 	Font *font = nullptr;
606 
607 	int vocabId;
608 	Common::String vocabStr;
609 	switch (category) {
610 	case CAT_COMMAND:
611 		font = _vm->_font->getFont(FONT_INTERFACE);
612 		vocabId = scene._verbList[id]._id;
613 		if (id == _highlightedCommandIndex) {
614 			_vm->_font->setColorMode(SELMODE_HIGHLIGHTED);
615 		} else {
616 			_vm->_font->setColorMode(id == _selectedActionIndex ? SELMODE_SELECTED : SELMODE_UNSELECTED);
617 		}
618 		vocabStr = scene.getVocab(vocabId);
619 		vocabStr.setChar(toupper(vocabStr[0]), 0);
620 		font->writeString(this, vocabStr, Common::Point(bounds.left, bounds.top));
621 		break;
622 
623 	case CAT_INV_LIST:
624 		font = _vm->_font->getFont(FONT_INTERFACE);
625 		vocabId = _vm->_game->_objects.getItem(id)._descId;
626 		if (id == _highlightedInvIndex) {
627 			_vm->_font->setColorMode(SELMODE_HIGHLIGHTED);
628 		} else {
629 			_vm->_font->setColorMode(id == _selectedInvIndex ? SELMODE_SELECTED : SELMODE_UNSELECTED);
630 		}
631 
632 		vocabStr = scene.getVocab(vocabId);
633 		vocabStr.setChar(toupper(vocabStr[0]), 0);
634 		font->writeString(this, vocabStr, Common::Point(bounds.left, bounds.top));
635 		break;
636 
637 	case CAT_TALK_ENTRY:
638 		font = _vm->_font->getFont(FONT_INTERFACE);
639 		font->setColorMode(id == _highlightedCommandIndex ? SELMODE_HIGHLIGHTED : SELMODE_UNSELECTED);
640 		font->writeString(this, _talkStrings[id], Common::Point(bounds.left, bounds.top));
641 		break;
642 
643 	case CAT_INV_SCROLLER:
644 		font = _vm->_font->getFont(FONT_MISC);
645 
646 		switch (id) {
647 		case 1:
648 			vocabStr = "a";
649 			break;
650 		case 2:
651 			vocabStr = "b";
652 			break;
653 		case 3:
654 			vocabStr = "d";
655 			break;
656 		case 4:
657 			vocabStr = "c";
658 			break;
659 		default:
660 			break;
661 		}
662 
663 		font->setColorMode((id == 4) || (_scrollbarActive == SCROLLBAR_ELEVATOR) ?
664 			SELMODE_HIGHLIGHTED : SELMODE_UNSELECTED);
665 		font->writeString(this, vocabStr, Common::Point(bounds.left, bounds.top));
666 		break;
667 	default:
668 		// Item specific verbs
669 		font = _vm->_font->getFont(FONT_INTERFACE);
670 		vocabId = _vm->_game->_objects.getItem(_selectedInvIndex)._vocabList[id]._vocabId;
671 		if (id == _highlightedItemVocabIndex) {
672 			_vm->_font->setColorMode(SELMODE_HIGHLIGHTED);
673 		} else {
674 			_vm->_font->setColorMode(id == _selectedInvIndex ? SELMODE_SELECTED : SELMODE_UNSELECTED);
675 			vocabStr = scene.getVocab(vocabId);
676 			vocabStr.setChar(toupper(vocabStr[0]), 0);
677 			font->writeString(this, vocabStr, Common::Point(bounds.left, bounds.top));
678 			break;
679 		}
680 		break;
681 	}
682 }
683 
loadElements()684 void UserInterface::loadElements() {
685 	Scene &scene = _vm->_game->_scene;
686 	Common::Rect bounds;
687 	_vm->_game->_screenObjects.clear();
688 
689 	if (_vm->_game->_screenObjects._inputMode == kInputBuildingSentences) {
690 		// Set up screen objects for the inventory scroller
691 		for (int idx = 1; idx <= 3; ++idx) {
692 			getBounds(CAT_INV_SCROLLER, idx, bounds);
693 			moveRect(bounds);
694 
695 			_vm->_game->_screenObjects.add(bounds, SCREENMODE_VGA, CAT_INV_SCROLLER, idx);
696 		}
697 
698 		// Set up actions
699 		_categoryIndexes[CAT_COMMAND - 1] = _vm->_game->_screenObjects.size() + 1;
700 		for (int idx = 0; idx < 10; ++idx) {
701 			getBounds(CAT_COMMAND, idx, bounds);
702 			moveRect(bounds);
703 
704 			_vm->_game->_screenObjects.add(bounds, SCREENMODE_VGA, CAT_COMMAND, idx);
705 		}
706 
707 		// Set up inventory list
708 		_categoryIndexes[CAT_INV_LIST - 1] = _vm->_game->_screenObjects.size() + 1;
709 		for (int idx = 0; idx < 5; ++idx) {
710 			getBounds(CAT_INV_LIST, _inventoryTopIndex + idx, bounds);
711 			moveRect(bounds);
712 
713 			_vm->_game->_screenObjects.add(bounds, SCREENMODE_VGA, CAT_INV_LIST, idx);
714 		}
715 
716 		// Set up the inventory vocab list
717 		_categoryIndexes[CAT_INV_VOCAB - 1] = _vm->_game->_screenObjects.size() + 1;
718 		for (int idx = 0; idx < 5; ++idx) {
719 			getBounds(CAT_INV_VOCAB, idx, bounds);
720 			moveRect(bounds);
721 
722 			_vm->_game->_screenObjects.add(bounds, SCREENMODE_VGA, CAT_INV_VOCAB, idx);
723 		}
724 
725 		// Set up the inventory item picture
726 		_categoryIndexes[CAT_INV_ANIM - 1] = _vm->_game->_screenObjects.size() + 1;
727 		_vm->_game->_screenObjects.add(Common::Rect(160, 159, 231, 194), SCREENMODE_VGA,
728 			CAT_INV_ANIM, 0);
729 	}
730 
731 	if (_vm->_game->_screenObjects._inputMode == kInputBuildingSentences ||
732 			_vm->_game->_screenObjects._inputMode == kInputLimitedSentences) {
733 		_categoryIndexes[CAT_HOTSPOT - 1] = _vm->_game->_screenObjects.size() + 1;
734 		for (int hotspotIdx = scene._hotspots.size() - 1; hotspotIdx >= 0; --hotspotIdx) {
735 			Hotspot &hs = scene._hotspots[hotspotIdx];
736 			ScreenObject *so = _vm->_game->_screenObjects.add(hs._bounds, SCREENMODE_VGA,
737 				CAT_HOTSPOT, hotspotIdx);
738 			so->_active = hs._active;
739 		}
740 	}
741 
742 	if (_vm->_game->_screenObjects._inputMode == kInputConversation) {
743 		// setup areas for talk entries
744 		_categoryIndexes[CAT_TALK_ENTRY - 1] = _vm->_game->_screenObjects.size() + 1;
745 		for (int idx = 0; idx < 5; ++idx) {
746 			getBounds(CAT_TALK_ENTRY, idx, bounds);
747 			moveRect(bounds);
748 
749 			_vm->_game->_screenObjects.add(bounds, SCREENMODE_VGA, CAT_TALK_ENTRY, idx);
750 		}
751 	}
752 
753 	// Store the number of UI elements loaded for easy nuking/refreshing hotspots added later
754 	_vm->_game->_screenObjects._uiCount = _vm->_game->_screenObjects.size();
755 }
756 
getBounds(ScrCategory category,int v,Common::Rect & bounds)757 bool UserInterface::getBounds(ScrCategory category, int v, Common::Rect &bounds) {
758 	int heightMultiplier, widthMultiplier;
759 	int leftStart, yOffset, widthAmt;
760 
761 	switch (category) {
762 	case CAT_COMMAND:
763 		heightMultiplier = v % 5;
764 		widthMultiplier = v / 5;
765 		leftStart = 2;
766 		yOffset = 3;
767 		widthAmt = 32;
768 		break;
769 
770 	case CAT_INV_LIST:
771 		if (v < _inventoryTopIndex || v >= (_inventoryTopIndex + 5))
772 			return false;
773 
774 		heightMultiplier = v - _inventoryTopIndex;
775 		widthMultiplier = 0;
776 		leftStart = 90;
777 		yOffset = 3;
778 		widthAmt = 69;
779 		break;
780 
781 	case CAT_TALK_ENTRY:
782 		heightMultiplier = v;
783 		widthMultiplier = 0;
784 		leftStart = 2;
785 		yOffset = 3;
786 		widthAmt = 310;
787 		break;
788 
789 	case CAT_INV_SCROLLER:
790 		heightMultiplier = 0;
791 		widthMultiplier = 0;
792 		yOffset = 0;
793 		widthAmt = 9;
794 		leftStart = (v != 73) ? 73 : 75;
795 		break;
796 
797 	default:
798 		heightMultiplier = v;
799 		widthMultiplier = 0;
800 		leftStart = 240;
801 		yOffset = 3;
802 		widthAmt = 80;
803 		break;
804 	}
805 
806 	bounds.left = (widthMultiplier > 0) ? widthMultiplier * widthAmt + leftStart : leftStart;
807 	bounds.setWidth(widthAmt);
808 	bounds.top = heightMultiplier * 8 + yOffset;
809 	bounds.setHeight(8);
810 
811 	if (category == CAT_INV_SCROLLER) {
812 		switch (v) {
813 		case SCROLLBAR_UP:
814 			// Arrow up
815 			bounds.top = 4;
816 			bounds.setHeight(7);
817 			break;
818 		case SCROLLBAR_DOWN:
819 			// Arrow down
820 			bounds.top = 35;
821 			bounds.setHeight(7);
822 			break;
823 		case SCROLLBAR_ELEVATOR:
824 			// Scroller
825 			bounds.top = 12;
826 			bounds.setHeight(22);
827 			break;
828 		case SCROLLBAR_THUMB:
829 			// Thumb
830 			bounds.top = _scrollbarElevator + 14;
831 			bounds.setHeight(1);
832 			break;
833 		default:
834 			break;
835 		}
836 	}
837 
838 	return true;
839 }
840 
moveRect(Common::Rect & bounds)841 void UserInterface::moveRect(Common::Rect &bounds) {
842 	bounds.translate(0, MADS_SCENE_HEIGHT);
843 }
844 
drawConversationList()845 void UserInterface::drawConversationList() {
846 	for (uint idx = 0; idx < _talkStrings.size(); ++idx) {
847 		writeVocab(CAT_TALK_ENTRY, idx);
848 	}
849 }
850 
emptyConversationList()851 void UserInterface::emptyConversationList() {
852 	_talkStrings.clear();
853 	_talkIds.clear();
854 }
855 
addConversationMessage(int vocabId,const Common::String & msg)856 void UserInterface::addConversationMessage(int vocabId, const Common::String &msg) {
857 	// Only allow a maximum of 5 talk entries to be displayed
858 	if (_talkStrings.size() < 5) {
859 		_talkStrings.push_back(msg);
860 		_talkIds.push_back(vocabId);
861 	}
862 }
863 
loadInventoryAnim(int objectId)864 void UserInterface::loadInventoryAnim(int objectId) {
865 	Scene &scene = _vm->_game->_scene;
866 	noInventoryAnim();
867 
868 	// WORKAROUND: Even in still mode, we now load the animation frames for the
869 	// object, so we can show the first frame as a 'still'
870 	Common::String resName = Common::String::format("*OB%.3dI", objectId);
871 	SpriteAsset *asset = new SpriteAsset(_vm, resName, ASSET_SPINNING_OBJECT);
872 	_invSpritesIndex = scene._sprites.add(asset, 1);
873 	if (_invSpritesIndex >= 0) {
874 		_invFrameNumber = 1;
875 	}
876 }
877 
noInventoryAnim()878 void UserInterface::noInventoryAnim() {
879 	Scene &scene = _vm->_game->_scene;
880 
881 	if (_invSpritesIndex >= 0) {
882 		scene._sprites.remove(_invSpritesIndex);
883 		_vm->_game->_screenObjects._baseTime = _vm->_events->getFrameCounter();
884 		_invSpritesIndex = -1;
885 	}
886 
887 	if (_vm->_game->_screenObjects._inputMode == kInputBuildingSentences)
888 		refresh();
889 }
890 
refresh()891 void UserInterface::refresh() {
892 	_uiSlots.clear();
893 	_uiSlots.fullRefresh();
894 	_uiSlots.draw(false, false);
895 
896 	drawTextElements();
897 }
898 
inventoryAnim()899 void UserInterface::inventoryAnim() {
900 	Scene &scene = _vm->_game->_scene;
901 	if (_vm->_game->_screenObjects._inputMode == kInputConversation ||
902 			_vm->_game->_screenObjects._inputMode == kInputLimitedSentences ||
903 			_invSpritesIndex < 0)
904 		return;
905 
906 	// WORKAROUND: Fix still inventory display, which was broken in the original
907 	if (_vm->_invObjectsAnimated) {
908 		// Move to the next frame number in the sequence, resetting if at the end
909 		SpriteAsset *asset = scene._sprites[_invSpritesIndex];
910 		if (++_invFrameNumber > asset->getCount())
911 			_invFrameNumber = 1;
912 	}
913 
914 	// Loop through the slots list for inventory animation entry
915 	for (uint i = 0; i < _uiSlots.size(); ++i) {
916 		if (_uiSlots[i]._segmentId == IMG_SPINNING_OBJECT)
917 			_uiSlots[i]._flags = IMG_FULL_UPDATE;
918 	}
919 
920 	// Add a new slot entry for the inventory animation
921 	UISlot slot;
922 	slot._flags = IMG_UPDATE;
923 	slot._segmentId = IMG_SPINNING_OBJECT;
924 	slot._frameNumber = _invFrameNumber;
925 	slot._spritesIndex = _invSpritesIndex;
926 	slot._position = Common::Point(160, 3);
927 
928 	_uiSlots.push_back(slot);
929 }
930 
doBackgroundAnimation()931 void UserInterface::doBackgroundAnimation() {
932 	Scene &scene = _vm->_game->_scene;
933 	Common::Array<AnimUIEntry> &uiEntries = scene._animationData->_uiEntries;
934 	Common::Array<AnimFrameEntry> &frameEntries = scene._animationData->_frameEntries;
935 
936 	_noSegmentsActive = !_someSegmentsActive;
937 	_someSegmentsActive = false;
938 
939 	for (int idx = 0; idx < (int)uiEntries.size(); ++idx) {
940 		AnimUIEntry &uiEntry = uiEntries[idx];
941 
942 		if (uiEntry._counter < 0) {
943 			if (uiEntry._counter == -1) {
944 				int probabilityRandom = _vm->getRandomNumber(1, 30000);
945 				int probability = uiEntry._probability;
946 				if (uiEntry._probability > 30000) {
947 					if (_noSegmentsActive) {
948 						probability -= 30000;
949 					} else {
950 						probability = -1;
951 					}
952 				}
953 				if (probabilityRandom <= probability) {
954 					uiEntry._counter = uiEntry._firstImage;
955 					_someSegmentsActive = true;
956 				}
957 			} else {
958 				uiEntry._counter = uiEntry._firstImage;
959 				_someSegmentsActive = true;
960 			}
961 		} else {
962 			for (int idx2 = 0; idx2 < ANIM_SPAWN_COUNT; idx2++) {
963 				if (uiEntry._spawnFrame[idx2] == (uiEntry._counter - uiEntry._firstImage)) {
964 					int tempIndex = uiEntry._spawn[idx2];
965 					if (idx >= tempIndex) {
966 						uiEntries[tempIndex]._counter = uiEntries[tempIndex]._firstImage;
967 					} else {
968 						uiEntries[tempIndex]._counter = -2;
969 					}
970 					_someSegmentsActive = true;
971 				}
972 			}
973 
974 			++uiEntry._counter;
975 			if (uiEntry._counter > uiEntry._lastImage) {
976 				uiEntry._counter = -1;
977 			} else {
978 				_someSegmentsActive = true;
979 			}
980 		}
981 	}
982 
983 	for (uint idx = 0; idx < uiEntries.size(); ++idx) {
984 		int imgScan = uiEntries[idx]._counter;
985 		if (imgScan >= 0) {
986 			_uiSlots.add(frameEntries[imgScan]);
987 		}
988 	}
989 }
990 
categoryChanged()991 void UserInterface::categoryChanged() {
992 	_highlightedInvIndex = -1;
993 	_vm->_events->initVars();
994 	_category = CAT_NONE;
995 }
996 
selectObject(int invIndex)997 void UserInterface::selectObject(int invIndex) {
998 	if (_selectedInvIndex != invIndex || _inventoryChanged) {
999 		int oldVocabCount = _selectedInvIndex < 0 ? 0 : _vm->_game->_objects.getItem(_selectedInvIndex)._vocabCount;
1000 		int newVocabCount = invIndex < 0 ? 0 : _vm->_game->_objects.getItem(invIndex)._vocabCount;
1001 		int maxVocab = MAX(oldVocabCount, newVocabCount);
1002 
1003 		updateSelection(CAT_INV_LIST, invIndex, &_selectedInvIndex);
1004 		_highlightedItemVocabIndex = -1;
1005 		_selectedItemVocabIdx = -1;
1006 
1007 		if (maxVocab) {
1008 			assert(_uiSlots.size() < 50);
1009 			int vocabHeight = maxVocab * 8;
1010 
1011 			Common::Rect bounds(240, 3, 240 + 80, 3 + vocabHeight);
1012 			_uiSlots.add(bounds);
1013 			_uiSlots.draw(false, false);
1014 			drawItemVocabList();
1015 			//updateRect(bounds);
1016 		}
1017 	}
1018 
1019 	if (invIndex == -1) {
1020 		noInventoryAnim();
1021 	} else {
1022 		loadInventoryAnim(_vm->_game->_objects._inventoryList[invIndex]);
1023 		_vm->_palette->setPalette(&_vm->_palette->_mainPalette[7 * 3], 7, 1);
1024 		_vm->_palette->setPalette(&_vm->_palette->_mainPalette[246 * 3], 246, 2);
1025 	}
1026 }
1027 
updateSelection(ScrCategory category,int newIndex,int * idx)1028 void UserInterface::updateSelection(ScrCategory category, int newIndex, int *idx) {
1029 	Game &game = *_vm->_game;
1030 	Common::Array<int> &invList = game._objects._inventoryList;
1031 	Common::Rect bounds;
1032 
1033 	if (category == CAT_INV_LIST && _inventoryChanged) {
1034 		*idx = newIndex;
1035 		bounds = Common::Rect(90, 3, 90 + 69, 3 + 40);
1036 		_uiSlots.add(bounds);
1037 		_uiSlots.draw(false, false);
1038 		drawInventoryList();
1039 		//updateRect(bounds);
1040 		_inventoryChanged = false;
1041 
1042 		if (invList.size() < 2) {
1043 			_scrollbarElevator = 0;
1044 		} else {
1045 			int v = _inventoryTopIndex * 18 / (invList.size() - 1);
1046 			_scrollbarElevator = MIN(v, 17);
1047 		}
1048 	} else {
1049 		int oldIndex = *idx;
1050 		*idx = newIndex;
1051 
1052 		if (oldIndex >= 0) {
1053 			writeVocab(category, oldIndex);
1054 
1055 /*			if (getBounds(category, oldIndex, bounds))
1056 				updateRect(bounds); */
1057 		}
1058 
1059 		if (newIndex >= 0) {
1060 			writeVocab(category, newIndex);
1061 
1062 /*			if (getBounds(category, newIndex, bounds))
1063 				updateRect(bounds); */
1064 		}
1065 	}
1066 }
1067 
scrollerChanged()1068 void UserInterface::scrollerChanged() {
1069 	warning("TODO: scrollerChanged");
1070 }
1071 
scrollInventory()1072 void UserInterface::scrollInventory() {
1073 	Common::Array<int> &invList = _vm->_game->_objects._inventoryList;
1074 
1075 	if (_vm->_events->_mouseButtons) {
1076 		int yp = _vm->_events->currentPos().y;
1077 		if (yp < MADS_SCENE_HEIGHT || yp == (MADS_SCREEN_HEIGHT - 1)) {
1078 			uint32 timeDiff = _scrollFlag ? 100 : 380;
1079 			uint32 currentMilli = g_system->getMillis();
1080 			_vm->_game->_screenObjects._v8332A = -1;
1081 
1082 			if (currentMilli >= (_scrollMilli + timeDiff)) {
1083 				_scrollMilli = currentMilli;
1084 				_scrollFlag = true;
1085 
1086 				if (yp == (MADS_SCREEN_HEIGHT - 1)) {
1087 					if (_inventoryTopIndex < ((int)invList.size() - 1)) {
1088 						++_inventoryTopIndex;
1089 						_inventoryChanged = true;
1090 					}
1091 				} else {
1092 					if (_inventoryTopIndex > 0) {
1093 						--_inventoryTopIndex;
1094 						_inventoryChanged = true;
1095 					}
1096 				}
1097 			}
1098 		}
1099 	}
1100 
1101 	_vm->_game->_screenObjects._v8332A = 0;
1102 }
1103 
synchronize(Common::Serializer & s)1104 void UserInterface::synchronize(Common::Serializer &s) {
1105 	InventoryObjects &invObjects = _vm->_game->_objects;
1106 
1107 	if (s.isLoading()) {
1108 		_selectedInvIndex = invObjects._inventoryList.empty() ? -1 : 0;
1109 	}
1110 
1111 	for (int i = 0; i < 8; ++i)
1112 		s.syncAsSint16LE(_categoryIndexes[i]);
1113 }
1114 
1115 } // End of namespace MADS
1116