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);
726 snd := 'SOUND_WEAPON_FIREBALL';
727 end;
729 TRIGGER_SHOT_PLASMA:
730 begin
731 g_Weapon_Plasma(wx, wy, dx, dy, 0, -1, True);
732 snd := 'SOUND_WEAPON_FIREPLASMA';
733 end;
735 TRIGGER_SHOT_SPIDER:
736 begin
737 g_Weapon_aplasma(wx, wy, dx, dy, 0, -1, True);
738 snd := 'SOUND_WEAPON_FIREPLASMA';
739 end;
741 TRIGGER_SHOT_CACO:
742 begin
743 g_Weapon_ball2(wx, wy, dx, dy, 0, -1, True);
744 snd := 'SOUND_WEAPON_FIREBALL';
745 end;
747 TRIGGER_SHOT_BARON:
748 begin
749 g_Weapon_ball7(wx, wy, dx, dy, 0, -1, True);
750 snd := 'SOUND_WEAPON_FIREBALL';
751 end;
753 TRIGGER_SHOT_MANCUB:
754 begin
755 g_Weapon_manfire(wx, wy, dx, dy, 0, -1, True);
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);
768 snd := 'SOUND_WEAPON_FIREROCKET';
769 end;
771 TRIGGER_SHOT_BFG:
772 begin
773 g_Weapon_BFGShot(wx, wy, dx, dy, 0, -1, True);
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);
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: g_GFX_Bubbles(X, Y, 1, 0, 0);
870 end;
871 end;
873 if T = TRIGGER_EFFECT_ANIMATION then
874 begin
875 case ST of
876 EFFECT_TELEPORT: begin
877 if not Silent then g_Sound_PlayExAt('SOUND_GAME_TELEPORT', X, Y);
878 g_GFX_QueueEffect(R_GFX_TELEPORT_FAST, X - 32, Y - 32);
879 if Send and g_Game_IsServer and g_Game_IsNet then MH_SEND_Effect(X, Y, Byte(not Silent), NET_GFX_TELE);
880 end;
881 EFFECT_RESPAWN: begin
882 if not Silent then g_Sound_PlayExAt('SOUND_ITEM_RESPAWNITEM', X, Y);
883 g_GFX_QueueEffect(R_GFX_ITEM_RESPAWN, X - 16, Y - 16);
884 if Send and g_Game_IsServer and g_Game_IsNet then MH_SEND_Effect(X-16, Y-16, Byte(not Silent), NET_GFX_RESPAWN);
885 end;
886 EFFECT_FIRE: begin
887 if not Silent then g_Sound_PlayExAt('SOUND_FIRE', X, Y);
888 g_GFX_QueueEffect(R_GFX_FIRE, X - 32, Y - 128);
889 if Send and g_Game_IsServer and g_Game_IsNet then MH_SEND_Effect(X-32, Y-128, Byte(not Silent), NET_GFX_FIRE);
890 end;
891 end;
892 end;
893 end;
896 function tr_Teleport (ActivateUID: Integer; TX, TY: Integer; TDir: Integer; Silent: Boolean; D2D: Boolean): Boolean;
897 var
898 p: TPlayer;
899 m: TMonster;
900 begin
901 Result := False;
902 if (ActivateUID < 0) or (ActivateUID > $FFFF) then Exit;
903 case g_GetUIDType(ActivateUID) of
904 UID_PLAYER:
905 begin
906 p := g_Player_Get(ActivateUID);
907 if p = nil then Exit;
908 if D2D then
909 begin
910 if p.TeleportTo(TX-(p.Obj.Rect.Width div 2), TY-p.Obj.Rect.Height, Silent, TDir) then result := true;
911 end
912 else
913 begin
914 if p.TeleportTo(TX, TY, Silent, TDir) then result := true;
915 end;
916 end;
917 UID_MONSTER:
918 begin
919 m := g_Monsters_ByUID(ActivateUID);
920 if m = nil then Exit;
921 if D2D then
922 begin
923 if m.TeleportTo(TX-(m.Obj.Rect.Width div 2), TY-m.Obj.Rect.Height, Silent, TDir) then result := true;
924 end
925 else
926 begin
927 if m.TeleportTo(TX, TY, Silent, TDir) then result := true;
928 end;
929 end;
930 end;
931 end;
934 function tr_Push (ActivateUID: Integer; VX, VY: Integer; ResetVel: Boolean): Boolean;
935 var
936 p: TPlayer;
937 m: TMonster;
938 begin
939 result := true;
940 if (ActivateUID < 0) or (ActivateUID > $FFFF) then exit;
941 case g_GetUIDType(ActivateUID) of
942 UID_PLAYER:
943 begin
944 p := g_Player_Get(ActivateUID);
945 if p = nil then Exit;
947 if ResetVel then
948 begin
949 p.GameVelX := 0;
950 p.GameVelY := 0;
951 p.GameAccelX := 0;
952 p.GameAccelY := 0;
953 end;
955 p.Push(VX, VY);
956 end;
958 UID_MONSTER:
959 begin
960 m := g_Monsters_ByUID(ActivateUID);
961 if m = nil then Exit;
963 if ResetVel then
964 begin
965 m.GameVelX := 0;
966 m.GameVelY := 0;
967 m.GameAccelX := 0;
968 m.GameAccelY := 0;
969 end;
971 m.Push(VX, VY);
972 end;
973 end;
974 end;
977 function tr_Message (MKind: Integer; MText: string; MSendTo: Integer; MTime: Integer; ActivateUID: Integer): Boolean;
978 var
979 msg: string;
980 p: TPlayer;
981 i: Integer;
982 begin
983 Result := True;
984 if (ActivateUID < 0) or (ActivateUID > $FFFF) then Exit;
985 msg := b_Text_Format(MText);
986 case MSendTo of
987 TRIGGER_MESSAGE_DEST_ME: // activator
988 begin
989 if g_GetUIDType(ActivateUID) = UID_PLAYER then
990 begin
991 if g_Game_IsWatchedPlayer(ActivateUID) then
992 begin
993 if MKind = TRIGGER_MESSAGE_KIND_CHAT then g_Console_Add(msg, True)
994 else if MKind = TRIGGER_MESSAGE_KIND_GAME then g_Game_Message(msg, MTime);
995 end
996 else
997 begin
998 p := g_Player_Get(ActivateUID);
999 if g_Game_IsNet and (p.FClientID >= 0) then
1000 begin
1001 if MKind = TRIGGER_MESSAGE_KIND_CHAT then MH_SEND_Chat(msg, NET_CHAT_SYSTEM, p.FClientID)
1002 else if MKind = TRIGGER_MESSAGE_KIND_GAME then MH_SEND_GameEvent(NET_EV_BIGTEXT, MTime, msg, p.FClientID);
1003 end;
1004 end;
1005 end;
1006 end;
1008 TRIGGER_MESSAGE_DEST_MY_TEAM: // activator's team
1009 begin
1010 if g_GetUIDType(ActivateUID) = UID_PLAYER then
1011 begin
1012 p := g_Player_Get(ActivateUID);
1013 if g_Game_IsWatchedTeam(p.Team) then
1014 begin
1015 if MKind = TRIGGER_MESSAGE_KIND_CHAT then g_Console_Add(msg, True)
1016 else if MKind = TRIGGER_MESSAGE_KIND_GAME then g_Game_Message(msg, MTime);
1017 end;
1019 if g_Game_IsNet then
1020 begin
1021 for i := Low(gPlayers) to High(gPlayers) do
1022 begin
1023 if (gPlayers[i].Team = p.Team) and (gPlayers[i].FClientID >= 0) then
1024 begin
1025 if MKind = TRIGGER_MESSAGE_KIND_CHAT then MH_SEND_Chat(msg, NET_CHAT_SYSTEM, gPlayers[i].FClientID)
1026 else if MKind = TRIGGER_MESSAGE_KIND_GAME then MH_SEND_GameEvent(NET_EV_BIGTEXT, MTime, msg, gPlayers[i].FClientID);
1027 end;
1028 end;
1029 end;
1030 end;
1031 end;
1033 TRIGGER_MESSAGE_DEST_ENEMY_TEAM: // activator's enemy team
1034 begin
1035 if g_GetUIDType(ActivateUID) = UID_PLAYER then
1036 begin
1037 p := g_Player_Get(ActivateUID);
1038 if g_Game_IsWatchedTeam(p.Team) then
1039 begin
1040 if MKind = TRIGGER_MESSAGE_KIND_CHAT then g_Console_Add(msg, True)
1041 else if MKind = TRIGGER_MESSAGE_KIND_GAME then g_Game_Message(msg, MTime);
1042 end;
1044 if g_Game_IsNet then
1045 begin
1046 for i := Low(gPlayers) to High(gPlayers) do
1047 begin
1048 if (gPlayers[i].Team <> p.Team) and (gPlayers[i].FClientID >= 0) then
1049 begin
1050 if MKind = TRIGGER_MESSAGE_KIND_CHAT then MH_SEND_Chat(msg, NET_CHAT_SYSTEM, gPlayers[i].FClientID)
1051 else if MKind = TRIGGER_MESSAGE_KIND_GAME then MH_SEND_GameEvent(NET_EV_BIGTEXT, MTime, msg, gPlayers[i].FClientID);
1052 end;
1053 end;
1054 end;
1055 end;
1056 end;
1058 TRIGGER_MESSAGE_DEST_RED_TEAM: // red team
1059 begin
1060 if g_Game_IsWatchedTeam(TEAM_RED) then
1061 begin
1062 if MKind = TRIGGER_MESSAGE_KIND_CHAT then g_Console_Add(msg, True)
1063 else if MKind = TRIGGER_MESSAGE_KIND_GAME then g_Game_Message(msg, MTime);
1064 end;
1066 if g_Game_IsNet then
1067 begin
1068 for i := Low(gPlayers) to High(gPlayers) do
1069 begin
1070 if (gPlayers[i].Team = TEAM_RED) and (gPlayers[i].FClientID >= 0) then
1071 begin
1072 if MKind = TRIGGER_MESSAGE_KIND_CHAT then MH_SEND_Chat(msg, NET_CHAT_SYSTEM, gPlayers[i].FClientID)
1073 else if MKind = TRIGGER_MESSAGE_KIND_GAME then MH_SEND_GameEvent(NET_EV_BIGTEXT, MTime, msg, gPlayers[i].FClientID);
1074 end;
1075 end;
1076 end;
1077 end;
1079 TRIGGER_MESSAGE_DEST_BLUE_TEAM: // blue team
1080 begin
1081 if g_Game_IsWatchedTeam(TEAM_BLUE) then
1082 begin
1083 if MKind = TRIGGER_MESSAGE_KIND_CHAT then g_Console_Add(msg, True)
1084 else if MKind = TRIGGER_MESSAGE_KIND_GAME then g_Game_Message(msg, MTime);
1085 end;
1087 if g_Game_IsNet then
1088 begin
1089 for i := Low(gPlayers) to High(gPlayers) do
1090 begin
1091 if (gPlayers[i].Team = TEAM_BLUE) and (gPlayers[i].FClientID >= 0) then
1092 begin
1093 if MKind = TRIGGER_MESSAGE_KIND_CHAT then MH_SEND_Chat(msg, NET_CHAT_SYSTEM, gPlayers[i].FClientID)
1094 else if MKind = TRIGGER_MESSAGE_KIND_GAME then MH_SEND_GameEvent(NET_EV_BIGTEXT, MTime, msg, gPlayers[i].FClientID);
1095 end;
1096 end;
1097 end;
1098 end;
1100 TRIGGER_MESSAGE_DEST_EVERYONE: // everyone
1101 begin
1102 if MKind = TRIGGER_MESSAGE_KIND_CHAT then g_Console_Add(msg, True)
1103 else if MKind = TRIGGER_MESSAGE_KIND_GAME then g_Game_Message(msg, MTime);
1105 if g_Game_IsNet then
1106 begin
1107 if MKind = TRIGGER_MESSAGE_KIND_CHAT then MH_SEND_Chat(msg, NET_CHAT_SYSTEM)
1108 else if MKind = TRIGGER_MESSAGE_KIND_GAME then MH_SEND_GameEvent(NET_EV_BIGTEXT, MTime, msg);
1109 end;
1110 end;
1111 end;
1112 end;
1115 function tr_ShotAimCheck (var Trigger: TTrigger; Obj: PObj): Boolean;
1116 begin
1117 result := false;
1118 with Trigger do
1119 begin
1120 if TriggerType <> TRIGGER_SHOT then Exit;
1121 result := (tgcAim and TRIGGER_SHOT_AIM_ALLMAP > 0)
1122 or g_Obj_Collide(X, Y, Width, Height, Obj);
1123 if result and (tgcAim and TRIGGER_SHOT_AIM_TRACE > 0) then
1124 begin
1125 result := g_TraceVector(tgcTX, tgcTY,
1126 Obj^.X + Obj^.Rect.X + (Obj^.Rect.Width div 2),
1127 Obj^.Y + Obj^.Rect.Y + (Obj^.Rect.Height div 2));
1128 end;
1129 end;
1130 end;
1133 function ActivateTrigger (var Trigger: TTrigger; actType: Byte): Boolean;
1134 var
1135 animonce: Boolean;
1136 p: TPlayer;
1137 m: TMonster;
1138 pan: TPanel;
1139 idx, k, wx, wy, xd, yd: Integer;
1140 iid: LongWord;
1141 coolDown: Boolean;
1142 pAngle: Real;
1143 UIDType: Byte;
1144 TargetUID: Word;
1145 it: PItem;
1146 mon: TMonster;
1148 function monsShotTarget (mon: TMonster): Boolean;
1149 begin
1150 result := false; // don't stop
1151 if mon.alive and tr_ShotAimCheck(Trigger, @(mon.Obj)) then
1152 begin
1153 xd := mon.GameX + mon.Obj.Rect.Width div 2;
1154 yd := mon.GameY + mon.Obj.Rect.Height div 2;
1155 TargetUID := mon.UID;
1156 result := true; // stop
1157 end;
1158 end;
1160 function monsShotTargetMonPlr (mon: TMonster): Boolean;
1161 begin
1162 result := false; // don't stop
1163 if mon.alive and tr_ShotAimCheck(Trigger, @(mon.Obj)) then
1164 begin
1165 xd := mon.GameX + mon.Obj.Rect.Width div 2;
1166 yd := mon.GameY + mon.Obj.Rect.Height div 2;
1167 TargetUID := mon.UID;
1168 result := true; // stop
1169 end;
1170 end;
1172 function monShotTargetPlrMon (mon: TMonster): Boolean;
1173 begin
1174 result := false; // don't stop
1175 if mon.alive and tr_ShotAimCheck(Trigger, @(mon.Obj)) then
1176 begin
1177 xd := mon.GameX + mon.Obj.Rect.Width div 2;
1178 yd := mon.GameY + mon.Obj.Rect.Height div 2;
1179 TargetUID := mon.UID;
1180 result := true; // stop
1181 end;
1182 end;
1184 var
1185 tvval: Variant;
1186 begin
1187 result := false;
1188 if g_Game_IsClient then exit;
1190 if not Trigger.Enabled then exit;
1191 if (Trigger.TimeOut <> 0) and (actType <> ACTIVATE_CUSTOM) then exit;
1192 if (gLMSRespawn > LMS_RESPAWN_NONE) then exit;
1194 if (Trigger.exoCheck <> nil) then
1195 begin
1196 //conwritefln('exocheck: [%s]', [Trigger.exoCheck.toString()]);
1197 try
1198 tgscope.me := @Trigger;
1199 tvval := Trigger.exoCheck.value(tgscope);
1200 tgscope.me := nil;
1201 if not Boolean(tvval) then exit;
1202 except on e: Exception do
1203 begin
1204 tgscope.me := nil;
1205 conwritefln('trigger exocheck error: %s [%s]', [e.message, Trigger.exoCheck.toString()]);
1206 exit;
1207 end;
1208 end;
1209 end;
1211 animonce := False;
1213 coolDown := (actType <> 0);
1215 if (Trigger.exoAction <> nil) then
1216 begin
1217 //conwritefln('exoactivate: [%s]', [Trigger.exoAction.toString()]);
1218 try
1219 tgscope.me := @Trigger;
1220 Trigger.exoAction.value(tgscope);
1221 tgscope.me := nil;
1222 except on e: Exception do
1223 begin
1224 tgscope.me := nil;
1225 conwritefln('trigger exoactivate error: %s [%s]', [e.message, Trigger.exoAction.toString()]);
1226 exit;
1227 end;
1228 end;
1229 end;
1231 with Trigger do
1232 begin
1233 case TriggerType of
1234 TRIGGER_EXIT:
1235 begin
1236 g_Sound_PlayEx('SOUND_GAME_SWITCH0');
1237 if g_Game_IsNet then MH_SEND_Sound(X, Y, 'SOUND_GAME_SWITCH0');
1238 gExitByTrigger := True;
1239 g_Game_ExitLevel(tgcMap);
1240 TimeOut := 18;
1241 Result := True;
1243 Exit;
1244 end;
1246 TRIGGER_TELEPORT:
1247 begin
1248 Result := tr_Teleport(ActivateUID,
1249 tgcTarget.X, tgcTarget.Y,
1250 tgcDirection, tgcSilent,
1251 tgcD2d);
1252 TimeOut := 0;
1253 end;
1255 TRIGGER_OPENDOOR:
1256 begin
1257 Result := tr_OpenDoor(trigPanelGUID, tgcSilent, tgcD2d);
1258 TimeOut := 0;
1259 end;
1261 TRIGGER_CLOSEDOOR:
1262 begin
1263 Result := tr_CloseDoor(trigPanelGUID, tgcSilent, tgcD2d);
1264 TimeOut := 0;
1265 end;
1267 TRIGGER_DOOR, TRIGGER_DOOR5:
1268 begin
1269 pan := g_Map_PanelByGUID(trigPanelGUID);
1270 if (pan <> nil) and pan.isGWall then
1271 begin
1272 if gWalls[{trigPanelID}pan.arrIdx].Enabled then
1273 begin
1274 result := tr_OpenDoor(trigPanelGUID, tgcSilent, tgcD2d);
1275 if (TriggerType = TRIGGER_DOOR5) then DoorTime := 180;
1276 end
1277 else
1278 begin
1279 result := tr_CloseDoor(trigPanelGUID, tgcSilent, tgcD2d);
1280 end;
1282 if result then TimeOut := 18;
1283 end;
1284 end;
1286 TRIGGER_CLOSETRAP, TRIGGER_TRAP:
1287 begin
1288 tr_CloseTrap(trigPanelGUID, tgcSilent, tgcD2d);
1290 if TriggerType = TRIGGER_TRAP then
1291 begin
1292 DoorTime := 40;
1293 TimeOut := 76;
1294 end
1295 else
1296 begin
1297 DoorTime := -1;
1298 TimeOut := 0;
1299 end;
1301 Result := True;
1302 end;
1304 TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF:
1305 begin
1306 PressCount += 1;
1307 if PressTime = -1 then PressTime := tgcWait;
1308 if coolDown then TimeOut := 18 else TimeOut := 0;
1309 Result := True;
1310 end;
1312 TRIGGER_SECRET:
1313 if g_GetUIDType(ActivateUID) = UID_PLAYER then
1314 begin
1315 Enabled := False;
1316 Result := True;
1317 p := g_Player_Get(ActivateUID);
1318 p.GetSecret();
1319 Inc(gCoopSecretsFound);
1320 if g_Game_IsNet then
1321 begin
1322 MH_SEND_GameStats();
1323 MH_SEND_GameEvent(NET_EV_SECRET, p.UID, '');
1324 end;
1325 end;
1327 TRIGGER_LIFTUP:
1328 begin
1329 Result := tr_SetLift(trigPanelGUID, 0, tgcSilent, tgcD2d);
1330 TimeOut := 0;
1332 if (not tgcSilent) and Result then begin
1333 g_Sound_PlayExAt('SOUND_GAME_SWITCH0',
1334 X + (Width div 2),
1335 Y + (Height div 2));
1336 if g_Game_IsServer and g_Game_IsNet then
1337 MH_SEND_Sound(X + (Width div 2),
1338 Y + (Height div 2),
1339 'SOUND_GAME_SWITCH0');
1340 end;
1341 end;
1343 TRIGGER_LIFTDOWN:
1344 begin
1345 Result := tr_SetLift(trigPanelGUID, 1, tgcSilent, tgcD2d);
1346 TimeOut := 0;
1348 if (not tgcSilent) and Result then begin
1349 g_Sound_PlayExAt('SOUND_GAME_SWITCH0',
1350 X + (Width div 2),
1351 Y + (Height div 2));
1352 if g_Game_IsServer and g_Game_IsNet then
1353 MH_SEND_Sound(X + (Width div 2),
1354 Y + (Height div 2),
1355 'SOUND_GAME_SWITCH0');
1356 end;
1357 end;
1359 TRIGGER_LIFT:
1360 begin
1361 Result := tr_SetLift(trigPanelGUID, 3, tgcSilent, tgcD2d);
1363 if Result then
1364 begin
1365 TimeOut := 18;
1367 if (not tgcSilent) and Result then begin
1368 g_Sound_PlayExAt('SOUND_GAME_SWITCH0',
1369 X + (Width div 2),
1370 Y + (Height div 2));
1371 if g_Game_IsServer and g_Game_IsNet then
1372 MH_SEND_Sound(X + (Width div 2),
1373 Y + (Height div 2),
1374 'SOUND_GAME_SWITCH0');
1375 end;
1376 end;
1377 end;
1379 TRIGGER_TEXTURE:
1380 begin
1381 if tgcActivateOnce then
1382 begin
1383 Enabled := False;
1384 TriggerType := TRIGGER_NONE;
1385 end
1386 else
1387 if coolDown then
1388 TimeOut := 6
1389 else
1390 TimeOut := 0;
1392 animonce := tgcAnimateOnce;
1393 Result := True;
1394 end;
1396 TRIGGER_SOUND:
1397 begin
1398 if Sound <> nil then
1399 begin
1400 if tgcSoundSwitch and Sound.IsPlaying() then
1401 begin // Íóæíî âûêëþ÷èòü, åñëè èãðàë
1402 Sound.Stop();
1403 SoundPlayCount := 0;
1404 Result := True;
1405 end
1406 else // (not Data.SoundSwitch) or (not Sound.IsPlaying())
1407 if (tgcPlayCount > 0) or (not Sound.IsPlaying()) then
1408 begin
1409 if tgcPlayCount > 0 then
1410 SoundPlayCount := tgcPlayCount
1411 else // 0 - èãðàåì áåñêîíå÷íî
1412 SoundPlayCount := 1;
1413 Result := True;
1414 end;
1415 if g_Game_IsNet then MH_SEND_TriggerSound(Trigger);
1416 end;
1417 end;
1419 TRIGGER_SPAWNMONSTER:
1420 if (tgcSpawnMonsType in [MONSTER_DEMON..MONSTER_MAN]) then
1421 begin
1422 Result := False;
1423 if (tgcDelay > 0) and (actType <> ACTIVATE_CUSTOM) then
1424 begin
1425 AutoSpawn := not AutoSpawn;
1426 SpawnCooldown := 0;
1427 // Àâòîñïàâíåð ïåðåêëþ÷åí - ìåíÿåì òåêñòóðó
1428 Result := True;
1429 end;
1431 if ((tgcDelay = 0) and (actType <> ACTIVATE_CUSTOM))
1432 or ((tgcDelay > 0) and (actType = ACTIVATE_CUSTOM)) then
1433 for k := 1 to tgcMonsCount do
1434 begin
1435 if (actType = ACTIVATE_CUSTOM) and (tgcDelay > 0) then
1436 SpawnCooldown := -1; // Çàäåðæêà âûñòàâèòñÿ ìîíñòðîì ïðè óíè÷òîæåíèè
1437 if (tgcMax > 0) and (SpawnedCount >= tgcMax) then
1438 Break;
1440 mon := g_Monsters_Create(tgcSpawnMonsType,
1441 tgcTX, tgcTY,
1442 TDirection(tgcDirection), True);
1444 Result := True;
1446 // Çäîðîâüå:
1447 if (tgcHealth > 0) then
1448 mon.SetHealth(tgcHealth);
1449 // Óñòàíàâëèâàåì ïîâåäåíèå:
1450 mon.MonsterBehaviour := tgcBehaviour;
1451 mon.FNoRespawn := True;
1452 if g_Game_IsNet then
1453 MH_SEND_MonsterSpawn(mon.UID);
1454 // Èäåì èñêàòü öåëü, åñëè íàäî:
1455 if tgcActive then
1456 mon.WakeUp();
1458 if tgcSpawnMonsType <> MONSTER_BARREL then Inc(gTotalMonsters);
1460 if g_Game_IsNet then
1461 begin
1462 SetLength(gMonstersSpawned, Length(gMonstersSpawned)+1);
1463 gMonstersSpawned[High(gMonstersSpawned)] := mon.UID;
1464 end;
1466 mon.SpawnTrigger := ID;
1467 if tgcMax > 0 then Inc(SpawnedCount);
1469 case tgcEffect of
1470 EFFECT_TELEPORT: begin
1471 g_Sound_PlayExAt('SOUND_GAME_TELEPORT', tgcTX, tgcTY);
1472 g_GFX_QueueEffect(
1473 R_GFX_TELEPORT_FAST,
1474 mon.Obj.X+mon.Obj.Rect.X+(mon.Obj.Rect.Width div 2)-32,
1475 mon.Obj.Y+mon.Obj.Rect.Y+(mon.Obj.Rect.Height div 2)-32
1476 );
1477 if g_Game_IsServer and g_Game_IsNet then
1478 MH_SEND_Effect(mon.Obj.X+mon.Obj.Rect.X+(mon.Obj.Rect.Width div 2)-32,
1479 mon.Obj.Y+mon.Obj.Rect.Y+(mon.Obj.Rect.Height div 2)-32, 1,
1480 NET_GFX_TELE);
1481 end;
1482 EFFECT_RESPAWN: begin
1483 g_Sound_PlayExAt('SOUND_ITEM_RESPAWNITEM', tgcTX, tgcTY);
1484 g_GFX_QueueEffect(
1485 R_GFX_ITEM_RESPAWN,
1486 mon.Obj.X+mon.Obj.Rect.X+(mon.Obj.Rect.Width div 2)-16,
1487 mon.Obj.Y+mon.Obj.Rect.Y+(mon.Obj.Rect.Height div 2)-16
1488 );
1489 if g_Game_IsServer and g_Game_IsNet then
1490 MH_SEND_Effect(mon.Obj.X+mon.Obj.Rect.X+(mon.Obj.Rect.Width div 2)-16,
1491 mon.Obj.Y+mon.Obj.Rect.Y+(mon.Obj.Rect.Height div 2)-16, 1,
1492 NET_GFX_RESPAWN);
1493 end;
1494 EFFECT_FIRE: begin
1495 g_Sound_PlayExAt('SOUND_FIRE', tgcTX, tgcTY);
1496 g_GFX_QueueEffect(
1497 R_GFX_FIRE,
1498 mon.Obj.X+mon.Obj.Rect.X+(mon.Obj.Rect.Width div 2)-32,
1499 mon.Obj.Y+mon.Obj.Rect.Y+mon.Obj.Rect.Height-128
1500 );
1501 if g_Game_IsServer and g_Game_IsNet then
1502 MH_SEND_Effect(mon.Obj.X+mon.Obj.Rect.X+(mon.Obj.Rect.Width div 2)-32,
1503 mon.Obj.Y+mon.Obj.Rect.Y+mon.Obj.Rect.Height-128, 1,
1504 NET_GFX_FIRE);
1505 end;
1506 end;
1507 end;
1508 if g_Game_IsNet then
1509 begin
1510 MH_SEND_GameStats();
1511 MH_SEND_CoopStats();
1512 end;
1514 if coolDown then
1515 TimeOut := 18
1516 else
1517 TimeOut := 0;
1518 // Åñëè àêòèâèðîâàí àâòîñïàâíåðîì, íå ìåíÿåì òåêñòóðó
1519 if actType = ACTIVATE_CUSTOM then
1520 Result := False;
1521 end;
1523 TRIGGER_SPAWNITEM:
1524 if (tgcSpawnItemType in [ITEM_MEDKIT_SMALL..ITEM_MAX]) then
1525 begin
1526 Result := False;
1527 if (tgcDelay > 0) and (actType <> ACTIVATE_CUSTOM) then
1528 begin
1529 AutoSpawn := not AutoSpawn;
1530 SpawnCooldown := 0;
1531 // Àâòîñïàâíåð ïåðåêëþ÷åí - ìåíÿåì òåêñòóðó
1532 Result := True;
1533 end;
1535 if ((tgcDelay = 0) and (actType <> ACTIVATE_CUSTOM))
1536 or ((tgcDelay > 0) and (actType = ACTIVATE_CUSTOM)) then
1537 if (not tgcDmonly) or
1538 (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF]) then
1539 for k := 1 to tgcItemCount do
1540 begin
1541 if (actType = ACTIVATE_CUSTOM) and (tgcDelay > 0) then
1542 SpawnCooldown := -1; // Çàäåðæêà âûñòàâèòñÿ èòåìîì ïðè óíè÷òîæåíèè
1543 if (tgcMax > 0) and (SpawnedCount >= tgcMax) then
1544 Break;
1546 iid := g_Items_Create(tgcTX, tgcTY,
1547 tgcSpawnItemType, tgcGravity, False, True);
1549 Result := True;
1551 it := g_Items_ByIdx(iid);
1552 it.SpawnTrigger := ID;
1553 if tgcMax > 0 then Inc(SpawnedCount);
1555 case tgcEffect of
1556 EFFECT_TELEPORT: begin
1557 it := g_Items_ByIdx(iid);
1558 g_Sound_PlayExAt('SOUND_GAME_TELEPORT', tgcTX, tgcTY);
1559 g_GFX_QueueEffect(
1560 R_GFX_TELEPORT_FAST,
1561 it.Obj.X+it.Obj.Rect.X+(it.Obj.Rect.Width div 2)-32,
1562 it.Obj.Y+it.Obj.Rect.Y+(it.Obj.Rect.Height div 2)-32
1563 );
1564 if g_Game_IsServer and g_Game_IsNet then
1565 MH_SEND_Effect(it.Obj.X+it.Obj.Rect.X+(it.Obj.Rect.Width div 2)-32,
1566 it.Obj.Y+it.Obj.Rect.Y+(it.Obj.Rect.Height div 2)-32, 1,
1567 NET_GFX_TELE);
1568 end;
1569 EFFECT_RESPAWN: begin
1570 it := g_Items_ByIdx(iid);
1571 g_Sound_PlayExAt('SOUND_ITEM_RESPAWNITEM', tgcTX, tgcTY);
1572 g_GFX_QueueEffect(
1573 R_GFX_ITEM_RESPAWN,
1574 it.Obj.X+it.Obj.Rect.X+(it.Obj.Rect.Width div 2)-16,
1575 it.Obj.Y+it.Obj.Rect.Y+(it.Obj.Rect.Height div 2)-16
1576 );
1577 if g_Game_IsServer and g_Game_IsNet then
1578 MH_SEND_Effect(it.Obj.X+it.Obj.Rect.X+(it.Obj.Rect.Width div 2)-16,
1579 it.Obj.Y+it.Obj.Rect.Y+(it.Obj.Rect.Height div 2)-16, 1,
1580 NET_GFX_RESPAWN);
1581 end;
1582 EFFECT_FIRE: begin
1583 it := g_Items_ByIdx(iid);
1584 g_Sound_PlayExAt('SOUND_FIRE', tgcTX, tgcTY);
1585 g_GFX_QueueEffect(
1586 R_GFX_FIRE,
1587 it.Obj.X+it.Obj.Rect.X+(it.Obj.Rect.Width div 2)-32,
1588 it.Obj.Y+it.Obj.Rect.Y+it.Obj.Rect.Height-128
1589 );
1590 if g_Game_IsServer and g_Game_IsNet then
1591 MH_SEND_Effect(it.Obj.X+it.Obj.Rect.X+(it.Obj.Rect.Width div 2)-32,
1592 it.Obj.Y+it.Obj.Rect.Y+it.Obj.Rect.Height-128, 1,
1593 NET_GFX_FIRE);
1594 end;
1595 end;
1597 if g_Game_IsNet then
1598 MH_SEND_ItemSpawn(True, iid);
1599 end;
1601 if coolDown then
1602 TimeOut := 18
1603 else
1604 TimeOut := 0;
1605 // Åñëè àêòèâèðîâàí àâòîñïàâíåðîì, íå ìåíÿåì òåêñòóðó
1606 if actType = ACTIVATE_CUSTOM then
1607 Result := False;
1608 end;
1610 TRIGGER_MUSIC:
1611 begin
1612 // Ìåíÿåì ìóçûêó, åñëè åñòü íà ÷òî:
1613 if (Trigger.tgcMusicName <> '') then
1614 begin
1615 gMusic.SetByName(Trigger.tgcMusicName);
1616 gMusic.SpecPause := True;
1617 gMusic.Play();
1618 end;
1620 case Trigger.tgcMusicAction of
1621 TRIGGER_MUSIC_ACTION_STOP: // Âûêëþ÷èòü
1622 gMusic.SpecPause := True; // Ïàóçà
1623 TRIGGER_MUSIC_ACTION_PLAY: // Âêëþ÷èòü
1624 if gMusic.SpecPause then // Áûëà íà ïàóçå => èãðàòü
1625 gMusic.SpecPause := False
1626 else // Èãðàëà => ñíà÷àëà
1627 gMusic.SetPosition(0);
1628 end;
1630 if coolDown then
1631 TimeOut := 36
1632 else
1633 TimeOut := 0;
1634 Result := True;
1635 if g_Game_IsNet then MH_SEND_TriggerMusic;
1636 end;
1638 TRIGGER_PUSH:
1639 begin
1640 pAngle := -DegToRad(tgcAngle);
1641 Result := tr_Push(ActivateUID,
1642 Floor(Cos(pAngle)*tgcForce),
1643 Floor(Sin(pAngle)*tgcForce),
1644 tgcResetVelocity);
1645 TimeOut := 0;
1646 end;
1648 TRIGGER_SCORE:
1649 begin
1650 Result := False;
1651 // Ïðèáàâèòü èëè îòíÿòü î÷êî
1652 if (tgcScoreAction in [TRIGGER_SCORE_ACTION_ADD, TRIGGER_SCORE_ACTION_SUB]) and (tgcScoreCount > 0) then
1653 begin
1654 // Ñâîåé èëè ÷óæîé êîìàíäå
1655 if (tgcScoreTeam in [TRIGGER_SCORE_TEAM_MINE_RED, TRIGGER_SCORE_TEAM_MINE_BLUE]) and (g_GetUIDType(ActivateUID) = UID_PLAYER) then
1656 begin
1657 p := g_Player_Get(ActivateUID);
1658 if ((tgcScoreAction = TRIGGER_SCORE_ACTION_ADD) and (tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_RED) and (p.Team = TEAM_RED))
1659 or ((tgcScoreAction = TRIGGER_SCORE_ACTION_ADD) and (tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_BLUE) and (p.Team = TEAM_BLUE)) then
1660 begin
1661 Inc(gTeamStat[TEAM_RED].Goals, tgcScoreCount); // Red Scores
1663 if tgcScoreCon then
1664 begin
1665 if (tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_RED) then
1666 begin
1667 g_Console_Add(Format(_lc[I_PLAYER_SCORE_ADD_OWN], [p.Name, tgcScoreCount, _lc[I_PLAYER_SCORE_TO_RED]]), True);
1668 if g_Game_IsServer and g_Game_IsNet then
1669 MH_SEND_GameEvent(NET_EV_SCORE, p.UID or (tgcScoreCount shl 16), '+r');
1670 end else
1671 begin
1672 g_Console_Add(Format(_lc[I_PLAYER_SCORE_ADD_ENEMY], [p.Name, tgcScoreCount, _lc[I_PLAYER_SCORE_TO_RED]]), True);
1673 if g_Game_IsServer and g_Game_IsNet then
1674 MH_SEND_GameEvent(NET_EV_SCORE, p.UID or (tgcScoreCount shl 16), '+re');
1675 end;
1676 end;
1678 if tgcScoreMsg then
1679 begin
1680 g_Game_Message(Format(_lc[I_MESSAGE_SCORE_ADD], [AnsiUpperCase(_lc[I_GAME_TEAM_RED])]), 108);
1681 if g_Game_IsServer and g_Game_IsNet then
1682 MH_SEND_GameEvent(NET_EV_SCORE_MSG, TEAM_RED);
1683 end;
1684 end;
1685 if ((tgcScoreAction = TRIGGER_SCORE_ACTION_SUB) and (tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_RED) and (p.Team = TEAM_RED))
1686 or ((tgcScoreAction = TRIGGER_SCORE_ACTION_SUB) and (tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_BLUE) and (p.Team = TEAM_BLUE)) then
1687 begin
1688 Dec(gTeamStat[TEAM_RED].Goals, tgcScoreCount); // Red Fouls
1690 if tgcScoreCon then
1691 begin
1692 if (tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_RED) then
1693 begin
1694 g_Console_Add(Format(_lc[I_PLAYER_SCORE_SUB_OWN], [p.Name, tgcScoreCount, _lc[I_PLAYER_SCORE_TO_RED]]), True);
1695 if g_Game_IsServer and g_Game_IsNet then
1696 MH_SEND_GameEvent(NET_EV_SCORE, p.UID or (tgcScoreCount shl 16), '-r');
1697 end else
1698 begin
1699 g_Console_Add(Format(_lc[I_PLAYER_SCORE_SUB_ENEMY], [p.Name, tgcScoreCount, _lc[I_PLAYER_SCORE_TO_RED]]), True);
1700 if g_Game_IsServer and g_Game_IsNet then
1701 MH_SEND_GameEvent(NET_EV_SCORE, p.UID or (tgcScoreCount shl 16), '-re');
1702 end;
1703 end;
1705 if tgcScoreMsg then
1706 begin
1707 g_Game_Message(Format(_lc[I_MESSAGE_SCORE_SUB], [AnsiUpperCase(_lc[I_GAME_TEAM_RED])]), 108);
1708 if g_Game_IsServer and g_Game_IsNet then
1709 MH_SEND_GameEvent(NET_EV_SCORE_MSG, -TEAM_RED);
1710 end;
1711 end;
1712 if ((tgcScoreAction = TRIGGER_SCORE_ACTION_ADD) and (tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_RED) and (p.Team = TEAM_BLUE))
1713 or ((tgcScoreAction = TRIGGER_SCORE_ACTION_ADD) and (tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_BLUE) and (p.Team = TEAM_RED)) then
1714 begin
1715 Inc(gTeamStat[TEAM_BLUE].Goals, tgcScoreCount); // Blue Scores
1717 if tgcScoreCon then
1718 begin
1719 if (tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_RED) then
1720 begin
1721 g_Console_Add(Format(_lc[I_PLAYER_SCORE_ADD_OWN], [p.Name, tgcScoreCount, _lc[I_PLAYER_SCORE_TO_BLUE]]), True);
1722 if g_Game_IsServer and g_Game_IsNet then
1723 MH_SEND_GameEvent(NET_EV_SCORE, p.UID or (tgcScoreCount shl 16), '+b');
1724 end else
1725 begin
1726 g_Console_Add(Format(_lc[I_PLAYER_SCORE_ADD_ENEMY], [p.Name, tgcScoreCount, _lc[I_PLAYER_SCORE_TO_BLUE]]), True);
1727 if g_Game_IsServer and g_Game_IsNet then
1728 MH_SEND_GameEvent(NET_EV_SCORE, p.UID or (tgcScoreCount shl 16), '+be');
1729 end;
1730 end;
1732 if tgcScoreMsg then
1733 begin
1734 g_Game_Message(Format(_lc[I_MESSAGE_SCORE_ADD], [AnsiUpperCase(_lc[I_GAME_TEAM_BLUE])]), 108);
1735 if g_Game_IsServer and g_Game_IsNet then
1736 MH_SEND_GameEvent(NET_EV_SCORE_MSG, TEAM_BLUE);
1737 end;
1738 end;
1739 if ((tgcScoreAction = TRIGGER_SCORE_ACTION_SUB) and (tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_RED) and (p.Team = TEAM_BLUE))
1740 or ((tgcScoreAction = TRIGGER_SCORE_ACTION_SUB) and (tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_BLUE) and (p.Team = TEAM_RED)) then
1741 begin
1742 Dec(gTeamStat[TEAM_BLUE].Goals, tgcScoreCount); // Blue Fouls
1744 if tgcScoreCon then
1745 begin
1746 if (tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_RED) then
1747 begin
1748 g_Console_Add(Format(_lc[I_PLAYER_SCORE_SUB_OWN], [p.Name, tgcScoreCount, _lc[I_PLAYER_SCORE_TO_BLUE]]), True);
1749 if g_Game_IsServer and g_Game_IsNet then
1750 MH_SEND_GameEvent(NET_EV_SCORE, p.UID or (tgcScoreCount shl 16), '-b');
1751 end else
1752 begin
1753 g_Console_Add(Format(_lc[I_PLAYER_SCORE_SUB_ENEMY], [p.Name, tgcScoreCount, _lc[I_PLAYER_SCORE_TO_BLUE]]), True);
1754 if g_Game_IsServer and g_Game_IsNet then
1755 MH_SEND_GameEvent(NET_EV_SCORE, p.UID or (tgcScoreCount shl 16), '-be');
1756 end;
1757 end;
1759 if tgcScoreMsg then
1760 begin
1761 g_Game_Message(Format(_lc[I_MESSAGE_SCORE_SUB], [AnsiUpperCase(_lc[I_GAME_TEAM_BLUE])]), 108);
1762 if g_Game_IsServer and g_Game_IsNet then
1763 MH_SEND_GameEvent(NET_EV_SCORE_MSG, -TEAM_BLUE);
1764 end;
1765 end;
1766 Result := (p.Team = TEAM_RED) or (p.Team = TEAM_BLUE);
1767 end;
1768 // Êàêîé-òî êîíêðåòíîé êîìàíäå
1769 if tgcScoreTeam in [TRIGGER_SCORE_TEAM_FORCE_RED, TRIGGER_SCORE_TEAM_FORCE_BLUE] then
1770 begin
1771 if (tgcScoreAction = TRIGGER_SCORE_ACTION_ADD) and (tgcScoreTeam = TRIGGER_SCORE_TEAM_FORCE_RED) then
1772 begin
1773 Inc(gTeamStat[TEAM_RED].Goals, tgcScoreCount); // Red Scores
1775 if tgcScoreCon then
1776 begin
1777 g_Console_Add(Format(_lc[I_PLAYER_SCORE_ADD_TEAM], [_lc[I_PLAYER_SCORE_RED], tgcScoreCount]), True);
1778 if g_Game_IsServer and g_Game_IsNet then
1779 MH_SEND_GameEvent(NET_EV_SCORE, tgcScoreCount shl 16, '+tr');
1780 end;
1782 if tgcScoreMsg then
1783 begin
1784 g_Game_Message(Format(_lc[I_MESSAGE_SCORE_ADD], [AnsiUpperCase(_lc[I_GAME_TEAM_RED])]), 108);
1785 if g_Game_IsServer and g_Game_IsNet then
1786 MH_SEND_GameEvent(NET_EV_SCORE_MSG, TEAM_RED);
1787 end;
1788 end;
1789 if (tgcScoreAction = TRIGGER_SCORE_ACTION_SUB) and (tgcScoreTeam = TRIGGER_SCORE_TEAM_FORCE_RED) then
1790 begin
1791 Dec(gTeamStat[TEAM_RED].Goals, tgcScoreCount); // Red Fouls
1793 if tgcScoreCon then
1794 begin
1795 g_Console_Add(Format(_lc[I_PLAYER_SCORE_SUB_TEAM], [_lc[I_PLAYER_SCORE_RED], tgcScoreCount]), True);
1796 if g_Game_IsServer and g_Game_IsNet then
1797 MH_SEND_GameEvent(NET_EV_SCORE, tgcScoreCount shl 16, '-tr');
1798 end;
1800 if tgcScoreMsg then
1801 begin
1802 g_Game_Message(Format(_lc[I_MESSAGE_SCORE_SUB], [AnsiUpperCase(_lc[I_GAME_TEAM_RED])]), 108);
1803 if g_Game_IsServer and g_Game_IsNet then
1804 MH_SEND_GameEvent(NET_EV_SCORE_MSG, -TEAM_RED);
1805 end;
1806 end;
1807 if (tgcScoreAction = TRIGGER_SCORE_ACTION_ADD) and (tgcScoreTeam = TRIGGER_SCORE_TEAM_FORCE_BLUE) then
1808 begin
1809 Inc(gTeamStat[TEAM_BLUE].Goals, tgcScoreCount); // Blue Scores
1811 if tgcScoreCon then
1812 begin
1813 g_Console_Add(Format(_lc[I_PLAYER_SCORE_ADD_TEAM], [_lc[I_PLAYER_SCORE_BLUE], tgcScoreCount]), True);
1814 if g_Game_IsServer and g_Game_IsNet then
1815 MH_SEND_GameEvent(NET_EV_SCORE, tgcScoreCount shl 16, '+tb');
1816 end;
1818 if tgcScoreMsg then
1819 begin
1820 g_Game_Message(Format(_lc[I_MESSAGE_SCORE_ADD], [AnsiUpperCase(_lc[I_GAME_TEAM_BLUE])]), 108);
1821 if g_Game_IsServer and g_Game_IsNet then
1822 MH_SEND_GameEvent(NET_EV_SCORE_MSG, TEAM_BLUE);
1823 end;
1824 end;
1825 if (tgcScoreAction = TRIGGER_SCORE_ACTION_SUB) and (tgcScoreTeam = TRIGGER_SCORE_TEAM_FORCE_BLUE) then
1826 begin
1827 Dec(gTeamStat[TEAM_BLUE].Goals, tgcScoreCount); // Blue Fouls
1829 if tgcScoreCon then
1830 begin
1831 g_Console_Add(Format(_lc[I_PLAYER_SCORE_SUB_TEAM], [_lc[I_PLAYER_SCORE_BLUE], tgcScoreCount]), True);
1832 if g_Game_IsServer and g_Game_IsNet then
1833 MH_SEND_GameEvent(NET_EV_SCORE, tgcScoreCount shl 16, '-tb');
1834 end;
1836 if tgcScoreMsg then
1837 begin
1838 g_Game_Message(Format(_lc[I_MESSAGE_SCORE_SUB], [AnsiUpperCase(_lc[I_GAME_TEAM_BLUE])]), 108);
1839 if g_Game_IsServer and g_Game_IsNet then
1840 MH_SEND_GameEvent(NET_EV_SCORE_MSG, -TEAM_BLUE);
1841 end;
1842 end;
1843 Result := True;
1844 end;
1845 end;
1846 // Âûèãðûø
1847 if (tgcScoreAction = TRIGGER_SCORE_ACTION_WIN) and (gGameSettings.GoalLimit > 0) then
1848 begin
1849 // Ñâîåé èëè ÷óæîé êîìàíäû
1850 if (tgcScoreTeam in [TRIGGER_SCORE_TEAM_MINE_RED, TRIGGER_SCORE_TEAM_MINE_BLUE]) and (g_GetUIDType(ActivateUID) = UID_PLAYER) then
1851 begin
1852 p := g_Player_Get(ActivateUID);
1853 if ((tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_RED) and (p.Team = TEAM_RED)) // Red Wins
1854 or ((tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_BLUE) and (p.Team = TEAM_BLUE)) then
1855 begin
1856 if gTeamStat[TEAM_RED].Goals < SmallInt(gGameSettings.GoalLimit) then
1857 begin
1858 gTeamStat[TEAM_RED].Goals := gGameSettings.GoalLimit;
1860 if tgcScoreCon then
1861 begin
1862 if (tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_RED) then
1863 begin
1864 g_Console_Add(Format(_lc[I_PLAYER_SCORE_WIN_OWN], [p.Name, _lc[I_PLAYER_SCORE_TO_RED]]), True);
1865 if g_Game_IsServer and g_Game_IsNet then
1866 MH_SEND_GameEvent(NET_EV_SCORE, p.UID, 'wr');
1867 end else
1868 begin
1869 g_Console_Add(Format(_lc[I_PLAYER_SCORE_WIN_ENEMY], [p.Name, _lc[I_PLAYER_SCORE_TO_RED]]), True);
1870 if g_Game_IsServer and g_Game_IsNet then
1871 MH_SEND_GameEvent(NET_EV_SCORE, p.UID, 'wre');
1872 end;
1873 end;
1875 Result := True;
1876 end;
1877 end;
1878 if ((tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_RED) and (p.Team = TEAM_BLUE)) // Blue Wins
1879 or ((tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_BLUE) and (p.Team = TEAM_RED)) then
1880 begin
1881 if gTeamStat[TEAM_BLUE].Goals < SmallInt(gGameSettings.GoalLimit) then
1882 begin
1883 gTeamStat[TEAM_BLUE].Goals := gGameSettings.GoalLimit;
1885 if tgcScoreCon then
1886 begin
1887 if (tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_RED) then
1888 begin
1889 g_Console_Add(Format(_lc[I_PLAYER_SCORE_WIN_OWN], [p.Name, _lc[I_PLAYER_SCORE_TO_BLUE]]), True);
1890 if g_Game_IsServer and g_Game_IsNet then
1891 MH_SEND_GameEvent(NET_EV_SCORE, p.UID, 'wb');
1892 end else
1893 begin
1894 g_Console_Add(Format(_lc[I_PLAYER_SCORE_WIN_ENEMY], [p.Name, _lc[I_PLAYER_SCORE_TO_BLUE]]), True);
1895 if g_Game_IsServer and g_Game_IsNet then
1896 MH_SEND_GameEvent(NET_EV_SCORE, p.UID, 'wbe');
1897 end;
1898 end;
1900 Result := True;
1901 end;
1902 end;
1903 end;
1904 // Êàêîé-òî êîíêðåòíîé êîìàíäû
1905 if tgcScoreTeam in [TRIGGER_SCORE_TEAM_FORCE_RED, TRIGGER_SCORE_TEAM_FORCE_BLUE] then
1906 begin
1907 if (tgcScoreTeam = TRIGGER_SCORE_TEAM_FORCE_RED) then // Red Wins
1908 begin
1909 if gTeamStat[TEAM_RED].Goals < SmallInt(gGameSettings.GoalLimit) then
1910 begin
1911 gTeamStat[TEAM_RED].Goals := gGameSettings.GoalLimit;
1912 Result := True;
1913 end;
1914 end;
1915 if (tgcScoreTeam = TRIGGER_SCORE_TEAM_FORCE_BLUE) then // Blue Wins
1916 begin
1917 if gTeamStat[TEAM_BLUE].Goals < SmallInt(gGameSettings.GoalLimit) then
1918 begin
1919 gTeamStat[TEAM_BLUE].Goals := gGameSettings.GoalLimit;
1920 Result := True;
1921 end;
1922 end;
1923 end;
1924 end;
1925 // Ïðîèãðûø
1926 if (tgcScoreAction = TRIGGER_SCORE_ACTION_LOOSE) and (gGameSettings.GoalLimit > 0) then
1927 begin
1928 // Ñâîåé èëè ÷óæîé êîìàíäû
1929 if (tgcScoreTeam in [TRIGGER_SCORE_TEAM_MINE_RED, TRIGGER_SCORE_TEAM_MINE_BLUE]) and (g_GetUIDType(ActivateUID) = UID_PLAYER) then
1930 begin
1931 p := g_Player_Get(ActivateUID);
1932 if ((tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_RED) and (p.Team = TEAM_BLUE)) // Red Wins
1933 or ((tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_BLUE) and (p.Team = TEAM_RED)) then
1934 if gTeamStat[TEAM_RED].Goals < SmallInt(gGameSettings.GoalLimit) then
1935 begin
1936 gTeamStat[TEAM_RED].Goals := gGameSettings.GoalLimit;
1938 if tgcScoreCon then
1939 if tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_RED then
1940 begin
1941 g_Console_Add(Format(_lc[I_PLAYER_SCORE_WIN_ENEMY], [p.Name, _lc[I_PLAYER_SCORE_TO_RED]]), True);
1942 if g_Game_IsServer and g_Game_IsNet then
1943 MH_SEND_GameEvent(NET_EV_SCORE, p.UID, 'wre');
1944 end else
1945 begin
1946 g_Console_Add(Format(_lc[I_PLAYER_SCORE_WIN_OWN], [p.Name, _lc[I_PLAYER_SCORE_TO_RED]]), True);
1947 if g_Game_IsServer and g_Game_IsNet then
1948 MH_SEND_GameEvent(NET_EV_SCORE, p.UID, 'wr');
1949 end;
1951 Result := True;
1952 end;
1953 if ((tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_RED) and (p.Team = TEAM_RED)) // Blue Wins
1954 or ((tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_BLUE) and (p.Team = TEAM_BLUE)) then
1955 if gTeamStat[TEAM_BLUE].Goals < SmallInt(gGameSettings.GoalLimit) then
1956 begin
1957 gTeamStat[TEAM_BLUE].Goals := gGameSettings.GoalLimit;
1959 if tgcScoreCon then
1960 if tgcScoreTeam = TRIGGER_SCORE_TEAM_MINE_RED then
1961 begin
1962 g_Console_Add(Format(_lc[I_PLAYER_SCORE_WIN_ENEMY], [p.Name, _lc[I_PLAYER_SCORE_TO_BLUE]]), True);
1963 if g_Game_IsServer and g_Game_IsNet then
1964 MH_SEND_GameEvent(NET_EV_SCORE, p.UID, 'wbe');
1965 end else
1966 begin
1967 g_Console_Add(Format(_lc[I_PLAYER_SCORE_WIN_OWN], [p.Name, _lc[I_PLAYER_SCORE_TO_BLUE]]), True);
1968 if g_Game_IsServer and g_Game_IsNet then
1969 MH_SEND_GameEvent(NET_EV_SCORE, p.UID, 'wb');
1970 end;
1972 Result := True;
1973 end;
1974 end;
1975 // Êàêîé-òî êîíêðåòíîé êîìàíäû
1976 if tgcScoreTeam in [TRIGGER_SCORE_TEAM_FORCE_BLUE, TRIGGER_SCORE_TEAM_FORCE_RED] then
1977 begin
1978 if (tgcScoreTeam = TRIGGER_SCORE_TEAM_FORCE_BLUE) then // Red Wins
1979 begin
1980 if gTeamStat[TEAM_RED].Goals < SmallInt(gGameSettings.GoalLimit) then
1981 begin
1982 gTeamStat[TEAM_RED].Goals := gGameSettings.GoalLimit;
1983 Result := True;
1984 end;
1985 end;
1986 if (tgcScoreTeam = TRIGGER_SCORE_TEAM_FORCE_RED) then // Blue Wins
1987 begin
1988 if gTeamStat[TEAM_BLUE].Goals < SmallInt(gGameSettings.GoalLimit) then
1989 begin
1990 gTeamStat[TEAM_BLUE].Goals := gGameSettings.GoalLimit;
1991 Result := True;
1992 end;
1993 end;
1994 end;
1995 end;
1996 if Result then begin
1997 if coolDown then
1998 TimeOut := 18
1999 else
2000 TimeOut := 0;
2001 if g_Game_IsServer and g_Game_IsNet then
2002 MH_SEND_GameStats;
2003 end;
2004 end;
2006 TRIGGER_MESSAGE:
2007 begin
2008 Result := tr_Message(tgcKind, tgcText,
2009 tgcMsgDest, tgcMsgTime,
2010 ActivateUID);
2011 TimeOut := 18;
2012 end;
2014 TRIGGER_DAMAGE, TRIGGER_HEALTH:
2015 begin
2016 Result := False;
2017 UIDType := g_GetUIDType(ActivateUID);
2018 if (UIDType = UID_PLAYER) or (UIDType = UID_MONSTER) then
2019 begin
2020 Result := True;
2021 k := -1;
2022 if coolDown then
2023 begin
2024 // Âñïîìèíàåì, àêòèâèðîâàë ëè îí ìåíÿ ðàíüøå
2025 for idx := 0 to High(Activators) do
2026 if Activators[idx].UID = ActivateUID then
2027 begin
2028 k := idx;
2029 Break;
2030 end;
2031 if k = -1 then
2032 begin // Âèäèì åãî âïåðâûå
2033 // Çàïîìèíàåì åãî
2034 SetLength(Activators, Length(Activators) + 1);
2035 k := High(Activators);
2036 Activators[k].UID := ActivateUID;
2037 end else
2038 begin // Óæå âèäåëè åãî
2039 // Åñëè èíòåðâàë îòêëþ÷¸í, íî îí âñ¸ åù¸ â çîíå ïîðàæåíèÿ, äà¸ì åìó âðåìÿ
2040 if (tgcInterval = 0) and (Activators[k].TimeOut > 0) then
2041 Activators[k].TimeOut := 65535;
2042 // Òàéìàóò ïðîø¸ë - ðàáîòàåì
2043 Result := Activators[k].TimeOut = 0;
2044 end;
2045 end;
2047 if Result then
2048 begin
2049 case UIDType of
2050 UID_PLAYER:
2051 begin
2052 p := g_Player_Get(ActivateUID);
2053 if p = nil then
2054 Exit;
2056 // Íàíîñèì óðîí èãðîêó
2057 if (TriggerType = TRIGGER_DAMAGE) and (tgcAmount > 0) then
2058 begin
2059 // Êèñëîòíûé óðîí íå íàíîñèòñÿ êîãäà åñòü êîñòþì
2060 // "Âîäÿíîé" óðîí íå íàíîñèòñÿ êîãäà åñòü êèñëîðîä
2061 if not (((tgcKind = HIT_ACID) and (p.FMegaRulez[MR_SUIT] > gTime)) or
2062 ((tgcKind = HIT_WATER) and (p.Air > 0))) then
2063 p.Damage(tgcAmount, 0, 0, 0, tgcKind);
2064 if (tgcKind = HIT_FLAME) then p.CatchFire(0);
2065 end;
2067 // Ëå÷èì èãðîêà
2068 if (TriggerType = TRIGGER_HEALTH) and (tgcAmount > 0) then
2069 if p.Heal(tgcAmount, not tgcHealMax) and (not tgcSilent) then
2070 begin
2071 g_Sound_PlayExAt('SOUND_ITEM_GETITEM', p.Obj.X, p.Obj.Y);
2072 if g_Game_IsServer and g_Game_IsNet then
2073 MH_SEND_Sound(p.Obj.X, p.Obj.Y, 'SOUND_ITEM_GETITEM');
2074 end;
2075 end;
2077 UID_MONSTER:
2078 begin
2079 m := g_Monsters_ByUID(ActivateUID);
2080 if m = nil then
2081 Exit;
2083 // Íàíîñèì óðîí ìîíñòðó
2084 if (TriggerType = TRIGGER_DAMAGE) and (tgcAmount > 0) then
2085 begin
2086 m.Damage(tgcAmount, 0, 0, 0, tgcKind);
2087 if (tgcKind = HIT_FLAME) then m.CatchFire(0);
2088 end;
2090 // Ëå÷èì ìîíñòðà
2091 if (TriggerType = TRIGGER_HEALTH) and (tgcAmount > 0) then
2092 if m.Heal(tgcAmount) and (not tgcSilent) then
2093 begin
2094 g_Sound_PlayExAt('SOUND_ITEM_GETITEM', m.Obj.X, m.Obj.Y);
2095 if g_Game_IsServer and g_Game_IsNet then
2096 MH_SEND_Sound(m.Obj.X, m.Obj.Y, 'SOUND_ITEM_GETITEM');
2097 end;
2098 end;
2099 end;
2100 // Íàçíà÷àåì âðåìÿ ñëåäóþùåãî âîçäåéñòâèÿ
2101 idx := tgcInterval;
2102 if coolDown then
2103 if idx > 0 then
2104 Activators[k].TimeOut := idx
2105 else
2106 Activators[k].TimeOut := 65535;
2107 end;
2108 end;
2109 TimeOut := 0;
2110 end;
2112 TRIGGER_SHOT:
2113 begin
2114 if ShotSightTime > 0 then
2115 Exit;
2117 // put this at the beginning so it doesn't trigger itself
2118 TimeOut := tgcWait + 1;
2120 wx := tgcTX;
2121 wy := tgcTY;
2122 pAngle := -DegToRad(tgcAngle);
2123 xd := wx + Round(Cos(pAngle) * 32.0);
2124 yd := wy + Round(Sin(pAngle) * 32.0);
2125 TargetUID := 0;
2127 case tgcShotTarget of
2128 TRIGGER_SHOT_TARGET_MON: // monsters
2129 //TODO: accelerate this!
2130 g_Mons_ForEachAlive(monsShotTarget);
2132 TRIGGER_SHOT_TARGET_PLR: // players
2133 if gPlayers <> nil then
2134 for idx := Low(gPlayers) to High(gPlayers) do
2135 if (gPlayers[idx] <> nil) and gPlayers[idx].alive and
2136 tr_ShotAimCheck(Trigger, @(gPlayers[idx].Obj)) then
2137 begin
2138 xd := gPlayers[idx].GameX + PLAYER_RECT_CX;
2139 yd := gPlayers[idx].GameY + PLAYER_RECT_CY;
2140 TargetUID := gPlayers[idx].UID;
2141 break;
2142 end;
2144 TRIGGER_SHOT_TARGET_RED: // red team
2145 if gPlayers <> nil then
2146 for idx := Low(gPlayers) to High(gPlayers) do
2147 if (gPlayers[idx] <> nil) and gPlayers[idx].alive and
2148 (gPlayers[idx].Team = TEAM_RED) and
2149 tr_ShotAimCheck(Trigger, @(gPlayers[idx].Obj)) then
2150 begin
2151 xd := gPlayers[idx].GameX + PLAYER_RECT_CX;
2152 yd := gPlayers[idx].GameY + PLAYER_RECT_CY;
2153 TargetUID := gPlayers[idx].UID;
2154 break;
2155 end;
2157 TRIGGER_SHOT_TARGET_BLUE: // blue team
2158 if gPlayers <> nil then
2159 for idx := Low(gPlayers) to High(gPlayers) do
2160 if (gPlayers[idx] <> nil) and gPlayers[idx].alive and
2161 (gPlayers[idx].Team = TEAM_BLUE) and
2162 tr_ShotAimCheck(Trigger, @(gPlayers[idx].Obj)) then
2163 begin
2164 xd := gPlayers[idx].GameX + PLAYER_RECT_CX;
2165 yd := gPlayers[idx].GameY + PLAYER_RECT_CY;
2166 TargetUID := gPlayers[idx].UID;
2167 break;
2168 end;
2170 TRIGGER_SHOT_TARGET_MONPLR: // monsters then players
2171 begin
2172 //TODO: accelerate this!
2173 g_Mons_ForEachAlive(monsShotTargetMonPlr);
2175 if (TargetUID = 0) and (gPlayers <> nil) then
2176 for idx := Low(gPlayers) to High(gPlayers) do
2177 if (gPlayers[idx] <> nil) and gPlayers[idx].alive and
2178 tr_ShotAimCheck(Trigger, @(gPlayers[idx].Obj)) then
2179 begin
2180 xd := gPlayers[idx].GameX + PLAYER_RECT_CX;
2181 yd := gPlayers[idx].GameY + PLAYER_RECT_CY;
2182 TargetUID := gPlayers[idx].UID;
2183 break;
2184 end;
2185 end;
2187 TRIGGER_SHOT_TARGET_PLRMON: // players then monsters
2188 begin
2189 if gPlayers <> nil then
2190 for idx := Low(gPlayers) to High(gPlayers) do
2191 if (gPlayers[idx] <> nil) and gPlayers[idx].alive and
2192 tr_ShotAimCheck(Trigger, @(gPlayers[idx].Obj)) then
2193 begin
2194 xd := gPlayers[idx].GameX + PLAYER_RECT_CX;
2195 yd := gPlayers[idx].GameY + PLAYER_RECT_CY;
2196 TargetUID := gPlayers[idx].UID;
2197 break;
2198 end;
2199 if TargetUID = 0 then
2200 begin
2201 //TODO: accelerate this!
2202 g_Mons_ForEachAlive(monShotTargetPlrMon);
2203 end;
2204 end;
2206 else begin
2207 if (tgcShotTarget <> TRIGGER_SHOT_TARGET_NONE) or
2208 (tgcShotType <> TRIGGER_SHOT_REV) then
2209 TargetUID := ActivateUID;
2210 end;
2211 end;
2213 if (tgcShotTarget = TRIGGER_SHOT_TARGET_NONE) or (TargetUID > 0) or
2214 ((tgcShotTarget > TRIGGER_SHOT_TARGET_NONE) and (TargetUID = 0)) then
2215 begin
2216 Result := True;
2217 if (tgcSight = 0) or
2218 (tgcShotTarget = TRIGGER_SHOT_TARGET_NONE) or
2219 (TargetUID = ShotSightTarget) then
2220 MakeShot(Trigger, wx, wy, xd, yd, TargetUID)
2221 else
2222 begin
2223 ShotSightTime := tgcSight;
2224 ShotSightTargetN := TargetUID;
2225 if tgcShotType = TRIGGER_SHOT_BFG then
2226 begin
2227 g_Sound_PlayExAt('SOUND_WEAPON_STARTFIREBFG', wx, wy);
2228 if g_Game_IsNet and g_Game_IsServer then
2229 MH_SEND_Sound(wx, wy, 'SOUND_WEAPON_STARTFIREBFG');
2230 end;
2231 end;
2232 end;
2233 end;
2235 TRIGGER_EFFECT:
2236 begin
2237 idx := tgcFXCount;
2239 while idx > 0 do
2240 begin
2241 case tgcFXPos of
2242 TRIGGER_EFFECT_POS_CENTER:
2243 begin
2244 wx := X + Width div 2;
2245 wy := Y + Height div 2;
2246 end;
2247 TRIGGER_EFFECT_POS_AREA:
2248 begin
2249 wx := X + Random(Width);
2250 wy := Y + Random(Height);
2251 end;
2252 else begin
2253 wx := X + Width div 2;
2254 wy := Y + Height div 2;
2255 end;
2256 end;
2257 xd := tgcVelX;
2258 yd := tgcVelY;
2259 if tgcSpreadL > 0 then xd -= Random(tgcSpreadL+1);
2260 if tgcSpreadR > 0 then xd += Random(tgcSpreadR+1);
2261 if tgcSpreadU > 0 then yd -= Random(tgcSpreadU+1);
2262 if tgcSpreadD > 0 then yd += Random(tgcSpreadD+1);
2263 tr_MakeEffect(wx, wy, xd, yd,
2264 tgcFXType, tgcFXSubType,
2265 tgcFXRed, tgcFXGreen, tgcFXBlue, True, False);
2266 Dec(idx);
2267 end;
2268 TimeOut := tgcWait;
2269 result := true;
2270 end;
2271 end;
2272 end;
2274 if Result {and (Trigger.TexturePanel <> -1)} then
2275 begin
2276 g_Map_SwitchTextureGUID({Trigger.TexturePanelType,} Trigger.TexturePanelGUID, IfThen(animonce, 2, 1));
2277 end;
2278 end;
2281 function g_Triggers_CreateWithMapIndex (aTrigger: TTrigger; arridx, mapidx: Integer): DWORD;
2282 var
2283 triggers: TDynField;
2284 begin
2285 triggers := gCurrentMap['trigger'];
2286 if (triggers = nil) then raise Exception.Create('LOAD: map has no triggers');
2287 if (mapidx < 0) or (mapidx >= triggers.count) then raise Exception.Create('LOAD: invalid map trigger index');
2288 aTrigger.mapIndex := mapidx;
2289 result := g_Triggers_Create(aTrigger, triggers.itemAt[mapidx], arridx);
2290 end;
2293 function g_Triggers_Create (aTrigger: TTrigger; trec: TDynRecord; forceInternalIndex: Integer=-1): DWORD;
2294 var
2295 find_id: DWORD;
2296 fn: AnsiString;
2297 f, olen: Integer;
2298 ptg: PTrigger;
2299 begin
2300 if (tgscope = nil) then tgscope := TTrigScope.Create();
2301 if (tgclist = nil) then tgclist := TMyConstList.Create();
2303 // Íå ñîçäàâàòü âûõîä, åñëè èãðà áåç âûõîäà
2304 if (aTrigger.TriggerType = TRIGGER_EXIT) and
2305 (not LongBool(gGameSettings.Options and GAME_OPTION_ALLOWEXIT)) then
2306 begin
2307 aTrigger.TriggerType := TRIGGER_NONE;
2308 end;
2310 // Åñëè ìîíñòðû çàïðåùåíû, îòìåíÿåì òðèããåð
2311 if (aTrigger.TriggerType = TRIGGER_SPAWNMONSTER) and
2312 (not LongBool(gGameSettings.Options and GAME_OPTION_MONSTERS)) and
2313 (gGameSettings.GameType <> GT_SINGLE) then
2314 begin
2315 aTrigger.TriggerType := TRIGGER_NONE;
2316 end;
2318 // Ñ÷èòàåì êîëè÷åñòâî ñåêðåòîâ íà êàðòå
2319 if (aTrigger.TriggerType = TRIGGER_SECRET) then gSecretsCount += 1;
2321 if (forceInternalIndex < 0) then
2322 begin
2323 find_id := FindTrigger();
2324 end
2325 else
2326 begin
2327 olen := Length(gTriggers);
2328 if (forceInternalIndex >= olen) then
2329 begin
2330 SetLength(gTriggers, forceInternalIndex+1);
2331 for f := olen to High(gTriggers) do
2332 begin
2333 gTriggers[f].TriggerType := TRIGGER_NONE;
2334 gTriggers[f].trigDataRec := nil;
2335 gTriggers[f].exoInit := nil;
2336 gTriggers[f].exoThink := nil;
2337 gTriggers[f].exoCheck := nil;
2338 gTriggers[f].exoAction := nil;
2339 gTriggers[f].userVars := nil;
2340 end;
2341 end;
2342 f := forceInternalIndex;
2343 gTriggers[f].trigDataRec.Free();
2344 gTriggers[f].exoInit.Free();
2345 gTriggers[f].exoThink.Free();
2346 gTriggers[f].exoCheck.Free();
2347 gTriggers[f].exoAction.Free();
2348 gTriggers[f].userVars.Free();
2349 gTriggers[f].trigDataRec := nil;
2350 gTriggers[f].exoInit := nil;
2351 gTriggers[f].exoThink := nil;
2352 gTriggers[f].exoCheck := nil;
2353 gTriggers[f].exoAction := nil;
2354 gTriggers[f].userVars := nil;
2355 find_id := DWORD(forceInternalIndex);
2356 end;
2357 gTriggers[find_id] := aTrigger;
2358 ptg := @gTriggers[find_id];
2360 ptg.mapId := trec.id;
2361 // clone trigger data
2362 if (trec.trigRec = nil) then
2363 begin
2364 ptg.trigDataRec := nil;
2365 //HACK!
2366 if (ptg.TriggerType <> TRIGGER_SECRET) then
2367 begin
2368 e_LogWritefln('trigger of type %s has no triggerdata; wtf?!', [ptg.TriggerType], TMsgType.Warning);
2369 end;
2370 end
2371 else
2372 begin
2373 ptg.trigDataRec := trec.trigRec.clone(nil);
2374 end;
2376 with ptg^ do
2377 begin
2378 ID := find_id;
2379 // if this type of trigger exists both on the client and on the server
2380 // use an uniform numeration
2381 ClientID := 0;
2382 if (ptg.TriggerType = TRIGGER_SOUND) then
2383 begin
2384 Inc(gTriggerClientID);
2385 ClientID := gTriggerClientID;
2386 end;
2387 TimeOut := 0;
2388 ActivateUID := 0;
2389 PlayerCollide := False;
2390 DoorTime := -1;
2391 PressTime := -1;
2392 PressCount := 0;
2393 SoundPlayCount := 0;
2394 Sound := nil;
2395 AutoSpawn := False;
2396 SpawnCooldown := 0;
2397 SpawnedCount := 0;
2398 end;
2400 // update cached trigger variables
2401 trigUpdateCacheData(ptg^, ptg.trigDataRec);
2403 ptg.userVars := nil;
2405 try
2406 ptg.exoThink := TExprBase.parseStatList(tgclist, VarToStr(trec.user['exoma_think']));
2407 except
2408 on e: TExomaParseException do
2409 begin
2410 conwritefln('*** ERROR parsing exoma_think (%s,%s): %s [%s]', [e.tokLine, e.tokCol, e.message, VarToStr(trec.user['exoma_think'])]);
2411 ptg.exoThink := nil;
2412 end;
2413 else
2414 raise;
2415 end;
2416 try
2417 ptg.exoCheck := TExprBase.parse(tgclist, VarToStr(trec.user['exoma_check']));
2418 except
2419 on e: TExomaParseException do
2420 begin
2421 conwritefln('*** ERROR parsing exoma_check (%s,%s): %s [%s]', [e.tokLine, e.tokCol, e.message, VarToStr(trec.user['exoma_check'])]);
2422 ptg.exoCheck := nil;
2423 end;
2424 else
2425 raise;
2426 end;
2427 try
2428 ptg.exoAction := TExprBase.parseStatList(tgclist, VarToStr(trec.user['exoma_action']));
2429 except
2430 on e: TExomaParseException do
2431 begin
2432 conwritefln('*** ERROR parsing exoma_action (%s,%s): %s [%s]', [e.tokLine, e.tokCol, e.message, VarToStr(trec.user['exoma_action'])]);
2433 ptg.exoAction := nil;
2434 end;
2435 else
2436 raise;
2437 end;
2438 try
2439 ptg.exoInit := TExprBase.parseStatList(tgclist, VarToStr(trec.user['exoma_init']));
2440 except
2441 on e: TExomaParseException do
2442 begin
2443 conwritefln('*** ERROR parsing exoma_init (%s,%s): %s [%s]', [e.tokLine, e.tokCol, e.message, VarToStr(trec.user['exoma_init'])]);
2444 ptg.exoInit := nil;
2445 end;
2446 else
2447 raise;
2448 end;
2450 if (forceInternalIndex < 0) and (ptg.exoInit <> nil) then
2451 begin
2452 //conwritefln('executing trigger init: [%s]', [gTriggers[find_id].exoInit.toString()]);
2453 try
2454 tgscope.me := ptg;
2455 ptg.exoInit.value(tgscope);
2456 tgscope.me := nil;
2457 except
2458 tgscope.me := nil;
2459 conwritefln('*** trigger exoactivate error: %s', [ptg.exoInit.toString()]);
2460 exit;
2461 end;
2462 end;
2464 // Çàãðóæàåì çâóê, åñëè ýòî òðèããåð "Çâóê"
2465 if (ptg.TriggerType = TRIGGER_SOUND) and (ptg.tgcSoundName <> '') then
2466 begin
2467 // Åùå íåò òàêîãî çâóêà
2468 if not g_Sound_Exists(ptg.tgcSoundName) then
2469 begin
2470 fn := e_GetResourcePath(WadDirs, ptg.tgcSoundName, g_ExtractWadName(gMapInfo.Map));
2471 //e_LogWritefln('loading trigger sound ''%s''', [fn]);
2472 if not g_Sound_CreateWADEx(ptg.tgcSoundName, fn) then
2473 begin
2474 g_FatalError(Format(_lc[I_GAME_ERROR_TR_SOUND], [fn, ptg.tgcSoundName]));
2475 end;
2476 end;
2478 // Ñîçäàåì îáúåêò çâóêà
2479 with ptg^ do
2480 begin
2481 Sound := TPlayableSound.Create();
2482 if not Sound.SetByName(ptg.tgcSoundName) then
2483 begin
2484 Sound.Free();
2485 Sound := nil;
2486 end;
2487 end;
2488 end;
2490 // Çàãðóæàåì ìóçûêó, åñëè ýòî òðèããåð "Ìóçûêà"
2491 if (ptg.TriggerType = TRIGGER_MUSIC) and (ptg.tgcMusicName <> '') then
2492 begin
2493 // Åùå íåò òàêîé ìóçûêè
2494 if not g_Sound_Exists(ptg.tgcMusicName) then
2495 begin
2496 fn := e_GetResourcePath(WadDirs, ptg.tgcMusicName, g_ExtractWadName(gMapInfo.Map));
2497 if not g_Sound_CreateWADEx(ptg.tgcMusicName, fn, True) then
2498 begin
2499 g_FatalError(Format(_lc[I_GAME_ERROR_TR_SOUND], [fn, ptg.tgcMusicName]));
2500 end;
2501 end;
2502 end;
2504 // Çàãðóæàåì äàííûå òðèããåðà "Òóðåëü"
2505 if (ptg.TriggerType = TRIGGER_SHOT) then
2506 begin
2507 with ptg^ do
2508 begin
2509 ShotPanelTime := 0;
2510 ShotSightTime := 0;
2511 ShotSightTimeout := 0;
2512 ShotSightTarget := 0;
2513 ShotSightTargetN := 0;
2514 ShotAmmoCount := ptg.tgcAmmo;
2515 ShotReloadTime := 0;
2516 end;
2517 end;
2519 Result := find_id;
2520 end;
2523 // sorry; grid doesn't support recursive queries, so we have to do this
2524 type
2525 TSimpleMonsterList = specialize TSimpleList<TMonster>;
2527 var
2528 tgMonsList: TSimpleMonsterList = nil;
2530 procedure g_Triggers_Update();
2531 var
2532 a, b, i: Integer;
2533 Affected: array of Integer;
2535 function monsNear (mon: TMonster): Boolean;
2536 begin
2537 result := false; // don't stop
2539 gTriggers[a].ActivateUID := mon.UID;
2540 ActivateTrigger(gTriggers[a], ACTIVATE_MONSTERCOLLIDE);
2542 tgMonsList.append(mon);
2543 end;
2545 var
2546 mon: TMonster;
2547 pan: TPanel;
2548 begin
2549 if (tgMonsList = nil) then tgMonsList := TSimpleMonsterList.Create();
2551 if gTriggers = nil then Exit;
2552 if gLMSRespawn > LMS_RESPAWN_NONE then Exit; // don't update triggers at all
2554 SetLength(Affected, 0);
2556 for a := 0 to High(gTriggers) do
2557 with gTriggers[a] do
2558 // Åñòü òðèããåð:
2559 if TriggerType <> TRIGGER_NONE then
2560 begin
2561 // Óìåíüøàåì âðåìÿ äî çàêðûòèÿ äâåðè (îòêðûòèÿ ëîâóøêè)
2562 if DoorTime > 0 then DoorTime := DoorTime - 1;
2563 // Óìåíüøàåì âðåìÿ îæèäàíèÿ ïîñëå íàæàòèÿ
2564 if PressTime > 0 then PressTime := PressTime - 1;
2565 // Ïðîâåðÿåì èãðîêîâ è ìîíñòðîâ, êîòîðûõ ðàíåå çàïîìíèëè:
2566 if (TriggerType = TRIGGER_DAMAGE) or (TriggerType = TRIGGER_HEALTH) then
2567 begin
2568 for b := 0 to High(Activators) do
2569 begin
2570 // Óìåíüøàåì âðåìÿ äî ïîâòîðíîãî âîçäåéñòâèÿ:
2571 if Activators[b].TimeOut > 0 then
2572 begin
2573 Dec(Activators[b].TimeOut);
2574 end
2575 else
2576 begin
2577 continue;
2578 end;
2579 // Ñ÷èòàåì, ÷òî îáúåêò ïîêèíóë çîíó äåéñòâèÿ òðèããåðà
2580 if (tgcInterval = 0) and (Activators[b].TimeOut < 65530) then Activators[b].TimeOut := 0;
2581 end;
2582 end;
2584 // Îáðàáàòûâàåì ñïàâíåðû
2585 if Enabled and AutoSpawn then
2586 begin
2587 if SpawnCooldown = 0 then
2588 begin
2589 // Åñëè ïðèøëî âðåìÿ, ñïàâíèì ìîíñòðà
2590 if (TriggerType = TRIGGER_SPAWNMONSTER) and (tgcDelay > 0) then
2591 begin
2592 ActivateUID := 0;
2593 ActivateTrigger(gTriggers[a], ACTIVATE_CUSTOM);
2594 end;
2595 // Åñëè ïðèøëî âðåìÿ, ñïàâíèì ïðåäìåò
2596 if (TriggerType = TRIGGER_SPAWNITEM) and (tgcDelay > 0) then
2597 begin
2598 ActivateUID := 0;
2599 ActivateTrigger(gTriggers[a], ACTIVATE_CUSTOM);
2600 end;
2601 end
2602 else
2603 begin
2604 // Óìåíüøàåì âðåìÿ îæèäàíèÿ
2605 Dec(SpawnCooldown);
2606 end;
2607 end;
2609 // Îáðàáàòûâàåì ñîáûòèÿ òðèããåðà "Òóðåëü"
2610 if TriggerType = TRIGGER_SHOT then
2611 begin
2612 if ShotPanelTime > 0 then
2613 begin
2614 Dec(ShotPanelTime);
2615 if ShotPanelTime = 0 then g_Map_SwitchTextureGUID({ShotPanelType,} trigPanelGUID);
2616 end;
2617 if ShotSightTime > 0 then
2618 begin
2619 Dec(ShotSightTime);
2620 if ShotSightTime = 0 then ShotSightTarget := ShotSightTargetN;
2621 end;
2622 if ShotSightTimeout > 0 then
2623 begin
2624 Dec(ShotSightTimeout);
2625 if ShotSightTimeout = 0 then ShotSightTarget := 0;
2626 end;
2627 if ShotReloadTime > 0 then
2628 begin
2629 Dec(ShotReloadTime);
2630 if ShotReloadTime = 0 then ShotAmmoCount := tgcAmmo;
2631 end;
2632 end;
2634 // Òðèããåð "Çâóê" óæå îòûãðàë, åñëè íóæíî åùå - ïåðåçàïóñêàåì
2635 if Enabled and (TriggerType = TRIGGER_SOUND) and (Sound <> nil) then
2636 begin
2637 if (SoundPlayCount > 0) and (not Sound.IsPlaying()) then
2638 begin
2639 if tgcPlayCount > 0 then Dec(SoundPlayCount); (* looped sound if zero *)
2640 if tgcLocal then
2641 Sound.PlayVolumeAtRect(X, Y, Width, Height, tgcVolume / 255.0)
2642 else
2643 Sound.PlayPanVolume((tgcPan - 127.0) / 128.0, tgcVolume / 255.0);
2644 if Sound.IsPlaying() and g_Game_IsNet and g_Game_IsServer then
2645 MH_SEND_TriggerSound(gTriggers[a])
2646 end
2647 end;
2649 // Òðèããåð "Ëîâóøêà" - ïîðà îòêðûâàòü
2650 if (TriggerType = TRIGGER_TRAP) and (DoorTime = 0) and (g_Map_PanelByGUID(trigPanelGUID) <> nil) then
2651 begin
2652 tr_OpenDoor(trigPanelGUID, tgcSilent, tgcD2d);
2653 DoorTime := -1;
2654 end;
2656 // Òðèããåð "Äâåðü 5 ñåê" - ïîðà çàêðûâàòü
2657 if (TriggerType = TRIGGER_DOOR5) and (DoorTime = 0) and (g_Map_PanelByGUID(trigPanelGUID) <> nil) then
2658 begin
2659 pan := g_Map_PanelByGUID(trigPanelGUID);
2660 if (pan <> nil) and pan.isGWall then
2661 begin
2662 // Óæå çàêðûòà
2663 if {gWalls[trigPanelID].Enabled} pan.Enabled then
2664 begin
2665 DoorTime := -1;
2666 end
2667 else
2668 begin
2669 // Ïîêà îòêðûòà - çàêðûâàåì
2670 if tr_CloseDoor(trigPanelGUID, tgcSilent, tgcD2d) then DoorTime := -1;
2671 end;
2672 end;
2673 end;
2675 // Òðèããåð - ðàñøèðèòåëü èëè ïåðåêëþ÷àòåëü, è ïðîøëà çàäåðæêà, è íàæàëè íóæíîå ÷èñëî ðàç:
2676 if (TriggerType in [TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF]) and
2677 (PressTime = 0) and (PressCount >= tgcPressCount) then
2678 begin
2679 // Ñáðàñûâàåì çàäåðæêó àêòèâàöèè:
2680 PressTime := -1;
2681 // Ñáðàñûâàåì ñ÷åò÷èê íàæàòèé:
2682 if tgcPressCount > 0 then PressCount -= tgcPressCount else PressCount := 0;
2684 // Îïðåäåëÿåì èçìåíÿåìûå èì òðèããåðû:
2685 for b := 0 to High(gTriggers) do
2686 begin
2687 if g_Collide(tgcTX, tgcTY, tgcTWidth, tgcTHeight, gTriggers[b].X, gTriggers[b].Y,
2688 gTriggers[b].Width, gTriggers[b].Height) and
2689 ((b <> a) or (tgcWait > 0)) then
2690 begin // Can be self-activated, if there is Data.Wait
2691 if (not tgcExtRandom) or gTriggers[b].Enabled then
2692 begin
2693 SetLength(Affected, Length(Affected) + 1);
2694 Affected[High(Affected)] := b;
2695 end;
2696 end;
2697 end;
2699 //HACK!
2700 // if we have panelid, assume that it will switch the moving platform
2701 pan := g_Map_PanelByGUID(trigPanelGUID);
2702 if (pan <> nil) then
2703 begin
2704 case TriggerType of
2705 TRIGGER_PRESS: pan.movingActive := true; // what to do here?
2706 TRIGGER_ON: pan.movingActive := true;
2707 TRIGGER_OFF: pan.movingActive := false;
2708 TRIGGER_ONOFF: pan.movingActive := not pan.movingActive;
2709 end;
2710 if not tgcSilent and (Length(tgcSound) > 0) then
2711 begin
2712 g_Sound_PlayExAt(tgcSound, X, Y);
2713 if g_Game_IsServer and g_Game_IsNet then MH_SEND_Sound(X, Y, tgcSound);
2714 end;
2715 end;
2717 // Âûáèðàåì îäèí èç òðèããåðîâ äëÿ ðàñøèðèòåëÿ, åñëè âêëþ÷åí ðàíäîì:
2718 if (TriggerType = TRIGGER_PRESS) and tgcExtRandom then
2719 begin
2720 if (Length(Affected) > 0) then
2721 begin
2722 b := Affected[Random(Length(Affected))];
2723 gTriggers[b].ActivateUID := gTriggers[a].ActivateUID;
2724 ActivateTrigger(gTriggers[b], 0);
2725 end;
2726 end
2727 else //  ïðîòèâíîì ñëó÷àå ðàáîòàåì êàê îáû÷íî:
2728 begin
2729 for i := 0 to High(Affected) do
2730 begin
2731 b := Affected[i];
2732 case TriggerType of
2733 TRIGGER_PRESS:
2734 begin
2735 gTriggers[b].ActivateUID := gTriggers[a].ActivateUID;
2736 ActivateTrigger(gTriggers[b], 0);
2737 end;
2738 TRIGGER_ON:
2739 begin
2740 gTriggers[b].Enabled := True;
2741 end;
2742 TRIGGER_OFF:
2743 begin
2744 gTriggers[b].Enabled := False;
2745 gTriggers[b].TimeOut := 0;
2746 if gTriggers[b].AutoSpawn then
2747 begin
2748 gTriggers[b].AutoSpawn := False;
2749 gTriggers[b].SpawnCooldown := 0;
2750 end;
2751 end;
2752 TRIGGER_ONOFF:
2753 begin
2754 gTriggers[b].Enabled := not gTriggers[b].Enabled;
2755 if not gTriggers[b].Enabled then
2756 begin
2757 gTriggers[b].TimeOut := 0;
2758 if gTriggers[b].AutoSpawn then
2759 begin
2760 gTriggers[b].AutoSpawn := False;
2761 gTriggers[b].SpawnCooldown := 0;
2762 end;
2763 end;
2764 end;
2765 end;
2766 end;
2767 end;
2768 SetLength(Affected, 0);
2769 end;
2771 // Óìåíüøàåì âðåìÿ äî âîçìîæíîñòè ïîâòîðíîé àêòèâàöèè:
2772 if TimeOut > 0 then
2773 begin
2774 TimeOut := TimeOut - 1;
2775 Continue; // ×òîáû íå ïîòåðÿòü 1 åäèíèöó çàäåðæêè
2776 end;
2778 // Íèæå èäóò òèïû àêòèâàöèè, åñëè òðèããåð îòêëþ÷¸í - èä¸ì äàëüøå
2779 if not Enabled then
2780 Continue;
2782 // "Èãðîê áëèçêî":
2783 if ByteBool(ActivateType and ACTIVATE_PLAYERCOLLIDE) and
2784 (TimeOut = 0) then
2785 if gPlayers <> nil then
2786 for b := 0 to High(gPlayers) do
2787 if gPlayers[b] <> nil then
2788 with gPlayers[b] do
2789 // Æèâ, åñòü íóæíûå êëþ÷è è îí ðÿäîì:
2790 if alive and ((gTriggers[a].Keys and GetKeys) = gTriggers[a].Keys) and
2791 Collide(X, Y, Width, Height) then
2792 begin
2793 gTriggers[a].ActivateUID := UID;
2795 if (gTriggers[a].TriggerType in [TRIGGER_SOUND, TRIGGER_MUSIC]) and
2796 PlayerCollide then
2797 { Don't activate sound/music again if player is here }
2798 else
2799 ActivateTrigger(gTriggers[a], ACTIVATE_PLAYERCOLLIDE);
2800 end;
2802 { TODO 5 : àêòèâàöèÿ ìîíñòðàìè òðèããåðîâ ñ êëþ÷àìè }
2804 if ByteBool(ActivateType and ACTIVATE_MONSTERCOLLIDE) and
2805 ByteBool(ActivateType and ACTIVATE_NOMONSTER) and
2806 (TimeOut = 0) and (Keys = 0) then
2807 begin
2808 // Åñëè "Ìîíñòð áëèçêî" è "Ìîíñòðîâ íåò",
2809 // çàïóñêàåì òðèããåð íà ñòàðòå êàðòû è ñíèìàåì îáà ôëàãà
2810 ActivateType := ActivateType and not (ACTIVATE_MONSTERCOLLIDE or ACTIVATE_NOMONSTER);
2811 gTriggers[a].ActivateUID := 0;
2812 ActivateTrigger(gTriggers[a], 0);
2813 end else
2814 begin
2815 // "Ìîíñòð áëèçêî"
2816 if ByteBool(ActivateType and ACTIVATE_MONSTERCOLLIDE) and
2817 (TimeOut = 0) and (Keys = 0) then // Åñëè íå íóæíû êëþ÷è
2818 begin
2819 //g_Mons_ForEach(monsNear);
2820 //Alive?!
2821 tgMonsList.reset();
2822 g_Mons_ForEachAt(gTriggers[a].X, gTriggers[a].Y, gTriggers[a].Width, gTriggers[a].Height, monsNear);
2823 for mon in tgMonsList do
2824 begin
2825 gTriggers[a].ActivateUID := mon.UID;
2826 ActivateTrigger(gTriggers[a], ACTIVATE_MONSTERCOLLIDE);
2827 end;
2828 tgMonsList.reset(); // just in case
2829 end;
2831 // "Ìîíñòðîâ íåò"
2832 if ByteBool(ActivateType and ACTIVATE_NOMONSTER) and
2833 (TimeOut = 0) and (Keys = 0) then
2834 if not g_Mons_IsAnyAliveAt(X, Y, Width, Height) then
2835 begin
2836 gTriggers[a].ActivateUID := 0;
2837 ActivateTrigger(gTriggers[a], ACTIVATE_NOMONSTER);
2838 end;
2839 end;
2841 PlayerCollide := g_CollidePlayer(X, Y, Width, Height);
2842 end;
2843 end;
2845 procedure g_Triggers_Press(ID: DWORD; ActivateType: Byte; ActivateUID: Word = 0);
2846 begin
2847 if (ID >= Length(gTriggers)) then exit;
2848 gTriggers[ID].ActivateUID := ActivateUID;
2849 ActivateTrigger(gTriggers[ID], ActivateType);
2850 end;
2852 function g_Triggers_PressR(X, Y: Integer; Width, Height: Word; UID: Word;
2853 ActivateType: Byte; IgnoreList: DWArray = nil): DWArray;
2854 var
2855 a: Integer;
2856 k: Byte;
2857 p: TPlayer;
2858 begin
2859 Result := nil;
2861 if gTriggers = nil then Exit;
2863 case g_GetUIDType(UID) of
2864 UID_GAME: k := 255;
2865 UID_PLAYER:
2866 begin
2867 p := g_Player_Get(UID);
2868 if p <> nil then
2869 k := p.GetKeys
2870 else
2871 k := 0;
2872 end;
2873 else k := 0;
2874 end;
2876 for a := 0 to High(gTriggers) do
2877 if (gTriggers[a].TriggerType <> TRIGGER_NONE) and
2878 (gTriggers[a].TimeOut = 0) and
2879 (not InDWArray(a, IgnoreList)) and
2880 ((gTriggers[a].Keys and k) = gTriggers[a].Keys) and
2881 ByteBool(gTriggers[a].ActivateType and ActivateType) then
2882 if g_Collide(X, Y, Width, Height,
2883 gTriggers[a].X, gTriggers[a].Y,
2884 gTriggers[a].Width, gTriggers[a].Height) then
2885 begin
2886 gTriggers[a].ActivateUID := UID;
2887 if ActivateTrigger(gTriggers[a], ActivateType) then
2888 begin
2889 SetLength(Result, Length(Result)+1);
2890 Result[High(Result)] := a;
2891 end;
2892 end;
2893 end;
2895 procedure g_Triggers_PressL(X1, Y1, X2, Y2: Integer; UID: DWORD; ActivateType: Byte);
2896 var
2897 a: Integer;
2898 k: Byte;
2899 p: TPlayer;
2900 begin
2901 if gTriggers = nil then Exit;
2903 case g_GetUIDType(UID) of
2904 UID_GAME: k := 255;
2905 UID_PLAYER:
2906 begin
2907 p := g_Player_Get(UID);
2908 if p <> nil then
2909 k := p.GetKeys
2910 else
2911 k := 0;
2912 end;
2913 else k := 0;
2914 end;
2916 for a := 0 to High(gTriggers) do
2917 if (gTriggers[a].TriggerType <> TRIGGER_NONE) and
2918 (gTriggers[a].TimeOut = 0) and
2919 ((gTriggers[a].Keys and k) = gTriggers[a].Keys) and
2920 ByteBool(gTriggers[a].ActivateType and ActivateType) then
2921 if g_CollideLine(x1, y1, x2, y2, gTriggers[a].X, gTriggers[a].Y,
2922 gTriggers[a].Width, gTriggers[a].Height) then
2923 begin
2924 gTriggers[a].ActivateUID := UID;
2925 ActivateTrigger(gTriggers[a], ActivateType);
2926 end;
2927 end;
2929 procedure g_Triggers_PressC(CX, CY: Integer; Radius: Word; UID: Word; ActivateType: Byte; IgnoreTrigger: Integer = -1);
2930 var
2931 a: Integer;
2932 k: Byte;
2933 rsq: Word;
2934 p: TPlayer;
2935 begin
2936 if gTriggers = nil then
2937 Exit;
2939 case g_GetUIDType(UID) of
2940 UID_GAME: k := 255;
2941 UID_PLAYER:
2942 begin
2943 p := g_Player_Get(UID);
2944 if p <> nil then
2945 k := p.GetKeys
2946 else
2947 k := 0;
2948 end;
2949 else k := 0;
2950 end;
2952 rsq := Radius * Radius;
2954 for a := 0 to High(gTriggers) do
2955 if (gTriggers[a].ID <> DWORD(IgnoreTrigger)) and
2956 (gTriggers[a].TriggerType <> TRIGGER_NONE) and
2957 (gTriggers[a].TimeOut = 0) and
2958 ((gTriggers[a].Keys and k) = gTriggers[a].Keys) and
2959 ByteBool(gTriggers[a].ActivateType and ActivateType) then
2960 with gTriggers[a] do
2961 if g_Collide(CX-Radius, CY-Radius, 2*Radius, 2*Radius,
2962 X, Y, Width, Height) then
2963 if ((Sqr(CX-X)+Sqr(CY-Y)) < rsq) or // Öåíòð êðóãà áëèçîê ê âåðõíåìó ëåâîìó óãëó
2964 ((Sqr(CX-X-Width)+Sqr(CY-Y)) < rsq) or // Öåíòð êðóãà áëèçîê ê âåðõíåìó ïðàâîìó óãëó
2965 ((Sqr(CX-X-Width)+Sqr(CY-Y-Height)) < rsq) or // Öåíòð êðóãà áëèçîê ê íèæíåìó ïðàâîìó óãëó
2966 ((Sqr(CX-X)+Sqr(CY-Y-Height)) < rsq) or // Öåíòð êðóãà áëèçîê ê íèæíåìó ëåâîìó óãëó
2967 ( (CX > (X-Radius)) and (CX < (X+Width+Radius)) and
2968 (CY > Y) and (CY < (Y+Height)) ) or // Öåíòð êðóãà íåäàëåêî îò âåðòèêàëüíûõ ãðàíèö ïðÿìîóãîëüíèêà
2969 ( (CY > (Y-Radius)) and (CY < (Y+Height+Radius)) and
2970 (CX > X) and (CX < (X+Width)) ) then // Öåíòð êðóãà íåäàëåêî îò ãîðèçîíòàëüíûõ ãðàíèö ïðÿìîóãîëüíèêà
2971 begin
2972 ActivateUID := UID;
2973 ActivateTrigger(gTriggers[a], ActivateType);
2974 end;
2975 end;
2977 procedure g_Triggers_OpenAll();
2978 var
2979 a: Integer;
2980 b: Boolean;
2981 begin
2982 if gTriggers = nil then Exit;
2984 b := False;
2985 for a := 0 to High(gTriggers) do
2986 begin
2987 with gTriggers[a] do
2988 begin
2989 if (TriggerType = TRIGGER_OPENDOOR) or
2990 (TriggerType = TRIGGER_DOOR5) or
2991 (TriggerType = TRIGGER_DOOR) then
2992 begin
2993 tr_OpenDoor(trigPanelGUID, True, tgcD2d);
2994 if TriggerType = TRIGGER_DOOR5 then DoorTime := 180;
2995 b := True;
2996 end;
2997 end;
2998 end;
3000 if b then g_Sound_PlayEx('SOUND_GAME_DOOROPEN');
3001 end;
3003 procedure g_Triggers_DecreaseSpawner(ID: DWORD);
3004 begin
3005 if (gTriggers <> nil) then
3006 begin
3007 if gTriggers[ID].tgcMax > 0 then
3008 begin
3009 if gTriggers[ID].SpawnedCount > 0 then
3010 Dec(gTriggers[ID].SpawnedCount);
3011 end;
3012 if gTriggers[ID].tgcDelay > 0 then
3013 begin
3014 if gTriggers[ID].SpawnCooldown < 0 then
3015 gTriggers[ID].SpawnCooldown := gTriggers[ID].tgcDelay;
3016 end;
3017 end;
3018 end;
3020 procedure g_Triggers_Free ();
3021 var
3022 a: Integer;
3023 begin
3024 for a := 0 to High(gTriggers) do
3025 begin
3026 if (gTriggers[a].TriggerType = TRIGGER_SOUND) then
3027 begin
3028 if g_Sound_Exists(gTriggers[a].tgcSoundName) then
3029 begin
3030 g_Sound_Delete(gTriggers[a].tgcSoundName);
3031 end;
3032 gTriggers[a].Sound.Free();
3033 end;
3034 if (gTriggers[a].Activators <> nil) then
3035 begin
3036 SetLength(gTriggers[a].Activators, 0);
3037 end;
3038 gTriggers[a].trigDataRec.Free();
3040 gTriggers[a].exoThink.Free();
3041 gTriggers[a].exoCheck.Free();
3042 gTriggers[a].exoAction.Free();
3043 gTriggers[a].userVars.Free();
3044 end;
3046 gTriggers := nil;
3047 gSecretsCount := 0;
3048 SetLength(gMonstersSpawned, 0);
3049 end;
3052 procedure g_Triggers_SaveState (st: TStream);
3053 var
3054 count, actCount, i, j: Integer;
3055 sg: Single;
3056 b: Boolean;
3057 kv: THashStrVariant.PEntry;
3058 t: LongInt;
3059 begin
3060 // Ñ÷èòàåì êîëè÷åñòâî ñóùåñòâóþùèõ òðèããåðîâ
3061 count := Length(gTriggers);
3063 // Êîëè÷åñòâî òðèããåðîâ
3064 utils.writeInt(st, LongInt(count));
3065 if (count = 0) then exit;
3067 for i := 0 to High(gTriggers) do
3068 begin
3069 // Ñèãíàòóðà òðèããåðà
3070 utils.writeSign(st, 'TRGX');
3071 utils.writeInt(st, Byte(0));
3072 // Òèï òðèããåðà
3073 utils.writeInt(st, Byte(gTriggers[i].TriggerType));
3074 if (gTriggers[i].TriggerType = TRIGGER_NONE) then continue; // empty one
3075 // Ñïåöèàëüíûå äàííûå òðèããåðà: ïîòîì èç êàðòû îïÿòü âûòàùèì; ñîõðàíèì òîëüêî èíäåêñ
3076 utils.writeInt(st, LongInt(gTriggers[i].mapIndex));
3077 // Êîîðäèíàòû ëåâîãî âåðõíåãî óãëà
3078 utils.writeInt(st, LongInt(gTriggers[i].X));
3079 utils.writeInt(st, LongInt(gTriggers[i].Y));
3080 // Ðàçìåðû
3081 utils.writeInt(st, Word(gTriggers[i].Width));
3082 utils.writeInt(st, Word(gTriggers[i].Height));
3083 // Âêëþ÷åí ëè òðèããåð
3084 utils.writeBool(st, gTriggers[i].Enabled);
3085 // Òèï àêòèâàöèè òðèããåðà
3086 utils.writeInt(st, Byte(gTriggers[i].ActivateType));
3087 // Êëþ÷è, íåîáõîäèìûå äëÿ àêòèâàöèè
3088 utils.writeInt(st, Byte(gTriggers[i].Keys));
3089 // ID ïàíåëè, òåêñòóðà êîòîðîé èçìåíèòñÿ
3090 utils.writeInt(st, LongInt(gTriggers[i].TexturePanelGUID));
3091 // Òèï ýòîé ïàíåëè
3092 //Mem.WriteWord(gTriggers[i].TexturePanelType);
3093 // Âíóòðåííèé íîìåð äðóãîé ïàíåëè (ïî ñ÷àñòëèâîé ñëó÷àéíîñòè îí áóäåò ñîâïàäàòü ñ òåì, ÷òî ñîçäàíî ïðè çàãðóçêå êàðòû)
3094 utils.writeInt(st, LongInt(gTriggers[i].trigPanelGUID));
3095 // Âðåìÿ äî âîçìîæíîñòè àêòèâàöèè
3096 utils.writeInt(st, Word(gTriggers[i].TimeOut));
3097 // UID òîãî, êòî àêòèâèðîâàë ýòîò òðèããåð
3098 utils.writeInt(st, Word(gTriggers[i].ActivateUID));
3099 // Ñïèñîê UID-îâ îáúåêòîâ, êîòîðûå íàõîäèëèñü ïîä âîçäåéñòâèåì
3100 actCount := Length(gTriggers[i].Activators);
3101 utils.writeInt(st, LongInt(actCount));
3102 for j := 0 to actCount-1 do
3103 begin
3104 // UID îáúåêòà
3105 utils.writeInt(st, Word(gTriggers[i].Activators[j].UID));
3106 // Âðåìÿ îæèäàíèÿ
3107 utils.writeInt(st, Word(gTriggers[i].Activators[j].TimeOut));
3108 end;
3109 // Ñòîèò ëè èãðîê â îáëàñòè òðèããåðà
3110 utils.writeBool(st, gTriggers[i].PlayerCollide);
3111 // Âðåìÿ äî çàêðûòèÿ äâåðè
3112 utils.writeInt(st, LongInt(gTriggers[i].DoorTime));
3113 // Çàäåðæêà àêòèâàöèè
3114 utils.writeInt(st, LongInt(gTriggers[i].PressTime));
3115 // Ñ÷åò÷èê íàæàòèé
3116 utils.writeInt(st, LongInt(gTriggers[i].PressCount));
3117 // Ñïàâíåð àêòèâåí
3118 utils.writeBool(st, gTriggers[i].AutoSpawn);
3119 // Çàäåðæêà ñïàâíåðà
3120 utils.writeInt(st, LongInt(gTriggers[i].SpawnCooldown));
3121 // Ñ÷åò÷èê ñîçäàíèÿ îáúåêòîâ
3122 utils.writeInt(st, LongInt(gTriggers[i].SpawnedCount));
3123 // Ñêîëüêî ðàç ïðîèãðàí çâóê
3124 utils.writeInt(st, LongInt(gTriggers[i].SoundPlayCount));
3125 // Ïðîèãðûâàåòñÿ ëè çâóê?
3126 if (gTriggers[i].Sound <> nil) then b := gTriggers[i].Sound.IsPlaying() else b := false;
3127 utils.writeBool(st, b);
3128 if b then
3129 begin
3130 // Ïîçèöèÿ ïðîèãðûâàíèÿ çâóêà
3131 utils.writeInt(st, LongWord(gTriggers[i].Sound.GetPosition()));
3132 // Ãðîìêîñòü çâóêà
3133 sg := gTriggers[i].Sound.GetVolume();
3134 sg := sg/(gSoundLevel/255.0);
3135 //Mem.WriteSingle(sg);
3136 st.WriteBuffer(sg, sizeof(sg)); // sorry
3137 // Ñòåðåî ñìåùåíèå çâóêà
3138 sg := gTriggers[i].Sound.GetPan();
3139 //Mem.WriteSingle(sg);
3140 st.WriteBuffer(sg, sizeof(sg)); // sorry
3141 end;
3142 // uservars
3143 if (gTriggers[i].userVars = nil) then
3144 begin
3145 utils.writeInt(st, LongInt(0));
3146 end
3147 else
3148 begin
3149 utils.writeInt(st, LongInt(gTriggers[i].userVars.count)); //FIXME: check for overflow
3150 for kv in gTriggers[i].userVars.byKeyValue do
3151 begin
3152 //writeln('<', kv.key, '>:<', VarToStr(kv.value), '>');
3153 utils.writeStr(st, kv.key);
3154 t := LongInt(varType(kv.value));
3155 utils.writeInt(st, LongInt(t));
3156 case t of
3157 varString: utils.writeStr(st, AnsiString(kv.value));
3158 varBoolean: utils.writeBool(st, Boolean(kv.value));
3159 varShortInt: utils.writeInt(st, LongInt(kv.value));
3160 varSmallint: utils.writeInt(st, LongInt(kv.value));
3161 varInteger: utils.writeInt(st, LongInt(kv.value));
3162 //varInt64: Mem.WriteInt(Integer(kv.value));
3163 varByte: utils.writeInt(st, LongInt(kv.value));
3164 varWord: utils.writeInt(st, LongInt(kv.value));
3165 varLongWord: utils.writeInt(st, LongInt(kv.value));
3166 //varQWord:
3167 else raise Exception.CreateFmt('cannot save uservar ''%s''', [kv.key]);
3168 end;
3169 end;
3170 end;
3171 end;
3172 end;
3175 procedure g_Triggers_LoadState (st: TStream);
3176 var
3177 count, actCount, i, j, a: Integer;
3178 dw: DWORD;
3179 vol, pan: Single;
3180 b: Boolean;
3181 Trig: TTrigger;
3182 mapIndex: Integer;
3183 uvcount: Integer;
3184 vt: LongInt;
3185 vv: Variant;
3186 uvname: AnsiString = '';
3187 ustr: AnsiString = '';
3188 uint: LongInt;
3189 ubool: Boolean;
3190 begin
3191 assert(st <> nil);
3193 g_Triggers_Free();
3195 // Êîëè÷åñòâî òðèããåðîâ
3196 count := utils.readLongInt(st);
3197 if (count = 0) then exit;
3198 if (count < 0) or (count > 1024*1024) then raise XStreamError.Create('invalid trigger count');
3200 for a := 0 to count-1 do
3201 begin
3202 // Ñèãíàòóðà òðèããåðà
3203 if not utils.checkSign(st, 'TRGX') then raise XStreamError.Create('invalid trigger signature');
3204 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid trigger version');
3205 // Òèï òðèããåðà
3206 Trig.TriggerType := utils.readByte(st);
3207 if (Trig.TriggerType = TRIGGER_NONE) then continue; // empty one
3208 // Ñïåöèàëüíûå äàííûå òðèããåðà: èíäåêñ â gCurrentMap.field['triggers']
3209 mapIndex := utils.readLongInt(st);
3210 i := g_Triggers_CreateWithMapIndex(Trig, a, mapIndex);
3211 // Êîîðäèíàòû ëåâîãî âåðõíåãî óãëà
3212 gTriggers[i].X := utils.readLongInt(st);
3213 gTriggers[i].Y := utils.readLongInt(st);
3214 // Ðàçìåðû
3215 gTriggers[i].Width := utils.readWord(st);
3216 gTriggers[i].Height := utils.readWord(st);
3217 // Âêëþ÷åí ëè òðèããåð
3218 gTriggers[i].Enabled := utils.readBool(st);
3219 // Òèï àêòèâàöèè òðèããåðà
3220 gTriggers[i].ActivateType := utils.readByte(st);
3221 // Êëþ÷è, íåîáõîäèìûå äëÿ àêòèâàöèè
3222 gTriggers[i].Keys := utils.readByte(st);
3223 // ID ïàíåëè, òåêñòóðà êîòîðîé èçìåíèòñÿ
3224 gTriggers[i].TexturePanelGUID := utils.readLongInt(st);
3225 // Òèï ýòîé ïàíåëè
3226 //Mem.ReadWord(gTriggers[i].TexturePanelType);
3227 // Âíóòðåííèé íîìåð äðóãîé ïàíåëè (ïî ñ÷àñòëèâîé ñëó÷àéíîñòè îí áóäåò ñîâïàäàòü ñ òåì, ÷òî ñîçäàíî ïðè çàãðóçêå êàðòû)
3228 gTriggers[i].trigPanelGUID := utils.readLongInt(st);
3229 // Âðåìÿ äî âîçìîæíîñòè àêòèâàöèè
3230 gTriggers[i].TimeOut := utils.readWord(st);
3231 // UID òîãî, êòî àêòèâèðîâàë ýòîò òðèããåð
3232 gTriggers[i].ActivateUID := utils.readWord(st);
3233 // Ñïèñîê UID-îâ îáúåêòîâ, êîòîðûå íàõîäèëèñü ïîä âîçäåéñòâèåì
3234 actCount := utils.readLongInt(st);
3235 if (actCount < 0) or (actCount > 1024*1024) then raise XStreamError.Create('invalid activated object count');
3236 if (actCount > 0) then
3237 begin
3238 SetLength(gTriggers[i].Activators, actCount);
3239 for j := 0 to actCount-1 do
3240 begin
3241 // UID îáúåêòà
3242 gTriggers[i].Activators[j].UID := utils.readWord(st);
3243 // Âðåìÿ îæèäàíèÿ
3244 gTriggers[i].Activators[j].TimeOut := utils.readWord(st);
3245 end;
3246 end;
3247 // Ñòîèò ëè èãðîê â îáëàñòè òðèããåðà
3248 gTriggers[i].PlayerCollide := utils.readBool(st);
3249 // Âðåìÿ äî çàêðûòèÿ äâåðè
3250 gTriggers[i].DoorTime := utils.readLongInt(st);
3251 // Çàäåðæêà àêòèâàöèè
3252 gTriggers[i].PressTime := utils.readLongInt(st);
3253 // Ñ÷åò÷èê íàæàòèé
3254 gTriggers[i].PressCount := utils.readLongInt(st);
3255 // Ñïàâíåð àêòèâåí
3256 gTriggers[i].AutoSpawn := utils.readBool(st);
3257 // Çàäåðæêà ñïàâíåðà
3258 gTriggers[i].SpawnCooldown := utils.readLongInt(st);
3259 // Ñ÷åò÷èê ñîçäàíèÿ îáúåêòîâ
3260 gTriggers[i].SpawnedCount := utils.readLongInt(st);
3261 // Ñêîëüêî ðàç ïðîèãðàí çâóê
3262 gTriggers[i].SoundPlayCount := utils.readLongInt(st);
3263 // Ïðîèãðûâàåòñÿ ëè çâóê?
3264 b := utils.readBool(st);
3265 if b then
3266 begin
3267 // Ïîçèöèÿ ïðîèãðûâàíèÿ çâóêà
3268 dw := utils.readLongWord(st);
3269 // Ãðîìêîñòü çâóêà
3270 //Mem.ReadSingle(vol);
3271 st.ReadBuffer(vol, sizeof(vol)); // sorry
3272 // Ñòåðåî ñìåùåíèå çâóêà
3273 //Mem.ReadSingle(pan);
3274 st.ReadBuffer(pan, sizeof(pan)); // sorry
3275 // Çàïóñêàåì çâóê, åñëè åñòü
3276 if (gTriggers[i].Sound <> nil) then
3277 begin
3278 gTriggers[i].Sound.PlayPanVolume(pan, vol);
3279 gTriggers[i].Sound.Pause(True);
3280 gTriggers[i].Sound.SetPosition(dw);
3281 end
3282 end;
3283 // uservars
3284 gTriggers[i].userVars.Free();
3285 gTriggers[i].userVars := nil;
3286 uvcount := utils.readLongInt(st);
3287 if (uvcount < 0) or (uvcount > 1024*1024) then raise XStreamError.Create('invalid number of user vars in trigger');
3288 if (uvcount > 0) then
3289 begin
3290 gTriggers[i].userVars := THashStrVariant.Create();
3291 vv := Unassigned;
3292 while (uvcount > 0) do
3293 begin
3294 Dec(uvcount);
3295 uvname := utils.readStr(st);
3296 vt := utils.readLongInt(st);
3297 case vt of
3298 varString: begin ustr := utils.readStr(st); vv := ustr; end;
3299 varBoolean: begin ubool := utils.readBool(st); vv := ubool; end;
3300 varShortInt: begin uint := utils.readLongInt(st); vv := ShortInt(uint); end;
3301 varSmallint: begin uint := utils.readLongInt(st); vv := SmallInt(uint); end;
3302 varInteger: begin uint := utils.readLongInt(st); vv := LongInt(uint); end;
3303 varByte: begin uint := utils.readLongInt(st); vv := Byte(uint); end;
3304 varWord: begin uint := utils.readLongInt(st); vv := Word(uint); end;
3305 varLongWord: begin uint := utils.readLongInt(st); vv := LongWord(uint); end;
3306 else raise Exception.CreateFmt('cannot load uservar ''%s''', [uvname]);
3307 end;
3308 gTriggers[i].userVars.put(uvname, vv);
3309 end;
3310 end;
3311 end;
3312 end;
3315 end.