Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,837 @@
/* 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/>.
*
*
* This file is dual-licensed.
* In addition to the GPLv3 license mentioned above, this code is also
* licensed under LGPL 2.1. See LICENSES/COPYING.LGPL file for the
* full text of the license.
*
*/
#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

View File

@@ -0,0 +1,198 @@
/* 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/>.
*
*
* This file is dual-licensed.
* In addition to the GPLv3 license mentioned above, this code is also
* licensed under LGPL 2.1. See LICENSES/COPYING.LGPL file for the
* full text of the license.
*
*/
#ifndef GOB_MINIGAMES_GEISHA_DIVING_H
#define GOB_MINIGAMES_GEISHA_DIVING_H
#include "common/system.h"
#include "gob/util.h"
#include "gob/sound/sounddesc.h"
namespace Gob {
class GobEngine;
class Surface;
class DECFile;
class ANIFile;
class ANIObject;
namespace Geisha {
class EvilFish;
class Oko;
class Meter;
/** Geisha's "Diving" minigame. */
class Diving {
public:
Diving(GobEngine *vm);
~Diving();
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;
uint32 enterAt;
uint32 leaveAt;
};
struct ManagedDecorFish {
ANIObject *decorFish;
uint32 enterAt;
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 *_okoAnim;
ANIObject *_water;
ANIObject *_lungs;
ANIObject *_heart;
ManagedEvilFish _evilFish[kEvilFishCount];
ManagedDecorFish _decorFish[kDecorFishCount];
ManagedPlant _plant[kPlantCount];
ManagedPearl _pearl;
Oko *_oko;
ANIObject *_shot[kMaxShotCount];
Common::List<int> _activeShots;
Common::List<ANIObject *> _anims;
Surface *_blackPearl;
uint8 _whitePearlCount;
uint8 _blackPearlCount;
Meter *_airMeter;
Meter *_healthMeter;
uint8 _airCycle;
uint8 _hurtGracePeriod;
uint8 _currentShot;
SoundDesc _soundShoot;
SoundDesc _soundBreathe;
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
} // End of namespace Gob
#endif // GOB_MINIGAMES_GEISHA_DIVING_H

View File

@@ -0,0 +1,186 @@
/* 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/>.
*
*
* This file is dual-licensed.
* In addition to the GPLv3 license mentioned above, this code is also
* licensed under LGPL 2.1. See LICENSES/COPYING.LGPL file for the
* full text of the license.
*
*/
#include "gob/minigames/geisha/evilfish.h"
namespace Gob {
namespace Geisha {
EvilFish::EvilFish(const ANIFile &ani, uint16 screenWidth,
uint16 animSwimLeft, uint16 animSwimRight,
uint16 animTurnLeft, uint16 animTurnRight, uint16 animDie) :
ANIObject(ani), _screenWidth(screenWidth),
_animSwimLeft(animSwimLeft), _animSwimRight(animSwimRight),
_animTurnLeft(animTurnLeft), _animTurnRight(animTurnRight), _animDie(animDie),
_shouldLeave(false), _state(kStateNone) {
}
EvilFish::~EvilFish() {
}
void EvilFish::enter(Direction from, int16 y) {
_shouldLeave = false;
bool left = from == kDirectionLeft;
setAnimation(left ? _animSwimLeft : _animSwimRight);
int16 width, height;
getFrameSize(width, height);
setPosition(left ? -width : _screenWidth, y);
setVisible(true);
_state = left ? kStateSwimLeft : kStateSwimRight;
}
void EvilFish::leave() {
if (_state == kStateNone)
return;
_shouldLeave = true;
}
void EvilFish::die() {
if ((_state == kStateNone) || (_state == kStateDie))
return;
int16 x, y;
getFramePosition(x, y);
setAnimation(_animDie);
setPosition(x, y);
_state = kStateDie;
}
void EvilFish::advance() {
if (_state == kStateNone)
return;
bool wasLastFrame = lastFrame();
int16 oldX, oldY;
getPosition(oldX, oldY);
ANIObject::advance();
int16 x, y, width, height;
getFramePosition(x, y);
getFrameSize(width, height);
switch (_state) {
case kStateNone:
default:
break;
case kStateSwimLeft:
if (!_shouldLeave && (x >= _screenWidth - width)) {
setAnimation(_animTurnRight);
setPosition(x, oldY);
_state = kStateTurnRight;
}
if (_shouldLeave && (x >= _screenWidth)) {
setVisible(false);
_shouldLeave = false;
_state = kStateNone;
}
break;
case kStateSwimRight:
if (!_shouldLeave && (x <= 0)) {
setAnimation(_animTurnLeft);
setPosition(x, oldY);
_state = kStateTurnLeft;
}
if (_shouldLeave && (x < -width)) {
setVisible(false);
_shouldLeave = false;
_state = kStateNone;
}
break;
case kStateTurnLeft:
if (wasLastFrame) {
setAnimation(_animSwimLeft);
_state = kStateSwimLeft;
}
break;
case kStateTurnRight:
if (wasLastFrame) {
setAnimation(_animSwimRight);
_state = kStateSwimRight;
}
break;
case kStateDie:
if (wasLastFrame) {
setVisible(false);
_state = kStateNone;
}
break;
}
}
void EvilFish::mutate(uint16 animSwimLeft, uint16 animSwimRight,
uint16 animTurnLeft, uint16 animTurnRight, uint16 animDie) {
_animSwimLeft = animSwimLeft;
_animSwimRight = animSwimRight;
_animTurnLeft = animTurnLeft;
_animTurnRight = animTurnRight;
_animDie = animDie;
switch (_state) {
case kStateSwimLeft:
setAnimation(_animSwimLeft);
break;
case kStateSwimRight:
setAnimation(_animSwimRight);
break;
default:
break;
}
}
bool EvilFish::isDead() const {
return !isVisible() || (_state == kStateNone) || (_state == kStateDie);
}
} // End of namespace Geisha
} // End of namespace Gob

View File

@@ -0,0 +1,95 @@
/* 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/>.
*
*
* This file is dual-licensed.
* In addition to the GPLv3 license mentioned above, this code is also
* licensed under LGPL 2.1. See LICENSES/COPYING.LGPL file for the
* full text of the license.
*
*/
#ifndef GOB_MINIGAMES_GEISHA_EVILFISH_H
#define GOB_MINIGAMES_GEISHA_EVILFISH_H
#include "gob/aniobject.h"
namespace Gob {
namespace Geisha {
/** An "evil" fish in Geisha's "Diving" minigame. */
class EvilFish : public ANIObject {
public:
enum Direction {
kDirectionLeft = 0,
kDirectionRight = 1
};
EvilFish(const ANIFile &ani, uint16 screenWidth,
uint16 animSwimLeft, uint16 animSwimRight,
uint16 animTurnLeft, uint16 animTurnRight, uint16 animDie);
~EvilFish() override;
/** Enter from this direction / screen edge. */
void enter(Direction from, int16 y);
/** Leave the screen in the current direction. */
void leave();
/** Kill the fish. */
void die();
/** Advance the animation to the next frame. */
void advance() override;
/** Change the fish's animations, effectively making it a different fish type. */
void mutate(uint16 animSwimLeft, uint16 animSwimRight,
uint16 animTurnLeft, uint16 animTurnRight, uint16 animDie);
/** Is the fish dead? */
bool isDead() const;
private:
enum State {
kStateNone,
kStateSwimLeft,
kStateSwimRight,
kStateTurnLeft,
kStateTurnRight,
kStateDie
};
uint16 _screenWidth;
uint16 _animSwimLeft;
uint16 _animSwimRight;
uint16 _animTurnLeft;
uint16 _animTurnRight;
uint16 _animDie;
bool _shouldLeave;
State _state;
};
} // End of namespace Geisha
} // End of namespace Gob
#endif // GOB_MINIGAMES_GEISHA_EVILFISH_H

View File

@@ -0,0 +1,139 @@
/* 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/>.
*
*
* This file is dual-licensed.
* In addition to the GPLv3 license mentioned above, this code is also
* licensed under LGPL 2.1. See LICENSES/COPYING.LGPL file for the
* full text of the license.
*
*/
#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(nullptr) {
}
Meter::~Meter() {
delete _surface;
}
int32 Meter::getMaxValue() const {
return _maxValue;
}
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);
}
int32 Meter::increase(int32 n) {
if (n < 0)
return decrease(-n);
int32 overflow = MAX<int32>(0, (_value + n) - _maxValue);
int32 value = CLIP<int32>(_value + n, 0, _maxValue);
if (_value == value)
return overflow;
_value = value;
_needUpdate = true;
return overflow;
}
int32 Meter::decrease(int32 n) {
if (n < 0)
return increase(-n);
int32 underflow = -MIN<int32>(0, _value - n);
int32 value = CLIP<int32>(_value - n, 0, _maxValue);
if (_value == value)
return underflow;
_value = value;
_needUpdate = true;
return underflow;
}
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

