DEADSOFTWARE

less log spam in model loader (hack!)
[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 unit g_playermodel;
19 interface
21 uses
22 g_textures, g_basic, g_weapons, e_graphics, wadreader;
24 const
25 A_STAND = 0;
26 A_WALK = 1;
27 A_DIE1 = 2;
28 A_DIE2 = 3;
29 A_ATTACK = 4;
30 A_SEEUP = 5;
31 A_SEEDOWN = 6;
32 A_ATTACKUP = 7;
33 A_ATTACKDOWN = 8;
34 A_PAIN = 9;
35 // EXTENDED
36 A_WALKATTACK = 10;
37 A_WALKSEEUP = 11;
38 A_WALKSEEDOWN = 12;
39 A_WALKATTACKUP = 13;
40 A_WALKATTACKDOWN = 14;
41 A_FISTSTAND = 15;
42 A_FISTWALK = 16;
43 A_FISTATTACK = 17;
44 A_FISTWALKATTACK = 18;
45 A_FISTSEEUP = 19;
46 A_FISTSEEDOWN = 20;
47 A_FISTATTACKUP = 21;
48 A_FISTATTACKDOWN = 22;
50 A_LASTBASE = A_PAIN;
51 A_LASTEXT = A_FISTATTACKDOWN;
52 A_LAST = A_LASTEXT;
54 MODELSOUND_PAIN = 0;
55 MODELSOUND_DIE = 1;
57 type
58 TModelInfo = record
59 Name: String;
60 Author: String;
61 Description: String;
62 HaveWeapon: Boolean;
63 end;
65 TModelSound = record
66 ID: DWORD;
67 Level: Byte;
68 end;
70 TGibSprite = record
71 ID: DWORD;
72 MaskID: DWORD;
73 Rect: TRectWH;
74 OnlyOne: Boolean;
75 end;
77 TModelSoundArray = Array of TModelSound;
78 TGibsArray = Array of TGibSprite;
79 TWeaponPoints = Array [WP_FIRST + 1..WP_LAST] of
80 Array [A_STAND..A_LAST] of
81 Array [D_LEFT..D_RIGHT] of Array of TPoint;
83 TPlayerModel = class (TObject)
84 private
85 FName: String;
86 FDirection: TDirection;
87 FColor: TRGB;
88 FCurrentAnimation: Byte;
89 FAnim: Array [D_LEFT..D_RIGHT] of Array [A_STAND..A_LAST] of TAnimation;
90 FMaskAnim: Array [D_LEFT..D_RIGHT] of Array [A_STAND..A_LAST] of TAnimation;
91 FWeaponPoints: TWeaponPoints;
92 FPainSounds: TModelSoundArray;
93 FDieSounds: TModelSoundArray;
94 FSlopSound: Byte;
95 FCurrentWeapon: Byte;
96 FDrawWeapon: Boolean;
97 FFlag: Byte;
98 FFlagPoint: TPoint;
99 FFlagAngle: SmallInt;
100 FFlagAnim: TAnimation;
101 FFire: Boolean;
102 FFireCounter: Byte;
104 public
105 destructor Destroy(); override;
106 procedure ChangeAnimation(Animation: Byte; Force: Boolean = False);
107 function GetCurrentAnimation: TAnimation;
108 function GetCurrentAnimationMask: TAnimation;
109 procedure SetColor(Red, Green, Blue: Byte);
110 procedure SetWeapon(Weapon: Byte);
111 procedure SetFlag(Flag: Byte);
112 procedure SetFire(Fire: Boolean);
113 function PlaySound(SoundType, Level: Byte; X, Y: Integer): Boolean;
114 procedure Update();
115 procedure Draw(X, Y: Integer; Alpha: Byte = 0);
117 property Fire: Boolean read FFire;
118 property Direction: TDirection read FDirection write FDirection;
119 property Animation: Byte read FCurrentAnimation;
120 property Weapon: Byte read FCurrentWeapon;
121 property Name: String read FName;
122 property Color: TRGB read FColor write FColor;
123 end;
125 procedure g_PlayerModel_LoadData();
126 procedure g_PlayerModel_FreeData();
127 function g_PlayerModel_Load(FileName: String): Boolean;
128 function g_PlayerModel_GetNames(): SArray;
129 function g_PlayerModel_GetInfo(ModelName: String): TModelInfo;
130 function g_PlayerModel_Get(ModelName: String): TPlayerModel;
131 function g_PlayerModel_GetAnim(ModelName: String; Anim: Byte; var _Anim, _Mask: TAnimation): Boolean;
132 function g_PlayerModel_GetGibs(ModelName: String; var Gibs: TGibsArray): Boolean;
134 implementation
136 uses
137 g_main, g_sound, g_console, SysUtils, g_player, CONFIG,
138 GL, GLExt, e_sound, g_options, g_map, Math, e_log;
140 type
141 TPlayerModelInfo = record
142 Info: TModelInfo;
143 ModelSpeed: Array [A_STAND..A_PAIN] of Byte;
144 FlagPoint: TPoint;
145 FlagAngle: SmallInt;
146 WeaponPoints: TWeaponPoints;
147 Gibs: TGibsArray;
148 PainSounds: TModelSoundArray;
149 DieSounds: TModelSoundArray;
150 SlopSound: Byte;
151 end;
153 const
154 W_POS_NORMAL = 0;
155 W_POS_UP = 1;
156 W_POS_DOWN = 2;
158 W_ACT_NORMAL = 0;
159 W_ACT_FIRE = 1;
161 FLAG_BASEPOINT: TPoint = (X:16; Y:43);
162 FLAG_DEFPOINT: TPoint = (X:32; Y:16);
163 FLAG_DEFANGLE = -20;
164 WEAPONBASE: Array [WP_FIRST + 1..WP_LAST] of TPoint =
165 ((X:8; Y:4), (X:8; Y:8), (X:16; Y:16), (X:16; Y:24),
166 (X:16; Y:16), (X:24; Y:24), (X:16; Y:16), (X:24; Y:24),
167 (X:16; Y:16), (X:8; Y:8));
169 AnimNames: Array [A_STAND..A_LASTEXT] of String =
170 ('StandAnim','WalkAnim','Die1Anim','Die2Anim','AttackAnim',
171 'SeeUpAnim','SeeDownAnim','AttackUpAnim','AttackDownAnim','PainAnim',
172 // EXTENDED
173 'WalkAttackAnim', 'WalkSeeUpAnim', 'WalkSeeDownAnim',
174 'WalkAttackUpAnim', 'WalkAttackDownAnim', 'FistStandAnim', 'FistWalkAnim',
175 'FistAttackAnim', 'FistWalkAttackAnim', 'FistSeeUpAnim', 'FistSeeDownAnim',
176 'FistAttackUpAnim', 'FistAttackDownAnim');
177 WeapNames: Array [WP_FIRST + 1..WP_LAST] of String =
178 ('csaw', 'hgun', 'sg', 'ssg', 'mgun', 'rkt', 'plz', 'bfg', 'spl', 'flm');
180 var
181 WeaponID: Array [WP_FIRST + 1..WP_LAST] of
182 Array [W_POS_NORMAL..W_POS_DOWN] of
183 Array [W_ACT_NORMAL..W_ACT_FIRE] of DWORD;
184 PlayerModelsArray: Array of TPlayerModelInfo;
186 procedure g_PlayerModel_LoadData();
187 var
188 a: Integer;
189 begin
190 for a := WP_FIRST + 1 to WP_LAST do
191 begin
192 g_Texture_CreateWAD(WeaponID[a][W_POS_NORMAL][W_ACT_NORMAL], GameWAD+':WEAPONS\'+UpperCase(WeapNames[a]));
193 g_Texture_CreateWAD(WeaponID[a][W_POS_NORMAL][W_ACT_FIRE], GameWAD+':WEAPONS\'+UpperCase(WeapNames[a])+'_FIRE');
194 g_Texture_CreateWAD(WeaponID[a][W_POS_UP][W_ACT_NORMAL], GameWAD+':WEAPONS\'+UpperCase(WeapNames[a])+'_UP');
195 g_Texture_CreateWAD(WeaponID[a][W_POS_UP][W_ACT_FIRE], GameWAD+':WEAPONS\'+UpperCase(WeapNames[a])+'_UP_FIRE');
196 g_Texture_CreateWAD(WeaponID[a][W_POS_DOWN][W_ACT_NORMAL], GameWAD+':WEAPONS\'+UpperCase(WeapNames[a])+'_DN');
197 g_Texture_CreateWAD(WeaponID[a][W_POS_DOWN][W_ACT_FIRE], GameWAD+':WEAPONS\'+UpperCase(WeapNames[a])+'_DN_FIRE');
198 end;
199 end;
201 function GetPoint(var str: String; var point: TPoint): Boolean;
202 var
203 a, x, y: Integer;
204 s: String;
205 begin
206 Result := False;
207 x := 0;
208 y := 0;
210 str := Trim(str);
211 if Length(str) < 3 then
212 Exit;
214 for a := 1 to Length(str) do
215 if (str[a] = ',') or (a = Length(str)) then
216 begin
217 s := Copy(str, 1, a);
218 if s[Length(s)] = ',' then
219 SetLength(s, Length(s)-1);
220 Delete(str, 1, a);
222 if (Sscanf(s, '%d:%d', [@x, @y]) < 2) or
223 (x < -64) or (x > 128) or
224 (y < -64) or (y > 128) then
225 Exit;
227 point.X := x;
228 point.Y := y;
230 Break;
231 end;
233 Result := True;
234 end;
236 function GetWeapPoints(str: String; weapon: Byte; anim: Byte; dir: TDirection;
237 frames: Word; backanim: Boolean; var wpoints: TWeaponPoints): Boolean;
238 var
239 a, b, h: Integer;
240 begin
241 Result := False;
243 if frames = 0 then
244 Exit;
246 backanim := backanim and (frames > 2);
248 for a := 1 to frames do
249 begin
250 if not GetPoint(str, wpoints[weapon, anim, dir, a-1]) then
251 Exit;
253 with wpoints[weapon, anim, dir, a-1] do
254 begin
255 X := X - WEAPONBASE[weapon].X;
256 Y := Y - WEAPONBASE[weapon].Y;
257 if dir = D_LEFT then
258 X := -X;
259 end;
260 end;
262 h := High(wpoints[weapon, anim, dir]);
263 if backanim then
264 for b := h downto frames do
265 wpoints[weapon, anim, dir, b] := wpoints[weapon, anim, dir, h-b+1];
267 Result := True;
268 end;
270 procedure ExtAnimFromBaseAnim(MName: String; AIdx: Integer);
271 const
272 CopyAnim: array [A_LASTBASE+1..A_LASTEXT] of Integer = (
273 A_WALK, A_WALK, A_WALK, A_WALK, A_WALK,
274 A_STAND, A_WALK, A_ATTACK, A_WALK, A_SEEUP, A_SEEDOWN,
275 A_ATTACKUP, A_ATTACKDOWN
276 );
277 var
278 OIdx, W, I: Integer;
279 D: TDirection;
280 AName, OName: String;
281 begin
282 // HACK: shitty workaround to duplicate base animations
283 // in place of extended, replace with something better later
285 Assert((AIdx > A_LASTBASE) and (AIdx <= A_LASTEXT));
286 OIdx := CopyAnim[AIdx];
288 AName := MName + '_RIGHTANIM' + IntToStr(AIdx);
289 OName := MName + '_RIGHTANIM' + IntToStr(OIdx);
290 Assert(g_Frames_Dup(AName, OName));
291 Assert(g_Frames_Dup(AName + '_MASK', OName + '_MASK'));
292 AName := MName + '_LEFTANIM' + IntToStr(AIdx);
293 OName := MName + '_LEFTANIM' + IntToStr(OIdx);
294 if g_Frames_Exists(AName) then
295 begin
296 g_Frames_Dup(AName, OName);
297 g_Frames_Dup(AName + '_MASK', OName + '_MASK');
298 end;
300 with PlayerModelsArray[High(PlayerModelsArray)] do
301 begin
302 for W := WP_FIRST + 1 to WP_LAST do
303 begin
304 for D := D_LEFT to D_RIGHT do
305 begin
306 SetLength(WeaponPoints[W, AIdx, D], Length(WeaponPoints[W, OIdx, D]));
307 for I := 0 to High(WeaponPoints[W, AIdx, D]) do
308 WeaponPoints[W, AIdx, D, I] := WeaponPoints[W, OIdx, D, I]
309 end;
310 end;
311 end;
312 end;
314 function g_PlayerModel_Load(FileName: string): Boolean;
315 var
316 ID: DWORD;
317 a, b, len, lenpd, lenpd2, aa, bb, f: Integer;
318 cc: TDirection;
319 config: TConfig;
320 pData, pData2: Pointer;
321 WAD: TWADFile;
322 s, aname: string;
323 prefix: string;
324 ok, chk: Boolean;
325 begin
326 e_WriteLog(Format('Loading player model: %s', [ExtractFileName(FileName)]), MSG_NOTIFY);
328 Result := False;
330 WAD := TWADFile.Create;
331 WAD.ReadFile(FileName);
333 if {WAD.GetLastError <> DFWAD_NOERROR} not WAD.isOpen then
334 begin
335 WAD.Free();
336 Exit;
337 end;
339 if not WAD.GetResource('TEXT/MODEL', pData, len) then
340 begin
341 WAD.Free();
342 Exit;
343 end;
345 config := TConfig.CreateMem(pData, len);
346 FreeMem(pData);
348 s := config.ReadStr('Model', 'name', '');
349 if s = '' then
350 begin
351 config.Free();
352 WAD.Free();
353 Exit;
354 end;
356 SetLength(PlayerModelsArray, Length(PlayerModelsArray)+1);
357 ID := High(PlayerModelsArray);
359 prefix := FileName+':TEXTURES\';
361 with PlayerModelsArray[ID].Info do
362 begin
363 Name := s;
364 Author := config.ReadStr('Model', 'author', '');
365 Description := config.ReadStr('Model', 'description', '');
366 end;
368 for b := A_STAND to A_LAST do
369 begin
370 aname := s+'_RIGHTANIM'+IntToStr(b);
371 //e_LogWritefln('### MODEL FILE: [%s]', [prefix+config.ReadStr(AnimNames[b], 'resource', '')]);
372 if not (g_Frames_CreateWAD(nil, aname,
373 prefix+config.ReadStr(AnimNames[b], 'resource', ''),
374 64, 64, config.ReadInt(AnimNames[b], 'frames', 1),
375 config.ReadBool(AnimNames[b], 'backanim', False)) and
376 g_Frames_CreateWAD(nil, aname+'_MASK',
377 prefix+config.ReadStr(AnimNames[b], 'mask', ''),
378 64, 64, config.ReadInt(AnimNames[b], 'frames', 1),
379 config.ReadBool(AnimNames[b], 'backanim', False))) then
380 begin
381 if b <= A_LASTBASE then
382 begin
383 config.Free();
384 WAD.Free();
385 Exit;
386 end
387 else
388 begin
389 ExtAnimFromBaseAnim(s, b);
390 continue;
391 end;
392 end;
394 for aa := WP_FIRST + 1 to WP_LAST do
395 for bb := A_STAND to A_LAST do
396 for cc := D_LEFT to D_RIGHT do
397 begin
398 f := config.ReadInt(AnimNames[bb], 'frames', 1);
399 if config.ReadBool(AnimNames[bb], 'backanim', False) then
400 if f > 2 then f := 2*f-2;
401 SetLength(PlayerModelsArray[ID].WeaponPoints[aa, bb, cc], f);
402 end;
404 if (config.ReadStr(AnimNames[b], 'resource2', '') <> '') and
405 (config.ReadStr(AnimNames[b], 'mask2', '') <> '') then
406 begin
407 aname := s+'_LEFTANIM'+IntToStr(b);
408 g_Frames_CreateWAD(nil, aname,
409 prefix+config.ReadStr(AnimNames[b], 'resource2', ''),
410 64, 64, config.ReadInt(AnimNames[b], 'frames', 1),
411 config.ReadBool(AnimNames[b], 'backanim', False));
413 g_Frames_CreateWAD(nil, aname+'_MASK',
414 prefix+config.ReadStr(AnimNames[b], 'mask2', ''),
415 64, 64, config.ReadInt(AnimNames[b], 'frames', 1),
416 config.ReadBool(AnimNames[b], 'backanim', False));
417 end;
419 PlayerModelsArray[ID].ModelSpeed[b] := Max(1, config.ReadInt(AnimNames[b], 'waitcount', 1) div 3);
420 end;
422 with PlayerModelsArray[ID], config do
423 begin
424 prefix := FileName+':SOUNDS\';
426 a := 1;
427 repeat
428 s := config.ReadStr('Sound', 'pain'+IntToStr(a), '');
429 if s <> '' then
430 begin
431 SetLength(PainSounds, Length(PainSounds)+1);
432 g_Sound_CreateWAD(PainSounds[High(PainSounds)].ID, prefix+s);
433 PainSounds[High(PainSounds)].Level := config.ReadInt('Sound', 'painlevel'+IntToStr(a), 1);
434 end;
435 a := a+1;
436 until s = '';
438 a := 1;
439 repeat
440 s := config.ReadStr('Sound', 'die'+IntToStr(a), '');
441 if s <> '' then
442 begin
443 SetLength(DieSounds, Length(DieSounds)+1);
444 g_Sound_CreateWAD(DieSounds[High(DieSounds)].ID, prefix+s);
445 DieSounds[High(DieSounds)].Level := config.ReadInt('Sound', 'dielevel'+IntToStr(a), 1);
446 end;
447 a := a+1;
448 until s = '';
450 SlopSound := Min(Max(config.ReadInt('Sound', 'slop', 0), 0), 2);
452 SetLength(Gibs, ReadInt('Gibs', 'count', 0));
454 if (Gibs <> nil) and
455 (WAD.GetResource('TEXTURES/'+config.ReadStr('Gibs', 'resource', 'GIBS'), pData, lenpd)) and
456 (WAD.GetResource('TEXTURES/'+config.ReadStr('Gibs', 'mask', 'GIBSMASK'), pData2, lenpd2)) then
457 begin
458 for a := 0 to High(Gibs) do
459 if e_CreateTextureMemEx(pData, lenpd, Gibs[a].ID, a*32, 0, 32, 32) and
460 e_CreateTextureMemEx(pData2, lenpd2, Gibs[a].MaskID, a*32, 0, 32, 32) then
461 begin
462 Gibs[a].Rect := e_GetTextureSize2(Gibs[a].ID);
463 with Gibs[a].Rect do
464 if Height > 3 then Height := Height-1-Random(2);
465 Gibs[a].OnlyOne := config.ReadInt('Gibs', 'once', -1) = a+1;
466 end;
468 FreeMem(pData);
469 FreeMem(pData2);
470 end;
472 ok := True;
473 for aa := WP_FIRST + 1 to WP_LAST do
474 for bb := A_STAND to A_LAST do
475 if not (bb in [A_DIE1, A_DIE2, A_PAIN]) then
476 begin
477 chk := GetWeapPoints(config.ReadStr(AnimNames[bb], WeapNames[aa]+'_points', ''), aa, bb, D_RIGHT,
478 config.ReadInt(AnimNames[bb], 'frames', 0),
479 config.ReadBool(AnimNames[bb], 'backanim', False),
480 WeaponPoints);
481 if ok and (not chk) and (aa = WEAPON_FLAMETHROWER) then
482 begin
483 // workaround for flamethrower
484 chk := GetWeapPoints(config.ReadStr(AnimNames[bb], WeapNames[WEAPON_PLASMA]+'_points', ''), aa, bb, D_RIGHT,
485 config.ReadInt(AnimNames[bb], 'frames', 0),
486 config.ReadBool(AnimNames[bb], 'backanim', False),
487 WeaponPoints);
488 if chk then
489 for f := 0 to High(WeaponPoints[aa, bb, D_RIGHT]) do
490 begin
491 case bb of
492 A_STAND, A_PAIN:
493 begin
494 Dec(WeaponPoints[aa, bb, D_RIGHT, f].X, 6);
495 Dec(WeaponPoints[aa, bb, D_RIGHT, f].Y, 8);
496 end;
497 A_WALKATTACK, A_WALK:
498 begin
499 Dec(WeaponPoints[aa, bb, D_RIGHT, f].X, 9);
500 Dec(WeaponPoints[aa, bb, D_RIGHT, f].Y, 9);
501 end;
502 A_ATTACK:
503 begin
504 Dec(WeaponPoints[aa, bb, D_RIGHT, f].X, 5);
505 Dec(WeaponPoints[aa, bb, D_RIGHT, f].Y, 8);
506 end;
507 A_WALKSEEUP, A_SEEUP:
508 begin
509 Dec(WeaponPoints[aa, bb, D_RIGHT, f].X, 5);
510 Dec(WeaponPoints[aa, bb, D_RIGHT, f].Y, 16);
511 end;
512 A_WALKSEEDOWN, A_SEEDOWN:
513 begin
514 Dec(WeaponPoints[aa, bb, D_RIGHT, f].X, 6);
515 Dec(WeaponPoints[aa, bb, D_RIGHT, f].Y, 5);
516 end;
517 A_WALKATTACKUP, A_ATTACKUP:
518 begin
519 Dec(WeaponPoints[aa, bb, D_RIGHT, f].X, 5);
520 Dec(WeaponPoints[aa, bb, D_RIGHT, f].Y, 16);
521 end;
522 A_WALKATTACKDOWN, A_ATTACKDOWN:
523 begin
524 Dec(WeaponPoints[aa, bb, D_RIGHT, f].X, 6);
525 Dec(WeaponPoints[aa, bb, D_RIGHT, f].Y, 4);
526 end;
527 end;
528 end;
529 end;
530 ok := ok and (chk or (bb > A_LASTBASE));
532 if not GetWeapPoints(config.ReadStr(AnimNames[bb], WeapNames[aa]+'2_points', ''), aa, bb, D_LEFT,
533 config.ReadInt(AnimNames[bb], 'frames', 0),
534 config.ReadBool(AnimNames[bb], 'backanim', False),
535 WeaponPoints) then
536 for f := 0 to High(WeaponPoints[aa, bb, D_RIGHT]) do
537 begin
538 WeaponPoints[aa, bb, D_LEFT, f].X := -WeaponPoints[aa, bb, D_RIGHT, f].X;
539 WeaponPoints[aa, bb, D_LEFT, f].Y := WeaponPoints[aa, bb, D_RIGHT, f].Y;
540 end;
542 if not ok then Break;
543 end;
544 {if ok then g_Console_Add(Info.Name+' weapon points ok')
545 else g_Console_Add(Info.Name+' weapon points fail');}
546 Info.HaveWeapon := ok;
548 s := config.ReadStr('Model', 'flag_point', '');
549 if not GetPoint(s, FlagPoint) then FlagPoint := FLAG_DEFPOINT;
551 FlagAngle := config.ReadInt('Model', 'flag_angle', FLAG_DEFANGLE);
552 end;
554 config.Free();
555 WAD.Free();
557 Result := True;
558 end;
560 function g_PlayerModel_Get(ModelName: String): TPlayerModel;
561 var
562 a: Integer;
563 b: Byte;
564 ID, ID2: DWORD;
565 begin
566 Result := nil;
568 if PlayerModelsArray = nil then Exit;
570 for a := 0 to High(PlayerModelsArray) do
571 if AnsiLowerCase(PlayerModelsArray[a].Info.Name) = AnsiLowerCase(ModelName) then
572 begin
573 Result := TPlayerModel.Create;
575 with PlayerModelsArray[a] do
576 begin
577 Result.FName := Info.Name;
579 for b := A_STAND to A_LAST do
580 begin
581 if not (g_Frames_Get(ID, Info.Name+'_RIGHTANIM'+IntToStr(b)) and
582 g_Frames_Get(ID2, Info.Name+'_RIGHTANIM'+IntToStr(b)+'_MASK')) then
583 begin
584 Result.Free();
585 Result := nil;
586 Exit;
587 end;
589 Result.FAnim[D_RIGHT][b] := TAnimation.Create(ID, b in [A_STAND, A_WALK], ModelSpeed[b]);
591 Result.FMaskAnim[D_RIGHT][b] := TAnimation.Create(ID2, b in [A_STAND, A_WALK], ModelSpeed[b]);
593 if g_Frames_Exists(Info.Name+'_LEFTANIM'+IntToStr(b)) and
594 g_Frames_Exists(Info.Name+'_LEFTANIM'+IntToStr(b)+'_MASK') then
595 if g_Frames_Get(ID, Info.Name+'_LEFTANIM'+IntToStr(b)) and
596 g_Frames_Get(ID2, Info.Name+'_LEFTANIM'+IntToStr(b)+'_MASK') then
597 begin
598 Result.FAnim[D_LEFT][b] := TAnimation.Create(ID, b in [A_STAND, A_WALK], ModelSpeed[b]);
600 Result.FMaskAnim[D_LEFT][b] := TAnimation.Create(ID2, b in [A_STAND, A_WALK], ModelSpeed[b]);
601 end;
602 end;
604 Result.FPainSounds := PainSounds;
605 Result.FDieSounds := DieSounds;
606 Result.FSlopSound := SlopSound;
607 Result.FDrawWeapon := Info.HaveWeapon;
608 Result.FWeaponPoints := WeaponPoints;
610 Result.FFlagPoint := FlagPoint;
611 Result.FFlagAngle := FlagAngle;
613 Break;
614 end;
615 end;
616 end;
618 function g_PlayerModel_GetAnim(ModelName: string; Anim: Byte; var _Anim, _Mask: TAnimation): Boolean;
619 var
620 a: Integer;
621 c: Boolean;
622 ID: DWORD;
623 begin
624 Result := False;
626 if PlayerModelsArray = nil then Exit;
627 for a := 0 to High(PlayerModelsArray) do
628 if PlayerModelsArray[a].Info.Name = ModelName then
629 with PlayerModelsArray[a] do
630 begin
631 if Anim in [A_STAND, A_WALK] then c := True else c := False;
633 if not g_Frames_Get(ID, Info.Name+'_RIGHTANIM'+IntToStr(Anim)) then
634 if not g_Frames_Get(ID, Info.Name+'_LEFTANIM'+IntToStr(Anim)) then Exit;
636 _Anim := TAnimation.Create(ID, c, ModelSpeed[Anim]);
637 _Anim.Speed := ModelSpeed[Anim];
639 if not g_Frames_Get(ID, Info.Name+'_RIGHTANIM'+IntToStr(Anim)+'_MASK') then
640 if not g_Frames_Get(ID, Info.Name+'_LEFTANIM'+IntToStr(Anim)+'_MASK') then Exit;
642 _Mask := TAnimation.Create(ID, c, ModelSpeed[Anim]);
643 _Mask.Speed := ModelSpeed[Anim];
645 Break;
646 end;
648 Result := True;
649 end;
651 function g_PlayerModel_GetGibs(ModelName: string; var Gibs: TGibsArray): Boolean;
652 var
653 a, i, b: Integer;
654 c: Boolean;
655 begin
656 Result := False;
658 if PlayerModelsArray = nil then Exit;
659 if gGibsCount = 0 then Exit;
661 c := False;
663 SetLength(Gibs, gGibsCount);
665 for a := 0 to High(PlayerModelsArray) do
666 if PlayerModelsArray[a].Info.Name = ModelName then
667 begin
668 for i := 0 to High(Gibs) do
669 begin
670 if c and (Length(PlayerModelsArray[a].Gibs) = 1) then
671 begin
672 SetLength(Gibs, i);
673 Break;
674 end;
676 repeat
677 b := Random(Length(PlayerModelsArray[a].Gibs));
678 until not (PlayerModelsArray[a].Gibs[b].OnlyOne and c);
680 Gibs[i] := PlayerModelsArray[a].Gibs[b];
682 if Gibs[i].OnlyOne then c := True;
683 end;
685 Result := True;
686 Break;
687 end;
688 end;
690 function g_PlayerModel_GetNames(): SArray;
691 var
692 i: DWORD;
693 begin
694 Result := nil;
696 if PlayerModelsArray = nil then Exit;
698 for i := 0 to High(PlayerModelsArray) do
699 begin
700 SetLength(Result, Length(Result)+1);
701 Result[High(Result)] := PlayerModelsArray[i].Info.Name;
702 end;
703 end;
705 function g_PlayerModel_GetInfo(ModelName: string): TModelInfo;
706 var
707 a: Integer;
708 begin
709 FillChar(Result, SizeOf(Result), 0);
710 if PlayerModelsArray = nil then Exit;
712 for a := 0 to High(PlayerModelsArray) do
713 if PlayerModelsArray[a].Info.Name = ModelName then
714 begin
715 Result := PlayerModelsArray[a].Info;
716 Break;
717 end;
718 end;
720 procedure g_PlayerModel_FreeData();
721 var
722 i: DWORD;
723 a, b, c: Integer;
724 begin
725 for a := WP_FIRST + 1 to WP_LAST do
726 for b := W_POS_NORMAL to W_POS_DOWN do
727 for c := W_ACT_NORMAL to W_ACT_FIRE do
728 e_DeleteTexture(WeaponID[a][b][c]);
730 e_WriteLog('Releasing models...', MSG_NOTIFY);
732 if PlayerModelsArray = nil then Exit;
734 for i := 0 to High(PlayerModelsArray) do
735 with PlayerModelsArray[i] do
736 begin
737 for a := A_STAND to A_LAST do
738 begin
739 g_Frames_DeleteByName(Info.Name+'_LEFTANIM'+IntToStr(a));
740 g_Frames_DeleteByName(Info.Name+'_LEFTANIM'+IntToStr(a)+'_MASK');
741 g_Frames_DeleteByName(Info.Name+'_RIGHTANIM'+IntToStr(a));
742 g_Frames_DeleteByName(Info.Name+'_RIGHTANIM'+IntToStr(a)+'_MASK');
743 end;
745 if PainSounds <> nil then
746 for b := 0 to High(PainSounds) do
747 e_DeleteSound(PainSounds[b].ID);
749 if DieSounds <> nil then
750 for b := 0 to High(DieSounds) do
751 e_DeleteSound(DieSounds[b].ID);
753 if Gibs <> nil then
754 for b := 0 to High(Gibs) do
755 begin
756 e_DeleteTexture(Gibs[b].ID);
757 e_DeleteTexture(Gibs[b].MaskID);
758 end;
759 end;
761 PlayerModelsArray := nil;
762 end;
764 { TPlayerModel }
766 procedure TPlayerModel.ChangeAnimation(Animation: Byte; Force: Boolean = False);
767 begin
768 if not Force then if FCurrentAnimation = Animation then Exit;
770 FCurrentAnimation := Animation;
772 if (FDirection = D_LEFT) and
773 (FAnim[D_LEFT][FCurrentAnimation] <> nil) and
774 (FMaskAnim[D_LEFT][FCurrentAnimation] <> nil) then
775 begin
776 FAnim[D_LEFT][FCurrentAnimation].Reset;
777 FMaskAnim[D_LEFT][FCurrentAnimation].Reset;
778 end
779 else
780 begin
781 FAnim[D_RIGHT][FCurrentAnimation].Reset;
782 FMaskAnim[D_RIGHT][FCurrentAnimation].Reset;
783 end;
784 end;
786 destructor TPlayerModel.Destroy();
787 var
788 a: Byte;
789 begin
790 for a := A_STAND to A_LAST do
791 begin
792 FAnim[D_LEFT][a].Free();
793 FMaskAnim[D_LEFT][a].Free();
794 FAnim[D_RIGHT][a].Free();
795 FMaskAnim[D_RIGHT][a].Free();
796 end;
798 inherited;
799 end;
801 procedure TPlayerModel.Draw(X, Y: Integer; Alpha: Byte = 0);
802 var
803 Mirror: TMirrorType;
804 pos, act: Byte;
805 p: TPoint;
806 begin
807 // Ôëàãè:
808 if Direction = D_LEFT then
809 Mirror := M_NONE
810 else
811 Mirror := M_HORIZONTAL;
813 if (FFlag <> FLAG_NONE) and (FFlagAnim <> nil) and
814 (not (FCurrentAnimation in [A_DIE1, A_DIE2])) then
815 begin
816 p.X := IfThen(Direction = D_LEFT,
817 FLAG_BASEPOINT.X,
818 64-FLAG_BASEPOINT.X);
819 p.Y := FLAG_BASEPOINT.Y;
821 FFlagAnim.DrawEx(X+IfThen(Direction = D_LEFT, FFlagPoint.X-1, 2*FLAG_BASEPOINT.X-FFlagPoint.X+1)-FLAG_BASEPOINT.X,
822 Y+FFlagPoint.Y-FLAG_BASEPOINT.Y+1, Mirror, p,
823 IfThen(FDirection = D_RIGHT, FFlagAngle, -FFlagAngle));
824 end;
826 // Îðóæèå:
827 if Direction = D_RIGHT then
828 Mirror := M_NONE
829 else
830 Mirror := M_HORIZONTAL;
832 if FDrawWeapon and
833 (not (FCurrentAnimation in [A_DIE1, A_DIE2, A_PAIN])) and
834 (FCurrentWeapon in [WP_FIRST + 1..WP_LAST]) then
835 begin
836 if FCurrentAnimation in [A_SEEUP, A_ATTACKUP] then
837 pos := W_POS_UP
838 else
839 if FCurrentAnimation in [A_SEEDOWN, A_ATTACKDOWN] then
840 pos := W_POS_DOWN
841 else
842 pos := W_POS_NORMAL;
844 if (FCurrentAnimation in [A_ATTACK, A_ATTACKUP, A_ATTACKDOWN]) or
845 FFire then
846 act := W_ACT_FIRE
847 else
848 act := W_ACT_NORMAL;
850 if Alpha < 201 then
851 e_Draw(WeaponID[FCurrentWeapon][pos][act],
852 X+FWeaponPoints[FCurrentWeapon, FCurrentAnimation, FDirection,
853 FAnim[D_RIGHT][FCurrentAnimation].CurrentFrame].X,
854 Y+FWeaponPoints[FCurrentWeapon, FCurrentAnimation, FDirection,
855 FAnim[D_RIGHT][FCurrentAnimation].CurrentFrame].Y,
856 0, True, False, Mirror);
857 end;
859 // Ìîäåëü:
860 if (FDirection = D_LEFT) and
861 (FAnim[D_LEFT][FCurrentAnimation] <> nil) then
862 begin
863 FAnim[D_LEFT][FCurrentAnimation].Alpha := Alpha;
864 FAnim[D_LEFT][FCurrentAnimation].Draw(X, Y, M_NONE);
865 end
866 else
867 begin
868 FAnim[D_RIGHT][FCurrentAnimation].Alpha := Alpha;
869 FAnim[D_RIGHT][FCurrentAnimation].Draw(X, Y, Mirror);
870 end;
872 // Ìàñêà ìîäåëè:
873 e_Colors := FColor;
875 if (FDirection = D_LEFT) and
876 (FMaskAnim[D_LEFT][FCurrentAnimation] <> nil) then
877 begin
878 FMaskAnim[D_LEFT][FCurrentAnimation].Alpha := Alpha;
879 FMaskAnim[D_LEFT][FCurrentAnimation].Draw(X, Y, M_NONE);
880 end
881 else
882 begin
883 FMaskAnim[D_RIGHT][FCurrentAnimation].Alpha := Alpha;
884 FMaskAnim[D_RIGHT][FCurrentAnimation].Draw(X, Y, Mirror);
885 end;
887 e_Colors.R := 255;
888 e_Colors.G := 255;
889 e_Colors.B := 255;
890 end;
892 function TPlayerModel.GetCurrentAnimation: TAnimation;
893 begin
894 if (FDirection = D_LEFT) and (FAnim[D_LEFT][FCurrentAnimation] <> nil) then
895 Result := FAnim[D_LEFT][FCurrentAnimation]
896 else
897 Result := FAnim[D_RIGHT][FCurrentAnimation];
898 end;
900 function TPlayerModel.GetCurrentAnimationMask: TAnimation;
901 begin
902 if (FDirection = D_LEFT) and (FMaskAnim[D_LEFT][FCurrentAnimation] <> nil) then
903 Result := FMaskAnim[D_LEFT][FCurrentAnimation]
904 else
905 Result := FMaskAnim[D_RIGHT][FCurrentAnimation];
906 end;
908 function TPlayerModel.PlaySound(SoundType, Level: Byte; X, Y: Integer): Boolean;
909 var
910 TempArray: array of DWORD;
911 a: Integer;
912 begin
913 Result := False;
914 SetLength(TempArray, 0);
916 if SoundType = MODELSOUND_PAIN then
917 begin
918 if FPainSounds = nil then Exit;
920 for a := 0 to High(FPainSounds) do
921 if FPainSounds[a].Level = Level then
922 begin
923 SetLength(TempArray, Length(TempArray)+1);
924 TempArray[High(TempArray)] := FPainSounds[a].ID;
925 end;
926 end
927 else
928 begin
929 if (Level in [2, 3, 5]) and (FSlopSound > 0) then
930 begin
931 g_Sound_PlayExAt('SOUND_MONSTER_SLOP', X, Y);
932 if FSlopSound = 1 then
933 begin
934 Result := True;
935 Exit;
936 end;
937 end;
938 if FDieSounds = nil then Exit;
940 for a := 0 to High(FDieSounds) do
941 if FDieSounds[a].Level = Level then
942 begin
943 SetLength(TempArray, Length(TempArray)+1);
944 TempArray[High(TempArray)] := FDieSounds[a].ID;
945 end;
946 if (TempArray = nil) and (Level = 5) then
947 begin
948 g_Sound_PlayExAt('SOUND_MONSTER_SLOP', X, Y);
949 Result := True;
950 Exit;
951 end;
952 end;
954 if TempArray = nil then Exit;
956 g_Sound_PlayAt(TempArray[Random(Length(TempArray))], X, Y);
958 Result := True;
959 end;
961 procedure TPlayerModel.SetColor(Red, Green, Blue: Byte);
962 begin
963 FColor.R := Red;
964 FColor.G := Green;
965 FColor.B := Blue;
966 end;
968 procedure TPlayerModel.SetFire(Fire: Boolean);
969 begin
970 FFire := Fire;
972 if FFire then FFireCounter := FAnim[D_RIGHT, A_ATTACK].Speed*FAnim[D_RIGHT, A_ATTACK].TotalFrames
973 else FFireCounter := 0;
974 end;
976 procedure TPlayerModel.SetFlag(Flag: Byte);
977 var
978 id: DWORD;
979 begin
980 FFlag := Flag;
982 FFlagAnim.Free();
983 FFlagAnim := nil;
985 case Flag of
986 FLAG_RED: g_Frames_Get(id, 'FRAMES_FLAG_RED');
987 FLAG_BLUE: g_Frames_Get(id, 'FRAMES_FLAG_BLUE');
988 else Exit;
989 end;
991 FFlagAnim := TAnimation.Create(id, True, 8);
992 end;
994 procedure TPlayerModel.SetWeapon(Weapon: Byte);
995 begin
996 FCurrentWeapon := Weapon;
997 end;
999 procedure TPlayerModel.Update();
1000 begin
1001 if (FDirection = D_LEFT) and (FAnim[D_LEFT][FCurrentAnimation] <> nil) then
1002 FAnim[D_LEFT][FCurrentAnimation].Update else FAnim[D_RIGHT][FCurrentAnimation].Update;
1004 if (FDirection = D_LEFT) and (FMaskAnim[D_LEFT][FCurrentAnimation] <> nil) then
1005 FMaskAnim[D_LEFT][FCurrentAnimation].Update else FMaskAnim[D_RIGHT][FCurrentAnimation].Update;
1007 if FFlagAnim <> nil then FFlagAnim.Update;
1009 if FFireCounter > 0 then Dec(FFireCounter) else FFire := False;
1010 end;
1012 end.