Subversion Repositories spacemission

Rev

Rev 34 | Rev 41 | 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;
38
    EnemyAdventTable: array of TEnemyAdvent;
27 daniel-mar 39
    function IndexOfEnemy(x,y:integer;enemyType:TEnemyType;lifes:integer): integer;
40
    procedure AddEnemy(x,y:integer;enemyType:TEnemyType;lifes:integer);
41
    procedure DeleteEnemy(i: integer); overload;
42
    procedure DeleteEnemy(x,y:integer;enemyType:TEnemyType;lifes:integer); overload;
43
    function CountEnemies: integer;
44
    function HasBoss: boolean;
14 daniel-mar 45
    procedure Clear;
40 daniel-mar 46
    procedure LoadFromStrings(sl: TStrings); // version 0.3 - version 1.2 files
47
    procedure LoadFromFile(filename: string); // version 0.2 - version 1.2 files
48
    procedure SaveToStrings(sl: TStrings);
49
    procedure SaveToFile(filename: string);
50
    destructor Destroy; override;
14 daniel-mar 51
  end;
52
 
40 daniel-mar 53
  TGameMode = (gmUnknown, gmLevels, gmRandom);
54
 
55
  TSaveData = class(TPersistent)
56
  strict protected
57
    procedure AssignTo(Dest: TPersistent); override;
58
  public
59
    Score: integer;
60
    Life: integer;
61
    Level: integer;
62
    GameMode: TGameMode;
63
    LevelData: TLevelData;
64
    procedure Clear;
65
    procedure LoadFromStrings(sl: TStrings);
66
    procedure LoadFromFile(filename: string);
67
    procedure SaveToStrings(sl: TStrings);
68
    procedure SaveToFile(filename: string);
69
    destructor Destroy; override;
70
  end;
71
 
17 daniel-mar 72
function GetLevelFileName(lev: integer): string;
73
 
14 daniel-mar 74
implementation
75
 
76
uses
40 daniel-mar 77
  SysUtils, StrUtils, Global;
14 daniel-mar 78
 
27 daniel-mar 79
const
80
  DefaultLevelLength = 1200;
81
 
17 daniel-mar 82
function GetLevelFileName(lev: integer): string;
83
begin
31 daniel-mar 84
  result := OwnDirectory+'Levels\Level '+inttostr(lev)+'.lev'; // Version 0.3+ Level Files
17 daniel-mar 85
  if not FileExists(Result) then
31 daniel-mar 86
    result := OwnDirectory+'Levels\Lev'+inttostr(lev)+'A1.lev'; // Version 0.2 Level Files
17 daniel-mar 87
end;
88
 
14 daniel-mar 89
{ TLevelData }
90
 
40 daniel-mar 91
procedure TLevelData.AssignTo(Dest: TPersistent);
92
var
93
  DestLevelData: TLevelData;
94
  i: integer;
95
begin
96
  DestLevelData := Dest as TLevelData;
97
  if Assigned(DestLevelData) then
98
  begin
99
    DestLevelData.RasterErzwingen := Self.RasterErzwingen;
100
    DestLevelData.LevelEditorLength := Self.LevelEditorLength;
101
    SetLength(DestLevelData.EnemyAdventTable, Length(Self.EnemyAdventTable));
102
    for i := 0 to Length(Self.EnemyAdventTable) do
103
    begin
104
      DestLevelData.EnemyAdventTable[i] := Self.EnemyAdventTable[i];
105
    end;
106
  end
107
  else
108
  begin
109
    inherited;
110
  end;
111
end;
112
 
14 daniel-mar 113
procedure TLevelData.Clear;
114
begin
115
  SetLength(EnemyAdventTable, 0);
27 daniel-mar 116
  LevelEditorLength := DefaultLevelLength;
14 daniel-mar 117
end;
118
 
27 daniel-mar 119
function TLevelData.CountEnemies: integer;
120
begin
121
  result := Length(EnemyAdventTable);
