aboutsummaryrefslogtreecommitdiff
path: root/engines/gob/minigames
diff options
context:
space:
mode:
authorWillem Jan Palenstijn2013-04-18 23:35:23 +0200
committerWillem Jan Palenstijn2013-05-08 20:40:58 +0200
commit9c2341678ef4984bf92b3878295250faf980b066 (patch)
tree2fb4805e05e16b9924e80c9947e6bad723b28c4b /engines/gob/minigames
parent8172d679df5148a4a32f46074b20cb6caf91844f (diff)
parenta5f4ff36ffc386d48f2da49387a9655ce9295a4d (diff)
downloadscummvm-rg350-9c2341678ef4984bf92b3878295250faf980b066.tar.gz
scummvm-rg350-9c2341678ef4984bf92b3878295250faf980b066.tar.bz2
scummvm-rg350-9c2341678ef4984bf92b3878295250faf980b066.zip
Merge branch 'master'
Diffstat (limited to 'engines/gob/minigames')
-rw-r--r--engines/gob/minigames/geisha/diving.cpp440
-rw-r--r--engines/gob/minigames/geisha/diving.h68
-rw-r--r--engines/gob/minigames/geisha/evilfish.cpp17
-rw-r--r--engines/gob/minigames/geisha/evilfish.h6
-rw-r--r--engines/gob/minigames/geisha/meter.cpp116
-rw-r--r--engines/gob/minigames/geisha/meter.h89
-rw-r--r--engines/gob/minigames/geisha/oko.cpp170
-rw-r--r--engines/gob/minigames/geisha/oko.h84
8 files changed, 950 insertions, 40 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
diff --git a/engines/gob/minigames/geisha/diving.h b/engines/gob/minigames/geisha/diving.h
index e386d783d7..089d60b260 100644
--- a/engines/gob/minigames/geisha/diving.h
+++ b/engines/gob/minigames/geisha/diving.h
@@ -40,6 +40,8 @@ class ANIObject;
namespace Geisha {
class EvilFish;
+class Oko;
+class Meter;
/** Geisha's "Diving" minigame. */
class Diving {
@@ -49,11 +51,34 @@ public:
bool play(uint16 playerCount, bool hasPearlLocation);
+ bool isPlaying() const;
+ void cheatWin();
+
private:
static const uint kEvilFishCount = 3;
static const uint kDecorFishCount = 3;
static const uint kMaxShotCount = 10;
+ static const uint kEvilFishTypeCount = 3;
+ static const uint16 kEvilFishTypes[kEvilFishTypeCount][5];
+
+ struct PlantLevel {
+ int16 y;
+ uint plantCount;
+ const uint16 *plants;
+ };
+
+ static const uint kPlantLevelCount = 3;
+ static const uint kPlantPerLevelCount = 5;
+
+ static const uint16 kPlantLevel1[];
+ static const uint16 kPlantLevel2[];
+ static const uint16 kPlantLevel3[];
+
+ static const PlantLevel kPlantLevels[kPlantLevelCount];
+
+ static const uint kPlantCount = kPlantLevelCount * kPlantPerLevelCount;
+
struct ManagedEvilFish {
EvilFish *evilFish;
@@ -68,12 +93,27 @@ private:
int8 deltaX;
};
+ struct ManagedPlant {
+ ANIObject *plant;
+
+ uint level;
+ int8 deltaX;
+ int16 x, y;
+ };
+
+ struct ManagedPearl {
+ ANIObject *pearl;
+
+ bool picked;
+ bool black;
+ };
+
GobEngine *_vm;
DECFile *_background;
ANIFile *_objects;
ANIFile *_gui;
- ANIFile *_oko;
+ ANIFile *_okoAnim;
ANIObject *_water;
ANIObject *_lungs;
@@ -81,6 +121,10 @@ private:
ManagedEvilFish _evilFish[kEvilFishCount];
ManagedDecorFish _decorFish[kDecorFishCount];
+ ManagedPlant _plant[kPlantCount];
+ ManagedPearl _pearl;
+
+ Oko *_oko;
ANIObject *_shot[kMaxShotCount];
@@ -93,6 +137,12 @@ private:
uint8 _whitePearlCount;
uint8 _blackPearlCount;
+ Meter *_airMeter;
+ Meter *_healthMeter;
+
+ uint8 _airCycle;
+ uint8 _hurtGracePeriod;
+
uint8 _currentShot;
SoundDesc _soundShoot;
@@ -100,24 +150,40 @@ private:
SoundDesc _soundWhitePearl;
SoundDesc _soundBlackPearl;
+ bool _hasPearlLocation;
+ bool _isPlaying;
+
void init();
void deinit();
void initScreen();
void initCursor();
+ void initPlants();
+
+ void enterPlant(ManagedPlant &plant, int16 prevPlantX);
+ void enterPearl(int16 x);
+
+ void getPearl();
void foundBlackPearl();
void foundWhitePearl();
+ void updateAirMeter();
void updateEvilFish();
void updateDecorFish();
+ void updatePlants();
+ void updatePearl();
void updateAnims();
int16 checkInput(int16 &mouseX, int16 &mouseY, MouseButtons &mouseButtons);
void shoot(int16 mouseX, int16 mouseY);
void checkShots();
+
+ void handleOko(int16 key);
+
+ void checkOkoHurt();
};
} // End of namespace Geisha
diff --git a/engines/gob/minigames/geisha/evilfish.cpp b/engines/gob/minigames/geisha/evilfish.cpp
index e9503f4aed..c7ef9d5622 100644
--- a/engines/gob/minigames/geisha/evilfish.cpp
+++ b/engines/gob/minigames/geisha/evilfish.cpp
@@ -39,19 +39,6 @@ EvilFish::EvilFish(const ANIFile &ani, uint16 screenWidth,
EvilFish::~EvilFish() {
}
-bool EvilFish::isIn(int16 x, int16 y) const {
- int16 frameX, frameY, frameWidth, frameHeight;
- getFramePosition(frameX, frameY);
- getFrameSize(frameWidth, frameHeight);
-
- if ((x < frameX) || (y < frameY))
- return false;
- if ((x > (frameX + frameWidth)) || (y > (frameY + frameHeight)))
- return false;
-
- return true;
-}
-
void EvilFish::enter(Direction from, int16 y) {
_shouldLeave = false;
@@ -184,6 +171,10 @@ void EvilFish::mutate(uint16 animSwimLeft, uint16 animSwimRight,
}
}
+bool EvilFish::isDead() {
+ return !isVisible() || (_state == kStateNone) || (_state == kStateDie);
+}
+
} // End of namespace Geisha
} // End of namespace Gob
diff --git a/engines/gob/minigames/geisha/evilfish.h b/engines/gob/minigames/geisha/evilfish.h
index 223645f47f..81efb676e2 100644
--- a/engines/gob/minigames/geisha/evilfish.h
+++ b/engines/gob/minigames/geisha/evilfish.h
@@ -42,9 +42,6 @@ public:
uint16 animTurnLeft, uint16 animTurnRight, uint16 animDie);
~EvilFish();
- /** Are there coordinates within the fish's sprite? */
- bool isIn(int16 x, int16 y) const;
-
/** Enter from this direction / screen edge. */
void enter(Direction from, int16 y);
/** Leave the screen in the current direction. */
@@ -60,6 +57,9 @@ public:
void mutate(uint16 animSwimLeft, uint16 animSwimRight,
uint16 animTurnLeft, uint16 animTurnRight, uint16 animDie);
+ /** Is the fish dead? */
+ bool isDead();
+
private:
enum State {
kStateNone,
diff --git a/engines/gob/minigames/geisha/meter.cpp b/engines/gob/minigames/geisha/meter.cpp
new file mode 100644
index 0000000000..e3b9bd1ccf
--- /dev/null
+++ b/engines/gob/minigames/geisha/meter.cpp
@@ -0,0 +1,116 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/util.h"
+
+#include "gob/surface.h"
+
+#include "gob/minigames/geisha/meter.h"
+
+namespace Gob {
+
+namespace Geisha {
+
+Meter::Meter(int16 x, int16 y, int16 width, int16 height, uint8 frontColor,
+ uint8 backColor, int32 maxValue, Direction direction) :
+ _x(x), _y(y), _width(width), _height(height), _frontColor(frontColor),
+ _backColor(backColor), _value(0), _maxValue(maxValue), _direction(direction),
+ _needUpdate(true), _surface(0) {
+
+}
+
+Meter::~Meter() {
+ delete _surface;
+}
+
+int32 Meter::getValue() const {
+ return _value;
+}
+
+void Meter::setValue(int32 value) {
+ value = CLIP<int32>(value, 0, _maxValue);
+ if (_value == value)
+ return;
+
+ _value = value;
+ _needUpdate = true;
+}
+
+void Meter::setMaxValue() {
+ setValue(_maxValue);
+}
+
+void Meter::increase(int32 n) {
+ int32 value = CLIP<int32>(_value + n, 0, _maxValue);
+ if (_value == value)
+ return;
+
+ _value = value;
+ _needUpdate = true;
+}
+
+void Meter::decrease(int32 n) {
+ int32 value = CLIP<int32>(_value - n, 0, _maxValue);
+ if (_value == value)
+ return;
+
+ _value = value;
+ _needUpdate = true;
+}
+
+void Meter::draw(Surface &dest, int16 &left, int16 &top, int16 &right, int16 &bottom) {
+ if (!_surface) {
+ _surface = new Surface(_width, _height, dest.getBPP());
+ _needUpdate = true;
+ }
+
+ update();
+
+ left = CLIP<int16>(_x , 0, dest.getWidth () - 1);
+ top = CLIP<int16>(_y , 0, dest.getHeight() - 1);
+ right = CLIP<int16>(_x + _width - 1, 0, dest.getWidth () - 1);
+ bottom = CLIP<int16>(_y + _height - 1, 0, dest.getHeight() - 1);
+
+ dest.blit(*_surface, left - _x, top - _y, _width, _height, left, top);
+}
+
+void Meter::update() {
+ if (!_needUpdate)
+ return;
+
+ _needUpdate = false;
+
+ _surface->fill(_backColor);
+
+ int32 n = (int32)floor((((float) _width) / _maxValue * _value) + 0.5);
+ if (n <= 0)
+ return;
+
+ if (_direction == kFillToLeft)
+ _surface->fillRect(_width - n, 0, _width - 1, _height - 1, _frontColor);
+ else
+ _surface->fillRect(0 , 0, n - 1, _height - 1, _frontColor);
+}
+
+} // End of namespace Geisha
+
+} // End of namespace Gob
diff --git a/engines/gob/minigames/geisha/meter.h b/engines/gob/minigames/geisha/meter.h
new file mode 100644
index 0000000000..a9bdb14d0f
--- /dev/null
+++ b/engines/gob/minigames/geisha/meter.h
@@ -0,0 +1,89 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef GOB_MINIGAMES_GEISHA_METER_H
+#define GOB_MINIGAMES_GEISHA_METER_H
+
+#include "gob/aniobject.h"
+
+namespace Gob {
+
+class Surface;
+
+namespace Geisha {
+
+/** A meter measuring a value. */
+class Meter {
+public:
+ enum Direction {
+ kFillToLeft,
+ kFillToRight
+ };
+
+ Meter(int16 x, int16 y, int16 width, int16 height,
+ uint8 frontColor, uint8 backColor, int32 maxValue,
+ Direction direction);
+ ~Meter();
+
+ /** Return the current value the meter is measuring. */
+ int32 getValue() const;
+
+ /** Set the current value the meter is measuring. */
+ void setValue(int32 value);
+
+ /** Set the current value the meter is measuring to the max value. */
+ void setMaxValue();
+
+ /** Increase the current value the meter is measuring. */
+ void increase(int32 n = 1);
+ /** Decrease the current value the meter is measuring. */
+ void decrease(int32 n = 1);
+
+ /** Draw the meter onto the surface and return the affected rectangle. */
+ void draw(Surface &dest, int16 &left, int16 &top, int16 &right, int16 &bottom);
+
+private:
+ int16 _x;
+ int16 _y;
+ int16 _width;
+ int16 _height;
+
+ uint8 _frontColor;
+ uint8 _backColor;
+
+ int32 _value;
+ int32 _maxValue;
+
+ Direction _direction;
+
+ bool _needUpdate;
+
+ Surface *_surface;
+
+ void update();
+};
+
+} // End of namespace Geisha
+
+} // End of namespace Gob
+
+#endif // GOB_MINIGAMES_GEISHA_METER_H
diff --git a/engines/gob/minigames/geisha/oko.cpp b/engines/gob/minigames/geisha/oko.cpp
new file mode 100644
index 0000000000..7ad8be6fa2
--- /dev/null
+++ b/engines/gob/minigames/geisha/oko.cpp
@@ -0,0 +1,170 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "gob/sound/sound.h"
+
+#include "gob/minigames/geisha/oko.h"
+
+namespace Gob {
+
+namespace Geisha {
+
+enum kOkoAnimation {
+ kOkoAnimationEnter = 0,
+ kOkoAnimationSwim = 1,
+ kOkoAnimationSink = 8,
+ kOkoAnimationRaise = 7,
+ kOkoAnimationBreathe = 2,
+ kOkoAnimationPick = 3,
+ kOkoAnimationHurt = 4,
+ kOkoAnimationDie0 = 17,
+ kOkoAnimationDie1 = 18,
+ kOkoAnimationDie2 = 19
+};
+
+static const int16 kOkoPositionX = 110;
+
+static const uint kLevelCount = 3;
+static const int16 kLevelPositionX[kLevelCount] = { 44, 84, 124 };
+
+
+Oko::Oko(const ANIFile &ani, Sound &sound, SoundDesc &breathe) :
+ ANIObject(ani), _sound(&sound), _breathe(&breathe), _state(kStateEnter), _level(0) {
+
+ setAnimation(kOkoAnimationEnter);
+ setVisible(true);
+}
+
+Oko::~Oko() {
+}
+
+void Oko::advance() {
+ bool wasLastFrame = lastFrame();
+
+ if ((_state == kStateDead) && wasLastFrame) {
+ setPause(true);
+ return;
+ }
+
+ ANIObject::advance();
+
+ switch (_state) {
+ case kStateEnter:
+ if (wasLastFrame) {
+ setAnimation(kOkoAnimationSwim);
+ setPosition(kOkoPositionX, kLevelPositionX[_level]);
+ _state = kStateSwim;
+ }
+ break;
+
+ case kStateBreathe:
+ if ((getFrame() == 6) || (getFrame() == 23))
+ _sound->blasterPlay(_breathe, 1, 0);
+ case kStateSink:
+ case kStateRaise:
+ case kStateHurt:
+ if (wasLastFrame) {
+ setAnimation(kOkoAnimationSwim);
+ setPosition(kOkoPositionX, kLevelPositionX[_level]);
+ _state = kStateSwim;
+ }
+ break;
+
+ case kStatePick:
+ if (wasLastFrame) {
+ _level = 1;
+ setAnimation(kOkoAnimationSwim);
+ setPosition(kOkoPositionX, kLevelPositionX[_level]);
+ _state = kStateSwim;
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+void Oko::sink() {
+ if (_state != kStateSwim)
+ return;
+
+ if (_level >= (kLevelCount - 1)) {
+ setAnimation(kOkoAnimationPick);
+ _state = kStatePick;
+ return;
+ }
+
+ setAnimation(kOkoAnimationSink);
+ setPosition(kOkoPositionX, kLevelPositionX[_level]);
+ _state = kStateSink;
+
+ _level++;
+}
+
+void Oko::raise() {
+ if (_state != kStateSwim)
+ return;
+
+ if (_level == 0) {
+ setAnimation(kOkoAnimationBreathe);
+ _state = kStateBreathe;
+ return;
+ }
+
+ setAnimation(kOkoAnimationRaise);
+ setPosition(kOkoPositionX, kLevelPositionX[_level]);
+ _state = kStateSink;
+
+ _level--;
+}
+
+void Oko::hurt() {
+ if (_state != kStateSwim)
+ return;
+
+ setAnimation(kOkoAnimationHurt);
+ _state = kStateHurt;
+}
+
+void Oko::die() {
+ if (_state != kStateSwim)
+ return;
+
+ setAnimation(kOkoAnimationDie0 + _level);
+ _state = kStateDead;
+}
+
+Oko::State Oko::getState() const {
+ return _state;
+}
+
+bool Oko::isBreathing() const {
+ return (_state == kStateBreathe) && ((getFrame() >= 9) && (getFrame() <= 30));
+}
+
+bool Oko::isMoving() const {
+ return (_state != kStateBreathe) && (_state != kStateHurt) && (_state != kStateDead);
+}
+
+} // End of namespace Geisha
+
+} // End of namespace Gob
diff --git a/engines/gob/minigames/geisha/oko.h b/engines/gob/minigames/geisha/oko.h
new file mode 100644
index 0000000000..82c6f59be4
--- /dev/null
+++ b/engines/gob/minigames/geisha/oko.h
@@ -0,0 +1,84 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef GOB_MINIGAMES_GEISHA_OKO_H
+#define GOB_MINIGAMES_GEISHA_OKO_H
+
+#include "gob/aniobject.h"
+
+namespace Gob {
+
+class Sound;
+class SoundDesc;
+
+namespace Geisha {
+
+/** Oko, the person you control, in Geisha's "Diving" minigame. */
+class Oko : public ANIObject {
+public:
+ enum State {
+ kStateEnter,
+ kStateSwim,
+ kStateSink,
+ kStateRaise,
+ kStateBreathe,
+ kStatePick,
+ kStateHurt,
+ kStateDead
+ };
+
+ Oko(const ANIFile &ani, Sound &sound, SoundDesc &breathe);
+ ~Oko();
+
+ /** Advance the animation to the next frame. */
+ void advance();
+
+ /** Oko should sink a level. */
+ void sink();
+ /** Oko should raise a level. */
+ void raise();
+
+ /** Oko should get hurt. */
+ void hurt();
+
+ /** Oko should die. */
+ void die();
+
+ State getState() const;
+
+ bool isBreathing() const;
+ bool isMoving() const;
+
+private:
+ Sound *_sound;
+ SoundDesc *_breathe;
+
+ State _state;
+
+ uint8 _level;
+};
+
+} // End of namespace Geisha
+
+} // End of namespace Gob
+
+#endif // GOB_MINIGAMES_GEISHA_OKO_H