mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-30 05:34:00 +00:00
575 lines
14 KiB
C++
575 lines
14 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* Copyright 2020 Google
|
|
*
|
|
*/
|
|
#include "common/file.h"
|
|
#include "hadesch/video.h"
|
|
#include "hadesch/pod_image.h"
|
|
#include "hadesch/tag_file.h"
|
|
#include "hadesch/hadesch.h"
|
|
#include "common/system.h"
|
|
#include "video/smk_decoder.h"
|
|
#include "audio/decoders/aiff.h"
|
|
#include "hadesch/pod_file.h"
|
|
#include "hadesch/baptr.h"
|
|
|
|
namespace Hadesch {
|
|
|
|
static const int kHeroBeltMinY = 378;
|
|
static const int kHeroBeltMaxY = 471;
|
|
|
|
static const char *powerSounds[3][2] = {
|
|
{"g0280nc0", "g0280ng0"},
|
|
{"g0280nb0", "g0280nf0"},
|
|
{"g0280ne0", "g0280nh0"},
|
|
};
|
|
|
|
static Common::Array <PodImage> loadImageArray(const Common::String &name) {
|
|
Common::SharedPtr<Common::SeekableReadStream> rs(g_vm->getWdPodFile()->getFileStream(name + ".pod"));
|
|
PodFile pf2(name);
|
|
pf2.openStore(rs);
|
|
return pf2.loadImageArray();
|
|
}
|
|
|
|
static PodImage loadImage(const Common::String &name) {
|
|
return loadImageArray(name)[0];
|
|
}
|
|
|
|
static const struct {
|
|
const char *background;
|
|
const char *iconNames;
|
|
const char *icons;
|
|
|
|
// TODO: figure out how the original handles
|
|
// cursors.
|
|
const char *iconCursors;
|
|
const char *iconCursorsBright;
|
|
|
|
const char *scrollBg;
|
|
const char *scrollBgHades;
|
|
const char *scrollTextCrete;
|
|
const char *scrollTextTroyFemale;
|
|
const char *scrollTextTroyMale;
|
|
const char *scrollTextMedusa;
|
|
const char *scrollTextHades;
|
|
const char *powerImageWisdom;
|
|
const char *powerImageStrength;
|
|
const char *powerImageStealth;
|
|
} assetTable[3] = {
|
|
// Warm
|
|
{
|
|
"g0010oa0",
|
|
"g0091ba0",
|
|
"g0091bb0",
|
|
"g0091bc0",
|
|
"g0093bc0",
|
|
"g0015oy0",
|
|
"g0015oy3",
|
|
"g0015td0",
|
|
"g0015te0",
|
|
"g0015te3",
|
|
"g0015tf0",
|
|
"g0015tg0",
|
|
"g0290oa0",
|
|
"g0290ob0",
|
|
"g0290oc0"
|
|
},
|
|
|
|
// Cold
|
|
{
|
|
"g0010ob0",
|
|
"g0092ba0",
|
|
"g0092bb0",
|
|
"g0091bc0",
|
|
"g0093bc0",
|
|
"g0015oy2",
|
|
"g0015oy5",
|
|
"g0015td2",
|
|
"g0015te2",
|
|
"g0015te5",
|
|
"g0015tf2",
|
|
"g0015tg2",
|
|
"g0092oa0",
|
|
"g0092ob0",
|
|
"g0092oc0"
|
|
},
|
|
|
|
// Cool
|
|
{
|
|
"g0010oc0",
|
|
"g0093ba0",
|
|
"g0093bb0",
|
|
"g0091bc0",
|
|
"g0093bc0",
|
|
"g0015oy1",
|
|
"g0015oy4",
|
|
"g0015td1",
|
|
"g0015te1",
|
|
"g0015te4",
|
|
"g0015tf1",
|
|
"g0015tg1",
|
|
"g0093oa0",
|
|
"g0093ob0",
|
|
"g0093oc0"
|
|
}
|
|
};
|
|
|
|
HeroBelt::HeroBelt() {
|
|
for (int i = 0; i < 3; i++) {
|
|
_background[i] = loadImage(assetTable[i].background);
|
|
_iconNames[i] = loadImageArray(assetTable[i].iconNames);
|
|
_icons[i] = loadImageArray(assetTable[i].icons);
|
|
_iconCursors[i] = loadImageArray(assetTable[i].iconCursors);
|
|
_iconCursorsBright[i] = loadImageArray(assetTable[i].iconCursorsBright);
|
|
_powerImages[0][i] = loadImageArray(assetTable[i].powerImageStrength);
|
|
_powerImages[1][i] = loadImageArray(assetTable[i].powerImageStealth);
|
|
_powerImages[2][i] = loadImageArray(assetTable[i].powerImageWisdom);
|
|
|
|
_scrollBg[i] = loadImage(assetTable[i].scrollBg);
|
|
_scrollBgHades[i] = loadImage(assetTable[i].scrollBgHades);
|
|
_scrollTextCrete[i] = loadImage(assetTable[i].scrollTextCrete);
|
|
_scrollTextTroyFemale[i] = loadImage(assetTable[i].scrollTextTroyFemale);
|
|
_scrollTextTroyMale[i] = loadImage(assetTable[i].scrollTextTroyMale);
|
|
_scrollTextMedusa[i] = loadImage(assetTable[i].scrollTextMedusa);
|
|
_scrollTextHades[i] = loadImage(assetTable[i].scrollTextHades);
|
|
}
|
|
|
|
_branchOfLife = loadImageArray("v7150ba0");
|
|
_overHeroBelt = false;
|
|
_heroBeltY = kHeroBeltMaxY;
|
|
_bottomEdge = false;
|
|
_heroBeltSpeed = 0;
|
|
_edgeStartTime = 0;
|
|
_animateItem = kNone;
|
|
_animateItemTargetSlot = -1;
|
|
_hotZone = -1;
|
|
_holdingItem = kNone;
|
|
_holdingSlot = -1;
|
|
_highlightTextIdx = -1;
|
|
_showScroll = false;
|
|
_hotZones.readHotzones(
|
|
Common::SharedPtr<Common::SeekableReadStream>(g_vm->getWdPodFile()->getFileStream("HeroBelt.HOT")),
|
|
true);
|
|
}
|
|
|
|
void HeroBelt::computeHotZone(int time, Common::Point mousePos, bool mouseEnabled) {
|
|
bool wasBottomEdge = _bottomEdge;
|
|
|
|
_overHeroBelt = false;
|
|
_bottomEdge = false;
|
|
|
|
_mousePos = mousePos;
|
|
|
|
if (!mouseEnabled) {
|
|
return;
|
|
}
|
|
|
|
_bottomEdge = mousePos.y > 460;
|
|
_overHeroBelt = (_bottomEdge && _heroBeltSpeed < 0) || mousePos.y > _heroBeltY;
|
|
|
|
if (!wasBottomEdge && _bottomEdge)
|
|
_edgeStartTime = time;
|
|
|
|
_currentTime = time;
|
|
|
|
int wasHotZone = _hotZone;
|
|
_hotZone = _hotZones.pointToIndex(mousePos);
|
|
if (_hotZone >= 0 && wasHotZone < 0) {
|
|
_startHotTime = time;
|
|
}
|
|
|
|
computeHighlight();
|
|
}
|
|
|
|
static bool isInFrieze() {
|
|
Persistent *persistent = g_vm->getPersistent();
|
|
return (persistent->_currentRoomId == kMinotaurPuzzle && persistent->_quest != kCreteQuest)
|
|
|| (persistent->_currentRoomId == kTrojanHorsePuzzle && persistent->_quest != kTroyQuest)
|
|
|| (persistent->_currentRoomId == kMedusaPuzzle && persistent->_quest != kMedusaQuest)
|
|
|| (persistent->_currentRoomId == kFerrymanPuzzle && persistent->_quest != kRescuePhilQuest)
|
|
|| (persistent->_currentRoomId == kMonsterPuzzle && persistent->_quest != kRescuePhilQuest);
|
|
}
|
|
|
|
void HeroBelt::render(Common::SharedPtr<GfxContext> context, int time, Common::Point viewPoint) {
|
|
Persistent *persistent = g_vm->getPersistent();
|
|
Common::Point beltPoint;
|
|
|
|
// TODO: use beltopen hotzone instead?
|
|
if (persistent->_currentRoomId == kMonsterPuzzle) {
|
|
_heroBeltY = kHeroBeltMinY;
|
|
} else {
|
|
if (_bottomEdge && _heroBeltY == kHeroBeltMaxY
|
|
&& (time > _edgeStartTime + 500)) {
|
|
_heroBeltSpeed = -10;
|
|
}
|
|
|
|
if (!_overHeroBelt && _heroBeltY != kHeroBeltMaxY
|
|
&& _animateItemTargetSlot == -1) {
|
|
_showScroll = false;
|
|
_heroBeltSpeed = +10;
|
|
}
|
|
|
|
if (_heroBeltSpeed != 0) {
|
|
_heroBeltY += _heroBeltSpeed;
|
|
if (_heroBeltY <= kHeroBeltMinY) {
|
|
_heroBeltY = kHeroBeltMinY;
|
|
_heroBeltSpeed = 0;
|
|
}
|
|
if (_heroBeltY >= kHeroBeltMaxY) {
|
|
_heroBeltY = kHeroBeltMaxY;
|
|
_heroBeltSpeed = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
_currentTime = time;
|
|
|
|
beltPoint = Common::Point(0, _heroBeltY) + viewPoint;
|
|
|
|
_background[_colour].render(context, beltPoint);
|
|
|
|
if (_animateItem != kNone) {
|
|
if (_currentTime > _animateItemStartTime + _animItemTime) {
|
|
_animateItem = kNone;
|
|
_animateItemTargetSlot = -1;
|
|
_animItemCallbackEvent();
|
|
}
|
|
}
|
|
|
|
if (_animateItem != kNone) {
|
|
Common::Point target = computeSlotPoint(_animateItemTargetSlot, true);
|
|
Common::Point delta1 = target - _animateItemStartPoint;
|
|
int elapsed = _currentTime - _animateItemStartTime;
|
|
double fraction = ((elapsed + 0.0) / _animItemTime);
|
|
Common::Point deltaPartial = fraction * delta1;
|
|
Common::Point cur = _animateItemStartPoint + deltaPartial;
|
|
_iconCursors[_colour][_animateItem - 1].render(
|
|
context, cur + viewPoint);
|
|
}
|
|
|
|
if (persistent->_currentRoomId == kMonsterPuzzle) {
|
|
_icons[_colour][kNumberI].render(
|
|
context,
|
|
computeSlotPoint(0, false) + viewPoint
|
|
+ Common::Point(0, 4));
|
|
_icons[_colour][kNumberII].render(
|
|
context,
|
|
computeSlotPoint(1, false) + viewPoint);
|
|
_icons[_colour][kNumberIII].render(
|
|
context,
|
|
computeSlotPoint(2, false) + viewPoint
|
|
+ Common::Point(0, 4));
|
|
for (unsigned i = 0; i < 3; i++) {
|
|
_icons[_colour][_thunderboltFrame].render(
|
|
context,
|
|
computeSlotPoint(3 + i, false) + viewPoint);
|
|
}
|
|
|
|
_branchOfLife[_branchOfLifeFrame].render(
|
|
context, beltPoint);
|
|
|
|
} else {
|
|
for (int i = 0; i < inventorySize; i++) {
|
|
if (i == _animateItemTargetSlot || i == _holdingSlot
|
|
|| persistent->_inventory[i] == kNone) {
|
|
continue;
|
|
}
|
|
_icons[_colour][persistent->_inventory[i] - 1].render(
|
|
context,
|
|
computeSlotPoint(i, false) + viewPoint);
|
|
}
|
|
}
|
|
|
|
_icons[_colour][persistent->_hintsAreEnabled ? kActiveHints : kInactiveHints].render(
|
|
context,
|
|
computeSlotPoint(6, false) + viewPoint);
|
|
|
|
if (isInFrieze())
|
|
_icons[_colour][kReturnToWall].render(
|
|
context,
|
|
computeSlotPoint(7, false) + viewPoint);
|
|
else
|
|
_icons[_colour][kQuestScroll].render(
|
|
context,
|
|
computeSlotPoint(7, false) + viewPoint);
|
|
|
|
_icons[_colour][kOptionsButton].render(
|
|
context,
|
|
computeSlotPoint(8, false) + viewPoint);
|
|
|
|
if (_highlightTextIdx >= 0) {
|
|
_iconNames[_colour][_highlightTextIdx].render(
|
|
context, beltPoint);
|
|
}
|
|
|
|
for (unsigned i = 0; i < ARRAYSIZE(persistent->_powerLevel); i++)
|
|
if (persistent->_powerLevel[i] > 0) {
|
|
_powerImages[i][_colour][(int)i == _selectedPower].render(
|
|
context, beltPoint);
|
|
}
|
|
|
|
if (_showScroll) {
|
|
PodImage *bg = _scrollBg, *text = nullptr;
|
|
switch (persistent->_quest) {
|
|
case kCreteQuest:
|
|
text = _scrollTextCrete;
|
|
break;
|
|
case kTroyQuest:
|
|
if (persistent->_gender == kMale)
|
|
text = _scrollTextTroyMale;
|
|
else
|
|
text = _scrollTextTroyFemale;
|
|
break;
|
|
case kMedusaQuest:
|
|
text = _scrollTextMedusa;
|
|
break;
|
|
case kRescuePhilQuest:
|
|
text = _scrollTextHades;
|
|
bg = _scrollBgHades;
|
|
break;
|
|
case kEndGame:
|
|
case kNoQuest:
|
|
case kNumQuests:
|
|
break;
|
|
}
|
|
bg[_colour].render(context, viewPoint);
|
|
if (text != nullptr)
|
|
text[_colour].render(
|
|
context, viewPoint);
|
|
}
|
|
}
|
|
|
|
bool HeroBelt::isPositionOverHeroBelt(Common::Point mousePos) const {
|
|
return mousePos.y > _heroBeltY;
|
|
}
|
|
|
|
void HeroBelt::handleClick(Common::Point mousePos) {
|
|
Persistent *persistent = g_vm->getPersistent();
|
|
Common::String q = _hotZones.pointToName(mousePos);
|
|
debug("handling belt click on <%s>", q.c_str());
|
|
for (int i = 0; i < inventorySize; i++) {
|
|
if (q == inventoryName(i)) {
|
|
if (_holdingItem != kNone) {
|
|
if (persistent->_inventory[i] != kNone &&
|
|
_holdingSlot != i)
|
|
return;
|
|
persistent->_inventory[_holdingSlot] = kNone;
|
|
persistent->_inventory[i] = _holdingItem;
|
|
_holdingItem = kNone;
|
|
_holdingSlot = -1;
|
|
return;
|
|
}
|
|
if (i == _animateItemTargetSlot
|
|
|| persistent->_inventory[i] == kNone)
|
|
return;
|
|
_holdingItem = persistent->_inventory[i];
|
|
_holdingSlot = i;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (q == "quest scroll") {
|
|
if (isInFrieze())
|
|
g_vm->moveToRoom(kWallOfFameRoom);
|
|
else
|
|
_showScroll = true;
|
|
}
|
|
|
|
if (q == "hints") {
|
|
persistent->_hintsAreEnabled = !persistent->_hintsAreEnabled;
|
|
}
|
|
|
|
if (q == "options") {
|
|
g_vm->enterOptions();
|
|
}
|
|
|
|
if (q == "strength")
|
|
clickPower(kPowerStrength);
|
|
if (q == "stealth")
|
|
clickPower(kPowerStealth);
|
|
if (q == "wisdom")
|
|
clickPower(kPowerWisdom);
|
|
}
|
|
|
|
void HeroBelt::clickPower(HeroPower pwr) {
|
|
Persistent *persistent = g_vm->getPersistent();
|
|
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
|
|
|
|
if (persistent->_currentRoomId == kMonsterPuzzle) {
|
|
_selectedPower = pwr;
|
|
return;
|
|
}
|
|
|
|
if (persistent->_quest == kRescuePhilQuest)
|
|
return;
|
|
|
|
room->playSound(powerSounds[pwr][!!persistent->_powerLevel[pwr]]);
|
|
}
|
|
|
|
void HeroBelt::computeHighlight() {
|
|
Common::String hotZone = _hotZones.indexToName(_hotZone);
|
|
Persistent *persistent = g_vm->getPersistent();
|
|
for (int i = 0; i < inventorySize; i++) {
|
|
if (hotZone == inventoryName(i)) {
|
|
if (persistent->_currentRoomId == kMonsterPuzzle) {
|
|
_highlightTextIdx = i < 3 ? kNumberI + i : kLightning1 + i - 3;
|
|
return;
|
|
} else {
|
|
if (persistent->_inventory[i] != kNone &&
|
|
_holdingSlot != i) {
|
|
_highlightTextIdx = persistent->_inventory[i] - 1;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hotZone == "quest scroll") {
|
|
// TODO: what's with 30 and 28?
|
|
if (isInFrieze())
|
|
_highlightTextIdx = kReturnToWall;
|
|
else if (persistent->_quest == kRescuePhilQuest)
|
|
_highlightTextIdx = kHadesScroll;
|
|
else
|
|
_highlightTextIdx = kQuestScroll;
|
|
return;
|
|
}
|
|
|
|
if (hotZone == "stealth") {
|
|
_highlightTextIdx = kPowerOfStealth;
|
|
return;
|
|
}
|
|
|
|
if (hotZone == "strength") {
|
|
_highlightTextIdx = kPowerOfStrength;
|
|
return;
|
|
}
|
|
|
|
if (hotZone == "wisdom") {
|
|
_highlightTextIdx = kPowerOfWisdom;
|
|
return;
|
|
}
|
|
|
|
if (hotZone == "hints") {
|
|
_highlightTextIdx = persistent->_hintsAreEnabled ? kActiveHints : kInactiveHints;
|
|
return;
|
|
}
|
|
|
|
if (hotZone == "options") {
|
|
_highlightTextIdx = kOptionsButton;
|
|
return;
|
|
}
|
|
|
|
_highlightTextIdx = -1;
|
|
}
|
|
|
|
void HeroBelt::placeToInventory(InventoryItem item, EventHandlerWrapper callbackEvent) {
|
|
Persistent *persistent = g_vm->getPersistent();
|
|
unsigned i;
|
|
for (i = 0; i < inventorySize; i++) {
|
|
if (persistent->_inventory[i] == kNone)
|
|
break;
|
|
}
|
|
|
|
if (i == inventorySize) {
|
|
debug("Out of inventory space");
|
|
return;
|
|
}
|
|
|
|
persistent->_inventory[i] = item;
|
|
_animateItem = item;
|
|
_animItemCallbackEvent = callbackEvent;
|
|
_animateItemStartPoint = _mousePos;
|
|
_animateItemTargetSlot = i;
|
|
_animateItemStartTime = _currentTime;
|
|
_animItemTime = 2000;
|
|
_heroBeltSpeed = -10;
|
|
}
|
|
|
|
void HeroBelt::removeFromInventory(InventoryItem item) {
|
|
Persistent *persistent = g_vm->getPersistent();
|
|
for (unsigned i = 0; i < inventorySize; i++) {
|
|
if (persistent->_inventory[i] == item) {
|
|
persistent->_inventory[i] = kNone;
|
|
}
|
|
}
|
|
|
|
if (_holdingItem == item) {
|
|
_holdingItem = kNone;
|
|
_holdingSlot = -1;
|
|
}
|
|
|
|
if (_animateItem == item) {
|
|
_animateItem = kNone;
|
|
_animateItemTargetSlot = -1;
|
|
}
|
|
}
|
|
|
|
void HeroBelt::clearHold() {
|
|
_holdingItem = kNone;
|
|
_holdingSlot = -1;
|
|
}
|
|
|
|
Common::Point HeroBelt::computeSlotPoint(int slot, bool fullyExtended) {
|
|
Common::Point ret = Common::Point(19, 35);
|
|
ret += Common::Point(0, (fullyExtended ? kHeroBeltMinY : _heroBeltY));
|
|
ret += Common::Point(39 * slot, slot % 2 ? 4 : 0);
|
|
if (slot >= 6) {
|
|
ret += Common::Point(253, 0);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int HeroBelt::getCursor(int time) {
|
|
Common::String q = _hotZones.indexToName(_hotZone);
|
|
Persistent *persistent = g_vm->getPersistent();
|
|
if (q == "")
|
|
return 0;
|
|
for (unsigned i = 0; i < inventorySize; i++) {
|
|
if (q == inventoryName(i)) {
|
|
if ((int) i != _animateItemTargetSlot
|
|
&& persistent->_inventory[i] != kNone)
|
|
return (time - _startHotTime) / kDefaultSpeed % 3;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return (time - _startHotTime) / kDefaultSpeed % 3;
|
|
}
|
|
|
|
Common::String HeroBelt::inventoryName(int slot) {
|
|
return Common::String::format("inventory%d", slot);
|
|
}
|
|
|
|
bool HeroBelt::isHoldingItem() const {
|
|
return _holdingItem != kNone;
|
|
}
|
|
|
|
const Graphics::Cursor *HeroBelt::getHoldingItemCursor(int cursorAnimationFrame) const {
|
|
if ((cursorAnimationFrame / 2) % 2 == 1)
|
|
return &_iconCursorsBright[_colour][_holdingItem - 1];
|
|
else
|
|
return &_iconCursors[_colour][_holdingItem - 1];
|
|
}
|
|
|
|
}
|