/* 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 . * */ #include "twine/scene/scene.h" #include "common/file.h" #include "common/memstream.h" #include "common/stream.h" #include "common/util.h" #include "twine/audio/music.h" #include "twine/audio/sound.h" #include "twine/debugger/debug_grid.h" #include "twine/debugger/debug_scene.h" #include "twine/holomap.h" #include "twine/renderer/redraw.h" #include "twine/renderer/renderer.h" #include "twine/renderer/screens.h" #include "twine/resources/resources.h" #include "twine/scene/actor.h" #include "twine/scene/animations.h" #include "twine/scene/extra.h" #include "twine/scene/gamestate.h" #include "twine/scene/grid.h" #include "twine/scene/movements.h" #include "twine/text.h" #include "twine/twine.h" namespace TwinE { Scene::~Scene() { free(_currentScene); } void Scene::setActorStaticFlags(ActorStruct *act, uint32 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) { //act->staticFlags.bIsBackgrounded = 1; } if (staticFlags & 0x4000) { act->_staticFlags.bIsCarrierActor = 1; } if (staticFlags & 0x8000) { act->_staticFlags.bUseMiniZv = 1; } if (staticFlags & 0x10000) { act->_staticFlags.bHasInvalidPosition = 1; } if (staticFlags & 0x20000) { act->_staticFlags.bNoElectricShock = 1; } if (staticFlags & 0x40000) { act->_staticFlags.bHasSpriteAnim3D = 1; } if (staticFlags & 0x80000) { act->_staticFlags.bNoPreClipping = 1; } if (staticFlags & 0x100000) { act->_staticFlags.bHasZBuffer = 1; } if (staticFlags & 0x200000) { act->_staticFlags.bHasZBufferInWater = 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::loadSceneLBA2() { Common::MemoryReadStream stream(_currentScene, _currentSceneSize); _sceneTextBank = (TextBankId)stream.readByte(); _currentGameOverScene = stream.readByte(); stream.skip(4); _alphaLight = ClampAngle((int16)stream.readUint16LE()); _betaLight = ClampAngle((int16)stream.readUint16LE()); debug(2, "Using %i and %i as light vectors", _alphaLight, _betaLight); _isOutsideScene = stream.readByte(); for (int i = 0; i < 4; ++i) { _sampleAmbiance[i] = stream.readUint16LE(); _sampleRepeat[i] = stream.readUint16LE(); _sampleRound[i] = stream.readUint16LE(); _sampleFrequency[i] = stream.readUint16LE(); _sampleVolume[i] = stream.readUint16LE(); } _sampleMinDelay = stream.readUint16LE(); _sampleMinDelayRnd = stream.readUint16LE(); _sceneMusic = stream.readByte(); // load hero properties _sceneHeroPos.x = stream.readSint16LE(); _sceneHeroPos.y = stream.readSint16LE(); _sceneHeroPos.z = stream.readSint16LE(); _sceneHero->_moveScriptSize = (int16)stream.readUint16LE(); _sceneHero->_moveScript = _currentScene + stream.pos(); stream.skip(_sceneHero->_moveScriptSize); _sceneHero->_lifeScriptSize = (int16)stream.readUint16LE(); _sceneHero->_lifeScript = _currentScene + stream.pos(); stream.skip(_sceneHero->_lifeScriptSize); _sceneNumActors = (int16)stream.readUint16LE(); int cnt = 1; for (int32 a = 1; a < _sceneNumActors; a++, cnt++) { _engine->_actor->resetActor(a); ActorStruct *act = &_sceneActors[a]; setActorStaticFlags(act, stream.readUint32LE()); act->loadModel((int16)stream.readUint16LE(), false); act->_body = (BodyType)stream.readSint16LE(); act->_anim = (AnimationTypes)stream.readByte(); act->_sprite = (int16)stream.readUint16LE(); act->_pos.x = (int16)stream.readUint16LE(); act->_pos.y = (int16)stream.readUint16LE(); act->_pos.z = (int16)stream.readUint16LE(); act->_collisionPos = act->pos(); act->_strengthOfHit = stream.readByte(); setBonusParameterFlags(act, stream.readUint16LE()); act->_angle = (int16)stream.readUint16LE(); act->_speed = (int16)stream.readUint16LE(); act->_controlMode = (ControlMode)stream.readByte(); 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.readSint16LE(); act->_talkColor = stream.readByte(); if (act->_staticFlags.bHasSpriteAnim3D) { /*act->spriteAnim3DNumber = */stream.readSint32LE(); /*act->spriteSizeHit = */stream.readSint16LE(); /*act->cropBottom = act->spriteSizeHit;*/ } act->_armor = stream.readByte(); act->setLife(stream.readByte()); act->_moveScriptSize = (int16)stream.readUint16LE(); act->_moveScript = _currentScene + stream.pos(); stream.skip(act->_moveScriptSize); act->_lifeScriptSize = (int16)stream.readUint16LE(); act->_lifeScript = _currentScene + stream.pos(); stream.skip(act->_lifeScriptSize); if (_engine->_debugScene->_onlyLoadActor != -1 && _engine->_debugScene->_onlyLoadActor != cnt) { _sceneNumActors--; a--; } } _sceneNumZones = (int16)stream.readUint16LE(); for (int32 i = 0; i < _sceneNumZones; i++) { ZoneStruct *zone = &_sceneZones[i]; zone->mins.x = stream.readSint32LE(); zone->mins.y = stream.readSint32LE(); zone->mins.z = stream.readSint32LE(); zone->maxs.x = stream.readSint32LE(); zone->maxs.y = stream.readSint32LE(); zone->maxs.z = stream.readSint32LE(); zone->infoData.generic.info0 = stream.readSint32LE(); zone->infoData.generic.info1 = stream.readSint32LE(); zone->infoData.generic.info2 = stream.readSint32LE(); zone->infoData.generic.info3 = stream.readSint32LE(); zone->infoData.generic.info4 = stream.readSint32LE(); zone->infoData.generic.info5 = stream.readSint32LE(); zone->infoData.generic.info6 = stream.readSint32LE(); zone->infoData.generic.info7 = stream.readSint32LE(); zone->type = (ZoneType)stream.readUint16LE(); zone->snap = stream.readUint16LE(); } _sceneNumTracks = (int16)stream.readUint16LE(); for (int32 i = 0; i < _sceneNumTracks; i++) { IVec3 *point = &_sceneTracks[i]; point->x = stream.readSint32LE(); point->y = stream.readSint32LE(); point->z = stream.readSint32LE(); } uint16 sceneNumPatches = stream.readUint16LE(); for (uint16 i = 0; i < sceneNumPatches; i++) { /*size = */stream.readUint16LE(); /*offset = */stream.readUint16LE(); } return true; } bool Scene::loadSceneLBA1() { Common::MemoryReadStream stream(_currentScene, _currentSceneSize); // load scene ambience properties _sceneTextBank = (TextBankId)stream.readByte(); _currentGameOverScene = stream.readByte(); stream.skip(4); // FIXME: Workaround to fix lighting issue - not using proper dark light // Using 1215 and 1087 as light vectors - scene 8 _alphaLight = ClampAngle((int16)stream.readUint16LE()); _betaLight = ClampAngle((int16)stream.readUint16LE()); debug(2, "Using %i and %i as light vectors", _alphaLight, _betaLight); for (int i = 0; i < 4; ++i) { _sampleAmbiance[i] = stream.readUint16LE(); _sampleRepeat[i] = stream.readUint16LE(); _sampleRound[i] = stream.readUint16LE(); } _sampleMinDelay = stream.readUint16LE(); _sampleMinDelayRnd = stream.readUint16LE(); _sceneMusic = stream.readByte(); // load hero properties _sceneHeroPos.x = (int16)stream.readUint16LE(); _sceneHeroPos.y = (int16)stream.readUint16LE(); _sceneHeroPos.z = (int16)stream.readUint16LE(); _sceneHero->_moveScriptSize = (int16)stream.readUint16LE(); _sceneHero->_moveScript = _currentScene + stream.pos(); stream.skip(_sceneHero->_moveScriptSize); _sceneHero->_lifeScriptSize = (int16)stream.readUint16LE(); _sceneHero->_lifeScript = _currentScene + stream.pos(); stream.skip(_sceneHero->_lifeScriptSize); _sceneNumActors = (int16)stream.readUint16LE(); int cnt = 1; for (int32 a = 1; a < _sceneNumActors; a++, cnt++) { _engine->_actor->resetActor(a); ActorStruct *act = &_sceneActors[a]; setActorStaticFlags(act, stream.readUint16LE()); act->loadModel(stream.readUint16LE(), true); act->_body = (BodyType)stream.readByte(); act->_anim = (AnimationTypes)stream.readByte(); act->_sprite = (int16)stream.readUint16LE(); act->_pos.x = (int16)stream.readUint16LE(); act->_pos.y = (int16)stream.readUint16LE(); act->_pos.z = (int16)stream.readUint16LE(); act->_collisionPos = act->pos(); act->_strengthOfHit = stream.readByte(); setBonusParameterFlags(act, stream.readUint16LE()); act->_angle = (int16)stream.readUint16LE(); act->_speed = (int16)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->setLife(stream.readByte()); act->_moveScriptSize = (int16)stream.readUint16LE(); act->_moveScript = _currentScene + stream.pos(); stream.skip(act->_moveScriptSize); act->_lifeScriptSize = (int16)stream.readUint16LE(); act->_lifeScript = _currentScene + stream.pos(); stream.skip(act->_lifeScriptSize); if (_engine->_debugScene->_onlyLoadActor != -1 && _engine->_debugScene->_onlyLoadActor != cnt) { _sceneNumActors--; a--; } } _sceneNumZones = (int16)stream.readUint16LE(); for (int32 i = 0; i < _sceneNumZones; i++) { ZoneStruct *zone = &_sceneZones[i]; zone->mins.x = (int16)stream.readUint16LE(); zone->mins.y = (int16)stream.readUint16LE(); zone->mins.z = (int16)stream.readUint16LE(); zone->maxs.x = (int16)stream.readUint16LE(); zone->maxs.y = (int16)stream.readUint16LE(); zone->maxs.z = (int16)stream.readUint16LE(); zone->type = (ZoneType)stream.readUint16LE(); zone->infoData.generic.info0 = (int16)stream.readUint16LE(); zone->infoData.generic.info1 = (int16)stream.readUint16LE(); zone->infoData.generic.info2 = (int16)stream.readUint16LE(); zone->infoData.generic.info3 = (int16)stream.readUint16LE(); zone->snap = stream.readUint16LE(); } _sceneNumTracks = stream.readUint16LE(); for (int32 i = 0; i < _sceneNumTracks; i++) { IVec3 *point = &_sceneTracks[i]; point->x = (int16)stream.readUint16LE(); point->y = (int16)stream.readUint16LE(); point->z = (int16)stream.readUint16LE(); } if (_enableEnhancements) { switch (_currentSceneIdx) { case LBA1SceneId::Hamalayi_Mountains_landing_place: _sceneActors[21]._pos.x = _sceneActors[21]._collisionPos.x = 6656 + 256; _sceneActors[21]._pos.z = _sceneActors[21]._collisionPos.z = 768; break; case LBA1SceneId::Principal_Island_outside_the_fortress: _sceneActors[29]._pos.z = _sceneActors[29]._collisionPos.z = 1795; #if 0 _sceneZones[15].mins.x = 1104; _sceneZones[15].mins.z = 8448; _sceneZones[15].maxs.x = 4336; _sceneZones[15].maxs.z = 11488; _sceneZones[16].mins.x = 21104; _sceneZones[16].mins.z = 4608; _sceneZones[16].maxs.x = 23824; _sceneZones[16].maxs.z = 8080; _sceneZones[22].mins.x = 6144; _sceneZones[22].mins.z = 6144; _sceneZones[22].maxs.x = 8865; _sceneZones[22].maxs.z = 6881; #endif break; case LBA1SceneId::Tippet_Island_Secret_passage_scene_1: _sceneZones[6].maxs.z = 3616; break; case LBA1SceneId::Principal_Island_inside_the_fortress: _sceneZones[11].type = (ZoneType)50; break; } } 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(); } else if (_engine->isLBA2()) { return loadSceneLBA2(); } 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->_screens->_useAlternatePalette = false; } void Scene::reloadCurrentScene() { _needChangeScene = _currentSceneIdx; } void Scene::dumpSceneScript(const char *type, int actorIdx, const uint8* script, int size) const { Common::String fname = Common::String::format("./dumps/%i-%i.%s", _currentSceneIdx, actorIdx, type); Common::DumpFile out; if (!out.open(fname.c_str(), true)) { warning("Scene::dumpSceneScript(): Can not open dump file %s", fname.c_str()); } else { out.write(script, size); out.flush(); out.close(); } } void Scene::dumpSceneScripts() const { for (int32 a = 0; a < _sceneNumActors; ++a) { const ActorStruct &actor = _sceneActors[a]; dumpSceneScript("life", a, actor._lifeScript, actor._lifeScriptSize); dumpSceneScript("move", a, actor._moveScript, actor._moveScriptSize); } } void Scene::changeScene() { if (_engine->isLBA1()) { if (_enableEnhancements) { if (_currentSceneIdx == LBA1SceneId::Citadel_Island_Harbor && _needChangeScene == LBA1SceneId::Principal_Island_Harbor) { if (_sceneNumZones >= 15 && _sceneNumTracks >= 8) { const ZoneStruct *zone = &_sceneZones[15]; const IVec3 &track = _sceneTracks[8]; IVec3 &pos = _zoneHeroPos; pos.x = zone->infoData.ChangeScene.x - zone->mins.x + track.x; pos.y = zone->infoData.ChangeScene.y - zone->mins.y + track.y; pos.z = zone->infoData.ChangeScene.z - zone->mins.z + track.z; _engine->_scene->_heroPositionType = ScenePositionType::kZone; debug(3, "Using zone position %i:%i:%i", pos.x, pos.y, pos.z); } } } // change twinsen house destroyed hard-coded if (_needChangeScene == LBA1SceneId::Citadel_Island_near_twinsens_house && _engine->_gameState->hasOpenedFunfrocksSafe()) { _needChangeScene = LBA1SceneId::Citadel_Island_Twinsens_house_destroyed; } } // local backup previous scene _previousSceneIdx = _currentSceneIdx; _currentSceneIdx = _needChangeScene; if (_engine->isLBA1() && _currentSceneIdx >= LBA1SceneId::Citadel_Island_Prison && _currentSceneIdx < LBA1SceneId::SceneIdMax) { snprintf(_engine->_gameState->_sceneName, sizeof(_engine->_gameState->_sceneName), "%i %s", _currentSceneIdx, _engine->_holomap->getLocationName(_currentSceneIdx)); } else { snprintf(_engine->_gameState->_sceneName, sizeof(_engine->_gameState->_sceneName), "%i", _currentSceneIdx); } debug(2, "Entering scene %s (came from %i)", _engine->_gameState->_sceneName, _previousSceneIdx); if (_engine->isLBA1()) { if (_needChangeScene == LBA1SceneId::Polar_Island_end_scene) { _engine->unlockAchievement("LBA_ACH_001"); // if you finish the game in less than 4 hours if (_engine->getTotalPlayTime() <= 1000 * 60 * 60 * 4) { _engine->unlockAchievement("LBA_ACH_005"); } } else if (_needChangeScene == LBA1SceneId::Brundle_Island_Secret_room) { _engine->unlockAchievement("LBA_ACH_006"); } } _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); if (ConfMan.getBool("dump_scripts")) { dumpSceneScripts(); } if (_holomapTrajectory != -1) { _engine->_holomap->drawHolomapTrajectory(_holomapTrajectory); _holomapTrajectory = -1; } if (_needChangeScene == LBA1SceneId::Citadel_Island_end_sequence_1 || _needChangeScene == LBA1SceneId::Citadel_Island_end_sequence_2) { _sceneTextBank = TextBankId::Tippet_Island; } _engine->_text->initSceneTextBank(); _engine->_grid->initGrid(_needChangeScene); if (_heroPositionType == ScenePositionType::kZone) { _newHeroPos = _zoneHeroPos; } else if (_heroPositionType == ScenePositionType::kScene || _heroPositionType == ScenePositionType::kNoPosition) { _newHeroPos = _sceneHeroPos; } _sceneHero->_pos = _newHeroPos; _startYFalling = _newHeroPos.y; _engine->_renderer->setLightVector(_alphaLight, _betaLight, ANGLE_0); if (_previousSceneIdx != SCENE_CEILING_GRID_FADE_1 && _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; ActorStruct *followedActor = getActor(_currentlyFollowedActor); _engine->_grid->centerOnActor(followedActor); _engine->_gameState->_magicBallIdx = -1; _engine->_movements->_lastJoyFlag = true; _engine->_grid->_useCellingGrid = -1; _engine->_grid->_cellingGridIdx = -1; _engine->_screens->_fadePalette = false; _needChangeScene = SCENE_CEILING_GRID_FADE_1; _enableGridTileRendering = true; _engine->_renderer->setLightVector(_alphaLight, _betaLight, ANGLE_0); if (_sceneMusic != -1) { debug(2, "Scene %i music track id: %i", _currentSceneIdx, _sceneMusic); _engine->_music->playTrackMusic(_sceneMusic); } _engine->_gameState->handleLateGameItems(); } ActorStruct *Scene::getActor(int32 actorIdx) { if (actorIdx < 0 || actorIdx >= NUM_MAX_ACTORS) { error("Invalid actor id given: %i", actorIdx); } 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::playSceneMusic() { if (_currentSceneIdx == LBA1SceneId::Tippet_Island_Twinsun_Cafe && _engine->_gameState->hasArrivedHamalayi()) { _engine->_music->playTrackMusic(8); } else { _engine->_music->playMidiMusic(_sceneMusic); } } void Scene::processEnvironmentSound() { if (_engine->_lbaTime < _sampleAmbienceTime) { return; } 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, repeat, 110, -1, 110); break; } } currentAmb++; // try next ambiance currentAmb &= 3; // loop in all 4 ambiances } // compute next ambiance timer _sampleAmbienceTime = _engine->_lbaTime + TO_SECONDS(_engine->getRandomNumber(_sampleMinDelayRnd) + _sampleMinDelay); } 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 int32 amount = zone->infoData.Bonus.amount; const int32 angle = _engine->_movements->getAngleAndSetTargetActorDistance(ABS(zone->maxs.x + zone->mins.x) / 2, ABS(zone->maxs.z + zone->mins.z) / 2, _sceneHero->_pos.x, _sceneHero->_pos.z); const int32 index = _engine->_extra->addExtraBonus(ABS(zone->maxs.x + zone->mins.x) / 2, zone->maxs.y, ABS(zone->maxs.z + zone->mins.z) / 2, ANGLE_63, angle, bonusSprite, amount); if (index != -1) { _engine->_extra->_extraList[index].type |= ExtraType::TIME_IN; zone->infoData.Bonus.used = 1; // set as used } } void Scene::processActorZones(int32 actorIdx) { ActorStruct *actor = &_sceneActors[actorIdx]; int32 currentX = actor->_pos.x; int32 currentY = actor->_pos.y; int32 currentZ = actor->_pos.z; actor->_zone = -1; bool tmpCellingGrid = false; 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->mins.x && currentX <= zone->maxs.x) && (currentY >= zone->mins.y && currentY <= zone->maxs.y) && (currentZ >= zone->mins.z && currentZ <= zone->maxs.z)) { switch (zone->type) { case ZoneType::kCube: if (IS_HERO(actorIdx) && actor->_life > 0) { _needChangeScene = zone->infoData.ChangeScene.newSceneIdx; _zoneHeroPos.x = actor->_pos.x - zone->mins.x + zone->infoData.ChangeScene.x; _zoneHeroPos.y = actor->_pos.y - zone->mins.y + zone->infoData.ChangeScene.y; _zoneHeroPos.z = actor->_pos.z - zone->mins.z + zone->infoData.ChangeScene.z; _heroPositionType = ScenePositionType::kZone; } break; case ZoneType::kCamera: if (_currentlyFollowedActor == actorIdx && !_engine->_debugGrid->_useFreeCamera) { _engine->_disableScreenRecenter = true; if (_engine->_grid->_newCamera.x != zone->infoData.CameraView.x || _engine->_grid->_newCamera.y != zone->infoData.CameraView.y || _engine->_grid->_newCamera.z != zone->infoData.CameraView.z) { _engine->_grid->_newCamera.x = zone->infoData.CameraView.x; _engine->_grid->_newCamera.y = zone->infoData.CameraView.y; _engine->_grid->_newCamera.z = zone->infoData.CameraView.z; _engine->_redraw->_reqBgRedraw = true; } } break; case ZoneType::kSceneric: actor->_zone = zone->infoData.Sceneric.zoneIdx; break; case ZoneType::kGrid: if (_currentlyFollowedActor == actorIdx) { tmpCellingGrid = true; 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 ZoneType::kObject: if (IS_HERO(actorIdx) && _engine->_movements->shouldTriggerZoneAction()) { _engine->_animations->initAnim(AnimationTypes::kAction, AnimType::kAnimationThen, AnimationTypes::kStanding, OWN_ACTOR_SCENE_INDEX); processZoneExtraBonus(zone); } break; case ZoneType::kText: if (IS_HERO(actorIdx) && _engine->_movements->shouldTriggerZoneAction()) { ScopedEngineFreeze scopedFreeze(_engine); _engine->exitSceneryView(); _engine->_text->setFontCrossColor(zone->infoData.DisplayText.textColor); _talkingActor = actorIdx; _engine->_text->drawTextProgressive(zone->infoData.DisplayText.textIdx); _engine->_redraw->redrawEngineActions(true); } break; case ZoneType::kLadder: if (IS_HERO(actorIdx) && _engine->_actor->_heroBehaviour != HeroBehaviourType::kProtoPack && (actor->_anim == AnimationTypes::kForward || actor->_anim == AnimationTypes::kTopLadder || actor->_anim == AnimationTypes::kClimbLadder)) { IVec3 destPos = _engine->_movements->rotateActor(actor->_boudingBox.mins.x, actor->_boudingBox.mins.z, actor->_angle + ANGLE_360 + ANGLE_135); destPos.x += _engine->_movements->_processActor.x; destPos.z += _engine->_movements->_processActor.z; if (destPos.x >= 0 && destPos.z >= 0 && destPos.x <= SCENE_SIZE_MAX && destPos.z <= SCENE_SIZE_MAX) { if (_engine->_grid->getBrickShape(destPos.x, actor->_pos.y + BRICK_HEIGHT, destPos.z) != ShapeType::kNone) { _currentActorInZone = true; if (actor->_pos.y >= ABS(zone->mins.y + zone->maxs.y) / 2) { _engine->_animations->initAnim(AnimationTypes::kTopLadder, AnimType::kAnimationAllThen, AnimationTypes::kStanding, actorIdx); // reached end of ladder } else { _engine->_animations->initAnim(AnimationTypes::kClimbLadder, AnimType::kAnimationTypeLoop, 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