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 §ion,
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 §ion,
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