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 §ion = 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 §ion = 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