1920 lines
55 KiB
C++
1920 lines
55 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 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "common/file.h"
|
|
#include "common/rational.h"
|
|
#include "common/translation.h"
|
|
#include "common/compression/unzip.h"
|
|
#include "gui/saveload.h"
|
|
#include "image/png.h"
|
|
#include "engines/dialogs.h"
|
|
#include "engines/util.h"
|
|
#include "ultima/ultima.h"
|
|
|
|
// TODO: !! a lot of these includes are just for some hacks... clean up sometime
|
|
#include "ultima/ultima8/conf/config_file_manager.h"
|
|
#include "ultima/ultima8/kernel/object_manager.h"
|
|
#include "ultima/ultima8/games/start_u8_process.h"
|
|
#include "ultima/ultima8/games/start_crusader_process.h"
|
|
#include "ultima/ultima8/gfx/fonts/font_manager.h"
|
|
#include "ultima/ultima8/gfx/render_surface.h"
|
|
#include "ultima/ultima8/games/game_data.h"
|
|
#include "ultima/ultima8/world/world.h"
|
|
#include "ultima/ultima8/world/get_object.h"
|
|
#include "ultima/ultima8/filesys/savegame.h"
|
|
#include "ultima/ultima8/gumps/game_map_gump.h"
|
|
#include "ultima/ultima8/gumps/inverter_gump.h"
|
|
#include "ultima/ultima8/gumps/menu_gump.h"
|
|
#include "ultima/ultima8/gumps/minimap_gump.h"
|
|
#include "ultima/ultima8/gumps/cru_status_gump.h"
|
|
#include "ultima/ultima8/gumps/movie_gump.h"
|
|
#include "ultima/ultima8/gumps/weasel_gump.h"
|
|
|
|
// For gump positioning... perhaps shouldn't do it this way....
|
|
#include "ultima/ultima8/gumps/message_box_gump.h"
|
|
#include "ultima/ultima8/gumps/keypad_gump.h"
|
|
#include "ultima/ultima8/gumps/computer_gump.h"
|
|
#include "ultima/ultima8/world/actors/quick_avatar_mover_process.h"
|
|
#include "ultima/ultima8/world/actors/battery_charger_process.h"
|
|
#include "ultima/ultima8/world/actors/cru_healer_process.h"
|
|
#include "ultima/ultima8/world/actors/targeted_anim_process.h"
|
|
#include "ultima/ultima8/usecode/u8_intrinsics.h"
|
|
#include "ultima/ultima8/usecode/remorse_intrinsics.h"
|
|
#include "ultima/ultima8/usecode/regret_intrinsics.h"
|
|
|
|
#include "ultima/ultima8/gfx/cycle_process.h"
|
|
#include "ultima/ultima8/world/actors/scheduler_process.h"
|
|
#include "ultima/ultima8/world/egg_hatcher_process.h" // for a hack
|
|
#include "ultima/ultima8/usecode/uc_process.h" // more hacking
|
|
#include "ultima/ultima8/world/actors/actor_bark_notify_process.h" // guess
|
|
#include "ultima/ultima8/kernel/delay_process.h"
|
|
#include "ultima/ultima8/world/actors/avatar_gravity_process.h"
|
|
#include "ultima/ultima8/world/actors/teleport_to_egg_process.h"
|
|
#include "ultima/ultima8/world/item_selection_process.h"
|
|
#include "ultima/ultima8/world/split_item_process.h"
|
|
#include "ultima/ultima8/world/target_reticle_process.h"
|
|
#include "ultima/ultima8/world/snap_process.h"
|
|
#include "ultima/ultima8/world/crosshair_process.h"
|
|
#include "ultima/ultima8/world/actors/pathfinder_process.h"
|
|
#include "ultima/ultima8/world/actors/u8_avatar_mover_process.h"
|
|
#include "ultima/ultima8/world/actors/cru_avatar_mover_process.h"
|
|
#include "ultima/ultima8/world/actors/cru_pathfinder_process.h"
|
|
#include "ultima/ultima8/world/actors/resurrection_process.h"
|
|
#include "ultima/ultima8/world/actors/clear_feign_death_process.h"
|
|
#include "ultima/ultima8/world/actors/loiter_process.h"
|
|
#include "ultima/ultima8/world/actors/avatar_death_process.h"
|
|
#include "ultima/ultima8/world/actors/surrender_process.h"
|
|
#include "ultima/ultima8/world/actors/combat_process.h"
|
|
#include "ultima/ultima8/world/actors/guard_process.h"
|
|
#include "ultima/ultima8/world/actors/attack_process.h"
|
|
#include "ultima/ultima8/world/actors/auto_firer_process.h"
|
|
#include "ultima/ultima8/world/actors/pace_process.h"
|
|
#include "ultima/ultima8/world/actors/rolling_thunder_process.h"
|
|
#include "ultima/ultima8/world/bobo_boomer_process.h"
|
|
#include "ultima/ultima8/world/super_sprite_process.h"
|
|
#include "ultima/ultima8/world/destroy_item_process.h"
|
|
#include "ultima/ultima8/world/actors/ambush_process.h"
|
|
#include "ultima/ultima8/audio/audio_mixer.h"
|
|
#include "ultima/ultima8/audio/u8_music_process.h"
|
|
#include "ultima/ultima8/audio/cru_music_process.h"
|
|
#include "ultima/ultima8/audio/midi_player.h"
|
|
#include "ultima/ultima8/gumps/shape_viewer_gump.h"
|
|
#include "ultima/ultima8/metaengine.h"
|
|
#ifdef USE_IMGUI
|
|
#include "ultima/ultima8/debugtools.h"
|
|
#endif
|
|
|
|
//#define PAINT_TIMING 1
|
|
|
|
#define GAME_FRAME_TIME 50
|
|
|
|
namespace Ultima {
|
|
namespace Ultima8 {
|
|
|
|
using Std::string;
|
|
|
|
// a bit of a hack to prevent having to write a load function for
|
|
// every process
|
|
template<class T>
|
|
struct ProcessLoader {
|
|
static Process *load(Common::ReadStream *rs, uint32 version) {
|
|
T *p = new T();
|
|
bool ok = p->loadData(rs, version);
|
|
if (!ok) {
|
|
delete p;
|
|
p = nullptr;
|
|
}
|
|
return p;
|
|
}
|
|
};
|
|
|
|
inline bool HasPreventSaveFlag(const Gump *g) { return g->hasFlags(Gump::FLAG_PREVENT_SAVE); }
|
|
|
|
Ultima8Engine *Ultima8Engine::_instance = nullptr;
|
|
|
|
Ultima8Engine::Ultima8Engine(OSystem *syst, const Ultima::UltimaGameDescription *gameDesc) :
|
|
Engine(syst), _gameDescription(gameDesc), _randomSource("Ultima8"),
|
|
_isRunning(false), _gameInfo(nullptr),
|
|
_configFileMan(nullptr), _saveCount(0), _game(nullptr), _lastError(Common::kNoError),
|
|
_kernel(nullptr), _objectManager(nullptr), _mouse(nullptr), _ucMachine(nullptr),
|
|
_screen(nullptr), _fontManager(nullptr), _paletteManager(nullptr), _gameData(nullptr),
|
|
_world(nullptr), _desktopGump(nullptr), _gameMapGump(nullptr), _avatarMoverProcess(nullptr),
|
|
_frameSkip(false), _frameLimit(true), _interpolate(true), _animationRate(100),
|
|
_avatarInStasis(false), _cruStasis(false), _showEditorItems(false), _inversion(0),
|
|
_showTouching(false), _hackMoverEnabled(false), _timeOffset(0),
|
|
_hasCheated(false), _cheatsEnabled(false),
|
|
_fontOverride(false), _fontAntialiasing(false), _audioMixer(0), _inverterGump(nullptr),
|
|
_lerpFactor(256), _inBetweenFrame(false), _crusaderTeleporting(false), _moveKeyFrame(0),
|
|
_highRes(false), _priorFrameCounterTime(0) {
|
|
_instance = this;
|
|
}
|
|
|
|
Ultima8Engine::~Ultima8Engine() {
|
|
delete _kernel;
|
|
delete _objectManager;
|
|
delete _audioMixer;
|
|
delete _ucMachine;
|
|
delete _paletteManager;
|
|
delete _mouse;
|
|
delete _gameData;
|
|
delete _world;
|
|
delete _fontManager;
|
|
delete _screen;
|
|
delete _configFileMan;
|
|
delete _gameInfo;
|
|
|
|
_instance = nullptr;
|
|
}
|
|
|
|
Common::Error Ultima8Engine::run() {
|
|
Common::Error result = initialize();
|
|
if (result.getCode() == Common::kNoError) {
|
|
result = runGame();
|
|
deinitialize();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void Ultima8Engine::initializePath(const Common::FSNode& gamePath) {
|
|
Engine::initializePath(gamePath);
|
|
|
|
// Crusader: No Regret (DOS/German)
|
|
SearchMan.addSubDirectoryMatching(gamePath, "data", 0, 4);
|
|
}
|
|
|
|
Common::Error Ultima8Engine::initialize() {
|
|
debug(1, "-- Initializing Pentagram --");
|
|
|
|
// Call syncSoundSettings to get default volumes set
|
|
syncSoundSettings();
|
|
|
|
// Try and set up the data archive
|
|
Common::Archive *archive = Common::makeZipArchive("ultima8.dat");
|
|
if (!archive) {
|
|
GUIErrorMessageFormat(_("Unable to locate the '%s' engine data file."), "ultima8.dat");
|
|
return Common::kPathDoesNotExist;
|
|
}
|
|
SearchMan.add("ultima8.dat", archive);
|
|
|
|
setDebugger(new Debugger());
|
|
|
|
_gameInfo = nullptr;
|
|
_configFileMan = new ConfigFileManager();
|
|
_fontManager = new FontManager();
|
|
_kernel = new Kernel();
|
|
|
|
//!! move this elsewhere
|
|
_kernel->addProcessLoader("DelayProcess",
|
|
ProcessLoader<DelayProcess>::load);
|
|
_kernel->addProcessLoader("GravityProcess",
|
|
ProcessLoader<GravityProcess>::load);
|
|
_kernel->addProcessLoader("AvatarGravityProcess",
|
|
ProcessLoader<AvatarGravityProcess>::load);
|
|
_kernel->addProcessLoader("PaletteFaderProcess",
|
|
ProcessLoader<PaletteFaderProcess>::load);
|
|
_kernel->addProcessLoader("TeleportToEggProcess",
|
|
ProcessLoader<TeleportToEggProcess>::load);
|
|
_kernel->addProcessLoader("ActorAnimProcess",
|
|
ProcessLoader<ActorAnimProcess>::load);
|
|
_kernel->addProcessLoader("TargetedAnimProcess",
|
|
ProcessLoader<TargetedAnimProcess>::load);
|
|
_kernel->addProcessLoader("AvatarMoverProcess", // parent class for backward compatibility
|
|
ProcessLoader<U8AvatarMoverProcess>::load);
|
|
_kernel->addProcessLoader("U8AvatarMoverProcess",
|
|
ProcessLoader<U8AvatarMoverProcess>::load);
|
|
_kernel->addProcessLoader("CruAvatarMoverProcess",
|
|
ProcessLoader<CruAvatarMoverProcess>::load);
|
|
_kernel->addProcessLoader("QuickAvatarMoverProcess",
|
|
ProcessLoader<QuickAvatarMoverProcess>::load);
|
|
_kernel->addProcessLoader("PathfinderProcess",
|
|
ProcessLoader<PathfinderProcess>::load);
|
|
_kernel->addProcessLoader("CruPathfinderProcess",
|
|
ProcessLoader<CruPathfinderProcess>::load);
|
|
_kernel->addProcessLoader("SpriteProcess",
|
|
ProcessLoader<SpriteProcess>::load);
|
|
_kernel->addProcessLoader("CameraProcess",
|
|
ProcessLoader<CameraProcess>::load);
|
|
_kernel->addProcessLoader("MusicProcess", // parent class name for save game backwards-compatibility.
|
|
ProcessLoader<U8MusicProcess>::load);
|
|
_kernel->addProcessLoader("U8MusicProcess",
|
|
ProcessLoader<U8MusicProcess>::load);
|
|
_kernel->addProcessLoader("RemorseMusicProcess", // name was changed, keep this for backward-compatibility.
|
|
ProcessLoader<CruMusicProcess>::load);
|
|
_kernel->addProcessLoader("CruMusicProcess",
|
|
ProcessLoader<CruMusicProcess>::load);
|
|
_kernel->addProcessLoader("AudioProcess",
|
|
ProcessLoader<AudioProcess>::load);
|
|
_kernel->addProcessLoader("EggHatcherProcess",
|
|
ProcessLoader<EggHatcherProcess>::load);
|
|
_kernel->addProcessLoader("UCProcess",
|
|
ProcessLoader<UCProcess>::load);
|
|
_kernel->addProcessLoader("GumpNotifyProcess",
|
|
ProcessLoader<GumpNotifyProcess>::load);
|
|
_kernel->addProcessLoader("ResurrectionProcess",
|
|
ProcessLoader<ResurrectionProcess>::load);
|
|
_kernel->addProcessLoader("DeleteActorProcess",
|
|
ProcessLoader<DestroyItemProcess>::load); // YES, this is intentional
|
|
_kernel->addProcessLoader("DestroyItemProcess",
|
|
ProcessLoader<DestroyItemProcess>::load);
|
|
_kernel->addProcessLoader("SplitItemProcess",
|
|
ProcessLoader<SplitItemProcess>::load);
|
|
_kernel->addProcessLoader("ClearFeignDeathProcess",
|
|
ProcessLoader<ClearFeignDeathProcess>::load);
|
|
_kernel->addProcessLoader("LoiterProcess",
|
|
ProcessLoader<LoiterProcess>::load);
|
|
_kernel->addProcessLoader("AvatarDeathProcess",
|
|
ProcessLoader<AvatarDeathProcess>::load);
|
|
_kernel->addProcessLoader("GrantPeaceProcess",
|
|
ProcessLoader<GrantPeaceProcess>::load);
|
|
_kernel->addProcessLoader("CombatProcess",
|
|
ProcessLoader<CombatProcess>::load);
|
|
_kernel->addProcessLoader("FireballProcess",
|
|
ProcessLoader<FireballProcess>::load);
|
|
_kernel->addProcessLoader("HealProcess",
|
|
ProcessLoader<HealProcess>::load);
|
|
_kernel->addProcessLoader("SchedulerProcess",
|
|
ProcessLoader<SchedulerProcess>::load);
|
|
_kernel->addProcessLoader("InverterProcess",
|
|
ProcessLoader<InverterProcess>::load);
|
|
_kernel->addProcessLoader("ActorBarkNotifyProcess",
|
|
ProcessLoader<ActorBarkNotifyProcess>::load);
|
|
_kernel->addProcessLoader("AmbushProcess",
|
|
ProcessLoader<AmbushProcess>::load);
|
|
_kernel->addProcessLoader("TargetReticleProcess",
|
|
ProcessLoader<TargetReticleProcess>::load);
|
|
_kernel->addProcessLoader("SurrenderProcess",
|
|
ProcessLoader<SurrenderProcess>::load);
|
|
_kernel->addProcessLoader("CruHealerProcess",
|
|
ProcessLoader<CruHealerProcess>::load);
|
|
_kernel->addProcessLoader("BatteryChargerProcess",
|
|
ProcessLoader<BatteryChargerProcess>::load);
|
|
_kernel->addProcessLoader("CycleProcess",
|
|
ProcessLoader<CycleProcess>::load);
|
|
_kernel->addProcessLoader("GuardProcess",
|
|
ProcessLoader<GuardProcess>::load);
|
|
_kernel->addProcessLoader("SnapProcess",
|
|
ProcessLoader<SnapProcess>::load);
|
|
_kernel->addProcessLoader("CrosshairProcess",
|
|
ProcessLoader<CrosshairProcess>::load);
|
|
_kernel->addProcessLoader("ItemSelectionProcess",
|
|
ProcessLoader<ItemSelectionProcess>::load);
|
|
_kernel->addProcessLoader("PaceProcess",
|
|
ProcessLoader<PaceProcess>::load);
|
|
_kernel->addProcessLoader("SuperSpriteProcess",
|
|
ProcessLoader<SuperSpriteProcess>::load);
|
|
_kernel->addProcessLoader("AttackProcess",
|
|
ProcessLoader<AttackProcess>::load);
|
|
_kernel->addProcessLoader("AutoFirerProcess",
|
|
ProcessLoader<AutoFirerProcess>::load);
|
|
_kernel->addProcessLoader("BoboBoomerProcess",
|
|
ProcessLoader<BoboBoomerProcess>::load);
|
|
_kernel->addProcessLoader("RollingThunderProcess",
|
|
ProcessLoader<RollingThunderProcess>::load);
|
|
|
|
_objectManager = new ObjectManager();
|
|
_mouse = new Mouse();
|
|
|
|
// Audio Mixer
|
|
_audioMixer = new AudioMixer(_mixer);
|
|
|
|
debug(1, "-- Pentagram Initialized -- ");
|
|
|
|
if (setupGame()) {
|
|
Common::Error result = startupGame();
|
|
if (result.getCode() != Common::kNoError)
|
|
return result;
|
|
} else {
|
|
// Couldn't setup the game, should never happen?
|
|
warning("game failed to initialize");
|
|
}
|
|
paint();
|
|
|
|
#ifdef USE_IMGUI
|
|
ImGuiCallbacks callbacks;
|
|
bool drawImGui = debugChannelSet(-1, kDebugImGui);
|
|
callbacks.init = Ultima8::onImGuiInit;
|
|
callbacks.render = drawImGui ? Ultima8::onImGuiRender : nullptr;
|
|
callbacks.cleanup = Ultima8::onImGuiCleanup;
|
|
_system->setImGuiCallbacks(callbacks);
|
|
#endif
|
|
|
|
return Common::kNoError;
|
|
}
|
|
|
|
void Ultima8Engine::deinitialize() {
|
|
debug(1, "-- Shutting down Game -- ");
|
|
|
|
// Save config here....
|
|
|
|
// reset mouse cursor
|
|
_mouse->popAllCursors();
|
|
_mouse->pushMouseCursor(Mouse::MOUSE_NORMAL);
|
|
|
|
delete _world;
|
|
_world = nullptr;
|
|
|
|
_objectManager->reset();
|
|
|
|
delete _ucMachine;
|
|
_ucMachine = nullptr;
|
|
|
|
// This process will be cleared in kernel reset.
|
|
_avatarMoverProcess = nullptr;
|
|
|
|
_kernel->reset();
|
|
_paletteManager->reset();
|
|
_fontManager->resetGameFonts();
|
|
|
|
delete _game;
|
|
_game = nullptr;
|
|
|
|
delete _gameData;
|
|
_gameData = nullptr;
|
|
|
|
if (_audioMixer) {
|
|
_audioMixer->closeMidiOutput();
|
|
_audioMixer->reset();
|
|
delete _audioMixer;
|
|
_audioMixer = nullptr;
|
|
}
|
|
|
|
_desktopGump = nullptr;
|
|
_gameMapGump = nullptr;
|
|
_inverterGump = nullptr;
|
|
|
|
_timeOffset = -(int32)Kernel::get_instance()->getFrameNum();
|
|
_saveCount = 0;
|
|
_hasCheated = false;
|
|
|
|
_configFileMan->clearRoot("bindings");
|
|
_configFileMan->clearRoot("language");
|
|
_configFileMan->clearRoot("weapons");
|
|
_configFileMan->clearRoot("armour");
|
|
_configFileMan->clearRoot("monsters");
|
|
_configFileMan->clearRoot("game");
|
|
_gameInfo = nullptr;
|
|
|
|
#ifdef USE_IMGUI
|
|
_system->setImGuiCallbacks(ImGuiCallbacks());
|
|
#endif
|
|
|
|
debug(1, "-- Game Shutdown -- ");
|
|
}
|
|
|
|
void Ultima8Engine::pauseEngineIntern(bool pause) {
|
|
if (_mixer)
|
|
_mixer->pauseAll(pause);
|
|
if (_audioMixer) {
|
|
MidiPlayer *midiPlayer = _audioMixer->getMidiPlayer();
|
|
if (midiPlayer)
|
|
midiPlayer->pause(pause);
|
|
}
|
|
|
|
// This will normally be non-null except in the case of
|
|
// a fatal error on startup (eg missing files)
|
|
if (_avatarMoverProcess)
|
|
_avatarMoverProcess->resetMovementFlags();
|
|
}
|
|
|
|
bool Ultima8Engine::hasFeature(EngineFeature f) const {
|
|
return
|
|
(f == kSupportsSubtitleOptions) ||
|
|
(f == kSupportsReturnToLauncher) ||
|
|
(f == kSupportsLoadingDuringRuntime) ||
|
|
(f == kSupportsSavingDuringRuntime) ||
|
|
(f == kSupportsChangingOptionsDuringRuntime);
|
|
}
|
|
|
|
Common::Language Ultima8Engine::getLanguage() const {
|
|
return _gameDescription->desc.language;
|
|
}
|
|
|
|
bool Ultima8Engine::setupGame() {
|
|
GameInfo *info = new GameInfo;
|
|
info->_name = _gameDescription->desc.gameId;
|
|
info->_type = GameInfo::GAME_UNKNOWN;
|
|
info->version = 0;
|
|
info->_language = GameInfo::GAMELANG_UNKNOWN;
|
|
info->_ucOffVariant = GameInfo::GAME_UC_DEFAULT;
|
|
|
|
if (info->_name == "ultima8")
|
|
info->_type = GameInfo::GAME_U8;
|
|
else if (info->_name == "remorse")
|
|
info->_type = GameInfo::GAME_REMORSE;
|
|
else if (info->_name == "regret")
|
|
info->_type = GameInfo::GAME_REGRET;
|
|
|
|
if (info->_type == GameInfo::GAME_REMORSE) {
|
|
switch (_gameDescription->desc.flags & ADGF_USECODE_MASK) {
|
|
case ADGF_USECODE_DEMO:
|
|
info->_ucOffVariant = GameInfo::GAME_UC_DEMO;
|
|
break;
|
|
case ADGF_USECODE_ORIG:
|
|
info->_ucOffVariant = GameInfo::GAME_UC_ORIG;
|
|
break;
|
|
case ADGF_USECODE_ES:
|
|
info->_ucOffVariant = GameInfo::GAME_UC_REM_ES;
|
|
break;
|
|
case ADGF_USECODE_FR:
|
|
info->_ucOffVariant = GameInfo::GAME_UC_REM_FR;
|
|
break;
|
|
case ADGF_USECODE_JA:
|
|
info->_ucOffVariant = GameInfo::GAME_UC_REM_JA;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} else if (info->_type == GameInfo::GAME_REGRET) {
|
|
switch (_gameDescription->desc.flags & ADGF_USECODE_MASK) {
|
|
case ADGF_USECODE_DEMO:
|
|
info->_ucOffVariant = GameInfo::GAME_UC_DEMO;
|
|
break;
|
|
case ADGF_USECODE_ORIG:
|
|
info->_ucOffVariant = GameInfo::GAME_UC_ORIG;
|
|
break;
|
|
case ADGF_USECODE_DE:
|
|
info->_ucOffVariant = GameInfo::GAME_UC_REG_DE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch (_gameDescription->desc.language) {
|
|
case Common::EN_ANY:
|
|
info->_language = GameInfo::GAMELANG_ENGLISH;
|
|
break;
|
|
case Common::FR_FRA:
|
|
info->_language = GameInfo::GAMELANG_FRENCH;
|
|
break;
|
|
case Common::DE_DEU:
|
|
info->_language = GameInfo::GAMELANG_GERMAN;
|
|
break;
|
|
case Common::ES_ESP:
|
|
info->_language = GameInfo::GAMELANG_SPANISH;
|
|
break;
|
|
case Common::JA_JPN:
|
|
info->_language = GameInfo::GAMELANG_JAPANESE;
|
|
break;
|
|
default:
|
|
error("Unknown language");
|
|
break;
|
|
}
|
|
|
|
if (info->_type == GameInfo::GAME_UNKNOWN) {
|
|
warning("%s: unknown, skipping", info->_name.c_str());
|
|
delete info;
|
|
return false;
|
|
}
|
|
|
|
// output detected game info
|
|
Std::string details = info->getPrintDetails();
|
|
debug(1, "%s: %s", info->_name.c_str(), details.c_str());
|
|
|
|
_gameInfo = info;
|
|
return true;
|
|
}
|
|
|
|
Common::Error Ultima8Engine::startupGame() {
|
|
debug(1, "-- Initializing Game: %s --", _gameInfo->_name.c_str());
|
|
|
|
if (ConfMan.hasKey("usehighres")) {
|
|
_highRes = ConfMan.getBool("usehighres");
|
|
}
|
|
|
|
if (GAME_IS_U8) {
|
|
ConfMan.registerDefault("width", _highRes ? U8_HIRES_SCREEN_WIDTH : U8_DEFAULT_SCREEN_WIDTH);
|
|
ConfMan.registerDefault("height", _highRes ? U8_HIRES_SCREEN_HEIGHT : U8_DEFAULT_SCREEN_HEIGHT);
|
|
} else {
|
|
ConfMan.registerDefault("width", _highRes ? CRUSADER_HIRES_SCREEN_WIDTH : CRUSADER_DEFAULT_SCREEN_WIDTH);
|
|
ConfMan.registerDefault("height", _highRes ? CRUSADER_HIRES_SCREEN_HEIGHT : CRUSADER_DEFAULT_SCREEN_HEIGHT);
|
|
}
|
|
|
|
int width = ConfMan.getInt("width");
|
|
int height = ConfMan.getInt("height");
|
|
changeVideoMode(width, height);
|
|
|
|
// Show the splash screen immediately now that the screen has been set up
|
|
_mouse->setMouseCursor(Mouse::MOUSE_NONE);
|
|
showSplashScreen();
|
|
|
|
_gameData = new GameData(_gameInfo);
|
|
|
|
if (_gameInfo->_type == GameInfo::GAME_U8) {
|
|
_ucMachine = new UCMachine(U8Intrinsics, ARRAYSIZE(U8Intrinsics));
|
|
} else if (_gameInfo->_type == GameInfo::GAME_REMORSE) {
|
|
switch (_gameInfo->_ucOffVariant) {
|
|
case GameInfo::GAME_UC_DEMO:
|
|
_ucMachine = new UCMachine(RemorseDemoIntrinsics, ARRAYSIZE(RemorseDemoIntrinsics));
|
|
break;
|
|
case GameInfo::GAME_UC_REM_ES:
|
|
_ucMachine = new UCMachine(RemorseEsIntrinsics, ARRAYSIZE(RemorseEsIntrinsics));
|
|
break;
|
|
case GameInfo::GAME_UC_REM_FR:
|
|
_ucMachine = new UCMachine(RemorseFrIntrinsics, ARRAYSIZE(RemorseFrIntrinsics));
|
|
break;
|
|
case GameInfo::GAME_UC_REM_JA:
|
|
warning("TODO: Create Remorse JA intrinsic list");
|
|
_ucMachine = new UCMachine(RemorseIntrinsics, ARRAYSIZE(RemorseIntrinsics));
|
|
break;
|
|
case GameInfo::GAME_UC_ORIG:
|
|
warning("TODO: Create Remorse original version intrinsic list");
|
|
_ucMachine = new UCMachine(RemorseIntrinsics, ARRAYSIZE(RemorseIntrinsics));
|
|
break;
|
|
default:
|
|
_ucMachine = new UCMachine(RemorseIntrinsics, ARRAYSIZE(RemorseIntrinsics));
|
|
break;
|
|
}
|
|
} else if (_gameInfo->_type == GameInfo::GAME_REGRET) {
|
|
switch (_gameInfo->_ucOffVariant) {
|
|
case GameInfo::GAME_UC_DEMO:
|
|
_ucMachine = new UCMachine(RegretDemoIntrinsics, ARRAYSIZE(RegretDemoIntrinsics));
|
|
break;
|
|
case GameInfo::GAME_UC_REG_DE:
|
|
_ucMachine = new UCMachine(RegretDeIntrinsics, ARRAYSIZE(RegretDeIntrinsics));
|
|
break;
|
|
case GameInfo::GAME_UC_ORIG: // 1.06 is the original CD release too?
|
|
default:
|
|
_ucMachine = new UCMachine(RegretIntrinsics, ARRAYSIZE(RegretIntrinsics));
|
|
break;
|
|
}
|
|
} else {
|
|
warning("Invalid game type.");
|
|
}
|
|
|
|
_inBetweenFrame = false;
|
|
_lerpFactor = 256;
|
|
|
|
// Initialize _world
|
|
_world = new World();
|
|
_world->initMaps();
|
|
|
|
_game = Game::createGame(getGameInfo());
|
|
|
|
ConfMan.registerDefault("font_override", false);
|
|
ConfMan.registerDefault("font_antialiasing", true);
|
|
ConfMan.registerDefault("frameSkip", false);
|
|
ConfMan.registerDefault("frameLimit", true);
|
|
// Position interpolation looks nice on U8, but causes Crusader to look janky.
|
|
ConfMan.registerDefault("interpolate", _gameInfo->_type == GameInfo::GAME_U8);
|
|
ConfMan.registerDefault("cheat", false);
|
|
|
|
bool loaded = _game->loadFiles();
|
|
if (!loaded)
|
|
return Common::kNoGameDataFoundError;
|
|
|
|
applyGameSettings();
|
|
|
|
// Create Midi Driver for Ultima 8
|
|
if (getGameInfo()->_type == GameInfo::GAME_U8)
|
|
_audioMixer->openMidiOutput();
|
|
|
|
int saveSlot = ConfMan.hasKey("save_slot") ? ConfMan.getInt("save_slot") : -1;
|
|
if (saveSlot == -1 && ConfMan.hasKey("lastSave"))
|
|
saveSlot = ConfMan.getInt("lastSave");
|
|
|
|
if (!newGame(saveSlot))
|
|
return Common::kNoGameDataFoundError;
|
|
|
|
debug(1, "-- Game Initialized --");
|
|
return Common::kNoError;
|
|
}
|
|
|
|
//
|
|
// To time the frames, we use "fast" ticks which come 3000 times a second.
|
|
//
|
|
static uint32 _fastTicksNow() {
|
|
return g_system->getMillis() * 3;
|
|
}
|
|
|
|
Common::Error Ultima8Engine::runGame() {
|
|
_isRunning = true;
|
|
|
|
int32 next_ticks = _fastTicksNow(); // Next time is right now!
|
|
|
|
Common::Event event;
|
|
while (_isRunning) {
|
|
_inBetweenFrame = true; // Will get set false if it's not an _inBetweenFrame
|
|
|
|
if (!_frameLimit) {
|
|
for (unsigned int tick = 0; tick < Kernel::TICKS_PER_FRAME; tick++) {
|
|
_kernel->runProcesses();
|
|
_desktopGump->run();
|
|
}
|
|
_inBetweenFrame = false;
|
|
next_ticks = _animationRate + _fastTicksNow();
|
|
_lerpFactor = 256;
|
|
} else {
|
|
int32 ticks = _fastTicksNow();
|
|
int32 diff = next_ticks - ticks;
|
|
|
|
while (diff < 0) {
|
|
next_ticks += _animationRate;
|
|
for (unsigned int tick = 0; tick < Kernel::TICKS_PER_FRAME; tick++) {
|
|
_kernel->runProcesses();
|
|
_desktopGump->run();
|
|
}
|
|
#if 0
|
|
debug(1, "--- NEW FRAME ---");
|
|
#endif
|
|
_inBetweenFrame = false;
|
|
|
|
ticks = _fastTicksNow();
|
|
|
|
// If frame skipping is off, we will only recalc next
|
|
// ticks IF the frames are taking up 'way' too much time.
|
|
if (!_frameSkip && diff <= -_animationRate * 2) next_ticks = _animationRate + ticks;
|
|
|
|
diff = next_ticks - ticks;
|
|
if (!_frameSkip) break;
|
|
}
|
|
|
|
// Calculate the lerp_factor
|
|
_lerpFactor = ((_animationRate - diff) * 256) / _animationRate;
|
|
if (!_interpolate || _kernel->isPaused() || _lerpFactor > 256)
|
|
_lerpFactor = 256;
|
|
}
|
|
|
|
// get & handle all events in queue
|
|
while (_isRunning && pollEvent(event)) {
|
|
handleEvent(event);
|
|
}
|
|
handleDelayedEvents();
|
|
|
|
// Update the mouse
|
|
_mouse->update();
|
|
|
|
// Paint Screen
|
|
paint();
|
|
|
|
if (_lastError.getCode() != Common::kNoError) {
|
|
return _lastError;
|
|
}
|
|
|
|
// Do a delay
|
|
g_system->delayMillis(5);
|
|
}
|
|
return Common::kNoError;
|
|
}
|
|
|
|
// Paint the _screen
|
|
void Ultima8Engine::paint() {
|
|
#ifdef PAINT_TIMING
|
|
static long prev = 0;
|
|
static long t = 0;
|
|
static long tdiff = 0;
|
|
static long tpaint = 0;
|
|
long now = g_system->getMillis();
|
|
|
|
if (prev != 0)
|
|
tdiff += now - prev;
|
|
prev = now;
|
|
++t;
|
|
#endif
|
|
|
|
if (!_screen) // need to worry if the graphics system has been started. Need nicer way.
|
|
return;
|
|
|
|
// Begin _painting
|
|
_screen->BeginPainting();
|
|
|
|
#ifdef PAINT_TIMING
|
|
tpaint -= g_system->getMillis();
|
|
#endif
|
|
|
|
Common::Rect32 r = _screen->getSurfaceDims();
|
|
if (_highRes)
|
|
_screen->fill32(TEX32_PACK_RGB(0, 0, 0), r);
|
|
|
|
#ifdef DEBUG
|
|
// Fill the screen with an annoying color so we can see fast area bugs
|
|
_screen->fill32(TEX32_PACK_RGB(0x10, 0xFF, 0x10), r);
|
|
#endif
|
|
|
|
_desktopGump->Paint(_screen, _lerpFactor, false);
|
|
#ifdef PAINT_TIMING
|
|
tpaint += g_system->getMillis();
|
|
|
|
if (t % 150 == 0) { // every ~5 seconds
|
|
debug("Ultima8Engine: Paint average %.03f millis", (float)tpaint / t);
|
|
}
|
|
#endif
|
|
|
|
// End _painting
|
|
_screen->EndPainting();
|
|
|
|
Graphics::Screen *screen = getScreen();
|
|
if (screen)
|
|
screen->update();
|
|
}
|
|
|
|
void Ultima8Engine::changeVideoMode(int width, int height) {
|
|
if (_screen) {
|
|
Common::Rect32 old_dims = _screen->getSurfaceDims();
|
|
if (width == old_dims.width() && height == old_dims.height())
|
|
return;
|
|
|
|
delete _screen;
|
|
}
|
|
|
|
// Set Screen Resolution
|
|
debug(1, "Setting Video Mode %dx%d...", width, height);
|
|
|
|
Common::List<Graphics::PixelFormat> tryModes = g_system->getSupportedFormats();
|
|
for (Common::List<Graphics::PixelFormat>::iterator g = tryModes.begin(); g != tryModes.end(); ++g) {
|
|
if (g->bytesPerPixel != 2 && g->bytesPerPixel != 4) {
|
|
g = tryModes.reverse_erase(g);
|
|
}
|
|
}
|
|
|
|
initGraphics(width, height, tryModes);
|
|
|
|
Graphics::PixelFormat format = g_system->getScreenFormat();
|
|
if (format.bytesPerPixel != 2 && format.bytesPerPixel != 4) {
|
|
error("Only 16 bit and 32 bit video modes supported");
|
|
}
|
|
|
|
// Set up blitting surface
|
|
Graphics::ManagedSurface *surface = new Graphics::Screen(width, height, format);
|
|
_screen = new RenderSurface(surface);
|
|
|
|
if (!_paletteManager) {
|
|
_paletteManager = new PaletteManager(format);
|
|
} else {
|
|
_paletteManager->PixelFormatChanged(format);
|
|
}
|
|
|
|
if (!_desktopGump) {
|
|
_desktopGump = new DesktopGump(0, 0, width, height);
|
|
_desktopGump->InitGump(0);
|
|
_desktopGump->MakeFocus();
|
|
} else {
|
|
_desktopGump->setDims(Common::Rect32(0, 0, width, height));
|
|
_desktopGump->RenderSurfaceChanged();
|
|
}
|
|
|
|
paint();
|
|
}
|
|
|
|
void Ultima8Engine::handleEvent(const Common::Event &event) {
|
|
// Handle the fact that we can get 2 modals stacking.
|
|
// We want the focussed one preferably.
|
|
Gump *modal = dynamic_cast<ModalGump *>(_desktopGump->GetFocusChild());
|
|
if (!modal)
|
|
modal = _desktopGump->FindGump<ModalGump>();
|
|
if (modal) {
|
|
_avatarMoverProcess->resetMovementFlags();
|
|
}
|
|
|
|
Common::Keymapper *const keymapper = _eventMan->getKeymapper();
|
|
keymapper->setEnabledKeymapType(modal ? Common::Keymap::kKeymapTypeGui : Common::Keymap::kKeymapTypeGame);
|
|
|
|
switch (event.type) {
|
|
case Common::EVENT_KEYDOWN:
|
|
if (modal) {
|
|
// Paste from Clip-Board on Ctrl-V - Note this should be a flag of some sort
|
|
if (event.kbd.keycode == Common::KEYCODE_v && (event.kbd.flags & Common::KBD_CTRL)) {
|
|
if (!g_system->hasTextInClipboard())
|
|
return;
|
|
|
|
Common::String text = g_system->getTextFromClipboard();
|
|
|
|
// Only read the first line of text
|
|
while (!text.empty() && text.firstChar() >= ' ')
|
|
modal->OnTextInput(text.firstChar());
|
|
|
|
return;
|
|
}
|
|
|
|
if (event.kbd.ascii >= ' ' &&
|
|
event.kbd.ascii <= 255 &&
|
|
!(event.kbd.ascii >= 0x7F && // control chars
|
|
event.kbd.ascii <= 0x9F)) {
|
|
modal->OnTextInput(event.kbd.ascii);
|
|
}
|
|
|
|
modal->OnKeyDown(event.kbd.keycode, event.kbd.flags);
|
|
}
|
|
break;
|
|
case Common::EVENT_KEYUP:
|
|
if (modal) {
|
|
modal->OnKeyUp(event.kbd.keycode);
|
|
}
|
|
break;
|
|
|
|
case Common::EVENT_MOUSEMOVE:
|
|
_mouse->setMouseCoords(event.mouse.x, event.mouse.y);
|
|
break;
|
|
|
|
case Common::EVENT_LBUTTONDOWN:
|
|
case Common::EVENT_MBUTTONDOWN:
|
|
case Common::EVENT_RBUTTONDOWN: {
|
|
Mouse::MouseButton button = Mouse::BUTTON_LEFT;
|
|
if (event.type == Common::EVENT_RBUTTONDOWN)
|
|
button = Mouse::BUTTON_RIGHT;
|
|
else if (event.type == Common::EVENT_MBUTTONDOWN)
|
|
button = Mouse::BUTTON_MIDDLE;
|
|
|
|
_mouse->setMouseCoords(event.mouse.x, event.mouse.y);
|
|
_mouse->buttonDown(button);
|
|
break;
|
|
}
|
|
|
|
case Common::EVENT_LBUTTONUP:
|
|
case Common::EVENT_MBUTTONUP:
|
|
case Common::EVENT_RBUTTONUP: {
|
|
Mouse::MouseButton button = Mouse::BUTTON_LEFT;
|
|
if (event.type == Common::EVENT_RBUTTONUP)
|
|
button = Mouse::BUTTON_RIGHT;
|
|
else if (event.type == Common::EVENT_MBUTTONUP)
|
|
button = Mouse::BUTTON_MIDDLE;
|
|
|
|
_mouse->setMouseCoords(event.mouse.x, event.mouse.y);
|
|
_mouse->buttonUp(button);
|
|
break;
|
|
}
|
|
|
|
case Common::EVENT_CUSTOM_ENGINE_ACTION_START:
|
|
handleActionDown((KeybindingAction)event.customType);
|
|
break;
|
|
|
|
case Common::EVENT_CUSTOM_ENGINE_ACTION_END:
|
|
handleActionUp((KeybindingAction)event.customType);
|
|
break;
|
|
|
|
case Common::EVENT_QUIT:
|
|
case Common::EVENT_RETURN_TO_LAUNCHER:
|
|
_isRunning = false;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Ultima8Engine::handleDelayedEvents() {
|
|
//uint32 now = g_system->getMillis();
|
|
|
|
_mouse->handleDelayedEvents();
|
|
}
|
|
|
|
void Ultima8Engine::handleActionDown(KeybindingAction action) {
|
|
if (!isAvatarInStasis() && QuickAvatarMoverProcess::isEnabled()) {
|
|
QuickAvatarMoverProcess *moverProcess = QuickAvatarMoverProcess::get_instance();
|
|
if (moverProcess && moverProcess->onActionDown(action)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!isAvatarInStasis()) {
|
|
if (_avatarMoverProcess && _avatarMoverProcess->onActionDown(action)) {
|
|
moveKeyEvent();
|
|
return;
|
|
}
|
|
}
|
|
|
|
switch (action) {
|
|
case ACTION_QUICKSAVE:
|
|
if (canSaveGameStateCurrently()) {
|
|
Common::Error result = saveGameState(1, "QuickSave");
|
|
if (result.getCode() != Common::kNoError) {
|
|
GUIErrorMessageFormat("Saving game failed: %s\n", result.getDesc().c_str());
|
|
}
|
|
} else {
|
|
Mouse::get_instance()->flashCrossCursor();
|
|
}
|
|
break;
|
|
case ACTION_SAVE:
|
|
saveGameDialog();
|
|
break;
|
|
case ACTION_LOAD:
|
|
loadGameDialog();
|
|
break;
|
|
case ACTION_BEDROLL:
|
|
if (!isAvatarInStasis()) {
|
|
MainActor *av = getMainActor();
|
|
av->useInventoryItem(534);
|
|
}
|
|
break;
|
|
case ACTION_COMBAT:
|
|
if (!isAvatarInStasis()) {
|
|
MainActor *av = getMainActor();
|
|
av->toggleInCombat();
|
|
}
|
|
break;
|
|
case ACTION_BACKPACK:
|
|
if (!isAvatarInStasis()) {
|
|
MainActor *av = getMainActor();
|
|
Item *backpack = getItem(av->getEquip(ShapeInfo::SE_BACKPACK));
|
|
if (backpack)
|
|
backpack->callUsecodeEvent_use();
|
|
}
|
|
break;
|
|
case ACTION_KEYRING:
|
|
if (!isAvatarInStasis()) {
|
|
MainActor *av = getMainActor();
|
|
av->useInventoryItem(79);
|
|
}
|
|
break;
|
|
case ACTION_MINIMAP: {
|
|
Gump *desktop = getDesktopGump();
|
|
Gump *mmg = desktop->FindGump<MiniMapGump>();
|
|
if (!mmg) {
|
|
mmg = new MiniMapGump(4, 4);
|
|
mmg->InitGump(0);
|
|
mmg->setRelativePosition(Gump::TOP_LEFT, 4, 4);
|
|
} else if (mmg->IsHidden()) {
|
|
mmg->UnhideGump();
|
|
} else {
|
|
mmg->HideGump();
|
|
}
|
|
} break;
|
|
case ACTION_RECALL:
|
|
if (!isAvatarInStasis()) {
|
|
MainActor *av = getMainActor();
|
|
av->useInventoryItem(833);
|
|
}
|
|
break;
|
|
case ACTION_INVENTORY:
|
|
if (!isAvatarInStasis()) {
|
|
MainActor *av = getMainActor();
|
|
av->callUsecodeEvent_use();
|
|
}
|
|
break;
|
|
case ACTION_NEXT_WEAPON:
|
|
if (!isAvatarInStasis() && isAvatarControlled()) {
|
|
MainActor *av = getMainActor();
|
|
av->nextWeapon();
|
|
}
|
|
break;
|
|
case ACTION_NEXT_INVENTORY:
|
|
if (!isAvatarInStasis() && isAvatarControlled()) {
|
|
MainActor *av = getMainActor();
|
|
av->nextInvItem();
|
|
}
|
|
break;
|
|
case ACTION_USE_INVENTORY:
|
|
if (!isAvatarInStasis() && isAvatarControlled()) {
|
|
MainActor *av = getMainActor();
|
|
ObjId activeitemid = av->getActiveInvItem();
|
|
if (activeitemid) {
|
|
Item *item = getItem(activeitemid);
|
|
if (item) {
|
|
av->useInventoryItem(item);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case ACTION_USE_MEDIKIT:
|
|
if (!isAvatarInStasis() && isAvatarControlled()) {
|
|
MainActor *av = getMainActor();
|
|
av->useInventoryItem(0x351);
|
|
}
|
|
break;
|
|
case ACTION_USE_ENERGYCUBE:
|
|
if (!isAvatarInStasis() && isAvatarControlled()) {
|
|
MainActor *av = getMainActor();
|
|
av->useInventoryItem(0x582);
|
|
}
|
|
break;
|
|
case ACTION_SELECT_ITEMS:
|
|
if (!isAvatarInStasis() && isAvatarControlled()) {
|
|
// Clear this flag on selection to match original behavior.
|
|
setCrusaderTeleporting(false);
|
|
|
|
ItemSelectionProcess *proc = ItemSelectionProcess::get_instance();
|
|
if (proc)
|
|
proc->selectNextItem(false);
|
|
}
|
|
break;
|
|
case ACTION_DETONATE_BOMB:
|
|
if (!isAvatarInStasis() && isAvatarControlled()) {
|
|
MainActor *av = getMainActor();
|
|
av->detonateBomb();
|
|
}
|
|
break;
|
|
case ACTION_DROP_WEAPON:
|
|
if (!isAvatarInStasis() && isAvatarControlled()) {
|
|
MainActor *av = getMainActor();
|
|
av->dropWeapon();
|
|
}
|
|
break;
|
|
case ACTION_USE_SELECTION:
|
|
if (!isAvatarInStasis() && isAvatarControlled()) {
|
|
ItemSelectionProcess *proc = ItemSelectionProcess::get_instance();
|
|
if (proc)
|
|
proc->useSelectedItem();
|
|
}
|
|
break;
|
|
case ACTION_GRAB_ITEMS:
|
|
if (!isAvatarInStasis() && isAvatarControlled()) {
|
|
// Clear this flag on selection to match original behavior.
|
|
setCrusaderTeleporting(false);
|
|
|
|
ItemSelectionProcess *proc = ItemSelectionProcess::get_instance();
|
|
if (proc)
|
|
proc->selectNextItem(true);
|
|
}
|
|
break;
|
|
case ACTION_MENU:
|
|
// In Crusader escape is also used to stop controlling another NPC
|
|
if (_world && _world->getControlledNPCNum() != kMainActorId) {
|
|
_world->setControlledNPCNum(kMainActorId);
|
|
} else if (isCruStasis()) {
|
|
moveKeyEvent();
|
|
} else {
|
|
Gump *gump = getDesktopGump()->FindGump<ModalGump>();
|
|
if (gump) {
|
|
// ensure any modal gump gets the message to close before we open the menu.
|
|
gump->Close();
|
|
} else {
|
|
MenuGump::showMenu();
|
|
}
|
|
}
|
|
break;
|
|
case ACTION_CLOSE_GUMPS:
|
|
getDesktopGump()->CloseItemDependents();
|
|
break;
|
|
case ACTION_CAMERA_AVATAR:
|
|
if (!isCruStasis()) {
|
|
Actor *actor = getControlledActor();
|
|
if (actor) {
|
|
Point3 pt = actor->getCentre();
|
|
if (pt.x > 0 || pt.y > 0)
|
|
CameraProcess::SetCameraProcess(new CameraProcess(pt));
|
|
}
|
|
}
|
|
break;
|
|
case ACTION_HIGHLIGHT_ITEMS:
|
|
GameMapGump::Set_highlightItems(true);
|
|
break;
|
|
case ACTION_DEC_SORT_ORDER:
|
|
if (_gameMapGump)
|
|
_gameMapGump->IncSortOrder(-1);
|
|
break;
|
|
case ACTION_INC_SORT_ORDER:
|
|
if (_gameMapGump)
|
|
_gameMapGump->IncSortOrder(1);
|
|
break;
|
|
case ACTION_FRAME_BY_FRAME:
|
|
if (_kernel) {
|
|
bool fbf = !_kernel->isFrameByFrame();
|
|
_kernel->setFrameByFrame(fbf);
|
|
if (fbf)
|
|
_kernel->pause();
|
|
else
|
|
_kernel->unpause();
|
|
}
|
|
break;
|
|
case ACTION_ADVANCE_FRAME:
|
|
if (_kernel) {
|
|
if (_kernel->isFrameByFrame())
|
|
_kernel->unpause();
|
|
}
|
|
break;
|
|
case ACTION_SHAPE_VIEWER:
|
|
ShapeViewerGump::U8ShapeViewer();
|
|
break;
|
|
case ACTION_TOGGLE_TOUCHING:
|
|
_showTouching = !_showTouching;
|
|
break;
|
|
case ACTION_TOGGLE_PAINT:
|
|
_showEditorItems = !_showEditorItems;
|
|
break;
|
|
case ACTION_TOGGLE_STASIS:
|
|
_avatarInStasis = !_avatarInStasis;
|
|
break;
|
|
case ACTION_CLIPPING:
|
|
if (areCheatsEnabled()) {
|
|
QuickAvatarMoverProcess::toggleClipping();
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Ultima8Engine::handleActionUp(KeybindingAction action) {
|
|
if (QuickAvatarMoverProcess::isEnabled()) {
|
|
QuickAvatarMoverProcess *moverProcess = QuickAvatarMoverProcess::get_instance();
|
|
if (moverProcess && moverProcess->onActionUp(action)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (_avatarMoverProcess && _avatarMoverProcess->onActionUp(action)) {
|
|
moveKeyEvent();
|
|
return;
|
|
}
|
|
|
|
switch (action) {
|
|
case ACTION_HIGHLIGHT_ITEMS:
|
|
GameMapGump::Set_highlightItems(false);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Ultima8Engine::writeSaveInfo(Common::WriteStream *ws) {
|
|
TimeDate timeInfo;
|
|
g_system->getTimeAndDate(timeInfo);
|
|
|
|
ws->writeUint16LE(static_cast<uint16>(timeInfo.tm_year + 1900));
|
|
ws->writeByte(static_cast<uint8>(timeInfo.tm_mon + 1));
|
|
ws->writeByte(static_cast<uint8>(timeInfo.tm_mday));
|
|
ws->writeByte(static_cast<uint8>(timeInfo.tm_hour));
|
|
ws->writeByte(static_cast<uint8>(timeInfo.tm_min));
|
|
ws->writeByte(static_cast<uint8>(timeInfo.tm_sec));
|
|
ws->writeUint32LE(_saveCount);
|
|
ws->writeUint32LE(getGameTimeInSeconds());
|
|
|
|
uint8 c = (_hasCheated ? 1 : 0);
|
|
ws->writeByte(c);
|
|
|
|
// write _game-specific info
|
|
_game->writeSaveInfo(ws);
|
|
}
|
|
|
|
bool Ultima8Engine::canSaveGameStateCurrently(Common::U32String *msg) {
|
|
// Can't save when avatar in stasis during cutscenes
|
|
if (_avatarInStasis || _cruStasis)
|
|
return false;
|
|
|
|
// Check for gumps that prevent saving
|
|
if (_desktopGump->FindGump(&HasPreventSaveFlag, true))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Check for processes that prevent saving
|
|
if (!_kernel->canSave()) {
|
|
return false;
|
|
}
|
|
|
|
// Don't allow saving when avatar is dead.
|
|
MainActor *av = getMainActor();
|
|
if (!av || av->hasActorFlags(Actor::ACT_DEAD))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
Common::Error Ultima8Engine::loadGameState(int slot) {
|
|
Common::Error result = Engine::loadGameState(slot);
|
|
if (result.getCode() == Common::kNoError)
|
|
ConfMan.setInt("lastSave", slot);
|
|
else
|
|
ConfMan.setInt("lastSave", -1);
|
|
|
|
ConfMan.flushToDisk();
|
|
|
|
return result;
|
|
}
|
|
|
|
Common::Error Ultima8Engine::saveGameState(int slot, const Common::String &desc, bool isAutosave) {
|
|
Common::Error result = Engine::saveGameState(slot, desc, isAutosave);
|
|
|
|
if (!isAutosave) {
|
|
if (result.getCode() == Common::kNoError)
|
|
ConfMan.setInt("lastSave", slot);
|
|
else
|
|
ConfMan.setInt("lastSave", -1);
|
|
}
|
|
|
|
ConfMan.flushToDisk();
|
|
|
|
return result;
|
|
}
|
|
|
|
Common::Error Ultima8Engine::saveGameStream(Common::WriteStream *stream, bool isAutosave) {
|
|
// Hack - don't save mouse over status for gumps
|
|
Gump *gump = _mouse->getMouseOverGump();
|
|
if (gump)
|
|
gump->onMouseLeft();
|
|
|
|
Gump *modalGump = _desktopGump->FindGump<ModalGump>();
|
|
if (modalGump)
|
|
modalGump->HideGump();
|
|
|
|
_mouse->pushMouseCursor(Mouse::MOUSE_WAIT);
|
|
|
|
// Redraw to indicate busy and for save thumbnail
|
|
paint();
|
|
|
|
if (modalGump)
|
|
modalGump->UnhideGump();
|
|
|
|
_saveCount++;
|
|
|
|
SavegameWriter *sgw = new SavegameWriter(stream);
|
|
Common::MemoryWriteStreamDynamic buf(DisposeAfterUse::YES);
|
|
|
|
_gameInfo->save(&buf);
|
|
sgw->writeFile("GAME", &buf);
|
|
buf.seek(0);
|
|
|
|
writeSaveInfo(&buf);
|
|
sgw->writeFile("INFO", &buf);
|
|
buf.seek(0);
|
|
|
|
_kernel->save(&buf);
|
|
sgw->writeFile("KERNEL", &buf);
|
|
buf.seek(0);
|
|
|
|
_objectManager->save(&buf);
|
|
sgw->writeFile("OBJECTS", &buf);
|
|
buf.seek(0);
|
|
|
|
_world->save(&buf);
|
|
sgw->writeFile("WORLD", &buf);
|
|
buf.seek(0);
|
|
|
|
_world->saveMaps(&buf);
|
|
sgw->writeFile("MAPS", &buf);
|
|
buf.seek(0);
|
|
|
|
_world->getCurrentMap()->save(&buf);
|
|
sgw->writeFile("CURRENTMAP", &buf);
|
|
buf.seek(0);
|
|
|
|
_ucMachine->saveStrings(&buf);
|
|
sgw->writeFile("UCSTRINGS", &buf);
|
|
buf.seek(0);
|
|
|
|
_ucMachine->saveGlobals(&buf);
|
|
sgw->writeFile("UCGLOBALS", &buf);
|
|
buf.seek(0);
|
|
|
|
_ucMachine->saveLists(&buf);
|
|
sgw->writeFile("UCLISTS", &buf);
|
|
buf.seek(0);
|
|
|
|
save(&buf);
|
|
sgw->writeFile("APP", &buf);
|
|
buf.seek(0);
|
|
|
|
sgw->finish();
|
|
|
|
delete sgw;
|
|
|
|
// Restore mouse over
|
|
if (gump) gump->onMouseOver();
|
|
|
|
debug(1, "Done");
|
|
|
|
_mouse->popMouseCursor();
|
|
|
|
return Common::kNoError;
|
|
}
|
|
|
|
void Ultima8Engine::resetEngine() {
|
|
debug(1, "-- Resetting Engine --");
|
|
|
|
// kill music
|
|
if (_audioMixer) _audioMixer->reset();
|
|
|
|
// now, reset everything (order matters)
|
|
_world->reset();
|
|
_ucMachine->reset();
|
|
|
|
// This process will be cleared by kernel reset.
|
|
_avatarMoverProcess = nullptr;
|
|
|
|
// ObjectManager, Kernel have to be last, because they kill
|
|
// all processes/objects
|
|
_objectManager->reset();
|
|
_kernel->reset();
|
|
_paletteManager->resetTransforms();
|
|
|
|
// Reset thet gumps
|
|
_desktopGump = nullptr;
|
|
_gameMapGump = nullptr;
|
|
_inverterGump = nullptr;
|
|
|
|
// reset mouse cursor
|
|
_mouse->popAllCursors();
|
|
_mouse->pushMouseCursor(Mouse::MOUSE_NORMAL);
|
|
|
|
_timeOffset = -(int32)Kernel::get_instance()->getFrameNum();
|
|
_inversion = 0;
|
|
_saveCount = 0;
|
|
_hasCheated = false;
|
|
|
|
debug(1, "-- Engine Reset --");
|
|
}
|
|
|
|
void Ultima8Engine::setupCoreGumps() {
|
|
debug(1, "Setting up core game gumps...");
|
|
|
|
Common::Rect32 dims = _screen->getSurfaceDims();
|
|
|
|
debug(1, "Creating Desktop...");
|
|
_desktopGump = new DesktopGump(0, 0, dims.width(), dims.height());
|
|
_desktopGump->InitGump(0);
|
|
_desktopGump->MakeFocus();
|
|
|
|
ConfMan.registerDefault("fadedModal", true);
|
|
bool faded_modal = ConfMan.getBool("fadedModal");
|
|
DesktopGump::SetFadedModal(faded_modal);
|
|
|
|
if (GAME_IS_U8) {
|
|
debug(1, "Creating Inverter...");
|
|
_inverterGump = new InverterGump(0, 0, dims.width(), dims.height());
|
|
_inverterGump->InitGump(0);
|
|
}
|
|
debug(1, "Creating GameMapGump...");
|
|
_gameMapGump = new GameMapGump(0, 0, dims.width(), dims.height());
|
|
_gameMapGump->InitGump(0);
|
|
|
|
// TODO: clean this up
|
|
if (GAME_IS_U8) {
|
|
assert(_desktopGump->getObjId() == 256);
|
|
assert(_inverterGump->getObjId() == 257);
|
|
assert(_gameMapGump->getObjId() == 258);
|
|
}
|
|
|
|
for (uint16 i = 259; i < 384; ++i)
|
|
_objectManager->reserveObjId(i);
|
|
}
|
|
|
|
bool Ultima8Engine::newGame(int saveSlot) {
|
|
debug(1, "Starting New Game (slot %d)... ", saveSlot);
|
|
|
|
resetEngine();
|
|
|
|
setupCoreGumps();
|
|
|
|
if (!_game->startGame())
|
|
return false;
|
|
|
|
debug(1, "Create Camera...");
|
|
CameraProcess::SetCameraProcess(new CameraProcess(kMainActorId));
|
|
|
|
debug(1, "Create persistent Processes...");
|
|
if (GAME_IS_U8)
|
|
_avatarMoverProcess = new U8AvatarMoverProcess();
|
|
else
|
|
_avatarMoverProcess = new CruAvatarMoverProcess();
|
|
_kernel->addProcess(_avatarMoverProcess);
|
|
|
|
if (GAME_IS_U8)
|
|
_kernel->addProcess(new HealProcess());
|
|
|
|
_kernel->addProcess(new SchedulerProcess());
|
|
|
|
if (_audioMixer) _audioMixer->createProcesses();
|
|
|
|
// av->teleport(40, 16240, 15240, 64); // central Tenebrae
|
|
// av->teleport(3, 11391, 1727, 64); // docks, near gate
|
|
// av->teleport(39, 16240, 15240, 64); // West Tenebrae
|
|
// av->teleport(41, 12000, 15000, 64); // East Tenebrae
|
|
// av->teleport(8, 14462, 15178, 48); // before entrance to Mythran's house
|
|
// av->teleport(40, 13102,9474,48); // entrance to Mordea's throne room
|
|
// av->teleport(54, 14783,5959,8); // shrine of the Ancient Ones; Hanoi
|
|
// av->teleport(5, 5104,22464,48); // East road (tenebrae end)
|
|
|
|
if (GAME_IS_CRUSADER) {
|
|
_kernel->addProcess(new TargetReticleProcess());
|
|
_kernel->addProcess(new ItemSelectionProcess());
|
|
_kernel->addProcess(new CrosshairProcess());
|
|
_kernel->addProcess(new CycleProcess());
|
|
_kernel->addProcess(new SnapProcess());
|
|
}
|
|
|
|
_game->startInitialUsecode(saveSlot);
|
|
|
|
if (saveSlot == -1)
|
|
ConfMan.setInt("lastSave", -1);
|
|
|
|
return true;
|
|
}
|
|
|
|
void Ultima8Engine::syncSoundSettings() {
|
|
Engine::syncSoundSettings();
|
|
|
|
// Update music volume
|
|
AudioMixer *audioMixer = AudioMixer::get_instance();
|
|
MidiPlayer *midiPlayer = audioMixer ? audioMixer->getMidiPlayer() : nullptr;
|
|
if (midiPlayer)
|
|
midiPlayer->syncSoundSettings();
|
|
}
|
|
|
|
void Ultima8Engine::applyGameSettings() {
|
|
Engine::applyGameSettings();
|
|
|
|
bool fontOverride = ConfMan.getBool("font_override");
|
|
bool fontAntialiasing = ConfMan.getBool("font_antialiasing");
|
|
|
|
if (_fontOverride != fontOverride || _fontAntialiasing != fontAntialiasing) {
|
|
_fontOverride = fontOverride;
|
|
_fontAntialiasing = fontAntialiasing;
|
|
|
|
_fontManager->resetGameFonts();
|
|
|
|
// TODO: assign names to these fontnumbers somehow
|
|
_fontManager->loadTTFont(0, "Vera.ttf", 18, 0xFFFFFF, 0);
|
|
_fontManager->loadTTFont(1, "VeraBd.ttf", 12, 0xFFFFFF, 0);
|
|
// GameWidget's version number information:
|
|
_fontManager->loadTTFont(2, "Vera.ttf", 8, 0xA0A0A0, 0);
|
|
}
|
|
|
|
_gameData->setupFontOverrides();
|
|
|
|
_frameSkip = ConfMan.getBool("frameSkip");
|
|
_frameLimit = ConfMan.getBool("frameLimit");
|
|
_interpolate = ConfMan.getBool("interpolate");
|
|
_cheatsEnabled = ConfMan.getBool("cheat");
|
|
}
|
|
|
|
void Ultima8Engine::openConfigDialog() {
|
|
GUI::ConfigDialog dlg;
|
|
dlg.runModal();
|
|
|
|
g_system->applyBackendSettings();
|
|
applyGameSettings();
|
|
syncSoundSettings();
|
|
}
|
|
|
|
Common::Error Ultima8Engine::loadGameStream(Common::SeekableReadStream *stream) {
|
|
SavegameReader *sg = new SavegameReader(stream);
|
|
SavegameReader::State state = sg->isValid();
|
|
if (state == SavegameReader::SAVE_CORRUPT) {
|
|
delete sg;
|
|
return Common::Error(Common::kReadingFailed, "Invalid or corrupt savegame");
|
|
}
|
|
|
|
if (state != SavegameReader::SAVE_VALID) {
|
|
delete sg;
|
|
return Common::Error(Common::kReadingFailed, "Unsupported savegame version");
|
|
}
|
|
|
|
_mouse->pushMouseCursor(Mouse::MOUSE_WAIT);
|
|
|
|
// Redraw to indicate busy
|
|
paint();
|
|
|
|
Common::SeekableReadStream *ds;
|
|
GameInfo saveinfo;
|
|
ds = sg->getDataSource("GAME");
|
|
uint32 version = sg->getVersion();
|
|
bool ok = saveinfo.load(ds, version);
|
|
|
|
if (!ok) {
|
|
delete sg;
|
|
return Common::Error(Common::kReadingFailed, "Invalid or corrupt savegame: missing GameInfo");
|
|
}
|
|
|
|
if (!_gameInfo->match(saveinfo, true)) {
|
|
Std::string message = "Game mismatch\n";
|
|
message += "Running _game: " + _gameInfo->getPrintDetails() + "\n";
|
|
message += "Savegame : " + saveinfo.getPrintDetails();
|
|
|
|
#ifdef DEBUG
|
|
ConfMan.registerDefault("ignore_savegame_mismatch", true);
|
|
bool ignore = ConfMan.getBool("ignore_savegame_mismatch");
|
|
|
|
if (!ignore) {
|
|
error("%s", message.c_str());
|
|
}
|
|
debug(1, "%s", message.c_str());
|
|
#else
|
|
delete sg;
|
|
return Common::Error(Common::kReadingFailed, message);
|
|
#endif
|
|
}
|
|
|
|
resetEngine();
|
|
|
|
setupCoreGumps();
|
|
|
|
// and load everything back (order matters)
|
|
// for each entry, check that we read exactly the number of bytes
|
|
// expected - anything else suggests a corrupt save (or a bug)
|
|
bool totalok = true;
|
|
|
|
Std::string message;
|
|
|
|
// UCSTRINGS, UCGLOBALS, UCLISTS don't depend on anything else,
|
|
// so load these first
|
|
ds = sg->getDataSource("UCSTRINGS");
|
|
ok = _ucMachine->loadStrings(ds, version);
|
|
ok &= (ds->pos() == ds->size() && !ds->eos());
|
|
totalok &= ok;
|
|
debug(1, "UCSTRINGS: %s", (ok ? "ok" : "failed"));
|
|
if (!ok) message += "UCSTRINGS: failed\n";
|
|
delete ds;
|
|
|
|
ds = sg->getDataSource("UCGLOBALS");
|
|
ok = _ucMachine->loadGlobals(ds, version);
|
|
ok &= (ds->pos() == ds->size() && !ds->eos());
|
|
totalok &= ok;
|
|
debug(1, "UCGLOBALS: %s", (ok ? "ok" : "failed"));
|
|
if (!ok) message += "UCGLOBALS: failed\n";
|
|
delete ds;
|
|
|
|
ds = sg->getDataSource("UCLISTS");
|
|
ok = _ucMachine->loadLists(ds, version);
|
|
ok &= (ds->pos() == ds->size() && !ds->eos());
|
|
totalok &= ok;
|
|
debug(1, "UCLISTS: %s", (ok ? "ok" : "failed"));
|
|
if (!ok) message += "UCLISTS: failed\n";
|
|
delete ds;
|
|
|
|
// KERNEL must be before OBJECTS, for the egghatcher
|
|
// KERNEL must be before APP, for the _avatarMoverProcess
|
|
ds = sg->getDataSource("KERNEL");
|
|
ok = _kernel->load(ds, version);
|
|
ok &= (ds->pos() == ds->size() && !ds->eos());
|
|
totalok &= ok;
|
|
debug(1, "KERNEL: %s", (ok ? "ok" : "failed"));
|
|
if (!ok) message += "KERNEL: failed\n";
|
|
delete ds;
|
|
|
|
ds = sg->getDataSource("APP");
|
|
ok = load(ds, version);
|
|
ok &= (ds->pos() == ds->size() && !ds->eos());
|
|
totalok &= ok;
|
|
debug(1, "APP: %s", (ok ? "ok" : "failed"));
|
|
if (!ok) message += "APP: failed\n";
|
|
delete ds;
|
|
|
|
// WORLD must be before OBJECTS, for the egghatcher
|
|
ds = sg->getDataSource("WORLD");
|
|
ok = _world->load(ds, version);
|
|
ok &= (ds->pos() == ds->size() && !ds->eos());
|
|
totalok &= ok;
|
|
debug(1, "WORLD: %s", (ok ? "ok" : "failed"));
|
|
if (!ok) message += "WORLD: failed\n";
|
|
delete ds;
|
|
|
|
ds = sg->getDataSource("CURRENTMAP");
|
|
ok = _world->getCurrentMap()->load(ds, version);
|
|
ok &= (ds->pos() == ds->size() && !ds->eos());
|
|
totalok &= ok;
|
|
debug(1, "CURRENTMAP: %s", (ok ? "ok" : "failed"));
|
|
if (!ok) message += "CURRENTMAP: failed\n";
|
|
delete ds;
|
|
|
|
ds = sg->getDataSource("OBJECTS");
|
|
ok = _objectManager->load(ds, version);
|
|
ok &= (ds->pos() == ds->size() && !ds->eos());
|
|
totalok &= ok;
|
|
debug(1, "OBJECTS: %s", (ok ? "ok" : "failed"));
|
|
if (!ok) message += "OBJECTS: failed\n";
|
|
delete ds;
|
|
|
|
ds = sg->getDataSource("MAPS");
|
|
ok = _world->loadMaps(ds, version);
|
|
ok &= (ds->pos() == ds->size() && !ds->eos());
|
|
totalok &= ok;
|
|
debug(1, "MAPS: %s", (ok ? "ok" : "failed"));
|
|
if (!ok) message += "MAPS: failed\n";
|
|
delete ds;
|
|
|
|
// Reset mouse cursor
|
|
_mouse->popAllCursors();
|
|
_mouse->pushMouseCursor(Mouse::MOUSE_NORMAL);
|
|
|
|
/*
|
|
// In case of bugs, ensure persistent processes are around?
|
|
if (!TargetReticleProcess::get_instance())
|
|
_kernel->addProcess(new TargetReticleProcess());
|
|
if (!ItemSelectionProcess::get_instance())
|
|
_kernel->addProcess(new ItemSelectionProcess());
|
|
if (!CrosshairProcess::get_instance())
|
|
_kernel->addProcess(new CrosshairProcess());
|
|
if (!CycleProcess::get_instance())
|
|
_kernel->addProcess(new CycleProcess());
|
|
if (!SnapProcess::get_instance())
|
|
_kernel->addProcess(new SnapProcess());
|
|
*/
|
|
|
|
if (!totalok) {
|
|
delete sg;
|
|
return Common::Error(Common::kReadingFailed, message);
|
|
}
|
|
|
|
debug(1, "Done");
|
|
|
|
delete sg;
|
|
return Common::kNoError;
|
|
}
|
|
|
|
void Ultima8Engine::setError(Common::Error &error) {
|
|
_lastError = error;
|
|
}
|
|
|
|
Gump *Ultima8Engine::getGump(uint16 gumpid) {
|
|
return dynamic_cast<Gump *>(ObjectManager::get_instance()->
|
|
getObject(gumpid));
|
|
}
|
|
|
|
void Ultima8Engine::addGump(Gump *gump) {
|
|
// TODO: At some point, this will have to _properly_ choose to
|
|
// which 'layer' to add the gump: inverted, scaled or neither.
|
|
|
|
assert(_desktopGump);
|
|
|
|
if (dynamic_cast<ShapeViewerGump *>(gump) || dynamic_cast<MiniMapGump *>(gump) ||
|
|
dynamic_cast<MessageBoxGump *>(gump)// ||
|
|
//(_fontOverrides && (dynamic_cast<BarkGump *>(gump) ||
|
|
// dynamic_cast<AskGump *>(gump)))
|
|
) {
|
|
_desktopGump->AddChild(gump);
|
|
} else if (dynamic_cast<GameMapGump *>(gump)) {
|
|
if (GAME_IS_U8)
|
|
_inverterGump->AddChild(gump);
|
|
else
|
|
_desktopGump->AddChild(gump);
|
|
} else if (dynamic_cast<InverterGump *>(gump)) {
|
|
_desktopGump->AddChild(gump);
|
|
} else if (dynamic_cast<DesktopGump *>(gump)) {
|
|
} else {
|
|
_desktopGump->AddChild(gump);
|
|
}
|
|
}
|
|
|
|
bool Ultima8Engine::isAvatarControlled() const {
|
|
return (_world && _world->getControlledNPCNum() == kMainActorId);
|
|
}
|
|
|
|
uint32 Ultima8Engine::getGameTimeInSeconds() {
|
|
// 1 second per every 30 frames
|
|
return (Kernel::get_instance()->getFrameNum() + _timeOffset) / Kernel::FRAMES_PER_SECOND; // constant!
|
|
}
|
|
|
|
void Ultima8Engine::moveKeyEvent() {
|
|
_moveKeyFrame = Kernel::get_instance()->getFrameNum();
|
|
}
|
|
|
|
bool Ultima8Engine::moveKeyDownRecently() {
|
|
uint32 nowframe = Kernel::get_instance()->getFrameNum();
|
|
return (nowframe - _moveKeyFrame) < 2 * Kernel::FRAMES_PER_SECOND;
|
|
}
|
|
|
|
void Ultima8Engine::save(Common::WriteStream *ws) {
|
|
uint8 s = (_avatarInStasis ? 1 : 0);
|
|
ws->writeByte(s);
|
|
|
|
if (GAME_IS_CRUSADER) {
|
|
uint8 f = (_crusaderTeleporting ? 1 : 0);
|
|
ws->writeByte(f);
|
|
}
|
|
|
|
int32 absoluteTime = Kernel::get_instance()->getFrameNum() + _timeOffset;
|
|
ws->writeUint32LE(static_cast<uint32>(absoluteTime));
|
|
ws->writeUint16LE(_avatarMoverProcess->getPid());
|
|
|
|
PaletteManager::get_instance()->saveTransforms(*ws);
|
|
|
|
ws->writeUint16LE(static_cast<uint16>(_inversion));
|
|
|
|
ws->writeUint32LE(_saveCount);
|
|
|
|
uint8 c = (_hasCheated ? 1 : 0);
|
|
ws->writeByte(c);
|
|
}
|
|
|
|
bool Ultima8Engine::load(Common::ReadStream *rs, uint32 version) {
|
|
_avatarInStasis = (rs->readByte() != 0);
|
|
|
|
if (GAME_IS_CRUSADER) {
|
|
_crusaderTeleporting = (rs->readByte() != 0);
|
|
_cruStasis = false;
|
|
}
|
|
|
|
// no gump should be moused over after load
|
|
_mouse->resetMouseOverGump();
|
|
|
|
int32 absoluteTime = static_cast<int32>(rs->readUint32LE());
|
|
_timeOffset = absoluteTime - Kernel::get_instance()->getFrameNum();
|
|
|
|
uint16 amppid = rs->readUint16LE();
|
|
_avatarMoverProcess = dynamic_cast<AvatarMoverProcess *>(Kernel::get_instance()->getProcess(amppid));
|
|
|
|
if (!PaletteManager::get_instance()->loadTransforms(*rs))
|
|
return false;
|
|
|
|
_inversion = rs->readUint16LE();
|
|
|
|
_saveCount = rs->readUint32LE();
|
|
|
|
_hasCheated = (rs->readByte() != 0);
|
|
|
|
// Integrity checks
|
|
if (!_avatarMoverProcess) {
|
|
warning("No AvatarMoverProcess. Corrupt savegame?");
|
|
return false;
|
|
}
|
|
if (_saveCount > 1024*1024) {
|
|
warning("Improbable savecount %d. Corrupt savegame?", _saveCount);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// Intrinsics
|
|
//
|
|
|
|
uint32 Ultima8Engine::I_avatarCanCheat(const uint8 * /*args*/,
|
|
unsigned int /*argsize*/) {
|
|
return Ultima8Engine::get_instance()->areCheatsEnabled() ? 1 : 0;
|
|
}
|
|
|
|
|
|
uint32 Ultima8Engine::I_makeAvatarACheater(const uint8 * /*args*/,
|
|
unsigned int /*argsize*/) {
|
|
Ultima8Engine::get_instance()->makeCheater();
|
|
return 0;
|
|
}
|
|
|
|
uint32 Ultima8Engine::I_getCurrentTimerTick(const uint8 * /*args*/,
|
|
unsigned int /*argsize*/) {
|
|
// number of ticks of a 60Hz timer, with the default animrate of 30Hz
|
|
return Kernel::get_instance()->getTickNum();
|
|
}
|
|
|
|
uint32 Ultima8Engine::I_setAvatarInStasis(const uint8 *args, unsigned int argsize) {
|
|
ARG_SINT16(stasis);
|
|
get_instance()->setAvatarInStasis(stasis != 0);
|
|
return 0;
|
|
}
|
|
|
|
uint32 Ultima8Engine::I_getAvatarInStasis(const uint8 * /*args*/, unsigned int /*argsize*/) {
|
|
if (get_instance()->_avatarInStasis)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
uint32 Ultima8Engine::I_setCruStasis(const uint8 *args, unsigned int argsize) {
|
|
get_instance()->setCruStasis(true);
|
|
return 0;
|
|
}
|
|
|
|
uint32 Ultima8Engine::I_clrCruStasis(const uint8 *args, unsigned int argsize) {
|
|
get_instance()->setCruStasis(false);
|
|
return 0;
|
|
}
|
|
|
|
uint32 Ultima8Engine::I_getTimeInGameHours(const uint8 * /*args*/,
|
|
unsigned int /*argsize*/) {
|
|
// 900 seconds per _game hour
|
|
return get_instance()->getGameTimeInSeconds() / 900;
|
|
}
|
|
|
|
uint32 Ultima8Engine::I_getCrusaderTeleporting(const uint8 * /*args*/,
|
|
unsigned int /*argsize*/) {
|
|
return get_instance()->isCrusaderTeleporting() ? 1 : 0;
|
|
}
|
|
|
|
uint32 Ultima8Engine::I_setCrusaderTeleporting(const uint8 * /*args*/,
|
|
unsigned int /*argsize*/) {
|
|
get_instance()->setCrusaderTeleporting(true);
|
|
return 0;
|
|
}
|
|
|
|
uint32 Ultima8Engine::I_clrCrusaderTeleporting(const uint8 * /*args*/,
|
|
unsigned int /*argsize*/) {
|
|
get_instance()->setCrusaderTeleporting(false);
|
|
return 0;
|
|
}
|
|
|
|
uint32 Ultima8Engine::I_getTimeInMinutes(const uint8 * /*args*/,
|
|
unsigned int /*argsize*/) {
|
|
// 60 seconds per minute
|
|
return get_instance()->getGameTimeInSeconds() / 60;
|
|
}
|
|
|
|
uint32 Ultima8Engine::I_getTimeInSeconds(const uint8 * /*args*/,
|
|
unsigned int /*argsize*/) {
|
|
return get_instance()->getGameTimeInSeconds();
|
|
}
|
|
|
|
uint32 Ultima8Engine::I_setTimeInGameHours(const uint8 *args,
|
|
unsigned int /*argsize*/) {
|
|
ARG_UINT16(newhour);
|
|
|
|
// 1 _game hour per every 27000 frames
|
|
int32 absolute = newhour * 27000;
|
|
get_instance()->_timeOffset = absolute - Kernel::get_instance()->getFrameNum();
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint32 Ultima8Engine::I_closeItemGumps(const uint8 *args, unsigned int /*argsize*/) {
|
|
Ultima8Engine *g = Ultima8Engine::get_instance();
|
|
g->getDesktopGump()->CloseItemDependents();
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint32 Ultima8Engine::I_moveKeyDownRecently(const uint8 *args, unsigned int /*argsize*/) {
|
|
Ultima8Engine *g = Ultima8Engine::get_instance();
|
|
return g->moveKeyDownRecently() ? 1 : 0;
|
|
}
|
|
|
|
Graphics::Screen *Ultima8Engine::getScreen() const {
|
|
Graphics::Screen *scr = dynamic_cast<Graphics::Screen *>(_screen->getRawSurface());
|
|
assert(scr);
|
|
return scr;
|
|
}
|
|
|
|
void Ultima8Engine::showSplashScreen() {
|
|
Image::PNGDecoder png;
|
|
Common::File f;
|
|
|
|
// Get splash _screen image
|
|
if (!f.open("pentagram.png") || !png.loadStream(f))
|
|
return;
|
|
|
|
// Blit the splash image to the _screen
|
|
Graphics::Screen *scr = Ultima8Engine::get_instance()->getScreen();
|
|
const Graphics::Surface *srcSurface = png.getSurface();
|
|
|
|
Common::Rect dest(0, 0, scr->w, scr->h);
|
|
|
|
// Splash screen is expected to be 640x480.
|
|
// If the window has a different aspect ratio or corrected aspect ratio,
|
|
// then scale to appropriate size and center.
|
|
frac_t aspectRatio = Common::Rational(scr->w, scr->h).toFrac();
|
|
if (aspectRatio != Common::Rational(320, 240).toFrac() &&
|
|
aspectRatio != Common::Rational(320, 200).toFrac()) {
|
|
Common::Rational scaleFactorX(scr->w, srcSurface->w);
|
|
Common::Rational scaleFactorY(scr->h, srcSurface->h);
|
|
Common::Rational scale = scaleFactorX < scaleFactorY ? scaleFactorX : scaleFactorY;
|
|
|
|
dest.setWidth((srcSurface->w * scale).toInt());
|
|
dest.setHeight((srcSurface->h * scale).toInt());
|
|
dest.moveTo((scr->w - dest.width()) / 2, (scr->h - dest.height()) / 2);
|
|
}
|
|
|
|
scr->blitFrom(*srcSurface, Common::Rect(0, 0, srcSurface->w, srcSurface->h), dest);
|
|
scr->update();
|
|
// Handle a single event to get the splash screen shown
|
|
Common::Event event;
|
|
pollEvent(event);
|
|
}
|
|
|
|
bool Ultima8Engine::pollEvent(Common::Event &event) {
|
|
uint32 timer = g_system->getMillis();
|
|
|
|
if (timer >= (_priorFrameCounterTime + GAME_FRAME_TIME)) {
|
|
// Time to build up next game frame
|
|
_priorFrameCounterTime = timer;
|
|
|
|
// Render anything pending for the screen
|
|
Graphics::Screen *screen = getScreen();
|
|
if (screen)
|
|
screen->update();
|
|
}
|
|
|
|
// Event handling
|
|
return g_system->getEventManager()->pollEvent(event);
|
|
}
|
|
|
|
} // End of namespace Ultima8
|
|
} // End of namespace Ultima
|