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