View File

@@ -0,0 +1,96 @@
/* 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/>.
*
*
* This file is dual-licensed.
* In addition to the GPLv3 license mentioned above, this code is also
* licensed under LGPL 2.1. See LICENSES/COPYING.LGPL file for the
* full text of the license.
*
*/
#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 max value the meter is measuring. */
int32 getMaxValue() const;
/** 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, returning the overflow. */
int32 increase(int32 n = 1);
/** Decrease the current value the meter is measuring, returning the underflow. */
int32 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

View File

@@ -0,0 +1,174 @@
/* 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/>.
*
*
* This file is dual-licensed.
* In addition to the GPLv3 license mentioned above, this code is also
* licensed under LGPL 2.1. See LICENSES/COPYING.LGPL file for the
* full text of the license.
*
*/
#include "common/util.h"
#include "gob/minigames/geisha/mouth.h"
namespace Gob {
namespace Geisha {
Mouth::Mouth(const ANIFile &ani, const CMPFile &cmp,
uint16 mouthAnim, uint16 mouthSprite, uint16 floorSprite) : ANIObject(ani) {
_sprite = new ANIObject(cmp);
_sprite->setAnimation(mouthSprite);
_sprite->setVisible(true);
for (int i = 0; i < kFloorCount; i++) {
_floor[i] = new ANIObject(cmp);
_floor[i]->setAnimation(floorSprite);
_floor[i]->setVisible(true);
}
_state = kStateDeactivated;
setAnimation(mouthAnim);
setMode(kModeOnce);
setPause(true);
setVisible(true);
}
Mouth::~Mouth() {
for (int i = 0; i < kFloorCount; i++)
delete _floor[i];
delete _sprite;
}
void Mouth::advance() {
if (_state != kStateActivated)
return;
// Animation finished, set state to dead
if (isPaused()) {
_state = kStateDead;
return;
}
ANIObject::advance();
}
void Mouth::activate() {
if (_state != kStateDeactivated)
return;
_state = kStateActivated;
setPause(false);
}
bool Mouth::isDeactivated() const {
return _state == kStateDeactivated;
}
void Mouth::setPosition(int16 x, int16 y) {
ANIObject::setPosition(x, y);
int16 floorWidth, floorHeight;
_floor[0]->getFrameSize(floorWidth, floorHeight);
_sprite->setPosition(x, y);
for (int i = 0; i < kFloorCount; i++)
_floor[i]->setPosition(x + (i * floorWidth), y);
}
bool Mouth::draw(Surface &dest, int16 &left, int16 &top, int16 &right, int16 &bottom) {
// If the mouth is deactivated, draw the default mouth sprite
if (_state == kStateDeactivated)
return _sprite->draw(dest, left, top, right, bottom);
// If the mouth is activated, draw the current mouth animation sprite
if (_state == kStateActivated)
return ANIObject::draw(dest, left, top, right, bottom);
// If the mouth is dead, draw the floor tiles
if (_state == kStateDead) {
int16 fLeft, fRight, fTop, fBottom;
bool drawn = false;
left = 0x7FFF;
top = 0x7FFF;
right = 0;
bottom = 0;
for (int i = 0; i < kFloorCount; i++) {
if (_floor[i]->draw(dest, fLeft, fTop, fRight, fBottom)) {
drawn = true;
left = MIN(left , fLeft);
top = MIN(top , fTop);
right = MAX(right , fRight);
bottom = MAX(bottom, fBottom);
}
}
return drawn;
}
return false;
}
bool Mouth::clear(Surface &dest, int16 &left , int16 &top, int16 &right, int16 &bottom) {
// If the mouth is deactivated, clear the default mouth sprite
if (_state == kStateDeactivated)
return _sprite->clear(dest, left, top, right, bottom);
// If the mouth is activated, clear the current mouth animation sprite
if (_state == kStateActivated)
return ANIObject::clear(dest, left, top, right, bottom);
// If the mouth is clear, draw the floor tiles
if (_state == kStateDead) {
int16 fLeft, fRight, fTop, fBottom;
bool cleared = false;
left = 0x7FFF;
top = 0x7FFF;
right = 0;
bottom = 0;
for (int i = 0; i < kFloorCount; i++) {
if (_floor[i]->clear(dest, fLeft, fTop, fRight, fBottom)) {
cleared = true;
left = MIN(left , fLeft);
top = MIN(top , fTop);
right = MAX(right , fRight);
bottom = MAX(bottom, fBottom);
}
}
return cleared;
}
return false;
}
} // End of namespace Geisha
} // End of namespace Gob

