1 (* Copyright (C) Doom 2D: Forever Developers
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, version 3 of the License ONLY.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 {$INCLUDE ../shared/a_modes.inc}
16 {.$DEFINE GWEP_HITSCAN_TRACE_BITMAP_CHECKER}
22 SysUtils
, Classes
, mempool
,
23 g_textures
, g_basic
, g_phys
, xprofiler
;
33 Animation
: TAnimationState
;
37 procedure positionChanged (); //WARNING! call this after monster position was changed, or coldet will not work right!
42 Shots
: array of TShot
= nil;
43 LastShotID
: Integer = 0;
45 procedure g_Weapon_LoadData();
46 procedure g_Weapon_FreeData();
47 procedure g_Weapon_Init();
48 procedure g_Weapon_Free();
49 function g_Weapon_Hit(obj
: PObj
; d
: Integer; SpawnerUID
: Word; t
: Byte; HitCorpses
: Boolean = True): Byte;
50 function g_Weapon_HitUID(UID
: Word; d
: Integer; SpawnerUID
: Word; t
: Byte): Boolean;
51 function g_Weapon_CreateShot(I
: Integer; ShotType
: Byte; Spawner
, TargetUID
: Word; X
, Y
, XV
, YV
: Integer): LongWord;
53 procedure g_Weapon_gun(const x
, y
, xd
, yd
, v
, indmg
: Integer; SpawnerUID
: Word; CheckTrigger
: Boolean);
54 procedure g_Weapon_punch(x
, y
: Integer; d
, SpawnerUID
: Word);
55 function g_Weapon_chainsaw(x
, y
: Integer; d
, SpawnerUID
: Word): Integer;
56 procedure g_Weapon_rocket(x
, y
, xd
, yd
: Integer; SpawnerUID
: Word; WID
: Integer = -1; Silent
: Boolean = False; compat
: Boolean = true);
57 procedure g_Weapon_revf(x
, y
, xd
, yd
: Integer; SpawnerUID
, TargetUID
: Word; WID
: Integer = -1; Silent
: Boolean = False);
58 procedure g_Weapon_flame(x
, y
, xd
, yd
: Integer; SpawnerUID
: Word; WID
: Integer = -1; Silent
: Boolean = False; compat
: Boolean = true);
59 procedure g_Weapon_plasma(x
, y
, xd
, yd
: Integer; SpawnerUID
: Word; WID
: Integer = -1; Silent
: Boolean = False; compat
: Boolean = true);
60 procedure g_Weapon_ball1(x
, y
, xd
, yd
: Integer; SpawnerUID
: Word; WID
: Integer = -1; Silent
: Boolean = False; compat
: Boolean = true);
61 procedure g_Weapon_ball2(x
, y
, xd
, yd
: Integer; SpawnerUID
: Word; WID
: Integer = -1; Silent
: Boolean = False; compat
: Boolean = true);
62 procedure g_Weapon_ball7(x
, y
, xd
, yd
: Integer; SpawnerUID
: Word; WID
: Integer = -1; Silent
: Boolean = False; compat
: Boolean = true);
63 procedure g_Weapon_aplasma(x
, y
, xd
, yd
: Integer; SpawnerUID
: Word; WID
: Integer = -1; Silent
: Boolean = False; compat
: Boolean = true);
64 procedure g_Weapon_manfire(x
, y
, xd
, yd
: Integer; SpawnerUID
: Word; WID
: Integer = -1; Silent
: Boolean = False; compat
: Boolean = true);
65 procedure g_Weapon_bfgshot(x
, y
, xd
, yd
: Integer; SpawnerUID
: Word; WID
: Integer = -1; Silent
: Boolean = False; compat
: Boolean = true);
66 procedure g_Weapon_bfghit(x
, y
: Integer);
67 procedure g_Weapon_pistol(x
, y
, xd
, yd
: Integer; SpawnerUID
: Word; Silent
: Boolean = False);
68 procedure g_Weapon_mgun(x
, y
, xd
, yd
: Integer; SpawnerUID
: Word; Silent
: Boolean = False);
69 procedure g_Weapon_shotgun(x
, y
, xd
, yd
: Integer; SpawnerUID
: Word; Silent
: Boolean = False);
70 procedure g_Weapon_dshotgun(x
, y
, xd
, yd
: Integer; SpawnerUID
: Word; Silent
: Boolean = False);
72 function g_Weapon_Explode(X
, Y
: Integer; rad
: Integer; SpawnerUID
: Word): Boolean;
73 procedure g_Weapon_BFG9000(X
, Y
: Integer; SpawnerUID
: Word);
74 procedure g_Weapon_PreUpdate();
75 procedure g_Weapon_Update();
76 function g_Weapon_Danger(UID
: Word; X
, Y
: Integer; Width
, Height
: Word; Time
: Byte): Boolean;
77 procedure g_Weapon_DestroyShot(I
: Integer; X
, Y
: Integer; Loud
: Boolean = True);
79 procedure g_Weapon_SaveState (st
: TStream
);
80 procedure g_Weapon_LoadState (st
: TStream
);
82 procedure g_Weapon_AddDynLights();
91 WEAPON_ROCKETLAUNCHER
= 6;
94 WEAPON_SUPERPULEMET
= 9;
95 WEAPON_FLAMETHROWER
= 10;
96 WEAPON_ZOMBY_PISTOL
= 20;
99 WEAPON_CACO_FIRE
= 23;
100 WEAPON_BARON_FIRE
= 24;
101 WEAPON_MANCUB_FIRE
= 25;
102 WEAPON_SKEL_FIRE
= 26;
104 WP_FIRST
= WEAPON_KASTET
;
105 WP_LAST
= WEAPON_FLAMETHROWER
;
108 gwep_debug_fast_trace
: Boolean = true;
120 {$IFDEF ENABLE_CORPSES}
123 Math
, g_map
, g_player
, g_sound
, g_panel
,
124 g_console
, g_options
, g_game
,
125 g_triggers
, MAPDEF
, e_log
, g_monsters
, g_saveload
,
126 g_language
, g_netmsg
, g_grid
,
127 geom
, binheap
, hashtable
, utils
, xstreams
138 SHOT_ROCKETLAUNCHER_WIDTH
= 14;
139 SHOT_ROCKETLAUNCHER_HEIGHT
= 14;
141 SHOT_SKELFIRE_WIDTH
= 14;
142 SHOT_SKELFIRE_HEIGHT
= 14;
144 SHOT_PLASMA_WIDTH
= 16;
145 SHOT_PLASMA_HEIGHT
= 16;
148 SHOT_BFG_HEIGHT
= 32;
149 SHOT_BFG_DAMAGE
= 100;
150 SHOT_BFG_RADIUS
= 256;
152 SHOT_FLAME_WIDTH
= 4;
153 SHOT_FLAME_HEIGHT
= 4;
154 SHOT_FLAME_LIFETIME
= 180;
156 SHOT_SIGNATURE
= $544F4853; // 'SHOT'
159 PHitTime
= ^THitTime
;
163 plridx
: Integer; // if mon=nil
167 TBinHeapKeyHitTime
= class
169 class function less (const a
, b
: Integer): Boolean; inline;
172 // indicies in `wgunHitTime` array
173 TBinaryHeapHitTimes
= specialize TBinaryHeapBase
<Integer, TBinHeapKeyHitTime
>;
176 WaterMap
: array of array of DWORD
= nil;
177 //wgunMonHash: THashIntInt = nil;
178 wgunHitHeap
: TBinaryHeapHitTimes
= nil;
179 wgunHitTime
: array of THitTime
= nil;
180 wgunHitTimeUsed
: Integer = 0;
183 class function TBinHeapKeyHitTime
.less (const a
, b
: Integer): Boolean;
187 hta
:= @wgunHitTime
[a
];
188 htb
:= @wgunHitTime
[b
];
189 if (hta
.distSq
<> htb
.distSq
) then begin result
:= (hta
.distSq
< htb
.distSq
); exit
; end;
190 if (hta
.mon
<> nil) then
193 if (htb
.mon
= nil) then begin result
:= false; exit
; end; // players first
194 result
:= (hta
.mon
.UID
< htb
.mon
.UID
); // why not?
199 if (htb
.mon
<> nil) then begin result
:= true; exit
; end; // players first
200 result
:= (hta
.plridx
< htb
.plridx
); // why not?
205 procedure appendHitTimeMon (adistSq
: Integer; amon
: TMonster
; ax
, ay
: Integer);
207 if (wgunHitTimeUsed
= Length(wgunHitTime
)) then SetLength(wgunHitTime
, wgunHitTimeUsed
+128);
208 with wgunHitTime
[wgunHitTimeUsed
] do
216 wgunHitHeap
.insert(wgunHitTimeUsed
);
217 Inc(wgunHitTimeUsed
);
221 procedure appendHitTimePlr (adistSq
: Integer; aplridx
: Integer; ax
, ay
: Integer);
223 if (wgunHitTimeUsed
= Length(wgunHitTime
)) then SetLength(wgunHitTime
, wgunHitTimeUsed
+128);
224 with wgunHitTime
[wgunHitTimeUsed
] do
232 wgunHitHeap
.insert(wgunHitTimeUsed
);
233 Inc(wgunHitTimeUsed
);
237 function FindShot(): DWORD
;
242 for i
:= 0 to High(Shots
) do
243 if Shots
[i
].ShotType
= 0 then
246 LastShotID
:= Result
;
252 SetLength(Shots
, 128);
257 Result
:= High(Shots
) + 1;
258 SetLength(Shots
, Length(Shots
) + 128);
260 LastShotID
:= Result
;
263 procedure CreateWaterMap();
265 WaterArray
: Array of TWaterPanel
;
272 SetLength(WaterArray
, Length(gWater
));
274 for a
:= 0 to High(gWater
) do
276 WaterArray
[a
].X
:= gWater
[a
].X
;
277 WaterArray
[a
].Y
:= gWater
[a
].Y
;
278 WaterArray
[a
].Width
:= gWater
[a
].Width
;
279 WaterArray
[a
].Height
:= gWater
[a
].Height
;
280 WaterArray
[a
].Active
:= True;
283 g_Game_SetLoadingText(_lc
[I_LOAD_WATER_MAP
], High(WaterArray
), False);
285 for a
:= 0 to High(WaterArray
) do
286 if WaterArray
[a
].Active
then
288 WaterArray
[a
].Active
:= False;
289 m
:= Length(WaterMap
);
290 SetLength(WaterMap
, m
+1);
291 SetLength(WaterMap
[m
], 1);
298 for b
:= 0 to High(WaterArray
) do
299 if WaterArray
[b
].Active
then
300 for c
:= 0 to High(WaterMap
[m
]) do
301 if g_CollideAround(WaterArray
[b
].X
,
304 WaterArray
[b
].Height
,
305 WaterArray
[WaterMap
[m
][c
]].X
,
306 WaterArray
[WaterMap
[m
][c
]].Y
,
307 WaterArray
[WaterMap
[m
][c
]].Width
,
308 WaterArray
[WaterMap
[m
][c
]].Height
) then
310 WaterArray
[b
].Active
:= False;
311 SetLength(WaterMap
[m
],
312 Length(WaterMap
[m
])+1);
313 WaterMap
[m
][High(WaterMap
[m
])] := b
;
319 g_Game_StepLoading();
327 chkTrap_pl
: array [0..256] of Integer;
328 chkTrap_mn
: array [0..65535] of TMonster
;
330 procedure CheckTrap(ID
: DWORD
; dm
: Integer; t
: Byte);
332 //a, b, c, d, i1, i2: Integer;
333 //chkTrap_pl, chkTrap_mn: WArray;
334 plaCount
: Integer = 0;
335 mnaCount
: Integer = 0;
339 function monsWaterCheck (mon: TMonster): Boolean;
341 result := false; // don't stop
342 if mon.alive and mon.Collide(gWater[WaterMap[a][c]]) and (not InWArray(monidx, chkTrap_mn)) and (i2 < 1023) then //FIXME
345 chkTrap_mn[i2] := monidx;
350 function monsWaterCheck (mon
: TMonster
): Boolean;
352 result
:= false; // don't stop
353 if (mon
.trapCheckFrameId
<> frameId
) then
355 mon
.trapCheckFrameId
:= frameId
;
356 chkTrap_mn
[mnaCount
] := mon
;
362 a
, b
, c
, d
, f
: Integer;
365 if (gWater
= nil) or (WaterMap
= nil) then Exit
;
367 frameId
:= g_Mons_getNewTrapFrameId();
372 //SetLength(chkTrap_pl, 1024);
373 //SetLength(chkTrap_mn, 1024);
374 //for d := 0 to 1023 do chkTrap_pl[d] := $FFFF;
375 //for d := 0 to 1023 do chkTrap_mn[d] := $FFFF;
377 for a
:= 0 to High(WaterMap
) do
379 for b
:= 0 to High(WaterMap
[a
]) do
381 pan
:= gWater
[WaterMap
[a
][b
]];
382 if not g_Obj_Collide(pan
.X
, pan
.Y
, pan
.Width
, pan
.Height
, @Shots
[ID
].Obj
) then continue
;
384 for c
:= 0 to High(WaterMap
[a
]) do
386 pan
:= gWater
[WaterMap
[a
][c
]];
387 for d
:= 0 to High(gPlayers
) do
389 if (gPlayers
[d
] <> nil) and (gPlayers
[d
].alive
) then
391 if gPlayers
[d
].Collide(pan
) then
394 while (f
< plaCount
) and (chkTrap_pl
[f
] <> d
) do Inc(f
);
395 if (f
= plaCount
) then
397 chkTrap_pl
[plaCount
] := d
;
399 if (plaCount
= Length(chkTrap_pl
)) then break
;
405 //g_Mons_ForEach(monsWaterCheck);
406 g_Mons_ForEachAliveAt(pan
.X
, pan
.Y
, pan
.Width
, pan
.Height
, monsWaterCheck
);
409 for f
:= 0 to plaCount
-1 do gPlayers
[chkTrap_pl
[f
]].Damage(dm
, Shots
[ID
].SpawnerUID
, 0, 0, t
);
410 for f
:= 0 to mnaCount
-1 do chkTrap_mn
[f
].Damage(dm
, 0, 0, Shots
[ID
].SpawnerUID
, t
);
418 function HitMonster(m
: TMonster
; d
: Integer; vx
, vy
: Integer; SpawnerUID
: Word; t
: Byte): Boolean;
425 tt
:= g_GetUIDType(SpawnerUID
);
426 if tt
= UID_MONSTER
then
428 mon
:= g_Monsters_ByUID(SpawnerUID
);
430 mt
:= g_Monsters_ByUID(SpawnerUID
).MonsterType
437 if m
= nil then Exit
;
438 if m
.UID
= SpawnerUID
then
440 // Ñàì ñåáÿ ìîæåò ðàíèòü òîëüêî ðàêåòîé è òîêîì:
441 if (t
<> HIT_ROCKET
) and (t
<> HIT_ELECTRO
) then
443 // Êèáåð äåìîí è áî÷êà âîîáùå íå ìîãóò ñåáÿ ðàíèòü:
444 if (m
.MonsterType
= MONSTER_CYBER
) or
445 (m
.MonsterType
= MONSTER_BARREL
) then
452 if tt
= UID_MONSTER
then
454 // Lost_Soul íå ìîæåò ðàíèòü Pain_Elemental'à:
455 if (mt
= MONSTER_SOUL
) and (m
.MonsterType
= MONSTER_PAIN
) then
458 // Îáà ìîíñòðà îäíîãî âèäà:
459 if mt
= m
.MonsterType
then
461 MONSTER_IMP
, MONSTER_DEMON
, MONSTER_BARON
, MONSTER_KNIGHT
, MONSTER_CACO
,
462 MONSTER_SOUL
, MONSTER_MANCUB
, MONSTER_SKEL
, MONSTER_FISH
:
463 Exit
; // Ýòè íå áüþò ñâîèõ
467 if g_Game_IsServer
then
469 if (t
<> HIT_FLAME
) or (m
.FFireTime
= 0) or (vx
<> 0) or (vy
<> 0) then
470 Result
:= m
.Damage(d
, vx
, vy
, SpawnerUID
, t
)
472 Result
:= (gLMSRespawn
= LMS_RESPAWN_NONE
); // don't hit monsters when it's warmup time
473 if t
= HIT_FLAME
then
474 m
.CatchFire(SpawnerUID
);
477 Result
:= (gLMSRespawn
= LMS_RESPAWN_NONE
); // don't hit monsters when it's warmup time
481 function HitPlayer (p
: TPlayer
; d
: Integer; vx
, vy
: Integer; SpawnerUID
: Word; t
: Byte): Boolean;
485 // Ñàì ñåáÿ ìîæåò ðàíèòü òîëüêî ðàêåòîé è òîêîì
486 if (p
.UID
= SpawnerUID
) and (t
<> HIT_ROCKET
) and (t
<> HIT_ELECTRO
) then exit
;
488 if g_Game_IsServer
then
490 if (t
<> HIT_FLAME
) or (p
.FFireTime
= 0) or (vx
<> 0) or (vy
<> 0) then p
.Damage(d
, SpawnerUID
, vx
, vy
, t
);
491 if (t
= HIT_FLAME
) then p
.CatchFire(SpawnerUID
);
498 procedure g_Weapon_BFG9000(X
, Y
: Integer; SpawnerUID
: Word);
500 function monsCheck (mon
: TMonster
): Boolean;
502 result
:= false; // don't stop
503 if (mon
.alive
) and (mon
.UID
<> SpawnerUID
) then
507 if (g_PatchLength(X
, Y
, Obj
.X
+Obj
.Rect
.X
+(Obj
.Rect
.Width
div 2),
508 Obj
.Y
+Obj
.Rect
.Y
+(Obj
.Rect
.Height
div 2)) <= SHOT_BFG_RADIUS
) and
509 g_TraceVector(X
, Y
, Obj
.X
+Obj
.Rect
.X
+(Obj
.Rect
.Width
div 2),
510 Obj
.Y
+Obj
.Rect
.Y
+(Obj
.Rect
.Height
div 2)) then
512 if HitMonster(mon
, 50, 0, 0, SpawnerUID
, HIT_SOME
) then mon
.BFGHit();
524 //g_Sound_PlayEx('SOUND_WEAPON_EXPLODEBFG', 255);
526 {$IFDEF ENABLE_CORPSES}
528 if gAdvCorpses
and (h
<> -1) then
532 if (gCorpses
[i
] <> nil) and (gCorpses
[i
].State
<> CORPSE_STATE_REMOVEME
) then
536 if (g_PatchLength(X
, Y
, Obj
.X
+Obj
.Rect
.X
+(Obj
.Rect
.Width
div 2),
537 Obj
.Y
+Obj
.Rect
.Y
+(Obj
.Rect
.Height
div 2)) <= SHOT_BFG_RADIUS
) and
538 g_TraceVector(X
, Y
, Obj
.X
+Obj
.Rect
.X
+(Obj
.Rect
.Width
div 2),
539 Obj
.Y
+Obj
.Rect
.Y
+(Obj
.Rect
.Height
div 2)) then
541 Damage(50, SpawnerUID
, 0, 0);
542 g_Weapon_BFGHit(Obj
.X
+Obj
.Rect
.X
+(Obj
.Rect
.Width
div 2), Obj
.Y
+Obj
.Rect
.Y
+(Obj
.Rect
.Height
div 2));
551 pl
:= g_Player_Get(SpawnerUID
);
559 if (gPlayers
[i
] <> nil) and (gPlayers
[i
].alive
) and (gPlayers
[i
].UID
<> SpawnerUID
) then
561 if (g_PatchLength(X
, Y
, GameX
+PLAYER_RECT
.X
+(PLAYER_RECT
.Width
div 2),
562 GameY
+PLAYER_RECT
.Y
+(PLAYER_RECT
.Height
div 2)) <= SHOT_BFG_RADIUS
) and
563 g_TraceVector(X
, Y
, GameX
+PLAYER_RECT
.X
+(PLAYER_RECT
.Width
div 2),
564 GameY
+PLAYER_RECT
.Y
+(PLAYER_RECT
.Height
div 2)) then
566 if (st
= TEAM_NONE
) or (st
<> gPlayers
[i
].Team
) then
567 b
:= HitPlayer(gPlayers
[i
], 50, 0, 0, SpawnerUID
, HIT_SOME
)
569 b
:= HitPlayer(gPlayers
[i
], 25, 0, 0, SpawnerUID
, HIT_SOME
);
571 gPlayers
[i
].BFGHit();
575 g_Mons_ForEachAlive(monsCheck
);
578 function g_Weapon_CreateShot(I
: Integer; ShotType
: Byte; Spawner
, TargetUID
: Word; X
, Y
, XV
, YV
: Integer): LongWord;
583 find_id
:= FindShot()
587 if Integer(find_id
) >= High(Shots
) then
588 SetLength(Shots
, find_id
+ 64)
592 WEAPON_ROCKETLAUNCHER
:
594 with Shots
[find_id
] do
598 Obj
.Rect
.Width
:= SHOT_ROCKETLAUNCHER_WIDTH
;
599 Obj
.Rect
.Height
:= SHOT_ROCKETLAUNCHER_HEIGHT
;
603 ShotType
:= WEAPON_ROCKETLAUNCHER
;
609 with Shots
[find_id
] do
613 Obj
.Rect
.Width
:= SHOT_PLASMA_WIDTH
;
614 Obj
.Rect
.Height
:= SHOT_PLASMA_HEIGHT
;
617 ShotType
:= WEAPON_PLASMA
;
618 Animation
:= TAnimationState
.Create(True, 5, 2); // !!! put values into table
624 with Shots
[find_id
] do
628 Obj
.Rect
.Width
:= SHOT_BFG_WIDTH
;
629 Obj
.Rect
.Height
:= SHOT_BFG_HEIGHT
;
632 ShotType
:= WEAPON_BFG
;
633 Animation
:= TAnimationState
.Create(True, 6, 2); // !!! put values into table
639 with Shots
[find_id
] do
643 Obj
.Rect
.Width
:= SHOT_FLAME_WIDTH
;
644 Obj
.Rect
.Height
:= SHOT_FLAME_HEIGHT
;
647 ShotType
:= WEAPON_FLAMETHROWER
;
648 // Animation := TAnimationState.Create(True, 6, 0); // drawed as gfx
654 with Shots
[find_id
] do
658 Obj
.Rect
.Width
:= 16;
659 Obj
.Rect
.Height
:= 16;
662 ShotType
:= WEAPON_IMP_FIRE
;
663 Animation
:= TAnimationState
.Create(True, 4, 2); // !!! put values into table
669 with Shots
[find_id
] do
673 Obj
.Rect
.Width
:= 16;
674 Obj
.Rect
.Height
:= 16;
677 ShotType
:= WEAPON_CACO_FIRE
;
678 Animation
:= TAnimationState
.Create(True, 4, 2); // !!! put values into table
684 with Shots
[find_id
] do
688 Obj
.Rect
.Width
:= 32;
689 Obj
.Rect
.Height
:= 32;
692 ShotType
:= WEAPON_MANCUB_FIRE
;
693 Animation
:= TAnimationState
.Create(True, 4, 2); // !!! put values into table
699 with Shots
[find_id
] do
703 Obj
.Rect
.Width
:= 16;
704 Obj
.Rect
.Height
:= 16;
707 ShotType
:= WEAPON_BARON_FIRE
;
708 Animation
:= TAnimationState
.Create(True, 4, 2); // !!! put values into table
714 with Shots
[find_id
] do
718 Obj
.Rect
.Width
:= 16;
719 Obj
.Rect
.Height
:= 16;
722 ShotType
:= WEAPON_BSP_FIRE
;
723 Animation
:= TAnimationState
.Create(True, 4, 2); // !!! put values into table
729 with Shots
[find_id
] do
733 Obj
.Rect
.Width
:= SHOT_SKELFIRE_WIDTH
;
734 Obj
.Rect
.Height
:= SHOT_SKELFIRE_HEIGHT
;
737 ShotType
:= WEAPON_SKEL_FIRE
;
739 Animation
:= TAnimationState
.Create(True, 5, 2); // !!! put values into table
744 Shots
[find_id
].Obj
.oldX
:= X
;
745 Shots
[find_id
].Obj
.oldY
:= Y
;
746 Shots
[find_id
].Obj
.X
:= X
;
747 Shots
[find_id
].Obj
.Y
:= Y
;
748 Shots
[find_id
].Obj
.Vel
.X
:= XV
;
749 Shots
[find_id
].Obj
.Vel
.Y
:= YV
;
750 Shots
[find_id
].Obj
.Accel
.X
:= 0;
751 Shots
[find_id
].Obj
.Accel
.Y
:= 0;
752 Shots
[find_id
].SpawnerUID
:= Spawner
;
753 if (ShotType
= WEAPON_FLAMETHROWER
) and (XV
= 0) and (YV
= 0) then
754 Shots
[find_id
].Stopped
:= 255
756 Shots
[find_id
].Stopped
:= 0;
760 procedure throw(i
, x
, y
, xd
, yd
, s
: Integer);
767 a
:= Max(Abs(xd
), Abs(yd
));
771 Shots
[i
].Obj
.oldX
:= x
;
772 Shots
[i
].Obj
.oldY
:= y
;
775 Shots
[i
].Obj
.Vel
.X
:= (xd
*s
) div a
;
776 Shots
[i
].Obj
.Vel
.Y
:= (yd
*s
) div a
;
777 Shots
[i
].Obj
.Accel
.X
:= 0;
778 Shots
[i
].Obj
.Accel
.Y
:= 0;
779 Shots
[i
].Stopped
:= 0;
780 if Shots
[i
].ShotType
in [WEAPON_ROCKETLAUNCHER
, WEAPON_BFG
] then
781 Shots
[i
].Timeout
:= 900 // ~25 sec
784 if Shots
[i
].ShotType
= WEAPON_FLAMETHROWER
then
785 Shots
[i
].Timeout
:= SHOT_FLAME_LIFETIME
787 Shots
[i
].Timeout
:= 550; // ~15 sec
791 function g_Weapon_Hit(obj
: PObj
; d
: Integer; SpawnerUID
: Word; t
: Byte; HitCorpses
: Boolean = True): Byte;
792 {$IFDEF ENABLE_CORPSES}
797 function PlayerHit(Team
: Byte = 0): Boolean;
808 if (gPlayers
[i
] <> nil) and gPlayers
[i
].alive
and g_Obj_Collide(obj
, @gPlayers
[i
].Obj
) then
811 if (Team
> 0) and (g_GetUIDType(SpawnerUID
) = UID_PLAYER
) then
813 p
:= g_Player_Get(SpawnerUID
);
815 ChkTeam
:= (p
.Team
= gPlayers
[i
].Team
) xor (Team
= 2);
818 if HitPlayer(gPlayers
[i
], d
, obj
^.Vel
.X
, obj
^.Vel
.Y
, SpawnerUID
, t
) then
820 if t
<> HIT_FLAME
then
821 gPlayers
[i
].Push((obj
^.Vel
.X
+obj
^.Accel
.X
)*IfThen(t
= HIT_BFG
, 8, 1) div 4,
822 (obj
^.Vel
.Y
+obj
^.Accel
.Y
)*IfThen(t
= HIT_BFG
, 8, 1) div 4);
824 g_Game_DelayEvent(DE_BFGHIT
, 1000, SpawnerUID
);
832 function monsCheckHit (monidx: Integer; mon: TMonster): Boolean;
834 result := false; // don't stop
835 if mon.alive and g_Obj_Collide(obj, @mon.Obj) then
837 if HitMonster(mon, d, obj^.Vel.X, obj^.Vel.Y, SpawnerUID, t) then
839 if (t <> HIT_FLAME) then
841 mon.Push((obj^.Vel.X+obj^.Accel.X)*IfThen(t = HIT_BFG, 8, 1) div 4,
842 (obj^.Vel.Y+obj^.Accel.Y)*IfThen(t = HIT_BFG, 8, 1) div 4);
850 function monsCheckHit (mon
: TMonster
): Boolean;
852 result
:= false; // don't stop
853 if HitMonster(mon
, d
, obj
.Vel
.X
, obj
.Vel
.Y
, SpawnerUID
, t
) then
855 if (t
<> HIT_FLAME
) then
857 mon
.Push((obj
.Vel
.X
+obj
.Accel
.X
)*IfThen(t
= HIT_BFG
, 8, 1) div 4,
858 (obj
.Vel
.Y
+obj
.Accel
.Y
)*IfThen(t
= HIT_BFG
, 8, 1) div 4);
864 function MonsterHit(): Boolean;
866 //result := g_Mons_ForEach(monsCheckHit);
867 //FIXME: accelerate this!
868 result
:= g_Mons_ForEachAliveAt(obj
.X
+obj
.Rect
.X
, obj
.Y
+obj
.Rect
.Y
, obj
.Rect
.Width
, obj
.Rect
.Height
, monsCheckHit
);
874 {$IFDEF ENABLE_CORPSES}
878 if gAdvCorpses
and (h
<> -1) then
882 if (gCorpses
[i
] <> nil) and (gCorpses
[i
].State
<> CORPSE_STATE_REMOVEME
) and
883 g_Obj_Collide(obj
, @gCorpses
[i
].Obj
) then
886 gCorpses
[i
].Damage(d
, SpawnerUID
, (obj
^.Vel
.X
+obj
^.Accel
.X
) div 4,
887 (obj
^.Vel
.Y
+obj
^.Accel
.Y
) div 4);
895 case gGameSettings
.GameMode
of
899 // Ñíà÷àëà áü¸ì ìîíñòðîâ, åñëè åñòü, ïîòîì ïûòàåìñÿ áèòü èãðîêîâ
906 // È â êîíöå èãðîêîâ, íî òîëüêî åñëè ïîëîæåíî
907 // (èëè ñíàðÿä îò ìîíñòðà, èëè friendlyfire, èëè friendly_hit_projectile)
908 if (g_GetUIDType(SpawnerUID
) <> UID_PLAYER
) or
909 LongBool(gGameSettings
.Options
and (GAME_OPTION_TEAMDAMAGE
or GAME_OPTION_TEAMHITPROJECTILE
)) then
922 // Ñíà÷àëà áü¸ì èãðîêîâ, åñëè åñòü, ïîòîì ïûòàåìñÿ áèòü ìîíñòðîâ
939 // Ñíà÷àëà áü¸ì èãðîêîâ êîìàíäû ñîïåðíèêà
953 // È â êîíöå ñâîèõ èãðîêîâ, íî òîëüêî åñëè ïîëîæåíî
954 // (èëè friendlyfire, èëè friendly_hit_projectile)
955 if LongBool(gGameSettings
.Options
and (GAME_OPTION_TEAMDAMAGE
or GAME_OPTION_TEAMHITPROJECTILE
)) then
968 function g_Weapon_HitUID(UID
: Word; d
: Integer; SpawnerUID
: Word; t
: Byte): Boolean;
972 case g_GetUIDType(UID
) of
973 UID_PLAYER
: Result
:= HitPlayer(g_Player_Get(UID
), d
, 0, 0, SpawnerUID
, t
);
974 UID_MONSTER
: Result
:= HitMonster(g_Monsters_ByUID(UID
), d
, 0, 0, SpawnerUID
, t
);
979 function g_Weapon_Explode(X
, Y
: Integer; rad
: Integer; SpawnerUID
: Word): Boolean;
981 r
: Integer; // squared radius
983 function monsExCheck (mon
: TMonster
): Boolean;
987 result
:= false; // don't stop
989 dx
:= mon
.Obj
.X
+mon
.Obj
.Rect
.X
+(mon
.Obj
.Rect
.Width
div 2)-X
;
990 dy
:= mon
.Obj
.Y
+mon
.Obj
.Rect
.Y
+(mon
.Obj
.Rect
.Height
div 2)-Y
;
992 if dx
> 1000 then dx
:= 1000;
993 if dy
> 1000 then dy
:= 1000;
995 if (dx
*dx
+dy
*dy
< r
) then
997 //m := PointToRect(X, Y, Obj.X+Obj.Rect.X, Obj.Y+Obj.Rect.Y, Obj.Rect.Width, Obj.Rect.Height);
998 //e_WriteLog(Format('explo monster #%d: x=%d; y=%d; rad=%d; dx=%d; dy=%d', [monidx, X, Y, rad, dx, dy]), MSG_NOTIFY);
1000 mm
:= Max(abs(dx
), abs(dy
));
1001 if mm
= 0 then mm
:= 1;
1005 HitMonster(mon
, ((mon
.Obj
.Rect
.Width
div 4)*10*(rad
-mm
)) div rad
, 0, 0, SpawnerUID
, HIT_ROCKET
);
1008 mon
.Push((dx
*7) div mm
, (dy
*7) div mm
);
1013 var i
, h
, dx
, dy
, mm
: Integer;
1014 {$IFDEF ENABLE_GIBS}
1015 var _angle
: SmallInt;
1017 {$IF DEFINED(ENABLE_GIBS) OR DEFINED(ENABLE_CORPSES)}
1023 g_Triggers_PressC(X
, Y
, rad
, SpawnerUID
, ACTIVATE_SHOT
);
1027 h
:= High(gPlayers
);
1031 if (gPlayers
[i
] <> nil) and gPlayers
[i
].alive
then
1034 dx
:= Obj
.X
+Obj
.Rect
.X
+(Obj
.Rect
.Width
div 2)-X
;
1035 dy
:= Obj
.Y
+Obj
.Rect
.Y
+(Obj
.Rect
.Height
div 2)-Y
;
1037 if dx
> 1000 then dx
:= 1000;
1038 if dy
> 1000 then dy
:= 1000;
1040 if dx
*dx
+dy
*dy
< r
then
1042 //m := PointToRect(X, Y, GameX+PLAYER_RECT.X, GameY+PLAYER_RECT.Y,
1043 // PLAYER_RECT.Width, PLAYER_RECT.Height);
1045 mm
:= Max(abs(dx
), abs(dy
));
1046 if mm
= 0 then mm
:= 1;
1048 HitPlayer(gPlayers
[i
], (100*(rad
-mm
)) div rad
, (dx
*10) div mm
, (dy
*10) div mm
, SpawnerUID
, HIT_ROCKET
);
1049 gPlayers
[i
].Push((dx
*7) div mm
, (dy
*7) div mm
);
1053 //g_Mons_ForEach(monsExCheck);
1054 g_Mons_ForEachAt(X
-(rad
+32), Y
-(rad
+32), (rad
+32)*2, (rad
+32)*2, monsExCheck
);
1056 {$IFDEF ENABLE_CORPSES}
1057 h
:= High(gCorpses
);
1058 if gAdvCorpses
and (h
<> -1) then
1062 if (gCorpses
[i
] <> nil) and (gCorpses
[i
].State
<> CORPSE_STATE_REMOVEME
) then
1066 dx
:= Obj
.X
+Obj
.Rect
.X
+(Obj
.Rect
.Width
div 2)-X
;
1067 dy
:= Obj
.Y
+Obj
.Rect
.Y
+(Obj
.Rect
.Height
div 2)-Y
;
1068 if dx
> 1000 then dx
:= 1000;
1069 if dy
> 1000 then dy
:= 1000;
1070 if dx
*dx
+dy
*dy
< r
then
1072 m
:= PointToRect(X
, Y
, Obj
.X
+Obj
.Rect
.X
, Obj
.Y
+Obj
.Rect
.Y
, Obj
.Rect
.Width
, Obj
.Rect
.Height
);
1073 mm
:= Max(abs(dx
), abs(dy
));
1076 Damage(Round(100*(rad
-m
)/rad
), SpawnerUID
, (dx
*10) div mm
, (dy
*10) div mm
);
1084 {$IFDEF ENABLE_GIBS}
1086 if gAdvGibs
and (h
<> -1) then
1088 if gGibs
[i
].alive
then
1091 dx
:= Obj
.X
+Obj
.Rect
.X
+(Obj
.Rect
.Width
div 2)-X
;
1092 dy
:= Obj
.Y
+Obj
.Rect
.Y
+(Obj
.Rect
.Height
div 2)-Y
;
1093 if dx
> 1000 then dx
:= 1000;
1094 if dy
> 1000 then dy
:= 1000;
1095 if dx
*dx
+dy
*dy
< r
then
1097 m
:= PointToRect(X
, Y
, Obj
.X
+Obj
.Rect
.X
, Obj
.Y
+Obj
.Rect
.Y
,
1098 Obj
.Rect
.Width
, Obj
.Rect
.Height
);
1099 _angle
:= GetAngle(Obj
.X
+Obj
.Rect
.X
+(Obj
.Rect
.Width
div 2),
1100 Obj
.Y
+Obj
.Rect
.Y
+(Obj
.Rect
.Height
div 2), X
, Y
);
1101 g_Obj_PushA(@Obj
, Round(15*(rad
-m
)/rad
), _angle
);
1102 positionChanged(); // this updates spatial accelerators
1108 procedure g_Weapon_Init();
1113 procedure g_Weapon_Free();
1117 if Shots
<> nil then
1119 for i
:= 0 to High(Shots
) do
1120 if Shots
[i
].ShotType
<> 0 then
1121 Shots
[i
].Animation
.Free();
1129 procedure g_Weapon_LoadData();
1131 e_WriteLog('Loading weapons data...', TMsgType
.Notify
);
1133 g_Sound_CreateWADEx('SOUND_WEAPON_HITPUNCH', GameWAD
+':SOUNDS\HITPUNCH');
1134 g_Sound_CreateWADEx('SOUND_WEAPON_MISSPUNCH', GameWAD
+':SOUNDS\MISSPUNCH');
1135 g_Sound_CreateWADEx('SOUND_WEAPON_HITBERSERK', GameWAD
+':SOUNDS\HITBERSERK');
1136 g_Sound_CreateWADEx('SOUND_WEAPON_MISSBERSERK', GameWAD
+':SOUNDS\MISSBERSERK');
1137 g_Sound_CreateWADEx('SOUND_WEAPON_SELECTSAW', GameWAD
+':SOUNDS\SELECTSAW');
1138 g_Sound_CreateWADEx('SOUND_WEAPON_IDLESAW', GameWAD
+':SOUNDS\IDLESAW');
1139 g_Sound_CreateWADEx('SOUND_WEAPON_HITSAW', GameWAD
+':SOUNDS\HITSAW');
1140 g_Sound_CreateWADEx('SOUND_WEAPON_FIRESHOTGUN2', GameWAD
+':SOUNDS\FIRESHOTGUN2');
1141 g_Sound_CreateWADEx('SOUND_WEAPON_FIRESHOTGUN', GameWAD
+':SOUNDS\FIRESHOTGUN');
1142 g_Sound_CreateWADEx('SOUND_WEAPON_FIRESAW', GameWAD
+':SOUNDS\FIRESAW');
1143 g_Sound_CreateWADEx('SOUND_WEAPON_FIREROCKET', GameWAD
+':SOUNDS\FIREROCKET');
1144 g_Sound_CreateWADEx('SOUND_WEAPON_FIREPLASMA', GameWAD
+':SOUNDS\FIREPLASMA');
1145 g_Sound_CreateWADEx('SOUND_WEAPON_FIREPISTOL', GameWAD
+':SOUNDS\FIREPISTOL');
1146 g_Sound_CreateWADEx('SOUND_WEAPON_FIRECGUN', GameWAD
+':SOUNDS\FIRECGUN');
1147 g_Sound_CreateWADEx('SOUND_WEAPON_FIREBFG', GameWAD
+':SOUNDS\FIREBFG');
1148 g_Sound_CreateWADEx('SOUND_FIRE', GameWAD
+':SOUNDS\FIRE');
1149 g_Sound_CreateWADEx('SOUND_IGNITE', GameWAD
+':SOUNDS\IGNITE');
1150 g_Sound_CreateWADEx('SOUND_WEAPON_STARTFIREBFG', GameWAD
+':SOUNDS\STARTFIREBFG');
1151 g_Sound_CreateWADEx('SOUND_WEAPON_EXPLODEROCKET', GameWAD
+':SOUNDS\EXPLODEROCKET');
1152 g_Sound_CreateWADEx('SOUND_WEAPON_EXPLODEBFG', GameWAD
+':SOUNDS\EXPLODEBFG');
1153 g_Sound_CreateWADEx('SOUND_WEAPON_BFGWATER', GameWAD
+':SOUNDS\BFGWATER');
1154 g_Sound_CreateWADEx('SOUND_WEAPON_EXPLODEPLASMA', GameWAD
+':SOUNDS\EXPLODEPLASMA');
1155 g_Sound_CreateWADEx('SOUND_WEAPON_PLASMAWATER', GameWAD
+':SOUNDS\PLASMAWATER');
1156 g_Sound_CreateWADEx('SOUND_WEAPON_FIREBALL', GameWAD
+':SOUNDS\FIREBALL');
1157 g_Sound_CreateWADEx('SOUND_WEAPON_EXPLODEBALL', GameWAD
+':SOUNDS\EXPLODEBALL');
1158 g_Sound_CreateWADEx('SOUND_WEAPON_FIREREV', GameWAD
+':SOUNDS\FIREREV');
1159 g_Sound_CreateWADEx('SOUND_WEAPON_FLAMEON', GameWAD
+':SOUNDS\STARTFLM');
1160 g_Sound_CreateWADEx('SOUND_WEAPON_FLAMEOFF', GameWAD
+':SOUNDS\STOPFLM');
1161 g_Sound_CreateWADEx('SOUND_WEAPON_FLAMEWORK', GameWAD
+':SOUNDS\WORKFLM');
1162 g_Sound_CreateWADEx('SOUND_PLAYER_JETFLY', GameWAD
+':SOUNDS\WORKJETPACK');
1163 g_Sound_CreateWADEx('SOUND_PLAYER_JETON', GameWAD
+':SOUNDS\STARTJETPACK');
1164 g_Sound_CreateWADEx('SOUND_PLAYER_JETOFF', GameWAD
+':SOUNDS\STOPJETPACK');
1165 g_Sound_CreateWADEx('SOUND_PLAYER_CASING1', GameWAD
+':SOUNDS\CASING1');
1166 g_Sound_CreateWADEx('SOUND_PLAYER_CASING2', GameWAD
+':SOUNDS\CASING2');
1167 g_Sound_CreateWADEx('SOUND_PLAYER_SHELL1', GameWAD
+':SOUNDS\SHELL1');
1168 g_Sound_CreateWADEx('SOUND_PLAYER_SHELL2', GameWAD
+':SOUNDS\SHELL2');
1170 //wgunMonHash := hashNewIntInt();
1171 wgunHitHeap
:= TBinaryHeapHitTimes
.Create();
1174 procedure g_Weapon_FreeData();
1176 e_WriteLog('Releasing weapons data...', TMsgType
.Notify
);
1178 g_Sound_Delete('SOUND_WEAPON_HITPUNCH');
1179 g_Sound_Delete('SOUND_WEAPON_MISSPUNCH');
1180 g_Sound_Delete('SOUND_WEAPON_HITBERSERK');
1181 g_Sound_Delete('SOUND_WEAPON_MISSBERSERK');
1182 g_Sound_Delete('SOUND_WEAPON_SELECTSAW');
1183 g_Sound_Delete('SOUND_WEAPON_IDLESAW');
1184 g_Sound_Delete('SOUND_WEAPON_HITSAW');
1185 g_Sound_Delete('SOUND_WEAPON_FIRESHOTGUN2');
1186 g_Sound_Delete('SOUND_WEAPON_FIRESHOTGUN');
1187 g_Sound_Delete('SOUND_WEAPON_FIRESAW');
1188 g_Sound_Delete('SOUND_WEAPON_FIREROCKET');
1189 g_Sound_Delete('SOUND_WEAPON_FIREPLASMA');
1190 g_Sound_Delete('SOUND_WEAPON_FIREPISTOL');
1191 g_Sound_Delete('SOUND_WEAPON_FIRECGUN');
1192 g_Sound_Delete('SOUND_WEAPON_FIREBFG');
1193 g_Sound_Delete('SOUND_FIRE');
1194 g_Sound_Delete('SOUND_IGNITE');
1195 g_Sound_Delete('SOUND_WEAPON_STARTFIREBFG');
1196 g_Sound_Delete('SOUND_WEAPON_EXPLODEROCKET');
1197 g_Sound_Delete('SOUND_WEAPON_EXPLODEBFG');
1198 g_Sound_Delete('SOUND_WEAPON_BFGWATER');
1199 g_Sound_Delete('SOUND_WEAPON_EXPLODEPLASMA');
1200 g_Sound_Delete('SOUND_WEAPON_PLASMAWATER');
1201 g_Sound_Delete('SOUND_WEAPON_FIREBALL');
1202 g_Sound_Delete('SOUND_WEAPON_EXPLODEBALL');
1203 g_Sound_Delete('SOUND_WEAPON_FIREREV');
1204 g_Sound_Delete('SOUND_WEAPON_FLAMEON');
1205 g_Sound_Delete('SOUND_WEAPON_FLAMEOFF');
1206 g_Sound_Delete('SOUND_WEAPON_FLAMEWORK');
1207 g_Sound_Delete('SOUND_PLAYER_JETFLY');
1208 g_Sound_Delete('SOUND_PLAYER_JETON');
1209 g_Sound_Delete('SOUND_PLAYER_JETOFF');
1210 g_Sound_Delete('SOUND_PLAYER_CASING1');
1211 g_Sound_Delete('SOUND_PLAYER_CASING2');
1212 g_Sound_Delete('SOUND_PLAYER_SHELL1');
1213 g_Sound_Delete('SOUND_PLAYER_SHELL2');
1217 function GunHitPlayer (X
, Y
: Integer; vx
, vy
: Integer; dmg
: Integer; SpawnerUID
: Word; AllowPush
: Boolean): Boolean;
1222 for i
:= 0 to High(gPlayers
) do
1224 if (gPlayers
[i
] <> nil) and gPlayers
[i
].alive
and gPlayers
[i
].Collide(X
, Y
) then
1226 if HitPlayer(gPlayers
[i
], dmg
, vx
*10, vy
*10-3, SpawnerUID
, HIT_SOME
) then
1228 if AllowPush
then gPlayers
[i
].Push(vx
, vy
);
1236 function GunHit (X
, Y
: Integer; vx
, vy
: Integer; dmg
: Integer; SpawnerUID
: Word; AllowPush
: Boolean): Byte;
1238 function monsCheck (mon
: TMonster
): Boolean;
1240 result
:= false; // don't stop
1241 if HitMonster(mon
, dmg
, vx
*10, vy
*10-3, SpawnerUID
, HIT_SOME
) then
1243 if AllowPush
then mon
.Push(vx
, vy
);
1250 if GunHitPlayer(X
, Y
, vx
, vy
, dmg
, SpawnerUID
, AllowPush
) then result
:= 1
1251 else if g_Mons_ForEachAliveAt(X
, Y
, 1, 1, monsCheck
) then result
:= 2;
1256 procedure g_Weapon_gunOld(const x, y, xd, yd, v, dmg: Integer; SpawnerUID: Word; CheckTrigger: Boolean);
1267 t1, _collide: Boolean;
1269 {$IF DEFINED(D2F_DEBUG)}
1271 showTime: Boolean = true;
1274 a := GetAngle(x, y, xd, yd)+180;
1276 SinCos(DegToRad(-a), s, c);
1278 if Abs(s) < 0.01 then s := 0;
1279 if Abs(c) < 0.01 then c := 0;
1281 x2 := x+Round(c*gMapInfo.Width);
1282 y2 := y+Round(s*gMapInfo.Width);
1284 t1 := gWalls <> nil;
1286 w := gMapInfo.Width;
1287 h := gMapInfo.Height;
1294 if (xd = 0) and (yd = 0) then Exit;
1296 if dx > 0 then xi := 1 else if dx < 0 then xi := -1 else xi := 0;
1297 if dy > 0 then yi := 1 else if dy < 0 then yi := -1 else yi := 0;
1302 if dx > dy then d := dx else d := dy;
1304 //blood vel, for Monster.Damage()
1305 //vx := (dx*10 div d)*xi;
1306 //vy := (dy*10 div d)*yi;
1308 {$IF DEFINED(D2F_DEBUG)}
1309 stt := getTimeMicro();
1332 if (yy > h) or (yy < 0) then Break;
1333 if (xx > w) or (xx < 0) then Break;
1336 if ByteBool(gCollideMap[yy, xx] and MARK_BLOCKED) then
1339 {$IF DEFINED(D2F_DEBUG)}
1340 stt := getTimeMicro()-stt;
1341 e_WriteLog(Format('*** old trace time: %u microseconds', [LongWord(stt)]), MSG_NOTIFY);
1344 g_GFX_Spark(xx-xi, yy-yi, 2+Random(2), 180+a, 0, 0);
1345 if g_Game_IsServer and g_Game_IsNet then
1346 MH_SEND_Effect(xx-xi, yy-yi, 180+a, NET_GFX_SPARK);
1349 if not _collide then
1351 _collide := GunHit(xx, yy, xi*v, yi*v, dmg, SpawnerUID, v <> 0) <> 0;
1354 if _collide then Break;
1357 {$IF DEFINED(D2F_DEBUG)}
1360 stt := getTimeMicro()-stt;
1361 e_WriteLog(Format('*** old trace time: %u microseconds', [LongWord(stt)]), MSG_NOTIFY);
1365 if CheckTrigger and g_Game_IsServer then
1366 g_Triggers_PressL(X, Y, xx-xi, yy-yi, SpawnerUID, ACTIVATE_SHOT);
1372 procedure g_Weapon_gun (const x
, y
, xd
, yd
, v
, indmg
: Integer; SpawnerUID
: Word; CheckTrigger
: Boolean);
1377 wallDistSq
: Integer = $3fffffff;
1378 spawnerPlr
: TPlayer
= nil;
1381 function doPlayerHit (idx
: Integer; hx
, hy
: Integer): Boolean;
1384 if (idx
< 0) or (idx
> High(gPlayers
)) then exit
;
1385 if (gPlayers
[idx
] = nil) or not gPlayers
[idx
].alive
then exit
;
1386 if (spawnerPlr
<> nil) then
1388 if ((gGameSettings
.Options
and (GAME_OPTION_TEAMHITTRACE
or GAME_OPTION_TEAMDAMAGE
)) = 0) and
1389 (spawnerPlr
.Team
<> TEAM_NONE
) and (spawnerPlr
.Team
= gPlayers
[idx
].Team
) then
1391 if (spawnerPlr
<> gPlayers
[idx
]) and ((gGameSettings
.Options
and GAME_OPTION_TEAMABSORBDAMAGE
) = 0) then
1392 dmg
:= Max(1, dmg
div 2);
1396 result
:= HitPlayer(gPlayers
[idx
], dmg
, (xi
*v
)*10, (yi
*v
)*10-3, SpawnerUID
, HIT_SOME
);
1397 if result
and (v
<> 0) then gPlayers
[idx
].Push((xi
*v
), (yi
*v
));
1398 {$IF DEFINED(D2F_DEBUG)}
1399 //if result then e_WriteLog(Format(' PLAYER #%d HIT', [idx]), MSG_NOTIFY);
1403 function doMonsterHit (mon
: TMonster
; hx
, hy
: Integer): Boolean;
1406 if (mon
= nil) then exit
;
1407 result
:= HitMonster(mon
, dmg
, (xi
*v
)*10, (yi
*v
)*10-3, SpawnerUID
, HIT_SOME
);
1408 if result
and (v
<> 0) then mon
.Push((xi
*v
), (yi
*v
));
1409 {$IF DEFINED(D2F_DEBUG)}
1410 //if result then e_WriteLog(Format(' MONSTER #%u HIT', [LongWord(mon.UID)]), MSG_NOTIFY);
1414 // collect players along hitray
1415 // return `true` if instant hit was detected
1416 function playerPossibleHit (): Boolean;
1419 px
, py
, pw
, ph
: Integer;
1425 for i
:= 0 to High(gPlayers
) do
1428 if (plr
<> nil) and plr
.alive
then
1430 plr
.getMapBox(px
, py
, pw
, ph
);
1431 if lineAABBIntersects(x
, y
, x2
, y2
, px
, py
, pw
, ph
, inx
, iny
) then
1433 distSq
:= distanceSq(x
, y
, inx
, iny
);
1434 if (distSq
= 0) then
1437 if doPlayerHit(i
, x
, y
) then begin result
:= true; exit
; end;
1439 else if (distSq
< wallDistSq
) then
1441 appendHitTimePlr(distSq
, i
, inx
, iny
);
1448 procedure sqchecker (mon
: TMonster
);
1450 mx
, my
, mw
, mh
: Integer;
1454 mon
.getMapBox(mx
, my
, mw
, mh
);
1455 if lineAABBIntersects(x0
, y0
, x2
, y2
, mx
, my
, mw
, mh
, inx
, iny
) then
1457 distSq
:= distanceSq(x0
, y0
, inx
, iny
);
1458 if (distSq
< wallDistSq
) then appendHitTimeMon(distSq
, mon
, inx
, iny
);
1468 wallHitFlag
: Boolean = false;
1469 wallHitX
: Integer = 0;
1470 wallHitY
: Integer = 0;
1471 didHit
: Boolean = false;
1472 {$IF DEFINED(D2F_DEBUG)}
1476 it
: TMonsterGrid
.Iter
;
1479 if not gwep_debug_fast_trace then
1481 g_Weapon_gunOld(x, y, xd, yd, v, dmg, SpawnerUID, CheckTrigger);
1486 if (xd
= 0) and (yd
= 0) then exit
;
1488 if (g_GetUIDType(SpawnerUID
) = UID_PLAYER
) then
1489 spawnerPlr
:= g_Player_Get(SpawnerUID
);
1493 //wgunMonHash.reset(); //FIXME: clear hash on level change
1494 wgunHitHeap
.clear();
1495 wgunHitTimeUsed
:= 0;
1497 a
:= GetAngle(x
, y
, xd
, yd
)+180;
1499 SinCos(DegToRad(-a
), s
, c
);
1501 if Abs(s
) < 0.01 then s
:= 0;
1502 if Abs(c
) < 0.01 then c
:= 0;
1506 x2
:= x
+Round(c
*gMapInfo
.Width
);
1507 y2
:= y
+Round(s
*gMapInfo
.Width
);
1512 if (dx
> 0) then xi
:= 1 else if (dx
< 0) then xi
:= -1 else xi
:= 0;
1513 if (dy
> 0) then yi
:= 1 else if (dy
< 0) then yi
:= -1 else yi
:= 0;
1515 {$IF DEFINED(D2F_DEBUG)}
1516 e_WriteLog(Format('GUN TRACE: (%d,%d) to (%d,%d)', [x
, y
, x2
, y2
]), TMsgType
.Notify
);
1517 stt
:= getTimeMicro();
1520 wallHitFlag
:= (g_Map_traceToNearestWall(x
, y
, x2
, y2
, @wallHitX
, @wallHitY
) <> nil);
1525 wallDistSq
:= distanceSq(x
, y
, wallHitX
, wallHitY
);
1533 if playerPossibleHit() then exit
; // instant hit
1536 //g_Mons_AlongLine(x, y, x2, y2, sqchecker);
1538 it
:= monsGrid
.forEachAlongLine(x
, y
, x2
, y2
, -1);
1539 for mit
in it
do sqchecker(mit
^);
1542 // here, we collected all monsters and players in `wgunHitHeap` and `wgunHitTime`
1543 // also, if `wallWasHit` is `true`, then `wallHitX` and `wallHitY` contains spark coords
1544 while (wgunHitHeap
.count
> 0) do
1546 // has some entities to check, do it
1547 i
:= wgunHitHeap
.front
;
1548 wgunHitHeap
.popFront();
1550 xe
:= wgunHitTime
[i
].x
;
1551 ye
:= wgunHitTime
[i
].y
;
1552 // check if it is not behind the wall
1553 if (wgunHitTime
[i
].mon
<> nil) then
1555 didHit
:= doMonsterHit(wgunHitTime
[i
].mon
, xe
, ye
);
1559 didHit
:= doPlayerHit(wgunHitTime
[i
].plridx
, xe
, ye
);
1563 // need new coords for trigger
1566 wallHitFlag
:= false; // no sparks
1574 {$IF DEFINED(D2F_DEBUG)}
1575 stt
:= getTimeMicro()-stt
;
1576 e_WriteLog(Format('*** new trace time: %u microseconds', [LongWord(stt
)]), TMsgType
.Notify
);
1579 g_GFX_Spark(wallHitX
, wallHitY
, 2+Random(2), 180+a
, 0, 0);
1581 if g_Game_IsServer
and g_Game_IsNet
then MH_SEND_Effect(wallHitX
, wallHitY
, 180+a
, NET_GFX_SPARK
);
1585 {$IF DEFINED(D2F_DEBUG)}
1586 stt
:= getTimeMicro()-stt
;
1587 e_WriteLog(Format('*** new trace time: %u microseconds', [LongWord(stt
)]), TMsgType
.Notify
);
1591 if CheckTrigger
and g_Game_IsServer
then g_Triggers_PressL(X
, Y
, wallHitX
, wallHitY
, SpawnerUID
, ACTIVATE_SHOT
);
1595 procedure g_Weapon_punch(x
, y
: Integer; d
, SpawnerUID
: Word);
1603 obj
.rect
.Width
:= 39;
1604 obj
.rect
.Height
:= 52;
1610 if g_Weapon_Hit(@obj
, d
, SpawnerUID
, HIT_SOME
) <> 0 then
1611 g_Sound_PlayExAt('SOUND_WEAPON_HITPUNCH', x
, y
)
1613 g_Sound_PlayExAt('SOUND_WEAPON_MISSPUNCH', x
, y
);
1616 function g_Weapon_chainsaw(x
, y
: Integer; d
, SpawnerUID
: Word): Integer;
1624 obj
.rect
.Width
:= 32;
1625 obj
.rect
.Height
:= 52;
1631 Result
:= g_Weapon_Hit(@obj
, d
, SpawnerUID
, HIT_SOME
);
1634 procedure g_Weapon_rocket(x
, y
, xd
, yd
: Integer; SpawnerUID
: Word; WID
: Integer = -1;
1635 Silent
: Boolean = False; compat
: Boolean = true);
1641 find_id
:= FindShot()
1645 if Integer(find_id
) >= High(Shots
) then
1646 SetLength(Shots
, find_id
+ 64)
1649 with Shots
[find_id
] do
1653 Obj
.Rect
.Width
:= SHOT_ROCKETLAUNCHER_WIDTH
;
1654 Obj
.Rect
.Height
:= SHOT_ROCKETLAUNCHER_HEIGHT
;
1657 dx
:= IfThen(xd
> x
, -Obj
.Rect
.Width
, 0)
1659 dx
:= -(Obj
.Rect
.Width
div 2);
1660 dy
:= -(Obj
.Rect
.Height
div 2);
1662 ShotType
:= WEAPON_ROCKETLAUNCHER
;
1663 throw(find_id
, x
+dx
, y
+dy
, xd
+dx
, yd
+dy
, 12);
1669 Shots
[find_id
].SpawnerUID
:= SpawnerUID
;
1672 g_Sound_PlayExAt('SOUND_WEAPON_FIREROCKET', x
, y
);
1675 procedure g_Weapon_revf(x
, y
, xd
, yd
: Integer; SpawnerUID
, TargetUID
: Word;
1676 WID
: Integer = -1; Silent
: Boolean = False);
1682 find_id
:= FindShot()
1686 if Integer(find_id
) >= High(Shots
) then
1687 SetLength(Shots
, find_id
+ 64)
1690 with Shots
[find_id
] do
1694 Obj
.Rect
.Width
:= SHOT_SKELFIRE_WIDTH
;
1695 Obj
.Rect
.Height
:= SHOT_SKELFIRE_HEIGHT
;
1697 dx
:= -(Obj
.Rect
.Width
div 2);
1698 dy
:= -(Obj
.Rect
.Height
div 2);
1700 ShotType
:= WEAPON_SKEL_FIRE
;
1701 throw(find_id
, x
+dx
, y
+dy
, xd
+dx
, yd
+dy
, 12);
1704 target
:= TargetUID
;
1705 Animation
:= TAnimationState
.Create(True, 5, 2); // !!! put values into table
1708 Shots
[find_id
].SpawnerUID
:= SpawnerUID
;
1711 g_Sound_PlayExAt('SOUND_WEAPON_FIREREV', x
, y
);
1714 procedure g_Weapon_plasma(x
, y
, xd
, yd
: Integer; SpawnerUID
: Word; WID
: Integer = -1;
1715 Silent
: Boolean = False; compat
: Boolean = true);
1721 find_id
:= FindShot()
1725 if Integer(find_id
) >= High(Shots
) then
1726 SetLength(Shots
, find_id
+ 64);
1729 with Shots
[find_id
] do
1733 Obj
.Rect
.Width
:= SHOT_PLASMA_WIDTH
;
1734 Obj
.Rect
.Height
:= SHOT_PLASMA_HEIGHT
;
1737 dx
:= IfThen(xd
> x
, -Obj
.Rect
.Width
, 0)
1739 dx
:= -(Obj
.Rect
.Width
div 2);
1740 dy
:= -(Obj
.Rect
.Height
div 2);
1742 ShotType
:= WEAPON_PLASMA
;
1743 throw(find_id
, x
+dx
, y
+dy
, xd
+dx
, yd
+dy
, 16);
1746 Animation
:= TAnimationState
.Create(True, 5, 2); // !!! put values into table
1749 Shots
[find_id
].SpawnerUID
:= SpawnerUID
;
1752 g_Sound_PlayExAt('SOUND_WEAPON_FIREPLASMA', x
, y
);
1755 procedure g_Weapon_flame(x
, y
, xd
, yd
: Integer; SpawnerUID
: Word; WID
: Integer = -1;
1756 Silent
: Boolean = False; compat
: Boolean = true);
1762 find_id
:= FindShot()
1766 if Integer(find_id
) >= High(Shots
) then
1767 SetLength(Shots
, find_id
+ 64);
1770 with Shots
[find_id
] do
1774 Obj
.Rect
.Width
:= SHOT_FLAME_WIDTH
;
1775 Obj
.Rect
.Height
:= SHOT_FLAME_HEIGHT
;
1778 dx
:= IfThen(xd
> x
, -Obj
.Rect
.Width
, 0)
1780 dx
:= -(Obj
.Rect
.Width
div 2);
1781 dy
:= -(Obj
.Rect
.Height
div 2);
1783 ShotType
:= WEAPON_FLAMETHROWER
;
1784 throw(find_id
, x
+dx
, y
+dy
, xd
+dx
, yd
+dy
, 16);
1790 Shots
[find_id
].SpawnerUID
:= SpawnerUID
;
1792 // if not Silent then
1793 // g_Sound_PlayExAt('SOUND_WEAPON_FIREPLASMA', x, y);
1796 procedure g_Weapon_ball1(x
, y
, xd
, yd
: Integer; SpawnerUID
: Word; WID
: Integer = -1;
1797 Silent
: Boolean = False; compat
: Boolean = true);
1803 find_id
:= FindShot()
1807 if Integer(find_id
) >= High(Shots
) then
1808 SetLength(Shots
, find_id
+ 64)
1811 with Shots
[find_id
] do
1815 Obj
.Rect
.Width
:= 16;
1816 Obj
.Rect
.Height
:= 16;
1819 dx
:= IfThen(xd
> x
, -Obj
.Rect
.Width
, 0)
1821 dx
:= -(Obj
.Rect
.Width
div 2);
1822 dy
:= -(Obj
.Rect
.Height
div 2);
1824 ShotType
:= WEAPON_IMP_FIRE
;
1825 throw(find_id
, x
+dx
, y
+dy
, xd
+dx
, yd
+dy
, 16);
1828 Animation
:= TAnimationState
.Create(True, 4, 2); // !!! put values into table
1831 Shots
[find_id
].SpawnerUID
:= SpawnerUID
;
1834 g_Sound_PlayExAt('SOUND_WEAPON_FIREBALL', x
, y
);
1837 procedure g_Weapon_ball2(x
, y
, xd
, yd
: Integer; SpawnerUID
: Word; WID
: Integer = -1;
1838 Silent
: Boolean = False; compat
: Boolean = true);
1844 find_id
:= FindShot()
1848 if Integer(find_id
) >= High(Shots
) then
1849 SetLength(Shots
, find_id
+ 64)
1852 with Shots
[find_id
] do
1856 Obj
.Rect
.Width
:= 16;
1857 Obj
.Rect
.Height
:= 16;
1860 dx
:= IfThen(xd
> x
, -Obj
.Rect
.Width
, 0)
1862 dx
:= -(Obj
.Rect
.Width
div 2);
1863 dy
:= -(Obj
.Rect
.Height
div 2);
1865 ShotType
:= WEAPON_CACO_FIRE
;
1866 throw(find_id
, x
+dx
, y
+dy
, xd
+dx
, yd
+dy
, 16);
1869 Animation
:= TAnimationState
.Create(True, 4, 2); // !!! put values into table
1872 Shots
[find_id
].SpawnerUID
:= SpawnerUID
;
1875 g_Sound_PlayExAt('SOUND_WEAPON_FIREBALL', x
, y
);
1878 procedure g_Weapon_ball7(x
, y
, xd
, yd
: Integer; SpawnerUID
: Word; WID
: Integer = -1;
1879 Silent
: Boolean = False; compat
: Boolean = true);
1885 find_id
:= FindShot()
1889 if Integer(find_id
) >= High(Shots
) then
1890 SetLength(Shots
, find_id
+ 64)
1893 with Shots
[find_id
] do
1897 Obj
.Rect
.Width
:= 32;
1898 Obj
.Rect
.Height
:= 16;
1901 dx
:= IfThen(xd
> x
, -Obj
.Rect
.Width
, 0)
1903 dx
:= -(Obj
.Rect
.Width
div 2);
1904 dy
:= -(Obj
.Rect
.Height
div 2);
1906 ShotType
:= WEAPON_BARON_FIRE
;
1907 throw(find_id
, x
+dx
, y
+dy
, xd
+dx
, yd
+dy
, 16);
1910 Animation
:= TAnimationState
.Create(True, 4, 2); // !!! put values into table
1913 Shots
[find_id
].SpawnerUID
:= SpawnerUID
;
1916 g_Sound_PlayExAt('SOUND_WEAPON_FIREBALL', x
, y
);
1919 procedure g_Weapon_aplasma(x
, y
, xd
, yd
: Integer; SpawnerUID
: Word; WID
: Integer = -1;
1920 Silent
: Boolean = False; compat
: Boolean = true);
1926 find_id
:= FindShot()
1930 if Integer(find_id
) >= High(Shots
) then
1931 SetLength(Shots
, find_id
+ 64)
1934 with Shots
[find_id
] do
1938 Obj
.Rect
.Width
:= 16;
1939 Obj
.Rect
.Height
:= 16;
1942 dx
:= IfThen(xd
> x
, -Obj
.Rect
.Width
, 0)
1944 dx
:= -(Obj
.Rect
.Width
div 2);
1945 dy
:= -(Obj
.Rect
.Height
div 2);
1947 ShotType
:= WEAPON_BSP_FIRE
;
1948 throw(find_id
, x
+dx
, y
+dy
, xd
+dx
, yd
+dy
, 16);
1952 Animation
:= TAnimationState
.Create(True, 4, 2); // !!! put values into table
1955 Shots
[find_id
].SpawnerUID
:= SpawnerUID
;
1958 g_Sound_PlayExAt('SOUND_WEAPON_FIREPLASMA', x
, y
);
1961 procedure g_Weapon_manfire(x
, y
, xd
, yd
: Integer; SpawnerUID
: Word; WID
: Integer = -1;
1962 Silent
: Boolean = False; compat
: Boolean = true);
1968 find_id
:= FindShot()
1972 if Integer(find_id
) >= High(Shots
) then
1973 SetLength(Shots
, find_id
+ 64)
1976 with Shots
[find_id
] do
1980 Obj
.Rect
.Width
:= 32;
1981 Obj
.Rect
.Height
:= 32;
1984 dx
:= IfThen(xd
> x
, -Obj
.Rect
.Width
, 0)
1986 dx
:= -(Obj
.Rect
.Width
div 2);
1987 dy
:= -(Obj
.Rect
.Height
div 2);
1989 ShotType
:= WEAPON_MANCUB_FIRE
;
1990 throw(find_id
, x
+dx
, y
+dy
, xd
+dx
, yd
+dy
, 16);
1994 Animation
:= TAnimationState
.Create(True, 4, 2); // !!! put values into table
1997 Shots
[find_id
].SpawnerUID
:= SpawnerUID
;
2000 g_Sound_PlayExAt('SOUND_WEAPON_FIREBALL', x
, y
);
2003 procedure g_Weapon_bfgshot(x
, y
, xd
, yd
: Integer; SpawnerUID
: Word; WID
: Integer = -1;
2004 Silent
: Boolean = False; compat
: Boolean = true);
2010 find_id
:= FindShot()
2014 if Integer(find_id
) >= High(Shots
) then
2015 SetLength(Shots
, find_id
+ 64)
2018 with Shots
[find_id
] do
2022 Obj
.Rect
.Width
:= SHOT_BFG_WIDTH
;
2023 Obj
.Rect
.Height
:= SHOT_BFG_HEIGHT
;
2026 dx
:= IfThen(xd
> x
, -Obj
.Rect
.Width
, 0)
2028 dx
:= -(Obj
.Rect
.Width
div 2);
2029 dy
:= -(Obj
.Rect
.Height
div 2);
2031 ShotType
:= WEAPON_BFG
;
2032 throw(find_id
, x
+dx
, y
+dy
, xd
+dx
, yd
+dy
, 16);
2035 Animation
:= TAnimationState
.Create(True, 6, 2); // !!! put values into table
2038 Shots
[find_id
].SpawnerUID
:= SpawnerUID
;
2041 g_Sound_PlayExAt('SOUND_WEAPON_FIREBFG', x
, y
);
2044 procedure g_Weapon_bfghit(x
, y
: Integer);
2047 g_GFX_QueueEffect(R_GFX_BFG_HIT
, x
- 32, y
- 32);
2051 procedure g_Weapon_pistol(x
, y
, xd
, yd
: Integer; SpawnerUID
: Word;
2052 Silent
: Boolean = False);
2055 g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', x
, y
);
2057 g_Weapon_gun(x
, y
, xd
, yd
, 1, 3, SpawnerUID
, True);
2058 if gGameSettings
.GameMode
in [GM_DM
, GM_TDM
, GM_CTF
] then
2060 if ABS(x
-xd
) >= ABS(y
-yd
) then
2062 g_Weapon_gun(x
, y
+1, xd
, yd
+1, 1, 3, SpawnerUID
, False);
2063 g_Weapon_gun(x
, y
-1, xd
, yd
-1, 1, 2, SpawnerUID
, False);
2067 g_Weapon_gun(x
+1, y
, xd
+1, yd
, 1, 3, SpawnerUID
, False);
2068 g_Weapon_gun(x
-1, y
, xd
-1, yd
, 1, 2, SpawnerUID
, False);
2073 procedure g_Weapon_mgun(x
, y
, xd
, yd
: Integer; SpawnerUID
: Word;
2074 Silent
: Boolean = False);
2077 if gSoundEffectsDF
then g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', x
, y
);
2079 g_Weapon_gun(x
, y
, xd
, yd
, 1, 3, SpawnerUID
, True);
2080 if (gGameSettings
.GameMode
in [GM_DM
, GM_TDM
, GM_CTF
]) and
2081 (g_GetUIDType(SpawnerUID
) = UID_PLAYER
) then
2083 if ABS(x
-xd
) >= ABS(y
-yd
) then
2085 g_Weapon_gun(x
, y
+1, xd
, yd
+1, 1, 2, SpawnerUID
, False);
2086 g_Weapon_gun(x
, y
-1, xd
, yd
-1, 1, 2, SpawnerUID
, False);
2090 g_Weapon_gun(x
+1, y
, xd
+1, yd
, 1, 2, SpawnerUID
, False);
2091 g_Weapon_gun(x
-1, y
, xd
-1, yd
, 1, 2, SpawnerUID
, False);
2096 procedure g_Weapon_shotgun(x
, y
, xd
, yd
: Integer; SpawnerUID
: Word;
2097 Silent
: Boolean = False);
2102 if gSoundEffectsDF
then g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', x
, y
);
2107 if ABS(x
-xd
) >= ABS(y
-yd
) then j
:= Random(17) - 8 else k
:= Random(17) - 8; // -8 .. 8
2108 g_Weapon_gun(x
+k
, y
+j
, xd
+k
, yd
+j
, IfThen(i
mod 2 <> 0, 1, 0), 3, SpawnerUID
, i
=0);
2112 procedure g_Weapon_dshotgun(x
, y
, xd
, yd
: Integer; SpawnerUID
: Word;
2113 Silent
: Boolean = False);
2115 a
, i
, j
, k
: Integer;
2118 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN2', x
, y
);
2120 if gGameSettings
.GameMode
in [GM_DM
, GM_TDM
, GM_CTF
] then a
:= 25 else a
:= 20;
2124 if ABS(x
-xd
) >= ABS(y
-yd
) then j
:= Random(41) - 20 else k
:= Random(41) - 20; // -20 .. 20
2125 g_Weapon_gun(x
+k
, y
+j
, xd
+k
, yd
+j
, IfThen(i
mod 3 <> 0, 0, 1), 3, SpawnerUID
, i
=0);
2129 procedure g_Weapon_PreUpdate();
2133 if Shots
= nil then Exit
;
2134 for i
:= 0 to High(Shots
) do
2135 if Shots
[i
].ShotType
<> 0 then
2137 Shots
[i
].Obj
.oldX
:= Shots
[i
].Obj
.X
;
2138 Shots
[i
].Obj
.oldY
:= Shots
[i
].Obj
.Y
;
2142 procedure g_Weapon_Update();
2144 i
, a
, h
, cx
, cy
, oldvx
, oldvy
, tf
: Integer;
2151 var tcx
, tcy
: Integer;
2157 for i
:= 0 to High(Shots
) do
2159 if Shots
[i
].ShotType
= 0 then
2166 Timeout
:= Timeout
- 1;
2169 // Àêòèâèðîâàòü òðèããåðû ïî ïóòè (êðîìå óæå àêòèâèðîâàííûõ):
2170 if (Stopped
= 0) and g_Game_IsServer
then
2171 t
:= g_Triggers_PressR(Obj
.X
, Obj
.Y
, Obj
.Rect
.Width
, Obj
.Rect
.Height
,
2172 SpawnerUID
, ACTIVATE_SHOT
, triggers
)
2178 // Ïîïîëíÿåì ñïèñîê àêòèâèðîâàííûõ òðèããåðîâ:
2179 if triggers
= nil then
2186 if not InDWArray(t
[a
], triggers
) then
2188 SetLength(triggers
, Length(triggers
)+1);
2189 triggers
[High(triggers
)] := t
[a
];
2194 // Àíèìàöèÿ ñíàðÿäà:
2195 if Animation
<> nil then
2199 spl
:= (ShotType
<> WEAPON_PLASMA
) and
2200 (ShotType
<> WEAPON_BFG
) and
2201 (ShotType
<> WEAPON_BSP_FIRE
) and
2202 (ShotType
<> WEAPON_FLAMETHROWER
);
2206 st
:= g_Obj_Move_Projectile(@Obj
, False, spl
);
2212 positionChanged(); // this updates spatial accelerators
2214 if WordBool(st
and MOVE_FALLOUT
) or (Obj
.X
< -1000) or
2215 (Obj
.X
> gMapInfo
.Width
+1000) or (Obj
.Y
< -1000) then
2217 // Íà êëèåíòå ñêîðåå âñåãî è òàê óæå âûïàë.
2223 cx
:= Obj
.X
+ (Obj
.Rect
.Width
div 2);
2224 cy
:= Obj
.Y
+ (Obj
.Rect
.Height
div 2);
2227 WEAPON_ROCKETLAUNCHER
, WEAPON_SKEL_FIRE
: // Ðàêåòû è ñíàðÿäû Ñêåëåòà
2229 // Âûëåòåëà èç âîäû:
2230 if WordBool(st
and MOVE_HITAIR
) then
2231 g_Obj_SetSpeed(@Obj
, 12);
2233 // Â âîäå øëåéô - ïóçûðè, â âîçäóõå øëåéô - äûì:
2234 if WordBool(st
and MOVE_INWATER
) then
2237 g_GFX_Bubbles(cx
, cy
, 1+Random(3), 16, 16);
2240 then g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', cx
, cy
)
2241 else g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', cx
, cy
);
2246 g_GFX_QueueEffect(R_GFX_SMOKE_TRANS
, Obj
.X
-14+Random(9), cy
-20+Random(9));
2250 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2251 if WordBool(st
and (MOVE_HITWALL
or MOVE_HITLAND
or MOVE_HITCEIL
)) or
2252 (g_Weapon_Hit(@Obj
, 10, SpawnerUID
, HIT_SOME
, False) <> 0) or
2258 g_Weapon_Explode(cx
, cy
, 60, SpawnerUID
);
2260 if ShotType
= WEAPON_SKEL_FIRE
then
2261 begin // Âçðûâ ñíàðÿäà Ñêåëåòà
2263 g_GFX_QueueEffect(R_GFX_EXPLODE_SKELFIRE
, Obj
.X
+ 32 - 58, Obj
.Y
+ 8 - 36);
2264 g_DynLightExplosion((Obj
.X
+32), (Obj
.Y
+8), 64, 1, 0, 0);
2268 begin // Âçðûâ Ðàêåòû
2270 g_GFX_QueueEffect(R_GFX_EXPLODE_ROCKET
, cx
- 64, cy
- 64);
2271 g_DynLightExplosion(cx
, cy
, 64, 1, 0, 0);
2275 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEROCKET', Obj
.X
, Obj
.Y
);
2280 if ShotType
= WEAPON_SKEL_FIRE
then
2281 begin // Ñàìîíàâîäêà ñíàðÿäà Ñêåëåòà:
2282 if GetPos(target
, @o
) then
2283 throw(i
, Obj
.X
, Obj
.Y
,
2284 o
.X
+o
.Rect
.X
+(o
.Rect
.Width
div 2)+o
.Vel
.X
+o
.Accel
.X
,
2285 o
.Y
+o
.Rect
.Y
+(o
.Rect
.Height
div 2)+o
.Vel
.Y
+o
.Accel
.Y
,
2290 WEAPON_PLASMA
, WEAPON_BSP_FIRE
: // Ïëàçìà, ïëàçìà Àðàõíàòðîíà
2292 // Ïîïàëà â âîäó - ýëåêòðîøîê ïî âîäå:
2293 if WordBool(st
and (MOVE_INWATER
or MOVE_HITWATER
)) then
2295 g_Sound_PlayExAt('SOUND_WEAPON_PLASMAWATER', Obj
.X
, Obj
.Y
);
2296 if g_Game_IsServer
then CheckTrap(i
, 10, HIT_ELECTRO
);
2302 if (ShotType
= WEAPON_PLASMA
) and
2303 (gGameSettings
.GameMode
in [GM_DM
, GM_TDM
, GM_CTF
]) then
2308 if ShotType
= WEAPON_BSP_FIRE
then
2311 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2312 if WordBool(st
and (MOVE_HITWALL
or MOVE_HITLAND
or MOVE_HITCEIL
)) or
2313 (g_Weapon_Hit(@Obj
, a
, SpawnerUID
, HIT_SOME
, False) <> 0) or
2317 if ShotType
= WEAPON_PLASMA
then
2318 g_GFX_QueueEffect(R_GFX_EXPLODE_PLASMA
, cx
- 16, cy
- 16)
2320 g_GFX_QueueEffect(R_GFX_EXPLODE_BSPFIRE
, cx
- 16, cy
- 16);
2321 g_DynLightExplosion(cx
, cy
, 32, 0, 0.5, 0.5);
2323 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEPLASMA', Obj
.X
, Obj
.Y
);
2328 WEAPON_FLAMETHROWER
: // Îãíåìåò
2330 // Ñî âðåìåíåì óìèðàåò
2331 if (Timeout
< 1) then
2337 if WordBool(st
and (MOVE_HITWATER
or MOVE_INWATER
)) then
2339 if WordBool(st
and MOVE_HITWATER
) then
2344 g_GFX_QueueEffect(R_GFX_SMOKE
, cx
-4+tcx
-(R_GFX_SMOKE_WIDTH
div 2), cy
-4+tcy
-(R_GFX_SMOKE_HEIGHT
div 2));
2350 g_GFX_Bubbles(cx
, cy
, 1+Random(3), 16, 16);
2353 then g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', cx
, cy
)
2354 else g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', cx
, cy
);
2362 Obj
.Accel
.Y
:= Obj
.Accel
.Y
+ 1;
2363 // Ïîïàëè â ñòåíó èëè â âîäó:
2364 if WordBool(st
and (MOVE_HITWALL
or MOVE_HITLAND
or MOVE_HITCEIL
or MOVE_HITWATER
)) then
2370 if WordBool(st
and MOVE_HITWALL
) then
2371 Stopped
:= MOVE_HITWALL
2372 else if WordBool(st
and MOVE_HITLAND
) then
2373 Stopped
:= MOVE_HITLAND
2374 else if WordBool(st
and MOVE_HITCEIL
) then
2375 Stopped
:= MOVE_HITCEIL
;
2378 a
:= IfThen(Stopped
= 0, 10, 1);
2379 // Åñëè â êîãî-òî ïîïàëè
2380 if g_Weapon_Hit(@Obj
, a
, SpawnerUID
, HIT_FLAME
, False) <> 0 then
2382 // HIT_FLAME ñàì ïîäîææåò
2383 // Åñëè â ïîëåòå ïîïàëè, èñ÷åçàåì
2393 if (gTime
mod LongWord(tf
) = 0) then
2397 MOVE_HITWALL
: begin tcx
:= cx
-4+Random(8); tcy
:= cy
-12+Random(24); end;
2398 MOVE_HITLAND
: begin tcx
:= cx
-12+Random(24); tcy
:= cy
-10+Random(8); end;
2399 MOVE_HITCEIL
: begin tcx
:= cx
-12+Random(24); tcy
:= cy
+6+Random(8); end;
2400 else begin tcx
:= cx
-4+Random(8); tcy
:= cy
-4+Random(8); end;
2402 g_GFX_QueueEffect(R_GFX_FLAME_RAND
, tcx
- (R_GFX_FLAME_WIDTH
div 2), tcy
- (R_GFX_FLAME_HEIGHT
div 2));
2403 //g_DynLightExplosion(tcx, tcy, 1, 1, 0.8, 0.3);
2410 // Ïîïàëà â âîäó - ýëåêòðîøîê ïî âîäå:
2411 if WordBool(st
and (MOVE_INWATER
or MOVE_HITWATER
)) then
2413 g_Sound_PlayExAt('SOUND_WEAPON_BFGWATER', Obj
.X
, Obj
.Y
);
2414 if g_Game_IsServer
then CheckTrap(i
, 1000, HIT_ELECTRO
);
2419 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2420 if WordBool(st
and (MOVE_HITWALL
or MOVE_HITLAND
or MOVE_HITCEIL
)) or
2421 (g_Weapon_Hit(@Obj
, SHOT_BFG_DAMAGE
, SpawnerUID
, HIT_BFG
, False) <> 0) or
2425 if g_Game_IsServer
then g_Weapon_BFG9000(cx
, cy
, SpawnerUID
);
2427 g_GFX_QueueEffect(R_GFX_EXPLODE_BFG
, cx
- 64, cy
- 64);
2428 g_DynLightExplosion(cx
, cy
, 96, 0, 1, 0);
2430 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBFG', Obj
.X
, Obj
.Y
);
2435 WEAPON_IMP_FIRE
, WEAPON_CACO_FIRE
, WEAPON_BARON_FIRE
: // Âûñòðåëû Áåñà, Êàêîäåìîíà Ðûöàðÿ/Áàðîíà àäà
2438 if WordBool(st
and MOVE_HITAIR
) then
2439 g_Obj_SetSpeed(@Obj
, 16);
2442 if ShotType
= WEAPON_IMP_FIRE
then
2445 if ShotType
= WEAPON_CACO_FIRE
then
2450 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2451 if WordBool(st
and (MOVE_HITWALL
or MOVE_HITLAND
or MOVE_HITCEIL
)) or
2452 (g_Weapon_Hit(@Obj
, a
, SpawnerUID
, HIT_SOME
) <> 0) or
2457 WEAPON_IMP_FIRE
: g_GFX_QueueEffect(R_GFX_EXPLODE_IMPFIRE
, cx
- 32, cy
- 32);
2458 WEAPON_CACO_FIRE
: g_GFX_QueueEffect(R_GFX_EXPLODE_CACOFIRE
, cx
- 32, cy
- 32);
2459 WEAPON_BARON_FIRE
: g_GFX_QueueEffect(R_GFX_EXPLODE_BARONFIRE
, cx
- 32, cy
- 32);
2462 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBALL', Obj
.X
, Obj
.Y
);
2467 WEAPON_MANCUB_FIRE
: // Âûñòðåë Ìàíêóáóñà
2470 if WordBool(st
and MOVE_HITAIR
) then
2471 g_Obj_SetSpeed(@Obj
, 16);
2473 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2474 if WordBool(st
and (MOVE_HITWALL
or MOVE_HITLAND
or MOVE_HITCEIL
)) or
2475 (g_Weapon_Hit(@Obj
, 40, SpawnerUID
, HIT_SOME
, False) <> 0) or
2480 g_GFX_QueueEffect(R_GFX_EXPLODE_ROCKET
, cx
- 64, cy
- 64);
2482 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBALL', Obj
.X
, Obj
.Y
);
2486 end; // case ShotType of...
2488 // Åñëè ñíàðÿäà óæå íåò, óäàëÿåì àíèìàöèþ:
2489 if (ShotType
= 0) then
2491 if gGameSettings
.GameType
= GT_SERVER
then
2492 MH_SEND_DeleteShot(i
, Obj
.X
, Obj
.Y
, Loud
);
2493 if Animation
<> nil then
2499 else if (ShotType
<> WEAPON_FLAMETHROWER
) and ((oldvx
<> Obj
.Vel
.X
) or (oldvy
<> Obj
.Vel
.Y
)) then
2500 if gGameSettings
.GameType
= GT_SERVER
then
2501 MH_SEND_UpdateShot(i
);
2506 function g_Weapon_Danger(UID
: Word; X
, Y
: Integer; Width
, Height
: Word; Time
: Byte): Boolean;
2515 for a
:= 0 to High(Shots
) do
2516 if (Shots
[a
].ShotType
<> 0) and (Shots
[a
].SpawnerUID
<> UID
) then
2517 if ((Shots
[a
].Obj
.Vel
.Y
= 0) and (Shots
[a
].Obj
.Vel
.X
> 0) and (Shots
[a
].Obj
.X
< X
)) or
2518 (Shots
[a
].Obj
.Vel
.Y
= 0) and (Shots
[a
].Obj
.Vel
.X
< 0) and (Shots
[a
].Obj
.X
> X
) then
2519 if (Abs(X
-Shots
[a
].Obj
.X
) < Abs(Shots
[a
].Obj
.Vel
.X
*Time
)) and
2520 g_Collide(X
, Y
, Width
, Height
, X
, Shots
[a
].Obj
.Y
,
2521 Shots
[a
].Obj
.Rect
.Width
, Shots
[a
].Obj
.Rect
.Height
) and
2522 g_TraceVector(X
, Y
, Shots
[a
].Obj
.X
, Shots
[a
].Obj
.Y
) then
2529 procedure g_Weapon_SaveState (st
: TStream
);
2531 count
, i
, j
: Integer;
2533 // Ñ÷èòàåì êîëè÷åñòâî ñóùåñòâóþùèõ ñíàðÿäîâ
2535 for i
:= 0 to High(Shots
) do if (Shots
[i
].ShotType
<> 0) then Inc(count
);
2537 // Êîëè÷åñòâî ñíàðÿäîâ
2538 utils
.WriteInt(st
, count
);
2540 if (count
= 0) then exit
;
2542 for i
:= 0 to High(Shots
) do
2544 if Shots
[i
].ShotType
<> 0 then
2546 // Ñèãíàòóðà ñíàðÿäà
2547 utils
.writeSign(st
, 'SHOT');
2548 utils
.writeInt(st
, Byte(0)); // version
2550 utils
.writeInt(st
, Byte(Shots
[i
].ShotType
));
2552 utils
.writeInt(st
, Word(Shots
[i
].Target
));
2554 utils
.writeInt(st
, Word(Shots
[i
].SpawnerUID
));
2555 // Ðàçìåð ïîëÿ Triggers
2556 utils
.writeInt(st
, Integer(Length(Shots
[i
].Triggers
)));
2557 // Òðèããåðû, àêòèâèðîâàííûå âûñòðåëîì
2558 for j
:= 0 to Length(Shots
[i
].Triggers
)-1 do utils
.writeInt(st
, LongWord(Shots
[i
].Triggers
[j
]));
2560 Obj_SaveState(st
, @Shots
[i
].Obj
);
2562 utils
.writeInt(st
, Byte(Shots
[i
].Stopped
));
2567 procedure g_Weapon_LoadState (st
: TStream
);
2569 count
, tc
, i
, j
: Integer;
2571 if (st
= nil) then exit
;
2573 // Êîëè÷åñòâî ñíàðÿäîâ
2574 count
:= utils
.readLongInt(st
);
2575 if (count
< 0) or (count
> 1024*1024) then raise XStreamError
.Create('invalid shots counter');
2577 SetLength(Shots
, count
);
2579 if (count
= 0) then exit
;
2581 for i
:= 0 to count
-1 do
2583 // Ñèãíàòóðà ñíàðÿäà
2584 if not utils
.checkSign(st
, 'SHOT') then raise XStreamError
.Create('invalid shot signature');
2585 if (utils
.readByte(st
) <> 0) then raise XStreamError
.Create('invalid shot version');
2587 Shots
[i
].ShotType
:= utils
.readByte(st
);
2589 Shots
[i
].Target
:= utils
.readWord(st
);
2591 Shots
[i
].SpawnerUID
:= utils
.readWord(st
);
2592 // Ðàçìåð ïîëÿ Triggers
2593 tc
:= utils
.readLongInt(st
);
2594 if (tc
< 0) or (tc
> 1024*1024) then raise XStreamError
.Create('invalid shot triggers counter');
2595 SetLength(Shots
[i
].Triggers
, tc
);
2596 // Òðèããåðû, àêòèâèðîâàííûå âûñòðåëîì
2597 for j
:= 0 to tc
-1 do Shots
[i
].Triggers
[j
] := utils
.readLongWord(st
);
2599 Obj_LoadState(@Shots
[i
].Obj
, st
);
2601 Shots
[i
].Stopped
:= utils
.readByte(st
);
2603 // Óñòàíîâêà òåêñòóðû èëè àíèìàöèè
2604 Shots
[i
].Animation
:= nil;
2606 case Shots
[i
].ShotType
of
2607 WEAPON_ROCKETLAUNCHER
, WEAPON_SKEL_FIRE
:
2612 Shots
[i
].Animation
:= TAnimationState
.Create(True, 5, 2); // !!! put values into table
2616 Shots
[i
].Animation
:= TAnimationState
.Create(True, 6, 2); // !!! put values into table
2620 Shots
[i
].Animation
:= TAnimationState
.Create(True, 4, 2); // !!! put values into table
2624 Shots
[i
].Animation
:= TAnimationState
.Create(True, 4, 2); // !!! put values into table
2628 Shots
[i
].Animation
:= TAnimationState
.Create(True, 4, 2); // !!! put values into table
2632 Shots
[i
].Animation
:= TAnimationState
.Create(True, 4, 2); // !!! put values into table
2636 Shots
[i
].Animation
:= TAnimationState
.Create(True, 4, 2); // !!! put values into table
2642 procedure g_Weapon_DestroyShot(I
: Integer; X
, Y
: Integer; Loud
: Boolean = True);
2644 var cx
, cy
: Integer;
2649 if (I
> High(Shots
)) or (I
< 0) then Exit
;
2653 if ShotType
= 0 then Exit
;
2657 cx
:= Obj
.X
+ (Obj
.Rect
.Width
div 2);
2658 cy
:= Obj
.Y
+ (Obj
.Rect
.Height
div 2);
2662 WEAPON_ROCKETLAUNCHER
, WEAPON_SKEL_FIRE
: // Ðàêåòû è ñíàðÿäû Ñêåëåòà
2667 if ShotType
= WEAPON_SKEL_FIRE
then
2668 g_GFX_QueueEffect(R_GFX_EXPLODE_SKELFIRE
, (Obj
.X
+ 32) - 32, (Obj
.Y
+ 8) - 32)
2670 g_GFX_QueueEffect(R_GFX_EXPLODE_ROCKET
, cx
- 64, cy
- 64);
2672 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEROCKET', Obj
.X
, Obj
.Y
);
2676 WEAPON_PLASMA
, WEAPON_BSP_FIRE
: // Ïëàçìà, ïëàçìà Àðàõíàòðîíà
2681 if ShotType
= WEAPON_PLASMA
then
2682 g_GFX_QueueEffect(R_GFX_EXPLODE_PLASMA
, cx
- 16, cy
- 16)
2684 g_GFX_QueueEffect(R_GFX_EXPLODE_BSPFIRE
, cx
- 16, cy
- 16);
2686 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEPLASMA', Obj
.X
, Obj
.Y
);
2693 g_GFX_QueueEffect(R_GFX_EXPLODE_BFG
, cx
- 64, cy
- 64);
2695 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBFG', Obj
.X
, Obj
.Y
);
2698 WEAPON_IMP_FIRE
, WEAPON_CACO_FIRE
, WEAPON_BARON_FIRE
: // Âûñòðåëû Áåñà, Êàêîäåìîíà Ðûöàðÿ/Áàðîíà àäà
2704 WEAPON_IMP_FIRE
: g_GFX_QueueEffect(R_GFX_EXPLODE_IMPFIRE
, cx
- 32, cy
- 32);
2705 WEAPON_CACO_FIRE
: g_GFX_QueueEffect(R_GFX_EXPLODE_CACOFIRE
, cx
- 32, cy
- 32);
2706 WEAPON_BARON_FIRE
: g_GFX_QueueEffect(R_GFX_EXPLODE_BARONFIRE
, cx
- 32, cy
- 32);
2709 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBALL', Obj
.X
, Obj
.Y
);
2713 WEAPON_MANCUB_FIRE
: // Âûñòðåë Ìàíêóáóñà
2716 g_GFX_QueueEffect(R_GFX_EXPLODE_ROCKET
, cx
- 64, cy
- 64);
2718 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBALL', Obj
.X
, Obj
.Y
);
2720 end; // case ShotType of...
2728 procedure g_Weapon_AddDynLights();
2732 if Shots
= nil then Exit
;
2733 for i
:= 0 to High(Shots
) do
2735 if Shots
[i
].ShotType
= 0 then continue
;
2736 if (Shots
[i
].ShotType
= WEAPON_ROCKETLAUNCHER
) or
2737 (Shots
[i
].ShotType
= WEAPON_BARON_FIRE
) or
2738 (Shots
[i
].ShotType
= WEAPON_MANCUB_FIRE
) or
2739 (Shots
[i
].ShotType
= WEAPON_SKEL_FIRE
) or
2740 (Shots
[i
].ShotType
= WEAPON_IMP_FIRE
) or
2741 (Shots
[i
].ShotType
= WEAPON_CACO_FIRE
) or
2742 (Shots
[i
].ShotType
= WEAPON_MANCUB_FIRE
) or
2743 (Shots
[i
].ShotType
= WEAPON_BSP_FIRE
) or
2744 (Shots
[i
].ShotType
= WEAPON_PLASMA
) or
2745 (Shots
[i
].ShotType
= WEAPON_BFG
) or
2746 (Shots
[i
].ShotType
= WEAPON_FLAMETHROWER
) or
2749 if (Shots
[i
].ShotType
= WEAPON_PLASMA
) then
2750 g_AddDynLight(Shots
[i
].Obj
.X
+(Shots
[i
].Obj
.Rect
.Width
div 2), Shots
[i
].Obj
.Y
+(Shots
[i
].Obj
.Rect
.Height
div 2), 128, 0, 0.3, 1, 0.4)
2751 else if (Shots
[i
].ShotType
= WEAPON_BFG
) then
2752 g_AddDynLight(Shots
[i
].Obj
.X
+(Shots
[i
].Obj
.Rect
.Width
div 2), Shots
[i
].Obj
.Y
+(Shots
[i
].Obj
.Rect
.Height
div 2), 128, 0, 1, 0, 0.5)
2753 else if (Shots
[i
].ShotType
= WEAPON_FLAMETHROWER
) then
2754 g_AddDynLight(Shots
[i
].Obj
.X
+(Shots
[i
].Obj
.Rect
.Width
div 2), Shots
[i
].Obj
.Y
+(Shots
[i
].Obj
.Rect
.Height
div 2), 42, 1, 0.8, 0, 0.4)
2756 g_AddDynLight(Shots
[i
].Obj
.X
+(Shots
[i
].Obj
.Rect
.Width
div 2), Shots
[i
].Obj
.Y
+(Shots
[i
].Obj
.Rect
.Height
div 2), 128, 1, 0, 0, 0.4);
2762 procedure TShot
.positionChanged (); begin end;