1 /*
2  *  Copyright (C) 2000-2013  The Exult Team
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17  */
18 
19 #ifdef HAVE_CONFIG_H
20 #  include <config.h>
21 #endif
22 
23 #include "gamewin.h"
24 #include "shapeid.h"
25 #include "vgafile.h"
26 #include "fontvga.h"
27 #include "fnames.h"
28 #include "game.h"
29 #include "Configuration.h"
30 #include "data/exult_bg_flx.h"
31 #include "data/exult_si_flx.h"
32 #include "utils.h"
33 #include "Flex.h"
34 #include "u7drag.h"
35 #include "U7file.h"
36 #include "U7fileman.h"
37 #include "exceptions.h"
38 #include "miscinf.h"
39 #include <fstream>
40 #include <memory>
41 #include <vector>
42 #include <utility>
43 
44 using std::cerr;
45 using std::cout;
46 using std::endl;
47 using std::string;
48 using std::vector;
49 using std::pair;
50 using std::unique_ptr;
51 using std::make_unique;
52 
53 Shape_manager *Shape_manager::instance = nullptr;
54 
55 /*
56  *  Singletons:
57  */
58 Game_window *Game_singletons::gwin = nullptr;
59 Game_map *Game_singletons::gmap = nullptr;
60 Effects_manager *Game_singletons::eman = nullptr;
61 Shape_manager *Game_singletons::sman = nullptr;
62 Usecode_machine *Game_singletons::ucmachine = nullptr;
63 Game_clock *Game_singletons::gclock = nullptr;
64 Palette *Game_singletons::pal = nullptr;
65 Gump_manager *Game_singletons::gumpman = nullptr;
66 Party_manager *Game_singletons::partyman = nullptr;
67 
init(Game_window * g)68 void Game_singletons::init(
69     Game_window *g
70 ) {
71 	gwin = g;
72 	gmap = g->get_map();
73 	eman = g->get_effects();
74 	sman = Shape_manager::get_instance();
75 	ucmachine = g->get_usecode();
76 	gclock = g->get_clock();
77 	pal = g->get_pal();
78 	gumpman = g->get_gump_man();
79 	partyman = g->get_party_man();
80 }
81 
82 /*
83  *  Create shape manager.
84  */
Shape_manager()85 Shape_manager::Shape_manager(
86 )  {
87 	assert(instance == nullptr);
88 	instance = this;
89 }
90 
91 /*
92  *  Read in shape-file info.
93  */
94 
read_shape_info()95 void Shape_manager::read_shape_info(
96 ) {
97 	// Want space for extra shapes if BG multiracial enabled.
98 	shapes.init();
99 	// Read in shape information.
100 	shapes.read_info(Game::get_game_type(), Game::is_editing());
101 	// Fixup Avatar shapes (1024-1035 in default SI).
102 	const Shape_info &male = shapes.get_info(Shapeinfo_lookup::GetMaleAvShape());
103 	const Shape_info &female = shapes.get_info(Shapeinfo_lookup::GetFemaleAvShape());
104 
105 	vector<Skin_data> *skins = Shapeinfo_lookup::GetSkinList();
106 	for (auto& skin : *skins) {
107 		if (skin.copy_info) {
108 			shapes.copy_info(skin.shape_num, skin.is_female ? female : male);
109 			shapes.copy_info(skin.naked_shape, skin.is_female ? female : male);
110 		}
111 	}
112 }
113 
114 /*
115  *  Load files.
116  */
117 
load()118 void Shape_manager::load(
119 ) {
120 	shapes.reset_imports();
121 
122 	// Determine some colors based on the default palette
123 	Palette pal;
124 	// could throw!
125 	pal.load(PALETTES_FLX, PATCH_PALETTES, 0);
126 	// Get a bright green.
127 	special_pixels[POISON_PIXEL] = pal.find_color(4, 63, 4);
128 	// Get a light gray.
129 	special_pixels[PROTECT_PIXEL] = pal.find_color(62, 62, 55);
130 	// Yellow for cursed.
131 	special_pixels[CURSED_PIXEL] = pal.find_color(62, 62, 5);
132 	// Light blue for charmed.
133 	special_pixels[CHARMED_PIXEL] = pal.find_color(30, 40, 63);
134 	// Red for hit in battle.
135 	special_pixels[HIT_PIXEL] = pal.find_color(63, 4, 4);
136 	// Purple for paralyze.
137 	special_pixels[PARALYZE_PIXEL] = pal.find_color(49, 27, 49);
138 	// Black for ShortcutBar_gump
139 	special_pixels[BLACK_PIXEL] = pal.find_color(0, 0, 0);
140 
141 	files[SF_GUMPS_VGA].load(GUMPS_VGA, PATCH_GUMPS, true);
142 
143 	if (!files[SF_PAPERDOL_VGA].load(*Shapeinfo_lookup::GetPaperdollSources())) {
144 		if (GAME_SI)
145 			gwin->abort("Can't open 'paperdol.vga' file.");
146 		else if (GAME_BG) // NOT for devel. games.
147 			std::cerr << "Couldn't open SI 'paperdol.vga'." << std::endl
148 			          << "Support for SI Paperdolls in BG is disabled." << std::endl;
149 		can_have_paperdolls = false;
150 	} else
151 		can_have_paperdolls = true;
152 
153 	if (GAME_SI)
154 		got_si_shapes = true;
155 	else if (GAME_BG) {
156 		// Source for importing SI data.
157 		pair<string, int> source;
158 
159 		vector<pair<int, int> > *imports;
160 		if (can_have_paperdolls) {  // Do this only if SI paperdol.vga was found.
161 			source = pair<string, int>(string("<SERPENT_STATIC>/gumps.vga"), -1);
162 			// Gump shapes to import from SI.
163 			imports = Shapeinfo_lookup::GetImportedGumpShapes();
164 
165 			if (!imports->empty())
166 				can_have_paperdolls = files[SF_GUMPS_VGA].import_shapes(source, *imports);
167 			else
168 				can_have_paperdolls = false;
169 
170 			if (can_have_paperdolls)
171 				std::cout << "Support for SI Paperdolls is enabled." << std::endl;
172 			else
173 				std::cerr << "Couldn't open SI 'gumps.vga'." << std::endl
174 				          << "Support for SI Paperdolls in BG is disabled." << std::endl;
175 		}
176 
177 		source = pair<string, int>(string("<SERPENT_STATIC>/shapes.vga"), -1);
178 		// Skin shapes to import from SI.
179 		imports = Shapeinfo_lookup::GetImportedSkins();
180 		if (!imports->empty())
181 			got_si_shapes = shapes.import_shapes(source, *imports);
182 		else
183 			got_si_shapes = false;
184 
185 		if (got_si_shapes)
186 			std::cout << "Support for SI Multiracial Avatars is enabled." << std::endl;
187 		else
188 			std::cerr << "Couldn't open SI 'shapes.vga'." << std::endl
189 			          << "Support for SI Multiracial Avatars is disabled." << std::endl;
190 	}
191 
192 	files[SF_SPRITES_VGA].load(SPRITES_VGA, PATCH_SPRITES);
193 	if (GAME_SIB) {
194 		// Lets try to import shape 0 of sprites.vga from BG or SI.
195 		pair<string, int> sourcebg(string("<ULTIMA7_STATIC>/sprites.vga"), -1);
196 		pair<string, int> sourcesi(string("<SERPENT_STATIC>/sprites.vga"), -1);
197 
198 		vector<pair<int, int> > imports;
199 		imports.emplace_back(0, 0);
200 		if (U7exists(sourcesi.first.c_str()))
201 			files[SF_SPRITES_VGA].import_shapes(sourcesi, imports);
202 		else if (U7exists(sourcebg.first.c_str()))
203 			files[SF_SPRITES_VGA].import_shapes(sourcebg, imports);
204 		else {
205 				// Create lots of pixel frames.
206 			Shape *shp = files[SF_SPRITES_VGA].new_shape(0);
207 			unsigned char whitepix = 118;
208 			for (int ii = 0; ii < 28; ii++) {
209 				shp->add_frame(std::make_unique<Shape_frame>(&whitepix, 1, 1, 0, 0, false), ii);
210 			}
211 		}
212 	}
213 
214 	vector<pair<string, int> > source;
215 	source.emplace_back(FACES_VGA, -1);
216 	if (GAME_BG) {
217 		// Multiracial faces.
218 		const str_int_pair &resource = game->get_resource("files/mrfacesvga");
219 		source.emplace_back(resource.str, resource.num);
220 	}
221 	source.emplace_back(PATCH_FACES, -1);
222 	files[SF_FACES_VGA].load(source);
223 
224 	files[SF_EXULT_FLX].load(BUNDLE_CHECK(BUNDLE_EXULT_FLX, EXULT_FLX));
225 
226 	const char *gamedata = game->get_resource("files/gameflx").str;
227 	std::cout << "Loading " << gamedata << "..." << std::endl;
228 	files[SF_GAME_FLX].load(gamedata);
229 
230 	read_shape_info();
231 
232 	fonts = make_unique<Fonts_vga_file>();
233 	fonts->init();
234 
235 	// Get translucency tables.
236 	unique_ptr<unsigned char[]> ptr; // We will delete THIS at the end, not blends!
237 	// ++++TODO: Make this file editable in ES.
238 	{
239 		auto blendsflexspec = GAME_BG
240 		                       ? File_spec(BUNDLE_CHECK(BUNDLE_EXULT_BG_FLX, EXULT_BG_FLX), EXULT_BG_FLX_BLENDS_DAT)
241 		                       : File_spec(BUNDLE_CHECK(BUNDLE_EXULT_SI_FLX, EXULT_SI_FLX), EXULT_SI_FLX_BLENDS_DAT);
242 		U7multiobject in(BLENDS, blendsflexspec, PATCH_BLENDS, 0);
243 		size_t len;
244 		ptr = in.retrieve(len);
245 	}
246 	unsigned char *blends;
247 	size_t nblends;
248 	if (!ptr) {
249 		// All else failed.
250 		// Note: the files bundled in exult_XX.flx contain these values.
251 		// They are "good" enough, but there is probably room for
252 		// improvement.
253 		static unsigned char hard_blends[4 * 17] = {
254 			208, 216, 224, 192,    136, 44, 148, 198,    248, 252, 80, 211,
255 			144, 148, 252, 247,     64, 216, 64, 201,    204, 60, 84, 140,
256 			144, 40, 192, 128,     96, 40, 16, 128,    100, 108, 116, 192,
257 			68, 132, 28, 128,    255, 208, 48, 64,     28, 52, 255, 128,
258 			8, 68,  0, 128,    255,  8,  8, 118,    255, 244, 248, 128,
259 			56, 40, 32, 128,    228, 224, 214, 82
260 		};
261 		blends = hard_blends;
262 		nblends = 17;
263 	} else {
264 		blends = ptr.get();
265 		nblends = *blends++;
266 	}
267 	xforms.resize(nblends);
268 	const size_t nxforms = nblends;
269 	// ++++TODO: Make this file editable in ES.
270 	if (U7exists(XFORMTBL) || U7exists(PATCH_XFORMS)) {
271 		// Read in translucency tables.
272 		U7multifile xformfile(XFORMTBL, PATCH_XFORMS);
273 		size_t nobjs = std::min(xformfile.number_of_objects(), nblends);  // Limit by blends.
274 		for (size_t i = 0; i < nobjs; i++) {
275 			auto ds = xformfile.retrieve(i);
276 			if (!ds.good()) {
277 				// No XForm data at all. Make this XForm into an
278 				// identity transformation.
279 				for (size_t j = 0; j < sizeof(xforms[0].colors); j++)
280 					xforms[nxforms - 1 - i].colors[j] = j;
281 			} else {
282 				ds.read(xforms[nxforms - 1 - i].colors, sizeof(xforms[0].colors));
283 			}
284 		}
285 	} else {            // Create algorithmically.
286 		gwin->get_pal()->load(PALETTES_FLX, PATCH_PALETTES, 0);
287 		for (size_t i = 0; i < nxforms; i++) {
288 			gwin->get_pal()->create_trans_table(blends[4 * i + 0] / 4,
289 			                                    blends[4 * i + 1] / 4,
290 			                                    blends[4 * i + 2] / 4,
291 			                                    blends[4 * i + 3],
292 			                                    xforms[i].colors);
293 		}
294 	}
295 
296 	invis_xform = &xforms[nxforms - 1 - 0];   // ->entry 0.
297 }
298 
299 
300 // Read in files needed to display gumps.
load_gumps_minimal()301 bool Shape_manager::load_gumps_minimal() {
302 	try {
303 		if (!files[SF_GUMPS_VGA].load(GUMPS_VGA, PATCH_GUMPS, true)) {
304 			std::cerr << "Couldn't open 'gumps.vga'." << std::endl;
305 			return false;
306 		}
307 	} catch (exult_exception &ex) {
308 		std::cerr << ex.what() << std::endl;
309 		return false;
310 	}
311 
312 	try {
313 		if (!files[SF_EXULT_FLX].load(BUNDLE_CHECK(BUNDLE_EXULT_FLX, EXULT_FLX))) {
314 			std::cerr << "Couldn't open 'exult.flx'." << std::endl;
315 			return false;
316 		}
317 	} catch (exult_exception &ex) {
318 		std::cerr << ex.what() << std::endl;
319 		return false;
320 	}
321 
322 	//if (fonts) delete fonts;
323 	//fonts = new Fonts_vga_file();
324 	//fonts->init();
325 
326 	return true;
327 }
328 
329 /*
330  *  Reload one of the shape files (msg. from ExultStudio).
331  */
332 
reload_shapes(int dragtype)333 void Shape_manager::reload_shapes(
334     int dragtype            // Type from u7drag.h.
335 ) {
336 	U7FileManager::get_ptr()->reset();  // Cache no longer valid.
337 	switch (dragtype) {
338 	case U7_SHAPE_SHAPES:
339 		read_shape_info();
340 		// ++++Reread text?
341 		break;
342 	case U7_SHAPE_GUMPS:
343 		files[SF_GUMPS_VGA].load(GUMPS_VGA, PATCH_GUMPS);
344 		break;
345 	case U7_SHAPE_FONTS:
346 		fonts->init();
347 		break;
348 	case U7_SHAPE_FACES:
349 		files[SF_FACES_VGA].load(FACES_VGA, PATCH_FACES);
350 		break;
351 	case U7_SHAPE_SPRITES:
352 		files[SF_SPRITES_VGA].load(SPRITES_VGA, PATCH_SPRITES);
353 		break;
354 	case U7_SHAPE_PAPERDOL:
355 		files[SF_PAPERDOL_VGA].load(PAPERDOL, PATCH_PAPERDOL);
356 		break;
357 	default:
358 		cerr << "Type not supported:  " << dragtype << endl;
359 		break;
360 	}
361 }
362 
363 /*
364  *  Just reload info. files.
365  */
366 
reload_shape_info()367 void Shape_manager::reload_shape_info(
368 ) {
369 	shapes.reload_info(Game::get_game_type());
370 }
371 
372 /*
373  *  Clean up.
374  */
~Shape_manager()375 Shape_manager::~Shape_manager() {
376 	assert(this == instance);
377 	instance = nullptr;
378 }
379 
380 /*
381  *  Text-drawing methods:
382  */
paint_text_box(int fontnum,const char * text,int x,int y,int w,int h,int vert_lead,bool pbreak,bool center,int shading,Cursor_info * cursor)383 int Shape_manager::paint_text_box(int fontnum, const char *text,
384                                   int x, int y, int w, int h, int vert_lead, bool pbreak,
385                                   bool center, int shading, Cursor_info *cursor) {
386 	if (shading >= 0)
387 		gwin->get_win()->fill_translucent8(
388 		    0, w, h, x, y, xforms[shading]);
389 	return fonts->paint_text_box(gwin->get_win()->get_ib8(),
390 	                             fontnum, text, x, y, w, h, vert_lead, pbreak, center, cursor);
391 }
paint_text(int fontnum,const char * text,int xoff,int yoff)392 int Shape_manager::paint_text(int fontnum, const char *text,
393                               int xoff, int yoff) {
394 	return fonts->paint_text(gwin->get_win()->get_ib8(), fontnum, text,
395 	                         xoff, yoff);
396 }
paint_text(int fontnum,const char * text,int textlen,int xoff,int yoff)397 int Shape_manager::paint_text(int fontnum, const char *text, int textlen,
398                               int xoff, int yoff) {
399 	return fonts->paint_text(gwin->get_win()->get_ib8(), fontnum,
400 	                         text, textlen, xoff, yoff);
401 }
402 
get_text_width(int fontnum,const char * text)403 int Shape_manager::get_text_width(int fontnum, const char *text) {
404 	return fonts->get_text_width(fontnum, text);
405 }
get_text_width(int fontnum,const char * text,int textlen)406 int Shape_manager::get_text_width(int fontnum, const char *text, int textlen) {
407 	return fonts->get_text_width(fontnum, text, textlen);
408 }
get_text_height(int fontnum)409 int Shape_manager::get_text_height(int fontnum) {
410 	return fonts->get_text_height(fontnum);
411 }
get_text_baseline(int fontnum)412 int Shape_manager::get_text_baseline(int fontnum) {
413 	return fonts->get_text_baseline(fontnum);
414 }
415 
find_cursor(int fontnum,const char * text,int x,int y,int w,int h,int cx,int cy,int vert_lead)416 int Shape_manager::find_cursor(int fontnum, const char *text, int x, int y,
417                                int w, int h, int cx, int cy, int vert_lead) {
418 	return fonts->find_cursor(fontnum, text, x, y, w, h, cx, cy,
419 	                          vert_lead);
420 }
421 
get_font(int fontnum)422 Font *Shape_manager::get_font(int fontnum) {
423 	return fonts->get_font(fontnum);
424 }
425 
426 
427 /*
428  *  Read in shape.
429  */
cache_shape() const430 Shape_frame *ShapeID::cache_shape() const {
431 	if (framenum == -1) return nullptr;
432 
433 	has_trans = false;
434 	if (!shapefile) {
435 		// Special case.
436 		shape = sman->shapes.get_shape(shapenum, framenum);
437 		has_trans = sman->shapes.get_info(shapenum).has_translucency();
438 	} else if (shapefile < SF_OTHER) {
439 		shape = sman->files[static_cast<int>(shapefile)].get_shape(
440 		            shapenum, framenum);
441 		if (shapefile == SF_SPRITES_VGA)
442 			has_trans = true;
443 	} else {
444 		std::cerr << "Error! Wrong ShapeFile!" << std::endl;
445 		return nullptr;
446 	}
447 	return shape;
448 }
449 
get_num_frames() const450 int ShapeID::get_num_frames() const {
451 	if (!shapefile)
452 		return sman->shapes.get_num_frames(shapenum);
453 	else if (shapefile < SF_OTHER) {
454 		if (!sman->files[static_cast<int>(shapefile)].is_good())
455 			return 0;
456 		return sman->files[static_cast<int>(shapefile)].get_num_frames(shapenum);
457 	}
458 	std::cerr << "Error! Wrong ShapeFile!" << std::endl;
459 	return 0;
460 }
461 
is_frame_empty() const462 bool ShapeID::is_frame_empty() const {
463 	auto *const shp = get_shape();
464 	return shp == nullptr || shp->is_empty();
465 }
466