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 	{ &centerActorIndicatorImageData,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