View File

@@ -0,0 +1,80 @@
/* 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/>.
*
*
* This file is dual-licensed.
* In addition to the GPLv3 license mentioned above, this code is also
* licensed under LGPL 2.1. See LICENSES/COPYING.LGPL file for the
* full text of the license.
*
*/
#ifndef GOB_MINIGAMES_GEISHA_MOUTH_H
#define GOB_MINIGAMES_GEISHA_MOUTH_H
#include "gob/aniobject.h"
namespace Gob {
namespace Geisha {
/** A kissing/biting mouth in Geisha's "Penetration" minigame. */
class Mouth : public ANIObject {
public:
Mouth(const ANIFile &ani, const CMPFile &cmp,
uint16 mouthAnim, uint16 mouthSprite, uint16 floorSprite);
~Mouth() override;
/** Advance the animation to the next frame. */
void advance() override;
/** Active the mouth's animation. */
void activate();
/** Is the mouth deactivated? */
bool isDeactivated() const;
/** Set the current position. */
void setPosition(int16 x, int16 y) override;
/** Draw the current frame onto the surface and return the affected rectangle. */
bool draw(Surface &dest, int16 &left, int16 &top, int16 &right, int16 &bottom) override;
/** Draw the current frame from the surface and return the affected rectangle. */
bool clear(Surface &dest, int16 &left, int16 &top, int16 &right, int16 &bottom) override;
private:
static const int kFloorCount = 2;
enum State {
kStateDeactivated,
kStateActivated,
kStateDead
};
ANIObject *_sprite;
ANIObject *_floor[kFloorCount];
State _state;
};
} // End of namespace Geisha
} // End of namespace Gob
#endif // GOB_MINIGAMES_GEISHA_MOUTH_H

