DEADSOFTWARE

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