SpaceQuakeHulk/Quake/ideas/turn_manager.c

321 lines
10 KiB
C
Raw Normal View History

2026-03-24 10:46:22 -04:00
#include "rlc_turn_manager.h"
#include "rlc_components.h"
#include "rlc_items.h"
#include "rlc_log.h"
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* 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);
}
}