DEADSOFTWARE

44d6a932ad94b7ddc1a22249f2fce0d7a5fdafe5
[d2df-sdl.git] / src / game / g_weapons.pas
1 (* Copyright (C) Doom 2D: Forever Developers
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, version 3 of the License ONLY.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 *)
15 {$INCLUDE ../shared/a_modes.inc}
16 {.$DEFINE GWEP_HITSCAN_TRACE_BITMAP_CHECKER}
17 unit g_weapons;
19 interface
21 uses
22 SysUtils, Classes, mempool,
23 g_textures, g_basic, g_phys, xprofiler;
26 type
27 TShot = record
28 ShotType: Byte;
29 Target: Word;
30 SpawnerUID: Word;
31 Triggers: DWArray;
32 Obj: TObj;
33 Animation: TAnimation;
34 TextureID: DWORD;
35 Timeout: DWORD;
36 Stopped: Byte;
38 procedure positionChanged (); //WARNING! call this after monster position was changed, or coldet will not work right!
39 end;
42 var
43 Shots: array of TShot = nil;
44 LastShotID: Integer = 0;
46 procedure g_Weapon_LoadData();
47 procedure g_Weapon_FreeData();
48 procedure g_Weapon_Init();
49 procedure g_Weapon_Free();
50 function g_Weapon_Hit(obj: PObj; d: Integer; SpawnerUID: Word; t: Byte; HitCorpses: Boolean = True): Byte;
51 function g_Weapon_HitUID(UID: Word; d: Integer; SpawnerUID: Word; t: Byte): Boolean;
52 function g_Weapon_CreateShot(I: Integer; ShotType: Byte; Spawner, TargetUID: Word; X, Y, XV, YV: Integer): LongWord;
54 procedure g_Weapon_gun(const x, y, xd, yd, v, indmg: Integer; SpawnerUID: Word; CheckTrigger: Boolean);
55 procedure g_Weapon_punch(x, y: Integer; d, SpawnerUID: Word);
56 function g_Weapon_chainsaw(x, y: Integer; d, SpawnerUID: Word): Integer;
57 procedure g_Weapon_rocket(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1; Silent: Boolean = False; compat: Boolean = true);
58 procedure g_Weapon_revf(x, y, xd, yd: Integer; SpawnerUID, TargetUID: Word; WID: Integer = -1; Silent: Boolean = False);
59 procedure g_Weapon_flame(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1; Silent: Boolean = False; compat: Boolean = true);
60 procedure g_Weapon_plasma(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1; Silent: Boolean = False; compat: Boolean = true);
61 procedure g_Weapon_ball1(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1; Silent: Boolean = False; compat: Boolean = true);
62 procedure g_Weapon_ball2(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1; Silent: Boolean = False; compat: Boolean = true);
63 procedure g_Weapon_ball7(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1; Silent: Boolean = False; compat: Boolean = true);
64 procedure g_Weapon_aplasma(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1; Silent: Boolean = False; compat: Boolean = true);
65 procedure g_Weapon_manfire(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1; Silent: Boolean = False; compat: Boolean = true);
66 procedure g_Weapon_bfgshot(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1; Silent: Boolean = False; compat: Boolean = true);
67 procedure g_Weapon_bfghit(x, y: Integer);
68 procedure g_Weapon_pistol(x, y, xd, yd: Integer; SpawnerUID: Word; Silent: Boolean = False);
69 procedure g_Weapon_mgun(x, y, xd, yd: Integer; SpawnerUID: Word; Silent: Boolean = False);
70 procedure g_Weapon_shotgun(x, y, xd, yd: Integer; SpawnerUID: Word; Silent: Boolean = False);
71 procedure g_Weapon_dshotgun(x, y, xd, yd: Integer; SpawnerUID: Word; Silent: Boolean = False);
73 function g_Weapon_Explode(X, Y: Integer; rad: Integer; SpawnerUID: Word): Boolean;
74 procedure g_Weapon_BFG9000(X, Y: Integer; SpawnerUID: Word);
75 procedure g_Weapon_PreUpdate();
76 procedure g_Weapon_Update();
77 function g_Weapon_Danger(UID: Word; X, Y: Integer; Width, Height: Word; Time: Byte): Boolean;
78 procedure g_Weapon_DestroyShot(I: Integer; X, Y: Integer; Loud: Boolean = True);
80 procedure g_Weapon_SaveState (st: TStream);
81 procedure g_Weapon_LoadState (st: TStream);
83 procedure g_Weapon_AddDynLights();
85 const
86 WEAPON_KASTET = 0;
87 WEAPON_SAW = 1;
88 WEAPON_PISTOL = 2;
89 WEAPON_SHOTGUN1 = 3;
90 WEAPON_SHOTGUN2 = 4;
91 WEAPON_CHAINGUN = 5;
92 WEAPON_ROCKETLAUNCHER = 6;
93 WEAPON_PLASMA = 7;
94 WEAPON_BFG = 8;
95 WEAPON_SUPERPULEMET = 9;
96 WEAPON_FLAMETHROWER = 10;
97 WEAPON_ZOMBY_PISTOL = 20;
98 WEAPON_IMP_FIRE = 21;
99 WEAPON_BSP_FIRE = 22;
100 WEAPON_CACO_FIRE = 23;
101 WEAPON_BARON_FIRE = 24;
102 WEAPON_MANCUB_FIRE = 25;
103 WEAPON_SKEL_FIRE = 26;
105 WP_FIRST = WEAPON_KASTET;
106 WP_LAST = WEAPON_FLAMETHROWER;
109 var
110 gwep_debug_fast_trace: Boolean = true;
113 implementation
115 uses
116 Math, g_map, g_player, g_gfx, g_sound, g_panel,
117 g_console, g_options, g_game, r_textures, r_animations,
118 g_triggers, MAPDEF, e_log, g_monsters, g_saveload,
119 g_language, g_netmsg, g_grid,
120 geom, binheap, hashtable, utils, xstreams;
122 type
123 TWaterPanel = record
124 X, Y: Integer;
125 Width, Height: Word;
126 Active: Boolean;
127 end;
129 const
130 SHOT_ROCKETLAUNCHER_WIDTH = 14;
131 SHOT_ROCKETLAUNCHER_HEIGHT = 14;
133 SHOT_SKELFIRE_WIDTH = 14;
134 SHOT_SKELFIRE_HEIGHT = 14;
136 SHOT_PLASMA_WIDTH = 16;
137 SHOT_PLASMA_HEIGHT = 16;
139 SHOT_BFG_WIDTH = 32;
140 SHOT_BFG_HEIGHT = 32;
141 SHOT_BFG_DAMAGE = 100;
142 SHOT_BFG_RADIUS = 256;
144 SHOT_FLAME_WIDTH = 4;
145 SHOT_FLAME_HEIGHT = 4;
146 SHOT_FLAME_LIFETIME = 180;
148 SHOT_SIGNATURE = $544F4853; // 'SHOT'
150 type
151 PHitTime = ^THitTime;
152 THitTime = record
153 distSq: Integer;
154 mon: TMonster;
155 plridx: Integer; // if mon=nil
156 x, y: Integer;
157 end;
159 TBinHeapKeyHitTime = class
160 public
161 class function less (const a, b: Integer): Boolean; inline;
162 end;
164 // indicies in `wgunHitTime` array
165 TBinaryHeapHitTimes = specialize TBinaryHeapBase<Integer, TBinHeapKeyHitTime>;
167 var
168 WaterMap: array of array of DWORD = nil;
169 //wgunMonHash: THashIntInt = nil;
170 wgunHitHeap: TBinaryHeapHitTimes = nil;
171 wgunHitTime: array of THitTime = nil;
172 wgunHitTimeUsed: Integer = 0;
175 class function TBinHeapKeyHitTime.less (const a, b: Integer): Boolean;
176 var
177 hta, htb: PHitTime;
178 begin
179 hta := @wgunHitTime[a];
180 htb := @wgunHitTime[b];
181 if (hta.distSq <> htb.distSq) then begin result := (hta.distSq < htb.distSq); exit; end;
182 if (hta.mon <> nil) then
183 begin
184 // a is monster
185 if (htb.mon = nil) then begin result := false; exit; end; // players first
186 result := (hta.mon.UID < htb.mon.UID); // why not?
187 end
188 else
189 begin
190 // a is player
191 if (htb.mon <> nil) then begin result := true; exit; end; // players first
192 result := (hta.plridx < htb.plridx); // why not?
193 end;
194 end;
197 procedure appendHitTimeMon (adistSq: Integer; amon: TMonster; ax, ay: Integer);
198 begin
199 if (wgunHitTimeUsed = Length(wgunHitTime)) then SetLength(wgunHitTime, wgunHitTimeUsed+128);
200 with wgunHitTime[wgunHitTimeUsed] do
201 begin
202 distSq := adistSq;
203 mon := amon;
204 plridx := -1;
205 x := ax;
206 y := ay;
207 end;
208 wgunHitHeap.insert(wgunHitTimeUsed);
209 Inc(wgunHitTimeUsed);
210 end;
213 procedure appendHitTimePlr (adistSq: Integer; aplridx: Integer; ax, ay: Integer);
214 begin
215 if (wgunHitTimeUsed = Length(wgunHitTime)) then SetLength(wgunHitTime, wgunHitTimeUsed+128);
216 with wgunHitTime[wgunHitTimeUsed] do
217 begin
218 distSq := adistSq;
219 mon := nil;
220 plridx := aplridx;
221 x := ax;
222 y := ay;
223 end;
224 wgunHitHeap.insert(wgunHitTimeUsed);
225 Inc(wgunHitTimeUsed);
226 end;
229 function FindShot(): DWORD;
230 var
231 i: Integer;
232 begin
233 if Shots <> nil then
234 for i := 0 to High(Shots) do
235 if Shots[i].ShotType = 0 then
236 begin
237 Result := i;
238 LastShotID := Result;
239 Exit;
240 end;
242 if Shots = nil then
243 begin
244 SetLength(Shots, 128);
245 Result := 0;
246 end
247 else
248 begin
249 Result := High(Shots) + 1;
250 SetLength(Shots, Length(Shots) + 128);
251 end;
252 LastShotID := Result;
253 end;
255 procedure CreateWaterMap();
256 var
257 WaterArray: Array of TWaterPanel;
258 a, b, c, m: Integer;
259 ok: Boolean;
260 begin
261 if gWater = nil then
262 Exit;
264 SetLength(WaterArray, Length(gWater));
266 for a := 0 to High(gWater) do
267 begin
268 WaterArray[a].X := gWater[a].X;
269 WaterArray[a].Y := gWater[a].Y;
270 WaterArray[a].Width := gWater[a].Width;
271 WaterArray[a].Height := gWater[a].Height;
272 WaterArray[a].Active := True;
273 end;
275 g_Game_SetLoadingText(_lc[I_LOAD_WATER_MAP], High(WaterArray), False);
277 for a := 0 to High(WaterArray) do
278 if WaterArray[a].Active then
279 begin
280 WaterArray[a].Active := False;
281 m := Length(WaterMap);
282 SetLength(WaterMap, m+1);
283 SetLength(WaterMap[m], 1);
284 WaterMap[m][0] := a;
285 ok := True;
287 while ok do
288 begin
289 ok := False;
290 for b := 0 to High(WaterArray) do
291 if WaterArray[b].Active then
292 for c := 0 to High(WaterMap[m]) do
293 if g_CollideAround(WaterArray[b].X,
294 WaterArray[b].Y,
295 WaterArray[b].Width,
296 WaterArray[b].Height,
297 WaterArray[WaterMap[m][c]].X,
298 WaterArray[WaterMap[m][c]].Y,
299 WaterArray[WaterMap[m][c]].Width,
300 WaterArray[WaterMap[m][c]].Height) then
301 begin
302 WaterArray[b].Active := False;
303 SetLength(WaterMap[m],
304 Length(WaterMap[m])+1);
305 WaterMap[m][High(WaterMap[m])] := b;
306 ok := True;
307 Break;
308 end;
309 end;
311 g_Game_StepLoading();
312 end;
314 WaterArray := nil;
315 end;
318 var
319 chkTrap_pl: array [0..256] of Integer;
320 chkTrap_mn: array [0..65535] of TMonster;
322 procedure CheckTrap(ID: DWORD; dm: Integer; t: Byte);
323 var
324 //a, b, c, d, i1, i2: Integer;
325 //chkTrap_pl, chkTrap_mn: WArray;
326 plaCount: Integer = 0;
327 mnaCount: Integer = 0;
328 frameId: DWord;
331 function monsWaterCheck (mon: TMonster): Boolean;
332 begin
333 result := false; // don't stop
334 if mon.alive and mon.Collide(gWater[WaterMap[a][c]]) and (not InWArray(monidx, chkTrap_mn)) and (i2 < 1023) then //FIXME
335 begin
336 i2 += 1;
337 chkTrap_mn[i2] := monidx;
338 end;
339 end;
342 function monsWaterCheck (mon: TMonster): Boolean;
343 begin
344 result := false; // don't stop
345 if (mon.trapCheckFrameId <> frameId) then
346 begin
347 mon.trapCheckFrameId := frameId;
348 chkTrap_mn[mnaCount] := mon;
349 Inc(mnaCount);
350 end;
351 end;
353 var
354 a, b, c, d, f: Integer;
355 pan: TPanel;
356 begin
357 if (gWater = nil) or (WaterMap = nil) then Exit;
359 frameId := g_Mons_getNewTrapFrameId();
361 //i1 := -1;
362 //i2 := -1;
364 //SetLength(chkTrap_pl, 1024);
365 //SetLength(chkTrap_mn, 1024);
366 //for d := 0 to 1023 do chkTrap_pl[d] := $FFFF;
367 //for d := 0 to 1023 do chkTrap_mn[d] := $FFFF;
369 for a := 0 to High(WaterMap) do
370 begin
371 for b := 0 to High(WaterMap[a]) do
372 begin
373 pan := gWater[WaterMap[a][b]];
374 if not g_Obj_Collide(pan.X, pan.Y, pan.Width, pan.Height, @Shots[ID].Obj) then continue;
376 for c := 0 to High(WaterMap[a]) do
377 begin
378 pan := gWater[WaterMap[a][c]];
379 for d := 0 to High(gPlayers) do
380 begin
381 if (gPlayers[d] <> nil) and (gPlayers[d].alive) then
382 begin
383 if gPlayers[d].Collide(pan) then
384 begin
385 f := 0;
386 while (f < plaCount) and (chkTrap_pl[f] <> d) do Inc(f);
387 if (f = plaCount) then
388 begin
389 chkTrap_pl[plaCount] := d;
390 Inc(plaCount);
391 if (plaCount = Length(chkTrap_pl)) then break;
392 end;
393 end;
394 end;
395 end;
397 //g_Mons_ForEach(monsWaterCheck);
398 g_Mons_ForEachAliveAt(pan.X, pan.Y, pan.Width, pan.Height, monsWaterCheck);
399 end;
401 for f := 0 to plaCount-1 do gPlayers[chkTrap_pl[f]].Damage(dm, Shots[ID].SpawnerUID, 0, 0, t);
402 for f := 0 to mnaCount-1 do chkTrap_mn[f].Damage(dm, 0, 0, Shots[ID].SpawnerUID, t);
403 end;
404 end;
406 //chkTrap_pl := nil;
407 //chkTrap_mn := nil;
408 end;
410 function HitMonster(m: TMonster; d: Integer; vx, vy: Integer; SpawnerUID: Word; t: Byte): Boolean;
411 var
412 tt, mt: Byte;
413 mon: TMonster;
414 begin
415 Result := False;
417 tt := g_GetUIDType(SpawnerUID);
418 if tt = UID_MONSTER then
419 begin
420 mon := g_Monsters_ByUID(SpawnerUID);
421 if mon <> nil then
422 mt := g_Monsters_ByUID(SpawnerUID).MonsterType
423 else
424 mt := 0;
425 end
426 else
427 mt := 0;
429 if m = nil then Exit;
430 if m.UID = SpawnerUID then
431 begin
432 // Ñàì ñåáÿ ìîæåò ðàíèòü òîëüêî ðàêåòîé è òîêîì:
433 if (t <> HIT_ROCKET) and (t <> HIT_ELECTRO) then
434 Exit;
435 // Êèáåð äåìîí è áî÷êà âîîáùå íå ìîãóò ñåáÿ ðàíèòü:
436 if (m.MonsterType = MONSTER_CYBER) or
437 (m.MonsterType = MONSTER_BARREL) then
438 begin
439 Result := True;
440 Exit;
441 end;
442 end;
444 if tt = UID_MONSTER then
445 begin
446 // Lost_Soul íå ìîæåò ðàíèòü Pain_Elemental'à:
447 if (mt = MONSTER_SOUL) and (m.MonsterType = MONSTER_PAIN) then
448 Exit;
450 // Îáà ìîíñòðà îäíîãî âèäà:
451 if mt = m.MonsterType then
452 case mt of
453 MONSTER_IMP, MONSTER_DEMON, MONSTER_BARON, MONSTER_KNIGHT, MONSTER_CACO,
454 MONSTER_SOUL, MONSTER_MANCUB, MONSTER_SKEL, MONSTER_FISH:
455 Exit; // Ýòè íå áüþò ñâîèõ
456 end;
457 end;
459 if g_Game_IsServer then
460 begin
461 if (t <> HIT_FLAME) or (m.FFireTime = 0) or (vx <> 0) or (vy <> 0) then
462 Result := m.Damage(d, vx, vy, SpawnerUID, t)
463 else
464 Result := (gLMSRespawn = LMS_RESPAWN_NONE); // don't hit monsters when it's warmup time
465 if t = HIT_FLAME then
466 m.CatchFire(SpawnerUID);
467 end
468 else
469 Result := (gLMSRespawn = LMS_RESPAWN_NONE); // don't hit monsters when it's warmup time
470 end;
473 function HitPlayer (p: TPlayer; d: Integer; vx, vy: Integer; SpawnerUID: Word; t: Byte): Boolean;
474 begin
475 result := False;
477 // Ñàì ñåáÿ ìîæåò ðàíèòü òîëüêî ðàêåòîé è òîêîì
478 if (p.UID = SpawnerUID) and (t <> HIT_ROCKET) and (t <> HIT_ELECTRO) then exit;
480 if g_Game_IsServer then
481 begin
482 if (t <> HIT_FLAME) or (p.FFireTime = 0) or (vx <> 0) or (vy <> 0) then p.Damage(d, SpawnerUID, vx, vy, t);
483 if (t = HIT_FLAME) then p.CatchFire(SpawnerUID);
484 end;
486 result := true;
487 end;
490 procedure g_Weapon_BFG9000(X, Y: Integer; SpawnerUID: Word);
492 function monsCheck (mon: TMonster): Boolean;
493 begin
494 result := false; // don't stop
495 if (mon.alive) and (mon.UID <> SpawnerUID) then
496 begin
497 with mon do
498 begin
499 if (g_PatchLength(X, Y, Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
500 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)) <= SHOT_BFG_RADIUS) and
501 g_TraceVector(X, Y, Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
502 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)) then
503 begin
504 if HitMonster(mon, 50, 0, 0, SpawnerUID, HIT_SOME) then mon.BFGHit();
505 end;
506 end;
507 end;
508 end;
510 var
511 i, h: Integer;
512 st: Byte;
513 pl: TPlayer;
514 b: Boolean;
515 begin
516 //g_Sound_PlayEx('SOUND_WEAPON_EXPLODEBFG', 255);
518 h := High(gCorpses);
520 if gAdvCorpses and (h <> -1) then
521 for i := 0 to h do
522 if (gCorpses[i] <> nil) and (gCorpses[i].State <> CORPSE_STATE_REMOVEME) then
523 with gCorpses[i] do
524 if (g_PatchLength(X, Y, Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
525 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)) <= SHOT_BFG_RADIUS) and
526 g_TraceVector(X, Y, Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
527 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)) then
528 begin
529 Damage(50, SpawnerUID, 0, 0);
530 g_Weapon_BFGHit(Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
531 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2));
532 end;
534 st := TEAM_NONE;
535 pl := g_Player_Get(SpawnerUID);
536 if pl <> nil then
537 st := pl.Team;
539 h := High(gPlayers);
541 if h <> -1 then
542 for i := 0 to h do
543 if (gPlayers[i] <> nil) and (gPlayers[i].alive) and (gPlayers[i].UID <> SpawnerUID) then
544 with gPlayers[i] do
545 if (g_PatchLength(X, Y, GameX+PLAYER_RECT.X+(PLAYER_RECT.Width div 2),
546 GameY+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)) <= SHOT_BFG_RADIUS) and
547 g_TraceVector(X, Y, GameX+PLAYER_RECT.X+(PLAYER_RECT.Width div 2),
548 GameY+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)) then
549 begin
550 if (st = TEAM_NONE) or (st <> gPlayers[i].Team) then
551 b := HitPlayer(gPlayers[i], 50, 0, 0, SpawnerUID, HIT_SOME)
552 else
553 b := HitPlayer(gPlayers[i], 25, 0, 0, SpawnerUID, HIT_SOME);
554 if b then
555 gPlayers[i].BFGHit();
556 end;
558 //FIXME
559 g_Mons_ForEachAlive(monsCheck);
560 end;
562 function g_Weapon_CreateShot(I: Integer; ShotType: Byte; Spawner, TargetUID: Word; X, Y, XV, YV: Integer): LongWord;
563 var
564 find_id: DWord;
565 FramesID: DWORD = 0;
566 begin
567 if I < 0 then
568 find_id := FindShot()
569 else
570 begin
571 find_id := I;
572 if Integer(find_id) >= High(Shots) then
573 SetLength(Shots, find_id + 64)
574 end;
576 case ShotType of
577 WEAPON_ROCKETLAUNCHER:
578 begin
579 with Shots[find_id] do
580 begin
581 g_Obj_Init(@Obj);
583 Obj.Rect.Width := SHOT_ROCKETLAUNCHER_WIDTH;
584 Obj.Rect.Height := SHOT_ROCKETLAUNCHER_HEIGHT;
586 Animation := nil;
587 Triggers := nil;
588 ShotType := WEAPON_ROCKETLAUNCHER;
589 g_Texture_Get('TEXTURE_WEAPON_ROCKET', TextureID);
590 end;
591 end;
593 WEAPON_PLASMA:
594 begin
595 with Shots[find_id] do
596 begin
597 g_Obj_Init(@Obj);
599 Obj.Rect.Width := SHOT_PLASMA_WIDTH;
600 Obj.Rect.Height := SHOT_PLASMA_HEIGHT;
602 Triggers := nil;
603 ShotType := WEAPON_PLASMA;
604 g_Frames_Get(FramesID, 'FRAMES_WEAPON_PLASMA');
605 Animation := TAnimation.Create(FramesID, True, 5);
606 end;
607 end;
609 WEAPON_BFG:
610 begin
611 with Shots[find_id] do
612 begin
613 g_Obj_Init(@Obj);
615 Obj.Rect.Width := SHOT_BFG_WIDTH;
616 Obj.Rect.Height := SHOT_BFG_HEIGHT;
618 Triggers := nil;
619 ShotType := WEAPON_BFG;
620 g_Frames_Get(FramesID, 'FRAMES_WEAPON_BFG');
621 Animation := TAnimation.Create(FramesID, True, 6);
622 end;
623 end;
625 WEAPON_FLAMETHROWER:
626 begin
627 with Shots[find_id] do
628 begin
629 g_Obj_Init(@Obj);
631 Obj.Rect.Width := SHOT_FLAME_WIDTH;
632 Obj.Rect.Height := SHOT_FLAME_HEIGHT;
634 Triggers := nil;
635 ShotType := WEAPON_FLAMETHROWER;
636 Animation := nil;
637 TextureID := 0;
638 g_Frames_Get(TextureID, 'FRAMES_FLAME');
639 end;
640 end;
642 WEAPON_IMP_FIRE:
643 begin
644 with Shots[find_id] do
645 begin
646 g_Obj_Init(@Obj);
648 Obj.Rect.Width := 16;
649 Obj.Rect.Height := 16;
651 Triggers := nil;
652 ShotType := WEAPON_IMP_FIRE;
653 g_Frames_Get(FramesID, 'FRAMES_WEAPON_IMPFIRE');
654 Animation := TAnimation.Create(FramesID, True, 4);
655 end;
656 end;
658 WEAPON_CACO_FIRE:
659 begin
660 with Shots[find_id] do
661 begin
662 g_Obj_Init(@Obj);
664 Obj.Rect.Width := 16;
665 Obj.Rect.Height := 16;
667 Triggers := nil;
668 ShotType := WEAPON_CACO_FIRE;
669 g_Frames_Get(FramesID, 'FRAMES_WEAPON_CACOFIRE');
670 Animation := TAnimation.Create(FramesID, True, 4);
671 end;
672 end;
674 WEAPON_MANCUB_FIRE:
675 begin
676 with Shots[find_id] do
677 begin
678 g_Obj_Init(@Obj);
680 Obj.Rect.Width := 32;
681 Obj.Rect.Height := 32;
683 Triggers := nil;
684 ShotType := WEAPON_MANCUB_FIRE;
685 g_Frames_Get(FramesID, 'FRAMES_WEAPON_MANCUBFIRE');
686 Animation := TAnimation.Create(FramesID, True, 4);
687 end;
688 end;
690 WEAPON_BARON_FIRE:
691 begin
692 with Shots[find_id] do
693 begin
694 g_Obj_Init(@Obj);
696 Obj.Rect.Width := 16;
697 Obj.Rect.Height := 16;
699 Triggers := nil;
700 ShotType := WEAPON_BARON_FIRE;
701 g_Frames_Get(FramesID, 'FRAMES_WEAPON_BARONFIRE');
702 Animation := TAnimation.Create(FramesID, True, 4);
703 end;
704 end;
706 WEAPON_BSP_FIRE:
707 begin
708 with Shots[find_id] do
709 begin
710 g_Obj_Init(@Obj);
712 Obj.Rect.Width := 16;
713 Obj.Rect.Height := 16;
715 Triggers := nil;
716 ShotType := WEAPON_BSP_FIRE;
717 g_Frames_Get(FramesID, 'FRAMES_WEAPON_BSPFIRE');
718 Animation := TAnimation.Create(FramesID, True, 4);
719 end;
720 end;
722 WEAPON_SKEL_FIRE:
723 begin
724 with Shots[find_id] do
725 begin
726 g_Obj_Init(@Obj);
728 Obj.Rect.Width := SHOT_SKELFIRE_WIDTH;
729 Obj.Rect.Height := SHOT_SKELFIRE_HEIGHT;
731 Triggers := nil;
732 ShotType := WEAPON_SKEL_FIRE;
733 target := TargetUID;
734 g_Frames_Get(FramesID, 'FRAMES_WEAPON_SKELFIRE');
735 Animation := TAnimation.Create(FramesID, True, 5);
736 end;
737 end;
738 end;
740 Shots[find_id].Obj.oldX := X;
741 Shots[find_id].Obj.oldY := Y;
742 Shots[find_id].Obj.X := X;
743 Shots[find_id].Obj.Y := Y;
744 Shots[find_id].Obj.Vel.X := XV;
745 Shots[find_id].Obj.Vel.Y := YV;
746 Shots[find_id].Obj.Accel.X := 0;
747 Shots[find_id].Obj.Accel.Y := 0;
748 Shots[find_id].SpawnerUID := Spawner;
749 if (ShotType = WEAPON_FLAMETHROWER) and (XV = 0) and (YV = 0) then
750 Shots[find_id].Stopped := 255
751 else
752 Shots[find_id].Stopped := 0;
753 Result := find_id;
754 end;
756 procedure throw(i, x, y, xd, yd, s: Integer);
757 var
758 a: Integer;
759 begin
760 yd := yd - y;
761 xd := xd - x;
763 a := Max(Abs(xd), Abs(yd));
764 if a = 0 then
765 a := 1;
767 Shots[i].Obj.oldX := x;
768 Shots[i].Obj.oldY := y;
769 Shots[i].Obj.X := x;
770 Shots[i].Obj.Y := y;
771 Shots[i].Obj.Vel.X := (xd*s) div a;
772 Shots[i].Obj.Vel.Y := (yd*s) div a;
773 Shots[i].Obj.Accel.X := 0;
774 Shots[i].Obj.Accel.Y := 0;
775 Shots[i].Stopped := 0;
776 if Shots[i].ShotType in [WEAPON_ROCKETLAUNCHER, WEAPON_BFG] then
777 Shots[i].Timeout := 900 // ~25 sec
778 else
779 begin
780 if Shots[i].ShotType = WEAPON_FLAMETHROWER then
781 Shots[i].Timeout := SHOT_FLAME_LIFETIME
782 else
783 Shots[i].Timeout := 550; // ~15 sec
784 end;
785 end;
787 function g_Weapon_Hit(obj: PObj; d: Integer; SpawnerUID: Word; t: Byte; HitCorpses: Boolean = True): Byte;
788 var
789 i, h: Integer;
791 function PlayerHit(Team: Byte = 0): Boolean;
792 var
793 i: Integer;
794 ChkTeam: Boolean;
795 p: TPlayer;
796 begin
797 Result := False;
798 h := High(gPlayers);
800 if h <> -1 then
801 for i := 0 to h do
802 if (gPlayers[i] <> nil) and gPlayers[i].alive and g_Obj_Collide(obj, @gPlayers[i].Obj) then
803 begin
804 ChkTeam := True;
805 if (Team > 0) and (g_GetUIDType(SpawnerUID) = UID_PLAYER) then
806 begin
807 p := g_Player_Get(SpawnerUID);
808 if p <> nil then
809 ChkTeam := (p.Team = gPlayers[i].Team) xor (Team = 2);
810 end;
811 if ChkTeam then
812 if HitPlayer(gPlayers[i], d, obj^.Vel.X, obj^.Vel.Y, SpawnerUID, t) then
813 begin
814 if t <> HIT_FLAME then
815 gPlayers[i].Push((obj^.Vel.X+obj^.Accel.X)*IfThen(t = HIT_BFG, 8, 1) div 4,
816 (obj^.Vel.Y+obj^.Accel.Y)*IfThen(t = HIT_BFG, 8, 1) div 4);
817 if t = HIT_BFG then
818 g_Game_DelayEvent(DE_BFGHIT, 1000, SpawnerUID);
819 Result := True;
820 break;
821 end;
822 end;
823 end;
826 function monsCheckHit (monidx: Integer; mon: TMonster): Boolean;
827 begin
828 result := false; // don't stop
829 if mon.alive and g_Obj_Collide(obj, @mon.Obj) then
830 begin
831 if HitMonster(mon, d, obj^.Vel.X, obj^.Vel.Y, SpawnerUID, t) then
832 begin
833 if (t <> HIT_FLAME) then
834 begin
835 mon.Push((obj^.Vel.X+obj^.Accel.X)*IfThen(t = HIT_BFG, 8, 1) div 4,
836 (obj^.Vel.Y+obj^.Accel.Y)*IfThen(t = HIT_BFG, 8, 1) div 4);
837 end;
838 result := True;
839 end;
840 end;
841 end;
844 function monsCheckHit (mon: TMonster): Boolean;
845 begin
846 result := false; // don't stop
847 if HitMonster(mon, d, obj.Vel.X, obj.Vel.Y, SpawnerUID, t) then
848 begin
849 if (t <> HIT_FLAME) then
850 begin
851 mon.Push((obj.Vel.X+obj.Accel.X)*IfThen(t = HIT_BFG, 8, 1) div 4,
852 (obj.Vel.Y+obj.Accel.Y)*IfThen(t = HIT_BFG, 8, 1) div 4);
853 end;
854 result := true;
855 end;
856 end;
858 function MonsterHit(): Boolean;
859 begin
860 //result := g_Mons_ForEach(monsCheckHit);
861 //FIXME: accelerate this!
862 result := g_Mons_ForEachAliveAt(obj.X+obj.Rect.X, obj.Y+obj.Rect.Y, obj.Rect.Width, obj.Rect.Height, monsCheckHit);
863 end;
865 begin
866 Result := 0;
868 if HitCorpses then
869 begin
870 h := High(gCorpses);
872 if gAdvCorpses and (h <> -1) then
873 for i := 0 to h do
874 if (gCorpses[i] <> nil) and (gCorpses[i].State <> CORPSE_STATE_REMOVEME) and
875 g_Obj_Collide(obj, @gCorpses[i].Obj) then
876 begin
877 // Ðàñïèëèâàåì òðóï:
878 gCorpses[i].Damage(d, SpawnerUID, (obj^.Vel.X+obj^.Accel.X) div 4,
879 (obj^.Vel.Y+obj^.Accel.Y) div 4);
880 Result := 1;
881 end;
882 end;
884 case gGameSettings.GameMode of
885 // Êàìïàíèÿ:
886 GM_COOP, GM_SINGLE:
887 begin
888 // Ñíà÷àëà áü¸ì ìîíñòðîâ, åñëè åñòü, ïîòîì ïûòàåìñÿ áèòü èãðîêîâ
889 if MonsterHit() then
890 begin
891 Result := 2;
892 Exit;
893 end;
895 // È â êîíöå èãðîêîâ, íî òîëüêî åñëè ïîëîæåíî
896 // (èëè ñíàðÿä îò ìîíñòðà, èëè friendlyfire, èëè friendly_hit_projectile)
897 if (g_GetUIDType(SpawnerUID) <> UID_PLAYER) or
898 LongBool(gGameSettings.Options and (GAME_OPTION_TEAMDAMAGE or GAME_OPTION_TEAMHITPROJECTILE)) then
899 begin
900 if PlayerHit() then
901 begin
902 Result := 1;
903 Exit;
904 end;
905 end;
906 end;
908 // Äåçìàò÷:
909 GM_DM:
910 begin
911 // Ñíà÷àëà áü¸ì èãðîêîâ, åñëè åñòü, ïîòîì ïûòàåìñÿ áèòü ìîíñòðîâ
912 if PlayerHit() then
913 begin
914 Result := 1;
915 Exit;
916 end;
918 if MonsterHit() then
919 begin
920 Result := 2;
921 Exit;
922 end;
923 end;
925 // Êîìàíäíûå:
926 GM_TDM, GM_CTF:
927 begin
928 // Ñíà÷àëà áü¸ì èãðîêîâ êîìàíäû ñîïåðíèêà
929 if PlayerHit(2) then
930 begin
931 Result := 1;
932 Exit;
933 end;
935 // Ïîòîì ìîíñòðîâ
936 if MonsterHit() then
937 begin
938 Result := 2;
939 Exit;
940 end;
942 // È â êîíöå ñâîèõ èãðîêîâ, íî òîëüêî åñëè ïîëîæåíî
943 // (èëè friendlyfire, èëè friendly_hit_projectile)
944 if LongBool(gGameSettings.Options and (GAME_OPTION_TEAMDAMAGE or GAME_OPTION_TEAMHITPROJECTILE)) then
945 begin
946 if PlayerHit(1) then
947 begin
948 Result := 1;
949 Exit;
950 end;
951 end;
952 end;
954 end;
955 end;
957 function g_Weapon_HitUID(UID: Word; d: Integer; SpawnerUID: Word; t: Byte): Boolean;
958 begin
959 Result := False;
961 case g_GetUIDType(UID) of
962 UID_PLAYER: Result := HitPlayer(g_Player_Get(UID), d, 0, 0, SpawnerUID, t);
963 UID_MONSTER: Result := HitMonster(g_Monsters_ByUID(UID), d, 0, 0, SpawnerUID, t);
964 else Exit;
965 end;
966 end;
968 function g_Weapon_Explode(X, Y: Integer; rad: Integer; SpawnerUID: Word): Boolean;
969 var
970 r: Integer; // squared radius
972 function monsExCheck (mon: TMonster): Boolean;
973 var
974 dx, dy, mm: Integer;
975 begin
976 result := false; // don't stop
977 begin
978 dx := mon.Obj.X+mon.Obj.Rect.X+(mon.Obj.Rect.Width div 2)-X;
979 dy := mon.Obj.Y+mon.Obj.Rect.Y+(mon.Obj.Rect.Height div 2)-Y;
981 if dx > 1000 then dx := 1000;
982 if dy > 1000 then dy := 1000;
984 if (dx*dx+dy*dy < r) then
985 begin
986 //m := PointToRect(X, Y, Obj.X+Obj.Rect.X, Obj.Y+Obj.Rect.Y, Obj.Rect.Width, Obj.Rect.Height);
987 //e_WriteLog(Format('explo monster #%d: x=%d; y=%d; rad=%d; dx=%d; dy=%d', [monidx, X, Y, rad, dx, dy]), MSG_NOTIFY);
989 mm := Max(abs(dx), abs(dy));
990 if mm = 0 then mm := 1;
992 if mon.alive then
993 begin
994 HitMonster(mon, ((mon.Obj.Rect.Width div 4)*10*(rad-mm)) div rad, 0, 0, SpawnerUID, HIT_ROCKET);
995 end;
997 mon.Push((dx*7) div mm, (dy*7) div mm);
998 end;
999 end;
1000 end;
1002 var
1003 i, h, dx, dy, m, mm: Integer;
1004 _angle: SmallInt;
1005 begin
1006 result := false;
1008 g_Triggers_PressC(X, Y, rad, SpawnerUID, ACTIVATE_SHOT);
1010 r := rad*rad;
1012 h := High(gPlayers);
1014 if h <> -1 then
1015 for i := 0 to h do
1016 if (gPlayers[i] <> nil) and gPlayers[i].alive then
1017 with gPlayers[i] do
1018 begin
1019 dx := Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2)-X;
1020 dy := Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)-Y;
1022 if dx > 1000 then dx := 1000;
1023 if dy > 1000 then dy := 1000;
1025 if dx*dx+dy*dy < r then
1026 begin
1027 //m := PointToRect(X, Y, GameX+PLAYER_RECT.X, GameY+PLAYER_RECT.Y,
1028 // PLAYER_RECT.Width, PLAYER_RECT.Height);
1030 mm := Max(abs(dx), abs(dy));
1031 if mm = 0 then mm := 1;
1033 HitPlayer(gPlayers[i], (100*(rad-mm)) div rad, (dx*10) div mm, (dy*10) div mm, SpawnerUID, HIT_ROCKET);
1034 gPlayers[i].Push((dx*7) div mm, (dy*7) div mm);
1035 end;
1036 end;
1038 //g_Mons_ForEach(monsExCheck);
1039 g_Mons_ForEachAt(X-(rad+32), Y-(rad+32), (rad+32)*2, (rad+32)*2, monsExCheck);
1041 h := High(gCorpses);
1043 if gAdvCorpses and (h <> -1) then
1044 for i := 0 to h do
1045 if (gCorpses[i] <> nil) and (gCorpses[i].State <> CORPSE_STATE_REMOVEME) then
1046 with gCorpses[i] do
1047 begin
1048 dx := Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2)-X;
1049 dy := Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)-Y;
1051 if dx > 1000 then dx := 1000;
1052 if dy > 1000 then dy := 1000;
1054 if dx*dx+dy*dy < r then
1055 begin
1056 m := PointToRect(X, Y, Obj.X+Obj.Rect.X, Obj.Y+Obj.Rect.Y,
1057 Obj.Rect.Width, Obj.Rect.Height);
1059 mm := Max(abs(dx), abs(dy));
1060 if mm = 0 then mm := 1;
1062 Damage(Round(100*(rad-m)/rad), SpawnerUID, (dx*10) div mm, (dy*10) div mm);
1063 end;
1064 end;
1066 h := High(gGibs);
1068 if gAdvGibs and (h <> -1) then
1069 for i := 0 to h do
1070 if gGibs[i].alive then
1071 with gGibs[i] do
1072 begin
1073 dx := Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2)-X;
1074 dy := Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)-Y;
1076 if dx > 1000 then dx := 1000;
1077 if dy > 1000 then dy := 1000;
1079 if dx*dx+dy*dy < r then
1080 begin
1081 m := PointToRect(X, Y, Obj.X+Obj.Rect.X, Obj.Y+Obj.Rect.Y,
1082 Obj.Rect.Width, Obj.Rect.Height);
1083 _angle := GetAngle(Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
1084 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2), X, Y);
1086 g_Obj_PushA(@Obj, Round(15*(rad-m)/rad), _angle);
1087 positionChanged(); // this updates spatial accelerators
1088 end;
1089 end;
1090 end;
1092 procedure g_Weapon_Init();
1093 begin
1094 CreateWaterMap();
1095 end;
1097 procedure g_Weapon_Free();
1098 var
1099 i: Integer;
1100 begin
1101 if Shots <> nil then
1102 begin
1103 for i := 0 to High(Shots) do
1104 if Shots[i].ShotType <> 0 then
1105 Shots[i].Animation.Free();
1107 Shots := nil;
1108 end;
1110 WaterMap := nil;
1111 end;
1113 procedure g_Weapon_LoadData();
1114 begin
1115 e_WriteLog('Loading weapons data...', TMsgType.Notify);
1117 g_Sound_CreateWADEx('SOUND_WEAPON_HITPUNCH', GameWAD+':SOUNDS\HITPUNCH');
1118 g_Sound_CreateWADEx('SOUND_WEAPON_MISSPUNCH', GameWAD+':SOUNDS\MISSPUNCH');
1119 g_Sound_CreateWADEx('SOUND_WEAPON_HITBERSERK', GameWAD+':SOUNDS\HITBERSERK');
1120 g_Sound_CreateWADEx('SOUND_WEAPON_MISSBERSERK', GameWAD+':SOUNDS\MISSBERSERK');
1121 g_Sound_CreateWADEx('SOUND_WEAPON_SELECTSAW', GameWAD+':SOUNDS\SELECTSAW');
1122 g_Sound_CreateWADEx('SOUND_WEAPON_IDLESAW', GameWAD+':SOUNDS\IDLESAW');
1123 g_Sound_CreateWADEx('SOUND_WEAPON_HITSAW', GameWAD+':SOUNDS\HITSAW');
1124 g_Sound_CreateWADEx('SOUND_WEAPON_FIRESHOTGUN2', GameWAD+':SOUNDS\FIRESHOTGUN2');
1125 g_Sound_CreateWADEx('SOUND_WEAPON_FIRESHOTGUN', GameWAD+':SOUNDS\FIRESHOTGUN');
1126 g_Sound_CreateWADEx('SOUND_WEAPON_FIRESAW', GameWAD+':SOUNDS\FIRESAW');
1127 g_Sound_CreateWADEx('SOUND_WEAPON_FIREROCKET', GameWAD+':SOUNDS\FIREROCKET');
1128 g_Sound_CreateWADEx('SOUND_WEAPON_FIREPLASMA', GameWAD+':SOUNDS\FIREPLASMA');
1129 g_Sound_CreateWADEx('SOUND_WEAPON_FIREPISTOL', GameWAD+':SOUNDS\FIREPISTOL');
1130 g_Sound_CreateWADEx('SOUND_WEAPON_FIRECGUN', GameWAD+':SOUNDS\FIRECGUN');
1131 g_Sound_CreateWADEx('SOUND_WEAPON_FIREBFG', GameWAD+':SOUNDS\FIREBFG');
1132 g_Sound_CreateWADEx('SOUND_FIRE', GameWAD+':SOUNDS\FIRE');
1133 g_Sound_CreateWADEx('SOUND_IGNITE', GameWAD+':SOUNDS\IGNITE');
1134 g_Sound_CreateWADEx('SOUND_WEAPON_STARTFIREBFG', GameWAD+':SOUNDS\STARTFIREBFG');
1135 g_Sound_CreateWADEx('SOUND_WEAPON_EXPLODEROCKET', GameWAD+':SOUNDS\EXPLODEROCKET');
1136 g_Sound_CreateWADEx('SOUND_WEAPON_EXPLODEBFG', GameWAD+':SOUNDS\EXPLODEBFG');
1137 g_Sound_CreateWADEx('SOUND_WEAPON_BFGWATER', GameWAD+':SOUNDS\BFGWATER');
1138 g_Sound_CreateWADEx('SOUND_WEAPON_EXPLODEPLASMA', GameWAD+':SOUNDS\EXPLODEPLASMA');
1139 g_Sound_CreateWADEx('SOUND_WEAPON_PLASMAWATER', GameWAD+':SOUNDS\PLASMAWATER');
1140 g_Sound_CreateWADEx('SOUND_WEAPON_FIREBALL', GameWAD+':SOUNDS\FIREBALL');
1141 g_Sound_CreateWADEx('SOUND_WEAPON_EXPLODEBALL', GameWAD+':SOUNDS\EXPLODEBALL');
1142 g_Sound_CreateWADEx('SOUND_WEAPON_FIREREV', GameWAD+':SOUNDS\FIREREV');
1143 g_Sound_CreateWADEx('SOUND_WEAPON_FLAMEON', GameWAD+':SOUNDS\STARTFLM');
1144 g_Sound_CreateWADEx('SOUND_WEAPON_FLAMEOFF', GameWAD+':SOUNDS\STOPFLM');
1145 g_Sound_CreateWADEx('SOUND_WEAPON_FLAMEWORK', GameWAD+':SOUNDS\WORKFLM');
1146 g_Sound_CreateWADEx('SOUND_PLAYER_JETFLY', GameWAD+':SOUNDS\WORKJETPACK');
1147 g_Sound_CreateWADEx('SOUND_PLAYER_JETON', GameWAD+':SOUNDS\STARTJETPACK');
1148 g_Sound_CreateWADEx('SOUND_PLAYER_JETOFF', GameWAD+':SOUNDS\STOPJETPACK');
1149 g_Sound_CreateWADEx('SOUND_PLAYER_CASING1', GameWAD+':SOUNDS\CASING1');
1150 g_Sound_CreateWADEx('SOUND_PLAYER_CASING2', GameWAD+':SOUNDS\CASING2');
1151 g_Sound_CreateWADEx('SOUND_PLAYER_SHELL1', GameWAD+':SOUNDS\SHELL1');
1152 g_Sound_CreateWADEx('SOUND_PLAYER_SHELL2', GameWAD+':SOUNDS\SHELL2');
1154 g_Texture_CreateWADEx('TEXTURE_WEAPON_ROCKET', GameWAD+':TEXTURES\BROCKET');
1155 g_Frames_CreateWAD(nil, 'FRAMES_WEAPON_SKELFIRE', GameWAD+':TEXTURES\BSKELFIRE', 64, 16, 2);
1156 g_Frames_CreateWAD(nil, 'FRAMES_WEAPON_BFG', GameWAD+':TEXTURES\BBFG', 64, 64, 2);
1157 g_Frames_CreateWAD(nil, 'FRAMES_WEAPON_PLASMA', GameWAD+':TEXTURES\BPLASMA', 16, 16, 2);
1158 g_Frames_CreateWAD(nil, 'FRAMES_WEAPON_IMPFIRE', GameWAD+':TEXTURES\BIMPFIRE', 16, 16, 2);
1159 g_Frames_CreateWAD(nil, 'FRAMES_WEAPON_BSPFIRE', GameWAD+':TEXTURES\BBSPFIRE', 16, 16, 2);
1160 g_Frames_CreateWAD(nil, 'FRAMES_WEAPON_CACOFIRE', GameWAD+':TEXTURES\BCACOFIRE', 16, 16, 2);
1161 g_Frames_CreateWAD(nil, 'FRAMES_WEAPON_BARONFIRE', GameWAD+':TEXTURES\BBARONFIRE', 64, 16, 2);
1162 g_Frames_CreateWAD(nil, 'FRAMES_WEAPON_MANCUBFIRE', GameWAD+':TEXTURES\BMANCUBFIRE', 64, 32, 2);
1163 g_Frames_CreateWAD(nil, 'FRAMES_EXPLODE_ROCKET', GameWAD+':TEXTURES\EROCKET', 128, 128, 6);
1164 g_Frames_CreateWAD(nil, 'FRAMES_EXPLODE_SKELFIRE', GameWAD+':TEXTURES\ESKELFIRE', 64, 64, 3);
1165 g_Frames_CreateWAD(nil, 'FRAMES_EXPLODE_BFG', GameWAD+':TEXTURES\EBFG', 128, 128, 6);
1166 g_Frames_CreateWAD(nil, 'FRAMES_EXPLODE_IMPFIRE', GameWAD+':TEXTURES\EIMPFIRE', 64, 64, 3);
1167 g_Frames_CreateWAD(nil, 'FRAMES_BFGHIT', GameWAD+':TEXTURES\BFGHIT', 64, 64, 4);
1168 g_Frames_CreateWAD(nil, 'FRAMES_FIRE', GameWAD+':TEXTURES\FIRE', 64, 128, 8);
1169 g_Frames_CreateWAD(nil, 'FRAMES_FLAME', GameWAD+':TEXTURES\FLAME', 32, 32, 11);
1170 g_Frames_CreateWAD(nil, 'FRAMES_EXPLODE_PLASMA', GameWAD+':TEXTURES\EPLASMA', 32, 32, 4, True);
1171 g_Frames_CreateWAD(nil, 'FRAMES_EXPLODE_BSPFIRE', GameWAD+':TEXTURES\EBSPFIRE', 32, 32, 5);
1172 g_Frames_CreateWAD(nil, 'FRAMES_EXPLODE_CACOFIRE', GameWAD+':TEXTURES\ECACOFIRE', 64, 64, 3);
1173 g_Frames_CreateWAD(nil, 'FRAMES_EXPLODE_BARONFIRE', GameWAD+':TEXTURES\EBARONFIRE', 64, 64, 3);
1174 g_Frames_CreateWAD(nil, 'FRAMES_SMOKE', GameWAD+':TEXTURES\SMOKE', 32, 32, 10, False);
1176 g_Texture_CreateWADEx('TEXTURE_SHELL_BULLET', GameWAD+':TEXTURES\EBULLET');
1177 g_Texture_CreateWADEx('TEXTURE_SHELL_SHELL', GameWAD+':TEXTURES\ESHELL');
1179 //wgunMonHash := hashNewIntInt();
1180 wgunHitHeap := TBinaryHeapHitTimes.Create();
1181 end;
1183 procedure g_Weapon_FreeData();
1184 begin
1185 e_WriteLog('Releasing weapons data...', TMsgType.Notify);
1187 g_Sound_Delete('SOUND_WEAPON_HITPUNCH');
1188 g_Sound_Delete('SOUND_WEAPON_MISSPUNCH');
1189 g_Sound_Delete('SOUND_WEAPON_HITBERSERK');
1190 g_Sound_Delete('SOUND_WEAPON_MISSBERSERK');
1191 g_Sound_Delete('SOUND_WEAPON_SELECTSAW');
1192 g_Sound_Delete('SOUND_WEAPON_IDLESAW');
1193 g_Sound_Delete('SOUND_WEAPON_HITSAW');
1194 g_Sound_Delete('SOUND_WEAPON_FIRESHOTGUN2');
1195 g_Sound_Delete('SOUND_WEAPON_FIRESHOTGUN');
1196 g_Sound_Delete('SOUND_WEAPON_FIRESAW');
1197 g_Sound_Delete('SOUND_WEAPON_FIREROCKET');
1198 g_Sound_Delete('SOUND_WEAPON_FIREPLASMA');
1199 g_Sound_Delete('SOUND_WEAPON_FIREPISTOL');
1200 g_Sound_Delete('SOUND_WEAPON_FIRECGUN');
1201 g_Sound_Delete('SOUND_WEAPON_FIREBFG');
1202 g_Sound_Delete('SOUND_FIRE');
1203 g_Sound_Delete('SOUND_IGNITE');
1204 g_Sound_Delete('SOUND_WEAPON_STARTFIREBFG');
1205 g_Sound_Delete('SOUND_WEAPON_EXPLODEROCKET');
1206 g_Sound_Delete('SOUND_WEAPON_EXPLODEBFG');
1207 g_Sound_Delete('SOUND_WEAPON_BFGWATER');
1208 g_Sound_Delete('SOUND_WEAPON_EXPLODEPLASMA');
1209 g_Sound_Delete('SOUND_WEAPON_PLASMAWATER');
1210 g_Sound_Delete('SOUND_WEAPON_FIREBALL');
1211 g_Sound_Delete('SOUND_WEAPON_EXPLODEBALL');
1212 g_Sound_Delete('SOUND_WEAPON_FIREREV');
1213 g_Sound_Delete('SOUND_WEAPON_FLAMEON');
1214 g_Sound_Delete('SOUND_WEAPON_FLAMEOFF');
1215 g_Sound_Delete('SOUND_WEAPON_FLAMEWORK');
1216 g_Sound_Delete('SOUND_PLAYER_JETFLY');
1217 g_Sound_Delete('SOUND_PLAYER_JETON');
1218 g_Sound_Delete('SOUND_PLAYER_JETOFF');
1219 g_Sound_Delete('SOUND_PLAYER_CASING1');
1220 g_Sound_Delete('SOUND_PLAYER_CASING2');
1221 g_Sound_Delete('SOUND_PLAYER_SHELL1');
1222 g_Sound_Delete('SOUND_PLAYER_SHELL2');
1224 g_Texture_Delete('TEXTURE_WEAPON_ROCKET');
1225 g_Frames_DeleteByName('FRAMES_WEAPON_BFG');
1226 g_Frames_DeleteByName('FRAMES_WEAPON_PLASMA');
1227 g_Frames_DeleteByName('FRAMES_WEAPON_IMPFIRE');
1228 g_Frames_DeleteByName('FRAMES_WEAPON_BSPFIRE');
1229 g_Frames_DeleteByName('FRAMES_WEAPON_CACOFIRE');
1230 g_Frames_DeleteByName('FRAMES_WEAPON_MANCUBFIRE');
1231 g_Frames_DeleteByName('FRAMES_EXPLODE_ROCKET');
1232 g_Frames_DeleteByName('FRAMES_EXPLODE_BFG');
1233 g_Frames_DeleteByName('FRAMES_EXPLODE_IMPFIRE');
1234 g_Frames_DeleteByName('FRAMES_BFGHIT');
1235 g_Frames_DeleteByName('FRAMES_FIRE');
1236 g_Frames_DeleteByName('FRAMES_EXPLODE_PLASMA');
1237 g_Frames_DeleteByName('FRAMES_EXPLODE_BSPFIRE');
1238 g_Frames_DeleteByName('FRAMES_EXPLODE_CACOFIRE');
1239 g_Frames_DeleteByName('FRAMES_SMOKE');
1240 g_Frames_DeleteByName('FRAMES_WEAPON_BARONFIRE');
1241 g_Frames_DeleteByName('FRAMES_EXPLODE_BARONFIRE');
1242 end;
1245 function GunHitPlayer (X, Y: Integer; vx, vy: Integer; dmg: Integer; SpawnerUID: Word; AllowPush: Boolean): Boolean;
1246 var
1247 i: Integer;
1248 begin
1249 result := false;
1250 for i := 0 to High(gPlayers) do
1251 begin
1252 if (gPlayers[i] <> nil) and gPlayers[i].alive and gPlayers[i].Collide(X, Y) then
1253 begin
1254 if HitPlayer(gPlayers[i], dmg, vx*10, vy*10-3, SpawnerUID, HIT_SOME) then
1255 begin
1256 if AllowPush then gPlayers[i].Push(vx, vy);
1257 result := true;
1258 end;
1259 end;
1260 end;
1261 end;
1264 function GunHit (X, Y: Integer; vx, vy: Integer; dmg: Integer; SpawnerUID: Word; AllowPush: Boolean): Byte;
1266 function monsCheck (mon: TMonster): Boolean;
1267 begin
1268 result := false; // don't stop
1269 if HitMonster(mon, dmg, vx*10, vy*10-3, SpawnerUID, HIT_SOME) then
1270 begin
1271 if AllowPush then mon.Push(vx, vy);
1272 result := true;
1273 end;
1274 end;
1276 begin
1277 result := 0;
1278 if GunHitPlayer(X, Y, vx, vy, dmg, SpawnerUID, AllowPush) then result := 1
1279 else if g_Mons_ForEachAliveAt(X, Y, 1, 1, monsCheck) then result := 2;
1280 end;
1283 (*
1284 procedure g_Weapon_gunOld(const x, y, xd, yd, v, dmg: Integer; SpawnerUID: Word; CheckTrigger: Boolean);
1285 var
1286 a: Integer;
1287 x2, y2: Integer;
1288 dx, dy: Integer;
1289 xe, ye: Integer;
1290 xi, yi: Integer;
1291 s, c: Extended;
1292 //vx, vy: Integer;
1293 xx, yy, d: Integer;
1294 i: Integer;
1295 t1, _collide: Boolean;
1296 w, h: Word;
1297 {$IF DEFINED(D2F_DEBUG)}
1298 stt: UInt64;
1299 showTime: Boolean = true;
1300 {$ENDIF}
1301 begin
1302 a := GetAngle(x, y, xd, yd)+180;
1304 SinCos(DegToRad(-a), s, c);
1306 if Abs(s) < 0.01 then s := 0;
1307 if Abs(c) < 0.01 then c := 0;
1309 x2 := x+Round(c*gMapInfo.Width);
1310 y2 := y+Round(s*gMapInfo.Width);
1312 t1 := gWalls <> nil;
1313 _collide := False;
1314 w := gMapInfo.Width;
1315 h := gMapInfo.Height;
1317 xe := 0;
1318 ye := 0;
1319 dx := x2-x;
1320 dy := y2-y;
1322 if (xd = 0) and (yd = 0) then Exit;
1324 if dx > 0 then xi := 1 else if dx < 0 then xi := -1 else xi := 0;
1325 if dy > 0 then yi := 1 else if dy < 0 then yi := -1 else yi := 0;
1327 dx := Abs(dx);
1328 dy := Abs(dy);
1330 if dx > dy then d := dx else d := dy;
1332 //blood vel, for Monster.Damage()
1333 //vx := (dx*10 div d)*xi;
1334 //vy := (dy*10 div d)*yi;
1336 {$IF DEFINED(D2F_DEBUG)}
1337 stt := getTimeMicro();
1338 {$ENDIF}
1340 xx := x;
1341 yy := y;
1343 for i := 1 to d do
1344 begin
1345 xe := xe+dx;
1346 ye := ye+dy;
1348 if xe > d then
1349 begin
1350 xe := xe-d;
1351 xx := xx+xi;
1352 end;
1354 if ye > d then
1355 begin
1356 ye := ye-d;
1357 yy := yy+yi;
1358 end;
1360 if (yy > h) or (yy < 0) then Break;
1361 if (xx > w) or (xx < 0) then Break;
1363 if t1 then
1364 if ByteBool(gCollideMap[yy, xx] and MARK_BLOCKED) then
1365 begin
1366 _collide := True;
1367 {$IF DEFINED(D2F_DEBUG)}
1368 stt := getTimeMicro()-stt;
1369 e_WriteLog(Format('*** old trace time: %u microseconds', [LongWord(stt)]), MSG_NOTIFY);
1370 showTime := false;
1371 {$ENDIF}
1372 g_GFX_Spark(xx-xi, yy-yi, 2+Random(2), 180+a, 0, 0);
1373 if g_Game_IsServer and g_Game_IsNet then
1374 MH_SEND_Effect(xx-xi, yy-yi, 180+a, NET_GFX_SPARK);
1375 end;
1377 if not _collide then
1378 begin
1379 _collide := GunHit(xx, yy, xi*v, yi*v, dmg, SpawnerUID, v <> 0) <> 0;
1380 end;
1382 if _collide then Break;
1383 end;
1385 {$IF DEFINED(D2F_DEBUG)}
1386 if showTime then
1387 begin
1388 stt := getTimeMicro()-stt;
1389 e_WriteLog(Format('*** old trace time: %u microseconds', [LongWord(stt)]), MSG_NOTIFY);
1390 end;
1391 {$ENDIF}
1393 if CheckTrigger and g_Game_IsServer then
1394 g_Triggers_PressL(X, Y, xx-xi, yy-yi, SpawnerUID, ACTIVATE_SHOT);
1395 end;
1396 *)
1399 //!!!FIXME!!!
1400 procedure g_Weapon_gun (const x, y, xd, yd, v, indmg: Integer; SpawnerUID: Word; CheckTrigger: Boolean);
1401 var
1402 x0, y0: Integer;
1403 x2, y2: Integer;
1404 xi, yi: Integer;
1405 wallDistSq: Integer = $3fffffff;
1406 spawnerPlr: TPlayer = nil;
1407 dmg: Integer;
1409 function doPlayerHit (idx: Integer; hx, hy: Integer): Boolean;
1410 begin
1411 result := false;
1412 if (idx < 0) or (idx > High(gPlayers)) then exit;
1413 if (gPlayers[idx] = nil) or not gPlayers[idx].alive then exit;
1414 if (spawnerPlr <> nil) then
1415 begin
1416 if ((gGameSettings.Options and (GAME_OPTION_TEAMHITTRACE or GAME_OPTION_TEAMDAMAGE)) = 0) and
1417 (spawnerPlr.Team <> TEAM_NONE) and (spawnerPlr.Team = gPlayers[idx].Team) then
1418 begin
1419 if (spawnerPlr <> gPlayers[idx]) and ((gGameSettings.Options and GAME_OPTION_TEAMABSORBDAMAGE) = 0) then
1420 dmg := Max(1, dmg div 2);
1421 exit;
1422 end;
1423 end;
1424 result := HitPlayer(gPlayers[idx], dmg, (xi*v)*10, (yi*v)*10-3, SpawnerUID, HIT_SOME);
1425 if result and (v <> 0) then gPlayers[idx].Push((xi*v), (yi*v));
1426 {$IF DEFINED(D2F_DEBUG)}
1427 //if result then e_WriteLog(Format(' PLAYER #%d HIT', [idx]), MSG_NOTIFY);
1428 {$ENDIF}
1429 end;
1431 function doMonsterHit (mon: TMonster; hx, hy: Integer): Boolean;
1432 begin
1433 result := false;
1434 if (mon = nil) then exit;
1435 result := HitMonster(mon, dmg, (xi*v)*10, (yi*v)*10-3, SpawnerUID, HIT_SOME);
1436 if result and (v <> 0) then mon.Push((xi*v), (yi*v));
1437 {$IF DEFINED(D2F_DEBUG)}
1438 //if result then e_WriteLog(Format(' MONSTER #%u HIT', [LongWord(mon.UID)]), MSG_NOTIFY);
1439 {$ENDIF}
1440 end;
1442 // collect players along hitray
1443 // return `true` if instant hit was detected
1444 function playerPossibleHit (): Boolean;
1445 var
1446 i: Integer;
1447 px, py, pw, ph: Integer;
1448 inx, iny: Integer;
1449 distSq: Integer;
1450 plr: TPlayer;
1451 begin
1452 result := false;
1453 for i := 0 to High(gPlayers) do
1454 begin
1455 plr := gPlayers[i];
1456 if (plr <> nil) and plr.alive then
1457 begin
1458 plr.getMapBox(px, py, pw, ph);
1459 if lineAABBIntersects(x, y, x2, y2, px, py, pw, ph, inx, iny) then
1460 begin
1461 distSq := distanceSq(x, y, inx, iny);
1462 if (distSq = 0) then
1463 begin
1464 // contains
1465 if doPlayerHit(i, x, y) then begin result := true; exit; end;
1466 end
1467 else if (distSq < wallDistSq) then
1468 begin
1469 appendHitTimePlr(distSq, i, inx, iny);
1470 end;
1471 end;
1472 end;
1473 end;
1474 end;
1476 procedure sqchecker (mon: TMonster);
1477 var
1478 mx, my, mw, mh: Integer;
1479 inx, iny: Integer;
1480 distSq: Integer;
1481 begin
1482 mon.getMapBox(mx, my, mw, mh);
1483 if lineAABBIntersects(x0, y0, x2, y2, mx, my, mw, mh, inx, iny) then
1484 begin
1485 distSq := distanceSq(x0, y0, inx, iny);
1486 if (distSq < wallDistSq) then appendHitTimeMon(distSq, mon, inx, iny);
1487 end;
1488 end;
1490 var
1491 a: Integer;
1492 dx, dy: Integer;
1493 xe, ye: Integer;
1494 s, c: Extended;
1495 i: Integer;
1496 wallHitFlag: Boolean = false;
1497 wallHitX: Integer = 0;
1498 wallHitY: Integer = 0;
1499 didHit: Boolean = false;
1500 {$IF DEFINED(D2F_DEBUG)}
1501 stt: UInt64;
1502 {$ENDIF}
1503 mit: PMonster;
1504 it: TMonsterGrid.Iter;
1505 begin
1506 (*
1507 if not gwep_debug_fast_trace then
1508 begin
1509 g_Weapon_gunOld(x, y, xd, yd, v, dmg, SpawnerUID, CheckTrigger);
1510 exit;
1511 end;
1512 *)
1514 if (xd = 0) and (yd = 0) then exit;
1516 if (g_GetUIDType(SpawnerUID) = UID_PLAYER) then
1517 spawnerPlr := g_Player_Get(SpawnerUID);
1519 dmg := indmg;
1521 //wgunMonHash.reset(); //FIXME: clear hash on level change
1522 wgunHitHeap.clear();
1523 wgunHitTimeUsed := 0;
1525 a := GetAngle(x, y, xd, yd)+180;
1527 SinCos(DegToRad(-a), s, c);
1529 if Abs(s) < 0.01 then s := 0;
1530 if Abs(c) < 0.01 then c := 0;
1532 x0 := x;
1533 y0 := y;
1534 x2 := x+Round(c*gMapInfo.Width);
1535 y2 := y+Round(s*gMapInfo.Width);
1537 dx := x2-x;
1538 dy := y2-y;
1540 if (dx > 0) then xi := 1 else if (dx < 0) then xi := -1 else xi := 0;
1541 if (dy > 0) then yi := 1 else if (dy < 0) then yi := -1 else yi := 0;
1543 {$IF DEFINED(D2F_DEBUG)}
1544 e_WriteLog(Format('GUN TRACE: (%d,%d) to (%d,%d)', [x, y, x2, y2]), TMsgType.Notify);
1545 stt := getTimeMicro();
1546 {$ENDIF}
1548 wallHitFlag := (g_Map_traceToNearestWall(x, y, x2, y2, @wallHitX, @wallHitY) <> nil);
1549 if wallHitFlag then
1550 begin
1551 x2 := wallHitX;
1552 y2 := wallHitY;
1553 wallDistSq := distanceSq(x, y, wallHitX, wallHitY);
1554 end
1555 else
1556 begin
1557 wallHitX := x2;
1558 wallHitY := y2;
1559 end;
1561 if playerPossibleHit() then exit; // instant hit
1563 // collect monsters
1564 //g_Mons_AlongLine(x, y, x2, y2, sqchecker);
1566 it := monsGrid.forEachAlongLine(x, y, x2, y2, -1);
1567 for mit in it do sqchecker(mit^);
1568 it.release();
1570 // here, we collected all monsters and players in `wgunHitHeap` and `wgunHitTime`
1571 // also, if `wallWasHit` is `true`, then `wallHitX` and `wallHitY` contains spark coords
1572 while (wgunHitHeap.count > 0) do
1573 begin
1574 // has some entities to check, do it
1575 i := wgunHitHeap.front;
1576 wgunHitHeap.popFront();
1577 // hitpoint
1578 xe := wgunHitTime[i].x;
1579 ye := wgunHitTime[i].y;
1580 // check if it is not behind the wall
1581 if (wgunHitTime[i].mon <> nil) then
1582 begin
1583 didHit := doMonsterHit(wgunHitTime[i].mon, xe, ye);
1584 end
1585 else
1586 begin
1587 didHit := doPlayerHit(wgunHitTime[i].plridx, xe, ye);
1588 end;
1589 if didHit then
1590 begin
1591 // need new coords for trigger
1592 wallHitX := xe;
1593 wallHitY := ye;
1594 wallHitFlag := false; // no sparks
1595 break;
1596 end;
1597 end;
1599 // need sparks?
1600 if wallHitFlag then
1601 begin
1602 {$IF DEFINED(D2F_DEBUG)}
1603 stt := getTimeMicro()-stt;
1604 e_WriteLog(Format('*** new trace time: %u microseconds', [LongWord(stt)]), TMsgType.Notify);
1605 {$ENDIF}
1606 g_GFX_Spark(wallHitX, wallHitY, 2+Random(2), 180+a, 0, 0);
1607 if g_Game_IsServer and g_Game_IsNet then MH_SEND_Effect(wallHitX, wallHitY, 180+a, NET_GFX_SPARK);
1608 end
1609 else
1610 begin
1611 {$IF DEFINED(D2F_DEBUG)}
1612 stt := getTimeMicro()-stt;
1613 e_WriteLog(Format('*** new trace time: %u microseconds', [LongWord(stt)]), TMsgType.Notify);
1614 {$ENDIF}
1615 end;
1617 if CheckTrigger and g_Game_IsServer then g_Triggers_PressL(X, Y, wallHitX, wallHitY, SpawnerUID, ACTIVATE_SHOT);
1618 end;
1621 procedure g_Weapon_punch(x, y: Integer; d, SpawnerUID: Word);
1622 var
1623 obj: TObj;
1624 begin
1625 obj.X := X;
1626 obj.Y := Y;
1627 obj.rect.X := 0;
1628 obj.rect.Y := 0;
1629 obj.rect.Width := 39;
1630 obj.rect.Height := 52;
1631 obj.Vel.X := 0;
1632 obj.Vel.Y := 0;
1633 obj.Accel.X := 0;
1634 obj.Accel.Y := 0;
1636 if g_Weapon_Hit(@obj, d, SpawnerUID, HIT_SOME) <> 0 then
1637 g_Sound_PlayExAt('SOUND_WEAPON_HITPUNCH', x, y)
1638 else
1639 g_Sound_PlayExAt('SOUND_WEAPON_MISSPUNCH', x, y);
1640 end;
1642 function g_Weapon_chainsaw(x, y: Integer; d, SpawnerUID: Word): Integer;
1643 var
1644 obj: TObj;
1645 begin
1646 obj.X := X;
1647 obj.Y := Y;
1648 obj.rect.X := 0;
1649 obj.rect.Y := 0;
1650 obj.rect.Width := 32;
1651 obj.rect.Height := 52;
1652 obj.Vel.X := 0;
1653 obj.Vel.Y := 0;
1654 obj.Accel.X := 0;
1655 obj.Accel.Y := 0;
1657 Result := g_Weapon_Hit(@obj, d, SpawnerUID, HIT_SOME);
1658 end;
1660 procedure g_Weapon_rocket(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1661 Silent: Boolean = False; compat: Boolean = true);
1662 var
1663 find_id: DWORD;
1664 dx, dy: Integer;
1665 begin
1666 if WID < 0 then
1667 find_id := FindShot()
1668 else
1669 begin
1670 find_id := WID;
1671 if Integer(find_id) >= High(Shots) then
1672 SetLength(Shots, find_id + 64)
1673 end;
1675 with Shots[find_id] do
1676 begin
1677 g_Obj_Init(@Obj);
1679 Obj.Rect.Width := SHOT_ROCKETLAUNCHER_WIDTH;
1680 Obj.Rect.Height := SHOT_ROCKETLAUNCHER_HEIGHT;
1682 if compat then
1683 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1684 else
1685 dx := -(Obj.Rect.Width div 2);
1686 dy := -(Obj.Rect.Height div 2);
1688 ShotType := WEAPON_ROCKETLAUNCHER;
1689 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 12);
1691 Animation := nil;
1692 triggers := nil;
1693 g_Texture_Get('TEXTURE_WEAPON_ROCKET', TextureID);
1694 end;
1696 Shots[find_id].SpawnerUID := SpawnerUID;
1698 if not Silent then
1699 g_Sound_PlayExAt('SOUND_WEAPON_FIREROCKET', x, y);
1700 end;
1702 procedure g_Weapon_revf(x, y, xd, yd: Integer; SpawnerUID, TargetUID: Word;
1703 WID: Integer = -1; Silent: Boolean = False);
1704 var
1705 find_id, FramesID: DWORD;
1706 dx, dy: Integer;
1707 begin
1708 if WID < 0 then
1709 find_id := FindShot()
1710 else
1711 begin
1712 find_id := WID;
1713 if Integer(find_id) >= High(Shots) then
1714 SetLength(Shots, find_id + 64)
1715 end;
1717 with Shots[find_id] do
1718 begin
1719 g_Obj_Init(@Obj);
1721 Obj.Rect.Width := SHOT_SKELFIRE_WIDTH;
1722 Obj.Rect.Height := SHOT_SKELFIRE_HEIGHT;
1724 dx := -(Obj.Rect.Width div 2);
1725 dy := -(Obj.Rect.Height div 2);
1727 ShotType := WEAPON_SKEL_FIRE;
1728 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 12);
1730 triggers := nil;
1731 target := TargetUID;
1732 g_Frames_Get(FramesID, 'FRAMES_WEAPON_SKELFIRE');
1733 Animation := TAnimation.Create(FramesID, True, 5);
1734 end;
1736 Shots[find_id].SpawnerUID := SpawnerUID;
1738 if not Silent then
1739 g_Sound_PlayExAt('SOUND_WEAPON_FIREREV', x, y);
1740 end;
1742 procedure g_Weapon_plasma(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1743 Silent: Boolean = False; compat: Boolean = true);
1744 var
1745 find_id, FramesID: DWORD;
1746 dx, dy: Integer;
1747 begin
1748 if WID < 0 then
1749 find_id := FindShot()
1750 else
1751 begin
1752 find_id := WID;
1753 if Integer(find_id) >= High(Shots) then
1754 SetLength(Shots, find_id + 64);
1755 end;
1757 with Shots[find_id] do
1758 begin
1759 g_Obj_Init(@Obj);
1761 Obj.Rect.Width := SHOT_PLASMA_WIDTH;
1762 Obj.Rect.Height := SHOT_PLASMA_HEIGHT;
1764 if compat then
1765 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1766 else
1767 dx := -(Obj.Rect.Width div 2);
1768 dy := -(Obj.Rect.Height div 2);
1770 ShotType := WEAPON_PLASMA;
1771 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1773 triggers := nil;
1774 g_Frames_Get(FramesID, 'FRAMES_WEAPON_PLASMA');
1775 Animation := TAnimation.Create(FramesID, True, 5);
1776 end;
1778 Shots[find_id].SpawnerUID := SpawnerUID;
1780 if not Silent then
1781 g_Sound_PlayExAt('SOUND_WEAPON_FIREPLASMA', x, y);
1782 end;
1784 procedure g_Weapon_flame(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1785 Silent: Boolean = False; compat: Boolean = true);
1786 var
1787 find_id: DWORD;
1788 dx, dy: Integer;
1789 begin
1790 if WID < 0 then
1791 find_id := FindShot()
1792 else
1793 begin
1794 find_id := WID;
1795 if Integer(find_id) >= High(Shots) then
1796 SetLength(Shots, find_id + 64);
1797 end;
1799 with Shots[find_id] do
1800 begin
1801 g_Obj_Init(@Obj);
1803 Obj.Rect.Width := SHOT_FLAME_WIDTH;
1804 Obj.Rect.Height := SHOT_FLAME_HEIGHT;
1806 if compat then
1807 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1808 else
1809 dx := -(Obj.Rect.Width div 2);
1810 dy := -(Obj.Rect.Height div 2);
1812 ShotType := WEAPON_FLAMETHROWER;
1813 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1815 triggers := nil;
1816 Animation := nil;
1817 TextureID := 0;
1818 g_Frames_Get(TextureID, 'FRAMES_FLAME');
1819 end;
1821 Shots[find_id].SpawnerUID := SpawnerUID;
1823 // if not Silent then
1824 // g_Sound_PlayExAt('SOUND_WEAPON_FIREPLASMA', x, y);
1825 end;
1827 procedure g_Weapon_ball1(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1828 Silent: Boolean = False; compat: Boolean = true);
1829 var
1830 find_id, FramesID: DWORD;
1831 dx, dy: Integer;
1832 begin
1833 if WID < 0 then
1834 find_id := FindShot()
1835 else
1836 begin
1837 find_id := WID;
1838 if Integer(find_id) >= High(Shots) then
1839 SetLength(Shots, find_id + 64)
1840 end;
1842 with Shots[find_id] do
1843 begin
1844 g_Obj_Init(@Obj);
1846 Obj.Rect.Width := 16;
1847 Obj.Rect.Height := 16;
1849 if compat then
1850 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1851 else
1852 dx := -(Obj.Rect.Width div 2);
1853 dy := -(Obj.Rect.Height div 2);
1855 ShotType := WEAPON_IMP_FIRE;
1856 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1858 triggers := nil;
1859 g_Frames_Get(FramesID, 'FRAMES_WEAPON_IMPFIRE');
1860 Animation := TAnimation.Create(FramesID, True, 4);
1861 end;
1863 Shots[find_id].SpawnerUID := SpawnerUID;
1865 if not Silent then
1866 g_Sound_PlayExAt('SOUND_WEAPON_FIREBALL', x, y);
1867 end;
1869 procedure g_Weapon_ball2(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1870 Silent: Boolean = False; compat: Boolean = true);
1871 var
1872 find_id, FramesID: DWORD;
1873 dx, dy: Integer;
1874 begin
1875 if WID < 0 then
1876 find_id := FindShot()
1877 else
1878 begin
1879 find_id := WID;
1880 if Integer(find_id) >= High(Shots) then
1881 SetLength(Shots, find_id + 64)
1882 end;
1884 with Shots[find_id] do
1885 begin
1886 g_Obj_Init(@Obj);
1888 Obj.Rect.Width := 16;
1889 Obj.Rect.Height := 16;
1891 if compat then
1892 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1893 else
1894 dx := -(Obj.Rect.Width div 2);
1895 dy := -(Obj.Rect.Height div 2);
1897 ShotType := WEAPON_CACO_FIRE;
1898 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1900 triggers := nil;
1901 g_Frames_Get(FramesID, 'FRAMES_WEAPON_CACOFIRE');
1902 Animation := TAnimation.Create(FramesID, True, 4);
1903 end;
1905 Shots[find_id].SpawnerUID := SpawnerUID;
1907 if not Silent then
1908 g_Sound_PlayExAt('SOUND_WEAPON_FIREBALL', x, y);
1909 end;
1911 procedure g_Weapon_ball7(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1912 Silent: Boolean = False; compat: Boolean = true);
1913 var
1914 find_id, FramesID: DWORD;
1915 dx, dy: Integer;
1916 begin
1917 if WID < 0 then
1918 find_id := FindShot()
1919 else
1920 begin
1921 find_id := WID;
1922 if Integer(find_id) >= High(Shots) then
1923 SetLength(Shots, find_id + 64)
1924 end;
1926 with Shots[find_id] do
1927 begin
1928 g_Obj_Init(@Obj);
1930 Obj.Rect.Width := 16;
1931 Obj.Rect.Height := 16;
1933 if compat then
1934 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1935 else
1936 dx := -(Obj.Rect.Width div 2);
1937 dy := -(Obj.Rect.Height div 2);
1939 ShotType := WEAPON_BARON_FIRE;
1940 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1942 triggers := nil;
1943 g_Frames_Get(FramesID, 'FRAMES_WEAPON_BARONFIRE');
1944 Animation := TAnimation.Create(FramesID, True, 4);
1945 end;
1947 Shots[find_id].SpawnerUID := SpawnerUID;
1949 if not Silent then
1950 g_Sound_PlayExAt('SOUND_WEAPON_FIREBALL', x, y);
1951 end;
1953 procedure g_Weapon_aplasma(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1954 Silent: Boolean = False; compat: Boolean = true);
1955 var
1956 find_id, FramesID: DWORD;
1957 dx, dy: Integer;
1958 begin
1959 if WID < 0 then
1960 find_id := FindShot()
1961 else
1962 begin
1963 find_id := WID;
1964 if Integer(find_id) >= High(Shots) then
1965 SetLength(Shots, find_id + 64)
1966 end;
1968 with Shots[find_id] do
1969 begin
1970 g_Obj_Init(@Obj);
1972 Obj.Rect.Width := 16;
1973 Obj.Rect.Height := 16;
1975 if compat then
1976 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1977 else
1978 dx := -(Obj.Rect.Width div 2);
1979 dy := -(Obj.Rect.Height div 2);
1981 ShotType := WEAPON_BSP_FIRE;
1982 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1984 triggers := nil;
1986 g_Frames_Get(FramesID, 'FRAMES_WEAPON_BSPFIRE');
1987 Animation := TAnimation.Create(FramesID, True, 4);
1988 end;
1990 Shots[find_id].SpawnerUID := SpawnerUID;
1992 if not Silent then
1993 g_Sound_PlayExAt('SOUND_WEAPON_FIREPLASMA', x, y);
1994 end;
1996 procedure g_Weapon_manfire(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1997 Silent: Boolean = False; compat: Boolean = true);
1998 var
1999 find_id, FramesID: DWORD;
2000 dx, dy: Integer;
2001 begin
2002 if WID < 0 then
2003 find_id := FindShot()
2004 else
2005 begin
2006 find_id := WID;
2007 if Integer(find_id) >= High(Shots) then
2008 SetLength(Shots, find_id + 64)
2009 end;
2011 with Shots[find_id] do
2012 begin
2013 g_Obj_Init(@Obj);
2015 Obj.Rect.Width := 32;
2016 Obj.Rect.Height := 32;
2018 if compat then
2019 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
2020 else
2021 dx := -(Obj.Rect.Width div 2);
2022 dy := -(Obj.Rect.Height div 2);
2024 ShotType := WEAPON_MANCUB_FIRE;
2025 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
2027 triggers := nil;
2029 g_Frames_Get(FramesID, 'FRAMES_WEAPON_MANCUBFIRE');
2030 Animation := TAnimation.Create(FramesID, True, 4);
2031 end;
2033 Shots[find_id].SpawnerUID := SpawnerUID;
2035 if not Silent then
2036 g_Sound_PlayExAt('SOUND_WEAPON_FIREBALL', x, y);
2037 end;
2039 procedure g_Weapon_bfgshot(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
2040 Silent: Boolean = False; compat: Boolean = true);
2041 var
2042 find_id, FramesID: DWORD;
2043 dx, dy: Integer;
2044 begin
2045 if WID < 0 then
2046 find_id := FindShot()
2047 else
2048 begin
2049 find_id := WID;
2050 if Integer(find_id) >= High(Shots) then
2051 SetLength(Shots, find_id + 64)
2052 end;
2054 with Shots[find_id] do
2055 begin
2056 g_Obj_Init(@Obj);
2058 Obj.Rect.Width := SHOT_BFG_WIDTH;
2059 Obj.Rect.Height := SHOT_BFG_HEIGHT;
2061 if compat then
2062 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
2063 else
2064 dx := -(Obj.Rect.Width div 2);
2065 dy := -(Obj.Rect.Height div 2);
2067 ShotType := WEAPON_BFG;
2068 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
2070 triggers := nil;
2071 g_Frames_Get(FramesID, 'FRAMES_WEAPON_BFG');
2072 Animation := TAnimation.Create(FramesID, True, 6);
2073 end;
2075 Shots[find_id].SpawnerUID := SpawnerUID;
2077 if not Silent then
2078 g_Sound_PlayExAt('SOUND_WEAPON_FIREBFG', x, y);
2079 end;
2081 procedure g_Weapon_bfghit(x, y: Integer);
2082 var
2083 ID: DWORD;
2084 Anim: TAnimation;
2085 begin
2086 if g_Frames_Get(ID, 'FRAMES_BFGHIT') then
2087 begin
2088 Anim := TAnimation.Create(ID, False, 4);
2089 g_GFX_OnceAnim(x-32, y-32, Anim);
2090 Anim.Free();
2091 end;
2092 end;
2094 procedure g_Weapon_pistol(x, y, xd, yd: Integer; SpawnerUID: Word;
2095 Silent: Boolean = False);
2096 begin
2097 if not Silent then
2098 g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', x, y);
2100 g_Weapon_gun(x, y, xd, yd, 1, 3, SpawnerUID, True);
2101 if gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF] then
2102 begin
2103 if ABS(x-xd) >= ABS(y-yd) then
2104 begin
2105 g_Weapon_gun(x, y+1, xd, yd+1, 1, 3, SpawnerUID, False);
2106 g_Weapon_gun(x, y-1, xd, yd-1, 1, 2, SpawnerUID, False);
2107 end
2108 else
2109 begin
2110 g_Weapon_gun(x+1, y, xd+1, yd, 1, 3, SpawnerUID, False);
2111 g_Weapon_gun(x-1, y, xd-1, yd, 1, 2, SpawnerUID, False);
2112 end;
2113 end;
2114 end;
2116 procedure g_Weapon_mgun(x, y, xd, yd: Integer; SpawnerUID: Word;
2117 Silent: Boolean = False);
2118 begin
2119 if not Silent then
2120 if gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', x, y);
2122 g_Weapon_gun(x, y, xd, yd, 1, 3, SpawnerUID, True);
2123 if (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF]) and
2124 (g_GetUIDType(SpawnerUID) = UID_PLAYER) then
2125 begin
2126 if ABS(x-xd) >= ABS(y-yd) then
2127 begin
2128 g_Weapon_gun(x, y+1, xd, yd+1, 1, 2, SpawnerUID, False);
2129 g_Weapon_gun(x, y-1, xd, yd-1, 1, 2, SpawnerUID, False);
2130 end
2131 else
2132 begin
2133 g_Weapon_gun(x+1, y, xd+1, yd, 1, 2, SpawnerUID, False);
2134 g_Weapon_gun(x-1, y, xd-1, yd, 1, 2, SpawnerUID, False);
2135 end;
2136 end;
2137 end;
2139 procedure g_Weapon_shotgun(x, y, xd, yd: Integer; SpawnerUID: Word;
2140 Silent: Boolean = False);
2141 var
2142 i, j, k: Integer;
2143 begin
2144 if not Silent then
2145 if gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', x, y);
2147 for i := 0 to 9 do
2148 begin
2149 j := 0; k := 0;
2150 if ABS(x-xd) >= ABS(y-yd) then j := Random(17) - 8 else k := Random(17) - 8; // -8 .. 8
2151 g_Weapon_gun(x+k, y+j, xd+k, yd+j, IfThen(i mod 2 <> 0, 1, 0), 3, SpawnerUID, i=0);
2152 end;
2153 end;
2155 procedure g_Weapon_dshotgun(x, y, xd, yd: Integer; SpawnerUID: Word;
2156 Silent: Boolean = False);
2157 var
2158 a, i, j, k: Integer;
2159 begin
2160 if not Silent then
2161 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN2', x, y);
2163 if gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF] then a := 25 else a := 20;
2164 for i := 0 to a do
2165 begin
2166 j := 0; k := 0;
2167 if ABS(x-xd) >= ABS(y-yd) then j := Random(41) - 20 else k := Random(41) - 20; // -20 .. 20
2168 g_Weapon_gun(x+k, y+j, xd+k, yd+j, IfThen(i mod 3 <> 0, 0, 1), 3, SpawnerUID, i=0);
2169 end;
2170 end;
2172 procedure g_Weapon_PreUpdate();
2173 var
2174 i: Integer;
2175 begin
2176 if Shots = nil then Exit;
2177 for i := 0 to High(Shots) do
2178 if Shots[i].ShotType <> 0 then
2179 begin
2180 Shots[i].Obj.oldX := Shots[i].Obj.X;
2181 Shots[i].Obj.oldY := Shots[i].Obj.Y;
2182 end;
2183 end;
2185 procedure g_Weapon_Update();
2186 var
2187 i, a, h, cx, cy, oldvx, oldvy, tf: Integer;
2188 _id: DWORD;
2189 Anim: TAnimation;
2190 t: DWArray;
2191 st: Word;
2192 s: String;
2193 o: TObj;
2194 spl: Boolean;
2195 Loud: Boolean;
2196 tcx, tcy: Integer;
2197 begin
2198 if Shots = nil then
2199 Exit;
2201 for i := 0 to High(Shots) do
2202 begin
2203 if Shots[i].ShotType = 0 then
2204 Continue;
2206 Loud := True;
2208 with Shots[i] do
2209 begin
2210 Timeout := Timeout - 1;
2211 oldvx := Obj.Vel.X;
2212 oldvy := Obj.Vel.Y;
2213 // Àêòèâèðîâàòü òðèããåðû ïî ïóòè (êðîìå óæå àêòèâèðîâàííûõ):
2214 if (Stopped = 0) and g_Game_IsServer then
2215 t := g_Triggers_PressR(Obj.X, Obj.Y, Obj.Rect.Width, Obj.Rect.Height,
2216 SpawnerUID, ACTIVATE_SHOT, triggers)
2217 else
2218 t := nil;
2220 if t <> nil then
2221 begin
2222 // Ïîïîëíÿåì ñïèñîê àêòèâèðîâàííûõ òðèããåðîâ:
2223 if triggers = nil then
2224 triggers := t
2225 else
2226 begin
2227 h := High(t);
2229 for a := 0 to h do
2230 if not InDWArray(t[a], triggers) then
2231 begin
2232 SetLength(triggers, Length(triggers)+1);
2233 triggers[High(triggers)] := t[a];
2234 end;
2235 end;
2236 end;
2238 // Àíèìàöèÿ ñíàðÿäà:
2239 if Animation <> nil then
2240 Animation.Update();
2242 // Äâèæåíèå:
2243 spl := (ShotType <> WEAPON_PLASMA) and
2244 (ShotType <> WEAPON_BFG) and
2245 (ShotType <> WEAPON_BSP_FIRE) and
2246 (ShotType <> WEAPON_FLAMETHROWER);
2248 if Stopped = 0 then
2249 begin
2250 st := g_Obj_Move_Projectile(@Obj, False, spl);
2251 end
2252 else
2253 begin
2254 st := 0;
2255 end;
2256 positionChanged(); // this updates spatial accelerators
2258 if WordBool(st and MOVE_FALLOUT) or (Obj.X < -1000) or
2259 (Obj.X > gMapInfo.Width+1000) or (Obj.Y < -1000) then
2260 begin
2261 // Íà êëèåíòå ñêîðåå âñåãî è òàê óæå âûïàë.
2262 ShotType := 0;
2263 Animation.Free();
2264 Continue;
2265 end;
2267 cx := Obj.X + (Obj.Rect.Width div 2);
2268 cy := Obj.Y + (Obj.Rect.Height div 2);
2270 case ShotType of
2271 WEAPON_ROCKETLAUNCHER, WEAPON_SKEL_FIRE: // Ðàêåòû è ñíàðÿäû Ñêåëåòà
2272 begin
2273 // Âûëåòåëà èç âîäû:
2274 if WordBool(st and MOVE_HITAIR) then
2275 g_Obj_SetSpeed(@Obj, 12);
2277 // Â âîäå øëåéô - ïóçûðè, â âîçäóõå øëåéô - äûì:
2278 if WordBool(st and MOVE_INWATER) then
2279 begin
2280 g_GFX_Bubbles(cx, cy, 1+Random(3), 16, 16);
2281 if Random(2) = 0
2282 then g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', cx, cy)
2283 else g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', cx, cy);
2284 end
2285 else if g_Frames_Get(_id, 'FRAMES_SMOKE') then
2286 begin
2287 Anim := TAnimation.Create(_id, False, 3);
2288 Anim.Alpha := 150;
2289 g_GFX_OnceAnim(Obj.X-14+Random(9), cy-20+Random(9),
2290 Anim, ONCEANIM_SMOKE);
2291 Anim.Free();
2292 end;
2294 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2295 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL)) or
2296 (g_Weapon_Hit(@Obj, 10, SpawnerUID, HIT_SOME, False) <> 0) or
2297 (Timeout < 1) then
2298 begin
2299 Obj.Vel.X := 0;
2300 Obj.Vel.Y := 0;
2302 g_Weapon_Explode(cx, cy, 60, SpawnerUID);
2304 if ShotType = WEAPON_SKEL_FIRE then
2305 begin // Âçðûâ ñíàðÿäà Ñêåëåòà
2306 if g_Frames_Get(TextureID, 'FRAMES_EXPLODE_SKELFIRE') then
2307 begin
2308 Anim := TAnimation.Create(TextureID, False, 8);
2309 Anim.Blending := False;
2310 g_GFX_OnceAnim((Obj.X+32)-58, (Obj.Y+8)-36, Anim);
2311 g_DynLightExplosion((Obj.X+32), (Obj.Y+8), 64, 1, 0, 0);
2312 Anim.Free();
2313 end;
2314 end
2315 else
2316 begin // Âçðûâ Ðàêåòû
2317 if g_Frames_Get(TextureID, 'FRAMES_EXPLODE_ROCKET') then
2318 begin
2319 Anim := TAnimation.Create(TextureID, False, 6);
2320 Anim.Blending := False;
2321 g_GFX_OnceAnim(cx-64, cy-64, Anim);
2322 g_DynLightExplosion(cx, cy, 64, 1, 0, 0);
2323 Anim.Free();
2324 end;
2325 end;
2327 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEROCKET', Obj.X, Obj.Y);
2329 ShotType := 0;
2330 end;
2332 if ShotType = WEAPON_SKEL_FIRE then
2333 begin // Ñàìîíàâîäêà ñíàðÿäà Ñêåëåòà:
2334 if GetPos(target, @o) then
2335 throw(i, Obj.X, Obj.Y,
2336 o.X+o.Rect.X+(o.Rect.Width div 2)+o.Vel.X+o.Accel.X,
2337 o.Y+o.Rect.Y+(o.Rect.Height div 2)+o.Vel.Y+o.Accel.Y,
2338 12);
2339 end;
2340 end;
2342 WEAPON_PLASMA, WEAPON_BSP_FIRE: // Ïëàçìà, ïëàçìà Àðàõíàòðîíà
2343 begin
2344 // Ïîïàëà â âîäó - ýëåêòðîøîê ïî âîäå:
2345 if WordBool(st and (MOVE_INWATER or MOVE_HITWATER)) then
2346 begin
2347 g_Sound_PlayExAt('SOUND_WEAPON_PLASMAWATER', Obj.X, Obj.Y);
2348 if g_Game_IsServer then CheckTrap(i, 10, HIT_ELECTRO);
2349 ShotType := 0;
2350 Continue;
2351 end;
2353 // Âåëè÷èíà óðîíà:
2354 if (ShotType = WEAPON_PLASMA) and
2355 (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF]) then
2356 a := 10
2357 else
2358 a := 5;
2360 if ShotType = WEAPON_BSP_FIRE then
2361 a := 10;
2363 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2364 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL)) or
2365 (g_Weapon_Hit(@Obj, a, SpawnerUID, HIT_SOME, False) <> 0) or
2366 (Timeout < 1) then
2367 begin
2368 if ShotType = WEAPON_PLASMA then
2369 s := 'FRAMES_EXPLODE_PLASMA'
2370 else
2371 s := 'FRAMES_EXPLODE_BSPFIRE';
2373 // Âçðûâ Ïëàçìû:
2374 if g_Frames_Get(TextureID, s) then
2375 begin
2376 Anim := TAnimation.Create(TextureID, False, 3);
2377 Anim.Blending := False;
2378 g_GFX_OnceAnim(cx-16, cy-16, Anim);
2379 Anim.Free();
2380 g_DynLightExplosion(cx, cy, 32, 0, 0.5, 0.5);
2381 end;
2383 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEPLASMA', Obj.X, Obj.Y);
2385 ShotType := 0;
2386 end;
2387 end;
2389 WEAPON_FLAMETHROWER: // Îãíåìåò
2390 begin
2391 // Ñî âðåìåíåì óìèðàåò
2392 if (Timeout < 1) then
2393 begin
2394 ShotType := 0;
2395 Continue;
2396 end;
2397 // Ïîä âîäîé òîæå
2398 if WordBool(st and (MOVE_HITWATER or MOVE_INWATER)) then
2399 begin
2400 if WordBool(st and MOVE_HITWATER) then
2401 begin
2402 if g_Frames_Get(_id, 'FRAMES_SMOKE') then
2403 begin
2404 Anim := TAnimation.Create(_id, False, 3);
2405 Anim.Alpha := 0;
2406 tcx := Random(8);
2407 tcy := Random(8);
2408 g_GFX_OnceAnim(cx-4+tcx-(Anim.Width div 2),
2409 cy-4+tcy-(Anim.Height div 2),
2410 Anim, ONCEANIM_SMOKE);
2411 Anim.Free();
2412 end;
2413 end
2414 else
2415 begin
2416 g_GFX_Bubbles(cx, cy, 1+Random(3), 16, 16);
2417 if Random(2) = 0
2418 then g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', cx, cy)
2419 else g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', cx, cy);
2420 end;
2421 ShotType := 0;
2422 Continue;
2423 end;
2425 // Ãðàâèòàöèÿ
2426 if Stopped = 0 then
2427 Obj.Accel.Y := Obj.Accel.Y + 1;
2428 // Ïîïàëè â ñòåíó èëè â âîäó:
2429 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL or MOVE_HITWATER)) then
2430 begin
2431 // Ïðèëèïàåì:
2432 Obj.Vel.X := 0;
2433 Obj.Vel.Y := 0;
2434 Obj.Accel.Y := 0;
2435 if WordBool(st and MOVE_HITWALL) then
2436 Stopped := MOVE_HITWALL
2437 else if WordBool(st and MOVE_HITLAND) then
2438 Stopped := MOVE_HITLAND
2439 else if WordBool(st and MOVE_HITCEIL) then
2440 Stopped := MOVE_HITCEIL;
2441 end;
2443 a := IfThen(Stopped = 0, 10, 1);
2444 // Åñëè â êîãî-òî ïîïàëè
2445 if g_Weapon_Hit(@Obj, a, SpawnerUID, HIT_FLAME, False) <> 0 then
2446 begin
2447 // HIT_FLAME ñàì ïîäîææåò
2448 // Åñëè â ïîëåòå ïîïàëè, èñ÷åçàåì
2449 if Stopped = 0 then
2450 ShotType := 0;
2451 end;
2453 if Stopped = 0 then
2454 tf := 2
2455 else
2456 tf := 3;
2458 if (gTime mod LongWord(tf) = 0) then
2459 begin
2460 Anim := TAnimation.Create(TextureID, False, 2 + Random(2));
2461 Anim.Alpha := 0;
2462 case Stopped of
2463 MOVE_HITWALL: begin tcx := cx-4+Random(8); tcy := cy-12+Random(24); end;
2464 MOVE_HITLAND: begin tcx := cx-12+Random(24); tcy := cy-10+Random(8); end;
2465 MOVE_HITCEIL: begin tcx := cx-12+Random(24); tcy := cy+6+Random(8); end;
2466 else begin tcx := cx-4+Random(8); tcy := cy-4+Random(8); end;
2467 end;
2468 g_GFX_OnceAnim(tcx-(Anim.Width div 2), tcy-(Anim.Height div 2), Anim, ONCEANIM_SMOKE);
2469 Anim.Free();
2470 //g_DynLightExplosion(tcx, tcy, 1, 1, 0.8, 0.3);
2471 end;
2472 end;
2474 WEAPON_BFG: // BFG
2475 begin
2476 // Ïîïàëà â âîäó - ýëåêòðîøîê ïî âîäå:
2477 if WordBool(st and (MOVE_INWATER or MOVE_HITWATER)) then
2478 begin
2479 g_Sound_PlayExAt('SOUND_WEAPON_BFGWATER', Obj.X, Obj.Y);
2480 if g_Game_IsServer then CheckTrap(i, 1000, HIT_ELECTRO);
2481 ShotType := 0;
2482 Continue;
2483 end;
2485 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2486 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL)) or
2487 (g_Weapon_Hit(@Obj, SHOT_BFG_DAMAGE, SpawnerUID, HIT_BFG, False) <> 0) or
2488 (Timeout < 1) then
2489 begin
2490 // Ëó÷è BFG:
2491 if g_Game_IsServer then g_Weapon_BFG9000(cx, cy, SpawnerUID);
2493 // Âçðûâ BFG:
2494 if g_Frames_Get(TextureID, 'FRAMES_EXPLODE_BFG') then
2495 begin
2496 Anim := TAnimation.Create(TextureID, False, 6);
2497 Anim.Blending := False;
2498 g_GFX_OnceAnim(cx-64, cy-64, Anim);
2499 Anim.Free();
2500 g_DynLightExplosion(cx, cy, 96, 0, 1, 0);
2501 end;
2503 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBFG', Obj.X, Obj.Y);
2505 ShotType := 0;
2506 end;
2507 end;
2509 WEAPON_IMP_FIRE, WEAPON_CACO_FIRE, WEAPON_BARON_FIRE: // Âûñòðåëû Áåñà, Êàêîäåìîíà Ðûöàðÿ/Áàðîíà àäà
2510 begin
2511 // Âûëåòåë èç âîäû:
2512 if WordBool(st and MOVE_HITAIR) then
2513 g_Obj_SetSpeed(@Obj, 16);
2515 // Âåëè÷èíà óðîíà:
2516 if ShotType = WEAPON_IMP_FIRE then
2517 a := 5
2518 else
2519 if ShotType = WEAPON_CACO_FIRE then
2520 a := 20
2521 else
2522 a := 40;
2524 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2525 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL)) or
2526 (g_Weapon_Hit(@Obj, a, SpawnerUID, HIT_SOME) <> 0) or
2527 (Timeout < 1) then
2528 begin
2529 if ShotType = WEAPON_IMP_FIRE then
2530 s := 'FRAMES_EXPLODE_IMPFIRE'
2531 else
2532 if ShotType = WEAPON_CACO_FIRE then
2533 s := 'FRAMES_EXPLODE_CACOFIRE'
2534 else
2535 s := 'FRAMES_EXPLODE_BARONFIRE';
2537 // Âçðûâ:
2538 if g_Frames_Get(TextureID, s) then
2539 begin
2540 Anim := TAnimation.Create(TextureID, False, 6);
2541 Anim.Blending := False;
2542 g_GFX_OnceAnim(cx-32, cy-32, Anim);
2543 Anim.Free();
2544 end;
2546 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBALL', Obj.X, Obj.Y);
2548 ShotType := 0;
2549 end;
2550 end;
2552 WEAPON_MANCUB_FIRE: // Âûñòðåë Ìàíêóáóñà
2553 begin
2554 // Âûëåòåë èç âîäû:
2555 if WordBool(st and MOVE_HITAIR) then
2556 g_Obj_SetSpeed(@Obj, 16);
2558 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2559 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL)) or
2560 (g_Weapon_Hit(@Obj, 40, SpawnerUID, HIT_SOME, False) <> 0) or
2561 (Timeout < 1) then
2562 begin
2563 // Âçðûâ:
2564 if g_Frames_Get(TextureID, 'FRAMES_EXPLODE_ROCKET') then
2565 begin
2566 Anim := TAnimation.Create(TextureID, False, 6);
2567 Anim.Blending := False;
2568 g_GFX_OnceAnim(cx-64, cy-64, Anim);
2569 Anim.Free();
2570 end;
2572 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBALL', Obj.X, Obj.Y);
2574 ShotType := 0;
2575 end;
2576 end;
2577 end; // case ShotType of...
2579 // Åñëè ñíàðÿäà óæå íåò, óäàëÿåì àíèìàöèþ:
2580 if (ShotType = 0) then
2581 begin
2582 if gGameSettings.GameType = GT_SERVER then
2583 MH_SEND_DeleteShot(i, Obj.X, Obj.Y, Loud);
2584 if Animation <> nil then
2585 begin
2586 Animation.Free();
2587 Animation := nil;
2588 end;
2589 end
2590 else if (ShotType <> WEAPON_FLAMETHROWER) and ((oldvx <> Obj.Vel.X) or (oldvy <> Obj.Vel.Y)) then
2591 if gGameSettings.GameType = GT_SERVER then
2592 MH_SEND_UpdateShot(i);
2593 end;
2594 end;
2595 end;
2597 function g_Weapon_Danger(UID: Word; X, Y: Integer; Width, Height: Word; Time: Byte): Boolean;
2598 var
2599 a: Integer;
2600 begin
2601 Result := False;
2603 if Shots = nil then
2604 Exit;
2606 for a := 0 to High(Shots) do
2607 if (Shots[a].ShotType <> 0) and (Shots[a].SpawnerUID <> UID) then
2608 if ((Shots[a].Obj.Vel.Y = 0) and (Shots[a].Obj.Vel.X > 0) and (Shots[a].Obj.X < X)) or
2609 (Shots[a].Obj.Vel.Y = 0) and (Shots[a].Obj.Vel.X < 0) and (Shots[a].Obj.X > X) then
2610 if (Abs(X-Shots[a].Obj.X) < Abs(Shots[a].Obj.Vel.X*Time)) and
2611 g_Collide(X, Y, Width, Height, X, Shots[a].Obj.Y,
2612 Shots[a].Obj.Rect.Width, Shots[a].Obj.Rect.Height) and
2613 g_TraceVector(X, Y, Shots[a].Obj.X, Shots[a].Obj.Y) then
2614 begin
2615 Result := True;
2616 Exit;
2617 end;
2618 end;
2620 procedure g_Weapon_SaveState (st: TStream);
2621 var
2622 count, i, j: Integer;
2623 begin
2624 // Ñ÷èòàåì êîëè÷åñòâî ñóùåñòâóþùèõ ñíàðÿäîâ
2625 count := 0;
2626 for i := 0 to High(Shots) do if (Shots[i].ShotType <> 0) then Inc(count);
2628 // Êîëè÷åñòâî ñíàðÿäîâ
2629 utils.WriteInt(st, count);
2631 if (count = 0) then exit;
2633 for i := 0 to High(Shots) do
2634 begin
2635 if Shots[i].ShotType <> 0 then
2636 begin
2637 // Ñèãíàòóðà ñíàðÿäà
2638 utils.writeSign(st, 'SHOT');
2639 utils.writeInt(st, Byte(0)); // version
2640 // Òèï ñíàðÿäà
2641 utils.writeInt(st, Byte(Shots[i].ShotType));
2642 // Öåëü
2643 utils.writeInt(st, Word(Shots[i].Target));
2644 // UID ñòðåëÿâøåãî
2645 utils.writeInt(st, Word(Shots[i].SpawnerUID));
2646 // Ðàçìåð ïîëÿ Triggers
2647 utils.writeInt(st, Integer(Length(Shots[i].Triggers)));
2648 // Òðèããåðû, àêòèâèðîâàííûå âûñòðåëîì
2649 for j := 0 to Length(Shots[i].Triggers)-1 do utils.writeInt(st, LongWord(Shots[i].Triggers[j]));
2650 // Îáúåêò ñíàðÿäà
2651 Obj_SaveState(st, @Shots[i].Obj);
2652 // Êîñòûëèíà åáàíàÿ
2653 utils.writeInt(st, Byte(Shots[i].Stopped));
2654 end;
2655 end;
2656 end;
2658 procedure g_Weapon_LoadState (st: TStream);
2659 var
2660 count, tc, i, j: Integer;
2661 dw: LongWord;
2662 begin
2663 if (st = nil) then exit;
2665 // Êîëè÷åñòâî ñíàðÿäîâ
2666 count := utils.readLongInt(st);
2667 if (count < 0) or (count > 1024*1024) then raise XStreamError.Create('invalid shots counter');
2669 SetLength(Shots, count);
2671 if (count = 0) then exit;
2673 for i := 0 to count-1 do
2674 begin
2675 // Ñèãíàòóðà ñíàðÿäà
2676 if not utils.checkSign(st, 'SHOT') then raise XStreamError.Create('invalid shot signature');
2677 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid shot version');
2678 // Òèï ñíàðÿäà:
2679 Shots[i].ShotType := utils.readByte(st);
2680 // Öåëü
2681 Shots[i].Target := utils.readWord(st);
2682 // UID ñòðåëÿâøåãî
2683 Shots[i].SpawnerUID := utils.readWord(st);
2684 // Ðàçìåð ïîëÿ Triggers
2685 tc := utils.readLongInt(st);
2686 if (tc < 0) or (tc > 1024*1024) then raise XStreamError.Create('invalid shot triggers counter');
2687 SetLength(Shots[i].Triggers, tc);
2688 // Òðèããåðû, àêòèâèðîâàííûå âûñòðåëîì
2689 for j := 0 to tc-1 do Shots[i].Triggers[j] := utils.readLongWord(st);
2690 // Îáúåêò ïðåäìåòà
2691 Obj_LoadState(@Shots[i].Obj, st);
2692 // Êîñòûëèíà åáàíàÿ
2693 Shots[i].Stopped := utils.readByte(st);
2695 // Óñòàíîâêà òåêñòóðû èëè àíèìàöèè
2696 Shots[i].TextureID := DWORD(-1);
2697 Shots[i].Animation := nil;
2699 case Shots[i].ShotType of
2700 WEAPON_ROCKETLAUNCHER, WEAPON_SKEL_FIRE:
2701 begin
2702 g_Texture_Get('TEXTURE_WEAPON_ROCKET', Shots[i].TextureID);
2703 end;
2704 WEAPON_PLASMA:
2705 begin
2706 g_Frames_Get(dw, 'FRAMES_WEAPON_PLASMA');
2707 Shots[i].Animation := TAnimation.Create(dw, True, 5);
2708 end;
2709 WEAPON_BFG:
2710 begin
2711 g_Frames_Get(dw, 'FRAMES_WEAPON_BFG');
2712 Shots[i].Animation := TAnimation.Create(dw, True, 6);
2713 end;
2714 WEAPON_IMP_FIRE:
2715 begin
2716 g_Frames_Get(dw, 'FRAMES_WEAPON_IMPFIRE');
2717 Shots[i].Animation := TAnimation.Create(dw, True, 4);
2718 end;
2719 WEAPON_BSP_FIRE:
2720 begin
2721 g_Frames_Get(dw, 'FRAMES_WEAPON_BSPFIRE');
2722 Shots[i].Animation := TAnimation.Create(dw, True, 4);
2723 end;
2724 WEAPON_CACO_FIRE:
2725 begin
2726 g_Frames_Get(dw, 'FRAMES_WEAPON_CACOFIRE');
2727 Shots[i].Animation := TAnimation.Create(dw, True, 4);
2728 end;
2729 WEAPON_BARON_FIRE:
2730 begin
2731 g_Frames_Get(dw, 'FRAMES_WEAPON_BARONFIRE');
2732 Shots[i].Animation := TAnimation.Create(dw, True, 4);
2733 end;
2734 WEAPON_MANCUB_FIRE:
2735 begin
2736 g_Frames_Get(dw, 'FRAMES_WEAPON_MANCUBFIRE');
2737 Shots[i].Animation := TAnimation.Create(dw, True, 4);
2738 end;
2739 end;
2740 end;
2741 end;
2743 procedure g_Weapon_DestroyShot(I: Integer; X, Y: Integer; Loud: Boolean = True);
2744 var
2745 cx, cy: Integer;
2746 Anim: TAnimation;
2747 s: string;
2748 begin
2749 if Shots = nil then
2750 Exit;
2751 if (I > High(Shots)) or (I < 0) then Exit;
2753 with Shots[I] do
2754 begin
2755 if ShotType = 0 then Exit;
2756 Obj.X := X;
2757 Obj.Y := Y;
2758 cx := Obj.X + (Obj.Rect.Width div 2);
2759 cy := Obj.Y + (Obj.Rect.Height div 2);
2761 case ShotType of
2762 WEAPON_ROCKETLAUNCHER, WEAPON_SKEL_FIRE: // Ðàêåòû è ñíàðÿäû Ñêåëåòà
2763 begin
2764 if Loud then
2765 begin
2766 if ShotType = WEAPON_SKEL_FIRE then
2767 begin // Âçðûâ ñíàðÿäà Ñêåëåòà
2768 if g_Frames_Get(TextureID, 'FRAMES_EXPLODE_SKELFIRE') then
2769 begin
2770 Anim := TAnimation.Create(TextureID, False, 8);
2771 Anim.Blending := False;
2772 g_GFX_OnceAnim((Obj.X+32)-32, (Obj.Y+8)-32, Anim);
2773 Anim.Free();
2774 end;
2775 end
2776 else
2777 begin // Âçðûâ Ðàêåòû
2778 if g_Frames_Get(TextureID, 'FRAMES_EXPLODE_ROCKET') then
2779 begin
2780 Anim := TAnimation.Create(TextureID, False, 6);
2781 Anim.Blending := False;
2782 g_GFX_OnceAnim(cx-64, cy-64, Anim);
2783 Anim.Free();
2784 end;
2785 end;
2786 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEROCKET', Obj.X, Obj.Y);
2787 end;
2788 end;
2790 WEAPON_PLASMA, WEAPON_BSP_FIRE: // Ïëàçìà, ïëàçìà Àðàõíàòðîíà
2791 begin
2792 if ShotType = WEAPON_PLASMA then
2793 s := 'FRAMES_EXPLODE_PLASMA'
2794 else
2795 s := 'FRAMES_EXPLODE_BSPFIRE';
2797 if g_Frames_Get(TextureID, s) and loud then
2798 begin
2799 Anim := TAnimation.Create(TextureID, False, 3);
2800 Anim.Blending := False;
2801 g_GFX_OnceAnim(cx-16, cy-16, Anim);
2802 Anim.Free();
2804 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEPLASMA', Obj.X, Obj.Y);
2805 end;
2806 end;
2808 WEAPON_BFG: // BFG
2809 begin
2810 // Âçðûâ BFG:
2811 if g_Frames_Get(TextureID, 'FRAMES_EXPLODE_BFG') and Loud then
2812 begin
2813 Anim := TAnimation.Create(TextureID, False, 6);
2814 Anim.Blending := False;
2815 g_GFX_OnceAnim(cx-64, cy-64, Anim);
2816 Anim.Free();
2818 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBFG', Obj.X, Obj.Y);
2819 end;
2820 end;
2822 WEAPON_IMP_FIRE, WEAPON_CACO_FIRE, WEAPON_BARON_FIRE: // Âûñòðåëû Áåñà, Êàêîäåìîíà Ðûöàðÿ/Áàðîíà àäà
2823 begin
2824 if ShotType = WEAPON_IMP_FIRE then
2825 s := 'FRAMES_EXPLODE_IMPFIRE'
2826 else
2827 if ShotType = WEAPON_CACO_FIRE then
2828 s := 'FRAMES_EXPLODE_CACOFIRE'
2829 else
2830 s := 'FRAMES_EXPLODE_BARONFIRE';
2832 if g_Frames_Get(TextureID, s) and Loud then
2833 begin
2834 Anim := TAnimation.Create(TextureID, False, 6);
2835 Anim.Blending := False;
2836 g_GFX_OnceAnim(cx-32, cy-32, Anim);
2837 Anim.Free();
2839 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBALL', Obj.X, Obj.Y);
2840 end;
2841 end;
2843 WEAPON_MANCUB_FIRE: // Âûñòðåë Ìàíêóáóñà
2844 begin
2845 if g_Frames_Get(TextureID, 'FRAMES_EXPLODE_ROCKET') and Loud then
2846 begin
2847 Anim := TAnimation.Create(TextureID, False, 6);
2848 Anim.Blending := False;
2849 g_GFX_OnceAnim(cx-64, cy-64, Anim);
2850 Anim.Free();
2852 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBALL', Obj.X, Obj.Y);
2853 end;
2854 end;
2855 end; // case ShotType of...
2857 ShotType := 0;
2858 Animation.Free();
2859 end;
2860 end;
2863 procedure g_Weapon_AddDynLights();
2864 var
2865 i: Integer;
2866 begin
2867 if Shots = nil then Exit;
2868 for i := 0 to High(Shots) do
2869 begin
2870 if Shots[i].ShotType = 0 then continue;
2871 if (Shots[i].ShotType = WEAPON_ROCKETLAUNCHER) or
2872 (Shots[i].ShotType = WEAPON_BARON_FIRE) or
2873 (Shots[i].ShotType = WEAPON_MANCUB_FIRE) or
2874 (Shots[i].ShotType = WEAPON_SKEL_FIRE) or
2875 (Shots[i].ShotType = WEAPON_IMP_FIRE) or
2876 (Shots[i].ShotType = WEAPON_CACO_FIRE) or
2877 (Shots[i].ShotType = WEAPON_MANCUB_FIRE) or
2878 (Shots[i].ShotType = WEAPON_BSP_FIRE) or
2879 (Shots[i].ShotType = WEAPON_PLASMA) or
2880 (Shots[i].ShotType = WEAPON_BFG) or
2881 (Shots[i].ShotType = WEAPON_FLAMETHROWER) or
2882 false then
2883 begin
2884 if (Shots[i].ShotType = WEAPON_PLASMA) then
2885 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)
2886 else if (Shots[i].ShotType = WEAPON_BFG) then
2887 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)
2888 else if (Shots[i].ShotType = WEAPON_FLAMETHROWER) then
2889 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)
2890 else
2891 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);
2892 end;
2893 end;
2894 end;
2897 procedure TShot.positionChanged (); begin end;
2900 end.