Subversion Repositories spacemission

Rev

Rev 53 | Rev 65 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
14 daniel-mar 1
unit ComLevelReader;
2
 
3
interface
4
 
40 daniel-mar 5
uses
6
  Classes;
7
 
27 daniel-mar 8
const
9
  NumEnemyTypes = 7;
10
 
14 daniel-mar 11
type
12
  TEnemyType = (
13
    etUnknown,
14
    etEnemyAttacker,
15
    etEnemyAttacker2,
16
    etEnemyAttacker3,
17
    etEnemyMeteor,
18
    etEnemyUFO,
19
    etEnemyUFO2,
20
    etEnemyBoss
21
  );
22
 
23
  TEnemyAdvent = record
24
    enemyType: TEnemyType;
25
    x: integer;
26
    y: integer;
27
    lifes: integer;
28
  end;
29
 
40 daniel-mar 30
  TLevelData = class(TPersistent)
27 daniel-mar 31
  strict private
32
    procedure SortEnemies;
40 daniel-mar 33
  strict protected
34
    procedure AssignTo(Dest: TPersistent); override;
14 daniel-mar 35
  public
40 daniel-mar 36
    RasterErzwingen: boolean;
14 daniel-mar 37
    LevelEditorLength: integer;
42 daniel-mar 38
    LevelName: string;
39
    LevelAuthor: string;
14 daniel-mar 40
    EnemyAdventTable: array of TEnemyAdvent;
27 daniel-mar 41
    function IndexOfEnemy(x,y:integer;enemyType:TEnemyType;lifes:integer): integer;
42
    procedure AddEnemy(x,y:integer;enemyType:TEnemyType;lifes:integer);
43
    procedure DeleteEnemy(i: integer); overload;
44
    procedure DeleteEnemy(x,y:integer;enemyType:TEnemyType;lifes:integer); overload;
45
    function CountEnemies: integer;
46
    function HasBoss: boolean;
14 daniel-mar 47
    procedure Clear;
40 daniel-mar 48
    procedure LoadFromStrings(sl: TStrings); // version 0.3 - version 1.2 files
49
    procedure LoadFromFile(filename: string); // version 0.2 - version 1.2 files
50
    procedure SaveToStrings(sl: TStrings);
51
    procedure SaveToFile(filename: string);
52
    destructor Destroy; override;
14 daniel-mar 53
  end;
54
 
40 daniel-mar 55
  TGameMode = (gmUnknown, gmLevels, gmRandom);
56
 
57
  TSaveData = class(TPersistent)
58
  strict protected
59
    procedure AssignTo(Dest: TPersistent); override;
60
  public
61
    Score: integer;
62
    Life: integer;
63
    Level: integer;
64
    GameMode: TGameMode;
65
    LevelData: TLevelData;
66
    procedure Clear;
67
    procedure LoadFromStrings(sl: TStrings);
68
    procedure LoadFromFile(filename: string);
69
    procedure SaveToStrings(sl: TStrings);
70
    procedure SaveToFile(filename: string);
71
    destructor Destroy; override;
72
  end;
73
 
61 daniel-mar 74
  TLevelFile = record
75
    levelNumber: integer;
76
    fileLocation: string;
77
    isUser: boolean;
78
    found: boolean;
79
  end;
17 daniel-mar 80
 
61 daniel-mar 81
function GetLevelFileName(lev: integer; forceuserdir: boolean): TLevelFile;
82
 
14 daniel-mar 83
implementation
84
 
85
uses
52 daniel-mar 86
  SysUtils, StrUtils, Global, Windows, System.Types;
14 daniel-mar 87
 
52 daniel-mar 88
const
89
  // { iso(1) identified-organization(3) dod(6) internet(1) private(4) enterprise(1) 37476 products(2) spacemission(8) file-format(1) lev-sav-v12(1) }
90
  // https://hosted.oidplus.com/viathinksoft/?goto=oid%3A1.3.6.1.4.1.37476.2.8.1.1
