DungeonCrawl
Loading...
Searching...
No Matches
combat_mode.c
Go to the documentation of this file.
1
5#include "combat_mode.h"
6
9#include "../common.h"
10#include "../game.h"
12#include "../io/io_handler.h"
17#include "ability.h"
19
20#include <stdlib.h>
21#include <unistd.h>
22
23// Internal functions
24void collect_ability_menu_options(ability_t* abilities[], int count);
25void collect_potion_menu_options(potion_t* potions[], int count);
26
27// Internal global variables
28vector2d_t combat_view_anchor = {1, 1};
29internal_combat_state_t combat_state = COMBAT_MENU;
30
31int ability_count = 0;
32int potion_count = 0;
33
34char** ability_menu_options = NULL;
35char** potion_menu_options = NULL;
36
37// Define internal functions
38
44void use_ability(character_t* attacker, character_t* target, const ability_t* ability);
50void use_potion(character_t* player, const character_t* monster, potion_t* potion);
56bool consume_ability_resource(character_t* attacker, const ability_t* ability);
62
68 combat_mode_strings = (char**) malloc(MAX_COMBAT_MODE_STRINGS * sizeof(char*));
69 RETURN_WHEN_NULL(combat_mode_strings, -1, "Combat Mode", "Allocated memory for combat mode strings in memory pool is NULL");
70
71 ability_menu_options = (char**) malloc(sizeof(char*) * MAX_ABILITY_LIMIT);
72 if (ability_menu_options == NULL) {
73 free(combat_mode_strings);
74 combat_mode_strings = NULL;
75 log_msg(ERROR, "Combat Mode", "Failed to allocate memory for ability menu options.");
76 return -1;
77 }
78
79 potion_menu_options = (char**) malloc(sizeof(char*) * MAX_POTION_LIMIT);
80 if (potion_menu_options == NULL) {
81 free(combat_mode_strings);
82 combat_mode_strings = NULL;
83 free(ability_menu_options);
84 log_msg(ERROR, "Combat Mode", "Failed to allocate memory for potion menu options.");
85 return -1;
86 }
87
88 for (int i = 0; i < MAX_COMBAT_MODE_STRINGS; i++) {
89 combat_mode_strings[i] = NULL;
90 }
91
92 for (int i = 0; i < MAX_ABILITY_LIMIT; i++) {
93 ability_menu_options[i] = NULL;
94 }
95
96 for (int i = 0; i < MAX_POTION_LIMIT; i++) {
97 potion_menu_options[i] = NULL;
98 }
99
100 //update local once, so the strings are initialized
102 //add update local function to the observer list
104 return 0;
105}
106
107combat_result_t start_combat(character_t* player, character_t* monster) {
108 // initial combat state
109 const vector2d_t anchor = draw_combat_view(combat_view_anchor, player, monster, GOBLIN_PNG, GOBLIN_HEIGHT, false);
110
111 //collect menu options
112 collect_ability_menu_options(player->abilities, player->ability_count);
113 collect_potion_menu_options(player->potion_inventory, player->potion_count);
114
115 switch (combat_state) {
116 case COMBAT_MENU:
117 combat_state = combat_menu(player, monster);
118 break;
119 case ABILITY_MENU:
120 combat_state = ability_menu(player, monster);
121 break;
122 case POTION_MENU:
123 combat_state = potion_menu(player, monster);
124 break;
125 case EVALUATE_COMBAT:
126 // evaluate the combat result
127 if (player->current_resources.health <= 0) {
128 media_cleanup();
130 return PLAYER_LOST;
131 }
132 if (monster->current_resources.health <= 0) {
133 clear_screen();
134 media_cleanup();
135
136 char message[MAX_STRING_LENGTH];
137 snprintf(message, sizeof(message), "You won the combat! %s is dead.", monster->name);
138 draw_combat_log(anchor, message);
139
140 player->xp += monster->xp_reward;
141 if (player->xp >= calculate_xp_for_next_level(player->level)) {
142 level_up(player);
143 }
144 return PLAYER_WON;
145 }
146 clear_screen();
147 combat_state = COMBAT_MENU;
148 break;
149 case COMBAT_EXIT:
150 media_cleanup();
151 return EXIT_GAME;
152 }
153 return CONTINUE_COMBAT;
154}
155
156internal_combat_state_t combat_menu(const character_t* player, const character_t* monster) {
157 // draw combat view
158 const vector2d_t anchor = draw_combat_view(combat_view_anchor, player, monster, GOBLIN_PNG, GOBLIN_HEIGHT, false);
159 int selected_index = 0;
160
161 internal_combat_state_t new_state = COMBAT_MENU;
162 bool submenu_selected = false;
163
164 while (!submenu_selected) {
165 // draw menu options
166 draw_combat_menu(anchor,
167 combat_mode_strings[MAIN_MENU_TITLE],
168 &combat_mode_strings[MAIN_MENU_OPTION1],
169 2,
170 selected_index,
171 NULL);
172
173 // check for input
174 input_event_t input_event;
175 if (!get_input_blocking(&input_event)) {
176 continue;
177 }
178
179 // Handle input using logical input types
180 switch (input_event.type) {
181 case INPUT_UP:
182 // Move up
183 selected_index = (selected_index - 1 + 2) % 2;
184 break;
185 case INPUT_DOWN:
186 // Move down
187 selected_index = (selected_index + 1) % 2;
188 break;
189 case INPUT_CONFIRM:
190 // Return the selected state
191 if (selected_index == 0) {
192 new_state = ABILITY_MENU;
193 } else if (selected_index == 1) {
194 new_state = POTION_MENU;
195 }
196 submenu_selected = true;
197 break;
198 case INPUT_QUIT:
199 // Exit the game
200 new_state = COMBAT_EXIT;
201 submenu_selected = true;
202 break;
203 default:;
204 }
205 }
206 return new_state;
207}
208
209internal_combat_state_t ability_menu(character_t* player, character_t* monster) {
210 clear_screen();
211 // draw combat view
212 const vector2d_t anchor = draw_combat_view(combat_view_anchor, player, monster, GOBLIN_PNG, GOBLIN_HEIGHT, false);
213 int selected_index = 0;
214
215 internal_combat_state_t new_state = ABILITY_MENU;
216 bool ability_used_or_esc = false;
217
218 while (!ability_used_or_esc) {
219 // draw menu options
220 draw_combat_menu(anchor,
221 combat_mode_strings[ABILITY_MENU_TITLE],
222 ability_menu_options,
223 player->ability_count,
224 selected_index,
225 combat_mode_strings[PRESS_C_RETURN]);
226
227 // check for input
228 input_event_t input_event;
229 if (!get_input_blocking(&input_event)) {
230 continue;
231 }
232
233 // Handle input using logical input types
234 switch (input_event.type) {
235 case INPUT_UP:
236 // Move up
237 selected_index = (selected_index - 1 + player->ability_count) % player->ability_count;
238 break;
239 case INPUT_DOWN:
240 // Move down
241 selected_index = (selected_index + 1) % player->ability_count;
242 break;
243 case INPUT_CONFIRM:
244 // Use ability
245 use_ability(player, monster, player->abilities[selected_index]);
246 use_ability(monster, player, get_random_ability(monster));
247
248 new_state = EVALUATE_COMBAT;
249 ability_used_or_esc = true;
250 break;
251 case INPUT_CANCEL:
252 // go back to the combat menu
253 new_state = COMBAT_MENU;
254 ability_used_or_esc = true;
255 break;
256 default:;
257 }
258 }
259 return new_state;
260}
261
262internal_combat_state_t potion_menu(character_t* player, character_t* monster) {
263 // draw combat view
264 const vector2d_t anchor = draw_combat_view(combat_view_anchor, player, monster, GOBLIN_PNG, GOBLIN_HEIGHT, false);
265 int selected_index = 0;
266
267 if (player->potion_count == 0) {
268 draw_combat_log(anchor, combat_mode_strings[NO_MORE_POTIONS]);
269 return COMBAT_MENU;
270 }
271
272 internal_combat_state_t new_state = POTION_MENU;
273 bool item_used_or_esc = false;
274
275 while (!item_used_or_esc) {
276 // draw menu options
277 draw_combat_menu(anchor,
278 combat_mode_strings[POTION_MENU_TITLE],
279 potion_menu_options,
280 player->potion_count,
281 selected_index,
282 combat_mode_strings[PRESS_C_RETURN]);
283
284 // check for input
285 input_event_t input_event;
286 if (!get_input_blocking(&input_event)) {
287 continue;
288 }
289
290 // Handle input using logical input types
291 switch (input_event.type) {
292 case INPUT_UP:
293 // Move up
294 selected_index = (selected_index - 1 + player->potion_count) % player->potion_count;
295 break;
296 case INPUT_DOWN:
297 // Move down
298 selected_index = (selected_index + 1) % player->potion_count;
299 break;
300 case INPUT_CONFIRM:
301 // Use the selected potion
302 use_potion(player, monster, player->potion_inventory[selected_index]);
303 use_ability(monster, player, get_random_ability(monster));
304 new_state = EVALUATE_COMBAT;
305
306 collect_potion_menu_options(player->potion_inventory, player->potion_count);
307 item_used_or_esc = true;
308 break;
309 case INPUT_CANCEL:
310 // Go back to the combat menu
311 new_state = COMBAT_MENU;
312 item_used_or_esc = true;
313 break;
314 default:;
315 }
316 }
317 return new_state;
318}
319
320void use_ability(character_t* attacker, character_t* target, const ability_t* ability) {
321 character_t* player;
322 character_t* monster;
323 bool sprite;
324 char message[MAX_STRING_LENGTH];
325 if (attacker->type == PLAYER) {
326 player = attacker;
327 monster = target;
328 sprite = true;
329 } else {
330 player = target;
331 monster = attacker;
332 sprite = false;
333 }
334
335 const vector2d_t anchor = draw_combat_view(combat_view_anchor, player, monster, GOBLIN_PNG, GOBLIN_HEIGHT, false);
336 if (consume_ability_resource(attacker, ability)) {
337 if (roll_hit(attacker->current_stats.dexterity, target->current_stats.dexterity)) {
338 const int damage_dealt = deal_damage(target, ability->damage_type, roll_damage(ability));
339
340 draw_combat_view(combat_view_anchor, player, monster, GOBLIN_PNG, GOBLIN_HEIGHT, sprite);
341
342 memset(message, 0, sizeof(message));
343 snprintf(message, sizeof(message), combat_mode_strings[ATTACK_SUCCESS],//TODO: This Method of using formats is not safe!!
344 attacker->name,
345 ability->name,
346 damage_dealt,
347 damage_type_to_string(ability->damage_type),
348 target->name);
349 draw_combat_log(anchor, message);
350 } else {
351 draw_combat_view(combat_view_anchor, player, monster, GOBLIN_PNG, GOBLIN_HEIGHT, false);
352
353 memset(message, 0, sizeof(message));
354 snprintf(message, sizeof(message), combat_mode_strings[ATTACK_MISS],//TODO: This Method of using formats is not safe!!
355 attacker->name,
356 ability->name);
357 draw_combat_log(anchor, message);
358 }
359 } else {
360 memset(message, 0, sizeof(message));
361 snprintf(message, sizeof(message), combat_mode_strings[ATTACK_FAIL],//TODO: This Method of using formats is not safe!!
362 attacker->name,
363 ability->name);
364 draw_combat_log(anchor, message);
365 }
366 render_frame();
367}
368
369void use_potion(character_t* player, const character_t* monster, potion_t* potion) {
370 const vector2d_t anchor = draw_combat_view(combat_view_anchor, player, monster, GOBLIN_PNG, GOBLIN_HEIGHT, false);
371 invoke_potion_effect(player, potion);
372
373 char message[MAX_STRING_LENGTH];
374 snprintf(message, sizeof(message), combat_mode_strings[POTION_USE],//TODO: This Method of using formats is not safe!!
375 player->name,
376 potion->name,
377 potion->value,
378 potion_type_to_string(potion->effectType));
379 draw_combat_log(anchor, message);
380}
381
383 const int random_index = rand() % character->ability_count;
384 return character->abilities[random_index];
385}
386
387void invoke_potion_effect(character_t* character, potion_t* potion) {
388 switch (potion->effectType) {
389 case HEALING:
390 if (potion->value > (character->max_resources.health - character->current_resources.health)) {
391 character->current_resources.health = character->max_resources.health;
392 } else {
393 character->current_resources.health += potion->value;
394 }
395 break;
396 case MANA:
397 if (potion->value > (character->max_resources.mana - character->current_resources.mana)) {
398 character->current_resources.mana = character->max_resources.mana;
399 } else {
400 character->current_resources.mana += potion->value;
401 }
402 break;
403
404 case STAMINA:
405 if (potion->value > (character->max_resources.stamina - character->current_resources.stamina)) {
406 character->current_resources.stamina = character->max_resources.stamina;
407 } else {
408 character->current_resources.stamina += potion->value;
409 }
410 break;
411
412 default:
413 log_msg(ERROR, "Character", "Unknown potion effect type: %d", potion->effectType);
414 break;
415 }
416 remove_potion(character, potion);
417}
418
419bool consume_ability_resource(character_t* attacker, const ability_t* ability) {
420 int* resource = NULL;
421
422 switch (ability->damage_type) {
423 case PHYSICAL:
424 resource = &attacker->current_resources.stamina;
425 break;
426 case MAGICAL:
427 resource = &attacker->current_resources.mana;
428 break;
429 }
430
431 if (resource != NULL && *resource >= ability->resource_cost) {
432 *resource -= ability->resource_cost;
433 return true;
434 }
435 return false;
436}
437
444void collect_ability_menu_options(ability_t* abilities[], const int count) {
445 for (int i = 0; i < MAX_ABILITY_LIMIT; i++) {
446 if (ability_menu_options[i] != NULL) {
447 free(ability_menu_options[i]);
448 ability_menu_options[i] = NULL;
449 }
450 }
451
452 for (int i = 0; i < count; i++) {
453 ability_menu_options[i] = (char*) malloc(MAX_STRING_LENGTH * sizeof(char));
454 if (ability_menu_options[i] == NULL) {
455 for (int j = 0; j < i; j++) {
456 free(ability_menu_options[j]);
457 ability_menu_options[j] = NULL;
458 }
459 log_msg(ERROR, "Combat Mode", "Failed to allocate memory for ability menu options.");
460 return;
461 }
462 }
463
464 for (int i = 0; i < count; i++) {
465 snprintf(ability_menu_options[i], MAX_STRING_LENGTH,
466 combat_mode_strings[ABILITY_FORMAT],//TODO: This Method of using formats is not safe!!
467 abilities[i]->name,
468 abilities[i]->roll_amount,
469 abilities[i]->accuracy,
470 abilities[i]->resource_cost,
471 dice_size_to_string(abilities[i]->dice_size),
472 damage_type_to_string(abilities[i]->damage_type));
473 }
474}
475
482void collect_potion_menu_options(potion_t* potions[], const int count) {
483 for (int i = 0; i < MAX_POTION_LIMIT; i++) {
484 if (potion_menu_options[i] != NULL) {
485 free(potion_menu_options[i]);
486 potion_menu_options[i] = NULL;
487 }
488 }
489
490 for (int i = 0; i < count; i++) {
491 potion_menu_options[i] = (char*) malloc(MAX_STRING_LENGTH * sizeof(char));
492 if (potion_menu_options[i] == NULL) {
493 for (int j = 0; j < i; j++) {
494 free(potion_menu_options[j]);
495 potion_menu_options[j] = NULL;
496 }
497 log_msg(ERROR, "Combat Mode", "Failed to allocate memory for potion menu options.");
498 return;
499 }
500 }
501
502 for (int i = 0; i < count; i++) {
503 snprintf(potion_menu_options[i], MAX_STRING_LENGTH,
504 combat_mode_strings[POTION_FORMAT],//TODO: This Method of using formats is not safe!!
505 potions[i]->name,
506 potion_type_to_string(potions[i]->effectType),
507 potions[i]->value);
508 }
509}
510
512 if (combat_mode_strings != NULL) {
513 for (int i = 0; i < MAX_COMBAT_MODE_STRINGS; i++) {
514 if (combat_mode_strings[i] != NULL) {
515 free(combat_mode_strings[i]);
516 combat_mode_strings[i] = NULL;
517 }
518 }
519 free(combat_mode_strings);
520 combat_mode_strings = NULL;
521 }
522
523 if (ability_menu_options != NULL) {
524 for (int i = 0; i < MAX_ABILITY_LIMIT; i++) {
525 if (ability_menu_options[i] != NULL) {
526 free(ability_menu_options[i]);
527 ability_menu_options[i] = NULL;
528 }
529 }
530 free(ability_menu_options);
531 ability_menu_options = NULL;
532 }
533
534 if (potion_menu_options != NULL) {
535 for (int i = 0; i < MAX_POTION_LIMIT; i++) {
536 if (potion_menu_options[i] != NULL) {
537 free(potion_menu_options[i]);
538 potion_menu_options[i] = NULL;
539 }
540 }
541 free(potion_menu_options);
542 potion_menu_options = NULL;
543 }
544}
Exposes functions for working with abilities.
void remove_potion(character_t *character, potion_t *potion)
Removes a potion from a character's inventory.
Definition character.c:254
Exposes functions for working working with the character.
internal_combat_state_t combat_menu(const character_t *player, const character_t *monster)
Collects the menu options for the ability menu.
ability_t * get_random_ability(const character_t *character)
Get a random ability from the character's abilities.
void collect_ability_menu_options(ability_t *abilities[], int count)
Collects all the options for abilities in the abilities menu for displaying.
void shutdown_combat_mode()
Shuts down the combat mode and frees allocated memory resources.
int init_combat_mode()
Initialize the combat mode.
Definition combat_mode.c:67
void invoke_potion_effect(character_t *character, potion_t *potion)
Invoke the effect of a potion on a character.
internal_combat_state_t ability_menu(character_t *player, character_t *monster)
Collects the menu options for the ability menu.
void collect_potion_menu_options(potion_t *potions[], int count)
Collects all the options for potions in the potion menu for displaying.
internal_combat_state_t potion_menu(character_t *player, character_t *monster)
Collects the menu options for the potion menu.
void use_ability(character_t *attacker, character_t *target, const ability_t *ability)
Use an ability on a target character.
void use_potion(character_t *player, const character_t *monster, potion_t *potion)
Use a potion on a target character.
combat_result_t start_combat(character_t *player, character_t *monster)
Starts the loop for combat between the player and the monster.
bool consume_ability_resource(character_t *attacker, const ability_t *ability)
Consumes the mana or stamina resource of the attacker character.
Declares combat mode state machine, including menus and combat operations.
void update_combat_local(void)
Updates the combat mode strings with localized versions.
Exposes functions for working with the localization of the combat mode.
void draw_combat_log(vector2d_t anchor, const char *combat_log_message)
Draws the combat log.
void draw_combat_menu(const vector2d_t anchor, const char *menu_name, char **menu_options, const int menu_option_count, const int selected_index, const char *tail_msg)
Draws the combat menu.
vector2d_t draw_combat_view(const vector2d_t anchor, const character_t *player, const character_t *enemy, const char *enemy_sprite, const int sprite_height, const bool red_enemy_sprite)
Draws the combat view UI.
void draw_game_over(void)
Draws the game over screen.
Exposes functions for outputing to the screen while in the combat mode.
Defines common macros, types, and global variables for color schemes and utilities.
const char * dice_size_to_string(const dice_size_t size)
Converts a dice size enum to a string representation.
Definition damage.c:61
bool roll_hit(const int attacker_dex, const int defender_dex)
Rolls a D20 to determine if an attack hits.
Definition damage.c:25
int deal_damage(character_t *character, damage_type_t damage_type, const int damage)
Deals damage to a character based on the damage type and amount.
Definition damage.c:42
const char * damage_type_to_string(const damage_type_t type)
Converts a damage type enum to a string representation.
Definition damage.c:78
int roll_damage(const ability_t *ability)
Rolls damage based on the ability's roll amount and dice size.
Definition damage.c:33
Declares core game states, global database connection, and main game control functions.
bool get_input_blocking(input_event_t *event)
Get the next input event (blocking)
Exposes functions for working with input.
Exposes functions for the IO-Handler.
void level_up(character_t *player)
Handles the level-up process for a character.
Definition level.c:27
int calculate_xp_for_next_level(int level)
Calculates the XP required for the next level.
Definition level.c:11
Exposes functions for working with the character.
void observe_local(void(*update_func)(void))
Registers an observer function to be notified of updates from the local handler.
Exposes public functions for the localization handler.
void log_msg(const log_level_t level, const char *module, const char *format,...)
Logs a formatted message with a specified log level and module.
Definition logger.c:246
Defines the path and metadata to media files.
bool render_frame(void)
Render the current frame.
void clear_screen(void)
Clear the screen.
Exposes functions for outputting to the console.
const char * potion_type_to_string(potion_type_t type)
Converts a potion type to a string representation.
Definition potion.c:40
Structure for advanced input events with context data.
Definition input_types.h:43
2-dimensional vector struct
Definition common.h:164