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