浏览代码

Save and load settings, fix loading, and read controls through game events

Settings in the savemap
- The savemap saves the config menu settings by name (<Settings> in the
  XML) and the scripts decide which ones there are. New savemap_manager
  functions: set_setting, is_slot_setting_set, get_slot_setting and
  get_slot_window_colour. Remove the unused Settings struct and the stub
  numeric settings.
- save_game and load_game use ConfigMenu:save_settings and load_settings.
  Loading applies the volumes, the movement and the window colours, keeps
  the current settings if the game was saved before they existed, and
  clamps damaged values.
- Fix the XML loader of the window colours: the bottom-left corner was
  never loaded (typo) and the maximum value of 255 was rejected.
- Savemap::Write creates the save folder if it is missing and logs an
  error if it can't write, instead of silently not saving.

Loading games
- Rewrite load_game as the inverse of save_game. Inventory, materia and key
  items are 1-based in the game and 0-based in the savemap, the character
  materia used wrong arguments and a binding that doesn't exist, limit
  levels were off by one, and the characters are now updated in place.
- Fix recalculate_stats crashing with armor materia in a slot where the
  weapon has none (it looked in the weapon).

Stats
- Equipment and materia bonuses were never applied: they were walked with
  ipairs although the stats are keyed by name. Use pairs, and read the
  accessory bonuses from the accessory instead of the armor.
- The installer wrote the stat ID instead of the bonus amount for weapons,
  armor and accessories.

Controls
- New InputManager::IsGameEventPressed, to poll the controls through the
  game events and honor the keys chosen in the controller settings.
- is_key_on translates the pad masks to game events instead of fixed keys.
  Menu, Extra, Select, Start, L2 and R2 are now mapped as well.
- The world map moves with the walk events and leaves a vehicle with
  CANCEL.
- Only letters and the space bar reach the scripts as raw keys. The name
  menu uses Config.CONTROLS, and a letter that is also the OK key types
  instead of confirming.
- While the name menu is not enabled (NameMenu.enabled), the characters
  get their default names and the game goes on.
Iñigo Valentin 12 小时之前
父节点
当前提交
56a76f64b0

+ 2 - 1
data/data/scripts/battle/battle_ui.lua

