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