DEADSOFTWARE

uidmap for monsters, so we don't have to do linear searches
[d2df-sdl.git] / src / game / g_triggers.pas
index 8e8cf44e9a8bbb282b541828fc6ee83517e5bad4..29ea42ee3ddf3b1702d8a0569a4a490600ee6672 100644 (file)
@@ -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 <http://www.gnu.org/licenses/>.
+ *)
+{$INCLUDE ../shared/a_modes.inc}
 unit g_triggers;
 
 interface
@@ -49,17 +65,30 @@ type
 
 function g_Triggers_Create(Trigger: TTrigger): DWORD;
 procedure g_Triggers_Update();
-procedure g_Triggers_Press(ID: DWORD; ActivateType: Byte);
+procedure g_Triggers_Press(ID: DWORD; ActivateType: Byte; ActivateUID: Word = 0);
 function g_Triggers_PressR(X, Y: Integer; Width, Height: Word; UID: Word;
                            ActivateType: Byte; IgnoreList: DWArray = nil): DWArray;
 procedure g_Triggers_PressL(X1, Y1, X2, Y2: Integer; UID: DWORD; ActivateType: Byte);
-procedure g_Triggers_PressC(CX, CY: Integer; Radius: Word; UID: Word; ActivateType: Byte);
+procedure g_Triggers_PressC(CX, CY: Integer; Radius: Word; UID: Word; ActivateType: Byte; IgnoreTrigger: Integer = -1);
 procedure g_Triggers_OpenAll();
 procedure g_Triggers_DecreaseSpawner(ID: DWORD);
 procedure g_Triggers_Free();
 procedure g_Triggers_SaveState(var Mem: TBinMemoryWriter);
 procedure g_Triggers_LoadState(var Mem: TBinMemoryReader);
 
+function tr_Message(MKind: Integer; MText: string; MSendTo: Integer; MTime: Integer; ActivateUID: Integer): Boolean;
+
+function tr_CloseDoor(PanelID: Integer; NoSound: Boolean; d2d: Boolean): Boolean;
+function tr_OpenDoor(PanelID: Integer; NoSound: Boolean; d2d: Boolean): Boolean;
+procedure tr_CloseTrap(PanelID: Integer; NoSound: Boolean; d2d: Boolean);
+function tr_SetLift(PanelID: Integer; d: Integer; NoSound: Boolean; d2d: Boolean): Boolean;
+
+function tr_Teleport(ActivateUID: Integer; TX, TY: Integer; TDir: Integer; Silent: Boolean; D2D: Boolean): Boolean;
+function tr_Push(ActivateUID: Integer; VX, VY: Integer; ResetVel: Boolean): Boolean;
+
+procedure tr_MakeEffect(X, Y, VX, VY: Integer; T, ST, CR, CG, CB: Byte; Silent, Send: Boolean);
+function tr_SpawnShot(ShotType: Integer; wx, wy, dx, dy: Integer; ShotSound: Boolean; ShotTarget: Word): Integer;
+
 var
   gTriggerClientID: Integer = 0;
   gTriggers: array of TTrigger;
@@ -102,7 +131,7 @@ begin
   end;
 end;
 
-function CloseDoor(PanelID: Integer; NoSound: Boolean; d2d: Boolean): Boolean;
+function tr_CloseDoor(PanelID: Integer; NoSound: Boolean; d2d: Boolean): Boolean;
 var
   a, b, c: Integer;
 begin
@@ -115,7 +144,7 @@ begin
     with gWalls[PanelID] do
     begin
       if g_CollidePlayer(X, Y, Width, Height) or
-         g_CollideMonster(X, Y, Width, Height) then Exit;
+         g_Mons_IsAnyAliveAt(X, Y, Width, Height) then Exit;
 
       if not Enabled then
       begin
@@ -152,7 +181,7 @@ begin
       with gWalls[gDoorMap[c, b]] do
       begin
         if g_CollidePlayer(X, Y, Width, Height) or
-          g_CollideMonster(X, Y, Width, Height) then Exit;
+          g_Mons_IsAnyAliveAt(X, Y, Width, Height) then Exit;
       end;
 
     if not NoSound then
@@ -177,22 +206,39 @@ begin
   end;
 end;
 
-procedure CloseTrap(PanelID: Integer; NoSound: Boolean; d2d: Boolean);
+procedure tr_CloseTrap(PanelID: Integer; NoSound: Boolean; d2d: Boolean);
 var
   a, b, c: Integer;
+  wx, wy, wh, ww: Integer;
+
+  function monsDamage (monidx: Integer; mon: TMonster): Boolean;
+  begin
+    result := false; // don't stop
+    if (mon <> nil) and mon.Live and g_Obj_Collide(wx, wy, ww, wh, @mon.Obj) then
+    begin
+      mon.Damage(TRAP_DAMAGE, 0, 0, 0, HIT_TRAP);
+    end;
+  end;
+
 begin
   if PanelID = -1 then Exit;
 
   if not d2d then
   begin
     with gWalls[PanelID] do
+    begin
       if (not NoSound) and (not Enabled) then
       begin
         g_Sound_PlayExAt('SOUND_GAME_SWITCH1', X, Y);
         if g_Game_IsServer and g_Game_IsNet then
           MH_SEND_Sound(X, Y, 'SOUND_GAME_SWITCH1');
       end;
+    end;
 
+    wx := gWalls[PanelID].X;
+    wy := gWalls[PanelID].Y;
+    ww := gWalls[PanelID].Width;
+    wh := gWalls[PanelID].Height;
 
     with gWalls[PanelID] do
     begin
@@ -202,11 +248,7 @@ begin
               gPlayers[a].Collide(X, Y, Width, Height) then
             gPlayers[a].Damage(TRAP_DAMAGE, 0, 0, 0, HIT_TRAP);
 
-      if gMonsters <> nil then
-        for a := 0 to High(gMonsters) do
-          if (gMonsters[a] <> nil) and gMonsters[a].Live and
-          g_Obj_Collide(X, Y, Width, Height, @gMonsters[a].Obj) then
-            gMonsters[a].Damage(TRAP_DAMAGE, 0, 0, 0, HIT_TRAP);
+      g_Mons_ForEach(monsDamage);
 
       if not Enabled then g_Map_EnableWall(PanelID);
     end;
@@ -219,30 +261,41 @@ begin
     for a := 0 to High(gDoorMap) do
     begin
       for b := 0 to High(gDoorMap[a]) do
+      begin
         if gDoorMap[a, b] = DWORD(PanelID) then
         begin
           c := a;
           Break;
         end;
+      end;
 
       if c <> -1 then Break;
     end;
     if c = -1 then Exit;
 
     if not NoSound then
+    begin
       for b := 0 to High(gDoorMap[c]) do
+      begin
         if not gWalls[gDoorMap[c, b]].Enabled then
         begin
           with gWalls[PanelID] do
           begin
             g_Sound_PlayExAt('SOUND_GAME_SWITCH1', X, Y);
-            if g_Game_IsServer and g_Game_IsNet then
-              MH_SEND_Sound(X, Y, 'SOUND_GAME_SWITCH1');
+            if g_Game_IsServer and g_Game_IsNet then MH_SEND_Sound(X, Y, 'SOUND_GAME_SWITCH1');
           end;
           Break;
         end;
+      end;
+    end;
 
     for b := 0 to High(gDoorMap[c]) do
+    begin
+      wx := gWalls[gDoorMap[c, b]].X;
+      wy := gWalls[gDoorMap[c, b]].Y;
+      ww := gWalls[gDoorMap[c, b]].Width;
+      wh := gWalls[gDoorMap[c, b]].Height;
+
       with gWalls[gDoorMap[c, b]] do
       begin
         if gPlayers <> nil then
@@ -251,18 +304,22 @@ begin
             gPlayers[a].Collide(X, Y, Width, Height) then
               gPlayers[a].Damage(TRAP_DAMAGE, 0, 0, 0, HIT_TRAP);
 
+        g_Mons_ForEach(monsDamage);
+        (*
         if gMonsters <> nil then
           for a := 0 to High(gMonsters) do
             if (gMonsters[a] <> nil) and gMonsters[a].Live and
             g_Obj_Collide(X, Y, Width, Height, @gMonsters[a].Obj) then
               gMonsters[a].Damage(TRAP_DAMAGE, 0, 0, 0, HIT_TRAP);
+        *)
 
         if not Enabled then g_Map_EnableWall(gDoorMap[c, b]);
       end;
+    end;
   end;
 end;
 
-function OpenDoor(PanelID: Integer; NoSound: Boolean; d2d: Boolean): Boolean;
+function tr_OpenDoor(PanelID: Integer; NoSound: Boolean; d2d: Boolean): Boolean;
 var
   a, b, c: Integer;
 begin
@@ -325,7 +382,7 @@ begin
   end;
 end;
 
-function SetLift(PanelID: Integer; d: Integer; NoSound: Boolean; d2d: Boolean): Boolean;
+function tr_SetLift(PanelID: Integer; d: Integer; NoSound: Boolean; d2d: Boolean): Boolean;
 var
   a, b, c, t: Integer;
 begin
@@ -399,195 +456,207 @@ begin
   end;
 end;
 
-procedure MakeShot(var Trigger: TTrigger; wx, wy, dx, dy: Integer; TargetUID: Word);
+function tr_SpawnShot(ShotType: Integer; wx, wy, dx, dy: Integer; ShotSound: Boolean; ShotTarget: Word): Integer;
 var
-  Projectile: Boolean;
   snd: string;
+  Projectile: Boolean;
   TextureID: DWORD;
   Anim: TAnimation;
 begin
-  with Trigger do
-    if (Data.ShotAmmo = 0) or
-       ((Data.ShotAmmo > 0) and (ShotAmmoCount > 0)) then
-    begin
-      if (Data.ShotPanelID <> -1) and (ShotPanelTime = 0) then
+  Result := -1;
+  TextureID := DWORD(-1);
+  snd := 'SOUND_WEAPON_FIREROCKET';
+  Projectile := True;
+  case ShotType of
+    TRIGGER_SHOT_PISTOL:
       begin
