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

Field fixes from a play-through: scripts, party, text, scheduling, backgrounds

Fixes found while playing the converted fields from mds7 to mds5_4.

Installer / decompiler
- Bit tests (IFUB types 6-8) use bit_and/bit_xor/bit_or ~= 0 instead of &, ^, | (LuaJIT has no bit operators). IFSW constants are signed (65504 -> -32), and the invalid IFKEY bits are cleared, not added.
- A jump into the middle of an if block no longer produces an unreachable label (the if becomes a conditional goto); a goto whose label is not in the function now ends the script. All installed scripts load in LuaJIT.
- Scripts that share an entry point become aliases of the first one (a line that starts a scene when crossed, REQ to a shared slot).
- New opcodes: XYI (position only), GETAXY, PRTYP, PRTYM, BGPDH, BGSCR. PC uses its own operand (the character), not the CHAR model slot. GOLDU/GOLDD read the amount from the right bytes, GOLDD removes gil.
- Director hack places the player at the map entry point (place_player_at_map_entry); the 256-dialog count of some fields no longer wraps to 0; <pause_ok /> is well formed; direction bytes use /256.
- Tiles are written with their background layer.

Engine
- Ladders and scripted moves: finish with a tolerance, ignore the entity and walkmesh probes for scripted walks, keep going over the walkmesh edge while the player is locked, re-attach the player to the walkmesh when its triangle is stale, zero-length linear moves no longer give a NaN position.
- set_player_entity no longer forces visible/solid; set_position_xy added; IsKeyOn works while the player is locked (minigames).
- Scripts: the talk script has its own priority (4); a script that yields without waiting lets the next one of its entity run; missing scripts to continue are logged quietly.
- UI: dialogs are drawn over fades and movies; the WMODE mode belongs to the script that set it.
- Background2D: per-layer parallax and auto-scroll (BGPDH/BGSCR); the far layers keep their order in the depth buffer.
- Player entity changes are logged.

Data
- Escaped & and < in the font files, field.lua (getaxy, place_player_at_map_entry), system.lua (money_to_banks).
Iñigo Valentin 5 часов назад
Родитель
Сommit
a59cf51bc4
33 измененных файлов с 926 добавлено и 185 удалено
  1. 2 2
      data/data/fonts/ffvii.xml
  2. 2 2
      data/data/fonts/ffvii_en.xml
  3. 1 1
      data/data/fonts/ffvii_menu.xml
  4. 1 1
      data/data/scripts/data.lua
  5. 52 22
      data/data/scripts/field.lua
  6. 7 7
      data/data/scripts/system.lua
  7. 25 0
      doc/TODO.md
  8. 124 18
      src/core/Background2D.cpp
  9. 113 1
      src/core/Background2D.h
  10. 25 3
      src/core/DialogsManager.cpp
  11. 14 0
      src/core/DialogsManager.h
  12. 5 0
      src/core/Entity.cpp
  13. 6 0
      src/core/Entity.h
  14. 91 26
      src/core/EntityManager.cpp
  15. 52 38
      src/core/ScriptManager.cpp
  16. 8 0
      src/core/ScriptManager.h
  17. 3 0
      src/core/ScriptManagerBinds.h
  18. 6 0
      src/core/UiManager.cpp
  19. 17 0
      src/core/UiManager.h
  20. 7 6
      src/core/XmlBackground2DFile.cpp
  21. 5 1
      src/installer/FieldDataInstaller.cpp
  22. 12 2
      src/installer/FieldTextWriter.cpp
  23. 1 1
      src/installer/WorldInstaller.cpp
  24. 79 4
      src/installer/decompiler/field/FieldCodeGenerator.cpp
  25. 52 2
      src/installer/decompiler/field/FieldDisassembler.cpp
  26. 6 0
      src/installer/decompiler/field/FieldEngine.cpp
  27. 35 0
      src/installer/decompiler/field/FieldEngine.h
  28. 38 2
      src/installer/decompiler/field/instruction/FieldBackgroundInstruction.cpp
  29. 4 0
      src/installer/decompiler/field/instruction/FieldBackgroundInstruction.h
  30. 38 12
      src/installer/decompiler/field/instruction/FieldCondJumpInstruction.cpp
  31. 45 4
      src/installer/decompiler/field/instruction/FieldModelInstruction.cpp
  32. 16 0
      src/installer/decompiler/field/instruction/FieldModelInstruction.h
  33. 34 30
      src/installer/decompiler/field/instruction/FieldPartyInstruction.cpp

+ 2 - 2
data/data/fonts/ffvii.xml

@@ -5,7 +5,7 @@
     <char name="#" x="36" y="0" width="12" height="12" pre="0" post="-2" />
     <char name="$" x="48" y="0" width="12" height="12" pre="0" post="-5" />
     <char name="%" x="60" y="0" width="12" height="12" pre="0" post="-2" />
-    <char name="&" x="72" y="0" width="12" height="12" pre="0" post="-3" />
+    <char name="&amp;" x="72" y="0" width="12" height="12" pre="0" post="-3" />
     <char name="'" x="84" y="0" width="12" height="12" pre="0" post="-9" />
     <char name="(" x="96" y="0" width="12" height="12" pre="2" post="-4" />
     <char name=")" x="108" y="0" width="12" height="12" pre="2" post="-4" />
@@ -27,7 +27,7 @@
     <char name="9" x="48" y="12" width="12" height="12" pre="0" post="-4" />
     <char name=":" x="60" y="12" width="12" height="12" pre="2" post="-7" />
     <char name=";" x="72" y="12" width="12" height="12" pre="0" post="-8" />
-    <char name="<" x="84" y="12" width="12" height="12" pre="0" post="-5" />
+    <char name="&lt;" x="84" y="12" width="12" height="12" pre="0" post="-5" />
     <char name="=" x="96" y="12" width="12" height="12" pre="0" post="-4" />
     <char name=">" x="108" y="12" width="12" height="12" pre="0" post="-5" />
     <char name="?" x="120" y="12" width="12" height="12" pre="1" post="-5" />

+ 2 - 2
data/data/fonts/ffvii_en.xml

@@ -5,7 +5,7 @@
     <char name="#" x="36" y="0" width="12" height="12" pre="0" post="-2" />
     <char name="$" x="48" y="0" width="12" height="12" pre="0" post="-5" />
     <char name="%" x="60" y="0" width="12" height="12" pre="0" post="-2" />
-    <char name="&" x="72" y="0" width="12" height="12" pre="0" post="-3" />
+    <char name="&amp;" x="72" y="0" width="12" height="12" pre="0" post="-3" />
     <char name="'" x="84" y="0" width="12" height="12" pre="0" post="-9" />
     <char name="(" x="96" y="0" width="12" height="12" pre="2" post="-4" />
     <char name=")" x="108" y="0" width="12" height="12" pre="2" post="-4" />
@@ -27,7 +27,7 @@
     <char name="9" x="48" y="12" width="12" height="12" pre="0" post="-4" />
     <char name=":" x="60" y="12" width="12" height="12" pre="2" post="-9" />
     <char name=";" x="72" y="12" width="12" height="12" pre="0" post="-8" />
-    <char name="<" x="84" y="12" width="12" height="12" pre="0" post="-5" />
+    <char name="&lt;" x="84" y="12" width="12" height="12" pre="0" post="-5" />
     <char name="=" x="96" y="12" width="12" height="12" pre="0" post="-4" />
     <char name=">" x="108" y="12" width="12" height="12" pre="0" post="-5" />
     <char name="?" x="120" y="12" width="12" height="12" pre="0" post="-6" />

+ 1 - 1
data/data/fonts/ffvii_menu.xml

@@ -162,7 +162,7 @@
     <char name="%" x="24" y="160" width="8" height="16" pre="0" post="0" />
     <char name="/" x="32" y="160" width="8" height="16" pre="0" post="0" />
     <char name=":" x="40" y="160" width="8" height="16" pre="0" post="0" />
-    <char name="&" x="48" y="160" width="8" height="16" pre="0" post="0" />
+    <char name="&amp;" x="48" y="160" width="8" height="16" pre="0" post="0" />
     <char name="【" x="56" y="160" width="8" height="16" pre="0" post="0" />
     <char name="】" x="64" y="160" width="8" height="16" pre="0" post="0" />
     <char name="「" x="104" y="160" width="8" height="16" pre="0" post="0" />

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

@@ -87,7 +87,7 @@ end
 --   get_item_quantity(item)
 --   add_money(amount)
 --   remove_money(amount)
---   money_to_2_banks(b1, b2, a1, a2)
+--   money_to_banks(b1, b2, a1, a2)
 --   sort(criteria)
 Inventory = {}
 Inventory.money = 0

+ 52 - 22
data/data/scripts/field.lua

@@ -52,34 +52,45 @@ dispatch_menu = function(menu_id, parameter)
     end
 end
 
+--- Makes an entity the player entity and places it where the previous map sent it to.
+--
+-- This is the entry point of the map (when it came from another field) or the field entry of the
+-- world map. Fields that don't have a PC opcode for the player need it too, or the player would
+-- stay at the origin of the field, out of its walkmesh.
+--
+-- @param entity_name The name of the entity that the player controls.
+place_player_at_map_entry = function(entity_name)
+    if System.MapChanger.point_name ~= "" then
+        local point = entity_manager:get_entity_point(System.MapChanger.point_name)
+        if point ~= nil then
+            local x, y, z = point:get_position()
+            local rotation = point:get_rotation()
+            local player = entity_manager:get_entity(entity_name)
+            player:set_position(x, y, z)
+            player:set_rotation(rotation)
+            player:set_solid(true)
+            player:set_visible(true)
+        end
+    end
+
+    entity_manager:set_player_entity(entity_name)
+    local world_map_entry = System.MapChanger.world_map_entry
+    if world_map_entry then
+        entity_manager:place_player_at_field_entry(
+            world_map_entry.x, world_map_entry.y,
+            world_map_entry.triangle, world_map_entry.direction
+        )
+        System.MapChanger.world_map_entry = nil
+    end
+end
+
 --- Assigns an entity to a playable character.
 --
 -- @param entity_name The entity name.
 -- @param character_id Id of the character to assign.
 set_entity_to_character = function(entity_name, character_id)
     if character_id == Party[1] and entity_name ~= "" then
-        if System.MapChanger.point_name ~= "" then
-            local point = entity_manager:get_entity_point(System.MapChanger.point_name)
-            if point ~= nil then
-                local x, y, z = point:get_position()
-                local rotation = point:get_rotation()
-                local player = entity_manager:get_entity(entity_name)
-                player:set_position(x, y, z)
-                player:set_rotation(rotation)
-                player:set_solid(true)
-                player:set_visible(true)
-            end
-        end
-
-        entity_manager:set_player_entity(entity_name)
-        local world_map_entry = System.MapChanger.world_map_entry
-        if world_map_entry then
-            entity_manager:place_player_at_field_entry(
-                world_map_entry.x, world_map_entry.y,
-                world_map_entry.triangle, world_map_entry.direction
-            )
-            System.MapChanger.world_map_entry = nil
-        end
+        place_player_at_map_entry(entity_name)
     end
     entity_manager:set_entity_to_character(entity_name, character_id)
 end
@@ -228,6 +239,25 @@ axyzi = function(bx, by, bz, bt, id, ax, ay, az, at, scale)
     return 0
 end
 
+--- Stores the X and Y position of an entity in two variables.
+--
+-- @param bx Bank for X.
+-- @param by Bank for Y.
+-- @param id Index of the entity in the field.
+-- @param ax Address at which to store the X coordinate.
+-- @param ay Address at which to store the Y coordinate.
+-- @param scale Map scale to multiply values
+getaxy = function(bx, by, id, ax, ay, scale)
+    local entity = entity_manager:get_entity_from_index(id)
+    if entity == nil then
+        do return end
+    end
+    local x, y = entity:get_position()
+    Banks[bx][ax] = math.floor(x * scale)
+    Banks[by][ay] = math.floor(y * scale)
+    return 0
+end
+
 --- Shows the ending credits.
 ending_credits = function()
     -- TODO: Implement. Probably in a looooong loooong time.

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

@@ -362,13 +362,13 @@ end
 -- destination addresses to place two two-byte values into. Address 1 takes the lower two bytes of
 -- the gil amount, while address 2 takes the higher two bytes.
 --
