1 ////////////////////////////////////////////////////////////////////////////////
2 // Copyright (C) 2004-2010 by The Allacrost Project
3 // All Rights Reserved
4 //
5 // This code is licensed under the GNU GPL version 2. It is free software and
6 // you may modify it and/or redistribute it under the terms of this license.
7 // See http://www.gnu.org/copyleft/gpl.html for details.
8 ////////////////////////////////////////////////////////////////////////////////
9
10 /** ****************************************************************************
11 *** \file battle_indicators.h
12 *** \author Tyler Olsen, roots@allacrost.org
13 *** \brief Source file for battle indicator displays.
14 *** ***************************************************************************/
15
16 #include "system.h"
17 #include "video.h"
18
19 #include "global_utils.h"
20
21 #include "battle.h"
22 #include "battle_actors.h"
23 #include "battle_indicators.h"
24 #include "battle_utils.h"
25
26 using namespace std;
27
28 using namespace hoa_utils;
29
30 using namespace hoa_system;
31 using namespace hoa_video;
32
33 using namespace hoa_global;
34
35 namespace hoa_battle {
36
37 namespace private_battle {
38
39 //! \brief The total amount of time (in milliseconds) that the display sequence lasts for indicator elements
40 const uint32 INDICTATOR_TIME = 5000;
41
42 //! \brief The amount of time (in milliseconds) that indicator elements fade at the beginning and end of the display sequence
43 const uint32 INDICATOR_FADE_TIME = 1000;
44
45 //! \brief The total vertical distance that indictor elements travel during the display sequence
46 const float INDICATOR_POSITION_CHANGE = 100.0f; // TODO: need to think of a better name for this constant
47
48 ////////////////////////////////////////////////////////////////////////////////
49 // IndicatorElement class
50 ////////////////////////////////////////////////////////////////////////////////
51
IndicatorElement(BattleActor * actor)52 IndicatorElement::IndicatorElement(BattleActor* actor) :
53 _actor(actor),
54 _timer(INDICTATOR_TIME),
55 _alpha_color(1.0f, 1.0f, 1.0f, 0.0f)
56 {
57 if (actor == NULL)
58 IF_PRINT_WARNING(BATTLE_DEBUG) << "constructor received NULL actor argument" << endl;
59 }
60
61
62
Start()63 void IndicatorElement::Start() {
64 if (_timer.IsInitial() == false) {
65 IF_PRINT_WARNING(BATTLE_DEBUG) << "timer was not in initial state when started" << endl;
66 }
67 _timer.Run();
68 }
69
70
71
Update()72 void IndicatorElement::Update() {
73 _timer.Update();
74 }
75
76
77
CalculateDrawAlpha()78 bool IndicatorElement::CalculateDrawAlpha() {
79 // Case 1: Timer is not running nor paused so indicator should not be drawn
80 if ((_timer.GetState() == SYSTEM_TIMER_RUNNING) && (_timer.GetState() == SYSTEM_TIMER_PAUSED)) {
81 _alpha_color.SetAlpha(0.0f);
82 return true;
83 }
84 // Case 2: Timer is in beginning stage and indicator graphic is fading in
85 else if (_timer.GetTimeExpired() < INDICATOR_FADE_TIME) {
86 _alpha_color.SetAlpha(static_cast<float>(_timer.GetTimeExpired()) / static_cast<float>(INDICATOR_FADE_TIME));
87 return true;
88 }
89 // Case 3: Timer is in final stage and indicator graphic is fading out
90 else if (_timer.TimeLeft() < INDICATOR_FADE_TIME) {
91 _alpha_color.SetAlpha(static_cast<float>(_timer.TimeLeft()) / static_cast<float>(INDICATOR_FADE_TIME));
92 return true;
93 }
94 // Case 4: Timer is in middle stage and indicator graphic should be drawn with no transparency
95 else {
96 return false;
97 }
98 }
99
100 ////////////////////////////////////////////////////////////////////////////////
101 // IndicatorText class
102 ////////////////////////////////////////////////////////////////////////////////
103
IndicatorText(BattleActor * actor,string & text,TextStyle & style)104 IndicatorText::IndicatorText(BattleActor* actor, string& text, TextStyle& style) :
105 IndicatorElement(actor),
106 _text_image(text, style)
107 {}
108
109
110
Draw()111 void IndicatorText::Draw() {
112 VideoManager->Move(_actor->GetXLocation(), _actor->GetYLocation());
113
114 // Draw to the left of the sprite for characters and to the right of the sprite for enemys
115 if (_actor->IsEnemy() == false) {
116 VideoManager->SetDrawFlags(VIDEO_X_RIGHT, VIDEO_Y_BOTTOM, VIDEO_BLEND, 0);
117 VideoManager->MoveRelative(-20.0f, 0.0f); // TEMP: should move to just past the edge of the sprite image
118 }
119 else {
120 VideoManager->SetDrawFlags(VIDEO_X_LEFT, VIDEO_Y_BOTTOM, VIDEO_BLEND, 0);
121 VideoManager->MoveRelative(_actor->GetSpriteWidth() / 2.0f, 0.0f);
122 }
123
124 float y_position = INDICATOR_POSITION_CHANGE * _timer.PercentComplete();
125 VideoManager->MoveRelative(0.0f, y_position);
126
127 if (CalculateDrawAlpha() == true)
128 _text_image.Draw(_alpha_color);
129 else
130 _text_image.Draw();
131 }
132
133 ////////////////////////////////////////////////////////////////////////////////
134 // IndicatorImage class
135 ////////////////////////////////////////////////////////////////////////////////
136
IndicatorImage(BattleActor * actor,const string & filename)137 IndicatorImage::IndicatorImage(BattleActor* actor, const string& filename) :
138 IndicatorElement(actor)
139 {
140 if (_image.Load(filename) == false)
141 PRINT_ERROR << "failed to load indicator image: " << filename << endl;
142 }
143
144
145
IndicatorImage(BattleActor * actor,const StillImage & image)146 IndicatorImage::IndicatorImage(BattleActor* actor, const StillImage& image) :
147 IndicatorElement(actor),
148 _image(image)
149 {} // TODO: check if the image argument is valid...currently seems the only possible check is that its filename is not an empty string.
150
151
152
Draw()153 void IndicatorImage::Draw() {
154 VideoManager->Move(_actor->GetXLocation(), _actor->GetYLocation());
155
156 // Draw to the left of the sprite for characters and to the right of the sprite for enemys
157 if (_actor->IsEnemy() == false) {
158 VideoManager->MoveRelative(-40.0f, 0.0f); // TEMP: should move to just past the edge of the sprite image, not -40.0f
159 VideoManager->SetDrawFlags(VIDEO_X_RIGHT, VIDEO_Y_BOTTOM, VIDEO_BLEND, 0);
160 }
161 else {
162 VideoManager->MoveRelative(40.0f, 0.0f); // TEMP: should move to just past the edge of the sprite image, not 40.0f
163 VideoManager->SetDrawFlags(VIDEO_X_LEFT, VIDEO_Y_BOTTOM, VIDEO_BLEND, 0);
164 }
165
166 float y_position = INDICATOR_POSITION_CHANGE * _timer.PercentComplete();
167 VideoManager->MoveRelative(0.0f, y_position);
168
169 if (CalculateDrawAlpha() == true)
170 _image.Draw(_alpha_color);
171 else
172 _image.Draw();
173 }
174
175 ////////////////////////////////////////////////////////////////////////////////
176 // IndicatorBlendedImage class
177 ////////////////////////////////////////////////////////////////////////////////
178
IndicatorBlendedImage(BattleActor * actor,const string & first_filename,const string & second_filename)179 IndicatorBlendedImage::IndicatorBlendedImage(BattleActor* actor, const string& first_filename, const string& second_filename) :
180 IndicatorElement(actor),
181 _second_alpha_color(1.0f, 1.0f, 1.0f, 0.0f)
182 {
183 if (_first_image.Load(first_filename) == false)
184 PRINT_ERROR << "failed to load first indicator image: " << first_filename << endl;
185 if (_second_image.Load(second_filename) == false)
186 PRINT_ERROR << "failed to load second indicator image: " << second_filename << endl;
187 }
188
189
190
IndicatorBlendedImage(BattleActor * actor,const StillImage & first_image,const StillImage & second_image)191 IndicatorBlendedImage::IndicatorBlendedImage(BattleActor* actor, const StillImage& first_image, const StillImage& second_image) :
192 IndicatorElement(actor),
193 _first_image(first_image),
194 _second_image(second_image),
195 _second_alpha_color(1.0f, 1.0f, 1.0f, 0.0f)
196 {} // TODO: check if the image arguments are valid...currently seems the only possible check is that its filename is not an empty string.
197
198
199
Draw()200 void IndicatorBlendedImage::Draw() {
201 VideoManager->Move(_actor->GetXLocation(), _actor->GetYLocation());
202
203 // Draw to the left of the sprite for characters and to the right of the sprite for enemys
204 if (_actor->IsEnemy() == false) {
205 VideoManager->MoveRelative(-40.0f, 0.0f); // TEMP: should move to just past the edge of the sprite image, not -40.0f
206 VideoManager->SetDrawFlags(VIDEO_X_RIGHT, VIDEO_Y_BOTTOM, VIDEO_BLEND, 0);
207 }
208 else {
209 VideoManager->MoveRelative(40.0f, 0.0f); // TEMP: should move to just past the edge of the sprite image, not 40.0f
210 VideoManager->SetDrawFlags(VIDEO_X_LEFT, VIDEO_Y_BOTTOM, VIDEO_BLEND, 0);
211 }
212
213 float y_position = INDICATOR_POSITION_CHANGE * _timer.PercentComplete();
214 VideoManager->MoveRelative(0.0f, y_position);
215
216 // Case 1: Initial fade in of first image
217 if (_timer.GetTimeExpired() <= INDICATOR_FADE_TIME) {
218 CalculateDrawAlpha();
219 _first_image.Draw(_alpha_color);
220 }
221 // Case 2: Opaque draw of first image
222 else if (_timer.GetTimeExpired() <= (INDICATOR_FADE_TIME * 2)) {
223 _first_image.Draw();
224 }
225 // Case 3: Blended draw of first and second images
226 else if (_timer.GetTimeExpired() <= (INDICATOR_FADE_TIME * 3)) {
227 _alpha_color.SetAlpha(static_cast<float>((INDICATOR_FADE_TIME * 3) - _timer.GetTimeExpired())
228 / static_cast<float>(INDICATOR_FADE_TIME));
229 _second_alpha_color.SetAlpha(1.0f - _alpha_color.GetAlpha());
230 _first_image.Draw(_alpha_color);
231 _second_image.Draw(_second_alpha_color);
232 }
233 // Case 4: Opaque draw of second image
234 else if (_timer.GetTimeExpired() <= (INDICATOR_FADE_TIME * 4)) {
235 _second_image.Draw();
236 }
237 // Case 5: Final fade out of second image
238 else { // (_timer.GetTimeExpired() <= INDICATOR_TIME)
239 CalculateDrawAlpha();
240 _second_image.Draw(_alpha_color);
241 }
242 }
243
244 ////////////////////////////////////////////////////////////////////////////////
245 // IndicatorSupervisor class
246 ////////////////////////////////////////////////////////////////////////////////
247
IndicatorSupervisor(BattleActor * actor)248 IndicatorSupervisor::IndicatorSupervisor(BattleActor* actor) :
249 _actor(actor)
250 {
251 if (actor == NULL)
252 IF_PRINT_WARNING(BATTLE_DEBUG) << "contructor received NULL actor argument" << endl;
253 }
254
255
256
~IndicatorSupervisor()257 IndicatorSupervisor::~IndicatorSupervisor() {
258 for (uint32 i = 0; i < _wait_queue.size(); i++)
259 delete _wait_queue[i];
260 _wait_queue.clear();
261
262 for (uint32 i = 0; i < _active_queue.size(); i++)
263 delete _active_queue[i];
264 _active_queue.clear();
265 }
266
267
268
Update()269 void IndicatorSupervisor::Update() {
270 // Update all active elements
271 for (uint32 i = 0; i < _active_queue.size(); i++)
272 _active_queue[i]->Update();
273
274 // Remove all expired elements from the active queue
275 while (_active_queue.empty() == false) {
276 if (_active_queue.front()->IsExpired() == true) {
277 delete _active_queue.front();
278 _active_queue.pop_front();
279 }
280 else {
281 // If the front element is not expired, no other elements should be expired either
282 break;
283 }
284 }
285
286 // TODO: determine if there is enough space to insert the next element
287
288 if (_wait_queue.empty() == false) {
289 _active_queue.push_back(_wait_queue.front());
290 _active_queue.back()->Start();
291 _wait_queue.pop_front();
292 }
293 }
294
295
296
Draw()297 void IndicatorSupervisor::Draw() {
298 for (uint32 i = 0; i < _active_queue.size(); i++) {
299 _active_queue[i]->Draw();
300 }
301 }
302
303
304
AddDamageIndicator(uint32 amount)305 void IndicatorSupervisor::AddDamageIndicator(uint32 amount) {
306 if (amount == 0) {
307 IF_PRINT_WARNING(BATTLE_DEBUG) << "function was given a zero value argument" << endl;
308 return;
309 }
310
311 string text = NumberToString(amount);
312 TextStyle style;
313
314 float damage_percent = static_cast<float>(amount / _actor->GetMaxHitPoints());
315 if (damage_percent < 0.10f) {
316 style.font = "text18";
317 style.color = Color(1.0f, 0.275f, 0.275f, 1.0f);
318 }
319 else if (damage_percent < 0.20f) {
320 style.font = "text20";
321 style.color = Color(1.0f, 0.0f, 0.0f, 1.0f);
322 }
323 else if (damage_percent < 0.30f) {
324 style.font = "text22";
325 style.color = Color(0.784f, 0.0f, 0.0f, 1.0f);
326 }
327 else { // (damage_percent >= 0.30f)
328 style.font = "text24";
329 style.color = Color(0.627f, 0.0f, 0.0f, 1.0f);
330 }
331
332 _wait_queue.push_back(new IndicatorText(_actor, text, style));
333 }
334
335
336
AddHealingIndicator(uint32 amount)337 void IndicatorSupervisor::AddHealingIndicator(uint32 amount) {
338 if (amount == 0) {
339 IF_PRINT_WARNING(BATTLE_DEBUG) << "function was given a zero value argument" << endl;
340 return;
341 }
342
343 string text = NumberToString(amount);
344 TextStyle style;
345
346 float healing_percent = static_cast<float>(amount / _actor->GetMaxHitPoints());
347 if (healing_percent < 0.10f) {
348 style.font = "text18";
349 style.color = Color(0.0f, 1.0f, 0.0f, 1.0f);
350 }
351 else if (healing_percent < 0.20f) {
352 style.font = "text20";
353 style.color = Color(0.06f, 1.0f, 0.0f, 1.0f);
354 }
355 else if (healing_percent < 0.30f) {
356 style.font = "text22";
357 style.color = Color(0.0f, 1.0f, 0.0f, 1.0f);
358 }
359 else { // (healing_percent >= 0.30f)
360 style.font = "text24";
361 style.color = Color(0.0f, 1.0f, 0.0f, 1.0f);
362 }
363
364 _wait_queue.push_back(new IndicatorText(_actor, text, style));
365 }
366
367
368
AddMissIndicator()369 void IndicatorSupervisor::AddMissIndicator() {
370 string text = Translate("Miss");
371 TextStyle style("text18", Color::white);
372 _wait_queue.push_back(new IndicatorText(_actor, text, style));
373 }
374
375
376
AddStatusIndicator(GLOBAL_STATUS old_status,GLOBAL_INTENSITY old_intensity,GLOBAL_STATUS new_status,GLOBAL_INTENSITY new_intensity)377 void IndicatorSupervisor::AddStatusIndicator(GLOBAL_STATUS old_status, GLOBAL_INTENSITY old_intensity,
378 GLOBAL_STATUS new_status, GLOBAL_INTENSITY new_intensity)
379 {
380 // If the status and intensity has not changed, only a single status icon needs to be used
381 if ((old_status == new_status) && (old_intensity == new_intensity)) {
382 StillImage* image = BattleMode::CurrentInstance()->GetStatusIcon(new_status, new_intensity);
383 _wait_queue.push_back(new IndicatorImage(_actor, *image));
384 }
385 // Otherwise two status icons need to be used in the indicator image
386 else {
387 StillImage* first_image = BattleMode::CurrentInstance()->GetStatusIcon(old_status, old_intensity);
388 StillImage* second_image = BattleMode::CurrentInstance()->GetStatusIcon(new_status, new_intensity);
389 _wait_queue.push_back(new IndicatorBlendedImage(_actor, *first_image, *second_image));
390 }
391 }
392
393 } // namespace private_battle
394
395 } // namespace hoa_battle
396