1 /* bzflag
2 * Copyright (c) 1993-2021 Tim Riker
3 *
4 * This package is free software; you can redistribute it and/or
5 * modify it under the terms of the license found in the file
6 * named COPYING that should have accompanied this file.
7 *
8 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
9 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
10 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
11 */
12
13 // interface header
14 #include "BZWReader.h"
15
16 // implementation-specific system headers
17 #include <fstream>
18 #include <sstream>
19 #include <ctype.h>
20
21 // implementation-specific bzflag headers
22 #include "BZDBCache.h"
23
24 // implementation-specific bzfs-specific headers
25 #include "TeamBases.h"
26 #include "WorldFileObject.h"
27 #include "CustomGroup.h"
28 #include "CustomBox.h"
29 #include "CustomPyramid.h"
30 #include "CustomGate.h"
31 #include "CustomLink.h"
32 #include "CustomBase.h"
33 #include "CustomWeapon.h"
34 #include "CustomWorld.h"
35 #include "CustomZone.h"
36 #include "CustomTetra.h"
37 #include "CustomMesh.h"
38 #include "CustomArc.h"
39 #include "CustomCone.h"
40 #include "CustomSphere.h"
41 #include "CustomWaterLevel.h"
42 #include "CustomDynamicColor.h"
43 #include "CustomTextureMatrix.h"
44 #include "CustomMaterial.h"
45 #include "CustomPhysicsDriver.h"
46 #include "CustomMeshTransform.h"
47
48 // common headers
49 #include "ObstacleMgr.h"
50 #include "BaseBuilding.h"
51 #include "TextUtils.h"
52 #include "StateDatabase.h"
53
54 // bzfs specific headers
55 #include "bzfs.h"
56
57
BZWReader(std::string filename)58 BZWReader::BZWReader(std::string filename) : cURLManager(), location(filename),
59 input(NULL)
60 {
61 static const std::string httpProtocol("http://");
62 static const std::string ftpProtocol("ftp://");
63 static const std::string fileProtocol("file:/");
64
65 errorHandler = new BZWError(location);
66
67 if ((filename.substr(0, httpProtocol.size()) == httpProtocol)
68 || (filename.substr(0, ftpProtocol.size()) == ftpProtocol)
69 || (filename.substr(0, fileProtocol.size()) == fileProtocol))
70 {
71 setURL(location);
72 performWait();
73 input = new std::istringstream(httpData);
74 }
75 else
76 input = new std::ifstream(filename.c_str(), std::ios::in);
77
78 // .BZW is the official worldfile extension, warn for others
79 if ((filename.length() < 4) ||
80 (strcasecmp(filename.substr(filename.length() - 4, 4).c_str(),
81 ".bzw") != 0))
82 {
83 errorHandler->warning(std::string(
84 "world file extension is not .bzw, trying to load anyway"), 0);
85 }
86
87 if (input->peek() == EOF)
88 errorHandler->fatalError(std::string("could not find bzflag world file"), 0);
89 }
90
91
~BZWReader()92 BZWReader::~BZWReader()
93 {
94 // clean up
95 delete errorHandler;
96 delete input;
97 }
98
99
finalization(char * data,unsigned int length,bool good)100 void BZWReader::finalization(char *data, unsigned int length, bool good)
101 {
102 if (good)
103 httpData = std::string(data, length);
104 else
105 httpData = "";
106 }
107
readToken(char * buffer,int n)108 void BZWReader::readToken(char *buffer, int n)
109 {
110 int c = -1;
111
112 // skip whitespace
113 while (input->good() && (c = input->get()) != -1 && isspace(c) && c != '\n')
114 ;
115
116 // read up to whitespace or n - 1 characters into buffer
117 int i = 0;
118 if (c != -1 && c != '\n')
119 {
120 buffer[i++] = c;
121 while (input->good() && i < n - 1 && (c = input->get()) != -1 && !isspace(c))
122 buffer[i++] = (char)c;
123 }
124
125 // terminate string
126 buffer[i] = 0;
127
128 // put back last character we didn't use
129 if (c != -1 && isspace(c))
130 input->putback(c);
131 }
132
133
parseNormalObject(const char * token,WorldFileObject ** object)134 static bool parseNormalObject(const char* token, WorldFileObject** object)
135 {
136 WorldFileObject* tmpObj = NULL;
137
138 if (strcasecmp(token, "box") == 0)
139 tmpObj = new CustomBox;
140 else if (strcasecmp(token, "pyramid") == 0)
141 tmpObj = new CustomPyramid();
142 else if (strcasecmp(token, "base") == 0)
143 tmpObj = new CustomBase;
144 else if (strcasecmp(token, "link") == 0)
145 tmpObj = new CustomLink();
146 else if (strcasecmp(token, "mesh") == 0)
147 tmpObj = new CustomMesh;
148 else if (strcasecmp(token, "arc") == 0)
149 tmpObj = new CustomArc(false);
150 else if (strcasecmp(token, "meshbox") == 0)
151 tmpObj = new CustomArc(true);
152 else if (strcasecmp(token, "cone") == 0)
153 tmpObj = new CustomCone(false);
154 else if (strcasecmp(token, "meshpyr") == 0)
155 tmpObj = new CustomCone(true);
156 else if (strcasecmp(token, "sphere") == 0)
157 tmpObj = new CustomSphere;
158 else if (strcasecmp(token, "tetra") == 0)
159 tmpObj = new CustomTetra();
160 else if (strcasecmp(token, "weapon") == 0)
161 tmpObj = new CustomWeapon;
162 else if (strcasecmp(token, "zone") == 0)
163 tmpObj = new CustomZone;
164 else if (strcasecmp(token, "waterLevel") == 0)
165 tmpObj = new CustomWaterLevel;
166 else if (strcasecmp(token, "dynamicColor") == 0)
167 tmpObj = new CustomDynamicColor;
168 else if (strcasecmp(token, "textureMatrix") == 0)
169 tmpObj = new CustomTextureMatrix;
170 else if (strcasecmp(token, "material") == 0)
171 tmpObj = new CustomMaterial;
172 else if (strcasecmp(token, "physics") == 0)
173 tmpObj = new CustomPhysicsDriver;
174 else if (strcasecmp(token, "transform") == 0)
175 tmpObj = new CustomMeshTransform;
176
177 if (tmpObj != NULL)
178 {
179 *object = tmpObj;
180 return true;
181 }
182 else
183 return false;
184 }
185
readWorldStream(std::vector<WorldFileObject * > & wlist,GroupDefinition * groupDef)186 bool BZWReader::readWorldStream(std::vector<WorldFileObject*>& wlist,
187 GroupDefinition* groupDef)
188 {
189 // make sure input is valid
190 if (input->peek() == EOF)
191 {
192 errorHandler->fatalError(std::string("unexpected EOF"), 0);
193 return false;
194 }
195
196 int line = 1;
197 char buffer[1024];
198 WorldFileObject* object = NULL;
199 WorldFileObject* newObject = NULL;
200 WorldFileObject* const fakeObject = (WorldFileObject*)((char*)1);
201 GroupDefinition* const worldDef = const_cast<GroupDefinition*>(OBSTACLEMGR.getWorld());
202 GroupDefinition* const startDef = groupDef;
203
204 std::string customObject;
205 std::vector<std::string> customLines;
206
207 bool gotWorld = false;
208
209 while (!input->eof() && !input->fail() && input->good())
210 {
211 // watch out for starting a new object when one is already in progress
212 if (newObject)
213 {
214 if (object)
215 {
216 errorHandler->warning(
217 std::string("discarding incomplete object"), line);
218 if (object != fakeObject)
219 delete object;
220 else if (customObject.size())
221 {
222 customObject = "";
223 customLines.clear();
224 }
225 }
226 object = newObject;
227 newObject = NULL;
228 }
229
230 // read first token but do not skip newlines
231 readToken(buffer, sizeof(buffer));
232 if (strcmp(buffer, "") == 0)
233 {
234 // ignore blank line
235 }
236 else if (buffer[0] == '#')
237 {
238 // ignore comment
239
240 }
241 else if (strcasecmp(buffer, "end") == 0)
242 {
243 if (object)
244 {
245 if (object != fakeObject)
246 {
247 line += object->getLineCount();
248
249 if (object->usesManager())
250 {
251 object->writeToManager();
252 delete object;
253 }
254 else if (object->usesGroupDef())
255 {
256 object->writeToGroupDef(groupDef);
257 delete object;
258 }
259 else
260 wlist.push_back(object);
261 }
262 else if (customObject.size())
263 {
264 bz_CustomMapObjectInfo data;
265 data.name = bz_ApiString(customObject);
266 for (unsigned int i = 0; i < customLines.size(); i++)
267 data.data.push_back(customLines[i]);
268 customObjectMap[customObject]->MapObject(bz_ApiString(customObject),&data);
269 object = NULL;
270 }
271 object = NULL;
272 }
273 else
274 {
275 errorHandler->fatalError(
276 std::string("unexpected \"end\" token"), line);
277 return false;
278 }
279
280 }
281 else if (parseNormalObject(buffer, &newObject))
282 {
283 // newObject has already been assigned
284
285 }
286 else if (strcasecmp(buffer, "define") == 0)
287 {
288 if (groupDef != worldDef)
289 {
290 errorHandler->warning(
291 std::string("group definitions can not be nested \"") +
292 std::string(buffer) + std::string("\" - skipping"), line);
293 }
294 else
295 {
296 readToken(buffer, sizeof(buffer));
297 if (strlen(buffer) > 0)
298 {
299 if (OBSTACLEMGR.findGroupDef(buffer) != NULL)
300 {
301 errorHandler->warning(
302 std::string("duplicate group definition \"") +
303 std::string(buffer) + std::string("\" - using newest"), line);
304 }
305 groupDef = new GroupDefinition(buffer);
306 }
307 else
308 {
309 errorHandler->warning(
310 std::string("missing group definition name"), line);
311 }
312 }
313
314 }
315 else if (strcasecmp(buffer, "enddef") == 0)
316 {
317 if (groupDef == worldDef)
318 {
319 errorHandler->warning(
320 std::string("enddef without define - skipping"), line);
321 }
322 else
323 {
324 OBSTACLEMGR.addGroupDef(groupDef);
325 groupDef = worldDef;
326 }
327
328 }
329 else if (strcasecmp(buffer, "group") == 0)
330 {
331 readToken(buffer, sizeof(buffer));
332 if (strlen(buffer) <= 0)
333 {
334 errorHandler->warning(
335 std::string("missing group definition reference"), line);
336 }
337 newObject = new CustomGroup(buffer);
338
339 }
340 else if (strcasecmp(buffer, "teleporter") == 0)
341 {
342 readToken(buffer, sizeof(buffer));
343 newObject = new CustomGate(buffer);
344
345 }
346 else if (strcasecmp(buffer, "options") == 0)
347 newObject = fakeObject;
348
349 else if (strcasecmp(buffer, "include") == 0)
350 {
351 // NOTE: intentionally undocumented (at the moment)
352 readToken(buffer, sizeof(buffer));
353 std::string incName = buffer;
354 if (object == NULL)
355 {
356 // FIXME - check for recursion
357 // - better filename handling ("", spaces, and / vs. \\)
358 // - make relative names work from the base file location
359 logDebugMessage(1,"%s: (line %i): including \"%s\"\n",
360 location.c_str(), line, incName.c_str());
361 BZWReader incFile(incName);
362 std::vector<WorldFileObject*> incWlist;
363 if (incFile.readWorldStream(incWlist, groupDef))
364 {
365 // add the included objects
366 for (unsigned int i = 0; i < incWlist.size(); i++)
367 wlist.push_back(incWlist[i]);
368 }
369 else
370 {
371 // empty the failed list
372 emptyWorldFileObjectList(incWlist);
373 errorHandler->fatalError(
374 TextUtils::format("including \"%s\"", incName.c_str()), line);
375 return false;
376 }
377 }
378 else
379 {
380 errorHandler->warning(
381 TextUtils::format("including \"%s\" within an obstacle, skipping",
382 incName.c_str()), line);
383 }
384
385 }
386 else if (strcasecmp(buffer, "world") == 0)
387 {
388 if (!gotWorld)
389 {
390 newObject = new CustomWorld();
391 gotWorld = true;
392 }
393 else
394 {
395 errorHandler->warning(
396 std::string("multiple \"world\" sections found"), line);
397 }
398
399 }
400 else if (object)
401 {
402 if (object != fakeObject)
403 {
404 if (!object->read(buffer, *input))
405 {
406 // unknown token
407 errorHandler->warning(
408 std::string("unknown object parameter \"") +
409 std::string(buffer) + std::string("\" - skipping"), line);
410 // delete object;
411 // return false;
412 }
413 }
414 else if (customObject.size())
415 {
416 std::string thisline = buffer;
417 thisline += " ";
418
419 while (input->good() && input->peek() != '\n')
420 {
421 input->get(buffer, sizeof(buffer));
422 thisline += buffer;
423 }
424
425 thisline = TextUtils::replace_all(thisline,std::string("\r"),std::string(""));
426 thisline = TextUtils::replace_all(thisline,std::string("\n"),std::string(""));
427 customLines.push_back(thisline);
428 }
429
430 }
431 else // filling the current object
432 {
433 // unknown token
434 if (customObjectMap.find(TextUtils::toupper(std::string(buffer))) != customObjectMap.end())
435 {
436 customObject = TextUtils::toupper(std::string(buffer));
437 object = fakeObject;
438 customLines.clear();
439 }
440 else
441 {
442 errorHandler->warning(
443 std::string("invalid object type \"") +
444 std::string(buffer) + std::string("\" - skipping"), line);
445 if (object != fakeObject)
446 delete object;
447 }
448 // return false;
449 }
450
451 // discard remainder of line
452 while (input->good() && input->peek() != '\n')
453 input->get(buffer, sizeof(buffer));
454 input->getline(buffer, sizeof(buffer));
455 ++line;
456 }
457
458 bool retval = true;
459 if (object)
460 {
461 errorHandler->fatalError(std::string("missing \"end\" parameter"), line);
462 if (object != fakeObject)
463 delete object;
464 retval = false;
465 }
466 if (groupDef != startDef)
467 {
468 errorHandler->fatalError(std::string("missing \"enddef\" parameter"), line);
469 if (startDef == worldDef)
470 delete groupDef;
471 retval = false;
472 }
473 return retval;
474 }
475
476
defineWorldFromFile()477 WorldInfo* BZWReader::defineWorldFromFile()
478 {
479 // create world object
480 WorldInfo *myWorld = new WorldInfo;
481 if (!myWorld)
482 {
483 errorHandler->fatalError(std::string("WorldInfo failed to initialize"), 0);
484 return NULL;
485 }
486
487 // read file
488 std::vector<WorldFileObject*> list;
489 GroupDefinition* worldDef = const_cast<GroupDefinition*>(OBSTACLEMGR.getWorld());
490 if (!readWorldStream(list, worldDef))
491 {
492 emptyWorldFileObjectList(list);
493 errorHandler->fatalError(std::string("world file failed to load."), 0);
494 delete myWorld;
495 return NULL;
496 }
497
498 if (!BZDB.isTrue("noWalls"))
499 makeWalls();
500
501 // generate group instances
502 OBSTACLEMGR.makeWorld();
503
504 // make local bases
505 unsigned int i;
506 const ObstacleList& baseList = OBSTACLEMGR.getBases();
507 for (i = 0; i < baseList.size(); i++)
508 {
509 const BaseBuilding* base = (const BaseBuilding*) baseList[i];
510 TeamColor color = (TeamColor)base->getTeam();
511 if (bases.find(color) == bases.end())
512 bases[color] = TeamBases((TeamColor)color);
513 bases[color].addBase(base->getPosition(), base->getSize(),
514 base->getRotation());
515 }
516
517 // add objects
518 const unsigned int n = list.size();
519 for (i = 0; i < n; ++i)
520 list[i]->writeToWorld(myWorld);
521
522 // clean up
523 emptyWorldFileObjectList(list);
524 myWorld->finishWorld();
525
526 return myWorld;
527 }
528
529
530 // Local Variables: ***
531 // mode: C++ ***
532 // tab-width: 4 ***
533 // c-basic-offset: 4 ***
534 // indent-tabs-mode: nil ***
535 // End: ***
536 // ex: shiftwidth=4 tabstop=4
537