diff options
Diffstat (limited to 'engines/gob/minigames/geisha/diving.cpp')
-rw-r--r-- | engines/gob/minigames/geisha/diving.cpp | 440 |
1 files changed, 417 insertions, 23 deletions
diff --git a/engines/gob/minigames/geisha/diving.cpp b/engines/gob/minigames/geisha/diving.cpp index e3bc69a503..6f4c6e168a 100644 --- a/engines/gob/minigames/geisha/diving.cpp +++ b/engines/gob/minigames/geisha/diving.cpp @@ -23,6 +23,7 @@ #include "common/list.h" #include "gob/global.h" +#include "gob/palanim.h" #include "gob/draw.h" #include "gob/video.h" #include "gob/decfile.h" @@ -31,93 +32,210 @@ #include "gob/sound/sound.h" #include "gob/minigames/geisha/evilfish.h" +#include "gob/minigames/geisha/oko.h" +#include "gob/minigames/geisha/meter.h" #include "gob/minigames/geisha/diving.h" namespace Gob { namespace Geisha { -static const int kEvilFishTypeCount = 3; +static const uint8 kAirDecreaseRate = 15; + +static const byte kPalette[48] = { + 0x00, 0x02, 0x12, + 0x01, 0x04, 0x1D, + 0x05, 0x08, 0x28, + 0x0C, 0x0D, 0x33, + 0x15, 0x14, 0x3F, + 0x00, 0x3F, 0x00, + 0x3F, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x21, 0x0D, 0x00, + 0x2F, 0x1A, 0x04, + 0x3D, 0x2B, 0x0D, + 0x10, 0x10, 0x10, + 0x1A, 0x1A, 0x1A, + 0x24, 0x24, 0x24, + 0x00, 0x01, 0x0F, + 0x3F, 0x3F, 0x3F +}; + +enum Animation { + kAnimationLungs = 0, + kAnimationHeart = 1, + kAnimationPearl = 4, + kAnimationJellyfish = 6, + kAnimationWater = 7, + kAnimationShot = 17, + kAnimationSwarmRedGreen = 32, + kAnimationSwarmOrange = 33 +}; -static const int kEvilFishTypes[kEvilFishTypeCount][5] = { + +const uint16 Diving::kEvilFishTypes[kEvilFishTypeCount][5] = { { 0, 14, 8, 9, 3}, // Shark {15, 1, 12, 13, 3}, // Moray {16, 2, 10, 11, 3} // Ray }; +const uint16 Diving::kPlantLevel1[] = { 18, 19, 20, 21 }; +const uint16 Diving::kPlantLevel2[] = { 22, 23, 24, 25 }; +const uint16 Diving::kPlantLevel3[] = { 26, 27, 28, 29, 30 }; + +const Diving::PlantLevel Diving::kPlantLevels[] = { + { 150, ARRAYSIZE(kPlantLevel1), kPlantLevel1 }, + { 120, ARRAYSIZE(kPlantLevel2), kPlantLevel2 }, + { 108, ARRAYSIZE(kPlantLevel3), kPlantLevel3 }, +}; + Diving::Diving(GobEngine *vm) : _vm(vm), _background(0), - _objects(0), _gui(0), _oko(0), _lungs(0), _heart(0), - _blackPearl(0), _whitePearlCount(0), _blackPearlCount(0) { + _objects(0), _gui(0), _okoAnim(0), _water(0), _lungs(0), _heart(0), + _blackPearl(0), _airMeter(0), _healthMeter(0), _isPlaying(false) { _blackPearl = new Surface(11, 8, 1); + + _airMeter = new Meter(3 , 195, 40, 2, 5, 7, 40, Meter::kFillToLeft); + _healthMeter = new Meter(275, 195, 40, 2, 6, 7, 4, Meter::kFillToLeft); + + for (uint i = 0; i < kEvilFishCount; i++) + _evilFish[i].evilFish = 0; + + for (uint i = 0; i < kDecorFishCount; i++) + _decorFish[i].decorFish = 0; + + for (uint i = 0; i < kPlantCount; i++) + _plant[i].plant = 0; + + for (uint i = 0; i < kMaxShotCount; i++) + _shot[i] = 0; + + _pearl.pearl = 0; + + _oko = 0; } Diving::~Diving() { + delete _airMeter; + delete _healthMeter; + delete _blackPearl; deinit(); } bool Diving::play(uint16 playerCount, bool hasPearlLocation) { + _hasPearlLocation = hasPearlLocation; + _isPlaying = true; + + // Fade to black + _vm->_palAnim->fade(0, 0, 0); + + // Initialize our playing field init(); initScreen(); initCursor(); + initPlants(); + + updateAirMeter(); + updateAnims(); _vm->_draw->blitInvalidated(); _vm->_video->retrace(); + // Fade in + _vm->_palAnim->fade(_vm->_global->_pPaletteDesc, 0, 0); + while (!_vm->shouldQuit()) { - checkShots(); + checkShots(); // Check if a shot hit something + checkOkoHurt(); // Check if Oko was hurt + + // Is Oko dead? + if (_oko->isPaused()) + break; + + // Update all objects and animations + updateAirMeter(); updateEvilFish(); updateDecorFish(); + updatePlants(); + updatePearl(); updateAnims(); _vm->_draw->animateCursor(1); + // Draw and wait for the end of the frame _vm->_draw->blitInvalidated(); - _vm->_util->waitEndFrame(); + + // Handle input _vm->_util->processInput(); int16 mouseX, mouseY; MouseButtons mouseButtons; int16 key = checkInput(mouseX, mouseY, mouseButtons); + + // Aborting the game if (key == kKeyEscape) break; + // Shoot the gun if (mouseButtons == kMouseButtonsLeft) shoot(mouseX, mouseY); + // Oko + handleOko(key); + + // Game end check if ((_whitePearlCount >= 20) || (_blackPearlCount >= 2)) break; } deinit(); + + _isPlaying = false; + + // The game succeeded when we got 2 black pearls return _blackPearlCount >= 2; } +bool Diving::isPlaying() const { + return _isPlaying; +} + +void Diving::cheatWin() { + _blackPearlCount = 2; +} + void Diving::init() { + // Load sounds + _vm->_sound->sampleLoad(&_soundShoot , SOUND_SND, "tirgim.snd"); + _vm->_sound->sampleLoad(&_soundBreathe , SOUND_SND, "respir.snd"); + _vm->_sound->sampleLoad(&_soundWhitePearl, SOUND_SND, "virtou.snd"); + _vm->_sound->sampleLoad(&_soundBlackPearl, SOUND_SND, "trouve.snd"); + + // Load and initialize sprites and animations _background = new DECFile(_vm, "tperle.dec" , 320, 200); _objects = new ANIFile(_vm, "tperle.ani" , 320); _gui = new ANIFile(_vm, "tperlcpt.ani", 320); - _oko = new ANIFile(_vm, "tplonge.ani" , 320); + _okoAnim = new ANIFile(_vm, "tplonge.ani" , 320); _water = new ANIObject(*_objects); _lungs = new ANIObject(*_gui); _heart = new ANIObject(*_gui); - _water->setAnimation(7); + _water->setAnimation(kAnimationWater); _water->setPosition(); _water->setVisible(true); - _lungs->setAnimation(0); + _lungs->setAnimation(kAnimationLungs); _lungs->setPosition(); _lungs->setVisible(true); _lungs->setPause(true); - _heart->setAnimation(1); + _heart->setAnimation(kAnimationHeart); _heart->setPosition(); _heart->setVisible(true); _heart->setPause(true); @@ -135,22 +253,39 @@ void Diving::init() { _decorFish[i].decorFish = new ANIObject(*_objects); } - _decorFish[0].decorFish->setAnimation( 6); // Jellyfish + for (uint i = 0; i < kPlantCount; i++) { + _plant[i].level = i / kPlantPerLevelCount; + _plant[i].deltaX = (kPlantLevelCount - _plant[i].level) * -2; + + _plant[i].x = -1; + _plant[i].y = -1; + + _plant[i].plant = new ANIObject(*_objects); + } + + _pearl.pearl = new ANIObject(*_objects); + _pearl.black = false; + + _pearl.pearl->setAnimation(kAnimationPearl); + + _decorFish[0].decorFish->setAnimation(kAnimationJellyfish); _decorFish[0].deltaX = 0; - _decorFish[1].decorFish->setAnimation(32); // Swarm of red/green fish - _decorFish[1].deltaX = -6; + _decorFish[1].decorFish->setAnimation(kAnimationSwarmRedGreen); + _decorFish[1].deltaX = -5; - _decorFish[2].decorFish->setAnimation(33); // Swarm of orange fish - _decorFish[2].deltaX = -6; + _decorFish[2].decorFish->setAnimation(kAnimationSwarmOrange); + _decorFish[2].deltaX = -5; for (uint i = 0; i < kMaxShotCount; i++) { _shot[i] = new ANIObject(*_objects); - _shot[i]->setAnimation(17); + _shot[i]->setAnimation(kAnimationShot); _shot[i]->setMode(ANIObject::kModeOnce); } + _oko = new Oko(*_okoAnim, *_vm->_sound, _soundBreathe); + Surface tmp(320, 103, 1); _vm->_video->drawPackedSprite("tperlobj.cmp", tmp); @@ -161,20 +296,30 @@ void Diving::init() { _currentShot = 0; + // Add the animations to our animation list _anims.push_back(_water); for (uint i = 0; i < kMaxShotCount; i++) _anims.push_back(_shot[i]); + _anims.push_back(_pearl.pearl); for (uint i = 0; i < kDecorFishCount; i++) _anims.push_back(_decorFish[i].decorFish); for (uint i = 0; i < kEvilFishCount; i++) _anims.push_back(_evilFish[i].evilFish); + for (int i = kPlantCount - 1; i >= 0; i--) + _anims.push_back(_plant[i].plant); + _anims.push_back(_oko); _anims.push_back(_lungs); _anims.push_back(_heart); - _vm->_sound->sampleLoad(&_soundShoot , SOUND_SND, "tirgim.snd"); - _vm->_sound->sampleLoad(&_soundBreathe , SOUND_SND, "respir.snd"); - _vm->_sound->sampleLoad(&_soundWhitePearl, SOUND_SND, "virtou.snd"); - _vm->_sound->sampleLoad(&_soundBlackPearl, SOUND_SND, "trouve.snd"); + // Air and health meter + _airMeter->setMaxValue(); + _healthMeter->setMaxValue(); + + _airCycle = 0; + _hurtGracePeriod = 0; + + _whitePearlCount = 0; + _blackPearlCount = 0; } void Diving::deinit() { @@ -208,11 +353,23 @@ void Diving::deinit() { _decorFish[i].decorFish = 0; } + for (uint i = 0; i < kPlantCount; i++) { + delete _plant[i].plant; + + _plant[i].plant = 0; + } + + delete _pearl.pearl; + _pearl.pearl = 0; + + delete _oko; + _oko = 0; + delete _heart; delete _lungs; delete _water; - delete _oko; + delete _okoAnim; delete _gui; delete _objects; delete _background; @@ -221,24 +378,30 @@ void Diving::deinit() { _heart = 0; _lungs = 0; - _oko = 0; + _okoAnim = 0; _gui = 0; _objects = 0; _background = 0; } void Diving::initScreen() { + // Set framerate _vm->_util->setFrameRate(15); - _vm->_video->setFullPalette(_vm->_global->_pPaletteDesc); + // Set palette + memcpy(_vm->_draw->_vgaPalette , kPalette, sizeof(kPalette)); + memcpy(_vm->_draw->_vgaSmallPalette, kPalette, sizeof(kPalette)); + // Draw background decal _vm->_draw->_backSurface->clear(); _background->draw(*_vm->_draw->_backSurface); + // Draw heart and lung boxes int16 left, top, right, bottom; _lungs->draw(*_vm->_draw->_backSurface, left, top, right, bottom); _heart->draw(*_vm->_draw->_backSurface, left, top, right, bottom); + // Mark everything as dirty _vm->_draw->dirtiedRect(_vm->_draw->_backSurface, 0, 0, 319, 199); } @@ -259,6 +422,89 @@ void Diving::initCursor() { _vm->_draw->_cursorHotspotY = 8; } + +void Diving::initPlants() { + // Create initial plantlife + for (uint i = 0; i < kPlantLevelCount; i++) { + for (uint j = 0; j < kPlantPerLevelCount; j++) { + int16 prevPlantX = -100; + if (j > 0) + prevPlantX = _plant[i * kPlantPerLevelCount + j - 1].x; + + enterPlant(_plant[i * kPlantPerLevelCount + j], prevPlantX); + } + } +} + +void Diving::enterPlant(ManagedPlant &plant, int16 prevPlantX) { + // Create a new plant outside the borders of the screen to scroll in + + const PlantLevel &level = kPlantLevels[plant.level]; + const uint anim = level.plants[_vm->_util->getRandom(kPlantLevels[plant.level].plantCount)]; + + plant.plant->setAnimation(anim); + plant.plant->rewind(); + + int16 width, height; + plant.plant->getFrameSize(width, height); + + // The new plant is created 140 - 160 pixels to the right of the right-most plant + plant.x = prevPlantX + 150 - 10 + _vm->_util->getRandom(21); + plant.y = kPlantLevels[plant.level].y - height; + + plant.plant->setPosition(plant.x, plant.y); + plant.plant->setVisible(true); + plant.plant->setPause(false); + + // If the plant is outside of the screen, create a pearl too if necessary + if (plant.x > 320) + enterPearl(plant.x); +} + +void Diving::enterPearl(int16 x) { + // Create a pearl outside the borders of the screen to scroll in + + // Only one pearl is ever visible + if (_pearl.pearl->isVisible()) + return; + + // Only every 4th potential pearl position has a pearl + if (_vm->_util->getRandom(4) != 0) + return; + + // Every 5th pearl is a black one, but only if the location is correct + _pearl.black = _hasPearlLocation && (_vm->_util->getRandom(5) == 0); + + // Set the pearl about in the middle of two bottom-level plants + _pearl.pearl->setPosition(x + 80, 130); + + _pearl.pearl->setVisible(true); + _pearl.pearl->setPause(false); + _pearl.picked = false; +} + +void Diving::updateAirMeter() { + if (_oko->isBreathing()) { + // If Oko is breathing, increase the air meter and play the lungs animation + _airCycle = 0; + _airMeter->increase(); + _lungs->setPause(false); + return; + } else + // Otherwise, don't play the lungs animation + _lungs->setPause(true); + + // Update the air cycle and decrease the air meter when the cycle ended + _airCycle = (_airCycle + 1) % kAirDecreaseRate; + + if (_airCycle == 0) + _airMeter->decrease(); + + // Without any air, Oko dies + if (_airMeter->getValue() == 0) + _oko->die(); +} + void Diving::updateEvilFish() { for (uint i = 0; i < kEvilFishCount; i++) { ManagedEvilFish &fish = _evilFish[i]; @@ -283,6 +529,7 @@ void Diving::updateEvilFish() { fish.enterAt = _vm->_util->getTimeKey() + 2000 + _vm->_util->getRandom(8000); if (_vm->_util->getTimeKey() >= fish.enterAt) { + // The new fish has a random type int fishType = _vm->_util->getRandom(kEvilFishTypeCount); fish.evilFish->mutate(kEvilFishTypes[fishType][0], kEvilFishTypes[fishType][1], kEvilFishTypes[fishType][2], kEvilFishTypes[fishType][3], @@ -333,9 +580,100 @@ void Diving::updateDecorFish() { } } +void Diving::updatePlants() { + // When Oko isn't moving, the plants don't continue to scroll by + if (!_oko->isMoving()) + return; + + for (uint i = 0; i < kPlantCount; i++) { + ManagedPlant &plant = _plant[i]; + + if (plant.plant->isVisible()) { + // Move the plant + plant.plant->setPosition(plant.x += plant.deltaX, plant.y); + + // Check if the plant has left the screen + int16 x, y, width, height; + plant.plant->getFramePosition(x, y); + plant.plant->getFrameSize(width, height); + + if ((x + width) <= 0) { + plant.plant->setVisible(false); + plant.plant->setPause(true); + + plant.x = 0; + } + + } else { + // Find the right-most plant in this level and enter the plant to the right of it + + int16 rightX = 320; + for (uint j = 0; j < kPlantPerLevelCount; j++) + rightX = MAX(rightX, _plant[plant.level * kPlantPerLevelCount + j].x); + + enterPlant(plant, rightX); + } + } +} + +void Diving::updatePearl() { + if (!_pearl.pearl->isVisible()) + return; + + // When Oko isn't moving, the pearl doesn't continue to scroll by + if (!_oko->isMoving()) + return; + + // Picking the pearl + if (_pearl.picked && (_oko->getState() == Oko::kStatePick) && (_oko->getFrame() == 8)) { + // Remove the pearl + _pearl.pearl->setVisible(false); + _pearl.pearl->setPause(true); + + // Add the pearl to our found pearls repository + if (_pearl.black) + foundBlackPearl(); + else + foundWhitePearl(); + + return; + } + + // Move the pearl + int16 x, y, width, height; + _pearl.pearl->getPosition(x, y); + _pearl.pearl->setPosition(x - 5, y); + + // Check if the pearl has left the screen + _pearl.pearl->getFramePosition(x, y); + _pearl.pearl->getFrameSize(width, height); + + if ((x + width) <= 0) { + _pearl.pearl->setVisible(false); + _pearl.pearl->setPause(true); + } +} + +void Diving::getPearl() { + if (!_pearl.pearl->isVisible()) + return; + + // Make sure the pearl is within Oko's grasp + + int16 x, y, width, height; + _pearl.pearl->getFramePosition(x, y); + _pearl.pearl->getFrameSize(width, height); + + if ((x > 190) || ((x + width) < 140)) + return; + + _pearl.picked = true; +} + void Diving::foundBlackPearl() { _blackPearlCount++; + // Put the black pearl drawing into the black pearl box if (_blackPearlCount == 1) { _vm->_draw->_backSurface->blit(*_blackPearl, 0, 0, 10, 7, 147, 179, 0); _vm->_draw->dirtiedRect(_vm->_draw->_backSurface, 147, 179, 157, 186); @@ -343,17 +681,22 @@ void Diving::foundBlackPearl() { _vm->_draw->_backSurface->blit(*_blackPearl, 0, 0, 10, 7, 160, 179, 0); _vm->_draw->dirtiedRect(_vm->_draw->_backSurface, 147, 179, 160, 186); } + + _vm->_sound->blasterPlay(&_soundBlackPearl, 1, 0); } void Diving::foundWhitePearl() { _whitePearlCount++; + // Put the white pearl drawing into the white pearl box int16 x = 54 + (_whitePearlCount - 1) * 8; if (_whitePearlCount > 10) x += 48; _background->drawLayer(*_vm->_draw->_backSurface, 0, 2, x, 177, 0); _vm->_draw->dirtiedRect(_vm->_draw->_backSurface, x, 177, x + 3, 180); + + _vm->_sound->blasterPlay(&_soundWhitePearl, 1, 0); } void Diving::updateAnims() { @@ -376,6 +719,13 @@ void Diving::updateAnims() { (*a)->advance(); } + + // Draw the meters + _airMeter->draw(*_vm->_draw->_backSurface, left, top, right, bottom); + _vm->_draw->dirtiedRect(_vm->_draw->_backSurface, left, top, right, bottom); + + _healthMeter->draw(*_vm->_draw->_backSurface, left, top, right, bottom); + _vm->_draw->dirtiedRect(_vm->_draw->_backSurface, left, top, right, bottom); } int16 Diving::checkInput(int16 &mouseX, int16 &mouseY, MouseButtons &mouseButtons) { @@ -410,6 +760,7 @@ void Diving::shoot(int16 mouseX, int16 mouseY) { void Diving::checkShots() { Common::List<int>::iterator activeShot = _activeShots.begin(); + // Check if we hit something with our shots while (activeShot != _activeShots.end()) { ANIObject &shot = *_shot[*activeShot]; @@ -418,6 +769,7 @@ void Diving::checkShots() { shot.getPosition(x, y); + // When we hit an evil fish, it dies for (uint i = 0; i < kEvilFishCount; i++) { EvilFish &evilFish = *_evilFish[i].evilFish; @@ -434,6 +786,48 @@ void Diving::checkShots() { } } +void Diving::handleOko(int16 key) { + if (key == kKeyDown) { + // Oko sinks down a level or picks up a pearl if already at the bottom + _oko->sink(); + + if ((_oko->getState() == Oko::kStatePick) && (_oko->getFrame() == 0)) + getPearl(); + + } else if (key == kKeyUp) + // Oko raises up a level or surfaces to breathe if already at the top + _oko->raise(); +} + +void Diving::checkOkoHurt() { + if (_oko->getState() != Oko::kStateSwim) + return; + + // Give Oko a grace period after being hurt + if (_hurtGracePeriod > 0) { + _hurtGracePeriod--; + return; + } + + // Check for a fish/Oko-collision + for (uint i = 0; i < kEvilFishCount; i++) { + EvilFish &evilFish = *_evilFish[i].evilFish; + + if (!evilFish.isDead() && evilFish.isIn(*_oko)) { + _healthMeter->decrease(); + + // If the health reached 0, Oko dies. Otherwise, she gets hurt + if (_healthMeter->getValue() == 0) + _oko->die(); + else + _oko->hurt(); + + _hurtGracePeriod = 10; + break; + } + } +} + } // End of namespace Geisha } // End of namespace Gob |