Răsfoiți Sursa

Installer improvements:

 - [Media]: Sound effects are all named during installation. They can have multiple name and refered by each of them.
 - [Battle]: Battle model animation scripts (*ab files) extracted and partially decompiled into Lua scripts during installation.
 - [Battle]: Spell model files extracted during installation. Not processed yet.
Iñigo Valentin 3 ani în urmă
părinte
comite
39e03aeaa6

+ 15 - 2
src/core/AudioManager.cpp

@@ -16,6 +16,8 @@
 #include <iostream>
 #include <list>
 #include <boost/thread.hpp>
+#include <boost/algorithm/string.hpp>
+#include <boost/algorithm/string/split.hpp>
 #include "core/AudioManager.h"
 #include "core/Event.h"
 #include "core/XmlMusicsFile.h"
@@ -215,8 +217,19 @@ AudioManager::Music* AudioManager::GetMusic(const Ogre::String& name){
 
 AudioManager::Sound* AudioManager::GetSound(const Ogre::String& name){
     boost::recursive_mutex::scoped_lock lock(update_mutex_);
-    for (auto it = sound_list_.begin(); it != sound_list_.end(); ++ it)
-        if (it->name == name) return &(*it);
+    for (auto it = sound_list_.begin(); it != sound_list_.end(); ++ it){
+        if (boost::algorithm::to_lower_copy(it->name) == boost::algorithm::to_lower_copy(name))
+            return &(*it);
+        if (it->name.find("|") != std::string::npos){
+            std::vector<std::string> names;
+            boost::split(
+              names, boost::algorithm::to_lower_copy(it->name),
+              boost::is_any_of("|"), boost::token_compress_on
+            );
+            for (int n = 0; n < names.size(); n ++)
+                if (names[n] == boost::algorithm::to_lower_copy(name)) return &(*it);
+        }
+    }
     return nullptr;
 }
 

+ 1 - 1
src/core/EntityManager.h

@@ -467,7 +467,7 @@ class EntityManager : public Manager, public Ogre::Singleton<EntityManager>{
          * @param[in] name Entity name.
          * @param[in] file_name Path to the entity model file.
          * @param[in] position Entity position.
-         * @param[in] rotation Entity face direction.
+         * @param[in] orientation Entity face direction.
          * @param[in] scale Entity scale.
          * @param[in] root_orientation The node orientation.
          * @param[in] index Index of the entity.

+ 269 - 31
src/installer/BattleDataInstaller.cpp

@@ -26,6 +26,7 @@
 #include "TexFile.h"
 #include "data/VGearsHRCFileManager.h"
 #include "data/VGearsAFileManager.h"
+#include "data/AbFile.h"
 #include "data/DaFile.h"
 #include "data/FF7Data.h"
 #include "common/VGearsStringUtil.h"
@@ -70,12 +71,12 @@ unsigned int BattleDataInstaller::InitializeScenes(){
     return total_scenes_;
 }
 
-unsigned int BattleDataInstaller::InitializeModels(){
+unsigned int BattleDataInstaller::InitializeBattleModels(){
     next_model_to_process_ = 0;
     next_model_to_convert_ = 0;
     battle_lgp_files_.clear();
     battle_lgp_file_names_.clear();
-    models_.clear();
+    battle_models_.clear();
     // Open battle.lgp
     File battle_lgp_file(input_dir_ + "data/battle/battle.lgp");
     // Also, open it as a LGP archive.
@@ -94,6 +95,30 @@ unsigned int BattleDataInstaller::InitializeModels(){
     return battle_lgp_files_.size();
 }
 
+unsigned int BattleDataInstaller::InitializeSpellModels(){
+    next_model_to_process_ = 0;
+    next_model_to_convert_ = 0;
+    battle_lgp_files_.clear();
+    battle_lgp_file_names_.clear();
+    battle_models_.clear();
+    // Open battle.lgp
+    File magic_lgp_file(input_dir_ + "data/battle/magic.lgp");
+    // Also, open it as a LGP archive.
+    VGears::LGPArchive magic_lgp(input_dir_ + "data/battle/magic.lgp", "LGP");
+    magic_lgp.open(input_dir_ + "data/battle/magic.lgp", true);
+    magic_lgp.load();
+    VGears::LGPArchive::FileList file_list = magic_lgp.GetFiles();
+    for (int i = 0; i < file_list.size(); i ++){
+        VGears::LGPArchive::FileEntry f = file_list.at(i);
+        if (f.data_offset + f.data_size <= magic_lgp_file.GetFileSize()){
+            File m_lgp_file(&magic_lgp_file, f.data_offset, f.data_size);
+            magic_lgp_files_.push_back(m_lgp_file);
+            magic_lgp_file_names_.push_back(f.file_name);
+        }
+    }
+    return magic_lgp_files_.size();
+}
+
 unsigned int BattleDataInstaller::ProcessScene(){
     if (next_scene_ >= scenes_uncompressed_.size()) return total_scenes_;
     BattleSceneFile scene(next_scene_, scenes_uncompressed_[next_scene_]);
@@ -134,14 +159,14 @@ unsigned int BattleDataInstaller::ProcessScene(){
     return next_scene_;
 }
 
-unsigned int BattleDataInstaller::ProcessModel(){
+unsigned int BattleDataInstaller::ProcessBattleModel(){
     if (next_model_to_process_ >= battle_lgp_files_.size()) return battle_lgp_files_.size();
     // TODO: Do something with the files.
     // A little explanation. Each file has a 4 letter name, for example: 1234
     // 12 is the model identifier.
     // 34 if the type of file:
     //  - aa: Skeleton file (.hrc)
-    //  - ab: Unknown
+    //  - ab: Animation scripts
     //  - ac - al: Textures (.tex)
     //  - am - cz: Polygon files (.p)
     //  - da: Animations (.anim)
@@ -159,9 +184,9 @@ unsigned int BattleDataInstaller::ProcessModel(){
           + battle_lgp_file_names_[next_model_to_process_] + ".hrcbin"
         );
         bool found = false;
-        for (int m = 0; m < models_.size(); m ++){
-            if (models_[m].id == id){
-                models_[m].hrc = battle_lgp_file_names_[next_model_to_process_] + ".hrc";
+        for (int m = 0; m < battle_models_.size(); m ++){
+            if (battle_models_[m].id == id){
+                battle_models_[m].hrc = battle_lgp_file_names_[next_model_to_process_] + ".hrc";
                 found = true;
                 break;
             }
@@ -170,25 +195,40 @@ unsigned int BattleDataInstaller::ProcessModel(){
             Model model;
             model.id = id;
             model.hrc = battle_lgp_file_names_[next_model_to_process_] + ".hrc";
-            models_.push_back(model);
+            battle_models_.push_back(model);
         }
     }
     else if (type == "ab"){
         battle_lgp_files_[next_model_to_process_].WriteFile(
           output_dir_ + "temp/battle_models/"
-          + battle_lgp_file_names_[next_model_to_process_] + ".unknown"
+          + battle_lgp_file_names_[next_model_to_process_] + ".script"
         );
-    } // Unknown and not needed.
+        bool found = false;
+        for (int m = 0; m < battle_models_.size(); m ++){
+            if (battle_models_[m].id == id){
+                battle_models_[m].script
+                  = battle_lgp_file_names_[next_model_to_process_] + ".script";
+                found = true;
+                break;
+            }
+        }
+        if (found == false){
+            Model model;
+            model.id = id;
+            model.script = battle_lgp_file_names_[next_model_to_process_] + ".script";
+            battle_models_.push_back(model);
+        }
+    }
     else if (type == "da"){
-        // .a Animation file.
+        // .da Animation file.
         battle_lgp_files_[next_model_to_process_].WriteFile(
           output_dir_ + "temp/battle_models/"
           + battle_lgp_file_names_[next_model_to_process_] + ".anim"
         );
         bool found = false;
-        for (int m = 0; m < models_.size(); m ++){
-            if (models_[m].id == id){
-                models_[m].anim = battle_lgp_file_names_[next_model_to_process_] + ".anim";
+        for (int m = 0; m < battle_models_.size(); m ++){
+            if (battle_models_[m].id == id){
+                battle_models_[m].anim = battle_lgp_file_names_[next_model_to_process_] + ".anim";
                 found = true;
                 break;
             }
@@ -197,7 +237,7 @@ unsigned int BattleDataInstaller::ProcessModel(){
             Model model;
             model.id = id;
             model.anim = battle_lgp_file_names_[next_model_to_process_] + ".anim";
-            models_.push_back(model);
+            battle_models_.push_back(model);
         }
     }
     else if (
@@ -260,9 +300,9 @@ unsigned int BattleDataInstaller::ProcessModel(){
           id + type + "_" + info.name_normal + ".png", "Texture", "FFVIITextures"
         );
         bool found = false;
-        for (int m = 0; m < models_.size(); m ++){
-            if (models_[m].id == id){
-                models_[m].tex.push_back(id + type+ "_" + info.name_normal + ".png");
+        for (int m = 0; m < battle_models_.size(); m ++){
+            if (battle_models_[m].id == id){
+                battle_models_[m].tex.push_back(id + type+ "_" + info.name_normal + ".png");
                 found = true;
                 break;
             }
@@ -271,7 +311,7 @@ unsigned int BattleDataInstaller::ProcessModel(){
             Model model;
             model.id = id;
             model.tex.push_back(id + type + "_" + info.name_normal + ".png");
-            models_.push_back(model);
+            battle_models_.push_back(model);
         }
     }
     else{
@@ -281,9 +321,9 @@ unsigned int BattleDataInstaller::ProcessModel(){
           + battle_lgp_file_names_[next_model_to_process_] + ".p"
         );
         bool found = false;
-        for (int m = 0; m < models_.size(); m ++){
-            if (models_[m].id == id){
-                models_[m].p.push_back(battle_lgp_file_names_[next_model_to_process_] + ".p");
+        for (int m = 0; m < battle_models_.size(); m ++){
+            if (battle_models_[m].id == id){
+                battle_models_[m].p.push_back(battle_lgp_file_names_[next_model_to_process_] + ".p");
                 found = true;
                 break;
             }
@@ -292,21 +332,152 @@ unsigned int BattleDataInstaller::ProcessModel(){
             Model model;
             model.id = id;
             model.p.push_back(battle_lgp_file_names_[next_model_to_process_] + ".p");
-            models_.push_back(model);
+            battle_models_.push_back(model);
+        }
+    }
+    next_model_to_process_ ++;
+    return next_model_to_process_;
+}
+
+unsigned int BattleDataInstaller::ProcessSpellModel(){
+    if (next_model_to_process_ >= magic_lgp_files_.size()) return magic_lgp_files_.size();
+    // TODO: Do something with the files.
+    // A little explanation. Each file has a 4 letter name, for example: 1234
+    // 12 is the model identifier.
+    // 34 if the type of file:
+    //  - aa: Skeleton file (.hrc)
+    //  - ab: Unknown
+    //  - ac - al: Textures (.tex)
+    //  - am - cz: Polygon files (.p)
+    //  - da: Animations (.anim)
+    //std::cout << "SPELL MODEL FILE: " << magic_lgp_file_names_[next_model_to_process_] << std::endl;
+    std::string id = magic_lgp_file_names_[next_model_to_process_].substr(0, 2);
+    /*if (id != "rt"){ // TODO DEBUG REMOVE
+        next_model_to_process_ ++;
+        return next_model_to_process_;
+    }*/
+    std::string fname = magic_lgp_file_names_[next_model_to_process_];
+    FF7Data::SpellModelInfo info = FF7Data::GetSpellModelInfo(id);
+    magic_lgp_files_[next_model_to_process_].WriteFile(output_dir_ + "temp/spell_models/" + fname);
+    // Decompile skeleton files
+    if (fname.length() > 2 && fname.substr(fname.length() - 2, 2) == ".d"){
+        std::string out_name = fname.substr(0, fname.length() - 2) + ".hrc";
+        /*DecompileHrc(
+          File(output_dir_ + "temp/spell_models/" + fname), model,
+          output_dir_ + "temp/spell_models/" + out_name,
+          model.id + "_" + info.name_normal
+        );*/
+    }
+    /*if (fname.substr(fname.length() - 2, 2) == ".d"){
+        // HRC file, skeleton
+        magic_lgp_files_[next_model_to_process_].WriteFile(
+          output_dir_ + "temp/spell_models/"
+          + magic_lgp_file_names_[next_model_to_process_] + ".hrcbin"
+        );
+        bool found = false;
+        for (int m = 0; m < spell_models_.size(); m ++){
+            if (spell_models_[m].id == id){
+                spell_models_[m].hrc = magic_lgp_file_names_[next_model_to_process_] + ".hrc";
+                found = true;
+                break;
+            }
+        }
+        if (found == false){
+            Model model;
+            model.id = id;
+            model.hrc = magic_lgp_file_names_[next_model_to_process_] + ".hrc";
+            spell_models_.push_back(model);
+        }
+    }
+    else if (fname.substr(fname.length() - 4, 2) == ".a"){
+        // .a Animation file.
+        magic_lgp_files_[next_model_to_process_].WriteFile(
+          output_dir_ + "temp/spell_models/"
+          + magic_lgp_file_names_[next_model_to_process_] + ".anim"
+        );
+        bool found = false;
+        for (int m = 0; m < spell_models_.size(); m ++){
+            if (spell_models_[m].id == id){
+                spell_models_[m].anim = magic_lgp_file_names_[next_model_to_process_] + ".anim";
+                found = true;
+                break;
+            }
+        }
+        if (found == false){
+            Model model;
+            model.id = id;
+            model.anim = magic_lgp_file_names_[next_model_to_process_] + ".anim";
+            spell_models_.push_back(model);
+        }
+    }
+    else if (fname.substr(fname.length() - 4, 4) == ".tex"){
+        // .tex texture files. Save directly to their directory.
+        magic_lgp_files_[next_model_to_process_].WriteFile(
+          output_dir_ + "temp/spell_models/"
+          + magic_lgp_file_names_[next_model_to_process_] + ".tex"
+        );
+        TexFile tex(magic_lgp_files_[next_model_to_process_]);
+        std::string path = output_dir_ + "models/battle/attacks/";
+        path += (id + "_" + info.name_normal + ".png");
+        tex.SavePng(path);
+        res_mgr_->declareResource(
+          id + "_" + info.name_normal + ".png", "Texture", "FFVIITextures"
+        );
+        bool found = false;
+        for (int m = 0; m < spell_models_.size(); m ++){
+            if (spell_models_[m].id == id){
+                spell_models_[m].tex.push_back(id + "_" + info.name_normal + ".png");
+                found = true;
+                break;
+            }
+        }
+        if (found == false){
+            Model model;
+            model.id = id;
+            model.tex.push_back(id + "_" + info.name_normal + ".png");
+            spell_models_.push_back(model);
         }
     }
+    else if (
+      fname.substr(fname.length() - 4, 2) == ".p" || fname.substr(fname.length() - 2, 2) == ".p"
+    ){
+        // .p polygon file.
+        magic_lgp_files_[next_model_to_process_].WriteFile(
+          output_dir_ + "temp/spell_models/"
+          + magic_lgp_file_names_[next_model_to_process_] + ".p"
+        );
+        bool found = false;
+        for (int m = 0; m < spell_models_.size(); m ++){
+            if (spell_models_[m].id == id){
+                spell_models_[m].p.push_back(magic_lgp_file_names_[next_model_to_process_] + ".p");
+                found = true;
+                break;
+            }
+        }
+        if (found == false){
+            Model model;
+            model.id = id;
+            model.p.push_back(magic_lgp_file_names_[next_model_to_process_] + ".p");
+            spell_models_.push_back(model);
+        }
+    }*/
     next_model_to_process_ ++;
     return next_model_to_process_;
 }
 
-unsigned int BattleDataInstaller::ConvertModelsInit(){
+unsigned int BattleDataInstaller::ConvertBattleModelsInit(){
+    next_model_to_convert_ = 0;
+    return battle_models_.size();
+}
+
+unsigned int BattleDataInstaller::ConvertSpellModelsInit(){
     next_model_to_convert_ = 0;
-    return models_.size();
+    return spell_models_.size();
 }
 
-unsigned int BattleDataInstaller::ConvertModel(){
-    if (next_model_to_convert_ >= models_.size()) return models_.size();
-    Model model = models_[next_model_to_convert_];
+unsigned int BattleDataInstaller::ConvertBattleModel(){
+    if (next_model_to_convert_ >= battle_models_.size()) return battle_models_.size();
+    Model model = battle_models_[next_model_to_convert_];
     try{
         std::string path = "";
         FF7Data::BattleModelInfo info = FF7Data::GetBattleModelInfo(model.id);
@@ -330,7 +501,6 @@ unsigned int BattleDataInstaller::ConvertModel(){
         GenerateRsdFiles(model, output_dir_ + "temp/battle_models/");
 
         if ("" != model.anim){ // Skip for non-animated, ie battle backgrounds
-            //std::cout << " GENERATE ANIMATIONS FOR "  << model.id << std::endl;
             DaFile da(File(output_dir_ + "temp/battle_models/" + model.anim));
             std::vector<std::string> a_files = da.GenerateAFiles(
               model.id, output_dir_ + "temp/battle_models/"
@@ -338,6 +508,14 @@ unsigned int BattleDataInstaller::ConvertModel(){
             for (std::string file_name : a_files) model.a.push_back(file_name);
         }
 
+        if ("" != model.script){ // Skip non scripted models
+            std::cout << "AB FILE: " << model.script << std::endl;
+            AbFile ab(File(output_dir_ + "temp/battle_models/" + model.script), info.is_enemy);
+            ab.GenerateScripts(
+                model.id + "_" + info.name_normal, output_dir_ + "scripts/battle_models/"
+            );
+        }
+
         // Reload the resources for the newly created hrc and rsd.
         // TODO: Could this be done with res_mgr_->declareResource() ? Probably faster
         res_mgr_->removeResourceLocation(output_dir_ + "temp/battle_models/", "FFVII");
@@ -377,6 +555,66 @@ unsigned int BattleDataInstaller::ConvertModel(){
     return next_model_to_convert_;
 }
 
+unsigned int BattleDataInstaller::ConvertSpellModel(){
+    if (next_model_to_convert_ >= spell_models_.size()) return spell_models_.size();
+    Model model = spell_models_[next_model_to_convert_];
+    try{
+        std::string path = "attacks/";
+        FF7Data::SpellModelInfo info = FF7Data::GetSpellModelInfo(model.id);
+        DecompileHrc(
+          File(output_dir_ + "temp/spell_models/" + model.hrc + "bin"), model,
+          output_dir_ + "temp/spell_models/" + model.id + "_" + info.name_normal + ".hrc",
+          model.id + "_" + info.name_normal
+        );
+        GenerateRsdFiles(model, output_dir_ + "temp/spell_models/");
+
+        if ("" != model.anim){ // Skip for non-animated, ie battle backgrounds
+            DaFile da(File(output_dir_ + "temp/spell_models/" + model.anim));
+            std::vector<std::string> a_files = da.GenerateAFiles(
+              model.id, output_dir_ + "temp/spell_models/"
+            );
+            for (std::string file_name : a_files) model.a.push_back(file_name);
+        }
+
+        // Reload the resources for the newly created hrc and rsd.
+        // TODO: Could this be done with res_mgr_->declareResource() ? Probably faster
+        res_mgr_->removeResourceLocation(output_dir_ + "temp/spell_models/", "FFVII");
+        res_mgr_->addResourceLocation(
+          output_dir_ + "temp/spell_models/", "FileSystem", "FFVII", true, true
+        );
+
+        Ogre::ResourcePtr hrc = VGears::HRCFileManager::GetSingleton().load(
+          model.id + "_" + info.name_normal + ".hrc", "FFVII"
+        );
+        Ogre::String base_name;
+        VGears::StringUtil::splitBase(model.hrc, base_name);
+        auto mesh_name = model.id + "_" + info.name_normal + ".mesh";
+        Ogre::MeshPtr mesh(Ogre::MeshManager::getSingleton().load(mesh_name, "FFVII"));
+        Ogre::SkeletonPtr skeleton(mesh->getSkeleton());
+        for (std::string anim : model.a){
+            VGears::AFileManager &afl_mgr(VGears::AFileManager::GetSingleton());
+            Ogre::String a_base_name;
+            VGears::StringUtil::splitBase(anim, a_base_name);
+            VGears::AFilePtr a
+              = afl_mgr.load(a_base_name + ".a", "FFVII").staticCast<VGears::AFile>();
+            // Convert the FF7 name to a more readable name set in the meta data.
+            VGears::StringUtil::splitBase(anim, base_name);
+            a->AddTo(skeleton, VGears::NameLookup::Animation(base_name));
+        }
+        ExportMesh(path, mesh);
+    }
+    catch (const Ogre::Exception& ex){
+        std::cerr << "[ERROR] Ogre exception converting battle model "
+          << model.hrc <<": " << ex.what() << std::endl;
+    }
+    catch (const std::exception& ex){
+        std::cerr << "[ERROR] Exception converting battle model "
+          << model.hrc << ": " << ex.what() << std::endl;
+    }
+    next_model_to_convert_ ++;
+    return next_model_to_convert_;
+}
+
 void BattleDataInstaller::WriteEnemies(){
     for (Enemy enemy : enemies_){
         TiXmlDocument xml;
@@ -883,9 +1121,9 @@ void BattleDataInstaller::ExportMesh(const std::string outdir, const Ogre::MeshP
                 }
             }
         }
-        if (std::count(materials_.begin(), materials_.end(), sub_mesh->getMaterialName()) == 0){
+        if (std::count(battle_materials_.begin(), battle_materials_.end(), sub_mesh->getMaterialName()) == 0){
             mat_ser.queueForExport(mat);
-            materials_.push_back(sub_mesh->getMaterialName());
+            battle_materials_.push_back(sub_mesh->getMaterialName());
         }
     }
     mat_ser.exportQueued(

+ 74 - 14
src/installer/BattleDataInstaller.h

@@ -56,11 +56,22 @@ class BattleDataInstaller{
         unsigned int InitializeScenes();
 
         /**
-         * Prepares the installer for 3D model processing.
+         * Prepares the installer for battle 3D model processing.
          *
-         * @return The total number of models to process.
+         * Reads the contents of the battle.lgp file.
+         *
+         * @return The total number of battle models to process.
+         */
+        unsigned int InitializeBattleModels();
+
+        /**
+         * Prepares the installer for spell 3D model processing.
+         *
+         * Reads the contents of the magic.lgp file.
+         *
+         * @return The total number of battle models to process.
          */
-        unsigned int InitializeModels();
+        unsigned int InitializeSpellModels();
 
         /**
          * Processes the next battle scene.
@@ -70,28 +81,52 @@ class BattleDataInstaller{
         unsigned int ProcessScene();
 
         /**
-         * Processes the 3D model.
+         * Processes the next battle 3D model.
          *
          * Saves .hrc, .a and .p files for later conversion. .tex files are converted directly to
          * png.
          *
          * @return The total number of processed models.
          */
-        unsigned int ProcessModel();
+        unsigned int ProcessBattleModel();
 
         /**
-         * Prepres the installer for model conversion.
+         * Processes the next spell 3D model.
          *
-         * @return The total number of models to convert.
+         * Saves .hrc, .a and .p files for later conversion. .tex files are converted directly to
+         * png.
+         *
+         * @return The total number of processed models.
          */
-        unsigned int ConvertModelsInit();
+        unsigned int ProcessSpellModel();
+
+        /**
+         * Prepares the installer for battle model conversion.
+         *
+         * @return The total number of battle models to convert.
+         */
+        unsigned int ConvertBattleModelsInit();
+
+        /**
+         * Prepares the installer for spell model conversion.
+         *
+         * @return The total number of spell models to convert.
+         */
+        unsigned int ConvertSpellModelsInit();
 
         /**
          * Converts a model.
          *
          * @return The total number of converted models.
          */
-        unsigned int ConvertModel();
+        unsigned int ConvertBattleModel();
+
+        /**
+         * Converts a spell model.
+         *
+         * @return The total number of converted models.
+         */
+        unsigned int ConvertSpellModel();
 
         /**
          * Writes enemy data to files.
@@ -140,6 +175,11 @@ class BattleDataInstaller{
              */
             std::string anim;
 
+            /**
+             * Name of the "ab" scripts file.
+             */
+            std::string script;
+
             /**
              * List of .p polygon files associated to the model
              */
@@ -158,7 +198,7 @@ class BattleDataInstaller{
             /**
              * Constructor, initializes default values.
              */
-            Model(): id(""), hrc(""), anim(""){
+            Model(): id(""), hrc(""), anim(""), script(""){
                 p.clear();
                 tex.clear();
                 a.clear();
@@ -397,6 +437,16 @@ class BattleDataInstaller{
          */
         std::vector<std::string> battle_lgp_file_names_;
 
+        /**
+         * The files in the original magic.lgp file.
+         */
+        std::vector<File> magic_lgp_files_;
+
+        /**
+         * File names for the files in {@see magic_lgp_files_}.
+         */
+        std::vector<std::string> magic_lgp_file_names_;
+
         /**
          * Next model to process;
          */
@@ -408,14 +458,24 @@ class BattleDataInstaller{
         unsigned int next_model_to_convert_;
 
         /**
-         * List of models files found in battle.lgp
+         * List of battle models files found in battle.lgp
+         */
+        std::vector<Model> battle_models_;
+
+        /**
+         * List of battle model materials;
+         */
+        std::vector<std::string> battle_materials_;
+
+        /**
+         * List of spell models files found in magic.lgp
          */
-        std::vector<Model> models_;
+        std::vector<Model> spell_models_;
 
         /**
-         * List of model materials;
+         * List of spell model materials;
          */
-        std::vector<std::string> materials_;
+        std::vector<std::string> spell_materials_;
 
 
 };

+ 1 - 0
src/installer/CMakeLists.txt

@@ -28,6 +28,7 @@ set(INSTALLER_SOURCE_FILES
     common/Surface.cpp
     common/TimToVram.cpp
     common/Vram.cpp
+    data/AbFile.cpp
     data/BattleSceneFile.cpp
     data/DaFile.cpp
     decompiler/CodeGenerator.cpp

+ 35 - 7
src/installer/DataInstaller.cpp

@@ -39,6 +39,8 @@ DataInstaller::DataInstaller(
     // Assign weights.
     for (int i = IDLE; i < STATE_COUNT; i ++) step_weight_[i] = 1;
     step_weight_[IDLE] = 0;
+    step_weight_[BATTLE_MODELS_CONVERT] = 2;
+    step_weight_[SPELL_MODELS_CONVERT] = 2;
     step_weight_[MEDIA_IMAGES] = 3;
     step_weight_[MEDIA_SOUNDS] = 8;
     step_weight_[MEDIA_MUSICS] = 9;
@@ -109,7 +111,9 @@ float DataInstaller::Progress(){
             cur_substep_ = 0;
             substeps_ = 0;
             battle_installer_->WriteFormations();
-            installation_state_ = BATTLE_MODELS_INIT;
+            // TODO: DEBUG
+            installation_state_ = BATTLE_MODELS_INIT; // NORMAL
+            //installation_state_ = SPELL_MODELS_INIT; // SKIP BATTLE MODELS
             return CalcProgress();
         case BATTLE_MODELS_INIT:
             // Skip kernel data if option is set.
@@ -119,21 +123,21 @@ float DataInstaller::Progress(){
                 return CalcProgress();
             }
             write_output_line_("Extracting battle models...", 2, true);
-            substeps_ = battle_installer_->InitializeModels();
+            substeps_ = battle_installer_->InitializeBattleModels();
             cur_substep_ = 0;
             installation_state_ = BATTLE_MODELS_PROCESS;
             return CalcProgress();
         case BATTLE_MODELS_PROCESS:
-            cur_substep_ = battle_installer_->ProcessModel();
+            cur_substep_ = battle_installer_->ProcessBattleModel();
             if (cur_substep_ >= substeps_) installation_state_ = BATTLE_MODELS_CONVERT_INIT;
             return CalcProgress();
         case BATTLE_MODELS_CONVERT_INIT:
             cur_substep_ = 0;
-            substeps_ =  battle_installer_->ConvertModelsInit();
+            substeps_ =  battle_installer_->ConvertBattleModelsInit();
             installation_state_ = BATTLE_MODELS_CONVERT;
             return CalcProgress();
         case BATTLE_MODELS_CONVERT:
-            cur_substep_ = battle_installer_->ConvertModel();
+            cur_substep_ = battle_installer_->ConvertBattleModel();
             if (cur_substep_ >= substeps_) installation_state_ = BATTLE_MODELS_WRITE_CHARACTERS;
             return CalcProgress();
         case BATTLE_MODELS_WRITE_CHARACTERS:
@@ -144,7 +148,26 @@ float DataInstaller::Progress(){
         case BATTLE_MODELS_WRITE_SCENES:
             write_output_line_("Writing battle scene data...", 2, true);
             battle_installer_->WriteSceneData();
-            installation_state_ = KERNEL_PRICES;
+            installation_state_ = SPELL_MODELS_INIT;
+            return CalcProgress();
+        case SPELL_MODELS_INIT:
+            write_output_line_("Extracting attack models...", 2, true);
+            substeps_ = battle_installer_->InitializeSpellModels();
+            cur_substep_ = 0;
+            installation_state_ = SPELL_MODELS_PROCESS;
+            return CalcProgress();
+        case SPELL_MODELS_PROCESS:
+            cur_substep_ = battle_installer_->ProcessSpellModel();
+            if (cur_substep_ >= substeps_) installation_state_ = SPELL_MODELS_CONVERT_INIT;
+            return CalcProgress();
+        case SPELL_MODELS_CONVERT_INIT:
+            cur_substep_ = 0;
+            substeps_ =  battle_installer_->ConvertSpellModelsInit();
+            installation_state_ = SPELL_MODELS_CONVERT;
+            return CalcProgress();
+        case SPELL_MODELS_CONVERT:
+            cur_substep_ = battle_installer_->ConvertSpellModel();
+            if (cur_substep_ >= substeps_) installation_state_ = KERNEL_PRICES;
             return CalcProgress();
         case KERNEL_PRICES:
             // Skip kernel data if option is set.
@@ -398,6 +421,7 @@ void DataInstaller::CreateDirectories(){
     CreateDir("temp");
     CreateDir("temp/char");
     CreateDir("temp/battle_models");
+    CreateDir("temp/spell_models");
     CreateDir("gamedata");
     CreateDir("gamedata/enemy");
     CreateDir("gamedata/attack");
@@ -410,13 +434,14 @@ void DataInstaller::CreateDirectories(){
     CreateDir("images/window");
     CreateDir("screens");
     CreateDir("scripts");
+    CreateDir("scripts/battle_models");
     CreateDir("system");
     CreateDir("texts");
     CreateDir("models/fields/");
     CreateDir("models/battle/characters");
     CreateDir("models/battle/scenes");
     CreateDir("models/battle/enemies");
-
+    CreateDir("models/battle/attacks");
 
     application_.ResMgr()->addResourceLocation(
       output_dir_ + "temp/char/", "FileSystem", "FFVII", true, true
@@ -439,6 +464,9 @@ void DataInstaller::CreateDirectories(){
     application_.ResMgr()->addResourceLocation(
       output_dir_ + "models/battle/enemies/", "FileSystem", "FFVIITextures", true, true
     );
+    application_.ResMgr()->addResourceLocation(
+      output_dir_ + "models/battle/attacks/", "FileSystem", "FFVIITextures", true, true
+    );
     fields_lgp_ = std::make_unique<ScopedLgp>(
       application_.getRoot(), input_dir_ + "data/field/flevel.lgp", "LGP", "FFVIIFields"
     );

+ 21 - 1
src/installer/DataInstaller.h

@@ -226,10 +226,30 @@ class DataInstaller{
             BATTLE_MODELS_WRITE_CHARACTERS,
 
             /**
-             * Writtes battle scene model info.
+             * Writes battle scene model info.
              */
             BATTLE_MODELS_WRITE_SCENES,
 
+            /**
+             * Initializes the battle installer for 3D spell processing.
+             */
+            SPELL_MODELS_INIT,
+
+            /**
+             * Extracts 3D spell models.
+             */
+            SPELL_MODELS_PROCESS,
+
+            /**
+             * Prepares for 3D spell model conversion.
+             */
+            SPELL_MODELS_CONVERT_INIT,
+
+            /**
+             * Converts 3D spell models.
+             */
+            SPELL_MODELS_CONVERT,
+
             /**
              * Parses item and materia prices from ff7.exe.
              */

Fișier diff suprimat deoarece este prea mare
+ 1025 - 7
src/installer/MediaDataInstaller.cpp


+ 5 - 0
src/installer/MediaDataInstaller.h

@@ -200,6 +200,11 @@ class MediaDataInstaller{
          */
         std::unordered_map<int, std::string> sound_map_;
 
+        /**
+         * Map for sound files with human readable descriptions.
+         */
+        std::unordered_map<int, std::string> sound_description_;
+
         /**
          * Sound data for each entry to write to the XML file
          */

+ 249 - 0
src/installer/data/AbFile.cpp

@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2022 The V-Gears Team
+ *
+ * This file is part of V-Gears
+ *
+ * V-Gears is free software: you can redistribute it and/or modify it under
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, version 3.0 (GPLv3) of the License.
+ *
+ * V-Gears is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <iostream>
+#include <fstream>
+#include "data/AbFile.h"
+
+AbFile::AbFile(File file, bool enemy): ab_file_(file), is_enemy_(enemy){
+    scripts_.clear();
+    file.SetOffset(0);
+    Read();
+}
+
+unsigned int AbFile::GenerateScripts(std::string model_id, std::string path) const{
+    std::string file_name = path + model_id + ".lua";
+    std::fstream file(file_name, std::ios::out);
+    if (!file.is_open()){
+        std::cerr << "Unable to open file " << file_name
+          << " for animation script generation." << std::endl;
+        return 0;
+    }
+    file << model_id << " = {}" << std::endl << std::endl;
+    for (int script = 0; script < scripts_.size(); script ++){
+        file << model_id << ".script" << script << " = function()" << std::endl;
+        file << scripts_[script];
+        file << "end" << std::endl << std::endl;
+    }
+    file.close();
+    return scripts_.size();
+}
+
+void AbFile::Read(){
+    ab_file_.SetOffset(0x68); // 104 / 0x68 is the first script index.
+    int total = is_enemy_ ? 74 : 32; // 32 for enemies, 74 for characters.
+    for (int a = 0; a < total; a ++){
+        scripts_.push_back(DecompileScript(ab_file_.readU16LE()));
+        ab_file_.readU16LE(); // Offset are 16 bits, in 32 bit blocks. 3rd and 4th bytes discarded.
+    }
+}
+
+std::string AbFile::DecompileScript(u32 offset){
+    u32 intial_offset = ab_file_.GetCurrentOffset();
+    ab_file_.SetOffset(offset);
+    bool label_set = false; // Indicates if at least one 0xC9 (jump label) opcode has been found.
+    bool end = false;
+    int total_opcodes = 0;
+
+    std::string str_string = "";
+    while (true){
+        std::stringstream line;
+        if (end) break;
+        if (total_opcodes > 100){
+            // Give up if there are more than 100
+            line << "    return 0 -- More than 100 opcodes in script, discard script.\n";
+            end = true;
+            break;
+        }
+        total_opcodes ++;
+        u8 opcode = ab_file_.readU8();
+        if (opcode >= 0x00 && opcode <= 0x8D){
+            line << "    play_animation(" << static_cast<int>(opcode) << ")";
+        }
+        else{
+            switch (opcode){
+                case 0x91: // Play effect, one 1 byte parameter.
+                    line << "    play_effect(" << static_cast<int>(ab_file_.readU8()) << ")";
+                    break;
+                case 0xA9: // Increase script counter by two. Two 1 byte parameters, ignored
+                    ab_file_.readU8();
+                    ab_file_.readU8();
+                    break;
+                case 0xAA: // Unpause camera, no parameters.
+                    line << "    unpause_macera()";
+                    break;
+                case 0xAD:
+                     // Set effect, four parameters:
+                     //   joint (1 byte), skeleton joint at which to set the effect.
+                     //   distance (2 bytes), distance from the joint.
+                     //   start (1 byte), effect start.
+                     //   end (1 byte), effect end.
+                    line << "    set_effect(" << static_cast<int>(ab_file_.readU8()) << ", "
+                      << ab_file_.readU16LE() << ", " << static_cast<int>(ab_file_.readU8()) << ", "
+                      << static_cast<int>(ab_file_.readU8()) << ")";
+                    break;
+                case 0xB6: // Pause camera and finish animation. One byte parameter, animation ID.
+                    line << "    pause_camera_finish_animation("
+                      << static_cast<int>(ab_file_.readU8()) << ")";
+                    break;
+                case 0xB9: // Set camera. One byte parameter, camera ID.
+                    line << "    set_camera(" << static_cast<int>(ab_file_.readU8()) << ")";
+                    break;
+                case 0x06:
+                     // Play hurt animation, including action, effect and sound. It will not display
+                     // damage and barrier effect. One byte parameter, the amount of frames to wait
+                     // before starting.
+                    line << "    execute_hurt(" << static_cast<int>(ab_file_.readU8()) << ")";
+                    break;
+                case 0xC1: // Jump to label set by 0xC9 (::jump::). No parameters.
+                    line << "    goto jump";
+                    break;
+                case 0xC2: // Show damage. One byte parameter, frames to wait before showing.
+                    line << "    execute_damage(" << static_cast<int>(ab_file_.readU8()) << ")";
+                    break;
+                case 0xC5:
+                     // Set value from 0x800f8374 (unit fade time) as wait time for action script.
+                     // No parameters. I have no idea what this is used for.
+                    line << "    set_unit_fade_wait()";
+                    break;
+                case 0xC6:
+                     // Set value to 0x800f8374 (unit fade time) for futher use. One byte parameter,
+                     // wait time. I have no idea what this is used for.
+                    line
+                      << "    set_unit_fade_time(" << static_cast<int>(ab_file_.readU8()) << ")";
+                    break;
+                case 0xC9: // Set a jump label. Only do it once per script. No prameters
+                    if (!label_set){
+                        line << "    ::jump::";
+                        label_set = true;
+                    }
+                    else{
+                        line << "    -- Invalid jump label";
+                    }
+                    break;
+                case 0xCA:
+                     // Jump to label set by 0xC9 (::jump::) if nothing is being loaded in the
+                     // the background. No parameters. I don't think this will be necessary.
+                    line << "    -- goto jump";
+                    break;
+                case 0xD0:
+                     // Jump to enemy, two parameters
+                     //   (2 bytes), I don't know what it is.
+                     //   (1 byte), I don't know what it is.
+                    line << "    jump_to_enemy(" << ab_file_.readU16LE() << ", "
+                      << static_cast<int>(ab_file_.readU8()) << ")";
+                    break;
+                case 0xD1:
+                     // Move to enemy using function 0x800cf5bc by number of steps. Stop distance is
+                     // distance to target collision radius. Three parameters:
+                     //   distance (2 byte), unknown.
+                     //   unknown (2 bytes), unknown.
+                     //   steps (1 byte), number of steps.
+                    line << "    move_to_target(" << ab_file_.readU16LE() << ", "
+                      << ab_file_.readU16LE() << ", " << static_cast<int>(ab_file_.readU8()) << ")";
+                    break;
+                case 0xD8:
+                    // Play sound using attacker settings after waiting given number of frames.
+                    // Two parameters:
+                    //   wait (1 byte), frames to wait before the sound.
+                    //   sound (2 bytes), sound ID.
+                    line << "    play_sound_for_attacker("
+                      << static_cast<int>(ab_file_.readU8()) << ", "
+                      << ab_file_.readU16LE() <<  ")";
+                    break;
+                case 0xE5:
+                    // Set initial (idle) direction for current unit according to situation. No
+                    // parameters.
+                    line << "    return_direction()";
+                    break;
+                case 0xE8:
+                    // Start load effect requested during attack (attack type id and attack id are
+                    // used to determinate what effect to load). No parameters.
+                    line << "    load_additional_effect()";
+                    break;
+                case 0xEA: // Show the current action name in the title window.
+                    line << "    show_action_name()";
+                    break;
+                case 0xEC:
+                    // if effect not loaded, call this opcode until it does. For magic, summon,
+                    // limit, enemy skill and enemy attack. execute loaded effect. All effects are
+                    // hardcoded so they can do whatever they want (play sounds, display damage,
+                    // request hurt for target and so on).
+                    line << "    exectute_aditional_effect()";
+                    break;
+                case 0xF0: // Set effect.
+                    line << "    set_effect()";
+                    break;
+                case 0xF3:
+                    // Repeat reading this opcode until wait time for script not reach 0.
+                    // It decreases by 1 each call. Waiting time is set with 0xF4
+                    line << "    wait()";
+                    break;
+                case 0xF4: // Set frames to wait. 1 parameter, frames to wait (1 byte).
+                    line << "    set_wait(" << static_cast<int>(ab_file_.readU8()) << ")";
+                    break;
+                case 0xF6:
+                    // Play die effect (depends on die type) if unit is dead. Used in enemy hurt
+                    // actions.
+                    line << "    play_die_if_dead()";
+                    break;
+                case 0xF7:
+                    // After wait time ends execute hurt action, effect, sound. This will display
+                    // damage and barriers effect. One byte parameter, wait time, in frames.
+                    line << "    execute_attack(" << static_cast<int>(ab_file_.readU8()) << ")";
+                    break;
+                case 0xFA: // Instantly set default position for units. No parameters.
+                    line << "    return_position()";
+                    break;
+                case 0xFC:
+                    // Set direction for targets (delayed) and attacker according to situation. No
+                    // parameters.
+                    line << "    set_direction()";
+                    break;
+                case 0x9E: // Return.
+                case 0xF1: // Return.
+                case 0xEE: // Return.
+                case 0xFF: // Return.
+                //case 0xC1: // Return.
+                case 0xA2: // Return.
+                    line << "    return 0";
+                    end = true;
+                    break;
+                case 0xFE:
+                    // Special return opcode. Return only if next byte is 0xC0.
+                    {
+                        u8 param = ab_file_.readU8();
+                        if (param == 0xC0){
+                            line << "    return 0";
+                            end = true;
+                        }
+                        else line << "    -- 0xFE not paired with 0xC0 for return.";
+                    }
+                    break;
+                default:
+                    line << "    -- Unknown/unused opcode.";
+
+
+            }
+        }
+        // Add a comment and a new line.
+        line << " -- 0x";
+        std::stringstream hex_opcode;
+        hex_opcode << std::hex << std::uppercase << static_cast<int>(opcode);
+        str_string += line.str() + hex_opcode.str() + "\n";
+    }
+    ab_file_.SetOffset(intial_offset);
+    return str_string;
+}

+ 93 - 0
src/installer/data/AbFile.h

@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2022 The V-Gears Team
+ *
+ * This file is part of V-Gears
+ *
+ * V-Gears is free software: you can redistribute it and/or modify it under
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, version 3.0 (GPLv3) of the License.
+ *
+ * V-Gears is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#pragma once
+
+#include <vector>
+#include "common/File.h"
+#include "common/TypeDefine.h"
+
+/**
+ * Represents an animation scripts file.
+ *
+ * *ab files are bundled into battle.lgp. They contain animation scripts. More info about them in
+ * {@see https://forums.qhimm.com/index.php?topic=14204.0}
+ */
+class AbFile{
+
+    public:
+
+        /**
+         * Constructor.
+         *
+         * Reads the file.
+         *
+         * @param[in, out] file The file to read from. The file contents will not be altered, but
+         * it's offset will be changed while reading it.
+         * @param[in] enemy Indicates if the animation is for an enemy. Enemies have a different
+         * number of animations than characters or backgrounds.
+         */
+        AbFile(File file, bool enemy);
+
+        /**
+         * Generates script files from the ba file.
+         *
+         * A file will be generated with every script in the *ab file. The file will be created in
+         * the specified path, and will be named like the model id, followed by the ".lua"
+         * extension. For example, if path is "/home/user/.v-gears", the model id is "rt", the file
+         * name will be: "/home/user/.v-gears/rt.lua". Each script will be named "script" + index
+         * (script1(), script2() ... script44() ...)
+         *
+         * @param[in] model_id ID of the model the animation belongs to, usually a two letter
+         * code. Used to generate the file name.
+         * @param[in] path Path to the directory where the files will be saved.
+         * @return The number of scripts written to the file.
+         */
+        unsigned int GenerateScripts(std::string model_id, std::string path) const;
+
+    private:
+
+        /**
+         * Reads the Ab file and extracts all the data.
+         */
+        void Read();
+
+        /**
+         * Decompiles a script in the *ab file.
+         *
+         * It won't nodify the file current offset.
+         *
+         * @param[in] offset Offset at which the script starts.
+         * @return The script text.
+         */
+        std::string DecompileScript(u32 offset);
+
+        /**
+         * The ab file.
+         */
+        File ab_file_;
+
+        /**
+         * Indicates if the model is for an enemy.
+         */
+        bool is_enemy_;
+
+        /**
+         * List of animations.
+         */
+        std::vector<std::string> scripts_;
+
+
+};

+ 89 - 1
src/installer/data/FF7Data.h

@@ -93,7 +93,9 @@ class FF7Data{
         };
 
         /**
-         * Information about a model.
+         * Information about a battle model.
+         *
+         * Used for party characters, enemies and scenarios, but not for spells.
          */
         struct BattleModelInfo{
 
@@ -141,6 +143,37 @@ class FF7Data{
             {}
         };
 
+        /**
+         * Information about a battle spell model.
+         */
+        struct SpellModelInfo{
+
+            /**
+             * A numeric ID assigned to the model.
+             */
+            int numeric_id;
+
+            /**
+             * An alphanumeric ID assigned to the model.
+             */
+            std::string alphanumeric_id;
+
+            /**
+             * A name for the model.
+             */
+            std::string name;
+
+            /**
+             * A normalized version of the name.
+             */
+            std::string name_normal;
+
+            /**
+             * Constructor. Initializes to default values.
+             */
+            SpellModelInfo(): numeric_id(-1), alphanumeric_id(""), name(""), name_normal(""){}
+        };
+
         /**
          * Retrieves an enemy model ID from an enemy ID.
          *
@@ -161,6 +194,9 @@ class FF7Data{
         /**
          * Retrieves information about a battle model from it's name.
          *
+         * Can retrieve information for party character, enemy and scenario models, but not for
+         * spells models.
+         *
          * The return structure has the following properties ({@see BattleModelInfo}):
          *
          * - numeric_id: A numeric ID assigned to the model. For enemies, it's a unique enemy ID.
@@ -3451,4 +3487,56 @@ class FF7Data{
             }
             return "";
         }
+
+        /**
+         * Retrieves information about a spell model from it's name.
+         *
+         *
+         * The return structure has the following properties ({@see BattleModelInfo}):
+         *
+         * - numeric_id: A numeric ID assigned to the model. For enemies, it's a unique enemy ID.
+         * For scenes, it's a unique numeric id. For playable characters, it is the id of the
+         * character the model references, but since  a character may have many models, it's not
+         * guaranteed to be unique. There is a special case where it is -1: for the playable frog,
+         * which is related to all characters.
+         *
+         * - alphanumeric_id: A unique id assigned to the model. Two lowercase letters.
+         *
+         * - name: A descriptive name for the model. For debugging purposes only, it's not intended
+         * to ever be displayed, and it's not suitable to use in filenames.
+         *
+         * - name_normal: Simplified name, suitable to be used on filenames. Not garanteed to be
+         * unique.
+         *
+         * @param[in] model_id ID of the model. It can be two letters, or two letters preceded by
+         * "btl_". Case insensitive. If the id contains more than four letters (not counting the
+         * optional "btl_" prefix), only the first two sill be considered.
+         * @return A spell model info structure. If an invalid model ID is provided, or if the
+         * model doesn't exist, an empty structure will be returned.
+         */
+        static SpellModelInfo GetSpellModelInfo(std::string model_id){
+            SpellModelInfo info;
+            std::string alphanumeric_id = model_id;
+            std::transform(
+              alphanumeric_id.begin(), alphanumeric_id.end(), alphanumeric_id.begin(), ::tolower
+            );
+            if (alphanumeric_id.size() < 2) return info; // Empty
+            if (alphanumeric_id.size() > 4 && alphanumeric_id.substr(0, 4) == "btl_")
+                alphanumeric_id = alphanumeric_id.substr(4, alphanumeric_id.size() - 4);
+            if (alphanumeric_id.size() > 4) alphanumeric_id = alphanumeric_id.substr(0, 2);
+            info.alphanumeric_id = alphanumeric_id;
+            info.name = alphanumeric_id;
+            info.name_normal = "btl_" + alphanumeric_id;
+            if ("aa" == alphanumeric_id){
+                info.numeric_id = 0;
+                info.name = "Unused Pyramid";
+                info.name_normal = "unused_pyramid";
+            }
+            else if ("ab" == alphanumeric_id){
+                info.numeric_id = 1;
+                info.name = "Unused Pyramid";
+                info.name_normal = "unused_pyramid";
+            }
+            return info;
+        }
 };

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff