X-Git-Url: http://deadsoftware.ru/gitweb?a=blobdiff_plain;f=src%2Fgame%2Fg_monsters.pas;h=36780ec4308c7f41d725717e48e6f7e04078b1cc;hb=8f8d15ad51eda2346afdafbb5827baee4bda83e3;hp=cde1aad4dff139c91b8ffdf65997fa9ce4ba1a3e;hpb=e706b4fd119a5f468477a139daa7a1ae87a9cd7e;p=d2df-sdl.git diff --git a/src/game/g_monsters.pas b/src/game/g_monsters.pas index cde1aad..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 @@ -13,14 +12,19 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . *) -{$MODE DELPHI} +{$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; @@ -76,17 +87,33 @@ type FBloodKind: Byte; FShellTimer: Integer; FShellType: Byte; + FFirePainTime: Integer; + FFireAttacker: Word; vilefire: TAnimation; + 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; @@ -94,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(Damage: Word; VelX, VelY: Integer; SpawnerUID: Word; t: Byte): Boolean; + 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, tx, ty: Integer); + procedure ClientAttack(wx, wy, atx, aty: Integer); procedure SetDeadAnim; - procedure Draw(); procedure WakeUp(); procedure WakeUpSound(); procedure DieSound(); @@ -113,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); @@ -122,6 +149,25 @@ type function AnimIsReverse: Boolean; function shoot(o: PObj; immediately: Boolean): Boolean; function kick(o: PObj): Boolean; + 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; @@ -135,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; @@ -148,74 +193,139 @@ 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; -procedure g_Monsters_LoadData(); -procedure g_Monsters_FreeData(); -procedure g_Monsters_Init(); -procedure g_Monsters_Free(); -function g_Monsters_Create(MonsterType: Byte; X, Y: Integer; - Direction: TDirection; AdjCoord: Boolean = False; ForcedUID: Integer = -1): Integer; -procedure g_Monsters_Update(); -procedure g_Monsters_Draw(); -procedure g_Monsters_DrawHealth(); -function g_Monsters_Get(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; + +// 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 (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 (); +function g_Monsters_ByUID (UID: Word): TMonster; +procedure g_Monsters_killedp (); +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 (mon: TMonster): Boolean is nested; // return `true` to stop + +// throws on invalid uid +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; +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; 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 - gMonsters: array of TMonster; + 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; +//HACK! +procedure g_Mons_ProfilersBegin (); +procedure g_Mons_ProfilersEnd (); -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; +procedure g_Mons_LOS_Start (); inline; +procedure g_Mons_LOS_End (); inline; - MONSTER_SIGNATURE = $534E4F4D; // 'MONS' +var + profMonsLOS: TProfiler = nil; //WARNING: FOR DEBUGGING ONLY! + + +type + TMonsterGrid = specialize TBodyGridBase; + +var + monsGrid: TMonsterGrid = nil; // DO NOT USE! public for debugging only! -// Òàáëèöà òèïîâ àíèìàöèè ìîíñòðîâ: - 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)); -// Òàáëèöà õàðàêòåðèñòèê ìîíñòðîâ: +var + 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; @@ -286,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)); @@ -303,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))), @@ -375,67 +485,262 @@ 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 - pt_x: Integer = 0; - pt_xs: Integer = 1; - pt_y: Integer = 0; - pt_ys: Integer = 1; - soulcount: Integer = 0; + 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; +} + -function FindMonster(): DWORD; +//WARNING! call this after monster position was changed, or coldet will not work right! +procedure TMonster.positionChanged (); var - i: Integer; + x, y, w, h: Integer; + nx, ny, nw, nh: Integer; begin - if gMonsters <> nil then - for i := 0 to High(gMonsters) do - if gMonsters[i] = nil then - begin - Result := i; - Exit; - end; + {$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 gMonsters = nil then + if (w <> nw) or (h <> nh) then begin - SetLength(gMonsters, 32); - Result := 0; + //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 + 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 + uidMap: array [0..65535] of TMonster; // monster knows it's index + freeInds: TIdPool = nil; + + +procedure clearUidMap (); +var + idx: Integer; +begin + for idx := 0 to High(uidMap) do uidMap[idx] := nil; + freeInds.clear(); +end; + + +function g_Mons_getNewTrapFrameId (): DWord; inline; +var + f: Integer; +begin + Inc(monCheckTrapLastFrameId); + if (monCheckTrapLastFrameId = 0) then + begin + // wraparound + monCheckTrapLastFrameId := 1; + for f := 0 to High(gMonsters) do begin - Result := High(gMonsters) + 1; - SetLength(gMonsters, Length(gMonsters) + 32); + if (gMonsters[f] <> nil) then gMonsters[f].trapCheckFrameId := 0; end; + end; + result := monCheckTrapLastFrameId; +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; + pt_y: Integer = 0; + pt_ys: Integer = 1; + soulcount: Integer = 0; + + +function allocMonster (): DWORD; +var + f, olen: Integer; +begin + result := freeInds.alloc(); + if (result > High(gMonsters)) then + begin + olen := Length(gMonsters); + SetLength(gMonsters, result+64); + for f := olen to High(gMonsters) do gMonsters[f] := nil; + end; end; + 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; @@ -447,7 +752,7 @@ begin UIDType := g_GetUIDType(SpawnerUID); if UIDType = UID_MONSTER then begin - m := g_Monsters_Get(SpawnerUID); + m := g_Monsters_ByUID(SpawnerUID); if m = nil then Exit; MonsterType := m.FMonsterType; end; @@ -465,6 +770,7 @@ begin end; end; + function canShoot(m: Byte): Boolean; begin Result := False; @@ -477,191 +783,236 @@ 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 = MONSTATE_DEAD) and g_Obj_Collide(o, @mon.FObj) then + begin + 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: PMonster; + mres: TMonster = nil; + it: TMonsterGrid.Iter; begin - Result := -1; + result := -1; -// Åñëè íóæíà âåðîÿòíîñòü: - if not immediately then - if Random(8) <> 0 then - Exit; + // Если нужна вероятность + if not immediately and (Random(8) <> 0) then exit; - if gMonsters = nil then - Exit; - -// Èùåì ìåðòâûõ ìîíñòðîâ ïîáëèçîñòè: - for a := 0 to High(gMonsters) do - if (gMonsters[a] <> nil) and (gMonsters[a].FState = STATE_DEAD) then - if g_Obj_Collide(o, @gMonsters[a].FObj) then - case gMonsters[a].FMonsterType of // Íå âîñêðåñèòü: + // Ищем мертвых монстров поблизости + if gmon_debug_use_sqaccel then + begin + //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 = MONSTATE_DEAD) and g_Obj_Collide(o, @gMonsters[a].FObj) then + begin + 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; end; end; + end; + end; + end; 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); @@ -743,11 +1094,16 @@ begin g_Sound_CreateWADEx('SOUND_MONSTER_SPIDER_WALK', GameWAD+':MSOUNDS\SPIDER_WALK'); g_Sound_CreateWADEx('SOUND_MONSTER_FISH_ATTACK', GameWAD+':MSOUNDS\FISH_ATTACK'); + + 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'); @@ -757,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'); @@ -959,6 +1317,9 @@ begin g_Sound_Delete('SOUND_MONSTER_SPIDER_WALK'); g_Sound_Delete('SOUND_MONSTER_FISH_ATTACK'); + + freeInds.Free(); + freeInds := nil; end; procedure g_Monsters_Init(); @@ -966,60 +1327,86 @@ begin soulcount := 0; end; -procedure g_Monsters_Free(); +procedure g_Monsters_Free (clearGrid: Boolean=true); var a: Integer; begin - if gMonsters <> nil then - for a := 0 to High(gMonsters) do - gMonsters[a].Free(); - + 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): Integer; + Direction: TDirection; AdjCoord: Boolean = False; ForcedUID: Integer = -1): TMonster; var find_id: DWORD; + mon: TMonster; begin - Result := -1; + result := nil; -// Íåò òàêîãî ìîíñòðà: - if (MonsterType > MONSTER_MAN) or (MonsterType = 0) then - Exit; + // Нет такого монстра + if (MonsterType > MONSTER_MAN) or (MonsterType = 0) then exit; -// Ñîáëþäàåì îãðàíè÷åíèå Lost_Soul'îâ: + // Соблюдаем ограничение Lost_Soul'ов if MonsterType = MONSTER_SOUL then - if soulcount > MAX_SOUL then - Exit - else - soulcount := soulcount + 1; + begin + if soulcount > MAX_SOUL then exit; + soulcount := soulcount + 1; + end; - find_id := FindMonster(); + find_id := allocMonster(); - gMonsters[find_id] := TMonster.Create(MonsterType, find_id, ForcedUID); + mon := TMonster.Create(MonsterType, find_id, ForcedUID); + gMonsters[find_id] := mon; + mon.mArrIdx := find_id; + mon.mProxyId := -1; -// Íàñòðàèâàåì ïîëîæåíèå: - with gMonsters[find_id] do + uidMap[mon.FUID] := mon; + + // Настраиваем положение + with mon do begin if AdjCoord then - begin - FObj.X := X-FObj.Rect.X - (FObj.Rect.Width div 2); - FObj.Y := Y-FObj.Rect.Y - FObj.Rect.Height; - end + begin + FObj.X := X-FObj.Rect.X - (FObj.Rect.Width div 2); + FObj.Y := Y-FObj.Rect.Y - FObj.Rect.Height; + end else - begin - FObj.X := X-FObj.Rect.X; - FObj.Y := Y-FObj.Rect.Y; - end; + begin + FObj.X := X-FObj.Rect.X; + FObj.Y := Y-FObj.Rect.Y; + end; FDirection := Direction; FStartDirection := Direction; FStartX := GameX; FStartY := GameY; + FObj.oldX := FObj.X; + FObj.oldY := FObj.Y; end; - Result := find_id; + mon.positionChanged(); + + result := mon; end; procedure g_Monsters_killedp(); @@ -1029,215 +1416,239 @@ 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; pt_y := pt_y+pt_ys; - if Abs(pt_x) > 246 then - pt_xs := -pt_xs; - if Abs(pt_y) > 100 then - pt_ys := -pt_ys; + if abs(pt_x) > 246 then pt_xs := -pt_xs; + if abs(pt_y) > 100 then pt_ys := -pt_ys; end; - gMon := True; // Äëÿ ðàáîòû BlockMon'à + gMon := True; // Для работы BlockMon'а - if gMonsters <> nil then + if gmon_debug_think or gmon_debug_one_think_step then + begin + gmon_debug_one_think_step := false; for a := 0 to High(gMonsters) do - if (gMonsters[a] <> nil) then - if not gMonsters[a].FRemoved then - begin - if g_Game_IsClient then - gMonsters[a].ClientUpdate() - else - gMonsters[a].Update(); - end + begin + if (gMonsters[a] = nil) then continue; + if not gMonsters[a].FRemoved then + begin + if g_Game_IsClient then + gMonsters[a].ClientUpdate() else - begin - gMonsters[a].Free(); - gMonsters[a] := nil; - end; + gMonsters[a].Update(); + end + else + 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_Get(UID: Word): TMonster; -var - a: Integer; -begin - 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; +function g_Monsters_ByUID (UID: Word): TMonster; +begin + result := uidMap[UID]; 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, i, a: Integer; + 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); - // Ñîçäàåì ìîíñòðà: - i := g_Monsters_Create(b, 0, 0, D_LEFT); - if i < 0 then - begin - raise EBinSizeError.Create('g_Monsters_LoadState: ID = -1 (Can''t create)'); - end; - // Çàãðóæàåì äàííûå ìîíñòðà: - gMonsters[i].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_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_Monsters_GetIDByName(name: String): Integer; + +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 @@ -1426,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; @@ -1439,9 +1850,11 @@ begin FChainFire := False; FShellTimer := -1; - FState := STATE_SLEEP; + FState := MONSTATE_SLEEP; FCurAnim := ANIM_SLEEP; + positionChanged(); // this updates spatial accelerators + if g_Game_IsNet and g_Game_IsServer then begin MH_SEND_MonsterPos(FUID); @@ -1452,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 @@ -1465,7 +1878,7 @@ begin g_Obj_Init(@FObj); - FState := STATE_SLEEP; + FState := MONSTATE_SLEEP; FCurAnim := ANIM_SLEEP; FHealth := MONSTERTABLE[MonsterType].Health; FMaxHealth := FHealth; @@ -1478,6 +1891,16 @@ begin FNoRespawn := False; FShellTimer := -1; FBehaviour := BH_NORMAL; + FFireTime := 0; + FFirePainTime := 0; + FFireAttacker := 0; + mEDamageType := HIT_SOME; + + mProxyId := -1; + mArrIdx := -1; + trapCheckFrameId := 0; + mplatCheckFrameId := 0; + mNeedSend := false; if FMonsterType in [MONSTER_ROBO, MONSTER_BARREL] then FBloodKind := BLOOD_SPARKS @@ -1506,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 @@ -1522,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+ @@ -1552,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'); @@ -1566,57 +1989,66 @@ begin vilefire := nil; end; -function TMonster.Damage(Damage: Word; VelX, VelY: Integer; SpawnerUID: Word; t: Byte): Boolean; +function TMonster.Damage(aDamage: Word; VelX, VelY: Integer; SpawnerUID: Word; t: Byte): Boolean; var c, it: Integer; p: TPlayer; 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 - Damage := 0; + aDamage := 0; -// Íàíîñèì óðîí: - if g_Game_IsServer then Dec(FHealth, Damage); +// Наносим урон: + if g_Game_IsServer then Dec(FHealth, aDamage); -// Óñèëèâàåì áîëü ìîíñòðà îò óðîíà: +// Усиливаем боль монстра от урона: if FPain = 0 then FPain := 3; - FPain := FPain+Damage; + 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(Damage, 200); - c := c*gBloodCount - (Damage div 4) + Random(c div 2); + c := Min(aDamage, 200); + c := c*gBloodCount - (aDamage div 4) + Random(c div 2); if (VelX = 0) and (VelY = 0) then MakeBloodSimple(c) @@ -1627,18 +2059,17 @@ begin end; end; -// Òåïåðü öåëü - óäàðèâøèé, åñëè òîëüêî íå ñàì ñåáÿ: - if (SpawnerUID <> FUID) and - (BehaviourDamage(SpawnerUID, FBehaviour, FMonsterType)) then +// Теперь цель - ударивший, если только не сам себя: + 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 @@ -1661,7 +2092,7 @@ begin end; end; - // Âûáèðàåì ëóò: + // Выбираем лут: case FMonsterType of MONSTER_ZOMBY: c := ITEM_AMMO_BULLETS; MONSTER_SERG: c := ITEM_WEAPON_SHOTGUN1; @@ -1670,53 +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_Obj_Push(@gItems[it].Obj, (FObj.Vel.X div 2)-3+Random(7), - (FObj.Vel.Y div 2)-Random(4)); + 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 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); @@ -1728,14 +2162,13 @@ begin Result := False; if g_Game_IsClient then Exit; - if not Live then + if not alive then Exit; if FHealth < FMaxHealth then begin IncMax(FHealth, Value, FMaxHealth); - if g_Game_IsServer and g_Game_IsNet then - MH_SEND_MonsterState(FUID); + if g_Game_IsServer and g_Game_IsNet then MH_SEND_MonsterState(FUID); Result := True; end; end; @@ -1746,90 +2179,34 @@ 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(); - 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 + if (mProxyId <> -1) 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); + if (monsGrid <> nil) then + begin + 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} + end; + mProxyId := -1; end; - if g_debug_Frames then + if (mArrIdx <> -1) and (mArrIdx < Length(gMonsters)) 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); + freeInds.release(mArrIdx); + gMonsters[mArrIdx] := nil; end; + mArrIdx := -1; + + uidMap[FUID] := nil; + + inherited Destroy(); end; procedure TMonster.MakeBloodSimple(Count: Word); @@ -1856,43 +2233,42 @@ procedure TMonster.Push(vx, vy: Integer); begin FObj.Accel.X := FObj.Accel.X + vx; FObj.Accel.Y := FObj.Accel.Y + vy; - if g_Game_IsServer and g_Game_IsNet then - MH_SEND_MonsterPos(FUID); + if g_Game_IsServer and g_Game_IsNet then MH_SEND_MonsterPos(FUID); end; 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); @@ -1904,11 +2280,11 @@ begin else Exit; end; -// Íàäî ñìåíèòü àíèìàöèþ íà íåñòàíäàðòíóþ: +// Надо сменить анимацию на нестандартную: if ForceAnim <> 255 then Anim := ForceAnim; -// Åñëè àíèìàöèÿ íîâàÿ - ïåðåçàïóñêàåì å¸: +// Если анимация новая - перезапускаем её: if FCurAnim <> Anim then if FAnim[Anim, FDirection] <> nil then begin @@ -1924,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); @@ -1935,7 +2311,7 @@ begin TA := nil; -// Ýôôåêò òåëåïîðòà â ïîçèöèè ìîíñòðà: +// Эффект телепорта в позиции монстра: if not silent then begin if g_Frames_Get(FramesID, 'FRAMES_TELEPORT') then @@ -1952,22 +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, @@ -1980,40 +2359,52 @@ begin NET_GFX_TELE); end; - if g_Game_IsServer and g_Game_IsNet then - MH_SEND_MonsterPos(FUID); + if g_Game_IsServer and g_Game_IsNet then MH_SEND_MonsterPos(FUID); 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; st: Word; 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); + positionChanged(); // this updates spatial accelerators Exit; end; @@ -2022,15 +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 @@ -2042,23 +2443,42 @@ 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 (FState = STATE_DEAD) then + if FFireTime > 0 then + begin + if WordBool(st and MOVE_INWATER) then + FFireTime := 0 + else + begin + OnFireFlame(1); + FFireTime := FFireTime - 1; + if (FState <> MONSTATE_DIE) and (FState <> MONSTATE_DEAD) then + if FFirePainTime = 0 then + begin + Damage(5, FFireAttacker, 0, 0, HIT_FLAME); + FFirePainTime := 18; + end + else + FFirePainTime := FFirePainTime - 1; + end; + end; + +// Мертвый ничего не делает: + 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: @@ -2078,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); @@ -2096,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 @@ -2124,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 @@ -2135,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; @@ -2146,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 @@ -2182,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 @@ -2211,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; @@ -2262,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; @@ -2276,134 +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 // "Ñòîèò" òâåðäî - // Ðûáà òðåïûõàåòñÿ íà ïîâåðõíîñòè: - if FObj.Accel.Y = 0 then - FObj.Vel.Y := -6; + 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 - if gGibs[a].Live and + begin + 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 - g_Obj_PushA(@gGibs[a].Obj, b, Random(61)+120) // íàëåâî + begin + g_Obj_PushA(@gGibs[a].Obj, b, Random(61)+120); // налево + end else - g_Obj_PushA(@gGibs[a].Obj, b, Random(61)); // íàïðàâî + begin + 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 @@ -2415,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; @@ -2440,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); @@ -2615,90 +3042,91 @@ 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 - sx := g_Monsters_Create(MONSTER_SOUL, FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)-30, - FObj.Y+FObj.Rect.Y+20, D_LEFT); - if sx <> -1 then + mon := g_Monsters_Create(MONSTER_SOUL, FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)-30, + FObj.Y+FObj.Rect.Y+20, TDirection.D_LEFT); + if mon <> nil then begin - gMonsters[sx].SetState(STATE_GO); - gMonsters[sx].FNoRespawn := True; + mon.SetState(MONSTATE_GO); + mon.FNoRespawn := True; Inc(gTotalMonsters); - if g_Game_IsNet then MH_SEND_MonsterSpawn(gMonsters[sx].UID); + if g_Game_IsNet then MH_SEND_MonsterSpawn(mon.UID); end; - sx := g_Monsters_Create(MONSTER_SOUL, FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2), - FObj.Y+FObj.Rect.Y+20, D_RIGHT); - if sx <> -1 then + mon := g_Monsters_Create(MONSTER_SOUL, FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2), + FObj.Y+FObj.Rect.Y+20, TDirection.D_RIGHT); + if mon <> nil then begin - gMonsters[sx].SetState(STATE_GO); - gMonsters[sx].FNoRespawn := True; + mon.SetState(MONSTATE_GO); + mon.FNoRespawn := True; Inc(gTotalMonsters); - if g_Game_IsNet then MH_SEND_MonsterSpawn(gMonsters[sx].UID); + if g_Game_IsNet then MH_SEND_MonsterSpawn(mon.UID); end; - sx := g_Monsters_Create(MONSTER_SOUL, FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)-15, - FObj.Y+FObj.Rect.Y, D_RIGHT); - if sx <> -1 then + mon := g_Monsters_Create(MONSTER_SOUL, FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)-15, + FObj.Y+FObj.Rect.Y, TDirection.D_RIGHT); + if mon <> nil then begin - gMonsters[sx].SetState(STATE_GO); - gMonsters[sx].FNoRespawn := True; + mon.SetState(MONSTATE_GO); + mon.FNoRespawn := True; Inc(gTotalMonsters); - if g_Game_IsNet then MH_SEND_MonsterSpawn(gMonsters[sx].UID); + if g_Game_IsNet then MH_SEND_MonsterSpawn(mon.UID); end; - if g_Game_IsNet then MH_SEND_CoopStats(gMonsters[sx].UID); + + 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; @@ -2706,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: @@ -2826,21 +3267,21 @@ _end: MONSTER_CACO: g_Weapon_ball2(wx, wy, tx, ty, FUID); MONSTER_PAIN: - begin // Ñîçäàåì Lost_Soul: - sx := g_Monsters_Create(MONSTER_SOUL, FObj.X+(FObj.Rect.Width div 2), - FObj.Y+FObj.Rect.Y, FDirection); + begin // Создаем Lost_Soul: + mon := g_Monsters_Create(MONSTER_SOUL, FObj.X+(FObj.Rect.Width div 2), + FObj.Y+FObj.Rect.Y, FDirection); - if sx <> -1 then - begin // Öåëü - öåëü Pain_Elemental'à. Ëåòèì ê íåé: - gMonsters[sx].FTargetUID := FTargetUID; + if mon <> nil then + begin // Цель - цель Pain_Elemental'а. Летим к ней: + mon.FTargetUID := FTargetUID; GetPos(FTargetUID, @o); - gMonsters[sx].FTargetTime := 0; - gMonsters[sx].FNoRespawn := True; - gMonsters[sx].SetState(STATE_GO); - gMonsters[sx].shoot(@o, True); + mon.FTargetTime := 0; + mon.FNoRespawn := True; + mon.SetState(MONSTATE_GO); + mon.shoot(@o, True); Inc(gTotalMonsters); - if g_Game_IsNet then MH_SEND_MonsterSpawn(gMonsters[sx].UID); + if g_Game_IsNet then MH_SEND_MonsterSpawn(mon.UID); end; end; end; @@ -2849,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 @@ -2893,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; @@ -2934,24 +3375,29 @@ 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); + positionChanged(); // this updates spatial accelerators Exit; end; @@ -2960,10 +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 @@ -2973,15 +3420,26 @@ 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 (FState = STATE_DEAD) then + if FFireTime > 0 then + begin + if WordBool(st and MOVE_INWATER) then + FFireTime := 0 + else + begin + OnFireFlame(1); + FFireTime := FFireTime - 1; + end; + end; + +// Мертвый ничего не делает: + if (FState = MONSTATE_DEAD) then goto _end; -// Âîçìîæíî, ñîçäàåì ïóçûðüêè â âîäå: +// Возможно, создаем пузырьки в воде: if WordBool(st and MOVE_INWATER) and (Random(32) = 0) then case FMonsterType of MONSTER_FISH: @@ -3001,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); @@ -3019,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 @@ -3046,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 @@ -3057,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; @@ -3068,152 +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 - if gGibs[a].Live and + begin + 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 - g_Obj_PushA(@gGibs[a].Obj, b, Random(61)+120) // íàëåâî + begin + g_Obj_PushA(@gGibs[a].Obj, b, Random(61)+120); // налево + end else - g_Obj_PushA(@gGibs[a].Obj, b, Random(61)); // íàïðàâî + begin + 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 @@ -3225,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; @@ -3244,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; @@ -3271,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; @@ -3408,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 @@ -3437,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; @@ -3466,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 @@ -3559,17 +4026,17 @@ _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; -procedure TMonster.ClientAttack(wx, wy, tx, ty: Integer); +procedure TMonster.ClientAttack(wx, wy, atx, aty: Integer); begin case FMonsterType of MONSTER_ZOMBY: @@ -3595,34 +4062,31 @@ begin g_Player_CreateShell(wx, wy, 0, -2, SHELL_BULLET); end; MONSTER_IMP: - g_Weapon_ball1(wx, wy, tx, ty, FUID); + g_Weapon_ball1(wx, wy, atx, aty, FUID); MONSTER_CYBER: - g_Weapon_rocket(wx, wy, tx, ty, FUID); + g_Weapon_rocket(wx, wy, atx, aty, FUID); MONSTER_SKEL: - g_Weapon_revf(wx, wy, tx, ty, FUID, FTargetUID); + g_Weapon_revf(wx, wy, atx, aty, FUID, FTargetUID); MONSTER_BSP: - g_Weapon_aplasma(wx, wy, tx, ty, FUID); + g_Weapon_aplasma(wx, wy, atx, aty, FUID); MONSTER_ROBO: - g_Weapon_plasma(wx, wy, tx, ty, FUID); + g_Weapon_plasma(wx, wy, atx, aty, FUID); MONSTER_MANCUB: - g_Weapon_manfire(wx, wy, tx, ty, FUID); + g_Weapon_manfire(wx, wy, atx, aty, FUID); MONSTER_BARON, MONSTER_KNIGHT: - g_Weapon_ball7(wx, wy, tx, ty, FUID); + g_Weapon_ball7(wx, wy, atx, aty, FUID); MONSTER_CACO: - g_Weapon_ball2(wx, wy, tx, ty, FUID); + g_Weapon_ball2(wx, wy, atx, aty, FUID); end; 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; @@ -3644,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 @@ -3665,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 @@ -3697,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; @@ -3724,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))]; @@ -3735,7 +4199,7 @@ begin a := MonstersSee[Random(Length(MonstersSee))]; FTargetUID := gMonsters[a].UID; end; - // Çàòåì èùóò êîãî-òî ïîáëèçîñòè + // Затем ищут кого-то поблизости if (FTargetUID = 0) and (PlayerNear > -1) then begin a := PlayerNear; @@ -3749,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; @@ -3772,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; @@ -3806,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; @@ -3826,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; @@ -3854,7 +4318,7 @@ begin else if Random(16) <> 0 then Exit; end; -// Öåëè íå âèäíî: +// Цели не видно: if not g_Look(@FObj, o, FDirection) then Exit; @@ -3863,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; @@ -3872,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(); @@ -3892,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)); @@ -3908,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; @@ -3921,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); @@ -3939,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; - -// Ñèãíàòóðà ìîíñòðà: - 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); -// Åñëè åñòü - çàãðóæàåì: + assert(st <> nil); + + // Сигнатура монстра: + 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; @@ -4144,4 +4597,236 @@ begin SetLength(FDieTriggers, 0); end; +procedure TMonster.CatchFire(Attacker: Word; Timeout: Integer = MON_BURN_TIME); +begin + 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; + +procedure TMonster.OnFireFlame(Times: DWORD = 1); +var + id, i: DWORD; + Anim: TAnimation; +begin + if (Random(10) = 1) and (Times = 1) then + Exit; + + if g_Frames_Get(id, 'FRAMES_FLAME') then + begin + for i := 1 to Times do + 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 = MONSTATE_DEAD, 16, 0), Anim, ONCEANIM_SMOKE); + Anim.Free(); + end; + end; +end; + + +// ////////////////////////////////////////////////////////////////////////// // +// throws on invalid uid +function g_Mons_ByIdx (uid: Integer): TMonster; inline; +begin + result := g_Mons_ByIdx_NC(uid); + 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 + if (uid < 0) or (uid > High(gMonsters)) then begin result := nil; exit; end; + result := gMonsters[uid]; +end; + +function g_Mons_TotalCount (): Integer; inline; +begin + result := Length(gMonsters); +end; + + +function g_Mons_ForEach (cb: TEachMonsterCB): Boolean; +var + idx: Integer; + mon: TMonster; +begin + result := false; + if (gMonsters = nil) or not assigned(cb) then exit; + for idx := 0 to High(gMonsters) do + begin + mon := gMonsters[idx]; + if (mon <> nil) then + begin + result := cb(mon); + if result then exit; + end; + end; +end; + + +function g_Mons_ForEachAlive (cb: TEachMonsterCB): Boolean; +var + idx: Integer; + mon: TMonster; +begin + result := false; + if (gMonsters = nil) or not assigned(cb) then exit; + for idx := 0 to High(gMonsters) do + begin + mon := gMonsters[idx]; + if (mon <> nil) and mon.alive then + begin + result := cb(mon); + if result then exit; + end; + end; +end; + + +function g_Mons_IsAnyAliveAt (x, y: Integer; width, height: Integer): Boolean; + (* + function monsCollCheck (mon: TMonster; atag: Integer): Boolean; + begin + 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 := (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.alive then + begin + if g_Obj_Collide(x, y, width, height, @mon.Obj) then + begin + result := true; + exit; + end; + end; + end; + end; +end; + + +function g_Mons_ForEachAt (x, y: Integer; width, height: Integer; cb: TEachMonsterCB): Boolean; + (* + function monsCollCheck (mon: TMonster; atag: Integer): Boolean; + begin + 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 := (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.alive then + begin + if g_Obj_Collide(x, y, width, height, @mon.Obj) then + begin + result := cb(mon); + if result then exit; + end; + end; + end; + end; +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.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 := (monsGrid.forEachAtPoint(x, y, monsCollCheck) <> nil); + end + else + begin + 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.alive then + begin + if g_Obj_Collide(x, y, width, height, @mon.Obj) then + begin + result := cb(mon); + if result then exit; + end; + end; + end; + end; +end; + + end.