Selaa lähdekoodia

Created a barebones battle manager and some handlers for battle-related XML files generated by the installer.

Iñigo Valentin 3 vuotta sitten
vanhempi
sitoutus
23c8ac4f6b

+ 4 - 0
src/CMakeLists.txt

@@ -82,6 +82,7 @@ set(VGEARS_SOURCE_FILES
     core/AudioManager.cpp
     core/Background2DAnimation.cpp
     core/Background2D.cpp
+    core/BattleManager.cpp
     core/CameraManager.cpp
     core/ConfigCmd.cpp
     core/ConfigCmdManager.cpp
@@ -91,6 +92,7 @@ set(VGEARS_SOURCE_FILES
     core/Console.cpp
     core/DebugDraw.cpp
     core/DialogsManager.cpp
+    core/Enemy.cpp
     core/EntityCollision.cpp
     core/Entity.cpp
     core/EntityDirection.cpp
@@ -132,9 +134,11 @@ set(VGEARS_SOURCE_FILES
     core/Utilites.cpp
     core/Walkmesh.cpp
     core/XmlBackground2DFile.cpp
+    core/XmlEnemyFile.cpp
     core/XmlFile.cpp
     core/XmlFontFile.cpp
     core/XmlFontsFile.cpp
+    core/XmlFormationFile.cpp
     core/XmlMapFile.cpp
     core/XmlMapsFile.cpp
     core/XmlMusicsFile.cpp

+ 139 - 0
src/core/BattleManager.cpp

@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2022 The V-Gears Team
+ *
+ * This file is part of V-Gears
+ *
+ * V-Gears is free software: you can redistribute it and/or modify it under
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, version 3.0 (GPLv3) of the License.
+ *
+ * V-Gears is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <iostream>
+#include <cmath>
+#include <OgreEntity.h>
+#include <OgreRoot.h>
+#include <OgreViewport.h>
+#include "core/BattleManager.h"
+#include "core/ConfigVar.h"
+#include "core/Logger.h"
+
+/**
+ * Battle manager singleton.
+ */
+template<>BattleManager *Ogre::Singleton<BattleManager>::msSingleton = nullptr;
+
+ConfigVar cv_debug_battle_grid("debug_battle_grid", "Draw debug battle grid", "false");
+ConfigVar cv_debug_battle_axis("debug_battle_axis", "Draw debug battle axis", "false");
+
+BattleManager::BattleManager(): paused_(false){
+    LOG_TRIVIAL("BattleManager created.");
+    scene_node_ = Ogre::Root::getSingleton().getSceneManager("Scene")
+      ->getRootSceneNode()->createChildSceneNode("BattleManager");
+}
+
+BattleManager::~BattleManager(){
+    Clear();
+    Ogre::Root::getSingleton().getSceneManager("Scene")->getRootSceneNode()->removeAndDestroyChild(
+      "BattleManager"
+    );
+    LOG_TRIVIAL("BattleManager destroyed.");
+}
+
+void BattleManager::Input(const VGears::Event& event){
+    // TODO: Change to battle input commands.
+    //background_2d_.InputDebug(event);
+    if (paused_ == true) return;
+    //if (event.type == VGears::ET_KEY_PRESS && event.event == "interact"){
+        // TODO
+    //}
+}
+
+void BattleManager::Update(){
+    UpdateDebug();
+    if (paused_ == true) return;
+
+    // TODO: Update all entity scripts
+    // for (unsigned int i = 0; i < party_.size(); ++ i) party_[i]->Update();
+    // for (unsigned int i = 0; i < enemy_.size(); ++ i) enemy_[i]->Update();
+    // TODO: Environment model update
+}
+
+void BattleManager::UpdateDebug(){
+    // TODO: Update all entity scripts
+    // for (unsigned int i = 0; i < party_.size(); ++ i) party_[i]->UpdateDebug();
+    // for (unsigned int i = 0; i < enemy_.size(); ++ i) enemy_[i]->UpdateDebug();
+    // TODO: Environment model update
+}
+
+void BattleManager::Clear(){
+    paused_ = false;
+    formation_id_ = -1;
+    next_formation_id_ = -1;
+    // TODO location_ = null;
+    // TODO camera_.clear();
+    initial_camera_ = 0;
+    layout_ = LAYOUT::NORMAL;
+    escape_difficulty_ = 0.0f;
+    arena_battle_ = false;
+    show_victory_pose_ = true;
+    show_spoils_ = true;
+    preemptive_ = true;
+    money_ = 0;
+    spoil_.clear();
+    // TODO enemy_.clear();
+    // TODO party_.clear();
+    scene_node_->removeAndDestroyAllChildren();
+}
+
+void BattleManager::ScriptSetPaused(const bool paused){paused_ = paused;}
+
+void BattleManager::SetLayout(const LAYOUT layout){
+    if (layout == LAYOUT::UNKNOWN_0 || layout == LAYOUT::UNKNOWN_1) layout_ = LAYOUT::NORMAL;
+    else layout_ = layout;
+}
+
+void BattleManager::SetFormationId(const int id){
+    if (id < 0) formation_id_ = -1;
+    else formation_id_ = id;
+}
+
+void BattleManager::SetNextFormationId(const int id){
+    if (id < 0) next_formation_id_ = -1;
+    else next_formation_id_ = id;
+}
+
+void BattleManager::SetEscapeability(const float difficulty){
+    if (difficulty < 0.0f || difficulty > 1.0f) escape_difficulty_ = -1.0f;
+    else escape_difficulty_ = difficulty;
+}
+
+void BattleManager::SetSkipVictoryPose(const bool skip){show_victory_pose_ = !skip;}
+
+void BattleManager::SetSkipSpoils(const bool skip){show_spoils_ = !skip;}
+
+void BattleManager::SetLocation(const int id, const Ogre::String name){
+    // TODO
+}
+
+void BattleManager::SetArenaBattle(const bool arena){arena_battle_ = arena;}
+
+void BattleManager::AddCamera(
+  const unsigned int id, const int x, const int y, const int z,
+  const int direction_x, const int direction_y, const int direction_z
+){
+    // TODO
+}
+
+void BattleManager::SetInitialCamera(const unsigned int id){initial_camera_ = id;}
+
+void BattleManager::AddEnemy(
+  const unsigned int id, const int x, const int y, const int z, const bool front_row,
+  const Ogre::String cover, const bool visible, const bool target, const int script
+){
+    // TODO
+}

+ 362 - 0
src/core/BattleManager.h

@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2022 The V-Gears Team
+ *
+ * This file is part of V-Gears
+ *
+ * V-Gears is free software: you can redistribute it and/or modify it under
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, version 3.0 (GPLv3) of the License.
+ *
+ * V-Gears is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#pragma once
+
+#include <OgreSingleton.h>
+#include "Entity.h"
+#include "EntityPoint.h"
+#include "EntityTrigger.h"
+#include "Event.h"
+#include "Walkmesh.h"
+
+/**
+ * The battle manager.
+ */
+class BattleManager : public Ogre::Singleton<BattleManager>{
+
+    public:
+
+        /**
+         * Possible battle layouts.
+         */
+        enum LAYOUT{
+
+            /**
+             * Normal battle.
+             *
+             * Party facing enemies, all ATBs filled at random, they start filling at the battle
+             * start.
+             */
+            NORMAL = 0,
+
+            /**
+             * Preemptive attack.
+             *
+             * Enemies are facing backwards, their ATBs start empty and only start to fill after an
+             * attack or after a set amount of time. PArty starts with full ATBs.
+             */
+            PREEMPTIVE = 1,
+
+            /**
+             * Party attacked from the back.
+             *
+             * Party members are facing backwards, with their rows reversed. Their ATBs are empty
+             * and only start to fill after an attack or after a set amount of time. Enemies start
+             * with full ATBs.
+             */
+            BACK_ATTACK = 2,
+
+            /**
+             * Party surrounds the enemy.
+             *
+             * Party members alternated on both sides of the battle. They are considered front row
+             * for offense and back row for defense. Their ATBs start full. Party-targeted commands
+             * only target one side. Enemies in the middle, facing in a random direction. Their
+             * ATBs start with a random amount. When attacked, an enemy turns to the attacker.
+             */
+            SIDE_ATTACK = 3,
+
+            /**
+             * Enemies surrounds the party.
+             *
+             * Enemies alternated on both sides of the battle. They are considered front row for
+             * offense and back row for defense. Their ATBs start full. Multiple-targeted commands
+             * only target one side. Party members in the middle, facing in a random direction.
+             * Their ATBs start with a random amount. When attacked, they turn to the attacker.
+             */
+            PINCER_ATTACK = 4,
+
+            /**
+             * Reserved for scripted battles with only one character.
+             *
+             * Works the same as {@see NORMAL}. In the original game, it's used for the Yuffie
+             * pagoda battles and Cloud's last battle against sephiroth
+             */
+            SOLO = 5,
+
+            /**
+             * Unknown.
+             *
+             * Never used in the original game.
+             */
+            UNKNOWN_0 = 6,
+
+            /**
+             * Unknown.
+             *
+             * Never used in the original game.
+             */
+            UNKNOWN_1 = 7,
+
+            /**
+             * Locked formation.
+             *
+             * Same as {@see NORMAL}, but every party member and enemy is forced in the front row,
+             * and the "Change" command is disabled.
+             */
+            LOCKED = 8,
+        };
+
+        /**
+         * Item earned during the battle.
+         */
+        struct Spoil {
+
+            /**
+             * Item ID.
+             */
+            unsigned int id;
+
+            /**
+             * Item quantity.
+             */
+            unsigned int qty;
+        };
+
+        /**
+         * Constructor.
+         */
+        BattleManager();
+
+        /**
+         * Destructor.
+         */
+        virtual ~BattleManager();
+
+        /**
+         * Handles an input event.
+         *
+         * @param[in] event Event to handle.
+         */
+        void Input(const VGears::Event& event);
+
+        /**
+         * Loads enemy info from the enemy XML enemy file.
+         */
+        void Load();
+
+        /**
+         * Updates the entities in the manager.
+         */
+        void Update();
+
+        /**
+         * Updates the entities in the manager with debug information.
+         *
+         * It's automatically called from {@see Update}.
+         */
+        void UpdateDebug();
+
+        /**
+         * Clears the entity manager.
+         */
+        void Clear();
+
+        /**
+         * Pauses or resumes the battle.
+         *
+         * @param[in] paused True to pause, false to resume.
+         */
+        void ScriptSetPaused(const bool paused);
+
+        /**
+         * Sets the battle layout.
+         *
+         * @param[in] The battle layout. {@see LAYOUT}. When one of the unknowns is specified, the
+         * normal will be applied.
+         */
+        void SetLayout(const LAYOUT layout);
+
+        /**
+         * Sets the battle formation ID.
+         *
+         * @param[in] id Battle formation ID. Negative values to clean the manager.
+         */
+        void SetFormationId(const int id);
+
+        /**
+         * Sets the battle formation ID for the next battle, if any.
+         *
+         * If set, a new battle will happen when this one is over, instead of returning to the
+         * field.
+         *
+         * @param[in] id Next battle formation ID. Negative values if none.
+         */
+        void SetNextFormationId(const int id);
+
+        /**
+         * Sets escaping data for the battle.
+         *
+         * @param[in] difficulty Values between 0 and 1 (exclusive) set the escaping difficulty
+         * from easy to hard. 0 Means the battle can be escaped instantly. Any other value means
+         * the battle can't be escaped from.
+         */
+        void SetEscapeability(const float difficulty);
+
+        /**
+         * Sets the battle to show or skip the victory pose.
+         */
+        void SetSkipVictoryPose(const bool skip);
+
+        /**
+         * Sets the battle to show or skip the spoils screens.
+         */
+        void SetSkipSpoils(const bool skip);
+
+        /**
+         * Sets the battle location.
+         *
+         * @param[in] id Location ID.
+         * @param[in] name Location name, for debug purposes only. Can be empty or null.
+         */
+        void SetLocation(const int id, const Ogre::String name);
+
+        /**
+         * Indicates if the battle is an arena battle.
+         *
+         * @param[in] arena True for arena battles, false for every other battles.
+         */
+        void SetArenaBattle(const bool arena);
+
+        /**
+         * Adds an camera for the battle.
+         *
+         * @param[in] id The camera ID.
+         * @param[in] x X coordinate for the camera.
+         * @param[in] y Y coordinate for the camera.
+         * @param[in] z Z coordinate for the camera.
+         * @param[in] direction_x X rotation for the camera.
+         * @param[in] direction_y Y rotation for the camera.
+         * @param[in] direction_z Z rotation for the camera.
+         */
+        void AddCamera(
+          const unsigned int id, const int x, const int y, const int z,
+          const int direction_x, const int direction_y, const int direction_z
+        );
+
+        /**
+         * Sets the initial camera.
+         *
+         * Can be set before configuring the camera, but if the battle starts and this index is
+         * invalid, it's undefined behavior.
+         *
+         * @param[in] ID of the initial camera.
+         */
+        void SetInitialCamera(const unsigned int id);
+
+        /**
+         * Adds an enemy to the enemy formation.
+         *
+         * @param[in] id The enemy ID.
+         * @param[in] x X coordinate for the enemy (center, cursor position).
+         * @param[in] y Y coordinate for the enemy (center, cursor position).
+         * @param[in] z Z coordinate for the enemy (center, cursor position).
+         * @param[in] front_row True to set the enemy in the front row, fals efor the back row.
+         * @param[in] cover Binary cover flags. {@see
+         * https://wiki.ffrtt.ru/index.php/FF7/Battle/Battle_Scenes#Binary_.22Cover_Flags.22}.
+         * @param[in] visible Enemy visibility.
+         * @param[in] target Indicates if the enemy can be targeted.
+         * @param[in] script Indicates the main script for the enemy.
+         */
+        void AddEnemy(
+          const unsigned int id, const int x, const int y, const int z, const bool front_row,
+          const Ogre::String cover, const bool visible, const bool target, const int script
+        );
+
+    private:
+
+        /**
+         * The scene node.
+         */
+        Ogre::SceneNode* scene_node_;
+
+        /**
+         * Indicates if the script execution is paused.
+         */
+        bool paused_;
+
+        /**
+         * The current battle formation ID.
+         */
+        int formation_id_;
+
+        /**
+         * The battle formation ID for the next battle, if any.
+         */
+        int next_formation_id_;
+
+        // TODO: Location
+        //BattleLocation location_;
+
+        // TODO: Camera list
+        //std::vector<BattleCamera> camera_;
+
+        /**
+         * Default camera ID
+         */
+        unsigned int initial_camera_;
+
+        /**
+         * Battle layout.
+         */
+        LAYOUT layout_;
+
+        /**
+         * Difficulty to escape the battle.
+         */
+        float escape_difficulty_;
+
+        /**
+         * Indicates if the current battle is an arena one.
+         */
+        bool arena_battle_;
+
+        /**
+         * Indicates if the victory pose must be shown at the battle end.
+         */
+        bool show_victory_pose_;
+
+        /**
+         * Indicates if the spoils screens must be shown at the battle end.
+         */
+        bool show_spoils_;
+
+        /**
+         * Indicates if the battle can be preemptive (not if it will definitely be).
+         */
+        bool preemptive_;
+
+        /**
+         * Money earned during the current battle.
+         */
+        unsigned int money_;
+
+        /**
+         * List of items earned during the battle.
+         */
+        std::vector<Spoil> spoil_;
+
+        /**
+         * List of enemies for the battle.
+         */
+        // TODO std::vector<Enemy> enemy_;
+
+        /**
+         * List of party members in the battle.
+         */
+        // TODO std::vector<Player> party_;
+
+};

+ 430 - 0
src/core/Enemy.cpp

@@ -0,0 +1,430 @@
+/*
+ * Copyright (C) 2022 The V-Gears Team
+ *
+ * This file is part of V-Gears
+ *
+ * V-Gears is free software: you can redistribute it and/or modify it under
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, version 3.0 (GPLv3) of the License.
+ *
+ * V-Gears is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <cmath>
+#include <OgreSceneNode.h>
+#include <OgreMaterialManager.h>
+#include "core/Enemy.h"
+#include "core/ConfigVar.h"
+#include "core/DebugDraw.h"
+#include "core/Logger.h"
+
+ConfigVar cv_debug_enemy("debug_enemy", "Draw enemy debug info", "0");
+
+Enemy::Enemy(const int enemy_id, Ogre::SceneNode* node):
+  enemy_id_(enemy_id),
+  name_("UNKNOWN"),
+  scene_node_(node),
+  height_(1.0f),
+  move_position_(Ogre::Vector3(0, 0, 0)),
+  move_auto_rotation_(true),
+  offset_position_start_(0.0f, 0.0f, 0.0f),
+  offset_position_end_(0.0f, 0.0f, 0.0f),
+  offset_type_(AT_NONE),
+  offset_seconds_(0.0f),
+  offset_current_seconds_(0.0f),
+  turn_direction_(TD_CLOSEST),
+  turn_direction_start_(0),
+  turn_direction_end_(0),
+  turn_type_(AT_NONE),
+  turn_seconds_(0.0f),
+  turn_current_seconds_(0.0f),
+  animation_speed_(1.0f),
+  animation_default_("Idle"),
+  animation_current_name_(""),
+  animation_auto_play_(true)
+{
+    model_root_node_ = scene_node_->createChildSceneNode();
+    model_node_ = model_root_node_->createChildSceneNode();
+    model_root_node_->setPosition(Ogre::Vector3::ZERO);
+    model_node_->setPosition(Ogre::Vector3::ZERO);
+    scene_node_->setPosition(Ogre::Vector3::ZERO);
+    LOG_TRIVIAL("Enemy " + std::to_string(enemy_id_) + " '" + name_ + "' created.");
+}
+
+Enemy::~Enemy(){
+    scene_node_->removeAndDestroyAllChildren();
+    LOG_TRIVIAL("Enemy " + std::to_string(enemy_id_) + " '" + name_ + "' destroyed.");
+}
+
+void Enemy::Update(){}
+
+void Enemy::UpdateDebug(){
+    int debug = cv_debug_enemy.GetI();
+    if (debug > 0){
+        DEBUG_DRAW.SetColour(Ogre::ColourValue::White);
+        DEBUG_DRAW.SetScreenSpace(true);
+        DEBUG_DRAW.SetTextAlignment(DEBUG_DRAW.CENTER);
+        DEBUG_DRAW.SetFadeDistance(40, 50);
+        Ogre::Vector3 enemy_pos = GetPosition();
+        DEBUG_DRAW.Text(enemy_pos, 0, 0, name_);
+        DEBUG_DRAW.Text(enemy_pos, 0, 12, animation_current_name_);
+    }
+}
+
+const int Enemy::GetEnemyId() const{return enemy_id_;}
+
+void Enemy::SetEnemyId(const int id){enemy_id_ = id;}
+
+const Ogre::String& Enemy::GetName() const{return name_;}
+
+void Enemy::SetName(const Ogre::String& name){name_ = name;}
+
+unsigned int Enemy::GetLevel() const{return level_;}
+
+void Enemy::SetLevel(const unsigned int level){level_ = level;}
+
+unsigned int Enemy::GetAp() const{return ap_;}
+
+void Enemy::SetAp(unsigned int ap){ap_ = ap;}
+
+unsigned int Enemy::GetExp() const{return exp_;}
+
+void Enemy::SetExp(const unsigned int exp){exp_ = exp;}
+
+unsigned int Enemy::GetMoney() const{return money_;}
+
+void Enemy::SetMoney(const unsigned int money){money_ = money;}
+
+unsigned int Enemy::GetMorph() const{return morph_;}
+
+void Enemy::SetMorph(const int morph){morph_ = std::max(-1, morph);}
+
+float Enemy::GetBackDamage() const{return back_damage_;}
+
+void Enemy::SetBackDamage(const float back_damage){
+    if (back_damage < 0.0f) back_damage_ = 0.0f;
+    else back_damage_ = back_damage;
+}
+
+unsigned int Enemy::GetStr() const{return str_;}
+
+void Enemy::SetStr(const unsigned int str){str_ = str;}
+
+unsigned int Enemy::GetDef() const{return def_;}
+
+void Enemy::SetDef(const unsigned int def){def_ = def;}
+
+unsigned int Enemy::GetMag() const{return mag_;}
+
+void Enemy::SetMag(const unsigned int mag){mag_ = mag;}
+
+unsigned int Enemy::GetSpr() const{return spr_;}
+
+void Enemy::SetSpr(const unsigned int spr){spr_ = spr;}
+
+unsigned int Enemy::GetDex() const{return dex_;}
+
+void Enemy::SetDex(const unsigned int dex){dex_ = dex;}
+
+unsigned int Enemy::GetLck() const{return lck_;}
+
+void Enemy::SetLck(const unsigned int lck){lck_ = lck;}
+
+unsigned int Enemy::GetHp() const{return hp_;}
+
+void Enemy::SetHp(const unsigned int hp){hp_ = std::min(hp, hp_max_);}
+
+unsigned int Enemy::GetHpMax() const{return hp_max_;}
+
+void Enemy::SetHpMax(const unsigned int hp_max){
+    hp_max_ = hp_max;
+    hp_ = std::min(hp_, hp_max_);
+}
+
+unsigned int Enemy::GetMp() const{return mp_;}
+
+void Enemy::SetMp(const unsigned int mp){mp_ = std::min(mp, mp_max_);}
+
+unsigned int Enemy::GetMpMax() const{return mp_max_;}
+
+void Enemy::SetMpMax(const unsigned int mp_max){
+    mp_max_ = mp_max;
+    mp_ = std::min(mp_, mp_max_);
+}
+
+void Enemy::SetPosition(const Ogre::Vector3& position){scene_node_->setPosition(position);}
+
+void Enemy::ScriptSetPosition(const float x, const float y, const float z){
+    SetPosition(Ogre::Vector3(x, y, z));
+    // Make the entity visible. TODO: Check if necessary.
+    SetVisible(true);
+}
+
+const Ogre::Vector3 Enemy::GetPosition() const{return scene_node_->getPosition();}
+
+void Enemy::ScriptGetPosition() const{
+    Ogre::Vector3 position = scene_node_->getPosition();
+    ScriptManager::getSingleton().AddValueToStack(position.x);
+    ScriptManager::getSingleton().AddValueToStack(position.y);
+    ScriptManager::getSingleton().AddValueToStack(position.z);
+}
+
+void Enemy::SetOffset(const Ogre::Vector3& position){
+    assert(model_root_node_);
+    model_root_node_->setPosition(position);
+}
+
+const Ogre::Vector3 Enemy::GetOffset() const{
+    assert(model_root_node_);
+    return model_root_node_->getPosition();
+}
+
+void Enemy::SetRotation(const Ogre::Degree& rotation){
+    assert(model_root_node_);
+    float angle
+      = rotation.valueDegrees() - Ogre::Math::Floor(rotation.valueDegrees() / 360.0f) * 360.0f;
+    if (angle < 0) angle = 360 + angle;
+    Ogre::Quaternion q;
+    Ogre::Vector3 vec = Ogre::Vector3::UNIT_Z;
+    q.FromAngleAxis(Ogre::Radian(Ogre::Degree(angle)), vec);
+    model_root_node_->setOrientation(q);
+}
+
+void Enemy::ScriptSetRotation(const float rotation){SetRotation(Ogre::Degree(rotation));}
+
+Ogre::Degree Enemy::GetRotation() const{
+    assert(model_root_node_);
+    Ogre::Quaternion q = model_root_node_->getOrientation();
+    Ogre::Degree temp;
+    Ogre::Vector3 vec = Ogre::Vector3::UNIT_Z;
+    q.ToAngleAxis(temp, vec);
+    return temp;
+}
+
+float Enemy::ScriptGetRotation() const{return GetRotation().valueDegrees();}
+
+void Enemy::setScale(const Ogre::Vector3 &scale) {
+    assert(model_root_node_);
+    model_root_node_->setScale(scale);
+}
+
+void Enemy::SetIndex(const int index){
+    assert(model_root_node_);
+    index_ = index;
+}
+
+int Enemy::GetIndex(){return index_;}
+
+void Enemy::setRootOrientation(const Ogre::Quaternion &root_orientation){
+    assert(model_node_);
+    model_node_->setOrientation(root_orientation);
+}
+
+float Enemy::GetHeight() const{return height_;}
+
+void Enemy::SetMoveSpeed(const float speed){move_speed_ = speed;}
+
+float Enemy::GetMoveSpeed() const{return move_speed_;}
+
+void Enemy::SetMovePosition(const Ogre::Vector3& target){move_position_ = target;}
+
+const Ogre::Vector3& Enemy::GetMovePosition() const{return move_position_;}
+
+float Enemy::GetMoveStopDistance() const{return move_stop_distance_;}
+
+void Enemy::ScriptMoveToPosition(const float x, const float y){
+    move_position_ = Ogre::Vector3(x, y, 0);
+    move_stop_distance_ = 0;
+    LOG_TRIVIAL(
+      "Enemy " + std::to_string(enemy_id_) + " '" + name_ + "' set move to position '"
+      + Ogre::StringConverter::toString(move_position_) + "'."
+    );
+}
+
+void Enemy::UnsetMove(){
+    move_stop_distance_ = 0;
+    sync_.clear();
+}
+
+void Enemy::ScriptOffsetToPosition(
+  const float x, const float y, const float z, const ActionType type, const float seconds
+){
+    LOG_TRIVIAL(
+      "Enemy " + std::to_string(enemy_id_) +" '" + name_ + "' set offset to position '"
+      + Ogre::StringConverter::toString(Ogre::Vector3(x, y, z)) + "'."
+    );
+    Ogre::Vector3 position = Ogre::Vector3(x, y, z);
+    if (type == AT_NONE){
+        this->SetOffset(position);
+        return;
+    }
+    offset_position_start_ = GetOffset();
+    offset_position_end_ = position;
+    offset_type_ = type;
+    offset_seconds_ = seconds;
+    offset_current_seconds_ = 0;
+}
+
+int Enemy::ScriptOffsetSync(){
+    // TODO
+    /*ScriptId script = ScriptManager::getSingleton().GetCurrentScriptId();
+    LOG_TRIVIAL(
+      "Wait entity '" + name_ + "' offset for function '"
+      + script.function + "' in script entity '" + script.entity + "'."
+    );
+    offset_sync_.push_back(script);
+    return -1;*/
+}
+
+void Enemy::UnsetOffset(){
+    // TODO
+    /*offset_type_ = AT_NONE;
+    for (unsigned int i = 0; i < offset_sync_.size(); ++ i)
+        ScriptManager::getSingleton().ContinueScriptExecution(offset_sync_[i]);
+    offset_sync_.clear();*/
+}
+
+const Ogre::Vector3& Enemy::GetOffsetPositionStart() const{return offset_position_start_;}
+
+const Ogre::Vector3& Enemy::GetOffsetPositionEnd() const{return offset_position_end_;}
+
+Enemy::ActionType Enemy::GetOffsetType() const{return offset_type_;}
+
+float Enemy::GetOffsetSeconds() const{return offset_seconds_;}
+
+void Enemy::SetOffsetCurrentSeconds(const float seconds){offset_current_seconds_ = seconds;}
+
+float Enemy::GetOffsetCurrentSeconds() const{return offset_current_seconds_;}
+
+void Enemy::ScriptTurnToDirection(
+  const float direction, const TurnDirection turn_direction,
+  const ActionType turn_type, const float seconds
+){
+    SetTurn(Ogre::Degree(direction), turn_direction, turn_type, seconds);
+    LOG_TRIVIAL(
+      "Enemy " + std::to_string(enemy_id_) + " '" + name_ + "' turn to angle '"
+      + Ogre::StringConverter::toString(direction) + "'."
+    );
+}
+
+int Enemy::ScriptTurnSync(){
+    // TODO
+    /*ScriptId script = ScriptManager::getSingleton().GetCurrentScriptId();
+    LOG_TRIVIAL(
+      "Wait enemy " + std::to_string(enemy_id_) + " '" + name_ + "' turn for function '"
+      + script.function + "' in script entity '" + script.entity + "'."
+    );
+    turn_sync_.push_back(script);
+    return -1;*/
+}
+
+void Enemy::SetTurn(
+  const Ogre::Degree& direction_to, const TurnDirection turn_direction,
+  const ActionType turn_type, const float seconds
+){
+    if (turn_type == AT_NONE){
+        SetRotation(direction_to);
+        return;
+    }
+    turn_direction_ = turn_direction;
+    Ogre::Degree angle_start = GetRotation();
+    Ogre::Degree angle_end = CalculateTurnAngle(angle_start, direction_to);
+    turn_direction_start_ = angle_start;
+    turn_direction_end_ = angle_end;
+    turn_type_ = turn_type;
+    turn_seconds_ = seconds;
+    turn_current_seconds_ = 0;
+}
+
+void Enemy::UnsetTurn(){
+    turn_type_ = AT_NONE;
+    //for (unsigned int i = 0; i < turn_sync_.size(); ++ i)
+    //    ScriptManager::getSingleton().ContinueScriptExecution(turn_sync_[i]);
+    turn_sync_.clear();
+}
+
+Ogre::Degree Enemy::CalculateTurnAngle(const Ogre::Degree& start, const Ogre::Degree& end) const{
+    Ogre::Degree ret = end;
+    switch(turn_direction_){
+        case TD_CLOCKWISE:
+            if (end <= start) ret = end + Ogre::Degree(360);
+            break;
+        case TD_ANTICLOCKWISE:
+            if (end >= start) ret = end - Ogre::Degree(360);
+            break;
+        case TD_CLOSEST:
+            Ogre::Degree delta = end - start;
+            delta = (delta < Ogre::Degree(0)) ? -delta : delta;
+            if (delta > Ogre::Degree(180)){
+                if (start < end) ret = end - Ogre::Degree(360);
+                else ret = end + Ogre::Degree(360);
+            }
+        break;
+    }
+    return ret;
+}
+
+Ogre::Degree Enemy::GetTurnDirectionStart() const{return turn_direction_start_;}
+
+Ogre::Degree Enemy::GetTurnDirectionEnd() const{return turn_direction_end_;}
+
+Enemy::ActionType Enemy::GetTurnType() const{return turn_type_;}
+
+float Enemy::GetTurnSeconds() const{return turn_seconds_;}
+
+void Enemy::SetTurnCurrentSeconds(const float seconds){turn_current_seconds_ = seconds;}
+
+float Enemy::GetTurnCurrentSeconds() const{return turn_current_seconds_;}
+
+void Enemy::ScriptSetAnimationSpeed(const float speed){animation_speed_ = speed;}
+
+const Ogre::String& Enemy::GetDefaultAnimationName() const{return animation_default_;}
+
+const Ogre::String& Enemy::GetCurrentAnimationName() const{return animation_current_name_;}
+
+Enemy::AnimationState Enemy::GetAnimationState() const{return animation_state_;}
+
+void Enemy::ScriptPlayAnimation(const char* name){
+    PlayAnimation(Ogre::String(name), Enemy::REQUESTED_ANIMATION, Enemy::PLAY_DEFAULT, 0, -1);
+}
+
+void Enemy::ScriptPlayAnimationStop(const char* name){
+    PlayAnimation(Ogre::String(name), Enemy::REQUESTED_ANIMATION, Enemy::PLAY_ONCE, 0, -1);
+}
+
+void Enemy::ScriptPlayAnimation(const char* name, const float start, const float end){
+    PlayAnimation(
+      Ogre::String(name), Enemy::REQUESTED_ANIMATION, Enemy::PLAY_DEFAULT, start, end
+    );
+}
+
+void Enemy::ScriptPlayAnimationStop(const char* name, const float start, const float end){
+    PlayAnimation(Ogre::String(name), Enemy::REQUESTED_ANIMATION, Enemy::PLAY_ONCE, start, end);
+}
+
+void Enemy::ScriptSetDefaultAnimation(const char* animation){
+    animation_default_ = Ogre::String(animation);
+}
+
+int Enemy::ScriptAnimationSync(){
+    // TODO
+    /*ScriptId script = ScriptManager::getSingleton().GetCurrentScriptId();
+    LOG_TRIVIAL(
+      "Wait entity '" + name_ + "' animation for function '"
+      + script.function + "' in script entity '" + script.entity + "'."
+    );
+    animation_sync_.push_back(script);*/
+    return -1;
+}
+
+Ogre::Degree Enemy::GetDirectionToPoint(Ogre::Vector2 point) const{
+    Ogre::Vector3 current_point = GetPosition();
+    Ogre::Vector2 up(0.0f, -1.0f);
+    Ogre::Vector2 dir(point.x - current_point.x, point.y - current_point.y);
+    // Angle between vectors
+    Ogre::Degree angle(Ogre::Radian(acosf(dir.dotProduct(up) / (dir.length() * up.length()))));
+    return (dir.x < 0) ? Ogre::Degree(360) - angle : angle;
+}

+ 1143 - 0
src/core/Enemy.h

@@ -0,0 +1,1143 @@
+/*
+ * Copyright (C) 2022 The V-Gears Team
+ *
+ * This file is part of V-Gears
+ *
+ * V-Gears is free software: you can redistribute it and/or modify it under
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, version 3.0 (GPLv3) of the License.
+ *
+ * V-Gears is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#pragma once
+
+#include <OgreString.h>
+#include "ScriptManager.h"
+
+/**
+ * Any enemy in a battle.
+ */
+class Enemy{
+
+    public:
+
+        /**
+         * Enemy animation states.
+         */
+        enum AnimationState{
+
+            /**
+             * An animation has been requested.
+             */
+            REQUESTED_ANIMATION,
+
+            /**
+             * An animation is set to play automatically.
+             */
+            AUTO_ANIMATION
+        };
+
+        /**
+         * Types of animations.
+         */
+        enum AnimationPlayType{
+
+            /**
+             * Default animation mode.
+             *
+             * @todo Same as PLAY_ONCE?
+             */
+            PLAY_DEFAULT,
+
+            /**
+             * Play the animation once, then stop.
+             */
+            PLAY_ONCE,
+
+            /**
+             * Play an animation in a continous loop.
+             */
+            PLAY_LOOPED
+        };
+
+
+        /**
+         * Action types.
+         */
+        enum ActionType{
+
+            /**
+             * No action.
+             */
+            AT_NONE,
+
+            /**
+             * Linear action.
+             *
+             * It starts and ends at full speed.
+             */
+            AT_LINEAR,
+
+            /**
+             * Smooth action.
+             *
+             * The action speed steadily increases when started, and it steadily
+             * decreases before the end.
+             */
+            AT_SMOOTH
+        };
+
+        /**
+         * The direction for an entity turn.
+         */
+        enum TurnDirection{
+
+            /**
+             * Turn clockwise.
+             */
+            TD_CLOCKWISE,
+
+            /**
+             * Turn anticlockwise.
+             */
+            TD_ANTICLOCKWISE,
+
+            /**
+             * Choose direction automatically.
+             *
+             * The direction in which the turn is shorter will be selected.
+             */
+            TD_CLOSEST
+        };
+
+        /**
+         * Constructor.
+         *
+         * @param[in] name Enemy ID.
+         * @param[in] node Scene node to which the enemy should be attached.
+         */
+        Enemy(const int enemy_id, Ogre::SceneNode* node);
+
+        /**
+         * Destructor.
+         */
+        virtual ~Enemy();
+
+        /**
+         * Updates the enemy status.
+         */
+        virtual void Update();
+
+        /**
+         * Updates the enemy status with debug information.
+         */
+        virtual void UpdateDebug();
+
+        /**
+         * Retrieves the enemy ID.
+         *
+         * @return The enemy ID, or -1 if it's not loaded.
+         */
+        const int GetEnemyId() const;
+
+        /**
+         * Sets the enemy ID.
+         *
+         * @param[in] id The enemy ID.
+         */
+        void SetEnemyId(const int id);
+
+        /**
+         * Retrieves the enemy name.
+         *
+         * @return The enemy name.
+         */
+        const Ogre::String& GetName() const;
+
+        /**
+         * Sets the enemy name.
+         *
+         * @param[in] name The enemy name.
+         */
+        void SetName(const Ogre::String& name);
+
+        /**
+         * Retrieves the enemy level.
+         *
+         * @return The enemy level.
+         */
+        unsigned int GetLevel() const;
+
+        /**
+         * Sets the enemy level.
+         *
+         * @param[in] level The enemy level.
+         */
+        void SetLevel(const unsigned int level);
+
+        /**
+         * Retrieves the AP gain upon defeating the enemy.
+         *
+         * @return The AP gain.
+         */
+        unsigned int GetAp() const;
+
+        /**
+         * Sets the AP gain upon defeating the enemy.
+         *
+         * @param[in] ap The AP gain.
+         */
+        void SetAp(unsigned int ap);
+
+        /**
+         * Retrieves the EXP gain upon defeating the enemy.
+         *
+         * @return The EXP gain.
+         */
+        unsigned int GetExp() const;
+
+        /**
+         * Sets the EXP gain upon defeating the enemy.
+         *
+         * @param[in] exp The EXP gain.
+         */
+        void SetExp(const unsigned int exp);
+
+        /**
+         * Retrieves the money gain upon defeating the enemy.
+         *
+         * @return The money gain.
+         */
+        unsigned int GetMoney() const;
+
+        /**
+         * Sets the money gain upon defeating the enemy.
+         *
+         * @param[in] money The money gain.
+         */
+        void SetMoney(const unsigned int money);
+
+        /**
+         * Retrieves the ID of the item the monster can be morphed into.
+         *
+         * @return ID of the item the enemy can be morphed into, -1 if none.
+         */
+        unsigned int GetMorph() const;
+
+        /**
+         * Sets the ID of the item the monster can be morphed into.
+         *
+         * @param[in] morph ID of the item the enemy can be morphed into, -1 if none.
+         */
+        void SetMorph(const int morph);
+
+        /**
+         * Retrieves the multiplier for the damage the enemy receives when attacked from the back.
+         *
+         * @return Back attack damage multiplier.
+         */
+        float GetBackDamage() const;
+
+        /**
+         * Sets the multiplier for the damage the enemy receives when attacked from the back.
+         *
+         * @param[in] back_damage Back attack damage multiplier, 0 or positive.
+         */
+        void SetBackDamage(const float back_damage);
+
+        /**
+         * Retrieves the enemy's strength stat.
+         *
+         * @return The strength stat.
+         */
+        unsigned int GetStr() const;
+
+        /**
+         * Sets the enemy's strength stat.
+         *
+         * @param[in] str The strength stat.
+         */
+        void SetStr(const unsigned int str);
+
+        /**
+         * Retrieves the enemy's defense stat.
+         *
+         * @return The defense stat.
+         */
+        unsigned int GetDef() const;
+
+        /**
+         * Sets the enemy's defense stat.
+         *
+         * @param[in] def The defense stat.
+         */
+        void SetDef(const unsigned int def);
+
+        /**
+         * Retrieves the enemy's magic stat.
+         *
+         * @return The magic stat.
+         */
+        unsigned int GetMag() const;
+
+        /**
+         * Sets the enemy's magic stat.
+         *
+         * @param[in] mag The magic stat.
+         */
+        void SetMag(const unsigned int mag);
+
+        /**
+         * Retrieves the enemy's spirit stat.
+         *
+         * @return The spirit stat.
+         */
+        unsigned int GetSpr() const;
+
+        /**
+         * Sets the enemy's spirit stat.
+         *
+         * @param[in] spr The spirit stat.
+         */
+        void SetSpr(const unsigned int spr);
+
+        /**
+         * Retrieves the enemy's dexterity stat.
+         *
+         * @return The dexterity stat.
+         */
+        unsigned int GetDex() const;
+
+        /**
+         * Sets the enemy's dexterity stat.
+         *
+         * @param[in] dex The dexterity stat.
+         */
+        void SetDex(const unsigned int dex);
+
+        /**
+         * Retrieves the enemy's luck stat.
+         *
+         * @return The luck stat.
+         */
+        unsigned int GetLck() const;
+
+        /**
+         * Sets the enemy's luck stat.
+         *
+         * @param[in] lck The luck stat.
+         */
+        void SetLck(const unsigned int lck);
+
+        /**
+         * Retrieves the enemy's current HP.
+         *
+         * @return The current HP.
+         */
+        unsigned int GetHp() const;
+
+        /**
+         * Sets the enemy's current HP.
+         *
+         * If set to higher then max HP reported by {@see GetHpMax()}, it will be capped to that
+         * value.
+         *
+         * @param[in] hp The current HP.
+         */
+        void SetHp(const unsigned int hp);
+
+        /**
+         * Retrieves the enemy's max HP.
+         *
+         * @return The max HP.
+         */
+        unsigned int GetHpMax() const;
+
+        /**
+         * Sets the enemy's max HP.
+         *
+         * If the new max HP is higher than the current HP, the current HP will be set to the new
+         * max HP.
+         *
+         * @param[in] str The strength stat.
+         */
+        void SetHpMax(const unsigned int hp_max);
+
+        /**
+         * Retrieves the enemy's current MP.
+         *
+         * @return The current MP.
+         */
+        unsigned int GetMp() const;
+
+        /**
+         * Sets the enemy's current MP.
+         *
+         * If set to higher then max MP reported by {@see GetMpMax()}, it will be capped to that
+         * value.
+         *
+         * @param[in] mp The current MP.
+         */
+        void SetMp(const unsigned int mp);
+
+        /**
+         * Retrieves the enemy's max MP.
+         *
+         * @return The max MP.
+         */
+        unsigned int GetMpMax() const;
+
+        /**
+         * Sets the enemy's max MP.
+         *
+         * If the new max MP is higher than the current MP, the current MP will be set to the new
+         * max MP.
+         *
+         * @param[in] str The strength stat.
+         */
+        void SetMpMax(const unsigned int mp_max);
+
+        /**
+         * Sets the enemy position.
+         *
+         * @param[in] position Enemy's new position.
+         */
+        void SetPosition(const Ogre::Vector3& position);
+
+        /**
+         * Sets the enemy position.
+         *
+         * @param[in] x Enemy's new position X coordinate.
+         * @param[in] y Enemy's new position Y coordinate.
+         * @param[in] z Enemy's new position Z coordinate.
+         */
+        void ScriptSetPosition(const float x, const float y, const float z);
+
+        /**
+         * Retrieves the enemy position.
+         *
+         * @return The enemy position.
+         */
+        const Ogre::Vector3 GetPosition() const;
+
+        /**
+         * Informs the script manager of the enemy position.
+         */
+        void ScriptGetPosition() const;
+
+        /**
+         * Sets the enemy offset.
+         *
+         * The offset is relative to it's defined position.
+         *
+         * @param[in] position The enemy's offset.
+         */
+        void SetOffset(const Ogre::Vector3& position);
+
+        /**
+         * Retrieves the enemy offset.
+         *
+         * The offset is relative to it's defined position.
+         *
+         * @return The enemy's offset.
+         */
+        const Ogre::Vector3 GetOffset() const;
+
+        /**
+         * Sets the enemy rotation.
+         *
+         * @param[in] rotation The enemy rotation.
+         */
+        void SetRotation(const Ogre::Degree& rotation);
+
+        /**
+         * Sets the enemy rotation.
+         *
+         * @param[in] rotation The enemy rotation, in degrees (0-360).
+         */
+        void ScriptSetRotation(const float rotation);
+
+        /**
+         * Retrieves the enemy rotation.
+         *
+         * @return The enemy rotation, in degrees.
+         */
+        Ogre::Degree GetRotation() const;
+
+        /**
+         * Retrieves the enemy rotation.
+         *
+         * @return The enemy rotation (0-360).
+         */
+        float ScriptGetRotation() const;
+
+        /**
+         * Sets the enemy scale.
+         *
+         * @param[in] scale Three dimensional scale.
+         */
+        virtual void setScale(const Ogre::Vector3 &scale);
+
+        /**
+         * Sets the enemy index in the field.
+         *
+         * @param[in] index Index of the enemy.
+         */
+        void SetIndex(const int index);
+
+        /**
+         * Retrieves the enemy index in the field.
+         *
+         * @return Index of the enemy.
+         */
+        int GetIndex();
+
+        /**
+         * Sets the enemy's absolute orientation.
+         *
+         * @param[in] root_orientation The enemy's new orientation.
+         */
+        virtual void setRootOrientation(const Ogre::Quaternion &root_orientation);
+
+        /**
+         * Retrieves the enemy's height.
+         *
+         * @return The enemy's height.
+         */
+        float GetHeight() const;
+
+        /**
+         * Makes the enemy visible or invisible.
+         *
+         * Invisible entities can't be interacted with.
+         *
+         * @param[in] visible True to make the unit visible, false to make it invisible.
+         */
+        virtual void SetVisible(const bool visible) = 0;
+
+        /**
+         * Checks if the enemy is visible or invisible.
+         *
+         * Invisible entities can't be interacted with.
+         *
+         * @return True if the unit is visible, false if it's invisible.
+         */
+        virtual bool IsVisible() const = 0;
+
+        /**
+         * Sets the enemy's movement destination position.
+         *
+         * @param[in] target The destination position.
+         */
+        void SetMovePosition(const Ogre::Vector3& target);
+
+        /**
+         * Sets the enemy's model movement speed.
+         *
+         * @param[in] speed Movement speed.
+         */
+        void SetMoveSpeed(const float speed);
+
+        /**
+         * Retrieves the enemy's automatic movement speed.
+         *
+         * @return The movement speed.
+         */
+        float GetMoveSpeed() const;
+
+        /**
+         * Retrieves the enemy's movement destination position.
+         *
+         * @return The destination position.
+         */
+        const Ogre::Vector3& GetMovePosition() const;
+
+        /**
+         * Retrieves the distance to destination.
+         *
+         * It's the distance between the enemy's current position and it's current movement
+         * destination point.
+         *
+         * @return The distance to destination.
+         */
+        float GetMoveStopDistance() const;
+
+        /**
+         * Makes the enemy move to a point in the map.
+         *
+         * @param[in] x X coordinate of the destination point.
+         * @param[in] y Y coordinate of the destination point.
+         */
+        void ScriptMoveToPosition(const float x, const float y);
+
+        /**
+         * Waits for enemy's movement to end.
+         *
+         * @return Always -1.
+         */
+        int ScriptMoveSync();
+
+        /**
+         * Cancels the enemy's current movement.
+         *
+         * It also clears the movement sync queue.
+         */
+        void UnsetMove();
+
+        /**
+         * @todo Understand and document.
+         *
+         * @param[in] x X coordinate of the destination point.
+         * @param[in] y Y coordinate of the destination point.
+         * @param[in] z Z coordinate of the destination point.
+         * @param[in] type Type of action.
+         * @param[in] seconds Duration of the action, in seconds.
+         */
+        void ScriptOffsetToPosition(
+          const float x, const float y, const float z,
+          const ActionType type, const float seconds
+        );
+
+        /**
+         * @todo Understand and document.
+         *
+         * @return Always -1.
+         */
+        int ScriptOffsetSync();
+
+        /**
+         * @todo Understand and document.
+         */
+        void UnsetOffset();
+
+        /**
+         * @todo Understand and document.
+         *
+         * @return Starting position.
+         */
+        const Ogre::Vector3& GetOffsetPositionStart() const;
+
+        /**
+         * @todo Understand and document.
+         *
+         * @return Ending position.
+         */
+        const Ogre::Vector3& GetOffsetPositionEnd() const;
+
+        /**
+         * @todo Understand and document.
+         *
+         * @return The action type.
+         */
+        ActionType GetOffsetType() const;
+
+        /**
+         * @todo Understand and document.
+         *
+         * @return Action total duration in seconds.
+         */
+        float GetOffsetSeconds() const;
+
+        /**
+         * @todo Understand and document.
+         *
+         * @param[in] seconds Action current duration in seconds.
+         */
+        void SetOffsetCurrentSeconds(const float seconds);
+
+        /**
+         * @todo Understand and document.
+         *
+         * @return Action current duration in seconds.
+         */
+        float GetOffsetCurrentSeconds() const;
+
+        /**
+         * Makes the enemy turn to a fixed direction.
+         *
+         * @param[in] direction Final direction to turn the enemy's to.
+         * @param[in] turn_direction Direction of the turn.
+         * @param[in] turn_type Turn mode.
+         * @param[in] seconds Total turn duration, in seconds.
+         */
+        void ScriptTurnToDirection(
+          const float direction, const TurnDirection turn_direction,
+          const ActionType turn_type, const float seconds
+        );
+
+        /**
+         * Makes the enemy turn to a fixed point.
+         *
+         * @param[in] x X coordinate of the point to turn to.
+         * @param[in] y Y coordinate of the point to turn to.
+         * @param[in] turn_direction Direction of the turn.
+         * @param[in] turn_type Turn mode.
+         * @param[in] seconds Total turn duration, in seconds.
+         */
+        void ScriptTurnToPosition(
+          const int x, const int y, const TurnDirection turn_direction,
+          const ActionType turn_type, const float seconds
+        );
+
+        /**
+         * Adds the enemy's turn to the sync queue.
+         *
+         * @return Always -1.
+         * @todo Properly describe this.
+         */
+        int ScriptTurnSync();
+
+        /**
+         * Makes the enemy turn towards a point or another enemy.
+         *
+         * @param[in] direction_to Final direction to turn the enemy's to.
+         * @param[in] turn_direction Direction of the turn.
+         * @param[in] turn_type Turn mode.
+         * @param[in] seconds Total turn duration, in seconds.
+         * @todo What if the point
+         */
+        void SetTurn(
+          const Ogre::Degree& direction_to, const TurnDirection turn_direction,
+          const ActionType turn_type, const float seconds
+        );
+
+        /**
+         * Cancels the enemy's current turn.
+         *
+         * It also clears the movement sync queue.
+         */
+        void UnsetTurn();
+
+        /**
+         * Calculates the turn angle.
+         *
+         * If the turn direction is {@see TD_CLOSEST}, the result is the
+         * smallest angle between the two orientations. Otherwise, is the angle
+         * in the specified turn direction.
+         *
+         * @param[in] start Starting angle.
+         * @param[in] end Ending angle.
+         * @return[in] Calculated turn angle.
+         */
+        Ogre::Degree CalculateTurnAngle(const Ogre::Degree& start, const Ogre::Degree& end) const;
+
+        /**
+         * Retrieves the turn staring orientation.
+         *
+         * @return The turn starting orientation.
+         */
+        Ogre::Degree GetTurnDirectionStart() const;
+
+        /**
+         * Retrieves the turn ending orientation.
+         *
+         * @return The turn ending orientation.
+         */
+        Ogre::Degree GetTurnDirectionEnd() const;
+
+        /**
+         * Retrieves the turn type.
+         *
+         * @return The turn type.
+         */
+        ActionType GetTurnType() const;
+
+        /**
+         * Retrieves the turn total duration.
+         *
+         * @return The turn total duration, in seconds.
+         */
+        float GetTurnSeconds() const;
+
+        /**
+         * Sets the turn current duration.
+         *
+         * @param[in] seconds The turn current duration, in seconds.
+         */
+        void SetTurnCurrentSeconds(const float seconds);
+
+        /**
+         * Retrieves the turn current duration.
+         *
+         * @return The turn current duration, in seconds.
+         */
+        float GetTurnCurrentSeconds() const;
+
+        /**
+         * Sets the animation speed.
+         *
+         * @param[in] speed The animation speed.
+         * @todo Indicate units, max and mins, or references.
+         */
+        void ScriptSetAnimationSpeed(const float speed);
+
+        /**
+         * Retrieves the enemy's default animation name.
+         *
+         * @return The default animation name.
+         */
+        const Ogre::String& GetDefaultAnimationName() const;
+
+        /**
+         * Retrieves the enemy's current animation name.
+         *
+         * @return The current animation name.
+         */
+        const Ogre::String& GetCurrentAnimationName() const;
+
+        /**
+         * Retrieves the enemy's current animation state.
+         *
+         * @return The current animation state.
+         */
+        AnimationState GetAnimationState() const;
+
+        /**
+         * Plays one of the enemy's animations.
+         *
+         * @param[in] animation Name of the animation to play.
+         * @param[in] state The animation initial state.
+         * @param[in] play_type The animation play type, to play it once or in
+         * a loop.
+         * @param[in] start Animation starting point in time, in seconds.
+         * @param[in] end Animation ending point in time, in seconds.
+         */
+        virtual void PlayAnimation(
+          const Ogre::String& animation, AnimationState state,
+          AnimationPlayType play_type, const float start, const float end
+        ) = 0;
+
+        /**
+         * Resumes an animation.
+         *
+         * @param[in] animation Name of the animation to resume.
+         */
+        virtual void PlayAnimationContinue(const Ogre::String& animation) = 0;
+
+        /**
+         * Updates the animation state.
+         *
+         * @param[in] delta The animation delta.
+         */
+        virtual void UpdateAnimation(const float delta) = 0;
+
+        /**
+         * Plays one of the enemy's animations.
+         *
+         * @param[in] name Name of the animation to play.
+         */
+        void ScriptPlayAnimation(const char* name);
+
+        /**
+         * Stops one of the enemy's animations.
+         *
+         * @param[in] name Name of the animation to stop.
+         */
+        void ScriptPlayAnimationStop(const char* name);
+
+        /**
+         * Plays one of the enemy's animations.
+         *
+         * @param[in] name Name of the animation to play.
+         * @param[in] start Animation starting point in time, in seconds.
+         * @param[in] end Animation ending point in time, in seconds.
+         */
+        void ScriptPlayAnimation(const char* name, const float start, const float end);
+
+        /**
+         * Stops one of the enemy's animations.
+         *
+         * @param[in] name Name of the animation to stop.
+         * @param[in] start Animation starting point in time, in seconds.
+         * @param[in] end Animation ending point in time, in seconds.
+         */
+        void ScriptPlayAnimationStop(const char* name, const float start, const float end);
+
+        /**
+         * Sets the default animation of the enemy.
+         *
+         * @param[in] animation Name of the default animation.
+         */
+        void ScriptSetDefaultAnimation(const char* animation);
+
+        /**
+         * Adds the enemy's animation to the sync queue.
+         *
+         * @return Always -1.
+         * @todo Properly describe this.
+         */
+        int ScriptAnimationSync();
+
+    protected:
+
+        /**
+         * The enemy ID.
+         */
+        int enemy_id_;
+
+        /**
+         * The name of the enemy.
+         */
+        Ogre::String name_;
+
+        /**
+         * The enemy level.
+         */
+        unsigned int level_;
+
+        /**
+         * EXP given upon defeating the enemy.
+         */
+        unsigned int exp_;
+
+        /**
+         * AP given upon defeating the enemy.
+         */
+        unsigned int ap_;
+
+        /**
+         * Money given upon defeating the enemy.
+         */
+        unsigned int money_;
+
+        /**
+         * ID of the item the enemy can be morphed into. -1 if none..
+         */
+        int morph_;
+
+        /**
+         * Multiplier for back damage.
+         */
+        float back_damage_;
+
+        /**
+         * Enemy strength stat.
+         */
+        unsigned int str_;
+
+        /**
+         * Enemy defense stat.
+         */
+        unsigned int def_;
+
+        /**
+         * Enemy magic stat.
+         */
+        unsigned int mag_;
+
+        /**
+         * Enemy spirit stat.
+         */
+        unsigned int spr_;
+
+        /**
+         * Enemy dexterity stat.
+         */
+        unsigned int dex_;
+
+        /**
+         * Enemy luck stat
+         */
+        unsigned int lck_;
+
+        /**
+         * Enemy's current HP.
+         */
+        unsigned int hp_;
+
+        /**
+         * Enemy's current MP.
+         */
+        unsigned int mp_;
+
+        /**
+         * Enemy's max HP.
+         */
+        unsigned int hp_max_;
+
+        /**
+         * Enemy's max MP.
+         */
+        unsigned int mp_max_;
+
+        /**
+         * The scene node the enemy is attached to.
+         */
+        Ogre::SceneNode* scene_node_;
+
+        /**
+         * The enemy's model.
+         */
+        Ogre::SceneNode* model_node_;
+
+        /**
+         * The enemy's root node.
+         */
+        Ogre::SceneNode* model_root_node_;
+
+        /**
+         * The enemy's height.
+         */
+        float height_;
+
+        /**
+         * Enemy's movement sync queue.
+         */
+        std::vector<ScriptId> sync_;
+
+        /**
+         * The speed of the models movements.
+         */
+        float move_speed_;
+
+        /**
+         * The enemy's movement destination point.
+         */
+        Ogre::Vector3 move_position_;
+
+        /**
+         * Distance between the enemy and it's movement destination point.
+         */
+        float move_stop_distance_;
+
+        /**
+         * Indicates if the enemy can rotate while moving.
+         */
+        bool move_auto_rotation_;
+
+        /**
+         * Indicates if the enemy can animate while moving.
+         */
+        bool move_auto_animation_;
+
+        /**
+         * @todo Understand and document.
+         */
+        Ogre::Vector3 offset_position_start_;
+
+        /**
+         * @todo Understand and document.
+         */
+        Ogre::Vector3 offset_position_end_;
+
+        /**
+         * @todo Understand and document.
+         */
+        ActionType offset_type_;
+
+        /**
+         * @todo Understand and document.
+         */
+        float offset_seconds_;
+
+        /**
+         * @todo Understand and document.
+         */
+        float offset_current_seconds_;
+
+        /**
+         * @todo Understand and document.
+         */
+        std::vector<ScriptId> offset_sync_;
+
+        /**
+         * Turn movement direction.
+         */
+        TurnDirection turn_direction_;
+
+        /**
+         * Turn initial orientation.
+         */
+        Ogre::Degree turn_direction_start_;
+
+        /**
+         * Turn final orientation.
+         */
+        Ogre::Degree turn_direction_end_;
+
+        /**
+         * The turn type.
+         */
+        ActionType turn_type_;
+
+        /**
+         * Total turn duration.
+         */
+        float turn_seconds_;
+
+        /**
+         * Current turn duration.
+         */
+        float turn_current_seconds_;
+
+        /**
+         * Enemy's turning sync queue.
+         */
+        std::vector<ScriptId> turn_sync_;
+
+        /**
+         * The animation speed.
+         */
+        float animation_speed_;
+
+        /**
+         * The enemy's current animation name.
+         */
+        Ogre::String animation_current_name_;
+
+        /**
+         * Enemy's animation sync queue.
+         */
+        std::vector<ScriptId> animation_sync_;
+
+        /**
+         * The enemy's current animation state.
+         */
+        AnimationState animation_state_;
+
+        /**
+         * The enemy's current animation type.
+         */
+        AnimationPlayType animation_play_type_;
+
+        /**
+         * The name of the enemy's default name.
+         */
+        Ogre::String animation_default_;
+
+        /**
+         * @todo Understand and document.
+         */
+        float animation_end_time_;
+
+        /**
+         * Indicates if an automation must be played automatically.
+         */
+        bool animation_auto_play_;
+
+    private:
+
+        /**
+         * Constructor.
+         */
+        Enemy();
+
+        /**
+         * Calculates the angular distance to a point.
+         *
+         * @param[in] point Point to calculate the angular distance to.
+         * @return Angular distance to the specified point.
+         */
+        Ogre::Degree GetDirectionToPoint(Ogre::Vector2 point) const;
+
+        /**
+         * Index of the enemy on the battle.
+         */
+        int index_;
+
+};
+

+ 65 - 0
src/core/XmlEnemyFile.cpp

@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 The V-Gears Team
+ *
+ * This file is part of V-Gears
+ *
+ * V-Gears is free software: you can redistribute it and/or modify it under
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, version 3.0 (GPLv3) of the License.
+ *
+ * V-Gears is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include "core/XmlEnemyFile.h"
+#include "core/Enemy.h"
+#include "core/Logger.h"
+
+XmlEnemyFile::XmlEnemyFile(const Ogre::String& file): XmlFile(file){}
+
+XmlEnemyFile::~XmlEnemyFile(){}
+
+void XmlEnemyFile::LoadEnemy(Enemy& enemy){
+    TiXmlNode* node = file_.RootElement();
+    if (node == nullptr || node->ValueStr() != "enemy"){
+        LOG_ERROR(file_.ValueStr() + " is not a valid enemy map file! No <enemy> in root.");
+        return;
+    }
+    enemy.SetEnemyId(GetInt(node, "id"));
+    enemy.SetName(GetString(node, "name"));
+    enemy.SetLevel(GetInt(node, "level"));
+    enemy.SetExp(GetInt(node, "exp"));
+    enemy.SetAp(GetInt(node, "ap"));
+    enemy.SetMoney(GetInt(node, "money"));
+    enemy.SetMorph(GetInt(node, "morph"));
+    enemy.SetBackDamage(GetFloat(node, "back_damage"));
+    node = node->FirstChild();
+    while (node != nullptr){
+        /*// Location data.
+        if (node->Type() == TiXmlNode::TINYXML_ELEMENT && node->ValueStr() == "location"){
+            int id = GetInt(node, "id");
+            Ogre::String name(GetString(node, "name"));
+            BattleManager::getSingleton().SetLocation(id, name);
+        }
+        else if (
+          node->Type() == TiXmlNode::TINYXML_ELEMENT && node->ValueStr() == "arena"
+        ){
+            bool is_arena = GetInt(node, "is_arena") == 1;
+            BattleManager::getSingleton().SetArenaBattle(is_arena);
+            if (is_arena){
+                // TODO: Read more info for arena battles
+            }
+        }
+        else if (node->Type() == TiXmlNode::TINYXML_ELEMENT && node->ValueStr() == "camera"){
+            BattleManager::getSingleton().SetInitialCamera(GetInt(node, "initial"));
+            // TODO: Loop cameras
+        }
+        else if (node->Type() == TiXmlNode::TINYXML_ELEMENT && node->ValueStr() == "enemies"){
+            // TODO: Loop enemies
+        }*/
+        node = node->NextSibling();
+    }
+}
+

+ 48 - 0
src/core/XmlEnemyFile.h

@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 The V-Gears Team
+ *
+ * This file is part of V-Gears
+ *
+ * V-Gears is free software: you can redistribute it and/or modify it under
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, version 3.0 (GPLv3) of the License.
+ *
+ * V-Gears is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#pragma once
+
+#include "XmlFile.h"
+#include "Enemy.h"
+
+/**
+ * Handles formation files.
+ *
+ * Formation files contain data specific for a battle. They have information about battle
+ * parameters, enemies, battle location, camera. They are also used for arena battles.
+ */
+class XmlEnemyFile : public XmlFile{
+
+    public:
+
+        /**
+         * Constructor.
+         *
+         * @param[in] file Path to the enemy XML file.
+         */
+        explicit XmlEnemyFile(const Ogre::String& file);
+
+        /**
+         * Destructor.
+         */
+        virtual ~XmlEnemyFile();
+
+        /**
+         * Parses the file and loads the formation data.
+         */
+        void LoadEnemy(Enemy& enemy);
+
+};

+ 64 - 0
src/core/XmlFormationFile.cpp

@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 The V-Gears Team
+ *
+ * This file is part of V-Gears
+ *
+ * V-Gears is free software: you can redistribute it and/or modify it under
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, version 3.0 (GPLv3) of the License.
+ *
+ * V-Gears is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include "core/XmlFormationFile.h"
+#include "core/BattleManager.h"
+#include "core/Logger.h"
+
+XmlFormationFile::XmlFormationFile(const Ogre::String& file): XmlFile(file){}
+
+XmlFormationFile::~XmlFormationFile(){}
+
+void XmlFormationFile::LoadFormation(){
+    TiXmlNode* node = file_.RootElement();
+    if (node == nullptr || node->ValueStr() != "formation"){
+        LOG_ERROR(file_.ValueStr() + " is not a valid fields map file! No <formation> in root.");
+        return;
+    }
+    BattleManager::getSingleton().SetFormationId(GetInt(node, "id"));
+    BattleManager::getSingleton().SetNextFormationId(GetInt(node, "next"));
+    if (GetInt(node, "escapable") == 1)
+        BattleManager::getSingleton().SetEscapeability(GetFloat(node, "escape_difficulty"));
+    else BattleManager::getSingleton().SetEscapeability(-1.0f);
+    BattleManager::getSingleton().SetSkipVictoryPose(GetInt(node, "skip_victory_pose") == 1);
+    BattleManager::getSingleton().SetSkipSpoils(GetInt(node, "skip_spoils") == 1);
+    node = node->FirstChild();
+    while (node != nullptr){
+        // Location data.
+        if (node->Type() == TiXmlNode::TINYXML_ELEMENT && node->ValueStr() == "location"){
+            int id = GetInt(node, "id");
+            Ogre::String name(GetString(node, "name"));
+            BattleManager::getSingleton().SetLocation(id, name);
+        }
+        else if (
+          node->Type() == TiXmlNode::TINYXML_ELEMENT && node->ValueStr() == "arena"
+        ){
+            bool is_arena = GetInt(node, "is_arena") == 1;
+            BattleManager::getSingleton().SetArenaBattle(is_arena);
+            if (is_arena){
+                // TODO: Read more info for arena battles
+            }
+        }
+        else if (node->Type() == TiXmlNode::TINYXML_ELEMENT && node->ValueStr() == "camera"){
+            BattleManager::getSingleton().SetInitialCamera(GetInt(node, "initial"));
+            // TODO: Loop cameras
+        }
+        else if (node->Type() == TiXmlNode::TINYXML_ELEMENT && node->ValueStr() == "enemies"){
+            // TODO: Loop enemies
+        }
+        node = node->NextSibling();
+    }
+}
+

+ 47 - 0
src/core/XmlFormationFile.h

@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The V-Gears Team
+ *
+ * This file is part of V-Gears
+ *
+ * V-Gears is free software: you can redistribute it and/or modify it under
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, version 3.0 (GPLv3) of the License.
+ *
+ * V-Gears is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#pragma once
+
+#include "XmlFile.h"
+
+/**
+ * Handles formation files.
+ *
+ * Formation files contain data specific for a battle. They have information about battle
+ * parameters, enemies, battle location, camera. They are also used for arena battles.
+ */
+class XmlFormationFile : public XmlFile{
+
+    public:
+
+        /**
+         * Constructor.
+         *
+         * @param[in] file Path to the formation XML file.
+         */
+        explicit XmlFormationFile(const Ogre::String& file);
+
+        /**
+         * Destructor.
+         */
+        virtual ~XmlFormationFile();
+
+        /**
+         * Parses the file and loads the formation data.
+         */
+        void LoadFormation();
+
+};

+ 48 - 0
src/data/GameData.h

@@ -19,6 +19,54 @@ class GameData{
 
     public:
 
+        /**
+         * Character or enemy stats.
+         */
+        class STAT{
+
+            public:
+
+                /**
+                 * Strength stat.
+                 */
+                static const unsigned int STR = 0;
+
+                /**
+                 * Vtatlity stat.
+                 */
+                static const unsigned int VIT = 1;
+
+                /**
+                 * Magic stat.
+                 */
+                static const unsigned int MAG = 2;
+
+                /**
+                 * Spirit stat.
+                 */
+                static const unsigned int SPR = 3;
+
+                /**
+                 * Dexterity stat.
+                 */
+                static const unsigned int DEX = 4;
+
+                /**
+                 * Luck stat.
+                 */
+                static const unsigned int LCK = 5;
+
+                /**
+                 * HP stat.
+                 */
+                static const unsigned int HP = 6;
+
+                /**
+                 * MP stat.
+                 */
+                static const unsigned int MP = 7;
+        };
+
         class ELEMENT{
 
             public:

+ 8 - 8
src/installer/BattleDataInstaller.cpp

@@ -576,7 +576,7 @@ void BattleDataInstaller::WriteAttacks(){
 void BattleDataInstaller::WriteFormations(){
     for (Formation formation : formations_){
         TiXmlDocument xml;
-        std::unique_ptr<TiXmlElement> container(new TiXmlElement("Formation"));
+        std::unique_ptr<TiXmlElement> container(new TiXmlElement("formation"));
         container->SetAttribute("id", formation.id);
         container->SetAttribute("next", formation.next_formation);
         container->SetDoubleAttribute("escape_difficulty", formation.escape_counter);
@@ -586,13 +586,13 @@ void BattleDataInstaller::WriteFormations(){
         container->SetAttribute("skip_victory_pose", formation.skip_victory_pose);
         container->SetAttribute("preemptive_disabled", formation.preemptive_disabled);
         container->SetAttribute("layout", static_cast<int>(formation.layout));
-        std::unique_ptr<TiXmlElement> location(new TiXmlElement("Location"));
+        std::unique_ptr<TiXmlElement> location(new TiXmlElement("location"));
         location->SetAttribute("id", formation.location);
         location->SetAttribute("name", formation.location_name);
         container->LinkEndChild(location.release());
-        std::unique_ptr<TiXmlElement> enemies(new TiXmlElement("Enemies"));
+        std::unique_ptr<TiXmlElement> enemies(new TiXmlElement("enemies"));
         for (Formation::Enemy enemy : formation.enemies){
-            std::unique_ptr<TiXmlElement> enemy_xml(new TiXmlElement("Enemy"));
+            std::unique_ptr<TiXmlElement> enemy_xml(new TiXmlElement("enemy"));
             enemy_xml->SetAttribute("id", enemy.id);
             enemy_xml->SetAttribute("x", enemy.x);
             enemy_xml->SetAttribute("y", enemy.y);
@@ -608,11 +608,11 @@ void BattleDataInstaller::WriteFormations(){
             enemies->LinkEndChild(enemy_xml.release());
         }
         container->LinkEndChild(enemies.release());
-        std::unique_ptr<TiXmlElement> cameras(new TiXmlElement("Camera"));
+        std::unique_ptr<TiXmlElement> cameras(new TiXmlElement("camera"));
         cameras->SetAttribute("initial", formation.initial_camera_position);
         int c = 0;
         for (Formation::Camera camera : formation.camera_positions){
-            std::unique_ptr<TiXmlElement> camera_xml(new TiXmlElement("Position"));
+            std::unique_ptr<TiXmlElement> camera_xml(new TiXmlElement("position"));
             camera_xml->SetAttribute("id", c);
             camera_xml->SetAttribute("x", camera.x);
             camera_xml->SetAttribute("y", camera.y);
@@ -624,10 +624,10 @@ void BattleDataInstaller::WriteFormations(){
             c ++;
         }
         container->LinkEndChild(cameras.release());
-        std::unique_ptr<TiXmlElement> arena(new TiXmlElement("Arena"));
+        std::unique_ptr<TiXmlElement> arena(new TiXmlElement("arena"));
         arena->SetAttribute("is_arena", formation.is_arena_battle);
         for (int next : formation.next_arena_formation_candidates){
-            std::unique_ptr<TiXmlElement> next_xml(new TiXmlElement("NextCandidate"));
+            std::unique_ptr<TiXmlElement> next_xml(new TiXmlElement("next_candidate"));
             next_xml->SetAttribute("id", next);
             arena->LinkEndChild(next_xml.release());
         }

+ 17 - 0
src/installer/data/FF7Data.h

@@ -141,6 +141,23 @@ class FF7Data{
             {}
         };
 
+        /**
+         * Retrieves an enemy model ID from an enemy ID.
+         *
+         * @param[in] enemy_id Numeric enemy ID.
+         * @return Enemy alphanumeric ID. Two-lowercase letters. If an invalid ID is provided, an
+         * empty string will be returned.
+         */
+        static std::string GetEnemyModelId(const unsigned int enemy_id){
+            if (enemy_id > 25 * 26) return "";
+            std::string model_id = "";
+            char letter = (enemy_id % 26) + 97; // 97 -> a, 122 -> z
+            model_id += letter;
+            letter = (enemy_id / 26) + 97;
+            model_id += letter;
+            return model_id;
+        }
+
         /**
          * Retrieves information about a battle model from it's name.
          *