Subversion Repositories spacemission

Rev

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