Просмотр исходного кода

Music partially implemented.

The installer now extracts, converts and indexes music. Fields get their tracks assigned during installation. Music can be played fields with the MUSIC opcode.
Music sounds very MIDI-like, and tracks don't loop, but at least they play.

Also, some improvements in the installer: Installation steps are now weighted for progress calculation, and the progress bar shows decimal points.
Iñigo Valentin 3 лет назад
Родитель
Сommit
d620125541

+ 38 - 2
V-Gears-Installer/include/DataInstaller.h

@@ -63,7 +63,7 @@ class DataInstaller{
          *
          * @return Installation progress [0-100].
          */
-        int Progress();
+        float Progress();
 
     private:
 
@@ -72,7 +72,7 @@ class DataInstaller{
          *
          * @return Installation progress [0-100]
          */
-        const int CalcProgress();
+        const float CalcProgress();
 
         /**
          * Creates a directory in the outputh path.
@@ -200,6 +200,26 @@ class DataInstaller{
              */
             MEDIA_SOUNDS_INDEX,
 
+            /**
+             * Prepares the installer for music extraction.
+             */
+            MEDIA_MUSICS_INIT,
+
+            /**
+             * Extracts the game music.
+             */
+            MEDIA_MUSICS,
+
+            /**
+             * Extracts the game high quality music.
+             */
+            MEDIA_MUSICS_HQ,
+
+            /**
+             * Build the music index.
+             */
+            MEDIA_MUSICS_INDEX,
+
             /**
              * Initializer for {@see SPAWN_POINTS_AND_SCALE_FACTORS}.
              */
@@ -261,10 +281,26 @@ class DataInstaller{
          */
         InstallationSteps installation_state_ = IDLE;
 
+        /**
+         * Substeps in the current installation step.
+         */
         int substeps_;
 
+        /**
+         * Current substep in the current installation step.
+         */
         int cur_substep_;
 
+        /**
+         * Weight of each installation step.
+         *
+         * Used to calculate current progress.
+         */
+        int step_weight_[STATE_COUNT];
+
+        /**
+         * List of field model names.
+         */
         std::vector<std::string> field_model_names_;
 
         /**

+ 8 - 0
V-Gears-Installer/include/FieldDataInstaller.h

@@ -535,6 +535,14 @@ class FieldDataInstaller{
          */
         void ExportMesh(const std::string outdir, const Ogre::MeshPtr &mesh);
 
+        /**
+         * Reads the music track IDs from a specific field.
+         *
+         * @param[in] field The field to extract tracks from.
+         * @return The list of tracks. It can be empty.
+         */
+        std::vector<int> ExtractMusicTrackIds(VGears::FLevelFilePtr& field);
+
         /**
          * Converts a FFVII PC field to a V-Gears field.
          *

+ 46 - 0
V-Gears-Installer/include/MediaDataInstaller.h

@@ -73,6 +73,32 @@ class MediaDataInstaller{
          */
         void WriteSoundIndex();
 
+        /**
+         * Prepares the installer for the sounds extraction.
+         *
+         * @return The total number of sounds to process.
+         */
+        int InstallMusicsInit();
+
+        /**
+         * Extracts the next music file contained in the midi.lgp, converts it and installs it.
+         *
+         * @return True if there are no more musics to extract, false otherwise.
+         */
+        bool InstallMusics();
+
+        /**
+         * Converts high quality musiscs to OGG.
+         *
+         * There are four of them.
+         */
+        void InstallHQMusics();
+
+        /**
+         * Writes the XML file with all music tracks.
+         */
+        void WriteMusicsIndex();
+
     private:
 
         /**
@@ -173,6 +199,26 @@ class MediaDataInstaller{
          */
         std::vector<std::string> sounds_;
 
+        /**
+         * The midi.lgp file.
+         */
+        VGears::LGPArchive midi_;
+
+        /**
+         * Number of music tracks already processed.
+         */
+        int processed_musics_;
+
+        /**
+         * Map for music files with descriptive names.
+         */
+        std::unordered_map<int, std::string> musics_map_;
+
+        /**
+         * Sound data for each entry to write to the XML file
+         */
+        std::vector<std::string> musics_;
+
 
 
 };

+ 50 - 12
V-Gears-Installer/src/DataInstaller.cpp

@@ -31,15 +31,26 @@ DataInstaller::DataInstaller(
   write_output_line_(write_output_line)
 {
     if (!application_.initOgre(true)) throw std::runtime_error("Ogre init failure");
-    Ogre::Log* default_log( Ogre::LogManager::getSingleton().getDefaultLog());
+    Ogre::Log* default_log(Ogre::LogManager::getSingleton().getDefaultLog());
     assert( default_log );
     default_log->setLogDetail(Ogre::LL_LOW);
 
+    // Assign weights.
+    for (int i = IDLE; i < STATE_COUNT; i ++) step_weight_[i] = 1;
+    step_weight_[IDLE] = 0;
+    step_weight_[MEDIA_IMAGES] = 3;
+    step_weight_[MEDIA_SOUNDS] = 8;
+    step_weight_[MEDIA_MUSICS] = 9;
+    step_weight_[MEDIA_MUSICS_HQ] = 2;
+    step_weight_[FIELD_SPAWN_POINTS_AND_SCALE_FACTORS] = 3;
+    step_weight_[FIELD_CONVERT] = 3;
+    step_weight_[FIELD_WRITE] = 2;
+    step_weight_[FIELD_CONVERT_MODELS] = 3;
 }
 
 DataInstaller::~DataInstaller(){}
 
-int DataInstaller::Progress(){
+float DataInstaller::Progress(){
     switch (installation_state_){
         case IDLE:
             installation_state_ = CREATE_DIRECTORIES;
@@ -152,6 +163,28 @@ int DataInstaller::Progress(){
         case MEDIA_SOUNDS_INDEX:
             write_output_line_("Building sound index...", 2, true);
             media_installer_->WriteSoundIndex();
+            installation_state_ = MEDIA_MUSICS_INIT;
+            return CalcProgress();
+        case MEDIA_MUSICS_INIT:
+            write_output_line_("Extracting music...", 2, true);
+            substeps_ = media_installer_->InstallMusicsInit();
+            installation_state_ = MEDIA_MUSICS;
+            cur_substep_ = 0;
+            return CalcProgress();
+        case MEDIA_MUSICS:
+            if (media_installer_->InstallMusics() == true)
+                installation_state_ = MEDIA_MUSICS_HQ;
+            else cur_substep_ ++;
+            return CalcProgress();
+        case MEDIA_MUSICS_HQ:
+            substeps_ = 0;
+            cur_substep_ = 0;
+            media_installer_->InstallHQMusics();
+            installation_state_ = MEDIA_MUSICS_INDEX;
+            return CalcProgress();
+        case MEDIA_MUSICS_INDEX:
+            write_output_line_("Building music track index...", 2, true);
+            media_installer_->WriteMusicsIndex();
             installation_state_ = FIELD_SPAWN_POINTS_AND_SCALE_FACTORS_INIT;
             return CalcProgress();
         case FIELD_SPAWN_POINTS_AND_SCALE_FACTORS_INIT:
@@ -221,18 +254,23 @@ int DataInstaller::Progress(){
     }
 }
 
-const int DataInstaller::CalcProgress(){
-    float curr_step = installation_state_ / static_cast<float>(STATE_COUNT);
+const float DataInstaller::CalcProgress(){
+    float weight_total = 0; // Sum of every step weight.
+    float weight_done = 0; // Sum of every previous step weight.
+    float weight = static_cast<float>(step_weight_[installation_state_]); // Weight of current step.
+    float weight_substeps = 0; // Weight of the already done substeps in current step.
+
+    for (int i = IDLE; i < STATE_COUNT; i ++){
+        weight_total += step_weight_[i];
+        if (installation_state_ > i) weight_done += step_weight_[i];
+    }
+
     if (substeps_ > 0 && cur_substep_ < substeps_){
-        curr_step
-          += (
-            (static_cast<float>(cur_substep_) / static_cast<float>(substeps_))
-            / static_cast<float>(STATE_COUNT)
-          );
+        weight_substeps = (cur_substep_ * weight) / substeps_;
     }
-    curr_step = curr_step * 100.0f;
-    int progress = static_cast<int>(curr_step);
-    if (progress >= 100) progress = 99;
+
+    float progress = 100 * (weight_done + weight_substeps) / weight_total;
+    if (progress >= 100.0f) progress = 99.9f;
     return progress;
 }
 

+ 36 - 1
V-Gears-Installer/src/FieldDataInstaller.cpp

@@ -453,6 +453,25 @@ float FieldDataInstaller::GetFieldScaleFactor(size_t field_id){
     return it->second;
 }
 
+std::vector<int> FieldDataInstaller::ExtractMusicTrackIds(VGears::FLevelFilePtr& field){
+    // Search for the "AKAO" string in the raw field data.
+    // The next byte is a track id.
+    const std::vector<u8> raw = field->GetRawScript();
+    std::vector<int> tracks;
+    for (int i = 0; i < raw.size() - 5; i ++){
+        if (raw[i] == 0x41){                   // A
+            if (raw[i + 1] == 0x4B){           // K
+                if (raw[i + 2] == 0x41){       // A
+                    if (raw[i + 3] == 0x4f){   // O
+                        tracks.push_back(static_cast<int>(raw[i + 4]) - 2);
+                    }
+                }
+            }
+        }
+    }
+    return tracks;
+}
+
 void FieldDataInstaller::PcFieldToVGearsField(VGears::FLevelFilePtr& field){
     // Generate triggers script to insert into main
     // decompiled FF7 field -> LUA script.
@@ -476,6 +495,7 @@ void FieldDataInstaller::PcFieldToVGearsField(VGears::FLevelFilePtr& field){
     try{
         // Get the raw script bytes.
         const std::vector<u8> raw_field_data = field->GetRawScript();
+
         // Decompile to LUA.
         decompiled = FieldDecompiler::Decompile(
           field->getName(), raw_field_data, formatter, gateway_script_data,
@@ -486,7 +506,9 @@ void FieldDataInstaller::PcFieldToVGearsField(VGears::FLevelFilePtr& field){
         );
         if (script_file.is_open()){
             script_file << decompiled.luaScript;
-            field_text_writer_.Begin(output_dir_ + "/" + FIELD_MAPS_DIR + "/" + field->getName() + "/text.xml");
+            field_text_writer_.Begin(
+              output_dir_ + "/" + FIELD_MAPS_DIR + "/" + field->getName() + "/text.xml"
+            );
             try{field_text_writer_.Write(raw_field_data, field->getName());}
             catch (const std::out_of_range& ex){
                 write_output_line_(
@@ -727,7 +749,20 @@ void FieldDataInstaller::PcFieldToVGearsField(VGears::FLevelFilePtr& field){
                 element->LinkEndChild(xml_entity_trigger.release());
             }
         }
+
+        // Get music tracks
+        std::vector<int> tracks = ExtractMusicTrackIds(field);
+        std::unique_ptr<TiXmlElement> xml_tracks(new TiXmlElement("tracks"));
+        for (int t = 0; t < tracks.size(); t ++){
+            std::unique_ptr<TiXmlElement> xml_track(new TiXmlElement("track"));
+            xml_track->SetAttribute("id", t);
+            xml_track->SetAttribute("track_id", tracks[t]);
+            xml_tracks->LinkEndChild(xml_track.release());
+        }
+        element->LinkEndChild(xml_tracks.release());
+
         doc.LinkEndChild(element.release());
+
         doc.SaveFile(output_dir_ + "/" + FIELD_MAPS_DIR + "/" + field->getName() + "/map.xml");
         const VGears::PaletteFilePtr& pal = field->GetPalette();
         const VGears::BackgroundFilePtr& bg = field->GetBackground();

+ 11 - 2
V-Gears-Installer/src/MainWindow.cpp

@@ -14,6 +14,8 @@
  */
 
 #include <iostream>
+#include <sstream>
+#include <iomanip>
 #include <QtCore/QProcess>
 #include <QtWidgets/QFileDialog>
 #include <QtCore/QDir>
@@ -177,6 +179,8 @@ void MainWindow::on_btn_data_run_clicked(){
           "data/menu/menu_us.lgp",
           "data/sound/audio.fmt",
           "data/sound/audio.dat",
+          "data/music/music.idx",
+          "data/midi/midi.lgp",
           "ff7.exe"
         };
         // Ensure required files are in the input dir
@@ -238,8 +242,13 @@ void MainWindow::OnInstallStopped(){EnableUi(true);}
 
 void MainWindow::DoProgress(){
     try{
-        const int progress = installer_->Progress();
-        main_window_->data_progress_bar->setValue(progress);
+        const float progress = installer_->Progress();
+        main_window_->data_progress_bar->setValue(std::floor(progress));
+        std::stringstream stream;
+        stream << std::fixed << std::setprecision(2) << progress;
+        stream << "%";
+        std::string s = stream.str();
+        main_window_->label_percent->setText(QString::fromUtf8(s.c_str()));
         if (progress >= 100) OnInstallStopped();
     }
     catch (const std::exception& ex){

+ 37 - 0
V-Gears-Installer/src/MainWindow.ui

@@ -161,11 +161,48 @@
           <property name="value">
            <number>0</number>
           </property>
+          <property name="textVisible">
+           <bool>false</bool>
+          </property>
           <property name="alignment">
            <set>Qt::AlignCenter</set>
           </property>
          </widget>
         </item>
+        <item>
+         <widget class="QLabel" name="label_percent">
+          <property name="sizePolicy">
+           <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+            <horstretch>1</horstretch>
+            <verstretch>0</verstretch>
+           </sizepolicy>
+          </property>
+          <property name="minimumSize">
+           <size>
+            <width>0</width>
+            <height>0</height>
+           </size>
+          </property>
+          <property name="maximumSize">
+           <size>
+            <width>16777215</width>
+            <height>10</height>
+           </size>
+          </property>
+          <property name="font">
+           <font>
+            <pointsize>8</pointsize>
+            <italic>true</italic>
+           </font>
+          </property>
+          <property name="text">
+           <string/>
+          </property>
+          <property name="alignment">
+           <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+          </property>
+         </widget>
+        </item>
         <item>
          <widget class="QLabel" name="label_progress">
           <property name="sizePolicy">

+ 106 - 1
V-Gears-Installer/src/MediaDataInstaller.cpp

@@ -21,6 +21,7 @@
 #include <OgreImage.h>
 #include <OgreColourValue.h>
 #include <boost/format.hpp>
+#include <boost/algorithm/string.hpp>
 #include <tinyxml.h>
 #include "MediaDataInstaller.h"
 #include "data/VGearsLGPArchive.h"
@@ -40,7 +41,8 @@ u8 MediaDataInstaller::WAV_HEADER[] = {
 MediaDataInstaller::MediaDataInstaller(const std::string input_dir, const std::string output_dir):
   input_dir_(input_dir), output_dir_(output_dir),
   menu_(input_dir + "data/menu/menu_us.lgp", "LGP"), window_(input_dir + "data/kernel/WINDOW.BIN"),
-  fmt_(input_dir_ + "data/sound/audio.fmt"), dat_(input_dir_ + "data/sound/audio.dat")
+  fmt_(input_dir_ + "data/sound/audio.fmt"), dat_(input_dir_ + "data/sound/audio.dat"),
+  midi_(input_dir + "data/midi/midi.lgp", "LGP")
 {PopulateMaps();}
 
 void MediaDataInstaller::PopulateMaps(){
@@ -390,3 +392,106 @@ void MediaDataInstaller::WriteSoundIndex(){
     xml.LinkEndChild(container.release());
     xml.SaveFile(output_dir_ + "sounds.xml");
 }
+
+int MediaDataInstaller::InstallMusicsInit(){
+    // Read the music index file.
+    std::ifstream music_idx(input_dir_ + "data/music/music.idx");
+    int i = 0;
+    std::string name;
+    if (music_idx.is_open()){
+        while (std::getline(music_idx, name)){
+            // Newlines are very likely WINDOWS/DOS format, but check them all.
+            boost::replace_all(name, "\r\n", "");
+            boost::replace_all(name, "\n", "");
+            boost::replace_all(name, "\r", "");
+            musics_map_[i] = name;
+            i ++;
+        }
+        music_idx.close();
+    }
+
+    // Load midi_
+    midi_.load();
+
+    processed_musics_ = 0;
+    return midi_.list(true, true)->size();
+}
+
+bool MediaDataInstaller::InstallMusics(){
+    VGears::LGPArchive::FileEntry f = midi_.GetFiles().at(processed_musics_);
+
+    // Find the index in the map.
+    int index = 1000 + processed_musics_;
+    for (auto const& m : musics_map_){
+        if (m.second + ".mid" == f.file_name){
+            index = m.first;
+            break;
+        }
+    }
+
+    File midi(input_dir_ + "data/midi/midi.lgp");
+
+    std::fstream out;
+    out.open(output_dir_ + "audio/music/" + std::to_string(index) + ".mid", std::ios::out);
+    midi.SetOffset(f.data_offset);
+    for (int j = 0; j < f.data_size; j ++) out << midi.readU8();
+    out.close();
+
+    // Convert to ogg (TiMidity + FFMpeg)
+    std::string command = (boost::format(
+      "timidity --quiet=3 %1%audio/music/%2%.mid -Ow -o - | ffmpeg -hide_banner -loglevel panic -y "
+      "-i - %1%audio/music/%2%.ogg; rm %1%audio/music/%2%.mid"
+    ) % output_dir_ % index).str();
+    std::system(command.c_str());
+
+
+    musics_.push_back("audio/music/" + std::to_string(processed_musics_) + ".ogg");
+    processed_musics_ ++;
+    if (processed_musics_ == midi_.list(true, true)->size()) return true;
+    else return false;
+}
+
+void MediaDataInstaller::InstallHQMusics(){
+    std::vector<std::string> hq_musics = {"hearth", "sato", "sensui", "wind"};
+    for (std::string hq_music : hq_musics){
+
+        int index = 2000;
+        for (auto const& m : musics_map_){
+            if (m.second == hq_music){
+                index = m.first;
+                break;
+            }
+        }
+
+        std::string command = (boost::format(
+          "ffmpeg -hide_banner -loglevel panic -y -i %1%music/%2%.wav %3%audio/sound/%4%.ogg"
+        ) % input_dir_ % hq_music % output_dir_ % index).str();
+        std::cout << "    HQ Command: " << command << "\n";
+        std::system(command.c_str());
+    }
+}
+
+void MediaDataInstaller::WriteMusicsIndex(){
+    TiXmlDocument xml;
+    std::unique_ptr<TiXmlElement> container(new TiXmlElement("musics"));
+    //for (string item : items_){
+    for (int id = 0; id < musics_.size(); id ++){
+        std::string path = musics_[id];
+        std::unique_ptr<TiXmlElement> xml_music(new TiXmlElement("music"));
+        xml_music->SetAttribute("file_name", path);
+        xml_music->SetAttribute("name", id);
+        xml_music->SetAttribute("loop", "0");
+        container->LinkEndChild(xml_music.release());
+        // If there is a friendly name for this sound, add another entry
+        if (musics_map_.count(id) != 0){
+            std::unique_ptr<TiXmlElement> xml_music_name(new TiXmlElement("music"));
+            xml_music_name->SetAttribute("file_name", path);
+            xml_music_name->SetAttribute("name", musics_map_[id]);
+            xml_music_name->SetAttribute("loop", "0");
+            container->LinkEndChild(xml_music_name.release());
+        }
+    }
+    xml.LinkEndChild(container.release());
+    xml.SaveFile(output_dir_ + "musics.xml");
+}
+

+ 1 - 1
V-Gears-Installer/src/decompiler/field/instruction/FieldMediaInstruction.cpp

@@ -78,7 +78,7 @@ void FieldMediaInstruction::ProcessAKAO2(CodeGenerator* code_gen){
 
 void FieldMediaInstruction::ProcessMUSIC(CodeGenerator* code_gen){
     code_gen->AddOutputLine(
-      (boost::format("-- play_map_music(%1%)") % params_[0]->GetUnsigned()).str()
+      (boost::format("play_map_music(%1%)") % params_[0]->GetUnsigned()).str()
     );
 }
 

+ 21 - 0
V-Gears/include/core/EntityManager.h

@@ -324,6 +324,22 @@ class EntityManager : public Ogre::Singleton<EntityManager>{
          */
         void SetEntityToCharacter(const char* entity_name, unsigned int char_id);
 
+        /**
+         * Adds a music track ID to the list of music tracks of the field.
+         *
+         * @param[in] id ID of the track in the map.
+         * @param[in] Music track ID.
+         */
+        void AddTrack(const int id, const int track_id);
+
+        /**
+         * Adds a music track ID from the list of music tracks of the field.
+         *
+         * @param[in] id ID of the track in the map.
+         * @return The music track ID, or -1 if it doesn't exist.
+         */
+        int GetTrack(const int id);
+
     private:
 
         /**
@@ -583,4 +599,9 @@ class EntityManager : public Ogre::Singleton<EntityManager>{
          * The encounter rate of the map.
          */
         float encounter_rate_;
+
+        /**
+         * IDs of the music tracks of the field.
+         */
+        std::unordered_map<int, int> tracks_;
 };

+ 1 - 0
V-Gears/include/core/ScriptManagerBinds.h

@@ -254,6 +254,7 @@ void ScriptManager::InitBinds(){
             "set_entity_to_character",
             (void(EntityManager::*)(const char*, unsigned int)) &EntityManager::SetEntityToCharacter
           )
+          .def("get_track_id", (int(EntityManager::*)(int)) &EntityManager::GetTrack)
     ];
 
     // Commands for the entity manager, not related to any particular entity.

+ 11 - 18
V-Gears/include/data/VGearsFLevelFile.h

@@ -47,25 +47,20 @@ namespace VGears{
             /**
              * Constructor.
              *
-             * @param[in] creator Pointer to the ResourceManager that is
-             * creating this resource.
+             * @param[in] creator Pointer to the ResourceManager that is creating this resource.
              * @param[in] name The unique name of the resource.
              * @param[in] handle @todo Understand and document.
-             * @param[in] group The name of the resource group to which this
-             * resource belong.
-             * @param[in] is_manual True if the resource is manually loaded,
-             * false otherwise.
-             * @param[in] loader Pointer to a ManualResourceLoader
-             * implementation which will be called when the Resource wishes to
-             * load (should be supplied if is_manual is set to true). It can be
-             * null, but the Resource will never be able to reload if anything
-             * ever causes it to unload. Therefore provision of a proper
-             * ManualResourceLoader instance is strongly recommended.
+             * @param[in] group The name of the resource group to which this resource belong.
+             * @param[in] is_manual True if the resource is manually loaded, false otherwise.
+             * @param[in] loader Pointer to a ManualResourceLoader implementation which will be
+             * called when the Resource wishes to load (should be supplied if is_manual is set to
+             * true). It can be null, but the Resource will never be able to reload if anything
+             * ever causes it to unload. Therefore provision of a proper ManualResourceLoader
+             * instance is strongly recommended.
              */
             FLevelFile(
-              Ogre::ResourceManager *creator, const String &name,
-              Ogre::ResourceHandle handle, const String &group,
-              bool is_manual = false, Ogre::ManualResourceLoader *loader = NULL
+              Ogre::ResourceManager *creator, const String &name, Ogre::ResourceHandle handle,
+              const String &group, bool is_manual = false, Ogre::ManualResourceLoader *loader = NULL
             );
 
             /**
@@ -221,9 +216,7 @@ namespace VGears{
             /**
              * Loads the level animations
              */
-            void LoadAnimations(
-              const HRCFilePtr &model, const AnimationList &animations
-            );
+            void LoadAnimations(const HRCFilePtr &model, const AnimationList &animations);
 
             /**
              * Unloads the file.

+ 9 - 0
V-Gears/src/core/EntityManager.cpp

@@ -485,6 +485,15 @@ void EntityManager::SetEntityToCharacter(const char* entity_name, unsigned int c
         if (entity_[i]->GetName() == entity_name) entity_[i]->SetCharacter(char_id);
 }
 
+void EntityManager::AddTrack(const int id, const int track_id){
+    if (id >= 0 && id < 255) tracks_[id] = track_id;
+}
+
+int EntityManager::GetTrack(const int id){
+    if (tracks_.count(id) == 0) return -1;
+    else return tracks_[id];
+}
+
 bool EntityManager::SetEntityOnWalkmesh(Entity* entity){
     Ogre::Vector3 position3 = entity->GetPosition();
     Ogre::Vector2 position2;

+ 7 - 0
V-Gears/src/core/XmlMapFile.cpp

@@ -117,6 +117,13 @@ void XmlMapFile::LoadMap(){
             Ogre::String file_name = GetString(node, "file_name");
             if (file_name != "") ScriptManager::getSingleton().RunFile(file_name);
         }
+        else if (node->Type() == TiXmlNode::TINYXML_ELEMENT && node->ValueStr() == "tracks"){
+            for (TiXmlNode* track = node->FirstChild(); track; track = track->NextSibling()){
+                int id = GetInt(track, "id");
+                int track_id = GetInt(track, "track_id");
+                EntityManager::getSingleton().AddTrack(id, track_id);
+            }
+        }
         node = node->NextSibling();
     }
 }

+ 23 - 51
V-Gears/src/data/VGearsFLevelFile.cpp

@@ -36,9 +36,8 @@ namespace VGears{
     const String FLevelFile::SUFFIX_BACKGROUND_2D("/background_2d");
 
     FLevelFile::FLevelFile(
-      Ogre::ResourceManager *creator, const String &name,
-      Ogre::ResourceHandle handle, const String &group, bool is_manual,
-      Ogre::ManualResourceLoader *loader
+      Ogre::ResourceManager *creator, const String &name, Ogre::ResourceHandle handle,
+      const String &group, bool is_manual, Ogre::ManualResourceLoader *loader
     ) :
       Resource(creator, name, handle, group, is_manual, loader),
       background_texture_loader_(nullptr), background_2d_loader_(nullptr)
@@ -47,18 +46,14 @@ namespace VGears{
     FLevelFile::~FLevelFile(){
         if (background_texture_loader_){
             assert(background_texture_ != nullptr);
-            Ogre::TextureManager::getSingleton().remove(
-              background_texture_->getHandle()
-            );
+            Ogre::TextureManager::getSingleton().remove(background_texture_->getHandle());
             delete background_texture_loader_;
             background_texture_loader_ = nullptr;
         }
         background_texture_.reset();
         if (background_2d_loader_){
             assert(background_2d_ != nullptr);
-            Background2DFileManager::getSingleton().remove(
-              background_2d_->getHandle()
-            );
+            Background2DFileManager::getSingleton().remove(background_2d_->getHandle());
             delete background_2d_loader_;
             background_2d_loader_ = nullptr;
         }
@@ -81,11 +76,10 @@ namespace VGears{
         String background_2d_name(GetBackground2DName());
         if (background_2d_loader_ == nullptr){
             background_2d_loader_ = new FLevelBackground2DLoader(*this);
-            background_2d_
-              = Background2DFileManager::getSingleton().createResource(
-                background_2d_name, mGroup, true,
-                background_2d_loader_
-              ).staticCast<Background2DFile>();
+            background_2d_ = Background2DFileManager::getSingleton().createResource(
+              background_2d_name, mGroup, true,
+              background_2d_loader_
+            ).staticCast<Background2DFile>();
         }
     }
 
@@ -95,11 +89,9 @@ namespace VGears{
         ModelList::const_iterator it(models.begin()), it_end(models.end());
         while (it != it_end){
             String hrc_name(it->hrc_name);
-            Ogre::LogManager::getSingleton().stream() << "Loading Model: "
-              << hrc_name;
+            Ogre::LogManager::getSingleton().stream() << "Loading Model: " << hrc_name;
             StringUtil::toLowerCase(hrc_name);
-            HRCFilePtr hrc
-              = hrc_mgr.load(hrc_name, mGroup).staticCast<HRCFile>();
+            HRCFilePtr hrc = hrc_mgr.load(hrc_name, mGroup).staticCast<HRCFile>();
             LoadAnimations(hrc, it->animations);
             hrc_files_.push_back(hrc);
             ++ it;
@@ -117,13 +109,11 @@ namespace VGears{
             StringUtil::splitBase(it->name, animation_name);
             StringUtil::toLowerCase(animation_name);
             String animation_filename(animation_name + EXT_A);
-            AFilePtr animation
-              = a_mgr.load(
-                animation_filename, model->getGroup()
-              ).staticCast<AFile>();
+            AFilePtr animation = a_mgr.load(
+              animation_filename, model->getGroup()
+            ).staticCast<AFile>();
             animation_name = NameLookup::Animation(animation_name);
-            Ogre::LogManager::getSingleton().stream() << " Adding Animation: "
-              << animation_name;
+            Ogre::LogManager::getSingleton().stream() << " Adding Animation: " << animation_name;
             animation->AddTo(model->GetSkeleton(), animation_name);
             ++ it;
         }
@@ -148,21 +138,13 @@ namespace VGears{
 
     const std::vector<u8>& FLevelFile::GetRawScript() const{return raw_script_;}
 
-    const BackgroundFilePtr& FLevelFile::GetBackground(void) const{
-        return background_;
-    }
+    const BackgroundFilePtr& FLevelFile::GetBackground(void) const{return background_;}
 
-    void FLevelFile::SetRawScript(const std::vector<u8>& script_data){
-        raw_script_ = script_data;
-    }
+    void FLevelFile::SetRawScript(const std::vector<u8>& script_data){raw_script_ = script_data;}
 
-    void FLevelFile::SetBackground(const BackgroundFilePtr &background){
-        background_ = background;
-    }
+    void FLevelFile::SetBackground(const BackgroundFilePtr &background){background_ = background;}
 
-    const CameraMatrixFilePtr& FLevelFile::GetCameraMatrix(void) const{
-        return camera_matrix_;
-    }
+    const CameraMatrixFilePtr& FLevelFile::GetCameraMatrix(void) const{return camera_matrix_;}
 
     void FLevelFile::SetCameraMatrix(const CameraMatrixFilePtr &camera_matrix){
         camera_matrix_ = camera_matrix;
@@ -170,29 +152,19 @@ namespace VGears{
 
     const PaletteFilePtr& FLevelFile::GetPalette(void) const{return palette_;}
 
-    void FLevelFile::SetPalette(const PaletteFilePtr &palette){
-        palette_ = palette;
-    }
+    void FLevelFile::SetPalette(const PaletteFilePtr &palette){palette_ = palette;}
 
-    const ModelListFilePtr& FLevelFile::GetModelList() const{
-        return model_list_;
-    }
+    const ModelListFilePtr& FLevelFile::GetModelList() const{return model_list_;}
 
-    void FLevelFile::SetModelList(const ModelListFilePtr &model_list){
-        model_list_ = model_list;
-    }
+    void FLevelFile::SetModelList(const ModelListFilePtr &model_list){model_list_ = model_list;}
 
     const WalkmeshFilePtr& FLevelFile::GetWalkmesh() const{return walkmesh_;}
 
     const TriggersFilePtr& FLevelFile::GetTriggers() const{return triggers_;}
 
-    void FLevelFile::SetWalkmesh(const WalkmeshFilePtr &walkmesh){
-        walkmesh_ = walkmesh;
-    }
+    void FLevelFile::SetWalkmesh(const WalkmeshFilePtr &walkmesh){walkmesh_ = walkmesh;}
 
-    void FLevelFile::SetTriggers(const TriggersFilePtr& triggers){
-        triggers_ = triggers;
-    }
+    void FLevelFile::SetTriggers(const TriggersFilePtr& triggers){triggers_ = triggers;}
 
     String FLevelFile::GetBackgroundTextureName(void) const{
         String base_name;

+ 12 - 0
data/data/scripts/field.lua

@@ -223,6 +223,18 @@ open_shop = function(id)
     -- TODO: Implement
 end
 
+--- Plays one of the tracks assigned to the field.
+--
+-- @param id ID of the track in the field.
+play_map_music = function(id)
+    local track_id = entity_manager:get_track_id(id)
+    if entity_manager:get_track_id(id) ~= -1 then
+        audio_manager:play_music(tostring(track_id))
+    else
+        print("Requested non-existent music track ID " .. tostring(id))
+    end
+end
+
 --- Utility to change fields and enter battles.
 System["MapChanger"] = {
     map_name = "",