Prechádzať zdrojové kódy

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 rokov pred
rodič
commit
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>