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