-        g_Map_SwitchTexture(ShotPanelType, Data.ShotPanelID);
-        ShotPanelTime := 4; // òèêîâ íà âñïûøêó âûñòðåëà
+        g_Weapon_pistol(wx, wy, dx, dy, 0, True);
+        snd := 'SOUND_WEAPON_FIREPISTOL';
+        Projectile := False;
+        if ShotSound then
+        begin
+          g_Player_CreateShell(wx, wy, 0, -2, SHELL_BULLET);
+          if g_Game_IsNet then
+            MH_SEND_Effect(wx, wy, 0, NET_GFX_SHELL1);
+        end;
       end;
 
-      if Data.ShotIntSight > 0 then
-        ShotSightTimeout := 180; // ~= 5 ñåêóíä
+    TRIGGER_SHOT_BULLET:
+      begin
+        g_Weapon_mgun(wx, wy, dx, dy, 0, True);
+        if gSoundEffectsDF then snd := 'SOUND_WEAPON_FIRECGUN'
+        else snd := 'SOUND_WEAPON_FIREPISTOL';
+        Projectile := False;
+        if ShotSound then
+        begin
+          g_Player_CreateShell(wx, wy, 0, -2, SHELL_BULLET);
+          if g_Game_IsNet then
+            MH_SEND_Effect(wx, wy, 0, NET_GFX_SHELL1);
+        end;
+      end;
 
-      if ShotAmmoCount > 0 then Dec(ShotAmmoCount);
-      Projectile := True;
-      snd := 'SOUND_WEAPON_FIREROCKET';
+    TRIGGER_SHOT_SHOTGUN:
+      begin
+        g_Weapon_Shotgun(wx, wy, dx, dy, 0, True);
+        snd := 'SOUND_WEAPON_FIRESHOTGUN';
+        Projectile := False;
+        if ShotSound then
+        begin
+          g_Player_CreateShell(wx, wy, 0, -2, SHELL_SHELL);
+          if g_Game_IsNet then
+            MH_SEND_Effect(wx, wy, 0, NET_GFX_SHELL2);
+        end;
+      end;
 
-      dx := dx + Random(Data.ShotAccuracy) - Random(Data.ShotAccuracy);
-      dy := dy + Random(Data.ShotAccuracy) - Random(Data.ShotAccuracy);
+    TRIGGER_SHOT_SSG:
+      begin
+        g_Weapon_DShotgun(wx, wy, dx, dy, 0, True);
+        snd := 'SOUND_WEAPON_FIRESHOTGUN2';
+        Projectile := False;
+        if ShotSound then
+        begin
+          g_Player_CreateShell(wx, wy, 0, -2, SHELL_SHELL);
+          g_Player_CreateShell(wx, wy, 0, -2, SHELL_SHELL);
+          if g_Game_IsNet then
+            MH_SEND_Effect(wx, wy, 0, NET_GFX_SHELL3);
+        end;
+      end;
 
-      case Data.ShotType of
-        TRIGGER_SHOT_PISTOL:
-          begin
-            g_Weapon_pistol(wx, wy, dx, dy, 0, True);
-            Projectile := False;
-            snd := 'SOUND_WEAPON_FIREPISTOL';
-            if Data.ShotSound then
-            begin
-              g_Player_CreateShell(wx, wy, 0, -2, SHELL_BULLET);
-              if g_Game_IsNet then
-                MH_SEND_Effect(wx, wy, 0, NET_GFX_SHELL1);
-            end;
-          end;
+    TRIGGER_SHOT_IMP:
+      begin
+        g_Weapon_ball1(wx, wy, dx, dy, 0, -1, True);
+        snd := 'SOUND_WEAPON_FIREBALL';
+      end;
 
-        TRIGGER_SHOT_BULLET:
-          begin
-            g_Weapon_mgun(wx, wy, dx, dy, 0, True);
-            Projectile := False;
-            if gSoundEffectsDF then snd := 'SOUND_WEAPON_FIRECGUN'
-            else snd := 'SOUND_WEAPON_FIREPISTOL';
-            if Data.ShotSound then
-            begin
-              g_Player_CreateShell(wx, wy, 0, -2, SHELL_BULLET);
-              if g_Game_IsNet then
-                MH_SEND_Effect(wx, wy, 0, NET_GFX_SHELL1);
-            end;
-          end;
+    TRIGGER_SHOT_PLASMA:
+      begin
+        g_Weapon_Plasma(wx, wy, dx, dy, 0, -1, True);
+        snd := 'SOUND_WEAPON_FIREPLASMA';
+      end;
 
-        TRIGGER_SHOT_SHOTGUN:
-          begin
-            g_Weapon_Shotgun(wx, wy, dx, dy, 0, True);
-            Projectile := False;
-            snd := 'SOUND_WEAPON_FIRESHOTGUN';
-            if Data.ShotSound then
-            begin
-              g_Player_CreateShell(wx, wy, 0, -2, SHELL_SHELL);
-              if g_Game_IsNet then
-                MH_SEND_Effect(wx, wy, 0, NET_GFX_SHELL2);
-            end;
-          end;
+    TRIGGER_SHOT_SPIDER:
+      begin
+        g_Weapon_aplasma(wx, wy, dx, dy, 0, -1, True);
+        snd := 'SOUND_WEAPON_FIREPLASMA';
+      end;
 
-        TRIGGER_SHOT_SSG:
-          begin
-            g_Weapon_DShotgun(wx, wy, dx, dy, 0, True);
-            Projectile := False;
-            snd := 'SOUND_WEAPON_FIRESHOTGUN2';
-            if Data.ShotSound then
-            begin
-              g_Player_CreateShell(wx, wy, 0, -2, SHELL_SHELL);
-              g_Player_CreateShell(wx, wy, 0, -2, SHELL_SHELL);
-              if g_Game_IsNet then
-                MH_SEND_Effect(wx, wy, 0, NET_GFX_SHELL3);
-            end;
-          end;
+    TRIGGER_SHOT_CACO:
+      begin
+        g_Weapon_ball2(wx, wy, dx, dy, 0, -1, True);
+        snd := 'SOUND_WEAPON_FIREBALL';
+      end;
 
-        TRIGGER_SHOT_IMP:
-          begin
-            g_Weapon_ball1(wx, wy, dx, dy, 0, -1, True);
-            snd := 'SOUND_WEAPON_FIREBALL';
-          end;
+    TRIGGER_SHOT_BARON:
+      begin
+        g_Weapon_ball7(wx, wy, dx, dy, 0, -1, True);
+        snd := 'SOUND_WEAPON_FIREBALL';
+      end;
 
-        TRIGGER_SHOT_PLASMA:
-          begin
-            g_Weapon_Plasma(wx, wy, dx, dy, 0, -1, True);
-            snd := 'SOUND_WEAPON_FIREPLASMA';
-          end;
+    TRIGGER_SHOT_MANCUB:
+      begin
+        g_Weapon_manfire(wx, wy, dx, dy, 0, -1, True);
+        snd := 'SOUND_WEAPON_FIREBALL';
+      end;
 
-        TRIGGER_SHOT_SPIDER:
-          begin
-            g_Weapon_aplasma(wx, wy, dx, dy, 0, -1, True);
-            snd := 'SOUND_WEAPON_FIREPLASMA';
-          end;
+    TRIGGER_SHOT_REV:
+      begin
+        g_Weapon_revf(wx, wy, dx, dy, 0, ShotTarget, -1, True);
+        snd := 'SOUND_WEAPON_FIREREV';
+      end;
 
-        TRIGGER_SHOT_CACO:
-          begin
-            g_Weapon_ball2(wx, wy, dx, dy, 0, -1, True);
-            snd := 'SOUND_WEAPON_FIREBALL';
-          end;
+    TRIGGER_SHOT_ROCKET:
+      begin
+        g_Weapon_Rocket(wx, wy, dx, dy, 0, -1, True);
+        snd := 'SOUND_WEAPON_FIREROCKET';
+      end;
 
-        TRIGGER_SHOT_BARON:
-          begin
-            g_Weapon_ball7(wx, wy, dx, dy, 0, -1, True);
-            snd := 'SOUND_WEAPON_FIREBALL';
-          end;
+    TRIGGER_SHOT_BFG:
+      begin
+        g_Weapon_BFGShot(wx, wy, dx, dy, 0, -1, True);
+        snd := 'SOUND_WEAPON_FIREBFG';
+      end;
 
-        TRIGGER_SHOT_MANCUB:
-          begin
-            g_Weapon_manfire(wx, wy, dx, dy, 0, -1, True);
-            snd := 'SOUND_WEAPON_FIREBALL';
-          end;
+    TRIGGER_SHOT_EXPL:
+      begin
+        if g_Frames_Get(TextureID, 'FRAMES_EXPLODE_ROCKET') then
+        begin
+          Anim := TAnimation.Create(TextureID, False, 6);
+          Anim.Blending := False;
+          g_GFX_OnceAnim(wx-64, wy-64, Anim);
+          Anim.Free();
+        end;
+        Projectile := False;
+        g_Weapon_Explode(wx, wy, 60, 0);
+        snd := 'SOUND_WEAPON_EXPLODEROCKET';
+      end;
 
-        TRIGGER_SHOT_REV:
-          begin
-            g_Weapon_revf(wx, wy, dx, dy, 0, TargetUID, -1, True);
-            snd := 'SOUND_WEAPON_FIREREV';
-          end;
+    TRIGGER_SHOT_BFGEXPL:
+      begin
+        if g_Frames_Get(TextureID, 'FRAMES_EXPLODE_BFG') then
+        begin
+          Anim := TAnimation.Create(TextureID, False, 6);
+          Anim.Blending := False;
+          g_GFX_OnceAnim(wx-64, wy-64, Anim);
+          Anim.Free();
+        end;
+        Projectile := False;
+        g_Weapon_BFG9000(wx, wy, 0);
+        snd := 'SOUND_WEAPON_EXPLODEBFG';
+      end;
 
