/* 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 .
*
* Copyright 2020 Google
*
*/
#include "hadesch/hadesch.h"
#include "hadesch/video.h"
#include "hadesch/rooms/monster.h"
namespace Hadesch {
struct BirdInfo {
int _projectileFrame;
const char *_flyAnim;
int _birdWidth;
int _birdHeight;
const char *_interceptAnim;
const char *_shootAnim;
int _birdShootWidth;
int _birdShootHeight;
Common::Point getBirdSize() const {
return Common::Point(_birdWidth, _birdHeight);
}
Common::Point getBirdShootSize() const {
return Common::Point(_birdShootWidth, _birdShootHeight);
}
};
static const TranscribedSound fakePhilReplics[] = {
{"v7220xb0", _hs("unclear utterance")}, // unclear
{"v7220xc0", _hs("Hey, this was close, buddy")},
{"v7220xd0", _hs("Get hold of thunderbolts")}, // unclear
{"v7220xe0", _hs("Keep going, kid. You're doing great job")},
{"v7220xf0", _hs("unclear utterance")} // unclear
};
static const BirdInfo birdInfo[] = {
{
10,
"v7220bh2",
151, 111,
"v7220bp2",
"v7220bl2",
154, 192,
},
{
6,
"v7220bi2",
167, 175,
"v7220bq2",
"v7220bm2",
190, 233,
},
{
10,
"v7220bh3",
151, 111,
"v7220bp3",
"v7220bl3",
154, 192,
},
{
6,
"v7220bi3",
167, 175,
"v7220bq3",
"v7220bm3",
190, 233,
},
{
10,
"v7220bh0",
141, 109,
"v7220bp0",
"v7220bl0",
141, 192,
},
{
6,
"v7220bi0",
110, 172,
"v7220bq0",
"v7220bm0",
94, 233,
},
{
10,
"v7220bh1",
141, 109,
"v7220bp1",
"v7220bl1",
141, 192,
},
{
6,
"v7220bi1",
110, 172,
"v7220bq1",
"v7220bm1",
94, 233,
}
};
void Illusion::stopAnims() {
Common::SharedPtr room = g_vm->getVideoRoom();
for (unsigned i = 0; i < ARRAYSIZE(birdInfo); i++)
for (unsigned j = 0; j < 3; j++) {
room->stopAnim(LayerId(birdInfo[i]._flyAnim, j, "bird"));
room->stopAnim(LayerId(birdInfo[i]._interceptAnim, j, "bird"));
room->stopAnim(LayerId(birdInfo[i]._shootAnim, j, "bird"));
}
}
Bird::Bird(int id) {
_id = id;
_isActive = false;
}
void Bird::launch(int level) {
Common::SharedPtr room = g_vm->getVideoRoom();
_isActive = true;
_level = level;
makeFlightParams();
room->playSFX("v7220eb0");
_flightStart = g_vm->getCurrentTime();
}
void Bird::stop() {
Common::SharedPtr room = g_vm->getVideoRoom();
room->stopAnim(LayerId(birdInfo[_birdType]._flyAnim, _id, "bird"));
}
FlightPosition Bird::getFlightPosition(double t) {
double t2 = t * t;
double t3 = t2 * t;
struct FlightPosition fp;
// Pseudo-Bezier
fp.centerPos = ((2 * t3 - 3 * t2 + 1.0) * _startPos
+ (t3 - 2 * t2 + t) * _attractor1
+ (t3 - t2) * _attractor2
+ (-2 * t3 + 3 * t2) * _targetPos);
fp.scale = 100 * t;
return fp;
}
void Bird::makeFlightParams() {
Common::RandomSource &rnd = g_vm->getRnd();
_startPos = Common::Point(
rnd.getRandomNumberRng(250, 350),
rnd.getRandomNumberRng(160, 310));
if (rnd.getRandomBit()) {
_targetPos = Common::Point(
650, rnd.getRandomNumberRng(100, 300));
_field84 = 1;
_birdType = rnd.getRandomNumberRng(0, 3);
} else {
_targetPos = Common::Point(-50, rnd.getRandomNumberRng(100, 300));
_field84 = -1;
_birdType = 4 + rnd.getRandomNumberRng(0, 3);
}
int _flightLengthFrames;
if (_level <= 19) {
_flightLengthFrames = 51 - _level;
} else {
_flightLengthFrames = 50 - _level;
}
_flightLengthMs = _flightLengthFrames * 100;
_attractor1 = Common::Point(
rnd.getRandomNumberRngSigned(-600, 600),
rnd.getRandomNumberRngSigned(-600, 600));
_attractor2 = Common::Point(
rnd.getRandomNumberRngSigned(-600, 600),
rnd.getRandomNumberRngSigned(-600, 600));
unsigned lastGoodShootFrame = 11;
for (; (int) lastGoodShootFrame < _flightLengthFrames; lastGoodShootFrame++) {
Common::Point p = getFlightPosition(lastGoodShootFrame / (double) _flightLengthFrames).centerPos;
if (p.x < 50 || p.x > 550 || p.y < 50 || p.y > 350)
break;
}
lastGoodShootFrame--;
_flightShootAnimFrame = rnd.getRandomNumberRng(10, lastGoodShootFrame);
_flightShootProjectileFrame = _flightShootAnimFrame + birdInfo[_birdType]._projectileFrame;
_flightShootEndFrame = _flightShootAnimFrame + (birdInfo[_birdType]._projectileFrame == 6 ? 13 : 18);
_hasShot = false;
}
// 15201
void Bird::tick(Common::SharedPtr backRef, Common::SharedPtr battleground) {
Common::SharedPtr room = g_vm->getVideoRoom();
if (!_isActive)
return;
LayerId flyLayer = LayerId(birdInfo[_birdType]._flyAnim, _id, "bird");
LayerId shootLayer = LayerId(birdInfo[_birdType]._shootAnim, _id, "bird");
_flightCounterMs = (g_vm->getCurrentTime() - _flightStart);
if (_flightCounterMs < _flightLengthMs) {
int frame = _flightCounterMs / 100;
FlightPosition fp = getFlightPosition(_flightCounterMs / (double) _flightLengthMs);
int scale = fp.scale;
if (frame < _flightShootAnimFrame) {
Common::Point cornerPos = fp.centerPos - (scale / 100.0)
* birdInfo[_birdType].getBirdSize();
room->selectFrame(flyLayer, 500, frame % 5, cornerPos);
room->setScale(flyLayer, scale);
room->stopAnim(shootLayer);
} else if (frame < _flightShootEndFrame) {
Common::Point cornerPos = fp.centerPos - (scale / 100.0)
* birdInfo[_birdType].getBirdShootSize();
room->selectFrame(shootLayer, 500, frame - _flightShootAnimFrame, cornerPos);
room->setScale(shootLayer, scale);
room->stopAnim(flyLayer);
} else { // 15204
Common::Point cornerPos = fp.centerPos - (scale / 100.0)
* birdInfo[_birdType].getBirdSize();
room->selectFrame(flyLayer, 500, (frame - _flightShootEndFrame) % 5, cornerPos);
room->setScale(flyLayer, scale);
room->stopAnim(shootLayer);
}
if (frame >= _flightShootProjectileFrame && !_hasShot) {
_hasShot = true;
battleground->launchProjectile(scale / 2, fp.centerPos, _targetPos.x < _startPos.x ? -1 : +1);
}
} else {
room->stopAnim(flyLayer);
room->stopAnim(shootLayer);
_isActive = false;
}
return;
}
void Bird::handleAbsoluteClick(Common::Point p) {
Common::SharedPtr room = g_vm->getVideoRoom();
if (!_isActive || _flightCounterMs >= _flightLengthMs)
return;
FlightPosition fp = getFlightPosition(_flightCounterMs / (double) _flightLengthMs);
int r = fp.scale * 40 / 100;
if ((int) p.sqrDist(fp.centerPos) > r * r)
return;
room->stopAnim(LayerId(birdInfo[_birdType]._flyAnim, _id, "bird"));
room->stopAnim(LayerId(birdInfo[_birdType]._shootAnim, _id, "bird"));
_isActive = false;
LayerId l = LayerId(birdInfo[_birdType]._interceptAnim, _id, "bird");
room->playAnimWithSFX(l, "v7220ec0", 500, PlayAnimParams::disappear(),
EventHandlerWrapper(),
fp.centerPos - birdInfo[_birdType].getBirdSize()
* (fp.scale / 100.0));
}
Illusion::Illusion(Common::SharedPtr battleground) {
_battleground = battleground;
for (unsigned i = 0; i < 3; i++)
_birds[i] = Common::SharedPtr(new Bird(i));
}
void Illusion::launchBird() {
for (unsigned i = 0; i < 3; i++) {
if (!_birds[i]->_isActive) {
_birds[i]->launch(_battleground->_level);
break;
}
}
}
void Illusion::handleAbsoluteClick(Common::Point p) {
for (unsigned i = 0; i < ARRAYSIZE(_birds); i++)
_birds[i]->handleAbsoluteClick(p);
}
void Illusion::movePhil() {
Common::SharedPtr room = g_vm->getVideoRoom();
if (_illusionIsKilled || !_battleground->_isInFight)
return;
room->disableHotzone(Common::String::format("Phil%d", _philPosition));
room->stopAnim(Common::String::format("v7220bt%d", _philPosition));
_philPosition = g_vm->getRnd().getRandomNumberRng(0, 5);
room->enableHotzone(Common::String::format("Phil%d", _philPosition));
room->playAnim(Common::String::format("v7220bt%d", _philPosition), 600,
PlayAnimParams::keepLastFrame().partial(0, 12), 15301);
}
void Illusion::enterIllusion(int level) {
Common::SharedPtr room = g_vm->getVideoRoom();
Typhoon::disableHotzones();
for (unsigned i = 0; i < 6; i++)
room->enableHotzone(Common::String::format("Phil%d", i));
room->playAnimWithSpeech(Common::String::format("v7220bg%d", g_vm->getRnd().getRandomNumberRng(0, 5)),
TranscribedSound::make("v7220xc1",
"It's me, Phil. These beasts are all that stands between me and freedom"), 600, // unclear
PlayAnimParams::disappear(),
15306);
_battleground->_level = level;
_battleground->_leavesRemaining = 9;
_battleground->_monsterNum = kIllusion;
_philPosition = -1;
_illusionIsKilled = false;
g_vm->getHeroBelt()->setBranchOfLifeFrame(0);
}
void Illusion::handleClick(const Common::String &name) {
if (_battleground->_isInFight && _battleground->_monsterNum == kIllusion && g_vm->getHeroBelt()->getSelectedStrength() == kPowerWisdom && !_illusionIsKilled
&& _philPosition >= 0 && name == Common::String::format("Phil%d", _philPosition)) {
Common::SharedPtr room = g_vm->getVideoRoom();
_illusionIsKilled = true;
_battleground->stopFight();
room->disableMouse();
room->playAnimKeepLastFrame(Common::String::format("v7220bv%d", _philPosition), 600);
room->playSFX("v7220eg0", 15307);
return;
}
}
void Illusion::handleEvent(int eventId) {
Common::SharedPtr room = g_vm->getVideoRoom();
switch (eventId) {
case 15301:
g_vm->addTimer(15302, 3500 - (_battleground->_level - 1) * 100);
break;
case 15302:
movePhil();
break;
case 15306:
room->enableMouse();
_battleground->_isInFight = true;
movePhil();
g_vm->addTimer(15312, g_vm->getRnd().getRandomNumberRng(2000, 4000));
room->playAnimKeepLastFrame("v7220oa0", 600);
g_vm->addTimer(15313, g_vm->getRnd().getRandomNumberRng(500, 5000));
launchBird();
break;
case 15307:
room->playSpeech(TranscribedSound::make("v7220wg0", "Oh no, we're gonna fry"), 15308);
break;
case 15308:
room->playSpeech(TranscribedSound::make("v7220wh0", "Let's get outta here"), 15309);
break;
case 15309:
g_vm->getCurrentHandler()->handleEvent(15383);
break;
case 15312:
if (!_battleground->_isInFight || _illusionIsKilled || _battleground->_monsterNum != kIllusion)
return;
room->playSpeech(fakePhilReplics[g_vm->getRnd().getRandomNumberRng(0, ARRAYSIZE(fakePhilReplics) - 1)]);
g_vm->addTimer(15312, g_vm->getRnd().getRandomNumberRng(6000, 10000));
break;
case 15313:
if (!_battleground->_isInFight || _illusionIsKilled || _battleground->_monsterNum != kIllusion)
return;
g_vm->addTimer(15313, g_vm->getRnd().getRandomNumberRng(500, 5000));
launchBird();
break;
// TODO: 15300, 15304, 15305, 15311
}
}
void Illusion::tick() {
if (!_battleground->_isInFight) {
for (unsigned i = 0; i < 3; i++)
_birds[i]->_isActive = false;
return;
}
for (unsigned i = 0; i < 3; i++) {
_birds[i]->tick(_birds[i], _battleground);
}
}
}