diff --git a/src/botlib/aas/aas_local.h b/src/botlib/aas/aas_local.h index 7fcfc07..0c0df76 100644 --- a/src/botlib/aas/aas_local.h +++ b/src/botlib/aas/aas_local.h @@ -34,6 +34,12 @@ typedef struct aas_node_s int children[2]; /* <0 means leaf/area, 0 means solid */ } aas_node_t; +typedef struct aas_campspot_s +{ + vec3_t origin; + struct aas_campspot_s *next; +} aas_campspot_t; + /* * Travel type definitions reconstructed from the Quake III / Gladiator * binaries. The values mirror the original enum encoded in the reachability @@ -227,6 +233,9 @@ typedef struct aas_world_s int maxEntities; aas_entity_t *entities; /* base pointer from data_100669a0 */ + int numCampSpots; + aas_campspot_t *campSpots; + size_t areaEntityListCount; /* number of heads in areaEntityLists */ aas_link_t **areaEntityLists; /* entities linked per area */ diff --git a/src/botlib/aas/aas_map.c b/src/botlib/aas/aas_map.c index 3021f3b..0c98a12 100644 --- a/src/botlib/aas/aas_map.c +++ b/src/botlib/aas/aas_map.c @@ -26,6 +26,8 @@ static size_t AAS_AreaBitWordCount(void); static void AAS_ClampMinsMaxs(vec3_t mins, vec3_t maxs); static void AAS_ClearWorld(void); static void AAS_ParseEntityLump(const char *data, size_t length); +static qboolean AAS_ParseVectorValue(const char *value, vec3_t outValue); +static void AAS_RegisterCampSpot(const aas_parsed_entity_t *entity); /* * Global AAS world state. The original DLL zeroed the data_100667e0 block @@ -105,6 +107,8 @@ typedef struct aas_parsed_entity_s qboolean hasClassname; char model[64]; qboolean hasModel; + vec3_t origin; + qboolean hasOrigin; float lip; qboolean hasLip; float height; @@ -193,6 +197,61 @@ static qboolean AAS_ParseIntValue(const char *value, int *outValue) return qtrue; } +/* +============= +AAS_ParseVectorValue + +Parses a whitespace-delimited vec3 from the entity lump. +============= +*/ +static qboolean AAS_ParseVectorValue(const char *value, vec3_t outValue) +{ + if (value == NULL || outValue == NULL) + { + return qfalse; + } + + const char *cursor = value; + float parsed[3] = {0.0f, 0.0f, 0.0f}; + + for (int axis = 0; axis < 3; ++axis) + { + while (*cursor != '\0' && isspace((unsigned char)*cursor)) + { + ++cursor; + } + + if (*cursor == '\0') + { + return qfalse; + } + + errno = 0; + char *endPtr = NULL; + float val = strtof(cursor, &endPtr); + if (endPtr == cursor || errno == ERANGE) + { + return qfalse; + } + + parsed[axis] = val; + cursor = endPtr; + } + + while (*cursor != '\0' && isspace((unsigned char)*cursor)) + { + ++cursor; + } + + if (*cursor != '\0') + { + return qfalse; + } + + VectorSet(outValue, parsed[0], parsed[1], parsed[2]); + return qtrue; +} + static void AAS_CopyStringField(char *destination, size_t destinationSize, const char *source, @@ -473,6 +532,21 @@ static void AAS_ParseEntityKeyValue(aas_parsed_entity_t *entity, const char *key AAS_CopyStringField(entity->model, sizeof(entity->model), value, key); entity->hasModel = qtrue; } + else if (strcmp(key, "origin") == 0) + { + vec3_t parsed; + if (AAS_ParseVectorValue(value, parsed)) + { + VectorCopy(parsed, entity->origin); + entity->hasOrigin = qtrue; + } + else + { + BotLib_Print(PRT_WARNING, + "AAS_ParseEntityLump: failed to parse origin value '%s'\n", + value); + } + } else if (strcmp(key, "lip") == 0) { float parsed = 0.0f; @@ -609,6 +683,39 @@ static void AAS_RegisterMoverEntity(const aas_parsed_entity_t *entity) } } +/* +============= +AAS_RegisterCampSpot + +Stores info_camp origins parsed from the BSP entity lump. +============= +*/ +static void AAS_RegisterCampSpot(const aas_parsed_entity_t *entity) +{ + if (entity == NULL || !entity->hasClassname || !entity->hasOrigin) + { + return; + } + + if (strcmp(entity->classname, "info_camp") != 0) + { + return; + } + + aas_campspot_t *spot = (aas_campspot_t *)calloc(1, sizeof(*spot)); + if (spot == NULL) + { + BotLib_Print(PRT_WARNING, + "AAS_ParseEntityLump: out of memory registering camp spot\n"); + return; + } + + VectorCopy(entity->origin, spot->origin); + spot->next = aasworld.campSpots; + aasworld.campSpots = spot; + aasworld.numCampSpots++; +} + static void AAS_ParseEntityLump(const char *data, size_t length) { if (data == NULL || length == 0U) @@ -718,6 +825,7 @@ static void AAS_ParseEntityLump(const char *data, size_t length) if (!malformed) { AAS_RegisterMoverEntity(&entity); + AAS_RegisterCampSpot(&entity); } } } @@ -1097,6 +1205,19 @@ static void AAS_ClearWorld(void) AAS_FreeAllRoutingCaches(); AAS_ClearReachabilityData(); + if (aasworld.campSpots != NULL) + { + aas_campspot_t *spot = aasworld.campSpots; + while (spot != NULL) + { + aas_campspot_t *next = spot->next; + free(spot); + spot = next; + } + aasworld.campSpots = NULL; + aasworld.numCampSpots = 0; + } + if (aasworld.entities != NULL) { for (int i = 0; i < aasworld.maxEntities; ++i) diff --git a/src/botlib/ai_goal/bot_goal.c b/src/botlib/ai_goal/bot_goal.c index 259f194..59ffe4c 100644 --- a/src/botlib/ai_goal/bot_goal.c +++ b/src/botlib/ai_goal/bot_goal.c @@ -1022,6 +1022,48 @@ int BotTouchingGoal(const vec3_t origin, const bot_goal_t *goal) return 1; } +/* +============= +BotGetNextCampSpotGoal + +Iterates the map camp spots and returns the next goal entry. +============= +*/ +int BotGetNextCampSpotGoal(int num, bot_goal_t *goal) +{ + if (goal == NULL) + { + return 0; + } + + if (num < 0) + { + num = 0; + } + + int index = num; + const aas_campspot_t *spot = aasworld.campSpots; + while (spot != NULL) + { + if (--index < 0) + { + vec3_t mins = {-8.0f, -8.0f, -8.0f}; + vec3_t maxs = {8.0f, 8.0f, 8.0f}; + + goal->areanum = BotGoal_PointAreaNum(spot->origin); + VectorCopy(spot->origin, goal->origin); + goal->entitynum = 0; + VectorCopy(mins, goal->mins); + VectorCopy(maxs, goal->maxs); + return num + 1; + } + + spot = spot->next; + } + + return 0; +} + void BotGoalName(int number, char *name, int size) { if (name == NULL || size <= 0) diff --git a/src/botlib/ai_goal/bot_goal.h b/src/botlib/ai_goal/bot_goal.h index eadd027..a7031eb 100644 --- a/src/botlib/ai_goal/bot_goal.h +++ b/src/botlib/ai_goal/bot_goal.h @@ -112,6 +112,7 @@ float BotGoal_EvaluateStackGoal(int handle, int *travel_time); int BotTouchingGoal(const vec3_t origin, const bot_goal_t *goal); +int BotGetNextCampSpotGoal(int num, bot_goal_t *goal); void BotGoalName(int number, char *name, int size); void BotDumpAvoidGoals(int handle); void BotDumpGoalStack(int handle); @@ -128,4 +129,3 @@ const bot_goalstate_t *BotGoalStatePeek(int handle); #ifdef __cplusplus } #endif -