DEADSOFTWARE

Fix handicap specifying in the 'bot_addlist' console command
[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, version 3 of the License ONLY.
6 *
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.
11 *
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/>.
14 *)
15 {$INCLUDE ../shared/a_modes.inc}
16 {$M+}
17 unit g_playermodel;
19 interface
21 uses
22 {$IFDEF USE_MEMPOOL}mempool,{$ENDIF}
23 MAPDEF, g_textures, g_basic, g_weapons, e_graphics, utils, g_gfx,
24 ImagingTypes, Imaging, ImagingUtility;
26 const
27 A_STAND = 0;
28 A_WALK = 1;
29 A_DIE1 = 2;
30 A_DIE2 = 3;
31 A_ATTACK = 4;
32 A_SEEUP = 5;
33 A_SEEDOWN = 6;
34 A_ATTACKUP = 7;
35 A_ATTACKDOWN = 8;
36 A_PAIN = 9;
37 // EXTENDED
38 A_WALKATTACK = 10;
39 A_WALKSEEUP = 11;
40 A_WALKSEEDOWN = 12;
41 A_WALKATTACKUP = 13;
42 A_WALKATTACKDOWN = 14;
43 A_FISTSTAND = 15;
44 A_FISTWALK = 16;
45 A_FISTATTACK = 17;
46 A_FISTWALKATTACK = 18;
47 A_FISTSEEUP = 19;
48 A_FISTSEEDOWN = 20;
49 A_FISTATTACKUP = 21;
50 A_FISTATTACKDOWN = 22;
52 A_LASTBASE = A_PAIN;
53 A_LASTEXT = A_FISTATTACKDOWN;
54 A_LAST = A_LASTEXT;
56 MODELSOUND_PAIN = 0;
57 MODELSOUND_DIE = 1;
59 type
60 TModelInfo = record
61 Name: String;
62 Author: String;
63 Description: String;
64 HaveWeapon: Boolean;
65 end;
67 TModelBlood = record
68 R, G, B, Kind: Byte;
69 end;
71 TModelSound = record
72 ID: DWORD;
73 Level: Byte;
74 end;
76 TGibSprite = record
77 ID: DWORD;
78 MaskID: DWORD;
79 Rect: TRectWH;
80 OnlyOne: Boolean;
81 end;
83 TModelSoundArray = Array of TModelSound;
84 TGibsArray = Array of TGibSprite;
85 TWeaponPoints = Array [WP_FIRST + 1..WP_LAST] of
86 Array [A_STAND..A_LAST] of
87 Array [TDirection.D_LEFT..TDirection.D_RIGHT] of Array of TDFPoint;
89 TPlayerModel = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF}
90 private
91 FName: String;
92 FDirection: TDirection;
93 FColor: TRGB;
94 FBlood: TModelBlood;
95 FCurrentAnimation: Byte;
96 FAnim: Array [TDirection.D_LEFT..TDirection.D_RIGHT] of Array [A_STAND..A_LAST] of TAnimation;
97 FMaskAnim: Array [TDirection.D_LEFT..TDirection.D_RIGHT] of Array [A_STAND..A_LAST] of TAnimation;
98 FWeaponPoints: TWeaponPoints;
99 FPainSounds: TModelSoundArray;
100 FDieSounds: TModelSoundArray;
101 FSlopSound: Byte;
102 FCurrentWeapon: Byte;
103 FDrawWeapon: Boolean;
104 FFlag: Byte;
105 FFlagPoint: TDFPoint;
106 FFlagAngle: SmallInt;
107 FFlagAnim: TAnimation;
108 FFire: Boolean;
109 FFireCounter: Byte;
111 public
112 destructor Destroy(); override;
113 procedure ChangeAnimation(Animation: Byte; Force: Boolean = False);
114 function GetCurrentAnimation: TAnimation;
115 function GetCurrentAnimationMask: TAnimation;
116 procedure SetColor(Red, Green, Blue: Byte);
117 procedure SetWeapon(Weapon: Byte);
118 procedure SetFlag(Flag: Byte);
119 procedure SetFire(Fire: Boolean);
120 function PlaySound(SoundType, Level: Byte; X, Y: Integer): Boolean;
121 procedure Update();
122 procedure Draw(X, Y: Integer; Alpha: Byte = 0);
124 published
125 property Fire: Boolean read FFire;
126 property Direction: TDirection read FDirection write FDirection;
127 property Animation: Byte read FCurrentAnimation;
128 property Weapon: Byte read FCurrentWeapon;
129 property Name: String read FName;
131 public
132 property Color: TRGB read FColor write FColor;
133 property Blood: TModelBlood read FBlood;
134 end;
136 procedure g_PlayerModel_LoadData();
137 procedure g_PlayerModel_FreeData();
138 function g_PlayerModel_Load(FileName: String): Boolean;
139 function g_PlayerModel_GetNames(): SSArray;
140 function g_PlayerModel_GetInfo(ModelName: String): TModelInfo;
141 function g_PlayerModel_GetBlood(ModelName: String): TModelBlood;
142 function g_PlayerModel_Get(ModelName: String): TPlayerModel;
143 function g_PlayerModel_GetAnim(ModelName: String; Anim: Byte; var _Anim, _Mask: TAnimation): Boolean;
144 function g_PlayerModel_GetGibs(ModelName: String; var Gibs: TGibsArray): Boolean;
147 implementation
149 uses
150 {$INCLUDE ../nogl/noGLuses.inc}
151 g_main, g_sound, g_console, SysUtils, g_player, CONFIG,
152 e_sound, g_options, g_map, Math, e_log, wadreader;
154 type
155 TPlayerModelInfo = record
156 Info: TModelInfo;
157 ModelSpeed: Array [A_STAND..A_PAIN] of Byte;
158 FlagPoint: TDFPoint;
159 FlagAngle: SmallInt;
160 WeaponPoints: TWeaponPoints;
161 Gibs: TGibsArray;
162 PainSounds: TModelSoundArray;
163 DieSounds: TModelSoundArray;
164 SlopSound: Byte;
165 Blood: TModelBlood;
166 end;
168 const
169 W_POS_NORMAL = 0;
170 W_POS_UP = 1;
171 W_POS_DOWN = 2;
173 W_ACT_NORMAL = 0;
174 W_ACT_FIRE = 1;
176 FLAG_BASEPOINT: TDFPoint = (X:16; Y:43);
177 FLAG_DEFPOINT: TDFPoint = (X:32; Y:16);
178 FLAG_DEFANGLE = -20;
179 WEAPONBASE: Array [WP_FIRST + 1..WP_LAST] of TDFPoint =
180 ((X:8; Y:4), (X:8; Y:8), (X:16; Y:16), (X:16; Y:24),
181 (X:16; Y:16), (X:24; Y:24), (X:16; Y:16), (X:24; Y:24),
182 (X:16; Y:16), (X:8; Y:8));
184 AnimNames: Array [A_STAND..A_LASTEXT] of String =
185 ('StandAnim','WalkAnim','Die1Anim','Die2Anim','AttackAnim',
186 'SeeUpAnim','SeeDownAnim','AttackUpAnim','AttackDownAnim','PainAnim',
187 // EXTENDED
188 'WalkAttackAnim', 'WalkSeeUpAnim', 'WalkSeeDownAnim',
189 'WalkAttackUpAnim', 'WalkAttackDownAnim', 'FistStandAnim', 'FistWalkAnim',
190 'FistAttackAnim', 'FistWalkAttackAnim', 'FistSeeUpAnim', 'FistSeeDownAnim',
191 'FistAttackUpAnim', 'FistAttackDownAnim');
192 WeapNames: Array [WP_FIRST + 1..WP_LAST] of String =
193 ('csaw', 'hgun', 'sg', 'ssg', 'mgun', 'rkt', 'plz', 'bfg', 'spl', 'flm');
195 var
196 WeaponID: Array [WP_FIRST + 1..WP_LAST] of
197 Array [W_POS_NORMAL..W_POS_DOWN] of
198 Array [W_ACT_NORMAL..W_ACT_FIRE] of DWORD;
199 PlayerModelsArray: Array of TPlayerModelInfo;
201 procedure g_PlayerModel_LoadData();
202 var
203 a: Integer;
204 begin
205 for a := WP_FIRST + 1 to WP_LAST do
206 begin
207 g_Texture_CreateWAD(WeaponID[a][W_POS_NORMAL][W_ACT_NORMAL], GameWAD+':WEAPONS\'+UpperCase(WeapNames[a]));
208 g_Texture_CreateWAD(WeaponID[a][W_POS_NORMAL][W_ACT_FIRE], GameWAD+':WEAPONS\'+UpperCase(WeapNames[a])+'_FIRE');
209 g_Texture_CreateWAD(WeaponID[a][W_POS_UP][W_ACT_NORMAL], GameWAD+':WEAPONS\'+UpperCase(WeapNames[a])+'_UP');
210 g_Texture_CreateWAD(WeaponID[a][W_POS_UP][W_ACT_FIRE], GameWAD+':WEAPONS\'+UpperCase(WeapNames[a])+'_UP_FIRE');
211 g_Texture_CreateWAD(WeaponID[a][W_POS_DOWN][W_ACT_NORMAL], GameWAD+':WEAPONS\'+UpperCase(WeapNames[a])+'_DN');
212 g_Texture_CreateWAD(WeaponID[a][W_POS_DOWN][W_ACT_FIRE], GameWAD+':WEAPONS\'+UpperCase(WeapNames[a])+'_DN_FIRE');
213 end;
214 end;
216 function GetPoint(var str: String; var point: TDFPoint): Boolean;
217 var
218 a, x, y: Integer;
219 s: String;
220 begin
221 Result := False;
222 x := 0;
223 y := 0;
225 str := Trim(str);
226 if Length(str) < 3 then
227 Exit;
229 for a := 1 to Length(str) do
230 if (str[a] = ',') or (a = Length(str)) then
231 begin
232 s := Copy(str, 1, a);
233 if s[Length(s)] = ',' then
234 SetLength(s, Length(s)-1);
235 Delete(str, 1, a);
237 if (Sscanf(s, '%d:%d', [@x, @y]) < 2) or
238 (x < -64) or (x > 128) or
239 (y < -64) or (y > 128) then
240 Exit;
242 point.X := x;
243 point.Y := y;
245 Break;
246 end;
248 Result := True;
249 end;
251 function GetWeapPoints(str: String; weapon: Byte; anim: Byte; dir: TDirection;
252 frames: Word; backanim: Boolean; var wpoints: TWeaponPoints): Boolean;
253 var
254 a, b, h: Integer;
255 begin
256 Result := False;
258 if frames = 0 then
259 Exit;
261 backanim := backanim and (frames > 2);
263 for a := 1 to frames do
264 begin
265 if not GetPoint(str, wpoints[weapon, anim, dir, a-1]) then
266 Exit;
268 with wpoints[weapon, anim, dir, a-1] do
269 begin
270 X := X - WEAPONBASE[weapon].X;
271 Y := Y - WEAPONBASE[weapon].Y;
272 if dir = TDirection.D_LEFT then
273 X := -X;
274 end;
275 end;
277 h := High(wpoints[weapon, anim, dir]);
278 if backanim then
279 for b := h downto frames do
280 wpoints[weapon, anim, dir, b] := wpoints[weapon, anim, dir, h-b+1];
282 Result := True;
283 end;
285 procedure ExtAnimFromBaseAnim(MName: String; AIdx: Integer);
286 const
287 CopyAnim: array [A_LASTBASE+1..A_LASTEXT] of Integer = (
288 A_WALK, A_WALK, A_WALK, A_WALK, A_WALK,
289 A_STAND, A_WALK, A_ATTACK, A_WALK, A_SEEUP, A_SEEDOWN,
290 A_ATTACKUP, A_ATTACKDOWN
291 );
292 var
293 OIdx, W, I: Integer;
294 D: TDirection;
295 AName, OName: String;
296 begin
297 // HACK: shitty workaround to duplicate base animations
298 // in place of extended, replace with something better later
300 Assert((AIdx > A_LASTBASE) and (AIdx <= A_LASTEXT));
301 OIdx := CopyAnim[AIdx];
303 AName := MName + '_RIGHTANIM' + IntToStr(AIdx);
304 OName := MName + '_RIGHTANIM' + IntToStr(OIdx);
305 Assert(g_Frames_Dup(AName, OName));
306 Assert(g_Frames_Dup(AName + '_MASK', OName + '_MASK'));
307 AName := MName + '_LEFTANIM' + IntToStr(AIdx);
308 OName := MName + '_LEFTANIM' + IntToStr(OIdx);
309 if g_Frames_Exists(AName) then
310 begin
311 g_Frames_Dup(AName, OName);
312 g_Frames_Dup(AName + '_MASK', OName + '_MASK');
313 end;
315 with PlayerModelsArray[High(PlayerModelsArray)] do
316 begin
317 for W := WP_FIRST + 1 to WP_LAST do
318 begin
319 for D := TDirection.D_LEFT to TDirection.D_RIGHT do
320 begin
321 SetLength(WeaponPoints[W, AIdx, D], Length(WeaponPoints[W, OIdx, D]));
322 for I := 0 to High(WeaponPoints[W, AIdx, D]) do
323 WeaponPoints[W, AIdx, D, I] := WeaponPoints[W, OIdx, D, I]
324 end;
325 end;
326 end;
327 end;
329 function g_PlayerModel_CalcGibSize (pData: Pointer; dataSize, x, y, w, h: Integer): TRectWH;
330 var i, j: Integer; done: Boolean; img: TImageData;
332 function IsVoid (i, j: Integer): Boolean;
333 begin
334 result := Byte((PByte(img.bits) + (y+j)*img.width*4 + (x+i)*4 + 3)^) = 0
335 end;
337 begin
338 InitImage(img);
339 assert(LoadImageFromMemory(pData, dataSize, img));
341 (* trace x from right to left *)
342 done := false; i := 0;
343 while not done and (i < w) do
344 begin
345 j := 0;
346 while (j < h) and IsVoid(i, j) do inc(j);
347 done := (j < h) and (IsVoid(i, j) = false);
348 result.x := i;
349 inc(i);
350 end;
352 (* trace y from up to down *)
353 done := false; j := 0;
354 while not done and (j < h) do
355 begin
356 i := 0;
357 while (i < w) and IsVoid(i, j) do inc(i);
358 done := (i < w) and (IsVoid(i, j) = false);
359 result.y := j;
360 inc(j);
361 end;
363 (* trace x from right to left *)
364 done := false; i := w - 1;
365 while not done and (i >= 0) do
366 begin
367 j := 0;
368 while (j < h) and IsVoid(i, j) do inc(j);
369 done := (j < h) and (IsVoid(i, j) = false);
370 result.width := i - result.x + 1;
371 dec(i);
372 end;
374 (* trace y from down to up *)
375 done := false; j := h - 1;
376 while not done and (j >= 0) do
377 begin
378 i := 0;
379 while (i < w) and IsVoid(i, j) do inc(i);
380 done := (i < w) and (IsVoid(i, j) = false);
381 result.height := j - result.y + 1;
382 dec(j);
383 end;
385 FreeImage(img);
386 end;
388 function g_PlayerModel_Load(FileName: string): Boolean;
389 var
390 ID: DWORD;
391 a, b, len, lenpd, lenpd2, aa, bb, f: Integer;
392 cc: TDirection;
393 config: TConfig;
394 pData, pData2: Pointer;
395 WAD: TWADFile;
396 s, aname: string;
397 prefix: string;
398 ok, chk: Boolean;
399 begin
400 e_WriteLog(Format('Loading player model "%s"...', [FileName]), TMsgType.Notify);
402 Result := False;
404 WAD := TWADFile.Create;
405 WAD.ReadFile(FileName);
407 if {WAD.GetLastError <> DFWAD_NOERROR} not WAD.isOpen then
408 begin
409 WAD.Free();
410 Exit;
411 end;
413 if not WAD.GetResource('TEXT/MODEL', pData, len) then
414 begin
415 WAD.Free();
416 Exit;
417 end;
419 config := TConfig.CreateMem(pData, len);
420 FreeMem(pData);
422 s := config.ReadStr('Model', 'name', '');
423 if s = '' then
424 begin
425 config.Free();
426 WAD.Free();
427 Exit;
428 end;
430 SetLength(PlayerModelsArray, Length(PlayerModelsArray)+1);
431 ID := High(PlayerModelsArray);
433 prefix := FileName+':TEXTURES\';
435 with PlayerModelsArray[ID].Info do
436 begin
437 Name := s;
438 Author := config.ReadStr('Model', 'author', '');
439 Description := config.ReadStr('Model', 'description', '');
440 end;
442 with PlayerModelsArray[ID] do
443 begin
444 Blood.R := MAX(0, MIN(255, config.ReadInt('Blood', 'R', 150)));
445 Blood.G := MAX(0, MIN(255, config.ReadInt('Blood', 'G', 0)));
446 Blood.B := MAX(0, MIN(255, config.ReadInt('Blood', 'B', 0)));
447 case config.ReadStr('Blood', 'Kind', 'NORMAL') of
448 'NORMAL': Blood.Kind := BLOOD_NORMAL;
449 'SPARKS': Blood.Kind := BLOOD_CSPARKS;
450 'COMBINE': Blood.Kind := BLOOD_COMBINE;
451 else
452 Blood.Kind := BLOOD_NORMAL
453 end
454 end;
456 for b := A_STAND to A_LAST do
457 begin
458 aname := s+'_RIGHTANIM'+IntToStr(b);
459 //e_LogWritefln('### MODEL FILE: [%s]', [prefix+config.ReadStr(AnimNames[b], 'resource', '')]);
460 if not (g_Frames_CreateWAD(nil, aname,
461 prefix+config.ReadStr(AnimNames[b], 'resource', ''),
462 64, 64, config.ReadInt(AnimNames[b], 'frames', 1),
463 config.ReadBool(AnimNames[b], 'backanim', False)) and
464 g_Frames_CreateWAD(nil, aname+'_MASK',
465 prefix+config.ReadStr(AnimNames[b], 'mask', ''),
466 64, 64, config.ReadInt(AnimNames[b], 'frames', 1),
467 config.ReadBool(AnimNames[b], 'backanim', False))) then
468 begin
469 if b <= A_LASTBASE then
470 begin
471 config.Free();
472 WAD.Free();
473 Exit;
474 end
475 else
476 begin
477 ExtAnimFromBaseAnim(s, b);
478 continue;
479 end;
480 end;
482 for aa := WP_FIRST + 1 to WP_LAST do
483 for bb := A_STAND to A_LAST do
484 for cc := TDirection.D_LEFT to TDirection.D_RIGHT do
485 begin
486 f := config.ReadInt(AnimNames[bb], 'frames', 1);
487 if config.ReadBool(AnimNames[bb], 'backanim', False) then
488 if f > 2 then f := 2*f-2;
489 SetLength(PlayerModelsArray[ID].WeaponPoints[aa, bb, cc], f);
490 end;
492 if (config.ReadStr(AnimNames[b], 'resource2', '') <> '') and
493 (config.ReadStr(AnimNames[b], 'mask2', '') <> '') then
494 begin
495 aname := s+'_LEFTANIM'+IntToStr(b);
496 g_Frames_CreateWAD(nil, aname,
497 prefix+config.ReadStr(AnimNames[b], 'resource2', ''),
498 64, 64, config.ReadInt(AnimNames[b], 'frames', 1),
499 config.ReadBool(AnimNames[b], 'backanim', False));
501 g_Frames_CreateWAD(nil, aname+'_MASK',
502 prefix+config.ReadStr(AnimNames[b], 'mask2', ''),
503 64, 64, config.ReadInt(AnimNames[b], 'frames', 1),
504 config.ReadBool(AnimNames[b], 'backanim', False));
505 end;
507 PlayerModelsArray[ID].ModelSpeed[b] := Max(1, config.ReadInt(AnimNames[b], 'waitcount', 1) div 3);
508 end;
510 with PlayerModelsArray[ID], config do
511 begin
512 prefix := FileName+':SOUNDS\';
514 a := 1;
515 repeat
516 s := config.ReadStr('Sound', 'pain'+IntToStr(a), '');
517 if s <> '' then
518 begin
519 SetLength(PainSounds, Length(PainSounds)+1);
520 g_Sound_CreateWAD(PainSounds[High(PainSounds)].ID, prefix+s);
521 PainSounds[High(PainSounds)].Level := config.ReadInt('Sound', 'painlevel'+IntToStr(a), 1);
522 end;
523 a := a+1;
524 until s = '';
526 a := 1;
527 repeat
528 s := config.ReadStr('Sound', 'die'+IntToStr(a), '');
529 if s <> '' then
530 begin
531 SetLength(DieSounds, Length(DieSounds)+1);
532 g_Sound_CreateWAD(DieSounds[High(DieSounds)].ID, prefix+s);
533 DieSounds[High(DieSounds)].Level := config.ReadInt('Sound', 'dielevel'+IntToStr(a), 1);
534 end;
535 a := a+1;
536 until s = '';
538 SlopSound := Min(Max(config.ReadInt('Sound', 'slop', 0), 0), 2);
540 SetLength(Gibs, ReadInt('Gibs', 'count', 0));
542 if (Gibs <> nil) and
543 (WAD.GetResource('TEXTURES/'+config.ReadStr('Gibs', 'resource', 'GIBS'), pData, lenpd)) and
544 (WAD.GetResource('TEXTURES/'+config.ReadStr('Gibs', 'mask', 'GIBSMASK'), pData2, lenpd2)) then
545 begin
546 for a := 0 to High(Gibs) do
547 if e_CreateTextureMemEx(pData, lenpd, Gibs[a].ID, a*32, 0, 32, 32) and
548 e_CreateTextureMemEx(pData2, lenpd2, Gibs[a].MaskID, a*32, 0, 32, 32) then
549 begin
550 //Gibs[a].Rect := e_GetTextureSize2(Gibs[a].ID);
551 Gibs[a].Rect := g_PlayerModel_CalcGibSize(pData, lenpd, a*32, 0, 32, 32);
552 with Gibs[a].Rect do
553 if Height > 3 then Height := Height-1-Random(2);
554 Gibs[a].OnlyOne := config.ReadInt('Gibs', 'once', -1) = a+1;
555 end;
557 FreeMem(pData);
558 FreeMem(pData2);
559 end;
561 ok := True;
562 for aa := WP_FIRST + 1 to WP_LAST do
563 for bb := A_STAND to A_LAST do
564 if not (bb in [A_DIE1, A_DIE2, A_PAIN]) then
565 begin
566 chk := GetWeapPoints(config.ReadStr(AnimNames[bb], WeapNames[aa]+'_points', ''), aa, bb, TDirection.D_RIGHT,
567 config.ReadInt(AnimNames[bb], 'frames', 0),
568 config.ReadBool(AnimNames[bb], 'backanim', False),
569 WeaponPoints);
570 if ok and (not chk) and (aa = WEAPON_FLAMETHROWER) then
571 begin
572 // workaround for flamethrower
573 chk := GetWeapPoints(config.ReadStr(AnimNames[bb], WeapNames[WEAPON_PLASMA]+'_points', ''), aa, bb, TDirection.D_RIGHT,
574 config.ReadInt(AnimNames[bb], 'frames', 0),
575 config.ReadBool(AnimNames[bb], 'backanim', False),
576 WeaponPoints);
577 if chk then
578 for f := 0 to High(WeaponPoints[aa, bb, TDirection.D_RIGHT]) do
579 begin
580 case bb of
581 A_STAND, A_PAIN:
582 begin
583 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 6);
584 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 8);
585 end;
586 A_WALKATTACK, A_WALK:
587 begin
588 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 9);
589 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 9);
590 end;
591 A_ATTACK:
592 begin
593 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 5);
594 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 8);
595 end;
596 A_WALKSEEUP, A_SEEUP:
597 begin
598 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 5);
599 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 16);
600 end;
601 A_WALKSEEDOWN, A_SEEDOWN:
602 begin
603 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 6);
604 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 5);
605 end;
606 A_WALKATTACKUP, A_ATTACKUP:
607 begin
608 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 5);
609 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 16);
610 end;
611 A_WALKATTACKDOWN, A_ATTACKDOWN:
612 begin
613 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 6);
614 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 4);
615 end;
616 end;
617 end;
618 end;
619 ok := ok and (chk or (bb > A_LASTBASE));
621 if not GetWeapPoints(config.ReadStr(AnimNames[bb], WeapNames[aa]+'2_points', ''), aa, bb, TDirection.D_LEFT,
622 config.ReadInt(AnimNames[bb], 'frames', 0),
623 config.ReadBool(AnimNames[bb], 'backanim', False),
624 WeaponPoints) then
625 for f := 0 to High(WeaponPoints[aa, bb, TDirection.D_RIGHT]) do
626 begin
627 WeaponPoints[aa, bb, TDirection.D_LEFT, f].X := -WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X;
628 WeaponPoints[aa, bb, TDirection.D_LEFT, f].Y := WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y;
629 end;
631 if not ok then Break;
632 end;
633 {if ok then g_Console_Add(Info.Name+' weapon points ok')
634 else g_Console_Add(Info.Name+' weapon points fail');}
635 Info.HaveWeapon := ok;
637 s := config.ReadStr('Model', 'flag_point', '');
638 if not GetPoint(s, FlagPoint) then FlagPoint := FLAG_DEFPOINT;
640 FlagAngle := config.ReadInt('Model', 'flag_angle', FLAG_DEFANGLE);
641 end;
643 config.Free();
644 WAD.Free();
646 Result := True;
647 end;
649 function g_PlayerModel_Get(ModelName: String): TPlayerModel;
650 var
651 a: Integer;
652 b: Byte;
653 ID, ID2: DWORD;
654 begin
655 Result := nil;
657 if PlayerModelsArray = nil then Exit;
659 for a := 0 to High(PlayerModelsArray) do
660 if AnsiLowerCase(PlayerModelsArray[a].Info.Name) = AnsiLowerCase(ModelName) then
661 begin
662 Result := TPlayerModel.Create;
664 with PlayerModelsArray[a] do
665 begin
666 Result.FName := Info.Name;
667 Result.FBlood := Blood;
669 for b := A_STAND to A_LAST do
670 begin
671 if not (g_Frames_Get(ID, Info.Name+'_RIGHTANIM'+IntToStr(b)) and
672 g_Frames_Get(ID2, Info.Name+'_RIGHTANIM'+IntToStr(b)+'_MASK')) then
673 begin
674 Result.Free();
675 Result := nil;
676 Exit;
677 end;
679 Result.FAnim[TDirection.D_RIGHT][b] := TAnimation.Create(ID, b in [A_STAND, A_WALK], ModelSpeed[b]);
681 Result.FMaskAnim[TDirection.D_RIGHT][b] := TAnimation.Create(ID2, b in [A_STAND, A_WALK], ModelSpeed[b]);
683 if g_Frames_Exists(Info.Name+'_LEFTANIM'+IntToStr(b)) and
684 g_Frames_Exists(Info.Name+'_LEFTANIM'+IntToStr(b)+'_MASK') then
685 if g_Frames_Get(ID, Info.Name+'_LEFTANIM'+IntToStr(b)) and
686 g_Frames_Get(ID2, Info.Name+'_LEFTANIM'+IntToStr(b)+'_MASK') then
687 begin
688 Result.FAnim[TDirection.D_LEFT][b] := TAnimation.Create(ID, b in [A_STAND, A_WALK], ModelSpeed[b]);
690 Result.FMaskAnim[TDirection.D_LEFT][b] := TAnimation.Create(ID2, b in [A_STAND, A_WALK], ModelSpeed[b]);
691 end;
692 end;
694 Result.FPainSounds := PainSounds;
695 Result.FDieSounds := DieSounds;
696 Result.FSlopSound := SlopSound;
697 Result.FDrawWeapon := Info.HaveWeapon;
698 Result.FWeaponPoints := WeaponPoints;
700 Result.FFlagPoint := FlagPoint;
701 Result.FFlagAngle := FlagAngle;
703 Break;
704 end;
705 end;
706 end;
708 function g_PlayerModel_GetAnim(ModelName: string; Anim: Byte; var _Anim, _Mask: TAnimation): Boolean;
709 var
710 a: Integer;
711 c: Boolean;
712 ID: DWORD;
713 begin
714 Result := False;
716 if PlayerModelsArray = nil then Exit;
717 for a := 0 to High(PlayerModelsArray) do
718 if PlayerModelsArray[a].Info.Name = ModelName then
719 with PlayerModelsArray[a] do
720 begin
721 if Anim in [A_STAND, A_WALK] then c := True else c := False;
723 if not g_Frames_Get(ID, Info.Name+'_RIGHTANIM'+IntToStr(Anim)) then
724 if not g_Frames_Get(ID, Info.Name+'_LEFTANIM'+IntToStr(Anim)) then Exit;
726 _Anim := TAnimation.Create(ID, c, ModelSpeed[Anim]);
727 _Anim.Speed := ModelSpeed[Anim];
729 if not g_Frames_Get(ID, Info.Name+'_RIGHTANIM'+IntToStr(Anim)+'_MASK') then
730 if not g_Frames_Get(ID, Info.Name+'_LEFTANIM'+IntToStr(Anim)+'_MASK') then Exit;
732 _Mask := TAnimation.Create(ID, c, ModelSpeed[Anim]);
733 _Mask.Speed := ModelSpeed[Anim];
735 Break;
736 end;
738 Result := True;
739 end;
741 function g_PlayerModel_GetGibs(ModelName: string; var Gibs: TGibsArray): Boolean;
742 var
743 a, i, b: Integer;
744 c: Boolean;
745 begin
746 Result := False;
748 if PlayerModelsArray = nil then Exit;
749 if gGibsCount = 0 then Exit;
751 c := False;
753 SetLength(Gibs, gGibsCount);
755 for a := 0 to High(PlayerModelsArray) do
756 if PlayerModelsArray[a].Info.Name = ModelName then
757 begin
758 for i := 0 to High(Gibs) do
759 begin
760 if c and (Length(PlayerModelsArray[a].Gibs) = 1) then
761 begin
762 SetLength(Gibs, i);
763 Break;
764 end;
766 repeat
767 b := Random(Length(PlayerModelsArray[a].Gibs));
768 until not (PlayerModelsArray[a].Gibs[b].OnlyOne and c);
770 Gibs[i] := PlayerModelsArray[a].Gibs[b];
772 if Gibs[i].OnlyOne then c := True;
773 end;
775 Result := True;
776 Break;
777 end;
778 end;
780 function g_PlayerModel_GetNames(): SSArray;
781 var
782 i: DWORD;
783 begin
784 Result := nil;
786 if PlayerModelsArray = nil then Exit;
788 for i := 0 to High(PlayerModelsArray) do
789 begin
790 SetLength(Result, Length(Result)+1);
791 Result[High(Result)] := PlayerModelsArray[i].Info.Name;
792 end;
793 end;
795 function g_PlayerModel_GetInfo(ModelName: string): TModelInfo;
796 var
797 a: Integer;
798 begin
799 FillChar(Result, SizeOf(Result), 0);
800 if PlayerModelsArray = nil then Exit;
802 for a := 0 to High(PlayerModelsArray) do
803 if PlayerModelsArray[a].Info.Name = ModelName then
804 begin
805 Result := PlayerModelsArray[a].Info;
806 Break;
807 end;
808 end;
810 function g_PlayerModel_GetBlood(ModelName: string): TModelBlood;
811 var
812 a: Integer;
813 begin
814 Result.R := 150;
815 Result.G := 0;
816 Result.B := 0;
817 Result.Kind := BLOOD_NORMAL;
818 if PlayerModelsArray = nil then Exit;
820 for a := 0 to High(PlayerModelsArray) do
821 if PlayerModelsArray[a].Info.Name = ModelName then
822 begin
823 Result := PlayerModelsArray[a].Blood;
824 Break;
825 end;
826 end;
828 procedure g_PlayerModel_FreeData();
829 var
830 i: DWORD;
831 a, b, c: Integer;
832 begin
833 for a := WP_FIRST + 1 to WP_LAST do
834 for b := W_POS_NORMAL to W_POS_DOWN do
835 for c := W_ACT_NORMAL to W_ACT_FIRE do
836 e_DeleteTexture(WeaponID[a][b][c]);
838 e_WriteLog('Releasing models...', TMsgType.Notify);
840 if PlayerModelsArray = nil then Exit;
842 for i := 0 to High(PlayerModelsArray) do
843 with PlayerModelsArray[i] do
844 begin
845 for a := A_STAND to A_LAST do
846 begin
847 g_Frames_DeleteByName(Info.Name+'_LEFTANIM'+IntToStr(a));
848 g_Frames_DeleteByName(Info.Name+'_LEFTANIM'+IntToStr(a)+'_MASK');
849 g_Frames_DeleteByName(Info.Name+'_RIGHTANIM'+IntToStr(a));
850 g_Frames_DeleteByName(Info.Name+'_RIGHTANIM'+IntToStr(a)+'_MASK');
851 end;
853 if PainSounds <> nil then
854 for b := 0 to High(PainSounds) do
855 e_DeleteSound(PainSounds[b].ID);
857 if DieSounds <> nil then
858 for b := 0 to High(DieSounds) do
859 e_DeleteSound(DieSounds[b].ID);
861 if Gibs <> nil then
862 for b := 0 to High(Gibs) do
863 begin
864 e_DeleteTexture(Gibs[b].ID);
865 e_DeleteTexture(Gibs[b].MaskID);
866 end;
867 end;
869 PlayerModelsArray := nil;
870 end;
872 { TPlayerModel }
874 procedure TPlayerModel.ChangeAnimation(Animation: Byte; Force: Boolean = False);
875 begin
876 if not Force then if FCurrentAnimation = Animation then Exit;
878 FCurrentAnimation := Animation;
880 if (FDirection = TDirection.D_LEFT) and
881 (FAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) and
882 (FMaskAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
883 begin
884 FAnim[TDirection.D_LEFT][FCurrentAnimation].Reset;
885 FMaskAnim[TDirection.D_LEFT][FCurrentAnimation].Reset;
886 end
887 else
888 begin
889 FAnim[TDirection.D_RIGHT][FCurrentAnimation].Reset;
890 FMaskAnim[TDirection.D_RIGHT][FCurrentAnimation].Reset;
891 end;
892 end;
894 destructor TPlayerModel.Destroy();
895 var
896 a: Byte;
897 begin
898 for a := A_STAND to A_LAST do
899 begin
900 FAnim[TDirection.D_LEFT][a].Free();
901 FMaskAnim[TDirection.D_LEFT][a].Free();
902 FAnim[TDirection.D_RIGHT][a].Free();
903 FMaskAnim[TDirection.D_RIGHT][a].Free();
904 end;
906 inherited;
907 end;
909 procedure TPlayerModel.Draw(X, Y: Integer; Alpha: Byte = 0);
910 var
911 Mirror: TMirrorType;
912 pos, act: Byte;
913 p: TDFPoint;
914 begin
915 // Ôëàãè:
916 if Direction = TDirection.D_LEFT then
917 Mirror := TMirrorType.None
918 else
919 Mirror := TMirrorType.Horizontal;
921 if (FFlag <> FLAG_NONE) and (FFlagAnim <> nil) and
922 (not (FCurrentAnimation in [A_DIE1, A_DIE2])) then
923 begin
924 p.X := IfThen(Direction = TDirection.D_LEFT,
925 FLAG_BASEPOINT.X,
926 64-FLAG_BASEPOINT.X);
927 p.Y := FLAG_BASEPOINT.Y;
929 FFlagAnim.DrawEx(X+IfThen(Direction = TDirection.D_LEFT, FFlagPoint.X-1, 2*FLAG_BASEPOINT.X-FFlagPoint.X+1)-FLAG_BASEPOINT.X,
930 Y+FFlagPoint.Y-FLAG_BASEPOINT.Y+1, Mirror, p,
931 IfThen(FDirection = TDirection.D_RIGHT, FFlagAngle, -FFlagAngle));
932 end;
934 // Îðóæèå:
935 if Direction = TDirection.D_RIGHT then
936 Mirror := TMirrorType.None
937 else
938 Mirror := TMirrorType.Horizontal;
940 if FDrawWeapon and
941 (not (FCurrentAnimation in [A_DIE1, A_DIE2, A_PAIN])) and
942 (FCurrentWeapon in [WP_FIRST + 1..WP_LAST]) then
943 begin
944 if FCurrentAnimation in [A_SEEUP, A_ATTACKUP] then
945 pos := W_POS_UP
946 else
947 if FCurrentAnimation in [A_SEEDOWN, A_ATTACKDOWN] then
948 pos := W_POS_DOWN
949 else
950 pos := W_POS_NORMAL;
952 if (FCurrentAnimation in [A_ATTACK, A_ATTACKUP, A_ATTACKDOWN]) or
953 FFire then
954 act := W_ACT_FIRE
955 else
956 act := W_ACT_NORMAL;
958 if Alpha < 201 then
959 e_Draw(WeaponID[FCurrentWeapon][pos][act],
960 X+FWeaponPoints[FCurrentWeapon, FCurrentAnimation, FDirection,
961 FAnim[TDirection.D_RIGHT][FCurrentAnimation].CurrentFrame].X,
962 Y+FWeaponPoints[FCurrentWeapon, FCurrentAnimation, FDirection,
963 FAnim[TDirection.D_RIGHT][FCurrentAnimation].CurrentFrame].Y,
964 0, True, False, Mirror);
965 end;
967 // Ìîäåëü:
968 if (FDirection = TDirection.D_LEFT) and
969 (FAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
970 begin
971 FAnim[TDirection.D_LEFT][FCurrentAnimation].Alpha := Alpha;
972 FAnim[TDirection.D_LEFT][FCurrentAnimation].Draw(X, Y, TMirrorType.None);
973 end
974 else
975 begin
976 FAnim[TDirection.D_RIGHT][FCurrentAnimation].Alpha := Alpha;
977 FAnim[TDirection.D_RIGHT][FCurrentAnimation].Draw(X, Y, Mirror);
978 end;
980 // Ìàñêà ìîäåëè:
981 e_Colors := FColor;
983 if (FDirection = TDirection.D_LEFT) and
984 (FMaskAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
985 begin
986 FMaskAnim[TDirection.D_LEFT][FCurrentAnimation].Alpha := Alpha;
987 FMaskAnim[TDirection.D_LEFT][FCurrentAnimation].Draw(X, Y, TMirrorType.None);
988 end
989 else
990 begin
991 FMaskAnim[TDirection.D_RIGHT][FCurrentAnimation].Alpha := Alpha;
992 FMaskAnim[TDirection.D_RIGHT][FCurrentAnimation].Draw(X, Y, Mirror);
993 end;
995 e_Colors.R := 255;
996 e_Colors.G := 255;
997 e_Colors.B := 255;
998 end;
1000 function TPlayerModel.GetCurrentAnimation: TAnimation;
1001 begin
1002 if (FDirection = TDirection.D_LEFT) and (FAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
1003 Result := FAnim[TDirection.D_LEFT][FCurrentAnimation]
1004 else
1005 Result := FAnim[TDirection.D_RIGHT][FCurrentAnimation];
1006 end;
1008 function TPlayerModel.GetCurrentAnimationMask: TAnimation;
1009 begin
1010 if (FDirection = TDirection.D_LEFT) and (FMaskAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
1011 Result := FMaskAnim[TDirection.D_LEFT][FCurrentAnimation]
1012 else
1013 Result := FMaskAnim[TDirection.D_RIGHT][FCurrentAnimation];
1014 end;
1016 function TPlayerModel.PlaySound(SoundType, Level: Byte; X, Y: Integer): Boolean;
1017 var
1018 TempArray: array of DWORD;
1019 a: Integer;
1020 begin
1021 Result := False;
1022 SetLength(TempArray, 0);
1024 if SoundType = MODELSOUND_PAIN then
1025 begin
1026 if FPainSounds = nil then Exit;
1028 for a := 0 to High(FPainSounds) do
1029 if FPainSounds[a].Level = Level then
1030 begin
1031 SetLength(TempArray, Length(TempArray)+1);
1032 TempArray[High(TempArray)] := FPainSounds[a].ID;
1033 end;
1034 end
1035 else
1036 begin
1037 if (Level in [2, 3, 5]) and (FSlopSound > 0) then
1038 begin
1039 g_Sound_PlayExAt('SOUND_MONSTER_SLOP', X, Y);
1040 if FSlopSound = 1 then
1041 begin
1042 Result := True;
1043 Exit;
1044 end;
1045 end;
1046 if FDieSounds = nil then Exit;
1048 for a := 0 to High(FDieSounds) do
1049 if FDieSounds[a].Level = Level then
1050 begin
1051 SetLength(TempArray, Length(TempArray)+1);
1052 TempArray[High(TempArray)] := FDieSounds[a].ID;
1053 end;
1054 if (TempArray = nil) and (Level = 5) then
1055 begin
1056 g_Sound_PlayExAt('SOUND_MONSTER_SLOP', X, Y);
1057 Result := True;
1058 Exit;
1059 end;
1060 end;
1062 if TempArray = nil then Exit;
1064 g_Sound_PlayAt(TempArray[Random(Length(TempArray))], X, Y);
1066 Result := True;
1067 end;
1069 procedure TPlayerModel.SetColor(Red, Green, Blue: Byte);
1070 begin
1071 FColor.R := Red;
1072 FColor.G := Green;
1073 FColor.B := Blue;
1074 end;
1076 procedure TPlayerModel.SetFire(Fire: Boolean);
1077 begin
1078 FFire := Fire;
1080 if FFire then FFireCounter := FAnim[TDirection.D_RIGHT, A_ATTACK].Speed*FAnim[TDirection.D_RIGHT, A_ATTACK].TotalFrames
1081 else FFireCounter := 0;
1082 end;
1084 procedure TPlayerModel.SetFlag(Flag: Byte);
1085 var
1086 id: DWORD;
1087 begin
1088 FFlag := Flag;
1090 FFlagAnim.Free();
1091 FFlagAnim := nil;
1093 case Flag of
1094 FLAG_RED: g_Frames_Get(id, 'FRAMES_FLAG_RED');
1095 FLAG_BLUE: g_Frames_Get(id, 'FRAMES_FLAG_BLUE');
1096 else Exit;
1097 end;
1099 FFlagAnim := TAnimation.Create(id, True, 8);
1100 end;
1102 procedure TPlayerModel.SetWeapon(Weapon: Byte);
1103 begin
1104 FCurrentWeapon := Weapon;
1105 end;
1107 procedure TPlayerModel.Update();
1108 begin
1109 if (FDirection = TDirection.D_LEFT) and (FAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
1110 FAnim[TDirection.D_LEFT][FCurrentAnimation].Update else FAnim[TDirection.D_RIGHT][FCurrentAnimation].Update;
1112 if (FDirection = TDirection.D_LEFT) and (FMaskAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
1113 FMaskAnim[TDirection.D_LEFT][FCurrentAnimation].Update else FMaskAnim[TDirection.D_RIGHT][FCurrentAnimation].Update;
1115 if FFlagAnim <> nil then FFlagAnim.Update;
1117 if FFireCounter > 0 then Dec(FFireCounter) else FFire := False;
1118 end;
1120 end.