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