@@ -310,7 +310,8 @@ UiContainer.BattleUi = {
     --- Handles button events.
     --
     -- For the current submenu, handles directional keys, enter and escape events.
-    -- @param button Pressed button string key. "Up", "Left", "Enter" and "Escape" are handled.
+    -- @param button Game event (the directions, OK, cancel...) or, for the debug keys, the name of
+    -- the key.
     -- @param event Trigger event. Normally, "Press".
     on_button = function(self, button, event)
         if self.active == false then do return 0 end end

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

@@ -242,6 +242,11 @@ name_character = function(id)
         print("Tried to rename invalid character with ID " .. tostring(id))
         return 0
     end
+    -- While the name menu is not enabled, the character just gets its default name.
+    if not UiContainer.NameMenu.enabled then
+        UiContainer.NameMenu:apply_default_name(id)
+        return 0
+    end
     UiContainer.NameMenu.id = id
     UiContainer.NameMenu.chocobo = false
     script:request(Script.UI, "NameMenu", "show", 0)

+ 63 - 4
data/data/scripts/menu/config_menu.lua

@@ -86,10 +86,8 @@ local ROWS = {
 --
 -- TODO: These are placeholders. They don't change the game yet. Each option must be connected to
 -- its setting as it is implemented.
--- TODO: Apply the volumes (apply_volumes), the movement (apply_movement) and the window colours
--- when the settings are loaded.
--- TODO: Save and load all of them with the savemap (see the TODO about the settings in save.lua and
--- Savemap.cpp).
+-- The settings are saved with the game (save_settings) and loaded with it (load_settings). The keys of
+-- the controller are not: they are saved in controls.cfg.
 MenuSettings.config = MenuSettings.config or {
     cursor = 1,         -- 1: Initial, 2: Memory.
     atb = 2,            -- 1: Active, 2: Recommended, 3: Wait.
@@ -149,6 +147,13 @@ local function set_keys(action, keys)
     end
 end
 
+--- Settings (fields of MenuSettings.config) saved with the game, apart from the window colours.
+-- The values are numbers: choices start at 1, and sliders and volumes at 0.
+local SAVED_SETTINGS = {
+    "music_volume", "sound_volume", "cursor", "atb", "battle_speed", "battle_message",
+    "field_message", "movement", "camera", "magic_order",
+}
+
 --- Handlers of the buttons that change the value of the colour under the cursor.
 local function colour_buttons()
     local buttons = {}
@@ -490,6 +495,60 @@ UiContainer.ConfigMenu = UiContainer.Menu.new{
         UiContainer.play_sound("Cursor")
     end,
 
+    --- Gives the settings to the savemap, to be saved with the game. See save_game in save.lua.
+    save_settings = function(self)
+        local config = MenuSettings.config
+        for _, key in ipairs(SAVED_SETTINGS) do
+            savemap_manager:set_setting(key, config[key])
+        end
+        local c = config.window_colours
+        savemap_manager:set_window_colours(
+            c[1][1], c[1][2], c[1][3], c[2][1], c[2][2], c[2][3],
+            c[3][1], c[3][2], c[3][3], c[4][1], c[4][2], c[4][3]
+        )
+    end,
+
+    --- Loads the settings of a saved game and applies them. See load_game in save.lua.
+    --
+    -- Games saved before the settings were saved don't have them, and don't change the ones the
+    -- player has. Values that are out of range (a damaged file) are brought inside it.
+    --
+    -- @param slot Slot of the saved game (0-15).
+    load_settings = function(self, slot)
+        if not savemap_manager:is_slot_setting_set(slot, SAVED_SETTINGS[1]) then return end
+        local config = MenuSettings.config
+        -- Range of the values of each setting.
+        local ranges = {
+            music_volume = { 0, VOLUME_MAX }, sound_volume = { 0, VOLUME_MAX },
+        }
+        for _, row in ipairs(ROWS) do
+            if row.kind == KIND.SLIDER then ranges[row.key] = { 0, SLIDER_MAX }
+            elseif row.key ~= nil then ranges[row.key] = { 1, row.count } end
+        end
+        for _, key in ipairs(SAVED_SETTINGS) do
+            if savemap_manager:is_slot_setting_set(slot, key) then
+                local range = ranges[key]
+                config[key] = math.max(range[1], math.min(range[2], savemap_manager:get_slot_setting(slot, key)))
+            end
+        end
+        -- Corners: top-left, top-right, bottom-right and bottom-left, as in the savemap.
+        for corner = 1, 4 do
+            for channel = 1, 3 do
+                config.window_colours[corner][channel] = math.max(COLOUR_MIN, math.min(COLOUR_MAX,
+                    savemap_manager:get_slot_window_colour(slot, corner - 1, channel - 1)))
+            end
+        end
+        self:apply_all()
+    end,
+
+    --- Applies all the settings that act on the game right away: volumes, movement and the
+    -- colours of the windows. The rest are read by the game when it needs them.
+    apply_all = function(self)
+        self:apply_volumes()
+        self:apply_movement()
+        self:apply_window_colours()
+    end,
+
     --- Applies the movement setting (walk or run by default) to the player.
     apply_movement = function(self)
         entity_manager:set_player_default_run(MenuSettings.config.movement == 2)

+ 47 - 18
data/data/scripts/menu/name_menu.lua

@@ -1,6 +1,12 @@
 if UiContainer == nil then UiContainer = {} end
 
 --- The name menu.
+--- Is a key one that types a character in the name menu? The letters (their name is one character)
+-- and the space bar.
+local function is_typing_key(key)
+    return key ~= nil and (key == "Space" or #key == 1)
+end
+
 UiContainer.NameMenu = {
 
     --- Indicates if a chocobo is being renamed instead of a character.
@@ -12,6 +18,12 @@ UiContainer.NameMenu = {
     --- Default name.
     default_name = "",
 
+    --- Is the name menu shown when the game asks to name a character?
+    --
+    -- TODO: Turn it on when the menu works with the controller settings (see is_typing_key). While
+    -- it is off, the characters keep their default names and the game goes on.
+    enabled = false,
+
     --- Currently input-ed name.
     name = "",
 
@@ -45,23 +57,26 @@ UiContainer.NameMenu = {
 
     --- Handles button events.
     --
-    -- For the current submenu, handles directional keys, enter and escape events.
-    -- @param button Pressed button string key. "Up", "Left", "Enter" and "Escape" are handled.
+    -- For the current submenu, handles the directions, OK, and the keys that type letters.
+    -- @param button Game event (the directions, OK...) or, for the letters and the space bar,
+    -- the name of the key.
     -- @param event Trigger event. Normally, "Press".
+    -- @param source_key Name of the key that produced the event.
     on_button = function(self, button, event, source_key)
         if UiContainer.current_menu == "name" then
-            if button == Config.ACTIONS.CONFIRM and source_key == "Space" and UiContainer.current_submenu == "" then
+            -- A letter or the space bar that is also the OK key types, it doesn't confirm.
+            if button == Config.ACTIONS.CONFIRM and is_typing_key(source_key) and UiContainer.current_submenu == "" then
                 return 0
             end
             if UiContainer.current_submenu == "confirm" then
-                if button == "Right" then
+                if button == Config.CONTROLS.RIGHT then
                     audio_manager:play_sound("Cursor")
                     self.confirm_position = self.confirm_position + 1
                     if self.confirm_position > self.confirm_position_total then
                         self.confirm_position = 1
                     end
                     ui_manager:get_widget("NameMenu.Container.Confirm.Cursor"):set_default_animation("Position" .. self.confirm_position)
-                elseif button == "Left" then
+                elseif button == Config.CONTROLS.LEFT then
                     audio_manager:play_sound("Cursor")
                     self.confirm_position = self.confirm_position - 1
                     if self.confirm_position < 1 then
@@ -83,26 +98,26 @@ UiContainer.NameMenu = {
                 local character_index
                 if button == "Space" then
                     character_index = string.find(self.letters, " ")
-                else
+                elseif is_typing_key(button) then
                     character_index = string.find(self.letters, button)
                 end
 
                 -- Handle buttons.
-                if button == "Down" then
+                if button == Config.CONTROLS.DOWN then
                     audio_manager:play_sound("Cursor")
                     self.options_position = self.options_position + 1
                     if self.options_position > self.options_position_total then
                         self.options_position = 1
                     end
                     ui_manager:get_widget("NameMenu.Container.Options.Cursor"):set_default_animation("Position" .. self.options_position)
-                elseif button == "Up" then
+                elseif button == Config.CONTROLS.UP then
                     audio_manager:play_sound("Cursor")
                     self.options_position = self.options_position - 1
                     if self.options_position < 1 then
                         self.options_position = self.options_position_total
                     end
                     ui_manager:get_widget("NameMenu.Container.Options.Cursor"):set_default_animation("Position" .. self.options_position)
-                elseif button == "Left" then
+                elseif button == Config.CONTROLS.LEFT then
                     if self.text_cursor > 1 then
                         self.text_cursor = self.text_cursor - 1
                         self.draw_cursor(self)
@@ -110,7 +125,7 @@ UiContainer.NameMenu = {
                     else
                         audio_manager:play_sound("Error")
                     end
-                elseif button == "Right" then
+                elseif button == Config.CONTROLS.RIGHT then
                     if self.text_cursor < self.max_characters then
                         audio_manager:play_sound("Cursor")
                         self.text_cursor = self.text_cursor + 1
@@ -164,6 +179,26 @@ UiContainer.NameMenu = {
         return 0
     end,
 
+    --- Gets the default name of a character.
+    --
+    -- @param id ID of the character.
+    get_default_name = function(self, id)
+        local name = Characters[id].name
+        -- HACK: Hack for Ex-Soldier default. Can this be extracted from menu.lgp?
+        if string.lower(name) == "ex-soldier" then return "Cloud" end
+        return name
+    end,
+
+    --- Gives a character its default name, without showing the menu.
+    --
+    -- Used when the menu is not enabled.
+    --
+    -- @param id ID of the character.
+    apply_default_name = function(self, id)
+        Characters[id].name = self:get_default_name(id)
+        export_character_names()
+    end,
+
     --- Opens the name menu.
     --
     -- Populates and updates displayed data before opening. It also pauses the game. Shows a fade
@@ -180,14 +215,8 @@ UiContainer.NameMenu = {
         ui_manager:get_widget("NameMenu.Container.Confirm.Cursor"):set_default_animation("Position" .. self.confirm_position)
         ui_manager:get_widget("NameMenu.Container.Options.Cursor"):set_default_animation("Position" .. self.options_position)
         ui_manager:get_widget("NameMenu.Container.Character.Portrait"):set_image("characters/" .. tostring(self.id) .. ".png")
-        if (string.lower(Characters[self.id].name) == "ex-soldier") then
-            -- HACK: Hack for Ex-Soldier default. Can this be extracted from menu.lgp?
-            self.default_name = "Cloud"
-            self.name = "Cloud"
-        else
-            self.default_name = Characters[self.id].name
-            self.name = Characters[self.id].name
-        end
+        self.default_name = self:get_default_name(self.id)
+        self.name = self.default_name
         self.text_cursor = math.min(#(self.name) + 1, self.max_characters)
         self.populate_name(self)
         self.draw_cursor(self)

+ 112 - 137
data/data/scripts/save.lua

@@ -14,13 +14,8 @@ end
 -- @param force If false, exisiting game data will not be overwritten if control keys are different.
 save_game = function(slot, force)
     savemap_manager:set_control_key(ControlKey)
-    -- Window colours of the config menu: red, green and blue (0-255) of the top-left, top-right,
-    -- bottom-right and bottom-left corners.
-    local colours = MenuSettings.config.window_colours
-    savemap_manager:set_window_colours(
-        colours[1][1], colours[1][2], colours[1][3], colours[2][1], colours[2][2], colours[2][3],
-        colours[3][1], colours[3][2], colours[3][3], colours[4][1], colours[4][2], colours[4][3]
-    )
+    -- Settings of the config menu, and the colours of the windows.
+    UiContainer.ConfigMenu:save_settings()
     savemap_manager:set_money(Inventory.money)
     savemap_manager:set_game_time(123456) -- TODO Actual data
     savemap_manager:set_countdown_time(0) -- TODO Actual data
@@ -52,7 +47,6 @@ save_game = function(slot, force)
     local x, y, z = player:get_position()
 
     savemap_manager:set_location(x, y, z, player:get_move_triangle_id(), player:get_rotation(), System["MapChanger"].current_map_name, System["MapChanger"].location_name) -- TODO Actual data
-    -- TODO: Save the rest of the settings of the config menu (MenuSettings.config).
     for i = 0, 11 do
         if Characters[i] ~= nil and Characters[i].char_id ~= nil then
             local c = Characters[i]
@@ -99,7 +93,97 @@ save_game = function(slot, force)
     savemap_manager:save(slot, force or true)
 end
 
---- Laods a game.
+--- Loads the characters from a saved game. Everything save_game saves of them.
+--
+-- The characters that already exist are updated in place, so the data that isn't saved stays.
+--
+-- @param slot The slot to load (0-15).
+local function load_characters(slot)
+    -- The savemap has room for 11 characters.
+    for c = 0, 10 do
+        if savemap_manager:get_slot_character_char_id(slot, c) ~= -1 then
+            local character = Characters[c] or {}
+            Characters[c] = character
+            character.char_id = savemap_manager:get_slot_character_char_id(slot, c)
+            character.name = savemap_manager:get_slot_character_name(slot, c)
+            character.level = savemap_manager:get_slot_character_level(slot, c)
+            character.enabled = savemap_manager:is_slot_character_enabled(slot, c)
+            character.locked = savemap_manager:is_slot_character_locked(slot, c)
+            character.kills = savemap_manager:get_slot_character_kills(slot, c)
+            character.back_row = savemap_manager:is_slot_character_back_row(slot, c)
+            character.exp = savemap_manager:get_slot_character_exp(slot, c)
+            character.exp_to_next = savemap_manager:get_slot_character_exp_to_next(slot, c)
+
+            -- Stats: strength, vitality, magic, spirit, dexterity and luck, then HP and MP.
+            character.stats = character.stats or {}
+            local stat_names = { "str", "vit", "mag", "spr", "dex", "lck" }
+            for index, name in ipairs(stat_names) do
+                character.stats[name] = {
+                    base = savemap_manager:get_slot_character_stat_base(slot, c, index - 1),
+                    bonus = savemap_manager:get_slot_character_stat_extra(slot, c, index - 1),
+                    total = 0,
+                }
+            end
+            -- For HP and MP the "extra" value is the current one. The maximum is calculated.
+            character.stats.hp = {
+                current = savemap_manager:get_slot_character_stat_extra(slot, c, 6),
+                base = savemap_manager:get_slot_character_stat_base(slot, c, 6),
+                max = 65535,
+            }
+            character.stats.mp = {
+                current = savemap_manager:get_slot_character_stat_extra(slot, c, 7),
+                base = savemap_manager:get_slot_character_stat_base(slot, c, 7),
+                max = 65535,
+            }
+
+            -- Limit breaks. Levels are 0-3 in the savemap, and 1-4 in the game.
+            character.limit = {
+                current = savemap_manager:get_slot_character_limit_level(slot, c),
+                bar = savemap_manager:get_slot_character_limit_bar(slot, c),
+                learned = {},
+                uses = {},
+            }
+            for l = 1, 4 do
+                character.limit.learned[l] = {
+                    [1] = savemap_manager:is_slot_character_limit_learned(slot, c, l - 1, 0),
+                    [2] = savemap_manager:is_slot_character_limit_learned(slot, c, l - 1, 1),
+                }
+                character.limit.uses[l] = savemap_manager:get_slot_character_limit_uses(slot, c, l - 1)
+            end
+
+            -- Equipment and its materia (slots 0-7).
+            local accessory = savemap_manager:get_slot_character_accessory_id(slot, c)
+            character.accessory = (accessory >= 0) and accessory or nil
+            character.weapon = { id = savemap_manager:get_slot_character_weapon_id(slot, c), materia = {} }
+            character.armor = { id = savemap_manager:get_slot_character_armor_id(slot, c), materia = {} }
+            for _, part in ipairs({ "weapon", "armor" }) do
+                local is_weapon = (part == "weapon")
+                for m = 0, 7 do
+                    local id = savemap_manager:get_slot_character_materia_id(slot, c, is_weapon, m)
+                    if id >= 0 then
+                        local materia = { id = id, ap = savemap_manager:get_slot_character_materia_ap(slot, c, is_weapon, m) }
+                        if id == Materia.ENEMY_SKILL_ID then
+                            materia.ap = 0
+                            materia.skills = {}
+                            for s = 1, 24 do
+                                materia.skills[s] = savemap_manager:get_slot_character_materia_e_skill_learned(slot, c, is_weapon, m, s - 1)
+                            end
+                        end
+                        character[part].materia[m] = materia
+                    end
+                end
+            end
+
+            -- TODO: Status
+            character.status = {}
+            Characters.recalculate_stats(c)
+        end
+    end
+end
+
+--- Loads a game.
+--
+-- It loads everything save_game saves, and then goes to the field where the game was saved.
 --
 -- @param slot The slot to load (0-15).
 load_game = function(slot)
@@ -109,8 +193,8 @@ load_game = function(slot)
             Banks[b][a] = savemap_manager:get_slot_data(slot, b, a)
         end
     end
-    -- TODO: Window colours and the rest of the settings of the config menu (MenuSettings.config),
-    -- then apply the colours with ui_manager:set_window_colours().
+    -- Settings of the config menu, and the colours of the windows.
+    UiContainer.ConfigMenu:load_settings(slot)
     Inventory.money = savemap_manager:get_slot_money(slot)
     -- TODO: Game time
     -- TODO: Countdown
@@ -120,25 +204,27 @@ load_game = function(slot)
             Party[p] = nil
         end
     end
-    for i = 0, MAX_INVENTORY_SLOTS do
-        Inventory[i] = {item = 0, quantity = 0}
-        Inventory[i].quantity = savemap_manager:get_slot_item_at_pos_qty(slot, i)
-        if Inventory[i].quantity > 0 then
-            Inventory[i].id = savemap_manager:get_slot_item_at_pos_id(slot, i)
+    -- The inventory and the materia are 1-based in the game, and 0-based in the savemap.
+    for i = 1, MAX_INVENTORY_SLOTS do
+        local quantity = savemap_manager:get_slot_item_at_pos_qty(slot, i - 1)
+        if quantity > 0 then
+            Inventory[i] = {
+                item = savemap_manager:get_slot_item_at_pos_id(slot, i - 1),
+                quantity = quantity,
+            }
         else
             Inventory[i] = nil
         end
     end
-    for i = 0, MAX_MATERIA_SLOTS do
-        Materia[i] = {id = -1, ap = 0}
-        Materia[i].id = savemap_manager:get_slot_materia_at_pos_id(slot, i)
-        if Materia[i].id >= 0 then
-            Materia[i].ap = savemap_manager:get_slot_materia_at_pos_ap(slot, i)
-            if Materia[i].id == Materia.ENEMY_SKILL_ID then
+    for i = 1, MAX_MATERIA_SLOTS do
+        local id = savemap_manager:get_slot_materia_at_pos_id(slot, i - 1)
+        if id >= 0 then
+            Materia[i] = { id = id, ap = savemap_manager:get_slot_materia_at_pos_ap(slot, i - 1) }
+            if id == Materia.ENEMY_SKILL_ID then
                 Materia[i].ap = 0
                 Materia[i].skills = {}
                 for s = 1, 24 do
-                    Materia[i].skills[s] = savemap_manager:is_slot_materia_at_pos_e_skill_learned(slot, i, s - 1)
+                    Materia[i].skills[s] = savemap_manager:is_slot_materia_at_pos_e_skill_learned(slot, i - 1, s - 1)
                 end
             end
         else
@@ -146,122 +232,11 @@ load_game = function(slot)
         end
     end
     -- TODO: Stash (same as materia)
-    for i = 0, MAX_KEY_SLOTS do
-        Inventory.key[i] = savemap_manager:get_slot_key_item(slot, i)
-    end
-    -- Characters
-    for c = 0, 11 do
-        Characters[c].char_id = savemap_manager:get_slot_character_char_id(slot, c)
-        if Characters[c].char_id == -1 then
-            Characters[c] = nil
-        else
-            Characters[c] = {}
-            Characters[c].name = savemap_manager:get_slot_character_name(slot, c)
-            Characters[c].level = savemap_manager:get_slot_character_level(slot, c)
-            Characters[c].enabled = savemap_manager:is_slot_character_enabled(slot, c)
-            Characters[c].locked = savemap_manager:is_slot_character_locked(slot, c)
-            Characters[c].kills = savemap_manager:get_slot_character_kills(slot, c)
-            Characters[c].back_row = savemap_manager:is_slot_character_back_row(slot, c)
-            Characters[c].exp = savemap_manager:get_slot_character_exp(slot, c)
-            Characters[c].exp_to_next = savemap_manager:get_slot_character_exp_to_next(slot, c)
-            Characters[c].stats = {}
-            Characters[c].stats.str = {
-                base = savemap_manager:get_slot_character_stat_base(slot, c, 0),
-                bonus = savemap_manager:get_slot_character_stat_extra(slot, c, 0),
-                total = 0
-            }
-            Characters[c].stats.vit = {
-                base = savemap_manager:get_slot_character_stat_base(slot, c, 1),
-                bonus = savemap_manager:get_slot_character_stat_extra(slot, c, 1),
-                total = 0
-            }
-            Characters[c].stats.mag = {
-                base = savemap_manager:get_slot_character_stat_base(slot, c, 2),
-                bonus = savemap_manager:get_slot_character_stat_extra(slot, c, 2),
-                total = 0
-            }
-            Characters[c].stats.spr = {
-                base = savemap_manager:get_slot_character_stat_base(slot, c, 3),
-                bonus = savemap_manager:get_slot_character_stat_extra(slot, c, 3),
-                total = 0
-            }
-            Characters[c].stats.dex = {
-                base = savemap_manager:get_slot_character_stat_base(slot, c, 4),
-                bonus = savemap_manager:get_slot_character_stat_extra(slot, c, 4),
-                total = 0
-            }
-            Characters[c].stats.lck = {
-                base = savemap_manager:get_slot_character_stat_base(slot, c, 5),
-                bonus = savemap_manager:get_slot_character_stat_extra(slot, c, 5),
-                total = 0
-            }
-            Characters[c].stats.hp = {
-                current = savemap_manager:get_slot_character_stat_extra(slot, c, 6),
-                base = savemap_manager:get_slot_character_stat_base(slot, c, 6),
-                max = 65535
-            }
-            Characters[c].stats.mp = {
-                current = savemap_manager:get_slot_character_stat_extra(slot, c, 7),
-                base = savemap_manager:get_slot_character_stat_base(slot, c, 7),
-                max = 65535
-            }
-            Characters[c].limit = {}
-            Characters[c].limit.current = savemap_manager:get_slot_character_limit_level(slot, c)
-            Characters[c].limit.bar = savemap_manager:get_slot_character_limit_bar(slot, c)
-            Characters[c].limit.learned = {}
-            Characters[c].limit.uses = {}
-            for l = 1, 4 do
-                Characters[c].limit.learned[l] = {
-                    [1] = savemap_manager:is_slot_character_limit_learned(slot, c, l, 0),
-                    [2] = savemap_manager:is_slot_character_limit_learned(slot, c, l, 1)
-                }
-                Characters[c].limit.uses[l] = savemap_manager:get_slot_character_limit_uses(slot, c, l)
-            end
-            Characters[c].accessory = savemap_manager:get_slot_character_accessory_id(slot, c)
-            Characters[c].weapon = {}
-            Characters[c].armor = {}
-            Characters[c].weapon.id = savemap_manager:get_slot_character_weapon_id(slot, c)
-            Characters[c].armor.id = savemap_manager:get_slot_character_armor_id(slot, c)
-            Characters[c].weapon.materia = {}
-            Characters[c].armor.materia = {}
-            for i = 0, 8 do
-                Characters[c].weapon.materia[i] = {id = -1, ap = 0}
-                Characters[c].armor.materia[i] = {id = -1, ap = 0}
-                Characters[c].weapon.materia[i].id = savemap_manager:get_slot_character_materia_id(slot, c, true, i)
-                Characters[c].armor.materia[i].id = savemap_manager:get_slot_character_materia_id(slot, c, false, i)
-                if Characters[c].weapon.materia[i].id >= 0 then
-                    Characters[c].weapon.materia[i].ap = savemap_manager:get_slot_character_materia_ap(slot, i, true, i)
-                    if Characters[c].weapon.materia[i].id == Materia.ENEMY_SKILL_ID then
-                        Characters[c].weapon.materia[i].ap = 0
-                        Characters[c].weapon.materia[i].skills = {}
-                        for s = 1, 24 do
-                            Characters[c].weapon.materia[i].skills[s] = savemap_manager:is_slot_character_materia_e_skill_learned(slot, i, c, true, s - 1)
-                        end
-                    end
-                else
-                    Characters[c].weapon.materia[i] = nil
-                end
-                if Characters[c].armor.materia[i].id >= 0 then
-                    Characters[c].armor.materia[i].ap = savemap_manager:get_slot_character_materia_ap(slot, i, false, i)
-                    if Characters[c].armor.materia[i].id == Materia.ENEMY_SKILL_ID then
-                        Characters[c].armor.materia[i].ap = 0
-                        Characters[c].armor.materia[i].skills = {}
-                        for s = 1, 24 do
-                            Characters[c].armor.materia[i].skills[s] = savemap_manager:is_slot_character_materia_e_skill_learned(slot, i, c, false, s - 1)
-                        end
-                    end
-                else
-                    Characters[c].armor.materia[i] = nil
-                end
-            end
-            -- TODO: Status
-            Characters[c].status = {}
-            Characters.recalculate_stats(c)
-        end
-
+    for i = 1, MAX_KEY_SLOTS do
+        Inventory.key[i] = savemap_manager:get_slot_key_item(slot, i - 1)
     end
+    load_characters(slot)
     export_character_names()
-    -- TODO: Settings
     -- TODO: Location
     -- TODO: Update character stats
     -- TODO: Handle world map when implemented

+ 7 - 6
data/data/scripts/system.lua

@@ -444,9 +444,10 @@ Characters.recalculate_stats = function(id)
     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.
+    -- Weapon bonus. HP and MP are percents of base, the rest absolutes. The stats are keyed by name,
+    -- so they are walked with pairs (ipairs only walks the array part and found nothing).
     local weapon = Game.Items[Characters[id].weapon.id]
-    for stat, value in ipairs(weapon.stats) do
+    for stat, value in pairs(weapon.stats or {}) do
         if stat == "str" then
             stats.str.total = stats.str.total + value
         elseif stat == "vit" then
@@ -467,7 +468,7 @@ Characters.recalculate_stats = function(id)
     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
+    for stat, value in pairs(armor.stats or {}) do
         if stat == "str" then
             stats.str.total = stats.str.total + value
         elseif stat == "vit" then
@@ -489,7 +490,7 @@ Characters.recalculate_stats = function(id)
     -- Accesory bonus
     local accessory = Game.Items[Characters[id].accessory]
     if (accessory ~= nil) then
-        for stat, value in ipairs(armor.stats) do
+        for stat, value in pairs(accessory.stats or {}) do
             if stat == "str" then
                 stats.str.total = stats.str.total + value
             elseif stat == "vit" then
@@ -516,12 +517,12 @@ Characters.recalculate_stats = function(id)
             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])
+            table.insert(materias, Game.Materia[Characters[id].armor.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
+        for stat, value in pairs(materia.stats or {}) do
             if stat == "str" then
                 stats.str.total = stats.str.total + (value * stats.str.base / 100)
             elseif stat == "vit" then

+ 9 - 7
src/core/EntityManager.cpp

@@ -923,14 +923,16 @@ bool EntityManager::IsKeyOn(unsigned int key_code){
     // Some scene where the player can't move but must press a button?
     if (player_lock_) return false;
     // The key code of the scripts is a mask of the pad buttons, and the check is true if any of
-    // them is pressed. Translate each to its key. For example 32 is "Circle/Action", the space bar.
-    // TODO: Get values from config.lua
-    static const struct{unsigned int mask; int key;} KEYS[] = {
-      {4, OIS::KC_PGUP}, {8, OIS::KC_PGDOWN}, {32, OIS::KC_SPACE}, {64, OIS::KC_ESCAPE},
-      {4096, OIS::KC_UP}, {8192, OIS::KC_RIGHT}, {16384, OIS::KC_DOWN}, {32768, OIS::KC_LEFT}
+    // them is pressed. Translate each to its game event (see bind_game_event in config.cfg), so
+    // the keys chosen in the controller settings are the ones that count. For example 32 is
+    // "Circle/Action", the OK button.
+    static const struct{unsigned int mask; const char* event;} BUTTONS[] = {
+      {1, "L2"}, {2, "R2"}, {4, "L1"}, {8, "R1"}, {16, "MENU"}, {32, "CONFIRM"}, {64, "CANCEL"},
+      {128, "EXTRA"}, {256, "SELECT"}, {2048, "START"},
+      {4096, "Up"}, {8192, "Right"}, {16384, "Down"}, {32768, "Left"}
     };
-    for (const auto& entry : KEYS)
-        if ((key_code & entry.mask) && InputManager::getSingleton().IsButtonPressed(entry.key))
+    for (const auto& entry : BUTTONS)
+        if ((key_code & entry.mask) && InputManager::getSingleton().IsGameEventPressed(entry.event))
             return true;
     return false;
 }

+ 16 - 0
src/core/InputManager.cpp

@@ -143,6 +143,22 @@ void InputManager::Update(){
 
 bool InputManager::IsButtonPressed(int button) const{return button_state_[button];}
 
+bool InputManager::IsGameEventPressed(const Ogre::String& event) const{
+    if (Console::getSingleton().IsVisible() == true) return false;
+    for (const BindGameEventInfo& bind : bind_game_events_){
+        if (bind.event != event || bind.buttons.empty()) continue;
+        bool all_pressed = true;
+        for (const int button : bind.buttons){
+            if (IsButtonPressed(button) == false){
+                all_pressed = false;
+                break;
+            }
+        }
+        if (all_pressed == true) return true;
+    }
+    return false;
+}
+
 void InputManager::GetInputEvents(InputEventArray& input_events){
     input_events = event_queue_;
     event_queue_.clear();

+ 11 - 0
src/core/InputManager.h

@@ -152,6 +152,17 @@ class InputManager : public Manager, public Ogre::Singleton<InputManager>{
          */
         bool IsButtonPressed(int button) const;
 
+        /**
+         * Checks if a game event is active: all the keys of any of its bindings are being pressed.
+         *
+         * Use it, instead of checking keys, to poll the controls of the game, so the keys chosen
+         * in the controller settings are used. It is false while the console is open.
+         *
+         * @param[in] event The game event, for example "CONFIRM" or "walk_left".
+         * @return true if the event is active, false otherwise.
+         */
+        bool IsGameEventPressed(const Ogre::String& event) const;
+
         /**
          * Retrieves the current input events.
          *

+ 59 - 25
src/core/Savemap.cpp

@@ -13,6 +13,7 @@
  * GNU General Public License for more details.
  */
 
+#include <boost/filesystem.hpp>
 #include <string>
 #include <iostream>
 #include <algorithm>
@@ -144,6 +145,7 @@ void Savemap::operator = (const Savemap &to_copy){
     }
     for (int i = 0; i < 11; i ++) characters_[i] = to_copy.characters_[i];
     for (int i = 0; i < 3; i ++) party_[i] = to_copy.party_[i];
+    settings_ = to_copy.settings_;
 }
 
 void Savemap::SetData(const unsigned int bank, const unsigned int address, const int value){
@@ -253,8 +255,13 @@ void Savemap::SetLocation(
     location_.name = name;
 }
 
-void Savemap::SetSetting(const unsigned int key, const unsigned int value){
-    // TODO : Implements when settings are working.
+void Savemap::SetSetting(const std::string& key, const int value){settings_[key] = value;}
+
+bool Savemap::HasSetting(const std::string& key) const{return settings_.count(key) > 0;}
+
+int Savemap::GetSetting(const std::string& key) const{
+    const auto it = settings_.find(key);
+    return (it == settings_.end()) ? 0 : it->second;
 }
 
 void Savemap::SetCharacterInfo(
@@ -485,11 +492,6 @@ std::string Savemap::GetLocationField(){return location_.field;}
 
 std::string Savemap::GetLocationName(){return location_.name;}
 
-int Savemap::GetSetting(const unsigned int key){
-    // TODO: Implement.
-    return 0;
-}
-
 int Savemap::GetCharacterCharId(const unsigned int id){
     if (id >= MAX_CHARACTERS) return -1;
     else return characters_[id].char_id;
@@ -824,10 +826,17 @@ void Savemap::Write(int slot, std::string file_name){
     }
     container->LinkEndChild(stash.release());
 
-    // TODO: Settings. Save all the options of the config menu (MenuSettings.config in the scripts):
-    // window colours, sound, controller, cursor mode, ATB mode, battle speed, battle message
-    // speed, field message speed, movement, camera angle and magic order. Savemap::Settings and the
-    // window colours already exist, but they are not written to the XML nor read back.
+    // Settings (config menu). The window colours are saved apart.
+    if (!settings_.empty()){
+        std::unique_ptr<TiXmlElement> settings(new TiXmlElement("Settings"));
+        for (const auto& setting : settings_){
+            std::unique_ptr<TiXmlElement> element(new TiXmlElement("Setting"));
+            element->SetAttribute("name", setting.first);
+            element->SetAttribute("value", setting.second);
+            settings->LinkEndChild(element.release());
+        }
+        container->LinkEndChild(settings.release());
+    }
 
     // Characters
     std::unique_ptr<TiXmlElement> characters(new TiXmlElement("Characters"));
@@ -1009,7 +1018,14 @@ void Savemap::Write(int slot, std::string file_name){
 
     // Save
     xml.LinkEndChild(container.release());
-    xml.SaveFile(file_name);
+    // The folder of the saved games may not exist yet (a fresh installation): create it, or the
+    // game would not be saved.
+    const boost::filesystem::path path(file_name);
+    if (path.has_parent_path()){
+        boost::system::error_code error;
+        boost::filesystem::create_directories(path.parent_path(), error);
+    }
+    if (!xml.SaveFile(file_name)) LOG_ERROR("Can't write the saved game \"" + file_name + "\".");
     slot_ = slot;
     return;
 
@@ -1055,51 +1071,69 @@ void Savemap::Read(std::string file_name){
                     if (position == "top_left"){
                         colour = -1;
                         colour_elm->QueryIntAttribute("red", &colour);
-                        if (colour >= 0 && colour < MAX_COLOUR) window_colours_[0][0] = colour;
+                        if (colour >= 0 && colour <= MAX_COLOUR) window_colours_[0][0] = colour;
                         colour = -1;
                         colour_elm->QueryIntAttribute("green", &colour);
-                        if (colour >= 0 && colour < MAX_COLOUR) window_colours_[0][1] = colour;
+                        if (colour >= 0 && colour <= MAX_COLOUR) window_colours_[0][1] = colour;
                         colour = -1;
                         colour_elm->QueryIntAttribute("blue", &colour);
-                        if (colour >= 0 && colour < MAX_COLOUR) window_colours_[0][2] = colour;
+                        if (colour >= 0 && colour <= MAX_COLOUR) window_colours_[0][2] = colour;
                     }
                     else if (position == "top_right"){
                         colour = -1;
                         colour_elm->QueryIntAttribute("red", &colour);
-                        if (colour >= 0 && colour < MAX_COLOUR) window_colours_[1][0] = colour;
+                        if (colour >= 0 && colour <= MAX_COLOUR) window_colours_[1][0] = colour;
                         colour = -1;
                         colour_elm->QueryIntAttribute("green", &colour);
-                        if (colour >= 0 && colour < MAX_COLOUR) window_colours_[1][1] = colour;
+                        if (colour >= 0 && colour <= MAX_COLOUR) window_colours_[1][1] = colour;
                         colour = -1;
                         colour_elm->QueryIntAttribute("blue", &colour);
-                        if (colour >= 0 && colour < MAX_COLOUR) window_colours_[1][2] = colour;
+                        if (colour >= 0 && colour <= MAX_COLOUR) window_colours_[1][2] = colour;
                     }
                     else if (position == "bottom_right"){
                         colour = -1;
                         colour_elm->QueryIntAttribute("red", &colour);
-                        if (colour >= 0 && colour < MAX_COLOUR) window_colours_[2][0] = colour;
+                        if (colour >= 0 && colour <= MAX_COLOUR) window_colours_[2][0] = colour;
                         colour = -1;
                         colour_elm->QueryIntAttribute("green", &colour);
-                        if (colour >= 0 && colour < MAX_COLOUR) window_colours_[2][1] = colour;
+                        if (colour >= 0 && colour <= MAX_COLOUR) window_colours_[2][1] = colour;
                         colour = -1;
                         colour_elm->QueryIntAttribute("blue", &colour);
-                        if (colour >= 0 && colour < MAX_COLOUR) window_colours_[2][2] = colour;
+                        if (colour >= 0 && colour <= MAX_COLOUR) window_colours_[2][2] = colour;
                     }
-                    else if (position == "bottol_left"){
+                    else if (position == "bottom_left"){
                         colour = -1;
                         colour_elm->QueryIntAttribute("red", &colour);
-                        if (colour >= 0 && colour < MAX_COLOUR) window_colours_[3][0] = colour;
+                        if (colour >= 0 && colour <= MAX_COLOUR) window_colours_[3][0] = colour;
                         colour = -1;
                         colour_elm->QueryIntAttribute("green", &colour);
-                        if (colour >= 0 && colour < MAX_COLOUR) window_colours_[3][1] = colour;
+                        if (colour >= 0 && colour <= MAX_COLOUR) window_colours_[3][1] = colour;
                         colour = -1;
                         colour_elm->QueryIntAttribute("blue", &colour);
-                        if (colour >= 0 && colour < MAX_COLOUR) window_colours_[3][2] = colour;
+                        if (colour >= 0 && colour <= MAX_COLOUR) window_colours_[3][2] = colour;
                     }
                 }
                 colour_node = colour_node->NextSibling();
             }
         }
+        else if (node->Type() == TiXmlNode::TINYXML_ELEMENT && node->ValueStr() == "Settings"){
+            TiXmlNode* setting_node = node->FirstChild();
+            while (setting_node != nullptr){
+                if (
+                  setting_node->Type() == TiXmlNode::TINYXML_ELEMENT
+                  && setting_node->ValueStr() == "Setting"
+                ){
+                    std::string name;
+                    int value = 0;
+                    TiXmlElement* setting_elm = setting_node->ToElement();
+                    if (
+                      setting_elm->QueryStringAttribute("name", &name) == TIXML_SUCCESS
+                      && setting_elm->QueryIntAttribute("value", &value) == TIXML_SUCCESS
+                    ) settings_[name] = value;
+                }
+                setting_node = setting_node->NextSibling();
+            }
+        }
         else if (node->Type() == TiXmlNode::TINYXML_ELEMENT && node->ValueStr() == "Money"){
             int money = -1;
             node->ToElement()->QueryIntAttribute("quantity", &money);

+ 21 - 110
src/core/Savemap.h

@@ -15,6 +15,7 @@
 
 #pragma once
 
+#include <map>
 #include <string>
 #include "common/TypeDefine.h"
 
@@ -211,11 +212,26 @@ class Savemap{
         /**
          * Sets a setting value in the savemap.
          *
-         * @param[in] key Setting key.
+         * @param[in] key Name of the setting.
          * @param[in] value Setting value.
-         * @todo Does nothing, implement when settings are working.
          */
-        void SetSetting(const unsigned int key, const unsigned int value);
+        void SetSetting(const std::string& key, const int value);
+
+        /**
+         * Checks if the savemap has a setting.
+         *
+         * @param[in] key Name of the setting.
+         * @return True if the setting was saved. Savemaps from older versions have none.
+         */
+        bool HasSetting(const std::string& key) const;
+
+        /**
+         * Gets the value of a setting.
+         *
+         * @param[in] key Name of the setting.
+         * @return The value of the setting, or 0 if the savemap doesn't have it.
+         */
+        int GetSetting(const std::string& key) const;
 
         /**
          * Sets a character basic information in the savemap.
@@ -537,15 +553,6 @@ class Savemap{
          */
         std::string GetLocationName();
 
-        /**
-         * Retrieves a setting.
-         *
-         * @param[in] key Setting key.
-         * @return The setting value.
-         * @todo Implement and document properly.
-         */
-        int GetSetting(const unsigned int key);
-
         /**
          * Retrieves the char ID of a character.
          *
@@ -941,102 +948,6 @@ class Savemap{
          */
         static constexpr unsigned int MAX_COLOUR = 255;
 
-
-        /**
-         * Game settings.
-         */
-        struct Settings{
-
-            /**
-             * Magic sorting methods.
-             */
-            enum class MagicOrder{
-
-                /**
-                 * Restore-Attack-Indirect.
-                 */
-                RAI,
-
-                /**
-                 * Restore-Indirect-Attack.
-                 */
-                RIA,
-
-                /**
-                 * Attack-Indirect-Restore.
-                 */
-                AIR,
-
-                /**
-                 * Attack-Restore-Indirect.
-                 */
-                ARI,
-
-                /**
-                 * Indirect-Restore-Attack.
-                 */
-                IRA,
-
-                /**
-                 * Indirect-Attack-Restore.
-                 */
-                IAR,
-            };
-
-            /**
-             * ATB modes.
-             */
-            enum class AtbMode{
-
-                /**
-                 * Active battle.
-                 */
-                ACTIVE,
-
-                /**
-                 * Mixed mode.
-                 */
-                RECOMMENDED,
-
-                /**
-                 * Paused mode.
-                 */
-                WAIT
-            };
-
-            /**
-             * Battle speed.
-             */
-            unsigned int battle_speed;
-
-            /**
-             * Battle message speed.
-             */
-            unsigned int battle_msg_speed;
-
-            /**
-             * Field message speed
-             */
-            unsigned int msg_speed;
-
-            /**
-             * Magic order.
-             */
-            MagicOrder order;
-
-            /**
-             * ATB mode.
-             */
-            AtbMode atb_mode;
-
-            /**
-             * Indicates if the battle help window is to be shown.
-             */
-            bool batle_help;
-
-            // TODO: Key mappings, when implemented.
-        };
-
         /**
          * Location information.
          *
@@ -1373,9 +1284,9 @@ class Savemap{
         Location location_;
 
         /**
-         * Game settings.
+         * Settings of the game (config menu), by name. The scripts decide which ones there are.
          */
-        Settings settings_;
+        std::map<std::string, int> settings_;
 
         /**
          * Memory banks.

+ 14 - 8
src/core/SavemapHandler.cpp

@@ -179,9 +179,21 @@ void SavemapHandler::SetLocation(
     current_savemap_->SetLocation(x, y, z, triangle, angle, std::string(field), std::string(name));
 }
 
-void SavemapHandler::SetSetting(const unsigned int key, const unsigned int value){
+void SavemapHandler::SetSetting(const char* key, const int value){
     if (current_savemap_ == nullptr) current_savemap_ = new Savemap();
-    current_savemap_->SetSetting(key, value);
+    current_savemap_->SetSetting(std::string(key), value);
+}
+
+bool SavemapHandler::IsSlotSettingSet(const unsigned int slot, const char* key){
+    if (slot >= MAX_SAVE_SLOTS) return false;
+    if (savemaps_read_ == false) ReadSavemaps();
+    return saved_savemaps_[slot]->HasSetting(std::string(key));
+}
+
+int SavemapHandler::GetSlotSetting(const unsigned int slot, const char* key){
+    if (slot >= MAX_SAVE_SLOTS) return 0;
+    if (savemaps_read_ == false) ReadSavemaps();
+    return saved_savemaps_[slot]->GetSetting(std::string(key));
 }
 
 void SavemapHandler::SetCharacterInfo(
@@ -393,12 +405,6 @@ std::string SavemapHandler::GetSlotLocationName(const unsigned int slot){
     return saved_savemaps_[slot]->GetLocationName();
 }
 
-int SavemapHandler::GetSlotSetting(const unsigned int slot, const unsigned int key){
-    if (slot >= MAX_SAVE_SLOTS) return 0;
-    if (savemaps_read_ == false) ReadSavemaps();
-    return saved_savemaps_[slot]->GetSetting(key);
-}
-
 int SavemapHandler::GetSlotCharacterCharId(const unsigned int slot, const unsigned int id){
     if (slot >= MAX_SAVE_SLOTS) return 0;
     if (savemaps_read_ == false) ReadSavemaps();

+ 19 - 11
src/core/SavemapHandler.h

@@ -252,7 +252,25 @@ class SavemapHandler : public Ogre::Singleton<SavemapHandler>{
          * @param[in] value Setting value.
          * @todo Does nothing, implement when settings are working.
          */
-        void SetSetting(const unsigned int key, const unsigned int value);
+        void SetSetting(const char* key, const int value);
+
+        /**
+         * Checks if a saved savemap has a setting.
+         *
+         * @param[in] slot The slot to read from.
+         * @param[in] key Name of the setting.
+         * @return True if the slot has the setting. False if it doesn't, or the slot is invalid.
+         */
+        bool IsSlotSettingSet(const unsigned int slot, const char* key);
+
+        /**
+         * Gets a setting from a saved savemap.
+         *
+         * @param[in] slot The slot to read from.
+         * @param[in] key Name of the setting.
+         * @return The value of the setting, or 0 if the slot doesn't have it.
+         */
+        int GetSlotSetting(const unsigned int slot, const char* key);
 
         /**
          * Sets a character basic information in the current savemap.
@@ -607,16 +625,6 @@ class SavemapHandler : public Ogre::Singleton<SavemapHandler>{
          */
         std::string GetSlotLocationName(const unsigned int slot);
 
-        /**
-         * Retrieves a setting from a saved savemap.
-         *
-         * @param[in] slot The slot to read from.
-         * @param[in] key Setting key.
-         * @return The setting value.
-         * @todo Implement and document properly.
-         */
-        int GetSlotSetting(const unsigned int slot, const unsigned int key);
-
         /**
          * Retrieves the char ID of a character from a saved savemap.
          *

+ 4 - 7
src/core/ScriptManager.cpp

@@ -87,14 +87,11 @@ void ScriptManager::Input(const VGears::Event& event){
     if (
       (event.type == VGears::ET_KEY_PRESS || event.type == VGears::ET_KEY_REPEAT_WAIT)
       && (
+        // Only the keys that scripts read by themselves reach them as raw keys: the letters and the
+        // space bar, to type in the name menu and for the debug keys of the battle. Everything else
+        // goes through the game events of config.cfg, so it can be remapped.
         // TODO: Do this the other way: block keys NOT TO pass to scripts.
-        event.param1 == OIS::KC_RETURN
-        || event.param1 == OIS::KC_ESCAPE
-        || event.param1 == OIS::KC_SPACE
-        || event.param1 == OIS::KC_LCONTROL
-        || event.param1 == OIS::KC_PGUP
-        || event.param1 == OIS::KC_PGDOWN
-        || event.param1 == OIS::KC_BACK
+        event.param1 == OIS::KC_SPACE
         || event.param1 == OIS::KC_A || event.param1 == OIS::KC_B || event.param1 == OIS::KC_C
         || event.param1 == OIS::KC_D || event.param1 == OIS::KC_E || event.param1 == OIS::KC_F
         || event.param1 == OIS::KC_G || event.param1 == OIS::KC_H || event.param1 == OIS::KC_I

+ 3 - 1
src/core/ScriptManagerBinds.h

@@ -392,6 +392,9 @@ void ScriptManager::InitBinds(){
         // Registered under the same name in the original bindings.
         &SavemapHandler::GetSlotWindowCornerColourComponent
       ),
+      "get_slot_window_colour", &SavemapHandler::GetSlotWindowCornerColourComponent,
+      "is_slot_setting_set", &SavemapHandler::IsSlotSettingSet,
+      "get_slot_setting", &SavemapHandler::GetSlotSetting,
       "get_slot_money", &SavemapHandler::GetSlotMoney,
       "get_slot_game_time", &SavemapHandler::GetSlotGameTime,
       "get_slot_countdown_time", &SavemapHandler::GetSlotCountdownTime,
@@ -416,7 +419,6 @@ void ScriptManager::InitBinds(){
       "get_slot_location_angle", &SavemapHandler::GetSlotLocationAngle,
       "get_slot_location_field", &SavemapHandler::GetSlotLocationField,
       "get_slot_location_name", &SavemapHandler::GetSlotLocationName,
-      "get_slot_setting", &SavemapHandler::GetSlotSetting,
       "get_slot_character_char_id", &SavemapHandler::GetSlotCharacterCharId,
       "get_slot_character_name", &SavemapHandler::GetSlotCharacterName,
       "get_slot_character_level", &SavemapHandler::GetSlotCharacterLevel,

+ 3 - 3
src/installer/KernelDataInstaller.cpp

@@ -1032,7 +1032,7 @@ void KernelDataInstaller::WriteWeapons(std::string file_name){
         file << "},\n    stats = {";
         for (StatBonus s : weapon.stat_bonus){
             file << "\n        " << GetBasicStatName(s.stat) << " = "
-              << static_cast<int>(s.stat) << ",";
+              << static_cast<int>(s.bonus) << ",";
         }
         if (weapon.stat_bonus.size() > 0) file << "\n    },\n";
         else file << "},\n";
@@ -1225,7 +1225,7 @@ void KernelDataInstaller::WriteArmors(std::string file_name){
         file << "},\n    stats = {";
         for (StatBonus s : armor.stat_bonus){
             file << "\n        " << GetBasicStatName(s.stat) << " = "
-              << static_cast<int>(s.stat) << ",";
+              << static_cast<int>(s.bonus) << ",";
         }
         if (armor.stat_bonus.size() > 0) file << "\n    },\n";
         else file << "},\n";
@@ -1429,7 +1429,7 @@ void KernelDataInstaller::WriteAccessories(std::string file_name){
         file << "},\n    stats = {";
         for (StatBonus s : accessory.stat_bonus){
             file << "\n        " << GetBasicStatName(s.stat) << " = "
-              << static_cast<int>(s.stat) << ",";
+              << static_cast<int>(s.bonus) << ",";
         }
         if (accessory.stat_bonus.size() > 0) file << "\n    },\n";
         else file << "},\n";

+ 7 - 6
src/modules/worldmap/WorldmapModule.cpp

@@ -1134,8 +1134,7 @@ namespace VGears {
 
         const bool highwind_active = active_entity == highwind_entity_;
       const bool vehicle_active = current_vehicle_ != VehicleType::ON_FOOT;
-        const bool cancel_pressed
-          = InputManager::getSingleton().IsButtonPressed(OIS::KC_ESCAPE);
+        const bool cancel_pressed = InputManager::getSingleton().IsGameEventPressed("CANCEL");
         if (vehicle_active && cancel_pressed && !cancel_was_pressed_){
             ScriptManager::getSingleton().ScriptRequest(
               ScriptManager::ENTITY, active_entity->GetName().c_str(), "unboard", 0
@@ -1155,10 +1154,12 @@ namespace VGears {
             // Nothing: no movement.
         }
         else{
-            if (InputManager::getSingleton().IsButtonPressed(OIS::KC_LEFT)) movement.x -= 1.0f;
-            if (InputManager::getSingleton().IsButtonPressed(OIS::KC_RIGHT)) movement.x += 1.0f;
-            if (InputManager::getSingleton().IsButtonPressed(OIS::KC_UP)) movement.z -= 1.0f;
-            if (InputManager::getSingleton().IsButtonPressed(OIS::KC_DOWN)) movement.z += 1.0f;
+            // The game events of the movement, so the keys of the controller settings are used.
+            InputManager& input = InputManager::getSingleton();
+            if (input.IsGameEventPressed("walk_left")) movement.x -= 1.0f;
+            if (input.IsGameEventPressed("walk_right")) movement.x += 1.0f;
+            if (input.IsGameEventPressed("walk_up")) movement.z -= 1.0f;
+            if (input.IsGameEventPressed("walk_down")) movement.z += 1.0f;
         }
         bool is_walking = false;
         if (!movement.isZeroLength()){