scummvm/engines/twine/scene.cpp
Martin Gerhardy 699c8225c6 TWINE: replaced magic numbers with constants
and started to mark the angles - to be able to refacto them easier later on
2020-11-20 18:34:50 +01:00

570 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/scene.h"
#include "common/memstream.h"
#include "common/stream.h"
#include "common/util.h"
#include "twine/actor.h"
#include "twine/animations.h"
#include "twine/debug_grid.h"
#include "twine/debug_scene.h"
#include "twine/extra.h"
#include "twine/gamestate.h"
#include "twine/grid.h"
#include "twine/movements.h"
#include "twine/music.h"
#include "twine/redraw.h"
#include "twine/renderer.h"
#include "twine/resources.h"
#include "twine/screens.h"
#include "twine/sound.h"
#include "twine/text.h"
#include "twine/twine.h"
namespace TwinE {
Scene::~Scene() {
free(currentScene);
}
void Scene::setActorStaticFlags(ActorStruct* act, uint16 staticFlags) {
if (staticFlags & 0x1) {
act->staticFlags.bComputeCollisionWithObj = 1;
}
if (staticFlags & 0x2) {
act->staticFlags.bComputeCollisionWithBricks = 1;
}
if (staticFlags & 0x4) {
act->staticFlags.bIsZonable = 1;
}
if (staticFlags & 0x8) {
act->staticFlags.bUsesClipping = 1;
}
if (staticFlags & 0x10) {
act->staticFlags.bCanBePushed = 1;
}
if (staticFlags & 0x20) {
act->staticFlags.bComputeLowCollision = 1;
}
if (staticFlags & 0x40) {
act->staticFlags.bCanDrown = 1;
}
if (staticFlags & 0x80) {
act->staticFlags.bComputeCollisionWithFloor = 1;
}
if (staticFlags & 0x100) {
act->staticFlags.bUnk0100 = 1;
}
if (staticFlags & 0x200) {
act->staticFlags.bIsHidden = 1;
}
if (staticFlags & 0x400) {
act->staticFlags.bIsSpriteActor = 1;
}
if (staticFlags & 0x800) {
act->staticFlags.bCanFall = 1;
}
if (staticFlags & 0x1000) {
act->staticFlags.bDoesntCastShadow = 1;
}
if (staticFlags & 0x2000) {
//sceneActors[actorIdx].staticFlags.bIsBackgrounded = 1;
}
if (staticFlags & 0x4000) {
act->staticFlags.bIsCarrierActor = 1;
}
if (staticFlags & 0x8000) {
act->staticFlags.bUseMiniZv = 1;
}
}
void Scene::setBonusParameterFlags(ActorStruct* act, uint16 bonusFlags) {
if (bonusFlags & 0x1) {
act->bonusParameter.unk1 = 1;
}
if (bonusFlags & 0x2) {
act->bonusParameter.unk2 = 1;
}
if (bonusFlags & 0x4) {
act->bonusParameter.unk3 = 1;
}
if (bonusFlags & 0x8) {
act->bonusParameter.unk4 = 1;
}
if (bonusFlags & 0x10) {
act->bonusParameter.kashes = 1;
}
if (bonusFlags & 0x20) {
act->bonusParameter.lifepoints = 1;
}
if (bonusFlags & 0x40) {
act->bonusParameter.magicpoints = 1;
}
if (bonusFlags & 0x80) {
act->bonusParameter.key = 1;
}
if (bonusFlags & 0x100) {
act->bonusParameter.cloverleaf = 1;
}
}
bool Scene::loadSceneLBA1() {
Common::MemoryReadStream stream(currentScene, _currentSceneSize);
// load scene ambience properties
sceneTextBank = stream.readByte();
_currentGameOverScene = stream.readByte();
stream.skip(4);
alphaLight = stream.readUint16LE();
betaLight = stream.readUint16LE();
// FIXME: Workaround to fix lighting issue - not using proper dark light
alphaLight = 896;
betaLight = 950;
_sampleAmbiance[0] = stream.readUint16LE();
_sampleRepeat[0] = stream.readUint16LE();
_sampleRound[0] = stream.readUint16LE();
_sampleAmbiance[1] = stream.readUint16LE();
_sampleRepeat[1] = stream.readUint16LE();
_sampleRound[1] = stream.readUint16LE();
_sampleAmbiance[2] = stream.readUint16LE();
_sampleRepeat[2] = stream.readUint16LE();
_sampleRound[2] = stream.readUint16LE();
_sampleAmbiance[3] = stream.readUint16LE();
_sampleRepeat[3] = stream.readUint16LE();
_sampleRound[3] = stream.readUint16LE();
_sampleMinDelay = stream.readUint16LE();
_sampleMinDelayRnd = stream.readUint16LE();
_sceneMusic = stream.readByte();
// load hero properties
_sceneHeroX = stream.readUint16LE();
_sceneHeroY = stream.readUint16LE();
_sceneHeroZ = stream.readUint16LE();
sceneHero->moveScriptSize = stream.readUint16LE();
sceneHero->moveScript = currentScene + stream.pos();
stream.skip(sceneHero->moveScriptSize);
sceneHero->lifeScriptSize = stream.readUint16LE();
sceneHero->lifeScript = currentScene + stream.pos();
stream.skip(sceneHero->lifeScriptSize);
sceneNumActors = stream.readUint16LE();
int cnt = 1;
for (int32 i = 1; i < sceneNumActors; i++, cnt++) {
_engine->_actor->resetActor(i);
ActorStruct* act = &_sceneActors[i];
setActorStaticFlags(act, stream.readUint16LE());
act->loadModel(stream.readUint16LE());
act->body = stream.readByte();
act->anim = (AnimationTypes) stream.readByte();
act->sprite = stream.readUint16LE();
act->x = stream.readUint16LE();
act->collisionX = act->x;
act->y = stream.readUint16LE();
act->collisionY = act->y;
act->z = stream.readUint16LE();
act->collisionZ = act->z;
act->strengthOfHit = stream.readByte();
setBonusParameterFlags(act, stream.readUint16LE());
act->angle = stream.readUint16LE();
act->speed = stream.readUint16LE();
act->controlMode = (ControlMode)stream.readUint16LE();
act->cropLeft = stream.readSint16LE();
act->delayInMillis = act->cropLeft; // TODO: this might not be needed
act->cropTop = stream.readSint16LE();
act->cropRight = stream.readSint16LE();
act->cropBottom = stream.readSint16LE();
act->followedActor = act->cropBottom; // TODO: is this needed? and valid?
act->bonusAmount = stream.readByte();
act->talkColor = stream.readByte();
act->armor = stream.readByte();
act->life = stream.readByte();
act->moveScriptSize = stream.readUint16LE();
act->moveScript = currentScene + stream.pos();
stream.skip(act->moveScriptSize);
act->lifeScriptSize = stream.readUint16LE();
act->lifeScript = currentScene + stream.pos();
stream.skip(act->lifeScriptSize);
if (_engine->_debugScene->onlyLoadActor != -1 && _engine->_debugScene->onlyLoadActor != cnt) {
sceneNumActors--;
i--;
}
}
sceneNumZones = stream.readUint16LE();
for (int32 i = 0; i < sceneNumZones; i++) {
ZoneStruct* zone = &sceneZones[i];
zone->bottomLeft.x = stream.readUint16LE();
zone->bottomLeft.y = stream.readUint16LE();
zone->bottomLeft.z = stream.readUint16LE();
zone->topRight.x = stream.readUint16LE();
zone->topRight.y = stream.readUint16LE();
zone->topRight.z = stream.readUint16LE();
zone->type = stream.readUint16LE();
zone->infoData.generic.info0 = stream.readUint16LE();
zone->infoData.generic.info1 = stream.readUint16LE();
zone->infoData.generic.info2 = stream.readUint16LE();
zone->infoData.generic.info3 = stream.readUint16LE();
zone->snap = stream.readUint16LE();
}
sceneNumTracks = stream.readUint16LE();
for (int32 i = 0; i < sceneNumTracks; i++) {
ScenePoint* point = &sceneTracks[i];
point->x = stream.readUint16LE();
point->y = stream.readUint16LE();
point->z = stream.readUint16LE();
}
return true;
}
bool Scene::initScene(int32 index) {
// load scene from file
_currentSceneSize = HQR::getAllocEntry(&currentScene, Resources::HQR_SCENE_FILE, index);
if (_currentSceneSize == 0) {
return false;
}
if (_engine->isLBA1()) {
return loadSceneLBA1();
}
return false;
}
void Scene::resetScene() {
_engine->_extra->resetExtras();
for (int32 i = 0; i < ARRAYSIZE(sceneFlags); i++) {
sceneFlags[i] = 0;
}
for (int32 i = 0; i < OVERLAY_MAX_ENTRIES; i++) {
_engine->_redraw->overlayList[i].info0 = -1;
}
_engine->_actor->currentPositionInBodyPtrTab = 0;
_engine->_screens->useAlternatePalette = false;
}
void Scene::changeScene() {
// change twinsen house destroyed hard-coded
if (needChangeScene == LBA1SceneId::Citadel_Island_near_twinsens_house && _engine->_gameState->gameFlags[30] != 0) {
needChangeScene = LBA1SceneId::Citadel_Island_Twinsens_house_destroyed;
}
// local backup previous scene
previousSceneIdx = currentSceneIdx;
currentSceneIdx = needChangeScene;
_engine->_sound->stopSamples();
resetScene();
_engine->_actor->loadHeroEntities();
sceneHero->controlMode = ControlMode::kManual;
sceneHero->zone = -1;
sceneHero->positionInLifeScript = 0;
sceneHero->positionInMoveScript = -1;
sceneHero->labelIdx = -1;
initScene(needChangeScene);
// TODO: treat holomap trajectories
if (needChangeScene == LBA1SceneId::Citadel_Island_end_sequence_1 || needChangeScene == LBA1SceneId::Citadel_Island_end_sequence_2) {
sceneTextBank = TextBankId::Tippet_Island;
}
_engine->_text->initTextBank(sceneTextBank + 3);
_engine->_grid->initGrid(needChangeScene);
if (heroPositionType == ScenePositionType::kZone) {
newHeroX = _zoneHeroX;
newHeroY = _zoneHeroY;
newHeroZ = _zoneHeroZ;
}
if (heroPositionType == ScenePositionType::kScene || heroPositionType == ScenePositionType::kNoPosition) {
newHeroX = _sceneHeroX;
newHeroY = _sceneHeroY;
newHeroZ = _sceneHeroZ;
}
sceneHero->x = newHeroX;
sceneHero->y = heroYBeforeFall = newHeroY;
sceneHero->z = newHeroZ;
_engine->_renderer->setLightVector(alphaLight, betaLight, 0);
if (previousSceneIdx != needChangeScene) {
_engine->_actor->previousHeroBehaviour = _engine->_actor->heroBehaviour;
_engine->_actor->previousHeroAngle = sceneHero->angle;
_engine->autoSave();
}
_engine->_actor->restartHeroScene();
for (int32 a = 1; a < sceneNumActors; a++) {
_engine->_actor->initActor(a);
}
_engine->_gameState->inventoryNumKeys = 0;
_engine->disableScreenRecenter = false;
heroPositionType = ScenePositionType::kNoPosition;
_sampleAmbienceTime = 0;
_engine->_grid->newCameraX = _sceneActors[currentlyFollowedActor].x >> 9;
_engine->_grid->newCameraY = _sceneActors[currentlyFollowedActor].y >> 8;
_engine->_grid->newCameraZ = _sceneActors[currentlyFollowedActor].z >> 9;
_engine->_gameState->magicBallIdx = -1;
_engine->_movements->heroMoved = true;
_engine->_grid->useCellingGrid = -1;
_engine->_grid->cellingGridIdx = -1;
_engine->_redraw->reqBgRedraw = true;
_engine->_screens->lockPalette = false;
needChangeScene = -1;
changeRoomVar10 = 1;
changeRoomVar11 = 14;
_engine->_renderer->setLightVector(alphaLight, betaLight, 0);
if (_sceneMusic != -1) {
_engine->_music->playTrackMusic(_sceneMusic);
}
}
ActorStruct *Scene::getActor(int32 actorIdx) {
assert(actorIdx >= 0);
assert(actorIdx < NUM_MAX_ACTORS);
return &_sceneActors[actorIdx];
}
void Scene::initSceneVars() {
_sampleAmbiance[0] = -1;
_sampleAmbiance[1] = -1;
_sampleAmbiance[2] = -1;
_sampleAmbiance[3] = -1;
_sampleRepeat[0] = 0;
_sampleRepeat[1] = 0;
_sampleRepeat[2] = 0;
_sampleRepeat[3] = 0;
_sampleRound[0] = 0;
_sampleRound[1] = 0;
_sampleRound[2] = 0;
_sampleRound[3] = 0;
sceneNumActors = 0;
sceneNumZones = 0;
sceneNumTracks = 0;
}
void Scene::processEnvironmentSound() {
if (_engine->lbaTime >= _sampleAmbienceTime) {
int16 currentAmb = _engine->getRandomNumber(4); // random ambiance
for (int32 s = 0; s < 4; s++) {
if (!(_samplePlayed & (1 << currentAmb))) { // if not already played
_samplePlayed |= (1 << currentAmb); // make sample played
if (_samplePlayed == 15) { // reset if all samples played
_samplePlayed = 0;
}
const int16 sampleIdx = _sampleAmbiance[currentAmb];
if (sampleIdx != -1) {
int16 decal = _sampleRound[currentAmb];
int16 repeat = _sampleRepeat[currentAmb];
_engine->_sound->playSample(sampleIdx, (4096 + _engine->getRandomNumber(decal) - (decal / 2)), repeat, 110, -1, 110);
break;
}
}
currentAmb++; // try next ambiance
currentAmb &= 3; // loop in all 4 ambiances
}
// compute next ambiance timer
_sampleAmbienceTime = _engine->lbaTime + (_engine->getRandomNumber(_sampleMinDelayRnd) + _sampleMinDelay) * 50;
}
}
void Scene::processZoneExtraBonus(ZoneStruct *zone) {
if (zone->infoData.Bonus.used) {
return;
}
const int bonusSprite = _engine->_extra->getBonusSprite(zone->infoData.Bonus.typesFlag);
if (bonusSprite == -1) {
return;
}
const int16 amount = zone->infoData.Bonus.amount;
const int32 angle = _engine->_movements->getAngleAndSetTargetActorDistance(ABS(zone->topRight.x + zone->bottomLeft.x) / 2, ABS(zone->topRight.z + zone->bottomLeft.z) / 2, sceneHero->x, sceneHero->z);
const int32 index = _engine->_extra->addExtraBonus(ABS(zone->topRight.x + zone->bottomLeft.x) / 2, zone->topRight.y, ABS(zone->topRight.z + zone->bottomLeft.z) / 2, 180, angle, bonusSprite, amount);
if (index != -1) {
_engine->_extra->extraList[index].type |= 0x400;
zone->infoData.Bonus.used = 1; // set as used
}
}
void Scene::processActorZones(int32 actorIdx) {
ActorStruct *actor = &_sceneActors[actorIdx];
int32 currentX = actor->x;
int32 currentY = actor->y;
int32 currentZ = actor->z;
actor->zone = -1;
int32 tmpCellingGrid = 0;
if (IS_HERO(actorIdx)) {
currentActorInZone = false;
}
for (int32 z = 0; z < sceneNumZones; z++) {
ZoneStruct *zone = &sceneZones[z];
// check if actor is in zone
if ((currentX >= zone->bottomLeft.x && currentX <= zone->topRight.x) &&
(currentY >= zone->bottomLeft.y && currentY <= zone->topRight.y) &&
(currentZ >= zone->bottomLeft.z && currentZ <= zone->topRight.z)) {
switch (zone->type) {
case kCube:
if (IS_HERO(actorIdx) && actor->life > 0) {
needChangeScene = zone->infoData.ChangeScene.newSceneIdx;
_zoneHeroX = actor->x - zone->bottomLeft.x + zone->infoData.ChangeScene.x;
_zoneHeroY = actor->y - zone->bottomLeft.y + zone->infoData.ChangeScene.y;
_zoneHeroZ = actor->z - zone->bottomLeft.z + zone->infoData.ChangeScene.z;
heroPositionType = ScenePositionType::kZone;
}
break;
case kCamera:
if (currentlyFollowedActor == actorIdx && !_engine->_debugGrid->useFreeCamera) {
_engine->disableScreenRecenter = true;
if (_engine->_grid->newCameraX != zone->infoData.CameraView.x || _engine->_grid->newCameraY != zone->infoData.CameraView.y || _engine->_grid->newCameraZ != zone->infoData.CameraView.z) {
_engine->_grid->newCameraX = zone->infoData.CameraView.x;
_engine->_grid->newCameraY = zone->infoData.CameraView.y;
_engine->_grid->newCameraZ = zone->infoData.CameraView.z;
_engine->_redraw->reqBgRedraw = true;
}
}
break;
case kSceneric:
actor->zone = zone->infoData.Sceneric.zoneIdx;
break;
case kGrid:
if (currentlyFollowedActor == actorIdx) {
tmpCellingGrid = 1;
if (_engine->_grid->useCellingGrid != zone->infoData.CeillingGrid.newGrid) {
if (zone->infoData.CeillingGrid.newGrid != -1) {
_engine->_grid->createGridMap();
}
_engine->_grid->useCellingGrid = zone->infoData.CeillingGrid.newGrid;
_engine->_grid->cellingGridIdx = z;
ScopedEngineFreeze freeze(_engine);
_engine->_grid->initCellingGrid(_engine->_grid->useCellingGrid);
}
}
break;
case kObject:
if (IS_HERO(actorIdx) && _engine->_movements->heroAction) {
_engine->_animations->initAnim(AnimationTypes::kAction, 1, AnimationTypes::kStanding, 0);
processZoneExtraBonus(zone);
}
break;
case kText:
if (IS_HERO(actorIdx) && _engine->_movements->heroAction) {
_engine->freezeTime();
_engine->_text->setFontCrossColor(zone->infoData.DisplayText.textColor);
talkingActor = actorIdx;
_engine->_text->drawTextFullscreen(zone->infoData.DisplayText.textIdx);
_engine->unfreezeTime();
_engine->_redraw->redrawEngineActions(1);
}
break;
case kLadder:
if (IS_HERO(actorIdx) && _engine->_actor->heroBehaviour != HeroBehaviourType::kProtoPack && (actor->anim == AnimationTypes::kForward || actor->anim == AnimationTypes::kTopLadder || actor->anim == AnimationTypes::kClimbLadder)) {
_engine->_movements->rotateActor(actor->boudingBox.x.bottomLeft, actor->boudingBox.z.bottomLeft, actor->angle + ANGLE_360 + ANGLE_135);
_engine->_renderer->destX += _engine->_movements->processActorX;
_engine->_renderer->destZ += _engine->_movements->processActorZ;
if (_engine->_renderer->destX >= 0 && _engine->_renderer->destZ >= 0 && _engine->_renderer->destX <= 0x7E00 && _engine->_renderer->destZ <= 0x7E00) {
if (_engine->_grid->getBrickShape(_engine->_renderer->destX, actor->y + ANGLE_90, _engine->_renderer->destZ) != ShapeType::kNone) {
currentActorInZone = true;
if (actor->y >= ABS(zone->bottomLeft.y + zone->topRight.y) / 2) {
_engine->_animations->initAnim(AnimationTypes::kTopLadder, 2, AnimationTypes::kStanding, actorIdx); // reached end of ladder
} else {
_engine->_animations->initAnim(AnimationTypes::kClimbLadder, 0, AnimationTypes::kAnimInvalid, actorIdx); // go up in ladder
}
}
}
}
break;
}
}
}
if (!tmpCellingGrid && actorIdx == currentlyFollowedActor && _engine->_grid->useCellingGrid != -1) {
_engine->_grid->useCellingGrid = -1;
_engine->_grid->cellingGridIdx = -1;
_engine->_grid->createGridMap();
_engine->_redraw->reqBgRedraw = true;
}
}
void Scene::stopRunningGame() {
free(currentScene);
currentScene = nullptr;
}
} // namespace TwinE