DEADSOFTWARE

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