1 /**
2  * @file
3  * @brief
4  */
5 
6 /*
7 Copyright (C) 1997-2001 Id Software, Inc.
8 
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License
11 as published by the Free Software Foundation; either version 2
12 of the License, or (at your option) any later version.
13 
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17 
18 See the GNU General Public License for more details.
19 
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
23 
24 */
25 
26 #include "shared.h"
27 #include "bspfile.h"
28 #include "scriplib.h"
29 #include "../bsp.h"
30 #include <errno.h>
31 
32 /**
33  * @brief Compress the routing data of a map
34  * @sa CMod_DeCompressRouting
35  * @sa CMod_LoadRouting
36  */
CompressRouting(byte * dataStart,byte * destStart,int l)37 byte* CompressRouting (byte* dataStart, byte* destStart, int l)
38 {
39 	int c;
40 	byte val;
41 	byte* data, *dend, *dest_p;
42 
43 	dest_p = destStart;
44 	data = dataStart;
45 	dend = dataStart + l;
46 
47 	while (data < dend) {
48 		byte* count_p;
49 		if (data + 1 < dend && *data == *(data + 1)) {
50 			/* repetitions */
51 			val = *data++;
52 			data++; /* Advance data again.  The first two bytes are identical!!! */
53 			c = 0; /* This means 2 bytes are the same.  Total bytes the same is 2 + c */
54 			/* Loop while the piece of data being looked at equals val */
55 			while (data + 1 < dend && val == *(data)) {
56 				if (c >= SCHAR_MAX) /* must fit into one byte */
57 					break;
58 				data++;
59 				c++;
60 			}
61 			count_p = dest_p;
62 			*dest_p++ = (byte)(c) | 0x80;
63 			*dest_p++ = val;
64 		} else {
65 			/* identities */
66 			count_p = dest_p++;
67 			c = 0;
68 			while ((data + 1 < dend && *data != *(data + 1)) || data == dend - 1) {
69 				if (c >= SCHAR_MAX) /* must fit into one byte */
70 					break;
71 				*dest_p++ = *data++;
72 				c++;
73 			}
74 			*count_p = (byte)c;
75 		}
76 	}
77 
78 	/* terminate compressed data */
79 	*dest_p++ = 0;
80 
81 	return dest_p;
82 }
83 
84 /**
85  * @brief Byte swaps all data in a bsp file.
86  */
SwapBSPFile(void)87 static void SwapBSPFile (void)
88 {
89 	int i, j, k;
90 
91 	/* models */
92 	for (i = 0; i < curTile->nummodels; i++) {
93 		dBspModel_t* d = &curTile->models[i];
94 
95 		d->firstface = LittleLong(d->firstface);
96 		d->numfaces = LittleLong(d->numfaces);
97 		d->headnode = LittleLong(d->headnode);
98 
99 		for (j = 0; j < 3; j++) {
100 			d->mins[j] = LittleFloat(d->mins[j]);
101 			d->maxs[j] = LittleFloat(d->maxs[j]);
102 			d->origin[j] = LittleFloat(d->origin[j]);
103 		}
104 	}
105 
106 	/* vertexes */
107 	for (i = 0; i < curTile->numvertexes; i++) {
108 		dBspVertex_t* vertexes = &curTile->vertexes[i];
109 		for (j = 0; j < 3; j++)
110 			vertexes->point[j] = LittleFloat(vertexes->point[j]);
111 	}
112 
113 	/* planes */
114 	for (i = 0; i < curTile->numplanes; i++) {
115 		dBspPlane_t* plane = &curTile->planes[i];
116 		for (j = 0; j < 3; j++)
117 			plane->normal[j] = LittleFloat(plane->normal[j]);
118 		plane->dist = LittleFloat(plane->dist);
119 		plane->type = LittleLong(plane->type);
120 	}
121 
122 	/* texinfos */
123 	for (i = 0; i < curTile->numtexinfo; i++) {
124 		dBspTexinfo_t* texinfo = &curTile->texinfo[i];
125 		for (j = 0; j < 2; j++)
126 			for (k = 0; k < 4; k++)
127 				texinfo->vecs[j][k] = LittleFloat(texinfo->vecs[j][k]);
128 		texinfo->surfaceFlags = LittleLong(texinfo->surfaceFlags);
129 		texinfo->value = LittleLong(texinfo->value);
130 	}
131 
132 	/* faces */
133 	for (i = 0; i < curTile->numfaces; i++) {
134 		dBspSurface_t* face = &curTile->faces[i];
135 		face->texinfo = LittleShort(face->texinfo);
136 		face->planenum = LittleShort(face->planenum);
137 		face->side = LittleShort(face->side);
138 		for (j = 0; j < LIGHTMAP_MAX; j++)
139 			face->lightofs[j] = LittleLong(face->lightofs[j]);
140 		face->firstedge = LittleLong(face->firstedge);
141 		face->numedges = LittleShort(face->numedges);
142 	}
143 
144 	/* nodes */
145 	for (i = 0; i < curTile->numnodes; i++) {
146 		dBspNode_t* node = &curTile->nodes[i];
147 		/* planenum might be -1 here - special case for pathfinding nodes */
148 		node->planenum = LittleLong(node->planenum);
149 		for (j = 0; j < 3; j++) {
150 			node->mins[j] = LittleShort(node->mins[j]);
151 			node->maxs[j] = LittleShort(node->maxs[j]);
152 		}
153 		node->children[0] = LittleLong(node->children[0]);
154 		node->children[1] = LittleLong(node->children[1]);
155 		node->firstface = LittleShort(node->firstface);
156 		node->numfaces = LittleShort(node->numfaces);
157 	}
158 
159 	/* leafs */
160 	for (i = 0; i < curTile->numleafs; i++) {
161 		dBspLeaf_t* leaf = &curTile->leafs[i];
162 		leaf->contentFlags = LittleLong(leaf->contentFlags);
163 		leaf->area = LittleShort(leaf->area);
164 		for (j = 0; j < 3; j++) {
165 			leaf->mins[j] = LittleShort(leaf->mins[j]);
166 			leaf->maxs[j] = LittleShort(leaf->maxs[j]);
167 		}
168 
169 		leaf->firstleafbrush = LittleShort(leaf->firstleafbrush);
170 		leaf->numleafbrushes = LittleShort(leaf->numleafbrushes);
171 	}
172 
173 	/* leafbrushes */
174 	for (i = 0; i < curTile->numleafbrushes; i++)
175 		curTile->leafbrushes[i] = LittleShort(curTile->leafbrushes[i]);
176 
177 	/* surfedges */
178 	for (i = 0; i < curTile->numsurfedges; i++)
179 		curTile->surfedges[i] = LittleLong(curTile->surfedges[i]);
180 
181 	/* edges */
182 	for (i = 0; i < curTile->numedges; i++) {
183 		dBspEdge_t* edge = &curTile->edges[i];
184 		edge->v[0] = LittleShort(edge->v[0]);
185 		edge->v[1] = LittleShort(edge->v[1]);
186 	}
187 
188 	/* dbrushes */
189 	for (i = 0; i < curTile->numbrushes; i++) {
190 		dBspBrush_t* dbrush = &curTile->dbrushes[i];
191 		dbrush->firstbrushside = LittleLong(dbrush->firstbrushside);
192 		dbrush->numsides = LittleLong(dbrush->numsides);
193 		dbrush->contentFlags = LittleLong(dbrush->contentFlags);
194 	}
195 
196 	/* brushes */
197 	for (i = 0; i < curTile->numbrushes; i++) {
198 		cBspBrush_t* cbrush = &curTile->brushes[i];
199 		cbrush->firstbrushside = LittleLong(cbrush->firstbrushside);
200 		cbrush->numsides = LittleLong(cbrush->numsides);
201 		cbrush->contentFlags = LittleLong(cbrush->contentFlags);
202 	}
203 
204 	/* brushsides */
205 	for (i = 0; i < curTile->numbrushsides; i++) {
206 		dBspBrushSide_t* brushSide = &curTile->brushsides[i];
207 		brushSide->planenum = LittleShort(brushSide->planenum);
208 		brushSide->texinfo = LittleShort(brushSide->texinfo);
209 	}
210 }
211 
CopyLump(const dBspHeader_t * header,int lumpIdx,void * dest,size_t size)212 static uint32_t CopyLump (const dBspHeader_t* header, int lumpIdx, void* dest, size_t size)
213 {
214 	const lump_t* lump = &header->lumps[lumpIdx];
215 	const uint32_t length = lump->filelen;
216 	const uint32_t ofs = lump->fileofs;
217 
218 	if (length == 0)
219 		return 0;
220 	if (length % size)
221 		Sys_Error("LoadBSPFile: odd lump size");
222 
223 	memcpy(dest, (const byte*)header + ofs, length);
224 
225 	return length / size;
226 }
227 
228 /**
229  * @sa WriteBSPFile
230  */
LoadBSPFile(const char * filename)231 dMapTile_t* LoadBSPFile (const char* filename)
232 {
233 	int size;
234 	unsigned int i;
235 	dBspHeader_t* header;
236 
237 	/* Create this shortcut to mapTiles[0] */
238 	curTile = &mapTiles.mapTiles[0];
239 	/* Set the number of tiles to 1. */
240 	mapTiles.numTiles = 1;
241 
242 	/* load the file header */
243 	size = FS_LoadFile(filename, (byte**)&header);
244 	if (size == -1)
245 		Sys_Error("'%s' doesn't exist", filename);
246 
247 	/* swap the header */
248 	BSP_SwapHeader(header, filename);
249 
250 	if (header->ident != IDBSPHEADER)
251 		Sys_Error("%s is not a IBSP file", filename);
252 	if (header->version != BSPVERSION)
253 		Sys_Error("%s is version %i, not %i", filename, header->version, BSPVERSION);
254 
255 	curTile->nummodels = CopyLump(header, LUMP_MODELS, curTile->models, sizeof(dBspModel_t));
256 	curTile->numvertexes = CopyLump(header, LUMP_VERTEXES, curTile->vertexes, sizeof(dBspVertex_t));
257 	curTile->numplanes = CopyLump(header, LUMP_PLANES, curTile->planes, sizeof(dBspPlane_t));
258 	curTile->numleafs = CopyLump(header, LUMP_LEAFS, curTile->leafs, sizeof(dBspLeaf_t));
259 	curTile->numnormals = CopyLump(header, LUMP_NORMALS, curTile->normals, sizeof(dBspNormal_t));
260 	curTile->numnodes = CopyLump(header, LUMP_NODES, curTile->nodes, sizeof(dBspNode_t));
261 	curTile->numtexinfo = CopyLump(header, LUMP_TEXINFO, curTile->texinfo, sizeof(dBspTexinfo_t));
262 	curTile->numfaces = CopyLump(header, LUMP_FACES, curTile->faces, sizeof(dBspSurface_t));
263 	curTile->numleafbrushes = CopyLump(header, LUMP_LEAFBRUSHES, curTile->leafbrushes, sizeof(curTile->leafbrushes[0]));
264 	curTile->numsurfedges = CopyLump(header, LUMP_SURFEDGES, curTile->surfedges, sizeof(curTile->surfedges[0]));
265 	curTile->numedges = CopyLump(header, LUMP_EDGES, curTile->edges, sizeof(dBspEdge_t));
266 	curTile->numbrushes = CopyLump(header, LUMP_BRUSHES, curTile->dbrushes, sizeof(dBspBrush_t));
267 	curTile->numbrushsides = CopyLump(header, LUMP_BRUSHSIDES, curTile->brushsides, sizeof(dBspBrushSide_t));
268 	curTile->routedatasize = CopyLump(header, LUMP_ROUTING, curTile->routedata, 1);
269 	curTile->lightdatasize[LIGHTMAP_NIGHT] = CopyLump(header, LUMP_LIGHTING_NIGHT, curTile->lightdata[LIGHTMAP_NIGHT], 1);
270 	curTile->lightdatasize[LIGHTMAP_DAY] = CopyLump(header, LUMP_LIGHTING_DAY, curTile->lightdata[LIGHTMAP_DAY], 1);
271 	curTile->entdatasize = CopyLump(header, LUMP_ENTITIES, curTile->entdata, 1);
272 
273 	/* Because the tracing functions use cBspBrush_t and not dBspBrush_t,
274 	 * copy data from curTile->dbrushes into curTile->cbrushes */
275 	OBJZERO(curTile->brushes);
276 	for (i = 0; i < curTile->numbrushes; i++) {
277 		dBspBrush_t* dbrush = &curTile->dbrushes[i];
278 		cBspBrush_t* brush = &curTile->brushes[i];
279 		brush->firstbrushside = dbrush->firstbrushside;
280 		brush->numsides = dbrush->numsides;
281 		brush->contentFlags = dbrush->contentFlags;
282 	}
283 
284 	/* everything has been copied out */
285 	FS_FreeFile(header);
286 
287 	/* swap everything */
288 	SwapBSPFile();
289 
290 	return curTile;
291 }
292 
293 /**
294  * @sa WriteBSPFile
295  * @todo Implement this without the ftell stuff - don't write the bsp file twice
296  */
AddLump(qFILE * bspfile,dBspHeader_t * header,int lumpnum,void * data,int len)297 static void AddLump (qFILE *bspfile, dBspHeader_t* header, int lumpnum, void* data, int len)
298 {
299 	lump_t* lump;
300 	long offset;
301 
302 	lump = &header->lumps[lumpnum];
303 
304 	offset = ftell(bspfile->f);
305 	if (offset == -1) {
306 		Sys_Error("Overflow in AddLump for lump %i (%s) %s", lumpnum, bspfile->name, strerror(errno));
307 	}
308 	lump->fileofs = LittleLong(offset);
309 	lump->filelen = LittleLong(len);
310 	/* 4 byte align */
311 	FS_Write(data, (len + 3) &~ 3, bspfile);
312 }
313 
314 /**
315  * @brief Swaps the bsp file in place, so it should not be referenced again
316  * @sa LoadBSPFile
317  */
WriteBSPFile(const char * filename)318 long WriteBSPFile (const char* filename)
319 {
320 	dBspHeader_t outheader;
321 	long size;
322 
323 	OBJZERO(outheader);
324 
325 	SwapBSPFile();
326 
327 	outheader.ident = LittleLong(IDBSPHEADER);
328 	outheader.version = LittleLong(BSPVERSION);
329 
330 	ScopedFile bspfile;
331 	FS_OpenFile(filename, &bspfile, FILE_WRITE);
332 	if (!bspfile)
333 		Sys_Error("Could not write bsp file");
334 	FS_Write(&outheader, sizeof(outheader), &bspfile);	/* overwritten later */
335 
336 	AddLump(&bspfile, &outheader, LUMP_PLANES, curTile->planes, curTile->numplanes * sizeof(dBspPlane_t));
337 	AddLump(&bspfile, &outheader, LUMP_LEAFS, curTile->leafs, curTile->numleafs * sizeof(dBspLeaf_t));
338 	AddLump(&bspfile, &outheader, LUMP_VERTEXES, curTile->vertexes, curTile->numvertexes * sizeof(dBspVertex_t));
339 	AddLump(&bspfile, &outheader, LUMP_NORMALS, curTile->normals, curTile->numnormals * sizeof(dBspNormal_t));
340 	AddLump(&bspfile, &outheader, LUMP_NODES, curTile->nodes, curTile->numnodes * sizeof(dBspNode_t));
341 	AddLump(&bspfile, &outheader, LUMP_TEXINFO, curTile->texinfo, curTile->numtexinfo * sizeof(dBspTexinfo_t));
342 	AddLump(&bspfile, &outheader, LUMP_FACES, curTile->faces, curTile->numfaces * sizeof(dBspSurface_t));
343 	AddLump(&bspfile, &outheader, LUMP_BRUSHES, curTile->dbrushes, curTile->numbrushes * sizeof(dBspBrush_t));
344 	AddLump(&bspfile, &outheader, LUMP_BRUSHSIDES, curTile->brushsides, curTile->numbrushsides * sizeof(dBspBrushSide_t));
345 	AddLump(&bspfile, &outheader, LUMP_LEAFBRUSHES, curTile->leafbrushes, curTile->numleafbrushes * sizeof(curTile->leafbrushes[0]));
346 	AddLump(&bspfile, &outheader, LUMP_SURFEDGES, curTile->surfedges, curTile->numsurfedges * sizeof(curTile->surfedges[0]));
347 	AddLump(&bspfile, &outheader, LUMP_EDGES, curTile->edges, curTile->numedges * sizeof(dBspEdge_t));
348 	AddLump(&bspfile, &outheader, LUMP_MODELS, curTile->models, curTile->nummodels * sizeof(dBspModel_t));
349 	AddLump(&bspfile, &outheader, LUMP_LIGHTING_NIGHT, curTile->lightdata[0], curTile->lightdatasize[0]);
350 	AddLump(&bspfile, &outheader, LUMP_LIGHTING_DAY, curTile->lightdata[1], curTile->lightdatasize[1]);
351 	AddLump(&bspfile, &outheader, LUMP_ROUTING, curTile->routedata, curTile->routedatasize);
352 	AddLump(&bspfile, &outheader, LUMP_ENTITIES, curTile->entdata, curTile->entdatasize);
353 	size = ftell(bspfile.getFile());
354 
355 	fseek(bspfile.getFile(), 0L, SEEK_SET);
356 	FS_Write(&outheader, sizeof(outheader), &bspfile);
357 
358 	SwapBSPFile();
359 
360 	return size;
361 }
362 
363 /**
364  * @brief Dumps info about current file
365  */
PrintBSPFileSizes(void)366 void PrintBSPFileSizes (void)
367 {
368 	if (!num_entities)
369 		ParseEntities();
370 
371 	Com_Printf("amount type        size in bytes\n");
372 	Com_Printf("================================\n");
373 	Com_Printf("%5i models            %7i\n", curTile->nummodels, (int)(curTile->nummodels * sizeof(cBspModel_t)));
374 	Com_Printf("%5i brushes           %7i\n", curTile->numbrushes, (int)(curTile->numbrushes * sizeof(dBspBrush_t)));
375 	Com_Printf("%5i brushsides        %7i\n", curTile->numbrushsides, (int)(curTile->numbrushsides * sizeof(dBspBrushSide_t)));
376 	Com_Printf("%5i planes            %7i\n", curTile->numplanes, (int)(curTile->numplanes * sizeof(dBspPlane_t)));
377 	Com_Printf("%5i texinfo           %7i\n", curTile->numtexinfo, (int)(curTile->numtexinfo * sizeof(dBspTexinfo_t)));
378 	Com_Printf("%5i entdata           %7i\n", num_entities, curTile->entdatasize);
379 
380 	Com_Printf("\n");
381 
382 	Com_Printf("%5i normales          %7i\n", curTile->numnormals, (int)(curTile->numnormals * sizeof(dBspNormal_t)));
383 	Com_Printf("%5i vertexes          %7i\n", curTile->numvertexes, (int)(curTile->numvertexes * sizeof(dBspVertex_t)));
384 	Com_Printf("%5i nodes             %7i\n", curTile->numnodes, (int)(curTile->numnodes * sizeof(dBspNode_t)));
385 	Com_Printf("%5i faces             %7i\n", curTile->numfaces, (int)(curTile->numfaces * sizeof(dBspSurface_t)));
386 	Com_Printf("%5i leafs             %7i\n", curTile->numleafs, (int)(curTile->numleafs * sizeof(dBspLeaf_t)));
387 	Com_Printf("%5i leafbrushes       %7i\n", curTile->numleafbrushes, (int)(curTile->numleafbrushes * sizeof(curTile->leafbrushes[0])));
388 	Com_Printf("%5i surfedges         %7i\n", curTile->numsurfedges, (int)(curTile->numsurfedges * sizeof(curTile->surfedges[0])));
389 	Com_Printf("%5i edges             %7i\n", curTile->numedges, (int)(curTile->numedges * sizeof(dBspEdge_t)));
390 	Com_Printf("night lightdata         %7i\n", curTile->lightdatasize[0]);
391 	Com_Printf("  day lightdata         %7i\n", curTile->lightdatasize[1]);
392 	Com_Printf("      routedata         %7i\n", curTile->routedatasize);
393 }
394 
395 
396 int num_entities;
397 entity_t entities[MAX_MAP_ENTITIES];
398 
399 /**
400  * @brief Removes trailing whitespaces from the given string
401  * @param[in,out] str The string to clean up
402  * @note Whitespaces are converted to \\0
403  */
StripTrailingWhitespaces(char * str)404 static void StripTrailingWhitespaces (char* str)
405 {
406 	char* s;
407 
408 	s = str + strlen(str) - 1;
409 	while (s >= str && *s <= ' ') {
410 		*s = '\0';
411 		s--;
412 	}
413 }
414 
IsInvalidEntityToken(const char * token)415 static inline bool IsInvalidEntityToken (const char* token)
416 {
417 	return Q_streq(token, "}") || Q_streq(token, "{");
418 }
419 
AddEpair(const char * key,const char * value,int entNum)420 epair_t* AddEpair (const char* key, const char* value, int entNum)
421 {
422 	epair_t	*e = Mem_AllocType(epair_t);
423 
424 	if (IsInvalidEntityToken(value) || IsInvalidEntityToken(key))
425 		Sys_Error("Invalid entity %i found with key '%s' and value '%s'", entNum, key, value);
426 
427 	if (strlen(key) >= MAX_KEY - 1)
428 		Sys_Error("ParseEpar: token too long");
429 	e->key = key;
430 	if (strlen(value) >= MAX_VALUE - 1)
431 		Sys_Error("ParseEpar: token too long");
432 	e->value = value;
433 
434 	return e;
435 }
436 
437 /**
438  * @brief Parses one key and value for an entity from the current tokens
439  * @sa parsedToken
440  * @sa GetToken
441  * @sa ParseEntity
442  * @sa ParseMapEntity
443  */
ParseEpair(int entNum)444 epair_t* ParseEpair (int entNum)
445 {
446 	StripTrailingWhitespaces(parsedToken);
447 	const char* key = Mem_StrDup(parsedToken);
448 	GetToken();
449 	StripTrailingWhitespaces(parsedToken);
450 	const char* value = Mem_StrDup(parsedToken);
451 	return AddEpair(key, value, entNum);
452 }
453 
454 /**
455  * @sa ParseEntities
456  */
ParseEntity(void)457 static entity_t* ParseEntity (void)
458 {
459 	entity_t* mapent;
460 
461 	if (Q_strnull(GetToken()))
462 		return nullptr;
463 
464 	if (parsedToken[0] != '{')
465 		Sys_Error("ParseEntity: { not found");
466 
467 	if (num_entities >= MAX_MAP_ENTITIES)
468 		Sys_Error("num_entities >= MAX_MAP_ENTITIES (%i)", num_entities);
469 
470 	mapent = &entities[num_entities];
471 	num_entities++;
472 
473 	do {
474 		if (Q_strnull(GetToken()))
475 			Sys_Error("ParseEntity: EOF without closing brace");
476 		if (*parsedToken == '}') {
477 			break;
478 		} else {
479 			epair_t* e = ParseEpair(num_entities);
480 			e->next = mapent->epairs;
481 			mapent->epairs = e;
482 		}
483 	} while (1);
484 
485 	return mapent;
486 }
487 
488 /**
489  * @brief Parses the curTile->entdata string into entities
490  * @sa UnparseEntities
491  * @sa ParseEntity
492  */
ParseEntities(void)493 void ParseEntities (void)
494 {
495 	num_entities = 0;
496 	ParseFromMemory(curTile->entdata, curTile->entdatasize);
497 
498 	while (ParseEntity() != nullptr) {
499 	}
500 }
501 
502 /**
503  * @brief Generates the curTile->entdata string from all the entities
504  * @sa ParseEntities
505  */
UnparseEntities(void)506 const char* UnparseEntities (void)
507 {
508 	char key[1024];
509 	char value[1024];
510 
511 	curTile->entdata[0] = '\0';
512 
513 	for (int i = 0; i < num_entities; i++) {
514 		const epair_t* ep = entities[i].epairs;
515 		if (!ep)
516 			continue;	/* ent got removed */
517 
518 		Q_strcat(curTile->entdata, sizeof(curTile->entdata), "{\n");
519 
520 		for (ep = entities[i].epairs; ep; ep = ep->next) {
521 			Q_strncpyz(key, ep->key, sizeof(key));
522 			StripTrailingWhitespaces(key);
523 			Q_strncpyz(value, ep->value, sizeof(value));
524 			StripTrailingWhitespaces(value);
525 
526 			if (IsInvalidEntityToken(value) || IsInvalidEntityToken(key))
527 				Sys_Error("Invalid entity %i found with key '%s' and value '%s'", i, key, value);
528 			Q_strcat(curTile->entdata, sizeof(curTile->entdata), "\"%s\" \"%s\"\n", key, value);
529 		}
530 		Q_strcat(curTile->entdata, sizeof(curTile->entdata), "}\n");
531 	}
532 	curTile->entdatasize = strlen(curTile->entdata);
533 
534 	return curTile->entdata;
535 }
536 
SetKeyValue(entity_t * ent,const char * key,const char * value)537 void SetKeyValue (entity_t* ent, const char* key, const char* value)
538 {
539 	epair_t* ep;
540 
541 	for (ep = ent->epairs; ep; ep = ep->next)
542 		if (Q_streq(ep->key, key)) {
543 			ep->value = Mem_StrDup(value);
544 			return;
545 		}
546 	ep = Mem_AllocType(epair_t);
547 	ep->next = ent->epairs;
548 	ent->epairs = ep;
549 	ep->key = Mem_StrDup(key);
550 	ep->value = Mem_StrDup(value);
551 }
552 
ValueForKey(const entity_t * ent,const char * key)553 const char* ValueForKey (const entity_t* ent, const char* key)
554 {
555 	const epair_t* ep;
556 
557 	for (ep = ent->epairs; ep; ep = ep->next)
558 		if (Q_streq(ep->key, key))
559 			return ep->value;
560 	return "";
561 }
562 
FloatForKey(const entity_t * ent,const char * key)563 vec_t FloatForKey (const entity_t* ent, const char* key)
564 {
565 	const char* k;
566 
567 	k = ValueForKey(ent, key);
568 	return atof(k);
569 }
570 
571 /**
572  * @brief Converts a string into a @c vec3_t
573  */
GetVectorFromString(const char * value,vec3_t vec)574 void GetVectorFromString (const char* value, vec3_t vec)
575 {
576 	if (value[0] != '\0') {
577 		double v1, v2, v3;
578 
579 		/* scanf into doubles, then assign, so it is vec_t size independent */
580 		v1 = v2 = v3 = 0;
581 		if (sscanf(value, "%lf %lf %lf", &v1, &v2, &v3) != 3)
582 			Sys_Error("invalid vector statement given: '%s'", value);
583 		VectorSet(vec, v1, v2, v3);
584 	} else
585 		VectorClear(vec);
586 }
587 
588 /**
589  * @brief Converts the value of a entity parameter into a @c vec3_t
590  */
GetVectorForKey(const entity_t * ent,const char * key,vec3_t vec)591 void GetVectorForKey (const entity_t* ent, const char* key, vec3_t vec)
592 {
593 	const char* k = ValueForKey(ent, key);
594 	GetVectorFromString(k, vec);
595 }
596