-        TRIGGER_SHOT_ROCKET:
-          begin
-            g_Weapon_Rocket(wx, wy, dx, dy, 0, -1, True);
-            snd := 'SOUND_WEAPON_FIREROCKET';
-          end;
+    else exit;
+  end;
 
-        TRIGGER_SHOT_BFG:
-          begin
-            g_Weapon_BFGShot(wx, wy, dx, dy, 0, -1, True);
-            snd := 'SOUND_WEAPON_FIREBFG';
-          end;
+  if g_Game_IsNet and g_Game_IsServer then
+    case ShotType of
+      TRIGGER_SHOT_EXPL:
+        MH_SEND_Effect(wx, wy, Byte(ShotSound), NET_GFX_EXPLODE);
+      TRIGGER_SHOT_BFGEXPL:
+        MH_SEND_Effect(wx, wy, Byte(ShotSound), NET_GFX_BFGEXPL);
+      else
+      begin
+        if Projectile then
+          MH_SEND_CreateShot(LastShotID);
+        if ShotSound then
+          MH_SEND_Sound(wx, wy, snd);
+      end;
+    end;
 
-        TRIGGER_SHOT_EXPL:
-          begin
-            if g_Frames_Get(TextureID, 'FRAMES_EXPLODE_ROCKET') then
-            begin
-              Anim := TAnimation.Create(TextureID, False, 6);
-              Anim.Blending := False;
-              g_GFX_OnceAnim(wx-64, wy-64, Anim);
-              Anim.Free();
-            end;
-            Projectile := False;
-            g_Weapon_Explode(wx, wy, 60, 0);
-            snd := 'SOUND_WEAPON_EXPLODEROCKET';
-          end;
+  if ShotSound then
+    g_Sound_PlayExAt(snd, wx, wy);
 
-        TRIGGER_SHOT_BFGEXPL:
-          begin
-            if g_Frames_Get(TextureID, 'FRAMES_EXPLODE_BFG') then
-            begin
-              Anim := TAnimation.Create(TextureID, False, 6);
-              Anim.Blending := False;
-              g_GFX_OnceAnim(wx-64, wy-64, Anim);
-              Anim.Free();
-            end;
-            Projectile := False;
-            g_Weapon_BFG9000(wx, wy, 0);
-            snd := 'SOUND_WEAPON_EXPLODEBFG';
-          end;
+  if Projectile then
+    Result := LastShotID;
+end;
+
+procedure MakeShot(var Trigger: TTrigger; wx, wy, dx, dy: Integer; TargetUID: Word);
+begin
+  with Trigger do
+    if (Data.ShotAmmo = 0) or
+       ((Data.ShotAmmo > 0) and (ShotAmmoCount > 0)) then
+    begin
+      if (Data.ShotPanelID <> -1) and (ShotPanelTime = 0) then
+      begin
+        g_Map_SwitchTexture(ShotPanelType, Data.ShotPanelID);
+        ShotPanelTime := 4; // òèêîâ íà âñïûøêó âûñòðåëà
       end;
 
-      if g_Game_IsNet and g_Game_IsServer then
-        case Data.ShotType of
-          TRIGGER_SHOT_EXPL:
-            MH_SEND_Effect(wx, wy, Byte(Data.ShotSound), NET_GFX_EXPLODE);
-          TRIGGER_SHOT_BFGEXPL:
-            MH_SEND_Effect(wx, wy, Byte(Data.ShotSound), NET_GFX_BFGEXPL);
-          else
-          begin
-            if Projectile then
-              MH_SEND_CreateShot(LastShotID);
-            if Data.ShotSound then
-              MH_SEND_Sound(wx, wy, snd);
-          end;
-        end;
+      if Data.ShotIntSight > 0 then
+        ShotSightTimeout := 180; // ~= 5 ñåêóíä
+
+      if ShotAmmoCount > 0 then Dec(ShotAmmoCount);
 
-      if Data.ShotSound then
-        g_Sound_PlayExAt(snd, wx, wy);
+      dx := dx + Random(Data.ShotAccuracy) - Random(Data.ShotAccuracy);
+      dy := dy + Random(Data.ShotAccuracy) - Random(Data.ShotAccuracy);
+      
+      tr_SpawnShot(Data.ShotType, wx, wy, dx, dy, Data.ShotSound, TargetUID);
     end
     else
       if (Data.ShotIntReload > 0) and (ShotReloadTime = 0) then
         ShotReloadTime := Data.ShotIntReload; // òèêîâ íà ïåðåçàðÿäêó ïóøêè
 end;
 
-procedure MakeEffect(X, Y, VX, VY: Integer; T, ST, CR, CG, CB: Byte; Silent, Send: Boolean);
+procedure tr_MakeEffect(X, Y, VX, VY: Integer; T, ST, CR, CG, CB: Byte; Silent, Send: Boolean);
 var
   FramesID: DWORD;
   Anim: TAnimation;
@@ -657,12 +726,256 @@ begin
     end;
 end;
 
+function tr_Teleport(ActivateUID: Integer; TX, TY: Integer; TDir: Integer; Silent: Boolean; D2D: Boolean): Boolean;
+var
+  p: TPlayer;
+  m: TMonster;
+begin
+  Result := False;
+  if (ActivateUID < 0) or (ActivateUID > $FFFF) then Exit;
+  case g_GetUIDType(ActivateUID) of
+    UID_PLAYER:
+      begin
+        p := g_Player_Get(ActivateUID);
+        if p = nil then
+          Exit;
+
+        if D2D then
+          begin
+            if p.TeleportTo(TX-(p.Obj.Rect.Width div 2),
+                            TY-p.Obj.Rect.Height,
+                            Silent,
+                            TDir) then
+              Result := True;
+          end
+        else
+          if p.TeleportTo(TX, TY, Silent, TDir) then
+            Result := True;
+      end;
+
+    UID_MONSTER:
+      begin
+        m := g_Monsters_ByUID(ActivateUID);
+        if m = nil then
+          Exit;
+
+        if D2D then
+          begin
+            if m.TeleportTo(TX-(m.Obj.Rect.Width div 2),
+                            TY-m.Obj.Rect.Height,
+                            Silent,
+                            TDir) then
+              Result := True;
+          end
+        else
+          if m.TeleportTo(TX, TY, Silent, TDir) then
+            Result := True;
+      end;
+  end;
+end;
+
+function tr_Push(ActivateUID: Integer; VX, VY: Integer; ResetVel: Boolean): Boolean;
+var
+  p: TPlayer;
+  m: TMonster;
+begin
+  Result := True;
+  if (ActivateUID < 0) or (ActivateUID > $FFFF) then Exit;
+  case g_GetUIDType(ActivateUID) of
+    UID_PLAYER:
+      begin
+        p := g_Player_Get(ActivateUID);
+        if p = nil then
+          Exit;
+
+        if ResetVel then
+        begin
+          p.GameVelX := 0;
+          p.GameVelY := 0;
+          p.GameAccelX := 0;
+          p.GameAccelY := 0;
+        end;
+
+        p.Push(VX, VY);
+      end;
+
+    UID_MONSTER:
+      begin
+        m := g_Monsters_ByUID(ActivateUID);
+        if m = nil then
+          Exit;
+
+        if ResetVel then
+        begin
+          m.GameVelX := 0;
+          m.GameVelY := 0;
+          m.GameAccelX := 0;
+          m.GameAccelY := 0;
+        end;
+
+        m.Push(VX, VY);
+      end;
+  end;
+end;
+
+function tr_Message(MKind: Integer; MText: string; MSendTo: Integer; MTime: Integer; ActivateUID: Integer): Boolean;
+var
+  msg: string;
+  p: TPlayer;
+  i: Integer;
+begin
+  Result := True;
+  if (ActivateUID < 0) or (ActivateUID > $FFFF) then Exit;
+  msg := b_Text_Format(MText);
+  case MSendTo of
+    0: // activator
+      begin
+        if g_GetUIDType(ActivateUID) = UID_PLAYER then
+        begin
+          if g_Game_IsWatchedPlayer(ActivateUID) then
+          begin
+            if MKind = 0 then
+              g_Console_Add(msg, True)
+            else if MKind = 1 then
+              g_Game_Message(msg, MTime);
+          end
+          else
+          begin
+            p := g_Player_Get(ActivateUID);
+            if g_Game_IsNet and (p.FClientID >= 0) then
+              if MKind = 0 then
+                MH_SEND_Chat(msg, NET_CHAT_SYSTEM, p.FClientID)
+              else if MKind = 1 then
+                MH_SEND_GameEvent(NET_EV_BIGTEXT, MTime, msg, p.FClientID);
+          end;
+        end;
+      end;
+
+    1: // activator's team
+      begin
+        if g_GetUIDType(ActivateUID) = UID_PLAYER then
+        begin
+          p := g_Player_Get(ActivateUID);
+          if g_Game_IsWatchedTeam(p.Team) then
+            if MKind = 0 then
+              g_Console_Add(msg, True)
+            else if MKind = 1 then
+              g_Game_Message(msg, MTime);
+
+          if g_Game_IsNet then
+          begin
+            for i := Low(gPlayers) to High(gPlayers) do
+              if (gPlayers[i].Team = p.Team) and (gPlayers[i].FClientID >= 0) then
+                if MKind = 0 then
+                  MH_SEND_Chat(msg, NET_CHAT_SYSTEM, gPlayers[i].FClientID)
+                else if MKind = 1 then
+                  MH_SEND_GameEvent(NET_EV_BIGTEXT, MTime, msg, gPlayers[i].FClientID);
+          end;
+        end;
+      end;
+
+    2: // activator's enemy team
+      begin
+        if g_GetUIDType(ActivateUID) = UID_PLAYER then
+        begin
+          p := g_Player_Get(ActivateUID);
+          if g_Game_IsWatchedTeam(p.Team) then
+            if MKind = 0 then
+              g_Console_Add(msg, True)
+            else if MKind = 1 then
+              g_Game_Message(msg, MTime);
+
+          if g_Game_IsNet then
+          begin
+            for i := Low(gPlayers) to High(gPlayers) do
+              if (gPlayers[i].Team <> p.Team) and (gPlayers[i].FClientID >= 0) then
+                if MKind = 0 then
+                  MH_SEND_Chat(msg, NET_CHAT_SYSTEM, gPlayers[i].FClientID)
+                else if MKind = 1 then
+                  MH_SEND_GameEvent(NET_EV_BIGTEXT, MTime, msg, gPlayers[i].FClientID);
+          end;
+        end;
+      end;
+
+    3: // red team
+      begin
+        if g_Game_IsWatchedTeam(TEAM_RED) then
+          if MKind = 0 then
+            g_Console_Add(msg, True)
+          else if MKind = 1 then
+            g_Game_Message(msg, MTime);
+
+        if g_Game_IsNet then
+        begin
+          for i := Low(gPlayers) to High(gPlayers) do
+            if (gPlayers[i].Team = TEAM_RED) and (gPlayers[i].FClientID >= 0) then
+              if MKind = 0 then
+                MH_SEND_Chat(msg, NET_CHAT_SYSTEM, gPlayers[i].FClientID)
+              else if MKind = 1 then
+                MH_SEND_GameEvent(NET_EV_BIGTEXT, MTime, msg, gPlayers[i].FClientID);
+        end;
+      end;
+
+    4: // blue team
+      begin
+        if g_Game_IsWatchedTeam(TEAM_BLUE) then
+          if MKind = 0 then
+            g_Console_Add(msg, True)
+          else if MKind = 1 then
+            g_Game_Message(msg, MTime);
+
+        if g_Game_IsNet then
+        begin
+          for i := Low(gPlayers) to High(gPlayers) do
+            if (gPlayers[i].Team = TEAM_BLUE) and (gPlayers[i].FClientID >= 0) then
+              if MKind = 0 then
+                MH_SEND_Chat(msg, NET_CHAT_SYSTEM, gPlayers[i].FClientID)
+              else if MKind = 1 then
+                MH_SEND_GameEvent(NET_EV_BIGTEXT, MTime, msg, gPlayers[i].FClientID);
+        end;
+      end;
+
+    5: // everyone
+      begin
+        if MKind = 0 then
+          g_Console_Add(msg, True)
+        else if MKind = 1 then
+          g_Game_Message(msg, MTime);
+
+        if g_Game_IsNet then
+        begin
+          if MKind = 0 then
+            MH_SEND_Chat(msg, NET_CHAT_SYSTEM)
+          else if MKind = 1 then
+            MH_SEND_GameEvent(NET_EV_BIGTEXT, MTime, msg);
+        end;
+      end;
+  end;
+end;
+
+function tr_ShotAimCheck(var Trigger: TTrigger; Obj: PObj): Boolean;
+begin
+  result := false;
+  with Trigger do
+  begin
+    if TriggerType <> TRIGGER_SHOT then
+      Exit;
+    Result := (Data.ShotAim and TRIGGER_SHOT_AIM_ALLMAP > 0)
+              or g_Obj_Collide(X, Y, Width, Height, Obj);
+    if Result and (Data.ShotAim and TRIGGER_SHOT_AIM_TRACE > 0) then
+      Result := g_TraceVector(Data.ShotPos.X,
+                              Data.ShotPos.Y,
+                              Obj^.X + Obj^.Rect.X + (Obj^.Rect.Width div 2),
+                              Obj^.Y + Obj^.Rect.Y + (Obj^.Rect.Height div 2));
+  end;
+end;
+
 function ActivateTrigger(var Trigger: TTrigger; actType: Byte): Boolean;
 var
   animonce: Boolean;
   p: TPlayer;
   m: TMonster;
