DEADSOFTWARE

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