mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-04 16:38:55 +00:00
496 lines
17 KiB
C++
496 lines
17 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.
|
|
*
|
|
*/
|
|
|
|
#include "twine/gamestate.h"
|
|
#include "common/file.h"
|
|
#include "common/str.h"
|
|
#include "common/system.h"
|
|
#include "common/textconsole.h"
|
|
#include "common/util.h"
|
|
#include "twine/actor.h"
|
|
#include "twine/animations.h"
|
|
#include "twine/collision.h"
|
|
#include "twine/extra.h"
|
|
#include "twine/grid.h"
|
|
#include "twine/input.h"
|
|
#include "twine/interface.h"
|
|
#include "twine/menu.h"
|
|
#include "twine/menuoptions.h"
|
|
#include "twine/music.h"
|
|
#include "twine/redraw.h"
|
|
#include "twine/renderer.h"
|
|
#include "twine/resources.h"
|
|
#include "twine/scene.h"
|
|
#include "twine/screens.h"
|
|
#include "twine/sound.h"
|
|
#include "twine/text.h"
|
|
#include "twine/twine.h"
|
|
|
|
namespace TwinE {
|
|
|
|
#define SAVE_DIR "save/"
|
|
|
|
GameState::GameState(TwinEEngine *engine) : _engine(engine) {
|
|
Common::fill(&gameFlags[0], &gameFlags[256], 0);
|
|
Common::fill(&inventoryFlags[0], &inventoryFlags[NUM_INVENTORY_ITEMS], 0);
|
|
Common::fill(&holomapFlags[0], &holomapFlags[150], 0);
|
|
playerName[0] = '\0';
|
|
Common::fill(&gameChoices[0], &gameChoices[10], 0);
|
|
}
|
|
|
|
void GameState::initEngineProjections() {
|
|
_engine->_renderer->setOrthoProjection(311, 240, 512);
|
|
_engine->_renderer->setBaseTranslation(0, 0, 0);
|
|
_engine->_renderer->setBaseRotation(0, 0, 0);
|
|
_engine->_renderer->setLightVector(_engine->_scene->alphaLight, _engine->_scene->betaLight, 0);
|
|
}
|
|
|
|
void GameState::initGameStateVars() {
|
|
_engine->_extra->resetExtras();
|
|
|
|
for (int32 i = 0; i < OVERLAY_MAX_ENTRIES; i++) {
|
|
_engine->_redraw->overlayList[i].info0 = -1;
|
|
}
|
|
|
|
for (int32 i = 0; i < ARRAYSIZE(_engine->_scene->sceneFlags); i++) {
|
|
_engine->_scene->sceneFlags[i] = 0;
|
|
}
|
|
|
|
for (int32 i = 0; i < ARRAYSIZE(gameFlags); i++) {
|
|
gameFlags[i] = 0;
|
|
}
|
|
|
|
for (int32 i = 0; i < ARRAYSIZE(inventoryFlags); i++) {
|
|
inventoryFlags[i] = 0;
|
|
}
|
|
|
|
_engine->_scene->initSceneVars();
|
|
|
|
for (int32 i = 0; i < ARRAYSIZE(holomapFlags); i++) {
|
|
holomapFlags[i] = 0;
|
|
}
|
|
|
|
_engine->_actor->currentPositionInBodyPtrTab = 0;
|
|
}
|
|
|
|
void GameState::initHeroVars() {
|
|
_engine->_actor->resetActor(0); // reset Hero
|
|
|
|
magicBallIdx = -1;
|
|
|
|
inventoryNumLeafsBox = 2;
|
|
inventoryNumLeafs = 2;
|
|
inventoryNumKashes = 0;
|
|
inventoryNumKeys = 0;
|
|
inventoryMagicPoints = 0;
|
|
|
|
usingSabre = false;
|
|
|
|
_engine->_scene->sceneHero->body = 0;
|
|
_engine->_scene->sceneHero->life = 50;
|
|
_engine->_scene->sceneHero->talkColor = 4;
|
|
}
|
|
|
|
void GameState::initEngineVars() {
|
|
_engine->_interface->resetClip();
|
|
|
|
_engine->_scene->alphaLight = 896;
|
|
_engine->_scene->betaLight = 950;
|
|
initEngineProjections();
|
|
initGameStateVars();
|
|
initHeroVars();
|
|
|
|
_engine->_scene->newHeroX = 0x2000;
|
|
_engine->_scene->newHeroY = 0x1800;
|
|
_engine->_scene->newHeroZ = 0x2000;
|
|
|
|
_engine->_scene->currentSceneIdx = -1;
|
|
_engine->_scene->needChangeScene = LBA1SceneId::Citadel_Island_Prison;
|
|
_engine->quitGame = -1;
|
|
_engine->_scene->mecaPinguinIdx = -1;
|
|
_engine->_menuOptions->canShowCredits = false;
|
|
|
|
inventoryNumLeafs = 0;
|
|
inventoryNumLeafsBox = 2;
|
|
inventoryMagicPoints = 0;
|
|
inventoryNumKashes = 0;
|
|
inventoryNumKeys = 0;
|
|
inventoryNumGas = 0;
|
|
|
|
_engine->_actor->cropBottomScreen = 0;
|
|
|
|
magicLevelIdx = 0;
|
|
usingSabre = false;
|
|
|
|
gameChapter = 0;
|
|
|
|
_engine->_scene->sceneTextBank = TextBankId::Options_and_menus;
|
|
_engine->_scene->currentlyFollowedActor = OWN_ACTOR_SCENE_INDEX;
|
|
_engine->_actor->heroBehaviour = HeroBehaviourType::kNormal;
|
|
_engine->_actor->previousHeroAngle = 0;
|
|
_engine->_actor->previousHeroBehaviour = HeroBehaviourType::kNormal;
|
|
}
|
|
|
|
bool GameState::loadGame(Common::SeekableReadStream *file) {
|
|
if (file == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
initEngineVars();
|
|
|
|
file->skip(1); // skip save game id
|
|
|
|
int playerNameIdx = 0;
|
|
do {
|
|
const byte c = file->readByte();
|
|
playerName[playerNameIdx++] = c;
|
|
if (c == '\0') {
|
|
break;
|
|
}
|
|
if (playerNameIdx >= ARRAYSIZE(playerName)) {
|
|
warning("Failed to load savegame. Invalid playername.");
|
|
return false;
|
|
}
|
|
} while (true);
|
|
|
|
byte numGameFlags = file->readByte();
|
|
if (numGameFlags != NUM_GAME_FLAGS) {
|
|
warning("Failed to load gameflags. Expected %u, but got %u", NUM_GAME_FLAGS, numGameFlags);
|
|
return false;
|
|
}
|
|
file->read(gameFlags, NUM_GAME_FLAGS);
|
|
_engine->_scene->needChangeScene = file->readByte(); // scene index
|
|
gameChapter = file->readByte();
|
|
|
|
_engine->_actor->heroBehaviour = (HeroBehaviourType)file->readByte();
|
|
_engine->_actor->previousHeroBehaviour = _engine->_actor->heroBehaviour;
|
|
_engine->_scene->sceneHero->life = file->readByte();
|
|
inventoryNumKashes = file->readSint16LE();
|
|
magicLevelIdx = file->readByte();
|
|
inventoryMagicPoints = file->readByte();
|
|
inventoryNumLeafsBox = file->readByte();
|
|
_engine->_scene->newHeroX = file->readSint16LE();
|
|
_engine->_scene->newHeroY = file->readSint16LE();
|
|
_engine->_scene->newHeroZ = file->readSint16LE();
|
|
_engine->_scene->sceneHero->angle = file->readSint16LE();
|
|
_engine->_actor->previousHeroAngle = _engine->_scene->sceneHero->angle;
|
|
_engine->_scene->sceneHero->body = file->readByte();
|
|
|
|
const byte numHolomapFlags = file->readByte(); // number of holomap locations
|
|
if (numHolomapFlags != NUM_LOCATIONS) {
|
|
warning("Failed to load holomapflags. Got %u, expected %i", numHolomapFlags, NUM_LOCATIONS);
|
|
return false;
|
|
}
|
|
file->read(holomapFlags, NUM_LOCATIONS);
|
|
|
|
inventoryNumGas = file->readByte();
|
|
|
|
const byte numInventoryFlags = file->readByte(); // number of used inventory items, always 28
|
|
if (numInventoryFlags != NUM_INVENTORY_ITEMS) {
|
|
warning("Failed to load inventoryFlags. Got %u, expected %i", numInventoryFlags, NUM_INVENTORY_ITEMS);
|
|
return false;
|
|
}
|
|
file->read(inventoryFlags, NUM_INVENTORY_ITEMS);
|
|
|
|
inventoryNumLeafs = file->readByte();
|
|
usingSabre = file->readByte();
|
|
|
|
_engine->_scene->currentSceneIdx = -1;
|
|
_engine->_scene->heroPositionType = ScenePositionType::kReborn;
|
|
return true;
|
|
}
|
|
|
|
bool GameState::saveGame(Common::WriteStream *file) {
|
|
if (playerName[0] == '\0') {
|
|
Common::strlcpy(playerName, "TwinEngineSave", sizeof(playerName));
|
|
}
|
|
|
|
file->writeByte(0x03);
|
|
file->writeString(playerName);
|
|
file->writeByte('\0');
|
|
file->writeByte(NUM_GAME_FLAGS);
|
|
file->write(gameFlags, NUM_GAME_FLAGS);
|
|
file->writeByte(_engine->_scene->currentSceneIdx);
|
|
file->writeByte(gameChapter);
|
|
file->writeByte((byte)_engine->_actor->heroBehaviour);
|
|
file->writeByte(_engine->_scene->sceneHero->life);
|
|
file->writeSint16LE(inventoryNumKashes);
|
|
file->writeByte(magicLevelIdx);
|
|
file->writeByte(inventoryMagicPoints);
|
|
file->writeByte(inventoryNumLeafsBox);
|
|
file->writeSint16LE(_engine->_scene->sceneHero->x);
|
|
file->writeSint16LE(_engine->_scene->sceneHero->y);
|
|
file->writeSint16LE(_engine->_scene->sceneHero->z);
|
|
file->writeSint16LE(_engine->_scene->sceneHero->angle);
|
|
file->writeByte(_engine->_scene->sceneHero->body);
|
|
|
|
// number of holomap locations
|
|
file->writeByte(NUM_LOCATIONS);
|
|
file->write(holomapFlags, NUM_LOCATIONS);
|
|
|
|
file->writeByte(inventoryNumGas);
|
|
|
|
// number of inventory items
|
|
file->writeByte(NUM_INVENTORY_ITEMS);
|
|
file->write(inventoryFlags, NUM_INVENTORY_ITEMS);
|
|
|
|
file->writeByte(inventoryNumLeafs);
|
|
file->writeByte(usingSabre);
|
|
|
|
return true;
|
|
}
|
|
|
|
void GameState::processFoundItem(int32 item) {
|
|
_engine->_grid->newCameraX = (_engine->_scene->sceneHero->x + 0x100) >> 9;
|
|
_engine->_grid->newCameraY = (_engine->_scene->sceneHero->y + 0x100) >> 8;
|
|
_engine->_grid->newCameraZ = (_engine->_scene->sceneHero->z + 0x100) >> 9;
|
|
|
|
// Hide hero in scene
|
|
_engine->_scene->sceneHero->staticFlags.bIsHidden = 1;
|
|
_engine->_redraw->redrawEngineActions(1);
|
|
_engine->_scene->sceneHero->staticFlags.bIsHidden = 0;
|
|
|
|
_engine->_screens->copyScreen(_engine->frontVideoBuffer, _engine->workVideoBuffer);
|
|
|
|
const int32 itemCameraX = _engine->_grid->newCameraX << 9;
|
|
const int32 itemCameraY = _engine->_grid->newCameraY << 8;
|
|
const int32 itemCameraZ = _engine->_grid->newCameraZ << 9;
|
|
|
|
_engine->_renderer->renderIsoModel(_engine->_scene->sceneHero->x - itemCameraX, _engine->_scene->sceneHero->y - itemCameraY, _engine->_scene->sceneHero->z - itemCameraZ, 0, 0x80, 0, _engine->_actor->bodyTable[_engine->_scene->sceneHero->entity]);
|
|
_engine->_interface->setClip(_engine->_redraw->renderLeft, _engine->_redraw->renderTop, _engine->_redraw->renderRight, _engine->_redraw->renderBottom);
|
|
|
|
const int32 itemX = (_engine->_scene->sceneHero->x + 0x100) >> 9;
|
|
int32 itemY = _engine->_scene->sceneHero->y >> 8;
|
|
if (_engine->_scene->sceneHero->brickShape() != ShapeType::kNone) {
|
|
itemY++;
|
|
}
|
|
const int32 itemZ = (_engine->_scene->sceneHero->z + 0x100) >> 9;
|
|
|
|
_engine->_grid->drawOverModelActor(itemX, itemY, itemZ);
|
|
_engine->flip();
|
|
|
|
_engine->_renderer->projectPositionOnScreen(_engine->_scene->sceneHero->x - itemCameraX, _engine->_scene->sceneHero->y - itemCameraY, _engine->_scene->sceneHero->z - itemCameraZ);
|
|
_engine->_renderer->projPosY -= 150;
|
|
|
|
const int32 boxTopLeftX = _engine->_renderer->projPosX - 65;
|
|
const int32 boxTopLeftY = _engine->_renderer->projPosY - 65;
|
|
const int32 boxBottomRightX = _engine->_renderer->projPosX + 65;
|
|
const int32 boxBottomRightY = _engine->_renderer->projPosY + 65;
|
|
|
|
_engine->_sound->playSample(Samples::BigItemFound);
|
|
|
|
// process vox play
|
|
_engine->_music->stopMusic();
|
|
_engine->_text->initTextBank(TextBankId::Inventory_Intro_and_Holomap);
|
|
|
|
_engine->_interface->resetClip();
|
|
_engine->_text->initText(item);
|
|
_engine->_text->initDialogueBox();
|
|
|
|
int32 textState = 1;
|
|
int32 quitItem = 0;
|
|
|
|
_engine->_text->initVoxToPlay(item);
|
|
|
|
const int32 bodyAnimIdx = _engine->_animations->getBodyAnimIndex(AnimationTypes::kFoundItem);
|
|
uint8 *currentAnim = _engine->_resources->animTable[bodyAnimIdx];
|
|
|
|
AnimTimerDataStruct tmpAnimTimer = _engine->_scene->sceneHero->animTimerData;
|
|
|
|
_engine->_animations->stockAnimation(_engine->_actor->bodyTable[_engine->_scene->sceneHero->entity], &_engine->_scene->sceneHero->animTimerData);
|
|
|
|
int32 currentAnimState = 0;
|
|
|
|
_engine->_renderer->prepareIsoModel(_engine->_resources->inventoryTable[item]);
|
|
_engine->_redraw->numOfRedrawBox = 0;
|
|
|
|
while (!quitItem) {
|
|
_engine->_interface->resetClip();
|
|
_engine->_redraw->currNumOfRedrawBox = 0;
|
|
_engine->_redraw->blitBackgroundAreas();
|
|
_engine->_interface->drawTransparentBox(boxTopLeftX, boxTopLeftY, boxBottomRightX, boxBottomRightY, 4);
|
|
|
|
_engine->_interface->setClip(boxTopLeftX, boxTopLeftY, boxBottomRightX, boxBottomRightY);
|
|
|
|
_engine->_menu->itemAngle[item] += 8;
|
|
|
|
_engine->_renderer->renderInventoryItem(_engine->_renderer->projPosX, _engine->_renderer->projPosY, _engine->_resources->inventoryTable[item], _engine->_menu->itemAngle[item], 10000);
|
|
|
|
_engine->_menu->drawBox(boxTopLeftX, boxTopLeftY, boxBottomRightX, boxBottomRightY);
|
|
_engine->_redraw->addRedrawArea(boxTopLeftX, boxTopLeftY, boxBottomRightX, boxBottomRightY);
|
|
_engine->_interface->resetClip();
|
|
initEngineProjections();
|
|
|
|
if (_engine->_animations->setModelAnimation(currentAnimState, currentAnim, _engine->_actor->bodyTable[_engine->_scene->sceneHero->entity], &_engine->_scene->sceneHero->animTimerData)) {
|
|
currentAnimState++; // keyframe
|
|
if (currentAnimState >= _engine->_animations->getNumKeyframes(currentAnim)) {
|
|
currentAnimState = _engine->_animations->getStartKeyframe(currentAnim);
|
|
}
|
|
}
|
|
|
|
_engine->_renderer->renderIsoModel(_engine->_scene->sceneHero->x - itemCameraX, _engine->_scene->sceneHero->y - itemCameraY, _engine->_scene->sceneHero->z - itemCameraZ, 0, 0x80, 0, _engine->_actor->bodyTable[_engine->_scene->sceneHero->entity]);
|
|
_engine->_interface->setClip(_engine->_redraw->renderLeft, _engine->_redraw->renderTop, _engine->_redraw->renderRight, _engine->_redraw->renderBottom);
|
|
_engine->_grid->drawOverModelActor(itemX, itemY, itemZ);
|
|
_engine->_redraw->addRedrawArea(_engine->_redraw->renderLeft, _engine->_redraw->renderTop, _engine->_redraw->renderRight, _engine->_redraw->renderBottom);
|
|
|
|
if (textState) {
|
|
_engine->_interface->resetClip();
|
|
textState = _engine->_text->printText10();
|
|
}
|
|
|
|
if (textState == 0 || textState == 2) {
|
|
_engine->_system->delayMillis(15);
|
|
}
|
|
|
|
_engine->_redraw->flipRedrawAreas();
|
|
|
|
_engine->readKeys();
|
|
if (_engine->_input->toggleAbortAction()) {
|
|
if (!textState) {
|
|
quitItem = 1;
|
|
}
|
|
|
|
if (textState == 2) {
|
|
textState = 1;
|
|
}
|
|
}
|
|
|
|
_engine->lbaTime++;
|
|
}
|
|
|
|
while (_engine->_text->playVoxSimple(_engine->_text->currDialTextEntry)) {
|
|
_engine->readKeys();
|
|
if (_engine->shouldQuit() || _engine->_input->toggleAbortAction()) {
|
|
break;
|
|
}
|
|
_engine->_system->delayMillis(1);
|
|
}
|
|
|
|
initEngineProjections();
|
|
_engine->_text->initTextBank(_engine->_scene->sceneTextBank + 3);
|
|
_engine->_text->stopVox(_engine->_text->currDialTextEntry);
|
|
|
|
_engine->_scene->sceneHero->animTimerData = tmpAnimTimer;
|
|
}
|
|
|
|
void GameState::processGameChoices(int32 choiceIdx) {
|
|
_engine->_screens->copyScreen(_engine->frontVideoBuffer, _engine->workVideoBuffer);
|
|
|
|
gameChoicesSettings.reset();
|
|
gameChoicesSettings.setTextBankId(_engine->_scene->sceneTextBank + 3);
|
|
|
|
// filled via script
|
|
for (int32 i = 0; i < numChoices; i++) {
|
|
gameChoicesSettings.addButton(gameChoices[i], 0);
|
|
}
|
|
|
|
_engine->_text->drawAskQuestion(choiceIdx);
|
|
|
|
_engine->_menu->processMenu(&gameChoicesSettings);
|
|
const int16 activeButton = gameChoicesSettings.getActiveButton();
|
|
choiceAnswer = gameChoices[activeButton];
|
|
|
|
// get right VOX entry index
|
|
if (_engine->_text->initVoxToPlay(choiceAnswer)) {
|
|
while (_engine->_text->playVoxSimple(_engine->_text->currDialTextEntry)) {
|
|
if (_engine->shouldQuit()) {
|
|
break;
|
|
}
|
|
}
|
|
_engine->_text->stopVox(_engine->_text->currDialTextEntry);
|
|
|
|
_engine->_text->hasHiddenVox = false;
|
|
_engine->_text->voxHiddenIndex = 0;
|
|
}
|
|
}
|
|
|
|
void GameState::processGameoverAnimation() {
|
|
int32 tmpLbaTime = _engine->lbaTime;
|
|
|
|
// workaround to fix hero redraw after drowning
|
|
_engine->_scene->sceneHero->staticFlags.bIsHidden = 1;
|
|
_engine->_redraw->redrawEngineActions(1);
|
|
_engine->_scene->sceneHero->staticFlags.bIsHidden = 0;
|
|
|
|
// TODO: drawInGameTransBox
|
|
_engine->setPalette(_engine->_screens->paletteRGBA);
|
|
_engine->_screens->copyScreen(_engine->frontVideoBuffer, _engine->workVideoBuffer);
|
|
uint8 *gameOverPtr = nullptr;
|
|
if (HQR::getAllocEntry(&gameOverPtr, Resources::HQR_RESS_FILE, RESSHQR_GAMEOVERMDL) == 0) {
|
|
return;
|
|
}
|
|
|
|
const int32 left = 120;
|
|
const int32 top = 120;
|
|
const int32 right = 519;
|
|
const int32 bottom = 359;
|
|
_engine->_renderer->prepareIsoModel(gameOverPtr);
|
|
_engine->_sound->stopSamples();
|
|
_engine->_music->stopMidiMusic(); // stop fade music
|
|
_engine->_renderer->setCameraPosition(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, 128, 200, 200);
|
|
int32 startLbaTime = _engine->lbaTime;
|
|
_engine->_interface->setClip(left, top, right, bottom);
|
|
|
|
while (!_engine->_input->toggleAbortAction() && (_engine->lbaTime - startLbaTime) <= 500) {
|
|
_engine->readKeys();
|
|
if (_engine->shouldQuit()) {
|
|
free(gameOverPtr);
|
|
return;
|
|
}
|
|
|
|
const int32 avg = _engine->_collision->getAverageValue(40000, 3200, 500, _engine->lbaTime - startLbaTime);
|
|
const int32 cdot = _engine->_screens->crossDot(1, 1024, 100, (_engine->lbaTime - startLbaTime) % 100);
|
|
|
|
_engine->_interface->blitBox(left, top, right, bottom, _engine->workVideoBuffer, 120, 120, _engine->frontVideoBuffer);
|
|
_engine->_renderer->setCameraAngle(0, 0, 0, 0, -cdot, 0, avg);
|
|
_engine->_renderer->renderIsoModel(0, 0, 0, 0, 0, 0, gameOverPtr);
|
|
_engine->copyBlockPhys(left, top, right, bottom);
|
|
|
|
_engine->lbaTime++;
|
|
_engine->_system->delayMillis(15);
|
|
}
|
|
|
|
_engine->_sound->playSample(Samples::Explode, _engine->getRandomNumber(2000) + 3096);
|
|
_engine->_interface->blitBox(left, top, right, bottom, _engine->workVideoBuffer, 120, 120, _engine->frontVideoBuffer);
|
|
_engine->_renderer->setCameraAngle(0, 0, 0, 0, 0, 0, 3200);
|
|
_engine->_renderer->renderIsoModel(0, 0, 0, 0, 0, 0, gameOverPtr);
|
|
_engine->copyBlockPhys(left, top, right, bottom);
|
|
|
|
_engine->delaySkip(2000);
|
|
|
|
_engine->_interface->resetClip();
|
|
free(gameOverPtr);
|
|
_engine->_screens->copyScreen(_engine->workVideoBuffer, _engine->frontVideoBuffer);
|
|
_engine->flip();
|
|
initEngineProjections();
|
|
|
|
_engine->lbaTime = tmpLbaTime;
|
|
}
|
|
|
|
void GameState::giveUp() {
|
|
_engine->_sound->stopSamples();
|
|
initGameStateVars();
|
|
_engine->_scene->stopRunningGame();
|
|
}
|
|
|
|
} // namespace TwinE
|