1 (* Copyright (C) Doom 2D: Forever Developers
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, version 3 of the License ONLY.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 {$INCLUDE ../shared/a_modes.inc}
21 uses MAPDEF
, g_textures
, g_base
, g_basic
, g_weapons
, utils
;
39 A_WALKATTACKDOWN
= 14;
43 A_FISTWALKATTACK
= 18;
47 A_FISTATTACKDOWN
= 22;
50 A_LASTEXT
= A_FISTATTACKDOWN
;
59 W_POS_LAST
= W_POS_DOWN
;
63 W_ACT_LAST
= W_ACT_FIRE
;
65 FLAG_BASEPOINT
: TDFPoint
= (X
:16; Y
:43);
68 TWeaponPoints
= Array [WP_FIRST
+ 1..WP_LAST
, A_STAND
..A_LAST
, TDirection
.D_LEFT
..TDirection
.D_RIGHT
] of Array of TDFPoint
;
70 TModelMatrix
= Array [TDirection
.D_LEFT
..TDirection
.D_RIGHT
, A_STAND
..A_LAST
] of TAnimState
;
72 TModelTextures
= Array [TDirection
.D_LEFT
..TDirection
.D_RIGHT
, A_STAND
..A_LAST
] of record
90 TModelSoundArray
= Array of TModelSound
;
92 TPlayerModel
= class{$IFDEF USE_MEMPOOL}(TPoolObject
){$ENDIF}
94 FDirection
: TDirection
;
96 FCurrentAnimation
: Byte;
97 FAnimState
: TAnimState
;
104 destructor Destroy(); override;
105 procedure ChangeAnimation(Animation
: Byte; Force
: Boolean = False);
106 procedure SetColor(Red
, Green
, Blue
: Byte);
107 procedure SetWeapon(Weapon
: Byte);
108 procedure SetFlag(Flag
: Byte);
109 procedure SetFire (Fire
: Boolean);
110 function GetFire (): Boolean;
111 function PlaySound(SoundType
, Level
: Byte; X
, Y
: Integer): Boolean;
115 function GetBlood (): TModelBlood
;
118 function GetName (): String;
121 property Direction
: TDirection read FDirection write FDirection
;
122 property Animation
: Byte read FCurrentAnimation
;
123 property Weapon
: Byte read FCurrentWeapon
;
126 property Color
: TRGB read FColor write FColor
;
127 property AnimState
: TAnimState read FAnimState
;
128 property CurrentAnimation
: Byte read FCurrentAnimation
;
129 property CurrentWeapon
: Byte read FCurrentWeapon
;
130 property Flag
: Byte read FFlag
;
131 property ID
: Integer read FID
;
134 procedure g_PlayerModel_LoadAll
;
135 procedure g_PlayerModel_FreeData();
136 function g_PlayerModel_Load(FileName
: String): Boolean;
137 function g_PlayerModel_GetNames(): SSArray
;
138 function g_PlayerModel_Get(ModelName
: String): TPlayerModel
;
139 function g_PlayerModel_GetIndex (ModelName
: String): Integer;
142 function g_PlayerModel_GetBlood(ModelName
: String): TModelBlood
;
145 procedure g_PlayerModel_LoadFake (ModelName
, FileName
: String);
147 (* --- private data --- *)
150 TPlayerModelInfo
= record
155 ModelSpeed
: Array [A_STAND
..A_PAIN
] of Byte;
158 WeaponPoints
: TWeaponPoints
;
159 PainSounds
: TModelSoundArray
;
160 DieSounds
: TModelSoundArray
;
165 // =======================
167 Anim
: TModelTextures
;
177 PlayerModelsArray
: Array of TPlayerModelInfo
;
185 g_sound
, g_console
, SysUtils
, g_player
, CONFIG
,
186 e_sound
, g_options
, g_map
, Math
, e_log
, wadreader
190 FLAG_DEFPOINT
: TDFPoint
= (X
:32; Y
:16);
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',
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 function g_PlayerModel_GetIndex (ModelName
: String): Integer;
212 if PlayerModelsArray
<> nil then
215 while (i
< Length(PlayerModelsArray
)) and (PlayerModelsArray
[i
].Name
<> ModelName
) do
217 if i
< Length(PlayerModelsArray
) then
222 function GetPoint(var str
: String; var point
: TDFPoint
): Boolean;
232 if Length(str
) < 3 then
235 for a
:= 1 to Length(str
) do
236 if (str
[a
] = ',') or (a
= Length(str
)) then
238 s
:= Copy(str
, 1, a
);
239 if s
[Length(s
)] = ',' then
240 SetLength(s
, Length(s
)-1);
243 if (Sscanf(s
, '%d:%d', [@x
, @y
]) < 2) or
244 (x
< -64) or (x
> 128) or
245 (y
< -64) or (y
> 128) then
257 function GetWeapPoints(str
: String; weapon
: Byte; anim
: Byte; dir
: TDirection
;
258 frames
: Word; backanim
: Boolean; var wpoints
: TWeaponPoints
): Boolean;
267 backanim
:= backanim
and (frames
> 2);
269 for a
:= 1 to frames
do
271 if not GetPoint(str
, wpoints
[weapon
, anim
, dir
, a
-1]) then
274 with wpoints
[weapon
, anim
, dir
, a
-1] do
276 X
:= X
- WEAPONBASE
[weapon
].X
;
277 Y
:= Y
- WEAPONBASE
[weapon
].Y
;
278 if dir
= TDirection
.D_LEFT
then
283 h
:= High(wpoints
[weapon
, anim
, dir
]);
285 for b
:= h
downto frames
do
286 wpoints
[weapon
, anim
, dir
, b
] := wpoints
[weapon
, anim
, dir
, h
-b
+1];
291 procedure g_PlayerMode_ExtendPoints (id
: Integer; AIdx
: Integer);
293 CopyAnim
: array [A_LASTBASE
+1..A_LASTEXT
] of Integer = (
294 A_WALK
, A_WALK
, A_WALK
, A_WALK
, A_WALK
,
295 A_STAND
, A_WALK
, A_ATTACK
, A_WALK
, A_SEEUP
, A_SEEDOWN
,
296 A_ATTACKUP
, A_ATTACKDOWN
298 var W
, I
, OIdx
: Integer; D
: TDirection
;
300 OIdx
:= CopyAnim
[AIdx
];
301 with PlayerModelsArray
[id
] do
303 for W
:= WP_FIRST
+ 1 to WP_LAST
do
305 for D
:= TDirection
.D_LEFT
to TDirection
.D_RIGHT
do
307 SetLength(WeaponPoints
[W
, AIdx
, D
], Length(WeaponPoints
[W
, OIdx
, D
]));
308 for I
:= 0 to High(WeaponPoints
[W
, AIdx
, D
]) do
309 WeaponPoints
[W
, AIdx
, D
, I
] := WeaponPoints
[W
, OIdx
, D
, I
]
315 procedure g_PlayerModel_LoadFake (ModelName
, FileName
: String);
318 SetLength(PlayerModelsArray
, Length(PlayerModelsArray
) + 1);
319 id
:= High(PlayerModelsArray
);
320 PlayerModelsArray
[id
].Name
:= ModelName
;
321 PlayerModelsArray
[id
].HaveWeapon
:= False;
322 PlayerModelsArray
[id
].FileName
:= FileName
;
325 function g_PlayerModel_Load(FileName
: string): Boolean;
328 a
, b
, len
, aa
, bb
, f
: Integer;
335 ok
, chk
, chk2
: Boolean;
337 e_WriteLog(Format('Loading player model "%s"...', [FileName
]), TMsgType
.Notify
);
341 WAD
:= TWADFile
.Create
;
342 WAD
.ReadFile(FileName
);
344 if {WAD.GetLastError <> DFWAD_NOERROR} not WAD
.isOpen
then
350 if not WAD
.GetResource('TEXT/MODEL', pData
, len
) then
356 config
:= TConfig
.CreateMem(pData
, len
);
359 s
:= config
.ReadStr('Model', 'name', '');
367 SetLength(PlayerModelsArray
, Length(PlayerModelsArray
)+1);
368 ID
:= High(PlayerModelsArray
);
370 prefix
:= FileName
+':TEXTURES\';
372 PlayerModelsArray
[ID
].Name
:= s
;
373 PlayerModelsArray
[ID
].Author
:= config
.ReadStr('Model', 'author', '');
374 PlayerModelsArray
[ID
].Description
:= config
.ReadStr('Model', 'description', '');
375 PlayerModelsArray
[ID
].FileName
:= FileName
;
378 with PlayerModelsArray
[ID
] do
380 Blood
.R
:= MAX(0, MIN(255, config
.ReadInt('Blood', 'R', 150)));
381 Blood
.G
:= MAX(0, MIN(255, config
.ReadInt('Blood', 'G', 0)));
382 Blood
.B
:= MAX(0, MIN(255, config
.ReadInt('Blood', 'B', 0)));
383 case config
.ReadStr('Blood', 'Kind', 'NORMAL') of
384 'NORMAL': Blood
.Kind
:= BLOOD_NORMAL
;
385 'SPARKS': Blood
.Kind
:= BLOOD_CSPARKS
;
386 'COMBINE': Blood
.Kind
:= BLOOD_COMBINE
;
388 Blood
.Kind
:= BLOOD_NORMAL
393 for b
:= A_STAND
to A_LAST
do
395 with PlayerModelsArray
[ID
].Anim
[TDirection
.D_RIGHT
, b
] do
397 Resource
:= config
.ReadStr(AnimNames
[b
], 'resource', '');
398 Mask
:= config
.ReadStr(AnimNames
[b
], 'mask', '');
399 Frames
:= config
.ReadInt(AnimNames
[b
], 'frames', 1);
400 Back
:= config
.ReadBool(AnimNames
[b
], 'backanim', False);
401 if (Resource
= '') or (Mask
= '') then
403 if b
<= A_LASTBASE
then
411 g_PlayerMode_ExtendPoints(ID
, b
);
417 for aa
:= WP_FIRST
+ 1 to WP_LAST
do
418 for bb
:= A_STAND
to A_LAST
do
419 for cc
:= TDirection
.D_LEFT
to TDirection
.D_RIGHT
do
421 f
:= PlayerModelsArray
[ID
].Anim
[cc
, bb
].Frames
;
422 if PlayerModelsArray
[ID
].Anim
[cc
, bb
].Back
and (f
> 2) then
424 SetLength(PlayerModelsArray
[ID
].WeaponPoints
[aa
, bb
, cc
], f
);
427 with PlayerModelsArray
[ID
].Anim
[TDirection
.D_LEFT
, b
] do
429 Frames
:= PlayerModelsArray
[ID
].Anim
[TDirection
.D_RIGHT
, b
].Frames
;
430 Back
:= PlayerModelsArray
[ID
].Anim
[TDirection
.D_RIGHT
, b
].Back
;
433 PlayerModelsArray
[ID
].ModelSpeed
[b
] := Max(1, config
.ReadInt(AnimNames
[b
], 'waitcount', 1) div 3);
436 with PlayerModelsArray
[ID
], config
do
438 prefix
:= FileName
+':SOUNDS\';
442 s
:= config
.ReadStr('Sound', 'pain'+IntToStr(a
), '');
445 SetLength(PainSounds
, Length(PainSounds
)+1);
446 g_Sound_CreateWAD(PainSounds
[High(PainSounds
)].ID
, prefix
+s
);
447 PainSounds
[High(PainSounds
)].Level
:= config
.ReadInt('Sound', 'painlevel'+IntToStr(a
), 1);
454 s
:= config
.ReadStr('Sound', 'die'+IntToStr(a
), '');
457 SetLength(DieSounds
, Length(DieSounds
)+1);
458 g_Sound_CreateWAD(DieSounds
[High(DieSounds
)].ID
, prefix
+s
);
459 DieSounds
[High(DieSounds
)].Level
:= config
.ReadInt('Sound', 'dielevel'+IntToStr(a
), 1);
464 SlopSound
:= Min(Max(config
.ReadInt('Sound', 'slop', 0), 0), 2);
467 GibsCount
:= config
.ReadInt('Gibs', 'count', 0);
468 GibsResource
:= config
.ReadStr('Gibs', 'resource', 'GIBS');
469 GibsMask
:= config
.ReadStr('Gibs', 'mask', 'GIBSMASK');
470 GibsOnce
:= config
.ReadInt('Gibs', 'once', -1);
474 for aa
:= WP_FIRST
+ 1 to WP_LAST
do
475 for bb
:= A_STAND
to A_LAST
do
476 if not (bb
in [A_DIE1
, A_DIE2
, A_PAIN
]) then
478 chk
:= GetWeapPoints(
479 config
.ReadStr(AnimNames
[bb
], WeapNames
[aa
] + '_points', ''),
483 Anim
[TDirection
.D_RIGHT
, bb
].Frames
,
484 Anim
[TDirection
.D_RIGHT
, bb
].Back
,
487 if ok
and (not chk
) and (aa
= WEAPON_FLAMETHROWER
) then
489 // workaround for flamethrower
490 chk
:= GetWeapPoints(
491 config
.ReadStr(AnimNames
[bb
], WeapNames
[WEAPON_PLASMA
] + '_points', ''),
495 Anim
[TDirection
.D_RIGHT
, bb
].Frames
,
496 Anim
[TDirection
.D_RIGHT
, bb
].Back
,
500 for f
:= 0 to High(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
]) do
505 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].X
, 6);
506 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].Y
, 8);
508 A_WALKATTACK
, A_WALK
:
510 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].X
, 9);
511 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].Y
, 9);
515 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].X
, 5);
516 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].Y
, 8);
518 A_WALKSEEUP
, A_SEEUP
:
520 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].X
, 5);
521 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].Y
, 16);
523 A_WALKSEEDOWN
, A_SEEDOWN
:
525 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].X
, 6);
526 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].Y
, 5);
528 A_WALKATTACKUP
, A_ATTACKUP
:
530 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].X
, 5);
531 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].Y
, 16);
533 A_WALKATTACKDOWN
, A_ATTACKDOWN
:
535 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].X
, 6);
536 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].Y
, 4);
542 ok
:= ok
and (chk
or (bb
> A_LASTBASE
));
544 chk2
:= GetWeapPoints(
545 config
.ReadStr(AnimNames
[bb
], WeapNames
[aa
] + '2_points', ''),
549 Anim
[TDirection
.D_LEFT
, bb
].Frames
,
550 Anim
[TDirection
.D_LEFT
, bb
].Back
,
555 for f
:= 0 to High(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
]) do
557 WeaponPoints
[aa
, bb
, TDirection
.D_LEFT
, f
].X
:= -WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].X
;
558 WeaponPoints
[aa
, bb
, TDirection
.D_LEFT
, f
].Y
:= WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].Y
;
562 if not ok
then Break
;
564 {if ok then g_Console_Add(Info.Name+' weapon points ok')
565 else g_Console_Add(Info.Name+' weapon points fail');}
566 PlayerModelsArray
[ID
].HaveWeapon
:= ok
;
568 s
:= config
.ReadStr('Model', 'flag_point', '');
569 if not GetPoint(s
, FlagPoint
) then
570 FlagPoint
:= FLAG_DEFPOINT
;
572 FlagAngle
:= config
.ReadInt('Model', 'flag_angle', FLAG_DEFANGLE
);
581 function g_PlayerModel_Get (ModelName
: String): TPlayerModel
;
586 if PlayerModelsArray
= nil then Exit
;
588 for a
:= 0 to High(PlayerModelsArray
) do
590 if AnsiLowerCase(PlayerModelsArray
[a
].Name
) = AnsiLowerCase(ModelName
) then
592 Result
:= TPlayerModel
.Create
;
594 with PlayerModelsArray
[a
] do
597 Result
.ChangeAnimation(A_STAND
, True);
604 function g_PlayerModel_GetNames(): SSArray
;
610 if PlayerModelsArray
= nil then Exit
;
612 for i
:= 0 to High(PlayerModelsArray
) do
614 SetLength(Result
, Length(Result
)+1);
615 Result
[High(Result
)] := PlayerModelsArray
[i
].Name
;
620 function g_PlayerModel_GetBlood(ModelName
: string): TModelBlood
;
627 Result
.Kind
:= BLOOD_NORMAL
;
628 if PlayerModelsArray
= nil then Exit
;
630 for a
:= 0 to High(PlayerModelsArray
) do
631 if PlayerModelsArray
[a
].Name
= ModelName
then
633 Result
:= PlayerModelsArray
[a
].Blood
;
639 procedure g_PlayerModel_FreeData();
642 e_WriteLog('Releasing models...', TMsgType
.Notify
);
644 if PlayerModelsArray
= nil then Exit
;
646 for i
:= 0 to High(PlayerModelsArray
) do
648 with PlayerModelsArray
[i
] do
650 if PainSounds
<> nil then
651 for b
:= 0 to High(PainSounds
) do
652 e_DeleteSound(PainSounds
[b
].ID
);
653 if DieSounds
<> nil then
654 for b
:= 0 to High(DieSounds
) do
655 e_DeleteSound(DieSounds
[b
].ID
);
658 PlayerModelsArray
:= nil;
663 procedure TPlayerModel
.ChangeAnimation (Animation
: Byte; Force
: Boolean = False);
664 var once
: Boolean; speed
, count
: Integer;
667 if FCurrentAnimation
= Animation
then
669 FCurrentAnimation
:= Animation
;
670 once
:= FCurrentAnimation
in [A_STAND
, A_WALK
];
671 speed
:= PlayerModelsArray
[FID
].ModelSpeed
[FCurrentAnimation
];
672 count
:= PlayerModelsArray
[FID
].Anim
[FDirection
, FCurrentAnimation
].Frames
;
673 FAnimState
:= TAnimState
.Create(once
, speed
, count
);
676 destructor TPlayerModel
.Destroy();
678 FAnimState
.Invalidate
;
682 function TPlayerModel
.PlaySound(SoundType
, Level
: Byte; X
, Y
: Integer): Boolean;
684 TempArray
: array of DWORD
;
688 SetLength(TempArray
, 0);
690 if SoundType
= MODELSOUND_PAIN
then
692 if PlayerModelsArray
[FID
].PainSounds
= nil then Exit
;
694 for a
:= 0 to High(PlayerModelsArray
[FID
].PainSounds
) do
695 if PlayerModelsArray
[FID
].PainSounds
[a
].Level
= Level
then
697 SetLength(TempArray
, Length(TempArray
) + 1);
698 TempArray
[High(TempArray
)] := PlayerModelsArray
[FID
].PainSounds
[a
].ID
;
703 if (Level
in [2, 3, 5]) and (PlayerModelsArray
[FID
].SlopSound
> 0) then
705 g_Sound_PlayExAt('SOUND_MONSTER_SLOP', X
, Y
);
706 if PlayerModelsArray
[FID
].SlopSound
= 1 then
712 if PlayerModelsArray
[FID
].DieSounds
= nil then Exit
;
714 for a
:= 0 to High(PlayerModelsArray
[FID
].DieSounds
) do
715 if PlayerModelsArray
[FID
].DieSounds
[a
].Level
= Level
then
717 SetLength(TempArray
, Length(TempArray
) + 1);
718 TempArray
[High(TempArray
)] := PlayerModelsArray
[FID
].DieSounds
[a
].ID
;
720 if (TempArray
= nil) and (Level
= 5) then
722 g_Sound_PlayExAt('SOUND_MONSTER_SLOP', X
, Y
);
728 if TempArray
= nil then Exit
;
730 g_Sound_PlayAt(TempArray
[Random(Length(TempArray
))], X
, Y
);
735 procedure TPlayerModel
.SetColor(Red
, Green
, Blue
: Byte);
742 procedure TPlayerModel
.SetFire (Fire
: Boolean);
745 FFireCounter
:= PlayerModelsArray
[FID
].ModelSpeed
[A_ATTACK
] * PlayerModelsArray
[FID
].Anim
[TDirection
.D_RIGHT
, A_ATTACK
].Frames
+ 1
750 function TPlayerModel
.GetFire (): Boolean;
752 Result
:= FFireCounter
> 0
755 procedure TPlayerModel
.SetFlag (Flag
: Byte);
760 procedure TPlayerModel
.SetWeapon (Weapon
: Byte);
762 FCurrentWeapon
:= Weapon
766 function TPlayerModel
.GetBlood (): TModelBlood
;
768 Result
:= PlayerModelsArray
[FID
].Blood
772 function TPlayerModel
.GetName (): String;
774 Result
:= PlayerModelsArray
[FID
].Name
777 procedure TPlayerModel
.Update
;
779 if FAnimState
.IsValid() then
781 if FFireCounter
> 0 then
785 procedure g_PlayerModel_LoadAll
;
788 knownFiles
: array of AnsiString = nil;
793 // load models from all possible wad types, in all known directories
794 // this does a loosy job (linear search, ooph!), but meh
795 for wext
in wadExtensions
do
797 for f
:= High(ModelDirs
) downto Low(ModelDirs
) do
799 if (FindFirst(ModelDirs
[f
]+DirectorySeparator
+'*'+wext
, faAnyFile
, SR
) = 0) then
803 for s
in knownFiles
do
805 if (strEquCI1251(forceFilenameExt(SR
.Name
, ''), forceFilenameExt(ExtractFileName(s
), ''))) then
813 SetLength(knownFiles
, length(knownFiles
)+1);
814 knownFiles
[High(knownFiles
)] := ModelDirs
[f
]+DirectorySeparator
+SR
.Name
;
816 until (FindNext(SR
) <> 0);
821 if (length(knownFiles
) = 0) then
822 raise Exception
.Create('no player models found!');
823 if (length(knownFiles
) = 1) then
824 e_LogWriteln('1 player model found.', TMsgType
.Notify
)
826 e_LogWritefln('%d player models found.', [Integer(length(knownFiles
))], TMsgType
.Notify
);
827 for s
in knownFiles
do
828 if not g_PlayerModel_Load(s
) then
829 e_LogWritefln('Error loading model "%s"', [s
], TMsgType
.Warning
);