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