View File

@@ -0,0 +1,176 @@
/* 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/>.
*
*
* This file is dual-licensed.
* In addition to the GPLv3 license mentioned above, this code is also
* licensed under LGPL 2.1. See LICENSES/COPYING.LGPL file for the
* full text of the license.
*
*/
#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);
// fall through
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

View File

@@ -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 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/>.
*
*
* This file is dual-licensed.
* In addition to the GPLv3 license mentioned above, this code is also
* licensed under LGPL 2.1. See LICENSES/COPYING.LGPL file for the
* full text of the license.
*
*/
#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() override;
/** Advance the animation to the next frame. */
void advance() override;
/** 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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,261 @@
/* 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/>.
*
*
* This file is dual-licensed.
* In addition to the GPLv3 license mentioned above, this code is also
* licensed under LGPL 2.1. See LICENSES/COPYING.LGPL file for the
* full text of the license.
*
*/
#ifndef GOB_MINIGAMES_GEISHA_PENETRATION_H
#define GOB_MINIGAMES_GEISHA_PENETRATION_H
#include "common/system.h"
#include "common/list.h"
#include "gob/sound/sounddesc.h"
#include "gob/minigames/geisha/submarine.h"
namespace Gob {
class GobEngine;
class Surface;
class CMPFile;
class ANIFile;
namespace Geisha {
class Meter;
class Mouth;
/** Geisha's "Penetration" minigame. */
class Penetration {
public:
Penetration(GobEngine *vm);
~Penetration();
bool play(bool hasAccessPass, bool hasMaxEnergy, bool testMode);
bool isPlaying() const;
void cheatWin();
private:
static const int kModeCount = 2;
static const int kFloorCount = 3;
static const int kMapWidth = 17;
static const int kMapHeight = 13;
static const int kPaletteSize = 16;
static const byte kPalettes[kFloorCount][3 * kPaletteSize];
static const byte kMaps[kModeCount][kFloorCount][kMapWidth * kMapHeight];
static const int kEnemyCount = 9;
static const int kMaxBulletCount = 10;
struct MapObject {
uint16 tileX;
uint16 tileY;
uint16 mapX;
uint16 mapY;
uint16 width;
uint16 height;
bool isBlocking;
MapObject(uint16 tX, uint16 tY, uint16 mX, uint16 mY, uint16 w, uint16 h);
MapObject(uint16 tX, uint16 tY, uint16 w, uint16 h);
void setTileFromMapPosition();
void setMapFromTilePosition();
bool isIn(uint16 mX, uint16 mY) const;
bool isIn(uint16 mX, uint16 mY, uint16 w, uint16 h) const;
bool isIn(const MapObject &obj) const;
};
enum MouthType {
kMouthTypeBite,
kMouthTypeKiss
};
struct ManagedMouth : public MapObject {
Mouth *mouth;
MouthType type;
ManagedMouth(uint16 tX, uint16 tY, MouthType t);
~ManagedMouth();
};
struct ManagedSub : public MapObject {
Submarine *sub;
ManagedSub(uint16 tX, uint16 tY);
~ManagedSub();
};
struct ManagedEnemy : public MapObject {
ANIObject *enemy;
bool dead;
ManagedEnemy();
~ManagedEnemy();
void clear();
};
struct ManagedBullet : public MapObject {
ANIObject *bullet;
int16 deltaX;
int16 deltaY;
ManagedBullet();
~ManagedBullet();
void clear();
};
enum Keys {
kKeyUp = 0,
kKeyDown,
kKeyLeft,
kKeyRight,
kKeySpace,
kKeyCount
};
GobEngine *_vm;
bool _hasAccessPass;
bool _hasMaxEnergy;
bool _testMode;
bool _needFadeIn;
bool _quit;
bool _keys[kKeyCount];
Surface *_background;
CMPFile *_sprites;
ANIFile *_objects;
Common::List<ANIObject *> _anims;
Common::List<ANIObject *> _mapAnims;
Meter *_shieldMeter;
Meter *_healthMeter;
uint8 _floor;
Surface *_map;
ManagedSub *_sub;
Common::List<MapObject> _walls;
Common::List<MapObject> _exits;
Common::List<MapObject> _shields;
Common::List<ManagedMouth> _mouths;
ManagedEnemy _enemies[kEnemyCount];
ManagedBullet _bullets[kMaxBulletCount];
Common::List<MapObject *> _blockingObjects;
uint8 _shotCoolDown;
SoundDesc _soundShield;
SoundDesc _soundBite;
SoundDesc _soundKiss;
SoundDesc _soundShoot;
SoundDesc _soundExit;
SoundDesc _soundExplode;
bool _isPlaying;
void init();
void deinit();
void clearMap();
void createMap();
void initScreen();
void setPalette();
void fadeIn();
void drawFloorText();
void drawEndText();
bool isBlocked(const MapObject &self, int16 x, int16 y, MapObject **blockedBy = 0);
void findPath(MapObject &obj, int x, int y, MapObject **blockedBy = 0);
void updateAnims();
void checkInput();
Submarine::Direction getDirection(int &x, int &y) const;
void handleSub();
void subMove(int x, int y, Submarine::Direction direction);
void subShoot();
int findEmptyBulletSlot() const;
uint16 directionToBullet(Submarine::Direction direction) const;
void setBulletPosition(const ManagedSub &sub, ManagedBullet &bullet) const;
void bulletsMove();
void bulletMove(ManagedBullet &bullet);
void checkShotEnemy(MapObject &shotObject);
void checkExits();
void checkShields();
void checkMouths();
void healthGain(int amount);
void healthLose(int amount);
void checkExited();
void enemiesCreate();
void enemiesMove();
void enemyMove(ManagedEnemy &enemy, int x, int y);
void enemyAttack(ManagedEnemy &enemy);
void enemyExplode(ManagedEnemy &enemy);
bool isDead() const;
bool hasWon() const;
int getLanguage() const;
};
} // End of namespace Geisha
} // End of namespace Gob
#endif // GOB_MINIGAMES_GEISHA_PENETRATION_H

