DEADSOFTWARE

acc89b26571f32665b37684b9ba4042999e8e938
[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, either version 3 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *)
16 {$INCLUDE ../shared/a_modes.inc}
17 {$M+}
18 unit g_playermodel;
20 interface
22 uses
23 {$IFDEF USE_MEMPOOL}mempool,{$ENDIF}
24 MAPDEF, g_textures, g_basic, g_weapons, e_graphics, utils, g_gfx,
25 ImagingTypes, Imaging, ImagingUtility;
27 const
28 A_STAND = 0;
29 A_WALK = 1;
30 A_DIE1 = 2;
31 A_DIE2 = 3;
32 A_ATTACK = 4;
33 A_SEEUP = 5;
34 A_SEEDOWN = 6;
35 A_ATTACKUP = 7;
36 A_ATTACKDOWN = 8;
37 A_PAIN = 9;
38 // EXTENDED
39 A_WALKATTACK = 10;
40 A_WALKSEEUP = 11;
41 A_WALKSEEDOWN = 12;
42 A_WALKATTACKUP = 13;
43 A_WALKATTACKDOWN = 14;
44 A_FISTSTAND = 15;
45 A_FISTWALK = 16;
46 A_FISTATTACK = 17;
47 A_FISTWALKATTACK = 18;
48 A_FISTSEEUP = 19;
49 A_FISTSEEDOWN = 20;
50 A_FISTATTACKUP = 21;
51 A_FISTATTACKDOWN = 22;
53 A_LASTBASE = A_PAIN;
54 A_LASTEXT = A_FISTATTACKDOWN;
55 A_LAST = A_LASTEXT;
57 MODELSOUND_PAIN = 0;
58 MODELSOUND_DIE = 1;
60 type
61 TModelInfo = record
62 Name: String;
63 Author: String;
64 Description: String;
65 HaveWeapon: Boolean;
66 end;
68 TModelBlood = record
69 R, G, B, Kind: Byte;
70 end;
72 TModelSound = record
73 ID: DWORD;
74 Level: Byte;
75 end;
77 TGibSprite = record
78 ID: DWORD;
79 MaskID: DWORD;
80 Rect: TRectWH;
81 OnlyOne: Boolean;
82 end;
84 TModelSoundArray = Array of TModelSound;
85 TGibsArray = Array of TGibSprite;
86 TWeaponPoints = Array [WP_FIRST + 1..WP_LAST] of
87 Array [A_STAND..A_LAST] of
88 Array [TDirection.D_LEFT..TDirection.D_RIGHT] of Array of TDFPoint;
90 TPlayerModel = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF}
91 private
92 FName: String;
93 FDirection: TDirection;
94 FColor: TRGB;
95 FBlood: TModelBlood;
96 FCurrentAnimation: Byte;
97 FAnim: Array [TDirection.D_LEFT..TDirection.D_RIGHT] of Array [A_STAND..A_LAST] of TAnimation;
98 FMaskAnim: Array [TDirection.D_LEFT..TDirection.D_RIGHT] of Array [A_STAND..A_LAST] of TAnimation;
99 FWeaponPoints: TWeaponPoints;
100 FPainSounds: TModelSoundArray;
101 FDieSounds: TModelSoundArray;
102 FSlopSound: Byte;
103 FCurrentWeapon: Byte;
104 FDrawWeapon: Boolean;
105 FFlag: Byte;
106 FFlagPoint: TDFPoint;
107 FFlagAngle: SmallInt;
108 FFlagAnim: TAnimation;
109 FFire: Boolean;
110 FFireCounter: Byte;
112 public
113 destructor Destroy(); override;
114 procedure ChangeAnimation(Animation: Byte; Force: Boolean = False);
115 function GetCurrentAnimation: TAnimation;
116 function GetCurrentAnimationMask: TAnimation;
117 procedure SetColor(Red, Green, Blue: Byte);
118 procedure SetWeapon(Weapon: Byte);
119 procedure SetFlag(Flag: Byte);
120 procedure SetFire(Fire: Boolean);
121 function PlaySound(SoundType, Level: Byte; X, Y: Integer): Boolean;
122 procedure Update();
123 procedure Draw(X, Y: Integer; Alpha: Byte = 0);
125 published
126 property Fire: Boolean read FFire;
127 property Direction: TDirection read FDirection write FDirection;
128 property Animation: Byte read FCurrentAnimation;
129 property Weapon: Byte read FCurrentWeapon;
130 property Name: String read FName;
132 public
133 property Color: TRGB read FColor write FColor;
134 property Blood: TModelBlood read FBlood;
135 end;
137 procedure g_PlayerModel_LoadData();
138 procedure g_PlayerModel_FreeData();
139 function g_PlayerModel_Load(FileName: String): Boolean;
140 function g_PlayerModel_GetNames(): SSArray;
141 function g_PlayerModel_GetInfo(ModelName: String): TModelInfo;
142 function g_PlayerModel_GetBlood(ModelName: String): TModelBlood;
143 function g_PlayerModel_Get(ModelName: String): TPlayerModel;
144 function g_PlayerModel_GetAnim(ModelName: String; Anim: Byte; var _Anim, _Mask: TAnimation): Boolean;
145 function g_PlayerModel_GetGibs(ModelName: String; var Gibs: TGibsArray): Boolean;
148 implementation
150 uses
151 {$INCLUDE ../nogl/noGLuses.inc}
152 g_main, g_sound, g_console, SysUtils, g_player, CONFIG,
153 e_sound, g_options, g_map, Math, e_log, wadreader;
155 type
156 TPlayerModelInfo = record
157 Info: TModelInfo;
158 ModelSpeed: Array [A_STAND..A_PAIN] of Byte;
159 FlagPoint: TDFPoint;
160 FlagAngle: SmallInt;
161 WeaponPoints: TWeaponPoints;
162 Gibs: TGibsArray;
163 PainSounds: TModelSoundArray;
164 DieSounds: TModelSoundArray;
165 SlopSound: Byte;
166 Blood: TModelBlood;
167 end;
169 const
170 W_POS_NORMAL = 0;
171 W_POS_UP = 1;
172 W_POS_DOWN = 2;
174 W_ACT_NORMAL = 0;
175 W_ACT_FIRE = 1;
177 FLAG_BASEPOINT: TDFPoint = (X:16; Y:43);
178 FLAG_DEFPOINT: TDFPoint = (X:32; Y:16);
179 FLAG_DEFANGLE = -20;
180 WEAPONBASE: Array [WP_FIRST + 1..WP_LAST] of TDFPoint =
181 ((X:8; Y:4), (X:8; Y:8), (X:16; Y:16), (X:16; Y:24),
182 (X:16; Y:16), (X:24; Y:24), (X:16; Y:16), (X:24; Y:24),
183 (X:16; Y:16), (X:8; Y:8));
185 AnimNames: Array [A_STAND..A_LASTEXT] of String =
186 ('StandAnim','WalkAnim','Die1Anim','Die2Anim','AttackAnim',
187 'SeeUpAnim','SeeDownAnim','AttackUpAnim','AttackDownAnim','PainAnim',
188 // EXTENDED
189 'WalkAttackAnim', 'WalkSeeUpAnim', 'WalkSeeDownAnim',
190 'WalkAttackUpAnim', 'WalkAttackDownAnim', 'FistStandAnim', 'FistWalkAnim',
191 'FistAttackAnim', 'FistWalkAttackAnim', 'FistSeeUpAnim', 'FistSeeDownAnim',
192 'FistAttackUpAnim', 'FistAttackDownAnim');
193 WeapNames: Array [WP_FIRST + 1..WP_LAST] of String =
194 ('csaw', 'hgun', 'sg', 'ssg', 'mgun', 'rkt', 'plz', 'bfg', 'spl', 'flm');
196 var
197 WeaponID: Array [WP_FIRST + 1..WP_LAST] of
198 Array [W_POS_NORMAL..W_POS_DOWN] of
199 Array [W_ACT_NORMAL..W_ACT_FIRE] of DWORD;
200 PlayerModelsArray: Array of TPlayerModelInfo;
202 procedure g_PlayerModel_LoadData();
203 var
204 a: Integer;
205 begin
206 for a := WP_FIRST + 1 to WP_LAST do
207 begin
208 g_Texture_CreateWAD(WeaponID[a][W_POS_NORMAL][W_ACT_NORMAL], GameWAD+':WEAPONS\'+UpperCase(WeapNames[a]));
209 g_Texture_CreateWAD(WeaponID[a][W_POS_NORMAL][W_ACT_FIRE], GameWAD+':WEAPONS\'+UpperCase(WeapNames[a])+'_FIRE');
210 g_Texture_CreateWAD(WeaponID[a][W_POS_UP][W_ACT_NORMAL], GameWAD+':WEAPONS\'+UpperCase(WeapNames[a])+'_UP');
211 g_Texture_CreateWAD(WeaponID[a][W_POS_UP][W_ACT_FIRE], GameWAD+':WEAPONS\'+UpperCase(WeapNames[a])+'_UP_FIRE');
212 g_Texture_CreateWAD(WeaponID[a][W_POS_DOWN][W_ACT_NORMAL], GameWAD+':WEAPONS\'+UpperCase(WeapNames[a])+'_DN');
213 g_Texture_CreateWAD(WeaponID[a][W_POS_DOWN][W_ACT_FIRE], GameWAD+':WEAPONS\'+UpperCase(WeapNames[a])+'_DN_FIRE');
214 end;
215 end;
217 function GetPoint(var str: String; var point: TDFPoint): Boolean;
218 var
219 a, x, y: Integer;
220 s: String;
221 begin
222 Result := False;
223 x := 0;
224 y := 0;
226 str := Trim(str);
227 if Length(str) < 3 then
228 Exit;
230 for a := 1 to Length(str) do
231 if (str[a] = ',') or (a = Length(str)) then
232 begin
233 s := Copy(str, 1, a);
234 if s[Length(s)] = ',' then
235 SetLength(s, Length(s)-1);
236 Delete(str, 1, a);
238 if (Sscanf(s, '%d:%d', [@x, @y]) < 2) or
239 (x < -64) or (x > 128) or
240 (y < -64) or (y > 128) then
241 Exit;
243 point.X := x;
244 point.Y := y;
246 Break;
247 end;
249 Result := True;
250 end;
252 function GetWeapPoints(str: String; weapon: Byte; anim: Byte; dir: TDirection;
253 frames: Word; backanim: Boolean; var wpoints: TWeaponPoints): Boolean;
254 var
255 a, b, h: Integer;
256 begin
257 Result := False;
259 if frames = 0 then
260 Exit;
262 backanim := backanim and (frames > 2);
264 for a := 1 to frames do
265 begin
266 if not GetPoint(str, wpoints[weapon, anim, dir, a-1]) then
267 Exit;
269 with wpoints[weapon, anim, dir, a-1] do
270 begin
271 X := X - WEAPONBASE[weapon].X;
272 Y := Y - WEAPONBASE[weapon].Y;
273 if dir = TDirection.D_LEFT then
274 X := -X;
275 end;
276 end;
278 h := High(wpoints[weapon, anim, dir]);
279 if backanim then
280 for b := h downto frames do
281 wpoints[weapon, anim, dir, b] := wpoints[weapon, anim, dir, h-b+1];
283 Result := True;
284 end;
286 procedure ExtAnimFromBaseAnim(MName: String; AIdx: Integer);
287 const
288 CopyAnim: array [A_LASTBASE+1..A_LASTEXT] of Integer = (
289 A_WALK, A_WALK, A_WALK, A_WALK, A_WALK,
290 A_STAND, A_WALK, A_ATTACK, A_WALK, A_SEEUP, A_SEEDOWN,
291 A_ATTACKUP, A_ATTACKDOWN
292 );
293 var
294 OIdx, W, I: Integer;
295 D: TDirection;
296 AName, OName: String;
297 begin
298 // HACK: shitty workaround to duplicate base animations
299 // in place of extended, replace with something better later
301 Assert((AIdx > A_LASTBASE) and (AIdx <= A_LASTEXT));
302 OIdx := CopyAnim[AIdx];
304 AName := MName + '_RIGHTANIM' + IntToStr(AIdx);
305 OName := MName + '_RIGHTANIM' + IntToStr(OIdx);
306 Assert(g_Frames_Dup(AName, OName));
307 Assert(g_Frames_Dup(AName + '_MASK', OName + '_MASK'));
308 AName := MName + '_LEFTANIM' + IntToStr(AIdx);
309 OName := MName + '_LEFTANIM' + IntToStr(OIdx);
310 if g_Frames_Exists(AName) then
311 begin
312 g_Frames_Dup(AName, OName);
313 g_Frames_Dup(AName + '_MASK', OName + '_MASK');
314 end;
316 with PlayerModelsArray[High(PlayerModelsArray)] do
317 begin
318 for W := WP_FIRST + 1 to WP_LAST do
319 begin
320 for D := TDirection.D_LEFT to TDirection.D_RIGHT do
321 begin
322 SetLength(WeaponPoints[W, AIdx, D], Length(WeaponPoints[W, OIdx, D]));
323 for I := 0 to High(WeaponPoints[W, AIdx, D]) do
324 WeaponPoints[W, AIdx, D, I] := WeaponPoints[W, OIdx, D, I]
325 end;
326 end;
327 end;
328 end;
330 function g_PlayerModel_CalcGibSize (pData: Pointer; dataSize, x, y, w, h: Integer): TRectWH;
331 var i, j: Integer; done: Boolean; img: TImageData;
333 function IsVoid (i, j: Integer): Boolean;
334 begin
335 result := Byte((PByte(img.bits) + (y+j)*img.width*4 + (x+i)*4 + 3)^) = 0
336 end;
338 begin
339 InitImage(img);
340 assert(LoadImageFromMemory(pData, dataSize, img));
342 (* trace x from right to left *)
343 done := false; i := 0;
344 while not done and (i < w) do
345 begin
346 j := 0;
347 while (j < h) and IsVoid(i, j) do inc(j);
348 done := (j < h) and (IsVoid(i, j) = false);
349 result.x := i;
350 inc(i);
351 end;
353 (* trace y from up to down *)
354 done := false; j := 0;
355 while not done and (j < h) do
356 begin
357 i := 0;
358 while (i < w) and IsVoid(i, j) do inc(i);
359 done := (i < w) and (IsVoid(i, j) = false);
360 result.y := j;
361 inc(j);
362 end;
364 (* trace x from right to left *)
365 done := false; i := w - 1;
366 while not done and (i >= 0) do
367 begin
368 j := 0;
369 while (j < h) and IsVoid(i, j) do inc(j);
370 done := (j < h) and (IsVoid(i, j) = false);
371 result.width := i - result.x + 1;
372 dec(i);
373 end;
375 (* trace y from down to up *)
376 done := false; j := h - 1;
377 while not done and (j >= 0) do
378 begin
379 i := 0;
380 while (i < w) and IsVoid(i, j) do inc(i);
381 done := (i < w) and (IsVoid(i, j) = false);
382 result.height := j - result.y + 1;
383 dec(j);
384 end;
386 FreeImage(img);
387 end;
389 function g_PlayerModel_Load(FileName: string): Boolean;
390 var
391 ID: DWORD;
392 a, b, len, lenpd, lenpd2, aa, bb, f: Integer;
393 cc: TDirection;
394 config: TConfig;
395 pData, pData2: Pointer;
396 WAD: TWADFile;
397 s, aname: string;
398 prefix: string;
399 ok, chk: Boolean;
400 begin
401 e_WriteLog(Format('Loading player model: %s', [ExtractFileName(FileName)]), TMsgType.Notify);
403 Result := False;
405 WAD := TWADFile.Create;
406 WAD.ReadFile(FileName);
408 if {WAD.GetLastError <> DFWAD_NOERROR} not WAD.isOpen then
409 begin
410 WAD.Free();
411 Exit;
412 end;
414 if not WAD.GetResource('TEXT/MODEL', pData, len) then
415 begin
416 WAD.Free();
417 Exit;
418 end;
420 config := TConfig.CreateMem(pData, len);
421 FreeMem(pData);
423 s := config.ReadStr('Model', 'name', '');
424 if s = '' then
425 begin
426 config.Free();
427 WAD.Free();
428 Exit;
429 end;
431 SetLength(PlayerModelsArray, Length(PlayerModelsArray)+1);
432 ID := High(PlayerModelsArray);
434 prefix := FileName+':TEXTURES\';
436 with PlayerModelsArray[ID].Info do
437 begin
438 Name := s;
439 Author := config.ReadStr('Model', 'author', '');
440 Description := config.ReadStr('Model', 'description', '');
441 end;
443 with PlayerModelsArray[ID] do
444 begin
445 Blood.R := MAX(0, MIN(255, config.ReadInt('Blood', 'R', 150)));
446 Blood.G := MAX(0, MIN(255, config.ReadInt('Blood', 'G', 0)));
447 Blood.B := MAX(0, MIN(255, config.ReadInt('Blood', 'B', 0)));
448 case config.ReadStr('Blood', 'Kind', 'NORMAL') of
449 'NORMAL': Blood.Kind := BLOOD_NORMAL;
450 'SPARKS': Blood.Kind := BLOOD_SPARKS;
451 else
452 Blood.Kind := BLOOD_NORMAL
453 end
454 end;
456 for b := A_STAND to A_LAST do
457 begin
458 aname := s+'_RIGHTANIM'+IntToStr(b);
459 //e_LogWritefln('### MODEL FILE: [%s]', [prefix+config.ReadStr(AnimNames[b], 'resource', '')]);
460 if not (g_Frames_CreateWAD(nil, aname,
461 prefix+config.ReadStr(AnimNames[b], 'resource', ''),
462 64, 64, config.ReadInt(AnimNames[b], 'frames', 1),
463 config.ReadBool(AnimNames[b], 'backanim', False)) and
464 g_Frames_CreateWAD(nil, aname+'_MASK',
465 prefix+config.ReadStr(AnimNames[b], 'mask', ''),
466 64, 64, config.ReadInt(AnimNames[b], 'frames', 1),
467 config.ReadBool(AnimNames[b], 'backanim', False))) then
468 begin
469 if b <= A_LASTBASE then
470 begin
471 config.Free();
472 WAD.Free();
473 Exit;
474 end
475 else
476 begin
477 ExtAnimFromBaseAnim(s, b);
478 continue;
479 end;
480 end;
482 for aa := WP_FIRST + 1 to WP_LAST do
483 for bb := A_STAND to A_LAST do
484 for cc := TDirection.D_LEFT to TDirection.D_RIGHT do
485 begin
486 f := config.ReadInt(AnimNames[bb], 'frames', 1);
487 if config.ReadBool(AnimNames[bb], 'backanim', False) then
488 if f > 2 then f := 2*f-2;
489 SetLength(PlayerModelsArray[ID].WeaponPoints[aa, bb, cc], f);
490 end;
492 if (config.ReadStr(AnimNames[b], 'resource2', '') <> '') and
493 (config.ReadStr(AnimNames[b], 'mask2', '') <> '') then
494 begin
495 aname := s+'_LEFTANIM'+IntToStr(b);
496 g_Frames_CreateWAD(nil, aname,
497 prefix+config.ReadStr(AnimNames[b], 'resource2', ''),
498 64, 64, config.ReadInt(AnimNames[b], 'frames', 1),
499 config.ReadBool(AnimNames[b], 'backanim', False));
501 g_Frames_CreateWAD(nil, aname+'_MASK',
502 prefix+config.ReadStr(AnimNames[b], 'mask2', ''),
503 64, 64, config.ReadInt(AnimNames[b], 'frames', 1),
504 config.ReadBool(AnimNames[b], 'backanim', False));
505 end;
507 PlayerModelsArray[ID].ModelSpeed[b] := Max(1, config.ReadInt(AnimNames[b], 'waitcount', 1) div 3);
508 end;
510 with PlayerModelsArray[ID], config do
511 begin
512 prefix := FileName+':SOUNDS\';
514 a := 1;
515 repeat
516 s := config.ReadStr('Sound', 'pain'+IntToStr(a), '');
517 if s <> '' then
518 begin
519 SetLength(PainSounds, Length(PainSounds)+1);
520 g_Sound_CreateWAD(PainSounds[High(PainSounds)].ID, prefix+s);
521 PainSounds[High(PainSounds)].Level := config.ReadInt('Sound', 'painlevel'+IntToStr(a), 1);
522 end;
523 a := a+1;
524 until s = '';
526 a := 1;
527 repeat
528 s := config.ReadStr('Sound', 'die'+IntToStr(a), '');
529 if s <> '' then
530 begin
531 SetLength(DieSounds, Length(DieSounds)+1);
532 g_Sound_CreateWAD(DieSounds[High(DieSounds)].ID, prefix+s);
533 DieSounds[High(DieSounds)].Level := config.ReadInt('Sound', 'dielevel'+IntToStr(a), 1);
534 end;
535 a := a+1;
536 until s = '';
538 SlopSound := Min(Max(config.ReadInt('Sound', 'slop', 0), 0), 2);
540 SetLength(Gibs, ReadInt('Gibs', 'count', 0));
542 if (Gibs <> nil) and
543 (WAD.GetResource('TEXTURES/'+config.ReadStr('Gibs', 'resource', 'GIBS'), pData, lenpd)) and
544 (WAD.GetResource('TEXTURES/'+config.ReadStr('Gibs', 'mask', 'GIBSMASK'), pData2, lenpd2)) then
545 begin
546 for a := 0 to High(Gibs) do
547 if e_CreateTextureMemEx(pData, lenpd, Gibs[a].ID, a*32, 0, 32, 32) and
548 e_CreateTextureMemEx(pData2, lenpd2, Gibs[a].MaskID, a*32, 0, 32, 32) then
549 begin
550 //Gibs[a].Rect := e_GetTextureSize2(Gibs[a].ID);
551 Gibs[a].Rect := g_PlayerModel_CalcGibSize(pData, lenpd, a*32, 0, 32, 32);
552 with Gibs[a].Rect do
553 if Height > 3 then Height := Height-1-Random(2);
554 Gibs[a].OnlyOne := config.ReadInt('Gibs', 'once', -1) = a+1;
555 end;
557 FreeMem(pData);
558 FreeMem(pData2);
559 end;
561 ok := True;
562 for aa := WP_FIRST + 1 to WP_LAST do
563 for bb := A_STAND to A_LAST do
564 if not (bb in [A_DIE1, A_DIE2, A_PAIN]) then
565 begin
566 chk := GetWeapPoints(config.ReadStr(AnimNames[bb], WeapNames[aa]+'_points', ''), aa, bb, TDirection.D_RIGHT,
567 config.ReadInt(AnimNames[bb], 'frames', 0),
568 config.ReadBool(AnimNames[bb], 'backanim', False),
569 WeaponPoints);
570 if ok and (not chk) and (aa = WEAPON_FLAMETHROWER) then
571 begin
572 // workaround for flamethrower
573 chk := GetWeapPoints(config.ReadStr(AnimNames[bb], WeapNames[WEAPON_PLASMA]+'_points', ''), aa, bb, TDirection.D_RIGHT,
574 config.ReadInt(AnimNames[bb], 'frames', 0),
575 config.ReadBool(AnimNames[bb], 'backanim', False),
576 WeaponPoints);
577 if chk then
578 for f := 0 to High(WeaponPoints[aa, bb, TDirection.D_RIGHT]) do
579 begin
580 case bb of
581 A_STAND, A_PAIN:
582 begin
583 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 6);
584 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 8);
585 end;
586 A_WALKATTACK, A_WALK:
587 begin
588 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 9);
589 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 9);
590 end;
591 A_ATTACK:
592 begin
593 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 5);
594 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 8);
595 end;
596 A_WALKSEEUP, A_SEEUP:
597 begin
598 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 5);
599 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 16);
600 end;
601 A_WALKSEEDOWN, A_SEEDOWN:
602 begin
603 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 6);
604 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 5);
605 end;
606 A_WALKATTACKUP, A_ATTACKUP:
607 begin
608 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 5);
609 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 16);
610 end;
611 A_WALKATTACKDOWN, A_ATTACKDOWN:
612 begin
613 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 6);
614 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 4);
615 end;
616 end;
617 end;
618 end;
619 ok := ok and (chk or (bb > A_LASTBASE));
621 if not GetWeapPoints(config.ReadStr(AnimNames[bb], WeapNames[aa]+'2_points', ''), aa, bb, TDirection.D_LEFT,
622 config.ReadInt(AnimNames[bb], 'frames', 0),
623 config.ReadBool(AnimNames[bb], 'backanim', False),
624 WeaponPoints) then
625 for f := 0 to High(WeaponPoints[aa, bb, TDirection.D_RIGHT]) do
626 begin
627 WeaponPoints[aa, bb, TDirection.D_LEFT, f].X := -WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X;
628 WeaponPoints[aa, bb, TDirection.D_LEFT, f].Y := WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y;
629 end;
631 if not ok then Break;
632 end;
633 {if ok then g_Console_Add(Info.Name+' weapon points ok')
634 else g_Console_Add(Info.Name+' weapon points fail');}
635 Info.HaveWeapon := ok;
637 s := config.ReadStr('Model', 'flag_point', '');
638 if not GetPoint(s, FlagPoint) then FlagPoint := FLAG_DEFPOINT;
640 FlagAngle := config.ReadInt('Model', 'flag_angle', FLAG_DEFANGLE);
641 end;
643 config.Free();
644 WAD.Free();
646 Result := True;
647 end;
649 function g_PlayerModel_Get(ModelName: String): TPlayerModel;
650 var
651 a: Integer;
652 b: Byte;
653 ID, ID2: DWORD;
654 begin
655 Result := nil;
657 if PlayerModelsArray = nil then Exit;
659 for a := 0 to High(PlayerModelsArray) do
660 if AnsiLowerCase(PlayerModelsArray[a].Info.Name) = AnsiLowerCase(ModelName) then
661 begin
662 Result := TPlayerModel.Create;
664 with PlayerModelsArray[a] do
665 begin
666 Result.FName := Info.Name;
667 Result.FBlood := Blood;
669 for b := A_STAND to A_LAST do
670 begin
671 if not (g_Frames_Get(ID, Info.Name+'_RIGHTANIM'+IntToStr(b)) and
672 g_Frames_Get(ID2, Info.Name+'_RIGHTANIM'+IntToStr(b)+'_MASK')) then
673 begin
674 Result.Free();
675 Result := nil;
676 Exit;
677 end;
679 Result.FAnim[TDirection.D_RIGHT][b] := TAnimation.Create(ID, b in [A_STAND, A_WALK], ModelSpeed[b]);
681 Result.FMaskAnim[TDirection.D_RIGHT][b] := TAnimation.Create(ID2, b in [A_STAND, A_WALK], ModelSpeed[b]);
683 if g_Frames_Exists(Info.Name+'_LEFTANIM'+IntToStr(b)) and
684 g_Frames_Exists(Info.Name+'_LEFTANIM'+IntToStr(b)+'_MASK') then
685 if g_Frames_Get(ID, Info.Name+'_LEFTANIM'+IntToStr(b)) and
686 g_Frames_Get(ID2, Info.Name+'_LEFTANIM'+IntToStr(b)+'_MASK') then
687 begin
688 Result.FAnim[TDirection.D_LEFT][b] := TAnimation.Create(ID, b in [A_STAND, A_WALK], ModelSpeed[b]);
690 Result.FMaskAnim[TDirection.D_LEFT][b] := TAnimation.Create(ID2, b in [A_STAND, A_WALK], ModelSpeed[b]);
691 end;
692 end;
694 Result.FPainSounds := PainSounds;
695 Result.FDieSounds := DieSounds;
696 Result.FSlopSound := SlopSound;
697 Result.FDrawWeapon := Info.HaveWeapon;
698 Result.FWeaponPoints := WeaponPoints;
700 Result.FFlagPoint := FlagPoint;
701 Result.FFlagAngle := FlagAngle;
703 Break;
704 end;
705 end;
706 end;
708 function g_PlayerModel_GetAnim(ModelName: string; Anim: Byte; var _Anim, _Mask: TAnimation): Boolean;
709 var
710 a: Integer;
711 c: Boolean;
712 ID: DWORD;
713 begin
714 Result := False;
716 if PlayerModelsArray = nil then Exit;
717 for a := 0 to High(PlayerModelsArray) do
718 if PlayerModelsArray[a].Info.Name = ModelName then
719 with PlayerModelsArray[a] do
720 begin
721 if Anim in [A_STAND, A_WALK] then c := True else c := False;
723 if not g_Frames_Get(ID, Info.Name+'_RIGHTANIM'+IntToStr(Anim)) then
724 if not g_Frames_Get(ID, Info.Name+'_LEFTANIM'+IntToStr(Anim)) then Exit;
726 _Anim := TAnimation.Create(ID, c, ModelSpeed[Anim]);
727 _Anim.Speed := ModelSpeed[Anim];
729 if not g_Frames_Get(ID, Info.Name+'_RIGHTANIM'+IntToStr(Anim)+'_MASK') then
730 if not g_Frames_Get(ID, Info.Name+'_LEFTANIM'+IntToStr(Anim)+'_MASK') then Exit;
732 _Mask := TAnimation.Create(ID, c, ModelSpeed[Anim]);
733 _Mask.Speed := ModelSpeed[Anim];
735 Break;
736 end;
738 Result := True;
739 end;
741 function g_PlayerModel_GetGibs(ModelName: string; var Gibs: TGibsArray): Boolean;
742 var
743 a, i, b: Integer;
744 c: Boolean;
745 begin
746 Result := False;
748 if PlayerModelsArray = nil then Exit;
749 if gGibsCount = 0 then Exit;
751 c := False;
753 SetLength(Gibs, gGibsCount);
755 for a := 0 to High(PlayerModelsArray) do
756 if PlayerModelsArray[a].Info.Name = ModelName then
757 begin
758 for i := 0 to High(Gibs) do
759 begin
760 if c and (Length(PlayerModelsArray[a].Gibs) = 1) then
761 begin
762 SetLength(Gibs, i);
763 Break;
764 end;
766 repeat
767 b := Random(Length(PlayerModelsArray[a].Gibs));
768 until not (PlayerModelsArray[a].Gibs[b].OnlyOne and c);
770 Gibs[i] := PlayerModelsArray[a].Gibs[b];
772 if Gibs[i].OnlyOne then c := True;
773 end;
775 Result := True;
776 Break;
777 end;
778 end;
780 function g_PlayerModel_GetNames(): SSArray;
781 var
782 i: DWORD;
783 begin
784 Result := nil;
786 if PlayerModelsArray = nil then Exit;
788 for i := 0 to High(PlayerModelsArray) do
789 begin
790 SetLength(Result, Length(Result)+1);
791 Result[High(Result)] := PlayerModelsArray[i].Info.Name;
792 end;
793 end;
795 function g_PlayerModel_GetInfo(ModelName: string): TModelInfo;
796 var
797 a: Integer;
798 begin
799 FillChar(Result, SizeOf(Result), 0);
800 if PlayerModelsArray = nil then Exit;
802 for a := 0 to High(PlayerModelsArray) do
803 if PlayerModelsArray[a].Info.Name = ModelName then
804 begin
805 Result := PlayerModelsArray[a].Info;
806 Break;
807 end;
808 end;
810 function g_PlayerModel_GetBlood(ModelName: string): TModelBlood;
811 var
812 a: Integer;
813 begin
814 Result.R := 150;
815 Result.G := 0;
816 Result.B := 0;
817 Result.Kind := BLOOD_NORMAL;
818 if PlayerModelsArray = nil then Exit;
820 for a := 0 to High(PlayerModelsArray) do
821 if PlayerModelsArray[a].Info.Name = ModelName then
822 begin
823 Result := PlayerModelsArray[a].Blood;
824 Break;
825 end;
826 end;
828 procedure g_PlayerModel_FreeData();
829 var
830 i: DWORD;
831 a, b, c: Integer;
832 begin
833 for a := WP_FIRST + 1 to WP_LAST do
834 for b := W_POS_NORMAL to W_POS_DOWN do
835 for c := W_ACT_NORMAL to W_ACT_FIRE do
836 e_DeleteTexture(WeaponID[a][b][c]);
838 e_WriteLog('Releasing models...', TMsgType.Notify);
840 if PlayerModelsArray = nil then Exit;
842 for i := 0 to High(PlayerModelsArray) do
843 with PlayerModelsArray[i] do
844 begin
845 for a := A_STAND to A_LAST do
846 begin
847 g_Frames_DeleteByName(Info.Name+'_LEFTANIM'+IntToStr(a));
848 g_Frames_DeleteByName(Info.Name+'_LEFTANIM'+IntToStr(a)+'_MASK');
849 g_Frames_DeleteByName(Info.Name+'_RIGHTANIM'+IntToStr(a));
850 g_Frames_DeleteByName(Info.Name+'_RIGHTANIM'+IntToStr(a)+'_MASK');
851 end;
853 if PainSounds <> nil then
854 for b := 0 to High(PainSounds) do
855 e_DeleteSound(PainSounds[b].ID);
857 if DieSounds <> nil then
858 for b := 0 to High(DieSounds) do
859 e_DeleteSound(DieSounds[b].ID);
861 if Gibs <> nil then
862 for b := 0 to High(Gibs) do
863 begin
864 e_DeleteTexture(Gibs[b].ID);
865 e_DeleteTexture(Gibs[b].MaskID);
866 end;
867 end;
869 PlayerModelsArray := nil;
870 end;
872 { TPlayerModel }
874 procedure TPlayerModel.ChangeAnimation(Animation: Byte; Force: Boolean = False);
875 begin
876 if not Force then if FCurrentAnimation = Animation then Exit;
878 FCurrentAnimation := Animation;
880 if (FDirection = TDirection.D_LEFT) and
881 (FAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) and
882 (FMaskAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
883 begin
884 FAnim[TDirection.D_LEFT][FCurrentAnimation].Reset;
885 FMaskAnim[TDirection.D_LEFT][FCurrentAnimation].Reset;
886 end
887 else
888 begin
889 FAnim[TDirection.D_RIGHT][FCurrentAnimation].Reset;
890 FMaskAnim[TDirection.D_RIGHT][FCurrentAnimation].Reset;
891 end;
892 end;
894 destructor TPlayerModel.Destroy();
895 var
896 a: Byte;
897 begin
898 for a := A_STAND to A_LAST do
899 begin
900 FAnim[TDirection.D_LEFT][a].Free();
901 FMaskAnim[TDirection.D_LEFT][a].Free();
902 FAnim[TDirection.D_RIGHT][a].Free();
903 FMaskAnim[TDirection.D_RIGHT][a].Free();
904 end;
906 inherited;
907 end;
909 procedure TPlayerModel.Draw(X, Y: Integer; Alpha: Byte = 0);
910 var
911 Mirror: TMirrorType;
912 pos, act: Byte;
913 p: TDFPoint;
914 begin
915 // Ôëàãè:
916 if Direction = TDirection.D_LEFT then
917 Mirror := TMirrorType.None
918 else
919 Mirror := TMirrorType.Horizontal;
921 if (FFlag <> FLAG_NONE) and (FFlagAnim <> nil) and
922 (not (FCurrentAnimation in [A_DIE1, A_DIE2])) then
923 begin
924 p.X := IfThen(Direction = TDirection.D_LEFT,
925 FLAG_BASEPOINT.X,
926 64-FLAG_BASEPOINT.X);
927 p.Y := FLAG_BASEPOINT.Y;
929 FFlagAnim.DrawEx(X+IfThen(Direction = TDirection.D_LEFT, FFlagPoint.X-1, 2*FLAG_BASEPOINT.X-FFlagPoint.X+1)-FLAG_BASEPOINT.X,
930 Y+FFlagPoint.Y-FLAG_BASEPOINT.Y+1, Mirror, p,
931 IfThen(FDirection = TDirection.D_RIGHT, FFlagAngle, -FFlagAngle));
932 end;
934 // Îðóæèå:
935 if Direction = TDirection.D_RIGHT then
936 Mirror := TMirrorType.None
937 else
938 Mirror := TMirrorType.Horizontal;
940 if FDrawWeapon and
941 (not (FCurrentAnimation in [A_DIE1, A_DIE2, A_PAIN])) and
942 (FCurrentWeapon in [WP_FIRST + 1..WP_LAST]) then
943 begin
944 if FCurrentAnimation in [A_SEEUP, A_ATTACKUP] then
945 pos := W_POS_UP
946 else
947 if FCurrentAnimation in [A_SEEDOWN, A_ATTACKDOWN] then
948 pos := W_POS_DOWN
949 else
950 pos := W_POS_NORMAL;
952 if (FCurrentAnimation in [A_ATTACK, A_ATTACKUP, A_ATTACKDOWN]) or
953 FFire then
954 act := W_ACT_FIRE
955 else
956 act := W_ACT_NORMAL;
958 if Alpha < 201 then
959 e_Draw(WeaponID[FCurrentWeapon][pos][act],
960 X+FWeaponPoints[FCurrentWeapon, FCurrentAnimation, FDirection,
961 FAnim[TDirection.D_RIGHT][FCurrentAnimation].CurrentFrame].X,
962 Y+FWeaponPoints[FCurrentWeapon, FCurrentAnimation, FDirection,
963 FAnim[TDirection.D_RIGHT][FCurrentAnimation].CurrentFrame].Y,
964 0, True, False, Mirror);
965 end;
967 // Ìîäåëü:
968 if (FDirection = TDirection.D_LEFT) and
969 (FAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
970 begin
971 FAnim[TDirection.D_LEFT][FCurrentAnimation].Alpha := Alpha;
972 FAnim[TDirection.D_LEFT][FCurrentAnimation].Draw(X, Y, TMirrorType.None);
973 end
974 else
975 begin
976 FAnim[TDirection.D_RIGHT][FCurrentAnimation].Alpha := Alpha;
977 FAnim[TDirection.D_RIGHT][FCurrentAnimation].Draw(X, Y, Mirror);
978 end;
980 // Ìàñêà ìîäåëè:
981 e_Colors := FColor;
983 if (FDirection = TDirection.D_LEFT) and
984 (FMaskAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
985 begin
986 FMaskAnim[TDirection.D_LEFT][FCurrentAnimation].Alpha := Alpha;
987 FMaskAnim[TDirection.D_LEFT][FCurrentAnimation].Draw(X, Y, TMirrorType.None);
988 end
989 else
990 begin
991 FMaskAnim[TDirection.D_RIGHT][FCurrentAnimation].Alpha := Alpha;
992 FMaskAnim[TDirection.D_RIGHT][FCurrentAnimation].Draw(X, Y, Mirror);
993 end;
995 e_Colors.R := 255;
996 e_Colors.G := 255;
997 e_Colors.B := 255;
998 end;
1000 function TPlayerModel.GetCurrentAnimation: TAnimation;
1001 begin
1002 if (FDirection = TDirection.D_LEFT) and (FAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
1003 Result := FAnim[TDirection.D_LEFT][FCurrentAnimation]
1004 else
1005 Result := FAnim[TDirection.D_RIGHT][FCurrentAnimation];
1006 end;
1008 function TPlayerModel.GetCurrentAnimationMask: TAnimation;
1009 begin
1010 if (FDirection = TDirection.D_LEFT) and (FMaskAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
1011 Result := FMaskAnim[TDirection.D_LEFT][FCurrentAnimation]
1012 else
1013 Result := FMaskAnim[TDirection.D_RIGHT][FCurrentAnimation];
1014 end;
1016 function TPlayerModel.PlaySound(SoundType, Level: Byte; X, Y: Integer): Boolean;
1017 var
1018 TempArray: array of DWORD;
1019 a: Integer;
1020 begin
1021 Result := False;
1022 SetLength(TempArray, 0);
1024 if SoundType = MODELSOUND_PAIN then
1025 begin
1026 if FPainSounds = nil then Exit;
1028 for a := 0 to High(FPainSounds) do
1029 if FPainSounds[a].Level = Level then
1030 begin
1031 SetLength(TempArray, Length(TempArray)+1);
1032 TempArray[High(TempArray)] := FPainSounds[a].ID;
1033 end;
1034 end
1035 else
1036 begin
1037 if (Level in [2, 3, 5]) and (FSlopSound > 0) then
1038 begin
1039 g_Sound_PlayExAt('SOUND_MONSTER_SLOP', X, Y);
1040 if FSlopSound = 1 then
1041 begin
1042 Result := True;
1043 Exit;
1044 end;
1045 end;
1046 if FDieSounds = nil then Exit;
1048 for a := 0 to High(FDieSounds) do
1049 if FDieSounds[a].Level = Level then
1050 begin
1051 SetLength(TempArray, Length(TempArray)+1);
1052 TempArray[High(TempArray)] := FDieSounds[a].ID;
1053 end;
1054 if (TempArray = nil) and (Level = 5) then
1055 begin
1056 g_Sound_PlayExAt('SOUND_MONSTER_SLOP', X, Y);
1057 Result := True;
1058 Exit;
1059 end;
1060 end;
1062 if TempArray = nil then Exit;
1064 g_Sound_PlayAt(TempArray[Random(Length(TempArray))], X, Y);
1066 Result := True;
1067 end;
1069 procedure TPlayerModel.SetColor(Red, Green, Blue: Byte);
1070 begin
1071 FColor.R := Red;
1072 FColor.G := Green;
1073 FColor.B := Blue;
1074 end;
1076 procedure TPlayerModel.SetFire(Fire: Boolean);
1077 begin
1078 FFire := Fire;
1080 if FFire then FFireCounter := FAnim[TDirection.D_RIGHT, A_ATTACK].Speed*FAnim[TDirection.D_RIGHT, A_ATTACK].TotalFrames
1081 else FFireCounter := 0;
1082 end;
1084 procedure TPlayerModel.SetFlag(Flag: Byte);
1085 var
1086 id: DWORD;
1087 begin
1088 FFlag := Flag;
1090 FFlagAnim.Free();
1091 FFlagAnim := nil;
1093 case Flag of
1094 FLAG_RED: g_Frames_Get(id, 'FRAMES_FLAG_RED');
1095 FLAG_BLUE: g_Frames_Get(id, 'FRAMES_FLAG_BLUE');
1096 else Exit;
1097 end;
1099 FFlagAnim := TAnimation.Create(id, True, 8);
1100 end;
1102 procedure TPlayerModel.SetWeapon(Weapon: Byte);
1103 begin
1104 FCurrentWeapon := Weapon;
1105 end;
1107 procedure TPlayerModel.Update();
1108 begin
1109 if (FDirection = TDirection.D_LEFT) and (FAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
1110 FAnim[TDirection.D_LEFT][FCurrentAnimation].Update else FAnim[TDirection.D_RIGHT][FCurrentAnimation].Update;
1112 if (FDirection = TDirection.D_LEFT) and (FMaskAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
1113 FMaskAnim[TDirection.D_LEFT][FCurrentAnimation].Update else FMaskAnim[TDirection.D_RIGHT][FCurrentAnimation].Update;
1115 if FFlagAnim <> nil then FFlagAnim.Update;
1117 if FFireCounter > 0 then Dec(FFireCounter) else FFire := False;
1118 end;
1120 end.