Parcourir la source

Let there be sound!

- Sound related features:
  - The installer now extracts all the sound effects from the original data and converts them to a compatible format.
  - Some AKAO operations implemented in the installer and in the engine (field sounds sound crappy, though).
  - Music can also be played, but the installer doesn't extract it yet.
  - Sound effects implemented in the existing menu screens.

- Materia related features:
  - The installer now extract all the materia information, with all of their effects.
  - Materia can be easily added to the inventory.
  - Several improvements to the materia menu: Materia can be equipped and removed, and enemy skill data is nicely displayed.
Iñigo Valentin il y a 3 ans
Parent
commit
e66295d06a
46 fichiers modifiés avec 1204 ajouts et 150 suppressions
  1. 10 0
      V-Gears-Installer/include/DataInstaller.h
  2. 82 0
      V-Gears-Installer/include/MediaDataInstaller.h
  3. 12 0
      V-Gears-Installer/src/DataInstaller.cpp
  4. 2 0
      V-Gears-Installer/src/MainWindow.cpp
  5. 103 1
      V-Gears-Installer/src/MediaDataInstaller.cpp
  6. 87 13
      V-Gears-Installer/src/decompiler/field/instruction/FieldMediaInstruction.cpp
  7. 6 15
      V-Gears/CMakeLists.txt
  8. 108 8
      V-Gears/include/core/AudioManager.h
  9. 19 1
      V-Gears/include/core/ScriptManagerBinds.h
  10. 43 0
      V-Gears/include/core/XmlSoundsFile.h
  11. 1 5
      V-Gears/src/Main.cpp
  12. 89 9
      V-Gears/src/core/AudioManager.cpp
  13. 40 0
      V-Gears/src/core/XmlSoundsFile.cpp
  14. 6 0
      data/data/audio/music/README.txt
  15. 6 0
      data/data/audio/sound/README.txt
  16. 4 0
      data/data/fields/README.txt
  17. 6 0
      data/data/game/README.txt
  18. 7 0
      data/data/images/characters/README.txt
  19. 5 0
      data/data/images/fonts/README.txt
  20. 6 0
      data/data/images/icons/README.txt
  21. 6 0
      data/data/images/other/README.txt
  22. 4 0
      data/data/images/reels/README.txt
  23. 6 0
      data/data/images/window/README.txt
  24. 2 16
      data/data/maps.xml
  25. 9 0
      data/data/models/fields/entities/README.txt
  26. 2 0
      data/data/screens/README.txt
  27. 1 1
      data/data/screens/equip_menu/equip_menu.xml
  28. 3 3
      data/data/screens/item_menu/item_menu.xml
  29. 1 1
      data/data/screens/main_menu/main_menu.xml
  30. 105 29
      data/data/screens/materia_menu/materia_menu.xml
  31. 1 1
      data/data/screens/name_menu/name_menu.xml
  32. 1 0
      data/data/scripts/README.txt
  33. 3 0
      data/data/scripts/data.lua
  34. 17 5
      data/data/scripts/debug_data.lua
  35. 3 0
      data/data/scripts/menu/begin_menu.lua
  36. 12 2
      data/data/scripts/menu/equip_menu.lua
  37. 42 2
      data/data/scripts/menu/item_menu.lua
  38. 14 0
      data/data/scripts/menu/main_menu.lua
  39. 273 35
      data/data/scripts/menu/materia_menu.lua
  40. 16 1
      data/data/scripts/menu/name_menu.lua
  41. 5 0
      data/data/scripts/menu/pause_menu.lua
  42. 23 2
      data/data/scripts/system.lua
  43. 4 0
      data/data/sounds.xml
  44. 1 0
      data/data/system/README.txt
  45. 2 0
      data/data/texts/README.txt
  46. 6 0
      doc/INSTALL_DATA.md

+ 10 - 0
V-Gears-Installer/include/DataInstaller.h