View File

@@ -0,0 +1,261 @@
/* 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/>.
*
*
* This file is dual-licensed.
* In addition to the GPLv3 license mentioned above, this code is also
* licensed under LGPL 2.1. See LICENSES/COPYING.LGPL file for the
* full text of the license.
*
*/
#include "gob/minigames/geisha/submarine.h"
namespace Gob {
namespace Geisha {
enum Animation {
kAnimationDriveS = 4,
kAnimationDriveE = 5,
kAnimationDriveN = 6,
kAnimationDriveW = 7,
kAnimationDriveSE = 8,
kAnimationDriveNE = 9,
kAnimationDriveSW = 10,
kAnimationDriveNW = 11,
kAnimationShootS = 12,
kAnimationShootN = 13,
kAnimationShootW = 14,
kAnimationShootE = 15,
kAnimationShootNE = 16,
kAnimationShootSE = 17,
kAnimationShootSW = 18,
kAnimationShootNW = 19,
kAnimationExplodeN = 28,
kAnimationExplodeS = 29,
kAnimationExplodeW = 30,
kAnimationExplodeE = 31,
kAnimationExit = 32
};
Submarine::Submarine(const ANIFile &ani) : ANIObject(ani), _state(kStateMove), _direction(kDirectionNone) {
turn(kDirectionN);
}
Submarine::~Submarine() {
}
Submarine::Direction Submarine::getDirection() const {
return _direction;
}
void Submarine::turn(Direction to) {
// Nothing to do
if ((to == kDirectionNone) || ((_state == kStateMove) && (_direction == to)))
return;
_direction = to;
move();
}
void Submarine::move() {
uint16 frame = getFrame();
uint16 anim = (_state == kStateShoot) ? directionToShoot(_direction) : directionToMove(_direction);
setAnimation(anim);
setFrame(frame);
setPause(false);
setVisible(true);
setMode((_state == kStateShoot) ? kModeOnce : kModeContinuous);
}
void Submarine::shoot() {
_state = kStateShoot;
setAnimation(directionToShoot(_direction));
setMode(kModeOnce);
setPause(false);
setVisible(true);
}
void Submarine::die() {
if (!canMove())
return;
_state = kStateDie;
setAnimation(directionToExplode(_direction));
setMode(kModeOnce);
setPause(false);
setVisible(true);
}
void Submarine::leave() {
_state = kStateExit;
setAnimation(kAnimationExit);
setMode(kModeOnce);
setPause(false);
setVisible(true);
}
void Submarine::advance() {
ANIObject::advance();
switch (_state) {
case kStateShoot:
if (isPaused()) {
_state = kStateMove;
move();
}
break;
case kStateExit:
if (isPaused())
_state = kStateExited;
break;
case kStateDie:
if (isPaused())
_state = kStateDead;
break;
default:
break;
}
}
bool Submarine::canMove() const {
return (_state == kStateMove) || (_state == kStateShoot);
}
bool Submarine::isDead() const {
return _state == kStateDead;
}
bool Submarine::isShooting() const {
return _state == kStateShoot;
}
bool Submarine::hasExited() const {
return _state == kStateExited;
}
uint16 Submarine::directionToMove(Direction direction) const {
switch (direction) {
case kDirectionN:
return kAnimationDriveN;
case kDirectionNE:
return kAnimationDriveNE;
case kDirectionE:
return kAnimationDriveE;
case kDirectionSE:
return kAnimationDriveSE;
case kDirectionS:
return kAnimationDriveS;
case kDirectionSW:
return kAnimationDriveSW;
case kDirectionW:
return kAnimationDriveW;
case kDirectionNW:
return kAnimationDriveNW;
default:
break;
}
return 0;
}
uint16 Submarine::directionToShoot(Direction direction) const {
switch (direction) {
case kDirectionN:
return kAnimationShootN;
case kDirectionNE:
return kAnimationShootNE;
case kDirectionE:
return kAnimationShootE;
case kDirectionSE:
return kAnimationShootSE;
case kDirectionS:
return kAnimationShootS;
case kDirectionSW:
return kAnimationShootSW;
case kDirectionW:
return kAnimationShootW;
case kDirectionNW:
return kAnimationShootNW;
default:
break;
}
return 0;
}
uint16 Submarine::directionToExplode(Direction direction) const {
// Only 4 exploding animations (spinning clockwise)
switch (direction) {
case kDirectionNW:
case kDirectionN:
return kAnimationExplodeN;
case kDirectionNE:
case kDirectionE:
return kAnimationExplodeE;
case kDirectionSE:
case kDirectionS:
return kAnimationExplodeS;
case kDirectionSW:
case kDirectionW:
return kAnimationExplodeW;
default:
break;
}
return 0;
}
} // End of namespace Geisha
} // End of namespace Gob

