Subversion Repositories spacemission

Rev

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