1 /* cfed -- a level editor for Crimson Fields
2    Copyright (C) 2000-2007 Jens Granseuer
3 
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 2 of the License, or
7    (at your option) any later version.
8 
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License
15    along with this program; if not, write to the Free Software
16    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17  */
18 
19 /* cfed.cpp */
20 
21 #include <stdlib.h>
22 #include <map>
23 
24 #include "SDL.h"
25 
26 #include "parser.h"
27 #include "mission.h"
28 #include "gamedefs.h"
29 #include "strutil.h"
30 #include "globals.h"
31 
32 #ifdef _MSC_VER
33 // SDL_Main linkage destroys the command line in VS8
34 #undef main
35 #endif
36 
37 #define MAX_MAP_WIDTH	180
38 #define MAX_MAP_HEIGHT	180
39 
40 class CfedParserUtils {
41 protected:
CfedParserUtils(Mission & m)42   CfedParserUtils( Mission &m ) : m(m) {}
43 
44   Point ParseCoords( const string &s ) const;
45   const UnitType *ParseUnitName( const string &name ) const;
46   int ParsePlayerId( const string &s ) const;
47 
48   Mission &m;
49 };
50 
51 class MissionHandler : public KeyValueSectionHandler {
52 public:
53   MissionHandler( Mission &m, string &tileset, string &unitset, Point &mapsize );
54 
55   int ParseSection( ifstream &in, const string *opt, unsigned long &line );
56 
57 private:
58   Mission &m;
59   string &tiles;
60   string &units;
61   Point &mapsize;
62 };
63 
64 class PlayerHandler : public KeyValueSectionHandler {
65 public:
PlayerHandler(Mission & m)66   PlayerHandler( Mission &m ) : m(m) {}
67 
68   int ParseSection( ifstream &in, const string *opt, unsigned long &line );
69 
70 private:
71   int ParseColor( const string &s, Color &col ) const;
72 
73   Mission &m;
74   static int parsed;
75 };
76 
77 class UnitHandler : public KeyValueSectionHandler, public CfedParserUtils {
78 public:
UnitHandler(Mission & m)79   UnitHandler( Mission &m ) : CfedParserUtils(m) {}
80 
81   int ParseSection( ifstream &in, const string *opt, unsigned long &line );
82 };
83 
84 class ShopHandler : public KeyValueSectionHandler, public CfedParserUtils {
85 public:
ShopHandler(Mission & m)86   ShopHandler( Mission &m ) : CfedParserUtils(m) {}
87 
88   int ParseSection( ifstream &in, const string *opt, unsigned long &line );
89 };
90 
91 class EventHandler : public KeyValueSectionHandler, public CfedParserUtils {
92 public:
EventHandler(Mission & m)93   EventHandler( Mission &m ) : CfedParserUtils(m) {}
94 
95   int ParseSection( ifstream &in, const string *opt, unsigned long &line );
96 };
97 
98 class MessagesHandler : public SectionHandler {
99 public:
MessagesHandler(Mission & m)100   MessagesHandler( Mission &m ) : m(m) {}
101 
102   int ParseSection( ifstream &in, const string *opt, unsigned long &line );
103 
104 private:
105   Mission &m;
106 };
107 
108 class MapHandler : public SectionHandler {
109 public:
MapHandler(Mission & m,const Point & size)110   MapHandler( Mission &m, const Point &size ) : m(m), size(size) {}
111 
112   int ParseSection( ifstream &in, const string *opt, unsigned long &line );
113 
114 private:
115   Mission &m;
116   const Point &size;
117 };
118 
119 class MapRawHandler : public SectionHandler {
120 public:
MapRawHandler(Mission & m,const Point & size)121   MapRawHandler( Mission &m, const Point &size ) : m(m), size(size) {}
122 
123   int ParseSection( ifstream &in, const string *opt, unsigned long &line );
124 
125 private:
126   Mission &m;
127   const Point &size;
128 };
129 
130 
131 // parser helpers
132 // parse positional information of the form "x/y"
ParseCoords(const string & s) const133 Point CfedParserUtils::ParseCoords( const string &s ) const {
134   string val;
135   Point pos;
136 
137   size_t sep = s.find( '/' );
138   if ( sep == string::npos ) return Point(-1,-1);
139 
140   val = s.substr( 0, sep );
141   CFParser::RemWhitespace( val );
142   if ( val.size() == 0 ) return Point(-1,-1);
143   pos.x = CFParser::StrToNum( val );
144 
145   val = s.substr( sep + 1 );
146   CFParser::RemWhitespace( val );
147   if ( val.size() == 0 ) return Point(-1,-1);
148   pos.y = CFParser::StrToNum( val );
149 
150   if ( (pos.x < 0) || (pos.y < 0) ) return Point(-1,-1);
151   return pos;
152 }
153 
154 // get the unit description from a unit name
ParseUnitName(const string & name) const155 const UnitType *CfedParserUtils::ParseUnitName( const string &name ) const {
156   const UnitType *ut = NULL;
157   const UnitSet &us = m.GetUnitSet();
158 
159   for ( int i = 0; i < us.NumTiles(); ++i ) {
160     if ( name == us.UnitName(i) ) {
161       ut = us.GetUnitInfo(i);
162       break;
163     }
164   }
165   return ut;
166 }
167 
168 // turn the number given into a player id
ParsePlayerId(const string & s) const169 int CfedParserUtils::ParsePlayerId( const string &s ) const {
170   int rc = -1;
171 
172   // this is different from internal use in the game! here 0 means no owner!
173   int id = CFParser::StrToNum(s);
174   if ( id == 1 ) rc = PLAYER_ONE;
175   else if ( id == 2 ) rc = PLAYER_TWO;
176   else if ( id == 0 ) rc = PLAYER_NONE;
177 
178   return rc;
179 }
180 
181 // mission parser
MissionHandler(Mission & m,string & tileset,string & unitset,Point & mapsize)182 MissionHandler::MissionHandler( Mission &m, string &tileset, string &unitset, Point &mapsize ) :
183   m(m), tiles(tileset), units(unitset), mapsize(mapsize) {
184   tiles = "default";
185   units = "default";
186   mapsize = Point(-1, -1);
187 }
188 
ParseSection(ifstream & in,const string * opt,unsigned long & line)189 int MissionHandler::ParseSection( ifstream &in, const string *opt, unsigned long &line ) {
190   int rc = KeyValueSectionHandler::ParseSection( in, opt, line );
191 
192   if ( rc == 0 ) {
193     for ( vector<pair<string, string> >::iterator it = pairs.begin();
194           it != pairs.end(); ++it ) {
195       string key = (*it).first;
196       string val = (*it).second;
197 
198       if ( key == "mapwidth" ) mapsize.x = StrToNum(val);                 // map width
199       else if ( key == "mapheight" ) mapsize.y = StrToNum(val);           // map height
200       else if ( key == "name" ) m.SetName(StrToNum(val));                 // map name
201       else if ( key == "info" ) m.SetLevelInfoMsg(StrToNum(val));         // level info
202       else if ( key == "campaignname" ) m.SetCampaignName(StrToNum(val)); // campaign name
203       else if ( key == "campaigninfo" ) m.SetCampaignInfo(StrToNum(val)); // campaign info
204       else if ( key == "players" ) m.SetNumPlayers(StrToNum(val));        // 1 or 2 player map
205       else if ( key == "unitset" ) units = val;                           // unit set to use
206       else if ( key == "tileset" ) tiles = val;                           // tile set to use
207       else if ( key == "nextmap" ) m.SetSequel(val.c_str());              // next map
208       else if ( key == "music" ) m.SetMusic(val.c_str());                 // soundtrack
209       else if ( key == "campaign" ) m.SetCampaign(StrToNum(val) != 0);    // campaign map
210       else if ( key == "skirmish" ) m.SetSkirmish(StrToNum(val) != 0);    // suitable for skirmishes
211       else {
212         rc = 1;
213         cerr << "Error near line " << line << ": Invalid keyword '" << key << "'" << endl;
214         break;
215       }
216     }
217   }
218 
219   return rc;
220 }
221 
222 // player parser
223 int PlayerHandler::parsed = 0;
224 
ParseSection(ifstream & in,const string * opt,unsigned long & line)225 int PlayerHandler::ParseSection( ifstream &in, const string *opt, unsigned long &line ) {
226   if ( parsed > 1 ) {
227     cerr << "Error in line " << line << ": more than two [player] sections defined" << endl;
228     return 1;
229   }
230 
231   int rc = KeyValueSectionHandler::ParseSection( in, opt, line );
232 
233   if ( rc == 0 ) {
234     Player &p = m.GetPlayer( parsed );
235 
236     for ( vector<pair<string, string> >::iterator it = pairs.begin();
237           it != pairs.end(); ++it ) {
238       string key = (*it).first;
239       string val = (*it).second;
240 
241       if ( key == "name" ) p.SetNameID(StrToNum(val));
242       else if ( key == "briefing" ) p.SetBriefing(StrToNum(val));
243       else if ( (key == "fcolor") || (key == "bcolor") ) {
244         Color col;
245         if ( !ParseColor(val, col) ) {
246           if ( key[0] == 'f' ) p.SetLightColor(col);
247           else p.SetDarkColor(col);
248         } else {
249           rc = 1;
250           cerr << "Error near line " << line << ": Could not parse color '" << val << "'" << endl;
251         }
252       } else {
253         rc = 1;
254         cerr << "Error near line " << line << ": Invalid keyword '" << key << "' in this context" << endl;
255       }
256     }
257   }
258 
259   ++parsed;
260   return rc;
261 }
262 
263 // parse a color defined as "rr,gg,bb"
ParseColor(const string & s,Color & col) const264 int PlayerHandler::ParseColor( const string &s, Color &col ) const {
265   size_t pos1, pos2;
266   short comp;
267   string val;
268 
269   pos1 = s.find( ',' );
270   pos2 = s.rfind( ',' );
271 
272   if ( pos1 == pos2 ) return -1;
273 
274   val = s.substr( 0, pos1 );
275   RemWhitespace( val );
276   if ( val.size() == 0 ) return -1;
277   comp = StrToNum( val );
278   if ( (comp < 0) || (comp > 255) ) return -1;
279   col.r = comp;
280 
281   val = s.substr( pos1 + 1, pos2 - pos1 - 1 );
282   RemWhitespace( val );
283   if ( val.size() == 0 ) return -1;
284   comp = StrToNum( val );
285   if ( (comp < 0) || (comp > 255) ) return -1;
286   col.g = comp;
287 
288   val = s.substr( pos2 + 1 );
289   RemWhitespace( val );
290   if ( val.size() == 0 ) return -1;
291   comp = StrToNum( val );
292   if ( (comp < 0) || (comp > 255) ) return -1;
293   col.b = comp;
294 
295   return 0;
296 }
297 
298 // unit parser
ParseSection(ifstream & in,const string * opt,unsigned long & line)299 int UnitHandler::ParseSection( ifstream &in, const string *opt, unsigned long &line ) {
300   int rc = KeyValueSectionHandler::ParseSection( in, opt, line );
301 
302   if ( rc == 0 ) {
303     Unit *u = new Unit();
304     u->SetPosition( Point(-1,-1) );
305     u->SetID( 0 );
306     u->UnsetFlags( ~0 );
307     u->SetOwner( PLAYER_NONE );
308     u->SetType( m.GetUnitSet().GetUnitInfo(0) );
309     u->SetCrystals( 0 );
310     u->SetDirection( 99 );
311     u->SetGroupSize( MAX_GROUP_SIZE );
312     u->SetXP( 0 );
313 
314     for ( vector<pair<string, string> >::iterator it = pairs.begin();
315           (it != pairs.end()) && (rc == 0); ++it ) {
316       string key = (*it).first;
317       string val = (*it).second;
318 
319       if ( key == "pos" ) u->SetPosition(ParseCoords(val));           // unit position
320       else if ( key == "id" ) u->SetID(StrToNum(val));                // unit id
321       else if ( key == "crystals" ) u->SetCrystals(StrToNum(val));    // crystals
322       else if ( key == "face" ) u->SetDirection(StrToNum(val));       // direction
323       else if ( key == "size" ) u->SetGroupSize(StrToNum(val));       // group size
324       else if ( key == "xp" ) u->SetXP(StrToNum(val) * XP_PER_LEVEL); // experience
325       else if ( key == "type" ) {                                     // unit type
326         const UnitType *type = ParseUnitName( val );
327         if ( type ) {
328           u->SetType( type );
329           u->SetFlags( u->Flags() | type->Flags() );
330         } else {
331           rc = 1;
332           cerr << "Error: Unknown unit type '" << val << "' near line " << line << endl;
333         }
334       } else if ( key == "player" ) {                             // owning player
335         int id = ParsePlayerId( val );
336         if ( id != -1 ) u->SetOwner( id );
337         else {
338           rc = 1;
339           cerr << "Error near line " << line << ": Invalid owner of unit" << endl;
340         }
341       } else {
342         rc = 1;
343         cerr << "Error near line " << line << ": Invalid keyword '" << key << "' in this context" << endl;
344       }
345     }
346 
347     if ( rc == 0 ) m.GetUnits().AddTail( u );
348     else delete u;
349   }
350 
351   return rc;
352 }
353 
354 
355 // shop parser
ParseSection(ifstream & in,const string * opt,unsigned long & line)356 int ShopHandler::ParseSection( ifstream &in, const string *opt, unsigned long &line ) {
357   int rc = KeyValueSectionHandler::ParseSection( in, opt, line );
358 
359   if ( rc == 0 ) {
360     Building *b = new Building();
361     b->SetPosition( Point(0,0) );
362     b->SetID( 0 );
363     b->SetOwner( PLAYER_NONE );
364     b->SetCrystalProduction( 0 );
365     b->SetCrystals( 0 );
366     b->SetMaxCrystals( 1000 );
367     b->SetUnitProduction( 0 );
368     b->UnsetFlags( ~0 );
369     b->SetMinWeight( 0 );
370     b->SetMaxWeight( 99 );
371     b->SetNameID( -1 );
372 
373     for ( vector<pair<string, string> >::iterator it = pairs.begin();
374           (it != pairs.end()) && (rc == 0); ++it ) {
375       string key = (*it).first;
376       string val = (*it).second;
377 
378       if ( key == "pos" ) b->SetPosition(ParseCoords(val));               // shop position
379       else if ( key == "id" ) b->SetID(StrToNum(val));                    // ID
380       else if ( key == "name" ) b->SetNameID(StrToNum(val));              // name
381       else if ( key == "mining" ) b->SetCrystalProduction(StrToNum(val)); // mining
382       else if ( key == "crystals" ) b->SetCrystals(StrToNum(val));        // resources
383       else if ( key == "capacity" ) b->SetMaxCrystals(StrToNum(val));     // max. resources
384       else if ( key == "minweight" ) b->SetMinWeight(StrToNum(val));      // smallest unit allowed
385       else if ( key == "maxweight" ) b->SetMaxWeight(StrToNum(val));      // biggest unit allowed
386       else if ( key == "player" ) {                                       // owning player
387         int id = ParsePlayerId( val );
388         if ( id != -1 ) b->SetOwner( id );
389         else {
390           rc = 1;
391           cerr << "Error near line " << line << ": Invalid owner of building" << endl;
392         }
393       } else if ( key == "type" ) {                                       // type
394         if ( val == "workshop" ) b->SetFlags(BLD_WORKSHOP);
395         else if ( val == "factory" ) b->SetFlags(BLD_FACTORY);
396         else {
397           rc = 1;
398           cerr << "Error in line " << line << ": Unknown type '" << val << "'" << endl;
399         }
400       } else if ( key == "factory" ) {                            // can produce which units
401         const UnitType *type = ParseUnitName( val );
402         if ( type ) {
403           b->SetFlags(BLD_FACTORY);
404           b->SetUnitProduction(b->UnitProduction()|(1<<type->ID()));
405         } else {
406           rc = 1;
407           cerr << "Error: Unknown unit type '" << val << "' near line " << line << endl;
408         }
409       } else {
410         rc = 1;
411         cerr << "Error near line " << line << ": Invalid keyword '" << key << "' in this context" << endl;
412       }
413     }
414 
415     if ( rc == 0 ) m.GetBuildings().AddTail( b );
416     else delete b;
417   }
418 
419   return rc;
420 }
421 
422 
423 // event parser
ParseSection(ifstream & in,const string * opt,unsigned long & line)424 int EventHandler::ParseSection( ifstream &in, const string *opt, unsigned long &line ) {
425   int rc = KeyValueSectionHandler::ParseSection( in, opt, line );
426 
427   if ( rc == 0 ) {
428     Event *e = new Event();
429     e->SetID( 0 );
430     e->SetType( 99 );
431     e->SetPlayer( PLAYER_NONE );
432     e->SetTrigger( 99 );
433     e->SetDependency( -1 );
434     e->SetDiscard( -1 );
435     for ( int i = 0; i < 3; ++i ) {
436       e->SetData( i, -9999 );
437       e->SetTData( i, -9999 );
438     }
439     e->SetTitle( -1 );
440     e->SetMessage( -1 );
441     e->SetFlags( 0 );
442     e->SetTmpBuf( "" );
443 
444     for ( vector<pair<string, string> >::iterator it = pairs.begin();
445           (it != pairs.end()) && (rc == 0); ++it ) {
446       string key = (*it).first;
447       string val = (*it).second;
448       Point loc;
449       int id;
450 
451       if ( key == "type" ) {                                 // type
452         if ( val == "message" ) e->SetType(EVENT_MESSAGE);
453         else if ( val == "research" ) e->SetType(EVENT_RESEARCH);
454         else if ( val == "mining" ) e->SetType(EVENT_MINING);
455         else if ( val == "score" ) e->SetType(EVENT_SCORE);
456         else if ( val == "configure" ) e->SetType(EVENT_CONFIGURE);
457         else if ( val == "manipulateevent" ) e->SetType(EVENT_MANIPULATE_EVENT);
458         else if ( val == "sethex" ) e->SetType(EVENT_SET_HEX);
459         else if ( val == "settimer" ) e->SetType(EVENT_SET_TIMER);
460         else if ( val == "destroyunit" ) e->SetType(EVENT_DESTROY_UNIT);
461         else if ( val == "createunit" ) {
462           e->SetType(EVENT_CREATE_UNIT);
463           e->SetData(2, NORTH | (MAX_GROUP_SIZE << 3) | (0 << 6)); // dir, group size, xp defaults
464         } else {
465           rc = 1;
466           cerr << "Error near line " << line << ": Unknown event type '" << val << "'" << endl;
467         }
468 
469       } else if ( key == "trigger" ) {                       // trigger
470         if ( val == "timer" ) e->SetTrigger(ETRIGGER_TIMER);
471         else if ( val == "unitdestroyed" ) e->SetTrigger(ETRIGGER_UNIT_DESTROYED);
472         else if ( val == "unitposition" ) e->SetTrigger(ETRIGGER_UNIT_POSITION);
473         else if ( val == "handicap" ) e->SetTrigger(ETRIGGER_HANDICAP);
474         else if ( val == "havecrystals" ) e->SetTrigger(ETRIGGER_HAVE_CRYSTALS);
475         else if ( val == "havebuilding" ) {
476           e->SetTrigger(ETRIGGER_HAVE_BUILDING);
477           e->SetTData(2, -1);
478         } else if ( val == "haveunit" ) {
479           e->SetTrigger(ETRIGGER_HAVE_UNIT);
480           e->SetTData(2, -1);
481         } else {
482           rc = 1;
483           cerr << "Error near line " << line << ": Unknown trigger type '" << val << "'" << endl;
484         }
485       }
486 
487       else if ( key == "player" ) {                             // player
488         id = ParsePlayerId( val );
489         if ( (id != -1) && (id != PLAYER_NONE) ) e->SetPlayer( id );
490         else {
491           rc = 1;
492           cerr << "Error near line " << line << ": Invalid player" << endl;
493         }
494       }
495       else if ( key == "title" ) e->SetTitle(StrToNum(val));
496       else if ( key == "message" ) e->SetMessage(StrToNum(val));
497       else if ( key == "id" ) e->SetID(StrToNum(val));
498       else if ( key == "flags" ) e->SetFlags(StrToNum(val));
499       else if ( key == "depend" ) e->SetDependency(StrToNum(val));
500       else if ( key == "discard" ) e->SetDiscard(StrToNum(val));
501       else {
502         if ( (e->Trigger() == ETRIGGER_TIMER) &&
503              (key == "ttime") ) e->SetTData(0, StrToNum(val));      // timer
504         else if ( (e->Trigger() == ETRIGGER_HAVE_UNIT) &&
505              (key == "tunit") ) e->SetTData(0, StrToNum(val));      // unit
506         else if ( (e->Trigger() == ETRIGGER_HAVE_BUILDING) &&
507              (key == "tbuilding") ) e->SetTData(0, StrToNum(val));  // building id
508         else if ( (e->Trigger() == ETRIGGER_HAVE_CRYSTALS) &&
509              (key == "tbuilding") ) e->SetTData(2, StrToNum(val));  // building id
510         else if ( (e->Trigger() == ETRIGGER_HAVE_CRYSTALS) &&
511              (key == "tcrystals") ) e->SetTData(0, StrToNum(val));  // number of crystals
512         else if ( ((e->Trigger() == ETRIGGER_HAVE_BUILDING) ||
513                    (e->Trigger() == ETRIGGER_HAVE_UNIT) ||
514                    (e->Trigger() == ETRIGGER_UNIT_DESTROYED) ||
515                    (e->Trigger() == ETRIGGER_HAVE_CRYSTALS)) &&
516              (key == "towner") ) e->SetTData(1, StrToNum(val)-1);   // owner of shop/unit
517         else if ( (e->Trigger() == ETRIGGER_UNIT_POSITION) &&
518              (key == "towner") ) e->SetTData(2, StrToNum(val)-1);   // owner of unit
519         else if ( ((e->Trigger() == ETRIGGER_HAVE_BUILDING) ||
520                    (e->Trigger() == ETRIGGER_HAVE_UNIT)) &&
521              (key == "ttime") ) e->SetTData(2, StrToNum(val));      // time after which to check
522         else if ( (e->Trigger() == ETRIGGER_UNIT_POSITION) && (key == "tpos") ) {
523           loc = ParseCoords(val);
524           if ( (loc.x < 0) || (loc.y < 0) ) {
525             rc = 1;
526             cerr << "Error near line " << line << ": Invalid position " << val << endl;
527           } else e->SetTData(1, m.GetMap().Hex2Index(loc));         // unit position
528         }
529         else if ( ((e->Trigger() == ETRIGGER_UNIT_POSITION) ||
530                    (e->Trigger() == ETRIGGER_UNIT_DESTROYED)) && (key == "tunit") ) { // unit or unit type
531           if ( val.size() > 0 && (val[0] == '-' || isdigit(val[0])) ) {
532             // seems to be a number -> specific unit or -1
533             e->SetTData(0, StrToNum(val));
534           } else {
535             const UnitType *type = ParseUnitName( val );
536             if ( type ) e->SetTData(0, -type->ID() - 2);
537             else {
538               rc = 1;
539               cerr << "Error near line " << line << ": Invalid unit type " << val << endl;
540             }
541           }
542         }
543         else if ( (e->Trigger() == ETRIGGER_HANDICAP) &&
544              (key == "thandicap") ) e->SetTData(0, StrToNum(val));   // handicap
545 
546         else switch ( e->Type() ) {
547           case EVENT_CREATE_UNIT:
548             // e_data[2] is split: 0000000 111 111 111
549             //                             xp size dir
550             if ( key == "face" )                                     // direction to face
551               e->SetData(2, (e->GetData(2) & ~0x0007) | (StrToNum(val) & 0x0007));
552             else if ( key == "size" )                                // group size
553               e->SetData(2, (e->GetData(2) & ~0x0038) | ((StrToNum(val) << 3) & 0x0038));
554             else if ( key == "xp" )                                  // experience
555               e->SetData(2, (e->GetData(2) & ~0x01C0) | ((StrToNum(val) << 6) & 0x01C0));
556             else if ( key == "pos" ) {                               // where to create
557               loc = ParseCoords(val);
558               if ( (loc.x < 0) || (loc.y < 0) ) {
559                 rc = 1;
560                 cerr << "Error near line " << line << ": Invalid position " << val << endl;
561               } else e->SetData(1, m.GetMap().Hex2Index(loc));
562             }
563             else if ( key == "unit" ) {                              // unit type
564               const UnitType *type = ParseUnitName( val );
565               if ( type ) e->SetData(0, type->ID());
566               else {
567                 rc = 1;
568                 cerr << "Error: Unknown unit type '" << val << "' near line " << line << endl;
569               }
570             } else {
571               rc = 1;
572               cerr << "Error near line " << line << ": Invalid keyword '" << key << "'" << endl;
573             }
574             break;
575 
576           case EVENT_DESTROY_UNIT:
577             if ( key == "unit" ) e->SetData(0, StrToNum(val));         // id of unit to destroy
578             else if ( key == "owner" ) e->SetData(1, StrToNum(val)-1); // unit owner
579             else if ( key == "pos" ) {                                 // unit position
580               loc = ParseCoords(val);
581               if ( (loc.x < 0) || (loc.y < 0) ) {
582                 rc = 1;
583                 cerr << "Error near line " << line << ": Invalid position " << val << endl;
584               } else e->SetData(2, m.GetMap().Hex2Index(loc));
585             }
586             break;
587 
588           case EVENT_MANIPULATE_EVENT:
589             if ( key == "event" ) e->SetData(0, StrToNum(val));       // ID of target event
590             else if ( key == "eflags" ) e->SetData(1, StrToNum(val)); // flags to modify
591             else if ( key == "action" ) e->SetData(2, StrToNum(val)); // set/clear/toggle
592             else {
593               rc = 1;
594               cerr << "Error near line " << line << ": Invalid keyword '" << key << "'" << endl;
595             }
596             break;
597 
598           case EVENT_MESSAGE:
599             if ( key == "pos" ) {                                  // hex to focus on
600               loc = ParseCoords(val);
601               if ( (loc.x < 0) || (loc.y < 0) ) {
602                 rc = 1;
603                 cerr << "Error near line " << line << ": Invalid position " << val << endl;
604               } else e->SetData(0, m.GetMap().Hex2Index(loc));
605             } else {
606               rc = 1;
607               cerr << "Error near line " << line << ": Invalid keyword '" << key << "'" << endl;
608             }
609             break;
610 
611           case EVENT_MINING:
612             if ( key == "building" ) e->SetData(0, StrToNum(val));      // id of building
613             else if ( key == "crystals" ) e->SetData(1, StrToNum(val)); // crystal production
614             else if ( key == "action" ) e->SetData(2, StrToNum(val));   // modification action
615             else {
616               rc = 1;
617               cerr << "Error near line " << line << ": Invalid keyword '" << key << "'" << endl;
618             }
619             break;
620 
621          case EVENT_CONFIGURE:
622             if ( key == "setting" ) {                          // setting to change
623               if ( val == "briefing1" ) e->SetData(0, 0);
624               else if ( val == "briefing2" ) e->SetData(0, 1);
625               else if ( val == "nextmap" ) e->SetData(0, 2);
626             } else if ( key == "value" ) {                     // value to set
627               switch (e->GetData(0)) {
628               case 0:
629               case 1:
630                 e->SetData(1, StrToNum(val));
631                 break;
632               case 2:
633                 e->SetTmpBuf(val);
634                 break;
635               default:
636                 rc = 1;
637                 cerr << "Error near line " << line << ": Configure event does not support value before setting" << endl;
638               }
639             } else {
640               rc = 1;
641               cerr << "Error near line " << line << ": Invalid keyword '" << key << "'" << endl;
642             }
643             break;
644 
645           case EVENT_RESEARCH:
646             if ( key == "building" ) e->SetData(0, StrToNum(val));    // id of building
647             else if ( key == "action" ) e->SetData(2, StrToNum(val)); // whether to allow or disallow this unit
648             else if ( key == "unit" ) {                               // unit type
649               const UnitType *type = ParseUnitName( val );
650               if ( type ) e->SetData(1, type->ID());
651               else {
652                 rc = 1;
653                 cerr << "Error: Unknown unit type '" << val << "' near line " << line << endl;
654               }
655             } else {
656               rc = 1;
657               cerr << "Error near line " << line << ": Invalid keyword '" << key << "'" << endl;
658             }
659             break;
660 
661           case EVENT_SCORE:
662             if ( key == "success" ) e->SetData(0, StrToNum(val));         // success rate increase
663             else if ( key == "othermsg" ) e->SetData(1, StrToNum(val));   // message for other player
664             else if ( key == "othertitle" ) e->SetData(2, StrToNum(val)); // message title for other player
665             else {
666               rc = 1;
667               cerr << "Error near line " << line << ": Invalid keyword '" << key << "'" << endl;
668             }
669             break;
670 
671           case EVENT_SET_HEX:
672             if ( key == "pos" ) {                                         // hex to change
673               loc = ParseCoords(val);
674               if ( (loc.x < 0) || (loc.y < 0) ) {
675                 rc = 1;
676                 cerr << "Error near line " << line << ": Invalid position " << val << endl;
677               } else e->SetData(1, m.GetMap().Hex2Index(loc));
678             }
679             else if ( key == "tile" ) {                                   // new terrain type
680               id = StrToNum(val);
681               if ( (id < m.GetTerrainSet().NumTiles()) && (id >= 0) ) e->SetData(0, id);
682               else {
683                 rc = 0;
684                 cerr << "Error: Invalid tile '" << val << "' near line " << line << endl;
685               }
686             } else {
687               rc = 1;
688               cerr << "Error near line " << line << ": Invalid keyword '" << key << "'" << endl;
689             }
690             break;
691 
692           case EVENT_SET_TIMER:
693             if ( key == "event" ) e->SetData(0, StrToNum(val));       // ID of target event
694             else if ( key == "time" ) e->SetData(1, StrToNum(val));   // time
695             else if ( key == "offset" ) e->SetData(2, StrToNum(val)); // time offset
696             else {
697               rc = 1;
698               cerr << "Error near line " << line << ": Invalid keyword '" << key << "'" << endl;
699             }
700             break;
701         }
702       }
703     }
704 
705     if ( rc == 0 ) m.GetEvents().AddTail( e );
706     else delete e;
707   }
708 
709   return rc;
710 }
711 
712 // map parser
ParseSection(ifstream & in,const string * opt,unsigned long & line)713 int MapHandler::ParseSection( ifstream &in, const string *opt, unsigned long &line ) {
714 
715   if ( (size.x <= 0) || (size.x > MAX_MAP_WIDTH) || (size.y <= 0) || (size.y > MAX_MAP_HEIGHT) ) {
716     cerr << "Error: Invalid map size" << endl;
717     return -1;
718   }
719 
720   m.GetMap().SetSize( size );
721 
722   string buf;
723   for ( int i = 0; i < size.y; ++i ) {
724     getline( in, buf );
725     ++line;
726 
727     if ( in.eof() ) {
728       cerr << "Error in line " << line << ": Unexpected end of map data" << endl;
729       return -1;
730     }
731 
732     RemWhitespace( buf );
733     if ( buf.size() != (unsigned short)size.x ) {
734       cerr << "Error in line " << line << ": map row " << i << " does not comply with width parameter" << endl;
735       return -1;
736     }
737 
738     for ( int j = 0; j < size.x; ++j ) {
739       unsigned short terrain;
740       switch ( buf[j] ) {
741         case '.': terrain = 30; break;                                  /* plains */
742         case '*': terrain = 81; break;                                  /* forest */
743         case '%': terrain = 117; break;                                 /* mountains */
744         case '=': terrain = 360; break;                                 /* shallow water */
745         case '~': terrain = 361; break;                                 /* water */
746         case '-': terrain = 362; break;                                 /* deep water */
747         case '1': terrain = 6; break;                                   /* yellow hq entrance, east */
748         case '2': terrain = 7; break;                                   /* blue hq entrance, east */
749         case '0': terrain = 8; break;                                   /* neutral hq entrance, east */
750         case '<': terrain = 22; break;                                  /* hq, left part */
751         case '>': terrain = 23; break;                                  /* hq, right part */
752         case '^': terrain = 21; break;                                  /* hq, upper part */
753         case 'v': terrain = 24; break;                                  /* hq, lower part */
754         case '#': terrain = 307; break;                                 /* swamp */
755         case '\\': terrain = 177; break;                                /* road, se-nw */
756         case '|': terrain = 176; break;                                 /* road, s-n */
757         case '/': terrain = 178; break;                                 /* road, sw-ne */
758         case 'y': terrain = 193; break;                                 /* road, sw-n-ne */
759         case 'Y': terrain = 194; break;                                 /* road, se-n-nw */
760         case '(': terrain = 180; break;                                 /* road, n-se */
761         case ')': terrain = 179; break;                                 /* road, n-sw */
762         case ']': terrain = 181; break;                                 /* road, nw-s */
763         case '[': terrain = 182; break;                                 /* road, ne-s */
764         case 'n': terrain = 184; break;                                 /* road, sw-se */
765         case 'u': terrain = 183; break;                                 /* road, nw-ne */
766         case 'o': terrain = 201; break;                                 /* road, sw-nw-ne-se */
767         case 'k': terrain = 192; break;                                 /* road, sw-s-ne */
768         case 'K': terrain = 191; break;                                 /* road, s-se-nw */
769         case 'T': terrain = 188; break;                                 /* road, n-s-se */
770         case 'U': terrain = 187; break;                                 /* road, n-s-sw */
771         case 'V': terrain = 198; break;                                 /* road, n-s-ne */
772         case 'W': terrain = 189; break;                                 /* road, n-s-nw */
773         case 'X': terrain = 199; break;                                 /* road, s-se-nw-n */
774         case 'x': terrain = 200; break;                                 /* road, s-sw-ne-n */
775         case '!': terrain = 208; break;                                 /* bridge, n-s */
776         case '`': terrain = 209; break;                                 /* bridge, se-nw */
777         case '\'': terrain = 210; break;                                /* bridge, sw-ne */
778         case '4': terrain = 9; break;                                   /* yellow depot entrance */
779         case '5': terrain = 10; break;                                  /* blue depot entrance */
780         case '3': terrain = 11; break;                                  /* neutral depot entrance */
781         case '7': terrain = 12; break;                                  /* yellow factory entrance, north */
782         case '8': terrain = 13; break;                                  /* blue factory entrance, north */
783         case '6': terrain = 14; break;                                  /* neutral factory entrance, north */
784         case 'a': terrain = 144; break;                                 /* wire fence, se-nw end */
785         case 'b': terrain = 147; break;                                 /* wire fence, nw-se end */
786         case 'c': terrain = 146; break;                                 /* wire fence, ne-sw end */
787         case 'd': terrain = 145; break;                                 /* wire fence, sw-ne end */
788         case 'e': terrain = 133; break;                                 /* wire fence, n-s */
789         case 'f': terrain = 135; break;                                 /* wire fence, sw-ne */
790         case 'g': terrain = 134; break;                                 /* wire fence, nw-se */
791         case 'h': terrain = 138; break;                                 /* wire fence, nw-s */
792         case 'i': terrain = 139; break;                                 /* wire fence, ne-s */
793         case 'j': terrain = 136; break;                                 /* wire fence, sw-n */
794         case 'l': terrain = 137; break;                                 /* wire fence, se-n */
795         case 'm': terrain = 140; break;                                 /* wire fence, nw-ne */
796         case 'p': terrain = 141; break;                                 /* wire fence, sw-se */
797         case '"': terrain = 363; break;                                 /* cliff 1 */
798         case 'A': terrain = 18; break;                                  /* yellow city */
799         case 'B': terrain = 19; break;                                  /* blue city */
800         case 'C': terrain = 20; break;                                  /* neutral city */
801         case 'D': terrain = 3; break;                                   /* yellow hq entrance, west */
802         case 'E': terrain = 4; break;                                   /* blue hq entrance, west */
803         case 'F': terrain = 5; break;                                   /* neutral hq entrance, west */
804         case 'G': terrain = 0; break;                                   /* yellow hq entrance, north */
805         case 'H': terrain = 1; break;                                   /* blue hq entrance, north */
806         case 'I': terrain = 2; break;                                   /* neutral hq entrance, north */
807         case '9': terrain = 15; break;                                  /* yellow factory entrance, east */
808         case 'J': terrain = 16; break;                                  /* blue factory entrance, east */
809         case 'L': terrain = 17; break;                                  /* neutral factory entrance, east */
810         default:
811           cerr << "Error in line " << line << ": Invalid character '" << buf[j] << "' in map" << endl;
812           return -1;
813       }
814 
815       m.GetMap().SetHexType( j, i, terrain );
816     }
817   }
818 
819   return 0;
820 }
821 
822 // read a raw map; a hex is represented by its ID and hexes are separated by comma;
823 // this way, much more hex types can be accessed than by using plain ASCII code
ParseSection(ifstream & in,const string * opt,unsigned long & line)824 int MapRawHandler::ParseSection( ifstream &in, const string *opt, unsigned long &line ) {
825   string buf;
826   unsigned short terrain;
827   size_t pos;
828   const char *start;
829   Map &map = m.GetMap();
830 
831   if ( (size.x <= 0) || (size.x > MAX_MAP_WIDTH) || (size.y <= 0) || (size.y > MAX_MAP_HEIGHT) ) {
832     cerr << "Error: Invalid map size" << endl;
833     return -1;
834   }
835 
836   map.SetSize( size );
837 
838   for ( int i = 0; i < size.y; ++i ) {
839     getline(in, buf);
840     ++line;
841     if ( in.eof() ) {
842       cerr << "Error in line " << line << ": unexpected end of map data" << endl;
843       return -1;
844     }
845 
846     RemWhitespace( buf );
847     pos = 0;
848     for ( int j = 0; j < size.x; ++j ) {
849       start = &buf.c_str()[pos];
850       while ( (buf[pos] >= '0') && (buf[pos] <= '9') ) ++pos;
851 
852       if ( (buf[pos] == ',') || (buf[pos] == '\0') ) {
853         buf[pos++] = '\0';
854 
855         terrain = atoi( start );
856         if ( terrain >= map.GetTerrainSet()->NumTiles() ) {
857           cerr << "Error in line " << line << ": Invalid hex id '" << terrain << "'" << endl;
858           return -1;
859         } else map.SetHexType( j, i, terrain );
860       } else {
861         cerr << "Error in line " << line << ": Invalid character '" << buf[pos] << "' in map" << endl;
862         return -1;
863       }
864     }
865   }
866 
867   return 0;
868 }
869 
870 // messages parser
ParseSection(ifstream & in,const string * opt,unsigned long & line)871 int MessagesHandler::ParseSection( ifstream &in, const string *opt, unsigned long &line ) {
872   if ( !opt ) {
873     cerr << "Error in line " << line << ": [Messages] section does not specify a language" << endl;
874     return -1;
875   } else if ( opt->size() != 2 ) {
876     cerr << "Error in line " << line << ": " << *opt << " is not a valid language" << endl;
877     return -1;
878   }
879 
880   Language lang;
881   lang.SetID( opt->c_str() );
882   string buf, msg;
883   bool done = false;
884 
885   do {
886     getline(in, buf);
887     ++line;
888 
889     RemWhitespace( buf );
890 
891     if ((buf.size() > 0) &&
892        ((buf[0] == '%') || (buf.substr(0,11) == "[/messages]"))) {
893       // save last message
894       if (!msg.empty()) {
895         lang.AddMsg(msg);
896         msg.erase();
897       }
898 
899       if (buf.substr(0,11) == "[/messages]") done = true;
900     } else {
901       if (!msg.empty()) msg += '\n';
902       msg.append(buf);
903     }
904   } while ( !in.eof() && !done );
905 
906   int rc;
907 
908   if (in.eof()) {
909     rc = -1;
910     cerr << "Error in line " << line << ": Messages section unterminated" << endl;
911   } else {
912     rc = 0;
913     m.GetMessages().AddLanguage( lang );
914   }
915 
916   return rc;
917 }
918 
919 #define SECTION_MAP	(1<<0)
920 #define SECTION_MISSION	(1<<1)
921 #define SECTION_PLAYER1	(1<<2)
922 #define SECTION_PLAYER2	(1<<3)
923 
924 class MissionParser : public Mission, public SectionParsedCallback {
925 public:
926   MissionParser( void );
927 
928   int parse( const char *file );
929 
930   int load_unit_set( const char *set );
931   int load_tile_set( const char *set );
932 
933   void SectionParsed( const string &section,
934        SectionHandler &handler, CFParser &parser );
935 
936 private:
937   int check_game( void ) const;
938   int check_units( void ) const;
939   int check_shops( void ) const;
940   int check_events( void ) const;
941   int check_player( const Player &p ) const;
942 
943   template <typename T>
find_dups(const List & l,const T * o1) const944   bool find_dups( const List &l, const T *o1 ) const {
945     int cnt = 0;
946     for ( T *o2 = static_cast<T *>(l.Head());
947           o2; o2 = static_cast<T *>(o2->Next()) ) {
948       if ( o1->ID() == o2->ID() ) ++cnt;
949     }
950     return cnt > 1;
951   }
952 
953   unsigned short parsed_sections;
954 };
955 
956 // start of program functions
main(int argc,char ** argv)957 int main( int argc, char **argv ) {
958   short show_help = 0;
959   int rc, i;
960   char *uset = NULL, *tset = NULL;
961   string outname;
962 
963   if ( SDL_Init( 0 ) < 0 ) {
964     cerr << "Error: Could not init SDL. " << SDL_GetError() << endl;
965     return 1;
966   }
967 
968   if ( argc < 2 ) show_help = 1;
969   else {
970     for ( i = argc - 1; i > 1; --i ) {
971       if (strcmp(argv[i], "--help") == 0) show_help = 1;
972       else if (strcmp(argv[i], "--version") == 0) {
973         cout << "cfed " VERSION << endl;
974         return 0;
975       }
976       else if (strcmp(argv[i-1], "--units") == 0) uset = argv[i];
977       else if (strcmp(argv[i-1], "--tiles") == 0) tset = argv[i];
978       else if (strcmp(argv[i-1], "-o") == 0) outname = argv[i];
979     }
980   }
981 
982   if ( !uset || !tset ) show_help = 1;
983 
984   if ( show_help ) {
985     cout << "Usage: " << argv[0] << " file --tiles <tileset> --units <unitset> [-o <outfile>]" << endl
986               << "  --help     display this help and exit" << endl
987               << "  --version  output version information and exit" << endl;
988     return 0;
989   }
990 
991   MissionParser parser;
992   if ( parser.load_unit_set( uset ) != 0 ) {
993     cerr << "Unable to open unit set " << uset << endl;
994     return 1;
995   }
996   if ( parser.load_tile_set( tset ) != 0 ) {
997     cerr << "Unable to open tile set " << tset << endl;
998     return 1;
999   }
1000 
1001   rc = parser.parse( argv[1] );
1002 
1003   if ( !rc ) {
1004     if ( outname.empty() ) {
1005       outname = argv[1];
1006       size_t pos = outname.rfind( '.' );
1007       if ( pos != string::npos ) outname.erase( pos );
1008       outname.append( ".lev" );
1009     }
1010 
1011     rc = parser.Save( outname.c_str() );
1012 
1013     if ( rc )
1014       cerr << "Error opening file '" << outname << "' for writing" << endl;
1015   }
1016 
1017   return rc;
1018 }
1019 
1020 /* parse the file and create a binary mission file from it */
parse(const char * file)1021 int MissionParser::parse( const char *file ) {
1022   parsed_sections = 0;
1023 
1024   int err = 0;
1025   Point mapsize;
1026   string tileset, unitset;
1027 
1028   CFParser parser;
1029   SectionHandler *h;
1030 
1031   parser.AddHandler( "mission", new MissionHandler( *this, tileset, unitset, mapsize ) );
1032   parser.AddHandler( "player", new PlayerHandler( *this ) );
1033   parser.AddHandler( "messages", new MessagesHandler( *this ) );
1034 
1035   h = new UnitHandler( *this );
1036   h->SetEnabled( false );
1037   parser.AddHandler( "unit", h );
1038 
1039   h = new ShopHandler( *this );
1040   h->SetEnabled( false );
1041   parser.AddHandler( "building", h );
1042 
1043   h = new EventHandler( *this );
1044   h->SetEnabled( false );
1045   parser.AddHandler( "event", h );
1046 
1047   h = new MapHandler( *this, mapsize );
1048   h->SetEnabled( false );
1049   parser.AddHandler( "map", h );
1050 
1051   h = new MapRawHandler( *this, mapsize );
1052   h->SetEnabled( false );
1053   parser.AddHandler( "map-raw", h );
1054 
1055   parser.SetCallback( this );
1056   err = parser.Parse( file );
1057 
1058   if ( !err ) {
1059     if ( (parsed_sections & SECTION_MISSION) == 0 ) {
1060       cerr << "Error: Basic mission definitions missing" << endl;
1061       err = 1;
1062     }
1063     else if ( (parsed_sections & SECTION_MAP) == 0 ) {
1064       cerr << "Error: No map" << endl;
1065       err = 1;
1066     }
1067   }
1068 
1069   if ( !err && !messages.SetDefaultLanguage( CF_LANG_DEFAULT ) ) {
1070     cerr << "Error: No english language data found" << endl;
1071     err = 1;
1072   }
1073   if ( !err ) err = check_game();
1074   if ( !err ) err = check_shops();
1075   if ( !err ) err = check_units();
1076   if ( !err ) err = check_events();
1077   if ( !err ) err = check_player( p1 );
1078   if ( !err ) err = check_player( p2 );
1079 
1080   return err;
1081 }
1082 
1083 // mission parser callback called when a section has been
1084 // successfully parsed
SectionParsed(const string & section,SectionHandler & handler,CFParser & parser)1085 void MissionParser::SectionParsed( const string &section,
1086      SectionHandler &handler, CFParser &parser ) {
1087 
1088   if ( section == "mission" ) {
1089     parsed_sections |= SECTION_MISSION;
1090     handler.SetEnabled( false );
1091     parser.EnableHandler( "unit", true );
1092     parser.EnableHandler( "building", true );
1093     parser.EnableHandler( "event", true );
1094     parser.EnableHandler( "map", true );
1095     parser.EnableHandler( "map-raw", true );
1096   } else if ( section == "player" ) {
1097     if ( parsed_sections & SECTION_PLAYER1 ) {
1098       parsed_sections |= SECTION_PLAYER2;
1099       handler.SetEnabled( false );
1100     } else parsed_sections |= SECTION_PLAYER1;
1101   } else if ( (section == "map") || (section == "map-raw") ) {
1102     parsed_sections |= SECTION_MAP;
1103     parser.EnableHandler( "map", false );
1104     parser.EnableHandler( "map-raw", false );
1105   }
1106 }
1107 
1108 /* check game data for validity */
check_game(void) const1109 int MissionParser::check_game( void ) const {
1110 
1111   if ( (map.Width() == 0) || (map.Height() == 0) ) {
1112     cerr << "Error: map width or height is 0" << endl;
1113     return 1;
1114   }
1115 
1116   if ( GetName() == -1 ) {
1117     cerr << "Error: Level has not been given a name" << endl;
1118     return 1;
1119   } else if ( !GetMessage(GetName()) ) {
1120     cerr << "Error: Invalid mission name " << (short)GetName() << endl;
1121     return 1;
1122   } else {
1123     short num_msgs = -1, cnt;
1124 
1125     for ( std::map<const string, Language>::const_iterator it = messages.GetLibrary().begin();
1126           it != messages.GetLibrary().end(); ++it ) {
1127       const Language *lang = &it->second;
1128 
1129       // count messages
1130       for ( cnt = 0; lang->GetMsg(cnt); ++cnt ); // empty loop
1131       if ( num_msgs == -1 ) num_msgs = cnt;
1132       else if ( num_msgs != cnt ) {
1133         cerr << "Error: Language data for '" << lang->ID() << "' contains " << cnt << " messages." << endl
1134              << "       Previous languages had " << num_msgs << endl;
1135         return 1;
1136       }
1137 
1138       if ( strlen(lang->GetMsg(GetName())) > 30 ) {
1139         cerr << "Error (" << lang->ID() << "): Mission name must not exceed 30 characters" << endl;
1140         return 1;
1141       }
1142     }
1143   }
1144 
1145   if ( (GetLevelInfoMsg() != -1) && !GetMessage(GetLevelInfoMsg()) ) {
1146     cerr << "Error: Invalid level info message " << (short)GetLevelInfoMsg() << endl;
1147     return 1;
1148   }
1149   if ( (GetCampaignName() != -1) && !GetMessage(GetCampaignName()) ) {
1150     cerr << "Error: Invalid campaign name " << (short)GetCampaignName() << endl;
1151     return 1;
1152   }
1153   if ( (GetCampaignInfo() != -1) && !GetMessage(GetCampaignInfo()) ) {
1154     cerr << "Error: Invalid campaign info message " << (short)GetCampaignInfo() << endl;
1155     return 1;
1156   }
1157   if ( !IsCampaign() &&
1158      (GetSequel() || (GetCampaignName() != -1) || (GetCampaignInfo() != -1)) ) {
1159     cerr << "Error: Campaign settings found but map is not marked as a campaign map" << endl;
1160     return 1;
1161   }
1162 
1163   return 0;
1164 }
1165 
1166 /* check players */
check_player(const Player & p) const1167 int MissionParser::check_player( const Player &p ) const {
1168 
1169   short msg = p.NameID();
1170   if ( msg == -1 ) {
1171     cerr << "Error: No name set for player" << endl;
1172     return 1;
1173   } else {
1174     if ( !GetMessage(msg) ) {
1175       cerr << "Error: Invalid name " << msg << " for player" << endl;
1176       return 1;
1177     } else {
1178       for ( std::map<const string, Language>::const_iterator it = messages.GetLibrary().begin();
1179             it != messages.GetLibrary().end(); ++it ) {
1180         const Language *lang = &it->second;
1181 
1182         if ( strlen(lang->GetMsg(msg)) > 30 ) {
1183           cerr << "Error(" << lang->ID() << "): Player names must not be longer than 30 characters" << endl;
1184           return 1;
1185         }
1186       }
1187     }
1188   }
1189 
1190   msg = p.Briefing();
1191   if ( (msg != -1) && !GetMessage(msg) ) {
1192     cerr << "Error: Invalid briefing " << msg << " for player" << endl;
1193     return 1;
1194   }
1195 
1196   return 0;
1197 }
1198 
1199 /* check units */
check_units(void) const1200 int MissionParser::check_units( void ) const {
1201   Unit *u, *walk;
1202 
1203   /* if there is a transport at a unit's location put it in if possible */
1204   /* this only checks for transports appearing before the actual unit in the file */
1205   for ( u = static_cast<Unit *>(units.Head()); u; u = static_cast<Unit *>(u->Next()) ) {
1206 
1207     if ( (u->Position().x >= map.Width()) || (u->Position().y >= map.Height()) ||
1208          (u->Position().x < 0) || (u->Position().y < 0) ) {
1209       cerr << "Error: unit (ID: " << u->ID() << ") out of map" << endl;
1210       return 1;
1211     }
1212 
1213     if ( u->IsTransport() && !u->IsSheltered() ) {
1214 
1215       if ( StorageLeft( *u ) < 0 ) {
1216         cerr << "Error: Unit " << u->Name() << " at "
1217                   << u->Position().x << '/' << u->Position().y
1218                   << " carries too many units or crystals" << endl;
1219         return 1;
1220       }
1221 
1222       for ( walk = static_cast<Unit *>(u->Next()); walk; walk = static_cast<Unit *>(walk->Next()) ) {
1223 
1224         if ( walk->Position() == u->Position() ) {
1225           if ( walk->Weight() > u->MaxWeight() ) {
1226             cerr << "Error: Unit " << walk->Name() << " too heavy for transport at "
1227                       << u->Position().x << '/' << u->Position().y << endl;
1228             return 1;
1229           } else if ( walk->Weight() < u->MinWeight() ) {
1230             cerr << "Error: Unit " << walk->Name() << " too light for transport at "
1231                       << u->Position().x << '/' << u->Position().y << endl;
1232             return 1;
1233           } else walk->SetFlags( U_SHELTERED );
1234         }
1235       }
1236     }
1237 
1238     if ( u->Crystals() && !u->IsTransport() )
1239       cerr << "Error: non-transport unit (ID: " << u->ID() << ") cannot carry crystals" << endl;
1240 
1241     for ( walk = static_cast<Unit *>(u->Next()); walk; walk = static_cast<Unit *>(walk->Next()) ) {
1242       if ( u->ID() == walk->ID() ) {
1243         cerr << "Error: Two or more units sharing one ID (" << u->ID() << ')' << endl;
1244         return 1;
1245       }
1246 
1247       if ( (u->Position() == walk->Position()) &&
1248            (!(u->IsSheltered() || u->IsTransport()) || !walk->IsSheltered()) ) {
1249         cerr << "Error: Two or more units sharing one hex (" << u->Position().x << '/'
1250                   << u->Position().y << ')' << endl;
1251 
1252         if ( walk->IsTransport() )
1253           cerr << "       This might be caused by a transport being declared after a unit it carries." << endl;
1254         return 1;
1255       }
1256     }
1257 
1258     if ( (u->Owner() == PLAYER_ONE) || (u->Owner() == PLAYER_TWO) ) {
1259       if ( u->GetDirection() == 99 ) u->SetDirection( u->Owner() * 3 );    /* direction not set, use defaults */
1260       else if ( u->GetDirection() > NORTHWEST ) {
1261         cerr << "Error: Invalid direction " << (short)u->GetDirection() << " for unit (ID: "
1262                   << u->ID() << ')' << endl;
1263         return 1;
1264       }
1265     } else {
1266       u->SetDirection( 0 );
1267       if ( !(u->Owner() == PLAYER_NONE) || !u->IsSheltered() ) {
1268         cerr << "Error: unit with ID " << u->ID() << " has no legal owner" << endl;
1269         return 1;
1270       }
1271     }
1272 
1273     if ( (u->GroupSize() > MAX_GROUP_SIZE) || (u->GroupSize() == 0) ) {
1274       cerr << "Error: unit with ID " << u->ID() << " has size " << (short)u->GroupSize() << endl;
1275       return 1;
1276     }
1277 
1278     if ( u->XPLevel() > XP_MAX_LEVEL ) {
1279       cerr << "Error: unit with ID " << u->ID() << " has been promoted to XP level " << (short)u->XPLevel()
1280            << ", maximum is " << XP_MAX_LEVEL << endl;
1281       return 1;
1282     }
1283   }
1284   return 0;
1285 }
1286 
1287 /* check buildings */
check_shops(void) const1288 int MissionParser::check_shops( void ) const {
1289   Building *b, *walk;
1290   Unit *u;
1291 
1292   for ( b = static_cast<Building *>(buildings.Head()); b; b = static_cast<Building *>(b->Next()) ) {
1293 
1294     if ( (b->Position().x >= map.Width()) || (b->Position().y >= map.Height()) ) {
1295       cerr << "Error: Shop (" << b->Position().x << '/' << b->Position().y
1296                 << ") out of map" << endl;
1297       return 1;
1298     }
1299 
1300     if ( !(map.TerrainTypes( b->Position() ) & TT_ENTRANCE) ) {
1301       cerr << "Error: Map does not have a shop entrance at " << b->Position().x
1302                 << '/' << b->Position().y << endl;
1303       return 1;
1304     }
1305 
1306     if ( b->NameID() == -1 ) {
1307       cerr << "Error: No name set for shop " << b->ID() <<  endl;
1308       return 1;
1309     } else if ( !GetMessage(b->NameID()) ) {
1310       cerr << "Error: Invalid name " << (short)b->NameID() << " for shop " << b->ID() <<  endl;
1311       return 1;
1312     } else {
1313       for ( std::map<const string, Language>::const_iterator it = messages.GetLibrary().begin();
1314             it != messages.GetLibrary().end(); ++it ) {
1315         const Language *lang = &it->second;
1316 
1317         if ( strlen(lang->GetMsg(b->NameID())) > 30 ) {
1318           cerr << "Error(" << lang->ID() << "): Shop names must not be longer than 30 characters" << endl;
1319           return 1;
1320         }
1321       }
1322     }
1323 
1324     if ( b->Crystals() > b->MaxCrystals() ) {
1325       cerr << "Error: Shop at " << b->Position().x << '/' << b->Position().y
1326                 << " contains " << b->Crystals() << " crystals, but only "
1327                 << b->MaxCrystals() << " fit in" << endl;
1328       return 1;
1329     }
1330 
1331     if ( b->MinWeight() > b->MaxWeight() ) {
1332       cerr << "Error: Shop at " << b->Position().x << '/' << b->Position().y
1333                 << " has minweight (" << (short)b->MinWeight() << ") > maxweight ("
1334                 << b->MaxWeight() << ')' << endl;
1335       return 1;
1336     }
1337 
1338 
1339     for ( walk = static_cast<Building *>(b->Next()); walk; walk = static_cast<Building *>(walk->Next()) ) {
1340       if ( b->ID() == walk->ID() ) {
1341         cerr << "Error: Two shops sharing ID " << b->ID() << endl;
1342         return 1;
1343       }
1344 
1345       if ( b->Position() == walk->Position() ) {
1346         cerr << "Error: Two shops sharing one hex ("
1347                   << b->Position().x << '/' << b->Position().y << ')' << endl;
1348         return 1;
1349       }
1350     }
1351 
1352     for ( u = static_cast<Unit *>(units.Head()); u; u = static_cast<Unit *>(u->Next()) ) {
1353       if ( b->Position() == u->Position() ) {
1354         if ( (u->Owner() != b->Owner()) && (b->Owner() != PLAYER_NONE) ) {
1355           cerr << "Error: Hostile unit (" << u->ID() << ") in building ("
1356                     << b->Position().x << '/' << b->Position().y << ')' << endl;
1357           return 1;
1358         } else u->SetOwner( b->Owner() );
1359 
1360         if ( u->Weight() < b->MinWeight() ) {
1361           cerr << "Error: Unit (" << u->ID() << ") too light for building at "
1362 	            << b->Position().x << '/' << b->Position().y << endl;
1363           return 1;
1364        } else if ( u->Weight() > b->MaxWeight() ) {
1365           cerr << "Error: Unit (" << u->ID() << ") too heavy for building at "
1366 	            << b->Position().x << '/' << b->Position().y << endl;
1367           return 1;
1368         }
1369 
1370         u->SetFlags( U_SHELTERED );
1371       }
1372     }
1373   }
1374   return 0;
1375 }
1376 
1377 /* check events for consistency */
check_events(void) const1378 int MissionParser::check_events( void ) const {
1379   Event *e;
1380   Building *b;
1381   Unit *u;
1382   short msg;
1383 
1384   for ( e = static_cast<Event *>(events.Head()); e; e = static_cast<Event *>(e->Next()) ){
1385 
1386     if ( (e->Player() != PLAYER_ONE) && (e->Player() != PLAYER_TWO) ) {
1387       cerr << "Error: Event " << (short)e->ID() << " has no player assigned" << endl;
1388       return 1;
1389     }
1390 
1391     msg = e->Message();
1392     if ( (msg != -1) && !messages.GetMsg(msg) ) {
1393       cerr << "Error: Event " << (short)e->ID() << " has invalid message '" << msg << "'" << endl;
1394       return 1;
1395     }
1396     msg = e->Title();
1397     if ( (msg != -1) && !messages.GetMsg(msg) ) {
1398       cerr << "Error: Event " << (short)e->ID() << " has invalid title '" << msg << "'" << endl;
1399       return 1;
1400     }
1401 
1402     /* check dependencies */
1403     if ( e->Dependency() != -1 ) {
1404       if ( !GetEventByID( e->Dependency() ) ) {
1405         cerr << "Error: Event " << (short)e->ID() << " depends on non-existing event "
1406                   << (short)e->Dependency() << endl;
1407         return 1;
1408       }
1409     }
1410 
1411     if ( e->Discard() != -1 ) {
1412       if ( !GetEventByID( e->Discard() ) ) {
1413         cerr << "Error: Event " << (short)e->ID() << " discards non-existing event "
1414                   << (short)e->Discard() << endl;
1415         return 1;
1416       }
1417     }
1418 
1419     /* check ID's */
1420     if ( find_dups( events, e ) ) {
1421       cerr << "Error: Two or more events with the same ID '" << (short)e->ID() << "'" << endl;
1422       return 1;
1423     }
1424 
1425 
1426     switch ( e->Type() ) {
1427       case EVENT_CREATE_UNIT:
1428         if ( e->GetData(0) < 0 ) {
1429           cerr << "Error: No unit type specified for Create Unit event " << (short)e->ID() << endl;
1430           return 1;
1431         }
1432 
1433         if ( map.Index2Hex(e->GetData(1)) == Point(-1, -1) ) {
1434           cerr << "Error: Invalid location for event " << (short)e->ID() << endl;
1435           return 1;
1436         }
1437 
1438         {
1439           unsigned short cu_dir = e->GetData(2) & 0x0007;
1440           unsigned short cu_size = (e->GetData(2) & 0x0038) >> 3;
1441           unsigned short cu_xp = (e->GetData(2) & 0x01C0) >> 6;
1442 	  if ( cu_dir > NORTHWEST ) {
1443             cerr << "Error: Invalid direction " << cu_dir << " for event " << (short)e->ID() << endl;
1444             return 1;
1445           }
1446 
1447 	  if ( (cu_size == 0) || (cu_size > MAX_GROUP_SIZE) ) {
1448             cerr << "Error: Invalid unit size " << cu_size << " for event " << (short)e->ID() << endl;
1449             return 1;
1450           }
1451 
1452 	  if ( cu_xp > XP_MAX_LEVEL ) {
1453             cerr << "Error: Invalid unit experience level " << cu_xp << " for event " << (short)e->ID() << endl;
1454             return 1;
1455           }
1456         }
1457         break;
1458       case EVENT_DESTROY_UNIT:
1459         if ( e->GetData(0) < 0 ) {
1460           if (e->GetData(2) == -9999) {
1461             cerr << "Error: No target specified for Destroy Unit event " << (short)e->ID() << endl;
1462             return 1;
1463           }
1464           e->SetData(0, -1);
1465         } else {
1466 
1467           if ( !GetUnitByID( e->GetData(0) ) ) {
1468             cerr << "Error: Unit with ID " << e->GetData(0) << " specified in event "
1469                  << (short)e->ID() << " does not exist" << endl;
1470             return 1;
1471           }
1472           e->SetData(2, 0);
1473         }
1474 
1475         if ( e->GetData(1) < -1 ) e->SetData(1, -1);
1476         else if ( e->GetData(1) > PLAYER_TWO ) {
1477           cerr << "Error: Invalid unit owner for event " << (short)e->ID() << endl;
1478           return 1;
1479         }
1480         break;
1481       case EVENT_MANIPULATE_EVENT:
1482         if ( e->GetData(0) < 0 ) {
1483           cerr << "Error: Event manipulation event " << (short)e->ID() << " without valid target" << endl;
1484           return 1;
1485         }
1486 
1487         /* for now, this event can only enable/disable other events */
1488         e->SetData(1, EFLAG_DISABLED);
1489         if ( e->GetData(1) == 0 ) {
1490           cerr << "Error: Event manipulation (" << (short)e->ID() << ") with invalid flags 0" << endl;
1491           return 1;
1492         }
1493 
1494         if ( (e->GetData(2) < 0) || (e->GetData(2) > 2) ) {
1495           cerr << "Error: Event manipulation (" << (short)e->ID() << ") with invalid action "
1496                     << e->GetData(2) << endl;
1497           return 1;
1498         }
1499 
1500         if ( !GetEventByID( e->GetData(0) ) ) {
1501           cerr << "Error: Event with ID " << e->GetData(0) << " does not exist" << endl;
1502           return 1;
1503         }
1504         break;
1505       case EVENT_MESSAGE:
1506         if ( e->Message() == -1 ) {
1507           cerr << "Error: Message event " << (short)e->ID() << " has no message" << endl;
1508           return 1;
1509         }
1510 
1511         if ( map.Index2Hex( e->GetData(0) ) == Point(-1,-1) ) e->SetData(0, -1);
1512         else if ( !map.Contains( map.Index2Hex( e->GetData(0) ) ) ) {
1513           cerr << "Error: Invalid location set for message event (" << (short)e->ID() << ')' << endl;
1514           return 1;
1515         }
1516 
1517         e->SetData(1, 0);
1518         e->SetData(2, 0);
1519         break;
1520       case EVENT_MINING:
1521         if ( e->GetData(0) < 0 ) {
1522           cerr << "Error: Mining event " << (short)e->ID() << " with no shop" << endl;
1523           return 1;
1524         }
1525 
1526         if ( (e->GetData(2) < 0) || (e->GetData(2) > 3) ) {
1527           cerr << "Error: Mining event " << (short)e->ID() << " with invalid action "
1528                     << e->GetData(2) << endl;
1529           return 1;
1530         }
1531 
1532         if ( ((e->GetData(2) == 0) || (e->GetData(2) == 2)) && (e->GetData(1) < 0) ) {
1533           cerr << "Error: Trying to set negative absolute amount for mining event "
1534                     << (short)e->ID() << endl;
1535           return 1;
1536         }
1537 
1538         if ( !GetBuildingByID(e->GetData(0)) ) {
1539           cerr << "Error: Shop with ID " << e->GetData(0) << " does not exist (event "
1540                     << (short)e->ID() << ')' << endl;
1541           return 1;
1542         }
1543         break;
1544       case EVENT_CONFIGURE:
1545         if ( e->GetData(0) < -9999 ) {
1546           cerr << "Error: Configure event " << (short)e->ID() << " does not specify setting" << endl;
1547           return 1;
1548         }
1549 
1550         if ( (e->GetData(0) == 0) || (e->GetData(0) == 1) ) {
1551           msg = e->GetData(1);
1552           if ( (msg != -1) && !messages.GetMsg(msg) ) {
1553             cerr << "Error: Event " << (short)e->ID() << " references invalid message '" << msg << "'" << endl;
1554             return 1;
1555           }
1556         }
1557         e->SetData(2, 0);
1558         break;
1559       case EVENT_RESEARCH:
1560         if ( e->GetData(0) < 0 ) {
1561           cerr << "Error: Research event " << (short)e->ID() << " with no shop" << endl;
1562           return 1;
1563         }
1564 
1565         if ( e->GetData(2) == -9999 ) e->SetData(2, 0);
1566         else if ( (e->GetData(2) < 0) || (e->GetData(2) > 1) ) {
1567           cerr << "Error: Research event " << (short)e->ID() << " specifies invalid action " << e->GetData(2) << endl;
1568           return 1;
1569         }
1570 
1571         if ( e->GetData(1) < 0 ) {
1572           cerr << "Error: Research event " << (short)e->ID() << " with no unit type" << endl;
1573           return 1;
1574         }
1575 
1576         if ( !GetBuildingByID(e->GetData(0)) ) {
1577           cerr << "Error: Shop with ID " << e->GetData(0) << " does not exist (event "
1578                     << (short)e->ID() << ')' << endl;
1579           return 1;
1580         }
1581         break;
1582       case EVENT_SCORE:
1583         if ( e->GetData(0) < 0 ) {
1584           cerr << "Warning: Corrected success rate for score event < 0" << endl;
1585           e->SetData(0, 0);
1586         }
1587 
1588         if ( e->GetData(1) < -1 ) e->SetData(1, -1);
1589         msg = e->GetData(1);
1590         if ( (msg != -1) && !messages.GetMsg(msg) ) {
1591           cerr << "Error: Event " << (short)e->ID() << " references invalid message '" << msg << "'" << endl;
1592           return 1;
1593         }
1594 
1595         if ( e->GetData(2) < -1 ) e->SetData(2, -1);
1596         msg = e->GetData(2);
1597         if ( (e->GetData(1) != -1) && (msg != -1) && !messages.GetMsg(msg) ) {
1598           cerr << "Error: Event " << (short)e->ID() << " references invalid message '" << msg << "'" << endl;
1599           return 1;
1600         } else if ( (e->GetData(1) == -1) && (msg != -1) ) {
1601           cerr << "Warning: Event " << (short)e->ID() << " set a title but no message" << endl;
1602           e->SetData(2, -1);
1603         }
1604         break;
1605       case EVENT_SET_HEX:
1606         if ( e->GetData(0) < 0 ) {
1607           cerr << "Error: No tile specified for Set Hex event " << (short)e->ID() << endl;
1608           return 1;
1609         }
1610 
1611         if ( map.Index2Hex( e->GetTData(1) ) == Point(-1,-1) ) e->SetTData(1, -1);
1612         else if ( !map.Contains( map.Index2Hex( e->GetTData(1) ) ) ) {
1613           cerr << "Error: Invalid location set for event trigger (" << (short)e->ID() << ')' << endl;
1614           return 1;
1615         }
1616         break;
1617       case EVENT_SET_TIMER:
1618         if ( e->GetData(0) < 0 ) {
1619           cerr << "Error: Set Timer event " << (short)e->ID() << " without valid target" << endl;
1620           return 1;
1621         } else {
1622           Event *tev = GetEventByID( e->GetData(0) );
1623           if ( !tev ) {
1624             cerr << "Error: Event with ID " << e->GetData(0) << " does not exist (event "
1625                  << (short)e->ID() << ")" << endl;
1626             return 1;
1627           } else if ( tev->Trigger() != ETRIGGER_TIMER ) {
1628             cerr << "Error: Event with ID " << e->GetData(0) << " has no timer trigger (event "
1629                  << (short)e->ID() << ")" << endl;
1630             return 1;
1631           }
1632         }
1633 
1634         if ( e->GetData(1) < 0 ) {
1635           cerr << "Error: Set Timer (" << (short)e->ID() << ") with invalid time "
1636                     << e->GetData(1) << endl;
1637           return 1;
1638         }
1639 
1640         if ( (e->GetData(2) < 0) || (e->GetData(2) > 2) ) {
1641           cerr << "Error: Set Timer (" << (short)e->ID() << ") with invalid offset "
1642                     << e->GetData(2) << endl;
1643           return 1;
1644         }
1645         break;
1646       default:
1647           cerr << "Error: Event with ID " << (short)e->ID() << " has invalid type" << endl;
1648     }
1649 
1650     switch ( e->Trigger() ) {
1651       case ETRIGGER_TIMER:
1652         if ( e->GetTData(0) < 0 ) {
1653           cerr << "Error: Event trigger lacks time (" << (short)e->ID() << ')' << endl;
1654           return 1;
1655         }
1656         e->SetTData(1, 0);
1657         e->SetTData(2, 0);
1658         break;
1659       case ETRIGGER_UNIT_DESTROYED:
1660         if ( e->GetTData(0) >= 0 ) {
1661           u = GetUnitByID( e->GetTData(0) );
1662 
1663           /* the event must also be triggered when the unit is not
1664              destroyed but captured by the enemy. Therefore we need
1665              the original owner */
1666           if (u) e->SetTData(1, u->Owner());
1667           else {
1668             cerr << "Error: Event trigger targets non-existing unit with ID " << e->GetTData(0) << endl;
1669             return 1;
1670           }
1671         } else {
1672           if ( -e->GetTData(0) - 2 >= unit_set->NumTiles() ) {
1673             cerr << "Error: Event trigger targets non-existing unit type " << -e->GetTData(0) - 2
1674                  << " (" << (short)e->ID() << ')' << endl;
1675             return 1;
1676           }
1677           if ( e->GetTData(1) == -9999 ) e->SetTData(1, e->Player()^1);
1678         }
1679         e->SetTData(2, 0);
1680         break;
1681       case ETRIGGER_HAVE_UNIT:
1682         if ( (e->GetTData(1) != PLAYER_ONE) && (e->GetTData(1) != PLAYER_TWO) ) {
1683           cerr << "Error: Event trigger wants invalid player to own a unit ("
1684                << (short)e->ID() << endl;
1685           return 1;
1686         }
1687         if ( e->GetTData(2) < 0 ) e->SetTData(2, -1);
1688 
1689         u = GetUnitByID( e->GetTData(0) );
1690 
1691         if (u) {
1692           if ( (u->Owner() == e->GetTData(1)) && (e->GetTData(2) < 0) && (e->Dependency() == -1) ) {
1693             cerr << "Error: Event trigger: unit " << u->ID() << " is already owned by player "
1694                  << u->Owner()+1 << '(' << (short)e->ID() << ')' << endl;
1695               return 1;
1696             }
1697         } else {
1698           cerr << "Error: Event trigger targets non-existing unit " << e->GetTData(0)
1699                << " (" << (short)e->ID() << ')' << endl;
1700           return 1;
1701         }
1702         break;
1703       case ETRIGGER_HAVE_BUILDING:
1704         if ( (e->GetTData(1) != PLAYER_ONE) && (e->GetTData(1) != PLAYER_TWO) ) {
1705           cerr << "Error: Event trigger wants invalid player to own a shop ("
1706                << (short)e->ID() << endl;
1707           return 1;
1708         }
1709         if ( e->GetTData(2) < 0 ) e->SetTData(2, -1);
1710 
1711         b = GetBuildingByID( e->GetTData(0) );
1712 
1713         if (b) {
1714           if ( (b->Owner() == e->GetTData(1)) && (e->GetTData(2) < 0) && (e->Dependency() == -1) ) {
1715             cerr << "Error: Event trigger: shop " << b->ID() << " is already owned by player "
1716                  << b->Owner()+1 << '(' << (short)e->ID() << ')' << endl;
1717               return 1;
1718             }
1719         } else {
1720           cerr << "Error: Event trigger targets non-existing shop " << e->GetTData(0)
1721                << " (" << (short)e->ID() << ')' << endl;
1722           return 1;
1723         }
1724         break;
1725       case ETRIGGER_HAVE_CRYSTALS:
1726         if ( e->GetTData(0) == -9999 ) {
1727           cerr << "Error: Crystals trigger does not specify amount (" << (short)e->ID() << ")" << endl;
1728           return 1;
1729         } else if ( ABS(e->GetTData(0)) > 5000 ) {
1730           cerr << "Error: Invalid crystals amount " << e->GetTData(0) << " for event trigger ("
1731                << (short)e->ID() << ")" << endl;
1732           return 1;
1733         }
1734 
1735         if ( e->GetTData(1) == -9999 ) e->SetTData(1, e->Player());
1736         else if ( (e->GetTData(1) != PLAYER_ONE) && (e->GetTData(1) != PLAYER_TWO) ) {
1737           cerr << "Error: Event trigger wants invalid player to own a shop ("
1738                << (short)e->ID() << ")" << endl;
1739           return 1;
1740         }
1741 
1742         if ( e->GetTData(2) < -2 ) e->SetTData(2, -1);
1743         else if ( e->GetTData(2) >= 0 ) {
1744           if ( !GetBuildingByID( e->GetTData(2) ) ) {
1745             cerr << "Error: Event trigger targets non-existing shop " << e->GetTData(2)
1746                  << " (" << (short)e->ID() << ')' << endl;
1747             return 1;
1748           }
1749         }
1750         break;
1751       case ETRIGGER_UNIT_POSITION:
1752         if ( map.Index2Hex( e->GetTData(1) ) == Point(-1,-1) ) {
1753           cerr << "Error: Invalid location set for event trigger (" << (short)e->ID() << ')' << endl;
1754           return 1;
1755         }
1756 
1757         if ( e->GetTData(0) < -1 ) {
1758           if ( -e->GetTData(0) - 2 >= unit_set->NumTiles() ) {
1759             cerr << "Error: Event trigger targets non-existing unit type " << -e->GetTData(0) - 2
1760                  << " (" << (short)e->ID() << ')' << endl;
1761             return 1;
1762           }
1763         } else if ( e->GetTData(0) >= 0 ) {
1764           if (!GetUnitByID( e->GetTData(0) )) {
1765             cerr << "Error: Event trigger targets non-existing unit " << e->GetTData(0)
1766                  << " (" << (short)e->ID() << ')' << endl;
1767             return 1;
1768           }
1769         }
1770 
1771         if ( e->GetTData(2) == -9999 ) e->SetTData( 2, e->Player() );
1772         else if ( (e->GetTData(2) != PLAYER_ONE) && (e->GetTData(2) != PLAYER_TWO) ) {
1773           cerr << "Error: Event trigger wants invalid player to control a unit ("
1774                << (short)e->ID() << endl;
1775           return 1;
1776         }
1777         break;
1778       case ETRIGGER_HANDICAP:
1779         if ( e->GetTData(0) == -9999 ) {
1780           cerr << "Error: Handicap event trigger without a handicap ("
1781                << (short)e->ID() << ')' << endl;
1782           return 1;
1783         } else if ( e->GetTData(0) & 0xFFF8 ) {
1784           cerr << "Error: Invalid handicap " << e->GetTData(0) << " ("
1785                << (short)e->ID() << ')' << endl;
1786           return 1;
1787         }
1788         e->SetTData(1, 0);
1789         e->SetTData(2, 0);
1790         break;
1791       default:
1792         cerr << "Error: Invalid event trigger type " << (short)e->Trigger() << " ("
1793              << (short)e->ID() << ')' << endl;
1794         return 1;
1795     }
1796   }
1797   return 0;
1798 }
1799 
1800 /* load a unit set */
load_unit_set(const char * set)1801 int MissionParser::load_unit_set( const char *set ) {
1802   string setshort( set );
1803 
1804   // keep only the file part; check for both Unix and Windows path
1805   // separator characters or this breaks when building with MinGW
1806   setshort = setshort.substr( setshort.find_last_of( "/\\" ) + 1 );
1807 
1808   size_t pos = setshort.find( ".units" );
1809   if ( pos != string::npos ) setshort.erase( pos );
1810   File file( set );
1811   if ( !file.Open("rb") ) return -1;
1812 
1813   unit_set = new UnitSet();
1814   if ( unit_set->Load( file, setshort.c_str() ) == -1 ) {
1815     delete unit_set;
1816     unit_set = 0;
1817     return -1;
1818   }
1819 
1820   map.SetUnitSet( unit_set );
1821   return 0;
1822 }
1823 
1824 /* load a terrain set */
load_tile_set(const char * set)1825 int MissionParser::load_tile_set( const char *set ) {
1826   string setshort( set );
1827 
1828   // keep only the file part; check for both Unix and Windows path
1829   // separator characters or this breaks when building with MinGW
1830   setshort = setshort.substr( setshort.find_last_of( "/\\" ) + 1 );
1831 
1832   size_t pos = setshort.find( ".tiles" );
1833   if ( pos != string::npos ) setshort.erase( pos );
1834   File file( set );
1835   if ( !file.Open("rb") ) return -1;
1836 
1837   terrain_set = new TerrainSet();
1838   if ( terrain_set->Load( file, setshort.c_str() ) == -1 ) {
1839     delete terrain_set;
1840     return -1;
1841   }
1842 
1843   map.SetTerrainSet( terrain_set );
1844   return 0;
1845 }
1846 
MissionParser(void)1847 MissionParser::MissionParser( void ) {
1848   SetLevelInfoMsg( -1 );
1849   SetCampaignName( -1 );
1850   SetCampaignInfo( -1 );
1851   SetTitle( "Unknown" );
1852 }
1853 
1854