1 /* ResidualVM - A 3D game interpreter
2  *
3  * ResidualVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the AUTHORS
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "engines/myst3/effects.h"
24 #include "engines/myst3/gfx.h"
25 #include "engines/myst3/myst3.h"
26 #include "engines/myst3/state.h"
27 #include "engines/myst3/sound.h"
28 
29 #include "graphics/surface.h"
30 
31 namespace Myst3 {
32 
FaceMask()33 Effect::FaceMask::FaceMask() :
34 		surface(nullptr) {
35 
36 	for (uint i = 0; i < 10; i++) {
37 		for (uint j = 0; j < 10; j++) {
38 			block[i][j] = false;
39 		}
40 	}
41 }
42 
~FaceMask()43 Effect::FaceMask::~FaceMask() {
44 	if (surface) {
45 		surface->free();
46 	}
47 
48 	delete surface;
49 }
50 
getBlockRect(uint x,uint y)51 Common::Rect Effect::FaceMask::getBlockRect(uint x, uint y) {
52 	Common::Rect rect = Common::Rect(64, 64);
53 	rect.translate(x * 64, y * 64);
54 	return rect;
55 }
56 
Effect(Myst3Engine * vm)57 Effect::Effect(Myst3Engine *vm) :
58 		_vm(vm) {
59 }
60 
~Effect()61 Effect::~Effect() {
62 	for (FaceMaskMap::iterator it = _facesMasks.begin(); it != _facesMasks.end(); it++) {
63 		delete it->_value;
64 	}
65 }
66 
loadMasks(const Common::String & room,uint32 id,DirectorySubEntry::ResourceType type)67 bool Effect::loadMasks(const Common::String &room, uint32 id, DirectorySubEntry::ResourceType type) {
68 	bool isFrame = _vm->_state->getViewType() == kFrame;
69 
70 	// Load the mask of each face
71 	for (uint i = 0; i < 6; i++) {
72 		const DirectorySubEntry *desc = _vm->getFileDescription(room, id, i + 1, type);
73 
74 		if (desc) {
75 			Common::SeekableReadStream *data = desc->getData();
76 
77 			// Check if we are overriding an existing mask
78 			delete _facesMasks[i];
79 			_facesMasks[i] = loadMask(data);
80 
81 			// Frame masks are vertically flipped for some reason
82 			if (isFrame) {
83 				_vm->_gfx->flipVertical(_facesMasks[i]->surface);
84 			}
85 
86 			delete data;
87 		}
88 	}
89 
90 	if (_facesMasks.empty())
91 		return false;
92 
93 	return true;
94 }
95 
loadMask(Common::SeekableReadStream * maskStream)96 Effect::FaceMask *Effect::loadMask(Common::SeekableReadStream *maskStream) {
97 	FaceMask *mask = new FaceMask();
98 	mask->surface = new Graphics::Surface();
99 	mask->surface->create(640, 640, Graphics::PixelFormat::createFormatCLUT8());
100 
101 	uint32 headerOffset = 0;
102 	uint32 dataOffset = 0;
103 
104 	while (headerOffset < 400) {
105 		int blockX = (headerOffset / sizeof(dataOffset)) % 10;
106 		int blockY = (headerOffset / sizeof(dataOffset)) / 10;
107 
108 		maskStream->seek(headerOffset, SEEK_SET);
109 		dataOffset = maskStream->readUint32LE();
110 		headerOffset = maskStream->pos();
111 
112 		if (dataOffset != 0) {
113 			maskStream->seek(dataOffset, SEEK_SET);
114 
115 			for(int i = 63; i >= 0; i--) {
116 				int x = 0;
117 				byte numValues = maskStream->readByte();
118 				for (int j = 0; j < numValues; j++) {
119 					byte repeat = maskStream->readByte();
120 					byte value = maskStream->readByte();
121 					for (int k = 0; k < repeat; k++) {
122 						((uint8*)mask->surface->getPixels())[((blockY * 64) + i) * 640 + blockX * 64 + x] = value;
123 						x++;
124 					}
125 
126 					// If a block has at least one non zero value, mark it as active
127 					if (value != 0) {
128 						mask->block[blockX][blockY] = true;
129 					}
130 				}
131 			}
132 		}
133 	}
134 
135 	return mask;
136 }
137 
getUpdateRectForFace(uint face)138 Common::Rect Effect::getUpdateRectForFace(uint face) {
139 	FaceMask *mask = _facesMasks.getVal(face);
140 	if (!mask)
141 		error("No mask for face %d", face);
142 
143 	Common::Rect rect;
144 
145 	// Build a rectangle containing all the active effect blocks
146 	for (uint i = 0; i < 10; i++) {
147 		for (uint j = 0; j < 10; j++) {
148 			if (mask->block[i][j]) {
149 				if (rect.isEmpty()) {
150 					rect = FaceMask::getBlockRect(i, j);
151 				} else {
152 					rect.extend(FaceMask::getBlockRect(i, j));
153 				}
154 			}
155 		}
156 	}
157 
158 	return rect;
159 }
160 
WaterEffect(Myst3Engine * vm)161 WaterEffect::WaterEffect(Myst3Engine *vm) :
162 		Effect(vm),
163 		_lastUpdate(0),
164 		_step(0) {
165 }
166 
~WaterEffect()167 WaterEffect::~WaterEffect() {
168 }
169 
create(Myst3Engine * vm,uint32 id)170 WaterEffect *WaterEffect::create(Myst3Engine *vm, uint32 id) {
171 	WaterEffect *s = new WaterEffect(vm);
172 
173 	if (!s->loadMasks("", id, DirectorySubEntry::kWaterEffectMask)) {
174 		delete s;
175 		return 0;
176 	}
177 
178 	return s;
179 }
180 
isRunning()181 bool WaterEffect::isRunning() {
182 	return _vm->_state->getWaterEffectActive()
183 			&& _vm->_state->getWaterEffectRunning();
184 }
185 
update()186 bool WaterEffect::update() {
187 	if (!isRunning()) {
188 		return false;
189 	}
190 
191 	if (g_system->getMillis() - _lastUpdate >= 1000 / (uint32)_vm->_state->getWaterEffectSpeed()) {
192 		_lastUpdate = g_system->getMillis();
193 
194 		_step++;
195 		if (_step > _vm->_state->getWaterEffectMaxStep())
196 			_step = 0;
197 
198 		float position = _step / (float)_vm->_state->getWaterEffectMaxStep();
199 
200 		doStep(position, _vm->_state->getViewType() == kFrame);
201 
202 		return true;
203 	}
204 
205 	return false;
206 }
207 
doStep(float position,bool isFrame)208 void WaterEffect::doStep(float position, bool isFrame) {
209 	double timeOffset;
210 	double frequency;
211 	double ampl;
212 
213 	timeOffset = position * 2 * M_PI;
214 	frequency = _vm->_state->getWaterEffectFrequency() * 0.1;
215 
216 	ampl = _vm->_state->getWaterEffectAmpl() / 10.0 / 2.0;
217 	for (uint i = 0; i < 640; i++) {
218 		double ampl1;
219 		if (i < 320)
220 			ampl1 = i / 320 + 1.0;
221 		else
222 			ampl1 = (640 - i) / 320 + 1.0;
223 
224 		_bottomDisplacement[i] = sin(i / 640.0 * frequency * 2 * M_PI + timeOffset) / 2 * ampl1 * ampl;
225 	}
226 
227 	// FIXME: The original sets this to WaterEffectAttenuation, which causes
228 	// glitches here
229 	uint32 attenuation = 640;
230 	for (uint i = 0; i < attenuation; i++) {
231 		double ampl2 = attenuation / (attenuation - i + 1.0);
232 
233 		int8 value = sin(i / 640.0 * frequency * 2 * M_PI * ampl2 + timeOffset) / 2 * 1.0 / ampl2 * ampl;
234 
235 		if (!isFrame) {
236 			_verticalDisplacement[i] = value;
237 		} else {
238 			_verticalDisplacement[attenuation - 1 - i] = value;
239 		}
240 	}
241 
242 	for (uint i = 0; i < 640; i++) {
243 		double ampl3 = sin(i / 640.0 * frequency * 2 * M_PI + timeOffset) / 2.0;
244 
245 		_horizontalDisplacements[0][i] = ampl3 * 1.25 * ampl + 0.5;
246 		_horizontalDisplacements[1][i] = ampl3 * 1.00 * ampl + 0.5;
247 		_horizontalDisplacements[2][i] = ampl3 * 0.75 * ampl + 0.5;
248 		_horizontalDisplacements[3][i] = ampl3 * 0.50 * ampl + 0.5;
249 		_horizontalDisplacements[4][i] = ampl3 * 0.25 * ampl + 0.5;
250 	}
251 }
252 
applyForFace(uint face,Graphics::Surface * src,Graphics::Surface * dst)253 void WaterEffect::applyForFace(uint face, Graphics::Surface *src, Graphics::Surface *dst) {
254 	if (!isRunning()) {
255 		return;
256 	}
257 
258 	FaceMask *mask = _facesMasks.getVal(face);
259 
260 	if (!mask)
261 		error("No mask for face %d", face);
262 
263 	apply(src, dst, mask->surface, face == 1, _vm->_state->getWaterEffectAmpl());
264 }
265 
apply(Graphics::Surface * src,Graphics::Surface * dst,Graphics::Surface * mask,bool bottomFace,int32 waterEffectAmpl)266 void WaterEffect::apply(Graphics::Surface *src, Graphics::Surface *dst, Graphics::Surface *mask, bool bottomFace, int32 waterEffectAmpl) {
267 	int32 waterEffectAttenuation = _vm->_state->getWaterEffectAttenuation();
268 	int32 waterEffectAmplOffset = _vm->_state->getWaterEffectAmplOffset();
269 
270 	int8 *hDisplacement = nullptr;
271 	int8 *vDisplacement = nullptr;
272 
273 	if (bottomFace) {
274 		hDisplacement = _bottomDisplacement;
275 		vDisplacement = _bottomDisplacement;
276 	} else {
277 		vDisplacement = _verticalDisplacement;
278 	}
279 
280 	uint32 *dstPtr = (uint32 *)dst->getPixels();
281 	byte *maskPtr = (byte *)mask->getPixels();
282 
283 	for (uint y = 0; y < dst->h; y++) {
284 		if (!bottomFace) {
285 			uint32 strength = (320 * (9 - y / 64)) / waterEffectAttenuation;
286 			if (strength > 4)
287 				strength = 4;
288 			hDisplacement = _horizontalDisplacements[strength];
289 		}
290 
291 		for (uint x = 0; x < dst->w; x++) {
292 			int8 maskValue = *maskPtr;
293 
294 			if (maskValue != 0) {
295 				int8 xOffset = hDisplacement[x];
296 				int8 yOffset = vDisplacement[y];
297 
298 				if (maskValue < 8) {
299 					maskValue -= waterEffectAmplOffset;
300 					if (maskValue < 0) {
301 						maskValue = 0;
302 					}
303 
304 					if (xOffset >= 0) {
305 						if (xOffset > maskValue)
306 							xOffset = maskValue;
307 					} else {
308 						if (-xOffset > maskValue)
309 							xOffset = -maskValue;
310 					}
311 					if (yOffset >= 0) {
312 						if (yOffset > maskValue)
313 							yOffset = maskValue;
314 					} else {
315 						if (-yOffset > maskValue)
316 							yOffset = -maskValue;
317 					}
318 				}
319 
320 				uint32 srcValue1 = *(uint32 *) src->getBasePtr(x + xOffset, y + yOffset);
321 				uint32 srcValue2 = *(uint32 *) src->getBasePtr(x, y);
322 
323 #ifdef SCUMM_BIG_ENDIAN
324 				*dstPtr = 0x000000FF | ((0x7F7F7F00 & (srcValue1 >> 1)) + (0x7F7F7F00 & (srcValue2 >> 1)));
325 #else
326 				*dstPtr = 0xFF000000 | ((0x007F7F7F & (srcValue1 >> 1)) + (0x007F7F7F & (srcValue2 >> 1)));
327 #endif
328 			}
329 
330 			maskPtr++;
331 			dstPtr++;
332 		}
333 	}
334 }
335 
LavaEffect(Myst3Engine * vm)336 LavaEffect::LavaEffect(Myst3Engine *vm) :
337 		Effect(vm),
338 		_lastUpdate(0),
339 		_step(0) {
340 }
341 
~LavaEffect()342 LavaEffect::~LavaEffect() {
343 
344 }
345 
create(Myst3Engine * vm,uint32 id)346 LavaEffect *LavaEffect::create(Myst3Engine *vm, uint32 id) {
347 	LavaEffect *s = new LavaEffect(vm);
348 
349 	if (!s->loadMasks("", id, DirectorySubEntry::kLavaEffectMask)) {
350 		delete s;
351 		return 0;
352 	}
353 
354 	return s;
355 }
356 
update()357 bool LavaEffect::update() {
358 	if (!_vm->_state->getLavaEffectActive()) {
359 		return false;
360 	}
361 
362 	if (g_system->getMillis() - _lastUpdate >= 1000 / (uint32)_vm->_state->getLavaEffectSpeed()) {
363 		_lastUpdate = g_system->getMillis();
364 
365 		_step += _vm->_state->getLavaEffectStepSize();
366 
367 		doStep(_step, _vm->_state->getLavaEffectAmpl() / 10);
368 
369 		if (_step > 256)
370 			_step -= 256;
371 
372 		return true;
373 	}
374 
375 	return false;
376 }
377 
doStep(int32 position,float ampl)378 void LavaEffect::doStep(int32 position, float ampl) {
379 	for (uint i = 0; i < 256; i++) {
380 		_displacement[i] = (sin((i + position) * 2 * M_PI / 256.0) + 1.0) * ampl;
381 	}
382 }
383 
applyForFace(uint face,Graphics::Surface * src,Graphics::Surface * dst)384 void LavaEffect::applyForFace(uint face, Graphics::Surface *src, Graphics::Surface *dst) {
385 	if (!_vm->_state->getLavaEffectActive()) {
386 		return;
387 	}
388 
389 	FaceMask *mask = _facesMasks.getVal(face);
390 
391 	if (!mask)
392 		error("No mask for face %d", face);
393 
394 	uint32 *dstPtr = (uint32 *)dst->getPixels();
395 	byte *maskPtr = (byte *)mask->surface->getPixels();
396 
397 	for (uint y = 0; y < dst->h; y++) {
398 		for (uint x = 0; x < dst->w; x++) {
399 			uint8 maskValue = *maskPtr;
400 
401 			if (maskValue != 0) {
402 				int32 xOffset= _displacement[(maskValue + y) % 256];
403 				int32 yOffset = _displacement[maskValue % 256];
404 				int32 maxOffset = (maskValue >> 6) & 0x3;
405 
406 				if (yOffset > maxOffset) {
407 					yOffset = maxOffset;
408 				}
409 				if (xOffset > maxOffset) {
410 					xOffset = maxOffset;
411 				}
412 
413 //				uint32 srcValue1 = *(uint32 *)src->getBasePtr(x + xOffset, y + yOffset);
414 //				uint32 srcValue2 = *(uint32 *)src->getBasePtr(x, y);
415 //
416 //				*dstPtr = 0xFF000000 | ((0x007F7F7F & (srcValue1 >> 1)) + (0x007F7F7F & (srcValue2 >> 1)));
417 
418 				// TODO: The original does "blending" as above, but strangely
419 				// this looks more like the original rendering
420 				*dstPtr = *(uint32 *)src->getBasePtr(x + xOffset, y + yOffset);
421 			}
422 
423 			maskPtr++;
424 			dstPtr++;
425 		}
426 	}
427 }
428 
MagnetEffect(Myst3Engine * vm)429 MagnetEffect::MagnetEffect(Myst3Engine *vm) :
430 		Effect(vm),
431 		_lastSoundId(0),
432 		_lastTime(0),
433 		_position(0),
434 		_lastAmpl(0),
435 		_shakeStrength(nullptr) {
436 }
437 
~MagnetEffect()438 MagnetEffect::~MagnetEffect() {
439 	delete _shakeStrength;
440 }
441 
create(Myst3Engine * vm,uint32 id)442 MagnetEffect *MagnetEffect::create(Myst3Engine *vm, uint32 id) {
443 	MagnetEffect *s = new MagnetEffect(vm);
444 
445 	if (!s->loadMasks("", id, DirectorySubEntry::kMagneticEffectMask)) {
446 		delete s;
447 		return 0;
448 	}
449 
450 	return s;
451 }
452 
update()453 bool MagnetEffect::update() {
454 	int32 soundId = _vm->_state->getMagnetEffectSound();
455 	if (!soundId) {
456 		// The effect is no longer active
457 		_lastSoundId = 0;
458 		_vm->_state->setMagnetEffectUnk3(0);
459 
460 		delete _shakeStrength;
461 		_shakeStrength = nullptr;
462 
463 		return false;
464 	}
465 
466 	if (soundId != _lastSoundId) {
467 		// The sound changed since last update
468 		_lastSoundId = soundId;
469 
470 		const DirectorySubEntry *desc = _vm->getFileDescription("", _vm->_state->getMagnetEffectNode(), 0, DirectorySubEntry::kRawData);
471 		if (!desc)
472 			error("Magnet effect support file %d does not exist", _vm->_state->getMagnetEffectNode());
473 
474 		delete _shakeStrength;
475 		_shakeStrength = desc->getData();
476 	}
477 
478 	int32 soundPosition = _vm->_sound->playedFrames(soundId);
479 	if (_shakeStrength && soundPosition >= 0) {
480 		// Update the shake amplitude according to the position in the playing sound.
481 		// This has no in-game effect (same as original) due to var 122 being 0.
482 		_shakeStrength->seek(soundPosition, SEEK_SET);
483 		_vm->_state->setMagnetEffectUnk3(_shakeStrength->readByte());
484 
485 		// Update the vertical displacements
486 		float ampl = (_vm->_state->getMagnetEffectUnk1() + _vm->_state->getMagnetEffectUnk3())
487 				/ (float)_vm->_state->getMagnetEffectUnk2();
488 
489 		if (ampl != _lastAmpl) {
490 			for (uint i = 0; i < 256; i++) {
491 				_verticalDisplacement[i] = sin(i * 2 * M_PI / 255.0) * ampl;
492 			}
493 
494 			_lastAmpl = ampl;
495 		}
496 
497 		// Update the position in the effect cycle
498 		uint32 time = g_system->getMillis();
499 		if (_lastTime) {
500 			_position += (float)_vm->_state->getMagnetEffectSpeed() * (time - _lastTime) / 1000 / 10;
501 
502 			while (_position > 1.0) {
503 				_position -= 1.0;
504 			}
505 		}
506 		_lastTime = time;
507 	} else {
508 		_vm->_state->setMagnetEffectUnk3(0);
509 	}
510 
511 	return true;
512 }
513 
applyForFace(uint face,Graphics::Surface * src,Graphics::Surface * dst)514 void MagnetEffect::applyForFace(uint face, Graphics::Surface *src, Graphics::Surface *dst) {
515 	FaceMask *mask = _facesMasks.getVal(face);
516 
517 	if (!mask)
518 		error("No mask for face %d", face);
519 
520 	apply(src, dst, mask->surface, _position * 256.0);
521 }
522 
apply(Graphics::Surface * src,Graphics::Surface * dst,Graphics::Surface * mask,int32 position)523 void MagnetEffect::apply(Graphics::Surface *src, Graphics::Surface *dst, Graphics::Surface *mask, int32 position) {
524 	uint32 *dstPtr = (uint32 *)dst->getPixels();
525 	byte *maskPtr = (byte *)mask->getPixels();
526 
527 	for (uint y = 0; y < dst->h; y++) {
528 		for (uint x = 0; x < dst->w; x++) {
529 			uint8 maskValue = *maskPtr;
530 
531 			if (maskValue != 0) {
532 				int32 displacement = _verticalDisplacement[(maskValue + position) % 256];
533 				int32 displacedY = CLIP<int32>(y + displacement, 0, src->h - 1);
534 
535 				uint32 srcValue1 = *(uint32 *) src->getBasePtr(x, displacedY);
536 				uint32 srcValue2 = *(uint32 *) src->getBasePtr(x, y);
537 
538 #ifdef SCUMM_BIG_ENDIAN
539 				*dstPtr = 0x000000FF | ((0x7F7F7F00 & (srcValue1 >> 1)) + (0x7F7F7F00 & (srcValue2 >> 1)));
540 #else
541 				*dstPtr = 0xFF000000 | ((0x007F7F7F & (srcValue1 >> 1)) + (0x007F7F7F & (srcValue2 >> 1)));
542 #endif
543 			}
544 
545 			maskPtr++;
546 			dstPtr++;
547 		}
548 	}
549 }
550 
ShakeEffect(Myst3Engine * vm)551 ShakeEffect::ShakeEffect(Myst3Engine *vm) :
552 		Effect(vm),
553 		_lastTick(0),
554 		_magnetEffectShakeStep(0),
555 		_pitchOffset(0),
556 		_headingOffset(0) {
557 }
558 
~ShakeEffect()559 ShakeEffect::~ShakeEffect() {
560 }
561 
create(Myst3Engine * vm)562 ShakeEffect *ShakeEffect::create(Myst3Engine *vm) {
563 	if (vm->_state->getShakeEffectAmpl() == 0) {
564 		return nullptr;
565 	}
566 
567 	return new ShakeEffect(vm);
568 }
569 
update()570 bool ShakeEffect::update() {
571 	// Check if the effect is active
572 	int32 ampl = _vm->_state->getShakeEffectAmpl();
573 	if (ampl == 0) {
574 		return false;
575 	}
576 
577 	// Check if the effect needs to be updated
578 	uint tick = _vm->_state->getTickCount();
579 	if (tick < _lastTick + _vm->_state->getShakeEffectTickPeriod()) {
580 		return false;
581 	}
582 
583 	if (_vm->_state->getMagnetEffectUnk3()) {
584 		// If the magnet effect is also active, use its parameters
585 		float magnetEffectAmpl = (_vm->_state->getMagnetEffectUnk1() + _vm->_state->getMagnetEffectUnk3()) / 32.0;
586 
587 		float shakeEffectAmpl;
588 		if (_magnetEffectShakeStep >= 2) {
589 			shakeEffectAmpl = ampl;
590 		} else {
591 			shakeEffectAmpl = -ampl;
592 		}
593 		_pitchOffset = shakeEffectAmpl / 200.0 * magnetEffectAmpl;
594 
595 		if (_magnetEffectShakeStep >= 1 && _magnetEffectShakeStep <= 2) {
596 			shakeEffectAmpl = ampl;
597 		} else {
598 			shakeEffectAmpl = -ampl;
599 		}
600 		_headingOffset = shakeEffectAmpl / 200.0 * magnetEffectAmpl;
601 
602 		_magnetEffectShakeStep++;
603 		_magnetEffectShakeStep %= 3;
604 	} else {
605 		// Shake effect only
606 		uint randomAmpl;
607 
608 		randomAmpl = _vm->_rnd->getRandomNumberRng(0, ampl);
609 		_pitchOffset = (randomAmpl - ampl / 2.0) / 100.0;
610 
611 		randomAmpl = _vm->_rnd->getRandomNumberRng(0, ampl);
612 		_headingOffset = (randomAmpl - ampl / 2.0) / 100.0;
613 	}
614 
615 	_lastTick = tick;
616 
617 	return true;
618 }
619 
applyForFace(uint face,Graphics::Surface * src,Graphics::Surface * dst)620 void ShakeEffect::applyForFace(uint face, Graphics::Surface* src, Graphics::Surface* dst) {
621 }
622 
RotationEffect(Myst3Engine * vm)623 RotationEffect::RotationEffect(Myst3Engine *vm) :
624 		Effect(vm),
625 		_lastUpdate(0),
626 		_headingOffset(0) {
627 }
628 
~RotationEffect()629 RotationEffect::~RotationEffect() {
630 }
631 
create(Myst3Engine * vm)632 RotationEffect *RotationEffect::create(Myst3Engine *vm) {
633 	if (vm->_state->getRotationEffectSpeed() == 0) {
634 		return nullptr;
635 	}
636 
637 	return new RotationEffect(vm);
638 }
639 
update()640 bool RotationEffect::update() {
641 	// Check if the effect is active
642 	int32 speed = _vm->_state->getRotationEffectSpeed();
643 	if (speed == 0) {
644 		return false;
645 	}
646 
647 	if (_lastUpdate != 0) {
648 		_headingOffset = speed * (g_system->getMillis() - _lastUpdate) / 1000.0;
649 	}
650 
651 	_lastUpdate = g_system->getMillis();
652 
653 	return true;
654 }
655 
applyForFace(uint face,Graphics::Surface * src,Graphics::Surface * dst)656 void RotationEffect::applyForFace(uint face, Graphics::Surface* src, Graphics::Surface* dst) {
657 }
658 
loadPattern()659 bool ShieldEffect::loadPattern() {
660 	// Read the shield effect support data
661 	const DirectorySubEntry *desc = _vm->getFileDescription("NARA", 10000, 0, DirectorySubEntry::kRawData);
662 	if (!desc) {
663 		return false;
664 	}
665 
666 	Common::MemoryReadStream *stream = desc->getData();
667 	if (stream->size() != 4096) {
668 		error("Incorrect shield effect support file size %d", stream->size());
669 	}
670 
671 	stream->read(_pattern, 4096);
672 
673 	delete stream;
674 
675 	return true;
676 }
677 
ShieldEffect(Myst3Engine * vm)678 ShieldEffect::ShieldEffect(Myst3Engine *vm):
679 	Effect(vm),
680 	_lastTick(0),
681 	_amplitude(1.0),
682 	_amplitudeIncrement(1.0 / 64.0) {
683 }
684 
~ShieldEffect()685 ShieldEffect::~ShieldEffect() {
686 
687 }
688 
create(Myst3Engine * vm,uint32 id)689 ShieldEffect *ShieldEffect::create(Myst3Engine *vm, uint32 id) {
690 	uint32 room = vm->_state->getLocationRoom();
691 	uint32 node = vm->_state->getLocationNode();
692 
693 	// This effect can only be found on Narayan cube nodes
694 	if (room != 801 || node >= 100)
695 		return nullptr;
696 
697 	ShieldEffect *s = new ShieldEffect(vm);
698 
699 	if (!s->loadPattern()) {
700 		delete s;
701 		return nullptr; // We don't have the effect file
702 	}
703 
704 	bool outerShieldUp = vm->_state->getOuterShieldUp();
705 	bool innerShieldUp = vm->_state->getInnerShieldUp();
706 	int32 saavedroStatus = vm->_state->getSaavedroStatus();
707 
708 	bool hasMasks = false;
709 
710 	int32 innerShieldMaskNode = 0;
711 	if (innerShieldUp) {
712 		innerShieldMaskNode = node + 100;
713 	}
714 
715 	if (outerShieldUp) {
716 		hasMasks |= s->loadMasks("NARA", node + 300, DirectorySubEntry::kShieldEffectMask);
717 		if (saavedroStatus == 2) {
718 			innerShieldMaskNode = node + 200;
719 		}
720 	}
721 
722 	if (innerShieldMaskNode) {
723 		hasMasks |= s->loadMasks("NARA", innerShieldMaskNode, DirectorySubEntry::kShieldEffectMask);
724 	}
725 
726 	if (innerShieldMaskNode && innerShieldUp && node > 6) {
727 		hasMasks |= s->loadMasks("NARA", node + 100, DirectorySubEntry::kShieldEffectMask);
728 	}
729 
730 	if (!hasMasks) {
731 		delete s;
732 		return nullptr;
733 	}
734 
735 	return s;
736 }
737 
update()738 bool ShieldEffect::update() {
739 	if (_vm->_state->getTickCount() == _lastTick)
740 		return false;
741 
742 	_lastTick = _vm->_state->getTickCount();
743 
744 	// Update the amplitude, varying between 1.0 and 4.0
745 	_amplitude += _amplitudeIncrement;
746 
747 	if (_amplitude >= 4.0) {
748 		_amplitude = 4.0;
749 		_amplitudeIncrement = -1.0 / 64.0;
750 	} else if (_amplitude <= 1.0) {
751 		_amplitude = 1.0;
752 		_amplitudeIncrement = 1.0 / 64.0;
753 	}
754 
755 	// Update the support data
756 	for (uint i = 0; i < ARRAYSIZE(_pattern); i++) {
757 		_pattern[i] += 2; // Intentional overflow
758 	}
759 
760 	// Update the displacement offsets
761 	for (uint i = 0; i < 256; i++) {
762 		_displacement[i] = (sin(i * 2 * M_PI / 255.0) + 1.0) * _amplitude;
763 	}
764 
765 	return true;
766 }
767 
applyForFace(uint face,Graphics::Surface * src,Graphics::Surface * dst)768 void ShieldEffect::applyForFace(uint face, Graphics::Surface *src, Graphics::Surface *dst) {
769 	if (!_vm->_state->getShieldEffectActive()) {
770 		return;
771 	}
772 
773 	FaceMask *mask = _facesMasks.getVal(face);
774 
775 	if (!mask)
776 		error("No mask for face %d", face);
777 
778 	uint32 *dstPtr = (uint32 *)dst->getPixels();
779 	byte *maskPtr = (byte *)mask->surface->getPixels();
780 
781 	for (uint y = 0; y < dst->h; y++) {
782 		for (uint x = 0; x < dst->w; x++) {
783 			uint8 maskValue = *maskPtr;
784 
785 			if (maskValue != 0) {
786 				int32 yOffset = _displacement[_pattern[(y % 64) * 64 + (x % 64)]];
787 
788 				if (yOffset > maskValue) {
789 					yOffset = maskValue;
790 				}
791 
792 				*dstPtr = *(uint32 *)src->getBasePtr(x, y + yOffset);
793 			}
794 
795 			maskPtr++;
796 			dstPtr++;
797 		}
798 	}
799 }
800 
801 } // End of namespace Myst3
802