Skip to content

Commit a4b643c

Browse files
committed
BSPC: load waypoints
1 parent badbdcb commit a4b643c

8 files changed

Lines changed: 1525 additions & 5 deletions

File tree

code/tools/bspc/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,10 @@ set(BSPC_SRCS
2929
map_q3.c
3030
nodraw.c
3131
portals.c
32+
rch_load.c
3233
textures.c
3334
tree.c
35+
yxml.c
3436
)
3537

3638
set(BSPC_BOTLIB_SRCS

code/tools/bspc/be_aas_bspc.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
3333
#include "botlib/be_aas_def.h"
3434
#include "qcommon/cm_public.h"
3535
#include "be_aas_bspc.h"
36+
#include "rch_load.h"
3637

3738
#include <stdarg.h>
3839

@@ -186,7 +187,7 @@ void AAS_InitBotImport(void) {
186187
botimport.BSPModelMinsMaxsOrigin = BotImport_BSPModelMinsMaxsOrigin;
187188
}
188189

189-
void AAS_CalcReachAndClusters(struct quakefile_s *qf) {
190+
void AAS_CalcReachAndClusters(struct quakefile_s *qf, const char *aasfilename) {
190191
float time;
191192

192193
Log_Print("loading collision map...\n");
@@ -217,6 +218,8 @@ void AAS_CalcReachAndClusters(struct quakefile_s *qf) {
217218
time = 0;
218219
while (AAS_ContinueInitReachability(time))
219220
time++;
221+
// load hand-crafted reachabilities from .rch XML file
222+
RCH_LoadReachFile(aasfilename);
220223
// calculate clusters
221224
AAS_InitClustering();
222225
}

code/tools/bspc/be_aas_bspc.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
2222

2323
#include <stdio.h>
2424

25-
void AAS_CalcReachAndClusters(struct quakefile_s *qf);
25+
void AAS_CalcReachAndClusters(struct quakefile_s *qf, const char *aasfilename);
2626
void AAS_InitBotImport(void);
2727
int Sys_MilliSeconds(void);
2828
FILE *Sys_FOpen(const char *ospath, const char *mode);

code/tools/bspc/bspc.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,7 @@ int main(int argc, char **argv) {
496496
AAS_Create(filename);
497497
// if it's a Quake3 map calculate the reachabilities and clusters
498498
if (loadedmaptype == MAPTYPE_QUAKE3)
499-
AAS_CalcReachAndClusters(qf);
499+
AAS_CalcReachAndClusters(qf, filename);
500500
//
501501
if (optimize)
502502
AAS_Optimize();
@@ -537,7 +537,7 @@ int main(int argc, char **argv) {
537537
}
538538
// if it's a Quake3 map calculate the reachabilities and clusters
539539
if (loadedmaptype == MAPTYPE_QUAKE3) {
540-
AAS_CalcReachAndClusters(qf);
540+
AAS_CalcReachAndClusters(qf, filename);
541541
}
542542
//
543543
if (optimize)
@@ -583,7 +583,7 @@ int main(int argc, char **argv) {
583583
AAS_Create(filename);
584584
// if it's a Quake3 map calculate the reachabilities and clusters
585585
if (loadedmaptype == MAPTYPE_QUAKE3)
586-
AAS_CalcReachAndClusters(qf);
586+
AAS_CalcReachAndClusters(qf, filename);
587587
}
588588
//
589589
if (optimize)

code/tools/bspc/rch_load.c

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
/*
2+
===========================================================================
3+
Copyright (C) 2007-2026 PadWorld Entertainment
4+
5+
This file is part of World of PADMAN source code.
6+
7+
World of PADMAN source code is free software; you can redistribute it
8+
and/or modify it under the terms of the GNU General Public License as
9+
published by the Free Software Foundation; either version 2 of the License,
10+
or (at your option) any later version.
11+
12+
World of PADMAN source code is distributed in the hope that it will be
13+
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
GNU General Public License for more details.
16+
17+
You should have received a copy of the GNU General Public License
18+
along with World of PADMAN source code; if not, write to the Free Software
19+
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20+
===========================================================================
21+
*/
22+
23+
// rch_load.c - Loads .rch (reachability) XML files to provide hand-crafted
24+
// reachability links (e.g. spray teleporters) that the automatic BSP-to-AAS
25+
// conversion cannot detect. Uses yxml parser (https://dev.yorhel.nl/yxml).
26+
//
27+
// The .rch XML format uses nested elements with text content:
28+
// <reachability>
29+
// <areanum>5</areanum>
30+
// <facenum>0</facenum>
31+
// <edgenum>0</edgenum>
32+
// <startX>100.0</startX>
33+
// <startY>200.0</startY>
34+
// <startZ>300.0</startZ>
35+
// <endX>400.0</endX>
36+
// <endY>500.0</endY>
37+
// <endZ>600.0</endZ>
38+
// <traveltype>18</traveltype>
39+
// <traveltime>100</traveltime>
40+
// </reachability>
41+
42+
#include "qcommon/q_shared.h"
43+
#include "l_log.h"
44+
#include "botlib/l_memory.h"
45+
#include "botlib/aasfile.h"
46+
#include "botlib/botlib.h"
47+
#include "aas_store.h"
48+
#include "rch_load.h"
49+
#include "yxml.h"
50+
#include <stdio.h>
51+
#include <stdlib.h>
52+
#include <string.h>
53+
#include <math.h>
54+
55+
#define RCH_READ_BUF_SIZE 8192
56+
#define RCH_YXML_BUF_SIZE 4096
57+
#define RCH_CONTENT_BUF_SIZE 64
58+
59+
// traveltype for jumppad (TRAVEL_JUMPPAD in be_aas_reach.c)
60+
#define TRAVEL_JUMPPAD 0x12
61+
62+
extern int AAS_PointAreaNum(vec3_t point);
63+
extern int AAS_NextBSPEntity(int ent);
64+
extern int AAS_ValueForBSPEpairKey(int ent, char *key, char *value, int size);
65+
66+
// Temporary linked-list node for parsed reachabilities
67+
typedef struct rch_reach_s {
68+
aas_reachability_t reach;
69+
struct rch_reach_s *next;
70+
} rch_reach_t;
71+
72+
// Parser state
73+
typedef struct {
74+
// Content accumulation for current sub-element
75+
char content[RCH_CONTENT_BUF_SIZE];
76+
char *contentptr;
77+
// Current element name (sub-element of <reachability>)
78+
char curelem[RCH_CONTENT_BUF_SIZE];
79+
// Whether we're inside a <reachability> element
80+
qboolean in_reach;
81+
// Current reachability being built
82+
rch_reach_t *current;
83+
// Linked list of completed reachabilities
84+
rch_reach_t *head;
85+
int count;
86+
// Nesting depth
87+
int depth;
88+
} rch_state_t;
89+
90+
static void RCH_SetFieldValue(rch_reach_t *r, const char *elem, const char *val) {
91+
if (!strcmp(elem, "areanum"))
92+
r->reach.areanum = atoi(val);
93+
else if (!strcmp(elem, "edgenum"))
94+
r->reach.edgenum = atoi(val);
95+
else if (!strcmp(elem, "facenum"))
96+
r->reach.facenum = atoi(val);
97+
else if (!strcmp(elem, "startX"))
98+
r->reach.start[0] = (float)atof(val);
99+
else if (!strcmp(elem, "startY"))
100+
r->reach.start[1] = (float)atof(val);
101+
else if (!strcmp(elem, "startZ"))
102+
r->reach.start[2] = (float)atof(val);
103+
else if (!strcmp(elem, "endX"))
104+
r->reach.end[0] = (float)atof(val);
105+
else if (!strcmp(elem, "endY"))
106+
r->reach.end[1] = (float)atof(val);
107+
else if (!strcmp(elem, "endZ"))
108+
r->reach.end[2] = (float)atof(val);
109+
else if (!strcmp(elem, "traveltime"))
110+
r->reach.traveltime = (unsigned short)atoi(val);
111+
else if (!strcmp(elem, "traveltype"))
112+
r->reach.traveltype = atoi(val);
113+
}
114+
115+
static void RCH_FinalizeReach(rch_state_t *state) {
116+
rch_reach_t *r = state->current;
117+
if (!r)
118+
return;
119+
120+
// Resolve areanum from start position (overrides XML areanum)
121+
r->reach.areanum = AAS_PointAreaNum(r->reach.start);
122+
123+
// Debug print
124+
Log_Print(" reach: area=%d face=%d edge=%d start=(%.1f %.1f %.1f) end=(%.1f %.1f %.1f) type=%d time=%d\n",
125+
r->reach.areanum, r->reach.facenum, r->reach.edgenum,
126+
r->reach.start[0], r->reach.start[1], r->reach.start[2],
127+
r->reach.end[0], r->reach.end[1], r->reach.end[2],
128+
r->reach.traveltype, r->reach.traveltime);
129+
130+
// Link into list
131+
r->next = state->head;
132+
state->head = r;
133+
state->count++;
134+
state->current = NULL;
135+
}
136+
137+
static void RCH_StoreReachabilities(rch_state_t *state) {
138+
rch_reach_t *r, *next;
139+
int stored = 0;
140+
141+
if (!state->count) {
142+
Log_Print("no reachabilities loaded from .rch file\n");
143+
return;
144+
}
145+
146+
Log_Print("storing %d reachabilities from .rch file...\n", state->count);
147+
148+
for (r = state->head; r; r = next) {
149+
next = r->next;
150+
151+
int destarea = AAS_PointAreaNum(r->reach.end);
152+
if (destarea <= 0 || destarea >= aasworld.numareas) {
153+
Log_Print("WARNING: reach end point not in valid area, skipping\n");
154+
FreeMemory(r);
155+
continue;
156+
}
157+
158+
if (aasworld.reachabilitysize >= AAS_MAX_REACHABILITYSIZE) {
159+
Log_Print("WARNING: AAS_MAX_REACHABILITYSIZE reached\n");
160+
FreeMemory(r);
161+
continue;
162+
}
163+
164+
aas_reachability_t *dest = &aasworld.reachability[aasworld.reachabilitysize];
165+
*dest = r->reach;
166+
167+
// Link into the destination area's reachability chain
168+
aasworld.areasettings[destarea].numreachableareas++;
169+
if (!aasworld.areasettings[destarea].firstreachablearea)
170+
aasworld.areasettings[destarea].firstreachablearea = aasworld.reachabilitysize;
171+
172+
aasworld.reachabilitysize++;
173+
stored++;
174+
FreeMemory(r);
175+
}
176+
177+
state->head = NULL;
178+
Log_Print("stored %d reachabilities from .rch file\n", stored);
179+
}
180+
181+
qboolean RCH_LoadReachFile(const char *aasfilename) {
182+
char rchpath[MAX_PATH];
183+
FILE *fp;
184+
yxml_t x;
185+
char yxmlbuf[RCH_YXML_BUF_SIZE];
186+
char readbuf[RCH_READ_BUF_SIZE];
187+
rch_state_t state;
188+
size_t nread;
189+
int i;
190+
yxml_ret_t r;
191+
192+
// Construct .rch path from .aas path
193+
Q_strncpyz(rchpath, aasfilename, sizeof(rchpath));
194+
// Replace .aas extension with .rch
195+
i = strlen(rchpath);
196+
while (i > 0 && rchpath[i - 1] != '.')
197+
i--;
198+
if (i > 0)
199+
rchpath[i - 1] = '\0';
200+
else
201+
rchpath[0] = '\0';
202+
strcat(rchpath, ".rch");
203+
204+
fp = fopen(rchpath, "rb");
205+
if (!fp) {
206+
Log_Print("no .rch file found (%s)\n", rchpath);
207+
return qfalse;
208+
}
209+
210+
Log_Print("loading reachability file %s...\n", rchpath);
211+
212+
memset(&state, 0, sizeof(state));
213+
yxml_init(&x, yxmlbuf, sizeof(yxmlbuf));
214+
215+
while ((nread = fread(readbuf, 1, sizeof(readbuf), fp)) > 0) {
216+
for (i = 0; i < (int)nread; i++) {
217+
r = yxml_parse(&x, readbuf[i]);
218+
if (r < 0) {
219+
Log_Print("ERROR: XML parse error in %s at line %u\n", rchpath, x.line);
220+
fclose(fp);
221+
return qfalse;
222+
}
223+
224+
switch (r) {
225+
case YXML_ELEMSTART:
226+
state.depth++;
227+
if (!strcmp(x.elem, "reachability")) {
228+
state.in_reach = qtrue;
229+
state.current = (rch_reach_t *)GetMemory(sizeof(rch_reach_t));
230+
memset(state.current, 0, sizeof(rch_reach_t));
231+
} else if (state.in_reach && state.depth == 2) {
232+
// Sub-element of <reachability>
233+
Q_strncpyz(state.curelem, x.elem, sizeof(state.curelem));
234+
state.contentptr = state.content;
235+
state.content[0] = '\0';
236+
}
237+
break;
238+
239+
case YXML_ELEMEND:
240+
if (state.in_reach && state.depth == 2 && state.current) {
241+
// Closing a sub-element: apply the accumulated content
242+
RCH_SetFieldValue(state.current, state.curelem, state.content);
243+
state.curelem[0] = '\0';
244+
} else if (state.in_reach && state.depth == 1) {
245+
// Closing </reachability>
246+
RCH_FinalizeReach(&state);
247+
state.in_reach = qfalse;
248+
}
249+
state.depth--;
250+
break;
251+
252+
case YXML_CONTENT:
253+
// Accumulate text content for sub-elements
254+
if (state.in_reach && state.depth == 2 && state.contentptr) {
255+
char *s = x.data;
256+
while (*s && state.contentptr < state.content + sizeof(state.content) - 1)
257+
*(state.contentptr++) = *(s++);
258+
*state.contentptr = '\0';
259+
}
260+
break;
261+
262+
default:
263+
break;
264+
}
265+
}
266+
}
267+
268+
fclose(fp);
269+
270+
if (yxml_eof(&x) < 0) {
271+
Log_Print("WARNING: .rch file %s has incomplete XML\n", rchpath);
272+
}
273+
274+
RCH_StoreReachabilities(&state);
275+
Log_Print("reachability file loaded: %d entries\n", state.count);
276+
return qtrue;
277+
}

code/tools/bspc/rch_load.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
===========================================================================
3+
Copyright (C) 2007-2026 PadWorld Entertainment
4+
===========================================================================
5+
*/
6+
7+
#ifndef RCH_LOAD_H
8+
#define RCH_LOAD_H
9+
10+
#include "qcommon/q_shared.h"
11+
12+
// Load a .rch reachability XML file corresponding to the given .aas filename.
13+
// Returns qtrue if the file was found and parsed successfully.
14+
qboolean RCH_LoadReachFile(const char *aasfilename);
15+
16+
#endif

0 commit comments

Comments
 (0)