DEADSOFTWARE

cleanup: remove g_main.pas
[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 MAPDEF, g_textures, g_base, g_basic, g_weapons, r_graphics, utils, g_gfx,
23 ImagingTypes, Imaging, ImagingUtility;
25 const
26 A_STAND = 0;
27 A_WALK = 1;
28 A_DIE1 = 2;
29 A_DIE2 = 3;
30 A_ATTACK = 4;
31 A_SEEUP = 5;
32 A_SEEDOWN = 6;
33 A_ATTACKUP = 7;
34 A_ATTACKDOWN = 8;
35 A_PAIN = 9;
36 // EXTENDED
37 A_WALKATTACK = 10;
38 A_WALKSEEUP = 11;
39 A_WALKSEEDOWN = 12;
40 A_WALKATTACKUP = 13;
41 A_WALKATTACKDOWN = 14;
42 A_FISTSTAND = 15;
43 A_FISTWALK = 16;
44 A_FISTATTACK = 17;
45 A_FISTWALKATTACK = 18;
46 A_FISTSEEUP = 19;
47 A_FISTSEEDOWN = 20;
48 A_FISTATTACKUP = 21;
49 A_FISTATTACKDOWN = 22;
51 A_LASTBASE = A_PAIN;
52 A_LASTEXT = A_FISTATTACKDOWN;
53 A_LAST = A_LASTEXT;
55 MODELSOUND_PAIN = 0;
56 MODELSOUND_DIE = 1;
58 W_POS_NORMAL = 0;
59 W_POS_UP = 1;
60 W_POS_DOWN = 2;
62 W_ACT_NORMAL = 0;
63 W_ACT_FIRE = 1;
65 FLAG_BASEPOINT: TDFPoint = (X:16; Y:43);
67 type
68 TModelInfo = record
69 Name: String;
70 Author: String;
71 Description: String;
72 HaveWeapon: Boolean;
73 end;
75 TModelBlood = record
76 R, G, B, Kind: Byte;
77 end;
79 TModelSound = record
80 ID: DWORD;
81 Level: Byte;
82 end;
84 TGibSprite = record
85 ID: DWORD;
86 MaskID: DWORD;
87 Rect: TRectWH;
88 OnlyOne: Boolean;
89 end;
91 TModelSoundArray = Array of TModelSound;
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;
96 TModelMatrix = Array [TDirection.D_LEFT..TDirection.D_RIGHT] of Array [A_STAND..A_LAST] of TAnimation;
98 TPlayerModel = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF}
99 private
100 FName: String;
101 FDirection: TDirection;
102 FColor: TRGB;
103 FBlood: TModelBlood;
104 FCurrentAnimation: Byte;
105 FAnim: TModelMatrix;
106 FMaskAnim: TModelMatrix;
107 FWeaponPoints: TWeaponPoints;
108 FPainSounds: TModelSoundArray;
109 FDieSounds: TModelSoundArray;
110 FSlopSound: Byte;
111 FCurrentWeapon: Byte;
112 FDrawWeapon: Boolean;
113 FFlag: Byte;
114 FFlagPoint: TDFPoint;
115 FFlagAngle: SmallInt;
116 FFlagAnim: TAnimation;
117 FFire: Boolean;
118 FFireCounter: Byte;
120 public
121 destructor Destroy(); override;
122 procedure ChangeAnimation(Animation: Byte; Force: Boolean = False);
123 function GetCurrentAnimation: TAnimation;
124 function GetCurrentAnimationMask: TAnimation;
125 procedure SetColor(Red, Green, Blue: Byte);
126 procedure SetWeapon(Weapon: Byte);
127 procedure SetFlag(Flag: Byte);
128 procedure SetFire(Fire: Boolean);
129 function PlaySound(SoundType, Level: Byte; X, Y: Integer): Boolean;
130 procedure Update();
132 published
133 property Fire: Boolean read FFire;
134 property Direction: TDirection read FDirection write FDirection;
135 property Animation: Byte read FCurrentAnimation;
136 property Weapon: Byte read FCurrentWeapon;
137 property Name: String read FName;
139 public
140 property Color: TRGB read FColor write FColor;
141 property Blood: TModelBlood read FBlood;
143 property Anim: TModelMatrix read FAnim;
144 property MaskAnim: TModelMatrix read FMaskAnim;
145 property CurrentAnimation: Byte read FCurrentAnimation;
147 property CurrentWeapon: Byte read FCurrentWeapon;
148 property DrawWeapon: Boolean read FDrawWeapon;
149 property WeaponPoints: TWeaponPoints read FWeaponPoints;
151 property Flag: Byte read FFlag;
152 property FlagAnim: TAnimation read FFlagAnim;
153 property FlagAngle: SmallInt read FFlagAngle;
154 property FlagPoint: TDFPoint read FFlagPoint;
155 end;
157 procedure g_PlayerModel_FreeData();
158 function g_PlayerModel_Load(FileName: String): Boolean;
159 function g_PlayerModel_GetNames(): SSArray;
160 function g_PlayerModel_GetInfo(ModelName: String): TModelInfo;
161 function g_PlayerModel_GetBlood(ModelName: String): TModelBlood;
162 function g_PlayerModel_Get(ModelName: String): TPlayerModel;
163 function g_PlayerModel_GetAnim(ModelName: String; Anim: Byte; var _Anim, _Mask: TAnimation): Boolean;
164 function g_PlayerModel_GetGibs(ModelName: String; var Gibs: TGibsArray): Boolean;
167 implementation
169 uses
170 g_sound, g_console, SysUtils, g_player, CONFIG,
171 e_sound, g_options, g_map, Math, e_log, wadreader;
173 type
174 TPlayerModelInfo = record
175 Info: TModelInfo;
176 ModelSpeed: Array [A_STAND..A_PAIN] of Byte;
177 FlagPoint: TDFPoint;
178 FlagAngle: SmallInt;
179 WeaponPoints: TWeaponPoints;
180 Gibs: TGibsArray;
181 PainSounds: TModelSoundArray;
182 DieSounds: TModelSoundArray;
183 SlopSound: Byte;
184 Blood: TModelBlood;
185 end;
187 const
188 FLAG_DEFPOINT: TDFPoint = (X:32; Y:16);
189 FLAG_DEFANGLE = -20;
190 WEAPONBASE: Array [WP_FIRST + 1..WP_LAST] of TDFPoint =
191 ((X:8; Y:4), (X:8; Y:8), (X:16; Y:16), (X:16; Y:24),
192 (X:16; Y:16), (X:24; Y:24), (X:16; Y:16), (X:24; Y:24),
193 (X:16; Y:16), (X:8; Y:8));
195 AnimNames: Array [A_STAND..A_LASTEXT] of String =
196 ('StandAnim','WalkAnim','Die1Anim','Die2Anim','AttackAnim',
197 'SeeUpAnim','SeeDownAnim','AttackUpAnim','AttackDownAnim','PainAnim',
198 // EXTENDED
199 'WalkAttackAnim', 'WalkSeeUpAnim', 'WalkSeeDownAnim',
200 'WalkAttackUpAnim', 'WalkAttackDownAnim', 'FistStandAnim', 'FistWalkAnim',
201 'FistAttackAnim', 'FistWalkAttackAnim', 'FistSeeUpAnim', 'FistSeeDownAnim',
202 'FistAttackUpAnim', 'FistAttackDownAnim');
203 WeapNames: Array [WP_FIRST + 1..WP_LAST] of String =
204 ('csaw', 'hgun', 'sg', 'ssg', 'mgun', 'rkt', 'plz', 'bfg', 'spl', 'flm');
206 var
207 PlayerModelsArray: Array of TPlayerModelInfo;
209 function GetPoint(var str: String; var point: TDFPoint): Boolean;
210 var
211 a, x, y: Integer;
212 s: String;
213 begin
214 Result := False;
215 x := 0;
216 y := 0;
218 str := Trim(str);
219 if Length(str) < 3 then
220 Exit;
222 for a := 1 to Length(str) do
223 if (str[a] = ',') or (a = Length(str)) then
224 begin
225 s := Copy(str, 1, a);
226 if s[Length(s)] = ',' then
227 SetLength(s, Length(s)-1);
228 Delete(str, 1, a);
230 if (Sscanf(s, '%d:%d', [@x, @y]) < 2) or
231 (x < -64) or (x > 128) or
232 (y < -64) or (y > 128) then
233 Exit;
235 point.X := x;
236 point.Y := y;
238 Break;
239 end;
241 Result := True;
242 end;
244 function GetWeapPoints(str: String; weapon: Byte; anim: Byte; dir: TDirection;
245 frames: Word; backanim: Boolean; var wpoints: TWeaponPoints): Boolean;
246 var
247 a, b, h: Integer;
248 begin
249 Result := False;
251 if frames = 0 then
252 Exit;
254 backanim := backanim and (frames > 2);
256 for a := 1 to frames do
257 begin
258 if not GetPoint(str, wpoints[weapon, anim, dir, a-1]) then
259 Exit;
261 with wpoints[weapon, anim, dir, a-1] do
262 begin
263 X := X - WEAPONBASE[weapon].X;
264 Y := Y - WEAPONBASE[weapon].Y;
265 if dir = TDirection.D_LEFT then
266 X := -X;
267 end;
268 end;
270 h := High(wpoints[weapon, anim, dir]);
271 if backanim then
272 for b := h downto frames do
273 wpoints[weapon, anim, dir, b] := wpoints[weapon, anim, dir, h-b+1];
275 Result := True;
276 end;
278 procedure ExtAnimFromBaseAnim(MName: String; AIdx: Integer);
279 const
280 CopyAnim: array [A_LASTBASE+1..A_LASTEXT] of Integer = (
281 A_WALK, A_WALK, A_WALK, A_WALK, A_WALK,
282 A_STAND, A_WALK, A_ATTACK, A_WALK, A_SEEUP, A_SEEDOWN,
283 A_ATTACKUP, A_ATTACKDOWN
284 );
285 var
286 OIdx, W, I: Integer;
287 D: TDirection;
288 AName, OName: String;
289 begin
290 // HACK: shitty workaround to duplicate base animations
291 // in place of extended, replace with something better later
293 Assert((AIdx > A_LASTBASE) and (AIdx <= A_LASTEXT));
294 OIdx := CopyAnim[AIdx];
296 AName := MName + '_RIGHTANIM' + IntToStr(AIdx);
297 OName := MName + '_RIGHTANIM' + IntToStr(OIdx);
298 Assert(g_Frames_Dup(AName, OName));
299 Assert(g_Frames_Dup(AName + '_MASK', OName + '_MASK'));
300 AName := MName + '_LEFTANIM' + IntToStr(AIdx);
301 OName := MName + '_LEFTANIM' + IntToStr(OIdx);
302 if g_Frames_Exists(AName) then
303 begin
304 g_Frames_Dup(AName, OName);
305 g_Frames_Dup(AName + '_MASK', OName + '_MASK');
306 end;
308 with PlayerModelsArray[High(PlayerModelsArray)] do
309 begin
310 for W := WP_FIRST + 1 to WP_LAST do
311 begin
312 for D := TDirection.D_LEFT to TDirection.D_RIGHT do
313 begin
314 SetLength(WeaponPoints[W, AIdx, D], Length(WeaponPoints[W, OIdx, D]));
315 for I := 0 to High(WeaponPoints[W, AIdx, D]) do
316 WeaponPoints[W, AIdx, D, I] := WeaponPoints[W, OIdx, D, I]
317 end;
318 end;
319 end;
320 end;
322 function g_PlayerModel_CalcGibSize (pData: Pointer; dataSize, x, y, w, h: Integer): TRectWH;
323 var i, j: Integer; done: Boolean; img: TImageData;
325 function IsVoid (i, j: Integer): Boolean;
326 begin
327 result := Byte((PByte(img.bits) + (y+j)*img.width*4 + (x+i)*4 + 3)^) = 0
328 end;
330 begin
331 InitImage(img);
332 assert(LoadImageFromMemory(pData, dataSize, img));
334 (* trace x from right to left *)
335 done := false; i := 0;
336 while not done and (i < w) do
337 begin
338 j := 0;
339 while (j < h) and IsVoid(i, j) do inc(j);
340 done := (j < h) and (IsVoid(i, j) = false);
341 result.x := i;
342 inc(i);
343 end;
345 (* trace y from up to down *)
346 done := false; j := 0;
347 while not done and (j < h) do
348 begin
349 i := 0;
350 while (i < w) and IsVoid(i, j) do inc(i);
351 done := (i < w) and (IsVoid(i, j) = false);
352 result.y := j;
353 inc(j);
354 end;
356 (* trace x from right to left *)
357 done := false; i := w - 1;
358 while not done and (i >= 0) do
359 begin
360 j := 0;
361 while (j < h) and IsVoid(i, j) do inc(j);
362 done := (j < h) and (IsVoid(i, j) = false);
363 result.width := i - result.x + 1;
364 dec(i);
365 end;
367 (* trace y from down to up *)
368 done := false; j := h - 1;
369 while not done and (j >= 0) do
370 begin
371 i := 0;
372 while (i < w) and IsVoid(i, j) do inc(i);
373 done := (i < w) and (IsVoid(i, j) = false);
374 result.height := j - result.y + 1;
375 dec(j);
376 end;
378 FreeImage(img);
379 end;
381 function g_PlayerModel_Load(FileName: string): Boolean;
382 var
383 ID: DWORD;
384 a, b, len, lenpd, lenpd2, aa, bb, f: Integer;
385 cc: TDirection;
386 config: TConfig;
387 pData, pData2: Pointer;
388 WAD: TWADFile;
389 s, aname: string;
390 prefix: string;
391 ok, chk: Boolean;
392 begin
393 e_WriteLog(Format('Loading player model "%s"...', [FileName]), TMsgType.Notify);
395 Result := False;
397 WAD := TWADFile.Create;
398 WAD.ReadFile(FileName);
400 if {WAD.GetLastError <> DFWAD_NOERROR} not WAD.isOpen then
401 begin
402 WAD.Free();
403 Exit;
404 end;
406 if not WAD.GetResource('TEXT/MODEL', pData, len) then
407 begin
408 WAD.Free();
409 Exit;
410 end;
412 config := TConfig.CreateMem(pData, len);
413 FreeMem(pData);
415 s := config.ReadStr('Model', 'name', '');
416 if s = '' then
417 begin
418 config.Free();
419 WAD.Free();
420 Exit;
421 end;
423 SetLength(PlayerModelsArray, Length(PlayerModelsArray)+1);
424 ID := High(PlayerModelsArray);
426 prefix := FileName+':TEXTURES\';
428 with PlayerModelsArray[ID].Info do
429 begin
430 Name := s;
431 Author := config.ReadStr('Model', 'author', '');
432 Description := config.ReadStr('Model', 'description', '');
433 end;
435 with PlayerModelsArray[ID] do
436 begin
437 Blood.R := MAX(0, MIN(255, config.ReadInt('Blood', 'R', 150)));
438 Blood.G := MAX(0, MIN(255, config.ReadInt('Blood', 'G', 0)));
439 Blood.B := MAX(0, MIN(255, config.ReadInt('Blood', 'B', 0)));
440 case config.ReadStr('Blood', 'Kind', 'NORMAL') of
441 'NORMAL': Blood.Kind := BLOOD_NORMAL;
442 'SPARKS': Blood.Kind := BLOOD_CSPARKS;
443 'COMBINE': Blood.Kind := BLOOD_COMBINE;
444 else
445 Blood.Kind := BLOOD_NORMAL
446 end
447 end;
449 for b := A_STAND to A_LAST do
450 begin
451 aname := s+'_RIGHTANIM'+IntToStr(b);
452 //e_LogWritefln('### MODEL FILE: [%s]', [prefix+config.ReadStr(AnimNames[b], 'resource', '')]);
453 if not (g_Frames_CreateWAD(nil, aname,
454 prefix+config.ReadStr(AnimNames[b], 'resource', ''),
455 64, 64, config.ReadInt(AnimNames[b], 'frames', 1),
456 config.ReadBool(AnimNames[b], 'backanim', False)) and
457 g_Frames_CreateWAD(nil, aname+'_MASK',
458 prefix+config.ReadStr(AnimNames[b], 'mask', ''),
459 64, 64, config.ReadInt(AnimNames[b], 'frames', 1),
460 config.ReadBool(AnimNames[b], 'backanim', False))) then
461 begin
462 if b <= A_LASTBASE then
463 begin
464 config.Free();
465 WAD.Free();
466 Exit;
467 end
468 else
469 begin
470 ExtAnimFromBaseAnim(s, b);
471 continue;
472 end;
473 end;
475 for aa := WP_FIRST + 1 to WP_LAST do
476 for bb := A_STAND to A_LAST do
477 for cc := TDirection.D_LEFT to TDirection.D_RIGHT do
478 begin
479 f := config.ReadInt(AnimNames[bb], 'frames', 1);
480 if config.ReadBool(AnimNames[bb], 'backanim', False) then
481 if f > 2 then f := 2*f-2;
482 SetLength(PlayerModelsArray[ID].WeaponPoints[aa, bb, cc], f);
483 end;
485 if (config.ReadStr(AnimNames[b], 'resource2', '') <> '') and
486 (config.ReadStr(AnimNames[b], 'mask2', '') <> '') then
487 begin
488 aname := s+'_LEFTANIM'+IntToStr(b);
489 g_Frames_CreateWAD(nil, aname,
490 prefix+config.ReadStr(AnimNames[b], 'resource2', ''),
491 64, 64, config.ReadInt(AnimNames[b], 'frames', 1),
492 config.ReadBool(AnimNames[b], 'backanim', False));
494 g_Frames_CreateWAD(nil, aname+'_MASK',
495 prefix+config.ReadStr(AnimNames[b], 'mask2', ''),
496 64, 64, config.ReadInt(AnimNames[b], 'frames', 1),
497 config.ReadBool(AnimNames[b], 'backanim', False));
498 end;
500 PlayerModelsArray[ID].ModelSpeed[b] := Max(1, config.ReadInt(AnimNames[b], 'waitcount', 1) div 3);
501 end;
503 with PlayerModelsArray[ID], config do
504 begin
505 prefix := FileName+':SOUNDS\';
507 a := 1;
508 repeat
509 s := config.ReadStr('Sound', 'pain'+IntToStr(a), '');
510 if s <> '' then
511 begin
512 SetLength(PainSounds, Length(PainSounds)+1);
513 g_Sound_CreateWAD(PainSounds[High(PainSounds)].ID, prefix+s);
514 PainSounds[High(PainSounds)].Level := config.ReadInt('Sound', 'painlevel'+IntToStr(a), 1);
515 end;
516 a := a+1;
517 until s = '';
519 a := 1;
520 repeat
521 s := config.ReadStr('Sound', 'die'+IntToStr(a), '');
522 if s <> '' then
523 begin
524 SetLength(DieSounds, Length(DieSounds)+1);
525 g_Sound_CreateWAD(DieSounds[High(DieSounds)].ID, prefix+s);
526 DieSounds[High(DieSounds)].Level := config.ReadInt('Sound', 'dielevel'+IntToStr(a), 1);
527 end;
528 a := a+1;
529 until s = '';
531 SlopSound := Min(Max(config.ReadInt('Sound', 'slop', 0), 0), 2);
533 SetLength(Gibs, ReadInt('Gibs', 'count', 0));
535 if (Gibs <> nil) and
536 (WAD.GetResource('TEXTURES/'+config.ReadStr('Gibs', 'resource', 'GIBS'), pData, lenpd)) and
537 (WAD.GetResource('TEXTURES/'+config.ReadStr('Gibs', 'mask', 'GIBSMASK'), pData2, lenpd2)) then
538 begin
539 for a := 0 to High(Gibs) do
540 if e_CreateTextureMemEx(pData, lenpd, Gibs[a].ID, a*32, 0, 32, 32) and
541 e_CreateTextureMemEx(pData2, lenpd2, Gibs[a].MaskID, a*32, 0, 32, 32) then
542 begin
543 //Gibs[a].Rect := e_GetTextureSize2(Gibs[a].ID);
544 Gibs[a].Rect := g_PlayerModel_CalcGibSize(pData, lenpd, a*32, 0, 32, 32);
545 with Gibs[a].Rect do
546 if Height > 3 then Height := Height-1-Random(2);
547 Gibs[a].OnlyOne := config.ReadInt('Gibs', 'once', -1) = a+1;
548 end;
550 FreeMem(pData);
551 FreeMem(pData2);
552 end;
554 ok := True;
555 for aa := WP_FIRST + 1 to WP_LAST do
556 for bb := A_STAND to A_LAST do
557 if not (bb in [A_DIE1, A_DIE2, A_PAIN]) then
558 begin
559 chk := GetWeapPoints(config.ReadStr(AnimNames[bb], WeapNames[aa]+'_points', ''), aa, bb, TDirection.D_RIGHT,
560 config.ReadInt(AnimNames[bb], 'frames', 0),
561 config.ReadBool(AnimNames[bb], 'backanim', False),
562 WeaponPoints);
563 if ok and (not chk) and (aa = WEAPON_FLAMETHROWER) then
564 begin
565 // workaround for flamethrower
566 chk := GetWeapPoints(config.ReadStr(AnimNames[bb], WeapNames[WEAPON_PLASMA]+'_points', ''), aa, bb, TDirection.D_RIGHT,
567 config.ReadInt(AnimNames[bb], 'frames', 0),
568 config.ReadBool(AnimNames[bb], 'backanim', False),
569 WeaponPoints);
570 if chk then
571 for f := 0 to High(WeaponPoints[aa, bb, TDirection.D_RIGHT]) do
572 begin
573 case bb of
574 A_STAND, A_PAIN:
575 begin
576 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 6);
577 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 8);
578 end;
579 A_WALKATTACK, A_WALK:
580 begin
581 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 9);
582 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 9);
583 end;
584 A_ATTACK:
585 begin
586 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 5);
587 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 8);
588 end;
589 A_WALKSEEUP, A_SEEUP:
590 begin
591 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 5);
592 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 16);
593 end;
594 A_WALKSEEDOWN, A_SEEDOWN:
595 begin
596 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 6);
597 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 5);
598 end;
599 A_WALKATTACKUP, A_ATTACKUP:
600 begin
601 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 5);
602 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 16);
603 end;
604 A_WALKATTACKDOWN, A_ATTACKDOWN:
605 begin
606 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 6);
607 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 4);
608 end;
609 end;
610 end;
611 end;
612 ok := ok and (chk or (bb > A_LASTBASE));
614 if not GetWeapPoints(config.ReadStr(AnimNames[bb], WeapNames[aa]+'2_points', ''), aa, bb, TDirection.D_LEFT,
615 config.ReadInt(AnimNames[bb], 'frames', 0),
616 config.ReadBool(AnimNames[bb], 'backanim', False),
617 WeaponPoints) then
618 for f := 0 to High(WeaponPoints[aa, bb, TDirection.D_RIGHT]) do
619 begin
620 WeaponPoints[aa, bb, TDirection.D_LEFT, f].X := -WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X;
621 WeaponPoints[aa, bb, TDirection.D_LEFT, f].Y := WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y;
622 end;
624 if not ok then Break;
625 end;
626 {if ok then g_Console_Add(Info.Name+' weapon points ok')
627 else g_Console_Add(Info.Name+' weapon points fail');}
628 Info.HaveWeapon := ok;
630 s := config.ReadStr('Model', 'flag_point', '');
631 if not GetPoint(s, FlagPoint) then FlagPoint := FLAG_DEFPOINT;
633 FlagAngle := config.ReadInt('Model', 'flag_angle', FLAG_DEFANGLE);
634 end;
636 config.Free();
637 WAD.Free();
639 Result := True;
640 end;
642 function g_PlayerModel_Get(ModelName: String): TPlayerModel;
643 var
644 a: Integer;
645 b: Byte;
646 ID, ID2: DWORD;
647 begin
648 Result := nil;
650 if PlayerModelsArray = nil then Exit;
652 for a := 0 to High(PlayerModelsArray) do
653 if AnsiLowerCase(PlayerModelsArray[a].Info.Name) = AnsiLowerCase(ModelName) then
654 begin
655 Result := TPlayerModel.Create;
657 with PlayerModelsArray[a] do
658 begin
659 Result.FName := Info.Name;
660 Result.FBlood := Blood;
662 for b := A_STAND to A_LAST do
663 begin
664 if not (g_Frames_Get(ID, Info.Name+'_RIGHTANIM'+IntToStr(b)) and
665 g_Frames_Get(ID2, Info.Name+'_RIGHTANIM'+IntToStr(b)+'_MASK')) then
666 begin
667 Result.Free();
668 Result := nil;
669 Exit;
670 end;
672 Result.FAnim[TDirection.D_RIGHT][b] := TAnimation.Create(ID, b in [A_STAND, A_WALK], ModelSpeed[b]);
674 Result.FMaskAnim[TDirection.D_RIGHT][b] := TAnimation.Create(ID2, b in [A_STAND, A_WALK], ModelSpeed[b]);
676 if g_Frames_Exists(Info.Name+'_LEFTANIM'+IntToStr(b)) and
677 g_Frames_Exists(Info.Name+'_LEFTANIM'+IntToStr(b)+'_MASK') then
678 if g_Frames_Get(ID, Info.Name+'_LEFTANIM'+IntToStr(b)) and
679 g_Frames_Get(ID2, Info.Name+'_LEFTANIM'+IntToStr(b)+'_MASK') then
680 begin
681 Result.FAnim[TDirection.D_LEFT][b] := TAnimation.Create(ID, b in [A_STAND, A_WALK], ModelSpeed[b]);
683 Result.FMaskAnim[TDirection.D_LEFT][b] := TAnimation.Create(ID2, b in [A_STAND, A_WALK], ModelSpeed[b]);
684 end;
685 end;
687 Result.FPainSounds := PainSounds;
688 Result.FDieSounds := DieSounds;
689 Result.FSlopSound := SlopSound;
690 Result.FDrawWeapon := Info.HaveWeapon;
691 Result.FWeaponPoints := WeaponPoints;
693 Result.FFlagPoint := FlagPoint;
694 Result.FFlagAngle := FlagAngle;
696 Break;
697 end;
698 end;
699 end;
701 function g_PlayerModel_GetAnim(ModelName: string; Anim: Byte; var _Anim, _Mask: TAnimation): Boolean;
702 var
703 a: Integer;
704 c: Boolean;
705 ID: DWORD;
706 begin
707 Result := False;
709 if PlayerModelsArray = nil then Exit;
710 for a := 0 to High(PlayerModelsArray) do
711 if PlayerModelsArray[a].Info.Name = ModelName then
712 with PlayerModelsArray[a] do
713 begin
714 if Anim in [A_STAND, A_WALK] then c := True else c := False;
716 if not g_Frames_Get(ID, Info.Name+'_RIGHTANIM'+IntToStr(Anim)) then
717 if not g_Frames_Get(ID, Info.Name+'_LEFTANIM'+IntToStr(Anim)) then Exit;
719 _Anim := TAnimation.Create(ID, c, ModelSpeed[Anim]);
720 _Anim.Speed := ModelSpeed[Anim];
722 if not g_Frames_Get(ID, Info.Name+'_RIGHTANIM'+IntToStr(Anim)+'_MASK') then
723 if not g_Frames_Get(ID, Info.Name+'_LEFTANIM'+IntToStr(Anim)+'_MASK') then Exit;
725 _Mask := TAnimation.Create(ID, c, ModelSpeed[Anim]);
726 _Mask.Speed := ModelSpeed[Anim];
728 Break;
729 end;
731 Result := True;
732 end;
734 function g_PlayerModel_GetGibs(ModelName: string; var Gibs: TGibsArray): Boolean;
735 var
736 a, i, b: Integer;
737 c: Boolean;
738 begin
739 Result := False;
741 if PlayerModelsArray = nil then Exit;
742 if gGibsCount = 0 then Exit;
744 c := False;
746 SetLength(Gibs, gGibsCount);
748 for a := 0 to High(PlayerModelsArray) do
749 if PlayerModelsArray[a].Info.Name = ModelName then
750 begin
751 for i := 0 to High(Gibs) do
752 begin
753 if c and (Length(PlayerModelsArray[a].Gibs) = 1) then
754 begin
755 SetLength(Gibs, i);
756 Break;
757 end;
759 repeat
760 b := Random(Length(PlayerModelsArray[a].Gibs));
761 until not (PlayerModelsArray[a].Gibs[b].OnlyOne and c);
763 Gibs[i] := PlayerModelsArray[a].Gibs[b];
765 if Gibs[i].OnlyOne then c := True;
766 end;
768 Result := True;
769 Break;
770 end;
771 end;
773 function g_PlayerModel_GetNames(): SSArray;
774 var
775 i: DWORD;
776 begin
777 Result := nil;
779 if PlayerModelsArray = nil then Exit;
781 for i := 0 to High(PlayerModelsArray) do
782 begin
783 SetLength(Result, Length(Result)+1);
784 Result[High(Result)] := PlayerModelsArray[i].Info.Name;
785 end;
786 end;
788 function g_PlayerModel_GetInfo(ModelName: string): TModelInfo;
789 var
790 a: Integer;
791 begin
792 FillChar(Result, SizeOf(Result), 0);
793 if PlayerModelsArray = nil then Exit;
795 for a := 0 to High(PlayerModelsArray) do
796 if PlayerModelsArray[a].Info.Name = ModelName then
797 begin
798 Result := PlayerModelsArray[a].Info;
799 Break;
800 end;
801 end;
803 function g_PlayerModel_GetBlood(ModelName: string): TModelBlood;
804 var
805 a: Integer;
806 begin
807 Result.R := 150;
808 Result.G := 0;
809 Result.B := 0;
810 Result.Kind := BLOOD_NORMAL;
811 if PlayerModelsArray = nil then Exit;
813 for a := 0 to High(PlayerModelsArray) do
814 if PlayerModelsArray[a].Info.Name = ModelName then
815 begin
816 Result := PlayerModelsArray[a].Blood;
817 Break;
818 end;
819 end;
821 procedure g_PlayerModel_FreeData();
822 var
823 i: DWORD;
824 a, b: Integer;
825 begin
826 e_WriteLog('Releasing models...', TMsgType.Notify);
828 if PlayerModelsArray = nil then Exit;
830 for i := 0 to High(PlayerModelsArray) do
831 with PlayerModelsArray[i] do
832 begin
833 for a := A_STAND to A_LAST do
834 begin
835 g_Frames_DeleteByName(Info.Name+'_LEFTANIM'+IntToStr(a));
836 g_Frames_DeleteByName(Info.Name+'_LEFTANIM'+IntToStr(a)+'_MASK');
837 g_Frames_DeleteByName(Info.Name+'_RIGHTANIM'+IntToStr(a));
838 g_Frames_DeleteByName(Info.Name+'_RIGHTANIM'+IntToStr(a)+'_MASK');
839 end;
841 if PainSounds <> nil then
842 for b := 0 to High(PainSounds) do
843 e_DeleteSound(PainSounds[b].ID);
845 if DieSounds <> nil then
846 for b := 0 to High(DieSounds) do
847 e_DeleteSound(DieSounds[b].ID);
849 if Gibs <> nil then
850 for b := 0 to High(Gibs) do
851 begin
852 e_DeleteTexture(Gibs[b].ID);
853 e_DeleteTexture(Gibs[b].MaskID);
854 end;
855 end;
857 PlayerModelsArray := nil;
858 end;
860 { TPlayerModel }
862 procedure TPlayerModel.ChangeAnimation(Animation: Byte; Force: Boolean = False);
863 begin
864 if not Force then if FCurrentAnimation = Animation then Exit;
866 FCurrentAnimation := Animation;
868 if (FDirection = TDirection.D_LEFT) and
869 (FAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) and
870 (FMaskAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
871 begin
872 FAnim[TDirection.D_LEFT][FCurrentAnimation].Reset;
873 FMaskAnim[TDirection.D_LEFT][FCurrentAnimation].Reset;
874 end
875 else
876 begin
877 FAnim[TDirection.D_RIGHT][FCurrentAnimation].Reset;
878 FMaskAnim[TDirection.D_RIGHT][FCurrentAnimation].Reset;
879 end;
880 end;
882 destructor TPlayerModel.Destroy();
883 var
884 a: Byte;
885 begin
886 for a := A_STAND to A_LAST do
887 begin
888 FAnim[TDirection.D_LEFT][a].Free();
889 FMaskAnim[TDirection.D_LEFT][a].Free();
890 FAnim[TDirection.D_RIGHT][a].Free();
891 FMaskAnim[TDirection.D_RIGHT][a].Free();
892 end;
894 inherited;
895 end;
897 function TPlayerModel.GetCurrentAnimation: TAnimation;
898 begin
899 if (FDirection = TDirection.D_LEFT) and (FAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
900 Result := FAnim[TDirection.D_LEFT][FCurrentAnimation]
901 else
902 Result := FAnim[TDirection.D_RIGHT][FCurrentAnimation];
903 end;
905 function TPlayerModel.GetCurrentAnimationMask: TAnimation;
906 begin
907 if (FDirection = TDirection.D_LEFT) and (FMaskAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
908 Result := FMaskAnim[TDirection.D_LEFT][FCurrentAnimation]
909 else
910 Result := FMaskAnim[TDirection.D_RIGHT][FCurrentAnimation];
911 end;
913 function TPlayerModel.PlaySound(SoundType, Level: Byte; X, Y: Integer): Boolean;
914 var
915 TempArray: array of DWORD;
916 a: Integer;
917 begin
918 Result := False;
919 SetLength(TempArray, 0);
921 if SoundType = MODELSOUND_PAIN then
922 begin
923 if FPainSounds = nil then Exit;
925 for a := 0 to High(FPainSounds) do
926 if FPainSounds[a].Level = Level then
927 begin
928 SetLength(TempArray, Length(TempArray)+1);
929 TempArray[High(TempArray)] := FPainSounds[a].ID;
930 end;
931 end
932 else
933 begin
934 if (Level in [2, 3, 5]) and (FSlopSound > 0) then
935 begin
936 g_Sound_PlayExAt('SOUND_MONSTER_SLOP', X, Y);
937 if FSlopSound = 1 then
938 begin
939 Result := True;
940 Exit;
941 end;
942 end;
943 if FDieSounds = nil then Exit;
945 for a := 0 to High(FDieSounds) do
946 if FDieSounds[a].Level = Level then
947 begin
948 SetLength(TempArray, Length(TempArray)+1);
949 TempArray[High(TempArray)] := FDieSounds[a].ID;
950 end;
951 if (TempArray = nil) and (Level = 5) then
952 begin
953 g_Sound_PlayExAt('SOUND_MONSTER_SLOP', X, Y);
954 Result := True;
955 Exit;
956 end;
957 end;
959 if TempArray = nil then Exit;
961 g_Sound_PlayAt(TempArray[Random(Length(TempArray))], X, Y);
963 Result := True;
964 end;
966 procedure TPlayerModel.SetColor(Red, Green, Blue: Byte);
967 begin
968 FColor.R := Red;
969 FColor.G := Green;
970 FColor.B := Blue;
971 end;
973 procedure TPlayerModel.SetFire(Fire: Boolean);
974 begin
975 FFire := Fire;
977 if FFire then FFireCounter := FAnim[TDirection.D_RIGHT, A_ATTACK].Speed*FAnim[TDirection.D_RIGHT, A_ATTACK].TotalFrames
978 else FFireCounter := 0;
979 end;
981 procedure TPlayerModel.SetFlag(Flag: Byte);
982 var
983 id: DWORD;
984 begin
985 FFlag := Flag;
987 FFlagAnim.Free();
988 FFlagAnim := nil;
990 case Flag of
991 FLAG_RED: g_Frames_Get(id, 'FRAMES_FLAG_RED');
992 FLAG_BLUE: g_Frames_Get(id, 'FRAMES_FLAG_BLUE');
993 else Exit;
994 end;
996 FFlagAnim := TAnimation.Create(id, True, 8);
997 end;
999 procedure TPlayerModel.SetWeapon(Weapon: Byte);
1000 begin
1001 FCurrentWeapon := Weapon;
1002 end;
1004 procedure TPlayerModel.Update();
1005 begin
1006 if (FDirection = TDirection.D_LEFT) and (FAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
1007 FAnim[TDirection.D_LEFT][FCurrentAnimation].Update else FAnim[TDirection.D_RIGHT][FCurrentAnimation].Update;
1009 if (FDirection = TDirection.D_LEFT) and (FMaskAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
1010 FMaskAnim[TDirection.D_LEFT][FCurrentAnimation].Update else FMaskAnim[TDirection.D_RIGHT][FCurrentAnimation].Update;
1012 if FFlagAnim <> nil then FFlagAnim.Update;
1014 if FFireCounter > 0 then Dec(FFireCounter) else FFire := False;
1015 end;
1017 end.