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
, g_gfx
;
39 A_WALKATTACKDOWN
= 14;
43 A_FISTWALKATTACK
= 18;
47 A_FISTATTACKDOWN
= 22;
50 A_LASTEXT
= A_FISTATTACKDOWN
;
63 FLAG_BASEPOINT
: TDFPoint
= (X
:16; Y
:43);
66 TWeaponPoints
= Array [WP_FIRST
+ 1..WP_LAST
, A_STAND
..A_LAST
, TDirection
.D_LEFT
..TDirection
.D_RIGHT
] of Array of TDFPoint
;
68 TModelMatrix
= Array [TDirection
.D_LEFT
..TDirection
.D_RIGHT
, A_STAND
..A_LAST
] of TAnimationState
;
70 TModelTextures
= Array [TDirection
.D_LEFT
..TDirection
.D_RIGHT
, A_STAND
..A_LAST
] of record
86 TModelSoundArray
= Array of TModelSound
;
88 TGibsArray
= Array of Integer;
90 TPlayerModel
= class{$IFDEF USE_MEMPOOL}(TPoolObject
){$ENDIF}
92 FDirection
: TDirection
;
94 FCurrentAnimation
: Byte;
95 FAnimState
: TAnimationState
;
102 destructor Destroy(); override;
103 procedure ChangeAnimation(Animation
: Byte; Force
: Boolean = False);
104 procedure SetColor(Red
, Green
, Blue
: Byte);
105 procedure SetWeapon(Weapon
: Byte);
106 procedure SetFlag(Flag
: Byte);
107 procedure SetFire (Fire
: Boolean);
108 function GetFire (): Boolean;
109 function PlaySound(SoundType
, Level
: Byte; X
, Y
: Integer): Boolean;
112 function GetBlood (): TModelBlood
;
113 function GetName (): String;
116 property Direction
: TDirection read FDirection write FDirection
;
117 property Animation
: Byte read FCurrentAnimation
;
118 property Weapon
: Byte read FCurrentWeapon
;
121 property Color
: TRGB read FColor write FColor
;
122 property AnimState
: TAnimationState read FAnimState
;
123 property CurrentAnimation
: Byte read FCurrentAnimation
;
124 property CurrentWeapon
: Byte read FCurrentWeapon
;
125 property Flag
: Byte read FFlag
;
126 property ID
: Integer read FID
;
129 procedure g_PlayerModel_LoadAll
;
130 procedure g_PlayerModel_FreeData();
131 function g_PlayerModel_Load(FileName
: String): Boolean;
132 function g_PlayerModel_GetNames(): SSArray
;
133 function g_PlayerModel_GetBlood(ModelName
: String): TModelBlood
;
134 function g_PlayerModel_Get(ModelName
: String): TPlayerModel
;
135 function g_PlayerModel_GetGibs (ModelID
: Integer; var Gibs
: TGibsArray
): Boolean;
136 function g_PlayerModel_GetIndex (ModelName
: String): Integer;
138 procedure g_PlayerModel_LoadFake (ModelName
, FileName
: String);
140 (* --- private data --- *)
143 TPlayerModelInfo
= record
148 ModelSpeed
: Array [A_STAND
..A_PAIN
] of Byte;
151 WeaponPoints
: TWeaponPoints
;
152 PainSounds
: TModelSoundArray
;
153 DieSounds
: TModelSoundArray
;
156 // =======================
158 Anim
: TModelTextures
;
166 PlayerModelsArray
: Array of TPlayerModelInfo
;
171 g_sound
, g_console
, SysUtils
, g_player
, CONFIG
,
172 e_sound
, g_options
, g_map
, Math
, e_log
, wadreader
;
175 FLAG_DEFPOINT
: TDFPoint
= (X
:32; Y
:16);
177 WEAPONBASE
: Array [WP_FIRST
+ 1..WP_LAST
] of TDFPoint
=
178 ((X
:8; Y
:4), (X
:8; Y
:8), (X
:16; Y
:16), (X
:16; Y
:24),
179 (X
:16; Y
:16), (X
:24; Y
:24), (X
:16; Y
:16), (X
:24; Y
:24),
180 (X
:16; Y
:16), (X
:8; Y
:8));
182 AnimNames
: Array [A_STAND
..A_LASTEXT
] of String =
183 ('StandAnim','WalkAnim','Die1Anim','Die2Anim','AttackAnim',
184 'SeeUpAnim','SeeDownAnim','AttackUpAnim','AttackDownAnim','PainAnim',
186 'WalkAttackAnim', 'WalkSeeUpAnim', 'WalkSeeDownAnim',
187 'WalkAttackUpAnim', 'WalkAttackDownAnim', 'FistStandAnim', 'FistWalkAnim',
188 'FistAttackAnim', 'FistWalkAttackAnim', 'FistSeeUpAnim', 'FistSeeDownAnim',
189 'FistAttackUpAnim', 'FistAttackDownAnim');
190 WeapNames
: Array [WP_FIRST
+ 1..WP_LAST
] of String =
191 ('csaw', 'hgun', 'sg', 'ssg', 'mgun', 'rkt', 'plz', 'bfg', 'spl', 'flm');
193 function g_PlayerModel_GetIndex (ModelName
: String): Integer;
197 if PlayerModelsArray
<> nil then
200 while (i
< Length(PlayerModelsArray
)) and (PlayerModelsArray
[i
].Name
<> ModelName
) do
202 if i
< Length(PlayerModelsArray
) then
207 function GetPoint(var str
: String; var point
: TDFPoint
): Boolean;
217 if Length(str
) < 3 then
220 for a
:= 1 to Length(str
) do
221 if (str
[a
] = ',') or (a
= Length(str
)) then
223 s
:= Copy(str
, 1, a
);
224 if s
[Length(s
)] = ',' then
225 SetLength(s
, Length(s
)-1);
228 if (Sscanf(s
, '%d:%d', [@x
, @y
]) < 2) or
229 (x
< -64) or (x
> 128) or
230 (y
< -64) or (y
> 128) then
242 function GetWeapPoints(str
: String; weapon
: Byte; anim
: Byte; dir
: TDirection
;
243 frames
: Word; backanim
: Boolean; var wpoints
: TWeaponPoints
): Boolean;
252 backanim
:= backanim
and (frames
> 2);
254 for a
:= 1 to frames
do
256 if not GetPoint(str
, wpoints
[weapon
, anim
, dir
, a
-1]) then
259 with wpoints
[weapon
, anim
, dir
, a
-1] do
261 X
:= X
- WEAPONBASE
[weapon
].X
;
262 Y
:= Y
- WEAPONBASE
[weapon
].Y
;
263 if dir
= TDirection
.D_LEFT
then
268 h
:= High(wpoints
[weapon
, anim
, dir
]);
270 for b
:= h
downto frames
do
271 wpoints
[weapon
, anim
, dir
, b
] := wpoints
[weapon
, anim
, dir
, h
-b
+1];
276 procedure g_PlayerMode_ExtendPoints (id
: Integer; AIdx
: Integer);
278 CopyAnim
: array [A_LASTBASE
+1..A_LASTEXT
] of Integer = (
279 A_WALK
, A_WALK
, A_WALK
, A_WALK
, A_WALK
,
280 A_STAND
, A_WALK
, A_ATTACK
, A_WALK
, A_SEEUP
, A_SEEDOWN
,
281 A_ATTACKUP
, A_ATTACKDOWN
283 var W
, I
, OIdx
: Integer; D
: TDirection
;
285 OIdx
:= CopyAnim
[AIdx
];
286 with PlayerModelsArray
[id
] do
288 for W
:= WP_FIRST
+ 1 to WP_LAST
do
290 for D
:= TDirection
.D_LEFT
to TDirection
.D_RIGHT
do
292 SetLength(WeaponPoints
[W
, AIdx
, D
], Length(WeaponPoints
[W
, OIdx
, D
]));
293 for I
:= 0 to High(WeaponPoints
[W
, AIdx
, D
]) do
294 WeaponPoints
[W
, AIdx
, D
, I
] := WeaponPoints
[W
, OIdx
, D
, I
]
300 procedure g_PlayerModel_LoadFake (ModelName
, FileName
: String);
303 SetLength(PlayerModelsArray
, Length(PlayerModelsArray
) + 1);
304 id
:= High(PlayerModelsArray
);
305 PlayerModelsArray
[id
].Name
:= ModelName
;
306 PlayerModelsArray
[id
].HaveWeapon
:= False;
307 PlayerModelsArray
[id
].FileName
:= FileName
;
310 function g_PlayerModel_Load(FileName
: string): Boolean;
313 a
, b
, len
, aa
, bb
, f
: Integer;
320 ok
, chk
, chk2
: Boolean;
322 e_WriteLog(Format('Loading player model "%s"...', [FileName
]), TMsgType
.Notify
);
326 WAD
:= TWADFile
.Create
;
327 WAD
.ReadFile(FileName
);
329 if {WAD.GetLastError <> DFWAD_NOERROR} not WAD
.isOpen
then
335 if not WAD
.GetResource('TEXT/MODEL', pData
, len
) then
341 config
:= TConfig
.CreateMem(pData
, len
);
344 s
:= config
.ReadStr('Model', 'name', '');
352 SetLength(PlayerModelsArray
, Length(PlayerModelsArray
)+1);
353 ID
:= High(PlayerModelsArray
);
355 prefix
:= FileName
+':TEXTURES\';
357 PlayerModelsArray
[ID
].Name
:= s
;
358 PlayerModelsArray
[ID
].Author
:= config
.ReadStr('Model', 'author', '');
359 PlayerModelsArray
[ID
].Description
:= config
.ReadStr('Model', 'description', '');
360 PlayerModelsArray
[ID
].FileName
:= FileName
;
361 with PlayerModelsArray
[ID
] do
363 Blood
.R
:= MAX(0, MIN(255, config
.ReadInt('Blood', 'R', 150)));
364 Blood
.G
:= MAX(0, MIN(255, config
.ReadInt('Blood', 'G', 0)));
365 Blood
.B
:= MAX(0, MIN(255, config
.ReadInt('Blood', 'B', 0)));
366 case config
.ReadStr('Blood', 'Kind', 'NORMAL') of
367 'NORMAL': Blood
.Kind
:= BLOOD_NORMAL
;
368 'SPARKS': Blood
.Kind
:= BLOOD_CSPARKS
;
369 'COMBINE': Blood
.Kind
:= BLOOD_COMBINE
;
371 Blood
.Kind
:= BLOOD_NORMAL
375 for b
:= A_STAND
to A_LAST
do
377 with PlayerModelsArray
[ID
].Anim
[TDirection
.D_RIGHT
, b
] do
379 Resource
:= config
.ReadStr(AnimNames
[b
], 'resource', '');
380 Mask
:= config
.ReadStr(AnimNames
[b
], 'mask', '');
381 Frames
:= config
.ReadInt(AnimNames
[b
], 'frames', 1);
382 Back
:= config
.ReadBool(AnimNames
[b
], 'backanim', False);
383 if (Resource
= '') or (Mask
= '') then
385 if b
<= A_LASTBASE
then
393 g_PlayerMode_ExtendPoints(ID
, b
);
399 for aa
:= WP_FIRST
+ 1 to WP_LAST
do
400 for bb
:= A_STAND
to A_LAST
do
401 for cc
:= TDirection
.D_LEFT
to TDirection
.D_RIGHT
do
403 f
:= PlayerModelsArray
[ID
].Anim
[cc
, bb
].Frames
;
404 if PlayerModelsArray
[ID
].Anim
[cc
, bb
].Back
and (f
> 2) then
406 SetLength(PlayerModelsArray
[ID
].WeaponPoints
[aa
, bb
, cc
], f
);
409 with PlayerModelsArray
[ID
].Anim
[TDirection
.D_LEFT
, b
] do
411 Frames
:= PlayerModelsArray
[ID
].Anim
[TDirection
.D_RIGHT
, b
].Frames
;
412 Back
:= PlayerModelsArray
[ID
].Anim
[TDirection
.D_RIGHT
, b
].Back
;
415 PlayerModelsArray
[ID
].ModelSpeed
[b
] := Max(1, config
.ReadInt(AnimNames
[b
], 'waitcount', 1) div 3);
418 with PlayerModelsArray
[ID
], config
do
420 prefix
:= FileName
+':SOUNDS\';
424 s
:= config
.ReadStr('Sound', 'pain'+IntToStr(a
), '');
427 SetLength(PainSounds
, Length(PainSounds
)+1);
428 g_Sound_CreateWAD(PainSounds
[High(PainSounds
)].ID
, prefix
+s
);
429 PainSounds
[High(PainSounds
)].Level
:= config
.ReadInt('Sound', 'painlevel'+IntToStr(a
), 1);
436 s
:= config
.ReadStr('Sound', 'die'+IntToStr(a
), '');
439 SetLength(DieSounds
, Length(DieSounds
)+1);
440 g_Sound_CreateWAD(DieSounds
[High(DieSounds
)].ID
, prefix
+s
);
441 DieSounds
[High(DieSounds
)].Level
:= config
.ReadInt('Sound', 'dielevel'+IntToStr(a
), 1);
446 SlopSound
:= Min(Max(config
.ReadInt('Sound', 'slop', 0), 0), 2);
448 GibsCount
:= config
.ReadInt('Gibs', 'count', 0);
449 GibsResource
:= config
.ReadStr('Gibs', 'resource', 'GIBS');
450 GibsMask
:= config
.ReadStr('Gibs', 'mask', 'GIBSMASK');
451 GibsOnce
:= config
.ReadInt('Gibs', 'once', -1);
454 for aa
:= WP_FIRST
+ 1 to WP_LAST
do
455 for bb
:= A_STAND
to A_LAST
do
456 if not (bb
in [A_DIE1
, A_DIE2
, A_PAIN
]) then
458 chk
:= GetWeapPoints(
459 config
.ReadStr(AnimNames
[bb
], WeapNames
[aa
] + '_points', ''),
463 Anim
[TDirection
.D_RIGHT
, bb
].Frames
,
464 Anim
[TDirection
.D_RIGHT
, bb
].Back
,
467 if ok
and (not chk
) and (aa
= WEAPON_FLAMETHROWER
) then
469 // workaround for flamethrower
470 chk
:= GetWeapPoints(
471 config
.ReadStr(AnimNames
[bb
], WeapNames
[WEAPON_PLASMA
] + '_points', ''),
475 Anim
[TDirection
.D_RIGHT
, bb
].Frames
,
476 Anim
[TDirection
.D_RIGHT
, bb
].Back
,
480 for f
:= 0 to High(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
]) do
485 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].X
, 6);
486 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].Y
, 8);
488 A_WALKATTACK
, A_WALK
:
490 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].X
, 9);
491 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].Y
, 9);
495 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].X
, 5);
496 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].Y
, 8);
498 A_WALKSEEUP
, A_SEEUP
:
500 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].X
, 5);
501 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].Y
, 16);
503 A_WALKSEEDOWN
, A_SEEDOWN
:
505 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].X
, 6);
506 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].Y
, 5);
508 A_WALKATTACKUP
, A_ATTACKUP
:
510 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].X
, 5);
511 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].Y
, 16);
513 A_WALKATTACKDOWN
, A_ATTACKDOWN
:
515 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].X
, 6);
516 Dec(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].Y
, 4);
522 ok
:= ok
and (chk
or (bb
> A_LASTBASE
));
524 chk2
:= GetWeapPoints(
525 config
.ReadStr(AnimNames
[bb
], WeapNames
[aa
] + '2_points', ''),
529 Anim
[TDirection
.D_LEFT
, bb
].Frames
,
530 Anim
[TDirection
.D_LEFT
, bb
].Back
,
535 for f
:= 0 to High(WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
]) do
537 WeaponPoints
[aa
, bb
, TDirection
.D_LEFT
, f
].X
:= -WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].X
;
538 WeaponPoints
[aa
, bb
, TDirection
.D_LEFT
, f
].Y
:= WeaponPoints
[aa
, bb
, TDirection
.D_RIGHT
, f
].Y
;
542 if not ok
then Break
;
544 {if ok then g_Console_Add(Info.Name+' weapon points ok')
545 else g_Console_Add(Info.Name+' weapon points fail');}
546 PlayerModelsArray
[ID
].HaveWeapon
:= ok
;
548 s
:= config
.ReadStr('Model', 'flag_point', '');
549 if not GetPoint(s
, FlagPoint
) then
550 FlagPoint
:= FLAG_DEFPOINT
;
552 FlagAngle
:= config
.ReadInt('Model', 'flag_angle', FLAG_DEFANGLE
);
561 function g_PlayerModel_Get (ModelName
: String): TPlayerModel
;
566 if PlayerModelsArray
= nil then Exit
;
568 for a
:= 0 to High(PlayerModelsArray
) do
570 if AnsiLowerCase(PlayerModelsArray
[a
].Name
) = AnsiLowerCase(ModelName
) then
572 Result
:= TPlayerModel
.Create
;
574 with PlayerModelsArray
[a
] do
577 Result
.ChangeAnimation(A_STAND
, True);
584 function g_PlayerModel_GetGibs (ModelID
: Integer; var Gibs
: TGibsArray
): Boolean;
585 var i
, b
: Integer; c
: Boolean;
589 if (PlayerModelsArray
= nil) or (gGibsCount
= 0) then
593 SetLength(Gibs
, gGibsCount
);
594 for i
:= 0 to High(Gibs
) do
596 if c
and (PlayerModelsArray
[ModelID
].GibsCount
= 1) then
603 b
:= Random(PlayerModelsArray
[ModelID
].GibsCount
);
604 until not ((PlayerModelsArray
[ModelID
].GibsOnce
= b
+ 1) and c
);
608 c
:= PlayerModelsArray
[ModelID
].GibsOnce
= b
+ 1;
613 function g_PlayerModel_GetNames(): SSArray
;
619 if PlayerModelsArray
= nil then Exit
;
621 for i
:= 0 to High(PlayerModelsArray
) do
623 SetLength(Result
, Length(Result
)+1);
624 Result
[High(Result
)] := PlayerModelsArray
[i
].Name
;
628 function g_PlayerModel_GetBlood(ModelName
: string): TModelBlood
;
635 Result
.Kind
:= BLOOD_NORMAL
;
636 if PlayerModelsArray
= nil then Exit
;
638 for a
:= 0 to High(PlayerModelsArray
) do
639 if PlayerModelsArray
[a
].Name
= ModelName
then
641 Result
:= PlayerModelsArray
[a
].Blood
;
646 procedure g_PlayerModel_FreeData();
649 e_WriteLog('Releasing models...', TMsgType
.Notify
);
651 if PlayerModelsArray
= nil then Exit
;
653 for i
:= 0 to High(PlayerModelsArray
) do
655 with PlayerModelsArray
[i
] do
657 if PainSounds
<> nil then
658 for b
:= 0 to High(PainSounds
) do
659 e_DeleteSound(PainSounds
[b
].ID
);
660 if DieSounds
<> nil then
661 for b
:= 0 to High(DieSounds
) do
662 e_DeleteSound(DieSounds
[b
].ID
);
665 PlayerModelsArray
:= nil;
670 procedure TPlayerModel
.ChangeAnimation (Animation
: Byte; Force
: Boolean = False);
671 var once
: Boolean; speed
, count
: Integer;
674 if FCurrentAnimation
= Animation
then
676 FCurrentAnimation
:= Animation
;
677 once
:= FCurrentAnimation
in [A_STAND
, A_WALK
];
678 speed
:= PlayerModelsArray
[FID
].ModelSpeed
[FCurrentAnimation
];
679 count
:= PlayerModelsArray
[FID
].Anim
[FDirection
, FCurrentAnimation
].Frames
;
680 FAnimState
:= TAnimationState
.Create(once
, speed
, count
);
683 destructor TPlayerModel
.Destroy();
689 function TPlayerModel
.PlaySound(SoundType
, Level
: Byte; X
, Y
: Integer): Boolean;
691 TempArray
: array of DWORD
;
695 SetLength(TempArray
, 0);
697 if SoundType
= MODELSOUND_PAIN
then
699 if PlayerModelsArray
[FID
].PainSounds
= nil then Exit
;
701 for a
:= 0 to High(PlayerModelsArray
[FID
].PainSounds
) do
702 if PlayerModelsArray
[FID
].PainSounds
[a
].Level
= Level
then
704 SetLength(TempArray
, Length(TempArray
) + 1);
705 TempArray
[High(TempArray
)] := PlayerModelsArray
[FID
].PainSounds
[a
].ID
;
710 if (Level
in [2, 3, 5]) and (PlayerModelsArray
[FID
].SlopSound
> 0) then
712 g_Sound_PlayExAt('SOUND_MONSTER_SLOP', X
, Y
);
713 if PlayerModelsArray
[FID
].SlopSound
= 1 then
719 if PlayerModelsArray
[FID
].DieSounds
= nil then Exit
;
721 for a
:= 0 to High(PlayerModelsArray
[FID
].DieSounds
) do
722 if PlayerModelsArray
[FID
].DieSounds
[a
].Level
= Level
then
724 SetLength(TempArray
, Length(TempArray
) + 1);
725 TempArray
[High(TempArray
)] := PlayerModelsArray
[FID
].DieSounds
[a
].ID
;
727 if (TempArray
= nil) and (Level
= 5) then
729 g_Sound_PlayExAt('SOUND_MONSTER_SLOP', X
, Y
);
735 if TempArray
= nil then Exit
;
737 g_Sound_PlayAt(TempArray
[Random(Length(TempArray
))], X
, Y
);
742 procedure TPlayerModel
.SetColor(Red
, Green
, Blue
: Byte);
749 procedure TPlayerModel
.SetFire (Fire
: Boolean);
752 FFireCounter
:= PlayerModelsArray
[FID
].ModelSpeed
[A_ATTACK
] * PlayerModelsArray
[FID
].Anim
[TDirection
.D_RIGHT
, A_ATTACK
].Frames
+ 1
757 function TPlayerModel
.GetFire (): Boolean;
759 Result
:= FFireCounter
> 0
762 procedure TPlayerModel
.SetFlag (Flag
: Byte);
767 procedure TPlayerModel
.SetWeapon (Weapon
: Byte);
769 FCurrentWeapon
:= Weapon
772 function TPlayerModel
.GetBlood (): TModelBlood
;
774 Result
:= PlayerModelsArray
[FID
].Blood
777 function TPlayerModel
.GetName (): String;
779 Result
:= PlayerModelsArray
[FID
].Name
782 procedure TPlayerModel
.Update
;
784 if FAnimState
<> nil then
786 if FFireCounter
> 0 then
790 procedure g_PlayerModel_LoadAll
;
793 knownFiles
: array of AnsiString = nil;
798 // load models from all possible wad types, in all known directories
799 // this does a loosy job (linear search, ooph!), but meh
800 for wext
in wadExtensions
do
802 for f
:= High(ModelDirs
) downto Low(ModelDirs
) do
804 if (FindFirst(ModelDirs
[f
]+DirectorySeparator
+'*'+wext
, faAnyFile
, SR
) = 0) then
808 for s
in knownFiles
do
810 if (strEquCI1251(forceFilenameExt(SR
.Name
, ''), forceFilenameExt(ExtractFileName(s
), ''))) then
818 SetLength(knownFiles
, length(knownFiles
)+1);
819 knownFiles
[High(knownFiles
)] := ModelDirs
[f
]+DirectorySeparator
+SR
.Name
;
821 until (FindNext(SR
) <> 0);
826 if (length(knownFiles
) = 0) then
827 raise Exception
.Create('no player models found!');
828 if (length(knownFiles
) = 1) then
829 e_LogWriteln('1 player model found.', TMsgType
.Notify
)
831 e_LogWritefln('%d player models found.', [Integer(length(knownFiles
))], TMsgType
.Notify
);
832 for s
in knownFiles
do
833 if not g_PlayerModel_Load(s
) then
834 e_LogWritefln('Error loading model "%s"', [s
], TMsgType
.Warning
);