Browse Source

Load, timers, and window modes.

- Loading games is now working, arbitrary maps and locations can be loaded.
- Location coordinates are saved as float, for more precission when loading.
- Implemented opcodes: STTIM, WSPCL and WMODE.
- Timers are implemented. They can be created, updated and displayed, with their own characteristic font.
- All variables read or written in the field scripts are now grouped in the memory banks. No more friendly names (but they remain as generated comments, for clarity).
- Location name is now persistant between map changes, unless changed by a script.
- Window modes: Transparent, translucent, and non-closeable windows are implemented.
- Name menu now resets cursor when shown multiple times.
- Basic implementation of bit operations in Lua scripts (still unused).
Iñigo Valentin 3 năm trước cách đây
mục cha
commit
d4a80968b6

+ 6 - 4
README.md

@@ -19,6 +19,8 @@ Note that, while these are actual screenshots of V-Gears, none of the images, sp
 |[<img src="https://v-gears.inigovalentin.com/img/screenshot/cloud.png" alt="Cloud model" width="250"/>](https://v-gears.inigovalentin.com/img/screenshot/cloud.png "Cloud model")|[<img src="https://v-gears.inigovalentin.com/img/screenshot/dialog.png" alt="dialog" width="250"/>](https://v-gears.inigovalentin.com/img/screenshot/dialog.png "Dialogs")|[<img src="https://v-gears.inigovalentin.com/img/screenshot/animation.png" alt="animation" width="250"/>](https://v-gears.inigovalentin.com/img/screenshot/animation.png "Animations")|
 |**Climbing**|**Mid-jump!**|**Materia**|
 |[<img src="https://v-gears.inigovalentin.com/img/screenshot/climb.png" alt="Climbing" width="250"/>](https://v-gears.inigovalentin.com/img/screenshot/climb.png "Climbing")|[<img src="https://v-gears.inigovalentin.com/img/screenshot/midjump.png" alt="Mid-jump" width="250"/>](https://v-gears.inigovalentin.com/img/screenshot/midjump.png "Mid-jump")|[<img src="https://v-gears.inigovalentin.com/img/screenshot/materia.png" alt="Materia" width="250"/>](https://v-gears.inigovalentin.com/img/screenshot/materia.png "Materia")|
+|**Transparencies**|**Timers**||
+|[<img src="https://v-gears.inigovalentin.com/img/screenshot/transparent_dialogs.png" alt="Transparency" width="250"/>](https://v-gears.inigovalentin.com/img/screenshot/transparent_dialogs.png "TRansparency")|[<img src="https://v-gears.inigovalentin.com/img/screenshot/timers.png" alt="Timers" width="250"/>](https://v-gears.inigovalentin.com/img/screenshot/timers.png "Timers")||
 
 ### Menus:
 |**Begin**|**Main**|**Name**|
@@ -48,11 +50,11 @@ Well, let's call it a work in progress... of a work in progress.
 |A few field maps|Most field maps|
 |Some menus|Some other menus|
 |Sound (mostly)|Battles, in general.|
-| |The world map, also in general|
-| |Saving / Loading|
-| |Minigames|
+|Saving / Loading|The world map, also in general|
+|Timers|Minigames|
+| |Party switching|
 | |Shops|
-| |Timers|
+| |Alternate controllable characters|
 | |Many other things|
 
 

+ 6 - 2
V-Gears-Installer/include/decompiler/field/FieldCodeGenerator.h

@@ -273,7 +273,9 @@ class FieldCodeGenerator : public CodeGenerator{
                         );
                         if (friendly_name.empty())
                             return (boost::format("Banks[%1%][%2%]") % bank % address).str();
-                        return (boost::format("Data.%1%") % friendly_name).str();
+                        return (
+                          boost::format("Banks[%1%][%2%]--[[%3%]]") % bank % address % friendly_name
+                        ).str();
                     }
                 case 5:
                 case 6:
@@ -282,7 +284,9 @@ class FieldCodeGenerator : public CodeGenerator{
                         const  auto friendly_name = formatter.GetFriendlyVarName(bank, address);
                         if (friendly_name.empty())
                             return (boost::format("Banks[%1%][%2%]") % bank % address).str();
-                        return "Data." + friendly_name;
+                        return (
+                          boost::format("Banks[%1%][%2%]--[[%3%]]") % bank % address % friendly_name
+                        ).str();
                     }
                 default:
                     return (

+ 90 - 0
V-Gears-Installer/include/decompiler/field/instruction/FieldWindowInstruction.h

@@ -41,6 +41,38 @@ class FieldWindowInstruction : public KernelCallInstruction{
 
     private:
 
+        /**
+         * Processes a STTIM opcode.
+         *
+         * Opcode: 0x38
+         * Short name: STTIM
+         * Long name: Set Timer
+         *
+         * Memory layout (6 bytes)
+         * |0x38|B1/B2|0/B3|H|M|S|
+         *
+         * Arguments
+         * - const Bit[4] B1: Bank to find hours value, or zero if hours (B1) is passed as a value.
+         * - const Bit[4] B2: Bank to find minutes value, or zero if minutes (B2) is passed as a
+         * value.
+         * - const Bit[4] 0: Zero.
+         * - const Bit[4] B3: Bank to find seconds value, or zero if seconds (B3) is passed as a
+         * value.
+         * - const UByte H: Hours, or address to find hours value, if B1 is non-zero.
+         * - const UByte M: Minutes, or address to find minutes value, if B2 is non-zero.
+         * - const UByte S: Seconds, or address to find seconds value, if B3 is non-zero.
+         *
+         * Sets the clock, as found in the WSPCL opcode. If the hours, minutes or seconds are
+         * specified in the argument, the corresponding B nybble is zero. Otherwise, the value for
+         * the time component is retrieved from the bank and address specified. The separate time
+         * components can be retrieved from memory or specified as a value, in the same argument
+         * list. Hours are not directly visible on the clock, as it only displays minutes and
+         * seconds. Hours are translated into minutes.
+         *
+         * @param[in,out] code_gen Code generator to append lines.
+         */
+        void ProcessSTTIM(CodeGenerator* code_gen);
+
         void ProcessMESSAGE(CodeGenerator* code_gen, const std::string& script_name);
 
         void ProcessMPNAM(CodeGenerator* code_gen, const std::string& script_name);
@@ -98,4 +130,62 @@ class FieldWindowInstruction : public KernelCallInstruction{
         void ProcessWINDOW(CodeGenerator* code_gen);
 
         void ProcessWCLSE(CodeGenerator* code_gen);
+
+        /**
+         * Processes a WSPCL opcode.
+         *
+         * Opcode: 0x36
+         * Short name: WSPCL
+         * Long name: Window Special (Numerical Display)
+         *
+         * Memory layout (5 bytes.)
+         * |0x36|W|T|X|Y|
+         *
+         * Arguments
+         *
+         * - const UByte W: WINDOW ID to apply the change to.
+         * - const UByte T: Type of display.
+         * - const UByte X: X-coordinate of the numerical display, relative to the top-left of the
+         * window.
+         * - const UByte Y: Y-coordinate of the numerical display, relative to the top-left of the
+         * window.
+         *
+         * Creates a numerical display inside the given window. The display can be either in the
+         * form of a clock, or a scoreboard with six digits. This only creates the numerical
+         * display; to actually show it, a MESSAGE or ASK command needs to be issued. Using a blank
+         * line of dialog will allow you to create a numerical display in the top-left of the
+         * window without field dialog hidden behind it. Alternatively, dialog can be shown along
+         * with the display by placing the display in an appropriate area of the window. To set the
+         * time for the clock variant, STTIM is used. To set the number for the numerical display,
+         * WNUMB is used.
+         *
+         * @param[in,out] code_gen Code generator to append lines.
+         */
+        void ProcessWSPCL(CodeGenerator* code_gen);
+
+        /**
+         * Processes a WMODE opcode.
+         *
+         * Opcode: 0x52
+         * Short name: WMODE
+         * Long name: Window Mode
+         *
+         * Memory layout (4 bytes)
+         * |0x52|N|M|C|
+         *
+         * Arguments
+         *
+         * - const UByte N: The ID of the window whose mode will be set.
+         * - const UByte M: Mode of the window.
+         * - const UByte C: Window permanency.
+         *
+         * Changes properties associated with the WINDOW whose ID is specified. The mode byte sets
+         * the style of the window, as detailed below. If the final byte is set to 1, the window
+         * cannot be closed by the player pushing [OK]. The mode of the window should be changed
+         * before it is displayed with MESSAGE or ASK, or the changes will not be visible unless
+         * the window is closed and reopened.
+         *
+         * @param[in,out] code_gen Code generator to append lines.
+         */
+        void ProcessWMODE(CodeGenerator* code_gen);
 };

+ 4 - 1
V-Gears-Installer/src/FieldDataInstaller.cpp

@@ -115,7 +115,10 @@ bool FieldDataInstaller::IsTestField(const Ogre::String& resource_name){
       || resource_name == "tin_1"
       || resource_name == "tin_2"
       || resource_name == "rootmap"
-      || resource_name == "md8_4"
+      || resource_name == "md8_1"
+      //|| resource_name == "md8_2"
+      //|| resource_name == "md8_3"
+      //|| resource_name == "md8_4"
     ){return true;}
     return false;
 }

+ 54 - 5
V-Gears-Installer/src/decompiler/field/instruction/FieldWindowInstruction.cpp

@@ -33,9 +33,9 @@ void FieldWindowInstruction::ProcessInst(
         case OPCODES::TUTOR: code_gen->WriteTodo(md.GetEntityName(), "TUTOR"); break;
         case OPCODES::WCLS: code_gen->WriteTodo(md.GetEntityName(), "WCLS"); break;
         case OPCODES::WSIZW: code_gen->WriteTodo(md.GetEntityName(), "WSIZW"); break;
-        case OPCODES::WSPCL: code_gen->WriteTodo(md.GetEntityName(), "WSPCL"); break;
+        case OPCODES::WSPCL: ProcessWSPCL(code_gen); break;
         case OPCODES::WNUMB: code_gen->WriteTodo(md.GetEntityName(), "WNUMB"); break;
-        case OPCODES::STTIM: code_gen->WriteTodo(md.GetEntityName(), "STTIM"); break;
+        case OPCODES::STTIM: ProcessSTTIM(code_gen); break;
         case OPCODES::MESSAGE: ProcessMESSAGE(code_gen, eng.GetScriptName()); break;
         case OPCODES::MPARA: code_gen->WriteTodo(md.GetEntityName(), "MPARA"); break;
         case OPCODES::MPRA2: code_gen->WriteTodo(md.GetEntityName(), "MPRA2"); break;
@@ -45,7 +45,7 @@ void FieldWindowInstruction::ProcessInst(
         case OPCODES::MENU2: ProcessMENU2(code_gen); break;
         case OPCODES::WINDOW: ProcessWINDOW(code_gen); break;
         case OPCODES::WMOVE: code_gen->WriteTodo(md.GetEntityName(), "WMOVE"); break;
-        case OPCODES::WMODE: code_gen->WriteTodo(md.GetEntityName(), "WMODE"); break;
+        case OPCODES::WMODE: ProcessWMODE(code_gen); break;
         case OPCODES::WREST: code_gen->WriteTodo(md.GetEntityName(), "WREST"); break;
         case OPCODES::WCLSE: ProcessWCLSE(code_gen); break;
         case OPCODES::WROW: code_gen->WriteTodo(md.GetEntityName(), "WROW"); break;
@@ -58,6 +58,34 @@ void FieldWindowInstruction::ProcessInst(
     }
 }
 
+void FieldWindowInstruction::ProcessWSPCL(CodeGenerator* code_gen){
+    auto window_id = params_[0]->GetUnsigned();
+    std::string numeric = "false";
+    std::string timer = "false";
+    if (params_[1]->GetUnsigned() == 1){
+        numeric = "true";
+        timer = "true";
+    }
+    else if (params_[1]->GetUnsigned() == 2){
+        numeric = "true";
+        timer = "false";
+    }
+    code_gen->AddOutputLine((
+      boost::format("dialog:set_numeric(\"%1%\", %2%, %3%) -- x: %4%,  y: %5%")
+      % window_id % numeric % timer % params_[2]->GetUnsigned() % params_[3]->GetUnsigned()
+    ).str());
+}
+
+void FieldWindowInstruction::ProcessWMODE(CodeGenerator* code_gen){
+    auto window_id = params_[0]->GetUnsigned();
+    std::string closeable = "true";
+    if (params_[2]->GetUnsigned() == 1) closeable = "false";
+    code_gen->AddOutputLine((
+      boost::format("dialog:set_mode(\"%1%\", %2%, %3%)")
+      % window_id % params_[1]->GetUnsigned() % closeable
+    ).str());
+}
+
 void FieldWindowInstruction::ProcessWINDOW(CodeGenerator* code_gen){
     // Initializes a new window. It won't be displayed until MESSAGE is used.
     auto windowId = params_[0]->GetUnsigned();
@@ -132,6 +160,28 @@ void FieldWindowInstruction::ProcessMENU(CodeGenerator* code_gen){
     }
 }
 
+void FieldWindowInstruction::ProcessSTTIM(CodeGenerator* code_gen){
+    std::cout << "STTIM params:\n";
+    for (int i = 0; i < params_.size(); i ++) std::cout << "    " << params_[i] << "\n";
+    std::cout << "STTIM params END\n";
+    FieldCodeGenerator* cg = static_cast<FieldCodeGenerator*>(code_gen);
+    int h = std::stoi(FieldCodeGenerator::FormatValueOrVariable(
+      cg->GetFormatter(), params_[0]->GetUnsigned(), params_[4]->GetSigned(),
+      FieldCodeGenerator::ValueType::Integer
+    ));
+    int m = std::stoi(FieldCodeGenerator::FormatValueOrVariable(
+      cg->GetFormatter(), params_[1]->GetUnsigned(), params_[5]->GetSigned(),
+      FieldCodeGenerator::ValueType::Integer
+    ));
+    int s = std::stoi(FieldCodeGenerator::FormatValueOrVariable(
+      cg->GetFormatter(), params_[3]->GetUnsigned(), params_[6]->GetSigned(),
+      FieldCodeGenerator::ValueType::Integer
+    ));
+    code_gen->AddOutputLine((
+      boost::format("Timer.set(%1%) -- %2%:%3%:%4%") % (3600 * h + 60 * m + s) % h % m % s
+    ).str());
+}
+
 void FieldWindowInstruction::ProcessMESSAGE(
   CodeGenerator* code_gen, const std::string& script_name
 ){
@@ -145,7 +195,6 @@ void FieldWindowInstruction::ProcessMESSAGE(
     code_gen->AddOutputLine(
       (boost::format("dialog:dialog_wait_for_close(\"%1%\")") % window_id).str()
     );
-
 }
 
 void FieldWindowInstruction::ProcessWCLSE(CodeGenerator* code_gen){
@@ -156,7 +205,7 @@ void FieldWindowInstruction::ProcessWCLSE(CodeGenerator* code_gen){
 
 void FieldWindowInstruction::ProcessMPNAM(CodeGenerator* code_gen, const std::string& script_name){
     code_gen->AddOutputLine((
-      boost::format("dialog:set_map_name(%1%_%2%)") % script_name % params_[0]->GetUnsigned()
+      boost::format("set_map_name(\"%1%_%2%\")") % script_name % params_[0]->GetUnsigned()
     ).str());
 }
 

+ 71 - 1
V-Gears/include/core/DialogsManager.h

@@ -92,7 +92,12 @@ struct MessageData{
       cursor_row_selected(0),
       cursor_row_current(0),
       cursor_row_first(0),
-      cursor_row_last(0)
+      cursor_row_last(0),
+      closeable(true),
+      visible(true),
+      translucent(false),
+      timer(false),
+      numeric(false)
     {}
 
     /**
@@ -200,6 +205,31 @@ struct MessageData{
      * Last chooseable line.
      */
     int cursor_row_last;
+
+    /**
+     * Indicates if the window is closeable.
+     */
+    bool closeable;
+
+    /**
+     * Indicates if the window background is visible.
+     */
+    bool visible;
+
+    /**
+     * Indicates if the window background is translucent.
+     */
+    bool translucent;
+
+    /**
+     * Indicates if the window is for a timer.
+     */
+    bool timer;
+
+    /**
+     * Indicates if the window background is for a number.
+     */
+    bool numeric;
 };
 
 /**
@@ -264,6 +294,34 @@ class DialogsManager : public Ogre::Singleton<DialogsManager>{
          */
         void SetText(const char* d_name, const char* text);
 
+        /**
+         * Sets or unsets a dialog window as numeric
+         *
+         * @param[in] d_name Name of the dialog.
+         * @param[in] numeric True to make it numeric, false otherwise.
+         * @param[in] timer True to use the window for times, false for regular numbers. If used
+         * for timer, the window will be used when calling {@see UpdateTimer}
+         */
+        void SetNumeric(const char* d_name, const bool numeric, const bool timer);
+
+        /**
+         * Updates the timer.
+         *
+         * Displays the formatted time in the timer window. To do so, a timer window must have been
+         * set as timer with {@see SetNumeric}
+         */
+        void UpdateTimer(const unsigned int seconds);
+
+        /**
+         * Sets the window mode.
+         *
+         * @param[in] d_name Name of the dialog.
+         * @param[in] bg Background mode. 1: No background/border. 2: Translucent background.
+         * Anything else: Normal.
+         * @param[in] closeable True if the window can be closed manually, false to lock it.
+         */
+        void SetMode(const char* d_name, const int bg, const bool closeable);
+
         /**
          * Syncs the dialog and makes the script wait until it is closed.
          *
@@ -421,5 +479,17 @@ class DialogsManager : public Ogre::Singleton<DialogsManager>{
          * Current map name.
          */
         std::string map_name_;
+
+        /**
+         * The ID of the window that contains the timer, is any
+         */
+        int timer_window_id_;
+
+        /**
+         * The number of seconds in the timer.
+         *
+         * Stored solely to not re-set the text if the time is the same.
+         */
+        unsigned int timer_seconds_;
 };
 

+ 15 - 8
V-Gears/include/core/Savemap.h

@@ -35,6 +35,13 @@ class Savemap{
          */
         ~Savemap();
 
+        /**
+         * Assignment operator.
+         *
+         * Copies a savemap into another, including control data.
+         *
+         * @param[in] The savemap to copy from.
+         */
         void operator = (const Savemap &to_copy);
 
         /**
@@ -197,7 +204,7 @@ class Savemap{
          * @param[in] Name of the location to show in the save slot.
          */
         void SetLocation(
-          const unsigned int x, const unsigned int y, const int z,
+          const float x, const float y, const float z,
           const unsigned int triangle, const int angle, std::string field, std::string name
         );
 
@@ -484,21 +491,21 @@ class Savemap{
          *
          * @return The X coordinate.
          */
-        unsigned int GetLocationX();
+        float GetLocationX();
 
         /**
          * Retrieves the Y coordinate of the player.
          *
          * @return The Y coordinate.
          */
-        unsigned int GetLocationY();
+        float GetLocationY();
 
         /**
          * Retrieves the Z coordinate of the player.
          *
-         * @return The Z coordinate. If it was not saved, -1.
+         * @return The Z coordinate. If it was not saved, it will be lower than 0.
          */
-        int GetLocationZ();
+        float GetLocationZ();
 
         /**
          * Retrieves the walkmesh triangle of the player.
@@ -1042,19 +1049,19 @@ class Savemap{
             /**
              * X coordinate.
              */
-            unsigned int x;
+            float x;
 
             /**
              * Y coordinate.
              */
-            unsigned int y;
+            float y;
 
             /**
              * Z coordinate.
              *
              * Usually unused.
              */
-            int z;
+            float z;
 
             /**
              * Triangle in the walkmesh;

+ 8 - 8
V-Gears/include/core/SavemapManager.h

@@ -231,14 +231,14 @@ class SavemapManager : public Ogre::Singleton<SavemapManager>{
          *
          * @param[in] x X coordinate.
          * @param[in] y Y coordinate.
-         * @param[in] z Z coordinate. It's optional, set it to -1 to ignore it.
+         * @param[in] z Z coordinate. It's optional, set it to lower than 0 to ignore it.
          * @param[in] triangle Walkmesh triangle ID.
          * @param[in] angle Facing direction.
          * @param[in] field Field map ID, or empty for the world map.
          * @param[in] Name of the location to show in the save slot.
          */
         void SetLocation(
-          const unsigned int x, const unsigned int y, const int z,
+          const float x, const float y, const float z,
           const unsigned int triangle, const int angle, const char* field, const char* name
         );
 
@@ -548,26 +548,26 @@ class SavemapManager : public Ogre::Singleton<SavemapManager>{
          * Retrieves the X coordinate of the player from a saved savemap.
          *
          * @param[in] slot The slot to read from.
-         * @return The X coordinate. If an invalid slot is specified, or if the slot is empty, 0.
+         * @return The X coordinate. If an invalid slot is specified, or if the slot is empty, 0.0.
          */
-        unsigned int GetSlotLocationX(const unsigned int slot);
+        float GetSlotLocationX(const unsigned int slot);
 
         /**
          * Retrieves the Y coordinate of the player from a saved savemap.
          *
          * @param[in] slot The slot to read from.
-         * @return The Y coordinate. If an invalid slot is specified, or if the slot is empty, 0.
+         * @return The Y coordinate. If an invalid slot is specified, or if the slot is empty, 0.0.
          */
-        unsigned int GetSlotLocationY(const unsigned int slot);
+        float GetSlotLocationY(const unsigned int slot);
 
         /**
          * Retrieves the Z coordinate of the player from a saved savemap.
          *
          * @param[in] slot The slot to read from.
          * @return The Z coordinate. If an invalid slot is specified, or if the slot is empty, or
-         * if the coordinate can be ignored -1.
+         * if the coordinate can be ignored, lower than 0.
          */
-        int GetSlotLocationZ(const unsigned int slot);
+        float GetSlotLocationZ(const unsigned int slot);
 
         /**
          * Retrieves the walkmesh triangle of the player from a saved savemap.

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

@@ -110,6 +110,7 @@ void ScriptManager::InitBinds(){
           .def("set_move_auto_speed", (void(Entity::*)(const float)) &Entity::SetMoveAutoSpeed)
           .def("get_move_auto_speed", (float(Entity::*)()) &Entity::GetMoveAutoSpeed)
           .def("get_move_triangle_id", (int(Entity::*)()) &Entity::GetMoveTriangleId)
+          .def("set_move_triangle_id", (void(Entity::*)(const int)) &Entity::SetMoveTriangleId)
           .def("move_auto_rotation", (void(Entity::*)(const bool)) &Entity::SetMoveAutoRotation)
           .def("move_auto_animation", (void(Entity::*)(const bool)) &Entity::SetMoveAutoAnimation)
           .def(
@@ -360,7 +361,7 @@ void ScriptManager::InitBinds(){
           .def(
             "set_location",
             (void(SavemapManager::*)(
-              const unsigned int, const unsigned int, const int, const unsigned int, const int,
+              const float, const float, const float, const unsigned int, const int,
               const char*, const char*
             )) &SavemapManager::SetLocation
           )
@@ -503,15 +504,15 @@ void ScriptManager::InitBinds(){
           )
           .def(
             "get_slot_location_x",
-            (unsigned int(SavemapManager::*)(const unsigned int)) &SavemapManager::GetSlotLocationX
+            (float(SavemapManager::*)(const unsigned int)) &SavemapManager::GetSlotLocationX
           )
           .def(
             "get_slot_location_y",
-            (unsigned int(SavemapManager::*)(const unsigned int)) &SavemapManager::GetSlotLocationX
+            (float(SavemapManager::*)(const unsigned int)) &SavemapManager::GetSlotLocationY
           )
           .def(
             "get_slot_location_z",
-            (int(SavemapManager::*)(const unsigned int)) &SavemapManager::GetSlotLocationZ
+            (float(SavemapManager::*)(const unsigned int)) &SavemapManager::GetSlotLocationZ
           )
           .def(
             "get_slot_location_triangle",
@@ -728,6 +729,20 @@ void ScriptManager::InitBinds(){
             "dialog_open",
             (void(DialogsManager::*)(const char*, int, int, int, int)) &DialogsManager::OpenDialog
           )
+          .def(
+            "set_numeric",
+            (void(DialogsManager::*)(const char*, const bool, const bool))
+              &DialogsManager::SetNumeric
+          )
+          .def(
+            "update_timer",
+            (void(DialogsManager::*)(const unsigned int)) &DialogsManager::UpdateTimer
+          )
+          .def(
+            "set_mode",
+            (void(DialogsManager::*)(const char*, const unsigned int, const bool))
+              &DialogsManager::SetMode
+          )
           .def(
             "dialog_set_text",
             (void(DialogsManager::*)(const char*, const char*)) &DialogsManager::SetText

+ 85 - 6
V-Gears/src/core/DialogsManager.cpp

@@ -38,7 +38,9 @@ DialogsManager::DialogsManager():
   up_pressed_(false),
   down_pressed_(false),
   limit_area_(NULL),
-  map_name_("")
+  map_name_(""),
+  timer_window_id_(-1),
+  timer_seconds_(0)
 {LOG_TRIVIAL("DialogsManager created.");}
 
 DialogsManager::~DialogsManager(){
@@ -143,11 +145,13 @@ void DialogsManager::Update(){
             case MS_OPENED:
                 {
                     if (messages_[i]->clickable == true && next_pressed_ == true){
-                        messages_[i]->auto_close = true;
-                        if ((messages_[i]->cursor != NULL) && (messages_[i]->show_cursor == true)){
-                            messages_[i]->show_cursor = false;
-                            messages_[i]->cursor_row_selected = messages_[i]->cursor_row_current;
-                            messages_[i]->cursor->SetVisible(false);
+                        if (messages_[i]->closeable == true){
+                            messages_[i]->auto_close = true;
+                            if ((messages_[i]->cursor != NULL) && (messages_[i]->show_cursor == true)){
+                                messages_[i]->show_cursor = false;
+                                messages_[i]->cursor_row_selected = messages_[i]->cursor_row_current;
+                                messages_[i]->cursor->SetVisible(false);
+                            }
                         }
                     }
                     if (AutoCloseCheck(i) == true) break;
@@ -272,6 +276,73 @@ void DialogsManager::SetText(const char* d_name, const char* text){
     }
 }
 
+void DialogsManager::SetNumeric(const char* d_name, const bool numeric, const bool timer){
+    int id = GetMessageId(d_name);
+    if (id == -1){
+        LOG_TRIVIAL("SetNumeric: dialog '" + Ogre::String(d_name) + "' doesn't exist.");
+        return;
+    }
+    if (numeric){
+        messages_[id]->text_area->SetFont("FFVIITimerFont");
+        if (timer){
+            messages_[id]->numeric = false;
+            messages_[id]->timer = true;
+            timer_window_id_ = id;
+        }
+        else{
+            messages_[id]->numeric = false;
+            messages_[id]->timer = true;
+        }
+    }
+    else{
+        messages_[id]->text_area->SetFont("FFVIIFont");
+        messages_[id]->numeric = false;
+        messages_[id]->timer = false;
+        timer_window_id_ = -1;
+    }
+}
+
+void DialogsManager::UpdateTimer(const unsigned int seconds){
+    if (
+      timer_window_id_ != - 1 && timer_window_id_ < messages_.size() && seconds != timer_seconds_
+    ){
+        std::string m = std::to_string(seconds / 60);
+        while (m.size() < 2) m = "0" + m;
+        std::string s = std::to_string(seconds % 60);
+        while (s.size() < 2) s = "0" + s;
+        messages_[timer_window_id_]->text_area->SetText(m + ":" + s);
+    }
+}
+
+void DialogsManager::SetMode(const char* d_name, const int bg, const bool closeable){
+    int id = GetMessageId(d_name);
+    if (id == -1){
+        LOG_TRIVIAL("SetMode: dialog '" + Ogre::String(d_name) + "' doesn't exist.");
+        return;
+    }
+    messages_[id]->closeable = closeable;
+    switch (bg){
+        case 1:
+            messages_[id]->visible = false;
+            messages_[id]->translucent = false;
+            for (int i = 0; i < messages_[id]->window->GetNumberOfChildren(); i ++)
+                messages_[id]->window->GetChild(i)->SetAlpha(0.0);
+            break;
+        case 2:
+            messages_[id]->visible = true;
+            messages_[id]->translucent = true;
+            if (messages_[id]->window->GetChild("Center") != nullptr)
+                messages_[id]->window->GetChild("Center")->SetAlpha(0.35);
+            break;
+        default:
+            messages_[id]->visible = true;
+            messages_[id]->translucent = false;
+            for (int i = 0; i < messages_[id]->window->GetNumberOfChildren(); i ++)
+                messages_[id]->window->GetChild(i)->SetAlpha(1.0);
+            break;
+    }
+}
+
 int DialogsManager::Sync(const char* d_name){
     int id = GetMessageId(d_name);
     if (id == -1){
@@ -379,6 +450,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.
+    messages_[id]->closeable = true;
+    messages_[id]->visible = true;
+    messages_[id]->translucent = false;
+    if (messages_[id]->window != NULL){
+        for (int i = 0; i < messages_[id]->window->GetNumberOfChildren(); i ++)
+            messages_[id]->window->GetChild(i)->SetAlpha(1.0);
+    }
 }
 
 int DialogsManager::GetMessageId(const char* d_name) const{

+ 23 - 29
V-Gears/src/core/Savemap.cpp

@@ -33,9 +33,9 @@ Savemap::Savemap():
     party_[0] = -1;
     party_[1] = -1;
     party_[2] = -1;
-    location_.x = 0;
-    location_.y = 0;
-    location_.z = -1;
+    location_.x = 0.0f;
+    location_.y = 0.0f;
+    location_.z = -1.0f;
     location_.triangle = 0;
     location_.angle = 0;
     location_.field = "";
@@ -241,18 +241,17 @@ void Savemap::SetESkillMateriaStash(
 }
 
 void Savemap::SetLocation(
-  const unsigned int x, const unsigned int y, const int z,
+  const float x, const float y, const float z,
   const unsigned int triangle, const int angle, std::string field, std::string name
 ){
-    if (z >= -1 && angle >= -360 && angle <= 360){
-        location_.x = x;
-        location_.y = y;
-        location_.z = z;
-        location_.triangle = triangle;
-        location_.angle = angle;
-        location_.field = field;
-        location_.name = name;
-    }
+
+    location_.x = x;
+    location_.y = y;
+    location_.z = z;
+    location_.triangle = triangle;
+    if (angle >= -360 && angle <= 360) location_.angle = angle;
+    location_.field = field;
+    location_.name = name;
 }
 
 void Savemap::SetSetting(const unsigned int key, const unsigned int value){
@@ -473,11 +472,11 @@ bool Savemap::IsStashAtPosESkillLearned(const unsigned int pos, const unsigned i
     else return materia_stash_[pos].enemy_skill_learned[skill];
 }
 
-unsigned int Savemap::GetLocationX(){return location_.x;}
+float Savemap::GetLocationX(){return location_.x;}
 
-unsigned int Savemap::GetLocationY(){return location_.y;}
+float Savemap::GetLocationY(){return location_.y;}
 
-int Savemap::GetLocationZ(){return location_.z;}
+float Savemap::GetLocationZ(){return location_.z;}
 
 unsigned int Savemap::GetLocationTriangle(){return location_.triangle;}
 
@@ -727,9 +726,10 @@ void Savemap::Write(int slot, std::string file_name){
 
     // Location.
     std::unique_ptr<TiXmlElement> location(new TiXmlElement("Location"));
-    location->SetAttribute("x", location_.x);
-    location->SetAttribute("y", location_.y);
-    if (location_.z >= 0) location->SetAttribute("z", location_.z);
+    location->SetDoubleAttribute("x", location_.x);
+    location->SetDoubleAttribute("y", location_.y);
+    if (location_.z < 0) location->SetAttribute("z", -1);
+    else location->SetDoubleAttribute("z", location_.z);
     location->SetAttribute("triangle", location_.triangle);
     location->SetAttribute("orientation", location_.angle);
     location->SetAttribute("field", location_.field);
@@ -1114,20 +1114,14 @@ void Savemap::Read(std::string file_name){
             if (seconds >= 0) countdown_ = seconds;
         }
         else if (node->Type() == TiXmlNode::TINYXML_ELEMENT && node->ValueStr() == "Location"){
+            node->ToElement()->QueryFloatAttribute("x", &location_.x);
+            node->ToElement()->QueryFloatAttribute("y", &location_.y);
+            node->ToElement()->QueryFloatAttribute("z", &location_.z);
             int val = -1;
-            node->ToElement()->QueryIntAttribute("x", &val);
-            if (val >= 0) location_.x = val;
-            val = -1;
-            node->ToElement()->QueryIntAttribute("y", &val);
-            if (val >= 0) location_.y = val;
-            val = -1;
-            node->ToElement()->QueryIntAttribute("z", &val);
-            if (val >= -1) location_.z = val;
-            val = -1;
             node->ToElement()->QueryIntAttribute("triangle", &val);
             if (val >= 0) location_.triangle = val;
             val = -1;
-            node->ToElement()->QueryIntAttribute("angle", &val);
+            node->ToElement()->QueryIntAttribute("orientation", &val);
             if (val >= -360 && val <=360) location_.angle = val;
             node->ToElement()->QueryStringAttribute("field", &location_.field);
             node->ToElement()->QueryStringAttribute("name", &location_.name);

+ 7 - 7
V-Gears/src/core/SavemapManager.cpp

@@ -176,7 +176,7 @@ void SavemapManager::SetESkillMateriaStash(
 }
 
 void SavemapManager::SetLocation(
-  const unsigned int x, const unsigned int y, const int z,
+  const float x, const float y, const float z,
   const unsigned int triangle, const int angle, const char* field, const char* name
 ){
     if (current_savemap_ == nullptr) current_savemap_ = new Savemap();
@@ -351,20 +351,20 @@ bool SavemapManager::IsSlotStashAtPosESkillLearned(const unsigned int slot, cons
     return saved_savemaps_[slot]->IsStashAtPosESkillLearned(pos, skill);
 }
 
-unsigned int SavemapManager::GetSlotLocationX(const unsigned int slot){
-    if (slot >= MAX_SAVE_SLOTS) return 0;
+float SavemapManager::GetSlotLocationX(const unsigned int slot){
+    if (slot >= MAX_SAVE_SLOTS) return 0.0f;
     if (savemaps_read_ == false) ReadSavemaps();
     return saved_savemaps_[slot]->GetLocationX();
 }
 
-unsigned int SavemapManager::GetSlotLocationY(const unsigned int slot){
-    if (slot >= MAX_SAVE_SLOTS) return 0;
+float SavemapManager::GetSlotLocationY(const unsigned int slot){
+    if (slot >= MAX_SAVE_SLOTS) return 0.0f;
     if (savemaps_read_ == false) ReadSavemaps();
     return saved_savemaps_[slot]->GetLocationY();
 }
 
-int SavemapManager::GetSlotLocationZ(const unsigned int slot){
-    if (slot >= MAX_SAVE_SLOTS) return -1;
+float SavemapManager::GetSlotLocationZ(const unsigned int slot){
+    if (slot >= MAX_SAVE_SLOTS) return -1.0f;
     if (savemaps_read_ == false) ReadSavemaps();
     return saved_savemaps_[slot]->GetLocationZ();
 }

+ 14 - 9
V-Gears/src/core/ScriptManager.cpp

@@ -169,6 +169,9 @@ void ScriptManager::Update(const ScriptManager::Type type){
         }
     }
 
+    // Before updating entities, call on_update on the system timer.
+    RunString("Timer.update()");
+
     for (unsigned int i = 0; i < script_entity_.size(); ++ i){
         if (script_entity_[i].type == type){
             if (script_entity_[i].queue.size() > 0){
@@ -190,7 +193,6 @@ void ScriptManager::Update(const ScriptManager::Type type){
                           type, current_script_id_.entity,
                           script_entity_[i].queue[0].state
                         );
-
                         if (
                           table.is_valid()
                           && luabind::type(table) == LUA_TTABLE
@@ -204,15 +206,18 @@ void ScriptManager::Update(const ScriptManager::Type type){
                                   script_entity_[i].queue[0].argument2.c_str()
                                 );
                             }
-                            catch(luabind::error& e){
-                                LOG_ERROR(
-                                  "LUA error in entity " + script_entity_[i].name + " ("
-                                  + current_script_id_.entity + ") " + " in function "
+                            catch (luabind::error& e){
+                                std::string msg = "LUA error in entity " + script_entity_[i].name
+                                  + " (" + current_script_id_.entity + ") " + " in function "
                                   + script_entity_[i].queue[0].function + " ("
-                                  + current_script_id_.function + "). Details: "
-                                  + Ogre::String(lua_tostring(script_entity_[i].queue[0].state, -1))
-                                  + " : " + e.what()
-                                );
+                                  + current_script_id_.function + ")";
+                                if (lua_tostring(script_entity_[i].queue[0].state, -1) != NULL){
+                                    msg = msg + ". Details: " + std::string(
+                                      lua_tostring(script_entity_[i].queue[0].state, -1)
+                                    );
+                                }
+                                msg = msg + ". Exception: " + e.what();
+                                LOG_ERROR(msg);
                             }
 
                             if (ret == 0){

+ 4 - 1
V-Gears/src/core/UiTextArea.cpp

@@ -312,7 +312,10 @@ void UiTextArea::SetFont(const Ogre::String& font){
     pass->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA);
     pass->setAlphaRejectFunction(Ogre::CMPF_GREATER);
     pass->setAlphaRejectValue(0);
-    Ogre::TextureUnitState* tex = pass->createTextureUnitState();
+    Ogre::TextureUnitState* tex;
+    if (pass->getTextureUnitStates().size() == 0)
+        tex = pass->createTextureUnitState();
+    else tex = pass->getTextureUnitState(0);//createTextureUnitState();
     tex->setTextureName(font_->GetImageName());
     tex->setNumMipmaps(-1);
     tex->setTextureFiltering(Ogre::TFO_NONE);

BIN
data/data/fonts/ffvii_timer.png


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

@@ -2,7 +2,7 @@
     <prototype name="MenuCharacterData">
         <text_area name="Name" x="0" y="0" font="FFVIIFont" visible="true" />
         <text_area name="Lv" x="0" y="5" text_name="CharacterDataLv" colour="0 0.9 0.9" font="FFVIIMenuFont" visible="true" />
-        <text_area name="LvNumber" x="18" y="13" font="FFVIIMenuDigits" visible="true" />
+        <text_area name="LvNumber" x="18" y="13" font="FFVIITimerFont" visible="true" />
         <text_area name="Hp" x="0" y="16" text_name="CharacterDataHp" colour="0 0.9 0.9" font="FFVIIMenuFont" visible="true" />
         <text_area name="HpCurrent" x="16" y="23" colour="0.9 0.9 0.9" font="FFVIIMenuDigits" visible="true" />
         <sprite name="HpSlash" image="images/icons/slash.png" x="51" y="23" width="4" height="8" visible="true" />

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

@@ -236,11 +236,26 @@ play_map_music = function(id)
     end
 end
 
+set_map_name = function(text_id)
+    dialog:set_map_name(text_id)
+    System["MapChanger"].location_name = dialog:get_map_name()
+end
+
 --- Utility to change fields and enter battles.
 System["MapChanger"] = {
+
+    --- ID of the map to change to.
     map_name = "",
+
+    --- Point of the map to change to.
     point_name = "",
 
+    --- ID of the current field.
+    current_map_name = "",
+
+    --- Location name, to display in the main menu and in save slots.
+    location_name = "",
+
     --- Jumps to a new map.
     ffvii_field = function(self)
         if self.map_name ~= "" then
@@ -248,6 +263,7 @@ System["MapChanger"] = {
             script:request_end_sync(Script.UI, "Fade", "fade_out", 0)
             map(self.map_name)
             player_lock(false) -- enable menu and pc movement after load map
+            self.current_map_name = self.map_name
             self.map_name = ""
             script:request_end_sync(Script.UI, "Fade", "fade_in", 0)
         end
@@ -264,7 +280,7 @@ System["MapChanger"] = {
             map(self.map_name)
             -- load battle ui
             -- load player entity
-            self.map_name = ""
+            --self.map_name = ""
             script:request_end_sync(Script.UI, "Fade", "fade_in", 0)
         end
 

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

@@ -167,7 +167,7 @@ UiContainer.MainMenu = {
         ui_manager:get_widget("MainMenu.Container.Menu.SaveText"):set_colour(0.4, 0.4, 0.4)
 
         -- Set location
-        ui_manager:get_widget("MainMenu.Container.Location.Text"):set_text((dialog:get_map_name()))
+        ui_manager:get_widget("MainMenu.Container.Location.Text"):set_text((System["MapChanger"].location_name))
 
         -- Set money
         local money_str = tostring(Inventory.money)

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

@@ -172,6 +172,8 @@ UiContainer.NameMenu = {
         ui_manager:get_widget("NameMenu"):set_visible(true)
         UiContainer.current_menu = "name"
         UiContainer.current_submenu = ""
+        self.options_position = 1
+        self.confirm_position = 1
         ui_manager:get_widget("NameMenu.Container.Character.Portrait"):set_image("images/characters/" .. tostring(self.id) .. ".png")
         if (string.lower(Characters[self.id].name) == "ex-soldier") then
             -- HACK: Hack for Ex-Soldier default. Can this be extracted from menu.lgp?

+ 34 - 4
data/data/scripts/save.lua

@@ -1,3 +1,6 @@
+--- Generates a control key for savegames.
+--
+-- It's a random character string, used to differenciate savegames.
 generate_control_key = function()
     ControlKey = ""
     for i = 1, 8 do
@@ -5,8 +8,12 @@ generate_control_key = function()
     end
 end
 
+--- Saves the game.
+--
+-- @param slot The slot to save at (0-15).
+-- @param force If false, exisiting game data will not be overwritten if control keys are different.
 save_game = function(slot, force)
-    savemap_manager:set_control_key("CONTROL_EXAMPLE") -- TODO Actual data
+    savemap_manager:set_control_key(ControlKey)
     savemap_manager:set_window_colours(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12) -- TODO Actual data
     savemap_manager:set_money(Inventory.money)
     savemap_manager:set_game_time(123456) -- TODO Actual data
@@ -35,7 +42,10 @@ save_game = function(slot, force)
         end
     end
     -- TODO: Materia stash, when implemented.
-    savemap_manager:set_location(10, 20, -1, 3, 120, "FIELD_ID", dialog:get_map_name()) -- TODO Actual data
+    local player = entity_manager:get_player_entity()
+    local x, y, z = player:get_position()
+
+    savemap_manager:set_location(x, y, z, player:get_move_triangle_id(), player:get_rotation(), System["MapChanger"].current_map_name, System["MapChanger"].location_name) -- TODO Actual data
     -- TODO Settings, when implemented
     for i = 0, 11 do
         if Characters[i] ~= nil and Characters[i].char_id ~= nil then
@@ -89,8 +99,10 @@ save_game = function(slot, force)
     savemap_manager:save(slot, force or true)
 end
 
+--- Laods a game.
+--
+-- @param slot The slot to load (0-15).
 load_game = function(slot)
-    print("LAOD SLOT " .. tostring(slot))
     -- Populate banks
     for b = 0, 14 do
         for a = 0, 254 do
@@ -240,7 +252,6 @@ load_game = function(slot)
             end
             -- TODO: Status
             Characters[c].status = {}
-            print("RECALC " .. tostring(c))
             Characters.recalculate_stats(c)
         end
 
@@ -249,6 +260,25 @@ load_game = function(slot)
     -- TODO: Location
     -- TODO: Update character stats
     -- TODO: Handle world map when implemented
+
+    console("camera_free false")
     load_field_map_request(savemap_manager:get_slot_location_field(slot), "")
+    System["MapChanger"].location_name = savemap_manager:get_slot_location_name(slot)
+    System["MapChanger"].current_map_name = savemap_manager:get_slot_location_field(slot)
+    -- TODO: For now, assume the PC is Cloub, but it must be saved.
+    local player = nil
+    while player == nil do
+        entity_manager:set_player_entity("Cloud")
+        player = entity_manager:get_player_entity()
+        script:wait(0)
+    end
+    --local player = entity_manager:get_entity("Cloud")
+    if player ~= nil then
+        player:set_position(savemap_manager:get_slot_location_x(slot), savemap_manager:get_slot_location_y(slot), savemap_manager:get_slot_location_z(slot))
+        player:set_move_triangle_id(savemap_manager:get_slot_location_triangle(slot))
+        player:set_rotation(savemap_manager:get_slot_location_angle(slot))
+        background2d:autoscroll_to_entity(player)
+        local x, y, z = player:get_position()
+    end
     MenuSettings.pause_available = true
 end

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

@@ -2,6 +2,37 @@ MAX_INVENTORY_SLOTS = 320
 MAX_KEY_SLOTS = 50
 MAX_MATERIA_SLOTS = 200
 
+Timer = {
+    -- TODO When the game clock is implemented, dont use FPS, call this every second.
+    FPS = 250,
+    time = 0,
+    frames = 0,
+    update = function()
+        if Timer.time > 0 or Timer.frames > 0 then
+            Timer.frames = Timer.frames - 1
+            if Timer.frames == 0 then
+                Timer.time = Timer.time - 1
+                dialog:update_timer(Timer.time)
+                if Timer.time > 0 then
+                    Timer.frames = Timer.FPS
+                end
+                Banks[1][20]--[[timer_hours]] = math.floor(Timer.time / 3600)
+                Banks[1][21]--[[timer_minutes]] = math.floor(Timer.time / 60) % 60
+                Banks[1][22]--[[timer_seconds]] = Timer.time % 60
+            end
+            Banks[1][23]--[[timer_frames]] = Timer.frames
+        end
+    end,
+    set = function(time)
+        Timer.time = tonumber(time)
+        Timer.frames = Timer.FPS
+        Banks[1][20]--[[timer_hours]] = math.floor(Timer.time / 3600)
+        Banks[1][21]--[[timer_minutes]] = math.floor(Timer.time / 60) % 60
+        Banks[1][22]--[[timer_seconds]] = Timer.time % 60
+        Banks[1][23]--[[timer_frames]] = Timer.frames
+    end
+}
+
 --- Export character names to the text manager.
 --
 -- Used to compose dialog with character names. Must be called on start and when a character is
@@ -693,3 +724,77 @@ Party.steal_materia = function()
     -- TODO: Implement.
     -- TODO: How is this reversed?
 end
+
+--- Implementation of OR bit operation.
+--
+-- @param a First operand.
+-- @param b Second operand.
+-- @return a | b.
+function bit_or(a, b)
+    local r, m, s = 0, 2 ^ 31
+    repeat
+        s, a, b = a + b + m, a % m, b % m
+        r, m = r + m * 1 % (s - a - b), m / 2
+    until m < 1
+    return r
+end
+
+--- Implementation of XOR bit operation.
+--
+-- @param a First operand.
+-- @param b Second operand.
+-- @return a ^^ b.
+function bit_xor(a, b)
+    local r, m, s = 0, 2 ^ 31
+    repeat
+        s, a, b = a + b + m, a % m, b % m
+        r, m = r + m * 3 % (s - a - b), m / 2
+    until m < 1
+    return r
+end
+
+--- Implementation of AND bit operation.
+--
+-- @param a First operand.
+-- @param b Second operand.
+-- @return a & b.
+function bit_and(a, b)
+    local r, m, s = 0, 2 ^ 31
+    repeat
+        s, a, b = a + b + m, a % m, b % m
+        r, m = r + m * 4 % (s - a - b), m / 2
+    until m < 1
+    return r
+end
+
+--- Obtains a bit from an address in a data bank
+--
+-- @param bank The bank (0-14)
+-- @param address The bank address (0-254)
+-- @param bit The bit to get (0-7)
+-- @return The bit value (1-0)
+function get_bank_bit(bank, address, bit)
+    -- TODO: Validation
+    local val = Banks[bank][address]
+    return get_byte_nth_bit(val, bit)
+end
+
+--- Obtains a bit from an unsigned byte
+--
+-- @param value The value (0-254)
+-- @param bit The bit to get (0-7)
+-- @return The bit value (1-0)
+function get_byte_nth_bit(value, n)
+    if n < 0 or n > 7 then
+        return 0
+    end
+    -- Convert to binary string
+    local s = ""
+    local temp = value
+    for i = 1, 8 do
+        s = s .. tostring(temp % 2)
+        temp = math.floor(temp / 2)
+    end
+    print("S:" .. s)
+    return tonumber(string.sub(s, n + 1, n + 1))
+end