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