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