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,386 @@
/* 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/>.
*
* Copyright 2020 Google
*
*/
#include "hadesch/hadesch.h"
#include "hadesch/video.h"
#include "hadesch/rooms/monster.h"
namespace Hadesch {
static const char *kCyclopsShootingEyeOpen = "v7180bh0";
static const char *kCyclopsShootingEyeClosed = "v7180bh1";
enum {
kCyclopsZ = 500
};
static const PrePoint cyclopsEyePositions[21] = {
{247, 175},
{235, 187},
{227, 183},
{221, 178},
{220, 170},
{230, 168},
{230, 168},
{224, 170},
{0, 0},
{0, 0},
{0, 0},
{281, 175},
{282, 170},
{282, 170},
{284, 175},
{270, 178},
{257, 179},
{250, 176},
{249, 176},
{248, 176},
{246, 176}
};
static const PrePoint cyclopsEyePositionsBA0[8] = {
{246, 176},
{248, 174},
{249, 166},
{248, 171},
{0, 0},
{241, 183},
{244, 181},
{246, 176}
};
void Cyclops::handleClick(Common::Point p) {
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
int frame;
if (g_vm->getHeroBelt()->getSelectedStrength() != kPowerStealth || (_currentCyclopsState != 0 && _currentCyclopsState != 1))
return;
switch (_currentCyclopsState) {
case 0:
if (!_cyclopsIsHiding)
return;
frame = room->getAnimFrameNum("v7180bh0");
break;
case 1:
frame = room->getAnimFrameNum("v7040ba0");
break;
default:
return;
}
if (!cyclopsIsHit(p, frame))
return;
room->disableMouse();
_battleground->stopFight();
room->playAnimWithSFX("v7180bj0", "v7180xc0", 500, PlayAnimParams::disappear(), 15352);
}
bool Cyclops::cyclopsIsHit(Common::Point p, int frame) {
if (frame < 0 || frame >= ARRAYSIZE(cyclopsEyePositions) || cyclopsEyePositions[frame].get() == Common::Point(0, 0))
return false;
return cyclopsEyePositions[frame].get().sqrDist(p) <= getSquareOfPrecision();
}
bool Cyclops::cyclopsIsHitBA0(Common::Point p, int frame) {
if (frame < 0 || frame >= ARRAYSIZE(cyclopsEyePositionsBA0) || cyclopsEyePositionsBA0[frame].get() == Common::Point(0, 0))
return false;
return cyclopsEyePositionsBA0[frame].get().sqrDist(p) <= getSquareOfPrecision();
}
unsigned Cyclops::getSquareOfPrecision() {
return 2050 - 50 * _battleground->_level;
}
void Cyclops::cyclopsState0() {
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
_currentCyclopsState = 0;
Common::Point mousePos = g_vm->getMousePos();
_cyclopsIsHiding = (g_vm->getHeroBelt()->getSelectedStrength() == kPowerStealth || !cyclopsIsHit(mousePos, 0));
room->playAnim(kCyclopsShootingEyeClosed, kCyclopsZ, PlayAnimParams::disappear().partial(0, 11), kCyclopsShootingEyeClosedMidAnim);
}
void Cyclops::cyclopsState1() {
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
_currentCyclopsState = 1;
room->playAnimWithSFX("v7040ba0", "v7040ea0",
kCyclopsZ,
PlayAnimParams::disappear(),
15257);
}
void Cyclops::cyclopsState2() {
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
room->playAnimWithSFX(
"v7180be0", "v7180ee0", kCyclopsZ,
PlayAnimParams::disappear()
.partial(0, 4), 15258);
_currentCyclopsState = 2;
}
void Cyclops::cyclopsState3() {
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
_currentCyclopsState = 3;
room->playAnim("v7180be0", kCyclopsZ, PlayAnimParams::disappear().partial(5, 11), 15259);
}
void Cyclops::cyclopsState4() {
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
_currentCyclopsState = 4;
room->playAnimWithSFX("v7180bk0", "v7180sc0", kCyclopsZ, PlayAnimParams::disappear(), 15260);
}
void Cyclops::cyclopsState5() {
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
_currentCyclopsState = 5;
room->playAnimWithSFX("v7180bi0", "v7180sa0", kCyclopsZ, PlayAnimParams::disappear().partial(0, 4), 15262);
}
void Cyclops::cyclopsState6() {
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
_currentCyclopsState = 6;
room->playAnimWithSFX("v7180bi0", "v7180ee0", kCyclopsZ, PlayAnimParams::disappear().partial(5, 11), 15264);
}
Cyclops::Cyclops(Common::SharedPtr<Battleground> battleground) {
_battleground = battleground;
}
void Cyclops::enterCyclops(int level) {
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
room->playAnimKeepLastFrame("v7180oa0", 600);
room->playAnimWithSFX("v7180ba0", "v7180ea0", kCyclopsZ,
PlayAnimParams::disappear(),
15252);
Typhoon::disableHotzones();
_cyclopsProximityCheckCountdown = 0;
_currentCyclopsState = 0;
_cyclopsIsHiding = true;
_battleground->_level = level;
_battleground->_leavesRemaining = 9;
_battleground->_monsterNum = kCyclops;
g_vm->getHeroBelt()->setBranchOfLifeFrame(0);
}
void Cyclops::handleEvent(int eventId) {
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
switch (eventId) {
case 15252:
room->enableMouse();
room->playAnimLoop("v7180bl0", 550);
_battleground->_isInFight = true;
g_vm->addTimer(15266, 100, -1);
switch (g_vm->getRnd().getRandomNumberRng(0, 2)) {
case 0:
cyclopsState0();
break;
case 1:
cyclopsState1();
break;
case 2:
cyclopsState2();
break;
}
break;
case 15255: {
if (_cyclopsIsHiding) {
switch (g_vm->getRnd().getRandomNumberRng(0, 4)) {
case 0:
cyclopsState0();
break;
case 1:
cyclopsState1();
break;
case 2:
case 3:
case 4:
cyclopsState2();
break;
}
break;
}
switch (g_vm->getRnd().getRandomNumberRng(0, 4)) {
case 0:
cyclopsState0();
break;
case 1:
cyclopsState1();
break;
case 2:
cyclopsState3();
break;
case 3:
cyclopsState4();
break;
case 4:
cyclopsState5();
break;
}
break;
}
case 15257:
switch (g_vm->getRnd().getRandomNumberRng(0, 2)) {
case 0:
cyclopsState0();
break;
case 1:
cyclopsState1();
break;
case 2:
cyclopsState2();
break;
}
break;
case 15258:
switch (g_vm->getRnd().getRandomNumberRng(0, 3)) {
case 0:
cyclopsState0();
break;
case 1:
cyclopsState3();
break;
case 2:
cyclopsState4();
break;
case 3:
cyclopsState5();
break;
}
break;
case 15259:
switch(g_vm->getRnd().getRandomNumberRng(0, 2)) {
case 0:
cyclopsState0();
break;
case 1:
cyclopsState1();
break;
case 2:
cyclopsState2();
break;
}
break;
case 15260:
switch (g_vm->getRnd().getRandomNumberRng(0, 3)) {
case 0:
cyclopsState0();
break;
case 1:
cyclopsState3();
break;
case 2:
cyclopsState4();
break;
case 3:
cyclopsState5();
break;
}
break;
case 15262:
switch(g_vm->getRnd().getRandomNumberRng(0, 2)) {
case 0:
cyclopsState0();
break;
case 1:
cyclopsState3();
break;
case 2:
cyclopsState6();
break;
}
break;
case 15264:
switch(g_vm->getRnd().getRandomNumberRng(0, 3)) {
case 0:
cyclopsState0();
break;
case 1:
cyclopsState3();
break;
case 2:
cyclopsState4();
break;
case 3:
cyclopsState5();
break;
}
break;
case 15266:
if (!_battleground->_isInFight || _battleground->_monsterNum != kCyclops)
break;
if (_currentCyclopsState == 0) {
Common::Point mousePos = g_vm->getMousePos();
if (_cyclopsProximityCheckCountdown)
_cyclopsProximityCheckCountdown--;
if (_cyclopsProximityCheckCountdown == 0) {
if (_cyclopsIsHiding) {
int frame = room->getAnimFrameNum("v7180bh0");
if (g_vm->getHeroBelt()->getSelectedStrength() != kPowerStealth
&& cyclopsIsHit(mousePos, frame)) {
room->stopAnim("v7180bh0");
room->playAnim("v7180bh1", kCyclopsZ, PlayAnimParams::disappear().partial(frame, -1), 15255);
_cyclopsProximityCheckCountdown = 5;
_cyclopsIsHiding = false;
}
} else {
int frame = room->getAnimFrameNum("v7180bh1");
if (frame <= 20 && frame >= 0 && !cyclopsIsHit(mousePos, frame) ) {
room->stopAnim("v7180bh1");
room->playAnim("v7180bh0", kCyclopsZ, PlayAnimParams::disappear().partial(frame, -1), 15255);
_cyclopsIsHiding = true;
}
}
}
}
if (_currentCyclopsState == 1) {
Common::Point mousePos = g_vm->getMousePos();
if (g_vm->getHeroBelt()->getSelectedStrength() != kPowerStealth
&& cyclopsIsHitBA0(mousePos, room->getAnimFrameNum("v7040ba0"))) {
room->stopAnim("v7040ba0");
_currentCyclopsState = 2;
room->playAnim(
"v7180be0", kCyclopsZ,
PlayAnimParams::disappear()
.partial(0, 4), 15258);
}
}
break;
case 15269:
for (int i = 0; i < _battleground->getNumOfProjectiles(); i++) {
_battleground->launchProjectile(50, Common::Point(60, 203), 0);
}
room->playSFX("v7140eb0");
break;
case kCyclopsShootingEyeClosedMidAnim:
room->playAnim(kCyclopsShootingEyeClosed, kCyclopsZ, PlayAnimParams::disappear().partial(12, -1), 15255);
handleEvent(15269);
break;
case kCyclopsShootingEyeOpenMidAnim:
room->playAnim(kCyclopsShootingEyeOpen, kCyclopsZ, PlayAnimParams::disappear().partial(12, -1), 15255);
handleEvent(15269);
break;
}
}
}