@@ -185,6 +185,16 @@ class DataInstaller{
              */
              */
             MEDIA_IMAGES,
             MEDIA_IMAGES,
 
 
+            /**
+             * Extract game sounds.
+             */
+            MEDIA_SOUNDS,
+
+            /**
+             * Build sounds index.
+             */
+            MEDIA_SOUNDS_INDEX,
+
             /**
             /**
              * Initializer for {@see SPAWN_POINTS_AND_SCALE_FACTORS}.
              * Initializer for {@see SPAWN_POINTS_AND_SCALE_FACTORS}.
              */
              */

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

@@ -15,6 +15,7 @@
 
 
 #pragma once
 #pragma once
 
 
+#include <unordered_map>
 #include "data/VGearsLGPArchive.h"
 #include "data/VGearsLGPArchive.h"
 #include "common/BinGZipFile.h"
 #include "common/BinGZipFile.h"
 #include "TexFile.h"
 #include "TexFile.h"
@@ -40,6 +41,11 @@ class MediaDataInstaller{
          */
          */
         ~MediaDataInstaller();
         ~MediaDataInstaller();
 
 
+        /**
+         * Populates the audio name maps.
+         */
+        void PopulateMaps();
+
         /**
         /**
          * Extracts all images contained in the menu LGP file and installs them.
          * Extracts all images contained in the menu LGP file and installs them.
          *
          *
@@ -48,8 +54,72 @@ class MediaDataInstaller{
          */
          */
         void InstallSprites();
         void InstallSprites();
 
 
+        /**
+         * Extracts all sound files contained in the audio.dat file and installs them.
+         *
+         */
+        void InstallSounds();
+
+        /**
+         * Writes the XML file with all audio entries.
+         */
+        void WriteSoundIndex();
+
     private:
     private:
 
 
+        /**
+         * Number of sound files to extract.
+         */
+        static int TOTAL_SOUNDS;
+
+        /**
+         * The standard WAV header.
+         *
+         * 78 bytes to be written to every wav file before anything else.
+         */
+        static u8 WAV_HEADER[78];
+
+        /**
+         * The structure of each audio file pointer in a sound FMT file. 74 bytes.
+         */
+        struct FmtFile{
+
+            /**
+             * Size of the wav file in the dat. 4 bytes.
+             */
+            u32 size;
+
+            /**
+             * Offset of the wav file in the dat. 4 bytes.
+             */
+            u32 offset;
+
+            /**
+             * Information for sound looping. 12 bytes.
+             */
+            u8 loop_metadata[16];
+
+            /**
+             * Microsoft WAVFORMATEX header for the wav file. 44 bytes.
+             */
+            u8 wav_header[18];
+
+            /**
+             * Samples per block. 2 bytes.
+             */
+            u16 samples_per_block;
+
+            /**
+             * Number of ADPCM coefficients (used for compression, should always be 7). 2 bytes.
+             */
+            u16 adpcm;
+
+            /**
+             * Standard Microsoft ADPCMCoefSets. 28 bytes.
+             */
+            u8 adpcm_sets[28];
+        };
+
         /**
         /**
          * The path to the directory from which to read the PC game data.
          * The path to the directory from which to read the PC game data.
          */
          */
@@ -70,4 +140,16 @@ class MediaDataInstaller{
          */
          */
         BinGZipFile window_;
         BinGZipFile window_;
 
 
+        /**
+         * Map for sound files with descriptive names.
+         */
+        std::unordered_map<int, std::string> sound_map_;
+
+        /**
+         * Sound data for each entry to write to the XML file
+         */
+        std::vector<std::string> sounds_;
+
+
+
 };
 };

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

@@ -136,6 +136,16 @@ int DataInstaller::Progress(){
         case MEDIA_IMAGES:
         case MEDIA_IMAGES:
             write_output_line_("Extracting game images...", 2, true);
             write_output_line_("Extracting game images...", 2, true);
             media_installer_->InstallSprites();
             media_installer_->InstallSprites();
+            installation_state_ = MEDIA_SOUNDS;
+            return CalcProgress();
+        case MEDIA_SOUNDS:
+            write_output_line_("Extracting sounds...", 2, true);
+            media_installer_->InstallSounds();
+            installation_state_ = MEDIA_SOUNDS_INDEX;
+            return CalcProgress();
+        case MEDIA_SOUNDS_INDEX:
+            write_output_line_("Building sound index...", 2, true);
+            media_installer_->WriteSoundIndex();
             installation_state_ = FIELD_SPAWN_POINTS_AND_SCALE_FACTORS_INIT;
             installation_state_ = FIELD_SPAWN_POINTS_AND_SCALE_FACTORS_INIT;
             return CalcProgress();
             return CalcProgress();
         case FIELD_SPAWN_POINTS_AND_SCALE_FACTORS_INIT:
         case FIELD_SPAWN_POINTS_AND_SCALE_FACTORS_INIT:
@@ -225,6 +235,8 @@ void DataInstaller::CreateDirectories(){
     CreateDir("images/reels");
     CreateDir("images/reels");
     CreateDir("images/window");
     CreateDir("images/window");
     CreateDir("models/fields/entities");
     CreateDir("models/fields/entities");
+    CreateDir("audio/fx");
+    CreateDir("audio/music");
     application_.ResMgr()->addResourceLocation("data/temp/char/", "FileSystem", "FFVII", true, true);
     application_.ResMgr()->addResourceLocation("data/temp/char/", "FileSystem", "FFVII", true, true);
     application_.ResMgr()->addResourceLocation("data/models/", "FileSystem", "FFVII", true, true);
     application_.ResMgr()->addResourceLocation("data/models/", "FileSystem", "FFVII", true, true);
     fields_lgp_ = std::make_unique<ScopedLgp>(
     fields_lgp_ = std::make_unique<ScopedLgp>(

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

@@ -175,6 +175,8 @@ void MainWindow::on_btn_data_run_clicked(){
           "data/field/flevel.lgp",
           "data/field/flevel.lgp",
           "data/kernel/KERNEL.BIN",
           "data/kernel/KERNEL.BIN",
           "data/menu/menu_us.lgp",
           "data/menu/menu_us.lgp",
+          "data/sound/audio.fmt",
+          "data/sound/audio.dat",
           "ff7.exe"
           "ff7.exe"
         };
         };
         // Ensure required files are in the input dir
         // Ensure required files are in the input dir

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

@@ -20,16 +20,39 @@
 #include <OgreResourceGroupManager.h>
 #include <OgreResourceGroupManager.h>
 #include <OgreImage.h>
 #include <OgreImage.h>
 #include <OgreColourValue.h>
 #include <OgreColourValue.h>
+#include <boost/format.hpp>
+#include <tinyxml.h>
 #include "MediaDataInstaller.h"
 #include "MediaDataInstaller.h"
 #include "data/VGearsLGPArchive.h"
 #include "data/VGearsLGPArchive.h"
 #include "data/VGearsTexFile.h"
 #include "data/VGearsTexFile.h"
 #include "TexFile.h"
 #include "TexFile.h"
 
 
+int MediaDataInstaller::TOTAL_SOUNDS = 723;
+
+u8 MediaDataInstaller::WAV_HEADER[] = {
+  0x52, 0x49, 0x46, 0x46, 0x70, 0x0B, 0x00, 0x00, 0x57, 0x41, 0x56, 0x45, 0x66, 0x6D, 0x74, 0x20,
+  0x32, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x44, 0xAC, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00,
+  0x00, 0x04, 0x04, 0x00, 0x20, 0x00, 0xF4, 0x07, 0x07, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02,
+  0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x40, 0x00, 0xF0, 0x00, 0x00, 0x00, 0xCC, 0x01,
+  0x30, 0xFF, 0x88, 0x01, 0x18, 0xFF, 0x64, 0x61, 0x74, 0x61, 0x2A, 0x0B, 0x00, 0x00
+};
 
 
 MediaDataInstaller::MediaDataInstaller(const std::string input_dir, const std::string output_dir):
 MediaDataInstaller::MediaDataInstaller(const std::string input_dir, const std::string output_dir):
   input_dir_(input_dir), output_dir_(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")
   menu_(input_dir + "data/menu/menu_us.lgp", "LGP"), window_(input_dir + "data/kernel/WINDOW.BIN")
-{}
+{PopulateMaps();}
+
+void MediaDataInstaller::PopulateMaps(){
+    // TODO: Names ending in "_?" are sounds that I think they match their name, but I'm not 100%
+    // sure.
+    sound_map_[0] = "Cursor";
+    sound_map_[1] = "Window";
+    sound_map_[2] = "Error";
+    sound_map_[3] = "Back";
+    sound_map_[4] = "Slash_Miss_?";
+    sound_map_[6] = "Chirp";
+    // TODO: Fill this list with somewhat descriptive names.
+}
 
 
 MediaDataInstaller::~MediaDataInstaller(){}
 MediaDataInstaller::~MediaDataInstaller(){}
 
 
@@ -273,3 +296,82 @@ void MediaDataInstaller::InstallSprites(){
         std::remove((output_dir_ + "images/" + f.file_name).c_str());
         std::remove((output_dir_ + "images/" + f.file_name).c_str());
     }
     }
 }
 }
+
+void MediaDataInstaller::InstallSounds(){
+    File fmt(input_dir_ + "data/sound/audio.fmt");
+    File dat(input_dir_ + "data/sound/audio.dat");
+
+    for (int i = 0; i < TOTAL_SOUNDS; i ++){
+        FmtFile header;
+
+        header.size = fmt.readU32LE();
+        // If size is 0, this is a bad header. There are 112 bytes of bad data, and after that,
+        // the next header.
+        if (header.size == 0){
+            for (int b = 0; b < 112; b += 4) fmt.readU32LE();
+            continue;
+        }
+
+        header.offset = fmt.readU32LE();
+        // If the offset is less than the previous one, also bad header. 34 bytes of bad data, and
+        // after that, the next header.
+        if (header.offset < dat.GetCurrentOffset()){
+            for (int b = 0; b < 34; b += 2) fmt.readU16LE();
+            continue;
+        }
+
+        // This should never happen, but just in case, never read outside the file
+        if (header.offset + header.size > dat.GetFileSize()) continue;
+
+        for (int l = 0; l < 16; l ++) header.loop_metadata[l] = fmt.readU8();
+        for (int l = 0; l < 18; l ++) header.wav_header[l] = fmt.readU8();
+        header.samples_per_block = fmt.readU16LE();
+        header.adpcm = fmt.readU16LE();
+        for (int l = 0; l < 28; l ++) header.adpcm_sets[l] = fmt.readU8();
+
+        dat.SetOffset(header.offset);
+        std::ofstream out(
+          output_dir_ + "audio/sound/" + std::to_string(i) + ".wav", std::ios::out | std::ios::binary
+        );
+
+
+        // Write the standard wav header.
+        for (int b = 0; b < 78; b ++) out.put(WAV_HEADER[b]);
+        // Write the data from the dat file.
+        for (int b = 0; b < header.size; b ++) out.put(dat.readU8());
+        out.close();
+
+        // Convert to OGG.
+        // TODO: Don't use system calls! Integrate libav or something that can do the conversion
+        // natively
+        std::string command = (boost::format(
+          "ffmpeg -hide_banner -loglevel panic -y -i %1%audio/sound/%2%.wav %1%audio/sound/%2%.ogg;"
+          "rm %1%audio/sound/%2%.wav"
+        ) % output_dir_ % i).str();
+        std::system(command.c_str());
+
+        sounds_.push_back("audio/sound/" + std::to_string(i) + ".ogg");
+    }
+}
+
+void MediaDataInstaller::WriteSoundIndex(){
+    TiXmlDocument xml;
+    std::unique_ptr<TiXmlElement> container(new TiXmlElement("sounds"));
+    //for (string item : items_){
+    for (int id = 0; id < sounds_.size(); id ++){
+        std::string path = sounds_[id];
+        std::unique_ptr<TiXmlElement> xml_sound(new TiXmlElement("sound"));
+        xml_sound->SetAttribute("file_name", path);
+        xml_sound->SetAttribute("name", id);
+        container->LinkEndChild(xml_sound.release());
+        // If there is a friendly name for this sound, add another entry
+        if (sound_map_.count(id) != 0){
+            std::unique_ptr<TiXmlElement> xml_sound_name(new TiXmlElement("sound"));
+            xml_sound_name->SetAttribute("file_name", path);
+            xml_sound_name->SetAttribute("name", sound_map_[id]);
+            container->LinkEndChild(xml_sound_name.release());
+        }
+    }
+    xml.LinkEndChild(container.release());
+    xml.SaveFile(output_dir_ + "sounds.xml");
+}

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

@@ -30,7 +30,7 @@ void FieldMediaInstruction::ProcessInst(
     FunctionMetaData md(func.metadata);
     FunctionMetaData md(func.metadata);
     switch (opcode_){
     switch (opcode_){
         case OPCODES::BGMOVIE: code_gen->WriteTodo(md.GetEntityName(), "BGMOVIE"); break;
         case OPCODES::BGMOVIE: code_gen->WriteTodo(md.GetEntityName(), "BGMOVIE"); break;
-        case OPCODES::AKAO2: ProcessAKAO2(code_gen); break;
+        case OPCODES::AKAO2: ProcessAKAO(code_gen); break;
         case OPCODES::MUSIC: ProcessMUSIC(code_gen); break;
         case OPCODES::MUSIC: ProcessMUSIC(code_gen); break;
         case OPCODES::SOUND: ProcessSOUND(code_gen); break;
         case OPCODES::SOUND: ProcessSOUND(code_gen); break;
         case OPCODES::AKAO: ProcessAKAO(code_gen); break;
         case OPCODES::AKAO: ProcessAKAO(code_gen); break;
@@ -77,10 +77,9 @@ void FieldMediaInstruction::ProcessAKAO2(CodeGenerator* code_gen){
 }
 }
 
 
 void FieldMediaInstruction::ProcessMUSIC(CodeGenerator* code_gen){
 void FieldMediaInstruction::ProcessMUSIC(CodeGenerator* code_gen){
-    code_gen->AddOutputLine((
-      boost::format("-- music:execute_akao(0x10, pointer_to_field_AKAO_%1%)")
-      % params_[0]->GetUnsigned()
-    ).str());
+    code_gen->AddOutputLine(
+      (boost::format("-- play_map_music(%1%)") % params_[0]->GetUnsigned()).str()
+    );
 }
 }
 
 
 void FieldMediaInstruction::ProcessSOUND(CodeGenerator* code_gen){
 void FieldMediaInstruction::ProcessSOUND(CodeGenerator* code_gen){
@@ -91,9 +90,9 @@ void FieldMediaInstruction::ProcessSOUND(CodeGenerator* code_gen){
     const auto& panning = FieldCodeGenerator::FormatValueOrVariable(
     const auto& panning = FieldCodeGenerator::FormatValueOrVariable(
       cg->GetFormatter(), params_[1]->GetUnsigned(), params_[3]->GetUnsigned()
       cg->GetFormatter(), params_[1]->GetUnsigned(), params_[3]->GetUnsigned()
     );
     );
-    code_gen->AddOutputLine(
-      (boost::format("-- music:execute_akao(0x20, %1%, %2%)") % soundId % panning).str()
-    );
+    code_gen->AddOutputLine((
+      boost::format("audio_manager:play_sound(\"%1%\") -- Direction: %2%)") % soundId % panning
+    ).str());
 }
 }
 
 
 void FieldMediaInstruction::ProcessAKAO(CodeGenerator* code_gen){
 void FieldMediaInstruction::ProcessAKAO(CodeGenerator* code_gen){
@@ -113,11 +112,86 @@ void FieldMediaInstruction::ProcessAKAO(CodeGenerator* code_gen){
     const auto& param5 = FieldCodeGenerator::FormatValueOrVariable(
     const auto& param5 = FieldCodeGenerator::FormatValueOrVariable(
       cg->GetFormatter(), params_[5]->GetUnsigned(), params_[11]->GetUnsigned()
       cg->GetFormatter(), params_[5]->GetUnsigned(), params_[11]->GetUnsigned()
     );
     );
-    auto op = params_[6]->GetUnsigned();
-    code_gen->AddOutputLine((
-      boost::format("-- music:execute_akao(0x%6$02x, %1%, %2%, %3%, %4%, %5%)")
-      % param1 % param2 % param3 % param4 % param5 % op
-    ).str());
+    int op = static_cast<int>(params_[6]->GetUnsigned());
+    switch (op){
+        /*code_gen->AddOutputLine((
+            boost::format("-- music:execute_akao(0x%6$02x, %1%, %2%, %3%, %4%, %5%)")
+            % param1 % param2 % param3 % param4 % param5 % op
+        ).str());*/
+        case 0x10: // Play music.
+        case 0x14: // Play music.
+            code_gen->AddOutputLine(
+              (boost::format("audio_manager:play_music(\"%1%\")") % param1).str()
+            );
+            break;
+        case 0x18: // Resume music.
+        case 0x19: // Resume music.
+            code_gen->AddOutputLine(
+              (boost::format("audio_manager:play_music(\"%1%\")") % param1).str()
+            );
+            break;
+        case 0x20: // Play 1 sound, channel 1.
+        case 0x24: // Play 1 sound, channel 1.
+            code_gen->AddOutputLine((
+              boost::format("audio_manager:play_sounds(\"%1%\", nil, nil, nil) -- Panning %2%")
+              % param2 % param1
+            ).str());
+            break;
+        case 0x21: // Play 2 sounds, channels 1 and 2.
+        case 0x25: // Play 2 sounds, channels 1 and 2.
+            code_gen->AddOutputLine((
+              boost::format("audio_manager:play_sounds(\"%1%\", \"%2%\", nil, nil) -- Panning %3%")
+              % param2 % param3 % param1
+            ).str());
+            break;
+        case 0x22: // Play 3 sounds, channels 1, 2 and 3.
+        case 0x26: // Play 3 sounds, channels 1, 2 and 3.
+            code_gen->AddOutputLine((
+              boost::format(
+                "audio_manager:play_sounds(\"%1%\", \"%2%\", \"%3%\", nil) -- Panning %4%"
+              ) % param2 % param3 % param4 % param1
+            ).str());
+            break;
+        case 0x23: // Play 4 sounds, channels 1, 2, 3 and 4.
+        case 0x27: // Play 4 sounds, channels 1, 2, 3 and 4.
+            code_gen->AddOutputLine((
+              boost::format(
+                "audio_manager:play_sounds(\"%1%\", \"%2%\", \"%3%\", \"%4%\") -- Panning %5%"
+              ) % param2 % param3 % param4 % param5 % param1
+            ).str());
+            break;
+        case 0x28: // Play sound, channel 1.
+            code_gen->AddOutputLine(
+              (boost::format("audio_manager:play_sound(\"%1%\", 1)") % param1).str()
+            );
+            break;
+        case 0x29: // Play sound, channel 2.
+            code_gen->AddOutputLine(
+              (boost::format("audio_manager:play_sound(\"%1%\", 2)") % param1).str()
+            );
+            break;
+        case 0x2A: // Play sound, channel 3.
+            code_gen->AddOutputLine(
+              (boost::format("audio_manager:play_sound(\"%1%\", 3)") % param1).str()
+            );
+            break;
+        case 0x2B: // Play sound, channel 4.
+            code_gen->AddOutputLine(
+              (boost::format("audio_manager:play_sound(\"%1%\", 4)") % param1).str()
+            );
+            break;
+        case 0x30: // Play sound, channel 5.
+            code_gen->AddOutputLine(
+              (boost::format("audio_manager:play_sound(\"%1%\", 5)") % param1).str()
+            );
+            break;
+        case 0x15: // Unused.
+        default:
+            code_gen->AddOutputLine((
+                boost::format("-- Unknown akao operation AKAO(0x%6$02x, %1%, %2%, %3%, %4%, %5%)")
+                % param1 % param2 % param3 % param4 % param5 % op
+            ).str());
+    }
 }
 }
 
 
 void FieldMediaInstruction::ProcessMULCK(CodeGenerator* code_gen){
 void FieldMediaInstruction::ProcessMULCK(CodeGenerator* code_gen){

+ 6 - 15
V-Gears/CMakeLists.txt

@@ -47,6 +47,7 @@ set(HEADER_FILES
     include/core/particles/ParticleTechniqueTranslator.h
     include/core/particles/ParticleTechniqueTranslator.h
     include/core/particles/ParticleVisual.h
     include/core/particles/ParticleVisual.h
     include/core/Assert.h
     include/core/Assert.h
+    include/core/AudioManager.h
     include/core/Background2D.h
     include/core/Background2D.h
     include/core/Background2DAnimation.h
     include/core/Background2DAnimation.h
     include/core/CameraManager.h
     include/core/CameraManager.h
@@ -89,10 +90,12 @@ set(HEADER_FILES
     include/core/XmlFontFile.h
     include/core/XmlFontFile.h
     include/core/XmlFontsFile.h
     include/core/XmlFontsFile.h
     include/core/XmlMapFile.h
     include/core/XmlMapFile.h
+    include/core/XmlMusicsFile.h
     include/core/XmlPrototypesFile.h
     include/core/XmlPrototypesFile.h
     include/core/XmlScreenFile.h
     include/core/XmlScreenFile.h
     include/core/XmlScreensFile.h
     include/core/XmlScreensFile.h
     include/core/XmlScriptsFile.h
     include/core/XmlScriptsFile.h
+    include/core/XmlSoundsFile.h
     include/core/XmlTextFile.h
     include/core/XmlTextFile.h
     include/core/XmlTextsFile.h
     include/core/XmlTextsFile.h
     include/core/DialogsManager.h
     include/core/DialogsManager.h
@@ -239,6 +242,7 @@ set(SOURCE_FILES
     src/core/particles/ParticleVisual.cpp
     src/core/particles/ParticleVisual.cpp
     src/core/particles/renderer/ParticleEntityRenderer.cpp
     src/core/particles/renderer/ParticleEntityRenderer.cpp
     src/core/particles/renderer/ParticleEntityRendererDictionary.cpp
     src/core/particles/renderer/ParticleEntityRendererDictionary.cpp
+    src/core/AudioManager.cpp
     src/core/Background2D.cpp
     src/core/Background2D.cpp
     src/core/Background2DAnimation.cpp
     src/core/Background2DAnimation.cpp
     src/core/CameraManager.cpp
     src/core/CameraManager.cpp
@@ -274,10 +278,12 @@ set(SOURCE_FILES
     src/core/XmlFontsFile.cpp
     src/core/XmlFontsFile.cpp
     src/core/XmlMapFile.cpp
     src/core/XmlMapFile.cpp
     src/core/XmlMapsFile.cpp
     src/core/XmlMapsFile.cpp
+    src/core/XmlMusicsFile.cpp
     src/core/XmlPrototypesFile.cpp
     src/core/XmlPrototypesFile.cpp
     src/core/XmlScreenFile.cpp
     src/core/XmlScreenFile.cpp
     src/core/XmlScreensFile.cpp
     src/core/XmlScreensFile.cpp
     src/core/XmlScriptsFile.cpp
     src/core/XmlScriptsFile.cpp
+    src/core/XmlSoundsFile.cpp
     src/core/XmlTextFile.cpp
     src/core/XmlTextFile.cpp
     src/core/XmlTextsFile.cpp
     src/core/XmlTextsFile.cpp
     src/core/DialogsManager.cpp
     src/core/DialogsManager.cpp
@@ -299,20 +305,6 @@ set(SOURCE_FILES
     src/FF7Common.cpp
     src/FF7Common.cpp
 )
 )
 
 
-# If sound, add more files.
-if(VGears_SOUND)
-    set(HEADER_FILES
-        ${HEADER_FILES}
-        include/core/AudioManager.h
-        include/core/XmlMusicsFile.h
-    )
-    set(SOURCE_FILES
-        ${SOURCE_FILES}
-        src/core/AudioManager.cpp
-        src/core/XmlMusicsFile.cpp
-    )
-endif()
-
 # Find LibOIS. TODO: Find it.
 # Find LibOIS. TODO: Find it.
 set(ois_lib "/usr/lib/x86_64-linux-gnu/libOIS.so")
 set(ois_lib "/usr/lib/x86_64-linux-gnu/libOIS.so")
 
 
@@ -321,7 +313,6 @@ include_directories(
     ${VGears_INCLUDE_DIRS}
     ${VGears_INCLUDE_DIRS}
     ${CMAKE_CURRENT_SOURCE_DIR}/include
     ${CMAKE_CURRENT_SOURCE_DIR}/include
     /usr/include/OIS/
     /usr/include/OIS/
-
 )
 )
 
 
 # Compiler options
 # Compiler options

+ 108 - 8
V-Gears/include/core/AudioManager.h

@@ -64,6 +64,15 @@ class AudioManager : public Ogre::Singleton<AudioManager>{
          */
          */
         void MusicPause();
         void MusicPause();
 
 
+        /**
+         * Plays a music track.
+         *
+         * To be called from Lua scripts
+         *
+         * @param[in] name Name of the track to play.
+         */
+        void ScriptPlayMusic(const char* name);
+
         /**
         /**
          * Plays a music track.
          * Plays a music track.
          *
          *
@@ -71,6 +80,46 @@ class AudioManager : public Ogre::Singleton<AudioManager>{
          */
          */
         void MusicPlay(const Ogre::String& name);
         void MusicPlay(const Ogre::String& name);
 
 
+        /**
+         * Plays a sound.
+         *
+         * To be called from Lua scripts
+         *
+         * @param[in] name Name of the sound to play.
+         */
+        void ScriptPlaySound(const char* name);
+
+        /**
+         * Plays a sound in a channel.
+         *
+         * To be called from Lua scripts
+         *
+         * @param[in] name Name of the sound to play.
+         * @param[in] channel Channel to play the sound in (1-5).
+         */
+        void ScriptPlaySound(const char* name, const int channel);
+
+        /**
+         * Plays up to 4 sounds, in 4 different channels.
+         *
+         * To be called from Lua scripts.
+         *
+         * @param[in] name1 Name of the first sound to play.
+         * @param[in] name2 Name of the second sound to play.
+         * @param[in] name3 Name of the third sound to play.
+         * @param[in] name4 Name of the fourth sound to play.
+         */
+        void AudioManager::ScriptPlaySounds(
+          const char* name1, const char* name2, const char* name3, const char* name4
+        );
+
+        /**
+         * Plays a sound.
+         *
+         * @param[in] name Name of the sound to play.
+         */
+        void SoundPlay(const Ogre::String& name);
+
         /**
         /**
          * Stops the currently playing music.
          * Stops the currently playing music.
          *
          *
@@ -81,7 +130,7 @@ class AudioManager : public Ogre::Singleton<AudioManager>{
         /**
         /**
          * Music structure.
          * Music structure.
          *
          *
-         * Defines a music track.
+         * Defines a music entry in musics.xml.
          */
          */
         struct Music{
         struct Music{
 
 
@@ -101,7 +150,25 @@ class AudioManager : public Ogre::Singleton<AudioManager>{
              * @todo How is a loop done with only one value? shouldn't it be
              * @todo How is a loop done with only one value? shouldn't it be
              * start and end of loop?
              * start and end of loop?
              */
              */
-            float        loop;
+            float loop;
+        };
+
+        /**
+         * Music structure.
+         *
+         * Defines a sound entry in sound.xml.
+         */
+        struct Sound{
+
+            /**
+             * The name of the sound.
+             */
+            Ogre::String name;
+
+            /**
+             * Sound filename
+             */
+            Ogre::String file;
         };
         };
 
 
         /**
         /**
@@ -112,14 +179,28 @@ class AudioManager : public Ogre::Singleton<AudioManager>{
         void AddMusic(const AudioManager::Music& music);
         void AddMusic(const AudioManager::Music& music);
 
 
         /**
         /**
-         * Retrieves a muisc track by name.
+         * Retrieves a music track by name.
          *
          *
-         * @param[in] name The track name
-         * @return The music track, or nullptr if there is no track by that
-         * name.
+         * @param[in] name The track name.
+         * @return The music track, or nullptr if there is no track by that name.
          */
          */
         AudioManager::Music* GetMusic(const Ogre::String& name);
         AudioManager::Music* GetMusic(const Ogre::String& name);
 
 
+        /**
+         * Adds a sound to the audio manager.
+         *
+         * @param[in] sound The sound to add.
+         */
+        void AddSound(const AudioManager::Sound& sound);
+
+        /**
+         * Retrieves a sound by name.
+         *
+         * @param[in] name The sound name.
+         * @return The sound track, or nullptr if there is no sound by that name.
+         */
+        AudioManager::Sound* GetSound(const Ogre::String& name);
+
     private:
     private:
 
 
         /**
         /**
@@ -234,6 +315,11 @@ class AudioManager : public Ogre::Singleton<AudioManager>{
                  */
                  */
                 bool stream_finished_;
                 bool stream_finished_;
 
 
+                /**
+                 * Audio buffer.
+                 */
+                char* buffer_;
+
                 /**
                 /**
                  * Audio source.
                  * Audio source.
                  */
                  */
@@ -266,6 +352,8 @@ class AudioManager : public Ogre::Singleton<AudioManager>{
 
 
         /**
         /**
          * Audio buffer.
          * Audio buffer.
+         *
+         * TODO: Unused? Not there are buffers per player. Remove.
          */
          */
         char* buffer_;
         char* buffer_;
 
 
@@ -275,7 +363,7 @@ class AudioManager : public Ogre::Singleton<AudioManager>{
         boost::recursive_mutex update_mutex_;
         boost::recursive_mutex update_mutex_;
 
 
         /**
         /**
-         * Thread to handle consurrent operations.
+         * Thread to handle concurrent operations.
          */
          */
         boost::thread* update_thread_;
         boost::thread* update_thread_;
 
 
@@ -285,15 +373,27 @@ class AudioManager : public Ogre::Singleton<AudioManager>{
         bool thread_continue_;
         bool thread_continue_;
 
 
         /**
         /**
-         * Music player
+         * Music player.
          */
          */
         AudioManager::Player music_;
         AudioManager::Player music_;
 
 
+        /**
+         * Sound effect player.
+         *
+         * TODO: Unused? remove.
+         */
+        AudioManager::Player fx_;
+
         /**
         /**
          * List of music.
          * List of music.
          */
          */
         std::list<AudioManager::Music> music_list_;
         std::list<AudioManager::Music> music_list_;
 
 
+        /**
+         * List of music.
+         */
+        std::list<AudioManager::Sound> sound_list_;
+
         /**
         /**
          * Size of a channel buffer.
          * Size of a channel buffer.
          *
          *

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

@@ -18,6 +18,7 @@
 #include "Logger.h"
 #include "Logger.h"
 #include "Entity.h"
 #include "Entity.h"
 #include "EntityManager.h"
 #include "EntityManager.h"
+#include "AudioManager.h"
 #include "Timer.h"
 #include "Timer.h"
 #include "UiManager.h"
 #include "UiManager.h"
 #include "UiWidget.h"
 #include "UiWidget.h"
@@ -189,7 +190,7 @@ void ScriptManager::InitBinds(){
 
 
     // Entity individual point commands
     // Entity individual point commands
     luabind::module(lua_state_)[
     luabind::module(lua_state_)[
-        luabind::class_< EntityPoint >("EntityPoint")
+        luabind::class_<EntityPoint>("EntityPoint")
           // Internally returns 3 values:
           // Internally returns 3 values:
           .def("get_position", (void(EntityPoint::*)()) &EntityPoint::ScriptGetPosition)
           .def("get_position", (void(EntityPoint::*)()) &EntityPoint::ScriptGetPosition)
           .def("get_rotation", (float(EntityPoint::*)()) &EntityPoint::ScriptGetRotation)
           .def("get_rotation", (float(EntityPoint::*)()) &EntityPoint::ScriptGetRotation)
@@ -255,6 +256,22 @@ void ScriptManager::InitBinds(){
           )
           )
     ];
     ];
 
 
+    // Commands for the entity manager, not related to any particular entity.
+    luabind::module(lua_state_)[
+        luabind::class_<AudioManager>("AudioManager")
+          .def("play_music", (void(AudioManager::*)(const char*)) &AudioManager::ScriptPlayMusic)
+          .def("play_sound", (void(AudioManager::*)(const char*)) &AudioManager::ScriptPlaySound)
+          .def(
+            "play_sound",
+            (void(AudioManager::*)(const char*, const int)) &AudioManager::ScriptPlaySound
+          )
+          .def(
+            "play_sounds",
+            (void(AudioManager::*)(const char*, const char*, const char*, const char*))
+            &AudioManager::ScriptPlaySounds
+          )
+    ];
+
     // 2D background and camera commands
     // 2D background and camera commands
     luabind::module(lua_state_)[
     luabind::module(lua_state_)[
         luabind::class_<Background2D>("Background2D")
         luabind::class_<Background2D>("Background2D")
@@ -448,6 +465,7 @@ void ScriptManager::InitBinds(){
     //auto b = luabind::globals(lua_state_)["entity_manager"];
     //auto b = luabind::globals(lua_state_)["entity_manager"];
     luabind::globals(lua_state_)["entity_manager"]
     luabind::globals(lua_state_)["entity_manager"]
       = boost::ref(*(EntityManager::getSingletonPtr()));
       = boost::ref(*(EntityManager::getSingletonPtr()));
+    luabind::globals(lua_state_)["audio_manager"] = boost::ref(*(AudioManager::getSingletonPtr()));
     luabind::globals(lua_state_)["background2d"]
     luabind::globals(lua_state_)["background2d"]
       = boost::ref(*(EntityManager::getSingletonPtr()->GetBackground2D()));
       = boost::ref(*(EntityManager::getSingletonPtr()->GetBackground2D()));
     luabind::globals(lua_state_)["walkmesh"]
     luabind::globals(lua_state_)["walkmesh"]

+ 43 - 0
V-Gears/include/core/XmlSoundsFile.h

@@ -0,0 +1,43 @@
+/*
+ * 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 "XmlFile.h"
+
+/**
+ * Handles the main sounds file.
+ */
+class XmlSoundsFile : public XmlFile{
+
+    public:
+
+        /**
+         * Constructor.
+         *
+         * @param[in] file Path to the main sounds file.
+         */
+        XmlSoundsFile(const Ogre::String& file);
+
+        /**
+         * Destructor.
+         */
+        virtual ~XmlSoundsFile();
+
+        /**
+         * Parses the file and loads the sounds.
+         */
+        void LoadSounds();
+};

+ 1 - 5
V-Gears/src/Main.cpp

@@ -21,9 +21,7 @@
 #include <OgreOverlayManager.h>
 #include <OgreOverlayManager.h>
 #include <OgreOverlaySystem.h>
 #include <OgreOverlaySystem.h>
 #include "VGearsGameState.h"
 #include "VGearsGameState.h"
-#ifdef VGears_SOUND
 #include "core/AudioManager.h"
 #include "core/AudioManager.h"
-#endif
 #include "common/VGearsApplication.h"
 #include "common/VGearsApplication.h"
 #include "core/CameraManager.h"
 #include "core/CameraManager.h"
 #include "core/ConfigCmdManager.h"
 #include "core/ConfigCmdManager.h"
@@ -106,10 +104,8 @@ int main(int argc, char *argv[]){
         auto input_manager = std::make_unique<InputManager>();
         auto input_manager = std::make_unique<InputManager>();
 
 
 
 
-#ifdef VGears_SOUND
-        //auto audio_manager = std::make_unique<AudioManager>();
+        auto audio_manager = std::make_unique<AudioManager>();
         //audio_manager->MusicPlay( "loop1" );
         //audio_manager->MusicPlay( "loop1" );
-#endif
 
 
         // Create this earlier than DisplayFrameListener cause it can fire
         // Create this earlier than DisplayFrameListener cause it can fire
         // events there
         // events there

+ 89 - 9
V-Gears/src/core/AudioManager.cpp

@@ -13,10 +13,12 @@
  * GNU General Public License for more details.
  * GNU General Public License for more details.
  */
  */
 
 
+#include <iostream>
 #include <list>
 #include <list>
 #include <boost/thread.hpp>
 #include <boost/thread.hpp>
 #include "core/AudioManager.h"
 #include "core/AudioManager.h"
 #include "core/XmlMusicsFile.h"
 #include "core/XmlMusicsFile.h"
+#include "core/XmlSoundsFile.h"
 #include "core/Logger.h"
 #include "core/Logger.h"
 
 
 template<>AudioManager *Ogre::Singleton<AudioManager>::msSingleton = nullptr;
 template<>AudioManager *Ogre::Singleton<AudioManager>::msSingleton = nullptr;
@@ -25,7 +27,8 @@ ALsizei AudioManager::channel_buffer_number_ = 2;
 int AudioManager::channel_buffer_size_ = 96 * 1024;
 int AudioManager::channel_buffer_size_ = 96 * 1024;
 
 
 AudioManager::AudioManager():
 AudioManager::AudioManager():
-  initialized_(false), thread_continue_(true), update_mutex_(), music_(&update_mutex_)
+  initialized_(false), thread_continue_(true), update_mutex_(), music_(&update_mutex_),
+  fx_(&update_mutex_)
 {
 {
     al_device_ = alcOpenDevice(nullptr);
     al_device_ = alcOpenDevice(nullptr);
     if (al_device_ != nullptr){
     if (al_device_ != nullptr){
@@ -52,9 +55,13 @@ AudioManager::AudioManager():
     }
     }
     else LOG_ERROR("AudioManager failed to initialised. There's no default sound device.");
     else LOG_ERROR("AudioManager failed to initialised. There's no default sound device.");
 
 
-    // Load musics
+    // Load musics.
     XmlMusicsFile musics("./data/musics.xml");
     XmlMusicsFile musics("./data/musics.xml");
     musics.LoadMusics();
     musics.LoadMusics();
+
+    // Load sounds.
+    XmlSoundsFile sounds("./data/sounds.xml");
+    sounds.LoadSounds();
 }
 }
 
 
 AudioManager::~AudioManager(){
 AudioManager::~AudioManager(){
@@ -85,6 +92,7 @@ void AudioManager::operator()(){
 void AudioManager::Update(){
 void AudioManager::Update(){
     boost::recursive_mutex::scoped_lock lock(update_mutex_);
     boost::recursive_mutex::scoped_lock lock(update_mutex_);
     music_.Update();
     music_.Update();
+    fx_.Update();
 }
 }
 
 
 void AudioManager::MusicPause(){
 void AudioManager::MusicPause(){
@@ -92,7 +100,13 @@ void AudioManager::MusicPause(){
     music_.Pause();
     music_.Pause();
 }
 }
 
 
+void AudioManager::ScriptPlayMusic(const char* name){
+    const Ogre::String name_str = Ogre::String(name);
+    MusicPlay(name_str);
+}
+
 void AudioManager::MusicPlay(const Ogre::String& name){
 void AudioManager::MusicPlay(const Ogre::String& name){
+    std::cout << "[MUSIC] play " << name << "\n";
     if (initialized_){
     if (initialized_){
         boost::recursive_mutex::scoped_lock lock(update_mutex_);
         boost::recursive_mutex::scoped_lock lock(update_mutex_);
         AudioManager::Music* music = GetMusic(name);
         AudioManager::Music* music = GetMusic(name);
@@ -105,6 +119,37 @@ void AudioManager::MusicPlay(const Ogre::String& name){
     }
     }
 }
 }
 
 
+void AudioManager::SoundPlay(const Ogre::String& name){
+    std::cout << "[FX] play " << name << "\n";
+    if (initialized_){
+        boost::recursive_mutex::scoped_lock lock(update_mutex_);
+        AudioManager::Sound* fx = GetSound(name);
+        if (fx == nullptr){
+            LOG_ERROR("No sound found with name \"" + name + "\".");
+            return;
+        }
+        //Player player(&update_mutex_);
+        //player.SetLoop(-1);
+        //player.Play(fx->file);
+        fx_.SetLoop(-1);
+        fx_.Play(fx->file);
+    }
+}
+
+void AudioManager::ScriptPlaySound(const char* name){
+    const Ogre::String name_str = Ogre::String(name);
+    SoundPlay(name_str);
+}
+
+void ScriptPlaySound(const char* name, const int channel){ScriptPlaySound(name1);}
+
+void AudioManager::ScriptPlaySounds(
+  const char* name1, const char* name2, const char* name3, const char* name4
+){
+    // TODO: Playing only name1, implement the rest.
+    ScriptPlaySound(name1);
+}
+
 void AudioManager::MusicStop(){
 void AudioManager::MusicStop(){
     boost::recursive_mutex::scoped_lock lock(update_mutex_);
     boost::recursive_mutex::scoped_lock lock(update_mutex_);
     music_.Stop();
     music_.Stop();
@@ -126,11 +171,33 @@ void AudioManager::AddMusic(const AudioManager::Music& music){
     music_list_.push_back(music);
     music_list_.push_back(music);
 }
 }
 
 
+void AudioManager::AddSound(const AudioManager::Sound& sound){
+    std::cout << "[ADD_SOUND] " << sound.name << "\n";
+    boost::recursive_mutex::scoped_lock lock(update_mutex_);
+    for (
+      std::list<AudioManager::Sound>::iterator it = sound_list_.begin();
+      it != sound_list_.end();
+      ++ it
+    ){
+        if (it->name == sound.name){
+            LOG_ERROR("Sound with name '" + sound.name + "' already exists.");
+            return;
+        }
+    }
+    sound_list_.push_back(sound);
+}
+
 AudioManager::Music* AudioManager::GetMusic(const Ogre::String& name){
 AudioManager::Music* AudioManager::GetMusic(const Ogre::String& name){
     boost::recursive_mutex::scoped_lock lock(update_mutex_);
     boost::recursive_mutex::scoped_lock lock(update_mutex_);
-    for (auto it = music_list_.begin(); it != music_list_.end(); ++ it){
+    for (auto it = music_list_.begin(); it != music_list_.end(); ++ it)
+        if (it->name == name) return &(*it);
+    return nullptr;
+}
+
+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);
         if (it->name == name) return &(*it);
-    }
     return nullptr;
     return nullptr;
 }
 }
 
 
@@ -154,7 +221,9 @@ const char* AudioManager::ALCError(const ALCdevice* device){
 AudioManager::Player::Player(boost::recursive_mutex* mutex):
 AudioManager::Player::Player(boost::recursive_mutex* mutex):
   loop_(-1.0), vorbis_info_(nullptr), vorbis_section_(0),
   loop_(-1.0), vorbis_info_(nullptr), vorbis_section_(0),
   stream_finished_(false), update_mutex_(mutex)
   stream_finished_(false), update_mutex_(mutex)
-{}
+{
+    buffer_ = new char[1024 * 96];
+}
 
 
 AudioManager::Player::~Player(){Stop();}
 AudioManager::Player::~Player(){Stop();}
 
 
@@ -185,7 +254,8 @@ void AudioManager::Player::Play(const Ogre::String &file){
         if (buffer_size){
         if (buffer_size){
             alBufferData(
             alBufferData(
               buffers[i], vorbis_info_->channels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16,
               buffers[i], vorbis_info_->channels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16,
-              static_cast<const ALvoid*>(AudioManager::getSingleton().buffer_),
+              //static_cast<const ALvoid*>(AudioManager::getSingleton().buffer_),
+              static_cast<const ALvoid*>(buffer_),
               static_cast<ALsizei>(buffer_size), static_cast<ALsizei>(vorbis_info_->rate)
               static_cast<ALsizei>(buffer_size), static_cast<ALsizei>(vorbis_info_->rate)
             );
             );
             alSourceQueueBuffers(source_, 1, &buffers[i]);
             alSourceQueueBuffers(source_, 1, &buffers[i]);
@@ -216,6 +286,7 @@ void AudioManager::Player::Stop(){
     alDeleteSources(1, &source_);
     alDeleteSources(1, &source_);
     // Cleanup
     // Cleanup
     ov_clear(&vorbis_file_);
     ov_clear(&vorbis_file_);
+    stream_finished_ = false;
 }
 }
 
 
 void AudioManager::Player::SetLoop(const float loop){
 void AudioManager::Player::SetLoop(const float loop){
@@ -236,18 +307,26 @@ void AudioManager::Player::Update(){
             alBufferData(
             alBufferData(
               buffer_id, vorbis_info_->channels == 1 ?
               buffer_id, vorbis_info_->channels == 1 ?
                 AL_FORMAT_MONO16 : AL_FORMAT_STEREO16,
                 AL_FORMAT_MONO16 : AL_FORMAT_STEREO16,
-              static_cast<const ALvoid*>(AudioManager::getSingleton().buffer_),
+              //static_cast<const ALvoid*>(AudioManager::getSingleton().buffer_),
+              static_cast<const ALvoid*>(buffer_),
               static_cast<ALsizei>(buffer_size), static_cast<ALsizei>(vorbis_info_->rate)
               static_cast<ALsizei>(buffer_size), static_cast<ALsizei>(vorbis_info_->rate)
             );
             );
             alSourceQueueBuffers(source_, 1, &buffer_id);
             alSourceQueueBuffers(source_, 1, &buffer_id);
+            std::cout << "Stream unfinished\n";
         }
         }
         // Finished reading stream
         // Finished reading stream
         else{
         else{
+            std::cout << "Stream finished\n";
             stream_finished_ = true;
             stream_finished_ = true;
             break;
             break;
         }
         }
     }
     }
 
 
+    if (stream_finished_ && loop_ < 0){
+        std::cout << "Stopping\n";
+        Stop();
+    }
+
     // Manage source state
     // Manage source state
     alGetSourcei(source_, AL_BUFFERS_PROCESSED, &processed);
     alGetSourcei(source_, AL_BUFFERS_PROCESSED, &processed);
     int queued;
     int queued;
@@ -263,7 +342,8 @@ void AudioManager::Player::Update(){
 ALsizei AudioManager::Player::FillBuffer(){
 ALsizei AudioManager::Player::FillBuffer(){
     ALsizei read = 0;
     ALsizei read = 0;
     if (stream_finished_) return read;
     if (stream_finished_) return read;
-    char *&buffer = AudioManager::getSingleton().buffer_;
+    //char *&buffer = AudioManager::getSingleton().buffer_;
+    char *&buffer = buffer_;
     bool finished = false;
     bool finished = false;
     do{
     do{
         long result = ov_read(
         long result = ov_read(
@@ -282,7 +362,7 @@ ALsizei AudioManager::Player::FillBuffer(){
             case 0:
             case 0:
                 // If there isn't loop point or can't seek
                 // If there isn't loop point or can't seek
                 if (loop_ < 0.0f || ov_time_seek(&vorbis_file_, loop_)) finished = true;
                 if (loop_ < 0.0f || ov_time_seek(&vorbis_file_, loop_)) finished = true;
-            break;
+                break;
             // Readed "result" bytes
             // Readed "result" bytes
             default:
             default:
                 read += result;
                 read += result;

+ 40 - 0
V-Gears/src/core/XmlSoundsFile.cpp

@@ -0,0 +1,40 @@
+/*
+ * 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 "core/AudioManager.h"
+#include "core/Logger.h"
+#include "core/XmlSoundsFile.h"
+
+XmlSoundsFile::XmlSoundsFile(const Ogre::String& file): XmlFile(file){}
+
+XmlSoundsFile::~XmlSoundsFile(){}
+
+void XmlSoundsFile::LoadSounds(){
+    TiXmlNode* node = file_.RootElement();
+    if (node == nullptr || node->ValueStr() != "sounds"){
+        LOG_ERROR(file_.ValueStr() + " is not a valid sounds file! No <sounds> in root.");
+        return;
+    }
+    node = node->FirstChild();
+    while (node != nullptr){
+        if (node->Type() == TiXmlNode::TINYXML_ELEMENT && node->ValueStr() == "sound"){
+            AudioManager::Sound sound;
+            sound.name = GetString(node, "name");
+            sound.file = "./data/" + GetString(node, "file_name");
+            AudioManager::getSingleton().AddSound(sound);
+        }
+        node = node->NextSibling();
+    }
+}

+ 6 - 0
data/data/audio/music/README.txt

@@ -0,0 +1,6 @@
+This directory is intentionally empty. It will be populated by the data installer.
+
+Once the data installation is complete, musics files will be installed here. They will have very
+un-descriptive file names, and there is no metadata in them, such as titles. But, if you want to
+find a specific one, a looc at the file data/musics.xml (also generated by the installer) may help
+you. There, some files in this folder may be referenced by a friendlier name.

+ 6 - 0
data/data/audio/sound/README.txt

@@ -0,0 +1,6 @@
+This directory is intentionally empty. It will be populated by the data installer.
+
+Once the data installation is complete, sound files will be installed here. They will have very
+un-descriptive file names, and there is no metadata in them, such as titles. But, if you want to
+find a specific one, a looc at the file data/sounds.xml (also generated by the installer) may help
+you. There, some files in this folder may be referenced by a friendlier name.

+ 4 - 0
data/data/fields/README.txt

@@ -0,0 +1,4 @@
+This directory is intentionally empty. It will be populated by the data installer.
+
+Once the data installation is complete, every field file will be installed here, grouped by field
+name. Field descriptions, backgrounds, walkeshes and scripts will be generated.

+ 6 - 0
data/data/game/README.txt

@@ -0,0 +1,6 @@
+This directory is intentionally empty. It will be populated by the data installer.
+
+Once the data installation is complete, several Lua scripts with static game data will be
+installed here. Those scripts only define game constants and settings, and are not to be written
+during a normal playthrough. If you are a modder, those scripts are a good place to start.
+

+ 7 - 0
data/data/images/characters/README.txt

@@ -0,0 +1,7 @@
+This directory is intentionally empty. It will be populated by the data installer.
+
+Once the data installation is complete, character portraits will be installed here, named after
+the character IDs. If you are a modder, it's perfectly fine to edit them as you see fit, but try
+to keep their aspect ratio. If changed, menu screens, and probably some other things, will need to
+be revised.
+

+ 5 - 0
data/data/images/fonts/README.txt

@@ -0,0 +1,5 @@
+This directory is intentionally empty. It will be populated by the data installer.
+
+Once the data installation is complete, images with game fonts will be installed here. As of the
+time of writting this, they are not used for anything.
+

+ 6 - 0
data/data/images/icons/README.txt

@@ -0,0 +1,6 @@
+This directory is intentionally empty. It will be populated by the data installer.
+
+Once the data installation is complete, several icons used in the game will be installed here
+If you are a modder, it's perfectly fine to edit them as you see fit, but try to keep their size
+and names intact. If changed, menu screens, and probably some other things, will need to be
+revised.

+ 6 - 0
data/data/images/other/README.txt

@@ -0,0 +1,6 @@
+This directory is intentionally empty. It will be populated by the data installer.
+
+Once the data installation is complete, several images used in the game will be installed here
+If you are a modder, it's perfectly fine to edit them as you see fit, but try to keep their size
+and names intact. If changed, menu screens, and probably some other things, will need to be
+revised.

+ 4 - 0
data/data/images/reels/README.txt

@@ -0,0 +1,4 @@
+This directory is intentionally empty. It will be populated by the data installer.
+
+Once the data installation is complete, several icons used in the game reels will be installed
+here. As of the time of this writting, they are not used for anything.

+ 6 - 0
data/data/images/window/README.txt

@@ -0,0 +1,6 @@
+This directory is intentionally empty. It will be populated by the data installer.
+
+Once the data installation is complete, several images used for window decorations will be
+installed here. If you are a modder, it's perfectly fine to edit them as you see fit, but try to
+keep their size and names intact. If changed, menu screens, and probably some other things, will
+need to be revised.

+ 2 - 16
data/data/maps.xml

@@ -1,18 +1,4 @@
+<!-- This file is to be overwritten by the installer -->
 <maps>
 <maps>
-    <map name="elevtr1" file_name="fields/elevtr1/map.xml" />
-    <map name="md1_1" file_name="fields/md1_1/map.xml" />
-    <map name="md1_2" file_name="fields/md1_2/map.xml" />
-    <map name="md1stin" file_name="fields/md1stin/map.xml" />
-    <map name="md8_1" file_name="fields/md8_1/map.xml" />
-    <map name="md8_4" file_name="fields/md8_4/map.xml" />
-    <map name="nmkin_1" file_name="fields/nmkin_1/map.xml" />
-    <map name="nmkin_2" file_name="fields/nmkin_2/map.xml" />
-    <map name="nmkin_3" file_name="fields/nmkin_3/map.xml" />
-    <map name="nmkin_4" file_name="fields/nmkin_4/map.xml" />
-    <map name="nmkin_5" file_name="fields/nmkin_5/map.xml" />
-    <map name="nrthmk" file_name="fields/nrthmk/map.xml" />
-    <map name="rootmap" file_name="fields/rootmap/map.xml" />
-    <map name="startmap" file_name="fields/startmap/map.xml" />
-    <map name="tin_1" file_name="fields/tin_1/map.xml" />
-    <map name="tin_2" file_name="fields/tin_2/map.xml" />
+    <map name="example" file_name="fields/example/map.xml" />
 </maps>
 </maps>

+ 9 - 0
data/data/models/fields/entities/README.txt

@@ -0,0 +1,9 @@
+This directory is intentionally empty. It will be populated by the data installer.
+
+Once the data installation is complete, a lot of files will be installed here. They contain
+information about 3D models and textures. Most of them will have very cryptic names. If you are a
+modder and want to manually edit the 3D models, I have a few pointers for you:
+
+ 1. Good luck.
+ 2. Make backups.
+ 3. Document everything! If you are succesfull, I would love to know how you did it.

+ 2 - 0
data/data/screens/README.txt

@@ -0,0 +1,2 @@
+This folder contains the definition for every static screen in the game, be it menus, dialog
+windows...

+ 1 - 1
data/data/screens/equip_menu/equip_menu.xml

@@ -2,7 +2,7 @@
     <widget name="Container" width="40%" height="40%" x="0" y="0" align="left" valign="top" scale="2.5 2.5" visible="true">
     <widget name="Container" width="40%" height="40%" x="0" y="0" align="left" valign="top" scale="2.5 2.5" visible="true">
         <widget name="Character" width="100%" height="70" visible="true">
         <widget name="Character" width="100%" height="70" visible="true">
             <prototype name="Window"/>
             <prototype name="Window"/>
-            <sprite name="Portrait" image="images/other/choco.png" x="10" y="12" width="48" height="48" visible="true" />
+            <sprite name="Portrait" image="images/other/choco.png" x="10" y="12" width="42" height="48" visible="true" />
             <widget name="Data" x="66" y="14" width="100%-66" height="100%-14" visible="true">
             <widget name="Data" x="66" y="14" width="100%-66" height="100%-14" visible="true">
                 <prototype name="MenuCharacterData" />
                 <prototype name="MenuCharacterData" />
             </widget>
             </widget>

+ 3 - 3
data/data/screens/item_menu/item_menu.xml

@@ -22,19 +22,19 @@
         <widget name="ItemMenuCharacters" x="0" y="48" width="50%" height="100%-48" visible="true">
         <widget name="ItemMenuCharacters" x="0" y="48" width="50%" height="100%-48" visible="true">
             <prototype name="Window" />
             <prototype name="Window" />
             <widget name="Character1" x="0" y="0" width="100%" height="55" visible="true">
             <widget name="Character1" x="0" y="0" width="100%" height="55" visible="true">
-                <sprite name="Portrait" image="images/other/choco.png" x="10" y="12" width="48" height="48" visible="true" />
+                <sprite name="Portrait" image="images/other/choco.png" x="10" y="12" width="42" height="48" visible="true" />
                 <widget name="Data" x="66" y="14" width="100%" height="100%" visible="true">
                 <widget name="Data" x="66" y="14" width="100%" height="100%" visible="true">
                     <prototype name="MenuCharacterData" />
                     <prototype name="MenuCharacterData" />
                 </widget>
                 </widget>
             </widget>
             </widget>
             <widget name="Character2" x="0" y="55" width="100%" height="55" visible="true">
             <widget name="Character2" x="0" y="55" width="100%" height="55" visible="true">
-                <sprite name="Portrait" image="images/other/choco.png" x="10" y="12" width="48" height="48" visible="true" />
+                <sprite name="Portrait" image="images/other/choco.png" x="10" y="12" width="42" height="48" visible="true" />
                 <widget name="Data" x="66" y="14" width="100%" height="100%" visible="true">
                 <widget name="Data" x="66" y="14" width="100%" height="100%" visible="true">
                     <prototype name="MenuCharacterData" />
                     <prototype name="MenuCharacterData" />
                 </widget>
                 </widget>
             </widget>
             </widget>
             <widget name="Character3" x="0" y="110" width="100%" height="55" visible="true">
             <widget name="Character3" x="0" y="110" width="100%" height="55" visible="true">
-                <sprite name="Portrait" image="images/other/choco.png" x="10" y="12" width="48" height="48" visible="true" />
+                <sprite name="Portrait" image="images/other/choco.png" x="10" y="12" width="42" height="48" visible="true" />
                 <widget name="Data" x="66" y="14" width="100%" height="100%" visible="true">
                 <widget name="Data" x="66" y="14" width="100%" height="100%" visible="true">
                     <prototype name="MenuCharacterData" />
                     <prototype name="MenuCharacterData" />
                 </widget>
                 </widget>

+ 1 - 1
data/data/screens/main_menu/main_menu.xml

@@ -5,7 +5,7 @@
             <animation name="Appear" length="0.3" x="0:105%,0.3:5%" />
             <animation name="Appear" length="0.3" x="0:105%,0.3:5%" />
             <animation name="Disappear" length="0.3" x="0:5%,0.3:105%" />
             <animation name="Disappear" length="0.3" x="0:5%,0.3:105%" />
             <widget name="Character1" x="2%" y="2%" width="100%" height="30%" visible="true">
             <widget name="Character1" x="2%" y="2%" width="100%" height="30%" visible="true">
-                <sprite name="Portrait" image="images/other/choco.png" x="5" y="12" width="56" height="56" visible="true">
+                <sprite name="Portrait" image="images/other/choco.png" x="5" y="12" width="49" height="56" visible="true">
                     <animation name="RowFront" length="0" x="0:5" />
                     <animation name="RowFront" length="0" x="0:5" />
                     <animation name="RowBack" length="0" x="0:25" />
                     <animation name="RowBack" length="0" x="0:25" />
                 </sprite>
                 </sprite>

+ 105 - 29
data/data/screens/materia_menu/materia_menu.xml

@@ -2,7 +2,7 @@
     <widget name="Container" width="40%" height="40%" x="0" y="0" align="left" valign="top" scale="2.5 2.5" visible="true">
     <widget name="Container" width="40%" height="40%" x="0" y="0" align="left" valign="top" scale="2.5 2.5" visible="true">
         <widget name="Character" width="100%" height="75" visible="true">
         <widget name="Character" width="100%" height="75" visible="true">
             <prototype name="Window"/>
             <prototype name="Window"/>
-            <sprite name="Portrait" image="images/other/choco.png" x="10" y="12" width="48" height="58" visible="true" />
+            <sprite name="Portrait" image="images/other/choco.png" x="10" y="8" width="50" height="58" visible="true" />
             <widget name="Data" x="66" y="14" width="100%-66" height="100%-14" visible="true">
             <widget name="Data" x="66" y="14" width="100%-66" height="100%-14" visible="true">
                 <prototype name="MenuCharacterData" />
                 <prototype name="MenuCharacterData" />
             </widget>
             </widget>
@@ -85,9 +85,73 @@
             <prototype name="Window" />
             <prototype name="Window" />
             <text_area name="Help" x="16" y="6" font="FFVIIFont" visible="true" />
             <text_area name="Help" x="16" y="6" font="FFVIIFont" visible="true" />
         </widget>
         </widget>
-        <widget name="Details" x="0" y="94" width="230" height="100%-99" visible="true">
+        <widget name="Details" x="0" y="99" width="230" height="100%-99" visible="true">
             <prototype name="Window" />
             <prototype name="Window" />
-            <widget name="Materia" x="0" y ="0" width="100%" height="100%" visible="true">
+            <widget name="Empty" x="0" y ="0" width="100%" height="100%" visible="false">
+            </widget>
+            <widget name="Commands" x="0" y ="0" width="100%" height="100%" visible="false">
+                <widget name="Box" x="20" y ="20" width="100%-40" height="60" visible="true">
+                    <prototype name="Window" />
+                    <!-- TODO -->
+                </widget>
+            </widget>
+            <widget name="EnemySkill" x="0" y ="0" width="100%" height="100%" visible="false">
+                <sprite name="Icon" image="images/icons/materia_3.png" x="5" y="5" width="18" height="18" visible="true"/>
+                <text_area name="Name" x="29" y="9" font="FFVIIFont" visible="true" />
+                <widget name="Stars" x="5%" y="25" width="90%" height="32" visible="true">
+                    <sprite name="Star1" image="images/icons/materia_star_3.png" x="0" y="0" width="15" height="15" visible="true"/>
+                    <sprite name="Star2" image="images/icons/materia_star_3.png" x="16" y="0" width="15" height="15" visible="true"/>
+                    <sprite name="Star3" image="images/icons/materia_star_3.png" x="32" y="0" width="15" height="15" visible="true"/>
+                    <sprite name="Star4" image="images/icons/materia_star_3.png" x="48" y="0" width="15" height="15" visible="true"/>
+                    <sprite name="Star5" image="images/icons/materia_star_3.png" x="64" y="0" width="15" height="15" visible="true"/>
+                    <sprite name="Star6" image="images/icons/materia_star_3.png" x="80" y="0" width="15" height="15" visible="true"/>
+                    <sprite name="Star7" image="images/icons/materia_star_3.png" x="96" y="0" width="15" height="15" visible="true"/>
+                    <sprite name="Star8" image="images/icons/materia_star_3.png" x="112" y="0" width="15" height="15" visible="true"/>
+                    <sprite name="Star9" image="images/icons/materia_star_3.png" x="128" y="0" width="15" height="15" visible="true"/>
+                    <sprite name="Star10" image="images/icons/materia_star_3.png" x="144" y="0" width="15" height="15" visible="true"/>
+                    <sprite name="Star11" image="images/icons/materia_star_3.png" x="160" y="0" width="15" height="15" visible="true"/>
+                    <sprite name="Star12" image="images/icons/materia_star_3.png" x="176" y="0" width="15" height="15" visible="true"/>
+                    <sprite name="Star13" image="images/icons/materia_star_3.png" x="0" y="16" width="15" height="15" visible="true"/>
+                    <sprite name="Star14" image="images/icons/materia_star_3.png" x="16" y="16" width="15" height="15" visible="true"/>
+                    <sprite name="Star15" image="images/icons/materia_star_3.png" x="32" y="16" width="15" height="15" visible="true"/>
+                    <sprite name="Star16" image="images/icons/materia_star_3.png" x="48" y="16" width="15" height="15" visible="true"/>
+                    <sprite name="Star17" image="images/icons/materia_star_3.png" x="64" y="16" width="15" height="15" visible="true"/>
+                    <sprite name="Star18" image="images/icons/materia_star_3.png" x="80" y="16" width="15" height="15" visible="true"/>
+                    <sprite name="Star19" image="images/icons/materia_star_3.png" x="96" y="16" width="15" height="15" visible="true"/>
+                    <sprite name="Star20" image="images/icons/materia_star_3.png" x="112" y="16" width="15" height="15" visible="true"/>
+                    <sprite name="Star21" image="images/icons/materia_star_3.png" x="128" y="16" width="15" height="15" visible="true"/>
+                    <sprite name="Star22" image="images/icons/materia_star_3.png" x="144" y="16" width="15" height="15" visible="true"/>
+                    <sprite name="Star23" image="images/icons/materia_star_3.png" x="160" y="16" width="15" height="15" visible="true"/>
+                    <sprite name="Star24" image="images/icons/materia_star_3.png" x="176" y="16" width="15" height="15" visible="true"/>
+                </widget>
+                <widget name="Skills" x="5%" y="57" width="95%" height="120" visible="true" >
+                    <text_area name="Skill1" text_name="MateriaMenuAbilities" x="0%" y="0" font="FFVIIFont" visible="true" />
+                    <text_area name="Skill2" text_name="MateriaMenuAbilities" x="50%" y="0" font="FFVIIFont" visible="true" />
+                    <text_area name="Skill3" text_name="MateriaMenuAbilities" x="0%" y="10" font="FFVIIFont" visible="true" />
+                    <text_area name="Skill4" text_name="MateriaMenuAbilities" x="50%" y="10" font="FFVIIFont" visible="true" />
+                    <text_area name="Skill5" text_name="MateriaMenuAbilities" x="0%" y="20" font="FFVIIFont" visible="true" />
+                    <text_area name="Skill6" text_name="MateriaMenuAbilities" x="50%" y="20" font="FFVIIFont" visible="true" />
+                    <text_area name="Skill7" text_name="MateriaMenuAbilities" x="0%" y="30" font="FFVIIFont" visible="true" />
+                    <text_area name="Skill8" text_name="MateriaMenuAbilities" x="50%" y="30" font="FFVIIFont" visible="true" />
+                    <text_area name="Skill9" text_name="MateriaMenuAbilities" x="0%" y="40" font="FFVIIFont" visible="true" />
+                    <text_area name="Skill10" text_name="MateriaMenuAbilities" x="50%" y="40" font="FFVIIFont" visible="true" />
+                    <text_area name="Skill11" text_name="MateriaMenuAbilities" x="0%" y="50" font="FFVIIFont" visible="true" />
+                    <text_area name="Skill12" text_name="MateriaMenuAbilities" x="50%" y="50" font="FFVIIFont" visible="true" />
+                    <text_area name="Skill13" text_name="MateriaMenuAbilities" x="0%" y="60" font="FFVIIFont" visible="true" />
+                    <text_area name="Skill14" text_name="MateriaMenuAbilities" x="50%" y="60" font="FFVIIFont" visible="true" />
+                    <text_area name="Skill15" text_name="MateriaMenuAbilities" x="0%" y="70" font="FFVIIFont" visible="true" />
+                    <text_area name="Skill16" text_name="MateriaMenuAbilities" x="50%" y="70" font="FFVIIFont" visible="true" />
+                    <text_area name="Skill17" text_name="MateriaMenuAbilities" x="0%" y="80" font="FFVIIFont" visible="true" />
+                    <text_area name="Skill18" text_name="MateriaMenuAbilities" x="50%" y="80" font="FFVIIFont" visible="true" />
+                    <text_area name="Skill19" text_name="MateriaMenuAbilities" x="0%" y="90" font="FFVIIFont" visible="true" />
+                    <text_area name="Skill20" text_name="MateriaMenuAbilities" x="50%" y="90" font="FFVIIFont" visible="true" />
+                    <text_area name="Skill21" text_name="MateriaMenuAbilities" x="0%" y="100" font="FFVIIFont" visible="true" />
+                    <text_area name="Skill22" text_name="MateriaMenuAbilities" x="50%" y="100" font="FFVIIFont" visible="true" />
+                    <text_area name="Skill23" text_name="MateriaMenuAbilities" x="0%" y="110" font="FFVIIFont" visible="true" />
+                    <text_area name="Skill24" text_name="MateriaMenuAbilities" x="50%" y="110" font="FFVIIFont" visible="true" />
+                </widget>
+            </widget>
+            <widget name="Materia" x="0" y ="0" width="100%" height="100%" visible="false">
                 <sprite name="Icon" image="images/icons/materia_3.png" x="5" y="5" width="18" height="18" visible="true"/>
                 <sprite name="Icon" image="images/icons/materia_3.png" x="5" y="5" width="18" height="18" visible="true"/>
                 <text_area name="Name" x="29" y="9" font="FFVIIFont" visible="true" />
                 <text_area name="Name" x="29" y="9" font="FFVIIFont" visible="true" />
                 <widget name="Stars" x="100%-90" y="8" width="86" height="15" visible="true">
                 <widget name="Stars" x="100%-90" y="8" width="86" height="15" visible="true">
@@ -111,36 +175,48 @@
                     <text_area name="Ability5" text_name="MateriaMenuAbilities" x="0" y="60" width="100%" height="15" font="FFVIIFont" visible="true" />
                     <text_area name="Ability5" text_name="MateriaMenuAbilities" x="0" y="60" width="100%" height="15" font="FFVIIFont" visible="true" />
                 </widget>
                 </widget>
                 <widget name="Effects" x="50%+20" y="80" width="50%-20" height="100%-80" visible="true">
                 <widget name="Effects" x="50%+20" y="80" width="50%-20" height="100%-80" visible="true">
-                    <text_area name="Stat1" text_name="MateriaMenuEffects" x="0" y="0" width="60%" height="15" font="FFVIIFont" visible="true" />
-                    <text_area name="Stat2" text_name="MateriaMenuEffects" x="0" y="15" width="60%" height="15" font="FFVIIFont" visible="true" />
-                    <text_area name="Stat3" text_name="MateriaMenuEffects" x="0" y="30" width="60%" height="15" font="FFVIIFont" visible="true" />
-                    <text_area name="Stat4" text_name="MateriaMenuEffects" x="0" y="45" width="60%" height="15" font="FFVIIFont" visible="true" />
-                    <text_area name="Stat5" text_name="MateriaMenuEffects" x="0" y="60" width="60%" height="15" font="FFVIIFont" visible="true" />
-                    <text_area name="Stat6" text_name="MateriaMenuEffects" x="0" y="75" width="60%" height="15" font="FFVIIFont" visible="true" />
-                    <text_area name="Stat7" text_name="MateriaMenuEffects" x="0" y="90" width="60%" height="15" font="FFVIIFont" visible="true" />
-                    <text_area name="Val1" text_name="MateriaMenuAP" x="60%" y="2" width="30%" height="15" font="FFVIIMenuDigits" visible="true" />
-                    <text_area name="Val2" text_name="MateriaMenuAP" x="60%" y="17" width="30%" height="15" font="FFVIIMenuDigits" visible="true" />
-                    <text_area name="Val3" text_name="MateriaMenuAP" x="60%" y="32" width="30%" height="15" font="FFVIIMenuDigits" visible="true" />
-                    <text_area name="Val4" text_name="MateriaMenuAP" x="60%" y="47" width="30%" height="15" font="FFVIIMenuDigits" visible="true" />
-                    <text_area name="Val5" text_name="MateriaMenuAP" x="60%" y="62" width="30%" height="15" font="FFVIIMenuDigits" visible="true" />
-                    <text_area name="Val6" text_name="MateriaMenuAP" x="60%" y="77" width="30%" height="15" font="FFVIIMenuDigits" visible="true" />
-                    <text_area name="Val7" text_name="MateriaMenuAP" x="60%" y="92" width="30%" height="15" font="FFVIIMenuDigits" visible="true" />
+                    <text_area name="Stat1" x="0" y="0" width="60%" height="15" font="FFVIIFont" visible="true" />
+                    <text_area name="Stat2" x="0" y="15" width="60%" height="15" font="FFVIIFont" visible="true" />
+                    <text_area name="Stat3" x="0" y="30" width="60%" height="15" font="FFVIIFont" visible="true" />
+                    <text_area name="Stat4" x="0" y="45" width="60%" height="15" font="FFVIIFont" visible="true" />
+                    <text_area name="Stat5" x="0" y="60" width="60%" height="15" font="FFVIIFont" visible="true" />
+                    <text_area name="Stat6" x="0" y="75" width="60%" height="15" font="FFVIIFont" visible="true" />
+                    <text_area name="Stat7" x="0" y="90" width="60%" height="15" font="FFVIIFont" visible="true" />
+                    <text_area name="Val1" x="60%" y="2" width="30%" height="15" font="FFVIIMenuDigits" visible="true" />
+                    <text_area name="Val2" x="60%" y="17" width="30%" height="15" font="FFVIIMenuDigits" visible="true" />
+                    <text_area name="Val3" x="60%" y="32" width="30%" height="15" font="FFVIIMenuDigits" visible="true" />
+                    <text_area name="Val4" x="60%" y="47" width="30%" height="15" font="FFVIIMenuDigits" visible="true" />
+                    <text_area name="Val5" x="60%" y="62" width="30%" height="15" font="FFVIIMenuDigits" visible="true" />
+                    <text_area name="Val6" x="60%" y="77" width="30%" height="15" font="FFVIIMenuDigits" visible="true" />
+                    <text_area name="Val7" x="60%" y="92" width="30%" height="15" font="FFVIIMenuDigits" visible="true" />
                 </widget>
                 </widget>
             </widget>
             </widget>
         </widget>
         </widget>
-        <widget name="List" x="230" y="94" width="100%-230" height="100%-99" visible="true">
+        <widget name="List" x="230" y="99" width="100%-230" height="100%-99" visible="true">
             <prototype name="Window" />
             <prototype name="Window" />
-            <text_area name="Item1" x="15" y="4%" font="FFVIIFont" visible="true" />
-            <text_area name="Item2" x="15" y="13%" font="FFVIIFont" visible="true" />
-            <text_area name="Item3" x="15" y="22%" font="FFVIIFont" visible="true" />
-            <text_area name="Item4" x="15" y="31%" font="FFVIIFont" visible="true" />
-            <text_area name="Item5" x="15" y="40%" font="FFVIIFont" visible="true" />
-            <text_area name="Item6" x="15" y="49%" font="FFVIIFont" visible="true" />
-            <text_area name="Item7" x="15" y="58%" font="FFVIIFont" visible="true" />
-            <text_area name="Item8" x="15" y="67%" font="FFVIIFont" visible="true" />
-            <text_area name="Item9" x="15" y="76%" font="FFVIIFont" visible="true" />
-            <text_area name="Item10" x="15" y="85%" font="FFVIIFont" visible="true" />
-            <sprite name="Cursor" image="images/icons/cursor.png" x="-10" y="8" width="24" height="17" visible="true">
+            <sprite name="ScrollBg" x="100%-12" y="2" width="10" height="100%-6" colour="0.2 0.2 0.2" alpha="0.8" visible="true"/>
+            <sprite name="Scroll" x="100%-11" y="3" width="8" height="18" colour="0.7 0.7 0.7" visible="true"/>
+            <sprite name="Icon1" image="images/icons/materia_3.png" x="5" y="4%-1" width="12" height="12" visible="true"/>
+            <sprite name="Icon2" image="images/icons/materia_3.png" x="5" y="13%-1" width="12" height="12" visible="true"/>
+            <sprite name="Icon3" image="images/icons/materia_3.png" x="5" y="22%-1" width="12" height="12" visible="true"/>
+            <sprite name="Icon4" image="images/icons/materia_3.png" x="5" y="31%-1" width="12" height="12" visible="true"/>
+            <sprite name="Icon5" image="images/icons/materia_3.png" x="5" y="40%-1" width="12" height="12" visible="true"/>
+            <sprite name="Icon6" image="images/icons/materia_3.png" x="5" y="49%-1" width="12" height="12" visible="true"/>
+            <sprite name="Icon7" image="images/icons/materia_3.png" x="5" y="58%-1" width="12" height="12" visible="true"/>
+            <sprite name="Icon8" image="images/icons/materia_3.png" x="5" y="67%-1" width="12" height="12" visible="true"/>
+            <sprite name="Icon9" image="images/icons/materia_3.png" x="5" y="76%-1" width="12" height="12" visible="true"/>
+            <sprite name="Icon10" image="images/icons/materia_3.png" x="5" y="85%-1" width="12" height="12" visible="true"/>
+            <text_area name="Name1" x="20" y="4%" font="FFVIIFont" visible="true" />
+            <text_area name="Name2" x="20" y="13%" font="FFVIIFont" visible="true" />
+            <text_area name="Name3" x="20" y="22%" font="FFVIIFont" visible="true" />
+            <text_area name="Name4" x="20" y="31%" font="FFVIIFont" visible="true" />
+            <text_area name="Name5" x="20" y="40%" font="FFVIIFont" visible="true" />
+            <text_area name="Name6" x="20" y="49%" font="FFVIIFont" visible="true" />
+            <text_area name="Name7" x="20" y="58%" font="FFVIIFont" visible="true" />
+            <text_area name="Name8" x="20" y="67%" font="FFVIIFont" visible="true" />
+            <text_area name="Name9" x="20" y="76%" font="FFVIIFont" visible="true" />
+            <text_area name="Name10" x="20" y="85%" font="FFVIIFont" visible="true" />
+            <sprite name="Cursor" image="images/icons/cursor.png" x="-15" y="8" width="24" height="17" visible="true">
                 <animation name="Position1" length="0" y="0:4%+3" />
                 <animation name="Position1" length="0" y="0:4%+3" />
                 <animation name="Position2" length="0" y="0:13%+3" />
                 <animation name="Position2" length="0" y="0:13%+3" />
                 <animation name="Position3" length="0" y="0:22%+3" />
                 <animation name="Position3" length="0" y="0:22%+3" />

+ 1 - 1
data/data/screens/name_menu/name_menu.xml

@@ -7,7 +7,7 @@
         </widget>
         </widget>
         <widget name="Character" width="100%" x="0" y="24" height="70" visible="true">
         <widget name="Character" width="100%" x="0" y="24" height="70" visible="true">
             <prototype name="Window"/>
             <prototype name="Window"/>
-            <sprite name="Portrait" image="images/other/choco.png" x="10" y="12" width="48" height="48" visible="true" />
+            <sprite name="Portrait" image="images/other/choco.png" x="10" y="12" width="42" height="48" visible="true" />
             <widget name="Name" width="72" height="16" x="80" y="50%-8" visible="true">
             <widget name="Name" width="72" height="16" x="80" y="50%-8" visible="true">
                 <text_area name="Char1" x="1" y="0" width="8" height="16" visible="true" font="FFVIIFont" />
                 <text_area name="Char1" x="1" y="0" width="8" height="16" visible="true" font="FFVIIFont" />
                 <text_area name="Char2" x="11" y="0" width="8" height="16" visible="true" font="FFVIIFont" />
                 <text_area name="Char2" x="11" y="0" width="8" height="16" visible="true" font="FFVIIFont" />

+ 1 - 0
data/data/scripts/README.txt

@@ -0,0 +1 @@
+This folders contains the scripts that make the game work.

+ 3 - 0
data/data/scripts/data.lua

@@ -127,6 +127,9 @@ Materia.TYPE = {
     SUMMON = 5
     SUMMON = 5
 }
 }
 
 
+--- ID of the enemy skill materia. This is handled differently.
+Materia.ENEMY_SKILL_ID = 44
+
 --- Abilities confered by independent materia.
 --- Abilities confered by independent materia.
 Materia.ABILITY = {
 Materia.ABILITY = {
     STR_PLUS = 0,
     STR_PLUS = 0,

+ 17 - 5
data/data/scripts/debug_data.lua

@@ -29,7 +29,7 @@ Inventory.add_item(240, 4)
 Inventory.add_item(289, 4)
 Inventory.add_item(289, 4)
 Inventory.add_item(290, 1)
 Inventory.add_item(290, 1)
 Inventory.add_item(294, 2)
 Inventory.add_item(294, 2)
---[[Inventory.add_key_item(0)
+Inventory.add_key_item(0)
 Inventory.add_key_item(1)
 Inventory.add_key_item(1)
 Inventory.add_key_item(5)
 Inventory.add_key_item(5)
 Inventory.add_key_item(18)
 Inventory.add_key_item(18)
@@ -38,10 +38,10 @@ Inventory.add_key_item(20)
 Inventory.add_key_item(21)
 Inventory.add_key_item(21)
 Inventory.add_key_item(22)
 Inventory.add_key_item(22)
 Inventory.add_key_item(33)
 Inventory.add_key_item(33)
-Inventory.add_key_item(38)]]
-for i = 0, 50 do
+Inventory.add_key_item(38)
+--[[for i = 0, 50 do
     Inventory.add_key_item(i)
     Inventory.add_key_item(i)
-end
+end]]
 Inventory.money = 1234567
 Inventory.money = 1234567
 Inventory.sort(Inventory.ORDER.NAME)
 Inventory.sort(Inventory.ORDER.NAME)
 Characters[0].stats.hp.current = 12345
 Characters[0].stats.hp.current = 12345
@@ -49,10 +49,22 @@ Characters[0].stats.hp.base = 54321
 Characters[0].armor.id = 268
 Characters[0].armor.id = 268
 Characters[0].armor.materia = {
 Characters[0].armor.materia = {
     [0] = {id = 62, ap = 5340},
     [0] = {id = 62, ap = 5340},
-    [2] = {id = 72, ap = 5},
+    [2] = {id = 44, ap = 0, skills = {}}, -- E. Skill
     [3] = {id = 78, ap = 520},
     [3] = {id = 78, ap = 520},
     [4] = {id = 36, ap = 0},
     [4] = {id = 36, ap = 0},
     [5] = {id = 36, ap = 50000},
     [5] = {id = 36, ap = 50000},
     [6] = {id = 0, ap = 0},
     [6] = {id = 0, ap = 0},
     [7] = {id = 26, ap = 0},
     [7] = {id = 26, ap = 0},
 }
 }
+for i = 1, 24 do -- E. Skill list
+    if i == 1 or i == 6 or i == 9 or i == 13 or i == 14 or i == 15 or i == 23 or i == 24 then
+        Characters[0].armor.materia[2].skills[i] = true
+    else
+        Characters[0].armor.materia[2].skills[i] = false
+    end
+end
+Materia.add(4)
+Materia.add(44)
+Materia.add(12)
+Materia.add(15, 2000)
+Materia.add(24)

+ 3 - 0
data/data/scripts/menu/begin_menu.lua

@@ -22,6 +22,7 @@ UiContainer.BeginMenu = {
 
 
             if button == "Enter" and event == "Press" then
             if button == "Enter" and event == "Press" then
                 if self.position == 1 then
                 if self.position == 1 then
+                    audio_manager:play_sound("Window")
                     load_field_map_request( "md1stin", "" )
                     load_field_map_request( "md1stin", "" )
                     console( "camera_free false" )
                     console( "camera_free false" )
                     --console( "debug_walkmesh true" )
                     --console( "debug_walkmesh true" )
@@ -87,12 +88,14 @@ UiContainer.BeginMenu = {
                     FFVII.MenuSettings.pause_available = true
                     FFVII.MenuSettings.pause_available = true
                 end
                 end
             elseif button == "Down" then
             elseif button == "Down" then
+                audio_manager:play_sound("Cursor")
                 self.position = self.position + 1
                 self.position = self.position + 1
                 if self.position > self.position_total then
                 if self.position > self.position_total then
                     self.position = 1;
                     self.position = 1;
                 end
                 end
                 cursor:set_default_animation( "Position" .. self.position )
                 cursor:set_default_animation( "Position" .. self.position )
             elseif button == "Up" then
             elseif button == "Up" then
+                audio_manager:play_sound("Cursor")
                 self.position = self.position - 1
                 self.position = self.position - 1
                 if self.position <= 0 then
                 if self.position <= 0 then
                     self.position = self.position_total;
                     self.position = self.position_total;

+ 12 - 2
data/data/scripts/menu/equip_menu.lua

@@ -64,9 +64,11 @@ UiContainer.EquipMenu = {
                 -- TODO: Handle L1/R1 (change character)
                 -- TODO: Handle L1/R1 (change character)
                 -- TODO: Handle square: Materia menu
                 -- TODO: Handle square: Materia menu
                 if button == "Escape" and event == "Press" then
                 if button == "Escape" and event == "Press" then
+                    audio_manager:play_sound("Back")
                     UiContainer.current_menu = "main"
                     UiContainer.current_menu = "main"
                     script:request_end_sync(Script.UI, "EquipMenu", "hide", 0)
                     script:request_end_sync(Script.UI, "EquipMenu", "hide", 0)
                 elseif button == "Down" then
                 elseif button == "Down" then
+                    audio_manager:play_sound("Cursor")
                     self.equip_position = self.equip_position + 1
                     self.equip_position = self.equip_position + 1
                     if self.equip_position > self.equip_position_total then
                     if self.equip_position > self.equip_position_total then
                         self.equip_position = 1
                         self.equip_position = 1
@@ -74,6 +76,7 @@ UiContainer.EquipMenu = {
                     ui_manager:get_widget("EquipMenu.Container.Character.Cursor"):set_default_animation("Position" .. self.equip_position)
                     ui_manager:get_widget("EquipMenu.Container.Character.Cursor"):set_default_animation("Position" .. self.equip_position)
                     self.populate_details(self, false)
                     self.populate_details(self, false)
                 elseif button == "Up" then
                 elseif button == "Up" then
+                    audio_manager:play_sound("Cursor")
                     self.equip_position = self.equip_position - 1
                     self.equip_position = self.equip_position - 1
                     if self.equip_position < 1 then
                     if self.equip_position < 1 then
                         self.equip_position = self.equip_position_total
                         self.equip_position = self.equip_position_total
@@ -88,6 +91,7 @@ UiContainer.EquipMenu = {
                     elseif self.equip_position == 3 then
                     elseif self.equip_position == 3 then
                         self.selecting_slot = Inventory.ITEM_TYPE.ACCESSORY
                         self.selecting_slot = Inventory.ITEM_TYPE.ACCESSORY
                     end
                     end
+                    audio_manager:play_sound("Cursor")
                     self.submenu_select(self)
                     self.submenu_select(self)
                 else
                 else
                     return 0
                     return 0
@@ -102,8 +106,10 @@ UiContainer.EquipMenu = {
                     list = self.avail_accessories
                     list = self.avail_accessories
                 end
                 end
                 if button == "Escape" and event == "Press" then
                 if button == "Escape" and event == "Press" then
+                    audio_manager:play_sound("Back")
                     self.submenu_none(self)
                     self.submenu_none(self)
                 elseif button == "Down" then
                 elseif button == "Down" then
+                    audio_manager:play_sound("Cursor")
                     -- Move one position down only if there is a next item
                     -- Move one position down only if there is a next item
                     if #(list) <= self.list_item_selected + 1 then
                     if #(list) <= self.list_item_selected + 1 then
                         return 0
                         return 0
@@ -122,6 +128,7 @@ UiContainer.EquipMenu = {
                     self.populate_details(self, true)
                     self.populate_details(self, true)
                     self.calculate_stat_diffs(self)
                     self.calculate_stat_diffs(self)
                 elseif button == "Up" then
                 elseif button == "Up" then
+                    audio_manager:play_sound("Cursor")
                     -- Move one position up only if not in the first.
                     -- Move one position up only if not in the first.
                     if self.list_item_selected <= 1 then
                     if self.list_item_selected <= 1 then
                         return 0
                         return 0
@@ -150,9 +157,11 @@ UiContainer.EquipMenu = {
                         item_id = self.avail_accessories[self.list_item_selected + 1]
                         item_id = self.avail_accessories[self.list_item_selected + 1]
                     end
                     end
                     if Characters.equip(self.char_id, item_id) == false then
                     if Characters.equip(self.char_id, item_id) == false then
-                        print("BEEP (error)")
+                        audio_manager:play_sound("Error")
                         return 0
                         return 0
                     end
                     end
+                    -- TODO: Replace for "Equip" sound
+                    audio_manager:play_sound("Cursor")
                     self.submenu_none(self)
                     self.submenu_none(self)
                 end
                 end
             end
             end
@@ -179,6 +188,7 @@ UiContainer.EquipMenu = {
         UiContainer.populate_character_data("EquipMenu.Container.Character", Characters[self.char_id])
         UiContainer.populate_character_data("EquipMenu.Container.Character", Characters[self.char_id])
         ui_manager:get_widget("EquipMenu.Container.Character.Portrait"):set_image("images/characters/" .. tostring(self.char_id) .. ".png")
         ui_manager:get_widget("EquipMenu.Container.Character.Portrait"):set_image("images/characters/" .. tostring(self.char_id) .. ".png")
         -- TODO: Do something for chars 9 and 10
         -- TODO: Do something for chars 9 and 10
+        ui_manager:get_widget("EquipMenu.Container.Character.WpnIcon"):set_image("images/icons/item_weapon_" .. tostring(self.char_id) .. ".png")
         ui_manager:get_widget("EquipMenu.Container.Character.WpnLbl"):set_text(Game.Items[Characters[self.char_id].weapon.id].name)
         ui_manager:get_widget("EquipMenu.Container.Character.WpnLbl"):set_text(Game.Items[Characters[self.char_id].weapon.id].name)
         ui_manager:get_widget("EquipMenu.Container.Character.ArmLbl"):set_text(Game.Items[Characters[self.char_id].armor.id].name)
         ui_manager:get_widget("EquipMenu.Container.Character.ArmLbl"):set_text(Game.Items[Characters[self.char_id].armor.id].name)
         if Characters[self.char_id].accessory == nil then
         if Characters[self.char_id].accessory == nil then
@@ -218,7 +228,7 @@ UiContainer.EquipMenu = {
         self.list_position = 1
         self.list_position = 1
         self.list_item_selected = 1
         self.list_item_selected = 1
         if self.populate_item_list(self) == false then
         if self.populate_item_list(self) == false then
-            print("BEEP")
+            audio_manager:play_sound("Error")
             return
             return
         end
         end
         UiContainer.current_submenu = "item_select"
         UiContainer.current_submenu = "item_select"

+ 42 - 2
data/data/scripts/menu/item_menu.lua

@@ -87,9 +87,11 @@ UiContainer.ItemMenu = {
         if UiContainer.current_menu == "item" then
         if UiContainer.current_menu == "item" then
             if UiContainer.current_submenu == "bar_selection" then
             if UiContainer.current_submenu == "bar_selection" then
                 if button == "Escape" and event == "Press" then
                 if button == "Escape" and event == "Press" then
+                    audio_manager:play_sound("Back")
                     UiContainer.current_menu = "main"
                     UiContainer.current_menu = "main"
                     script:request_end_sync(Script.UI, "ItemMenu", "hide", 0)
                     script:request_end_sync(Script.UI, "ItemMenu", "hide", 0)
                 elseif button == "Right" then
                 elseif button == "Right" then
+                    audio_manager:play_sound("Cursor")
                     self.bar_position = self.bar_position + 1
                     self.bar_position = self.bar_position + 1
                     if self.bar_position > self.bar_position_total then
                     if self.bar_position > self.bar_position_total then
                         self.bar_position = 1
                         self.bar_position = 1
@@ -102,6 +104,7 @@ UiContainer.ItemMenu = {
                         ui_manager:get_widget("ItemMenu.Container.KeyItems"):set_visible(false)
                         ui_manager:get_widget("ItemMenu.Container.KeyItems"):set_visible(false)
                     end
                     end
                 elseif button == "Left" then
                 elseif button == "Left" then
+                    audio_manager:play_sound("Cursor")
                     self.bar_position = self.bar_position - 1
                     self.bar_position = self.bar_position - 1
                     if self.bar_position < 1 then
                     if self.bar_position < 1 then
                         self.bar_position = self.bar_position_total
                         self.bar_position = self.bar_position_total
@@ -123,25 +126,30 @@ UiContainer.ItemMenu = {
                     else
                     else
                         return 0
                         return 0
                     end
                     end
+                    audio_manager:play_sound("Cursor")
                 else
                 else
                     return 0
                     return 0
                 end
                 end
             elseif UiContainer.current_submenu == "order_selection" then
             elseif UiContainer.current_submenu == "order_selection" then
                 if button == "Escape" and event == "Press" then
                 if button == "Escape" and event == "Press" then
+                    audio_manager:play_sound("Back")
                     self.submenu_bar(self)
                     self.submenu_bar(self)
                 elseif button == "Down" then
                 elseif button == "Down" then
+                    audio_manager:play_sound("Cursor")
                     self.order_cursor_position = self.order_cursor_position + 1
                     self.order_cursor_position = self.order_cursor_position + 1
                     if self.order_cursor_position > self.order_cursor_position_total then
                     if self.order_cursor_position > self.order_cursor_position_total then
                         self.order_cursor_position = 1
                         self.order_cursor_position = 1
                     end
                     end
                     ui_manager:get_widget("ItemMenu.Container.ItemOrder.Cursor"):set_default_animation("Position" .. self.order_cursor_position)
                     ui_manager:get_widget("ItemMenu.Container.ItemOrder.Cursor"):set_default_animation("Position" .. self.order_cursor_position)
                 elseif button == "Up" then
                 elseif button == "Up" then
+                    audio_manager:play_sound("Cursor")
                     self.order_cursor_position = self.order_cursor_position - 1
                     self.order_cursor_position = self.order_cursor_position - 1
                     if self.order_cursor_position <= 0 then
                     if self.order_cursor_position <= 0 then
                         self.order_cursor_position = self.order_cursor_position_total
                         self.order_cursor_position = self.order_cursor_position_total
                     end
                     end
                     ui_manager:get_widget("ItemMenu.Container.ItemOrder.Cursor"):set_default_animation("Position" .. self.order_cursor_position)
                     ui_manager:get_widget("ItemMenu.Container.ItemOrder.Cursor"):set_default_animation("Position" .. self.order_cursor_position)
                 elseif button == "Enter" then
                 elseif button == "Enter" then
+                    audio_manager:play_sound("Cursor")
                     if self.order_cursor_position == 1 then
                     if self.order_cursor_position == 1 then
                         Inventory.sort(Inventory.ORDER.CUSTOM)
                         Inventory.sort(Inventory.ORDER.CUSTOM)
                     elseif self.order_cursor_position == 2 then
                     elseif self.order_cursor_position == 2 then
@@ -168,8 +176,10 @@ UiContainer.ItemMenu = {
                 if button == "Down" then
                 if button == "Down" then
                     if self.item_cursor_item_selected >= self.inventory_size then
                     if self.item_cursor_item_selected >= self.inventory_size then
                         -- At the very end, do nothing
                         -- At the very end, do nothing
+                        audio_manager:play_sound("Error")
                         return 0
                         return 0
                     end
                     end
+                    audio_manager:play_sound("Cursor")
                     self.item_cursor_item_selected = self.item_cursor_item_selected + 1
                     self.item_cursor_item_selected = self.item_cursor_item_selected + 1
                     if self.item_cursor_position == self.item_cursor_position_total then
                     if self.item_cursor_position == self.item_cursor_position_total then
                         -- The cursor is at the end. Scroll page, but dont change cursor.
                         -- The cursor is at the end. Scroll page, but dont change cursor.
@@ -189,8 +199,10 @@ UiContainer.ItemMenu = {
                 elseif button == "Up" then
                 elseif button == "Up" then
                     if self.item_cursor_item_selected == 0 then
                     if self.item_cursor_item_selected == 0 then
                         -- At the very begining, do nothing
                         -- At the very begining, do nothing
+                        audio_manager:play_sound("Error")
                         return 0
                         return 0
                     end
                     end
+                    audio_manager:play_sound("Cursor")
                     self.item_cursor_item_selected = self.item_cursor_item_selected - 1
                     self.item_cursor_item_selected = self.item_cursor_item_selected - 1
                     if self.item_cursor_position == 1 then
                     if self.item_cursor_position == 1 then
                         -- The cursor is at the end. Scroll page, but dont change cursor.
                         -- The cursor is at the end. Scroll page, but dont change cursor.
@@ -209,6 +221,7 @@ UiContainer.ItemMenu = {
                     end
                     end
                 elseif button == "PGDN" then -- TODO: Also, the bind to R1
                 elseif button == "PGDN" then -- TODO: Also, the bind to R1
                     if self.first_item_in_window + self.item_cursor_position_total < self.inventory_size - self.item_cursor_position_total then
                     if self.first_item_in_window + self.item_cursor_position_total < self.inventory_size - self.item_cursor_position_total then
+                        audio_manager:play_sound("Cursor")
                         self.item_cursor_item_selected = self.item_cursor_item_selected + self.item_cursor_position_total
                         self.item_cursor_item_selected = self.item_cursor_item_selected + self.item_cursor_position_total
                         self.first_item_in_window = self.first_item_in_window + self.item_cursor_position_total
                         self.first_item_in_window = self.first_item_in_window + self.item_cursor_position_total
                         self.populate_items(self)
                         self.populate_items(self)
@@ -217,9 +230,12 @@ UiContainer.ItemMenu = {
                         else
                         else
                             desc_label:set_text("")
                             desc_label:set_text("")
                         end
                         end
+                    else
+                        audio_manager:play_sound("Error")
                     end
                     end
                 elseif button == "PGUP" then -- TODO: Also, the bind to L1
                 elseif button == "PGUP" then -- TODO: Also, the bind to L1
                     if self.first_item_in_window - self.item_cursor_position_total > 1 then
                     if self.first_item_in_window - self.item_cursor_position_total > 1 then
+                        audio_manager:play_sound("Cursor")
                         self.item_cursor_item_selected = self.item_cursor_item_selected - self.item_cursor_position_total
                         self.item_cursor_item_selected = self.item_cursor_item_selected - self.item_cursor_position_total
                         self.first_item_in_window = self.first_item_in_window - self.item_cursor_position_total
                         self.first_item_in_window = self.first_item_in_window - self.item_cursor_position_total
                         self.populate_items(self)
                         self.populate_items(self)
@@ -228,13 +244,17 @@ UiContainer.ItemMenu = {
                         else
                         else
                             desc_label:set_text("")
                             desc_label:set_text("")
                         end
                         end
+                    else
+                        audio_manager:play_sound("Error")
                     end
                     end
                 elseif button == "Escape" and event == "Press" then
                 elseif button == "Escape" and event == "Press" then
+                    audio_manager:play_sound("Back")
                     self.submenu_bar(self)
                     self.submenu_bar(self)
                     UiContainer.current_submenu = "bar_selection"
                     UiContainer.current_submenu = "bar_selection"
                     ui_manager:get_widget("ItemMenu.Container.ItemList.Cursor"):set_visible(false)
                     ui_manager:get_widget("ItemMenu.Container.ItemList.Cursor"):set_visible(false)
                 elseif button == "Enter" then
                 elseif button == "Enter" then
                     if Game.Items[Inventory[self.item_cursor_item_selected].item].menu == 1 then
                     if Game.Items[Inventory[self.item_cursor_item_selected].item].menu == 1 then
+                        audio_manager:play_sound("Cursor")
                         self.item_in_use = Inventory[self.item_cursor_item_selected].item
                         self.item_in_use = Inventory[self.item_cursor_item_selected].item
                         if (Game.Items[Inventory[self.item_cursor_item_selected - 1].item].target.default_multiple) == 1 then
                         if (Game.Items[Inventory[self.item_cursor_item_selected - 1].item].target.default_multiple) == 1 then
                             self.item_in_use_party = true
                             self.item_in_use_party = true
@@ -244,7 +264,7 @@ UiContainer.ItemMenu = {
                             self.submenu_chars(self, false)
                             self.submenu_chars(self, false)
                         end
                         end
                     else
                     else
-                        print("BEEP")
+                        audio_manager:play_sound("Error")
                     end
                     end
                 end
                 end
             elseif UiContainer.current_submenu == "char_selection" then
             elseif UiContainer.current_submenu == "char_selection" then
@@ -252,6 +272,7 @@ UiContainer.ItemMenu = {
                     if self.item_in_use_party == true then
                     if self.item_in_use_party == true then
                         return 0
                         return 0
                     else
                     else
+                        audio_manager:play_sound("Cursor")
                         self.char_position = self.char_position + 1
                         self.char_position = self.char_position + 1
                         if self.char_position > self.char_position_total then
                         if self.char_position > self.char_position_total then
                             self.char_position = 1
                             self.char_position = 1
@@ -262,6 +283,7 @@ UiContainer.ItemMenu = {
                     if self.item_in_use_party == true then
                     if self.item_in_use_party == true then
                         return 0
                         return 0
                     else
                     else
+                        audio_manager:play_sound("Cursor")
                         self.char_position = self.char_position - 1
                         self.char_position = self.char_position - 1
                         if self.char_position < 1 then
                         if self.char_position < 1 then
                             self.char_position = self.char_position_total
                             self.char_position = self.char_position_total
@@ -269,6 +291,7 @@ UiContainer.ItemMenu = {
                         ui_manager:get_widget("ItemMenu.Container.ItemMenuCharacters.Cursor"):set_default_animation("Position" .. self.char_position)
                         ui_manager:get_widget("ItemMenu.Container.ItemMenuCharacters.Cursor"):set_default_animation("Position" .. self.char_position)
                     end
                     end
                 elseif button == "Escape" then
                 elseif button == "Escape" then
+                    audio_manager:play_sound("Back")
                     self.submenu_items(self)
                     self.submenu_items(self)
                 elseif button == "Enter" then
                 elseif button == "Enter" then
                     local use_result = false
                     local use_result = false
@@ -278,15 +301,19 @@ UiContainer.ItemMenu = {
                         use_result = self.use_item(self, self.item_in_use, self.char_position)
                         use_result = self.use_item(self, self.item_in_use, self.char_position)
                     end
                     end
                     if use_result == false then
                     if use_result == false then
-                        print("USE BEEP")
+                        audio_manager:play_sound("Error")
+                    else
+                        -- TODO: Audio here? or in use_item?
                     end
                     end
                 end
                 end
             elseif UiContainer.current_submenu == "key_item_selection" then
             elseif UiContainer.current_submenu == "key_item_selection" then
                 if button == "Down" then
                 if button == "Down" then
                     if self.key_items_cursor_y_position == self.key_items_cursor_y_position_total and self.first_key_item_row_in_window == self.key_items_total_rows - self.key_items_cursor_y_position then
                     if self.key_items_cursor_y_position == self.key_items_cursor_y_position_total and self.first_key_item_row_in_window == self.key_items_total_rows - self.key_items_cursor_y_position then
                         -- Last key item selected, do nothing
                         -- Last key item selected, do nothing
+                        audio_manager:play_sound("Error")
                         return 0
                         return 0
                     end
                     end
+                    audio_manager:play_sound("Cursor")
                     if self.key_items_cursor_y_position == self.key_items_cursor_y_position_total then
                     if self.key_items_cursor_y_position == self.key_items_cursor_y_position_total then
                         -- Scroll down by 1, don't move cursor
                         -- Scroll down by 1, don't move cursor
                         self.first_key_item_row_in_window = self.first_key_item_row_in_window + 1
                         self.first_key_item_row_in_window = self.first_key_item_row_in_window + 1
@@ -306,8 +333,10 @@ UiContainer.ItemMenu = {
                 elseif button == "Up" then
                 elseif button == "Up" then
                     if self.key_items_cursor_y_position == 1 and self.first_key_item_row_in_window == 1 then
                     if self.key_items_cursor_y_position == 1 and self.first_key_item_row_in_window == 1 then
                         -- First item, do nothing.
                         -- First item, do nothing.
+                        audio_manager:play_sound("Error")
                         return 0
                         return 0
                     end
                     end
+                    audio_manager:play_sound("Cursor")
                     if self.key_items_cursor_y_position == 1 then
                     if self.key_items_cursor_y_position == 1 then
                         -- Scroll down by 1, don't move cursor
                         -- Scroll down by 1, don't move cursor
                         self.first_key_item_row_in_window = self.first_key_item_row_in_window - 1
                         self.first_key_item_row_in_window = self.first_key_item_row_in_window - 1
@@ -328,8 +357,10 @@ UiContainer.ItemMenu = {
                     local k_selected_id = 2 * (self.first_key_item_row_in_window + self.key_items_cursor_y_position - 2) + self.key_items_cursor_x_position - 1
                     local k_selected_id = 2 * (self.first_key_item_row_in_window + self.key_items_cursor_y_position - 2) + self.key_items_cursor_x_position - 1
                     if k_selected_id >= 50 then
                     if k_selected_id >= 50 then
                         -- Already in last item, do nothing
                         -- Already in last item, do nothing
+                        audio_manager:play_sound("Error")
                         return 0
                         return 0
                     end
                     end
+                    audio_manager:play_sound("Cursor")
                     k_selected_id = k_selected_id + 1
                     k_selected_id = k_selected_id + 1
                     if self.key_items_cursor_x_position == 1 then
                     if self.key_items_cursor_x_position == 1 then
                         -- If on the left, move to the right. No scrolling.
                         -- If on the left, move to the right. No scrolling.
@@ -357,8 +388,10 @@ UiContainer.ItemMenu = {
                     local k_selected_id = 2 * (self.first_key_item_row_in_window + self.key_items_cursor_y_position - 2) + self.key_items_cursor_x_position - 1
                     local k_selected_id = 2 * (self.first_key_item_row_in_window + self.key_items_cursor_y_position - 2) + self.key_items_cursor_x_position - 1
                     if k_selected_id <= 0 then
                     if k_selected_id <= 0 then
                         -- Already in first item, do nothing
                         -- Already in first item, do nothing
+                        audio_manager:play_sound("Error")
                         return 0
                         return 0
                     end
                     end
+                    audio_manager:play_sound("Cursor")
                     k_selected_id = k_selected_id - 1
                     k_selected_id = k_selected_id - 1
                     if self.key_items_cursor_x_position == 2 then
                     if self.key_items_cursor_x_position == 2 then
                         -- If on the right, move to the left. No scrolling.
                         -- If on the right, move to the left. No scrolling.
@@ -384,6 +417,7 @@ UiContainer.ItemMenu = {
                     end
                     end
                 elseif button == "PGDN" then -- TODO: Also, the bind to R1
                 elseif button == "PGDN" then -- TODO: Also, the bind to R1
                     if self.first_key_item_row_in_window + self.key_items_cursor_y_position_total < self.key_items_total_rows - self.key_items_cursor_y_position_total then
                     if self.first_key_item_row_in_window + self.key_items_cursor_y_position_total < self.key_items_total_rows - self.key_items_cursor_y_position_total then
+                        audio_manager:play_sound("Cursor")
                         self.first_key_item_row_in_window = self.first_key_item_row_in_window + self.key_items_cursor_y_position_total
                         self.first_key_item_row_in_window = self.first_key_item_row_in_window + self.key_items_cursor_y_position_total
                         self.populate_items(self)
                         self.populate_items(self)
                         -- Show description of the selected item, if any.
                         -- Show description of the selected item, if any.
@@ -393,9 +427,12 @@ UiContainer.ItemMenu = {
                         else
                         else
                             ui_manager:get_widget("ItemMenu.Container.ItemMenuSecondBar.ItemText"):set_text("")
                             ui_manager:get_widget("ItemMenu.Container.ItemMenuSecondBar.ItemText"):set_text("")
                         end
                         end
+                    else
+                        audio_manager:play_sound("Error")
                     end
                     end
                 elseif button == "PGUP" then -- TODO: Also, the bind to L1
                 elseif button == "PGUP" then -- TODO: Also, the bind to L1
                     if self.first_key_item_row_in_window + self.key_items_cursor_y_position_total > 1 then
                     if self.first_key_item_row_in_window + self.key_items_cursor_y_position_total > 1 then
+                        audio_manager:play_sound("Cursor")
                         self.first_key_item_row_in_window = self.first_key_item_row_in_window - self.key_items_cursor_y_position_total
                         self.first_key_item_row_in_window = self.first_key_item_row_in_window - self.key_items_cursor_y_position_total
                         self.populate_items(self)
                         self.populate_items(self)
                         -- Show description of the selected item, if any.
                         -- Show description of the selected item, if any.
@@ -405,8 +442,11 @@ UiContainer.ItemMenu = {
                         else
                         else
                             ui_manager:get_widget("ItemMenu.Container.ItemMenuSecondBar.ItemText"):set_text("")
                             ui_manager:get_widget("ItemMenu.Container.ItemMenuSecondBar.ItemText"):set_text("")
                         end
                         end
+                    else
+                        audio_manager:play_sound("Cursor")
                     end
                     end
                 elseif button == "Escape" then
                 elseif button == "Escape" then
+                    audio_manager:play_sound("Back")
                     self.submenu_bar(self)
                     self.submenu_bar(self)
                 end
                 end
 
 

+ 14 - 0
data/data/scripts/menu/main_menu.lua

@@ -41,28 +41,36 @@ UiContainer.MainMenu = {
             local location = ui_manager:get_widget("MainMenu.Container.Location")
             local location = ui_manager:get_widget("MainMenu.Container.Location")
             if UiContainer.current_submenu == "" then
             if UiContainer.current_submenu == "" then
                 if button == "Escape" and event == "Press" then
                 if button == "Escape" and event == "Press" then
+                    audio_manager:play_sound("Back")
                     script:request_end_sync(Script.UI, "MainMenu", "hide", 0)
                     script:request_end_sync(Script.UI, "MainMenu", "hide", 0)
                 elseif button == "Enter" and event == "Press" then
                 elseif button == "Enter" and event == "Press" then
                     if self.position == 1 then -- Item menu
                     if self.position == 1 then -- Item menu
+                        audio_manager:play_sound("Cursor")
                         script:request_end_sync(Script.UI, "ItemMenu", "show", 0)
                         script:request_end_sync(Script.UI, "ItemMenu", "show", 0)
                     elseif self.position == 8 then -- Config menu
                     elseif self.position == 8 then -- Config menu
+                        audio_manager:play_sound("Error")
                         print("TODO: Open config menu")
                         print("TODO: Open config menu")
                     elseif self.position == 9 then -- PHS menu
                     elseif self.position == 9 then -- PHS menu
+                        audio_manager:play_sound("Error")
                         print("TODO: Open PHS menu")
                         print("TODO: Open PHS menu")
                     elseif self.position == 10 then -- Save menu
                     elseif self.position == 10 then -- Save menu
+                        audio_manager:play_sound("Error")
                         print("TODO: Open save menu")
                         print("TODO: Open save menu")
                     else -- Any other menu that needs a character
                     else -- Any other menu that needs a character
+                        audio_manager:play_sound("Cursor")
                         self.select_character_for_menu = self.position
                         self.select_character_for_menu = self.position
                         UiContainer.current_submenu = "main_character"
                         UiContainer.current_submenu = "main_character"
                         ui_manager:get_widget("MainMenu.Container.Characters.Cursor"):set_visible(true)
                         ui_manager:get_widget("MainMenu.Container.Characters.Cursor"):set_visible(true)
                     end
                     end
                 elseif button == "Down" then
                 elseif button == "Down" then
+                    audio_manager:play_sound("Cursor")
                     self.position = self.position + 1
                     self.position = self.position + 1
                     if self.position > self.position_total then
                     if self.position > self.position_total then
                         self.position = 1;
                         self.position = 1;
                     end
                     end
                     menu_cursor:set_default_animation("Position" .. self.position)
                     menu_cursor:set_default_animation("Position" .. self.position)
                 elseif button == "Up" then
                 elseif button == "Up" then
+                    audio_manager:play_sound("Cursor")
                     self.position = self.position - 1
                     self.position = self.position - 1
                     if self.position <= 0 then
                     if self.position <= 0 then
                         self.position = self.position_total;
                         self.position = self.position_total;
@@ -71,9 +79,11 @@ UiContainer.MainMenu = {
                 end
                 end
             elseif UiContainer.current_submenu == "main_character" then
             elseif UiContainer.current_submenu == "main_character" then
                 if button == "Escape" and event == "Press" then
                 if button == "Escape" and event == "Press" then
+                    audio_manager:play_sound("Back")
                     UiContainer.current_submenu = ""
                     UiContainer.current_submenu = ""
                     ui_manager:get_widget("MainMenu.Container.Characters.Cursor"):set_visible(false)
                     ui_manager:get_widget("MainMenu.Container.Characters.Cursor"):set_visible(false)
                 elseif button == "Down" then
                 elseif button == "Down" then
+                    audio_manager:play_sound("Cursor")
                     -- TODO: Skip empty character slots
                     -- TODO: Skip empty character slots
                     self.character_position = self.character_position + 1
                     self.character_position = self.character_position + 1
                     if self.character_position > self.character_position_total then
                     if self.character_position > self.character_position_total then
@@ -81,6 +91,7 @@ UiContainer.MainMenu = {
                     end
                     end
                     ui_manager:get_widget("MainMenu.Container.Characters.Cursor"):set_default_animation("Position" .. self.character_position)
                     ui_manager:get_widget("MainMenu.Container.Characters.Cursor"):set_default_animation("Position" .. self.character_position)
                 elseif button == "Up" then
                 elseif button == "Up" then
+                    audio_manager:play_sound("Cursor")
                     -- TODO: Skip empty character slots
                     -- TODO: Skip empty character slots
                     self.character_position = self.character_position - 1
                     self.character_position = self.character_position - 1
                     if self.character_position < 1 then
                     if self.character_position < 1 then
@@ -88,6 +99,8 @@ UiContainer.MainMenu = {
                     end
                     end
                     ui_manager:get_widget("MainMenu.Container.Characters.Cursor"):set_default_animation("Position" .. self.character_position)
                     ui_manager:get_widget("MainMenu.Container.Characters.Cursor"):set_default_animation("Position" .. self.character_position)
                 elseif button == "Enter" then
                 elseif button == "Enter" then
+                    -- TODO: Check for empty character.
+                    audio_manager:play_sound("Cursor")
                     if self.select_character_for_menu == 2 then -- Magic menu
                     if self.select_character_for_menu == 2 then -- Magic menu
                         print("Open magic menu for char in slot " .. self.character_position)
                         print("Open magic menu for char in slot " .. self.character_position)
                     elseif self.select_character_for_menu == 3 then -- Materia menu
                     elseif self.select_character_for_menu == 3 then -- Materia menu
@@ -107,6 +120,7 @@ UiContainer.MainMenu = {
             end
             end
         elseif ui_manager:get_widget("MainMenu"):is_visible() == false and FFVII.MenuSettings.available == true then
         elseif ui_manager:get_widget("MainMenu"):is_visible() == false and FFVII.MenuSettings.available == true then
             if button == "Escape" and event == "Press" then
             if button == "Escape" and event == "Press" then
+                audio_manager:play_sound("Back")
                 script:request_end_sync(Script.UI, "MainMenu", "show", 0)
                 script:request_end_sync(Script.UI, "MainMenu", "show", 0)
             end
             end
         else
         else

+ 273 - 35
data/data/scripts/menu/materia_menu.lua

@@ -18,12 +18,18 @@ UiContainer.MateriaMenu = {
     --- ID of the character the menu is open for.
     --- ID of the character the menu is open for.
     char_id = -1,
     char_id = -1,
 
 
+    --- Cursor position in the materia list.
     list_position = 1,
     list_position = 1,
 
 
+    --- Max cursor postion in the materia list.
     list_position_total = 10,
     list_position_total = 10,
 
 
+    --- First visible position in the materia list
     list_first_visible = 1,
     list_first_visible = 1,
 
 
+    --- Max materias in the list
+    list_size = 200,
+
     --- Run when the menu is creaded.
     --- Run when the menu is creaded.
     --
     --
     -- It does nothing.
     -- It does nothing.
@@ -42,9 +48,11 @@ UiContainer.MateriaMenu = {
                 -- TODO: Handle L1/R1 (change character)
                 -- TODO: Handle L1/R1 (change character)
                 -- TODO: Handle square: Materia menu
                 -- TODO: Handle square: Materia menu
                 if button == "Escape" and event == "Press" then
                 if button == "Escape" and event == "Press" then
+                    audio_manager:play_sound("Back")
                     UiContainer.current_menu = "main"
                     UiContainer.current_menu = "main"
                     script:request_end_sync(Script.UI, "MateriaMenu", "hide", 0)
                     script:request_end_sync(Script.UI, "MateriaMenu", "hide", 0)
                 elseif button == "Down" then
                 elseif button == "Down" then
+                    audio_manager:play_sound("Cursor")
                     self.header_position_row = self.header_position_row + 1
                     self.header_position_row = self.header_position_row + 1
                     if self.header_position_row > self.header_position_row_total then
                     if self.header_position_row > self.header_position_row_total then
                         self.header_position_row = 1
                         self.header_position_row = 1
@@ -52,6 +60,7 @@ UiContainer.MateriaMenu = {
                     ui_manager:get_widget("MateriaMenu.Container.Character.Cursor"):set_default_animation("Position" .. self.header_position_row .. "-" .. self.header_position)
                     ui_manager:get_widget("MateriaMenu.Container.Character.Cursor"):set_default_animation("Position" .. self.header_position_row .. "-" .. self.header_position)
                     self.populate_details(self, false)
                     self.populate_details(self, false)
                 elseif button == "Up" then
                 elseif button == "Up" then
+                    audio_manager:play_sound("Cursor")
                     self.header_position_row = self.header_position_row - 1
                     self.header_position_row = self.header_position_row - 1
                     if self.header_position_row < 1 then
                     if self.header_position_row < 1 then
                         self.header_position_row = self.header_position_row_total
                         self.header_position_row = self.header_position_row_total
@@ -59,6 +68,7 @@ UiContainer.MateriaMenu = {
                     ui_manager:get_widget("MateriaMenu.Container.Character.Cursor"):set_default_animation("Position" .. self.header_position_row .. "-" .. self.header_position)
                     ui_manager:get_widget("MateriaMenu.Container.Character.Cursor"):set_default_animation("Position" .. self.header_position_row .. "-" .. self.header_position)
                     self.populate_details(self, false)
                     self.populate_details(self, false)
                 elseif button == "Right" then
                 elseif button == "Right" then
+                    audio_manager:play_sound("Cursor")
                     self.header_position = self.header_position + 1
                     self.header_position = self.header_position + 1
                     if self.header_position > self.header_position_total then
                     if self.header_position > self.header_position_total then
                         self.header_position = 1
                         self.header_position = 1
@@ -66,6 +76,7 @@ UiContainer.MateriaMenu = {
                     ui_manager:get_widget("MateriaMenu.Container.Character.Cursor"):set_default_animation("Position" .. self.header_position_row .. "-" .. self.header_position)
                     ui_manager:get_widget("MateriaMenu.Container.Character.Cursor"):set_default_animation("Position" .. self.header_position_row .. "-" .. self.header_position)
                     self.populate_details(self, false)
                     self.populate_details(self, false)
                 elseif button == "Left" then
                 elseif button == "Left" then
+                    audio_manager:play_sound("Cursor")
                     self.header_position = self.header_position - 1
                     self.header_position = self.header_position - 1
                     if self.header_position < 1 then
                     if self.header_position < 1 then
                         self.header_position = self.header_position_total
                         self.header_position = self.header_position_total
@@ -73,18 +84,89 @@ UiContainer.MateriaMenu = {
                     ui_manager:get_widget("MateriaMenu.Container.Character.Cursor"):set_default_animation("Position" .. self.header_position_row .. "-" .. self.header_position)
                     ui_manager:get_widget("MateriaMenu.Container.Character.Cursor"):set_default_animation("Position" .. self.header_position_row .. "-" .. self.header_position)
                     self.populate_details(self, false)
                     self.populate_details(self, false)
                 elseif button == "Enter" then
                 elseif button == "Enter" then
-                    --[[if self.equip_position == 1 then
-                        self.selecting_slot = Inventory.ITEM_TYPE.WEAPON
-                    elseif self.equip_position == 2 then
-                        self.selecting_slot = Inventory.ITEM_TYPE.ARMOR
-                    elseif self.equip_position == 3 then
-                        self.selecting_slot = Inventory.ITEM_TYPE.ACCESSORY
+                    if self.header_position_row == 1 and self.header_position == 1 then
+                        -- TODO: Command menu
+                    elseif self.header_position_row == 2 and self.header_position == 1 then
+                        -- TODO: Order menu
+                    else
+                        -- TODO: Check for  avalid slot before entering list
+                        if self.header_position_row == 1 and #(Game.Items[Characters[self.char_id].weapon.id].slots) > self.header_position - 2 then
+                            self.submenu_list(self)
+                        elseif self.header_position_row == 2 and #(Game.Items[Characters[self.char_id].armor.id].slots) > self.header_position -2  then
+                            self.submenu_list(self)
+                        else
+                            audio_manager:play_sound("Error")
+                            return 0
+                        end
+                        audio_manager:play_sound("Cursor")
                     end
                     end
-                    self.submenu_select(self)]]
                 -- TODO: Triangle->Remove
                 -- TODO: Triangle->Remove
                 else
                 else
                     return 0
                     return 0
                 end
                 end
+            elseif UiContainer.current_submenu == "list" then
+                if button == "Down" then
+                    if self.list_first_visible + self.list_position > self.list_size then
+                        -- At the very end, do nothing
+                        audio_manager:play_sound("Error")
+                        return 0
+                    end
+                    audio_manager:play_sound("Cursor")
+                    if self.list_position == self.list_position_total then
+                        -- The cursor is at the end. Scroll page, but dont change cursor.
+                        self.list_first_visible = self.list_first_visible + 1
+                        self.populate_list(self)
+                    else
+                        -- The cursor is not at the end, move cursor down.
+                        self.list_position = self.list_position + 1
+                    end
+                    ui_manager:get_widget("MateriaMenu.Container.List.Cursor"):set_default_animation("Position" .. self.list_position)
+                    -- Show details
+                    self.populate_details(self, true)
+                elseif button == "Up" then
+                    if self.list_first_visible == 1 and self.list_position == 1 then
+                        -- At the very begining, do nothing
+                        audio_manager:play_sound("Error")
+                        return 0
+                    end
+                    audio_manager:play_sound("Cursor")
+                    if self.list_position == 1 then
+                        -- The cursor is at the end. Scroll page, but dont change cursor.
+                        self.list_first_visible = self.list_first_visible - 1
+                        self.populate_list(self)
+                    else
+                        -- The cursor is not at the end, move cursor down.
+                        self.list_position = self.list_position - 1
+                    end
+                    ui_manager:get_widget("MateriaMenu.Container.List.Cursor"):set_default_animation("Position" .. self.list_position)
+                    -- Show details
+                    self.populate_details(self, true)
+                elseif button == "PGDN" then -- TODO: Also, the bind to R1
+                    if self.list_first_visible + self.list_position_total < self.list_size - self.list_position_total then
+                        audio_manager:play_sound("Cursor")
+                        self.list_first_visible = self.list_first_visible + self.list_position_total
+                        self.populate_list(self)
+                        self.populate_details(self, true)
+                    else
+                        audio_manager:play_sound("Error")
+                    end
+                elseif button == "PGUP" then -- TODO: Also, the bind to L1
+                    if self.list_first_visible - self.list_position_total > 1 then
+                        audio_manager:play_sound("Cursor")
+                        self.list_first_visible = self.list_first_visible - self.list_position_total
+                        self.populate_list(self)
+                        self.populate_details(self, true)
+                    else
+                        audio_manager:play_sound("Error")
+                    end
+                elseif button == "Escape" and event == "Press" then
+                    audio_manager:play_sound("Back")
+                    self.submenu_none(self)
+                elseif button == "Enter" then
+                    -- TODO: Replace for "Equip materia" sound.
+                    audio_manager:play_sound("Cursor")
+                    self.equip_materia(self)
+                end
             end
             end
         end
         end
         return 0
         return 0
@@ -100,7 +182,11 @@ UiContainer.MateriaMenu = {
         UiContainer.current_submenu = ""
         UiContainer.current_submenu = ""
         self.header_position_row = 1
         self.header_position_row = 1
         self.header_position = 1
         self.header_position = 1
+        self.list_position = 1
+        self.list_first_visible = 1
+        self.populate_list(self)
         self.submenu_none(self)
         self.submenu_none(self)
+        self.populate_commands(self)
         return 0;
         return 0;
     end,
     end,
 
 
@@ -111,33 +197,22 @@ UiContainer.MateriaMenu = {
         ui_manager:get_widget("MateriaMenu.Container.Character.Portrait"):set_image("images/characters/" .. tostring(self.char_id) .. ".png")
         ui_manager:get_widget("MateriaMenu.Container.Character.Portrait"):set_image("images/characters/" .. tostring(self.char_id) .. ".png")
         self.populate_equip(self)
         self.populate_equip(self)
         -- TODO: Do something for chars 9 and 10
         -- TODO: Do something for chars 9 and 10
-
-
-        --[[self.load_availables(self)
+        ui_manager:get_widget("MateriaMenu.Container.Character.WpnIcon"):set_image("images/icons/item_weapon_" .. tostring(self.char_id) .. ".png")
+        ui_manager:get_widget("MateriaMenu.Container.List.Cursor"):set_visible(false)
+        --TODO: Uncomment when implemented: ui_manager:get_widget("MateriaMenu.Container.Order.Cursor"):set_visible(false)
+        ui_manager:get_widget("MateriaMenu.Container.Character.Cursor"):set_visible(true)
         self.populate_details(self, false)
         self.populate_details(self, false)
-        self.populate_current_stats(self)
-        self.calculate_stat_diffs(self)
-        -- Hide stat arrows and difs
-        ui_manager:get_widget("EquipMenu.Container.Stats.AtkDif"):set_visible(false)
-        ui_manager:get_widget("EquipMenu.Container.Stats.AccDif"):set_visible(false)
-        ui_manager:get_widget("EquipMenu.Container.Stats.DefDif"):set_visible(false)
-        ui_manager:get_widget("EquipMenu.Container.Stats.EvaDif"):set_visible(false)
-        ui_manager:get_widget("EquipMenu.Container.Stats.MAtkDif"):set_visible(false)
-        ui_manager:get_widget("EquipMenu.Container.Stats.MDefDif"):set_visible(false)
-        ui_manager:get_widget("EquipMenu.Container.Stats.MEvaDif"):set_visible(false)
-        ui_manager:get_widget("EquipMenu.Container.Stats.AtkArw"):set_visible(false)
-        ui_manager:get_widget("EquipMenu.Container.Stats.AccArw"):set_visible(false)
-        ui_manager:get_widget("EquipMenu.Container.Stats.DefArw"):set_visible(false)
-        ui_manager:get_widget("EquipMenu.Container.Stats.EvaArw"):set_visible(false)
-        ui_manager:get_widget("EquipMenu.Container.Stats.MAtkArw"):set_visible(false)
-        ui_manager:get_widget("EquipMenu.Container.Stats.MDefArw"):set_visible(false)
-        ui_manager:get_widget("EquipMenu.Container.Stats.MEvaArw"):set_visible(false)
-        ui_manager:get_widget("EquipMenu.Container.List.Cursor"):set_visible(false)
-        for i = 1, self.list_position_total do
-            ui_manager:get_widget("EquipMenu.Container.List.Item" .. tostring(i)):set_text("")
-        end]]
     end,
     end,
 
 
+    submenu_list = function(self)
+        UiContainer.current_submenu = "list"
+        ui_manager:get_widget("MateriaMenu.Container.List.Cursor"):set_visible(true)
+        self.populate_details(self, true)
+    end,
+
+    --- Populate the equipment section.
+    --
+    -- It fills the header with weapn and armor names, slots, and current materia.
     populate_equip = function(self)
     populate_equip = function(self)
         local weapon = Game.Items[Characters[self.char_id].weapon.id]
         local weapon = Game.Items[Characters[self.char_id].weapon.id]
         local armor = Game.Items[Characters[self.char_id].armor.id]
         local armor = Game.Items[Characters[self.char_id].armor.id]
@@ -192,12 +267,53 @@ UiContainer.MateriaMenu = {
     -- Must be called when the cursor goes into an empty slot
     -- Must be called when the cursor goes into an empty slot
     hide_details = function(self)
     hide_details = function(self)
         -- TODO
         -- TODO
+        ui_manager:get_widget("MateriaMenu.Container.Details.Empty"):set_visible(true)
+        ui_manager:get_widget("MateriaMenu.Container.Details.Commands"):set_visible(false)
+        ui_manager:get_widget("MateriaMenu.Container.Details.EnemySkill"):set_visible(false)
+        ui_manager:get_widget("MateriaMenu.Container.Details.Materia"):set_visible(false)
+        ui_manager:get_widget("MateriaMenu.Container.Help.Help"):set_text("")
     end,
     end,
 
 
     --- Show details of an enemy skill materia.
     --- Show details of an enemy skill materia.
     --
     --
-    -- Must be called when the cursor goes into an empty slot
-    populate_e_skill_details = function(self)
+    -- Must be called when the cursor goes over an enemy skill materia
+    --
+    -- @param materia Selected Enemy Skill materia.
+    populate_e_skill_details = function(self, materia)
+        if materia.id ~= Materia.ENEMY_SKILL_ID then
+            print("ERROR: Tried to get enemy skill info form non-enemy skill materia with ID " .. tostring(materia.id))
+            return 0
+        elseif materia.skills == nil then
+            print("ERROR: Enemy skill materia has no skill information")
+            --return 0
+        end
+        ui_manager:get_widget("MateriaMenu.Container.Help.Help"):set_text(Game.Materia[materia.id].description)
+        ui_manager:get_widget("MateriaMenu.Container.Details.EnemySkill.Name"):set_text(Game.Materia[materia.id].name)
+        ui_manager:get_widget("MateriaMenu.Container.Details.Empty"):set_visible(false)
+        ui_manager:get_widget("MateriaMenu.Container.Details.Commands"):set_visible(false)
+        ui_manager:get_widget("MateriaMenu.Container.Details.EnemySkill"):set_visible(true)
+        ui_manager:get_widget("MateriaMenu.Container.Details.Materia"):set_visible(false)
+        for s = 1, 24 do
+            if materia.skills[s] ~= nil and materia.skills[s] == true then
+                ui_manager:get_widget("MateriaMenu.Container.Details.EnemySkill.Stars.Star" .. tostring(s)):set_image("images/icons/materia_star_3.png")
+                ui_manager:get_widget("MateriaMenu.Container.Details.EnemySkill.Skills.Skill" .. tostring(s)):set_text(Game.Attacks[71 + s].name)
+                ui_manager:get_widget("MateriaMenu.Container.Details.EnemySkill.Skills.Skill" .. tostring(s)):set_visible(true)
+            else
+                ui_manager:get_widget("MateriaMenu.Container.Details.EnemySkill.Stars.Star" .. tostring(s)):set_image("images/icons/materia_star_empty_3.png")
+                ui_manager:get_widget("MateriaMenu.Container.Details.EnemySkill.Skills.Skill" .. tostring(s)):set_visible(false)
+            end
+        end
+
+
+    end,
+
+    --- Displays the command list in the details window
+    populate_commands = function(self)
+        ui_manager:get_widget("MateriaMenu.Container.Details.Empty"):set_visible(false)
+        ui_manager:get_widget("MateriaMenu.Container.Details.Commands"):set_visible(true)
+        ui_manager:get_widget("MateriaMenu.Container.Details.EnemySkill"):set_visible(false)
+        ui_manager:get_widget("MateriaMenu.Container.Details.Materia"):set_visible(false)
+        ui_manager:get_widget("MateriaMenu.Container.Help.Help"):set_text("")
         -- TODO
         -- TODO
     end,
     end,
 
 
@@ -212,9 +328,16 @@ UiContainer.MateriaMenu = {
         local level
         local level
         local max_level
         local max_level
         local master = false
         local master = false
-        local materia
+        local materia -- Materia static information, as in Game.Materia[]
+        local materia_selected -- Materia instance information, as in Materia[]
         if from_list == false then
         if from_list == false then
+            -- Get data from equiped materia
             if self.header_position_row == 1 then
             if self.header_position_row == 1 then
+                -- "Check" highlighted
+                if self.header_position == 1 then
+                    self.populate_commands(self)
+                    return 0
+                end
                 -- Weapon materia
                 -- Weapon materia
                 local weapon_id = Characters[self.char_id].weapon.id
                 local weapon_id = Characters[self.char_id].weapon.id
                 local weapon = Game.Items[weapon_id]
                 local weapon = Game.Items[weapon_id]
@@ -223,10 +346,16 @@ UiContainer.MateriaMenu = {
                     self.hide_details(self)
                     self.hide_details(self)
                     return 0
                     return 0
                 end
                 end
+                materia_selected = Characters[self.char_id].weapon.materia[self.header_position - 2]
                 id = Characters[self.char_id].weapon.materia[self.header_position - 2].id
                 id = Characters[self.char_id].weapon.materia[self.header_position - 2].id
                 ap = Characters[self.char_id].weapon.materia[self.header_position - 2].ap
                 ap = Characters[self.char_id].weapon.materia[self.header_position - 2].ap
                 materia = Game.Materia[id]
                 materia = Game.Materia[id]
             elseif self.header_position_row == 2 then
             elseif self.header_position_row == 2 then
+                -- "Order" highlighted
+                if self.header_position == 1 then
+                    self.hide_details(self)
+                    return 0
+                end
                 -- Armor materia
                 -- Armor materia
                 local armor_id = Characters[self.char_id].armor.id
                 local armor_id = Characters[self.char_id].armor.id
                 local armor = Game.Items[armor_id]
                 local armor = Game.Items[armor_id]
@@ -235,6 +364,7 @@ UiContainer.MateriaMenu = {
                     self.hide_details(self)
                     self.hide_details(self)
                     return 0
                     return 0
                 end
                 end
+                materia_selected = Characters[self.char_id].armor.materia[self.header_position - 2]
                 id = Characters[self.char_id].armor.materia[self.header_position - 2].id
                 id = Characters[self.char_id].armor.materia[self.header_position - 2].id
                 ap = Characters[self.char_id].armor.materia[self.header_position - 2].ap
                 ap = Characters[self.char_id].armor.materia[self.header_position - 2].ap
                 materia = Game.Materia[id]
                 materia = Game.Materia[id]
@@ -244,9 +374,36 @@ UiContainer.MateriaMenu = {
                 return 0
                 return 0
             end
             end
         else
         else
-            -- TODO: Get materia data from list
+            --Get materia data from list.
+            local mat_position = self.list_first_visible + self.list_position - 1
+            if Materia[mat_position] == nil then
+                self.hide_details(self)
+                return 0
+            end
+            materia_selected = Materia[mat_position]
+            id = Materia[mat_position].id
+            ap = Materia[mat_position].ap
+            materia = Game.Materia[Materia[mat_position].id]
+        end
+
+        -- Materia should be set, but just in case, one last check.
+        if materia == nil then
+            self.hide_details(self)
+            return
+        end
+
+        -- Check for enemy skill. It is handled by another function.
+        if id == Materia.ENEMY_SKILL_ID then
+            self.populate_e_skill_details(self, materia_selected)
+            return 0
         end
         end
 
 
+        ui_manager:get_widget("MateriaMenu.Container.Details.Empty"):set_visible(false)
+        ui_manager:get_widget("MateriaMenu.Container.Details.Commands"):set_visible(false)
+        ui_manager:get_widget("MateriaMenu.Container.Details.EnemySkill"):set_visible(false)
+        ui_manager:get_widget("MateriaMenu.Container.Details.Materia"):set_visible(true)
+        ui_manager:get_widget("MateriaMenu.Container.Help.Help"):set_text(Game.Materia[id].description)
+
         -- Get level and max level, and check for master.
         -- Get level and max level, and check for master.
         level = 0
         level = 0
         for lv, lv_ap in ipairs(materia.levels_ap) do
         for lv, lv_ap in ipairs(materia.levels_ap) do
@@ -471,6 +628,87 @@ UiContainer.MateriaMenu = {
         end
         end
     end,
     end,
 
 
+    --- Populates the materia list.
+    populate_list = function(self)
+        local pos = 1
+        for i = self.list_first_visible, self.list_first_visible + self.list_position_total - 1 do
+            if Materia[i] ~= nil then
+                local type = Game.Materia[Materia[i].id].type
+                local name = Game.Materia[Materia[i].id].name
+                ui_manager:get_widget("MateriaMenu.Container.List.Icon" .. tostring(pos)):set_image("images/icons/materia_" .. tostring(type) .. ".png")
+                ui_manager:get_widget("MateriaMenu.Container.List.Icon" .. tostring(pos)):set_visible(true)
+                ui_manager:get_widget("MateriaMenu.Container.List.Name" .. tostring(pos)):set_text(name)
+                ui_manager:get_widget("MateriaMenu.Container.List.Name" .. tostring(pos)):set_visible(true)
+            else
+                ui_manager:get_widget("MateriaMenu.Container.List.Icon" .. tostring(pos)):set_visible(false)
+                ui_manager:get_widget("MateriaMenu.Container.List.Name" .. tostring(pos)):set_visible(false)
+            end
+            pos = pos + 1
+        end
+         -- Set scroll bar position
+        local scroll = ui_manager:get_widget("MateriaMenu.Container.List.Scroll")
+        -- first_item_in_window == 1 then Top -> 0%3
+        -- first_item_in_window == inventory_size - first_item_in_window then top = 100%-21
+        local percent = math.floor((self.list_first_visible - 1) * 100 / (self.list_size - self.list_position_total))
+        local fixed = 3 + (-1 * math.floor(self.list_first_visible - 1) * 25 / (self.list_size - self.list_position_total))
+        scroll:set_y(percent, fixed)
+    end,
+
+    --- Equips the materia selected in the list at the currently selected slot.
+    --
+    -- It can also be used to swap or remove materia.
+    equip_materia = function(self)
+        local equip
+        local equip_slot = self.header_position - 2 -- 0-8
+        local list_pos = self.list_first_visible + self.list_position - 1
+        local materia_equip = {id = nil, ap = 0}
+        local materia_list = {id = nil, ap = 0}
+        --- Get info about the seleceted materias (equipped and in the list)
+        if self.header_position_row == 1 then
+            equip = Inventory.ITEM_TYPE.WEAPON
+            if equip_slot + 1 > #(Game.Items[Characters[self.char_id].weapon.id].slots) then
+                print("BEEP No slot " .. tostring(equip_slot) .. " in current weapon.")
+                return 0
+            end
+            if Characters[self.char_id].weapon.materia[equip_slot] ~= nil then
+                materia_equip = Characters[self.char_id].weapon.materia[equip_slot]
+            end
+        elseif self.header_position_row == 2 then
+            equip = Inventory.ITEM_TYPE.ARMOR
+            if equip_slot + 1 > #(Game.Items[Characters[self.char_id].armor.id].slots) then
+                print("BEEP No slot " .. tostring(equip_slot) .. " in current armor.")
+                return 0
+            end
+            if Characters[self.char_id].armor.materia[equip_slot] ~= nil then
+                materia_equip = Characters[self.char_id].armor.materia[equip_slot]
+            end
+        end
+        if Materia[list_pos] ~= nil then
+            materia_list = Materia[list_pos]
+        end
+        --- Equipped materia to the list (can be null)
+        Materia[list_pos] = materia_equip
+        if Materia[list_pos].id == nil then
+            Materia[list_pos] = nil
+        end
+        --- Materia from list to weapon or armor
+        if equip == Inventory.ITEM_TYPE.WEAPON then
+            Characters[self.char_id].weapon.materia[equip_slot] = materia_list
+            if Characters[self.char_id].weapon.materia[equip_slot].id == nil then
+                Characters[self.char_id].weapon.materia[equip_slot] = nil
+            end
+        elseif equip == Inventory.ITEM_TYPE.ARMOR then
+            Characters[self.char_id].armor.materia[equip_slot] = materia_list
+            if Characters[self.char_id].armor.materia[equip_slot].id == nil then
+                Characters[self.char_id].armor.materia[equip_slot] = nil
+            end
+        end
+        -- Reload character data and list.
+        self.populate_equip(self)
+        self.populate_list(self)
+        self.submenu_none(self)
+    end,
+
     --- Hides the item menu and goes back to the main menu.
     --- Hides the item menu and goes back to the main menu.
     hide = function(self)
     hide = function(self)
         ui_manager:get_widget("MateriaMenu"):set_visible(false)
         ui_manager:get_widget("MateriaMenu"):set_visible(false)

+ 16 - 1
data/data/scripts/menu/name_menu.lua

@@ -52,18 +52,21 @@ UiContainer.NameMenu = {
         if UiContainer.current_menu == "name" then
         if UiContainer.current_menu == "name" then
             if UiContainer.current_submenu == "confirm" then
             if UiContainer.current_submenu == "confirm" then
                 if button == "Right" then
                 if button == "Right" then
+                    audio_manager:play_sound("Cursor")
                     self.confirm_position = self.confirm_position + 1
                     self.confirm_position = self.confirm_position + 1
                     if self.confirm_position > self.confirm_position_total then
                     if self.confirm_position > self.confirm_position_total then
                         self.confirm_position = 1
                         self.confirm_position = 1
                     end
                     end
                     ui_manager:get_widget("NameMenu.Container.Confirm.Cursor"):set_default_animation("Position" .. self.confirm_position)
                     ui_manager:get_widget("NameMenu.Container.Confirm.Cursor"):set_default_animation("Position" .. self.confirm_position)
                 elseif button == "Left" then
                 elseif button == "Left" then
+                    audio_manager:play_sound("Cursor")
                     self.confirm_position = self.confirm_position - 1
                     self.confirm_position = self.confirm_position - 1
                     if self.confirm_position < 1 then
                     if self.confirm_position < 1 then
                         self.confirm_position = self.confirm_position_total
                         self.confirm_position = self.confirm_position_total
                     end
                     end
                     ui_manager:get_widget("NameMenu.Container.Confirm.Cursor"):set_default_animation("Position" .. self.confirm_position)
                     ui_manager:get_widget("NameMenu.Container.Confirm.Cursor"):set_default_animation("Position" .. self.confirm_position)
                 elseif button == "Enter" then
                 elseif button == "Enter" then
+                    audio_manager:play_sound("Cursor")
                     if self.confirm_position == 2 then -- Back
                     if self.confirm_position == 2 then -- Back
                         UiContainer.current_submenu = ""
                         UiContainer.current_submenu = ""
                         ui_manager:get_widget("NameMenu.Container.Confirm"):set_visible(false)
                         ui_manager:get_widget("NameMenu.Container.Confirm"):set_visible(false)
@@ -83,12 +86,14 @@ UiContainer.NameMenu = {
 
 
                 -- Handle buttons.
                 -- Handle buttons.
                 if button == "Down" then
                 if button == "Down" then
+                    audio_manager:play_sound("Cursor")
                     self.options_position = self.options_position + 1
                     self.options_position = self.options_position + 1
                     if self.options_position > self.options_position_total then
                     if self.options_position > self.options_position_total then
                         self.options_position = 1
                         self.options_position = 1
                     end
                     end
                     ui_manager:get_widget("NameMenu.Container.Options.Cursor"):set_default_animation("Position" .. self.options_position)
                     ui_manager:get_widget("NameMenu.Container.Options.Cursor"):set_default_animation("Position" .. self.options_position)
                 elseif button == "Up" then
                 elseif button == "Up" then
+                    audio_manager:play_sound("Cursor")
                     self.options_position = self.options_position - 1
                     self.options_position = self.options_position - 1
                     if self.options_position < 1 then
                     if self.options_position < 1 then
                         self.options_position = self.options_position_total
                         self.options_position = self.options_position_total
@@ -98,19 +103,27 @@ UiContainer.NameMenu = {
                     if self.text_cursor > 1 then
                     if self.text_cursor > 1 then
                         self.text_cursor = self.text_cursor - 1
                         self.text_cursor = self.text_cursor - 1
                         self.draw_cursor(self)
                         self.draw_cursor(self)
+                        audio_manager:play_sound("Cursor")
+                    else
+                        audio_manager:play_sound("Error")
                     end
                     end
                 elseif button == "Right" then
                 elseif button == "Right" then
                     if self.text_cursor < self.max_characters then
                     if self.text_cursor < self.max_characters then
+                        audio_manager:play_sound("Cursor")
                         self.text_cursor = self.text_cursor + 1
                         self.text_cursor = self.text_cursor + 1
                         self.draw_cursor(self)
                         self.draw_cursor(self)
+                    else
+                        audio_manager:play_sound("Error")
                     end
                     end
                 elseif button == "Enter" then
                 elseif button == "Enter" then
                     if self.options_position == 1 then -- Clear name.
                     if self.options_position == 1 then -- Clear name.
+                        audio_manager:play_sound("Cursor")
                         self.name = ""
                         self.name = ""
                         self.text_cursor = 1
                         self.text_cursor = 1
                         self.populate_name(self)
                         self.populate_name(self)
                         self.draw_cursor(self)
                         self.draw_cursor(self)
                     elseif self.options_position == 2 then -- Default name.
                     elseif self.options_position == 2 then -- Default name.
+                        audio_manager:play_sound("Cursor")
                         self.name = self.default_name
                         self.name = self.default_name
                         self.text_cursor = math.min(#(self.name) + 1, self.max_characters)
                         self.text_cursor = math.min(#(self.name) + 1, self.max_characters)
                         self.populate_name(self)
                         self.populate_name(self)
@@ -120,6 +133,7 @@ UiContainer.NameMenu = {
                         self.populate_name(self)
                         self.populate_name(self)
                         self.draw_cursor(self)
                         self.draw_cursor(self)
                         if #(self.name) > 0 then
                         if #(self.name) > 0 then
+                            audio_manager:play_sound("Cursor")
                             UiContainer.current_submenu = "confirm"
                             UiContainer.current_submenu = "confirm"
                             local name_display = self.name -- Pad with spaces, to display only.
                             local name_display = self.name -- Pad with spaces, to display only.
                             while #(name_display) < self.max_characters do
                             while #(name_display) < self.max_characters do
@@ -130,12 +144,13 @@ UiContainer.NameMenu = {
                             self.confirm_position = 1
                             self.confirm_position = 1
                             ui_manager:get_widget("NameMenu.Container.Options.Cursor"):set_default_animation("Position" .. self.options_position)
                             ui_manager:get_widget("NameMenu.Container.Options.Cursor"):set_default_animation("Position" .. self.options_position)
                         else
                         else
-                            print("BEEP no name")
+                            audio_manager:play_sound("Error")
                         end
                         end
                     end
                     end
                 -- TODO: Implement backspace, but how?
                 -- TODO: Implement backspace, but how?
                 -- Letters and spaces
                 -- Letters and spaces
                 elseif character_index ~= nil then
                 elseif character_index ~= nil then
+                    audio_manager:play_sound("Cursor")
                     self.set_char(self, character_index)
                     self.set_char(self, character_index)
                     self.text_cursor = math.min(self.text_cursor + 1, self.max_characters)
                     self.text_cursor = math.min(self.text_cursor + 1, self.max_characters)
                     self.populate_name(self)
                     self.populate_name(self)

+ 5 - 0
data/data/scripts/menu/pause_menu.lua

@@ -19,6 +19,7 @@ UiContainer.PauseMenu = {
             local menu_cursor = ui_manager:get_widget( "PauseMenu.Menu.Cursor" )
             local menu_cursor = ui_manager:get_widget( "PauseMenu.Menu.Cursor" )
 
 
             if button == "Enter" and event == "Press" then
             if button == "Enter" and event == "Press" then
+                audio_manager:play_sound("Cursor")
                 script:request_end_sync( Script.UI, "PauseMenu", "hide", 0 )
                 script:request_end_sync( Script.UI, "PauseMenu", "hide", 0 )
                 if self.position == 1 then
                 if self.position == 1 then
                     script:request_end_sync( Script.UI, "BeginMenu", "show", 0 )
                     script:request_end_sync( Script.UI, "BeginMenu", "show", 0 )
@@ -28,15 +29,18 @@ UiContainer.PauseMenu = {
                     FFVII.MenuSettings.available = true
                     FFVII.MenuSettings.available = true
                 end
                 end
             elseif button == "Escape" and event == "Press" then
             elseif button == "Escape" and event == "Press" then
+                audio_manager:play_sound("Back")
                 script:request_end_sync( Script.UI, "PauseMenu", "hide", 0 )
                 script:request_end_sync( Script.UI, "PauseMenu", "hide", 0 )
                 FFVII.MenuSettings.available = true
                 FFVII.MenuSettings.available = true
             elseif button == "Right" then
             elseif button == "Right" then
+                audio_manager:play_sound("Cursor")
                 self.position = self.position + 1
                 self.position = self.position + 1
                 if self.position > self.position_total then
                 if self.position > self.position_total then
                     self.position = 1;
                     self.position = 1;
                 end
                 end
                 menu_cursor:set_default_animation( "Position" .. self.position )
                 menu_cursor:set_default_animation( "Position" .. self.position )
             elseif button == "Left" then
             elseif button == "Left" then
+                audio_manager:play_sound("Cursor")
                 self.position = self.position - 1
                 self.position = self.position - 1
                 if self.position <= 0 then
                 if self.position <= 0 then
                     self.position = self.position_total;
                     self.position = self.position_total;
@@ -45,6 +49,7 @@ UiContainer.PauseMenu = {
             end
             end
         elseif FFVII.MenuSettings.pause_available == true then
         elseif FFVII.MenuSettings.pause_available == true then
             if button == "Enter" and event == "Press" then
             if button == "Enter" and event == "Press" then
+                audio_manager:play_sound("Cursor")
                 FFVII.MenuSettings.available = false
                 FFVII.MenuSettings.available = false
                 script:request_end_sync( Script.UI, "PauseMenu", "show", 0 )
                 script:request_end_sync( Script.UI, "PauseMenu", "show", 0 )
             end
             end

+ 23 - 2
data/data/scripts/system.lua

@@ -1,4 +1,5 @@
 MAX_INVENTORY_SLOTS = 320
 MAX_INVENTORY_SLOTS = 320
+MAX_MATERIA_SLOTS = 200
 
 
 --- Export character names to the text manager.
 --- Export character names to the text manager.
 --
 --
@@ -642,8 +643,28 @@ end
 
 
 
 
 Materia.add = function(id, ap)
 Materia.add = function(id, ap)
-    ap = ap or 0
-    -- TODO Implement.
+    local ap = ap or 0
+    if Game.Materia[id] == nil then
+        print("Tried to add invalid materia \"" .. item .. "\".");
+        return
+    end
+    -- TODO Cap AP?
+    for i = 1, MAX_MATERIA_SLOTS do
+        if Materia[i] == nil then
+            Materia[i] = {id = id, ap = ap}
+            if Materia[i].id == Materia.ENEMY_SKILL_ID then
+                Materia[i].ap = 0
+                Materia[i].skills = {}
+                for s = 1, 24 do
+                    Materia[i].skills[s] = false
+                end
+            end
+            return
+        end
+    end
+    -- If reached this point, it means that the materia wasn't added because all slots are full.
+    -- TODO: Do something.
+    return
 end
 end
 
 
 --- Splits the party in teams.
 --- Splits the party in teams.

+ 4 - 0
data/data/sounds.xml

@@ -0,0 +1,4 @@
+<!-- This file is to be overwritten by the installer -->
+<sounds>
+    <sound file_name="audio/sound/example.ogg" name="Example" />
+</sounds>

+ 1 - 0
data/data/system/README.txt

@@ -0,0 +1 @@
+This directory contains a few files required to make the engine work.

+ 2 - 0
data/data/texts/README.txt

@@ -0,0 +1,2 @@
+This folder contains the game static texts. As of the time of this writting, only the english
+language is actively maintained.

+ 6 - 0
doc/INSTALL_DATA.md

@@ -17,6 +17,12 @@ FFVIIPC/
 └── wm
 └── wm
 ```
 ```
 
 
+## Install FFmpeg
+
+The installer uses [FFmpeg](https://ffmpeg.org "FFmpeg") to convert audio files. To do this, it uses the ffmpeg executable in the system, and the sounds files will not be properly installed if there is such executable. In most GNU/Linux distros, ffmpeg is available as a package, so you can install it with your package manager. For the rest of cases, see [the downloads page](https://ffmpeg.org/download.html "FFmpeg download page").
+
+It's important that the ffmpeg executable is located in a globally accessed location. To verify this, try to run this command from a console: `ffmpeg -version`. If ffmpeg version is show, you are all set. If not, you must include the path to the ffmpeg executable to your system PATH environment variable.
+
 ## Launch the Installer
 ## Launch the Installer
 
 
 If you have followed the [build](BUILD.md "Building guide") and [installaton](INSTALL.md "Installation guide") guides, you'll have the game data copied to `~/.v-gears/`, and the binaries compilled somewhere (we'll assum they are in `~/V-Gears/build/bin`, change the directories according to your system).
 If you have followed the [build](BUILD.md "Building guide") and [installaton](INSTALL.md "Installation guide") guides, you'll have the game data copied to `~/.v-gears/`, and the binaries compilled somewhere (we'll assum they are in `~/V-Gears/build/bin`, change the directories according to your system).