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 * Additional copyright for this file:
8 * Copyright (C) 1994-1998 Revolution Software Ltd.
9 *
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU General Public License
12 * as published by the Free Software Foundation; either version 2
13 * of the License, or (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 */
24
25 // ---------------------------------------------------------------------------
26 // BUILD_DISPLAY.CPP like the old spr_engi but slightly more aptly named
27 // ---------------------------------------------------------------------------
28
29
30 #include "common/system.h"
31 #include "common/events.h"
32 #include "common/textconsole.h"
33
34 #include "sword2/sword2.h"
35 #include "sword2/defs.h"
36 #include "sword2/header.h"
37 #include "sword2/console.h"
38 #include "sword2/logic.h"
39 #include "sword2/maketext.h"
40 #include "sword2/mouse.h"
41 #include "sword2/object.h"
42 #include "sword2/resman.h"
43 #include "sword2/screen.h"
44 #include "sword2/sound.h"
45
46 namespace Sword2 {
47
Screen(Sword2Engine * vm,int16 width,int16 height)48 Screen::Screen(Sword2Engine *vm, int16 width, int16 height) {
49 _vm = vm;
50
51 _dirtyGrid = _buffer = NULL;
52
53 _screenWide = width;
54 _screenDeep = height;
55
56 _gridWide = width / CELLWIDE;
57 _gridDeep = height / CELLDEEP;
58
59 if ((width % CELLWIDE) || (height % CELLDEEP))
60 error("Bad cell size");
61
62 _dirtyGrid = (byte *)calloc(_gridWide, _gridDeep);
63 if (!_dirtyGrid)
64 error("Could not initialize dirty grid");
65
66 _buffer = (byte *)malloc(width * height);
67 if (!_buffer)
68 error("Could not initialize display");
69
70 for (int i = 0; i < ARRAYSIZE(_blockSurfaces); i++)
71 _blockSurfaces[i] = NULL;
72
73 _lightMask = NULL;
74 _needFullRedraw = false;
75
76 memset(&_thisScreen, 0, sizeof(_thisScreen));
77
78 _fps = 0;
79 _frameCount = 0;
80 _cycleTime = 0;
81
82 _lastPaletteRes = 0;
83
84 _scrollFraction = 16;
85
86 _largestLayerArea = 0;
87 _largestSpriteArea = 0;
88
89 strcpy(_largestLayerInfo, "largest layer: none registered");
90 strcpy(_largestSpriteInfo, "largest sprite: none registered");
91
92 _fadeStatus = RDFADE_NONE;
93 _renderAverageTime = 60;
94
95 _layer = 0;
96
97 _dimPalette = false;
98
99 _pauseTicks = 0;
100 _pauseStartTick = 0;
101
102 // Clean the cache for PSX version SCREENS.CLU
103 _psxScrCache[0] = NULL;
104 _psxScrCache[1] = NULL;
105 _psxScrCache[2] = NULL;
106 _psxCacheEnabled[0] = true;
107 _psxCacheEnabled[1] = true;
108 _psxCacheEnabled[2] = true;
109 }
110
~Screen()111 Screen::~Screen() {
112 flushPsxScrCache();
113 free(_buffer);
114 free(_dirtyGrid);
115 closeBackgroundLayer();
116 free(_lightMask);
117 }
118
getTick()119 uint32 Screen::getTick() {
120 return _vm->getMillis() - _pauseTicks;
121 }
122
pauseScreen(bool pause)123 void Screen::pauseScreen(bool pause) {
124 if (pause) {
125 _pauseStartTick = _vm->_system->getMillis();
126 } else {
127 _pauseTicks += (_vm->_system->getMillis() - _pauseStartTick);
128 }
129 }
130
131 /**
132 * @return the graphics detail setting
133 */
134
getRenderLevel()135 int8 Screen::getRenderLevel() {
136 return _renderLevel;
137 }
138
setRenderLevel(int8 level)139 void Screen::setRenderLevel(int8 level) {
140 _renderLevel = level;
141
142 switch (_renderLevel) {
143 case 0:
144 // Lowest setting: no fancy stuff
145 _renderCaps = 0;
146 break;
147 case 1:
148 // Medium-low setting: transparency-blending
149 _renderCaps = RDBLTFX_SPRITEBLEND;
150 break;
151 case 2:
152 // Medium-high setting: transparency-blending + shading
153 _renderCaps = RDBLTFX_SPRITEBLEND | RDBLTFX_SHADOWBLEND;
154 break;
155 case 3:
156 // Highest setting: transparency-blending + shading +
157 // edge-blending + improved stretching
158 _renderCaps = RDBLTFX_SPRITEBLEND | RDBLTFX_SHADOWBLEND | RDBLTFX_EDGEBLEND;
159 break;
160 }
161 }
162
163 /**
164 * Tell updateDisplay() that the scene needs to be completely updated.
165 */
166
setNeedFullRedraw()167 void Screen::setNeedFullRedraw() {
168 _needFullRedraw = true;
169 }
170
171 /**
172 * Mark an area of the screen as dirty, first generation.
173 */
174
markAsDirty(int16 x0,int16 y0,int16 x1,int16 y1)175 void Screen::markAsDirty(int16 x0, int16 y0, int16 x1, int16 y1) {
176 int16 gridX0 = x0 / CELLWIDE;
177 int16 gridY0 = y0 / CELLDEEP;
178 int16 gridX1 = x1 / CELLWIDE;
179 int16 gridY1 = y1 / CELLDEEP;
180
181 for (int16 i = gridY0; i <= gridY1; i++)
182 for (int16 j = gridX0; j <= gridX1; j++)
183 _dirtyGrid[i * _gridWide + j] = 2;
184 }
185
186 /**
187 * This function has two purposes: It redraws the scene, and it handles input
188 * events, palette fading, etc. It should be called at a high rate (> 20 per
189 * second), but the scene is usually only redrawn about 12 times per second,
190 * except when then screen is scrolling.
191 *
192 * @param redrawScene If true, redraw the scene.
193 */
194
updateDisplay(bool redrawScene)195 void Screen::updateDisplay(bool redrawScene) {
196 _vm->parseInputEvents();
197 fadeServer();
198
199 if (redrawScene) {
200 int i;
201
202 // Note that the entire scene is always rendered, which is less
203 // than optimal, but at least we can try to be intelligent
204 // about updating the screen afterwards.
205
206 if (_needFullRedraw) {
207 // Update the entire screen. This is necessary when
208 // scrolling, fading, etc.
209
210 _vm->_system->copyRectToScreen(_buffer + MENUDEEP * _screenWide, _screenWide, 0, MENUDEEP, _screenWide, _screenDeep - 2 * MENUDEEP);
211 _needFullRedraw = false;
212 } else {
213 // Update only the dirty areas of the screen
214
215 int j, x, y;
216 int stripWide;
217
218 for (i = 0; i < _gridDeep; i++) {
219 stripWide = 0;
220
221 for (j = 0; j < _gridWide; j++) {
222 if (_dirtyGrid[i * _gridWide + j]) {
223 stripWide++;
224 } else if (stripWide) {
225 x = CELLWIDE * (j - stripWide);
226 y = CELLDEEP * i;
227 _vm->_system->copyRectToScreen(_buffer + y * _screenWide + x, _screenWide, x, y, stripWide * CELLWIDE, CELLDEEP);
228 stripWide = 0;
229 }
230 }
231
232 if (stripWide) {
233 x = CELLWIDE * (j - stripWide);
234 y = CELLDEEP * i;
235 _vm->_system->copyRectToScreen(_buffer + y * _screenWide + x, _screenWide, x, y, stripWide * CELLWIDE, CELLDEEP);
236 stripWide = 0;
237 }
238 }
239 }
240
241 // Age the dirty cells one generation. This way we keep track
242 // of both the cells that were updated this time, and the ones
243 // that were updated the last time.
244
245 for (i = 0; i < _gridWide * _gridDeep; i++)
246 _dirtyGrid[i] >>= 1;
247 }
248
249 // We always need to update because of fades, menu animations, etc.
250 _vm->_system->updateScreen();
251 }
252
253 /**
254 * Fill the screen buffer with palette color zero. Note that it does not
255 * touch the menu areas of the screen.
256 */
257
clearScene()258 void Screen::clearScene() {
259 memset(_buffer + MENUDEEP * _screenWide, 0, _screenWide * RENDERDEEP);
260 _needFullRedraw = true;
261 }
262
buildDisplay()263 void Screen::buildDisplay() {
264 if (_thisScreen.new_palette) {
265 // start the layer palette fading up
266 startNewPalette();
267
268 // should be reset to zero at start of each screen change
269 _largestLayerArea = 0;
270 _largestSpriteArea = 0;
271 }
272
273 // Does this ever happen?
274 if (!_thisScreen.background_layer_id)
275 return;
276
277 // there is a valid screen to run
278
279 setScrollTarget(_thisScreen.scroll_offset_x, _thisScreen.scroll_offset_y);
280 _vm->_mouse->animateMouse();
281 startRenderCycle();
282
283 byte *file = _vm->_resman->openResource(_thisScreen.background_layer_id);
284
285 MultiScreenHeader screenLayerTable;
286 memset(&screenLayerTable, 0, sizeof(screenLayerTable));
287
288 if (!Sword2Engine::isPsx()) // On PSX version, there would be nothing to read here
289 screenLayerTable.read(file + ResHeader::size());
290
291 // Render at least one frame, but if the screen is scrolling, and if
292 // there is time left, we will render extra frames to smooth out the
293 // scrolling.
294
295 do {
296 // first background parallax + related anims
297 if (Sword2Engine::isPsx() || screenLayerTable.bg_parallax[0]) { // No need to check on PSX version
298 renderParallax(_vm->fetchBackgroundParallaxLayer(file, 0), 0);
299 drawBackPar0Frames();
300 }
301
302 // second background parallax + related anims
303 if (!Sword2Engine::isPsx() && screenLayerTable.bg_parallax[1]) { // Nothing here in PSX version
304 renderParallax(_vm->fetchBackgroundParallaxLayer(file, 1), 1);
305 drawBackPar1Frames();
306 }
307
308 // normal backround layer (just the one!)
309 renderParallax(_vm->fetchBackgroundLayer(file), 2);
310
311 // sprites & layers
312 drawBackFrames(); // background sprites
313 drawSortFrames(file); // sorted sprites & layers
314 drawForeFrames(); // foreground sprites
315
316 // first foreground parallax + related anims
317
318 if (Sword2Engine::isPsx() || screenLayerTable.fg_parallax[0]) {
319 renderParallax(_vm->fetchForegroundParallaxLayer(file, 0), 3);
320 drawForePar0Frames();
321 }
322
323 // second foreground parallax + related anims
324
325 if (!Sword2Engine::isPsx() && screenLayerTable.fg_parallax[1]) {
326 renderParallax(_vm->fetchForegroundParallaxLayer(file, 1), 4);
327 drawForePar1Frames();
328 }
329
330 _vm->_debugger->drawDebugGraphics();
331 _vm->_fontRenderer->printTextBlocs();
332 _vm->_mouse->processMenu();
333
334 updateDisplay();
335
336 _frameCount++;
337 if (getTick() > _cycleTime) {
338 _fps = _frameCount;
339 _frameCount = 0;
340 _cycleTime = getTick() + 1000;
341 }
342 } while (!endRenderCycle());
343
344 _vm->_resman->closeResource(_thisScreen.background_layer_id);
345
346 }
347
348 /**
349 * Fades down and displays a message on the screen.
350 * @param text The message
351 * @param time The number of seconds to display the message, or 0 to display it
352 * until the user clicks the mouse or presses a key.
353 */
354
displayMsg(byte * text,int time)355 void Screen::displayMsg(byte *text, int time) {
356 byte pal[256 * 3];
357 byte oldPal[256 * 3];
358
359 debug(2, "DisplayMsg: %s", text);
360
361 if (getFadeStatus() != RDFADE_BLACK) {
362 fadeDown();
363 waitForFade();
364 }
365
366 _vm->_mouse->setMouse(0);
367 _vm->_mouse->setLuggage(0);
368 _vm->_mouse->closeMenuImmediately();
369
370 clearScene();
371
372 byte *text_spr = _vm->_fontRenderer->makeTextSprite(text, 640, 187, _vm->_speechFontId);
373
374 FrameHeader frame;
375
376 frame.read(text_spr);
377
378 SpriteInfo spriteInfo;
379
380 spriteInfo.x = _screenWide / 2 - frame.width / 2;
381 if (!time)
382 spriteInfo.y = _screenDeep / 2 - frame.height / 2 - MENUDEEP;
383 else
384 spriteInfo.y = 400 - frame.height;
385 spriteInfo.w = frame.width;
386 spriteInfo.h = frame.height;
387 spriteInfo.scale = 0;
388 spriteInfo.scaledWidth = 0;
389 spriteInfo.scaledHeight = 0;
390 spriteInfo.type = RDSPR_DISPLAYALIGN | RDSPR_NOCOMPRESSION | RDSPR_TRANS;
391 spriteInfo.blend = 0;
392 spriteInfo.data = text_spr + FrameHeader::size();
393 spriteInfo.colorTable = 0;
394 spriteInfo.isText = true;
395
396 uint32 rv = drawSprite(&spriteInfo);
397 if (rv)
398 error("Driver Error %.8x (in DisplayMsg)", rv);
399
400 memcpy(oldPal, _palette, sizeof(oldPal));
401 memset(pal, 0, sizeof(pal));
402
403 pal[187 * 3 + 0] = 255;
404 pal[187 * 3 + 1] = 255;
405 pal[187 * 3 + 2] = 255;
406
407 setPalette(0, 256, pal, RDPAL_FADE);
408 fadeUp();
409 free(text_spr);
410 waitForFade();
411
412 if (time > 0) {
413 uint32 targetTime = _vm->_system->getMillis() + (time * 1000);
414 _vm->sleepUntil(targetTime);
415 } else {
416 while (!_vm->shouldQuit()) {
417 MouseEvent *me = _vm->mouseEvent();
418 if (me && (me->buttons & (RD_LEFTBUTTONDOWN | RD_RIGHTBUTTONDOWN)))
419 break;
420
421 if (_vm->keyboardEvent())
422 break;
423
424 updateDisplay();
425 _vm->_system->delayMillis(50);
426 }
427 }
428
429 fadeDown();
430 waitForFade();
431 clearScene();
432 setPalette(0, 256, oldPal, RDPAL_FADE);
433 fadeUp();
434 }
435
drawBackPar0Frames()436 void Screen::drawBackPar0Frames() {
437 // frame attached to 1st background parallax
438 for (uint i = 0; i < _curBgp0; i++)
439 processImage(&_bgp0List[i]);
440 }
441
drawBackPar1Frames()442 void Screen::drawBackPar1Frames() {
443 // frame attached to 2nd background parallax
444 for (uint i = 0; i < _curBgp1; i++)
445 processImage(&_bgp1List[i]);
446 }
447
drawBackFrames()448 void Screen::drawBackFrames() {
449 // background sprite, fixed to main background
450 for (uint i = 0; i < _curBack; i++)
451 processImage(&_backList[i]);
452 }
453
drawSortFrames(byte * file)454 void Screen::drawSortFrames(byte *file) {
455 uint i, j;
456
457 // Sort the sort list. Used to be a separate function, but it was only
458 // called once, right before calling drawSortFrames().
459
460 if (_curSort > 1) {
461 for (i = 0; i < _curSort - 1; i++) {
462 for (j = 0; j < _curSort - 1; j++) {
463 if (_sortList[_sortOrder[j]].sort_y > _sortList[_sortOrder[j + 1]].sort_y) {
464 SWAP(_sortOrder[j], _sortOrder[j + 1]);
465 }
466 }
467 }
468 }
469
470 // Draw the sorted frames - layers, shrinkers & normal flat sprites
471
472 for (i = 0; i < _curSort; i++) {
473 if (_sortList[_sortOrder[i]].layer_number) {
474 // it's a layer - minus 1 for true layer number
475 // we need to know from the BuildUnit because the
476 // layers will have been sorted in random order
477 processLayer(file, _sortList[_sortOrder[i]].layer_number - 1);
478 } else {
479 // it's a sprite
480 processImage(&_sortList[_sortOrder[i]]);
481 }
482 }
483 }
484
drawForeFrames()485 void Screen::drawForeFrames() {
486 // foreground sprite, fixed to main background
487 for (uint i = 0; i < _curFore; i++)
488 processImage(&_foreList[i]);
489 }
490
drawForePar0Frames()491 void Screen::drawForePar0Frames() {
492 // frame attached to 1st foreground parallax
493 for (uint i = 0; i < _curFgp0; i++)
494 processImage(&_fgp0List[i]);
495 }
496
drawForePar1Frames()497 void Screen::drawForePar1Frames() {
498 // frame attached to 2nd foreground parallax
499 for (uint i = 0; i < _curFgp1; i++)
500 processImage(&_fgp1List[i]);
501 }
502
processLayer(byte * file,uint32 layer_number)503 void Screen::processLayer(byte *file, uint32 layer_number) {
504
505 LayerHeader layer_head;
506
507 layer_head.read(_vm->fetchLayerHeader(file, layer_number));
508
509 SpriteInfo spriteInfo;
510
511 spriteInfo.x = layer_head.x;
512 spriteInfo.y = layer_head.y;
513 spriteInfo.w = layer_head.width;
514 spriteInfo.scale = 0;
515 spriteInfo.scaledWidth = 0;
516 spriteInfo.scaledHeight = 0;
517 spriteInfo.h = layer_head.height;
518 spriteInfo.isText = false;
519
520 // Layers are uncompressed in PSX version, RLE256 compressed
521 // in PC version.
522 if (Sword2Engine::isPsx()) {
523 spriteInfo.type = RDSPR_TRANS | RDSPR_NOCOMPRESSION;
524 spriteInfo.data = file + layer_head.offset;
525 } else {
526 spriteInfo.type = RDSPR_TRANS | RDSPR_RLE256FAST;
527 spriteInfo.data = file + ResHeader::size() + layer_head.offset;
528 }
529
530 spriteInfo.blend = 0;
531 spriteInfo.colorTable = 0;
532
533 // check for largest layer for debug info
534
535 uint32 current_layer_area = layer_head.width * layer_head.height;
536
537 if (current_layer_area > _largestLayerArea) {
538 _largestLayerArea = current_layer_area;
539 sprintf(_largestLayerInfo,
540 "largest layer: %s layer(%d) is %dx%d",
541 _vm->_resman->fetchName(_thisScreen.background_layer_id),
542 layer_number, layer_head.width, layer_head.height);
543 }
544
545 uint32 rv = drawSprite(&spriteInfo);
546 if (rv)
547 error("Driver Error %.8x in processLayer(%d)", rv, layer_number);
548 }
549
processImage(BuildUnit * build_unit)550 void Screen::processImage(BuildUnit *build_unit) {
551
552 // We have some problematic animation frames in PSX demo (looks like there is missing data),
553 // so we just skip them.
554 if ( (Sword2Engine::isPsx() && _vm->_logic->readVar(DEMO)) &&
555 ((build_unit->anim_resource == 369 && build_unit->anim_pc == 0) ||
556 (build_unit->anim_resource == 296 && build_unit->anim_pc == 5) ||
557 (build_unit->anim_resource == 534 && build_unit->anim_pc == 13)) )
558 return;
559
560 byte *file = _vm->_resman->openResource(build_unit->anim_resource);
561 byte *colTablePtr = NULL;
562
563 byte *frame = _vm->fetchFrameHeader(file, build_unit->anim_pc);
564
565 AnimHeader anim_head;
566 CdtEntry cdt_entry;
567 FrameHeader frame_head;
568
569 anim_head.read(_vm->fetchAnimHeader(file));
570 cdt_entry.read(_vm->fetchCdtEntry(file, build_unit->anim_pc));
571 frame_head.read(frame);
572
573 // so that 0-color is transparent
574 uint32 spriteType = RDSPR_TRANS;
575
576 if (anim_head.blend)
577 spriteType |= RDSPR_BLEND;
578
579 // if the frame is to be flipped (only really applicable to frames
580 // using offsets)
581 if (cdt_entry.frameType & FRAME_FLIPPED)
582 spriteType |= RDSPR_FLIP;
583
584 if (cdt_entry.frameType & FRAME_256_FAST) {
585 // scaling, shading & blending don't work with RLE256FAST
586 // but the same compression can be decompressed using the
587 // RLE256 routines!
588
589 // NOTE: If this restriction refers to drawSprite(), I don't
590 // think we have it any more. But I'm not sure.
591
592 if (build_unit->scale || anim_head.blend || build_unit->shadingFlag)
593 spriteType |= RDSPR_RLE256;
594 else
595 spriteType |= RDSPR_RLE256FAST;
596 } else {
597 switch (anim_head.runTimeComp) {
598 case NONE:
599 spriteType |= RDSPR_NOCOMPRESSION;
600 break;
601 case RLE256:
602 spriteType |= RDSPR_RLE256;
603 break;
604 case RLE16:
605 spriteType |= RDSPR_RLE16;
606 // points to just after last cdt_entry, ie.
607 // start of color table
608 colTablePtr = _vm->fetchAnimHeader(file) + AnimHeader::size() + anim_head.noAnimFrames * CdtEntry::size();
609 if (Sword2Engine::isPsx())
610 colTablePtr++; // There is one additional byte to skip before the table in psx version
611 break;
612 }
613 }
614
615 // if we want this frame to be affected by the shading mask,
616 // add the status bit
617 if (build_unit->shadingFlag)
618 spriteType |= RDSPR_SHADOW;
619
620 SpriteInfo spriteInfo;
621
622 spriteInfo.x = build_unit->x;
623 spriteInfo.y = build_unit->y;
624 spriteInfo.w = frame_head.width;
625 spriteInfo.h = frame_head.height;
626 spriteInfo.scale = build_unit->scale;
627 spriteInfo.scaledWidth = build_unit->scaled_width;
628 spriteInfo.scaledHeight = build_unit->scaled_height;
629 spriteInfo.type = spriteType;
630 spriteInfo.blend = anim_head.blend;
631 // points to just after frame header, ie. start of sprite data
632 spriteInfo.data = frame + FrameHeader::size();
633 spriteInfo.colorTable = colTablePtr;
634 spriteInfo.isText = false;
635
636 // check for largest layer for debug info
637 uint32 current_sprite_area = frame_head.width * frame_head.height;
638
639 if (current_sprite_area > _largestSpriteArea) {
640 _largestSpriteArea = current_sprite_area;
641 sprintf(_largestSpriteInfo,
642 "largest sprite: %s frame(%d) is %dx%d",
643 _vm->_resman->fetchName(build_unit->anim_resource),
644 build_unit->anim_pc,
645 frame_head.width,
646 frame_head.height);
647 }
648
649 if (_vm->_logic->readVar(SYSTEM_TESTING_ANIMS)) { // see anims.cpp
650 // bring the anim into the visible screen
651 // but leave extra pixel at edge for box
652 if (spriteInfo.x + spriteInfo.scaledWidth >= 639)
653 spriteInfo.x = 639 - spriteInfo.scaledWidth;
654
655 if (spriteInfo.y + spriteInfo.scaledHeight >= 399)
656 spriteInfo.y = 399 - spriteInfo.scaledHeight;
657
658 if (spriteInfo.x < 1)
659 spriteInfo.x = 1;
660
661 if (spriteInfo.y < 1)
662 spriteInfo.y = 1;
663
664 // create box to surround sprite - just outside sprite box
665 _vm->_debugger->_rectX1 = spriteInfo.x - 1;
666 _vm->_debugger->_rectY1 = spriteInfo.y - 1;
667 _vm->_debugger->_rectX2 = spriteInfo.x + spriteInfo.scaledWidth;
668 _vm->_debugger->_rectY2 = spriteInfo.y + spriteInfo.scaledHeight;
669 }
670
671 uint32 rv = drawSprite(&spriteInfo);
672 if (rv) {
673 error("Driver Error %.8x with sprite %s (%d, %d) in processImage",
674 rv,
675 _vm->_resman->fetchName(build_unit->anim_resource),
676 build_unit->anim_resource, build_unit->anim_pc);
677 }
678
679 // release the anim resource
680 _vm->_resman->closeResource(build_unit->anim_resource);
681 }
682
resetRenderLists()683 void Screen::resetRenderLists() {
684 // reset the sort lists - do this before a logic loop
685 // takes into account the fact that the start of the list is pre-built
686 // with the special sortable layers
687
688 _curBgp0 = 0;
689 _curBgp1 = 0;
690 _curBack = 0;
691 // beginning of sort list is setup with the special sort layers
692 _curSort = _thisScreen.number_of_layers;
693 _curFore = 0;
694 _curFgp0 = 0;
695 _curFgp1 = 0;
696
697 if (_curSort) {
698 // there are some layers - so rebuild the sort order list
699 for (uint i = 0; i < _curSort; i++)
700 _sortOrder[i] = i;
701 }
702 }
703
registerFrame(byte * ob_mouse,byte * ob_graph,byte * ob_mega,BuildUnit * build_unit)704 void Screen::registerFrame(byte *ob_mouse, byte *ob_graph, byte *ob_mega, BuildUnit *build_unit) {
705 ObjectGraphic obGraph(ob_graph);
706 ObjectMega obMega(ob_mega);
707
708 assert(obGraph.getAnimResource());
709
710 byte *file = _vm->_resman->openResource(obGraph.getAnimResource());
711
712 AnimHeader anim_head;
713 CdtEntry cdt_entry;
714 FrameHeader frame_head;
715
716 anim_head.read(_vm->fetchAnimHeader(file));
717 cdt_entry.read(_vm->fetchCdtEntry(file, obGraph.getAnimPc()));
718 frame_head.read(_vm->fetchFrameHeader(file, obGraph.getAnimPc()));
719
720 // update player graphic details for on-screen debug info
721 if (_vm->_logic->readVar(ID) == CUR_PLAYER_ID) {
722 _vm->_debugger->_graphType = obGraph.getType();
723 _vm->_debugger->_graphAnimRes = obGraph.getAnimResource();
724 // counting 1st frame as 'frame 1'
725 _vm->_debugger->_graphAnimPc = obGraph.getAnimPc() + 1;
726 _vm->_debugger->_graphNoFrames = anim_head.noAnimFrames;
727 }
728
729 // fill in the BuildUnit structure for this frame
730
731 build_unit->anim_resource = obGraph.getAnimResource();
732 build_unit->anim_pc = obGraph.getAnimPc();
733 build_unit->layer_number = 0;
734
735 // Affected by shading mask?
736 if (obGraph.getType() & SHADED_SPRITE)
737 build_unit->shadingFlag = true;
738 else
739 build_unit->shadingFlag = false;
740
741 // Check if this frame has offsets ie. this is a scalable mega frame
742
743 int scale = 0;
744
745 if (cdt_entry.frameType & FRAME_OFFSET) {
746 scale = obMega.calcScale();
747
748 // calc final render coordinates (top-left of sprite), based
749 // on feet coords & scaled offsets
750
751 // add scaled offsets to feet coords
752 build_unit->x = obMega.getFeetX() + (cdt_entry.x * scale) / 256;
753 build_unit->y = obMega.getFeetY() + (cdt_entry.y * scale) / 256;
754
755 // Work out new width and height. Always divide by 256 after
756 // everything else, to maintain accurary
757 build_unit->scaled_width = ((scale * frame_head.width) / 256);
758 build_unit->scaled_height = ((scale * frame_head.height) / 256);
759 } else {
760 // It's a non-scaling anim. Get render coords for sprite, from cdt
761 build_unit->x = cdt_entry.x;
762 build_unit->y = cdt_entry.y;
763
764 // Get width and height
765 build_unit->scaled_width = frame_head.width;
766 build_unit->scaled_height = frame_head.height;
767 }
768
769 // either 0 or required scale, depending on whether 'scale' computed
770 build_unit->scale = scale;
771
772 // calc the bottom y-coord for sorting purposes
773 build_unit->sort_y = build_unit->y + build_unit->scaled_height - 1;
774
775 if (ob_mouse) {
776 // passed a mouse structure, so add to the _mouseList
777 _vm->_mouse->registerMouse(ob_mouse, build_unit);
778
779 }
780
781 _vm->_resman->closeResource(obGraph.getAnimResource());
782 }
783
registerFrame(byte * ob_mouse,byte * ob_graph,byte * ob_mega)784 void Screen::registerFrame(byte *ob_mouse, byte *ob_graph, byte *ob_mega) {
785 ObjectGraphic obGraph(ob_graph);
786
787 // check low word for sprite type
788 switch (obGraph.getType() & 0x0000ffff) {
789 case BGP0_SPRITE:
790 assert(_curBgp0 < MAX_bgp0_sprites);
791 registerFrame(ob_mouse, ob_graph, ob_mega, &_bgp0List[_curBgp0]);
792 _curBgp0++;
793 break;
794 case BGP1_SPRITE:
795 assert(_curBgp1 < MAX_bgp1_sprites);
796 registerFrame(ob_mouse, ob_graph, ob_mega, &_bgp1List[_curBgp1]);
797 _curBgp1++;
798 break;
799 case BACK_SPRITE:
800 assert(_curBack < MAX_back_sprites);
801 registerFrame(ob_mouse, ob_graph, ob_mega, &_backList[_curBack]);
802 _curBack++;
803 break;
804 case SORT_SPRITE:
805 assert(_curSort < MAX_sort_sprites);
806 _sortOrder[_curSort] = _curSort;
807 registerFrame(ob_mouse, ob_graph, ob_mega, &_sortList[_curSort]);
808 _curSort++;
809 break;
810 case FORE_SPRITE:
811 assert(_curFore < MAX_fore_sprites);
812 registerFrame(ob_mouse, ob_graph, ob_mega, &_foreList[_curFore]);
813 _curFore++;
814 break;
815 case FGP0_SPRITE:
816 assert(_curFgp0 < MAX_fgp0_sprites);
817 registerFrame(ob_mouse, ob_graph, ob_mega, &_fgp0List[_curFgp0]);
818 _curFgp0++;
819 break;
820 case FGP1_SPRITE:
821 assert(_curFgp1 < MAX_fgp1_sprites);
822 registerFrame(ob_mouse, ob_graph, ob_mega, &_fgp1List[_curFgp1]);
823 _curFgp1++;
824 break;
825 default:
826 // NO_SPRITE no registering!
827 break;
828 }
829 }
830
831 // FIXME:
832 //
833 // The original credits used a different font. I think it's stored in the
834 // font.clu file, but I don't know how to interpret it.
835 //
836 // The original used the entire screen. This version cuts off the top and
837 // bottom of the screen, because that's where the menus would usually be.
838 //
839 // The original had some sort of smoke effect at the bottom of the screen.
840
841 enum {
842 LINE_LEFT,
843 LINE_CENTER,
844 LINE_RIGHT
845 };
846
847 struct CreditsLine {
848 Common::String str;
849 byte type;
850 int top;
851 int height;
852 byte *sprite;
853
CreditsLineSword2::CreditsLine854 CreditsLine() {
855 sprite = NULL;
856 }
857
~CreditsLineSword2::CreditsLine858 ~CreditsLine() {
859 free(sprite);
860 }
861 };
862
863 #define CREDITS_FONT_HEIGHT 25
864 #define CREDITS_LINE_SPACING 20
865
rollCredits()866 void Screen::rollCredits() {
867 uint32 loopingMusicId = _vm->_sound->getLoopingMusicId();
868
869 // Prepare for the credits by fading down, stoping the music, etc.
870
871 _vm->_mouse->setMouse(0);
872
873 _vm->_sound->muteFx(true);
874 _vm->_sound->muteSpeech(true);
875
876 waitForFade();
877 fadeDown();
878 waitForFade();
879
880 _vm->_mouse->closeMenuImmediately();
881
882 // There are three files which I believe are involved in showing the
883 // credits:
884 //
885 // credits.bmp - The "Smacker" logo, stored as follows:
886 //
887 // width 2 bytes, little endian
888 // height 2 bytes, little endian
889 // palette 3 * 256 bytes
890 // data width * height bytes
891 //
892 // Note that the maximum color component in the palette is 0x3F.
893 // This is the same resolution as the _paletteMatch table. I doubt
894 // that this is a coincidence, but let's use the image palette
895 // directly anyway, just to be safe.
896 //
897 // credits.clu - The credits text (credits.txt in PSX version)
898 //
899 // This is simply a text file with CRLF line endings.
900 // '^' is not shown, but used to mark the center of the line.
901 // '@' is used as a placeholder for the "Smacker" logo. At least
902 // when it appears alone.
903 // Remaining lines are centered.
904 // The German version also contains character code 9 for no
905 // apparent reason. We ignore them.
906 //
907 // fonts.clu - The credits font?
908 //
909 // FIXME: At this time I don't know how to interpret fonts.clu. For
910 // now, let's just the standard speech font instead.
911
912 SpriteInfo spriteInfo;
913 Common::File f;
914 int i;
915
916 spriteInfo.isText = false;
917
918 // Read the "Smacker" logo
919
920 uint16 logoWidth = 0;
921 uint16 logoHeight = 0;
922 byte *logoData = NULL;
923 byte palette[256 * 3];
924
925 if (f.open("credits.bmp")) {
926 logoWidth = f.readUint16LE();
927 logoHeight = f.readUint16LE();
928
929 for (i = 0; i < 256; i++) {
930 palette[i * 3 + 0] = f.readByte() << 2;
931 palette[i * 3 + 1] = f.readByte() << 2;
932 palette[i * 3 + 2] = f.readByte() << 2;
933 }
934
935 logoData = (byte *)malloc(logoWidth * logoHeight);
936
937 f.read(logoData, logoWidth * logoHeight);
938 f.close();
939 } else {
940 warning("Can't find credits.bmp");
941 memset(palette, 0, sizeof(palette));
942 palette[14 * 3 + 0] = 252;
943 palette[14 * 3 + 1] = 252;
944 palette[14 * 3 + 2] = 252;
945 }
946
947 setPalette(0, 256, palette, RDPAL_INSTANT);
948
949 // Read the credits text
950
951 Common::Array<CreditsLine *> creditsLines;
952
953 int lineCount = 0;
954 int lineTop = 400;
955 int paragraphStart = 0;
956 bool hasCenterMark = false;
957
958 if (Sword2Engine::isPsx()) {
959 if (!f.open("credits.txt")) {
960 warning("Can't find credits.txt");
961
962 free(logoData);
963 return;
964 }
965 } else {
966 if (!f.open("credits.clu")) {
967 warning("Can't find credits.clu");
968
969 free(logoData);
970 return;
971 }
972 }
973
974 while (1) {
975 char buffer[80];
976 char *line = f.readLine(buffer, sizeof(buffer));
977
978 if (line) {
979 // Replace invalid character codes prevent the 'dud'
980 // symbol from showing up in the credits.
981
982 for (byte *ptr = (byte *)line; *ptr; ptr++) {
983 switch (*ptr) {
984 case 9:
985 // The German credits contain these.
986 // Convert them to spaces.
987 *ptr = 32;
988 break;
989 case 10:
990 // LF is treated as end of line.
991 *ptr = 0;
992 break;
993 case 170:
994 // The Spanish credits contain these.
995 // Convert them to periods.
996 *ptr = '.';
997 default:
998 break;
999 }
1000 }
1001 }
1002
1003 if (!line || *line == 0) {
1004 if (!hasCenterMark) {
1005 for (i = paragraphStart; i < lineCount; i++)
1006 creditsLines[i]->type = LINE_CENTER;
1007 }
1008 paragraphStart = lineCount;
1009 hasCenterMark = false;
1010 if (paragraphStart == lineCount)
1011 lineTop += CREDITS_LINE_SPACING;
1012
1013 if (!line)
1014 break;
1015
1016 continue;
1017 }
1018
1019 char *center_mark = strchr(line, '^');
1020
1021 if (center_mark) {
1022 // The current paragraph has at least one center mark.
1023 hasCenterMark = true;
1024
1025 if (center_mark != line) {
1026 creditsLines.push_back(new CreditsLine);
1027
1028 // The center mark is somewhere inside the
1029 // line. Split it into left and right side.
1030 *center_mark = 0;
1031
1032 creditsLines[lineCount]->top = lineTop;
1033 creditsLines[lineCount]->height = CREDITS_FONT_HEIGHT;
1034 creditsLines[lineCount]->type = LINE_LEFT;
1035 creditsLines[lineCount]->str = line;
1036
1037 lineCount++;
1038 *center_mark = '^';
1039 }
1040
1041 line = center_mark;
1042 }
1043
1044 creditsLines.push_back(new CreditsLine);
1045
1046 creditsLines[lineCount]->top = lineTop;
1047
1048 if (*line == '^') {
1049 creditsLines[lineCount]->type = LINE_RIGHT;
1050 line++;
1051 } else
1052 creditsLines[lineCount]->type = LINE_LEFT;
1053
1054 if (strcmp(line, "@") == 0) {
1055 creditsLines[lineCount]->height = logoHeight;
1056 lineTop += logoHeight;
1057 } else {
1058 creditsLines[lineCount]->height = CREDITS_FONT_HEIGHT;
1059 lineTop += CREDITS_LINE_SPACING;
1060 }
1061
1062 creditsLines[lineCount]->str = line;
1063 lineCount++;
1064 }
1065
1066 f.close();
1067
1068 // We could easily add some ScummVM stuff to the credits, if we wanted
1069 // to. On the other hand, anyone with the attention span to actually
1070 // read all the credits probably already knows. :-)
1071
1072 // Start the music and roll the credits
1073
1074 // The credits music (which can also be heard briefly in the "carib"
1075 // cutscene) is played once.
1076
1077 _vm->_sound->streamCompMusic(309, false);
1078
1079 clearScene();
1080 fadeUp(0);
1081
1082 spriteInfo.scale = 0;
1083 spriteInfo.scaledWidth = 0;
1084 spriteInfo.scaledHeight = 0;
1085 spriteInfo.type = RDSPR_DISPLAYALIGN | RDSPR_NOCOMPRESSION | RDSPR_TRANS;
1086 spriteInfo.blend = 0;
1087
1088 int startLine = 0;
1089 int scrollPos = 0;
1090
1091 bool abortCredits = false;
1092
1093 int scrollSteps = lineTop + CREDITS_FONT_HEIGHT;
1094 uint32 musicStart = getTick();
1095
1096 // Ideally the music should last just a tiny bit longer than the
1097 // credits. Note that musicTimeRemaining() will return 0 if the music
1098 // is muted, so we need a sensible fallback for that case.
1099
1100 uint32 musicLength = MAX((int32)(1000 * (_vm->_sound->musicTimeRemaining() - 3)), 25 * (int32)scrollSteps);
1101
1102 while (scrollPos < scrollSteps && !_vm->shouldQuit()) {
1103 clearScene();
1104
1105 for (i = startLine; i < lineCount; i++) {
1106 if (!creditsLines[i])
1107 continue;
1108
1109 // Free any sprites that have scrolled off the screen
1110
1111 if (creditsLines[i]->top + creditsLines[i]->height < scrollPos) {
1112 debug(2, "Freeing line %d: '%s'", i, creditsLines[i]->str.c_str());
1113
1114 delete creditsLines[i];
1115 creditsLines[i] = NULL;
1116
1117 startLine = i + 1;
1118 } else if (creditsLines[i]->top < scrollPos + 400) {
1119 if (!creditsLines[i]->sprite) {
1120 debug(2, "Creating line %d: '%s'", i, creditsLines[i]->str.c_str());
1121 creditsLines[i]->sprite = _vm->_fontRenderer->makeTextSprite((const byte *)creditsLines[i]->str.c_str(), 600, 14, _vm->_speechFontId, 0);
1122 }
1123
1124 FrameHeader frame;
1125
1126 frame.read(creditsLines[i]->sprite);
1127
1128 spriteInfo.y = creditsLines[i]->top - scrollPos;
1129 spriteInfo.w = frame.width;
1130 spriteInfo.h = frame.height;
1131 spriteInfo.data = creditsLines[i]->sprite + FrameHeader::size();
1132 spriteInfo.isText = true;
1133
1134 switch (creditsLines[i]->type) {
1135 case LINE_LEFT:
1136 spriteInfo.x = RENDERWIDE / 2 - 5 - frame.width;
1137 break;
1138 case LINE_RIGHT:
1139 spriteInfo.x = RENDERWIDE / 2 + 5;
1140 break;
1141 case LINE_CENTER:
1142 if (strcmp(creditsLines[i]->str.c_str(), "@") == 0) {
1143 spriteInfo.data = logoData;
1144 spriteInfo.x = (RENDERWIDE - logoWidth) / 2;
1145 spriteInfo.w = logoWidth;
1146 spriteInfo.h = logoHeight;
1147 } else
1148 spriteInfo.x = (RENDERWIDE - frame.width) / 2;
1149 break;
1150 }
1151
1152 if (spriteInfo.data)
1153 drawSprite(&spriteInfo);
1154 } else
1155 break;
1156 }
1157
1158 updateDisplay();
1159
1160 KeyboardEvent *ke = _vm->keyboardEvent();
1161
1162 if (ke && ke->kbd.keycode == Common::KEYCODE_ESCAPE) {
1163 if (!abortCredits) {
1164 abortCredits = true;
1165 fadeDown();
1166 }
1167 }
1168
1169 if (abortCredits && getFadeStatus() == RDFADE_BLACK)
1170 break;
1171
1172 _vm->sleepUntil(musicStart + (musicLength * scrollPos) / scrollSteps + _pauseTicks);
1173 scrollPos++;
1174 }
1175
1176 // We're done. Clean up and try to put everything back where it was
1177 // before the credits.
1178
1179 for (i = 0; i < lineCount; i++) {
1180 delete creditsLines[i];
1181 }
1182
1183 free(logoData);
1184
1185 if (!abortCredits) {
1186 fadeDown();
1187
1188 // The music should either have stopped or be about to stop, so
1189 // wait for it to really happen.
1190
1191 while (_vm->_sound->musicTimeRemaining() && !_vm->shouldQuit()) {
1192 updateDisplay(false);
1193 _vm->_system->delayMillis(100);
1194 }
1195 }
1196
1197 if (_vm->shouldQuit())
1198 return;
1199
1200 waitForFade();
1201
1202 _vm->_sound->muteFx(false);
1203 _vm->_sound->muteSpeech(false);
1204
1205 if (loopingMusicId)
1206 _vm->_sound->streamCompMusic(loopingMusicId, true);
1207 else
1208 _vm->_sound->stopMusic(false);
1209
1210 if (!_vm->_mouse->getMouseStatus() || _vm->_mouse->isChoosing())
1211 _vm->_mouse->setMouse(NORMAL_MOUSE_ID);
1212
1213 if (_vm->_logic->readVar(DEAD))
1214 _vm->_mouse->buildSystemMenu();
1215 }
1216
1217 // This image used to be shown by CacheNewCluster() while copying a data file
1218 // from the CD to the hard disk. ScummVM doesn't do that, so the image is never
1219 // shown. It'd be nice if we could do something useful with it some day...
1220
splashScreen()1221 void Screen::splashScreen() {
1222 byte *bgfile = _vm->_resman->openResource(2950);
1223
1224 initializeBackgroundLayer(NULL);
1225 initializeBackgroundLayer(NULL);
1226 initializeBackgroundLayer(_vm->fetchBackgroundLayer(bgfile));
1227 initializeBackgroundLayer(NULL);
1228 initializeBackgroundLayer(NULL);
1229
1230 _vm->fetchPalette(bgfile, _palette);
1231 setPalette(0, 256, _palette, RDPAL_FADE);
1232 renderParallax(_vm->fetchBackgroundLayer(bgfile), 2);
1233
1234 closeBackgroundLayer();
1235
1236 byte *loadingBar = _vm->_resman->openResource(2951);
1237 byte *frame = _vm->fetchFrameHeader(loadingBar, 0);
1238
1239 AnimHeader animHead;
1240 CdtEntry cdt;
1241 FrameHeader frame_head;
1242
1243 animHead.read(_vm->fetchAnimHeader(loadingBar));
1244 cdt.read(_vm->fetchCdtEntry(loadingBar, 0));
1245 frame_head.read(_vm->fetchFrameHeader(loadingBar, 0));
1246
1247 SpriteInfo barSprite;
1248
1249 barSprite.x = cdt.x;
1250 barSprite.y = cdt.y;
1251 barSprite.w = frame_head.width;
1252 barSprite.h = frame_head.height;
1253 barSprite.scale = 0;
1254 barSprite.scaledWidth = 0;
1255 barSprite.scaledHeight = 0;
1256 barSprite.type = RDSPR_RLE256FAST | RDSPR_TRANS;
1257 barSprite.blend = 0;
1258 barSprite.colorTable = 0;
1259 barSprite.data = frame + FrameHeader::size();
1260 barSprite.isText = false;
1261
1262 drawSprite(&barSprite);
1263
1264 fadeUp();
1265 waitForFade();
1266
1267 for (int i = 0; i < animHead.noAnimFrames; i++) {
1268 frame = _vm->fetchFrameHeader(loadingBar, i);
1269 barSprite.data = frame + FrameHeader::size();
1270 drawSprite(&barSprite);
1271 updateDisplay();
1272 _vm->_system->delayMillis(30);
1273 }
1274
1275 _vm->_resman->closeResource(2951);
1276
1277 fadeDown();
1278 waitForFade();
1279 }
1280
1281 // Following functions are used to manage screen cache for psx version.
1282
setPsxScrCache(byte * psxScrCache,uint8 level)1283 void Screen::setPsxScrCache(byte *psxScrCache, uint8 level) {
1284 if (level < 3) {
1285 if (psxScrCache)
1286 _psxCacheEnabled[level] = true;
1287 else
1288 _psxCacheEnabled[level] = false;
1289
1290 _psxScrCache[level] = psxScrCache;
1291 }
1292 }
1293
getPsxScrCache(uint8 level)1294 byte *Screen::getPsxScrCache(uint8 level) {
1295 if (level > 2) {
1296 level = 0;
1297 }
1298
1299 if (_psxCacheEnabled[level])
1300 return _psxScrCache[level];
1301 else
1302 return NULL;
1303 }
1304
getPsxScrCacheStatus(uint8 level)1305 bool Screen::getPsxScrCacheStatus(uint8 level) {
1306 if (level > 2) {
1307 level = 0;
1308 }
1309
1310 return _psxCacheEnabled[level];
1311 }
1312
flushPsxScrCache()1313 void Screen::flushPsxScrCache() {
1314 for (uint8 i = 0; i < 3; i++) {
1315 free(_psxScrCache[i]);
1316 _psxScrCache[i] = NULL;
1317 _psxCacheEnabled[i] = true;
1318 }
1319 }
1320
1321 } // End of namespace Sword2
1322