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 // Based on Deniz Oezmen's code: http://oezmen.eu/
24 
25 #include "lastexpress/data/animation.h"
26 
27 #include "lastexpress/data/sequence.h"
28 #include "lastexpress/data/snd.h"
29 
30 #include "lastexpress/debug.h"
31 #include "lastexpress/helpers.h"
32 
33 #include "common/events.h"
34 #include "common/rational.h"
35 #include "common/stream.h"
36 #include "common/system.h"
37 
38 #include "engines/engine.h"
39 
40 namespace LastExpress {
41 
Animation()42 Animation::Animation() : _stream(NULL), _currentChunk(NULL), _overlay(NULL), _background1(NULL), _background2(NULL), _backgroundCurrent(0), _audio(NULL), _startTime(0), _changed(false) {
43 }
44 
~Animation()45 Animation::~Animation() {
46 	reset();
47 }
48 
reset()49 void Animation::reset() {
50 	SAFE_DELETE(_overlay);
51 	SAFE_DELETE(_background1);
52 	SAFE_DELETE(_background2);
53 	SAFE_DELETE(_audio);
54 
55 	_backgroundCurrent = 0;
56 	_chunks.clear();
57 
58 	_currentChunk = NULL;
59 
60 	SAFE_DELETE(_stream);
61 }
62 
load(Common::SeekableReadStream * stream,int flag)63 bool Animation::load(Common::SeekableReadStream *stream, int flag) {
64 	if (!stream)
65 		return false;
66 
67 	reset();
68 
69 	// Keep stream for later decoding of animation
70 	_stream = stream;
71 
72 	// Read header to get the number of chunks
73 	uint32 numChunks = _stream->readUint32LE();
74 	debugC(3, kLastExpressDebugGraphics, "Number of chunks in NIS file: %d", numChunks);
75 
76 	// Check if there is enough data
77 	if (_stream->size() - _stream->pos() < (signed)(numChunks * sizeof(Chunk))) {
78 		debugC(2, kLastExpressDebugGraphics, "NIS file seems to be corrupted");
79 		return false;
80 	}
81 
82 	// Read all the chunks
83 	for (uint32 i = 0; i < numChunks; ++i) {
84 		Chunk chunk;
85 		chunk.type = (ChunkType)_stream->readUint16LE();
86 		chunk.frame = _stream->readUint16LE();
87 		chunk.size = _stream->readUint32LE();
88 
89 		_chunks.push_back(chunk);
90 
91 		debugC(9, kLastExpressDebugGraphics, "Chunk Entry: type 0x%.4x, frame=%d, size=%d", chunk.type, chunk.frame, chunk.size);
92 	}
93 	_currentChunk = _chunks.begin();
94 	_changed = false;
95 	_startTime = g_system->getMillis();
96 
97 	return true;
98 }
99 
process()100 bool Animation::process() {
101 	if (!_currentChunk)
102 		error("[Animation::process] Current chunk iterator is invalid");
103 
104 	if (_stream == NULL || _chunks.size() == 0)
105 		error("[Animation::process] Trying to show an animation before loading data");
106 
107 	// TODO: - subtract the time paused by the GUI
108 	//       - Re-implement to be closer to the original engine
109 	//       - Add support for subtitles
110 	//       - Use engine sound queue instead of our own appendable sound instance
111 	int32 currentFrame = (g_system->getMillis() - _startTime) * 3 / 100;
112 
113 	// Process all chunks until the current frame
114 	while (!_changed && _currentChunk != NULL && currentFrame > _currentChunk->frame && !hasEnded()) {
115 		switch(_currentChunk->type) {
116 		//TODO: some info chunks are probably subtitle/sync related
117 		case kChunkTypeUnknown1:
118 		case kChunkTypeUnknown2:
119 		case kChunkTypeUnknown5:
120 			debugC(9, kLastExpressDebugGraphics | kLastExpressDebugUnknown, "  info chunk: type 0x%.4x (size %d)", _currentChunk->type, _currentChunk->size);
121 			assert (_currentChunk->frame == 0);
122 			//TODO: _currentChunk->size?
123 			break;
124 
125 		case kChunkTypeAudioInfo:
126 			debugC(9, kLastExpressDebugGraphics, "  audio info: %d blocks", _currentChunk->size);
127 			assert (_currentChunk->frame == 0);
128 			//TODO: save the size?
129 			_audio = new AppendableSound();
130 			break;
131 
132 		case kChunkTypeUnknown4:
133 			debugC(9, kLastExpressDebugGraphics | kLastExpressDebugUnknown, "  info block 4");
134 			assert (_currentChunk->frame == 0 && _currentChunk->size == 0);
135 			//TODO unknown type of chunk
136 			break;
137 
138 		case kChunkTypeBackground1:
139 			debugC(9, kLastExpressDebugGraphics, "  background frame 1 (%d bytes, frame %d)", _currentChunk->size, _currentChunk->frame);
140 			delete _background1;
141 			_background1 = processChunkFrame(_stream, *_currentChunk);
142 			break;
143 
144 		case kChunkTypeSelectBackground1:
145 			debugC(9, kLastExpressDebugGraphics, "  select background 1");
146 			assert (_currentChunk->frame == 0 && _currentChunk->size == 0);
147 			_backgroundCurrent = 1;
148 			break;
149 
150 		case kChunkTypeBackground2:
151 			debugC(9, kLastExpressDebugGraphics, "  background frame 2 (%d bytes, frame %d)", _currentChunk->size, _currentChunk->frame);
152 			delete _background2;
153 			_background2 = processChunkFrame(_stream, *_currentChunk);
154 			break;
155 
156 		case kChunkTypeSelectBackground2:
157 			debugC(9, kLastExpressDebugGraphics, "  select background 2");
158 			assert (_currentChunk->frame == 0 && _currentChunk->size == 0);
159 			_backgroundCurrent = 2;
160 			break;
161 
162 		case kChunkTypeOverlay:
163 			debugC(9, kLastExpressDebugGraphics, "  overlay frame (%d bytes, frame %d)", _currentChunk->size, _currentChunk->frame);
164 			delete _overlay;
165 			_overlay = processChunkFrame(_stream, *_currentChunk);
166 			break;
167 
168 		case kChunkTypeUpdate:
169 		case kChunkTypeUpdateTransition:
170 			debugC(9, kLastExpressDebugGraphics, "  update%s: frame %d", _currentChunk->type == 15 ? "" : " with transition", _currentChunk->frame);
171 			assert (_currentChunk->size == 0);
172 			_changed = true;
173 			break;
174 
175 		case kChunkTypeAudioData:
176 			debugC(9, kLastExpressDebugGraphics, "  audio (%d blocks, %d bytes, frame %d)", _currentChunk->size / _soundBlockSize, _currentChunk->size, _currentChunk->frame);
177 			processChunkAudio(_stream, *_currentChunk);
178 
179 			// Synchronize the audio by resetting the start time
180 			if (_currentChunk->frame == 0)
181 				_startTime = g_system->getMillis();
182 			break;
183 
184 		case kChunkTypeAudioEnd:
185 			debugC(9, kLastExpressDebugGraphics, "  audio end: %d blocks", _currentChunk->frame);
186 			assert (_currentChunk->size == 0);
187 			_audio->finish();
188 			//TODO: we need to start the linked sound (.LNK) after the audio from the animation ends
189 			break;
190 
191 		default:
192 			error("[Animation::process] UNKNOWN chunk type=%x frame=%d size=%d", _currentChunk->type, _currentChunk->frame, _currentChunk->size);
193 			break;
194 		}
195 		_currentChunk++;
196 	}
197 
198 	return true;
199 }
200 
hasEnded()201 bool Animation::hasEnded() {
202 	return _currentChunk == _chunks.end();
203 }
204 
draw(Graphics::Surface * surface)205 Common::Rect Animation::draw(Graphics::Surface *surface) {
206 	if (!_overlay)
207 		error("[Animation::draw] Current overlay animation frame is invalid");
208 
209 	// Paint the background
210 	if (_backgroundCurrent == 1 && _background1)
211 		_background1->draw(surface);
212 	else if (_backgroundCurrent == 2 && _background2)
213 		_background2->draw(surface);
214 
215 	// Paint the overlay
216 	_overlay->draw(surface);
217 
218 	//TODO
219 	return Common::Rect();
220 }
221 
processChunkFrame(Common::SeekableReadStream * in,const Chunk & c) const222 AnimFrame *Animation::processChunkFrame(Common::SeekableReadStream *in, const Chunk &c) const {
223 	assert (c.frame == 0);
224 
225 	// Create a temporary chunk buffer
226 	Common::SeekableReadStream *str = in->readStream(c.size);
227 
228 	// Read the frame information
229 	FrameInfo i;
230 	i.read(str, false);
231 
232 	// Decode the frame
233 	AnimFrame *f = new AnimFrame(str, i, true);
234 
235 	// Delete the temporary chunk buffer
236 	delete str;
237 
238 	return f;
239 }
240 
processChunkAudio(Common::SeekableReadStream * in,const Chunk & c)241 void Animation::processChunkAudio(Common::SeekableReadStream *in, const Chunk &c) {
242 	if (!_audio)
243 		error("[Animation::processChunkAudio] Audio stream is invalid");
244 
245 	// Skip the Snd header, to queue just the audio blocks
246 	uint32 size = c.size;
247 	if ((c.size % 739) != 0) {
248 		// Read Snd header
249 		uint32 header1 = in->readUint32LE();
250 		uint16 header2 = in->readUint16LE();
251 		debugC(4, kLastExpressDebugSound, "Start ADPCM: %d, %d", header1, header2);
252 		size -= 6;
253 	}
254 
255 	// Append the current chunk to the Snd
256 	_audio->queueBuffer(in->readStream(size));
257 }
258 
259 // TODO: this method will probably go away and be integrated in the main loop
play()260 void Animation::play() {
261 	Common::EventManager *eventMan = g_system->getEventManager();
262 	while (!hasEnded() && !Engine::shouldQuit()) {
263 		process();
264 
265 		if (_changed) {
266 			// Create a temporary surface to merge the overlay with the background
267 			Graphics::Surface *s = new Graphics::Surface;
268 			s->create(640, 480, Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0));
269 
270 			draw(s);
271 
272 			// XXX: Update the screen
273 			g_system->copyRectToScreen(s->getPixels(), s->pitch, 0, 0, s->w, s->h);
274 
275 			// Free the temporary surface
276 			s->free();
277 			delete s;
278 
279 			_changed = false;
280 		}
281 
282 		g_system->updateScreen();
283 
284 		//FIXME: implement subtitles
285 		g_system->delayMillis(20);
286 
287 		// Handle right-click to interrupt animations
288 		Common::Event ev = Common::Event();
289 		while (eventMan->pollEvent(ev)) {
290 			if (ev.type == Common::EVENT_RBUTTONUP) {
291 				// Stop audio
292 				if (_audio)
293 					_audio->finish();
294 
295 				// TODO start LNK file sound?
296 				return;
297 			}
298 		}
299 	}
300 }
301 
302 } // End of namespace LastExpress
303