DEADSOFTWARE

game: disable shells for server
[d2df-sdl.git] / src / game / g_triggers.pas
1 (* Copyright (C) Doom 2D: Forever Developers
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, version 3 of the License ONLY.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 *)
15 {$INCLUDE ../shared/a_modes.inc}
16 unit g_triggers;
18 interface
20 uses
21 SysUtils, Variants, Classes,
22 MAPDEF, g_basic, g_sound,
23 xdynrec, hashtable, exoma;
25 type
26 TActivator = record
27 UID: Word;
28 TimeOut: Word;
29 end;
31 PTrigger = ^TTrigger;
32 TTrigger = record
33 public
34 ID: DWORD;
35 ClientID: DWORD;
36 TriggerType: Byte;
37 X, Y: Integer;
38 Width, Height: Word;
39 Enabled: Boolean;
40 ActivateType: Byte;
41 Keys: Byte;
42 TexturePanelGUID: Integer;
43 //TexturePanelType: Word;
45 TimeOut: Word;
46 ActivateUID: Word;
47 Activators: array of TActivator;
48 PlayerCollide: Boolean;
49 DoorTime: Integer;
50 PressTime: Integer;
51 PressCount: Integer;
52 SoundPlayCount: Integer;
53 Sound: TPlayableSound;
54 AutoSpawn: Boolean;
55 SpawnCooldown: Integer;
56 SpawnedCount: Integer;
57 //ShotPanelType: Word;
58 ShotPanelTime: Integer;
59 ShotSightTime: Integer;
60 ShotSightTimeout: Integer;
61 ShotSightTarget: Word;
62 ShotSightTargetN: Word;
63 ShotAmmoCount: Word;
64 ShotReloadTime: Integer;
66 mapId: AnsiString; // trigger id, from map
67 mapIndex: Integer; // index in fields['trigger'], used in save/load
68 trigPanelGUID: Integer;
70 trigDataRec: TDynRecord; // triggerdata; owned by trigger (cloned)
71 exoInit, exoThink, exoCheck, exoAction: TExprBase;
73 userVars: THashStrVariant;
75 {$INCLUDE ../shared/mapdef_tgc_def.inc}
77 public
78 function trigCenter (): TDFPoint; inline;
79 end;
81 function g_Triggers_Create (aTrigger: TTrigger; trec: TDynRecord; forceInternalIndex: Integer=-1): DWORD;
82 procedure g_Triggers_Update();
83 procedure g_Triggers_Press(ID: DWORD; ActivateType: Byte; ActivateUID: Word = 0);
84 function g_Triggers_PressR(X, Y: Integer; Width, Height: Word; UID: Word;
85 ActivateType: Byte; IgnoreList: DWArray = nil): DWArray;
86 procedure g_Triggers_PressL(X1, Y1, X2, Y2: Integer; UID: DWORD; ActivateType: Byte);
87 procedure g_Triggers_PressC(CX, CY: Integer; Radius: Word; UID: Word; ActivateType: Byte; IgnoreTrigger: Integer = -1);
88 procedure g_Triggers_OpenAll();
89 procedure g_Triggers_DecreaseSpawner(ID: DWORD);
90 procedure g_Triggers_Free();
91 procedure g_Triggers_SaveState (st: TStream);
92 procedure g_Triggers_LoadState (st: TStream);
95 var
96 gTriggerClientID: Integer = 0;
97 gTriggers: array of TTrigger;
98 gSecretsCount: Integer = 0;
99 gMonstersSpawned: array of LongInt = nil;
102 implementation
104 uses
105 {$IFDEF ENABLE_GFX}
106 g_gfx,
107 {$ENDIF}
108 {$IFDEF ENABLE_SHELLS}
109 g_shells,
110 {$ENDIF}
111 Math,
112 g_player, g_map, g_panel, g_game,
113 g_console, g_monsters, g_items, g_phys, g_weapons,
114 wadreader, e_log, g_language, e_res,
115 g_options, g_net, g_netmsg, utils, xparser, xstreams
118 const
119 TRIGGER_SIGNATURE = $58475254; // 'TRGX'
120 TRAP_DAMAGE = 1000;
122 {$INCLUDE ../shared/mapdef_tgc_impl.inc}
125 // ////////////////////////////////////////////////////////////////////////// //
126 type
127 TTrigScope = class(TExprScope)
128 private
129 plrprops: TPropHash;
130 monsprops: TPropHash;
131 platprops: TPropHash;
133 public
134 me: PTrigger;
136 public
137 constructor Create ();
138 destructor Destroy (); override;
140 function getObj (const aname: AnsiString): TObject; override;
141 function getField (obj: TObject; const afldname: AnsiString): Variant; override;
142 procedure setField (obj: TObject; const afldname: AnsiString; var aval: Variant); override;
143 end;
146 // ////////////////////////////////////////////////////////////////////////// //
147 type
148 TMyConstList = class(TExprConstList)
149 public
150 function valid (const cname: AnsiString): Boolean; override;
151 function get (const cname: AnsiString; out v: Variant): Boolean; override;
152 end;
155 // ////////////////////////////////////////////////////////////////////////// //
156 function TMyConstList.valid (const cname: AnsiString): Boolean;
157 begin
158 //writeln('CHECK: ''', cname, '''');
159 result :=
160 (cname = 'player') or
161 (cname = 'self') or
162 false;
163 end;
165 function TMyConstList.get (const cname: AnsiString; out v: Variant): Boolean;
166 var
167 eidx: Integer;
168 ebs: TDynEBS;
169 begin
170 //if (cname = 'answer') then begin v := LongInt(42); result := true; exit; end;
171 result := false;
172 if (gCurrentMap = nil) then exit;
173 for eidx := 0 to gCurrentMap.mapdef.ebsTypeCount-1 do
174 begin
175 ebs := gCurrentMap.mapdef.ebsTypeAt[eidx];
176 if ebs.has[cname] then
177 begin
178 //writeln('FOUND: ''', cname, '''');
179 v := ebs[cname];
180 result := true;
181 exit;
182 end;
183 end;
184 end;
187 // ////////////////////////////////////////////////////////////////////////// //
188 constructor TTrigScope.Create ();
189 begin
190 plrprops := TPropHash.Create(TPlayer, 'e');
191 monsprops := TPropHash.Create(TMonster, 'e');
192 platprops := TPropHash.Create(TPanel, 'e');
193 me := nil;
194 end;
197 destructor TTrigScope.Destroy ();
198 begin
199 platprops.Free();
200 monsprops.Free();
201 plrprops.Free();
202 inherited;
203 end;
206 function TTrigScope.getObj (const aname: AnsiString): TObject;
207 begin
208 if (aname = 'player') then result := gPlayers[0] //FIXME
209 else if (aname = 'self') or (aname = 'this') then result := TObject(Pointer(PtrUInt(1)))
210 else result := inherited getObj(aname);
211 end;
214 function TTrigScope.getField (obj: TObject; const afldname: AnsiString): Variant;
215 begin
216 if (obj = gPlayers[0]) then
217 begin
218 if plrprops.get(obj, afldname, result) then exit;
219 end
220 else if (obj = TObject(Pointer(PtrUInt(1)))) then
221 begin
222 if (me <> nil) and (me.userVars <> nil) then
223 begin
224 if me.userVars.get(afldname, result) then exit;
225 end;
226 end;
227 result := inherited getField(obj, afldname);
228 end;
231 procedure TTrigScope.setField (obj: TObject; const afldname: AnsiString; var aval: Variant);
232 begin
233 if (obj = gPlayers[0]) then
234 begin
235 if plrprops.put(obj, afldname, aval) then exit;
236 end
237 else if (obj = TObject(Pointer(PtrUInt(1)))) then
238 begin
239 if (me <> nil) then
240 begin
241 if (Length(afldname) > 4) and (afldname[1] = 'u') and (afldname[2] = 's') and
242 (afldname[3] = 'e') and (afldname[4] = 'r') then
243 begin
244 if (me.userVars = nil) then me.userVars := THashStrVariant.Create();
245 me.userVars.put(afldname, aval);
246 exit;
247 end;
248 end;
249 end;
250 inherited setField(obj, afldname, aval);
251 end;
254 // ////////////////////////////////////////////////////////////////////////// //
255 var
256 tgscope: TTrigScope = nil;
257 tgclist: TMyConstList = nil;
260 // ////////////////////////////////////////////////////////////////////////// //
261 function TTrigger.trigCenter (): TDFPoint; inline;
262 begin
263 result := TDFPoint.Create(x+width div 2, y+height div 2);
264 end;
267 function FindTrigger (): DWORD;
268 var
269 i, olen: Integer;
270 begin
271 olen := Length(gTriggers);
273 for i := 0 to olen-1 do
274 begin
275 if gTriggers[i].TriggerType = TRIGGER_NONE then begin result := i; exit; end;
276 end;
278 SetLength(gTriggers, olen+8);
279 result := olen;
281 for i := result to High(gTriggers) do
282 begin
283 gTriggers[i].TriggerType := TRIGGER_NONE;
284 gTriggers[i].trigDataRec := nil;
285 gTriggers[i].exoInit := nil;
286 gTriggers[i].exoThink := nil;
287 gTriggers[i].exoCheck := nil;
288 gTriggers[i].exoAction := nil;
289 gTriggers[i].userVars := nil;
290 end;
291 end;
294 function tr_CloseDoor (PanelGUID: Integer; NoSound: Boolean; d2d: Boolean): Boolean;
295 var
296 a, b, c: Integer;
297 pan: TPanel;
298 PanelID: Integer;
299 begin
300 result := false;
301 pan := g_Map_PanelByGUID(PanelGUID);
302 if (pan = nil) or not pan.isGWall then exit; //!FIXME!TRIGANY!
303 PanelID := pan.arrIdx;
305 if not d2d then
306 begin
307 with gWalls[PanelID] do
308 begin
309 if g_CollidePlayer(X, Y, Width, Height) or g_Mons_IsAnyAliveAt(X, Y, Width, Height) then Exit;
310 if not Enabled then
311 begin
312 if not NoSound then
313 begin
314 g_Sound_PlayExAt('SOUND_GAME_DOORCLOSE', X, Y);
315 if g_Game_IsServer and g_Game_IsNet then MH_SEND_Sound(X, Y, 'SOUND_GAME_DOORCLOSE');
316 end;
317 g_Map_EnableWallGUID(PanelGUID);
318 result := true;
319 end;
320 end;
321 end
322 else
323 begin
324 if (gDoorMap = nil) then exit;
326 c := -1;
327 for a := 0 to High(gDoorMap) do
328 begin
329 for b := 0 to High(gDoorMap[a]) do
330 begin
331 if gDoorMap[a, b] = DWORD(PanelID) then
332 begin
333 c := a;
334 break;
335 end;
336 end;
337 if (c <> -1) then break;
338 end;
339 if (c = -1) then exit;
341 for b := 0 to High(gDoorMap[c]) do
342 begin
343 with gWalls[gDoorMap[c, b]] do
344 begin
345 if g_CollidePlayer(X, Y, Width, Height) or g_Mons_IsAnyAliveAt(X, Y, Width, Height) then exit;
346 end;
347 end;
349 if not NoSound then
350 begin
351 for b := 0 to High(gDoorMap[c]) do
352 begin
353 if not gWalls[gDoorMap[c, b]].Enabled then
354 begin
355 with gWalls[PanelID] do
356 begin
357 g_Sound_PlayExAt('SOUND_GAME_DOORCLOSE', X, Y);
358 if g_Game_IsServer and g_Game_IsNet then MH_SEND_Sound(X, Y, 'SOUND_GAME_DOORCLOSE');
359 end;
360 break;
361 end;
362 end;
363 end;
365 for b := 0 to High(gDoorMap[c]) do
366 begin
367 if not gWalls[gDoorMap[c, b]].Enabled then
368 begin
369 g_Map_EnableWall_XXX(gDoorMap[c, b]);
370 result := true;
371 end;
372 end;
373 end;
374 end;
377 procedure tr_CloseTrap (PanelGUID: Integer; NoSound: Boolean; d2d: Boolean);
378 var
379 a, b, c: Integer;
380 wx, wy, wh, ww: Integer;
381 pan: TPanel;
382 PanelID: Integer;
384 function monsDamage (mon: TMonster): Boolean;
385 begin
386 result := false; // don't stop
387 if g_Obj_Collide(wx, wy, ww, wh, @mon.Obj) then mon.Damage(TRAP_DAMAGE, 0, 0, 0, HIT_TRAP);
388 end;
390 begin
391 pan := g_Map_PanelByGUID(PanelGUID);
393 if (pan = nil) then
394 begin
395 e_LogWritefln('tr_CloseTrap: pguid=%s; NO PANEL!', [PanelGUID], MSG_WARNING);
396 end
397 else
398 begin
399 e_LogWritefln('tr_CloseTrap: pguid=%s; isGWall=%s; arrIdx=%s', [PanelGUID, pan.isGWall, pan.arrIdx]);
400 end;
402 if (pan = nil) or not pan.isGWall then exit; //!FIXME!TRIGANY!
403 PanelID := pan.arrIdx;
405 if not d2d then
406 begin
407 with gWalls[PanelID] do
408 begin
409 if (not NoSound) and (not Enabled) then
410 begin
411 g_Sound_PlayExAt('SOUND_GAME_SWITCH1', X, Y);
412 if g_Game_IsServer and g_Game_IsNet then MH_SEND_Sound(X, Y, 'SOUND_GAME_SWITCH1');
413 end;
414 end;
416 wx := gWalls[PanelID].X;
417 wy := gWalls[PanelID].Y;
418 ww := gWalls[PanelID].Width;
419 wh := gWalls[PanelID].Height;
421 with gWalls[PanelID] do
422 begin
423 if gPlayers <> nil then
424 begin
425 for a := 0 to High(gPlayers) do
426 begin
427 if (gPlayers[a] <> nil) and gPlayers[a].alive and gPlayers[a].Collide(X, Y, Width, Height) then
428 begin
429 gPlayers[a].Damage(TRAP_DAMAGE, 0, 0, 0, HIT_TRAP);
430 end;
431 end;
432 end;
434 //g_Mons_ForEach(monsDamage);
435 g_Mons_ForEachAliveAt(wx, wy, ww, wh, monsDamage);
437 if not Enabled then g_Map_EnableWallGUID(PanelGUID);
438 end;
439 end
440 else
441 begin
442 if (gDoorMap = nil) then exit;
444 c := -1;
445 for a := 0 to High(gDoorMap) do
446 begin
447 for b := 0 to High(gDoorMap[a]) do
448 begin
449 if gDoorMap[a, b] = DWORD(PanelID) then
450 begin
451 c := a;
452 break;
453 end;
454 end;
455 if (c <> -1) then break;
456 end;
457 if (c = -1) then exit;
459 if not NoSound then
460 begin
461 for b := 0 to High(gDoorMap[c]) do
462 begin
463 if not gWalls[gDoorMap[c, b]].Enabled then
464 begin
465 with gWalls[PanelID] do
466 begin
467 g_Sound_PlayExAt('SOUND_GAME_SWITCH1', X, Y);
468 if g_Game_IsServer and g_Game_IsNet then MH_SEND_Sound(X, Y, 'SOUND_GAME_SWITCH1');
469 end;
470 Break;
471 end;
472 end;
473 end;
475 for b := 0 to High(gDoorMap[c]) do
476 begin
477 wx := gWalls[gDoorMap[c, b]].X;
478 wy := gWalls[gDoorMap[c, b]].Y;
479 ww := gWalls[gDoorMap[c, b]].Width;
480 wh := gWalls[gDoorMap[c, b]].Height;
482 with gWalls[gDoorMap[c, b]] do
483 begin
484 if gPlayers <> nil then
485 begin
486 for a := 0 to High(gPlayers) do
487 begin
488 if (gPlayers[a] <> nil) and gPlayers[a].alive and gPlayers[a].Collide(X, Y, Width, Height) then
489 begin
490 gPlayers[a].Damage(TRAP_DAMAGE, 0, 0, 0, HIT_TRAP);
491 end;
492 end;
493 end;
495 //g_Mons_ForEach(monsDamage);
496 g_Mons_ForEachAliveAt(wx, wy, ww, wh, monsDamage);
497 (*
498 if gMonsters <> nil then
499 for a := 0 to High(gMonsters) do
500 if (gMonsters[a] <> nil) and gMonsters[a].alive and
501 g_Obj_Collide(X, Y, Width, Height, @gMonsters[a].Obj) then
502 gMonsters[a].Damage(TRAP_DAMAGE, 0, 0, 0, HIT_TRAP);
503 *)
505 if not Enabled then g_Map_EnableWall_XXX(gDoorMap[c, b]);
506 end;
507 end;
508 end;
509 end;
512 function tr_OpenDoor (PanelGUID: Integer; NoSound: Boolean; d2d: Boolean): Boolean;
513 var
514 a, b, c: Integer;
515 pan: TPanel;
516 PanelID: Integer;
517 begin
518 result := false;
519 pan := g_Map_PanelByGUID(PanelGUID);
520 if (pan = nil) or not pan.isGWall then exit; //!FIXME!TRIGANY!
521 PanelID := pan.arrIdx;
523 if not d2d then
524 begin
525 with gWalls[PanelID] do
526 begin
527 if Enabled then
528 begin
529 if not NoSound then
530 begin
531 g_Sound_PlayExAt('SOUND_GAME_DOOROPEN', X, Y);
532 if g_Game_IsServer and g_Game_IsNet then MH_SEND_Sound(X, Y, 'SOUND_GAME_DOOROPEN');
533 end;
534 g_Map_DisableWallGUID(PanelGUID);
535 result := true;
536 end;
537 end
538 end
539 else
540 begin
541 if (gDoorMap = nil) then exit;
543 c := -1;
544 for a := 0 to High(gDoorMap) do
545 begin
546 for b := 0 to High(gDoorMap[a]) do
547 begin
548 if gDoorMap[a, b] = DWORD(PanelID) then
549 begin
550 c := a;
551 break;
552 end;
553 end;
554 if (c <> -1) then break;
555 end;
556 if (c = -1) then exit;
558 if not NoSound then
559 begin
560 for b := 0 to High(gDoorMap[c]) do
561 begin
562 if gWalls[gDoorMap[c, b]].Enabled then
563 begin
564 with gWalls[PanelID] do
565 begin
566 g_Sound_PlayExAt('SOUND_GAME_DOOROPEN', X, Y);
567 if g_Game_IsServer and g_Game_IsNet then MH_SEND_Sound(X, Y, 'SOUND_GAME_DOOROPEN');
568 end;
569 break;
570 end;
571 end;
572 end;
574 for b := 0 to High(gDoorMap[c]) do
575 begin
576 if gWalls[gDoorMap[c, b]].Enabled then
577 begin
578 g_Map_DisableWall_XXX(gDoorMap[c, b]);
579 result := true;
580 end;
581 end;
582 end;
583 end;
586 function tr_SetLift (PanelGUID: Integer; d: Integer; NoSound: Boolean; d2d: Boolean): Boolean;
587 var
588 a, b, c: Integer;
589 t: Integer = 0;
590 pan: TPanel;
591 PanelID: Integer;
592 begin
593 result := false;
594 pan := g_Map_PanelByGUID(PanelGUID);
595 if (pan = nil) or not pan.isGLift then exit; //!FIXME!TRIGANY!
596 PanelID := pan.arrIdx;
598 if (gLifts[PanelID].PanelType = PANEL_LIFTUP) or (gLifts[PanelID].PanelType = PANEL_LIFTDOWN) then
599 begin
600 case d of
601 0: t := LIFTTYPE_UP;
602 1: t := LIFTTYPE_DOWN;
603 else t := IfThen(gLifts[PanelID].LiftType = LIFTTYPE_DOWN, LIFTTYPE_UP, LIFTTYPE_DOWN);
604 end
605 end
606 else if (gLifts[PanelID].PanelType = PANEL_LIFTLEFT) or (gLifts[PanelID].PanelType = PANEL_LIFTRIGHT) then
607 begin
608 case d of
609 0: t := LIFTTYPE_LEFT;
610 1: t := LIFTTYPE_RIGHT;
611 else t := IfThen(gLifts[PanelID].LiftType = LIFTTYPE_LEFT, LIFTTYPE_RIGHT, LIFTTYPE_LEFT);
612 end;
613 end;
615 if not d2d then
616 begin
617 with gLifts[PanelID] do
618 begin
619 if (LiftType <> t) then
620 begin
621 g_Map_SetLiftGUID(PanelGUID, t); //???
622 //if not NoSound then g_Sound_PlayExAt('SOUND_GAME_SWITCH0', X, Y);
623 result := true;
624 end;
625 end;
626 end
627 else // Êàê â D2d
628 begin
629 if (gLiftMap = nil) then exit;
631 c := -1;
632 for a := 0 to High(gLiftMap) do
633 begin
634 for b := 0 to High(gLiftMap[a]) do
635 begin
636 if (gLiftMap[a, b] = DWORD(PanelID)) then
637 begin
638 c := a;
639 break;
640 end;
641 end;
642 if (c <> -1) then break;
643 end;
644 if (c = -1) then exit;
646 {if not NoSound then
647 for b := 0 to High(gLiftMap[c]) do
648 if gLifts[gLiftMap[c, b]].LiftType <> t then
649 begin
650 with gLifts[PanelID] do
651 g_Sound_PlayExAt('SOUND_GAME_SWITCH0', X, Y);
652 Break;
653 end;}
655 for b := 0 to High(gLiftMap[c]) do
656 begin
657 with gLifts[gLiftMap[c, b]] do
658 begin
659 if (LiftType <> t) then
660 begin
661 g_Map_SetLift_XXX(gLiftMap[c, b], t);
662 result := true;
663 end;
664 end;
665 end;
666 end;
667 end;
670 function tr_SpawnShot (ShotType: Integer; wx, wy, dx, dy: Integer; ShotSound: Boolean; ShotTarget: Word): Integer;
671 var
672 snd: string;
673 Projectile: Boolean;
674 begin
675 result := -1;
676 snd := 'SOUND_WEAPON_FIREROCKET';
677 Projectile := true;
679 case ShotType of
680 TRIGGER_SHOT_PISTOL:
681 begin
682 g_Weapon_pistol(wx, wy, dx, dy, 0, True);
683 snd := 'SOUND_WEAPON_FIREPISTOL';
684 Projectile := False;
685 if ShotSound then
686 begin
687 {$IFDEF ENABLE_SHELLS}
688 g_Shells_Create(wx, wy, 0, -2, SHELL_BULLET);
689 {$ENDIF}
690 if g_Game_IsNet then MH_SEND_Effect(wx, wy, 0, NET_GFX_SHELL1);
691 end;
692 end;
694 TRIGGER_SHOT_BULLET:
695 begin
696 g_Weapon_mgun(wx, wy, dx, dy, 0, True);
697 if gSoundEffectsDF then snd := 'SOUND_WEAPON_FIRECGUN'
698 else snd := 'SOUND_WEAPON_FIREPISTOL';
699 Projectile := False;
700 if ShotSound then
701 begin
702 {$IFDEF ENABLE_SHELLS}
703 g_Shells_Create(wx, wy, 0, -2, SHELL_BULLET);
704 {$ENDIF}
705 if g_Game_IsNet then MH_SEND_Effect(wx, wy, 0, NET_GFX_SHELL1);
706 end;
707 end;
709 TRIGGER_SHOT_SHOTGUN:
710 begin
711 g_Weapon_Shotgun(wx, wy, dx, dy, 0, True);
712 snd := 'SOUND_WEAPON_FIRESHOTGUN';
713 Projectile := False;
714 if ShotSound then
715 begin
716 {$IFDEF ENABLE_SHELLS}
717 g_Shells_Create(wx, wy, 0, -2, SHELL_SHELL);
718 {$ENDIF}
719 if g_Game_IsNet then MH_SEND_Effect(wx, wy, 0, NET_GFX_SHELL2);
720 end;
721 end;
723 TRIGGER_SHOT_SSG:
724 begin
725 g_Weapon_DShotgun(wx, wy, dx, dy, 0, True);
726 snd := 'SOUND_WEAPON_FIRESHOTGUN2';
727 Projectile := False;
728 if ShotSound then
729 begin
730 {$IFDEF ENABLE_SHELLS}
731 g_Shells_Create(wx, wy, 0, -2, SHELL_SHELL);
732 g_Shells_Create(wx, wy, 0, -2, SHELL_SHELL);
733 {$ENDIF}
734 if g_Game_IsNet then MH_SEND_Effect(wx, wy, 0, NET_GFX_SHELL3);
735 end;
736 end;
738 TRIGGER_SHOT_IMP:
739 begin
740 g_Weapon_ball1(wx, wy, dx, dy, 0, -1, True);
741 snd := 'SOUND_WEAPON_FIREBALL';
742 end;
744 TRIGGER_SHOT_PLASMA:
745 begin
746 g_Weapon_Plasma(wx, wy, dx, dy, 0, -1, True);
747 snd := 'SOUND_WEAPON_FIREPLASMA';
748 end;
750 TRIGGER_SHOT_SPIDER:
751 begin
752 g_Weapon_aplasma(wx, wy, dx, dy, 0, -1, True);
753 snd := 'SOUND_WEAPON_FIREPLASMA';
754 end;
756 TRIGGER_SHOT_CACO:
757 begin
758 g_Weapon_ball2(wx, wy, dx, dy, 0, -1, True);
759 snd := 'SOUND_WEAPON_FIREBALL';
760 end;
762 TRIGGER_SHOT_BARON:
763 begin
764 g_Weapon_ball7(wx, wy, dx, dy, 0, -1, True);
765 snd := 'SOUND_WEAPON_FIREBALL';
766 end;
768 TRIGGER_SHOT_MANCUB:
769 begin
770 g_Weapon_manfire(wx, wy, dx, dy, 0, -1, True);
771 snd := 'SOUND_WEAPON_FIREBALL';
772 end;
774 TRIGGER_SHOT_REV:
775 begin
776 g_Weapon_revf(wx, wy, dx, dy, 0, ShotTarget, -1, True);
777 snd := 'SOUND_WEAPON_FIREREV';
778 end;
780 TRIGGER_SHOT_ROCKET:
781 begin
782 g_Weapon_Rocket(wx, wy, dx, dy, 0, -1, True);
783 snd := 'SOUND_WEAPON_FIREROCKET';
784 end;
786 TRIGGER_SHOT_BFG:
787 begin
788 g_Weapon_BFGShot(wx, wy, dx, dy, 0, -1, True);
789 snd := 'SOUND_WEAPON_FIREBFG';
790 end;
792 TRIGGER_SHOT_EXPL:
793 begin
794 {$IFDEF ENABLE_GFX}
795 g_GFX_QueueEffect(R_GFX_EXPLODE_ROCKET, wx - 64, wy - 64);
796 {$ENDIF}
797 Projectile := False;
798 g_Weapon_Explode(wx, wy, 60, 0);
799 snd := 'SOUND_WEAPON_EXPLODEROCKET';
800 end;
802 TRIGGER_SHOT_BFGEXPL:
803 begin
804 {$IFDEF ENABLE_GFX}
805 g_GFX_QueueEffect(R_GFX_EXPLODE_BFG, wx - 64, wy - 64);
806 {$ENDIF}
807 Projectile := False;
808 g_Weapon_BFG9000(wx, wy, 0);
809 snd := 'SOUND_WEAPON_EXPLODEBFG';
810 end;
812 TRIGGER_SHOT_FLAME:
813 begin
814 g_Weapon_flame(wx, wy, dx, dy, 0, -1, True);
815 snd := 'SOUND_GAME_BURNING';
816 end;
818 else exit;
819 end;
821 if g_Game_IsNet and g_Game_IsServer then
822 begin
823 case ShotType of
824 TRIGGER_SHOT_EXPL: MH_SEND_Effect(wx, wy, Byte(ShotSound), NET_GFX_EXPLODE);
825 TRIGGER_SHOT_BFGEXPL: MH_SEND_Effect(wx, wy, Byte(ShotSound), NET_GFX_BFGEXPL);
826 else
827 begin
828 if Projectile then MH_SEND_CreateShot(LastShotID);
829 if ShotSound then MH_SEND_Sound(wx, wy, snd);
830 end;
831 end;
832 end;
834 if ShotSound then g_Sound_PlayExAt(snd, wx, wy);
836 if Projectile then Result := LastShotID;
837 end;
840 procedure MakeShot (var Trigger: TTrigger; wx, wy, dx, dy: Integer; TargetUID: Word);
841 begin
842 with Trigger do
843 begin
844 if (tgcAmmo = 0) or ((tgcAmmo > 0) and (ShotAmmoCount > 0)) then
845 begin
846 if (trigPanelGUID <> -1) and (ShotPanelTime = 0) then
847 begin
848 g_Map_SwitchTextureGUID({ShotPanelType,} trigPanelGUID);
849 ShotPanelTime := 4; // òèêîâ íà âñïûøêó âûñòðåëà
850 end;
852 if (tgcSight > 0) then ShotSightTimeout := 180; // ~= 5 ñåêóíä
854 if (ShotAmmoCount > 0) then Dec(ShotAmmoCount);
856 dx += Random(tgcAccuracy)-Random(tgcAccuracy);
857 dy += Random(tgcAccuracy)-Random(tgcAccuracy);
859 tr_SpawnShot(tgcShotType, wx, wy, dx, dy, tgcShotSound, TargetUID);
860 end
861 else
862 begin
863 if (tgcReload > 0) and (ShotReloadTime = 0) then
864 begin
865 ShotReloadTime := tgcReload; // òèêîâ íà ïåðåçàðÿäêó ïóøêè
866 end;
867 end;
868 end;
869 end;
872 procedure tr_MakeEffect (X, Y, VX, VY: Integer; T, ST, CR, CG, CB: Byte; Silent, Send: Boolean);
873 begin
874 {$IFDEF ENABLE_GFX}
875 if T = TRIGGER_EFFECT_PARTICLE then
876 begin
877 case ST of
878 TRIGGER_EFFECT_SLIQUID:
879 begin
880 if (CR = 255) and (CG = 0) and (CB = 0) then g_GFX_SimpleWater(X, Y, 1, VX, VY, 1, 0, 0, 0)
881 else if (CR = 0) and (CG = 255) and (CB = 0) then g_GFX_SimpleWater(X, Y, 1, VX, VY, 2, 0, 0, 0)
882 else if (CR = 0) and (CG = 0) and (CB = 255) then g_GFX_SimpleWater(X, Y, 1, VX, VY, 3, 0, 0, 0)
883 else g_GFX_SimpleWater(X, Y, 1, VX, VY, 0, 0, 0, 0);
884 end;
885 TRIGGER_EFFECT_LLIQUID: g_GFX_SimpleWater(X, Y, 1, VX, VY, 4, CR, CG, CB);
886 TRIGGER_EFFECT_DLIQUID: g_GFX_SimpleWater(X, Y, 1, VX, VY, 5, CR, CG, CB);
887 TRIGGER_EFFECT_BLOOD: g_GFX_Blood(X, Y, 1, VX, VY, 0, 0, CR, CG, CB);
888 TRIGGER_EFFECT_SPARK: g_GFX_Spark(X, Y, 1, GetAngle2(VX, VY), 0, 0);
889 TRIGGER_EFFECT_BUBBLE: g_GFX_Bubbles(X, Y, 1, 0, 0);
890 end;
891 end;
892 {$ENDIF}
894 if T = TRIGGER_EFFECT_ANIMATION then
895 begin
896 case ST of
897 EFFECT_TELEPORT: begin
898 if not Silent then g_Sound_PlayExAt('SOUND_GAME_TELEPORT', X, Y);
899 {$IFDEF ENABLE_GFX}
900 g_GFX_QueueEffect(R_GFX_TELEPORT_FAST, X - 32, Y - 32);
901 {$ENDIF}
902 if Send and g_Game_IsServer and g_Game_IsNet then MH_SEND_Effect(X, Y, Byte(not Silent), NET_GFX_TELE);
903 end;
904 EFFECT_RESPAWN: begin
905 if not Silent then g_Sound_PlayExAt('SOUND_ITEM_RESPAWNITEM', X, Y);
906 {$IFDEF ENABLE_GFX}
907 g_GFX_QueueEffect(R_GFX_ITEM_RESPAWN, X - 16, Y - 16);
908 {$ENDIF}
909 if Send and g_Game_IsServer and g_Game_IsNet then MH_SEND_Effect(X-16, Y-16, Byte(not Silent), NET_GFX_RESPAWN);
910 end;
911 EFFECT_FIRE: begin
912 if not Silent then g_Sound_PlayExAt('SOUND_FIRE', X, Y);
913 {$IFDEF ENABLE_GFX}
914 g_GFX_QueueEffect(R_GFX_FIRE, X - 32, Y - 128);
915 {$ENDIF}
916 if Send and g_Game_IsServer and g_Game_IsNet then MH_SEND_Effect(X-32, Y-128, Byte(not Silent), NET_GFX_FIRE);
917 end;
918 end;
919 end;
920 end;
923 function tr_Teleport (ActivateUID: Integer; TX, TY: Integer; TDir: Integer; Silent: Boolean; D2D: Boolean): Boolean;
924 var
925 p: TPlayer;
926 m: TMonster;
927 begin
928 Result := False;
929 if (ActivateUID < 0) or (ActivateUID > $FFFF) then Exit;
930 case g_GetUIDType(ActivateUID) of
931 UID_PLAYER:
932 begin
933 p := g_Player_Get(ActivateUID);
934 if p = nil then Exit;
935 if D2D then
936 begin
937 if p.TeleportTo(TX-(p.Obj.Rect.Width div 2), TY-p.Obj.Rect.Height, Silent, TDir) then result := true;
938 end
939 else
940 begin
941 if p.TeleportTo(TX, TY, Silent, TDir) then result := true;
942 end;
943 end;
944 UID_MONSTER:
945 begin
946 m := g_Monsters_ByUID(ActivateUID);
947 if m = nil then Exit;
948 if D2D then
949 begin
950 if m.TeleportTo(TX-(m.Obj.Rect.Width div 2), TY-m.Obj.Rect.Height, Silent, TDir) then result := true;
951 end
952 else
953 begin
954 if m.TeleportTo(TX, TY, Silent, TDir) then result := true;
955 end;
956 end;
957 end;
958 end;
961 function tr_Push (ActivateUID: Integer; VX, VY: Integer; ResetVel: Boolean): Boolean;
962 var
963 p: TPlayer;
964 m: TMonster;
965 begin
966 result := true;
967 if (ActivateUID < 0) or (ActivateUID > $FFFF) then exit;
968 case g_GetUIDType(ActivateUID) of
969 UID_PLAYER:
970 begin
971 p := g_Player_Get(ActivateUID);
972 if p = nil then Exit;
974 if ResetVel then
975 begin
976 p.GameVelX := 0;
977 p.GameVelY := 0;
978 p.GameAccelX := 0;
979 p.GameAccelY := 0;
980 end;
982 p.Push(VX, VY);
983 end;
985 UID_MONSTER:
986 begin
987 m := g_Monsters_ByUID(ActivateUID);
988 if m = nil then Exit;
990 if ResetVel then
991 begin
992 m.GameVelX := 0;
993 m.GameVelY := 0;
994 m.GameAccelX := 0;
995 m.GameAccelY := 0;
996 end;
998 m.Push(VX, VY);
999 end;
1000 end;
1001 end;
1004 function tr_Message (MKind: Integer; MText: string; MSendTo: Integer; MTime: Integer; ActivateUID: Integer): Boolean;
1005 var
1006 msg: string;
1007 p: TPlayer;
1008 i: Integer;
1009 begin
1010 Result := True;
1011 if (ActivateUID < 0) or (ActivateUID > $FFFF) then Exit;
1012 msg := b_Text_Format(MText);
1013 case MSendTo of
1014 TRIGGER_MESSAGE_DEST_ME: // activator
1015 begin
1016 if g_GetUIDType(ActivateUID) = UID_PLAYER then
1017 begin
1018 if g_Game_IsWatchedPlayer(ActivateUID) then
1019 begin
1020 if MKind = TRIGGER_MESSAGE_KIND_CHAT then g_Console_Add(msg, True)
1021 else if MKind = TRIGGER_MESSAGE_KIND_GAME then g_Game_Message(msg, MTime);
1022 end
1023 else
1024 begin
1025 p := g_Player_Get(ActivateUID);
1026 if g_Game_IsNet and (p.FClientID >= 0) then
1027 begin
1028 if MKind = TRIGGER_MESSAGE_KIND_CHAT then MH_SEND_Chat(msg, NET_CHAT_SYSTEM, p.FClientID)
1029 else if MKind = TRIGGER_MESSAGE_KIND_GAME then MH_SEND_GameEvent(NET_EV_BIGTEXT, MTime, msg, p.FClientID);
1030 end;
1031 end;
1032 end;
1033 end;
1035 TRIGGER_MESSAGE_DEST_MY_TEAM: // activator's team
1036 begin
1037 if g_GetUIDType(ActivateUID) = UID_PLAYER then
1038 begin
1039 p := g_Player_Get(ActivateUID);
1040 if g_Game_IsWatchedTeam(p.Team) then
1041 begin
1042 if MKind = TRIGGER_MESSAGE_KIND_CHAT then g_Console_Add(msg, True)
1043 else if MKind = TRIGGER_MESSAGE_KIND_GAME then g_Game_Message(msg, MTime);
1044 end;
1046 if g_Game_IsNet then
1047 begin
1048 for i := Low(gPlayers) to High(gPlayers) do
1049 begin
1050 if (gPlayers[i].Team = p.Team) and (gPlayers[i].FClientID >= 0) then
1051 begin
1052 if MKind = TRIGGER_MESSAGE_KIND_CHAT then MH_SEND_Chat(msg, NET_CHAT_SYSTEM, gPlayers[i].FClientID)
1053 else if MKind = TRIGGER_MESSAGE_KIND_GAME then MH_SEND_GameEvent(NET_EV_BIGTEXT, MTime, msg, gPlayers[i].FClientID);
1054 end;
1055 end;
1056 end;
1057 end;
1058 end;
1060 TRIGGER_MESSAGE_DEST_ENEMY_TEAM: // activator's enemy team
1061 begin
1062 if g_GetUIDType(ActivateUID) = UID_PLAYER then
1063 begin
1064 p := g_Player_Get(ActivateUID);
1065 if g_Game_IsWatchedTeam(p.Team) then
1066 begin
1067 if MKind = TRIGGER_MESSAGE_KIND_CHAT then g_Console_Add(msg, True)
1068 else if MKind = TRIGGER_MESSAGE_KIND_GAME then g_Game_Message(msg, MTime);
1069 end;
1071 if g_Game_IsNet then
1072 begin
1073 for i := Low(gPlayers) to High(gPlayers) do
1074 begin
1075 if (gPlayers[i].Team <> p.Team) and (gPlayers[i].FClientID >= 0) then
1076 begin
1077 if MKind = TRIGGER_MESSAGE_KIND_CHAT then MH_SEND_Chat(msg, NET_CHAT_SYSTEM, gPlayers[i].FClientID)
1078 else if MKind = TRIGGER_MESSAGE_KIND_GAME then MH_SEND_GameEvent(NET_EV_BIGTEXT, MTime, msg, gPlayers[i].FClientID);
1079 end;
1080 end;
1081 end;
1082 end;
1083 end;
1085 TRIGGER_MESSAGE_DEST_RED_TEAM: // red team
1086 begin
1087 if g_Game_IsWatchedTeam(TEAM_RED) then
1088 begin
1089 if MKind = TRIGGER_MESSAGE_KIND_CHAT then g_Console_Add(msg, True)
1090 else if MKind = TRIGGER_MESSAGE_KIND_GAME then g_Game_Message(msg, MTime);
1091 end;
1093 if g_Game_IsNet then
1094 begin
1095 for i := Low(gPlayers) to High(gPlayers) do
1096 begin
1097 if (gPlayers[i].Team = TEAM_RED) and (gPlayers[i].FClientID >= 0) then
1098 begin
1099 if MKind = TRIGGER_MESSAGE_KIND_CHAT then MH_SEND_Chat(msg, NET_CHAT_SYSTEM, gPlayers[i].FClientID)
1100 else if MKind = TRIGGER_MESSAGE_KIND_GAME then MH_SEND_GameEvent(NET_EV_BIGTEXT, MTime, msg, gPlayers[i].FClientID);
1101 end;
1102 end;
1103 end;
1104 end;
1106 TRIGGER_MESSAGE_DEST_BLUE_TEAM: // blue team
1107 begin
1108 if g_Game_IsWatchedTeam(TEAM_BLUE) then
1109 begin
1110 if MKind = TRIGGER_MESSAGE_KIND_CHAT then g_Console_Add(msg, True)
1111 else if MKind = TRIGGER_MESSAGE_KIND_GAME then g_Game_Message(msg, MTime);
1112 end;
1114 if g_Game_IsNet then
1115 begin
1116 for i := Low(gPlayers) to High(gPlayers) do
1117 begin
1118 if (gPlayers[i].Team = TEAM_BLUE) and (gPlayers[i].FClientID >= 0) then
1119 begin
1120 if MKind = TRIGGER_MESSAGE_KIND_CHAT then MH_SEND_Chat(msg, NET_CHAT_SYSTEM, gPlayers[i].FClientID)
1121 else if MKind = TRIGGER_MESSAGE_KIND_GAME then MH_SEND_GameEvent(NET_EV_BIGTEXT, MTime, msg, gPlayers[i].FClientID);
1122 end;
1123 end;
1124 end;
1125 end;
1127 TRIGGER_MESSAGE_DEST_EVERYONE: // everyone
1128 begin
1129 if MKind = TRIGGER_MESSAGE_KIND_CHAT then g_Console_Add(msg, True)
1130 else if MKind = TRIGGER_MESSAGE_KIND_GAME then g_Game_Message(msg, MTime);
1132 if g_Game_IsNet then
1133 begin
1134 if MKind = TRIGGER_MESSAGE_KIND_CHAT then MH_SEND_Chat(msg, NET_CHAT_SYSTEM)
1135 else if MKind = TRIGGER_MESSAGE_KIND_GAME then MH_SEND_GameEvent(NET_EV_BIGTEXT, MTime, msg);
1136 end;
1137 end;
1138 end;
1139 end;
1142 function tr_ShotAimCheck (var Trigger: TTrigger; Obj: PObj): Boolean;
1143 begin
1144 result := false;
1145 with Trigger do
1146 begin
1147 if TriggerType <> TRIGGER_SHOT then Exit;
1148 result := (tgcAim and TRIGGER_SHOT_AIM_ALLMAP > 0)
1149 or g_Obj_Collide(X, Y, Width, Height, Obj);
1150 if result and (tgcAim and TRIGGER_SHOT_AIM_TRACE > 0) then
1151 begin
1152 result := g_TraceVector(tgcTX, tgcTY,
1153 Obj^.X + Obj^.Rect.X + (Obj^.Rect.Width div 2),
1154 Obj^.Y + Obj^.Rect.Y + (Obj^.Rect.Height div 2));
1155 end;
1156 end;
1157 end;
1160 function ActivateTrigger (var Trigger: TTrigger; actType: Byte): Boolean;
1161 var
1162 animonce: Boolean;
1163 p: TPlayer;
1164 m: TMonster;
1165 pan: TPanel;
1166 idx, k, wx, wy, xd, yd: Integer;
1167 iid: LongWord;
1168 coolDown: Boolean;
1169 pAngle: Real;
1170 UIDType: Byte;
1171 TargetUID: Word;
1172 it: PItem;
1173 mon: TMonster;
1175 function monsShotTarget (mon: TMonster): Boolean;
1176 begin
1177 result := false; // don't stop
1178 if mon.alive and tr_ShotAimCheck(Trigger, @(mon.Obj)) then
1179 begin
1180 xd := mon.GameX + mon.Obj.Rect.Width div 2;
1181 yd := mon.GameY + mon.Obj.Rect.Height div 2;
1182 TargetUID := mon.UID;
1183 result := true; // stop
1184 end;
1185 end;
1187 function monsShotTargetMonPlr (mon: TMonster): Boolean;
1188 begin
1189 result := false; // don't stop
1190 if mon.alive and tr_ShotAimCheck(Trigger, @(mon.Obj)) then
1191 begin
1192 xd := mon.GameX + mon.Obj.Rect.Width div 2;
1193 yd := mon.GameY + mon.Obj.Rect.Height div 2;
1194 TargetUID := mon.UID;
1195 result := true; // stop
1196 end;
1197 end;
1199 function monShotTargetPlrMon (mon: TMonster): Boolean;
1200 begin
1201 result := false; // don't stop
1202 if mon.alive and tr_ShotAimCheck(Trigger, @(mon.Obj)) then
1203 begin
1204 xd := mon.GameX + mon.Obj.Rect.Width div 2;
1205 yd := mon.GameY + mon.Obj.Rect.Height div 2;
1206 TargetUID := mon.UID;
1207 result := true; // stop
1208 end;
1209 end;
1211 var
1212 tvval: Variant;
1213 begin
1214 result := false;
1215 if g_Game_IsClient then exit;
1217 if not Trigger.Enabled then exit;
1218 if (Trigger.TimeOut <> 0) and (actType <> ACTIVATE_CUSTOM) then exit;
1219 if (gLMSRespawn > LMS_RESPAWN_NONE) then exit;
1221 if (Trigger.exoCheck <> nil) then
1222 begin
1223 //conwritefln('exocheck: [%s]', [Trigger.exoCheck.toString()]);
1224 try
1225 tgscope.me := @Trigger;
1226 tvval := Trigger.exoCheck.value(tgscope);
1227 tgscope.me := nil;
1228 if not Boolean(tvval) then exit;
1229 except on e: Exception do
1230 begin
1231 tgscope.me := nil;
1232 conwritefln('trigger exocheck error: %s [%s]', [e.message, Trigger.exoCheck.toString()]);
1233 exit;
1234 end;
1235 end;
1236 end;
1238 animonce := False;
1240 coolDown := (actType <> 0);
1242 if (Trigger.exoAction <> nil) then
1243 begin
1244 //conwritefln('exoactivate: [%s]', [Trigger.exoAction.toString()]);
1245 try
1246 tgscope.me := @Trigger;
1247 Trigger.exoAction.value(tgscope);
1248 tgscope.me := nil;
1249 except on e: Exception do
1250 begin
1251 tgscope.me := nil;
1252 conwritefln('trigger exoactivate error: %s [%s]', [e.message, Trigger.exoAction.toString()]);
1253 exit;
1254 end;
1255 end;
1256 end;
1258 with Trigger do
1259 begin
1260 case TriggerType of
1261 TRIGGER_EXIT:
1262 begin
1263 g_Sound_PlayEx('SOUND_GAME_SWITCH0');
1264 if g_Game_IsNet then MH_SEND_Sound(X, Y, 'SOUND_GAME_SWITCH0');
1265 gExitByTrigger := True;
1266 g_Game_ExitLevel(tgcMap);
1267 TimeOut := 18;
1268 Result := True;
1270 Exit;
1271 end;
1273 TRIGGER_TELEPORT:
1274 begin
1275 Result := tr_Teleport(ActivateUID,
1276 tgcTarget.X, tgcTarget.Y,
1277 tgcDirection, tgcSilent,
1278 tgcD2d);
1279 TimeOut := 0;
1280 end;
1282 TRIGGER_OPENDOOR:
1283 begin
1284 Result := tr_OpenDoor(trigPanelGUID, tgcSilent, tgcD2d);
1285 TimeOut := 0;
1286 end;
1288 TRIGGER_CLOSEDOOR:
1289 begin
1290 Result := tr_CloseDoor(trigPanelGUID, tgcSilent, tgcD2d);
1291 TimeOut := 0;
1292 end;
1294 TRIGGER_DOOR, TRIGGER_DOOR5:
1295 begin
1296 pan := g_Map_PanelByGUID(trigPanelGUID);
1297 if (pan <> nil) and pan.isGWall then
1298 begin
1299 if gWalls[{trigPanelID}pan.arrIdx].Enabled then
1300 begin
1301 result := tr_OpenDoor(trigPanelGUID, tgcSilent, tgcD2d);
1302 if (TriggerType = TRIGGER_DOOR5) then DoorTime := 180;
1303 end
1304 else
1305 begin
1306 result := tr_CloseDoor(trigPanelGUID, tgcSilent, tgcD2d);
1307 end;
1309 if result then TimeOut := 18;
1310 end;
1311 end;
1313 TRIGGER_CLOSETRAP, TRIGGER_TRAP:
1314 begin
1315 tr_CloseTrap(trigPanelGUID, tgcSilent, tgcD2d);
1317 if TriggerType = TRIGGER_TRAP then
1318 begin
1319 DoorTime := 40;
1320 TimeOut := 76;
1321 end
1322 else
1323 begin
1324 DoorTime := -1;
1325 TimeOut := 0;
1326 end;
1328 Result := True;
1329 end;
1331 TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF:
1332 begin
1333 PressCount += 1;
1334 if PressTime = -1 then PressTime := tgcWait;
1335 if coolDown then TimeOut := 18 else TimeOut := 0;
1336 Result := True;
1337 end;
1339 TRIGGER_SECRET:
1340 if g_GetUIDType(ActivateUID) = UID_PLAYER then
1341 begin
1342 Enabled := False;
1343 Result := True;
1344 p := g_Player_Get(ActivateUID);
1345 p.GetSecret();
1346 Inc(gCoopSecretsFound);
1347 if g_Game_IsNet then
1348 begin
1349 MH_SEND_GameStats();
1350 MH_SEND_GameEvent(NET_EV_SECRET, p.UID, '');
1351 end;
1352 end;
1354 TRIGGER_LIFTUP:
1355 begin
1356 Result := tr_SetLift(trigPanelGUID, 0, tgcSilent, tgcD2d);
1357 TimeOut := 0;
1359 if (not tgcSilent) and Result then begin
1360 g_Sound_PlayExAt('SOUND_GAME_SWITCH0',
1361 X + (Width div 2),
1362 Y + (Height div 2));
1363 if g_Game_IsServer and g_Game_IsNet then
1364 MH_SEND_Sound(X + (Width div 2),
1365 Y + (Height div 2),
1366 'SOUND_GAME_SWITCH0');
1367 end;
1368 end;
1370 TRIGGER_LIFTDOWN:
1371 begin
1372 Result := tr_SetLift(trigPanelGUID, 1, tgcSilent, tgcD2d);
1373 TimeOut := 0;
1375 if (not tgcSilent) and Result then begin
1376 g_Sound_PlayExAt('SOUND_GAME_SWITCH0',
1377 X + (Width div 2),
1378 Y + (Height div 2));
1379 if g_Game_IsServer and g_Game_IsNet then
1380 MH_SEND_Sound(X + (Width div 2),
1381 Y + (Height div 2),
1382 'SOUND_GAME_SWITCH0');
1383 end;
1384 end;
1386 TRIGGER_LIFT:
1387 begin
1388 Result := tr_SetLift(trigPanelGUID, 3, tgcSilent, tgcD2d);
1390 if Result then
1391 begin
1392 TimeOut := 18;
1394 if (not tgcSilent) and Result then begin
1395 g_Sound_PlayExAt('SOUND_GAME_SWITCH0',
1396 X + (Width div 2),
1397 Y + (Height div 2));
1398 if g_Game_IsServer and g_Game_IsNet then
1399 MH_SEND_Sound(X + (Width div 2),
1400 Y + (Height div 2),
1401 'SOUND_GAME_SWITCH0');
1402 end;
1403 end;
1404 end;
1406 TRIGGER_TEXTURE:
1407 begin
1408 if tgcActivateOnce then
1409 begin
1410 Enabled := False;
1411 TriggerType := TRIGGER_NONE;
1412 end
1413 else
1414 if coolDown then
1415 TimeOut := 6
1416 else
1417 TimeOut := 0;
1419 animonce := tgcAnimateOnce;
1420 Result := True;
1421 end;
1423 TRIGGER_SOUND:
1424 begin
1425 if Sound <> nil then
1426 begin
1427 if tgcSoundSwitch and Sound.IsPlaying() then
1428 begin // Íóæíî âûêëþ÷èòü, åñëè èãðàë
1429 Sound.Stop();
1430 SoundPlayCount := 0;
1431 Result := True;
1432 end
1433 else // (not Data.SoundSwitch) or (not Sound.IsPlaying())
1434 if (tgcPlayCount > 0) or (not Sound.IsPlaying()) then
1435 begin
1436 if tgcPlayCount > 0 then
1437 SoundPlayCount := tgcPlayCount
1438 else // 0 - èãðàåì áåñêîíå÷íî
1439 SoundPlayCount := 1;
1440 Result := True;
1441 end;
1442 if g_Game_IsNet then MH_SEND_TriggerSound(Trigger);
1443 end;
1444 end;
1446 TRIGGER_SPAWNMONSTER:
1447 if (tgcSpawnMonsType in [MONSTER_DEMON..MONSTER_MAN]) then
1448 begin
1449 Result := False;
1450 if (tgcDelay > 0) and (actType <> ACTIVATE_CUSTOM) then
1451 begin
1452 AutoSpawn := not AutoSpawn;
1453 SpawnCooldown := 0;
1454 // Àâòîñïàâíåð ïåðåêëþ÷åí - ìåíÿåì òåêñòóðó
1455 Result := True;
1456 end;
1458 if ((tgcDelay = 0) and (actType <> ACTIVATE_CUSTOM))
1459 or ((tgcDelay > 0) and (actType = ACTIVATE_CUSTOM)) then
1460 for k := 1 to tgcMonsCount do
1461 begin
1462 if (actType = ACTIVATE_CUSTOM) and (tgcDelay > 0) then
1463 SpawnCooldown := -1; // Çàäåðæêà âûñòàâèòñÿ ìîíñòðîì ïðè óíè÷òîæåíèè
1464 if (tgcMax > 0) and (SpawnedCount >= tgcMax) then
1465 Break;
1467 mon := g_Monsters_Create(tgcSpawnMonsType,
1468 tgcTX, tgcTY,
1469 TDirection(tgcDirection), True);
1471 Result := True;
1473 // Çäîðîâüå:
1474 if (tgcHealth > 0) then
1475 mon.SetHealth(tgcHealth);
1476 // Óñòàíàâëèâàåì ïîâåäåíèå:
1477 mon.MonsterBehaviour := tgcBehaviour;
1478 mon.FNoRespawn := True;
1479 if g_Game_IsNet then
1480 MH_SEND_MonsterSpawn(mon.UID);
1481 // Èäåì èñêàòü öåëü, åñëè íàäî:
1482 if tgcActive then
1483 mon.WakeUp();
1485 if tgcSpawnMonsType <> MONSTER_BARREL then Inc(gTotalMonsters);
1487 if g_Game_IsNet then
1488 begin
1489 SetLength(gMonstersSpawned, Length(gMonstersSpawned)+1);
1490 gMonstersSpawned[High(gMonstersSpawned)] := mon.UID;
1491 end;
1493 mon.SpawnTrigger := ID;
1494 if tgcMax > 0 then Inc(SpawnedCount);
1496 case tgcEffect of
1497 EFFECT_TELEPORT:
1498 begin
1499 g_Sound_PlayExAt('SOUND_GAME_TELEPORT', tgcTX, tgcTY);
1500 {$IFDEF ENABLE_GFX}
1501 g_GFX_QueueEffect(
1502 R_GFX_TELEPORT_FAST,
1503 mon.Obj.X+mon.Obj.Rect.X+(mon.Obj.Rect.Width div 2)-32,
1504 mon.Obj.Y+mon.Obj.Rect.Y+(mon.Obj.Rect.Height div 2)-32
1505 );
1506 {$ENDIF}
1507 if g_Game_IsServer and g_Game_IsNet then
1508 MH_SEND_Effect(mon.Obj.X+mon.Obj.Rect.X+(mon.Obj.Rect.Width div 2)-32,
1509 mon.Obj.Y+mon.Obj.Rect.Y+(mon.Obj.Rect.Height div 2)-32, 1,
1510 NET_GFX_TELE);
1511 end;
1512 EFFECT_RESPAWN:
1513 begin
1514 g_Sound_PlayExAt('SOUND_ITEM_RESPAWNITEM', tgcTX, tgcTY);
1515 {$IFDEF ENABLE_GFX}
1516 g_GFX_QueueEffect(
1517 R_GFX_ITEM_RESPAWN,
1518 mon.Obj.X+mon.Obj.Rect.X+(mon.Obj.Rect.Width div 2)-16,
1519 mon.Obj.Y+mon.Obj.Rect.Y+(mon.Obj.Rect.Height div 2)-16
1520 );
1521 {$ENDIF}
1522 if g_Game_IsServer and g_Game_IsNet then
1523 MH_SEND_Effect(mon.Obj.X+mon.Obj.Rect.X+(mon.Obj.Rect.Width div 2)-16,
1524 mon.Obj.Y+mon.Obj.Rect.Y+(mon.Obj.Rect.Height div 2)-16, 1,
1525 NET_GFX_RESPAWN);
1526 end;
1527 EFFECT_FIRE:
1528 begin
1529 g_Sound_PlayExAt('SOUND_FIRE', tgcTX, tgcTY);
1530 {$IFDEF ENABLE_GFX}
1531 g_GFX_QueueEffect(
1532 R_GFX_FIRE,
1533 mon.Obj.X+mon.Obj.Rect.X+(mon.Obj.Rect.Width div 2)-32,
1534 mon.Obj.Y+mon.Obj.Rect.Y+mon.Obj.Rect.Height-128
1535 );
1536 {$ENDIF}
1537 if g_Game_IsServer and g_Game_IsNet then
1538 MH_SEND_Effect(mon.Obj.X+mon.Obj.Rect.X+(mon.Obj.Rect.Width div 2)-32,
1539 mon.Obj.Y+mon.Obj.Rect.Y+mon.Obj.Rect.Height-128, 1,
1540 NET_GFX_FIRE);
1541 end;
1542 end;
1543 end;
1544 if g_Game_IsNet then
1545 begin
1546 MH_SEND_GameStats();
1547 MH_SEND_CoopStats();
1548 end;
1550 if coolDown then
1551 TimeOut := 18
1552 else
1553 TimeOut := 0;
1554 // Åñëè àêòèâèðîâàí àâòîñïàâíåðîì, íå ìåíÿåì òåêñòóðó
1555 if actType = ACTIVATE_CUSTOM then
1556 Result := False;
1557 end;
1559 TRIGGER_SPAWNITEM:
1560 if (tgcSpawnItemType in [ITEM_MEDKIT_SMALL..ITEM_MAX]) then
1561 begin
1562 Result := False;
1563 if (tgcDelay > 0) and (actType <> ACTIVATE_CUSTOM) then
1564 begin
1565 AutoSpawn := not AutoSpawn;
1566 SpawnCooldown := 0;
1567 // Àâòîñïàâíåð ïåðåêëþ÷åí - ìåíÿåì òåêñòóðó
1568 Result := True;
1569 end;
1571 if ((tgcDelay = 0) and (actType <> ACTIVATE_CUSTOM))
1572 or ((tgcDelay > 0) and (actType = ACTIVATE_CUSTOM)) then
1573 if (not tgcDmonly) or
1574 (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF]) then
1575 for k := 1 to tgcItemCount do
1576 begin
1577 if (actType = ACTIVATE_CUSTOM) and (tgcDelay > 0) then
1578 SpawnCooldown := -1; // Çàäåðæêà âûñòàâèòñÿ èòåìîì ïðè óíè÷òîæåíèè
1579 if (tgcMax > 0) and (SpawnedCount >= tgcMax) then
1580 Break;
1582 iid := g_Items_Create(tgcTX, tgcTY,
1583 tgcSpawnItemType, tgcGravity, False, True);
1585 Result := True;
1587 it := g_Items_ByIdx(iid);
1588 it.SpawnTrigger := ID;
1589 if tgcMax > 0 then Inc(SpawnedCount);
1591 case tgcEffect of
1592 EFFECT_TELEPORT:
1593 begin
1594 it := g_Items_ByIdx(iid);
1595 g_Sound_PlayExAt('SOUND_GAME_TELEPORT', tgcTX, tgcTY);
1596 {$IFDEF ENABLE_GFX}
1597 g_GFX_QueueEffect(
1598 R_GFX_TELEPORT_FAST,
1599 it.Obj.X+it.Obj.Rect.X+(it.Obj.Rect.Width div 2)-32,
1600 it.Obj.Y+it.Obj.Rect.Y+(it.Obj.Rect.Height div 2)-32
1601 );
1602 {$ENDIF}
1603 if g_Game_IsServer and g_Game_IsNet then
1604 MH_SEND_Effect(it.Obj.X+it.Obj.Rect.X+(it.Obj.Rect.Width div 2)-32,
1605 it.Obj.Y+it.Obj.Rect.Y+(it.Obj.Rect.Height div 2)-32, 1,
1606 NET_GFX_TELE);
1607 end;
1608 EFFECT_RESPAWN:
1609 begin
1610 it := g_Items_ByIdx(iid);
1611 g_Sound_PlayExAt('SOUND_ITEM_RESPAWNITEM', tgcTX, tgcTY);
1612 {$IFDEF ENABLE_GFX}
1613 g_GFX_QueueEffect(
1614 R_GFX_ITEM_RESPAWN,
1615 it.Obj.X+it.Obj.Rect.X+(it.Obj.Rect.Width div 2)-16,
1616 it.Obj.Y+it.Obj.Rect.Y+(it.Obj.Rect.Height div 2)-16
1617 );
1618 {$ENDIF}
1619 if g_Game_IsServer and g_Game_IsNet then
1620 MH_SEND_Effect(it.Obj.X+it.Obj.Rect.X+(it.Obj.Rect.Width div 2)-16,
1621 it.Obj.Y+it.Obj.Rect.Y+(it.Obj.Rect.Height div 2)-16, 1,
1622 NET_GFX_RESPAWN);
1623 end;
1624 EFFECT_FIRE:
1625 begin
1626 it := g_Items_ByIdx(iid);
1627 g_Sound_PlayExAt('SOUND_FIRE', tgcTX, tgcTY);
1628 {$IFDEF ENABLE_GFX}
1629 g_GFX_QueueEffect(
1630 R_GFX_FIRE,
1631 it.Obj.X+it.Obj.Rect.X+(it.Obj.Rect.Width div 2)-32,
1632 it.Obj.Y+it.Obj.Rect.Y+it.Obj.Rect.Height-128
1633 );
1634 {$ENDIF}
1635 if g_Game_IsServer and g_Game_IsNet then
1636 MH_SEND_Effect(it.Obj.X+it.Obj.Rect.X+(it.Obj.Rect.Width div 2)-32,
1637 it.Obj.Y+it.Obj.Rect.Y+it.Obj.Rect.Height-128, 1,
1638 NET_GFX_FIRE);
1639 end;
1640 end;
1642 if g_Game_IsNet then
1643 MH_SEND_ItemSpawn(True, iid);
1644 end;
1646 if coolDown then
1647 TimeOut := 18
1648 else
1649 TimeOut := 0;
1650 // Åñëè àêòèâèðîâàí àâòîñïàâíåðîì, íå ìåíÿåì òåêñòóðó
1651 if actType = ACTIVATE_CUSTOM then
1652 Result := False;
1653 end;
1655 TRIGGER_MUSIC:
1656 begin
1657 // Ìåíÿåì ìóçûêó, åñëè åñòü íà ÷òî:
1658 if (Trigger.tgcMusicName <> '') then
1659 begin
1660 gMusic.SetByName(Trigger.tgcMusicName);
1661 gMusic.SpecPause := True;
1662 gMusic.Play();
1663 end;
1665 case Trigger.tgcMusicAction of
1666 TRIGGER_MUSIC_ACTION_STOP: // Âûêëþ÷èòü
1667 gMusic.SpecPause := True; // Ïàóçà
1668 TRIGGER_MUSIC_ACTION_PLAY: // Âêëþ÷èòü
1669 if gMusic.SpecPause then // Áûëà íà ïàóçå => èãðàòü
1670 gMusic.SpecPause := False
1671 else // Èãðàëà => ñíà÷àëà
1672 gMusic.SetPosition(0);
1673 end;
1675 if coolDown then
1676 TimeOut := 36
1677 else
1678 TimeOut := 0;
1679 Result := True;
1680 if g_Game_IsNet then MH_SEND_TriggerMusic;
1681 end;
1683 TRIGGER_PUSH:
1684 begin
1685 pAngle := -DegToRad(tgcAngle);
1686 Result := tr_Push(ActivateUID,
1687 Floor(Cos(pAngle)*tgcForce),
1688 Floor(Sin(pAngle)*tgcForce),
1689 tgcResetVelocity);
1690 TimeOut := 0;
1691 end;
1693 TRIGGER_SCORE:
1694 begin
1695 Result := False;
1696 // Ïðèáàâèòü èëè îòíÿòü î÷êî
1697 if (tgcScoreAction in [TRIGGER_SCORE_ACTION_ADD, TRIGGER_SCORE_ACTION_SUB]) and (tgcScoreCount > 0) then
1698 begin
1699 // Ñâîåé èëè ÷óæîé êîìàíäå
1700 if (tgcScoreTeam in [TRIGGER_SCORE_TEAM_MINE_RED, TRIGGER_SCORE_TEAM_MINE_BLUE]) and (g_GetUIDType(ActivateUID) = UID_PLAYER) then
1701 begin
1702 p := g_Player_Get(ActivateUID);
1703 if ((tgcScoreAction = TRIGGER_SCORE_ACTION_ADD) and (tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_RED) and (p.Team = TEAM_RED))
1704 or ((tgcScoreAction = TRIGGER_SCORE_ACTION_ADD) and (tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_BLUE) and (p.Team = TEAM_BLUE)) then
1705 begin
1706 Inc(gTeamStat[TEAM_RED].Goals, tgcScoreCount); // Red Scores
1708 if tgcScoreCon then
1709 begin
1710 if (tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_RED) then
1711 begin
1712 g_Console_Add(Format(_lc[I_PLAYER_SCORE_ADD_OWN], [p.Name, tgcScoreCount, _lc[I_PLAYER_SCORE_TO_RED]]), True);
1713 if g_Game_IsServer and g_Game_IsNet then
1714 MH_SEND_GameEvent(NET_EV_SCORE, p.UID or (tgcScoreCount shl 16), '+r');
1715 end else
1716 begin
1717 g_Console_Add(Format(_lc[I_PLAYER_SCORE_ADD_ENEMY], [p.Name, tgcScoreCount, _lc[I_PLAYER_SCORE_TO_RED]]), True);
1718 if g_Game_IsServer and g_Game_IsNet then
1719 MH_SEND_GameEvent(NET_EV_SCORE, p.UID or (tgcScoreCount shl 16), '+re');
1720 end;
1721 end;
1723 if tgcScoreMsg then
1724 begin
1725 g_Game_Message(Format(_lc[I_MESSAGE_SCORE_ADD], [AnsiUpperCase(_lc[I_GAME_TEAM_RED])]), 108);
1726 if g_Game_IsServer and g_Game_IsNet then
1727 MH_SEND_GameEvent(NET_EV_SCORE_MSG, TEAM_RED);
1728 end;
1729 end;
1730 if ((tgcScoreAction = TRIGGER_SCORE_ACTION_SUB) and (tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_RED) and (p.Team = TEAM_RED))
1731 or ((tgcScoreAction = TRIGGER_SCORE_ACTION_SUB) and (tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_BLUE) and (p.Team = TEAM_BLUE)) then
1732 begin
1733 Dec(gTeamStat[TEAM_RED].Goals, tgcScoreCount); // Red Fouls
1735 if tgcScoreCon then
1736 begin
1737 if (tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_RED) then
1738 begin
1739 g_Console_Add(Format(_lc[I_PLAYER_SCORE_SUB_OWN], [p.Name, tgcScoreCount, _lc[I_PLAYER_SCORE_TO_RED]]), True);
1740 if g_Game_IsServer and g_Game_IsNet then
1741 MH_SEND_GameEvent(NET_EV_SCORE, p.UID or (tgcScoreCount shl 16), '-r');
1742 end else
1743 begin
1744 g_Console_Add(Format(_lc[I_PLAYER_SCORE_SUB_ENEMY], [p.Name, tgcScoreCount, _lc[I_PLAYER_SCORE_TO_RED]]), True);
1745 if g_Game_IsServer and g_Game_IsNet then
1746 MH_SEND_GameEvent(NET_EV_SCORE, p.UID or (tgcScoreCount shl 16), '-re');
1747 end;
1748 end;
1750 if tgcScoreMsg then
1751 begin
1752 g_Game_Message(Format(_lc[I_MESSAGE_SCORE_SUB], [AnsiUpperCase(_lc[I_GAME_TEAM_RED])]), 108);
1753 if g_Game_IsServer and g_Game_IsNet then
1754 MH_SEND_GameEvent(NET_EV_SCORE_MSG, -TEAM_RED);
1755 end;
1756 end;
1757 if ((tgcScoreAction = TRIGGER_SCORE_ACTION_ADD) and (tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_RED) and (p.Team = TEAM_BLUE))
1758 or ((tgcScoreAction = TRIGGER_SCORE_ACTION_ADD) and (tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_BLUE) and (p.Team = TEAM_RED)) then
1759 begin
1760 Inc(gTeamStat[TEAM_BLUE].Goals, tgcScoreCount); // Blue Scores
1762 if tgcScoreCon then
1763 begin
1764 if (tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_RED) then
1765 begin
1766 g_Console_Add(Format(_lc[I_PLAYER_SCORE_ADD_OWN], [p.Name, tgcScoreCount, _lc[I_PLAYER_SCORE_TO_BLUE]]), True);
1767 if g_Game_IsServer and g_Game_IsNet then
1768 MH_SEND_GameEvent(NET_EV_SCORE, p.UID or (tgcScoreCount shl 16), '+b');
1769 end else
1770 begin
1771 g_Console_Add(Format(_lc[I_PLAYER_SCORE_ADD_ENEMY], [p.Name, tgcScoreCount, _lc[I_PLAYER_SCORE_TO_BLUE]]), True);
1772 if g_Game_IsServer and g_Game_IsNet then
1773 MH_SEND_GameEvent(NET_EV_SCORE, p.UID or (tgcScoreCount shl 16), '+be');
1774 end;
1775 end;
1777 if tgcScoreMsg then
1778 begin
1779 g_Game_Message(Format(_lc[I_MESSAGE_SCORE_ADD], [AnsiUpperCase(_lc[I_GAME_TEAM_BLUE])]), 108);
1780 if g_Game_IsServer and g_Game_IsNet then
1781 MH_SEND_GameEvent(NET_EV_SCORE_MSG, TEAM_BLUE);
1782 end;
1783 end;
1784 if ((tgcScoreAction = TRIGGER_SCORE_ACTION_SUB) and (tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_RED) and (p.Team = TEAM_BLUE))
1785 or ((tgcScoreAction = TRIGGER_SCORE_ACTION_SUB) and (tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_BLUE) and (p.Team = TEAM_RED)) then
1786 begin
1787 Dec(gTeamStat[TEAM_BLUE].Goals, tgcScoreCount); // Blue Fouls
1789 if tgcScoreCon then
1790 begin
1791 if (tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_RED) then
1792 begin
1793 g_Console_Add(Format(_lc[I_PLAYER_SCORE_SUB_OWN], [p.Name, tgcScoreCount, _lc[I_PLAYER_SCORE_TO_BLUE]]), True);
1794 if g_Game_IsServer and g_Game_IsNet then
1795 MH_SEND_GameEvent(NET_EV_SCORE, p.UID or (tgcScoreCount shl 16), '-b');
1796 end else
1797 begin
1798 g_Console_Add(Format(_lc[I_PLAYER_SCORE_SUB_ENEMY], [p.Name, tgcScoreCount, _lc[I_PLAYER_SCORE_TO_BLUE]]), True);
1799 if g_Game_IsServer and g_Game_IsNet then
1800 MH_SEND_GameEvent(NET_EV_SCORE, p.UID or (tgcScoreCount shl 16), '-be');
1801 end;
1802 end;
1804 if tgcScoreMsg then
1805 begin
1806 g_Game_Message(Format(_lc[I_MESSAGE_SCORE_SUB], [AnsiUpperCase(_lc[I_GAME_TEAM_BLUE])]), 108);
1807 if g_Game_IsServer and g_Game_IsNet then
1808 MH_SEND_GameEvent(NET_EV_SCORE_MSG, -TEAM_BLUE);
1809 end;
1810 end;
1811 Result := (p.Team = TEAM_RED) or (p.Team = TEAM_BLUE);
1812 end;
1813 // Êàêîé-òî êîíêðåòíîé êîìàíäå
1814 if tgcScoreTeam in [TRIGGER_SCORE_TEAM_FORCE_RED, TRIGGER_SCORE_TEAM_FORCE_BLUE] then
1815 begin
1816 if (tgcScoreAction = TRIGGER_SCORE_ACTION_ADD) and (tgcScoreTeam = TRIGGER_SCORE_TEAM_FORCE_RED) then
1817 begin
1818 Inc(gTeamStat[TEAM_RED].Goals, tgcScoreCount); // Red Scores
1820 if tgcScoreCon then
1821 begin
1822 g_Console_Add(Format(_lc[I_PLAYER_SCORE_ADD_TEAM], [_lc[I_PLAYER_SCORE_RED], tgcScoreCount]), True);
1823 if g_Game_IsServer and g_Game_IsNet then
1824 MH_SEND_GameEvent(NET_EV_SCORE, tgcScoreCount shl 16, '+tr');
1825 end;
1827 if tgcScoreMsg then
1828 begin
1829 g_Game_Message(Format(_lc[I_MESSAGE_SCORE_ADD], [AnsiUpperCase(_lc[I_GAME_TEAM_RED])]), 108);
1830 if g_Game_IsServer and g_Game_IsNet then
1831 MH_SEND_GameEvent(NET_EV_SCORE_MSG, TEAM_RED);
1832 end;
1833 end;
1834 if (tgcScoreAction = TRIGGER_SCORE_ACTION_SUB) and (tgcScoreTeam = TRIGGER_SCORE_TEAM_FORCE_RED) then
1835 begin
1836 Dec(gTeamStat[TEAM_RED].Goals, tgcScoreCount); // Red Fouls
1838 if tgcScoreCon then
1839 begin
1840 g_Console_Add(Format(_lc[I_PLAYER_SCORE_SUB_TEAM], [_lc[I_PLAYER_SCORE_RED], tgcScoreCount]), True);
1841 if g_Game_IsServer and g_Game_IsNet then
1842 MH_SEND_GameEvent(NET_EV_SCORE, tgcScoreCount shl 16, '-tr');
1843 end;
1845 if tgcScoreMsg then
1846 begin
1847 g_Game_Message(Format(_lc[I_MESSAGE_SCORE_SUB], [AnsiUpperCase(_lc[I_GAME_TEAM_RED])]), 108);
1848 if g_Game_IsServer and g_Game_IsNet then
1849 MH_SEND_GameEvent(NET_EV_SCORE_MSG, -TEAM_RED);
1850 end;
1851 end;
1852 if (tgcScoreAction = TRIGGER_SCORE_ACTION_ADD) and (tgcScoreTeam = TRIGGER_SCORE_TEAM_FORCE_BLUE) then
1853 begin
1854 Inc(gTeamStat[TEAM_BLUE].Goals, tgcScoreCount); // Blue Scores
1856 if tgcScoreCon then
1857 begin
1858 g_Console_Add(Format(_lc[I_PLAYER_SCORE_ADD_TEAM], [_lc[I_PLAYER_SCORE_BLUE], tgcScoreCount]), True);
1859 if g_Game_IsServer and g_Game_IsNet then
1860 MH_SEND_GameEvent(NET_EV_SCORE, tgcScoreCount shl 16, '+tb');
1861 end;
1863 if tgcScoreMsg then
1864 begin
1865 g_Game_Message(Format(_lc[I_MESSAGE_SCORE_ADD], [AnsiUpperCase(_lc[I_GAME_TEAM_BLUE])]), 108);
1866 if g_Game_IsServer and g_Game_IsNet then
1867 MH_SEND_GameEvent(NET_EV_SCORE_MSG, TEAM_BLUE);
1868 end;
1869 end;
1870 if (tgcScoreAction = TRIGGER_SCORE_ACTION_SUB) and (tgcScoreTeam = TRIGGER_SCORE_TEAM_FORCE_BLUE) then
1871 begin
1872 Dec(gTeamStat[TEAM_BLUE].Goals, tgcScoreCount); // Blue Fouls
1874 if tgcScoreCon then
1875 begin
1876 g_Console_Add(Format(_lc[I_PLAYER_SCORE_SUB_TEAM], [_lc[I_PLAYER_SCORE_BLUE], tgcScoreCount]), True);
1877 if g_Game_IsServer and g_Game_IsNet then
1878 MH_SEND_GameEvent(NET_EV_SCORE, tgcScoreCount shl 16, '-tb');
1879 end;
1881 if tgcScoreMsg then
1882 begin
1883 g_Game_Message(Format(_lc[I_MESSAGE_SCORE_SUB], [AnsiUpperCase(_lc[I_GAME_TEAM_BLUE])]), 108);
1884 if g_Game_IsServer and g_Game_IsNet then
1885 MH_SEND_GameEvent(NET_EV_SCORE_MSG, -TEAM_BLUE);
1886 end;
1887 end;
1888 Result := True;
1889 end;
1890 end;
1891 // Âûèãðûø
1892 if (tgcScoreAction = TRIGGER_SCORE_ACTION_WIN) and (gGameSettings.GoalLimit > 0) then
1893 begin
1894 // Ñâîåé èëè ÷óæîé êîìàíäû
1895 if (tgcScoreTeam in [TRIGGER_SCORE_TEAM_MINE_RED, TRIGGER_SCORE_TEAM_MINE_BLUE]) and (g_GetUIDType(ActivateUID) = UID_PLAYER) then
1896 begin
1897 p := g_Player_Get(ActivateUID);
1898 if ((tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_RED) and (p.Team = TEAM_RED)) // Red Wins
1899 or ((tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_BLUE) and (p.Team = TEAM_BLUE)) then
1900 begin
1901 if gTeamStat[TEAM_RED].Goals < SmallInt(gGameSettings.GoalLimit) then
1902 begin
1903 gTeamStat[TEAM_RED].Goals := gGameSettings.GoalLimit;
1905 if tgcScoreCon then
1906 begin
1907 if (tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_RED) then
1908 begin
1909 g_Console_Add(Format(_lc[I_PLAYER_SCORE_WIN_OWN], [p.Name, _lc[I_PLAYER_SCORE_TO_RED]]), True);
1910 if g_Game_IsServer and g_Game_IsNet then
1911 MH_SEND_GameEvent(NET_EV_SCORE, p.UID, 'wr');
1912 end else
1913 begin
1914 g_Console_Add(Format(_lc[I_PLAYER_SCORE_WIN_ENEMY], [p.Name, _lc[I_PLAYER_SCORE_TO_RED]]), True);
1915 if g_Game_IsServer and g_Game_IsNet then
1916 MH_SEND_GameEvent(NET_EV_SCORE, p.UID, 'wre');
1917 end;
1918 end;
1920 Result := True;
1921 end;
1922 end;
1923 if ((tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_RED) and (p.Team = TEAM_BLUE)) // Blue Wins
1924 or ((tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_BLUE) and (p.Team = TEAM_RED)) then
1925 begin
1926 if gTeamStat[TEAM_BLUE].Goals < SmallInt(gGameSettings.GoalLimit) then
1927 begin
1928 gTeamStat[TEAM_BLUE].Goals := gGameSettings.GoalLimit;
1930 if tgcScoreCon then
1931 begin
1932 if (tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_RED) then
1933 begin
1934 g_Console_Add(Format(_lc[I_PLAYER_SCORE_WIN_OWN], [p.Name, _lc[I_PLAYER_SCORE_TO_BLUE]]), True);
1935 if g_Game_IsServer and g_Game_IsNet then
1936 MH_SEND_GameEvent(NET_EV_SCORE, p.UID, 'wb');
1937 end else
1938 begin
1939 g_Console_Add(Format(_lc[I_PLAYER_SCORE_WIN_ENEMY], [p.Name, _lc[I_PLAYER_SCORE_TO_BLUE]]), True);
1940 if g_Game_IsServer and g_Game_IsNet then
1941 MH_SEND_GameEvent(NET_EV_SCORE, p.UID, 'wbe');
1942 end;
1943 end;
1945 Result := True;
1946 end;
1947 end;
1948 end;
1949 // Êàêîé-òî êîíêðåòíîé êîìàíäû
1950 if tgcScoreTeam in [TRIGGER_SCORE_TEAM_FORCE_RED, TRIGGER_SCORE_TEAM_FORCE_BLUE] then
1951 begin
1952 if (tgcScoreTeam = TRIGGER_SCORE_TEAM_FORCE_RED) then // Red Wins
1953 begin
1954 if gTeamStat[TEAM_RED].Goals < SmallInt(gGameSettings.GoalLimit) then
1955 begin
1956 gTeamStat[TEAM_RED].Goals := gGameSettings.GoalLimit;
1957 Result := True;
1958 end;
1959 end;
1960 if (tgcScoreTeam = TRIGGER_SCORE_TEAM_FORCE_BLUE) then // Blue Wins
1961 begin
1962 if gTeamStat[TEAM_BLUE].Goals < SmallInt(gGameSettings.GoalLimit) then
1963 begin
1964 gTeamStat[TEAM_BLUE].Goals := gGameSettings.GoalLimit;
1965 Result := True;
1966 end;
1967 end;
1968 end;
1969 end;
1970 // Ïðîèãðûø
1971 if (tgcScoreAction = TRIGGER_SCORE_ACTION_LOOSE) and (gGameSettings.GoalLimit > 0) then
1972 begin
1973 // Ñâîåé èëè ÷óæîé êîìàíäû
1974 if (tgcScoreTeam in [TRIGGER_SCORE_TEAM_MINE_RED, TRIGGER_SCORE_TEAM_MINE_BLUE]) and (g_GetUIDType(ActivateUID) = UID_PLAYER) then
1975 begin
1976 p := g_Player_Get(ActivateUID);
1977 if ((tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_RED) and (p.Team = TEAM_BLUE)) // Red Wins
1978 or ((tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_BLUE) and (p.Team = TEAM_RED)) then
1979 if gTeamStat[TEAM_RED].Goals < SmallInt(gGameSettings.GoalLimit) then
1980 begin
1981 gTeamStat[TEAM_RED].Goals := gGameSettings.GoalLimit;
1983 if tgcScoreCon then
1984 if tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_RED then
1985 begin
1986 g_Console_Add(Format(_lc[I_PLAYER_SCORE_WIN_ENEMY], [p.Name, _lc[I_PLAYER_SCORE_TO_RED]]), True);
1987 if g_Game_IsServer and g_Game_IsNet then
1988 MH_SEND_GameEvent(NET_EV_SCORE, p.UID, 'wre');
1989 end else
1990 begin
1991 g_Console_Add(Format(_lc[I_PLAYER_SCORE_WIN_OWN], [p.Name, _lc[I_PLAYER_SCORE_TO_RED]]), True);
1992 if g_Game_IsServer and g_Game_IsNet then
1993 MH_SEND_GameEvent(NET_EV_SCORE, p.UID, 'wr');
1994 end;
1996 Result := True;
1997 end;
1998 if ((tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_RED) and (p.Team = TEAM_RED)) // Blue Wins
1999 or ((tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_BLUE) and (p.Team = TEAM_BLUE)) then
2000 if gTeamStat[TEAM_BLUE].Goals < SmallInt(gGameSettings.GoalLimit) then
2001 begin
2002 gTeamStat[TEAM_BLUE].Goals := gGameSettings.GoalLimit;
2004 if tgcScoreCon then
2005 if tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_RED then
2006 begin
2007 g_Console_Add(Format(_lc[I_PLAYER_SCORE_WIN_ENEMY], [p.Name, _lc[I_PLAYER_SCORE_TO_BLUE]]), True);
2008 if g_Game_IsServer and g_Game_IsNet then
2009 MH_SEND_GameEvent(NET_EV_SCORE, p.UID, 'wbe');
2010 end else
2011 begin
2012 g_Console_Add(Format(_lc[I_PLAYER_SCORE_WIN_OWN], [p.Name, _lc[I_PLAYER_SCORE_TO_BLUE]]), True);
2013 if g_Game_IsServer and g_Game_IsNet then
2014 MH_SEND_GameEvent(NET_EV_SCORE, p.UID, 'wb');
2015 end;
2017 Result := True;
2018 end;
2019 end;
2020 // Êàêîé-òî êîíêðåòíîé êîìàíäû
2021 if tgcScoreTeam in [TRIGGER_SCORE_TEAM_FORCE_BLUE, TRIGGER_SCORE_TEAM_FORCE_RED] then
2022 begin
2023 if (tgcScoreTeam = TRIGGER_SCORE_TEAM_FORCE_BLUE) then // Red Wins
2024 begin
2025 if gTeamStat[TEAM_RED].Goals < SmallInt(gGameSettings.GoalLimit) then
2026 begin
2027 gTeamStat[TEAM_RED].Goals := gGameSettings.GoalLimit;
2028 Result := True;
2029 end;
2030 end;
2031 if (tgcScoreTeam = TRIGGER_SCORE_TEAM_FORCE_RED) then // Blue Wins
2032 begin
2033 if gTeamStat[TEAM_BLUE].Goals < SmallInt(gGameSettings.GoalLimit) then
2034 begin
2035 gTeamStat[TEAM_BLUE].Goals := gGameSettings.GoalLimit;
2036 Result := True;
2037 end;
2038 end;
2039 end;
2040 end;
2041 if Result then begin
2042 if coolDown then
2043 TimeOut := 18
2044 else
2045 TimeOut := 0;
2046 if g_Game_IsServer and g_Game_IsNet then
2047 MH_SEND_GameStats;
2048 end;
2049 end;
2051 TRIGGER_MESSAGE:
2052 begin
2053 Result := tr_Message(tgcKind, tgcText,
2054 tgcMsgDest, tgcMsgTime,
2055 ActivateUID);
2056 TimeOut := 18;
2057 end;
2059 TRIGGER_DAMAGE, TRIGGER_HEALTH:
2060 begin
2061 Result := False;
2062 UIDType := g_GetUIDType(ActivateUID);
2063 if (UIDType = UID_PLAYER) or (UIDType = UID_MONSTER) then
2064 begin
2065 Result := True;
2066 k := -1;
2067 if coolDown then
2068 begin
2069 // Âñïîìèíàåì, àêòèâèðîâàë ëè îí ìåíÿ ðàíüøå
2070 for idx := 0 to High(Activators) do
2071 if Activators[idx].UID = ActivateUID then
2072 begin
2073 k := idx;
2074 Break;
2075 end;
2076 if k = -1 then
2077 begin // Âèäèì åãî âïåðâûå
2078 // Çàïîìèíàåì åãî
2079 SetLength(Activators, Length(Activators) + 1);
2080 k := High(Activators);
2081 Activators[k].UID := ActivateUID;
2082 end else
2083 begin // Óæå âèäåëè åãî
2084 // Åñëè èíòåðâàë îòêëþ÷¸í, íî îí âñ¸ åù¸ â çîíå ïîðàæåíèÿ, äà¸ì åìó âðåìÿ
2085 if (tgcInterval = 0) and (Activators[k].TimeOut > 0) then
2086 Activators[k].TimeOut := 65535;
2087 // Òàéìàóò ïðîø¸ë - ðàáîòàåì
2088 Result := Activators[k].TimeOut = 0;
2089 end;
2090 end;
2092 if Result then
2093 begin
2094 case UIDType of
2095 UID_PLAYER:
2096 begin
2097 p := g_Player_Get(ActivateUID);
2098 if p = nil then
2099 Exit;
2101 // Íàíîñèì óðîí èãðîêó
2102 if (TriggerType = TRIGGER_DAMAGE) and (tgcAmount > 0) then
2103 begin
2104 // Êèñëîòíûé óðîí íå íàíîñèòñÿ êîãäà åñòü êîñòþì
2105 // "Âîäÿíîé" óðîí íå íàíîñèòñÿ êîãäà åñòü êèñëîðîä
2106 if not (((tgcKind = HIT_ACID) and (p.FMegaRulez[MR_SUIT] > gTime)) or
2107 ((tgcKind = HIT_WATER) and (p.Air > 0))) then
2108 p.Damage(tgcAmount, 0, 0, 0, tgcKind);
2109 if (tgcKind = HIT_FLAME) then p.CatchFire(0);
2110 end;
2112 // Ëå÷èì èãðîêà
2113 if (TriggerType = TRIGGER_HEALTH) and (tgcAmount > 0) then
2114 if p.Heal(tgcAmount, not tgcHealMax) and (not tgcSilent) then
2115 begin
2116 g_Sound_PlayExAt('SOUND_ITEM_GETITEM', p.Obj.X, p.Obj.Y);
2117 if g_Game_IsServer and g_Game_IsNet then
2118 MH_SEND_Sound(p.Obj.X, p.Obj.Y, 'SOUND_ITEM_GETITEM');
2119 end;
2120 end;
2122 UID_MONSTER:
2123 begin
2124 m := g_Monsters_ByUID(ActivateUID);
2125 if m = nil then
2126 Exit;
2128 // Íàíîñèì óðîí ìîíñòðó
2129 if (TriggerType = TRIGGER_DAMAGE) and (tgcAmount > 0) then
2130 begin
2131 m.Damage(tgcAmount, 0, 0, 0, tgcKind);
2132 if (tgcKind = HIT_FLAME) then m.CatchFire(0);
2133 end;
2135 // Ëå÷èì ìîíñòðà
2136 if (TriggerType = TRIGGER_HEALTH) and (tgcAmount > 0) then
2137 if m.Heal(tgcAmount) and (not tgcSilent) then
2138 begin
2139 g_Sound_PlayExAt('SOUND_ITEM_GETITEM', m.Obj.X, m.Obj.Y);
2140 if g_Game_IsServer and g_Game_IsNet then
2141 MH_SEND_Sound(m.Obj.X, m.Obj.Y, 'SOUND_ITEM_GETITEM');
2142 end;
2143 end;
2144 end;
2145 // Íàçíà÷àåì âðåìÿ ñëåäóþùåãî âîçäåéñòâèÿ
2146 idx := tgcInterval;
2147 if coolDown then
2148 if idx > 0 then
2149 Activators[k].TimeOut := idx
2150 else
2151 Activators[k].TimeOut := 65535;
2152 end;
2153 end;
2154 TimeOut := 0;
2155 end;
2157 TRIGGER_SHOT:
2158 begin
2159 if ShotSightTime > 0 then
2160 Exit;
2162 // put this at the beginning so it doesn't trigger itself
2163 TimeOut := tgcWait + 1;
2165 wx := tgcTX;
2166 wy := tgcTY;
2167 pAngle := -DegToRad(tgcAngle);
2168 xd := wx + Round(Cos(pAngle) * 32.0);
2169 yd := wy + Round(Sin(pAngle) * 32.0);
2170 TargetUID := 0;
2172 case tgcShotTarget of
2173 TRIGGER_SHOT_TARGET_MON: // monsters
2174 //TODO: accelerate this!
2175 g_Mons_ForEachAlive(monsShotTarget);
2177 TRIGGER_SHOT_TARGET_PLR: // players
2178 if gPlayers <> nil then
2179 for idx := Low(gPlayers) to High(gPlayers) do
2180 if (gPlayers[idx] <> nil) and gPlayers[idx].alive and
2181 tr_ShotAimCheck(Trigger, @(gPlayers[idx].Obj)) then
2182 begin
2183 xd := gPlayers[idx].GameX + PLAYER_RECT_CX;
2184 yd := gPlayers[idx].GameY + PLAYER_RECT_CY;
2185 TargetUID := gPlayers[idx].UID;
2186 break;
2187 end;
2189 TRIGGER_SHOT_TARGET_RED: // red team
2190 if gPlayers <> nil then
2191 for idx := Low(gPlayers) to High(gPlayers) do
2192 if (gPlayers[idx] <> nil) and gPlayers[idx].alive and
2193 (gPlayers[idx].Team = TEAM_RED) and
2194 tr_ShotAimCheck(Trigger, @(gPlayers[idx].Obj)) then
2195 begin
2196 xd := gPlayers[idx].GameX + PLAYER_RECT_CX;
2197 yd := gPlayers[idx].GameY + PLAYER_RECT_CY;
2198 TargetUID := gPlayers[idx].UID;
2199 break;
2200 end;
2202 TRIGGER_SHOT_TARGET_BLUE: // blue team
2203 if gPlayers <> nil then
2204 for idx := Low(gPlayers) to High(gPlayers) do
2205 if (gPlayers[idx] <> nil) and gPlayers[idx].alive and
2206 (gPlayers[idx].Team = TEAM_BLUE) and
2207 tr_ShotAimCheck(Trigger, @(gPlayers[idx].Obj)) then
2208 begin
2209 xd := gPlayers[idx].GameX + PLAYER_RECT_CX;
2210 yd := gPlayers[idx].GameY + PLAYER_RECT_CY;
2211 TargetUID := gPlayers[idx].UID;
2212 break;
2213 end;
2215 TRIGGER_SHOT_TARGET_MONPLR: // monsters then players
2216 begin
2217 //TODO: accelerate this!
2218 g_Mons_ForEachAlive(monsShotTargetMonPlr);
2220 if (TargetUID = 0) and (gPlayers <> nil) then
2221 for idx := Low(gPlayers) to High(gPlayers) do
2222 if (gPlayers[idx] <> nil) and gPlayers[idx].alive and
2223 tr_ShotAimCheck(Trigger, @(gPlayers[idx].Obj)) then
2224 begin
2225 xd := gPlayers[idx].GameX + PLAYER_RECT_CX;
2226 yd := gPlayers[idx].GameY + PLAYER_RECT_CY;
2227 TargetUID := gPlayers[idx].UID;
2228 break;
2229 end;
2230 end;
2232 TRIGGER_SHOT_TARGET_PLRMON: // players then monsters
2233 begin
2234 if gPlayers <> nil then
2235 for idx := Low(gPlayers) to High(gPlayers) do
2236 if (gPlayers[idx] <> nil) and gPlayers[idx].alive and
2237 tr_ShotAimCheck(Trigger, @(gPlayers[idx].Obj)) then
2238 begin
2239 xd := gPlayers[idx].GameX + PLAYER_RECT_CX;
2240 yd := gPlayers[idx].GameY + PLAYER_RECT_CY;
2241 TargetUID := gPlayers[idx].UID;
2242 break;
2243 end;
2244 if TargetUID = 0 then
2245 begin
2246 //TODO: accelerate this!
2247 g_Mons_ForEachAlive(monShotTargetPlrMon);
2248 end;
2249 end;
2251 else begin
2252 if (tgcShotTarget <> TRIGGER_SHOT_TARGET_NONE) or
2253 (tgcShotType <> TRIGGER_SHOT_REV) then
2254 TargetUID := ActivateUID;
2255 end;
2256 end;
2258 if (tgcShotTarget = TRIGGER_SHOT_TARGET_NONE) or (TargetUID > 0) or
2259 ((tgcShotTarget > TRIGGER_SHOT_TARGET_NONE) and (TargetUID = 0)) then
2260 begin
2261 Result := True;
2262 if (tgcSight = 0) or
2263 (tgcShotTarget = TRIGGER_SHOT_TARGET_NONE) or
2264 (TargetUID = ShotSightTarget) then
2265 MakeShot(Trigger, wx, wy, xd, yd, TargetUID)
2266 else
2267 begin
2268 ShotSightTime := tgcSight;
2269 ShotSightTargetN := TargetUID;
2270 if tgcShotType = TRIGGER_SHOT_BFG then
2271 begin
2272 g_Sound_PlayExAt('SOUND_WEAPON_STARTFIREBFG', wx, wy);
2273 if g_Game_IsNet and g_Game_IsServer then
2274 MH_SEND_Sound(wx, wy, 'SOUND_WEAPON_STARTFIREBFG');
2275 end;
2276 end;
2277 end;
2278 end;
2280 TRIGGER_EFFECT:
2281 begin
2282 idx := tgcFXCount;
2284 while idx > 0 do
2285 begin
2286 case tgcFXPos of
2287 TRIGGER_EFFECT_POS_CENTER:
2288 begin
2289 wx := X + Width div 2;
2290 wy := Y + Height div 2;
2291 end;
2292 TRIGGER_EFFECT_POS_AREA:
2293 begin
2294 wx := X + Random(Width);
2295 wy := Y + Random(Height);
2296 end;
2297 else begin
2298 wx := X + Width div 2;
2299 wy := Y + Height div 2;
2300 end;
2301 end;
2302 xd := tgcVelX;
2303 yd := tgcVelY;
2304 if tgcSpreadL > 0 then xd -= Random(tgcSpreadL+1);
2305 if tgcSpreadR > 0 then xd += Random(tgcSpreadR+1);
2306 if tgcSpreadU > 0 then yd -= Random(tgcSpreadU+1);
2307 if tgcSpreadD > 0 then yd += Random(tgcSpreadD+1);
2308 tr_MakeEffect(wx, wy, xd, yd,
2309 tgcFXType, tgcFXSubType,
2310 tgcFXRed, tgcFXGreen, tgcFXBlue, True, False);
2311 Dec(idx);
2312 end;
2313 TimeOut := tgcWait;
2314 result := true;
2315 end;
2316 end;
2317 end;
2319 if Result {and (Trigger.TexturePanel <> -1)} then
2320 begin
2321 g_Map_SwitchTextureGUID({Trigger.TexturePanelType,} Trigger.TexturePanelGUID, IfThen(animonce, 2, 1));
2322 end;
2323 end;
2326 function g_Triggers_CreateWithMapIndex (aTrigger: TTrigger; arridx, mapidx: Integer): DWORD;
2327 var
2328 triggers: TDynField;
2329 begin
2330 triggers := gCurrentMap['trigger'];
2331 if (triggers = nil) then raise Exception.Create('LOAD: map has no triggers');
2332 if (mapidx < 0) or (mapidx >= triggers.count) then raise Exception.Create('LOAD: invalid map trigger index');
2333 aTrigger.mapIndex := mapidx;
2334 result := g_Triggers_Create(aTrigger, triggers.itemAt[mapidx], arridx);
2335 end;
2338 function g_Triggers_Create (aTrigger: TTrigger; trec: TDynRecord; forceInternalIndex: Integer=-1): DWORD;
2339 var
2340 find_id: DWORD;
2341 fn: AnsiString;
2342 f, olen: Integer;
2343 ptg: PTrigger;
2344 begin
2345 if (tgscope = nil) then tgscope := TTrigScope.Create();
2346 if (tgclist = nil) then tgclist := TMyConstList.Create();
2348 // Íå ñîçäàâàòü âûõîä, åñëè èãðà áåç âûõîäà
2349 if (aTrigger.TriggerType = TRIGGER_EXIT) and
2350 (not LongBool(gGameSettings.Options and GAME_OPTION_ALLOWEXIT)) then
2351 begin
2352 aTrigger.TriggerType := TRIGGER_NONE;
2353 end;
2355 // Åñëè ìîíñòðû çàïðåùåíû, îòìåíÿåì òðèããåð
2356 if (aTrigger.TriggerType = TRIGGER_SPAWNMONSTER) and
2357 (not LongBool(gGameSettings.Options and GAME_OPTION_MONSTERS)) and
2358 (gGameSettings.GameType <> GT_SINGLE) then
2359 begin
2360 aTrigger.TriggerType := TRIGGER_NONE;
2361 end;
2363 // Ñ÷èòàåì êîëè÷åñòâî ñåêðåòîâ íà êàðòå
2364 if (aTrigger.TriggerType = TRIGGER_SECRET) then gSecretsCount += 1;
2366 if (forceInternalIndex < 0) then
2367 begin
2368 find_id := FindTrigger();
2369 end
2370 else
2371 begin
2372 olen := Length(gTriggers);
2373 if (forceInternalIndex >= olen) then
2374 begin
2375 SetLength(gTriggers, forceInternalIndex+1);
2376 for f := olen to High(gTriggers) do
2377 begin
2378 gTriggers[f].TriggerType := TRIGGER_NONE;
2379 gTriggers[f].trigDataRec := nil;
2380 gTriggers[f].exoInit := nil;
2381 gTriggers[f].exoThink := nil;
2382 gTriggers[f].exoCheck := nil;
2383 gTriggers[f].exoAction := nil;
2384 gTriggers[f].userVars := nil;
2385 end;
2386 end;
2387 f := forceInternalIndex;
2388 gTriggers[f].trigDataRec.Free();
2389 gTriggers[f].exoInit.Free();
2390 gTriggers[f].exoThink.Free();
2391 gTriggers[f].exoCheck.Free();
2392 gTriggers[f].exoAction.Free();
2393 gTriggers[f].userVars.Free();
2394 gTriggers[f].trigDataRec := nil;
2395 gTriggers[f].exoInit := nil;
2396 gTriggers[f].exoThink := nil;
2397 gTriggers[f].exoCheck := nil;
2398 gTriggers[f].exoAction := nil;
2399 gTriggers[f].userVars := nil;
2400 find_id := DWORD(forceInternalIndex);
2401 end;
2402 gTriggers[find_id] := aTrigger;
2403 ptg := @gTriggers[find_id];
2405 ptg.mapId := trec.id;
2406 // clone trigger data
2407 if (trec.trigRec = nil) then
2408 begin
2409 ptg.trigDataRec := nil;
2410 //HACK!
2411 if (ptg.TriggerType <> TRIGGER_SECRET) then
2412 begin
2413 e_LogWritefln('trigger of type %s has no triggerdata; wtf?!', [ptg.TriggerType], TMsgType.Warning);
2414 end;
2415 end
2416 else
2417 begin
2418 ptg.trigDataRec := trec.trigRec.clone(nil);
2419 end;
2421 with ptg^ do
2422 begin
2423 ID := find_id;
2424 // if this type of trigger exists both on the client and on the server
2425 // use an uniform numeration
2426 ClientID := 0;
2427 if (ptg.TriggerType = TRIGGER_SOUND) then
2428 begin
2429 Inc(gTriggerClientID);
2430 ClientID := gTriggerClientID;
2431 end;
2432 TimeOut := 0;
2433 ActivateUID := 0;
2434 PlayerCollide := False;
2435 DoorTime := -1;
2436 PressTime := -1;
2437 PressCount := 0;
2438 SoundPlayCount := 0;
2439 Sound := nil;
2440 AutoSpawn := False;
2441 SpawnCooldown := 0;
2442 SpawnedCount := 0;
2443 end;
2445 // update cached trigger variables
2446 trigUpdateCacheData(ptg^, ptg.trigDataRec);
2448 ptg.userVars := nil;
2450 try
2451 ptg.exoThink := TExprBase.parseStatList(tgclist, VarToStr(trec.user['exoma_think']));
2452 except
2453 on e: TExomaParseException do
2454 begin
2455 conwritefln('*** ERROR parsing exoma_think (%s,%s): %s [%s]', [e.tokLine, e.tokCol, e.message, VarToStr(trec.user['exoma_think'])]);
2456 ptg.exoThink := nil;
2457 end;
2458 else
2459 raise;
2460 end;
2461 try
2462 ptg.exoCheck := TExprBase.parse(tgclist, VarToStr(trec.user['exoma_check']));
2463 except
2464 on e: TExomaParseException do
2465 begin
2466 conwritefln('*** ERROR parsing exoma_check (%s,%s): %s [%s]', [e.tokLine, e.tokCol, e.message, VarToStr(trec.user['exoma_check'])]);
2467 ptg.exoCheck := nil;
2468 end;
2469 else
2470 raise;
2471 end;
2472 try
2473 ptg.exoAction := TExprBase.parseStatList(tgclist, VarToStr(trec.user['exoma_action']));
2474 except
2475 on e: TExomaParseException do
2476 begin
2477 conwritefln('*** ERROR parsing exoma_action (%s,%s): %s [%s]', [e.tokLine, e.tokCol, e.message, VarToStr(trec.user['exoma_action'])]);
2478 ptg.exoAction := nil;
2479 end;
2480 else
2481 raise;
2482 end;
2483 try
2484 ptg.exoInit := TExprBase.parseStatList(tgclist, VarToStr(trec.user['exoma_init']));
2485 except
2486 on e: TExomaParseException do
2487 begin
2488 conwritefln('*** ERROR parsing exoma_init (%s,%s): %s [%s]', [e.tokLine, e.tokCol, e.message, VarToStr(trec.user['exoma_init'])]);
2489 ptg.exoInit := nil;
2490 end;
2491 else
2492 raise;
2493 end;
2495 if (forceInternalIndex < 0) and (ptg.exoInit <> nil) then
2496 begin
2497 //conwritefln('executing trigger init: [%s]', [gTriggers[find_id].exoInit.toString()]);
2498 try
2499 tgscope.me := ptg;
2500 ptg.exoInit.value(tgscope);
2501 tgscope.me := nil;
2502 except
2503 tgscope.me := nil;
2504 conwritefln('*** trigger exoactivate error: %s', [ptg.exoInit.toString()]);
2505 exit;
2506 end;
2507 end;
2509 // Çàãðóæàåì çâóê, åñëè ýòî òðèããåð "Çâóê"
2510 if (ptg.TriggerType = TRIGGER_SOUND) and (ptg.tgcSoundName <> '') then
2511 begin
2512 // Åùå íåò òàêîãî çâóêà
2513 if not g_Sound_Exists(ptg.tgcSoundName) then
2514 begin
2515 fn := e_GetResourcePath(WadDirs, ptg.tgcSoundName, g_ExtractWadName(gMapInfo.Map));
2516 //e_LogWritefln('loading trigger sound ''%s''', [fn]);
2517 if not g_Sound_CreateWADEx(ptg.tgcSoundName, fn) then
2518 begin
2519 g_FatalError(Format(_lc[I_GAME_ERROR_TR_SOUND], [fn, ptg.tgcSoundName]));
2520 end;
2521 end;
2523 // Ñîçäàåì îáúåêò çâóêà
2524 with ptg^ do
2525 begin
2526 Sound := TPlayableSound.Create();
2527 if not Sound.SetByName(ptg.tgcSoundName) then
2528 begin
2529 Sound.Free();
2530 Sound := nil;
2531 end;
2532 end;
2533 end;
2535 // Çàãðóæàåì ìóçûêó, åñëè ýòî òðèããåð "Ìóçûêà"
2536 if (ptg.TriggerType = TRIGGER_MUSIC) and (ptg.tgcMusicName <> '') then
2537 begin
2538 // Åùå íåò òàêîé ìóçûêè
2539 if not g_Sound_Exists(ptg.tgcMusicName) then
2540 begin
2541 fn := e_GetResourcePath(WadDirs, ptg.tgcMusicName, g_ExtractWadName(gMapInfo.Map));
2542 if not g_Sound_CreateWADEx(ptg.tgcMusicName, fn, True) then
2543 begin
2544 g_FatalError(Format(_lc[I_GAME_ERROR_TR_SOUND], [fn, ptg.tgcMusicName]));
2545 end;
2546 end;
2547 end;
2549 // Çàãðóæàåì äàííûå òðèããåðà "Òóðåëü"
2550 if (ptg.TriggerType = TRIGGER_SHOT) then
2551 begin
2552 with ptg^ do
2553 begin
2554 ShotPanelTime := 0;
2555 ShotSightTime := 0;
2556 ShotSightTimeout := 0;
2557 ShotSightTarget := 0;
2558 ShotSightTargetN := 0;
2559 ShotAmmoCount := ptg.tgcAmmo;
2560 ShotReloadTime := 0;
2561 end;
2562 end;
2564 Result := find_id;
2565 end;
2568 // sorry; grid doesn't support recursive queries, so we have to do this
2569 type
2570 TSimpleMonsterList = specialize TSimpleList<TMonster>;
2572 var
2573 tgMonsList: TSimpleMonsterList = nil;
2575 procedure g_Triggers_Update();
2576 var
2577 a, b, i: Integer;
2578 Affected: array of Integer;
2580 function monsNear (mon: TMonster): Boolean;
2581 begin
2582 result := false; // don't stop
2584 gTriggers[a].ActivateUID := mon.UID;
2585 ActivateTrigger(gTriggers[a], ACTIVATE_MONSTERCOLLIDE);
2587 tgMonsList.append(mon);
2588 end;
2590 var
2591 mon: TMonster;
2592 pan: TPanel;
2593 begin
2594 if (tgMonsList = nil) then tgMonsList := TSimpleMonsterList.Create();
2596 if gTriggers = nil then Exit;
2597 if gLMSRespawn > LMS_RESPAWN_NONE then Exit; // don't update triggers at all
2599 SetLength(Affected, 0);
2601 for a := 0 to High(gTriggers) do
2602 with gTriggers[a] do
2603 // Åñòü òðèããåð:
2604 if TriggerType <> TRIGGER_NONE then
2605 begin
2606 // Óìåíüøàåì âðåìÿ äî çàêðûòèÿ äâåðè (îòêðûòèÿ ëîâóøêè)
2607 if DoorTime > 0 then DoorTime := DoorTime - 1;
2608 // Óìåíüøàåì âðåìÿ îæèäàíèÿ ïîñëå íàæàòèÿ
2609 if PressTime > 0 then PressTime := PressTime - 1;
2610 // Ïðîâåðÿåì èãðîêîâ è ìîíñòðîâ, êîòîðûõ ðàíåå çàïîìíèëè:
2611 if (TriggerType = TRIGGER_DAMAGE) or (TriggerType = TRIGGER_HEALTH) then
2612 begin
2613 for b := 0 to High(Activators) do
2614 begin
2615 // Óìåíüøàåì âðåìÿ äî ïîâòîðíîãî âîçäåéñòâèÿ:
2616 if Activators[b].TimeOut > 0 then
2617 begin
2618 Dec(Activators[b].TimeOut);
2619 end
2620 else
2621 begin
2622 continue;
2623 end;
2624 // Ñ÷èòàåì, ÷òî îáúåêò ïîêèíóë çîíó äåéñòâèÿ òðèããåðà
2625 if (tgcInterval = 0) and (Activators[b].TimeOut < 65530) then Activators[b].TimeOut := 0;
2626 end;
2627 end;
2629 // Îáðàáàòûâàåì ñïàâíåðû
2630 if Enabled and AutoSpawn then
2631 begin
2632 if SpawnCooldown = 0 then
2633 begin
2634 // Åñëè ïðèøëî âðåìÿ, ñïàâíèì ìîíñòðà
2635 if (TriggerType = TRIGGER_SPAWNMONSTER) and (tgcDelay > 0) then
2636 begin
2637 ActivateUID := 0;
2638 ActivateTrigger(gTriggers[a], ACTIVATE_CUSTOM);
2639 end;
2640 // Åñëè ïðèøëî âðåìÿ, ñïàâíèì ïðåäìåò
2641 if (TriggerType = TRIGGER_SPAWNITEM) and (tgcDelay > 0) then
2642 begin
2643 ActivateUID := 0;
2644 ActivateTrigger(gTriggers[a], ACTIVATE_CUSTOM);
2645 end;
2646 end
2647 else
2648 begin
2649 // Óìåíüøàåì âðåìÿ îæèäàíèÿ
2650 Dec(SpawnCooldown);
2651 end;
2652 end;
2654 // Îáðàáàòûâàåì ñîáûòèÿ òðèããåðà "Òóðåëü"
2655 if TriggerType = TRIGGER_SHOT then
2656 begin
2657 if ShotPanelTime > 0 then
2658 begin
2659 Dec(ShotPanelTime);
2660 if ShotPanelTime = 0 then g_Map_SwitchTextureGUID({ShotPanelType,} trigPanelGUID);
2661 end;
2662 if ShotSightTime > 0 then
2663 begin
2664 Dec(ShotSightTime);
2665 if ShotSightTime = 0 then ShotSightTarget := ShotSightTargetN;
2666 end;
2667 if ShotSightTimeout > 0 then
2668 begin
2669 Dec(ShotSightTimeout);
2670 if ShotSightTimeout = 0 then ShotSightTarget := 0;
2671 end;
2672 if ShotReloadTime > 0 then
2673 begin
2674 Dec(ShotReloadTime);
2675 if ShotReloadTime = 0 then ShotAmmoCount := tgcAmmo;
2676 end;
2677 end;
2679 // Òðèããåð "Çâóê" óæå îòûãðàë, åñëè íóæíî åùå - ïåðåçàïóñêàåì
2680 if Enabled and (TriggerType = TRIGGER_SOUND) and (Sound <> nil) then
2681 begin
2682 if (SoundPlayCount > 0) and (not Sound.IsPlaying()) then
2683 begin
2684 if tgcPlayCount > 0 then Dec(SoundPlayCount); (* looped sound if zero *)
2685 if tgcLocal then
2686 Sound.PlayVolumeAtRect(X, Y, Width, Height, tgcVolume / 255.0)
2687 else
2688 Sound.PlayPanVolume((tgcPan - 127.0) / 128.0, tgcVolume / 255.0);
2689 if Sound.IsPlaying() and g_Game_IsNet and g_Game_IsServer then
2690 MH_SEND_TriggerSound(gTriggers[a])
2691 end
2692 end;
2694 // Òðèããåð "Ëîâóøêà" - ïîðà îòêðûâàòü
2695 if (TriggerType = TRIGGER_TRAP) and (DoorTime = 0) and (g_Map_PanelByGUID(trigPanelGUID) <> nil) then
2696 begin
2697 tr_OpenDoor(trigPanelGUID, tgcSilent, tgcD2d);
2698 DoorTime := -1;
2699 end;
2701 // Òðèããåð "Äâåðü 5 ñåê" - ïîðà çàêðûâàòü
2702 if (TriggerType = TRIGGER_DOOR5) and (DoorTime = 0) and (g_Map_PanelByGUID(trigPanelGUID) <> nil) then
2703 begin
2704 pan := g_Map_PanelByGUID(trigPanelGUID);
2705 if (pan <> nil) and pan.isGWall then
2706 begin
2707 // Óæå çàêðûòà
2708 if {gWalls[trigPanelID].Enabled} pan.Enabled then
2709 begin
2710 DoorTime := -1;
2711 end
2712 else
2713 begin
2714 // Ïîêà îòêðûòà - çàêðûâàåì
2715 if tr_CloseDoor(trigPanelGUID, tgcSilent, tgcD2d) then DoorTime := -1;
2716 end;
2717 end;
2718 end;
2720 // Òðèããåð - ðàñøèðèòåëü èëè ïåðåêëþ÷àòåëü, è ïðîøëà çàäåðæêà, è íàæàëè íóæíîå ÷èñëî ðàç:
2721 if (TriggerType in [TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF]) and
2722 (PressTime = 0) and (PressCount >= tgcPressCount) then
2723 begin
2724 // Ñáðàñûâàåì çàäåðæêó àêòèâàöèè:
2725 PressTime := -1;
2726 // Ñáðàñûâàåì ñ÷åò÷èê íàæàòèé:
2727 if tgcPressCount > 0 then PressCount -= tgcPressCount else PressCount := 0;
2729 // Îïðåäåëÿåì èçìåíÿåìûå èì òðèããåðû:
2730 for b := 0 to High(gTriggers) do
2731 begin
2732 if g_Collide(tgcTX, tgcTY, tgcTWidth, tgcTHeight, gTriggers[b].X, gTriggers[b].Y,
2733 gTriggers[b].Width, gTriggers[b].Height) and
2734 ((b <> a) or (tgcWait > 0)) then
2735 begin // Can be self-activated, if there is Data.Wait
2736 if (not tgcExtRandom) or gTriggers[b].Enabled then
2737 begin
2738 SetLength(Affected, Length(Affected) + 1);
2739 Affected[High(Affected)] := b;
2740 end;
2741 end;
2742 end;
2744 //HACK!
2745 // if we have panelid, assume that it will switch the moving platform
2746 pan := g_Map_PanelByGUID(trigPanelGUID);
2747 if (pan <> nil) then
2748 begin
2749 case TriggerType of
2750 TRIGGER_PRESS: pan.movingActive := true; // what to do here?
2751 TRIGGER_ON: pan.movingActive := true;
2752 TRIGGER_OFF: pan.movingActive := false;
2753 TRIGGER_ONOFF: pan.movingActive := not pan.movingActive;
2754 end;
2755 if not tgcSilent and (Length(tgcSound) > 0) then
2756 begin
2757 g_Sound_PlayExAt(tgcSound, X, Y);
2758 if g_Game_IsServer and g_Game_IsNet then MH_SEND_Sound(X, Y, tgcSound);
2759 end;
2760 end;
2762 // Âûáèðàåì îäèí èç òðèããåðîâ äëÿ ðàñøèðèòåëÿ, åñëè âêëþ÷åí ðàíäîì:
2763 if (TriggerType = TRIGGER_PRESS) and tgcExtRandom then
2764 begin
2765 if (Length(Affected) > 0) then
2766 begin
2767 b := Affected[Random(Length(Affected))];
2768 gTriggers[b].ActivateUID := gTriggers[a].ActivateUID;
2769 ActivateTrigger(gTriggers[b], 0);
2770 end;
2771 end
2772 else //  ïðîòèâíîì ñëó÷àå ðàáîòàåì êàê îáû÷íî:
2773 begin
2774 for i := 0 to High(Affected) do
2775 begin
2776 b := Affected[i];
2777 case TriggerType of
2778 TRIGGER_PRESS:
2779 begin
2780 gTriggers[b].ActivateUID := gTriggers[a].ActivateUID;
2781 ActivateTrigger(gTriggers[b], 0);
2782 end;
2783 TRIGGER_ON:
2784 begin
2785 gTriggers[b].Enabled := True;
2786 end;
2787 TRIGGER_OFF:
2788 begin
2789 gTriggers[b].Enabled := False;
2790 gTriggers[b].TimeOut := 0;
2791 if gTriggers[b].AutoSpawn then
2792 begin
2793 gTriggers[b].AutoSpawn := False;
2794 gTriggers[b].SpawnCooldown := 0;
2795 end;
2796 end;
2797 TRIGGER_ONOFF:
2798 begin
2799 gTriggers[b].Enabled := not gTriggers[b].Enabled;
2800 if not gTriggers[b].Enabled then
2801 begin
2802 gTriggers[b].TimeOut := 0;
2803 if gTriggers[b].AutoSpawn then
2804 begin
2805 gTriggers[b].AutoSpawn := False;
2806 gTriggers[b].SpawnCooldown := 0;
2807 end;
2808 end;
2809 end;
2810 end;
2811 end;
2812 end;
2813 SetLength(Affected, 0);
2814 end;
2816 // Óìåíüøàåì âðåìÿ äî âîçìîæíîñòè ïîâòîðíîé àêòèâàöèè:
2817 if TimeOut > 0 then
2818 begin
2819 TimeOut := TimeOut - 1;
2820 Continue; // ×òîáû íå ïîòåðÿòü 1 åäèíèöó çàäåðæêè
2821 end;
2823 // Íèæå èäóò òèïû àêòèâàöèè, åñëè òðèããåð îòêëþ÷¸í - èä¸ì äàëüøå
2824 if not Enabled then
2825 Continue;
2827 // "Èãðîê áëèçêî":
2828 if ByteBool(ActivateType and ACTIVATE_PLAYERCOLLIDE) and
2829 (TimeOut = 0) then
2830 if gPlayers <> nil then
2831 for b := 0 to High(gPlayers) do
2832 if gPlayers[b] <> nil then
2833 with gPlayers[b] do
2834 // Æèâ, åñòü íóæíûå êëþ÷è è îí ðÿäîì:
2835 if alive and ((gTriggers[a].Keys and GetKeys) = gTriggers[a].Keys) and
2836 Collide(X, Y, Width, Height) then
2837 begin
2838 gTriggers[a].ActivateUID := UID;
2840 if (gTriggers[a].TriggerType in [TRIGGER_SOUND, TRIGGER_MUSIC]) and
2841 PlayerCollide then
2842 { Don't activate sound/music again if player is here }
2843 else
2844 ActivateTrigger(gTriggers[a], ACTIVATE_PLAYERCOLLIDE);
2845 end;
2847 { TODO 5 : àêòèâàöèÿ ìîíñòðàìè òðèããåðîâ ñ êëþ÷àìè }
2849 if ByteBool(ActivateType and ACTIVATE_MONSTERCOLLIDE) and
2850 ByteBool(ActivateType and ACTIVATE_NOMONSTER) and
2851 (TimeOut = 0) and (Keys = 0) then
2852 begin
2853 // Åñëè "Ìîíñòð áëèçêî" è "Ìîíñòðîâ íåò",
2854 // çàïóñêàåì òðèããåð íà ñòàðòå êàðòû è ñíèìàåì îáà ôëàãà
2855 ActivateType := ActivateType and not (ACTIVATE_MONSTERCOLLIDE or ACTIVATE_NOMONSTER);
2856 gTriggers[a].ActivateUID := 0;
2857 ActivateTrigger(gTriggers[a], 0);
2858 end else
2859 begin
2860 // "Ìîíñòð áëèçêî"
2861 if ByteBool(ActivateType and ACTIVATE_MONSTERCOLLIDE) and
2862 (TimeOut = 0) and (Keys = 0) then // Åñëè íå íóæíû êëþ÷è
2863 begin
2864 //g_Mons_ForEach(monsNear);
2865 //Alive?!
2866 tgMonsList.reset();
2867 g_Mons_ForEachAt(gTriggers[a].X, gTriggers[a].Y, gTriggers[a].Width, gTriggers[a].Height, monsNear);
2868 for mon in tgMonsList do
2869 begin
2870 gTriggers[a].ActivateUID := mon.UID;
2871 ActivateTrigger(gTriggers[a], ACTIVATE_MONSTERCOLLIDE);
2872 end;
2873 tgMonsList.reset(); // just in case
2874 end;
2876 // "Ìîíñòðîâ íåò"
2877 if ByteBool(ActivateType and ACTIVATE_NOMONSTER) and
2878 (TimeOut = 0) and (Keys = 0) then
2879 if not g_Mons_IsAnyAliveAt(X, Y, Width, Height) then
2880 begin
2881 gTriggers[a].ActivateUID := 0;
2882 ActivateTrigger(gTriggers[a], ACTIVATE_NOMONSTER);
2883 end;
2884 end;
2886 PlayerCollide := g_CollidePlayer(X, Y, Width, Height);
2887 end;
2888 end;
2890 procedure g_Triggers_Press(ID: DWORD; ActivateType: Byte; ActivateUID: Word = 0);
2891 begin
2892 if (ID >= Length(gTriggers)) then exit;
2893 gTriggers[ID].ActivateUID := ActivateUID;
2894 ActivateTrigger(gTriggers[ID], ActivateType);
2895 end;
2897 function g_Triggers_PressR(X, Y: Integer; Width, Height: Word; UID: Word;
2898 ActivateType: Byte; IgnoreList: DWArray = nil): DWArray;
2899 var
2900 a: Integer;
2901 k: Byte;
2902 p: TPlayer;
2903 begin
2904 Result := nil;
2906 if gTriggers = nil then Exit;
2908 case g_GetUIDType(UID) of
2909 UID_GAME: k := 255;
2910 UID_PLAYER:
2911 begin
2912 p := g_Player_Get(UID);
2913 if p <> nil then
2914 k := p.GetKeys
2915 else
2916 k := 0;
2917 end;
2918 else k := 0;
2919 end;
2921 for a := 0 to High(gTriggers) do
2922 if (gTriggers[a].TriggerType <> TRIGGER_NONE) and
2923 (gTriggers[a].TimeOut = 0) and
2924 (not InDWArray(a, IgnoreList)) and
2925 ((gTriggers[a].Keys and k) = gTriggers[a].Keys) and
2926 ByteBool(gTriggers[a].ActivateType and ActivateType) then
2927 if g_Collide(X, Y, Width, Height,
2928 gTriggers[a].X, gTriggers[a].Y,
2929 gTriggers[a].Width, gTriggers[a].Height) then
2930 begin
2931 gTriggers[a].ActivateUID := UID;
2932 if ActivateTrigger(gTriggers[a], ActivateType) then
2933 begin
2934 SetLength(Result, Length(Result)+1);
2935 Result[High(Result)] := a;
2936 end;
2937 end;
2938 end;
2940 procedure g_Triggers_PressL(X1, Y1, X2, Y2: Integer; UID: DWORD; ActivateType: Byte);
2941 var
2942 a: Integer;
2943 k: Byte;
2944 p: TPlayer;
2945 begin
2946 if gTriggers = nil then Exit;
2948 case g_GetUIDType(UID) of
2949 UID_GAME: k := 255;
2950 UID_PLAYER:
2951 begin
2952 p := g_Player_Get(UID);
2953 if p <> nil then
2954 k := p.GetKeys
2955 else
2956 k := 0;
2957 end;
2958 else k := 0;
2959 end;
2961 for a := 0 to High(gTriggers) do
2962 if (gTriggers[a].TriggerType <> TRIGGER_NONE) and
2963 (gTriggers[a].TimeOut = 0) and
2964 ((gTriggers[a].Keys and k) = gTriggers[a].Keys) and
2965 ByteBool(gTriggers[a].ActivateType and ActivateType) then
2966 if g_CollideLine(x1, y1, x2, y2, gTriggers[a].X, gTriggers[a].Y,
2967 gTriggers[a].Width, gTriggers[a].Height) then
2968 begin
2969 gTriggers[a].ActivateUID := UID;
2970 ActivateTrigger(gTriggers[a], ActivateType);
2971 end;
2972 end;
2974 procedure g_Triggers_PressC(CX, CY: Integer; Radius: Word; UID: Word; ActivateType: Byte; IgnoreTrigger: Integer = -1);
2975 var
2976 a: Integer;
2977 k: Byte;
2978 rsq: Word;
2979 p: TPlayer;
2980 begin
2981 if gTriggers = nil then
2982 Exit;
2984 case g_GetUIDType(UID) of
2985 UID_GAME: k := 255;
2986 UID_PLAYER:
2987 begin
2988 p := g_Player_Get(UID);
2989 if p <> nil then
2990 k := p.GetKeys
2991 else
2992 k := 0;
2993 end;
2994 else k := 0;
2995 end;
2997 rsq := Radius * Radius;
2999 for a := 0 to High(gTriggers) do
3000 if (gTriggers[a].ID <> DWORD(IgnoreTrigger)) and
3001 (gTriggers[a].TriggerType <> TRIGGER_NONE) and
3002 (gTriggers[a].TimeOut = 0) and
3003 ((gTriggers[a].Keys and k) = gTriggers[a].Keys) and
3004 ByteBool(gTriggers[a].ActivateType and ActivateType) then
3005 with gTriggers[a] do
3006 if g_Collide(CX-Radius, CY-Radius, 2*Radius, 2*Radius,
3007 X, Y, Width, Height) then
3008 if ((Sqr(CX-X)+Sqr(CY-Y)) < rsq) or // Öåíòð êðóãà áëèçîê ê âåðõíåìó ëåâîìó óãëó
3009 ((Sqr(CX-X-Width)+Sqr(CY-Y)) < rsq) or // Öåíòð êðóãà áëèçîê ê âåðõíåìó ïðàâîìó óãëó
3010 ((Sqr(CX-X-Width)+Sqr(CY-Y-Height)) < rsq) or // Öåíòð êðóãà áëèçîê ê íèæíåìó ïðàâîìó óãëó
3011 ((Sqr(CX-X)+Sqr(CY-Y-Height)) < rsq) or // Öåíòð êðóãà áëèçîê ê íèæíåìó ëåâîìó óãëó
3012 ( (CX > (X-Radius)) and (CX < (X+Width+Radius)) and
3013 (CY > Y) and (CY < (Y+Height)) ) or // Öåíòð êðóãà íåäàëåêî îò âåðòèêàëüíûõ ãðàíèö ïðÿìîóãîëüíèêà
3014 ( (CY > (Y-Radius)) and (CY < (Y+Height+Radius)) and
3015 (CX > X) and (CX < (X+Width)) ) then // Öåíòð êðóãà íåäàëåêî îò ãîðèçîíòàëüíûõ ãðàíèö ïðÿìîóãîëüíèêà
3016 begin
3017 ActivateUID := UID;
3018 ActivateTrigger(gTriggers[a], ActivateType);
3019 end;
3020 end;
3022 procedure g_Triggers_OpenAll();
3023 var
3024 a: Integer;
3025 b: Boolean;
3026 begin
3027 if gTriggers = nil then Exit;
3029 b := False;
3030 for a := 0 to High(gTriggers) do
3031 begin
3032 with gTriggers[a] do
3033 begin
3034 if (TriggerType = TRIGGER_OPENDOOR) or
3035 (TriggerType = TRIGGER_DOOR5) or
3036 (TriggerType = TRIGGER_DOOR) then
3037 begin
3038 tr_OpenDoor(trigPanelGUID, True, tgcD2d);
3039 if TriggerType = TRIGGER_DOOR5 then DoorTime := 180;
3040 b := True;
3041 end;
3042 end;
3043 end;
3045 if b then g_Sound_PlayEx('SOUND_GAME_DOOROPEN');
3046 end;
3048 procedure g_Triggers_DecreaseSpawner(ID: DWORD);
3049 begin
3050 if (gTriggers <> nil) then
3051 begin
3052 if gTriggers[ID].tgcMax > 0 then
3053 begin
3054 if gTriggers[ID].SpawnedCount > 0 then
3055 Dec(gTriggers[ID].SpawnedCount);
3056 end;
3057 if gTriggers[ID].tgcDelay > 0 then
3058 begin
3059 if gTriggers[ID].SpawnCooldown < 0 then
3060 gTriggers[ID].SpawnCooldown := gTriggers[ID].tgcDelay;
3061 end;
3062 end;
3063 end;
3065 procedure g_Triggers_Free ();
3066 var
3067 a: Integer;
3068 begin
3069 for a := 0 to High(gTriggers) do
3070 begin
3071 if (gTriggers[a].TriggerType = TRIGGER_SOUND) then
3072 begin
3073 if g_Sound_Exists(gTriggers[a].tgcSoundName) then
3074 begin
3075 g_Sound_Delete(gTriggers[a].tgcSoundName);
3076 end;
3077 gTriggers[a].Sound.Free();
3078 end;
3079 if (gTriggers[a].Activators <> nil) then
3080 begin
3081 SetLength(gTriggers[a].Activators, 0);
3082 end;
3083 gTriggers[a].trigDataRec.Free();
3085 gTriggers[a].exoThink.Free();
3086 gTriggers[a].exoCheck.Free();
3087 gTriggers[a].exoAction.Free();
3088 gTriggers[a].userVars.Free();
3089 end;
3091 gTriggers := nil;
3092 gSecretsCount := 0;
3093 SetLength(gMonstersSpawned, 0);
3094 end;
3097 procedure g_Triggers_SaveState (st: TStream);
3098 var
3099 count, actCount, i, j: Integer;
3100 sg: Single;
3101 b: Boolean;
3102 kv: THashStrVariant.PEntry;
3103 t: LongInt;
3104 begin
3105 // Ñ÷èòàåì êîëè÷åñòâî ñóùåñòâóþùèõ òðèããåðîâ
3106 count := Length(gTriggers);
3108 // Êîëè÷åñòâî òðèããåðîâ
3109 utils.writeInt(st, LongInt(count));
3110 if (count = 0) then exit;
3112 for i := 0 to High(gTriggers) do
3113 begin
3114 // Ñèãíàòóðà òðèããåðà
3115 utils.writeSign(st, 'TRGX');
3116 utils.writeInt(st, Byte(0));
3117 // Òèï òðèããåðà
3118 utils.writeInt(st, Byte(gTriggers[i].TriggerType));
3119 if (gTriggers[i].TriggerType = TRIGGER_NONE) then continue; // empty one
3120 // Ñïåöèàëüíûå äàííûå òðèããåðà: ïîòîì èç êàðòû îïÿòü âûòàùèì; ñîõðàíèì òîëüêî èíäåêñ
3121 utils.writeInt(st, LongInt(gTriggers[i].mapIndex));
3122 // Êîîðäèíàòû ëåâîãî âåðõíåãî óãëà
3123 utils.writeInt(st, LongInt(gTriggers[i].X));
3124 utils.writeInt(st, LongInt(gTriggers[i].Y));
3125 // Ðàçìåðû
3126 utils.writeInt(st, Word(gTriggers[i].Width));
3127 utils.writeInt(st, Word(gTriggers[i].Height));
3128 // Âêëþ÷åí ëè òðèããåð
3129 utils.writeBool(st, gTriggers[i].Enabled);
3130 // Òèï àêòèâàöèè òðèããåðà
3131 utils.writeInt(st, Byte(gTriggers[i].ActivateType));
3132 // Êëþ÷è, íåîáõîäèìûå äëÿ àêòèâàöèè
3133 utils.writeInt(st, Byte(gTriggers[i].Keys));
3134 // ID ïàíåëè, òåêñòóðà êîòîðîé èçìåíèòñÿ
3135 utils.writeInt(st, LongInt(gTriggers[i].TexturePanelGUID));
3136 // Òèï ýòîé ïàíåëè
3137 //Mem.WriteWord(gTriggers[i].TexturePanelType);
3138 // Âíóòðåííèé íîìåð äðóãîé ïàíåëè (ïî ñ÷àñòëèâîé ñëó÷àéíîñòè îí áóäåò ñîâïàäàòü ñ òåì, ÷òî ñîçäàíî ïðè çàãðóçêå êàðòû)
3139 utils.writeInt(st, LongInt(gTriggers[i].trigPanelGUID));
3140 // Âðåìÿ äî âîçìîæíîñòè àêòèâàöèè
3141 utils.writeInt(st, Word(gTriggers[i].TimeOut));
3142 // UID òîãî, êòî àêòèâèðîâàë ýòîò òðèããåð
3143 utils.writeInt(st, Word(gTriggers[i].ActivateUID));
3144 // Ñïèñîê UID-îâ îáúåêòîâ, êîòîðûå íàõîäèëèñü ïîä âîçäåéñòâèåì
3145 actCount := Length(gTriggers[i].Activators);
3146 utils.writeInt(st, LongInt(actCount));
3147 for j := 0 to actCount-1 do
3148 begin
3149 // UID îáúåêòà
3150 utils.writeInt(st, Word(gTriggers[i].Activators[j].UID));
3151 // Âðåìÿ îæèäàíèÿ
3152 utils.writeInt(st, Word(gTriggers[i].Activators[j].TimeOut));
3153 end;
3154 // Ñòîèò ëè èãðîê â îáëàñòè òðèããåðà
3155 utils.writeBool(st, gTriggers[i].PlayerCollide);
3156 // Âðåìÿ äî çàêðûòèÿ äâåðè
3157 utils.writeInt(st, LongInt(gTriggers[i].DoorTime));
3158 // Çàäåðæêà àêòèâàöèè
3159 utils.writeInt(st, LongInt(gTriggers[i].PressTime));
3160 // Ñ÷åò÷èê íàæàòèé
3161 utils.writeInt(st, LongInt(gTriggers[i].PressCount));
3162 // Ñïàâíåð àêòèâåí
3163 utils.writeBool(st, gTriggers[i].AutoSpawn);
3164 // Çàäåðæêà ñïàâíåðà
3165 utils.writeInt(st, LongInt(gTriggers[i].SpawnCooldown));
3166 // Ñ÷åò÷èê ñîçäàíèÿ îáúåêòîâ
3167 utils.writeInt(st, LongInt(gTriggers[i].SpawnedCount));
3168 // Ñêîëüêî ðàç ïðîèãðàí çâóê
3169 utils.writeInt(st, LongInt(gTriggers[i].SoundPlayCount));
3170 // Ïðîèãðûâàåòñÿ ëè çâóê?
3171 if (gTriggers[i].Sound <> nil) then b := gTriggers[i].Sound.IsPlaying() else b := false;
3172 utils.writeBool(st, b);
3173 if b then
3174 begin
3175 // Ïîçèöèÿ ïðîèãðûâàíèÿ çâóêà
3176 utils.writeInt(st, LongWord(gTriggers[i].Sound.GetPosition()));
3177 // Ãðîìêîñòü çâóêà
3178 sg := gTriggers[i].Sound.GetVolume();
3179 sg := sg/(gSoundLevel/255.0);
3180 //Mem.WriteSingle(sg);
3181 st.WriteBuffer(sg, sizeof(sg)); // sorry
3182 // Ñòåðåî ñìåùåíèå çâóêà
3183 sg := gTriggers[i].Sound.GetPan();
3184 //Mem.WriteSingle(sg);
3185 st.WriteBuffer(sg, sizeof(sg)); // sorry
3186 end;
3187 // uservars
3188 if (gTriggers[i].userVars = nil) then
3189 begin
3190 utils.writeInt(st, LongInt(0));
3191 end
3192 else
3193 begin
3194 utils.writeInt(st, LongInt(gTriggers[i].userVars.count)); //FIXME: check for overflow
3195 for kv in gTriggers[i].userVars.byKeyValue do
3196 begin
3197 //writeln('<', kv.key, '>:<', VarToStr(kv.value), '>');
3198 utils.writeStr(st, kv.key);
3199 t := LongInt(varType(kv.value));
3200 utils.writeInt(st, LongInt(t));
3201 case t of
3202 varString: utils.writeStr(st, AnsiString(kv.value));
3203 varBoolean: utils.writeBool(st, Boolean(kv.value));
3204 varShortInt: utils.writeInt(st, LongInt(kv.value));
3205 varSmallint: utils.writeInt(st, LongInt(kv.value));
3206 varInteger: utils.writeInt(st, LongInt(kv.value));
3207 //varInt64: Mem.WriteInt(Integer(kv.value));
3208 varByte: utils.writeInt(st, LongInt(kv.value));
3209 varWord: utils.writeInt(st, LongInt(kv.value));
3210 varLongWord: utils.writeInt(st, LongInt(kv.value));
3211 //varQWord:
3212 else raise Exception.CreateFmt('cannot save uservar ''%s''', [kv.key]);
3213 end;
3214 end;
3215 end;
3216 end;
3217 end;
3220 procedure g_Triggers_LoadState (st: TStream);
3221 var
3222 count, actCount, i, j, a: Integer;
3223 dw: DWORD;
3224 vol, pan: Single;
3225 b: Boolean;
3226 Trig: TTrigger;
3227 mapIndex: Integer;
3228 uvcount: Integer;
3229 vt: LongInt;
3230 vv: Variant;
3231 uvname: AnsiString = '';
3232 ustr: AnsiString = '';
3233 uint: LongInt;
3234 ubool: Boolean;
3235 begin
3236 assert(st <> nil);
3238 g_Triggers_Free();
3240 // Êîëè÷åñòâî òðèããåðîâ
3241 count := utils.readLongInt(st);
3242 if (count = 0) then exit;
3243 if (count < 0) or (count > 1024*1024) then raise XStreamError.Create('invalid trigger count');
3245 for a := 0 to count-1 do
3246 begin
3247 // Ñèãíàòóðà òðèããåðà
3248 if not utils.checkSign(st, 'TRGX') then raise XStreamError.Create('invalid trigger signature');
3249 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid trigger version');
3250 // Òèï òðèããåðà
3251 Trig.TriggerType := utils.readByte(st);
3252 if (Trig.TriggerType = TRIGGER_NONE) then continue; // empty one
3253 // Ñïåöèàëüíûå äàííûå òðèããåðà: èíäåêñ â gCurrentMap.field['triggers']
3254 mapIndex := utils.readLongInt(st);
3255 i := g_Triggers_CreateWithMapIndex(Trig, a, mapIndex);
3256 // Êîîðäèíàòû ëåâîãî âåðõíåãî óãëà
3257 gTriggers[i].X := utils.readLongInt(st);
3258 gTriggers[i].Y := utils.readLongInt(st);
3259 // Ðàçìåðû
3260 gTriggers[i].Width := utils.readWord(st);
3261 gTriggers[i].Height := utils.readWord(st);
3262 // Âêëþ÷åí ëè òðèããåð
3263 gTriggers[i].Enabled := utils.readBool(st);
3264 // Òèï àêòèâàöèè òðèããåðà
3265 gTriggers[i].ActivateType := utils.readByte(st);
3266 // Êëþ÷è, íåîáõîäèìûå äëÿ àêòèâàöèè
3267 gTriggers[i].Keys := utils.readByte(st);
3268 // ID ïàíåëè, òåêñòóðà êîòîðîé èçìåíèòñÿ
3269 gTriggers[i].TexturePanelGUID := utils.readLongInt(st);
3270 // Òèï ýòîé ïàíåëè
3271 //Mem.ReadWord(gTriggers[i].TexturePanelType);
3272 // Âíóòðåííèé íîìåð äðóãîé ïàíåëè (ïî ñ÷àñòëèâîé ñëó÷àéíîñòè îí áóäåò ñîâïàäàòü ñ òåì, ÷òî ñîçäàíî ïðè çàãðóçêå êàðòû)
3273 gTriggers[i].trigPanelGUID := utils.readLongInt(st);
3274 // Âðåìÿ äî âîçìîæíîñòè àêòèâàöèè
3275 gTriggers[i].TimeOut := utils.readWord(st);
3276 // UID òîãî, êòî àêòèâèðîâàë ýòîò òðèããåð
3277 gTriggers[i].ActivateUID := utils.readWord(st);
3278 // Ñïèñîê UID-îâ îáúåêòîâ, êîòîðûå íàõîäèëèñü ïîä âîçäåéñòâèåì
3279 actCount := utils.readLongInt(st);
3280 if (actCount < 0) or (actCount > 1024*1024) then raise XStreamError.Create('invalid activated object count');
3281 if (actCount > 0) then
3282 begin
3283 SetLength(gTriggers[i].Activators, actCount);
3284 for j := 0 to actCount-1 do
3285 begin
3286 // UID îáúåêòà
3287 gTriggers[i].Activators[j].UID := utils.readWord(st);
3288 // Âðåìÿ îæèäàíèÿ
3289 gTriggers[i].Activators[j].TimeOut := utils.readWord(st);
3290 end;
3291 end;
3292 // Ñòîèò ëè èãðîê â îáëàñòè òðèããåðà
3293 gTriggers[i].PlayerCollide := utils.readBool(st);
3294 // Âðåìÿ äî çàêðûòèÿ äâåðè
3295 gTriggers[i].DoorTime := utils.readLongInt(st);
3296 // Çàäåðæêà àêòèâàöèè
3297 gTriggers[i].PressTime := utils.readLongInt(st);
3298 // Ñ÷åò÷èê íàæàòèé
3299 gTriggers[i].PressCount := utils.readLongInt(st);
3300 // Ñïàâíåð àêòèâåí
3301 gTriggers[i].AutoSpawn := utils.readBool(st);
3302 // Çàäåðæêà ñïàâíåðà
3303 gTriggers[i].SpawnCooldown := utils.readLongInt(st);
3304 // Ñ÷åò÷èê ñîçäàíèÿ îáúåêòîâ
3305 gTriggers[i].SpawnedCount := utils.readLongInt(st);
3306 // Ñêîëüêî ðàç ïðîèãðàí çâóê
3307 gTriggers[i].SoundPlayCount := utils.readLongInt(st);
3308 // Ïðîèãðûâàåòñÿ ëè çâóê?
3309 b := utils.readBool(st);
3310 if b then
3311 begin
3312 // Ïîçèöèÿ ïðîèãðûâàíèÿ çâóêà
3313 dw := utils.readLongWord(st);
3314 // Ãðîìêîñòü çâóêà
3315 //Mem.ReadSingle(vol);
3316 st.ReadBuffer(vol, sizeof(vol)); // sorry
3317 // Ñòåðåî ñìåùåíèå çâóêà
3318 //Mem.ReadSingle(pan);
3319 st.ReadBuffer(pan, sizeof(pan)); // sorry
3320 // Çàïóñêàåì çâóê, åñëè åñòü
3321 if (gTriggers[i].Sound <> nil) then
3322 begin
3323 gTriggers[i].Sound.PlayPanVolume(pan, vol);
3324 gTriggers[i].Sound.Pause(True);
3325 gTriggers[i].Sound.SetPosition(dw);
3326 end
3327 end;
3328 // uservars
3329 gTriggers[i].userVars.Free();
3330 gTriggers[i].userVars := nil;
3331 uvcount := utils.readLongInt(st);
3332 if (uvcount < 0) or (uvcount > 1024*1024) then raise XStreamError.Create('invalid number of user vars in trigger');
3333 if (uvcount > 0) then
3334 begin
3335 gTriggers[i].userVars := THashStrVariant.Create();
3336 vv := Unassigned;
3337 while (uvcount > 0) do
3338 begin
3339 Dec(uvcount);
3340 uvname := utils.readStr(st);
3341 vt := utils.readLongInt(st);
3342 case vt of
3343 varString: begin ustr := utils.readStr(st); vv := ustr; end;
3344 varBoolean: begin ubool := utils.readBool(st); vv := ubool; end;
3345 varShortInt: begin uint := utils.readLongInt(st); vv := ShortInt(uint); end;
3346 varSmallint: begin uint := utils.readLongInt(st); vv := SmallInt(uint); end;
3347 varInteger: begin uint := utils.readLongInt(st); vv := LongInt(uint); end;
3348 varByte: begin uint := utils.readLongInt(st); vv := Byte(uint); end;
3349 varWord: begin uint := utils.readLongInt(st); vv := Word(uint); end;
3350 varLongWord: begin uint := utils.readLongInt(st); vv := LongWord(uint); end;
3351 else raise Exception.CreateFmt('cannot load uservar ''%s''', [uvname]);
3352 end;
3353 gTriggers[i].userVars.put(uvname, vv);
3354 end;
3355 end;
3356 end;
3357 end;
3360 end.