DEADSOFTWARE

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