View File

@@ -0,0 +1,395 @@
/* 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/>.
*
* 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<VideoRoom> 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<VideoRoom> room = g_vm->getVideoRoom();
_isActive = true;
_level = level;
makeFlightParams();
room->playSFX("v7220eb0");
_flightStart = g_vm->getCurrentTime();
}
void Bird::stop() {
Common::SharedPtr<VideoRoom> 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<Bird> backRef, Common::SharedPtr<Battleground> battleground) {
Common::SharedPtr<VideoRoom> 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<VideoRoom> 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 = battleground;
for (unsigned i = 0; i < 3; i++)
_birds[i] = Common::SharedPtr<Bird>(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<VideoRoom> 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<VideoRoom> 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<VideoRoom> 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<VideoRoom> 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);
}
}
}

View File

@@ -0,0 +1,303 @@
/* 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/>.
*
* Copyright 2020 Google
*
*/
#include "hadesch/hadesch.h"
#include "hadesch/video.h"
#include "hadesch/rooms/monster.h"
namespace Hadesch {
Projectile::Projectile(int id, int level, Monster monster, int startScale, Common::Point startPoint, int xmomentum) {
_level = level;
switch (monster) {
case kCyclops:
_flyAnim = "V7140BA0";
_interceptAnim = "V7130BD0";
_hitAnim = "V7140BD0";
break;
case kTyphoon:
_flyAnim = "V7140BB0";
_interceptAnim = "V7130BD1";
_hitAnim = "V7140BE0";
break;
case kIllusion:
_flyAnim = "V7140BC0";
_interceptAnim = "V7130BD2";
_hitAnim = "V7140BF0";
break;
}
_isMiss = g_vm->getRnd().getRandomNumberRng(0, getProjectileHitChance()) == 0;
_isFlightFinished = false;
_flightCounterMs = -1;
_projectileId = id;
_pending = 0;
_flightStart = g_vm->getCurrentTime();
_startScale = startScale;
_start = startPoint;
makeFlightParams(xmomentum);
}
void Projectile::handleEvent(int ev) {
switch (ev) {
case 15053:
g_vm->handleEvent(kHitReceived);
// TODO: stop red
_pending--;
break;
case 15054:
_pending--;
break;
}
}
void Projectile::stop() {
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
room->stopAnim(LayerId(_flyAnim, _projectileId, "projectile"));
room->stopAnim(LayerId(_hitAnim, _projectileId, "projectile"));
room->stopAnim(LayerId(_interceptAnim, _projectileId, "projectile"));
}
void Projectile::makeFlightParams(int xmomentum) {
Common::RandomSource &rnd = g_vm->getRnd();
_flightLengthMs = getProjectileFlightLength(_level) * 100;
if (_isMiss) {
switch (rnd.getRandomNumberRng(0, 2)) {
case 0:
_target = Common::Point(
-50, rnd.getRandomNumberRngSigned(-50, 400));
break;
case 1:
_target = Common::Point(
rnd.getRandomNumberRngSigned(-50, 650), -50);
break;
case 2:
_target = Common::Point(
650, rnd.getRandomNumberRngSigned(-50, 400));
break;
}
} else {
_target = Common::Point(
rnd.getRandomNumberRng(100, 500),
rnd.getRandomNumberRng(100, 300));
}
switch (xmomentum) {
case 1:
_attractor1 = Common::Point(
rnd.getRandomNumberRng(0, 600),
rnd.getRandomNumberRng(0, 300));
break;
case -1:
_attractor1 = Common::Point(
rnd.getRandomNumberRngSigned(-600, 0),
rnd.getRandomNumberRng(0, 300));
break;
case 0:
_attractor1 = Common::Point(
rnd.getRandomNumberRngSigned(-600, 600),
rnd.getRandomNumberRngSigned(-600, 600));
break;
}
_attractor2 = Common::Point(
rnd.getRandomNumberRngSigned(-600, 600),
rnd.getRandomNumberRng(0, 600));
}
FlightPosition Projectile::getFlightPosition(double t) {
double t2 = t * t;
double t3 = t2 * t;
struct FlightPosition fp;
// Pseudo-Bezier
fp.centerPos = ((2 * t3 - 3 * t2 + 1.0) * _start
+ (t3 - 2 * t2 + t) * _attractor1
+ (t3 - t2) * _attractor2
+ (-2 * t3 + 3 * t2) * _target);
fp.scale = _startScale + (120 - _startScale) * t;
return fp;
}
Projectile::~Projectile() {
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
room->purgeAnim(LayerId(_flyAnim, _projectileId, "projectile"));
room->purgeAnim(LayerId(_hitAnim, _projectileId, "projectile"));
room->purgeAnim(LayerId(_interceptAnim, _projectileId, "projectile"));
}
int Projectile::getProjectileFlightLength(int level) {
return 41 - _level;
}
int Projectile::getProjectileHitChance() {
if (_level >= 26)
return 6;
if (_level >= 17)
return 5;
if (_level >= 12)
return 4;
if (_level >= 7)
return 3;
return 2;
}
Battleground::Battleground() {
_level = 1;
_projectileId = 0;
_isInFight = false;
}
int Battleground::getNumOfProjectiles() {
return (_level - 1) / 10 + 1;
}
void Battleground::launchProjectile(int startScale, Common::Point startPoint, int xmomentum) {
++_projectileId;
Common::SharedPtr<Projectile> pj(new Projectile(_projectileId, _level, _monsterNum, startScale, startPoint, xmomentum));
_projectiles.push_back(pj);
pj->tick(pj);
}
void Battleground::handleAbsoluteClick(Common::Point p) {
for (auto &projectile : _projectiles) {
projectile.operator->()->handleAbsoluteClick(projectile, p);
}
}
void Battleground::tick() {
if (!_isInFight)
_projectiles.clear();
else
for (Common::Array<Common::SharedPtr<Projectile> >::iterator it = _projectiles.begin(); it != _projectiles.end();) {
if (it->operator->()->tick(*it)) {
it++;
} else {
it = _projectiles.erase(it);
}
}
}
void Battleground::stopFight() {
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
_isInFight = false;
room->stopAnim("v7040ba0");
room->stopAnim("V7100BJ0");
room->stopAnim("v7180ba0");
room->stopAnim("V7180BB0");
room->stopAnim("v7180be0");
room->stopAnim("v7180bh0");
room->stopAnim("v7180bh1");
room->stopAnim("v7180bi0");
room->stopAnim("v7180bk0");
room->stopAnim("v7180bl0");
room->stopAnim("v7180oa0");
room->stopAnim("v7210bx0");
stopProjectiles();
Typhoon::stopAnims();
Illusion::stopAnims();
for (unsigned i = 0; i < 6; i++) {
room->stopAnim(Common::String::format("v7220bt%d", i));
room->stopAnim(Common::String::format("v7220bg%d", i));
}
room->dumpLayers();
}
void Battleground::stopProjectiles() {
for (auto &projectile : _projectiles)
projectile.operator->()->stop();
}
class HandlerProjectile : public EventHandler {
public:
void operator()() override {
_projectile->handleEvent(_event);
}
HandlerProjectile(Common::SharedPtr <Projectile> projectile,
int event) {
_projectile = projectile;
_event = event;
}
private:
Common::SharedPtr <Projectile> _projectile;
int _event;
};
bool Projectile::tick(Common::SharedPtr <Projectile> backRef) {
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
if (_isFlightFinished)
return _pending > 0;
LayerId flyLayer = LayerId(_flyAnim, _projectileId, "projectile");
_flightCounterMs = (g_vm->getCurrentTime() - _flightStart);
if (_flightCounterMs < _flightLengthMs) {
FlightPosition fp = getFlightPosition(_flightCounterMs / (double) _flightLengthMs);
int scale = fp.scale;
Common::Point cornerPos = fp.centerPos - (scale / 100.0)
* Common::Point(186, 210);
room->selectFrame(flyLayer, 400, (_flightCounterMs / 100) % 8, cornerPos);
room->setScale(flyLayer, scale);
} else {
room->stopAnim(flyLayer);
_isFlightFinished = true;
if (_isMiss) {
_pending = 0;
} else {
FlightPosition fp = getFlightPosition(_flightCounterMs / (double) _flightLengthMs);
LayerId l = LayerId(_hitAnim, _projectileId, "projectile");
room->playAnimWithSFX(l, "v7130ea0", 400, PlayAnimParams::disappear(),
Common::SharedPtr<EventHandler>(new HandlerProjectile(backRef, 15053)),
fp.centerPos - Common::Point(182, 205));
_pending = 1;
// TODO: fade to red, in 100 ms, callback 15055
// TODO: shake camera for 1s
}
}
return true;
}
void Projectile::handleAbsoluteClick(Common::SharedPtr <Projectile> backRef, Common::Point p) {
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
if (_isFlightFinished || _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(_flyAnim, _projectileId, "projectile"));
_isFlightFinished = true;
_pending = 1;
LayerId l = LayerId(_interceptAnim, _projectileId, "projectile");
room->playAnimWithSFX(l, "v7130eg0", 400, PlayAnimParams::disappear(),
Common::SharedPtr<EventHandler>(new HandlerProjectile(backRef, 15054)),
fp.centerPos - Common::Point(186, 210) * (fp.scale / 100.0));
}
}

