1 // Crimson Fields -- a game of tactical warfare
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 ////////////////////////////////////////////////////////////////////////
20 // mission.cpp
21 ////////////////////////////////////////////////////////////////////////
22 
23 #include <iostream>
24 
25 #include "mission.h"
26 #include "fileio.h"
27 #include "strutil.h"
28 #include "globals.h"
29 
30 ////////////////////////////////////////////////////////////////////////
31 // NAME       : Mission::~Mission
32 // DESCRIPTION: Free mission resources.
33 // PARAMETERS : -
34 // RETURNS    : -
35 ////////////////////////////////////////////////////////////////////////
36 
~Mission(void)37 Mission::~Mission( void ) {
38   delete history;
39 }
40 
41 ////////////////////////////////////////////////////////////////////////
42 // NAME       : Mission::Load
43 // DESCRIPTION: Load a game from a mission file.
44 // PARAMETERS : file - mission file
45 // RETURNS    : 0 on success, non-null otherwise
46 ////////////////////////////////////////////////////////////////////////
47 
Load(MemBuffer & file)48 int Mission::Load( MemBuffer &file ) {
49   int rc = -1;
50 
51   // read game info
52   if ( (file.Read32() == FID_MISSION) && (file.Read8() == FILE_VERSION) ) {
53     unsigned short len, i;
54     string uset, tset;
55 
56     flags = file.Read16();
57     turn = file.Read16();
58 
59     name = file.Read8();
60     level_info = file.Read8();
61     campaign_name = file.Read8();
62     campaign_info = file.Read8();
63     next_map = file.Read8();
64     music = file.Read8();
65     handicap = file.Read8();
66     current_player = file.Read8();
67     turn_phase = file.Read8();
68 
69     p1.Load( file );
70     p2.Load( file );
71 
72     messages.Load( file );
73 
74     map.Load( file );               // load map
75 
76     len = file.Read16();            // load name of unit set
77     uset = file.ReadS( len );
78 
79     File ufile( uset + ".units" );
80     if ( !ufile.OpenData("rb") || unit_set.Load( ufile, uset.c_str() ) ) {
81       cerr << "Error: Unit set '" << uset << "' not available" << endl;
82       return -1;
83     }
84     ufile.Close();
85 
86     len = file.Read16();            // load name of terrain set
87     tset = file.ReadS( len );
88 
89     File tfile( tset + ".tiles" );
90     if ( !tfile.OpenData("rb") || terrain_set.Load( tfile, tset.c_str() ) ) {
91       cerr << "Error: Terrain set '" << tset << "' not available" << endl;
92       return -1;
93     }
94     tfile.Close();
95 
96     map.SetUnitSet( &unit_set );
97     map.SetTerrainSet( &terrain_set );
98 
99     len = file.Read16();         // load shops
100     for ( i = 0; i < len; ++i ) {
101       Building *b = new Building();
102       short pid = b->Load( file );
103       b->SetOwner( pid == PLAYER_NONE ? 0 : &GetPlayer( pid ), false );
104       shops.AddTail( b );
105       map.SetBuilding( b, b->Position() );
106     }
107 
108     len = file.Read16();         // load units
109     for ( i = 0; i < len; ++i ) {
110       Unit *u = LoadUnit( file );
111       if ( u ) {
112         units.AddTail( u );
113         map.SetUnit( u, u->Position() );
114       }
115     }
116 
117     len = file.Read16();             // load battles
118     for ( i = 0; i < len; ++i ) {    // only present in saved games
119       Combat *combat = new Combat();
120       combat->Load( file, *this );
121       battles.AddTail( combat );
122     }
123 
124     len = file.Read16();            // load events
125     for ( i = 0; i < len; ++i ) {
126       Event *e = new Event();
127       short pid = e->Load( file );
128       e->SetPlayer( GetPlayer( pid ) );
129       events.AddTail( e );
130     }
131 
132     internal_messages.ReadCatalog( file );
133 
134     history = new History();
135     if ( history->Load( file, *this ) == 0 ) {
136       if ( GetPlayer(current_player^1).IsHuman() ) {
137         history->StartRecording( GetUnits() );
138       } else {
139         delete history;
140         history = 0;
141       }
142     }
143 
144     rc = 0;
145   } else
146     cerr << "Warning: invalid header or version" << endl;
147 
148   return rc;
149 }
150 
151 ////////////////////////////////////////////////////////////////////////
152 // NAME       : Mission::LoadUnit
153 // DESCRIPTION: Load a unit from a file.
154 // PARAMETERS : file  - file descriptor
155 //              dummy - if true don't create transporters (used by History)
156 // RETURNS    : pointer to loaded unit or NULL on error
157 ////////////////////////////////////////////////////////////////////////
158 
LoadUnit(MemBuffer & file,bool dummy)159 Unit *Mission::LoadUnit( MemBuffer &file, bool dummy /* = false */ ) {
160   unsigned char tid, pid;
161   const UnitType *type;
162   Player *p = 0;
163   Unit *u;
164 
165   tid = file.Read8();
166   pid = file.Read8();
167 
168   if ( pid != PLAYER_NONE ) p = &GetPlayer( pid );
169   type = unit_set.GetUnitInfo( tid );
170 
171   if ( (type->Flags() & U_TRANSPORT) && !dummy ) u = new Transport();
172   else u = new Unit();
173 
174   u->Load( file, type, p );
175   return u;
176 }
177 
178 ////////////////////////////////////////////////////////////////////////
179 // NAME       : Mission::QuickLoad
180 // DESCRIPTION: Load parts of a game from a mission file. This retrieves
181 //              only the information required for the map selection
182 //              lists.
183 // PARAMETERS : file - mission file
184 // RETURNS    : 0 on success, non-null otherwise
185 ////////////////////////////////////////////////////////////////////////
186 
QuickLoad(MemBuffer & file)187 int Mission::QuickLoad( MemBuffer &file ) {
188   int rc = -1;
189 
190   // read game info
191   if ( (file.Read32() == FID_MISSION) && (file.Read8() == FILE_VERSION) ) {
192     flags = file.Read16();
193     turn = file.Read16();
194 
195     name = file.Read8();
196     level_info = file.Read8();
197     campaign_name = file.Read8();
198     campaign_info = file.Read8();
199     next_map = file.Read8();
200     music = file.Read8();
201     handicap = file.Read8();
202     current_player = file.Read8();
203     turn_phase = file.Read8();
204 
205     p1.Load( file );
206     p2.Load( file );
207 
208     messages.Load( file );
209 
210     rc = 0;
211   }
212 
213   return rc;
214 }
215 
216 ////////////////////////////////////////////////////////////////////////
217 // NAME       : Mission::Save
218 // DESCRIPTION: Save the mission to a file.
219 // PARAMETERS : file - data file
220 // RETURNS    : 0 on successful write, non-zero otherwise
221 ////////////////////////////////////////////////////////////////////////
222 
Save(MemBuffer & file)223 int Mission::Save( MemBuffer &file ) {
224   // save game info
225   file.Write32( FID_MISSION );
226   file.Write8( FILE_VERSION );
227 
228   file.Write16( flags );
229   file.Write16( turn );
230   file.Write8( name );
231   file.Write8( level_info );
232   file.Write8( campaign_name );
233   file.Write8( campaign_info );
234   file.Write8( next_map );
235   file.Write8( music );
236   file.Write8( handicap );
237   file.Write8( current_player );
238   file.Write8( turn_phase );
239 
240   p1.Save( file );      // save player data
241   p2.Save( file );
242 
243   messages.Save( file );
244 
245   map.Save( file );     // save map
246 
247   // save mission set info
248   file.Write16( unit_set.GetName().length() );
249   file.WriteS( unit_set.GetName() );
250   file.Write16( terrain_set.GetName().length() );
251   file.WriteS( terrain_set.GetName() );
252 
253   // save shops
254   file.Write16( shops.CountNodes() );
255   for ( Building *b = static_cast<Building *>(shops.Head());
256         b; b = static_cast<Building *>(b->Next()) ) b->Save( file );
257 
258   // save transports; basically, transports are not much different
259   // from other units but we MUST make sure that transports having
260   // other units on board are loaded before those units; we need to
261   // make two passes through the list, first saving all unsheltered
262   // units (which are possibly transports carrying other units), and
263   // all sheltered units in the second run
264   file.Write16( units.CountNodes() );
265   const Unit *u;
266   for ( u = static_cast<Unit *>(units.Head());
267         u; u = static_cast<Unit *>(u->Next()) ) {  // make sure transports are
268     if ( !u->IsSheltered() ) u->Save( file );      // stored before carried units
269   }
270 
271   for ( u = static_cast<Unit *>(units.Head());
272         u; u = static_cast<Unit *>(u->Next()) ) {
273     if ( u->IsSheltered() ) u->Save( file );
274   }
275 
276   // save combat data
277   file.Write16( battles.CountNodes() );
278   for ( Combat *com = static_cast<Combat *>(battles.Head());
279         com; com = static_cast<Combat *>(com->Next()) )
280     com->Save( file );
281 
282   // save events
283   file.Write16( events.CountNodes() );
284   for ( Event *ev = static_cast<Event *>(events.Head());
285         ev; ev = static_cast<Event *>(ev->Next()) )
286     ev->Save( file );
287 
288   internal_messages.WriteCatalog( file );
289 
290   if ( history ) history->Save( file );         // save turn history
291   else file.Write16( 0 );
292   return 0;
293 }
294 
295 ////////////////////////////////////////////////////////////////////////
296 // NAME       : Mission::CreateUnit
297 // DESCRIPTION: Create a new unit.
298 // PARAMETERS : type  - unit type identifier
299 //              p     - controlling player
300 //              pos   - position on map
301 //              dir   - direction the unit is facing (default is North)
302 //              group - group size (default is MAX_GROUP_SIZE)
303 //              xp    - initial experience level (default is 0)
304 // RETURNS    : created unit or NULL on error
305 ////////////////////////////////////////////////////////////////////////
306 
CreateUnit(unsigned char type,Player & p,const Point & pos,Direction dir,unsigned char group,unsigned char xp)307 Unit *Mission::CreateUnit( unsigned char type, Player &p,
308      const Point &pos, Direction dir, unsigned char group, unsigned char xp ) {
309   Unit *u;
310   const UnitType *utype = unit_set.GetUnitInfo( type );
311 
312   if ( utype->Flags() & U_TRANSPORT )
313     u = new Transport( utype, &p, CreateUnitID(), pos );
314   else
315     u = new Unit( utype, &p, CreateUnitID(), pos );
316 
317   u->Face( dir );
318   u->SetGroupSize( group );
319   u->AwardXP( xp * XP_PER_LEVEL );
320   units.AddTail( u );
321   map.SetUnit( u, pos );
322 
323   if ( history ) history->RecordUnitEvent( *u, History::HIST_UEVENT_CREATE );
324 
325   u->SetFlags( U_DONE );     // can't move on the first turn
326 
327   return u;
328 }
329 
330 ////////////////////////////////////////////////////////////////////////
331 // NAME       : Mission::GetUnit
332 // DESCRIPTION: Find the unit corresponding to the given identifier.
333 // PARAMETERS : id - identifier of the unit to be searched for
334 // RETURNS    : pointer to the unit, or NULL if no unit with that ID
335 //              exists
336 ////////////////////////////////////////////////////////////////////////
337 
GetUnit(unsigned short id) const338 Unit *Mission::GetUnit( unsigned short id ) const {
339   return FindID<Unit>( units, id );
340 }
341 
342 ////////////////////////////////////////////////////////////////////////
343 // NAME       : Mission::CreateUnitID
344 // DESCRIPTION: Get a unique identifier for a new unit.
345 // PARAMETERS : -
346 // RETURNS    : unique unit ID
347 ////////////////////////////////////////////////////////////////////////
348 
CreateUnitID(void) const349 unsigned short Mission::CreateUnitID( void ) const {
350   // find an unused unit ID; start from the back of the range to avoid
351   // potential conflicts with IDs of destroyed units
352   unsigned short id = 32000;
353 
354   while ( GetUnit( id ) != 0 )
355     --id;
356 
357   return id;
358 }
359 
360 ////////////////////////////////////////////////////////////////////////
361 // NAME       : Mission::GetShop
362 // DESCRIPTION: Find the shop corresponding to the given identifier.
363 // PARAMETERS : id - identifier of the shop to be searched for
364 // RETURNS    : pointer to the shop, or NULL if no building with that ID
365 //              exists
366 ////////////////////////////////////////////////////////////////////////
367 
GetShop(unsigned short id) const368 Building *Mission::GetShop( unsigned short id ) const {
369   return FindID<Building>( shops, id );
370 }
371 
372 ////////////////////////////////////////////////////////////////////////
373 // NAME       : Mission::GetEvent
374 // DESCRIPTION: Find the event corresponding to the given identifier.
375 // PARAMETERS : id - identifier of the event to be searched for
376 // RETURNS    : pointer to the event, or NULL if no event with that ID
377 //              exists
378 ////////////////////////////////////////////////////////////////////////
379 
GetEvent(unsigned short id) const380 Event *Mission::GetEvent( unsigned short id ) const {
381   return FindID<Event>( events, id );
382 }
383 
384 ////////////////////////////////////////////////////////////////////////
385 // NAME       : Mission::GetMessage
386 // DESCRIPTION: Get a message from the catalog.
387 // PARAMETERS : id - message identifier of the requested message
388 // RETURNS    : pointer to requested message if successful, 0 otherwise
389 ////////////////////////////////////////////////////////////////////////
390 
GetMessage(short id) const391 const char *Mission::GetMessage( short id ) const {
392   return messages.GetMsg( id );
393 }
394 
395 ////////////////////////////////////////////////////////////////////////
396 // NAME       : Mission::GetInternalMessage
397 // DESCRIPTION: Get a message from the internal catalog which contains
398 //              untranslatable strings like next map name, or soundtrack
399 //              names.
400 // PARAMETERS : id - message identifier of the requested message
401 // RETURNS    : pointer to requested message if successful, 0 otherwise
402 ////////////////////////////////////////////////////////////////////////
403 
GetInternalMessage(short id) const404 const char *Mission::GetInternalMessage( short id ) const {
405   return internal_messages.GetMsg( id );
406 }
407 
408 ////////////////////////////////////////////////////////////////////////
409 // NAME       : Mission::RegisterBattle
410 // DESCRIPTION: File a new combat. Record it for historical replay.
411 // PARAMETERS : att - attacking unit
412 //              def - defending unit
413 // RETURNS    : -
414 ////////////////////////////////////////////////////////////////////////
415 
RegisterBattle(Unit * att,Unit * def)416 void Mission::RegisterBattle( Unit *att, Unit *def ) {
417   battles.AddTail( new Combat( att, def ) );
418   att->Attack( def );
419   if ( history ) history->RecordAttackEvent( *att, *def );
420 }
421 
422 ////////////////////////////////////////////////////////////////////////
423 // NAME       : Mission::SetLocale
424 // DESCRIPTION: Set the language to use.
425 // PARAMETERS : lang - locale identifier
426 // RETURNS    : -
427 ////////////////////////////////////////////////////////////////////////
428 
SetLocale(const string & lang)429 void Mission::SetLocale( const string &lang ) {
430   messages.SetDefaultLanguage( lang );
431   unit_set.SetLocale( lang );
432 
433   p1.SetName( messages.GetMsg(p1.NameID()) );
434   p2.SetName( messages.GetMsg(p2.NameID()) );
435 
436   for ( Building *b = static_cast<Building *>(shops.Head());
437         b; b = static_cast<Building *>(b->Next()) ) {
438     const char *name = messages.GetMsg(b->NameID());
439     b->SetName( name != NULL ? name : "" );
440   }
441 }
442 
443