122
end;
123
 
124
procedure TLevelData.DeleteEnemy(i: integer);
125
var
126
  j: integer;
127
begin
128
  for j := i+1 to CountEnemies-1 do
129
  begin
130
    EnemyAdventTable[j-1] := EnemyAdventTable[j];
131
  end;
132
  SetLength(EnemyAdventTable, Length(EnemyAdventTable)-1);
133
end;
134
 
135
procedure TLevelData.DeleteEnemy(x, y: integer; enemyType: TEnemyType;
136
  lifes: integer);
137
begin
138
  DeleteEnemy(IndexOfEnemy(x, y, enemyType, lifes));
139
end;
140
 
40 daniel-mar 141
destructor TLevelData.Destroy;
142
begin
143
  Clear;
144
  inherited;
145
end;
146
 
27 daniel-mar 147
function TLevelData.HasBoss: boolean;
148
var
149
  i: integer;
150
begin
151
  for i := 0 to Length(EnemyAdventTable) - 1 do
152
  begin
153
    if EnemyAdventTable[i].enemyType = etEnemyBoss then
154
    begin
155
      result := true;
156
      exit;
157
    end;
158
  end;
159
  result := false;
160
end;
161
 
162
procedure TLevelData.AddEnemy(x,y:integer;enemyType:TEnemyType;lifes:integer);
163
begin
164
  SetLength(EnemyAdventTable, Length(EnemyAdventTable)+1);
28 daniel-mar 165
 
166
  if enemyType = etEnemyMeteor then lifes := 0;
40 daniel-mar 167
  if RasterErzwingen then
168
  begin
169
    if x mod RasterW <> 0 then raise Exception.CreateFmt('X-Koordinate muss ohne Rest durch %d teilbar sein', [RasterW]);
170
    if y mod RasterH <> 0 then raise Exception.CreateFmt('Y-Koordinate muss ohne Rest durch %d teilbar sein', [RasterH]);
171
  end;
172
  if lifes > MaxPossibleEnemyLives then lifes := MaxPossibleEnemyLives;
28 daniel-mar 173
 
27 daniel-mar 174
  EnemyAdventTable[Length(EnemyAdventTable)-1].x         := x;
175
  EnemyAdventTable[Length(EnemyAdventTable)-1].y         := y;
176
  EnemyAdventTable[Length(EnemyAdventTable)-1].enemyType := enemyType;
177
  EnemyAdventTable[Length(EnemyAdventTable)-1].lifes     := lifes;
178
end;
179
 
180
function TLevelData.IndexOfEnemy(x, y: integer; enemyType: TEnemyType;
181
  lifes: integer): integer;
182
var
183
  i: integer;
184
begin
185
  for i := 0 to Length(EnemyAdventTable) - 1 do
186
  begin
187
    if (EnemyAdventTable[i].x = x) and
188
       (EnemyAdventTable[i].y = y) and
189
       (EnemyAdventTable[i].enemyType = enemyType) and
190
       (EnemyAdventTable[i].lifes = lifes) then
191
    begin
192
      result := i;
193
      exit;
194
    end;
195
  end;
196
  result := -1;
197
end;
198
 
40 daniel-mar 199
procedure TLevelData.LoadFromStrings(sl: TStrings);
14 daniel-mar 200
var
201
  curline: integer;
17 daniel-mar 202
  z, act: integer;
40 daniel-mar 203
  sl2: TStringList;
27 daniel-mar 204
  tmpX, tmpY, tmpLifes: integer;
205
  tmpEnemy: TEnemyType;
30 daniel-mar 206
  tmpRest: string;
40 daniel-mar 207
  ergebnis: string;
14 daniel-mar 208
begin
27 daniel-mar 209
  Clear;
210
 
40 daniel-mar 211
  RasterErzwingen := true;
212
 
213
  if sl.Strings[0] = '; SpaceMission 0.3' then
214
  begin
215
    {$REGION 'Backwards compatibility level format 0.3 (convert to 0.4)'}