View File

@@ -0,0 +1,356 @@
/* 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/>.
*
* Copyright 2020 Google
*
*/
#include "hadesch/hadesch.h"
#include "hadesch/video.h"
#include "hadesch/rooms/monster.h"
namespace Hadesch {
enum {
kTyphoonZ = 500
};
struct TyphoonHeadInfo {
const char *_animDie;
const char *_animRespawn;
const char * _animNormal;
const char * _hotZone;
int _xVal;
int _yVal;
int _zVal;
Common::Point getPosition() const {
return Common::Point(_xVal, _yVal);
}
};
static const TyphoonHeadInfo typhonHeadInfo[] = {
{"V7210BO1", "V7210BS1", "V7210BC1", "head00c1", 275, 186, 480},
{"V7210BO0", "V7210BS0", "V7210BC0", "head01c0", 320, 166, 481},
{"V7210BO0", "V7210BS0", "V7210BC0", "head02c0", 313, 221, 482},
{"V7210BO1", "V7210BS1", "V7210BC1", "head03c1", 279, 223, 483},
{"V7210BP1", "V7210BT1", "V7210BD1", "head04d1", 237, 221, 484},
{"V7210BP0", "V7210BT0", "V7210BD0", "head05d0", 234, 189, 485},
{"V7210BP1", "V7210BT1", "V7210BD1", "head06d1", 234, 160, 486},
{"V7210BP0", "V7210BT0", "V7210BD0", "head07d0", 289, 137, 487},
{"V7210BO0", "V7210BS0", "V7210BC0", "head08c0", 253, 135, 488},
{"V7210BP0", "V7210BT0", "V7210BD0", "head09d0", 355, 219, 489},
{"V7210BP0", "V7210BT0", "V7210BD0", "head10d0", 368, 182, 490},
{"V7210BP0", "V7210BT0", "V7210BD0", "head11d0", 351, 152, 491},
{"V7210BP0", "V7210BT0", "V7210BD0", "head12d0", 329, 126, 492},
{"V7210BO0", "V7210BS0", "V7210BC0", "head13c0", 289, 99, 493},
{"V7210BP0", "V7210BT0", "V7210BD0", "head14d0", 333, 107, 494},
{"V7210BO0", "V7210BS0", "V7210BC0", "head15c0", 360, 135, 495},
{"V7210BO1", "V7210BS1", "V7210BC1", "head16c1", 226, 147, 496},
{"V7210BP0", "V7210BT0", "V7210BD0", "head17d0", 257, 107, 497}
};
Typhoon::Typhoon(Common::SharedPtr<Battleground> battleground) {
_battleground = battleground;
_playingTyphoonRespawnSound = false;
_playingTyphoonDieSound = false;
for (unsigned i = 0; i < ARRAYSIZE(_headIsAlive); i++)
_headIsAlive[i] = false;
}
void Typhoon::handleEvent(int eventId) {
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
switch (eventId) {
case 15104:
_playingTyphoonDieSound = false;
break;
case 15105:
_playingTyphoonRespawnSound = false;
break;
case 15152:
room->enableMouse();
room->playAnimLoop("v7210bx0", 490);
room->playSFX("v7210ed0");
_battleground->_isInFight = true;
typhoonA();
for (unsigned i = 0; i < ARRAYSIZE(_headIsAlive); i++) {
showHeadNormal(i);
_headIsAlive[i] = true;
}
schedule15154();
handleEvent(15163);
break;
case 15153:
typhoonA();
break;
case 15154:
if (!_battleground->_isInFight || _isKilled || _battleground->_monsterNum != kTyphoon)
return;
room->playSFX("v7050ea0");
schedule15154();
break;
case 15159:
room->playAnim("v7210bj0", 500, PlayAnimParams::disappear().partial(7, -1), 15153);
if (!_isKilled && _battleground->_isInFight) {
for (int y = 351, i = 0; i < _battleground->getNumOfProjectiles(); y++, i++)
_battleground->launchProjectile(80, Common::Point(
220, g_vm->getRnd().getRandomNumberRng(351, y)), 0);
}
break;
case 15160:
room->playAnim("v7210bi0", 500, PlayAnimParams::disappear().partial(7, -1), 15153);
if (!_isKilled && _battleground->_isInFight) {
for (int y = 359, i = 0; i < _battleground->getNumOfProjectiles(); y++, i++)
_battleground->launchProjectile(80, Common::Point(
456, g_vm->getRnd().getRandomNumberRng(359, y)), 0);
}
break;
case 15163:
if (!_battleground->_isInFight || _isKilled || _battleground->_monsterNum != kTyphoon)
return;
room->playSFX(g_vm->getHeroBelt()->getSelectedStrength() == kPowerStrength
? "v7210eb0" : "v7210ea0");
g_vm->addTimer(15163, g_vm->getRnd().getRandomNumberRng(3000, 7000));
break;
/*
TODO:
15167
*/
case 15168:
g_vm->getCurrentHandler()->handleEvent(15351);
break;
}
}
void Typhoon::enterTyphoon(int level) {
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
room->playAnimKeepLastFrame("v7210oa0", 600);
room->playAnim("v7210ba0", kTyphoonZ,
PlayAnimParams::disappear(),
15152);
room->playSFX("v7050eb0");
for (unsigned i = 0; i < ARRAYSIZE(typhonHeadInfo); i++) {
room->enableHotzone(typhonHeadInfo[i]._hotZone);
room->setHotZoneOffset(typhonHeadInfo[i]._hotZone, typhonHeadInfo[i].getPosition());
}
for (unsigned i = 0; i < 6; i++)
room->disableHotzone(Common::String::format("Phil%d", i));
_battleground->_level = level;
_battleground->_leavesRemaining = 9;
_battleground->_monsterNum = kTyphoon;
_isKilled = false;
_playingTyphoonDieSound = false;
g_vm->getHeroBelt()->setBranchOfLifeFrame(0);
}
void Typhoon::handleClick(Common::SharedPtr<Typhoon> backRef,
const Common::String &name) {
if (_battleground->_isInFight && _battleground->_monsterNum == kTyphoon
&& g_vm->getHeroBelt()->getSelectedStrength() == kPowerStrength && !_isKilled) {
for (unsigned i = 0; i < ARRAYSIZE(typhonHeadInfo); i++)
if (name == typhonHeadInfo[i]._hotZone) {
hitTyphoonHead(backRef, i);
return;
}
}
}
void Typhoon::typhoonA() {
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
if (_isKilled)
return;
if (g_vm->getRnd().getRandomNumberRng(0, 3)) {
room->playAnim("v7050ba0", 500, PlayAnimParams::disappear(), 15153);
} else if (g_vm->getRnd().getRandomBit()) {
room->playAnim("v7210bi0", 500, PlayAnimParams::disappear().partial(0, 6), 15160);
room->playSFX("v7140ec0");
}
else {
room->playAnim("v7210bj0", 500, PlayAnimParams::disappear().partial(0, 6), 15159);
room->playSFX("v7140ec0");
}
}
void Typhoon::schedule15154() {
int ha = typhonGetNumAliveHeads() * 50;
g_vm->addTimer(15154, g_vm->getRnd().getRandomNumberRng(1100-ha, 1200 - ha));
}
int Typhoon::typhonGetNumAliveHeads() {
int v = 0;
for (unsigned i = 0; i < ARRAYSIZE(_headIsAlive); i++)
v += !!_headIsAlive[i];
return v;
}
void Typhoon::hideHead(int idx) {
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
room->stopAnim(LayerId(typhonHeadInfo[idx]._animNormal, idx, "head"));
room->stopAnim(LayerId(typhonHeadInfo[idx]._animDie, idx, "head"));
room->stopAnim(LayerId(typhonHeadInfo[idx]._animRespawn, idx, "head"));
}
void Typhoon::showHeadNormal(int idx) {
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
hideHead(idx);
room->playAnimLoop(LayerId(typhonHeadInfo[idx]._animNormal, idx, "head"),
typhonHeadInfo[idx]._zVal,
typhonHeadInfo[idx].getPosition());
}
// 15103
class TyphoonHeadRespawnComplete : public EventHandler {
public:
void operator()() override {
_typhoon->showHeadNormal(_idx);
}
TyphoonHeadRespawnComplete(Common::SharedPtr<Typhoon> typhoon, int idx) {
_idx = idx;
_typhoon = typhoon;
}
private:
int _idx;
Common::SharedPtr<Typhoon> _typhoon;
};
// 15102
class TyphoonHeadRespawnEvent : public EventHandler {
public:
void operator()() override {
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
if (_typhoon->_headIsAlive[_idx] || _typhoon->_isKilled)
return;
room->enableHotzone(typhonHeadInfo[_idx]._hotZone);
_typhoon->_headIsAlive[_idx] = true;
if (!_typhoon->_playingTyphoonRespawnSound) {
_typhoon->_playingTyphoonRespawnSound = true;
room->playSFX("v7050ed0", 15105);
}
_typhoon->hideHead(_idx);
room->playAnim(LayerId(typhonHeadInfo[_idx]._animRespawn, _idx, "head"),
typhonHeadInfo[_idx]._zVal,
PlayAnimParams::disappear(),
Common::SharedPtr<EventHandler>(new TyphoonHeadRespawnComplete(_typhoon, _idx)),
typhonHeadInfo[_idx].getPosition());
}
TyphoonHeadRespawnEvent(Common::SharedPtr<Typhoon> typhoon, int idx) {
_idx = idx;
_typhoon = typhoon;
}
private:
int _idx;
Common::SharedPtr<Typhoon> _typhoon;
};
// 15101
class TyphoonHeadDieAnimFinishedEvent : public EventHandler {
public:
void operator()() override {
int minRespawnInterval = 10000;
int maxRespawnInterval = 10000;
if (_level <= 21)
minRespawnInterval = 15000 - 500 * (_level - 1);
else if (_level == 22)
minRespawnInterval = 4600;
else if (_level <= 25)
minRespawnInterval = 4200 - 200 * (_level - 23);
else if (_level == 26)
minRespawnInterval = 3700;
else
minRespawnInterval = 3600 - 200 * (_level - 27);
if (_level <= 21)
maxRespawnInterval = 20000 - 500 * (_level - 1);
else
maxRespawnInterval = 9600 - 200 * (_level - 22);
g_vm->addTimer(Common::SharedPtr<EventHandler>(new TyphoonHeadRespawnEvent(_typhoon, _idx)),
g_vm->getRnd().getRandomNumberRng(minRespawnInterval, maxRespawnInterval));
}
TyphoonHeadDieAnimFinishedEvent(Common::SharedPtr<Typhoon> typhoon, int idx, int level) {
_idx = idx;
_level = level;
_typhoon = typhoon;
}
private:
int _idx;
int _level;
Common::SharedPtr<Typhoon> _typhoon;
};
void Typhoon::hitTyphoonHead(Common::SharedPtr<Typhoon> backRef, int idx) {
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
if (!_headIsAlive[idx])
return;
if (!_playingTyphoonDieSound) {
room->playSFX("v7050ec0", 15104);
_playingTyphoonDieSound = true;
}
_headIsAlive[idx] = false;
hideHead(idx);
room->playAnimKeepLastFrame(LayerId(typhonHeadInfo[idx]._animDie, idx, "head"),
typhonHeadInfo[idx]._zVal,
Common::SharedPtr<EventHandler>(new TyphoonHeadDieAnimFinishedEvent(backRef, idx, _battleground->_level)),
typhonHeadInfo[idx].getPosition());
room->disableHotzone(typhonHeadInfo[idx]._hotZone);
bool isKilled = true;
for (unsigned i = 0; i < ARRAYSIZE(_headIsAlive); i++) {
if (_headIsAlive[i])
isKilled = false;
}
if (!isKilled)
return;
_isKilled = true;
_battleground->stopFight();
room->disableMouse();
room->playAnimWithSFX("v7210bw0", "v7050ee0", 500, PlayAnimParams::disappear(), 15168);
}
void Typhoon::stopAnims() {
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
for (unsigned i = 0; i < ARRAYSIZE(typhonHeadInfo); i++) {
room->stopAnim(LayerId(typhonHeadInfo[i]._animNormal, i, "head"));
room->stopAnim(LayerId(typhonHeadInfo[i]._animDie, i, "head"));
room->stopAnim(LayerId(typhonHeadInfo[i]._animRespawn, i, "head"));
room->stopAnim("v7050ba0");
room->stopAnim("v7210bi0");
room->stopAnim("v7140ec0");
room->stopAnim("v7210bj0");
room->stopAnim("v7140ec0");
}
}
void Typhoon::disableHotzones() {
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
for (unsigned i = 0; i < ARRAYSIZE(typhonHeadInfo); i++)
room->disableHotzone(typhonHeadInfo[i]._hotZone);
}
}