1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "common/translation.h"
24 #include "gui/saveload.h"
25 #include "image/png.h"
26 #include "engines/dialogs.h"
27 
28  // TODO: !! a lot of these includes are just for some hacks... clean up sometime
29 #include "ultima/ultima8/conf/config_file_manager.h"
30 #include "ultima/ultima8/filesys/file_system.h"
31 #include "ultima/ultima8/kernel/object_manager.h"
32 #include "ultima/ultima8/games/start_u8_process.h"
33 #include "ultima/ultima8/games/start_crusader_process.h"
34 #include "ultima/ultima8/graphics/fonts/font_manager.h"
35 #include "ultima/ultima8/graphics/render_surface.h"
36 #include "ultima/ultima8/games/game_data.h"
37 #include "ultima/ultima8/world/world.h"
38 #include "ultima/ultima8/world/get_object.h"
39 #include "ultima/ultima8/filesys/savegame.h"
40 #include "ultima/ultima8/gumps/game_map_gump.h"
41 #include "ultima/ultima8/gumps/inverter_gump.h"
42 #include "ultima/ultima8/gumps/minimap_gump.h"
43 #include "ultima/ultima8/gumps/cru_status_gump.h"
44 #include "ultima/ultima8/gumps/movie_gump.h"
45 #include "ultima/ultima8/gumps/weasel_gump.h"
46 
47 // For gump positioning... perhaps shouldn't do it this way....
48 #include "ultima/ultima8/gumps/message_box_gump.h"
49 #include "ultima/ultima8/gumps/keypad_gump.h"
50 #include "ultima/ultima8/gumps/computer_gump.h"
51 #include "ultima/ultima8/world/actors/quick_avatar_mover_process.h"
52 #include "ultima/ultima8/world/actors/battery_charger_process.h"
53 #include "ultima/ultima8/world/actors/cru_healer_process.h"
54 #include "ultima/ultima8/world/actors/targeted_anim_process.h"
55 #include "ultima/ultima8/usecode/u8_intrinsics.h"
56 #include "ultima/ultima8/usecode/remorse_intrinsics.h"
57 #include "ultima/ultima8/usecode/regret_intrinsics.h"
58 
59 #include "ultima/ultima8/graphics/cycle_process.h"
60 #include "ultima/ultima8/world/actors/scheduler_process.h"
61 #include "ultima/ultima8/world/egg_hatcher_process.h" // for a hack
62 #include "ultima/ultima8/usecode/uc_process.h" // more hacking
63 #include "ultima/ultima8/world/actors/actor_bark_notify_process.h" // guess
64 #include "ultima/ultima8/kernel/delay_process.h"
65 #include "ultima/ultima8/world/actors/avatar_gravity_process.h"
66 #include "ultima/ultima8/world/actors/teleport_to_egg_process.h"
67 #include "ultima/ultima8/world/item_selection_process.h"
68 #include "ultima/ultima8/world/split_item_process.h"
69 #include "ultima/ultima8/world/target_reticle_process.h"
70 #include "ultima/ultima8/world/snap_process.h"
71 #include "ultima/ultima8/world/crosshair_process.h"
72 #include "ultima/ultima8/world/actors/pathfinder_process.h"
73 #include "ultima/ultima8/world/actors/u8_avatar_mover_process.h"
74 #include "ultima/ultima8/world/actors/cru_avatar_mover_process.h"
75 #include "ultima/ultima8/world/actors/cru_pathfinder_process.h"
76 #include "ultima/ultima8/world/actors/resurrection_process.h"
77 #include "ultima/ultima8/world/actors/clear_feign_death_process.h"
78 #include "ultima/ultima8/world/actors/loiter_process.h"
79 #include "ultima/ultima8/world/actors/avatar_death_process.h"
80 #include "ultima/ultima8/world/actors/surrender_process.h"
81 #include "ultima/ultima8/world/actors/combat_process.h"
82 #include "ultima/ultima8/world/actors/guard_process.h"
83 #include "ultima/ultima8/world/actors/attack_process.h"
84 #include "ultima/ultima8/world/actors/auto_firer_process.h"
85 #include "ultima/ultima8/world/actors/pace_process.h"
86 #include "ultima/ultima8/world/actors/rolling_thunder_process.h"
87 #include "ultima/ultima8/world/bobo_boomer_process.h"
88 #include "ultima/ultima8/world/super_sprite_process.h"
89 #include "ultima/ultima8/world/destroy_item_process.h"
90 #include "ultima/ultima8/world/actors/ambush_process.h"
91 #include "ultima/ultima8/audio/audio_mixer.h"
92 #include "ultima/ultima8/audio/u8_music_process.h"
93 #include "ultima/ultima8/audio/cru_music_process.h"
94 #include "ultima/ultima8/audio/midi_player.h"
95 #include "ultima/ultima8/gumps/shape_viewer_gump.h"
96 #include "ultima/ultima8/meta_engine.h"
97 
98 namespace Ultima {
99 namespace Ultima8 {
100 
101 using Std::string;
102 
103 // a bit of a hack to prevent having to write a load function for
104 // every process
105 template<class T>
106 struct ProcessLoader {
loadUltima::Ultima8::ProcessLoader107 	static Process *load(Common::ReadStream *rs, uint32 version) {
108 		T *p = new T();
109 		bool ok = p->loadData(rs, version);
110 		if (!ok) {
111 			delete p;
112 			p = nullptr;
113 		}
114 		return p;
115 	}
116 };
117 
HasPreventSaveFlag(const Gump * g)118 inline bool HasPreventSaveFlag(const Gump *g) { return g->hasFlags(Gump::FLAG_PREVENT_SAVE); }
119 
120 Ultima8Engine *Ultima8Engine::_instance = nullptr;
121 
Ultima8Engine(OSystem * syst,const Ultima::UltimaGameDescription * gameDesc)122 Ultima8Engine::Ultima8Engine(OSystem *syst, const Ultima::UltimaGameDescription *gameDesc) :
123 		Shared::UltimaEngine(syst, gameDesc),
124 		_isRunning(false),  _gameInfo(nullptr), _fileSystem(nullptr),
125 		_configFileMan(nullptr), _saveCount(0), _game(nullptr),
126 		_kernel(nullptr), _objectManager(nullptr), _mouse(nullptr), _ucMachine(nullptr),
127 		_screen(nullptr), _fontManager(nullptr), _paletteManager(nullptr), _gameData(nullptr),
128 		_world(nullptr), _desktopGump(nullptr), _gameMapGump(nullptr), _avatarMoverProcess(nullptr),
129 		_frameSkip(false), _frameLimit(true), _interpolate(true), _animationRate(100),
130 		_avatarInStasis(false), _cruStasis(false), _paintEditorItems(false), _inversion(0),
131 		_showTouching(false), _timeOffset(0), _hasCheated(false), _cheatsEnabled(false),
132 		_fontOverride(false), _fontAntialiasing(false), _audioMixer(0), _inverterGump(nullptr),
133 	    _lerpFactor(256), _inBetweenFrame(false), _unkCrusaderFlag(false), _moveKeyFrame(0),
134 		_highRes(false) {
135 	_instance = this;
136 }
137 
~Ultima8Engine()138 Ultima8Engine::~Ultima8Engine() {
139 	FORGET_OBJECT(_events);
140 	FORGET_OBJECT(_kernel);
141 	FORGET_OBJECT(_objectManager);
142 	FORGET_OBJECT(_audioMixer);
143 	FORGET_OBJECT(_ucMachine);
144 	FORGET_OBJECT(_paletteManager);
145 	FORGET_OBJECT(_mouse);
146 	FORGET_OBJECT(_gameData);
147 	FORGET_OBJECT(_world);
148 	FORGET_OBJECT(_ucMachine);
149 	FORGET_OBJECT(_fontManager);
150 	FORGET_OBJECT(_screen);
151 	FORGET_OBJECT(_fileSystem);
152 	FORGET_OBJECT(_configFileMan);
153 	FORGET_OBJECT(_gameInfo);
154 
155 	_instance = nullptr;
156 }
157 
run()158 Common::Error Ultima8Engine::run() {
159 	bool result = true;
160 	if (initialize()) {
161 		result = startup();
162 		if (result)
163 			result = runGame();
164 
165 		deinitialize();
166 		shutdown();
167 	}
168 
169 	if (result)
170 		return Common::kNoError;
171 	else
172 		return Common::kNoGameDataFoundError;
173 }
174 
175 
initialize()176 bool Ultima8Engine::initialize() {
177 	if (!Shared::UltimaEngine::initialize())
178 		return false;
179 
180 	// Set up the events manager
181 	_events = new Shared::EventsManager(this);
182 
183 	return true;
184 }
185 
deinitialize()186 void Ultima8Engine::deinitialize() {
187 }
188 
pauseEngineIntern(bool pause)189 void Ultima8Engine::pauseEngineIntern(bool pause) {
190 	if (_mixer)
191 		_mixer->pauseAll(pause);
192 	if (_audioMixer) {
193 		MidiPlayer *midiPlayer = _audioMixer->getMidiPlayer();
194 		if (midiPlayer)
195 			midiPlayer->pause(pause);
196 	}
197 }
198 
hasFeature(EngineFeature f) const199 bool Ultima8Engine::hasFeature(EngineFeature f) const {
200 	return
201 		(f == kSupportsSubtitleOptions) ||
202 		(f == kSupportsReturnToLauncher) ||
203 		(f == kSupportsLoadingDuringRuntime) ||
204 		(f == kSupportsSavingDuringRuntime) ||
205 		(f == kSupportsChangingOptionsDuringRuntime);
206 }
207 
startup()208 bool Ultima8Engine::startup() {
209 	setDebugger(new Debugger());
210 	pout << "-- Initializing Pentagram -- " << Std::endl;
211 
212 	_gameInfo = nullptr;
213 	_fileSystem = new FileSystem;
214 	_configFileMan = new ConfigFileManager();
215 	_fontManager = new FontManager();
216 	_kernel = new Kernel();
217 
218 	//!! move this elsewhere
219 	_kernel->addProcessLoader("DelayProcess",
220 		ProcessLoader<DelayProcess>::load);
221 	_kernel->addProcessLoader("GravityProcess",
222 		ProcessLoader<GravityProcess>::load);
223 	_kernel->addProcessLoader("AvatarGravityProcess",
224 		ProcessLoader<AvatarGravityProcess>::load);
225 	_kernel->addProcessLoader("PaletteFaderProcess",
226 		ProcessLoader<PaletteFaderProcess>::load);
227 	_kernel->addProcessLoader("TeleportToEggProcess",
228 		ProcessLoader<TeleportToEggProcess>::load);
229 	_kernel->addProcessLoader("ActorAnimProcess",
230 		ProcessLoader<ActorAnimProcess>::load);
231 	_kernel->addProcessLoader("TargetedAnimProcess",
232 		ProcessLoader<TargetedAnimProcess>::load);
233 	_kernel->addProcessLoader("AvatarMoverProcess", // parent class for backward compatibility
234 		ProcessLoader<U8AvatarMoverProcess>::load);
235 	_kernel->addProcessLoader("U8AvatarMoverProcess",
236 		ProcessLoader<U8AvatarMoverProcess>::load);
237 	_kernel->addProcessLoader("CruAvatarMoverProcess",
238 		ProcessLoader<CruAvatarMoverProcess>::load);
239 	_kernel->addProcessLoader("QuickAvatarMoverProcess",
240 		ProcessLoader<QuickAvatarMoverProcess>::load);
241 	_kernel->addProcessLoader("PathfinderProcess",
242 		ProcessLoader<PathfinderProcess>::load);
243 	_kernel->addProcessLoader("CruPathfinderProcess",
244 		ProcessLoader<CruPathfinderProcess>::load);
245 	_kernel->addProcessLoader("SpriteProcess",
246 		ProcessLoader<SpriteProcess>::load);
247 	_kernel->addProcessLoader("CameraProcess",
248 		ProcessLoader<CameraProcess>::load);
249 	_kernel->addProcessLoader("MusicProcess", // parent class name for save game backwards-compatibility.
250 		ProcessLoader<U8MusicProcess>::load);
251 	_kernel->addProcessLoader("U8MusicProcess",
252 		ProcessLoader<U8MusicProcess>::load);
253 	_kernel->addProcessLoader("RemorseMusicProcess", // name was changed, keep this for backward-compatibility.
254 		ProcessLoader<CruMusicProcess>::load);
255 	_kernel->addProcessLoader("CruMusicProcess",
256 		ProcessLoader<CruMusicProcess>::load);
257 	_kernel->addProcessLoader("AudioProcess",
258 		ProcessLoader<AudioProcess>::load);
259 	_kernel->addProcessLoader("EggHatcherProcess",
260 		ProcessLoader<EggHatcherProcess>::load);
261 	_kernel->addProcessLoader("UCProcess",
262 		ProcessLoader<UCProcess>::load);
263 	_kernel->addProcessLoader("GumpNotifyProcess",
264 		ProcessLoader<GumpNotifyProcess>::load);
265 	_kernel->addProcessLoader("ResurrectionProcess",
266 		ProcessLoader<ResurrectionProcess>::load);
267 	_kernel->addProcessLoader("DeleteActorProcess",
268 		ProcessLoader<DestroyItemProcess>::load);  // YES, this is intentional
269 	_kernel->addProcessLoader("DestroyItemProcess",
270 		ProcessLoader<DestroyItemProcess>::load);
271 	_kernel->addProcessLoader("SplitItemProcess",
272 		ProcessLoader<SplitItemProcess>::load);
273 	_kernel->addProcessLoader("ClearFeignDeathProcess",
274 		ProcessLoader<ClearFeignDeathProcess>::load);
275 	_kernel->addProcessLoader("LoiterProcess",
276 		ProcessLoader<LoiterProcess>::load);
277 	_kernel->addProcessLoader("AvatarDeathProcess",
278 		ProcessLoader<AvatarDeathProcess>::load);
279 	_kernel->addProcessLoader("GrantPeaceProcess",
280 		ProcessLoader<GrantPeaceProcess>::load);
281 	_kernel->addProcessLoader("CombatProcess",
282 		ProcessLoader<CombatProcess>::load);
283 	_kernel->addProcessLoader("FireballProcess",
284 		ProcessLoader<FireballProcess>::load);
285 	_kernel->addProcessLoader("HealProcess",
286 		ProcessLoader<HealProcess>::load);
287 	_kernel->addProcessLoader("SchedulerProcess",
288 		ProcessLoader<SchedulerProcess>::load);
289 	_kernel->addProcessLoader("InverterProcess",
290 		ProcessLoader<InverterProcess>::load);
291 	_kernel->addProcessLoader("ActorBarkNotifyProcess",
292 		ProcessLoader<ActorBarkNotifyProcess>::load);
293 	_kernel->addProcessLoader("AmbushProcess",
294 		ProcessLoader<AmbushProcess>::load);
295 	_kernel->addProcessLoader("TargetReticleProcess",
296 		ProcessLoader<TargetReticleProcess>::load);
297 	_kernel->addProcessLoader("SurrenderProcess",
298 		ProcessLoader<SurrenderProcess>::load);
299 	_kernel->addProcessLoader("CruHealerProcess",
300 		ProcessLoader<CruHealerProcess>::load);
301 	_kernel->addProcessLoader("BatteryChargerProcess",
302 		ProcessLoader<BatteryChargerProcess>::load);
303 	_kernel->addProcessLoader("CycleProcess",
304 		ProcessLoader<CycleProcess>::load);
305 	_kernel->addProcessLoader("GuardProcess",
306 		ProcessLoader<GuardProcess>::load);
307 	_kernel->addProcessLoader("SnapProcess",
308 		ProcessLoader<SnapProcess>::load);
309 	_kernel->addProcessLoader("CrosshairProcess",
310 		ProcessLoader<CrosshairProcess>::load);
311 	_kernel->addProcessLoader("ItemSelectionProcess",
312 		ProcessLoader<ItemSelectionProcess>::load);
313 	_kernel->addProcessLoader("PaceProcess",
314 		ProcessLoader<PaceProcess>::load);
315 	_kernel->addProcessLoader("SuperSpriteProcess",
316 		ProcessLoader<SuperSpriteProcess>::load);
317 	_kernel->addProcessLoader("AttackProcess",
318 		ProcessLoader<AttackProcess>::load);
319 	_kernel->addProcessLoader("AutoFirerProcess",
320 		ProcessLoader<AutoFirerProcess>::load);
321 	_kernel->addProcessLoader("BoboBoomerProcess",
322 		ProcessLoader<BoboBoomerProcess>::load);
323 	_kernel->addProcessLoader("RollingThunderProcess",
324 		ProcessLoader<RollingThunderProcess>::load);
325 
326 	_objectManager = new ObjectManager();
327 	_mouse = new Mouse();
328 
329 	// Audio Mixer
330 	_audioMixer = new AudioMixer(_mixer);
331 
332 	pout << "-- Pentagram Initialized -- " << Std::endl << Std::endl;
333 
334 	if (setupGame()) {
335 		GraphicSysInit();
336 		if (!startupGame())
337 			return false;
338 	} else {
339 		// Couldn't setup the game, should never happen?
340 		CANT_HAPPEN_MSG("game failed to initialize");
341 	}
342 	paint();
343 	return true;
344 }
345 
setupGame()346 bool Ultima8Engine::setupGame() {
347 	istring gamename = _gameDescription->desc.gameId;
348 	GameInfo *info = new GameInfo;
349 	bool detected = getGameInfo(gamename, info);
350 
351 	// output detected game info
352 	debugN(MM_INFO, "%s: ", gamename.c_str());
353 	if (detected) {
354 		// add game to games map
355 		Std::string details = info->getPrintDetails();
356 		debugN(MM_INFO, "%s", details.c_str());
357 	} else {
358 		debugN(MM_INFO, "unknown, skipping");
359 		return false;
360 	}
361 
362 	_gameInfo = info;
363 
364 	pout << "Selected game: " << info->_name << Std::endl;
365 	pout << info->getPrintDetails() << Std::endl;
366 
367 	return true;
368 }
369 
startupGame()370 bool Ultima8Engine::startupGame() {
371 	pout  << Std::endl << "-- Initializing Game: " << _gameInfo->_name << " --" << Std::endl;
372 
373 	GraphicSysInit();
374 
375 	_gameData = new GameData(_gameInfo);
376 
377 	if (_gameInfo->_type == GameInfo::GAME_U8) {
378 		_ucMachine = new UCMachine(U8Intrinsics, ARRAYSIZE(U8Intrinsics));
379 	} else if (_gameInfo->_type == GameInfo::GAME_REMORSE) {
380 		switch (_gameInfo->_ucOffVariant) {
381 		case GameInfo::GAME_UC_DEMO:
382 			_ucMachine = new UCMachine(RemorseDemoIntrinsics, ARRAYSIZE(RemorseDemoIntrinsics));
383 			break;
384 		case GameInfo::GAME_UC_REM_ES:
385 			_ucMachine = new UCMachine(RemorseEsIntrinsics, ARRAYSIZE(RemorseEsIntrinsics));
386 			break;
387 		case GameInfo::GAME_UC_REM_FR:
388 			_ucMachine = new UCMachine(RemorseFrIntrinsics, ARRAYSIZE(RemorseFrIntrinsics));
389 			break;
390 		case GameInfo::GAME_UC_REM_JA:
391 			warning("TODO: Create Remorse JA intrinsic list");
392 			_ucMachine = new UCMachine(RemorseIntrinsics, ARRAYSIZE(RemorseIntrinsics));
393 			break;
394 		case GameInfo::GAME_UC_ORIG:
395 			warning("TODO: Create Remorse original version intrinsic list");
396 			_ucMachine = new UCMachine(RemorseIntrinsics, ARRAYSIZE(RemorseIntrinsics));
397 			break;
398 		default:
399 			_ucMachine = new UCMachine(RemorseIntrinsics, ARRAYSIZE(RemorseIntrinsics));
400 			break;
401 		}
402 	} else if (_gameInfo->_type == GameInfo::GAME_REGRET) {
403 		switch (_gameInfo->_ucOffVariant) {
404 		case GameInfo::GAME_UC_DEMO:
405 			_ucMachine = new UCMachine(RegretDemoIntrinsics, ARRAYSIZE(RegretDemoIntrinsics));
406 			break;
407 		case GameInfo::GAME_UC_REG_DE:
408 			_ucMachine = new UCMachine(RegretDeIntrinsics, ARRAYSIZE(RegretDeIntrinsics));
409 			break;
410 		case GameInfo::GAME_UC_ORIG: // 1.06 is the original CD release too?
411 		default:
412 			_ucMachine = new UCMachine(RegretIntrinsics, ARRAYSIZE(RegretIntrinsics));
413 			break;
414 		}
415 	} else {
416 		CANT_HAPPEN_MSG("Invalid game type.");
417 	}
418 
419 	_inBetweenFrame = false;
420 	_lerpFactor = 256;
421 
422 	// Initialize _world
423 	_world = new World();
424 	_world->initMaps();
425 
426 	_game = Game::createGame(getGameInfo());
427 
428 	ConfMan.registerDefault("font_override", false);
429 	ConfMan.registerDefault("font_antialiasing", true);
430 	ConfMan.registerDefault("frameSkip", false);
431 	ConfMan.registerDefault("frameLimit", true);
432 	// Position interpolation looks nice on U8, but causes Crusader to look janky.
433 	ConfMan.registerDefault("interpolate", _gameInfo->_type == GameInfo::GAME_U8);
434 	ConfMan.registerDefault("cheat", false);
435 
436 	bool loaded = _game->loadFiles();
437 	if (!loaded)
438 		return false;
439 
440 	applyGameSettings();
441 
442 	// Create Midi Driver for Ultima 8
443 	if (getGameInfo()->_type == GameInfo::GAME_U8)
444 		_audioMixer->openMidiOutput();
445 
446 	int saveSlot = ConfMan.hasKey("save_slot") ? ConfMan.getInt("save_slot") : -1;
447 	if (saveSlot == -1)
448 		saveSlot = ConfMan.getInt("lastSave");
449 
450 	newGame(saveSlot);
451 
452 	pout << "-- Game Initialized --" << Std::endl << Std::endl;
453 	return true;
454 }
455 
shutdown()456 void Ultima8Engine::shutdown() {
457 	shutdownGame(false);
458 }
459 
shutdownGame(bool reloading)460 void Ultima8Engine::shutdownGame(bool reloading) {
461 	pout << "-- Shutting down Game -- " << Std::endl;
462 
463 	// Save config here....
464 
465 	_textModes.clear();
466 
467 	// reset mouse cursor
468 	_mouse->popAllCursors();
469 	_mouse->pushMouseCursor();
470 
471 	FORGET_OBJECT(_world);
472 	_objectManager->reset();
473 	FORGET_OBJECT(_ucMachine);
474 	_kernel->reset();
475 	_paletteManager->reset();
476 	_fontManager->resetGameFonts();
477 
478 	FORGET_OBJECT(_game);
479 	FORGET_OBJECT(_gameData);
480 
481 	if (_audioMixer) {
482 		_audioMixer->closeMidiOutput();
483 		_audioMixer->reset();
484 		FORGET_OBJECT(_audioMixer);
485 	}
486 
487 	_desktopGump = nullptr;
488 	_gameMapGump = nullptr;
489 	_inverterGump = nullptr;
490 
491 	_timeOffset = -(int32)Kernel::get_instance()->getFrameNum();
492 	_saveCount = 0;
493 	_hasCheated = false;
494 
495 	_configFileMan->clearRoot("bindings");
496 	_configFileMan->clearRoot("language");
497 	_configFileMan->clearRoot("weapons");
498 	_configFileMan->clearRoot("armour");
499 	_configFileMan->clearRoot("monsters");
500 	_configFileMan->clearRoot("game");
501 	_gameInfo = nullptr;
502 
503 	pout << "-- Game Shutdown -- " << Std::endl;
504 
505 	if (reloading) {
506 		Rect dims;
507 		_screen->GetSurfaceDims(dims);
508 
509 		debugN(MM_INFO, "Creating Desktop...\n");
510 		_desktopGump = new DesktopGump(0, 0, dims.width(), dims.height());
511 		_desktopGump->InitGump(0);
512 		_desktopGump->MakeFocus();
513 
514 		if (GAME_IS_U8) {
515 			debugN(MM_INFO, "Creating Inverter...\n");
516 			_inverterGump = new InverterGump(0, 0, dims.width(), dims.height());
517 			_inverterGump->InitGump(0);
518 		}
519 	}
520 }
521 
522 //
523 // To time the frames, we use "fast" ticks which come 3000 times a second.
524 //
_fastTicksNow()525 static uint32 _fastTicksNow() {
526 	return g_system->getMillis() * 3;
527 }
528 
runGame()529 bool Ultima8Engine::runGame() {
530 	_isRunning = true;
531 
532 	int32 next_ticks = _fastTicksNow();  // Next time is right now!
533 
534 	Common::Event event;
535 	while (_isRunning) {
536 		_inBetweenFrame = true;  // Will get set false if it's not an _inBetweenFrame
537 
538 		if (!_frameLimit) {
539 			for (unsigned int tick = 0; tick < Kernel::TICKS_PER_FRAME; tick++) {
540 				_kernel->runProcesses();
541 				_desktopGump->run();
542 			}
543 			_inBetweenFrame = false;
544 			next_ticks = _animationRate + _fastTicksNow();
545 			_lerpFactor = 256;
546 		} else {
547 			int32 ticks = _fastTicksNow();
548 			int32 diff = next_ticks - ticks;
549 
550 			while (diff < 0) {
551 				next_ticks += _animationRate;
552 				for (unsigned int tick = 0; tick < Kernel::TICKS_PER_FRAME; tick++) {
553 					_kernel->runProcesses();
554 					_desktopGump->run();
555 				}
556 #if 0
557 				perr << "--------------------------------------" << Std::endl;
558 				perr << "NEW FRAME" << Std::endl;
559 				perr << "--------------------------------------" << Std::endl;
560 #endif
561 				_inBetweenFrame = false;
562 
563 				ticks = _fastTicksNow();
564 
565 				// If frame skipping is off, we will only recalc next
566 				// ticks IF the frames are taking up 'way' too much time.
567 				if (!_frameSkip && diff <= -_animationRate * 2) next_ticks = _animationRate + ticks;
568 
569 				diff = next_ticks - ticks;
570 				if (!_frameSkip) break;
571 			}
572 
573 			// Calculate the lerp_factor
574 			_lerpFactor = ((_animationRate - diff) * 256) / _animationRate;
575 			//pout << "_lerpFactor: " << _lerpFactor << " framenum: " << framenum << Std::endl;
576 			if (!_interpolate || _kernel->isPaused() || _lerpFactor > 256)
577 				_lerpFactor = 256;
578 		}
579 
580 		// get & handle all events in queue
581 		while (_isRunning && _events->pollEvent(event)) {
582 			handleEvent(event);
583 		}
584 		handleDelayedEvents();
585 
586 		// Paint Screen
587 		paint();
588 
589 		if (!_errorMessage.empty()) {
590 			MessageBoxGump::Show(_errorTitle, _errorMessage, 0xFF8F3030);
591 			_errorTitle.clear();
592 			_errorMessage.clear();
593 		}
594 
595 		// Do a delay
596 		g_system->delayMillis(5);
597 	}
598 	return true;
599 }
600 
601 // Paint the _screen
paint()602 void Ultima8Engine::paint() {
603 	static long prev = 0;
604 	static long t = 0;
605 	static long tdiff = 0;
606 	static long tpaint = 0;
607 	long now = g_system->getMillis();
608 
609 	if (!_screen) // need to worry if the graphics system has been started. Need nicer way.
610 		return;
611 
612 	if (prev != 0)
613 		tdiff += now - prev;
614 	prev = now;
615 	++t;
616 
617 	// Begin _painting
618 	_screen->BeginPainting();
619 
620 	tpaint -= g_system->getMillis();
621 
622 	Rect r;
623 	_screen->GetSurfaceDims(r);
624 	if (_highRes)
625 		_screen->Fill32(0, 0, 0, r.width(), r.height());
626 
627 #ifdef DEBUG
628 	// Fill the screen with an annoying color so we can see fast area bugs
629 	_screen->Fill32(0xFF10FF10, 0, 0, r.width(), r.height());
630 #endif
631 
632 	_desktopGump->Paint(_screen, _lerpFactor, false);
633 	tpaint += g_system->getMillis();
634 
635 	// Draw the mouse
636 	_mouse->paint();
637 
638 	// End _painting
639 	_screen->EndPainting();
640 }
641 
GraphicSysInit()642 void Ultima8Engine::GraphicSysInit() {
643 	if (ConfMan.hasKey("usehighres")) {
644 		_highRes = ConfMan.getBool("usehighres");
645 	}
646 
647 	if (GAME_IS_U8) {
648 		ConfMan.registerDefault("width", _highRes ? U8_HIRES_SCREEN_WIDTH : U8_DEFAULT_SCREEN_WIDTH);
649 		ConfMan.registerDefault("height", _highRes ? U8_HIRES_SCREEN_HEIGHT : U8_DEFAULT_SCREEN_HEIGHT);
650 	} else {
651 		ConfMan.registerDefault("width", _highRes ? CRUSADER_HIRES_SCREEN_WIDTH : CRUSADER_DEFAULT_SCREEN_WIDTH);
652 		ConfMan.registerDefault("height", _highRes ? CRUSADER_HIRES_SCREEN_HEIGHT : CRUSADER_DEFAULT_SCREEN_HEIGHT);
653 	}
654 	ConfMan.registerDefault("bpp", 16);
655 
656 	int width = ConfMan.getInt("width");
657 	int height = ConfMan.getInt("height");
658 	int bpp = ConfMan.getInt("bpp");
659 
660 	if (_screen) {
661 		Rect old_dims;
662 		_screen->GetSurfaceDims(old_dims);
663 		if (width == old_dims.width() && height == old_dims.height())
664 			return;
665 		bpp = RenderSurface::_format.bpp();
666 
667 		delete _screen;
668 	}
669 	_screen = nullptr;
670 
671 	// Set Screen Resolution
672 	debugN(MM_INFO, "Setting Video Mode %dx%dx%d...\n", width, height, bpp);
673 
674 	RenderSurface *new_screen = RenderSurface::SetVideoMode(width, height, bpp);
675 
676 	if (!new_screen) {
677 		perr << Common::String::format("Unable to set new video mode. Trying %dx%dx32", U8_DEFAULT_SCREEN_WIDTH, U8_DEFAULT_SCREEN_HEIGHT) << Std::endl;
678 		new_screen = RenderSurface::SetVideoMode(U8_DEFAULT_SCREEN_WIDTH, U8_DEFAULT_SCREEN_HEIGHT, 32);
679 	}
680 
681 	if (!new_screen) {
682 		error("Unable to set video mode");
683 	}
684 
685 	if (_desktopGump) {
686 		_paletteManager->RenderSurfaceChanged(new_screen);
687 		static_cast<DesktopGump *>(_desktopGump)->RenderSurfaceChanged(new_screen);
688 		_screen = new_screen;
689 		paint();
690 		return;
691 	}
692 
693 	// setup normal mouse cursor
694 	debugN(MM_INFO, "Loading Default Mouse Cursor...\n");
695 	_mouse->setup();
696 
697 	_desktopGump = new DesktopGump(0, 0, width, height);
698 	_desktopGump->InitGump(0);
699 	_desktopGump->MakeFocus();
700 
701 	if (GAME_IS_U8) {
702 		_inverterGump = new InverterGump(0, 0, width, height);
703 		_inverterGump->InitGump(0);
704 	}
705 
706 	_screen = new_screen;
707 
708 	// Show the splash screen immediately now that the screen has been set up
709 	int saveSlot = ConfMan.hasKey("save_slot") ? ConfMan.getInt("save_slot") : -1;
710 	if (saveSlot == -1) {
711 		_mouse->setMouseCursor(Mouse::MOUSE_NONE);
712 		showSplashScreen();
713 	}
714 
715 	_paletteManager = new PaletteManager(new_screen);
716 
717 	ConfMan.registerDefault("fadedModal", true);
718 	bool faded_modal = ConfMan.getBool("fadedModal");
719 	DesktopGump::SetFadedModal(faded_modal);
720 
721 	paint();
722 }
723 
changeVideoMode(int width,int height)724 void Ultima8Engine::changeVideoMode(int width, int height) {
725 	if (width > 0) width = ConfMan.getInt("width");
726 	if (height > 0) height = ConfMan.getInt("height");
727 
728 	GraphicSysInit();
729 }
730 
enterTextMode(Gump * gump)731 void Ultima8Engine::enterTextMode(Gump *gump) {
732 	if (!_textModes.empty()) {
733 		_textModes.remove(gump->getObjId());
734 	}
735 	_textModes.push_front(gump->getObjId());
736 
737 	MetaEngine::setTextInputActive(true);
738 }
739 
leaveTextMode(Gump * gump)740 void Ultima8Engine::leaveTextMode(Gump *gump) {
741 	if (!_textModes.empty())
742 		_textModes.remove(gump->getObjId());
743 
744 	if (_textModes.empty())
745 		MetaEngine::setTextInputActive(false);
746 }
747 
handleEvent(const Common::Event & event)748 void Ultima8Engine::handleEvent(const Common::Event &event) {
749 	switch (event.type) {
750 	case Common::EVENT_KEYDOWN:
751 		break;
752 	case Common::EVENT_KEYUP:
753 		// Any system keys not in the bindings can be handled here
754 		break;
755 
756 	case Common::EVENT_MOUSEMOVE:
757 		_mouse->setMouseCoords(event.mouse.x, event.mouse.y);
758 		break;
759 
760 	case Common::EVENT_LBUTTONDOWN:
761 	case Common::EVENT_MBUTTONDOWN:
762 	case Common::EVENT_RBUTTONDOWN: {
763 		Shared::MouseButton button = Shared::BUTTON_LEFT;
764 		if (event.type == Common::EVENT_RBUTTONDOWN)
765 			button = Shared::BUTTON_RIGHT;
766 		else if (event.type == Common::EVENT_MBUTTONDOWN)
767 			button = Shared::BUTTON_MIDDLE;
768 
769 		_mouse->setMouseCoords(event.mouse.x, event.mouse.y);
770 		_mouse->buttonDown(button);
771 		break;
772 	}
773 
774 	case Common::EVENT_LBUTTONUP:
775 	case Common::EVENT_MBUTTONUP:
776 	case Common::EVENT_RBUTTONUP: {
777 		Shared::MouseButton button = Shared::BUTTON_LEFT;
778 		if (event.type == Common::EVENT_RBUTTONUP)
779 			button = Shared::BUTTON_RIGHT;
780 		else if (event.type == Common::EVENT_MBUTTONUP)
781 			button = Shared::BUTTON_MIDDLE;
782 
783 		_mouse->setMouseCoords(event.mouse.x, event.mouse.y);
784 		_mouse->buttonUp(button);
785 		break;
786 	}
787 
788 	case Common::EVENT_CUSTOM_ENGINE_ACTION_START:
789 		MetaEngine::pressAction((KeybindingAction)event.customType);
790 		return;
791 
792 	case Common::EVENT_CUSTOM_ENGINE_ACTION_END:
793 		MetaEngine::releaseAction((KeybindingAction)event.customType);
794 		return;
795 
796 	case Common::EVENT_QUIT:
797 	case Common::EVENT_RETURN_TO_LAUNCHER:
798 		_isRunning = false;
799 		break;
800 
801 	default:
802 		break;
803 	}
804 
805 	// Text mode input. A few hacks here
806 	Gump *gump = nullptr;
807 	while (!_textModes.empty()) {
808 		gump = dynamic_cast<Gump *>(_objectManager->getObject(_textModes.front()));
809 		if (gump)
810 			break;
811 
812 		_textModes.pop_front();
813 		if (_textModes.empty()) {
814 			MetaEngine::setTextInputActive(false);
815 		}
816 	}
817 
818 	if (gump) {
819 		switch (event.type) {
820 		case Common::EVENT_KEYDOWN:
821 			// Paste from Clip-Board on Ctrl-V - Note this should be a flag of some sort
822 			if (event.kbd.keycode == Common::KEYCODE_v && (event.kbd.flags & Common::KBD_CTRL)) {
823 				if (!g_system->hasTextInClipboard())
824 					return;
825 
826 				Common::String text = g_system->getTextFromClipboard();
827 
828 				// Only read the first line of text
829 				while (!text.empty() && text.firstChar() >= ' ')
830 					gump->OnTextInput(text.firstChar());
831 
832 				return;
833 			}
834 
835 			if (event.kbd.ascii >= ' ' &&
836 				event.kbd.ascii <= 255 &&
837 				!(event.kbd.ascii >= 0x7F && // control chars
838 					event.kbd.ascii <= 0x9F)) {
839 				gump->OnTextInput(event.kbd.ascii);
840 			}
841 
842 			gump->OnKeyDown(event.kbd.keycode, event.kbd.flags);
843 			return;
844 
845 		case Common::EVENT_KEYUP:
846 			gump->OnKeyUp(event.kbd.keycode);
847 			return;
848 
849 		default:
850 			break;
851 		}
852 	}
853 }
854 
handleDelayedEvents()855 void Ultima8Engine::handleDelayedEvents() {
856 	//uint32 now = g_system->getMillis();
857 
858 	_mouse->handleDelayedEvents();
859 }
860 
getGameInfo(const istring & game,GameInfo * ginfo)861 bool Ultima8Engine::getGameInfo(const istring &game, GameInfo *ginfo) {
862 	ginfo->_name = game;
863 	ginfo->_type = GameInfo::GAME_UNKNOWN;
864 	ginfo->version = 0;
865 	ginfo->_language = GameInfo::GAMELANG_UNKNOWN;
866 	ginfo->_ucOffVariant = GameInfo::GAME_UC_DEFAULT;
867 
868 	assert(game == "ultima8" || game == "remorse" || game == "regret");
869 
870 	if (game == "ultima8")
871 		ginfo->_type = GameInfo::GAME_U8;
872 	else if (game == "remorse")
873 		ginfo->_type = GameInfo::GAME_REMORSE;
874 	else if (game == "regret")
875 		ginfo->_type = GameInfo::GAME_REGRET;
876 
877 	if (ginfo->_type == GameInfo::GAME_REMORSE)
878 	{
879 		switch (_gameDescription->desc.flags & ADGF_USECODE_MASK) {
880 		case ADGF_USECODE_DEMO:
881 			ginfo->_ucOffVariant = GameInfo::GAME_UC_DEMO;
882 			break;
883 		case ADGF_USECODE_ORIG:
884 			ginfo->_ucOffVariant = GameInfo::GAME_UC_ORIG;
885 			break;
886 		case ADGF_USECODE_ES:
887 			ginfo->_ucOffVariant = GameInfo::GAME_UC_REM_ES;
888 			break;
889 		case ADGF_USECODE_FR:
890 			ginfo->_ucOffVariant = GameInfo::GAME_UC_REM_FR;
891 			break;
892 		case ADGF_USECODE_JA:
893 			ginfo->_ucOffVariant = GameInfo::GAME_UC_REM_JA;
894 			break;
895 		default:
896 			break;
897 		}
898 	} else if (ginfo->_type == GameInfo::GAME_REGRET) {
899 		switch (_gameDescription->desc.flags & ADGF_USECODE_MASK) {
900 		case ADGF_USECODE_DEMO:
901 			ginfo->_ucOffVariant = GameInfo::GAME_UC_DEMO;
902 			break;
903 		case ADGF_USECODE_ORIG:
904 			ginfo->_ucOffVariant = GameInfo::GAME_UC_ORIG;
905 			break;
906 		case ADGF_USECODE_DE:
907 			ginfo->_ucOffVariant = GameInfo::GAME_UC_REG_DE;
908 			break;
909 		default:
910 			break;
911 		}
912 	}
913 
914 	switch (_gameDescription->desc.language) {
915 	case Common::EN_ANY:
916 		ginfo->_language = GameInfo::GAMELANG_ENGLISH;
917 		break;
918 	case Common::FR_FRA:
919 		ginfo->_language = GameInfo::GAMELANG_FRENCH;
920 		break;
921 	case Common::DE_DEU:
922 		ginfo->_language = GameInfo::GAMELANG_GERMAN;
923 		break;
924 	case Common::ES_ESP:
925 		ginfo->_language = GameInfo::GAMELANG_SPANISH;
926 		break;
927 	case Common::JA_JPN:
928 		ginfo->_language = GameInfo::GAMELANG_JAPANESE;
929 		break;
930 	default:
931 		error("Unknown language");
932 		break;
933 	}
934 
935 	return ginfo->_type != GameInfo::GAME_UNKNOWN;
936 }
937 
writeSaveInfo(Common::WriteStream * ws)938 void Ultima8Engine::writeSaveInfo(Common::WriteStream *ws) {
939 	TimeDate timeInfo;
940 	g_system->getTimeAndDate(timeInfo);
941 
942 	ws->writeUint16LE(static_cast<uint16>(timeInfo.tm_year + 1900));
943 	ws->writeByte(static_cast<uint8>(timeInfo.tm_mon + 1));
944 	ws->writeByte(static_cast<uint8>(timeInfo.tm_mday));
945 	ws->writeByte(static_cast<uint8>(timeInfo.tm_hour));
946 	ws->writeByte(static_cast<uint8>(timeInfo.tm_min));
947 	ws->writeByte(static_cast<uint8>(timeInfo.tm_sec));
948 	ws->writeUint32LE(_saveCount);
949 	ws->writeUint32LE(getGameTimeInSeconds());
950 
951 	uint8 c = (_hasCheated ? 1 : 0);
952 	ws->writeByte(c);
953 
954 	// write _game-specific info
955 	_game->writeSaveInfo(ws);
956 }
957 
canSaveGameStateCurrently(bool isAutosave)958 bool Ultima8Engine::canSaveGameStateCurrently(bool isAutosave) {
959 	// Can't save when avatar in stasis during cutscenes
960 	if (_avatarInStasis || _cruStasis)
961 		return false;
962 
963 	// Check for gumps that prevent saving
964 	if (_desktopGump->FindGump(&HasPreventSaveFlag, true))
965 	{
966 		return false;
967 	}
968 
969 
970 	if (dynamic_cast<StartU8Process *>(_kernel->getRunningProcess())
971 		|| dynamic_cast<StartCrusaderProcess *>(_kernel->getRunningProcess()))
972 		// Don't save while starting up.
973 		return false;
974 
975 	// Don't allow saving when avatar is dead.
976 	MainActor *av = getMainActor();
977 	if (!av || av->hasActorFlags(Actor::ACT_DEAD))
978 		return false;
979 
980 	return true;
981 }
982 
saveGame(int slot,const Std::string & desc)983 bool Ultima8Engine::saveGame(int slot, const Std::string &desc) {
984 	// Check for gumps that prevent saving
985 	if (_desktopGump->FindGump(&HasPreventSaveFlag, true)) {
986 		pout << "Can't save: open gump preventing save." << Std::endl;
987 		return false;
988 	}
989 
990 	// Don't allow saving when avatar is dead.
991 	// (Avatar is flagged dead by usecode when you finish the _game as well.)
992 	MainActor *av = getMainActor();
993 	if (!av || av->hasActorFlags(Actor::ACT_DEAD)) {
994 		pout << "Can't save: game over." << Std::endl;
995 		return false;
996 	}
997 
998 	return saveGameState(slot, desc).getCode() == Common::kNoError;
999 }
1000 
loadGameState(int slot)1001 Common::Error Ultima8Engine::loadGameState(int slot) {
1002 	Common::Error result = Shared::UltimaEngine::loadGameState(slot);
1003 	if (result.getCode() == Common::kNoError)
1004 		ConfMan.setInt("lastSave", slot);
1005 	else
1006 		ConfMan.set("lastSave", "");
1007 
1008 	ConfMan.flushToDisk();
1009 
1010 	return result;
1011 }
1012 
saveGameState(int slot,const Common::String & desc,bool isAutosave)1013 Common::Error Ultima8Engine::saveGameState(int slot, const Common::String &desc, bool isAutosave) {
1014 	Common::Error result = Shared::UltimaEngine::saveGameState(slot, desc, isAutosave);
1015 
1016 	if (!isAutosave) {
1017 		if (result.getCode() == Common::kNoError)
1018 			ConfMan.setInt("lastSave", slot);
1019 		else
1020 			ConfMan.set("lastSave", "");
1021 	}
1022 
1023 	ConfMan.flushToDisk();
1024 
1025 	return result;
1026 }
1027 
saveGameStream(Common::WriteStream * stream,bool isAutosave)1028 Common::Error Ultima8Engine::saveGameStream(Common::WriteStream *stream, bool isAutosave) {
1029 	// Hack - don't save mouse over status for gumps
1030 	Gump *gump = _mouse->getMouseOverGump();
1031 	if (gump)
1032 		gump->onMouseLeft();
1033 
1034 	Gump *modalGump = _desktopGump->FindGump<ModalGump>();
1035 	if (modalGump)
1036 		modalGump->HideGump();
1037 
1038 	_mouse->pushMouseCursor();
1039 	_mouse->setMouseCursor(Mouse::MOUSE_PENTAGRAM);
1040 
1041 	// Redraw to indicate busy and for save thumbnail
1042 	paint();
1043 
1044 	if (modalGump)
1045 		modalGump->UnhideGump();
1046 
1047 	_saveCount++;
1048 
1049 	SavegameWriter *sgw = new SavegameWriter(stream);
1050 	Common::MemoryWriteStreamDynamic buf(DisposeAfterUse::YES);
1051 
1052 	_gameInfo->save(&buf);
1053 	sgw->writeFile("GAME", &buf);
1054 	buf.seek(0);
1055 
1056 	writeSaveInfo(&buf);
1057 	sgw->writeFile("INFO", &buf);
1058 	buf.seek(0);
1059 
1060 	_kernel->save(&buf);
1061 	sgw->writeFile("KERNEL", &buf);
1062 	buf.seek(0);
1063 
1064 	_objectManager->save(&buf);
1065 	sgw->writeFile("OBJECTS", &buf);
1066 	buf.seek(0);
1067 
1068 	_world->save(&buf);
1069 	sgw->writeFile("WORLD", &buf);
1070 	buf.seek(0);
1071 
1072 	_world->saveMaps(&buf);
1073 	sgw->writeFile("MAPS", &buf);
1074 	buf.seek(0);
1075 
1076 	_world->getCurrentMap()->save(&buf);
1077 	sgw->writeFile("CURRENTMAP", &buf);
1078 	buf.seek(0);
1079 
1080 	_ucMachine->saveStrings(&buf);
1081 	sgw->writeFile("UCSTRINGS", &buf);
1082 	buf.seek(0);
1083 
1084 	_ucMachine->saveGlobals(&buf);
1085 	sgw->writeFile("UCGLOBALS", &buf);
1086 	buf.seek(0);
1087 
1088 	_ucMachine->saveLists(&buf);
1089 	sgw->writeFile("UCLISTS", &buf);
1090 	buf.seek(0);
1091 
1092 	save(&buf);
1093 	sgw->writeFile("APP", &buf);
1094 	buf.seek(0);
1095 
1096 	sgw->finish();
1097 
1098 	delete sgw;
1099 
1100 	// Restore mouse over
1101 	if (gump) gump->onMouseOver();
1102 
1103 	pout << "Done" << Std::endl;
1104 
1105 	_mouse->popMouseCursor();
1106 
1107 	return Common::kNoError;
1108 }
1109 
resetEngine()1110 void Ultima8Engine::resetEngine() {
1111 	debugN(MM_INFO, "-- Resetting Engine --\n");
1112 
1113 	// kill music
1114 	if (_audioMixer) _audioMixer->reset();
1115 
1116 	// now, reset everything (order matters)
1117 	_world->reset();
1118 	_ucMachine->reset();
1119 	// ObjectManager, Kernel have to be last, because they kill
1120 	// all processes/objects
1121 	_objectManager->reset();
1122 	_kernel->reset();
1123 	_paletteManager->resetTransforms();
1124 
1125 	// Reset thet gumps
1126 	_desktopGump = nullptr;
1127 	_gameMapGump = nullptr;
1128 	_inverterGump = nullptr;
1129 
1130 	_textModes.clear();
1131 	MetaEngine::setTextInputActive(false);
1132 
1133 	// reset mouse cursor
1134 	_mouse->popAllCursors();
1135 	_mouse->pushMouseCursor();
1136 
1137 	_timeOffset = -(int32)Kernel::get_instance()->getFrameNum();
1138 	_inversion = 0;
1139 	_saveCount = 0;
1140 	_hasCheated = false;
1141 
1142 	debugN(MM_INFO, "-- Engine Reset --\n");
1143 }
1144 
setupCoreGumps()1145 void Ultima8Engine::setupCoreGumps() {
1146 	debugN(MM_INFO, "Setting up core game gumps...\n");
1147 
1148 	Rect dims;
1149 	_screen->GetSurfaceDims(dims);
1150 
1151 	debugN(MM_INFO, "Creating Desktop...\n");
1152 	_desktopGump = new DesktopGump(0, 0, dims.width(), dims.height());
1153 	_desktopGump->InitGump(0);
1154 	_desktopGump->MakeFocus();
1155 
1156 	if (GAME_IS_U8) {
1157 		debugN(MM_INFO, "Creating Inverter...\n");
1158 		_inverterGump = new InverterGump(0, 0, dims.width(), dims.height());
1159 		_inverterGump->InitGump(0);
1160 	}
1161 	debugN(MM_INFO, "Creating GameMapGump...\n");
1162 	_gameMapGump = new GameMapGump(0, 0, dims.width(), dims.height());
1163 	_gameMapGump->InitGump(0);
1164 
1165 	// TODO: clean this up
1166 	if (GAME_IS_U8) {
1167 		assert(_desktopGump->getObjId() == 256);
1168 		assert(_inverterGump->getObjId() == 257);
1169 		assert(_gameMapGump->getObjId() == 258);
1170 	}
1171 
1172 	for (uint16 i = 259; i < 384; ++i)
1173 		_objectManager->reserveObjId(i);
1174 }
1175 
newGame(int saveSlot)1176 bool Ultima8Engine::newGame(int saveSlot) {
1177 	debugN(MM_INFO, "Starting New Game (slot %d)... \n", saveSlot);
1178 
1179 	// First validate we still have a save file for the slot
1180 	if (saveSlot != -1) {
1181 		SaveStateDescriptor desc = getMetaEngine()->querySaveMetaInfos(_targetName.c_str(), saveSlot);
1182 		if (desc.getSaveSlot() != saveSlot)
1183 			saveSlot = -1;
1184 	}
1185 
1186 	resetEngine();
1187 
1188 	setupCoreGumps();
1189 
1190 	_game->startGame();
1191 
1192 	debugN(MM_INFO, "Create Camera...\n");
1193 	CameraProcess::SetCameraProcess(new CameraProcess(1)); // Follow Avatar
1194 
1195 	debugN(MM_INFO, "Create persistent Processes...\n");
1196 	if (GAME_IS_U8)
1197 		_avatarMoverProcess = new U8AvatarMoverProcess();
1198 	else
1199 		_avatarMoverProcess = new CruAvatarMoverProcess();
1200 	_kernel->addProcess(_avatarMoverProcess);
1201 
1202 	if (GAME_IS_U8)
1203 		_kernel->addProcess(new HealProcess());
1204 
1205 	_kernel->addProcess(new SchedulerProcess());
1206 
1207 	if (_audioMixer) _audioMixer->createProcesses();
1208 
1209 	//	av->teleport(40, 16240, 15240, 64); // central Tenebrae
1210 	//	av->teleport(3, 11391, 1727, 64); // docks, near gate
1211 	//	av->teleport(39, 16240, 15240, 64); // West Tenebrae
1212 	//	av->teleport(41, 12000, 15000, 64); // East Tenebrae
1213 	//	av->teleport(8, 14462, 15178, 48); // before entrance to Mythran's house
1214 	//	av->teleport(40, 13102,9474,48); // entrance to Mordea's throne room
1215 	//	av->teleport(54, 14783,5959,8); // shrine of the Ancient Ones; Hanoi
1216 	//	av->teleport(5, 5104,22464,48); // East road (tenebrae end)
1217 
1218 	if (GAME_IS_CRUSADER) {
1219 		_kernel->addProcess(new TargetReticleProcess());
1220 		_kernel->addProcess(new ItemSelectionProcess());
1221 		_kernel->addProcess(new CrosshairProcess());
1222 		_kernel->addProcess(new CycleProcess());
1223 		_kernel->addProcess(new SnapProcess());
1224 	}
1225 
1226 	_game->startInitialUsecode(saveSlot);
1227 
1228 	if (saveSlot == -1)
1229 		ConfMan.set("lastSave", "");
1230 
1231 	return true;
1232 }
1233 
syncSoundSettings()1234 void Ultima8Engine::syncSoundSettings() {
1235 	UltimaEngine::syncSoundSettings();
1236 
1237 	// Update music volume
1238 	AudioMixer *audioMixer = AudioMixer::get_instance();
1239 	MidiPlayer *midiPlayer = audioMixer ? audioMixer->getMidiPlayer() : nullptr;
1240 	if (midiPlayer)
1241 		midiPlayer->syncSoundSettings();
1242 }
1243 
applyGameSettings()1244 void Ultima8Engine::applyGameSettings() {
1245 	UltimaEngine::applyGameSettings();
1246 
1247 	bool fontOverride = ConfMan.getBool("font_override");
1248 	bool fontAntialiasing = ConfMan.getBool("font_antialiasing");
1249 
1250 	if (_fontOverride != fontOverride || _fontAntialiasing != fontAntialiasing) {
1251 		_fontOverride = fontOverride;
1252 		_fontAntialiasing = fontAntialiasing;
1253 
1254 		_fontManager->resetGameFonts();
1255 
1256 		// TODO: assign names to these fontnumbers somehow
1257 		_fontManager->loadTTFont(0, "Vera.ttf", 18, 0xFFFFFF, 0);
1258 		_fontManager->loadTTFont(1, "VeraBd.ttf", 12, 0xFFFFFF, 0);
1259 		// GameWidget's version number information:
1260 		_fontManager->loadTTFont(2, "Vera.ttf", 8, 0xA0A0A0, 0);
1261 
1262 		_gameData->setupFontOverrides();
1263 	}
1264 
1265 	_frameSkip = ConfMan.getBool("frameSkip");
1266 	_frameLimit = ConfMan.getBool("frameLimit");
1267 	_interpolate = ConfMan.getBool("interpolate");
1268 	_cheatsEnabled = ConfMan.getBool("cheat");
1269 }
1270 
openConfigDialog()1271 void Ultima8Engine::openConfigDialog() {
1272 	GUI::ConfigDialog dlg;
1273 	dlg.runModal();
1274 
1275 	g_system->applyBackendSettings();
1276 	applyGameSettings();
1277 	syncSoundSettings();
1278 }
1279 
loadGameStream(Common::SeekableReadStream * stream)1280 Common::Error Ultima8Engine::loadGameStream(Common::SeekableReadStream *stream) {
1281 	SavegameReader *sg = new SavegameReader(stream);
1282 	SavegameReader::State state = sg->isValid();
1283 	if (state == SavegameReader::SAVE_CORRUPT) {
1284 		Error("Invalid or corrupt savegame", "Error Loading savegame");
1285 		delete sg;
1286 		return Common::kReadingFailed;
1287 	}
1288 
1289 	if (state != SavegameReader::SAVE_VALID) {
1290 		Error("Unsupported savegame version", "Error Loading savegame");
1291 		delete sg;
1292 		return Common::kReadingFailed;
1293 	}
1294 
1295 	_mouse->pushMouseCursor();
1296 	_mouse->setMouseCursor(Mouse::MOUSE_PENTAGRAM);
1297 	_screen->BeginPainting();
1298 	_mouse->paint();
1299 	_screen->EndPainting();
1300 
1301 	Common::SeekableReadStream *ds;
1302 	GameInfo saveinfo;
1303 	ds = sg->getDataSource("GAME");
1304 	uint32 version = sg->getVersion();
1305 	bool ok = saveinfo.load(ds, version);
1306 
1307 	if (!ok) {
1308 		Error("Invalid or corrupt savegame: missing GameInfo", "Error Loading savegame");
1309 		delete sg;
1310 		return Common::kReadingFailed;
1311 	}
1312 
1313 	if (!_gameInfo->match(saveinfo)) {
1314 		Std::string message = "Game mismatch\n";
1315 		message += "Running _game: " + _gameInfo->getPrintDetails()  + "\n";
1316 		message += "Savegame    : " + saveinfo.getPrintDetails();
1317 
1318 #ifdef DEBUG
1319 		ConfMan.registerDefault("ignore_savegame_mismatch", true);
1320 		bool ignore = ConfMan.getBool("ignore_savegame_mismatch");
1321 
1322 		if (!ignore) {
1323 			error("%s", message.c_str());
1324 		}
1325 		perr << message << Std::endl;
1326 #else
1327 		Error(message, "Error Loading savegame");
1328 		delete sg;
1329 		return Common::kReadingFailed;
1330 #endif
1331 	}
1332 
1333 	resetEngine();
1334 
1335 	setupCoreGumps();
1336 
1337 	// and load everything back (order matters)
1338 	// for each entry, check that we read exactly the number of bytes
1339 	// expected - anything else suggests a corrupt save (or a bug)
1340 	bool totalok = true;
1341 
1342 	Std::string message;
1343 
1344 	// UCSTRINGS, UCGLOBALS, UCLISTS don't depend on anything else,
1345 	// so load these first
1346 	ds = sg->getDataSource("UCSTRINGS");
1347 	ok = _ucMachine->loadStrings(ds, version);
1348 	ok &= (ds->pos() == ds->size() && !ds->eos());
1349 	totalok &= ok;
1350 	pout << "UCSTRINGS: " << (ok ? "ok" : "failed") << Std::endl;
1351 	if (!ok) message += "UCSTRINGS: failed\n";
1352 	delete ds;
1353 
1354 	ds = sg->getDataSource("UCGLOBALS");
1355 	ok = _ucMachine->loadGlobals(ds, version);
1356 	ok &= (ds->pos() == ds->size() && !ds->eos());
1357 	totalok &= ok;
1358 	pout << "UCGLOBALS: " << (ok ? "ok" : "failed") << Std::endl;
1359 	if (!ok) message += "UCGLOBALS: failed\n";
1360 	delete ds;
1361 
1362 	ds = sg->getDataSource("UCLISTS");
1363 	ok = _ucMachine->loadLists(ds, version);
1364 	ok &= (ds->pos() == ds->size() && !ds->eos());
1365 	totalok &= ok;
1366 	pout << "UCLISTS: " << (ok ? "ok" : "failed") << Std::endl;
1367 	if (!ok) message += "UCLISTS: failed\n";
1368 	delete ds;
1369 
1370 	// KERNEL must be before OBJECTS, for the egghatcher
1371 	// KERNEL must be before APP, for the _avatarMoverProcess
1372 	ds = sg->getDataSource("KERNEL");
1373 	ok = _kernel->load(ds, version);
1374 	ok &= (ds->pos() == ds->size() && !ds->eos());
1375 	totalok &= ok;
1376 	pout << "KERNEL: " << (ok ? "ok" : "failed") << Std::endl;
1377 	if (!ok) message += "KERNEL: failed\n";
1378 	delete ds;
1379 
1380 	ds = sg->getDataSource("APP");
1381 	ok = load(ds, version);
1382 	ok &= (ds->pos() == ds->size() && !ds->eos());
1383 	totalok &= ok;
1384 	pout << "APP: " << (ok ? "ok" : "failed") << Std::endl;
1385 	if (!ok) message += "APP: failed\n";
1386 	delete ds;
1387 
1388 	// WORLD must be before OBJECTS, for the egghatcher
1389 	ds = sg->getDataSource("WORLD");
1390 	ok = _world->load(ds, version);
1391 	ok &= (ds->pos() == ds->size() && !ds->eos());
1392 	totalok &= ok;
1393 	pout << "WORLD: " << (ok ? "ok" : "failed") << Std::endl;
1394 	if (!ok) message += "WORLD: failed\n";
1395 	delete ds;
1396 
1397 	ds = sg->getDataSource("CURRENTMAP");
1398 	ok = _world->getCurrentMap()->load(ds, version);
1399 	ok &= (ds->pos() == ds->size() && !ds->eos());
1400 	totalok &= ok;
1401 	pout << "CURRENTMAP: " << (ok ? "ok" : "failed") << Std::endl;
1402 	if (!ok) message += "CURRENTMAP: failed\n";
1403 	delete ds;
1404 
1405 	ds = sg->getDataSource("OBJECTS");
1406 	ok = _objectManager->load(ds, version);
1407 	ok &= (ds->pos() == ds->size() && !ds->eos());
1408 	totalok &= ok;
1409 	pout << "OBJECTS: " << (ok ? "ok" : "failed") << Std::endl;
1410 	if (!ok) message += "OBJECTS: failed\n";
1411 	delete ds;
1412 
1413 	ds = sg->getDataSource("MAPS");
1414 	ok = _world->loadMaps(ds, version);
1415 	ok &= (ds->pos() == ds->size() && !ds->eos());
1416 	totalok &= ok;
1417 	pout << "MAPS: " << (ok ? "ok" : "failed") << Std::endl;
1418 	if (!ok) message += "MAPS: failed\n";
1419 	delete ds;
1420 
1421 	// Reset mouse cursor
1422 	_mouse->popAllCursors();
1423 	_mouse->pushMouseCursor();
1424 
1425 	/*
1426 	// In case of bugs, ensure persistent processes are around?
1427 	if (!TargetReticleProcess::get_instance())
1428 		_kernel->addProcess(new TargetReticleProcess());
1429 	if (!ItemSelectionProcess::get_instance())
1430 		_kernel->addProcess(new ItemSelectionProcess());
1431 	if (!CrosshairProcess::get_instance())
1432 		_kernel->addProcess(new CrosshairProcess());
1433 	if (!CycleProcess::get_instance())
1434 		_kernel->addProcess(new CycleProcess());
1435 	if (!SnapProcess::get_instance())
1436 		_kernel->addProcess(new SnapProcess());
1437 	 */
1438 
1439 	if (!totalok) {
1440 		Error(message, "Error Loading savegame");
1441 		delete sg;
1442 		return Common::kReadingFailed;
1443 	}
1444 
1445 	pout << "Done" << Std::endl;
1446 
1447 	delete sg;
1448 	return Common::kNoError;
1449 }
1450 
Error(Std::string message,Std::string title)1451 void Ultima8Engine::Error(Std::string message, Std::string title) {
1452 	if (title.empty()) title = "Error";
1453 
1454 	perr << title << ": " << message << Std::endl;
1455 
1456 	_errorMessage = message;
1457 	_errorTitle = title;
1458 }
1459 
getGump(uint16 gumpid)1460 Gump *Ultima8Engine::getGump(uint16 gumpid) {
1461 	return dynamic_cast<Gump *>(ObjectManager::get_instance()->
1462 		getObject(gumpid));
1463 }
1464 
addGump(Gump * gump)1465 void Ultima8Engine::addGump(Gump *gump) {
1466 	// TODO: At some point, this will have to _properly_ choose to
1467 	// which 'layer' to add the gump: inverted, scaled or neither.
1468 
1469 	assert(_desktopGump);
1470 
1471 	if (dynamic_cast<ShapeViewerGump *>(gump) || dynamic_cast<MiniMapGump *>(gump) ||
1472 		dynamic_cast<MessageBoxGump *>(gump)// ||
1473 		//(_fontOverrides && (dynamic_cast<BarkGump *>(gump) ||
1474 		//                dynamic_cast<AskGump *>(gump)))
1475 		) {
1476 		_desktopGump->AddChild(gump);
1477 	} else if (dynamic_cast<GameMapGump *>(gump)) {
1478 		if (GAME_IS_U8)
1479 			_inverterGump->AddChild(gump);
1480 		else
1481 			_desktopGump->AddChild(gump);
1482 	} else if (dynamic_cast<InverterGump *>(gump)) {
1483 		_desktopGump->AddChild(gump);
1484 	} else if (dynamic_cast<DesktopGump *>(gump)) {
1485 	} else {
1486 		_desktopGump->AddChild(gump);
1487 	}
1488 }
1489 
getGameTimeInSeconds()1490 uint32 Ultima8Engine::getGameTimeInSeconds() {
1491 	// 1 second per every 30 frames
1492 	return (Kernel::get_instance()->getFrameNum() + _timeOffset) / Kernel::FRAMES_PER_SECOND; // constant!
1493 }
1494 
moveKeyEvent()1495 void Ultima8Engine::moveKeyEvent() {
1496 	_moveKeyFrame = Kernel::get_instance()->getFrameNum();
1497 }
1498 
moveKeyDownRecently()1499 bool Ultima8Engine::moveKeyDownRecently() {
1500 	uint32 nowframe = Kernel::get_instance()->getFrameNum();
1501 	return (nowframe - _moveKeyFrame) < 2 * Kernel::FRAMES_PER_SECOND;
1502 }
1503 
save(Common::WriteStream * ws)1504 void Ultima8Engine::save(Common::WriteStream *ws) {
1505 	uint8 s = (_avatarInStasis ? 1 : 0);
1506 	ws->writeByte(s);
1507 
1508 	if (GAME_IS_CRUSADER) {
1509 		uint8 f = (_unkCrusaderFlag ? 1 : 0);
1510 		ws->writeByte(f);
1511 	}
1512 
1513 	int32 absoluteTime = Kernel::get_instance()->getFrameNum() + _timeOffset;
1514 	ws->writeUint32LE(static_cast<uint32>(absoluteTime));
1515 	ws->writeUint16LE(_avatarMoverProcess->getPid());
1516 
1517 	Palette *pal = PaletteManager::get_instance()->getPalette(PaletteManager::Pal_Game);
1518 	for (int i = 0; i < 12; i++) ws->writeUint16LE(pal->_matrix[i]);
1519 	ws->writeUint16LE(pal->_transform);
1520 
1521 	ws->writeUint16LE(static_cast<uint16>(_inversion));
1522 
1523 	ws->writeUint32LE(_saveCount);
1524 
1525 	uint8 c = (_hasCheated ? 1 : 0);
1526 	ws->writeByte(c);
1527 }
1528 
load(Common::ReadStream * rs,uint32 version)1529 bool Ultima8Engine::load(Common::ReadStream *rs, uint32 version) {
1530 	_avatarInStasis = (rs->readByte() != 0);
1531 
1532 	if (GAME_IS_CRUSADER) {
1533 		_unkCrusaderFlag  = (rs->readByte() != 0);
1534 		_cruStasis = false;
1535 	}
1536 
1537 	// no gump should be moused over after load
1538 	_mouse->resetMouseOverGump();
1539 
1540 	int32 absoluteTime = static_cast<int32>(rs->readUint32LE());
1541 	_timeOffset = absoluteTime - Kernel::get_instance()->getFrameNum();
1542 
1543 	uint16 amppid = rs->readUint16LE();
1544 	_avatarMoverProcess = dynamic_cast<AvatarMoverProcess *>(Kernel::get_instance()->getProcess(amppid));
1545 
1546 	int16 matrix[12];
1547 	for (int i = 0; i < 12; i++)
1548 		matrix[i] = rs->readUint16LE();
1549 
1550 	PaletteManager::get_instance()->transformPalette(PaletteManager::Pal_Game, matrix);
1551 	Palette *pal = PaletteManager::get_instance()->getPalette(PaletteManager::Pal_Game);
1552 	pal->_transform = static_cast<PalTransforms>(rs->readUint16LE());
1553 
1554 	_inversion = rs->readUint16LE();
1555 
1556 	_saveCount = rs->readUint32LE();
1557 
1558 	_hasCheated = (rs->readByte() != 0);
1559 
1560 	// Integrity checks
1561 	if (!_avatarMoverProcess) {
1562 		warning("No AvatarMoverProcess.  Corrupt savegame?");
1563 		return false;
1564 	}
1565 	if (pal->_transform >= Transform_Invalid) {
1566 		warning("Invalid palette transform %d.  Corrupt savegame?", static_cast<int>(pal->_transform));
1567 		return false;
1568 	}
1569 	if (_saveCount > 1024*1024) {
1570 		warning("Improbable savecount %d.  Corrupt savegame?", _saveCount);
1571 		return false;
1572 	}
1573 
1574 	return true;
1575 }
1576 
1577 //
1578 // Intrinsics
1579 //
1580 
I_avatarCanCheat(const uint8 *,unsigned int)1581 uint32 Ultima8Engine::I_avatarCanCheat(const uint8 * /*args*/,
1582 	unsigned int /*argsize*/) {
1583 	return Ultima8Engine::get_instance()->areCheatsEnabled() ? 1 : 0;
1584 }
1585 
1586 
I_makeAvatarACheater(const uint8 *,unsigned int)1587 uint32 Ultima8Engine::I_makeAvatarACheater(const uint8 * /*args*/,
1588 	unsigned int /*argsize*/) {
1589 	Ultima8Engine::get_instance()->makeCheater();
1590 	return 0;
1591 }
1592 
I_getCurrentTimerTick(const uint8 *,unsigned int)1593 uint32 Ultima8Engine::I_getCurrentTimerTick(const uint8 * /*args*/,
1594 	unsigned int /*argsize*/) {
1595 	// number of ticks of a 60Hz timer, with the default animrate of 30Hz
1596 	return Kernel::get_instance()->getTickNum();
1597 }
1598 
I_setAvatarInStasis(const uint8 * args,unsigned int argsize)1599 uint32 Ultima8Engine::I_setAvatarInStasis(const uint8 *args, unsigned int argsize) {
1600 	ARG_SINT16(stasis);
1601 	get_instance()->setAvatarInStasis(stasis != 0);
1602 	return 0;
1603 }
1604 
I_getAvatarInStasis(const uint8 *,unsigned int)1605 uint32 Ultima8Engine::I_getAvatarInStasis(const uint8 * /*args*/, unsigned int /*argsize*/) {
1606 	if (get_instance()->_avatarInStasis)
1607 		return 1;
1608 	else
1609 		return 0;
1610 }
1611 
I_setCruStasis(const uint8 * args,unsigned int argsize)1612 uint32 Ultima8Engine::I_setCruStasis(const uint8 *args, unsigned int argsize) {
1613 	get_instance()->setCruStasis(true);
1614 	return 0;
1615 }
1616 
I_clrCruStasis(const uint8 * args,unsigned int argsize)1617 uint32 Ultima8Engine::I_clrCruStasis(const uint8 *args, unsigned int argsize) {
1618 	get_instance()->setCruStasis(false);
1619 	return 0;
1620 }
1621 
I_getTimeInGameHours(const uint8 *,unsigned int)1622 uint32 Ultima8Engine::I_getTimeInGameHours(const uint8 * /*args*/,
1623 	unsigned int /*argsize*/) {
1624 	// 900 seconds per _game hour
1625 	return get_instance()->getGameTimeInSeconds() / 900;
1626 }
1627 
I_getUnkCrusaderFlag(const uint8 *,unsigned int)1628 uint32 Ultima8Engine::I_getUnkCrusaderFlag(const uint8 * /*args*/,
1629 	unsigned int /*argsize*/) {
1630 	return get_instance()->isUnkCrusaderFlag() ? 1 : 0;
1631 }
1632 
I_setUnkCrusaderFlag(const uint8 *,unsigned int)1633 uint32 Ultima8Engine::I_setUnkCrusaderFlag(const uint8 * /*args*/,
1634 	unsigned int /*argsize*/) {
1635 	get_instance()->setUnkCrusaderFlag(true);
1636 	return 0;
1637 }
1638 
I_clrUnkCrusaderFlag(const uint8 *,unsigned int)1639 uint32 Ultima8Engine::I_clrUnkCrusaderFlag(const uint8 * /*args*/,
1640 	unsigned int /*argsize*/) {
1641 	get_instance()->setUnkCrusaderFlag(false);
1642 	return 0;
1643 }
1644 
I_getTimeInMinutes(const uint8 *,unsigned int)1645 uint32 Ultima8Engine::I_getTimeInMinutes(const uint8 * /*args*/,
1646 	unsigned int /*argsize*/) {
1647 	// 60 seconds per minute
1648 	return get_instance()->getGameTimeInSeconds() / 60;
1649 }
1650 
I_getTimeInSeconds(const uint8 *,unsigned int)1651 uint32 Ultima8Engine::I_getTimeInSeconds(const uint8 * /*args*/,
1652 	unsigned int /*argsize*/) {
1653 	return get_instance()->getGameTimeInSeconds();
1654 }
1655 
I_setTimeInGameHours(const uint8 * args,unsigned int)1656 uint32 Ultima8Engine::I_setTimeInGameHours(const uint8 *args,
1657 	unsigned int /*argsize*/) {
1658 	ARG_UINT16(newhour);
1659 
1660 	// 1 _game hour per every 27000 frames
1661 	int32 absolute = newhour * 27000;
1662 	get_instance()->_timeOffset = absolute - Kernel::get_instance()->getFrameNum();
1663 
1664 	return 0;
1665 }
1666 
I_closeItemGumps(const uint8 * args,unsigned int)1667 uint32 Ultima8Engine::I_closeItemGumps(const uint8 *args, unsigned int /*argsize*/) {
1668 	Ultima8Engine *g = Ultima8Engine::get_instance();
1669 	g->getDesktopGump()->CloseItemDependents();
1670 
1671 	return 0;
1672 }
1673 
I_moveKeyDownRecently(const uint8 * args,unsigned int)1674 uint32 Ultima8Engine::I_moveKeyDownRecently(const uint8 *args, unsigned int /*argsize*/) {
1675 	Ultima8Engine *g = Ultima8Engine::get_instance();
1676 	return g->moveKeyDownRecently() ? 1 : 0;
1677 }
1678 
isDataRequired(Common::String & folder,int & majorVersion,int & minorVersion)1679 bool Ultima8Engine::isDataRequired(Common::String &folder, int &majorVersion, int &minorVersion) {
1680 	folder = "ultima8";
1681 	// Version 1: Initial release
1682 	// Version 2: Add data for Crusader games
1683 	majorVersion = 2;
1684 	minorVersion = 0;
1685 	return true;
1686 }
1687 
getScreen() const1688 Graphics::Screen *Ultima8Engine::getScreen() const {
1689 	Graphics::Screen *scr = dynamic_cast<Graphics::Screen *>(_screen->getRawSurface());
1690 	assert(scr);
1691 	return scr;
1692 }
1693 
showSplashScreen()1694 void Ultima8Engine::showSplashScreen() {
1695 	Image::PNGDecoder png;
1696 	Common::File f;
1697 
1698 	// Get splash _screen image
1699 	if (!f.open("data/pentagram.png") || !png.loadStream(f))
1700 		return;
1701 
1702 	// Blit the splash image to the _screen
1703 	Graphics::Screen *scr = Ultima8Engine::get_instance()->getScreen();
1704 	const Graphics::Surface *srcSurface = png.getSurface();
1705 
1706 	scr->transBlitFrom(*srcSurface, Common::Rect(0, 0, srcSurface->w, srcSurface->h),
1707 		Common::Rect(0, 0, scr->w, scr->h));
1708 	scr->update();
1709 	// Handle a single event to get the splash screen shown
1710 	Common::Event event;
1711 	_events->pollEvent(event);
1712 }
1713 
getMenuGump() const1714 Gump *Ultima8Engine::getMenuGump() const {
1715 	if (_textModes.empty())
1716 		return nullptr;
1717 
1718 	return dynamic_cast<Gump *>(_objectManager->getObject(_textModes.front()));
1719 }
1720 
1721 } // End of namespace Ultima8
1722 } // End of namespace Ultima
1723