DataInstaller.cpp 60 KB

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