(* 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_triggers; interface uses MAPDEF, e_graphics, g_basic, g_sound, BinEditor, xdynrec; type TActivator = record UID: Word; TimeOut: Word; end; PTrigger = ^TTrigger; TTrigger = record public ID: DWORD; ClientID: DWORD; TriggerType: Byte; X, Y: Integer; Width, Height: Word; Enabled: Boolean; ActivateType: Byte; Keys: Byte; TexturePanel: Integer; TexturePanelType: Word; TimeOut: Word; ActivateUID: Word; Activators: array of TActivator; PlayerCollide: Boolean; DoorTime: Integer; PressTime: Integer; PressCount: Integer; SoundPlayCount: Integer; Sound: TPlayableSound; AutoSpawn: Boolean; SpawnCooldown: Integer; SpawnedCount: Integer; ShotPanelType: Word; ShotPanelTime: Integer; ShotSightTime: Integer; ShotSightTimeout: Integer; ShotSightTarget: Word; ShotSightTargetN: Word; ShotAmmoCount: Word; ShotReloadTime: Integer; mapId: AnsiString; // trigger id, from map //trigShotPanelId: Integer; trigPanelId: Integer; //TrigData: TTriggerData; trigData: TDynRecord; // triggerdata; owned by trigger public function trigCenter (): TDFPoint; inline; public property trigShotPanelId: Integer read trigPanelId write trigPanelId; end; function g_Triggers_Create(Trigger: TTrigger): DWORD; procedure g_Triggers_Update(); 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; 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; gSecretsCount: Integer = 0; gMonstersSpawned: array of LongInt = nil; implementation uses g_player, g_map, Math, g_gfx, g_game, g_textures, g_console, g_monsters, g_items, g_phys, g_weapons, wadreader, g_main, SysUtils, e_log, g_language, g_options, g_net, g_netmsg, utils; const TRIGGER_SIGNATURE = $52475254; // 'TRGR' TRAP_DAMAGE = 1000; function TTrigger.trigCenter (): TDFPoint; inline; begin result := TDFPoint.Create(x+width div 2, y+height div 2); end; function FindTrigger(): DWORD; var i: Integer; begin if gTriggers <> nil then for i := 0 to High(gTriggers) do if gTriggers[i].TriggerType = TRIGGER_NONE then begin Result := i; Exit; end; if gTriggers = nil then begin SetLength(gTriggers, 8); Result := 0; end else begin Result := High(gTriggers) + 1; SetLength(gTriggers, Length(gTriggers) + 8); end; end; function tr_CloseDoor(PanelID: Integer; NoSound: Boolean; d2d: Boolean): Boolean; var a, b, c: Integer; begin Result := False; if PanelID = -1 then Exit; if not d2d then begin with gWalls[PanelID] do begin if g_CollidePlayer(X, Y, Width, Height) or g_Mons_IsAnyAliveAt(X, Y, Width, Height) then Exit; if not Enabled then begin if not NoSound then begin g_Sound_PlayExAt('SOUND_GAME_DOORCLOSE', X, Y); if g_Game_IsServer and g_Game_IsNet then MH_SEND_Sound(X, Y, 'SOUND_GAME_DOORCLOSE'); end; g_Map_EnableWall(PanelID); Result := True; end; end; end else begin if gDoorMap = nil then Exit; c := -1; for a := 0 to High(gDoorMap) do begin for b := 0 to High(gDoorMap[a]) do if gDoorMap[a, b] = DWORD(PanelID) then begin c := a; Break; end; if c <> -1 then Break; end; if c = -1 then Exit; for b := 0 to High(gDoorMap[c]) do with gWalls[gDoorMap[c, b]] do begin if g_CollidePlayer(X, Y, Width, Height) or g_Mons_IsAnyAliveAt(X, Y, Width, Height) then Exit; end; if not NoSound then for b := 0 to High(gDoorMap[c]) do if not gWalls[gDoorMap[c, b]].Enabled then begin with gWalls[PanelID] do begin g_Sound_PlayExAt('SOUND_GAME_DOORCLOSE', X, Y); if g_Game_IsServer and g_Game_IsNet then MH_SEND_Sound(X, Y, 'SOUND_GAME_DOORCLOSE'); end; Break; end; for b := 0 to High(gDoorMap[c]) do if not gWalls[gDoorMap[c, b]].Enabled then begin g_Map_EnableWall(gDoorMap[c, b]); Result := True; end; end; end; procedure tr_CloseTrap(PanelID: Integer; NoSound: Boolean; d2d: Boolean); var a, b, c: Integer; wx, wy, wh, ww: Integer; function monsDamage (mon: TMonster): Boolean; begin result := false; // don't stop if g_Obj_Collide(wx, wy, ww, wh, @mon.Obj) then mon.Damage(TRAP_DAMAGE, 0, 0, 0, HIT_TRAP); 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 if gPlayers <> nil then for a := 0 to High(gPlayers) do if (gPlayers[a] <> nil) and gPlayers[a].Live and gPlayers[a].Collide(X, Y, Width, Height) then gPlayers[a].Damage(TRAP_DAMAGE, 0, 0, 0, HIT_TRAP); //g_Mons_ForEach(monsDamage); g_Mons_ForEachAliveAt(wx, wy, ww, wh, monsDamage); if not Enabled then g_Map_EnableWall(PanelID); end; end else begin if gDoorMap = nil then Exit; c := -1; 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'); 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 for a := 0 to High(gPlayers) do if (gPlayers[a] <> nil) and gPlayers[a].Live and gPlayers[a].Collide(X, Y, Width, Height) then gPlayers[a].Damage(TRAP_DAMAGE, 0, 0, 0, HIT_TRAP); //g_Mons_ForEach(monsDamage); g_Mons_ForEachAliveAt(wx, wy, ww, wh, 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 tr_OpenDoor(PanelID: Integer; NoSound: Boolean; d2d: Boolean): Boolean; var a, b, c: Integer; begin Result := False; if PanelID = -1 then Exit; if not d2d then begin with gWalls[PanelID] do if Enabled then begin if not NoSound then begin g_Sound_PlayExAt('SOUND_GAME_DOOROPEN', X, Y); if g_Game_IsServer and g_Game_IsNet then MH_SEND_Sound(X, Y, 'SOUND_GAME_DOOROPEN'); end; g_Map_DisableWall(PanelID); Result := True; end; end else begin if gDoorMap = nil then Exit; c := -1; for a := 0 to High(gDoorMap) do begin for b := 0 to High(gDoorMap[a]) do if gDoorMap[a, b] = DWORD(PanelID) then begin c := a; Break; end; if c <> -1 then Break; end; if c = -1 then Exit; if not NoSound then for b := 0 to High(gDoorMap[c]) do if gWalls[gDoorMap[c, b]].Enabled then begin with gWalls[PanelID] do begin g_Sound_PlayExAt('SOUND_GAME_DOOROPEN', X, Y); if g_Game_IsServer and g_Game_IsNet then MH_SEND_Sound(X, Y, 'SOUND_GAME_DOOROPEN'); end; Break; end; for b := 0 to High(gDoorMap[c]) do if gWalls[gDoorMap[c, b]].Enabled then begin g_Map_DisableWall(gDoorMap[c, b]); Result := True; end; end; end; function tr_SetLift(PanelID: Integer; d: Integer; NoSound: Boolean; d2d: Boolean): Boolean; var a, b, c, t: Integer; begin t := 0; Result := False; if PanelID = -1 then Exit; if (gLifts[PanelID].PanelType = PANEL_LIFTUP) or (gLifts[PanelID].PanelType = PANEL_LIFTDOWN) then case d of 0: t := 0; 1: t := 1; else t := IfThen(gLifts[PanelID].LiftType = 1, 0, 1); end else if (gLifts[PanelID].PanelType = PANEL_LIFTLEFT) or (gLifts[PanelID].PanelType = PANEL_LIFTRIGHT) then case d of 0: t := 2; 1: t := 3; else t := IfThen(gLifts[PanelID].LiftType = 2, 3, 2); end; if not d2d then begin with gLifts[PanelID] do if LiftType <> t then begin g_Map_SetLift(PanelID, t); {if not NoSound then g_Sound_PlayExAt('SOUND_GAME_SWITCH0', X, Y);} Result := True; end; end else // Как в D2d begin if gLiftMap = nil then Exit; c := -1; for a := 0 to High(gLiftMap) do begin for b := 0 to High(gLiftMap[a]) do if gLiftMap[a, b] = DWORD(PanelID) then begin c := a; Break; end; if c <> -1 then Break; end; if c = -1 then Exit; {if not NoSound then for b := 0 to High(gLiftMap[c]) do if gLifts[gLiftMap[c, b]].LiftType <> t then begin with gLifts[PanelID] do g_Sound_PlayExAt('SOUND_GAME_SWITCH0', X, Y); Break; end;} for b := 0 to High(gLiftMap[c]) do with gLifts[gLiftMap[c, b]] do if LiftType <> t then begin g_Map_SetLift(gLiftMap[c, b], t); Result := True; end; end; end; function tr_SpawnShot(ShotType: Integer; wx, wy, dx, dy: Integer; ShotSound: Boolean; ShotTarget: Word): Integer; var snd: string; Projectile: Boolean; TextureID: DWORD; Anim: TAnimation; begin Result := -1; TextureID := DWORD(-1); snd := 'SOUND_WEAPON_FIREROCKET'; Projectile := True; case ShotType of TRIGGER_SHOT_PISTOL: begin 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; 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; 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; 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; TRIGGER_SHOT_IMP: begin g_Weapon_ball1(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_SPIDER: begin g_Weapon_aplasma(wx, wy, dx, dy, 0, -1, True); snd := 'SOUND_WEAPON_FIREPLASMA'; end; TRIGGER_SHOT_CACO: begin g_Weapon_ball2(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_MANCUB: begin g_Weapon_manfire(wx, wy, dx, dy, 0, -1, True); snd := 'SOUND_WEAPON_FIREBALL'; end; TRIGGER_SHOT_REV: begin g_Weapon_revf(wx, wy, dx, dy, 0, ShotTarget, -1, True); snd := 'SOUND_WEAPON_FIREREV'; end; TRIGGER_SHOT_ROCKET: begin g_Weapon_Rocket(wx, wy, dx, dy, 0, -1, True); snd := 'SOUND_WEAPON_FIREROCKET'; end; TRIGGER_SHOT_BFG: begin g_Weapon_BFGShot(wx, wy, dx, dy, 0, -1, True); snd := 'SOUND_WEAPON_FIREBFG'; 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_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; else exit; 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; if ShotSound then g_Sound_PlayExAt(snd, wx, wy); if Projectile then Result := LastShotID; end; procedure MakeShot(var Trigger: TTrigger; wx, wy, dx, dy: Integer; TargetUID: Word); begin with Trigger do if (trigData.trigShotAmmo = 0) or ((trigData.trigShotAmmo > 0) and (ShotAmmoCount > 0)) then begin if (trigShotPanelID <> -1) and (ShotPanelTime = 0) then begin g_Map_SwitchTexture(ShotPanelType, trigShotPanelID); ShotPanelTime := 4; // тиков на вспышку выстрела end; if trigData.trigShotIntSight > 0 then ShotSightTimeout := 180; // ~= 5 секунд if ShotAmmoCount > 0 then Dec(ShotAmmoCount); dx := dx + Random(trigData.trigShotAccuracy) - Random(trigData.trigShotAccuracy); dy := dy + Random(trigData.trigShotAccuracy) - Random(trigData.trigShotAccuracy); tr_SpawnShot(trigData.trigShotType, wx, wy, dx, dy, trigData.trigShotSound, TargetUID); end else if (trigData.trigShotIntReload > 0) and (ShotReloadTime = 0) then ShotReloadTime := trigData.trigShotIntReload; // тиков на перезарядку пушки end; procedure tr_MakeEffect(X, Y, VX, VY: Integer; T, ST, CR, CG, CB: Byte; Silent, Send: Boolean); var FramesID: DWORD; Anim: TAnimation; begin if T = TRIGGER_EFFECT_PARTICLE then case ST of TRIGGER_EFFECT_SLIQUID: begin if (CR = 255) and (CG = 0) and (CB = 0) then g_GFX_SimpleWater(X, Y, 1, VX, VY, 1, 0, 0, 0) else if (CR = 0) and (CG = 255) and (CB = 0) then g_GFX_SimpleWater(X, Y, 1, VX, VY, 2, 0, 0, 0) else if (CR = 0) and (CG = 0) and (CB = 255) then g_GFX_SimpleWater(X, Y, 1, VX, VY, 3, 0, 0, 0) else g_GFX_SimpleWater(X, Y, 1, VX, VY, 0, 0, 0, 0); end; TRIGGER_EFFECT_LLIQUID: g_GFX_SimpleWater(X, Y, 1, VX, VY, 4, CR, CG, CB); TRIGGER_EFFECT_DLIQUID: g_GFX_SimpleWater(X, Y, 1, VX, VY, 5, CR, CG, CB); TRIGGER_EFFECT_BLOOD: g_GFX_Blood(X, Y, 1, VX, VY, 0, 0, CR, CG, CB); TRIGGER_EFFECT_SPARK: g_GFX_Spark(X, Y, 1, GetAngle2(VX, VY), 0, 0); TRIGGER_EFFECT_BUBBLE: g_GFX_Bubbles(X, Y, 1, 0, 0); end; if T = TRIGGER_EFFECT_ANIMATION then case ST of EFFECT_TELEPORT: begin if g_Frames_Get(FramesID, 'FRAMES_TELEPORT') then begin Anim := TAnimation.Create(FramesID, False, 3); if not Silent then g_Sound_PlayExAt('SOUND_GAME_TELEPORT', X, Y); g_GFX_OnceAnim(X-32, Y-32, Anim); Anim.Free(); end; if Send and g_Game_IsServer and g_Game_IsNet then MH_SEND_Effect(X, Y, Byte(not Silent), NET_GFX_TELE); end; EFFECT_RESPAWN: begin if g_Frames_Get(FramesID, 'FRAMES_ITEM_RESPAWN') then begin Anim := TAnimation.Create(FramesID, False, 4); if not Silent then g_Sound_PlayExAt('SOUND_ITEM_RESPAWNITEM', X, Y); g_GFX_OnceAnim(X-16, Y-16, Anim); Anim.Free(); end; if Send and g_Game_IsServer and g_Game_IsNet then MH_SEND_Effect(X-16, Y-16, Byte(not Silent), NET_GFX_RESPAWN); end; EFFECT_FIRE: begin if g_Frames_Get(FramesID, 'FRAMES_FIRE') then begin Anim := TAnimation.Create(FramesID, False, 4); if not Silent then g_Sound_PlayExAt('SOUND_FIRE', X, Y); g_GFX_OnceAnim(X-32, Y-128, Anim); Anim.Free(); end; if Send and g_Game_IsServer and g_Game_IsNet then MH_SEND_Effect(X-32, Y-128, Byte(not Silent), NET_GFX_FIRE); end; 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 := (trigData.trigShotAim and TRIGGER_SHOT_AIM_ALLMAP > 0) or g_Obj_Collide(X, Y, Width, Height, Obj); if Result and (trigData.trigShotAim and TRIGGER_SHOT_AIM_TRACE > 0) then Result := g_TraceVector(trigData.trigShotPos.X, trigData.trigShotPos.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; idx, k, wx, wy, xd, yd: Integer; iid: LongWord; coolDown: Boolean; pAngle: Real; FramesID: DWORD; Anim: TAnimation; UIDType: Byte; TargetUID: Word; it: PItem; mon: TMonster; function monsShotTarget (mon: TMonster): Boolean; begin result := false; // don't stop if 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 (mon: TMonster): Boolean; begin result := false; // don't stop if 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 (mon: TMonster): Boolean; begin result := false; // don't stop if 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 Exit; if not Trigger.Enabled then Exit; if (Trigger.TimeOut <> 0) and (actType <> ACTIVATE_CUSTOM) then Exit; if gLMSRespawn = LMS_RESPAWN_WARMUP then Exit; animonce := False; coolDown := (actType <> 0); with Trigger do begin case TriggerType of TRIGGER_EXIT: begin g_Sound_PlayEx('SOUND_GAME_SWITCH0'); if g_Game_IsNet then MH_SEND_Sound(X, Y, 'SOUND_GAME_SWITCH0'); gExitByTrigger := True; g_Game_ExitLevel(trigData.trigMapName); TimeOut := 18; Result := True; Exit; end; TRIGGER_TELEPORT: begin Result := tr_Teleport(ActivateUID, trigData.trigTargetPoint.X, trigData.trigTargetPoint.Y, trigData.trigTlpDir, trigData.trigsilent_teleport, trigData.trigd2d_teleport); TimeOut := 0; end; TRIGGER_OPENDOOR: begin Result := tr_OpenDoor(trigPanelID, trigData.trigNoSound, trigData.trigd2d_doors); TimeOut := 0; end; TRIGGER_CLOSEDOOR: begin Result := tr_CloseDoor(trigPanelID, trigData.trigNoSound, trigData.trigd2d_doors); TimeOut := 0; end; TRIGGER_DOOR, TRIGGER_DOOR5: begin if trigPanelID <> -1 then begin if gWalls[trigPanelID].Enabled then begin Result := tr_OpenDoor(trigPanelID, trigData.trigNoSound, trigData.trigd2d_doors); if TriggerType = TRIGGER_DOOR5 then DoorTime := 180; end else Result := tr_CloseDoor(trigPanelID, trigData.trigNoSound, trigData.trigd2d_doors); if Result then TimeOut := 18; end; end; TRIGGER_CLOSETRAP, TRIGGER_TRAP: begin tr_CloseTrap(trigPanelID, trigData.trigNoSound, trigData.trigd2d_doors); if TriggerType = TRIGGER_TRAP then begin DoorTime := 40; TimeOut := 76; end else begin DoorTime := -1; TimeOut := 0; end; Result := True; end; TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF: begin PressCount := PressCount + 1; if PressTime = -1 then PressTime := trigData.trigWait; if coolDown then TimeOut := 18 else TimeOut := 0; Result := True; end; TRIGGER_SECRET: if g_GetUIDType(ActivateUID) = UID_PLAYER then begin Enabled := False; Result := True; if gLMSRespawn = LMS_RESPAWN_NONE then begin g_Player_Get(ActivateUID).GetSecret(); Inc(gCoopSecretsFound); if g_Game_IsNet then MH_SEND_GameStats(); end; end; TRIGGER_LIFTUP: begin Result := tr_SetLift(trigPanelID, 0, trigData.trigNoSound, trigData.trigd2d_doors); TimeOut := 0; if (not trigData.trigNoSound) and Result then begin g_Sound_PlayExAt('SOUND_GAME_SWITCH0', X + (Width div 2), Y + (Height div 2)); if g_Game_IsServer and g_Game_IsNet then MH_SEND_Sound(X + (Width div 2), Y + (Height div 2), 'SOUND_GAME_SWITCH0'); end; end; TRIGGER_LIFTDOWN: begin Result := tr_SetLift(trigPanelID, 1, trigData.trigNoSound, trigData.trigd2d_doors); TimeOut := 0; if (not trigData.trigNoSound) and Result then begin g_Sound_PlayExAt('SOUND_GAME_SWITCH0', X + (Width div 2), Y + (Height div 2)); if g_Game_IsServer and g_Game_IsNet then MH_SEND_Sound(X + (Width div 2), Y + (Height div 2), 'SOUND_GAME_SWITCH0'); end; end; TRIGGER_LIFT: begin Result := tr_SetLift(trigPanelID, 3, trigData.trigNoSound, trigData.trigd2d_doors); if Result then begin TimeOut := 18; if (not trigData.trigNoSound) and Result then begin g_Sound_PlayExAt('SOUND_GAME_SWITCH0', X + (Width div 2), Y + (Height div 2)); if g_Game_IsServer and g_Game_IsNet then MH_SEND_Sound(X + (Width div 2), Y + (Height div 2), 'SOUND_GAME_SWITCH0'); end; end; end; TRIGGER_TEXTURE: begin if trigData.trigActivateOnce then begin Enabled := False; TriggerType := TRIGGER_NONE; end else if coolDown then TimeOut := 6 else TimeOut := 0; animonce := trigData.trigAnimOnce; Result := True; end; TRIGGER_SOUND: begin if Sound <> nil then begin if trigData.trigSoundSwitch and Sound.IsPlaying() then begin // Нужно выключить, если играл Sound.Stop(); SoundPlayCount := 0; Result := True; end else // (not Data.SoundSwitch) or (not Sound.IsPlaying()) if (trigData.trigPlayCount > 0) or (not Sound.IsPlaying()) then begin if trigData.trigPlayCount > 0 then SoundPlayCount := trigData.trigPlayCount else // 0 - играем бесконечно SoundPlayCount := 1; Result := True; end; if g_Game_IsNet then MH_SEND_TriggerSound(Trigger); end; end; TRIGGER_SPAWNMONSTER: if (trigData.trigMonType in [MONSTER_DEMON..MONSTER_MAN]) then begin Result := False; if (trigData.trigMonDelay > 0) and (actType <> ACTIVATE_CUSTOM) then begin AutoSpawn := not AutoSpawn; SpawnCooldown := 0; // Автоспавнер переключен - меняем текстуру Result := True; end; if ((trigData.trigMonDelay = 0) and (actType <> ACTIVATE_CUSTOM)) or ((trigData.trigMonDelay > 0) and (actType = ACTIVATE_CUSTOM)) then for k := 1 to trigData.trigMonCount do begin if (actType = ACTIVATE_CUSTOM) and (trigData.trigMonDelay > 0) then SpawnCooldown := trigData.trigMonDelay; if (trigData.trigMonMax > 0) and (SpawnedCount >= trigData.trigMonMax) then Break; mon := g_Monsters_Create(trigData.trigMonType, trigData.trigMonPos.X, trigData.trigMonPos.Y, TDirection(trigData.trigMonDir), True); Result := True; // Здоровье: if (trigData.trigMonHealth > 0) then mon.SetHealth(trigData.trigMonHealth); // Устанавливаем поведение: mon.MonsterBehaviour := trigData.trigMonBehav; mon.FNoRespawn := True; if g_Game_IsNet then MH_SEND_MonsterSpawn(mon.UID); // Идем искать цель, если надо: if trigData.trigMonActive then mon.WakeUp(); if trigData.trigMonType <> MONSTER_BARREL then Inc(gTotalMonsters); if g_Game_IsNet then begin SetLength(gMonstersSpawned, Length(gMonstersSpawned)+1); gMonstersSpawned[High(gMonstersSpawned)] := mon.UID; end; if trigData.trigMonMax > 0 then begin mon.SpawnTrigger := ID; Inc(SpawnedCount); end; case trigData.trigMonEffect of EFFECT_TELEPORT: begin if g_Frames_Get(FramesID, 'FRAMES_TELEPORT') then begin Anim := TAnimation.Create(FramesID, False, 3); g_Sound_PlayExAt('SOUND_GAME_TELEPORT', trigData.trigMonPos.X, trigData.trigMonPos.Y); 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(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 if g_Frames_Get(FramesID, 'FRAMES_ITEM_RESPAWN') then begin Anim := TAnimation.Create(FramesID, False, 4); g_Sound_PlayExAt('SOUND_ITEM_RESPAWNITEM', trigData.trigMonPos.X, trigData.trigMonPos.Y); 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(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 if g_Frames_Get(FramesID, 'FRAMES_FIRE') then begin Anim := TAnimation.Create(FramesID, False, 4); g_Sound_PlayExAt('SOUND_FIRE', trigData.trigMonPos.X, trigData.trigMonPos.Y); 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(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; end; if g_Game_IsNet then begin MH_SEND_GameStats(); MH_SEND_CoopStats(); end; if coolDown then TimeOut := 18 else TimeOut := 0; // Если активирован автоспавнером, не меняем текстуру if actType = ACTIVATE_CUSTOM then Result := False; end; TRIGGER_SPAWNITEM: if (trigData.trigItemType in [ITEM_MEDKIT_SMALL..ITEM_MAX]) then begin Result := False; if (trigData.trigItemDelay > 0) and (actType <> ACTIVATE_CUSTOM) then begin AutoSpawn := not AutoSpawn; SpawnCooldown := 0; // Автоспавнер переключен - меняем текстуру Result := True; end; if ((trigData.trigItemDelay = 0) and (actType <> ACTIVATE_CUSTOM)) or ((trigData.trigItemDelay > 0) and (actType = ACTIVATE_CUSTOM)) then if (not trigData.trigItemOnlyDM) or (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF]) then for k := 1 to trigData.trigItemCount do begin if (actType = ACTIVATE_CUSTOM) and (trigData.trigItemDelay > 0) then SpawnCooldown := trigData.trigItemDelay; if (trigData.trigItemMax > 0) and (SpawnedCount >= trigData.trigItemMax) then Break; iid := g_Items_Create(trigData.trigItemPos.X, trigData.trigItemPos.Y, trigData.trigItemType, trigData.trigItemFalls, False, True); Result := True; if trigData.trigItemMax > 0 then begin it := g_Items_ByIdx(iid); it.SpawnTrigger := ID; Inc(SpawnedCount); end; case trigData.trigItemEffect 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', trigData.trigItemPos.X, trigData.trigItemPos.Y); 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(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', trigData.trigItemPos.X, trigData.trigItemPos.Y); 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(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', trigData.trigItemPos.X, trigData.trigItemPos.Y); 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(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; if g_Game_IsNet then MH_SEND_ItemSpawn(True, iid); end; if coolDown then TimeOut := 18 else TimeOut := 0; // Если активирован автоспавнером, не меняем текстуру if actType = ACTIVATE_CUSTOM then Result := False; end; TRIGGER_MUSIC: begin // Меняем музыку, если есть на что: if (Trigger.trigData.trigMusicName <> '') then begin gMusic.SetByName(Trigger.trigData.trigMusicName); gMusic.SpecPause := True; gMusic.Play(); end; if Trigger.trigData.trigMusicAction = 1 then begin // Включить if gMusic.SpecPause then // Была на паузе => играть gMusic.SpecPause := False else // Играла => сначала gMusic.SetPosition(0); end else // Выключить begin // Пауза: gMusic.SpecPause := True; end; if coolDown then TimeOut := 36 else TimeOut := 0; Result := True; if g_Game_IsNet then MH_SEND_TriggerMusic; end; TRIGGER_PUSH: begin pAngle := -DegToRad(trigData.trigPushAngle); Result := tr_Push(ActivateUID, Floor(Cos(pAngle)*trigData.trigPushForce), Floor(Sin(pAngle)*trigData.trigPushForce), trigData.trigResetVel); TimeOut := 0; end; TRIGGER_SCORE: begin Result := False; // Прибавить или отнять очко if (trigData.trigScoreAction in [0..1]) and (trigData.trigScoreCount > 0) then begin // Своей или чужой команде if (trigData.trigScoreTeam in [0..1]) and (g_GetUIDType(ActivateUID) = UID_PLAYER) then begin p := g_Player_Get(ActivateUID); if ((trigData.trigScoreAction = 0) and (trigData.trigScoreTeam = 0) and (p.Team = TEAM_RED)) or ((trigData.trigScoreAction = 0) and (trigData.trigScoreTeam = 1) and (p.Team = TEAM_BLUE)) then begin Inc(gTeamStat[TEAM_RED].Goals, trigData.trigScoreCount); // Red Scores if trigData.trigScoreCon then if trigData.trigScoreTeam = 0 then begin g_Console_Add(Format(_lc[I_PLAYER_SCORE_ADD_OWN], [p.Name, trigData.trigScoreCount, _lc[I_PLAYER_SCORE_TO_RED]]), True); if g_Game_IsServer and g_Game_IsNet then MH_SEND_GameEvent(NET_EV_SCORE, p.UID or (trigData.trigScoreCount shl 16), '+r'); end else begin g_Console_Add(Format(_lc[I_PLAYER_SCORE_ADD_ENEMY], [p.Name, trigData.trigScoreCount, _lc[I_PLAYER_SCORE_TO_RED]]), True); if g_Game_IsServer and g_Game_IsNet then MH_SEND_GameEvent(NET_EV_SCORE, p.UID or (trigData.trigScoreCount shl 16), '+re'); end; if trigData.trigScoreMsg then begin g_Game_Message(Format(_lc[I_MESSAGE_SCORE_ADD], [AnsiUpperCase(_lc[I_GAME_TEAM_RED])]), 108); if g_Game_IsServer and g_Game_IsNet then MH_SEND_GameEvent(NET_EV_SCORE_MSG, TEAM_RED); end; end; if ((trigData.trigScoreAction = 1) and (trigData.trigScoreTeam = 0) and (p.Team = TEAM_RED)) or ((trigData.trigScoreAction = 1) and (trigData.trigScoreTeam = 1) and (p.Team = TEAM_BLUE)) then begin Dec(gTeamStat[TEAM_RED].Goals, trigData.trigScoreCount); // Red Fouls if trigData.trigScoreCon then if trigData.trigScoreTeam = 0 then begin g_Console_Add(Format(_lc[I_PLAYER_SCORE_SUB_OWN], [p.Name, trigData.trigScoreCount, _lc[I_PLAYER_SCORE_TO_RED]]), True); if g_Game_IsServer and g_Game_IsNet then MH_SEND_GameEvent(NET_EV_SCORE, p.UID or (trigData.trigScoreCount shl 16), '-r'); end else begin g_Console_Add(Format(_lc[I_PLAYER_SCORE_SUB_ENEMY], [p.Name, trigData.trigScoreCount, _lc[I_PLAYER_SCORE_TO_RED]]), True); if g_Game_IsServer and g_Game_IsNet then MH_SEND_GameEvent(NET_EV_SCORE, p.UID or (trigData.trigScoreCount shl 16), '-re'); end; if trigData.trigScoreMsg then begin g_Game_Message(Format(_lc[I_MESSAGE_SCORE_SUB], [AnsiUpperCase(_lc[I_GAME_TEAM_RED])]), 108); if g_Game_IsServer and g_Game_IsNet then MH_SEND_GameEvent(NET_EV_SCORE_MSG, -TEAM_RED); end; end; if ((trigData.trigScoreAction = 0) and (trigData.trigScoreTeam = 0) and (p.Team = TEAM_BLUE)) or ((trigData.trigScoreAction = 0) and (trigData.trigScoreTeam = 1) and (p.Team = TEAM_RED)) then begin Inc(gTeamStat[TEAM_BLUE].Goals, trigData.trigScoreCount); // Blue Scores if trigData.trigScoreCon then if trigData.trigScoreTeam = 0 then begin g_Console_Add(Format(_lc[I_PLAYER_SCORE_ADD_OWN], [p.Name, trigData.trigScoreCount, _lc[I_PLAYER_SCORE_TO_BLUE]]), True); if g_Game_IsServer and g_Game_IsNet then MH_SEND_GameEvent(NET_EV_SCORE, p.UID or (trigData.trigScoreCount shl 16), '+b'); end else begin g_Console_Add(Format(_lc[I_PLAYER_SCORE_ADD_ENEMY], [p.Name, trigData.trigScoreCount, _lc[I_PLAYER_SCORE_TO_BLUE]]), True); if g_Game_IsServer and g_Game_IsNet then MH_SEND_GameEvent(NET_EV_SCORE, p.UID or (trigData.trigScoreCount shl 16), '+be'); end; if trigData.trigScoreMsg then begin g_Game_Message(Format(_lc[I_MESSAGE_SCORE_ADD], [AnsiUpperCase(_lc[I_GAME_TEAM_BLUE])]), 108); if g_Game_IsServer and g_Game_IsNet then MH_SEND_GameEvent(NET_EV_SCORE_MSG, TEAM_BLUE); end; end; if ((trigData.trigScoreAction = 1) and (trigData.trigScoreTeam = 0) and (p.Team = TEAM_BLUE)) or ((trigData.trigScoreAction = 1) and (trigData.trigScoreTeam = 1) and (p.Team = TEAM_RED)) then begin Dec(gTeamStat[TEAM_BLUE].Goals, trigData.trigScoreCount); // Blue Fouls if trigData.trigScoreCon then if trigData.trigScoreTeam = 0 then begin g_Console_Add(Format(_lc[I_PLAYER_SCORE_SUB_OWN], [p.Name, trigData.trigScoreCount, _lc[I_PLAYER_SCORE_TO_BLUE]]), True); if g_Game_IsServer and g_Game_IsNet then MH_SEND_GameEvent(NET_EV_SCORE, p.UID or (trigData.trigScoreCount shl 16), '-b'); end else begin g_Console_Add(Format(_lc[I_PLAYER_SCORE_SUB_ENEMY], [p.Name, trigData.trigScoreCount, _lc[I_PLAYER_SCORE_TO_BLUE]]), True); if g_Game_IsServer and g_Game_IsNet then MH_SEND_GameEvent(NET_EV_SCORE, p.UID or (trigData.trigScoreCount shl 16), '-be'); end; if trigData.trigScoreMsg then begin g_Game_Message(Format(_lc[I_MESSAGE_SCORE_SUB], [AnsiUpperCase(_lc[I_GAME_TEAM_BLUE])]), 108); if g_Game_IsServer and g_Game_IsNet then MH_SEND_GameEvent(NET_EV_SCORE_MSG, -TEAM_BLUE); end; end; Result := (p.Team = TEAM_RED) or (p.Team = TEAM_BLUE); end; // Какой-то конкретной команде if trigData.trigScoreTeam in [2..3] then begin if (trigData.trigScoreAction = 0) and (trigData.trigScoreTeam = 2) then begin Inc(gTeamStat[TEAM_RED].Goals, trigData.trigScoreCount); // Red Scores if trigData.trigScoreCon then begin g_Console_Add(Format(_lc[I_PLAYER_SCORE_ADD_TEAM], [_lc[I_PLAYER_SCORE_RED], trigData.trigScoreCount]), True); if g_Game_IsServer and g_Game_IsNet then MH_SEND_GameEvent(NET_EV_SCORE, trigData.trigScoreCount shl 16, '+tr'); end; if trigData.trigScoreMsg then begin g_Game_Message(Format(_lc[I_MESSAGE_SCORE_ADD], [AnsiUpperCase(_lc[I_GAME_TEAM_RED])]), 108); if g_Game_IsServer and g_Game_IsNet then MH_SEND_GameEvent(NET_EV_SCORE_MSG, TEAM_RED); end; end; if (trigData.trigScoreAction = 1) and (trigData.trigScoreTeam = 2) then begin Dec(gTeamStat[TEAM_RED].Goals, trigData.trigScoreCount); // Red Fouls if trigData.trigScoreCon then begin g_Console_Add(Format(_lc[I_PLAYER_SCORE_SUB_TEAM], [_lc[I_PLAYER_SCORE_RED], trigData.trigScoreCount]), True); if g_Game_IsServer and g_Game_IsNet then MH_SEND_GameEvent(NET_EV_SCORE, trigData.trigScoreCount shl 16, '-tr'); end; if trigData.trigScoreMsg then begin g_Game_Message(Format(_lc[I_MESSAGE_SCORE_SUB], [AnsiUpperCase(_lc[I_GAME_TEAM_RED])]), 108); if g_Game_IsServer and g_Game_IsNet then MH_SEND_GameEvent(NET_EV_SCORE_MSG, -TEAM_RED); end; end; if (trigData.trigScoreAction = 0) and (trigData.trigScoreTeam = 3) then begin Inc(gTeamStat[TEAM_BLUE].Goals, trigData.trigScoreCount); // Blue Scores if trigData.trigScoreCon then begin g_Console_Add(Format(_lc[I_PLAYER_SCORE_ADD_TEAM], [_lc[I_PLAYER_SCORE_BLUE], trigData.trigScoreCount]), True); if g_Game_IsServer and g_Game_IsNet then MH_SEND_GameEvent(NET_EV_SCORE, trigData.trigScoreCount shl 16, '+tb'); end; if trigData.trigScoreMsg then begin g_Game_Message(Format(_lc[I_MESSAGE_SCORE_ADD], [AnsiUpperCase(_lc[I_GAME_TEAM_BLUE])]), 108); if g_Game_IsServer and g_Game_IsNet then MH_SEND_GameEvent(NET_EV_SCORE_MSG, TEAM_BLUE); end; end; if (trigData.trigScoreAction = 1) and (trigData.trigScoreTeam = 3) then begin Dec(gTeamStat[TEAM_BLUE].Goals, trigData.trigScoreCount); // Blue Fouls if trigData.trigScoreCon then begin g_Console_Add(Format(_lc[I_PLAYER_SCORE_SUB_TEAM], [_lc[I_PLAYER_SCORE_BLUE], trigData.trigScoreCount]), True); if g_Game_IsServer and g_Game_IsNet then MH_SEND_GameEvent(NET_EV_SCORE, trigData.trigScoreCount shl 16, '-tb'); end; if trigData.trigScoreMsg then begin g_Game_Message(Format(_lc[I_MESSAGE_SCORE_SUB], [AnsiUpperCase(_lc[I_GAME_TEAM_BLUE])]), 108); if g_Game_IsServer and g_Game_IsNet then MH_SEND_GameEvent(NET_EV_SCORE_MSG, -TEAM_BLUE); end; end; Result := True; end; end; // Выигрыш if (trigData.trigScoreAction = 2) and (gGameSettings.GoalLimit > 0) then begin // Своей или чужой команды if (trigData.trigScoreTeam in [0..1]) and (g_GetUIDType(ActivateUID) = UID_PLAYER) then begin p := g_Player_Get(ActivateUID); if ((trigData.trigScoreTeam = 0) and (p.Team = TEAM_RED)) // Red Wins or ((trigData.trigScoreTeam = 1) and (p.Team = TEAM_BLUE)) then if gTeamStat[TEAM_RED].Goals < SmallInt(gGameSettings.GoalLimit) then begin gTeamStat[TEAM_RED].Goals := gGameSettings.GoalLimit; if trigData.trigScoreCon then if trigData.trigScoreTeam = 0 then begin g_Console_Add(Format(_lc[I_PLAYER_SCORE_WIN_OWN], [p.Name, _lc[I_PLAYER_SCORE_TO_RED]]), True); if g_Game_IsServer and g_Game_IsNet then MH_SEND_GameEvent(NET_EV_SCORE, p.UID, 'wr'); end else begin g_Console_Add(Format(_lc[I_PLAYER_SCORE_WIN_ENEMY], [p.Name, _lc[I_PLAYER_SCORE_TO_RED]]), True); if g_Game_IsServer and g_Game_IsNet then MH_SEND_GameEvent(NET_EV_SCORE, p.UID, 'wre'); end; Result := True; end; if ((trigData.trigScoreTeam = 0) and (p.Team = TEAM_BLUE)) // Blue Wins or ((trigData.trigScoreTeam = 1) and (p.Team = TEAM_RED)) then if gTeamStat[TEAM_BLUE].Goals < SmallInt(gGameSettings.GoalLimit) then begin gTeamStat[TEAM_BLUE].Goals := gGameSettings.GoalLimit; if trigData.trigScoreCon then if trigData.trigScoreTeam = 0 then begin g_Console_Add(Format(_lc[I_PLAYER_SCORE_WIN_OWN], [p.Name, _lc[I_PLAYER_SCORE_TO_BLUE]]), True); if g_Game_IsServer and g_Game_IsNet then MH_SEND_GameEvent(NET_EV_SCORE, p.UID, 'wb'); end else begin g_Console_Add(Format(_lc[I_PLAYER_SCORE_WIN_ENEMY], [p.Name, _lc[I_PLAYER_SCORE_TO_BLUE]]), True); if g_Game_IsServer and g_Game_IsNet then MH_SEND_GameEvent(NET_EV_SCORE, p.UID, 'wbe'); end; Result := True; end; end; // Какой-то конкретной команды if trigData.trigScoreTeam in [2..3] then begin if trigData.trigScoreTeam = 2 then // Red Wins if gTeamStat[TEAM_RED].Goals < SmallInt(gGameSettings.GoalLimit) then begin gTeamStat[TEAM_RED].Goals := gGameSettings.GoalLimit; Result := True; end; if trigData.trigScoreTeam = 3 then // Blue Wins if gTeamStat[TEAM_BLUE].Goals < SmallInt(gGameSettings.GoalLimit) then begin gTeamStat[TEAM_BLUE].Goals := gGameSettings.GoalLimit; Result := True; end; end; end; // Проигрыш if (trigData.trigScoreAction = 3) and (gGameSettings.GoalLimit > 0) then begin // Своей или чужой команды if (trigData.trigScoreTeam in [0..1]) and (g_GetUIDType(ActivateUID) = UID_PLAYER) then begin p := g_Player_Get(ActivateUID); if ((trigData.trigScoreTeam = 0) and (p.Team = TEAM_BLUE)) // Red Wins or ((trigData.trigScoreTeam = 1) and (p.Team = TEAM_RED)) then if gTeamStat[TEAM_RED].Goals < SmallInt(gGameSettings.GoalLimit) then begin gTeamStat[TEAM_RED].Goals := gGameSettings.GoalLimit; if trigData.trigScoreCon then if trigData.trigScoreTeam = 0 then begin g_Console_Add(Format(_lc[I_PLAYER_SCORE_WIN_ENEMY], [p.Name, _lc[I_PLAYER_SCORE_TO_RED]]), True); if g_Game_IsServer and g_Game_IsNet then MH_SEND_GameEvent(NET_EV_SCORE, p.UID, 'wre'); end else begin g_Console_Add(Format(_lc[I_PLAYER_SCORE_WIN_OWN], [p.Name, _lc[I_PLAYER_SCORE_TO_RED]]), True); if g_Game_IsServer and g_Game_IsNet then MH_SEND_GameEvent(NET_EV_SCORE, p.UID, 'wr'); end; Result := True; end; if ((trigData.trigScoreTeam = 0) and (p.Team = TEAM_RED)) // Blue Wins or ((trigData.trigScoreTeam = 1) and (p.Team = TEAM_BLUE)) then if gTeamStat[TEAM_BLUE].Goals < SmallInt(gGameSettings.GoalLimit) then begin gTeamStat[TEAM_BLUE].Goals := gGameSettings.GoalLimit; if trigData.trigScoreCon then if trigData.trigScoreTeam = 0 then begin g_Console_Add(Format(_lc[I_PLAYER_SCORE_WIN_ENEMY], [p.Name, _lc[I_PLAYER_SCORE_TO_BLUE]]), True); if g_Game_IsServer and g_Game_IsNet then MH_SEND_GameEvent(NET_EV_SCORE, p.UID, 'wbe'); end else begin g_Console_Add(Format(_lc[I_PLAYER_SCORE_WIN_OWN], [p.Name, _lc[I_PLAYER_SCORE_TO_BLUE]]), True); if g_Game_IsServer and g_Game_IsNet then MH_SEND_GameEvent(NET_EV_SCORE, p.UID, 'wb'); end; Result := True; end; end; // Какой-то конкретной команды if trigData.trigScoreTeam in [2..3] then begin if trigData.trigScoreTeam = 3 then // Red Wins if gTeamStat[TEAM_RED].Goals < SmallInt(gGameSettings.GoalLimit) then begin gTeamStat[TEAM_RED].Goals := gGameSettings.GoalLimit; Result := True; end; if trigData.trigScoreTeam = 2 then // Blue Wins if gTeamStat[TEAM_BLUE].Goals < SmallInt(gGameSettings.GoalLimit) then begin gTeamStat[TEAM_BLUE].Goals := gGameSettings.GoalLimit; Result := True; end; end; end; if Result then begin if coolDown then TimeOut := 18 else TimeOut := 0; if g_Game_IsServer and g_Game_IsNet then MH_SEND_GameStats; end; end; TRIGGER_MESSAGE: begin Result := tr_Message(trigData.trigMessageKind, trigData.trigMessageText, trigData.trigMessageSendTo, trigData.trigMessageTime, ActivateUID); TimeOut := 18; end; TRIGGER_DAMAGE, TRIGGER_HEALTH: begin Result := False; UIDType := g_GetUIDType(ActivateUID); if (UIDType = UID_PLAYER) or (UIDType = UID_MONSTER) then begin Result := True; k := -1; if coolDown then begin // Вспоминаем, активировал ли он меня раньше for idx := 0 to High(Activators) do if Activators[idx].UID = ActivateUID then begin k := idx; Break; end; if k = -1 then begin // Видим его впервые // Запоминаем его SetLength(Activators, Length(Activators) + 1); k := High(Activators); Activators[k].UID := ActivateUID; end else begin // Уже видели его // Если интервал отключён, но он всё ещё в зоне поражения, даём ему время if (trigData.trigDamageInterval = 0) and (Activators[k].TimeOut > 0) then Activators[k].TimeOut := 65535; // Таймаут прошёл - работаем Result := Activators[k].TimeOut = 0; end; end; if Result then begin case UIDType of UID_PLAYER: begin p := g_Player_Get(ActivateUID); if p = nil then Exit; // Наносим урон игроку if (TriggerType = TRIGGER_DAMAGE) and (trigData.trigDamageValue > 0) then p.Damage(trigData.trigDamageValue, 0, 0, 0, HIT_SOME); // Лечим игрока if (TriggerType = TRIGGER_HEALTH) and (trigData.trigHealValue > 0) then if p.Heal(trigData.trigHealValue, not trigData.trigHealMax) and (not trigData.trigHealSilent) then begin g_Sound_PlayExAt('SOUND_ITEM_GETITEM', p.Obj.X, p.Obj.Y); if g_Game_IsServer and g_Game_IsNet then MH_SEND_Sound(p.Obj.X, p.Obj.Y, 'SOUND_ITEM_GETITEM'); end; end; UID_MONSTER: begin m := g_Monsters_ByUID(ActivateUID); if m = nil then Exit; // Наносим урон монстру if (TriggerType = TRIGGER_DAMAGE) and (trigData.trigDamageValue > 0) then m.Damage(trigData.trigDamageValue, 0, 0, 0, HIT_SOME); // Лечим монстра if (TriggerType = TRIGGER_HEALTH) and (trigData.trigHealValue > 0) then if m.Heal(trigData.trigHealValue) and (not trigData.trigHealSilent) then begin g_Sound_PlayExAt('SOUND_ITEM_GETITEM', m.Obj.X, m.Obj.Y); if g_Game_IsServer and g_Game_IsNet then MH_SEND_Sound(m.Obj.X, m.Obj.Y, 'SOUND_ITEM_GETITEM'); end; end; end; // Назначаем время следующего воздействия if TriggerType = TRIGGER_DAMAGE then idx := trigData.trigDamageInterval else idx := trigData.trigHealInterval; if coolDown then if idx > 0 then Activators[k].TimeOut := idx else Activators[k].TimeOut := 65535; end; end; TimeOut := 0; end; TRIGGER_SHOT: begin if ShotSightTime > 0 then Exit; // put this at the beginning so it doesn't trigger itself TimeOut := trigData.trigShotWait + 1; wx := trigData.trigShotPos.X; wy := trigData.trigShotPos.Y; pAngle := -DegToRad(trigData.trigShotAngle); xd := wx + Round(Cos(pAngle) * 32.0); yd := wy + Round(Sin(pAngle) * 32.0); TargetUID := 0; case trigData.trigShotTarget of TRIGGER_SHOT_TARGET_MON: // monsters //TODO: accelerate this! g_Mons_ForEachAlive(monsShotTarget); TRIGGER_SHOT_TARGET_PLR: // players if gPlayers <> nil 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[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 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[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 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[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 //TODO: accelerate this! g_Mons_ForEachAlive(monsShotTargetMonPlr); if (TargetUID = 0) and (gPlayers <> nil) 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[idx].GameX + PLAYER_RECT_CX; yd := gPlayers[idx].GameY + PLAYER_RECT_CY; TargetUID := gPlayers[idx].UID; break; end; end; TRIGGER_SHOT_TARGET_PLRMON: // players then monsters begin if gPlayers <> nil 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[idx].GameX + PLAYER_RECT_CX; yd := gPlayers[idx].GameY + PLAYER_RECT_CY; TargetUID := gPlayers[idx].UID; break; end; if TargetUID = 0 then begin //TODO: accelerate this! g_Mons_ForEachAlive(monShotTargetPlrMon); end; end; else begin if (trigData.trigShotTarget <> TRIGGER_SHOT_TARGET_NONE) or (trigData.trigShotType <> TRIGGER_SHOT_REV) then TargetUID := ActivateUID; end; end; if (trigData.trigShotTarget = TRIGGER_SHOT_TARGET_NONE) or (TargetUID > 0) or ((trigData.trigShotTarget > TRIGGER_SHOT_TARGET_NONE) and (TargetUID = 0)) then begin Result := True; if (trigData.trigShotIntSight = 0) or (trigData.trigShotTarget = TRIGGER_SHOT_TARGET_NONE) or (TargetUID = ShotSightTarget) then MakeShot(Trigger, wx, wy, xd, yd, TargetUID) else begin ShotSightTime := trigData.trigShotIntSight; ShotSightTargetN := TargetUID; if trigData.trigShotType = TRIGGER_SHOT_BFG then begin g_Sound_PlayExAt('SOUND_WEAPON_STARTFIREBFG', wx, wy); if g_Game_IsNet and g_Game_IsServer then MH_SEND_Sound(wx, wy, 'SOUND_WEAPON_STARTFIREBFG'); end; end; end; end; TRIGGER_EFFECT: begin idx := trigData.trigFXCount; while idx > 0 do begin case trigData.trigFXPos of TRIGGER_EFFECT_POS_CENTER: begin wx := X + Width div 2; wy := Y + Height div 2; end; TRIGGER_EFFECT_POS_AREA: begin wx := X + Random(Width); wy := Y + Random(Height); end; else begin wx := X + Width div 2; wy := Y + Height div 2; end; end; xd := trigData.trigFXVelX; yd := trigData.trigFXVelY; if trigData.trigFXSpreadL > 0 then xd := xd - Random(trigData.trigFXSpreadL + 1); if trigData.trigFXSpreadR > 0 then xd := xd + Random(trigData.trigFXSpreadR + 1); if trigData.trigFXSpreadU > 0 then yd := yd - Random(trigData.trigFXSpreadU + 1); if trigData.trigFXSpreadD > 0 then yd := yd + Random(trigData.trigFXSpreadD + 1); tr_MakeEffect(wx, wy, xd, yd, trigData.trigFXType, trigData.trigFXSubType, trigData.trigFXColorR, trigData.trigFXColorG, trigData.trigFXColorB, True, False); Dec(idx); end; TimeOut := trigData.trigFXWait; end; end; end; if Result and (Trigger.TexturePanel <> -1) then g_Map_SwitchTexture(Trigger.TexturePanelType, Trigger.TexturePanel, IfThen(animonce, 2, 1)); end; function g_Triggers_Create(Trigger: TTrigger): DWORD; var find_id: DWORD; fn, mapw: String; begin // Не создавать выход, если игра без выхода: if (Trigger.TriggerType = TRIGGER_EXIT) and (not LongBool(gGameSettings.Options and GAME_OPTION_ALLOWEXIT)) then Trigger.TriggerType := TRIGGER_NONE; // Если монстры запрещены, отменяем триггер: if (Trigger.TriggerType = TRIGGER_SPAWNMONSTER) and (not LongBool(gGameSettings.Options and GAME_OPTION_MONSTERS)) and (gGameSettings.GameType <> GT_SINGLE) then Trigger.TriggerType := TRIGGER_NONE; // Считаем количество секретов на карте: if Trigger.TriggerType = TRIGGER_SECRET then gSecretsCount := gSecretsCount + 1; find_id := FindTrigger(); gTriggers[find_id] := Trigger; { writeln('trigger #', find_id, ': pos=(', Trigger.x, ',', Trigger.y, ')-(', Trigger.width, 'x', Trigger.height, ')', '; TexturePanel=', Trigger.TexturePanel, '; TexturePanelType=', Trigger.TexturePanelType, '; ShotPanelType=', Trigger.ShotPanelType, '; TriggerType=', Trigger.TriggerType, '; ActivateType=', Trigger.ActivateType, '; Keys=', Trigger.Keys, '; trigPanelId=', Trigger.trigPanelId, '; trigShotPanelId=', Trigger.trigShotPanelId ); } with gTriggers[find_id] do begin ID := find_id; // if this type of trigger exists both on the client and on the server // use an uniform numeration if Trigger.TriggerType = TRIGGER_SOUND then begin Inc(gTriggerClientID); ClientID := gTriggerClientID; end else ClientID := 0; TimeOut := 0; ActivateUID := 0; PlayerCollide := False; DoorTime := -1; PressTime := -1; PressCount := 0; SoundPlayCount := 0; Sound := nil; AutoSpawn := False; SpawnCooldown := 0; SpawnedCount := 0; end; // Загружаем звук, если это триггер "Звук": if (Trigger.TriggerType = TRIGGER_SOUND) and (Trigger.trigData.trigSoundName <> '') then begin // Еще нет такого звука: if not g_Sound_Exists(Trigger.trigData.trigSoundName) then begin fn := g_ExtractWadName(Trigger.trigData.trigSoundName); if fn = '' then begin // Звук в файле с картой mapw := g_ExtractWadName(gMapInfo.Map); fn := mapw+':'+g_ExtractFilePathName(Trigger.trigData.trigSoundName); end else // Звук в отдельном файле fn := GameDir + '/wads/' + Trigger.trigData.trigSoundName; if not g_Sound_CreateWADEx(Trigger.trigData.trigSoundName, fn) then g_FatalError(Format(_lc[I_GAME_ERROR_TR_SOUND], [fn, Trigger.trigData.trigSoundName])); end; // Создаем объект звука: with gTriggers[find_id] do begin Sound := TPlayableSound.Create(); if not Sound.SetByName(Trigger.trigData.trigSoundName) then begin Sound.Free(); Sound := nil; end; end; end; // Загружаем музыку, если это триггер "Музыка": if (Trigger.TriggerType = TRIGGER_MUSIC) and (Trigger.trigData.trigMusicName <> '') then begin // Еще нет такой музыки: if not g_Sound_Exists(Trigger.trigData.trigMusicName) then begin fn := g_ExtractWadName(Trigger.trigData.trigMusicName); if fn = '' then begin // Музыка в файле с картой mapw := g_ExtractWadName(gMapInfo.Map); fn := mapw+':'+g_ExtractFilePathName(Trigger.trigData.trigMusicName); end else // Музыка в файле с картой fn := GameDir+'/wads/'+Trigger.trigData.trigMusicName; if not g_Sound_CreateWADEx(Trigger.trigData.trigMusicName, fn, True) then g_FatalError(Format(_lc[I_GAME_ERROR_TR_SOUND], [fn, Trigger.trigData.trigMusicName])); end; end; // Загружаем данные триггера "Турель": if Trigger.TriggerType = TRIGGER_SHOT then with gTriggers[find_id] do begin ShotPanelTime := 0; ShotSightTime := 0; ShotSightTimeout := 0; ShotSightTarget := 0; ShotSightTargetN := 0; ShotAmmoCount := Trigger.trigData.trigShotAmmo; ShotReloadTime := 0; end; Result := find_id; end; // sorry; grid doesn't support recursive queries, so we have to do this type TSimpleMonsterList = specialize TSimpleList; var tgMonsList: TSimpleMonsterList = nil; procedure g_Triggers_Update(); var a, b, i: Integer; Affected: array of Integer; function monsNear (mon: TMonster): Boolean; begin result := false; // don't stop { gTriggers[a].ActivateUID := mon.UID; ActivateTrigger(gTriggers[a], ACTIVATE_MONSTERCOLLIDE); } tgMonsList.append(mon); end; var mon: TMonster; begin if (tgMonsList = nil) then tgMonsList := TSimpleMonsterList.Create(); if gTriggers = nil then Exit; SetLength(Affected, 0); for a := 0 to High(gTriggers) do with gTriggers[a] do // Есть триггер: if TriggerType <> TRIGGER_NONE then begin // Уменьшаем время до закрытия двери (открытия ловушки): if DoorTime > 0 then DoorTime := DoorTime - 1; // Уменьшаем время ожидания после нажатия: if PressTime > 0 then PressTime := PressTime - 1; // Проверяем игроков и монстров, которых ранее запомнили: if (TriggerType = TRIGGER_DAMAGE) or (TriggerType = TRIGGER_HEALTH) then for b := 0 to High(Activators) do begin // Уменьшаем время до повторного воздействия: if Activators[b].TimeOut > 0 then Dec(Activators[b].TimeOut) else Continue; // Считаем, что объект покинул зону действия триггера if (trigData.trigDamageInterval = 0) and (Activators[b].TimeOut < 65530) then Activators[b].TimeOut := 0; end; // Обрабатываем спавнеры: if Enabled and AutoSpawn then if SpawnCooldown = 0 then begin // Если пришло время, спавним монстра: if (TriggerType = TRIGGER_SPAWNMONSTER) and (trigData.trigMonDelay > 0) then begin ActivateUID := 0; ActivateTrigger(gTriggers[a], ACTIVATE_CUSTOM); end; // Если пришло время, спавним предмет: if (TriggerType = TRIGGER_SPAWNITEM) and (trigData.trigItemDelay > 0) then begin ActivateUID := 0; ActivateTrigger(gTriggers[a], ACTIVATE_CUSTOM); end; end else // Уменьшаем время ожидания: Dec(SpawnCooldown); // Обрабатываем события триггера "Турель": if TriggerType = TRIGGER_SHOT then begin if ShotPanelTime > 0 then begin Dec(ShotPanelTime); if ShotPanelTime = 0 then g_Map_SwitchTexture(ShotPanelType, trigShotPanelID); end; if ShotSightTime > 0 then begin Dec(ShotSightTime); if ShotSightTime = 0 then ShotSightTarget := ShotSightTargetN; end; if ShotSightTimeout > 0 then begin Dec(ShotSightTimeout); if ShotSightTimeout = 0 then ShotSightTarget := 0; end; if ShotReloadTime > 0 then begin Dec(ShotReloadTime); if ShotReloadTime = 0 then ShotAmmoCount := trigData.trigShotAmmo; end; end; // Триггер "Звук" уже отыграл, если нужно еще - перезапускаем: if Enabled and (TriggerType = TRIGGER_SOUND) and (Sound <> nil) then if (SoundPlayCount > 0) and (not Sound.IsPlaying()) then begin if trigData.trigPlayCount > 0 then // Если 0 - играем звук бесконечно SoundPlayCount := SoundPlayCount - 1; if trigData.trigLocal then Sound.PlayVolumeAt(X+(Width div 2), Y+(Height div 2), trigData.trigVolume/255.0) else Sound.PlayPanVolume((trigData.trigPan-127.0)/128.0, trigData.trigVolume/255.0); if Sound.IsPlaying() and g_Game_IsNet and g_Game_IsServer then MH_SEND_TriggerSound(gTriggers[a]); end; // Триггер "Ловушка" - пора открывать: if (TriggerType = TRIGGER_TRAP) and (DoorTime = 0) and (trigPanelID <> -1) then begin tr_OpenDoor(trigPanelID, trigData.trigNoSound, trigData.trigd2d_doors); DoorTime := -1; end; // Триггер "Дверь 5 сек" - пора закрывать: if (TriggerType = TRIGGER_DOOR5) and (DoorTime = 0) and (trigPanelID <> -1) then begin // Уже закрыта: if gWalls[trigPanelID].Enabled then DoorTime := -1 else // Пока открыта - закрываем if tr_CloseDoor(trigPanelID, trigData.trigNoSound, trigData.trigd2d_doors) then DoorTime := -1; end; // Триггер - расширитель или переключатель, и прошла задержка, и нажали нужное число раз: if (TriggerType in [TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF]) and (PressTime = 0) and (PressCount >= trigData.trigCount) then begin // Сбрасываем задержку активации: PressTime := -1; // Сбрасываем счетчик нажатий: if trigData.trigCount > 0 then PressCount := PressCount - trigData.trigCount else PressCount := 0; // Определяем изменяемые им триггеры: for b := 0 to High(gTriggers) do if g_Collide(trigData.trigtX, trigData.trigtY, trigData.trigtWidth, trigData.trigtHeight, gTriggers[b].X, gTriggers[b].Y, gTriggers[b].Width, gTriggers[b].Height) and ((b <> a) or (trigData.trigWait > 0)) then begin // Can be self-activated, if there is Data.Wait if (not trigData.trigExtRandom) or gTriggers[b].Enabled then begin SetLength(Affected, Length(Affected) + 1); Affected[High(Affected)] := b; end; end; // Выбираем один из триггеров для расширителя, если включен рандом: if (TriggerType = TRIGGER_PRESS) and trigData.trigExtRandom then begin if (Length(Affected) > 0) then begin b := Affected[Random(Length(Affected))]; gTriggers[b].ActivateUID := gTriggers[a].ActivateUID; ActivateTrigger(gTriggers[b], 0); end; end else // В противном случае работаем как обычно: for i := 0 to High(Affected) do begin b := Affected[i]; case TriggerType of TRIGGER_PRESS: begin gTriggers[b].ActivateUID := gTriggers[a].ActivateUID; ActivateTrigger(gTriggers[b], 0); end; TRIGGER_ON: begin gTriggers[b].Enabled := True; end; TRIGGER_OFF: begin gTriggers[b].Enabled := False; gTriggers[b].TimeOut := 0; if gTriggers[b].AutoSpawn then begin gTriggers[b].AutoSpawn := False; gTriggers[b].SpawnCooldown := 0; end; end; TRIGGER_ONOFF: begin gTriggers[b].Enabled := not gTriggers[b].Enabled; if not gTriggers[b].Enabled then begin gTriggers[b].TimeOut := 0; if gTriggers[b].AutoSpawn then begin gTriggers[b].AutoSpawn := False; gTriggers[b].SpawnCooldown := 0; end; end; end; end; end; SetLength(Affected, 0); end; // Уменьшаем время до возможности повторной активации: if TimeOut > 0 then begin TimeOut := TimeOut - 1; Continue; // Чтобы не потерять 1 единицу задержки end; // Ниже идут типы активации, если триггер отключён - идём дальше if not Enabled then Continue; // "Игрок близко": if ByteBool(ActivateType and ACTIVATE_PLAYERCOLLIDE) and (TimeOut = 0) then if gPlayers <> nil then for b := 0 to High(gPlayers) do if gPlayers[b] <> nil then with gPlayers[b] do // Жив, есть нужные ключи и он рядом: if Live and ((gTriggers[a].Keys and GetKeys) = gTriggers[a].Keys) and Collide(X, Y, Width, Height) then begin gTriggers[a].ActivateUID := UID; if (gTriggers[a].TriggerType in [TRIGGER_SOUND, TRIGGER_MUSIC]) and PlayerCollide then { Don't activate sound/music again if player is here } else ActivateTrigger(gTriggers[a], ACTIVATE_PLAYERCOLLIDE); end; { TODO 5 : активация монстрами триггеров с ключами } if ByteBool(ActivateType and ACTIVATE_MONSTERCOLLIDE) and ByteBool(ActivateType and ACTIVATE_NOMONSTER) and (TimeOut = 0) and (Keys = 0) then begin // Если "Монстр близко" и "Монстров нет", // запускаем триггер на старте карты и снимаем оба флага ActivateType := ActivateType and not (ACTIVATE_MONSTERCOLLIDE or ACTIVATE_NOMONSTER); gTriggers[a].ActivateUID := 0; ActivateTrigger(gTriggers[a], 0); end else begin // "Монстр близко" if ByteBool(ActivateType and ACTIVATE_MONSTERCOLLIDE) and (TimeOut = 0) and (Keys = 0) then // Если не нужны ключи begin //g_Mons_ForEach(monsNear); //Alive?! tgMonsList.reset(); g_Mons_ForEachAt(gTriggers[a].X, gTriggers[a].Y, gTriggers[a].Width, gTriggers[a].Height, monsNear); for mon in tgMonsList do begin gTriggers[a].ActivateUID := mon.UID; ActivateTrigger(gTriggers[a], ACTIVATE_MONSTERCOLLIDE); end; tgMonsList.reset(); // just in case end; // "Монстров нет" if ByteBool(ActivateType and ACTIVATE_NOMONSTER) and (TimeOut = 0) and (Keys = 0) then if not g_Mons_IsAnyAliveAt(X, Y, Width, Height) then begin gTriggers[a].ActivateUID := 0; ActivateTrigger(gTriggers[a], ACTIVATE_NOMONSTER); end; end; PlayerCollide := g_CollidePlayer(X, Y, Width, Height); end; end; procedure g_Triggers_Press(ID: DWORD; ActivateType: Byte; ActivateUID: Word = 0); begin gTriggers[ID].ActivateUID := ActivateUID; ActivateTrigger(gTriggers[ID], ActivateType); end; function g_Triggers_PressR(X, Y: Integer; Width, Height: Word; UID: Word; ActivateType: Byte; IgnoreList: DWArray = nil): DWArray; var a: Integer; k: Byte; p: TPlayer; begin Result := nil; if gTriggers = nil then Exit; case g_GetUIDType(UID) of UID_GAME: k := 255; UID_PLAYER: begin p := g_Player_Get(UID); if p <> nil then k := p.GetKeys else k := 0; end; else k := 0; end; for a := 0 to High(gTriggers) do if (gTriggers[a].TriggerType <> TRIGGER_NONE) and (gTriggers[a].TimeOut = 0) and (not InDWArray(a, IgnoreList)) and ((gTriggers[a].Keys and k) = gTriggers[a].Keys) and ByteBool(gTriggers[a].ActivateType and ActivateType) then if g_Collide(X, Y, Width, Height, gTriggers[a].X, gTriggers[a].Y, gTriggers[a].Width, gTriggers[a].Height) then begin gTriggers[a].ActivateUID := UID; if ActivateTrigger(gTriggers[a], ActivateType) then begin SetLength(Result, Length(Result)+1); Result[High(Result)] := a; end; end; end; procedure g_Triggers_PressL(X1, Y1, X2, Y2: Integer; UID: DWORD; ActivateType: Byte); var a: Integer; k: Byte; p: TPlayer; begin if gTriggers = nil then Exit; case g_GetUIDType(UID) of UID_GAME: k := 255; UID_PLAYER: begin p := g_Player_Get(UID); if p <> nil then k := p.GetKeys else k := 0; end; else k := 0; end; for a := 0 to High(gTriggers) do if (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 if g_CollideLine(x1, y1, x2, y2, gTriggers[a].X, gTriggers[a].Y, gTriggers[a].Width, gTriggers[a].Height) then begin gTriggers[a].ActivateUID := UID; ActivateTrigger(gTriggers[a], ActivateType); end; end; procedure g_Triggers_PressC(CX, CY: Integer; Radius: Word; UID: Word; ActivateType: Byte; IgnoreTrigger: Integer = -1); var a: Integer; k: Byte; rsq: Word; p: TPlayer; begin if gTriggers = nil then Exit; case g_GetUIDType(UID) of UID_GAME: k := 255; UID_PLAYER: begin p := g_Player_Get(UID); if p <> nil then k := p.GetKeys else k := 0; end; else k := 0; end; rsq := Radius * Radius; for a := 0 to High(gTriggers) do 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 with gTriggers[a] do if g_Collide(CX-Radius, CY-Radius, 2*Radius, 2*Radius, X, Y, Width, Height) then if ((Sqr(CX-X)+Sqr(CY-Y)) < rsq) or // Центр круга близок к верхнему левому углу ((Sqr(CX-X-Width)+Sqr(CY-Y)) < rsq) or // Центр круга близок к верхнему правому углу ((Sqr(CX-X-Width)+Sqr(CY-Y-Height)) < rsq) or // Центр круга близок к нижнему правому углу ((Sqr(CX-X)+Sqr(CY-Y-Height)) < rsq) or // Центр круга близок к нижнему левому углу ( (CX > (X-Radius)) and (CX < (X+Width+Radius)) and (CY > Y) and (CY < (Y+Height)) ) or // Центр круга недалеко от вертикальных границ прямоугольника ( (CY > (Y-Radius)) and (CY < (Y+Height+Radius)) and (CX > X) and (CX < (X+Width)) ) then // Центр круга недалеко от горизонтальных границ прямоугольника begin ActivateUID := UID; ActivateTrigger(gTriggers[a], ActivateType); end; end; procedure g_Triggers_OpenAll(); var a: Integer; b: Boolean; begin if gTriggers = nil then Exit; b := False; for a := 0 to High(gTriggers) do with gTriggers[a] do if (TriggerType = TRIGGER_OPENDOOR) or (TriggerType = TRIGGER_DOOR5) or (TriggerType = TRIGGER_DOOR) then begin tr_OpenDoor(trigPanelID, True, trigData.trigd2d_doors); if TriggerType = TRIGGER_DOOR5 then DoorTime := 180; b := True; end; if b then g_Sound_PlayEx('SOUND_GAME_DOOROPEN'); end; procedure g_Triggers_DecreaseSpawner(ID: DWORD); begin if (gTriggers <> nil) then if gTriggers[ID].SpawnedCount > 0 then Dec(gTriggers[ID].SpawnedCount); end; procedure g_Triggers_Free(); var a: Integer; begin for a := 0 to High(gTriggers) do begin if (gTriggers[a].TriggerType = TRIGGER_SOUND) then begin if g_Sound_Exists(gTriggers[a].trigData.trigSoundName) then begin g_Sound_Delete(gTriggers[a].trigData.trigSoundName); end; gTriggers[a].Sound.Free(); end; if (gTriggers[a].Activators <> nil) then begin SetLength(gTriggers[a].Activators, 0); end; gTriggers[a].trigData.Free(); end; gTriggers := nil; gSecretsCount := 0; SetLength(gMonstersSpawned, 0); end; procedure g_Triggers_SaveState(var Mem: TBinMemoryWriter); var count, act_count, i, j: Integer; dw: DWORD; sg: Single; b: Boolean; //p: Pointer; begin // Считаем количество существующих триггеров: count := 0; if gTriggers <> nil then for i := 0 to High(gTriggers) do count := count + 1; Mem := TBinMemoryWriter.Create((count+1) * 200); // Количество триггеров: Mem.WriteInt(count); if count = 0 then Exit; for i := 0 to High(gTriggers) do begin // Сигнатура триггера: dw := TRIGGER_SIGNATURE; // 'TRGR' Mem.WriteDWORD(dw); // Тип триггера: Mem.WriteByte(gTriggers[i].TriggerType); // Специальные данные триггера: //!!!FIXME!!! //p := @gTriggers[i].Data; //Mem.WriteMemory(p, SizeOf(TTriggerData)); // Координаты левого верхнего угла: Mem.WriteInt(gTriggers[i].X); Mem.WriteInt(gTriggers[i].Y); // Размеры: Mem.WriteWord(gTriggers[i].Width); Mem.WriteWord(gTriggers[i].Height); // Включен ли триггер: Mem.WriteBoolean(gTriggers[i].Enabled); // Тип активации триггера: Mem.WriteByte(gTriggers[i].ActivateType); // Ключи, необходимые для активации: Mem.WriteByte(gTriggers[i].Keys); // ID панели, текстура которой изменится: Mem.WriteInt(gTriggers[i].TexturePanel); // Тип этой панели: Mem.WriteWord(gTriggers[i].TexturePanelType); // Время до возможности активации: Mem.WriteWord(gTriggers[i].TimeOut); // UID того, кто активировал этот триггер: Mem.WriteWord(gTriggers[i].ActivateUID); // Список UID-ов объектов, которые находились под воздействием: act_count := Length(gTriggers[i].Activators); Mem.WriteInt(act_count); for j := 0 to act_count-1 do begin // UID объекта Mem.WriteWord(gTriggers[i].Activators[j].UID); // Время ожидания Mem.WriteWord(gTriggers[i].Activators[j].TimeOut); end; // Стоит ли игрок в области триггера: Mem.WriteBoolean(gTriggers[i].PlayerCollide); // Время до закрытия двери: Mem.WriteInt(gTriggers[i].DoorTime); // Задержка активации: Mem.WriteInt(gTriggers[i].PressTime); // Счетчик нажатий: Mem.WriteInt(gTriggers[i].PressCount); // Спавнер активен: Mem.WriteBoolean(gTriggers[i].AutoSpawn); // Задержка спавнера: Mem.WriteInt(gTriggers[i].SpawnCooldown); // Счетчик создания объектов: Mem.WriteInt(gTriggers[i].SpawnedCount); // Сколько раз проигран звук: Mem.WriteInt(gTriggers[i].SoundPlayCount); // Проигрывается ли звук? if gTriggers[i].Sound <> nil then b := gTriggers[i].Sound.IsPlaying() else b := False; Mem.WriteBoolean(b); if b then begin // Позиция проигрывания звука: dw := gTriggers[i].Sound.GetPosition(); Mem.WriteDWORD(dw); // Громкость звука: sg := gTriggers[i].Sound.GetVolume(); sg := sg / (gSoundLevel/255.0); Mem.WriteSingle(sg); // Стерео смещение звука: sg := gTriggers[i].Sound.GetPan(); Mem.WriteSingle(sg); end; end; end; procedure g_Triggers_LoadState(var Mem: TBinMemoryReader); var count, act_count, i, j, a: Integer; dw: DWORD; vol, pan: Single; b: Boolean; //p: Pointer; Trig: TTrigger; begin if Mem = nil then Exit; g_Triggers_Free(); // Количество триггеров: Mem.ReadInt(count); if count = 0 then Exit; for a := 0 to count-1 do begin // Сигнатура триггера: Mem.ReadDWORD(dw); if dw <> TRIGGER_SIGNATURE then // 'TRGR' begin raise EBinSizeError.Create('g_Triggers_LoadState: Wrong Trigger Signature'); end; // Тип триггера: Mem.ReadByte(Trig.TriggerType); // Специальные данные триггера: //!!!FIXME!!! { Mem.ReadMemory(p, dw); if dw <> SizeOf(TTriggerData) then begin raise EBinSizeError.Create('g_Triggers_LoadState: Wrong TriggerData Size'); end; Trig.Data := TTriggerData(p^); } // Создаем триггер: i := g_Triggers_Create(Trig); // Координаты левого верхнего угла: Mem.ReadInt(gTriggers[i].X); Mem.ReadInt(gTriggers[i].Y); // Размеры: Mem.ReadWord(gTriggers[i].Width); Mem.ReadWord(gTriggers[i].Height); // Включен ли триггер: Mem.ReadBoolean(gTriggers[i].Enabled); // Тип активации триггера: Mem.ReadByte(gTriggers[i].ActivateType); // Ключи, необходимые для активации: Mem.ReadByte(gTriggers[i].Keys); // ID панели, текстура которой изменится: Mem.ReadInt(gTriggers[i].TexturePanel); // Тип этой панели: Mem.ReadWord(gTriggers[i].TexturePanelType); // Время до возможности активации: Mem.ReadWord(gTriggers[i].TimeOut); // UID того, кто активировал этот триггер: Mem.ReadWord(gTriggers[i].ActivateUID); // Список UID-ов объектов, которые находились под воздействием: Mem.ReadInt(act_count); if act_count > 0 then begin SetLength(gTriggers[i].Activators, act_count); for j := 0 to act_count-1 do begin // UID объекта Mem.ReadWord(gTriggers[i].Activators[j].UID); // Время ожидания Mem.ReadWord(gTriggers[i].Activators[j].TimeOut); end; end; // Стоит ли игрок в области триггера: Mem.ReadBoolean(gTriggers[i].PlayerCollide); // Время до закрытия двери: Mem.ReadInt(gTriggers[i].DoorTime); // Задержка активации: Mem.ReadInt(gTriggers[i].PressTime); // Счетчик нажатий: Mem.ReadInt(gTriggers[i].PressCount); // Спавнер активен: Mem.ReadBoolean(gTriggers[i].AutoSpawn); // Задержка спавнера: Mem.ReadInt(gTriggers[i].SpawnCooldown); // Счетчик создания объектов: Mem.ReadInt(gTriggers[i].SpawnedCount); // Сколько раз проигран звук: Mem.ReadInt(gTriggers[i].SoundPlayCount); // Проигрывается ли звук? Mem.ReadBoolean(b); if b then begin // Позиция проигрывания звука: Mem.ReadDWORD(dw); // Громкость звука: Mem.ReadSingle(vol); // Стерео смещение звука: Mem.ReadSingle(pan); // Запускаем звук, если есть: if gTriggers[i].Sound <> nil then begin gTriggers[i].Sound.PlayPanVolume(pan, vol); gTriggers[i].Sound.Pause(True); gTriggers[i].Sound.SetPosition(dw); end end; end; end; end.