216
    sl.Strings[0] := '; SpaceMission 0.4';
217
    sl.Insert(1, '; LEV-File');
218
    {$ENDREGION}
219
  end;
220
 
221
  if (sl.Strings[0] = '; SpaceMission 0.4') and
222
     (sl.Strings[1] = '; LEV-File') then
223
  begin
224
    {$REGION 'Backwards compatibility level format 0.4 (convert to 1.0)'}
225
    sl2 := TStringList.Create;
226
    try
227
      z := 0;
228
      act := 0;
229
      while z < sl.Count do
230
      begin
231
        inc(z);
232
        if z > 2 then inc(act);
233
        if act = 5 then act := 1;
234
        ergebnis := sl.Strings[z-1];
235
        if ergebnis = '; SpaceMission 0.4' then
236
          sl2.Add('; SpaceMission 1.0')
237
        else
238
        begin
239
          if (ergebnis = '30000') and (z = 3) then
240
            sl2.Add(IntTostr(DefaultLevelLength))
241
          else
242
          begin
243
            //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
244
            if (z < 4) or (z > 7) then
245
            begin
246
              if act = 4 then
247
                sl2.Add(inttostr(strtoint(ergebnis) + 32 - (5 * (strtoint(ergebnis) div 37))))
248
              else
249
                sl2.Add(Ergebnis);
250
            end;
251
          end;
252
        end;
253
      end;
254
      sl.Text := sl2.Text;
255
    finally
256
      FreeAndNil(sl2);
257
    end;
258
    {$ENDREGION}
259
  end;
260
 
261
  if (sl.Strings[0] = '; SpaceMission 1.0') and
262
     (sl.Strings[1] = '; LEV-File') then
263
  begin
264
    {$REGION 'Level format 1.0'}
265
    LevelEditorLength := StrToInt(sl.Strings[2]);
266
    curline := 3;
267
    while curline < sl.Count do
268
    begin
269
      tmpEnemy := TEnemyType(strtoint(sl.Strings[curline]));
270
      Inc(curline);
271
      tmpX := strtoint(sl.Strings[curline]);
272
      Inc(curline);
273
      tmpY := strtoint(sl.Strings[curline]);
274
      Inc(curline);
275
      tmpLifes := strtoint(sl.Strings[curline]);
276
      Inc(curline);
277
      AddEnemy(tmpX, tmpY, tmpEnemy, tmpLifes);
278
    end;
279
    {$ENDREGION}
280
  end
281
  else if (SameText(sl.Strings[0], '[SpaceMission Level, Format 1.2]')) or
282
          (SameText(sl.Strings[0], '[SpaceMission Savegame, Format 1.2]')) then
283
  begin
284
    RasterErzwingen := SameText(sl.Strings[0], '[SpaceMission Level, Format 1.2]');
285
    {$REGION 'Level format 1.2'}
286
    LevelEditorLength := DefaultLevelLength;
287
    for curline := 1 to sl.Count-1 do
288
    begin
289
      // 1234567890123456789012345678901234567890
290
      // 123456 123456 123456 123456 123456 ; Kommentar
291
      if (sl.Strings[curline].Trim = '') or
292
         (Copy(sl.Strings[curline], 1, 1) = ';') then
293
        // Do nothing
294
      else if Copy(sl.Strings[curline], 1, 6).TrimRight = 'Width' then
295
      begin
296
        LevelEditorLength := StrToInt(TrimRight(Copy(sl.Strings[curline], 8, 6)))
297
      end
298
      else if Copy(sl.Strings[curline], 1, 6).TrimRight = 'Enemy' then
299
      begin
300
        tmpEnemy := TEnemyType(strtoint(TrimRight(Copy(sl.Strings[curline], 8, 6))));
301
        tmpX     := strtoint(TrimRight(Copy(sl.Strings[curline], 15, 6)));
302
        tmpY     := strtoint(TrimRight(Copy(sl.Strings[curline], 22, 6)));
