X-Git-Url: http://deadsoftware.ru/gitweb?a=blobdiff_plain;f=src%2Fgame%2Fg_monsters.pas;h=9f395fdde5dc474ab1ef27649f64d3b6ea88eaef;hb=dc9a52bb70e79e18ad014ce777029bb8ee1d85c4;hp=1c9040aabb0f5cf0b5f052fbaa88958827cb5530;hpb=ff104a8cdc79693b7546d839605ec73ae135d904;p=d2df-sdl.git diff --git a/src/game/g_monsters.pas b/src/game/g_monsters.pas index 1c9040a..9f395fd 100644 --- a/src/game/g_monsters.pas +++ b/src/game/g_monsters.pas @@ -1,3 +1,19 @@ +(* Copyright (C) DooM 2D:Forever Developers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 g_monsters; interface @@ -48,6 +64,7 @@ type FPain: Integer; FSleep: Integer; FPainSound: Boolean; + FPainTicks: Integer; FWaitAttackAnim: Boolean; FChainFire: Boolean; tx, ty: Integer; @@ -59,8 +76,13 @@ type FBloodKind: Byte; FShellTimer: Integer; FShellType: Byte; + FFirePainTime: Integer; + FFireAttacker: Word; vilefire: TAnimation; + treeNode: Integer; // node in dyntree or -1 + arrIdx: Integer; // in gMonsters + FDieTriggers: Array of Integer; FSpawnTrigger: Integer; @@ -70,6 +92,8 @@ type public FNoRespawn: Boolean; + FFireTime: Integer; + trapCheckFrameId: DWord; // for `g_weapons.CheckTrap()` constructor Create(MonsterType: Byte; aID: Integer; ForcedUID: Integer = -1); destructor Destroy(); override; @@ -80,12 +104,12 @@ type function Live(): Boolean; procedure SetHealth(aH: Integer); procedure Push(vx, vy: Integer); - function Damage(Damage: Word; VelX, VelY: Integer; SpawnerUID: Word; t: Byte): Boolean; + function Damage(aDamage: Word; VelX, VelY: Integer; SpawnerUID: Word; t: Byte): Boolean; function Heal(Value: Word): Boolean; procedure BFGHit(); procedure Update(); procedure ClientUpdate(); - procedure ClientAttack(wx, wy, tx, ty: Integer); + procedure ClientAttack(wx, wy, atx, aty: Integer); procedure SetDeadAnim; procedure Draw(); procedure WakeUp(); @@ -105,6 +129,10 @@ type function AnimIsReverse: Boolean; function shoot(o: PObj; immediately: Boolean): Boolean; function kick(o: PObj): Boolean; + procedure CatchFire(Attacker: Word); + procedure OnFireFlame(Times: DWORD = 1); + + procedure positionChanged (); //WARNING! call this after monster position was changed, or coldet will not work right! property MonsterType: Byte read FMonsterType; property MonsterHealth: Integer read FHealth write FHealth; @@ -133,34 +161,123 @@ type property StartID: Integer read FStartID; end; -procedure g_Monsters_LoadData(); -procedure g_Monsters_FreeData(); -procedure g_Monsters_Init(); -procedure g_Monsters_Free(); -function g_Monsters_Create(MonsterType: Byte; X, Y: Integer; - Direction: TDirection; AdjCoord: Boolean = False; ForcedUID: Integer = -1): Integer; -procedure g_Monsters_Update(); -procedure g_Monsters_Draw(); -procedure g_Monsters_DrawHealth(); -function g_Monsters_Get(UID: Word): TMonster; -procedure g_Monsters_killedp(); -procedure g_Monsters_SaveState(var Mem: TBinMemoryWriter); -procedure g_Monsters_LoadState(var Mem: TBinMemoryReader); -function g_Monsters_GetIDByName(name: String): Integer; -function g_Monsters_GetNameByID(MonsterType: Byte): String; -function g_Monsters_GetKilledBy(MonsterType: Byte): String; + +procedure g_Monsters_LoadData (); +procedure g_Monsters_FreeData (); +procedure g_Monsters_Init (); +procedure g_Monsters_Free (); +function g_Monsters_Create (MonsterType: Byte; X, Y: Integer; Direction: TDirection; + AdjCoord: Boolean = False; ForcedUID: Integer = -1): TMonster; +procedure g_Monsters_Update (); +procedure g_Monsters_Draw (); +procedure g_Monsters_DrawHealth (); +function g_Monsters_ByUID (UID: Word): TMonster; +procedure g_Monsters_killedp (); +procedure g_Monsters_SaveState (var Mem: TBinMemoryWriter); +procedure g_Monsters_LoadState (var Mem: TBinMemoryReader); +function g_Monsters_GetIDByName (name: String): Integer; +function g_Monsters_GetNameByID (MonsterType: Byte): String; +function g_Monsters_GetKilledBy (MonsterType: Byte): String; + + +type + TEachMonsterCB = function (monidx: Integer; mon: TMonster): Boolean is nested; // return `true` to stop + +function g_Mons_ForEach (cb: TEachMonsterCB): Boolean; + +// throws on invalid uid +function g_Mons_ByIdx (uid: Integer): TMonster; inline; + +// can return null +function g_Mons_ByIdx_NC (uid: Integer): TMonster; inline; + +function g_Mons_IsAnyAliveAt (x, y: Integer; width, height: Integer): Boolean; + +function g_Mons_ForEachAt (x, y: Integer; width, height: Integer; cb: TEachMonsterCB): Boolean; +function g_Mons_ForEachAtAlive (x, y: Integer; width, height: Integer; cb: TEachMonsterCB): Boolean; + +function g_Mons_getNewTrapFrameId (): DWord; + var - gMonsters: array of TMonster; + gmon_debug_use_sqaccel: Boolean = true; + implementation uses e_log, g_main, g_sound, g_gfx, g_player, g_game, g_weapons, g_triggers, MAPDEF, g_items, g_options, - g_console, g_map, Math, SysUtils, g_menu, WADEDITOR, - g_language, g_netmsg; + g_console, g_map, Math, SysUtils, g_menu, wadreader, + g_language, g_netmsg, z_aabbtree; + +// ////////////////////////////////////////////////////////////////////////// // +var + monCheckTrapLastFrameId: DWord; + + +// ////////////////////////////////////////////////////////////////////////// // +type + TDynAABBTreeMonsBase = specialize TDynAABBTreeBase; + + TDynAABBTreeMons = class(TDynAABBTreeMonsBase) + function getFleshAABB (out aabb: AABB2D; flesh: TMonster; tag: Integer): Boolean; override; + end; + +function TDynAABBTreeMons.getFleshAABB (out aabb: AABB2D; flesh: TMonster; tag: Integer): Boolean; +begin + result := false; + if (flesh = nil) then raise Exception.Create('DynTree: trying to get dimensions of inexistant monsters'); + if (flesh.Obj.Rect.Width < 1) or (flesh.Obj.Rect.Height < 1) then raise Exception.Create('DynTree: monster without size, wtf?!'); + aabb := AABB2D.CreateWH(flesh.Obj.X+flesh.Obj.Rect.X, flesh.Obj.Y+flesh.Obj.Rect.Y, flesh.Obj.Rect.Width, flesh.Obj.Rect.Height); + if not aabb.valid then raise Exception.Create('wutafuuuuuuu?!'); + result := true; +end; + + +var + monsTree: TDynAABBTreeMons = nil; + + +//WARNING! call this after monster position was changed, or coldet will not work right! +procedure TMonster.positionChanged (); +var + x, y: Integer; +begin + {$IF DEFINED(D2F_DEBUG)} + //e_WriteLog(Format('monster #%d(%u): pos=(%d,%d); rpos=(%d,%d)', [arrIdx, UID, FObj.X, FObj.Y, FObj.Rect.X, FObj.Rect.Y]), MSG_NOTIFY); + {$ENDIF} + if (treeNode = -1) then + begin + treeNode := monsTree.insertObject(self, 0); + {$IF DEFINED(D2F_DEBUG)} + monsTree.getNodeXY(treeNode, x, y); + e_WriteLog(Format('monster #%d(%u): inserted into the tree; nodeid=%d; x=%d; y=%d', [arrIdx, UID, treeNode, x, y]), MSG_NOTIFY); + {$ENDIF} + end + else + begin + monsTree.getNodeXY(treeNode, x, y); + if (FObj.X+FObj.Rect.X = x) and (FObj.Y+FObj.Rect.Y = y) then exit; // nothing to do + {$IF DEFINED(D2F_DEBUG)}e_WriteLog(Format('monster #%d(%u): updating tree; nodeid=%d; x=%d; y=%d', [arrIdx, UID, treeNode, x, y]), MSG_NOTIFY);{$ENDIF} + + {$IFDEF TRUE} + monsTree.updateObject(treeNode); + {$ELSE} + monsTree.removeObject(treeNode); + treeNode := monsTree.insertObject(self); + {$ENDIF} + + {$IF DEFINED(D2F_DEBUG)} + monsTree.getNodeXY(treeNode, x, y); + e_WriteLog(Format('monster #%d(%u): updated tree; nodeid=%d; x=%d; y=%d', [arrIdx, UID, treeNode, x, y]), MSG_NOTIFY); + {$ENDIF} + end; +end; + + +// ////////////////////////////////////////////////////////////////////////// // const ANIM_SLEEP = 0; ANIM_GO = 1; @@ -294,9 +411,9 @@ const AnimDeltaRight: ((X: 0; Y: -4), (X: 0; Y: -4), (X: -3; Y: -1), (X: -4; Y: -1), (X: 1; Y: -4), (X: 1; Y: -4), (X: 0; Y: -4)); AnimDeltaLeft: ((X: 0; Y: -4), (X: 0; Y: -4), (X: -3; Y: -1), (X: -4; Y: -1), (X: 1; Y: -4), (X: 1; Y: -4), (X: 0; Y: -4))), - (LeftAnim: True; wX: 70; wY: 73; AnimSpeed:(3, 3, 3, 3, 3, 0, 3); //CYBER - AnimDeltaRight: ((X: 2; Y: -6), (X: 2; Y: -6), (X: -3; Y: -4), (X: -3; Y: -4), (X: 25; Y: -6), (X: 25; Y: -6), (X: -2; Y: -6)); - AnimDeltaLeft: ((X: 3; Y: -3), (X: 3; Y: -3), (X: -3; Y: -4), (X: -3; Y: -4), (X:-26; Y: -3), (X:-26; Y: -3), (X: 1; Y: -3))), + (LeftAnim: True; wX: 70; wY: 73; AnimSpeed:(3, 3, 3, 3, 3, 4, 3); //CYBER + AnimDeltaRight: ((X: 2; Y: -6), (X: 2; Y: -6), (X: -3; Y: -4), (X: -3; Y: -4), (X: 25; Y: -6), (X: 0; Y: -6), (X: -2; Y: -6)); + AnimDeltaLeft: ((X: 3; Y: -3), (X: 3; Y: -3), (X: -3; Y: -4), (X: -3; Y: -4), (X:-26; Y: -3), (X:-1; Y: -3), (X: 1; Y: -3))), (LeftAnim: True; wX: 32; wY: 32; AnimSpeed:(3, 2, 2, 2, 1, 0, 4); //CGUN AnimDeltaRight: ((X: -1; Y: -2), (X: -1; Y: -2), (X: -2; Y: 0), (X: -2; Y: 0), (X: 0; Y: -3), (X: 0; Y: -3), (X: -1; Y: -2)); @@ -361,6 +478,38 @@ const MAX_ATM = 89; // Âðåìÿ îæèäàíèÿ ïîñëå ïîòåðè öåëè MAX_SOUL = 512; // Îãðàíè÷åíèå Lost_Soul'îâ + +var + gMonsters: array of TMonster; + uidMap: array [0..65535] of TMonster; // monster knows it's index + + +procedure clearUidMap (); +var + idx: Integer; +begin + for idx := 0 to High(uidMap) do uidMap[idx] := nil; +end; + + +function g_Mons_getNewTrapFrameId (): DWord; +var + f: Integer; +begin + Inc(monCheckTrapLastFrameId); + if monCheckTrapLastFrameId = 0 then + begin + // wraparound + monCheckTrapLastFrameId := 1; + for f := 0 to High(gMonsters) do + begin + if (gMonsters[f] <> nil) then gMonsters[f].trapCheckFrameId := 0; + end; + end; + result := monCheckTrapLastFrameId; +end; + + var pt_x: Integer = 0; pt_xs: Integer = 1; @@ -368,30 +517,34 @@ var pt_ys: Integer = 1; soulcount: Integer = 0; -function FindMonster(): DWORD; + +function allocMonster(): DWORD; var - i: Integer; + i, olen: Integer; begin - if gMonsters <> nil then - for i := 0 to High(gMonsters) do - if gMonsters[i] = nil then - begin - Result := i; - Exit; - end; - - if gMonsters = nil then - begin - SetLength(gMonsters, 32); - Result := 0; - end - else + for i := 0 to High(gMonsters) do + begin + if (gMonsters[i] = nil) then begin - Result := High(gMonsters) + 1; - SetLength(gMonsters, Length(gMonsters) + 32); + result := i; + exit; end; + end; + + olen := Length(gMonsters); + if (olen = 0) then + begin + SetLength(gMonsters, 64); + result := 0; + end + else + begin + result := olen; + SetLength(gMonsters, Length(gMonsters)+32); + end; end; + function IsFriend(a, b: Byte): Boolean; begin Result := True; @@ -430,7 +583,7 @@ begin UIDType := g_GetUIDType(SpawnerUID); if UIDType = UID_MONSTER then begin - m := g_Monsters_Get(SpawnerUID); + m := g_Monsters_ByUID(SpawnerUID); if m = nil then Exit; MonsterType := m.FMonsterType; end; @@ -461,23 +614,42 @@ begin end; function isCorpse(o: PObj; immediately: Boolean): Integer; + + function monsCollCheck (mon: TMonster; atag: Integer): Boolean; + begin + result := false; // don't stop + if (mon.FState = STATE_DEAD) and g_Obj_Collide(o, @mon.FObj) then + begin + case mon.FMonsterType of // Íå âîñêðåñèòü: + MONSTER_SOUL, MONSTER_PAIN, MONSTER_CYBER, MONSTER_SPIDER, + MONSTER_VILE, MONSTER_BARREL, MONSTER_ROBO: exit; + end; + // Îñòàëüíûõ ìîæíî âîñêðåñèòü + result := true; + end; + end; + var a: Integer; + mon: TMonster; begin - Result := -1; + result := -1; -// Åñëè íóæíà âåðîÿòíîñòü: - if not immediately then - if Random(8) <> 0 then - Exit; - - if gMonsters = nil then - Exit; + // Åñëè íóæíà âåðîÿòíîñòü + if not immediately and (Random(8) <> 0) then exit; -// Èùåì ìåðòâûõ ìîíñòðîâ ïîáëèçîñòè: - for a := 0 to High(gMonsters) do - if (gMonsters[a] <> nil) and (gMonsters[a].FState = STATE_DEAD) then - if g_Obj_Collide(o, @gMonsters[a].FObj) then + // Èùåì ìåðòâûõ ìîíñòðîâ ïîáëèçîñòè + if gmon_debug_use_sqaccel then + begin + mon := monsTree.aabbQuery(o.X+o.Rect.X, o.Y+o.Rect.Y, o.Rect.Width, o.Rect.Height, monsCollCheck); + if (mon <> nil) then result := mon.arrIdx; + end + else + begin + for a := 0 to High(gMonsters) do + begin + if (gMonsters[a] <> nil) and (gMonsters[a].FState = STATE_DEAD) and g_Obj_Collide(o, @gMonsters[a].FObj) then + begin case gMonsters[a].FMonsterType of // Íå âîñêðåñèòü: MONSTER_SOUL, MONSTER_PAIN, MONSTER_CYBER, MONSTER_SPIDER, MONSTER_VILE, MONSTER_BARREL, MONSTER_ROBO: Continue; @@ -487,6 +659,9 @@ begin Exit; end; end; + end; + end; + end; end; procedure g_Monsters_LoadData(); @@ -642,6 +817,8 @@ begin g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_CYBER_PAIN_L', GameWAD+':MTEXTURES\CYBER_PAIN_L', 128, 128, 1); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_CYBER_ATTACK', GameWAD+':MTEXTURES\CYBER_ATTACK', 128, 128, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_CYBER_ATTACK_L', GameWAD+':MTEXTURES\CYBER_ATTACK_L', 128, 128, 2); + g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_CYBER_ATTACK2', GameWAD+':MTEXTURES\CYBER_ATTACK2', 128, 128, 2); + g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_CYBER_ATTACK2_L', GameWAD+':MTEXTURES\CYBER_ATTACK2_L', 128, 128, 2); g_Frames_CreateWAD(nil, 'FRAMES_MONSTER_CYBER_DIE', GameWAD+':MTEXTURES\CYBER_DIE', 128, 128, 9); g_Game_SetLoadingText(_lc[I_LOAD_MONSTER_SOUNDS], 0, False); @@ -724,6 +901,10 @@ begin g_Sound_CreateWADEx('SOUND_MONSTER_SPIDER_WALK', GameWAD+':MSOUNDS\SPIDER_WALK'); g_Sound_CreateWADEx('SOUND_MONSTER_FISH_ATTACK', GameWAD+':MSOUNDS\FISH_ATTACK'); + + monsTree := TDynAABBTreeMons.Create(); + clearUidMap(); + monCheckTrapLastFrameId := 0; end; procedure g_Monsters_FreeData(); @@ -858,6 +1039,8 @@ begin g_Frames_DeleteByName('FRAMES_MONSTER_CYBER_PAIN_L'); g_Frames_DeleteByName('FRAMES_MONSTER_CYBER_ATTACK'); g_Frames_DeleteByName('FRAMES_MONSTER_CYBER_ATTACK_L'); + g_Frames_DeleteByName('FRAMES_MONSTER_CYBER_ATTACK2'); + g_Frames_DeleteByName('FRAMES_MONSTER_CYBER_ATTACK2_L'); g_Frames_DeleteByName('FRAMES_MONSTER_CYBER_DIE'); g_Sound_Delete('SOUND_MONSTER_BARREL_DIE'); @@ -938,6 +1121,8 @@ begin g_Sound_Delete('SOUND_MONSTER_SPIDER_WALK'); g_Sound_Delete('SOUND_MONSTER_FISH_ATTACK'); + + monsTree.Free(); end; procedure g_Monsters_Init(); @@ -949,48 +1134,53 @@ procedure g_Monsters_Free(); var a: Integer; begin - if gMonsters <> nil then - for a := 0 to High(gMonsters) do - gMonsters[a].Free(); - + for a := 0 to High(gMonsters) do gMonsters[a].Free(); + monsTree.reset(); gMonsters := nil; + clearUidMap(); + monCheckTrapLastFrameId := 0; end; function g_Monsters_Create(MonsterType: Byte; X, Y: Integer; - Direction: TDirection; AdjCoord: Boolean = False; ForcedUID: Integer = -1): Integer; + Direction: TDirection; AdjCoord: Boolean = False; ForcedUID: Integer = -1): TMonster; var find_id: DWORD; + mon: TMonster; begin - Result := -1; + result := nil; -// Íåò òàêîãî ìîíñòðà: - if (MonsterType > MONSTER_MAN) or (MonsterType = 0) then - Exit; + // Íåò òàêîãî ìîíñòðà + if (MonsterType > MONSTER_MAN) or (MonsterType = 0) then exit; -// Ñîáëþäàåì îãðàíè÷åíèå Lost_Soul'îâ: + // Ñîáëþäàåì îãðàíè÷åíèå Lost_Soul'îâ if MonsterType = MONSTER_SOUL then - if soulcount > MAX_SOUL then - Exit - else - soulcount := soulcount + 1; + begin + if soulcount > MAX_SOUL then exit; + soulcount := soulcount + 1; + end; + + find_id := allocMonster(); - find_id := FindMonster(); + mon := TMonster.Create(MonsterType, find_id, ForcedUID); + gMonsters[find_id] := mon; + mon.arrIdx := find_id; + mon.treeNode := -1; - gMonsters[find_id] := TMonster.Create(MonsterType, find_id, ForcedUID); + uidMap[mon.FUID] := mon; -// Íàñòðàèâàåì ïîëîæåíèå: - with gMonsters[find_id] do + // Íàñòðàèâàåì ïîëîæåíèå + with mon do begin if AdjCoord then - begin - FObj.X := X-FObj.Rect.X - (FObj.Rect.Width div 2); - FObj.Y := Y-FObj.Rect.Y - FObj.Rect.Height; - end + begin + FObj.X := X-FObj.Rect.X - (FObj.Rect.Width div 2); + FObj.Y := Y-FObj.Rect.Y - FObj.Rect.Height; + end else - begin - FObj.X := X-FObj.Rect.X; - FObj.Y := Y-FObj.Rect.Y; - end; + begin + FObj.X := X-FObj.Rect.X; + FObj.Y := Y-FObj.Rect.Y; + end; FDirection := Direction; FStartDirection := Direction; @@ -998,7 +1188,9 @@ begin FStartY := GameY; end; - Result := find_id; + mon.positionChanged(); + + result := mon; end; procedure g_Monsters_killedp(); @@ -1027,34 +1219,33 @@ procedure g_Monsters_Update(); var a: Integer; begin -// Öåëåóêàçàòåëü: + // Öåëåóêàçàòåëü if gTime mod (GAME_TICK*2) = 0 then begin pt_x := pt_x+pt_xs; pt_y := pt_y+pt_ys; - if Abs(pt_x) > 246 then - pt_xs := -pt_xs; - if Abs(pt_y) > 100 then - pt_ys := -pt_ys; + if abs(pt_x) > 246 then pt_xs := -pt_xs; + if abs(pt_y) > 100 then pt_ys := -pt_ys; end; gMon := True; // Äëÿ ðàáîòû BlockMon'à - if gMonsters <> nil then - for a := 0 to High(gMonsters) do - if (gMonsters[a] <> nil) then - if not gMonsters[a].FRemoved then - begin - if g_Game_IsClient then - gMonsters[a].ClientUpdate() - else - gMonsters[a].Update(); - end - else - begin - gMonsters[a].Free(); - gMonsters[a] := nil; - end; + for a := 0 to High(gMonsters) do + begin + if (gMonsters[a] = nil) then continue; + if not gMonsters[a].FRemoved then + begin + if g_Game_IsClient then + gMonsters[a].ClientUpdate() + else + gMonsters[a].Update(); + end + else + begin + gMonsters[a].Free(); + gMonsters[a] := nil; + end; + end; gMon := False; end; @@ -1086,12 +1277,12 @@ begin end; end; -function g_Monsters_Get(UID: Word): TMonster; -var - a: Integer; +function g_Monsters_ByUID (UID: Word): TMonster; +//var a: Integer; begin + result := uidMap[UID]; + { Result := nil; - if gMonsters <> nil then for a := 0 to High(gMonsters) do if (gMonsters[a] <> nil) and @@ -1100,6 +1291,7 @@ begin Result := gMonsters[a]; Break; end; + } end; procedure g_Monsters_SaveState(var Mem: TBinMemoryWriter); @@ -1144,39 +1336,35 @@ end; procedure g_Monsters_LoadState(var Mem: TBinMemoryReader); var - count, i, a: Integer; + count, a: Integer; b: Byte; + mon: TMonster; begin - if Mem = nil then - Exit; + if Mem = nil then exit; g_Monsters_Free(); -// Çàãðóæàåì èíôîðìàöèþ öåëåóêàçàòåëÿ: + // Çàãðóæàåì èíôîðìàöèþ öåëåóêàçàòåëÿ Mem.ReadInt(pt_x); Mem.ReadInt(pt_xs); Mem.ReadInt(pt_y); Mem.ReadInt(pt_ys); -// Êîëè÷åñòâî ìîíñòðîâ: + // Êîëè÷åñòâî ìîíñòðîâ Mem.ReadInt(count); - if count = 0 then - Exit; + if count = 0 then exit; -// Çàãðóæàåì ìîíñòðîâ: + // Çàãðóæàåì ìîíñòðîâ for a := 0 to count-1 do begin - // Òèï ìîíñòðà: + // Òèï ìîíñòðà Mem.ReadByte(b); - // Ñîçäàåì ìîíñòðà: - i := g_Monsters_Create(b, 0, 0, D_LEFT); - if i < 0 then - begin - raise EBinSizeError.Create('g_Monsters_LoadState: ID = -1 (Can''t create)'); - end; - // Çàãðóæàåì äàííûå ìîíñòðà: - gMonsters[i].LoadState(Mem); + // Ñîçäàåì ìîíñòðà + mon := g_Monsters_Create(b, 0, 0, D_LEFT); + if mon = nil then raise EBinSizeError.Create('g_Monsters_LoadState: ID = -1 (Can''t create)'); + // Çàãðóæàåì äàííûå ìîíñòðà + mon.LoadState(Mem); end; end; @@ -1248,6 +1436,7 @@ begin Exit; FPainSound := True; + FPainTicks := 20; case FMonsterType of MONSTER_IMP, MONSTER_ZOMBY, MONSTER_SERG, @@ -1255,7 +1444,7 @@ begin g_Sound_PlayExAt('SOUND_MONSTER_PAIN', FObj.X, FObj.Y); MONSTER_SOUL, MONSTER_BARON, MONSTER_CACO, MONSTER_KNIGHT, MONSTER_DEMON, MONSTER_SPIDER, - MONSTER_CYBER: + MONSTER_BSP, MONSTER_CYBER: g_Sound_PlayExAt('SOUND_MONSTER_PAIN2', FObj.X, FObj.Y); MONSTER_VILE: g_Sound_PlayExAt('SOUND_MONSTER_VILE_PAIN', FObj.X, FObj.Y); @@ -1420,6 +1609,8 @@ begin FState := STATE_SLEEP; FCurAnim := ANIM_SLEEP; + positionChanged(); // this updates spatial accelerators + if g_Game_IsNet and g_Game_IsServer then begin MH_SEND_MonsterPos(FUID); @@ -1456,6 +1647,13 @@ begin FNoRespawn := False; FShellTimer := -1; FBehaviour := BH_NORMAL; + FFireTime := 0; + FFirePainTime := 0; + FFireAttacker := 0; + + treeNode := -1; + arrIdx := -1; + trapCheckFrameId := 0; if FMonsterType in [MONSTER_ROBO, MONSTER_BARREL] then FBloodKind := BLOOD_SPARKS @@ -1544,7 +1742,7 @@ begin vilefire := nil; end; -function TMonster.Damage(Damage: Word; VelX, VelY: Integer; SpawnerUID: Word; t: Byte): Boolean; +function TMonster.Damage(aDamage: Word; VelX, VelY: Integer; SpawnerUID: Word; t: Byte): Boolean; var c, it: Integer; p: TPlayer; @@ -1574,15 +1772,15 @@ begin // Ðîáîòó óðîíà íåò: if FMonsterType = MONSTER_ROBO then - Damage := 0; + aDamage := 0; // Íàíîñèì óðîí: - if g_Game_IsServer then Dec(FHealth, Damage); + if g_Game_IsServer then Dec(FHealth, aDamage); // Óñèëèâàåì áîëü ìîíñòðà îò óðîíà: if FPain = 0 then FPain := 3; - FPain := FPain+Damage; + FPain := FPain+aDamage; // Åñëè áîëü ñóùåñòâåííàÿ, òî ìåíÿåì ñîñòîÿíèå íà áîëåâîå: if FState <> STATE_PAIN then @@ -1593,8 +1791,8 @@ begin // Åñëè ðàçðåøåíà êðîâü - ñîçäàåì áðûçãè êðîâè: if (gBloodCount > 0) then begin - c := Min(Damage, 200); - c := c*gBloodCount - (Damage div 4) + Random(c div 2); + c := Min(aDamage, 200); + c := c*gBloodCount - (aDamage div 4) + Random(c div 2); if (VelX = 0) and (VelY = 0) then MakeBloodSimple(c) @@ -1606,8 +1804,7 @@ begin end; // Òåïåðü öåëü - óäàðèâøèé, åñëè òîëüêî íå ñàì ñåáÿ: - if (SpawnerUID <> FUID) and - (BehaviourDamage(SpawnerUID, FBehaviour, FMonsterType)) then + if (SpawnerUID <> FUID) and (BehaviourDamage(SpawnerUID, FBehaviour, FMonsterType)) then begin FTargetUID := SpawnerUID; FTargetTime := 0; @@ -1654,8 +1851,9 @@ begin it := g_Items_Create(FObj.X + (FObj.Rect.Width div 2), FObj.Y + (FObj.Rect.Height div 2), c, True, False); - g_Obj_Push(@gItems[it].Obj, (FObj.Vel.X div 2)-3+Random(7), - (FObj.Vel.Y div 2)-Random(4)); + g_Obj_Push(g_Items_ObjByIdx(it), (FObj.Vel.X div 2)-3+Random(7), + (FObj.Vel.Y div 2)-Random(4)); + positionChanged(); // this updates spatial accelerators if g_Game_IsServer and g_Game_IsNet then MH_SEND_ItemSpawn(True, it); end; @@ -1712,8 +1910,7 @@ begin if FHealth < FMaxHealth then begin IncMax(FHealth, Value, FMaxHealth); - if g_Game_IsServer and g_Game_IsNet then - MH_SEND_MonsterState(FUID); + if g_Game_IsServer and g_Game_IsNet then MH_SEND_MonsterState(FUID); Result := True; end; end; @@ -1730,6 +1927,21 @@ begin vilefire.Free(); + if (treeNode <> -1) then + begin + {$IF DEFINED(D2F_DEBUG)} + e_WriteLog(Format('monster #%d(%u): removed from tree; nodeid=%d', [arrIdx, UID, treeNode]), MSG_NOTIFY); + {$ENDIF} + monsTree.removeObject(treeNode); + end; + + if (arrIdx <> -1) then + begin + gMonsters[arrIdx] := nil; + end; + + uidMap[FUID] := nil; + inherited Destroy(); end; @@ -1834,8 +2046,7 @@ procedure TMonster.Push(vx, vy: Integer); begin FObj.Accel.X := FObj.Accel.X + vx; FObj.Accel.Y := FObj.Accel.Y + vy; - if g_Game_IsServer and g_Game_IsNet then - MH_SEND_MonsterPos(FUID); + if g_Game_IsServer and g_Game_IsNet then MH_SEND_MonsterPos(FUID); end; procedure TMonster.SetState(State: Byte; ForceAnim: Byte = 255); @@ -1930,6 +2141,7 @@ begin FObj.X := X - FObj.Rect.X; FObj.Y := Y - FObj.Rect.Y; + positionChanged(); if dir = 1 then FDirection := D_LEFT @@ -1958,8 +2170,7 @@ begin NET_GFX_TELE); end; - if g_Game_IsServer and g_Game_IsNet then - MH_SEND_MonsterPos(FUID); + if g_Game_IsServer and g_Game_IsNet then MH_SEND_MonsterPos(FUID); Result := True; end; @@ -1969,6 +2180,7 @@ var st: Word; o, co: TObj; fall: Boolean; + mon: TMonster; label _end; begin @@ -1992,13 +2204,18 @@ begin if gTime mod (GAME_TICK*2) <> 0 then begin g_Obj_Move(@FObj, fall, True, True); + positionChanged(); // this updates spatial accelerators Exit; end; - FPainSound := False; + if FPainTicks > 0 then + Dec(FPainTicks) + 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 @@ -2021,6 +2238,25 @@ begin if (FState = STATE_DIE) or (FState = STATE_DEAD) then FObj.Vel.X := z_dec(FObj.Vel.X, 1); + if FFireTime > 0 then + begin + if WordBool(st and MOVE_INWATER) then + FFireTime := 0 + else + begin + OnFireFlame(1); + FFireTime := FFireTime - 1; + if (FState <> STATE_DIE) and (FState <> STATE_DEAD) then + if FFirePainTime = 0 then + begin + Damage(5, FFireAttacker, 0, 0, HIT_FLAME); + FFirePainTime := 18; + end + else + FFirePainTime := FFirePainTime - 1; + end; + end; + // Ìåðòâûé íè÷åãî íå äåëàåò: if (FState = STATE_DEAD) then goto _end; @@ -2118,6 +2354,8 @@ begin FPain := MONSTERTABLE[FMonsterType].Pain; if gSoundEffectsDF then PainSound(); end; + if (not gSoundEffectsDF) and (FPain >= MONSTERTABLE[FMonsterType].MinPain) then + PainSound(); // Ñíèæàåì áîëü ñî âðåìåíåì: FPain := FPain - 5; @@ -2242,8 +2480,11 @@ begin if g_Obj_Collide(@FObj, @o) and (FTargetUID <> 0) then begin FTargetTime := 0; - if kick(@o) then - goto _end; + if (FMonsterType <> MONSTER_CYBER) or (Random(2) = 0) then + begin + if kick(@o) then + goto _end; + end; end; // Ðàññòîÿíèå äî öåëè: @@ -2314,8 +2555,7 @@ begin if g_Obj_CollideLevel(@FObj, 0, 1) or g_Obj_StayOnStep(@FObj) then begin // "Ñòîèò" òâåðäî // Ðûáà òðåïûõàåòñÿ íà ïîâåðõíîñòè: - if FObj.Accel.Y = 0 then - FObj.Vel.Y := -6; + if FObj.Accel.Y = 0 then FObj.Vel.Y := -6; FObj.Accel.X := FObj.Accel.X - 8 + Random(17); end; @@ -2364,14 +2604,22 @@ begin b := Abs(FObj.Vel.X); if b > 1 then b := b * (Random(8 div b) + 1); for a := 0 to High(gGibs) do + begin if gGibs[a].Live and g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y+FObj.Rect.Height-4, FObj.Rect.Width, 8, @gGibs[a].Obj) and (Random(3) = 0) then + begin // Ïèíàåì êóñêè if FObj.Vel.X < 0 then - g_Obj_PushA(@gGibs[a].Obj, b, Random(61)+120) // íàëåâî + begin + g_Obj_PushA(@gGibs[a].Obj, b, Random(61)+120); // íàëåâî + end else + begin g_Obj_PushA(@gGibs[a].Obj, b, Random(61)); // íàïðàâî + end; + end; + end; end; // Áîññû ìîãóò ïèíàòü òðóïû: if (FMonsterType in [MONSTER_CYBER, MONSTER_SPIDER, MONSTER_ROBO]) and @@ -2608,32 +2856,32 @@ _end: // Pain_Elemental ïðè ñìåðòè âûïóñêàåò 3 Lost_Soul'à: if (FMonsterType = MONSTER_PAIN) then begin - sx := g_Monsters_Create(MONSTER_SOUL, FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)-30, - FObj.Y+FObj.Rect.Y+20, D_LEFT); - if sx <> -1 then + mon := g_Monsters_Create(MONSTER_SOUL, FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)-30, + FObj.Y+FObj.Rect.Y+20, D_LEFT); + if mon <> nil then begin - gMonsters[sx].SetState(STATE_GO); - gMonsters[sx].FNoRespawn := True; + mon.SetState(STATE_GO); + mon.FNoRespawn := True; Inc(gTotalMonsters); if g_Game_IsNet then MH_SEND_MonsterSpawn(gMonsters[sx].UID); end; - sx := g_Monsters_Create(MONSTER_SOUL, FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2), - FObj.Y+FObj.Rect.Y+20, D_RIGHT); - if sx <> -1 then + mon := g_Monsters_Create(MONSTER_SOUL, FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2), + FObj.Y+FObj.Rect.Y+20, D_RIGHT); + if mon <> nil then begin - gMonsters[sx].SetState(STATE_GO); - gMonsters[sx].FNoRespawn := True; + mon.SetState(STATE_GO); + mon.FNoRespawn := True; Inc(gTotalMonsters); if g_Game_IsNet then MH_SEND_MonsterSpawn(gMonsters[sx].UID); end; - sx := g_Monsters_Create(MONSTER_SOUL, FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)-15, - FObj.Y+FObj.Rect.Y, D_RIGHT); - if sx <> -1 then + mon := g_Monsters_Create(MONSTER_SOUL, FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)-15, + FObj.Y+FObj.Rect.Y, D_RIGHT); + if mon <> nil then begin - gMonsters[sx].SetState(STATE_GO); - gMonsters[sx].FNoRespawn := True; + mon.SetState(STATE_GO); + mon.FNoRespawn := True; Inc(gTotalMonsters); if g_Game_IsNet then MH_SEND_MonsterSpawn(gMonsters[sx].UID); end; @@ -2702,13 +2950,13 @@ _end: 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_SKEL, MONSTER_ROBO, MONSTER_CYBER: + // Ðîáîò, êèáåð èëè ñêåëåò ñèëüíî ïèíàþòñÿ: if FCurAnim = ANIM_ATTACK2 then begin o := FObj; - o.Vel.X := IfThen(FDirection = D_RIGHT, 1, -1)*50; - if g_Weapon_Hit(@o, 50, FUID, HIT_SOME) <> 0 then + o.Vel.X := IfThen(FDirection = D_RIGHT, 1, -1)*IfThen(FMonsterType = MONSTER_CYBER, 60, 50); + if g_Weapon_Hit(@o, IfThen(FMonsterType = MONSTER_CYBER, 33, 50), FUID, HIT_SOME) <> 0 then g_Sound_PlayExAt('SOUND_MONSTER_SKEL_HIT', FObj.X, FObj.Y); end; @@ -2797,17 +3045,17 @@ _end: g_Weapon_ball2(wx, wy, tx, ty, FUID); MONSTER_PAIN: begin // Ñîçäàåì Lost_Soul: - sx := g_Monsters_Create(MONSTER_SOUL, FObj.X+(FObj.Rect.Width div 2), - FObj.Y+FObj.Rect.Y, FDirection); + mon := g_Monsters_Create(MONSTER_SOUL, FObj.X+(FObj.Rect.Width div 2), + FObj.Y+FObj.Rect.Y, FDirection); - if sx <> -1 then + if mon <> nil then begin // Öåëü - öåëü Pain_Elemental'à. Ëåòèì ê íåé: - gMonsters[sx].FTargetUID := FTargetUID; + mon.FTargetUID := FTargetUID; GetPos(FTargetUID, @o); - gMonsters[sx].FTargetTime := 0; - gMonsters[sx].FNoRespawn := True; - gMonsters[sx].SetState(STATE_GO); - gMonsters[sx].shoot(@o, True); + mon.FTargetTime := 0; + mon.FNoRespawn := True; + mon.SetState(STATE_GO); + mon.shoot(@o, True); Inc(gTotalMonsters); if g_Game_IsNet then MH_SEND_MonsterSpawn(gMonsters[sx].UID); @@ -2922,13 +3170,18 @@ begin if gTime mod (GAME_TICK*2) <> 0 then begin g_Obj_Move(@FObj, fall, True, True); + positionChanged(); // this updates spatial accelerators Exit; end; - FPainSound := False; + if FPainTicks > 0 then + Dec(FPainTicks) + 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 @@ -2944,6 +3197,17 @@ begin if (FState = STATE_DIE) or (FState = STATE_DEAD) then FObj.Vel.X := z_dec(FObj.Vel.X, 1); + if FFireTime > 0 then + begin + if WordBool(st and MOVE_INWATER) then + FFireTime := 0 + else + begin + OnFireFlame(1); + FFireTime := FFireTime - 1; + end; + end; + // Ìåðòâûé íè÷åãî íå äåëàåò: if (FState = STATE_DEAD) then goto _end; @@ -3030,8 +3294,10 @@ begin if FPain >= MONSTERTABLE[FMonsterType].Pain then begin FPain := MONSTERTABLE[FMonsterType].Pain; - PainSound(); + if gSoundEffectsDF then PainSound(); end; + if (not gSoundEffectsDF) and (FPain >= MONSTERTABLE[FMonsterType].MinPain) then + PainSound(); // Ñíèæàåì áîëü ñî âðåìåíåì: FPain := FPain - 5; @@ -3169,14 +3435,23 @@ begin b := Abs(FObj.Vel.X); if b > 1 then b := b * (Random(8 div b) + 1); for a := 0 to High(gGibs) do + begin if gGibs[a].Live and g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y+FObj.Rect.Height-4, FObj.Rect.Width, 8, @gGibs[a].Obj) and (Random(3) = 0) then + begin // Ïèíàåì êóñêè if FObj.Vel.X < 0 then - g_Obj_PushA(@gGibs[a].Obj, b, Random(61)+120) // íàëåâî + begin + g_Obj_PushA(@gGibs[a].Obj, b, Random(61)+120); // íàëåâî + end else + begin g_Obj_PushA(@gGibs[a].Obj, b, Random(61)); // íàïðàâî + end; + positionChanged(); // this updates spatial accelerators + end; + end; end; // Áîññû ìîãóò ïèíàòü òðóïû: if (FMonsterType in [MONSTER_CYBER, MONSTER_SPIDER, MONSTER_ROBO]) and @@ -3455,13 +3730,13 @@ _end: MONSTER_FISH: g_Weapon_Hit(@FObj, 10, FUID, HIT_SOME); - MONSTER_SKEL, MONSTER_ROBO: - // Ðîáîò èëè ñêåëåò ñèëüíî ïèíàþòñÿ: + MONSTER_SKEL, MONSTER_ROBO, MONSTER_CYBER: + // Ðîáîò, êèáåð èëè ñêåëåò ñèëüíî ïèíàþòñÿ: if FCurAnim = ANIM_ATTACK2 then begin o := FObj; - o.Vel.X := IfThen(FDirection = D_RIGHT, 1, -1)*50; - g_Weapon_Hit(@o, 50, FUID, HIT_SOME); + o.Vel.X := IfThen(FDirection = D_RIGHT, 1, -1)*IfThen(FMonsterType = MONSTER_CYBER, 60, 50); + g_Weapon_Hit(@o, IfThen(FMonsterType = MONSTER_CYBER, 33, 50), FUID, HIT_SOME); end; MONSTER_VILE: @@ -3534,7 +3809,7 @@ _end: FAnim[FCurAnim, FDirection].Update(); end; -procedure TMonster.ClientAttack(wx, wy, tx, ty: Integer); +procedure TMonster.ClientAttack(wx, wy, atx, aty: Integer); begin case FMonsterType of MONSTER_ZOMBY: @@ -3560,21 +3835,21 @@ begin g_Player_CreateShell(wx, wy, 0, -2, SHELL_BULLET); end; MONSTER_IMP: - g_Weapon_ball1(wx, wy, tx, ty, FUID); + g_Weapon_ball1(wx, wy, atx, aty, FUID); MONSTER_CYBER: - g_Weapon_rocket(wx, wy, tx, ty, FUID); + g_Weapon_rocket(wx, wy, atx, aty, FUID); MONSTER_SKEL: - g_Weapon_revf(wx, wy, tx, ty, FUID, FTargetUID); + g_Weapon_revf(wx, wy, atx, aty, FUID, FTargetUID); MONSTER_BSP: - g_Weapon_aplasma(wx, wy, tx, ty, FUID); + g_Weapon_aplasma(wx, wy, atx, aty, FUID); MONSTER_ROBO: - g_Weapon_plasma(wx, wy, tx, ty, FUID); + g_Weapon_plasma(wx, wy, atx, aty, FUID); MONSTER_MANCUB: - g_Weapon_manfire(wx, wy, tx, ty, FUID); + g_Weapon_manfire(wx, wy, atx, aty, FUID); MONSTER_BARON, MONSTER_KNIGHT: - g_Weapon_ball7(wx, wy, tx, ty, FUID); + g_Weapon_ball7(wx, wy, atx, aty, FUID); MONSTER_CACO: - g_Weapon_ball2(wx, wy, tx, ty, FUID); + g_Weapon_ball2(wx, wy, atx, aty, FUID); end; end; @@ -3752,13 +4027,7 @@ begin g_Sound_PlayExAt('SOUND_MONSTER_IMP_ATTACK', FObj.X, FObj.Y); Result := True; end; - MONSTER_SKEL: - begin - SetState(STATE_ATTACK, ANIM_ATTACK2); - g_Sound_PlayExAt('SOUND_MONSTER_SKEL_ATTACK', FObj.X, FObj.Y); - Result := True; - end; - MONSTER_ROBO: + MONSTER_SKEL, MONSTER_ROBO, MONSTER_CYBER: begin SetState(STATE_ATTACK, ANIM_ATTACK2); g_Sound_PlayExAt('SOUND_MONSTER_SKEL_ATTACK', FObj.X, FObj.Y); @@ -4115,4 +4384,178 @@ begin SetLength(FDieTriggers, 0); end; +procedure TMonster.CatchFire(Attacker: Word); +begin + FFireTime := 100; + FFireAttacker := Attacker; + if g_Game_IsNet and g_Game_IsServer then MH_SEND_MonsterState(FUID); +end; + +procedure TMonster.OnFireFlame(Times: DWORD = 1); +var + id, i: DWORD; + Anim: TAnimation; +begin + if (Random(10) = 1) and (Times = 1) then + Exit; + + if g_Frames_Get(id, 'FRAMES_FLAME') then + begin + for i := 1 to Times do + begin + Anim := TAnimation.Create(id, False, 3); + Anim.Alpha := 0; + g_GFX_OnceAnim(Obj.X+Obj.Rect.X+Random(Obj.Rect.Width+Times*2)-(Anim.Width div 2), + Obj.Y+8+Random(8+Times*2)+IfThen(FState = STATE_DEAD, 16, 0), Anim, ONCEANIM_SMOKE); + Anim.Free(); + end; + end; +end; + + +// ////////////////////////////////////////////////////////////////////////// // +function g_Mons_ForEach (cb: TEachMonsterCB): Boolean; +var + idx: Integer; +begin + result := false; + if (gMonsters = nil) or not assigned(cb) then exit; + for idx := 0 to High(gMonsters) do + begin + if (gMonsters[idx] <> nil) then + begin + result := cb(idx, gMonsters[idx]); + if result then exit; + end; + end; +end; + + +// throws on invalid uid +function g_Mons_ByIdx (uid: Integer): TMonster; inline; +begin + result := g_Mons_ByIdx_NC(uid); + if (result = nil) then raise Exception.Create('g_Mons_ByIdx: invalid monster id'); +end; + + +// can return null +function g_Mons_ByIdx_NC (uid: Integer): TMonster; inline; +begin + if (uid < 0) or (uid > High(gMonsters)) then begin result := nil; exit; end; + result := gMonsters[uid]; +end; + + +function g_Mons_IsAnyAliveAt (x, y: Integer; width, height: Integer): Boolean; + + function monsCollCheck (mon: TMonster; atag: Integer): Boolean; + begin + result := (mon.Live and g_Obj_Collide(x, y, width, height, @mon.Obj)); + end; + +var + idx: Integer; +begin + result := false; + if (width < 1) or (height < 1) then exit; + + if gmon_debug_use_sqaccel then + begin + result := (monsTree.aabbQuery(x, y, width, height, monsCollCheck) <> nil); + end + else + begin + for idx := 0 to High(gMonsters) do + begin + if (gMonsters[idx] <> nil) and gMonsters[idx].Live then + begin + if g_Obj_Collide(x, y, width, height, @gMonsters[idx].Obj) then + begin + result := true; + exit; + end; + end; + end; + end; +end; + + +function g_Mons_ForEachAt (x, y: Integer; width, height: Integer; cb: TEachMonsterCB): Boolean; + + function monsCollCheck (mon: TMonster; atag: Integer): Boolean; + begin + result := false; + if g_Obj_Collide(x, y, width, height, @mon.Obj) then result := cb(mon.arrIdx, mon); + end; + +var + idx: Integer; +begin + result := false; + if (width < 1) or (height < 1) then exit; + + if gmon_debug_use_sqaccel then + begin + result := (monsTree.aabbQuery(x, y, width, height, monsCollCheck) <> nil); + end + else + begin + for idx := 0 to High(gMonsters) do + begin + if (gMonsters[idx] <> nil) and gMonsters[idx].Live then + begin + if g_Obj_Collide(x, y, width, height, @gMonsters[idx].Obj) then + begin + result := cb(idx, gMonsters[idx]); + if result then exit; + end; + end; + end; + end; +end; + + +function g_Mons_ForEachAtAlive (x, y: Integer; width, height: Integer; cb: TEachMonsterCB): Boolean; + + function monsCollCheck (mon: TMonster; atag: Integer): Boolean; + begin + result := false; + if mon.Live and g_Obj_Collide(x, y, width, height, @mon.Obj) then result := cb(mon.arrIdx, mon); + end; + +var + idx: Integer; +begin + result := false; + if (width < 1) or (height < 1) then exit; + + if gmon_debug_use_sqaccel then + begin + if (width = 1) and (height = 1) then + begin + result := (monsTree.pointQuery(x, y, monsCollCheck) <> nil); + end + else + begin + result := (monsTree.aabbQuery(x, y, width, height, monsCollCheck) <> nil); + end; + end + else + begin + for idx := 0 to High(gMonsters) do + begin + if (gMonsters[idx] <> nil) and gMonsters[idx].Live then + begin + if g_Obj_Collide(x, y, width, height, @gMonsters[idx].Obj) then + begin + result := cb(idx, gMonsters[idx]); + if result then exit; + end; + end; + end; + end; +end; + + end.