DEADSOFTWARE

718ff68a3ae6427474ac3faa0f3214cfe5453e15
[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, bubbles: Boolean;
2087 mon: TMonster;
2088 mit: PMonster;
2089 it: TMonsterGrid.Iter;
2090 label
2091 _end;
2092 begin
2093 fall := True;
2094 bubbles := True;
2096 // Монстр статичен пока идет warmup
2097 if (gLMSRespawn > LMS_RESPAWN_NONE) then exit;
2099 // Рыбы "летают" только в воде:
2100 if FMonsterType = MONSTER_FISH then
2101 if g_Obj_CollidePanel(@FObj, 0, 0, PANEL_WATER or PANEL_ACID1 or PANEL_ACID2) then
2102 if (FState <> MONSTATE_DIE) and (FState <> MONSTATE_DEAD) then
2103 fall := False;
2105 // Летающие монтсры:
2106 if ((FMonsterType = MONSTER_SOUL) or
2107 (FMonsterType = MONSTER_PAIN) or
2108 (FMonsterType = MONSTER_CACO)) and
2109 (FState <> MONSTATE_DIE) and
2110 (FState <> MONSTATE_DEAD) then
2111 fall := False;
2113 // Меняем скорость только по четным кадрам:
2114 if gTime mod (GAME_TICK*2) <> 0 then
2115 begin
2116 g_Obj_Move(@FObj, fall, True, True);
2117 positionChanged(); // this updates spatial accelerators
2118 Exit;
2119 end;
2121 if FPainTicks > 0 then
2122 Dec(FPainTicks)
2123 else
2124 FPainSound := False;
2126 // Двигаемся:
2127 st := g_Obj_Move(@FObj, fall, True, True);
2128 positionChanged(); // this updates spatial accelerators
2130 // Если горим - поджигаем других монстров, но не на 100 тиков каждый раз:
2131 if FFireTime > 0 then
2132 begin
2133 it := monsGrid.forEachInAABB(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, FObj.Rect.Width, FObj.Rect.Height);
2134 for mit in it do
2135 if mit.UID <> FUID then
2136 mit.CatchFire(FFireAttacker, FFireTime);
2137 end;
2139 // Вылетел за карту - удаляем и запускаем триггеры:
2140 if WordBool(st and MOVE_FALLOUT) or (FObj.X < -1000) or
2141 (FObj.X > gMapInfo.Width+1000) or (FObj.Y < -1000) then
2142 begin
2143 FRemoved := True;
2144 if alive and (gLMSRespawn = LMS_RESPAWN_NONE) then
2145 begin
2146 Inc(gCoopMonstersKilled);
2147 if g_Game_IsNet then
2148 MH_SEND_GameStats;
2149 end;
2150 ActivateTriggers();
2151 Exit;
2152 end;
2154 oldvelx := FObj.Vel.X;
2156 // Сопротивление воздуха для трупа:
2157 if (FState = MONSTATE_DIE) or (FState = MONSTATE_DEAD) then
2158 FObj.Vel.X := z_dec(FObj.Vel.X, 1);
2160 if FFireTime > 0 then
2161 begin
2162 if WordBool(st and MOVE_INWATER) then
2163 FFireTime := 0
2164 else
2165 begin
2166 OnFireFlame(1);
2167 FFireTime := FFireTime - 1;
2168 if (FState <> MONSTATE_DIE) and (FState <> MONSTATE_DEAD) then
2169 if FFirePainTime = 0 then
2170 begin
2171 Damage(5, FFireAttacker, 0, 0, HIT_FLAME);
2172 FFirePainTime := 18;
2173 end
2174 else
2175 FFirePainTime := FFirePainTime - 1;
2176 end;
2177 end;
2179 // Мертвый ничего не делает:
2180 if (FState = MONSTATE_DEAD) then
2181 goto _end;
2183 // AI монстров выключен:
2184 if g_debug_MonsterOff then
2185 begin
2186 FSleep := 1;
2187 if FState <> MONSTATE_SLEEP then
2188 SetState(MONSTATE_SLEEP);
2189 end;
2191 // Возможно, создаем пузырьки в воде:
2192 if WordBool(st and MOVE_INWATER) and (Random(32) = 0) then
2193 begin
2194 {$IFDEF ENABLE_GFX}
2195 case FMonsterType of
2196 MONSTER_FISH:
2197 if Random(4) <> 0 then bubbles := False else
2198 g_GFX_Bubbles(FObj.X+FObj.Rect.X + Random(FObj.Rect.Width),
2199 FObj.Y+FObj.Rect.Y + Random(4), 1, 0, 0);
2200 MONSTER_ROBO, MONSTER_BARREL:
2201 g_GFX_Bubbles(FObj.X+FObj.Rect.X + Random(FObj.Rect.Width),
2202 FObj.Y+FObj.Rect.Y + Random(4), 1, 0, 0);
2203 else
2204 g_GFX_Bubbles(FObj.X+FObj.Rect.X + Random(FObj.Rect.Width-4),
2205 FObj.Y+FObj.Rect.Y + Random(4), 5, 4, 4);
2206 end;
2207 {$ENDIF}
2208 if bubbles then if Random(2) = 0
2209 then g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', FObj.X, FObj.Y)
2210 else g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', FObj.X, FObj.Y);
2211 end;
2213 // Если прошел первый кадр анимации взрыва бочки, то взрыв:
2214 if FMonsterType = MONSTER_BARREL then
2215 begin
2216 if (FState = MONSTATE_DIE) and (FAnim[FCurAnim, FDirection].CurrentFrame = 1) and
2217 (FAnim[FCurAnim, FDirection].Counter = 0) then
2218 g_Weapon_Explode(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
2219 FObj.Y+FObj.Rect.Y+FObj.Rect.Height-16,
2220 60, FUID);
2221 end;
2223 // Lost_Soul вылетел из воды => ускоряется:
2224 if FMonsterType = MONSTER_SOUL then
2225 if WordBool(st and MOVE_HITAIR) then
2226 g_Obj_SetSpeed(@FObj, 16);
2228 if FAmmo < 0 then
2229 FAmmo := FAmmo + 1;
2231 // Если начали всплывать, то продолжаем:
2232 if FObj.Vel.Y < 0 then
2233 if WordBool(st and MOVE_INWATER) then
2234 FObj.Vel.Y := -4;
2236 // Таймер - ждем после потери цели:
2237 FTargetTime := FTargetTime + 1;
2239 {$IFDEF ENABLE_SHELLS}
2240 // Гильзы
2241 if FShellTimer > -1 then
2242 begin
2243 if FShellTimer = 0 then
2244 begin
2245 if FShellType = SHELL_SHELL then
2246 begin
2247 g_Shells_Create(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
2248 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2),
2249 GameVelX, GameVelY-2, SHELL_SHELL)
2250 end
2251 else if FShellType = SHELL_DBLSHELL then
2252 begin
2253 g_Shells_Create(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
2254 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2),
2255 GameVelX-1, GameVelY-2, SHELL_SHELL);
2256 g_Shells_Create(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
2257 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2),
2258 GameVelX+1, GameVelY-2, SHELL_SHELL);
2259 end;
2260 FShellTimer := -1;
2261 end
2262 else
2263 begin
2264 Dec(FShellTimer);
2265 end;
2266 end;
2267 {$ENDIF}
2269 // Пробуем увернуться от летящей пули:
2270 if fall then
2271 if (FState in [MONSTATE_GO, MONSTATE_RUN, MONSTATE_RUNOUT,
2272 MONSTATE_ATTACK, MONSTATE_SHOOT]) then
2273 if g_Weapon_Danger(FUID, FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y,
2274 FObj.Rect.Width, FObj.Rect.Height, 50) then
2275 if (g_Obj_CollideLevel(@FObj, 0, 1) or g_Obj_StayOnStep(@FObj)) and
2276 (FObj.Accel.Y = 0) then
2277 FObj.Vel.Y := -MONSTERTABLE[FMonsterType].Jump;
2279 case FState of
2280 MONSTATE_PAIN: // Состояние - Боль
2281 begin
2282 // Боль сильная => монстр кричит:
2283 if FPain >= MONSTERTABLE[FMonsterType].Pain then
2284 begin
2285 FPain := MONSTERTABLE[FMonsterType].Pain;
2286 if gSoundEffectsDF then PainSound();
2287 end;
2288 if (not gSoundEffectsDF) and (FPain >= MONSTERTABLE[FMonsterType].MinPain) then
2289 PainSound();
2291 // Снижаем боль со временем:
2292 FPain := FPain - 5;
2294 // Боль уже не ошутимая => идем дальше:
2295 if FPain <= MONSTERTABLE[FMonsterType].MinPain then
2296 begin
2297 FPain := 0;
2298 FAmmo := -9;
2299 SetState(MONSTATE_GO);
2300 end;
2301 end;
2303 MONSTATE_SLEEP: // Состояние - Сон
2304 begin
2305 // Спим:
2306 FSleep := FSleep + 1;
2308 // Проспали достаточно:
2309 if FSleep >= 18 then
2310 FSleep := 0
2311 else // еще спим
2312 goto _end;
2314 // На игроков идут только обычные монстры, киллеры и маньяки
2315 if (FBehaviour = BH_NORMAL) or (FBehaviour = BH_KILLER) or (FBehaviour = BH_MANIAC) then
2316 // Если есть игрок рядом, просыпаемся и идем к нему:
2317 if (gPlayers <> nil) then
2318 for a := 0 to High(gPlayers) do
2319 if (gPlayers[a] <> nil) and (gPlayers[a].alive)
2320 and (not gPlayers[a].NoTarget) and (gPlayers[a].FMegaRulez[MR_INVIS] < gTime) then
2321 with gPlayers[a] do
2322 if g_Look(@FObj, @Obj, FDirection) then
2323 begin
2324 FTargetUID := gPlayers[a].UID;
2325 FTargetTime := 0;
2326 WakeUpSound();
2327 SetState(MONSTATE_GO);
2328 Break;
2329 end;
2331 // На монстров тянет маньяков, поехавших и каннибалов
2332 if (FTargetUID = 0) and ((FBehaviour = BH_MANIAC)
2333 or (FBehaviour = BH_INSANE) or (FBehaviour = BH_CANNIBAL)) then
2334 // Если есть подходящий монстр рядом:
2335 if gMonsters <> nil then
2336 for a := 0 to High(gMonsters) do
2337 if (gMonsters[a] <> nil) and (gMonsters[a].alive) and
2338 (gMonsters[a].FUID <> FUID) then
2339 begin
2340 // Маньяки нападают на всех монстров, кроме друзей
2341 if (FBehaviour = BH_MANIAC) and
2342 (IsFriend(gMonsters[a].FMonsterType, FMonsterType)) then
2343 Continue;
2344 // Поехавшие также, но могут обозлиться на бочку
2345 if (FBehaviour = BH_INSANE) and (gMonsters[a].FMonsterType <> MONSTER_BARREL) and
2346 (IsFriend(gMonsters[a].FMonsterType, FMonsterType)) then
2347 Continue;
2348 // Каннибалы нападают на себе подобных
2349 if (FBehaviour = BH_CANNIBAL) and (gMonsters[a].FMonsterType <> FMonsterType) then
2350 Continue;
2351 if g_Look(@FObj, @gMonsters[a].Obj, FDirection) then
2352 begin
2353 FTargetUID := gMonsters[a].UID;
2354 FTargetTime := 0;
2355 WakeUpSound();
2356 SetState(MONSTATE_GO);
2357 Break;
2358 end;
2359 end;
2360 end;
2362 MONSTATE_WAIT: // Состояние - Ожидание
2363 begin
2364 // Ждем:
2365 FSleep := FSleep - 1;
2367 // Выждали достаточно - идем:
2368 if FSleep < 0 then
2369 SetState(MONSTATE_GO);
2370 end;
2372 MONSTATE_GO: // Состояние - Движение (с осмотром ситуации)
2373 begin
2374 // Если наткнулись на БлокМон - убегаем от него:
2375 if WordBool(st and MOVE_BLOCK) then
2376 begin
2377 Turn();
2378 FSleep := 40;
2379 SetState(MONSTATE_RUNOUT);
2381 goto _end;
2382 end;
2384 // Если монстр - колдун, то пробуем воскресить кого-нибудь:
2385 if (FMonsterType = MONSTER_VILE) then
2386 if isCorpse(@FObj, False) <> -1 then
2387 begin
2388 FObj.Vel.X := 0;
2389 SetState(MONSTATE_ATTACK, ANIM_ATTACK2);
2391 goto _end;
2392 end;
2394 // Цель погибла или давно ждем:
2395 if (not GetPos(FTargetUID, @o)) or (FTargetTime > MAX_ATM) then
2396 if not findNewPrey() then
2397 begin // Новых целей нет
2398 FTargetUID := 0;
2399 o.X := FObj.X+pt_x;
2400 o.Y := FObj.Y+pt_y;
2401 o.Vel.X := 0;
2402 o.Vel.Y := 0;
2403 o.Accel.X := 0;
2404 o.Accel.Y := 0;
2405 o.Rect := _Rect(0, 0, 0, 1);
2406 end
2407 else // Новая цель есть - берем ее координаты
2408 GetPos(FTargetUID, @o);
2410 // Цель очень близко - пинаем:
2411 if g_Obj_Collide(@FObj, @o) and (FTargetUID <> 0) then
2412 begin
2413 FTargetTime := 0;
2414 if (FMonsterType <> MONSTER_CYBER) or (Random(2) = 0) then
2415 begin
2416 if kick(@o) then
2417 goto _end;
2418 end;
2419 end;
2421 // Расстояние до цели:
2422 sx := o.X+o.Rect.X+(o.Rect.Width div 2)-(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2));
2423 sy := o.Y+o.Rect.Y+(o.Rect.Height div 2)-(FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2));
2425 // Поворачиваемся в сторону цели:
2426 if sx > 0 then
2427 FDirection := TDirection.D_RIGHT
2428 else
2429 FDirection := TDirection.D_LEFT;
2431 // Если монстр умеет стрелять и есть по кому - стреляем:
2432 if canShoot(FMonsterType) and (FTargetUID <> 0) then
2433 if Abs(sx) > Abs(sy) then // угол выстрела удобный
2434 if shoot(@o, False) then
2435 goto _end;
2437 // Если цель почти на одной вертикали - бегаем туда-сюда:
2438 if Abs(sx) < 40 then
2439 if FMonsterType <> MONSTER_FISH then
2440 begin
2441 FSleep := 15;
2442 SetState(MONSTATE_RUN);
2443 if Random(2) = 0 then
2444 FDirection := TDirection.D_LEFT
2445 else
2446 FDirection := TDirection.D_RIGHT;
2448 goto _end;
2449 end;
2451 // Уперлись в стену:
2452 if WordBool(st and MOVE_HITWALL) then
2453 begin
2454 if g_Triggers_PressR(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, FObj.Rect.Width,
2455 FObj.Rect.Height, FUID, ACTIVATE_MONSTERPRESS) <> nil then
2456 begin // Смогли нажать кнопку - небольшое ожидание
2457 FSleep := 4;
2458 SetState(MONSTATE_WAIT);
2460 goto _end;
2461 end;
2463 case FMonsterType of
2464 MONSTER_CACO, MONSTER_SOUL, MONSTER_PAIN, MONSTER_FISH: ;
2465 else // Не летают:
2466 if (g_Obj_CollideLevel(@FObj, 0, 1) or g_Obj_StayOnStep(@FObj)) and
2467 (FObj.Accel.Y = 0) then
2468 begin // Стоим на твердом полу или ступени
2469 // Прыжок через стену:
2470 FObj.Vel.Y := -MONSTERTABLE[FMonsterType].Jump;
2471 SetState(MONSTATE_CLIMB);
2472 end;
2473 end;
2475 goto _end;
2476 end;
2478 // Монстры, не подверженные гравитации:
2479 if (FMonsterType = MONSTER_CACO) or (FMonsterType = MONSTER_SOUL) or
2480 (FMonsterType = MONSTER_PAIN) or (FMonsterType = MONSTER_FISH) then
2481 begin
2482 if FMonsterType = MONSTER_FISH then
2483 begin // Рыба
2484 if not WordBool(st and MOVE_INWATER) then
2485 begin // Рыба вне воды:
2486 if g_Obj_CollideLevel(@FObj, 0, 1) or g_Obj_StayOnStep(@FObj) then
2487 begin // "Стоит" твердо
2488 // Рыба трепыхается на поверхности:
2489 if FObj.Accel.Y = 0 then FObj.Vel.Y := -6;
2490 FObj.Accel.X := FObj.Accel.X - 8 + Random(17);
2491 end;
2493 // Рыбе больно:
2494 SetState(MONSTATE_PAIN);
2495 FPain := FPain + 50;
2496 end
2497 else // Рыба в воде
2498 begin
2499 // Плывем в сторону цели по-вертикали:
2500 if Abs(sy) > 8 then
2501 FObj.Vel.Y := g_basic.Sign(sy)*4
2502 else
2503 FObj.Vel.Y := 0;
2505 // Рыба плывет вверх:
2506 if FObj.Vel.Y < 0 then
2507 if not g_Obj_CollideLiquid(@FObj, 0, -16) then
2508 begin
2509 // Всплыли до поверхности - стоп:
2510 FObj.Vel.Y := 0;
2511 // Плаваем туда-сюда:
2512 if Random(2) = 0 then
2513 FDirection := TDirection.D_LEFT
2514 else
2515 FDirection := TDirection.D_RIGHT;
2516 FSleep := 20;
2517 SetState(MONSTATE_RUN);
2518 end;
2519 end;
2520 end
2521 else // Летающие монстры
2522 begin
2523 // Летим в сторону цели по-вертикали:
2524 if Abs(sy) > 8 then
2525 FObj.Vel.Y := g_basic.Sign(sy)*4
2526 else
2527 FObj.Vel.Y := 0;
2528 end;
2529 end
2530 else // "Наземные" монстры
2531 begin
2532 {$IFDEF ENABLE_GIBS}
2533 // Возможно, пинаем куски:
2534 if (FObj.Vel.X <> 0) and (gGibs <> nil) then
2535 begin
2536 b := Abs(FObj.Vel.X);
2537 if b > 1 then b := b * (Random(8 div b) + 1);
2538 for a := 0 to High(gGibs) do
2539 begin
2540 if gGibs[a].alive and
2541 g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y+FObj.Rect.Height-4,
2542 FObj.Rect.Width, 8, @gGibs[a].Obj) and (Random(3) = 0) then
2543 begin
2544 // Пинаем куски
2545 if FObj.Vel.X < 0 then
2546 begin
2547 g_Obj_PushA(@gGibs[a].Obj, b, Random(61)+120); // налево
2548 end
2549 else
2550 begin
2551 g_Obj_PushA(@gGibs[a].Obj, b, Random(61)); // направо
2552 end;
2553 end;
2554 end;
2555 end;
2556 {$ENDIF}
2557 {$IFDEF ENABLE_CORPSES}
2558 // Боссы могут пинать трупы:
2559 if (FMonsterType in [MONSTER_CYBER, MONSTER_SPIDER, MONSTER_ROBO]) and
2560 (FObj.Vel.X <> 0) and (gCorpses <> nil) then
2561 begin
2562 b := Abs(FObj.Vel.X);
2563 if b > 1 then b := b * (Random(8 div b) + 1);
2564 for a := 0 to High(gCorpses) do
2565 if (gCorpses[a] <> nil) and (gCorpses[a].State > 0) then
2566 begin
2567 co := gCorpses[a].Obj;
2568 if g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y+FObj.Rect.Height-4,
2569 FObj.Rect.Width, 8, @co) and (Random(3) = 0) then
2570 // Пинаем трупы
2571 if FObj.Vel.X < 0 then
2572 gCorpses[a].Damage(b*2, FUID, -b, Random(7)) // налево
2573 else
2574 gCorpses[a].Damage(b*2, FUID, b, Random(7)); // направо
2575 end;
2576 end;
2577 {$ENDIF}
2578 // Если цель высоко, то, возможно, прыгаем:
2579 if sy < -40 then
2580 if g_Obj_CollideLevel(@FObj, 0, 1) or g_Obj_StayOnStep(@FObj) then
2581 // стоит твердо
2582 if (Random(4) = 0) and (FObj.Accel.Y = 0) then
2583 FObj.Vel.Y := -MONSTERTABLE[FMonsterType].Jump;
2584 end;
2586 FSleep := FSleep + 1;
2588 // Иногда рычим:
2589 if FSleep >= 8 then
2590 begin
2591 FSleep := 0;
2592 if Random(8) = 0 then
2593 ActionSound();
2594 end;
2596 // Бежим в выбранную сторону:
2597 if FDirection = TDirection.D_RIGHT then
2598 FObj.Vel.X := MONSTERTABLE[FMonsterType].RunVel
2599 else
2600 FObj.Vel.X := -MONSTERTABLE[FMonsterType].RunVel;
2602 // Если в воде, то замедляемся:
2603 if WordBool(st and MOVE_INWATER) then
2604 FObj.Vel.X := FObj.Vel.X div 2
2605 else // Рыбам не нужно замедляться
2606 if FMonsterType = MONSTER_FISH then
2607 FObj.Vel.X := 0;
2608 end;
2610 MONSTATE_RUN: // Состояние - Бег
2611 begin
2612 // Если наткнулись на БлокМон - убегаем от него:
2613 if WordBool(st and MOVE_BLOCK) then
2614 begin
2615 Turn();
2616 FSleep := 40;
2617 SetState(MONSTATE_RUNOUT);
2619 goto _end;
2620 end;
2622 FSleep := FSleep - 1;
2624 // Пробежали достаточно или врезались в стену => переходим на шаг:
2625 if (FSleep <= 0) or (WordBool(st and MOVE_HITWALL) and ((FObj.Vel.Y+FObj.Accel.Y) = 0)) then
2626 begin
2627 FSleep := 0;
2628 SetState(MONSTATE_GO);
2629 // Стена - идем обратно:
2630 if WordBool(st and (MOVE_HITWALL or MOVE_BLOCK)) then
2631 Turn();
2632 // Иногда рычим:
2633 if Random(8) = 0 then
2634 ActionSound();
2635 end;
2637 // Бежим в выбранную сторону:
2638 if FDirection = TDirection.D_RIGHT then
2639 FObj.Vel.X := MONSTERTABLE[FMonsterType].RunVel
2640 else
2641 FObj.Vel.X := -MONSTERTABLE[FMonsterType].RunVel;
2643 // Если в воде, то замедляемся:
2644 if WordBool(st and MOVE_INWATER) then
2645 FObj.Vel.X := FObj.Vel.X div 2
2646 else // Рыбам не нужно замедляться
2647 if FMonsterType = MONSTER_FISH then
2648 FObj.Vel.X := 0;
2649 end;
2651 MONSTATE_RUNOUT: // Состояние - Убегает от чего-то
2652 begin
2653 // Вышли из БлокМона:
2654 if (not WordBool(st and MOVE_BLOCK)) and (FSleep > 0) then
2655 FSleep := 0;
2657 FSleep := FSleep - 1;
2659 // Убажели достаточно далеко => переходим на шаг:
2660 if FSleep <= -18 then
2661 begin
2662 FSleep := 0;
2663 SetState(MONSTATE_GO);
2664 // Стена/БлокМон - идем обратно:
2665 if WordBool(st and (MOVE_HITWALL or MOVE_BLOCK)) then
2666 Turn();
2667 // Иногда рычим:
2668 if Random(8) = 0 then
2669 ActionSound();
2670 end;
2672 // Бежим в выбранную сторону:
2673 if FDirection = TDirection.D_RIGHT then
2674 FObj.Vel.X := MONSTERTABLE[FMonsterType].RunVel
2675 else
2676 FObj.Vel.X := -MONSTERTABLE[FMonsterType].RunVel;
2678 // Если в воде, то замедляемся:
2679 if WordBool(st and MOVE_INWATER) then
2680 FObj.Vel.X := FObj.Vel.X div 2
2681 else // Рыбам не нужно замедляться
2682 if FMonsterType = MONSTER_FISH then
2683 FObj.Vel.X := 0;
2684 end;
2686 MONSTATE_CLIMB: // Состояние - Прыжок (чтобы обойти стену)
2687 begin
2688 // Достигли высшей точки прыжка или стена кончилась => переходим на шаг:
2689 if ((FObj.Vel.Y+FObj.Accel.Y) >= 0) or
2690 (not WordBool(st and MOVE_HITWALL)) then
2691 begin
2692 FSleep := 0;
2693 SetState(MONSTATE_GO);
2695 // Стена не кончилась => бежим от нее:
2696 if WordBool(st and (MOVE_HITWALL or MOVE_BLOCK)) then
2697 begin
2698 Turn();
2699 FSleep := 15;
2700 SetState(MONSTATE_RUN);
2701 end;
2702 end;
2704 // Бежим в выбранную сторону:
2705 if FDirection = TDirection.D_RIGHT then
2706 FObj.Vel.X := MONSTERTABLE[FMonsterType].RunVel
2707 else
2708 FObj.Vel.X := -MONSTERTABLE[FMonsterType].RunVel;
2710 // Если в воде, то замедляемся:
2711 if WordBool(st and MOVE_INWATER) then
2712 FObj.Vel.X := FObj.Vel.X div 2
2713 else // Рыбам не нужно замедляться
2714 if FMonsterType = MONSTER_FISH then
2715 FObj.Vel.X := 0;
2716 end;
2718 MONSTATE_ATTACK, // Состояние - Атака
2719 MONSTATE_SHOOT: // Состояние - Стрельба
2720 begin
2721 // Lost_Soul врезался в стену при атаке => переходит на шаг:
2722 if FMonsterType = MONSTER_SOUL then
2723 begin
2724 if WordBool(st and (MOVE_HITWALL or MOVE_HITCEIL or MOVE_HITLAND)) then
2725 SetState(MONSTATE_GO);
2727 goto _end;
2728 end;
2730 // Замедляемся при атаке:
2731 if FMonsterType <> MONSTER_FISH then
2732 FObj.Vel.X := z_dec(FObj.Vel.X, 1);
2734 // Нужно стрелять, а монстр - колдун:
2735 if (FMonsterType = MONSTER_VILE) and (FState = MONSTATE_SHOOT) then
2736 begin
2737 // Цель погибла => идем дальше:
2738 if not GetPos(FTargetUID, @o) then
2739 begin
2740 SetState(MONSTATE_GO);
2742 goto _end;
2743 end;
2745 // Цель не видно => идем дальше:
2746 if not g_Look(@FObj, @o, FDirection) then
2747 begin
2748 SetState(MONSTATE_GO);
2750 goto _end;
2751 end;
2753 // Цель в воде - не загорится => идем дальше:
2754 if g_Obj_CollideWater(@o, 0, 0) then
2755 begin
2756 SetState(MONSTATE_GO);
2758 goto _end;
2759 end;
2761 // Жарим цель:
2762 tx := o.X+o.Rect.X+(o.Rect.Width div 2);
2763 ty := o.Y+o.Rect.Y+(o.Rect.Height div 2);
2764 g_Weapon_HitUID(FTargetUID, 2, FUID, HIT_SOME);
2765 end;
2766 end;
2767 end; // case FState of ...
2769 _end:
2771 // Состояние - Воскрешение:
2772 if FState = MONSTATE_REVIVE then
2773 if FAnim[FCurAnim, FDirection].Played then
2774 begin // Обратная анимация умирания закончилась - идем дальше:
2775 FAnim[FCurAnim, FDirection].Revert(False);
2776 SetState(MONSTATE_GO);
2777 end;
2779 // Если есть анимация огня колдуна - пусть она идет:
2780 if vilefire <> nil then
2781 vilefire.Update();
2783 // Состояние - Умирает и текущая анимация проиграна:
2784 if (FState = MONSTATE_DIE) and
2785 (FAnim[FCurAnim, FDirection] <> nil) and
2786 (FAnim[FCurAnim, FDirection].Played) then
2787 begin
2788 // Умер:
2789 SetState(MONSTATE_DEAD);
2791 // Pain_Elemental при смерти выпускает 3 Lost_Soul'а:
2792 if (FMonsterType = MONSTER_PAIN) then
2793 begin
2794 mon := g_Monsters_Create(MONSTER_SOUL, FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)-30,
2795 FObj.Y+FObj.Rect.Y+20, TDirection.D_LEFT);
2796 if mon <> nil then
2797 begin
2798 mon.SetState(MONSTATE_GO);
2799 mon.FNoRespawn := True;
2800 Inc(gTotalMonsters);
2801 if g_Game_IsNet then MH_SEND_MonsterSpawn(mon.UID);
2802 end;
2804 mon := g_Monsters_Create(MONSTER_SOUL, FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
2805 FObj.Y+FObj.Rect.Y+20, TDirection.D_RIGHT);
2806 if mon <> nil then
2807 begin
2808 mon.SetState(MONSTATE_GO);
2809 mon.FNoRespawn := True;
2810 Inc(gTotalMonsters);
2811 if g_Game_IsNet then MH_SEND_MonsterSpawn(mon.UID);
2812 end;
2814 mon := g_Monsters_Create(MONSTER_SOUL, FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)-15,
2815 FObj.Y+FObj.Rect.Y, TDirection.D_RIGHT);
2816 if mon <> nil then
2817 begin
2818 mon.SetState(MONSTATE_GO);
2819 mon.FNoRespawn := True;
2820 Inc(gTotalMonsters);
2821 if g_Game_IsNet then MH_SEND_MonsterSpawn(mon.UID);
2822 end;
2824 if g_Game_IsNet then MH_SEND_CoopStats();
2825 end;
2827 // У этих монстров нет трупов:
2828 if (FMonsterType = MONSTER_PAIN) or
2829 (FMonsterType = MONSTER_SOUL) or
2830 (FMonsterType = MONSTER_BARREL) then
2831 FRemoved := True;
2832 end;
2834 // Совершение атаки и стрельбы:
2835 if (FState = MONSTATE_ATTACK) or (FState = MONSTATE_SHOOT) then
2836 if (FAnim[FCurAnim, FDirection] <> nil) then
2837 // Анимация атаки есть - можно атаковать
2838 if (FAnim[FCurAnim, FDirection].Played) then
2839 begin // Анимация атаки закончилась => переходим на шаг
2840 if FState = MONSTATE_ATTACK then
2841 begin // Состояние - Атака
2842 // Если монстр не Lost_Soul, то после атаки переходим на шаг:
2843 if FMonsterType <> MONSTER_SOUL then
2844 SetState(MONSTATE_GO);
2845 end
2846 else // Состояние - Стрельба
2847 begin
2848 // Переходим на шаг, если не надо стрелять еще раз:
2849 if not FChainFire then
2850 SetState(MONSTATE_GO)
2851 else
2852 begin // Надо стрелять еще
2853 FChainFire := False;
2854 // Т.к. состояние не изменилось, и нужен
2855 // новый цикл ожидания завершения анимации:
2856 FAnim[FCurAnim, FDirection].Reset();
2857 end;
2858 end;
2860 FWaitAttackAnim := False;
2861 end
2863 else // Анимация атаки еще идет (исключение - Lost_Soul):
2864 if (FMonsterType = MONSTER_SOUL) or
2865 ( (not FWaitAttackAnim) and
2866 (FAnim[FCurAnim, FDirection].CurrentFrame =
2867 (FAnim[FCurAnim, FDirection].TotalFrames div 2))
2868 ) then
2869 begin // Атаки еще не было и это середина анимации атаки
2870 if FState = MONSTATE_ATTACK then
2871 begin // Состояние - Атака
2872 // Если это Lost_Soul, то сбрасываем анимацию атаки:
2873 if FMonsterType = MONSTER_SOUL then
2874 FAnim[FCurAnim, FDirection].Reset();
2876 case FMonsterType of
2877 MONSTER_SOUL, MONSTER_IMP, MONSTER_DEMON:
2878 // Грызем первого попавшегося:
2879 if g_Weapon_Hit(@FObj, 15, FUID, HIT_SOME) <> 0 then
2880 // Lost_Soul укусил кого-то => переходит на шаг:
2881 if FMonsterType = MONSTER_SOUL then
2882 SetState(MONSTATE_GO);
2884 MONSTER_FISH:
2885 // Рыба кусает первого попавшегося со звуком:
2886 if g_Weapon_Hit(@FObj, 10, FUID, HIT_SOME) <> 0 then
2887 g_Sound_PlayExAt('SOUND_MONSTER_FISH_ATTACK', FObj.X, FObj.Y);
2889 MONSTER_SKEL, MONSTER_ROBO, MONSTER_CYBER:
2890 // Робот, кибер или скелет сильно пинаются:
2891 if FCurAnim = ANIM_ATTACK2 then
2892 begin
2893 o := FObj;
2894 o.Vel.X := IfThen(FDirection = TDirection.D_RIGHT, 1, -1)*IfThen(FMonsterType = MONSTER_CYBER, 60, 50);
2895 if g_Weapon_Hit(@o, IfThen(FMonsterType = MONSTER_CYBER, 33, 50), FUID, HIT_SOME) <> 0 then
2896 g_Sound_PlayExAt('SOUND_MONSTER_SKEL_HIT', FObj.X, FObj.Y);
2897 end;
2899 MONSTER_VILE:
2900 // Колдун пытается воскрешать:
2901 if FCurAnim = ANIM_ATTACK2 then
2902 begin
2903 sx := isCorpse(@FObj, True);
2904 if sx <> -1 then
2905 begin // Нашли, кого воскресить
2906 gMonsters[sx].SetState(MONSTATE_REVIVE);
2907 g_Sound_PlayExAt('SOUND_MONSTER_SLOP', FObj.X, FObj.Y);
2908 // Воскрешать - себе вредить:
2909 {g_Weapon_HitUID(FUID, 5, 0, HIT_SOME);}
2910 end;
2911 end;
2912 end;
2913 end
2915 else // Состояние - Стрельба
2916 begin
2917 // Вычисляем координаты, откуда вылетит пуля:
2918 wx := MONSTER_ANIMTABLE[FMonsterType].wX;
2920 if FDirection = TDirection.D_LEFT then
2921 begin
2922 wx := MONSTER_ANIMTABLE[FMonsterType].wX-(MONSTERTABLE[FMonsterType].Rect.X+(MONSTERTABLE[FMonsterType].Rect.Width div 2));
2923 wx := MONSTERTABLE[FMonsterType].Rect.X+(MONSTERTABLE[FMonsterType].Rect.Width div 2)-wx;
2924 end; // Это значит: wx := hitX + (hitWidth / 2) - (wx - (hitX + (hitWidth / 2)))
2926 wx := FObj.X + wx;
2927 wy := FObj.Y + MONSTER_ANIMTABLE[FMonsterType].wY;
2929 // Монстр не может целиться в объект за спиной, стреляя влево:
2930 if (FDirection = TDirection.D_LEFT) and (tx > wx) then
2931 begin
2932 tx := wx - 32;
2933 ty := wy + Random(11) - 5;
2934 end;
2935 // И аналогично, стреляя вправо:
2936 if (FDirection = TDirection.D_RIGHT) and (tx < wx) then
2937 begin
2938 tx := wx + 32;
2939 ty := wy + Random(11) - 5;
2940 end;
2942 // Делаем выстрел нужным оружием:
2943 case FMonsterType of
2944 MONSTER_IMP:
2945 g_Weapon_ball1(wx, wy, tx, ty, FUID);
2946 MONSTER_ZOMBY:
2947 begin
2948 g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', wx, wy);
2949 g_Weapon_gun(wx, wy, tx, ty, 1, 3, FUID, True);
2950 {$IFDEF ENABLE_SHELLS}
2951 g_Shells_Create(wx, wy, 0, -2, SHELL_BULLET);
2952 {$ENDIF}
2953 end;
2954 MONSTER_SERG:
2955 begin
2956 g_Weapon_shotgun(wx, wy, tx, ty, FUID);
2957 if not gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', wx, wy);
2958 {$IFDEF ENABLE_SHELLS}
2959 FShellTimer := 10;
2960 FShellType := SHELL_SHELL;
2961 {$ENDIF}
2962 end;
2963 MONSTER_MAN:
2964 begin
2965 g_Weapon_dshotgun(wx, wy, tx, ty, FUID);
2966 {$IFDEF ENABLE_SHELLS}
2967 FShellTimer := 13;
2968 FShellType := SHELL_DBLSHELL;
2969 {$ENDIF}
2970 FAmmo := -36;
2971 end;
2972 MONSTER_CYBER:
2973 begin
2974 g_Weapon_rocket(wx, wy, tx, ty, FUID);
2975 // MH_SEND_MonsterAttack(FUID, wx, wy, tx, ty);
2976 end;
2977 MONSTER_SKEL:
2978 g_Weapon_revf(wx, wy, tx, ty, FUID, FTargetUID);
2979 MONSTER_CGUN:
2980 begin
2981 g_Weapon_mgun(wx, wy, tx, ty, FUID);
2982 if not gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', wx, wy);
2983 {$IFDEF ENABLE_SHELLS}
2984 g_Shells_Create(wx, wy, 0, -2, SHELL_BULLET);
2985 {$ENDIF}
2986 end;
2987 MONSTER_SPIDER:
2988 begin
2989 g_Weapon_mgun(wx, wy, tx, ty, FUID);
2990 if not gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', wx, wy);
2991 {$IFDEF ENABLE_SHELLS}
2992 g_Shells_Create(wx, wy, 0, -2, SHELL_SHELL);
2993 {$ENDIF}
2994 end;
2995 MONSTER_BSP:
2996 g_Weapon_aplasma(wx, wy, tx, ty, FUID);
2997 MONSTER_ROBO:
2998 g_Weapon_plasma(wx, wy, tx, ty, FUID);
2999 MONSTER_MANCUB:
3000 g_Weapon_manfire(wx, wy, tx, ty, FUID);
3001 MONSTER_BARON, MONSTER_KNIGHT:
3002 g_Weapon_ball7(wx, wy, tx, ty, FUID);
3003 MONSTER_CACO:
3004 g_Weapon_ball2(wx, wy, tx, ty, FUID);
3005 MONSTER_PAIN:
3006 begin // Создаем Lost_Soul:
3007 mon := g_Monsters_Create(MONSTER_SOUL, FObj.X+(FObj.Rect.Width div 2),
3008 FObj.Y+FObj.Rect.Y, FDirection);
3010 if mon <> nil then
3011 begin // Цель - цель Pain_Elemental'а. Летим к ней:
3012 mon.FTargetUID := FTargetUID;
3013 GetPos(FTargetUID, @o);
3014 mon.FTargetTime := 0;
3015 mon.FNoRespawn := True;
3016 mon.SetState(MONSTATE_GO);
3017 mon.shoot(@o, True);
3018 Inc(gTotalMonsters);
3020 if g_Game_IsNet then MH_SEND_MonsterSpawn(mon.UID);
3021 end;
3022 end;
3023 end;
3025 if FMonsterType <> MONSTER_PAIN then
3026 if g_Game_IsNet then
3027 MH_SEND_MonsterShot(FUID, wx, wy, tx, ty);
3029 // Скорострельные монстры:
3030 if (FMonsterType = MONSTER_CGUN) or
3031 (FMonsterType = MONSTER_SPIDER) or
3032 (FMonsterType = MONSTER_BSP) or
3033 (FMonsterType = MONSTER_MANCUB) or
3034 (FMonsterType = MONSTER_ROBO) then
3035 if not GetPos(FTargetUID, @o) then
3036 // Цель мертва - ищем новую:
3037 findNewPrey()
3038 else // Цель жива - продолжаем стрелять:
3039 if shoot(@o, False) then
3040 FChainFire := True;
3041 end;
3043 // Атака только 1 раз за анимацию атаки:
3044 FWaitAttackAnim := True;
3045 end;
3047 // Последний кадр текущей анимации:
3048 if FAnim[FCurAnim, FDirection].Counter = FAnim[FCurAnim, FDirection].Speed-1 then
3049 case FState of
3050 MONSTATE_GO, MONSTATE_RUN, MONSTATE_CLIMB, MONSTATE_RUNOUT:
3051 // Звуки при передвижении:
3052 case FMonsterType of
3053 MONSTER_CYBER:
3054 if (FAnim[FCurAnim, FDirection].CurrentFrame = 0) or
3055 (FAnim[FCurAnim, FDirection].CurrentFrame = 2) then
3056 g_Sound_PlayExAt('SOUND_MONSTER_CYBER_WALK', FObj.X, FObj.Y);
3057 MONSTER_SPIDER:
3058 if (FAnim[FCurAnim, FDirection].CurrentFrame = 0) or
3059 (FAnim[FCurAnim, FDirection].CurrentFrame = 2) then
3060 g_Sound_PlayExAt('SOUND_MONSTER_SPIDER_WALK', FObj.X, FObj.Y);
3061 MONSTER_BSP:
3062 if (FAnim[FCurAnim, FDirection].CurrentFrame = 0) or
3063 (FAnim[FCurAnim, FDirection].CurrentFrame = 2) then
3064 g_Sound_PlayExAt('SOUND_MONSTER_BSP_WALK', FObj.X, FObj.Y);
3065 MONSTER_ROBO:
3066 if (FAnim[FCurAnim, FDirection].CurrentFrame = 0) or
3067 (FAnim[FCurAnim, FDirection].CurrentFrame = 5) then
3068 g_Sound_PlayExAt('SOUND_MONSTER_BSP_WALK', FObj.X, FObj.Y);
3069 end;
3070 end;
3072 if g_Obj_CollidePanel(@FObj, 0, 0, PANEL_LIFTLEFT or PANEL_LIFTRIGHT) and
3073 not ((FState = MONSTATE_DEAD) or (FState = MONSTATE_DIE)) then
3074 FObj.Vel.X := oldvelx;
3076 // Если есть анимация, то пусть она идет:
3077 if FAnim[FCurAnim, FDirection] <> nil then
3078 FAnim[FCurAnim, FDirection].Update();
3079 end;
3081 procedure TMonster.SetDeadAnim;
3082 begin
3083 if FAnim <> nil then
3084 FAnim[FCurAnim, FDirection].CurrentFrame := FAnim[FCurAnim, FDirection].TotalFrames - 1;
3085 end;
3087 procedure TMonster.RevertAnim(R: Boolean = True);
3088 begin
3089 if FAnim <> nil then
3090 if FAnim[FCurAnim, FDirection].IsReverse <> R then
3091 FAnim[FCurAnim, FDirection].Revert(R);
3092 end;
3094 function TMonster.AnimIsReverse: Boolean;
3095 begin
3096 if FAnim <> nil then
3097 Result := FAnim[FCurAnim, FDirection].IsReverse
3098 else
3099 Result := False;
3100 end;
3102 procedure TMonster.ClientUpdate();
3103 {$IFDEF ENABLE_CORPSES}
3104 var a, b: Integer; co: TObj;
3105 {$ENDIF}
3106 var
3107 sx, sy, oldvelx: Integer;
3108 st: Word;
3109 o: TObj;
3110 fall, bubbles: Boolean;
3111 label
3112 _end;
3113 begin
3114 sx := 0; // SHUT UP COMPILER
3115 sy := 0;
3116 fall := True;
3117 bubbles := True;
3119 // Монстр статичен пока идет warmup
3120 if (gLMSRespawn > LMS_RESPAWN_NONE) then exit;
3122 // Рыбы "летают" только в воде:
3123 if FMonsterType = MONSTER_FISH then
3124 if g_Obj_CollidePanel(@FObj, 0, 0, PANEL_WATER or PANEL_ACID1 or PANEL_ACID2) then
3125 if (FState <> MONSTATE_DIE) and (FState <> MONSTATE_DEAD) then
3126 fall := False;
3128 // Летающие монтсры:
3129 if ((FMonsterType = MONSTER_SOUL) or
3130 (FMonsterType = MONSTER_PAIN) or
3131 (FMonsterType = MONSTER_CACO)) and
3132 (FState <> MONSTATE_DIE) and
3133 (FState <> MONSTATE_DEAD) then
3134 fall := False;
3136 // Меняем скорость только по четным кадрам:
3137 if gTime mod (GAME_TICK*2) <> 0 then
3138 begin
3139 g_Obj_Move(@FObj, fall, True, True);
3140 positionChanged(); // this updates spatial accelerators
3141 Exit;
3142 end;
3144 if FPainTicks > 0 then
3145 Dec(FPainTicks)
3146 else
3147 FPainSound := False;
3149 // Двигаемся:
3150 st := g_Obj_Move(@FObj, fall, True, True);
3151 positionChanged(); // this updates spatial accelerators
3153 // Вылетел за карту - удаляем и запускаем триггеры:
3154 if WordBool(st and MOVE_FALLOUT) or (FObj.X < -1000) or
3155 (FObj.X > gMapInfo.Width+1000) or (FObj.Y < -1000) then
3156 begin
3157 FRemoved := True;
3158 Exit;
3159 end;
3161 oldvelx := FObj.Vel.X;
3163 // Сопротивление воздуха для трупа:
3164 if (FState = MONSTATE_DIE) or (FState = MONSTATE_DEAD) then
3165 FObj.Vel.X := z_dec(FObj.Vel.X, 1);
3167 if FFireTime > 0 then
3168 begin
3169 if WordBool(st and MOVE_INWATER) then
3170 FFireTime := 0
3171 else
3172 begin
3173 OnFireFlame(1);
3174 FFireTime := FFireTime - 1;
3175 end;
3176 end;
3178 // Мертвый ничего не делает:
3179 if (FState = MONSTATE_DEAD) then
3180 goto _end;
3182 // Возможно, создаем пузырьки в воде:
3183 if WordBool(st and MOVE_INWATER) and (Random(32) = 0) then
3184 begin
3185 {$IFDEF ENABLE_GFX}
3186 case FMonsterType of
3187 MONSTER_FISH:
3188 if Random(4) <> 0 then bubbles := False else
3189 g_GFX_Bubbles(FObj.X+FObj.Rect.X + Random(FObj.Rect.Width),
3190 FObj.Y+FObj.Rect.Y + Random(4), 1, 0, 0);
3191 MONSTER_ROBO, MONSTER_BARREL:
3192 g_GFX_Bubbles(FObj.X+FObj.Rect.X + Random(FObj.Rect.Width),
3193 FObj.Y+FObj.Rect.Y + Random(4), 1, 0, 0);
3194 else
3195 g_GFX_Bubbles(FObj.X+FObj.Rect.X + Random(FObj.Rect.Width-4),
3196 FObj.Y+FObj.Rect.Y + Random(4), 5, 4, 4);
3197 end;
3198 {$ENDIF}
3199 if bubbles then if Random(2) = 0
3200 then g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', FObj.X, FObj.Y)
3201 else g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', FObj.X, FObj.Y);
3202 end;
3204 // Если прошел первый кадр анимации взрыва бочки, то взрыв:
3205 if FMonsterType = MONSTER_BARREL then
3206 begin
3207 if (FState = MONSTATE_DIE) and (FAnim[FCurAnim, FDirection].CurrentFrame = 1) and
3208 (FAnim[FCurAnim, FDirection].Counter = 0) then
3209 g_Weapon_Explode(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
3210 FObj.Y+FObj.Rect.Y+FObj.Rect.Height-16,
3211 60, FUID);
3212 end;
3214 // Lost_Soul вылетел из воды => ускоряется:
3215 if FMonsterType = MONSTER_SOUL then
3216 if WordBool(st and MOVE_HITAIR) then
3217 g_Obj_SetSpeed(@FObj, 16);
3219 if FAmmo < 0 then
3220 FAmmo := FAmmo + 1;
3222 // Если начали всплывать, то продолжаем:
3223 if FObj.Vel.Y < 0 then
3224 if WordBool(st and MOVE_INWATER) then
3225 FObj.Vel.Y := -4;
3227 // Таймер - ждем после потери цели:
3228 FTargetTime := FTargetTime + 1;
3230 {$IFDEF ENABLE_SHELLS}
3231 if FShellTimer > -1 then
3232 begin
3233 if FShellTimer = 0 then
3234 begin
3235 if FShellType = SHELL_SHELL then
3236 begin
3237 g_Shells_Create(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
3238 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2),
3239 GameVelX, GameVelY-2, SHELL_SHELL)
3240 end
3241 else if FShellType = SHELL_DBLSHELL then
3242 begin
3243 g_Shells_Create(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
3244 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2),
3245 GameVelX-1, GameVelY-2, SHELL_SHELL);
3246 g_Shells_Create(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
3247 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2),
3248 GameVelX+1, GameVelY-2, SHELL_SHELL);
3249 end;
3250 FShellTimer := -1;
3251 end
3252 else
3253 begin
3254 Dec(FShellTimer);
3255 end;
3256 end;
3257 {$ENDIF}
3259 // Пробуем увернуться от летящей пули:
3260 if fall then
3261 if (FState in [MONSTATE_GO, MONSTATE_RUN, MONSTATE_RUNOUT,
3262 MONSTATE_ATTACK, MONSTATE_SHOOT]) then
3263 if g_Weapon_Danger(FUID, FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y,
3264 FObj.Rect.Width, FObj.Rect.Height, 50) then
3265 if (g_Obj_CollideLevel(@FObj, 0, 1) or g_Obj_StayOnStep(@FObj)) and
3266 (FObj.Accel.Y = 0) then
3267 FObj.Vel.Y := -MONSTERTABLE[FMonsterType].Jump;
3269 case FState of
3270 MONSTATE_PAIN: // Состояние - Боль
3271 begin
3272 // Боль сильная => монстр кричит:
3273 if FPain >= MONSTERTABLE[FMonsterType].Pain then
3274 begin
3275 FPain := MONSTERTABLE[FMonsterType].Pain;
3276 if gSoundEffectsDF then PainSound();
3277 end;
3278 if (not gSoundEffectsDF) and (FPain >= MONSTERTABLE[FMonsterType].MinPain) then
3279 PainSound();
3281 // Снижаем боль со временем:
3282 FPain := FPain - 5;
3284 // Боль уже не ошутимая => идем дальше:
3285 if FPain <= MONSTERTABLE[FMonsterType].MinPain then
3286 begin
3287 SetState(MONSTATE_GO);
3288 FPain := 0;
3289 end;
3290 end;
3292 MONSTATE_SLEEP: // Состояние - Сон
3293 begin
3294 // Спим:
3295 FSleep := FSleep + 1;
3297 // Проспали достаточно:
3298 if FSleep >= 18 then
3299 FSleep := 0
3300 else // еще спим
3301 goto _end;
3302 end;
3304 MONSTATE_WAIT: // Состояние - Ожидание
3305 begin
3306 // Ждем:
3307 FSleep := FSleep - 1;
3308 end;
3310 MONSTATE_GO: // Состояние - Движение (с осмотром ситуации)
3311 begin
3312 // Если наткнулись на БлокМон - убегаем от него:
3313 if WordBool(st and MOVE_BLOCK) then
3314 begin
3315 Turn();
3316 FSleep := 40;
3317 SetState(MONSTATE_RUNOUT);
3319 goto _end;
3320 end;
3322 // Если монстр - колдун, то пробуем воскресить кого-нибудь:
3323 if (FMonsterType = MONSTER_VILE) then
3324 if isCorpse(@FObj, False) <> -1 then
3325 begin
3326 SetState(MONSTATE_ATTACK, ANIM_ATTACK2);
3327 FObj.Vel.X := 0;
3329 goto _end;
3330 end;
3332 // Если цель почти на одной вертикали - бегаем туда-сюда:
3333 if Abs(sx) < 40 then
3334 if FMonsterType <> MONSTER_FISH then
3335 begin
3336 SetState(MONSTATE_RUN);
3337 FSleep := 15;
3339 goto _end;
3340 end;
3342 // Уперлись в стену:
3343 if WordBool(st and MOVE_HITWALL) then
3344 begin
3345 case FMonsterType of
3346 MONSTER_CACO, MONSTER_SOUL, MONSTER_PAIN, MONSTER_FISH: ;
3347 else // Не летают:
3348 if (g_Obj_CollideLevel(@FObj, 0, 1) or g_Obj_StayOnStep(@FObj)) and
3349 (FObj.Accel.Y = 0) then
3350 begin // Стоим на твердом полу или ступени
3351 // Прыжок через стену:
3352 FObj.Vel.Y := -MONSTERTABLE[FMonsterType].Jump;
3353 SetState(MONSTATE_CLIMB);
3354 end;
3355 end;
3357 goto _end;
3358 end;
3360 // Монстры, не подверженные гравитации:
3361 if (FMonsterType = MONSTER_CACO) or (FMonsterType = MONSTER_SOUL) or
3362 (FMonsterType = MONSTER_PAIN) or (FMonsterType = MONSTER_FISH) then
3363 begin
3364 if FMonsterType = MONSTER_FISH then
3365 begin // Рыба
3366 if not WordBool(st and MOVE_INWATER) then
3367 begin // Рыба вне воды:
3368 if g_Obj_CollideLevel(@FObj, 0, 1) or g_Obj_StayOnStep(@FObj) then
3369 begin // "Стоит" твердо
3370 // Рыба трепыхается на поверхности:
3371 if FObj.Accel.Y = 0 then
3372 FObj.Vel.Y := -6;
3373 FObj.Accel.X := FObj.Accel.X - 8 + Random(17);
3374 end;
3376 // Рыбе больно:
3377 SetState(MONSTATE_PAIN);
3378 FPain := FPain + 50;
3379 end
3380 else // Рыба в воде
3381 begin
3382 // Плывем в сторону цели по-вертикали:
3383 if Abs(sy) > 8 then
3384 FObj.Vel.Y := g_basic.Sign(sy)*4
3385 else
3386 FObj.Vel.Y := 0;
3388 // Рыба плывет вверх:
3389 if FObj.Vel.Y < 0 then
3390 if not g_Obj_CollideLiquid(@FObj, 0, -16) then
3391 begin
3392 // Всплыли до поверхности - стоп:
3393 FObj.Vel.Y := 0;
3394 // Плаваем туда-сюда:
3395 SetState(MONSTATE_RUN);
3396 FSleep := 20;
3397 end;
3398 end;
3399 end
3400 else // Летающие монстры
3401 begin
3402 // Летим в сторону цели по-вертикали:
3403 if Abs(sy) > 8 then
3404 FObj.Vel.Y := g_basic.Sign(sy)*4
3405 else
3406 FObj.Vel.Y := 0;
3407 end;
3408 end
3409 else // "Наземные" монстры
3410 begin
3411 {$IFDEF ENBALE_GIBS}
3412 // Возможно, пинаем куски:
3413 if (FObj.Vel.X <> 0) and (gGibs <> nil) then
3414 begin
3415 b := Abs(FObj.Vel.X);
3416 if b > 1 then b := b * (Random(8 div b) + 1);
3417 for a := 0 to High(gGibs) do
3418 begin
3419 if gGibs[a].alive and
3420 g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y+FObj.Rect.Height-4,
3421 FObj.Rect.Width, 8, @gGibs[a].Obj) and (Random(3) = 0) then
3422 begin
3423 // Пинаем куски
3424 if FObj.Vel.X < 0 then
3425 begin
3426 g_Obj_PushA(@gGibs[a].Obj, b, Random(61)+120); // налево
3427 end
3428 else
3429 begin
3430 g_Obj_PushA(@gGibs[a].Obj, b, Random(61)); // направо
3431 end;
3432 positionChanged(); // this updates spatial accelerators
3433 end;
3434 end;
3435 end;
3436 {$ENDIF}
3437 {$IFDEF ENABLE_CORPSES}
3438 // Боссы могут пинать трупы:
3439 if (FMonsterType in [MONSTER_CYBER, MONSTER_SPIDER, MONSTER_ROBO]) and
3440 (FObj.Vel.X <> 0) and (gCorpses <> nil) then
3441 begin
3442 b := Abs(FObj.Vel.X);
3443 if b > 1 then b := b * (Random(8 div b) + 1);
3444 for a := 0 to High(gCorpses) do
3445 if (gCorpses[a] <> nil) and (gCorpses[a].State > 0) then
3446 begin
3447 co := gCorpses[a].Obj;
3448 if g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y+FObj.Rect.Height-4,
3449 FObj.Rect.Width, 8, @co) and (Random(3) = 0) then
3450 // Пинаем трупы
3451 if FObj.Vel.X < 0 then
3452 gCorpses[a].Damage(b*2, FUID, -b, Random(7)) // налево
3453 else
3454 gCorpses[a].Damage(b*2, FUID, b, Random(7)); // направо
3455 end;
3456 end;
3457 {$ENDIF}
3458 end;
3460 FSleep := FSleep + 1;
3462 // Иногда рычим:
3463 if FSleep >= 8 then
3464 begin
3465 FSleep := 0;
3466 if Random(8) = 0 then
3467 ActionSound();
3468 end;
3470 // Бежим в выбранную сторону:
3471 if FDirection = TDirection.D_RIGHT then
3472 FObj.Vel.X := MONSTERTABLE[FMonsterType].RunVel
3473 else
3474 FObj.Vel.X := -MONSTERTABLE[FMonsterType].RunVel;
3476 // Если в воде, то замедляемся:
3477 if WordBool(st and MOVE_INWATER) then
3478 FObj.Vel.X := FObj.Vel.X div 2
3479 else // Рыбам не нужно замедляться
3480 if FMonsterType = MONSTER_FISH then
3481 FObj.Vel.X := 0;
3482 end;
3484 MONSTATE_RUN: // Состояние - Бег
3485 begin
3486 // Если наткнулись на БлокМон - убегаем от него:
3487 if WordBool(st and MOVE_BLOCK) then
3488 begin
3489 SetState(MONSTATE_RUNOUT);
3490 FSleep := 40;
3492 goto _end;
3493 end;
3495 FSleep := FSleep - 1;
3497 // Пробежали достаточно или врезались в стену => переходим на шаг:
3498 if (FSleep <= 0) or (WordBool(st and MOVE_HITWALL) and ((FObj.Vel.Y+FObj.Accel.Y) = 0)) then
3499 begin
3500 SetState(MONSTATE_GO);
3501 FSleep := 0;
3503 // Иногда рычим:
3504 if Random(8) = 0 then
3505 ActionSound();
3506 end;
3508 // Бежим в выбранную сторону:
3509 if FDirection = TDirection.D_RIGHT then
3510 FObj.Vel.X := MONSTERTABLE[FMonsterType].RunVel
3511 else
3512 FObj.Vel.X := -MONSTERTABLE[FMonsterType].RunVel;
3514 // Если в воде, то замедляемся:
3515 if WordBool(st and MOVE_INWATER) then
3516 FObj.Vel.X := FObj.Vel.X div 2
3517 else // Рыбам не нужно замедляться
3518 if FMonsterType = MONSTER_FISH then
3519 FObj.Vel.X := 0;
3520 end;
3522 MONSTATE_RUNOUT: // Состояние - Убегает от чего-то
3523 begin
3524 // Вышли из БлокМона:
3525 if (not WordBool(st and MOVE_BLOCK)) and (FSleep > 0) then
3526 FSleep := 0;
3528 FSleep := FSleep - 1;
3530 // Убажели достаточно далеко => переходим на шаг:
3531 if FSleep <= -18 then
3532 begin
3533 SetState(MONSTATE_GO);
3534 FSleep := 0;
3536 // Иногда рычим:
3537 if Random(8) = 0 then
3538 ActionSound();
3539 end;
3541 // Бежим в выбранную сторону:
3542 if FDirection = TDirection.D_RIGHT then
3543 FObj.Vel.X := MONSTERTABLE[FMonsterType].RunVel
3544 else
3545 FObj.Vel.X := -MONSTERTABLE[FMonsterType].RunVel;
3547 // Если в воде, то замедляемся:
3548 if WordBool(st and MOVE_INWATER) then
3549 FObj.Vel.X := FObj.Vel.X div 2
3550 else // Рыбам не нужно замедляться
3551 if FMonsterType = MONSTER_FISH then
3552 FObj.Vel.X := 0;
3553 end;
3555 MONSTATE_CLIMB: // Состояние - Прыжок (чтобы обойти стену)
3556 begin
3557 // Достигли высшей точки прыжка или стена кончилась => переходим на шаг:
3558 if ((FObj.Vel.Y+FObj.Accel.Y) >= 0) or
3559 (not WordBool(st and MOVE_HITWALL)) then
3560 begin
3561 SetState(MONSTATE_GO);
3562 FSleep := 0;
3564 // Стена не кончилась => бежим от нее:
3565 if WordBool(st and (MOVE_HITWALL or MOVE_BLOCK)) then
3566 begin
3567 SetState(MONSTATE_RUN);
3568 FSleep := 15;
3569 end;
3570 end;
3572 // Бежим в выбранную сторону:
3573 if FDirection = TDirection.D_RIGHT then
3574 FObj.Vel.X := MONSTERTABLE[FMonsterType].RunVel
3575 else
3576 FObj.Vel.X := -MONSTERTABLE[FMonsterType].RunVel;
3578 // Если в воде, то замедляемся:
3579 if WordBool(st and MOVE_INWATER) then
3580 FObj.Vel.X := FObj.Vel.X div 2
3581 else // Рыбам не нужно замедляться
3582 if FMonsterType = MONSTER_FISH then
3583 FObj.Vel.X := 0;
3584 end;
3586 MONSTATE_ATTACK, // Состояние - Атака
3587 MONSTATE_SHOOT: // Состояние - Стрельба
3588 begin
3589 // Lost_Soul врезался в стену при атаке => переходит на шаг:
3590 if FMonsterType = MONSTER_SOUL then
3591 begin
3592 if WordBool(st and (MOVE_HITWALL or MOVE_HITCEIL or MOVE_HITLAND)) then
3593 SetState(MONSTATE_GO);
3595 goto _end;
3596 end;
3598 // Замедляемся при атаке:
3599 if FMonsterType <> MONSTER_FISH then
3600 FObj.Vel.X := z_dec(FObj.Vel.X, 1);
3602 // Нужно стрелять, а монстр - колдун:
3603 if (FMonsterType = MONSTER_VILE) and (FState = MONSTATE_SHOOT) then
3604 begin
3605 // Цель погибла => идем дальше:
3606 if not GetPos(FTargetUID, @o) then
3607 begin
3608 SetState(MONSTATE_GO);
3610 goto _end;
3611 end;
3613 // Цель не видно => идем дальше:
3614 if not g_Look(@FObj, @o, FDirection) then
3615 begin
3616 SetState(MONSTATE_GO);
3618 goto _end;
3619 end;
3621 // Цель в воде - не загорится => идем дальше:
3622 if g_Obj_CollideWater(@o, 0, 0) then
3623 begin
3624 SetState(MONSTATE_GO);
3626 goto _end;
3627 end;
3628 end;
3629 end;
3630 end; // case FState of ...
3632 _end:
3634 // Состояние - Воскрешение:
3635 if FState = MONSTATE_REVIVE then
3636 if FAnim[FCurAnim, FDirection].Played then
3637 begin // Обратная анимация умирания закончилась - идем дальше:
3638 FAnim[FCurAnim, FDirection].Revert(False);
3639 SetState(MONSTATE_GO);
3640 end;
3642 // Если есть анимация огня колдуна - пусть она идет:
3643 if vilefire <> nil then
3644 vilefire.Update();
3646 // Состояние - Умирает и текущая анимация проиграна:
3647 if (FState = MONSTATE_DIE) and
3648 (FAnim[FCurAnim, FDirection] <> nil) and
3649 (FAnim[FCurAnim, FDirection].Played) then
3650 begin
3651 // Умер:
3652 SetState(MONSTATE_DEAD);
3654 // У этих монстров нет трупов:
3655 if (FMonsterType = MONSTER_PAIN) or
3656 (FMonsterType = MONSTER_SOUL) or
3657 (FMonsterType = MONSTER_BARREL) then
3658 FRemoved := True
3659 else
3660 FAnim[FCurAnim, FDirection].CurrentFrame := FAnim[FCurAnim, FDirection].TotalFrames - 1;
3661 end;
3663 // Совершение атаки и стрельбы:
3664 if (FState = MONSTATE_ATTACK) or (FState = MONSTATE_SHOOT) then
3665 if (FAnim[FCurAnim, FDirection] <> nil) then
3666 // Анимация атаки есть - можно атаковать
3667 if (FAnim[FCurAnim, FDirection].Played) then
3668 begin // Анимация атаки закончилась => переходим на шаг
3669 if FState = MONSTATE_ATTACK then
3670 begin // Состояние - Атака
3671 // Если монстр не Lost_Soul, то после атаки переходим на шаг:
3672 if FMonsterType <> MONSTER_SOUL then
3673 SetState(MONSTATE_GO);
3674 end
3675 else // Состояние - Стрельба
3676 begin
3677 // Переходим на шаг, если не надо стрелять еще раз:
3678 if not FChainFire then
3679 SetState(MONSTATE_GO)
3680 else
3681 begin // Надо стрелять еще
3682 FChainFire := False;
3683 // Т.к. состояние не изменилось, и нужен
3684 // новый цикл ожидания завершения анимации:
3685 FAnim[FCurAnim, FDirection].Reset();
3686 end;
3687 end;
3689 FWaitAttackAnim := False;
3690 end
3692 else // Анимация атаки еще идет (исключение - Lost_Soul):
3693 if (FMonsterType = MONSTER_SOUL) or
3694 ( (not FWaitAttackAnim) and
3695 (FAnim[FCurAnim, FDirection].CurrentFrame =
3696 (FAnim[FCurAnim, FDirection].TotalFrames div 2))
3697 ) then
3698 begin // Атаки еще не было и это середина анимации атаки
3699 if FState = MONSTATE_ATTACK then
3700 begin // Состояние - Атака
3701 // Если это Lost_Soul, то сбрасываем анимацию атаки:
3702 if FMonsterType = MONSTER_SOUL then
3703 FAnim[FCurAnim, FDirection].Reset();
3705 case FMonsterType of
3706 MONSTER_SOUL, MONSTER_IMP, MONSTER_DEMON:
3707 // Грызем первого попавшегося:
3708 if g_Weapon_Hit(@FObj, 15, FUID, HIT_SOME) <> 0 then
3709 // Lost_Soul укусил кого-то => переходит на шаг:
3710 if FMonsterType = MONSTER_SOUL then
3711 SetState(MONSTATE_GO);
3713 MONSTER_FISH:
3714 g_Weapon_Hit(@FObj, 10, FUID, HIT_SOME);
3716 MONSTER_SKEL, MONSTER_ROBO, MONSTER_CYBER:
3717 // Робот, кибер или скелет сильно пинаются:
3718 if FCurAnim = ANIM_ATTACK2 then
3719 begin
3720 o := FObj;
3721 o.Vel.X := IfThen(FDirection = TDirection.D_RIGHT, 1, -1)*IfThen(FMonsterType = MONSTER_CYBER, 60, 50);
3722 g_Weapon_Hit(@o, IfThen(FMonsterType = MONSTER_CYBER, 33, 50), FUID, HIT_SOME);
3723 end;
3725 MONSTER_VILE:
3726 // Колдун пытается воскрешать:
3727 if FCurAnim = ANIM_ATTACK2 then
3728 begin
3729 sx := isCorpse(@FObj, True);
3730 if sx <> -1 then
3731 begin // Нашли, кого воскресить
3732 g_Sound_PlayExAt('SOUND_MONSTER_SLOP', FObj.X, FObj.Y);
3733 // Воскрешать - себе вредить:
3734 {g_Weapon_HitUID(FUID, 5, 0, HIT_SOME);}
3735 end;
3736 end;
3737 end;
3738 end
3740 else // Состояние - Стрельба
3741 begin
3742 // Скорострельные монстры:
3743 if (FMonsterType = MONSTER_CGUN) or
3744 (FMonsterType = MONSTER_SPIDER) or
3745 (FMonsterType = MONSTER_BSP) or
3746 (FMonsterType = MONSTER_MANCUB) or
3747 (FMonsterType = MONSTER_ROBO) then
3748 if not GetPos(FTargetUID, @o) then
3749 // Цель мертва - ищем новую:
3750 findNewPrey()
3751 else // Цель жива - продолжаем стрелять:
3752 if shoot(@o, False) then
3753 FChainFire := True;
3754 end;
3756 // Атака только 1 раз за анимацию атаки:
3757 FWaitAttackAnim := True;
3758 end;
3760 // Последний кадр текущей анимации:
3761 if FAnim[FCurAnim, FDirection].Counter = FAnim[FCurAnim, FDirection].Speed-1 then
3762 case FState of
3763 MONSTATE_GO, MONSTATE_RUN, MONSTATE_CLIMB, MONSTATE_RUNOUT:
3764 // Звуки при передвижении:
3765 case FMonsterType of
3766 MONSTER_CYBER:
3767 if (FAnim[FCurAnim, FDirection].CurrentFrame = 0) or
3768 (FAnim[FCurAnim, FDirection].CurrentFrame = 2) then
3769 g_Sound_PlayExAt('SOUND_MONSTER_CYBER_WALK', FObj.X, FObj.Y);
3770 MONSTER_SPIDER:
3771 if (FAnim[FCurAnim, FDirection].CurrentFrame = 0) or
3772 (FAnim[FCurAnim, FDirection].CurrentFrame = 2) then
3773 g_Sound_PlayExAt('SOUND_MONSTER_SPIDER_WALK', FObj.X, FObj.Y);
3774 MONSTER_BSP:
3775 if (FAnim[FCurAnim, FDirection].CurrentFrame = 0) or
3776 (FAnim[FCurAnim, FDirection].CurrentFrame = 2) then
3777 g_Sound_PlayExAt('SOUND_MONSTER_BSP_WALK', FObj.X, FObj.Y);
3778 MONSTER_ROBO:
3779 if (FAnim[FCurAnim, FDirection].CurrentFrame = 0) or
3780 (FAnim[FCurAnim, FDirection].CurrentFrame = 5) then
3781 g_Sound_PlayExAt('SOUND_MONSTER_BSP_WALK', FObj.X, FObj.Y);
3782 end;
3783 end;
3785 // Костыль для потоков
3786 if g_Obj_CollidePanel(@FObj, 0, 0, PANEL_LIFTLEFT or PANEL_LIFTRIGHT) and
3787 not ((FState = MONSTATE_DEAD) or (FState = MONSTATE_DIE)) then
3788 FObj.Vel.X := oldvelx;
3790 // Если есть анимация, то пусть она идет:
3791 if FAnim[FCurAnim, FDirection] <> nil then
3792 FAnim[FCurAnim, FDirection].Update();
3793 end;
3795 procedure TMonster.ClientAttack(wx, wy, atx, aty: Integer);
3796 begin
3797 case FMonsterType of
3798 MONSTER_ZOMBY:
3799 begin
3800 g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', wx, wy);
3801 {$IFDEF ENABLE_SHELLS}
3802 g_Shells_Create(wx, wy, 0, -2, SHELL_BULLET);
3803 {$ENDIF}
3804 end;
3805 MONSTER_SERG:
3806 begin
3807 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', wx, wy);
3808 {$IFDEF ENABLE_SHELLS}
3809 FShellTimer := 10;
3810 FShellType := SHELL_SHELL;
3811 {$ENDIF}
3812 end;
3813 MONSTER_MAN:
3814 begin
3815 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN2', wx, wy);
3816 {$IFDEF ENABLE_SHELLS}
3817 FShellTimer := 13;
3818 FShellType := SHELL_DBLSHELL;
3819 {$ENDIF}
3820 end;
3821 MONSTER_CGUN, MONSTER_SPIDER:
3822 begin
3823 g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', wx, wy);
3824 {$IFDEF ENABLE_SHELLS}
3825 g_Shells_Create(wx, wy, 0, -2, SHELL_BULLET);
3826 {$ENDIF}
3827 end;
3828 MONSTER_IMP:
3829 g_Weapon_ball1(wx, wy, atx, aty, FUID);
3830 MONSTER_CYBER:
3831 g_Weapon_rocket(wx, wy, atx, aty, FUID);
3832 MONSTER_SKEL:
3833 g_Weapon_revf(wx, wy, atx, aty, FUID, FTargetUID);
3834 MONSTER_BSP:
3835 g_Weapon_aplasma(wx, wy, atx, aty, FUID);
3836 MONSTER_ROBO:
3837 g_Weapon_plasma(wx, wy, atx, aty, FUID);
3838 MONSTER_MANCUB:
3839 g_Weapon_manfire(wx, wy, atx, aty, FUID);
3840 MONSTER_BARON, MONSTER_KNIGHT:
3841 g_Weapon_ball7(wx, wy, atx, aty, FUID);
3842 MONSTER_CACO:
3843 g_Weapon_ball2(wx, wy, atx, aty, FUID);
3844 end;
3845 end;
3847 procedure TMonster.Turn();
3848 begin
3849 // Разворачиваемся:
3850 if FDirection = TDirection.D_LEFT then FDirection := TDirection.D_RIGHT else FDirection := TDirection.D_LEFT;
3852 // Бежим в выбранную сторону:
3853 if FDirection = TDirection.D_RIGHT then
3854 FObj.Vel.X := MONSTERTABLE[FMonsterType].RunVel
3855 else
3856 FObj.Vel.X := -MONSTERTABLE[FMonsterType].RunVel;
3857 end;
3859 function TMonster.findNewPrey(): Boolean;
3860 var
3861 a: DWORD;
3862 l, l2: Integer;
3863 PlayersSee, MonstersSee: Array of DWORD;
3864 PlayerNear, MonsterNear: Integer;
3865 begin
3866 Result := False;
3867 SetLength(MonstersSee, 0);
3868 SetLength(PlayersSee, 0);
3870 FTargetUID := 0;
3871 l := 32000;
3872 PlayerNear := -1;
3873 MonsterNear := -1;
3875 // Поехавшие, каннибалы, и добрые игроков не трогают
3876 if (gPlayers <> nil) and (FBehaviour <> BH_INSANE) and
3877 (FBehaviour <> BH_CANNIBAL) and (FBehaviour <> BH_GOOD) then
3878 for a := 0 to High(gPlayers) do
3879 if (gPlayers[a] <> nil) and (gPlayers[a].alive)
3880 and (not gPlayers[a].NoTarget) and (gPlayers[a].FMegaRulez[MR_INVIS] < gTime) then
3881 begin
3882 if g_Look(@FObj, @gPlayers[a].Obj, FDirection) then
3883 begin
3884 SetLength(PlayersSee, Length(PlayersSee) + 1);
3885 PlayersSee[High(PlayersSee)] := a;
3886 end;
3887 l2 := Abs(gPlayers[a].GameX-FObj.X)+
3888 Abs(gPlayers[a].GameY-FObj.Y);
3889 if l2 < l then
3890 begin
3891 l := l2;
3892 PlayerNear := Integer(a);
3893 end;
3894 end;
3896 // Киллеры и добрые не трогают монстров
3897 if (gMonsters <> nil) and (FBehaviour <> BH_KILLER) and (FBehaviour <> BH_GOOD) then
3898 for a := 0 to High(gMonsters) do
3899 if (gMonsters[a] <> nil) and (gMonsters[a].alive) and
3900 (gMonsters[a].FUID <> FUID) then
3901 begin
3902 if (FBehaviour = BH_CANNIBAL) and (gMonsters[a].FMonsterType <> FMonsterType) then
3903 Continue; // Каннибалы атакуют только сородичей
3904 if (FBehaviour = BH_INSANE) and (gMonsters[a].FMonsterType <> MONSTER_BARREL)
3905 and (IsFriend(gMonsters[a].FMonsterType, FMonsterType)) then
3906 Continue; // Поехавшие не трогают друзей, но им не нравятся бочки
3907 if ((FBehaviour = BH_NORMAL) or (FBehaviour = BH_MANIAC))
3908 and (IsFriend(gMonsters[a].FMonsterType, FMonsterType)) then
3909 Continue; // Оставшиеся типы, кроме каннибалов, не трогают своих друзей
3911 if g_Look(@FObj, @gMonsters[a].Obj, FDirection) then
3912 begin
3913 SetLength(MonstersSee, Length(MonstersSee) + 1);
3914 MonstersSee[High(MonstersSee)] := a;
3915 end;
3916 l2 := Abs(gMonsters[a].FObj.X-FObj.X)+
3917 Abs(gMonsters[a].FObj.Y-FObj.Y);
3918 if l2 < l then
3919 begin
3920 l := l2;
3921 MonsterNear := Integer(a);
3922 end;
3923 end;
3925 case FBehaviour of
3926 BH_NORMAL, BH_KILLER:
3927 begin
3928 // Обычный и киллер сначала ищут игроков в поле зрения
3929 if (FTargetUID = 0) and (Length(PlayersSee) > 0) then
3930 begin
3931 a := PlayersSee[Random(Length(PlayersSee))];
3932 FTargetUID := gPlayers[a].UID;
3933 end;
3934 // Затем поблизости
3935 if (FTargetUID = 0) and (PlayerNear > -1) then
3936 begin
3937 a := PlayerNear;
3938 FTargetUID := gPlayers[a].UID;
3939 end;
3940 // Потом обычные ищут монстров в поле зрения
3941 if (FTargetUID = 0) and (Length(MonstersSee) > 0) then
3942 begin
3943 a := MonstersSee[Random(Length(MonstersSee))];
3944 FTargetUID := gMonsters[a].UID;
3945 end;
3946 // Затем поблизости
3947 if (FTargetUID = 0) and (MonsterNear > -1) then
3948 begin
3949 a := MonsterNear;
3950 FTargetUID := gMonsters[a].UID;
3951 end;
3952 end;
3953 BH_MANIAC, BH_INSANE, BH_CANNIBAL:
3954 begin
3955 // Маньяки, поехавшие и каннибалы сначала истребляют всё в поле зрения
3956 if (FTargetUID = 0) and (Length(PlayersSee) > 0) then
3957 begin
3958 a := PlayersSee[Random(Length(PlayersSee))];
3959 FTargetUID := gPlayers[a].UID;
3960 end;
3961 if (FTargetUID = 0) and (Length(MonstersSee) > 0) then
3962 begin
3963 a := MonstersSee[Random(Length(MonstersSee))];
3964 FTargetUID := gMonsters[a].UID;
3965 end;
3966 // Затем ищут кого-то поблизости
3967 if (FTargetUID = 0) and (PlayerNear > -1) then
3968 begin
3969 a := PlayerNear;
3970 FTargetUID := gPlayers[a].UID;
3971 end;
3972 if (FTargetUID = 0) and (MonsterNear > -1) then
3973 begin
3974 a := MonsterNear;
3975 FTargetUID := gMonsters[a].UID;
3976 end;
3977 end;
3978 end;
3980 // Если и монстров нет - начинаем ждать цель:
3981 if FTargetUID = 0 then
3982 begin
3983 // Поехавший пытается самоубиться
3984 if FBehaviour = BH_INSANE then
3985 FTargetUID := FUID
3986 else
3987 FTargetTime := MAX_ATM;
3988 end
3989 else
3990 begin // Цель нашли
3991 FTargetTime := 0;
3992 Result := True;
3993 end;
3994 end;
3996 function TMonster.kick(o: PObj): Boolean;
3997 begin
3998 Result := False;
4000 case FMonsterType of
4001 MONSTER_FISH:
4002 begin
4003 SetState(MONSTATE_ATTACK);
4004 Result := True;
4005 end;
4006 MONSTER_DEMON:
4007 begin
4008 SetState(MONSTATE_ATTACK);
4009 g_Sound_PlayExAt('SOUND_MONSTER_DEMON_ATTACK', FObj.X, FObj.Y);
4010 Result := True;
4011 end;
4012 MONSTER_IMP:
4013 begin
4014 SetState(MONSTATE_ATTACK);
4015 g_Sound_PlayExAt('SOUND_MONSTER_IMP_ATTACK', FObj.X, FObj.Y);
4016 Result := True;
4017 end;
4018 MONSTER_SKEL, MONSTER_ROBO, MONSTER_CYBER:
4019 begin
4020 SetState(MONSTATE_ATTACK, ANIM_ATTACK2);
4021 g_Sound_PlayExAt('SOUND_MONSTER_SKEL_ATTACK', FObj.X, FObj.Y);
4022 Result := True;
4023 end;
4024 MONSTER_BARON, MONSTER_KNIGHT,
4025 MONSTER_CACO, MONSTER_MANCUB:
4026 // Эти монстры не пинают - они стреляют в упор:
4027 if not g_Game_IsClient then Result := shoot(o, True);
4028 end;
4029 end;
4031 function TMonster.shoot(o: PObj; immediately: Boolean): Boolean;
4032 var
4033 xd, yd, m: Integer;
4034 begin
4035 Result := False;
4037 // Стрелять рано:
4038 if FAmmo < 0 then
4039 Exit;
4041 // Ждать времени готовности к выстрелу:
4042 if not immediately then
4043 case FMonsterType of
4044 MONSTER_FISH, MONSTER_BARREL, MONSTER_DEMON:
4045 Exit; // не стреляют
4046 MONSTER_CGUN, MONSTER_BSP, MONSTER_ROBO:
4047 begin
4048 FAmmo := FAmmo + 1;
4049 // Время выстрела упущено:
4050 if FAmmo >= 50 then
4051 FAmmo := IfThen(FMonsterType = MONSTER_ROBO, -200, -50);
4052 end;
4053 MONSTER_MAN: ;
4054 MONSTER_MANCUB:
4055 begin
4056 FAmmo := FAmmo + 1;
4057 // Время выстрела упущено:
4058 if FAmmo >= 5 then
4059 FAmmo := -50;
4060 end;
4061 MONSTER_SPIDER:
4062 begin
4063 FAmmo := FAmmo + 1;
4064 // Время выстрела упущено:
4065 if FAmmo >= 100 then
4066 FAmmo := -50;
4067 end;
4068 MONSTER_CYBER:
4069 begin
4070 // Стреляет не всегда:
4071 if Random(2) = 0 then
4072 Exit;
4073 FAmmo := FAmmo + 1;
4074 // Время выстрела упущено:
4075 if FAmmo >= 10 then
4076 FAmmo := -50;
4077 end;
4078 MONSTER_BARON, MONSTER_KNIGHT: if Random(8) <> 0 then Exit;
4079 MONSTER_SKEL: if Random(32) <> 0 then Exit;
4080 MONSTER_VILE: if Random(8) <> 0 then Exit;
4081 MONSTER_PAIN: if Random(8) <> 0 then Exit;
4082 else if Random(16) <> 0 then Exit;
4083 end;
4085 // Цели не видно:
4086 if not g_Look(@FObj, o, FDirection) then
4087 Exit;
4089 FTargetTime := 0;
4091 tx := o^.X+o^.Rect.X+(o^.Rect.Width div 2)+((o^.Vel.X{+o^.Accel.X})*12);
4092 ty := o^.Y+o^.Rect.Y+(o^.Rect.Height div 2)+((o^.Vel.Y{+o^.Accel.Y})*12);
4094 // Разница по высоте больше разницы по горизонтали
4095 // (не может стрелять под таким большим углом):
4096 if Abs(tx-(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2))) <
4097 Abs(ty-(FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2))) then
4098 Exit;
4100 case FMonsterType of
4101 MONSTER_IMP, MONSTER_BARON, MONSTER_KNIGHT, MONSTER_CACO:
4102 begin
4103 SetState(MONSTATE_SHOOT);
4104 {nn}
4105 end;
4106 MONSTER_SKEL:
4107 begin
4108 SetState(MONSTATE_SHOOT);
4109 {nn}
4110 end;
4111 MONSTER_VILE:
4112 begin // Зажигаем огонь
4113 tx := o^.X+o^.Rect.X+(o^.Rect.Width div 2);
4114 ty := o^.Y+o^.Rect.Y;
4115 SetState(MONSTATE_SHOOT);
4117 vilefire.Reset();
4119 g_Sound_PlayExAt('SOUND_MONSTER_VILE_ATTACK', FObj.X, FObj.Y);
4120 g_Sound_PlayExAt('SOUND_FIRE', o^.X, o^.Y);
4121 end;
4122 MONSTER_SOUL:
4123 begin // Летит в сторону цели:
4124 SetState(MONSTATE_ATTACK);
4125 g_Sound_PlayExAt('SOUND_MONSTER_SOUL_ATTACK', FObj.X, FObj.Y);
4127 xd := tx-(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2));
4128 yd := ty-(FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2));
4129 m := Max(Abs(xd), Abs(yd));
4130 if m = 0 then
4131 m := 1;
4133 FObj.Vel.X := (xd*16) div m;
4134 FObj.Vel.Y := (yd*16) div m;
4135 end;
4136 MONSTER_MANCUB, MONSTER_ZOMBY, MONSTER_SERG, MONSTER_BSP, MONSTER_ROBO,
4137 MONSTER_CYBER, MONSTER_CGUN, MONSTER_SPIDER, MONSTER_PAIN, MONSTER_MAN:
4138 begin
4139 // Манкубус рявкает перед первой атакой:
4140 if FMonsterType = MONSTER_MANCUB then
4141 if FAmmo = 1 then
4142 g_Sound_PlayExAt('SOUND_MONSTER_MANCUB_ATTACK', FObj.X, FObj.Y);
4144 SetState(MONSTATE_SHOOT);
4145 end;
4146 else Exit;
4147 end;
4149 Result := True;
4150 end;
4152 function TMonster.alive(): Boolean;
4153 begin
4154 Result := (FState <> MONSTATE_DIE) and (FState <> MONSTATE_DEAD) and (FHealth > 0);
4155 end;
4157 procedure TMonster.SetHealth(aH: Integer);
4158 begin
4159 if (aH > 0) and (aH < 1000000) then
4160 begin
4161 FHealth := aH;
4162 if FHealth > FMaxHealth then
4163 FMaxHealth := FHealth;
4164 end;
4165 end;
4167 procedure TMonster.WakeUp();
4168 begin
4169 if g_Game_IsClient then Exit;
4170 SetState(MONSTATE_GO);
4171 FTargetTime := MAX_ATM;
4172 WakeUpSound();
4173 end;
4175 procedure TMonster.SaveState (st: TStream);
4176 var
4177 i: Integer;
4178 b: Byte;
4179 anim: Boolean;
4180 begin
4181 assert(st <> nil);
4183 // Сигнатура монстра:
4184 utils.writeSign(st, 'MONS');
4185 utils.writeInt(st, Byte(0)); // version
4186 // UID монстра:
4187 utils.writeInt(st, Word(FUID));
4188 // Направление
4189 if FDirection = TDirection.D_LEFT then b := 1 else b := 2; // D_RIGHT
4190 utils.writeInt(st, Byte(b));
4191 // Надо ли удалить его
4192 utils.writeBool(st, FRemoved);
4193 // Осталось здоровья
4194 utils.writeInt(st, LongInt(FHealth));
4195 // Состояние
4196 utils.writeInt(st, Byte(FState));
4197 // Текущая анимация
4198 utils.writeInt(st, Byte(FCurAnim));
4199 // UID цели
4200 utils.writeInt(st, Word(FTargetUID));
4201 // Время после потери цели
4202 utils.writeInt(st, LongInt(FTargetTime));
4203 // Поведение монстра
4204 utils.writeInt(st, Byte(FBehaviour));
4205 // Готовность к выстрелу
4206 utils.writeInt(st, LongInt(FAmmo));
4207 // Боль
4208 utils.writeInt(st, LongInt(FPain));
4209 // Время ожидания
4210 utils.writeInt(st, LongInt(FSleep));
4211 // Озвучивать ли боль
4212 utils.writeBool(st, FPainSound);
4213 // Была ли атака во время анимации атаки
4214 utils.writeBool(st, FWaitAttackAnim);
4215 // Надо ли стрелять на следующем шаге
4216 utils.writeBool(st, FChainFire);
4217 // Подлежит ли респавну
4218 utils.writeBool(st, FNoRespawn);
4219 // Координаты цели
4220 utils.writeInt(st, LongInt(tx));
4221 utils.writeInt(st, LongInt(ty));
4222 // ID монстра при старте карты
4223 utils.writeInt(st, LongInt(FStartID));
4224 // Индекс триггера, создавшего монстра
4225 utils.writeInt(st, LongInt(FSpawnTrigger));
4226 // Объект монстра
4227 Obj_SaveState(st, @FObj);
4228 // Есть ли анимация огня колдуна
4229 anim := (vilefire <> nil);
4230 utils.writeBool(st, anim);
4231 // Если есть - сохраняем:
4232 if anim then vilefire.SaveState(st, 0, False);
4233 // Анимации
4234 for i := ANIM_SLEEP to ANIM_PAIN do
4235 begin
4236 // Есть ли левая анимация
4237 anim := (FAnim[i, TDirection.D_LEFT] <> nil);
4238 utils.writeBool(st, anim);
4239 // Если есть - сохраняем
4240 if anim then FAnim[i, TDirection.D_LEFT].SaveState(st, 0, False);
4241 // Есть ли правая анимация
4242 anim := (FAnim[i, TDirection.D_RIGHT] <> nil);
4243 utils.writeBool(st, anim);
4244 // Если есть - сохраняем
4245 if anim then FAnim[i, TDirection.D_RIGHT].SaveState(st, 0, False);
4246 end;
4247 end;
4250 procedure TMonster.LoadState (st: TStream);
4251 var
4252 i: Integer;
4253 b, alpha: Byte;
4254 anim, blending: Boolean;
4255 begin
4256 assert(st <> nil);
4258 // Сигнатура монстра:
4259 if not utils.checkSign(st, 'MONS') then raise XStreamError.Create('invalid monster signature');
4260 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid monster version');
4261 if (uidMap[FUID] <> nil) and (uidMap[FUID] <> self) then raise Exception.Create('internal error in monster loader (0)');
4262 uidMap[FUID] := nil;
4263 // UID монстра:
4264 FUID := utils.readWord(st);
4265 //if (arrIdx = -1) then raise Exception.Create('internal error in monster loader');
4266 if (uidMap[FUID] <> nil) then raise Exception.Create('internal error in monster loader (1)');
4267 uidMap[FUID] := self;
4268 // Направление
4269 b := utils.readByte(st);
4270 if b = 1 then FDirection := TDirection.D_LEFT else FDirection := TDirection.D_RIGHT; // b = 2
4271 // Надо ли удалить его
4272 FRemoved := utils.readBool(st);
4273 // Осталось здоровья
4274 FHealth := utils.readLongInt(st);
4275 // Состояние
4276 FState := utils.readByte(st);
4277 // Текущая анимация
4278 FCurAnim := utils.readByte(st);
4279 // UID цели
4280 FTargetUID := utils.readWord(st);
4281 // Время после потери цели
4282 FTargetTime := utils.readLongInt(st);
4283 // Поведение монстра
4284 FBehaviour := utils.readByte(st);
4285 // Готовность к выстрелу
4286 FAmmo := utils.readLongInt(st);
4287 // Боль
4288 FPain := utils.readLongInt(st);
4289 // Время ожидания
4290 FSleep := utils.readLongInt(st);
4291 // Озвучивать ли боль
4292 FPainSound := utils.readBool(st);
4293 // Была ли атака во время анимации атаки
4294 FWaitAttackAnim := utils.readBool(st);
4295 // Надо ли стрелять на следующем шаге
4296 FChainFire := utils.readBool(st);
4297 // Подлежит ли респавну
4298 FNoRespawn := utils.readBool(st);
4299 // Координаты цели
4300 tx := utils.readLongInt(st);
4301 ty := utils.readLongInt(st);
4302 // ID монстра при старте карты
4303 FStartID := utils.readLongInt(st);
4304 // Индекс триггера, создавшего монстра
4305 FSpawnTrigger := utils.readLongInt(st);
4306 // Объект монстра
4307 Obj_LoadState(@FObj, st);
4308 // Есть ли анимация огня колдуна
4309 anim := utils.readBool(st);
4310 // Если есть - загружаем:
4311 if anim then
4312 begin
4313 Assert(vilefire <> nil, 'TMonster.LoadState: no vilefire anim');
4314 vilefire.LoadState(st, alpha, blending);
4315 end;
4316 // Анимации
4317 for i := ANIM_SLEEP to ANIM_PAIN do
4318 begin
4319 // Есть ли левая анимация
4320 anim := utils.readBool(st);
4321 // Если есть - загружаем
4322 if anim then
4323 begin
4324 Assert(FAnim[i, TDirection.D_LEFT] <> nil, 'TMonster.LoadState: no '+IntToStr(i)+'_left anim');
4325 FAnim[i, TDirection.D_LEFT].LoadState(st, alpha, blending);
4326 end;
4327 // Есть ли правая анимация
4328 anim := utils.readBool(st);
4329 // Если есть - загружаем
4330 if anim then
4331 begin
4332 Assert(FAnim[i, TDirection.D_RIGHT] <> nil, 'TMonster.LoadState: no '+IntToStr(i)+'_right anim');
4333 FAnim[i, TDirection.D_RIGHT].LoadState(st, alpha, blending);
4334 end;
4335 end;
4336 // update cache
4337 self.positionChanged
4338 end;
4341 procedure TMonster.ActivateTriggers();
4342 var
4343 a: Integer;
4344 begin
4345 if FDieTriggers <> nil then
4346 for a := 0 to High(FDieTriggers) do
4347 g_Triggers_Press(FDieTriggers[a], ACTIVATE_MONSTERPRESS);
4348 if FSpawnTrigger > -1 then
4349 begin
4350 g_Triggers_DecreaseSpawner(FSpawnTrigger);
4351 FSpawnTrigger := -1;
4352 end;
4353 end;
4355 procedure TMonster.AddTrigger(t: Integer);
4356 begin
4357 SetLength(FDieTriggers, Length(FDieTriggers)+1);
4358 FDieTriggers[High(FDieTriggers)] := t;
4359 end;
4361 procedure TMonster.ClearTriggers();
4362 begin
4363 SetLength(FDieTriggers, 0);
4364 end;
4366 procedure TMonster.CatchFire(Attacker: Word; Timeout: Integer = MON_BURN_TIME);
4367 begin
4368 if FMonsterType in [MONSTER_SOUL, MONSTER_VILE] then
4369 exit; // арчи не горят, черепа уже горят
4370 if Timeout <= 0 then exit;
4371 if g_Obj_CollidePanel(@FObj, 0, 0, PANEL_WATER or PANEL_ACID1 or PANEL_ACID2) then
4372 exit; // не подгораем в воде на всякий случай
4373 if FFireTime <= 0 then
4374 g_Sound_PlayExAt('SOUND_IGNITE', FObj.X, FObj.Y);
4375 FFireTime := Timeout;
4376 FFireAttacker := Attacker;
4377 if g_Game_IsNet and g_Game_IsServer then MH_SEND_MonsterState(FUID);
4378 end;
4380 procedure TMonster.OnFireFlame(Times: DWORD = 1);
4381 {$IFDEF ENABLE_GFX}
4382 var i: DWORD; x, y: Integer;
4383 {$ENDIF}
4384 begin
4385 {$IFDEF ENABLE_GFX}
4386 if (Random(10) = 1) and (Times = 1) then
4387 Exit;
4388 for i := 1 to Times do
4389 begin
4390 x := Obj.X + Obj.Rect.X + Random(Obj.Rect.Width + Times * 2) - (R_GFX_FLAME_WIDTH div 2);
4391 y := Obj.Y + 8 + Random(8 + Times * 2) + IfThen(FState = MONSTATE_DEAD, 16, 0);
4392 g_GFX_QueueEffect(R_GFX_FLAME, x, y);
4393 end;
4394 {$ENDIF}
4395 end;
4398 // ////////////////////////////////////////////////////////////////////////// //
4399 // throws on invalid uid
4400 function g_Mons_ByIdx (uid: Integer): TMonster; inline;
4401 begin
4402 result := g_Mons_ByIdx_NC(uid);
4403 if (result = nil) then raise Exception.Create('g_Mons_ByIdx: invalid monster id');
4404 end;
4406 // can return null
4407 function g_Mons_ByIdx_NC (uid: Integer): TMonster; inline;
4408 begin
4409 if (uid < 0) or (uid > High(gMonsters)) then begin result := nil; exit; end;
4410 result := gMonsters[uid];
4411 end;
4413 function g_Mons_TotalCount (): Integer; inline;
4414 begin
4415 result := Length(gMonsters);
4416 end;
4419 function g_Mons_ForEach (cb: TEachMonsterCB): Boolean;
4420 var
4421 idx: Integer;
4422 mon: TMonster;
4423 begin
4424 result := false;
4425 if (gMonsters = nil) or not assigned(cb) then exit;
4426 for idx := 0 to High(gMonsters) do
4427 begin
4428 mon := gMonsters[idx];
4429 if (mon <> nil) then
4430 begin
4431 result := cb(mon);
4432 if result then exit;
4433 end;
4434 end;
4435 end;
4438 function g_Mons_ForEachAlive (cb: TEachMonsterCB): Boolean;
4439 var
4440 idx: Integer;
4441 mon: TMonster;
4442 begin
4443 result := false;
4444 if (gMonsters = nil) or not assigned(cb) then exit;
4445 for idx := 0 to High(gMonsters) do
4446 begin
4447 mon := gMonsters[idx];
4448 if (mon <> nil) and mon.alive then
4449 begin
4450 result := cb(mon);
4451 if result then exit;
4452 end;
4453 end;
4454 end;
4457 function g_Mons_IsAnyAliveAt (x, y: Integer; width, height: Integer): Boolean;
4458 (*
4459 function monsCollCheck (mon: TMonster; atag: Integer): Boolean;
4460 begin
4461 result := mon.alive;// and g_Obj_Collide(x, y, width, height, @mon.Obj));
4462 end;
4463 *)
4464 var
4465 idx: Integer;
4466 mon: TMonster;
4467 mit: PMonster;
4468 it: TMonsterGrid.Iter;
4469 begin
4470 result := false;
4471 if (width < 1) or (height < 1) then exit;
4472 if gmon_debug_use_sqaccel then
4473 begin
4474 //result := (monsGrid.forEachInAABB(x, y, width, height, monsCollCheck) <> nil);
4475 it := monsGrid.forEachInAABB(x, y, width, height);
4476 for mit in it do if (mit.alive) then begin result := true; break; end;
4477 it.release();
4478 end
4479 else
4480 begin
4481 for idx := 0 to High(gMonsters) do
4482 begin
4483 mon := gMonsters[idx];
4484 if (mon <> nil) and mon.alive then
4485 begin
4486 if g_Obj_Collide(x, y, width, height, @mon.Obj) then
4487 begin
4488 result := true;
4489 exit;
4490 end;
4491 end;
4492 end;
4493 end;
4494 end;
4497 function g_Mons_ForEachAt (x, y: Integer; width, height: Integer; cb: TEachMonsterCB): Boolean;
4498 (*
4499 function monsCollCheck (mon: TMonster; atag: Integer): Boolean;
4500 begin
4501 result := cb(mon);
4502 end;
4503 *)
4504 var
4505 idx: Integer;
4506 mon: TMonster;
4507 mit: PMonster;
4508 it: TMonsterGrid.Iter;
4509 begin
4510 result := false;
4511 if (width < 1) or (height < 1) then exit;
4512 if gmon_debug_use_sqaccel then
4513 begin
4514 //result := (monsGrid.forEachInAABB(x, y, width, height, monsCollCheck) <> nil);
4515 it := monsGrid.forEachInAABB(x, y, width, height);
4516 for mit in it do if (cb(mit^)) then begin result := true; break; end;
4517 it.release();
4518 end
4519 else
4520 begin
4521 for idx := 0 to High(gMonsters) do
4522 begin
4523 mon := gMonsters[idx];
4524 if (mon <> nil) and mon.alive then
4525 begin
4526 if g_Obj_Collide(x, y, width, height, @mon.Obj) then
4527 begin
4528 result := cb(mon);
4529 if result then exit;
4530 end;
4531 end;
4532 end;
4533 end;
4534 end;
4537 function g_Mons_ForEachAliveAt (x, y: Integer; width, height: Integer; cb: TEachMonsterCB): Boolean;
4538 (*
4539 function monsCollCheck (mon: TMonster; atag: Integer): Boolean;
4540 begin
4541 //result := false;
4542 //if mon.alive and g_Obj_Collide(x, y, width, height, @mon.Obj) then result := cb(mon);
4543 if mon.alive then result := cb(mon) else result := false;
4544 end;
4545 *)
4546 var
4547 idx: Integer;
4548 mon: TMonster;
4549 mit: PMonster;
4550 it: TMonsterGrid.Iter;
4551 begin
4552 result := false;
4553 if (width < 1) or (height < 1) then exit;
4554 if gmon_debug_use_sqaccel then
4555 begin
4557 if (width = 1) and (height = 1) then
4558 begin
4559 result := (monsGrid.forEachAtPoint(x, y, monsCollCheck) <> nil);
4560 end
4561 else
4562 begin
4563 result := (monsGrid.forEachInAABB(x, y, width, height, monsCollCheck) <> nil);
4564 end;
4566 it := monsGrid.forEachInAABB(x, y, width, height);
4567 for mit in it do
4568 begin
4569 if (mit^.alive) then
4570 begin
4571 if (cb(mit^)) then begin result := true; break; end;
4572 end;
4573 end;
4574 it.release();
4575 end
4576 else
4577 begin
4578 for idx := 0 to High(gMonsters) do
4579 begin
4580 mon := gMonsters[idx];
4581 if (mon <> nil) and mon.alive then
4582 begin
4583 if g_Obj_Collide(x, y, width, height, @mon.Obj) then
4584 begin
4585 result := cb(mon);
4586 if result then exit;
4587 end;
4588 end;
4589 end;
4590 end;
4591 end;
4594 end.