303
        tmpLifes := strtoint(TrimRight(Copy(sl.Strings[curline], 29, 6)));
304
        tmpRest  := Copy(sl.Strings[curline], 36, Length(sl.Strings[curline])-36+1);
305
        if (Copy(tmpRest, 1, 1) <> ';') and (Trim(tmpRest) <> '') then
306
          raise Exception.CreateFmt('Zeile %d ist ungültig (Zusatzinfo am Ende)', [curline+1]);
307
        AddEnemy(tmpX, tmpY, tmpEnemy, tmpLifes);
308
      end;
309
    end;
310
    {$ENDREGION}
311
  end
312
  else
313
  begin
314
    raise Exception.Create('Level-Format nicht unterstützt oder Datei ist beschädigt');
315
  end;
316
 
317
  SortEnemies; // Sortierung nach X-Koordinate ist sehr wichtig für das Spiel!
318
end;
319
 
320
procedure TLevelData.LoadFromFile(filename: string);
321
var
322
  sl: TStringList;
323
  i, j: integer;
324
  temp: string;
325
  m: array[1..6] of tstrings;
326
begin
15 daniel-mar 327
  sl := TStringList.Create;
14 daniel-mar 328
  try
17 daniel-mar 329
    if EndsText('A1.lev', filename) then
330
    begin
30 daniel-mar 331
      {$REGION 'Backwards compatibility level format 0.2 (split into 5-6 files; convert to 0.3)'}
17 daniel-mar 332
      m[1] := TStringList.create;
333
      m[2] := TStringList.create;
334
      m[3] := TStringList.create;
335
      m[4] := TStringList.create;
336
      m[5] := TStringList.create;
337
      m[6] := TStringList.create;
338
      try
339
        for i := 1 to 6 do
340
        begin
341
          filename[Length(filename)-4] := IntToStr(i)[1]; // ...A2.sav, ...A3.sav, etc.
342
          if FileExists(filename) then
343
            m[i].loadfromfile(filename);
344
        end;
345
        m[1].strings[0] := '-624';
346
        if m[6].Text = '' then m[6].Text := '30000';
14 daniel-mar 347
 
17 daniel-mar 348
        sl.Add('; SpaceMission 0.3');
349
        sl.Add(temp);
350
        for j := 0 to m[1].count-2 do
351
        begin
352
          for i := 0 to m[1].count-2 do
353
          begin
354
            if strtoint(m[1].strings[i]) > strtoint(m[1].strings[i+1]) then
355
            begin
356
              m[1].exchange(i, i+1);
357
              m[2].exchange(i, i+1);
358
              m[3].exchange(i, i+1);
359
              m[4].exchange(i, i+1);
360
              m[5].exchange(i, i+1);
361
            end;
362
          end;
363
        end;
364
        for i := 0 to m[3].count-1 do
365
        begin
366
          for j := 1 to 4 do
367
          begin
368
            if j = 1 then sl.Add(m[3].strings[i]);
369
            if j = 2 then sl.Add(m[1].strings[i]);
370
            if j = 3 then sl.Add(m[2].strings[i]);
371
            if j = 4 then sl.Add(m[4].strings[i]);
372
          end;
373
        end;
374
      finally
375
        FreeAndNil(m[1]);
376
        FreeAndNil(m[2]);
377
        FreeAndNil(m[3]);
378
        FreeAndNil(m[4]);
379
        FreeAndNil(m[5]);
380
        FreeAndNil(m[6]);
381
      end;
382
      {$ENDREGION}
383
    end
384
    else
14 daniel-mar 385
    begin
17 daniel-mar 386
      sl.LoadFromFile(filename);
387
    end;
388
 
40 daniel-mar 389
    LoadFromStrings(sl);
14 daniel-mar 390
  finally
15 daniel-mar 391
    FreeAndNil(sl);
14 daniel-mar 392
  end;
393
end;
394
 