-  i, k, wx, wy, xd, yd: Integer;
+  idx, k, wx, wy, xd, yd: Integer;
   iid: LongWord;
   coolDown: Boolean;
   pAngle: Real;
@@ -670,7 +983,45 @@ var
   Anim: TAnimation;
   UIDType: Byte;
   TargetUID: Word;
-  msg: string;
+  it: PItem;
+  mon: TMonster;
+
+  function monsShotTarget (monidx: Integer; mon: TMonster): Boolean;
+  begin
+    result := false; // don't stop
+    if (mon <> nil) and mon.Live and tr_ShotAimCheck(Trigger, @(mon.Obj)) then
+    begin
+      xd := mon.GameX + mon.Obj.Rect.Width div 2;
+      yd := mon.GameY + mon.Obj.Rect.Height div 2;
+      TargetUID := mon.UID;
+      result := true; // stop
+    end;
+  end;
+
+  function monsShotTargetMonPlr (monidx: Integer; mon: TMonster): Boolean;
+  begin
+    result := false; // don't stop
+    if (mon <> nil) and mon.Live and tr_ShotAimCheck(Trigger, @(mon.Obj)) then
+    begin
+      xd := mon.GameX + mon.Obj.Rect.Width div 2;
+      yd := mon.GameY + mon.Obj.Rect.Height div 2;
+      TargetUID := mon.UID;
+      result := true; // stop
+    end;
+  end;
+
+  function monShotTargetPlrMon (monidx: Integer; mon: TMonster): Boolean;
+  begin
+    result := false; // don't stop
+    if (mon <> nil) and mon.Live and tr_ShotAimCheck(Trigger, @(mon.Obj)) then
+    begin
+      xd := mon.GameX + mon.Obj.Rect.Width div 2;
+      yd := mon.GameY + mon.Obj.Rect.Height div 2;
+      TargetUID := mon.UID;
+      result := true; // stop
+    end;
+  end;
+
 begin
   Result := False;
   if g_Game_IsClient then
@@ -704,64 +1055,22 @@ begin
 
       TRIGGER_TELEPORT:
         begin
-          case g_GetUIDType(ActivateUID) of
-            UID_PLAYER:
-              begin
-                p := g_Player_Get(ActivateUID);
-                if p = nil then
-                  Exit;
-
-                if Data.d2d_teleport then
-                  begin
-                    if p.TeleportTo(Data.TargetPoint.X-(p.Obj.Rect.Width div 2),
-                                    Data.TargetPoint.Y-p.Obj.Rect.Height,
-                                    Data.silent_teleport,
-                                    Data.TlpDir) then
-                      Result := True;
-                  end
-                else
-                  if p.TeleportTo(Data.TargetPoint.X,
-                                  Data.TargetPoint.Y,
-                                  Data.silent_teleport,
-                                  Data.TlpDir) then
-                    Result := True;
-              end;
-
-            UID_MONSTER:
-              begin
-                m := g_Monsters_Get(ActivateUID);
-                if m = nil then
-                  Exit;
-
-                if Data.d2d_teleport then
-                  begin
-                    if m.TeleportTo(Data.TargetPoint.X-(m.Obj.Rect.Width div 2),
-                                    Data.TargetPoint.Y-m.Obj.Rect.Height,
-                                    Data.silent_teleport,
-                                    Data.TlpDir) then
-                      Result := True;
-                  end
-                else
-                  if m.TeleportTo(Data.TargetPoint.X,
-                                  Data.TargetPoint.Y,
-                                  Data.silent_teleport,
-                                  Data.TlpDir) then
-                    Result := True;
-              end;
-          end;
-
+          Result := tr_Teleport(ActivateUID,
+                                Data.TargetPoint.X, Data.TargetPoint.Y,
+                                Data.TlpDir, Data.silent_teleport,
+                                Data.d2d_teleport);
           TimeOut := 0;
         end;
 
       TRIGGER_OPENDOOR:
         begin
-          Result := OpenDoor(Data.PanelID, Data.NoSound, Data.d2d_doors);
+          Result := tr_OpenDoor(Data.PanelID, Data.NoSound, Data.d2d_doors);
           TimeOut := 0;
         end;
 
       TRIGGER_CLOSEDOOR:
         begin
-          Result := CloseDoor(Data.PanelID, Data.NoSound, Data.d2d_doors);
+          Result := tr_CloseDoor(Data.PanelID, Data.NoSound, Data.d2d_doors);
           TimeOut := 0;
         end;
 
@@ -771,13 +1080,13 @@ begin
           begin
             if gWalls[Data.PanelID].Enabled then
               begin
-                Result := OpenDoor(Data.PanelID, Data.NoSound, Data.d2d_doors);
+                Result := tr_OpenDoor(Data.PanelID, Data.NoSound, Data.d2d_doors);
 
                 if TriggerType = TRIGGER_DOOR5 then
                   DoorTime := 180;
               end
             else
-              Result := CloseDoor(Data.PanelID, Data.NoSound, Data.d2d_doors);
+              Result := tr_CloseDoor(Data.PanelID, Data.NoSound, Data.d2d_doors);
 
             if Result then
               TimeOut := 18;
@@ -786,7 +1095,7 @@ begin
 
       TRIGGER_CLOSETRAP, TRIGGER_TRAP:
         begin
-          CloseTrap(Data.PanelID, Data.NoSound, Data.d2d_doors);
+          tr_CloseTrap(Data.PanelID, Data.NoSound, Data.d2d_doors);
 
           if TriggerType = TRIGGER_TRAP then
             begin
@@ -831,7 +1140,7 @@ begin
 
       TRIGGER_LIFTUP:
         begin
-          Result := SetLift(Data.PanelID, 0, Data.NoSound, Data.d2d_doors);
+          Result := tr_SetLift(Data.PanelID, 0, Data.NoSound, Data.d2d_doors);
           TimeOut := 0;
 
           if (not Data.NoSound) and Result then begin
