mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-14 21:59:17 +00:00
831 lines
23 KiB
C++
831 lines
23 KiB
C++
/* ResidualVM - A 3D game interpreter
|
|
*
|
|
* ResidualVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the AUTHORS
|
|
* 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 "engines/myst3/state.h"
|
|
#include "engines/myst3/database.h"
|
|
#include "engines/myst3/gfx.h"
|
|
|
|
#include "common/debug-channels.h"
|
|
#include "common/ptr.h"
|
|
#include "common/savefile.h"
|
|
|
|
#include "graphics/surface.h"
|
|
|
|
namespace Myst3 {
|
|
|
|
GameState::StateData::StateData() {
|
|
version = GameState::kSaveVersion;
|
|
gameRunning = true;
|
|
tickCount = 0;
|
|
nextSecondsUpdate = 0;
|
|
secondsPlayed = 0;
|
|
dword_4C2C44 = 0;
|
|
dword_4C2C48 = 0;
|
|
dword_4C2C4C = 0;
|
|
dword_4C2C50 = 0;
|
|
dword_4C2C54 = 0;
|
|
dword_4C2C58 = 0;
|
|
dword_4C2C5C = 0;
|
|
dword_4C2C60 = 0;
|
|
currentNodeType = 0;
|
|
lookatPitch = 0;
|
|
lookatHeading = 0;
|
|
lookatFOV = 0;
|
|
pitchOffset = 0;
|
|
headingOffset = 0;
|
|
limitCubeCamera = 0;
|
|
minPitch = 0;
|
|
maxPitch = 0;
|
|
minHeading = 0;
|
|
maxHeading = 0;
|
|
dword_4C2C90 = 0;
|
|
|
|
for (uint i = 0; i < 2048; i++)
|
|
vars[i] = 0;
|
|
|
|
vars[0] = 0;
|
|
vars[1] = 1;
|
|
|
|
inventoryCount = 0;
|
|
|
|
for (uint i = 0; i < 7; i++)
|
|
inventoryList[i] = 0;
|
|
|
|
for (uint i = 0; i < 64; i++)
|
|
zipDestinations[i] = 0;
|
|
|
|
saveDay = 0;
|
|
saveMonth = 0;
|
|
saveYear = 0;
|
|
saveHour = 0;
|
|
saveMinute = 0;
|
|
}
|
|
|
|
GameState::GameState(const Common::Platform platform, Database *database):
|
|
_platform(platform),
|
|
_db(database) {
|
|
|
|
#define VAR(var, x, unk) _varDescriptions.setVal(#x, VarDescription(var, #x, unk));
|
|
|
|
VAR(14, CursorTransparency, false)
|
|
|
|
VAR(47, ProjectorAngleX, false)
|
|
VAR(48, ProjectorAngleY, false)
|
|
VAR(49, ProjectorAngleZoom, false)
|
|
VAR(50, ProjectorAngleBlur, false)
|
|
VAR(51, DraggedWeight, false)
|
|
|
|
VAR(57, DragEnded, false)
|
|
VAR(58, DragLeverSpeed, false)
|
|
VAR(59, DragPositionFound, false)
|
|
VAR(60, DragLeverPositionChanged, false)
|
|
|
|
VAR(61, LocationAge, false)
|
|
VAR(62, LocationRoom, false)
|
|
VAR(63, LocationNode, false)
|
|
VAR(64, BookSavedAge, false)
|
|
VAR(65, BookSavedRoom, false)
|
|
VAR(66, BookSavedNode, false)
|
|
VAR(67, MenuSavedAge, false)
|
|
VAR(68, MenuSavedRoom, false)
|
|
VAR(69, MenuSavedNode, false)
|
|
|
|
VAR(70, SecondsCountdown, false)
|
|
VAR(71, TickCountdown, false)
|
|
|
|
// Counters, unused by the game scripts
|
|
VAR(76, CounterUnk76, false)
|
|
VAR(77, CounterUnk77, false)
|
|
VAR(78, CounterUnk78, false)
|
|
|
|
VAR(79, SweepEnabled, false)
|
|
VAR(80, SweepValue, false)
|
|
VAR(81, SweepStep, false)
|
|
VAR(82, SweepMin, false)
|
|
VAR(83, SweepMax, false)
|
|
|
|
VAR(84, InputMousePressed, false)
|
|
VAR(88, InputEscapePressed, false)
|
|
VAR(89, InputTildePressed, false)
|
|
VAR(90, InputSpacePressed, false)
|
|
|
|
VAR(92, HotspotActiveRect, false)
|
|
|
|
VAR(93, WaterEffectRunning, false)
|
|
VAR(94, WaterEffectActive, false)
|
|
VAR(95, WaterEffectSpeed, false)
|
|
VAR(96, WaterEffectAttenuation, false)
|
|
VAR(97, WaterEffectFrequency, false)
|
|
VAR(98, WaterEffectAmpl, false)
|
|
VAR(99, WaterEffectMaxStep, false)
|
|
VAR(100, WaterEffectAmplOffset, false)
|
|
|
|
VAR(101, LavaEffectActive, false)
|
|
VAR(102, LavaEffectSpeed, false)
|
|
VAR(103, LavaEffectAmpl, false)
|
|
VAR(104, LavaEffectStepSize, false)
|
|
|
|
VAR(105, MagnetEffectActive, false)
|
|
VAR(106, MagnetEffectSpeed, false)
|
|
VAR(107, MagnetEffectUnk1, false)
|
|
VAR(108, MagnetEffectUnk2, false)
|
|
VAR(109, MagnetEffectSound, false)
|
|
VAR(110, MagnetEffectNode, false)
|
|
VAR(111, MagnetEffectUnk3, false)
|
|
|
|
VAR(112, ShakeEffectAmpl, false)
|
|
VAR(113, ShakeEffectTickPeriod, false)
|
|
VAR(114, RotationEffectSpeed, false)
|
|
VAR(115, SunspotIntensity, false)
|
|
VAR(116, SunspotColor, false)
|
|
VAR(117, SunspotRadius, false)
|
|
|
|
VAR(119, AmbiantFadeOutDelay, false)
|
|
VAR(120, AmbiantPreviousFadeOutDelay, false)
|
|
VAR(121, AmbientOverrideFadeOutDelay, false)
|
|
VAR(122, SoundScriptsSuspended, false)
|
|
|
|
VAR(124, SoundNextMultipleSounds, false)
|
|
VAR(125, SoundNextIsChoosen, false)
|
|
VAR(126, SoundNextId, false)
|
|
VAR(127, SoundNextIsLast, false)
|
|
VAR(128, SoundScriptsTimer, false)
|
|
VAR(129, SoundScriptsPaused, false)
|
|
VAR(130, SoundScriptFadeOutDelay, false)
|
|
|
|
VAR(131, CursorLocked, false)
|
|
VAR(132, CursorHidden, false)
|
|
|
|
VAR(136, CameraPitch, false)
|
|
VAR(137, CameraHeading, false)
|
|
VAR(140, CameraMinPitch, false)
|
|
VAR(141, CameraMaxPitch, false)
|
|
|
|
VAR(142, MovieStartFrame, false)
|
|
VAR(143, MovieEndFrame, false)
|
|
VAR(144, MovieVolume1, false)
|
|
VAR(145, MovieVolume2, false)
|
|
VAR(146, MovieOverrideSubtitles, false)
|
|
|
|
VAR(149, MovieConditionBit, false)
|
|
VAR(150, MoviePreloadToMemory, false)
|
|
VAR(151, MovieScriptDriven, false)
|
|
VAR(152, MovieNextFrameSetVar, false)
|
|
VAR(153, MovieNextFrameGetVar, false)
|
|
VAR(154, MovieStartFrameVar, false)
|
|
VAR(155, MovieEndFrameVar, false)
|
|
VAR(156, MovieForce2d, false)
|
|
VAR(157, MovieVolumeVar, false)
|
|
VAR(158, MovieSoundHeading, false)
|
|
VAR(159, MoviePanningStrenght, false)
|
|
VAR(160, MovieSynchronized, false)
|
|
|
|
// We ignore this, and never skip frames
|
|
VAR(161, MovieNoFrameSkip, false)
|
|
|
|
// Only play the audio track. This is used in TOHO 3 only.
|
|
// Looks like it works fine without any specific implementation
|
|
VAR(162, MovieAudioOnly, false)
|
|
|
|
VAR(163, MovieOverrideCondition, false)
|
|
VAR(164, MovieUVar, false)
|
|
VAR(165, MovieVVar, false)
|
|
VAR(166, MovieOverridePosition, false)
|
|
VAR(167, MovieOverridePosU, false)
|
|
VAR(168, MovieOverridePosV, false)
|
|
VAR(169, MovieScale, false)
|
|
VAR(170, MovieAdditiveBlending, false)
|
|
VAR(171, MovieTransparency, false)
|
|
VAR(172, MovieTransparencyVar, false)
|
|
VAR(173, MoviePlayingVar, false)
|
|
VAR(174, MovieStartSoundId, false)
|
|
VAR(175, MovieStartSoundVolume, false)
|
|
VAR(176, MovieStartSoundHeading, false)
|
|
VAR(177, MovieStartSoundAttenuation, false)
|
|
|
|
VAR(178, MovieUseBackground, false)
|
|
VAR(179, CameraSkipAnimation, false)
|
|
VAR(180, MovieAmbiantScriptStartFrame, false)
|
|
VAR(181, MovieAmbiantScript, false)
|
|
VAR(182, MovieScriptStartFrame, false)
|
|
VAR(183, MovieScript, false)
|
|
|
|
VAR(185, CameraMoveSpeed, false)
|
|
|
|
// We always allow missing SpotItem data
|
|
VAR(186, SpotItemAllowMissing, false)
|
|
|
|
VAR(187, TransitionSound, false)
|
|
VAR(188, TransitionSoundVolume, false)
|
|
VAR(189, LocationNextNode, false)
|
|
VAR(190, LocationNextRoom, false)
|
|
VAR(191, LocationNextAge, false)
|
|
|
|
VAR(195, BallPosition, false)
|
|
VAR(196, BallFrame, false)
|
|
VAR(197, BallLeverLeft, false)
|
|
VAR(198, BallLeverRight, false)
|
|
|
|
VAR(228, BallDoorOpen, false)
|
|
|
|
VAR(243, ProjectorX, false)
|
|
VAR(244, ProjectorY, false)
|
|
VAR(245, ProjectorZoom, false)
|
|
VAR(246, ProjectorBlur, false)
|
|
VAR(247, ProjectorAngleXOffset, false)
|
|
VAR(248, ProjectorAngleYOffset, false)
|
|
VAR(249, ProjectorAngleZoomOffset, false)
|
|
VAR(250, ProjectorAngleBlurOffset, false)
|
|
|
|
VAR(277, JournalAtrusState, false)
|
|
VAR(279, JournalSaavedroState, false)
|
|
VAR(280, JournalSaavedroClosed, false)
|
|
VAR(281, JournalSaavedroOpen, false)
|
|
VAR(282, JournalSaavedroLastPage, false)
|
|
VAR(283, JournalSaavedroChapter, false)
|
|
VAR(284, JournalSaavedroPageInChapter, false)
|
|
|
|
VAR(329, TeslaAllAligned, false)
|
|
VAR(330, TeslaTopAligned, false)
|
|
VAR(331, TeslaMiddleAligned, false)
|
|
VAR(332, TeslaBottomAligned, false)
|
|
VAR(333, TeslaMovieStart, false)
|
|
|
|
// Amateria ambient sound / movie counters (XXXX 1001 and XXXX 1002)
|
|
VAR(406, AmateriaSecondsCounter, false)
|
|
VAR(407, AmateriaTicksCounter, false)
|
|
|
|
VAR(444, ResonanceRingsSolved, false)
|
|
|
|
VAR(460, PinballRemainingPegs, false)
|
|
|
|
VAR(475, OuterShieldUp, false)
|
|
VAR(476, InnerShieldUp, false)
|
|
VAR(479, SaavedroStatus, false)
|
|
|
|
VAR(480, BookStateTomahna, false)
|
|
VAR(481, BookStateReleeshahn, false)
|
|
|
|
VAR(489, SymbolCode2Solved, false)
|
|
VAR(495, SymbolCode1AllSolved, false)
|
|
VAR(496, SymbolCode1CurrentSolved, false)
|
|
VAR(497, SymbolCode1TopSolved, false)
|
|
VAR(502, SymbolCode1LeftSolved, false)
|
|
VAR(507, SymbolCode1RightSolved, false)
|
|
|
|
VAR(540, SoundVoltaicUnk540, false)
|
|
VAR(587, SoundEdannaUnk587, false)
|
|
VAR(627, SoundAmateriaUnk627, false)
|
|
VAR(930, SoundAmateriaUnk930, false)
|
|
VAR(1031, SoundEdannaUnk1031, false)
|
|
VAR(1146, SoundVoltaicUnk1146, false)
|
|
|
|
VAR(1322, ZipModeEnabled, false)
|
|
VAR(1323, SubtitlesEnabled, false)
|
|
VAR(1324, WaterEffects, false)
|
|
VAR(1325, TransitionSpeed, false)
|
|
VAR(1326, MouseSpeed, false)
|
|
VAR(1327, DialogResult, false)
|
|
|
|
VAR(1395, HotspotIgnoreClick, false)
|
|
VAR(1396, HotspotHovered, false)
|
|
VAR(1397, SpotSubtitle, false)
|
|
|
|
// Override node from which effect masks are loaded
|
|
// This is only used in LEIS x75, but is useless
|
|
// since all the affected nodes have the same effect masks
|
|
VAR(1398, EffectsOverrideMaskNode, false)
|
|
|
|
VAR(1399, DragLeverLimited, false)
|
|
VAR(1400, DragLeverLimitMin, false)
|
|
VAR(1401, DragLeverLimitMax, false)
|
|
|
|
// Mouse unk
|
|
VAR(6, Unk6, true)
|
|
|
|
// Backup var for opcodes 245, 246 => find usage
|
|
VAR(13, Unk13, true)
|
|
|
|
// ???
|
|
VAR(147, MovieUnk147, true)
|
|
VAR(148, MovieUnk148, true)
|
|
|
|
if (_platform != Common::kPlatformXbox) {
|
|
VAR(1337, MenuEscapePressed, false)
|
|
VAR(1338, MenuNextAction, false)
|
|
VAR(1339, MenuLoadBack, false)
|
|
VAR(1340, MenuSaveBack, false)
|
|
VAR(1341, MenuSaveAction, false)
|
|
VAR(1342, MenuOptionsBack, false)
|
|
|
|
VAR(1350, MenuSaveLoadPageLeft, false)
|
|
VAR(1351, MenuSaveLoadPageRight, false)
|
|
VAR(1352, MenuSaveLoadSelectedItem, false)
|
|
VAR(1353, MenuSaveLoadCurrentPage, false)
|
|
|
|
// Menu stuff does not look like it's too useful
|
|
VAR(1361, Unk1361, true)
|
|
VAR(1362, Unk1362, true)
|
|
VAR(1363, Unk1363, true)
|
|
|
|
VAR(1374, OverallVolume, false)
|
|
VAR(1377, MusicVolume, false)
|
|
VAR(1380, MusicFrequency, false)
|
|
VAR(1393, LanguageAudio, false)
|
|
VAR(1394, LanguageText, false)
|
|
|
|
VAR(1406, ShieldEffectActive, false)
|
|
|
|
} else {
|
|
shiftVariables(927, 1);
|
|
shiftVariables(1031, 2);
|
|
shiftVariables(1395, -22);
|
|
|
|
VAR(1340, MenuSavesAvailable, false)
|
|
VAR(1341, MenuNextAction, false)
|
|
VAR(1342, MenuLoadBack, false)
|
|
VAR(1343, MenuSaveBack, false)
|
|
VAR(1344, MenuSaveAction, false)
|
|
VAR(1345, MenuOptionsBack, false)
|
|
VAR(1346, MenuSelectedSave, false)
|
|
|
|
VAR(1384, MovieOptional, false)
|
|
VAR(1386, VibrationEnabled, false)
|
|
|
|
VAR(1430, GamePadActionPressed, false)
|
|
VAR(1431, GamePadDownPressed, false)
|
|
VAR(1432, GamePadUpPressed, false)
|
|
VAR(1433, GamePadLeftPressed, false)
|
|
VAR(1434, GamePadRightPressed, false)
|
|
VAR(1435, GamePadCancelPressed, false)
|
|
|
|
VAR(1437, DragWithDirectionKeys, false)
|
|
VAR(1438, MenuAttractCountDown, false)
|
|
VAR(1439, ShieldEffectActive, false)
|
|
|
|
VAR(1445, StateCanSave, false)
|
|
}
|
|
|
|
#undef VAR
|
|
|
|
newGame();
|
|
}
|
|
|
|
GameState::~GameState() {
|
|
}
|
|
|
|
void GameState::syncFloat(Common::Serializer &s, float &val,
|
|
Common::Serializer::Version minVersion, Common::Serializer::Version maxVersion) {
|
|
static const float precision = 10000.0;
|
|
|
|
if (s.isLoading()) {
|
|
int32 saved = 0;
|
|
s.syncAsSint32LE(saved, minVersion, maxVersion);
|
|
val = saved / precision;
|
|
} else {
|
|
int32 toSave = static_cast<int32>(val * precision);
|
|
s.syncAsSint32LE(toSave, minVersion, maxVersion);
|
|
}
|
|
}
|
|
|
|
Common::Error GameState::StateData::syncWithSaveGame(Common::Serializer &s) {
|
|
if (!s.syncVersion(kSaveVersion))
|
|
return Common::Error(Common::kUnknownError, Common::String::format("This savegame (v%d) is too recent (max %d) please get a newer version of ResidualVM", s.getVersion(), kSaveVersion));
|
|
|
|
s.syncAsUint32LE(gameRunning);
|
|
s.syncAsUint32LE(tickCount);
|
|
s.syncAsUint32LE(nextSecondsUpdate);
|
|
s.syncAsUint32LE(secondsPlayed);
|
|
s.syncAsUint32LE(dword_4C2C44);
|
|
s.syncAsUint32LE(dword_4C2C48);
|
|
s.syncAsUint32LE(dword_4C2C4C);
|
|
s.syncAsUint32LE(dword_4C2C50);
|
|
s.syncAsUint32LE(dword_4C2C54);
|
|
s.syncAsUint32LE(dword_4C2C58);
|
|
s.syncAsUint32LE(dword_4C2C5C);
|
|
s.syncAsUint32LE(dword_4C2C60);
|
|
s.syncAsUint32LE(currentNodeType);
|
|
|
|
// The original engine (v148) saved the raw IEE754 data,
|
|
// we save decimal data as fixed point instead to be achieve portability
|
|
if (s.getVersion() < 149) {
|
|
s.syncBytes((byte*) &lookatPitch, sizeof(float));
|
|
s.syncBytes((byte*) &lookatHeading, sizeof(float));
|
|
s.syncBytes((byte*) &lookatFOV, sizeof(float));
|
|
s.syncBytes((byte*) &pitchOffset, sizeof(float));
|
|
s.syncBytes((byte*) &headingOffset, sizeof(float));
|
|
} else {
|
|
syncFloat(s, lookatPitch);
|
|
syncFloat(s, lookatHeading);
|
|
syncFloat(s, lookatFOV);
|
|
syncFloat(s, pitchOffset);
|
|
syncFloat(s, headingOffset);
|
|
}
|
|
|
|
s.syncAsUint32LE(limitCubeCamera);
|
|
|
|
if (s.getVersion() < 149) {
|
|
s.syncBytes((byte*) &minPitch, sizeof(float));
|
|
s.syncBytes((byte*) &maxPitch, sizeof(float));
|
|
s.syncBytes((byte*) &minHeading, sizeof(float));
|
|
s.syncBytes((byte*) &maxHeading, sizeof(float));
|
|
} else {
|
|
syncFloat(s, minPitch);
|
|
syncFloat(s, maxPitch);
|
|
syncFloat(s, minHeading);
|
|
syncFloat(s, maxHeading);
|
|
}
|
|
|
|
s.syncAsUint32LE(dword_4C2C90);
|
|
|
|
for (uint i = 0; i < 2048; i++)
|
|
s.syncAsSint32LE(vars[i]);
|
|
|
|
s.syncAsUint32LE(inventoryCount);
|
|
|
|
for (uint i = 0; i < 7; i++)
|
|
s.syncAsUint32LE(inventoryList[i]);
|
|
|
|
for (uint i = 0; i < 64; i++)
|
|
s.syncAsUint32LE(zipDestinations[i]);
|
|
|
|
s.syncAsByte(saveDay, 149);
|
|
s.syncAsByte(saveMonth, 149);
|
|
s.syncAsUint16LE(saveYear, 149);
|
|
s.syncAsByte(saveHour, 149);
|
|
s.syncAsByte(saveMinute, 149);
|
|
s.syncString(saveDescription, 149);
|
|
|
|
return Common::kNoError;
|
|
}
|
|
|
|
const Graphics::PixelFormat GameState::getThumbnailSavePixelFormat() {
|
|
#ifdef SCUMM_BIG_ENDIAN
|
|
return Graphics::PixelFormat(4, 8, 8, 8, 0, 8, 16, 24, 0);
|
|
#else
|
|
return Graphics::PixelFormat(4, 8, 8, 8, 0, 16, 8, 0, 24);
|
|
#endif
|
|
}
|
|
|
|
Graphics::Surface *GameState::readThumbnail(Common::ReadStream *inStream) {
|
|
Graphics::Surface *thumbnail = new Graphics::Surface();
|
|
thumbnail->create(kThumbnailWidth, kThumbnailHeight, getThumbnailSavePixelFormat());
|
|
|
|
inStream->read((byte *)thumbnail->getPixels(), kThumbnailWidth * kThumbnailHeight * 4);
|
|
|
|
thumbnail->convertToInPlace(Texture::getRGBAPixelFormat());
|
|
|
|
return thumbnail;
|
|
}
|
|
|
|
void GameState::writeThumbnail(Common::WriteStream *outStream, const Graphics::Surface *thumbnail) {
|
|
assert(thumbnail->format == Texture::getRGBAPixelFormat());
|
|
assert(thumbnail && thumbnail->w == kThumbnailWidth && thumbnail->h == kThumbnailHeight);
|
|
|
|
Graphics::Surface *converted = thumbnail->convertTo(getThumbnailSavePixelFormat());
|
|
|
|
outStream->write((byte *)converted->getPixels(), kThumbnailWidth * kThumbnailHeight * 4);
|
|
|
|
converted->free();
|
|
delete converted;
|
|
}
|
|
|
|
Graphics::Surface *GameState::resizeThumbnail(Graphics::Surface *big, uint width, uint height) {
|
|
assert(big->format.bytesPerPixel == 4);
|
|
Graphics::Surface *small = new Graphics::Surface();
|
|
small->create(width, height, big->format);
|
|
|
|
uint32 *dst = (uint32 *)small->getPixels();
|
|
for (uint i = 0; i < small->h; i++) {
|
|
for (uint j = 0; j < small->w; j++) {
|
|
uint32 srcX = big->w * j / small->w;
|
|
uint32 srcY = big->h * i / small->h;
|
|
uint32 *src = (uint32 *)big->getBasePtr(srcX, srcY);
|
|
|
|
// Copy RGBA pixel
|
|
*dst++ = *src;
|
|
}
|
|
}
|
|
|
|
return small;
|
|
}
|
|
|
|
void GameState::newGame() {
|
|
_data = StateData();
|
|
_lastTickStartTime = g_system->getMillis();
|
|
}
|
|
|
|
Common::Error GameState::load(Common::InSaveFile *saveFile) {
|
|
Common::Serializer s = Common::Serializer(saveFile, 0);
|
|
Common::Error loadError = _data.syncWithSaveGame(s);
|
|
|
|
_data.gameRunning = true;
|
|
|
|
if (loadError.getCode() != Common::kNoError) {
|
|
return loadError;
|
|
}
|
|
|
|
return Common::kNoError;
|
|
}
|
|
|
|
Common::Error GameState::save(Common::OutSaveFile *saveFile, const Common::String &description, const Graphics::Surface *thumbnail) {
|
|
Common::Serializer s = Common::Serializer(0, saveFile);
|
|
|
|
// Update save creation info
|
|
TimeDate t;
|
|
g_system->getTimeAndDate(t);
|
|
_data.saveYear = t.tm_year + 1900;
|
|
_data.saveMonth = t.tm_mon + 1;
|
|
_data.saveDay = t.tm_mday;
|
|
_data.saveHour = t.tm_hour;
|
|
_data.saveMinute = t.tm_min;
|
|
_data.saveDescription = description;
|
|
|
|
_data.gameRunning = false;
|
|
|
|
Common::Error saveError = _data.syncWithSaveGame(s);
|
|
if (saveError.getCode() != Common::kNoError) {
|
|
return saveError;
|
|
}
|
|
|
|
writeThumbnail(saveFile, thumbnail);
|
|
|
|
_data.gameRunning = true;
|
|
|
|
return Common::kNoError;
|
|
}
|
|
|
|
Common::String GameState::formatSaveTime() {
|
|
if (_data.saveYear == 0)
|
|
return "";
|
|
|
|
// TODO: Check the Xbox NTSC version, maybe it uses that strange MM/DD/YYYY format
|
|
return Common::String::format("%02d/%02d/%02d %02d:%02d",
|
|
_data.saveDay, _data.saveMonth, _data.saveYear,
|
|
_data.saveHour, _data.saveMinute);
|
|
}
|
|
|
|
Common::Array<uint16> GameState::getInventory() {
|
|
Common::Array<uint16> items;
|
|
|
|
for (uint i = 0; i < _data.inventoryCount; i++)
|
|
items.push_back(_data.inventoryList[i]);
|
|
|
|
return items;
|
|
}
|
|
|
|
void GameState::updateInventory(const Common::Array<uint16> &items) {
|
|
for (uint i = 0; i < 7; i++)
|
|
_data.inventoryList[i] = 0;
|
|
|
|
for (uint i = 0; i < items.size(); i++)
|
|
_data.inventoryList[i] = items[i];
|
|
|
|
_data.inventoryCount = items.size();
|
|
}
|
|
|
|
void GameState::checkRange(uint16 var) {
|
|
if (var < 1 || var > 2047)
|
|
error("Variable out of range %d", var);
|
|
}
|
|
|
|
const GameState::VarDescription GameState::findDescription(uint16 var) {
|
|
for (VarMap::const_iterator it = _varDescriptions.begin(); it != _varDescriptions.end(); it++) {
|
|
if (it->_value.var == var) {
|
|
return it->_value;
|
|
}
|
|
}
|
|
|
|
return VarDescription();
|
|
}
|
|
|
|
void GameState::shiftVariables(uint16 base, int32 value) {
|
|
for (VarMap::iterator it = _varDescriptions.begin(); it != _varDescriptions.end(); it++) {
|
|
if (it->_value.var >= base) {
|
|
it->_value.var += value;
|
|
}
|
|
}
|
|
}
|
|
|
|
int32 GameState::getVar(uint16 var) {
|
|
checkRange(var);
|
|
|
|
return _data.vars[var];
|
|
}
|
|
|
|
void GameState::setVar(uint16 var, int32 value) {
|
|
checkRange(var);
|
|
|
|
if (DebugMan.isDebugChannelEnabled(kDebugVariable)) {
|
|
const VarDescription &d = findDescription(var);
|
|
|
|
if (d.name && d.unknown) {
|
|
warning("A script is writing to the unimplemented engine-mapped var %d (%s)", var, d.name);
|
|
}
|
|
}
|
|
|
|
_data.vars[var] = value;
|
|
}
|
|
|
|
bool GameState::evaluate(int16 condition) {
|
|
uint16 unsignedCond = abs(condition);
|
|
uint16 var = unsignedCond & 2047;
|
|
int32 varValue = getVar(var);
|
|
int32 targetValue = (unsignedCond >> 11) - 1;
|
|
|
|
if (targetValue >= 0) {
|
|
if (condition >= 0)
|
|
return varValue == targetValue;
|
|
else
|
|
return varValue != targetValue;
|
|
} else {
|
|
if (condition >= 0)
|
|
return varValue != 0;
|
|
else
|
|
return varValue == 0;
|
|
}
|
|
}
|
|
|
|
int32 GameState::valueOrVarValue(int16 value) {
|
|
if (value < 0)
|
|
return getVar(-value);
|
|
|
|
return value;
|
|
}
|
|
|
|
int32 GameState::engineGet(const Common::String &varName) {
|
|
if (!_varDescriptions.contains(varName))
|
|
error("The engine is trying to access an undescribed var (%s)", varName.c_str());
|
|
|
|
const VarDescription &d = _varDescriptions.getVal(varName);
|
|
|
|
return _data.vars[d.var];
|
|
}
|
|
|
|
void GameState::engineSet(const Common::String &varName, int32 value) {
|
|
if (!_varDescriptions.contains(varName))
|
|
error("The engine is trying to access an undescribed var (%s)", varName.c_str());
|
|
|
|
const VarDescription &d = _varDescriptions.getVal(varName);
|
|
|
|
_data.vars[d.var] = value;
|
|
}
|
|
|
|
const Common::String GameState::describeVar(uint16 var) {
|
|
const VarDescription &d = findDescription(var);
|
|
|
|
if (d.name) {
|
|
return Common::String::format("v%s", d.name);
|
|
} else {
|
|
return Common::String::format("v%d", var);
|
|
}
|
|
}
|
|
|
|
const Common::String GameState::describeCondition(int16 condition) {
|
|
uint16 unsignedCond = abs(condition);
|
|
uint16 var = unsignedCond & 2047;
|
|
int16 value = (unsignedCond >> 11) - 1;
|
|
|
|
return Common::String::format("c[%s %s %d]",
|
|
describeVar(var).c_str(),
|
|
(condition >= 0 && value >= 0) || (condition < 0 && value < 0) ? "==" : "!=",
|
|
value >= 0 ? value : 0);
|
|
}
|
|
|
|
void GameState::limitCubeCamera(float minPitch, float maxPitch, float minHeading, float maxHeading) {
|
|
_data.limitCubeCamera = true;
|
|
_data.minPitch = minPitch;
|
|
_data.maxPitch = maxPitch;
|
|
_data.minHeading = minHeading;
|
|
_data.maxHeading = maxHeading;
|
|
}
|
|
|
|
void GameState::updateFrameCounters() {
|
|
if (!_data.gameRunning)
|
|
return;
|
|
|
|
uint32 currentTime = g_system->getMillis();
|
|
int32 timeToNextTick = _lastTickStartTime + kTickDuration - currentTime;
|
|
|
|
if (timeToNextTick <= 0) {
|
|
_data.tickCount++;
|
|
updateTickCounters();
|
|
_lastTickStartTime = currentTime + timeToNextTick;
|
|
}
|
|
|
|
if (currentTime > _data.nextSecondsUpdate || ABS<int32>(_data.nextSecondsUpdate - currentTime) > 2000) {
|
|
_data.secondsPlayed++;
|
|
_data.nextSecondsUpdate = currentTime + 1000;
|
|
|
|
int32 secondsCountdown = getSecondsCountdown();
|
|
if (secondsCountdown > 0)
|
|
setSecondsCountdown(--secondsCountdown);
|
|
|
|
if (getAmateriaSecondsCounter() > 0)
|
|
setAmateriaSecondsCounter(getAmateriaSecondsCounter() - 1);
|
|
|
|
if (getSoundScriptsTimer() > 0)
|
|
setSoundScriptsTimer(getSoundScriptsTimer() - 1);
|
|
|
|
if (hasVarMenuAttractCountDown() && getMenuAttractCountDown() > 0)
|
|
setMenuAttractCountDown(getMenuAttractCountDown() - 1);
|
|
}
|
|
}
|
|
|
|
void GameState::updateTickCounters() {
|
|
int32 tickCountdown = getTickCountdown();
|
|
if (tickCountdown > 0)
|
|
setTickCountdown(--tickCountdown);
|
|
|
|
if (getAmateriaTicksCounter() > 0)
|
|
setAmateriaTicksCounter(getAmateriaTicksCounter() - 1);
|
|
|
|
if (getSweepEnabled()) {
|
|
if (getSweepValue() + getSweepStep() > getSweepMax()) {
|
|
setSweepValue(getSweepMax());
|
|
|
|
if (getSweepStep() > 0) {
|
|
setSweepStep(-getSweepStep());
|
|
}
|
|
} else if (getSweepValue() + getSweepStep() < getSweepMin()) {
|
|
setSweepValue(getSweepMin());
|
|
|
|
if (getSweepStep() < 0) {
|
|
setSweepStep(-getSweepStep());
|
|
}
|
|
} else {
|
|
setSweepValue(getSweepValue() + getSweepStep());
|
|
}
|
|
}
|
|
}
|
|
|
|
uint GameState::getTickCount() const {
|
|
return _data.tickCount;
|
|
}
|
|
|
|
void GameState::pauseEngine(bool pause) {
|
|
if (!pause) {
|
|
_lastTickStartTime = g_system->getMillis();
|
|
}
|
|
}
|
|
|
|
bool GameState::isZipDestinationAvailable(uint16 node, uint16 room, uint32 age) {
|
|
int32 zipBitIndex = _db->getNodeZipBitIndex(node, room, age);
|
|
|
|
int32 arrayIndex = zipBitIndex / 32;
|
|
assert(arrayIndex < 64);
|
|
|
|
return (_data.zipDestinations[arrayIndex] & (1 << (zipBitIndex % 32))) != 0;
|
|
}
|
|
|
|
void GameState::markNodeAsVisited(uint16 node, uint16 room, uint32 age) {
|
|
int32 zipBitIndex = _db->getNodeZipBitIndex(node, room, age);
|
|
|
|
int32 arrayIndex = zipBitIndex / 32;
|
|
assert(arrayIndex < 64);
|
|
|
|
_data.zipDestinations[arrayIndex] |= 1 << (zipBitIndex % 32);
|
|
}
|
|
|
|
Common::String Saves::buildName(const char *name, Common::Platform platform) {
|
|
const char *format;
|
|
|
|
if (platform == Common::kPlatformXbox) {
|
|
format = "%s.m3x";
|
|
} else {
|
|
format = "%s.m3s";
|
|
}
|
|
|
|
return Common::String::format(format, name);
|
|
}
|
|
|
|
Common::StringArray Saves::list(Common::SaveFileManager *saveFileManager, Common::Platform platform) {
|
|
Common::String searchPattern = Saves::buildName("*", platform);
|
|
Common::StringArray filenames = saveFileManager->listSavefiles(searchPattern);
|
|
|
|
// The saves are sorted alphabetically
|
|
Common::sort(filenames.begin(), filenames.end());
|
|
|
|
return filenames;
|
|
}
|
|
} // End of namespace Myst3
|