ff7DataInstaller.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  1. #include "ff7DataInstaller.h"
  2. #include <boost/filesystem.hpp>
  3. #include <boost/program_options.hpp>
  4. #include <OgreConfigFile.h>
  5. #include <OgreArchiveManager.h>
  6. #include <OgreMeshSerializer.h>
  7. #include <OgreSkeletonSerializer.h>
  8. #include <OgreMeshManager.h>
  9. #include <OgreRenderWindow.h>
  10. #include <OgreHardwarePixelBuffer.h>
  11. #include "QGearsGameState.h"
  12. #include "data/QGearsAFileManager.h"
  13. #include "data/QGearsBackgroundFileManager.h"
  14. #include "data/QGearsCameraMatrixFileManager.h"
  15. #include "data/QGearsHRCFileManager.h"
  16. #include "data/QGearsLZSFLevelFileManager.h"
  17. #include "data/QGearsPaletteFileManager.h"
  18. #include "data/QGearsPFileManager.h"
  19. #include "data/QGearsRSDFileManager.h"
  20. #include "data/QGearsTexCodec.h"
  21. #include "map/QGearsBackground2DFileManager.h"
  22. #include "map/QGearsWalkmeshFileManager.h"
  23. #include "data/FF7ModelListFileManager.h"
  24. #include "data/QGearsLGPArchiveFactory.h"
  25. #include "common/QGearsStringUtil.h"
  26. #include "common/FF7NameLookup.h"
  27. #include "data/QGearsTexCodec.h"
  28. #include "data/QGearsMapListFile.h"
  29. #include "decompiler/sudm.h"
  30. #include <memory>
  31. FF7DataInstaller::FF7DataInstaller()
  32. #ifdef _DEBUG
  33. : mApp("plugins_d.cfg", "resources_d.cfg", "install_d.log")
  34. #else
  35. : mApp("plugins.cfg", "resources.cfg", "install.log")
  36. #endif
  37. {
  38. mApp.initOgre(true);
  39. }
  40. FF7DataInstaller::~FF7DataInstaller()
  41. {
  42. }
  43. void FF7DataInstaller::Convert(std::string inputDir, std::string outputDir, const std::vector<std::string>& files)
  44. {
  45. for (const auto& file : files)
  46. {
  47. if (file == "field/char.lgp")
  48. {
  49. /*
  50. auto fullPath = inputDir + file;
  51. mApp.getRoot()->addResourceLocation(fullPath, "LGP", "FFVII");
  52. ConvertFieldModels(fullPath, outputDir);
  53. mApp.getRoot()->removeResourceLocation(fullPath, "FFVII");*/
  54. }
  55. else if (file == "field/flevel.lgp")
  56. {
  57. auto fullPath = inputDir + file;
  58. mApp.getRoot()->addResourceLocation(fullPath, "LGP", "FFVIIFields");
  59. ConvertFields(fullPath, outputDir);
  60. mApp.getRoot()->removeResourceLocation(fullPath, "FFVIIFields");
  61. }
  62. }
  63. }
  64. // TOOD: Share with pc model exporter
  65. static void exportMesh(std::string outdir, const Ogre::MeshPtr &mesh)
  66. {
  67. Ogre::MeshSerializer mesh_ser;
  68. mesh_ser.exportMesh(mesh.getPointer(), outdir + mesh->getName());
  69. Ogre::SkeletonPtr skeleton(mesh->getSkeleton());
  70. Ogre::SkeletonSerializer sk_ser;
  71. sk_ser.exportSkeleton(skeleton.getPointer(), outdir + skeleton->getName());
  72. Ogre::Mesh::SubMeshIterator it(mesh->getSubMeshIterator());
  73. Ogre::MaterialSerializer mat_ser;
  74. size_t i(0);
  75. while (it.hasMoreElements())
  76. {
  77. Ogre::SubMesh *sub_mesh(it.getNext());
  78. Ogre::MaterialPtr mat(Ogre::MaterialManager::getSingleton().getByName(sub_mesh->getMaterialName()));
  79. if (!mat.isNull())
  80. {
  81. mat_ser.queueForExport(mat);
  82. }
  83. ++i;
  84. }
  85. QGears::String base_name;
  86. QGears::StringUtil::splitFull(mesh->getName(), base_name);
  87. mat_ser.exportQueued(outdir + base_name + QGears::EXT_MATERIAL);
  88. }
  89. void FF7DataInstaller::ConvertFieldModels(std::string archive, std::string outDir)
  90. {
  91. Ogre::StringVectorPtr resources = mApp.ResMgr()->listResourceNames("FFVII", "*.hrc");
  92. for (auto& resourceName : *resources)
  93. {
  94. if (QGears::StringUtil::endsWith(resourceName, ".hrc"))
  95. {
  96. //try
  97. {
  98. Ogre::ResourcePtr hrc = QGears::HRCFileManager::getSingleton().load(resourceName, "FFVII");
  99. Ogre::String baseName;
  100. QGears::StringUtil::splitBase(resourceName, baseName);
  101. auto meshName = QGears::FF7::NameLookup::model(baseName) + ".mesh";
  102. Ogre::MeshPtr mesh(Ogre::MeshManager::getSingleton().load(meshName, "FFVII"));
  103. Ogre::SkeletonPtr skeleton(mesh->getSkeleton());
  104. exportMesh(outDir, mesh);
  105. }
  106. // catch (const Ogre::Exception& ex)
  107. {
  108. // std::cout << "ERROR converting: " << resourceName << " Exception: " << ex.what() << std::endl;
  109. }
  110. }
  111. }
  112. /*
  113. TODO:
  114. To figure out what .a files relate to which .hrc files, we need to enumerate every field
  115. and check its model loader data. These are always Idle, Walk, Run and then field specific
  116. ordering.
  117. Model loader is section 3 of the field file.
  118. */
  119. }
  120. class FF7FieldScriptFormatter : public SUDM::IScriptFormatter
  121. {
  122. public:
  123. // Renames a variable, return empty string for generated name
  124. virtual std::string VarName(unsigned int bank, unsigned int addr) override
  125. {
  126. return "";
  127. }
  128. // Renames an entity
  129. virtual std::string EntityName(const std::string& entity) override
  130. {
  131. return entity;
  132. }
  133. // Renames an animation, return empty string for generated name
  134. virtual std::string AnimationName(int id) override
  135. {
  136. return "";
  137. }
  138. // Renames a function in an entity
  139. virtual std::string FunctionName(const std::string& entity, const std::string& funcName) override
  140. {
  141. return funcName;
  142. }
  143. // Sets the header comment for a function in an entity
  144. virtual std::string FunctionComment(const std::string& entity, const std::string& funcName) override
  145. {
  146. return "";
  147. }
  148. };
  149. const int kInactiveGateWayId = 32767;
  150. class SpawnPointDb
  151. {
  152. public:
  153. // Id of the field the gateways records from N other number of fields are linking to
  154. u16 mTargetFieldId = 0;
  155. class Record
  156. {
  157. public:
  158. u16 mFieldId = 0;
  159. u32 GatewayIndex = 0;
  160. QGears::TriggersFile::Gateway mGateway;
  161. };
  162. std::vector<Record> mGatewaysToThisField;
  163. };
  164. typedef std::map<u16, SpawnPointDb> FieldSpawnPointsMap;
  165. static size_t FieldId(const std::string& name, const std::vector<std::string>& fieldIdToNameLookup)
  166. {
  167. for (size_t i = 0; i < fieldIdToNameLookup.size(); i++)
  168. {
  169. if (fieldIdToNameLookup[i] == name)
  170. {
  171. return i;
  172. }
  173. }
  174. throw std::runtime_error("No Id found for field name");
  175. }
  176. static void FF7PcFieldToQGearsField(QGears::FLevelFilePtr& field, const std::string& outDir, const std::vector<std::string>& fieldIdToNameLookup, const FieldSpawnPointsMap& spawnMap)
  177. {
  178. // TODO: Insert triggers into LUA script
  179. SUDM::FF7::Field::DecompiledScript decompiled;
  180. try
  181. {
  182. // Get the raw script bytes
  183. const std::vector<u8> rawFieldData = field->getRawScript();
  184. // Decompile to LUA
  185. FF7FieldScriptFormatter formatter;
  186. decompiled = SUDM::FF7::Field::Decompile(field->getName(), rawFieldData, formatter);
  187. std::ofstream scriptFile(outDir + "/" + field->getName() + ".lua");
  188. if (scriptFile.is_open())
  189. {
  190. scriptFile << decompiled.luaScript;
  191. }
  192. else
  193. {
  194. std::cerr << "Failed to open script file for writing" << std::endl;
  195. }
  196. }
  197. catch (const ::InternalDecompilerError& ex)
  198. {
  199. std::cerr << "InternalDecompilerError: " << ex.what() << std::endl;
  200. }
  201. const QGears::TriggersFilePtr& triggers = field->getTriggers();
  202. // Write out the map file which links all the other files together for a given field
  203. {
  204. TiXmlDocument doc;
  205. std::unique_ptr<TiXmlElement> element(new TiXmlElement("map"));
  206. // Script
  207. std::unique_ptr<TiXmlElement> xmlScriptElement(new TiXmlElement("script"));
  208. xmlScriptElement->SetAttribute("file_name", field->getName() + ".lua");
  209. element->LinkEndChild(xmlScriptElement.release());
  210. // Background
  211. std::unique_ptr<TiXmlElement> xmlBackground2d(new TiXmlElement("background2d"));
  212. xmlBackground2d->SetAttribute("file_name", field->getName() + "_bg.xml");
  213. element->LinkEndChild(xmlBackground2d.release());
  214. // walkmesh
  215. std::unique_ptr<TiXmlElement> xmlWalkmesh(new TiXmlElement("walkmesh"));
  216. xmlWalkmesh->SetAttribute("file_name", field->getName() + "_wm.xml");
  217. element->LinkEndChild(xmlWalkmesh.release());
  218. // Angle player moves when "up" is pressed
  219. std::unique_ptr<TiXmlElement> xmlMovementRotation(new TiXmlElement("movement_rotation"));
  220. xmlMovementRotation->SetAttribute("degree", std::to_string(triggers->MovementRotation()));
  221. element->LinkEndChild(xmlMovementRotation.release());
  222. const QGears::ModelListFilePtr& models = field->getModelList();
  223. for (const auto& it : decompiled.entities)
  224. {
  225. const int charId = it.second;
  226. if (charId != -1)
  227. {
  228. const QGears::ModelListFile::ModelDescription& desc = models->getModels().at(charId);
  229. std::unique_ptr<TiXmlElement> xmlEntityScript(new TiXmlElement("entity_model"));
  230. xmlEntityScript->SetAttribute("name", it.first);
  231. // TODO: Add to list of HRC's to convert, obtain name of target converted .mesh file
  232. xmlEntityScript->SetAttribute("file_name", desc.hrc_name);
  233. // TODO: entity_model - name, file_name, position, direction
  234. // We set char 1 position to be position of first entity_point so player is spawned in sane
  235. // position if map is manually loaded via console.
  236. // None player chars set their first position in the init scripts. We know its a entity_model
  237. // because it uses PC opcode in init script.
  238. // entity_manager:get_entity("cl") is done via CHAR opcode
  239. xmlEntityScript->SetAttribute("position", "0 0 0");
  240. xmlEntityScript->SetAttribute("direction", "0");
  241. element->LinkEndChild(xmlEntityScript.release());
  242. }
  243. else
  244. {
  245. std::unique_ptr<TiXmlElement> xmlEntityScript(new TiXmlElement("entity_script"));
  246. xmlEntityScript->SetAttribute("name", it.first);
  247. element->LinkEndChild(xmlEntityScript.release());
  248. }
  249. }
  250. // TODO: Get these scales from the field game data, 1024 for md1_1 and 512 for md1_2, DAT CFG has them as 2 and 1?
  251. const float downscaler_next = 128.0f; // * MapIdToScale( map_id );
  252. const float downscaler_this = 128.0f; // * field.scale
  253. const auto& gateways = triggers->GetGateways();
  254. for (size_t i = 0; i < gateways.size(); i++)
  255. {
  256. const QGears::TriggersFile::Gateway& gateway = gateways[i];
  257. // if not inactive gateway
  258. if (gateway.destinationFieldId != kInactiveGateWayId)
  259. {
  260. std::unique_ptr<TiXmlElement> xmlEntityTrigger(new TiXmlElement("entity_trigger"));
  261. xmlEntityTrigger->SetAttribute("name", "Gateway" + std::to_string(i));
  262. xmlEntityTrigger->SetAttribute("point1",
  263. Ogre::StringConverter::toString(
  264. Ogre::Vector3(gateway.exit_line[0].x, gateway.exit_line[0].y, gateway.exit_line[0].z) / downscaler_this));
  265. xmlEntityTrigger->SetAttribute("point2",
  266. Ogre::StringConverter::toString(
  267. Ogre::Vector3(gateway.exit_line[1].x, gateway.exit_line[1].y, gateway.exit_line[1].z) / downscaler_this));
  268. // enabled hard coded to true
  269. xmlEntityTrigger->SetAttribute("enabled", "true");
  270. element->LinkEndChild(xmlEntityTrigger.release());
  271. }
  272. }
  273. // Get this fields Id
  274. const size_t thisFieldId = FieldId(field->getName(), fieldIdToNameLookup);
  275. // Use that to find the pre-computed list of gateways in all other fields that link to this field
  276. auto spawnIterator = spawnMap.find(thisFieldId);
  277. // If not found that it probably just means no other fields have doors to this one
  278. if (spawnIterator != std::end(spawnMap))
  279. {
  280. const std::vector<SpawnPointDb::Record>& spawnPointRecords = spawnIterator->second.mGatewaysToThisField;
  281. for (size_t i = 0; i < spawnPointRecords.size(); i++)
  282. {
  283. const QGears::TriggersFile::Gateway& gateway = spawnPointRecords[i].mGateway;
  284. // entity_point
  285. std::unique_ptr<TiXmlElement> xmlEntityPoint(new TiXmlElement("entity_point"));
  286. // Must also include the gateway index for the case where 2 fields have more than one door linking to each other
  287. xmlEntityPoint->SetAttribute("name", "Spawn_" + fieldIdToNameLookup.at(spawnPointRecords[i].mFieldId) + "_" + std::to_string(spawnPointRecords[i].GatewayIndex));
  288. xmlEntityPoint->SetAttribute("position",
  289. Ogre::StringConverter::toString(
  290. Ogre::Vector3(gateway.destination.x, gateway.destination.y, gateway.destination.z) / downscaler_next));
  291. const float rotation = (360.0f * static_cast<float>(gateway.dir)) / 255.0f;
  292. xmlEntityPoint->SetAttribute("rotation", std::to_string(rotation));
  293. element->LinkEndChild(xmlEntityPoint.release());
  294. }
  295. }
  296. doc.LinkEndChild(element.release());
  297. doc.SaveFile(outDir + "/" + field->getName() + ".xml");
  298. }
  299. const QGears::PaletteFilePtr& pal = field->getPalette();
  300. const QGears::BackgroundFilePtr& bg = field->getBackground();
  301. std::unique_ptr<Ogre::Image> bgImage(bg->createImage(pal));
  302. bgImage->save(outDir + "/" + field->getName() + ".png");
  303. {
  304. TiXmlDocument doc;
  305. std::unique_ptr<TiXmlElement> element(new TiXmlElement("background2d"));
  306. // Magic constants
  307. const int kScaleUpFactor = 3;
  308. const int kPsxScreenWidth = 320;
  309. const int kPsxScreenHeight = 240;
  310. // Get texture atlas size
  311. const int width = bgImage->getWidth();
  312. const int height = bgImage->getHeight();
  313. const QGears::CameraMatrixFilePtr& camMatrix = field->getCameraMatrix();
  314. const Ogre::Vector3 position = camMatrix->getPosition();
  315. const Ogre::Quaternion orientation = camMatrix->getOrientation();
  316. const Ogre::Degree fov = camMatrix->getFov(static_cast<float>(kPsxScreenHeight));
  317. const int min_x = triggers->getCameraRange().left * kScaleUpFactor;
  318. const int min_y = triggers->getCameraRange().top * kScaleUpFactor;
  319. const int max_x = triggers->getCameraRange().right * kScaleUpFactor;
  320. const int max_y = triggers->getCameraRange().bottom * kScaleUpFactor;
  321. element->SetAttribute("image", field->getName() + ".png");
  322. element->SetAttribute("position", Ogre::StringConverter::toString(position));
  323. element->SetAttribute("orientation", Ogre::StringConverter::toString(orientation));
  324. element->SetAttribute("fov", Ogre::StringConverter::toString(fov));
  325. element->SetAttribute("range", std::to_string(min_x) + " " + std::to_string(min_y) + " " + std::to_string(max_x) + " " + std::to_string(max_y));
  326. element->SetAttribute("clip", std::to_string(kPsxScreenWidth * kScaleUpFactor) + " " + std::to_string(kPsxScreenHeight * kScaleUpFactor));
  327. // Write out the *_BG.XML data
  328. auto& layers = bg->getLayers();
  329. for (const auto& layer : layers)
  330. {
  331. // TODO: Should we only output tiles to construct enabled layers?
  332. // if (layer.enabled)
  333. {
  334. for (const QGears::BackgroundFile::SpriteData& sprite : layer.sprites)
  335. {
  336. std::unique_ptr<TiXmlElement> xmlElement(new TiXmlElement("tile"));
  337. xmlElement->SetAttribute("destination",
  338. Ogre::StringConverter::toString(sprite.dst.x * kScaleUpFactor) + " " +
  339. Ogre::StringConverter::toString(sprite.dst.y * kScaleUpFactor));
  340. xmlElement->SetAttribute("width", Ogre::StringConverter::toString(sprite.width * kScaleUpFactor));
  341. xmlElement->SetAttribute("height", Ogre::StringConverter::toString(sprite.height * kScaleUpFactor));
  342. // Each tile is added to a big texture atlas with hard coded size of 1024x1024, convert UV's to the 0.0f to 1.0f range
  343. const float u0 = static_cast<float>(sprite.src.x) / bgImage->getWidth();
  344. const float v0 = static_cast<float>(sprite.src.y) / bgImage->getHeight();
  345. const float u1 = static_cast<float>(sprite.src.x + sprite.width) / bgImage->getWidth();
  346. const float v1 = static_cast<float>(sprite.src.y + sprite.height) / bgImage->getHeight();
  347. xmlElement->SetAttribute("uv",
  348. Ogre::StringConverter::toString(u0) + " " + Ogre::StringConverter::toString(v0) + " " +
  349. Ogre::StringConverter::toString(u1) + " " + Ogre::StringConverter::toString(v1)
  350. );
  351. // TODO: Figure out how to scale this value correctly
  352. xmlElement->SetAttribute("depth", "999" /* Ogre::StringConverter::toString(static_cast<float>(sprite.depth) / (4096.0f/10.0f))*/);
  353. // TODO: Copied from DatFile::AddTile - add to common method
  354. Ogre::String blending_str = "";
  355. if (sprite.blending == 0)
  356. {
  357. blending_str = "alpha";
  358. }
  359. else if (sprite.blending == 1 || sprite.blending == 3) // 3 is source + 0.25 * destination
  360. {
  361. blending_str = "add";
  362. }
  363. else if (sprite.blending == 2)
  364. {
  365. blending_str = "subtract";
  366. }
  367. else
  368. {
  369. // TODO: Should probably throw to fail conversion
  370. blending_str = "unknown";
  371. }
  372. xmlElement->SetAttribute("blending", blending_str);
  373. element->LinkEndChild(xmlElement.release());
  374. }
  375. }
  376. }
  377. doc.LinkEndChild(element.release());
  378. doc.SaveFile(outDir + "/" + field->getName() + "_bg.xml");
  379. }
  380. {
  381. // Save out the walk mesh as XML
  382. const QGears::WalkmeshFilePtr& walkmesh = field->getWalkmesh();
  383. TiXmlDocument doc;
  384. std::unique_ptr<TiXmlElement> element(new TiXmlElement("walkmesh"));
  385. for (QGears::WalkmeshFile::Triangle& tri : walkmesh->getTriangles())
  386. {
  387. std::unique_ptr<TiXmlElement> xmlElement(new TiXmlElement("triangle"));
  388. xmlElement->SetAttribute("a", Ogre::StringConverter::toString(tri.a));
  389. xmlElement->SetAttribute("b", Ogre::StringConverter::toString(tri.b));
  390. xmlElement->SetAttribute("c", Ogre::StringConverter::toString(tri.c));
  391. xmlElement->SetAttribute("a_b", std::to_string(tri.access_side[0]));
  392. xmlElement->SetAttribute("b_c", std::to_string(tri.access_side[1]));
  393. xmlElement->SetAttribute("c_a", std::to_string(tri.access_side[2]));
  394. element->LinkEndChild(xmlElement.release());
  395. }
  396. doc.LinkEndChild(element.release());
  397. doc.SaveFile(outDir + "/" + field->getName() + "_wm.xml");
  398. }
  399. }
  400. static void CollectSpawnPoints(QGears::FLevelFilePtr& field, const std::vector<std::string>& fieldIdToNameLookup, FieldSpawnPointsMap& spawnPoints)
  401. {
  402. const size_t thisFieldId = FieldId(field->getName(), fieldIdToNameLookup);
  403. const QGears::TriggersFilePtr& triggers = field->getTriggers();
  404. const auto& gateways = triggers->GetGateways();
  405. for (size_t i = 0; i < gateways.size(); i++)
  406. {
  407. const QGears::TriggersFile::Gateway& gateway = gateways[i];
  408. if (gateway.destinationFieldId != kInactiveGateWayId)
  409. {
  410. auto it = spawnPoints.find(gateway.destinationFieldId);
  411. if (it != std::end(spawnPoints))
  412. {
  413. // Add to the list of gateways that link to destinationFieldId
  414. SpawnPointDb::Record rec;
  415. rec.mFieldId = thisFieldId;
  416. rec.mGateway = gateway;
  417. rec.GatewayIndex = i;
  418. it->second.mGatewaysToThisField.push_back(rec);
  419. }
  420. else
  421. {
  422. // Create a new record for destinationFieldId
  423. SpawnPointDb db;
  424. db.mTargetFieldId = gateway.destinationFieldId;
  425. SpawnPointDb::Record rec;
  426. rec.mFieldId = thisFieldId;
  427. rec.mGateway = gateway;
  428. rec.GatewayIndex = i;
  429. db.mGatewaysToThisField.push_back(rec);
  430. spawnPoints.insert(std::make_pair(db.mTargetFieldId, db));
  431. }
  432. }
  433. }
  434. }
  435. static bool IsAFieldFile(Ogre::String& resourceName)
  436. {
  437. return (!QGears::StringUtil::endsWith(resourceName, ".tex")
  438. && !QGears::StringUtil::endsWith(resourceName, ".tut")
  439. && !QGears::StringUtil::endsWith(resourceName, ".siz")
  440. && resourceName != "maplist");
  441. }
  442. void FF7DataInstaller::ConvertFields(std::string archive, std::string outDir)
  443. {
  444. // List whats in the LGP archive
  445. Ogre::StringVectorPtr resources = mApp.ResMgr()->listResourceNames("FFVIIFields", "*");
  446. // Load the map list field
  447. QGears::MapListFilePtr mapList = QGears::MapListFileManager::getSingleton().load("maplist", "FFVIIFields").staticCast<QGears::MapListFile>();
  448. // On the first pass collate required field information
  449. FieldSpawnPointsMap spawnPoints;
  450. for (auto& resourceName : *resources)
  451. {
  452. // Exclude things that are not fields
  453. if (IsAFieldFile(resourceName))
  454. {
  455. QGears::FLevelFilePtr field = QGears::LZSFLevelFileManager::getSingleton().load(resourceName, "FFVIIFields").staticCast<QGears::FLevelFile>();
  456. CollectSpawnPoints(field, mapList->GetMapList(), spawnPoints);
  457. }
  458. }
  459. // Now we can do the full conversion with the collated data
  460. for (auto& resourceName : *resources)
  461. {
  462. // Exclude things that are not fields
  463. if (IsAFieldFile(resourceName))
  464. {
  465. if (resourceName == "md1_2") // Testing conversion only on this field for now
  466. {
  467. QGears::FLevelFilePtr field = QGears::LZSFLevelFileManager::getSingleton().load(resourceName, "FFVIIFields").staticCast<QGears::FLevelFile>();
  468. FF7PcFieldToQGearsField(field, outDir, mapList->GetMapList(), spawnPoints);
  469. }
  470. }
  471. }
  472. }