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. |