40 daniel-mar 395
procedure TLevelData.SaveToStrings(sl: TStrings);
15 daniel-mar 396
var
397
  i: integer;
14 daniel-mar 398
begin
40 daniel-mar 399
  sl.Clear;
400
  sl.Add('[SpaceMission Level, Format 1.2]');
401
  sl.Add(
402
    'Width'.PadRight(6, ' ')+
403
    ' '+
404
    IntToStr(LevelEditorLength)+
405
    ' '
406
  );
407
  SortEnemies;
408
  for i := 0 to Length(EnemyAdventTable)-1 do
409
  begin
30 daniel-mar 410
    sl.Add(
40 daniel-mar 411
      'Enemy'.PadRight(6, ' ')+
30 daniel-mar 412
      ' '+
40 daniel-mar 413
      IntToStr(Ord(EnemyAdventTable[i].enemyType)).PadRight(6, ' ')+
414
      ' '+
415
      IntToStr(EnemyAdventTable[i].x).PadRight(6, ' ')+
416
      ' '+
417
      IntToStr(EnemyAdventTable[i].y).PadRight(6, ' ')+
418
      ' '+
419
      IntToStr(EnemyAdventTable[i].lifes).PadRight(6, ' ')+
30 daniel-mar 420
      ' '
421
    );
40 daniel-mar 422
  end;
423
end;
424
 
425
procedure TLevelData.SaveToFile(filename: string);
426
var
427
  sl: TStringList;
428
begin
429
  sl := TStringList.Create;
430
  try
431
    SaveToStrings(sl);
15 daniel-mar 432
    sl.SaveToFile(filename);
433
  finally
434
    FreeAndNil(sl);
435
  end;
14 daniel-mar 436
end;
437
 
27 daniel-mar 438
procedure TLevelData.SortEnemies;
439
var
440
  i, n: integer;
441
  e: TEnemyAdvent;
442
begin
443
  // Bubble Sort Algorithmus
444
  for n := Length(EnemyAdventTable) downto 2 do
445
  begin
446
    for i := 0 to n - 2 do
447
    begin
448
      if
449
        // Sort by X-coord (important for the game!)
450
        (EnemyAdventTable[i].x > EnemyAdventTable[i+1].x)
451
        or
452
        // Sort by Y-coord (just cosmetics)
453
        ((EnemyAdventTable[i].x = EnemyAdventTable[i+1].x) and (EnemyAdventTable[i].y > EnemyAdventTable[i+1].y))
454
      then
455
      begin
456
        e := EnemyAdventTable[i];
457
        EnemyAdventTable[i] := EnemyAdventTable[i + 1];
458
        EnemyAdventTable[i + 1] := e;
459
      end;
460
    end;
461
  end;
462
end;
463
 
40 daniel-mar 464
{ TSaveData }
465
 
466
procedure TSaveData.AssignTo(Dest: TPersistent);
467
var
468
  DestSaveData: TSaveData;
469
begin
470
  DestSaveData := Dest as TSaveData;
471
  if Assigned(DestSaveData) then
472
  begin
473
    DestSaveData.Score := Self.Score;
474
    DestSaveData.Life := Self.Life;
475
    DestSaveData.Level := Self.Level;
476
    DestSaveData.GameMode := Self.GameMode;
477
    if not Assigned(DestSaveData.LevelData) then DestSaveData.LevelData := TLevelData.Create;
478
    DestSaveData.LevelData.Assign(Self.LevelData);
479
  end
480
  else
481
  begin
482
    inherited;
483
  end;
484
end;
485
 
486
procedure TSaveData.Clear;
487
begin
488
  Score := 0;
489
  Life := 0;
490
  Level := 0;
491
  GameMode := gmUnknown;
492
  FreeAndNil(LevelData);
493
end;
494
 
495
destructor TSaveData.Destroy;
496
begin
497
  Clear;
498
  inherited;
499
end;
500
 