--- @param b1 First destination bank.
--- @param b2 Second destination address.
--- @param a1 First destination bank.
--- @param a2 Second destination address.
-Inventory.money_to_2_banks = function(b1, b2, a1, a2)
-    Banks[b1][a1] = math.floor(Inventory.money / 65535)
-    Banks[b2][a2] = math.floor(Inventory.money % 65535) - math.floor(Inventory.money / 65535)
+-- @param b1 Bank of the first destination (lower two bytes).
+-- @param b2 Bank of the second destination (higher two bytes).
+-- @param a1 Address in the first bank.
+-- @param a2 Address in the second bank.
+Inventory.money_to_banks = function(b1, b2, a1, a2)
+    Banks[b1][a1] = Inventory.money % 65536
+    Banks[b2][a2] = math.floor(Inventory.money / 65536)
 end
 
 --- Sorts the items in the inventory.

+ 25 - 0
doc/TODO.md

@@ -0,0 +1,25 @@
+# TODO
+
+Notes of things found while playing through the fields that still have to be reviewed.
+
+## Long pauses between messages in chrin_1b
+
+Some scenes in chrin_1b (the opening, and the ones after it) wait a long time between messages, so
+it looks as if the game had frozen. Not investigated yet.
+
+Suspects:
+
+- The script scheduling in `ScriptManager::Update`: a script that yields with `wait(0)` now lets the
+  next script of its entity run in the same cycle.
+- The priority of the talk script, `INTERACT_SCRIPT_PRIORITY` in `EntityManager.cpp`.
+- The durations of `WAIT` and `script:wait` that the installer converts (frames to seconds).
+
+First step: play the scene with log level 3 and look at the time between the `dialog_open` and
+`Start script` lines.
+
+## Background parallax
+
+`BGPDH` and `BGSCR` are converted, but the meaning of their values (ratio = distance / 4096, speed in
+1/256 of a pixel per frame, sign of the speed) has not been checked against the original game. The far
+layer of mds5_4 has no such opcodes and still scrolls with the rest, and it is not known why it should
+not.

+ 124 - 18
src/core/Background2D.cpp

@@ -127,6 +127,11 @@ void Background2D::Update(){
         SetScroll(pending_scroll_);
     }
     UpdateShake(Timer::getSingleton().GetGameTimeDelta());