View File

@@ -0,0 +1,112 @@
/* 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/>.
*
*
* This file is dual-licensed.
* In addition to the GPLv3 license mentioned above, this code is also
* licensed under LGPL 2.1. See LICENSES/COPYING.LGPL file for the
* full text of the license.
*
*/
#ifndef GOB_MINIGAMES_GEISHA_SUBMARINE_H
#define GOB_MINIGAMES_GEISHA_SUBMARINE_H
#include "gob/aniobject.h"
namespace Gob {
namespace Geisha {
/** The submarine Geisha's "Penetration" minigame. */
class Submarine : public ANIObject {
public:
enum Direction {
kDirectionNone,
kDirectionN,
kDirectionNE,
kDirectionE,
kDirectionSE,
kDirectionS,
kDirectionSW,
kDirectionW,
kDirectionNW
};
Submarine(const ANIFile &ani);
~Submarine() override;
Direction getDirection() const;
/** Turn to the specified direction. */
void turn(Direction to);
/** Play the shoot animation. */
void shoot();
/** Play the exploding animation. */
void die();
/** Play the exiting animation. */
void leave();
/** Advance the animation to the next frame. */
void advance() override;
/** Can the submarine move at the moment? */
bool canMove() const;
/** Is the submarine dead? */
bool isDead() const;
/** Is the submarine shooting? */
bool isShooting() const;
/** Has the submarine finished exiting the level? */
bool hasExited() const;
private:
enum State {
kStateNone = 0,
kStateMove,
kStateShoot,
kStateExit,
kStateExited,
kStateDie,
kStateDead
};
State _state;
Direction _direction;
/** Map the directions to move animation indices. */
uint16 directionToMove(Direction direction) const;
/** Map the directions to shoot animation indices. */
uint16 directionToShoot(Direction direction) const;
/** Map the directions to explode animation indices. */
uint16 directionToExplode(Direction direction) const;
void move();
};
} // End of namespace Geisha
} // End of namespace Gob
#endif // GOB_MINIGAMES_GEISHA_SUBMARINE_H