501
procedure TSaveData.SaveToStrings(sl: TStrings);
502
var
503
  sl2: TStringList;
504
begin
505
  sl2 := TStringList.Create;
506
  try
507
    sl.Add('[SpaceMission Savegame, Format 1.2]');
508
    sl.Add('Score  ' + IntToStr(Score));
509
    sl.Add('Lives  ' + IntToStr(Life));
510
    sl.Add('Level  ' + IntToStr(Level));
511
    sl.Add('Mode   ' + IntToStr(Ord(GameMode)));
512
    LevelData.SaveToStrings(sl2);
513
    sl2.Delete(0); // Signature
514
    sl.AddStrings(sl2);
515
  finally
516
    FreeAndNil(sl2);
517
  end;
518
end;
519
 
520
procedure TSaveData.LoadFromStrings(sl: TStrings);
521
var
522
  curline: Integer;
523
begin
524
  if (sl.Strings[0] = '; SpaceMission 1.0') and
525
     (sl.Strings[1] = '; SAV-File') then
526
  begin
527
    Score    := StrToInt(sl.Strings[2]);
528
    Life     := StrToInt(sl.Strings[3]);
529
    Level    := StrToInt(sl.Strings[4]);
530
    GameMode := TGameMode(StrToInt(sl.Strings[5]));
531
    if Assigned(LevelData) then FreeAndNil(LevelData);
532
  end
533
  else if SameText(sl.Strings[0], '[SpaceMission Savegame, Format 1.2]') then
534
  begin
535
    Score    := 0;
536
    Life     := 0;
537
    Level    := 0;
538
    GameMode := gmUnknown;
539
    for curline := 1 to sl.Count-1 do
540
    begin
541
      // 1234567890123456789012345678901234567890
542
      // 123456 123456 123456 123456 123456 ; Kommentar
543
      if (sl.Strings[curline].Trim = '') or
544
         (Copy(sl.Strings[curline], 1, 1) = ';') then
545
        // Do nothing
546
      else if Copy(sl.Strings[curline], 1, 6).TrimRight = 'Score' then
547
      begin
548
        Score := StrToInt(TrimRight(Copy(sl.Strings[curline], 8, 6)))
549
      end
550
      else if Copy(sl.Strings[curline], 1, 6).TrimRight = 'Lives' then
551
      begin
552
        Life := StrToInt(TrimRight(Copy(sl.Strings[curline], 8, 6)))
553
      end
554
      else if Copy(sl.Strings[curline], 1, 6).TrimRight = 'Level' then
555
      begin
556
        Level := StrToInt(TrimRight(Copy(sl.Strings[curline], 8, 6)))
557
      end
558
      else if Copy(sl.Strings[curline], 1, 6).TrimRight = 'Mode' then
559
      begin
560
        GameMode := TGameMode(StrToInt(TrimRight(Copy(sl.Strings[curline], 8, 6))))
561
      end;
562
    end;
563
    if Assigned(LevelData) then FreeAndNil(LevelData);
564
    LevelData := TLevelData.Create;
565
    LevelData.LoadFromStrings(sl);
566
  end
567
  else
568
  begin
569
    raise Exception.Create('Spielstand-Format nicht unterstützt oder Datei beschädigt');
570
  end;
571
end;
572
 
573
procedure TSaveData.LoadFromFile(filename: string);
574
var
575
  sl: TStringList;
576
begin
577
  sl := TStringList.Create;
578
  try
579
    sl.LoadFromFile(filename);
580
    LoadFromStrings(sl);
581
  finally
582
    FreeAndNil(sl);
583
  end;
584
end;
585
 
586
procedure TSaveData.SaveToFile(filename: string);
587
var
588
  sl: TStringList;
589
begin
590
  sl := TStringList.Create;
591
  try
592
    SaveToStrings(sl);
593
    sl.SaveToFile(filename);
594
  finally
595
    FreeAndNil(sl);
596
  end;
597
end;
598
 
14 daniel-mar 599
end.