X-Git-Url: http://deadsoftware.ru/gitweb?a=blobdiff_plain;f=src%2Fgame%2Fg_monsters.pas;h=36780ec4308c7f41d725717e48e6f7e04078b1cc;hb=8f8d15ad51eda2346afdafbb5827baee4bda83e3;hp=55a02104f4a56e602de638d507b13490936100cf;hpb=ae5dd5cd82a0c9548c27d2e646de1492e66d98df;p=d2df-sdl.git diff --git a/src/game/g_monsters.pas b/src/game/g_monsters.pas index 55a0210..36780ec 100644 --- a/src/game/g_monsters.pas +++ b/src/game/g_monsters.pas @@ -1,9 +1,8 @@ -(* Copyright (C) DooM 2D:Forever Developers +(* Copyright (C) Doom 2D: Forever Developers * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. + * the Free Software Foundation, version 3 of the License ONLY. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -14,13 +13,18 @@ * along with this program. If not, see . *) {$INCLUDE ../shared/a_modes.inc} +{$M+} +{.$DEFINE D2F_DEBUG_MONS_MOVE} unit g_monsters; interface uses - g_basic, e_graphics, g_phys, g_textures, - g_saveload, BinEditor, g_panel; + SysUtils, Classes, + mempool, + MAPDEF, + g_base, g_basic, g_phys, g_textures, g_grid, + g_saveload, g_panel, xprofiler; const MONSTATE_SLEEP = 0; @@ -36,15 +40,22 @@ const MONSTATE_REVIVE = 10; MONSTATE_RUNOUT = 11; + MON_BURN_TIME = 100; + +{ in mapdef now BH_NORMAL = 0; BH_KILLER = 1; BH_MANIAC = 2; BH_INSANE = 3; BH_CANNIBAL = 4; BH_GOOD = 5; +} type - TMonster = Class (TObject) + ADirectedAnim = Array of Array [TDirection.D_LEFT..TDirection.D_RIGHT] of TAnimation; + + PMonster = ^TMonster; + TMonster = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF} private FMonsterType: Byte; FUID: Word; @@ -56,7 +67,7 @@ type FMaxHealth: Integer; FState: Byte; FCurAnim: Byte; - FAnim: Array of Array [D_LEFT..D_RIGHT] of TAnimation; + FAnim: ADirectedAnim; FTargetUID: Word; FTargetTime: Integer; FBehaviour: Byte; @@ -79,21 +90,30 @@ type FFirePainTime: Integer; FFireAttacker: Word; vilefire: TAnimation; - - treeNode: Integer; // node in dyntree or -1 - arrIdx: Integer; // in gMonsters + mProxyId: Integer; // node in dyntree or -1 + mArrIdx: Integer; // in gMonsters FDieTriggers: Array of Integer; FSpawnTrigger: Integer; + mNeedSend: Boolean; // for network + + mEDamageType: Integer; + procedure Turn(); function findNewPrey(): Boolean; procedure ActivateTriggers(); + procedure setGameX (v: Integer); inline; + procedure setGameY (v: Integer); inline; + + procedure doDamage (v: Integer); + public FNoRespawn: Boolean; FFireTime: Integer; trapCheckFrameId: DWord; // for `g_weapons.CheckTrap()` + mplatCheckFrameId: LongWord; constructor Create(MonsterType: Byte; aID: Integer; ForcedUID: Integer = -1); destructor Destroy(); override; @@ -101,17 +121,17 @@ type function Collide(Panel: TPanel): Boolean; overload; function Collide(X, Y: Integer): Boolean; overload; function TeleportTo(X, Y: Integer; silent: Boolean; dir: Byte): Boolean; - function Live(): Boolean; + function alive(): Boolean; procedure SetHealth(aH: Integer); procedure Push(vx, vy: Integer); function Damage(aDamage: Word; VelX, VelY: Integer; SpawnerUID: Word; t: Byte): Boolean; function Heal(Value: Word): Boolean; procedure BFGHit(); + procedure PreUpdate(); procedure Update(); procedure ClientUpdate(); procedure ClientAttack(wx, wy, atx, aty: Integer); procedure SetDeadAnim; - procedure Draw(); procedure WakeUp(); procedure WakeUpSound(); procedure DieSound(); @@ -120,8 +140,8 @@ type procedure AddTrigger(t: Integer); procedure ClearTriggers(); procedure Respawn(); - procedure SaveState(var Mem: TBinMemoryWriter); - procedure LoadState(var Mem: TBinMemoryReader); + procedure SaveState (st: TStream); + procedure LoadState (st: TStream); procedure SetState(State: Byte; ForceAnim: Byte = 255); procedure MakeBloodVector(Count: Word; VelX, VelY: Integer); procedure MakeBloodSimple(Count: Word); @@ -129,11 +149,26 @@ type function AnimIsReverse: Boolean; function shoot(o: PObj; immediately: Boolean): Boolean; function kick(o: PObj): Boolean; - procedure CatchFire(Attacker: Word); + procedure CatchFire(Attacker: Word; Timeout: Integer = MON_BURN_TIME); procedure OnFireFlame(Times: DWORD = 1); procedure positionChanged (); //WARNING! call this after monster position was changed, or coldet will not work right! + procedure setPosition (ax, ay: Integer; callPosChanged: Boolean=true); inline; + procedure moveBy (dx, dy: Integer); inline; + + procedure getMapBox (out x, y, w, h: Integer); inline; + + // get-and-clear + function gncNeedSend (): Boolean; inline; + procedure setDirty (); inline; // why `dirty`? 'cause i may introduce property `needSend` later + + public + property Obj: TObj read FObj; + + property proxyId: Integer read mProxyId; + property arrIdx: Integer read mArrIdx; + property MonsterType: Byte read FMonsterType; property MonsterHealth: Integer read FHealth write FHealth; property MonsterAmmo: Integer read FAmmo write FAmmo; @@ -146,12 +181,11 @@ type property MonsterPain: Integer read FPain write FPain; property MonsterAnim: Byte read FCurAnim write FCurAnim; - property Obj: TObj read FObj; property UID: Word read FUID write FUID; property SpawnTrigger: Integer read FSpawnTrigger write FSpawnTrigger; - property GameX: Integer read FObj.X write FObj.X; - property GameY: Integer read FObj.Y write FObj.Y; + property GameX: Integer read FObj.X write setGameX; + property GameY: Integer read FObj.Y write setGameY; property GameVelX: Integer read FObj.Vel.X write FObj.Vel.X; property GameVelY: Integer read FObj.Vel.Y write FObj.Vel.Y; property GameAccelX: Integer read FObj.Accel.X write FObj.Accel.X; @@ -159,29 +193,71 @@ type property GameDirection: TDirection read FDirection write FDirection; property StartID: Integer read FStartID; + + property VileFireAnim: TAnimation read vilefire; + property DirAnim: ADirectedAnim read FAnim; + + published + property eMonsterType: Byte read FMonsterType; + property eMonsterHealth: Integer read FHealth write FHealth; + property eMonsterAmmo: Integer read FAmmo write FAmmo; + property eMonsterTargetUID: Word read FTargetUID write FTargetUID; + property eMonsterTargetTime: Integer read FTargetTime write FTargetTime; + property eMonsterBehaviour: Byte read FBehaviour write FBehaviour; + property eMonsterSleep: Integer read FSleep write FSleep; + property eMonsterState: Byte read FState write FState; + property eMonsterRemoved: Boolean read FRemoved; + property eMonsterPain: Integer read FPain write FPain; + property eMonsterAnim: Byte read FCurAnim; + + property eUID: Word read FUID; + property eSpawnTrigger: Integer read FSpawnTrigger; + + property eGameX: Integer read FObj.X write setGameX; + property eGameY: Integer read FObj.Y write setGameY; + property eGameVelX: Integer read FObj.Vel.X write FObj.Vel.X; + property eGameVelY: Integer read FObj.Vel.Y write FObj.Vel.Y; + property eGameAccelX: Integer read FObj.Accel.X write FObj.Accel.X; + property eGameAccelY: Integer read FObj.Accel.Y write FObj.Accel.Y; + property eGameDirection: TDirection read FDirection write FDirection; + + property eStartID: Integer read FStartID; + + // set this before assigning something to `eDamage` + property eDamageType: Integer read mEDamageType write mEDamageType; + property eDamage: Integer write doDamage; end; +// will be called from map loader +procedure g_Mons_InitTree (x, y, w, h: Integer); + procedure g_Monsters_LoadData (); procedure g_Monsters_FreeData (); procedure g_Monsters_Init (); -procedure g_Monsters_Free (); +procedure g_Monsters_Free (clearGrid: Boolean=true); function g_Monsters_Create (MonsterType: Byte; X, Y: Integer; Direction: TDirection; AdjCoord: Boolean = False; ForcedUID: Integer = -1): TMonster; +procedure g_Monsters_PreUpdate (); procedure g_Monsters_Update (); -procedure g_Monsters_Draw (); -procedure g_Monsters_DrawHealth (); function g_Monsters_ByUID (UID: Word): TMonster; procedure g_Monsters_killedp (); -procedure g_Monsters_SaveState (var Mem: TBinMemoryWriter); -procedure g_Monsters_LoadState (var Mem: TBinMemoryReader); -function g_Monsters_GetIDByName (name: String): Integer; -function g_Monsters_GetNameByID (MonsterType: Byte): String; -function g_Monsters_GetKilledBy (MonsterType: Byte): String; +procedure g_Monsters_SaveState (st: TStream); +procedure g_Monsters_LoadState (st: TStream); + +function g_Mons_SpawnAt (monType: Integer; x, y: Integer; dir: TDirection=TDirection.D_LEFT): TMonster; overload; +function g_Mons_SpawnAt (const typeName: AnsiString; x, y: Integer; dir: TDirection=TDirection.D_LEFT): TMonster; overload; + +function g_Mons_TypeLo (): Integer; inline; +function g_Mons_TypeHi (): Integer; inline; + +function g_Mons_TypeIdByName (const name: AnsiString): Integer; +function g_Mons_NameByTypeId (monType: Integer): AnsiString; +function g_Mons_GetKilledByTypeId (monType: Integer): AnsiString; type - TEachMonsterCB = function (monidx: Integer; mon: TMonster): Boolean is nested; // return `true` to stop + TEachMonsterCB = function (mon: TMonster): Boolean is nested; // return `true` to stop // throws on invalid uid function g_Mons_ByIdx (uid: Integer): TMonster; inline; @@ -189,6 +265,8 @@ function g_Mons_ByIdx (uid: Integer): TMonster; inline; // can return null function g_Mons_ByIdx_NC (uid: Integer): TMonster; inline; +function g_Mons_TotalCount (): Integer; inline; + function g_Mons_IsAnyAliveAt (x, y: Integer; width, height: Integer): Boolean; function g_Mons_ForEach (cb: TEachMonsterCB): Boolean; @@ -197,126 +275,57 @@ function g_Mons_ForEachAlive (cb: TEachMonsterCB): Boolean; function g_Mons_ForEachAt (x, y: Integer; width, height: Integer; cb: TEachMonsterCB): Boolean; function g_Mons_ForEachAliveAt (x, y: Integer; width, height: Integer; cb: TEachMonsterCB): Boolean; -function g_Mons_getNewTrapFrameId (): DWord; +function g_Mons_getNewTrapFrameId (): DWord; inline; +function g_Mons_getNewMPlatFrameId (): LongWord; inline; + + +{ +type + TMonsAlongLineCB = function (mon: TMonster; tag: Integer): Boolean is nested; + +function g_Mons_AlongLine (x0, y0, x1, y1: Integer; cb: TMonsAlongLineCB; log: Boolean=false): TMonster; +} var gmon_debug_use_sqaccel: Boolean = true; -implementation - -uses - e_log, g_main, g_sound, g_gfx, g_player, g_game, - g_weapons, g_triggers, MAPDEF, g_items, g_options, - g_console, g_map, Math, SysUtils, g_menu, wadreader, - g_language, g_netmsg, z_aabbtree; +//HACK! +procedure g_Mons_ProfilersBegin (); +procedure g_Mons_ProfilersEnd (); +procedure g_Mons_LOS_Start (); inline; +procedure g_Mons_LOS_End (); inline; -// ////////////////////////////////////////////////////////////////////////// // var - monCheckTrapLastFrameId: DWord; + profMonsLOS: TProfiler = nil; //WARNING: FOR DEBUGGING ONLY! -// ////////////////////////////////////////////////////////////////////////// // type - TDynAABBTreeMonsBase = specialize TDynAABBTreeBase; - - TDynAABBTreeMons = class(TDynAABBTreeMonsBase) - function getFleshAABB (out aabb: AABB2D; flesh: TMonster; tag: Integer): Boolean; override; - end; - -function TDynAABBTreeMons.getFleshAABB (out aabb: AABB2D; flesh: TMonster; tag: Integer): Boolean; -begin - result := false; - if (flesh = nil) then raise Exception.Create('DynTree: trying to get dimensions of inexistant monsters'); - if (flesh.Obj.Rect.Width < 1) or (flesh.Obj.Rect.Height < 1) then raise Exception.Create('DynTree: monster without size, wtf?!'); - aabb := AABB2D.CreateWH(flesh.Obj.X+flesh.Obj.Rect.X, flesh.Obj.Y+flesh.Obj.Rect.Y, flesh.Obj.Rect.Width, flesh.Obj.Rect.Height); - if not aabb.valid then raise Exception.Create('wutafuuuuuuu?!'); - result := true; -end; - + TMonsterGrid = specialize TBodyGridBase; var - monsTree: TDynAABBTreeMons = nil; + monsGrid: TMonsterGrid = nil; // DO NOT USE! public for debugging only! -//WARNING! call this after monster position was changed, or coldet will not work right! -procedure TMonster.positionChanged (); var - x, y: Integer; -begin - {$IF DEFINED(D2F_DEBUG)} - //e_WriteLog(Format('monster #%d(%u): pos=(%d,%d); rpos=(%d,%d)', [arrIdx, UID, FObj.X, FObj.Y, FObj.Rect.X, FObj.Rect.Y]), MSG_NOTIFY); - {$ENDIF} - if (treeNode = -1) then - begin - treeNode := monsTree.insertObject(self, 0); - {$IF DEFINED(D2F_DEBUG)} - monsTree.getNodeXY(treeNode, x, y); - e_WriteLog(Format('monster #%d(%u): inserted into the tree; nodeid=%d; x=%d; y=%d', [arrIdx, UID, treeNode, x, y]), MSG_NOTIFY); - {$ENDIF} - end - else - begin - monsTree.getNodeXY(treeNode, x, y); - if (FObj.X+FObj.Rect.X = x) and (FObj.Y+FObj.Rect.Y = y) then exit; // nothing to do - {$IF DEFINED(D2F_DEBUG)}e_WriteLog(Format('monster #%d(%u): updating tree; nodeid=%d; x=%d; y=%d', [arrIdx, UID, treeNode, x, y]), MSG_NOTIFY);{$ENDIF} - - {$IFDEF TRUE} - monsTree.updateObject(treeNode); - {$ELSE} - monsTree.removeObject(treeNode); - treeNode := monsTree.insertObject(self); - {$ENDIF} - - {$IF DEFINED(D2F_DEBUG)} - monsTree.getNodeXY(treeNode, x, y); - e_WriteLog(Format('monster #%d(%u): updated tree; nodeid=%d; x=%d; y=%d', [arrIdx, UID, treeNode, x, y]), MSG_NOTIFY); - {$ENDIF} - end; -end; - - -// ////////////////////////////////////////////////////////////////////////// // -const - ANIM_SLEEP = 0; - ANIM_GO = 1; - ANIM_DIE = 2; - ANIM_MESS = 3; - ANIM_ATTACK = 4; - ANIM_ATTACK2 = 5; - ANIM_PAIN = 6; - - STATE_SLEEP = 0; - STATE_GO = 1; - STATE_RUN = 2; - STATE_CLIMB = 3; - STATE_DIE = 4; - STATE_DEAD = 5; - STATE_ATTACK = 6; - STATE_SHOOT = 7; - STATE_PAIN = 8; - STATE_WAIT = 9; - STATE_REVIVE = 10; - STATE_RUNOUT = 11; - - MONSTER_SIGNATURE = $534E4F4D; // 'MONS' - -// Òàáëèöà òèïîâ àíèìàöèè ìîíñòðîâ: - ANIMTABLE: Array [ANIM_SLEEP..ANIM_PAIN] of - record - name: String; - loop: Boolean; - end = ((name: 'SLEEP'; loop: True), - (name: 'GO'; loop: True), - (name: 'DIE'; loop: False), - (name: 'MESS'; loop: False), - (name: 'ATTACK'; loop: False), - (name: 'ATTACK2'; loop: False), - (name: 'PAIN'; loop: False)); - -// Òàáëèöà õàðàêòåðèñòèê ìîíñòðîâ: + gmon_debug_think: Boolean = true; + gmon_debug_one_think_step: Boolean = false; + + var (* private state *) + gMonsters: array of TMonster; + + const + ANIM_SLEEP = 0; + ANIM_GO = 1; + ANIM_DIE = 2; + ANIM_MESS = 3; + ANIM_ATTACK = 4; + ANIM_ATTACK2 = 5; + ANIM_PAIN = 6; + +// Таблица характеристик монстров: MONSTERTABLE: Array [MONSTER_DEMON..MONSTER_MAN] of record Name: String; @@ -387,14 +396,14 @@ const (Name:'MAN'; Rect:(X:15; Y:6; Width:34; Height:52); Health:400; RunVel: 8; MinPain: 50; Pain: 70; Jump: 10)); -// Òàáëèöà ïàðàìåòðîâ àíèìàöèè ìîíñòðîâ: +// Таблица параметров анимации монстров: MONSTER_ANIMTABLE: Array [MONSTER_DEMON..MONSTER_MAN] of record LeftAnim: Boolean; - wX, wY: Integer; // Îòêóäà âûëåòèò ïóëÿ + wX, wY: Integer; // Откуда вылетит пуля AnimSpeed: Array [ANIM_SLEEP..ANIM_PAIN] of Byte; - AnimDeltaRight: Array [ANIM_SLEEP..ANIM_PAIN] of TPoint; - AnimDeltaLeft: Array [ANIM_SLEEP..ANIM_PAIN] of TPoint; + AnimDeltaRight: Array [ANIM_SLEEP..ANIM_PAIN] of TDFPoint; + AnimDeltaLeft: Array [ANIM_SLEEP..ANIM_PAIN] of TDFPoint; end = // SLEEP GO DIE MESS ATTACK ATTACK2 PAIN ((LeftAnim: False; wX: 54; wY: 32; AnimSpeed:(3, 2, 3, 2, 3, 0, 4); //DEMON 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)); @@ -404,11 +413,11 @@ const 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)); 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))), - (LeftAnim: False; wX: 32; wY: 32; AnimSpeed:(3, 2, 3, 2, 3, 0, 4); //ZOMBY + (LeftAnim: True; wX: 32; wY: 32; AnimSpeed:(3, 2, 3, 2, 3, 0, 4); //ZOMBY 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)); 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))), - (LeftAnim: False; wX: 32; wY: 32; AnimSpeed:(3, 2, 3, 2, 3, 0, 4); //SERG + (LeftAnim: True; wX: 32; wY: 32; AnimSpeed:(3, 2, 3, 2, 3, 0, 4); //SERG 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)); 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))), @@ -476,13 +485,165 @@ const 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)); 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))) ); - MAX_ATM = 89; // Âðåìÿ îæèäàíèÿ ïîñëå ïîòåðè öåëè - MAX_SOUL = 512; // Îãðàíè÷åíèå Lost_Soul'îâ +implementation + +uses + e_log, e_texture, g_main, g_sound, g_gfx, g_player, g_game, + g_weapons, g_triggers, g_items, g_options, + g_console, g_map, Math, g_menu, wadreader, + g_language, g_netmsg, idpool, utils, xstreams; + + +function g_Look(a, b: PObj; d: TDirection): Boolean; +begin + if not gmon_dbg_los_enabled then begin result := false; exit; end; // always "wall hit" + + if ((b^.X > a^.X) and (d = TDirection.D_LEFT)) or + ((b^.X < a^.X) and (d = TDirection.D_RIGHT)) then + begin + Result := False; + Exit; + end; + + Result := g_TraceVector(a^.X+a^.Rect.X+(a^.Rect.Width div 2), + a^.Y+a^.Rect.Y+(a^.Rect.Height div 2), + b^.X+b^.Rect.X+(b^.Rect.Width div 2), + b^.Y+b^.Rect.Y+(b^.Rect.Height div 2)); +end; + +// ////////////////////////////////////////////////////////////////////////// // +procedure g_Mons_ProfilersBegin (); +begin + if (profMonsLOS = nil) then profMonsLOS := TProfiler.Create('LOS CALC', g_profile_history_size); + if (profMonsLOS <> nil) then profMonsLOS.mainBegin(g_profile_los); + if g_profile_los and (profMonsLOS <> nil) then + begin + profMonsLOS.sectionBegin('loscalc'); + profMonsLOS.sectionEnd(); + end; +end; + +procedure g_Mons_ProfilersEnd (); +begin + if (profMonsLOS <> nil) and (g_profile_los) then profMonsLOS.mainEnd(); +end; + +procedure g_Mons_LOS_Start (); inline; +begin + if (profMonsLOS <> nil) then profMonsLOS.sectionBeginAccum('loscalc'); +end; + +procedure g_Mons_LOS_End (); inline; +begin + if (profMonsLOS <> nil) then profMonsLOS.sectionEnd(); +end; + + +// ////////////////////////////////////////////////////////////////////////// // +var + monCheckTrapLastFrameId: DWord = 0; + monCheckMPlatLastFrameId: LongWord = 0; + + +procedure TMonster.getMapBox (out x, y, w, h: Integer); inline; +begin + x := FObj.X+FObj.Rect.X; + y := FObj.Y+FObj.Rect.Y; + w := FObj.Rect.Width; + h := FObj.Rect.Height; +end; + +function TMonster.gncNeedSend (): Boolean; inline; begin result := mNeedSend; mNeedSend := false; end; + +procedure TMonster.setDirty (); inline; begin mNeedSend := true; end; + + +// ////////////////////////////////////////////////////////////////////////// // +{ +function g_Mons_AlongLine (x0, y0, x1, y1: Integer; cb: TMonsAlongLineCB; log: Boolean=false): TMonster; +begin + if not assigned(cb) then begin result := nil; exit; end; + result := monsGrid.forEachAlongLine(x0, y0, x1, y1, cb, -1, log); +end; +} + + +//WARNING! call this after monster position was changed, or coldet will not work right! +procedure TMonster.positionChanged (); +var + x, y, w, h: Integer; + nx, ny, nw, nh: Integer; +begin + {$IF DEFINED(D2F_DEBUG_MONS_MOVE)} + //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); + {$ENDIF} + if (mProxyId = -1) then + begin + //mNeedSend := true; + mProxyId := monsGrid.insertBody(self, FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, FObj.Rect.Width, FObj.Rect.Height); + {$IF DEFINED(D2F_DEBUG_MONS_MOVE)} + monsGrid.getBodyXY(mProxyId, x, y); + 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); + {$ENDIF} + end + else + begin + monsGrid.getBodyDims(mProxyId, x, y, w, h); + getMapBox(nx, ny, nw, nh); + + if (w <> nw) or (h <> nh) then + begin + //mNeedSend := true; + {$IF DEFINED(D2F_DEBUG_MONS_MOVE)} + e_WriteLog(Format('monster #%d:(%u): resized; mProxyid=%d; gx=%d; gy=%d', [mArrIdx, UID, mProxyId, x-monsGrid.gridX0, y-monsGrid.gridY0]), MSG_NOTIFY); + {$ENDIF} + monsGrid.moveResizeBody(mProxyId, nx, ny, nw, nh); + end + else if (x <> nx) or (y <> ny) then + begin + //mNeedSend := true; + {$IF DEFINED(D2F_DEBUG_MONS_MOVE)} + 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); + {$ENDIF} + monsGrid.moveBody(mProxyId, nx, ny); + end + else + begin + exit; // nothing to do + end; + {$IF DEFINED(D2F_DEBUG_MONS_MOVE)} + monsGrid.getBodyXY(mProxyId, x, y); + 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); + {$ENDIF} + end; +end; +// ////////////////////////////////////////////////////////////////////////// // +const + MONSTER_SIGNATURE = $534E4F4D; // 'MONS' + +// Таблица типов анимации монстров: + ANIMTABLE: Array [ANIM_SLEEP..ANIM_PAIN] of + record + name: String; + loop: Boolean; + end = ((name: 'SLEEP'; loop: True), + (name: 'GO'; loop: True), + (name: 'DIE'; loop: False), + (name: 'MESS'; loop: False), + (name: 'ATTACK'; loop: False), + (name: 'ATTACK2'; loop: False), + (name: 'PAIN'; loop: False)); + + MAX_ATM = 89; // Время ожидания после потери цели + MAX_SOUL = 512; // Ограничение Lost_Soul'ов + + +// ////////////////////////////////////////////////////////////////////////// // var - gMonsters: array of TMonster; uidMap: array [0..65535] of TMonster; // monster knows it's index + freeInds: TIdPool = nil; procedure clearUidMap (); @@ -490,15 +651,16 @@ var idx: Integer; begin for idx := 0 to High(uidMap) do uidMap[idx] := nil; + freeInds.clear(); end; -function g_Mons_getNewTrapFrameId (): DWord; +function g_Mons_getNewTrapFrameId (): DWord; inline; var f: Integer; begin Inc(monCheckTrapLastFrameId); - if monCheckTrapLastFrameId = 0 then + if (monCheckTrapLastFrameId = 0) then begin // wraparound monCheckTrapLastFrameId := 1; @@ -511,6 +673,24 @@ begin end; +function g_Mons_getNewMPlatFrameId (): LongWord; inline; +var + f: Integer; +begin + Inc(monCheckMPlatLastFrameId); + if (monCheckMPlatLastFrameId = 0) then + begin + // wraparound + monCheckMPlatLastFrameId := 1; + for f := 0 to High(gMonsters) do + begin + if (gMonsters[f] <> nil) then gMonsters[f].mplatCheckFrameId := 0; + end; + end; + result := monCheckMPlatLastFrameId; +end; + + var pt_x: Integer = 0; pt_xs: Integer = 1; @@ -519,29 +699,16 @@ var soulcount: Integer = 0; -function allocMonster(): DWORD; +function allocMonster (): DWORD; var - i, olen: Integer; + f, olen: Integer; begin - for i := 0 to High(gMonsters) do - begin - if (gMonsters[i] = nil) then - begin - result := i; - exit; - end; - end; - - olen := Length(gMonsters); - if (olen = 0) then - begin - SetLength(gMonsters, 64); - result := 0; - end - else + result := freeInds.alloc(); + if (result > High(gMonsters)) then begin - result := olen; - SetLength(gMonsters, Length(gMonsters)+32); + olen := Length(gMonsters); + SetLength(gMonsters, result+64); + for f := olen to High(gMonsters) do gMonsters[f] := nil; end; end; @@ -550,29 +717,30 @@ function IsFriend(a, b: Byte): Boolean; begin Result := True; -// Áî÷êà - âñåì äðóã: +// Бочка - всем друг: if (a = MONSTER_BARREL) or (b = MONSTER_BARREL) then Exit; -// Ìîíñòðû îäíîãî âèäà: +// Монстры одного вида: if a = b then case a of MONSTER_IMP, MONSTER_DEMON, MONSTER_BARON, MONSTER_KNIGHT, MONSTER_CACO, MONSTER_SOUL, MONSTER_PAIN, MONSTER_MANCUB, MONSTER_SKEL, MONSTER_FISH: - Exit; // Ýòè íå áüþò ñâîèõ + Exit; // Эти не бьют своих end; -// Lost_Soul íå ìîæåò ðàíèòü Pain_Elemental'à: +// Lost_Soul не может ранить Pain_Elemental'а: if (a = MONSTER_SOUL) and (b = MONSTER_PAIN) then Exit; -// Pain_Elemental íå ìîæåò ðàíèòü Lost_Soul'à: +// Pain_Elemental не может ранить Lost_Soul'а: if (b = MONSTER_SOUL) and (a = MONSTER_PAIN) then Exit; -//  îñòàëüíûõ ñëó÷àÿõ - áóäóò áèòü äðóã äðóãà: +// В остальных случаях - будут бить друг друга: Result := False; end; + function BehaviourDamage(SpawnerUID: Word; BH, SelfType: Byte): Boolean; var m: TMonster; @@ -602,6 +770,7 @@ begin end; end; + function canShoot(m: Byte): Boolean; begin Result := False; @@ -614,47 +783,66 @@ begin end; end; -function isCorpse(o: PObj; immediately: Boolean): Integer; +function isCorpse (o: PObj; immediately: Boolean): Integer; + + (* function monsCollCheck (mon: TMonster; atag: Integer): Boolean; begin + atag := atag; // shut up, fpc! result := false; // don't stop - if (mon.FState = STATE_DEAD) and g_Obj_Collide(o, @mon.FObj) then + if (mon.FState = MONSTATE_DEAD) and g_Obj_Collide(o, @mon.FObj) then begin - case mon.FMonsterType of // Íå âîñêðåñèòü: + case mon.FMonsterType of // Не воскресить: MONSTER_SOUL, MONSTER_PAIN, MONSTER_CYBER, MONSTER_SPIDER, MONSTER_VILE, MONSTER_BARREL, MONSTER_ROBO: exit; end; - // Îñòàëüíûõ ìîæíî âîñêðåñèòü + // Остальных можно воскресить result := true; end; end; + *) var a: Integer; - mon: TMonster; + mon: PMonster; + mres: TMonster = nil; + it: TMonsterGrid.Iter; begin result := -1; - // Åñëè íóæíà âåðîÿòíîñòü + // Если нужна вероятность if not immediately and (Random(8) <> 0) then exit; - // Èùåì ìåðòâûõ ìîíñòðîâ ïîáëèçîñòè + // Ищем мертвых монстров поблизости if gmon_debug_use_sqaccel then begin - mon := monsTree.aabbQuery(o.X+o.Rect.X, o.Y+o.Rect.Y, o.Rect.Width, o.Rect.Height, monsCollCheck); - if (mon <> nil) then result := mon.arrIdx; + //mon := monsGrid.forEachInAABB(o.X+o.Rect.X, o.Y+o.Rect.Y, o.Rect.Width, o.Rect.Height, monsCollCheck); + //if (mon <> nil) then result := mon.mArrIdx; + it := monsGrid.forEachInAABB(o.X+o.Rect.X, o.Y+o.Rect.Y, o.Rect.Width, o.Rect.Height); + for mon in it do + begin + case mon.FMonsterType of // Не воскресить: + MONSTER_SOUL, MONSTER_PAIN, MONSTER_CYBER, MONSTER_SPIDER, + MONSTER_VILE, MONSTER_BARREL, MONSTER_ROBO: begin end; + // Остальных можно воскресить + else mres := mon^; + end; + if (mres <> nil) then break; + end; + it.release(); + if (mres <> nil) then result := mres.mArrIdx; end else begin for a := 0 to High(gMonsters) do begin - if (gMonsters[a] <> nil) and (gMonsters[a].FState = STATE_DEAD) and g_Obj_Collide(o, @gMonsters[a].FObj) then + if (gMonsters[a] <> nil) and (gMonsters[a].FState = MONSTATE_DEAD) and g_Obj_Collide(o, @gMonsters[a].FObj) then begin - case gMonsters[a].FMonsterType of // Íå âîñêðåñèòü: + case gMonsters[a].FMonsterType of // Не воскресить: MONSTER_SOUL, MONSTER_PAIN, MONSTER_CYBER, MONSTER_SPIDER, MONSTER_VILE, MONSTER_BARREL, MONSTER_ROBO: Continue; - else // Îñòàëüíûõ ìîæíî âîñêðåñèòü + else // Остальных можно воскресить begin Result := a; Exit; @@ -667,160 +855,164 @@ end; procedure g_Monsters_LoadData(); begin - e_WriteLog('Loading monsters data...', MSG_NOTIFY); + e_WriteLog('Loading monsters data...', TMsgType.Notify); - g_Game_SetLoadingText(_lc[I_LOAD_MONSTER_TEXTURES]+' 0%', 0, False); + g_Game_SetLoadingText(_lc[I_LOAD_MONSTER_TEXTURES], 133, False); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_BARREL_SLEEP', GameWAD+':MTEXTURES\BARREL_SLEEP', 64, 64, 3); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_BARREL_DIE', GameWAD+':MTEXTURES\BARREL_DIE', 64, 64, 4); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_BARREL_PAIN', GameWAD+':MTEXTURES\BARREL_PAIN', 64, 64, 1); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_ZOMBY_SLEEP', GameWAD+':MTEXTURES\ZOMBY_SLEEP', 64, 64, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_ZOMBY_GO', GameWAD+':MTEXTURES\ZOMBY_GO', 64, 64, 4); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_ZOMBY_DIE', GameWAD+':MTEXTURES\ZOMBY_DIE', 64, 64, 6); - g_Game_SetLoadingText(_lc[I_LOAD_MONSTER_TEXTURES]+' 5%', 0, True); + g_Game_StepLoading(6); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_ZOMBY_MESS', GameWAD+':MTEXTURES\ZOMBY_MESS', 64, 64, 9); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_ZOMBY_ATTACK', GameWAD+':MTEXTURES\ZOMBY_ATTACK', 64, 64, 2); + g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_ZOMBY_ATTACK_L', GameWAD+':MTEXTURES\ZOMBY_ATTACK_L', 64, 64, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_ZOMBY_PAIN', GameWAD+':MTEXTURES\ZOMBY_PAIN', 64, 64, 1); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_SERG_SLEEP', GameWAD+':MTEXTURES\SERG_SLEEP', 64, 64, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_SERG_GO', GameWAD+':MTEXTURES\SERG_GO', 64, 64, 4); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_SERG_DIE', GameWAD+':MTEXTURES\SERG_DIE', 64, 64, 5); - g_Game_SetLoadingText(_lc[I_LOAD_MONSTER_TEXTURES]+' 10%', 0, True); + g_Game_StepLoading(13); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_SERG_MESS', GameWAD+':MTEXTURES\SERG_MESS', 64, 64, 9); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_SERG_ATTACK', GameWAD+':MTEXTURES\SERG_ATTACK', 64, 64, 2); + g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_SERG_ATTACK_L', GameWAD+':MTEXTURES\SERG_ATTACK_L', 64, 64, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_SERG_PAIN', GameWAD+':MTEXTURES\SERG_PAIN', 64, 64, 1); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_MAN_SLEEP', GameWAD+':MTEXTURES\MAN_SLEEP', 64, 64, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_MAN_GO', GameWAD+':MTEXTURES\MAN_GO', 64, 64, 4); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_MAN_DIE', GameWAD+':MTEXTURES\MAN_DIE', 64, 64, 7); - g_Game_SetLoadingText(_lc[I_LOAD_MONSTER_TEXTURES]+' 15%', 0, True); + g_Game_StepLoading(20); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_MAN_MESS', GameWAD+':MTEXTURES\MAN_MESS', 64, 64, 9); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_MAN_ATTACK', GameWAD+':MTEXTURES\MAN_ATTACK', 64, 64, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_MAN_PAIN', GameWAD+':MTEXTURES\MAN_PAIN', 64, 64, 1); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_CGUN_SLEEP', GameWAD+':MTEXTURES\CGUN_SLEEP', 64, 64, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_CGUN_SLEEP_L', GameWAD+':MTEXTURES\CGUN_SLEEP_L', 64, 64, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_CGUN_GO', GameWAD+':MTEXTURES\CGUN_GO', 64, 64, 4); - g_Game_SetLoadingText(_lc[I_LOAD_MONSTER_TEXTURES]+' 20%', 0, True); + g_Game_StepLoading(26); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_CGUN_GO_L', GameWAD+':MTEXTURES\CGUN_GO_L', 64, 64, 4); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_CGUN_DIE', GameWAD+':MTEXTURES\CGUN_DIE', 64, 64, 7); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_CGUN_MESS', GameWAD+':MTEXTURES\CGUN_MESS', 64, 64, 6); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_CGUN_ATTACK', GameWAD+':MTEXTURES\CGUN_ATTACK', 64, 64, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_CGUN_ATTACK_L', GameWAD+':MTEXTURES\CGUN_ATTACK_L', 64, 64, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_CGUN_PAIN', GameWAD+':MTEXTURES\CGUN_PAIN', 64, 64, 1); - g_Game_SetLoadingText(_lc[I_LOAD_MONSTER_TEXTURES]+' 25%', 0, True); + g_Game_StepLoading(32); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_CGUN_PAIN_L', GameWAD+':MTEXTURES\CGUN_PAIN_L', 64, 64, 1); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_IMP_SLEEP', GameWAD+':MTEXTURES\IMP_SLEEP', 64, 64, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_IMP_GO', GameWAD+':MTEXTURES\IMP_GO', 64, 64, 4); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_IMP_DIE', GameWAD+':MTEXTURES\IMP_DIE', 64, 64, 5); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_IMP_MESS', GameWAD+':MTEXTURES\IMP_MESS', 64, 64, 8); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_IMP_ATTACK', GameWAD+':MTEXTURES\IMP_ATTACK', 64, 64, 3); - g_Game_SetLoadingText(_lc[I_LOAD_MONSTER_TEXTURES]+' 30%', 0, True); + g_Game_StepLoading(38); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_IMP_PAIN', GameWAD+':MTEXTURES\IMP_PAIN', 64, 64, 1); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_DEMON_SLEEP', GameWAD+':MTEXTURES\DEMON_SLEEP', 64, 64, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_DEMON_GO', GameWAD+':MTEXTURES\DEMON_GO', 64, 64, 4); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_DEMON_DIE', GameWAD+':MTEXTURES\DEMON_DIE', 64, 64, 6); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_DEMON_ATTACK', GameWAD+':MTEXTURES\DEMON_ATTACK', 64, 64, 3); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_DEMON_PAIN', GameWAD+':MTEXTURES\DEMON_PAIN', 64, 64, 1); - g_Game_SetLoadingText(_lc[I_LOAD_MONSTER_TEXTURES]+' 35%', 0, True); + g_Game_StepLoading(44); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_SOUL_SLEEP', GameWAD+':MTEXTURES\SOUL_SLEEP', 64, 64, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_SOUL_GO', GameWAD+':MTEXTURES\SOUL_GO', 64, 64, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_SOUL_PAIN', GameWAD+':MTEXTURES\SOUL_PAIN', 64, 64, 1); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_SOUL_ATTACK', GameWAD+':MTEXTURES\SOUL_ATTACK', 64, 64, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_SOUL_DIE', GameWAD+':MTEXTURES\SOUL_DIE', 128, 128, 7); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_FISH_SLEEP', GameWAD+':MTEXTURES\FISH_SLEEP', 32, 32, 2); - g_Game_SetLoadingText(_lc[I_LOAD_MONSTER_TEXTURES]+' 40%', 0, True); + g_Game_StepLoading(50); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_FISH_GO', GameWAD+':MTEXTURES\FISH_GO', 32, 32, 4); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_FISH_PAIN', GameWAD+':MTEXTURES\FISH_PAIN', 32, 32, 3); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_FISH_ATTACK', GameWAD+':MTEXTURES\FISH_ATTACK', 32, 32, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_FISH_DIE', GameWAD+':MTEXTURES\FISH_DIE', 32, 32, 1); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_SPIDER_SLEEP', GameWAD+':MTEXTURES\SPIDER_SLEEP', 256, 128, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_SPIDER_GO', GameWAD+':MTEXTURES\SPIDER_GO', 256, 128, 6); - g_Game_SetLoadingText(_lc[I_LOAD_MONSTER_TEXTURES]+' 45%', 0, True); + g_Game_StepLoading(56); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_SPIDER_PAIN', GameWAD+':MTEXTURES\SPIDER_PAIN', 256, 128, 1); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_SPIDER_PAIN_L', GameWAD+':MTEXTURES\SPIDER_PAIN_L', 256, 128, 1); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_SPIDER_ATTACK', GameWAD+':MTEXTURES\SPIDER_ATTACK', 256, 128, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_SPIDER_DIE', GameWAD+':MTEXTURES\SPIDER_DIE', 256, 128, 10); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_BSP_SLEEP', GameWAD+':MTEXTURES\BSP_SLEEP', 128, 64, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_BSP_GO', GameWAD+':MTEXTURES\BSP_GO', 128, 64, 6); - g_Game_SetLoadingText(_lc[I_LOAD_MONSTER_TEXTURES]+' 50%', 0, True); + g_Game_StepLoading(62); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_BSP_PAIN', GameWAD+':MTEXTURES\BSP_PAIN', 128, 64, 1); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_BSP_PAIN_L', GameWAD+':MTEXTURES\BSP_PAIN_L', 128, 64, 1); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_BSP_ATTACK', GameWAD+':MTEXTURES\BSP_ATTACK', 128, 64, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_BSP_DIE', GameWAD+':MTEXTURES\BSP_DIE', 128, 64, 7); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_CACO_SLEEP', GameWAD+':MTEXTURES\CACO_SLEEP', 128, 128, 1); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_CACO_GO', GameWAD+':MTEXTURES\CACO_GO', 128, 128, 1); - g_Game_SetLoadingText(_lc[I_LOAD_MONSTER_TEXTURES]+' 55%', 0, True); + g_Game_StepLoading(68); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_CACO_PAIN', GameWAD+':MTEXTURES\CACO_PAIN', 128, 128, 1); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_CACO_ATTACK', GameWAD+':MTEXTURES\CACO_ATTACK', 128, 128, 6); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_CACO_DIE', GameWAD+':MTEXTURES\CACO_DIE', 128, 128, 7); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_PAIN_SLEEP', GameWAD+':MTEXTURES\PAIN_SLEEP', 128, 128, 4); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_PAIN_GO', GameWAD+':MTEXTURES\PAIN_GO', 128, 128, 4); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_PAIN_PAIN', GameWAD+':MTEXTURES\PAIN_PAIN', 128, 128, 1); - g_Game_SetLoadingText(_lc[I_LOAD_MONSTER_TEXTURES]+' 60%', 0, True); + g_Game_StepLoading(74); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_PAIN_ATTACK', GameWAD+':MTEXTURES\PAIN_ATTACK', 128, 128, 4); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_PAIN_DIE', GameWAD+':MTEXTURES\PAIN_DIE', 128, 128, 7); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_BARON_SLEEP', GameWAD+':MTEXTURES\BARON_SLEEP', 128, 128, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_BARON_SLEEP_L', GameWAD+':MTEXTURES\BARON_SLEEP_L', 128, 128, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_BARON_GO', GameWAD+':MTEXTURES\BARON_GO', 128, 128, 4); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_BARON_PAIN', GameWAD+':MTEXTURES\BARON_PAIN', 128, 128, 1); - g_Game_SetLoadingText(_lc[I_LOAD_MONSTER_TEXTURES]+' 65%', 0, True); + g_Game_StepLoading(80); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_BARON_PAIN_L', GameWAD+':MTEXTURES\BARON_PAIN_L', 128, 128, 1); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_BARON_ATTACK', GameWAD+':MTEXTURES\BARON_ATTACK', 128, 128, 3); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_BARON_ATTACK_L', GameWAD+':MTEXTURES\BARON_ATTACK_L', 128, 128, 3); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_BARON_DIE', GameWAD+':MTEXTURES\BARON_DIE', 128, 128, 7); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_KNIGHT_SLEEP', GameWAD+':MTEXTURES\KNIGHT_SLEEP', 128, 128, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_KNIGHT_SLEEP_L', GameWAD+':MTEXTURES\KNIGHT_SLEEP_L', 128, 128, 2); - g_Game_SetLoadingText(_lc[I_LOAD_MONSTER_TEXTURES]+' 70%', 0, True); + g_Game_StepLoading(86); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_KNIGHT_GO', GameWAD+':MTEXTURES\KNIGHT_GO', 128, 128, 4); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_KNIGHT_PAIN', GameWAD+':MTEXTURES\KNIGHT_PAIN', 128, 128, 1); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_KNIGHT_PAIN_L', GameWAD+':MTEXTURES\KNIGHT_PAIN_L', 128, 128, 1); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_KNIGHT_ATTACK', GameWAD+':MTEXTURES\KNIGHT_ATTACK', 128, 128, 3); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_KNIGHT_ATTACK_L', GameWAD+':MTEXTURES\KNIGHT_ATTACK_L', 128, 128, 3); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_KNIGHT_DIE', GameWAD+':MTEXTURES\KNIGHT_DIE', 128, 128, 7); - g_Game_SetLoadingText(_lc[I_LOAD_MONSTER_TEXTURES]+' 75%', 0, True); + g_Game_StepLoading(92); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_MANCUB_SLEEP', GameWAD+':MTEXTURES\MANCUB_SLEEP', 128, 128, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_MANCUB_GO', GameWAD+':MTEXTURES\MANCUB_GO', 128, 128, 6); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_MANCUB_PAIN', GameWAD+':MTEXTURES\MANCUB_PAIN', 128, 128, 1); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_MANCUB_ATTACK', GameWAD+':MTEXTURES\MANCUB_ATTACK', 128, 128, 3); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_MANCUB_DIE', GameWAD+':MTEXTURES\MANCUB_DIE', 128, 128, 10); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_SKEL_SLEEP', GameWAD+':MTEXTURES\SKEL_SLEEP', 128, 128, 2); - g_Game_SetLoadingText(_lc[I_LOAD_MONSTER_TEXTURES]+' 80%', 0, True); + g_Game_StepLoading(98); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_SKEL_SLEEP_L', GameWAD+':MTEXTURES\SKEL_SLEEP_L', 128, 128, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_SKEL_GO', GameWAD+':MTEXTURES\SKEL_GO', 128, 128, 6); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_SKEL_PAIN', GameWAD+':MTEXTURES\SKEL_PAIN', 128, 128, 1); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_SKEL_PAIN_L', GameWAD+':MTEXTURES\SKEL_PAIN_L', 128, 128, 1); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_SKEL_ATTACK', GameWAD+':MTEXTURES\SKEL_ATTACK', 128, 128, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_SKEL_ATTACK_L', GameWAD+':MTEXTURES\SKEL_ATTACK_L', 128, 128, 2); - g_Game_SetLoadingText(_lc[I_LOAD_MONSTER_TEXTURES]+' 85%', 0, True); + g_Game_StepLoading(104); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_SKEL_ATTACK2', GameWAD+':MTEXTURES\SKEL_ATTACK2', 128, 128, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_SKEL_ATTACK2_L', GameWAD+':MTEXTURES\SKEL_ATTACK2_L', 128, 128, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_SKEL_DIE', GameWAD+':MTEXTURES\SKEL_DIE', 128, 128, 5); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_VILE_SLEEP', GameWAD+':MTEXTURES\VILE_SLEEP', 128, 128, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_VILE_SLEEP_L', GameWAD+':MTEXTURES\VILE_SLEEP_L', 128, 128, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_VILE_GO', GameWAD+':MTEXTURES\VILE_GO', 128, 128, 6); - g_Game_SetLoadingText(_lc[I_LOAD_MONSTER_TEXTURES]+' 90%', 0, True); + g_Game_StepLoading(110); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_VILE_PAIN', GameWAD+':MTEXTURES\VILE_PAIN', 128, 128, 1); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_VILE_PAIN_L', GameWAD+':MTEXTURES\VILE_PAIN_L', 128, 128, 1); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_VILE_ATTACK', GameWAD+':MTEXTURES\VILE_ATTACK', 128, 128, 10); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_VILE_ATTACK_L', GameWAD+':MTEXTURES\VILE_ATTACK_L', 128, 128, 10); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_VILE_ATTACK2', GameWAD+':MTEXTURES\VILE_ATTACK2', 128, 128, 3); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_VILE_ATTACK2_L', GameWAD+':MTEXTURES\VILE_ATTACK2_L', 128, 128, 3); - g_Game_SetLoadingText(_lc[I_LOAD_MONSTER_TEXTURES]+' 95%', 0, True); + g_Game_StepLoading(116); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_VILE_DIE', GameWAD+':MTEXTURES\VILE_DIE', 128, 128, 9); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_ROBO_SLEEP', GameWAD+':MTEXTURES\ROBO_SLEEP', 128, 128, 1); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_ROBO_GO', GameWAD+':MTEXTURES\ROBO_GO', 128, 128, 12); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_ROBO_ATTACK', GameWAD+':MTEXTURES\ROBO_ATTACK', 128, 128, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_ROBO_ATTACK2', GameWAD+':MTEXTURES\ROBO_ATTACK2', 128, 128, 4); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_ROBO_DIE', GameWAD+':MTEXTURES\ROBO_DIE', 128, 128, 1); - g_Game_SetLoadingText(_lc[I_LOAD_MONSTER_TEXTURES]+' 100%', 0, True); + g_Game_StepLoading(122); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_CYBER_SLEEP', GameWAD+':MTEXTURES\CYBER_SLEEP', 128, 128, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_CYBER_SLEEP_L', GameWAD+':MTEXTURES\CYBER_SLEEP_L', 128, 128, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_CYBER_GO', GameWAD+':MTEXTURES\CYBER_GO', 128, 128, 4); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_CYBER_GO_L', GameWAD+':MTEXTURES\CYBER_GO_L', 128, 128, 4); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_CYBER_PAIN', GameWAD+':MTEXTURES\CYBER_PAIN', 128, 128, 1); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_CYBER_PAIN_L', GameWAD+':MTEXTURES\CYBER_PAIN_L', 128, 128, 1); + g_Game_StepLoading(128); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_CYBER_ATTACK', GameWAD+':MTEXTURES\CYBER_ATTACK', 128, 128, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_CYBER_ATTACK_L', GameWAD+':MTEXTURES\CYBER_ATTACK_L', 128, 128, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_CYBER_ATTACK2', GameWAD+':MTEXTURES\CYBER_ATTACK2', 128, 128, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_CYBER_ATTACK2_L', GameWAD+':MTEXTURES\CYBER_ATTACK2_L', 128, 128, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_CYBER_DIE', GameWAD+':MTEXTURES\CYBER_DIE', 128, 128, 9); + g_Game_StepLoading(133); g_Game_SetLoadingText(_lc[I_LOAD_MONSTER_SOUNDS], 0, False); @@ -903,14 +1095,15 @@ begin g_Sound_CreateWADEx('SOUND_MONSTER_FISH_ATTACK', GameWAD+':MSOUNDS\FISH_ATTACK'); - monsTree := TDynAABBTreeMons.Create(); + freeInds := TIdPool.Create(); clearUidMap(); monCheckTrapLastFrameId := 0; + monCheckMPlatLastFrameId := 0; end; procedure g_Monsters_FreeData(); begin - e_WriteLog('Releasing monsters data...', MSG_NOTIFY); + e_WriteLog('Releasing monsters data...', TMsgType.Notify); g_Frames_DeleteByName('FRAMES_MONSTER_BARREL_SLEEP'); g_Frames_DeleteByName('FRAMES_MONSTER_BARREL_PAIN'); @@ -920,12 +1113,14 @@ begin g_Frames_DeleteByName('FRAMES_MONSTER_ZOMBY_DIE'); g_Frames_DeleteByName('FRAMES_MONSTER_ZOMBY_MESS'); g_Frames_DeleteByName('FRAMES_MONSTER_ZOMBY_ATTACK'); + g_Frames_DeleteByName('FRAMES_MONSTER_ZOMBY_ATTACK_L'); g_Frames_DeleteByName('FRAMES_MONSTER_ZOMBY_PAIN'); g_Frames_DeleteByName('FRAMES_MONSTER_SERG_SLEEP'); g_Frames_DeleteByName('FRAMES_MONSTER_SERG_GO'); g_Frames_DeleteByName('FRAMES_MONSTER_SERG_DIE'); g_Frames_DeleteByName('FRAMES_MONSTER_SERG_MESS'); g_Frames_DeleteByName('FRAMES_MONSTER_SERG_ATTACK'); + g_Frames_DeleteByName('FRAMES_MONSTER_SERG_ATTACK_L'); g_Frames_DeleteByName('FRAMES_MONSTER_SERG_PAIN'); g_Frames_DeleteByName('FRAMES_MONSTER_MAN_SLEEP'); g_Frames_DeleteByName('FRAMES_MONSTER_MAN_GO'); @@ -1123,7 +1318,8 @@ begin g_Sound_Delete('SOUND_MONSTER_FISH_ATTACK'); - monsTree.Free(); + freeInds.Free(); + freeInds := nil; end; procedure g_Monsters_Init(); @@ -1131,17 +1327,34 @@ begin soulcount := 0; end; -procedure g_Monsters_Free(); +procedure g_Monsters_Free (clearGrid: Boolean=true); var a: Integer; begin - monsTree.reset(); + e_LogWritefln('Cleared monster data (clearGrid=%s)', [clearGrid]); + if (clearGrid) then + begin + monsGrid.Free(); + monsGrid := nil; + end; for a := 0 to High(gMonsters) do gMonsters[a].Free(); gMonsters := nil; clearUidMap(); monCheckTrapLastFrameId := 0; + monCheckMPlatLastFrameId := 0; end; + +// will be called from map loader +procedure g_Mons_InitTree (x, y, w, h: Integer); +begin + monsGrid.Free(); + monsGrid := TMonsterGrid.Create(x, y, w, h); + //clearUidMap(); // why not? + e_LogWritefln('%s', ['Recreated monster tree']); +end; + + function g_Monsters_Create(MonsterType: Byte; X, Y: Integer; Direction: TDirection; AdjCoord: Boolean = False; ForcedUID: Integer = -1): TMonster; var @@ -1150,10 +1363,10 @@ var begin result := nil; - // Íåò òàêîãî ìîíñòðà + // Нет такого монстра if (MonsterType > MONSTER_MAN) or (MonsterType = 0) then exit; - // Ñîáëþäàåì îãðàíè÷åíèå Lost_Soul'îâ + // Соблюдаем ограничение Lost_Soul'ов if MonsterType = MONSTER_SOUL then begin if soulcount > MAX_SOUL then exit; @@ -1164,12 +1377,12 @@ begin mon := TMonster.Create(MonsterType, find_id, ForcedUID); gMonsters[find_id] := mon; - mon.arrIdx := find_id; - mon.treeNode := -1; + mon.mArrIdx := find_id; + mon.mProxyId := -1; uidMap[mon.FUID] := mon; - // Íàñòðàèâàåì ïîëîæåíèå + // Настраиваем положение with mon do begin if AdjCoord then @@ -1187,6 +1400,8 @@ begin FStartDirection := Direction; FStartX := GameX; FStartY := GameY; + FObj.oldX := FObj.X; + FObj.oldY := FObj.Y; end; mon.positionChanged(); @@ -1201,26 +1416,42 @@ begin if gMonsters = nil then Exit; -// Ïðèêîëèñò ñìååòñÿ íàä ñìåðòüþ èãðîêà: + // Приколист смеется над смертью игрока: h := High(gMonsters); for a := 0 to h do + begin if (gMonsters[a] <> nil) then + begin with gMonsters[a] do + begin if (FMonsterType = MONSTER_MAN) and - (FState <> STATE_DEAD) and - (FState <> STATE_SLEEP) and - (FState <> STATE_DIE) then + (FState <> MONSTATE_DEAD) and + (FState <> MONSTATE_SLEEP) and + (FState <> MONSTATE_DIE) then begin g_Sound_PlayExAt('SOUND_MONSTER_TRUP', FObj.X, FObj.Y); Exit; end; + end; + end; + end; +end; + +procedure g_Monsters_PreUpdate(); +var + a: Integer; +begin + if gMonsters = nil then Exit; + for a := 0 to High(gMonsters) do + if (gMonsters[a] <> nil) and (not gMonsters[a].FRemoved) then + gMonsters[a].PreUpdate(); end; procedure g_Monsters_Update(); var a: Integer; begin - // Öåëåóêàçàòåëü + // Целеуказатель if gTime mod (GAME_TICK*2) = 0 then begin pt_x := pt_x+pt_xs; @@ -1229,183 +1460,195 @@ begin if abs(pt_y) > 100 then pt_ys := -pt_ys; end; - gMon := True; // Äëÿ ðàáîòû BlockMon'à + gMon := True; // Для работы BlockMon'а - for a := 0 to High(gMonsters) do + if gmon_debug_think or gmon_debug_one_think_step then begin - if (gMonsters[a] = nil) then continue; - if not gMonsters[a].FRemoved then + gmon_debug_one_think_step := false; + for a := 0 to High(gMonsters) do begin - if g_Game_IsClient then - gMonsters[a].ClientUpdate() + if (gMonsters[a] = nil) then continue; + if not gMonsters[a].FRemoved then + begin + if g_Game_IsClient then + gMonsters[a].ClientUpdate() + else + gMonsters[a].Update(); + end else - gMonsters[a].Update(); - end - else - begin - gMonsters[a].Free(); - gMonsters[a] := nil; + begin + gMonsters[a].Free(); + gMonsters[a] := nil; + end; end; end; gMon := False; end; -procedure g_Monsters_Draw(); -var - a: Integer; -begin - if gMonsters <> nil then - for a := 0 to High(gMonsters) do - if gMonsters[a] <> nil then - gMonsters[a].Draw(); -end; - -procedure g_Monsters_DrawHealth(); -var - a: Integer; - fW, fH: Byte; -begin - if gMonsters = nil then Exit; - e_TextureFontGetSize(gStdFont, fW, fH); - - for a := 0 to High(gMonsters) do - if gMonsters[a] <> nil then - begin - e_TextureFontPrint(gMonsters[a].FObj.X + gMonsters[a].FObj.Rect.X, - gMonsters[a].FObj.Y + gMonsters[a].FObj.Rect.Y + gMonsters[a].FObj.Rect.Height - fH, - IntToStr(gMonsters[a].FHealth), gStdFont); - end; -end; - function g_Monsters_ByUID (UID: Word): TMonster; -//var a: Integer; begin result := uidMap[UID]; - { - Result := nil; - if gMonsters <> nil then - for a := 0 to High(gMonsters) do - if (gMonsters[a] <> nil) and - (gMonsters[a].FUID = UID) then - begin - Result := gMonsters[a]; - Break; - end; - } end; -procedure g_Monsters_SaveState(var Mem: TBinMemoryWriter); +procedure g_Monsters_SaveState (st: TStream); var count, i: Integer; - b: Byte; begin -// Ñ÷èòàåì êîëè÷åñòâî ñóùåñòâóþùèõ ìîíñòðîâ: + // Считаем количество существующих монстров count := 0; - if gMonsters <> nil then - for i := 0 to High(gMonsters) do - if gMonsters[i] <> nil then - if gMonsters[i].FMonsterType <> MONSTER_NONE then - count := count + 1; - - Mem := TBinMemoryWriter.Create((count+1) * 350); + for i := 0 to High(gMonsters) do + begin + if (gMonsters[i] <> nil) and (gMonsters[i].FMonsterType <> MONSTER_NONE) then count += 1; + end; -// Ñîõðàíÿåì èíôîðìàöèþ öåëåóêàçàòåëÿ: - Mem.WriteInt(pt_x); - Mem.WriteInt(pt_xs); - Mem.WriteInt(pt_y); - Mem.WriteInt(pt_ys); + // Сохраняем информацию целеуказателя + utils.writeInt(st, LongInt(pt_x)); + utils.writeInt(st, LongInt(pt_xs)); + utils.writeInt(st, LongInt(pt_y)); + utils.writeInt(st, LongInt(pt_ys)); -// Êîëè÷åñòâî ìîíñòðîâ: - Mem.WriteInt(count); + // Количество монстров + utils.writeInt(st, LongInt(count)); - if count = 0 then - Exit; + if (count = 0) then exit; -// Ñîõðàíÿåì ìîíñòðîâ: + // Сохраняем монстров for i := 0 to High(gMonsters) do - if gMonsters[i] <> nil then - if gMonsters[i].FMonsterType <> MONSTER_NONE then - begin - // Òèï ìîíñòðà: - b := gMonsters[i].MonsterType; - Mem.WriteByte(b); - // Ñîõðàíÿåì äàííûå ìîíñòðà: - gMonsters[i].SaveState(Mem); - end; + begin + if (gMonsters[i] <> nil) and (gMonsters[i].FMonsterType <> MONSTER_NONE) then + begin + // Тип монстра + utils.writeInt(st, Byte(gMonsters[i].MonsterType)); + // Сохраняем данные монстра: + gMonsters[i].SaveState(st); + end; + end; end; -procedure g_Monsters_LoadState(var Mem: TBinMemoryReader); + +procedure g_Monsters_LoadState (st: TStream); var count, a: Integer; b: Byte; mon: TMonster; begin - if Mem = nil then exit; + assert(st <> nil); - g_Monsters_Free(); + g_Monsters_Free(false); - // Çàãðóæàåì èíôîðìàöèþ öåëåóêàçàòåëÿ - Mem.ReadInt(pt_x); - Mem.ReadInt(pt_xs); - Mem.ReadInt(pt_y); - Mem.ReadInt(pt_ys); + // Загружаем информацию целеуказателя + pt_x := utils.readLongInt(st); + pt_xs := utils.readLongInt(st); + pt_y := utils.readLongInt(st); + pt_ys := utils.readLongInt(st); - // Êîëè÷åñòâî ìîíñòðîâ - Mem.ReadInt(count); + // Количество монстров + count := utils.readLongInt(st); - if count = 0 then exit; + if (count = 0) then exit; + if (count < 0) or (count > 1024*1024) then raise XStreamError.Create('invalid monster count'); - // Çàãðóæàåì ìîíñòðîâ + // Загружаем монстров for a := 0 to count-1 do begin - // Òèï ìîíñòðà - Mem.ReadByte(b); - // Ñîçäàåì ìîíñòðà - mon := g_Monsters_Create(b, 0, 0, D_LEFT); - if mon = nil then raise EBinSizeError.Create('g_Monsters_LoadState: ID = -1 (Can''t create)'); - // Çàãðóæàåì äàííûå ìîíñòðà - mon.LoadState(Mem); + // Тип монстра + b := utils.readByte(st); + // Создаем монстра + mon := g_Monsters_Create(b, 0, 0, TDirection.D_LEFT); + if (mon = nil) then raise XStreamError.Create('g_Monsters_LoadState: ID = -1 (can''t create)'); + // Загружаем данные монстра + mon.LoadState(st); end; end; -function g_Monsters_GetIDByName(name: String): Integer; + +// ////////////////////////////////////////////////////////////////////////// // +function g_Mons_SpawnAt (monType: Integer; x, y: Integer; dir: TDirection=TDirection.D_LEFT): TMonster; overload; +begin + result := nil; + if (monType >= MONSTER_DEMON) and (monType <= MONSTER_MAN) then + begin + result := g_Monsters_Create(monType, x, y, dir); + end; +end; + + +function g_Mons_SpawnAt (const typeName: AnsiString; x, y: Integer; dir: TDirection=TDirection.D_LEFT): TMonster; overload; +begin + result := g_Mons_SpawnAt(g_Mons_TypeIdByName(typeName), x, y, dir); +end; + + + +// ////////////////////////////////////////////////////////////////////////// // +function g_Mons_TypeLo (): Integer; inline; begin result := Low(MONSTERTABLE); end; +function g_Mons_TypeHi (): Integer; inline; begin result := High(MONSTERTABLE); end; + + +function g_Mons_TypeIdByName (const name: String): Integer; var i: Integer; begin - name := UpperCase(name); i := MONSTER_DEMON; while (i <= MONSTER_MAN) do begin - if name = MONSTERTABLE[i].Name then + if (CompareText(name, MONSTERTABLE[i].Name) = 0) then begin - Result := i; - Exit; + result := i; + exit; end; Inc(i); end; - - Result := -1; + result := -1; + // HACK! + if (CompareText(name, 'zombie') = 0) then result := MONSTER_ZOMBY; end; -function g_Monsters_GetNameByID(MonsterType: Byte): String; + +function g_Mons_NameByTypeId (monType: Integer): AnsiString; begin - if MonsterType in [MONSTER_DEMON..MONSTER_MAN] then - Result := MONSTERTABLE[MonsterType].Name + if (monType >= MONSTER_DEMON) and (monType <= MONSTER_MAN) then + result := MONSTERTABLE[monType].Name else - Result := '?'; + result := '?'; end; -function g_Monsters_GetKilledBy(MonsterType: Byte): String; + +function g_Mons_GetKilledByTypeId (monType: Integer): AnsiString; begin - if MonsterType in [MONSTER_DEMON..MONSTER_MAN] then - Result := KilledByMonster[MonsterType] + if (monType >= MONSTER_DEMON) and (monType <= MONSTER_MAN) then + Result := KilledByMonster[monType] else Result := '?'; end; + +// ////////////////////////////////////////////////////////////////////////// // { T M o n s t e r : } +procedure TMonster.setGameX (v: Integer); inline; begin FObj.X := v; positionChanged(); end; +procedure TMonster.setGameY (v: Integer); inline; begin FObj.Y := v; positionChanged(); end; + +procedure TMonster.setPosition (ax, ay: Integer; callPosChanged: Boolean=true); inline; begin FObj.X := ax; FObj.Y := ay; if callPosChanged then positionChanged(); end; + +procedure TMonster.moveBy (dx, dy: Integer); inline; +begin + if (dx <> 0) or (dy <> 0) then + begin + FObj.X += dx; + FObj.Y += dy; + positionChanged(); + end; +end; + +procedure TMonster.doDamage (v: Integer); +begin + if (v <= 0) then exit; + if (v > 32767) then v := 32767; + Damage(v, 0, 0, 0, mEDamageType); +end; + procedure TMonster.ActionSound(); begin case FMonsterType of @@ -1594,8 +1837,8 @@ begin FObj.Accel.X := 0; FObj.Accel.Y := 0; FDirection := FStartDirection; - GameX := FStartX; - GameY := FStartY; + {GameX}FObj.X := FStartX; + {GameY}FObj.Y := FStartY; FObj.Rect := MONSTERTABLE[FMonsterType].Rect; FHealth := MONSTERTABLE[FMonsterType].Health; FAmmo := 0; @@ -1607,7 +1850,7 @@ begin FChainFire := False; FShellTimer := -1; - FState := STATE_SLEEP; + FState := MONSTATE_SLEEP; FCurAnim := ANIM_SLEEP; positionChanged(); // this updates spatial accelerators @@ -1622,7 +1865,7 @@ end; constructor TMonster.Create(MonsterType: Byte; aID: Integer; ForcedUID: Integer = -1); var a: Integer; - FramesID: DWORD; + FramesID: DWORD = 0; s: String; res: Boolean; begin @@ -1635,7 +1878,7 @@ begin g_Obj_Init(@FObj); - FState := STATE_SLEEP; + FState := MONSTATE_SLEEP; FCurAnim := ANIM_SLEEP; FHealth := MONSTERTABLE[MonsterType].Health; FMaxHealth := FHealth; @@ -1651,10 +1894,13 @@ begin FFireTime := 0; FFirePainTime := 0; FFireAttacker := 0; + mEDamageType := HIT_SOME; - treeNode := -1; - arrIdx := -1; + mProxyId := -1; + mArrIdx := -1; trapCheckFrameId := 0; + mplatCheckFrameId := 0; + mNeedSend := false; if FMonsterType in [MONSTER_ROBO, MONSTER_BARREL] then FBloodKind := BLOOD_SPARKS @@ -1683,8 +1929,8 @@ begin for a := 0 to High(FAnim) do begin - FAnim[a, D_LEFT] := nil; - FAnim[a, D_RIGHT] := nil; + FAnim[a, TDirection.D_LEFT] := nil; + FAnim[a, TDirection.D_RIGHT] := nil; end; for a := ANIM_SLEEP to ANIM_PAIN do @@ -1699,28 +1945,28 @@ begin if res then res := g_Frames_Get(FramesID, s); - // Åñëè íåò òàêîé àíèìàöèè, òî ïðîáóåì çàìåíèòü åå íà àíèìàöèþ ñìåðòè: + // Если нет такой анимации, то пробуем заменить ее на анимацию смерти: if (not res) then begin - // Çàìåíÿåì òîëüêî ANIM_MESS íà ANIM_DIE: + // Заменяем только ANIM_MESS на ANIM_DIE: if a <> ANIM_MESS then Continue; if g_Frames_Get(FramesID, 'FRAMES_MONSTER_'+MONSTERTABLE[MonsterType].Name+ '_'+ANIMTABLE[ANIM_DIE].name) then begin - FAnim[a, D_RIGHT] := TAnimation.Create(FramesID, ANIMTABLE[ANIM_DIE].loop, + FAnim[a, TDirection.D_RIGHT] := TAnimation.Create(FramesID, ANIMTABLE[ANIM_DIE].loop, MONSTER_ANIMTABLE[MonsterType].AnimSpeed[ANIM_DIE]); - FAnim[a, D_LEFT] := TAnimation.Create(FramesID, ANIMTABLE[ANIM_DIE].loop, + FAnim[a, TDirection.D_LEFT] := TAnimation.Create(FramesID, ANIMTABLE[ANIM_DIE].loop, MONSTER_ANIMTABLE[MonsterType].AnimSpeed[ANIM_DIE]); Continue; end; end; - FAnim[a, D_RIGHT] := TAnimation.Create(FramesID, ANIMTABLE[a].loop, + FAnim[a, TDirection.D_RIGHT] := TAnimation.Create(FramesID, ANIMTABLE[a].loop, MONSTER_ANIMTABLE[MonsterType].AnimSpeed[a]); - // Åñëè åñòü îòäåëüíàÿ ëåâàÿ àíèìàöèÿ - çàãðóæàåì: + // Если есть отдельная левая анимация - загружаем: if MONSTER_ANIMTABLE[MonsterType].LeftAnim then begin s := 'FRAMES_MONSTER_'+MONSTERTABLE[MonsterType].Name+ @@ -1729,11 +1975,11 @@ begin g_Frames_Get(FramesID, s); end; - FAnim[a, D_LEFT] := TAnimation.Create(FramesID, ANIMTABLE[a].loop, + FAnim[a, TDirection.D_LEFT] := TAnimation.Create(FramesID, ANIMTABLE[a].loop, MONSTER_ANIMTABLE[MonsterType].AnimSpeed[a]); end; -// Äëÿ êîëäóíà çàãðóæàåì òàêæå àíèìàöèþ îãíÿ: +// Для колдуна загружаем также анимацию огня: if MonsterType = MONSTER_VILE then begin g_Frames_Get(FramesID, 'FRAMES_FIRE'); @@ -1750,46 +1996,55 @@ var begin Result := False; -// Óìèðàåò, óìåð èëè âîñêðåøàåòñÿ => óðîí äåëàòü íåêîìó: - if (FState = STATE_DEAD) or (FState = STATE_DIE) or (FState = STATE_REVIVE) then +// Монстр статичен пока идет warmup + if (gLMSRespawn > LMS_RESPAWN_NONE) then exit; + +// Умирает, умер или воскрешается => урон делать некому: + if (FState = MONSTATE_DEAD) or (FState = MONSTATE_DIE) or (FState = MONSTATE_REVIVE) then Exit; -// Ðûáó â âîäå áüåò òîêîì => ïàíèêà áåç óðîíà: +// Рыбу в воде бьет током => паника без урона: if (t = HIT_ELECTRO) and (FMonsterType = MONSTER_FISH) and g_Game_IsServer then begin FSleep := 20; - if Random(2) = 0 then - FDirection := D_RIGHT - else - FDirection := D_LEFT; + if Random(2) = 0 then FDirection := TDirection.D_RIGHT else FDirection := TDirection.D_LEFT; Result := True; - SetState(STATE_RUN); + SetState(MONSTATE_RUN); Exit; end; -// Ëîâóøêà óáèâàåò ñðàçó: +// Арчи не горят, черепа уже горят + if (t = HIT_FLAME) and (FMonsterType in [MONSTER_VILE, MONSTER_SOUL]) then + begin + // Проснуться все-таки стоит + if FState = MONSTATE_SLEEP then + SetState(MONSTATE_GO); + Exit; + end; + +// Ловушка убивает сразу: if t = HIT_TRAP then FHealth := -100; -// Ðîáîòó óðîíà íåò: +// Роботу урона нет: if FMonsterType = MONSTER_ROBO then aDamage := 0; -// Íàíîñèì óðîí: +// Наносим урон: if g_Game_IsServer then Dec(FHealth, aDamage); -// Óñèëèâàåì áîëü ìîíñòðà îò óðîíà: +// Усиливаем боль монстра от урона: if FPain = 0 then FPain := 3; FPain := FPain+aDamage; -// Åñëè áîëü ñóùåñòâåííàÿ, òî ìåíÿåì ñîñòîÿíèå íà áîëåâîå: - if FState <> STATE_PAIN then +// Если боль существенная, то меняем состояние на болевое: + if FState <> MONSTATE_PAIN then if (FPain >= MONSTERTABLE[FMonsterType].MinPain) and (FMonsterType <> MONSTER_BARREL) then - SetState(STATE_PAIN); + SetState(MONSTATE_PAIN); -// Åñëè ðàçðåøåíà êðîâü - ñîçäàåì áðûçãè êðîâè: +// Если разрешена кровь - создаем брызги крови: if (gBloodCount > 0) then begin c := Min(aDamage, 200); @@ -1804,17 +2059,17 @@ begin end; end; -// Òåïåðü öåëü - óäàðèâøèé, åñëè òîëüêî íå ñàì ñåáÿ: +// Теперь цель - ударивший, если только не сам себя: if (SpawnerUID <> FUID) and (BehaviourDamage(SpawnerUID, FBehaviour, FMonsterType)) then begin FTargetUID := SpawnerUID; FTargetTime := 0; end; -// Çäîðîâüå çàêîí÷èëîñü: +// Здоровье закончилось: if FHealth <= 0 then begin - // Åñëè ýòî íå áî÷êà è óáèë èãðîê, òî åìó +1: + // Если это не бочка и убил игрок, то ему +1: if (FMonsterType <> MONSTER_BARREL) then begin if (g_GetUIDType(SpawnerUID) = UID_PLAYER) then @@ -1837,7 +2092,7 @@ begin end; end; - // Âûáèðàåì ëóò: + // Выбираем лут: case FMonsterType of MONSTER_ZOMBY: c := ITEM_AMMO_BULLETS; MONSTER_SERG: c := ITEM_WEAPON_SHOTGUN1; @@ -1846,54 +2101,56 @@ begin else c := 0; end; - // Áðîñàåì ëóò: + // Бросаем лут: if c <> 0 then begin it := g_Items_Create(FObj.X + (FObj.Rect.Width div 2), FObj.Y + (FObj.Rect.Height div 2), c, True, False); + g_Items_SetDrop(it); // mark it as monster drop g_Obj_Push(g_Items_ObjByIdx(it), (FObj.Vel.X div 2)-3+Random(7), (FObj.Vel.Y div 2)-Random(4)); - positionChanged(); // this updates spatial accelerators + //positionChanged(); // this updates spatial accelerators if g_Game_IsServer and g_Game_IsNet then MH_SEND_ItemSpawn(True, it); end; - // Òðóï äàëüøå íå èäåò: + // Труп дальше не идет: FObj.Vel.X := 0; - // Ó òðóïà ðàçìåðû ìåíüøå: + // У трупа размеры меньше: if (FMonsterType <> MONSTER_FISH) and (FMonsterType <> MONSTER_PAIN) then begin FObj.Rect.Y := FObj.Rect.Y + FObj.Rect.Height-12; FObj.Rect.Height := 12; + positionChanged(); end; - // Óðîí áûë ñèëüíûì => ñëàáûå - â êàøó: + // Урон был сильным => слабые - в кашу: if (FHealth <= -30) and ((FMonsterType = MONSTER_IMP) or (FMonsterType = MONSTER_ZOMBY) or (FMonsterType = MONSTER_SERG) or (FMonsterType = MONSTER_CGUN) or (FMonsterType = MONSTER_MAN)) then begin g_Sound_PlayExAt('SOUND_MONSTER_SLOP', FObj.X, FObj.Y); - SetState(STATE_DIE, ANIM_MESS); + SetState(MONSTATE_DIE, ANIM_MESS); end else begin DieSound(); - SetState(STATE_DIE); + SetState(MONSTATE_DIE); end; - // Àêòèâèðîâàòü òðèããåðû, æäóùèå ñìåðòè ýòîãî ìîíñòðà: + // Активировать триггеры, ждущие смерти этого монстра: if g_Game_IsServer then ActivateTriggers(); FHealth := 0; end else - if FState = STATE_SLEEP then - begin // Ñïàë, ðàçáóäèëè íåñìåðòåëüíûì óäàðîì: + if FState = MONSTATE_SLEEP then + begin // Спал, разбудили несмертельным ударом: FPain := MONSTERTABLE[FMonsterType].Pain; - SetState(STATE_GO); + SetState(MONSTATE_GO); end; if g_Game_IsServer and g_Game_IsNet then MH_SEND_MonsterState(FUID); @@ -1905,7 +2162,7 @@ begin Result := False; if g_Game_IsClient then Exit; - if not Live then + if not alive then Exit; if FHealth < FMaxHealth then @@ -1922,110 +2179,36 @@ var begin for a := 0 to High(FAnim) do begin - FAnim[a, D_LEFT].Free(); - FAnim[a, D_RIGHT].Free(); + FAnim[a, TDirection.D_LEFT].Free(); + FAnim[a, TDirection.D_RIGHT].Free(); end; vilefire.Free(); - if (treeNode <> -1) then + if (mProxyId <> -1) then begin - if monsTree.isValidId(treeNode) then + if (monsGrid <> nil) then begin - {$IF DEFINED(D2F_DEBUG)} - e_WriteLog(Format('monster #%d(%u): removed from tree; nodeid=%d', [arrIdx, UID, treeNode]), MSG_NOTIFY); + monsGrid.removeBody(mProxyId); + {$IF DEFINED(D2F_DEBUG_MONS_MOVE)} + e_WriteLog(Format('monster #%d:(%u): removed from grid; mProxyid=%d', [mArrIdx, UID, mProxyId]), MSG_NOTIFY); {$ENDIF} - monsTree.removeObject(treeNode); end; + mProxyId := -1; end; - if (arrIdx <> -1) then + if (mArrIdx <> -1) and (mArrIdx < Length(gMonsters)) then begin - gMonsters[arrIdx] := nil; + freeInds.release(mArrIdx); + gMonsters[mArrIdx] := nil; end; + mArrIdx := -1; uidMap[FUID] := nil; inherited Destroy(); end; -procedure TMonster.Draw(); -var - m: TMirrorType; - dx, dy, c: Integer; - o: TObj; -begin - //e_CharFont_Print(gMenuSmallFont, Obj.X+Obj.Rect.X, Obj.Y+Obj.Rect.Y, 'TYPE: '+IntToStr(FMonsterType)); - //e_CharFont_Print(gMenuSmallFont, Obj.X+Obj.Rect.X, Obj.Y+Obj.Rect.Y+16, 'STATE: '+IntToStr(FState)); - -// Åñëè êîëäóí ñòðåëÿåò, òî ðèñóåì îãîíü: - if FMonsterType = MONSTER_VILE then - if FState = STATE_SHOOT then - if GetPos(FTargetUID, @o) then - vilefire.Draw(o.X+o.Rect.X+(o.Rect.Width div 2)-32, - o.Y+o.Rect.Y+o.Rect.Height-128, M_NONE); - -// Íå â îáëàñòè ðèñîâàíèÿ íå ðåñóåì: - if not g_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, FObj.Rect.Width, FObj.Rect.Height, - sX-128, sY-128, sWidth+256, sHeight+256) then - Exit; - -// Ýòè ìîíñòðû, óìèðàÿ, íå îñòàâëÿþò òðóïîâ: - if FState = STATE_DEAD then - case FMonsterType of - MONSTER_BARREL, MONSTER_SOUL, MONSTER_PAIN: Exit; - end; - -// Åñòü ÷òî ðèñîâàòü ïðè òåêóùåì ïîâåäåíèè: - if FAnim[FCurAnim, FDirection] <> nil then - begin - // Åñëè íåò ëåâîé àíèìàöèè èëè îíà ñîâïàäàåò ñ ïðàâîé => îòðàæàåì ïðàâóþ: - if (FDirection = D_LEFT) and - ((not MONSTER_ANIMTABLE[FMonsterType].LeftAnim) or - (FAnim[FCurAnim, D_LEFT].FramesID = FAnim[FCurAnim, D_RIGHT].FramesID)) and - (FMonsterType <> MONSTER_BARREL) then - m := M_HORIZONTAL - else - m := M_NONE; - - // Ëåâàÿ àíèìàöèÿ => ìåíÿåì ñìåùåíèå îòíîñèòåëüíî öåíòðà: - if (FDirection = D_LEFT) and - (FMonsterType <> MONSTER_BARREL) then - begin - dx := MONSTER_ANIMTABLE[FMonsterType].AnimDeltaLeft[FCurAnim].X; - dy := MONSTER_ANIMTABLE[FMonsterType].AnimDeltaLeft[FCurAnim].Y; - - if m = M_HORIZONTAL then - begin // Íåò îòäåëüíîé ëåâîé àíèìàöèè - // Ðàññòîÿíèå îò êðàÿ òåêñòóðû äî êðàÿ âèçóàëüíîãî ïîëîæåíèÿ îáúåêòà íà òåêñòóðå: - c := (MONSTERTABLE[FMonsterType].Rect.X - dx) + MONSTERTABLE[FMonsterType].Rect.Width; - // Ðàññòîÿíèå îò êðàÿ õèò áîêñà äî êðàÿ âèçóàëüíîãî ïîëîæåíèÿ îáúåêòà íà òåêñòóðå: - dx := FAnim[FCurAnim, FDirection].Width - c - MONSTERTABLE[FMonsterType].Rect.X; - // Ò.ê. äâèãàòü òåêñòóðó íóæíî áóäåò â ïðîòèâîïîëîæíîì íàïðàâëåíèè: - dx := -dx; - // Ýòî çíà÷èò: dX := -frameWidth - animDeltaX + hitX + hitWidth + hitX - end; - end - else // Ïðàâàÿ àíèìàöèÿ - begin - dx := MONSTER_ANIMTABLE[FMonsterType].AnimDeltaRight[FCurAnim].X; - dy := MONSTER_ANIMTABLE[FMonsterType].AnimDeltaRight[FCurAnim].Y; - end; - - // Ðèñóåì: - FAnim[FCurAnim, FDirection].Draw(Obj.X+dx, Obj.Y+dy, m); - end; - - if g_debug_Frames then - begin - e_DrawQuad(FObj.X+FObj.Rect.X, - FObj.Y+FObj.Rect.Y, - FObj.X+FObj.Rect.X+FObj.Rect.Width-1, - FObj.Y+FObj.Rect.Y+FObj.Rect.Height-1, - 0, 255, 0); - end; -end; - procedure TMonster.MakeBloodSimple(Count: Word); begin g_GFX_Blood(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)+8, @@ -2057,35 +2240,35 @@ procedure TMonster.SetState(State: Byte; ForceAnim: Byte = 255); var Anim: Byte; begin -// Åñëè ñîñòîÿíèå = íà÷àëè óìèðàòü, à ýòîò ìîíñòð = Lost_Soul, -// òî ñîáëþäàåì îãðàíè÷åíèå êîëè÷åñòâà Lost_Soul'îâ: - if (State = STATE_DIE) and (MonsterType = MONSTER_SOUL) then +// Если состояние = начали умирать, а этот монстр = Lost_Soul, +// то соблюдаем ограничение количества Lost_Soul'ов: + if (State = MONSTATE_DIE) and (MonsterType = MONSTER_SOUL) then soulcount := soulcount-1; -// Ïðèñìåðòè - íåëüçÿ ñðàçó íà÷èíàòü àòàêîâàòü èëè áåãàòü: +// Присмерти - нельзя сразу начинать атаковать или бегать: case FState of - STATE_DIE, STATE_DEAD, STATE_REVIVE: - if (State <> STATE_DEAD) and (State <> STATE_REVIVE) and - (State <> STATE_GO) then + MONSTATE_DIE, MONSTATE_DEAD, MONSTATE_REVIVE: + if (State <> MONSTATE_DEAD) and (State <> MONSTATE_REVIVE) and + (State <> MONSTATE_GO) then Exit; end; -// Ñìåíà ñîñòîÿíèÿ: +// Смена состояния: FState := State; if g_Game_IsServer and g_Game_IsNet then MH_SEND_MonsterState(FUID, ForceAnim); -// Íîâàÿ àíèìàöèÿ ïðè íîâîì ñîñòîÿíèè: +// Новая анимация при новом состоянии: case FState of - STATE_SLEEP: Anim := ANIM_SLEEP; - STATE_PAIN: Anim := ANIM_PAIN; - STATE_WAIT: Anim := ANIM_SLEEP; - STATE_CLIMB, STATE_RUN, STATE_RUNOUT, STATE_GO: Anim := ANIM_GO; - STATE_SHOOT: Anim := ANIM_ATTACK; - STATE_ATTACK: Anim := ANIM_ATTACK; - STATE_DIE: Anim := ANIM_DIE; - STATE_REVIVE: - begin // íà÷àëè âîñðåøàòüñÿ + MONSTATE_SLEEP: Anim := ANIM_SLEEP; + MONSTATE_PAIN: Anim := ANIM_PAIN; + MONSTATE_WAIT: Anim := ANIM_SLEEP; + MONSTATE_CLIMB, MONSTATE_RUN, MONSTATE_RUNOUT, MONSTATE_GO: Anim := ANIM_GO; + MONSTATE_SHOOT: Anim := ANIM_ATTACK; + MONSTATE_ATTACK: Anim := ANIM_ATTACK; + MONSTATE_DIE: Anim := ANIM_DIE; + MONSTATE_REVIVE: + begin // начали восрешаться Anim := FCurAnim; FAnim[Anim, FDirection].Revert(True); @@ -2097,11 +2280,11 @@ begin else Exit; end; -// Íàäî ñìåíèòü àíèìàöèþ íà íåñòàíäàðòíóþ: +// Надо сменить анимацию на нестандартную: if ForceAnim <> 255 then Anim := ForceAnim; -// Åñëè àíèìàöèÿ íîâàÿ - ïåðåçàïóñêàåì å¸: +// Если анимация новая - перезапускаем её: if FCurAnim <> Anim then if FAnim[Anim, FDirection] <> nil then begin @@ -2117,7 +2300,7 @@ var begin Result := False; -//  òî÷êå íàçíà÷åíèÿ ñòåíà: +// В точке назначения стена: if g_CollideLevel(X, Y, FObj.Rect.Width, FObj.Rect.Height) then begin g_Sound_PlayExAt('SOUND_GAME_NOTELEPORT', FObj.X, FObj.Y); @@ -2128,7 +2311,7 @@ begin TA := nil; -// Ýôôåêò òåëåïîðòà â ïîçèöèè ìîíñòðà: +// Эффект телепорта в позиции монстра: if not silent then begin if g_Frames_Get(FramesID, 'FRAMES_TELEPORT') then @@ -2145,23 +2328,25 @@ begin FObj.X := X - FObj.Rect.X; FObj.Y := Y - FObj.Rect.Y; + FObj.oldX := FObj.X; // don't interpolate after teleport + FObj.oldY := FObj.Y; positionChanged(); if dir = 1 then - FDirection := D_LEFT + FDirection := TDirection.D_LEFT else if dir = 2 then - FDirection := D_RIGHT + FDirection := TDirection.D_RIGHT else if dir = 3 then - begin // îáðàòíîå - if FDirection = D_RIGHT then - FDirection := D_LEFT + begin // обратное + if FDirection = TDirection.D_RIGHT then + FDirection := TDirection.D_LEFT else - FDirection := D_RIGHT; + FDirection := TDirection.D_RIGHT; end; -// Ýôôåêò òåëåïîðòà â òî÷êå íàçíà÷åíèÿ: +// Эффект телепорта в точке назначения: if not silent and (TA <> nil) then begin g_GFX_OnceAnim(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)-32, @@ -2178,6 +2363,12 @@ begin Result := True; end; +procedure TMonster.PreUpdate(); +begin + FObj.oldX := FObj.X; + FObj.oldY := FObj.Y; +end; + procedure TMonster.Update(); var a, b, sx, sy, wx, wy, oldvelx: Integer; @@ -2185,26 +2376,31 @@ var o, co: TObj; fall: Boolean; mon: TMonster; + mit: PMonster; + it: TMonsterGrid.Iter; label _end; begin fall := True; -// Ðûáû "ëåòàþò" òîëüêî â âîäå: +// Монстр статичен пока идет warmup + if (gLMSRespawn > LMS_RESPAWN_NONE) then exit; + +// Рыбы "летают" только в воде: if FMonsterType = MONSTER_FISH then if g_Obj_CollidePanel(@FObj, 0, 0, PANEL_WATER or PANEL_ACID1 or PANEL_ACID2) then - if (FState <> STATE_DIE) and (FState <> STATE_DEAD) then + if (FState <> MONSTATE_DIE) and (FState <> MONSTATE_DEAD) then fall := False; -// Ëåòàþùèå ìîíòñðû: +// Летающие монтсры: if ((FMonsterType = MONSTER_SOUL) or (FMonsterType = MONSTER_PAIN) or (FMonsterType = MONSTER_CACO)) and - (FState <> STATE_DIE) and - (FState <> STATE_DEAD) then + (FState <> MONSTATE_DIE) and + (FState <> MONSTATE_DEAD) then fall := False; -// Ìåíÿåì ñêîðîñòü òîëüêî ïî ÷åòíûì êàäðàì: +// Меняем скорость только по четным кадрам: if gTime mod (GAME_TICK*2) <> 0 then begin g_Obj_Move(@FObj, fall, True, True); @@ -2217,16 +2413,25 @@ begin else FPainSound := False; -// Äâèãàåìñÿ: +// Двигаемся: st := g_Obj_Move(@FObj, fall, True, True); positionChanged(); // this updates spatial accelerators -// Âûëåòåë çà êàðòó - óäàëÿåì è çàïóñêàåì òðèããåðû: +// Если горим - поджигаем других монстров, но не на 100 тиков каждый раз: + if FFireTime > 0 then + begin + it := monsGrid.forEachInAABB(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, FObj.Rect.Width, FObj.Rect.Height); + for mit in it do + if mit.UID <> FUID then + mit.CatchFire(FFireAttacker, FFireTime); + end; + +// Вылетел за карту - удаляем и запускаем триггеры: if WordBool(st and MOVE_FALLOUT) or (FObj.X < -1000) or (FObj.X > gMapInfo.Width+1000) or (FObj.Y < -1000) then begin FRemoved := True; - if Live and (gLMSRespawn = LMS_RESPAWN_NONE) then + if alive and (gLMSRespawn = LMS_RESPAWN_NONE) then begin Inc(gCoopMonstersKilled); if g_Game_IsNet then @@ -2238,8 +2443,8 @@ begin oldvelx := FObj.Vel.X; -// Ñîïðîòèâëåíèå âîçäóõà äëÿ òðóïà: - if (FState = STATE_DIE) or (FState = STATE_DEAD) then +// Сопротивление воздуха для трупа: + if (FState = MONSTATE_DIE) or (FState = MONSTATE_DEAD) then FObj.Vel.X := z_dec(FObj.Vel.X, 1); if FFireTime > 0 then @@ -2250,7 +2455,7 @@ begin begin OnFireFlame(1); FFireTime := FFireTime - 1; - if (FState <> STATE_DIE) and (FState <> STATE_DEAD) then + if (FState <> MONSTATE_DIE) and (FState <> MONSTATE_DEAD) then if FFirePainTime = 0 then begin Damage(5, FFireAttacker, 0, 0, HIT_FLAME); @@ -2261,19 +2466,19 @@ begin end; end; -// Ìåðòâûé íè÷åãî íå äåëàåò: - if (FState = STATE_DEAD) then +// Мертвый ничего не делает: + if (FState = MONSTATE_DEAD) then goto _end; -// AI ìîíñòðîâ âûêëþ÷åí: +// AI монстров выключен: if g_debug_MonsterOff then begin FSleep := 1; - if FState <> STATE_SLEEP then - SetState(STATE_SLEEP); + if FState <> MONSTATE_SLEEP then + SetState(MONSTATE_SLEEP); end; -// Âîçìîæíî, ñîçäàåì ïóçûðüêè â âîäå: +// Возможно, создаем пузырьки в воде: if WordBool(st and MOVE_INWATER) and (Random(32) = 0) then case FMonsterType of MONSTER_FISH: @@ -2293,17 +2498,17 @@ begin end; end; -// Åñëè ïðîøåë ïåðâûé êàäð àíèìàöèè âçðûâà áî÷êè, òî âçðûâ: +// Если прошел первый кадр анимации взрыва бочки, то взрыв: if FMonsterType = MONSTER_BARREL then begin - if (FState = STATE_DIE) and (FAnim[FCurAnim, FDirection].CurrentFrame = 1) and + if (FState = MONSTATE_DIE) and (FAnim[FCurAnim, FDirection].CurrentFrame = 1) and (FAnim[FCurAnim, FDirection].Counter = 0) then g_Weapon_Explode(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2), FObj.Y+FObj.Rect.Y+FObj.Rect.Height-16, 60, FUID); end; -// Lost_Soul âûëåòåë èç âîäû => óñêîðÿåòñÿ: +// Lost_Soul вылетел из воды => ускоряется: if FMonsterType = MONSTER_SOUL then if WordBool(st and MOVE_HITAIR) then g_Obj_SetSpeed(@FObj, 16); @@ -2311,15 +2516,15 @@ begin if FAmmo < 0 then FAmmo := FAmmo + 1; -// Åñëè íà÷àëè âñïëûâàòü, òî ïðîäîëæàåì: +// Если начали всплывать, то продолжаем: if FObj.Vel.Y < 0 then if WordBool(st and MOVE_INWATER) then FObj.Vel.Y := -4; -// Òàéìåð - æäåì ïîñëå ïîòåðè öåëè: +// Таймер - ждем после потери цели: FTargetTime := FTargetTime + 1; -// Ãèëüçû +// Гильзы if FShellTimer > -1 then if FShellTimer = 0 then begin @@ -2339,10 +2544,10 @@ begin FShellTimer := -1; end else Dec(FShellTimer); -// Ïðîáóåì óâåðíóòüñÿ îò ëåòÿùåé ïóëè: +// Пробуем увернуться от летящей пули: if fall then - if (FState in [STATE_GO, STATE_RUN, STATE_RUNOUT, - STATE_ATTACK, STATE_SHOOT]) then + if (FState in [MONSTATE_GO, MONSTATE_RUN, MONSTATE_RUNOUT, + MONSTATE_ATTACK, MONSTATE_SHOOT]) then if g_Weapon_Danger(FUID, FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, FObj.Rect.Width, FObj.Rect.Height, 50) then if (g_Obj_CollideLevel(@FObj, 0, 1) or g_Obj_StayOnStep(@FObj)) and @@ -2350,9 +2555,9 @@ begin FObj.Vel.Y := -MONSTERTABLE[FMonsterType].Jump; case FState of - STATE_PAIN: // Ñîñòîÿíèå - Áîëü + MONSTATE_PAIN: // Состояние - Боль begin - // Áîëü ñèëüíàÿ => ìîíñòð êðè÷èò: + // Боль сильная => монстр кричит: if FPain >= MONSTERTABLE[FMonsterType].Pain then begin FPain := MONSTERTABLE[FMonsterType].Pain; @@ -2361,35 +2566,35 @@ begin if (not gSoundEffectsDF) and (FPain >= MONSTERTABLE[FMonsterType].MinPain) then PainSound(); - // Ñíèæàåì áîëü ñî âðåìåíåì: + // Снижаем боль со временем: FPain := FPain - 5; - // Áîëü óæå íå îøóòèìàÿ => èäåì äàëüøå: + // Боль уже не ошутимая => идем дальше: if FPain <= MONSTERTABLE[FMonsterType].MinPain then begin FPain := 0; FAmmo := -9; - SetState(STATE_GO); + SetState(MONSTATE_GO); end; end; - STATE_SLEEP: // Ñîñòîÿíèå - Ñîí + MONSTATE_SLEEP: // Состояние - Сон begin - // Ñïèì: + // Спим: FSleep := FSleep + 1; - // Ïðîñïàëè äîñòàòî÷íî: + // Проспали достаточно: if FSleep >= 18 then FSleep := 0 - else // åùå ñïèì + else // еще спим goto _end; - // Íà èãðîêîâ èäóò òîëüêî îáû÷íûå ìîíñòðû, êèëëåðû è ìàíüÿêè + // На игроков идут только обычные монстры, киллеры и маньяки if (FBehaviour = BH_NORMAL) or (FBehaviour = BH_KILLER) or (FBehaviour = BH_MANIAC) then - // Åñëè åñòü èãðîê ðÿäîì, ïðîñûïàåìñÿ è èäåì ê íåìó: + // Если есть игрок рядом, просыпаемся и идем к нему: if (gPlayers <> nil) then for a := 0 to High(gPlayers) do - if (gPlayers[a] <> nil) and (gPlayers[a].Live) + if (gPlayers[a] <> nil) and (gPlayers[a].alive) and (not gPlayers[a].NoTarget) and (gPlayers[a].FMegaRulez[MR_INVIS] < gTime) then with gPlayers[a] do if g_Look(@FObj, @Obj, FDirection) then @@ -2397,28 +2602,28 @@ begin FTargetUID := gPlayers[a].UID; FTargetTime := 0; WakeUpSound(); - SetState(STATE_GO); + SetState(MONSTATE_GO); Break; end; - // Íà ìîíñòðîâ òÿíåò ìàíüÿêîâ, ïîåõàâøèõ è êàííèáàëîâ + // На монстров тянет маньяков, поехавших и каннибалов if (FTargetUID = 0) and ((FBehaviour = BH_MANIAC) or (FBehaviour = BH_INSANE) or (FBehaviour = BH_CANNIBAL)) then - // Åñëè åñòü ïîäõîäÿùèé ìîíñòð ðÿäîì: + // Если есть подходящий монстр рядом: if gMonsters <> nil then for a := 0 to High(gMonsters) do - if (gMonsters[a] <> nil) and (gMonsters[a].Live) and + if (gMonsters[a] <> nil) and (gMonsters[a].alive) and (gMonsters[a].FUID <> FUID) then begin - // Ìàíüÿêè íàïàäàþò íà âñåõ ìîíñòðîâ, êðîìå äðóçåé + // Маньяки нападают на всех монстров, кроме друзей if (FBehaviour = BH_MANIAC) and (IsFriend(gMonsters[a].FMonsterType, FMonsterType)) then Continue; - // Ïîåõàâøèå òàêæå, íî ìîãóò îáîçëèòüñÿ íà áî÷êó + // Поехавшие также, но могут обозлиться на бочку if (FBehaviour = BH_INSANE) and (gMonsters[a].FMonsterType <> MONSTER_BARREL) and (IsFriend(gMonsters[a].FMonsterType, FMonsterType)) then Continue; - // Êàííèáàëû íàïàäàþò íà ñåáå ïîäîáíûõ + // Каннибалы нападают на себе подобных if (FBehaviour = BH_CANNIBAL) and (gMonsters[a].FMonsterType <> FMonsterType) then Continue; if g_Look(@FObj, @gMonsters[a].Obj, FDirection) then @@ -2426,48 +2631,48 @@ begin FTargetUID := gMonsters[a].UID; FTargetTime := 0; WakeUpSound(); - SetState(STATE_GO); + SetState(MONSTATE_GO); Break; end; end; end; - STATE_WAIT: // Ñîñòîÿíèå - Îæèäàíèå + MONSTATE_WAIT: // Состояние - Ожидание begin - // Æäåì: + // Ждем: FSleep := FSleep - 1; - // Âûæäàëè äîñòàòî÷íî - èäåì: + // Выждали достаточно - идем: if FSleep < 0 then - SetState(STATE_GO); + SetState(MONSTATE_GO); end; - STATE_GO: // Ñîñòîÿíèå - Äâèæåíèå (ñ îñìîòðîì ñèòóàöèè) + MONSTATE_GO: // Состояние - Движение (с осмотром ситуации) begin - // Åñëè íàòêíóëèñü íà ÁëîêÌîí - óáåãàåì îò íåãî: + // Если наткнулись на БлокМон - убегаем от него: if WordBool(st and MOVE_BLOCK) then begin Turn(); FSleep := 40; - SetState(STATE_RUNOUT); + SetState(MONSTATE_RUNOUT); goto _end; end; - // Åñëè ìîíñòð - êîëäóí, òî ïðîáóåì âîñêðåñèòü êîãî-íèáóäü: + // Если монстр - колдун, то пробуем воскресить кого-нибудь: if (FMonsterType = MONSTER_VILE) then if isCorpse(@FObj, False) <> -1 then begin FObj.Vel.X := 0; - SetState(STATE_ATTACK, ANIM_ATTACK2); + SetState(MONSTATE_ATTACK, ANIM_ATTACK2); goto _end; end; - // Öåëü ïîãèáëà èëè äàâíî æäåì: + // Цель погибла или давно ждем: if (not GetPos(FTargetUID, @o)) or (FTargetTime > MAX_ATM) then if not findNewPrey() then - begin // Íîâûõ öåëåé íåò + begin // Новых целей нет FTargetUID := 0; o.X := FObj.X+pt_x; o.Y := FObj.Y+pt_y; @@ -2477,10 +2682,10 @@ begin o.Accel.Y := 0; o.Rect := _Rect(0, 0, 0, 1); end - else // Íîâàÿ öåëü åñòü - áåðåì åå êîîðäèíàòû + else // Новая цель есть - берем ее координаты GetPos(FTargetUID, @o); - // Öåëü î÷åíü áëèçêî - ïèíàåì: + // Цель очень близко - пинаем: if g_Obj_Collide(@FObj, @o) and (FTargetUID <> 0) then begin FTargetTime := 0; @@ -2491,141 +2696,141 @@ begin end; end; - // Ðàññòîÿíèå äî öåëè: + // Расстояние до цели: sx := o.X+o.Rect.X+(o.Rect.Width div 2)-(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)); sy := o.Y+o.Rect.Y+(o.Rect.Height div 2)-(FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2)); - // Ïîâîðà÷èâàåìñÿ â ñòîðîíó öåëè: + // Поворачиваемся в сторону цели: if sx > 0 then - FDirection := D_RIGHT + FDirection := TDirection.D_RIGHT else - FDirection := D_LEFT; + FDirection := TDirection.D_LEFT; - // Åñëè ìîíñòð óìååò ñòðåëÿòü è åñòü ïî êîìó - ñòðåëÿåì: + // Если монстр умеет стрелять и есть по кому - стреляем: if canShoot(FMonsterType) and (FTargetUID <> 0) then - if Abs(sx) > Abs(sy) then // óãîë âûñòðåëà óäîáíûé + if Abs(sx) > Abs(sy) then // угол выстрела удобный if shoot(@o, False) then goto _end; - // Åñëè öåëü ïî÷òè íà îäíîé âåðòèêàëè - áåãàåì òóäà-ñþäà: + // Если цель почти на одной вертикали - бегаем туда-сюда: if Abs(sx) < 40 then if FMonsterType <> MONSTER_FISH then begin FSleep := 15; - SetState(STATE_RUN); + SetState(MONSTATE_RUN); if Random(2) = 0 then - FDirection := D_LEFT + FDirection := TDirection.D_LEFT else - FDirection := D_RIGHT; + FDirection := TDirection.D_RIGHT; goto _end; end; - // Óïåðëèñü â ñòåíó: + // Уперлись в стену: if WordBool(st and MOVE_HITWALL) then begin if g_Triggers_PressR(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, FObj.Rect.Width, FObj.Rect.Height, FUID, ACTIVATE_MONSTERPRESS) <> nil then - begin // Ñìîãëè íàæàòü êíîïêó - íåáîëüøîå îæèäàíèå + begin // Смогли нажать кнопку - небольшое ожидание FSleep := 4; - SetState(STATE_WAIT); + SetState(MONSTATE_WAIT); goto _end; end; case FMonsterType of MONSTER_CACO, MONSTER_SOUL, MONSTER_PAIN, MONSTER_FISH: ; - else // Íå ëåòàþò: + else // Не летают: if (g_Obj_CollideLevel(@FObj, 0, 1) or g_Obj_StayOnStep(@FObj)) and (FObj.Accel.Y = 0) then - begin // Ñòîèì íà òâåðäîì ïîëó èëè ñòóïåíè - // Ïðûæîê ÷åðåç ñòåíó: + begin // Стоим на твердом полу или ступени + // Прыжок через стену: FObj.Vel.Y := -MONSTERTABLE[FMonsterType].Jump; - SetState(STATE_CLIMB); + SetState(MONSTATE_CLIMB); end; end; goto _end; end; - // Ìîíñòðû, íå ïîäâåðæåííûå ãðàâèòàöèè: + // Монстры, не подверженные гравитации: if (FMonsterType = MONSTER_CACO) or (FMonsterType = MONSTER_SOUL) or (FMonsterType = MONSTER_PAIN) or (FMonsterType = MONSTER_FISH) then begin if FMonsterType = MONSTER_FISH then - begin // Ðûáà + begin // Рыба if not WordBool(st and MOVE_INWATER) then - begin // Ðûáà âíå âîäû: + begin // Рыба вне воды: if g_Obj_CollideLevel(@FObj, 0, 1) or g_Obj_StayOnStep(@FObj) then - begin // "Ñòîèò" òâåðäî - // Ðûáà òðåïûõàåòñÿ íà ïîâåðõíîñòè: + begin // "Стоит" твердо + // Рыба трепыхается на поверхности: if FObj.Accel.Y = 0 then FObj.Vel.Y := -6; FObj.Accel.X := FObj.Accel.X - 8 + Random(17); end; - // Ðûáå áîëüíî: - SetState(STATE_PAIN); + // Рыбе больно: + SetState(MONSTATE_PAIN); FPain := FPain + 50; end - else // Ðûáà â âîäå + else // Рыба в воде begin - // Ïëûâåì â ñòîðîíó öåëè ïî-âåðòèêàëè: + // Плывем в сторону цели по-вертикали: if Abs(sy) > 8 then FObj.Vel.Y := g_basic.Sign(sy)*4 else FObj.Vel.Y := 0; - // Ðûáà ïëûâåò ââåðõ: + // Рыба плывет вверх: if FObj.Vel.Y < 0 then - if not g_Obj_CollideWater(@FObj, 0, -16) then + if not g_Obj_CollideLiquid(@FObj, 0, -16) then begin - // Âñïëûëè äî ïîâåðõíîñòè - ñòîï: + // Всплыли до поверхности - стоп: FObj.Vel.Y := 0; - // Ïëàâàåì òóäà-ñþäà: + // Плаваем туда-сюда: if Random(2) = 0 then - FDirection := D_LEFT + FDirection := TDirection.D_LEFT else - FDirection := D_RIGHT; + FDirection := TDirection.D_RIGHT; FSleep := 20; - SetState(STATE_RUN); + SetState(MONSTATE_RUN); end; end; end - else // Ëåòàþùèå ìîíñòðû + else // Летающие монстры begin - // Ëåòèì â ñòîðîíó öåëè ïî-âåðòèêàëè: + // Летим в сторону цели по-вертикали: if Abs(sy) > 8 then FObj.Vel.Y := g_basic.Sign(sy)*4 else FObj.Vel.Y := 0; end; end - else // "Íàçåìíûå" ìîíñòðû + else // "Наземные" монстры begin - // Âîçìîæíî, ïèíàåì êóñêè: + // Возможно, пинаем куски: if (FObj.Vel.X <> 0) and (gGibs <> nil) then begin b := Abs(FObj.Vel.X); if b > 1 then b := b * (Random(8 div b) + 1); for a := 0 to High(gGibs) do begin - if gGibs[a].Live and + if gGibs[a].alive and g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y+FObj.Rect.Height-4, FObj.Rect.Width, 8, @gGibs[a].Obj) and (Random(3) = 0) then begin - // Ïèíàåì êóñêè + // Пинаем куски if FObj.Vel.X < 0 then begin - g_Obj_PushA(@gGibs[a].Obj, b, Random(61)+120); // íàëåâî + g_Obj_PushA(@gGibs[a].Obj, b, Random(61)+120); // налево end else begin - g_Obj_PushA(@gGibs[a].Obj, b, Random(61)); // íàïðàâî + g_Obj_PushA(@gGibs[a].Obj, b, Random(61)); // направо end; end; end; end; - // Áîññû ìîãóò ïèíàòü òðóïû: + // Боссы могут пинать трупы: if (FMonsterType in [MONSTER_CYBER, MONSTER_SPIDER, MONSTER_ROBO]) and (FObj.Vel.X <> 0) and (gCorpses <> nil) then begin @@ -2637,24 +2842,24 @@ begin co := gCorpses[a].Obj; if g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y+FObj.Rect.Height-4, FObj.Rect.Width, 8, @co) and (Random(3) = 0) then - // Ïèíàåì òðóïû + // Пинаем трупы if FObj.Vel.X < 0 then - gCorpses[a].Damage(b*2, -b, Random(7)) // íàëåâî + gCorpses[a].Damage(b*2, FUID, -b, Random(7)) // налево else - gCorpses[a].Damage(b*2, b, Random(7)); // íàïðàâî + gCorpses[a].Damage(b*2, FUID, b, Random(7)); // направо end; end; - // Åñëè öåëü âûñîêî, òî, âîçìîæíî, ïðûãàåì: + // Если цель высоко, то, возможно, прыгаем: if sy < -40 then if g_Obj_CollideLevel(@FObj, 0, 1) or g_Obj_StayOnStep(@FObj) then - // ñòîèò òâåðäî + // стоит твердо if (Random(4) = 0) and (FObj.Accel.Y = 0) then FObj.Vel.Y := -MONSTERTABLE[FMonsterType].Jump; end; FSleep := FSleep + 1; - // Èíîãäà ðû÷èì: + // Иногда рычим: if FSleep >= 8 then begin FSleep := 0; @@ -2662,172 +2867,172 @@ begin ActionSound(); end; - // Áåæèì â âûáðàííóþ ñòîðîíó: - if FDirection = D_RIGHT then + // Бежим в выбранную сторону: + if FDirection = TDirection.D_RIGHT then FObj.Vel.X := MONSTERTABLE[FMonsterType].RunVel else FObj.Vel.X := -MONSTERTABLE[FMonsterType].RunVel; - // Åñëè â âîäå, òî çàìåäëÿåìñÿ: + // Если в воде, то замедляемся: if WordBool(st and MOVE_INWATER) then FObj.Vel.X := FObj.Vel.X div 2 - else // Ðûáàì íå íóæíî çàìåäëÿòüñÿ + else // Рыбам не нужно замедляться if FMonsterType = MONSTER_FISH then FObj.Vel.X := 0; end; - STATE_RUN: // Ñîñòîÿíèå - Áåã + MONSTATE_RUN: // Состояние - Бег begin - // Åñëè íàòêíóëèñü íà ÁëîêÌîí - óáåãàåì îò íåãî: + // Если наткнулись на БлокМон - убегаем от него: if WordBool(st and MOVE_BLOCK) then begin Turn(); FSleep := 40; - SetState(STATE_RUNOUT); + SetState(MONSTATE_RUNOUT); goto _end; end; FSleep := FSleep - 1; - // Ïðîáåæàëè äîñòàòî÷íî èëè âðåçàëèñü â ñòåíó => ïåðåõîäèì íà øàã: + // Пробежали достаточно или врезались в стену => переходим на шаг: if (FSleep <= 0) or (WordBool(st and MOVE_HITWALL) and ((FObj.Vel.Y+FObj.Accel.Y) = 0)) then begin FSleep := 0; - SetState(STATE_GO); - // Ñòåíà - èäåì îáðàòíî: + SetState(MONSTATE_GO); + // Стена - идем обратно: if WordBool(st and (MOVE_HITWALL or MOVE_BLOCK)) then Turn(); - // Èíîãäà ðû÷èì: + // Иногда рычим: if Random(8) = 0 then ActionSound(); end; - // Áåæèì â âûáðàííóþ ñòîðîíó: - if FDirection = D_RIGHT then + // Бежим в выбранную сторону: + if FDirection = TDirection.D_RIGHT then FObj.Vel.X := MONSTERTABLE[FMonsterType].RunVel else FObj.Vel.X := -MONSTERTABLE[FMonsterType].RunVel; - // Åñëè â âîäå, òî çàìåäëÿåìñÿ: + // Если в воде, то замедляемся: if WordBool(st and MOVE_INWATER) then FObj.Vel.X := FObj.Vel.X div 2 - else // Ðûáàì íå íóæíî çàìåäëÿòüñÿ + else // Рыбам не нужно замедляться if FMonsterType = MONSTER_FISH then FObj.Vel.X := 0; end; - STATE_RUNOUT: // Ñîñòîÿíèå - Óáåãàåò îò ÷åãî-òî + MONSTATE_RUNOUT: // Состояние - Убегает от чего-то begin - // Âûøëè èç ÁëîêÌîíà: + // Вышли из БлокМона: if (not WordBool(st and MOVE_BLOCK)) and (FSleep > 0) then FSleep := 0; FSleep := FSleep - 1; - // Óáàæåëè äîñòàòî÷íî äàëåêî => ïåðåõîäèì íà øàã: + // Убажели достаточно далеко => переходим на шаг: if FSleep <= -18 then begin FSleep := 0; - SetState(STATE_GO); - // Ñòåíà/ÁëîêÌîí - èäåì îáðàòíî: + SetState(MONSTATE_GO); + // Стена/БлокМон - идем обратно: if WordBool(st and (MOVE_HITWALL or MOVE_BLOCK)) then Turn(); - // Èíîãäà ðû÷èì: + // Иногда рычим: if Random(8) = 0 then ActionSound(); end; - // Áåæèì â âûáðàííóþ ñòîðîíó: - if FDirection = D_RIGHT then + // Бежим в выбранную сторону: + if FDirection = TDirection.D_RIGHT then FObj.Vel.X := MONSTERTABLE[FMonsterType].RunVel else FObj.Vel.X := -MONSTERTABLE[FMonsterType].RunVel; - // Åñëè â âîäå, òî çàìåäëÿåìñÿ: + // Если в воде, то замедляемся: if WordBool(st and MOVE_INWATER) then FObj.Vel.X := FObj.Vel.X div 2 - else // Ðûáàì íå íóæíî çàìåäëÿòüñÿ + else // Рыбам не нужно замедляться if FMonsterType = MONSTER_FISH then FObj.Vel.X := 0; end; - STATE_CLIMB: // Ñîñòîÿíèå - Ïðûæîê (÷òîáû îáîéòè ñòåíó) + MONSTATE_CLIMB: // Состояние - Прыжок (чтобы обойти стену) begin - // Äîñòèãëè âûñøåé òî÷êè ïðûæêà èëè ñòåíà êîí÷èëàñü => ïåðåõîäèì íà øàã: + // Достигли высшей точки прыжка или стена кончилась => переходим на шаг: if ((FObj.Vel.Y+FObj.Accel.Y) >= 0) or (not WordBool(st and MOVE_HITWALL)) then begin FSleep := 0; - SetState(STATE_GO); + SetState(MONSTATE_GO); - // Ñòåíà íå êîí÷èëàñü => áåæèì îò íåå: + // Стена не кончилась => бежим от нее: if WordBool(st and (MOVE_HITWALL or MOVE_BLOCK)) then begin Turn(); FSleep := 15; - SetState(STATE_RUN); + SetState(MONSTATE_RUN); end; end; - // Áåæèì â âûáðàííóþ ñòîðîíó: - if FDirection = D_RIGHT then + // Бежим в выбранную сторону: + if FDirection = TDirection.D_RIGHT then FObj.Vel.X := MONSTERTABLE[FMonsterType].RunVel else FObj.Vel.X := -MONSTERTABLE[FMonsterType].RunVel; - // Åñëè â âîäå, òî çàìåäëÿåìñÿ: + // Если в воде, то замедляемся: if WordBool(st and MOVE_INWATER) then FObj.Vel.X := FObj.Vel.X div 2 - else // Ðûáàì íå íóæíî çàìåäëÿòüñÿ + else // Рыбам не нужно замедляться if FMonsterType = MONSTER_FISH then FObj.Vel.X := 0; end; - STATE_ATTACK, // Ñîñòîÿíèå - Àòàêà - STATE_SHOOT: // Ñîñòîÿíèå - Ñòðåëüáà + MONSTATE_ATTACK, // Состояние - Атака + MONSTATE_SHOOT: // Состояние - Стрельба begin - // Lost_Soul âðåçàëñÿ â ñòåíó ïðè àòàêå => ïåðåõîäèò íà øàã: + // Lost_Soul врезался в стену при атаке => переходит на шаг: if FMonsterType = MONSTER_SOUL then begin if WordBool(st and (MOVE_HITWALL or MOVE_HITCEIL or MOVE_HITLAND)) then - SetState(STATE_GO); + SetState(MONSTATE_GO); goto _end; end; - // Çàìåäëÿåìñÿ ïðè àòàêå: + // Замедляемся при атаке: if FMonsterType <> MONSTER_FISH then FObj.Vel.X := z_dec(FObj.Vel.X, 1); - // Íóæíî ñòðåëÿòü, à ìîíñòð - êîëäóí: - if (FMonsterType = MONSTER_VILE) and (FState = STATE_SHOOT) then + // Нужно стрелять, а монстр - колдун: + if (FMonsterType = MONSTER_VILE) and (FState = MONSTATE_SHOOT) then begin - // Öåëü ïîãèáëà => èäåì äàëüøå: + // Цель погибла => идем дальше: if not GetPos(FTargetUID, @o) then begin - SetState(STATE_GO); + SetState(MONSTATE_GO); goto _end; end; - // Öåëü íå âèäíî => èäåì äàëüøå: + // Цель не видно => идем дальше: if not g_Look(@FObj, @o, FDirection) then begin - SetState(STATE_GO); + SetState(MONSTATE_GO); goto _end; end; - // Öåëü â âîäå - íå çàãîðèòñÿ => èäåì äàëüøå: + // Цель в воде - не загорится => идем дальше: if g_Obj_CollideWater(@o, 0, 0) then begin - SetState(STATE_GO); + SetState(MONSTATE_GO); goto _end; end; - // Æàðèì öåëü: + // Жарим цель: tx := o.X+o.Rect.X+(o.Rect.Width div 2); ty := o.Y+o.Rect.Y+(o.Rect.Height div 2); g_Weapon_HitUID(FTargetUID, 2, FUID, HIT_SOME); @@ -2837,54 +3042,54 @@ begin _end: -// Ñîñòîÿíèå - Âîñêðåøåíèå: - if FState = STATE_REVIVE then +// Состояние - Воскрешение: + if FState = MONSTATE_REVIVE then if FAnim[FCurAnim, FDirection].Played then - begin // Îáðàòíàÿ àíèìàöèÿ óìèðàíèÿ çàêîí÷èëàñü - èäåì äàëüøå: + begin // Обратная анимация умирания закончилась - идем дальше: FAnim[FCurAnim, FDirection].Revert(False); - SetState(STATE_GO); + SetState(MONSTATE_GO); end; -// Åñëè åñòü àíèìàöèÿ îãíÿ êîëäóíà - ïóñòü îíà èäåò: +// Если есть анимация огня колдуна - пусть она идет: if vilefire <> nil then vilefire.Update(); -// Ñîñòîÿíèå - Óìèðàåò è òåêóùàÿ àíèìàöèÿ ïðîèãðàíà: - if (FState = STATE_DIE) and +// Состояние - Умирает и текущая анимация проиграна: + if (FState = MONSTATE_DIE) and (FAnim[FCurAnim, FDirection] <> nil) and (FAnim[FCurAnim, FDirection].Played) then begin - // Óìåð: - SetState(STATE_DEAD); + // Умер: + SetState(MONSTATE_DEAD); - // Pain_Elemental ïðè ñìåðòè âûïóñêàåò 3 Lost_Soul'à: + // Pain_Elemental при смерти выпускает 3 Lost_Soul'а: if (FMonsterType = MONSTER_PAIN) then begin mon := g_Monsters_Create(MONSTER_SOUL, FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)-30, - FObj.Y+FObj.Rect.Y+20, D_LEFT); + FObj.Y+FObj.Rect.Y+20, TDirection.D_LEFT); if mon <> nil then begin - mon.SetState(STATE_GO); + mon.SetState(MONSTATE_GO); mon.FNoRespawn := True; Inc(gTotalMonsters); if g_Game_IsNet then MH_SEND_MonsterSpawn(mon.UID); end; mon := g_Monsters_Create(MONSTER_SOUL, FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2), - FObj.Y+FObj.Rect.Y+20, D_RIGHT); + FObj.Y+FObj.Rect.Y+20, TDirection.D_RIGHT); if mon <> nil then begin - mon.SetState(STATE_GO); + mon.SetState(MONSTATE_GO); mon.FNoRespawn := True; Inc(gTotalMonsters); if g_Game_IsNet then MH_SEND_MonsterSpawn(mon.UID); end; mon := g_Monsters_Create(MONSTER_SOUL, FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)-15, - FObj.Y+FObj.Rect.Y, D_RIGHT); + FObj.Y+FObj.Rect.Y, TDirection.D_RIGHT); if mon <> nil then begin - mon.SetState(STATE_GO); + mon.SetState(MONSTATE_GO); mon.FNoRespawn := True; Inc(gTotalMonsters); if g_Game_IsNet then MH_SEND_MonsterSpawn(mon.UID); @@ -2893,35 +3098,35 @@ _end: if g_Game_IsNet then MH_SEND_CoopStats(); end; - // Ó ýòèõ ìîíñòðîâ íåò òðóïîâ: + // У этих монстров нет трупов: if (FMonsterType = MONSTER_PAIN) or (FMonsterType = MONSTER_SOUL) or (FMonsterType = MONSTER_BARREL) then FRemoved := True; end; -// Ñîâåðøåíèå àòàêè è ñòðåëüáû: - if (FState = STATE_ATTACK) or (FState = STATE_SHOOT) then +// Совершение атаки и стрельбы: + if (FState = MONSTATE_ATTACK) or (FState = MONSTATE_SHOOT) then if (FAnim[FCurAnim, FDirection] <> nil) then - // Àíèìàöèÿ àòàêè åñòü - ìîæíî àòàêîâàòü + // Анимация атаки есть - можно атаковать if (FAnim[FCurAnim, FDirection].Played) then - begin // Àíèìàöèÿ àòàêè çàêîí÷èëàñü => ïåðåõîäèì íà øàã - if FState = STATE_ATTACK then - begin // Ñîñòîÿíèå - Àòàêà - // Åñëè ìîíñòð íå Lost_Soul, òî ïîñëå àòàêè ïåðåõîäèì íà øàã: + begin // Анимация атаки закончилась => переходим на шаг + if FState = MONSTATE_ATTACK then + begin // Состояние - Атака + // Если монстр не Lost_Soul, то после атаки переходим на шаг: if FMonsterType <> MONSTER_SOUL then - SetState(STATE_GO); + SetState(MONSTATE_GO); end - else // Ñîñòîÿíèå - Ñòðåëüáà + else // Состояние - Стрельба begin - // Ïåðåõîäèì íà øàã, åñëè íå íàäî ñòðåëÿòü åùå ðàç: + // Переходим на шаг, если не надо стрелять еще раз: if not FChainFire then - SetState(STATE_GO) + SetState(MONSTATE_GO) else - begin // Íàäî ñòðåëÿòü åùå + begin // Надо стрелять еще FChainFire := False; - // Ò.ê. ñîñòîÿíèå íå èçìåíèëîñü, è íóæåí - // íîâûé öèêë îæèäàíèÿ çàâåðøåíèÿ àíèìàöèè: + // Т.к. состояние не изменилось, и нужен + // новый цикл ожидания завершения анимации: FAnim[FCurAnim, FDirection].Reset(); end; end; @@ -2929,80 +3134,93 @@ _end: FWaitAttackAnim := False; end - else // Àíèìàöèÿ àòàêè åùå èäåò (èñêëþ÷åíèå - Lost_Soul): + else // Анимация атаки еще идет (исключение - Lost_Soul): if (FMonsterType = MONSTER_SOUL) or ( (not FWaitAttackAnim) and (FAnim[FCurAnim, FDirection].CurrentFrame = (FAnim[FCurAnim, FDirection].TotalFrames div 2)) ) then - begin // Àòàêè åùå íå áûëî è ýòî ñåðåäèíà àíèìàöèè àòàêè - if FState = STATE_ATTACK then - begin // Ñîñòîÿíèå - Àòàêà - // Åñëè ýòî Lost_Soul, òî ñáðàñûâàåì àíèìàöèþ àòàêè: + begin // Атаки еще не было и это середина анимации атаки + if FState = MONSTATE_ATTACK then + begin // Состояние - Атака + // Если это Lost_Soul, то сбрасываем анимацию атаки: if FMonsterType = MONSTER_SOUL then FAnim[FCurAnim, FDirection].Reset(); case FMonsterType of MONSTER_SOUL, MONSTER_IMP, MONSTER_DEMON: - // Ãðûçåì ïåðâîãî ïîïàâøåãîñÿ: + // Грызем первого попавшегося: if g_Weapon_Hit(@FObj, 15, FUID, HIT_SOME) <> 0 then - // Lost_Soul óêóñèë êîãî-òî => ïåðåõîäèò íà øàã: + // Lost_Soul укусил кого-то => переходит на шаг: if FMonsterType = MONSTER_SOUL then - SetState(STATE_GO); + SetState(MONSTATE_GO); MONSTER_FISH: - // Ðûáà êóñàåò ïåðâîãî ïîïàâøåãîñÿ ñî çâóêîì: + // Рыба кусает первого попавшегося со звуком: if g_Weapon_Hit(@FObj, 10, FUID, HIT_SOME) <> 0 then g_Sound_PlayExAt('SOUND_MONSTER_FISH_ATTACK', FObj.X, FObj.Y); MONSTER_SKEL, MONSTER_ROBO, MONSTER_CYBER: - // Ðîáîò, êèáåð èëè ñêåëåò ñèëüíî ïèíàþòñÿ: + // Робот, кибер или скелет сильно пинаются: if FCurAnim = ANIM_ATTACK2 then begin o := FObj; - o.Vel.X := IfThen(FDirection = D_RIGHT, 1, -1)*IfThen(FMonsterType = MONSTER_CYBER, 60, 50); + o.Vel.X := IfThen(FDirection = TDirection.D_RIGHT, 1, -1)*IfThen(FMonsterType = MONSTER_CYBER, 60, 50); if g_Weapon_Hit(@o, IfThen(FMonsterType = MONSTER_CYBER, 33, 50), FUID, HIT_SOME) <> 0 then g_Sound_PlayExAt('SOUND_MONSTER_SKEL_HIT', FObj.X, FObj.Y); end; MONSTER_VILE: - // Êîëäóí ïûòàåòñÿ âîñêðåøàòü: + // Колдун пытается воскрешать: if FCurAnim = ANIM_ATTACK2 then begin sx := isCorpse(@FObj, True); if sx <> -1 then - begin // Íàøëè, êîãî âîñêðåñèòü - gMonsters[sx].SetState(STATE_REVIVE); + begin // Нашли, кого воскресить + gMonsters[sx].SetState(MONSTATE_REVIVE); g_Sound_PlayExAt('SOUND_MONSTER_SLOP', FObj.X, FObj.Y); - // Âîñêðåøàòü - ñåáå âðåäèòü: + // Воскрешать - себе вредить: {g_Weapon_HitUID(FUID, 5, 0, HIT_SOME);} end; end; end; end - else // Ñîñòîÿíèå - Ñòðåëüáà + else // Состояние - Стрельба begin - // Âû÷èñëÿåì êîîðäèíàòû, îòêóäà âûëåòèò ïóëÿ: + // Вычисляем координаты, откуда вылетит пуля: wx := MONSTER_ANIMTABLE[FMonsterType].wX; - if FDirection = D_LEFT then + if FDirection = TDirection.D_LEFT then begin wx := MONSTER_ANIMTABLE[FMonsterType].wX-(MONSTERTABLE[FMonsterType].Rect.X+(MONSTERTABLE[FMonsterType].Rect.Width div 2)); wx := MONSTERTABLE[FMonsterType].Rect.X+(MONSTERTABLE[FMonsterType].Rect.Width div 2)-wx; - end; // Ýòî çíà÷èò: wx := hitX + (hitWidth / 2) - (wx - (hitX + (hitWidth / 2))) + end; // Это значит: wx := hitX + (hitWidth / 2) - (wx - (hitX + (hitWidth / 2))) wx := FObj.X + wx; wy := FObj.Y + MONSTER_ANIMTABLE[FMonsterType].wY; - // Äåëàåì âûñòðåë íóæíûì îðóæèåì: + // Монстр не может целиться в объект за спиной, стреляя влево: + if (FDirection = TDirection.D_LEFT) and (tx > wx) then + begin + tx := wx - 32; + ty := wy + Random(11) - 5; + end; + // И аналогично, стреляя вправо: + if (FDirection = TDirection.D_RIGHT) and (tx < wx) then + begin + tx := wx + 32; + ty := wy + Random(11) - 5; + end; + + // Делаем выстрел нужным оружием: case FMonsterType of MONSTER_IMP: g_Weapon_ball1(wx, wy, tx, ty, FUID); MONSTER_ZOMBY: begin g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', wx, wy); - g_Weapon_gun(wx, wy, tx, ty, 1, 3, FUID, False); + g_Weapon_gun(wx, wy, tx, ty, 1, 3, FUID, True); g_Player_CreateShell(wx, wy, 0, -2, SHELL_BULLET); end; MONSTER_SERG: @@ -3049,17 +3267,17 @@ _end: MONSTER_CACO: g_Weapon_ball2(wx, wy, tx, ty, FUID); MONSTER_PAIN: - begin // Ñîçäàåì Lost_Soul: + begin // Создаем Lost_Soul: mon := g_Monsters_Create(MONSTER_SOUL, FObj.X+(FObj.Rect.Width div 2), FObj.Y+FObj.Rect.Y, FDirection); if mon <> nil then - begin // Öåëü - öåëü Pain_Elemental'à. Ëåòèì ê íåé: + begin // Цель - цель Pain_Elemental'а. Летим к ней: mon.FTargetUID := FTargetUID; GetPos(FTargetUID, @o); mon.FTargetTime := 0; mon.FNoRespawn := True; - mon.SetState(STATE_GO); + mon.SetState(MONSTATE_GO); mon.shoot(@o, True); Inc(gTotalMonsters); @@ -3072,29 +3290,29 @@ _end: if g_Game_IsNet then MH_SEND_MonsterShot(FUID, wx, wy, tx, ty); - // Ñêîðîñòðåëüíûå ìîíñòðû: + // Скорострельные монстры: if (FMonsterType = MONSTER_CGUN) or (FMonsterType = MONSTER_SPIDER) or (FMonsterType = MONSTER_BSP) or (FMonsterType = MONSTER_MANCUB) or (FMonsterType = MONSTER_ROBO) then if not GetPos(FTargetUID, @o) then - // Öåëü ìåðòâà - èùåì íîâóþ: + // Цель мертва - ищем новую: findNewPrey() - else // Öåëü æèâà - ïðîäîëæàåì ñòðåëÿòü: + else // Цель жива - продолжаем стрелять: if shoot(@o, False) then FChainFire := True; end; - // Àòàêà òîëüêî 1 ðàç çà àíèìàöèþ àòàêè: + // Атака только 1 раз за анимацию атаки: FWaitAttackAnim := True; end; -// Ïîñëåäíèé êàäð òåêóùåé àíèìàöèè: +// Последний кадр текущей анимации: if FAnim[FCurAnim, FDirection].Counter = FAnim[FCurAnim, FDirection].Speed-1 then case FState of - STATE_GO, STATE_RUN, STATE_CLIMB, STATE_RUNOUT: - // Çâóêè ïðè ïåðåäâèæåíèè: + MONSTATE_GO, MONSTATE_RUN, MONSTATE_CLIMB, MONSTATE_RUNOUT: + // Звуки при передвижении: case FMonsterType of MONSTER_CYBER: if (FAnim[FCurAnim, FDirection].CurrentFrame = 0) or @@ -3116,10 +3334,10 @@ _end: end; if g_Obj_CollidePanel(@FObj, 0, 0, PANEL_LIFTLEFT or PANEL_LIFTRIGHT) and - not ((FState = STATE_DEAD) or (FState = STATE_DIE)) then + not ((FState = MONSTATE_DEAD) or (FState = MONSTATE_DIE)) then FObj.Vel.X := oldvelx; -// Åñëè åñòü àíèìàöèÿ, òî ïóñòü îíà èäåò: +// Если есть анимация, то пусть она идет: if FAnim[FCurAnim, FDirection] <> nil then FAnim[FCurAnim, FDirection].Update(); end; @@ -3157,21 +3375,25 @@ begin sx := 0; // SHUT UP COMPILER sy := 0; fall := True; -// Ðûáû "ëåòàþò" òîëüêî â âîäå: + +// Монстр статичен пока идет warmup + if (gLMSRespawn > LMS_RESPAWN_NONE) then exit; + +// Рыбы "летают" только в воде: if FMonsterType = MONSTER_FISH then if g_Obj_CollidePanel(@FObj, 0, 0, PANEL_WATER or PANEL_ACID1 or PANEL_ACID2) then - if (FState <> STATE_DIE) and (FState <> STATE_DEAD) then + if (FState <> MONSTATE_DIE) and (FState <> MONSTATE_DEAD) then fall := False; -// Ëåòàþùèå ìîíòñðû: +// Летающие монтсры: if ((FMonsterType = MONSTER_SOUL) or (FMonsterType = MONSTER_PAIN) or (FMonsterType = MONSTER_CACO)) and - (FState <> STATE_DIE) and - (FState <> STATE_DEAD) then + (FState <> MONSTATE_DIE) and + (FState <> MONSTATE_DEAD) then fall := False; -// Ìåíÿåì ñêîðîñòü òîëüêî ïî ÷åòíûì êàäðàì: +// Меняем скорость только по четным кадрам: if gTime mod (GAME_TICK*2) <> 0 then begin g_Obj_Move(@FObj, fall, True, True); @@ -3184,11 +3406,11 @@ begin else FPainSound := False; -// Äâèãàåìñÿ: +// Двигаемся: st := g_Obj_Move(@FObj, fall, True, True); positionChanged(); // this updates spatial accelerators -// Âûëåòåë çà êàðòó - óäàëÿåì è çàïóñêàåì òðèããåðû: +// Вылетел за карту - удаляем и запускаем триггеры: if WordBool(st and MOVE_FALLOUT) or (FObj.X < -1000) or (FObj.X > gMapInfo.Width+1000) or (FObj.Y < -1000) then begin @@ -3198,8 +3420,8 @@ begin oldvelx := FObj.Vel.X; -// Ñîïðîòèâëåíèå âîçäóõà äëÿ òðóïà: - if (FState = STATE_DIE) or (FState = STATE_DEAD) then +// Сопротивление воздуха для трупа: + if (FState = MONSTATE_DIE) or (FState = MONSTATE_DEAD) then FObj.Vel.X := z_dec(FObj.Vel.X, 1); if FFireTime > 0 then @@ -3213,11 +3435,11 @@ begin end; end; -// Ìåðòâûé íè÷åãî íå äåëàåò: - if (FState = STATE_DEAD) then +// Мертвый ничего не делает: + if (FState = MONSTATE_DEAD) then goto _end; -// Âîçìîæíî, ñîçäàåì ïóçûðüêè â âîäå: +// Возможно, создаем пузырьки в воде: if WordBool(st and MOVE_INWATER) and (Random(32) = 0) then case FMonsterType of MONSTER_FISH: @@ -3237,17 +3459,17 @@ begin end; end; -// Åñëè ïðîøåë ïåðâûé êàäð àíèìàöèè âçðûâà áî÷êè, òî âçðûâ: +// Если прошел первый кадр анимации взрыва бочки, то взрыв: if FMonsterType = MONSTER_BARREL then begin - if (FState = STATE_DIE) and (FAnim[FCurAnim, FDirection].CurrentFrame = 1) and + if (FState = MONSTATE_DIE) and (FAnim[FCurAnim, FDirection].CurrentFrame = 1) and (FAnim[FCurAnim, FDirection].Counter = 0) then g_Weapon_Explode(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2), FObj.Y+FObj.Rect.Y+FObj.Rect.Height-16, 60, FUID); end; -// Lost_Soul âûëåòåë èç âîäû => óñêîðÿåòñÿ: +// Lost_Soul вылетел из воды => ускоряется: if FMonsterType = MONSTER_SOUL then if WordBool(st and MOVE_HITAIR) then g_Obj_SetSpeed(@FObj, 16); @@ -3255,12 +3477,12 @@ begin if FAmmo < 0 then FAmmo := FAmmo + 1; -// Åñëè íà÷àëè âñïëûâàòü, òî ïðîäîëæàåì: +// Если начали всплывать, то продолжаем: if FObj.Vel.Y < 0 then if WordBool(st and MOVE_INWATER) then FObj.Vel.Y := -4; -// Òàéìåð - æäåì ïîñëå ïîòåðè öåëè: +// Таймер - ждем после потери цели: FTargetTime := FTargetTime + 1; if FShellTimer > -1 then @@ -3282,10 +3504,10 @@ begin FShellTimer := -1; end else Dec(FShellTimer); -// Ïðîáóåì óâåðíóòüñÿ îò ëåòÿùåé ïóëè: +// Пробуем увернуться от летящей пули: if fall then - if (FState in [STATE_GO, STATE_RUN, STATE_RUNOUT, - STATE_ATTACK, STATE_SHOOT]) then + if (FState in [MONSTATE_GO, MONSTATE_RUN, MONSTATE_RUNOUT, + MONSTATE_ATTACK, MONSTATE_SHOOT]) then if g_Weapon_Danger(FUID, FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, FObj.Rect.Width, FObj.Rect.Height, 50) then if (g_Obj_CollideLevel(@FObj, 0, 1) or g_Obj_StayOnStep(@FObj)) and @@ -3293,9 +3515,9 @@ begin FObj.Vel.Y := -MONSTERTABLE[FMonsterType].Jump; case FState of - STATE_PAIN: // Ñîñòîÿíèå - Áîëü + MONSTATE_PAIN: // Состояние - Боль begin - // Áîëü ñèëüíàÿ => ìîíñòð êðè÷èò: + // Боль сильная => монстр кричит: if FPain >= MONSTERTABLE[FMonsterType].Pain then begin FPain := MONSTERTABLE[FMonsterType].Pain; @@ -3304,161 +3526,161 @@ begin if (not gSoundEffectsDF) and (FPain >= MONSTERTABLE[FMonsterType].MinPain) then PainSound(); - // Ñíèæàåì áîëü ñî âðåìåíåì: + // Снижаем боль со временем: FPain := FPain - 5; - // Áîëü óæå íå îøóòèìàÿ => èäåì äàëüøå: + // Боль уже не ошутимая => идем дальше: if FPain <= MONSTERTABLE[FMonsterType].MinPain then begin - SetState(STATE_GO); + SetState(MONSTATE_GO); FPain := 0; end; end; - STATE_SLEEP: // Ñîñòîÿíèå - Ñîí + MONSTATE_SLEEP: // Состояние - Сон begin - // Ñïèì: + // Спим: FSleep := FSleep + 1; - // Ïðîñïàëè äîñòàòî÷íî: + // Проспали достаточно: if FSleep >= 18 then FSleep := 0 - else // åùå ñïèì + else // еще спим goto _end; end; - STATE_WAIT: // Ñîñòîÿíèå - Îæèäàíèå + MONSTATE_WAIT: // Состояние - Ожидание begin - // Æäåì: + // Ждем: FSleep := FSleep - 1; end; - STATE_GO: // Ñîñòîÿíèå - Äâèæåíèå (ñ îñìîòðîì ñèòóàöèè) + MONSTATE_GO: // Состояние - Движение (с осмотром ситуации) begin - // Åñëè íàòêíóëèñü íà ÁëîêÌîí - óáåãàåì îò íåãî: + // Если наткнулись на БлокМон - убегаем от него: if WordBool(st and MOVE_BLOCK) then begin Turn(); FSleep := 40; - SetState(STATE_RUNOUT); + SetState(MONSTATE_RUNOUT); goto _end; end; - // Åñëè ìîíñòð - êîëäóí, òî ïðîáóåì âîñêðåñèòü êîãî-íèáóäü: + // Если монстр - колдун, то пробуем воскресить кого-нибудь: if (FMonsterType = MONSTER_VILE) then if isCorpse(@FObj, False) <> -1 then begin - SetState(STATE_ATTACK, ANIM_ATTACK2); + SetState(MONSTATE_ATTACK, ANIM_ATTACK2); FObj.Vel.X := 0; goto _end; end; - // Åñëè öåëü ïî÷òè íà îäíîé âåðòèêàëè - áåãàåì òóäà-ñþäà: + // Если цель почти на одной вертикали - бегаем туда-сюда: if Abs(sx) < 40 then if FMonsterType <> MONSTER_FISH then begin - SetState(STATE_RUN); + SetState(MONSTATE_RUN); FSleep := 15; goto _end; end; - // Óïåðëèñü â ñòåíó: + // Уперлись в стену: if WordBool(st and MOVE_HITWALL) then begin case FMonsterType of MONSTER_CACO, MONSTER_SOUL, MONSTER_PAIN, MONSTER_FISH: ; - else // Íå ëåòàþò: + else // Не летают: if (g_Obj_CollideLevel(@FObj, 0, 1) or g_Obj_StayOnStep(@FObj)) and (FObj.Accel.Y = 0) then - begin // Ñòîèì íà òâåðäîì ïîëó èëè ñòóïåíè - // Ïðûæîê ÷åðåç ñòåíó: + begin // Стоим на твердом полу или ступени + // Прыжок через стену: FObj.Vel.Y := -MONSTERTABLE[FMonsterType].Jump; - SetState(STATE_CLIMB); + SetState(MONSTATE_CLIMB); end; end; goto _end; end; - // Ìîíñòðû, íå ïîäâåðæåííûå ãðàâèòàöèè: + // Монстры, не подверженные гравитации: if (FMonsterType = MONSTER_CACO) or (FMonsterType = MONSTER_SOUL) or (FMonsterType = MONSTER_PAIN) or (FMonsterType = MONSTER_FISH) then begin if FMonsterType = MONSTER_FISH then - begin // Ðûáà + begin // Рыба if not WordBool(st and MOVE_INWATER) then - begin // Ðûáà âíå âîäû: + begin // Рыба вне воды: if g_Obj_CollideLevel(@FObj, 0, 1) or g_Obj_StayOnStep(@FObj) then - begin // "Ñòîèò" òâåðäî - // Ðûáà òðåïûõàåòñÿ íà ïîâåðõíîñòè: + begin // "Стоит" твердо + // Рыба трепыхается на поверхности: if FObj.Accel.Y = 0 then FObj.Vel.Y := -6; FObj.Accel.X := FObj.Accel.X - 8 + Random(17); end; - // Ðûáå áîëüíî: - SetState(STATE_PAIN); + // Рыбе больно: + SetState(MONSTATE_PAIN); FPain := FPain + 50; end - else // Ðûáà â âîäå + else // Рыба в воде begin - // Ïëûâåì â ñòîðîíó öåëè ïî-âåðòèêàëè: + // Плывем в сторону цели по-вертикали: if Abs(sy) > 8 then FObj.Vel.Y := g_basic.Sign(sy)*4 else FObj.Vel.Y := 0; - // Ðûáà ïëûâåò ââåðõ: + // Рыба плывет вверх: if FObj.Vel.Y < 0 then - if not g_Obj_CollideWater(@FObj, 0, -16) then + if not g_Obj_CollideLiquid(@FObj, 0, -16) then begin - // Âñïëûëè äî ïîâåðõíîñòè - ñòîï: + // Всплыли до поверхности - стоп: FObj.Vel.Y := 0; - // Ïëàâàåì òóäà-ñþäà: - SetState(STATE_RUN); + // Плаваем туда-сюда: + SetState(MONSTATE_RUN); FSleep := 20; end; end; end - else // Ëåòàþùèå ìîíñòðû + else // Летающие монстры begin - // Ëåòèì â ñòîðîíó öåëè ïî-âåðòèêàëè: + // Летим в сторону цели по-вертикали: if Abs(sy) > 8 then FObj.Vel.Y := g_basic.Sign(sy)*4 else FObj.Vel.Y := 0; end; end - else // "Íàçåìíûå" ìîíñòðû + else // "Наземные" монстры begin - // Âîçìîæíî, ïèíàåì êóñêè: + // Возможно, пинаем куски: if (FObj.Vel.X <> 0) and (gGibs <> nil) then begin b := Abs(FObj.Vel.X); if b > 1 then b := b * (Random(8 div b) + 1); for a := 0 to High(gGibs) do begin - if gGibs[a].Live and + if gGibs[a].alive and g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y+FObj.Rect.Height-4, FObj.Rect.Width, 8, @gGibs[a].Obj) and (Random(3) = 0) then begin - // Ïèíàåì êóñêè + // Пинаем куски if FObj.Vel.X < 0 then begin - g_Obj_PushA(@gGibs[a].Obj, b, Random(61)+120); // íàëåâî + g_Obj_PushA(@gGibs[a].Obj, b, Random(61)+120); // налево end else begin - g_Obj_PushA(@gGibs[a].Obj, b, Random(61)); // íàïðàâî + g_Obj_PushA(@gGibs[a].Obj, b, Random(61)); // направо end; positionChanged(); // this updates spatial accelerators end; end; end; - // Áîññû ìîãóò ïèíàòü òðóïû: + // Боссы могут пинать трупы: if (FMonsterType in [MONSTER_CYBER, MONSTER_SPIDER, MONSTER_ROBO]) and (FObj.Vel.X <> 0) and (gCorpses <> nil) then begin @@ -3470,18 +3692,18 @@ begin co := gCorpses[a].Obj; if g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y+FObj.Rect.Height-4, FObj.Rect.Width, 8, @co) and (Random(3) = 0) then - // Ïèíàåì òðóïû + // Пинаем трупы if FObj.Vel.X < 0 then - gCorpses[a].Damage(b*2, -b, Random(7)) // íàëåâî + gCorpses[a].Damage(b*2, FUID, -b, Random(7)) // налево else - gCorpses[a].Damage(b*2, b, Random(7)); // íàïðàâî + gCorpses[a].Damage(b*2, FUID, b, Random(7)); // направо end; end; end; FSleep := FSleep + 1; - // Èíîãäà ðû÷èì: + // Иногда рычим: if FSleep >= 8 then begin FSleep := 0; @@ -3489,26 +3711,26 @@ begin ActionSound(); end; - // Áåæèì â âûáðàííóþ ñòîðîíó: - if FDirection = D_RIGHT then + // Бежим в выбранную сторону: + if FDirection = TDirection.D_RIGHT then FObj.Vel.X := MONSTERTABLE[FMonsterType].RunVel else FObj.Vel.X := -MONSTERTABLE[FMonsterType].RunVel; - // Åñëè â âîäå, òî çàìåäëÿåìñÿ: + // Если в воде, то замедляемся: if WordBool(st and MOVE_INWATER) then FObj.Vel.X := FObj.Vel.X div 2 - else // Ðûáàì íå íóæíî çàìåäëÿòüñÿ + else // Рыбам не нужно замедляться if FMonsterType = MONSTER_FISH then FObj.Vel.X := 0; end; - STATE_RUN: // Ñîñòîÿíèå - Áåã + MONSTATE_RUN: // Состояние - Бег begin - // Åñëè íàòêíóëèñü íà ÁëîêÌîí - óáåãàåì îò íåãî: + // Если наткнулись на БлокМон - убегаем от него: if WordBool(st and MOVE_BLOCK) then begin - SetState(STATE_RUNOUT); + SetState(MONSTATE_RUNOUT); FSleep := 40; goto _end; @@ -3516,134 +3738,134 @@ begin FSleep := FSleep - 1; - // Ïðîáåæàëè äîñòàòî÷íî èëè âðåçàëèñü â ñòåíó => ïåðåõîäèì íà øàã: + // Пробежали достаточно или врезались в стену => переходим на шаг: if (FSleep <= 0) or (WordBool(st and MOVE_HITWALL) and ((FObj.Vel.Y+FObj.Accel.Y) = 0)) then begin - SetState(STATE_GO); + SetState(MONSTATE_GO); FSleep := 0; - // Èíîãäà ðû÷èì: + // Иногда рычим: if Random(8) = 0 then ActionSound(); end; - // Áåæèì â âûáðàííóþ ñòîðîíó: - if FDirection = D_RIGHT then + // Бежим в выбранную сторону: + if FDirection = TDirection.D_RIGHT then FObj.Vel.X := MONSTERTABLE[FMonsterType].RunVel else FObj.Vel.X := -MONSTERTABLE[FMonsterType].RunVel; - // Åñëè â âîäå, òî çàìåäëÿåìñÿ: + // Если в воде, то замедляемся: if WordBool(st and MOVE_INWATER) then FObj.Vel.X := FObj.Vel.X div 2 - else // Ðûáàì íå íóæíî çàìåäëÿòüñÿ + else // Рыбам не нужно замедляться if FMonsterType = MONSTER_FISH then FObj.Vel.X := 0; end; - STATE_RUNOUT: // Ñîñòîÿíèå - Óáåãàåò îò ÷åãî-òî + MONSTATE_RUNOUT: // Состояние - Убегает от чего-то begin - // Âûøëè èç ÁëîêÌîíà: + // Вышли из БлокМона: if (not WordBool(st and MOVE_BLOCK)) and (FSleep > 0) then FSleep := 0; FSleep := FSleep - 1; - // Óáàæåëè äîñòàòî÷íî äàëåêî => ïåðåõîäèì íà øàã: + // Убажели достаточно далеко => переходим на шаг: if FSleep <= -18 then begin - SetState(STATE_GO); + SetState(MONSTATE_GO); FSleep := 0; - // Èíîãäà ðû÷èì: + // Иногда рычим: if Random(8) = 0 then ActionSound(); end; - // Áåæèì â âûáðàííóþ ñòîðîíó: - if FDirection = D_RIGHT then + // Бежим в выбранную сторону: + if FDirection = TDirection.D_RIGHT then FObj.Vel.X := MONSTERTABLE[FMonsterType].RunVel else FObj.Vel.X := -MONSTERTABLE[FMonsterType].RunVel; - // Åñëè â âîäå, òî çàìåäëÿåìñÿ: + // Если в воде, то замедляемся: if WordBool(st and MOVE_INWATER) then FObj.Vel.X := FObj.Vel.X div 2 - else // Ðûáàì íå íóæíî çàìåäëÿòüñÿ + else // Рыбам не нужно замедляться if FMonsterType = MONSTER_FISH then FObj.Vel.X := 0; end; - STATE_CLIMB: // Ñîñòîÿíèå - Ïðûæîê (÷òîáû îáîéòè ñòåíó) + MONSTATE_CLIMB: // Состояние - Прыжок (чтобы обойти стену) begin - // Äîñòèãëè âûñøåé òî÷êè ïðûæêà èëè ñòåíà êîí÷èëàñü => ïåðåõîäèì íà øàã: + // Достигли высшей точки прыжка или стена кончилась => переходим на шаг: if ((FObj.Vel.Y+FObj.Accel.Y) >= 0) or (not WordBool(st and MOVE_HITWALL)) then begin - SetState(STATE_GO); + SetState(MONSTATE_GO); FSleep := 0; - // Ñòåíà íå êîí÷èëàñü => áåæèì îò íåå: + // Стена не кончилась => бежим от нее: if WordBool(st and (MOVE_HITWALL or MOVE_BLOCK)) then begin - SetState(STATE_RUN); + SetState(MONSTATE_RUN); FSleep := 15; end; end; - // Áåæèì â âûáðàííóþ ñòîðîíó: - if FDirection = D_RIGHT then + // Бежим в выбранную сторону: + if FDirection = TDirection.D_RIGHT then FObj.Vel.X := MONSTERTABLE[FMonsterType].RunVel else FObj.Vel.X := -MONSTERTABLE[FMonsterType].RunVel; - // Åñëè â âîäå, òî çàìåäëÿåìñÿ: + // Если в воде, то замедляемся: if WordBool(st and MOVE_INWATER) then FObj.Vel.X := FObj.Vel.X div 2 - else // Ðûáàì íå íóæíî çàìåäëÿòüñÿ + else // Рыбам не нужно замедляться if FMonsterType = MONSTER_FISH then FObj.Vel.X := 0; end; - STATE_ATTACK, // Ñîñòîÿíèå - Àòàêà - STATE_SHOOT: // Ñîñòîÿíèå - Ñòðåëüáà + MONSTATE_ATTACK, // Состояние - Атака + MONSTATE_SHOOT: // Состояние - Стрельба begin - // Lost_Soul âðåçàëñÿ â ñòåíó ïðè àòàêå => ïåðåõîäèò íà øàã: + // Lost_Soul врезался в стену при атаке => переходит на шаг: if FMonsterType = MONSTER_SOUL then begin if WordBool(st and (MOVE_HITWALL or MOVE_HITCEIL or MOVE_HITLAND)) then - SetState(STATE_GO); + SetState(MONSTATE_GO); goto _end; end; - // Çàìåäëÿåìñÿ ïðè àòàêå: + // Замедляемся при атаке: if FMonsterType <> MONSTER_FISH then FObj.Vel.X := z_dec(FObj.Vel.X, 1); - // Íóæíî ñòðåëÿòü, à ìîíñòð - êîëäóí: - if (FMonsterType = MONSTER_VILE) and (FState = STATE_SHOOT) then + // Нужно стрелять, а монстр - колдун: + if (FMonsterType = MONSTER_VILE) and (FState = MONSTATE_SHOOT) then begin - // Öåëü ïîãèáëà => èäåì äàëüøå: + // Цель погибла => идем дальше: if not GetPos(FTargetUID, @o) then begin - SetState(STATE_GO); + SetState(MONSTATE_GO); goto _end; end; - // Öåëü íå âèäíî => èäåì äàëüøå: + // Цель не видно => идем дальше: if not g_Look(@FObj, @o, FDirection) then begin - SetState(STATE_GO); + SetState(MONSTATE_GO); goto _end; end; - // Öåëü â âîäå - íå çàãîðèòñÿ => èäåì äàëüøå: + // Цель в воде - не загорится => идем дальше: if g_Obj_CollideWater(@o, 0, 0) then begin - SetState(STATE_GO); + SetState(MONSTATE_GO); goto _end; end; @@ -3653,27 +3875,27 @@ begin _end: -// Ñîñòîÿíèå - Âîñêðåøåíèå: - if FState = STATE_REVIVE then +// Состояние - Воскрешение: + if FState = MONSTATE_REVIVE then if FAnim[FCurAnim, FDirection].Played then - begin // Îáðàòíàÿ àíèìàöèÿ óìèðàíèÿ çàêîí÷èëàñü - èäåì äàëüøå: + begin // Обратная анимация умирания закончилась - идем дальше: FAnim[FCurAnim, FDirection].Revert(False); - SetState(STATE_GO); + SetState(MONSTATE_GO); end; -// Åñëè åñòü àíèìàöèÿ îãíÿ êîëäóíà - ïóñòü îíà èäåò: +// Если есть анимация огня колдуна - пусть она идет: if vilefire <> nil then vilefire.Update(); -// Ñîñòîÿíèå - Óìèðàåò è òåêóùàÿ àíèìàöèÿ ïðîèãðàíà: - if (FState = STATE_DIE) and +// Состояние - Умирает и текущая анимация проиграна: + if (FState = MONSTATE_DIE) and (FAnim[FCurAnim, FDirection] <> nil) and (FAnim[FCurAnim, FDirection].Played) then begin - // Óìåð: - SetState(STATE_DEAD); + // Умер: + SetState(MONSTATE_DEAD); - // Ó ýòèõ ìîíñòðîâ íåò òðóïîâ: + // У этих монстров нет трупов: if (FMonsterType = MONSTER_PAIN) or (FMonsterType = MONSTER_SOUL) or (FMonsterType = MONSTER_BARREL) then @@ -3682,28 +3904,28 @@ _end: FAnim[FCurAnim, FDirection].CurrentFrame := FAnim[FCurAnim, FDirection].TotalFrames - 1; end; -// Ñîâåðøåíèå àòàêè è ñòðåëüáû: - if (FState = STATE_ATTACK) or (FState = STATE_SHOOT) then +// Совершение атаки и стрельбы: + if (FState = MONSTATE_ATTACK) or (FState = MONSTATE_SHOOT) then if (FAnim[FCurAnim, FDirection] <> nil) then - // Àíèìàöèÿ àòàêè åñòü - ìîæíî àòàêîâàòü + // Анимация атаки есть - можно атаковать if (FAnim[FCurAnim, FDirection].Played) then - begin // Àíèìàöèÿ àòàêè çàêîí÷èëàñü => ïåðåõîäèì íà øàã - if FState = STATE_ATTACK then - begin // Ñîñòîÿíèå - Àòàêà - // Åñëè ìîíñòð íå Lost_Soul, òî ïîñëå àòàêè ïåðåõîäèì íà øàã: + begin // Анимация атаки закончилась => переходим на шаг + if FState = MONSTATE_ATTACK then + begin // Состояние - Атака + // Если монстр не Lost_Soul, то после атаки переходим на шаг: if FMonsterType <> MONSTER_SOUL then - SetState(STATE_GO); + SetState(MONSTATE_GO); end - else // Ñîñòîÿíèå - Ñòðåëüáà + else // Состояние - Стрельба begin - // Ïåðåõîäèì íà øàã, åñëè íå íàäî ñòðåëÿòü åùå ðàç: + // Переходим на шаг, если не надо стрелять еще раз: if not FChainFire then - SetState(STATE_GO) + SetState(MONSTATE_GO) else - begin // Íàäî ñòðåëÿòü åùå + begin // Надо стрелять еще FChainFire := False; - // Ò.ê. ñîñòîÿíèå íå èçìåíèëîñü, è íóæåí - // íîâûé öèêë îæèäàíèÿ çàâåðøåíèÿ àíèìàöèè: + // Т.к. состояние не изменилось, и нужен + // новый цикл ожидания завершения анимации: FAnim[FCurAnim, FDirection].Reset(); end; end; @@ -3711,79 +3933,79 @@ _end: FWaitAttackAnim := False; end - else // Àíèìàöèÿ àòàêè åùå èäåò (èñêëþ÷åíèå - Lost_Soul): + else // Анимация атаки еще идет (исключение - Lost_Soul): if (FMonsterType = MONSTER_SOUL) or ( (not FWaitAttackAnim) and (FAnim[FCurAnim, FDirection].CurrentFrame = (FAnim[FCurAnim, FDirection].TotalFrames div 2)) ) then - begin // Àòàêè åùå íå áûëî è ýòî ñåðåäèíà àíèìàöèè àòàêè - if FState = STATE_ATTACK then - begin // Ñîñòîÿíèå - Àòàêà - // Åñëè ýòî Lost_Soul, òî ñáðàñûâàåì àíèìàöèþ àòàêè: + begin // Атаки еще не было и это середина анимации атаки + if FState = MONSTATE_ATTACK then + begin // Состояние - Атака + // Если это Lost_Soul, то сбрасываем анимацию атаки: if FMonsterType = MONSTER_SOUL then FAnim[FCurAnim, FDirection].Reset(); case FMonsterType of MONSTER_SOUL, MONSTER_IMP, MONSTER_DEMON: - // Ãðûçåì ïåðâîãî ïîïàâøåãîñÿ: + // Грызем первого попавшегося: if g_Weapon_Hit(@FObj, 15, FUID, HIT_SOME) <> 0 then - // Lost_Soul óêóñèë êîãî-òî => ïåðåõîäèò íà øàã: + // Lost_Soul укусил кого-то => переходит на шаг: if FMonsterType = MONSTER_SOUL then - SetState(STATE_GO); + SetState(MONSTATE_GO); MONSTER_FISH: g_Weapon_Hit(@FObj, 10, FUID, HIT_SOME); MONSTER_SKEL, MONSTER_ROBO, MONSTER_CYBER: - // Ðîáîò, êèáåð èëè ñêåëåò ñèëüíî ïèíàþòñÿ: + // Робот, кибер или скелет сильно пинаются: if FCurAnim = ANIM_ATTACK2 then begin o := FObj; - o.Vel.X := IfThen(FDirection = D_RIGHT, 1, -1)*IfThen(FMonsterType = MONSTER_CYBER, 60, 50); + o.Vel.X := IfThen(FDirection = TDirection.D_RIGHT, 1, -1)*IfThen(FMonsterType = MONSTER_CYBER, 60, 50); g_Weapon_Hit(@o, IfThen(FMonsterType = MONSTER_CYBER, 33, 50), FUID, HIT_SOME); end; MONSTER_VILE: - // Êîëäóí ïûòàåòñÿ âîñêðåøàòü: + // Колдун пытается воскрешать: if FCurAnim = ANIM_ATTACK2 then begin sx := isCorpse(@FObj, True); if sx <> -1 then - begin // Íàøëè, êîãî âîñêðåñèòü + begin // Нашли, кого воскресить g_Sound_PlayExAt('SOUND_MONSTER_SLOP', FObj.X, FObj.Y); - // Âîñêðåøàòü - ñåáå âðåäèòü: + // Воскрешать - себе вредить: {g_Weapon_HitUID(FUID, 5, 0, HIT_SOME);} end; end; end; end - else // Ñîñòîÿíèå - Ñòðåëüáà + else // Состояние - Стрельба begin - // Ñêîðîñòðåëüíûå ìîíñòðû: + // Скорострельные монстры: if (FMonsterType = MONSTER_CGUN) or (FMonsterType = MONSTER_SPIDER) or (FMonsterType = MONSTER_BSP) or (FMonsterType = MONSTER_MANCUB) or (FMonsterType = MONSTER_ROBO) then if not GetPos(FTargetUID, @o) then - // Öåëü ìåðòâà - èùåì íîâóþ: + // Цель мертва - ищем новую: findNewPrey() - else // Öåëü æèâà - ïðîäîëæàåì ñòðåëÿòü: + else // Цель жива - продолжаем стрелять: if shoot(@o, False) then FChainFire := True; end; - // Àòàêà òîëüêî 1 ðàç çà àíèìàöèþ àòàêè: + // Атака только 1 раз за анимацию атаки: FWaitAttackAnim := True; end; -// Ïîñëåäíèé êàäð òåêóùåé àíèìàöèè: +// Последний кадр текущей анимации: if FAnim[FCurAnim, FDirection].Counter = FAnim[FCurAnim, FDirection].Speed-1 then case FState of - STATE_GO, STATE_RUN, STATE_CLIMB, STATE_RUNOUT: - // Çâóêè ïðè ïåðåäâèæåíèè: + MONSTATE_GO, MONSTATE_RUN, MONSTATE_CLIMB, MONSTATE_RUNOUT: + // Звуки при передвижении: case FMonsterType of MONSTER_CYBER: if (FAnim[FCurAnim, FDirection].CurrentFrame = 0) or @@ -3804,12 +4026,12 @@ _end: end; end; -// Êîñòûëü äëÿ ïîòîêîâ +// Костыль для потоков if g_Obj_CollidePanel(@FObj, 0, 0, PANEL_LIFTLEFT or PANEL_LIFTRIGHT) and - not ((FState = STATE_DEAD) or (FState = STATE_DIE)) then + not ((FState = MONSTATE_DEAD) or (FState = MONSTATE_DIE)) then FObj.Vel.X := oldvelx; -// Åñëè åñòü àíèìàöèÿ, òî ïóñòü îíà èäåò: +// Если есть анимация, то пусть она идет: if FAnim[FCurAnim, FDirection] <> nil then FAnim[FCurAnim, FDirection].Update(); end; @@ -3860,14 +4082,11 @@ end; procedure TMonster.Turn(); begin -// Ðàçâîðà÷èâàåìñÿ: - if FDirection = D_LEFT then - FDirection := D_RIGHT - else - FDirection := D_LEFT; +// Разворачиваемся: + if FDirection = TDirection.D_LEFT then FDirection := TDirection.D_RIGHT else FDirection := TDirection.D_LEFT; -// Áåæèì â âûáðàííóþ ñòîðîíó: - if FDirection = D_RIGHT then +// Бежим в выбранную сторону: + if FDirection = TDirection.D_RIGHT then FObj.Vel.X := MONSTERTABLE[FMonsterType].RunVel else FObj.Vel.X := -MONSTERTABLE[FMonsterType].RunVel; @@ -3889,11 +4108,11 @@ begin PlayerNear := -1; MonsterNear := -1; - // Ïîåõàâøèå, êàííèáàëû, è äîáðûå èãðîêîâ íå òðîãàþò + // Поехавшие, каннибалы, и добрые игроков не трогают if (gPlayers <> nil) and (FBehaviour <> BH_INSANE) and (FBehaviour <> BH_CANNIBAL) and (FBehaviour <> BH_GOOD) then for a := 0 to High(gPlayers) do - if (gPlayers[a] <> nil) and (gPlayers[a].Live) + if (gPlayers[a] <> nil) and (gPlayers[a].alive) and (not gPlayers[a].NoTarget) and (gPlayers[a].FMegaRulez[MR_INVIS] < gTime) then begin if g_Look(@FObj, @gPlayers[a].Obj, FDirection) then @@ -3910,20 +4129,20 @@ begin end; end; - // Êèëëåðû è äîáðûå íå òðîãàþò ìîíñòðîâ + // Киллеры и добрые не трогают монстров if (gMonsters <> nil) and (FBehaviour <> BH_KILLER) and (FBehaviour <> BH_GOOD) then for a := 0 to High(gMonsters) do - if (gMonsters[a] <> nil) and (gMonsters[a].Live) and + if (gMonsters[a] <> nil) and (gMonsters[a].alive) and (gMonsters[a].FUID <> FUID) then begin if (FBehaviour = BH_CANNIBAL) and (gMonsters[a].FMonsterType <> FMonsterType) then - Continue; // Êàííèáàëû àòàêóþò òîëüêî ñîðîäè÷åé + Continue; // Каннибалы атакуют только сородичей if (FBehaviour = BH_INSANE) and (gMonsters[a].FMonsterType <> MONSTER_BARREL) and (IsFriend(gMonsters[a].FMonsterType, FMonsterType)) then - Continue; // Ïîåõàâøèå íå òðîãàþò äðóçåé, íî èì íå íðàâÿòñÿ áî÷êè + Continue; // Поехавшие не трогают друзей, но им не нравятся бочки if ((FBehaviour = BH_NORMAL) or (FBehaviour = BH_MANIAC)) and (IsFriend(gMonsters[a].FMonsterType, FMonsterType)) then - Continue; // Îñòàâøèåñÿ òèïû, êðîìå êàííèáàëîâ, íå òðîãàþò ñâîèõ äðóçåé + Continue; // Оставшиеся типы, кроме каннибалов, не трогают своих друзей if g_Look(@FObj, @gMonsters[a].Obj, FDirection) then begin @@ -3942,25 +4161,25 @@ begin case FBehaviour of BH_NORMAL, BH_KILLER: begin - // Îáû÷íûé è êèëëåð ñíà÷àëà èùóò èãðîêîâ â ïîëå çðåíèÿ + // Обычный и киллер сначала ищут игроков в поле зрения if (FTargetUID = 0) and (Length(PlayersSee) > 0) then begin a := PlayersSee[Random(Length(PlayersSee))]; FTargetUID := gPlayers[a].UID; end; - // Çàòåì ïîáëèçîñòè + // Затем поблизости if (FTargetUID = 0) and (PlayerNear > -1) then begin a := PlayerNear; FTargetUID := gPlayers[a].UID; end; - // Ïîòîì îáû÷íûå èùóò ìîíñòðîâ â ïîëå çðåíèÿ + // Потом обычные ищут монстров в поле зрения if (FTargetUID = 0) and (Length(MonstersSee) > 0) then begin a := MonstersSee[Random(Length(MonstersSee))]; FTargetUID := gMonsters[a].UID; end; - // Çàòåì ïîáëèçîñòè + // Затем поблизости if (FTargetUID = 0) and (MonsterNear > -1) then begin a := MonsterNear; @@ -3969,7 +4188,7 @@ begin end; BH_MANIAC, BH_INSANE, BH_CANNIBAL: begin - // Ìàíüÿêè, ïîåõàâøèå è êàííèáàëû ñíà÷àëà èñòðåáëÿþò âñ¸ â ïîëå çðåíèÿ + // Маньяки, поехавшие и каннибалы сначала истребляют всё в поле зрения if (FTargetUID = 0) and (Length(PlayersSee) > 0) then begin a := PlayersSee[Random(Length(PlayersSee))]; @@ -3980,7 +4199,7 @@ begin a := MonstersSee[Random(Length(MonstersSee))]; FTargetUID := gMonsters[a].UID; end; - // Çàòåì èùóò êîãî-òî ïîáëèçîñòè + // Затем ищут кого-то поблизости if (FTargetUID = 0) and (PlayerNear > -1) then begin a := PlayerNear; @@ -3994,17 +4213,17 @@ begin end; end; -// Åñëè è ìîíñòðîâ íåò - íà÷èíàåì æäàòü öåëü: +// Если и монстров нет - начинаем ждать цель: if FTargetUID = 0 then begin - // Ïîåõàâøèé ïûòàåòñÿ ñàìîóáèòüñÿ + // Поехавший пытается самоубиться if FBehaviour = BH_INSANE then FTargetUID := FUID else FTargetTime := MAX_ATM; end else - begin // Öåëü íàøëè + begin // Цель нашли FTargetTime := 0; Result := True; end; @@ -4017,30 +4236,30 @@ begin case FMonsterType of MONSTER_FISH: begin - SetState(STATE_ATTACK); + SetState(MONSTATE_ATTACK); Result := True; end; MONSTER_DEMON: begin - SetState(STATE_ATTACK); + SetState(MONSTATE_ATTACK); g_Sound_PlayExAt('SOUND_MONSTER_DEMON_ATTACK', FObj.X, FObj.Y); Result := True; end; MONSTER_IMP: begin - SetState(STATE_ATTACK); + SetState(MONSTATE_ATTACK); g_Sound_PlayExAt('SOUND_MONSTER_IMP_ATTACK', FObj.X, FObj.Y); Result := True; end; MONSTER_SKEL, MONSTER_ROBO, MONSTER_CYBER: begin - SetState(STATE_ATTACK, ANIM_ATTACK2); + SetState(MONSTATE_ATTACK, ANIM_ATTACK2); g_Sound_PlayExAt('SOUND_MONSTER_SKEL_ATTACK', FObj.X, FObj.Y); Result := True; end; MONSTER_BARON, MONSTER_KNIGHT, MONSTER_CACO, MONSTER_MANCUB: - // Ýòè ìîíñòðû íå ïèíàþò - îíè ñòðåëÿþò â óïîð: + // Эти монстры не пинают - они стреляют в упор: if not g_Game_IsClient then Result := shoot(o, True); end; end; @@ -4051,19 +4270,19 @@ var begin Result := False; -// Ñòðåëÿòü ðàíî: +// Стрелять рано: if FAmmo < 0 then Exit; -// Æäàòü âðåìåíè ãîòîâíîñòè ê âûñòðåëó: +// Ждать времени готовности к выстрелу: if not immediately then case FMonsterType of MONSTER_FISH, MONSTER_BARREL, MONSTER_DEMON: - Exit; // íå ñòðåëÿþò + Exit; // не стреляют MONSTER_CGUN, MONSTER_BSP, MONSTER_ROBO: begin FAmmo := FAmmo + 1; - // Âðåìÿ âûñòðåëà óïóùåíî: + // Время выстрела упущено: if FAmmo >= 50 then FAmmo := IfThen(FMonsterType = MONSTER_ROBO, -200, -50); end; @@ -4071,24 +4290,24 @@ begin MONSTER_MANCUB: begin FAmmo := FAmmo + 1; - // Âðåìÿ âûñòðåëà óïóùåíî: + // Время выстрела упущено: if FAmmo >= 5 then FAmmo := -50; end; MONSTER_SPIDER: begin FAmmo := FAmmo + 1; - // Âðåìÿ âûñòðåëà óïóùåíî: + // Время выстрела упущено: if FAmmo >= 100 then FAmmo := -50; end; MONSTER_CYBER: begin - // Ñòðåëÿåò íå âñåãäà: + // Стреляет не всегда: if Random(2) = 0 then Exit; FAmmo := FAmmo + 1; - // Âðåìÿ âûñòðåëà óïóùåíî: + // Время выстрела упущено: if FAmmo >= 10 then FAmmo := -50; end; @@ -4099,7 +4318,7 @@ begin else if Random(16) <> 0 then Exit; end; -// Öåëè íå âèäíî: +// Цели не видно: if not g_Look(@FObj, o, FDirection) then Exit; @@ -4108,8 +4327,8 @@ begin tx := o^.X+o^.Rect.X+(o^.Rect.Width div 2)+((o^.Vel.X{+o^.Accel.X})*12); ty := o^.Y+o^.Rect.Y+(o^.Rect.Height div 2)+((o^.Vel.Y{+o^.Accel.Y})*12); -// Ðàçíèöà ïî âûñîòå áîëüøå ðàçíèöû ïî ãîðèçîíòàëè -// (íå ìîæåò ñòðåëÿòü ïîä òàêèì áîëüøèì óãëîì): +// Разница по высоте больше разницы по горизонтали +// (не может стрелять под таким большим углом): if Abs(tx-(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2))) < Abs(ty-(FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2))) then Exit; @@ -4117,19 +4336,19 @@ begin case FMonsterType of MONSTER_IMP, MONSTER_BARON, MONSTER_KNIGHT, MONSTER_CACO: begin - SetState(STATE_SHOOT); + SetState(MONSTATE_SHOOT); {nn} end; MONSTER_SKEL: begin - SetState(STATE_SHOOT); + SetState(MONSTATE_SHOOT); {nn} end; MONSTER_VILE: - begin // Çàæèãàåì îãîíü + begin // Зажигаем огонь tx := o^.X+o^.Rect.X+(o^.Rect.Width div 2); ty := o^.Y+o^.Rect.Y; - SetState(STATE_SHOOT); + SetState(MONSTATE_SHOOT); vilefire.Reset(); @@ -4137,8 +4356,8 @@ begin g_Sound_PlayExAt('SOUND_FIRE', o^.X, o^.Y); end; MONSTER_SOUL: - begin // Ëåòèò â ñòîðîíó öåëè: - SetState(STATE_ATTACK); + begin // Летит в сторону цели: + SetState(MONSTATE_ATTACK); g_Sound_PlayExAt('SOUND_MONSTER_SOUL_ATTACK', FObj.X, FObj.Y); xd := tx-(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)); @@ -4153,12 +4372,12 @@ begin MONSTER_MANCUB, MONSTER_ZOMBY, MONSTER_SERG, MONSTER_BSP, MONSTER_ROBO, MONSTER_CYBER, MONSTER_CGUN, MONSTER_SPIDER, MONSTER_PAIN, MONSTER_MAN: begin - // Ìàíêóáóñ ðÿâêàåò ïåðåä ïåðâîé àòàêîé: + // Манкубус рявкает перед первой атакой: if FMonsterType = MONSTER_MANCUB then if FAmmo = 1 then g_Sound_PlayExAt('SOUND_MONSTER_MANCUB_ATTACK', FObj.X, FObj.Y); - SetState(STATE_SHOOT); + SetState(MONSTATE_SHOOT); end; else Exit; end; @@ -4166,9 +4385,9 @@ begin Result := True; end; -function TMonster.Live(): Boolean; +function TMonster.alive(): Boolean; begin - Result := (FState <> STATE_DIE) and (FState <> STATE_DEAD) and (FHealth > 0); + Result := (FState <> MONSTATE_DIE) and (FState <> MONSTATE_DEAD) and (FHealth > 0); end; procedure TMonster.SetHealth(aH: Integer); @@ -4184,186 +4403,175 @@ end; procedure TMonster.WakeUp(); begin if g_Game_IsClient then Exit; - SetState(STATE_GO); + SetState(MONSTATE_GO); FTargetTime := MAX_ATM; WakeUpSound(); end; -procedure TMonster.SaveState(var Mem: TBinMemoryWriter); +procedure TMonster.SaveState (st: TStream); var i: Integer; - sig: DWORD; b: Byte; anim: Boolean; begin - if Mem = nil then - Exit; - -// Ñèãíàòóðà ìîíñòðà: - sig := MONSTER_SIGNATURE; // 'MONS' - Mem.WriteDWORD(sig); -// UID ìîíñòðà: - Mem.WriteWord(FUID); -// Íàïðàâëåíèå: - if FDirection = D_LEFT then - b := 1 - else // D_RIGHT - b := 2; - Mem.WriteByte(b); -// Íàäî ëè óäàëèòü åãî: - Mem.WriteBoolean(FRemoved); -// Îñòàëîñü çäîðîâüÿ: - Mem.WriteInt(FHealth); -// Ñîñòîÿíèå: - Mem.WriteByte(FState); -// Òåêóùàÿ àíèìàöèÿ: - Mem.WriteByte(FCurAnim); -// UID öåëè: - Mem.WriteWord(FTargetUID); -// Âðåìÿ ïîñëå ïîòåðè öåëè: - Mem.WriteInt(FTargetTime); -// Ïîâåäåíèå ìîíñòðà: - Mem.WriteByte(FBehaviour); -// Ãîòîâíîñòü ê âûñòðåëó: - Mem.WriteInt(FAmmo); -// Áîëü: - Mem.WriteInt(FPain); -// Âðåìÿ îæèäàíèÿ: - Mem.WriteInt(FSleep); -// Îçâó÷èâàòü ëè áîëü: - Mem.WriteBoolean(FPainSound); -// Áûëà ëè àòàêà âî âðåìÿ àíèìàöèè àòàêè: - Mem.WriteBoolean(FWaitAttackAnim); -// Íàäî ëè ñòðåëÿòü íà ñëåäóþùåì øàãå: - Mem.WriteBoolean(FChainFire); -// Ïîäëåæèò ëè ðåñïàâíó: - Mem.WriteBoolean(FNoRespawn); -// Êîîðäèíàòû öåëè: - Mem.WriteInt(tx); - Mem.WriteInt(ty); -// ID ìîíñòðà ïðè ñòàðòå êàðòû: - Mem.WriteInt(FStartID); -// Èíäåêñ òðèããåðà, ñîçäàâøåãî ìîíñòðà: - Mem.WriteInt(FSpawnTrigger); -// Îáúåêò ìîíñòðà: - Obj_SaveState(@FObj, Mem); -// Åñòü ëè àíèìàöèÿ îãíÿ êîëäóíà: - anim := vilefire <> nil; - Mem.WriteBoolean(anim); -// Åñëè åñòü - ñîõðàíÿåì: - if anim then - vilefire.SaveState(Mem); -// Àíèìàöèè: + assert(st <> nil); + + // Сигнатура монстра: + utils.writeSign(st, 'MONS'); + utils.writeInt(st, Byte(0)); // version + // UID монстра: + utils.writeInt(st, Word(FUID)); + // Направление + if FDirection = TDirection.D_LEFT then b := 1 else b := 2; // D_RIGHT + utils.writeInt(st, Byte(b)); + // Надо ли удалить его + utils.writeBool(st, FRemoved); + // Осталось здоровья + utils.writeInt(st, LongInt(FHealth)); + // Состояние + utils.writeInt(st, Byte(FState)); + // Текущая анимация + utils.writeInt(st, Byte(FCurAnim)); + // UID цели + utils.writeInt(st, Word(FTargetUID)); + // Время после потери цели + utils.writeInt(st, LongInt(FTargetTime)); + // Поведение монстра + utils.writeInt(st, Byte(FBehaviour)); + // Готовность к выстрелу + utils.writeInt(st, LongInt(FAmmo)); + // Боль + utils.writeInt(st, LongInt(FPain)); + // Время ожидания + utils.writeInt(st, LongInt(FSleep)); + // Озвучивать ли боль + utils.writeBool(st, FPainSound); + // Была ли атака во время анимации атаки + utils.writeBool(st, FWaitAttackAnim); + // Надо ли стрелять на следующем шаге + utils.writeBool(st, FChainFire); + // Подлежит ли респавну + utils.writeBool(st, FNoRespawn); + // Координаты цели + utils.writeInt(st, LongInt(tx)); + utils.writeInt(st, LongInt(ty)); + // ID монстра при старте карты + utils.writeInt(st, LongInt(FStartID)); + // Индекс триггера, создавшего монстра + utils.writeInt(st, LongInt(FSpawnTrigger)); + // Объект монстра + Obj_SaveState(st, @FObj); + // Есть ли анимация огня колдуна + anim := (vilefire <> nil); + utils.writeBool(st, anim); + // Если есть - сохраняем: + if anim then vilefire.SaveState(st); + // Анимации for i := ANIM_SLEEP to ANIM_PAIN do begin - // Åñòü ëè ëåâàÿ àíèìàöèÿ: - anim := FAnim[i, D_LEFT] <> nil; - Mem.WriteBoolean(anim); - // Åñëè åñòü - ñîõðàíÿåì: - if anim then - FAnim[i, D_LEFT].SaveState(Mem); - // Åñòü ëè ïðàâàÿ àíèìàöèÿ: - anim := FAnim[i, D_RIGHT] <> nil; - Mem.WriteBoolean(anim); - // Åñëè åñòü - ñîõðàíÿåì: - if anim then - FAnim[i, D_RIGHT].SaveState(Mem); + // Есть ли левая анимация + anim := (FAnim[i, TDirection.D_LEFT] <> nil); + utils.writeBool(st, anim); + // Если есть - сохраняем + if anim then FAnim[i, TDirection.D_LEFT].SaveState(st); + // Есть ли правая анимация + anim := (FAnim[i, TDirection.D_RIGHT] <> nil); + utils.writeBool(st, anim); + // Если есть - сохраняем + if anim then FAnim[i, TDirection.D_RIGHT].SaveState(st); end; end; -procedure TMonster.LoadState(var Mem: TBinMemoryReader); + +procedure TMonster.LoadState (st: TStream); var i: Integer; - sig: DWORD; b: Byte; anim: Boolean; begin - if Mem = nil then - Exit; + assert(st <> nil); -// Ñèãíàòóðà ìîíñòðà: - Mem.ReadDWORD(sig); - if sig <> MONSTER_SIGNATURE then // 'MONS' - begin - raise EBinSizeError.Create('TMonster.LoadState: Wrong Monster Signature'); - end; -// UID ìîíñòðà: - Mem.ReadWord(FUID); -// Íàïðàâëåíèå: - Mem.ReadByte(b); - if b = 1 then - FDirection := D_LEFT - else // b = 2 - FDirection := D_RIGHT; -// Íàäî ëè óäàëèòü åãî: - Mem.ReadBoolean(FRemoved); -// Îñòàëîñü çäîðîâüÿ: - Mem.ReadInt(FHealth); -// Ñîñòîÿíèå: - Mem.ReadByte(FState); -// Òåêóùàÿ àíèìàöèÿ: - Mem.ReadByte(FCurAnim); -// UID öåëè: - Mem.ReadWord(FTargetUID); -// Âðåìÿ ïîñëå ïîòåðè öåëè: - Mem.ReadInt(FTargetTime); -// Ïîâåäåíèå ìîíñòðà: - Mem.ReadByte(FBehaviour); -// Ãîòîâíîñòü ê âûñòðåëó: - Mem.ReadInt(FAmmo); -// Áîëü: - Mem.ReadInt(FPain); -// Âðåìÿ îæèäàíèÿ: - Mem.ReadInt(FSleep); -// Îçâó÷èâàòü ëè áîëü: - Mem.ReadBoolean(FPainSound); -// Áûëà ëè àòàêà âî âðåìÿ àíèìàöèè àòàêè: - Mem.ReadBoolean(FWaitAttackAnim); -// Íàäî ëè ñòðåëÿòü íà ñëåäóþùåì øàãå: - Mem.ReadBoolean(FChainFire); -// Ïîäëåæèò ëè ðåñïàâíó - Mem.ReadBoolean(FNoRespawn); -// Êîîðäèíàòû öåëè: - Mem.ReadInt(tx); - Mem.ReadInt(ty); -// ID ìîíñòðà ïðè ñòàðòå êàðòû: - Mem.ReadInt(FStartID); -// Èíäåêñ òðèããåðà, ñîçäàâøåãî ìîíñòðà: - Mem.ReadInt(FSpawnTrigger); -// Îáúåêò ìîíñòðà: - Obj_LoadState(@FObj, Mem); -// Åñòü ëè àíèìàöèÿ îãíÿ êîëäóíà: - Mem.ReadBoolean(anim); -// Åñëè åñòü - çàãðóæàåì: + // Сигнатура монстра: + if not utils.checkSign(st, 'MONS') then raise XStreamError.Create('invalid monster signature'); + if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid monster version'); + if (uidMap[FUID] <> nil) and (uidMap[FUID] <> self) then raise Exception.Create('internal error in monster loader (0)'); + uidMap[FUID] := nil; + // UID монстра: + FUID := utils.readWord(st); + //if (arrIdx = -1) then raise Exception.Create('internal error in monster loader'); + if (uidMap[FUID] <> nil) then raise Exception.Create('internal error in monster loader (1)'); + uidMap[FUID] := self; + // Направление + b := utils.readByte(st); + if b = 1 then FDirection := TDirection.D_LEFT else FDirection := TDirection.D_RIGHT; // b = 2 + // Надо ли удалить его + FRemoved := utils.readBool(st); + // Осталось здоровья + FHealth := utils.readLongInt(st); + // Состояние + FState := utils.readByte(st); + // Текущая анимация + FCurAnim := utils.readByte(st); + // UID цели + FTargetUID := utils.readWord(st); + // Время после потери цели + FTargetTime := utils.readLongInt(st); + // Поведение монстра + FBehaviour := utils.readByte(st); + // Готовность к выстрелу + FAmmo := utils.readLongInt(st); + // Боль + FPain := utils.readLongInt(st); + // Время ожидания + FSleep := utils.readLongInt(st); + // Озвучивать ли боль + FPainSound := utils.readBool(st); + // Была ли атака во время анимации атаки + FWaitAttackAnim := utils.readBool(st); + // Надо ли стрелять на следующем шаге + FChainFire := utils.readBool(st); + // Подлежит ли респавну + FNoRespawn := utils.readBool(st); + // Координаты цели + tx := utils.readLongInt(st); + ty := utils.readLongInt(st); + // ID монстра при старте карты + FStartID := utils.readLongInt(st); + // Индекс триггера, создавшего монстра + FSpawnTrigger := utils.readLongInt(st); + // Объект монстра + Obj_LoadState(@FObj, st); + // Есть ли анимация огня колдуна + anim := utils.readBool(st); + // Если есть - загружаем: if anim then begin Assert(vilefire <> nil, 'TMonster.LoadState: no vilefire anim'); - vilefire.LoadState(Mem); + vilefire.LoadState(st); end; -// Àíèìàöèè: + // Анимации for i := ANIM_SLEEP to ANIM_PAIN do begin - // Åñòü ëè ëåâàÿ àíèìàöèÿ: - Mem.ReadBoolean(anim); - // Åñëè åñòü - çàãðóæàåì: + // Есть ли левая анимация + anim := utils.readBool(st); + // Если есть - загружаем if anim then begin - Assert(FAnim[i, D_LEFT] <> nil, - 'TMonster.LoadState: no '+IntToStr(i)+'_left anim'); - FAnim[i, D_LEFT].LoadState(Mem); + Assert(FAnim[i, TDirection.D_LEFT] <> nil, 'TMonster.LoadState: no '+IntToStr(i)+'_left anim'); + FAnim[i, TDirection.D_LEFT].LoadState(st); end; - // Åñòü ëè ïðàâàÿ àíèìàöèÿ: - Mem.ReadBoolean(anim); - // Åñëè åñòü - çàãðóæàåì: + // Есть ли правая анимация + anim := utils.readBool(st); + // Если есть - загружаем if anim then begin - Assert(FAnim[i, D_RIGHT] <> nil, - 'TMonster.LoadState: no '+IntToStr(i)+'_right anim'); - FAnim[i, D_RIGHT].LoadState(Mem); + Assert(FAnim[i, TDirection.D_RIGHT] <> nil, 'TMonster.LoadState: no '+IntToStr(i)+'_right anim'); + FAnim[i, TDirection.D_RIGHT].LoadState(st); end; end; end; + procedure TMonster.ActivateTriggers(); var a: Integer; @@ -4389,9 +4597,16 @@ begin SetLength(FDieTriggers, 0); end; -procedure TMonster.CatchFire(Attacker: Word); +procedure TMonster.CatchFire(Attacker: Word; Timeout: Integer = MON_BURN_TIME); begin - FFireTime := 100; + if FMonsterType in [MONSTER_SOUL, MONSTER_VILE] then + exit; // арчи не горят, черепа уже горят + if Timeout <= 0 then exit; + if g_Obj_CollidePanel(@FObj, 0, 0, PANEL_WATER or PANEL_ACID1 or PANEL_ACID2) then + exit; // не подгораем в воде на всякий случай + if FFireTime <= 0 then + g_Sound_PlayExAt('SOUND_IGNITE', FObj.X, FObj.Y); + FFireTime := Timeout; FFireAttacker := Attacker; if g_Game_IsNet and g_Game_IsServer then MH_SEND_MonsterState(FUID); end; @@ -4411,7 +4626,7 @@ begin Anim := TAnimation.Create(id, False, 3); Anim.Alpha := 0; g_GFX_OnceAnim(Obj.X+Obj.Rect.X+Random(Obj.Rect.Width+Times*2)-(Anim.Width div 2), - Obj.Y+8+Random(8+Times*2)+IfThen(FState = STATE_DEAD, 16, 0), Anim, ONCEANIM_SMOKE); + Obj.Y+8+Random(8+Times*2)+IfThen(FState = MONSTATE_DEAD, 16, 0), Anim, ONCEANIM_SMOKE); Anim.Free(); end; end; @@ -4426,7 +4641,6 @@ begin if (result = nil) then raise Exception.Create('g_Mons_ByIdx: invalid monster id'); end; - // can return null function g_Mons_ByIdx_NC (uid: Integer): TMonster; inline; begin @@ -4434,6 +4648,11 @@ begin result := gMonsters[uid]; end; +function g_Mons_TotalCount (): Integer; inline; +begin + result := Length(gMonsters); +end; + function g_Mons_ForEach (cb: TEachMonsterCB): Boolean; var @@ -4447,7 +4666,7 @@ begin mon := gMonsters[idx]; if (mon <> nil) then begin - result := cb(idx, gMonsters[idx]); + result := cb(mon); if result then exit; end; end; @@ -4464,9 +4683,9 @@ begin for idx := 0 to High(gMonsters) do begin mon := gMonsters[idx]; - if (mon <> nil) and mon.Live then + if (mon <> nil) and mon.alive then begin - result := cb(idx, gMonsters[idx]); + result := cb(mon); if result then exit; end; end; @@ -4474,28 +4693,33 @@ end; function g_Mons_IsAnyAliveAt (x, y: Integer; width, height: Integer): Boolean; - + (* function monsCollCheck (mon: TMonster; atag: Integer): Boolean; begin - result := (mon.Live and g_Obj_Collide(x, y, width, height, @mon.Obj)); + result := mon.alive;// and g_Obj_Collide(x, y, width, height, @mon.Obj)); end; - + *) var idx: Integer; mon: TMonster; + mit: PMonster; + it: TMonsterGrid.Iter; begin result := false; if (width < 1) or (height < 1) then exit; if gmon_debug_use_sqaccel then begin - result := (monsTree.aabbQuery(x, y, width, height, monsCollCheck) <> nil); + //result := (monsGrid.forEachInAABB(x, y, width, height, monsCollCheck) <> nil); + it := monsGrid.forEachInAABB(x, y, width, height); + for mit in it do if (mit.alive) then begin result := true; break; end; + it.release(); end else begin for idx := 0 to High(gMonsters) do begin mon := gMonsters[idx]; - if (mon <> nil) and mon.Live then + if (mon <> nil) and mon.alive then begin if g_Obj_Collide(x, y, width, height, @mon.Obj) then begin @@ -4509,33 +4733,37 @@ end; function g_Mons_ForEachAt (x, y: Integer; width, height: Integer; cb: TEachMonsterCB): Boolean; - + (* function monsCollCheck (mon: TMonster; atag: Integer): Boolean; begin - result := false; - if g_Obj_Collide(x, y, width, height, @mon.Obj) then result := cb(mon.arrIdx, mon); + result := cb(mon); end; - + *) var idx: Integer; mon: TMonster; + mit: PMonster; + it: TMonsterGrid.Iter; begin result := false; if (width < 1) or (height < 1) then exit; if gmon_debug_use_sqaccel then begin - result := (monsTree.aabbQuery(x, y, width, height, monsCollCheck) <> nil); + //result := (monsGrid.forEachInAABB(x, y, width, height, monsCollCheck) <> nil); + it := monsGrid.forEachInAABB(x, y, width, height); + for mit in it do if (cb(mit^)) then begin result := true; break; end; + it.release(); end else begin for idx := 0 to High(gMonsters) do begin mon := gMonsters[idx]; - if (mon <> nil) and mon.Live then + if (mon <> nil) and mon.alive then begin if g_Obj_Collide(x, y, width, height, @mon.Obj) then begin - result := cb(idx, mon); + result := cb(mon); if result then exit; end; end; @@ -4545,40 +4773,54 @@ end; function g_Mons_ForEachAliveAt (x, y: Integer; width, height: Integer; cb: TEachMonsterCB): Boolean; - + (* function monsCollCheck (mon: TMonster; atag: Integer): Boolean; begin - result := false; - if mon.Live and g_Obj_Collide(x, y, width, height, @mon.Obj) then result := cb(mon.arrIdx, mon); + //result := false; + //if mon.alive and g_Obj_Collide(x, y, width, height, @mon.Obj) then result := cb(mon); + if mon.alive then result := cb(mon) else result := false; end; - + *) var idx: Integer; mon: TMonster; + mit: PMonster; + it: TMonsterGrid.Iter; begin result := false; if (width < 1) or (height < 1) then exit; if gmon_debug_use_sqaccel then begin + { if (width = 1) and (height = 1) then begin - result := (monsTree.pointQuery(x, y, monsCollCheck) <> nil); + result := (monsGrid.forEachAtPoint(x, y, monsCollCheck) <> nil); end else begin - result := (monsTree.aabbQuery(x, y, width, height, monsCollCheck) <> nil); + result := (monsGrid.forEachInAABB(x, y, width, height, monsCollCheck) <> nil); + end; + } + it := monsGrid.forEachInAABB(x, y, width, height); + for mit in it do + begin + if (mit^.alive) then + begin + if (cb(mit^)) then begin result := true; break; end; + end; end; + it.release(); end else begin for idx := 0 to High(gMonsters) do begin mon := gMonsters[idx]; - if (mon <> nil) and mon.Live then + if (mon <> nil) and mon.alive then begin if g_Obj_Collide(x, y, width, height, @mon.Obj) then begin - result := cb(idx, mon); + result := cb(mon); if result then exit; end; end;