From: DeaDDooMER Date: Mon, 7 Jun 2021 21:28:50 +0000 (+0300) Subject: render: separate monster logic and drawing X-Git-Url: http://deadsoftware.ru/gitweb?a=commitdiff_plain;h=273292d8328e27d0819e361198010dfbcd01ad60;p=d2df-sdl.git render: separate monster logic and drawing --- diff --git a/src/game/Doom2DF.lpr b/src/game/Doom2DF.lpr index 2629d70..0d2c38a 100644 --- a/src/game/Doom2DF.lpr +++ b/src/game/Doom2DF.lpr @@ -158,6 +158,7 @@ uses r_gfx in 'opengl/r_gfx.pas', r_items in 'opengl/r_items.pas', r_map in 'opengl/r_map.pas', + r_monsters in 'opengl/r_monsters.pas', r_panel in 'opengl/r_panel.pas', {$IFDEF USE_FMOD} diff --git a/src/game/g_monsters.pas b/src/game/g_monsters.pas index d8adad8..5aa8bdc 100644 --- a/src/game/g_monsters.pas +++ b/src/game/g_monsters.pas @@ -22,6 +22,7 @@ interface uses SysUtils, Classes, mempool, + MAPDEF, g_basic, e_graphics, g_phys, g_textures, g_grid, g_saveload, g_panel, xprofiler; @@ -51,6 +52,8 @@ const } type + ADirectedAnim = Array of Array [TDirection.D_LEFT..TDirection.D_RIGHT] of TAnimation; + PMonster = ^TMonster; TMonster = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF} private @@ -64,7 +67,7 @@ type FMaxHealth: Integer; FState: Byte; FCurAnim: Byte; - FAnim: Array of Array [TDirection.D_LEFT..TDirection.D_RIGHT] of TAnimation; + FAnim: ADirectedAnim; FTargetUID: Word; FTargetTime: Integer; FBehaviour: Byte; @@ -129,7 +132,6 @@ type procedure ClientUpdate(); procedure ClientAttack(wx, wy, atx, aty: Integer); procedure SetDeadAnim; - procedure Draw(); procedure WakeUp(); procedure WakeUpSound(); procedure DieSound(); @@ -192,6 +194,9 @@ type 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; @@ -235,8 +240,6 @@ function g_Monsters_Create (MonsterType: Byte; X, Y: Integer; Direction: TDirect AdjCoord: Boolean = False; ForcedUID: Integer = -1): TMonster; procedure g_Monsters_PreUpdate (); procedure g_Monsters_Update (); -procedure g_Monsters_Draw (); -procedure g_Monsters_DrawHealth (); function g_Monsters_ByUID (UID: Word): TMonster; procedure g_Monsters_killedp (); procedure g_Monsters_SaveState (st: TStream); @@ -310,151 +313,19 @@ var gmon_debug_think: Boolean = true; gmon_debug_one_think_step: Boolean = false; + var (* private state *) + gMonsters: array of TMonster; -implementation - -uses - e_log, e_texture, g_main, g_sound, g_gfx, g_player, g_game, - g_weapons, g_triggers, MAPDEF, g_items, g_options, - g_console, g_map, Math, g_menu, wadreader, - g_language, g_netmsg, idpool, utils, xstreams; - - - -// ////////////////////////////////////////////////////////////////////////// // -procedure g_Mons_ProfilersBegin (); -begin - if (profMonsLOS = nil) then profMonsLOS := TProfiler.Create('LOS CALC', g_profile_history_size); - if (profMonsLOS <> nil) then profMonsLOS.mainBegin(g_profile_los); - if g_profile_los and (profMonsLOS <> nil) then - begin - profMonsLOS.sectionBegin('loscalc'); - profMonsLOS.sectionEnd(); - end; -end; - -procedure g_Mons_ProfilersEnd (); -begin - if (profMonsLOS <> nil) and (g_profile_los) then profMonsLOS.mainEnd(); -end; - -procedure g_Mons_LOS_Start (); inline; -begin - if (profMonsLOS <> nil) then profMonsLOS.sectionBeginAccum('loscalc'); -end; - -procedure g_Mons_LOS_End (); inline; -begin - if (profMonsLOS <> nil) then profMonsLOS.sectionEnd(); -end; - - -// ////////////////////////////////////////////////////////////////////////// // -var - monCheckTrapLastFrameId: DWord = 0; - monCheckMPlatLastFrameId: LongWord = 0; - - -procedure TMonster.getMapBox (out x, y, w, h: Integer); inline; -begin - x := FObj.X+FObj.Rect.X; - y := FObj.Y+FObj.Rect.Y; - w := FObj.Rect.Width; - h := FObj.Rect.Height; -end; - -function TMonster.gncNeedSend (): Boolean; inline; begin result := mNeedSend; mNeedSend := false; end; - -procedure TMonster.setDirty (); inline; begin mNeedSend := true; end; - - -// ////////////////////////////////////////////////////////////////////////// // -{ -function g_Mons_AlongLine (x0, y0, x1, y1: Integer; cb: TMonsAlongLineCB; log: Boolean=false): TMonster; -begin - if not assigned(cb) then begin result := nil; exit; end; - result := monsGrid.forEachAlongLine(x0, y0, x1, y1, cb, -1, log); -end; -} - - -//WARNING! call this after monster position was changed, or coldet will not work right! -procedure TMonster.positionChanged (); -var - x, y, w, h: Integer; - nx, ny, nw, nh: Integer; -begin - {$IF DEFINED(D2F_DEBUG_MONS_MOVE)} - //e_WriteLog(Format('monster #%d(%u): pos=(%d,%d); rpos=(%d,%d)', [mArrIdx, UID, FObj.X, FObj.Y, FObj.Rect.X, FObj.Rect.Y]), MSG_NOTIFY); - {$ENDIF} - if (mProxyId = -1) then - begin - //mNeedSend := true; - mProxyId := monsGrid.insertBody(self, FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, FObj.Rect.Width, FObj.Rect.Height); - {$IF DEFINED(D2F_DEBUG_MONS_MOVE)} - monsGrid.getBodyXY(mProxyId, x, y); - e_WriteLog(Format('monster #%d:(%u): inserted into the grid; mProxyid=%d; gx=%d; gy=%d', [mArrIdx, UID, mProxyId, x-monsGrid.gridX0, y-monsGrid.gridY0]), MSG_NOTIFY); - {$ENDIF} - end - else - begin - monsGrid.getBodyDims(mProxyId, x, y, w, h); - getMapBox(nx, ny, nw, nh); - - if (w <> nw) or (h <> nh) then - begin - //mNeedSend := true; - {$IF DEFINED(D2F_DEBUG_MONS_MOVE)} - e_WriteLog(Format('monster #%d:(%u): resized; mProxyid=%d; gx=%d; gy=%d', [mArrIdx, UID, mProxyId, x-monsGrid.gridX0, y-monsGrid.gridY0]), MSG_NOTIFY); - {$ENDIF} - monsGrid.moveResizeBody(mProxyId, nx, ny, nw, nh); - end - else if (x <> nx) or (y <> ny) then - begin - //mNeedSend := true; - {$IF DEFINED(D2F_DEBUG_MONS_MOVE)} - e_WriteLog(Format('monster #%d:(%u): updating grid; mProxyid=%d; gx=%d; gy=%d', [mArrIdx, UID, mProxyId, x-monsGrid.gridX0, y-monsGrid.gridY0]), MSG_NOTIFY); - {$ENDIF} - monsGrid.moveBody(mProxyId, nx, ny); - end - else - begin - exit; // nothing to do - end; - {$IF DEFINED(D2F_DEBUG_MONS_MOVE)} - monsGrid.getBodyXY(mProxyId, x, y); - e_WriteLog(Format('monster #%d:(%u): updated grid; mProxyid=%d; gx=%d; gy=%d', [mArrIdx, UID, mProxyId, x-monsGrid.gridX0, y-monsGrid.gridY0]), MSG_NOTIFY); - {$ENDIF} - end; -end; - - -// ////////////////////////////////////////////////////////////////////////// // -const - ANIM_SLEEP = 0; - ANIM_GO = 1; - ANIM_DIE = 2; - ANIM_MESS = 3; - ANIM_ATTACK = 4; - ANIM_ATTACK2 = 5; - ANIM_PAIN = 6; - - 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)); + 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; @@ -525,11 +396,11 @@ 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 TDFPoint; AnimDeltaLeft: Array [ANIM_SLEEP..ANIM_PAIN] of TDFPoint; @@ -614,13 +485,147 @@ 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; + + + +// ////////////////////////////////////////////////////////////////////////// // +procedure g_Mons_ProfilersBegin (); +begin + if (profMonsLOS = nil) then profMonsLOS := TProfiler.Create('LOS CALC', g_profile_history_size); + if (profMonsLOS <> nil) then profMonsLOS.mainBegin(g_profile_los); + if g_profile_los and (profMonsLOS <> nil) then + begin + profMonsLOS.sectionBegin('loscalc'); + profMonsLOS.sectionEnd(); + end; +end; + +procedure g_Mons_ProfilersEnd (); +begin + if (profMonsLOS <> nil) and (g_profile_los) then profMonsLOS.mainEnd(); +end; + +procedure g_Mons_LOS_Start (); inline; +begin + if (profMonsLOS <> nil) then profMonsLOS.sectionBeginAccum('loscalc'); +end; + +procedure g_Mons_LOS_End (); inline; +begin + if (profMonsLOS <> nil) then profMonsLOS.sectionEnd(); +end; + + +// ////////////////////////////////////////////////////////////////////////// // +var + monCheckTrapLastFrameId: DWord = 0; + monCheckMPlatLastFrameId: LongWord = 0; + + +procedure TMonster.getMapBox (out x, y, w, h: Integer); inline; +begin + x := FObj.X+FObj.Rect.X; + y := FObj.Y+FObj.Rect.Y; + w := FObj.Rect.Width; + h := FObj.Rect.Height; +end; + +function TMonster.gncNeedSend (): Boolean; inline; begin result := mNeedSend; mNeedSend := false; end; + +procedure TMonster.setDirty (); inline; begin mNeedSend := true; end; + + +// ////////////////////////////////////////////////////////////////////////// // +{ +function g_Mons_AlongLine (x0, y0, x1, y1: Integer; cb: TMonsAlongLineCB; log: Boolean=false): TMonster; +begin + if not assigned(cb) then begin result := nil; exit; end; + result := monsGrid.forEachAlongLine(x0, y0, x1, y1, cb, -1, log); +end; +} + + +//WARNING! call this after monster position was changed, or coldet will not work right! +procedure TMonster.positionChanged (); +var + x, y, w, h: Integer; + nx, ny, nw, nh: Integer; +begin + {$IF DEFINED(D2F_DEBUG_MONS_MOVE)} + //e_WriteLog(Format('monster #%d(%u): pos=(%d,%d); rpos=(%d,%d)', [mArrIdx, UID, FObj.X, FObj.Y, FObj.Rect.X, FObj.Rect.Y]), MSG_NOTIFY); + {$ENDIF} + if (mProxyId = -1) then + begin + //mNeedSend := true; + mProxyId := monsGrid.insertBody(self, FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, FObj.Rect.Width, FObj.Rect.Height); + {$IF DEFINED(D2F_DEBUG_MONS_MOVE)} + monsGrid.getBodyXY(mProxyId, x, y); + e_WriteLog(Format('monster #%d:(%u): inserted into the grid; mProxyid=%d; gx=%d; gy=%d', [mArrIdx, UID, mProxyId, x-monsGrid.gridX0, y-monsGrid.gridY0]), MSG_NOTIFY); + {$ENDIF} + end + else + begin + monsGrid.getBodyDims(mProxyId, x, y, w, h); + getMapBox(nx, ny, nw, nh); + + if (w <> nw) or (h <> nh) then + begin + //mNeedSend := true; + {$IF DEFINED(D2F_DEBUG_MONS_MOVE)} + e_WriteLog(Format('monster #%d:(%u): resized; mProxyid=%d; gx=%d; gy=%d', [mArrIdx, UID, mProxyId, x-monsGrid.gridX0, y-monsGrid.gridY0]), MSG_NOTIFY); + {$ENDIF} + monsGrid.moveResizeBody(mProxyId, nx, ny, nw, nh); + end + else if (x <> nx) or (y <> ny) then + begin + //mNeedSend := true; + {$IF DEFINED(D2F_DEBUG_MONS_MOVE)} + e_WriteLog(Format('monster #%d:(%u): updating grid; mProxyid=%d; gx=%d; gy=%d', [mArrIdx, UID, mProxyId, x-monsGrid.gridX0, y-monsGrid.gridY0]), MSG_NOTIFY); + {$ENDIF} + monsGrid.moveBody(mProxyId, nx, ny); + end + else + begin + exit; // nothing to do + end; + {$IF DEFINED(D2F_DEBUG_MONS_MOVE)} + monsGrid.getBodyXY(mProxyId, x, y); + e_WriteLog(Format('monster #%d:(%u): updated grid; mProxyid=%d; gx=%d; gy=%d', [mArrIdx, UID, mProxyId, x-monsGrid.gridX0, y-monsGrid.gridY0]), MSG_NOTIFY); + {$ENDIF} + end; +end; + + +// ////////////////////////////////////////////////////////////////////////// // +const + MONSTER_SIGNATURE = $534E4F4D; // 'MONS' + +// Таблица типов анимации монстров: + ANIMTABLE: Array [ANIM_SLEEP..ANIM_PAIN] of + record + name: String; + loop: Boolean; + end = ((name: 'SLEEP'; loop: True), + (name: 'GO'; loop: True), + (name: 'DIE'; loop: False), + (name: 'MESS'; loop: False), + (name: 'ATTACK'; loop: False), + (name: 'ATTACK2'; loop: False), + (name: 'PAIN'; loop: False)); + + MAX_ATM = 89; // Время ожидания после потери цели + MAX_SOUL = 512; // Ограничение Lost_Soul'ов // ////////////////////////////////////////////////////////////////////////// // var - gMonsters: array of TMonster; uidMap: array [0..65535] of TMonster; // monster knows it's index freeInds: TIdPool = nil; @@ -696,26 +701,26 @@ 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; @@ -772,11 +777,11 @@ function isCorpse (o: PObj; immediately: Boolean): Integer; result := false; // don't stop if (mon.FState = MONSTATE_DEAD) and g_Obj_Collide(o, @mon.FObj) then begin - case mon.FMonsterType of // Íå âîñêðåñèòü: + case mon.FMonsterType of // Не воскресить: MONSTER_SOUL, MONSTER_PAIN, MONSTER_CYBER, MONSTER_SPIDER, MONSTER_VILE, MONSTER_BARREL, MONSTER_ROBO: exit; end; - // Îñòàëüíûõ ìîæíî âîñêðåñèòü + // Остальных можно воскресить result := true; end; end; @@ -790,10 +795,10 @@ var begin result := -1; - // Åñëè íóæíà âåðîÿòíîñòü + // Если нужна вероятность if not immediately and (Random(8) <> 0) then exit; - // Èùåì ìåðòâûõ ìîíñòðîâ ïîáëèçîñòè + // Ищем мертвых монстров поблизости 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); @@ -801,10 +806,10 @@ begin 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 // Íå âîñêðåñèòü: + 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; @@ -818,10 +823,10 @@ begin 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 // Íå âîñêðåñèòü: + 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; @@ -1342,10 +1347,10 @@ var begin result := nil; - // Íåò òàêîãî ìîíñòðà + // Нет такого монстра if (MonsterType > MONSTER_MAN) or (MonsterType = 0) then exit; - // Ñîáëþäàåì îãðàíè÷åíèå Lost_Soul'îâ + // Соблюдаем ограничение Lost_Soul'ов if MonsterType = MONSTER_SOUL then begin if soulcount > MAX_SOUL then exit; @@ -1361,7 +1366,7 @@ begin uidMap[mon.FUID] := mon; - // Íàñòðàèâàåì ïîëîæåíèå + // Настраиваем положение with mon do begin if AdjCoord then @@ -1395,7 +1400,7 @@ begin if gMonsters = nil then Exit; - // Ïðèêîëèñò ñìååòñÿ íàä ñìåðòüþ èãðîêà: + // Приколист смеется над смертью игрока: h := High(gMonsters); for a := 0 to h do begin @@ -1430,7 +1435,7 @@ procedure g_Monsters_Update(); var a: Integer; begin - // Öåëåóêàçàòåëü + // Целеуказатель if gTime mod (GAME_TICK*2) = 0 then begin pt_x := pt_x+pt_xs; @@ -1439,7 +1444,7 @@ begin if abs(pt_y) > 100 then pt_ys := -pt_ys; end; - gMon := True; // Äëÿ ðàáîòû BlockMon'à + gMon := True; // Для работы BlockMon'а if gmon_debug_think or gmon_debug_one_think_step then begin @@ -1465,38 +1470,6 @@ begin gMon := False; end; -procedure g_Monsters_Draw(); -var - a: Integer; -begin - if gMonsters <> nil then - begin - for a := 0 to High(gMonsters) do - begin - if (gMonsters[a] <> nil) then gMonsters[a].Draw(); - end; - end; -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 - begin - 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; -end; - function g_Monsters_ByUID (UID: Word): TMonster; begin result := uidMap[UID]; @@ -1506,32 +1479,32 @@ procedure g_Monsters_SaveState (st: TStream); var count, i: Integer; begin - // Ñ÷èòàåì êîëè÷åñòâî ñóùåñòâóþùèõ ìîíñòðîâ + // Считаем количество существующих монстров count := 0; for i := 0 to High(gMonsters) do begin if (gMonsters[i] <> nil) and (gMonsters[i].FMonsterType <> MONSTER_NONE) then count += 1; end; - // Ñîõðàíÿåì èíôîðìàöèþ öåëåóêàçàòåëÿ + // Сохраняем информацию целеуказателя utils.writeInt(st, LongInt(pt_x)); utils.writeInt(st, LongInt(pt_xs)); utils.writeInt(st, LongInt(pt_y)); utils.writeInt(st, LongInt(pt_ys)); - // Êîëè÷åñòâî ìîíñòðîâ + // Количество монстров utils.writeInt(st, LongInt(count)); if (count = 0) then exit; - // Ñîõðàíÿåì ìîíñòðîâ + // Сохраняем монстров for i := 0 to High(gMonsters) do 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; @@ -1548,27 +1521,27 @@ begin g_Monsters_Free(false); - // Çàãðóæàåì èíôîðìàöèþ öåëåóêàçàòåëÿ + // Загружаем информацию целеуказателя pt_x := utils.readLongInt(st); pt_xs := utils.readLongInt(st); pt_y := utils.readLongInt(st); pt_ys := utils.readLongInt(st); - // Êîëè÷åñòâî ìîíñòðîâ + // Количество монстров count := utils.readLongInt(st); 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 - // Òèï ìîíñòðà + // Тип монстра 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; @@ -1956,10 +1929,10 @@ 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; @@ -1977,7 +1950,7 @@ begin 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+ @@ -1990,7 +1963,7 @@ begin MONSTER_ANIMTABLE[MonsterType].AnimSpeed[a]); end; -// Äëÿ êîëäóíà çàãðóæàåì òàêæå àíèìàöèþ îãíÿ: +// Для колдуна загружаем также анимацию огня: if MonsterType = MONSTER_VILE then begin g_Frames_Get(FramesID, 'FRAMES_FIRE'); @@ -2007,14 +1980,14 @@ var begin Result := False; -// Ìîíñòð ñòàòè÷åí ïîêà èäåò warmup +// Монстр статичен пока идет 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; @@ -2024,38 +1997,38 @@ begin Exit; end; -// Àð÷è íå ãîðÿò, ÷åðåïà óæå ãîðÿò +// Арчи не горят, черепа уже горят if (t = HIT_FLAME) and (FMonsterType in [MONSTER_VILE, MONSTER_SOUL]) then begin - // Ïðîñíóòüñÿ âñå-òàêè ñòîèò + // Проснуться все-таки стоит if FState = MONSTATE_SLEEP then SetState(MONSTATE_GO); Exit; end; -// Ëîâóøêà óáèâàåò ñðàçó: +// Ловушка убивает сразу: if t = HIT_TRAP then FHealth := -100; -// Ðîáîòó óðîíà íåò: +// Роботу урона нет: if FMonsterType = MONSTER_ROBO then aDamage := 0; -// Íàíîñèì óðîí: +// Наносим урон: if g_Game_IsServer then Dec(FHealth, aDamage); -// Óñèëèâàåì áîëü ìîíñòðà îò óðîíà: +// Усиливаем боль монстра от урона: if FPain = 0 then FPain := 3; FPain := FPain+aDamage; -// Åñëè áîëü ñóùåñòâåííàÿ, òî ìåíÿåì ñîñòîÿíèå íà áîëåâîå: +// Если боль существенная, то меняем состояние на болевое: if FState <> MONSTATE_PAIN then if (FPain >= MONSTERTABLE[FMonsterType].MinPain) and (FMonsterType <> MONSTER_BARREL) then SetState(MONSTATE_PAIN); -// Åñëè ðàçðåøåíà êðîâü - ñîçäàåì áðûçãè êðîâè: +// Если разрешена кровь - создаем брызги крови: if (gBloodCount > 0) then begin c := Min(aDamage, 200); @@ -2070,17 +2043,17 @@ begin end; end; -// Òåïåðü öåëü - óäàðèâøèé, åñëè òîëüêî íå ñàì ñåáÿ: +// Теперь цель - ударивший, если только не сам себя: if (SpawnerUID <> FUID) and (BehaviourDamage(SpawnerUID, FBehaviour, FMonsterType)) then begin FTargetUID := SpawnerUID; FTargetTime := 0; end; -// Çäîðîâüå çàêîí÷èëîñü: +// Здоровье закончилось: if FHealth <= 0 then begin - // Åñëè ýòî íå áî÷êà è óáèë èãðîê, òî åìó +1: + // Если это не бочка и убил игрок, то ему +1: if (FMonsterType <> MONSTER_BARREL) then begin if (g_GetUIDType(SpawnerUID) = UID_PLAYER) then @@ -2103,7 +2076,7 @@ begin end; end; - // Âûáèðàåì ëóò: + // Выбираем лут: case FMonsterType of MONSTER_ZOMBY: c := ITEM_AMMO_BULLETS; MONSTER_SERG: c := ITEM_WEAPON_SHOTGUN1; @@ -2112,7 +2085,7 @@ begin else c := 0; end; - // Áðîñàåì ëóò: + // Бросаем лут: if c <> 0 then begin it := g_Items_Create(FObj.X + (FObj.Rect.Width div 2), @@ -2126,10 +2099,10 @@ begin 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; @@ -2137,7 +2110,7 @@ begin positionChanged(); end; - // Óðîí áûë ñèëüíûì => ñëàáûå - â êàøó: + // Урон был сильным => слабые - в кашу: if (FHealth <= -30) and ((FMonsterType = MONSTER_IMP) or (FMonsterType = MONSTER_ZOMBY) or (FMonsterType = MONSTER_SERG) or (FMonsterType = MONSTER_CGUN) or @@ -2152,14 +2125,14 @@ begin SetState(MONSTATE_DIE); end; - // Àêòèâèðîâàòü òðèããåðû, æäóùèå ñìåðòè ýòîãî ìîíñòðà: + // Активировать триггеры, ждущие смерти этого монстра: if g_Game_IsServer then ActivateTriggers(); FHealth := 0; end else if FState = MONSTATE_SLEEP then - begin // Ñïàë, ðàçáóäèëè íåñìåðòåëüíûì óäàðîì: + begin // Спал, разбудили несмертельным ударом: FPain := MONSTERTABLE[FMonsterType].Pain; SetState(MONSTATE_GO); end; @@ -2220,89 +2193,6 @@ begin inherited Destroy(); end; -procedure TMonster.Draw(); -var - m: TMirrorType; - dx, dy, c, fX, fY: 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)); - - FObj.lerp(gLerpFactor, fX, fY); - -// Åñëè êîëäóí ñòðåëÿåò, òî ðèñóåì îãîíü: - if FMonsterType = MONSTER_VILE then - if FState = MONSTATE_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, TMirrorType.None); - -// Íå â îáëàñòè ðèñîâàíèÿ íå ðåñóåì: -//FIXME! - if (g_dbg_scale = 1.0) then - begin - 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; - end; - -// Ýòè ìîíñòðû, óìèðàÿ, íå îñòàâëÿþò òðóïîâ: - if FState = MONSTATE_DEAD then - case FMonsterType of - MONSTER_BARREL, MONSTER_SOUL, MONSTER_PAIN: Exit; - end; - -// Åñòü ÷òî ðèñîâàòü ïðè òåêóùåì ïîâåäåíèè: - if FAnim[FCurAnim, FDirection] <> nil then - begin - // Åñëè íåò ëåâîé àíèìàöèè èëè îíà ñîâïàäàåò ñ ïðàâîé => îòðàæàåì ïðàâóþ: - if (FDirection = TDirection.D_LEFT) and - ((not MONSTER_ANIMTABLE[FMonsterType].LeftAnim) or - (FAnim[FCurAnim, TDirection.D_LEFT].FramesID = FAnim[FCurAnim, TDirection.D_RIGHT].FramesID)) and - (FMonsterType <> MONSTER_BARREL) then - m := TMirrorType.Horizontal - else - m := TMirrorType.None; - - // Ëåâàÿ àíèìàöèÿ => ìåíÿåì ñìåùåíèå îòíîñèòåëüíî öåíòðà: - if (FDirection = TDirection.D_LEFT) and - (FMonsterType <> MONSTER_BARREL) then - begin - dx := MONSTER_ANIMTABLE[FMonsterType].AnimDeltaLeft[FCurAnim].X; - dy := MONSTER_ANIMTABLE[FMonsterType].AnimDeltaLeft[FCurAnim].Y; - - if m = TMirrorType.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(fX+dx, fY+dy, m); - end; - - if g_debug_Frames then - begin - e_DrawQuad(FObj.X+FObj.Rect.X, - FObj.Y+FObj.Rect.Y, - FObj.X+FObj.Rect.X+FObj.Rect.Width-1, - FObj.Y+FObj.Rect.Y+FObj.Rect.Height-1, - 0, 255, 0); - end; -end; - procedure TMonster.MakeBloodSimple(Count: Word); begin g_GFX_Blood(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)+8, @@ -2334,12 +2224,12 @@ procedure TMonster.SetState(State: Byte; ForceAnim: Byte = 255); var Anim: Byte; begin -// Åñëè ñîñòîÿíèå = íà÷àëè óìèðàòü, à ýòîò ìîíñòð = Lost_Soul, -// òî ñîáëþäàåì îãðàíè÷åíèå êîëè÷åñòâà Lost_Soul'îâ: +// Если состояние = начали умирать, а этот монстр = Lost_Soul, +// то соблюдаем ограничение количества Lost_Soul'ов: if (State = MONSTATE_DIE) and (MonsterType = MONSTER_SOUL) then soulcount := soulcount-1; -// Ïðèñìåðòè - íåëüçÿ ñðàçó íà÷èíàòü àòàêîâàòü èëè áåãàòü: +// Присмерти - нельзя сразу начинать атаковать или бегать: case FState of MONSTATE_DIE, MONSTATE_DEAD, MONSTATE_REVIVE: if (State <> MONSTATE_DEAD) and (State <> MONSTATE_REVIVE) and @@ -2347,12 +2237,12 @@ begin Exit; end; -// Ñìåíà ñîñòîÿíèÿ: +// Смена состояния: FState := State; if g_Game_IsServer and g_Game_IsNet then MH_SEND_MonsterState(FUID, ForceAnim); -// Íîâàÿ àíèìàöèÿ ïðè íîâîì ñîñòîÿíèè: +// Новая анимация при новом состоянии: case FState of MONSTATE_SLEEP: Anim := ANIM_SLEEP; MONSTATE_PAIN: Anim := ANIM_PAIN; @@ -2362,7 +2252,7 @@ begin MONSTATE_ATTACK: Anim := ANIM_ATTACK; MONSTATE_DIE: Anim := ANIM_DIE; MONSTATE_REVIVE: - begin // íà÷àëè âîñðåøàòüñÿ + begin // начали восрешаться Anim := FCurAnim; FAnim[Anim, FDirection].Revert(True); @@ -2374,11 +2264,11 @@ begin else Exit; end; -// Íàäî ñìåíèòü àíèìàöèþ íà íåñòàíäàðòíóþ: +// Надо сменить анимацию на нестандартную: if ForceAnim <> 255 then Anim := ForceAnim; -// Åñëè àíèìàöèÿ íîâàÿ - ïåðåçàïóñêàåì å¸: +// Если анимация новая - перезапускаем её: if FCurAnim <> Anim then if FAnim[Anim, FDirection] <> nil then begin @@ -2394,7 +2284,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); @@ -2405,7 +2295,7 @@ begin TA := nil; -// Ýôôåêò òåëåïîðòà â ïîçèöèè ìîíñòðà: +// Эффект телепорта в позиции монстра: if not silent then begin if g_Frames_Get(FramesID, 'FRAMES_TELEPORT') then @@ -2433,14 +2323,14 @@ begin FDirection := TDirection.D_RIGHT else if dir = 3 then - begin // îáðàòíîå + begin // обратное if FDirection = TDirection.D_RIGHT then FDirection := TDirection.D_LEFT else 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, @@ -2478,16 +2368,16 @@ begin fall := True; bubbles := True; -// Ìîíñòð ñòàòè÷åí ïîêà èäåò warmup +// Монстр статичен пока идет 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 <> MONSTATE_DIE) and (FState <> MONSTATE_DEAD) then fall := False; -// Ëåòàþùèå ìîíòñðû: +// Летающие монтсры: if ((FMonsterType = MONSTER_SOUL) or (FMonsterType = MONSTER_PAIN) or (FMonsterType = MONSTER_CACO)) and @@ -2495,7 +2385,7 @@ begin (FState <> MONSTATE_DEAD) then fall := False; -// Ìåíÿåì ñêîðîñòü òîëüêî ïî ÷åòíûì êàäðàì: +// Меняем скорость только по четным кадрам: if gTime mod (GAME_TICK*2) <> 0 then begin g_Obj_Move(@FObj, fall, True, True); @@ -2508,11 +2398,11 @@ begin else FPainSound := False; -// Äâèãàåìñÿ: +// Двигаемся: st := g_Obj_Move(@FObj, fall, True, True); positionChanged(); // this updates spatial accelerators -// Åñëè ãîðèì - ïîäæèãàåì äðóãèõ ìîíñòðîâ, íî íå íà 100 òèêîâ êàæäûé ðàç: +// Если горим - поджигаем других монстров, но не на 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); @@ -2521,7 +2411,7 @@ begin 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 @@ -2538,7 +2428,7 @@ begin oldvelx := FObj.Vel.X; -// Ñîïðîòèâëåíèå âîçäóõà äëÿ òðóïà: +// Сопротивление воздуха для трупа: if (FState = MONSTATE_DIE) or (FState = MONSTATE_DEAD) then FObj.Vel.X := z_dec(FObj.Vel.X, 1); @@ -2561,11 +2451,11 @@ begin end; end; -// Ìåðòâûé íè÷åãî íå äåëàåò: +// Мертвый ничего не делает: if (FState = MONSTATE_DEAD) then goto _end; -// AI ìîíñòðîâ âûêëþ÷åí: +// AI монстров выключен: if g_debug_MonsterOff then begin FSleep := 1; @@ -2573,7 +2463,7 @@ begin SetState(MONSTATE_SLEEP); end; -// Âîçìîæíî, ñîçäàåì ïóçûðüêè â âîäå: +// Возможно, создаем пузырьки в воде: if WordBool(st and MOVE_INWATER) and (Random(32) = 0) then begin case FMonsterType of @@ -2593,7 +2483,7 @@ begin else g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', FObj.X, FObj.Y); end; -// Åñëè ïðîøåë ïåðâûé êàäð àíèìàöèè âçðûâà áî÷êè, òî âçðûâ: +// Если прошел первый кадр анимации взрыва бочки, то взрыв: if FMonsterType = MONSTER_BARREL then begin if (FState = MONSTATE_DIE) and (FAnim[FCurAnim, FDirection].CurrentFrame = 1) and @@ -2603,7 +2493,7 @@ begin 60, FUID); end; -// Lost_Soul âûëåòåë èç âîäû => óñêîðÿåòñÿ: +// Lost_Soul вылетел из воды => ускоряется: if FMonsterType = MONSTER_SOUL then if WordBool(st and MOVE_HITAIR) then g_Obj_SetSpeed(@FObj, 16); @@ -2611,15 +2501,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 @@ -2639,7 +2529,7 @@ begin FShellTimer := -1; end else Dec(FShellTimer); -// Ïðîáóåì óâåðíóòüñÿ îò ëåòÿùåé ïóëè: +// Пробуем увернуться от летящей пули: if fall then if (FState in [MONSTATE_GO, MONSTATE_RUN, MONSTATE_RUNOUT, MONSTATE_ATTACK, MONSTATE_SHOOT]) then @@ -2650,9 +2540,9 @@ begin FObj.Vel.Y := -MONSTERTABLE[FMonsterType].Jump; case FState of - MONSTATE_PAIN: // Ñîñòîÿíèå - Áîëü + MONSTATE_PAIN: // Состояние - Боль begin - // Áîëü ñèëüíàÿ => ìîíñòð êðè÷èò: + // Боль сильная => монстр кричит: if FPain >= MONSTERTABLE[FMonsterType].Pain then begin FPain := MONSTERTABLE[FMonsterType].Pain; @@ -2661,10 +2551,10 @@ begin if (not gSoundEffectsDF) and (FPain >= MONSTERTABLE[FMonsterType].MinPain) then PainSound(); - // Ñíèæàåì áîëü ñî âðåìåíåì: + // Снижаем боль со временем: FPain := FPain - 5; - // Áîëü óæå íå îøóòèìàÿ => èäåì äàëüøå: + // Боль уже не ошутимая => идем дальше: if FPain <= MONSTERTABLE[FMonsterType].MinPain then begin FPain := 0; @@ -2673,20 +2563,20 @@ begin end; end; - MONSTATE_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].alive) @@ -2701,24 +2591,24 @@ begin 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].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 @@ -2732,19 +2622,19 @@ begin end; end; - MONSTATE_WAIT: // Ñîñòîÿíèå - Îæèäàíèå + MONSTATE_WAIT: // Состояние - Ожидание begin - // Æäåì: + // Ждем: FSleep := FSleep - 1; - // Âûæäàëè äîñòàòî÷íî - èäåì: + // Выждали достаточно - идем: if FSleep < 0 then SetState(MONSTATE_GO); end; - MONSTATE_GO: // Ñîñòîÿíèå - Äâèæåíèå (ñ îñìîòðîì ñèòóàöèè) + MONSTATE_GO: // Состояние - Движение (с осмотром ситуации) begin - // Åñëè íàòêíóëèñü íà ÁëîêÌîí - óáåãàåì îò íåãî: + // Если наткнулись на БлокМон - убегаем от него: if WordBool(st and MOVE_BLOCK) then begin Turn(); @@ -2754,7 +2644,7 @@ begin goto _end; end; - // Åñëè ìîíñòð - êîëäóí, òî ïðîáóåì âîñêðåñèòü êîãî-íèáóäü: + // Если монстр - колдун, то пробуем воскресить кого-нибудь: if (FMonsterType = MONSTER_VILE) then if isCorpse(@FObj, False) <> -1 then begin @@ -2764,10 +2654,10 @@ begin 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; @@ -2777,10 +2667,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; @@ -2791,23 +2681,23 @@ 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 := TDirection.D_RIGHT else 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 @@ -2821,12 +2711,12 @@ begin 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(MONSTATE_WAIT); @@ -2835,11 +2725,11 @@ 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(MONSTATE_CLIMB); end; @@ -2848,40 +2738,40 @@ begin 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(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_CollideLiquid(@FObj, 0, -16) then begin - // Âñïëûëè äî ïîâåðõíîñòè - ñòîï: + // Всплыли до поверхности - стоп: FObj.Vel.Y := 0; - // Ïëàâàåì òóäà-ñþäà: + // Плаваем туда-сюда: if Random(2) = 0 then FDirection := TDirection.D_LEFT else @@ -2891,18 +2781,18 @@ begin 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); @@ -2913,19 +2803,19 @@ begin g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y+FObj.Rect.Height-4, FObj.Rect.Width, 8, @gGibs[a].Obj) and (Random(3) = 0) then begin - // Ïèíàåì êóñêè + // Пинаем куски if FObj.Vel.X < 0 then begin - g_Obj_PushA(@gGibs[a].Obj, b, Random(61)+120); // íàëåâî + g_Obj_PushA(@gGibs[a].Obj, b, Random(61)+120); // налево end else begin - g_Obj_PushA(@gGibs[a].Obj, b, Random(61)); // íàïðàâî + g_Obj_PushA(@gGibs[a].Obj, b, Random(61)); // направо end; end; end; end; - // Áîññû ìîãóò ïèíàòü òðóïû: + // Боссы могут пинать трупы: if (FMonsterType in [MONSTER_CYBER, MONSTER_SPIDER, MONSTER_ROBO]) and (FObj.Vel.X <> 0) and (gCorpses <> nil) then begin @@ -2937,24 +2827,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, FUID, -b, Random(7)) // íàëåâî + gCorpses[a].Damage(b*2, FUID, -b, Random(7)) // налево else - gCorpses[a].Damage(b*2, FUID, 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; @@ -2962,23 +2852,23 @@ begin ActionSound(); end; - // Áåæèì â âûáðàííóþ ñòîðîíó: + // Бежим в выбранную сторону: 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; - MONSTATE_RUN: // Ñîñòîÿíèå - Áåã + MONSTATE_RUN: // Состояние - Бег begin - // Åñëè íàòêíóëèñü íà ÁëîêÌîí - óáåãàåì îò íåãî: + // Если наткнулись на БлокМон - убегаем от него: if WordBool(st and MOVE_BLOCK) then begin Turn(); @@ -2990,78 +2880,78 @@ begin 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(MONSTATE_GO); - // Ñòåíà - èäåì îáðàòíî: + // Стена - идем обратно: if WordBool(st and (MOVE_HITWALL or MOVE_BLOCK)) then Turn(); - // Èíîãäà ðû÷èì: + // Иногда рычим: if Random(8) = 0 then ActionSound(); end; - // Áåæèì â âûáðàííóþ ñòîðîíó: + // Бежим в выбранную сторону: 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; - MONSTATE_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(MONSTATE_GO); - // Ñòåíà/ÁëîêÌîí - èäåì îáðàòíî: + // Стена/БлокМон - идем обратно: if WordBool(st and (MOVE_HITWALL or MOVE_BLOCK)) then Turn(); - // Èíîãäà ðû÷èì: + // Иногда рычим: if Random(8) = 0 then ActionSound(); end; - // Áåæèì â âûáðàííóþ ñòîðîíó: + // Бежим в выбранную сторону: 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; - MONSTATE_CLIMB: // Ñîñòîÿíèå - Ïðûæîê (÷òîáû îáîéòè ñòåíó) + MONSTATE_CLIMB: // Состояние - Прыжок (чтобы обойти стену) begin - // Äîñòèãëè âûñøåé òî÷êè ïðûæêà èëè ñòåíà êîí÷èëàñü => ïåðåõîäèì íà øàã: + // Достигли высшей точки прыжка или стена кончилась => переходим на шаг: if ((FObj.Vel.Y+FObj.Accel.Y) >= 0) or (not WordBool(st and MOVE_HITWALL)) then begin FSleep := 0; SetState(MONSTATE_GO); - // Ñòåíà íå êîí÷èëàñü => áåæèì îò íåå: + // Стена не кончилась => бежим от нее: if WordBool(st and (MOVE_HITWALL or MOVE_BLOCK)) then begin Turn(); @@ -3070,24 +2960,24 @@ begin end; end; - // Áåæèì â âûáðàííóþ ñòîðîíó: + // Бежим в выбранную сторону: 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; - MONSTATE_ATTACK, // Ñîñòîÿíèå - Àòàêà - MONSTATE_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 @@ -3096,14 +2986,14 @@ begin goto _end; end; - // Çàìåäëÿåìñÿ ïðè àòàêå: + // Замедляемся при атаке: if FMonsterType <> MONSTER_FISH then FObj.Vel.X := z_dec(FObj.Vel.X, 1); - // Íóæíî ñòðåëÿòü, à ìîíñòð - êîëäóí: + // Нужно стрелять, а монстр - колдун: if (FMonsterType = MONSTER_VILE) and (FState = MONSTATE_SHOOT) then begin - // Öåëü ïîãèáëà => èäåì äàëüøå: + // Цель погибла => идем дальше: if not GetPos(FTargetUID, @o) then begin SetState(MONSTATE_GO); @@ -3111,7 +3001,7 @@ begin goto _end; end; - // Öåëü íå âèäíî => èäåì äàëüøå: + // Цель не видно => идем дальше: if not g_Look(@FObj, @o, FDirection) then begin SetState(MONSTATE_GO); @@ -3119,7 +3009,7 @@ begin goto _end; end; - // Öåëü â âîäå - íå çàãîðèòñÿ => èäåì äàëüøå: + // Цель в воде - не загорится => идем дальше: if g_Obj_CollideWater(@o, 0, 0) then begin SetState(MONSTATE_GO); @@ -3127,7 +3017,7 @@ begin 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); @@ -3137,27 +3027,27 @@ begin _end: -// Ñîñòîÿíèå - Âîñêðåøåíèå: +// Состояние - Воскрешение: if FState = MONSTATE_REVIVE then if FAnim[FCurAnim, FDirection].Played then - begin // Îáðàòíàÿ àíèìàöèÿ óìèðàíèÿ çàêîí÷èëàñü - èäåì äàëüøå: + begin // Обратная анимация умирания закончилась - идем дальше: FAnim[FCurAnim, FDirection].Revert(False); SetState(MONSTATE_GO); end; -// Åñëè åñòü àíèìàöèÿ îãíÿ êîëäóíà - ïóñòü îíà èäåò: +// Если есть анимация огня колдуна - пусть она идет: if vilefire <> nil then vilefire.Update(); -// Ñîñòîÿíèå - Óìèðàåò è òåêóùàÿ àíèìàöèÿ ïðîèãðàíà: +// Состояние - Умирает и текущая анимация проиграна: if (FState = MONSTATE_DIE) and (FAnim[FCurAnim, FDirection] <> nil) and (FAnim[FCurAnim, FDirection].Played) then begin - // Óìåð: + // Умер: SetState(MONSTATE_DEAD); - // Pain_Elemental ïðè ñìåðòè âûïóñêàåò 3 Lost_Soul'à: + // Pain_Elemental при смерти выпускает 3 Lost_Soul'а: if (FMonsterType = MONSTER_PAIN) then begin mon := g_Monsters_Create(MONSTER_SOUL, FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)-30, @@ -3193,35 +3083,35 @@ _end: if g_Game_IsNet then MH_SEND_CoopStats(); end; - // Ó ýòèõ ìîíñòðîâ íåò òðóïîâ: + // У этих монстров нет трупов: if (FMonsterType = MONSTER_PAIN) or (FMonsterType = MONSTER_SOUL) or (FMonsterType = MONSTER_BARREL) then FRemoved := True; end; -// Ñîâåðøåíèå àòàêè è ñòðåëüáû: +// Совершение атаки и стрельбы: if (FState = MONSTATE_ATTACK) or (FState = MONSTATE_SHOOT) then if (FAnim[FCurAnim, FDirection] <> nil) then - // Àíèìàöèÿ àòàêè åñòü - ìîæíî àòàêîâàòü + // Анимация атаки есть - можно атаковать if (FAnim[FCurAnim, FDirection].Played) then - begin // Àíèìàöèÿ àòàêè çàêîí÷èëàñü => ïåðåõîäèì íà øàã + begin // Анимация атаки закончилась => переходим на шаг if FState = MONSTATE_ATTACK then - begin // Ñîñòîÿíèå - Àòàêà - // Åñëè ìîíñòð íå Lost_Soul, òî ïîñëå àòàêè ïåðåõîäèì íà øàã: + begin // Состояние - Атака + // Если монстр не Lost_Soul, то после атаки переходим на шаг: if FMonsterType <> MONSTER_SOUL then SetState(MONSTATE_GO); end - else // Ñîñòîÿíèå - Ñòðåëüáà + else // Состояние - Стрельба begin - // Ïåðåõîäèì íà øàã, åñëè íå íàäî ñòðåëÿòü åùå ðàç: + // Переходим на шаг, если не надо стрелять еще раз: if not FChainFire then SetState(MONSTATE_GO) else - begin // Íàäî ñòðåëÿòü åùå + begin // Надо стрелять еще FChainFire := False; - // Ò.ê. ñîñòîÿíèå íå èçìåíèëîñü, è íóæåí - // íîâûé öèêë îæèäàíèÿ çàâåðøåíèÿ àíèìàöèè: + // Т.к. состояние не изменилось, и нужен + // новый цикл ожидания завершения анимации: FAnim[FCurAnim, FDirection].Reset(); end; end; @@ -3229,34 +3119,34 @@ _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 // Àòàêè åùå íå áûëî è ýòî ñåðåäèíà àíèìàöèè àòàêè + begin // Атаки еще не было и это середина анимации атаки if FState = MONSTATE_ATTACK then - begin // Ñîñòîÿíèå - Àòàêà - // Åñëè ýòî Lost_Soul, òî ñáðàñûâàåì àíèìàöèþ àòàêè: + 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(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; @@ -3266,49 +3156,49 @@ _end: end; MONSTER_VILE: - // Êîëäóí ïûòàåòñÿ âîñêðåøàòü: + // Колдун пытается воскрешать: if FCurAnim = ANIM_ATTACK2 then begin sx := isCorpse(@FObj, True); if sx <> -1 then - begin // Íàøëè, êîãî âîñêðåñèòü + 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 = 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); @@ -3362,12 +3252,12 @@ _end: MONSTER_CACO: g_Weapon_ball2(wx, wy, tx, ty, FUID); MONSTER_PAIN: - begin // Ñîçäàåì Lost_Soul: + begin // Создаем Lost_Soul: mon := g_Monsters_Create(MONSTER_SOUL, FObj.X+(FObj.Rect.Width div 2), FObj.Y+FObj.Rect.Y, FDirection); if mon <> nil then - begin // Öåëü - öåëü Pain_Elemental'à. Ëåòèì ê íåé: + begin // Цель - цель Pain_Elemental'а. Летим к ней: mon.FTargetUID := FTargetUID; GetPos(FTargetUID, @o); mon.FTargetTime := 0; @@ -3385,29 +3275,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 MONSTATE_GO, MONSTATE_RUN, MONSTATE_CLIMB, MONSTATE_RUNOUT: - // Çâóêè ïðè ïåðåäâèæåíèè: + // Звуки при передвижении: case FMonsterType of MONSTER_CYBER: if (FAnim[FCurAnim, FDirection].CurrentFrame = 0) or @@ -3432,7 +3322,7 @@ _end: not ((FState = MONSTATE_DEAD) or (FState = MONSTATE_DIE)) then FObj.Vel.X := oldvelx; -// Åñëè åñòü àíèìàöèÿ, òî ïóñòü îíà èäåò: +// Если есть анимация, то пусть она идет: if FAnim[FCurAnim, FDirection] <> nil then FAnim[FCurAnim, FDirection].Update(); end; @@ -3472,16 +3362,16 @@ begin fall := True; bubbles := True; -// Ìîíñòð ñòàòè÷åí ïîêà èäåò warmup +// Монстр статичен пока идет 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 <> MONSTATE_DIE) and (FState <> MONSTATE_DEAD) then fall := False; -// Ëåòàþùèå ìîíòñðû: +// Летающие монтсры: if ((FMonsterType = MONSTER_SOUL) or (FMonsterType = MONSTER_PAIN) or (FMonsterType = MONSTER_CACO)) and @@ -3489,7 +3379,7 @@ begin (FState <> MONSTATE_DEAD) then fall := False; -// Ìåíÿåì ñêîðîñòü òîëüêî ïî ÷åòíûì êàäðàì: +// Меняем скорость только по четным кадрам: if gTime mod (GAME_TICK*2) <> 0 then begin g_Obj_Move(@FObj, fall, True, True); @@ -3502,11 +3392,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 @@ -3516,7 +3406,7 @@ begin oldvelx := FObj.Vel.X; -// Ñîïðîòèâëåíèå âîçäóõà äëÿ òðóïà: +// Сопротивление воздуха для трупа: if (FState = MONSTATE_DIE) or (FState = MONSTATE_DEAD) then FObj.Vel.X := z_dec(FObj.Vel.X, 1); @@ -3531,11 +3421,11 @@ begin end; end; -// Ìåðòâûé íè÷åãî íå äåëàåò: +// Мертвый ничего не делает: if (FState = MONSTATE_DEAD) then goto _end; -// Âîçìîæíî, ñîçäàåì ïóçûðüêè â âîäå: +// Возможно, создаем пузырьки в воде: if WordBool(st and MOVE_INWATER) and (Random(32) = 0) then begin case FMonsterType of @@ -3555,7 +3445,7 @@ begin else g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', FObj.X, FObj.Y); end; -// Åñëè ïðîøåë ïåðâûé êàäð àíèìàöèè âçðûâà áî÷êè, òî âçðûâ: +// Если прошел первый кадр анимации взрыва бочки, то взрыв: if FMonsterType = MONSTER_BARREL then begin if (FState = MONSTATE_DIE) and (FAnim[FCurAnim, FDirection].CurrentFrame = 1) and @@ -3565,7 +3455,7 @@ begin 60, FUID); end; -// Lost_Soul âûëåòåë èç âîäû => óñêîðÿåòñÿ: +// Lost_Soul вылетел из воды => ускоряется: if FMonsterType = MONSTER_SOUL then if WordBool(st and MOVE_HITAIR) then g_Obj_SetSpeed(@FObj, 16); @@ -3573,12 +3463,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 @@ -3600,7 +3490,7 @@ begin FShellTimer := -1; end else Dec(FShellTimer); -// Ïðîáóåì óâåðíóòüñÿ îò ëåòÿùåé ïóëè: +// Пробуем увернуться от летящей пули: if fall then if (FState in [MONSTATE_GO, MONSTATE_RUN, MONSTATE_RUNOUT, MONSTATE_ATTACK, MONSTATE_SHOOT]) then @@ -3611,9 +3501,9 @@ begin FObj.Vel.Y := -MONSTERTABLE[FMonsterType].Jump; case FState of - MONSTATE_PAIN: // Ñîñòîÿíèå - Áîëü + MONSTATE_PAIN: // Состояние - Боль begin - // Áîëü ñèëüíàÿ => ìîíñòð êðè÷èò: + // Боль сильная => монстр кричит: if FPain >= MONSTERTABLE[FMonsterType].Pain then begin FPain := MONSTERTABLE[FMonsterType].Pain; @@ -3622,10 +3512,10 @@ begin if (not gSoundEffectsDF) and (FPain >= MONSTERTABLE[FMonsterType].MinPain) then PainSound(); - // Ñíèæàåì áîëü ñî âðåìåíåì: + // Снижаем боль со временем: FPain := FPain - 5; - // Áîëü óæå íå îøóòèìàÿ => èäåì äàëüøå: + // Боль уже не ошутимая => идем дальше: if FPain <= MONSTERTABLE[FMonsterType].MinPain then begin SetState(MONSTATE_GO); @@ -3633,27 +3523,27 @@ begin end; end; - MONSTATE_SLEEP: // Ñîñòîÿíèå - Ñîí + MONSTATE_SLEEP: // Состояние - Сон begin - // Ñïèì: + // Спим: FSleep := FSleep + 1; - // Ïðîñïàëè äîñòàòî÷íî: + // Проспали достаточно: if FSleep >= 18 then FSleep := 0 - else // åùå ñïèì + else // еще спим goto _end; end; - MONSTATE_WAIT: // Ñîñòîÿíèå - Îæèäàíèå + MONSTATE_WAIT: // Состояние - Ожидание begin - // Æäåì: + // Ждем: FSleep := FSleep - 1; end; - MONSTATE_GO: // Ñîñòîÿíèå - Äâèæåíèå (ñ îñìîòðîì ñèòóàöèè) + MONSTATE_GO: // Состояние - Движение (с осмотром ситуации) begin - // Åñëè íàòêíóëèñü íà ÁëîêÌîí - óáåãàåì îò íåãî: + // Если наткнулись на БлокМон - убегаем от него: if WordBool(st and MOVE_BLOCK) then begin Turn(); @@ -3663,7 +3553,7 @@ begin goto _end; end; - // Åñëè ìîíñòð - êîëäóí, òî ïðîáóåì âîñêðåñèòü êîãî-íèáóäü: + // Если монстр - колдун, то пробуем воскресить кого-нибудь: if (FMonsterType = MONSTER_VILE) then if isCorpse(@FObj, False) <> -1 then begin @@ -3673,7 +3563,7 @@ begin goto _end; end; - // Åñëè öåëü ïî÷òè íà îäíîé âåðòèêàëè - áåãàåì òóäà-ñþäà: + // Если цель почти на одной вертикали - бегаем туда-сюда: if Abs(sx) < 40 then if FMonsterType <> MONSTER_FISH then begin @@ -3683,16 +3573,16 @@ begin 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(MONSTATE_CLIMB); end; @@ -3701,58 +3591,58 @@ begin 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(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_CollideLiquid(@FObj, 0, -16) then begin - // Âñïëûëè äî ïîâåðõíîñòè - ñòîï: + // Всплыли до поверхности - стоп: FObj.Vel.Y := 0; - // Ïëàâàåì òóäà-ñþäà: + // Плаваем туда-сюда: 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); @@ -3763,20 +3653,20 @@ begin g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y+FObj.Rect.Height-4, FObj.Rect.Width, 8, @gGibs[a].Obj) and (Random(3) = 0) then begin - // Ïèíàåì êóñêè + // Пинаем куски if FObj.Vel.X < 0 then begin - g_Obj_PushA(@gGibs[a].Obj, b, Random(61)+120); // íàëåâî + g_Obj_PushA(@gGibs[a].Obj, b, Random(61)+120); // налево end else begin - g_Obj_PushA(@gGibs[a].Obj, b, Random(61)); // íàïðàâî + g_Obj_PushA(@gGibs[a].Obj, b, Random(61)); // направо end; positionChanged(); // this updates spatial accelerators end; end; end; - // Áîññû ìîãóò ïèíàòü òðóïû: + // Боссы могут пинать трупы: if (FMonsterType in [MONSTER_CYBER, MONSTER_SPIDER, MONSTER_ROBO]) and (FObj.Vel.X <> 0) and (gCorpses <> nil) then begin @@ -3788,18 +3678,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, FUID, -b, Random(7)) // íàëåâî + gCorpses[a].Damage(b*2, FUID, -b, Random(7)) // налево else - gCorpses[a].Damage(b*2, FUID, 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; @@ -3807,23 +3697,23 @@ begin ActionSound(); end; - // Áåæèì â âûáðàííóþ ñòîðîíó: + // Бежим в выбранную сторону: 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; - MONSTATE_RUN: // Ñîñòîÿíèå - Áåã + MONSTATE_RUN: // Состояние - Бег begin - // Åñëè íàòêíóëèñü íà ÁëîêÌîí - óáåãàåì îò íåãî: + // Если наткнулись на БлокМон - убегаем от него: if WordBool(st and MOVE_BLOCK) then begin SetState(MONSTATE_RUNOUT); @@ -3834,74 +3724,74 @@ begin FSleep := FSleep - 1; - // Ïðîáåæàëè äîñòàòî÷íî èëè âðåçàëèñü â ñòåíó => ïåðåõîäèì íà øàã: + // Пробежали достаточно или врезались в стену => переходим на шаг: if (FSleep <= 0) or (WordBool(st and MOVE_HITWALL) and ((FObj.Vel.Y+FObj.Accel.Y) = 0)) then begin SetState(MONSTATE_GO); FSleep := 0; - // Èíîãäà ðû÷èì: + // Иногда рычим: if Random(8) = 0 then ActionSound(); end; - // Áåæèì â âûáðàííóþ ñòîðîíó: + // Бежим в выбранную сторону: 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; - MONSTATE_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(MONSTATE_GO); FSleep := 0; - // Èíîãäà ðû÷èì: + // Иногда рычим: if Random(8) = 0 then ActionSound(); end; - // Áåæèì â âûáðàííóþ ñòîðîíó: + // Бежим в выбранную сторону: 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; - MONSTATE_CLIMB: // Ñîñòîÿíèå - Ïðûæîê (÷òîáû îáîéòè ñòåíó) + MONSTATE_CLIMB: // Состояние - Прыжок (чтобы обойти стену) begin - // Äîñòèãëè âûñøåé òî÷êè ïðûæêà èëè ñòåíà êîí÷èëàñü => ïåðåõîäèì íà øàã: + // Достигли высшей точки прыжка или стена кончилась => переходим на шаг: if ((FObj.Vel.Y+FObj.Accel.Y) >= 0) or (not WordBool(st and MOVE_HITWALL)) then begin SetState(MONSTATE_GO); FSleep := 0; - // Ñòåíà íå êîí÷èëàñü => áåæèì îò íåå: + // Стена не кончилась => бежим от нее: if WordBool(st and (MOVE_HITWALL or MOVE_BLOCK)) then begin SetState(MONSTATE_RUN); @@ -3909,24 +3799,24 @@ begin end; end; - // Áåæèì â âûáðàííóþ ñòîðîíó: + // Бежим в выбранную сторону: 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; - MONSTATE_ATTACK, // Ñîñòîÿíèå - Àòàêà - MONSTATE_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 @@ -3935,14 +3825,14 @@ begin goto _end; end; - // Çàìåäëÿåìñÿ ïðè àòàêå: + // Замедляемся при атаке: if FMonsterType <> MONSTER_FISH then FObj.Vel.X := z_dec(FObj.Vel.X, 1); - // Íóæíî ñòðåëÿòü, à ìîíñòð - êîëäóí: + // Нужно стрелять, а монстр - колдун: if (FMonsterType = MONSTER_VILE) and (FState = MONSTATE_SHOOT) then begin - // Öåëü ïîãèáëà => èäåì äàëüøå: + // Цель погибла => идем дальше: if not GetPos(FTargetUID, @o) then begin SetState(MONSTATE_GO); @@ -3950,7 +3840,7 @@ begin goto _end; end; - // Öåëü íå âèäíî => èäåì äàëüøå: + // Цель не видно => идем дальше: if not g_Look(@FObj, @o, FDirection) then begin SetState(MONSTATE_GO); @@ -3958,7 +3848,7 @@ begin goto _end; end; - // Öåëü â âîäå - íå çàãîðèòñÿ => èäåì äàëüøå: + // Цель в воде - не загорится => идем дальше: if g_Obj_CollideWater(@o, 0, 0) then begin SetState(MONSTATE_GO); @@ -3971,27 +3861,27 @@ begin _end: -// Ñîñòîÿíèå - Âîñêðåøåíèå: +// Состояние - Воскрешение: if FState = MONSTATE_REVIVE then if FAnim[FCurAnim, FDirection].Played then - begin // Îáðàòíàÿ àíèìàöèÿ óìèðàíèÿ çàêîí÷èëàñü - èäåì äàëüøå: + begin // Обратная анимация умирания закончилась - идем дальше: FAnim[FCurAnim, FDirection].Revert(False); SetState(MONSTATE_GO); end; -// Åñëè åñòü àíèìàöèÿ îãíÿ êîëäóíà - ïóñòü îíà èäåò: +// Если есть анимация огня колдуна - пусть она идет: if vilefire <> nil then vilefire.Update(); -// Ñîñòîÿíèå - Óìèðàåò è òåêóùàÿ àíèìàöèÿ ïðîèãðàíà: +// Состояние - Умирает и текущая анимация проиграна: if (FState = MONSTATE_DIE) and (FAnim[FCurAnim, FDirection] <> nil) and (FAnim[FCurAnim, FDirection].Played) then begin - // Óìåð: + // Умер: SetState(MONSTATE_DEAD); - // Ó ýòèõ ìîíñòðîâ íåò òðóïîâ: + // У этих монстров нет трупов: if (FMonsterType = MONSTER_PAIN) or (FMonsterType = MONSTER_SOUL) or (FMonsterType = MONSTER_BARREL) then @@ -4000,28 +3890,28 @@ _end: FAnim[FCurAnim, FDirection].CurrentFrame := FAnim[FCurAnim, FDirection].TotalFrames - 1; end; -// Ñîâåðøåíèå àòàêè è ñòðåëüáû: +// Совершение атаки и стрельбы: if (FState = MONSTATE_ATTACK) or (FState = MONSTATE_SHOOT) then if (FAnim[FCurAnim, FDirection] <> nil) then - // Àíèìàöèÿ àòàêè åñòü - ìîæíî àòàêîâàòü + // Анимация атаки есть - можно атаковать if (FAnim[FCurAnim, FDirection].Played) then - begin // Àíèìàöèÿ àòàêè çàêîí÷èëàñü => ïåðåõîäèì íà øàã + begin // Анимация атаки закончилась => переходим на шаг if FState = MONSTATE_ATTACK then - begin // Ñîñòîÿíèå - Àòàêà - // Åñëè ìîíñòð íå Lost_Soul, òî ïîñëå àòàêè ïåðåõîäèì íà øàã: + begin // Состояние - Атака + // Если монстр не Lost_Soul, то после атаки переходим на шаг: if FMonsterType <> MONSTER_SOUL then SetState(MONSTATE_GO); end - else // Ñîñòîÿíèå - Ñòðåëüáà + else // Состояние - Стрельба begin - // Ïåðåõîäèì íà øàã, åñëè íå íàäî ñòðåëÿòü åùå ðàç: + // Переходим на шаг, если не надо стрелять еще раз: if not FChainFire then SetState(MONSTATE_GO) else - begin // Íàäî ñòðåëÿòü åùå + begin // Надо стрелять еще FChainFire := False; - // Ò.ê. ñîñòîÿíèå íå èçìåíèëîñü, è íóæåí - // íîâûé öèêë îæèäàíèÿ çàâåðøåíèÿ àíèìàöèè: + // Т.к. состояние не изменилось, и нужен + // новый цикл ожидания завершения анимации: FAnim[FCurAnim, FDirection].Reset(); end; end; @@ -4029,24 +3919,24 @@ _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 // Àòàêè åùå íå áûëî è ýòî ñåðåäèíà àíèìàöèè àòàêè + begin // Атаки еще не было и это середина анимации атаки if FState = MONSTATE_ATTACK then - begin // Ñîñòîÿíèå - Àòàêà - // Åñëè ýòî Lost_Soul, òî ñáðàñûâàåì àíèìàöèþ àòàêè: + 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(MONSTATE_GO); @@ -4054,7 +3944,7 @@ _end: g_Weapon_Hit(@FObj, 10, FUID, HIT_SOME); MONSTER_SKEL, MONSTER_ROBO, MONSTER_CYBER: - // Ðîáîò, êèáåð èëè ñêåëåò ñèëüíî ïèíàþòñÿ: + // Робот, кибер или скелет сильно пинаются: if FCurAnim = ANIM_ATTACK2 then begin o := FObj; @@ -4063,45 +3953,45 @@ _end: 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 MONSTATE_GO, MONSTATE_RUN, MONSTATE_CLIMB, MONSTATE_RUNOUT: - // Çâóêè ïðè ïåðåäâèæåíèè: + // Звуки при передвижении: case FMonsterType of MONSTER_CYBER: if (FAnim[FCurAnim, FDirection].CurrentFrame = 0) or @@ -4122,12 +4012,12 @@ _end: end; end; -// Êîñòûëü äëÿ ïîòîêîâ +// Костыль для потоков if g_Obj_CollidePanel(@FObj, 0, 0, PANEL_LIFTLEFT or PANEL_LIFTRIGHT) and not ((FState = MONSTATE_DEAD) or (FState = MONSTATE_DIE)) then FObj.Vel.X := oldvelx; -// Åñëè åñòü àíèìàöèÿ, òî ïóñòü îíà èäåò: +// Если есть анимация, то пусть она идет: if FAnim[FCurAnim, FDirection] <> nil then FAnim[FCurAnim, FDirection].Update(); end; @@ -4178,10 +4068,10 @@ end; procedure TMonster.Turn(); begin -// Ðàçâîðà÷èâàåìñÿ: +// Разворачиваемся: if FDirection = TDirection.D_LEFT then FDirection := TDirection.D_RIGHT else FDirection := TDirection.D_LEFT; -// Áåæèì â âûáðàííóþ ñòîðîíó: +// Бежим в выбранную сторону: if FDirection = TDirection.D_RIGHT then FObj.Vel.X := MONSTERTABLE[FMonsterType].RunVel else @@ -4204,7 +4094,7 @@ 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 @@ -4225,20 +4115,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].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 @@ -4257,25 +4147,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; @@ -4284,7 +4174,7 @@ begin end; BH_MANIAC, BH_INSANE, BH_CANNIBAL: begin - // Ìàíüÿêè, ïîåõàâøèå è êàííèáàëû ñíà÷àëà èñòðåáëÿþò âñ¸ â ïîëå çðåíèÿ + // Маньяки, поехавшие и каннибалы сначала истребляют всё в поле зрения if (FTargetUID = 0) and (Length(PlayersSee) > 0) then begin a := PlayersSee[Random(Length(PlayersSee))]; @@ -4295,7 +4185,7 @@ begin a := MonstersSee[Random(Length(MonstersSee))]; FTargetUID := gMonsters[a].UID; end; - // Çàòåì èùóò êîãî-òî ïîáëèçîñòè + // Затем ищут кого-то поблизости if (FTargetUID = 0) and (PlayerNear > -1) then begin a := PlayerNear; @@ -4309,17 +4199,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; @@ -4355,7 +4245,7 @@ begin end; MONSTER_BARON, MONSTER_KNIGHT, MONSTER_CACO, MONSTER_MANCUB: - // Ýòè ìîíñòðû íå ïèíàþò - îíè ñòðåëÿþò â óïîð: + // Эти монстры не пинают - они стреляют в упор: if not g_Game_IsClient then Result := shoot(o, True); end; end; @@ -4366,19 +4256,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; @@ -4386,24 +4276,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; @@ -4414,7 +4304,7 @@ begin else if Random(16) <> 0 then Exit; end; -// Öåëè íå âèäíî: +// Цели не видно: if not g_Look(@FObj, o, FDirection) then Exit; @@ -4423,8 +4313,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; @@ -4441,7 +4331,7 @@ begin {nn} end; MONSTER_VILE: - begin // Çàæèãàåì îãîíü + begin // Зажигаем огонь tx := o^.X+o^.Rect.X+(o^.Rect.Width div 2); ty := o^.Y+o^.Rect.Y; SetState(MONSTATE_SHOOT); @@ -4452,7 +4342,7 @@ begin g_Sound_PlayExAt('SOUND_FIRE', o^.X, o^.Y); end; MONSTER_SOUL: - begin // Ëåòèò â ñòîðîíó öåëè: + begin // Летит в сторону цели: SetState(MONSTATE_ATTACK); g_Sound_PlayExAt('SOUND_MONSTER_SOUL_ATTACK', FObj.X, FObj.Y); @@ -4468,7 +4358,7 @@ 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); @@ -4512,68 +4402,68 @@ var begin assert(st <> nil); - // Ñèãíàòóðà ìîíñòðà: + // Сигнатура монстра: utils.writeSign(st, 'MONS'); utils.writeInt(st, Byte(0)); // version - // UID ìîíñòðà: + // 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 öåëè + // 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 ìîíñòðà ïðè ñòàðòå êàðòû + // 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, 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; @@ -4587,78 +4477,78 @@ var begin 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 ìîíñòðà: + // 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 öåëè + // 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 ìîíñòðà ïðè ñòàðòå êàðòû + // 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(st); end; - // Àíèìàöèè + // Анимации for i := ANIM_SLEEP to ANIM_PAIN do begin - // Åñòü ëè ëåâàÿ àíèìàöèÿ + // Есть ли левая анимация anim := utils.readBool(st); - // Åñëè åñòü - çàãðóæàåì + // Если есть - загружаем if anim then begin Assert(FAnim[i, TDirection.D_LEFT] <> nil, 'TMonster.LoadState: no '+IntToStr(i)+'_left anim'); FAnim[i, TDirection.D_LEFT].LoadState(st); end; - // Åñòü ëè ïðàâàÿ àíèìàöèÿ + // Есть ли правая анимация anim := utils.readBool(st); - // Åñëè åñòü - çàãðóæàåì + // Если есть - загружаем if anim then begin Assert(FAnim[i, TDirection.D_RIGHT] <> nil, 'TMonster.LoadState: no '+IntToStr(i)+'_right anim'); @@ -4698,10 +4588,10 @@ end; procedure TMonster.CatchFire(Attacker: Word; Timeout: Integer = MON_BURN_TIME); begin if FMonsterType in [MONSTER_SOUL, MONSTER_VILE] then - exit; // àð÷è íå ãîðÿò, ÷åðåïà óæå ãîðÿò + exit; // арчи не горят, черепа уже горят if Timeout <= 0 then exit; if g_Obj_CollidePanel(@FObj, 0, 0, PANEL_WATER or PANEL_ACID1 or PANEL_ACID2) then - exit; // íå ïîäãîðàåì â âîäå íà âñÿêèé ñëó÷àé + exit; // не подгораем в воде на всякий случай if FFireTime <= 0 then g_Sound_PlayExAt('SOUND_IGNITE', FObj.X, FObj.Y); FFireTime := Timeout; diff --git a/src/game/opengl/r_game.pas b/src/game/opengl/r_game.pas index abf0068..b9ca74d 100644 --- a/src/game/opengl/r_game.pas +++ b/src/game/opengl/r_game.pas @@ -32,7 +32,7 @@ implementation g_textures, e_input, e_sound, g_language, g_console, g_menu, g_triggers, g_player, g_options, g_monsters, g_map, g_panel, g_window, g_items, g_weapons, g_gfx, g_phys, g_net, g_gui, g_netmaster, - g_game, r_console, r_gfx, r_items, r_map, r_panel + g_game, r_console, r_gfx, r_items, r_map, r_panel, r_monsters ; var @@ -1244,7 +1244,7 @@ begin drawOther('drawall', @g_Player_DrawAll); drawOther('corpses', @g_Player_DrawCorpses); drawPanelType('*wall', PANEL_WALL, g_rlayer_wall); - drawOther('monsters', @g_Monsters_Draw); + drawOther('monsters', @r_Monsters_Draw); drawOther('itemdrop', @r_Items_DrawDrop); drawPanelType('*door', PANEL_CLOSEDOOR, g_rlayer_door); drawOther('gfx', @r_GFX_Draw); @@ -1265,7 +1265,7 @@ begin if g_debug_HealthBar then begin - g_Monsters_DrawHealth(); + r_Monsters_DrawHealth(); g_Player_DrawHealth(); end; diff --git a/src/game/opengl/r_monsters.pas b/src/game/opengl/r_monsters.pas new file mode 100644 index 0000000..fe822be --- /dev/null +++ b/src/game/opengl/r_monsters.pas @@ -0,0 +1,131 @@ +(* 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, 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 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *) +{$INCLUDE ../../shared/a_modes.inc} +unit r_monsters; + +interface + + procedure r_Monsters_Draw; + procedure r_Monsters_DrawHealth; + +implementation + + uses + SysUtils, Classes, Math, + e_graphics, + MAPDEF, + g_basic, g_game, g_phys, + g_monsters + ; + + procedure r_Monsters_Draw (constref monster: TMonster); + var m: TMirrorType; dx, dy, c, fX, fY: Integer; o: TObj; + begin + with monster do + begin + //e_CharFont_Print(gMenuSmallFont, Obj.X + Obj.Rect.X, Obj.Y + Obj.Rect.Y, 'TYPE: ' + IntToStr(MonsterType)); + //e_CharFont_Print(gMenuSmallFont, Obj.X + Obj.Rect.X, Obj.Y + Obj.Rect.Y + 16, 'STATE: ' + IntToStr(MonsterState)); + + Obj.lerp(gLerpFactor, fX, fY); + + // Если колдун стреляет, то рисуем огонь: + if MonsterType = MONSTER_VILE then + if MonsterState = MONSTATE_SHOOT then + if GetPos(MonsterTargetUID, @o) then + VileFireAnim.Draw(o.X + o.Rect.X + (o.Rect.Width div 2) - 32, o.Y + o.Rect.Y + o.Rect.Height - 128, TMirrorType.None); + + // Не в области рисования не ресуем: + //FIXME! + if (g_dbg_scale = 1.0) then + begin + if not g_Collide(Obj.X + Obj.Rect.X, Obj.Y + Obj.Rect.Y, Obj.Rect.Width, Obj.Rect.Height, sX - 128, sY - 128, sWidth + 256, sHeight + 256) then + Exit; + end; + + // Эти монстры, умирая, не оставляют трупов: + if MonsterState = MONSTATE_DEAD then + case MonsterType of + MONSTER_BARREL, MONSTER_SOUL, MONSTER_PAIN: Exit; + end; + + // Есть что рисовать при текущем поведении: + if DirAnim[MonsterAnim, GameDirection] <> nil then + begin + // Если нет левой анимации или она совпадает с правой => отражаем правую: + if (GameDirection = TDirection.D_LEFT) and ((not MONSTER_ANIMTABLE[MonsterType].LeftAnim) or (DirAnim[MonsterAnim, TDirection.D_LEFT].FramesID = DirAnim[MonsterAnim, TDirection.D_RIGHT].FramesID)) and (MonsterType <> MONSTER_BARREL) then + m := TMirrorType.Horizontal + else + m := TMirrorType.None; + + // Левая анимация => меняем смещение относительно центра: + if (GameDirection = TDirection.D_LEFT) and (MonsterType <> MONSTER_BARREL) then + begin + dx := MONSTER_ANIMTABLE[MonsterType].AnimDeltaLeft[MonsterAnim].X; + dy := MONSTER_ANIMTABLE[MonsterType].AnimDeltaLeft[MonsterAnim].Y; + + if m = TMirrorType.Horizontal then + begin + // Нет отдельной левой анимации + // Расстояние от края текстуры до края визуального положения объекта на текстуре: + c := (MONSTERTABLE[MonsterType].Rect.X - dx) + MONSTERTABLE[MonsterType].Rect.Width; + // Расстояние от края хит бокса до края визуального положения объекта на текстуре: + dx := DirAnim[MonsterAnim, GameDirection].Width - c - MONSTERTABLE[MonsterType].Rect.X; + // Т.к. двигать текстуру нужно будет в противоположном направлении: + dx := -dx; + // Это значит: dX := -frameWidth - animDeltaX + hitX + hitWidth + hitX + end + end + else // Правая анимация + begin + dx := MONSTER_ANIMTABLE[MonsterType].AnimDeltaRight[MonsterAnim].X; + dy := MONSTER_ANIMTABLE[MonsterType].AnimDeltaRight[MonsterAnim].Y; + end; + + // Рисуем: + DirAnim[MonsterAnim, GameDirection].Draw(fX + dx, fY + dy, m); + end; + + if g_debug_Frames then + begin + e_DrawQuad(Obj.X + Obj.Rect.X, Obj.Y + Obj.Rect.Y, Obj.X + Obj.Rect.X + Obj.Rect.Width - 1, Obj.Y + Obj.Rect.Y + Obj.Rect.Height - 1, 0, 255, 0); + end + end + end; + + procedure r_Monsters_Draw; + var a: Integer; + begin + if gMonsters <> nil then + for a := 0 to High(gMonsters) do + if (gMonsters[a] <> nil) then r_Monsters_Draw(gMonsters[a]); + end; + + procedure r_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 + begin + if gMonsters[a] <> nil then + begin + e_TextureFontPrint(gMonsters[a].Obj.X + gMonsters[a].Obj.Rect.X, + gMonsters[a].Obj.Y + gMonsters[a].Obj.Rect.Y + gMonsters[a].Obj.Rect.Height - fH, + IntToStr(gMonsters[a].MonsterHealth), gStdFont); + end + end + end; + +end.