#include "rlc_turn_manager.h" #include "rlc_components.h" #include "rlc_items.h" #include "rlc_log.h" #include #include #include #include /* Action queue comparison function for qsort */ static int action_compare(const void *a, const void *b) { const QueuedAction *qa = (const QueuedAction *)a; const QueuedAction *qb = (const QueuedAction *)b; /* Sort by delay ascending, then priority descending */ if (qa->delay != qb->delay) return qa->delay - qb->delay; return qb->priority - qa->priority; } /* Event queue comparison function for qsort */ static int event_compare(const void *a, const void *b) { const DelayedEvent *ea = (const DelayedEvent *)a; const DelayedEvent *eb = (const DelayedEvent *)b; /* Sort by turn_due ascending, then priority descending */ /* Use comparison operators instead of subtraction to prevent overflow */ if (ea->turn_due < eb->turn_due) return -1; if (ea->turn_due > eb->turn_due) return 1; if (eb->priority < ea->priority) return -1; if (eb->priority > ea->priority) return 1; return 0; } /* Action queue management */ void rlc_action_queue_init(TurnState *ts) { ts->action_capacity = 32; ts->action_count = 0; ts->action_queue = malloc(sizeof(QueuedAction) * ts->action_capacity); } void rlc_action_queue_push(TurnState *ts, QueuedAction action) { if (ts->action_count >= ts->action_capacity) { size_t new_capacity = ts->action_capacity * 2; QueuedAction *new_queue = realloc(ts->action_queue, sizeof(QueuedAction) * new_capacity); if (!new_queue) { RLC_LOG_ERROR("Failed to resize action queue (capacity=%zu)", ts->action_capacity); return; } ts->action_queue = new_queue; ts->action_capacity = new_capacity; } ts->action_queue[ts->action_count++] = action; } void rlc_action_queue_sort(TurnState *ts) { if (ts->action_count > 1) { qsort(ts->action_queue, ts->action_count, sizeof(QueuedAction), action_compare); } } void rlc_action_queue_clear(TurnState *ts) { ts->action_count = 0; } void rlc_action_queue_destroy(TurnState *ts) { if (ts->action_queue) { free(ts->action_queue); ts->action_queue = NULL; ts->action_count = 0; ts->action_capacity = 0; } } /* Event queue management */ void rlc_event_queue_init(TurnState *ts) { ts->event_capacity = 16; ts->event_count = 0; ts->event_queue = malloc(sizeof(DelayedEvent) * ts->event_capacity); } void rlc_event_queue_push(TurnState *ts, DelayedEvent event) { if (ts->event_count >= ts->event_capacity) { size_t new_capacity = ts->event_capacity * 2; DelayedEvent *new_queue = realloc(ts->event_queue, sizeof(DelayedEvent) * new_capacity); if (!new_queue) { RLC_LOG_ERROR("Failed to resize event queue (capacity=%zu)", ts->event_capacity); return; } ts->event_queue = new_queue; ts->event_capacity = new_capacity; } ts->event_queue[ts->event_count++] = event; } void rlc_event_queue_process(ecs_world_t *world, TurnState *ts) { if (ts->event_count == 0) return; /* Sort events by turn_due and priority */ qsort(ts->event_queue, ts->event_count, sizeof(DelayedEvent), event_compare); size_t remaining = 0; for (size_t i = 0; i < ts->event_count; i++) { DelayedEvent *event = &ts->event_queue[i]; if (event->turn_due <= ts->round_number) { /* Execute event */ if (event->callback) { event->callback(world, event->data); } /* Free data if allocated */ if (event->data) { free(event->data); event->data = NULL; } } else { /* Keep event - move to front if needed */ if (remaining != i) { ts->event_queue[remaining] = *event; } remaining++; } } ts->event_count = remaining; } void rlc_event_queue_clear(TurnState *ts) { for (size_t i = 0; i < ts->event_count; i++) { if (ts->event_queue[i].data) { free(ts->event_queue[i].data); ts->event_queue[i].data = NULL; } } ts->event_count = 0; } void rlc_event_queue_destroy(TurnState *ts) { if (ts->event_queue) { rlc_event_queue_clear(ts); free(ts->event_queue); ts->event_queue = NULL; ts->event_capacity = 0; } } /* Turn manager initialization */ void rlc_turn_manager_init(ecs_world_t *world) { TurnState *ts = ecs_singleton_get_mut(world, TurnState); if (ts) { memset(ts, 0, sizeof(TurnState)); ts->phase = PHASE_WAITING_FOR_PLAYER; ts->round_number = 1; ts->processing = false; ts->phase_transitioned = false; rlc_action_queue_init(ts); rlc_event_queue_init(ts); } } /* --- Message Log API Implementation --- */ const char* rlc_get_entity_display_name(ecs_world_t *world, ecs_entity_t entity) { if (!world || !entity) { return "something"; } /* Check for flecs built-in name first */ const char *name = ecs_get_name(world, entity); if (name && name[0] != '\0') { return name; } /* Check if it's the player */ if (ecs_has(world, entity, Player)) { return "you"; } return "something"; } void rlc_log_message(ecs_world_t *world, int type, const char *fmt, ...) { if (!world || !fmt) return; MessageLog *log = ecs_singleton_get_mut(world, MessageLog); if (!log) return; const TurnState *ts = ecs_singleton_get(world, TurnState); int turn = ts ? ts->round_number : 0; /* Format the message */ char formatted[128]; va_list args; va_start(args, fmt); vsnprintf(formatted, sizeof(formatted), fmt, args); va_end(args); /* Check for stacking with the most recent message */ if (log->message_count > 0) { int prev_idx = (log->write_index - 1 + MAX_LOG_MESSAGES) % MAX_LOG_MESSAGES; LogMessage *prev = &log->messages[prev_idx]; /* Stack if same message on same turn */ if ((int)prev->type == type && prev->turn_number == turn && strcmp(prev->formatted, formatted) == 0) { prev->stack_count++; log->updated_this_frame = true; return; } } /* Write new message */ LogMessage *msg = &log->messages[log->write_index]; strncpy(msg->formatted, formatted, sizeof(msg->formatted) - 1); msg->formatted[sizeof(msg->formatted) - 1] = '\0'; msg->type = (LogMessageType)type; msg->turn_number = turn; msg->stack_count = 1; /* Advance write index (circular) */ log->write_index = (log->write_index + 1) % MAX_LOG_MESSAGES; if (log->message_count < MAX_LOG_MESSAGES) { log->message_count++; } log->updated_this_frame = true; } void rlc_log_combat(ecs_world_t *world, ecs_entity_t attacker, ecs_entity_t target, int damage, bool critical) { if (!world) return; /* Cache names at call time to avoid entity recycling issues */ const char *attacker_name = rlc_get_entity_display_name(world, attacker); const char *target_name = rlc_get_entity_display_name(world, target); /* Copy names to local buffers in case entities are deleted */ char atk_buf[64], tgt_buf[64]; snprintf(atk_buf, sizeof(atk_buf), "%s", attacker_name); snprintf(tgt_buf, sizeof(tgt_buf), "%s", target_name); /* Capitalize first letter for display */ if (atk_buf[0] >= 'a' && atk_buf[0] <= 'z') { atk_buf[0] = atk_buf[0] - 'a' + 'A'; } /* Check if target is dead (for kill message) */ const Health *target_hp = ecs_get(world, target, Health); bool is_kill = target_hp && target_hp->current <= 0; if (is_kill) { rlc_log_message(world, LOG_MSG_COMBAT_KILL, "%s kills %s!", atk_buf, tgt_buf); } else if (critical) { rlc_log_message(world, LOG_MSG_COMBAT_CRITICAL, "%s critically hits %s for %d damage!", atk_buf, tgt_buf, damage); } else if (damage > 0) { rlc_log_message(world, LOG_MSG_COMBAT_HIT, "%s hits %s for %d damage.", atk_buf, tgt_buf, damage); } else { rlc_log_message(world, LOG_MSG_COMBAT_MISS, "%s misses %s.", atk_buf, tgt_buf); } } void rlc_log_combat_detailed(ecs_world_t *world, ecs_entity_t attacker, ecs_entity_t target, int raw_damage, int defense, int final_damage, ecs_entity_t weapon_entity) { if (!world) return; const char *attacker_name = rlc_get_entity_display_name(world, attacker); const char *target_name = rlc_get_entity_display_name(world, target); char atk_buf[64], tgt_buf[64]; snprintf(atk_buf, sizeof(atk_buf), "%s", attacker_name); snprintf(tgt_buf, sizeof(tgt_buf), "%s", target_name); if (atk_buf[0] >= 'a' && atk_buf[0] <= 'z') { atk_buf[0] = atk_buf[0] - 'a' + 'A'; } const Health *target_hp = ecs_get(world, target, Health); bool is_kill = target_hp && target_hp->current <= 0; if (is_kill) { rlc_log_message(world, LOG_MSG_COMBAT_KILL, "%s kills %s!", atk_buf, tgt_buf); } else if (final_damage > 0) { /* Get weapon name for detailed message */ const char *wpn_name = NULL; if (weapon_entity) { const Name *wn = ecs_get(world, weapon_entity, Name); if (wn) wpn_name = wn->str; } if (wpn_name && defense > 0) { rlc_log_message(world, LOG_MSG_COMBAT_HIT, "%s hits %s with %s: %d - %d def = %d", atk_buf, tgt_buf, wpn_name, raw_damage, defense, final_damage); } else if (wpn_name) { rlc_log_message(world, LOG_MSG_COMBAT_HIT, "%s hits %s with %s for %d damage.", atk_buf, tgt_buf, wpn_name, final_damage); } else if (defense > 0) { rlc_log_message(world, LOG_MSG_COMBAT_HIT, "%s hits %s: %d - %d def = %d", atk_buf, tgt_buf, raw_damage, defense, final_damage); } else { rlc_log_message(world, LOG_MSG_COMBAT_HIT, "%s hits %s for %d damage.", atk_buf, tgt_buf, final_damage); } } else { rlc_log_message(world, LOG_MSG_COMBAT_MISS, "%s misses %s.", atk_buf, tgt_buf); } }