DataInstaller.cpp 62 KB

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