@@ -847,7 +1156,7 @@ begin
 
       TRIGGER_LIFTDOWN:
         begin
-          Result := SetLift(Data.PanelID, 1, Data.NoSound, Data.d2d_doors);
+          Result := tr_SetLift(Data.PanelID, 1, Data.NoSound, Data.d2d_doors);
           TimeOut := 0;
 
           if (not Data.NoSound) and Result then begin
@@ -863,7 +1172,7 @@ begin
 
       TRIGGER_LIFT:
         begin
-          Result := SetLift(Data.PanelID, 3, Data.NoSound, Data.d2d_doors);
+          Result := tr_SetLift(Data.PanelID, 3, Data.NoSound, Data.d2d_doors);
 
           if Result then
           begin
@@ -942,7 +1251,7 @@ begin
               if (Data.MonMax > 0) and (SpawnedCount >= Data.MonMax) then
                 Break;
 
-              i := g_Monsters_Create(Data.MonType,
+              mon := g_Monsters_Create(Data.MonType,
                      Data.MonPos.X, Data.MonPos.Y,
                      TDirection(Data.MonDir), True);
 
@@ -950,25 +1259,27 @@ begin
 
             // Çäîðîâüå:
               if (Data.MonHealth > 0) then
-                gMonsters[i].SetHealth(Data.MonHealth);
+                mon.SetHealth(Data.MonHealth);
             // Óñòàíàâëèâàåì ïîâåäåíèå:
-              gMonsters[i].MonsterBehaviour := Data.MonBehav;
+              mon.MonsterBehaviour := Data.MonBehav;
+              mon.FNoRespawn := True;
+              if g_Game_IsNet then
+                MH_SEND_MonsterSpawn(mon.UID);
             // Èäåì èñêàòü öåëü, åñëè íàäî:
               if Data.MonActive then
-                gMonsters[i].WakeUp();
-              gMonsters[i].FNoRespawn := True;
+                mon.WakeUp();
 
               if Data.MonType <> MONSTER_BARREL then Inc(gTotalMonsters);
 
               if g_Game_IsNet then
               begin
                 SetLength(gMonstersSpawned, Length(gMonstersSpawned)+1);
-                gMonstersSpawned[High(gMonstersSpawned)] := gMonsters[i].UID;
+                gMonstersSpawned[High(gMonstersSpawned)] := mon.UID;
               end;
 
               if Data.MonMax > 0 then
               begin
-                gMonsters[i].SpawnTrigger := ID;
+                mon.SpawnTrigger := ID;
                 Inc(SpawnedCount);
               end;
 
@@ -978,13 +1289,13 @@ begin
                   begin
                     Anim := TAnimation.Create(FramesID, False, 3);
                     g_Sound_PlayExAt('SOUND_GAME_TELEPORT', Data.MonPos.X, Data.MonPos.Y);
-                    g_GFX_OnceAnim(gMonsters[i].Obj.X+gMonsters[i].Obj.Rect.X+(gMonsters[i].Obj.Rect.Width div 2)-32,
-                                   gMonsters[i].Obj.Y+gMonsters[i].Obj.Rect.Y+(gMonsters[i].Obj.Rect.Height div 2)-32, Anim);
+                    g_GFX_OnceAnim(mon.Obj.X+mon.Obj.Rect.X+(mon.Obj.Rect.Width div 2)-32,
+                                   mon.Obj.Y+mon.Obj.Rect.Y+(mon.Obj.Rect.Height div 2)-32, Anim);
                     Anim.Free();
                   end;
                   if g_Game_IsServer and g_Game_IsNet then
-                    MH_SEND_Effect(gMonsters[i].Obj.X+gMonsters[i].Obj.Rect.X+(gMonsters[i].Obj.Rect.Width div 2)-32,
-                                   gMonsters[i].Obj.Y+gMonsters[i].Obj.Rect.Y+(gMonsters[i].Obj.Rect.Height div 2)-32, 1,
+                    MH_SEND_Effect(mon.Obj.X+mon.Obj.Rect.X+(mon.Obj.Rect.Width div 2)-32,
+                                   mon.Obj.Y+mon.Obj.Rect.Y+(mon.Obj.Rect.Height div 2)-32, 1,
                                    NET_GFX_TELE);
                 end;
                 EFFECT_RESPAWN: begin
@@ -992,13 +1303,13 @@ begin
                   begin
                     Anim := TAnimation.Create(FramesID, False, 4);
                     g_Sound_PlayExAt('SOUND_ITEM_RESPAWNITEM', Data.MonPos.X, Data.MonPos.Y);
-                    g_GFX_OnceAnim(gMonsters[i].Obj.X+gMonsters[i].Obj.Rect.X+(gMonsters[i].Obj.Rect.Width div 2)-16,
-                                   gMonsters[i].Obj.Y+gMonsters[i].Obj.Rect.Y+(gMonsters[i].Obj.Rect.Height div 2)-16, Anim);
+                    g_GFX_OnceAnim(mon.Obj.X+mon.Obj.Rect.X+(mon.Obj.Rect.Width div 2)-16,
+                                   mon.Obj.Y+mon.Obj.Rect.Y+(mon.Obj.Rect.Height div 2)-16, Anim);
                     Anim.Free();
                   end;
                   if g_Game_IsServer and g_Game_IsNet then
-                    MH_SEND_Effect(gMonsters[i].Obj.X+gMonsters[i].Obj.Rect.X+(gMonsters[i].Obj.Rect.Width div 2)-16,
-                                   gMonsters[i].Obj.Y+gMonsters[i].Obj.Rect.Y+(gMonsters[i].Obj.Rect.Height div 2)-16, 1,
+                    MH_SEND_Effect(mon.Obj.X+mon.Obj.Rect.X+(mon.Obj.Rect.Width div 2)-16,
+                                   mon.Obj.Y+mon.Obj.Rect.Y+(mon.Obj.Rect.Height div 2)-16, 1,
                                    NET_GFX_RESPAWN);
                 end;
                 EFFECT_FIRE: begin
@@ -1006,19 +1317,16 @@ begin
                   begin
                     Anim := TAnimation.Create(FramesID, False, 4);
                     g_Sound_PlayExAt('SOUND_FIRE', Data.MonPos.X, Data.MonPos.Y);
-                    g_GFX_OnceAnim(gMonsters[i].Obj.X+gMonsters[i].Obj.Rect.X+(gMonsters[i].Obj.Rect.Width div 2)-32,
-                                   gMonsters[i].Obj.Y+gMonsters[i].Obj.Rect.Y+gMonsters[i].Obj.Rect.Height-128, Anim);
+                    g_GFX_OnceAnim(mon.Obj.X+mon.Obj.Rect.X+(mon.Obj.Rect.Width div 2)-32,
+                                   mon.Obj.Y+mon.Obj.Rect.Y+mon.Obj.Rect.Height-128, Anim);
                     Anim.Free();
                   end;
                   if g_Game_IsServer and g_Game_IsNet then
-                    MH_SEND_Effect(gMonsters[i].Obj.X+gMonsters[i].Obj.Rect.X+(gMonsters[i].Obj.Rect.Width div 2)-32,
-                                   gMonsters[i].Obj.Y+gMonsters[i].Obj.Rect.Y+gMonsters[i].Obj.Rect.Height-128, 1,
+                    MH_SEND_Effect(mon.Obj.X+mon.Obj.Rect.X+(mon.Obj.Rect.Width div 2)-32,
+                                   mon.Obj.Y+mon.Obj.Rect.Y+mon.Obj.Rect.Height-128, 1,
                                    NET_GFX_FIRE);
                 end;
               end;
-
-              if g_Game_IsNet then
-                MH_SEND_MonsterSpawn(gMonsters[i].UID);
             end;
           if g_Game_IsNet then
           begin
@@ -1065,51 +1373,55 @@ begin
 
                 if Data.ItemMax > 0 then
                 begin
-                  gItems[iid].SpawnTrigger := ID;
+                  it := g_Items_ByIdx(iid);
+                  it.SpawnTrigger := ID;
                   Inc(SpawnedCount);
                 end;
 
                 case Data.ItemEffect of
                   EFFECT_TELEPORT: begin
+                    it := g_Items_ByIdx(iid);
                     if g_Frames_Get(FramesID, 'FRAMES_TELEPORT') then
                     begin
                       Anim := TAnimation.Create(FramesID, False, 3);
                       g_Sound_PlayExAt('SOUND_GAME_TELEPORT', Data.ItemPos.X, Data.ItemPos.Y);
-                      g_GFX_OnceAnim(gItems[iid].Obj.X+gItems[iid].Obj.Rect.X+(gItems[iid].Obj.Rect.Width div 2)-32,
-                                     gItems[iid].Obj.Y+gItems[iid].Obj.Rect.Y+(gItems[iid].Obj.Rect.Height div 2)-32, Anim);
+                      g_GFX_OnceAnim(it.Obj.X+it.Obj.Rect.X+(it.Obj.Rect.Width div 2)-32,
+                                     it.Obj.Y+it.Obj.Rect.Y+(it.Obj.Rect.Height div 2)-32, Anim);
                       Anim.Free();
                     end;
                     if g_Game_IsServer and g_Game_IsNet then
-                      MH_SEND_Effect(gItems[iid].Obj.X+gItems[iid].Obj.Rect.X+(gItems[iid].Obj.Rect.Width div 2)-32,
-                                     gItems[iid].Obj.Y+gItems[iid].Obj.Rect.Y+(gItems[iid].Obj.Rect.Height div 2)-32, 1,
+                      MH_SEND_Effect(it.Obj.X+it.Obj.Rect.X+(it.Obj.Rect.Width div 2)-32,
+                                     it.Obj.Y+it.Obj.Rect.Y+(it.Obj.Rect.Height div 2)-32, 1,
                                      NET_GFX_TELE);
                   end;
                   EFFECT_RESPAWN: begin
+                    it := g_Items_ByIdx(iid);
                     if g_Frames_Get(FramesID, 'FRAMES_ITEM_RESPAWN') then
                     begin
                       Anim := TAnimation.Create(FramesID, False, 4);
                       g_Sound_PlayExAt('SOUND_ITEM_RESPAWNITEM', Data.ItemPos.X, Data.ItemPos.Y);
-                      g_GFX_OnceAnim(gItems[iid].Obj.X+gItems[iid].Obj.Rect.X+(gItems[iid].Obj.Rect.Width div 2)-16,
-                                     gItems[iid].Obj.Y+gItems[iid].Obj.Rect.Y+(gItems[iid].Obj.Rect.Height div 2)-16, Anim);
+                      g_GFX_OnceAnim(it.Obj.X+it.Obj.Rect.X+(it.Obj.Rect.Width div 2)-16,
+                                     it.Obj.Y+it.Obj.Rect.Y+(it.Obj.Rect.Height div 2)-16, Anim);
                       Anim.Free();
                     end;
                     if g_Game_IsServer and g_Game_IsNet then
-                      MH_SEND_Effect(gItems[iid].Obj.X+gItems[iid].Obj.Rect.X+(gItems[iid].Obj.Rect.Width div 2)-16,
-                                     gItems[iid].Obj.Y+gItems[iid].Obj.Rect.Y+(gItems[iid].Obj.Rect.Height div 2)-16, 1,
+                      MH_SEND_Effect(it.Obj.X+it.Obj.Rect.X+(it.Obj.Rect.Width div 2)-16,
+                                     it.Obj.Y+it.Obj.Rect.Y+(it.Obj.Rect.Height div 2)-16, 1,
                                      NET_GFX_RESPAWN);
                   end;
                   EFFECT_FIRE: begin
+                    it := g_Items_ByIdx(iid);
                     if g_Frames_Get(FramesID, 'FRAMES_FIRE') then
                     begin
                       Anim := TAnimation.Create(FramesID, False, 4);
                       g_Sound_PlayExAt('SOUND_FIRE', Data.ItemPos.X, Data.ItemPos.Y);
-                      g_GFX_OnceAnim(gItems[iid].Obj.X+gItems[iid].Obj.Rect.X+(gItems[iid].Obj.Rect.Width div 2)-32,
-                                     gItems[iid].Obj.Y+gItems[iid].Obj.Rect.Y+gItems[iid].Obj.Rect.Height-128, Anim);
+                      g_GFX_OnceAnim(it.Obj.X+it.Obj.Rect.X+(it.Obj.Rect.Width div 2)-32,
+                                     it.Obj.Y+it.Obj.Rect.Y+it.Obj.Rect.Height-128, Anim);
                       Anim.Free();
                     end;
                     if g_Game_IsServer and g_Game_IsNet then
-                      MH_SEND_Effect(gItems[iid].Obj.X+gItems[iid].Obj.Rect.X+(gItems[iid].Obj.Rect.Width div 2)-32,
-                                     gItems[iid].Obj.Y+gItems[iid].Obj.Rect.Y+gItems[iid].Obj.Rect.Height-128, 1,
+                      MH_SEND_Effect(it.Obj.X+it.Obj.Rect.X+(it.Obj.Rect.Width div 2)-32,
+                                     it.Obj.Y+it.Obj.Rect.Y+it.Obj.Rect.Height-128, 1,
                                      NET_GFX_FIRE);
                   end;
                 end;
@@ -1160,47 +1472,12 @@ begin
 
       TRIGGER_PUSH:
         begin
-          case g_GetUIDType(ActivateUID) of
-            UID_PLAYER:
-              begin
-                p := g_Player_Get(ActivateUID);
-                if p = nil then
-                  Exit;
-
-                if Data.ResetVel then
-                begin
-                  p.GameVelX := 0;
-                  p.GameVelY := 0;
-                  p.GameAccelX := 0;
-                  p.GameAccelY := 0;
-                end;
-
-                pAngle := -DegToRad(Data.PushAngle);
-                p.Push(Floor(Cos(pAngle)*Data.PushForce),
-                       Floor(Sin(pAngle)*Data.PushForce));
-              end;
-
-            UID_MONSTER:
-              begin
-                m := g_Monsters_Get(ActivateUID);
-                if m = nil then
-                  Exit;
-                if Data.ResetVel then
-                begin
-                  m.GameVelX := 0;
-                  m.GameVelY := 0;
-                  m.GameAccelX := 0;
-                  m.GameAccelY := 0;
-                end;
-
-                pAngle := -DegToRad(Data.PushAngle);
-                m.Push(Floor(Cos(pAngle)*Data.PushForce),
-                       Floor(Sin(pAngle)*Data.PushForce));
-              end;
-          end;
-
+          pAngle := -DegToRad(Data.PushAngle);
+          Result := tr_Push(ActivateUID,
+                            Floor(Cos(pAngle)*Data.PushForce),
+                            Floor(Sin(pAngle)*Data.PushForce),
+                            Data.ResetVel);
           TimeOut := 0;
-          Result := True;
         end;
 
       TRIGGER_SCORE:
@@ -1539,131 +1816,9 @@ begin
 
       TRIGGER_MESSAGE:
         begin
-          msg := b_Text_Format(Data.MessageText);
-          case Data.MessageSendTo of
-            0: // activator
-              begin
-                if g_GetUIDType(ActivateUID) = UID_PLAYER then
-                begin
-                  if g_Game_IsWatchedPlayer(ActivateUID) then
-                  begin
-                    if Data.MessageKind = 0 then
-                      g_Console_Add(msg, True)
-                    else if Data.MessageKind = 1 then
-                      g_Game_Message(msg, Data.MessageTime);
-                  end
-                  else
-                  begin
-                    p := g_Player_Get(ActivateUID);
-                    if g_Game_IsNet and (p.FClientID >= 0) then
-                      if Data.MessageKind = 0 then
-                        MH_SEND_Chat(msg, NET_CHAT_SYSTEM, p.FClientID)
-                      else if Data.MessageKind = 1 then
-                        MH_SEND_GameEvent(NET_EV_BIGTEXT, Data.MessageTime, msg, p.FClientID);
-                  end;
-                end;
-              end;
-
-            1: // activator's team
-              begin
-                if g_GetUIDType(ActivateUID) = UID_PLAYER then
-                begin
-                  p := g_Player_Get(ActivateUID);
-                  if g_Game_IsWatchedTeam(p.Team) then
-                    if Data.MessageKind = 0 then
-                      g_Console_Add(msg, True)
-                    else if Data.MessageKind = 1 then
-                      g_Game_Message(msg, Data.MessageTime);
-
-                  if g_Game_IsNet then
-                  begin
-                    for i := Low(gPlayers) to High(gPlayers) do
-                      if (gPlayers[i].Team = p.Team) and (gPlayers[i].FClientID >= 0) then
-                        if Data.MessageKind = 0 then
-                          MH_SEND_Chat(msg, NET_CHAT_SYSTEM, gPlayers[i].FClientID)
-                        else if Data.MessageKind = 1 then
-                          MH_SEND_GameEvent(NET_EV_BIGTEXT, Data.MessageTime, msg, gPlayers[i].FClientID);
-                  end;
-                end;
-              end;
-
-            2: // activator's enemy team
-              begin
-                if g_GetUIDType(ActivateUID) = UID_PLAYER then
-                begin
-                  p := g_Player_Get(ActivateUID);
-                  if g_Game_IsWatchedTeam(p.Team) then
-                    if Data.MessageKind = 0 then
-                      g_Console_Add(msg, True)
-                    else if Data.MessageKind = 1 then
-                      g_Game_Message(msg, Data.MessageTime);
-
-                  if g_Game_IsNet then
-                  begin
-                    for i := Low(gPlayers) to High(gPlayers) do
-                      if (gPlayers[i].Team <> p.Team) and (gPlayers[i].FClientID >= 0) then
-                        if Data.MessageKind = 0 then
-                          MH_SEND_Chat(msg, NET_CHAT_SYSTEM, gPlayers[i].FClientID)
-                        else if Data.MessageKind = 1 then
-                          MH_SEND_GameEvent(NET_EV_BIGTEXT, Data.MessageTime, msg, gPlayers[i].FClientID);
-                  end;
-                end;
-              end;
-
-            3: // red team
-              begin
-                if g_Game_IsWatchedTeam(TEAM_RED) then
-                  if Data.MessageKind = 0 then
-                    g_Console_Add(msg, True)
-                  else if Data.MessageKind = 1 then
-                    g_Game_Message(msg, Data.MessageTime);
-
-                if g_Game_IsNet then
-                begin
-                  for i := Low(gPlayers) to High(gPlayers) do
-                    if (gPlayers[i].Team = TEAM_RED) and (gPlayers[i].FClientID >= 0) then
-                      if Data.MessageKind = 0 then
-                        MH_SEND_Chat(msg, NET_CHAT_SYSTEM, gPlayers[i].FClientID)
-                      else if Data.MessageKind = 1 then
-                        MH_SEND_GameEvent(NET_EV_BIGTEXT, Data.MessageTime, msg, gPlayers[i].FClientID);
-                end;
-              end;
-
-            4: // blue team
-              begin
-                if g_Game_IsWatchedTeam(TEAM_BLUE) then
-                  if Data.MessageKind = 0 then
-                    g_Console_Add(msg, True)
-                  else if Data.MessageKind = 1 then
-                    g_Game_Message(msg, Data.MessageTime);
-
-                if g_Game_IsNet then
-                begin
-                  for i := Low(gPlayers) to High(gPlayers) do
-                    if (gPlayers[i].Team = TEAM_BLUE) and (gPlayers[i].FClientID >= 0) then
-                      if Data.MessageKind = 0 then
-                        MH_SEND_Chat(msg, NET_CHAT_SYSTEM, gPlayers[i].FClientID)
-                      else if Data.MessageKind = 1 then
-                        MH_SEND_GameEvent(NET_EV_BIGTEXT, Data.MessageTime, msg, gPlayers[i].FClientID);
-                end;
-              end;
-
-            5: // everyone
-              begin
-                if Data.MessageKind = 0 then
-                  g_Console_Add(msg, True)
-                else if Data.MessageKind = 1 then
-                  g_Game_Message(msg, Data.MessageTime);
-
-                if g_Game_IsNet then
-                begin
-                  if Data.MessageKind = 0 then
-                    MH_SEND_Chat(msg, NET_CHAT_SYSTEM)
-                  else if Data.MessageKind = 1 then
-                    MH_SEND_GameEvent(NET_EV_BIGTEXT, Data.MessageTime, msg);
-                end;
-              end;
-          end;
+          Result := tr_Message(Data.MessageKind, Data.MessageText, 
+                               Data.MessageSendTo, Data.MessageTime,
+                               ActivateUID);
           TimeOut := 18;
         end;
 
@@ -1678,10 +1833,10 @@ begin
             if coolDown then
             begin
               // Âñïîìèíàåì, àêòèâèðîâàë ëè îí ìåíÿ ðàíüøå
-              for i := 0 to High(Activators) do
-                if Activators[i].UID = ActivateUID then
+              for idx := 0 to High(Activators) do
+                if Activators[idx].UID = ActivateUID then
                 begin
-                  k := i;
+                  k := idx;
                   Break;
                 end;
               if k = -1 then
@@ -1725,7 +1880,7 @@ begin
 
                 UID_MONSTER:
                   begin
-                    m := g_Monsters_Get(ActivateUID);
+                    m := g_Monsters_ByUID(ActivateUID);
                     if m = nil then
                       Exit;
 
@@ -1745,12 +1900,12 @@ begin
               end;
               // Íàçíà÷àåì âðåìÿ ñëåäóþùåãî âîçäåéñòâèÿ
               if TriggerType = TRIGGER_DAMAGE then
-                i := Data.DamageInterval
+                idx := Data.DamageInterval
               else
-                i := Data.HealInterval;
+                idx := Data.HealInterval;
               if coolDown then
-                if i > 0 then
-                  Activators[k].TimeOut := i
+                if idx > 0 then
+                  Activators[k].TimeOut := idx
                 else
                   Activators[k].TimeOut := 65535;
             end;
@@ -1763,6 +1918,9 @@ begin
           if ShotSightTime > 0 then
             Exit;
 
+          // put this at the beginning so it doesn't trigger itself
+          TimeOut := Data.ShotWait + 1;
+
           wx := Data.ShotPos.X;
           wy := Data.ShotPos.Y;
           pAngle := -DegToRad(Data.ShotAngle);
@@ -1772,75 +1930,58 @@ begin
 
           case Data.ShotTarget of
             TRIGGER_SHOT_TARGET_MON: // monsters
-              if gMonsters <> nil then
-                for i := Low(gMonsters) to High(gMonsters) do
-                  if (gMonsters[i] <> nil) and gMonsters[i].Live and
-                     (Data.ShotAllMap or g_Obj_Collide(X, Y, Width, Height, @(gMonsters[i].Obj))) then
-                  begin
-                    xd := gMonsters[i].GameX + gMonsters[i].Obj.Rect.Width div 2;
-                    yd := gMonsters[i].GameY + gMonsters[i].Obj.Rect.Height div 2;
-                    TargetUID := gMonsters[i].UID;
-                    break;
-                  end;
+              g_Mons_ForEach(monsShotTarget);
 
             TRIGGER_SHOT_TARGET_PLR: // players
               if gPlayers <> nil then
-                for i := Low(gPlayers) to High(gPlayers) do
-                  if (gPlayers[i] <> nil) and gPlayers[i].Live and
-                     (Data.ShotAllMap or g_Obj_Collide(X, Y, Width, Height, @(gPlayers[i].Obj))) then
+                for idx := Low(gPlayers) to High(gPlayers) do
+                  if (gPlayers[idx] <> nil) and gPlayers[idx].Live and
+                     tr_ShotAimCheck(Trigger, @(gPlayers[idx].Obj)) then
                   begin
-                    xd := gPlayers[i].GameX + PLAYER_RECT_CX;
-                    yd := gPlayers[i].GameY + PLAYER_RECT_CY;
-                    TargetUID := gPlayers[i].UID;
+                    xd := gPlayers[idx].GameX + PLAYER_RECT_CX;
+                    yd := gPlayers[idx].GameY + PLAYER_RECT_CY;
+                    TargetUID := gPlayers[idx].UID;
                     break;
                   end;
 
             TRIGGER_SHOT_TARGET_RED: // red team
               if gPlayers <> nil then
-                for i := Low(gPlayers) to High(gPlayers) do
-                  if (gPlayers[i] <> nil) and gPlayers[i].Live and
-                     (gPlayers[i].Team = TEAM_RED) and
-                     (Data.ShotAllMap or g_Obj_Collide(X, Y, Width, Height, @(gPlayers[i].Obj))) then
+                for idx := Low(gPlayers) to High(gPlayers) do
+                  if (gPlayers[idx] <> nil) and gPlayers[idx].Live and
+                     (gPlayers[idx].Team = TEAM_RED) and
+                     tr_ShotAimCheck(Trigger, @(gPlayers[idx].Obj)) then
                   begin
-                    xd := gPlayers[i].GameX + PLAYER_RECT_CX;
-                    yd := gPlayers[i].GameY + PLAYER_RECT_CY;
-                    TargetUID := gPlayers[i].UID;
+                    xd := gPlayers[idx].GameX + PLAYER_RECT_CX;
+                    yd := gPlayers[idx].GameY + PLAYER_RECT_CY;
+                    TargetUID := gPlayers[idx].UID;
                     break;
                   end;
 
             TRIGGER_SHOT_TARGET_BLUE: // blue team
               if gPlayers <> nil then
-                for i := Low(gPlayers) to High(gPlayers) do
-                  if (gPlayers[i] <> nil) and gPlayers[i].Live and
-                     (gPlayers[i].Team = TEAM_BLUE) and
-                     (Data.ShotAllMap or g_Obj_Collide(X, Y, Width, Height, @(gPlayers[i].Obj))) then
+                for idx := Low(gPlayers) to High(gPlayers) do
+                  if (gPlayers[idx] <> nil) and gPlayers[idx].Live and
+                     (gPlayers[idx].Team = TEAM_BLUE) and
+                     tr_ShotAimCheck(Trigger, @(gPlayers[idx].Obj)) then
                   begin
-                    xd := gPlayers[i].GameX + PLAYER_RECT_CX;
-                    yd := gPlayers[i].GameY + PLAYER_RECT_CY;
-                    TargetUID := gPlayers[i].UID;
+                    xd := gPlayers[idx].GameX + PLAYER_RECT_CX;
+                    yd := gPlayers[idx].GameY + PLAYER_RECT_CY;
+                    TargetUID := gPlayers[idx].UID;
                     break;
                   end;
 
             TRIGGER_SHOT_TARGET_MONPLR: // monsters then players
             begin
-              if gMonsters <> nil then
-                for i := Low(gMonsters) to High(gMonsters) do
-                  if (gMonsters[i] <> nil) and gMonsters[i].Live and
-                     (Data.ShotAllMap or g_Obj_Collide(X, Y, Width, Height, @(gMonsters[i].Obj))) then
-                  begin
-                    xd := gMonsters[i].GameX + gMonsters[i].Obj.Rect.Width div 2;
-                    yd := gMonsters[i].GameY + gMonsters[i].Obj.Rect.Height div 2;
-                    TargetUID := gMonsters[i].UID;
-                    break;
-                  end;
+              g_Mons_ForEach(monsShotTargetMonPlr);
+
               if (TargetUID = 0) and (gPlayers <> nil) then
-                for i := Low(gPlayers) to High(gPlayers) do
-                  if (gPlayers[i] <> nil) and gPlayers[i].Live and
-                     (Data.ShotAllMap or g_Obj_Collide(X, Y, Width, Height, @(gPlayers[i].Obj))) then
+                for idx := Low(gPlayers) to High(gPlayers) do
+                  if (gPlayers[idx] <> nil) and gPlayers[idx].Live and
+                     tr_ShotAimCheck(Trigger, @(gPlayers[idx].Obj)) then
                   begin
-                    xd := gPlayers[i].GameX + PLAYER_RECT_CX;
-                    yd := gPlayers[i].GameY + PLAYER_RECT_CY;
-                    TargetUID := gPlayers[i].UID;
+                    xd := gPlayers[idx].GameX + PLAYER_RECT_CX;
+                    yd := gPlayers[idx].GameY + PLAYER_RECT_CY;
+                    TargetUID := gPlayers[idx].UID;
                     break;
                   end;
             end;
@@ -1848,31 +1989,27 @@ begin
             TRIGGER_SHOT_TARGET_PLRMON: // players then monsters
             begin
               if gPlayers <> nil then
-                for i := Low(gPlayers) to High(gPlayers) do
-                  if (gPlayers[i] <> nil) and gPlayers[i].Live and
-                     (Data.ShotAllMap or g_Obj_Collide(X, Y, Width, Height, @(gPlayers[i].Obj))) then
-                  begin
-                    xd := gPlayers[i].GameX + PLAYER_RECT_CX;
-                    yd := gPlayers[i].GameY + PLAYER_RECT_CY;
-                    TargetUID := gPlayers[i].UID;
-                    break;
-                  end;
-              if (TargetUID = 0) and (gMonsters <> nil) then
-                for i := Low(gMonsters) to High(gMonsters) do
-                  if (gMonsters[i] <> nil) and gMonsters[i].Live and
-                     (Data.ShotAllMap or g_Obj_Collide(X, Y, Width, Height, @(gMonsters[i].Obj))) then
+                for idx := Low(gPlayers) to High(gPlayers) do
+                  if (gPlayers[idx] <> nil) and gPlayers[idx].Live and
+                     tr_ShotAimCheck(Trigger, @(gPlayers[idx].Obj)) then
                   begin
-                    xd := gMonsters[i].GameX + gMonsters[i].Obj.Rect.Width div 2;
-                    yd := gMonsters[i].GameY + gMonsters[i].Obj.Rect.Height div 2;
-                    TargetUID := gMonsters[i].UID;
+                    xd := gPlayers[idx].GameX + PLAYER_RECT_CX;
+                    yd := gPlayers[idx].GameY + PLAYER_RECT_CY;
+                    TargetUID := gPlayers[idx].UID;
                     break;
                   end;
+              if TargetUID = 0 then g_Mons_ForEach(monShotTargetPlrMon);
             end;
 
-            else TargetUID := ActivateUID;
+            else begin
+              if (Data.ShotTarget <> TRIGGER_SHOT_TARGET_NONE) or
+                 (Data.ShotType <> TRIGGER_SHOT_REV) then
+                TargetUID := ActivateUID;
+            end;
           end;
 
-          if (Data.ShotTarget = TRIGGER_SHOT_TARGET_NONE) or (TargetUID > 0) then
+          if (Data.ShotTarget = TRIGGER_SHOT_TARGET_NONE) or (TargetUID > 0) or
+            ((Data.ShotTarget > TRIGGER_SHOT_TARGET_NONE) and (TargetUID = 0)) then
           begin
             Result := True;
             if (Data.ShotIntSight = 0) or
@@ -1891,15 +2028,13 @@ begin
               end;
             end;
           end;
-
-          TimeOut := Data.ShotWait + 1;
         end;
 
       TRIGGER_EFFECT:
         begin
-          i := Data.FXCount;
+          idx := Data.FXCount;
 
-          while i > 0 do
+          while idx > 0 do
           begin
             case Data.FXPos of
               TRIGGER_EFFECT_POS_CENTER:
@@ -1923,10 +2058,10 @@ begin
             if Data.FXSpreadR > 0 then xd := xd + Random(Data.FXSpreadR + 1);
             if Data.FXSpreadU > 0 then yd := yd - Random(Data.FXSpreadU + 1);
             if Data.FXSpreadD > 0 then yd := yd + Random(Data.FXSpreadD + 1);
-            MakeEffect(wx, wy, xd, yd,
+            tr_MakeEffect(wx, wy, xd, yd,
                        Data.FXType, Data.FXSubType,
                        Data.FXColorR, Data.FXColorG, Data.FXColorB, True, False);
-            Dec(i);
+            Dec(idx);
           end;
           TimeOut := Data.FXWait;
         end;
@@ -1992,12 +2127,12 @@ begin
   // Åùå íåò òàêîãî çâóêà:
     if not g_Sound_Exists(Trigger.Data.SoundName) then
     begin
-      g_ProcessResourceStr(Trigger.Data.SoundName, @fn, nil, nil);
+      fn := g_ExtractWadName(Trigger.Data.SoundName);
 
       if fn = '' then
         begin // Çâóê â ôàéëå ñ êàðòîé
-          g_ProcessResourceStr(gMapInfo.Map, @mapw, nil, nil);
-          fn := mapw + Trigger.Data.SoundName;
+          mapw := g_ExtractWadName(gMapInfo.Map);
+          fn := mapw+':'+g_ExtractFilePathName(Trigger.Data.SoundName);
         end
       else // Çâóê â îòäåëüíîì ôàéëå
         fn := GameDir + '/wads/' + Trigger.Data.SoundName;
@@ -2025,12 +2160,12 @@ begin
   // Åùå íåò òàêîé ìóçûêè:
     if not g_Sound_Exists(Trigger.Data.MusicName) then
     begin
-      g_ProcessResourceStr(Trigger.Data.MusicName, @fn, nil, nil);
+      fn := g_ExtractWadName(Trigger.Data.MusicName);
 
       if fn = '' then
         begin // Ìóçûêà â ôàéëå ñ êàðòîé
-          g_ProcessResourceStr(gMapInfo.Map, @mapw, nil, nil);
-          fn := mapw + Trigger.Data.MusicName;
+          mapw := g_ExtractWadName(gMapInfo.Map);
+          fn := mapw+':'+g_ExtractFilePathName(Trigger.Data.MusicName);
         end
       else // Ìóçûêà â ôàéëå ñ êàðòîé
         fn := GameDir+'/wads/'+Trigger.Data.MusicName;
@@ -2060,6 +2195,24 @@ procedure g_Triggers_Update();
 var
   a, b, i: Integer;
   Affected: array of Integer;
+
+  {function monsNear (monidx: Integer; mon: TMonster): Boolean;
+  begin
+    result := false; // don't stop
+    if mon.Collide(gTriggers[a].X, gTriggers[a].Y, gTriggers[a].Width, gTriggers[a].Height) then
+    begin
+      gTriggers[a].ActivateUID := mon.UID;
+      ActivateTrigger(gTriggers[a], ACTIVATE_MONSTERCOLLIDE);
+    end;
+  end;}
+
+  function monsNear (monidx: Integer; mon: TMonster): Boolean;
+  begin
+    result := false; // don't stop
+    gTriggers[a].ActivateUID := mon.UID;
+    ActivateTrigger(gTriggers[a], ACTIVATE_MONSTERCOLLIDE);
+  end;
+
 begin
   if gTriggers = nil then
     Exit;
@@ -2155,7 +2308,7 @@ begin
       // Òðèããåð "Ëîâóøêà" - ïîðà îòêðûâàòü:
         if (TriggerType = TRIGGER_TRAP) and (DoorTime = 0) and (Data.PanelID <> -1) then
         begin
-          OpenDoor(Data.PanelID, Data.NoSound, Data.d2d_doors);
+          tr_OpenDoor(Data.PanelID, Data.NoSound, Data.d2d_doors);
           DoorTime := -1;
         end;
 
@@ -2166,7 +2319,7 @@ begin
           if gWalls[Data.PanelID].Enabled then
             DoorTime := -1
           else // Ïîêà îòêðûòà - çàêðûâàåì
-            if CloseDoor(Data.PanelID, Data.NoSound, Data.d2d_doors) then
+            if tr_CloseDoor(Data.PanelID, Data.NoSound, Data.d2d_doors) then
               DoorTime := -1;
         end;
 
@@ -2290,23 +2443,18 @@ begin
           ActivateTrigger(gTriggers[a], 0);
         end else
         begin
-        // "Ìîíñòð áëèçêî":
+          // "Ìîíñòð áëèçêî"
           if ByteBool(ActivateType and ACTIVATE_MONSTERCOLLIDE) and
              (TimeOut = 0) and (Keys = 0) then // Åñëè íå íóæíû êëþ÷è
-            if gMonsters <> nil then
-              for b := 0 to High(gMonsters) do
-                if (gMonsters[b] <> nil) then
-                  with gMonsters[b] do
-                    if Collide(X, Y, Width, Height) then
-                    begin
-                      gTriggers[a].ActivateUID := UID;
-                      ActivateTrigger(gTriggers[a], ACTIVATE_MONSTERCOLLIDE);
-                    end;
+          begin
+            //g_Mons_ForEach(monsNear);
+            g_Mons_ForEachAt(gTriggers[a].X, gTriggers[a].Y, gTriggers[a].Width, gTriggers[a].Height, monsNear);
+          end;
 
-        // "Ìîíñòðîâ íåò":
+          // "Ìîíñòðîâ íåò"
           if ByteBool(ActivateType and ACTIVATE_NOMONSTER) and
              (TimeOut = 0) and (Keys = 0) then
-            if not g_CollideMonster(X, Y, Width, Height) then
+            if not g_Mons_IsAnyAliveAt(X, Y, Width, Height) then
             begin
               gTriggers[a].ActivateUID := 0;
               ActivateTrigger(gTriggers[a], ACTIVATE_NOMONSTER);
@@ -2317,9 +2465,9 @@ begin
       end;
 end;
 
-procedure g_Triggers_Press(ID: DWORD; ActivateType: Byte);
+procedure g_Triggers_Press(ID: DWORD; ActivateType: Byte; ActivateUID: Word = 0);
 begin
-  gTriggers[ID].ActivateUID := 0;
+  gTriggers[ID].ActivateUID := ActivateUID;
   ActivateTrigger(gTriggers[ID], ActivateType);
 end;
 
@@ -2400,7 +2548,7 @@ begin
       end;
 end;
 
-procedure g_Triggers_PressC(CX, CY: Integer; Radius: Word; UID: Word; ActivateType: Byte);
+procedure g_Triggers_PressC(CX, CY: Integer; Radius: Word; UID: Word; ActivateType: Byte; IgnoreTrigger: Integer = -1);
 var
   a: Integer;
   k: Byte;
@@ -2426,7 +2574,8 @@ begin
   rsq := Radius * Radius;
 
   for a := 0 to High(gTriggers) do
-    if (gTriggers[a].TriggerType <> TRIGGER_NONE) and
+    if (gTriggers[a].ID <> DWORD(IgnoreTrigger)) and
+       (gTriggers[a].TriggerType <> TRIGGER_NONE) and
        (gTriggers[a].TimeOut = 0) and
        ((gTriggers[a].Keys and k) = gTriggers[a].Keys) and
        ByteBool(gTriggers[a].ActivateType and ActivateType) then
@@ -2461,7 +2610,7 @@ begin
          (TriggerType = TRIGGER_DOOR5) or
          (TriggerType = TRIGGER_DOOR) then
       begin
-        OpenDoor(Data.PanelID, True, Data.d2d_doors);
+        tr_OpenDoor(Data.PanelID, True, Data.d2d_doors);
         if TriggerType = TRIGGER_DOOR5 then DoorTime := 180;
         b := True;
       end;