DEADSOFTWARE

game: disable corpses for server
[d2df-sdl.git] / src / game / g_monsters.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 {.$DEFINE D2F_DEBUG_MONS_MOVE}
18 unit g_monsters;
20 interface
22 uses
23 SysUtils, Classes,
24 mempool,
25 MAPDEF,
26 g_base, g_basic, g_phys, g_textures, g_grid,
27 g_saveload, g_panel, xprofiler;
29 const
30 MONSTATE_SLEEP = 0;
31 MONSTATE_GO = 1;
32 MONSTATE_RUN = 2;
33 MONSTATE_CLIMB = 3;
34 MONSTATE_DIE = 4;
35 MONSTATE_DEAD = 5;
36 MONSTATE_ATTACK = 6;
37 MONSTATE_SHOOT = 7;
38 MONSTATE_PAIN = 8;
39 MONSTATE_WAIT = 9;
40 MONSTATE_REVIVE = 10;
41 MONSTATE_RUNOUT = 11;
43 MON_BURN_TIME = 100;
45 { in mapdef now
46 BH_NORMAL = 0;
47 BH_KILLER = 1;
48 BH_MANIAC = 2;
49 BH_INSANE = 3;
50 BH_CANNIBAL = 4;
51 BH_GOOD = 5;
52 }
54 type
55 ADirectedAnim = Array of Array [TDirection.D_LEFT..TDirection.D_RIGHT] of TAnimationState;
57 PMonster = ^TMonster;
58 TMonster = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF}
59 private
60 FMonsterType: Byte;
61 FUID: Word;
62 FDirection: TDirection;
63 FStartDirection: TDirection;
64 FStartX, FStartY: Integer;
65 FRemoved: Boolean;
66 FHealth: Integer;
67 FMaxHealth: Integer;
68 FState: Byte;
69 FCurAnim: Byte;
70 FAnim: ADirectedAnim;
71 FTargetUID: Word;
72 FTargetTime: Integer;
73 FBehaviour: Byte;
74 FAmmo: Integer;
75 FPain: Integer;
76 FSleep: Integer;
77 FPainSound: Boolean;
78 FPainTicks: Integer;
79 FWaitAttackAnim: Boolean;
80 FChainFire: Boolean;
81 tx, ty: Integer;
82 FStartID: Integer;
83 FObj: TObj;
84 {$IFDEF ENABLE_GFX}
85 FBloodRed: Byte;
86 FBloodGreen: Byte;
87 FBloodBlue: Byte;
88 FBloodKind: Byte;
89 {$ENDIF}
90 {$IFDEF ENABLE_SHELLS}
91 FShellTimer: Integer;
92 FShellType: Byte;
93 {$ENDIF}
94 FFirePainTime: Integer;
95 FFireAttacker: Word;
96 vilefire: TAnimationState;
97 mProxyId: Integer; // node in dyntree or -1
98 mArrIdx: Integer; // in gMonsters
100 FDieTriggers: Array of Integer;
101 FSpawnTrigger: Integer;
103 mNeedSend: Boolean; // for network
105 mEDamageType: Integer;
107 procedure Turn();
108 function findNewPrey(): Boolean;
109 procedure ActivateTriggers();
111 procedure setGameX (v: Integer); inline;
112 procedure setGameY (v: Integer); inline;
114 procedure doDamage (v: Integer);
116 public
117 FNoRespawn: Boolean;
118 FFireTime: Integer;
119 trapCheckFrameId: DWord; // for `g_weapons.CheckTrap()`
120 mplatCheckFrameId: LongWord;
122 constructor Create(MonsterType: Byte; aID: Integer; ForcedUID: Integer = -1);
123 destructor Destroy(); override;
124 function Collide(X, Y: Integer; Width, Height: Word): Boolean; overload;
125 function Collide(Panel: TPanel): Boolean; overload;
126 function Collide(X, Y: Integer): Boolean; overload;
127 function TeleportTo(X, Y: Integer; silent: Boolean; dir: Byte): Boolean;
128 function alive(): Boolean;
129 procedure SetHealth(aH: Integer);
130 procedure Push(vx, vy: Integer);
131 function Damage(aDamage: Word; VelX, VelY: Integer; SpawnerUID: Word; t: Byte): Boolean;
132 function Heal(Value: Word): Boolean;
133 procedure BFGHit();
134 procedure PreUpdate();
135 procedure Update();
136 procedure ClientUpdate();
137 procedure ClientAttack(wx, wy, atx, aty: Integer);
138 procedure SetDeadAnim;
139 procedure WakeUp();
140 procedure WakeUpSound();
141 procedure DieSound();
142 procedure PainSound();
143 procedure ActionSound();
144 procedure AddTrigger(t: Integer);
145 procedure ClearTriggers();
146 procedure Respawn();
147 procedure SaveState (st: TStream);
148 procedure LoadState (st: TStream);
149 procedure SetState(State: Byte; ForceAnim: Byte = 255);
150 procedure MakeBloodVector(Count: Word; VelX, VelY: Integer);
151 procedure MakeBloodSimple(Count: Word);
152 procedure RevertAnim(R: Boolean = True);
153 function AnimIsReverse: Boolean;
154 function shoot(o: PObj; immediately: Boolean): Boolean;
155 function kick(o: PObj): Boolean;
156 procedure CatchFire(Attacker: Word; Timeout: Integer = MON_BURN_TIME);
157 procedure OnFireFlame(Times: DWORD = 1);
159 procedure positionChanged (); //WARNING! call this after monster position was changed, or coldet will not work right!
161 procedure setPosition (ax, ay: Integer; callPosChanged: Boolean=true); inline;
162 procedure moveBy (dx, dy: Integer); inline;
164 procedure getMapBox (out x, y, w, h: Integer); inline;
166 // get-and-clear
167 function gncNeedSend (): Boolean; inline;
168 procedure setDirty (); inline; // why `dirty`? 'cause i may introduce property `needSend` later
170 public
171 property Obj: TObj read FObj;
173 property proxyId: Integer read mProxyId;
174 property arrIdx: Integer read mArrIdx;
176 property MonsterType: Byte read FMonsterType;
177 property MonsterHealth: Integer read FHealth write FHealth;
178 property MonsterAmmo: Integer read FAmmo write FAmmo;
179 property MonsterTargetUID: Word read FTargetUID write FTargetUID;
180 property MonsterTargetTime: Integer read FTargetTime write FTargetTime;
181 property MonsterBehaviour: Byte read FBehaviour write FBehaviour;
182 property MonsterSleep: Integer read FSleep write FSleep;
183 property MonsterState: Byte read FState write FState;
184 property MonsterRemoved: Boolean read FRemoved write FRemoved;
185 property MonsterPain: Integer read FPain write FPain;
186 property MonsterAnim: Byte read FCurAnim write FCurAnim;
188 property UID: Word read FUID write FUID;
189 property SpawnTrigger: Integer read FSpawnTrigger write FSpawnTrigger;
191 property GameX: Integer read FObj.X write setGameX;
192 property GameY: Integer read FObj.Y write setGameY;
193 property GameVelX: Integer read FObj.Vel.X write FObj.Vel.X;
194 property GameVelY: Integer read FObj.Vel.Y write FObj.Vel.Y;
195 property GameAccelX: Integer read FObj.Accel.X write FObj.Accel.X;
196 property GameAccelY: Integer read FObj.Accel.Y write FObj.Accel.Y;
197 property GameDirection: TDirection read FDirection write FDirection;
199 property StartID: Integer read FStartID;
201 property VileFireAnim: TAnimationState read vilefire;
202 property DirAnim: ADirectedAnim read FAnim;
204 published
205 property eMonsterType: Byte read FMonsterType;
206 property eMonsterHealth: Integer read FHealth write FHealth;
207 property eMonsterAmmo: Integer read FAmmo write FAmmo;
208 property eMonsterTargetUID: Word read FTargetUID write FTargetUID;
209 property eMonsterTargetTime: Integer read FTargetTime write FTargetTime;
210 property eMonsterBehaviour: Byte read FBehaviour write FBehaviour;
211 property eMonsterSleep: Integer read FSleep write FSleep;
212 property eMonsterState: Byte read FState write FState;
213 property eMonsterRemoved: Boolean read FRemoved;
214 property eMonsterPain: Integer read FPain write FPain;
215 property eMonsterAnim: Byte read FCurAnim;
217 property eUID: Word read FUID;
218 property eSpawnTrigger: Integer read FSpawnTrigger;
220 property eGameX: Integer read FObj.X write setGameX;
221 property eGameY: Integer read FObj.Y write setGameY;
222 property eGameVelX: Integer read FObj.Vel.X write FObj.Vel.X;
223 property eGameVelY: Integer read FObj.Vel.Y write FObj.Vel.Y;
224 property eGameAccelX: Integer read FObj.Accel.X write FObj.Accel.X;
225 property eGameAccelY: Integer read FObj.Accel.Y write FObj.Accel.Y;
226 property eGameDirection: TDirection read FDirection write FDirection;
228 property eStartID: Integer read FStartID;
230 // set this before assigning something to `eDamage`
231 property eDamageType: Integer read mEDamageType write mEDamageType;
232 property eDamage: Integer write doDamage;
233 end;
236 // will be called from map loader
237 procedure g_Mons_InitTree (x, y, w, h: Integer);
239 procedure g_Monsters_LoadData ();
240 procedure g_Monsters_FreeData ();
241 procedure g_Monsters_Init ();
242 procedure g_Monsters_Free (clearGrid: Boolean=true);
243 function g_Monsters_Create (MonsterType: Byte; X, Y: Integer; Direction: TDirection;
244 AdjCoord: Boolean = False; ForcedUID: Integer = -1): TMonster;
245 procedure g_Monsters_PreUpdate ();
246 procedure g_Monsters_Update ();
247 function g_Monsters_ByUID (UID: Word): TMonster;
248 procedure g_Monsters_killedp ();
249 procedure g_Monsters_SaveState (st: TStream);
250 procedure g_Monsters_LoadState (st: TStream);
252 function g_Mons_SpawnAt (monType: Integer; x, y: Integer; dir: TDirection=TDirection.D_LEFT): TMonster; overload;
253 function g_Mons_SpawnAt (const typeName: AnsiString; x, y: Integer; dir: TDirection=TDirection.D_LEFT): TMonster; overload;
255 function g_Mons_TypeLo (): Integer; inline;
256 function g_Mons_TypeHi (): Integer; inline;
258 function g_Mons_TypeIdByName (const name: AnsiString): Integer;
259 function g_Mons_NameByTypeId (monType: Integer): AnsiString;
260 function g_Mons_GetKilledByTypeId (monType: Integer): AnsiString;
263 type
264 TEachMonsterCB = function (mon: TMonster): Boolean is nested; // return `true` to stop
266 // throws on invalid uid
267 function g_Mons_ByIdx (uid: Integer): TMonster; inline;
269 // can return null
270 function g_Mons_ByIdx_NC (uid: Integer): TMonster; inline;
272 function g_Mons_TotalCount (): Integer; inline;
274 function g_Mons_IsAnyAliveAt (x, y: Integer; width, height: Integer): Boolean;
276 function g_Mons_ForEach (cb: TEachMonsterCB): Boolean;
277 function g_Mons_ForEachAlive (cb: TEachMonsterCB): Boolean;
279 function g_Mons_ForEachAt (x, y: Integer; width, height: Integer; cb: TEachMonsterCB): Boolean;
280 function g_Mons_ForEachAliveAt (x, y: Integer; width, height: Integer; cb: TEachMonsterCB): Boolean;
282 function g_Mons_getNewTrapFrameId (): DWord; inline;
283 function g_Mons_getNewMPlatFrameId (): LongWord; inline;
287 type
288 TMonsAlongLineCB = function (mon: TMonster; tag: Integer): Boolean is nested;
290 function g_Mons_AlongLine (x0, y0, x1, y1: Integer; cb: TMonsAlongLineCB; log: Boolean=false): TMonster;
294 var
295 gmon_debug_use_sqaccel: Boolean = true;
298 //HACK!
299 procedure g_Mons_ProfilersBegin ();
300 procedure g_Mons_ProfilersEnd ();
302 procedure g_Mons_LOS_Start (); inline;
303 procedure g_Mons_LOS_End (); inline;
305 var
306 profMonsLOS: TProfiler = nil; //WARNING: FOR DEBUGGING ONLY!
309 type
310 TMonsterGrid = specialize TBodyGridBase<TMonster>;
312 var
313 monsGrid: TMonsterGrid = nil; // DO NOT USE! public for debugging only!
316 var
317 gmon_debug_think: Boolean = true;
318 gmon_debug_one_think_step: Boolean = false;
320 var (* private state *)
321 gMonsters: array of TMonster;
323 const
324 ANIM_SLEEP = 0;
325 ANIM_GO = 1;
326 ANIM_DIE = 2;
327 ANIM_MESS = 3;
328 ANIM_ATTACK = 4;
329 ANIM_ATTACK2 = 5;
330 ANIM_PAIN = 6;
332 // Таблица характеристик монстров:
333 MONSTERTABLE: Array [MONSTER_DEMON..MONSTER_MAN] of
334 record
335 Name: String;
336 Rect: TRectWH;
337 Health: Word;
338 RunVel: Byte;
339 MinPain: Byte;
340 Pain: Byte;
341 Jump: Byte;
342 end =
343 ((Name:'DEMON'; Rect:(X:7; Y:8; Width:50; Height:52); Health:60;
344 RunVel: 7; MinPain: 10; Pain: 20; Jump: 10),
346 (Name:'IMP'; Rect:(X:15; Y:10; Width:34; Height:50); Health:25;
347 RunVel: 3; MinPain: 0; Pain: 15; Jump: 10),
349 (Name:'ZOMBY'; Rect:(X:15; Y:8; Width:34; Height:52); Health:15;
350 RunVel: 3; MinPain: 0; Pain: 10; Jump: 10),
352 (Name:'SERG'; Rect:(X:15; Y:8; Width:34; Height:52); Health:20;
353 RunVel: 3; MinPain: 0; Pain: 10; Jump: 10),
355 (Name:'CYBER'; Rect:(X:24; Y:9; Width:80; Height:110); Health:500;
356 RunVel: 5; MinPain: 50; Pain: 70; Jump: 10),
358 (Name:'CGUN'; Rect:(X:15; Y:4; Width:34; Height:56); Health:60;
359 RunVel: 3; MinPain: 10; Pain: 20; Jump: 10),
361 (Name:'BARON'; Rect:(X:39; Y:32; Width:50; Height:64); Health:150;
362 RunVel: 3; MinPain: 30; Pain: 40; Jump: 10),
364 (Name:'KNIGHT'; Rect:(X:39; Y:32; Width:50; Height:64); Health:75;
365 RunVel: 3; MinPain: 30; Pain: 40; Jump: 10),
367 (Name:'CACO'; Rect:(X:34; Y:36; Width:60; Height:56); Health:100;
368 RunVel: 4; MinPain: 0; Pain: 10; Jump: 4),
370 (Name:'SOUL'; Rect:(X:16; Y:14; Width:32; Height:36); Health:60;
371 RunVel: 4; MinPain: 0; Pain: 10; Jump: 4),
373 (Name:'PAIN'; Rect:(X:34; Y:36; Width:60; Height:56); Health:100;
374 RunVel: 4; MinPain: 0; Pain: 10; Jump: 4),
376 (Name:'SPIDER'; Rect:(X:23; Y:14; Width:210; Height:100); Health:500;
377 RunVel: 4; MinPain: 50; Pain: 70; Jump: 10),
379 (Name:'BSP'; Rect:(X:14; Y:17; Width:100; Height:42); Health:150;
380 RunVel: 4; MinPain: 0; Pain: 20; Jump: 10),
382 (Name:'MANCUB'; Rect:(X:28; Y:34; Width:72; Height:60); Health:200;
383 RunVel: 3; MinPain: 20; Pain: 40; Jump: 7),
385 (Name:'SKEL'; Rect:(X:30; Y:28; Width:68; Height:72); Health:200;
386 RunVel: 6; MinPain: 20; Pain: 40; Jump: 11),
388 (Name:'VILE'; Rect:(X:30; Y:28; Width:68; Height:72); Health:150;
389 RunVel: 7; MinPain: 10; Pain: 30; Jump: 12),
391 (Name:'FISH'; Rect:(X:6; Y:11; Width:20; Height:10); Health:35;
392 RunVel: 14; MinPain: 10; Pain: 20; Jump: 6),
394 (Name:'BARREL'; Rect:(X:20; Y:13; Width:24; Height:36); Health:20;
395 RunVel: 0; MinPain: 0; Pain: 0; Jump: 0),
397 (Name:'ROBO'; Rect:(X:30; Y:26; Width:68; Height:76); Health:20;
398 RunVel: 3; MinPain: 20; Pain: 40; Jump: 6),
400 (Name:'MAN'; Rect:(X:15; Y:6; Width:34; Height:52); Health:400;
401 RunVel: 8; MinPain: 50; Pain: 70; Jump: 10));
403 // Таблица параметров анимации монстров:
404 MONSTER_ANIMTABLE: Array [MONSTER_DEMON..MONSTER_MAN] of
405 record
406 LeftAnim: Boolean;
407 wX, wY: Integer; // Откуда вылетит пуля
408 AnimSpeed: Array [ANIM_SLEEP..ANIM_PAIN] of Byte;
409 AnimCount: Array [ANIM_SLEEP..ANIM_PAIN] of Byte;
410 AnimDeltaRight: Array [ANIM_SLEEP..ANIM_PAIN] of TDFPoint;
411 AnimDeltaLeft: Array [ANIM_SLEEP..ANIM_PAIN] of TDFPoint;
412 end = // SLEEP GO DIE MESS ATTACK ATTACK2 PAIN
413 ((LeftAnim: False; wX: 54; wY: 32; AnimSpeed:(3, 2, 3, 2, 3, 0, 4); //DEMON
414 AnimCount: (2, 4, 6, 6, 3, 0, 1);
415 AnimDeltaRight: ((X: 1; Y: 4), (X: 1; Y: 4), (X: 0; Y: 4), (X: 0; Y: 4), (X: 2; Y: 6), (X: 2; Y: 6), (X: 2; Y: 5));
416 AnimDeltaLeft: ((X: 1; Y: 4), (X: 1; Y: 4), (X: 0; Y: 4), (X: 0; Y: 4), (X: 2; Y: 6), (X: 2; Y: 6), (X: 2; Y: 5))),
418 (LeftAnim: False; wX: 32; wY: 32; AnimSpeed:(3, 2, 3, 2, 3, 0, 4); //IMP
419 AnimCount: (2, 4, 5, 8, 3, 0, 1);
420 AnimDeltaRight: ((X: 8; Y: -4), (X: 8; Y: -4), (X: -2; Y: -1), (X: 3; Y: -2), (X: 14; Y: -4), (X: 14; Y: -4), (X: -5; Y: -4));
421 AnimDeltaLeft: ((X: 8; Y: -4), (X: 8; Y: -4), (X: -2; Y: -1), (X: 3; Y: -2), (X: 14; Y: -4), (X: 14; Y: -4), (X: -5; Y: -4))),
423 (LeftAnim: True; wX: 32; wY: 32; AnimSpeed:(3, 2, 3, 2, 3, 0, 4); //ZOMBY
424 AnimCount: (2, 4, 6, 9, 2, 0, 1);
425 AnimDeltaRight: ((X: 1; Y: -4), (X: 1; Y: -4), (X: 3; Y: -1), (X: 2; Y: -1), (X: 2; Y: -4), (X: 2; Y: -4), (X: 1; Y: -4));
426 AnimDeltaLeft: ((X: 1; Y: -4), (X: 1; Y: -4), (X: 3; Y: -1), (X: 2; Y: -1), (X: 2; Y: -4), (X: 2; Y: -4), (X: 1; Y: -4))),
428 (LeftAnim: True; wX: 32; wY: 32; AnimSpeed:(3, 2, 3, 2, 3, 0, 4); //SERG
429 AnimCount: (2, 4, 5, 9, 2, 0, 1);
430 AnimDeltaRight: ((X: 0; Y: -4), (X: 0; Y: -4), (X: -3; Y: -1), (X: -4; Y: -1), (X: 1; Y: -4), (X: 1; Y: -4), (X: 0; Y: -4));
431 AnimDeltaLeft: ((X: 0; Y: -4), (X: 0; Y: -4), (X: -3; Y: -1), (X: -4; Y: -1), (X: 1; Y: -4), (X: 1; Y: -4), (X: 0; Y: -4))),
433 (LeftAnim: True; wX: 70; wY: 73; AnimSpeed:(3, 3, 3, 3, 3, 4, 3); //CYBER
434 AnimCount: (2, 4, 9, 9, 2, 2, 1);
435 AnimDeltaRight: ((X: 2; Y: -6), (X: 2; Y: -6), (X: -3; Y: -4), (X: -3; Y: -4), (X: 25; Y: -6), (X: 0; Y: -6), (X: -2; Y: -6));
436 AnimDeltaLeft: ((X: 3; Y: -3), (X: 3; Y: -3), (X: -3; Y: -4), (X: -3; Y: -4), (X:-26; Y: -3), (X:-1; Y: -3), (X: 1; Y: -3))),
438 (LeftAnim: True; wX: 32; wY: 32; AnimSpeed:(3, 2, 2, 2, 1, 0, 4); //CGUN
439 AnimCount: (2, 4, 7, 6, 2, 0, 1);
440 AnimDeltaRight: ((X: -1; Y: -2), (X: -1; Y: -2), (X: -2; Y: 0), (X: -2; Y: 0), (X: 0; Y: -3), (X: 0; Y: -3), (X: -1; Y: -2));
441 AnimDeltaLeft: ((X: -1; Y: -2), (X: -1; Y: -2), (X: -2; Y: 0), (X: -2; Y: 0), (X: -1; Y: -4), (X: -1; Y: -4), (X: 2; Y: -4))),
443 (LeftAnim: True; wX: 64; wY: 64; AnimSpeed:(3, 2, 3, 4, 2, 0, 4); //BARON
444 AnimCount: (2, 4, 7, 7, 3, 0, 1);
445 AnimDeltaRight: ((X: 4; Y: 0), (X: 2; Y: 0), (X: -1; Y: -1), (X: -1; Y: -1), (X: 1; Y: 0), (X: 1; Y: 0), (X: -1; Y: 0));
446 AnimDeltaLeft: ((X: 0; Y: 0), (X: 2; Y: 0), (X: -1; Y: -1), (X: -1; Y: -1), (X: -2; Y: 0), (X: -2; Y: 0), (X: 1; Y: 0))),
448 (LeftAnim: True; wX: 64; wY: 64; AnimSpeed:(3, 2, 3, 4, 2, 0, 4); //KNIGHT
449 AnimCount: (2, 4, 7, 7, 3, 0, 1);
450 AnimDeltaRight: ((X: 4; Y: 0), (X: 2; Y: 0), (X: -1; Y: -1), (X: -1; Y: -1), (X: 1; Y: 0), (X: 1; Y: 0), (X: -1; Y: 0));
451 AnimDeltaLeft: ((X: 0; Y: 0), (X: 2; Y: 0), (X: -1; Y: -1), (X: -1; Y: -1), (X: -2; Y: 0), (X: -2; Y: 0), (X: 1; Y: 0))),
453 (LeftAnim: False; wX: 88; wY: 69; AnimSpeed:(3, 2, 3, 4, 2, 0, 4); //CACO
454 AnimCount: (1, 1, 7, 7, 6, 0, 1);
455 AnimDeltaRight: ((X: 0; Y: -4), (X: 0; Y: -4), (X: 0; Y: -5), (X: 0; Y: -5), (X: 0; Y: -4), (X: 0; Y: -4), (X: 0; Y: -4));
456 AnimDeltaLeft: ((X: 0; Y: -4), (X: 0; Y: -4), (X: 0; Y: -5), (X: 0; Y: -5), (X: 0; Y: -4), (X: 0; Y: -4), (X: 0; Y: -4))),
458 (LeftAnim: False; wX: 32; wY: 32; AnimSpeed:(3, 2, 3, 4, 1, 0, 4); //SOUL
459 AnimCount: (2, 2, 7, 7, 2, 0, 1); // 10
460 AnimDeltaRight: ((X: 1; Y:-10), (X: 1; Y:-10), (X:-33; Y:-34), (X:-33; Y:-34), (X:-16; Y:-10), (X:-16; Y:-10), (X: -1; Y: -7));
461 AnimDeltaLeft: ((X: 1; Y:-10), (X: 1; Y:-10), (X:-33; Y:-34), (X:-33; Y:-34), (X:-16; Y:-10), (X:-16; Y:-10), (X: -1; Y: -7))),
463 (LeftAnim: False; wX: 64; wY: 64; AnimSpeed:(3, 2, 3, 4, 2, 0, 4); //PAIN
464 AnimCount: (4, 4, 7, 7, 4, 0, 1);
465 AnimDeltaRight: ((X: -1; Y: -3), (X: -1; Y: -3), (X: -3; Y: 0), (X: -3; Y: 0), (X: -1; Y: -3), (X: -1; Y: -3), (X: -1; Y: -4));
466 AnimDeltaLeft: ((X: -1; Y: -3), (X: -1; Y: -3), (X: -3; Y: 0), (X: -3; Y: 0), (X: -1; Y: -3), (X: -1; Y: -3), (X: -1; Y: -4))),
468 (LeftAnim: True; wX: 128; wY: 64; AnimSpeed:(3, 2, 4, 4, 1, 0, 4); //SPIDER
469 AnimCount: (2, 6, 10, 10, 2, 0, 1);
470 AnimDeltaRight: ((X: -4; Y: -4), (X: -4; Y: -4), (X: -2; Y: 8), (X: -2; Y: 8), (X: -3; Y: -3), (X: -3; Y: -3), (X: -3; Y: -4));
471 AnimDeltaLeft: ((X: -4; Y: -4), (X: -4; Y: -4), (X: -2; Y: 8), (X: -2; Y: 8), (X: -3; Y: -3), (X: -3; Y: -3), (X: 18; Y: -5))),
473 (LeftAnim: True; wX: 64; wY: 32; AnimSpeed:(3, 2, 3, 4, 1, 0, 4); //BSP
474 AnimCount: (2, 6, 7, 7, 2, 0, 1);
475 AnimDeltaRight: ((X: 0; Y: -1), (X: 0; Y: -1), (X: -3; Y: 5), (X: -3; Y: 5), (X: 7; Y: -1), (X: 7; Y: -1), (X: 1; Y: -3));
476 AnimDeltaLeft: ((X: 0; Y: -1), (X: 0; Y: -1), (X: -3; Y: 5), (X: -3; Y: 5), (X: 7; Y: -1), (X: 7; Y: -1), (X: 6; Y: -3))),
478 (LeftAnim: False; wX: 64; wY: 64; AnimSpeed:(3, 2, 2, 4, 2, 0, 4); //MANCUB
479 AnimCount: (2, 6, 10, 10, 3, 0, 1);
480 AnimDeltaRight: ((X: -2; Y: -7), (X: -2; Y: -7), (X: -4; Y: -2), (X: -4; Y: -2), (X: -4; Y: -7), (X: -4; Y: -7), (X:-14; Y: -7));
481 AnimDeltaLeft: ((X: -2; Y: -7), (X: -2; Y: -7), (X: -4; Y: -2), (X: -4; Y: -2), (X: -4; Y: -7), (X: -4; Y: -7), (X:-14; Y: -7))),
483 (LeftAnim: True; wX: 64; wY: 32; AnimSpeed:(3, 3, 3, 3, 3, 3, 3); //SKEL
484 AnimCount: (2, 6, 5, 5, 2, 2, 1);
485 AnimDeltaRight: ((X: -1; Y: 4), (X: -1; Y: 4), (X: -2; Y: 4), (X: -2; Y: 4), (X: -1; Y: 4), (X: 6; Y: 2), (X:-24; Y: 4));
486 AnimDeltaLeft: ((X: 1; Y: 4), (X: -1; Y: 4), (X: -2; Y: 4), (X: -2; Y: 4), (X: -2; Y: 2), (X: -5; Y: 4), (X: 26; Y: 4))),
488 (LeftAnim: True; wX: 64; wY: 32; AnimSpeed:(3, 3, 3, 3, 3, 3, 3); //VILE
489 AnimCount: (2, 6, 9, 9, 10, 3, 1);
490 AnimDeltaRight: ((X: 5; Y:-21), (X: 5; Y:-21), (X: 1; Y:-21), (X: 1; Y:-21), (X: 8; Y:-23), (X: -1; Y:-23), (X: 4; Y:-20));
491 AnimDeltaLeft: ((X: -8; Y:-21), (X: 5; Y:-21), (X: 1; Y:-21), (X: 1; Y:-21), (X:-10; Y:-24), (X: 3; Y:-23), (X: -4; Y:-22))),
493 (LeftAnim: False; wX: 8; wY: 8; AnimSpeed:(2, 2, 2, 2, 3, 0, 1); //FISH
494 AnimCount: (2, 4, 1, 1, 2, 0, 3);
495 AnimDeltaRight: ((X: -1; Y: 0), (X: -1; Y: 0), (X: -2; Y: -1), (X: -2; Y: -1), (X: -1; Y: -1), (X: -1; Y: -1), (X: -1; Y: -1));
496 AnimDeltaLeft: ((X: -1; Y: 0), (X: -1; Y: 0), (X: -2; Y: -1), (X: -2; Y: -1), (X: -1; Y: -1), (X: -1; Y: -1), (X: -1; Y: -1 ))),
498 (LeftAnim: False; wX: 32; wY: 32; AnimSpeed:(3, 0, 3, 0, 0, 0, 5); //BARREL
499 AnimCount: (3, 0, 4, 0, 0, 0, 1);
500 AnimDeltaRight: ((X: 0; Y:-15), (X: 0; Y:-15), (X: -1; Y:-15), (X: -1; Y:-15), (X: 0; Y:-15), (X: 0; Y:-15), (X: 0; Y:-15));
501 AnimDeltaLeft: ((X: 0; Y:-15), (X: 0; Y:-15), (X: -1; Y:-15), (X: -1; Y:-15), (X: 0; Y:-15), (X: 0; Y:-15), (X: 0; Y:-15))),
503 (LeftAnim: False; wX: 95; wY: 57; AnimSpeed:(1, 2, 1, 0, 1, 1, 0); //ROBO
504 AnimCount: (1, 12, 1, 0, 2, 4, 0);
505 AnimDeltaRight: ((X: -2; Y:-26), (X: -2; Y:-26), (X: 0; Y:-26), (X: 0; Y:-26), (X: 2; Y:-26), (X: 15; Y:-26), (X: -2; Y:-26));
506 AnimDeltaLeft: ((X: -2; Y:-26), (X: -2; Y:-26), (X: 0; Y:-26), (X: 0; Y:-26), (X: 2; Y:-26), (X: 15; Y:-26), (X: -2; Y:-26))),
508 (LeftAnim: False; wX: 32; wY: 32; AnimSpeed:(3, 2, 2, 2, 2, 0, 5); //MAN
509 AnimCount: (2, 4, 7, 9, 2, 0, 1);
510 AnimDeltaRight: ((X: 0; Y: -6), (X: 0; Y: -6), (X: -2; Y: 0), (X: 2; Y: 0), (X: 1; Y: -6), (X: 1; Y: -6), (X: 0; Y: -6));
511 AnimDeltaLeft: ((X: 0; Y: -6), (X: 0; Y: -6), (X: -2; Y: 0), (X: 2; Y: 0), (X: 1; Y: -6), (X: 1; Y: -6), (X: 0; Y: -6))) );
513 // Таблица типов анимации монстров:
514 ANIMTABLE: Array [ANIM_SLEEP..ANIM_PAIN] of
515 record
516 name: String;
517 loop: Boolean;
518 end = ((name: 'SLEEP'; loop: True),
519 (name: 'GO'; loop: True),
520 (name: 'DIE'; loop: False),
521 (name: 'MESS'; loop: False),
522 (name: 'ATTACK'; loop: False),
523 (name: 'ATTACK2'; loop: False),
524 (name: 'PAIN'; loop: False));
525 implementation
527 uses
528 {$IFDEF ENABLE_MENU}
529 g_menu,
530 {$ENDIF}
531 {$IFDEF ENABLE_GFX}
532 g_gfx,
533 {$ENDIF}
534 {$IFDEF ENABLE_GIBS}
535 g_gibs,
536 {$ENDIF}
537 {$IFDEF ENABLE_SHELLS}
538 g_shells,
539 {$ENDIF}
540 {$IFDEF ENABLE_CORPSES}
541 g_corpses,
542 {$ENDIF}
543 e_log, g_sound, g_player, g_game,
544 g_weapons, g_triggers, g_items, g_options,
545 g_console, g_map, Math, wadreader,
546 g_language, g_netmsg, idpool, utils, xstreams;
549 function g_Look(a, b: PObj; d: TDirection): Boolean;
550 begin
551 if not gmon_dbg_los_enabled then begin result := false; exit; end; // always "wall hit"
553 if ((b^.X > a^.X) and (d = TDirection.D_LEFT)) or
554 ((b^.X < a^.X) and (d = TDirection.D_RIGHT)) then
555 begin
556 Result := False;
557 Exit;
558 end;
560 Result := g_TraceVector(a^.X+a^.Rect.X+(a^.Rect.Width div 2),
561 a^.Y+a^.Rect.Y+(a^.Rect.Height div 2),
562 b^.X+b^.Rect.X+(b^.Rect.Width div 2),
563 b^.Y+b^.Rect.Y+(b^.Rect.Height div 2));
564 end;
566 // ////////////////////////////////////////////////////////////////////////// //
567 procedure g_Mons_ProfilersBegin ();
568 begin
569 if (profMonsLOS = nil) then profMonsLOS := TProfiler.Create('LOS CALC', g_profile_history_size);
570 if (profMonsLOS <> nil) then profMonsLOS.mainBegin(g_profile_los);
571 if g_profile_los and (profMonsLOS <> nil) then
572 begin
573 profMonsLOS.sectionBegin('loscalc');
574 profMonsLOS.sectionEnd();
575 end;
576 end;
578 procedure g_Mons_ProfilersEnd ();
579 begin
580 if (profMonsLOS <> nil) and (g_profile_los) then profMonsLOS.mainEnd();
581 end;
583 procedure g_Mons_LOS_Start (); inline;
584 begin
585 if (profMonsLOS <> nil) then profMonsLOS.sectionBeginAccum('loscalc');
586 end;
588 procedure g_Mons_LOS_End (); inline;
589 begin
590 if (profMonsLOS <> nil) then profMonsLOS.sectionEnd();
591 end;
594 // ////////////////////////////////////////////////////////////////////////// //
595 var
596 monCheckTrapLastFrameId: DWord = 0;
597 monCheckMPlatLastFrameId: LongWord = 0;
600 procedure TMonster.getMapBox (out x, y, w, h: Integer); inline;
601 begin
602 x := FObj.X+FObj.Rect.X;
603 y := FObj.Y+FObj.Rect.Y;
604 w := FObj.Rect.Width;
605 h := FObj.Rect.Height;
606 end;
608 function TMonster.gncNeedSend (): Boolean; inline; begin result := mNeedSend; mNeedSend := false; end;
610 procedure TMonster.setDirty (); inline; begin mNeedSend := true; end;
613 // ////////////////////////////////////////////////////////////////////////// //
615 function g_Mons_AlongLine (x0, y0, x1, y1: Integer; cb: TMonsAlongLineCB; log: Boolean=false): TMonster;
616 begin
617 if not assigned(cb) then begin result := nil; exit; end;
618 result := monsGrid.forEachAlongLine(x0, y0, x1, y1, cb, -1, log);
619 end;
623 //WARNING! call this after monster position was changed, or coldet will not work right!
624 procedure TMonster.positionChanged ();
625 var
626 x, y, w, h: Integer;
627 nx, ny, nw, nh: Integer;
628 begin
629 {$IF DEFINED(D2F_DEBUG_MONS_MOVE)}
630 //e_WriteLog(Format('monster #%d(%u): pos=(%d,%d); rpos=(%d,%d)', [mArrIdx, UID, FObj.X, FObj.Y, FObj.Rect.X, FObj.Rect.Y]), MSG_NOTIFY);
631 {$ENDIF}
632 if (mProxyId = -1) then
633 begin
634 //mNeedSend := true;
635 mProxyId := monsGrid.insertBody(self, FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, FObj.Rect.Width, FObj.Rect.Height);
636 {$IF DEFINED(D2F_DEBUG_MONS_MOVE)}
637 monsGrid.getBodyXY(mProxyId, x, y);
638 e_WriteLog(Format('monster #%d:(%u): inserted into the grid; mProxyid=%d; gx=%d; gy=%d', [mArrIdx, UID, mProxyId, x-monsGrid.gridX0, y-monsGrid.gridY0]), MSG_NOTIFY);
639 {$ENDIF}
640 end
641 else
642 begin
643 monsGrid.getBodyDims(mProxyId, x, y, w, h);
644 getMapBox(nx, ny, nw, nh);
646 if (w <> nw) or (h <> nh) then
647 begin
648 //mNeedSend := true;
649 {$IF DEFINED(D2F_DEBUG_MONS_MOVE)}
650 e_WriteLog(Format('monster #%d:(%u): resized; mProxyid=%d; gx=%d; gy=%d', [mArrIdx, UID, mProxyId, x-monsGrid.gridX0, y-monsGrid.gridY0]), MSG_NOTIFY);
651 {$ENDIF}
652 monsGrid.moveResizeBody(mProxyId, nx, ny, nw, nh);
653 end
654 else if (x <> nx) or (y <> ny) then
655 begin
656 //mNeedSend := true;
657 {$IF DEFINED(D2F_DEBUG_MONS_MOVE)}
658 e_WriteLog(Format('monster #%d:(%u): updating grid; mProxyid=%d; gx=%d; gy=%d', [mArrIdx, UID, mProxyId, x-monsGrid.gridX0, y-monsGrid.gridY0]), MSG_NOTIFY);
659 {$ENDIF}
660 monsGrid.moveBody(mProxyId, nx, ny);
661 end
662 else
663 begin
664 exit; // nothing to do
665 end;
666 {$IF DEFINED(D2F_DEBUG_MONS_MOVE)}
667 monsGrid.getBodyXY(mProxyId, x, y);
668 e_WriteLog(Format('monster #%d:(%u): updated grid; mProxyid=%d; gx=%d; gy=%d', [mArrIdx, UID, mProxyId, x-monsGrid.gridX0, y-monsGrid.gridY0]), MSG_NOTIFY);
669 {$ENDIF}
670 end;
671 end;
674 // ////////////////////////////////////////////////////////////////////////// //
675 const
676 MONSTER_SIGNATURE = $534E4F4D; // 'MONS'
678 MAX_ATM = 89; // Время ожидания после потери цели
679 MAX_SOUL = 512; // Ограничение Lost_Soul'ов
682 // ////////////////////////////////////////////////////////////////////////// //
683 var
684 uidMap: array [0..65535] of TMonster; // monster knows it's index
685 freeInds: TIdPool = nil;
688 procedure clearUidMap ();
689 var
690 idx: Integer;
691 begin
692 for idx := 0 to High(uidMap) do uidMap[idx] := nil;
693 freeInds.clear();
694 end;
697 function g_Mons_getNewTrapFrameId (): DWord; inline;
698 var
699 f: Integer;
700 begin
701 Inc(monCheckTrapLastFrameId);
702 if (monCheckTrapLastFrameId = 0) then
703 begin
704 // wraparound
705 monCheckTrapLastFrameId := 1;
706 for f := 0 to High(gMonsters) do
707 begin
708 if (gMonsters[f] <> nil) then gMonsters[f].trapCheckFrameId := 0;
709 end;
710 end;
711 result := monCheckTrapLastFrameId;
712 end;
715 function g_Mons_getNewMPlatFrameId (): LongWord; inline;
716 var
717 f: Integer;
718 begin
719 Inc(monCheckMPlatLastFrameId);
720 if (monCheckMPlatLastFrameId = 0) then
721 begin
722 // wraparound
723 monCheckMPlatLastFrameId := 1;
724 for f := 0 to High(gMonsters) do
725 begin
726 if (gMonsters[f] <> nil) then gMonsters[f].mplatCheckFrameId := 0;
727 end;
728 end;
729 result := monCheckMPlatLastFrameId;
730 end;
733 var
734 pt_x: Integer = 0;
735 pt_xs: Integer = 1;
736 pt_y: Integer = 0;
737 pt_ys: Integer = 1;
738 soulcount: Integer = 0;
741 function allocMonster (): DWORD;
742 var
743 f, olen: Integer;
744 begin
745 result := freeInds.alloc();
746 if (result > High(gMonsters)) then
747 begin
748 olen := Length(gMonsters);
749 SetLength(gMonsters, result+64);
750 for f := olen to High(gMonsters) do gMonsters[f] := nil;
751 end;
752 end;
755 function IsFriend(a, b: Byte): Boolean;
756 begin
757 Result := True;
759 // Бочка - всем друг:
760 if (a = MONSTER_BARREL) or (b = MONSTER_BARREL) then
761 Exit;
763 // Монстры одного вида:
764 if a = b then
765 case a of
766 MONSTER_IMP, MONSTER_DEMON, MONSTER_BARON, MONSTER_KNIGHT, MONSTER_CACO,
767 MONSTER_SOUL, MONSTER_PAIN, MONSTER_MANCUB, MONSTER_SKEL, MONSTER_FISH:
768 Exit; // Эти не бьют своих
769 end;
771 // Lost_Soul не может ранить Pain_Elemental'а:
772 if (a = MONSTER_SOUL) and (b = MONSTER_PAIN) then
773 Exit;
774 // Pain_Elemental не может ранить Lost_Soul'а:
775 if (b = MONSTER_SOUL) and (a = MONSTER_PAIN) then
776 Exit;
778 // В остальных случаях - будут бить друг друга:
779 Result := False;
780 end;
783 function BehaviourDamage(SpawnerUID: Word; BH, SelfType: Byte): Boolean;
784 var
785 m: TMonster;
786 UIDType, MonsterType: Byte;
787 begin
788 Result := False;
789 MonsterType := 0;
791 UIDType := g_GetUIDType(SpawnerUID);
792 if UIDType = UID_MONSTER then
793 begin
794 m := g_Monsters_ByUID(SpawnerUID);
795 if m = nil then Exit;
796 MonsterType := m.FMonsterType;
797 end;
799 case BH of
800 BH_NORMAL: Result := (UIDType = UID_PLAYER) or
801 ((UIDType = UID_MONSTER) and (not IsFriend(MonsterType, SelfType)));
803 BH_KILLER: Result := UIDType = UID_PLAYER;
804 BH_MANIAC: Result := (UIDType = UID_PLAYER) or
805 ((UIDType = UID_MONSTER) and (not IsFriend(MonsterType, SelfType)));
807 BH_INSANE: Result := (UIDType = UID_MONSTER) and (not IsFriend(MonsterType, SelfType));
808 BH_CANNIBAL: Result := (UIDType = UID_MONSTER) and (MonsterType = SelfType);
809 end;
810 end;
813 function canShoot(m: Byte): Boolean;
814 begin
815 Result := False;
817 case m of
818 MONSTER_DEMON, MONSTER_FISH, MONSTER_BARREL:
819 Exit;
820 else
821 Result := True;
822 end;
823 end;
826 function isCorpse (o: PObj; immediately: Boolean): Integer;
828 (*
829 function monsCollCheck (mon: TMonster; atag: Integer): Boolean;
830 begin
831 atag := atag; // shut up, fpc!
832 result := false; // don't stop
833 if (mon.FState = MONSTATE_DEAD) and g_Obj_Collide(o, @mon.FObj) then
834 begin
835 case mon.FMonsterType of // Не воскресить:
836 MONSTER_SOUL, MONSTER_PAIN, MONSTER_CYBER, MONSTER_SPIDER,
837 MONSTER_VILE, MONSTER_BARREL, MONSTER_ROBO: exit;
838 end;
839 // Остальных можно воскресить
840 result := true;
841 end;
842 end;
843 *)
845 var
846 a: Integer;
847 mon: PMonster;
848 mres: TMonster = nil;
849 it: TMonsterGrid.Iter;
850 begin
851 result := -1;
853 // Если нужна вероятность
854 if not immediately and (Random(8) <> 0) then exit;
856 // Ищем мертвых монстров поблизости
857 if gmon_debug_use_sqaccel then
858 begin
859 //mon := monsGrid.forEachInAABB(o.X+o.Rect.X, o.Y+o.Rect.Y, o.Rect.Width, o.Rect.Height, monsCollCheck);
860 //if (mon <> nil) then result := mon.mArrIdx;
861 it := monsGrid.forEachInAABB(o.X+o.Rect.X, o.Y+o.Rect.Y, o.Rect.Width, o.Rect.Height);
862 for mon in it do
863 begin
864 case mon.FMonsterType of // Не воскресить:
865 MONSTER_SOUL, MONSTER_PAIN, MONSTER_CYBER, MONSTER_SPIDER,
866 MONSTER_VILE, MONSTER_BARREL, MONSTER_ROBO: begin end;
867 // Остальных можно воскресить
868 else mres := mon^;
869 end;
870 if (mres <> nil) then break;
871 end;
872 it.release();
873 if (mres <> nil) then result := mres.mArrIdx;
874 end
875 else
876 begin
877 for a := 0 to High(gMonsters) do
878 begin
879 if (gMonsters[a] <> nil) and (gMonsters[a].FState = MONSTATE_DEAD) and g_Obj_Collide(o, @gMonsters[a].FObj) then
880 begin
881 case gMonsters[a].FMonsterType of // Не воскресить:
882 MONSTER_SOUL, MONSTER_PAIN, MONSTER_CYBER, MONSTER_SPIDER,
883 MONSTER_VILE, MONSTER_BARREL, MONSTER_ROBO: Continue;
884 else // Остальных можно воскресить
885 begin
886 Result := a;
887 Exit;
888 end;
889 end;
890 end;
891 end;
892 end;
893 end;
895 procedure g_Monsters_LoadData();
896 begin
897 e_WriteLog('Loading monsters data...', TMsgType.Notify);
899 g_Game_SetLoadingText(_lc[I_LOAD_MONSTER_TEXTURES], 133, False);
900 g_Game_StepLoading(133);
902 g_Game_SetLoadingText(_lc[I_LOAD_MONSTER_SOUNDS], 0, False);
904 g_Sound_CreateWADEx('SOUND_MONSTER_BARREL_DIE', GameWAD+':MSOUNDS\BARREL_DIE');
906 g_Sound_CreateWADEx('SOUND_MONSTER_PAIN', GameWAD+':MSOUNDS\PAIN');
907 g_Sound_CreateWADEx('SOUND_MONSTER_PAIN2', GameWAD+':MSOUNDS\PAIN2');
908 g_Sound_CreateWADEx('SOUND_MONSTER_ACTION', GameWAD+':MSOUNDS\ACTION');
909 g_Sound_CreateWADEx('SOUND_MONSTER_ACTION2', GameWAD+':MSOUNDS\ACTION2');
910 g_Sound_CreateWADEx('SOUND_MONSTER_ALERT_1', GameWAD+':MSOUNDS\ALERT_1');
911 g_Sound_CreateWADEx('SOUND_MONSTER_ALERT_2', GameWAD+':MSOUNDS\ALERT_2');
912 g_Sound_CreateWADEx('SOUND_MONSTER_ALERT_3', GameWAD+':MSOUNDS\ALERT_3');
913 g_Sound_CreateWADEx('SOUND_MONSTER_DIE_1', GameWAD+':MSOUNDS\DIE_1');
914 g_Sound_CreateWADEx('SOUND_MONSTER_DIE_2', GameWAD+':MSOUNDS\DIE_2');
915 g_Sound_CreateWADEx('SOUND_MONSTER_DIE_3', GameWAD+':MSOUNDS\DIE_3');
916 g_Sound_CreateWADEx('SOUND_MONSTER_SLOP', GameWAD+':MSOUNDS\SLOP');
918 g_Sound_CreateWADEx('SOUND_MONSTER_DEMON_ATTACK', GameWAD+':MSOUNDS\DEMON_ATTACK');
919 g_Sound_CreateWADEx('SOUND_MONSTER_DEMON_ALERT', GameWAD+':MSOUNDS\DEMON_ALERT');
920 g_Sound_CreateWADEx('SOUND_MONSTER_DEMON_DIE', GameWAD+':MSOUNDS\DEMON_DIE');
922 g_Sound_CreateWADEx('SOUND_MONSTER_IMP_ALERT_1', GameWAD+':MSOUNDS\IMP_ALERT_1');
923 g_Sound_CreateWADEx('SOUND_MONSTER_IMP_ALERT_2', GameWAD+':MSOUNDS\IMP_ALERT_2');
924 g_Sound_CreateWADEx('SOUND_MONSTER_IMP_DIE_1', GameWAD+':MSOUNDS\IMP_DIE_1');
925 g_Sound_CreateWADEx('SOUND_MONSTER_IMP_DIE_2', GameWAD+':MSOUNDS\IMP_DIE_2');
926 g_Sound_CreateWADEx('SOUND_MONSTER_IMP_ACTION', GameWAD+':MSOUNDS\IMP_ACTION');
927 g_Sound_CreateWADEx('SOUND_MONSTER_IMP_ATTACK', GameWAD+':MSOUNDS\IMP_ATTACK');
929 g_Sound_CreateWADEx('SOUND_MONSTER_MAN_PAIN', GameWAD+':MSOUNDS\MAN_PAIN');
930 g_Sound_CreateWADEx('SOUND_MONSTER_MAN_ALERT', GameWAD+':MSOUNDS\MAN_ALERT');
931 g_Sound_CreateWADEx('SOUND_MONSTER_MAN_DIE', GameWAD+':MSOUNDS\MAN_DIE');
932 g_Sound_CreateWADEx('SOUND_MONSTER_HAHA', GameWAD+':MSOUNDS\HAHA');
933 g_Sound_CreateWADEx('SOUND_MONSTER_TRUP', GameWAD+':MSOUNDS\TRUP');
935 g_Sound_CreateWADEx('SOUND_MONSTER_SOUL_ATTACK', GameWAD+':MSOUNDS\SOUL_ATTACK');
936 g_Sound_CreateWADEx('SOUND_MONSTER_SOUL_DIE', GameWAD+':MSOUNDS\SOUL_DIE');
938 g_Sound_CreateWADEx('SOUND_MONSTER_BSP_ACTION', GameWAD+':MSOUNDS\BSP_ACTION');
939 g_Sound_CreateWADEx('SOUND_MONSTER_BSP_DIE', GameWAD+':MSOUNDS\BSP_DIE');
940 g_Sound_CreateWADEx('SOUND_MONSTER_BSP_ALERT', GameWAD+':MSOUNDS\BSP_ALERT');
941 g_Sound_CreateWADEx('SOUND_MONSTER_BSP_WALK', GameWAD+':MSOUNDS\BSP_WALK');
943 g_Sound_CreateWADEx('SOUND_MONSTER_VILE_ACTION', GameWAD+':MSOUNDS\VILE_ACTION');
944 g_Sound_CreateWADEx('SOUND_MONSTER_VILE_PAIN', GameWAD+':MSOUNDS\VILE_PAIN');
945 g_Sound_CreateWADEx('SOUND_MONSTER_VILE_DIE', GameWAD+':MSOUNDS\VILE_DIE');
946 g_Sound_CreateWADEx('SOUND_MONSTER_VILE_ALERT', GameWAD+':MSOUNDS\VILE_ALERT');
947 g_Sound_CreateWADEx('SOUND_MONSTER_VILE_ATTACK', GameWAD+':MSOUNDS\VILE_ATTACK');
949 g_Sound_CreateWADEx('SOUND_MONSTER_SKEL_ACTION', GameWAD+':MSOUNDS\SKEL_ACTION');
950 g_Sound_CreateWADEx('SOUND_MONSTER_SKEL_DIE', GameWAD+':MSOUNDS\SKEL_DIE');
951 g_Sound_CreateWADEx('SOUND_MONSTER_SKEL_ALERT', GameWAD+':MSOUNDS\SKEL_ALERT');
952 g_Sound_CreateWADEx('SOUND_MONSTER_SKEL_ATTACK', GameWAD+':MSOUNDS\SKEL_ATTACK');
953 g_Sound_CreateWADEx('SOUND_MONSTER_SKEL_HIT', GameWAD+':MSOUNDS\SKEL_HIT');
955 g_Sound_CreateWADEx('SOUND_MONSTER_MANCUB_PAIN', GameWAD+':MSOUNDS\MANCUB_PAIN');
956 g_Sound_CreateWADEx('SOUND_MONSTER_MANCUB_DIE', GameWAD+':MSOUNDS\MANCUB_DIE');
957 g_Sound_CreateWADEx('SOUND_MONSTER_MANCUB_ALERT', GameWAD+':MSOUNDS\MANCUB_ALERT');
958 g_Sound_CreateWADEx('SOUND_MONSTER_MANCUB_ATTACK', GameWAD+':MSOUNDS\MANCUB_ATTACK');
960 g_Sound_CreateWADEx('SOUND_MONSTER_PAIN_PAIN', GameWAD+':MSOUNDS\PAIN_PAIN');
961 g_Sound_CreateWADEx('SOUND_MONSTER_PAIN_DIE', GameWAD+':MSOUNDS\PAIN_DIE');
962 g_Sound_CreateWADEx('SOUND_MONSTER_PAIN_ALERT', GameWAD+':MSOUNDS\PAIN_ALERT');
964 g_Sound_CreateWADEx('SOUND_MONSTER_BARON_DIE', GameWAD+':MSOUNDS\BARON_DIE');
965 g_Sound_CreateWADEx('SOUND_MONSTER_BARON_ALERT', GameWAD+':MSOUNDS\BARON_ALERT');
967 g_Sound_CreateWADEx('SOUND_MONSTER_CACO_DIE', GameWAD+':MSOUNDS\CACO_DIE');
968 g_Sound_CreateWADEx('SOUND_MONSTER_CACO_ALERT', GameWAD+':MSOUNDS\CACO_ALERT');
970 g_Sound_CreateWADEx('SOUND_MONSTER_CYBER_DIE', GameWAD+':MSOUNDS\CYBER_DIE');
971 g_Sound_CreateWADEx('SOUND_MONSTER_CYBER_ALERT', GameWAD+':MSOUNDS\CYBER_ALERT');
972 g_Sound_CreateWADEx('SOUND_MONSTER_CYBER_WALK', GameWAD+':MSOUNDS\CYBER_WALK');
974 g_Sound_CreateWADEx('SOUND_MONSTER_KNIGHT_DIE', GameWAD+':MSOUNDS\KNIGHT_DIE');
975 g_Sound_CreateWADEx('SOUND_MONSTER_KNIGHT_ALERT', GameWAD+':MSOUNDS\KNIGHT_ALERT');
977 g_Sound_CreateWADEx('SOUND_MONSTER_SPIDER_DIE', GameWAD+':MSOUNDS\SPIDER_DIE');
978 g_Sound_CreateWADEx('SOUND_MONSTER_SPIDER_ALERT', GameWAD+':MSOUNDS\SPIDER_ALERT');
979 g_Sound_CreateWADEx('SOUND_MONSTER_SPIDER_WALK', GameWAD+':MSOUNDS\SPIDER_WALK');
981 g_Sound_CreateWADEx('SOUND_MONSTER_FISH_ATTACK', GameWAD+':MSOUNDS\FISH_ATTACK');
983 freeInds := TIdPool.Create();
984 clearUidMap();
985 monCheckTrapLastFrameId := 0;
986 monCheckMPlatLastFrameId := 0;
987 end;
989 procedure g_Monsters_FreeData();
990 begin
991 e_WriteLog('Releasing monsters data...', TMsgType.Notify);
993 g_Sound_Delete('SOUND_MONSTER_BARREL_DIE');
995 g_Sound_Delete('SOUND_MONSTER_PAIN');
996 g_Sound_Delete('SOUND_MONSTER_PAIN2');
997 g_Sound_Delete('SOUND_MONSTER_ACTION');
998 g_Sound_Delete('SOUND_MONSTER_ACTION2');
999 g_Sound_Delete('SOUND_MONSTER_ALERT_1');
1000 g_Sound_Delete('SOUND_MONSTER_ALERT_2');
1001 g_Sound_Delete('SOUND_MONSTER_ALERT_3');
1002 g_Sound_Delete('SOUND_MONSTER_DIE_1');
1003 g_Sound_Delete('SOUND_MONSTER_DIE_2');
1004 g_Sound_Delete('SOUND_MONSTER_DIE_3');
1005 g_Sound_Delete('SOUND_MONSTER_SLOP');
1007 g_Sound_Delete('SOUND_MONSTER_DEMON_ATTACK');
1008 g_Sound_Delete('SOUND_MONSTER_DEMON_ALERT');
1009 g_Sound_Delete('SOUND_MONSTER_DEMON_DIE');
1011 g_Sound_Delete('SOUND_MONSTER_IMP_ALERT_1');
1012 g_Sound_Delete('SOUND_MONSTER_IMP_ALERT_2');
1013 g_Sound_Delete('SOUND_MONSTER_IMP_DIE_1');
1014 g_Sound_Delete('SOUND_MONSTER_IMP_DIE_2');
1015 g_Sound_Delete('SOUND_MONSTER_IMP_ACTION');
1016 g_Sound_Delete('SOUND_MONSTER_IMP_ATTACK');
1018 g_Sound_Delete('SOUND_MONSTER_MAN_PAIN');
1019 g_Sound_Delete('SOUND_MONSTER_MAN_ALERT');
1020 g_Sound_Delete('SOUND_MONSTER_MAN_DIE');
1021 g_Sound_Delete('SOUND_MONSTER_HAHA');
1022 g_Sound_Delete('SOUND_MONSTER_TRUP');
1024 g_Sound_Delete('SOUND_MONSTER_SOUL_ATTACK');
1025 g_Sound_Delete('SOUND_MONSTER_SOUL_DIE');
1027 g_Sound_Delete('SOUND_MONSTER_BSP_ACTION');
1028 g_Sound_Delete('SOUND_MONSTER_BSP_DIE');
1029 g_Sound_Delete('SOUND_MONSTER_BSP_ALERT');
1030 g_Sound_Delete('SOUND_MONSTER_BSP_WALK');
1032 g_Sound_Delete('SOUND_MONSTER_VILE_ACTION');
1033 g_Sound_Delete('SOUND_MONSTER_VILE_PAIN');
1034 g_Sound_Delete('SOUND_MONSTER_VILE_DIE');
1035 g_Sound_Delete('SOUND_MONSTER_VILE_ALERT');
1036 g_Sound_Delete('SOUND_MONSTER_VILE_ATTACK');
1038 g_Sound_Delete('SOUND_MONSTER_SKEL_ACTION');
1039 g_Sound_Delete('SOUND_MONSTER_SKEL_DIE');
1040 g_Sound_Delete('SOUND_MONSTER_SKEL_ALERT');
1041 g_Sound_Delete('SOUND_MONSTER_SKEL_ATTACK');
1042 g_Sound_Delete('SOUND_MONSTER_SKEL_HIT');
1044 g_Sound_Delete('SOUND_MONSTER_MANCUB_PAIN');
1045 g_Sound_Delete('SOUND_MONSTER_MANCUB_DIE');
1046 g_Sound_Delete('SOUND_MONSTER_MANCUB_ALERT');
1047 g_Sound_Delete('SOUND_MONSTER_MANCUB_ATTACK');
1049 g_Sound_Delete('SOUND_MONSTER_PAIN_PAIN');
1050 g_Sound_Delete('SOUND_MONSTER_PAIN_DIE');
1051 g_Sound_Delete('SOUND_MONSTER_PAIN_ALERT');
1053 g_Sound_Delete('SOUND_MONSTER_BARON_DIE');
1054 g_Sound_Delete('SOUND_MONSTER_BARON_ALERT');
1056 g_Sound_Delete('SOUND_MONSTER_CACO_DIE');
1057 g_Sound_Delete('SOUND_MONSTER_CACO_ALERT');
1059 g_Sound_Delete('SOUND_MONSTER_CYBER_DIE');
1060 g_Sound_Delete('SOUND_MONSTER_CYBER_ALERT');
1061 g_Sound_Delete('SOUND_MONSTER_CYBER_WALK');
1063 g_Sound_Delete('SOUND_MONSTER_KNIGHT_DIE');
1064 g_Sound_Delete('SOUND_MONSTER_KNIGHT_ALERT');
1066 g_Sound_Delete('SOUND_MONSTER_SPIDER_DIE');
1067 g_Sound_Delete('SOUND_MONSTER_SPIDER_ALERT');
1068 g_Sound_Delete('SOUND_MONSTER_SPIDER_WALK');
1070 g_Sound_Delete('SOUND_MONSTER_FISH_ATTACK');
1072 freeInds.Free();
1073 freeInds := nil;
1074 end;
1076 procedure g_Monsters_Init();
1077 begin
1078 soulcount := 0;
1079 end;
1081 procedure g_Monsters_Free (clearGrid: Boolean=true);
1082 var
1083 a: Integer;
1084 begin
1085 e_LogWritefln('Cleared monster data (clearGrid=%s)', [clearGrid]);
1086 if (clearGrid) then
1087 begin
1088 monsGrid.Free();
1089 monsGrid := nil;
1090 end;
1091 for a := 0 to High(gMonsters) do gMonsters[a].Free();
1092 gMonsters := nil;
1093 clearUidMap();
1094 monCheckTrapLastFrameId := 0;
1095 monCheckMPlatLastFrameId := 0;
1096 end;
1099 // will be called from map loader
1100 procedure g_Mons_InitTree (x, y, w, h: Integer);
1101 begin
1102 monsGrid.Free();
1103 monsGrid := TMonsterGrid.Create(x, y, w, h);
1104 //clearUidMap(); // why not?
1105 e_LogWritefln('%s', ['Recreated monster tree']);
1106 end;
1109 function g_Monsters_Create(MonsterType: Byte; X, Y: Integer;
1110 Direction: TDirection; AdjCoord: Boolean = False; ForcedUID: Integer = -1): TMonster;
1111 var
1112 find_id: DWORD;
1113 mon: TMonster;
1114 begin
1115 result := nil;
1117 // Нет такого монстра
1118 if (MonsterType > MONSTER_MAN) or (MonsterType = 0) then exit;
1120 // Соблюдаем ограничение Lost_Soul'ов
1121 if MonsterType = MONSTER_SOUL then
1122 begin
1123 if soulcount > MAX_SOUL then exit;
1124 soulcount := soulcount + 1;
1125 end;
1127 find_id := allocMonster();
1129 mon := TMonster.Create(MonsterType, find_id, ForcedUID);
1130 gMonsters[find_id] := mon;
1131 mon.mArrIdx := find_id;
1132 mon.mProxyId := -1;
1134 uidMap[mon.FUID] := mon;
1136 // Настраиваем положение
1137 with mon do
1138 begin
1139 if AdjCoord then
1140 begin
1141 FObj.X := X-FObj.Rect.X - (FObj.Rect.Width div 2);
1142 FObj.Y := Y-FObj.Rect.Y - FObj.Rect.Height;
1143 end
1144 else
1145 begin
1146 FObj.X := X-FObj.Rect.X;
1147 FObj.Y := Y-FObj.Rect.Y;
1148 end;
1150 FDirection := Direction;
1151 FStartDirection := Direction;
1152 FStartX := GameX;
1153 FStartY := GameY;
1154 FObj.oldX := FObj.X;
1155 FObj.oldY := FObj.Y;
1156 end;
1158 mon.positionChanged();
1160 result := mon;
1161 end;
1163 procedure g_Monsters_killedp();
1164 var
1165 a, h: Integer;
1166 begin
1167 if gMonsters = nil then
1168 Exit;
1170 // Приколист смеется над смертью игрока:
1171 h := High(gMonsters);
1172 for a := 0 to h do
1173 begin
1174 if (gMonsters[a] <> nil) then
1175 begin
1176 with gMonsters[a] do
1177 begin
1178 if (FMonsterType = MONSTER_MAN) and
1179 (FState <> MONSTATE_DEAD) and
1180 (FState <> MONSTATE_SLEEP) and
1181 (FState <> MONSTATE_DIE) then
1182 begin
1183 g_Sound_PlayExAt('SOUND_MONSTER_TRUP', FObj.X, FObj.Y);
1184 Exit;
1185 end;
1186 end;
1187 end;
1188 end;
1189 end;
1191 procedure g_Monsters_PreUpdate();
1192 var
1193 a: Integer;
1194 begin
1195 if gMonsters = nil then Exit;
1196 for a := 0 to High(gMonsters) do
1197 if (gMonsters[a] <> nil) and (not gMonsters[a].FRemoved) then
1198 gMonsters[a].PreUpdate();
1199 end;
1201 procedure g_Monsters_Update();
1202 var
1203 a: Integer;
1204 begin
1205 // Целеуказатель
1206 if gTime mod (GAME_TICK*2) = 0 then
1207 begin
1208 pt_x := pt_x+pt_xs;
1209 pt_y := pt_y+pt_ys;
1210 if abs(pt_x) > 246 then pt_xs := -pt_xs;
1211 if abs(pt_y) > 100 then pt_ys := -pt_ys;
1212 end;
1214 gMon := True; // Для работы BlockMon'а
1216 if gmon_debug_think or gmon_debug_one_think_step then
1217 begin
1218 gmon_debug_one_think_step := false;
1219 for a := 0 to High(gMonsters) do
1220 begin
1221 if (gMonsters[a] = nil) then continue;
1222 if not gMonsters[a].FRemoved then
1223 begin
1224 if g_Game_IsClient then
1225 gMonsters[a].ClientUpdate()
1226 else
1227 gMonsters[a].Update();
1228 end
1229 else
1230 begin
1231 gMonsters[a].Free();
1232 gMonsters[a] := nil;
1233 end;
1234 end;
1235 end;
1237 gMon := False;
1238 end;
1240 function g_Monsters_ByUID (UID: Word): TMonster;
1241 begin
1242 result := uidMap[UID];
1243 end;
1245 procedure g_Monsters_SaveState (st: TStream);
1246 var
1247 count, i: Integer;
1248 begin
1249 // Считаем количество существующих монстров
1250 count := 0;
1251 for i := 0 to High(gMonsters) do
1252 begin
1253 if (gMonsters[i] <> nil) and (gMonsters[i].FMonsterType <> MONSTER_NONE) then count += 1;
1254 end;
1256 // Сохраняем информацию целеуказателя
1257 utils.writeInt(st, LongInt(pt_x));
1258 utils.writeInt(st, LongInt(pt_xs));
1259 utils.writeInt(st, LongInt(pt_y));
1260 utils.writeInt(st, LongInt(pt_ys));
1262 // Количество монстров
1263 utils.writeInt(st, LongInt(count));
1265 if (count = 0) then exit;
1267 // Сохраняем монстров
1268 for i := 0 to High(gMonsters) do
1269 begin
1270 if (gMonsters[i] <> nil) and (gMonsters[i].FMonsterType <> MONSTER_NONE) then
1271 begin
1272 // Тип монстра
1273 utils.writeInt(st, Byte(gMonsters[i].MonsterType));
1274 // Сохраняем данные монстра:
1275 gMonsters[i].SaveState(st);
1276 end;
1277 end;
1278 end;
1281 procedure g_Monsters_LoadState (st: TStream);
1282 var
1283 count, a: Integer;
1284 b: Byte;
1285 mon: TMonster;
1286 begin
1287 assert(st <> nil);
1289 g_Monsters_Free(false);
1291 // Загружаем информацию целеуказателя
1292 pt_x := utils.readLongInt(st);
1293 pt_xs := utils.readLongInt(st);
1294 pt_y := utils.readLongInt(st);
1295 pt_ys := utils.readLongInt(st);
1297 // Количество монстров
1298 count := utils.readLongInt(st);
1300 if (count = 0) then exit;
1301 if (count < 0) or (count > 1024*1024) then raise XStreamError.Create('invalid monster count');
1303 // Загружаем монстров
1304 for a := 0 to count-1 do
1305 begin
1306 // Тип монстра
1307 b := utils.readByte(st);
1308 // Создаем монстра
1309 mon := g_Monsters_Create(b, 0, 0, TDirection.D_LEFT);
1310 if (mon = nil) then raise XStreamError.Create('g_Monsters_LoadState: ID = -1 (can''t create)');
1311 // Загружаем данные монстра
1312 mon.LoadState(st);
1313 end;
1314 end;
1317 // ////////////////////////////////////////////////////////////////////////// //
1318 function g_Mons_SpawnAt (monType: Integer; x, y: Integer; dir: TDirection=TDirection.D_LEFT): TMonster; overload;
1319 begin
1320 result := nil;
1321 if (monType >= MONSTER_DEMON) and (monType <= MONSTER_MAN) then
1322 begin
1323 result := g_Monsters_Create(monType, x, y, dir);
1324 end;
1325 end;
1328 function g_Mons_SpawnAt (const typeName: AnsiString; x, y: Integer; dir: TDirection=TDirection.D_LEFT): TMonster; overload;
1329 begin
1330 result := g_Mons_SpawnAt(g_Mons_TypeIdByName(typeName), x, y, dir);
1331 end;
1335 // ////////////////////////////////////////////////////////////////////////// //
1336 function g_Mons_TypeLo (): Integer; inline; begin result := Low(MONSTERTABLE); end;
1337 function g_Mons_TypeHi (): Integer; inline; begin result := High(MONSTERTABLE); end;
1340 function g_Mons_TypeIdByName (const name: String): Integer;
1341 var
1342 i: Integer;
1343 begin
1344 i := MONSTER_DEMON;
1345 while (i <= MONSTER_MAN) do
1346 begin
1347 if (CompareText(name, MONSTERTABLE[i].Name) = 0) then
1348 begin
1349 result := i;
1350 exit;
1351 end;
1352 Inc(i);
1353 end;
1354 result := -1;
1355 // HACK!
1356 if (CompareText(name, 'zombie') = 0) then result := MONSTER_ZOMBY;
1357 end;
1360 function g_Mons_NameByTypeId (monType: Integer): AnsiString;
1361 begin
1362 if (monType >= MONSTER_DEMON) and (monType <= MONSTER_MAN) then
1363 result := MONSTERTABLE[monType].Name
1364 else
1365 result := '?';
1366 end;
1369 function g_Mons_GetKilledByTypeId (monType: Integer): AnsiString;
1370 begin
1371 if (monType >= MONSTER_DEMON) and (monType <= MONSTER_MAN) then
1372 Result := KilledByMonster[monType]
1373 else
1374 Result := '?';
1375 end;
1378 // ////////////////////////////////////////////////////////////////////////// //
1379 { T M o n s t e r : }
1381 procedure TMonster.setGameX (v: Integer); inline; begin FObj.X := v; positionChanged(); end;
1382 procedure TMonster.setGameY (v: Integer); inline; begin FObj.Y := v; positionChanged(); end;
1384 procedure TMonster.setPosition (ax, ay: Integer; callPosChanged: Boolean=true); inline; begin FObj.X := ax; FObj.Y := ay; if callPosChanged then positionChanged(); end;
1386 procedure TMonster.moveBy (dx, dy: Integer); inline;
1387 begin
1388 if (dx <> 0) or (dy <> 0) then
1389 begin
1390 FObj.X += dx;
1391 FObj.Y += dy;
1392 positionChanged();
1393 end;
1394 end;
1396 procedure TMonster.doDamage (v: Integer);
1397 begin
1398 if (v <= 0) then exit;
1399 if (v > 32767) then v := 32767;
1400 Damage(v, 0, 0, 0, mEDamageType);
1401 end;
1403 procedure TMonster.ActionSound();
1404 begin
1405 case FMonsterType of
1406 MONSTER_IMP:
1407 g_Sound_PlayExAt('SOUND_MONSTER_IMP_ACTION', FObj.X, FObj.Y);
1408 MONSTER_ZOMBY, MONSTER_SERG, MONSTER_CGUN,
1409 MONSTER_MANCUB:
1410 g_Sound_PlayExAt('SOUND_MONSTER_ACTION', FObj.X, FObj.Y);
1411 MONSTER_SOUL, MONSTER_BARON, MONSTER_CACO,
1412 MONSTER_KNIGHT, MONSTER_PAIN, MONSTER_DEMON,
1413 MONSTER_SPIDER:
1414 g_Sound_PlayExAt('SOUND_MONSTER_ACTION2', FObj.X, FObj.Y);
1415 MONSTER_BSP:
1416 g_Sound_PlayExAt('SOUND_MONSTER_BSP_ACTION', FObj.X, FObj.Y);
1417 MONSTER_VILE:
1418 g_Sound_PlayExAt('SOUND_MONSTER_VILE_ACTION', FObj.X, FObj.Y);
1419 MONSTER_SKEL:
1420 g_Sound_PlayExAt('SOUND_MONSTER_SKEL_ACTION', FObj.X, FObj.Y);
1421 MONSTER_CYBER:
1423 MONSTER_MAN:
1424 g_Sound_PlayExAt('SOUND_MONSTER_HAHA', FObj.X, FObj.Y);
1425 end;
1426 end;
1428 procedure TMonster.PainSound();
1429 begin
1430 if FPainSound then
1431 Exit;
1433 FPainSound := True;
1434 FPainTicks := 20;
1436 case FMonsterType of
1437 MONSTER_IMP, MONSTER_ZOMBY, MONSTER_SERG,
1438 MONSTER_SKEL, MONSTER_CGUN:
1439 g_Sound_PlayExAt('SOUND_MONSTER_PAIN', FObj.X, FObj.Y);
1440 MONSTER_SOUL, MONSTER_BARON, MONSTER_CACO,
1441 MONSTER_KNIGHT, MONSTER_DEMON, MONSTER_SPIDER,
1442 MONSTER_BSP, MONSTER_CYBER:
1443 g_Sound_PlayExAt('SOUND_MONSTER_PAIN2', FObj.X, FObj.Y);
1444 MONSTER_VILE:
1445 g_Sound_PlayExAt('SOUND_MONSTER_VILE_PAIN', FObj.X, FObj.Y);
1446 MONSTER_MANCUB:
1447 g_Sound_PlayExAt('SOUND_MONSTER_MANCUB_PAIN', FObj.X, FObj.Y);
1448 MONSTER_PAIN:
1449 g_Sound_PlayExAt('SOUND_MONSTER_PAIN_PAIN', FObj.X, FObj.Y);
1450 MONSTER_MAN:
1451 g_Sound_PlayExAt('SOUND_MONSTER_MAN_PAIN', FObj.X, FObj.Y);
1452 end;
1453 end;
1455 procedure TMonster.DieSound();
1456 begin
1457 case FMonsterType of
1458 MONSTER_IMP:
1459 case Random(2) of
1460 0: g_Sound_PlayExAt('SOUND_MONSTER_IMP_DIE_1', FObj.X, FObj.Y);
1461 1: g_Sound_PlayExAt('SOUND_MONSTER_IMP_DIE_2', FObj.X, FObj.Y);
1462 end;
1463 MONSTER_ZOMBY, MONSTER_SERG, MONSTER_CGUN:
1464 case Random(3) of
1465 0: g_Sound_PlayExAt('SOUND_MONSTER_DIE_1', FObj.X, FObj.Y);
1466 1: g_Sound_PlayExAt('SOUND_MONSTER_DIE_2', FObj.X, FObj.Y);
1467 2: g_Sound_PlayExAt('SOUND_MONSTER_DIE_3', FObj.X, FObj.Y);
1468 end;
1469 MONSTER_DEMON:
1470 g_Sound_PlayExAt('SOUND_MONSTER_DEMON_DIE', FObj.X, FObj.Y);
1471 MONSTER_BARREL:
1472 g_Sound_PlayExAt('SOUND_MONSTER_BARREL_DIE', FObj.X, FObj.Y);
1473 MONSTER_SOUL:
1474 g_Sound_PlayExAt('SOUND_MONSTER_SOUL_DIE', FObj.X, FObj.Y);
1475 MONSTER_BSP:
1476 g_Sound_PlayExAt('SOUND_MONSTER_BSP_DIE', FObj.X, FObj.Y);
1477 MONSTER_VILE:
1478 g_Sound_PlayExAt('SOUND_MONSTER_VILE_DIE', FObj.X, FObj.Y);
1479 MONSTER_BARON:
1480 g_Sound_PlayExAt('SOUND_MONSTER_BARON_DIE', FObj.X, FObj.Y);
1481 MONSTER_CACO:
1482 g_Sound_PlayExAt('SOUND_MONSTER_CACO_DIE', FObj.X, FObj.Y);
1483 MONSTER_CYBER:
1484 g_Sound_PlayExAt('SOUND_MONSTER_CYBER_DIE', FObj.X, FObj.Y);
1485 MONSTER_KNIGHT:
1486 g_Sound_PlayExAt('SOUND_MONSTER_KNIGHT_DIE', FObj.X, FObj.Y);
1487 MONSTER_MANCUB:
1488 g_Sound_PlayExAt('SOUND_MONSTER_MANCUB_DIE', FObj.X, FObj.Y);
1489 MONSTER_PAIN:
1490 g_Sound_PlayExAt('SOUND_MONSTER_PAIN_DIE', FObj.X, FObj.Y);
1491 MONSTER_SKEL:
1492 g_Sound_PlayExAt('SOUND_MONSTER_SKEL_DIE', FObj.X, FObj.Y);
1493 MONSTER_SPIDER:
1494 g_Sound_PlayExAt('SOUND_MONSTER_SPIDER_DIE', FObj.X, FObj.Y);
1495 MONSTER_MAN:
1496 g_Sound_PlayExAt('SOUND_MONSTER_MAN_DIE', FObj.X, FObj.Y);
1497 end;
1498 end;
1500 procedure TMonster.WakeUpSound();
1501 begin
1502 case FMonsterType of
1503 MONSTER_IMP:
1504 case Random(2) of
1505 0: g_Sound_PlayExAt('SOUND_MONSTER_IMP_ALERT_1', FObj.X, FObj.Y);
1506 1: g_Sound_PlayExAt('SOUND_MONSTER_IMP_ALERT_2', FObj.X, FObj.Y);
1507 end;
1508 MONSTER_ZOMBY, MONSTER_SERG, MONSTER_CGUN:
1509 case Random(3) of
1510 0: g_Sound_PlayExAt('SOUND_MONSTER_ALERT_1', FObj.X, FObj.Y);
1511 1: g_Sound_PlayExAt('SOUND_MONSTER_ALERT_2', FObj.X, FObj.Y);
1512 2: g_Sound_PlayExAt('SOUND_MONSTER_ALERT_3', FObj.X, FObj.Y);
1513 end;
1514 MONSTER_MAN:
1515 g_Sound_PlayExAt('SOUND_MONSTER_MAN_ALERT', FObj.X, FObj.Y);
1516 MONSTER_BSP:
1517 g_Sound_PlayExAt('SOUND_MONSTER_BSP_ALERT', FObj.X, FObj.Y);
1518 MONSTER_VILE:
1519 g_Sound_PlayExAt('SOUND_MONSTER_VILE_ALERT', FObj.X, FObj.Y);
1520 MONSTER_BARON:
1521 g_Sound_PlayExAt('SOUND_MONSTER_BARON_ALERT', FObj.X, FObj.Y);
1522 MONSTER_CACO:
1523 g_Sound_PlayExAt('SOUND_MONSTER_CACO_ALERT', FObj.X, FObj.Y);
1524 MONSTER_CYBER:
1525 g_Sound_PlayExAt('SOUND_MONSTER_CYBER_ALERT', FObj.X, FObj.Y);
1526 MONSTER_KNIGHT:
1527 g_Sound_PlayExAt('SOUND_MONSTER_KNIGHT_ALERT', FObj.X, FObj.Y);
1528 MONSTER_MANCUB:
1529 g_Sound_PlayExAt('SOUND_MONSTER_MANCUB_ALERT', FObj.X, FObj.Y);
1530 MONSTER_PAIN:
1531 g_Sound_PlayExAt('SOUND_MONSTER_PAIN_ALERT', FObj.X, FObj.Y);
1532 MONSTER_DEMON:
1533 g_Sound_PlayExAt('SOUND_MONSTER_DEMON_ALERT', FObj.X, FObj.Y);
1534 MONSTER_SKEL:
1535 g_Sound_PlayExAt('SOUND_MONSTER_SKEL_ALERT', FObj.X, FObj.Y);
1536 MONSTER_SPIDER:
1537 g_Sound_PlayExAt('SOUND_MONSTER_SPIDER_ALERT', FObj.X, FObj.Y);
1538 MONSTER_SOUL:
1540 end;
1541 end;
1543 procedure TMonster.BFGHit();
1544 begin
1545 if FMonsterType = MONSTER_FISH then
1546 Exit;
1548 g_Weapon_BFGHit(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
1549 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2));
1550 {if g_Game_IsServer and g_Game_IsNet then
1551 MH_SEND_Effect(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
1552 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2),
1553 0, NET_GFX_BFG);}
1554 end;
1556 function TMonster.Collide(X, Y: Integer; Width, Height: Word): Boolean;
1557 begin
1558 Result := g_Collide(FObj.X+FObj.Rect.X,
1559 FObj.Y+FObj.Rect.Y,
1560 FObj.Rect.Width,
1561 FObj.Rect.Height,
1562 X, Y,
1563 Width, Height);
1564 end;
1566 function TMonster.Collide(Panel: TPanel): Boolean;
1567 begin
1568 Result := g_Collide(FObj.X+FObj.Rect.X,
1569 FObj.Y+FObj.Rect.Y,
1570 FObj.Rect.Width,
1571 FObj.Rect.Height,
1572 Panel.X, Panel.Y,
1573 Panel.Width, Panel.Height);
1574 end;
1576 function TMonster.Collide(X, Y: Integer): Boolean;
1577 begin
1578 X := X - FObj.X - FObj.Rect.X;
1579 Y := Y - FObj.Y - FObj.Rect.Y;
1580 Result := (x >= 0) and (x <= FObj.Rect.Width) and
1581 (y >= 0) and (y <= FObj.Rect.Height);
1582 end;
1584 procedure TMonster.Respawn;
1585 begin
1586 FObj.Vel.X := 0;
1587 FObj.Vel.Y := 0;
1588 FObj.Accel.X := 0;
1589 FObj.Accel.Y := 0;
1590 FDirection := FStartDirection;
1591 {GameX}FObj.X := FStartX;
1592 {GameY}FObj.Y := FStartY;
1593 FObj.Rect := MONSTERTABLE[FMonsterType].Rect;
1594 FHealth := MONSTERTABLE[FMonsterType].Health;
1595 FAmmo := 0;
1596 FPain := 0;
1597 FTargetUID := 0;
1598 FTargetTime := 0;
1599 FDieTriggers := nil;
1600 FWaitAttackAnim := False;
1601 FChainFire := False;
1602 {$IFDEF ENABLE_SHELLS}
1603 FShellTimer := -1;
1604 {$ENDIF}
1606 FState := MONSTATE_SLEEP;
1607 FCurAnim := ANIM_SLEEP;
1609 positionChanged(); // this updates spatial accelerators
1611 if g_Game_IsNet and g_Game_IsServer then
1612 begin
1613 MH_SEND_MonsterPos(FUID);
1614 MH_SEND_MonsterState(FUID);
1615 end;
1616 end;
1618 constructor TMonster.Create(MonsterType: Byte; aID: Integer; ForcedUID: Integer = -1);
1619 var a: Integer;
1620 begin
1621 if ForcedUID < 0 then
1622 FUID := g_CreateUID(UID_MONSTER)
1623 else
1624 FUID := ForcedUID;
1626 FMonsterType := MonsterType;
1628 g_Obj_Init(@FObj);
1630 FState := MONSTATE_SLEEP;
1631 FCurAnim := ANIM_SLEEP;
1632 FHealth := MONSTERTABLE[MonsterType].Health;
1633 FMaxHealth := FHealth;
1634 FObj.Rect := MONSTERTABLE[MonsterType].Rect;
1635 FDieTriggers := nil;
1636 FSpawnTrigger := -1;
1637 FWaitAttackAnim := False;
1638 FChainFire := False;
1639 FStartID := aID;
1640 FNoRespawn := False;
1641 {$IFDEF ENABLE_SHELLS}
1642 FShellTimer := -1;
1643 {$ENDIF}
1644 FBehaviour := BH_NORMAL;
1645 FFireTime := 0;
1646 FFirePainTime := 0;
1647 FFireAttacker := 0;
1648 mEDamageType := HIT_SOME;
1650 mProxyId := -1;
1651 mArrIdx := -1;
1652 trapCheckFrameId := 0;
1653 mplatCheckFrameId := 0;
1654 mNeedSend := false;
1656 {$IFDEF ENABLE_GFX}
1657 if FMonsterType in [MONSTER_ROBO, MONSTER_BARREL] then
1658 FBloodKind := BLOOD_SPARKS
1659 else
1660 FBloodKind := BLOOD_NORMAL;
1661 if FMonsterType = MONSTER_CACO then
1662 begin
1663 FBloodRed := 0;
1664 FBloodGreen := 0;
1665 FBloodBlue := 150;
1666 end
1667 else if FMonsterType in [MONSTER_BARON, MONSTER_KNIGHT] then
1668 begin
1669 FBloodRed := 0;
1670 FBloodGreen := 150;
1671 FBloodBlue := 0;
1672 end
1673 else
1674 begin
1675 FBloodRed := 150;
1676 FBloodGreen := 0;
1677 FBloodBlue := 0;
1678 end;
1679 {$ENDIF}
1681 SetLength(FAnim, Length(ANIMTABLE));
1682 for a := ANIM_SLEEP to ANIM_PAIN do
1683 begin
1684 FAnim[a, TDirection.D_RIGHT] := TAnimationState.Create(ANIMTABLE[a].loop, MONSTER_ANIMTABLE[MonsterType].AnimSpeed[a], MONSTER_ANIMTABLE[MonsterType].AnimCount[a]);
1685 FAnim[a, TDirection.D_LEFT] := TAnimationState.Create(ANIMTABLE[a].loop, MONSTER_ANIMTABLE[MonsterType].AnimSpeed[a], MONSTER_ANIMTABLE[MonsterType].AnimCount[a]);
1686 end;
1687 if MonsterType = MONSTER_VILE then
1688 vilefire := TAnimationState.Create(True, 2, 8);
1689 end;
1691 function TMonster.Damage(aDamage: Word; VelX, VelY: Integer; SpawnerUID: Word; t: Byte): Boolean;
1692 var
1693 c, it: Integer;
1694 p: TPlayer;
1695 begin
1696 Result := False;
1698 // Монстр статичен пока идет warmup
1699 if (gLMSRespawn > LMS_RESPAWN_NONE) then exit;
1701 // Умирает, умер или воскрешается => урон делать некому:
1702 if (FState = MONSTATE_DEAD) or (FState = MONSTATE_DIE) or (FState = MONSTATE_REVIVE) then
1703 Exit;
1705 // Рыбу в воде бьет током => паника без урона:
1706 if (t = HIT_ELECTRO) and (FMonsterType = MONSTER_FISH) and g_Game_IsServer then
1707 begin
1708 FSleep := 20;
1709 if Random(2) = 0 then FDirection := TDirection.D_RIGHT else FDirection := TDirection.D_LEFT;
1710 Result := True;
1711 SetState(MONSTATE_RUN);
1712 Exit;
1713 end;
1715 // Арчи не горят, черепа уже горят
1716 if (t = HIT_FLAME) and (FMonsterType in [MONSTER_VILE, MONSTER_SOUL]) then
1717 begin
1718 // Проснуться все-таки стоит
1719 if FState = MONSTATE_SLEEP then
1720 SetState(MONSTATE_GO);
1721 Exit;
1722 end;
1724 // Ловушка убивает сразу:
1725 if t = HIT_TRAP then
1726 FHealth := -100;
1728 // Роботу урона нет:
1729 if FMonsterType = MONSTER_ROBO then
1730 aDamage := 0;
1732 // Наносим урон:
1733 if g_Game_IsServer then Dec(FHealth, aDamage);
1735 // Усиливаем боль монстра от урона:
1736 if FPain = 0 then
1737 FPain := 3;
1738 FPain := FPain+aDamage;
1740 // Если боль существенная, то меняем состояние на болевое:
1741 if FState <> MONSTATE_PAIN then
1742 if (FPain >= MONSTERTABLE[FMonsterType].MinPain) and
1743 (FMonsterType <> MONSTER_BARREL) then
1744 SetState(MONSTATE_PAIN);
1746 // Если разрешена кровь - создаем брызги крови:
1747 if (gBloodCount > 0) then
1748 begin
1749 c := Min(aDamage, 200);
1750 c := c*gBloodCount - (aDamage div 4) + Random(c div 2);
1752 if (VelX = 0) and (VelY = 0) then
1753 MakeBloodSimple(c)
1754 else
1755 case t of
1756 HIT_TRAP, HIT_ACID, HIT_ELECTRO, HIT_FLAME: MakeBloodSimple(c);
1757 HIT_BFG, HIT_ROCKET, HIT_SOME: MakeBloodVector(c, VelX, VelY);
1758 end;
1759 end;
1761 // Теперь цель - ударивший, если только не сам себя:
1762 if (SpawnerUID <> FUID) and (BehaviourDamage(SpawnerUID, FBehaviour, FMonsterType)) then
1763 begin
1764 FTargetUID := SpawnerUID;
1765 FTargetTime := 0;
1766 end;
1768 // Здоровье закончилось:
1769 if FHealth <= 0 then
1770 begin
1771 // Если это не бочка и убил игрок, то ему +1:
1772 if (FMonsterType <> MONSTER_BARREL) then
1773 begin
1774 if (g_GetUIDType(SpawnerUID) = UID_PLAYER) then
1775 begin
1776 p := g_Player_Get(SpawnerUID);
1777 if (p <> nil) and (gLMSRespawn = LMS_RESPAWN_NONE) then
1778 begin
1779 p.MonsterKills := p.MonsterKills+1;
1780 if gGameSettings.GameMode = GM_COOP then
1781 p.Frags := p.Frags + 1;
1782 // Uncomment this if you want to double-kill monsters
1783 //p.FragCombo();
1784 end;
1785 end;
1786 if gLMSRespawn = LMS_RESPAWN_NONE then
1787 begin
1788 Inc(gCoopMonstersKilled);
1789 if g_Game_IsNet then
1790 MH_SEND_GameStats;
1791 end;
1792 end;
1794 // Выбираем лут:
1795 case FMonsterType of
1796 MONSTER_ZOMBY: c := ITEM_AMMO_BULLETS;
1797 MONSTER_SERG: c := ITEM_WEAPON_SHOTGUN1;
1798 MONSTER_CGUN: c := ITEM_WEAPON_CHAINGUN;
1799 MONSTER_MAN: c := ITEM_KEY_RED;
1800 else c := 0;
1801 end;
1803 // Бросаем лут:
1804 if c <> 0 then
1805 begin
1806 it := g_Items_Create(FObj.X + (FObj.Rect.Width div 2),
1807 FObj.Y + (FObj.Rect.Height div 2),
1808 c, True, False);
1809 g_Items_SetDrop(it); // mark it as monster drop
1810 g_Obj_Push(g_Items_ObjByIdx(it), (FObj.Vel.X div 2)-3+Random(7),
1811 (FObj.Vel.Y div 2)-Random(4));
1812 //positionChanged(); // this updates spatial accelerators
1813 if g_Game_IsServer and g_Game_IsNet then
1814 MH_SEND_ItemSpawn(True, it);
1815 end;
1817 // Труп дальше не идет:
1818 FObj.Vel.X := 0;
1820 // У трупа размеры меньше:
1821 if (FMonsterType <> MONSTER_FISH) and (FMonsterType <> MONSTER_PAIN) then
1822 begin
1823 FObj.Rect.Y := FObj.Rect.Y + FObj.Rect.Height-12;
1824 FObj.Rect.Height := 12;
1825 positionChanged();
1826 end;
1828 // Урон был сильным => слабые - в кашу:
1829 if (FHealth <= -30) and
1830 ((FMonsterType = MONSTER_IMP) or (FMonsterType = MONSTER_ZOMBY) or
1831 (FMonsterType = MONSTER_SERG) or (FMonsterType = MONSTER_CGUN) or
1832 (FMonsterType = MONSTER_MAN)) then
1833 begin
1834 g_Sound_PlayExAt('SOUND_MONSTER_SLOP', FObj.X, FObj.Y);
1835 SetState(MONSTATE_DIE, ANIM_MESS);
1836 end
1837 else
1838 begin
1839 DieSound();
1840 SetState(MONSTATE_DIE);
1841 end;
1843 // Активировать триггеры, ждущие смерти этого монстра:
1844 if g_Game_IsServer then ActivateTriggers();
1846 FHealth := 0;
1847 end
1848 else
1849 if FState = MONSTATE_SLEEP then
1850 begin // Спал, разбудили несмертельным ударом:
1851 FPain := MONSTERTABLE[FMonsterType].Pain;
1852 SetState(MONSTATE_GO);
1853 end;
1855 if g_Game_IsServer and g_Game_IsNet then MH_SEND_MonsterState(FUID);
1856 Result := True;
1857 end;
1859 function TMonster.Heal(Value: Word): Boolean;
1860 begin
1861 Result := False;
1862 if g_Game_IsClient then
1863 Exit;
1864 if not alive then
1865 Exit;
1867 if FHealth < FMaxHealth then
1868 begin
1869 IncMax(FHealth, Value, FMaxHealth);
1870 if g_Game_IsServer and g_Game_IsNet then MH_SEND_MonsterState(FUID);
1871 Result := True;
1872 end;
1873 end;
1875 destructor TMonster.Destroy();
1876 var
1877 a: Integer;
1878 begin
1879 for a := 0 to High(FAnim) do
1880 begin
1881 FAnim[a, TDirection.D_LEFT].Free();
1882 FAnim[a, TDirection.D_RIGHT].Free();
1883 end;
1885 vilefire.Free();
1887 if (mProxyId <> -1) then
1888 begin
1889 if (monsGrid <> nil) then
1890 begin
1891 monsGrid.removeBody(mProxyId);
1892 {$IF DEFINED(D2F_DEBUG_MONS_MOVE)}
1893 e_WriteLog(Format('monster #%d:(%u): removed from grid; mProxyid=%d', [mArrIdx, UID, mProxyId]), MSG_NOTIFY);
1894 {$ENDIF}
1895 end;
1896 mProxyId := -1;
1897 end;
1899 if (mArrIdx <> -1) and (mArrIdx < Length(gMonsters)) then
1900 begin
1901 freeInds.release(mArrIdx);
1902 gMonsters[mArrIdx] := nil;
1903 end;
1904 mArrIdx := -1;
1906 uidMap[FUID] := nil;
1908 inherited Destroy();
1909 end;
1911 procedure TMonster.MakeBloodSimple(Count: Word);
1912 begin
1913 {$IFDEF ENABLE_GFX}
1914 g_GFX_Blood(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)+8,
1915 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2),
1916 Count div 2, 3, -1, 16, (FObj.Rect.Height*2 div 3),
1917 FBloodRed, FBloodGreen, FBloodBlue, FBloodKind);
1918 g_GFX_Blood(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)-8,
1919 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2),
1920 Count div 2, -3, -1, 16, (FObj.Rect.Height*2) div 3,
1921 FBloodRed, FBloodGreen, FBloodBlue, FBloodKind);
1922 {$ENDIF}
1923 end;
1925 procedure TMonster.MakeBloodVector(Count: Word; VelX, VelY: Integer);
1926 begin
1927 {$IFDEF ENABLE_GFX}
1928 g_GFX_Blood(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
1929 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2),
1930 Count, VelX, VelY, 16, (FObj.Rect.Height*2) div 3,
1931 FBloodRed, FBloodGreen, FBloodBlue, FBloodKind);
1932 {$ENDIF}
1933 end;
1935 procedure TMonster.Push(vx, vy: Integer);
1936 begin
1937 FObj.Accel.X := FObj.Accel.X + vx;
1938 FObj.Accel.Y := FObj.Accel.Y + vy;
1939 if g_Game_IsServer and g_Game_IsNet then MH_SEND_MonsterPos(FUID);
1940 end;
1942 procedure TMonster.SetState(State: Byte; ForceAnim: Byte = 255);
1943 var
1944 Anim: Byte;
1945 begin
1946 // Если состояние = начали умирать, а этот монстр = Lost_Soul,
1947 // то соблюдаем ограничение количества Lost_Soul'ов:
1948 if (State = MONSTATE_DIE) and (MonsterType = MONSTER_SOUL) then
1949 soulcount := soulcount-1;
1951 // Присмерти - нельзя сразу начинать атаковать или бегать:
1952 case FState of
1953 MONSTATE_DIE, MONSTATE_DEAD, MONSTATE_REVIVE:
1954 if (State <> MONSTATE_DEAD) and (State <> MONSTATE_REVIVE) and
1955 (State <> MONSTATE_GO) then
1956 Exit;
1957 end;
1959 // Смена состояния:
1960 FState := State;
1962 if g_Game_IsServer and g_Game_IsNet then MH_SEND_MonsterState(FUID, ForceAnim);
1964 // Новая анимация при новом состоянии:
1965 case FState of
1966 MONSTATE_SLEEP: Anim := ANIM_SLEEP;
1967 MONSTATE_PAIN: Anim := ANIM_PAIN;
1968 MONSTATE_WAIT: Anim := ANIM_SLEEP;
1969 MONSTATE_CLIMB, MONSTATE_RUN, MONSTATE_RUNOUT, MONSTATE_GO: Anim := ANIM_GO;
1970 MONSTATE_SHOOT: Anim := ANIM_ATTACK;
1971 MONSTATE_ATTACK: Anim := ANIM_ATTACK;
1972 MONSTATE_DIE: Anim := ANIM_DIE;
1973 MONSTATE_REVIVE:
1974 begin // начали восрешаться
1975 Anim := FCurAnim;
1976 FAnim[Anim, FDirection].Revert(True);
1978 FObj.Rect := MONSTERTABLE[FMonsterType].Rect;
1979 FHealth := MONSTERTABLE[FMonsterType].Health;
1980 FAmmo := 0;
1981 FPain := 0;
1982 end;
1983 else Exit;
1984 end;
1986 // Надо сменить анимацию на нестандартную:
1987 if ForceAnim <> 255 then
1988 Anim := ForceAnim;
1990 // Если анимация новая - перезапускаем её:
1991 if FCurAnim <> Anim then
1992 if FAnim[Anim, FDirection] <> nil then
1993 begin
1994 FAnim[Anim, FDirection].Reset();
1995 FCurAnim := Anim;
1996 end;
1997 end;
1999 function TMonster.TeleportTo(X, Y: Integer; silent: Boolean; dir: Byte): Boolean;
2000 begin
2001 Result := False;
2003 // В точке назначения стена:
2004 if g_CollideLevel(X, Y, FObj.Rect.Width, FObj.Rect.Height) then
2005 begin
2006 g_Sound_PlayExAt('SOUND_GAME_NOTELEPORT', FObj.X, FObj.Y);
2007 if g_Game_IsServer and g_Game_IsNet then
2008 MH_SEND_Sound(FObj.X, FObj.Y, 'SOUND_GAME_NOTELEPORT');
2009 Exit;
2010 end;
2012 // Эффект телепорта в позиции монстра:
2013 if not silent then
2014 begin
2015 g_Sound_PlayExAt('SOUND_GAME_TELEPORT', Obj.X, Obj.Y);
2016 {$IFDEF ENABLE_GFX}
2017 g_GFX_QueueEffect(
2018 R_GFX_TELEPORT,
2019 FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)-32,
2020 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2)-32
2021 );
2022 {$ENDIF}
2023 if g_Game_IsServer and g_Game_IsNet then
2024 MH_SEND_Effect(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)-32,
2025 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2)-32, 1,
2026 NET_GFX_TELE);
2027 end;
2029 FObj.X := X - FObj.Rect.X;
2030 FObj.Y := Y - FObj.Rect.Y;
2031 FObj.oldX := FObj.X; // don't interpolate after teleport
2032 FObj.oldY := FObj.Y;
2033 positionChanged();
2035 if dir = 1 then
2036 FDirection := TDirection.D_LEFT
2037 else
2038 if dir = 2 then
2039 FDirection := TDirection.D_RIGHT
2040 else
2041 if dir = 3 then
2042 begin // обратное
2043 if FDirection = TDirection.D_RIGHT then
2044 FDirection := TDirection.D_LEFT
2045 else
2046 FDirection := TDirection.D_RIGHT;
2047 end;
2049 // Эффект телепорта в точке назначения:
2050 if not silent then
2051 begin
2052 {$IFDEF ENABLE_GFX}
2053 g_GFX_QueueEffect(
2054 R_GFX_TELEPORT,
2055 FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)-32,
2056 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2)-32
2057 );
2058 {$ENDIF}
2059 if g_Game_IsServer and g_Game_IsNet then
2060 MH_SEND_Effect(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)-32,
2061 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2)-32, 0,
2062 NET_GFX_TELE);
2063 end;
2065 if g_Game_IsServer and g_Game_IsNet then MH_SEND_MonsterPos(FUID);
2066 Result := True;
2067 end;
2069 procedure TMonster.PreUpdate();
2070 begin
2071 FObj.oldX := FObj.X;
2072 FObj.oldY := FObj.Y;
2073 end;
2075 procedure TMonster.Update();
2076 {$IFDEF ENABLE_CORPSES}
2077 var co: TObj;
2078 {$ENDIF}
2079 {$IF DEFINED(ENABLE_GIBS) OR DEFINED(ENABLE_CORPSES)}
2080 var b: Integer;
2081 {$ENDIF}
2082 var
2083 a, sx, sy, wx, wy, oldvelx: Integer;
2084 st: Word;
2085 o: TObj;
2086 fall: Boolean;
2087 mon: TMonster;
2088 mit: PMonster;
2089 it: TMonsterGrid.Iter;
2090 label
2091 _end;
2092 begin
2093 fall := True;
2095 // Монстр статичен пока идет warmup
2096 if (gLMSRespawn > LMS_RESPAWN_NONE) then exit;
2098 // Рыбы "летают" только в воде:
2099 if FMonsterType = MONSTER_FISH then
2100 if g_Obj_CollidePanel(@FObj, 0, 0, PANEL_WATER or PANEL_ACID1 or PANEL_ACID2) then
2101 if (FState <> MONSTATE_DIE) and (FState <> MONSTATE_DEAD) then
2102 fall := False;
2104 // Летающие монтсры:
2105 if ((FMonsterType = MONSTER_SOUL) or
2106 (FMonsterType = MONSTER_PAIN) or
2107 (FMonsterType = MONSTER_CACO)) and
2108 (FState <> MONSTATE_DIE) and
2109 (FState <> MONSTATE_DEAD) then
2110 fall := False;
2112 // Меняем скорость только по четным кадрам:
2113 if gTime mod (GAME_TICK*2) <> 0 then
2114 begin
2115 g_Obj_Move(@FObj, fall, True, True);
2116 positionChanged(); // this updates spatial accelerators
2117 Exit;
2118 end;
2120 if FPainTicks > 0 then
2121 Dec(FPainTicks)
2122 else
2123 FPainSound := False;
2125 // Двигаемся:
2126 st := g_Obj_Move(@FObj, fall, True, True);
2127 positionChanged(); // this updates spatial accelerators
2129 // Если горим - поджигаем других монстров, но не на 100 тиков каждый раз:
2130 if FFireTime > 0 then
2131 begin
2132 it := monsGrid.forEachInAABB(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, FObj.Rect.Width, FObj.Rect.Height);
2133 for mit in it do
2134 if mit.UID <> FUID then
2135 mit.CatchFire(FFireAttacker, FFireTime);
2136 end;
2138 // Вылетел за карту - удаляем и запускаем триггеры:
2139 if WordBool(st and MOVE_FALLOUT) or (FObj.X < -1000) or
2140 (FObj.X > gMapInfo.Width+1000) or (FObj.Y < -1000) then
2141 begin
2142 FRemoved := True;
2143 if alive and (gLMSRespawn = LMS_RESPAWN_NONE) then
2144 begin
2145 Inc(gCoopMonstersKilled);
2146 if g_Game_IsNet then
2147 MH_SEND_GameStats;
2148 end;
2149 ActivateTriggers();
2150 Exit;
2151 end;
2153 oldvelx := FObj.Vel.X;
2155 // Сопротивление воздуха для трупа:
2156 if (FState = MONSTATE_DIE) or (FState = MONSTATE_DEAD) then
2157 FObj.Vel.X := z_dec(FObj.Vel.X, 1);
2159 if FFireTime > 0 then
2160 begin
2161 if WordBool(st and MOVE_INWATER) then
2162 FFireTime := 0
2163 else
2164 begin
2165 OnFireFlame(1);
2166 FFireTime := FFireTime - 1;
2167 if (FState <> MONSTATE_DIE) and (FState <> MONSTATE_DEAD) then
2168 if FFirePainTime = 0 then
2169 begin
2170 Damage(5, FFireAttacker, 0, 0, HIT_FLAME);
2171 FFirePainTime := 18;
2172 end
2173 else
2174 FFirePainTime := FFirePainTime - 1;
2175 end;
2176 end;
2178 // Мертвый ничего не делает:
2179 if (FState = MONSTATE_DEAD) then
2180 goto _end;
2182 // AI монстров выключен:
2183 if g_debug_MonsterOff then
2184 begin
2185 FSleep := 1;
2186 if FState <> MONSTATE_SLEEP then
2187 SetState(MONSTATE_SLEEP);
2188 end;
2190 // Возможно, создаем пузырьки в воде:
2191 if WordBool(st and MOVE_INWATER) and (Random(32) = 0) then
2192 case FMonsterType of
2193 MONSTER_FISH:
2194 begin
2195 {$IFDEF ENABLE_GFX}
2196 if Random(4) = 0 then
2197 begin
2198 g_GFX_Bubbles(
2199 FObj.X + FObj.Rect.X + Random(FObj.Rect.Width),
2200 FObj.Y + FObj.Rect.Y + Random(4),
2201 1,
2202 0,
2204 );
2205 end;
2206 {$ENDIF}
2207 end;
2208 MONSTER_ROBO, MONSTER_BARREL:
2209 begin
2210 {$IFDEF ENABLE_GFX}
2211 g_GFX_Bubbles(
2212 FObj.X + FObj.Rect.X + Random(FObj.Rect.Width),
2213 FObj.Y + FObj.Rect.Y + Random(4),
2214 1,
2215 0,
2217 );
2218 {$ENDIF}
2219 end;
2220 else
2221 begin
2222 {$IFDEF ENABLE_GFX}
2223 g_GFX_Bubbles(
2224 FObj.X + FObj.Rect.X + Random(FObj.Rect.Width - 4),
2225 FObj.Y+FObj.Rect.Y + Random(4),
2226 5,
2227 4,
2229 );
2230 {$ENDIF}
2231 if Random(2) = 0 then
2232 g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', FObj.X, FObj.Y)
2233 else
2234 g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', FObj.X, FObj.Y);
2235 end;
2236 end;
2238 // Если прошел первый кадр анимации взрыва бочки, то взрыв:
2239 if FMonsterType = MONSTER_BARREL then
2240 begin
2241 if (FState = MONSTATE_DIE) and (FAnim[FCurAnim, FDirection].CurrentFrame = 1) and
2242 (FAnim[FCurAnim, FDirection].Counter = 0) then
2243 g_Weapon_Explode(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
2244 FObj.Y+FObj.Rect.Y+FObj.Rect.Height-16,
2245 60, FUID);
2246 end;
2248 // Lost_Soul вылетел из воды => ускоряется:
2249 if FMonsterType = MONSTER_SOUL then
2250 if WordBool(st and MOVE_HITAIR) then
2251 g_Obj_SetSpeed(@FObj, 16);
2253 if FAmmo < 0 then
2254 FAmmo := FAmmo + 1;
2256 // Если начали всплывать, то продолжаем:
2257 if FObj.Vel.Y < 0 then
2258 if WordBool(st and MOVE_INWATER) then
2259 FObj.Vel.Y := -4;
2261 // Таймер - ждем после потери цели:
2262 FTargetTime := FTargetTime + 1;
2264 {$IFDEF ENABLE_SHELLS}
2265 // Гильзы
2266 if FShellTimer > -1 then
2267 begin
2268 if FShellTimer = 0 then
2269 begin
2270 if FShellType = SHELL_SHELL then
2271 begin
2272 g_Shells_Create(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
2273 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2),
2274 GameVelX, GameVelY-2, SHELL_SHELL)
2275 end
2276 else if FShellType = SHELL_DBLSHELL then
2277 begin
2278 g_Shells_Create(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
2279 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2),
2280 GameVelX-1, GameVelY-2, SHELL_SHELL);
2281 g_Shells_Create(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
2282 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2),
2283 GameVelX+1, GameVelY-2, SHELL_SHELL);
2284 end;
2285 FShellTimer := -1;
2286 end
2287 else
2288 begin
2289 Dec(FShellTimer);
2290 end;
2291 end;
2292 {$ENDIF}
2294 // Пробуем увернуться от летящей пули:
2295 if fall then
2296 if (FState in [MONSTATE_GO, MONSTATE_RUN, MONSTATE_RUNOUT,
2297 MONSTATE_ATTACK, MONSTATE_SHOOT]) then
2298 if g_Weapon_Danger(FUID, FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y,
2299 FObj.Rect.Width, FObj.Rect.Height, 50) then
2300 if (g_Obj_CollideLevel(@FObj, 0, 1) or g_Obj_StayOnStep(@FObj)) and
2301 (FObj.Accel.Y = 0) then
2302 FObj.Vel.Y := -MONSTERTABLE[FMonsterType].Jump;
2304 case FState of
2305 MONSTATE_PAIN: // Состояние - Боль
2306 begin
2307 // Боль сильная => монстр кричит:
2308 if FPain >= MONSTERTABLE[FMonsterType].Pain then
2309 begin
2310 FPain := MONSTERTABLE[FMonsterType].Pain;
2311 if gSoundEffectsDF then PainSound();
2312 end;
2313 if (not gSoundEffectsDF) and (FPain >= MONSTERTABLE[FMonsterType].MinPain) then
2314 PainSound();
2316 // Снижаем боль со временем:
2317 FPain := FPain - 5;
2319 // Боль уже не ошутимая => идем дальше:
2320 if FPain <= MONSTERTABLE[FMonsterType].MinPain then
2321 begin
2322 FPain := 0;
2323 FAmmo := -9;
2324 SetState(MONSTATE_GO);
2325 end;
2326 end;
2328 MONSTATE_SLEEP: // Состояние - Сон
2329 begin
2330 // Спим:
2331 FSleep := FSleep + 1;
2333 // Проспали достаточно:
2334 if FSleep >= 18 then
2335 FSleep := 0
2336 else // еще спим
2337 goto _end;
2339 // На игроков идут только обычные монстры, киллеры и маньяки
2340 if (FBehaviour = BH_NORMAL) or (FBehaviour = BH_KILLER) or (FBehaviour = BH_MANIAC) then
2341 // Если есть игрок рядом, просыпаемся и идем к нему:
2342 if (gPlayers <> nil) then
2343 for a := 0 to High(gPlayers) do
2344 if (gPlayers[a] <> nil) and (gPlayers[a].alive)
2345 and (not gPlayers[a].NoTarget) and (gPlayers[a].FMegaRulez[MR_INVIS] < gTime) then
2346 with gPlayers[a] do
2347 if g_Look(@FObj, @Obj, FDirection) then
2348 begin
2349 FTargetUID := gPlayers[a].UID;
2350 FTargetTime := 0;
2351 WakeUpSound();
2352 SetState(MONSTATE_GO);
2353 Break;
2354 end;
2356 // На монстров тянет маньяков, поехавших и каннибалов
2357 if (FTargetUID = 0) and ((FBehaviour = BH_MANIAC)
2358 or (FBehaviour = BH_INSANE) or (FBehaviour = BH_CANNIBAL)) then
2359 // Если есть подходящий монстр рядом:
2360 if gMonsters <> nil then
2361 for a := 0 to High(gMonsters) do
2362 if (gMonsters[a] <> nil) and (gMonsters[a].alive) and
2363 (gMonsters[a].FUID <> FUID) then
2364 begin
2365 // Маньяки нападают на всех монстров, кроме друзей
2366 if (FBehaviour = BH_MANIAC) and
2367 (IsFriend(gMonsters[a].FMonsterType, FMonsterType)) then
2368 Continue;
2369 // Поехавшие также, но могут обозлиться на бочку
2370 if (FBehaviour = BH_INSANE) and (gMonsters[a].FMonsterType <> MONSTER_BARREL) and
2371 (IsFriend(gMonsters[a].FMonsterType, FMonsterType)) then
2372 Continue;
2373 // Каннибалы нападают на себе подобных
2374 if (FBehaviour = BH_CANNIBAL) and (gMonsters[a].FMonsterType <> FMonsterType) then
2375 Continue;
2376 if g_Look(@FObj, @gMonsters[a].Obj, FDirection) then
2377 begin
2378 FTargetUID := gMonsters[a].UID;
2379 FTargetTime := 0;
2380 WakeUpSound();
2381 SetState(MONSTATE_GO);
2382 Break;
2383 end;
2384 end;
2385 end;
2387 MONSTATE_WAIT: // Состояние - Ожидание
2388 begin
2389 // Ждем:
2390 FSleep := FSleep - 1;
2392 // Выждали достаточно - идем:
2393 if FSleep < 0 then
2394 SetState(MONSTATE_GO);
2395 end;
2397 MONSTATE_GO: // Состояние - Движение (с осмотром ситуации)
2398 begin
2399 // Если наткнулись на БлокМон - убегаем от него:
2400 if WordBool(st and MOVE_BLOCK) then
2401 begin
2402 Turn();
2403 FSleep := 40;
2404 SetState(MONSTATE_RUNOUT);
2406 goto _end;
2407 end;
2409 // Если монстр - колдун, то пробуем воскресить кого-нибудь:
2410 if (FMonsterType = MONSTER_VILE) then
2411 if isCorpse(@FObj, False) <> -1 then
2412 begin
2413 FObj.Vel.X := 0;
2414 SetState(MONSTATE_ATTACK, ANIM_ATTACK2);
2416 goto _end;
2417 end;
2419 // Цель погибла или давно ждем:
2420 if (not GetPos(FTargetUID, @o)) or (FTargetTime > MAX_ATM) then
2421 if not findNewPrey() then
2422 begin // Новых целей нет
2423 FTargetUID := 0;
2424 o.X := FObj.X+pt_x;
2425 o.Y := FObj.Y+pt_y;
2426 o.Vel.X := 0;
2427 o.Vel.Y := 0;
2428 o.Accel.X := 0;
2429 o.Accel.Y := 0;
2430 o.Rect := _Rect(0, 0, 0, 1);
2431 end
2432 else // Новая цель есть - берем ее координаты
2433 GetPos(FTargetUID, @o);
2435 // Цель очень близко - пинаем:
2436 if g_Obj_Collide(@FObj, @o) and (FTargetUID <> 0) then
2437 begin
2438 FTargetTime := 0;
2439 if (FMonsterType <> MONSTER_CYBER) or (Random(2) = 0) then
2440 begin
2441 if kick(@o) then
2442 goto _end;
2443 end;
2444 end;
2446 // Расстояние до цели:
2447 sx := o.X+o.Rect.X+(o.Rect.Width div 2)-(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2));
2448 sy := o.Y+o.Rect.Y+(o.Rect.Height div 2)-(FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2));
2450 // Поворачиваемся в сторону цели:
2451 if sx > 0 then
2452 FDirection := TDirection.D_RIGHT
2453 else
2454 FDirection := TDirection.D_LEFT;
2456 // Если монстр умеет стрелять и есть по кому - стреляем:
2457 if canShoot(FMonsterType) and (FTargetUID <> 0) then
2458 if Abs(sx) > Abs(sy) then // угол выстрела удобный
2459 if shoot(@o, False) then
2460 goto _end;
2462 // Если цель почти на одной вертикали - бегаем туда-сюда:
2463 if Abs(sx) < 40 then
2464 if FMonsterType <> MONSTER_FISH then
2465 begin
2466 FSleep := 15;
2467 SetState(MONSTATE_RUN);
2468 if Random(2) = 0 then
2469 FDirection := TDirection.D_LEFT
2470 else
2471 FDirection := TDirection.D_RIGHT;
2473 goto _end;
2474 end;
2476 // Уперлись в стену:
2477 if WordBool(st and MOVE_HITWALL) then
2478 begin
2479 if g_Triggers_PressR(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, FObj.Rect.Width,
2480 FObj.Rect.Height, FUID, ACTIVATE_MONSTERPRESS) <> nil then
2481 begin // Смогли нажать кнопку - небольшое ожидание
2482 FSleep := 4;
2483 SetState(MONSTATE_WAIT);
2485 goto _end;
2486 end;
2488 case FMonsterType of
2489 MONSTER_CACO, MONSTER_SOUL, MONSTER_PAIN, MONSTER_FISH: ;
2490 else // Не летают:
2491 if (g_Obj_CollideLevel(@FObj, 0, 1) or g_Obj_StayOnStep(@FObj)) and
2492 (FObj.Accel.Y = 0) then
2493 begin // Стоим на твердом полу или ступени
2494 // Прыжок через стену:
2495 FObj.Vel.Y := -MONSTERTABLE[FMonsterType].Jump;
2496 SetState(MONSTATE_CLIMB);
2497 end;
2498 end;
2500 goto _end;
2501 end;
2503 // Монстры, не подверженные гравитации:
2504 if (FMonsterType = MONSTER_CACO) or (FMonsterType = MONSTER_SOUL) or
2505 (FMonsterType = MONSTER_PAIN) or (FMonsterType = MONSTER_FISH) then
2506 begin
2507 if FMonsterType = MONSTER_FISH then
2508 begin // Рыба
2509 if not WordBool(st and MOVE_INWATER) then
2510 begin // Рыба вне воды:
2511 if g_Obj_CollideLevel(@FObj, 0, 1) or g_Obj_StayOnStep(@FObj) then
2512 begin // "Стоит" твердо
2513 // Рыба трепыхается на поверхности:
2514 if FObj.Accel.Y = 0 then FObj.Vel.Y := -6;
2515 FObj.Accel.X := FObj.Accel.X - 8 + Random(17);
2516 end;
2518 // Рыбе больно:
2519 SetState(MONSTATE_PAIN);
2520 FPain := FPain + 50;
2521 end
2522 else // Рыба в воде
2523 begin
2524 // Плывем в сторону цели по-вертикали:
2525 if Abs(sy) > 8 then
2526 FObj.Vel.Y := g_basic.Sign(sy)*4
2527 else
2528 FObj.Vel.Y := 0;
2530 // Рыба плывет вверх:
2531 if FObj.Vel.Y < 0 then
2532 if not g_Obj_CollideLiquid(@FObj, 0, -16) then
2533 begin
2534 // Всплыли до поверхности - стоп:
2535 FObj.Vel.Y := 0;
2536 // Плаваем туда-сюда:
2537 if Random(2) = 0 then
2538 FDirection := TDirection.D_LEFT
2539 else
2540 FDirection := TDirection.D_RIGHT;
2541 FSleep := 20;
2542 SetState(MONSTATE_RUN);
2543 end;
2544 end;
2545 end
2546 else // Летающие монстры
2547 begin
2548 // Летим в сторону цели по-вертикали:
2549 if Abs(sy) > 8 then
2550 FObj.Vel.Y := g_basic.Sign(sy)*4
2551 else
2552 FObj.Vel.Y := 0;
2553 end;
2554 end
2555 else // "Наземные" монстры
2556 begin
2557 {$IFDEF ENABLE_GIBS}
2558 // Возможно, пинаем куски:
2559 if (FObj.Vel.X <> 0) and (gGibs <> nil) then
2560 begin
2561 b := Abs(FObj.Vel.X);
2562 if b > 1 then b := b * (Random(8 div b) + 1);
2563 for a := 0 to High(gGibs) do
2564 begin
2565 if gGibs[a].alive and
2566 g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y+FObj.Rect.Height-4,
2567 FObj.Rect.Width, 8, @gGibs[a].Obj) and (Random(3) = 0) then
2568 begin
2569 // Пинаем куски
2570 if FObj.Vel.X < 0 then
2571 begin
2572 g_Obj_PushA(@gGibs[a].Obj, b, Random(61)+120); // налево
2573 end
2574 else
2575 begin
2576 g_Obj_PushA(@gGibs[a].Obj, b, Random(61)); // направо
2577 end;
2578 end;
2579 end;
2580 end;
2581 {$ENDIF}
2582 {$IFDEF ENABLE_CORPSES}
2583 // Боссы могут пинать трупы:
2584 if (FMonsterType in [MONSTER_CYBER, MONSTER_SPIDER, MONSTER_ROBO]) and
2585 (FObj.Vel.X <> 0) and (gCorpses <> nil) then
2586 begin
2587 b := Abs(FObj.Vel.X);
2588 if b > 1 then b := b * (Random(8 div b) + 1);
2589 for a := 0 to High(gCorpses) do
2590 if (gCorpses[a] <> nil) and (gCorpses[a].State > 0) then
2591 begin
2592 co := gCorpses[a].Obj;
2593 if g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y+FObj.Rect.Height-4,
2594 FObj.Rect.Width, 8, @co) and (Random(3) = 0) then
2595 // Пинаем трупы
2596 if FObj.Vel.X < 0 then
2597 gCorpses[a].Damage(b*2, FUID, -b, Random(7)) // налево
2598 else
2599 gCorpses[a].Damage(b*2, FUID, b, Random(7)); // направо
2600 end;
2601 end;
2602 {$ENDIF}
2603 // Если цель высоко, то, возможно, прыгаем:
2604 if sy < -40 then
2605 if g_Obj_CollideLevel(@FObj, 0, 1) or g_Obj_StayOnStep(@FObj) then
2606 // стоит твердо
2607 if (Random(4) = 0) and (FObj.Accel.Y = 0) then
2608 FObj.Vel.Y := -MONSTERTABLE[FMonsterType].Jump;
2609 end;
2611 FSleep := FSleep + 1;
2613 // Иногда рычим:
2614 if FSleep >= 8 then
2615 begin
2616 FSleep := 0;
2617 if Random(8) = 0 then
2618 ActionSound();
2619 end;
2621 // Бежим в выбранную сторону:
2622 if FDirection = TDirection.D_RIGHT then
2623 FObj.Vel.X := MONSTERTABLE[FMonsterType].RunVel
2624 else
2625 FObj.Vel.X := -MONSTERTABLE[FMonsterType].RunVel;
2627 // Если в воде, то замедляемся:
2628 if WordBool(st and MOVE_INWATER) then
2629 FObj.Vel.X := FObj.Vel.X div 2
2630 else // Рыбам не нужно замедляться
2631 if FMonsterType = MONSTER_FISH then
2632 FObj.Vel.X := 0;
2633 end;
2635 MONSTATE_RUN: // Состояние - Бег
2636 begin
2637 // Если наткнулись на БлокМон - убегаем от него:
2638 if WordBool(st and MOVE_BLOCK) then
2639 begin
2640 Turn();
2641 FSleep := 40;
2642 SetState(MONSTATE_RUNOUT);
2644 goto _end;
2645 end;
2647 FSleep := FSleep - 1;
2649 // Пробежали достаточно или врезались в стену => переходим на шаг:
2650 if (FSleep <= 0) or (WordBool(st and MOVE_HITWALL) and ((FObj.Vel.Y+FObj.Accel.Y) = 0)) then
2651 begin
2652 FSleep := 0;
2653 SetState(MONSTATE_GO);
2654 // Стена - идем обратно:
2655 if WordBool(st and (MOVE_HITWALL or MOVE_BLOCK)) then
2656 Turn();
2657 // Иногда рычим:
2658 if Random(8) = 0 then
2659 ActionSound();
2660 end;
2662 // Бежим в выбранную сторону:
2663 if FDirection = TDirection.D_RIGHT then
2664 FObj.Vel.X := MONSTERTABLE[FMonsterType].RunVel
2665 else
2666 FObj.Vel.X := -MONSTERTABLE[FMonsterType].RunVel;
2668 // Если в воде, то замедляемся:
2669 if WordBool(st and MOVE_INWATER) then
2670 FObj.Vel.X := FObj.Vel.X div 2
2671 else // Рыбам не нужно замедляться
2672 if FMonsterType = MONSTER_FISH then
2673 FObj.Vel.X := 0;
2674 end;
2676 MONSTATE_RUNOUT: // Состояние - Убегает от чего-то
2677 begin
2678 // Вышли из БлокМона:
2679 if (not WordBool(st and MOVE_BLOCK)) and (FSleep > 0) then
2680 FSleep := 0;
2682 FSleep := FSleep - 1;
2684 // Убажели достаточно далеко => переходим на шаг:
2685 if FSleep <= -18 then
2686 begin
2687 FSleep := 0;
2688 SetState(MONSTATE_GO);
2689 // Стена/БлокМон - идем обратно:
2690 if WordBool(st and (MOVE_HITWALL or MOVE_BLOCK)) then
2691 Turn();
2692 // Иногда рычим:
2693 if Random(8) = 0 then
2694 ActionSound();
2695 end;
2697 // Бежим в выбранную сторону:
2698 if FDirection = TDirection.D_RIGHT then
2699 FObj.Vel.X := MONSTERTABLE[FMonsterType].RunVel
2700 else
2701 FObj.Vel.X := -MONSTERTABLE[FMonsterType].RunVel;
2703 // Если в воде, то замедляемся:
2704 if WordBool(st and MOVE_INWATER) then
2705 FObj.Vel.X := FObj.Vel.X div 2
2706 else // Рыбам не нужно замедляться
2707 if FMonsterType = MONSTER_FISH then
2708 FObj.Vel.X := 0;
2709 end;
2711 MONSTATE_CLIMB: // Состояние - Прыжок (чтобы обойти стену)
2712 begin
2713 // Достигли высшей точки прыжка или стена кончилась => переходим на шаг:
2714 if ((FObj.Vel.Y+FObj.Accel.Y) >= 0) or
2715 (not WordBool(st and MOVE_HITWALL)) then
2716 begin
2717 FSleep := 0;
2718 SetState(MONSTATE_GO);
2720 // Стена не кончилась => бежим от нее:
2721 if WordBool(st and (MOVE_HITWALL or MOVE_BLOCK)) then
2722 begin
2723 Turn();
2724 FSleep := 15;
2725 SetState(MONSTATE_RUN);
2726 end;
2727 end;
2729 // Бежим в выбранную сторону:
2730 if FDirection = TDirection.D_RIGHT then
2731 FObj.Vel.X := MONSTERTABLE[FMonsterType].RunVel
2732 else
2733 FObj.Vel.X := -MONSTERTABLE[FMonsterType].RunVel;
2735 // Если в воде, то замедляемся:
2736 if WordBool(st and MOVE_INWATER) then
2737 FObj.Vel.X := FObj.Vel.X div 2
2738 else // Рыбам не нужно замедляться
2739 if FMonsterType = MONSTER_FISH then
2740 FObj.Vel.X := 0;
2741 end;
2743 MONSTATE_ATTACK, // Состояние - Атака
2744 MONSTATE_SHOOT: // Состояние - Стрельба
2745 begin
2746 // Lost_Soul врезался в стену при атаке => переходит на шаг:
2747 if FMonsterType = MONSTER_SOUL then
2748 begin
2749 if WordBool(st and (MOVE_HITWALL or MOVE_HITCEIL or MOVE_HITLAND)) then
2750 SetState(MONSTATE_GO);
2752 goto _end;
2753 end;
2755 // Замедляемся при атаке:
2756 if FMonsterType <> MONSTER_FISH then
2757 FObj.Vel.X := z_dec(FObj.Vel.X, 1);
2759 // Нужно стрелять, а монстр - колдун:
2760 if (FMonsterType = MONSTER_VILE) and (FState = MONSTATE_SHOOT) then
2761 begin
2762 // Цель погибла => идем дальше:
2763 if not GetPos(FTargetUID, @o) then
2764 begin
2765 SetState(MONSTATE_GO);
2767 goto _end;
2768 end;
2770 // Цель не видно => идем дальше:
2771 if not g_Look(@FObj, @o, FDirection) then
2772 begin
2773 SetState(MONSTATE_GO);
2775 goto _end;
2776 end;
2778 // Цель в воде - не загорится => идем дальше:
2779 if g_Obj_CollideWater(@o, 0, 0) then
2780 begin
2781 SetState(MONSTATE_GO);
2783 goto _end;
2784 end;
2786 // Жарим цель:
2787 tx := o.X+o.Rect.X+(o.Rect.Width div 2);
2788 ty := o.Y+o.Rect.Y+(o.Rect.Height div 2);
2789 g_Weapon_HitUID(FTargetUID, 2, FUID, HIT_SOME);
2790 end;
2791 end;
2792 end; // case FState of ...
2794 _end:
2796 // Состояние - Воскрешение:
2797 if FState = MONSTATE_REVIVE then
2798 if FAnim[FCurAnim, FDirection].Played then
2799 begin // Обратная анимация умирания закончилась - идем дальше:
2800 FAnim[FCurAnim, FDirection].Revert(False);
2801 SetState(MONSTATE_GO);
2802 end;
2804 // Если есть анимация огня колдуна - пусть она идет:
2805 if vilefire <> nil then
2806 vilefire.Update();
2808 // Состояние - Умирает и текущая анимация проиграна:
2809 if (FState = MONSTATE_DIE) and
2810 (FAnim[FCurAnim, FDirection] <> nil) and
2811 (FAnim[FCurAnim, FDirection].Played) then
2812 begin
2813 // Умер:
2814 SetState(MONSTATE_DEAD);
2816 // Pain_Elemental при смерти выпускает 3 Lost_Soul'а:
2817 if (FMonsterType = MONSTER_PAIN) then
2818 begin
2819 mon := g_Monsters_Create(MONSTER_SOUL, FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)-30,
2820 FObj.Y+FObj.Rect.Y+20, TDirection.D_LEFT);
2821 if mon <> nil then
2822 begin
2823 mon.SetState(MONSTATE_GO);
2824 mon.FNoRespawn := True;
2825 Inc(gTotalMonsters);
2826 if g_Game_IsNet then MH_SEND_MonsterSpawn(mon.UID);
2827 end;
2829 mon := g_Monsters_Create(MONSTER_SOUL, FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
2830 FObj.Y+FObj.Rect.Y+20, TDirection.D_RIGHT);
2831 if mon <> nil then
2832 begin
2833 mon.SetState(MONSTATE_GO);
2834 mon.FNoRespawn := True;
2835 Inc(gTotalMonsters);
2836 if g_Game_IsNet then MH_SEND_MonsterSpawn(mon.UID);
2837 end;
2839 mon := g_Monsters_Create(MONSTER_SOUL, FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)-15,
2840 FObj.Y+FObj.Rect.Y, TDirection.D_RIGHT);
2841 if mon <> nil then
2842 begin
2843 mon.SetState(MONSTATE_GO);
2844 mon.FNoRespawn := True;
2845 Inc(gTotalMonsters);
2846 if g_Game_IsNet then MH_SEND_MonsterSpawn(mon.UID);
2847 end;
2849 if g_Game_IsNet then MH_SEND_CoopStats();
2850 end;
2852 // У этих монстров нет трупов:
2853 if (FMonsterType = MONSTER_PAIN) or
2854 (FMonsterType = MONSTER_SOUL) or
2855 (FMonsterType = MONSTER_BARREL) then
2856 FRemoved := True;
2857 end;
2859 // Совершение атаки и стрельбы:
2860 if (FState = MONSTATE_ATTACK) or (FState = MONSTATE_SHOOT) then
2861 if (FAnim[FCurAnim, FDirection] <> nil) then
2862 // Анимация атаки есть - можно атаковать
2863 if (FAnim[FCurAnim, FDirection].Played) then
2864 begin // Анимация атаки закончилась => переходим на шаг
2865 if FState = MONSTATE_ATTACK then
2866 begin // Состояние - Атака
2867 // Если монстр не Lost_Soul, то после атаки переходим на шаг:
2868 if FMonsterType <> MONSTER_SOUL then
2869 SetState(MONSTATE_GO);
2870 end
2871 else // Состояние - Стрельба
2872 begin
2873 // Переходим на шаг, если не надо стрелять еще раз:
2874 if not FChainFire then
2875 SetState(MONSTATE_GO)
2876 else
2877 begin // Надо стрелять еще
2878 FChainFire := False;
2879 // Т.к. состояние не изменилось, и нужен
2880 // новый цикл ожидания завершения анимации:
2881 FAnim[FCurAnim, FDirection].Reset();
2882 end;
2883 end;
2885 FWaitAttackAnim := False;
2886 end
2888 else // Анимация атаки еще идет (исключение - Lost_Soul):
2889 if (FMonsterType = MONSTER_SOUL) or
2890 ( (not FWaitAttackAnim) and
2891 (FAnim[FCurAnim, FDirection].CurrentFrame =
2892 (FAnim[FCurAnim, FDirection].TotalFrames div 2))
2893 ) then
2894 begin // Атаки еще не было и это середина анимации атаки
2895 if FState = MONSTATE_ATTACK then
2896 begin // Состояние - Атака
2897 // Если это Lost_Soul, то сбрасываем анимацию атаки:
2898 if FMonsterType = MONSTER_SOUL then
2899 FAnim[FCurAnim, FDirection].Reset();
2901 case FMonsterType of
2902 MONSTER_SOUL, MONSTER_IMP, MONSTER_DEMON:
2903 // Грызем первого попавшегося:
2904 if g_Weapon_Hit(@FObj, 15, FUID, HIT_SOME) <> 0 then
2905 // Lost_Soul укусил кого-то => переходит на шаг:
2906 if FMonsterType = MONSTER_SOUL then
2907 SetState(MONSTATE_GO);
2909 MONSTER_FISH:
2910 // Рыба кусает первого попавшегося со звуком:
2911 if g_Weapon_Hit(@FObj, 10, FUID, HIT_SOME) <> 0 then
2912 g_Sound_PlayExAt('SOUND_MONSTER_FISH_ATTACK', FObj.X, FObj.Y);
2914 MONSTER_SKEL, MONSTER_ROBO, MONSTER_CYBER:
2915 // Робот, кибер или скелет сильно пинаются:
2916 if FCurAnim = ANIM_ATTACK2 then
2917 begin
2918 o := FObj;
2919 o.Vel.X := IfThen(FDirection = TDirection.D_RIGHT, 1, -1)*IfThen(FMonsterType = MONSTER_CYBER, 60, 50);
2920 if g_Weapon_Hit(@o, IfThen(FMonsterType = MONSTER_CYBER, 33, 50), FUID, HIT_SOME) <> 0 then
2921 g_Sound_PlayExAt('SOUND_MONSTER_SKEL_HIT', FObj.X, FObj.Y);
2922 end;
2924 MONSTER_VILE:
2925 // Колдун пытается воскрешать:
2926 if FCurAnim = ANIM_ATTACK2 then
2927 begin
2928 sx := isCorpse(@FObj, True);
2929 if sx <> -1 then
2930 begin // Нашли, кого воскресить
2931 gMonsters[sx].SetState(MONSTATE_REVIVE);
2932 g_Sound_PlayExAt('SOUND_MONSTER_SLOP', FObj.X, FObj.Y);
2933 // Воскрешать - себе вредить:
2934 {g_Weapon_HitUID(FUID, 5, 0, HIT_SOME);}
2935 end;
2936 end;
2937 end;
2938 end
2940 else // Состояние - Стрельба
2941 begin
2942 // Вычисляем координаты, откуда вылетит пуля:
2943 wx := MONSTER_ANIMTABLE[FMonsterType].wX;
2945 if FDirection = TDirection.D_LEFT then
2946 begin
2947 wx := MONSTER_ANIMTABLE[FMonsterType].wX-(MONSTERTABLE[FMonsterType].Rect.X+(MONSTERTABLE[FMonsterType].Rect.Width div 2));
2948 wx := MONSTERTABLE[FMonsterType].Rect.X+(MONSTERTABLE[FMonsterType].Rect.Width div 2)-wx;
2949 end; // Это значит: wx := hitX + (hitWidth / 2) - (wx - (hitX + (hitWidth / 2)))
2951 wx := FObj.X + wx;
2952 wy := FObj.Y + MONSTER_ANIMTABLE[FMonsterType].wY;
2954 // Монстр не может целиться в объект за спиной, стреляя влево:
2955 if (FDirection = TDirection.D_LEFT) and (tx > wx) then
2956 begin
2957 tx := wx - 32;
2958 ty := wy + Random(11) - 5;
2959 end;
2960 // И аналогично, стреляя вправо:
2961 if (FDirection = TDirection.D_RIGHT) and (tx < wx) then
2962 begin
2963 tx := wx + 32;
2964 ty := wy + Random(11) - 5;
2965 end;
2967 // Делаем выстрел нужным оружием:
2968 case FMonsterType of
2969 MONSTER_IMP:
2970 g_Weapon_ball1(wx, wy, tx, ty, FUID);
2971 MONSTER_ZOMBY:
2972 begin
2973 g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', wx, wy);
2974 g_Weapon_gun(wx, wy, tx, ty, 1, 3, FUID, True);
2975 {$IFDEF ENABLE_SHELLS}
2976 g_Shells_Create(wx, wy, 0, -2, SHELL_BULLET);
2977 {$ENDIF}
2978 end;
2979 MONSTER_SERG:
2980 begin
2981 g_Weapon_shotgun(wx, wy, tx, ty, FUID);
2982 if not gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', wx, wy);
2983 {$IFDEF ENABLE_SHELLS}
2984 FShellTimer := 10;
2985 FShellType := SHELL_SHELL;
2986 {$ENDIF}
2987 end;
2988 MONSTER_MAN:
2989 begin
2990 g_Weapon_dshotgun(wx, wy, tx, ty, FUID);
2991 {$IFDEF ENABLE_SHELLS}
2992 FShellTimer := 13;
2993 FShellType := SHELL_DBLSHELL;
2994 {$ENDIF}
2995 FAmmo := -36;
2996 end;
2997 MONSTER_CYBER:
2998 begin
2999 g_Weapon_rocket(wx, wy, tx, ty, FUID);
3000 // MH_SEND_MonsterAttack(FUID, wx, wy, tx, ty);
3001 end;
3002 MONSTER_SKEL:
3003 g_Weapon_revf(wx, wy, tx, ty, FUID, FTargetUID);
3004 MONSTER_CGUN:
3005 begin
3006 g_Weapon_mgun(wx, wy, tx, ty, FUID);
3007 if not gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', wx, wy);
3008 {$IFDEF ENABLE_SHELLS}
3009 g_Shells_Create(wx, wy, 0, -2, SHELL_BULLET);
3010 {$ENDIF}
3011 end;
3012 MONSTER_SPIDER:
3013 begin
3014 g_Weapon_mgun(wx, wy, tx, ty, FUID);
3015 if not gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', wx, wy);
3016 {$IFDEF ENABLE_SHELLS}
3017 g_Shells_Create(wx, wy, 0, -2, SHELL_SHELL);
3018 {$ENDIF}
3019 end;
3020 MONSTER_BSP:
3021 g_Weapon_aplasma(wx, wy, tx, ty, FUID);
3022 MONSTER_ROBO:
3023 g_Weapon_plasma(wx, wy, tx, ty, FUID);
3024 MONSTER_MANCUB:
3025 g_Weapon_manfire(wx, wy, tx, ty, FUID);
3026 MONSTER_BARON, MONSTER_KNIGHT:
3027 g_Weapon_ball7(wx, wy, tx, ty, FUID);
3028 MONSTER_CACO:
3029 g_Weapon_ball2(wx, wy, tx, ty, FUID);
3030 MONSTER_PAIN:
3031 begin // Создаем Lost_Soul:
3032 mon := g_Monsters_Create(MONSTER_SOUL, FObj.X+(FObj.Rect.Width div 2),
3033 FObj.Y+FObj.Rect.Y, FDirection);
3035 if mon <> nil then
3036 begin // Цель - цель Pain_Elemental'а. Летим к ней:
3037 mon.FTargetUID := FTargetUID;
3038 GetPos(FTargetUID, @o);
3039 mon.FTargetTime := 0;
3040 mon.FNoRespawn := True;
3041 mon.SetState(MONSTATE_GO);
3042 mon.shoot(@o, True);
3043 Inc(gTotalMonsters);
3045 if g_Game_IsNet then MH_SEND_MonsterSpawn(mon.UID);
3046 end;
3047 end;
3048 end;
3050 if FMonsterType <> MONSTER_PAIN then
3051 if g_Game_IsNet then
3052 MH_SEND_MonsterShot(FUID, wx, wy, tx, ty);
3054 // Скорострельные монстры:
3055 if (FMonsterType = MONSTER_CGUN) or
3056 (FMonsterType = MONSTER_SPIDER) or
3057 (FMonsterType = MONSTER_BSP) or
3058 (FMonsterType = MONSTER_MANCUB) or
3059 (FMonsterType = MONSTER_ROBO) then
3060 if not GetPos(FTargetUID, @o) then
3061 // Цель мертва - ищем новую:
3062 findNewPrey()
3063 else // Цель жива - продолжаем стрелять:
3064 if shoot(@o, False) then
3065 FChainFire := True;
3066 end;
3068 // Атака только 1 раз за анимацию атаки:
3069 FWaitAttackAnim := True;
3070 end;
3072 // Последний кадр текущей анимации:
3073 if FAnim[FCurAnim, FDirection].Counter = FAnim[FCurAnim, FDirection].Speed-1 then
3074 case FState of
3075 MONSTATE_GO, MONSTATE_RUN, MONSTATE_CLIMB, MONSTATE_RUNOUT:
3076 // Звуки при передвижении:
3077 case FMonsterType of
3078 MONSTER_CYBER:
3079 if (FAnim[FCurAnim, FDirection].CurrentFrame = 0) or
3080 (FAnim[FCurAnim, FDirection].CurrentFrame = 2) then
3081 g_Sound_PlayExAt('SOUND_MONSTER_CYBER_WALK', FObj.X, FObj.Y);
3082 MONSTER_SPIDER:
3083 if (FAnim[FCurAnim, FDirection].CurrentFrame = 0) or
3084 (FAnim[FCurAnim, FDirection].CurrentFrame = 2) then
3085 g_Sound_PlayExAt('SOUND_MONSTER_SPIDER_WALK', FObj.X, FObj.Y);
3086 MONSTER_BSP:
3087 if (FAnim[FCurAnim, FDirection].CurrentFrame = 0) or
3088 (FAnim[FCurAnim, FDirection].CurrentFrame = 2) then
3089 g_Sound_PlayExAt('SOUND_MONSTER_BSP_WALK', FObj.X, FObj.Y);
3090 MONSTER_ROBO:
3091 if (FAnim[FCurAnim, FDirection].CurrentFrame = 0) or
3092 (FAnim[FCurAnim, FDirection].CurrentFrame = 5) then
3093 g_Sound_PlayExAt('SOUND_MONSTER_BSP_WALK', FObj.X, FObj.Y);
3094 end;
3095 end;
3097 if g_Obj_CollidePanel(@FObj, 0, 0, PANEL_LIFTLEFT or PANEL_LIFTRIGHT) and
3098 not ((FState = MONSTATE_DEAD) or (FState = MONSTATE_DIE)) then
3099 FObj.Vel.X := oldvelx;
3101 // Если есть анимация, то пусть она идет:
3102 if FAnim[FCurAnim, FDirection] <> nil then
3103 FAnim[FCurAnim, FDirection].Update();
3104 end;
3106 procedure TMonster.SetDeadAnim;
3107 begin
3108 if FAnim <> nil then
3109 FAnim[FCurAnim, FDirection].CurrentFrame := FAnim[FCurAnim, FDirection].TotalFrames - 1;
3110 end;
3112 procedure TMonster.RevertAnim(R: Boolean = True);
3113 begin
3114 if FAnim <> nil then
3115 if FAnim[FCurAnim, FDirection].IsReverse <> R then
3116 FAnim[FCurAnim, FDirection].Revert(R);
3117 end;
3119 function TMonster.AnimIsReverse: Boolean;
3120 begin
3121 if FAnim <> nil then
3122 Result := FAnim[FCurAnim, FDirection].IsReverse
3123 else
3124 Result := False;
3125 end;
3127 procedure TMonster.ClientUpdate();
3128 {$IFDEF ENABLE_CORPSES}
3129 var a, b: Integer; co: TObj;
3130 {$ENDIF}
3131 var
3132 sx, sy, oldvelx: Integer;
3133 st: Word;
3134 o: TObj;
3135 fall: Boolean;
3136 label
3137 _end;
3138 begin
3139 sx := 0; // SHUT UP COMPILER
3140 sy := 0;
3141 fall := True;
3143 // Монстр статичен пока идет warmup
3144 if (gLMSRespawn > LMS_RESPAWN_NONE) then exit;
3146 // Рыбы "летают" только в воде:
3147 if FMonsterType = MONSTER_FISH then
3148 if g_Obj_CollidePanel(@FObj, 0, 0, PANEL_WATER or PANEL_ACID1 or PANEL_ACID2) then
3149 if (FState <> MONSTATE_DIE) and (FState <> MONSTATE_DEAD) then
3150 fall := False;
3152 // Летающие монтсры:
3153 if ((FMonsterType = MONSTER_SOUL) or
3154 (FMonsterType = MONSTER_PAIN) or
3155 (FMonsterType = MONSTER_CACO)) and
3156 (FState <> MONSTATE_DIE) and
3157 (FState <> MONSTATE_DEAD) then
3158 fall := False;
3160 // Меняем скорость только по четным кадрам:
3161 if gTime mod (GAME_TICK*2) <> 0 then
3162 begin
3163 g_Obj_Move(@FObj, fall, True, True);
3164 positionChanged(); // this updates spatial accelerators
3165 Exit;
3166 end;
3168 if FPainTicks > 0 then
3169 Dec(FPainTicks)
3170 else
3171 FPainSound := False;
3173 // Двигаемся:
3174 st := g_Obj_Move(@FObj, fall, True, True);
3175 positionChanged(); // this updates spatial accelerators
3177 // Вылетел за карту - удаляем и запускаем триггеры:
3178 if WordBool(st and MOVE_FALLOUT) or (FObj.X < -1000) or
3179 (FObj.X > gMapInfo.Width+1000) or (FObj.Y < -1000) then
3180 begin
3181 FRemoved := True;
3182 Exit;
3183 end;
3185 oldvelx := FObj.Vel.X;
3187 // Сопротивление воздуха для трупа:
3188 if (FState = MONSTATE_DIE) or (FState = MONSTATE_DEAD) then
3189 FObj.Vel.X := z_dec(FObj.Vel.X, 1);
3191 if FFireTime > 0 then
3192 begin
3193 if WordBool(st and MOVE_INWATER) then
3194 FFireTime := 0
3195 else
3196 begin
3197 OnFireFlame(1);
3198 FFireTime := FFireTime - 1;
3199 end;
3200 end;
3202 // Мертвый ничего не делает:
3203 if (FState = MONSTATE_DEAD) then
3204 goto _end;
3206 // Возможно, создаем пузырьки в воде:
3207 if WordBool(st and MOVE_INWATER) and (Random(32) = 0) then
3208 case FMonsterType of
3209 MONSTER_FISH:
3210 begin
3211 {$IFDEF ENABLE_GFX}
3212 if Random(4) = 0 then
3213 begin
3214 g_GFX_Bubbles(
3215 FObj.X + FObj.Rect.X + Random(FObj.Rect.Width),
3216 FObj.Y + FObj.Rect.Y + Random(4),
3217 1,
3218 0,
3220 );
3221 end;
3222 {$ENDIF}
3223 end;
3224 MONSTER_ROBO, MONSTER_BARREL:
3225 begin
3226 {$IFDEF ENABLE_GFX}
3227 g_GFX_Bubbles(
3228 FObj.X + FObj.Rect.X + Random(FObj.Rect.Width),
3229 FObj.Y + FObj.Rect.Y + Random(4),
3230 1,
3231 0,
3233 );
3234 {$ENDIF}
3235 end;
3236 else
3237 begin
3238 {$IFDEF ENABLE_GFX}
3239 g_GFX_Bubbles(
3240 FObj.X + FObj.Rect.X + Random(FObj.Rect.Width - 4),
3241 FObj.Y + FObj.Rect.Y + Random(4),
3242 5,
3243 4,
3245 );
3246 {$ENDIF}
3247 if Random(2) = 0 then
3248 g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', FObj.X, FObj.Y)
3249 else
3250 g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', FObj.X, FObj.Y);
3251 end;
3252 end;
3254 // Если прошел первый кадр анимации взрыва бочки, то взрыв:
3255 if FMonsterType = MONSTER_BARREL then
3256 begin
3257 if (FState = MONSTATE_DIE) and (FAnim[FCurAnim, FDirection].CurrentFrame = 1) and
3258 (FAnim[FCurAnim, FDirection].Counter = 0) then
3259 g_Weapon_Explode(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
3260 FObj.Y+FObj.Rect.Y+FObj.Rect.Height-16,
3261 60, FUID);
3262 end;
3264 // Lost_Soul вылетел из воды => ускоряется:
3265 if FMonsterType = MONSTER_SOUL then
3266 if WordBool(st and MOVE_HITAIR) then
3267 g_Obj_SetSpeed(@FObj, 16);
3269 if FAmmo < 0 then
3270 FAmmo := FAmmo + 1;
3272 // Если начали всплывать, то продолжаем:
3273 if FObj.Vel.Y < 0 then
3274 if WordBool(st and MOVE_INWATER) then
3275 FObj.Vel.Y := -4;
3277 // Таймер - ждем после потери цели:
3278 FTargetTime := FTargetTime + 1;
3280 {$IFDEF ENABLE_SHELLS}
3281 if FShellTimer > -1 then
3282 begin
3283 if FShellTimer = 0 then
3284 begin
3285 if FShellType = SHELL_SHELL then
3286 begin
3287 g_Shells_Create(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
3288 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2),
3289 GameVelX, GameVelY-2, SHELL_SHELL)
3290 end
3291 else if FShellType = SHELL_DBLSHELL then
3292 begin
3293 g_Shells_Create(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
3294 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2),
3295 GameVelX-1, GameVelY-2, SHELL_SHELL);
3296 g_Shells_Create(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
3297 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2),
3298 GameVelX+1, GameVelY-2, SHELL_SHELL);
3299 end;
3300 FShellTimer := -1;
3301 end
3302 else
3303 begin
3304 Dec(FShellTimer);
3305 end;
3306 end;
3307 {$ENDIF}
3309 // Пробуем увернуться от летящей пули:
3310 if fall then
3311 if (FState in [MONSTATE_GO, MONSTATE_RUN, MONSTATE_RUNOUT,
3312 MONSTATE_ATTACK, MONSTATE_SHOOT]) then
3313 if g_Weapon_Danger(FUID, FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y,
3314 FObj.Rect.Width, FObj.Rect.Height, 50) then
3315 if (g_Obj_CollideLevel(@FObj, 0, 1) or g_Obj_StayOnStep(@FObj)) and
3316 (FObj.Accel.Y = 0) then
3317 FObj.Vel.Y := -MONSTERTABLE[FMonsterType].Jump;
3319 case FState of
3320 MONSTATE_PAIN: // Состояние - Боль
3321 begin
3322 // Боль сильная => монстр кричит:
3323 if FPain >= MONSTERTABLE[FMonsterType].Pain then
3324 begin
3325 FPain := MONSTERTABLE[FMonsterType].Pain;
3326 if gSoundEffectsDF then PainSound();
3327 end;
3328 if (not gSoundEffectsDF) and (FPain >= MONSTERTABLE[FMonsterType].MinPain) then
3329 PainSound();
3331 // Снижаем боль со временем:
3332 FPain := FPain - 5;
3334 // Боль уже не ошутимая => идем дальше:
3335 if FPain <= MONSTERTABLE[FMonsterType].MinPain then
3336 begin
3337 SetState(MONSTATE_GO);
3338 FPain := 0;
3339 end;
3340 end;
3342 MONSTATE_SLEEP: // Состояние - Сон
3343 begin
3344 // Спим:
3345 FSleep := FSleep + 1;
3347 // Проспали достаточно:
3348 if FSleep >= 18 then
3349 FSleep := 0
3350 else // еще спим
3351 goto _end;
3352 end;
3354 MONSTATE_WAIT: // Состояние - Ожидание
3355 begin
3356 // Ждем:
3357 FSleep := FSleep - 1;
3358 end;
3360 MONSTATE_GO: // Состояние - Движение (с осмотром ситуации)
3361 begin
3362 // Если наткнулись на БлокМон - убегаем от него:
3363 if WordBool(st and MOVE_BLOCK) then
3364 begin
3365 Turn();
3366 FSleep := 40;
3367 SetState(MONSTATE_RUNOUT);
3369 goto _end;
3370 end;
3372 // Если монстр - колдун, то пробуем воскресить кого-нибудь:
3373 if (FMonsterType = MONSTER_VILE) then
3374 if isCorpse(@FObj, False) <> -1 then
3375 begin
3376 SetState(MONSTATE_ATTACK, ANIM_ATTACK2);
3377 FObj.Vel.X := 0;
3379 goto _end;
3380 end;
3382 // Если цель почти на одной вертикали - бегаем туда-сюда:
3383 if Abs(sx) < 40 then
3384 if FMonsterType <> MONSTER_FISH then
3385 begin
3386 SetState(MONSTATE_RUN);
3387 FSleep := 15;
3389 goto _end;
3390 end;
3392 // Уперлись в стену:
3393 if WordBool(st and MOVE_HITWALL) then
3394 begin
3395 case FMonsterType of
3396 MONSTER_CACO, MONSTER_SOUL, MONSTER_PAIN, MONSTER_FISH: ;
3397 else // Не летают:
3398 if (g_Obj_CollideLevel(@FObj, 0, 1) or g_Obj_StayOnStep(@FObj)) and
3399 (FObj.Accel.Y = 0) then
3400 begin // Стоим на твердом полу или ступени
3401 // Прыжок через стену:
3402 FObj.Vel.Y := -MONSTERTABLE[FMonsterType].Jump;
3403 SetState(MONSTATE_CLIMB);
3404 end;
3405 end;
3407 goto _end;
3408 end;
3410 // Монстры, не подверженные гравитации:
3411 if (FMonsterType = MONSTER_CACO) or (FMonsterType = MONSTER_SOUL) or
3412 (FMonsterType = MONSTER_PAIN) or (FMonsterType = MONSTER_FISH) then
3413 begin
3414 if FMonsterType = MONSTER_FISH then
3415 begin // Рыба
3416 if not WordBool(st and MOVE_INWATER) then
3417 begin // Рыба вне воды:
3418 if g_Obj_CollideLevel(@FObj, 0, 1) or g_Obj_StayOnStep(@FObj) then
3419 begin // "Стоит" твердо
3420 // Рыба трепыхается на поверхности:
3421 if FObj.Accel.Y = 0 then
3422 FObj.Vel.Y := -6;
3423 FObj.Accel.X := FObj.Accel.X - 8 + Random(17);
3424 end;
3426 // Рыбе больно:
3427 SetState(MONSTATE_PAIN);
3428 FPain := FPain + 50;
3429 end
3430 else // Рыба в воде
3431 begin
3432 // Плывем в сторону цели по-вертикали:
3433 if Abs(sy) > 8 then
3434 FObj.Vel.Y := g_basic.Sign(sy)*4
3435 else
3436 FObj.Vel.Y := 0;
3438 // Рыба плывет вверх:
3439 if FObj.Vel.Y < 0 then
3440 if not g_Obj_CollideLiquid(@FObj, 0, -16) then
3441 begin
3442 // Всплыли до поверхности - стоп:
3443 FObj.Vel.Y := 0;
3444 // Плаваем туда-сюда:
3445 SetState(MONSTATE_RUN);
3446 FSleep := 20;
3447 end;
3448 end;
3449 end
3450 else // Летающие монстры
3451 begin
3452 // Летим в сторону цели по-вертикали:
3453 if Abs(sy) > 8 then
3454 FObj.Vel.Y := g_basic.Sign(sy)*4
3455 else
3456 FObj.Vel.Y := 0;
3457 end;
3458 end
3459 else // "Наземные" монстры
3460 begin
3461 {$IFDEF ENBALE_GIBS}
3462 // Возможно, пинаем куски:
3463 if (FObj.Vel.X <> 0) and (gGibs <> nil) then
3464 begin
3465 b := Abs(FObj.Vel.X);
3466 if b > 1 then b := b * (Random(8 div b) + 1);
3467 for a := 0 to High(gGibs) do
3468 begin
3469 if gGibs[a].alive and
3470 g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y+FObj.Rect.Height-4,
3471 FObj.Rect.Width, 8, @gGibs[a].Obj) and (Random(3) = 0) then
3472 begin
3473 // Пинаем куски
3474 if FObj.Vel.X < 0 then
3475 begin
3476 g_Obj_PushA(@gGibs[a].Obj, b, Random(61)+120); // налево
3477 end
3478 else
3479 begin
3480 g_Obj_PushA(@gGibs[a].Obj, b, Random(61)); // направо
3481 end;
3482 positionChanged(); // this updates spatial accelerators
3483 end;
3484 end;
3485 end;
3486 {$ENDIF}
3487 {$IFDEF ENABLE_CORPSES}
3488 // Боссы могут пинать трупы:
3489 if (FMonsterType in [MONSTER_CYBER, MONSTER_SPIDER, MONSTER_ROBO]) and
3490 (FObj.Vel.X <> 0) and (gCorpses <> nil) then
3491 begin
3492 b := Abs(FObj.Vel.X);
3493 if b > 1 then b := b * (Random(8 div b) + 1);
3494 for a := 0 to High(gCorpses) do
3495 if (gCorpses[a] <> nil) and (gCorpses[a].State > 0) then
3496 begin
3497 co := gCorpses[a].Obj;
3498 if g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y+FObj.Rect.Height-4,
3499 FObj.Rect.Width, 8, @co) and (Random(3) = 0) then
3500 // Пинаем трупы
3501 if FObj.Vel.X < 0 then
3502 gCorpses[a].Damage(b*2, FUID, -b, Random(7)) // налево
3503 else
3504 gCorpses[a].Damage(b*2, FUID, b, Random(7)); // направо
3505 end;
3506 end;
3507 {$ENDIF}
3508 end;
3510 FSleep := FSleep + 1;
3512 // Иногда рычим:
3513 if FSleep >= 8 then
3514 begin
3515 FSleep := 0;
3516 if Random(8) = 0 then
3517 ActionSound();
3518 end;
3520 // Бежим в выбранную сторону:
3521 if FDirection = TDirection.D_RIGHT then
3522 FObj.Vel.X := MONSTERTABLE[FMonsterType].RunVel
3523 else
3524 FObj.Vel.X := -MONSTERTABLE[FMonsterType].RunVel;
3526 // Если в воде, то замедляемся:
3527 if WordBool(st and MOVE_INWATER) then
3528 FObj.Vel.X := FObj.Vel.X div 2
3529 else // Рыбам не нужно замедляться
3530 if FMonsterType = MONSTER_FISH then
3531 FObj.Vel.X := 0;
3532 end;
3534 MONSTATE_RUN: // Состояние - Бег
3535 begin
3536 // Если наткнулись на БлокМон - убегаем от него:
3537 if WordBool(st and MOVE_BLOCK) then
3538 begin
3539 SetState(MONSTATE_RUNOUT);
3540 FSleep := 40;
3542 goto _end;
3543 end;
3545 FSleep := FSleep - 1;
3547 // Пробежали достаточно или врезались в стену => переходим на шаг:
3548 if (FSleep <= 0) or (WordBool(st and MOVE_HITWALL) and ((FObj.Vel.Y+FObj.Accel.Y) = 0)) then
3549 begin
3550 SetState(MONSTATE_GO);
3551 FSleep := 0;
3553 // Иногда рычим:
3554 if Random(8) = 0 then
3555 ActionSound();
3556 end;
3558 // Бежим в выбранную сторону:
3559 if FDirection = TDirection.D_RIGHT then
3560 FObj.Vel.X := MONSTERTABLE[FMonsterType].RunVel
3561 else
3562 FObj.Vel.X := -MONSTERTABLE[FMonsterType].RunVel;
3564 // Если в воде, то замедляемся:
3565 if WordBool(st and MOVE_INWATER) then
3566 FObj.Vel.X := FObj.Vel.X div 2
3567 else // Рыбам не нужно замедляться
3568 if FMonsterType = MONSTER_FISH then
3569 FObj.Vel.X := 0;
3570 end;
3572 MONSTATE_RUNOUT: // Состояние - Убегает от чего-то
3573 begin
3574 // Вышли из БлокМона:
3575 if (not WordBool(st and MOVE_BLOCK)) and (FSleep > 0) then
3576 FSleep := 0;
3578 FSleep := FSleep - 1;
3580 // Убажели достаточно далеко => переходим на шаг:
3581 if FSleep <= -18 then
3582 begin
3583 SetState(MONSTATE_GO);
3584 FSleep := 0;
3586 // Иногда рычим:
3587 if Random(8) = 0 then
3588 ActionSound();
3589 end;
3591 // Бежим в выбранную сторону:
3592 if FDirection = TDirection.D_RIGHT then
3593 FObj.Vel.X := MONSTERTABLE[FMonsterType].RunVel
3594 else
3595 FObj.Vel.X := -MONSTERTABLE[FMonsterType].RunVel;
3597 // Если в воде, то замедляемся:
3598 if WordBool(st and MOVE_INWATER) then
3599 FObj.Vel.X := FObj.Vel.X div 2
3600 else // Рыбам не нужно замедляться
3601 if FMonsterType = MONSTER_FISH then
3602 FObj.Vel.X := 0;
3603 end;
3605 MONSTATE_CLIMB: // Состояние - Прыжок (чтобы обойти стену)
3606 begin
3607 // Достигли высшей точки прыжка или стена кончилась => переходим на шаг:
3608 if ((FObj.Vel.Y+FObj.Accel.Y) >= 0) or
3609 (not WordBool(st and MOVE_HITWALL)) then
3610 begin
3611 SetState(MONSTATE_GO);
3612 FSleep := 0;
3614 // Стена не кончилась => бежим от нее:
3615 if WordBool(st and (MOVE_HITWALL or MOVE_BLOCK)) then
3616 begin
3617 SetState(MONSTATE_RUN);
3618 FSleep := 15;
3619 end;
3620 end;
3622 // Бежим в выбранную сторону:
3623 if FDirection = TDirection.D_RIGHT then
3624 FObj.Vel.X := MONSTERTABLE[FMonsterType].RunVel
3625 else
3626 FObj.Vel.X := -MONSTERTABLE[FMonsterType].RunVel;
3628 // Если в воде, то замедляемся:
3629 if WordBool(st and MOVE_INWATER) then
3630 FObj.Vel.X := FObj.Vel.X div 2
3631 else // Рыбам не нужно замедляться
3632 if FMonsterType = MONSTER_FISH then
3633 FObj.Vel.X := 0;
3634 end;
3636 MONSTATE_ATTACK, // Состояние - Атака
3637 MONSTATE_SHOOT: // Состояние - Стрельба
3638 begin
3639 // Lost_Soul врезался в стену при атаке => переходит на шаг:
3640 if FMonsterType = MONSTER_SOUL then
3641 begin
3642 if WordBool(st and (MOVE_HITWALL or MOVE_HITCEIL or MOVE_HITLAND)) then
3643 SetState(MONSTATE_GO);
3645 goto _end;
3646 end;
3648 // Замедляемся при атаке:
3649 if FMonsterType <> MONSTER_FISH then
3650 FObj.Vel.X := z_dec(FObj.Vel.X, 1);
3652 // Нужно стрелять, а монстр - колдун:
3653 if (FMonsterType = MONSTER_VILE) and (FState = MONSTATE_SHOOT) then
3654 begin
3655 // Цель погибла => идем дальше:
3656 if not GetPos(FTargetUID, @o) then
3657 begin
3658 SetState(MONSTATE_GO);
3660 goto _end;
3661 end;
3663 // Цель не видно => идем дальше:
3664 if not g_Look(@FObj, @o, FDirection) then
3665 begin
3666 SetState(MONSTATE_GO);
3668 goto _end;
3669 end;
3671 // Цель в воде - не загорится => идем дальше:
3672 if g_Obj_CollideWater(@o, 0, 0) then
3673 begin
3674 SetState(MONSTATE_GO);
3676 goto _end;
3677 end;
3678 end;
3679 end;
3680 end; // case FState of ...
3682 _end:
3684 // Состояние - Воскрешение:
3685 if FState = MONSTATE_REVIVE then
3686 if FAnim[FCurAnim, FDirection].Played then
3687 begin // Обратная анимация умирания закончилась - идем дальше:
3688 FAnim[FCurAnim, FDirection].Revert(False);
3689 SetState(MONSTATE_GO);
3690 end;
3692 // Если есть анимация огня колдуна - пусть она идет:
3693 if vilefire <> nil then
3694 vilefire.Update();
3696 // Состояние - Умирает и текущая анимация проиграна:
3697 if (FState = MONSTATE_DIE) and
3698 (FAnim[FCurAnim, FDirection] <> nil) and
3699 (FAnim[FCurAnim, FDirection].Played) then
3700 begin
3701 // Умер:
3702 SetState(MONSTATE_DEAD);
3704 // У этих монстров нет трупов:
3705 if (FMonsterType = MONSTER_PAIN) or
3706 (FMonsterType = MONSTER_SOUL) or
3707 (FMonsterType = MONSTER_BARREL) then
3708 FRemoved := True
3709 else
3710 FAnim[FCurAnim, FDirection].CurrentFrame := FAnim[FCurAnim, FDirection].TotalFrames - 1;
3711 end;
3713 // Совершение атаки и стрельбы:
3714 if (FState = MONSTATE_ATTACK) or (FState = MONSTATE_SHOOT) then
3715 if (FAnim[FCurAnim, FDirection] <> nil) then
3716 // Анимация атаки есть - можно атаковать
3717 if (FAnim[FCurAnim, FDirection].Played) then
3718 begin // Анимация атаки закончилась => переходим на шаг
3719 if FState = MONSTATE_ATTACK then
3720 begin // Состояние - Атака
3721 // Если монстр не Lost_Soul, то после атаки переходим на шаг:
3722 if FMonsterType <> MONSTER_SOUL then
3723 SetState(MONSTATE_GO);
3724 end
3725 else // Состояние - Стрельба
3726 begin
3727 // Переходим на шаг, если не надо стрелять еще раз:
3728 if not FChainFire then
3729 SetState(MONSTATE_GO)
3730 else
3731 begin // Надо стрелять еще
3732 FChainFire := False;
3733 // Т.к. состояние не изменилось, и нужен
3734 // новый цикл ожидания завершения анимации:
3735 FAnim[FCurAnim, FDirection].Reset();
3736 end;
3737 end;
3739 FWaitAttackAnim := False;
3740 end
3742 else // Анимация атаки еще идет (исключение - Lost_Soul):
3743 if (FMonsterType = MONSTER_SOUL) or
3744 ( (not FWaitAttackAnim) and
3745 (FAnim[FCurAnim, FDirection].CurrentFrame =
3746 (FAnim[FCurAnim, FDirection].TotalFrames div 2))
3747 ) then
3748 begin // Атаки еще не было и это середина анимации атаки
3749 if FState = MONSTATE_ATTACK then
3750 begin // Состояние - Атака
3751 // Если это Lost_Soul, то сбрасываем анимацию атаки:
3752 if FMonsterType = MONSTER_SOUL then
3753 FAnim[FCurAnim, FDirection].Reset();
3755 case FMonsterType of
3756 MONSTER_SOUL, MONSTER_IMP, MONSTER_DEMON:
3757 // Грызем первого попавшегося:
3758 if g_Weapon_Hit(@FObj, 15, FUID, HIT_SOME) <> 0 then
3759 // Lost_Soul укусил кого-то => переходит на шаг:
3760 if FMonsterType = MONSTER_SOUL then
3761 SetState(MONSTATE_GO);
3763 MONSTER_FISH:
3764 g_Weapon_Hit(@FObj, 10, FUID, HIT_SOME);
3766 MONSTER_SKEL, MONSTER_ROBO, MONSTER_CYBER:
3767 // Робот, кибер или скелет сильно пинаются:
3768 if FCurAnim = ANIM_ATTACK2 then
3769 begin
3770 o := FObj;
3771 o.Vel.X := IfThen(FDirection = TDirection.D_RIGHT, 1, -1)*IfThen(FMonsterType = MONSTER_CYBER, 60, 50);
3772 g_Weapon_Hit(@o, IfThen(FMonsterType = MONSTER_CYBER, 33, 50), FUID, HIT_SOME);
3773 end;
3775 MONSTER_VILE:
3776 // Колдун пытается воскрешать:
3777 if FCurAnim = ANIM_ATTACK2 then
3778 begin
3779 sx := isCorpse(@FObj, True);
3780 if sx <> -1 then
3781 begin // Нашли, кого воскресить
3782 g_Sound_PlayExAt('SOUND_MONSTER_SLOP', FObj.X, FObj.Y);
3783 // Воскрешать - себе вредить:
3784 {g_Weapon_HitUID(FUID, 5, 0, HIT_SOME);}
3785 end;
3786 end;
3787 end;
3788 end
3790 else // Состояние - Стрельба
3791 begin
3792 // Скорострельные монстры:
3793 if (FMonsterType = MONSTER_CGUN) or
3794 (FMonsterType = MONSTER_SPIDER) or
3795 (FMonsterType = MONSTER_BSP) or
3796 (FMonsterType = MONSTER_MANCUB) or
3797 (FMonsterType = MONSTER_ROBO) then
3798 if not GetPos(FTargetUID, @o) then
3799 // Цель мертва - ищем новую:
3800 findNewPrey()
3801 else // Цель жива - продолжаем стрелять:
3802 if shoot(@o, False) then
3803 FChainFire := True;
3804 end;
3806 // Атака только 1 раз за анимацию атаки:
3807 FWaitAttackAnim := True;
3808 end;
3810 // Последний кадр текущей анимации:
3811 if FAnim[FCurAnim, FDirection].Counter = FAnim[FCurAnim, FDirection].Speed-1 then
3812 case FState of
3813 MONSTATE_GO, MONSTATE_RUN, MONSTATE_CLIMB, MONSTATE_RUNOUT:
3814 // Звуки при передвижении:
3815 case FMonsterType of
3816 MONSTER_CYBER:
3817 if (FAnim[FCurAnim, FDirection].CurrentFrame = 0) or
3818 (FAnim[FCurAnim, FDirection].CurrentFrame = 2) then
3819 g_Sound_PlayExAt('SOUND_MONSTER_CYBER_WALK', FObj.X, FObj.Y);
3820 MONSTER_SPIDER:
3821 if (FAnim[FCurAnim, FDirection].CurrentFrame = 0) or
3822 (FAnim[FCurAnim, FDirection].CurrentFrame = 2) then
3823 g_Sound_PlayExAt('SOUND_MONSTER_SPIDER_WALK', FObj.X, FObj.Y);
3824 MONSTER_BSP:
3825 if (FAnim[FCurAnim, FDirection].CurrentFrame = 0) or
3826 (FAnim[FCurAnim, FDirection].CurrentFrame = 2) then
3827 g_Sound_PlayExAt('SOUND_MONSTER_BSP_WALK', FObj.X, FObj.Y);
3828 MONSTER_ROBO:
3829 if (FAnim[FCurAnim, FDirection].CurrentFrame = 0) or
3830 (FAnim[FCurAnim, FDirection].CurrentFrame = 5) then
3831 g_Sound_PlayExAt('SOUND_MONSTER_BSP_WALK', FObj.X, FObj.Y);
3832 end;
3833 end;
3835 // Костыль для потоков
3836 if g_Obj_CollidePanel(@FObj, 0, 0, PANEL_LIFTLEFT or PANEL_LIFTRIGHT) and
3837 not ((FState = MONSTATE_DEAD) or (FState = MONSTATE_DIE)) then
3838 FObj.Vel.X := oldvelx;
3840 // Если есть анимация, то пусть она идет:
3841 if FAnim[FCurAnim, FDirection] <> nil then
3842 FAnim[FCurAnim, FDirection].Update();
3843 end;
3845 procedure TMonster.ClientAttack(wx, wy, atx, aty: Integer);
3846 begin
3847 case FMonsterType of
3848 MONSTER_ZOMBY:
3849 begin
3850 g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', wx, wy);
3851 {$IFDEF ENABLE_SHELLS}
3852 g_Shells_Create(wx, wy, 0, -2, SHELL_BULLET);
3853 {$ENDIF}
3854 end;
3855 MONSTER_SERG:
3856 begin
3857 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', wx, wy);
3858 {$IFDEF ENABLE_SHELLS}
3859 FShellTimer := 10;
3860 FShellType := SHELL_SHELL;
3861 {$ENDIF}
3862 end;
3863 MONSTER_MAN:
3864 begin
3865 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN2', wx, wy);
3866 {$IFDEF ENABLE_SHELLS}
3867 FShellTimer := 13;
3868 FShellType := SHELL_DBLSHELL;
3869 {$ENDIF}
3870 end;
3871 MONSTER_CGUN, MONSTER_SPIDER:
3872 begin
3873 g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', wx, wy);
3874 {$IFDEF ENABLE_SHELLS}
3875 g_Shells_Create(wx, wy, 0, -2, SHELL_BULLET);
3876 {$ENDIF}
3877 end;
3878 MONSTER_IMP:
3879 g_Weapon_ball1(wx, wy, atx, aty, FUID);
3880 MONSTER_CYBER:
3881 g_Weapon_rocket(wx, wy, atx, aty, FUID);
3882 MONSTER_SKEL:
3883 g_Weapon_revf(wx, wy, atx, aty, FUID, FTargetUID);
3884 MONSTER_BSP:
3885 g_Weapon_aplasma(wx, wy, atx, aty, FUID);
3886 MONSTER_ROBO:
3887 g_Weapon_plasma(wx, wy, atx, aty, FUID);
3888 MONSTER_MANCUB:
3889 g_Weapon_manfire(wx, wy, atx, aty, FUID);
3890 MONSTER_BARON, MONSTER_KNIGHT:
3891 g_Weapon_ball7(wx, wy, atx, aty, FUID);
3892 MONSTER_CACO:
3893 g_Weapon_ball2(wx, wy, atx, aty, FUID);
3894 end;
3895 end;
3897 procedure TMonster.Turn();
3898 begin
3899 // Разворачиваемся:
3900 if FDirection = TDirection.D_LEFT then FDirection := TDirection.D_RIGHT else FDirection := TDirection.D_LEFT;
3902 // Бежим в выбранную сторону:
3903 if FDirection = TDirection.D_RIGHT then
3904 FObj.Vel.X := MONSTERTABLE[FMonsterType].RunVel
3905 else
3906 FObj.Vel.X := -MONSTERTABLE[FMonsterType].RunVel;
3907 end;
3909 function TMonster.findNewPrey(): Boolean;
3910 var
3911 a: DWORD;
3912 l, l2: Integer;
3913 PlayersSee, MonstersSee: Array of DWORD;
3914 PlayerNear, MonsterNear: Integer;
3915 begin
3916 Result := False;
3917 SetLength(MonstersSee, 0);
3918 SetLength(PlayersSee, 0);
3920 FTargetUID := 0;
3921 l := 32000;
3922 PlayerNear := -1;
3923 MonsterNear := -1;
3925 // Поехавшие, каннибалы, и добрые игроков не трогают
3926 if (gPlayers <> nil) and (FBehaviour <> BH_INSANE) and
3927 (FBehaviour <> BH_CANNIBAL) and (FBehaviour <> BH_GOOD) then
3928 for a := 0 to High(gPlayers) do
3929 if (gPlayers[a] <> nil) and (gPlayers[a].alive)
3930 and (not gPlayers[a].NoTarget) and (gPlayers[a].FMegaRulez[MR_INVIS] < gTime) then
3931 begin
3932 if g_Look(@FObj, @gPlayers[a].Obj, FDirection) then
3933 begin
3934 SetLength(PlayersSee, Length(PlayersSee) + 1);
3935 PlayersSee[High(PlayersSee)] := a;
3936 end;
3937 l2 := Abs(gPlayers[a].GameX-FObj.X)+
3938 Abs(gPlayers[a].GameY-FObj.Y);
3939 if l2 < l then
3940 begin
3941 l := l2;
3942 PlayerNear := Integer(a);
3943 end;
3944 end;
3946 // Киллеры и добрые не трогают монстров
3947 if (gMonsters <> nil) and (FBehaviour <> BH_KILLER) and (FBehaviour <> BH_GOOD) then
3948 for a := 0 to High(gMonsters) do
3949 if (gMonsters[a] <> nil) and (gMonsters[a].alive) and
3950 (gMonsters[a].FUID <> FUID) then
3951 begin
3952 if (FBehaviour = BH_CANNIBAL) and (gMonsters[a].FMonsterType <> FMonsterType) then
3953 Continue; // Каннибалы атакуют только сородичей
3954 if (FBehaviour = BH_INSANE) and (gMonsters[a].FMonsterType <> MONSTER_BARREL)
3955 and (IsFriend(gMonsters[a].FMonsterType, FMonsterType)) then
3956 Continue; // Поехавшие не трогают друзей, но им не нравятся бочки
3957 if ((FBehaviour = BH_NORMAL) or (FBehaviour = BH_MANIAC))
3958 and (IsFriend(gMonsters[a].FMonsterType, FMonsterType)) then
3959 Continue; // Оставшиеся типы, кроме каннибалов, не трогают своих друзей
3961 if g_Look(@FObj, @gMonsters[a].Obj, FDirection) then
3962 begin
3963 SetLength(MonstersSee, Length(MonstersSee) + 1);
3964 MonstersSee[High(MonstersSee)] := a;
3965 end;
3966 l2 := Abs(gMonsters[a].FObj.X-FObj.X)+
3967 Abs(gMonsters[a].FObj.Y-FObj.Y);
3968 if l2 < l then
3969 begin
3970 l := l2;
3971 MonsterNear := Integer(a);
3972 end;
3973 end;
3975 case FBehaviour of
3976 BH_NORMAL, BH_KILLER:
3977 begin
3978 // Обычный и киллер сначала ищут игроков в поле зрения
3979 if (FTargetUID = 0) and (Length(PlayersSee) > 0) then
3980 begin
3981 a := PlayersSee[Random(Length(PlayersSee))];
3982 FTargetUID := gPlayers[a].UID;
3983 end;
3984 // Затем поблизости
3985 if (FTargetUID = 0) and (PlayerNear > -1) then
3986 begin
3987 a := PlayerNear;
3988 FTargetUID := gPlayers[a].UID;
3989 end;
3990 // Потом обычные ищут монстров в поле зрения
3991 if (FTargetUID = 0) and (Length(MonstersSee) > 0) then
3992 begin
3993 a := MonstersSee[Random(Length(MonstersSee))];
3994 FTargetUID := gMonsters[a].UID;
3995 end;
3996 // Затем поблизости
3997 if (FTargetUID = 0) and (MonsterNear > -1) then
3998 begin
3999 a := MonsterNear;
4000 FTargetUID := gMonsters[a].UID;
4001 end;
4002 end;
4003 BH_MANIAC, BH_INSANE, BH_CANNIBAL:
4004 begin
4005 // Маньяки, поехавшие и каннибалы сначала истребляют всё в поле зрения
4006 if (FTargetUID = 0) and (Length(PlayersSee) > 0) then
4007 begin
4008 a := PlayersSee[Random(Length(PlayersSee))];
4009 FTargetUID := gPlayers[a].UID;
4010 end;
4011 if (FTargetUID = 0) and (Length(MonstersSee) > 0) then
4012 begin
4013 a := MonstersSee[Random(Length(MonstersSee))];
4014 FTargetUID := gMonsters[a].UID;
4015 end;
4016 // Затем ищут кого-то поблизости
4017 if (FTargetUID = 0) and (PlayerNear > -1) then
4018 begin
4019 a := PlayerNear;
4020 FTargetUID := gPlayers[a].UID;
4021 end;
4022 if (FTargetUID = 0) and (MonsterNear > -1) then
4023 begin
4024 a := MonsterNear;
4025 FTargetUID := gMonsters[a].UID;
4026 end;
4027 end;
4028 end;
4030 // Если и монстров нет - начинаем ждать цель:
4031 if FTargetUID = 0 then
4032 begin
4033 // Поехавший пытается самоубиться
4034 if FBehaviour = BH_INSANE then
4035 FTargetUID := FUID
4036 else
4037 FTargetTime := MAX_ATM;
4038 end
4039 else
4040 begin // Цель нашли
4041 FTargetTime := 0;
4042 Result := True;
4043 end;
4044 end;
4046 function TMonster.kick(o: PObj): Boolean;
4047 begin
4048 Result := False;
4050 case FMonsterType of
4051 MONSTER_FISH:
4052 begin
4053 SetState(MONSTATE_ATTACK);
4054 Result := True;
4055 end;
4056 MONSTER_DEMON:
4057 begin
4058 SetState(MONSTATE_ATTACK);
4059 g_Sound_PlayExAt('SOUND_MONSTER_DEMON_ATTACK', FObj.X, FObj.Y);
4060 Result := True;
4061 end;
4062 MONSTER_IMP:
4063 begin
4064 SetState(MONSTATE_ATTACK);
4065 g_Sound_PlayExAt('SOUND_MONSTER_IMP_ATTACK', FObj.X, FObj.Y);
4066 Result := True;
4067 end;
4068 MONSTER_SKEL, MONSTER_ROBO, MONSTER_CYBER:
4069 begin
4070 SetState(MONSTATE_ATTACK, ANIM_ATTACK2);
4071 g_Sound_PlayExAt('SOUND_MONSTER_SKEL_ATTACK', FObj.X, FObj.Y);
4072 Result := True;
4073 end;
4074 MONSTER_BARON, MONSTER_KNIGHT,
4075 MONSTER_CACO, MONSTER_MANCUB:
4076 // Эти монстры не пинают - они стреляют в упор:
4077 if not g_Game_IsClient then Result := shoot(o, True);
4078 end;
4079 end;
4081 function TMonster.shoot(o: PObj; immediately: Boolean): Boolean;
4082 var
4083 xd, yd, m: Integer;
4084 begin
4085 Result := False;
4087 // Стрелять рано:
4088 if FAmmo < 0 then
4089 Exit;
4091 // Ждать времени готовности к выстрелу:
4092 if not immediately then
4093 case FMonsterType of
4094 MONSTER_FISH, MONSTER_BARREL, MONSTER_DEMON:
4095 Exit; // не стреляют
4096 MONSTER_CGUN, MONSTER_BSP, MONSTER_ROBO:
4097 begin
4098 FAmmo := FAmmo + 1;
4099 // Время выстрела упущено:
4100 if FAmmo >= 50 then
4101 FAmmo := IfThen(FMonsterType = MONSTER_ROBO, -200, -50);
4102 end;
4103 MONSTER_MAN: ;
4104 MONSTER_MANCUB:
4105 begin
4106 FAmmo := FAmmo + 1;
4107 // Время выстрела упущено:
4108 if FAmmo >= 5 then
4109 FAmmo := -50;
4110 end;
4111 MONSTER_SPIDER:
4112 begin
4113 FAmmo := FAmmo + 1;
4114 // Время выстрела упущено:
4115 if FAmmo >= 100 then
4116 FAmmo := -50;
4117 end;
4118 MONSTER_CYBER:
4119 begin
4120 // Стреляет не всегда:
4121 if Random(2) = 0 then
4122 Exit;
4123 FAmmo := FAmmo + 1;
4124 // Время выстрела упущено:
4125 if FAmmo >= 10 then
4126 FAmmo := -50;
4127 end;
4128 MONSTER_BARON, MONSTER_KNIGHT: if Random(8) <> 0 then Exit;
4129 MONSTER_SKEL: if Random(32) <> 0 then Exit;
4130 MONSTER_VILE: if Random(8) <> 0 then Exit;
4131 MONSTER_PAIN: if Random(8) <> 0 then Exit;
4132 else if Random(16) <> 0 then Exit;
4133 end;
4135 // Цели не видно:
4136 if not g_Look(@FObj, o, FDirection) then
4137 Exit;
4139 FTargetTime := 0;
4141 tx := o^.X+o^.Rect.X+(o^.Rect.Width div 2)+((o^.Vel.X{+o^.Accel.X})*12);
4142 ty := o^.Y+o^.Rect.Y+(o^.Rect.Height div 2)+((o^.Vel.Y{+o^.Accel.Y})*12);
4144 // Разница по высоте больше разницы по горизонтали
4145 // (не может стрелять под таким большим углом):
4146 if Abs(tx-(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2))) <
4147 Abs(ty-(FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2))) then
4148 Exit;
4150 case FMonsterType of
4151 MONSTER_IMP, MONSTER_BARON, MONSTER_KNIGHT, MONSTER_CACO:
4152 begin
4153 SetState(MONSTATE_SHOOT);
4154 {nn}
4155 end;
4156 MONSTER_SKEL:
4157 begin
4158 SetState(MONSTATE_SHOOT);
4159 {nn}
4160 end;
4161 MONSTER_VILE:
4162 begin // Зажигаем огонь
4163 tx := o^.X+o^.Rect.X+(o^.Rect.Width div 2);
4164 ty := o^.Y+o^.Rect.Y;
4165 SetState(MONSTATE_SHOOT);
4167 vilefire.Reset();
4169 g_Sound_PlayExAt('SOUND_MONSTER_VILE_ATTACK', FObj.X, FObj.Y);
4170 g_Sound_PlayExAt('SOUND_FIRE', o^.X, o^.Y);
4171 end;
4172 MONSTER_SOUL:
4173 begin // Летит в сторону цели:
4174 SetState(MONSTATE_ATTACK);
4175 g_Sound_PlayExAt('SOUND_MONSTER_SOUL_ATTACK', FObj.X, FObj.Y);
4177 xd := tx-(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2));
4178 yd := ty-(FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2));
4179 m := Max(Abs(xd), Abs(yd));
4180 if m = 0 then
4181 m := 1;
4183 FObj.Vel.X := (xd*16) div m;
4184 FObj.Vel.Y := (yd*16) div m;
4185 end;
4186 MONSTER_MANCUB, MONSTER_ZOMBY, MONSTER_SERG, MONSTER_BSP, MONSTER_ROBO,
4187 MONSTER_CYBER, MONSTER_CGUN, MONSTER_SPIDER, MONSTER_PAIN, MONSTER_MAN:
4188 begin
4189 // Манкубус рявкает перед первой атакой:
4190 if FMonsterType = MONSTER_MANCUB then
4191 if FAmmo = 1 then
4192 g_Sound_PlayExAt('SOUND_MONSTER_MANCUB_ATTACK', FObj.X, FObj.Y);
4194 SetState(MONSTATE_SHOOT);
4195 end;
4196 else Exit;
4197 end;
4199 Result := True;
4200 end;
4202 function TMonster.alive(): Boolean;
4203 begin
4204 Result := (FState <> MONSTATE_DIE) and (FState <> MONSTATE_DEAD) and (FHealth > 0);
4205 end;
4207 procedure TMonster.SetHealth(aH: Integer);
4208 begin
4209 if (aH > 0) and (aH < 1000000) then
4210 begin
4211 FHealth := aH;
4212 if FHealth > FMaxHealth then
4213 FMaxHealth := FHealth;
4214 end;
4215 end;
4217 procedure TMonster.WakeUp();
4218 begin
4219 if g_Game_IsClient then Exit;
4220 SetState(MONSTATE_GO);
4221 FTargetTime := MAX_ATM;
4222 WakeUpSound();
4223 end;
4225 procedure TMonster.SaveState (st: TStream);
4226 var
4227 i: Integer;
4228 b: Byte;
4229 anim: Boolean;
4230 begin
4231 assert(st <> nil);
4233 // Сигнатура монстра:
4234 utils.writeSign(st, 'MONS');
4235 utils.writeInt(st, Byte(0)); // version
4236 // UID монстра:
4237 utils.writeInt(st, Word(FUID));
4238 // Направление
4239 if FDirection = TDirection.D_LEFT then b := 1 else b := 2; // D_RIGHT
4240 utils.writeInt(st, Byte(b));
4241 // Надо ли удалить его
4242 utils.writeBool(st, FRemoved);
4243 // Осталось здоровья
4244 utils.writeInt(st, LongInt(FHealth));
4245 // Состояние
4246 utils.writeInt(st, Byte(FState));
4247 // Текущая анимация
4248 utils.writeInt(st, Byte(FCurAnim));
4249 // UID цели
4250 utils.writeInt(st, Word(FTargetUID));
4251 // Время после потери цели
4252 utils.writeInt(st, LongInt(FTargetTime));
4253 // Поведение монстра
4254 utils.writeInt(st, Byte(FBehaviour));
4255 // Готовность к выстрелу
4256 utils.writeInt(st, LongInt(FAmmo));
4257 // Боль
4258 utils.writeInt(st, LongInt(FPain));
4259 // Время ожидания
4260 utils.writeInt(st, LongInt(FSleep));
4261 // Озвучивать ли боль
4262 utils.writeBool(st, FPainSound);
4263 // Была ли атака во время анимации атаки
4264 utils.writeBool(st, FWaitAttackAnim);
4265 // Надо ли стрелять на следующем шаге
4266 utils.writeBool(st, FChainFire);
4267 // Подлежит ли респавну
4268 utils.writeBool(st, FNoRespawn);
4269 // Координаты цели
4270 utils.writeInt(st, LongInt(tx));
4271 utils.writeInt(st, LongInt(ty));
4272 // ID монстра при старте карты
4273 utils.writeInt(st, LongInt(FStartID));
4274 // Индекс триггера, создавшего монстра
4275 utils.writeInt(st, LongInt(FSpawnTrigger));
4276 // Объект монстра
4277 Obj_SaveState(st, @FObj);
4278 // Есть ли анимация огня колдуна
4279 anim := (vilefire <> nil);
4280 utils.writeBool(st, anim);
4281 // Если есть - сохраняем:
4282 if anim then vilefire.SaveState(st, 0, False);
4283 // Анимации
4284 for i := ANIM_SLEEP to ANIM_PAIN do
4285 begin
4286 // Есть ли левая анимация
4287 anim := (FAnim[i, TDirection.D_LEFT] <> nil);
4288 utils.writeBool(st, anim);
4289 // Если есть - сохраняем
4290 if anim then FAnim[i, TDirection.D_LEFT].SaveState(st, 0, False);
4291 // Есть ли правая анимация
4292 anim := (FAnim[i, TDirection.D_RIGHT] <> nil);
4293 utils.writeBool(st, anim);
4294 // Если есть - сохраняем
4295 if anim then FAnim[i, TDirection.D_RIGHT].SaveState(st, 0, False);
4296 end;
4297 end;
4300 procedure TMonster.LoadState (st: TStream);
4301 var
4302 i: Integer;
4303 b, alpha: Byte;
4304 anim, blending: Boolean;
4305 begin
4306 assert(st <> nil);
4308 // Сигнатура монстра:
4309 if not utils.checkSign(st, 'MONS') then raise XStreamError.Create('invalid monster signature');
4310 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid monster version');
4311 if (uidMap[FUID] <> nil) and (uidMap[FUID] <> self) then raise Exception.Create('internal error in monster loader (0)');
4312 uidMap[FUID] := nil;
4313 // UID монстра:
4314 FUID := utils.readWord(st);
4315 //if (arrIdx = -1) then raise Exception.Create('internal error in monster loader');
4316 if (uidMap[FUID] <> nil) then raise Exception.Create('internal error in monster loader (1)');
4317 uidMap[FUID] := self;
4318 // Направление
4319 b := utils.readByte(st);
4320 if b = 1 then FDirection := TDirection.D_LEFT else FDirection := TDirection.D_RIGHT; // b = 2
4321 // Надо ли удалить его
4322 FRemoved := utils.readBool(st);
4323 // Осталось здоровья
4324 FHealth := utils.readLongInt(st);
4325 // Состояние
4326 FState := utils.readByte(st);
4327 // Текущая анимация
4328 FCurAnim := utils.readByte(st);
4329 // UID цели
4330 FTargetUID := utils.readWord(st);
4331 // Время после потери цели
4332 FTargetTime := utils.readLongInt(st);
4333 // Поведение монстра
4334 FBehaviour := utils.readByte(st);
4335 // Готовность к выстрелу
4336 FAmmo := utils.readLongInt(st);
4337 // Боль
4338 FPain := utils.readLongInt(st);
4339 // Время ожидания
4340 FSleep := utils.readLongInt(st);
4341 // Озвучивать ли боль
4342 FPainSound := utils.readBool(st);
4343 // Была ли атака во время анимации атаки
4344 FWaitAttackAnim := utils.readBool(st);
4345 // Надо ли стрелять на следующем шаге
4346 FChainFire := utils.readBool(st);
4347 // Подлежит ли респавну
4348 FNoRespawn := utils.readBool(st);
4349 // Координаты цели
4350 tx := utils.readLongInt(st);
4351 ty := utils.readLongInt(st);
4352 // ID монстра при старте карты
4353 FStartID := utils.readLongInt(st);
4354 // Индекс триггера, создавшего монстра
4355 FSpawnTrigger := utils.readLongInt(st);
4356 // Объект монстра
4357 Obj_LoadState(@FObj, st);
4358 // Есть ли анимация огня колдуна
4359 anim := utils.readBool(st);
4360 // Если есть - загружаем:
4361 if anim then
4362 begin
4363 Assert(vilefire <> nil, 'TMonster.LoadState: no vilefire anim');
4364 vilefire.LoadState(st, alpha, blending);
4365 end;
4366 // Анимации
4367 for i := ANIM_SLEEP to ANIM_PAIN do
4368 begin
4369 // Есть ли левая анимация
4370 anim := utils.readBool(st);
4371 // Если есть - загружаем
4372 if anim then
4373 begin
4374 Assert(FAnim[i, TDirection.D_LEFT] <> nil, 'TMonster.LoadState: no '+IntToStr(i)+'_left anim');
4375 FAnim[i, TDirection.D_LEFT].LoadState(st, alpha, blending);
4376 end;
4377 // Есть ли правая анимация
4378 anim := utils.readBool(st);
4379 // Если есть - загружаем
4380 if anim then
4381 begin
4382 Assert(FAnim[i, TDirection.D_RIGHT] <> nil, 'TMonster.LoadState: no '+IntToStr(i)+'_right anim');
4383 FAnim[i, TDirection.D_RIGHT].LoadState(st, alpha, blending);
4384 end;
4385 end;
4386 end;
4389 procedure TMonster.ActivateTriggers();
4390 var
4391 a: Integer;
4392 begin
4393 if FDieTriggers <> nil then
4394 for a := 0 to High(FDieTriggers) do
4395 g_Triggers_Press(FDieTriggers[a], ACTIVATE_MONSTERPRESS);
4396 if FSpawnTrigger > -1 then
4397 begin
4398 g_Triggers_DecreaseSpawner(FSpawnTrigger);
4399 FSpawnTrigger := -1;
4400 end;
4401 end;
4403 procedure TMonster.AddTrigger(t: Integer);
4404 begin
4405 SetLength(FDieTriggers, Length(FDieTriggers)+1);
4406 FDieTriggers[High(FDieTriggers)] := t;
4407 end;
4409 procedure TMonster.ClearTriggers();
4410 begin
4411 SetLength(FDieTriggers, 0);
4412 end;
4414 procedure TMonster.CatchFire(Attacker: Word; Timeout: Integer = MON_BURN_TIME);
4415 begin
4416 if FMonsterType in [MONSTER_SOUL, MONSTER_VILE] then
4417 exit; // арчи не горят, черепа уже горят
4418 if Timeout <= 0 then exit;
4419 if g_Obj_CollidePanel(@FObj, 0, 0, PANEL_WATER or PANEL_ACID1 or PANEL_ACID2) then
4420 exit; // не подгораем в воде на всякий случай
4421 if FFireTime <= 0 then
4422 g_Sound_PlayExAt('SOUND_IGNITE', FObj.X, FObj.Y);
4423 FFireTime := Timeout;
4424 FFireAttacker := Attacker;
4425 if g_Game_IsNet and g_Game_IsServer then MH_SEND_MonsterState(FUID);
4426 end;
4428 procedure TMonster.OnFireFlame(Times: DWORD = 1);
4429 {$IFDEF ENABLE_GFX}
4430 var i: DWORD; x, y: Integer;
4431 {$ENDIF}
4432 begin
4433 {$IFDEF ENABLE_GFX}
4434 if (Random(10) = 1) and (Times = 1) then
4435 Exit;
4436 for i := 1 to Times do
4437 begin
4438 x := Obj.X + Obj.Rect.X + Random(Obj.Rect.Width + Times * 2) - (R_GFX_FLAME_WIDTH div 2);
4439 y := Obj.Y + 8 + Random(8 + Times * 2) + IfThen(FState = MONSTATE_DEAD, 16, 0);
4440 g_GFX_QueueEffect(R_GFX_FLAME, x, y);
4441 end;
4442 {$ENDIF}
4443 end;
4446 // ////////////////////////////////////////////////////////////////////////// //
4447 // throws on invalid uid
4448 function g_Mons_ByIdx (uid: Integer): TMonster; inline;
4449 begin
4450 result := g_Mons_ByIdx_NC(uid);
4451 if (result = nil) then raise Exception.Create('g_Mons_ByIdx: invalid monster id');
4452 end;
4454 // can return null
4455 function g_Mons_ByIdx_NC (uid: Integer): TMonster; inline;
4456 begin
4457 if (uid < 0) or (uid > High(gMonsters)) then begin result := nil; exit; end;
4458 result := gMonsters[uid];
4459 end;
4461 function g_Mons_TotalCount (): Integer; inline;
4462 begin
4463 result := Length(gMonsters);
4464 end;
4467 function g_Mons_ForEach (cb: TEachMonsterCB): Boolean;
4468 var
4469 idx: Integer;
4470 mon: TMonster;
4471 begin
4472 result := false;
4473 if (gMonsters = nil) or not assigned(cb) then exit;
4474 for idx := 0 to High(gMonsters) do
4475 begin
4476 mon := gMonsters[idx];
4477 if (mon <> nil) then
4478 begin
4479 result := cb(mon);
4480 if result then exit;
4481 end;
4482 end;
4483 end;
4486 function g_Mons_ForEachAlive (cb: TEachMonsterCB): Boolean;
4487 var
4488 idx: Integer;
4489 mon: TMonster;
4490 begin
4491 result := false;
4492 if (gMonsters = nil) or not assigned(cb) then exit;
4493 for idx := 0 to High(gMonsters) do
4494 begin
4495 mon := gMonsters[idx];
4496 if (mon <> nil) and mon.alive then
4497 begin
4498 result := cb(mon);
4499 if result then exit;
4500 end;
4501 end;
4502 end;
4505 function g_Mons_IsAnyAliveAt (x, y: Integer; width, height: Integer): Boolean;
4506 (*
4507 function monsCollCheck (mon: TMonster; atag: Integer): Boolean;
4508 begin
4509 result := mon.alive;// and g_Obj_Collide(x, y, width, height, @mon.Obj));
4510 end;
4511 *)
4512 var
4513 idx: Integer;
4514 mon: TMonster;
4515 mit: PMonster;
4516 it: TMonsterGrid.Iter;
4517 begin
4518 result := false;
4519 if (width < 1) or (height < 1) then exit;
4520 if gmon_debug_use_sqaccel then
4521 begin
4522 //result := (monsGrid.forEachInAABB(x, y, width, height, monsCollCheck) <> nil);
4523 it := monsGrid.forEachInAABB(x, y, width, height);
4524 for mit in it do if (mit.alive) then begin result := true; break; end;
4525 it.release();
4526 end
4527 else
4528 begin
4529 for idx := 0 to High(gMonsters) do
4530 begin
4531 mon := gMonsters[idx];
4532 if (mon <> nil) and mon.alive then
4533 begin
4534 if g_Obj_Collide(x, y, width, height, @mon.Obj) then
4535 begin
4536 result := true;
4537 exit;
4538 end;
4539 end;
4540 end;
4541 end;
4542 end;
4545 function g_Mons_ForEachAt (x, y: Integer; width, height: Integer; cb: TEachMonsterCB): Boolean;
4546 (*
4547 function monsCollCheck (mon: TMonster; atag: Integer): Boolean;
4548 begin
4549 result := cb(mon);
4550 end;
4551 *)
4552 var
4553 idx: Integer;
4554 mon: TMonster;
4555 mit: PMonster;
4556 it: TMonsterGrid.Iter;
4557 begin
4558 result := false;
4559 if (width < 1) or (height < 1) then exit;
4560 if gmon_debug_use_sqaccel then
4561 begin
4562 //result := (monsGrid.forEachInAABB(x, y, width, height, monsCollCheck) <> nil);
4563 it := monsGrid.forEachInAABB(x, y, width, height);
4564 for mit in it do if (cb(mit^)) then begin result := true; break; end;
4565 it.release();
4566 end
4567 else
4568 begin
4569 for idx := 0 to High(gMonsters) do
4570 begin
4571 mon := gMonsters[idx];
4572 if (mon <> nil) and mon.alive then
4573 begin
4574 if g_Obj_Collide(x, y, width, height, @mon.Obj) then
4575 begin
4576 result := cb(mon);
4577 if result then exit;
4578 end;
4579 end;
4580 end;
4581 end;
4582 end;
4585 function g_Mons_ForEachAliveAt (x, y: Integer; width, height: Integer; cb: TEachMonsterCB): Boolean;
4586 (*
4587 function monsCollCheck (mon: TMonster; atag: Integer): Boolean;
4588 begin
4589 //result := false;
4590 //if mon.alive and g_Obj_Collide(x, y, width, height, @mon.Obj) then result := cb(mon);
4591 if mon.alive then result := cb(mon) else result := false;
4592 end;
4593 *)
4594 var
4595 idx: Integer;
4596 mon: TMonster;
4597 mit: PMonster;
4598 it: TMonsterGrid.Iter;
4599 begin
4600 result := false;
4601 if (width < 1) or (height < 1) then exit;
4602 if gmon_debug_use_sqaccel then
4603 begin
4605 if (width = 1) and (height = 1) then
4606 begin
4607 result := (monsGrid.forEachAtPoint(x, y, monsCollCheck) <> nil);
4608 end
4609 else
4610 begin
4611 result := (monsGrid.forEachInAABB(x, y, width, height, monsCollCheck) <> nil);
4612 end;
4614 it := monsGrid.forEachInAABB(x, y, width, height);
4615 for mit in it do
4616 begin
4617 if (mit^.alive) then
4618 begin
4619 if (cb(mit^)) then begin result := true; break; end;
4620 end;
4621 end;
4622 it.release();
4623 end
4624 else
4625 begin
4626 for idx := 0 to High(gMonsters) do
4627 begin
4628 mon := gMonsters[idx];
4629 if (mon <> nil) and mon.alive then
4630 begin
4631 if g_Obj_Collide(x, y, width, height, @mon.Obj) then
4632 begin
4633 result := cb(mon);
4634 if result then exit;
4635 end;
4636 end;
4637 end;
4638 end;
4639 end;
4642 end.