mirror of
https://github.com/scummvm/scummvm.git
synced 2025-04-02 10:52:32 -04:00
831 lines
20 KiB
C++
831 lines
20 KiB
C++
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "common/list.h"
|
|
|
|
#include "gob/global.h"
|
|
#include "gob/palanim.h"
|
|
#include "gob/draw.h"
|
|
#include "gob/video.h"
|
|
#include "gob/decfile.h"
|
|
#include "gob/anifile.h"
|
|
|
|
#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 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
|
|
};
|
|
|
|
|
|
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(nullptr),
|
|
_objects(nullptr), _gui(nullptr), _okoAnim(nullptr), _water(nullptr), _lungs(nullptr), _heart(nullptr),
|
|
_blackPearl(nullptr), _airMeter(nullptr), _healthMeter(nullptr), _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 = nullptr;
|
|
|
|
for (uint i = 0; i < kDecorFishCount; i++)
|
|
_decorFish[i].decorFish = nullptr;
|
|
|
|
for (uint i = 0; i < kPlantCount; i++)
|
|
_plant[i].plant = nullptr;
|
|
|
|
for (uint i = 0; i < kMaxShotCount; i++)
|
|
_shot[i] = nullptr;
|
|
|
|
_pearl.pearl = nullptr;
|
|
|
|
_oko = nullptr;
|
|
}
|
|
|
|
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(nullptr, 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(); // 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);
|
|
_okoAnim = new ANIFile(_vm, "tplonge.ani" , 320);
|
|
|
|
_water = new ANIObject(*_objects);
|
|
_lungs = new ANIObject(*_gui);
|
|
_heart = new ANIObject(*_gui);
|
|
|
|
_water->setAnimation(kAnimationWater);
|
|
_water->setPosition();
|
|
_water->setVisible(true);
|
|
|
|
_lungs->setAnimation(kAnimationLungs);
|
|
_lungs->setPosition();
|
|
_lungs->setVisible(true);
|
|
_lungs->setPause(true);
|
|
|
|
_heart->setAnimation(kAnimationHeart);
|
|
_heart->setPosition();
|
|
_heart->setVisible(true);
|
|
_heart->setPause(true);
|
|
|
|
for (uint i = 0; i < kEvilFishCount; i++) {
|
|
_evilFish[i].enterAt = 0;
|
|
_evilFish[i].leaveAt = 0;
|
|
|
|
_evilFish[i].evilFish = new EvilFish(*_objects, 320, 0, 0, 0, 0, 0);
|
|
}
|
|
|
|
for (uint i = 0; i < kDecorFishCount; i++) {
|
|
_decorFish[i].enterAt = 0;
|
|
|
|
_decorFish[i].decorFish = new ANIObject(*_objects);
|
|
}
|
|
|
|
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(kAnimationSwarmRedGreen);
|
|
_decorFish[1].deltaX = -5;
|
|
|
|
_decorFish[2].decorFish->setAnimation(kAnimationSwarmOrange);
|
|
_decorFish[2].deltaX = -5;
|
|
|
|
for (uint i = 0; i < kMaxShotCount; i++) {
|
|
_shot[i] = new ANIObject(*_objects);
|
|
|
|
_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);
|
|
|
|
_blackPearl->blit(tmp, 282, 80, 292, 87, 0, 0);
|
|
|
|
_blackPearlCount = 0;
|
|
|
|
_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);
|
|
|
|
// Air and health meter
|
|
_airMeter->setMaxValue();
|
|
_healthMeter->setMaxValue();
|
|
|
|
_airCycle = 0;
|
|
_hurtGracePeriod = 0;
|
|
|
|
_whitePearlCount = 0;
|
|
_blackPearlCount = 0;
|
|
}
|
|
|
|
void Diving::deinit() {
|
|
_vm->_draw->_cursorHotspotX = -1;
|
|
_vm->_draw->_cursorHotspotY = -1;
|
|
|
|
_soundShoot.free();
|
|
_soundBreathe.free();
|
|
_soundWhitePearl.free();
|
|
_soundBlackPearl.free();
|
|
|
|
_anims.clear();
|
|
|
|
_activeShots.clear();
|
|
|
|
for (uint i = 0; i < kMaxShotCount; i++) {
|
|
delete _shot[i];
|
|
|
|
_shot[i] = nullptr;
|
|
}
|
|
|
|
for (uint i = 0; i < kEvilFishCount; i++) {
|
|
delete _evilFish[i].evilFish;
|
|
|
|
_evilFish[i].evilFish = nullptr;
|
|
}
|
|
|
|
for (uint i = 0; i < kDecorFishCount; i++) {
|
|
delete _decorFish[i].decorFish;
|
|
|
|
_decorFish[i].decorFish = nullptr;
|
|
}
|
|
|
|
for (uint i = 0; i < kPlantCount; i++) {
|
|
delete _plant[i].plant;
|
|
|
|
_plant[i].plant = nullptr;
|
|
}
|
|
|
|
delete _pearl.pearl;
|
|
_pearl.pearl = nullptr;
|
|
|
|
delete _oko;
|
|
_oko = nullptr;
|
|
|
|
delete _heart;
|
|
delete _lungs;
|
|
delete _water;
|
|
|
|
delete _okoAnim;
|
|
delete _gui;
|
|
delete _objects;
|
|
delete _background;
|
|
|
|
_water = nullptr;
|
|
_heart = nullptr;
|
|
_lungs = nullptr;
|
|
|
|
_okoAnim = nullptr;
|
|
_gui = nullptr;
|
|
_objects = nullptr;
|
|
_background = nullptr;
|
|
}
|
|
|
|
void Diving::initScreen() {
|
|
// Set framerate
|
|
_vm->_util->setFrameRate(15);
|
|
|
|
// Set palette
|
|
memcpy(_vm->_draw->_vgaPalette, 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);
|
|
}
|
|
|
|
void Diving::initCursor() {
|
|
const int index = _vm->_draw->_cursorIndex;
|
|
|
|
const int16 left = index * _vm->_draw->_cursorWidth;
|
|
const int16 top = 0;
|
|
const int16 right = left + _vm->_draw->_cursorWidth - 1;
|
|
const int16 bottom = _vm->_draw->_cursorHeight - 1;
|
|
|
|
_vm->_draw->_cursorSprites->fillRect(left, top, right, bottom, 0);
|
|
|
|
_objects->draw(*_vm->_draw->_cursorSprites, 31, 0, left, top);
|
|
_vm->_draw->_cursorAnimLow[index] = 0;
|
|
|
|
_vm->_draw->_cursorHotspotX = 8;
|
|
_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];
|
|
|
|
if (fish.evilFish->isVisible()) {
|
|
// Evil fishes leave on their own after 30s - 40s
|
|
|
|
fish.enterAt = 0;
|
|
|
|
if (fish.leaveAt == 0)
|
|
fish.leaveAt = _vm->_util->getTimeKey() + 30000 + _vm->_util->getRandom(10000);
|
|
|
|
if (_vm->_util->getTimeKey() >= fish.leaveAt)
|
|
fish.evilFish->leave();
|
|
|
|
} else {
|
|
// Evil fishes enter the screen in 2s - 10s
|
|
|
|
fish.leaveAt = 0;
|
|
|
|
if (fish.enterAt == 0)
|
|
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],
|
|
kEvilFishTypes[fishType][4]);
|
|
|
|
fish.evilFish->enter((EvilFish::Direction)_vm->_util->getRandom(2),
|
|
36 + _vm->_util->getRandom(3) * 40);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Diving::updateDecorFish() {
|
|
for (uint i = 0; i < kDecorFishCount; i++) {
|
|
ManagedDecorFish &fish = _decorFish[i];
|
|
|
|
if (fish.decorFish->isVisible()) {
|
|
// Move the fish
|
|
int16 x, y;
|
|
fish.decorFish->getPosition(x, y);
|
|
fish.decorFish->setPosition(x + fish.deltaX, y);
|
|
|
|
// Check if the fish has left the screen
|
|
int16 width, height;
|
|
fish.decorFish->getFramePosition(x, y);
|
|
fish.decorFish->getFrameSize(width, height);
|
|
|
|
if ((x + width) <= 0) {
|
|
fish.decorFish->setVisible(false);
|
|
fish.decorFish->setPause(true);
|
|
|
|
fish.enterAt = 0;
|
|
}
|
|
|
|
} else {
|
|
// Decor fishes enter the screen every 0s - 10s
|
|
|
|
if (fish.enterAt == 0)
|
|
fish.enterAt = _vm->_util->getTimeKey() + _vm->_util->getRandom(10000);
|
|
|
|
if (_vm->_util->getTimeKey() >= fish.enterAt) {
|
|
fish.decorFish->rewind();
|
|
fish.decorFish->setPosition(320, 30 + _vm->_util->getRandom(100));
|
|
fish.decorFish->setVisible(true);
|
|
fish.decorFish->setPause(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
} else if (_blackPearlCount == 2) {
|
|
_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() {
|
|
int16 left, top, right, bottom;
|
|
|
|
// Clear the previous animation frames
|
|
for (Common::List<ANIObject *>::iterator a = _anims.reverse_begin();
|
|
a != _anims.end(); --a) {
|
|
|
|
if ((*a)->clear(*_vm->_draw->_backSurface, left, top, right, bottom))
|
|
_vm->_draw->dirtiedRect(_vm->_draw->_backSurface, left, top, right, bottom);
|
|
}
|
|
|
|
// Draw the current animation frames
|
|
for (Common::List<ANIObject *>::iterator a = _anims.begin();
|
|
a != _anims.end(); ++a) {
|
|
|
|
if ((*a)->draw(*_vm->_draw->_backSurface, left, top, right, bottom))
|
|
_vm->_draw->dirtiedRect(_vm->_draw->_backSurface, left, top, right, bottom);
|
|
|
|
(*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) {
|
|
_vm->_util->getMouseState(&mouseX, &mouseY, &mouseButtons);
|
|
|
|
return _vm->_util->checkKey();
|
|
}
|
|
|
|
void Diving::shoot(int16 mouseX, int16 mouseY) {
|
|
// Outside the playable area?
|
|
if (mouseY > 157)
|
|
return;
|
|
|
|
// Too many shots still active?
|
|
if (_activeShots.size() >= kMaxShotCount)
|
|
return;
|
|
|
|
ANIObject &shot = *_shot[_currentShot];
|
|
|
|
shot.rewind();
|
|
shot.setVisible(true);
|
|
shot.setPause(false);
|
|
shot.setPosition(mouseX - 8, mouseY - 8);
|
|
|
|
_activeShots.push_back(_currentShot);
|
|
|
|
_currentShot = (_currentShot + 1) % kMaxShotCount;
|
|
|
|
_vm->_sound->blasterPlay(&_soundShoot, 1, 0);
|
|
}
|
|
|
|
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];
|
|
|
|
if (shot.lastFrame()) {
|
|
int16 x, y;
|
|
|
|
shot.getPosition(x, y);
|
|
|
|
// When we hit an evil fish, it dies
|
|
for (uint i = 0; i < kEvilFishCount; i++) {
|
|
EvilFish &evilFish = *_evilFish[i].evilFish;
|
|
|
|
if (evilFish.isIn(x + 8, y + 8)) {
|
|
evilFish.die();
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
activeShot = _activeShots.erase(activeShot);
|
|
} else
|
|
++activeShot;
|
|
}
|
|
}
|
|
|
|
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
|