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