1 /*
2 Copyright (C) 2009-2021 Parallel Realities
3 
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8 
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12 
13 See the GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
18 */
19 
20 #include "headers.h"
21 
22 #include "audio/audio.h"
23 #include "graphics/font.h"
24 #include "graphics/graphics.h"
25 #include "graphics/texture_cache.h"
26 #include "hud.h"
27 #include "inventory.h"
28 #include "medal.h"
29 #include "system/error.h"
30 
31 extern Game game;
32 extern Entity player, *self, playerWeapon;
33 
34 static Hud hud;
35 static Message messageHead;
36 
37 static void addMessageToQueue(char *, int, int, int, int);
38 static void getNextMessageFromQueue(void);
39 
initHud()40 void initHud()
41 {
42 	SDL_Surface *heart;
43 
44 	hud.itemBox = loadImage("gfx/hud/item_box.png");
45 
46 	heart = loadImageAsSurface("gfx/hud/heart.png");
47 
48 	hud.heart = convertSurfaceToTexture(heart, FALSE);
49 
50 	hud.whiteHeart = convertImageToWhite(heart, TRUE);
51 
52 	hud.emptyHeart = loadImage("gfx/hud/heart_empty.png");
53 
54 	hud.spotlight = loadImage("gfx/hud/spotlight.png");
55 
56 	hud.medalSurface[0] = loadImage("gfx/hud/bronze_medal.png");
57 
58 	hud.medalSurface[1] = loadImage("gfx/hud/silver_medal.png");
59 
60 	hud.medalSurface[2] = loadImage("gfx/hud/gold_medal.png");
61 
62 	hud.medalSurface[3] = loadImage("gfx/hud/ruby_medal.png");
63 
64 	hud.disabledMedalSurface = loadImage("gfx/hud/disabled_medal.png");
65 
66 	messageHead.next = NULL;
67 
68 	hud.bossHealth = NULL;
69 
70 	hud.medalTextSurface = NULL;
71 
72 	hud.slimeTimerSurface = NULL;
73 }
74 
doHud()75 void doHud()
76 {
77 	hud.thinkTime--;
78 
79 	if (hud.thinkTime <= 0)
80 	{
81 		hud.thinkTime = 60;
82 	}
83 
84 	hud.medalThinkTime--;
85 
86 	if (hud.medalThinkTime <= 0)
87 	{
88 		if (hud.medalTextSurface != NULL)
89 		{
90 			destroyTexture(hud.medalTextSurface);
91 
92 			hud.medalTextSurface = NULL;
93 
94 			medalProcessingFinished();
95 		}
96 
97 		hud.medalThinkTime = 0;
98 	}
99 
100 	hud.infoMessage.thinkTime--;
101 
102 	if (hud.infoMessage.thinkTime <= 0)
103 	{
104 		if (hud.infoMessage.surface != NULL)
105 		{
106 			hud.infoMessage.surface = NULL;
107 
108 			hud.infoMessage.text[0] = '\0';
109 		}
110 
111 		getNextMessageFromQueue();
112 	}
113 
114 	if (hud.bossHealth != NULL)
115 	{
116 		if (hud.bossHealthIndex < *hud.bossHealth)
117 		{
118 			hud.bossHealthIndex += (hud.bossMaxHealth / 100);
119 
120 			if (hud.bossHealthIndex > *hud.bossHealth)
121 			{
122 				hud.bossHealthIndex = *hud.bossHealth;
123 			}
124 		}
125 
126 		else if (*hud.bossHealth < hud.bossHealthIndex)
127 		{
128 			hud.bossHealthIndex -= 3;
129 
130 			if (hud.bossHealthIndex < *hud.bossHealth)
131 			{
132 				hud.bossHealthIndex = *hud.bossHealth;
133 			}
134 		}
135 	}
136 }
137 
drawHud()138 void drawHud()
139 {
140 	char quantity[4];
141 	char cacheName[10];
142 	int i, x, y, h, w, itemBoxMid, quant;
143 	float percentage, clipWidth;
144 	Entity *e;
145 	SDL_Surface *quantitySurface;
146 
147 	if (game.showHUD == TRUE)
148 	{
149 		itemBoxMid = (SCREEN_WIDTH - hud.itemBox->w) / 2;
150 
151 		if (game.status == IN_INVENTORY)
152 		{
153 			drawBox(itemBoxMid, 15, hud.itemBox->w, hud.itemBox->h, 0, 0, 0, 255);
154 		}
155 
156 		drawSelectedInventoryItem(itemBoxMid, 15, hud.itemBox->w, hud.itemBox->h);
157 
158 		drawImage(hud.itemBox, itemBoxMid, 15, FALSE, 255);
159 
160 		x = FALSE;
161 
162 		if (playerWeapon.inUse == TRUE)
163 		{
164 			if (strcmpignorecase(playerWeapon.name, "weapon/bow") == 0)
165 			{
166 				e = getInventoryItemByObjectiveName(playerWeapon.requires);
167 
168 				if (e != NULL)
169 				{
170 					x = TRUE;
171 
172 					quant = e->health;
173 				}
174 			}
175 
176 			else if (strcmpignorecase(playerWeapon.name, "weapon/lightning_sword") == 0)
177 			{
178 				x = TRUE;
179 
180 				quant = playerWeapon.mental;
181 			}
182 
183 			if (x == TRUE)
184 			{
185 				if (quant < 0)
186 				{
187 					quant = 0;
188 				}
189 
190 				if (hud.quantity != quant || (game.frames % (TEXTURE_CACHE_TIME / 2)) == 0)
191 				{
192 					SNPRINTF(quantity, 4, "%d", quant);
193 
194 					SNPRINTF(cacheName, 10, "hud_%d", quant);
195 
196 					hud.quantitySurface = getTextureFromCache(cacheName);
197 
198 					if (hud.quantitySurface == NULL)
199 					{
200 						quantitySurface = generateTransparentTextSurface(quantity, game.font, 255, 255, 255, FALSE);
201 
202 						hud.quantitySurface = convertSurfaceToTexture(quantitySurface, TRUE);
203 
204 						addTextureToCache(cacheName, hud.quantitySurface, FALSE);
205 					}
206 
207 					hud.quantity = quant;
208 				}
209 
210 				drawImage(hud.quantitySurface, (SCREEN_WIDTH - hud.quantitySurface->w) / 2, 15 + hud.itemBox->h + 5, FALSE, 255);
211 			}
212 		}
213 
214 		if (x == FALSE)
215 		{
216 			hud.quantity = -1;
217 		}
218 
219 		percentage = 0;
220 
221 		if (hud.bossHealth != NULL)
222 		{
223 			x = SCREEN_WIDTH - 6;
224 			y = 5;
225 
226 			x -= (hud.heart->w + 5) * 10;
227 
228 			percentage = hud.bossHealthIndex * 100;
229 
230 			percentage /= hud.bossMaxHealth;
231 
232 			for (i=10;i<=100;i+=10)
233 			{
234 				if (i <= percentage)
235 				{
236 					drawImage(hud.heart, x, y, FALSE, 255);
237 				}
238 
239 				else if (i - 10 < percentage)
240 				{
241 					clipWidth = (percentage - (i - 10)) / 10;
242 
243 					w = hud.heart->w * clipWidth;
244 
245 					drawClippedImage(hud.heart, 0, 0, x, y, w, hud.heart->h);
246 				}
247 
248 				drawImage(hud.emptyHeart, x, y, FALSE, 255);
249 
250 				x += hud.heart->w + 5;
251 			}
252 		}
253 
254 		if (hud.infoMessage.surface != NULL)
255 		{
256 			drawImage(hud.infoMessage.surface, (SCREEN_WIDTH - hud.infoMessage.surface->w) / 2, SCREEN_HEIGHT - TILE_SIZE - 1, FALSE, 255);
257 		}
258 
259 		w = h = 5;
260 
261 		for (i=0;i<player.maxHealth;i++)
262 		{
263 			if (i != 0 && (i % 10) == 0)
264 			{
265 				h += hud.heart->h;
266 
267 				w = 5;
268 			}
269 
270 			if (i < player.health)
271 			{
272 				if (player.health <= 3 && hud.thinkTime <= 30)
273 				{
274 					drawImage(hud.whiteHeart, w, h, FALSE, 255);
275 				}
276 
277 				else
278 				{
279 					drawImage(hud.heart, w, h, FALSE, 255);
280 				}
281 			}
282 
283 			drawImage(hud.emptyHeart, w, h, FALSE, 255);
284 
285 			w += hud.heart->w + 5;
286 		}
287 	}
288 
289 	if (hud.medalTextSurface != NULL)
290 	{
291 		x = SCREEN_WIDTH - hud.medalTextSurface->w - 5;
292 
293 		drawImage(hud.medalTextSurface, x, 5, FALSE, 255);
294 	}
295 
296 	if (hud.slimeTimerSurface != NULL)
297 	{
298 		x = player.x + player.w / 2 - hud.slimeTimerSurface->w / 2;
299 		y = player.y - hud.slimeTimerSurface->h - 5;
300 
301 		drawImageToMap(hud.slimeTimerSurface, x, y, FALSE, 255);
302 	}
303 }
304 
freeHud()305 void freeHud()
306 {
307 	int i;
308 
309 	if (hud.itemBox != NULL)
310 	{
311 		destroyTexture(hud.itemBox);
312 
313 		hud.itemBox = NULL;
314 	}
315 
316 	if (hud.heart != NULL)
317 	{
318 		destroyTexture(hud.heart);
319 
320 		hud.heart = NULL;
321 	}
322 
323 	if (hud.emptyHeart != NULL)
324 	{
325 		destroyTexture(hud.emptyHeart);
326 
327 		hud.emptyHeart = NULL;
328 	}
329 
330 	if (hud.spotlight != NULL)
331 	{
332 		destroyTexture(hud.spotlight);
333 
334 		hud.spotlight = NULL;
335 	}
336 
337 	if (hud.infoMessage.surface != NULL)
338 	{
339 		hud.infoMessage.surface = NULL;
340 	}
341 
342 	if (hud.medalTextSurface != NULL)
343 	{
344 		destroyTexture(hud.medalTextSurface);
345 
346 		hud.medalTextSurface = NULL;
347 	}
348 
349 	hud.quantitySurface = NULL;
350 
351 	for (i=0;i<4;i++)
352 	{
353 		if (hud.medalSurface[i] != NULL)
354 		{
355 			destroyTexture(hud.medalSurface[i]);
356 
357 			hud.medalSurface[i] = NULL;
358 		}
359 	}
360 
361 	if (hud.disabledMedalSurface != NULL)
362 	{
363 		destroyTexture(hud.disabledMedalSurface);
364 
365 		hud.disabledMedalSurface = NULL;
366 	}
367 
368 	hud.slimeTimerSurface = NULL;
369 
370 	freeMessageQueue();
371 }
372 
setSlimeTimerValue(int value)373 void setSlimeTimerValue(int value)
374 {
375 	SDL_Surface *slimeTimerSurface;
376 
377 	char timeValue[5];
378 
379 	if (hud.slimeTimerSurface != NULL || value < 0)
380 	{
381 		hud.slimeTimerSurface = NULL;
382 
383 		if (value < 0)
384 		{
385 			return;
386 		}
387 	}
388 
389 	SNPRINTF(timeValue, 5, "%d", value);
390 
391 	hud.slimeTimerSurface = getTextureFromCache(timeValue);
392 
393 	if (hud.slimeTimerSurface == NULL)
394 	{
395 		slimeTimerSurface = generateTextSurface(timeValue, game.font, 220, 220, 220, 0, 0, 0);
396 
397 		hud.slimeTimerSurface = addBorder(slimeTimerSurface, 255, 255, 255, 0, 0, 0);
398 
399 		addTextureToCache(timeValue, hud.slimeTimerSurface, FALSE);
400 	}
401 }
402 
setInfoBoxMessage(int thinkTime,int r,int g,int b,char * fmt,...)403 void setInfoBoxMessage(int thinkTime, int r, int g, int b, char *fmt, ...)
404 {
405 	char text[MAX_MESSAGE_LENGTH];
406 	va_list ap;
407 
408 	va_start(ap, fmt);
409 	vsnprintf(text, sizeof(text), fmt, ap);
410 	va_end(ap);
411 
412 	addMessageToQueue(text, thinkTime, r, g, b);
413 }
414 
addMessageToQueue(char * text,int thinkTime,int r,int g,int b)415 static void addMessageToQueue(char *text, int thinkTime, int r, int g, int b)
416 {
417 	Message *head, *msg;
418 
419 	head = &messageHead;
420 
421 	while (head->next != NULL)
422 	{
423 		if (strcmpignorecase(text, head->text) == 0)
424 		{
425 			return;
426 		}
427 
428 		head = head->next;
429 	}
430 
431 	msg = malloc(sizeof(Message));
432 
433 	if (msg == NULL)
434 	{
435 		showErrorAndExit("Failed to allocate %d bytes for message queue", (int)sizeof(Message));
436 	}
437 
438 	STRNCPY(msg->text, text, sizeof(messageHead.text));
439 
440 	msg->thinkTime = thinkTime;
441 
442 	msg->r = r;
443 	msg->g = g;
444 	msg->b = b;
445 
446 	msg->next = NULL;
447 
448 	head->next = msg;
449 }
450 
getNextMessageFromQueue()451 static void getNextMessageFromQueue()
452 {
453 	Message *head = messageHead.next;
454 	SDL_Surface *infoSurface;
455 
456 	if (head != NULL)
457 	{
458 		STRNCPY(hud.infoMessage.text, head->text, sizeof(hud.infoMessage.text));
459 
460 		hud.infoMessage.r = head->r;
461 		hud.infoMessage.g = head->g;
462 		hud.infoMessage.b = head->b;
463 
464 		hud.infoMessage.surface = getTextureFromCache(hud.infoMessage.text);
465 
466 		if (hud.infoMessage.surface == NULL)
467 		{
468 			infoSurface = generateTextSurface(hud.infoMessage.text, game.font, hud.infoMessage.r, hud.infoMessage.g, hud.infoMessage.b, 0, 0, 0);
469 
470 			hud.infoMessage.surface = addBorder(infoSurface, 255, 255, 255, 0, 0, 0);
471 
472 			addTextureToCache(hud.infoMessage.text, hud.infoMessage.surface, FALSE);
473 		}
474 
475 		hud.infoMessage.thinkTime = (head->thinkTime <= 0 ? 5 : head->thinkTime);
476 
477 		messageHead.next = head->next;
478 
479 		free(head);
480 	}
481 }
482 
freeMessageQueue()483 void freeMessageQueue()
484 {
485 	Message *p, *q;
486 
487 	for (p=messageHead.next;p!=NULL;p=q)
488 	{
489 		q = p->next;
490 
491 		free(p);
492 	}
493 
494 	messageHead.next = NULL;
495 
496 	if (hud.infoMessage.surface != NULL)
497 	{
498 		hud.infoMessage.surface = NULL;
499 
500 		hud.infoMessage.text[0] = '\0';
501 
502 		hud.infoMessage.thinkTime = 0;
503 	}
504 }
505 
initBossHealthBar()506 void initBossHealthBar()
507 {
508 	hud.bossHealth = &self->health;
509 
510 	hud.bossMaxHealth = self->health;
511 
512 	hud.bossHealthIndex = 0;
513 }
514 
freeBossHealthBar()515 void freeBossHealthBar()
516 {
517 	hud.bossHealth = NULL;
518 }
519 
drawSpotlight(int x,int y)520 void drawSpotlight(int x, int y)
521 {
522 	drawImage(hud.spotlight, x - game.offsetX, y - game.offsetY, FALSE, 255);
523 }
524 
showMedal(int medalType,char * message)525 void showMedal(int medalType, char *message)
526 {
527 	SDL_Surface *textSurface, *medalSurface;
528 	SDL_Rect dest;
529 	Texture *medalTexture, *textTexture, *targetTexture;
530 
531 	if (hud.medalTextSurface != NULL)
532 	{
533 		return;
534 	}
535 
536 	textSurface = generateTextSurface(_(message), game.font, 0, 220, 0, 0, 0, 0);
537 
538 	textTexture = convertSurfaceToTexture(textSurface, TRUE);
539 
540 	medalSurface = createSurface(textSurface->w + hud.medalSurface[medalType]->w + 18, MAX(textTexture->h, hud.medalSurface[medalType]->h), FALSE);
541 
542 	medalTexture = addBorder(medalSurface, 255, 255, 255, 0, 0, 0);
543 
544 	targetTexture = createWritableTexture(medalTexture->w, medalTexture->h);
545 
546 	SDL_SetRenderTarget(game.renderer, targetTexture->texture);
547 
548 	SDL_RenderCopy(game.renderer, medalTexture->texture, NULL, NULL);
549 
550 	dest.x = 5 + BORDER_PADDING;
551 	dest.y = medalTexture->h / 2 - hud.medalSurface[medalType]->h / 2;
552 	dest.w = hud.medalSurface[medalType]->w;
553 	dest.h = hud.medalSurface[medalType]->h;
554 
555 	SDL_RenderCopy(game.renderer, hud.medalSurface[medalType]->texture, NULL, &dest);
556 
557 	dest.x = hud.medalSurface[medalType]->w + 13 + BORDER_PADDING;
558 	dest.y = medalTexture->h / 2 - textTexture->h / 2;
559 	dest.w = textTexture->w;
560 	dest.h = textTexture->h;
561 
562 	SDL_RenderCopy(game.renderer, textTexture->texture, NULL, &dest);
563 
564 	hud.medalTextSurface = targetTexture;
565 
566 	hud.medalThinkTime = 180;
567 
568 	playSound("sound/common/trophy");
569 
570 	SDL_SetRenderTarget(game.renderer, NULL);
571 }
572 
spotlightSize()573 int spotlightSize()
574 {
575 	return hud.spotlight->w;
576 }
577 
getMedalImage(int medalType,int obtained)578 Texture *getMedalImage(int medalType, int obtained)
579 {
580 	return obtained == TRUE ? hud.medalSurface[medalType] : hud.disabledMedalSurface;
581 }
582