ff7DataInstaller.cpp 60 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434
  1. /*
  2. * Copyright (C) 2022 The V-Gears Team
  3. *
  4. * This file is part of V-Gears
  5. *
  6. * V-Gears is free software: you can redistribute it and/or modify it under
  7. * terms of the GNU General Public License as published by the Free Software
  8. * Foundation, version 3.0 (GPLv3) of the License.
  9. *
  10. * V-Gears is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. */
  15. #include <iostream>
  16. #include <boost/filesystem.hpp>
  17. #include <boost/program_options.hpp>
  18. #include <OgreConfigFile.h>
  19. #include <OgreArchiveManager.h>
  20. #include <OgreMeshSerializer.h>
  21. #include <OgreMaterialSerializer.h>
  22. #include <OgreTechnique.h>
  23. #include <OgreTextureManager.h>
  24. #include <OgreMaterialManager.h>
  25. #include <OgreSkeletonSerializer.h>
  26. #include <OgreMeshManager.h>
  27. #include <OgreRenderWindow.h>
  28. #include <OgreHardwarePixelBuffer.h>
  29. #include <OgreResourceGroupManager.h>
  30. #include <OgreLog.h>
  31. #include "ff7DataInstaller.h"
  32. #include "VGearsGameState.h"
  33. #include "data/VGearsAFileManager.h"
  34. #include "data/VGearsBackgroundFileManager.h"
  35. #include "data/VGearsCameraMatrixFileManager.h"
  36. #include "data/VGearsHRCFileManager.h"
  37. #include "data/VGearsLZSFLevelFileManager.h"
  38. #include "data/VGearsPaletteFileManager.h"
  39. #include "data/VGearsPFileManager.h"
  40. #include "data/VGearsRSDFileManager.h"
  41. #include "data/VGearsTexFile.h"
  42. #include "data/VGearsTexCodec.h"
  43. #include "map/VGearsBackground2DFileManager.h"
  44. #include "map/VGearsWalkmeshFileManager.h"
  45. #include "data/FinalFantasy7/FF7ModelListFileManager.h"
  46. #include "data/VGearsLGPArchiveFactory.h"
  47. #include "common/FinalFantasy7/FF7NameLookup.h"
  48. #include "data/VGearsTexCodec.h"
  49. #include "data/VGearsMapListFile.h"
  50. #include "decompiler/sudm.h"
  51. #include <memory>
  52. #include <QtCore/QDir>
  53. float FF7DataInstaller::LINE_SCALE_FACTOR = 0.0078124970964f;
  54. FF7DataInstaller::FF7DataInstaller(
  55. std::string input_dir, std::string output_dir,
  56. std::function<void(std::string)> write_output_line
  57. )
  58. #ifdef _DEBUG
  59. : application_("plugins_d.cfg", "resources_d.cfg", "install_d.log"),
  60. #else
  61. : application_("plugins.cfg", "resources.cfg", "install.log"),
  62. #endif
  63. input_dir_(input_dir),
  64. output_dir_(output_dir),
  65. write_output_line(write_output_line)
  66. {
  67. if (!application_.initOgre(true)) throw std::runtime_error("Ogre init failure");
  68. Ogre::Log* default_log( Ogre::LogManager::getSingleton().getDefaultLog());
  69. assert( default_log );
  70. default_log->setLogDetail(Ogre::LL_LOW);
  71. }
  72. FF7DataInstaller::~FF7DataInstaller(){}
  73. int FF7DataInstaller::CalcProgress(){
  74. // TODO: Make more accurate with iterator_counter_ and
  75. // progress_step_num_elements_.
  76. float curr_step = installation_state_ / static_cast<float>(STATE_COUNT);
  77. curr_step = curr_step * 100.0f;
  78. return static_cast<int>(curr_step);
  79. }
  80. int FF7DataInstaller::Progress(){
  81. switch (installation_state_){
  82. case IDLE:
  83. write_output_line("Loading flevel.lgp");
  84. progress_step_num_elements_ = 1;
  85. iterator_counter_ = 0;
  86. fields_lgp_ = std::make_unique<ScopedLgp>(
  87. application_.getRoot(), input_dir_ + "field/flevel.lgp",
  88. "LGP", "FFVIIFields"
  89. );
  90. installation_state_ = SPAWN_POINTS_AND_SCALE_FACTORS_INIT;
  91. // TODO: DEBUG:
  92. //installation_state_ = CONVERT_FIELDS;
  93. return CalcProgress();
  94. case SPAWN_POINTS_AND_SCALE_FACTORS_INIT:
  95. write_output_line("Collecting spawn points and scale factors...");
  96. InitCollectSpawnAndScaleFactors();
  97. return CalcProgress();
  98. case SPAWN_POINTS_AND_SCALE_FACTORS:
  99. CollectionFieldSpawnAndScaleFactors();
  100. return CalcProgress();
  101. case CONVERT_FIELDS:
  102. ConvertFieldsIteration();
  103. return CalcProgress();
  104. case WRITE_MAPS_INIT:
  105. write_output_line("Writing fields...");
  106. WriteMapsXmlBegin();
  107. return CalcProgress();
  108. case WRITE_MAPS:
  109. WriteMapsXmlIteration();
  110. return CalcProgress();
  111. case WRITE_MAPS_CLEAN:
  112. EndWriteMapsXml();
  113. return CalcProgress();
  114. case CONVERT_FIELD_MODELS_INIT:
  115. write_output_line("Converting field models...");
  116. ConvertFieldModelsBegin();
  117. return CalcProgress();
  118. case CONVERT_FIELD_MODELS:
  119. ConvertFieldModelsIteration();
  120. return CalcProgress();
  121. case DONE:
  122. default:
  123. return 100;
  124. }
  125. }
  126. static std::string FieldModelDir(){return "models/ffvii/field/units";}
  127. std::vector<std::string> materials_;
  128. /**
  129. * Converts a tex file to png.
  130. *
  131. * The image is saved in data/models/ffvii/field/units/
  132. *
  133. * @param name[in] Path to the tex file to convert.
  134. */
  135. void TexToPng(const std::string name){
  136. Ogre::DataStreamPtr stream(
  137. Ogre::ResourceGroupManager::getSingleton().openResource(name, "FFVIITextures", true, NULL)
  138. );
  139. VGears::TexFile* tex = new VGears::TexFile();
  140. tex->Read(stream);
  141. Ogre::ImageCodec::ImageData* image_data = tex->GetImageData();
  142. Ogre::Image* image = new Ogre::Image(
  143. Ogre::PF_R8G8B8A8_UINT, image_data->width, image_data->height
  144. );
  145. for (int y = 0; y < image_data->height; y ++){
  146. for (int x = 0; x < image_data->width; x ++){
  147. // This is the position from which to read from the tex file colour data
  148. // TODO: It doesn't work for non-square images. For example, the lower part of bxje is
  149. // all wrong.
  150. int i = (image_data->height * y) + x;
  151. image->setColourAt(
  152. Ogre::ColourValue(
  153. tex->image_data_[i].comp.red / 255.0f,
  154. tex->image_data_[i].comp.green / 255.0f,
  155. tex->image_data_[i].comp.blue / 255.0f,
  156. tex->image_data_[i].comp.alpha),
  157. x, y, 0
  158. );
  159. }
  160. }
  161. Ogre::String base_name;
  162. VGears::StringUtil::splitBase(name, base_name);
  163. // TODO: Detect path
  164. image->save("data/models/ffvii/field/units/" + base_name + ".png");
  165. }
  166. /**
  167. * Exports a mesh to a file.
  168. *
  169. * The file will have the mesh name.
  170. *
  171. * @param outdir[in] Path to the directory where the file will be saved.
  172. * @param mesh[in] The mesh to export.
  173. */
  174. static void ExportMesh(std::string outdir, const Ogre::MeshPtr &mesh){
  175. // TODO: Share function with pc model exporter
  176. VGears::String base_mesh_name;
  177. VGears::StringUtil::splitFull(mesh->getName(), base_mesh_name);
  178. Ogre::MeshSerializer mesh_serializer;
  179. Ogre::SkeletonPtr skeleton(mesh->getSkeleton());
  180. Ogre::SkeletonSerializer skeleton_serializer;
  181. skeleton_serializer.exportSkeleton(
  182. skeleton.getPointer(), outdir + base_mesh_name + ".skeleton"
  183. );
  184. mesh->setSkeletonName(FieldModelDir() + "/" + base_mesh_name + ".skeleton");
  185. mesh_serializer.exportMesh(mesh.getPointer(), outdir + mesh->getName());
  186. Ogre::Mesh::SubMeshIterator it(mesh->getSubMeshIterator());
  187. Ogre::MaterialSerializer mat_ser;
  188. size_t i(0);
  189. std::set<std::string> textures;
  190. while (it.hasMoreElements()){
  191. Ogre::SubMesh *sub_mesh(it.getNext());
  192. Ogre::MaterialPtr mat(Ogre::MaterialManager::getSingleton().getByName(
  193. sub_mesh->getMaterialName())
  194. );
  195. if (mat != nullptr){
  196. for (size_t techs = 0; techs < mat->getNumTechniques(); techs ++){
  197. Ogre::Technique* tech = mat->getTechnique(techs);
  198. if (tech){
  199. for (size_t pass_num = 0; pass_num < tech->getNumPasses(); pass_num ++){
  200. Ogre::Pass* pass = tech->getPass(pass_num);
  201. if (pass){
  202. for (
  203. size_t texture_unit_num = 0;
  204. texture_unit_num < pass->getNumTextureUnitStates();
  205. texture_unit_num ++
  206. ){
  207. Ogre::TextureUnitState* unit
  208. = pass->getTextureUnitState(texture_unit_num);
  209. if (unit && unit->getTextureName().empty() == false){
  210. // Convert the texture from .tex to .png.
  211. TexToPng(unit->getTextureName());
  212. // Ensure the output material script references png files
  213. // rather than tex
  214. // files.
  215. Ogre::String base_name;
  216. VGears::StringUtil::splitBase(
  217. unit->getTextureName(), base_name
  218. );
  219. unit->setTextureName(
  220. FieldModelDir() + "/" + base_mesh_name
  221. + "_" + base_name + ".png"
  222. );
  223. // Copy subtexture (xxxx.png) to
  224. // model_xxxx.png
  225. // TODO: obtain the "data" folder
  226. // programatically.
  227. boost::filesystem::copy_file(
  228. "data/" + FieldModelDir() + "/" + base_name + ".png",
  229. "data/" + FieldModelDir() + "/"
  230. + base_mesh_name + "_" + base_name + ".png",
  231. boost::filesystem::copy_option::overwrite_if_exists
  232. );
  233. textures.insert(unit->getTextureName());
  234. }
  235. }
  236. }
  237. }
  238. }
  239. }
  240. if (std::count(materials_.begin(), materials_.end(), sub_mesh->getMaterialName()) == 0){
  241. //std::cout << "[MATERIAL] Writting material "
  242. // << sub_mesh->getMaterialName() << std::endl;
  243. mat_ser.queueForExport(mat);
  244. materials_.push_back(sub_mesh->getMaterialName());
  245. }
  246. }
  247. ++ i;
  248. }
  249. mat_ser.exportQueued(outdir + base_mesh_name + VGears::EXT_MATERIAL);
  250. for (auto& texture_name : textures){
  251. std::string tex_name = texture_name.c_str();
  252. try{
  253. Ogre::TexturePtr texture_ptr
  254. = Ogre::TextureManager::getSingleton().load(tex_name, "FFVIITextures" /*"FFVII"*/);
  255. Ogre::Image image;
  256. texture_ptr->convertToImage(image);
  257. Ogre::String base_name;
  258. VGears::StringUtil::splitBase(texture_name, base_name);
  259. image.save(outdir + base_mesh_name + "_" + base_name + ".png");
  260. }
  261. catch (std::exception const& ex){
  262. std::cerr << "[ERROR] Exception: " << ex.what() << std::endl;
  263. }
  264. }
  265. }
  266. /**
  267. * Normalizes a field name.
  268. *
  269. * It prefixes a name witg "ffvii_".
  270. *
  271. * @param name[in] The field name.
  272. * @return The normalized field name.
  273. */
  274. static std::string FieldName(const std::string& name){return "ffvii_" + name;}
  275. void FF7DataInstaller::CreateDir(const std::string& path){
  276. QString target = QString::fromStdString(output_dir_ + path);
  277. QDir dir(target);
  278. if (!dir.mkpath(".")) throw std::runtime_error("Failed to mkpath");
  279. }
  280. /**
  281. * Retrieves the path to the field map directory.
  282. *
  283. * @return The relative path to the field map directory.
  284. */
  285. static std::string FieldMapDir(){return "maps/ffvii/field";}
  286. /**
  287. * Utilities for formatting field scripts.
  288. */
  289. class BaseFF7FieldScriptFormatter : public SUDM::IScriptFormatter{
  290. public:
  291. /**
  292. * Constructor.
  293. *
  294. * @param field_name[in] The field name.
  295. * @param field_id_to_name_lookup[in] Field name lookup table to use.
  296. * @param spawn_points[in] The list of spawn points.
  297. */
  298. BaseFF7FieldScriptFormatter(
  299. const std::string& field_name, const std::vector<std::string>& field_id_to_name_lookup,
  300. FieldSpawnPointsMap& spawn_points
  301. ) :
  302. field_name_(field_name), field_id_to_name_lookup_(field_id_to_name_lookup),
  303. spawn_points_(spawn_points)
  304. {}
  305. /**
  306. * Composes a spawn point name
  307. *
  308. * The name will have the format
  309. * {FIELD_NAME}_{ENTITY}_{FUNCTION_NAME}_addr_{ADDRESS}
  310. *
  311. * @param map_id[in] Unused.
  312. * @param entity[in] Entity name.
  313. * @param function_name[in] Name of the function.
  314. * @param address[in] @todo Understand and document.
  315. * @return The composed spawn point name.
  316. */
  317. virtual std::string SpawnPointName(
  318. unsigned int map_id, const std::string& entity,
  319. const std::string& function_name, unsigned int address
  320. ) override{
  321. return
  322. field_name_ + "_" + entity + "_" + function_name + "_addr_" + std::to_string(address);
  323. }
  324. /**
  325. * Retrieves a map name from it's ID.
  326. *
  327. * @param[in] The map ID.
  328. * @return The map name.
  329. */
  330. virtual std::string MapName(unsigned int map_id) override{
  331. return FieldName(field_id_to_name_lookup_[map_id]);
  332. }
  333. /**
  334. * Retrieves a user friendly variable name.
  335. *
  336. * @param bank[in] Variable bank.
  337. * @param address[in] Variable address.
  338. * @return Friendly name for the variable, or an empty string if it
  339. * doesn't have one assigned.
  340. */
  341. virtual std::string VarName(
  342. unsigned int bank, unsigned int addr
  343. ) override{
  344. return VGears::FF7::NameLookup::FieldScriptVarName(bank, addr);
  345. }
  346. /**
  347. * Retrieves a user friendly entity name.
  348. *
  349. * @param entity[in] Entity name.
  350. * @return Friendly name for the entity, or the current name if it
  351. * doesn't have one assigned.
  352. */
  353. virtual std::string EntityName(const std::string& entity) override{
  354. return VGears::FF7::NameLookup::FieldScriptEntityName(entity);
  355. }
  356. /**
  357. * Retrieves a user friendly character name.
  358. *
  359. * @param char_id[in] Character ID.
  360. * @return Friendly name for the entity, or an empty string if it
  361. * doesn't have one assigned.
  362. */
  363. virtual std::string CharName(int char_id) override{
  364. return VGears::FF7::NameLookup::CharName(char_id);
  365. }
  366. /**
  367. * Retrieves a user friendly function name.
  368. *
  369. * @param entity[in] The name of the entity the function belongs to.
  370. * @param function_name[in] Function name.
  371. * @return Friendly name for the function, or the current name if it
  372. * doesn't have one assigned.
  373. */
  374. virtual std::string FunctionName(
  375. const std::string& entity, const std::string& function_name
  376. ) override{
  377. return VGears::FF7::NameLookup::FieldScriptFunctionName(
  378. field_name_, entity, function_name
  379. );
  380. }
  381. /**
  382. * Retrieves the header comment for a function in an entity.
  383. *
  384. * @param entity[in] The name of the entity the function belongs to.
  385. * @param function_name[in] Function name.
  386. * @return Friendly comment for the function, or an empty string if it
  387. * doesn't have one assigned.
  388. */
  389. virtual std::string FunctionComment(
  390. const std::string& entity, const std::string& function_name
  391. ) override{
  392. return VGears::FF7::NameLookup::FieldScriptFunctionComment(
  393. field_name_, entity, function_name
  394. );
  395. }
  396. protected:
  397. /**
  398. * The field name.
  399. */
  400. std::string field_name_;
  401. /**
  402. * The list of spawn points.
  403. */
  404. FieldSpawnPointsMap& spawn_points_;
  405. /**
  406. * Look up table relatingfield IDs and names.
  407. */
  408. const std::vector<std::string>& field_id_to_name_lookup_;
  409. };
  410. /**
  411. * Handles the formatting of field scripts.
  412. */
  413. class FF7FieldScriptFormatter : public BaseFF7FieldScriptFormatter{
  414. public:
  415. /**
  416. * Constructor.
  417. *
  418. * @param field_name[in] The field name.
  419. * @param models[in] The list of field models.
  420. * @param field_id_to_name_lookup[in] Field name lookup table to use.
  421. * @param spawn_points[in] The list of spawn points.
  422. */
  423. FF7FieldScriptFormatter(
  424. const std::string& field_name, const VGears::ModelListFilePtr& models,
  425. const std::vector<std::string>& field_id_to_name_lookup,
  426. FieldSpawnPointsMap& spawn_points
  427. ) :
  428. BaseFF7FieldScriptFormatter(field_name, field_id_to_name_lookup, spawn_points),
  429. model_loader_(models)
  430. {}
  431. // Names an animation, can't return empty
  432. /**
  433. * Retreieves an animation name from iths ID.
  434. *
  435. * @param char_id[in] ID of the character the animation belogs to.
  436. * @param id[in] Animation ID.
  437. * @return The animation name. If there is no name assigned, or it
  438. * can't be found, a string with the animation ID.
  439. */
  440. virtual std::string AnimationName(int char_id, int id) override{
  441. // Get the animation file name, then look up the friendly name of
  442. // the "raw" animation.
  443. if (static_cast<unsigned int>(char_id) >= model_loader_->GetModels().size()){
  444. std::cerr << "FF7FieldScriptFormatter::AnimationName ERROR:"
  445. << "Char ID " << char_id << " out of bounds" << std::endl;
  446. return std::to_string(id);
  447. }
  448. const auto& model_info = model_loader_->GetModels().at(char_id);
  449. if (static_cast<unsigned int>(id) >= model_info.animations.size()){
  450. std::cerr << "FF7FieldScriptFormatter::AnimationName ERROR:"
  451. << "In field " << field_name_ << " the model " << model_info.name
  452. << " animation with ID " << id << " is out of bounds ("
  453. << model_info.animations.size() << ")" << std::endl;
  454. return std::to_string(id);
  455. }
  456. const auto raw_name = model_info.animations.at(id).name;
  457. // Trim off ".yos" or whatever extension the model loader adds in.
  458. Ogre::String base_name;
  459. VGears::StringUtil::splitBase(raw_name, base_name);
  460. VGears::StringUtil::toLowerCase(base_name);
  461. return VGears::FF7::NameLookup::Animation(base_name);
  462. }
  463. private:
  464. /**
  465. * The field model loader.
  466. */
  467. const VGears::ModelListFilePtr& model_loader_;
  468. };
  469. /**
  470. * Creates a gateway script.
  471. *
  472. * @param gateway_entity_name[in] Name of the gateway entity.
  473. * @param target_map_name[in] Name of the target map.
  474. * @param source_spawn_point_name[in] The spawn point name.
  475. * @return Gateway LUA script.
  476. */
  477. static std::string CreateGateWayScript(
  478. const std::string& gateway_entity_name, const std::string& target_map_name,
  479. const std::string& source_spawn_point_name
  480. ){
  481. return
  482. "\nEntityContainer[ \"" + gateway_entity_name + "\" ] = {\n"
  483. "on_start = function(self)\n"
  484. " return 0\n"
  485. "end,\n"
  486. "on_enter_line = function(self, entity)\n"
  487. " return 0\n"
  488. "end,\n\n"
  489. "on_move_to_line = function(self, entity)\n"
  490. " return 0\n"
  491. "end,\n\n"
  492. "on_cross_line = function(self, entity)\n"
  493. " if entity == \"Cloud\" then\n"
  494. " if not FFVII.Data.DisableGateways then\n"
  495. " load_field_map_request(\""
  496. + target_map_name + "\", \"" + source_spawn_point_name + "\")\n"
  497. " end\n"
  498. " end\n\n"
  499. " return 0\n"
  500. "end,\n\n"
  501. "on_leave_line = function(self, entity)\n"
  502. " return 0\n"
  503. "end,\n"
  504. "}\n\n";
  505. }
  506. /**
  507. * Gateways to whis map ID are considered to be inactive.
  508. */
  509. const int INACTIVE_GATEWAY_ID = 32767;
  510. /**
  511. * Retrieves a field ID from a name.
  512. *
  513. * @param name[in] The name of the field.
  514. * @param field_id_to_name_lookup[in] Lookup table to use.
  515. * @return The field ID for the specified name.
  516. * @throws std::runtime_error If there is no ID for the field name.
  517. */
  518. static size_t FieldId(
  519. const std::string& name,
  520. const std::vector<std::string>& field_id_to_name_lookup
  521. ){
  522. for (size_t i = 0; i < field_id_to_name_lookup.size(); i ++)
  523. if (field_id_to_name_lookup[i] == name) return i;
  524. throw std::runtime_error("No Id found for field name");
  525. }
  526. /**
  527. * Retrievs a field scale factor.
  528. *
  529. * @param name[in] The name of the field.
  530. * @param field_id_to_name_lookup[in] Lookup table to use.
  531. * @return The field ID for the specified name.
  532. * @throws std::runtime_error If there is no ID for the field name.
  533. */
  534. static float FieldScaleFactor(
  535. const FieldScaleFactorMap& scale_factor_map, size_t field_id
  536. ){
  537. auto it = scale_factor_map.find(field_id);
  538. if (it == std::end(scale_factor_map))
  539. throw std::runtime_error("Scale factor not found for field id");
  540. return it->second;
  541. }
  542. /**
  543. * Converts a FFVII PC field to a V-Gears field.
  544. *
  545. * @param field_text_writter[in] The field text writer.
  546. * @param field[in] The field map.
  547. * @param out_dir[in] The path to the directory where to save the map file.
  548. * @param field_id_to_name_lookup[in] Lookup table used to relate map IDs and
  549. * names
  550. * @param spawn_map[in] List of spawn points.
  551. * @param scale_factor_map[in] List of scale factors.
  552. * @param model_animation_db[in] Database of model animations.
  553. * @param maps[out] The list of maps. The converted one will be added at the
  554. * end.
  555. * @param write_output_line[in] Functon to print output to console.
  556. */
  557. static void FF7PcFieldToVGearsField(
  558. FF7FieldTextWriter& field_text_writter,
  559. VGears::FLevelFilePtr& field,
  560. const std::string& out_dir,
  561. const std::vector<std::string>& field_id_to_name_lookup,
  562. FieldSpawnPointsMap& spawn_map,
  563. const FieldScaleFactorMap& scale_factor_map,
  564. ModelsAndAnimationsDb& model_animation_db,
  565. MapCollection& maps,
  566. std::function<void(std::string)> write_output_line
  567. ){
  568. // Generate triggers script to insert into main
  569. // decompiled FF7 field -> LUA script.
  570. std::string gateway_script_data;
  571. const VGears::TriggersFilePtr& triggers = field->GetTriggers();
  572. const auto& gateways = triggers->GetGateways();
  573. for (size_t i = 0; i < gateways.size(); i ++){
  574. const VGears::TriggersFile::Gateway& gateway = gateways[i];
  575. if (gateway.destination_field_id != INACTIVE_GATEWAY_ID){
  576. const std::string gateway_entity_name = "Gateway" + std::to_string(i);
  577. gateway_script_data += CreateGateWayScript(
  578. gateway_entity_name,
  579. FieldName(field_id_to_name_lookup.at(gateway.destination_field_id)),
  580. "Spawn_" + field->getName() + "_" + std::to_string(i)
  581. );
  582. }
  583. }
  584. const VGears::ModelListFilePtr& models = field->GetModelList();
  585. SUDM::Field::DecompiledScript decompiled;
  586. FF7FieldScriptFormatter formatter(field->getName(), models, field_id_to_name_lookup, spawn_map);
  587. try{
  588. // Get the raw script bytes.
  589. const std::vector<u8> raw_field_data = field->GetRawScript();
  590. // Decompile to LUA.
  591. decompiled = SUDM::Field::Decompile(
  592. field->getName(), raw_field_data, formatter, gateway_script_data,
  593. "EntityContainer = {}\n\n"
  594. );
  595. std::ofstream script_file(
  596. out_dir + "/" + FieldMapDir() + "/" + field->getName() + "/script.lua"
  597. );
  598. if (script_file.is_open()){
  599. script_file << decompiled.luaScript;
  600. try{field_text_writter.Write(raw_field_data, field->getName());}
  601. catch (const std::out_of_range& ex){
  602. write_output_line(
  603. "[ERROR] Failed to read texts from field " + field->getName() + ": " + ex.what()
  604. );
  605. std::cerr << "[ERROR] Failed to read texts from field "
  606. << field->getName() << ": " << ex.what() << std::endl;
  607. }
  608. }
  609. else{
  610. write_output_line(
  611. "[ERROR] Failed to open script file from field " + field->getName() + " for writing."
  612. );
  613. std::cerr << "[ERROR] Failed to open script file from field "
  614. << field->getName() << "for writing." << std::endl;
  615. }
  616. }
  617. catch (const ::InternalDecompilerError& ex){
  618. write_output_line(
  619. "[ERROR] Internal decompiler error in field " + field->getName() + ": " + ex.what()
  620. );
  621. std::cerr << "[ERROR] Internal decompiler error in field "
  622. << field->getName() << ": " << ex.what() << std::endl;
  623. }
  624. // Write out the map file which links all the other files together for a given field.
  625. {
  626. TiXmlDocument doc;
  627. std::unique_ptr<TiXmlElement> element(new TiXmlElement("map"));
  628. // Script.
  629. std::unique_ptr<TiXmlElement> xml_script_element(new TiXmlElement("script"));
  630. xml_script_element->SetAttribute(
  631. "file_name", FieldMapDir() + "/" + field->getName() + "/script.lua"
  632. );
  633. element->LinkEndChild(xml_script_element.release());
  634. // Background.
  635. std::unique_ptr<TiXmlElement> xml_background_2d(new TiXmlElement("background2d"));
  636. xml_background_2d->SetAttribute(
  637. "file_name", FieldMapDir() + "/" + field->getName() + "/bg.xml"
  638. );
  639. element->LinkEndChild(xml_background_2d.release());
  640. // Walkmesh.
  641. std::unique_ptr<TiXmlElement> xml_walkmesh(new TiXmlElement("walkmesh"));
  642. xml_walkmesh->SetAttribute(
  643. "file_name", FieldMapDir() + "/" + field->getName() + "/wm.xml"
  644. );
  645. element->LinkEndChild(xml_walkmesh.release());
  646. // Forward direction (angle player moves when "up" is pressed)
  647. std::unique_ptr<TiXmlElement> xml_forward_direction(new TiXmlElement("movement_rotation"));
  648. xml_forward_direction->SetAttribute(
  649. "degree", std::to_string(triggers->MovementRotation())
  650. );
  651. element->LinkEndChild(xml_forward_direction.release());
  652. // Get this fields Id.
  653. const size_t this_field_id = FieldId(field->getName(), field_id_to_name_lookup);
  654. // Use the ID to find the pre-computed list of gateways in all other fields that link to
  655. // this field.
  656. auto spawn_iterator = spawn_map.find(this_field_id);
  657. Ogre::Vector3 first_entity_point = Ogre::Vector3::ZERO;
  658. // If none found it probably just means no other fields have doors to this one.
  659. if (spawn_iterator != std::end(spawn_map)){
  660. const std::vector<SpawnPointDb::Record>& spawn_point_records
  661. = spawn_iterator->second.gateways_to_this_field;
  662. for (size_t i = 0; i < spawn_point_records.size(); i ++){
  663. const VGears::TriggersFile::Gateway& gateway = spawn_point_records[i].gateway;
  664. // Entity_point:
  665. std::unique_ptr<TiXmlElement> xml_entity_point(new TiXmlElement("entity_point"));
  666. if (spawn_point_records[i].from_script){
  667. // Spawn points from map jumps have a another algorithm.
  668. xml_entity_point->SetAttribute(
  669. "name",
  670. field_id_to_name_lookup.at(
  671. spawn_point_records[i].field_id) + "_" + spawn_point_records[i].entity_name
  672. + "_" + spawn_point_records[i].script_function_name + "_addr_"
  673. + std::to_string(spawn_point_records[i].gateway_index_or_map_jump_address)
  674. );
  675. }
  676. else{
  677. // Must also include the gateway index for the case where 2
  678. // fields have more than one door linking to each other.
  679. xml_entity_point->SetAttribute(
  680. "name",
  681. "Spawn_"
  682. + field_id_to_name_lookup.at(spawn_point_records[i].field_id)
  683. + "_"
  684. + std::to_string(spawn_point_records[i].gateway_index_or_map_jump_address)
  685. );
  686. }
  687. const float downscaler_next = 128.0f * FieldScaleFactor(
  688. scale_factor_map, gateway.destination_field_id
  689. );
  690. // Position Z is actually the target walkmesh triangle ID, so this is tiny bit more
  691. // complex. Now "get the Z value of the triangle with that ID". Note that ID
  692. // actually just means index.
  693. unsigned int triangle_index = static_cast<unsigned int>(gateway.destination.z);
  694. if (
  695. triangle_index >= field->GetWalkmesh()->GetTriangles().size()
  696. ){
  697. write_output_line(
  698. "[WARNING] In field " + field->getName() + ": Map jump triangle ("
  699. + std::to_string(triangle_index) + ") out of bounds ("
  700. + std::to_string(field->GetWalkmesh()->GetTriangles().size()) + ")"
  701. );
  702. std::cerr << "[WARNING] In field " << field->getName()
  703. << ": Map jump triangle (" << triangle_index << ") out of bounds ("
  704. << field->GetWalkmesh()->GetTriangles().size() << ")" << std::endl;
  705. triangle_index = 0;
  706. }
  707. const float z_of_triangle_with_id
  708. = field->GetWalkmesh()->GetTriangles().at(triangle_index).a.z;
  709. const Ogre::Vector3 position(
  710. gateway.destination.x / downscaler_next, gateway.destination.y / downscaler_next,
  711. z_of_triangle_with_id
  712. );
  713. if (position != Ogre::Vector3::ZERO && first_entity_point == Ogre::Vector3::ZERO)
  714. first_entity_point = position;
  715. xml_entity_point->SetAttribute(
  716. "position", Ogre::StringConverter::toString(position)
  717. );
  718. const float rotation = (360.0f * static_cast<float>(gateway.dir)) / 255.0f;
  719. xml_entity_point->SetAttribute("rotation", std::to_string(rotation));
  720. element->LinkEndChild(xml_entity_point.release());
  721. }
  722. }
  723. // Get lines. Add them to a list, so they aren't processed later as regular entities.
  724. std::vector<std::string> line_entities;
  725. for (SUDM::Field::Line line : decompiled.lines){
  726. std::unique_ptr<TiXmlElement> xml_entity_trigger(new TiXmlElement("entity_trigger"));
  727. line_entities.push_back(line.name);
  728. xml_entity_trigger->SetAttribute("name", line.name);
  729. xml_entity_trigger->SetAttribute(
  730. "point1",
  731. std::to_string(line.point_a[0] * FF7DataInstaller::LINE_SCALE_FACTOR)
  732. + " " + std::to_string(line.point_a[1] * FF7DataInstaller::LINE_SCALE_FACTOR)
  733. + " " + std::to_string(line.point_a[2] * FF7DataInstaller::LINE_SCALE_FACTOR)
  734. );
  735. xml_entity_trigger->SetAttribute(
  736. "point2",
  737. std::to_string(line.point_b[0] * FF7DataInstaller::LINE_SCALE_FACTOR)
  738. + " " + std::to_string(line.point_b[1] * FF7DataInstaller::LINE_SCALE_FACTOR)
  739. + " " + std::to_string(line.point_b[2] * FF7DataInstaller::LINE_SCALE_FACTOR)
  740. );
  741. xml_entity_trigger->SetAttribute("enabled", "true");
  742. element->LinkEndChild(xml_entity_trigger.release());
  743. }
  744. // Get entities.
  745. for (SUDM::Field::FieldEntity entity : decompiled.entities){
  746. // If the entity has been added as a line, skip.
  747. if (line_entities.size() > 0){
  748. if (*std::find(line_entities.begin(), line_entities.end(), entity.name) == entity.name) {
  749. continue;
  750. }
  751. }
  752. const int char_id = entity.char_id;
  753. if (char_id != -1){
  754. const VGears::ModelListFile::ModelDescription& desc
  755. = models->GetModels().at(char_id);
  756. auto& animations = model_animation_db.ModelAnimations(desc.hrc_name);
  757. for (auto& anim : desc.animations)
  758. animations.insert(model_animation_db.NormalizeAnimationName(anim.name));
  759. std::unique_ptr<TiXmlElement> xml_entity_script(new TiXmlElement("entity_model"));
  760. xml_entity_script->SetAttribute("name", entity.name);
  761. // TODO: Add to list of HRC's to convert, obtain name of converted .mesh file.
  762. auto lower_case_hrc_name = desc.hrc_name;
  763. VGears::StringUtil::toLowerCase(lower_case_hrc_name);
  764. xml_entity_script->SetAttribute(
  765. "file_name",
  766. FieldModelDir() + "/" + model_animation_db.ModelMetaDataName(lower_case_hrc_name)
  767. );
  768. xml_entity_script->SetAttribute("index", entity.index);
  769. if (desc.type == VGears::ModelListFile::PLAYER){
  770. // For player models the position is set manually in the xml because if a map
  771. // is loaded manually this is where the player will end up. Hence we set the
  772. // position to the first entity_point that we have.
  773. xml_entity_script->SetAttribute(
  774. "position", Ogre::StringConverter::toString(first_entity_point)
  775. );
  776. }
  777. else{
  778. xml_entity_script->SetAttribute(
  779. "position", Ogre::StringConverter::toString(Ogre::Vector3::ZERO)
  780. );
  781. }
  782. xml_entity_script->SetAttribute("direction", "0");
  783. // TODO: This isn't quite right, the models animation
  784. // translation seems to be inverted?
  785. xml_entity_script->SetAttribute("scale", "0.03125 0.03125 0.03125");
  786. xml_entity_script->SetAttribute(
  787. "root_orientation", "0.7071067811865476 0.7071067811865476 0 0"
  788. );
  789. element->LinkEndChild(xml_entity_script.release());
  790. }
  791. else{
  792. std::unique_ptr<TiXmlElement> xml_entity_script(new TiXmlElement("entity_script"));
  793. xml_entity_script->SetAttribute("name", entity.name);//it.first);
  794. element->LinkEndChild(xml_entity_script.release());
  795. }
  796. }
  797. const float downscaler_this = 128.0f * FieldScaleFactor(scale_factor_map, this_field_id);
  798. const auto& gateways = triggers->GetGateways();
  799. for (size_t i = 0; i < gateways.size(); i ++){
  800. const VGears::TriggersFile::Gateway& gateway = gateways[i];
  801. // If non-inactive gateway:
  802. if (gateway.destination_field_id != INACTIVE_GATEWAY_ID){
  803. std::unique_ptr<TiXmlElement> xml_entity_trigger(
  804. new TiXmlElement("entity_trigger")
  805. );
  806. xml_entity_trigger->SetAttribute("name", "Gateway" + std::to_string(i));
  807. xml_entity_trigger->SetAttribute(
  808. "point1",
  809. Ogre::StringConverter::toString(
  810. Ogre::Vector3(
  811. gateway.exit_line[0].x,
  812. gateway.exit_line[0].y,
  813. gateway.exit_line[0].z) / downscaler_this
  814. )
  815. );
  816. xml_entity_trigger->SetAttribute(
  817. "point2",
  818. Ogre::StringConverter::toString(
  819. Ogre::Vector3(
  820. gateway.exit_line[1].x,
  821. gateway.exit_line[1].y,
  822. gateway.exit_line[1].z) / downscaler_this
  823. )
  824. );
  825. // Enabled is hard coded to true.
  826. xml_entity_trigger->SetAttribute("enabled", "true");
  827. element->LinkEndChild(xml_entity_trigger.release());
  828. }
  829. }
  830. doc.LinkEndChild(element.release());
  831. doc.SaveFile(out_dir + "/" + FieldMapDir() + "/" + field->getName() + "/map.xml");
  832. const VGears::PaletteFilePtr& pal = field->GetPalette();
  833. const VGears::BackgroundFilePtr& bg = field->GetBackground();
  834. std::unique_ptr<Ogre::Image> bg_image(bg->CreateImage(pal));
  835. bg_image->encode("png");
  836. bg_image->save(out_dir + "/" + FieldMapDir() + "/" + field->getName() + "/tiles.png");
  837. {
  838. TiXmlDocument doc;
  839. std::unique_ptr<TiXmlElement> element(new TiXmlElement("background2d"));
  840. // Magic constants.
  841. const int BG_SCALE_UP_FACTOR = 3;
  842. const int BG_PSX_SCREEN_WIDTH = 320;
  843. const int BG_PSX_SCREEN_HEIGHT = 240;
  844. // Get texture atlas size.
  845. const int width = bg_image->getWidth();
  846. const int height = bg_image->getHeight();
  847. const VGears::CameraMatrixFilePtr& camera_matrix = field->GetCameraMatrix();
  848. const Ogre::Vector3 position
  849. = camera_matrix->GetPosition() / FieldScaleFactor(scale_factor_map, this_field_id);
  850. const Ogre::Quaternion orientation = camera_matrix->GetOrientation();
  851. const Ogre::Degree fov
  852. = camera_matrix->GetFov(static_cast<float>(BG_PSX_SCREEN_HEIGHT));
  853. const int min_x = triggers->GetCameraRange().left * BG_SCALE_UP_FACTOR;
  854. const int min_y = triggers->GetCameraRange().top * BG_SCALE_UP_FACTOR;
  855. const int max_x = triggers->GetCameraRange().right * BG_SCALE_UP_FACTOR;
  856. const int max_y = triggers->GetCameraRange().bottom * BG_SCALE_UP_FACTOR;
  857. element->SetAttribute("image", FieldMapDir() + "/" + field->getName() + "/tiles.png");
  858. element->SetAttribute("position", Ogre::StringConverter::toString(position));
  859. element->SetAttribute("orientation", Ogre::StringConverter::toString(orientation));
  860. element->SetAttribute("fov", Ogre::StringConverter::toString(fov));
  861. element->SetAttribute(
  862. "range",
  863. std::to_string(min_x) + " " + std::to_string(min_y) + " "
  864. + std::to_string(max_x) + " " + std::to_string(max_y)
  865. );
  866. element->SetAttribute(
  867. "clip",
  868. std::to_string(BG_PSX_SCREEN_WIDTH * BG_SCALE_UP_FACTOR) + " "
  869. + std::to_string(BG_PSX_SCREEN_HEIGHT * BG_SCALE_UP_FACTOR)
  870. );
  871. // Write out the *_BG.XML data.
  872. auto& layers = bg->GetLayers();
  873. for (const auto& layer : layers){
  874. // TODO: Should only the tiles to construct enabled layers be outputted?
  875. // if (layer.enabled){
  876. for (const VGears::BackgroundFile::SpriteData& sprite : layer.sprites){
  877. std::unique_ptr<TiXmlElement> xml_element(new TiXmlElement("tile"));
  878. xml_element->SetAttribute(
  879. "destination",
  880. Ogre::StringConverter::toString(sprite.dst.x * BG_SCALE_UP_FACTOR) + " "
  881. + Ogre::StringConverter::toString(sprite.dst.y * BG_SCALE_UP_FACTOR)
  882. );
  883. xml_element->SetAttribute(
  884. "width", Ogre::StringConverter::toString(sprite.width * BG_SCALE_UP_FACTOR)
  885. );
  886. xml_element->SetAttribute(
  887. "height", Ogre::StringConverter::toString(sprite.height * BG_SCALE_UP_FACTOR)
  888. );
  889. // Each tile is added to a big texture atlas with hard coded size of 1024x1024,
  890. // convert UV's to the 0.0f to 1.0f range.
  891. const float u0 = static_cast<float>(sprite.src.x) / bg_image->getWidth();
  892. const float v0 = static_cast<float>(sprite.src.y) / bg_image->getHeight();
  893. const float u1
  894. = static_cast<float>(sprite.src.x + sprite.width) / bg_image->getWidth();
  895. const float v1
  896. = static_cast<float>(sprite.src.y + sprite.height) / bg_image->getHeight();
  897. xml_element->SetAttribute(
  898. "uv",
  899. Ogre::StringConverter::toString(u0) + " "
  900. + Ogre::StringConverter::toString(v0) + " "
  901. + Ogre::StringConverter::toString(u1) + " "
  902. + Ogre::StringConverter::toString(v1)
  903. );
  904. // TODO: It works (on FFVII PC), but why this number?
  905. xml_element->SetAttribute(
  906. "depth",
  907. Ogre::StringConverter::toString(static_cast<float>(sprite.depth) * (0.03125f))
  908. );
  909. // TODO: Copied from DatFile::AddTile.
  910. // Add to common method.
  911. Ogre::String blending_str = "";
  912. if (sprite.blending == 0) blending_str = "alpha";
  913. // 3 is source + 0.25 * destination:
  914. else if (sprite.blending == 1 || sprite.blending == 3) blending_str = "add";
  915. else if (sprite.blending == 2) blending_str = "subtract";
  916. // TODO: Should probably throw to fail conversion:
  917. else blending_str = "unknown";
  918. xml_element->SetAttribute("blending", blending_str);
  919. element->LinkEndChild(xml_element.release());
  920. }
  921. //}
  922. }
  923. doc.LinkEndChild(element.release());
  924. doc.SaveFile(out_dir + "/" + FieldMapDir() + "/" + field->getName() + "/bg.xml");
  925. }
  926. }
  927. {
  928. // Save out the walk mesh as XML.
  929. const VGears::WalkmeshFilePtr& walkmesh = field->GetWalkmesh();
  930. TiXmlDocument doc;
  931. std::unique_ptr<TiXmlElement> element(new TiXmlElement("walkmesh"));
  932. for (VGears::WalkmeshFile::Triangle& tri : walkmesh->GetTriangles()){
  933. std::unique_ptr<TiXmlElement> xml_element(new TiXmlElement("triangle"));
  934. xml_element->SetAttribute("a", Ogre::StringConverter::toString(tri.a));
  935. xml_element->SetAttribute("b", Ogre::StringConverter::toString(tri.b));
  936. xml_element->SetAttribute("c", Ogre::StringConverter::toString(tri.c));
  937. xml_element->SetAttribute("a_b", std::to_string(tri.access_side[0]));
  938. xml_element->SetAttribute("b_c", std::to_string(tri.access_side[1]));
  939. xml_element->SetAttribute("c_a", std::to_string(tri.access_side[2]));
  940. element->LinkEndChild(xml_element.release());
  941. }
  942. doc.LinkEndChild(element.release());
  943. doc.SaveFile(out_dir + "/" + FieldMapDir() + "/" + field->getName() + "/wm.xml");
  944. }
  945. maps.insert(field->getName());
  946. }
  947. /**
  948. * Handles the script gateway colection.
  949. */
  950. class FF7FieldScriptGatewayCollector : public BaseFF7FieldScriptFormatter{
  951. // TODO: Move declaration to a .h file.
  952. public:
  953. /**
  954. * Cosntructor.
  955. */
  956. FF7FieldScriptGatewayCollector(
  957. const std::string& field_name, const std::vector<std::string>& field_id_to_name_lookup,
  958. FieldSpawnPointsMap& spawn_points, size_t this_field_id
  959. ) :
  960. BaseFF7FieldScriptFormatter(field_name,field_id_to_name_lookup, spawn_points),
  961. field_id_(this_field_id)
  962. {}
  963. /**
  964. * Adds an spawn point.
  965. *
  966. * If the spawn is new, a new record will be added to the spawn point
  967. * database. If it was already there, update the record to add the
  968. * origin.
  969. *
  970. * @param map_id[in] The target map ID.
  971. * @param entity[in] The entity that acts as the spawn point.
  972. * @param function_name[in] The spawn function name.
  973. * @param address[in] @todo Understand and document.
  974. * @param x[in] X coordinate of the field target at which to spawn.
  975. * @param y[in] Y coordinate of the field target at which to spawn.
  976. * @param z[in] Z coordinate of the field target at which to spawn.
  977. * @param angle[in] Orientation at which to spawn.
  978. */
  979. virtual void AddSpawnPoint(
  980. unsigned int map_id, const std::string& entity, const std::string& function_name,
  981. unsigned int address, int x, int y, int z, int angle
  982. ) override {
  983. auto it = spawn_points_.find(map_id);
  984. VGears::TriggersFile::Gateway gw = {};
  985. gw.destination_field_id = map_id;
  986. gw.destination.x = x;
  987. gw.destination.y = y;
  988. gw.destination.z = z;
  989. gw.dir = angle;
  990. if (it != std::end(spawn_points_)){
  991. // Add to the list of gateways that link to destination_field_id.
  992. SpawnPointDb::Record rec;
  993. rec.from_script = true;
  994. rec.field_id = field_id_;
  995. rec.gateway = gw;
  996. rec.entity_name = entity;
  997. rec.script_function_name = function_name;
  998. rec.gateway_index_or_map_jump_address = address;
  999. it->second.gateways_to_this_field.push_back(rec);
  1000. }
  1001. else{
  1002. // Create a new record for destination_field_id
  1003. SpawnPointDb db;
  1004. db.target_field_id = map_id;
  1005. SpawnPointDb::Record rec;
  1006. rec.from_script = true;
  1007. rec.field_id = field_id_;
  1008. rec.gateway = gw;
  1009. rec.entity_name = entity;
  1010. rec.script_function_name = function_name;
  1011. rec.gateway_index_or_map_jump_address = address;
  1012. db.gateways_to_this_field.push_back(rec);
  1013. spawn_points_.insert(std::make_pair(db.target_field_id, db));
  1014. }
  1015. }
  1016. private:
  1017. /**
  1018. * The field map ID.
  1019. */
  1020. size_t field_id_;
  1021. };
  1022. /**
  1023. * Collects the spawn point from a field.
  1024. *
  1025. * If a spawn is new, a new record will be added to the spawn point database.
  1026. * If it was already there, update the record to add the origin.
  1027. *
  1028. * @param field[in] The field to collect from.
  1029. * @param field_id_to_name_lookup[in] Lookup table to relate field IDs and
  1030. * names.
  1031. * @param spawn_points[out] Spawn points will be added sequentially to the end
  1032. * of this map.
  1033. */
  1034. static void CollectSpawnPoints(
  1035. VGears::FLevelFilePtr& field, const std::vector<std::string>& field_id_to_name_lookup,
  1036. FieldSpawnPointsMap& spawn_points
  1037. ){
  1038. const size_t this_field_id = FieldId(field->getName(), field_id_to_name_lookup);
  1039. // Decompile the script just so the MAPJUMPs can be collected. This is needed to construct the
  1040. // full list of entries to each field.
  1041. try{
  1042. // Get the raw script bytes.
  1043. SUDM::Field::DecompiledScript decompiled;
  1044. const std::vector<u8> raw_field_data = field->GetRawScript();
  1045. // Decompile to LUA.
  1046. FF7FieldScriptGatewayCollector formatter(
  1047. field->getName(), field_id_to_name_lookup, spawn_points, this_field_id
  1048. );
  1049. decompiled = SUDM::Field::Decompile(
  1050. field->getName(), raw_field_data, formatter, "", "EntityContainer = {}\n\n"
  1051. );
  1052. }
  1053. catch (const ::InternalDecompilerError& ex){
  1054. std::cerr << "CollectSpawnPoints: InternalDecompilerError: " << ex.what() << std::endl;
  1055. }
  1056. const VGears::TriggersFilePtr& triggers = field->GetTriggers();
  1057. const auto& gateways = triggers->GetGateways();
  1058. for (size_t i = 0; i < gateways.size(); i ++){
  1059. const VGears::TriggersFile::Gateway& gateway = gateways[i];
  1060. if (gateway.destination_field_id != INACTIVE_GATEWAY_ID){
  1061. auto it = spawn_points.find(gateway.destination_field_id);
  1062. if (it != std::end(spawn_points)){
  1063. // Add to the list of gateways that link to
  1064. // destination_field_id.
  1065. SpawnPointDb::Record rec;
  1066. rec.field_id = this_field_id;
  1067. rec.gateway = gateway;
  1068. rec.gateway_index_or_map_jump_address = i;
  1069. it->second.gateways_to_this_field.push_back(rec);
  1070. }
  1071. else{
  1072. // Create a new record for destination_field_id.
  1073. SpawnPointDb db;
  1074. db.target_field_id = gateway.destination_field_id;
  1075. SpawnPointDb::Record rec;
  1076. rec.field_id = this_field_id;
  1077. rec.gateway = gateway;
  1078. rec.gateway_index_or_map_jump_address = i;
  1079. db.gateways_to_this_field.push_back(rec);
  1080. spawn_points.insert(std::make_pair(db.target_field_id, db));
  1081. }
  1082. }
  1083. }
  1084. }
  1085. /**
  1086. * Checks if a file is a field file.
  1087. *
  1088. * To verify it, it just checks that the extension is not '.tex', '.tut' or
  1089. * '.siz' and that the name is not 'maplist'.
  1090. *
  1091. * @param resource_name[in] The name of the file to test.
  1092. * @return False if the file is surely not a field file, true otherwise.
  1093. */
  1094. static bool IsAFieldFile(Ogre::String& resource_name){
  1095. return (
  1096. !VGears::StringUtil::endsWith(resource_name, ".tex")
  1097. && !VGears::StringUtil::endsWith(resource_name, ".tut")
  1098. && !VGears::StringUtil::endsWith(resource_name, ".siz")
  1099. && resource_name != "maplist"
  1100. );
  1101. }
  1102. /**
  1103. * Checks if a map cause fatal crash bugs.
  1104. *
  1105. * It uses a hardcoded list of maps that are known to cause errors. This can be
  1106. * removed when whatever is causing the crash(s) is fixed.
  1107. *
  1108. * NOTE: Even so, conversion of all models will fail with a bone index out of
  1109. * bounds.
  1110. *
  1111. * @param resource_name[in] The name of the map file to test.
  1112. * @return True if the map will crash, false otherwise.
  1113. */
  1114. static bool WillCrash(Ogre::String& resource_name){
  1115. if (
  1116. resource_name == "bugin1a" // crashes in back ground image generation
  1117. || resource_name == "frcyo" // Hangs in decompliation
  1118. || resource_name == "fr_e" // Background image crash
  1119. || resource_name == "las4_2" // Background image crash
  1120. || resource_name == "las4_3" // Background image crash
  1121. || resource_name == "lastmap" // Background image crash
  1122. || resource_name == "md_e1" // Background image crash
  1123. || resource_name == "trnad_3" // Background image crash
  1124. ){return true;}
  1125. return false;
  1126. }
  1127. /**
  1128. * Checks if a map is a test field.
  1129. *
  1130. * It uses a hardcoded list of maps that are known to be test maps. Unused maps
  1131. * are considered to be test maps.
  1132. *
  1133. * @param resource_name[in] The name of the map file to test.
  1134. * @return True if the map is a test map, false otherwise.
  1135. */
  1136. static bool IsTestField(Ogre::String& resource_name){
  1137. if (
  1138. resource_name == "startmap"
  1139. || resource_name == "md1stin"
  1140. || resource_name == "md1_1"
  1141. || resource_name == "md1_2"
  1142. || resource_name == "md8_1"
  1143. || resource_name == "nmkin_1"
  1144. || resource_name == "nmkin_2"
  1145. || resource_name == "nmkin_3"
  1146. || resource_name == "nmkin_4"
  1147. || resource_name == "nmkin_5"
  1148. || resource_name == "nrthmk"
  1149. || resource_name == "elevtr1"
  1150. || resource_name == "tin_1"
  1151. || resource_name == "tin_2"
  1152. || resource_name == "rootmap"
  1153. || resource_name == "md8_4"
  1154. ){return true;}
  1155. return false;
  1156. }
  1157. /**
  1158. * Collects the scale factor from a map.
  1159. *
  1160. * @param field[in] The field to collect from.
  1161. * @param scale_factors[out] The scale factor for the map will be set here, in
  1162. * the appropiate map entry.
  1163. * @param field_id_to_name_lookup[in] Lookup table to relate field IDs and
  1164. * names.
  1165. */
  1166. static void CollectFieldScaleFactors(
  1167. VGears::FLevelFilePtr& field, FieldScaleFactorMap& scale_factors,
  1168. const std::vector<std::string>& field_id_to_name_lookup
  1169. ){
  1170. scale_factors[FieldId(field->getName(), field_id_to_name_lookup)]
  1171. = ::SUDM::Field::ScaleFactor(field->GetRawScript());
  1172. }
  1173. void FF7DataInstaller::InitCollectSpawnAndScaleFactors(){
  1174. progress_step_num_elements_ = 1;
  1175. CreateDir(FieldMapDir());
  1176. CreateDir(FieldModelDir());
  1177. // List what's in the LGP archive.
  1178. flevel_file_list_ = application_.ResMgr()->listResourceNames("FFVIIFields", "*");
  1179. // Load the map list field.
  1180. VGears::MapListFilePtr map_list
  1181. = VGears::MapListFileManager::GetSingleton().load(
  1182. "maplist", "FFVIIFields"
  1183. ).staticCast<VGears::MapListFile>();
  1184. map_list_ = map_list->GetMapList();
  1185. iterator_counter_ = 0;
  1186. conversion_step_ = 0;
  1187. installation_state_ = SPAWN_POINTS_AND_SCALE_FACTORS;
  1188. }
  1189. void FF7DataInstaller::CollectionFieldSpawnAndScaleFactors(){
  1190. // On the first pass collate required field information.
  1191. progress_step_num_elements_ = flevel_file_list_->size();
  1192. if (iterator_counter_ < flevel_file_list_->size()){
  1193. auto resource_name = (*flevel_file_list_)[iterator_counter_];
  1194. // Exclude things that are not fields.
  1195. //if (IsAFieldFile(resource_name) /*&& IsTestField(resource_name)*/){
  1196. // TODO: DEBUG: Only testing fields.
  1197. if (IsAFieldFile(resource_name) && IsTestField(resource_name)){
  1198. conversion_step_ ++;
  1199. if (conversion_step_ == 1){
  1200. field_ = VGears::LZSFLevelFileManager::GetSingleton().load(
  1201. resource_name, "FFVIIFields"
  1202. ).staticCast<VGears::FLevelFile>();
  1203. //write_output_line("Load field " + resource_name);
  1204. return;
  1205. }
  1206. if (conversion_step_ == 2){
  1207. //write_output_line("Read spawn points from scripts");
  1208. CollectSpawnPoints(field_, map_list_, spawn_points_);
  1209. return;
  1210. }
  1211. if (conversion_step_ == 3){
  1212. //write_output_line("Read scale factor");
  1213. CollectFieldScaleFactors(field_, scale_factors_, map_list_);
  1214. conversion_step_ = 0;
  1215. iterator_counter_ ++;
  1216. return;
  1217. }
  1218. }
  1219. else iterator_counter_ ++;
  1220. }
  1221. else{
  1222. field_.reset();
  1223. iterator_counter_ = 0;
  1224. installation_state_ = CONVERT_FIELDS;
  1225. field_text_writer_.Begin(output_dir_ + "/texts/english/dialogs_mission1.xml");
  1226. }
  1227. }
  1228. void FF7DataInstaller::ConvertFieldsIteration(){
  1229. // Do the full conversion with the collated data.
  1230. progress_step_num_elements_ = flevel_file_list_->size();
  1231. if (iterator_counter_ < flevel_file_list_->size()){
  1232. auto resource_name = (*flevel_file_list_)[iterator_counter_];
  1233. // Exclude things that are not fields.
  1234. if (IsAFieldFile(resource_name)){
  1235. //if (/*IsTestField(resource_name) &&*/ !WillCrash(resource_name)){
  1236. // TODO: DEBUG: Only test fields
  1237. if (IsTestField(resource_name) && !WillCrash(resource_name)){
  1238. //write_output_line("Converting field " + resource_name);
  1239. std::cout << " - Converting field: " << resource_name << std::endl;
  1240. CreateDir(FieldMapDir() + "/" + resource_name);
  1241. VGears::FLevelFilePtr field
  1242. = VGears::LZSFLevelFileManager::GetSingleton().load(
  1243. resource_name, "FFVIIFields"
  1244. ).staticCast<VGears::FLevelFile>();
  1245. FF7PcFieldToVGearsField(
  1246. field_text_writer_, field, output_dir_, map_list_, spawn_points_, scale_factors_,
  1247. used_models_and_anims_, converted_map_list_, write_output_line
  1248. );
  1249. }
  1250. else{
  1251. write_output_line(
  1252. "[ERROR] Skip field " + resource_name + " due to crash or hang issue."
  1253. );
  1254. std::cerr << "[ERROR] Skip field: " << resource_name
  1255. << " due to crash or hang issue." << std::endl;
  1256. }
  1257. }
  1258. iterator_counter_ ++;
  1259. }
  1260. else{
  1261. iterator_counter_ = 0;
  1262. installation_state_ = WRITE_MAPS_INIT;
  1263. field_text_writer_.End();
  1264. }
  1265. }
  1266. void FF7DataInstaller::WriteMapsXmlBegin(){
  1267. // Write out maps.xml.
  1268. progress_step_num_elements_ = 1;
  1269. doc_ = std::make_unique<TiXmlDocument>();
  1270. element_ = std::make_unique<TiXmlElement>("maps");
  1271. iterator_counter_ = 0;
  1272. installation_state_ = WRITE_MAPS;
  1273. iterator_counter_ = 0;
  1274. converted_map_list_iterator_ = converted_map_list_.begin();
  1275. }
  1276. void FF7DataInstaller::WriteMapsXmlIteration(){
  1277. // TODO: Probably need to inject "empty" and "test" fields.
  1278. progress_step_num_elements_ = converted_map_list_.size();
  1279. if (converted_map_list_iterator_ != converted_map_list_.end()){
  1280. auto map = *converted_map_list_iterator_;
  1281. write_output_line("Writing field " + FieldName(map));
  1282. std::unique_ptr<TiXmlElement> xml_element(new TiXmlElement("map"));
  1283. xml_element->SetAttribute("name", FieldName(map));
  1284. xml_element->SetAttribute("file_name", FieldMapDir() + "/" + map + "/map.xml");
  1285. element_->LinkEndChild(xml_element.release());
  1286. iterator_counter_++;
  1287. converted_map_list_iterator_++;
  1288. }
  1289. else installation_state_ = WRITE_MAPS_CLEAN;
  1290. }
  1291. void FF7DataInstaller::EndWriteMapsXml(){
  1292. progress_step_num_elements_ = 1;
  1293. doc_->LinkEndChild(element_.release());
  1294. doc_->SaveFile(output_dir_ + "/maps.xml");
  1295. installation_state_ = CONVERT_FIELD_MODELS_INIT;
  1296. }
  1297. void FF7DataInstaller::ConvertFieldModelsBegin(){
  1298. // TODO: Convert models and animations in model_animation_db.
  1299. progress_step_num_elements_ = 1;
  1300. field_models_lgp_ = std::make_unique<ScopedLgp>(
  1301. application_.getRoot(), input_dir_ + "field/char.lgp", "LGP", "FFVII"
  1302. );
  1303. field_model_file_list_ = application_.ResMgr()->listResourceNames("FFVII", "*");
  1304. iterator_counter_ = 0;
  1305. installation_state_ = CONVERT_FIELD_MODELS;
  1306. iterator_counter_ = 0;
  1307. conversion_step_ = 0;
  1308. model_animation_map_iterator_ = used_models_and_anims_.map.begin();
  1309. }
  1310. void FF7DataInstaller::ConvertFieldModelsIteration(){
  1311. progress_step_num_elements_ = used_models_and_anims_.map.size();
  1312. if (model_animation_map_iterator_ != used_models_and_anims_.map.end()){
  1313. if (conversion_step_ == 0){
  1314. write_output_line("Converting model " + model_animation_map_iterator_->first);
  1315. conversion_step_++;
  1316. }
  1317. else{
  1318. try{
  1319. Ogre::ResourcePtr hrc = VGears::HRCFileManager::GetSingleton().load(
  1320. model_animation_map_iterator_->first, "FFVII"
  1321. );
  1322. Ogre::String base_name;
  1323. VGears::StringUtil::splitBase(model_animation_map_iterator_->first, base_name);
  1324. auto mesh_name = VGears::FF7::NameLookup::model(base_name) + ".mesh";
  1325. Ogre::MeshPtr mesh(Ogre::MeshManager::getSingleton().load(mesh_name, "FFVII"));
  1326. Ogre::SkeletonPtr skeleton(mesh->getSkeleton());
  1327. for (auto& anim : model_animation_map_iterator_->second){
  1328. VGears::AFileManager &afl_mgr(VGears::AFileManager::GetSingleton());
  1329. VGears::AFilePtr a = afl_mgr.load(anim, "FFVII").staticCast<VGears::AFile>();
  1330. // Convert the FF7 name to a more readable name set in the meta data.
  1331. Ogre::String base_name;
  1332. VGears::StringUtil::splitBase(anim, base_name);
  1333. a->AddTo(skeleton, VGears::FF7::NameLookup::Animation(base_name));
  1334. }
  1335. ExportMesh(output_dir_ + "/" + FieldModelDir() + "/", mesh);
  1336. }
  1337. catch (const Ogre::Exception& ex){
  1338. write_output_line(
  1339. "[ERROR] Ogre exception converting model "
  1340. + model_animation_map_iterator_->first + ": " + ex.what()
  1341. );
  1342. std::cerr << "[ERROR] Ogre exception converting model "
  1343. << model_animation_map_iterator_->first <<": " << ex.what() << std::endl;
  1344. }
  1345. catch (const std::exception& ex){
  1346. write_output_line(
  1347. "[ERROR] Exception converting model "
  1348. + model_animation_map_iterator_->first + ": " + ex.what()
  1349. );
  1350. std::cerr << "[ERROR] Exception converting model "
  1351. << model_animation_map_iterator_->first << ": " << ex.what() << std::endl;
  1352. }
  1353. iterator_counter_++;
  1354. model_animation_map_iterator_++;
  1355. conversion_step_ = 0;
  1356. }
  1357. }
  1358. else{
  1359. std::cout << "Installation finished." << std::endl;
  1360. write_output_line("Installation finished.");
  1361. installation_state_ = DONE;
  1362. }
  1363. }