{$MODE DELPHI} unit g_triggers; interface uses MAPSTRUCT, e_graphics, MAPDEF, g_basic, g_sound, BinEditor; type TActivator = record UID: Word; TimeOut: Word; end; TTrigger = record 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; Data: TTriggerData; end; function g_Triggers_Create(Trigger: TTrigger): DWORD; procedure g_Triggers_Update(); procedure g_Triggers_Press(ID: DWORD; ActivateType: Byte); 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_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); 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; const TRIGGER_SIGNATURE = $52475254; // 'TRGR' TRAP_DAMAGE = 1000; 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 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_CollideMonster(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_CollideMonster(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 CloseTrap(PanelID: Integer; NoSound: Boolean; d2d: Boolean); var a, b, c: Integer; begin if PanelID = -1 then Exit; if not d2d then begin with gWalls[PanelID] do 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; 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); 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(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 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 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; for b := 0 to High(gDoorMap[c]) do 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); 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; function 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 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; procedure MakeShot(var Trigger: TTrigger; wx, wy, dx, dy: Integer; TargetUID: Word); var Projectile: Boolean; snd: string; 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 begin g_Map_SwitchTexture(ShotPanelType, Data.ShotPanelID); ShotPanelTime := 4; // тиков на вспышку выстрела end; if Data.ShotIntSight > 0 then ShotSightTimeout := 180; // ~= 5 секунд if ShotAmmoCount > 0 then Dec(ShotAmmoCount); Projectile := True; snd := 'SOUND_WEAPON_FIREROCKET'; dx := dx + Random(Data.ShotAccuracy) - Random(Data.ShotAccuracy); dy := dy + Random(Data.ShotAccuracy) - Random(Data.ShotAccuracy); 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_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_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_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_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, TargetUID, -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; 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.ShotSound then g_Sound_PlayExAt(snd, wx, wy); 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); 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 ActivateTrigger(var Trigger: TTrigger; actType: Byte): Boolean; var animonce: Boolean; p: TPlayer; m: TMonster; i, k, wx, wy, xd, yd: Integer; iid: LongWord; coolDown: Boolean; pAngle: Real; FramesID: DWORD; Anim: TAnimation; UIDType: Byte; TargetUID: Word; msg: string; 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(Data.MapName); TimeOut := 18; Result := True; Exit; end; 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; TimeOut := 0; end; TRIGGER_OPENDOOR: begin Result := OpenDoor(Data.PanelID, Data.NoSound, Data.d2d_doors); TimeOut := 0; end; TRIGGER_CLOSEDOOR: begin Result := CloseDoor(Data.PanelID, Data.NoSound, Data.d2d_doors); TimeOut := 0; end; TRIGGER_DOOR, TRIGGER_DOOR5: begin if Data.PanelID <> -1 then begin if gWalls[Data.PanelID].Enabled then begin Result := 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); if Result then TimeOut := 18; end; end; TRIGGER_CLOSETRAP, TRIGGER_TRAP: begin CloseTrap(Data.PanelID, Data.NoSound, Data.d2d_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 := Data.Wait; 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 := SetLift(Data.PanelID, 0, Data.NoSound, Data.d2d_doors); TimeOut := 0; if (not Data.NoSound) 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 := SetLift(Data.PanelID, 1, Data.NoSound, Data.d2d_doors); TimeOut := 0; if (not Data.NoSound) 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 := SetLift(Data.PanelID, 3, Data.NoSound, Data.d2d_doors); if Result then begin TimeOut := 18; if (not Data.NoSound) 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 ByteBool(Data.ActivateOnce) then begin Enabled := False; TriggerType := TRIGGER_NONE; end else if coolDown then TimeOut := 6 else TimeOut := 0; animonce := Data.AnimOnce; Result := True; end; TRIGGER_SOUND: begin if Sound <> nil then begin if Data.SoundSwitch and Sound.IsPlaying() then begin // Нужно выключить, если играл Sound.Stop(); SoundPlayCount := 0; Result := True; end else // (not Data.SoundSwitch) or (not Sound.IsPlaying()) if (Data.PlayCount > 0) or (not Sound.IsPlaying()) then begin if Data.PlayCount > 0 then SoundPlayCount := Data.PlayCount else // 0 - играем бесконечно SoundPlayCount := 1; Result := True; end; if g_Game_IsNet then MH_SEND_TriggerSound(Trigger); end; end; TRIGGER_SPAWNMONSTER: if (Data.MonType in [MONSTER_DEMON..MONSTER_MAN]) then begin Result := False; if (Data.MonDelay > 0) and (actType <> ACTIVATE_CUSTOM) then begin AutoSpawn := not AutoSpawn; SpawnCooldown := 0; // Автоспавнер переключен - меняем текстуру Result := True; end; if ((Data.MonDelay = 0) and (actType <> ACTIVATE_CUSTOM)) or ((Data.MonDelay > 0) and (actType = ACTIVATE_CUSTOM)) then for k := 1 to Data.MonCount do begin if (actType = ACTIVATE_CUSTOM) and (Data.MonDelay > 0) then SpawnCooldown := Data.MonDelay; if (Data.MonMax > 0) and (SpawnedCount >= Data.MonMax) then Break; i := g_Monsters_Create(Data.MonType, Data.MonPos.X, Data.MonPos.Y, TDirection(Data.MonDir), True); Result := True; // Здоровье: if (Data.MonHealth > 0) then gMonsters[i].SetHealth(Data.MonHealth); // Устанавливаем поведение: gMonsters[i].MonsterBehaviour := Data.MonBehav; // Идем искать цель, если надо: if Data.MonActive then gMonsters[i].WakeUp(); gMonsters[i].FNoRespawn := True; 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; end; if Data.MonMax > 0 then begin gMonsters[i].SpawnTrigger := ID; Inc(SpawnedCount); end; case Data.MonEffect 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', 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); 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, 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', 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); 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, 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', 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); 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, NET_GFX_FIRE); end; end; if g_Game_IsNet then MH_SEND_MonsterSpawn(gMonsters[i].UID); 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 (Data.ItemType in [ITEM_MEDKIT_SMALL..ITEM_MAX]) then begin Result := False; if (Data.ItemDelay > 0) and (actType <> ACTIVATE_CUSTOM) then begin AutoSpawn := not AutoSpawn; SpawnCooldown := 0; // Автоспавнер переключен - меняем текстуру Result := True; end; if ((Data.ItemDelay = 0) and (actType <> ACTIVATE_CUSTOM)) or ((Data.ItemDelay > 0) and (actType = ACTIVATE_CUSTOM)) then if (not Data.ItemOnlyDM) or (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF]) then for k := 1 to Data.ItemCount do begin if (actType = ACTIVATE_CUSTOM) and (Data.ItemDelay > 0) then SpawnCooldown := Data.ItemDelay; if (Data.ItemMax > 0) and (SpawnedCount >= Data.ItemMax) then Break; iid := g_Items_Create(Data.ItemPos.X, Data.ItemPos.Y, Data.ItemType, Data.ItemFalls, False, True); Result := True; if Data.ItemMax > 0 then begin gItems[iid].SpawnTrigger := ID; Inc(SpawnedCount); end; case Data.ItemEffect 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', 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); 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, 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', 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); 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, 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', 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); 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, 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.Data.MusicName <> '') then begin gMusic.SetByName(Trigger.Data.MusicName); gMusic.SpecPause := True; gMusic.Play(); end; if Trigger.Data.MusicAction = 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 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; TimeOut := 0; Result := True; end; TRIGGER_SCORE: begin Result := False; // Прибавить или отнять очко if (Data.ScoreAction in [0..1]) and (Data.ScoreCount > 0) then begin // Своей или чужой команде if (Data.ScoreTeam in [0..1]) and (g_GetUIDType(ActivateUID) = UID_PLAYER) then begin p := g_Player_Get(ActivateUID); if ((Data.ScoreAction = 0) and (Data.ScoreTeam = 0) and (p.Team = TEAM_RED)) or ((Data.ScoreAction = 0) and (Data.ScoreTeam = 1) and (p.Team = TEAM_BLUE)) then begin Inc(gTeamStat[TEAM_RED].Goals, Data.ScoreCount); // Red Scores if Data.ScoreCon then if Data.ScoreTeam = 0 then begin g_Console_Add(Format(_lc[I_PLAYER_SCORE_ADD_OWN], [p.Name, Data.ScoreCount, _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 (Data.ScoreCount shl 16), '+r'); end else begin g_Console_Add(Format(_lc[I_PLAYER_SCORE_ADD_ENEMY], [p.Name, Data.ScoreCount, _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 (Data.ScoreCount shl 16), '+re'); end; if Data.ScoreMsg 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 ((Data.ScoreAction = 1) and (Data.ScoreTeam = 0) and (p.Team = TEAM_RED)) or ((Data.ScoreAction = 1) and (Data.ScoreTeam = 1) and (p.Team = TEAM_BLUE)) then begin Dec(gTeamStat[TEAM_RED].Goals, Data.ScoreCount); // Red Fouls if Data.ScoreCon then if Data.ScoreTeam = 0 then begin g_Console_Add(Format(_lc[I_PLAYER_SCORE_SUB_OWN], [p.Name, Data.ScoreCount, _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 (Data.ScoreCount shl 16), '-r'); end else begin g_Console_Add(Format(_lc[I_PLAYER_SCORE_SUB_ENEMY], [p.Name, Data.ScoreCount, _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 (Data.ScoreCount shl 16), '-re'); end; if Data.ScoreMsg 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 ((Data.ScoreAction = 0) and (Data.ScoreTeam = 0) and (p.Team = TEAM_BLUE)) or ((Data.ScoreAction = 0) and (Data.ScoreTeam = 1) and (p.Team = TEAM_RED)) then begin Inc(gTeamStat[TEAM_BLUE].Goals, Data.ScoreCount); // Blue Scores if Data.ScoreCon then if Data.ScoreTeam = 0 then begin g_Console_Add(Format(_lc[I_PLAYER_SCORE_ADD_OWN], [p.Name, Data.ScoreCount, _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 (Data.ScoreCount shl 16), '+b'); end else begin g_Console_Add(Format(_lc[I_PLAYER_SCORE_ADD_ENEMY], [p.Name, Data.ScoreCount, _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 (Data.ScoreCount shl 16), '+be'); end; if Data.ScoreMsg 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 ((Data.ScoreAction = 1) and (Data.ScoreTeam = 0) and (p.Team = TEAM_BLUE)) or ((Data.ScoreAction = 1) and (Data.ScoreTeam = 1) and (p.Team = TEAM_RED)) then begin Dec(gTeamStat[TEAM_BLUE].Goals, Data.ScoreCount); // Blue Fouls if Data.ScoreCon then if Data.ScoreTeam = 0 then begin g_Console_Add(Format(_lc[I_PLAYER_SCORE_SUB_OWN], [p.Name, Data.ScoreCount, _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 (Data.ScoreCount shl 16), '-b'); end else begin g_Console_Add(Format(_lc[I_PLAYER_SCORE_SUB_ENEMY], [p.Name, Data.ScoreCount, _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 (Data.ScoreCount shl 16), '-be'); end; if Data.ScoreMsg 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 Data.ScoreTeam in [2..3] then begin if (Data.ScoreAction = 0) and (Data.ScoreTeam = 2) then begin Inc(gTeamStat[TEAM_RED].Goals, Data.ScoreCount); // Red Scores if Data.ScoreCon then begin g_Console_Add(Format(_lc[I_PLAYER_SCORE_ADD_TEAM], [_lc[I_PLAYER_SCORE_RED], Data.ScoreCount]), True); if g_Game_IsServer and g_Game_IsNet then MH_SEND_GameEvent(NET_EV_SCORE, Data.ScoreCount shl 16, '+tr'); end; if Data.ScoreMsg 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 (Data.ScoreAction = 1) and (Data.ScoreTeam = 2) then begin Dec(gTeamStat[TEAM_RED].Goals, Data.ScoreCount); // Red Fouls if Data.ScoreCon then begin g_Console_Add(Format(_lc[I_PLAYER_SCORE_SUB_TEAM], [_lc[I_PLAYER_SCORE_RED], Data.ScoreCount]), True); if g_Game_IsServer and g_Game_IsNet then MH_SEND_GameEvent(NET_EV_SCORE, Data.ScoreCount shl 16, '-tr'); end; if Data.ScoreMsg 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 (Data.ScoreAction = 0) and (Data.ScoreTeam = 3) then begin Inc(gTeamStat[TEAM_BLUE].Goals, Data.ScoreCount); // Blue Scores if Data.ScoreCon then begin g_Console_Add(Format(_lc[I_PLAYER_SCORE_ADD_TEAM], [_lc[I_PLAYER_SCORE_BLUE], Data.ScoreCount]), True); if g_Game_IsServer and g_Game_IsNet then MH_SEND_GameEvent(NET_EV_SCORE, Data.ScoreCount shl 16, '+tb'); end; if Data.ScoreMsg 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 (Data.ScoreAction = 1) and (Data.ScoreTeam = 3) then begin Dec(gTeamStat[TEAM_BLUE].Goals, Data.ScoreCount); // Blue Fouls if Data.ScoreCon then begin g_Console_Add(Format(_lc[I_PLAYER_SCORE_SUB_TEAM], [_lc[I_PLAYER_SCORE_BLUE], Data.ScoreCount]), True); if g_Game_IsServer and g_Game_IsNet then MH_SEND_GameEvent(NET_EV_SCORE, Data.ScoreCount shl 16, '-tb'); end; if Data.ScoreMsg 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 (Data.ScoreAction = 2) and (gGameSettings.GoalLimit > 0) then begin // Своей или чужой команды if (Data.ScoreTeam in [0..1]) and (g_GetUIDType(ActivateUID) = UID_PLAYER) then begin p := g_Player_Get(ActivateUID); if ((Data.ScoreTeam = 0) and (p.Team = TEAM_RED)) // Red Wins or ((Data.ScoreTeam = 1) and (p.Team = TEAM_BLUE)) then if gTeamStat[TEAM_RED].Goals < SmallInt(gGameSettings.GoalLimit) then begin gTeamStat[TEAM_RED].Goals := gGameSettings.GoalLimit; if Data.ScoreCon then if Data.ScoreTeam = 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 ((Data.ScoreTeam = 0) and (p.Team = TEAM_BLUE)) // Blue Wins or ((Data.ScoreTeam = 1) and (p.Team = TEAM_RED)) then if gTeamStat[TEAM_BLUE].Goals < SmallInt(gGameSettings.GoalLimit) then begin gTeamStat[TEAM_BLUE].Goals := gGameSettings.GoalLimit; if Data.ScoreCon then if Data.ScoreTeam = 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 Data.ScoreTeam in [2..3] then begin if Data.ScoreTeam = 2 then // Red Wins if gTeamStat[TEAM_RED].Goals < SmallInt(gGameSettings.GoalLimit) then begin gTeamStat[TEAM_RED].Goals := gGameSettings.GoalLimit; Result := True; end; if Data.ScoreTeam = 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 (Data.ScoreAction = 3) and (gGameSettings.GoalLimit > 0) then begin // Своей или чужой команды if (Data.ScoreTeam in [0..1]) and (g_GetUIDType(ActivateUID) = UID_PLAYER) then begin p := g_Player_Get(ActivateUID); if ((Data.ScoreTeam = 0) and (p.Team = TEAM_BLUE)) // Red Wins or ((Data.ScoreTeam = 1) and (p.Team = TEAM_RED)) then if gTeamStat[TEAM_RED].Goals < SmallInt(gGameSettings.GoalLimit) then begin gTeamStat[TEAM_RED].Goals := gGameSettings.GoalLimit; if Data.ScoreCon then if Data.ScoreTeam = 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 ((Data.ScoreTeam = 0) and (p.Team = TEAM_RED)) // Blue Wins or ((Data.ScoreTeam = 1) and (p.Team = TEAM_BLUE)) then if gTeamStat[TEAM_BLUE].Goals < SmallInt(gGameSettings.GoalLimit) then begin gTeamStat[TEAM_BLUE].Goals := gGameSettings.GoalLimit; if Data.ScoreCon then if Data.ScoreTeam = 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 Data.ScoreTeam in [2..3] then begin if Data.ScoreTeam = 3 then // Red Wins if gTeamStat[TEAM_RED].Goals < SmallInt(gGameSettings.GoalLimit) then begin gTeamStat[TEAM_RED].Goals := gGameSettings.GoalLimit; Result := True; end; if Data.ScoreTeam = 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 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; 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 i := 0 to High(Activators) do if Activators[i].UID = ActivateUID then begin k := i; Break; end; if k = -1 then begin // Видим его впервые // Запоминаем его SetLength(Activators, Length(Activators) + 1); k := High(Activators); Activators[k].UID := ActivateUID; end else begin // Уже видели его // Если интервал отключён, но он всё ещё в зоне поражения, даём ему время if (Data.DamageInterval = 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 (Data.DamageValue > 0) then p.Damage(Data.DamageValue, 0, 0, 0, HIT_SOME); // Лечим игрока if (TriggerType = TRIGGER_HEALTH) and (Data.HealValue > 0) then if p.Heal(Data.HealValue, not Data.HealMax) and (not Data.HealSilent) 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_Get(ActivateUID); if m = nil then Exit; // Наносим урон монстру if (TriggerType = TRIGGER_DAMAGE) and (Data.DamageValue > 0) then m.Damage(Data.DamageValue, 0, 0, 0, HIT_SOME); // Лечим монстра if (TriggerType = TRIGGER_HEALTH) and (Data.HealValue > 0) then if m.Heal(Data.HealValue) and (not Data.HealSilent) 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 i := Data.DamageInterval else i := Data.HealInterval; if coolDown then if i > 0 then Activators[k].TimeOut := i else Activators[k].TimeOut := 65535; end; end; TimeOut := 0; end; TRIGGER_SHOT: begin if ShotSightTime > 0 then Exit; wx := Data.ShotPos.X; wy := Data.ShotPos.Y; pAngle := -DegToRad(Data.ShotAngle); xd := wx + Round(Cos(pAngle) * 32.0); yd := wy + Round(Sin(pAngle) * 32.0); TargetUID := 0; 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; 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 begin xd := gPlayers[i].GameX + PLAYER_RECT_CX; yd := gPlayers[i].GameY + PLAYER_RECT_CY; TargetUID := gPlayers[i].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 begin xd := gPlayers[i].GameX + PLAYER_RECT_CX; yd := gPlayers[i].GameY + PLAYER_RECT_CY; TargetUID := gPlayers[i].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 begin xd := gPlayers[i].GameX + PLAYER_RECT_CX; yd := gPlayers[i].GameY + PLAYER_RECT_CY; TargetUID := gPlayers[i].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; 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 begin xd := gPlayers[i].GameX + PLAYER_RECT_CX; yd := gPlayers[i].GameY + PLAYER_RECT_CY; TargetUID := gPlayers[i].UID; break; end; end; 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 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; end; else TargetUID := ActivateUID; end; if (Data.ShotTarget = TRIGGER_SHOT_TARGET_NONE) or (TargetUID > 0) then begin Result := True; if (Data.ShotIntSight = 0) or (Data.ShotTarget = TRIGGER_SHOT_TARGET_NONE) or (TargetUID = ShotSightTarget) then MakeShot(Trigger, wx, wy, xd, yd, TargetUID) else begin ShotSightTime := Data.ShotIntSight; ShotSightTargetN := TargetUID; if Data.ShotType = 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; TimeOut := Data.ShotWait + 1; end; TRIGGER_EFFECT: begin i := Data.FXCount; while i > 0 do begin case Data.FXPos 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 := Data.FXVelX; yd := Data.FXVelY; if Data.FXSpreadL > 0 then xd := xd - Random(Data.FXSpreadL + 1); 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, Data.FXType, Data.FXSubType, Data.FXColorR, Data.FXColorG, Data.FXColorB, True, False); Dec(i); end; TimeOut := Data.FXWait; 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; 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.Data.SoundName <> '') then begin // Еще нет такого звука: if not g_Sound_Exists(Trigger.Data.SoundName) then begin g_ProcessResourceStr(Trigger.Data.SoundName, @fn, nil, nil); if fn = '' then begin // Звук в файле с картой g_ProcessResourceStr(gMapInfo.Map, @mapw, nil, nil); fn := mapw + Trigger.Data.SoundName; end else // Звук в отдельном файле fn := GameDir + '/wads/' + Trigger.Data.SoundName; if not g_Sound_CreateWADEx(Trigger.Data.SoundName, fn) then g_FatalError(Format(_lc[I_GAME_ERROR_TR_SOUND], [fn, Trigger.Data.SoundName])); end; // Создаем объект звука: with gTriggers[find_id] do begin Sound := TPlayableSound.Create(); if not Sound.SetByName(Trigger.Data.SoundName) then begin Sound.Free(); Sound := nil; end; end; end; // Загружаем музыку, если это триггер "Музыка": if (Trigger.TriggerType = TRIGGER_MUSIC) and (Trigger.Data.MusicName <> '') then begin // Еще нет такой музыки: if not g_Sound_Exists(Trigger.Data.MusicName) then begin g_ProcessResourceStr(Trigger.Data.MusicName, @fn, nil, nil); if fn = '' then begin // Музыка в файле с картой g_ProcessResourceStr(gMapInfo.Map, @mapw, nil, nil); fn := mapw + Trigger.Data.MusicName; end else // Музыка в файле с картой fn := GameDir+'/wads/'+Trigger.Data.MusicName; if not g_Sound_CreateWADEx(Trigger.Data.MusicName, fn, True) then g_FatalError(Format(_lc[I_GAME_ERROR_TR_SOUND], [fn, Trigger.Data.MusicName])); 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.Data.ShotAmmo; ShotReloadTime := 0; end; Result := find_id; end; procedure g_Triggers_Update(); var a, b, i: Integer; Affected: array of Integer; begin 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 (Data.DamageInterval = 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 (Data.MonDelay > 0) then begin ActivateUID := 0; ActivateTrigger(gTriggers[a], ACTIVATE_CUSTOM); end; // Если пришло время, спавним предмет: if (TriggerType = TRIGGER_SPAWNITEM) and (Data.ItemDelay > 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, Data.ShotPanelID); 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 := Data.ShotAmmo; end; end; // Триггер "Звук" уже отыграл, если нужно еще - перезапускаем: if Enabled and (TriggerType = TRIGGER_SOUND) and (Sound <> nil) then if (SoundPlayCount > 0) and (not Sound.IsPlaying()) then begin if Data.PlayCount > 0 then // Если 0 - играем звук бесконечно SoundPlayCount := SoundPlayCount - 1; if Data.Local then Sound.PlayVolumeAt(X+(Width div 2), Y+(Height div 2), Data.Volume/255.0) else Sound.PlayPanVolume((Data.Pan-127.0)/128.0, Data.Volume/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 (Data.PanelID <> -1) then begin OpenDoor(Data.PanelID, Data.NoSound, Data.d2d_doors); DoorTime := -1; end; // Триггер "Дверь 5 сек" - пора закрывать: if (TriggerType = TRIGGER_DOOR5) and (DoorTime = 0) and (Data.PanelID <> -1) then begin // Уже закрыта: if gWalls[Data.PanelID].Enabled then DoorTime := -1 else // Пока открыта - закрываем if CloseDoor(Data.PanelID, Data.NoSound, Data.d2d_doors) then DoorTime := -1; end; // Триггер - расширитель или переключатель, и прошла задержка, и нажали нужное число раз: if (TriggerType in [TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF]) and (PressTime = 0) and (PressCount >= Data.Count) then begin // Сбрасываем задержку активации: PressTime := -1; // Сбрасываем счетчик нажатий: if Data.Count > 0 then PressCount := PressCount - Data.Count else PressCount := 0; // Определяем изменяемые им триггеры: for b := 0 to High(gTriggers) do if g_Collide(Data.tX, Data.tY, Data.tWidth, Data.tHeight, gTriggers[b].X, gTriggers[b].Y, gTriggers[b].Width, gTriggers[b].Height) and ((b <> a) or (Data.Wait > 0)) then begin // Can be self-activated, if there is Data.Wait if (not Data.ExtRandom) or gTriggers[b].Enabled then begin SetLength(Affected, Length(Affected) + 1); Affected[High(Affected)] := b; end; end; // Выбираем один из триггеров для расширителя, если включен рандом: if (TriggerType = TRIGGER_PRESS) and Data.ExtRandom 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 // Если не нужны ключи 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; // "Монстров нет": if ByteBool(ActivateType and ACTIVATE_NOMONSTER) and (TimeOut = 0) and (Keys = 0) then if not g_CollideMonster(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); begin gTriggers[ID].ActivateUID := 0; 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); 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].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 OpenDoor(Data.PanelID, True, Data.d2d_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 if gTriggers <> nil then for a := 0 to High(gTriggers) do begin if gTriggers[a].TriggerType = TRIGGER_SOUND then begin if g_Sound_Exists(gTriggers[a].Data.SoundName) then g_Sound_Delete(gTriggers[a].Data.SoundName); gTriggers[a].Sound.Free(); end; if gTriggers[a].Activators <> nil then SetLength(gTriggers[a].Activators, 0); 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); // Специальные данные триггера: 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); // Специальные данные триггера: 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.