saveload: fix read/write unexisting value
[d2df-sdl.git] / src / game / g_playermodel.pas
blob1918574a8bcfb5ec9be543ec27cede4a1b58b02f
1 (* Copyright (C) Doom 2D: Forever Developers
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, version 3 of the License ONLY.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 {$INCLUDE ../shared/a_modes.inc}
16 {$M+}
17 unit g_playermodel;
19 interface
21 uses
22 {$IFDEF USE_MEMPOOL}mempool,{$ENDIF}
23 {$IFDEF ENABLE_SOUND}
24 e_sound,
25 {$ENDIF}
26 MAPDEF, g_textures, g_basic, g_weapons, e_graphics, utils, g_gfx,
27 ImagingTypes, Imaging, ImagingUtility;
29 const
30 A_STAND = 0;
31 A_WALK = 1;
32 A_DIE1 = 2;
33 A_DIE2 = 3;
34 A_ATTACK = 4;
35 A_SEEUP = 5;
36 A_SEEDOWN = 6;
37 A_ATTACKUP = 7;
38 A_ATTACKDOWN = 8;
39 A_PAIN = 9;
40 // EXTENDED
41 A_WALKATTACK = 10;
42 A_WALKSEEUP = 11;
43 A_WALKSEEDOWN = 12;
44 A_WALKATTACKUP = 13;
45 A_WALKATTACKDOWN = 14;
46 A_MELEESTAND = 15;
47 A_MELEEWALK = 16;
48 A_MELEEATTACK = 17;
49 A_MELEEWALKATTACK = 18;
50 A_MELEESEEUP = 19;
51 A_MELEESEEDOWN = 20;
52 A_MELEEATTACKUP = 21;
53 A_MELEEATTACKDOWN = 22;
55 A_LASTBASE = A_PAIN;
56 A_LASTEXT = A_MELEEATTACKDOWN;
57 A_LAST = A_LASTEXT;
59 MODELSOUND_PAIN = 0;
60 MODELSOUND_DIE = 1;
62 type
63 TModelInfo = record
64 Name: String;
65 Author: String;
66 Description: String;
67 HaveWeapon: Boolean;
68 end;
70 TModelBlood = record
71 R, G, B, Kind: Byte;
72 end;
74 {$IFDEF ENABLE_SOUND}
75 TModelSound = record
76 ID: TSoundID;
77 Level: Byte;
78 end;
79 {$ENDIF}
81 TGibSprite = record
82 ID: DWORD;
83 MaskID: DWORD;
84 Rect: TRectWH;
85 OnlyOne: Boolean;
86 end;
88 {$IFDEF ENABLE_SOUND}
89 TModelSoundArray = Array of TModelSound;
90 {$ENDIF}
92 TGibsArray = Array of TGibSprite;
93 TWeaponPoints = Array [WP_FIRST + 1..WP_LAST] of
94 Array [A_STAND..A_LAST] of
95 Array [TDirection.D_LEFT..TDirection.D_RIGHT] of Array of TDFPoint;
97 TPlayerModel = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF}
98 private
99 FName: String;
100 FDirection: TDirection;
101 FColor: TRGB;
102 FBlood: TModelBlood;
103 FCurrentAnimation: Byte;
104 FAnim: Array [TDirection.D_LEFT..TDirection.D_RIGHT] of Array [A_STAND..A_LAST] of TAnimation;
105 FMaskAnim: Array [TDirection.D_LEFT..TDirection.D_RIGHT] of Array [A_STAND..A_LAST] of TAnimation;
106 FWeaponPoints: TWeaponPoints;
107 {$IFDEF ENABLE_SOUND}
108 FPainSounds: TModelSoundArray;
109 FDieSounds: TModelSoundArray;
110 FSlopSound: Byte;
111 {$ENDIF}
112 FCurrentWeapon: Byte;
113 FDrawWeapon: Boolean;
114 FFlag: Byte;
115 FFlagPoint: TDFPoint;
116 FFlagAngle: SmallInt;
117 FFlagAnim: TAnimation;
118 FFire: Boolean;
119 FFireCounter: Byte;
121 public
122 destructor Destroy(); override;
123 procedure ChangeAnimation(Animation: Byte; Force: Boolean = False);
124 function GetCurrentAnimation: TAnimation;
125 function GetCurrentAnimationMask: TAnimation;
126 procedure SetColor(Red, Green, Blue: Byte);
127 procedure SetWeapon(Weapon: Byte);
128 procedure SetFlag(Flag: Byte);
129 procedure SetFire(Fire: Boolean);
130 {$IFDEF ENABLE_SOUND}
131 function PlaySound(SoundType, Level: Byte; X, Y: Integer): Boolean;
132 {$ENDIF}
133 procedure Update();
134 procedure Draw(X, Y: Integer; Alpha: Byte = 0);
136 published
137 property Fire: Boolean read FFire;
138 property Direction: TDirection read FDirection write FDirection;
139 property Animation: Byte read FCurrentAnimation;
140 property Weapon: Byte read FCurrentWeapon;
141 property Name: String read FName;
143 public
144 property Color: TRGB read FColor write FColor;
145 property Blood: TModelBlood read FBlood;
146 end;
148 procedure g_PlayerModel_LoadData();
149 procedure g_PlayerModel_FreeData();
150 function g_PlayerModel_Load(FileName: String): Boolean;
151 function g_PlayerModel_GetNames(): SSArray;
152 function g_PlayerModel_GetInfo(ModelName: String): TModelInfo;
153 function g_PlayerModel_GetBlood(ModelName: String): TModelBlood;
154 function g_PlayerModel_Get(ModelName: String): TPlayerModel;
155 function g_PlayerModel_GetAnim(ModelName: String; Anim: Byte; var _Anim, _Mask: TAnimation): Boolean;
156 function g_PlayerModel_GetGibs(ModelName: String; var Gibs: TGibsArray): Boolean;
159 implementation
161 uses
162 {$INCLUDE ../nogl/noGLuses.inc}
163 {$IFDEF ENABLE_SOUND}
164 g_sound,
165 {$ENDIF}
166 g_main, g_console, SysUtils, g_player, CONFIG,
167 g_options, g_map, Math, e_log, wadreader;
169 type
170 TPlayerModelInfo = record
171 Info: TModelInfo;
172 ModelSpeed: Array [A_STAND..A_PAIN] of Byte;
173 FlagPoint: TDFPoint;
174 FlagAngle: SmallInt;
175 WeaponPoints: TWeaponPoints;
176 Gibs: TGibsArray;
177 {$IFDEF ENABLE_SOUND}
178 PainSounds: TModelSoundArray;
179 DieSounds: TModelSoundArray;
180 SlopSound: Byte;
181 {$ENDIF}
182 Blood: TModelBlood;
183 end;
185 const
186 W_POS_NORMAL = 0;
187 W_POS_UP = 1;
188 W_POS_DOWN = 2;
190 W_ACT_NORMAL = 0;
191 W_ACT_FIRE = 1;
193 FLAG_BASEPOINT: TDFPoint = (X:16; Y:43);
194 FLAG_DEFPOINT: TDFPoint = (X:32; Y:16);
195 FLAG_DEFANGLE = -20;
196 WEAPONBASE: Array [WP_FIRST + 1..WP_LAST] of TDFPoint =
197 ((X:8; Y:4), (X:8; Y:8), (X:16; Y:16), (X:16; Y:24),
198 (X:16; Y:16), (X:24; Y:24), (X:16; Y:16), (X:24; Y:24),
199 (X:16; Y:16), (X:8; Y:8));
201 AnimNames: Array [A_STAND..A_LASTEXT] of String =
202 ('StandAnim','WalkAnim','Die1Anim','Die2Anim','AttackAnim',
203 'SeeUpAnim','SeeDownAnim','AttackUpAnim','AttackDownAnim','PainAnim',
204 // EXTENDED
205 'WalkAttackAnim', 'WalkSeeUpAnim', 'WalkSeeDownAnim',
206 'WalkAttackUpAnim', 'WalkAttackDownAnim', 'MeleeStandAnim', 'MeleeWalkAnim',
207 'MeleeAttackAnim', 'MeleeWalkAttackAnim', 'MeleeSeeUpAnim', 'MeleeSeeDownAnim',
208 'MeleeAttackUpAnim', 'MeleeAttackDownAnim');
209 WeapNames: Array [WP_FIRST + 1..WP_LAST] of String =
210 ('csaw', 'hgun', 'sg', 'ssg', 'mgun', 'rkt', 'plz', 'bfg', 'spl', 'flm');
213 WeaponID: Array [WP_FIRST + 1..WP_LAST] of
214 Array [W_POS_NORMAL..W_POS_DOWN] of
215 Array [W_ACT_NORMAL..W_ACT_FIRE] of DWORD;
216 PlayerModelsArray: Array of TPlayerModelInfo;
218 procedure g_PlayerModel_LoadData();
220 a: Integer;
221 begin
222 for a := WP_FIRST + 1 to WP_LAST do
223 begin
224 g_Texture_CreateWAD(WeaponID[a][W_POS_NORMAL][W_ACT_NORMAL], GameWAD+':WEAPONS\'+UpperCase(WeapNames[a]));
225 g_Texture_CreateWAD(WeaponID[a][W_POS_NORMAL][W_ACT_FIRE], GameWAD+':WEAPONS\'+UpperCase(WeapNames[a])+'_FIRE');
226 g_Texture_CreateWAD(WeaponID[a][W_POS_UP][W_ACT_NORMAL], GameWAD+':WEAPONS\'+UpperCase(WeapNames[a])+'_UP');
227 g_Texture_CreateWAD(WeaponID[a][W_POS_UP][W_ACT_FIRE], GameWAD+':WEAPONS\'+UpperCase(WeapNames[a])+'_UP_FIRE');
228 g_Texture_CreateWAD(WeaponID[a][W_POS_DOWN][W_ACT_NORMAL], GameWAD+':WEAPONS\'+UpperCase(WeapNames[a])+'_DN');
229 g_Texture_CreateWAD(WeaponID[a][W_POS_DOWN][W_ACT_FIRE], GameWAD+':WEAPONS\'+UpperCase(WeapNames[a])+'_DN_FIRE');
230 end;
231 end;
233 function GetPoint(var str: String; var point: TDFPoint): Boolean;
235 a, x, y: Integer;
236 s: String;
237 begin
238 Result := False;
239 x := 0;
240 y := 0;
242 str := Trim(str);
243 if Length(str) < 3 then
244 Exit;
246 for a := 1 to Length(str) do
247 if (str[a] = ',') or (a = Length(str)) then
248 begin
249 s := Copy(str, 1, a);
250 if s[Length(s)] = ',' then
251 SetLength(s, Length(s)-1);
252 Delete(str, 1, a);
254 if (Sscanf(s, '%d:%d', [@x, @y]) < 2) or
255 (x < -64) or (x > 128) or
256 (y < -64) or (y > 128) then
257 Exit;
259 point.X := x;
260 point.Y := y;
262 Break;
263 end;
265 Result := True;
266 end;
268 function GetWeapPoints(str: String; weapon: Byte; anim: Byte; dir: TDirection;
269 frames: Word; backanim: Boolean; var wpoints: TWeaponPoints): Boolean;
271 a, b, h: Integer;
272 begin
273 Result := False;
275 if frames = 0 then
276 Exit;
278 backanim := backanim and (frames > 2);
280 for a := 1 to frames do
281 begin
282 if not GetPoint(str, wpoints[weapon, anim, dir, a-1]) then
283 Exit;
285 with wpoints[weapon, anim, dir, a-1] do
286 begin
287 X := X - WEAPONBASE[weapon].X;
288 Y := Y - WEAPONBASE[weapon].Y;
289 if dir = TDirection.D_LEFT then
290 X := -X;
291 end;
292 end;
294 h := High(wpoints[weapon, anim, dir]);
295 if backanim then
296 for b := h downto frames do
297 wpoints[weapon, anim, dir, b] := wpoints[weapon, anim, dir, h-b+1];
299 Result := True;
300 end;
302 procedure ExtAnimFromBaseAnim(MName: String; AIdx: Integer);
303 const
304 CopyAnim: array [A_LASTBASE+1..A_LASTEXT] of Integer = (
305 A_WALK, A_WALK, A_WALK, A_WALK, A_WALK,
306 A_STAND, A_WALK, A_ATTACK, A_WALK, A_SEEUP, A_SEEDOWN,
307 A_ATTACKUP, A_ATTACKDOWN
310 OIdx, W, I: Integer;
311 D: TDirection;
312 AName, OName: String;
313 begin
314 // HACK: shitty workaround to duplicate base animations
315 // in place of extended, replace with something better later
317 Assert((AIdx > A_LASTBASE) and (AIdx <= A_LASTEXT));
318 OIdx := CopyAnim[AIdx];
320 AName := MName + '_RIGHTANIM' + IntToStr(AIdx);
321 OName := MName + '_RIGHTANIM' + IntToStr(OIdx);
322 Assert(g_Frames_Dup(AName, OName));
323 Assert(g_Frames_Dup(AName + '_MASK', OName + '_MASK'));
324 AName := MName + '_LEFTANIM' + IntToStr(AIdx);
325 OName := MName + '_LEFTANIM' + IntToStr(OIdx);
326 if g_Frames_Exists(AName) then
327 begin
328 g_Frames_Dup(AName, OName);
329 g_Frames_Dup(AName + '_MASK', OName + '_MASK');
330 end;
332 with PlayerModelsArray[High(PlayerModelsArray)] do
333 begin
334 for W := WP_FIRST + 1 to WP_LAST do
335 begin
336 for D := TDirection.D_LEFT to TDirection.D_RIGHT do
337 begin
338 SetLength(WeaponPoints[W, AIdx, D], Length(WeaponPoints[W, OIdx, D]));
339 for I := 0 to High(WeaponPoints[W, AIdx, D]) do
340 WeaponPoints[W, AIdx, D, I] := WeaponPoints[W, OIdx, D, I]
341 end;
342 end;
343 end;
344 end;
346 function g_PlayerModel_CalcGibSize (pData: Pointer; dataSize, x, y, w, h: Integer): TRectWH;
347 var i, j: Integer; done: Boolean; img: TImageData;
349 function IsVoid (i, j: Integer): Boolean;
350 begin
351 result := Byte((PByte(img.bits) + (y+j)*img.width*4 + (x+i)*4 + 3)^) = 0
352 end;
354 begin
355 InitImage(img);
356 assert(LoadImageFromMemory(pData, dataSize, img));
358 (* trace x from right to left *)
359 done := false; i := 0;
360 while not done and (i < w) do
361 begin
362 j := 0;
363 while (j < h) and IsVoid(i, j) do inc(j);
364 done := (j < h) and (IsVoid(i, j) = false);
365 result.x := i;
366 inc(i);
367 end;
369 (* trace y from up to down *)
370 done := false; j := 0;
371 while not done and (j < h) do
372 begin
373 i := 0;
374 while (i < w) and IsVoid(i, j) do inc(i);
375 done := (i < w) and (IsVoid(i, j) = false);
376 result.y := j;
377 inc(j);
378 end;
380 (* trace x from right to left *)
381 done := false; i := w - 1;
382 while not done and (i >= 0) do
383 begin
384 j := 0;
385 while (j < h) and IsVoid(i, j) do inc(j);
386 done := (j < h) and (IsVoid(i, j) = false);
387 result.width := i - result.x + 1;
388 dec(i);
389 end;
391 (* trace y from down to up *)
392 done := false; j := h - 1;
393 while not done and (j >= 0) do
394 begin
395 i := 0;
396 while (i < w) and IsVoid(i, j) do inc(i);
397 done := (i < w) and (IsVoid(i, j) = false);
398 result.height := j - result.y + 1;
399 dec(j);
400 end;
402 FreeImage(img);
403 end;
405 function g_PlayerModel_Load(FileName: string): Boolean;
407 ID: DWORD;
408 a, b, len, lenpd, lenpd2, aa, bb, f: Integer;
409 cc: TDirection;
410 config: TConfig;
411 pData, pData2: Pointer;
412 WAD: TWADFile;
413 s, aname: string;
414 prefix: string;
415 ok, chk: Boolean;
416 begin
417 e_WriteLog(Format('Loading player model "%s"...', [FileName]), TMsgType.Notify);
419 Result := False;
421 WAD := TWADFile.Create;
422 WAD.ReadFile(FileName);
424 if {WAD.GetLastError <> DFWAD_NOERROR} not WAD.isOpen then
425 begin
426 WAD.Free();
427 Exit;
428 end;
430 if not WAD.GetResource('TEXT/MODEL', pData, len) then
431 begin
432 WAD.Free();
433 Exit;
434 end;
436 config := TConfig.CreateMem(pData, len);
437 FreeMem(pData);
439 s := config.ReadStr('Model', 'name', '');
440 if s = '' then
441 begin
442 config.Free();
443 WAD.Free();
444 Exit;
445 end;
447 SetLength(PlayerModelsArray, Length(PlayerModelsArray)+1);
448 ID := High(PlayerModelsArray);
450 prefix := FileName+':TEXTURES\';
452 with PlayerModelsArray[ID].Info do
453 begin
454 Name := s;
455 Author := config.ReadStr('Model', 'author', '');
456 Description := config.ReadStr('Model', 'description', '');
457 end;
459 with PlayerModelsArray[ID] do
460 begin
461 Blood.R := MAX(0, MIN(255, config.ReadInt('Blood', 'R', 150)));
462 Blood.G := MAX(0, MIN(255, config.ReadInt('Blood', 'G', 0)));
463 Blood.B := MAX(0, MIN(255, config.ReadInt('Blood', 'B', 0)));
464 case config.ReadStr('Blood', 'Kind', 'NORMAL') of
465 'NORMAL': Blood.Kind := BLOOD_NORMAL;
466 'SPARKS': Blood.Kind := BLOOD_CSPARKS;
467 'COMBINE': Blood.Kind := BLOOD_COMBINE;
468 else
469 Blood.Kind := BLOOD_NORMAL
471 end;
473 for b := A_STAND to A_LAST do
474 begin
475 aname := s+'_RIGHTANIM'+IntToStr(b);
476 //e_LogWritefln('### MODEL FILE: [%s]', [prefix+config.ReadStr(AnimNames[b], 'resource', '')]);
477 if not (g_Frames_CreateWAD(nil, aname,
478 prefix+config.ReadStr(AnimNames[b], 'resource', ''),
479 64, 64, config.ReadInt(AnimNames[b], 'frames', 1),
480 config.ReadBool(AnimNames[b], 'backanim', False)) and
481 g_Frames_CreateWAD(nil, aname+'_MASK',
482 prefix+config.ReadStr(AnimNames[b], 'mask', ''),
483 64, 64, config.ReadInt(AnimNames[b], 'frames', 1),
484 config.ReadBool(AnimNames[b], 'backanim', False))) then
485 begin
486 if b <= A_LASTBASE then
487 begin
488 config.Free();
489 WAD.Free();
490 Exit;
492 else
493 begin
494 ExtAnimFromBaseAnim(s, b);
495 continue;
496 end;
497 end;
499 for aa := WP_FIRST + 1 to WP_LAST do
500 for bb := A_STAND to A_LAST do
501 for cc := TDirection.D_LEFT to TDirection.D_RIGHT do
502 begin
503 f := config.ReadInt(AnimNames[bb], 'frames', 1);
504 if config.ReadBool(AnimNames[bb], 'backanim', False) then
505 if f > 2 then f := 2*f-2;
506 SetLength(PlayerModelsArray[ID].WeaponPoints[aa, bb, cc], f);
507 end;
509 if (config.ReadStr(AnimNames[b], 'resource2', '') <> '') and
510 (config.ReadStr(AnimNames[b], 'mask2', '') <> '') then
511 begin
512 aname := s+'_LEFTANIM'+IntToStr(b);
513 g_Frames_CreateWAD(nil, aname,
514 prefix+config.ReadStr(AnimNames[b], 'resource2', ''),
515 64, 64, config.ReadInt(AnimNames[b], 'frames', 1),
516 config.ReadBool(AnimNames[b], 'backanim', False));
518 g_Frames_CreateWAD(nil, aname+'_MASK',
519 prefix+config.ReadStr(AnimNames[b], 'mask2', ''),
520 64, 64, config.ReadInt(AnimNames[b], 'frames', 1),
521 config.ReadBool(AnimNames[b], 'backanim', False));
522 end;
524 PlayerModelsArray[ID].ModelSpeed[b] := Max(1, config.ReadInt(AnimNames[b], 'waitcount', 1) div 3);
525 end;
527 with PlayerModelsArray[ID], config do
528 begin
529 {$IFDEF ENABLE_SOUND}
530 prefix := FileName+':SOUNDS\';
532 a := 1;
533 repeat
534 s := config.ReadStr('Sound', 'pain'+IntToStr(a), '');
535 if s <> '' then
536 begin
537 SetLength(PainSounds, Length(PainSounds)+1);
538 g_Sound_CreateWAD(PainSounds[High(PainSounds)].ID, prefix+s);
539 PainSounds[High(PainSounds)].Level := config.ReadInt('Sound', 'painlevel'+IntToStr(a), 1);
540 end;
541 a := a+1;
542 until s = '';
544 a := 1;
545 repeat
546 s := config.ReadStr('Sound', 'die'+IntToStr(a), '');
547 if s <> '' then
548 begin
549 SetLength(DieSounds, Length(DieSounds)+1);
550 g_Sound_CreateWAD(DieSounds[High(DieSounds)].ID, prefix+s);
551 DieSounds[High(DieSounds)].Level := config.ReadInt('Sound', 'dielevel'+IntToStr(a), 1);
552 end;
553 a := a+1;
554 until s = '';
556 SlopSound := Min(Max(config.ReadInt('Sound', 'slop', 0), 0), 2);
557 {$ENDIF}
559 SetLength(Gibs, ReadInt('Gibs', 'count', 0));
561 if (Gibs <> nil) and
562 (WAD.GetResource('TEXTURES/'+config.ReadStr('Gibs', 'resource', 'GIBS'), pData, lenpd)) and
563 (WAD.GetResource('TEXTURES/'+config.ReadStr('Gibs', 'mask', 'GIBSMASK'), pData2, lenpd2)) then
564 begin
565 for a := 0 to High(Gibs) do
566 if e_CreateTextureMemEx(pData, lenpd, Gibs[a].ID, a*32, 0, 32, 32) and
567 e_CreateTextureMemEx(pData2, lenpd2, Gibs[a].MaskID, a*32, 0, 32, 32) then
568 begin
569 //Gibs[a].Rect := e_GetTextureSize2(Gibs[a].ID);
570 Gibs[a].Rect := g_PlayerModel_CalcGibSize(pData, lenpd, a*32, 0, 32, 32);
571 with Gibs[a].Rect do
572 if Height > 3 then Height := Height-1-Random(2);
573 Gibs[a].OnlyOne := config.ReadInt('Gibs', 'once', -1) = a+1;
574 end;
576 FreeMem(pData);
577 FreeMem(pData2);
578 end;
580 ok := True;
581 for aa := WP_FIRST + 1 to WP_LAST do
582 for bb := A_STAND to A_LAST do
583 if not (bb in [A_DIE1, A_DIE2, A_PAIN]) then
584 begin
585 chk := GetWeapPoints(config.ReadStr(AnimNames[bb], WeapNames[aa]+'_points', ''), aa, bb, TDirection.D_RIGHT,
586 config.ReadInt(AnimNames[bb], 'frames', 0),
587 config.ReadBool(AnimNames[bb], 'backanim', False),
588 WeaponPoints);
589 if ok and (not chk) and (aa = WEAPON_FLAMETHROWER) then
590 begin
591 // workaround for flamethrower
592 chk := GetWeapPoints(config.ReadStr(AnimNames[bb], WeapNames[WEAPON_PLASMA]+'_points', ''), aa, bb, TDirection.D_RIGHT,
593 config.ReadInt(AnimNames[bb], 'frames', 0),
594 config.ReadBool(AnimNames[bb], 'backanim', False),
595 WeaponPoints);
596 if chk then
597 for f := 0 to High(WeaponPoints[aa, bb, TDirection.D_RIGHT]) do
598 begin
599 case bb of
600 A_STAND, A_PAIN:
601 begin
602 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 6);
603 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 8);
604 end;
605 A_WALKATTACK, A_WALK:
606 begin
607 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 9);
608 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 9);
609 end;
610 A_ATTACK:
611 begin
612 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 5);
613 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 8);
614 end;
615 A_WALKSEEUP, A_SEEUP:
616 begin
617 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 5);
618 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 16);
619 end;
620 A_WALKSEEDOWN, A_SEEDOWN:
621 begin
622 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 6);
623 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 5);
624 end;
625 A_WALKATTACKUP, A_ATTACKUP:
626 begin
627 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 5);
628 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 16);
629 end;
630 A_WALKATTACKDOWN, A_ATTACKDOWN:
631 begin
632 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 6);
633 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 4);
634 end;
635 end;
636 end;
637 end;
638 ok := ok and (chk or (bb > A_LASTBASE));
640 if not GetWeapPoints(config.ReadStr(AnimNames[bb], WeapNames[aa]+'2_points', ''), aa, bb, TDirection.D_LEFT,
641 config.ReadInt(AnimNames[bb], 'frames', 0),
642 config.ReadBool(AnimNames[bb], 'backanim', False),
643 WeaponPoints) then
644 for f := 0 to High(WeaponPoints[aa, bb, TDirection.D_RIGHT]) do
645 begin
646 WeaponPoints[aa, bb, TDirection.D_LEFT, f].X := -WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X;
647 WeaponPoints[aa, bb, TDirection.D_LEFT, f].Y := WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y;
648 end;
650 if not ok then Break;
651 end;
652 {if ok then g_Console_Add(Info.Name+' weapon points ok')
653 else g_Console_Add(Info.Name+' weapon points fail');}
654 Info.HaveWeapon := ok;
656 s := config.ReadStr('Model', 'flag_point', '');
657 if not GetPoint(s, FlagPoint) then FlagPoint := FLAG_DEFPOINT;
659 FlagAngle := config.ReadInt('Model', 'flag_angle', FLAG_DEFANGLE);
660 end;
662 config.Free();
663 WAD.Free();
665 Result := True;
666 end;
668 function g_PlayerModel_Get(ModelName: String): TPlayerModel;
670 a: Integer;
671 b: Byte;
672 ID, ID2: DWORD;
673 begin
674 Result := nil;
676 if PlayerModelsArray = nil then Exit;
678 for a := 0 to High(PlayerModelsArray) do
679 if AnsiLowerCase(PlayerModelsArray[a].Info.Name) = AnsiLowerCase(ModelName) then
680 begin
681 Result := TPlayerModel.Create;
683 with PlayerModelsArray[a] do
684 begin
685 Result.FName := Info.Name;
686 Result.FBlood := Blood;
688 for b := A_STAND to A_LAST do
689 begin
690 if not (g_Frames_Get(ID, Info.Name+'_RIGHTANIM'+IntToStr(b)) and
691 g_Frames_Get(ID2, Info.Name+'_RIGHTANIM'+IntToStr(b)+'_MASK')) then
692 begin
693 FreeAndNil(Result);
694 Exit;
695 end;
697 Result.FAnim[TDirection.D_RIGHT][b] := TAnimation.Create(ID, b in [A_STAND, A_WALK], ModelSpeed[b]);
698 Result.FMaskAnim[TDirection.D_RIGHT][b] := TAnimation.Create(ID2, b in [A_STAND, A_WALK], ModelSpeed[b]);
700 if g_Frames_Exists(Info.Name+'_LEFTANIM'+IntToStr(b)) and
701 g_Frames_Exists(Info.Name+'_LEFTANIM'+IntToStr(b)+'_MASK') then
702 if g_Frames_Get(ID, Info.Name+'_LEFTANIM'+IntToStr(b)) and
703 g_Frames_Get(ID2, Info.Name+'_LEFTANIM'+IntToStr(b)+'_MASK') then
704 begin
705 Result.FAnim[TDirection.D_LEFT][b] := TAnimation.Create(ID, b in [A_STAND, A_WALK], ModelSpeed[b]);
706 Result.FMaskAnim[TDirection.D_LEFT][b] := TAnimation.Create(ID2, b in [A_STAND, A_WALK], ModelSpeed[b]);
707 end;
708 end;
710 {$IFDEF ENABLE_SOUND}
711 Result.FPainSounds := PainSounds;
712 Result.FDieSounds := DieSounds;
713 Result.FSlopSound := SlopSound;
714 {$ENDIF}
715 Result.FDrawWeapon := Info.HaveWeapon;
716 Result.FWeaponPoints := WeaponPoints;
718 Result.FFlagPoint := FlagPoint;
719 Result.FFlagAngle := FlagAngle;
721 Break;
722 end;
723 end;
724 end;
726 function g_PlayerModel_GetAnim(ModelName: string; Anim: Byte; var _Anim, _Mask: TAnimation): Boolean;
728 a: Integer;
729 c: Boolean;
730 ID: DWORD;
731 begin
732 _Anim := nil;
733 _Mask := nil;
734 Result := False;
736 for a := 0 to High(PlayerModelsArray) do
737 if PlayerModelsArray[a].Info.Name = ModelName then
738 with PlayerModelsArray[a] do
739 begin
740 c := Anim in [A_STAND, A_WALK];
742 if not g_Frames_Get(ID, Info.Name+'_RIGHTANIM'+IntToStr(Anim)) then
743 if not g_Frames_Get(ID, Info.Name+'_LEFTANIM'+IntToStr(Anim)) then
744 Exit;
746 _Anim := TAnimation.Create(ID, c, ModelSpeed[Anim]);
747 _Anim.Speed := ModelSpeed[Anim];
749 if not g_Frames_Get(ID, Info.Name+'_RIGHTANIM'+IntToStr(Anim)+'_MASK') then
750 if not g_Frames_Get(ID, Info.Name+'_LEFTANIM'+IntToStr(Anim)+'_MASK') then
751 Exit;
753 _Mask := TAnimation.Create(ID, c, ModelSpeed[Anim]);
754 _Mask.Speed := ModelSpeed[Anim];
756 Result := True;
757 Exit;
758 end;
759 end;
761 function g_PlayerModel_GetGibs(ModelName: string; var Gibs: TGibsArray): Boolean;
763 a, i, b: Integer;
764 c: Boolean;
765 begin
766 Result := False;
768 if PlayerModelsArray = nil then Exit;
769 if gGibsCount = 0 then Exit;
771 c := False;
773 SetLength(Gibs, gGibsCount);
775 for a := 0 to High(PlayerModelsArray) do
776 if PlayerModelsArray[a].Info.Name = ModelName then
777 begin
778 for i := 0 to High(Gibs) do
779 begin
780 if c and (Length(PlayerModelsArray[a].Gibs) = 1) then
781 begin
782 SetLength(Gibs, i);
783 Break;
784 end;
786 repeat
787 b := Random(Length(PlayerModelsArray[a].Gibs));
788 until not (PlayerModelsArray[a].Gibs[b].OnlyOne and c);
790 Gibs[i] := PlayerModelsArray[a].Gibs[b];
792 if Gibs[i].OnlyOne then c := True;
793 end;
795 Result := True;
796 Break;
797 end;
798 end;
800 function g_PlayerModel_GetNames(): SSArray;
802 i: DWORD;
803 begin
804 Result := nil;
806 if PlayerModelsArray = nil then Exit;
808 for i := 0 to High(PlayerModelsArray) do
809 begin
810 SetLength(Result, Length(Result)+1);
811 Result[High(Result)] := PlayerModelsArray[i].Info.Name;
812 end;
813 end;
815 function g_PlayerModel_GetInfo(ModelName: string): TModelInfo;
817 a: Integer;
818 begin
819 Result := Default(TModelInfo);
820 if PlayerModelsArray = nil then Exit;
822 for a := 0 to High(PlayerModelsArray) do
823 if PlayerModelsArray[a].Info.Name = ModelName then
824 begin
825 Result := PlayerModelsArray[a].Info;
826 Break;
827 end;
828 end;
830 function g_PlayerModel_GetBlood(ModelName: string): TModelBlood;
832 a: Integer;
833 begin
834 Result.R := 150;
835 Result.G := 0;
836 Result.B := 0;
837 Result.Kind := BLOOD_NORMAL;
838 if PlayerModelsArray = nil then Exit;
840 for a := 0 to High(PlayerModelsArray) do
841 if PlayerModelsArray[a].Info.Name = ModelName then
842 begin
843 Result := PlayerModelsArray[a].Blood;
844 Break;
845 end;
846 end;
848 procedure g_PlayerModel_FreeData();
850 i: DWORD;
851 a, b, c: Integer;
852 begin
853 for a := WP_FIRST + 1 to WP_LAST do
854 for b := W_POS_NORMAL to W_POS_DOWN do
855 for c := W_ACT_NORMAL to W_ACT_FIRE do
856 e_DeleteTexture(WeaponID[a][b][c]);
858 e_WriteLog('Releasing models...', TMsgType.Notify);
860 if PlayerModelsArray = nil then Exit;
862 for i := 0 to High(PlayerModelsArray) do
863 with PlayerModelsArray[i] do
864 begin
865 for a := A_STAND to A_LAST do
866 begin
867 g_Frames_DeleteByName(Info.Name+'_LEFTANIM'+IntToStr(a));
868 g_Frames_DeleteByName(Info.Name+'_LEFTANIM'+IntToStr(a)+'_MASK');
869 g_Frames_DeleteByName(Info.Name+'_RIGHTANIM'+IntToStr(a));
870 g_Frames_DeleteByName(Info.Name+'_RIGHTANIM'+IntToStr(a)+'_MASK');
871 end;
873 {$IFDEF ENABLE_SOUND}
874 if PainSounds <> nil then
875 for b := 0 to High(PainSounds) do
876 e_DeleteSound(PainSounds[b].ID);
878 if DieSounds <> nil then
879 for b := 0 to High(DieSounds) do
880 e_DeleteSound(DieSounds[b].ID);
881 {$ENDIF}
883 if Gibs <> nil then
884 for b := 0 to High(Gibs) do
885 begin
886 e_DeleteTexture(Gibs[b].ID);
887 e_DeleteTexture(Gibs[b].MaskID);
888 end;
889 end;
891 PlayerModelsArray := nil;
892 end;
894 { TPlayerModel }
896 procedure TPlayerModel.ChangeAnimation(Animation: Byte; Force: Boolean = False);
897 begin
898 if not Force then if FCurrentAnimation = Animation then Exit;
900 FCurrentAnimation := Animation;
902 if (FDirection = TDirection.D_LEFT) and
903 (FAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) and
904 (FMaskAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
905 begin
906 FAnim[TDirection.D_LEFT][FCurrentAnimation].Reset;
907 FMaskAnim[TDirection.D_LEFT][FCurrentAnimation].Reset;
909 else
910 begin
911 FAnim[TDirection.D_RIGHT][FCurrentAnimation].Reset;
912 FMaskAnim[TDirection.D_RIGHT][FCurrentAnimation].Reset;
913 end;
914 end;
916 destructor TPlayerModel.Destroy();
918 a: Byte;
919 begin
920 for a := A_STAND to A_LAST do
921 begin
922 FAnim[TDirection.D_LEFT][a].Free();
923 FMaskAnim[TDirection.D_LEFT][a].Free();
924 FAnim[TDirection.D_RIGHT][a].Free();
925 FMaskAnim[TDirection.D_RIGHT][a].Free();
926 end;
928 inherited;
929 end;
931 procedure TPlayerModel.Draw(X, Y: Integer; Alpha: Byte = 0);
933 Mirror: TMirrorType;
934 pos, act: Byte;
935 p: TDFPoint;
936 begin
937 // Ôëàãè:
938 if Direction = TDirection.D_LEFT then
939 Mirror := TMirrorType.None
940 else
941 Mirror := TMirrorType.Horizontal;
943 if (FFlag <> FLAG_NONE) and (FFlagAnim <> nil) and
944 (not (FCurrentAnimation in [A_DIE1, A_DIE2])) then
945 begin
946 p.X := IfThen(Direction = TDirection.D_LEFT,
947 FLAG_BASEPOINT.X,
948 64-FLAG_BASEPOINT.X);
949 p.Y := FLAG_BASEPOINT.Y;
951 FFlagAnim.DrawEx(X+IfThen(Direction = TDirection.D_LEFT, FFlagPoint.X-1, 2*FLAG_BASEPOINT.X-FFlagPoint.X+1)-FLAG_BASEPOINT.X,
952 Y+FFlagPoint.Y-FLAG_BASEPOINT.Y+1, Mirror, p,
953 IfThen(FDirection = TDirection.D_RIGHT, FFlagAngle, -FFlagAngle));
954 end;
956 // Îðóæèå:
957 if Direction = TDirection.D_RIGHT
958 then Mirror := TMirrorType.None
959 else Mirror := TMirrorType.Horizontal;
961 if FDrawWeapon and
962 (not (FCurrentAnimation in [A_DIE1, A_DIE2, A_PAIN])) and
963 (FCurrentWeapon in [WP_FIRST + 1..WP_LAST]) then
964 begin
965 if FCurrentAnimation in [A_SEEUP, A_ATTACKUP] then
966 pos := W_POS_UP
967 else
968 if FCurrentAnimation in [A_SEEDOWN, A_ATTACKDOWN]
969 then pos := W_POS_DOWN
970 else pos := W_POS_NORMAL;
972 if (FCurrentAnimation in [A_ATTACK, A_ATTACKUP, A_ATTACKDOWN]) or FFire
973 then act := W_ACT_FIRE
974 else act := W_ACT_NORMAL;
976 if Alpha < 201 then
977 e_Draw(WeaponID[FCurrentWeapon][pos][act],
978 X+FWeaponPoints[FCurrentWeapon, FCurrentAnimation, FDirection,
979 FAnim[TDirection.D_RIGHT][FCurrentAnimation].CurrentFrame].X,
980 Y+FWeaponPoints[FCurrentWeapon, FCurrentAnimation, FDirection,
981 FAnim[TDirection.D_RIGHT][FCurrentAnimation].CurrentFrame].Y,
982 0, True, False, Mirror);
983 end;
985 // Ìîäåëü:
986 if (FDirection = TDirection.D_LEFT) and
987 (FAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
988 begin
989 FAnim[TDirection.D_LEFT][FCurrentAnimation].Alpha := Alpha;
990 FAnim[TDirection.D_LEFT][FCurrentAnimation].Draw(X, Y, TMirrorType.None);
992 else
993 begin
994 FAnim[TDirection.D_RIGHT][FCurrentAnimation].Alpha := Alpha;
995 FAnim[TDirection.D_RIGHT][FCurrentAnimation].Draw(X, Y, Mirror);
996 end;
998 // Ìàñêà ìîäåëè:
999 e_Colors := FColor;
1001 if (FDirection = TDirection.D_LEFT) and
1002 (FMaskAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
1003 begin
1004 FMaskAnim[TDirection.D_LEFT][FCurrentAnimation].Alpha := Alpha;
1005 FMaskAnim[TDirection.D_LEFT][FCurrentAnimation].Draw(X, Y, TMirrorType.None);
1007 else
1008 begin
1009 FMaskAnim[TDirection.D_RIGHT][FCurrentAnimation].Alpha := Alpha;
1010 FMaskAnim[TDirection.D_RIGHT][FCurrentAnimation].Draw(X, Y, Mirror);
1011 end;
1013 e_Colors.R := 255;
1014 e_Colors.G := 255;
1015 e_Colors.B := 255;
1016 end;
1018 function TPlayerModel.GetCurrentAnimation: TAnimation;
1019 begin
1020 if (FDirection = TDirection.D_LEFT) and (FAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
1021 Result := FAnim[TDirection.D_LEFT][FCurrentAnimation]
1022 else
1023 Result := FAnim[TDirection.D_RIGHT][FCurrentAnimation];
1024 end;
1026 function TPlayerModel.GetCurrentAnimationMask: TAnimation;
1027 begin
1028 if (FDirection = TDirection.D_LEFT) and (FMaskAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
1029 Result := FMaskAnim[TDirection.D_LEFT][FCurrentAnimation]
1030 else
1031 Result := FMaskAnim[TDirection.D_RIGHT][FCurrentAnimation];
1032 end;
1034 {$IFDEF ENABLE_SOUND}
1035 function TPlayerModel.PlaySound(SoundType, Level: Byte; X, Y: Integer): Boolean;
1037 TempArray: array of DWORD;
1038 a: Integer;
1039 begin
1040 Result := False;
1041 SetLength(TempArray, 0);
1043 if SoundType = MODELSOUND_PAIN then
1044 begin
1045 if FPainSounds = nil then Exit;
1047 for a := 0 to High(FPainSounds) do
1048 if FPainSounds[a].Level = Level then
1049 begin
1050 SetLength(TempArray, Length(TempArray)+1);
1051 TempArray[High(TempArray)] := FPainSounds[a].ID;
1052 end;
1054 else
1055 begin
1056 if (Level in [2, 3, 5]) and (FSlopSound > 0) then
1057 begin
1058 g_Sound_PlayExAt('SOUND_MONSTER_SLOP', X, Y);
1059 if FSlopSound = 1 then
1060 begin
1061 Result := True;
1062 Exit;
1063 end;
1064 end;
1065 if FDieSounds = nil then Exit;
1067 for a := 0 to High(FDieSounds) do
1068 if FDieSounds[a].Level = Level then
1069 begin
1070 SetLength(TempArray, Length(TempArray)+1);
1071 TempArray[High(TempArray)] := FDieSounds[a].ID;
1072 end;
1073 if (TempArray = nil) and (Level = 5) then
1074 begin
1075 g_Sound_PlayExAt('SOUND_MONSTER_SLOP', X, Y);
1076 Result := True;
1077 Exit;
1078 end;
1079 end;
1081 if TempArray = nil then Exit;
1083 g_Sound_PlayAt(TempArray[Random(Length(TempArray))], X, Y);
1085 Result := True;
1086 end;
1087 {$ENDIF}
1089 procedure TPlayerModel.SetColor(Red, Green, Blue: Byte);
1090 begin
1091 FColor.R := Red;
1092 FColor.G := Green;
1093 FColor.B := Blue;
1094 end;
1096 procedure TPlayerModel.SetFire(Fire: Boolean);
1097 begin
1098 FFire := Fire;
1100 if FFire then FFireCounter := FAnim[TDirection.D_RIGHT, A_ATTACK].Speed*FAnim[TDirection.D_RIGHT, A_ATTACK].TotalFrames
1101 else FFireCounter := 0;
1102 end;
1104 procedure TPlayerModel.SetFlag(Flag: Byte);
1106 id: DWORD;
1107 begin
1108 FFlag := Flag;
1110 FFlagAnim.Free();
1111 FFlagAnim := nil;
1113 case Flag of
1114 FLAG_RED: g_Frames_Get(id, 'FRAMES_FLAG_RED');
1115 FLAG_BLUE: g_Frames_Get(id, 'FRAMES_FLAG_BLUE');
1116 else Exit;
1117 end;
1119 FFlagAnim := TAnimation.Create(id, True, 8);
1120 end;
1122 procedure TPlayerModel.SetWeapon(Weapon: Byte);
1123 begin
1124 FCurrentWeapon := Weapon;
1125 end;
1127 procedure TPlayerModel.Update();
1128 begin
1129 if (FDirection = TDirection.D_LEFT) and (FAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
1130 FAnim[TDirection.D_LEFT][FCurrentAnimation].Update else FAnim[TDirection.D_RIGHT][FCurrentAnimation].Update;
1132 if (FDirection = TDirection.D_LEFT) and (FMaskAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
1133 FMaskAnim[TDirection.D_LEFT][FCurrentAnimation].Update else FMaskAnim[TDirection.D_RIGHT][FCurrentAnimation].Update;
1135 if FFlagAnim <> nil then FFlagAnim.Update;
1137 if FFireCounter > 0 then Dec(FFireCounter) else FFire := False;
1138 end;
1140 end.