91
  OID_LEVSAV_VER12 = '1.3.6.1.4.1.37476.2.8.1.1';
92
 
61 daniel-mar 93
function GetLevelFileName(lev: integer; forceuserdir: boolean): TLevelFile;
27 daniel-mar 94
 
51 daniel-mar 95
  function _GetLevelVerzeichnisSystem: string;
96
  begin
97
    // Für die Auslieferungs-Levels
98
    result := OwnDirectory + 'Levels';
99
  end;
100
 
101
  function _GetLevelVerzeichnisUser: string;
102
  begin
103
    try
104
      result := GetKnownFolderPath(FOLDERID_SavedGames);
105
    except
106
      result := '';
107
    end;
108
    if result = '' then
109
    begin
110
      // Pre Vista
111
      result := OwnDirectory + 'Levels';
112
    end
113
    else
114
    begin
115
      result := IncludeTrailingPathDelimiter(result);
116
      result := result + 'SpaceMission';
117
    end;
118
    result := IncludeTrailingPathDelimiter(result);
119
    ForceDirectories(result);
120
  end;
121
 
122
  function _GetLevelFileNameUser(lev: integer): string;
123
  var
124
    old, new: string;
125
  begin
126
    new := IncludeTrailingPathDelimiter(_GetLevelVerzeichnisUser)+'Level '+inttostr(lev)+'.lev'; // Version 0.3+ Level Files
127
    old := IncludeTrailingPathDelimiter(_GetLevelVerzeichnisUser)+'Lev'+inttostr(lev)+'A1.lev'; // Version 0.2 Level Files
128
    if fileexists(new) then exit(new);
129
    if fileexists(old) then exit(old);
130
    exit(new);
131
  end;
132
 
133
  function _GetLevelFileNameSystem(lev: integer): string;
134
  var
135
    old, new: string;
136
  begin
137
    new := IncludeTrailingPathDelimiter(_GetLevelVerzeichnisSystem)+'Level '+inttostr(lev)+'.lev'; // Version 0.3+ Level Files
138
    old := IncludeTrailingPathDelimiter(_GetLevelVerzeichnisSystem)+'Lev'+inttostr(lev)+'A1.lev'; // Version 0.2 Level Files
139
    if fileexists(new) then exit(new);
140
    if fileexists(old) then exit(old);
141
    exit(new);
142
  end;
143
 
144
var
145
  usr, sys: string;
61 daniel-mar 146
  bfound: boolean;
17 daniel-mar 147
begin
61 daniel-mar 148
  result.levelNumber := lev;
51 daniel-mar 149
  usr := _GetLevelFileNameUser(lev);
150
  sys := _GetLevelFileNameSystem(lev);
61 daniel-mar 151
  bfound := fileexists(usr);
152
  if bfound or forceuserdir then
153
  begin
154
    result.isUser := true;
155
    result.fileLocation := usr;
156
    result.found := bfound;
157
    exit;
158
  end;
159
  bfound := fileexists(sys);
160
  if bfound then
161
  begin
162
    result.isUser := false;
163
    result.fileLocation := sys;
164
    result.found := bfound;
165
    exit;
166
  end;
167
  result.isUser := true;
168
  result.fileLocation := usr;
169
  result.found := false;
17 daniel-mar 170
end;
171
 
52 daniel-mar 172
// this is just an example, there are many
173
// different ways you can implement this
174
// more efficiently, ie using a TStringBuilder,
175
// or even modifying the String in-place...
176
function CollapseSpaces(const S: string): string;
177
var
178
  P: PChar;
179
  AddSpace: Boolean;
180
begin
181
  Result := '';
182
  AddSpace := False;
183
  P := PChar(S);
184
  while P^ <> #0 do
185
  begin