+    // The layers that scroll by themselves.
+    for (int layer = 0; layer < LAYER_COUNT; ++ layer){
+        layer_scroll_offset_[layer][0] += layer_scroll_speed_[layer][0] * Timer::getSingleton().GetGameTimeDelta();
+        layer_scroll_offset_[layer][1] += layer_scroll_speed_[layer][1] * Timer::getSingleton().GetGameTimeDelta();
+    }
     for (unsigned int i = 0; i < animation_played_.size(); ++ i){
         for (unsigned int j = 0; j < animations_.size(); ++ j){
             if (animations_[j]->GetName() == animation_played_[i].name){
@@ -623,6 +628,25 @@ void Background2D::SetRange(const int min_x, const int min_y, const int max_x, c
     calculateScreenScale();
 }
 
+float Background2D::TileDepthToBuffer(const float depth, const Ogre::Matrix4& projection){
+    Ogre::Vector4 result = projection * Ogre::Vector4(0, 0, -depth, 1);
+    result /= result.w;
+    // Starting from this depth the projected values are too close to each other for the depth
+    // buffer to tell them apart.
+    const float far_group_start = 127.0f;
+    if (depth >= far_group_start){
+        Ogre::Vector4 start = projection * Ogre::Vector4(0, 0, -far_group_start, 1);
+        start /= start.w;
+        // The depth of the render system goes from -1 to 1 (OpenGL), so between the start of the
+        // group and the far plane there are about 1.4e-5 to use. With 1e-5 per unit of depth,
+        // tiles whose depths differ by 0.06 (127.938 and 128) end up several steps of a 24 bit
+        // depth buffer apart, and the biggest depth used (128) is still in front of the far plane.
+        const float far_group_step = 0.00001f;
+        return std::min(start.z + (depth - far_group_start) * far_group_step, 0.9999995f);
+    }
+    return result.z;
+}
+
 void Background2D::AddTile(const VGears::Tile& tile){
     // TODO: move depth calculation to flevelBackgroundLoader maybe? and let
     // Backgorund2D only handle 0 <= depth <= 1 or so?
@@ -630,13 +654,10 @@ void Background2D::AddTile(const VGears::Tile& tile){
     Ogre::Real depth(0.0001f);
     if (tile.depth >= 1){
         if (tile.depth < 4095){
-            const Ogre::Matrix4 &cam_projection(
+            depth = TileDepthToBuffer(
+              tile.depth,
               CameraManager::getSingleton().GetCurrentCamera()->getProjectionMatrixWithRSDepth()
             );
-            Ogre::Vector4 res(0, 0, -tile.depth, 1);
-            res = cam_projection * res;
-            res /= res.w;
-            depth = res.z;
         }
         else depth = 0.9999f;
     }
@@ -667,7 +688,7 @@ void Background2D::virtualScreenToWorldSpace(Ogre::Vector2& pos) const{
 void Background2D::AddTile(
   const int x, const int y, const int width, const int height, const float depth, const float u1,
   const float v1, const float u2, const float v2, const Blending blending, const int area,
-  const int state
+  const int state, const int layer
 ){
     Ogre::RenderOperation render_op;
     Ogre::HardwareVertexBufferSharedPtr vertex_buffer;
@@ -698,6 +719,16 @@ void Background2D::AddTile(
         );
         return;
     }
+    // Keep where the tiles of each layer are in the buffer.
+    {
+        const int buffer = (blending == VGears::B_ALPHA) ? 0 : ((blending == VGears::B_ADD) ? 1 : 2);
+        const int clamped = (layer < 0) ? 0 : ((layer >= LAYER_COUNT) ? LAYER_COUNT - 1 : layer);
+        if (last_layer_[buffer] > clamped) layers_contiguous_[buffer] = false;
+        if (clamped > last_layer_[buffer]) last_layer_[buffer] = clamped;
+        LayerRange& range = layer_ranges_[buffer][clamped];
+        if (range.count == 0) range.first = render_op.vertexData->vertexCount;
+        range.count += TILE_VERTEX_COUNT;
+    }
     Tile tile;
     tile.x = x;
     tile.y = y;
@@ -901,23 +932,98 @@ void Background2D::renderQueueEnded(
         render_system_->_setViewMatrix(view);
         rs_params->setConstant(Ogre::GpuProgramParameters::ACT_VIEW_MATRIX, view);
         render_system_->applyFixedFunctionParams(rs_params, Ogre::GPV_GLOBAL);
-        if (alpha_render_op_.vertexData->vertexCount != 0){
-            scene_manager_->_setPass(alpha_material_->getTechnique(0)->getPass(0), true, false);
-            render_system_->_render(alpha_render_op_);
-        }
-        if (add_render_op_.vertexData->vertexCount != 0){
-            scene_manager_->_setPass(add_material->getTechnique(0)->getPass(0), true, false);
-            render_system_->_render(add_render_op_);
-        }
-        if(subtract_render_op_.vertexData->vertexCount != 0){
-            scene_manager_->_setPass(subtract_material_->getTechnique(0)->getPass(0), true, false);
-            render_system_->_render(subtract_render_op_);
-        }
+        RenderBuffer(0, alpha_render_op_, alpha_material_, rs_params, width, height);
+        RenderBuffer(1, add_render_op_, add_material, rs_params, width, height);
+        RenderBuffer(2, subtract_render_op_, subtract_material_, rs_params, width, height);
+    }
+}
+
+void Background2D::ResetLayers(){
+    for (int buffer = 0; buffer < 3; ++ buffer){
+        for (int layer = 0; layer < LAYER_COUNT; ++ layer) layer_ranges_[buffer][layer] = LayerRange();
+        last_layer_[buffer] = -1;
+        layers_contiguous_[buffer] = true;
+    }
+    for (int layer = 0; layer < LAYER_COUNT; ++ layer){
+        layer_parallax_[layer][0] = layer_parallax_[layer][1] = 1.0f;
+        layer_scroll_speed_[layer][0] = layer_scroll_speed_[layer][1] = 0.0f;
+        layer_scroll_offset_[layer][0] = layer_scroll_offset_[layer][1] = 0.0f;
+    }
+}
 
+bool Background2D::HasLayerScroll() const{
+    for (int layer = 0; layer < LAYER_COUNT; ++ layer){
+        if (
+          layer_parallax_[layer][0] != 1.0f || layer_parallax_[layer][1] != 1.0f
+          || layer_scroll_speed_[layer][0] != 0.0f || layer_scroll_speed_[layer][1] != 0.0f
+        ) return true;
+    }
+    return false;
+}
+
+void Background2D::ScriptSetLayerParallax(const int layer, const int distance){
+    if (layer < 0 || layer >= LAYER_COUNT) return;
+    const float ratio = static_cast<float>(distance) / 4096.0f;
+    layer_parallax_[layer][0] = ratio;
+    layer_parallax_[layer][1] = ratio;
+}
+
+void Background2D::ScriptSetLayerScroll(const int layer, const int x, const int y){
+    if (layer < 0 || layer >= LAYER_COUNT) return;
+    // 1/256 of a pixel of the original screen for each frame, at 30 frames per second. The
+    // virtual screen is 3 times as big as the one of the original game.
+    const float to_virtual_pixels_per_second = 30.0f * 3.0f / 256.0f;
+    layer_scroll_speed_[layer][0] = static_cast<float>(x) * to_virtual_pixels_per_second;
+    layer_scroll_speed_[layer][1] = static_cast<float>(y) * to_virtual_pixels_per_second;
+}
+
+void Background2D::RenderBuffer(
+  const int buffer, Ogre::RenderOperation& render_op, const Ogre::MaterialPtr& material,
+  Ogre::GpuProgramParametersPtr& rs_params, const float width, const float height
+){
+    if (render_op.vertexData->vertexCount == 0) return;
+    const unsigned int total = render_op.vertexData->vertexCount;
+    const bool by_layer = layers_contiguous_[buffer] && HasLayerScroll();
+    if (!by_layer){
+        // The view the layers of other buffers could have left.
+        Ogre::Matrix4 view;
+        view.makeTrans(Ogre::Vector3(position_real_.x / width, -position_real_.y / height, 0));
+        render_system_->_setViewMatrix(view);
+        rs_params->setConstant(Ogre::GpuProgramParameters::ACT_VIEW_MATRIX, view);
+        render_system_->applyFixedFunctionParams(rs_params, Ogre::GPV_GLOBAL);
+        scene_manager_->_setPass(material->getTechnique(0)->getPass(0), true, false);
+        render_system_->_render(render_op);
+        return;
+    }
+    for (int layer = 0; layer < LAYER_COUNT; ++ layer){
+        const LayerRange& range = layer_ranges_[buffer][layer];
+        if (range.count == 0) continue;
+        // The scroll of the background, changed by the parallax of the layer, plus the one it has
+        // by itself. The scroll by itself is in pixels of the virtual screen.
+        const Ogre::Vector2 by_itself(
+          layer_scroll_offset_[layer][0] * screen_scale_,
+          layer_scroll_offset_[layer][1] * screen_scale_
+        );
+        Ogre::Matrix4 view;
+        view.makeTrans(Ogre::Vector3(
+          (position_real_.x * layer_parallax_[layer][0] + by_itself.x) / width,
+          -(position_real_.y * layer_parallax_[layer][1] + by_itself.y) / height, 0
+        ));
+        // TODO This is deprecated, but if not done , the background image disappears.
+        render_system_->_setViewMatrix(view);
+        rs_params->setConstant(Ogre::GpuProgramParameters::ACT_VIEW_MATRIX, view);
+        render_system_->applyFixedFunctionParams(rs_params, Ogre::GPV_GLOBAL);
+        scene_manager_->_setPass(material->getTechnique(0)->getPass(0), true, false);
+        render_op.vertexData->vertexStart = range.first;
+        render_op.vertexData->vertexCount = range.count;
+        render_system_->_render(render_op);
+        render_op.vertexData->vertexStart = 0;
+        render_op.vertexData->vertexCount = total;
     }
 }
 
 void Background2D::CreateVertexBuffers(){
+    ResetLayers();
     alpha_max_vertex_count_ = 2048 * TILE_VERTEX_COUNT;
     alpha_render_op_.vertexData = new Ogre::VertexData;
     alpha_render_op_.vertexData->vertexStart = 0;

+ 113 - 1
src/core/Background2D.h

@@ -230,6 +230,32 @@ class Background2D : public Ogre::RenderQueueListener{
          * @param[in] y_amplitude Amplitude of the bounces in the Y axis, in original screen pixels.
          * @param[in] y_frames Frames between bounces in the Y axis.
          */
+        /**
+         * Sets the parallax of a background layer, from the distance the scripts give it.
+         *
+         * The layer scrolls at the speed of the background multiplied by the distance divided by
+         * 4096 (the distance of the layers that don't move with it is 4096), so a layer far away
+         * moves less than the foreground. That relation comes from the values the field scripts
+         * use and has not been verified against the original game.
+         *
+         * @param[in] layer Layer of the background, from 0 to 3.
+         * @param[in] distance Parallax distance of the layer.
+         */
+        void ScriptSetLayerParallax(const int layer, const int distance);
+
+        /**
+         * Sets the speed a background layer scrolls by itself, on top of the scroll of the
+         * background.
+         *
+         * The speeds are given in 1/256 of a pixel of the original screen for each frame of the
+         * original game (30 per second). That unit has not been verified against the original game.
+         *
+         * @param[in] layer Layer of the background, from 0 to 3.
+         * @param[in] x Speed on the X axis.
+         * @param[in] y Speed on the Y axis.
+         */
+        void ScriptSetLayerScroll(const int layer, const int x, const int y);
+
         void ScriptShake(
           const int type, const int x_amplitude, const int x_frames, const int y_amplitude,
           const int y_frames
@@ -363,7 +389,7 @@ class Background2D : public Ogre::RenderQueueListener{
         void AddTile(
           const int x, const int y, const int width, const int height, const float depth,
           const float u1, const float v1, const float u2, const float v2, const Blending blending,
-          const int area = 0, const int state = 0
+          const int area = 0, const int state = 0, const int layer = 0
         );
 
         /**
@@ -390,6 +416,22 @@ class Background2D : public Ogre::RenderQueueListener{
          */
         void AddTile(const VGears::Tile& tile);
 
+        /**
+         * Converts the depth of a tile to the value that is written to the depth buffer.
+         *
+         * The depth is projected with the camera. The tiles that are the farthest (the background
+         * layers) are at depths so close to each other (as 127.938 and 128) that their projected
+         * values are closer than what the depth buffer can tell apart, and the order they are
+         * drawn in decided what was in front. For them the order is kept with a linear mapping
+         * right behind all the rest of what is drawn.
+         *
+         * @param[in] depth Depth of the tile.
+         * @param[in] projection Projection matrix of the camera, with the depth range of the
+         * render system.
+         * @return The value for the depth buffer.
+         */
+        static float TileDepthToBuffer(const float depth, const Ogre::Matrix4& projection);
+
         /**
          * Updates the UV vector of a tile
          *
@@ -765,6 +807,76 @@ class Background2D : public Ogre::RenderQueueListener{
         /**
          * Alpha blending render operation.
          */
+        /**
+         * The number of layers of a background.
+         */
+        static const int LAYER_COUNT = 4;
+
+        /**
+         * Part of a vertex buffer that has the tiles of a layer.
+         */
+        struct LayerRange{
+            LayerRange(): first(0), count(0){}
+            unsigned int first;
+            unsigned int count;
+        };
+
+        /**
+         * The tiles of each layer in the buffers of alpha, add and subtract tiles. They are only
+         * used if the tiles of a layer are together, which is how the installer writes them.
+         */
+        LayerRange layer_ranges_[3][LAYER_COUNT];
+
+        /**
+         * The last layer a tile has been added to for each buffer.
+         */
+        int last_layer_[3];
+
+        /**
+         * If the tiles of the layers of each buffer are together, one layer after the other.
+         */
+        bool layers_contiguous_[3];
+
+        /**
+         * Scroll of each layer relative to the one of the background.
+         */
+        float layer_parallax_[LAYER_COUNT][2];
+
+        /**
+         * Speed at which each layer scrolls by itself, in pixels of the virtual screen per second.
+         */
+        float layer_scroll_speed_[LAYER_COUNT][2];
+
+        /**
+         * Distance each layer has scrolled by itself, in pixels of the virtual screen.
+         */
+        float layer_scroll_offset_[LAYER_COUNT][2];
+
+        /**
+         * Sets the layers to the state they have when a background is loaded.
+         */
+        void ResetLayers();
+
+        /**
+         * If any layer is not scrolled as the background is.
+         */
+        bool HasLayerScroll() const;
+
+        /**
+         * Draws the tiles of a buffer, each layer scrolled as it has to be.
+         *
+         * @param[in] buffer Buffer to draw: 0 for alpha, 1 for add and 2 for subtract.
+         * @param[in,out] render_op What to draw.
+         * @param[in] material Material to draw it with.
+         * @param[in] rs_params Parameters of the render system.
+         * @param[in] width Width of the viewport.
+         * @param[in] height Height of the viewport.
+         */
+        void RenderBuffer(
+          const int buffer, Ogre::RenderOperation& render_op, const Ogre::MaterialPtr& material,
+          Ogre::GpuProgramParametersPtr& rs_params, const float width, const float height
+        );
+
         Ogre::RenderOperation alpha_render_op_;
 
         /**

+ 25 - 3
src/core/DialogsManager.cpp

@@ -101,7 +101,10 @@ void DialogsManager::UpdateDebug(){}
 void DialogsManager::OnResize(){}
 
 void DialogsManager::ClearField(){
-    for (unsigned int i = 0; i < messages_.size(); ++ i) HideMessage(i);
+    for (unsigned int i = 0; i < messages_.size(); ++ i){
+        HideMessage(i);
+        ResetMessageMode(i);
+    }
 }
 
 void DialogsManager::ClearBattle(){
@@ -109,7 +112,10 @@ void DialogsManager::ClearBattle(){
 }
 
 void DialogsManager::ClearWorld(){
-    for (unsigned int i = 0; i < messages_.size(); ++ i) HideMessage(i);
+    for (unsigned int i = 0; i < messages_.size(); ++ i){
+        HideMessage(i);
+        ResetMessageMode(i);
+    }
 }
 
 void DialogsManager::ScriptSetMapName(const char* text_id){
@@ -134,6 +140,11 @@ void DialogsManager::OpenDialog(const char* d_name, int x, int y, int w, int h){
         );
         return;
     }
+    // The mode is for the script that set it. When another one opens the window it is the default.
+    if (!data->mode_owner.empty()){
+        const ScriptId script = ScriptManager::getSingleton().GetCurrentScriptId();
+        if (data->mode_owner != script.entity + "/" + script.function) ResetMessageMode(id);
+    }
     data->x = x;
     data->y = y;
     data->w = w;
@@ -238,6 +249,10 @@ void DialogsManager::SetMode(const char* d_name, const int bg, const bool closea
         return;
     }
     messages_[id]->closeable = closeable;
+    {
+        const ScriptId script = ScriptManager::getSingleton().GetCurrentScriptId();
+        messages_[id]->mode_owner = script.entity + "/" + script.function;
+    }
     switch (bg){
         case 1:
             messages_[id]->visible = false;
@@ -366,7 +381,14 @@ void DialogsManager::HideMessage(const int id){
     for (unsigned int i = 0; i < messages_[id]->sync.size(); ++ i)
         ScriptManager::getSingleton().ContinueScriptExecution(messages_[id]->sync[i]);
     messages_[id]->sync.clear();
-    // Reset mode.
+    // The mode (WMODE) is not reset here: it is kept for the script that set it, so one that is
+    // set once is used by all the messages that script opens, as in the scenes that show several
+    // texts over a black screen, with no window. See OpenDialog. It is also reset when the
+    // field changes.
+}
+
+void DialogsManager::ResetMessageMode(const int id){
+    messages_[id]->mode_owner.clear();
     messages_[id]->closeable = true;
     messages_[id]->visible = true;
     messages_[id]->translucent = false;

+ 14 - 0
src/core/DialogsManager.h

@@ -222,6 +222,13 @@ struct MessageData{
      */
     bool translucent;
 
+    /**
+     * The script that set the mode of the message, as "entity/function", or empty if the mode is
+     * the default one. The mode is used by the messages that script opens, and set back to the
+     * default when another script opens one.
+     */
+    std::string mode_owner;
+
     /**
      * Indicates if the window is for a timer.
      */
@@ -459,6 +466,13 @@ class DialogsManager : public Manager, public Ogre::Singleton<DialogsManager>{
          */
         void HideMessage(const int id);
 
+        /**
+         * Sets the mode of a message (see {@see SetMode}) back to the default one.
+         *
+         * @param[in] id The ID of the message.
+         */
+        void ResetMessageMode(const int id);
+
         /**
          * Retrieves a message Id from it's name.
          *

+ 5 - 0
src/core/Entity.cpp

@@ -243,6 +243,11 @@ void Entity::ScriptSetPosition(const float x, const float y, const float z){
     move_triangle_id_ = -1;
 }
 
+void Entity::ScriptSetPositionXY(const float x, const float y){
+    SetPosition(Ogre::Vector3(x, y, GetPosition().z));
+    move_triangle_id_ = -1;
+}
+
   void Entity::ScriptMoveBy(const float x, const float y, const float z){
     SetPosition(GetPosition() + Ogre::Vector3(x, y, z));
     move_triangle_id_ = -1;

+ 6 - 0
src/core/Entity.h

@@ -225,6 +225,12 @@ class Entity{
          */
         void ScriptSetPosition(const float x, const float y, const float z);
 
+        /**
+         * Sets the X and Y position from a script, keeping Z and the visibility, solidity and
+         * talkability of the entity. The walkmesh triangle is looked up again on the next move.
+         */
+        void ScriptSetPositionXY(const float x, const float y);
+
         /**
          * Moves the entity by the specified position offset.
          *

+ 91 - 26
src/core/EntityManager.cpp

@@ -35,6 +35,17 @@
 /**
  * Entity manager singleton.
  */
+/**
+ * Priority of the script that runs when the player presses OK in front of an entity.
+ *
+ * It has to run before the scripts that fields leave running on an entity (the loops that play an
+ * animation, requested with priority 5 or 6), or the entity could not be talked to. But it can't
+ * be the highest: a script of the same entity that is requested with a higher priority (3 or
+ * smaller) has to be able to run while the talk script waits for it, as in the scenes where a
+ * talk script asks its own entity to do something and waits for it to start.
+ */
+static const int INTERACT_SCRIPT_PRIORITY = 4;
+
 template<>EntityManager *Ogre::Singleton<EntityManager>::msSingleton = nullptr;
 
 ConfigVar cv_debug_grid("debug_grid", "Draw debug grid", "false");
@@ -182,7 +193,7 @@ void EntityManager::Input(const VGears::Event& event){
                       ScriptManager::ENTITY, entity_triggers_[i]->GetName()
                     );
                     bool added = ScriptManager::getSingleton().ScriptRequest(
-                      scr_entity, "on_interact", 1, "", "", false, false
+                      scr_entity, "on_interact", INTERACT_SCRIPT_PRIORITY, "", "", false, false
                     );
                     if (added == false){
                         LOG_WARNING(
@@ -193,10 +204,6 @@ void EntityManager::Input(const VGears::Event& event){
                 }
             }
         }
-        // Rotate move vector to field move rotation
-        Ogre::Quaternion q1;
-        q1.FromAngleAxis(player_move_rotation_, Ogre::Vector3::UNIT_Z);
-        player_move_ = q1 * player_move_;
     }
 }
 
@@ -210,9 +217,36 @@ void EntityManager::UpdateField(){
     if (player_entity_ != nullptr && player_move_ != Ogre::Vector3::ZERO){
         Entity::State state = player_entity_->GetState();
         if (state == Entity::WALKMESH || state == Entity::NONE){
-            player_move_
-              *= player_entity_->GetMoveWalkSpeed() * Timer::getSingleton().GetGameTimeDelta();
-            player_entity_->SetMovePosition(player_entity_->GetPosition() + player_move_);
+            // The directional keys are relative to the screen. Rotate them to the field's move
+            // rotation here, once per frame. This can't be done in Input, which runs once for each
+            // event, so the rotation would be applied several times when there is more than one
+            // event in the frame (diagonals, running, ...). player_move_ stays as the raw input,
+            // which is what ladders need.
+            // A scripted walk of an entity that isn't the player keeps the last triangle when it
+            // goes over an edge, so the stored triangle can be far from the position. Reattach to
+            // the walkmesh before moving manually, otherwise every move would be blocked.
+            const int current_triangle = player_entity_->GetMoveTriangleId();
+            bool in_current_triangle = false;
+            if (current_triangle >= 0 && current_triangle < walkmesh_.GetNumberOfTriangles()){
+                const Ogre::Vector3 position = player_entity_->GetPosition();
+                const Ogre::Vector3 A = walkmesh_.GetA(current_triangle);
+                const Ogre::Vector3 B = walkmesh_.GetB(current_triangle);
+                const Ogre::Vector3 C = walkmesh_.GetC(current_triangle);
+                in_current_triangle = Ogre::Math::pointInTri2D(
+                  Ogre::Vector2(position.x, position.y), Ogre::Vector2(A.x, A.y),
+                  Ogre::Vector2(B.x, B.y), Ogre::Vector2(C.x, C.y)
+                );
+            }
+            if (!in_current_triangle) SetEntityOnWalkmesh(player_entity_);
+
+            Ogre::Quaternion rotation;
+            rotation.FromAngleAxis(player_move_rotation_, Ogre::Vector3::UNIT_Z);
+            // Normalized, so diagonals aren't faster than straight moves.
+            Ogre::Vector3 input = player_move_;
+            input.normalise();
+            const Ogre::Vector3 walk_move = rotation * input
+              * (player_entity_->GetMoveWalkSpeed() * Timer::getSingleton().GetGameTimeDelta());
+            player_entity_->SetMovePosition(player_entity_->GetPosition() + walk_move);
             player_entity_->SetState(Entity::WALKMESH);
         }
 
@@ -740,10 +774,16 @@ void EntityManager::ScriptSetPlayerEntity(const char* name){
     }
     for (unsigned int i = 0; i < entity_.size(); ++ i){
         if (entity_[i]->GetName() == name){
+            // A script can read the walkmesh triangle of the player right after this, so if it was
+            // placed by position the triangle has to be known. Done before it's the player entity
+            // so that the entity not being on the walkmesh yet isn't logged as an error.
+            if (entity_[i]->GetMoveTriangleId() == -1) SetEntityOnWalkmesh(entity_[i]);
+            LOG_TRIVIAL("Player entity set to '" + std::string(name) + "'.");
             player_entity_ = entity_[i];
-            // Also, make is solid, visible, and non-talkable.
-            entity_[i]->SetSolid(true);
-            entity_[i]->SetVisible(true);
+            // Also, make it non-talkable. Visibility and solidity are left to the scripts, as in
+            // the original game: some fields use hidden helper entities (like the 'camera') as
+            // the player entity to scroll the screen, and they must stay invisible and
+            // non-solid. When the player moves manually, UpdateField makes it solid anyway.
             entity_[i]->SetTalkable(false);
         }
     }
@@ -934,10 +974,10 @@ void EntityManager::SetEncounterRate(float rate){
 }
 
 bool EntityManager::IsKeyOn(unsigned int key_code){
-    // If the player is locked, always false.
-    // TODO: Maybe this will interfere with something down the road?
-    // Some scene where the player can't move but must press a button?
-    if (player_lock_) return false;
+    // The keys are read even if the player is locked: scenes lock the player and still ask for a
+    // button, as the minigames that need to be pressed at the right time do. The scripts that
+    // must not read the keys at some point (while a message is open, for example) have their own
+    // flag for it. The triggers that check keys aren't run while the player is locked.
     // 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 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
@@ -1119,6 +1159,14 @@ bool EntityManager::PerformWalkmeshMove(Entity* entity, const float speed, const
         angle = (direction.x < 0) ? Ogre::Degree(360) - angle : angle;
         entity->SetRotation(Ogre::Degree(angle));
     }
+    // A scripted walk (also of the player entity, while a scene has it locked) must end, or the
+    // scene waiting for it would never go on. Several scripts place solid entities next to where
+    // another one has to walk to, so they don't block it: only the walkmesh does. A manual move
+    // of the player is never scripted.
+    // The same goes for the points around the end of the step: they are there for the player and
+    // NPCs to slide along walls, and a destination next to an edge of the walkmesh would never be
+    // reached. The end point itself is still checked.
+    const bool check_probes = !scripted_move;
     float solid = (entity->IsSolid() == true) ? entity->GetSolidRadius() : 0.01f;
 
     // For not playable entities, be a bit more lenient with solid collisions.
@@ -1150,10 +1198,10 @@ bool EntityManager::PerformWalkmeshMove(Entity* entity, const float speed, const
         end_point2.x = end_point.x + rotation.x;
         end_point2.y = end_point.y + rotation.y;
         // Check_triangle.
-        first_triangle_check = WalkmeshBorderCross(entity, end_point2, direction);
+        first_triangle_check = check_probes && WalkmeshBorderCross(entity, end_point2, direction);
         entity->SetMoveTriangleId(current_triangle);
         // Model check.
-        first_entity_check = CheckSolidCollisions(entity, end_point2);
+        first_entity_check = check_probes && CheckSolidCollisions(entity, end_point2);
 
         // 2nd check.
         // Rotate move_vector +45.
@@ -1168,10 +1216,10 @@ bool EntityManager::PerformWalkmeshMove(Entity* entity, const float speed, const
         end_point2.x = end_point.x + rotation.x;
         end_point2.y = end_point.y + rotation.y;
         // Check triangle.
-        second_triangle_check = WalkmeshBorderCross(entity, end_point2, direction);
+        second_triangle_check = check_probes && WalkmeshBorderCross(entity, end_point2, direction);
         entity->SetMoveTriangleId(current_triangle);
         // Model check.
-        second_entity_check = CheckSolidCollisions(entity, end_point2);
+        second_entity_check = check_probes && CheckSolidCollisions(entity, end_point2);
 
         // 3rd check.
         rotation.x = direction.x;
@@ -1182,10 +1230,10 @@ bool EntityManager::PerformWalkmeshMove(Entity* entity, const float speed, const
         end_point2.x = end_point.x + rotation.x;
         end_point2.y = end_point.y + rotation.y;
         // Check triangle.
-        third_triangle_check = WalkmeshBorderCross(entity, end_point2, direction);
+        third_triangle_check = check_probes && WalkmeshBorderCross(entity, end_point2, direction);
         entity->SetMoveTriangleId(current_triangle);
         // Model check.
-        third_entity_check = CheckSolidCollisions(entity, end_point2);
+        third_entity_check = check_probes && CheckSolidCollisions(entity, end_point2);
 
         // Check condition and modify move_vector.
         if (
@@ -1329,7 +1377,11 @@ bool EntityManager::WalkmeshBorderCross(
     }
     int current_triangle = entity->GetMoveTriangleId();
     if (current_triangle == -1) return true;
-    const bool free_walk = !WalkmeshEdgeBlocks(entity == player_entity_, entity->IsScriptedMove());
+    // While a scene has the player locked, the scene walks the player like any other character,
+    // and it can lead over the edge of the walkmesh.
+    const bool free_walk = !WalkmeshEdgeBlocks(
+      entity == player_entity_ && !player_lock_, entity->IsScriptedMove()
+    );
     Ogre::Vector2 pos = Ogre::Vector2(position.x, position.y);
     for (;;){
         Ogre::Vector3 A3 = walkmesh_.GetA(current_triangle);
@@ -1597,7 +1649,7 @@ void EntityManager::CheckEntityInteract(){
           ScriptManager::ENTITY, name
         );
         bool added = ScriptManager::getSingleton().ScriptRequest(
-          scr_entity, "on_interact", 1, "", "", false, false
+          scr_entity, "on_interact", INTERACT_SCRIPT_PRIORITY, "", "", false, false
         );
         if (added == false){
             LOG_WARNING("Script 'on_interact' for entity '" +  name + "' doesn't exist.");
@@ -1680,7 +1732,15 @@ void EntityManager::SetNextLinearStep(Entity* entity){
         }
     }
     else is_move = true;
-    if (is_move == true){
+    if (is_move == true && (end - start).length() < 0.0001f){
+        // Start and end are the same place, so there is nothing to walk. Dividing by the time to
+        // cross the line (zero) would give a NaN position. Just finish the move.
+        entity->UnsetLinear();
+        if (to_end == true) entity->SetMoveTriangleId(entity->GetLinearDestTriangle());
+        SetEntityOnWalkmesh(entity);
+        entity->SetPosition(to_end ? end : start);
+    }
+    else if (is_move == true){
         Ogre::Vector3 position;
         if (to_end == true){
             Ogre::Vector3 end_start = end - start;
@@ -1695,7 +1755,11 @@ void EntityManager::SetNextLinearStep(Entity* entity){
               ? std::max(position.y, end.y) : std::min(position.y, end.y);
             position.z = (end_start.z < 0)
               ? std::max(position.z, end.z) : std::min(position.z, end.z);
-            if (position == end){
+            // Compared with a tolerance: if the entity starts a bit off the end in one of the
+            // axes (the entry points are written with a few digits only), that axis could move
+            // less than the precision of a float per frame and never be exactly equal.
+            if ((position - end).length() < 0.0001f){
+                position = end;
                 entity->UnsetLinear();
                 entity->SetMoveTriangleId(entity->GetLinearDestTriangle());
                 SetEntityOnWalkmesh(entity);
@@ -1714,7 +1778,8 @@ void EntityManager::SetNextLinearStep(Entity* entity){
               ? std::max(position.y, start.y) : std::min(position.y, start.y);
             position.z = (end_start.z < 0)
               ? std::max(position.z, start.z) : std::min(position.z, start.z);
-            if (position == start){
+            if ((position - start).length() < 0.0001f){
+                position = start;
                 entity->UnsetLinear();
                 SetEntityOnWalkmesh(entity);
             }

+ 52 - 38
src/core/ScriptManager.cpp

@@ -13,6 +13,7 @@
  * GNU General Public License for more details.
  */
 
+#include <algorithm>
 #include "core/ConfigVar.h"
 #include "core/DebugDraw.h"
 #include "core/Logger.h"
@@ -186,23 +187,28 @@ void ScriptManager::Update(const ScriptManager::Type type){
 
     for (unsigned int i = 0; i < script_entity_.size(); ++ i){
         if (script_entity_[i].type == type){
-            if (script_entity_[i].queue.size() > 0){
+            // One script of an entity is run each cycle, the first in the queue. If it yields
+            // without waiting for anything, as the ones that loop for ever, or until a flag
+            // changes, do, it doesn't take the cycle of the rest: the next one is run too. In the
+            // original game the scripts of an entity that have different priorities all run.
+            for (size_t script_index = 0; script_index < script_entity_[i].queue.size(); ++ script_index){
+                bool yielded = false;
                 current_script_id_.entity = script_entity_[i].name;
-                current_script_id_.function = script_entity_[i].queue[0].function;
+                current_script_id_.function = script_entity_[i].queue[script_index].function;
 
-                if (script_entity_[i].queue[0].wait == false){
-                    if (script_entity_[i].queue[0].yield == false){
+                if (script_entity_[i].queue[script_index].wait == false){
+                    if (script_entity_[i].queue[script_index].yield == false){
                         LOG_TRIVIAL(
                           "[SCRIPT] Start script \"" + current_script_id_.function
                           + "\" for entity \"" + current_script_id_.entity + "\"."
                         );
 
-                        for (const ScriptId& waiting : script_entity_[i].queue[0].paused_scripts_start)
+                        for (const ScriptId& waiting : script_entity_[i].queue[script_index].paused_scripts_start)
                             ContinueScriptExecution(waiting);
-                        script_entity_[i].queue[0].paused_scripts_start.clear();
+                        script_entity_[i].queue[script_index].paused_scripts_start.clear();
                         sol::object table = GetTableByEntityName(
                           type, current_script_id_.entity,
-                          script_entity_[i].queue[0].state
+                          script_entity_[i].queue[script_index].state
                         );
                         sol::object function;
                         if (table.get_type() == sol::type::table)
@@ -212,12 +218,12 @@ void ScriptManager::Update(const ScriptManager::Type type){
                           && function.get_type() == sol::type::function
                         ){
                             int ret = 0;
-                            lua_State* thread = script_entity_[i].queue[0].state;
+                            lua_State* thread = script_entity_[i].queue[script_index].state;
                             sol::coroutine coroutine(thread, sol::function(function));
                             auto result = coroutine(
-                              table, script_entity_[i].queue[0].argument1,
-                              script_entity_[i].queue[0].argument2,
-                              script_entity_[i].queue[0].argument3
+                              table, script_entity_[i].queue[script_index].argument1,
+                              script_entity_[i].queue[script_index].argument2,
+                              script_entity_[i].queue[script_index].argument3
                             );
                             if (result.valid()){
                                 // The coroutine yields with the bound function
@@ -231,7 +237,7 @@ void ScriptManager::Update(const ScriptManager::Type type){
                                 sol::error error = result;
                                 std::string msg = "LUA error in entity " + script_entity_[i].name
                                   + " (" + current_script_id_.entity + ") " + " in function "
-                                  + script_entity_[i].queue[0].function + " ("
+                                  + script_entity_[i].queue[script_index].function + " ("
                                   + current_script_id_.function + ")";
                                 if (lua_tostring(thread, -1) != nullptr){
                                     msg = msg + ". Details: " + std::string(
@@ -248,8 +254,8 @@ void ScriptManager::Update(const ScriptManager::Type type){
                                   "[SCRIPT] Script \"" + current_script_id_.function
                                   + "\" for entity \"" + current_script_id_.entity + "\" finished."
                                 );
-                                if (script_entity_[i].queue[0].function != "on_update")
-                                    RemoveEntityTopScript(script_entity_[i]);
+                                if (script_entity_[i].queue[script_index].function != "on_update")
+                                    RemoveEntityScript(script_entity_[i], script_index);
                             }
                             else if (ret == 1){
                                 LOG_TRIVIAL(
@@ -257,7 +263,8 @@ void ScriptManager::Update(const ScriptManager::Type type){
                                   + "\" for entity \"" + current_script_id_.entity
                                   + "\" not paused and will be continued next cycle."
                                 );
-                                script_entity_[i].queue[0].yield = true;
+                                script_entity_[i].queue[script_index].yield = true;
+                                yielded = true;
                             }
                             else{
                                 LOG_TRIVIAL(
@@ -265,8 +272,8 @@ void ScriptManager::Update(const ScriptManager::Type type){
                                   + "\" for entity \"" + current_script_id_.entity
                                   + "\" not finished yet."
                                 );
-                                script_entity_[i].queue[0].yield = true;
-                                script_entity_[i].queue[0].wait = true;
+                                script_entity_[i].queue[script_index].yield = true;
+                                script_entity_[i].queue[script_index].wait = true;
                             }
                         }
                         else{
@@ -274,7 +281,7 @@ void ScriptManager::Update(const ScriptManager::Type type){
                               "Script \"" + current_script_id_.function + "\" for entity \""
                               + current_script_id_.entity + "\" doesn't exist."
                             );
-                            RemoveEntityTopScript(script_entity_[i]);
+                            RemoveEntityScript(script_entity_[i], script_index);
                         }
                     }
                     else{
@@ -284,7 +291,7 @@ void ScriptManager::Update(const ScriptManager::Type type){
                         );
 
                         int ret = 0;
-                        lua_State* thread = script_entity_[i].queue[0].state;
+                        lua_State* thread = script_entity_[i].queue[script_index].state;
                         // sol.hpp bundles a compat53 shim: on LuaJIT this maps
                         // to the native 2-argument lua_resume.
                         int status = lua_resume(thread, nullptr, 0);
@@ -308,10 +315,10 @@ void ScriptManager::Update(const ScriptManager::Type type){
                             );
 
                             // Stop yield for on_update.
-                            script_entity_[i].queue[0].yield = false;
+                            script_entity_[i].queue[script_index].yield = false;
 
-                            if (script_entity_[i].queue[0].function != "on_update")
-                                RemoveEntityTopScript(script_entity_[i]);
+                            if (script_entity_[i].queue[script_index].function != "on_update")
+                                RemoveEntityScript(script_entity_[i], script_index);
                         }
                         else if (ret == 1){
                             LOG_TRIVIAL(
@@ -319,6 +326,7 @@ void ScriptManager::Update(const ScriptManager::Type type){
                               + "\" for entity \"" + current_script_id_.entity
                               + "\" not paused and will be continued next cycle."
                             );
+                            yielded = true;
                         }
                         else{
                             LOG_TRIVIAL(
@@ -326,20 +334,22 @@ void ScriptManager::Update(const ScriptManager::Type type){
                               + "\" for entity \"" + current_script_id_.entity
                               + "\" not finished yet."
                             );
-                            script_entity_[i].queue[0].wait = true;
+                            script_entity_[i].queue[script_index].wait = true;
                         }
                     }
                 }
-                else if (script_entity_[i].queue[0].seconds_to_wait > 0){
-                    script_entity_[i].queue[0].seconds_to_wait
+                else if (script_entity_[i].queue[script_index].seconds_to_wait > 0){
+                    script_entity_[i].queue[script_index].seconds_to_wait
                       -= Timer::getSingleton().GetGameTimeDelta();
-                    script_entity_[i].queue[0].seconds_to_wait
-                      = (script_entity_[i].queue[0].seconds_to_wait < 0)
-                        ? 0 : script_entity_[i].queue[0].seconds_to_wait;
+                    script_entity_[i].queue[script_index].seconds_to_wait
+                      = (script_entity_[i].queue[script_index].seconds_to_wait < 0)
+                        ? 0 : script_entity_[i].queue[script_index].seconds_to_wait;
 
-                    if (script_entity_[i].queue[0].seconds_to_wait == 0)
-                        script_entity_[i].queue[0].wait = false;
+                    if (script_entity_[i].queue[script_index].seconds_to_wait == 0)
+                        script_entity_[i].queue[script_index].wait = false;
                 }
+            
+                if (!yielded) break;
             }
         }
     }
@@ -456,18 +466,20 @@ void ScriptManager::RemoveEntity(const ScriptManager::Type type, const Ogre::Str
     }
 }
 
-void ScriptManager::RemoveEntityTopScript(ScriptEntity& entity){
-    if (entity.queue.size() > 0){
+void ScriptManager::RemoveEntityScript(ScriptEntity& entity, const size_t index){
+    if (entity.queue.size() > index){
         // Delete the thread.
-        luaL_unref(lua_state_, LUA_REGISTRYINDEX, entity.queue[0].state_id);
+        luaL_unref(lua_state_, LUA_REGISTRYINDEX, entity.queue[index].state_id);
 
-        for (const ScriptId& waiting : entity.queue[0].paused_scripts_end)
+        for (const ScriptId& waiting : entity.queue[index].paused_scripts_end)
             ContinueScriptExecution(waiting);
-        entity.queue[0].paused_scripts_end.clear();
-        entity.queue.erase(entity.queue.begin());
+        entity.queue[index].paused_scripts_end.clear();
+        entity.queue.erase(entity.queue.begin() + index);
     }
 }
 
+void ScriptManager::RemoveEntityTopScript(ScriptEntity& entity){RemoveEntityScript(entity, 0);}
+
 sol::object ScriptManager::GetTableByEntityName(
   const ScriptManager::Type type, const Ogre::String& name, lua_State* state
 ) const{
@@ -524,9 +536,11 @@ const ScriptId ScriptManager::GetCurrentScriptId() const{return current_script_i
 void ScriptManager::ContinueScriptExecution(const ScriptId& script){
     QueueScript* script_pointer = GetScriptByScriptId(script);
     if (script_pointer == nullptr){
-        LOG_ERROR(
+        // The script that waited for this is already gone, for example because the field changed
+        // or the script was cancelled, so there is nothing to continue.
+        LOG_TRIVIAL(
           "There is no script \"" + script.function + "\" for entity \""
-          + script.entity + "\". Can't continue script execution."
+          + script.entity + "\" to continue. It has already ended."
         );
         return;
     }

+ 8 - 0
src/core/ScriptManager.h

@@ -295,6 +295,14 @@ class ScriptManager : public Manager, public Ogre::Singleton<ScriptManager>{
           const Ogre::String& entity_name, const Ogre::String& function_name, int priority
         );
 
+        /**
+         * Removes a script of an entity.
+         *
+         * @param[in] entity Entity whose script to remove.
+         * @param[in] index Position of the script in the queue of the entity.
+         */
+        void RemoveEntityScript(ScriptEntity& entity, const size_t index);
+
         /**
          * Removes the top script of an entity.
          *

+ 3 - 0
src/core/ScriptManagerBinds.h

@@ -116,6 +116,7 @@ void ScriptManager::InitBinds(){
     lua.new_usertype<Entity>(
       "Entity",
       "set_position", &Entity::ScriptSetPosition,
+      "set_position_xy", &Entity::ScriptSetPositionXY,
       "move_by", &Entity::ScriptMoveBy,
       "get_position", &Entity::ScriptGetPosition,
       "get_name", &Entity::GetName,
@@ -466,6 +467,8 @@ void ScriptManager::InitBinds(){
       "area_off", &Background2D::ScriptAreaOff,
       "area_clear", &Background2D::ScriptAreaClear,
       "shake", &Background2D::ScriptShake,
+      "set_layer_parallax", &Background2D::ScriptSetLayerParallax,
+      "set_layer_scroll", &Background2D::ScriptSetLayerScroll,
       "play_animation_looped", &Background2D::ScriptPlayAnimationLooped,
       "play_animation_once", &Background2D::ScriptPlayAnimationOnce
     );

+ 6 - 0
src/core/UiManager.cpp

@@ -150,6 +150,12 @@ void UiManager::ApplyWindowColours(UiWidget* widget){
 
 void UiManager::renderQueueStarted(
   Ogre::uint8 queueGroupId, const Ogre::String& invocation, bool& skipThisInvocation
+){
+    // The widgets are drawn when the queue ends. See renderQueueEnded.
+}
+
+void UiManager::renderQueueEnded(
+  Ogre::uint8 queueGroupId, const Ogre::String& invocation, bool& repeatThisInvocation
 ){
     if (queueGroupId == Ogre::RENDER_QUEUE_OVERLAY){
         Ogre::Root::getSingletonPtr()->getRenderSystem()->clearFrameBuffer(Ogre::FBT_DEPTH);

+ 17 - 0
src/core/UiManager.h

@@ -185,6 +185,23 @@ class UiManager :
           bool& skipThisInvocation
         );
 
+        /**
+         * Draws the widgets when the overlay queue ends.
+         *
+         * The screen fade and the movies are drawn when the overlay queue starts, and the order
+         * the listeners are called in is the order they were added in. The UI was added first,
+         * so it was drawn before them, and they were drawn over the dialogs. At the end of the
+         * queue it is drawn over all of them.
+         *
+         * @param[in] queueGroupId The queue group ID.
+         * @param[in] invocation Unused.
+         * @param[in] repeatThisInvocation Unused.
+         */
+        void renderQueueEnded(
+          Ogre::uint8 queueGroupId, const Ogre::String& invocation,
+          bool& repeatThisInvocation
+        );
+
     private:
 
         /**

+ 7 - 6
src/core/XmlBackground2DFile.cpp

@@ -63,18 +63,19 @@ void XmlBackground2DFile::Load(){
                 if (blending_str == "add") blending = VGears::B_ADD;
                 else if (blending_str == "subtract") blending = VGears::B_SUBTRACT;
                 else blending = VGears::B_ALPHA;
-                Ogre::Vector4 distance = Ogre::Vector4(0, 0, -depth, 1);
-                Ogre::Vector4 res
-                  = CameraManager::getSingleton().GetCurrentCamera()
-                    ->getProjectionMatrixWithRSDepth() * distance;
-                res = res / res.w;
+                const float buffer_depth = Background2D::TileDepthToBuffer(
+                  depth,
+                  CameraManager::getSingleton().GetCurrentCamera()->getProjectionMatrixWithRSDepth()
+                );
                 // Tiles that belong to a scripted area are only drawn while a script turns on
                 // the state they belong to.
                 const int area = GetInt(node, "area", 0);
                 const int state = GetInt(node, "state", 0);
+                // The layer of the original background the tile is from.
+                const int layer = GetInt(node, "layer", 0);
                 background->AddTile(
                   static_cast<int>(destination.x), static_cast<int>(destination.y),
-                  width, height, res.z, uv.x, uv.y, uv.z, uv.w, blending, area, state
+                  width, height, buffer_depth, uv.x, uv.y, uv.z, uv.w, blending, area, state, layer
                 );
                 TiXmlNode* node2 = node->FirstChild();
                 while (node2 != nullptr){

+ 5 - 1
src/installer/FieldDataInstaller.cpp

@@ -692,7 +692,7 @@ void FieldDataInstaller::PcFieldToVGearsField(VGears::FLevelFilePtr& field){
                     first_entity_point = position;
                 }
                 xml_entity_point->SetAttribute("position", Ogre::StringConverter::toString(position));
-                const float rotation = (360.0f * static_cast<float>(gateway.dir)) / 255.0f;
+                const float rotation = (360.0f * static_cast<float>(gateway.dir)) / 256.0f;
                 xml_entity_point->SetAttribute("rotation", std::to_string(rotation));
                 element->LinkEndChild(xml_entity_point.release());
             }
@@ -847,7 +847,9 @@ void FieldDataInstaller::PcFieldToVGearsField(VGears::FLevelFilePtr& field){
             );
             // Write out the *_BG.XML data.
             auto& layers = bg->GetLayers();
+            int layer_index = -1;
             for (const auto& layer : layers){
+                ++ layer_index;
                 // TODO: Should only the tiles to construct enabled layers be outputted?
                 //  if (layer.enabled){
 
@@ -889,6 +891,8 @@ void FieldDataInstaller::PcFieldToVGearsField(VGears::FLevelFilePtr& field){
                     // TODO: Should probably throw to fail conversion:
                     else blending_str = "unknown";
                     xml_element->SetAttribute("blending", blending_str);
+                    // The layer is needed by the scripts that scroll them at their own speed.
+                    xml_element->SetAttribute("layer", layer_index);
                     // Sprites in an area the script controls (BGON, BGOFF, BGCLR) are drawn only
                     // while it turns on one of their layers. The rest are always drawn.
                     if (

+ 12 - 2
src/installer/FieldTextWriter.cpp

@@ -65,7 +65,17 @@ void FieldTextWriter::Write(const std::vector<u8>& script_section_buffer, std::s
         logger_->Log("[ERROR exception]");
         logger_->Log("</dialog>\n");
     }
-    const u16 dialogCount = GetU16LE(offset_to_sector + offset_to_dialogs);
+    u16 dialogCount = GetU16LE(offset_to_sector + offset_to_dialogs);
+    // The first string comes right after the table of offsets, so its offset says how many strings
+    // there are. A field with 256 of them has a count that doesn't fit (it is read as 0), and the
+    // field would have no texts at all.
+    if (offset_to_sector + offset_to_dialogs + 0x03 < data_.size()){
+        const u16 first_offset = GetU16LE(offset_to_sector + offset_to_dialogs + 0x02);
+        if (first_offset >= 0x02 && (first_offset % 2) == 0){
+            const u16 count_from_table = (first_offset - 0x02) / 2;
+            if (count_from_table > dialogCount) dialogCount = count_from_table;
+        }
+    }
     for (u16 i = 0; i <dialogCount; ++ i){
         // gGt offset of string data.
         u32 offset = offset_to_sector + offset_to_dialogs + GetU16LE(
@@ -271,7 +281,7 @@ void FieldTextWriter::Write(const std::vector<u8>& script_section_buffer, std::s
                 }
                 else if (temp2 == 0xDC){
                     AddText(dialog);
-                    logger_->Log("<pause_ok >");
+                    logger_->Log("<pause_ok />");
                 }
                 else if (temp2 == 0xDD){
                     u16 wait = GetU16LE(offset + 1);

+ 1 - 1
src/installer/WorldInstaller.cpp

@@ -899,7 +899,7 @@ bool WorldInstaller::GenerateScripts(
                         const int16 triangle = static_cast<int16>(raw_triangle);
                         const uint8 direction = field_table_data[offset + 8];
                         const float direction_degrees
-                            = 360.0f * static_cast<float>(direction) / 255.0f;
+                            = 360.0f * static_cast<float>(direction) / 256.0f;
                         const size_t table_id = record_index + 1;
                         script_file << "System.WorldMap.field_table[" << table_id << "]"
                             << " = System.WorldMap.field_table[" << table_id << "] or {}\n"

+ 79 - 4
src/installer/decompiler/field/FieldCodeGenerator.cpp

@@ -14,7 +14,9 @@
  */
 
 #include <boost/algorithm/string/predicate.hpp>
+#include <algorithm>
 #include <stdexcept>
+#include <unordered_set>
 #include "FieldCodeGenerator.h"
 #include "FieldDisassembler.h"
 #include "FieldEngine.h"
@@ -113,6 +115,7 @@ void FieldCodeGenerator::Generate(InstVec& insts, const Graph& graph){
 
     for (auto function = functions_with_bodies.begin(); function != functions_with_bodies.end(); ++ function){
 
+        const size_t first_line_of_function = lines_.size();
         OnBeforeStartFunction(function->first);
         auto signature = ConstructFuncSignature(function->first);
         AddOutputLine(signature, false, true);
@@ -129,6 +132,31 @@ void FieldCodeGenerator::Generate(InstVec& insts, const Graph& graph){
             }
         }
 
+        // Lua can't jump into a block. If a jump from outside of the body of a conditional lands
+        // in the middle of it, the conditional is written as a jump over its body instead of a
+        // block, so the label ends up where every jump can see it.
+        std::unordered_set<const Instruction*> flattened;
+        for (auto cond = function->second.begin(); cond != function->second.end(); ++ cond){
+            if (!(*cond)->IsCondJump()) continue;
+            const uint32 start = (*cond)->GetAddress();
+            const uint32 end = (*cond)->GetDestAddress();
+            // The target has to be in the function to have a label.
+            const bool target_in_function = std::any_of(
+              function->second.begin(), function->second.end(),
+              [end](const InstPtr& i){return i->GetAddress() == end;}
+            );
+            if (!target_in_function) continue;
+            for (auto jump = function->second.begin(); jump != function->second.end(); ++ jump){
+                if (!(*jump)->IsUncondJump()) continue;
+                const uint32 destination = (*jump)->GetDestAddress();
+                const uint32 origin = (*jump)->GetAddress();
+                if (destination > start && destination < end && (origin < start || origin >= end)){
+                    flattened.insert(cond->get());
+                    break;
+                }
+            }
+        }
+
         // Implemented instructions.
 
         // Some conditional jumps point to an address outside the function (i.e the starting
@@ -141,7 +169,7 @@ void FieldCodeGenerator::Generate(InstVec& insts, const Graph& graph){
                 bool needs_label = false;
                 bool needs_new_line = false;
                 for (auto origin = label->second.begin(); origin != label->second.end(); ++ origin){
-                    if ((*origin)->IsCondJump()){
+                    if ((*origin)->IsCondJump() && flattened.count(origin->get()) == 0){
                         ends_needed --;
                         AddOutputLine("end", true, false);
                         needs_new_line = true;
@@ -157,7 +185,13 @@ void FieldCodeGenerator::Generate(InstVec& insts, const Graph& graph){
             //    AddOutputLine("end -- end if", true, false);
             //    end_needed = false;
             //}
-            if ((*instruction)->IsCondJump()){
+            if ((*instruction)->IsCondJump() && flattened.count(instruction->get()) != 0){
+                AddOutputLine((
+                  boost::format("if not (%s) then goto label_0x%X end")
+                    % stack.Pop()->GetString() % (*instruction)->GetDestAddress()
+                ).str());
+            }
+            else if ((*instruction)->IsCondJump()){
                 ends_needed ++;
                 AddOutputLine((boost::format("if (%s) then") % stack.Pop()->GetString()).str(), false, true);
                 // If the next instruction is the last in the function, mark the next pass to
@@ -192,6 +226,20 @@ void FieldCodeGenerator::Generate(InstVec& insts, const Graph& graph){
             if ("on_update" == function->first.name) AddOutputLine(ON_UPDATE_FINISHED_STATEMENT);
             AddOutputLine("do return 0 end", false, false);
         }
+        // A goto to a label that isn't in the function can't be compiled, and the whole script
+        // would fail to load. It can't continue where it was told to, so it ends the script.
+        {
+            std::unordered_set<std::string> emitted_labels;
+            for (size_t i = first_line_of_function; i < lines_.size(); ++ i)
+                if (lines_[i].line.rfind("::label_0x", 0) == 0) emitted_labels.insert(lines_[i].line);
+            for (size_t i = first_line_of_function; i < lines_.size(); ++ i){
+                const std::string prefix = "goto label_0x";
+                if (lines_[i].line.rfind(prefix, 0) != 0) continue;
+                const std::string label = "::" + lines_[i].line.substr(5) + "::";
+                if (emitted_labels.count(label) == 0)
+                    lines_[i].line = "do return 0 end -- was: " + lines_[i].line + " (not in this function)";
+            }
+        }
         OnEndFunction(function->first);
     }
 
@@ -224,7 +272,7 @@ void FieldCodeGenerator::OnStartFunction(const Function& func){
     if (meta_data.GetEntityName() == "Director" && func.name == "on_start"){
         // TODO: Don't hardcode Cloud, Use the one from the previous level,
         AddOutputLine("-- HACK: Set default playable entity an ensure the camera follows it.");
-        AddOutputLine("entity_manager:set_player_entity(\"Cloud\")");
+        AddOutputLine("place_player_at_map_entry(\"Cloud\")");
         AddOutputLine("background2d:autoscroll_to_entity(entity_manager:get_entity(\"Cloud\"))\n");
     }
     // Don't run on_update again once it has reached its end.
@@ -255,7 +303,34 @@ void FieldCodeGenerator::OnEndFunction(const Function& function){
     AddOutputLine("end,", true, false);
     // End class?
     FunctionMetaData meta_data(function.metadata);
-    if (meta_data.IsEnd()) AddOutputLine("}\n\n\n", true, false);
+    if (meta_data.IsEnd()){
+        AddOutputLine("}\n", true, false);
+        // The scripts of the entity that have the same code as other of its scripts.
+        for (const auto& alias : static_cast<FieldEngine*>(engine_)->GetFunctionAliases()){
+            if (alias.entity_name != meta_data.GetEntityName()) continue;
+            const std::string container = "EntityContainer[\"" + alias.entity_name + "\"]";
+            const std::string alias_name
+              = formatter_.GetFriendlyFunctionName(alias.entity_name, alias.alias);
+            const std::string target_name
+              = formatter_.GetFriendlyFunctionName(alias.entity_name, alias.target);
+            // The events of a line don't run while it is off, as in the functions of their own.
+            if (
+              alias.alias == "on_approach" || alias.alias == "on_cross" || alias.alias == "on_near"
+              || alias.alias == "on_near_once" || alias.alias == "on_leave"
+            ){
+                AddOutputLine(
+                  container + "." + alias_name + " = function(self, ...)", false, true
+                );
+                AddOutputLine("if self.on ~= nil and self.on == false then", false, true);
+                AddOutputLine("return 0");
+                AddOutputLine("end", true, false);
+                AddOutputLine("return " + container + "." + target_name + "(self, ...)");
+                AddOutputLine("end", true, false);
+            }
+            else AddOutputLine(container + "." + alias_name + " = " + container + "." + target_name);
+        }
+        AddOutputLine("\n\n");
+    }
     else AddOutputLine("\n");
 }
 

+ 52 - 2
src/installer/decompiler/field/FieldDisassembler.cpp

@@ -128,6 +128,12 @@ void FieldDisassembler::DoDisassemble(){
         }
         // Only parse each script one.
         std::set<uint16> parsed_scripts;
+        // The first script that has each entry point, and the scripts that repeat it.
+        std::map<uint16, size_t> first_script_with_entry_point;
+        std::vector<std::pair<size_t, size_t>> repeated_scripts;
+        // The unused scripts at the end of an entity point to its last script, as the ones that
+        // are empty point to the one after them. Those are not names for it.
+        const uint16 last_script_entry_point = header_.entity_scripts[entity_number].back();
         std::vector<ScriptInfo> script_info;
         // Collect the scripts to parse
         for (
@@ -136,12 +142,27 @@ void FieldDisassembler::DoDisassemble(){
             uint16 script_entry_point = header_.entity_scripts[entity_number][script_index];
             // If this scripts entry point is already parsed, don't do it again as it means
             // two scripts have the same entry, which only seems to be true for "empty" scripts.
-            if (parsed_scripts.find(script_entry_point) != std::end(parsed_scripts)) continue;
-            parsed_scripts.insert(script_entry_point);
             const uint32 next_script_entry_point = GetEndOfScriptOffset(
               script_entry_point, entity_number, script_index
             );
             const uint32 script_size = next_script_entry_point - script_entry_point;
+            if (parsed_scripts.find(script_entry_point) != std::end(parsed_scripts)){
+                // It is the same code as another script. If it is more than a return it has to
+                // be a name for it, or the event that starts it would be lost. For example, a
+                // line that starts a scene when it is crossed does it with the same code as when
+                // OK is pressed.
+                const size_t first = first_script_with_entry_point[script_entry_point];
+                if (script_size > 1 && first > 0 && script_index > 0){
+                    const bool unused_tail = script_entry_point == last_script_entry_point;
+                    // Only the events of a line can be one of the unused ones: they have a name
+                    // (they are checked below, when it is known that the entity is a line).
+                    if (!unused_tail || script_index <= 6)
+                        repeated_scripts.push_back(std::make_pair(script_index, first));
+                }
+                continue;
+            }
+            parsed_scripts.insert(script_entry_point);
+            first_script_with_entry_point[script_entry_point] = script_index;
             if (script_size > 0){
                 ScriptInfo info = {script_entry_point, next_script_entry_point, script_index};
                 script_info.push_back(info);
@@ -154,6 +175,35 @@ void FieldDisassembler::DoDisassemble(){
               entity_name, entity_number, it->index, it->entry_point, it->next_entry_point, is_start, is_end
             );
         }
+        // Name the repeated scripts, now that it is known if the entity is a line.
+        for (const auto& repeated : repeated_scripts){
+            const bool is_line = engine_->EntityIsLine(entity_number);
+            // The slots of an entity that is not a line don't have a name, only the ones of a
+            // line do. The ones in the unused tail are kept only if they have one.
+            if (
+              header_.entity_scripts[entity_number][repeated.first]
+                == header_.entity_scripts[entity_number].back()
+              && !(is_line && repeated.first <= 6)
+            ) continue;
+            std::string alias = "script_" + std::to_string(repeated.first);
+            if (repeated.first == 1) alias = "on_interact";
+            else if (is_line){
+                switch (repeated.first){
+                    case 2: alias = "on_approach"; break;
+                    case 3: alias = "on_cross"; break;
+                    case 4: alias = "on_near"; break;
+                    case 5: alias = "on_near_once"; break;
+                    case 6: alias = "on_leave"; break;
+                    default: break;
+                }
+            }
+            std::string target;
+            try{target = engine_->EntityByIndex(entity_number).FunctionByIndex(repeated.second);}
+            catch (const DecompilerException&){continue;}
+            if (alias == target) continue;
+            engine_->AddEntityFunction(entity_name, entity_number, alias, repeated.first);
+            engine_->AddFunctionAlias(entity_name, alias, target);
+        }
     }
 }
 

+ 6 - 0
src/installer/decompiler/field/FieldEngine.cpp

@@ -213,6 +213,12 @@ void FieldEngine::AddEntityFunction(
     }
 }
 
+void FieldEngine::AddFunctionAlias(
+  const std::string& entity_name, const std::string& alias, const std::string& target
+){
+    function_aliases_.push_back(FunctionAlias{entity_name, alias, target});
+}
+
 void FieldEngine::MarkEntityAsLine(
   size_t entity_index, bool line, std::vector<float> point_a, std::vector<float> point_b
 ){

+ 35 - 0
src/installer/decompiler/field/FieldEngine.h

@@ -310,6 +310,36 @@ class FieldEngine : public Engine{
           const std::string& func_name, size_t func_index
         );
 
+        /**
+         * A function of an entity that is the same code as another function of it.
+         *
+         * Several scripts of an entity can start at the same address in the original game, so the
+         * code is only written once, and the rest of them are names for it.
+         */
+        struct FunctionAlias{
+            std::string entity_name;
+            std::string alias;
+            std::string target;
+        };
+
+        /**
+         * Adds a function that is another name for a function of the same entity.
+         *
+         * @param[in] entity_name Name of the entity.
+         * @param[in] alias Name of the new function.
+         * @param[in] target Name of the function that has the code.
+         */
+        void AddFunctionAlias(
+          const std::string& entity_name, const std::string& alias, const std::string& target
+        );
+
+        /**
+         * Retrieves the functions that are another name for other function.
+         *
+         * @return The aliases.
+         */
+        const std::vector<FunctionAlias>& GetFunctionAliases() const{return function_aliases_;}
+
         /**
          * Marks an entity as a line.
          *
@@ -403,6 +433,11 @@ class FieldEngine : public Engine{
          */
         std::map<size_t, Entity> entity_index_map_;
 
+        /**
+         * Functions that are another name for other functions.
+         */
+        std::vector<FunctionAlias> function_aliases_;
+
         /**
          * The map scale factor.
          */

+ 38 - 2
src/installer/decompiler/field/instruction/FieldBackgroundInstruction.cpp

@@ -16,6 +16,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <cstdint>
 #include <iostream>
 #include <sstream>
 #include <boost/format.hpp>
@@ -30,8 +31,8 @@ void FieldBackgroundInstruction::ProcessInst(
     FunctionMetaData md(func.metadata);
     //std::cout << "Background opcode: " << opcode_ << "\n";
     switch (opcode_){
-        case OPCODES::BGPDH: code_gen->WriteTodo(md.GetEntityName(), "BGPDH");break;
-        case OPCODES::BGSCR: code_gen->WriteTodo(md.GetEntityName(), "BGSCR"); break;
+        case OPCODES::BGPDH: ProcessBGPDH(code_gen); break;
+        case OPCODES::BGSCR: ProcessBGSCR(code_gen); break;
         case OPCODES::MPPAL: code_gen->WriteTodo(md.GetEntityName(), "MPPAL"); break;
         case OPCODES::BGON: ProcessBGON(code_gen, static_cast<FieldEngine&>(*engine)); break;
         case OPCODES::BGOFF: ProcessBGOFF(code_gen, static_cast<FieldEngine&>(*engine)); break;
@@ -56,6 +57,41 @@ void FieldBackgroundInstruction::ProcessInst(
     }
 }
 
+void FieldBackgroundInstruction::ProcessBGPDH(CodeGenerator* code_gen){
+    // Parallax distance of a background layer. The layer is a value, and the distance can be a
+    // value or a variable (the bank is the first nibble).
+    FieldCodeGenerator* cg = static_cast<FieldCodeGenerator*>(code_gen);
+    const auto& distance = FieldCodeGenerator::FormatValueOrVariable(
+      cg->GetFormatter(), params_[0]->GetUnsigned(), static_cast<int>(
+        static_cast<int16_t>(params_[3]->GetUnsigned() & 0xFFFF)
+      )
+    );
+    code_gen->AddOutputLine((
+      boost::format("background2d:set_layer_parallax(%1%, %2%)") % params_[2]->GetUnsigned()
+        % distance
+    ).str());
+}
+
+void FieldBackgroundInstruction::ProcessBGSCR(CodeGenerator* code_gen){
+    // Speed at which a background layer scrolls by itself, on each axis. The layer is a value. X
+    // and Y can be values or variables, with the bank in the first and second nibbles.
+    FieldCodeGenerator* cg = static_cast<FieldCodeGenerator*>(code_gen);
+    const auto& x = FieldCodeGenerator::FormatValueOrVariable(
+      cg->GetFormatter(), params_[0]->GetUnsigned(), static_cast<int>(
+        static_cast<int16_t>(params_[3]->GetUnsigned() & 0xFFFF)
+      )
+    );
+    const auto& y = FieldCodeGenerator::FormatValueOrVariable(
+      cg->GetFormatter(), params_[1]->GetUnsigned(), static_cast<int>(
+        static_cast<int16_t>(params_[4]->GetUnsigned() & 0xFFFF)
+      )
+    );
+    code_gen->AddOutputLine((
+      boost::format("background2d:set_layer_scroll(%1%, %2%, %3%)") % params_[2]->GetUnsigned()
+        % x % y
+    ).str());
+}
+
 void FieldBackgroundInstruction::ProcessBGON(CodeGenerator* code_gen, FieldEngine& engine){
     engine.AddBackgroundArea(params_[0]->GetUnsigned() == 0 ? params_[2]->GetUnsigned() : -1);
     FieldCodeGenerator* cg = static_cast<FieldCodeGenerator*>(code_gen);

+ 4 - 0
src/installer/decompiler/field/instruction/FieldBackgroundInstruction.h

@@ -41,6 +41,10 @@ class FieldBackgroundInstruction : public KernelCallInstruction{
 
     private:
 
+        void ProcessBGPDH(CodeGenerator* code_gen);
+
+        void ProcessBGSCR(CodeGenerator* code_gen);
+
         void ProcessBGON(CodeGenerator* code_gen, FieldEngine& engine);
         void ProcessBGOFF(CodeGenerator* code_gen, FieldEngine& engine);
         void ProcessBGCLR(CodeGenerator* code_gen, FieldEngine& engine);

+ 38 - 12
src/installer/decompiler/field/instruction/FieldCondJumpInstruction.cpp

@@ -16,6 +16,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <cstdint>
 #include <iostream>
 #include <sstream>
 #include <boost/format.hpp>
@@ -46,8 +47,10 @@ void FieldCondJumpInstruction::ProcessInst(
           || opcode_ == OPCODES::IFKEYON
           || opcode_ == OPCODES::IFKEYOFF
         ){
-            if (param >= 1024) param = param ^ 1024;
-            if (param >= 512) param = param ^ 512;
+            // XOR only if the bit is set, to clear it. Testing with >= added them to the keys
+            // that had a bigger value, such as 32768.
+            if (param & 1024) param = param ^ 1024;
+            if (param & 512) param = param ^ 512;
         }
         ValuePtr v = new UnquotedStringValue(func_name + "(" + std::to_string(param) + ")");
         stack.Push(v);
@@ -55,12 +58,25 @@ void FieldCondJumpInstruction::ProcessInst(
     }
     std::string op;
     uint32 type = params_[4]->GetUnsigned();
-    const auto& source = FieldCodeGenerator::FormatValueOrVariable(
-      cg->GetFormatter(), params_[0]->GetUnsigned(), params_[2]->GetUnsigned()
-    );
-    const auto& destination = FieldCodeGenerator::FormatValueOrVariable(
-      cg->GetFormatter(), params_[1]->GetUnsigned(), params_[3]->GetUnsigned()
-    );
+    // The values of the signed comparisons (IFSW) are signed words: 65504 is -32. The ones of the
+    // unsigned comparisons, and the addresses of the variables, are not.
+    const bool is_signed = opcode_ == OPCODES::IFSW || opcode_ == OPCODES::IFSWL;
+    const auto& source = is_signed
+      ? FieldCodeGenerator::FormatValueOrVariable(
+          cg->GetFormatter(), params_[0]->GetUnsigned(),
+          static_cast<int>(static_cast<int16_t>(params_[2]->GetUnsigned() & 0xFFFF))
+        )
+      : FieldCodeGenerator::FormatValueOrVariable(
+          cg->GetFormatter(), params_[0]->GetUnsigned(), params_[2]->GetUnsigned()
+        );
+    const auto& destination = is_signed
+      ? FieldCodeGenerator::FormatValueOrVariable(
+          cg->GetFormatter(), params_[1]->GetUnsigned(),
+          static_cast<int>(static_cast<int16_t>(params_[3]->GetUnsigned() & 0xFFFF))
+        )
+      : FieldCodeGenerator::FormatValueOrVariable(
+          cg->GetFormatter(), params_[1]->GetUnsigned(), params_[3]->GetUnsigned()
+        );
 
     switch (type){
         case 0: op = "=="; break;
@@ -69,10 +85,20 @@ void FieldCondJumpInstruction::ProcessInst(
         case 3: op = "<"; break;
         case 4: op = ">="; break;
         case 5: op = "<="; break;
-        // TODO: FIX those, use the new binary functions.
-        case 6: op = "&"; break;
-        case 7: op = "^"; break;
-        case 8: op = "|"; break;
+        // The game runs on LuaJIT (Lua 5.1), where there are no operators for the bits, and a
+        // number is always true, even if it is 0. These are true if the result isn't zero.
+        case 6:
+        case 7:
+        case 8:
+            {
+                const std::string function
+                  = (type == 6) ? "bit_and" : ((type == 7) ? "bit_xor" : "bit_or");
+                ValuePtr v = new UnquotedStringValue(
+                  function + "(" + source + ", " + destination + ") ~= 0"
+                );
+                stack.Push(v);
+            }
+            return;
         case 9:
             {
                 op = "get_bank_bit(" + params_[0]->GetString() + ", " + params_[2]->GetString()

+ 45 - 4
src/installer/decompiler/field/instruction/FieldModelInstruction.cpp

@@ -101,7 +101,11 @@ void FieldModelInstruction::ProcessInst(
         case OPCODES::PGTDR: code_gen->WriteTodo(md.GetEntityName(), "PGTDR"); break;
         case OPCODES::PXYZI: code_gen->WriteTodo(md.GetEntityName(), "PXYZI"); break;
         case OPCODES::TLKON: ProcessTLKON(code_gen, md.GetEntityName()); break;
-        case OPCODES::PC: ProcessPC(code_gen, md.GetEntityName(), md.GetCharacterId()); break;
+        case OPCODES::PC:
+            // The character is the one in the opcode. The one of CHAR is the model of the field's
+            // list, and it is not the same for all (for Aeris it is 1, and she is character 3).
+            ProcessPC(code_gen, md.GetEntityName(), params_[0]->GetUnsigned());
+            break;
         case OPCODES::opCodeCHAR: ProcessCHAR(code_gen, md.GetEntityName()); break;
         case OPCODES::DFANM: ProcessDFANM(code_gen, md.GetEntityName(), md.GetCharacterId()); break;
         case OPCODES::ANIME1:
@@ -109,7 +113,7 @@ void FieldModelInstruction::ProcessInst(
             break;
         case OPCODES::VISI: ProcessVISI(code_gen, md.GetEntityName()); break;
         case OPCODES::XYZI: ProcessXYZI(code_gen, md.GetEntityName()); break;
-        case OPCODES::XYI: code_gen->WriteTodo(md.GetEntityName(), "XYI"); break;
+        case OPCODES::XYI: ProcessXYI(code_gen, md.GetEntityName()); break;
         case OPCODES::XYZ: code_gen->WriteTodo(md.GetEntityName(), "XYZ"); break;
         case OPCODES::MOVE: ProcessMOVE(code_gen, md.GetEntityName()); break;
         case OPCODES::CMOVE: code_gen->WriteTodo(md.GetEntityName(), "CMOVE"); break;
@@ -135,7 +139,7 @@ void FieldModelInstruction::ProcessInst(
         case OPCODES::TURN: code_gen->WriteTodo(md.GetEntityName(), "TURN"); break;
         case OPCODES::DIRA: ProcessDIRA(code_gen, md.GetEntityName(), eng); break;
         case OPCODES::GETDIR: code_gen->WriteTodo(md.GetEntityName(), "GETDIR"); break;
-        case OPCODES::GETAXY: code_gen->WriteTodo(md.GetEntityName(), "GETAXY"); break;
+        case OPCODES::GETAXY: ProcessGETAXY(code_gen); break;
         case OPCODES::GETAI: ProcessGETAI(code_gen, eng); break;
         case OPCODES::ANIM_2:
             ProcessANIM_2(code_gen, md.GetEntityName(), md.GetCharacterId());
@@ -455,6 +459,29 @@ void FieldModelInstruction::ProcessXYZI(CodeGenerator* code_gen, const std::stri
     ).str());
 }
 
+void FieldModelInstruction::ProcessXYI(CodeGenerator* code_gen, const std::string& entity){
+    FieldCodeGenerator* cg = static_cast<FieldCodeGenerator*>(code_gen);
+    const float scale = 128.0f * cg->GetScaleFactor();
+    // Constants are already scaled by the formatter, but values read from a bank (usually stored
+    // by AXYZI) are in field units, so they need to be scaled here.
+    const auto format_coordinate = [&](uint32 bank, int16 value){
+        const auto& formatted = FieldCodeGenerator::FormatValueOrVariable(
+          cg->GetFormatter(), bank, value, FieldCodeGenerator::ValueType::Float, scale
+        );
+        if (bank == 0) return formatted;
+        return "(" + formatted + ") / " + std::to_string(scale);
+    };
+    const auto x = format_coordinate(params_[0]->GetUnsigned(), params_[4]->GetSigned());
+    const auto y = format_coordinate(params_[1]->GetUnsigned(), params_[5]->GetSigned());
+    const auto& triangle_id = FieldCodeGenerator::FormatValueOrVariable(
+      cg->GetFormatter(), params_[3]->GetUnsigned(), params_[6]->GetUnsigned()
+    );
+    code_gen->AddOutputLine((
+      boost::format("self.%1%:set_position_xy(%2%, %3%) -- triangle ID %4%")
+      % entity % x % y % triangle_id
+    ).str());
+}
+
 void FieldModelInstruction::ProcessMOVE(CodeGenerator* code_gen, const std::string& entity){
     FieldCodeGenerator* cg = static_cast<FieldCodeGenerator*>(code_gen);
     const float scale = 128.0f * cg->GetScaleFactor();
@@ -691,6 +718,18 @@ void FieldModelInstruction::ProcessAXYZI(CodeGenerator* code_gen){
     ).str());
 }
 
+void FieldModelInstruction::ProcessGETAXY(CodeGenerator* code_gen){
+    // Stores the X and Y of an entity (given by its index in the field) in two variables, in
+    // field units. The script of a line, for example, keeps looking at where the player is.
+    FieldCodeGenerator* cg = static_cast<FieldCodeGenerator*>(code_gen);
+    const float scale = 128.0f * cg->GetScaleFactor();
+    code_gen->AddOutputLine((
+      boost::format("getaxy(%1%, %2%, %3%, %4%, %5%, %6%)")
+      % params_[0]->GetSigned() % params_[1]->GetSigned() % params_[2]->GetSigned()
+      % params_[3]->GetSigned() % params_[4]->GetSigned() % scale
+    ).str());
+}
+
 void FieldModelInstruction::ProcessLADER(
   CodeGenerator* code_gen, const std::string& entity, int char_id
 ){
@@ -753,8 +792,10 @@ void FieldModelInstruction::ProcessSLIDR(CodeGenerator* code_gen, const std::str
 }
 
 void FieldModelInstruction::ProcessSOLID(CodeGenerator* code_gen, const std::string& entity){
+    // Lines (triggers) can run this opcode too, but they don't have a model to make solid, so
+    // the entity may not exist in the script.
     code_gen->AddOutputLine((
-      boost::format("self.%1%:set_solid(%2%)")
+      boost::format("if self.%1% ~= nil then self.%1%:set_solid(%2%) end")
       % entity % FieldCodeGenerator::FormatInvertedBool(params_[0]->GetUnsigned())
     ).str());
 }

+ 16 - 0
src/installer/decompiler/field/instruction/FieldModelInstruction.h

@@ -154,6 +154,12 @@ class FieldModelInstruction : public KernelCallInstruction{
 
         void ProcessXYZI(CodeGenerator* code_gen, const std::string& entity);
 
+        /**
+         * Processes a XYI opcode: sets the X and Y position of the entity (and its triangle).
+         * Unlike XYZI, it doesn't change the visibility of the entity.
+         */
+        void ProcessXYI(CodeGenerator* code_gen, const std::string& entity);
+
         void ProcessMOVE(CodeGenerator* code_gen, const std::string& entity);
 
         /**
@@ -432,6 +438,16 @@ class FieldModelInstruction : public KernelCallInstruction{
          */
         void ProcessAXYZI(CodeGenerator* code_gen);
 
+        /**
+         * Processes a GETAXY opcode: stores the X and Y of an entity in two variables.
+         *
+         * Opcode: 0xB8
+         * Short name: GETAXY
+         *
+         * @param[in,out] code_gen Code generator to append lines.
+         */
+        void ProcessGETAXY(CodeGenerator* code_gen);
+
         /**
          * Processes a LADER opcode.
          *

+ 34 - 30
src/installer/decompiler/field/instruction/FieldPartyInstruction.cpp

@@ -49,8 +49,18 @@ void FieldPartyInstruction::ProcessInst(
         case OPCODES::DMTRA: code_gen->WriteTodo(md.GetEntityName(), "DMTRA"); break;
         case OPCODES::CMTRA: code_gen->WriteTodo(md.GetEntityName(), "CMTRA"); break;
         case OPCODES::GETPC: code_gen->WriteTodo(md.GetEntityName(), "GETPC"); break;
-        case OPCODES::PRTYP: code_gen->WriteTodo(md.GetEntityName(), "PRTYP"); break;
-        case OPCODES::PRTYM: code_gen->WriteTodo(md.GetEntityName(), "PRTYM"); break;
+        case OPCODES::PRTYP:
+            // Adds a character to the party, in the first free place.
+            code_gen->AddOutputLine(
+              (boost::format("add_party_character(%1%)") % params_[0]->GetString()).str()
+            );
+            break;
+        case OPCODES::PRTYM:
+            // Removes a character from the party.
+            code_gen->AddOutputLine(
+              (boost::format("remove_party_character(%1%)") % params_[0]->GetString()).str()
+            );
+            break;
         case OPCODES::PRTYE: ProcessPRTYE(code_gen); break;
         case OPCODES::MMBUD: ProcessMMBUD(code_gen); break;
         case OPCODES::MMBLK: ProcessMMBLK(code_gen); break;
@@ -63,38 +73,32 @@ void FieldPartyInstruction::ProcessInst(
     }
 }
 
-void FieldPartyInstruction::ProcessGOLDU(CodeGenerator* code_gen){
-    if (params_[0]->GetUnsigned() == 0){
-        // Add by constant amount, params 1-4 are a dword with the amount.
-        // Quick and dirty hex operation:
-        long amount = params_[1]->GetUnsigned() + params_[2]->GetUnsigned() * 256
-          + params_[3]->GetUnsigned() * 256 * 256 + params_[4]->GetUnsigned() * 256 * 256 * 256;
-        code_gen->AddOutputLine((boost::format("Inventory.add_money(%1%)") % amount).str());
-    }
-    else{
-        // Add by variable amount, param 0 is bank and 1 is address. 2-4 are zeroes, ignored.
-        code_gen->AddOutputLine((
-          boost::format("Inventory.add_money(Banks[%1%][%2%])")
-            % params_[0]->GetUnsigned() % params_[1]->GetUnsigned()
-        ).str());
+namespace{
+    /**
+     * Writes the amount of gil of a GOLDU or GOLDD.
+     *
+     * The opcode is read as a byte of two nibbles (two parameters, the bank is the first) followed
+     * by four bytes. With a bank of zero, the four bytes are the amount, as a little endian dword.
+     * Otherwise, the first of the four bytes is the address in the bank, and the rest are zeroes.
+     */
+    std::string GilAmount(const std::vector<ValuePtr>& params){
+        if (params[0]->GetUnsigned() == 0){
+            const long amount = params[2]->GetUnsigned() + params[3]->GetUnsigned() * 256
+              + params[4]->GetUnsigned() * 256 * 256 + params[5]->GetUnsigned() * 256 * 256 * 256;
+            return std::to_string(amount);
+        }
+        return (
+          boost::format("Banks[%1%][%2%]") % params[0]->GetUnsigned() % params[2]->GetUnsigned()
+        ).str();
     }
 }
 
+void FieldPartyInstruction::ProcessGOLDU(CodeGenerator* code_gen){
+    code_gen->AddOutputLine("Inventory.add_money(" + GilAmount(params_) + ")");
+}
+
 void FieldPartyInstruction::ProcessGOLDD(CodeGenerator* code_gen){
-    if (params_[0]->GetUnsigned() == 0){
-        // Add by constant amount, params 1-4 are a dword with the amount.
-        // Quick and dirty hex operation:
-        long amount = params_[1]->GetUnsigned() + params_[2]->GetUnsigned() * 256
-          + params_[3]->GetUnsigned() * 256 * 256 + params_[4]->GetUnsigned() * 256 * 256 * 256;
-        code_gen->AddOutputLine((boost::format("Inventory.add_money(%1%)") % amount).str());
-    }
-    else{
-        // Add by variable amount, param 0 is bank and 1 is address. 2-4 are zeroes, ignored.
-        code_gen->AddOutputLine((
-          boost::format("Inventory.add_money(Banks[%1%][%2%])")
-            % params_[0]->GetUnsigned() % params_[1]->GetUnsigned()
-        ).str());
-    }
+    code_gen->AddOutputLine("Inventory.remove_money(" + GilAmount(params_) + ")");
 }
 
 void FieldPartyInstruction::ProcessSTITM(CodeGenerator* code_gen){