1 ////////////////////////////////////////////////////////////////////////////////
2 //            Copyright (C) 2004-2011 by The Allacrost Project
3 //            Copyright (C) 2012-2018 by Bertram (Valyria Tear)
4 //                         All Rights Reserved
5 //
6 // This code is licensed under the GNU GPL version 2. It is free software
7 // and you may modify it and/or redistribute it under the terms of this license.
8 // See http://www.gnu.org/copyleft/gpl.html for details.
9 ////////////////////////////////////////////////////////////////////////////////
10 
11 #include "global_enemy.h"
12 
13 #include "global_attack_point.h"
14 
15 #include "common/global/global.h"
16 
17 #include "script/script_read.h"
18 #include "utils/utils_random.h"
19 
20 using namespace vt_utils;
21 using namespace vt_video;
22 using namespace vt_script;
23 
24 namespace vt_global
25 {
26 
27 extern bool GLOBAL_DEBUG;
28 
GlobalEnemy(uint32_t id)29 GlobalEnemy::GlobalEnemy(uint32_t id) :
30     GlobalActor(),
31     _experience_points(0),
32     _sprite_width(0),
33     _sprite_height(0),
34     _drunes_dropped(0)
35 {
36     _id = id;
37 
38     if(_id == 0) {
39         PRINT_ERROR << "invalid id for loading enemy data: " << _id << std::endl;
40         return;
41     }
42 
43     // Open the script file and table that store the enemy data
44     ReadScriptDescriptor& enemy_data = GlobalManager->GetEnemiesScript();
45 
46     if (!enemy_data.OpenTable(_id)) {
47         PRINT_ERROR << "Failed to open the enemies[" << _id << "] table in: "
48             << enemy_data.GetFilename() << std::endl;
49         return;
50     }
51 
52     // Load the enemy's name and sprite data
53     _name = MakeUnicodeString(enemy_data.ReadString("name"));
54 
55     // Attempt to load the animations for each harm levels
56     _battle_animations.assign(GLOBAL_ENEMY_HURT_TOTAL, AnimatedImage());
57     if (enemy_data.OpenTable("battle_animations" )) {
58 
59         std::vector<uint32_t> animations_id;
60         enemy_data.ReadTableKeys(animations_id);
61         for (uint32_t i = 0; i < animations_id.size(); ++i) {
62             uint32_t anim_id = animations_id[i];
63             if (anim_id >= GLOBAL_ENEMY_HURT_TOTAL) {
64                 PRINT_WARNING << "Invalid table id in 'battle_animations' table for enemy: "
65                     << _id << std::endl;
66                 continue;
67             }
68 
69             _battle_animations[anim_id].LoadFromAnimationScript(enemy_data.ReadString(anim_id));
70 
71             // Updates the sprite dimensions
72             if (_battle_animations[anim_id].GetWidth() > _sprite_width)
73                 _sprite_width =_battle_animations[anim_id].GetWidth();
74             if (_battle_animations[anim_id].GetHeight() > _sprite_height)
75                 _sprite_height =_battle_animations[anim_id].GetHeight();
76         }
77 
78         enemy_data.CloseTable(); // battle_animations
79     }
80     else {
81         PRINT_WARNING << "No 'battle_animations' table for enemy: " << _id << std::endl;
82     }
83 
84     std::string stamina_icon_filename = enemy_data.ReadString("stamina_icon");
85     if(!stamina_icon_filename.empty()) {
86         if(!_stamina_icon.Load(stamina_icon_filename)) {
87             PRINT_WARNING << "Invalid stamina icon image: " << stamina_icon_filename
88                           << " for enemy: " << MakeStandardString(_name) << ". Loading default one." << std::endl;
89 
90             _stamina_icon.Load("data/battles/stamina_icons/default_stamina_icon.png");
91         }
92     } else {
93         _stamina_icon.Load("data/battles/stamina_icons/default_stamina_icon.png");
94     }
95 
96     // Loads enemy battle animation scripts
97     if (enemy_data.OpenTable("scripts")) {
98         _death_script_filename = enemy_data.ReadString("death");
99         _ai_script_filename = enemy_data.ReadString("battle_ai");
100         enemy_data.CloseTable();
101     }
102 
103     if (enemy_data.OpenTable("base_stats")) {
104         _max_hit_points = enemy_data.ReadUInt("hit_points");
105         _hit_points = _max_hit_points;
106         _max_skill_points = enemy_data.ReadUInt("skill_points");
107         _skill_points = _max_skill_points;
108         _experience_points = enemy_data.ReadUInt("experience_points");
109         _char_phys_atk.SetBase(enemy_data.ReadUInt("phys_atk"));
110         _char_mag_atk.SetBase(enemy_data.ReadUInt("mag_atk"));
111         _char_phys_def.SetBase(enemy_data.ReadUInt("phys_def"));
112         _char_mag_def.SetBase(enemy_data.ReadUInt("mag_def"));
113         _stamina.SetBase(enemy_data.ReadUInt("stamina"));
114         _evade.SetBase(enemy_data.ReadFloat("evade"));
115         _drunes_dropped = enemy_data.ReadUInt("drunes");
116         enemy_data.CloseTable();
117     }
118 
119     // Create the attack points for the enemy
120     if (enemy_data.OpenTable("attack_points")) {
121         uint32_t ap_size = enemy_data.GetTableSize();
122         for(uint32_t i = 1; i <= ap_size; ++i) {
123             _attack_points.push_back(new GlobalAttackPoint(this));
124             if (enemy_data.OpenTable(i)) {
125                 if(_attack_points.back()->LoadData(enemy_data) == false) {
126                     IF_PRINT_WARNING(GLOBAL_DEBUG) << "Failed to load data for an attack point: "
127                         << i << std::endl;
128                 }
129                 enemy_data.CloseTable();
130             }
131         }
132         enemy_data.CloseTable();
133     }
134 
135     // Add the set of skills for the enemy
136     if (enemy_data.OpenTable("skills")) {
137         for(uint32_t i = 1; i <= enemy_data.GetTableSize(); ++i) {
138             _skill_set.push_back(enemy_data.ReadUInt(i));
139         }
140         enemy_data.CloseTable();
141     }
142 
143     // Load the possible items that the enemy may drop
144     if (enemy_data.OpenTable("drop_objects")) {
145         for(uint32_t i = 1; i <= enemy_data.GetTableSize(); ++i) {
146             enemy_data.OpenTable(i);
147             _dropped_objects.push_back(enemy_data.ReadUInt(1));
148             _dropped_chance.push_back(enemy_data.ReadFloat(2));
149             enemy_data.CloseTable();
150         }
151         enemy_data.CloseTable();
152     }
153 
154     enemy_data.CloseTable(); // enemies[_id]
155 
156     if(enemy_data.IsErrorDetected()) {
157         PRINT_WARNING << "One or more errors occurred while reading the enemy data - they are listed below"
158                       << std::endl << enemy_data.GetErrorMessages() << std::endl;
159     }
160 
161     // stats and skills.
162     _Initialize();
163 
164     _CalculateAttackRatings();
165     _CalculateDefenseRatings();
166     _CalculateEvadeRatings();
167 }
168 
AddSkill(uint32_t skill_id)169 bool GlobalEnemy::AddSkill(uint32_t skill_id)
170 {
171     if(skill_id == 0) {
172         IF_PRINT_WARNING(GLOBAL_DEBUG) << "function received an invalid skill_id argument: " << skill_id << std::endl;
173         return false;
174     }
175 
176     if(HasSkill(skill_id)) {
177         IF_PRINT_WARNING(GLOBAL_DEBUG) << "failed to add skill because the enemy already knew this skill: " << skill_id << std::endl;
178         return false;
179     }
180 
181     GlobalSkill *skill = new GlobalSkill(skill_id);
182     if(skill->IsValid() == false) {
183         IF_PRINT_WARNING(GLOBAL_DEBUG) << "the skill to add failed to load: " << skill_id << std::endl;
184         delete skill;
185         return false;
186     }
187 
188     // Insert the pointer to the new skill inside of the global skills vectors
189     _skills.push_back(skill);
190     _skills_id.push_back(skill_id);
191     return true;
192 }
193 
_Initialize()194 void GlobalEnemy::_Initialize()
195 {
196     // Add all new skills that should be available at the current experience level
197     _skills.clear();
198     for(uint32_t i = 0; i < _skill_set.size(); ++i)
199         AddSkill(_skill_set[i]);
200 
201     if(_skills.empty())
202         PRINT_WARNING << "No skills were added for the enemy: " << _id << std::endl;
203 
204     // Randomize the stats by using a random diff of 10%
205     _max_hit_points = RandomDiffValue(_max_hit_points, _max_hit_points / 10.0f);
206     _max_skill_points = RandomDiffValue(_max_skill_points, _max_skill_points / 10.0f);
207     _experience_points = RandomDiffValue(_experience_points, _experience_points / 10.0f);
208     _char_phys_atk.SetBase(RandomDiffValue(_char_phys_atk.GetBase(), _char_phys_atk.GetBase() / 10.0f));
209     _char_mag_atk.SetBase(RandomDiffValue(_char_mag_atk.GetBase(), _char_mag_atk.GetBase() / 10.0f));
210     _char_phys_def.SetBase(RandomDiffValue(_char_phys_def.GetBase(), _char_phys_def.GetBase() / 10.0f));
211     _char_mag_def.SetBase(RandomDiffValue(_char_mag_def.GetBase(), _char_mag_def.GetBase() / 10.0f));
212     _stamina.SetBase(RandomDiffValue(_stamina.GetBase(), _stamina.GetBase() / 10.0f));
213 
214     // Multiply the evade value by 10 to permit the decimal to be kept
215     float evade = _evade.GetBase() * 10.0f;
216     _evade.SetBase(static_cast<float>(RandomDiffValue(evade, evade / 10.0f)) / 10.0f);
217 
218     _drunes_dropped = RandomDiffValue(_drunes_dropped, _drunes_dropped / 10.0f);
219 
220     // Set the current hit points and skill points to their new maximum values
221     _hit_points = _max_hit_points;
222     _skill_points = _max_skill_points;
223 }
224 
DetermineDroppedObjects()225 std::vector<std::shared_ptr<GlobalObject>> GlobalEnemy::DetermineDroppedObjects()
226 {
227     std::vector<std::shared_ptr<GlobalObject>> result;
228 
229     for (uint32_t i = 0; i < _dropped_objects.size(); ++i) {
230         if (RandomFloat() < _dropped_chance[i]) {
231             std::shared_ptr<GlobalObject> global_object = GlobalCreateNewObject(_dropped_objects[i]);
232             result.push_back(global_object);
233         }
234     }
235 
236     return result;
237 }
238 
239 } // namespace vt_global
240