186
    while CharInSet(P^, [#1..' ']) do Inc(P);
187
    if P^ = #0 then Exit;
188
    if AddSpace then
189
      Result := Result + ' '
190
    else
191
      AddSpace := True;
192
    repeat
193
      Result := Result + P^;
194
      Inc(P);
195
    until P^ <= ' ';
196
  end;
197
end;
198
 
14 daniel-mar 199
{ TLevelData }
200
 
40 daniel-mar 201
procedure TLevelData.AssignTo(Dest: TPersistent);
202
var
203
  DestLevelData: TLevelData;
204
  i: integer;
205
begin
206
  DestLevelData := Dest as TLevelData;
207
  if Assigned(DestLevelData) then
208
  begin
209
    DestLevelData.RasterErzwingen := Self.RasterErzwingen;
210
    DestLevelData.LevelEditorLength := Self.LevelEditorLength;
42 daniel-mar 211
    DestLevelData.LevelName := Self.LevelName;
212
    DestLevelData.LevelAuthor := Self.LevelAuthor;
40 daniel-mar 213
    SetLength(DestLevelData.EnemyAdventTable, Length(Self.EnemyAdventTable));
214
    for i := 0 to Length(Self.EnemyAdventTable) do
215
    begin
216
      DestLevelData.EnemyAdventTable[i] := Self.EnemyAdventTable[i];
217
    end;
218
  end
219
  else
220
  begin
221
    inherited;
222
  end;
223
end;
224
 
14 daniel-mar 225
procedure TLevelData.Clear;
226
begin
227
  SetLength(EnemyAdventTable, 0);
27 daniel-mar 228
  LevelEditorLength := DefaultLevelLength;
42 daniel-mar 229
  LevelName := '';
230
  LevelAuthor := '';
14 daniel-mar 231
end;
232
 
27 daniel-mar 233
function TLevelData.CountEnemies: integer;
234
begin
235
  result := Length(EnemyAdventTable);
236
end;
237
 
238
procedure TLevelData.DeleteEnemy(i: integer);
239
var
240
  j: integer;
241
begin
242
  for j := i+1 to CountEnemies-1 do
243
  begin
244
    EnemyAdventTable[j-1] := EnemyAdventTable[j];
245
  end;
246
  SetLength(EnemyAdventTable, Length(EnemyAdventTable)-1);
247
end;
248
 
249
procedure TLevelData.DeleteEnemy(x, y: integer; enemyType: TEnemyType;
250
  lifes: integer);
251
begin
252
  DeleteEnemy(IndexOfEnemy(x, y, enemyType, lifes));
253
end;
254
 
40 daniel-mar 255
destructor TLevelData.Destroy;
256
begin
257
  Clear;
258
  inherited;
259
end;
260
 
27 daniel-mar 261
function TLevelData.HasBoss: boolean;
262
var
263
  i: integer;
264
begin
265
  for i := 0 to Length(EnemyAdventTable) - 1 do
266
  begin
267
    if EnemyAdventTable[i].enemyType = etEnemyBoss then
268
    begin
269
      result := true;
270
      exit;
271
    end;
272
  end;
273
  result := false;
274
end;
275
 
276
procedure TLevelData.AddEnemy(x,y:integer;enemyType:TEnemyType;lifes:integer);
277
begin
278
  SetLength(EnemyAdventTable, Length(EnemyAdventTable)+1);
28 daniel-mar 279
 
280
  if enemyType = etEnemyMeteor then lifes := 0;
40 daniel-mar 281
  if RasterErzwingen then
282
  begin
283
    if x mod RasterW <> 0 then raise Exception.CreateFmt('X-Koordinate muss ohne Rest durch %d teilbar sein', [RasterW]);
284
    if y mod RasterH <> 0 then raise Exception.CreateFmt('Y-Koordinate muss ohne Rest durch %d teilbar sein', [RasterH]);
285
  end;
286
  if lifes > MaxPossibleEnemyLives then lifes := MaxPossibleEnemyLives;
28 daniel-mar 287
 
27 daniel-mar 288
  EnemyAdventTable[Length(EnemyAdventTable)-1].x         := x;
289
  EnemyAdventTable[Length(EnemyAdventTable)-1].y         := y;
290
  EnemyAdventTable[Length(EnemyAdventTable)-1].enemyType := enemyType;
291
  EnemyAdventTable[Length(EnemyAdventTable)-1].lifes     := lifes;
292
end;
293
 
294
function TLevelData.IndexOfEnemy(x, y: integer; enemyType: TEnemyType;
295
  lifes: integer): integer;
296
var
297
  i: integer;
298
begin
299
  for i := 0 to Length(EnemyAdventTable) - 1 do
300
  begin
301
    if (EnemyAdventTable[i].x = x) and
302
       (EnemyAdventTable[i].y = y) and
303
       (EnemyAdventTable[i].enemyType = enemyType) and
304
       (EnemyAdventTable[i].lifes = lifes) then
305
    begin
306
      result := i;
307
      exit;
308
    end;
309
  end;
310
  result := -1;
311
end;
312
 
40 daniel-mar 313
procedure TLevelData.LoadFromStrings(sl: TStrings);
14 daniel-mar 314
var
315
  curline: integer;
17 daniel-mar 316
  z, act: integer;
40 daniel-mar 317
  sl2: TStringList;
27 daniel-mar 318
  tmpX, tmpY, tmpLifes: integer;
319
  tmpEnemy: TEnemyType;
40 daniel-mar 320
  ergebnis: string;
52 daniel-mar 321
  ary: TStringDynArray;
322
  sLine: string;
14 daniel-mar 323
begin
27 daniel-mar 324
  Clear;
325
 
42 daniel-mar 326
  LevelEditorLength := DefaultLevelLength;
327
  LevelName := '';
328
  LevelAuthor := '';
329
 
52 daniel-mar 330
  if sl.Strings[0] = '; SpaceMission 0.3' then // do not localize
40 daniel-mar 331
  begin
332
    {$REGION 'Backwards compatibility level format 0.3 (convert to 0.4)'}
52 daniel-mar 333
    sl.Strings[0] := '; SpaceMission 0.4'; // do not localize
334
    sl.Insert(1, '; LEV-File'); // do not localize
40 daniel-mar 335
    {$ENDREGION}
336
  end;
337
 
52 daniel-mar 338
  if (sl.Strings[0] = '; SpaceMission 0.4') and // do not localize
339
     (sl.Strings[1] = '; LEV-File') then // do not localize
40 daniel-mar 340
  begin
341
    {$REGION 'Backwards compatibility level format 0.4 (convert to 1.0)'}
342
    sl2 := TStringList.Create;
343
    try
344
      z := 0;
345
      act := 0;
346
      while z < sl.Count do
347
      begin
348
        inc(z);
349
        if z > 2 then inc(act);
350
        if act = 5 then act := 1;
351
        ergebnis := sl.Strings[z-1];
352
        if ergebnis = '; SpaceMission 0.4' then
353
          sl2.Add('; SpaceMission 1.0')
354
        else
355
        begin
356
          if (ergebnis = '30000') and (z = 3) then
357
            sl2.Add(IntTostr(DefaultLevelLength))
358
          else
359
          begin
360
            //if not (((ergebnis = '0') and (z = 4)) or ((ergebnis = '-624') and (z = 5)) or ((ergebnis = '222') and (z = 6)) or ((ergebnis = '3') and (z = 7))) then
361
            if (z < 4) or (z > 7) then
362
            begin
363
              if act = 4 then
364
                sl2.Add(inttostr(strtoint(ergebnis) + 32 - (5 * (strtoint(ergebnis) div 37))))
365
              else
366
                sl2.Add(Ergebnis);
367
            end;
368
          end;
369
        end;
370
      end;
371
      sl.Text := sl2.Text;
372
    finally
373
      FreeAndNil(sl2);
374
    end;
375
    {$ENDREGION}
376
  end;
377
 
52 daniel-mar 378
  if (sl.Strings[0] = '; SpaceMission 1.0') and // do not localize
379
     (sl.Strings[1] = '; LEV-File') then // do not localize
40 daniel-mar 380
  begin
381
    {$REGION 'Level format 1.0'}
382
    LevelEditorLength := StrToInt(sl.Strings[2]);
383
    curline := 3;
384
    while curline < sl.Count do
385
    begin
386
      tmpEnemy := TEnemyType(strtoint(sl.Strings[curline]));
387
      Inc(curline);
388
      tmpX := strtoint(sl.Strings[curline]);
389
      Inc(curline);
390
      tmpY := strtoint(sl.Strings[curline]);
391
      Inc(curline);
392
      tmpLifes := strtoint(sl.Strings[curline]);
393
      Inc(curline);
394
      AddEnemy(tmpX, tmpY, tmpEnemy, tmpLifes);
395
    end;
396
    {$ENDREGION}
397
  end
52 daniel-mar 398
  else if (SameText(sl.Strings[0], '['+OID_LEVSAV_VER12+']')) then
40 daniel-mar 399
  begin
400
    {$REGION 'Level format 1.2'}
401
    for curline := 1 to sl.Count-1 do
402
    begin
52 daniel-mar 403
      sLine := sl.Strings[curline].Trim;
404
      if (sLine = '') or (Copy(sLine, 1, 1) = ';') then continue;
405
      ary := SplitString(CollapseSpaces(sLine), ' ');
53 daniel-mar 406
      if SameText(ary[0], 'Width') then // do not localize
40 daniel-mar 407
      begin
52 daniel-mar 408
        LevelEditorLength := StrToInt(ary[1]);
409
        if (Length(ary) > 2) and (Copy(ary[2], 1, 1) <> ';') then
410
          raise Exception.CreateFmt('Zeile %d ist ungültig (Zusatzinfo am Ende)', [curline+1]);
40 daniel-mar 411
      end
53 daniel-mar 412
      else if SameText(ary[0], 'Name') then // do not localize
42 daniel-mar 413
      begin
53 daniel-mar 414
        LevelName := Trim(Copy(sLine, Length(ary[0])+2, Length(sLine)));
42 daniel-mar 415
      end
53 daniel-mar 416
      else if SameText(ary[0], 'Author') then // do not localize
42 daniel-mar 417
      begin
53 daniel-mar 418
        LevelAuthor := Trim(Copy(sLine, Length(ary[0])+2, Length(sLine)));
42 daniel-mar 419
      end
53 daniel-mar 420
      else if SameText(ary[0], 'Enemy') then // do not localize
40 daniel-mar 421
      begin
52 daniel-mar 422
        tmpEnemy := TEnemyType(strtoint(ary[1]));
423
        tmpX     := strtoint(ary[2]);
424
        tmpY     := strtoint(ary[3]);
425
        tmpLifes := strtoint(ary[4]);
426
        if (Length(ary) > 5) and (Copy(ary[5], 1, 1) <> ';') then
40 daniel-mar 427
          raise Exception.CreateFmt('Zeile %d ist ungültig (Zusatzinfo am Ende)', [curline+1]);
428
        AddEnemy(tmpX, tmpY, tmpEnemy, tmpLifes);
429
      end;
430
    end;
431
    {$ENDREGION}
432
  end
433
  else
434
  begin
435
    raise Exception.Create('Level-Format nicht unterstützt oder Datei ist beschädigt');
436
  end;
437
 
438
  SortEnemies; // Sortierung nach X-Koordinate ist sehr wichtig für das Spiel!
439
end;
440
 
441
procedure TLevelData.LoadFromFile(filename: string);
442
var
443
  sl: TStringList;
444
  i, j: integer;
445
  temp: string;
446
  m: array[1..6] of tstrings;
447
begin
15 daniel-mar 448
  sl := TStringList.Create;
14 daniel-mar 449
  try
52 daniel-mar 450
    if EndsText('A1.lev', filename) then // do not localize
17 daniel-mar 451
    begin
30 daniel-mar 452
      {$REGION 'Backwards compatibility level format 0.2 (split into 5-6 files; convert to 0.3)'}
17 daniel-mar 453
      m[1] := TStringList.create;
454
      m[2] := TStringList.create;
455
      m[3] := TStringList.create;
456
      m[4] := TStringList.create;
457
      m[5] := TStringList.create;
458
      m[6] := TStringList.create;
459
      try
460
        for i := 1 to 6 do
461
        begin
462
          filename[Length(filename)-4] := IntToStr(i)[1]; // ...A2.sav, ...A3.sav, etc.
463
          if FileExists(filename) then
464
            m[i].loadfromfile(filename);
465
        end;
466
        m[1].strings[0] := '-624';
467
        if m[6].Text = '' then m[6].Text := '30000';
14 daniel-mar 468
 
52 daniel-mar 469
        sl.Add('; SpaceMission 0.3'); // do not localize
17 daniel-mar 470
        sl.Add(temp);
471
        for j := 0 to m[1].count-2 do
472
        begin
473
          for i := 0 to m[1].count-2 do
474
          begin
475
            if strtoint(m[1].strings[i]) > strtoint(m[1].strings[i+1]) then
476
            begin
477
              m[1].exchange(i, i+1);
478
              m[2].exchange(i, i+1);
479
              m[3].exchange(i, i+1);
480
              m[4].exchange(i, i+1);
481
              m[5].exchange(i, i+1);
482
            end;
483
          end;
484
        end;
485
        for i := 0 to m[3].count-1 do
486
        begin
487
          for j := 1 to 4 do
488
          begin
489
            if j = 1 then sl.Add(m[3].strings[i]);
490
            if j = 2 then sl.Add(m[1].strings[i]);
491
            if j = 3 then sl.Add(m[2].strings[i]);
492
            if j = 4 then sl.Add(m[4].strings[i]);
493
          end;
494
        end;
495
      finally
496
        FreeAndNil(m[1]);
497
        FreeAndNil(m[2]);
498
        FreeAndNil(m[3]);
499
        FreeAndNil(m[4]);
500
        FreeAndNil(m[5]);
501
        FreeAndNil(m[6]);
502
      end;
503
      {$ENDREGION}
504
    end
505
    else
14 daniel-mar 506
    begin
17 daniel-mar 507
      sl.LoadFromFile(filename);
508
    end;
509
 
40 daniel-mar 510
    LoadFromStrings(sl);
14 daniel-mar 511
  finally
15 daniel-mar 512
    FreeAndNil(sl);
14 daniel-mar 513
  end;
514
end;
515
 
40 daniel-mar 516
procedure TLevelData.SaveToStrings(sl: TStrings);
15 daniel-mar 517
var
518
  i: integer;
14 daniel-mar 519
begin
40 daniel-mar 520
  sl.Clear;
52 daniel-mar 521
  sl.Add('['+OID_LEVSAV_VER12+']');
522
  if LevelName   <> '' then sl.Add('Name   ' + LevelName); // do not localize
523
  if LevelAuthor <> '' then sl.Add('Author ' + LevelAuthor); // do not localize
524
  sl.Add('Width  ' + IntToStr(LevelEditorLength)); // do not localize
40 daniel-mar 525
  SortEnemies;
48 daniel-mar 526
  sl.Add(';      Type   XCoord YCoord Lives');
40 daniel-mar 527
  for i := 0 to Length(EnemyAdventTable)-1 do
528
  begin
52 daniel-mar 529
    sl.Add(Trim(
530
      'Enemy'.PadRight(6, ' ')+ // do not localize
30 daniel-mar 531
      ' '+
40 daniel-mar 532
      IntToStr(Ord(EnemyAdventTable[i].enemyType)).PadRight(6, ' ')+
533
      ' '+
534
      IntToStr(EnemyAdventTable[i].x).PadRight(6, ' ')+
535
      ' '+
536
      IntToStr(EnemyAdventTable[i].y).PadRight(6, ' ')+
537
      ' '+
538
      IntToStr(EnemyAdventTable[i].lifes).PadRight(6, ' ')+
30 daniel-mar 539
      ' '
52 daniel-mar 540
    ));
40 daniel-mar 541
  end;
542
end;
543
 
544
procedure TLevelData.SaveToFile(filename: string);
545
var
546
  sl: TStringList;
547
begin
548
  sl := TStringList.Create;
549
  try
550
    SaveToStrings(sl);
15 daniel-mar 551
    sl.SaveToFile(filename);
552
  finally
553
    FreeAndNil(sl);
554
  end;
14 daniel-mar 555
end;
556
 
27 daniel-mar 557
procedure TLevelData.SortEnemies;
558
var
559
  i, n: integer;
560
  e: TEnemyAdvent;
561
begin
562
  // Bubble Sort Algorithmus
563
  for n := Length(EnemyAdventTable) downto 2 do
564
  begin
565
    for i := 0 to n - 2 do
566
    begin
567
      if
568
        // Sort by X-coord (important for the game!)
569
        (EnemyAdventTable[i].x > EnemyAdventTable[i+1].x)
570
        or
571
        // Sort by Y-coord (just cosmetics)
572
        ((EnemyAdventTable[i].x = EnemyAdventTable[i+1].x) and (EnemyAdventTable[i].y > EnemyAdventTable[i+1].y))
573
      then
574
      begin
575
        e := EnemyAdventTable[i];
576
        EnemyAdventTable[i] := EnemyAdventTable[i + 1];
577
        EnemyAdventTable[i + 1] := e;
578
      end;
579
    end;
580
  end;
581
end;
582
 
40 daniel-mar 583
{ TSaveData }
584
 
585
procedure TSaveData.AssignTo(Dest: TPersistent);
586
var
587
  DestSaveData: TSaveData;
588
begin
589
  DestSaveData := Dest as TSaveData;
590
  if Assigned(DestSaveData) then
591
  begin
592
    DestSaveData.Score := Self.Score;
593
    DestSaveData.Life := Self.Life;
594
    DestSaveData.Level := Self.Level;
595
    DestSaveData.GameMode := Self.GameMode;
596
    if not Assigned(DestSaveData.LevelData) then DestSaveData.LevelData := TLevelData.Create;
597
    DestSaveData.LevelData.Assign(Self.LevelData);
598
  end
599
  else
600
  begin
601
    inherited;
602
  end;
603
end;
604
 
605
procedure TSaveData.Clear;
606
begin
607
  Score := 0;
608
  Life := 0;
609
  Level := 0;
610
  GameMode := gmUnknown;
611
  FreeAndNil(LevelData);
612
end;
613
 
614
destructor TSaveData.Destroy;
615
begin
616
  Clear;
617
  inherited;
618
end;
619
 
620
procedure TSaveData.SaveToStrings(sl: TStrings);
621
var
622
  sl2: TStringList;
623
begin
624
  sl2 := TStringList.Create;
625
  try
52 daniel-mar 626
    sl.Add('['+OID_LEVSAV_VER12+']');
627
    sl.Add('Score  ' + IntToStr(Score)); // do not localize
628
    sl.Add('Lives  ' + IntToStr(Life)); // do not localize
629
    sl.Add('Level  ' + IntToStr(Level)); // do not localize
630
    sl.Add('Mode   ' + IntToStr(Ord(GameMode))); // do not localize
40 daniel-mar 631
    LevelData.SaveToStrings(sl2);
52 daniel-mar 632
    sl2.Delete(0); // Delete additional level signature
40 daniel-mar 633
    sl.AddStrings(sl2);
634
  finally
635
    FreeAndNil(sl2);
636
  end;
637
end;
638
 
639
procedure TSaveData.LoadFromStrings(sl: TStrings);
640
var
641
  curline: Integer;
52 daniel-mar 642
  ary: TStringDynArray;
643
  sLine: string;
40 daniel-mar 644
begin
52 daniel-mar 645
  if (sl.Strings[0] = '; SpaceMission 1.0') and // do not localize
646
     (sl.Strings[1] = '; SAV-File') then // do not localize
40 daniel-mar 647
  begin
648
    Score    := StrToInt(sl.Strings[2]);
649
    Life     := StrToInt(sl.Strings[3]);
650
    Level    := StrToInt(sl.Strings[4]);
651
    GameMode := TGameMode(StrToInt(sl.Strings[5]));
652
    if Assigned(LevelData) then FreeAndNil(LevelData);
653
  end
52 daniel-mar 654
  else if SameText(sl.Strings[0], '['+OID_LEVSAV_VER12+']') then
40 daniel-mar 655
  begin
656
    Score    := 0;
657
    Life     := 0;
658
    Level    := 0;
659
    GameMode := gmUnknown;
660
    for curline := 1 to sl.Count-1 do
661
    begin
52 daniel-mar 662
      sLine := sl.Strings[curline].Trim;
663
      if (sLine = '') or (Copy(sLine, 1, 1) = ';') then continue;
664
      ary := SplitString(CollapseSpaces(sLine), ' ');
53 daniel-mar 665
      if SameText(ary[0], 'Score') then // do not localize
40 daniel-mar 666
      begin
52 daniel-mar 667
        Score := StrToInt(ary[1]);
668
        if (Length(ary) > 2) and (Copy(ary[2], 1, 1) <> ';') then
669
          raise Exception.CreateFmt('Zeile %d ist ungültig (Zusatzinfo am Ende)', [curline+1]);
40 daniel-mar 670
      end
53 daniel-mar 671
      else if SameText(ary[0], 'Lives') then // do not localize
40 daniel-mar 672
      begin
52 daniel-mar 673
        Life := StrToInt(ary[1]);
674
        if (Length(ary) > 2) and (Copy(ary[2], 1, 1) <> ';') then
675
          raise Exception.CreateFmt('Zeile %d ist ungültig (Zusatzinfo am Ende)', [curline+1]);
40 daniel-mar 676
      end
53 daniel-mar 677
      else if SameText(ary[0], 'Level') then // do not localize
40 daniel-mar 678
      begin
52 daniel-mar 679
        Level := StrToInt(ary[1]);
680
        if (Length(ary) > 2) and (Copy(ary[2], 1, 1) <> ';') then
681
          raise Exception.CreateFmt('Zeile %d ist ungültig (Zusatzinfo am Ende)', [curline+1]);
40 daniel-mar 682
      end
53 daniel-mar 683
      else if SameText(ary[0], 'Mode') then // do not localize
40 daniel-mar 684
      begin
52 daniel-mar 685
        GameMode := TGameMode(StrToInt(ary[1]));
686
        if (Length(ary) > 2) and (Copy(ary[2], 1, 1) <> ';') then
687
          raise Exception.CreateFmt('Zeile %d ist ungültig (Zusatzinfo am Ende)', [curline+1]);
40 daniel-mar 688
      end;
689
    end;
690
    if Assigned(LevelData) then FreeAndNil(LevelData);
691
    LevelData := TLevelData.Create;
44 daniel-mar 692
    LevelData.RasterErzwingen := false;
40 daniel-mar 693
    LevelData.LoadFromStrings(sl);
694
  end
695
  else
696
  begin
697
    raise Exception.Create('Spielstand-Format nicht unterstützt oder Datei beschädigt');
698
  end;
699
end;
700
 
701
procedure TSaveData.LoadFromFile(filename: string);
702
var
703
  sl: TStringList;
704
begin
705
  sl := TStringList.Create;
706
  try
707
    sl.LoadFromFile(filename);
708
    LoadFromStrings(sl);
709
  finally
710
    FreeAndNil(sl);
711
  end;
712
end;
713
 
714
procedure TSaveData.SaveToFile(filename: string);
715
var
716
  sl: TStringList;
717
begin
718
  sl := TStringList.Create;
719
  try
720
    SaveToStrings(sl);
721
    sl.SaveToFile(filename);
722
  finally
723
    FreeAndNil(sl);
724
  end;
725
end;
726
 
14 daniel-mar 727
end.