DEADSOFTWARE

load extended model animations if present
[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 if not (g_Frames_CreateWAD(nil, aname,
372 prefix+config.ReadStr(AnimNames[b], 'resource', ''),
373 64, 64, config.ReadInt(AnimNames[b], 'frames', 1),
374 config.ReadBool(AnimNames[b], 'backanim', False)) and
375 g_Frames_CreateWAD(nil, aname+'_MASK',
376 prefix+config.ReadStr(AnimNames[b], 'mask', ''),
377 64, 64, config.ReadInt(AnimNames[b], 'frames', 1),
378 config.ReadBool(AnimNames[b], 'backanim', False))) then
379 begin
380 if b <= A_LASTBASE then
381 begin
382 config.Free();
383 WAD.Free();
384 Exit;
385 end
386 else
387 begin
388 ExtAnimFromBaseAnim(s, b);
389 continue;
390 end;
391 end;
393 for aa := WP_FIRST + 1 to WP_LAST do
394 for bb := A_STAND to A_LAST do
395 for cc := D_LEFT to D_RIGHT do
396 begin
397 f := config.ReadInt(AnimNames[bb], 'frames', 1);
398 if config.ReadBool(AnimNames[bb], 'backanim', False) then
399 if f > 2 then f := 2*f-2;
400 SetLength(PlayerModelsArray[ID].WeaponPoints[aa, bb, cc], f);
401 end;
403 if (config.ReadStr(AnimNames[b], 'resource2', '') <> '') and
404 (config.ReadStr(AnimNames[b], 'mask2', '') <> '') then
405 begin
406 aname := s+'_LEFTANIM'+IntToStr(b);
407 g_Frames_CreateWAD(nil, aname,
408 prefix+config.ReadStr(AnimNames[b], 'resource2', ''),
409 64, 64, config.ReadInt(AnimNames[b], 'frames', 1),
410 config.ReadBool(AnimNames[b], 'backanim', False));
412 g_Frames_CreateWAD(nil, aname+'_MASK',
413 prefix+config.ReadStr(AnimNames[b], 'mask2', ''),
414 64, 64, config.ReadInt(AnimNames[b], 'frames', 1),
415 config.ReadBool(AnimNames[b], 'backanim', False));
416 end;
418 PlayerModelsArray[ID].ModelSpeed[b] := Max(1, config.ReadInt(AnimNames[b], 'waitcount', 1) div 3);
419 end;
421 with PlayerModelsArray[ID], config do
422 begin
423 prefix := FileName+':SOUNDS\';
425 a := 1;
426 repeat
427 s := config.ReadStr('Sound', 'pain'+IntToStr(a), '');
428 if s <> '' then
429 begin
430 SetLength(PainSounds, Length(PainSounds)+1);
431 g_Sound_CreateWAD(PainSounds[High(PainSounds)].ID, prefix+s);
432 PainSounds[High(PainSounds)].Level := config.ReadInt('Sound', 'painlevel'+IntToStr(a), 1);
433 end;
434 a := a+1;
435 until s = '';
437 a := 1;
438 repeat
439 s := config.ReadStr('Sound', 'die'+IntToStr(a), '');
440 if s <> '' then
441 begin
442 SetLength(DieSounds, Length(DieSounds)+1);
443 g_Sound_CreateWAD(DieSounds[High(DieSounds)].ID, prefix+s);
444 DieSounds[High(DieSounds)].Level := config.ReadInt('Sound', 'dielevel'+IntToStr(a), 1);
445 end;
446 a := a+1;
447 until s = '';
449 SlopSound := Min(Max(config.ReadInt('Sound', 'slop', 0), 0), 2);
451 SetLength(Gibs, ReadInt('Gibs', 'count', 0));
453 if (Gibs <> nil) and
454 (WAD.GetResource('TEXTURES/'+config.ReadStr('Gibs', 'resource', 'GIBS'), pData, lenpd)) and
455 (WAD.GetResource('TEXTURES/'+config.ReadStr('Gibs', 'mask', 'GIBSMASK'), pData2, lenpd2)) then
456 begin
457 for a := 0 to High(Gibs) do
458 if e_CreateTextureMemEx(pData, lenpd, Gibs[a].ID, a*32, 0, 32, 32) and
459 e_CreateTextureMemEx(pData2, lenpd2, Gibs[a].MaskID, a*32, 0, 32, 32) then
460 begin
461 Gibs[a].Rect := e_GetTextureSize2(Gibs[a].ID);
462 with Gibs[a].Rect do
463 if Height > 3 then Height := Height-1-Random(2);
464 Gibs[a].OnlyOne := config.ReadInt('Gibs', 'once', -1) = a+1;
465 end;
467 FreeMem(pData);
468 FreeMem(pData2);
469 end;
471 ok := True;
472 for aa := WP_FIRST + 1 to WP_LAST do
473 for bb := A_STAND to A_LAST do
474 if not (bb in [A_DIE1, A_DIE2, A_PAIN]) then
475 begin
476 chk := GetWeapPoints(config.ReadStr(AnimNames[bb], WeapNames[aa]+'_points', ''), aa, bb, D_RIGHT,
477 config.ReadInt(AnimNames[bb], 'frames', 0),
478 config.ReadBool(AnimNames[bb], 'backanim', False),
479 WeaponPoints);
480 if ok and (not chk) and (aa = WEAPON_FLAMETHROWER) then
481 begin
482 // workaround for flamethrower
483 chk := GetWeapPoints(config.ReadStr(AnimNames[bb], WeapNames[WEAPON_PLASMA]+'_points', ''), aa, bb, D_RIGHT,
484 config.ReadInt(AnimNames[bb], 'frames', 0),
485 config.ReadBool(AnimNames[bb], 'backanim', False),
486 WeaponPoints);
487 if chk then
488 for f := 0 to High(WeaponPoints[aa, bb, D_RIGHT]) do
489 begin
490 case bb of
491 A_STAND, A_PAIN:
492 begin
493 Dec(WeaponPoints[aa, bb, D_RIGHT, f].X, 6);
494 Dec(WeaponPoints[aa, bb, D_RIGHT, f].Y, 8);
495 end;
496 A_WALKATTACK, A_WALK:
497 begin
498 Dec(WeaponPoints[aa, bb, D_RIGHT, f].X, 9);
499 Dec(WeaponPoints[aa, bb, D_RIGHT, f].Y, 9);
500 end;
501 A_ATTACK:
502 begin
503 Dec(WeaponPoints[aa, bb, D_RIGHT, f].X, 5);
504 Dec(WeaponPoints[aa, bb, D_RIGHT, f].Y, 8);
505 end;
506 A_WALKSEEUP, A_SEEUP:
507 begin
508 Dec(WeaponPoints[aa, bb, D_RIGHT, f].X, 5);
509 Dec(WeaponPoints[aa, bb, D_RIGHT, f].Y, 16);
510 end;
511 A_WALKSEEDOWN, A_SEEDOWN:
512 begin
513 Dec(WeaponPoints[aa, bb, D_RIGHT, f].X, 6);
514 Dec(WeaponPoints[aa, bb, D_RIGHT, f].Y, 5);
515 end;
516 A_WALKATTACKUP, A_ATTACKUP:
517 begin
518 Dec(WeaponPoints[aa, bb, D_RIGHT, f].X, 5);
519 Dec(WeaponPoints[aa, bb, D_RIGHT, f].Y, 16);
520 end;
521 A_WALKATTACKDOWN, A_ATTACKDOWN:
522 begin
523 Dec(WeaponPoints[aa, bb, D_RIGHT, f].X, 6);
524 Dec(WeaponPoints[aa, bb, D_RIGHT, f].Y, 4);
525 end;
526 end;
527 end;
528 end;
529 ok := ok and (chk or (bb > A_LASTBASE));
531 if not GetWeapPoints(config.ReadStr(AnimNames[bb], WeapNames[aa]+'2_points', ''), aa, bb, D_LEFT,
532 config.ReadInt(AnimNames[bb], 'frames', 0),
533 config.ReadBool(AnimNames[bb], 'backanim', False),
534 WeaponPoints) then
535 for f := 0 to High(WeaponPoints[aa, bb, D_RIGHT]) do
536 begin
537 WeaponPoints[aa, bb, D_LEFT, f].X := -WeaponPoints[aa, bb, D_RIGHT, f].X;
538 WeaponPoints[aa, bb, D_LEFT, f].Y := WeaponPoints[aa, bb, D_RIGHT, f].Y;
539 end;
541 if not ok then Break;
542 end;
543 {if ok then g_Console_Add(Info.Name+' weapon points ok')
544 else g_Console_Add(Info.Name+' weapon points fail');}
545 Info.HaveWeapon := ok;
547 s := config.ReadStr('Model', 'flag_point', '');
548 if not GetPoint(s, FlagPoint) then FlagPoint := FLAG_DEFPOINT;
550 FlagAngle := config.ReadInt('Model', 'flag_angle', FLAG_DEFANGLE);
551 end;
553 config.Free();
554 WAD.Free();
556 Result := True;
557 end;
559 function g_PlayerModel_Get(ModelName: String): TPlayerModel;
560 var
561 a: Integer;
562 b: Byte;
563 ID, ID2: DWORD;
564 begin
565 Result := nil;
567 if PlayerModelsArray = nil then Exit;
569 for a := 0 to High(PlayerModelsArray) do
570 if AnsiLowerCase(PlayerModelsArray[a].Info.Name) = AnsiLowerCase(ModelName) then
571 begin
572 Result := TPlayerModel.Create;
574 with PlayerModelsArray[a] do
575 begin
576 Result.FName := Info.Name;
578 for b := A_STAND to A_LAST do
579 begin
580 if not (g_Frames_Get(ID, Info.Name+'_RIGHTANIM'+IntToStr(b)) and
581 g_Frames_Get(ID2, Info.Name+'_RIGHTANIM'+IntToStr(b)+'_MASK')) then
582 begin
583 Result.Free();
584 Result := nil;
585 Exit;
586 end;
588 Result.FAnim[D_RIGHT][b] := TAnimation.Create(ID, b in [A_STAND, A_WALK], ModelSpeed[b]);
590 Result.FMaskAnim[D_RIGHT][b] := TAnimation.Create(ID2, b in [A_STAND, A_WALK], ModelSpeed[b]);
592 if g_Frames_Exists(Info.Name+'_LEFTANIM'+IntToStr(b)) and
593 g_Frames_Exists(Info.Name+'_LEFTANIM'+IntToStr(b)+'_MASK') then
594 if g_Frames_Get(ID, Info.Name+'_LEFTANIM'+IntToStr(b)) and
595 g_Frames_Get(ID2, Info.Name+'_LEFTANIM'+IntToStr(b)+'_MASK') then
596 begin
597 Result.FAnim[D_LEFT][b] := TAnimation.Create(ID, b in [A_STAND, A_WALK], ModelSpeed[b]);
599 Result.FMaskAnim[D_LEFT][b] := TAnimation.Create(ID2, b in [A_STAND, A_WALK], ModelSpeed[b]);
600 end;
601 end;
603 Result.FPainSounds := PainSounds;
604 Result.FDieSounds := DieSounds;
605 Result.FSlopSound := SlopSound;
606 Result.FDrawWeapon := Info.HaveWeapon;
607 Result.FWeaponPoints := WeaponPoints;
609 Result.FFlagPoint := FlagPoint;
610 Result.FFlagAngle := FlagAngle;
612 Break;
613 end;
614 end;
615 end;
617 function g_PlayerModel_GetAnim(ModelName: string; Anim: Byte; var _Anim, _Mask: TAnimation): Boolean;
618 var
619 a: Integer;
620 c: Boolean;
621 ID: DWORD;
622 begin
623 Result := False;
625 if PlayerModelsArray = nil then Exit;
626 for a := 0 to High(PlayerModelsArray) do
627 if PlayerModelsArray[a].Info.Name = ModelName then
628 with PlayerModelsArray[a] do
629 begin
630 if Anim in [A_STAND, A_WALK] then c := True else c := False;
632 if not g_Frames_Get(ID, Info.Name+'_RIGHTANIM'+IntToStr(Anim)) then
633 if not g_Frames_Get(ID, Info.Name+'_LEFTANIM'+IntToStr(Anim)) then Exit;
635 _Anim := TAnimation.Create(ID, c, ModelSpeed[Anim]);
636 _Anim.Speed := ModelSpeed[Anim];
638 if not g_Frames_Get(ID, Info.Name+'_RIGHTANIM'+IntToStr(Anim)+'_MASK') then
639 if not g_Frames_Get(ID, Info.Name+'_LEFTANIM'+IntToStr(Anim)+'_MASK') then Exit;
641 _Mask := TAnimation.Create(ID, c, ModelSpeed[Anim]);
642 _Mask.Speed := ModelSpeed[Anim];
644 Break;
645 end;
647 Result := True;
648 end;
650 function g_PlayerModel_GetGibs(ModelName: string; var Gibs: TGibsArray): Boolean;
651 var
652 a, i, b: Integer;
653 c: Boolean;
654 begin
655 Result := False;
657 if PlayerModelsArray = nil then Exit;
658 if gGibsCount = 0 then Exit;
660 c := False;
662 SetLength(Gibs, gGibsCount);
664 for a := 0 to High(PlayerModelsArray) do
665 if PlayerModelsArray[a].Info.Name = ModelName then
666 begin
667 for i := 0 to High(Gibs) do
668 begin
669 if c and (Length(PlayerModelsArray[a].Gibs) = 1) then
670 begin
671 SetLength(Gibs, i);
672 Break;
673 end;
675 repeat
676 b := Random(Length(PlayerModelsArray[a].Gibs));
677 until not (PlayerModelsArray[a].Gibs[b].OnlyOne and c);
679 Gibs[i] := PlayerModelsArray[a].Gibs[b];
681 if Gibs[i].OnlyOne then c := True;
682 end;
684 Result := True;
685 Break;
686 end;
687 end;
689 function g_PlayerModel_GetNames(): SArray;
690 var
691 i: DWORD;
692 begin
693 Result := nil;
695 if PlayerModelsArray = nil then Exit;
697 for i := 0 to High(PlayerModelsArray) do
698 begin
699 SetLength(Result, Length(Result)+1);
700 Result[High(Result)] := PlayerModelsArray[i].Info.Name;
701 end;
702 end;
704 function g_PlayerModel_GetInfo(ModelName: string): TModelInfo;
705 var
706 a: Integer;
707 begin
708 FillChar(Result, SizeOf(Result), 0);
709 if PlayerModelsArray = nil then Exit;
711 for a := 0 to High(PlayerModelsArray) do
712 if PlayerModelsArray[a].Info.Name = ModelName then
713 begin
714 Result := PlayerModelsArray[a].Info;
715 Break;
716 end;
717 end;
719 procedure g_PlayerModel_FreeData();
720 var
721 i: DWORD;
722 a, b, c: Integer;
723 begin
724 for a := WP_FIRST + 1 to WP_LAST do
725 for b := W_POS_NORMAL to W_POS_DOWN do
726 for c := W_ACT_NORMAL to W_ACT_FIRE do
727 e_DeleteTexture(WeaponID[a][b][c]);
729 e_WriteLog('Releasing models...', MSG_NOTIFY);
731 if PlayerModelsArray = nil then Exit;
733 for i := 0 to High(PlayerModelsArray) do
734 with PlayerModelsArray[i] do
735 begin
736 for a := A_STAND to A_LAST do
737 begin
738 g_Frames_DeleteByName(Info.Name+'_LEFTANIM'+IntToStr(a));
739 g_Frames_DeleteByName(Info.Name+'_LEFTANIM'+IntToStr(a)+'_MASK');
740 g_Frames_DeleteByName(Info.Name+'_RIGHTANIM'+IntToStr(a));
741 g_Frames_DeleteByName(Info.Name+'_RIGHTANIM'+IntToStr(a)+'_MASK');
742 end;
744 if PainSounds <> nil then
745 for b := 0 to High(PainSounds) do
746 e_DeleteSound(PainSounds[b].ID);
748 if DieSounds <> nil then
749 for b := 0 to High(DieSounds) do
750 e_DeleteSound(DieSounds[b].ID);
752 if Gibs <> nil then
753 for b := 0 to High(Gibs) do
754 begin
755 e_DeleteTexture(Gibs[b].ID);
756 e_DeleteTexture(Gibs[b].MaskID);
757 end;
758 end;
760 PlayerModelsArray := nil;
761 end;
763 { TPlayerModel }
765 procedure TPlayerModel.ChangeAnimation(Animation: Byte; Force: Boolean = False);
766 begin
767 if not Force then if FCurrentAnimation = Animation then Exit;
769 FCurrentAnimation := Animation;
771 if (FDirection = D_LEFT) and
772 (FAnim[D_LEFT][FCurrentAnimation] <> nil) and
773 (FMaskAnim[D_LEFT][FCurrentAnimation] <> nil) then
774 begin
775 FAnim[D_LEFT][FCurrentAnimation].Reset;
776 FMaskAnim[D_LEFT][FCurrentAnimation].Reset;
777 end
778 else
779 begin
780 FAnim[D_RIGHT][FCurrentAnimation].Reset;
781 FMaskAnim[D_RIGHT][FCurrentAnimation].Reset;
782 end;
783 end;
785 destructor TPlayerModel.Destroy();
786 var
787 a: Byte;
788 begin
789 for a := A_STAND to A_LAST do
790 begin
791 FAnim[D_LEFT][a].Free();
792 FMaskAnim[D_LEFT][a].Free();
793 FAnim[D_RIGHT][a].Free();
794 FMaskAnim[D_RIGHT][a].Free();
795 end;
797 inherited;
798 end;
800 procedure TPlayerModel.Draw(X, Y: Integer; Alpha: Byte = 0);
801 var
802 Mirror: TMirrorType;
803 pos, act: Byte;
804 p: TPoint;
805 begin
806 // Ôëàãè:
807 if Direction = D_LEFT then
808 Mirror := M_NONE
809 else
810 Mirror := M_HORIZONTAL;
812 if (FFlag <> FLAG_NONE) and (FFlagAnim <> nil) and
813 (not (FCurrentAnimation in [A_DIE1, A_DIE2])) then
814 begin
815 p.X := IfThen(Direction = D_LEFT,
816 FLAG_BASEPOINT.X,
817 64-FLAG_BASEPOINT.X);
818 p.Y := FLAG_BASEPOINT.Y;
820 FFlagAnim.DrawEx(X+IfThen(Direction = D_LEFT, FFlagPoint.X-1, 2*FLAG_BASEPOINT.X-FFlagPoint.X+1)-FLAG_BASEPOINT.X,
821 Y+FFlagPoint.Y-FLAG_BASEPOINT.Y+1, Mirror, p,
822 IfThen(FDirection = D_RIGHT, FFlagAngle, -FFlagAngle));
823 end;
825 // Îðóæèå:
826 if Direction = D_RIGHT then
827 Mirror := M_NONE
828 else
829 Mirror := M_HORIZONTAL;
831 if FDrawWeapon and
832 (not (FCurrentAnimation in [A_DIE1, A_DIE2, A_PAIN])) and
833 (FCurrentWeapon in [WP_FIRST + 1..WP_LAST]) then
834 begin
835 if FCurrentAnimation in [A_SEEUP, A_ATTACKUP] then
836 pos := W_POS_UP
837 else
838 if FCurrentAnimation in [A_SEEDOWN, A_ATTACKDOWN] then
839 pos := W_POS_DOWN
840 else
841 pos := W_POS_NORMAL;
843 if (FCurrentAnimation in [A_ATTACK, A_ATTACKUP, A_ATTACKDOWN]) or
844 FFire then
845 act := W_ACT_FIRE
846 else
847 act := W_ACT_NORMAL;
849 if Alpha < 201 then
850 e_Draw(WeaponID[FCurrentWeapon][pos][act],
851 X+FWeaponPoints[FCurrentWeapon, FCurrentAnimation, FDirection,
852 FAnim[D_RIGHT][FCurrentAnimation].CurrentFrame].X,
853 Y+FWeaponPoints[FCurrentWeapon, FCurrentAnimation, FDirection,
854 FAnim[D_RIGHT][FCurrentAnimation].CurrentFrame].Y,
855 0, True, False, Mirror);
856 end;
858 // Ìîäåëü:
859 if (FDirection = D_LEFT) and
860 (FAnim[D_LEFT][FCurrentAnimation] <> nil) then
861 begin
862 FAnim[D_LEFT][FCurrentAnimation].Alpha := Alpha;
863 FAnim[D_LEFT][FCurrentAnimation].Draw(X, Y, M_NONE);
864 end
865 else
866 begin
867 FAnim[D_RIGHT][FCurrentAnimation].Alpha := Alpha;
868 FAnim[D_RIGHT][FCurrentAnimation].Draw(X, Y, Mirror);
869 end;
871 // Ìàñêà ìîäåëè:
872 e_Colors := FColor;
874 if (FDirection = D_LEFT) and
875 (FMaskAnim[D_LEFT][FCurrentAnimation] <> nil) then
876 begin
877 FMaskAnim[D_LEFT][FCurrentAnimation].Alpha := Alpha;
878 FMaskAnim[D_LEFT][FCurrentAnimation].Draw(X, Y, M_NONE);
879 end
880 else
881 begin
882 FMaskAnim[D_RIGHT][FCurrentAnimation].Alpha := Alpha;
883 FMaskAnim[D_RIGHT][FCurrentAnimation].Draw(X, Y, Mirror);
884 end;
886 e_Colors.R := 255;
887 e_Colors.G := 255;
888 e_Colors.B := 255;
889 end;
891 function TPlayerModel.GetCurrentAnimation: TAnimation;
892 begin
893 if (FDirection = D_LEFT) and (FAnim[D_LEFT][FCurrentAnimation] <> nil) then
894 Result := FAnim[D_LEFT][FCurrentAnimation]
895 else
896 Result := FAnim[D_RIGHT][FCurrentAnimation];
897 end;
899 function TPlayerModel.GetCurrentAnimationMask: TAnimation;
900 begin
901 if (FDirection = D_LEFT) and (FMaskAnim[D_LEFT][FCurrentAnimation] <> nil) then
902 Result := FMaskAnim[D_LEFT][FCurrentAnimation]
903 else
904 Result := FMaskAnim[D_RIGHT][FCurrentAnimation];
905 end;
907 function TPlayerModel.PlaySound(SoundType, Level: Byte; X, Y: Integer): Boolean;
908 var
909 TempArray: array of DWORD;
910 a: Integer;
911 begin
912 Result := False;
913 SetLength(TempArray, 0);
915 if SoundType = MODELSOUND_PAIN then
916 begin
917 if FPainSounds = nil then Exit;
919 for a := 0 to High(FPainSounds) do
920 if FPainSounds[a].Level = Level then
921 begin
922 SetLength(TempArray, Length(TempArray)+1);
923 TempArray[High(TempArray)] := FPainSounds[a].ID;
924 end;
925 end
926 else
927 begin
928 if (Level in [2, 3, 5]) and (FSlopSound > 0) then
929 begin
930 g_Sound_PlayExAt('SOUND_MONSTER_SLOP', X, Y);
931 if FSlopSound = 1 then
932 begin
933 Result := True;
934 Exit;
935 end;
936 end;
937 if FDieSounds = nil then Exit;
939 for a := 0 to High(FDieSounds) do
940 if FDieSounds[a].Level = Level then
941 begin
942 SetLength(TempArray, Length(TempArray)+1);
943 TempArray[High(TempArray)] := FDieSounds[a].ID;
944 end;
945 if (TempArray = nil) and (Level = 5) then
946 begin
947 g_Sound_PlayExAt('SOUND_MONSTER_SLOP', X, Y);
948 Result := True;
949 Exit;
950 end;
951 end;
953 if TempArray = nil then Exit;
955 g_Sound_PlayAt(TempArray[Random(Length(TempArray))], X, Y);
957 Result := True;
958 end;
960 procedure TPlayerModel.SetColor(Red, Green, Blue: Byte);
961 begin
962 FColor.R := Red;
963 FColor.G := Green;
964 FColor.B := Blue;
965 end;
967 procedure TPlayerModel.SetFire(Fire: Boolean);
968 begin
969 FFire := Fire;
971 if FFire then FFireCounter := FAnim[D_RIGHT, A_ATTACK].Speed*FAnim[D_RIGHT, A_ATTACK].TotalFrames
972 else FFireCounter := 0;
973 end;
975 procedure TPlayerModel.SetFlag(Flag: Byte);
976 var
977 id: DWORD;
978 begin
979 FFlag := Flag;
981 FFlagAnim.Free();
982 FFlagAnim := nil;
984 case Flag of
985 FLAG_RED: g_Frames_Get(id, 'FRAMES_FLAG_RED');
986 FLAG_BLUE: g_Frames_Get(id, 'FRAMES_FLAG_BLUE');
987 else Exit;
988 end;
990 FFlagAnim := TAnimation.Create(id, True, 8);
991 end;
993 procedure TPlayerModel.SetWeapon(Weapon: Byte);
994 begin
995 FCurrentWeapon := Weapon;
996 end;
998 procedure TPlayerModel.Update();
999 begin
1000 if (FDirection = D_LEFT) and (FAnim[D_LEFT][FCurrentAnimation] <> nil) then
1001 FAnim[D_LEFT][FCurrentAnimation].Update else FAnim[D_RIGHT][FCurrentAnimation].Update;
1003 if (FDirection = D_LEFT) and (FMaskAnim[D_LEFT][FCurrentAnimation] <> nil) then
1004 FMaskAnim[D_LEFT][FCurrentAnimation].Update else FMaskAnim[D_RIGHT][FCurrentAnimation].Update;
1006 if FFlagAnim <> nil then FFlagAnim.Update;
1008 if FFireCounter > 0 then Dec(FFireCounter) else FFire := False;
1009 end;
1011 end.