DEADSOFTWARE

render: separate player model logic and drawing
[d2df-sdl.git] / src / game / g_playermodel.pas
1 (* Copyright (C) Doom 2D: Forever Developers
2 *
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.
6 *
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.
11 *
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/>.
14 *)
15 {$INCLUDE ../shared/a_modes.inc}
16 {$M+}
17 unit g_playermodel;
19 interface
21 uses
22 {$IFDEF USE_MEMPOOL}mempool,{$ENDIF}
23 MAPDEF, g_textures, g_basic, g_weapons, e_graphics, utils, g_gfx,
24 ImagingTypes, Imaging, ImagingUtility;
26 const
27 A_STAND = 0;
28 A_WALK = 1;
29 A_DIE1 = 2;
30 A_DIE2 = 3;
31 A_ATTACK = 4;
32 A_SEEUP = 5;
33 A_SEEDOWN = 6;
34 A_ATTACKUP = 7;
35 A_ATTACKDOWN = 8;
36 A_PAIN = 9;
37 // EXTENDED
38 A_WALKATTACK = 10;
39 A_WALKSEEUP = 11;
40 A_WALKSEEDOWN = 12;
41 A_WALKATTACKUP = 13;
42 A_WALKATTACKDOWN = 14;
43 A_FISTSTAND = 15;
44 A_FISTWALK = 16;
45 A_FISTATTACK = 17;
46 A_FISTWALKATTACK = 18;
47 A_FISTSEEUP = 19;
48 A_FISTSEEDOWN = 20;
49 A_FISTATTACKUP = 21;
50 A_FISTATTACKDOWN = 22;
52 A_LASTBASE = A_PAIN;
53 A_LASTEXT = A_FISTATTACKDOWN;
54 A_LAST = A_LASTEXT;
56 MODELSOUND_PAIN = 0;
57 MODELSOUND_DIE = 1;
59 W_POS_NORMAL = 0;
60 W_POS_UP = 1;
61 W_POS_DOWN = 2;
63 W_ACT_NORMAL = 0;
64 W_ACT_FIRE = 1;
66 FLAG_BASEPOINT: TDFPoint = (X:16; Y:43);
68 type
69 TModelInfo = record
70 Name: String;
71 Author: String;
72 Description: String;
73 HaveWeapon: Boolean;
74 end;
76 TModelBlood = record
77 R, G, B, Kind: Byte;
78 end;
80 TModelSound = record
81 ID: DWORD;
82 Level: Byte;
83 end;
85 TGibSprite = record
86 ID: DWORD;
87 MaskID: DWORD;
88 Rect: TRectWH;
89 OnlyOne: Boolean;
90 end;
92 TModelSoundArray = Array of TModelSound;
93 TGibsArray = Array of TGibSprite;
94 TWeaponPoints = Array [WP_FIRST + 1..WP_LAST] of
95 Array [A_STAND..A_LAST] of
96 Array [TDirection.D_LEFT..TDirection.D_RIGHT] of Array of TDFPoint;
97 TModelMatrix = Array [TDirection.D_LEFT..TDirection.D_RIGHT] of Array [A_STAND..A_LAST] of TAnimation;
99 TPlayerModel = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF}
100 private
101 FName: String;
102 FDirection: TDirection;
103 FColor: TRGB;
104 FBlood: TModelBlood;
105 FCurrentAnimation: Byte;
106 FAnim: TModelMatrix;
107 FMaskAnim: TModelMatrix;
108 FWeaponPoints: TWeaponPoints;
109 FPainSounds: TModelSoundArray;
110 FDieSounds: TModelSoundArray;
111 FSlopSound: Byte;
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 function PlaySound(SoundType, Level: Byte; X, Y: Integer): Boolean;
131 procedure Update();
133 published
134 property Fire: Boolean read FFire;
135 property Direction: TDirection read FDirection write FDirection;
136 property Animation: Byte read FCurrentAnimation;
137 property Weapon: Byte read FCurrentWeapon;
138 property Name: String read FName;
140 public
141 property Color: TRGB read FColor write FColor;
142 property Blood: TModelBlood read FBlood;
144 property Anim: TModelMatrix read FAnim;
145 property MaskAnim: TModelMatrix read FMaskAnim;
146 property CurrentAnimation: Byte read FCurrentAnimation;
148 property CurrentWeapon: Byte read FCurrentWeapon;
149 property DrawWeapon: Boolean read FDrawWeapon;
150 property WeaponPoints: TWeaponPoints read FWeaponPoints;
152 property Flag: Byte read FFlag;
153 property FlagAnim: TAnimation read FFlagAnim;
154 property FlagAngle: SmallInt read FFlagAngle;
155 property FlagPoint: TDFPoint read FFlagPoint;
156 end;
158 procedure g_PlayerModel_FreeData();
159 function g_PlayerModel_Load(FileName: String): Boolean;
160 function g_PlayerModel_GetNames(): SSArray;
161 function g_PlayerModel_GetInfo(ModelName: String): TModelInfo;
162 function g_PlayerModel_GetBlood(ModelName: String): TModelBlood;
163 function g_PlayerModel_Get(ModelName: String): TPlayerModel;
164 function g_PlayerModel_GetAnim(ModelName: String; Anim: Byte; var _Anim, _Mask: TAnimation): Boolean;
165 function g_PlayerModel_GetGibs(ModelName: String; var Gibs: TGibsArray): Boolean;
168 implementation
170 uses
171 {$INCLUDE ../nogl/noGLuses.inc}
172 g_main, g_sound, g_console, SysUtils, g_player, CONFIG,
173 e_sound, g_options, g_map, Math, e_log, wadreader;
175 type
176 TPlayerModelInfo = record
177 Info: TModelInfo;
178 ModelSpeed: Array [A_STAND..A_PAIN] of Byte;
179 FlagPoint: TDFPoint;
180 FlagAngle: SmallInt;
181 WeaponPoints: TWeaponPoints;
182 Gibs: TGibsArray;
183 PainSounds: TModelSoundArray;
184 DieSounds: TModelSoundArray;
185 SlopSound: Byte;
186 Blood: TModelBlood;
187 end;
189 const
190 FLAG_DEFPOINT: TDFPoint = (X:32; Y:16);
191 FLAG_DEFANGLE = -20;
192 WEAPONBASE: Array [WP_FIRST + 1..WP_LAST] of TDFPoint =
193 ((X:8; Y:4), (X:8; Y:8), (X:16; Y:16), (X:16; Y:24),
194 (X:16; Y:16), (X:24; Y:24), (X:16; Y:16), (X:24; Y:24),
195 (X:16; Y:16), (X:8; Y:8));
197 AnimNames: Array [A_STAND..A_LASTEXT] of String =
198 ('StandAnim','WalkAnim','Die1Anim','Die2Anim','AttackAnim',
199 'SeeUpAnim','SeeDownAnim','AttackUpAnim','AttackDownAnim','PainAnim',
200 // EXTENDED
201 'WalkAttackAnim', 'WalkSeeUpAnim', 'WalkSeeDownAnim',
202 'WalkAttackUpAnim', 'WalkAttackDownAnim', 'FistStandAnim', 'FistWalkAnim',
203 'FistAttackAnim', 'FistWalkAttackAnim', 'FistSeeUpAnim', 'FistSeeDownAnim',
204 'FistAttackUpAnim', 'FistAttackDownAnim');
205 WeapNames: Array [WP_FIRST + 1..WP_LAST] of String =
206 ('csaw', 'hgun', 'sg', 'ssg', 'mgun', 'rkt', 'plz', 'bfg', 'spl', 'flm');
208 var
209 PlayerModelsArray: Array of TPlayerModelInfo;
211 function GetPoint(var str: String; var point: TDFPoint): Boolean;
212 var
213 a, x, y: Integer;
214 s: String;
215 begin
216 Result := False;
217 x := 0;
218 y := 0;
220 str := Trim(str);
221 if Length(str) < 3 then
222 Exit;
224 for a := 1 to Length(str) do
225 if (str[a] = ',') or (a = Length(str)) then
226 begin
227 s := Copy(str, 1, a);
228 if s[Length(s)] = ',' then
229 SetLength(s, Length(s)-1);
230 Delete(str, 1, a);
232 if (Sscanf(s, '%d:%d', [@x, @y]) < 2) or
233 (x < -64) or (x > 128) or
234 (y < -64) or (y > 128) then
235 Exit;
237 point.X := x;
238 point.Y := y;
240 Break;
241 end;
243 Result := True;
244 end;
246 function GetWeapPoints(str: String; weapon: Byte; anim: Byte; dir: TDirection;
247 frames: Word; backanim: Boolean; var wpoints: TWeaponPoints): Boolean;
248 var
249 a, b, h: Integer;
250 begin
251 Result := False;
253 if frames = 0 then
254 Exit;
256 backanim := backanim and (frames > 2);
258 for a := 1 to frames do
259 begin
260 if not GetPoint(str, wpoints[weapon, anim, dir, a-1]) then
261 Exit;
263 with wpoints[weapon, anim, dir, a-1] do
264 begin
265 X := X - WEAPONBASE[weapon].X;
266 Y := Y - WEAPONBASE[weapon].Y;
267 if dir = TDirection.D_LEFT then
268 X := -X;
269 end;
270 end;
272 h := High(wpoints[weapon, anim, dir]);
273 if backanim then
274 for b := h downto frames do
275 wpoints[weapon, anim, dir, b] := wpoints[weapon, anim, dir, h-b+1];
277 Result := True;
278 end;
280 procedure ExtAnimFromBaseAnim(MName: String; AIdx: Integer);
281 const
282 CopyAnim: array [A_LASTBASE+1..A_LASTEXT] of Integer = (
283 A_WALK, A_WALK, A_WALK, A_WALK, A_WALK,
284 A_STAND, A_WALK, A_ATTACK, A_WALK, A_SEEUP, A_SEEDOWN,
285 A_ATTACKUP, A_ATTACKDOWN
286 );
287 var
288 OIdx, W, I: Integer;
289 D: TDirection;
290 AName, OName: String;
291 begin
292 // HACK: shitty workaround to duplicate base animations
293 // in place of extended, replace with something better later
295 Assert((AIdx > A_LASTBASE) and (AIdx <= A_LASTEXT));
296 OIdx := CopyAnim[AIdx];
298 AName := MName + '_RIGHTANIM' + IntToStr(AIdx);
299 OName := MName + '_RIGHTANIM' + IntToStr(OIdx);
300 Assert(g_Frames_Dup(AName, OName));
301 Assert(g_Frames_Dup(AName + '_MASK', OName + '_MASK'));
302 AName := MName + '_LEFTANIM' + IntToStr(AIdx);
303 OName := MName + '_LEFTANIM' + IntToStr(OIdx);
304 if g_Frames_Exists(AName) then
305 begin
306 g_Frames_Dup(AName, OName);
307 g_Frames_Dup(AName + '_MASK', OName + '_MASK');
308 end;
310 with PlayerModelsArray[High(PlayerModelsArray)] do
311 begin
312 for W := WP_FIRST + 1 to WP_LAST do
313 begin
314 for D := TDirection.D_LEFT to TDirection.D_RIGHT do
315 begin
316 SetLength(WeaponPoints[W, AIdx, D], Length(WeaponPoints[W, OIdx, D]));
317 for I := 0 to High(WeaponPoints[W, AIdx, D]) do
318 WeaponPoints[W, AIdx, D, I] := WeaponPoints[W, OIdx, D, I]
319 end;
320 end;
321 end;
322 end;
324 function g_PlayerModel_CalcGibSize (pData: Pointer; dataSize, x, y, w, h: Integer): TRectWH;
325 var i, j: Integer; done: Boolean; img: TImageData;
327 function IsVoid (i, j: Integer): Boolean;
328 begin
329 result := Byte((PByte(img.bits) + (y+j)*img.width*4 + (x+i)*4 + 3)^) = 0
330 end;
332 begin
333 InitImage(img);
334 assert(LoadImageFromMemory(pData, dataSize, img));
336 (* trace x from right to left *)
337 done := false; i := 0;
338 while not done and (i < w) do
339 begin
340 j := 0;
341 while (j < h) and IsVoid(i, j) do inc(j);
342 done := (j < h) and (IsVoid(i, j) = false);
343 result.x := i;
344 inc(i);
345 end;
347 (* trace y from up to down *)
348 done := false; j := 0;
349 while not done and (j < h) do
350 begin
351 i := 0;
352 while (i < w) and IsVoid(i, j) do inc(i);
353 done := (i < w) and (IsVoid(i, j) = false);
354 result.y := j;
355 inc(j);
356 end;
358 (* trace x from right to left *)
359 done := false; i := w - 1;
360 while not done and (i >= 0) 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.width := i - result.x + 1;
366 dec(i);
367 end;
369 (* trace y from down to up *)
370 done := false; j := h - 1;
371 while not done and (j >= 0) 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.height := j - result.y + 1;
377 dec(j);
378 end;
380 FreeImage(img);
381 end;
383 function g_PlayerModel_Load(FileName: string): Boolean;
384 var
385 ID: DWORD;
386 a, b, len, lenpd, lenpd2, aa, bb, f: Integer;
387 cc: TDirection;
388 config: TConfig;
389 pData, pData2: Pointer;
390 WAD: TWADFile;
391 s, aname: string;
392 prefix: string;
393 ok, chk: Boolean;
394 begin
395 e_WriteLog(Format('Loading player model "%s"...', [FileName]), TMsgType.Notify);
397 Result := False;
399 WAD := TWADFile.Create;
400 WAD.ReadFile(FileName);
402 if {WAD.GetLastError <> DFWAD_NOERROR} not WAD.isOpen then
403 begin
404 WAD.Free();
405 Exit;
406 end;
408 if not WAD.GetResource('TEXT/MODEL', pData, len) then
409 begin
410 WAD.Free();
411 Exit;
412 end;
414 config := TConfig.CreateMem(pData, len);
415 FreeMem(pData);
417 s := config.ReadStr('Model', 'name', '');
418 if s = '' then
419 begin
420 config.Free();
421 WAD.Free();
422 Exit;
423 end;
425 SetLength(PlayerModelsArray, Length(PlayerModelsArray)+1);
426 ID := High(PlayerModelsArray);
428 prefix := FileName+':TEXTURES\';
430 with PlayerModelsArray[ID].Info do
431 begin
432 Name := s;
433 Author := config.ReadStr('Model', 'author', '');
434 Description := config.ReadStr('Model', 'description', '');
435 end;
437 with PlayerModelsArray[ID] do
438 begin
439 Blood.R := MAX(0, MIN(255, config.ReadInt('Blood', 'R', 150)));
440 Blood.G := MAX(0, MIN(255, config.ReadInt('Blood', 'G', 0)));
441 Blood.B := MAX(0, MIN(255, config.ReadInt('Blood', 'B', 0)));
442 case config.ReadStr('Blood', 'Kind', 'NORMAL') of
443 'NORMAL': Blood.Kind := BLOOD_NORMAL;
444 'SPARKS': Blood.Kind := BLOOD_CSPARKS;
445 'COMBINE': Blood.Kind := BLOOD_COMBINE;
446 else
447 Blood.Kind := BLOOD_NORMAL
448 end
449 end;
451 for b := A_STAND to A_LAST do
452 begin
453 aname := s+'_RIGHTANIM'+IntToStr(b);
454 //e_LogWritefln('### MODEL FILE: [%s]', [prefix+config.ReadStr(AnimNames[b], 'resource', '')]);
455 if not (g_Frames_CreateWAD(nil, aname,
456 prefix+config.ReadStr(AnimNames[b], 'resource', ''),
457 64, 64, config.ReadInt(AnimNames[b], 'frames', 1),
458 config.ReadBool(AnimNames[b], 'backanim', False)) and
459 g_Frames_CreateWAD(nil, aname+'_MASK',
460 prefix+config.ReadStr(AnimNames[b], 'mask', ''),
461 64, 64, config.ReadInt(AnimNames[b], 'frames', 1),
462 config.ReadBool(AnimNames[b], 'backanim', False))) then
463 begin
464 if b <= A_LASTBASE then
465 begin
466 config.Free();
467 WAD.Free();
468 Exit;
469 end
470 else
471 begin
472 ExtAnimFromBaseAnim(s, b);
473 continue;
474 end;
475 end;
477 for aa := WP_FIRST + 1 to WP_LAST do
478 for bb := A_STAND to A_LAST do
479 for cc := TDirection.D_LEFT to TDirection.D_RIGHT do
480 begin
481 f := config.ReadInt(AnimNames[bb], 'frames', 1);
482 if config.ReadBool(AnimNames[bb], 'backanim', False) then
483 if f > 2 then f := 2*f-2;
484 SetLength(PlayerModelsArray[ID].WeaponPoints[aa, bb, cc], f);
485 end;
487 if (config.ReadStr(AnimNames[b], 'resource2', '') <> '') and
488 (config.ReadStr(AnimNames[b], 'mask2', '') <> '') then
489 begin
490 aname := s+'_LEFTANIM'+IntToStr(b);
491 g_Frames_CreateWAD(nil, aname,
492 prefix+config.ReadStr(AnimNames[b], 'resource2', ''),
493 64, 64, config.ReadInt(AnimNames[b], 'frames', 1),
494 config.ReadBool(AnimNames[b], 'backanim', False));
496 g_Frames_CreateWAD(nil, aname+'_MASK',
497 prefix+config.ReadStr(AnimNames[b], 'mask2', ''),
498 64, 64, config.ReadInt(AnimNames[b], 'frames', 1),
499 config.ReadBool(AnimNames[b], 'backanim', False));
500 end;
502 PlayerModelsArray[ID].ModelSpeed[b] := Max(1, config.ReadInt(AnimNames[b], 'waitcount', 1) div 3);
503 end;
505 with PlayerModelsArray[ID], config do
506 begin
507 prefix := FileName+':SOUNDS\';
509 a := 1;
510 repeat
511 s := config.ReadStr('Sound', 'pain'+IntToStr(a), '');
512 if s <> '' then
513 begin
514 SetLength(PainSounds, Length(PainSounds)+1);
515 g_Sound_CreateWAD(PainSounds[High(PainSounds)].ID, prefix+s);
516 PainSounds[High(PainSounds)].Level := config.ReadInt('Sound', 'painlevel'+IntToStr(a), 1);
517 end;
518 a := a+1;
519 until s = '';
521 a := 1;
522 repeat
523 s := config.ReadStr('Sound', 'die'+IntToStr(a), '');
524 if s <> '' then
525 begin
526 SetLength(DieSounds, Length(DieSounds)+1);
527 g_Sound_CreateWAD(DieSounds[High(DieSounds)].ID, prefix+s);
528 DieSounds[High(DieSounds)].Level := config.ReadInt('Sound', 'dielevel'+IntToStr(a), 1);
529 end;
530 a := a+1;
531 until s = '';
533 SlopSound := Min(Max(config.ReadInt('Sound', 'slop', 0), 0), 2);
535 SetLength(Gibs, ReadInt('Gibs', 'count', 0));
537 if (Gibs <> nil) and
538 (WAD.GetResource('TEXTURES/'+config.ReadStr('Gibs', 'resource', 'GIBS'), pData, lenpd)) and
539 (WAD.GetResource('TEXTURES/'+config.ReadStr('Gibs', 'mask', 'GIBSMASK'), pData2, lenpd2)) then
540 begin
541 for a := 0 to High(Gibs) do
542 if e_CreateTextureMemEx(pData, lenpd, Gibs[a].ID, a*32, 0, 32, 32) and
543 e_CreateTextureMemEx(pData2, lenpd2, Gibs[a].MaskID, a*32, 0, 32, 32) then
544 begin
545 //Gibs[a].Rect := e_GetTextureSize2(Gibs[a].ID);
546 Gibs[a].Rect := g_PlayerModel_CalcGibSize(pData, lenpd, a*32, 0, 32, 32);
547 with Gibs[a].Rect do
548 if Height > 3 then Height := Height-1-Random(2);
549 Gibs[a].OnlyOne := config.ReadInt('Gibs', 'once', -1) = a+1;
550 end;
552 FreeMem(pData);
553 FreeMem(pData2);
554 end;
556 ok := True;
557 for aa := WP_FIRST + 1 to WP_LAST do
558 for bb := A_STAND to A_LAST do
559 if not (bb in [A_DIE1, A_DIE2, A_PAIN]) then
560 begin
561 chk := GetWeapPoints(config.ReadStr(AnimNames[bb], WeapNames[aa]+'_points', ''), aa, bb, TDirection.D_RIGHT,
562 config.ReadInt(AnimNames[bb], 'frames', 0),
563 config.ReadBool(AnimNames[bb], 'backanim', False),
564 WeaponPoints);
565 if ok and (not chk) and (aa = WEAPON_FLAMETHROWER) then
566 begin
567 // workaround for flamethrower
568 chk := GetWeapPoints(config.ReadStr(AnimNames[bb], WeapNames[WEAPON_PLASMA]+'_points', ''), aa, bb, TDirection.D_RIGHT,
569 config.ReadInt(AnimNames[bb], 'frames', 0),
570 config.ReadBool(AnimNames[bb], 'backanim', False),
571 WeaponPoints);
572 if chk then
573 for f := 0 to High(WeaponPoints[aa, bb, TDirection.D_RIGHT]) do
574 begin
575 case bb of
576 A_STAND, A_PAIN:
577 begin
578 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 6);
579 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 8);
580 end;
581 A_WALKATTACK, A_WALK:
582 begin
583 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 9);
584 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 9);
585 end;
586 A_ATTACK:
587 begin
588 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 5);
589 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 8);
590 end;
591 A_WALKSEEUP, A_SEEUP:
592 begin
593 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 5);
594 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 16);
595 end;
596 A_WALKSEEDOWN, A_SEEDOWN:
597 begin
598 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 6);
599 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 5);
600 end;
601 A_WALKATTACKUP, A_ATTACKUP:
602 begin
603 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 5);
604 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 16);
605 end;
606 A_WALKATTACKDOWN, A_ATTACKDOWN:
607 begin
608 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 6);
609 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 4);
610 end;
611 end;
612 end;
613 end;
614 ok := ok and (chk or (bb > A_LASTBASE));
616 if not GetWeapPoints(config.ReadStr(AnimNames[bb], WeapNames[aa]+'2_points', ''), aa, bb, TDirection.D_LEFT,
617 config.ReadInt(AnimNames[bb], 'frames', 0),
618 config.ReadBool(AnimNames[bb], 'backanim', False),
619 WeaponPoints) then
620 for f := 0 to High(WeaponPoints[aa, bb, TDirection.D_RIGHT]) do
621 begin
622 WeaponPoints[aa, bb, TDirection.D_LEFT, f].X := -WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X;
623 WeaponPoints[aa, bb, TDirection.D_LEFT, f].Y := WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y;
624 end;
626 if not ok then Break;
627 end;
628 {if ok then g_Console_Add(Info.Name+' weapon points ok')
629 else g_Console_Add(Info.Name+' weapon points fail');}
630 Info.HaveWeapon := ok;
632 s := config.ReadStr('Model', 'flag_point', '');
633 if not GetPoint(s, FlagPoint) then FlagPoint := FLAG_DEFPOINT;
635 FlagAngle := config.ReadInt('Model', 'flag_angle', FLAG_DEFANGLE);
636 end;
638 config.Free();
639 WAD.Free();
641 Result := True;
642 end;
644 function g_PlayerModel_Get(ModelName: String): TPlayerModel;
645 var
646 a: Integer;
647 b: Byte;
648 ID, ID2: DWORD;
649 begin
650 Result := nil;
652 if PlayerModelsArray = nil then Exit;
654 for a := 0 to High(PlayerModelsArray) do
655 if AnsiLowerCase(PlayerModelsArray[a].Info.Name) = AnsiLowerCase(ModelName) then
656 begin
657 Result := TPlayerModel.Create;
659 with PlayerModelsArray[a] do
660 begin
661 Result.FName := Info.Name;
662 Result.FBlood := Blood;
664 for b := A_STAND to A_LAST do
665 begin
666 if not (g_Frames_Get(ID, Info.Name+'_RIGHTANIM'+IntToStr(b)) and
667 g_Frames_Get(ID2, Info.Name+'_RIGHTANIM'+IntToStr(b)+'_MASK')) then
668 begin
669 Result.Free();
670 Result := nil;
671 Exit;
672 end;
674 Result.FAnim[TDirection.D_RIGHT][b] := TAnimation.Create(ID, b in [A_STAND, A_WALK], ModelSpeed[b]);
676 Result.FMaskAnim[TDirection.D_RIGHT][b] := TAnimation.Create(ID2, b in [A_STAND, A_WALK], ModelSpeed[b]);
678 if g_Frames_Exists(Info.Name+'_LEFTANIM'+IntToStr(b)) and
679 g_Frames_Exists(Info.Name+'_LEFTANIM'+IntToStr(b)+'_MASK') then
680 if g_Frames_Get(ID, Info.Name+'_LEFTANIM'+IntToStr(b)) and
681 g_Frames_Get(ID2, Info.Name+'_LEFTANIM'+IntToStr(b)+'_MASK') then
682 begin
683 Result.FAnim[TDirection.D_LEFT][b] := TAnimation.Create(ID, b in [A_STAND, A_WALK], ModelSpeed[b]);
685 Result.FMaskAnim[TDirection.D_LEFT][b] := TAnimation.Create(ID2, b in [A_STAND, A_WALK], ModelSpeed[b]);
686 end;
687 end;
689 Result.FPainSounds := PainSounds;
690 Result.FDieSounds := DieSounds;
691 Result.FSlopSound := SlopSound;
692 Result.FDrawWeapon := Info.HaveWeapon;
693 Result.FWeaponPoints := WeaponPoints;
695 Result.FFlagPoint := FlagPoint;
696 Result.FFlagAngle := FlagAngle;
698 Break;
699 end;
700 end;
701 end;
703 function g_PlayerModel_GetAnim(ModelName: string; Anim: Byte; var _Anim, _Mask: TAnimation): Boolean;
704 var
705 a: Integer;
706 c: Boolean;
707 ID: DWORD;
708 begin
709 Result := False;
711 if PlayerModelsArray = nil then Exit;
712 for a := 0 to High(PlayerModelsArray) do
713 if PlayerModelsArray[a].Info.Name = ModelName then
714 with PlayerModelsArray[a] do
715 begin
716 if Anim in [A_STAND, A_WALK] then c := True else c := False;
718 if not g_Frames_Get(ID, Info.Name+'_RIGHTANIM'+IntToStr(Anim)) then
719 if not g_Frames_Get(ID, Info.Name+'_LEFTANIM'+IntToStr(Anim)) then Exit;
721 _Anim := TAnimation.Create(ID, c, ModelSpeed[Anim]);
722 _Anim.Speed := ModelSpeed[Anim];
724 if not g_Frames_Get(ID, Info.Name+'_RIGHTANIM'+IntToStr(Anim)+'_MASK') then
725 if not g_Frames_Get(ID, Info.Name+'_LEFTANIM'+IntToStr(Anim)+'_MASK') then Exit;
727 _Mask := TAnimation.Create(ID, c, ModelSpeed[Anim]);
728 _Mask.Speed := ModelSpeed[Anim];
730 Break;
731 end;
733 Result := True;
734 end;
736 function g_PlayerModel_GetGibs(ModelName: string; var Gibs: TGibsArray): Boolean;
737 var
738 a, i, b: Integer;
739 c: Boolean;
740 begin
741 Result := False;
743 if PlayerModelsArray = nil then Exit;
744 if gGibsCount = 0 then Exit;
746 c := False;
748 SetLength(Gibs, gGibsCount);
750 for a := 0 to High(PlayerModelsArray) do
751 if PlayerModelsArray[a].Info.Name = ModelName then
752 begin
753 for i := 0 to High(Gibs) do
754 begin
755 if c and (Length(PlayerModelsArray[a].Gibs) = 1) then
756 begin
757 SetLength(Gibs, i);
758 Break;
759 end;
761 repeat
762 b := Random(Length(PlayerModelsArray[a].Gibs));
763 until not (PlayerModelsArray[a].Gibs[b].OnlyOne and c);
765 Gibs[i] := PlayerModelsArray[a].Gibs[b];
767 if Gibs[i].OnlyOne then c := True;
768 end;
770 Result := True;
771 Break;
772 end;
773 end;
775 function g_PlayerModel_GetNames(): SSArray;
776 var
777 i: DWORD;
778 begin
779 Result := nil;
781 if PlayerModelsArray = nil then Exit;
783 for i := 0 to High(PlayerModelsArray) do
784 begin
785 SetLength(Result, Length(Result)+1);
786 Result[High(Result)] := PlayerModelsArray[i].Info.Name;
787 end;
788 end;
790 function g_PlayerModel_GetInfo(ModelName: string): TModelInfo;
791 var
792 a: Integer;
793 begin
794 FillChar(Result, SizeOf(Result), 0);
795 if PlayerModelsArray = nil then Exit;
797 for a := 0 to High(PlayerModelsArray) do
798 if PlayerModelsArray[a].Info.Name = ModelName then
799 begin
800 Result := PlayerModelsArray[a].Info;
801 Break;
802 end;
803 end;
805 function g_PlayerModel_GetBlood(ModelName: string): TModelBlood;
806 var
807 a: Integer;
808 begin
809 Result.R := 150;
810 Result.G := 0;
811 Result.B := 0;
812 Result.Kind := BLOOD_NORMAL;
813 if PlayerModelsArray = nil then Exit;
815 for a := 0 to High(PlayerModelsArray) do
816 if PlayerModelsArray[a].Info.Name = ModelName then
817 begin
818 Result := PlayerModelsArray[a].Blood;
819 Break;
820 end;
821 end;
823 procedure g_PlayerModel_FreeData();
824 var
825 i: DWORD;
826 a, b: Integer;
827 begin
828 e_WriteLog('Releasing models...', TMsgType.Notify);
830 if PlayerModelsArray = nil then Exit;
832 for i := 0 to High(PlayerModelsArray) do
833 with PlayerModelsArray[i] do
834 begin
835 for a := A_STAND to A_LAST do
836 begin
837 g_Frames_DeleteByName(Info.Name+'_LEFTANIM'+IntToStr(a));
838 g_Frames_DeleteByName(Info.Name+'_LEFTANIM'+IntToStr(a)+'_MASK');
839 g_Frames_DeleteByName(Info.Name+'_RIGHTANIM'+IntToStr(a));
840 g_Frames_DeleteByName(Info.Name+'_RIGHTANIM'+IntToStr(a)+'_MASK');
841 end;
843 if PainSounds <> nil then
844 for b := 0 to High(PainSounds) do
845 e_DeleteSound(PainSounds[b].ID);
847 if DieSounds <> nil then
848 for b := 0 to High(DieSounds) do
849 e_DeleteSound(DieSounds[b].ID);
851 if Gibs <> nil then
852 for b := 0 to High(Gibs) do
853 begin
854 e_DeleteTexture(Gibs[b].ID);
855 e_DeleteTexture(Gibs[b].MaskID);
856 end;
857 end;
859 PlayerModelsArray := nil;
860 end;
862 { TPlayerModel }
864 procedure TPlayerModel.ChangeAnimation(Animation: Byte; Force: Boolean = False);
865 begin
866 if not Force then if FCurrentAnimation = Animation then Exit;
868 FCurrentAnimation := Animation;
870 if (FDirection = TDirection.D_LEFT) and
871 (FAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) and
872 (FMaskAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
873 begin
874 FAnim[TDirection.D_LEFT][FCurrentAnimation].Reset;
875 FMaskAnim[TDirection.D_LEFT][FCurrentAnimation].Reset;
876 end
877 else
878 begin
879 FAnim[TDirection.D_RIGHT][FCurrentAnimation].Reset;
880 FMaskAnim[TDirection.D_RIGHT][FCurrentAnimation].Reset;
881 end;
882 end;
884 destructor TPlayerModel.Destroy();
885 var
886 a: Byte;
887 begin
888 for a := A_STAND to A_LAST do
889 begin
890 FAnim[TDirection.D_LEFT][a].Free();
891 FMaskAnim[TDirection.D_LEFT][a].Free();
892 FAnim[TDirection.D_RIGHT][a].Free();
893 FMaskAnim[TDirection.D_RIGHT][a].Free();
894 end;
896 inherited;
897 end;
899 function TPlayerModel.GetCurrentAnimation: TAnimation;
900 begin
901 if (FDirection = TDirection.D_LEFT) and (FAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
902 Result := FAnim[TDirection.D_LEFT][FCurrentAnimation]
903 else
904 Result := FAnim[TDirection.D_RIGHT][FCurrentAnimation];
905 end;
907 function TPlayerModel.GetCurrentAnimationMask: TAnimation;
908 begin
909 if (FDirection = TDirection.D_LEFT) and (FMaskAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
910 Result := FMaskAnim[TDirection.D_LEFT][FCurrentAnimation]
911 else
912 Result := FMaskAnim[TDirection.D_RIGHT][FCurrentAnimation];
913 end;
915 function TPlayerModel.PlaySound(SoundType, Level: Byte; X, Y: Integer): Boolean;
916 var
917 TempArray: array of DWORD;
918 a: Integer;
919 begin
920 Result := False;
921 SetLength(TempArray, 0);
923 if SoundType = MODELSOUND_PAIN then
924 begin
925 if FPainSounds = nil then Exit;
927 for a := 0 to High(FPainSounds) do
928 if FPainSounds[a].Level = Level then
929 begin
930 SetLength(TempArray, Length(TempArray)+1);
931 TempArray[High(TempArray)] := FPainSounds[a].ID;
932 end;
933 end
934 else
935 begin
936 if (Level in [2, 3, 5]) and (FSlopSound > 0) then
937 begin
938 g_Sound_PlayExAt('SOUND_MONSTER_SLOP', X, Y);
939 if FSlopSound = 1 then
940 begin
941 Result := True;
942 Exit;
943 end;
944 end;
945 if FDieSounds = nil then Exit;
947 for a := 0 to High(FDieSounds) do
948 if FDieSounds[a].Level = Level then
949 begin
950 SetLength(TempArray, Length(TempArray)+1);
951 TempArray[High(TempArray)] := FDieSounds[a].ID;
952 end;
953 if (TempArray = nil) and (Level = 5) then
954 begin
955 g_Sound_PlayExAt('SOUND_MONSTER_SLOP', X, Y);
956 Result := True;
957 Exit;
958 end;
959 end;
961 if TempArray = nil then Exit;
963 g_Sound_PlayAt(TempArray[Random(Length(TempArray))], X, Y);
965 Result := True;
966 end;
968 procedure TPlayerModel.SetColor(Red, Green, Blue: Byte);
969 begin
970 FColor.R := Red;
971 FColor.G := Green;
972 FColor.B := Blue;
973 end;
975 procedure TPlayerModel.SetFire(Fire: Boolean);
976 begin
977 FFire := Fire;
979 if FFire then FFireCounter := FAnim[TDirection.D_RIGHT, A_ATTACK].Speed*FAnim[TDirection.D_RIGHT, A_ATTACK].TotalFrames
980 else FFireCounter := 0;
981 end;
983 procedure TPlayerModel.SetFlag(Flag: Byte);
984 var
985 id: DWORD;
986 begin
987 FFlag := Flag;
989 FFlagAnim.Free();
990 FFlagAnim := nil;
992 case Flag of
993 FLAG_RED: g_Frames_Get(id, 'FRAMES_FLAG_RED');
994 FLAG_BLUE: g_Frames_Get(id, 'FRAMES_FLAG_BLUE');
995 else Exit;
996 end;
998 FFlagAnim := TAnimation.Create(id, True, 8);
999 end;
1001 procedure TPlayerModel.SetWeapon(Weapon: Byte);
1002 begin
1003 FCurrentWeapon := Weapon;
1004 end;
1006 procedure TPlayerModel.Update();
1007 begin
1008 if (FDirection = TDirection.D_LEFT) and (FAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
1009 FAnim[TDirection.D_LEFT][FCurrentAnimation].Update else FAnim[TDirection.D_RIGHT][FCurrentAnimation].Update;
1011 if (FDirection = TDirection.D_LEFT) and (FMaskAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
1012 FMaskAnim[TDirection.D_LEFT][FCurrentAnimation].Update else FMaskAnim[TDirection.D_RIGHT][FCurrentAnimation].Update;
1014 if FFlagAnim <> nil then FFlagAnim.Update;
1016 if FFireCounter > 0 then Dec(FFireCounter) else FFire := False;
1017 end;
1019 end.