Remove noreturns from functions that throw
[d2df-sdl.git] / src / game / g_playermodel.pas
blob6c06bae8f1ea0a3f2bb10242e74b1b407762fafb
1 (* Copyright (C) 2016 - The Doom2D.org team & involved community members <http://www.doom2d.org>.
2 * This file is part of Doom2D Forever.
4 * This program is free software: you can redistribute it and/or modify it under the terms of
5 * the GNU General Public License as published by the Free Software Foundation, version 3 of
6 * the License ONLY.
8 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
9 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10 * See the GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License along with this program.
13 * If not, see <http://www.gnu.org/licenses/>.
16 {$INCLUDE ../shared/a_modes.inc}
17 {$M+}
18 unit g_playermodel;
20 interface
22 uses
23 {$IFDEF USE_MEMPOOL}mempool,{$ENDIF}
24 {$IFDEF ENABLE_SOUND}
25 e_sound,
26 {$ENDIF}
27 MAPDEF, g_textures, g_basic, g_weapons, e_graphics, utils, g_gfx,
28 ImagingTypes, Imaging, ImagingUtility;
30 const
31 A_STAND = 0;
32 A_WALK = 1;
33 A_DIE1 = 2;
34 A_DIE2 = 3;
35 A_ATTACK = 4;
36 A_SEEUP = 5;
37 A_SEEDOWN = 6;
38 A_ATTACKUP = 7;
39 A_ATTACKDOWN = 8;
40 A_PAIN = 9;
41 // EXTENDED
42 A_WALKATTACK = 10;
43 A_WALKSEEUP = 11;
44 A_WALKSEEDOWN = 12;
45 A_WALKATTACKUP = 13;
46 A_WALKATTACKDOWN = 14;
47 A_MELEESTAND = 15;
48 A_MELEEWALK = 16;
49 A_MELEEATTACK = 17;
50 A_MELEEWALKATTACK = 18;
51 A_MELEESEEUP = 19;
52 A_MELEESEEDOWN = 20;
53 A_MELEEATTACKUP = 21;
54 A_MELEEATTACKDOWN = 22;
56 A_LASTBASE = A_PAIN;
57 A_LASTEXT = A_MELEEATTACKDOWN;
58 A_LAST = A_LASTEXT;
60 MODELSOUND_PAIN = 0;
61 MODELSOUND_DIE = 1;
63 type
64 TModelInfo = record
65 Name: String;
66 Author: String;
67 Description: String;
68 HaveWeapon: Boolean;
69 end;
71 TModelBlood = record
72 R, G, B, Kind: Byte;
73 end;
75 {$IFDEF ENABLE_SOUND}
76 TModelSound = record
77 ID: TSoundID;
78 Level: Byte;
79 end;
80 {$ENDIF}
82 TGibSprite = record
83 ID: DWORD;
84 MaskID: DWORD;
85 Rect: TRectWH;
86 OnlyOne: Boolean;
87 end;
89 {$IFDEF ENABLE_SOUND}
90 TModelSoundArray = Array of TModelSound;
91 {$ENDIF}
93 TGibsArray = Array of TGibSprite;
94 TWeaponPoints = Array [WP_FIRST + 1..WP_LAST] of
95 Array [A_STAND..A_LAST] of
96 Array [TDirection.D_LEFT..TDirection.D_RIGHT] of Array of TDFPoint;
98 TPlayerModel = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF}
99 private
100 FName: String;
101 FDirection: TDirection;
102 FColor: TRGB;
103 FBlood: TModelBlood;
104 FCurrentAnimation: Byte;
105 FAnim: Array [TDirection.D_LEFT..TDirection.D_RIGHT] of Array [A_STAND..A_LAST] of TAnimation;
106 FMaskAnim: Array [TDirection.D_LEFT..TDirection.D_RIGHT] of Array [A_STAND..A_LAST] of TAnimation;
107 FWeaponPoints: TWeaponPoints;
108 {$IFDEF ENABLE_SOUND}
109 FPainSounds: TModelSoundArray;
110 FDieSounds: TModelSoundArray;
111 FSlopSound: Byte;
112 {$ENDIF}
113 FCurrentWeapon: Byte;
114 FDrawWeapon: Boolean;
115 FFlag: Byte;
116 FFlagPoint: TDFPoint;
117 FFlagAngle: SmallInt;
118 FFlagAnim: TAnimation;
119 FFire: Boolean;
120 FFireCounter: Byte;
122 public
123 destructor Destroy(); override;
124 procedure ChangeAnimation(Animation: Byte; Force: Boolean = False);
125 function GetCurrentAnimation: TAnimation;
126 function GetCurrentAnimationMask: TAnimation;
127 procedure SetColor(Red, Green, Blue: Byte);
128 procedure SetWeapon(Weapon: Byte);
129 procedure SetFlag(Flag: Byte);
130 procedure SetFire(Fire: Boolean);
131 procedure InvertDirection();
132 {$IFDEF ENABLE_SOUND}
133 function PlaySound(SoundType, Level: Byte; X, Y: Integer): Boolean;
134 {$ENDIF}
135 procedure Update();
136 procedure Draw(X, Y: Integer; Alpha: Byte = 0);
138 published
139 property Fire: Boolean read FFire;
140 property Direction: TDirection read FDirection write FDirection;
141 property Animation: Byte read FCurrentAnimation;
142 property Weapon: Byte read FCurrentWeapon;
143 property Name: String read FName;
145 public
146 property Color: TRGB read FColor write FColor;
147 property Blood: TModelBlood read FBlood;
148 end;
150 procedure g_PlayerModel_LoadData();
151 procedure g_PlayerModel_FreeData();
152 function g_PlayerModel_Load(FileName: String): Boolean;
153 function g_PlayerModel_GetNames(): SSArray;
154 function g_PlayerModel_GetInfo(ModelName: String): TModelInfo;
155 function g_PlayerModel_GetBlood(ModelName: String): TModelBlood;
156 function g_PlayerModel_Get(ModelName: String): TPlayerModel;
157 function g_PlayerModel_GetAnim(ModelName: String; Anim: Byte; var _Anim, _Mask: TAnimation): Boolean;
158 function g_PlayerModel_GetGibs(ModelName: String; var Gibs: TGibsArray): Boolean;
159 function g_PlayerModel_MakeColor(const aColor: TRGB; aTeam: Byte): TRGB;
162 implementation
164 uses
165 {$INCLUDE ../nogl/noGLuses.inc}
166 {$IFDEF ENABLE_SOUND}
167 g_sound,
168 {$ENDIF}
169 g_main, g_console, SysUtils, g_player, CONFIG,
170 g_options, g_map, Math, e_log, wadreader;
172 type
173 TPlayerModelInfo = record
174 Info: TModelInfo;
175 ModelSpeed: Array [A_STAND..A_PAIN] of Byte;
176 FlagPoint: TDFPoint;
177 FlagAngle: SmallInt;
178 WeaponPoints: TWeaponPoints;
179 Gibs: TGibsArray;
180 {$IFDEF ENABLE_SOUND}
181 PainSounds: TModelSoundArray;
182 DieSounds: TModelSoundArray;
183 SlopSound: Byte;
184 {$ENDIF}
185 Blood: TModelBlood;
186 end;
188 const
189 W_POS_NORMAL = 0;
190 W_POS_UP = 1;
191 W_POS_DOWN = 2;
193 W_ACT_NORMAL = 0;
194 W_ACT_FIRE = 1;
196 FLAG_BASEPOINT: TDFPoint = (X:16; Y:43);
197 FLAG_DEFPOINT: TDFPoint = (X:32; Y:16);
198 FLAG_DEFANGLE = -20;
199 WEAPONBASE: Array [WP_FIRST + 1..WP_LAST] of TDFPoint =
200 ((X:8; Y:4), (X:8; Y:8), (X:16; Y:16), (X:16; Y:24),
201 (X:16; Y:16), (X:24; Y:24), (X:16; Y:16), (X:24; Y:24),
202 (X:16; Y:16), (X:8; Y:8));
204 AnimNames: Array [A_STAND..A_LASTEXT] of String =
205 ('StandAnim','WalkAnim','Die1Anim','Die2Anim','AttackAnim',
206 'SeeUpAnim','SeeDownAnim','AttackUpAnim','AttackDownAnim','PainAnim',
207 // EXTENDED
208 'WalkAttackAnim', 'WalkSeeUpAnim', 'WalkSeeDownAnim',
209 'WalkAttackUpAnim', 'WalkAttackDownAnim', 'MeleeStandAnim', 'MeleeWalkAnim',
210 'MeleeAttackAnim', 'MeleeWalkAttackAnim', 'MeleeSeeUpAnim', 'MeleeSeeDownAnim',
211 'MeleeAttackUpAnim', 'MeleeAttackDownAnim');
212 WeapNames: Array [WP_FIRST + 1..WP_LAST] of String =
213 ('csaw', 'hgun', 'sg', 'ssg', 'mgun', 'rkt', 'plz', 'bfg', 'spl', 'flm');
216 WeaponID: Array [WP_FIRST + 1..WP_LAST] of
217 Array [W_POS_NORMAL..W_POS_DOWN] of
218 Array [W_ACT_NORMAL..W_ACT_FIRE] of DWORD;
219 PlayerModelsArray: Array of TPlayerModelInfo;
221 procedure g_PlayerModel_LoadData();
223 a: Integer;
224 begin
225 for a := WP_FIRST + 1 to WP_LAST do
226 begin
227 g_Texture_CreateWAD(WeaponID[a][W_POS_NORMAL][W_ACT_NORMAL], GameWAD+':WEAPONS\'+UpperCase(WeapNames[a]));
228 g_Texture_CreateWAD(WeaponID[a][W_POS_NORMAL][W_ACT_FIRE], GameWAD+':WEAPONS\'+UpperCase(WeapNames[a])+'_FIRE');
229 g_Texture_CreateWAD(WeaponID[a][W_POS_UP][W_ACT_NORMAL], GameWAD+':WEAPONS\'+UpperCase(WeapNames[a])+'_UP');
230 g_Texture_CreateWAD(WeaponID[a][W_POS_UP][W_ACT_FIRE], GameWAD+':WEAPONS\'+UpperCase(WeapNames[a])+'_UP_FIRE');
231 g_Texture_CreateWAD(WeaponID[a][W_POS_DOWN][W_ACT_NORMAL], GameWAD+':WEAPONS\'+UpperCase(WeapNames[a])+'_DN');
232 g_Texture_CreateWAD(WeaponID[a][W_POS_DOWN][W_ACT_FIRE], GameWAD+':WEAPONS\'+UpperCase(WeapNames[a])+'_DN_FIRE');
233 end;
234 end;
236 function GetPoint(var str: String; var point: TDFPoint): Boolean;
238 a, x, y: Integer;
239 s: String;
240 begin
241 Result := False;
242 x := 0;
243 y := 0;
245 str := Trim(str);
246 if Length(str) < 3 then
247 Exit;
249 for a := 1 to Length(str) do
250 if (str[a] = ',') or (a = Length(str)) then
251 begin
252 s := Copy(str, 1, a);
253 if s[Length(s)] = ',' then
254 SetLength(s, Length(s)-1);
255 Delete(str, 1, a);
257 if (Sscanf(s, '%d:%d', [@x, @y]) < 2) or
258 (x < -64) or (x > 128) or
259 (y < -64) or (y > 128) then
260 Exit;
262 point.X := x;
263 point.Y := y;
265 Break;
266 end;
268 Result := True;
269 end;
271 function GetWeapPoints(str: String; weapon: Byte; anim: Byte; dir: TDirection;
272 frames: Word; backanim: Boolean; var wpoints: TWeaponPoints): Boolean;
274 a, b, h: Integer;
275 begin
276 Result := False;
278 if frames = 0 then
279 Exit;
281 backanim := backanim and (frames > 2);
283 for a := 1 to frames do
284 begin
285 if not GetPoint(str, wpoints[weapon, anim, dir, a-1]) then
286 Exit;
288 with wpoints[weapon, anim, dir, a-1] do
289 begin
290 X := X - WEAPONBASE[weapon].X;
291 Y := Y - WEAPONBASE[weapon].Y;
292 if dir = TDirection.D_LEFT then
293 X := -X;
294 end;
295 end;
297 h := High(wpoints[weapon, anim, dir]);
298 if backanim then
299 for b := h downto frames do
300 wpoints[weapon, anim, dir, b] := wpoints[weapon, anim, dir, h-b+1];
302 Result := True;
303 end;
305 procedure ExtAnimFromBaseAnim(MName: String; AIdx: Integer);
306 const
307 CopyAnim: array [A_LASTBASE+1..A_LASTEXT] of Integer = (
308 A_WALK, A_WALK, A_WALK, A_WALK, A_WALK,
309 A_STAND, A_WALK, A_ATTACK, A_WALK, A_SEEUP, A_SEEDOWN,
310 A_ATTACKUP, A_ATTACKDOWN
313 OIdx, W, I: Integer;
314 D: TDirection;
315 AName, OName: String;
316 begin
317 // HACK: shitty workaround to duplicate base animations
318 // in place of extended, replace with something better later
320 Assert((AIdx > A_LASTBASE) and (AIdx <= A_LASTEXT));
321 OIdx := CopyAnim[AIdx];
323 AName := MName + '_RIGHTANIM' + IntToStr(AIdx);
324 OName := MName + '_RIGHTANIM' + IntToStr(OIdx);
325 Assert(g_Frames_Dup(AName, OName));
326 Assert(g_Frames_Dup(AName + '_MASK', OName + '_MASK'));
327 AName := MName + '_LEFTANIM' + IntToStr(AIdx);
328 OName := MName + '_LEFTANIM' + IntToStr(OIdx);
329 if g_Frames_Exists(AName) then
330 begin
331 g_Frames_Dup(AName, OName);
332 g_Frames_Dup(AName + '_MASK', OName + '_MASK');
333 end;
335 with PlayerModelsArray[High(PlayerModelsArray)] do
336 begin
337 for W := WP_FIRST + 1 to WP_LAST do
338 begin
339 for D := TDirection.D_LEFT to TDirection.D_RIGHT do
340 begin
341 SetLength(WeaponPoints[W, AIdx, D], Length(WeaponPoints[W, OIdx, D]));
342 for I := 0 to High(WeaponPoints[W, AIdx, D]) do
343 WeaponPoints[W, AIdx, D, I] := WeaponPoints[W, OIdx, D, I]
344 end;
345 end;
346 end;
347 end;
349 function g_PlayerModel_CalcGibSize (pData: Pointer; dataSize, x, y, w, h: Integer): TRectWH;
350 var i, j: Integer; done: Boolean; img: TImageData;
352 function IsVoid (i, j: Integer): Boolean;
353 begin
354 result := Byte((PByte(img.bits) + (y+j)*img.width*4 + (x+i)*4 + 3)^) = 0
355 end;
357 begin
358 InitImage(img);
359 assert(LoadImageFromMemory(pData, dataSize, img));
361 (* trace x from right to left *)
362 done := false; i := 0;
363 while not done and (i < w) do
364 begin
365 j := 0;
366 while (j < h) and IsVoid(i, j) do inc(j);
367 done := (j < h) and (IsVoid(i, j) = false);
368 result.x := i;
369 inc(i);
370 end;
372 (* trace y from up to down *)
373 done := false; j := 0;
374 while not done and (j < h) do
375 begin
376 i := 0;
377 while (i < w) and IsVoid(i, j) do inc(i);
378 done := (i < w) and (IsVoid(i, j) = false);
379 result.y := j;
380 inc(j);
381 end;
383 (* trace x from right to left *)
384 done := false; i := w - 1;
385 while not done and (i >= 0) do
386 begin
387 j := 0;
388 while (j < h) and IsVoid(i, j) do inc(j);
389 done := (j < h) and (IsVoid(i, j) = false);
390 result.width := i - result.x + 1;
391 dec(i);
392 end;
394 (* trace y from down to up *)
395 done := false; j := h - 1;
396 while not done and (j >= 0) do
397 begin
398 i := 0;
399 while (i < w) and IsVoid(i, j) do inc(i);
400 done := (i < w) and (IsVoid(i, j) = false);
401 result.height := j - result.y + 1;
402 dec(j);
403 end;
405 FreeImage(img);
406 end;
408 function g_PlayerModel_Load(FileName: string): Boolean;
410 ID: DWORD;
411 a, b, len, lenpd, lenpd2, aa, bb, f: Integer;
412 cc: TDirection;
413 config: TConfig;
414 pData, pData2: Pointer;
415 WAD: TWADFile;
416 s, aname: string;
417 prefix: string;
418 ok, chk: Boolean;
419 begin
420 e_WriteLog(Format('Loading player model "%s"...', [FileName]), TMsgType.Notify);
422 Result := False;
424 WAD := TWADFile.Create;
425 WAD.ReadFile(FileName);
427 if {WAD.GetLastError <> DFWAD_NOERROR} not WAD.isOpen then
428 begin
429 WAD.Free();
430 Exit;
431 end;
433 if not WAD.GetResource('TEXT/MODEL', pData, len) then
434 begin
435 WAD.Free();
436 Exit;
437 end;
439 config := TConfig.CreateMem(pData, len);
440 FreeMem(pData);
442 s := config.ReadStr('Model', 'name', '');
443 if s = '' then
444 begin
445 config.Free();
446 WAD.Free();
447 Exit;
448 end;
450 SetLength(PlayerModelsArray, Length(PlayerModelsArray)+1);
451 ID := High(PlayerModelsArray);
453 prefix := FileName+':TEXTURES\';
455 with PlayerModelsArray[ID].Info do
456 begin
457 Name := s;
458 Author := config.ReadStr('Model', 'author', '');
459 Description := config.ReadStr('Model', 'description', '');
460 end;
462 with PlayerModelsArray[ID] do
463 begin
464 Blood.R := MAX(0, MIN(255, config.ReadInt('Blood', 'R', 150)));
465 Blood.G := MAX(0, MIN(255, config.ReadInt('Blood', 'G', 0)));
466 Blood.B := MAX(0, MIN(255, config.ReadInt('Blood', 'B', 0)));
467 case config.ReadStr('Blood', 'Kind', 'NORMAL') of
468 'NORMAL': Blood.Kind := BLOOD_NORMAL;
469 'SPARKS': Blood.Kind := BLOOD_CSPARKS;
470 'COMBINE': Blood.Kind := BLOOD_COMBINE;
471 else
472 Blood.Kind := BLOOD_NORMAL
474 end;
476 for b := A_STAND to A_LAST do
477 begin
478 aname := s+'_RIGHTANIM'+IntToStr(b);
479 //e_LogWritefln('### MODEL FILE: [%s]', [prefix+config.ReadStr(AnimNames[b], 'resource', '')]);
480 if not (g_Frames_CreateWAD(nil, aname,
481 prefix+config.ReadStr(AnimNames[b], 'resource', ''),
482 64, 64, config.ReadInt(AnimNames[b], 'frames', 1),
483 config.ReadBool(AnimNames[b], 'backanim', False)) and
484 g_Frames_CreateWAD(nil, aname+'_MASK',
485 prefix+config.ReadStr(AnimNames[b], 'mask', ''),
486 64, 64, config.ReadInt(AnimNames[b], 'frames', 1),
487 config.ReadBool(AnimNames[b], 'backanim', False))) then
488 begin
489 if b <= A_LASTBASE then
490 begin
491 config.Free();
492 WAD.Free();
493 Exit;
495 else
496 begin
497 ExtAnimFromBaseAnim(s, b);
498 continue;
499 end;
500 end;
502 for aa := WP_FIRST + 1 to WP_LAST do
503 for bb := A_STAND to A_LAST do
504 for cc := TDirection.D_LEFT to TDirection.D_RIGHT do
505 begin
506 f := config.ReadInt(AnimNames[bb], 'frames', 1);
507 if config.ReadBool(AnimNames[bb], 'backanim', False) then
508 if f > 2 then f := 2*f-2;
509 SetLength(PlayerModelsArray[ID].WeaponPoints[aa, bb, cc], f);
510 end;
512 if (config.ReadStr(AnimNames[b], 'resource2', '') <> '') and
513 (config.ReadStr(AnimNames[b], 'mask2', '') <> '') then
514 begin
515 aname := s+'_LEFTANIM'+IntToStr(b);
516 g_Frames_CreateWAD(nil, aname,
517 prefix+config.ReadStr(AnimNames[b], 'resource2', ''),
518 64, 64, config.ReadInt(AnimNames[b], 'frames', 1),
519 config.ReadBool(AnimNames[b], 'backanim', False));
521 g_Frames_CreateWAD(nil, aname+'_MASK',
522 prefix+config.ReadStr(AnimNames[b], 'mask2', ''),
523 64, 64, config.ReadInt(AnimNames[b], 'frames', 1),
524 config.ReadBool(AnimNames[b], 'backanim', False));
525 end;
527 PlayerModelsArray[ID].ModelSpeed[b] := Max(1, config.ReadInt(AnimNames[b], 'waitcount', 1) div 3);
528 end;
530 with PlayerModelsArray[ID], config do
531 begin
532 {$IFDEF ENABLE_SOUND}
533 prefix := FileName+':SOUNDS\';
535 a := 1;
536 repeat
537 s := config.ReadStr('Sound', 'pain'+IntToStr(a), '');
538 if s <> '' then
539 begin
540 SetLength(PainSounds, Length(PainSounds)+1);
541 g_Sound_CreateWAD(PainSounds[High(PainSounds)].ID, prefix+s);
542 PainSounds[High(PainSounds)].Level := config.ReadInt('Sound', 'painlevel'+IntToStr(a), 1);
543 end;
544 a := a+1;
545 until s = '';
547 a := 1;
548 repeat
549 s := config.ReadStr('Sound', 'die'+IntToStr(a), '');
550 if s <> '' then
551 begin
552 SetLength(DieSounds, Length(DieSounds)+1);
553 g_Sound_CreateWAD(DieSounds[High(DieSounds)].ID, prefix+s);
554 DieSounds[High(DieSounds)].Level := config.ReadInt('Sound', 'dielevel'+IntToStr(a), 1);
555 end;
556 a := a+1;
557 until s = '';
559 SlopSound := Min(Max(config.ReadInt('Sound', 'slop', 0), 0), 2);
560 {$ENDIF}
562 SetLength(Gibs, ReadInt('Gibs', 'count', 0));
564 if (Gibs <> nil) and
565 (WAD.GetResource('TEXTURES/'+config.ReadStr('Gibs', 'resource', 'GIBS'), pData, lenpd)) and
566 (WAD.GetResource('TEXTURES/'+config.ReadStr('Gibs', 'mask', 'GIBSMASK'), pData2, lenpd2)) then
567 begin
568 for a := 0 to High(Gibs) do
569 if e_CreateTextureMemEx(pData, lenpd, Gibs[a].ID, a*32, 0, 32, 32) and
570 e_CreateTextureMemEx(pData2, lenpd2, Gibs[a].MaskID, a*32, 0, 32, 32) then
571 begin
572 //Gibs[a].Rect := e_GetTextureSize2(Gibs[a].ID);
573 Gibs[a].Rect := g_PlayerModel_CalcGibSize(pData, lenpd, a*32, 0, 32, 32);
574 with Gibs[a].Rect do
575 if Height > 3 then Height := Height-1-Random(2);
576 Gibs[a].OnlyOne := config.ReadInt('Gibs', 'once', -1) = a+1;
577 end;
579 FreeMem(pData);
580 FreeMem(pData2);
581 end;
583 ok := True;
584 for aa := WP_FIRST + 1 to WP_LAST do
585 for bb := A_STAND to A_LAST do
586 if not (bb in [A_DIE1, A_DIE2, A_PAIN]) then
587 begin
588 chk := GetWeapPoints(config.ReadStr(AnimNames[bb], WeapNames[aa]+'_points', ''), aa, bb, TDirection.D_RIGHT,
589 config.ReadInt(AnimNames[bb], 'frames', 0),
590 config.ReadBool(AnimNames[bb], 'backanim', False),
591 WeaponPoints);
592 if ok and (not chk) and (aa = WEAPON_FLAMETHROWER) then
593 begin
594 // workaround for flamethrower
595 chk := GetWeapPoints(config.ReadStr(AnimNames[bb], WeapNames[WEAPON_PLASMA]+'_points', ''), aa, bb, TDirection.D_RIGHT,
596 config.ReadInt(AnimNames[bb], 'frames', 0),
597 config.ReadBool(AnimNames[bb], 'backanim', False),
598 WeaponPoints);
599 if chk then
600 for f := 0 to High(WeaponPoints[aa, bb, TDirection.D_RIGHT]) do
601 begin
602 case bb of
603 A_STAND, A_PAIN:
604 begin
605 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 6);
606 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 8);
607 end;
608 A_WALKATTACK, A_WALK:
609 begin
610 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 9);
611 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 9);
612 end;
613 A_ATTACK:
614 begin
615 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 5);
616 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 8);
617 end;
618 A_WALKSEEUP, A_SEEUP:
619 begin
620 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 5);
621 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 16);
622 end;
623 A_WALKSEEDOWN, A_SEEDOWN:
624 begin
625 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 6);
626 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 5);
627 end;
628 A_WALKATTACKUP, A_ATTACKUP:
629 begin
630 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 5);
631 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 16);
632 end;
633 A_WALKATTACKDOWN, A_ATTACKDOWN:
634 begin
635 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 6);
636 Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 4);
637 end;
638 end;
639 end;
640 end;
641 ok := ok and (chk or (bb > A_LASTBASE));
643 if not GetWeapPoints(config.ReadStr(AnimNames[bb], WeapNames[aa]+'2_points', ''), aa, bb, TDirection.D_LEFT,
644 config.ReadInt(AnimNames[bb], 'frames', 0),
645 config.ReadBool(AnimNames[bb], 'backanim', False),
646 WeaponPoints) then
647 for f := 0 to High(WeaponPoints[aa, bb, TDirection.D_RIGHT]) do
648 begin
649 WeaponPoints[aa, bb, TDirection.D_LEFT, f].X := -WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X;
650 WeaponPoints[aa, bb, TDirection.D_LEFT, f].Y := WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y;
651 end;
653 if not ok then Break;
654 end;
655 {if ok then g_Console_Add(Info.Name+' weapon points ok')
656 else g_Console_Add(Info.Name+' weapon points fail');}
657 Info.HaveWeapon := ok;
659 s := config.ReadStr('Model', 'flag_point', '');
660 if not GetPoint(s, FlagPoint) then FlagPoint := FLAG_DEFPOINT;
662 FlagAngle := config.ReadInt('Model', 'flag_angle', FLAG_DEFANGLE);
663 end;
665 config.Free();
666 WAD.Free();
668 Result := True;
669 end;
671 function g_PlayerModel_Get(ModelName: String): TPlayerModel;
673 a: Integer;
674 b: Byte;
675 ID, ID2: DWORD;
676 begin
677 Result := nil;
679 if PlayerModelsArray = nil then Exit;
681 for a := 0 to High(PlayerModelsArray) do
682 if AnsiLowerCase(PlayerModelsArray[a].Info.Name) = AnsiLowerCase(ModelName) then
683 begin
684 Result := TPlayerModel.Create;
686 with PlayerModelsArray[a] do
687 begin
688 Result.FName := Info.Name;
689 Result.FBlood := Blood;
691 for b := A_STAND to A_LAST do
692 begin
693 if not (g_Frames_Get(ID, Info.Name+'_RIGHTANIM'+IntToStr(b)) and
694 g_Frames_Get(ID2, Info.Name+'_RIGHTANIM'+IntToStr(b)+'_MASK')) then
695 begin
696 FreeAndNil(Result);
697 Exit;
698 end;
700 Result.FAnim[TDirection.D_RIGHT][b] := TAnimation.Create(ID, b in [A_STAND, A_WALK], ModelSpeed[b]);
701 Result.FMaskAnim[TDirection.D_RIGHT][b] := TAnimation.Create(ID2, b in [A_STAND, A_WALK], ModelSpeed[b]);
703 if g_Frames_Exists(Info.Name+'_LEFTANIM'+IntToStr(b)) and
704 g_Frames_Exists(Info.Name+'_LEFTANIM'+IntToStr(b)+'_MASK') then
705 if g_Frames_Get(ID, Info.Name+'_LEFTANIM'+IntToStr(b)) and
706 g_Frames_Get(ID2, Info.Name+'_LEFTANIM'+IntToStr(b)+'_MASK') then
707 begin
708 Result.FAnim[TDirection.D_LEFT][b] := TAnimation.Create(ID, b in [A_STAND, A_WALK], ModelSpeed[b]);
709 Result.FMaskAnim[TDirection.D_LEFT][b] := TAnimation.Create(ID2, b in [A_STAND, A_WALK], ModelSpeed[b]);
710 end;
711 end;
713 {$IFDEF ENABLE_SOUND}
714 Result.FPainSounds := PainSounds;
715 Result.FDieSounds := DieSounds;
716 Result.FSlopSound := SlopSound;
717 {$ENDIF}
718 Result.FDrawWeapon := Info.HaveWeapon;
719 Result.FWeaponPoints := WeaponPoints;
721 Result.FFlagPoint := FlagPoint;
722 Result.FFlagAngle := FlagAngle;
724 Break;
725 end;
726 end;
727 end;
729 function g_PlayerModel_GetAnim(ModelName: string; Anim: Byte; var _Anim, _Mask: TAnimation): Boolean;
731 a: Integer;
732 c: Boolean;
733 ID: DWORD;
734 begin
735 _Anim := nil;
736 _Mask := nil;
737 Result := False;
739 for a := 0 to High(PlayerModelsArray) do
740 if PlayerModelsArray[a].Info.Name = ModelName then
741 with PlayerModelsArray[a] do
742 begin
743 c := Anim in [A_STAND, A_WALK];
745 if not g_Frames_Get(ID, Info.Name+'_RIGHTANIM'+IntToStr(Anim)) then
746 if not g_Frames_Get(ID, Info.Name+'_LEFTANIM'+IntToStr(Anim)) then
747 Exit;
749 _Anim := TAnimation.Create(ID, c, ModelSpeed[Anim]);
750 _Anim.Speed := ModelSpeed[Anim];
752 if not g_Frames_Get(ID, Info.Name+'_RIGHTANIM'+IntToStr(Anim)+'_MASK') then
753 if not g_Frames_Get(ID, Info.Name+'_LEFTANIM'+IntToStr(Anim)+'_MASK') then
754 Exit;
756 _Mask := TAnimation.Create(ID, c, ModelSpeed[Anim]);
757 _Mask.Speed := ModelSpeed[Anim];
759 Result := True;
760 Exit;
761 end;
762 end;
764 function g_PlayerModel_GetGibs(ModelName: string; var Gibs: TGibsArray): Boolean;
766 a, i, b: Integer;
767 c: Boolean;
768 begin
769 Result := False;
771 if PlayerModelsArray = nil then Exit;
772 if gGibsCount = 0 then Exit;
774 c := False;
776 SetLength(Gibs, gGibsCount);
778 for a := 0 to High(PlayerModelsArray) do
779 if PlayerModelsArray[a].Info.Name = ModelName then
780 begin
781 for i := 0 to High(Gibs) do
782 begin
783 if c and (Length(PlayerModelsArray[a].Gibs) = 1) then
784 begin
785 SetLength(Gibs, i);
786 Break;
787 end;
789 repeat
790 b := Random(Length(PlayerModelsArray[a].Gibs));
791 until not (PlayerModelsArray[a].Gibs[b].OnlyOne and c);
793 Gibs[i] := PlayerModelsArray[a].Gibs[b];
795 if Gibs[i].OnlyOne then c := True;
796 end;
798 Result := True;
799 Break;
800 end;
801 end;
803 function g_PlayerModel_GetNames(): SSArray;
805 i: DWORD;
806 begin
807 Result := nil;
809 if PlayerModelsArray = nil then Exit;
811 for i := 0 to High(PlayerModelsArray) do
812 begin
813 SetLength(Result, Length(Result)+1);
814 Result[High(Result)] := PlayerModelsArray[i].Info.Name;
815 end;
816 end;
818 function g_PlayerModel_GetInfo(ModelName: string): TModelInfo;
820 a: Integer;
821 begin
822 Result := Default(TModelInfo);
823 if PlayerModelsArray = nil then Exit;
825 for a := 0 to High(PlayerModelsArray) do
826 if PlayerModelsArray[a].Info.Name = ModelName then
827 begin
828 Result := PlayerModelsArray[a].Info;
829 Break;
830 end;
831 end;
833 function g_PlayerModel_GetBlood(ModelName: string): TModelBlood;
835 a: Integer;
836 begin
837 Result.R := 150;
838 Result.G := 0;
839 Result.B := 0;
840 Result.Kind := BLOOD_NORMAL;
841 if PlayerModelsArray = nil then Exit;
843 for a := 0 to High(PlayerModelsArray) do
844 if PlayerModelsArray[a].Info.Name = ModelName then
845 begin
846 Result := PlayerModelsArray[a].Blood;
847 Break;
848 end;
849 end;
851 procedure g_PlayerModel_FreeData();
853 i: DWORD;
854 a, b, c: Integer;
855 begin
856 for a := WP_FIRST + 1 to WP_LAST do
857 for b := W_POS_NORMAL to W_POS_DOWN do
858 for c := W_ACT_NORMAL to W_ACT_FIRE do
859 e_DeleteTexture(WeaponID[a][b][c]);
861 e_WriteLog('Releasing models...', TMsgType.Notify);
863 if PlayerModelsArray = nil then Exit;
865 for i := 0 to High(PlayerModelsArray) do
866 with PlayerModelsArray[i] do
867 begin
868 for a := A_STAND to A_LAST do
869 begin
870 g_Frames_DeleteByName(Info.Name+'_LEFTANIM'+IntToStr(a));
871 g_Frames_DeleteByName(Info.Name+'_LEFTANIM'+IntToStr(a)+'_MASK');
872 g_Frames_DeleteByName(Info.Name+'_RIGHTANIM'+IntToStr(a));
873 g_Frames_DeleteByName(Info.Name+'_RIGHTANIM'+IntToStr(a)+'_MASK');
874 end;
876 {$IFDEF ENABLE_SOUND}
877 if PainSounds <> nil then
878 for b := 0 to High(PainSounds) do
879 e_DeleteSound(PainSounds[b].ID);
881 if DieSounds <> nil then
882 for b := 0 to High(DieSounds) do
883 e_DeleteSound(DieSounds[b].ID);
884 {$ENDIF}
886 if Gibs <> nil then
887 for b := 0 to High(Gibs) do
888 begin
889 e_DeleteTexture(Gibs[b].ID);
890 e_DeleteTexture(Gibs[b].MaskID);
891 end;
892 end;
894 PlayerModelsArray := nil;
895 end;
897 function g_PlayerModel_MakeColor(const aColor: TRGB; aTeam: Byte): TRGB;
899 TeamTone, EnemyTone, TintTone: Byte;
900 begin
901 case aTeam of
902 TEAM_RED: begin
903 TeamTone := Max(191, aColor.R); // 191..255
904 TintTone := aColor.G div 3; // 0..85
905 EnemyTone := nclamp(aColor.B - 160, 0, TintTone); // 0..tint..95
906 Result := _RGB(TeamTone, TintTone, EnemyTone);
907 end;
909 TEAM_BLUE: begin
910 TeamTone := Max(127, aColor.B); // 127..255
911 TintTone := aColor.G div 3; // 0..85
912 EnemyTone := nclamp(aColor.R - 192, 0, TintTone); // 0..tint..63
913 Result := _RGB(EnemyTone, TintTone, TeamTone);
914 end;
916 else
917 Result := aColor;
918 end;
919 end;
921 { TPlayerModel }
923 procedure TPlayerModel.ChangeAnimation(Animation: Byte; Force: Boolean = False);
924 begin
925 if not Force then if FCurrentAnimation = Animation then Exit;
927 FCurrentAnimation := Animation;
929 if (FDirection = TDirection.D_LEFT) and
930 (FAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) and
931 (FMaskAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
932 begin
933 FAnim[TDirection.D_LEFT][FCurrentAnimation].Reset;
934 FMaskAnim[TDirection.D_LEFT][FCurrentAnimation].Reset;
936 else
937 begin
938 FAnim[TDirection.D_RIGHT][FCurrentAnimation].Reset;
939 FMaskAnim[TDirection.D_RIGHT][FCurrentAnimation].Reset;
940 end;
941 end;
943 destructor TPlayerModel.Destroy();
945 a: Byte;
946 begin
947 for a := A_STAND to A_LAST do
948 begin
949 FAnim[TDirection.D_LEFT][a].Free();
950 FMaskAnim[TDirection.D_LEFT][a].Free();
951 FAnim[TDirection.D_RIGHT][a].Free();
952 FMaskAnim[TDirection.D_RIGHT][a].Free();
953 end;
955 inherited;
956 end;
958 procedure TPlayerModel.Draw(X, Y: Integer; Alpha: Byte = 0);
960 Mirror: TMirrorType;
961 pos, act: Byte;
962 p: TDFPoint;
963 begin
964 // Ôëàãè:
965 if Direction = TDirection.D_LEFT then
966 Mirror := TMirrorType.None
967 else
968 Mirror := TMirrorType.Horizontal;
970 if (FFlag <> FLAG_NONE) and (FFlagAnim <> nil) and
971 (not (FCurrentAnimation in [A_DIE1, A_DIE2])) then
972 begin
973 p.X := IfThen(Direction = TDirection.D_LEFT,
974 FLAG_BASEPOINT.X,
975 64-FLAG_BASEPOINT.X);
976 p.Y := FLAG_BASEPOINT.Y;
978 FFlagAnim.DrawEx(X+IfThen(Direction = TDirection.D_LEFT, FFlagPoint.X-1, 2*FLAG_BASEPOINT.X-FFlagPoint.X+1)-FLAG_BASEPOINT.X,
979 Y+FFlagPoint.Y-FLAG_BASEPOINT.Y+1, Mirror, p,
980 IfThen(FDirection = TDirection.D_RIGHT, FFlagAngle, -FFlagAngle));
981 end;
983 // Îðóæèå:
984 if Direction = TDirection.D_RIGHT
985 then Mirror := TMirrorType.None
986 else Mirror := TMirrorType.Horizontal;
988 if FDrawWeapon and
989 (not (FCurrentAnimation in [A_DIE1, A_DIE2, A_PAIN])) and
990 (FCurrentWeapon in [WP_FIRST + 1..WP_LAST]) then
991 begin
992 if FCurrentAnimation in [A_SEEUP, A_ATTACKUP] then
993 pos := W_POS_UP
994 else
995 if FCurrentAnimation in [A_SEEDOWN, A_ATTACKDOWN]
996 then pos := W_POS_DOWN
997 else pos := W_POS_NORMAL;
999 if (FCurrentAnimation in [A_ATTACK, A_ATTACKUP, A_ATTACKDOWN]) or FFire
1000 then act := W_ACT_FIRE
1001 else act := W_ACT_NORMAL;
1003 if Alpha < 201 then
1004 e_Draw(WeaponID[FCurrentWeapon][pos][act],
1005 X+FWeaponPoints[FCurrentWeapon, FCurrentAnimation, FDirection,
1006 FAnim[TDirection.D_RIGHT][FCurrentAnimation].CurrentFrame].X,
1007 Y+FWeaponPoints[FCurrentWeapon, FCurrentAnimation, FDirection,
1008 FAnim[TDirection.D_RIGHT][FCurrentAnimation].CurrentFrame].Y,
1009 0, True, False, Mirror);
1010 end;
1012 // Ìîäåëü:
1013 if (FDirection = TDirection.D_LEFT) and
1014 (FAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
1015 begin
1016 FAnim[TDirection.D_LEFT][FCurrentAnimation].Alpha := Alpha;
1017 FAnim[TDirection.D_LEFT][FCurrentAnimation].Draw(X, Y, TMirrorType.None);
1019 else
1020 begin
1021 FAnim[TDirection.D_RIGHT][FCurrentAnimation].Alpha := Alpha;
1022 FAnim[TDirection.D_RIGHT][FCurrentAnimation].Draw(X, Y, Mirror);
1023 end;
1025 // Ìàñêà ìîäåëè:
1026 e_Colors := FColor;
1028 if (FDirection = TDirection.D_LEFT) and
1029 (FMaskAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
1030 begin
1031 FMaskAnim[TDirection.D_LEFT][FCurrentAnimation].Alpha := Alpha;
1032 FMaskAnim[TDirection.D_LEFT][FCurrentAnimation].Draw(X, Y, TMirrorType.None);
1034 else
1035 begin
1036 FMaskAnim[TDirection.D_RIGHT][FCurrentAnimation].Alpha := Alpha;
1037 FMaskAnim[TDirection.D_RIGHT][FCurrentAnimation].Draw(X, Y, Mirror);
1038 end;
1040 e_Colors.R := 255;
1041 e_Colors.G := 255;
1042 e_Colors.B := 255;
1043 end;
1045 function TPlayerModel.GetCurrentAnimation: TAnimation;
1046 begin
1047 if (FDirection = TDirection.D_LEFT) and (FAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
1048 Result := FAnim[TDirection.D_LEFT][FCurrentAnimation]
1049 else
1050 Result := FAnim[TDirection.D_RIGHT][FCurrentAnimation];
1051 end;
1053 function TPlayerModel.GetCurrentAnimationMask: TAnimation;
1054 begin
1055 if (FDirection = TDirection.D_LEFT) and (FMaskAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
1056 Result := FMaskAnim[TDirection.D_LEFT][FCurrentAnimation]
1057 else
1058 Result := FMaskAnim[TDirection.D_RIGHT][FCurrentAnimation];
1059 end;
1061 {$IFDEF ENABLE_SOUND}
1062 function TPlayerModel.PlaySound(SoundType, Level: Byte; X, Y: Integer): Boolean;
1064 TempArray: array of DWORD;
1065 a: Integer;
1066 begin
1067 Result := False;
1068 SetLength(TempArray, 0);
1070 if SoundType = MODELSOUND_PAIN then
1071 begin
1072 if FPainSounds = nil then Exit;
1074 for a := 0 to High(FPainSounds) do
1075 if FPainSounds[a].Level = Level then
1076 begin
1077 SetLength(TempArray, Length(TempArray)+1);
1078 TempArray[High(TempArray)] := FPainSounds[a].ID;
1079 end;
1081 else
1082 begin
1083 if (Level in [2, 3, 5]) and (FSlopSound > 0) then
1084 begin
1085 g_Sound_PlayExAt('SOUND_MONSTER_SLOP', X, Y);
1086 if FSlopSound = 1 then
1087 begin
1088 Result := True;
1089 Exit;
1090 end;
1091 end;
1092 if FDieSounds = nil then Exit;
1094 for a := 0 to High(FDieSounds) do
1095 if FDieSounds[a].Level = Level then
1096 begin
1097 SetLength(TempArray, Length(TempArray)+1);
1098 TempArray[High(TempArray)] := FDieSounds[a].ID;
1099 end;
1100 if (TempArray = nil) and (Level = 5) then
1101 begin
1102 g_Sound_PlayExAt('SOUND_MONSTER_SLOP', X, Y);
1103 Result := True;
1104 Exit;
1105 end;
1106 end;
1108 if TempArray = nil then Exit;
1110 g_Sound_PlayAt(TempArray[Random(Length(TempArray))], X, Y);
1112 Result := True;
1113 end;
1114 {$ENDIF}
1116 procedure TPlayerModel.SetColor(Red, Green, Blue: Byte);
1117 begin
1118 FColor.R := Red;
1119 FColor.G := Green;
1120 FColor.B := Blue;
1121 end;
1123 procedure TPlayerModel.SetFire(Fire: Boolean);
1124 begin
1125 FFire := Fire;
1127 if FFire then FFireCounter := FAnim[TDirection.D_RIGHT, A_ATTACK].Speed*FAnim[TDirection.D_RIGHT, A_ATTACK].TotalFrames
1128 else FFireCounter := 0;
1129 end;
1131 procedure TPlayerModel.SetFlag(Flag: Byte);
1133 id: DWORD;
1134 begin
1135 FFlag := Flag;
1137 FFlagAnim.Free();
1138 FFlagAnim := nil;
1140 case Flag of
1141 FLAG_RED: g_Frames_Get(id, 'FRAMES_FLAG_RED');
1142 FLAG_BLUE: g_Frames_Get(id, 'FRAMES_FLAG_BLUE');
1143 else Exit;
1144 end;
1146 FFlagAnim := TAnimation.Create(id, True, 8);
1147 end;
1149 procedure TPlayerModel.SetWeapon(Weapon: Byte);
1150 begin
1151 FCurrentWeapon := Weapon;
1152 end;
1154 procedure TPlayerModel.InvertDirection();
1155 begin
1156 if Direction = TDirection.D_LEFT
1157 then Direction := TDirection.D_RIGHT
1158 else Direction := TDirection.D_LEFT;
1159 end;
1161 procedure TPlayerModel.Update();
1162 begin
1163 if (FDirection = TDirection.D_LEFT) and (FAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
1164 FAnim[TDirection.D_LEFT][FCurrentAnimation].Update else FAnim[TDirection.D_RIGHT][FCurrentAnimation].Update;
1166 if (FDirection = TDirection.D_LEFT) and (FMaskAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
1167 FMaskAnim[TDirection.D_LEFT][FCurrentAnimation].Update else FMaskAnim[TDirection.D_RIGHT][FCurrentAnimation].Update;
1169 if FFlagAnim <> nil then FFlagAnim.Update;
1171 if FFireCounter > 0 then Dec(FFireCounter) else FFire := False;
1172 end;
1174 end.