1 (* Copyright (C) 2016 - The Doom2D.org team & involved community members <http://www.doom2d.org>.
2 * This file is part of Doom2D Forever.
4 * This program is free software: you can redistribute it and/or modify it under the terms of
5 * the GNU General Public License as published by the Free Software Foundation, version 3 of
8 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
9 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10 * See the GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License along with this program.
13 * If not, see <http://www.gnu.org/licenses/>.
16 {$INCLUDE ../shared/a_modes.inc}
23 {$IFDEF USE_MEMPOOL}mempool
,{$ENDIF}
27 MAPDEF
, g_textures
, g_basic
, g_weapons
, e_graphics
, utils
, g_gfx
,
28 ImagingTypes
, Imaging
, ImagingUtility
;
46 A_WALKATTACKDOWN
= 14;
50 A_MELEEWALKATTACK
= 18;
54 A_MELEEATTACKDOWN
= 22;
57 A_LASTEXT
= A_MELEEATTACKDOWN
;
90 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
;
98 TPlayerModel
= class{$IFDEF USE_MEMPOOL}(TPoolObject
){$ENDIF}
101 FDirection
: TDirection
;
104 FCurrentAnimation
: Byte;
105 FAnim
: Array [TDirection
.D_LEFT
..TDirection
.D_RIGHT
] of Array [A_STAND
..A_LAST
] of TAnimation
;
106 FMaskAnim
: Array [TDirection
.D_LEFT
..TDirection
.D_RIGHT
] of Array [A_STAND
..A_LAST
] of TAnimation
;
107 FWeaponPoints
: TWeaponPoints
;
108 {$IFDEF ENABLE_SOUND}
109 FPainSounds
: TModelSoundArray
;
110 FDieSounds
: TModelSoundArray
;
113 FCurrentWeapon
: Byte;
114 FDrawWeapon
: Boolean;
116 FFlagPoint
: TDFPoint
;
117 FFlagAngle
: SmallInt
;
118 FFlagAnim
: TAnimation
;
123 destructor Destroy(); override;
124 procedure ChangeAnimation(Animation
: Byte; Force
: Boolean = False);
125 function GetCurrentAnimation
: TAnimation
;
126 function GetCurrentAnimationMask
: TAnimation
;
127 procedure SetColor(Red
, Green
, Blue
: Byte);
128 procedure SetWeapon(Weapon
: Byte);
129 procedure SetFlag(Flag
: Byte);
130 procedure SetFire(Fire
: Boolean);
131 procedure InvertDirection();
132 {$IFDEF ENABLE_SOUND}
133 function PlaySound(SoundType
, Level
: Byte; X
, Y
: Integer): Boolean;
136 procedure Draw(X
, Y
: Integer; Alpha
: Byte = 0);
139 property Fire
: Boolean read FFire
;
140 property Direction
: TDirection read FDirection write FDirection
;
141 property Animation
: Byte read FCurrentAnimation
;
142 property Weapon
: Byte read FCurrentWeapon
;
143 property Name
: String read FName
;
146 property Color
: TRGB read FColor write FColor
;
147 property Blood
: TModelBlood read FBlood
;
150 procedure g_PlayerModel_LoadData();
151 procedure g_PlayerModel_FreeData();
152 function g_PlayerModel_Load(FileName
: String): Boolean;
153 function g_PlayerModel_GetNames(): SSArray
;
154 function g_PlayerModel_GetInfo(ModelName
: String): TModelInfo
;
155 function g_PlayerModel_GetBlood(ModelName
: String): TModelBlood
;
156 function g_PlayerModel_Get(ModelName
: String): TPlayerModel
;
157 function g_PlayerModel_GetAnim(ModelName
: String; Anim
: Byte; var _Anim
, _Mask
: TAnimation
): Boolean;
158 function g_PlayerModel_GetGibs(ModelName
: String; var Gibs
: TGibsArray
): Boolean;
159 function g_PlayerModel_MakeColor(const aColor
: TRGB
; aTeam
: Byte): TRGB
;
165 {$INCLUDE ../nogl/noGLuses.inc}
166 {$IFDEF ENABLE_SOUND}
169 g_main
, g_console
, SysUtils
, g_player
, CONFIG
,
170 g_options
, g_map
, Math
, e_log
, wadreader
;
173 TPlayerModelInfo
= record
175 ModelSpeed
: Array [A_STAND
..A_PAIN
] of Byte;
178 WeaponPoints
: TWeaponPoints
;
180 {$IFDEF ENABLE_SOUND}
181 PainSounds
: TModelSoundArray
;
182 DieSounds
: TModelSoundArray
;
196 FLAG_BASEPOINT
: TDFPoint
= (X
:16; Y
:43);
197 FLAG_DEFPOINT
: TDFPoint
= (X
:32; Y
:16);
199 WEAPONBASE
: Array [WP_FIRST
+ 1..WP_LAST
] of TDFPoint
=
200 ((X
:8; Y
:4), (X
:8; Y
:8), (X
:16; Y
:16), (X
:16; Y
:24),
201 (X
:16; Y
:16), (X
:24; Y
:24), (X
:16; Y
:16), (X
:24; Y
:24),
202 (X
:16; Y
:16), (X
:8; Y
:8));
204 AnimNames
: Array [A_STAND
..A_LASTEXT
] of String =
205 ('StandAnim','WalkAnim','Die1Anim','Die2Anim','AttackAnim',
206 'SeeUpAnim','SeeDownAnim','AttackUpAnim','AttackDownAnim','PainAnim',
208 'WalkAttackAnim', 'WalkSeeUpAnim', 'WalkSeeDownAnim',
209 'WalkAttackUpAnim', 'WalkAttackDownAnim', 'MeleeStandAnim', 'MeleeWalkAnim',
210 'MeleeAttackAnim', 'MeleeWalkAttackAnim', 'MeleeSeeUpAnim', 'MeleeSeeDownAnim',
211 'MeleeAttackUpAnim', 'MeleeAttackDownAnim');
212 WeapNames
: Array [WP_FIRST
+ 1..WP_LAST
] of String =
213 ('csaw', 'hgun', 'sg', 'ssg', 'mgun', 'rkt', 'plz', 'bfg', 'spl', 'flm');
216 WeaponID
: Array [WP_FIRST
+ 1..WP_LAST
] of
217 Array [W_POS_NORMAL
..W_POS_DOWN
] of
218 Array [W_ACT_NORMAL
..W_ACT_FIRE
] of DWORD
;
219 PlayerModelsArray
: Array of TPlayerModelInfo
;
221 procedure g_PlayerModel_LoadData();
225 for a
:= WP_FIRST
+ 1 to WP_LAST
do
227 g_Texture_CreateWAD(WeaponID
[a
][W_POS_NORMAL
][W_ACT_NORMAL
], GameWAD
+':WEAPONS\'+UpperCase(WeapNames
[a
]));
228 g_Texture_CreateWAD(WeaponID
[a
][W_POS_NORMAL
][W_ACT_FIRE
], GameWAD
+':WEAPONS\'+UpperCase(WeapNames
[a
])+'_FIRE');
229 g_Texture_CreateWAD(WeaponID
[a
][W_POS_UP
][W_ACT_NORMAL
], GameWAD
+':WEAPONS\'+UpperCase(WeapNames
[a
])+'_UP');
230 g_Texture_CreateWAD(WeaponID
[a
][W_POS_UP
][W_ACT_FIRE
], GameWAD
+':WEAPONS\'+UpperCase(WeapNames
[a
])+'_UP_FIRE');
231 g_Texture_CreateWAD(WeaponID
[a
][W_POS_DOWN
][W_ACT_NORMAL
], GameWAD
+':WEAPONS\'+UpperCase(WeapNames
[a
])+'_DN');
232 g_Texture_CreateWAD(WeaponID
[a
][W_POS_DOWN
][W_ACT_FIRE
], GameWAD
+':WEAPONS\'+UpperCase(WeapNames
[a
])+'_DN_FIRE');
236 function GetPoint(var str
: String; var point
: TDFPoint
): Boolean;
246 if Length(str
) < 3 then
249 for a
:= 1 to Length(str
) do
250 if (str
[a
] = ',') or (a
= Length(str
)) then
252 s
:= Copy(str
, 1, a
);
253 if s
[Length(s
)] = ',' then
254 SetLength(s
, Length(s
)-1);
257 if (Sscanf(s
, '%d:%d', [@x
, @y
]) < 2) or
258 (x
< -64) or (x
> 128) or
259 (y
< -64) or (y
> 128) then
271 function GetWeapPoints(str
: String; weapon
: Byte; anim
: Byte; dir
: TDirection
;
272 frames
: Word; backanim
: Boolean; var wpoints
: TWeaponPoints
): Boolean;
281 backanim
:= backanim
and (frames
> 2);
283 for a
:= 1 to frames
do
285 if not GetPoint(str
, wpoints
[weapon
, anim
, dir
, a
-1]) then
288 with wpoints
[weapon
, anim
, dir
, a
-1] do
290 X
:= X
- WEAPONBASE
[weapon
].X
;
291 Y
:= Y
- WEAPONBASE
[weapon
].Y
;
292 if dir
= TDirection
.D_LEFT
then
297 h
:= High(wpoints
[weapon
, anim
, dir
]);
299 for b
:= h
downto frames
do
300 wpoints
[weapon
, anim
, dir
, b
] := wpoints
[weapon
, anim
, dir
, h
-b
+1];
305 procedure ExtAnimFromBaseAnim(MName
: String; AIdx
: Integer);
307 CopyAnim
: array [A_LASTBASE
+1..A_LASTEXT
] of Integer = (
308 A_WALK
, A_WALK
, A_WALK
, A_WALK
, A_WALK
,
309 A_STAND
, A_WALK
, A_ATTACK
, A_WALK
, A_SEEUP
, A_SEEDOWN
,
310 A_ATTACKUP
, A_ATTACKDOWN
315 AName
, OName
: String;
317 // HACK: shitty workaround to duplicate base animations
318 // in place of extended, replace with something better later
320 Assert((AIdx
> A_LASTBASE
) and (AIdx
<= A_LASTEXT
));
321 OIdx
:= CopyAnim
[AIdx
];
323 AName
:= MName
+ '_RIGHTANIM' + IntToStr(AIdx
);
324 OName
:= MName
+ '_RIGHTANIM' + IntToStr(OIdx
);
325 Assert(g_Frames_Dup(AName
, OName
));
326 Assert(g_Frames_Dup(AName
+ '_MASK', OName
+ '_MASK'));
327 AName
:= MName
+ '_LEFTANIM' + IntToStr(AIdx
);
328 OName
:= MName
+ '_LEFTANIM' + IntToStr(OIdx
);
329 if g_Frames_Exists(AName
) then
331 g_Frames_Dup(AName
, OName
);
332 g_Frames_Dup(AName
+ '_MASK', OName
+ '_MASK');
335 with PlayerModelsArray
[High(PlayerModelsArray
)] do
337 for W
:= WP_FIRST
+ 1 to WP_LAST
do
339 for D
:= TDirection
.D_LEFT
to TDirection
.D_RIGHT
do
341 SetLength(WeaponPoints
[W
, AIdx
, D
], Length(WeaponPoints
[W
, OIdx
, D
]));
342 for I
:= 0 to High(WeaponPoints
[W
, AIdx
, D
]) do
343 WeaponPoints
[W
, AIdx
, D
, I
] := WeaponPoints
[W
, OIdx
, D
, I
]
349 function g_PlayerModel_CalcGibSize (pData
: Pointer; dataSize
, x
, y
, w
, h
: Integer): TRectWH
;
350 var i
, j
: Integer; done
: Boolean; img
: TImageData
;
352 function IsVoid (i
, j
: Integer): Boolean;
354 result
:= Byte((PByte(img
.bits
) + (y
+j
)*img
.width
*4 + (x
+i
)*4 + 3)^) = 0
359 assert(LoadImageFromMemory(pData
, dataSize
, img
));
361 (* trace x from right to left *)
362 done
:= false; i
:= 0;
363 while not done
and (i
< w
) do
366 while (j
< h
) and IsVoid(i
, j
) do inc(j
);
367 done
:= (j
< h
) and (IsVoid(i
, j
) = false);
372 (* trace y from up to down *)
373 done
:= false; j
:= 0;
374 while not done
and (j
< h
) do
377 while (i
< w
) and IsVoid(i
, j
) do inc(i
);
378 done
:= (i
< w
) and (IsVoid(i
, j
) = false);
383 (* trace x from right to left *)
384 done
:= false; i
:= w
- 1;
385 while not done
and (i
>= 0) do
388 while (j
< h
) and IsVoid(i
, j
) do inc(j
);
389 done
:= (j
< h
) and (IsVoid(i
, j
) = false);
390 result
.width
:= i
- result
.x
+ 1;
394 (* trace y from down to up *)
395 done
:= false; j
:= h
- 1;
396 while not done
and (j
>= 0) do
399 while (i
< w
) and IsVoid(i
, j
) do inc(i
);
400 done
:= (i
< w
) and (IsVoid(i
, j
) = false);
401 result
.height
:= j
- result
.y
+ 1;
408 function g_PlayerModel_Load(FileName
: string): Boolean;
411 a
, b
, len
, lenpd
, lenpd2
, aa
, bb
, f
: Integer;
414 pData
, pData2
: Pointer;
420 e_WriteLog(Format('Loading player model "%s"...', [FileName
]), TMsgType
.Notify
);
424 WAD
:= TWADFile
.Create
;
425 WAD
.ReadFile(FileName
);
427 if {WAD.GetLastError <> DFWAD_NOERROR} not WAD
.isOpen
then
433 if not WAD
.GetResource('TEXT/MODEL', pData
, len
) then
439 config
:= TConfig
.CreateMem(pData
, len
);
442 s
:= config
.ReadStr('Model', 'name', '');
450 SetLength(PlayerModelsArray
, Length(PlayerModelsArray
)+1);
451 ID
:= High(PlayerModelsArray
);
453 prefix
:= FileName
+':TEXTURES\';
455 with PlayerModelsArray
[ID
].Info
do
458 Author
:= config
.ReadStr('Model', 'author', '');
459 Description
:= config
.ReadStr('Model', 'description', '');
462 with PlayerModelsArray
[ID
] do
464 Blood
.R
:= MAX(0, MIN(255, config
.ReadInt('Blood', 'R', 150)));
465 Blood
.G
:= MAX(0, MIN(255, config
.ReadInt('Blood', 'G', 0)));
466 Blood
.B
:= MAX(0, MIN(255, config
.ReadInt('Blood', 'B', 0)));
467 case config
.ReadStr('Blood', 'Kind', 'NORMAL') of
468 'NORMAL': Blood
.Kind
:= BLOOD_NORMAL
;
469 'SPARKS': Blood
.Kind
:= BLOOD_CSPARKS
;
470 'COMBINE': Blood
.Kind
:= BLOOD_COMBINE
;
472 Blood
.Kind
:= BLOOD_NORMAL
476 for b
:= A_STAND
to A_LAST
do
478 aname
:= s
+'_RIGHTANIM'+IntToStr(b
);
479 //e_LogWritefln('### MODEL FILE: [%s]', [prefix+config.ReadStr(AnimNames[b], 'resource', '')]);
480 if not (g_Frames_CreateWAD(nil, aname
,
481 prefix
+config
.ReadStr(AnimNames
[b
], 'resource', ''),
482 64, 64, config
.ReadInt(AnimNames
[b
], 'frames', 1),
483 config
.ReadBool(AnimNames
[b
], 'backanim', False)) and
484 g_Frames_CreateWAD(nil, aname
+'_MASK',
485 prefix
+config
.ReadStr(AnimNames
[b
], 'mask', ''),
486 64, 64, config
.ReadInt(AnimNames
[b
], 'frames', 1),
487 config
.ReadBool(AnimNames
[b
], 'backanim', False))) then
489 if b
<= A_LASTBASE
then
497 ExtAnimFromBaseAnim(s
, b
);
502 for aa
:= WP_FIRST
+ 1 to WP_LAST
do
503 for bb
:= A_STAND
to A_LAST
do
504 for cc
:= TDirection
.D_LEFT
to TDirection
.D_RIGHT
do
506 f
:= config
.ReadInt(AnimNames
[bb
], 'frames', 1);
507 if config
.ReadBool(AnimNames
[bb
], 'backanim', False) then
508 if f
> 2 then f
:= 2*f
-2;
509 SetLength(PlayerModelsArray
[ID
].WeaponPoints
[aa
, bb
, cc
], f
);
512 if (config
.ReadStr(AnimNames
[b
], 'resource2', '') <> '') and
513 (config
.ReadStr(AnimNames
[b
], 'mask2', '') <> '') then
515 aname
:= s
+'_LEFTANIM'+IntToStr(b
);
516 g_Frames_CreateWAD(nil, aname
,
517 prefix
+config
.ReadStr(AnimNames
[b
], 'resource2', ''),
518 64, 64, config
.ReadInt(AnimNames
[b
], 'frames', 1),
519 config
.ReadBool(AnimNames
[b
], 'backanim', False));
521 g_Frames_CreateWAD(nil, aname
+'_MASK',
522 prefix
+config
.ReadStr(AnimNames
[b
], 'mask2', ''),
523 64, 64, config
.ReadInt(AnimNames
[b
], 'frames', 1),
524 config
.ReadBool(AnimNames
[b
], 'backanim', False));
527 PlayerModelsArray
[ID
].ModelSpeed
[b
] := Max(1, config
.ReadInt(AnimNames
[b
], 'waitcount', 1) div 3);
530 with PlayerModelsArray
[ID
], config
do
532 {$IFDEF ENABLE_SOUND}
533 prefix
:= FileName
+':SOUNDS\';
537 s
:= config
.ReadStr('Sound', 'pain'+IntToStr(a
), '');
540 SetLength(PainSounds
, Length(PainSounds
)+1);
541 g_Sound_CreateWAD(PainSounds
[High(PainSounds
)].ID
, prefix
+s
);
542 PainSounds
[High(PainSounds
)].Level
:= config
.ReadInt('Sound', 'painlevel'+IntToStr(a
), 1);
549 s
:= config
.ReadStr('Sound', 'die'+IntToStr(a
), '');
552 SetLength(DieSounds
, Length(DieSounds
)+1);
553 g_Sound_CreateWAD(DieSounds
[High(DieSounds
)].ID
, prefix
+s
);
554 DieSounds
[High(DieSounds
)].Level
:= config
.ReadInt('Sound', 'dielevel'+IntToStr(a
), 1);
559 SlopSound
:= Min(Max(config
.ReadInt('Sound', 'slop', 0), 0), 2);
562 SetLength(Gibs
, ReadInt('Gibs', 'count', 0));
565 (WAD
.GetResource('TEXTURES/'+config
.ReadStr('Gibs', 'resource', 'GIBS'), pData
, lenpd
)) and
566 (WAD
.GetResource('TEXTURES/'+config
.ReadStr('Gibs', 'mask', 'GIBSMASK'), pData2
, lenpd2
)) then
568 for a
:= 0 to High(Gibs
) do
569 if e_CreateTextureMemEx(pData
, lenpd
, Gibs
[a
].ID
, a
*32, 0, 32, 32) and
570 e_CreateTextureMemEx(pData2
, lenpd2
, Gibs
[a
].MaskID
, a
*32, 0, 32, 32) then
572 //Gibs[a].Rect := e_GetTextureSize2(Gibs[a].ID);
573 Gibs
[a
].Rect
:= g_PlayerModel_CalcGibSize(pData
, lenpd
, a
*32, 0, 32, 32);
575 if Height
> 3 then Height
:= Height
-1-Random(2);
576 Gibs
[a
].OnlyOne
:= config
.ReadInt('Gibs', 'once', -1) = a
+1;
584 for aa
:= WP_FIRST
+ 1 to WP_LAST
do
585 for bb
:= A_STAND
to A_LAST
do
586 if not (bb
in [A_DIE1
, A_DIE2
, A_PAIN
]) then
588 chk
:= GetWeapPoints(config
.ReadStr(AnimNames
[bb
], WeapNames
[aa
]+'_points', ''), aa
, bb
, TDirection
.D_RIGHT
,
589 config
.ReadInt(AnimNames
[bb
], 'frames', 0),
590 config
.ReadBool(AnimNames
[bb
], 'backanim', False),
592 if ok
and (not chk
) and (aa
= WEAPON_FLAMETHROWER
) then
594 // workaround for flamethrower
595 chk
:= GetWeapPoints(config
.ReadStr(AnimNames
[bb
], WeapNames
[WEAPON_PLASMA
]+'_points', ''), aa
, bb
, TDirection
.D_RIGHT
,
596 config
.ReadInt(AnimNames
[bb
], 'frames', 0),
597 config
.ReadBool(AnimNames
[bb
], 'backanim', False),
600 for f
:= 0 to High(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
]) do
605 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].X
, 6);
606 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].Y
, 8);
608 A_WALKATTACK
, A_WALK
:
610 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].X
, 9);
611 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].Y
, 9);
615 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].X
, 5);
616 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].Y
, 8);
618 A_WALKSEEUP
, A_SEEUP
:
620 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].X
, 5);
621 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].Y
, 16);
623 A_WALKSEEDOWN
, A_SEEDOWN
:
625 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].X
, 6);
626 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].Y
, 5);
628 A_WALKATTACKUP
, A_ATTACKUP
:
630 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].X
, 5);
631 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].Y
, 16);
633 A_WALKATTACKDOWN
, A_ATTACKDOWN
:
635 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].X
, 6);
636 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].Y
, 4);
641 ok
:= ok
and (chk
or (bb
> A_LASTBASE
));
643 if not GetWeapPoints(config
.ReadStr(AnimNames
[bb
], WeapNames
[aa
]+'2_points', ''), aa
, bb
, TDirection
.D_LEFT
,
644 config
.ReadInt(AnimNames
[bb
], 'frames', 0),
645 config
.ReadBool(AnimNames
[bb
], 'backanim', False),
647 for f
:= 0 to High(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
]) do
649 WeaponPoints
[aa
, bb
, TDirection
.D_LEFT
, f
].X
:= -WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].X
;
650 WeaponPoints
[aa
, bb
, TDirection
.D_LEFT
, f
].Y
:= WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].Y
;
653 if not ok
then Break
;
655 {if ok then g_Console_Add(Info.Name+' weapon points ok')
656 else g_Console_Add(Info.Name+' weapon points fail');}
657 Info
.HaveWeapon
:= ok
;
659 s
:= config
.ReadStr('Model', 'flag_point', '');
660 if not GetPoint(s
, FlagPoint
) then FlagPoint
:= FLAG_DEFPOINT
;
662 FlagAngle
:= config
.ReadInt('Model', 'flag_angle', FLAG_DEFANGLE
);
671 function g_PlayerModel_Get(ModelName
: String): TPlayerModel
;
679 if PlayerModelsArray
= nil then Exit
;
681 for a
:= 0 to High(PlayerModelsArray
) do
682 if AnsiLowerCase(PlayerModelsArray
[a
].Info
.Name
) = AnsiLowerCase(ModelName
) then
684 Result
:= TPlayerModel
.Create
;
686 with PlayerModelsArray
[a
] do
688 Result
.FName
:= Info
.Name
;
689 Result
.FBlood
:= Blood
;
691 for b
:= A_STAND
to A_LAST
do
693 if not (g_Frames_Get(ID
, Info
.Name
+'_RIGHTANIM'+IntToStr(b
)) and
694 g_Frames_Get(ID2
, Info
.Name
+'_RIGHTANIM'+IntToStr(b
)+'_MASK')) then
700 Result
.FAnim
[TDirection
.D_RIGHT
][b
] := TAnimation
.Create(ID
, b
in [A_STAND
, A_WALK
], ModelSpeed
[b
]);
701 Result
.FMaskAnim
[TDirection
.D_RIGHT
][b
] := TAnimation
.Create(ID2
, b
in [A_STAND
, A_WALK
], ModelSpeed
[b
]);
703 if g_Frames_Exists(Info
.Name
+'_LEFTANIM'+IntToStr(b
)) and
704 g_Frames_Exists(Info
.Name
+'_LEFTANIM'+IntToStr(b
)+'_MASK') then
705 if g_Frames_Get(ID
, Info
.Name
+'_LEFTANIM'+IntToStr(b
)) and
706 g_Frames_Get(ID2
, Info
.Name
+'_LEFTANIM'+IntToStr(b
)+'_MASK') then
708 Result
.FAnim
[TDirection
.D_LEFT
][b
] := TAnimation
.Create(ID
, b
in [A_STAND
, A_WALK
], ModelSpeed
[b
]);
709 Result
.FMaskAnim
[TDirection
.D_LEFT
][b
] := TAnimation
.Create(ID2
, b
in [A_STAND
, A_WALK
], ModelSpeed
[b
]);
713 {$IFDEF ENABLE_SOUND}
714 Result
.FPainSounds
:= PainSounds
;
715 Result
.FDieSounds
:= DieSounds
;
716 Result
.FSlopSound
:= SlopSound
;
718 Result
.FDrawWeapon
:= Info
.HaveWeapon
;
719 Result
.FWeaponPoints
:= WeaponPoints
;
721 Result
.FFlagPoint
:= FlagPoint
;
722 Result
.FFlagAngle
:= FlagAngle
;
729 function g_PlayerModel_GetAnim(ModelName
: string; Anim
: Byte; var _Anim
, _Mask
: TAnimation
): Boolean;
739 for a
:= 0 to High(PlayerModelsArray
) do
740 if PlayerModelsArray
[a
].Info
.Name
= ModelName
then
741 with PlayerModelsArray
[a
] do
743 c
:= Anim
in [A_STAND
, A_WALK
];
745 if not g_Frames_Get(ID
, Info
.Name
+'_RIGHTANIM'+IntToStr(Anim
)) then
746 if not g_Frames_Get(ID
, Info
.Name
+'_LEFTANIM'+IntToStr(Anim
)) then
749 _Anim
:= TAnimation
.Create(ID
, c
, ModelSpeed
[Anim
]);
750 _Anim
.Speed
:= ModelSpeed
[Anim
];
752 if not g_Frames_Get(ID
, Info
.Name
+'_RIGHTANIM'+IntToStr(Anim
)+'_MASK') then
753 if not g_Frames_Get(ID
, Info
.Name
+'_LEFTANIM'+IntToStr(Anim
)+'_MASK') then
756 _Mask
:= TAnimation
.Create(ID
, c
, ModelSpeed
[Anim
]);
757 _Mask
.Speed
:= ModelSpeed
[Anim
];
764 function g_PlayerModel_GetGibs(ModelName
: string; var Gibs
: TGibsArray
): Boolean;
771 if PlayerModelsArray
= nil then Exit
;
772 if gGibsCount
= 0 then Exit
;
776 SetLength(Gibs
, gGibsCount
);
778 for a
:= 0 to High(PlayerModelsArray
) do
779 if PlayerModelsArray
[a
].Info
.Name
= ModelName
then
781 for i
:= 0 to High(Gibs
) do
783 if c
and (Length(PlayerModelsArray
[a
].Gibs
) = 1) then
790 b
:= Random(Length(PlayerModelsArray
[a
].Gibs
));
791 until not (PlayerModelsArray
[a
].Gibs
[b
].OnlyOne
and c
);
793 Gibs
[i
] := PlayerModelsArray
[a
].Gibs
[b
];
795 if Gibs
[i
].OnlyOne
then c
:= True;
803 function g_PlayerModel_GetNames(): SSArray
;
809 if PlayerModelsArray
= nil then Exit
;
811 for i
:= 0 to High(PlayerModelsArray
) do
813 SetLength(Result
, Length(Result
)+1);
814 Result
[High(Result
)] := PlayerModelsArray
[i
].Info
.Name
;
818 function g_PlayerModel_GetInfo(ModelName
: string): TModelInfo
;
822 Result
:= Default(TModelInfo
);
823 if PlayerModelsArray
= nil then Exit
;
825 for a
:= 0 to High(PlayerModelsArray
) do
826 if PlayerModelsArray
[a
].Info
.Name
= ModelName
then
828 Result
:= PlayerModelsArray
[a
].Info
;
833 function g_PlayerModel_GetBlood(ModelName
: string): TModelBlood
;
840 Result
.Kind
:= BLOOD_NORMAL
;
841 if PlayerModelsArray
= nil then Exit
;
843 for a
:= 0 to High(PlayerModelsArray
) do
844 if PlayerModelsArray
[a
].Info
.Name
= ModelName
then
846 Result
:= PlayerModelsArray
[a
].Blood
;
851 procedure g_PlayerModel_FreeData();
856 for a
:= WP_FIRST
+ 1 to WP_LAST
do
857 for b
:= W_POS_NORMAL
to W_POS_DOWN
do
858 for c
:= W_ACT_NORMAL
to W_ACT_FIRE
do
859 e_DeleteTexture(WeaponID
[a
][b
][c
]);
861 e_WriteLog('Releasing models...', TMsgType
.Notify
);
863 if PlayerModelsArray
= nil then Exit
;
865 for i
:= 0 to High(PlayerModelsArray
) do
866 with PlayerModelsArray
[i
] do
868 for a
:= A_STAND
to A_LAST
do
870 g_Frames_DeleteByName(Info
.Name
+'_LEFTANIM'+IntToStr(a
));
871 g_Frames_DeleteByName(Info
.Name
+'_LEFTANIM'+IntToStr(a
)+'_MASK');
872 g_Frames_DeleteByName(Info
.Name
+'_RIGHTANIM'+IntToStr(a
));
873 g_Frames_DeleteByName(Info
.Name
+'_RIGHTANIM'+IntToStr(a
)+'_MASK');
876 {$IFDEF ENABLE_SOUND}
877 if PainSounds
<> nil then
878 for b
:= 0 to High(PainSounds
) do
879 e_DeleteSound(PainSounds
[b
].ID
);
881 if DieSounds
<> nil then
882 for b
:= 0 to High(DieSounds
) do
883 e_DeleteSound(DieSounds
[b
].ID
);
887 for b
:= 0 to High(Gibs
) do
889 e_DeleteTexture(Gibs
[b
].ID
);
890 e_DeleteTexture(Gibs
[b
].MaskID
);
894 PlayerModelsArray
:= nil;
897 function g_PlayerModel_MakeColor(const aColor
: TRGB
; aTeam
: Byte): TRGB
;
899 TeamTone
, EnemyTone
, TintTone
: Byte;
903 TeamTone
:= Max(191, aColor
.R
); // 191..255
904 TintTone
:= aColor
.G
div 3; // 0..85
905 EnemyTone
:= nclamp(aColor
.B
- 160, 0, TintTone
); // 0..tint..95
906 Result
:= _RGB(TeamTone
, TintTone
, EnemyTone
);
910 TeamTone
:= Max(127, aColor
.B
); // 127..255
911 TintTone
:= aColor
.G
div 3; // 0..85
912 EnemyTone
:= nclamp(aColor
.R
- 192, 0, TintTone
); // 0..tint..63
913 Result
:= _RGB(EnemyTone
, TintTone
, TeamTone
);
923 procedure TPlayerModel
.ChangeAnimation(Animation
: Byte; Force
: Boolean = False);
925 if not Force
then if FCurrentAnimation
= Animation
then Exit
;
927 FCurrentAnimation
:= Animation
;
929 if (FDirection
= TDirection
.D_LEFT
) and
930 (FAnim
[TDirection
.D_LEFT
][FCurrentAnimation
] <> nil) and
931 (FMaskAnim
[TDirection
.D_LEFT
][FCurrentAnimation
] <> nil) then
933 FAnim
[TDirection
.D_LEFT
][FCurrentAnimation
].Reset
;
934 FMaskAnim
[TDirection
.D_LEFT
][FCurrentAnimation
].Reset
;
938 FAnim
[TDirection
.D_RIGHT
][FCurrentAnimation
].Reset
;
939 FMaskAnim
[TDirection
.D_RIGHT
][FCurrentAnimation
].Reset
;
943 destructor TPlayerModel
.Destroy();
947 for a
:= A_STAND
to A_LAST
do
949 FAnim
[TDirection
.D_LEFT
][a
].Free();
950 FMaskAnim
[TDirection
.D_LEFT
][a
].Free();
951 FAnim
[TDirection
.D_RIGHT
][a
].Free();
952 FMaskAnim
[TDirection
.D_RIGHT
][a
].Free();
958 procedure TPlayerModel
.Draw(X
, Y
: Integer; Alpha
: Byte = 0);
965 if Direction
= TDirection
.D_LEFT
then
966 Mirror
:= TMirrorType
.None
968 Mirror
:= TMirrorType
.Horizontal
;
970 if (FFlag
<> FLAG_NONE
) and (FFlagAnim
<> nil) and
971 (not (FCurrentAnimation
in [A_DIE1
, A_DIE2
])) then
973 p
.X
:= IfThen(Direction
= TDirection
.D_LEFT
,
975 64-FLAG_BASEPOINT
.X
);
976 p
.Y
:= FLAG_BASEPOINT
.Y
;
978 FFlagAnim
.DrawEx(X
+IfThen(Direction
= TDirection
.D_LEFT
, FFlagPoint
.X
-1, 2*FLAG_BASEPOINT
.X
-FFlagPoint
.X
+1)-FLAG_BASEPOINT
.X
,
979 Y
+FFlagPoint
.Y
-FLAG_BASEPOINT
.Y
+1, Mirror
, p
,
980 IfThen(FDirection
= TDirection
.D_RIGHT
, FFlagAngle
, -FFlagAngle
));
984 if Direction
= TDirection
.D_RIGHT
985 then Mirror
:= TMirrorType
.None
986 else Mirror
:= TMirrorType
.Horizontal
;
989 (not (FCurrentAnimation
in [A_DIE1
, A_DIE2
, A_PAIN
])) and
990 (FCurrentWeapon
in [WP_FIRST
+ 1..WP_LAST
]) then
992 if FCurrentAnimation
in [A_SEEUP
, A_ATTACKUP
] then
995 if FCurrentAnimation
in [A_SEEDOWN
, A_ATTACKDOWN
]
996 then pos
:= W_POS_DOWN
997 else pos
:= W_POS_NORMAL
;
999 if (FCurrentAnimation
in [A_ATTACK
, A_ATTACKUP
, A_ATTACKDOWN
]) or FFire
1000 then act
:= W_ACT_FIRE
1001 else act
:= W_ACT_NORMAL
;
1004 e_Draw(WeaponID
[FCurrentWeapon
][pos
][act
],
1005 X
+FWeaponPoints
[FCurrentWeapon
, FCurrentAnimation
, FDirection
,
1006 FAnim
[TDirection
.D_RIGHT
][FCurrentAnimation
].CurrentFrame
].X
,
1007 Y
+FWeaponPoints
[FCurrentWeapon
, FCurrentAnimation
, FDirection
,
1008 FAnim
[TDirection
.D_RIGHT
][FCurrentAnimation
].CurrentFrame
].Y
,
1009 0, True, False, Mirror
);
1013 if (FDirection
= TDirection
.D_LEFT
) and
1014 (FAnim
[TDirection
.D_LEFT
][FCurrentAnimation
] <> nil) then
1016 FAnim
[TDirection
.D_LEFT
][FCurrentAnimation
].Alpha
:= Alpha
;
1017 FAnim
[TDirection
.D_LEFT
][FCurrentAnimation
].Draw(X
, Y
, TMirrorType
.None
);
1021 FAnim
[TDirection
.D_RIGHT
][FCurrentAnimation
].Alpha
:= Alpha
;
1022 FAnim
[TDirection
.D_RIGHT
][FCurrentAnimation
].Draw(X
, Y
, Mirror
);
1028 if (FDirection
= TDirection
.D_LEFT
) and
1029 (FMaskAnim
[TDirection
.D_LEFT
][FCurrentAnimation
] <> nil) then
1031 FMaskAnim
[TDirection
.D_LEFT
][FCurrentAnimation
].Alpha
:= Alpha
;
1032 FMaskAnim
[TDirection
.D_LEFT
][FCurrentAnimation
].Draw(X
, Y
, TMirrorType
.None
);
1036 FMaskAnim
[TDirection
.D_RIGHT
][FCurrentAnimation
].Alpha
:= Alpha
;
1037 FMaskAnim
[TDirection
.D_RIGHT
][FCurrentAnimation
].Draw(X
, Y
, Mirror
);
1045 function TPlayerModel
.GetCurrentAnimation
: TAnimation
;
1047 if (FDirection
= TDirection
.D_LEFT
) and (FAnim
[TDirection
.D_LEFT
][FCurrentAnimation
] <> nil) then
1048 Result
:= FAnim
[TDirection
.D_LEFT
][FCurrentAnimation
]
1050 Result
:= FAnim
[TDirection
.D_RIGHT
][FCurrentAnimation
];
1053 function TPlayerModel
.GetCurrentAnimationMask
: TAnimation
;
1055 if (FDirection
= TDirection
.D_LEFT
) and (FMaskAnim
[TDirection
.D_LEFT
][FCurrentAnimation
] <> nil) then
1056 Result
:= FMaskAnim
[TDirection
.D_LEFT
][FCurrentAnimation
]
1058 Result
:= FMaskAnim
[TDirection
.D_RIGHT
][FCurrentAnimation
];
1061 {$IFDEF ENABLE_SOUND}
1062 function TPlayerModel
.PlaySound(SoundType
, Level
: Byte; X
, Y
: Integer): Boolean;
1064 TempArray
: array of DWORD
;
1068 SetLength(TempArray
, 0);
1070 if SoundType
= MODELSOUND_PAIN
then
1072 if FPainSounds
= nil then Exit
;
1074 for a
:= 0 to High(FPainSounds
) do
1075 if FPainSounds
[a
].Level
= Level
then
1077 SetLength(TempArray
, Length(TempArray
)+1);
1078 TempArray
[High(TempArray
)] := FPainSounds
[a
].ID
;
1083 if (Level
in [2, 3, 5]) and (FSlopSound
> 0) then
1085 g_Sound_PlayExAt('SOUND_MONSTER_SLOP', X
, Y
);
1086 if FSlopSound
= 1 then
1092 if FDieSounds
= nil then Exit
;
1094 for a
:= 0 to High(FDieSounds
) do
1095 if FDieSounds
[a
].Level
= Level
then
1097 SetLength(TempArray
, Length(TempArray
)+1);
1098 TempArray
[High(TempArray
)] := FDieSounds
[a
].ID
;
1100 if (TempArray
= nil) and (Level
= 5) then
1102 g_Sound_PlayExAt('SOUND_MONSTER_SLOP', X
, Y
);
1108 if TempArray
= nil then Exit
;
1110 g_Sound_PlayAt(TempArray
[Random(Length(TempArray
))], X
, Y
);
1116 procedure TPlayerModel
.SetColor(Red
, Green
, Blue
: Byte);
1123 procedure TPlayerModel
.SetFire(Fire
: Boolean);
1127 if FFire
then FFireCounter
:= FAnim
[TDirection
.D_RIGHT
, A_ATTACK
].Speed
*FAnim
[TDirection
.D_RIGHT
, A_ATTACK
].TotalFrames
1128 else FFireCounter
:= 0;
1131 procedure TPlayerModel
.SetFlag(Flag
: Byte);
1141 FLAG_RED
: g_Frames_Get(id
, 'FRAMES_FLAG_RED');
1142 FLAG_BLUE
: g_Frames_Get(id
, 'FRAMES_FLAG_BLUE');
1146 FFlagAnim
:= TAnimation
.Create(id
, True, 8);
1149 procedure TPlayerModel
.SetWeapon(Weapon
: Byte);
1151 FCurrentWeapon
:= Weapon
;
1154 procedure TPlayerModel
.InvertDirection();
1156 if Direction
= TDirection
.D_LEFT
1157 then Direction
:= TDirection
.D_RIGHT
1158 else Direction
:= TDirection
.D_LEFT
;
1161 procedure TPlayerModel
.Update();
1163 if (FDirection
= TDirection
.D_LEFT
) and (FAnim
[TDirection
.D_LEFT
][FCurrentAnimation
] <> nil) then
1164 FAnim
[TDirection
.D_LEFT
][FCurrentAnimation
].Update
else FAnim
[TDirection
.D_RIGHT
][FCurrentAnimation
].Update
;
1166 if (FDirection
= TDirection
.D_LEFT
) and (FMaskAnim
[TDirection
.D_LEFT
][FCurrentAnimation
] <> nil) then
1167 FMaskAnim
[TDirection
.D_LEFT
][FCurrentAnimation
].Update
else FMaskAnim
[TDirection
.D_RIGHT
][FCurrentAnimation
].Update
;
1169 if FFlagAnim
<> nil then FFlagAnim
.Update
;
1171 if FFireCounter
> 0 then Dec(FFireCounter
) else FFire
:= False;