1 /*
2  *  miscinf.cc - Information about several previously-hardcoded shape data.
3  *
4  *  Copyright (C) 2006  The Exult Team
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19  */
20 
21 #ifdef HAVE_CONFIG_H
22 #  include <config.h>
23 #endif
24 
25 #include "exult_constants.h"
26 #include "miscinf.h"
27 #include "game.h"
28 #include "utils.h"
29 #include "msgfile.h"
30 #include "U7file.h"
31 #include "databuf.h"
32 #include "shapeid.h"
33 #include "gamewin.h"
34 #include "ucmachine.h"
35 #include "actors.h"
36 #include "ignore_unused_variable_warning.h"
37 
38 #include <iomanip>
39 #include <iostream>
40 #include <fstream>
41 #include <map>
42 #include <sstream>
43 #include <string>
44 #include <vector>
45 
46 using std::vector;
47 using std::ifstream;
48 using std::map;
49 using std::pair;
50 using std::string;
51 using std::stringstream;
52 using std::skipws;
53 
54 using namespace std;
55 
56 struct Shapeinfo_data {
57 	vector<pair<string, int>> paperdoll_source_table;
58 	vector<pair<int, int>> imported_gump_shapes;
59 	vector<pair<int, int>> blue_shapes;
60 	vector<pair<int, int>> imported_skin_shapes;
61 	map<string, int> gumpvars;
62 	map<string, int> skinvars;
63 };
64 
65 struct Avatar_data {
66 	map<bool, Base_Avatar_info> def_av_info;
67 	Avatar_default_skin base_av_info;
68 	vector<Skin_data> skins_table;
69 	map<int, bool> unselectable_skins;
70 	map<int, int> petra_table;
71 	map<int, Usecode_function_data> usecode_funs;
72 	static int last_skin;
73 };
74 
75 std::unique_ptr<Shapeinfo_data> Shapeinfo_lookup::data(nullptr);
76 std::unique_ptr<Avatar_data> Shapeinfo_lookup::avdata(nullptr);
77 int Avatar_data::last_skin = 0;
78 
79 
80 // HACK. NPC Paperdolls need this, but miscinf has too many
81 // Exult-dependant stuff to be included in ES. Thus, ES
82 // defines a non-operant version of this.
83 // Maybe we should do something about this...
get_skinvar(const std::string & key)84 int get_skinvar(const std::string& key) {
85 	return Shapeinfo_lookup::get_skinvar(key);
86 }
87 
88 /*
89  *  Base parser class shape data.
90  */
91 class Shapeinfo_entry_parser {
92 public:
93 	virtual ~Shapeinfo_entry_parser() noexcept = default;
94 	virtual void parse_entry(int index, istream &src,
95 	                         bool for_patch, int version) = 0;
ReadInt(istream & src,int off=1)96 	int ReadInt(istream &src, int off = 1) {
97 		src.ignore(off);
98 		int ret;
99 		if (src.peek() == '0') {
100 			src.ignore(1);
101 			char chr = src.peek();
102 			if (chr == 'x' || chr == 'X') {
103 				src.ignore(1);
104 				src >> hex;
105 			} else {
106 				src.unget();
107 			}
108 		}
109 		src >> ret >> skipws >> dec;
110 		return ret;
111 	}
ReadStr(istream & src,int off=1)112 	string ReadStr(istream &src, int off = 1) {
113 		src.ignore(off);
114 		string ret;
115 		getline(src, ret, '/');
116 		src.unget();
117 		return ret;
118 	}
119 };
120 
121 class Int_pair_parser: public Shapeinfo_entry_parser {
122 	map<int, int>& table;
123 public:
Int_pair_parser(map<int,int> & tbl)124 	explicit Int_pair_parser(map<int, int>& tbl)
125 		: table(tbl)
126 	{  }
parse_entry(int index,istream & src,bool for_patch,int version)127 	void parse_entry(int index, istream &src,
128 	                 bool for_patch, int version) final {
129 		ignore_unused_variable_warning(index, for_patch, version);
130 		int key = ReadInt(src, 0);
131 		int data = ReadInt(src);
132 		table[key] = data;
133 	}
134 };
135 
136 class Bool_parser: public Shapeinfo_entry_parser {
137 	map<int, bool>& table;
138 public:
Bool_parser(map<int,bool> & tbl)139 	explicit Bool_parser(map<int, bool>& tbl)
140 		: table(tbl)
141 	{  }
parse_entry(int index,istream & src,bool for_patch,int version)142 	void parse_entry(int index, istream &src,
143                      bool for_patch, int version) final {
144 		ignore_unused_variable_warning(index, for_patch, version);
145 		int key = ReadInt(src, 0);
146 		table[key] = true;
147 	}
148 };
149 
150 class Shape_imports_parser: public Shapeinfo_entry_parser {
151 	vector<pair<int, int>>& table;
152 	map<string, int>& shapevars;
153 	int shape;
154 public:
Shape_imports_parser(vector<pair<int,int>> & tbl,map<string,int> & sh)155 	Shape_imports_parser(vector<pair<int, int>>& tbl, map<string, int>& sh)
156 		: table(tbl), shapevars(sh), shape(c_max_shapes)
157 	{  }
parse_entry(int index,istream & src,bool for_patch,int version)158 	void parse_entry(int index, istream &src,
159 	                 bool for_patch, int version) final {
160 		ignore_unused_variable_warning(index, for_patch, version);
161 		pair<int, int> data;
162 		data.second = ReadInt(src, 0); // The real shape.
163 		for (auto& elem : table) {
164 			if (elem.second == data.second) {
165 				return;     // Do nothing for repeated entries.
166 			}
167 		}
168 		src.ignore(1);
169 		if (src.peek() == '%') {
170 			data.first = shape;     // The assigned shape.
171 			string key;
172 			src >> key;
173 			shapevars[key] = shape;
174 			shape++;    // Leave it ready for the next shape.
175 		} else {
176 			data.first = ReadInt(src, 0);
177 		}
178 		table.push_back(data);
179 	}
180 };
181 
182 class Shaperef_parser: public Shapeinfo_entry_parser {
183 	vector<pair<int, int>>& table;
184 	map<string, int>& shapevars;
185 public:
Shaperef_parser(vector<pair<int,int>> & tbl,map<string,int> & sh)186 	Shaperef_parser(vector<pair<int, int>>& tbl, map<string, int>& sh)
187 		: table(tbl), shapevars(sh)
188 	{  }
parse_entry(int index,istream & src,bool for_patch,int version)189 	void parse_entry(int index, istream &src,
190 	                 bool for_patch, int version) final {
191 		ignore_unused_variable_warning(index, for_patch, version);
192 		pair<int, int> data;
193 		data.first = ReadInt(src, 0);  // The spot.
194 		src.ignore(1);
195 		if (src.peek() == '%') {
196 			string key;
197 			src >> key;
198 			auto it = shapevars.find(key);
199 			if (it != shapevars.end()) {
200 				data.second = (*it).second; // The shape #.
201 			} else {
202 				return; // Invalid reference; bail out.
203 			}
204 		} else
205 			data.second = ReadInt(src, 0);
206 		table.push_back(data);
207 	}
208 };
209 
210 class Paperdoll_source_parser: public Shapeinfo_entry_parser {
211 	vector<pair<string, int>>& table;
212 	bool erased_for_patch;
213 public:
Paperdoll_source_parser(vector<pair<string,int>> & tbl)214 	explicit Paperdoll_source_parser(vector<pair<string, int>>& tbl)
215 		: table(tbl), erased_for_patch(false)
216 	{  }
217 	void parse_entry(int index, istream &src,
218 	                 bool for_patch, int version) final;
219 };
220 
221 class Def_av_shape_parser: public Shapeinfo_entry_parser {
222 	map<bool, Base_Avatar_info>& table;
223 public:
Def_av_shape_parser(map<bool,Base_Avatar_info> & tbl)224 	explicit Def_av_shape_parser(map<bool, Base_Avatar_info>& tbl)
225 		: table(tbl)
226 	{  }
parse_entry(int index,istream & src,bool for_patch,int version)227 	void parse_entry(int index, istream &src,
228 	                 bool for_patch, int version) final {
229 		ignore_unused_variable_warning(index, for_patch, version);
230 		bool fmale = ReadInt(src, 0) != 0;
231 		Base_Avatar_info entry;
232 		entry.shape_num = ReadInt(src);
233 		entry.face_shape = ReadInt(src);
234 		entry.face_frame = ReadInt(src);
235 		table[fmale] = entry;
236 	}
237 };
238 
239 class Base_av_race_parser: public Shapeinfo_entry_parser {
240 	Avatar_default_skin& table;
241 public:
Base_av_race_parser(Avatar_default_skin & tbl)242 	explicit Base_av_race_parser(Avatar_default_skin& tbl)
243 		: table(tbl)
244 	{  }
parse_entry(int index,istream & src,bool for_patch,int version)245 	void parse_entry(int index, istream &src,
246 	                 bool for_patch, int version) final {
247 		ignore_unused_variable_warning(index, for_patch, version);
248 		table.default_skin = ReadInt(src, 0);
249 		table.default_female = ReadInt(src) != 0;
250 	}
251 };
252 
253 class Multiracial_parser: public Shapeinfo_entry_parser {
254 	vector<Skin_data>& table;
255 	map<string, int>& shapevars;
256 public:
Multiracial_parser(vector<Skin_data> & tbl,map<string,int> & sh)257 	explicit Multiracial_parser(vector<Skin_data>& tbl, map<string, int>& sh)
258 		: table(tbl), shapevars(sh)
259 	{  }
ReadVar(istream & src)260 	int ReadVar(istream &src) {
261 		src.ignore(1);
262 		if (src.peek() == '%') {
263 			string key = ReadStr(src, 0);
264 			auto it = shapevars.find(key);
265 			if (it != shapevars.end()) {
266 				return (*it).second;    // The var value.
267 			}
268 			return -1;  // Invalid reference; bail out.
269 		}
270 		return ReadInt(src, 0);
271 	}
parse_entry(int index,istream & src,bool for_patch,int version)272 	void parse_entry(int index, istream &src,
273 	                 bool for_patch, int version) final {
274 		ignore_unused_variable_warning(index);
275 		Skin_data entry;
276 		entry.skin_id = ReadInt(src, 0);
277 		if (entry.skin_id > Avatar_data::last_skin) {
278 			Avatar_data::last_skin = entry.skin_id;
279 		}
280 		entry.is_female = ReadInt(src) != 0;
281 		if ((entry.shape_num = ReadVar(src)) < 0) {
282 			return;
283 		}
284 		if ((entry.naked_shape = ReadVar(src)) < 0) {
285 			return;
286 		}
287 		entry.face_shape = ReadInt(src);
288 		entry.face_frame = ReadInt(src);
289 		entry.alter_face_shape = ReadInt(src);
290 		entry.alter_face_frame = ReadInt(src);
291 		entry.copy_info = !(version == 2 && !src.eof() && ReadInt(src) == 0);
292 		if (for_patch && !table.empty()) {
293 			unsigned int i;
294 			int found = -1;
295 			for (i = 0; i < table.size(); i++) {
296 				if (table[i].skin_id == entry.skin_id &&
297 				        table[i].is_female == entry.is_female) {
298 					found = i;
299 					break;
300 				}
301 			}
302 			if (found > -1) {
303 				table[found] = entry;
304 				return;
305 			}
306 		}
307 		table.push_back(entry);
308 	}
309 };
310 
311 class Avatar_usecode_parser: public Shapeinfo_entry_parser {
312 	map<int, Usecode_function_data>& table;
313 	Usecode_machine *usecode;
314 public:
Avatar_usecode_parser(map<int,Usecode_function_data> & tbl)315 	explicit Avatar_usecode_parser(map<int, Usecode_function_data>& tbl)
316 		: table(tbl), usecode(Game_window::get_instance()->get_usecode())
317 	{  }
parse_entry(int index,istream & src,bool for_patch,int version)318 	void parse_entry(int index, istream &src,
319 	                 bool for_patch, int version) final {
320 		ignore_unused_variable_warning(index, for_patch, version);
321 		Usecode_function_data entry;
322 		int type = ReadInt(src, 0);
323 		if (src.peek() == ':') {
324 			string name = ReadStr(src);
325 			entry.fun_id = usecode->find_function(name.c_str(), true);
326 		} else {
327 			entry.fun_id = ReadInt(src);
328 		}
329 		entry.event_id = ReadInt(src);
330 		table[type] = entry;
331 	}
332 };
333 
334 
parse_entry(int index,istream & src,bool for_patch,int version)335 void Paperdoll_source_parser::parse_entry(
336     int index,
337     istream &src,
338     bool for_patch,
339     int version
340 ) {
341 	ignore_unused_variable_warning(index, version);
342 	if (!erased_for_patch && for_patch) {
343 		table.clear();
344 	}
345 	string line;
346 	src >> line;
347 	if (line == "static" ||
348 	        (GAME_BG && line == "bg") ||
349 	        (GAME_SI && line == "si")) {
350 		table.emplace_back(PAPERDOL, -1);
351 	} else if (line == "si")  {
352 		table.emplace_back("<SERPENT_STATIC>/paperdol.vga", -1);
353 	} else if (GAME_SI && line == "flx") {
354 		// ++++ FIMXME: Implement in the future for SI paperdoll patches.
355 		CERR("Paperdoll source file '" << line << "' is not implemented yet.");
356 	} else if (GAME_BG && line == "flx") {
357 		const str_int_pair &resource = game->get_resource("files/paperdolvga");
358 		table.emplace_back(resource.str, resource.num);
359 	} else {
360 		CERR("Unknown paperdoll source file '" << line << "' was specified.");
361 	}
362 }
363 
364 /*
365  *  Parses a shape data file.
366  */
Read_data_file(const char * fname,const char * sections[],Shapeinfo_entry_parser * parsers[],int numsections)367 void Shapeinfo_lookup::Read_data_file(
368     const char *fname,                  // Name of file to read, sans extension
369     const char *sections[],             // The names of the sections
370     Shapeinfo_entry_parser *parsers[],  // Parsers to use for each section
371     int numsections                     // Number of sections
372 ) {
373 	vector<Readstrings> static_strings;
374 	vector<Readstrings> patch_strings;
375 	static_strings.resize(numsections);
376 	int static_version = 1;
377 	int patch_version = 1;
378 	char buf[50];
379 	if (GAME_BG || GAME_SI) {
380 		snprintf(buf, 50, "config/%s", fname);
381 		const str_int_pair &resource = game->get_resource(buf);
382 		IExultDataSource ds(resource.str, resource.num);
383 		static_version = Read_text_msg_file_sections(&ds,
384 		                 static_strings, sections, numsections);
385 	} else {
386 		try {
387 			snprintf(buf, 50, "<STATIC>/%s.txt", fname);
388 			ifstream in;
389 			U7open(in, buf, false);
390 			static_version = Read_text_msg_file_sections(in,
391 			                 static_strings, sections, numsections);
392 			in.close();
393 		} catch (std::exception const&) {
394 			if (!Game::is_editing()) {
395 				throw;
396 			}
397 			static_strings.resize(numsections);
398 		}
399 	}
400 	patch_strings.resize(numsections);
401 	snprintf(buf, 50, "<PATCH>/%s.txt", fname);
402 	if (U7exists(buf)) {
403 		ifstream in;
404 		U7open(in, buf, false);
405 		patch_version = Read_text_msg_file_sections(in, patch_strings,
406 		                sections, numsections);
407 		in.close();
408 	}
409 
410 	for (size_t i = 0; i < static_strings.size(); i++) {
411 		Readstrings &section = static_strings[i];
412 		for (size_t j = 0; j < section.size(); j++) {
413 			if (!section[j].empty()) {
414 				stringstream src(section[j]);
415 				parsers[i]->parse_entry(j, src, false, static_version);
416 			}
417 		}
418 		section.clear();
419 	}
420 	static_strings.clear();
421 	for (size_t i = 0; i < patch_strings.size(); i++) {
422 		Readstrings &section = patch_strings[i];
423 		for (size_t j = 0; j < section.size(); j++) {
424 			if (!section[j].empty()) {
425 				stringstream src(section[j]);
426 				parsers[i]->parse_entry(j, src, true, patch_version);
427 			}
428 		}
429 		section.clear();
430 	}
431 	patch_strings.clear();
432 	for (int i = 0; i < numsections; i++) {
433 		delete parsers[i];
434 	}
435 }
436 
437 /*
438  *  Setup shape file tables.
439  */
setup_shape_files()440 void Shapeinfo_lookup::setup_shape_files() {
441 	if (data != nullptr) {
442 		return;
443 	}
444 	data = make_unique<Shapeinfo_data>();
445 	const int size = 4;
446 	const char *sections[size] = {
447 		"paperdoll_source",
448 		"gump_imports",
449 		"blue_shapes",
450 		"multiracial_imports"
451 	};
452 	Shapeinfo_entry_parser *parsers[size] = {
453 		new Paperdoll_source_parser(data->paperdoll_source_table),
454 		new Shape_imports_parser(data->imported_gump_shapes, data->gumpvars),
455 		new Shaperef_parser(data->blue_shapes, data->gumpvars),
456 		new Shape_imports_parser(data->imported_skin_shapes, data->skinvars)
457 	};
458 	Read_data_file("shape_files", sections, parsers, size);
459 	// For safety.
460 	if (data->paperdoll_source_table.empty()) {
461 		data->paperdoll_source_table.emplace_back(PAPERDOL, -1);
462 	}
463 	// Add in patch paperdolls too.
464 	data->paperdoll_source_table.emplace_back(PATCH_PAPERDOL, -1);
465 }
466 
467 /*
468  *  Setup avatar data tables.
469  */
setup_avatar_data()470 void Shapeinfo_lookup::setup_avatar_data() {
471 	setup_shape_files();
472 	if (avdata != nullptr) {
473 		return;
474 	}
475 	avdata = make_unique<Avatar_data>();
476 	const int size = 6;
477 	const char *sections[size] = {
478 		"defaultshape",
479 		"baseracesex",
480 		"multiracial_table",
481 		"unselectable_races_table",
482 		"petra_face_table",
483 		"usecode_info"
484 	};
485 	Shapeinfo_entry_parser *parsers[size] = {
486 		new Def_av_shape_parser(avdata->def_av_info),
487 		new Base_av_race_parser(avdata->base_av_info),
488 		new Multiracial_parser(avdata->skins_table, data->skinvars),
489 		new Bool_parser(avdata->unselectable_skins),
490 		new Int_pair_parser(avdata->petra_table),
491 		new Avatar_usecode_parser(avdata->usecode_funs)
492 	};
493 	Read_data_file("avatar_data", sections, parsers, size);
494 }
495 
496 
GetPaperdollSources()497 vector<pair<string, int> > *Shapeinfo_lookup::GetPaperdollSources() {
498 	setup_shape_files();
499 	return &data->paperdoll_source_table;
500 }
501 
GetImportedSkins()502 vector<pair<int, int> > *Shapeinfo_lookup::GetImportedSkins() {
503 	setup_shape_files();
504 	return &data->imported_skin_shapes;
505 }
506 
IsSkinImported(int shape)507 bool Shapeinfo_lookup::IsSkinImported(int shape) {
508 	setup_shape_files();
509 	for (auto& elem : data->imported_skin_shapes) {
510 		if (elem.first == shape) {
511 			return true;
512 		}
513 	}
514 	return false;
515 }
516 
GetImportedGumpShapes()517 vector<pair<int, int>> *Shapeinfo_lookup::GetImportedGumpShapes() {
518 	setup_shape_files();
519 	return &data->imported_gump_shapes;
520 }
521 
GetBlueShapeData(int spot)522 int Shapeinfo_lookup::GetBlueShapeData(int spot) {
523 	setup_shape_files();
524 	for (auto& elem : data->blue_shapes) {
525 		if (elem.first == -1 || elem.first == spot) {
526 			return elem.second;
527 		}
528 	}
529 	return 54;
530 }
531 
GetBaseAvInfo(bool sex)532 Base_Avatar_info *Shapeinfo_lookup::GetBaseAvInfo(bool sex) {
533 	setup_avatar_data();
534 	auto it = avdata->def_av_info.find(sex);
535 	if (it != avdata->def_av_info.end()) {
536 		return &((*it).second);
537 	}
538 	return nullptr;
539 }
540 
get_skinvar(const string & key)541 int Shapeinfo_lookup::get_skinvar(const string& key) {
542 	setup_shape_files();
543 	auto it = data->skinvars.find(key);
544 	if (it != data->skinvars.end()) {
545 		return (*it).second;    // The shape #.
546 	}
547 	return -1;  // Invalid reference; bail out.
548 }
549 
GetMaleAvShape()550 int Shapeinfo_lookup::GetMaleAvShape() {
551 	setup_avatar_data();
552 	return avdata->def_av_info[false].shape_num;
553 }
554 
GetFemaleAvShape()555 int Shapeinfo_lookup::GetFemaleAvShape() {
556 	setup_avatar_data();
557 	return avdata->def_av_info[true].shape_num;
558 }
559 
GetDefaultAvSkin()560 Avatar_default_skin *Shapeinfo_lookup::GetDefaultAvSkin() {
561 	setup_avatar_data();
562 	return &avdata->base_av_info;
563 }
564 
GetSkinList()565 vector<Skin_data> *Shapeinfo_lookup::GetSkinList() {
566 	setup_avatar_data();
567 	return &avdata->skins_table;
568 }
569 
GetSkinInfo(int skin,bool sex)570 Skin_data *Shapeinfo_lookup::GetSkinInfo(int skin, bool sex) {
571 	setup_avatar_data();
572 	for (auto& elem : avdata->skins_table) {
573 		if (elem.skin_id == skin && elem.is_female == sex){
574 			return &elem;
575 		}
576 	}
577 	return nullptr;
578 }
579 
GetSkinInfoSafe(int skin,bool sex,bool sishapes)580 Skin_data *Shapeinfo_lookup::GetSkinInfoSafe(int skin, bool sex, bool sishapes) {
581 	Skin_data *sk = GetSkinInfo(skin, sex);
582 	if ((sk != nullptr) && (sishapes ||
583 	           (!IsSkinImported(sk->shape_num) &&
584 	            !IsSkinImported(sk->naked_shape)))) {
585 		return sk;
586 	}
587 	sk = GetSkinInfo(GetDefaultAvSkin()->default_skin, sex);
588 	// Prevent unavoidable problems. *Should* never be needed.
589 	assert(sk && (sishapes ||
590 	              (!IsSkinImported(sk->shape_num) &&
591 	               !IsSkinImported(sk->naked_shape))));
592 	return sk;
593 }
594 
GetSkinInfoSafe(Actor * npc)595 Skin_data *Shapeinfo_lookup::GetSkinInfoSafe(Actor *npc) {
596 	int skin = npc->get_skin_color();
597 	bool sex = npc->get_type_flag(Actor::tf_sex);
598 	return GetSkinInfoSafe(skin, sex, Shape_manager::get_instance()->have_si_shapes());
599 }
600 
ScrollSkins(int skin,bool sex,bool sishapes,bool ignoresex,bool prev,bool sel)601 Skin_data *Shapeinfo_lookup::ScrollSkins(
602     int skin, bool sex, bool sishapes, bool ignoresex, bool prev, bool sel
603 ) {
604 	setup_avatar_data();
605 	bool nsex = sex;
606 	int nskin = skin;
607 	bool chskin = true;
608 	while (true) {
609 		if (ignoresex) {
610 			nsex = !nsex;
611 			chskin = (nsex == avdata->base_av_info.default_female);
612 		}
613 		nskin = (nskin + ((prev != 0) ? Avatar_data::last_skin : 0) + chskin) % (Avatar_data::last_skin + 1);
614 		if (sel && !IsSkinSelectable(nskin)) {
615 			continue;
616 		}
617 		Skin_data *newskin = GetSkinInfo(nskin, nsex);
618 		if ((newskin != nullptr) && (sishapes ||
619 		                (!IsSkinImported(newskin->shape_num) &&
620 		                 !IsSkinImported(newskin->naked_shape)))) {
621 			return newskin;
622 		}
623 	}
624 }
625 
GetNumSkins(bool sex)626 int Shapeinfo_lookup::GetNumSkins(bool sex) {
627 	setup_avatar_data();
628 	int cnt = 0;
629 	for (auto& elem : avdata->skins_table) {
630 		if (elem.is_female == sex) {
631 			cnt++;
632 		}
633 	}
634 	return cnt;
635 }
636 
GetAvUsecode(int type)637 Usecode_function_data *Shapeinfo_lookup::GetAvUsecode(int type) {
638 	setup_avatar_data();
639 	auto it = avdata->usecode_funs.find(type);
640 	if (it != avdata->usecode_funs.end()) {
641 		return &(it->second);
642 	}
643 	return nullptr;
644 }
645 
IsSkinSelectable(int skin)646 bool Shapeinfo_lookup::IsSkinSelectable(int skin) {
647 	setup_avatar_data();
648 	auto it = avdata->unselectable_skins.find(skin);
649 	return it == avdata->unselectable_skins.end();
650 }
651 
HasFaceReplacement(int npcid)652 bool Shapeinfo_lookup::HasFaceReplacement(int npcid) {
653 	setup_avatar_data();
654 	auto it = avdata->petra_table.find(npcid);
655 	if (it != avdata->petra_table.end()) {
656 		return it->second != 0;
657 	}
658 	return npcid != 0;
659 }
660 
GetFaceReplacement(int facenum)661 int Shapeinfo_lookup::GetFaceReplacement(int facenum) {
662 	setup_avatar_data();
663 	Game_window *gwin = Game_window::get_instance();
664 	if (gwin->get_main_actor()->get_flag(Obj_flags::petra)) {
665 		auto it = avdata->petra_table.find(facenum);
666 		if (it != avdata->petra_table.end())
667 			return it->second;
668 	}
669 	return facenum;
670 }
671 
reset()672 void Shapeinfo_lookup::reset() {
673 	data.reset();
674 	avdata.reset();
675 	Avatar_data::last_skin = 0;
676 }
677