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