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/scummsys.h"
24
25 #include "common/config-manager.h"
26 #include "common/debug.h"
27 #include "common/error.h"
28 #include "common/events.h"
29 #include "common/file.h"
30 #include "common/fs.h"
31 #include "common/system.h"
32
33 #include "engines/util.h"
34
35 #include "saga2/saga2.h"
36 #include "saga2/fta.h"
37
38 #include "saga2/actor.h"
39 #include "saga2/audio.h"
40 #include "saga2/band.h"
41 #include "saga2/beegee.h"
42 #include "saga2/calender.h"
43 #include "saga2/contain.h"
44 #include "saga2/dispnode.h"
45 #include "saga2/gdraw.h"
46 #include "saga2/imagcach.h"
47 #include "saga2/mouseimg.h"
48 #include "saga2/motion.h"
49 #include "saga2/music.h"
50 #include "saga2/panel.h"
51 #include "saga2/spelshow.h"
52 #include "saga2/tilemode.h"
53 #include "saga2/vpal.h"
54
55 namespace Saga2 {
56
57 void main_saga2();
58
59 Saga2Engine *g_vm;
60
Saga2Engine(OSystem * syst)61 Saga2Engine::Saga2Engine(OSystem *syst)
62 : Engine(syst) {
63 const Common::FSNode gameDataDir(ConfMan.get("path"));
64
65 // Don't forget to register your random source
66 _rnd = new Common::RandomSource("saga2");
67
68 g_vm = this;
69
70 _console = nullptr;
71 _renderer = nullptr;
72 _audio = nullptr;
73 _pal = nullptr;
74 _act = nullptr;
75 _calender = nullptr;
76 _tmm = nullptr;
77 _cnm = nullptr;
78
79 _bandList = nullptr;
80 _mouseInfo = nullptr;
81 _smkDecoder = nullptr;
82 _videoX = _videoY = 0;
83 _loadedWeapons = 0;
84
85 _gameRunning = true;
86 _autoAggression = true;
87 _autoWeapon = true;
88 _showNight = true;
89 _speechText = true;
90 _speechVoice = true;
91
92 _showPosition = false;
93 _showStats = false;
94 _teleportOnClick = false;
95 _teleportOnMap = false;
96
97 _indivControlsFlag = false;
98 _userControlsSetup = false;
99 _fadeDepth = 1;
100 _currentMapNum = 0;
101
102 SearchMan.addSubDirectoryMatching(gameDataDir, "res");
103 SearchMan.addSubDirectoryMatching(gameDataDir, "dos/drivers"); // For Miles Sound files
104 SearchMan.addSubDirectoryMatching(gameDataDir, "drivers");
105
106 _loadedWeapons = 0;
107
108 _imageCache = new CImageCache;
109 _mTaskList = new MotionTaskList;
110 _bandList = new BandList();
111 _mainDisplayList = new DisplayNodeList;
112 _activeSpells = new SpellDisplayList(kMaxActiveSpells);
113 _pointer = new gMousePointer(_mainPort);
114 _activeRegionList = new ActiveRegion[kPlayerActors];
115 _toolBase = new gToolBase;
116 _properties = new Properties;
117 _aTaskList = new TileActivityTaskList;
118 _grandMasterFTA = new Deejay;
119
120 _edpList = nullptr;
121 _sdpList = nullptr;
122 _tileImageBanks = nullptr;
123 _stackList = nullptr;
124 _taskList = nullptr;
125 _frate = nullptr;
126 _lrate = nullptr;
127 }
128
~Saga2Engine()129 Saga2Engine::~Saga2Engine() {
130 debug("Saga2Engine::~Saga2Engine");
131
132 freeExeResources();
133
134 // Dispose your resources here
135 delete _rnd;
136 delete _renderer;
137 delete _pal;
138 delete _act;
139 delete _calender;
140 delete _tmm;
141 delete _cnm;
142
143 delete _imageCache;
144 delete _mTaskList;
145 delete _bandList;
146 delete _mainDisplayList;
147 delete _activeSpells;
148 delete _pointer;
149 delete[] _activeRegionList;
150 delete _toolBase;
151 delete _properties;
152 delete _aTaskList;
153 delete _grandMasterFTA;
154 }
155
run()156 Common::Error Saga2Engine::run() {
157 // Initialize graphics using following:
158 initGraphics(640, 480);
159
160 _console = new Console(this);
161 setDebugger(_console);
162
163 _renderer = new Renderer();
164
165 _pal = new PaletteManager;
166 _act = new ActorManager;
167 _calender = new CalenderTime;
168 _tmm = new TileModeManager;
169 _cnm = new ContainerManager;
170
171 readConfig();
172
173 loadExeResources();
174
175 main_saga2();
176
177 return Common::kNoError;
178 }
179
hasFeature(EngineFeature f) const180 bool Saga2Engine::hasFeature(EngineFeature f) const {
181 return
182 (f == kSupportsReturnToLauncher) ||
183 (f == kSupportsLoadingDuringRuntime) ||
184 (f == kSupportsSavingDuringRuntime) ||
185 (f == kSupportsSubtitleOptions);
186 }
187
loadGameStream(Common::SeekableReadStream * stream)188 Common::Error Saga2Engine::loadGameStream(Common::SeekableReadStream *stream) {
189 Common::Serializer s(stream, nullptr);
190 syncGameStream(s);
191 return Common::kNoError;
192 }
193
saveGameStream(Common::WriteStream * stream,bool isAutosave)194 Common::Error Saga2Engine::saveGameStream(Common::WriteStream *stream, bool isAutosave) {
195 Common::Serializer s(nullptr, stream);
196 syncGameStream(s);
197 return Common::kNoError;
198 }
199
getSavegameFile(int slot)200 Common::String Saga2Engine::getSavegameFile(int slot) {
201 return getMetaEngine()->getSavegameFile(slot, _targetName.c_str());
202 }
203
saveGameState(int slot,const Common::String & desc,bool isAutosave)204 Common::Error Saga2Engine::saveGameState(int slot, const Common::String &desc, bool isAutosave) {
205 pauseTimer();
206
207 Common::OutSaveFile *outS = getSaveFileManager()->openForSaving(getSavegameFile(slot), false);
208 if (!outS)
209 return Common::kCreatingFileFailed;
210
211 saveGame(outS, desc);
212
213 outS->write("SCVM", 4);
214 CHUNK_BEGIN;
215 uint32 pos = outS->pos() + 4;
216
217 _renderer->saveBackBuffer(kBeforeTakingThumbnail);
218
219 if (_renderer->hasSavedBackBuffer(kBeforeOpeningMenu))
220 _renderer->popSavedBackBuffer(kBeforeOpeningMenu);
221
222 getMetaEngine()->appendExtendedSaveToStream(out, g_vm->getTotalPlayTime() / 1000, desc, isAutosave, pos);
223
224 _renderer->popSavedBackBuffer(kBeforeTakingThumbnail);
225 CHUNK_END;
226
227 outS->finalize();
228
229 delete outS;
230
231 resumeTimer();
232
233 return Common::kNoError;
234 }
235
loadGameState(int slot)236 Common::Error Saga2Engine::loadGameState(int slot) {
237 loadGame(slot);
238
239 return Common::kNoError;
240 }
241
syncSoundSettings()242 void Saga2Engine::syncSoundSettings() {
243 Engine::syncSoundSettings();
244
245 _speechText = true;
246
247 if (ConfMan.hasKey("subtitles"))
248 _speechText = ConfMan.getBool("subtitles");
249
250 _speechVoice = true;
251
252 if (ConfMan.hasKey("speech_mute"))
253 _speechVoice = !ConfMan.getBool("speech_mute");
254
255 if (_audio)
256 _audio->_music->syncSoundSettings();
257 }
258
syncGameStream(Common::Serializer & s)259 void Saga2Engine::syncGameStream(Common::Serializer &s) {
260 // Use methods of Serializer to save/load fields
261 int dummy = 0;
262 s.syncAsUint16LE(dummy);
263 }
264
clamp(int32 a,int32 val,int32 c)265 int32 clamp(int32 a, int32 val, int32 c) {
266 if (val < a) return a;
267 if (val > c) return c;
268 return val;
269 }
270
271 gFont Onyx10Font;
272 gFont Plate18Font;
273 gFont Helv11Font;
274 gFont Amber13Font;
275 gFont ThinFix8Font;
276 gFont Script10Font;
277
278 uint32 loadingWindowWidth = 640;
279 uint32 loadingWindowHeight = 480;
280
281 uint8 *loadingWindowPalette;
282 uint8 *loadingWindowData;
283
284 uint8 *ColorMapRanges;
285
286 uint8 *closeBx1ImageData;
287 uint8 *closeBx2ImageData;
288 uint8 *usePtrImageData;
289 uint8 *xPointerImageData;
290 uint8 *arrowImageData;
291 uint8 *grabPtrImageData;
292 uint8 *attakPtrImageData;
293 uint8 *centerActorIndicatorImageData;
294 uint8 *pgUpImageData;
295 uint8 *pgDownImageData;
296 uint8 *pgLeftImageData;
297 uint8 *pgRightImageData;
298 uint8 *autoWalkImageData;
299 uint8 *gaugeImageData;
300
loadFont(Common::File & file,gFont * font,uint32 offset)301 static void loadFont(Common::File &file, gFont *font, uint32 offset) {
302 file.seek(offset);
303
304 font->height = file.readUint16LE();
305 font->baseLine = file.readUint16LE();
306 font->rowMod = file.readUint16LE();
307
308 for (int i = 0; i < 256; i++)
309 font->charXOffset[i] = file.readUint16LE();
310
311 file.read(font->charWidth, 256);
312 file.read(font->charKern, 256);
313 file.read(font->charSpace, 256);
314
315 uint size = font->height * font->rowMod;
316
317 font->fontdata = (byte *)malloc(size);
318 file.read(font->fontdata, size);
319 }
320
321 struct dataChunks {
322 uint8 **ptr;
323 uint32 offset;
324 uint32 size;
325 } chunks[] = {
326 { (uint8 **)&Onyx10Font, 0x004F7258, 0 },
327 { (uint8 **)&Plate18Font, 0x004F7EE0, 0 },
328 { (uint8 **)&Helv11Font, 0x004F9F30, 0 },
329 { (uint8 **)&Amber13Font, 0x004FAC60, 0 },
330 { (uint8 **)&ThinFix8Font, 0x004FC210, 0 },
331 { (uint8 **)&Script10Font, 0x004FCD18, 0 },
332 { &loadingWindowPalette, 0x004A2600, 1024 },
333 { &loadingWindowData, 0x004A2A00, 307200 },
334 { &ColorMapRanges, 0x004EDC20, 1584 },
335 { &closeBx1ImageData, 0x004EE2B8, 144 },
336 { &closeBx2ImageData, 0x004EE348, 144 },
337 { &usePtrImageData, 0x004EE3D8, 232 },
338 { &xPointerImageData, 0x004EE4C0, 232 },
339 { &arrowImageData, 0x004EE5A8, 192 },
340 { &grabPtrImageData, 0x004EE668, 208 },
341 { &attakPtrImageData, 0x004EE738, 536 },
342 { ¢erActorIndicatorImageData,0x004EE950, 96 },
343 { &pgUpImageData, 0x004EE9B0, 256 },
344 { &pgDownImageData, 0x004EEAB0, 256 },
345 { &pgLeftImageData, 0x004EEBB0, 256 },
346 { &pgRightImageData, 0x004EECB0, 256 },
347 { &autoWalkImageData, 0x004EEDB0, 228 },
348 { &gaugeImageData, 0x004EF257, 241 },
349 { NULL, 0, 0 }
350 };
351
loadExeResources()352 void Saga2Engine::loadExeResources() {
353 Common::File exe;
354 const uint32 offset = 0x4F6D90 - 0xF4990;
355
356 if (!(exe.open("win/fta2win.exe") || exe.open("fta2win.exe")))
357 error("FTA2WIN.EXE file is missing");
358
359 if (exe.size() != 1093120)
360 error("Incorrect FTA2WIN.EXE file size. Expected is 1093120");
361
362 for (int i = 0; chunks[i].ptr; i++) {
363 if (chunks[i].size == 0) { // Font
364 loadFont(exe, (gFont *)chunks[i].ptr, chunks[i].offset - offset);
365 } else {
366 *chunks[i].ptr = (uint8 *)malloc(chunks[i].size);
367 exe.seek(chunks[i].offset - offset);
368 exe.read(*chunks[i].ptr, chunks[i].size);
369 }
370 }
371
372 initCursors();
373
374 exe.close();
375 }
376
freeExeResources()377 void Saga2Engine::freeExeResources() {
378 for (int i = 0; chunks[i].ptr; i++)
379 if (chunks[i].size == 0) // Font
380 free(((gFont *)chunks[i].ptr)->fontdata);
381 else
382 free(*chunks[i].ptr);
383
384 freeCursors();
385 }
386
readConfig()387 void Saga2Engine::readConfig() {
388 _autoWeapon = true;
389
390 if (ConfMan.hasKey("auto_weapon"))
391 _autoWeapon = ConfMan.getBool("auto_weapon");
392
393 _autoAggression = true;
394
395 if (ConfMan.hasKey("auto_aggression"))
396 _autoAggression = ConfMan.getBool("auto_aggression");
397
398 _showNight = true;
399
400 if (ConfMan.hasKey("show_night"))
401 _showNight = ConfMan.getBool("show_night");
402
403 syncSoundSettings();
404 }
405
saveConfig()406 void Saga2Engine::saveConfig() {
407 ConfMan.flushToDisk();
408 }
409
410 } // End of namespace Saga2
411