소스 검색

Equip menu is now functional.
Character change and materia menu link are unimplemented for now, the rest is working.
Minor modifications in the kernel data installer to provide easier-to-handle scripts.

Iñigo Valentin 3 년 전
부모
커밋
bc54d61cc5

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

@@ -2381,6 +2381,14 @@ class KernelDataInstaller{
             StatCurve curves[64];
         };
 
+        /**
+         * Retrieves the 3-letter name of a basic stat from it's id.
+         *
+         * @param[in] id The stat ID.
+         * @return Stat name abbreviation. If the stat ID is unknown, "stat" will be returned.
+         */
+        std::string GetBasicStatName(int id);
+
         /**
          * Items read from the kernel.
          */

+ 41 - 37
V-Gears-Installer/src/KernelDataInstaller.cpp

@@ -1035,8 +1035,8 @@ void KernelDataInstaller::WriteWeapons(std::string file_name){
         for (u8 slot : weapon.materia_slots) file << static_cast<int>(slot) << ", ";
         file << "},\n    stats = {";
         for (StatBonus s : weapon.stat_bonus){
-            file << "\n        {stat = " << static_cast<int>(s.stat) << ", bonus = "
-              << static_cast<int>(s.bonus) << "},";
+            file << "\n        " << GetBasicStatName(s.stat) << " = "
+              << static_cast<int>(s.stat) << ",";
         }
         if (weapon.stat_bonus.size() > 0) file << "\n    },\n";
         else file << "},\n";
@@ -1107,11 +1107,11 @@ int KernelDataInstaller::ReadArmors(){
         }
         boost::replace_all(data.description, "\"", "'");
 
-        // If the item doesn't have a name, it means all items have been read.
+        // If the armor doesn't have a name, it means all items have been read.
         // break the loop.
         if ("" == data.name) break;
 
-        // Read data from the weapon data section.
+        // Read data from the armor data section.
         data.unknown_0 = armor_file.readU8();
         data.element_defense_mode = armor_file.readU8();
         data.defense = armor_file.readU8();
@@ -1218,7 +1218,7 @@ void KernelDataInstaller::WriteArmors(std::string file_name){
           << static_cast<int>(armor.m_defense) << "},\n"
           << "    evasion = {physical = " << static_cast<int>(armor.evasion) << ", magical = "
           << static_cast<int>(armor.m_evasion) << "},\n"
-          << "    growth = " << static_cast<int>(armor.growth) + 1 << ",\n"
+          << "    growth = " << static_cast<int>(armor.growth) << ",\n"
           << "    status = {";
         if (armor.status != 0XFF) file << armor.status;
         file
@@ -1234,8 +1234,8 @@ void KernelDataInstaller::WriteArmors(std::string file_name){
         for (u8 slot : armor.materia_slots) file << static_cast<int>(slot) << ", ";
         file << "},\n    stats = {";
         for (StatBonus s : armor.stat_bonus){
-            file << "\n        {stat = " << static_cast<int>(s.stat) << ", bonus = "
-              << static_cast<int>(s.bonus) << "},";
+            file << "\n        " << GetBasicStatName(s.stat) << " = "
+              << static_cast<int>(s.stat) << ",";
         }
         if (armor.stat_bonus.size() > 0) file << "\n    },\n";
         else file << "},\n";
@@ -1436,8 +1436,8 @@ void KernelDataInstaller::WriteAccessories(std::string file_name){
         for (int s : accessory.elements) file << s << ", ";
         file << "},\n    stats = {";
         for (StatBonus s : accessory.stat_bonus){
-            file << "\n        {stat = " << static_cast<int>(s.stat) << ", bonus = "
-              << static_cast<int>(s.bonus) << "},";
+            file << "\n        " << GetBasicStatName(s.stat) << " = "
+              << static_cast<int>(s.stat) << ",";
         }
         if (accessory.stat_bonus.size() > 0) file << "\n    },\n";
         else file << "},\n";
@@ -1713,34 +1713,18 @@ void KernelDataInstaller::WriteMateria(std::string file_name){
           << "    status = {";
         for (int s : materia.status) file << s << ", ";
         file
-          << "}\n"
+          << "},\n"
           << "    elements = {";
         if (materia.element != 0xFF) file << static_cast<int>(materia.element);
-        file << "}\n    stats = {";
-        if (materia.stats.str != 0)
-            file
-              << "\n        {stat = \"str\", bonus = " << materia.stats.str << ", mode = \"abs\"},";
-        if (materia.stats.vit != 0)
-            file
-              << "\n        {stat = \"vit\", bonus = " << materia.stats.vit << ", mode = \"abs\"},";
-        if (materia.stats.mag != 0)
-            file
-              << "\n        {stat = \"mag\", bonus = " << materia.stats.mag << ", mode = \"abs\"},";
-        if (materia.stats.spr != 0)
-            file
-              << "\n        {stat = \"spr\", bonus = " << materia.stats.spr << ", mode = \"abs\"},";
-        if (materia.stats.dex != 0)
-            file
-              << "\n        {stat = \"dex\", bonus = " << materia.stats.dex << ", mode = \"abs\"},";
-        if (materia.stats.lck != 0)
-            file
-              << "\n        {stat = \"lck\", bonus = " << materia.stats.lck << ", mode = \"abs\"},";
-        if (materia.stats.hp != 0)
-            file
-              << "\n        {stat = \"hp\", bonus = " << materia.stats.hp << ", mode = \"rel\"},";
-        if (materia.stats.mp != 0)
-            file
-              << "\n        {stat = \"mp\", bonus = " << materia.stats.mp << ", mode = \"rel\"},";
+        file << "},\n    stats = {";
+        if (materia.stats.str != 0) file << "\n        str = " << materia.stats.str << ",";
+        if (materia.stats.vit != 0) file << "\n        vit = " << materia.stats.vit << ",";
+        if (materia.stats.mag != 0) file << "\n        mag = " << materia.stats.mag << ",";
+        if (materia.stats.spr != 0) file << "\n        spr = " << materia.stats.spr << ",";
+        if (materia.stats.dex != 0) file << "\n        dex = " << materia.stats.dex << ",";
+        if (materia.stats.lck != 0) file << "\n        lck = " << materia.stats.lck << ",";
+        if (materia.stats.hp != 0) file << "\n        hp = " << materia.stats.hp << ",";
+        if (materia.stats.mp != 0) file << "\n        mp = " << materia.stats.mp << ",";
         if (materia.stats.change) file << "\n    }\n";
         else file << "}\n";
         file << "}\n";
@@ -2029,7 +2013,14 @@ void KernelDataInstaller::WriteInitialSaveMap(std::string file_name){
           << "        hp = {current = " << character.hp  << ", base = " << character.base_hp
           << ", max = " << character.max_hp << "},\n"
           << "        mp = {current = " << character.mp  << ", base = " << character.base_mp
-          << ", max = " << character.max_mp << "}\n"
+          << ", max = " << character.max_mp << "},\n"
+          << "        atk = 0,"
+          << "        acc = 0,"
+          << "        def = 0,"
+          << "        eva = 0,"
+          << "        matk = 0,"
+          << "        mdef = 0,"
+          << "        meva = 0"
           << "    },\n"
           << "    limit = {\n"
           << "        current = " << static_cast<int>(character.limit_level) << ",\n"
@@ -2082,7 +2073,20 @@ void KernelDataInstaller::WriteInitialSaveMap(std::string file_name){
         file << ",\n    status = {";
         if (character.fury) file << FURY;
         if (character.sadness) file << SADNESS;
-        file << "}\n}\n";
+        file << "}\n}\nCharacters.recalculate_stats(" << c << ")\n\n";
     }
     file.close();
 }
+
+std::string KernelDataInstaller::GetBasicStatName(int id){
+    std::string name = "stat";
+    switch (id){
+        case 0: name = "str"; break;
+        case 1: name = "vit"; break;
+        case 2: name = "mag"; break;
+        case 3: name = "spr"; break;
+        case 4: name = "dex"; break;
+        case 5: name = "lck"; break;
+    }
+    return name;
+}

+ 72 - 19
data/data/screens/equip_menu/equip_menu.xml

@@ -26,28 +26,81 @@
             <prototype name="Window" />
             <text_area name="Help" x="16" y="6" font="FFVIIFont" visible="true" />
         </widget>
-        <widget name="Slots" x="0" y="94" width="260" height="60" visible="true">
+        <widget name="Slots" x="0" y="94" width="260" height="50" visible="true">
             <prototype name="Window" />
-            <text_area name="SlotsLbl" colours="0 0.8 1, 0 0.8 1, 0 0.8 1, 0 0.8 1" text_name="EquipMenuSlots" x="16" y="12" font="FFVIIFont" visible="true" />
-            <text_area name="GrowthLbl" text_name="EquipMenuGrowth" x="16" y="32" font="FFVIIFont" visible="true" colour="0 0.8 1" />
-            <text_area name="Growth1" text_name="EquipMenuGrowth1" x="80" y="32" font="FFVIIFont" visible="true" colour="0 0.8 1" />
-            <text_area name="Growth2" text_name="EquipMenuGrowth2" x="80" y="32" font="FFVIIFont" visible="false" colour="0 0.8 1" />
-            <text_area name="Growth3" text_name="EquipMenuGrowth3" x="80" y="32" font="FFVIIFont" visible="false" colour="0 0.8 1" />
-            <sprite name="Slot1" image="images/icons/materia_slot.png" x="80" y="10" width="18" height="18" visible="true"/>
-            <sprite name="Slot2" image="images/icons/materia_slot.png" x="100" y="10" width="18" height="18" visible="true"/>
-            <sprite name="Link1" image="images/icons/materia_slot_link.png" x="94" y="10" width="10" height="18" visible="true"/>
-            <sprite name="Slot3" image="images/icons/materia_slot.png" x="120" y="10" width="18" height="18" visible="true"/>
-            <sprite name="Slot4" image="images/icons/materia_slot.png" x="140" y="10" width="18" height="18" visible="true"/>
-            <sprite name="Link2" image="images/icons/materia_slot_link.png" x="134" y="10" width="10" height="18" visible="true"/>
-            <sprite name="Slot5" image="images/icons/materia_slot.png" x="160" y="10" width="18" height="18" visible="true"/>
-            <sprite name="Slot6" image="images/icons/materia_slot.png" x="180" y="10" width="18" height="18" visible="true"/>
-            <sprite name="Link3" image="images/icons/materia_slot_link.png" x="174" y="10" width="10" height="18" visible="true"/>
-            <sprite name="Slot7" image="images/icons/materia_slot.png" x="200" y="10" width="18" height="18" visible="true"/>
-            <sprite name="Slot8" image="images/icons/materia_slot.png" x="220" y="10" width="18" height="18" visible="true"/>
-            <sprite name="Link4" image="images/icons/materia_slot_link.png" x="214" y="10" width="10" height="18" visible="true"/>
+            <text_area name="SlotsLbl" text_name="EquipMenuSlots" x="16" y="10" font="FFVIIFont" visible="true" />
+            <text_area name="GrowthLbl" text_name="EquipMenuGrowth" x="16" y="28" font="FFVIIFont" visible="true" />
+            <text_area name="Growth0" text_name="EquipMenuGrowth1" x="80" y="28" font="FFVIIFont" visible="true" />
+            <text_area name="Growth1" text_name="EquipMenuGrowth1" x="80" y="28" font="FFVIIFont" visible="true" />
+            <text_area name="Growth2" text_name="EquipMenuGrowth2" x="80" y="28" font="FFVIIFont" visible="false" />
+            <text_area name="Growth3" text_name="EquipMenuGrowth3" x="80" y="28" font="FFVIIFont" visible="false" />
+            <sprite name="Slot1" image="images/icons/materia_slot.png" x="80" y="8" width="18" height="18" visible="true"/>
+            <sprite name="Slot2" image="images/icons/materia_slot.png" x="100" y="8" width="18" height="18" visible="true"/>
+            <sprite name="Link1" image="images/icons/materia_slot_link.png" x="94" y="8" width="10" height="18" visible="true"/>
+            <sprite name="Slot3" image="images/icons/materia_slot.png" x="120" y="8" width="18" height="18" visible="true"/>
+            <sprite name="Slot4" image="images/icons/materia_slot.png" x="140" y="8" width="18" height="18" visible="true"/>
+            <sprite name="Link2" image="images/icons/materia_slot_link.png" x="134" y="8" width="10" height="18" visible="true"/>
+            <sprite name="Slot5" image="images/icons/materia_slot.png" x="160" y="8" width="18" height="18" visible="true"/>
+            <sprite name="Slot6" image="images/icons/materia_slot.png" x="180" y="8" width="18" height="18" visible="true"/>
+            <sprite name="Link3" image="images/icons/materia_slot_link.png" x="174" y="8" width="10" height="18" visible="true"/>
+            <sprite name="Slot7" image="images/icons/materia_slot.png" x="200" y="8" width="18" height="18" visible="true"/>
+            <sprite name="Slot8" image="images/icons/materia_slot.png" x="220" y="8" width="18" height="18" visible="true"/>
+            <sprite name="Link4" image="images/icons/materia_slot_link.png" x="214" y="8" width="10" height="18" visible="true"/>
         </widget>
-        <widget name="SlotsEmpty" x="0" y="94" width="260" height="60" visible="false">
+        <widget name="SlotsEmpty" x="0" y="94" width="260" height="50" visible="false">
             <prototype name="Window" />
         </widget>
+        <widget name="Stats" x="0" y="144" width="260" height="100%-145" visible="true">
+            <prototype name="Window" />
+            <text_area name="AtkLbl" text_name="EquipMenuAtk" x="20" y="4" font="FFVIIFont" visible="true" />
+            <text_area name="AccLbl" text_name="EquipMenuAcc" x="20" y="16" font="FFVIIFont" visible="true" />
+            <text_area name="DefLbl" text_name="EquipMenuDef" x="20" y="28" font="FFVIIFont" visible="true" />
+            <text_area name="EvaLbl" text_name="EquipMenuEva" x="20" y="40" font="FFVIIFont" visible="true" />
+            <text_area name="MAtkLbl" text_name="EquipMenuMAtk" x="20" y="52" font="FFVIIFont" visible="true" />
+            <text_area name="MDefLbl" text_name="EquipMenuMDef" x="20" y="64" font="FFVIIFont" visible="true" />
+            <text_area name="MEvaLbl" text_name="EquipMenuMEva" x="20" y="76" font="FFVIIFont" visible="true" />
+            <text_area name="Atk" x="120" y="5" font="FFVIIMenuDigits" visible="true" />
+            <text_area name="Acc" x="120" y="17" font="FFVIIMenuDigits" visible="true" />
+            <text_area name="Def" x="120" y="29" font="FFVIIMenuDigits" visible="true" />
+            <text_area name="Eva" x="120" y="41" font="FFVIIMenuDigits" visible="true" />
+            <text_area name="MAtk" x="120" y="53" font="FFVIIMenuDigits" visible="true" />
+            <text_area name="MDef" x="120" y="65" font="FFVIIMenuDigits" visible="true" />
+            <text_area name="MEva" x="120" y="77" font="FFVIIMenuDigits" visible="true" />
+            <text_area name="AtkArw" text_name="EquipMenuArrow" x="155" y="4" font="FFVIIFont" visible="true" />
+            <text_area name="AccArw" text_name="EquipMenuArrow" x="155" y="16" font="FFVIIFont" visible="true" />
+            <text_area name="DefArw" text_name="EquipMenuArrow" x="155" y="28" font="FFVIIFont" visible="true" />
+            <text_area name="EvaArw" text_name="EquipMenuArrow" x="155" y="40" font="FFVIIFont" visible="true" />
+            <text_area name="MAtkArw" text_name="EquipMenuArrow" x="155" y="52" font="FFVIIFont" visible="true" />
+            <text_area name="MDefArw" text_name="EquipMenuArrow" x="155" y="64" font="FFVIIFont" visible="true" />
+            <text_area name="MEvaArw" text_name="EquipMenuArrow" x="155" y="76" font="FFVIIFont" visible="true" />
+            <text_area name="AtkDif" x="170" y="5" font="FFVIIMenuDigits" visible="true" />
+            <text_area name="AccDif" x="170" y="17" font="FFVIIMenuDigits" visible="true" />
+            <text_area name="DefDif" x="170" y="29" font="FFVIIMenuDigits" visible="true" />
+            <text_area name="EvaDif" x="170" y="41" font="FFVIIMenuDigits" visible="true" />
+            <text_area name="MAtkDif" x="170" y="53" font="FFVIIMenuDigits" visible="true" />
+            <text_area name="MDefDif" x="170" y="65" font="FFVIIMenuDigits" visible="true" />
+            <text_area name="MEvaDif" x="170" y="77" font="FFVIIMenuDigits" visible="true" />
+        </widget>
+        <widget name="List" x="260" y="94" width="100%-260" height="100%-95" visible="true">
+            <prototype name="Window" />
+            <text_area name="Item1" x="15" y="5" font="FFVIIFont" visible="true" />
+            <text_area name="Item2" x="15" y="22" font="FFVIIFont" visible="true" />
+            <text_area name="Item3" x="15" y="39" font="FFVIIFont" visible="true" />
+            <text_area name="Item4" x="15" y="56" font="FFVIIFont" visible="true" />
+            <text_area name="Item5" x="15" y="73" font="FFVIIFont" visible="true" />
+            <text_area name="Item6" x="15" y="90" font="FFVIIFont" visible="true" />
+            <text_area name="Item7" x="15" y="107" font="FFVIIFont" visible="true" />
+            <text_area name="Item8" x="15" y="124" font="FFVIIFont" visible="true" />
+            <sprite name="Cursor" image="images/icons/cursor.png" x="-10" y="8" width="24" height="17" visible="true">
+                <animation name="Position1" length="0" y="0:8" />
+                <animation name="Position2" length="0" y="0:25" />
+                <animation name="Position3" length="0" y="0:42" />
+                <animation name="Position4" length="0" y="0:59" />
+                <animation name="Position5" length="0" y="0:76" />
+                <animation name="Position6" length="0" y="0:93" />
+                <animation name="Position7" length="0" y="0:110" />
+                <animation name="Position8" length="0" y="0:127" />
+            </sprite>
+        </widget>
     </widget>
 </screen>

+ 5 - 1
data/data/scripts.xml

@@ -8,11 +8,15 @@
     <script file="game/growth.lua" />
     <script file="game/items.lua" />
     <script file="game/key_items.lua" />
+    <script file="game/materia.lua" />
     <script file="game/summons.lua" />
     <script file="game/weapons.lua" />
+
+    <script file="scripts/system.lua" />
+
     <!-- TODO: Don't always parse initial savemap when loading is implemented. -->
     <script file="game/initial_savemap.lua" />
-    <script file="scripts/system.lua" />
+
     <script file="scripts/field.lua" />
     <script file="scripts/battle.lua" />
     <script file="scripts/battle_ai.lua" />

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

@@ -116,6 +116,8 @@ Inventory.ORDER = {
     LEAST = 7
 }
 
+Materia = {}
+
 
 
 FFVII = {}

+ 16 - 0
data/data/scripts/debug_data.lua

@@ -9,10 +9,26 @@ Inventory.add_item(42, 3)
 Inventory.add_item(1, 4)
 Inventory.add_item(71, 4)
 Inventory.add_item(259, 2)
+Inventory.add_item(260, 2)
+Inventory.add_item(261, 2)
+Inventory.add_item(262, 2)
+Inventory.add_item(263, 2)
+Inventory.add_item(264, 2)
+Inventory.add_item(265, 2)
+Inventory.add_item(266, 2)
+Inventory.add_item(267, 2)
+Inventory.add_item(268, 2)
 Inventory.add_item(13, 2)
+Inventory.add_item(129, 1)
+Inventory.add_item(130, 1)
+Inventory.add_item(131, 1)
+Inventory.add_item(181, 1)
 Inventory.add_item(14, 4)
 Inventory.add_item(58, 3)
 Inventory.add_item(240, 4)
+Inventory.add_item(289, 4)
+Inventory.add_item(290, 1)
+Inventory.add_item(294, 2)
 Inventory.add_key_item(0)
 Inventory.add_key_item(1)
 Inventory.add_key_item(5)

+ 1 - 1
data/data/scripts/menu/begin_menu.lua

@@ -24,7 +24,7 @@ UiContainer.BeginMenu = {
                 if self.position == 1 then
                     load_field_map_request( "md1stin", "" )
                     console( "camera_free false" )
-                    console( "debug_walkmesh true" )
+                    --console( "debug_walkmesh true" )
                     script:request_end_sync( Script.UI, "BeginMenu", "hide", 0 )
                     FFVII.MenuSettings.pause_available = true
                 elseif self.position == 2 then

+ 382 - 47
data/data/scripts/menu/equip_menu.lua

@@ -14,8 +14,37 @@ UiContainer.EquipMenu = {
 
     --- The type of item currently being selected.
     --
-    -- Inventory.TYPE.WEAPON, Inventory.TYPE.ARMOR, or Inventory.TYPE.ACCESSORY.
-    selecting_slot = 0,
+    -- Inventory.ITEM_TYPE.WEAPON, Inventory.ITEM_TYPE.ARMOR, or Inventory.ITEM_TYPE.ACCESSORY.
+    selecting_slot = Inventory.ITEM_TYPE.WEAPON,
+
+    --- List of owned weapons equipable by the current characters.
+    --
+    -- Only one of each, nevermind the owned quantity.
+    avail_weapons = {},
+
+    --- List of owned armors equipable by the current characters.
+    --
+    -- Only one of each, nevermind the owned quantity.
+    avail_armors = {},
+
+    --- List of owned accessories equipable by the current characters.
+    --
+    -- Only one of each, nevermind the owned quantity.
+    avail_accessories = {},
+
+    --- First visible position in the item list.
+    --
+    -- I.e. How many position the list is scrolled down + 1
+    list_first_visible = 1,
+
+    --- Cursor position in the list.
+    list_position = 1,
+
+    --- Maximum slots in the item list.
+    list_position_total = 8,
+
+    --- highlighted position in the current item list.
+    list_item_selected = 1,
 
     --- Run when the menu is creaded.
     --
@@ -32,6 +61,8 @@ UiContainer.EquipMenu = {
     on_button = function(self, button, event)
         if UiContainer.current_menu == "equip" then
             if UiContainer.current_submenu == "" then
+                -- TODO: Handle L1/R1 (change character)
+                -- TODO: Handle square: Materia menu
                 if button == "Escape" and event == "Press" then
                     UiContainer.current_menu = "main"
                     script:request_end_sync(Script.UI, "EquipMenu", "hide", 0)
@@ -51,16 +82,79 @@ UiContainer.EquipMenu = {
                     self.populate_details(self, false)
                 elseif button == "Enter" then
                     if self.equip_position == 1 then
-                        self.selecting_slot = Inventory.TYPE.WEAPON
-                    elseif self.bar_position == 2 then
-                        self.selecting_slot = Inventory.TYPE.ARMOR
-                    elseif self.bar_position == 3 then
-                        self.selecting_slot = Inventory.TYPE.ACCESSORY
+                        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
                     end
                     self.submenu_select(self)
                 else
                     return 0
                 end
+            elseif UiContainer.current_submenu == "item_select" then
+                local list
+                if self.selecting_slot == Inventory.ITEM_TYPE.WEAPON then
+                    list = self.avail_weapons
+                elseif self.selecting_slot == Inventory.ITEM_TYPE.ARMOR then
+                    list = self.avail_armors
+                elseif self.selecting_slot == Inventory.ITEM_TYPE.ACCESSORY then
+                    list = self.avail_accessories
+                end
+                if button == "Escape" and event == "Press" then
+                    self.submenu_none(self)
+                elseif button == "Down" then
+                    -- Move one position down only if there is a next item
+                    if #(list) <= self.list_item_selected + 1 then
+                        return 0
+                    end
+                    self.list_item_selected = self.list_item_selected + 1
+                    -- Move cursor...
+                    if self.list_position < self.list_position_total then
+                        -- ... by moving it one position down.
+                        self.list_position = self.list_position + 1
+                        ui_manager:get_widget("EquipMenu.Container.List.Cursor"):set_default_animation("Position" .. self.list_position)
+                    else
+                        -- ... by scrolling the list.
+                        self.list_first_visible = self.list_first_visible + 1
+                        self.populate_item_list(self)
+                    end
+                    self.populate_details(self, true)
+                    self.calculate_stat_diffs(self)
+                elseif button == "Up" then
+                    -- Move one position up only if not in the first.
+                    if self.list_item_selected <= 1 then
+                        return 0
+                    end
+                    self.list_item_selected = self.list_item_selected - 1
+                    -- Move cursor...
+                    if self.list_position > 1 then
+                        -- ... by moving it one position up
+                        self.list_position = self.list_position - 1
+                        ui_manager:get_widget("EquipMenu.Container.List.Cursor"):set_default_animation("Position" .. self.list_position)
+                    else
+                        -- ... by scrolling the list.
+                        self.list_first_visible = self.list_first_visible - 1
+                        self.populate_item_list(self)
+                    end
+                    self.populate_details(self, true)
+                    self.calculate_stat_diffs(self)
+                elseif button == "Enter" and event == "Press" then
+                    -- Equip the seleced item
+                    local item_id = nil
+                    if self.selecting_slot == Inventory.ITEM_TYPE.WEAPON then
+                        item_id = self.avail_weapons[self.list_item_selected + 1]
+                    elseif self.selecting_slot == Inventory.ITEM_TYPE.ARMOR then
+                        item_id = self.avail_armors[self.list_item_selected + 1]
+                    elseif self.selecting_slot == Inventory.ITEM_TYPE.ACCESSORY then
+                        item_id = self.avail_accessories[self.list_item_selected + 1]
+                    end
+                    if Characters.equip(self.char_id, item_id) == false then
+                        print("BEEP (error)")
+                        return 0
+                    end
+                    self.submenu_none(self)
+                end
             end
         end
         return 0
@@ -75,6 +169,13 @@ UiContainer.EquipMenu = {
         UiContainer.current_menu = "equip"
         UiContainer.current_submenu = ""
         self.equip_position = 1
+        self.submenu_none(self)
+        return 0;
+    end,
+
+    --- Enters or returns to the main equip menu.
+    submenu_none = function(self)
+        UiContainer.current_submenu = ""
         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")
         -- TODO: Do something for chars 9 and 10
@@ -85,10 +186,121 @@ UiContainer.EquipMenu = {
         else
             ui_manager:get_widget("EquipMenu.Container.Character.AccLbl"):set_text(Game.Items[Characters[self.char_id].accessory].name)
         end
+        self.load_availables(self)
         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, 8 do
+            ui_manager:get_widget("EquipMenu.Container.List.Item" .. tostring(i)):set_text("")
+        end
+    end,
 
-        --self.populate_stats(self)
-        return 0;
+    --- Initializzes the item selection submenu
+    submenu_select = function(self)
+        -- Populate the list
+        self.list_first_visible = 1
+        self.list_position = 1
+        self.list_item_selected = 1
+        if self.populate_item_list(self) == false then
+            print("BEEP")
+            return
+        end
+        UiContainer.current_submenu = "item_select"
+        ui_manager:get_widget("EquipMenu.Container.List.Cursor"):set_visible(true)
+        ui_manager:get_widget("EquipMenu.Container.List.Cursor"):set_default_animation("Position" .. self.list_position)
+        -- TODO: Description and details of first item
+        self.populate_details(self, true)
+        self.calculate_stat_diffs(self)
+    end,
+
+    populate_item_list = function(self)
+        local list
+        if self.selecting_slot == Inventory.ITEM_TYPE.WEAPON then
+            list = self.avail_weapons
+        elseif self.selecting_slot == Inventory.ITEM_TYPE.ARMOR then
+            list = self.avail_armors
+        elseif self.selecting_slot == Inventory.ITEM_TYPE.ACCESSORY then
+            list = self.avail_accessories
+        else
+            for i = 1, 8 do
+                ui_manager:get_widget("EquipMenu.Container.List.Item" .. tostring(i)):set_text("")
+            end
+            return false
+        end
+        if list == nil or #(list) == 0 then
+            -- No items for the list
+            return false
+        end
+        for i = 1, 8 do
+            if #(list) >= i + self.list_first_visible then
+                local name = Game.Items[list[i + self.list_first_visible]].name
+                ui_manager:get_widget("EquipMenu.Container.List.Item" .. tostring(i)):set_text(name)
+            else
+                ui_manager:get_widget("EquipMenu.Container.List.Item" .. tostring(i)):set_text("")
+            end
+        end
+    end,
+
+    --- Loads the list of equipment available for the current character.
+    --
+    -- Currently equiped items are not added, even if more of them are in the inventory.
+    load_availables = function(self)
+        self.avail_weapons = {}
+        self.avail_armors = {}
+        self.avail_accessories = {}
+        for _, item in ipairs(Inventory) do
+            if item.item ~= nil then
+                local item_type = Game.Items[item.item].type
+                if item_type == Inventory.ITEM_TYPE.WEAPON or item_type == Inventory.ITEM_TYPE.ARMOR or item_type == Inventory.ITEM_TYPE.ACCESSORY then
+                    local equip = Game.Items[item.item].users
+                    for _, ch in ipairs(equip) do
+                        if ch == self.char_id then
+                            if item_type == Inventory.ITEM_TYPE.WEAPON then
+                                if item.item ~= Characters[self.char_id].weapon.id then
+                                    table.insert(self.avail_weapons, item.item)
+                                end
+                            elseif item_type == Inventory.ITEM_TYPE.ARMOR then
+                                if item.item ~= Characters[self.char_id].armor.id then
+                                    table.insert(self.avail_armors, item.item)
+                                end
+                            elseif item_type == Inventory.ITEM_TYPE.ACCESSORY then
+                                if item.item ~= Characters[self.char_id].accessory then
+                                    table.insert(self.avail_accessories, item.item)
+                                end
+                            end
+                        end
+                    end
+                end
+            end
+        end
+    end,
+
+    populate_current_stats = function(self)
+        -- TODO: Cap at 255?
+        local stats = Characters[self.char_id].stats
+        ui_manager:get_widget("EquipMenu.Container.Stats.Atk"):set_text(UiContainer.pad_value(stats.atk, 4))
+        ui_manager:get_widget("EquipMenu.Container.Stats.Acc"):set_text(UiContainer.pad_value(stats.acc, 4))
+        ui_manager:get_widget("EquipMenu.Container.Stats.Def"):set_text(UiContainer.pad_value(stats.def, 4))
+        ui_manager:get_widget("EquipMenu.Container.Stats.Eva"):set_text(UiContainer.pad_value(stats.eva, 4))
+        ui_manager:get_widget("EquipMenu.Container.Stats.MAtk"):set_text(UiContainer.pad_value(stats.matk, 4))
+        ui_manager:get_widget("EquipMenu.Container.Stats.MDef"):set_text(UiContainer.pad_value(stats.mdef, 4))
+        ui_manager:get_widget("EquipMenu.Container.Stats.MEva"):set_text(UiContainer.pad_value(stats.meva, 4))
     end,
 
     --- Populated the details of the highlighted item.
@@ -110,51 +322,174 @@ UiContainer.EquipMenu = {
             elseif self.equip_position == 3 then
                 type = Inventory.ITEM_TYPE.ACCESSORY
                 item = Game.Items[Characters[self.char_id].accessory]
-            elseif self.equip_position == 3 and Characters[self.char_id].accessory ~= nil then
-                ui_manager:get_widget("EquipMenu.Container.Help.Help"):set_text(Game.Items[Characters[self.char_id].accessory].description)
-            end
-            -- Show description
-            if item ~= nil then
-                ui_manager:get_widget("EquipMenu.Container.Help.Help"):set_text(item.description)
-            else
-                ui_manager:get_widget("EquipMenu.Container.Help.Help"):set_text("")
+            --elseif self.equip_position == 3 and Characters[self.char_id].accessory ~= nil then
+            --    ui_manager:get_widget("EquipMenu.Container.Help.Help"):set_text(Game.Items[Characters[self.char_id].accessory].description)
             end
-            -- If accessory, hide all details and return
-            if type == Inventory.ITEM_TYPE.ACCESSORY then
-                ui_manager:get_widget("EquipMenu.Container.SlotsEmpty"):set_visible(true)
-                return 0
+        else
+            -- Selecting from the list
+            if self.selecting_slot == Inventory.ITEM_TYPE.WEAPON then
+                item = Game.Items[self.avail_weapons[self.list_item_selected + 1]]
+                type = Inventory.ITEM_TYPE.WEAPON
+            elseif self.selecting_slot == Inventory.ITEM_TYPE.ARMOR then
+                item = Game.Items[self.avail_armors[self.list_item_selected + 1]]
+                type = Inventory.ITEM_TYPE.ARMOR
+            elseif self.selecting_slot == Inventory.ITEM_TYPE.ACCESSORY then
+                item = Game.Items[self.avail_accessories[self.list_item_selected + 1]]
+                type = Inventory.ITEM_TYPE.ACCESSORY
             end
-            ui_manager:get_widget("EquipMenu.Container.SlotsEmpty"):set_visible(false)
-            ui_manager:get_widget("EquipMenu.Container.Slots.Growth1"):set_visible(false)
-            ui_manager:get_widget("EquipMenu.Container.Slots.Growth2"):set_visible(false)
-            ui_manager:get_widget("EquipMenu.Container.Slots.Growth3"):set_visible(false)
-            if item.growth >= 1 and item.growth <= 3 then
-                ui_manager:get_widget("EquipMenu.Container.Slots.Growth" .. item.growth):set_visible(true)
+        end
+
+        -- Show description
+        if item ~= nil then
+            ui_manager:get_widget("EquipMenu.Container.Help.Help"):set_text(item.description)
+        else
+            ui_manager:get_widget("EquipMenu.Container.Help.Help"):set_text("")
+        end
+        -- If accessory, hide all details and return
+        if type == Inventory.ITEM_TYPE.ACCESSORY then
+            ui_manager:get_widget("EquipMenu.Container.SlotsEmpty"):set_visible(true)
+            return 0
+        end
+        ui_manager:get_widget("EquipMenu.Container.SlotsEmpty"):set_visible(false)
+        ui_manager:get_widget("EquipMenu.Container.Slots.Growth0"):set_visible(false)
+        ui_manager:get_widget("EquipMenu.Container.Slots.Growth1"):set_visible(false)
+        ui_manager:get_widget("EquipMenu.Container.Slots.Growth2"):set_visible(false)
+        ui_manager:get_widget("EquipMenu.Container.Slots.Growth3"):set_visible(false)
+        if item.growth >= 0 and item.growth <= 3 then
+            ui_manager:get_widget("EquipMenu.Container.Slots.Growth" .. item.growth):set_visible(true)
+        else
+            ui_manager:get_widget("EquipMenu.Container.Slots.Growth1"):set_visible(true)
+        end
+        for s = 1, 8 do
+            if #(item.slots) < s then
+                ui_manager:get_widget("EquipMenu.Container.Slots.Slot" .. tostring(s)):set_visible(false)
             else
-                ui_manager:get_widget("EquipMenu.Container.Slots.Growth1"):set_visible(true)
-            end
-            for s = 1, 8 do
-                if #(item.slots) < s then
-                    ui_manager:get_widget("EquipMenu.Container.Slots.Slot" .. tostring(s)):set_visible(false)
+                ui_manager:get_widget("EquipMenu.Container.Slots.Slot" .. tostring(s)):set_visible(true)
+                if item.growth == 0 then
+                    ui_manager:get_widget("EquipMenu.Container.Slots.Slot" .. tostring(s)):set_image("images/icons/materia_slot_no_growth.png")
                 else
-                    ui_manager:get_widget("EquipMenu.Container.Slots.Slot" .. tostring(s)):set_visible(true)
-                    if item.growth == 0 then
-                        ui_manager:get_widget("EquipMenu.Container.Slots.Slot" .. tostring(s)):set_image("images/icons/materia_slot_no_growth.png")
-                    else
-                        ui_manager:get_widget("EquipMenu.Container.Slots.Slot" .. tostring(s)):set_image("images/icons/materia_slot.png")
-                    end
+                    ui_manager:get_widget("EquipMenu.Container.Slots.Slot" .. tostring(s)):set_image("images/icons/materia_slot.png")
                 end
-                if s % 2 ~= 0 then
-                    -- At every odd slot, if there is a next slot and its linked, show linker.
-                    if #(item.slots) > s  and item.slots[s] == 1 then
-                        ui_manager:get_widget("EquipMenu.Container.Slots.Link" .. tostring(math.floor(s / 2) + 1)):set_visible(true)
-                    else
-                        ui_manager:get_widget("EquipMenu.Container.Slots.Link" .. tostring(math.floor(s / 2) + 1)):set_visible(false)
-                    end
+            end
+            if s % 2 ~= 0 then
+                -- At every odd slot, if there is a next slot and its linked, show linker.
+                if #(item.slots) > s  and item.slots[s] == 1 then
+                    ui_manager:get_widget("EquipMenu.Container.Slots.Link" .. tostring(math.floor(s / 2) + 1)):set_visible(true)
+                else
+                    ui_manager:get_widget("EquipMenu.Container.Slots.Link" .. tostring(math.floor(s / 2) + 1)):set_visible(false)
                 end
             end
-        elseif from_list == true then
-            --TODO: Implement
+        end
+    end,
+
+    --- Populates the stat difference panel.
+    calculate_stat_diffs = function(self)
+        local item
+        local cur_item
+        if self.selecting_slot == Inventory.ITEM_TYPE.WEAPON then
+            item = Game.Items[self.avail_weapons[self.list_item_selected + 1]]
+            cur_item = Game.Items[Characters[self.char_id].weapon.id]
+        elseif self.selecting_slot == Inventory.ITEM_TYPE.ARMOR then
+            item = Game.Items[self.avail_armors[self.list_item_selected + 1]]
+            cur_item = Game.Items[Characters[self.char_id].armor.id]
+        end
+        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)
+        if item == nil or cur_item == nil then
+            return 0
+        end
+        if item.type == Inventory.ITEM_TYPE.WEAPON then
+            if item.power > cur_item.power then
+                ui_manager:get_widget("EquipMenu.Container.Stats.AtkArw"):set_visible(true)
+                ui_manager:get_widget("EquipMenu.Container.Stats.AtkDif"):set_colour(1, 1, 0)
+                local val = tostring(Characters[self.char_id].stats.atk - cur_item.power + item.power)
+                ui_manager:get_widget("EquipMenu.Container.Stats.AtkDif"):set_text(val)
+                ui_manager:get_widget("EquipMenu.Container.Stats.AtkDif"):set_visible(true)
+            elseif item.power < cur_item.power then
+                ui_manager:get_widget("EquipMenu.Container.Stats.AtkArw"):set_visible(true)
+                ui_manager:get_widget("EquipMenu.Container.Stats.AtkDif"):set_colour(1, 0, 0)
+                local val = tostring(Characters[self.char_id].stats.atk - cur_item.power + item.power)
+                ui_manager:get_widget("EquipMenu.Container.Stats.AtkDif"):set_text(val)
+                ui_manager:get_widget("EquipMenu.Container.Stats.AtkDif"):set_visible(true)
+            end
+            if item.accuracy > cur_item.accuracy then
+                ui_manager:get_widget("EquipMenu.Container.Stats.AccArw"):set_visible(true)
+                ui_manager:get_widget("EquipMenu.Container.Stats.AccDif"):set_colour(1, 1, 0)
+                local val = tostring(Characters[self.char_id].stats.acc - cur_item.accuracy + item.accuracy)
+                ui_manager:get_widget("EquipMenu.Container.Stats.AccDif"):set_text(val)
+                ui_manager:get_widget("EquipMenu.Container.Stats.AccDif"):set_visible(true)
+            elseif item.accuracy < cur_item.accuracy then
+                ui_manager:get_widget("EquipMenu.Container.Stats.AccArw"):set_visible(true)
+                ui_manager:get_widget("EquipMenu.Container.Stats.AccDif"):set_colour(1, 0, 0)
+                local val = tostring(Characters[self.char_id].stats.acc - cur_item.accuracy + item.accuracy)
+                ui_manager:get_widget("EquipMenu.Container.Stats.AccDif"):set_text(val)
+                ui_manager:get_widget("EquipMenu.Container.Stats.AccDif"):set_visible(true)
+            end
+        elseif item.type == Inventory.ITEM_TYPE.ARMOR then
+            if item.defense.physical > cur_item.defense.physical then
+                ui_manager:get_widget("EquipMenu.Container.Stats.DefArw"):set_visible(true)
+                ui_manager:get_widget("EquipMenu.Container.Stats.DefDif"):set_colour(1, 1, 0)
+                local val = tostring(Characters[self.char_id].stats.def - cur_item.defense.physical + item.defense.physical)
+                ui_manager:get_widget("EquipMenu.Container.Stats.DefDif"):set_text(val)
+                ui_manager:get_widget("EquipMenu.Container.Stats.DefDif"):set_visible(true)
+            elseif item.defense.physical < cur_item.defense.physical then
+                ui_manager:get_widget("EquipMenu.Container.Stats.DefArw"):set_visible(true)
+                ui_manager:get_widget("EquipMenu.Container.Stats.DefDif"):set_colour(1, 0, 0)
+                local val = tostring(Characters[self.char_id].stats.def - cur_item.defense.physical + item.defense.physical)
+                ui_manager:get_widget("EquipMenu.Container.Stats.DefDif"):set_text(val)
+                ui_manager:get_widget("EquipMenu.Container.Stats.DefDif"):set_visible(true)
+            end
+            if item.defense.magical > cur_item.defense.magical then
+                ui_manager:get_widget("EquipMenu.Container.Stats.MDefArw"):set_visible(true)
+                ui_manager:get_widget("EquipMenu.Container.Stats.MDefDif"):set_colour(1, 1, 0)
+                local val = tostring(Characters[self.char_id].stats.mdef - cur_item.defense.magical + item.defense.magical)
+                ui_manager:get_widget("EquipMenu.Container.Stats.MDefDif"):set_text(val)
+                ui_manager:get_widget("EquipMenu.Container.Stats.MDefDif"):set_visible(true)
+            elseif item.defense.magical < cur_item.defense.magical then
+                ui_manager:get_widget("EquipMenu.Container.Stats.MDefArw"):set_visible(true)
+                ui_manager:get_widget("EquipMenu.Container.Stats.MDefDif"):set_colour(1, 0, 0)
+                local val = tostring(Characters[self.char_id].stats.mdef - cur_item.defense.magical + item.defense.magical)
+                ui_manager:get_widget("EquipMenu.Container.Stats.MDefDif"):set_text(val)
+                ui_manager:get_widget("EquipMenu.Container.Stats.MDefDif"):set_visible(true)
+            end
+            if item.evasion.physical > cur_item.evasion.physical then
+                ui_manager:get_widget("EquipMenu.Container.Stats.EvaArw"):set_visible(true)
+                ui_manager:get_widget("EquipMenu.Container.Stats.EvaDif"):set_colour(1, 1, 0)
+                local val = tostring(Characters[self.char_id].stats.eva - cur_item.evasion.physical + item.evasion.physical)
+                ui_manager:get_widget("EquipMenu.Container.Stats.EvaDif"):set_text(val)
+                ui_manager:get_widget("EquipMenu.Container.Stats.EvaDif"):set_visible(true)
+            elseif item.evasion.physical < cur_item.evasion.physical then
+                ui_manager:get_widget("EquipMenu.Container.Stats.EvaArw"):set_visible(true)
+                ui_manager:get_widget("EquipMenu.Container.Stats.EvaDif"):set_colour(1, 0, 0)
+                local val = tostring(Characters[self.char_id].stats.eva - cur_item.evasion.physical + item.evasion.physical)
+                ui_manager:get_widget("EquipMenu.Container.Stats.EvaDif"):set_text(val)
+                ui_manager:get_widget("EquipMenu.Container.Stats.EvaDif"):set_visible(true)
+            end
+            if item.evasion.magical > cur_item.evasion.magical then
+                ui_manager:get_widget("EquipMenu.Container.Stats.MEvaArw"):set_visible(true)
+                ui_manager:get_widget("EquipMenu.Container.Stats.MEvaDif"):set_colour(1, 1, 0)
+                local val = tostring(Characters[self.char_id].stats.mdef - cur_item.evasion.magical + item.evasion.magical)
+                ui_manager:get_widget("EquipMenu.Container.Stats.MEvaDif"):set_text(val)
+                ui_manager:get_widget("EquipMenu.Container.Stats.MEvaDif"):set_visible(true)
+            elseif item.evasion.magical < cur_item.evasion.magical then
+                ui_manager:get_widget("EquipMenu.Container.Stats.MEvaArw"):set_visible(true)
+                ui_manager:get_widget("EquipMenu.Container.Stats.MEvaDif"):set_colour(1, 0, 0)
+                local val = tostring(Characters[self.char_id].stats.mdef - cur_item.evasion.magical + item.evasion.magical)
+                ui_manager:get_widget("EquipMenu.Container.Stats.MEvaDif"):set_text(val)
+                ui_manager:get_widget("EquipMenu.Container.Stats.MEvaDif"):set_visible(true)
+            end
         end
     end,
 

+ 8 - 0
data/data/scripts/menu/menu_helpers.lua

@@ -2,6 +2,14 @@ if UiContainer == nil then UiContainer = {} end
 
 print("ADD helpers")
 
+UiContainer.pad_value = function(value, length)
+    local str = tostring(value)
+    while #(str) < length do
+        str = " " .. str
+    end
+    return str
+end
+
 --- Populates a character data.
 --
 -- @param widget Name of the widget with the character data.

+ 274 - 0
data/data/scripts/system.lua

@@ -356,3 +356,277 @@ Inventory.sort = function(criteria)
         )
     end
 end
+
+Characters.recalculate_stats = function(id)
+    -- Initialize
+    local stats = Characters[id].stats
+    stats.str.total = stats.str.base
+    stats.vit.total = stats.vit.base
+    stats.mag.total = stats.mag.base
+    stats.spr.total = stats.spr.base
+    stats.dex.total = stats.dex.base
+    stats.lck.total = stats.lck.base
+    stats.hp.max = stats.hp.base
+    stats.mp.max = stats.mp.base
+    -- Weapon bonus. HP and MP are percents of base, the rest absolutes.
+    local weapon = Game.Items[Characters[id].weapon.id]
+    for stat, value in ipairs(weapon.stats) do
+        if stat == "str" then
+            stats.str.total = stats.str.total + value
+        elseif stat == "vit" then
+            stats.vit.total = stats.vit.total + value
+        elseif stat == "mag" then
+            stats.mag.total = stats.mag.total + value
+        elseif stat == "spr" then
+            stats.spr.total = stats.spr.total + value
+        elseif stat == "dex" then
+            stats.dex.total = stats.dex.total + value
+        elseif stat == "lck" then
+            stats.lck.total = stats.lck.total + value
+        elseif stat == "hp" then
+            stats.hp.max = stats.hp.max + (value * stats.hp.max / 100)
+        elseif stat == "mp" then
+            stats.mp.max = stats.mp.max + (value * stats.mp.max / 100)
+        end
+    end
+    -- Armor bonus. HP and MP are percents of bsae, the rest absolutes.
+    local armor = Game.Items[Characters[id].armor.id]
+    for stat, value in ipairs(armor.stats) do
+        if stat == "str" then
+            stats.str.total = stats.str.total + value
+        elseif stat == "vit" then
+            stats.vit.total = stats.vit.total + value
+        elseif stat == "mag" then
+            stats.mag.total = stats.mag.total + value
+        elseif stat == "spr" then
+            stats.spr.total = stats.spr.total + value
+        elseif stat == "dex" then
+            stats.dex.total = stats.dex.total + value
+        elseif stat == "lck" then
+            stats.lck.total = stats.lck.total + value
+        elseif stat == "hp" then
+            stats.hp.max = stats.hp.max + (value * stats.hp.max / 100)
+        elseif stat == "mp" then
+            stats.mp.max = stats.mp.max + (value * stats.mp.max / 100)
+        end
+    end
+    -- Accesory bonus
+    local accessory = Game.Items[Characters[id].accessory]
+    if (accessory ~= nil) then
+        for stat, value in ipairs(armor.stats) do
+            if stat == "str" then
+                stats.str.total = stats.str.total + value
+            elseif stat == "vit" then
+                stats.vit.total = stats.vit.total + value
+            elseif stat == "mag" then
+                stats.mag.total = stats.mag.total + value
+            elseif stat == "spr" then
+                stats.spr.total = stats.spr.total + value
+            elseif stat == "dex" then
+                stats.dex.total = stats.dex.total + value
+            elseif stat == "lck" then
+                stats.lck.total = stats.lck.total + value
+            elseif stat == "hp" then
+                stats.hp.max = stats.hp.max + (value * stats.hp.max / 100)
+            elseif stat == "mp" then
+                stats.mp.max = stats.mp.max + (value * stats.mp.max / 100)
+            end
+        end
+    end
+    -- Get a list of materias
+    local materias = {}
+    for m = 0, 7 do
+        if Characters[id].weapon.materia[m] ~= nil then
+            table.insert(materias, Game.Materia[Characters[id].weapon.materia[m].id])
+        end
+        if Characters[id].armor.materia[m] ~= nil then
+            table.insert(materias, Game.Materia[Characters[id].weapon.materia[m].id])
+        end
+    end
+    -- Get materia bonuses. All are percents of base.
+    for _, materia in ipairs(materias) do
+        for stat, value in ipairs(materia.stats) do
+            if stat == "str" then
+                stats.str.total = stats.str.total + (value * stats.str.base / 100)
+            elseif stat == "vit" then
+                stats.vit.total = stats.vit.total + (value * stats.vit.base / 100)
+            elseif stat == "mag" then
+                stats.mag.total = stats.mag.total + (value * stats.mag.base / 100)
+            elseif stat == "spr" then
+                stats.spr.total = stats.spr.total + (value * stats.spr.base / 100)
+            elseif stat == "dex" then
+                stats.dex.total = stats.dex.total + (value * stats.dex.base / 100)
+            elseif stat == "lck" then
+                stats.lck.total = stats.lck.total + (value * stats.lck.base / 100)
+            elseif stat == "hp" then
+                stats.hp.max = stats.hp.max + (value * stats.hp.max / 100)
+            elseif stat == "mp" then
+                stats.mp.max = stats.mp.max + (value * stats.mp.max / 100)
+            end
+        end
+    end
+    -- Round down all basic stats
+    stats.str.total = math.floor(stats.str.total)
+    stats.vit.total = math.floor(stats.vit.total)
+    stats.mag.total = math.floor(stats.mag.total)
+    stats.spr.total = math.floor(stats.spr.total)
+    stats.dex.total = math.floor(stats.dex.total)
+    stats.lck.total = math.floor(stats.lck.total)
+    stats.hp.max = math.floor(stats.hp.max)
+    stats.mp.max = math.floor(stats.mp.max)
+
+    if stats.str.total < 1 then
+        stats.str.total = 1
+    end
+    if stats.vit.total < 1 then
+        stats.vit.total = 1
+    end
+    if stats.mag.total < 1 then
+        stats.mag.total = 1
+    end
+    if stats.spr.total < 1 then
+        stats.spr.total = 1
+    end
+    if stats.dex.total < 1 then
+        stats.dex.total = 1
+    end
+    if stats.lck.total < 1 then
+        stats.lck.total = 1
+    end
+    if stats.hp.max < 1 then
+        stats.hp.max = 1
+    end
+    if stats.mp.max < 1 then
+        stats.mp.max = 1
+    end
+
+    -- Calculate composed stats.
+    stats.atk = stats.str.total + weapon.power
+    stats.acc = weapon.accuracy
+    stats.def = stats.vit.total + armor.defense.physical
+    stats.eva = armor.evasion.physical
+    stats.matk = stats.mag.total
+    stats.mdef = stats.spr.total + armor.defense.magical
+    stats.meva = armor.evasion.magical
+
+    -- Cap composed stats. TODO: Max caps?
+    if stats.atk < 1 then
+        stats.atk = 1
+    end
+    if stats.acc < 1 then
+        stats.acc = 1
+    end
+    if stats.def < 1 then
+        stats.def = 1
+    end
+    if stats.eva < 1 then
+        stats.eva = 1
+    end
+    if stats.matk < 1 then
+        stats.matk = 1
+    end
+    if stats.mdef < 1 then
+        stats.mdef = 1
+    end
+    if stats.meva < 1 then
+        stats.meva = 1
+    end
+
+    -- TODO: If HP<->MP materia, invert values.
+
+    -- Cap current HP/MP
+    if stats.hp.current > stats.hp.max then
+        stats.hp.current = stats.hp.max
+    end
+    if stats.mp.current > stats.mp.max then
+        stats.mp.current = stats.mp.max
+    end
+
+    -- Assign
+    Characters[id].stats = stats
+end
+
+--- Equips an item on a character.
+--
+-- Works for weapons, armors and accessories. If there is a previously equiped item, it
+-- will be unequiped and added to the inventory. The equiped item will be removed form the
+-- inventory. For armors and weapons, if the newly equiped item has less materia slots than
+-- the previuos one, remainig materia will be unequiped and added to the materia inventory.
+--
+-- @param char_id ID of the character to equip the item to.
+-- @param item_id ID of the item to equip.
+-- @return True if the item was equipped. False if the character or item don't exist, if
+-- the item is not in the inventory, or if the item is not equippable or not equippable by
+-- the selected character. In any of those cases, an error message will be printed.
+Characters.equip = function(char_id, item_id)
+
+    -- Error checks
+    if Characters[char_id] == nil then
+        print("Tried to equip item on wrong character: " .. tostring(char_id))
+        return false
+    elseif Game.Items[item_id] == nil then
+        print("Tried to equip invalid item: " .. tostring(character))
+        return false
+    elseif Inventory.get_item_quantity(item_id) < 1 then
+        print("Tried to equip item not in inventory: " .. item_id .. " (" .. Game.Items[item.id].name .. ")")
+        return false
+    end
+    local type = Game.Items[item_id].type
+    if type ~= Inventory.ITEM_TYPE.WEAPON and type ~= Inventory.ITEM_TYPE.ARMOR and type ~= Inventory.ITEM_TYPE.ACCESSORY then
+        print("Tried to equip non-equipable item: " .. item_id .. " (" .. Game.Items[item.id].name .. ")")
+        return false
+    end
+    local equipable = false
+    for _, u in ipairs(Game.Items[item_id].users) do
+        if u == char_id then
+            equipable = true
+            break
+        end
+    end
+    if equipable == false then
+        print("Tried to equip item " .. tostring(item_id) .. " (" .. Game.Items[item.id].name .. ") on a non valid character: " .. tostring(char_id) .. " (" .. Characters[char_id].name .. ")")
+        return false
+    end
+
+    -- All is good, equip.
+    if type == Inventory.ITEM_TYPE.ACCESSORY then
+        -- Equip accessory. If any was equipped, send it to the inventory.
+        if Characters[char_id].accessory ~= nil then
+            Inventory.add_item(Characters[char_id].accessory, 1)
+        end
+        Characters[char_id].accessory = item_id
+        Inventory.remove_item(item_id, 1)
+    elseif type == Inventory.ITEM_TYPE.WEAPON then
+        -- Weapon. Remove excess materia, send previuos one to the inventory.
+        for m = 0, 7 do
+            if Characters[char_id].weapon.materia[i] ~= nil and #(Game.Items[item_id].slots) < m then
+                -- Materia to inventory
+                Materia.add(Characters[char_id].weapon.materia[i].id, Characters[char_id].weapon.materia[i].ap)
+                Characters[char_id].weapon.materia[i] = nil
+            end
+        end
+        Inventory.add_item(Characters[char_id].weapon.id, 1)
+        Characters[char_id].weapon.id = item_id
+        Inventory.remove_item(item_id, 1)
+    elseif type == Inventory.ITEM_TYPE.ARMOR then
+        -- Armor. Remove excess materia, send previuos one to the inventory.
+        for m = 0, 7 do
+            if Characters[char_id].armor.materia[i] ~= nil and #(Game.Items[item_id].slots) < m then
+                -- Materia to inventory
+                Materia.add(Characters[char_id].armor.materia[i].id, Characters[char_id].armor.materia[i].ap)
+                Characters[char_id].armor.materia[i] = nil
+            end
+        end
+        Inventory.add_item(Characters[char_id].armor.id, 1)
+        Characters[char_id].armor.id = item_id
+        Inventory.remove_item(item_id, 1)
+    else
+        return false
+    end
+    return true
+end
+
+Materia.add = function(id, ap)
+    ap = ap or 0
+    -- TODO
+end

+ 10 - 10
data/data/texts/english/menu.xml

@@ -56,19 +56,19 @@
     <text name="ItemMenuOrder8">Least</text>
 
     <text name="EquipMenuTitle">Equip</text>
-    <text name="EquipMenuSlots"> Slots:</text>
-    <text name="EquipMenuGrowth">Growth:</text>
+    <text name="EquipMenuSlots"><colour value="0 0.8 1">Slots:</colour></text>
+    <text name="EquipMenuGrowth"><colour value="0 0.8 1">Growth:</colour></text>
     <text name="EquipMenuGrowth1">Normal</text>
     <text name="EquipMenuGrowth2">Double</text>
     <text name="EquipMenuGrowth3">Triple</text>
-    <text name="EquipMenuAtk">        Attack:</text>
-    <text name="EquipMenuAcc">      Accuracy:</text>
-    <text name="EquipMenuDef">       Defense:</text>
-    <text name="EquipMenuEva">       Evasion:</text>
-    <text name="EquipMenuMAtk"> Magic attack:</text>
-    <text name="EquipMenuMDef">Magic defense:</text>
-    <text name="EquipMenuMEva">Magic evasion:</text>
-
+    <text name="EquipMenuAtk"><colour value="0 0.8 1">Attack:</colour></text>
+    <text name="EquipMenuAcc"><colour value="0 0.8 1">Accuracy:</colour></text>
+    <text name="EquipMenuDef"><colour value="0 0.8 1">Defense:</colour></text>
+    <text name="EquipMenuEva"><colour value="0 0.8 1">Evasion:</colour></text>
+    <text name="EquipMenuMAtk"><colour value="0 0.8 1">Magic attack:</colour></text>
+    <text name="EquipMenuMDef"><colour value="0 0.8 1">Magic defense:</colour></text>
+    <text name="EquipMenuMEva"><colour value="0 0.8 1">Magic evasion:</colour></text>
+    <text name="EquipMenuArrow"><colour value="0 0.6 0.8">-></colour></text>
 
     <text name="CharacterDataName">#Name#</text>
     <text name="CharacterDataLv">LV</text>