DEADSOFTWARE

gl: implement load screen
[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_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 time: LongWord;
34 Timeout: DWORD;
35 Stopped: Byte;
37 procedure positionChanged (); //WARNING! call this after monster position was changed, or coldet will not work right!
38 end;
41 var
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();
84 const
85 WEAPON_KASTET = 0;
86 WEAPON_SAW = 1;
87 WEAPON_PISTOL = 2;
88 WEAPON_SHOTGUN1 = 3;
89 WEAPON_SHOTGUN2 = 4;
90 WEAPON_CHAINGUN = 5;
91 WEAPON_ROCKETLAUNCHER = 6;
92 WEAPON_PLASMA = 7;
93 WEAPON_BFG = 8;
94 WEAPON_SUPERPULEMET = 9;
95 WEAPON_FLAMETHROWER = 10;
96 WEAPON_ZOMBY_PISTOL = 20;
97 WEAPON_IMP_FIRE = 21;
98 WEAPON_BSP_FIRE = 22;
99 WEAPON_CACO_FIRE = 23;
100 WEAPON_BARON_FIRE = 24;
101 WEAPON_MANCUB_FIRE = 25;
102 WEAPON_SKEL_FIRE = 26;
103 WEAPON_LAST = WEAPON_SKEL_FIRE;
105 WP_FIRST = WEAPON_KASTET;
106 WP_LAST = WEAPON_FLAMETHROWER;
108 var
109 gwep_debug_fast_trace: Boolean = true;
112 implementation
114 uses
115 {$IFDEF ENABLE_GFX}
116 g_gfx,
117 {$ENDIF}
118 {$IFDEF ENABLE_GIBS}
119 g_gibs,
120 {$ENDIF}
121 {$IFDEF ENABLE_CORPSES}
122 g_corpses,
123 {$ENDIF}
124 Math, g_map, g_player, g_sound, g_panel,
125 g_console, g_options, g_game,
126 g_triggers, MAPDEF, e_log, g_monsters, g_saveload,
127 g_language, g_netmsg, g_grid, g_window,
128 geom, binheap, hashtable, utils, xstreams
131 type
132 TWaterPanel = record
133 X, Y: Integer;
134 Width, Height: Word;
135 Active: Boolean;
136 end;
138 const
139 SHOT_ROCKETLAUNCHER_WIDTH = 14;
140 SHOT_ROCKETLAUNCHER_HEIGHT = 14;
142 SHOT_SKELFIRE_WIDTH = 14;
143 SHOT_SKELFIRE_HEIGHT = 14;
145 SHOT_PLASMA_WIDTH = 16;
146 SHOT_PLASMA_HEIGHT = 16;
148 SHOT_BFG_WIDTH = 32;
149 SHOT_BFG_HEIGHT = 32;
150 SHOT_BFG_DAMAGE = 100;
151 SHOT_BFG_RADIUS = 256;
153 SHOT_FLAME_WIDTH = 4;
154 SHOT_FLAME_HEIGHT = 4;
155 SHOT_FLAME_LIFETIME = 180;
157 SHOT_SIGNATURE = $544F4853; // 'SHOT'
159 type
160 PHitTime = ^THitTime;
161 THitTime = record
162 distSq: Integer;
163 mon: TMonster;
164 plridx: Integer; // if mon=nil
165 x, y: Integer;
166 end;
168 TBinHeapKeyHitTime = class
169 public
170 class function less (const a, b: Integer): Boolean; inline;
171 end;
173 // indicies in `wgunHitTime` array
174 TBinaryHeapHitTimes = specialize TBinaryHeapBase<Integer, TBinHeapKeyHitTime>;
176 var
177 WaterMap: array of array of DWORD = nil;
178 //wgunMonHash: THashIntInt = nil;
179 wgunHitHeap: TBinaryHeapHitTimes = nil;
180 wgunHitTime: array of THitTime = nil;
181 wgunHitTimeUsed: Integer = 0;
184 class function TBinHeapKeyHitTime.less (const a, b: Integer): Boolean;
185 var
186 hta, htb: PHitTime;
187 begin
188 hta := @wgunHitTime[a];
189 htb := @wgunHitTime[b];
190 if (hta.distSq <> htb.distSq) then begin result := (hta.distSq < htb.distSq); exit; end;
191 if (hta.mon <> nil) then
192 begin
193 // a is monster
194 if (htb.mon = nil) then begin result := false; exit; end; // players first
195 result := (hta.mon.UID < htb.mon.UID); // why not?
196 end
197 else
198 begin
199 // a is player
200 if (htb.mon <> nil) then begin result := true; exit; end; // players first
201 result := (hta.plridx < htb.plridx); // why not?
202 end;
203 end;
206 procedure appendHitTimeMon (adistSq: Integer; amon: TMonster; ax, ay: Integer);
207 begin
208 if (wgunHitTimeUsed = Length(wgunHitTime)) then SetLength(wgunHitTime, wgunHitTimeUsed+128);
209 with wgunHitTime[wgunHitTimeUsed] do
210 begin
211 distSq := adistSq;
212 mon := amon;
213 plridx := -1;
214 x := ax;
215 y := ay;
216 end;
217 wgunHitHeap.insert(wgunHitTimeUsed);
218 Inc(wgunHitTimeUsed);
219 end;
222 procedure appendHitTimePlr (adistSq: Integer; aplridx: Integer; ax, ay: Integer);
223 begin
224 if (wgunHitTimeUsed = Length(wgunHitTime)) then SetLength(wgunHitTime, wgunHitTimeUsed+128);
225 with wgunHitTime[wgunHitTimeUsed] do
226 begin
227 distSq := adistSq;
228 mon := nil;
229 plridx := aplridx;
230 x := ax;
231 y := ay;
232 end;
233 wgunHitHeap.insert(wgunHitTimeUsed);
234 Inc(wgunHitTimeUsed);
235 end;
238 function FindShot(): DWORD;
239 var
240 i: Integer;
241 begin
242 if Shots <> nil then
243 for i := 0 to High(Shots) do
244 if Shots[i].ShotType = 0 then
245 begin
246 Result := i;
247 LastShotID := Result;
248 Exit;
249 end;
251 if Shots = nil then
252 begin
253 SetLength(Shots, 128);
254 Result := 0;
255 end
256 else
257 begin
258 Result := High(Shots) + 1;
259 SetLength(Shots, Length(Shots) + 128);
260 end;
261 LastShotID := Result;
262 end;
264 procedure CreateWaterMap();
265 var
266 WaterArray: Array of TWaterPanel;
267 a, b, c, m: Integer;
268 ok: Boolean;
269 begin
270 if gWater = nil then
271 Exit;
273 SetLength(WaterArray, Length(gWater));
275 for a := 0 to High(gWater) do
276 begin
277 WaterArray[a].X := gWater[a].X;
278 WaterArray[a].Y := gWater[a].Y;
279 WaterArray[a].Width := gWater[a].Width;
280 WaterArray[a].Height := gWater[a].Height;
281 WaterArray[a].Active := True;
282 end;
284 g_Game_SetLoadingText(_lc[I_LOAD_WATER_MAP], High(WaterArray), False);
286 for a := 0 to High(WaterArray) do
287 if WaterArray[a].Active then
288 begin
289 WaterArray[a].Active := False;
290 m := Length(WaterMap);
291 SetLength(WaterMap, m+1);
292 SetLength(WaterMap[m], 1);
293 WaterMap[m][0] := a;
294 ok := True;
296 while ok do
297 begin
298 ok := False;
299 for b := 0 to High(WaterArray) do
300 if WaterArray[b].Active then
301 for c := 0 to High(WaterMap[m]) do
302 if g_CollideAround(WaterArray[b].X,
303 WaterArray[b].Y,
304 WaterArray[b].Width,
305 WaterArray[b].Height,
306 WaterArray[WaterMap[m][c]].X,
307 WaterArray[WaterMap[m][c]].Y,
308 WaterArray[WaterMap[m][c]].Width,
309 WaterArray[WaterMap[m][c]].Height) then
310 begin
311 WaterArray[b].Active := False;
312 SetLength(WaterMap[m],
313 Length(WaterMap[m])+1);
314 WaterMap[m][High(WaterMap[m])] := b;
315 ok := True;
316 Break;
317 end;
318 end;
320 g_Game_StepLoading();
321 end;
323 WaterArray := nil;
324 end;
327 var
328 chkTrap_pl: array [0..256] of Integer;
329 chkTrap_mn: array [0..65535] of TMonster;
331 procedure CheckTrap(ID: DWORD; dm: Integer; t: Byte);
332 var
333 //a, b, c, d, i1, i2: Integer;
334 //chkTrap_pl, chkTrap_mn: WArray;
335 plaCount: Integer = 0;
336 mnaCount: Integer = 0;
337 frameId: DWord;
340 function monsWaterCheck (mon: TMonster): Boolean;
341 begin
342 result := false; // don't stop
343 if mon.alive and mon.Collide(gWater[WaterMap[a][c]]) and (not InWArray(monidx, chkTrap_mn)) and (i2 < 1023) then //FIXME
344 begin
345 i2 += 1;
346 chkTrap_mn[i2] := monidx;
347 end;
348 end;
351 function monsWaterCheck (mon: TMonster): Boolean;
352 begin
353 result := false; // don't stop
354 if (mon.trapCheckFrameId <> frameId) then
355 begin
356 mon.trapCheckFrameId := frameId;
357 chkTrap_mn[mnaCount] := mon;
358 Inc(mnaCount);
359 end;
360 end;
362 var
363 a, b, c, d, f: Integer;
364 pan: TPanel;
365 begin
366 if (gWater = nil) or (WaterMap = nil) then Exit;
368 frameId := g_Mons_getNewTrapFrameId();
370 //i1 := -1;
371 //i2 := -1;
373 //SetLength(chkTrap_pl, 1024);
374 //SetLength(chkTrap_mn, 1024);
375 //for d := 0 to 1023 do chkTrap_pl[d] := $FFFF;
376 //for d := 0 to 1023 do chkTrap_mn[d] := $FFFF;
378 for a := 0 to High(WaterMap) do
379 begin
380 for b := 0 to High(WaterMap[a]) do
381 begin
382 pan := gWater[WaterMap[a][b]];
383 if not g_Obj_Collide(pan.X, pan.Y, pan.Width, pan.Height, @Shots[ID].Obj) then continue;
385 for c := 0 to High(WaterMap[a]) do
386 begin
387 pan := gWater[WaterMap[a][c]];
388 for d := 0 to High(gPlayers) do
389 begin
390 if (gPlayers[d] <> nil) and (gPlayers[d].alive) then
391 begin
392 if gPlayers[d].Collide(pan) then
393 begin
394 f := 0;
395 while (f < plaCount) and (chkTrap_pl[f] <> d) do Inc(f);
396 if (f = plaCount) then
397 begin
398 chkTrap_pl[plaCount] := d;
399 Inc(plaCount);
400 if (plaCount = Length(chkTrap_pl)) then break;
401 end;
402 end;
403 end;
404 end;
406 //g_Mons_ForEach(monsWaterCheck);
407 g_Mons_ForEachAliveAt(pan.X, pan.Y, pan.Width, pan.Height, monsWaterCheck);
408 end;
410 for f := 0 to plaCount-1 do gPlayers[chkTrap_pl[f]].Damage(dm, Shots[ID].SpawnerUID, 0, 0, t);
411 for f := 0 to mnaCount-1 do chkTrap_mn[f].Damage(dm, 0, 0, Shots[ID].SpawnerUID, t);
412 end;
413 end;
415 //chkTrap_pl := nil;
416 //chkTrap_mn := nil;
417 end;
419 function HitMonster(m: TMonster; d: Integer; vx, vy: Integer; SpawnerUID: Word; t: Byte): Boolean;
420 var
421 tt, mt: Byte;
422 mon: TMonster;
423 begin
424 Result := False;
426 tt := g_GetUIDType(SpawnerUID);
427 if tt = UID_MONSTER then
428 begin
429 mon := g_Monsters_ByUID(SpawnerUID);
430 if mon <> nil then
431 mt := g_Monsters_ByUID(SpawnerUID).MonsterType
432 else
433 mt := 0;
434 end
435 else
436 mt := 0;
438 if m = nil then Exit;
439 if m.UID = SpawnerUID then
440 begin
441 // Ñàì ñåáÿ ìîæåò ðàíèòü òîëüêî ðàêåòîé è òîêîì:
442 if (t <> HIT_ROCKET) and (t <> HIT_ELECTRO) then
443 Exit;
444 // Êèáåð äåìîí è áî÷êà âîîáùå íå ìîãóò ñåáÿ ðàíèòü:
445 if (m.MonsterType = MONSTER_CYBER) or
446 (m.MonsterType = MONSTER_BARREL) then
447 begin
448 Result := True;
449 Exit;
450 end;
451 end;
453 if tt = UID_MONSTER then
454 begin
455 // Lost_Soul íå ìîæåò ðàíèòü Pain_Elemental'à:
456 if (mt = MONSTER_SOUL) and (m.MonsterType = MONSTER_PAIN) then
457 Exit;
459 // Îáà ìîíñòðà îäíîãî âèäà:
460 if mt = m.MonsterType then
461 case mt of
462 MONSTER_IMP, MONSTER_DEMON, MONSTER_BARON, MONSTER_KNIGHT, MONSTER_CACO,
463 MONSTER_SOUL, MONSTER_MANCUB, MONSTER_SKEL, MONSTER_FISH:
464 Exit; // Ýòè íå áüþò ñâîèõ
465 end;
466 end;
468 if g_Game_IsServer then
469 begin
470 if (t <> HIT_FLAME) or (m.FFireTime = 0) or (vx <> 0) or (vy <> 0) then
471 Result := m.Damage(d, vx, vy, SpawnerUID, t)
472 else
473 Result := (gLMSRespawn = LMS_RESPAWN_NONE); // don't hit monsters when it's warmup time
474 if t = HIT_FLAME then
475 m.CatchFire(SpawnerUID);
476 end
477 else
478 Result := (gLMSRespawn = LMS_RESPAWN_NONE); // don't hit monsters when it's warmup time
479 end;
482 function HitPlayer (p: TPlayer; d: Integer; vx, vy: Integer; SpawnerUID: Word; t: Byte): Boolean;
483 begin
484 result := False;
486 // Ñàì ñåáÿ ìîæåò ðàíèòü òîëüêî ðàêåòîé è òîêîì
487 if (p.UID = SpawnerUID) and (t <> HIT_ROCKET) and (t <> HIT_ELECTRO) then exit;
489 if g_Game_IsServer then
490 begin
491 if (t <> HIT_FLAME) or (p.FFireTime = 0) or (vx <> 0) or (vy <> 0) then p.Damage(d, SpawnerUID, vx, vy, t);
492 if (t = HIT_FLAME) then p.CatchFire(SpawnerUID);
493 end;
495 result := true;
496 end;
499 procedure g_Weapon_BFG9000(X, Y: Integer; SpawnerUID: Word);
501 function monsCheck (mon: TMonster): Boolean;
502 begin
503 result := false; // don't stop
504 if (mon.alive) and (mon.UID <> SpawnerUID) then
505 begin
506 with mon do
507 begin
508 if (g_PatchLength(X, Y, Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
509 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)) <= SHOT_BFG_RADIUS) and
510 g_TraceVector(X, Y, Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
511 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)) then
512 begin
513 if HitMonster(mon, 50, 0, 0, SpawnerUID, HIT_SOME) then mon.BFGHit();
514 end;
515 end;
516 end;
517 end;
519 var
520 i, h: Integer;
521 st: Byte;
522 pl: TPlayer;
523 b: Boolean;
524 begin
525 //g_Sound_PlayEx('SOUND_WEAPON_EXPLODEBFG', 255);
527 {$IFDEF ENABLE_CORPSES}
528 h := High(gCorpses);
529 if gAdvCorpses and (h <> -1) then
530 begin
531 for i := 0 to h do
532 begin
533 if (gCorpses[i] <> nil) and (gCorpses[i].State <> CORPSE_STATE_REMOVEME) then
534 begin
535 with gCorpses[i] do
536 begin
537 if (g_PatchLength(X, Y, Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
538 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)) <= SHOT_BFG_RADIUS) and
539 g_TraceVector(X, Y, Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
540 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)) then
541 begin
542 Damage(50, SpawnerUID, 0, 0);
543 g_Weapon_BFGHit(Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2), Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2));
544 end;
545 end;
546 end;
547 end;
548 end;
549 {$ENDIF}
551 st := TEAM_NONE;
552 pl := g_Player_Get(SpawnerUID);
553 if pl <> nil then
554 st := pl.Team;
556 h := High(gPlayers);
558 if h <> -1 then
559 for i := 0 to h do
560 if (gPlayers[i] <> nil) and (gPlayers[i].alive) and (gPlayers[i].UID <> SpawnerUID) then
561 with gPlayers[i] do
562 if (g_PatchLength(X, Y, GameX+PLAYER_RECT.X+(PLAYER_RECT.Width div 2),
563 GameY+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)) <= SHOT_BFG_RADIUS) and
564 g_TraceVector(X, Y, GameX+PLAYER_RECT.X+(PLAYER_RECT.Width div 2),
565 GameY+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)) then
566 begin
567 if (st = TEAM_NONE) or (st <> gPlayers[i].Team) then
568 b := HitPlayer(gPlayers[i], 50, 0, 0, SpawnerUID, HIT_SOME)
569 else
570 b := HitPlayer(gPlayers[i], 25, 0, 0, SpawnerUID, HIT_SOME);
571 if b then
572 gPlayers[i].BFGHit();
573 end;
575 //FIXME
576 g_Mons_ForEachAlive(monsCheck);
577 end;
579 function g_Weapon_CreateShot(I: Integer; ShotType: Byte; Spawner, TargetUID: Word; X, Y, XV, YV: Integer): LongWord;
580 var
581 find_id: DWord;
582 begin
583 if I < 0 then
584 find_id := FindShot()
585 else
586 begin
587 find_id := I;
588 if Integer(find_id) >= High(Shots) then
589 SetLength(Shots, find_id + 64)
590 end;
592 shots[find_id].time := gTime;
594 case ShotType of
595 WEAPON_ROCKETLAUNCHER:
596 begin
597 with Shots[find_id] do
598 begin
599 g_Obj_Init(@Obj);
601 Obj.Rect.Width := SHOT_ROCKETLAUNCHER_WIDTH;
602 Obj.Rect.Height := SHOT_ROCKETLAUNCHER_HEIGHT;
604 Triggers := nil;
605 ShotType := WEAPON_ROCKETLAUNCHER;
606 end;
607 end;
609 WEAPON_PLASMA:
610 begin
611 with Shots[find_id] do
612 begin
613 g_Obj_Init(@Obj);
615 Obj.Rect.Width := SHOT_PLASMA_WIDTH;
616 Obj.Rect.Height := SHOT_PLASMA_HEIGHT;
618 Triggers := nil;
619 ShotType := WEAPON_PLASMA;
620 end;
621 end;
623 WEAPON_BFG:
624 begin
625 with Shots[find_id] do
626 begin
627 g_Obj_Init(@Obj);
629 Obj.Rect.Width := SHOT_BFG_WIDTH;
630 Obj.Rect.Height := SHOT_BFG_HEIGHT;
632 Triggers := nil;
633 ShotType := WEAPON_BFG;
634 end;
635 end;
637 WEAPON_FLAMETHROWER:
638 begin
639 with Shots[find_id] do
640 begin
641 g_Obj_Init(@Obj);
643 Obj.Rect.Width := SHOT_FLAME_WIDTH;
644 Obj.Rect.Height := SHOT_FLAME_HEIGHT;
646 Triggers := nil;
647 ShotType := WEAPON_FLAMETHROWER;
648 end;
649 end;
651 WEAPON_IMP_FIRE:
652 begin
653 with Shots[find_id] do
654 begin
655 g_Obj_Init(@Obj);
657 Obj.Rect.Width := 16;
658 Obj.Rect.Height := 16;
660 Triggers := nil;
661 ShotType := WEAPON_IMP_FIRE;
662 end;
663 end;
665 WEAPON_CACO_FIRE:
666 begin
667 with Shots[find_id] do
668 begin
669 g_Obj_Init(@Obj);
671 Obj.Rect.Width := 16;
672 Obj.Rect.Height := 16;
674 Triggers := nil;
675 ShotType := WEAPON_CACO_FIRE;
676 end;
677 end;
679 WEAPON_MANCUB_FIRE:
680 begin
681 with Shots[find_id] do
682 begin
683 g_Obj_Init(@Obj);
685 Obj.Rect.Width := 32;
686 Obj.Rect.Height := 32;
688 Triggers := nil;
689 ShotType := WEAPON_MANCUB_FIRE;
690 end;
691 end;
693 WEAPON_BARON_FIRE:
694 begin
695 with Shots[find_id] do
696 begin
697 g_Obj_Init(@Obj);
699 Obj.Rect.Width := 16;
700 Obj.Rect.Height := 16;
702 Triggers := nil;
703 ShotType := WEAPON_BARON_FIRE;
704 end;
705 end;
707 WEAPON_BSP_FIRE:
708 begin
709 with Shots[find_id] do
710 begin
711 g_Obj_Init(@Obj);
713 Obj.Rect.Width := 16;
714 Obj.Rect.Height := 16;
716 Triggers := nil;
717 ShotType := WEAPON_BSP_FIRE;
718 end;
719 end;
721 WEAPON_SKEL_FIRE:
722 begin
723 with Shots[find_id] do
724 begin
725 g_Obj_Init(@Obj);
727 Obj.Rect.Width := SHOT_SKELFIRE_WIDTH;
728 Obj.Rect.Height := SHOT_SKELFIRE_HEIGHT;
730 Triggers := nil;
731 ShotType := WEAPON_SKEL_FIRE;
732 target := TargetUID;
733 end;
734 end;
735 end;
737 Shots[find_id].Obj.oldX := X;
738 Shots[find_id].Obj.oldY := Y;
739 Shots[find_id].Obj.X := X;
740 Shots[find_id].Obj.Y := Y;
741 Shots[find_id].Obj.Vel.X := XV;
742 Shots[find_id].Obj.Vel.Y := YV;
743 Shots[find_id].Obj.Accel.X := 0;
744 Shots[find_id].Obj.Accel.Y := 0;
745 Shots[find_id].SpawnerUID := Spawner;
746 if (ShotType = WEAPON_FLAMETHROWER) and (XV = 0) and (YV = 0) then
747 Shots[find_id].Stopped := 255
748 else
749 Shots[find_id].Stopped := 0;
750 Result := find_id;
751 end;
753 procedure throw(i, x, y, xd, yd, s: Integer);
754 var
755 a: Integer;
756 begin
757 yd := yd - y;
758 xd := xd - x;
760 a := Max(Abs(xd), Abs(yd));
761 if a = 0 then
762 a := 1;
764 Shots[i].Obj.oldX := x;
765 Shots[i].Obj.oldY := y;
766 Shots[i].Obj.X := x;
767 Shots[i].Obj.Y := y;
768 Shots[i].Obj.Vel.X := (xd*s) div a;
769 Shots[i].Obj.Vel.Y := (yd*s) div a;
770 Shots[i].Obj.Accel.X := 0;
771 Shots[i].Obj.Accel.Y := 0;
772 Shots[i].Stopped := 0;
773 if Shots[i].ShotType in [WEAPON_ROCKETLAUNCHER, WEAPON_BFG] then
774 Shots[i].Timeout := 900 // ~25 sec
775 else
776 begin
777 if Shots[i].ShotType = WEAPON_FLAMETHROWER then
778 Shots[i].Timeout := SHOT_FLAME_LIFETIME
779 else
780 Shots[i].Timeout := 550; // ~15 sec
781 end;
782 end;
784 function g_Weapon_Hit(obj: PObj; d: Integer; SpawnerUID: Word; t: Byte; HitCorpses: Boolean = True): Byte;
785 {$IFDEF ENABLE_CORPSES}
786 var i: Integer;
787 {$ENDIF}
788 var h: Integer;
790 function PlayerHit(Team: Byte = 0): Boolean;
791 var
792 i: Integer;
793 ChkTeam: Boolean;
794 p: TPlayer;
795 begin
796 Result := False;
797 h := High(gPlayers);
799 if h <> -1 then
800 for i := 0 to h do
801 if (gPlayers[i] <> nil) and gPlayers[i].alive and g_Obj_Collide(obj, @gPlayers[i].Obj) then
802 begin
803 ChkTeam := True;
804 if (Team > 0) and (g_GetUIDType(SpawnerUID) = UID_PLAYER) then
805 begin
806 p := g_Player_Get(SpawnerUID);
807 if p <> nil then
808 ChkTeam := (p.Team = gPlayers[i].Team) xor (Team = 2);
809 end;
810 if ChkTeam then
811 if HitPlayer(gPlayers[i], d, obj^.Vel.X, obj^.Vel.Y, SpawnerUID, t) then
812 begin
813 if t <> HIT_FLAME then
814 gPlayers[i].Push((obj^.Vel.X+obj^.Accel.X)*IfThen(t = HIT_BFG, 8, 1) div 4,
815 (obj^.Vel.Y+obj^.Accel.Y)*IfThen(t = HIT_BFG, 8, 1) div 4);
816 if t = HIT_BFG then
817 g_Game_DelayEvent(DE_BFGHIT, 1000, SpawnerUID);
818 Result := True;
819 break;
820 end;
821 end;
822 end;
825 function monsCheckHit (monidx: Integer; mon: TMonster): Boolean;
826 begin
827 result := false; // don't stop
828 if mon.alive and g_Obj_Collide(obj, @mon.Obj) then
829 begin
830 if HitMonster(mon, d, obj^.Vel.X, obj^.Vel.Y, SpawnerUID, t) then
831 begin
832 if (t <> HIT_FLAME) then
833 begin
834 mon.Push((obj^.Vel.X+obj^.Accel.X)*IfThen(t = HIT_BFG, 8, 1) div 4,
835 (obj^.Vel.Y+obj^.Accel.Y)*IfThen(t = HIT_BFG, 8, 1) div 4);
836 end;
837 result := True;
838 end;
839 end;
840 end;
843 function monsCheckHit (mon: TMonster): Boolean;
844 begin
845 result := false; // don't stop
846 if HitMonster(mon, d, obj.Vel.X, obj.Vel.Y, SpawnerUID, t) then
847 begin
848 if (t <> HIT_FLAME) then
849 begin
850 mon.Push((obj.Vel.X+obj.Accel.X)*IfThen(t = HIT_BFG, 8, 1) div 4,
851 (obj.Vel.Y+obj.Accel.Y)*IfThen(t = HIT_BFG, 8, 1) div 4);
852 end;
853 result := true;
854 end;
855 end;
857 function MonsterHit(): Boolean;
858 begin
859 //result := g_Mons_ForEach(monsCheckHit);
860 //FIXME: accelerate this!
861 result := g_Mons_ForEachAliveAt(obj.X+obj.Rect.X, obj.Y+obj.Rect.Y, obj.Rect.Width, obj.Rect.Height, monsCheckHit);
862 end;
864 begin
865 Result := 0;
867 {$IFDEF ENABLE_CORPSES}
868 if HitCorpses then
869 begin
870 h := High(gCorpses);
871 if gAdvCorpses and (h <> -1) then
872 begin
873 for i := 0 to h do
874 begin
875 if (gCorpses[i] <> nil) and (gCorpses[i].State <> CORPSE_STATE_REMOVEME) and
876 g_Obj_Collide(obj, @gCorpses[i].Obj) then
877 begin
878 // Ðàñïèëèâàåì òðóï:
879 gCorpses[i].Damage(d, SpawnerUID, (obj^.Vel.X+obj^.Accel.X) div 4,
880 (obj^.Vel.Y+obj^.Accel.Y) div 4);
881 Result := 1;
882 end;
883 end;
884 end;
885 end;
886 {$ENDIF}
888 case gGameSettings.GameMode of
889 // Êàìïàíèÿ:
890 GM_COOP, GM_SINGLE:
891 begin
892 // Ñíà÷àëà áü¸ì ìîíñòðîâ, åñëè åñòü, ïîòîì ïûòàåìñÿ áèòü èãðîêîâ
893 if MonsterHit() then
894 begin
895 Result := 2;
896 Exit;
897 end;
899 // È â êîíöå èãðîêîâ, íî òîëüêî åñëè ïîëîæåíî
900 // (èëè ñíàðÿä îò ìîíñòðà, èëè friendlyfire, èëè friendly_hit_projectile)
901 if (g_GetUIDType(SpawnerUID) <> UID_PLAYER) or
902 LongBool(gGameSettings.Options and (GAME_OPTION_TEAMDAMAGE or GAME_OPTION_TEAMHITPROJECTILE)) then
903 begin
904 if PlayerHit() then
905 begin
906 Result := 1;
907 Exit;
908 end;
909 end;
910 end;
912 // Äåçìàò÷:
913 GM_DM:
914 begin
915 // Ñíà÷àëà áü¸ì èãðîêîâ, åñëè åñòü, ïîòîì ïûòàåìñÿ áèòü ìîíñòðîâ
916 if PlayerHit() then
917 begin
918 Result := 1;
919 Exit;
920 end;
922 if MonsterHit() then
923 begin
924 Result := 2;
925 Exit;
926 end;
927 end;
929 // Êîìàíäíûå:
930 GM_TDM, GM_CTF:
931 begin
932 // Ñíà÷àëà áü¸ì èãðîêîâ êîìàíäû ñîïåðíèêà
933 if PlayerHit(2) then
934 begin
935 Result := 1;
936 Exit;
937 end;
939 // Ïîòîì ìîíñòðîâ
940 if MonsterHit() then
941 begin
942 Result := 2;
943 Exit;
944 end;
946 // È â êîíöå ñâîèõ èãðîêîâ, íî òîëüêî åñëè ïîëîæåíî
947 // (èëè friendlyfire, èëè friendly_hit_projectile)
948 if LongBool(gGameSettings.Options and (GAME_OPTION_TEAMDAMAGE or GAME_OPTION_TEAMHITPROJECTILE)) then
949 begin
950 if PlayerHit(1) then
951 begin
952 Result := 1;
953 Exit;
954 end;
955 end;
956 end;
958 end;
959 end;
961 function g_Weapon_HitUID(UID: Word; d: Integer; SpawnerUID: Word; t: Byte): Boolean;
962 begin
963 Result := False;
965 case g_GetUIDType(UID) of
966 UID_PLAYER: Result := HitPlayer(g_Player_Get(UID), d, 0, 0, SpawnerUID, t);
967 UID_MONSTER: Result := HitMonster(g_Monsters_ByUID(UID), d, 0, 0, SpawnerUID, t);
968 else Exit;
969 end;
970 end;
972 function g_Weapon_Explode(X, Y: Integer; rad: Integer; SpawnerUID: Word): Boolean;
973 var
974 r: Integer; // squared radius
976 function monsExCheck (mon: TMonster): Boolean;
977 var
978 dx, dy, mm: Integer;
979 begin
980 result := false; // don't stop
981 begin
982 dx := mon.Obj.X+mon.Obj.Rect.X+(mon.Obj.Rect.Width div 2)-X;
983 dy := mon.Obj.Y+mon.Obj.Rect.Y+(mon.Obj.Rect.Height div 2)-Y;
985 if dx > 1000 then dx := 1000;
986 if dy > 1000 then dy := 1000;
988 if (dx*dx+dy*dy < r) then
989 begin
990 //m := PointToRect(X, Y, Obj.X+Obj.Rect.X, Obj.Y+Obj.Rect.Y, Obj.Rect.Width, Obj.Rect.Height);
991 //e_WriteLog(Format('explo monster #%d: x=%d; y=%d; rad=%d; dx=%d; dy=%d', [monidx, X, Y, rad, dx, dy]), MSG_NOTIFY);
993 mm := Max(abs(dx), abs(dy));
994 if mm = 0 then mm := 1;
996 if mon.alive then
997 begin
998 HitMonster(mon, ((mon.Obj.Rect.Width div 4)*10*(rad-mm)) div rad, 0, 0, SpawnerUID, HIT_ROCKET);
999 end;
1001 mon.Push((dx*7) div mm, (dy*7) div mm);
1002 end;
1003 end;
1004 end;
1006 var i, h, dx, dy, mm: Integer;
1007 {$IFDEF ENABLE_GIBS}
1008 var _angle: SmallInt;
1009 {$ENDIF}
1010 {$IF DEFINED(ENABLE_GIBS) OR DEFINED(ENABLE_CORPSES)}
1011 var m: Integer;
1012 {$ENDIF}
1013 begin
1014 result := false;
1016 g_Triggers_PressC(X, Y, rad, SpawnerUID, ACTIVATE_SHOT);
1018 r := rad*rad;
1020 h := High(gPlayers);
1022 if h <> -1 then
1023 for i := 0 to h do
1024 if (gPlayers[i] <> nil) and gPlayers[i].alive then
1025 with gPlayers[i] do
1026 begin
1027 dx := Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2)-X;
1028 dy := Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)-Y;
1030 if dx > 1000 then dx := 1000;
1031 if dy > 1000 then dy := 1000;
1033 if dx*dx+dy*dy < r then
1034 begin
1035 //m := PointToRect(X, Y, GameX+PLAYER_RECT.X, GameY+PLAYER_RECT.Y,
1036 // PLAYER_RECT.Width, PLAYER_RECT.Height);
1038 mm := Max(abs(dx), abs(dy));
1039 if mm = 0 then mm := 1;
1041 HitPlayer(gPlayers[i], (100*(rad-mm)) div rad, (dx*10) div mm, (dy*10) div mm, SpawnerUID, HIT_ROCKET);
1042 gPlayers[i].Push((dx*7) div mm, (dy*7) div mm);
1043 end;
1044 end;
1046 //g_Mons_ForEach(monsExCheck);
1047 g_Mons_ForEachAt(X-(rad+32), Y-(rad+32), (rad+32)*2, (rad+32)*2, monsExCheck);
1049 {$IFDEF ENABLE_CORPSES}
1050 h := High(gCorpses);
1051 if gAdvCorpses and (h <> -1) then
1052 begin
1053 for i := 0 to h do
1054 begin
1055 if (gCorpses[i] <> nil) and (gCorpses[i].State <> CORPSE_STATE_REMOVEME) then
1056 begin
1057 with gCorpses[i] do
1058 begin
1059 dx := Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2)-X;
1060 dy := Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)-Y;
1061 if dx > 1000 then dx := 1000;
1062 if dy > 1000 then dy := 1000;
1063 if dx*dx+dy*dy < r then
1064 begin
1065 m := PointToRect(X, Y, Obj.X+Obj.Rect.X, Obj.Y+Obj.Rect.Y, Obj.Rect.Width, Obj.Rect.Height);
1066 mm := Max(abs(dx), abs(dy));
1067 if mm = 0 then
1068 mm := 1;
1069 Damage(Round(100*(rad-m)/rad), SpawnerUID, (dx*10) div mm, (dy*10) div mm);
1070 end;
1071 end;
1072 end;
1073 end;
1074 end;
1075 {$ENDIF}
1077 {$IFDEF ENABLE_GIBS}
1078 h := High(gGibs);
1079 if gAdvGibs and (h <> -1) then
1080 for i := 0 to h do
1081 if gGibs[i].alive then
1082 with gGibs[i] do
1083 begin
1084 dx := Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2)-X;
1085 dy := Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)-Y;
1086 if dx > 1000 then dx := 1000;
1087 if dy > 1000 then dy := 1000;
1088 if dx*dx+dy*dy < r then
1089 begin
1090 m := PointToRect(X, Y, Obj.X+Obj.Rect.X, Obj.Y+Obj.Rect.Y,
1091 Obj.Rect.Width, Obj.Rect.Height);
1092 _angle := GetAngle(Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
1093 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2), X, Y);
1094 g_Obj_PushA(@Obj, Round(15*(rad-m)/rad), _angle);
1095 positionChanged(); // this updates spatial accelerators
1096 end;
1097 end;
1098 {$ENDIF}
1099 end;
1101 procedure g_Weapon_Init();
1102 begin
1103 CreateWaterMap();
1104 end;
1106 procedure g_Weapon_Free();
1107 begin
1108 Shots := nil;
1109 WaterMap := nil;
1110 end;
1112 procedure g_Weapon_LoadData();
1113 begin
1114 e_WriteLog('Loading weapons data...', TMsgType.Notify);
1115 g_Game_SetLoadingText(_lc[I_LOAD_WEAPONS_DATA], 0, False);
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 //wgunMonHash := hashNewIntInt();
1155 wgunHitHeap := TBinaryHeapHitTimes.Create();
1156 end;
1158 procedure g_Weapon_FreeData();
1159 begin
1160 e_WriteLog('Releasing weapons data...', TMsgType.Notify);
1162 g_Sound_Delete('SOUND_WEAPON_HITPUNCH');
1163 g_Sound_Delete('SOUND_WEAPON_MISSPUNCH');
1164 g_Sound_Delete('SOUND_WEAPON_HITBERSERK');
1165 g_Sound_Delete('SOUND_WEAPON_MISSBERSERK');
1166 g_Sound_Delete('SOUND_WEAPON_SELECTSAW');
1167 g_Sound_Delete('SOUND_WEAPON_IDLESAW');
1168 g_Sound_Delete('SOUND_WEAPON_HITSAW');
1169 g_Sound_Delete('SOUND_WEAPON_FIRESHOTGUN2');
1170 g_Sound_Delete('SOUND_WEAPON_FIRESHOTGUN');
1171 g_Sound_Delete('SOUND_WEAPON_FIRESAW');
1172 g_Sound_Delete('SOUND_WEAPON_FIREROCKET');
1173 g_Sound_Delete('SOUND_WEAPON_FIREPLASMA');
1174 g_Sound_Delete('SOUND_WEAPON_FIREPISTOL');
1175 g_Sound_Delete('SOUND_WEAPON_FIRECGUN');
1176 g_Sound_Delete('SOUND_WEAPON_FIREBFG');
1177 g_Sound_Delete('SOUND_FIRE');
1178 g_Sound_Delete('SOUND_IGNITE');
1179 g_Sound_Delete('SOUND_WEAPON_STARTFIREBFG');
1180 g_Sound_Delete('SOUND_WEAPON_EXPLODEROCKET');
1181 g_Sound_Delete('SOUND_WEAPON_EXPLODEBFG');
1182 g_Sound_Delete('SOUND_WEAPON_BFGWATER');
1183 g_Sound_Delete('SOUND_WEAPON_EXPLODEPLASMA');
1184 g_Sound_Delete('SOUND_WEAPON_PLASMAWATER');
1185 g_Sound_Delete('SOUND_WEAPON_FIREBALL');
1186 g_Sound_Delete('SOUND_WEAPON_EXPLODEBALL');
1187 g_Sound_Delete('SOUND_WEAPON_FIREREV');
1188 g_Sound_Delete('SOUND_WEAPON_FLAMEON');
1189 g_Sound_Delete('SOUND_WEAPON_FLAMEOFF');
1190 g_Sound_Delete('SOUND_WEAPON_FLAMEWORK');
1191 g_Sound_Delete('SOUND_PLAYER_JETFLY');
1192 g_Sound_Delete('SOUND_PLAYER_JETON');
1193 g_Sound_Delete('SOUND_PLAYER_JETOFF');
1194 g_Sound_Delete('SOUND_PLAYER_CASING1');
1195 g_Sound_Delete('SOUND_PLAYER_CASING2');
1196 g_Sound_Delete('SOUND_PLAYER_SHELL1');
1197 g_Sound_Delete('SOUND_PLAYER_SHELL2');
1198 end;
1201 function GunHitPlayer (X, Y: Integer; vx, vy: Integer; dmg: Integer; SpawnerUID: Word; AllowPush: Boolean): Boolean;
1202 var
1203 i: Integer;
1204 begin
1205 result := false;
1206 for i := 0 to High(gPlayers) do
1207 begin
1208 if (gPlayers[i] <> nil) and gPlayers[i].alive and gPlayers[i].Collide(X, Y) then
1209 begin
1210 if HitPlayer(gPlayers[i], dmg, vx*10, vy*10-3, SpawnerUID, HIT_SOME) then
1211 begin
1212 if AllowPush then gPlayers[i].Push(vx, vy);
1213 result := true;
1214 end;
1215 end;
1216 end;
1217 end;
1220 function GunHit (X, Y: Integer; vx, vy: Integer; dmg: Integer; SpawnerUID: Word; AllowPush: Boolean): Byte;
1222 function monsCheck (mon: TMonster): Boolean;
1223 begin
1224 result := false; // don't stop
1225 if HitMonster(mon, dmg, vx*10, vy*10-3, SpawnerUID, HIT_SOME) then
1226 begin
1227 if AllowPush then mon.Push(vx, vy);
1228 result := true;
1229 end;
1230 end;
1232 begin
1233 result := 0;
1234 if GunHitPlayer(X, Y, vx, vy, dmg, SpawnerUID, AllowPush) then result := 1
1235 else if g_Mons_ForEachAliveAt(X, Y, 1, 1, monsCheck) then result := 2;
1236 end;
1239 (*
1240 procedure g_Weapon_gunOld(const x, y, xd, yd, v, dmg: Integer; SpawnerUID: Word; CheckTrigger: Boolean);
1241 var
1242 a: Integer;
1243 x2, y2: Integer;
1244 dx, dy: Integer;
1245 xe, ye: Integer;
1246 xi, yi: Integer;
1247 s, c: Extended;
1248 //vx, vy: Integer;
1249 xx, yy, d: Integer;
1250 i: Integer;
1251 t1, _collide: Boolean;
1252 w, h: Word;
1253 {$IF DEFINED(D2F_DEBUG)}
1254 stt: UInt64;
1255 showTime: Boolean = true;
1256 {$ENDIF}
1257 begin
1258 a := GetAngle(x, y, xd, yd)+180;
1260 SinCos(DegToRad(-a), s, c);
1262 if Abs(s) < 0.01 then s := 0;
1263 if Abs(c) < 0.01 then c := 0;
1265 x2 := x+Round(c*gMapInfo.Width);
1266 y2 := y+Round(s*gMapInfo.Width);
1268 t1 := gWalls <> nil;
1269 _collide := False;
1270 w := gMapInfo.Width;
1271 h := gMapInfo.Height;
1273 xe := 0;
1274 ye := 0;
1275 dx := x2-x;
1276 dy := y2-y;
1278 if (xd = 0) and (yd = 0) then Exit;
1280 if dx > 0 then xi := 1 else if dx < 0 then xi := -1 else xi := 0;
1281 if dy > 0 then yi := 1 else if dy < 0 then yi := -1 else yi := 0;
1283 dx := Abs(dx);
1284 dy := Abs(dy);
1286 if dx > dy then d := dx else d := dy;
1288 //blood vel, for Monster.Damage()
1289 //vx := (dx*10 div d)*xi;
1290 //vy := (dy*10 div d)*yi;
1292 {$IF DEFINED(D2F_DEBUG)}
1293 stt := getTimeMicro();
1294 {$ENDIF}
1296 xx := x;
1297 yy := y;
1299 for i := 1 to d do
1300 begin
1301 xe := xe+dx;
1302 ye := ye+dy;
1304 if xe > d then
1305 begin
1306 xe := xe-d;
1307 xx := xx+xi;
1308 end;
1310 if ye > d then
1311 begin
1312 ye := ye-d;
1313 yy := yy+yi;
1314 end;
1316 if (yy > h) or (yy < 0) then Break;
1317 if (xx > w) or (xx < 0) then Break;
1319 if t1 then
1320 if ByteBool(gCollideMap[yy, xx] and MARK_BLOCKED) then
1321 begin
1322 _collide := True;
1323 {$IF DEFINED(D2F_DEBUG)}
1324 stt := getTimeMicro()-stt;
1325 e_WriteLog(Format('*** old trace time: %u microseconds', [LongWord(stt)]), MSG_NOTIFY);
1326 showTime := false;
1327 {$ENDIF}
1328 g_GFX_Spark(xx-xi, yy-yi, 2+Random(2), 180+a, 0, 0);
1329 if g_Game_IsServer and g_Game_IsNet then
1330 MH_SEND_Effect(xx-xi, yy-yi, 180+a, NET_GFX_SPARK);
1331 end;
1333 if not _collide then
1334 begin
1335 _collide := GunHit(xx, yy, xi*v, yi*v, dmg, SpawnerUID, v <> 0) <> 0;
1336 end;
1338 if _collide then Break;
1339 end;
1341 {$IF DEFINED(D2F_DEBUG)}
1342 if showTime then
1343 begin
1344 stt := getTimeMicro()-stt;
1345 e_WriteLog(Format('*** old trace time: %u microseconds', [LongWord(stt)]), MSG_NOTIFY);
1346 end;
1347 {$ENDIF}
1349 if CheckTrigger and g_Game_IsServer then
1350 g_Triggers_PressL(X, Y, xx-xi, yy-yi, SpawnerUID, ACTIVATE_SHOT);
1351 end;
1352 *)
1355 //!!!FIXME!!!
1356 procedure g_Weapon_gun (const x, y, xd, yd, v, indmg: Integer; SpawnerUID: Word; CheckTrigger: Boolean);
1357 var
1358 x0, y0: Integer;
1359 x2, y2: Integer;
1360 xi, yi: Integer;
1361 wallDistSq: Integer = $3fffffff;
1362 spawnerPlr: TPlayer = nil;
1363 dmg: Integer;
1365 function doPlayerHit (idx: Integer; hx, hy: Integer): Boolean;
1366 begin
1367 result := false;
1368 if (idx < 0) or (idx > High(gPlayers)) then exit;
1369 if (gPlayers[idx] = nil) or not gPlayers[idx].alive then exit;
1370 if (spawnerPlr <> nil) then
1371 begin
1372 if ((gGameSettings.Options and (GAME_OPTION_TEAMHITTRACE or GAME_OPTION_TEAMDAMAGE)) = 0) and
1373 (spawnerPlr.Team <> TEAM_NONE) and (spawnerPlr.Team = gPlayers[idx].Team) then
1374 begin
1375 if (spawnerPlr <> gPlayers[idx]) and ((gGameSettings.Options and GAME_OPTION_TEAMABSORBDAMAGE) = 0) then
1376 dmg := Max(1, dmg div 2);
1377 exit;
1378 end;
1379 end;
1380 result := HitPlayer(gPlayers[idx], dmg, (xi*v)*10, (yi*v)*10-3, SpawnerUID, HIT_SOME);
1381 if result and (v <> 0) then gPlayers[idx].Push((xi*v), (yi*v));
1382 {$IF DEFINED(D2F_DEBUG)}
1383 //if result then e_WriteLog(Format(' PLAYER #%d HIT', [idx]), MSG_NOTIFY);
1384 {$ENDIF}
1385 end;
1387 function doMonsterHit (mon: TMonster; hx, hy: Integer): Boolean;
1388 begin
1389 result := false;
1390 if (mon = nil) then exit;
1391 result := HitMonster(mon, dmg, (xi*v)*10, (yi*v)*10-3, SpawnerUID, HIT_SOME);
1392 if result and (v <> 0) then mon.Push((xi*v), (yi*v));
1393 {$IF DEFINED(D2F_DEBUG)}
1394 //if result then e_WriteLog(Format(' MONSTER #%u HIT', [LongWord(mon.UID)]), MSG_NOTIFY);
1395 {$ENDIF}
1396 end;
1398 // collect players along hitray
1399 // return `true` if instant hit was detected
1400 function playerPossibleHit (): Boolean;
1401 var
1402 i: Integer;
1403 px, py, pw, ph: Integer;
1404 inx, iny: Integer;
1405 distSq: Integer;
1406 plr: TPlayer;
1407 begin
1408 result := false;
1409 for i := 0 to High(gPlayers) do
1410 begin
1411 plr := gPlayers[i];
1412 if (plr <> nil) and plr.alive then
1413 begin
1414 plr.getMapBox(px, py, pw, ph);
1415 if lineAABBIntersects(x, y, x2, y2, px, py, pw, ph, inx, iny) then
1416 begin
1417 distSq := distanceSq(x, y, inx, iny);
1418 if (distSq = 0) then
1419 begin
1420 // contains
1421 if doPlayerHit(i, x, y) then begin result := true; exit; end;
1422 end
1423 else if (distSq < wallDistSq) then
1424 begin
1425 appendHitTimePlr(distSq, i, inx, iny);
1426 end;
1427 end;
1428 end;
1429 end;
1430 end;
1432 procedure sqchecker (mon: TMonster);
1433 var
1434 mx, my, mw, mh: Integer;
1435 inx, iny: Integer;
1436 distSq: Integer;
1437 begin
1438 mon.getMapBox(mx, my, mw, mh);
1439 if lineAABBIntersects(x0, y0, x2, y2, mx, my, mw, mh, inx, iny) then
1440 begin
1441 distSq := distanceSq(x0, y0, inx, iny);
1442 if (distSq < wallDistSq) then appendHitTimeMon(distSq, mon, inx, iny);
1443 end;
1444 end;
1446 var
1447 a: Integer;
1448 dx, dy: Integer;
1449 xe, ye: Integer;
1450 s, c: Extended;
1451 i: Integer;
1452 wallHitFlag: Boolean = false;
1453 wallHitX: Integer = 0;
1454 wallHitY: Integer = 0;
1455 didHit: Boolean = false;
1456 {$IF DEFINED(D2F_DEBUG)}
1457 stt: UInt64;
1458 {$ENDIF}
1459 mit: PMonster;
1460 it: TMonsterGrid.Iter;
1461 begin
1462 (*
1463 if not gwep_debug_fast_trace then
1464 begin
1465 g_Weapon_gunOld(x, y, xd, yd, v, dmg, SpawnerUID, CheckTrigger);
1466 exit;
1467 end;
1468 *)
1470 if (xd = 0) and (yd = 0) then exit;
1472 if (g_GetUIDType(SpawnerUID) = UID_PLAYER) then
1473 spawnerPlr := g_Player_Get(SpawnerUID);
1475 dmg := indmg;
1477 //wgunMonHash.reset(); //FIXME: clear hash on level change
1478 wgunHitHeap.clear();
1479 wgunHitTimeUsed := 0;
1481 a := GetAngle(x, y, xd, yd)+180;
1483 SinCos(DegToRad(-a), s, c);
1485 if Abs(s) < 0.01 then s := 0;
1486 if Abs(c) < 0.01 then c := 0;
1488 x0 := x;
1489 y0 := y;
1490 x2 := x+Round(c*gMapInfo.Width);
1491 y2 := y+Round(s*gMapInfo.Width);
1493 dx := x2-x;
1494 dy := y2-y;
1496 if (dx > 0) then xi := 1 else if (dx < 0) then xi := -1 else xi := 0;
1497 if (dy > 0) then yi := 1 else if (dy < 0) then yi := -1 else yi := 0;
1499 {$IF DEFINED(D2F_DEBUG)}
1500 e_WriteLog(Format('GUN TRACE: (%d,%d) to (%d,%d)', [x, y, x2, y2]), TMsgType.Notify);
1501 stt := getTimeMicro();
1502 {$ENDIF}
1504 wallHitFlag := (g_Map_traceToNearestWall(x, y, x2, y2, @wallHitX, @wallHitY) <> nil);
1505 if wallHitFlag then
1506 begin
1507 x2 := wallHitX;
1508 y2 := wallHitY;
1509 wallDistSq := distanceSq(x, y, wallHitX, wallHitY);
1510 end
1511 else
1512 begin
1513 wallHitX := x2;
1514 wallHitY := y2;
1515 end;
1517 if playerPossibleHit() then exit; // instant hit
1519 // collect monsters
1520 //g_Mons_AlongLine(x, y, x2, y2, sqchecker);
1522 it := monsGrid.forEachAlongLine(x, y, x2, y2, -1);
1523 for mit in it do sqchecker(mit^);
1524 it.release();
1526 // here, we collected all monsters and players in `wgunHitHeap` and `wgunHitTime`
1527 // also, if `wallWasHit` is `true`, then `wallHitX` and `wallHitY` contains spark coords
1528 while (wgunHitHeap.count > 0) do
1529 begin
1530 // has some entities to check, do it
1531 i := wgunHitHeap.front;
1532 wgunHitHeap.popFront();
1533 // hitpoint
1534 xe := wgunHitTime[i].x;
1535 ye := wgunHitTime[i].y;
1536 // check if it is not behind the wall
1537 if (wgunHitTime[i].mon <> nil) then
1538 begin
1539 didHit := doMonsterHit(wgunHitTime[i].mon, xe, ye);
1540 end
1541 else
1542 begin
1543 didHit := doPlayerHit(wgunHitTime[i].plridx, xe, ye);
1544 end;
1545 if didHit then
1546 begin
1547 // need new coords for trigger
1548 wallHitX := xe;
1549 wallHitY := ye;
1550 wallHitFlag := false; // no sparks
1551 break;
1552 end;
1553 end;
1555 // need sparks?
1556 if wallHitFlag then
1557 begin
1558 {$IF DEFINED(D2F_DEBUG)}
1559 stt := getTimeMicro()-stt;
1560 e_WriteLog(Format('*** new trace time: %u microseconds', [LongWord(stt)]), TMsgType.Notify);
1561 {$ENDIF}
1562 {$IFDEF ENABLE_GFX}
1563 g_GFX_Spark(wallHitX, wallHitY, 2+Random(2), 180+a, 0, 0);
1564 {$ENDIF}
1565 if g_Game_IsServer and g_Game_IsNet then MH_SEND_Effect(wallHitX, wallHitY, 180+a, NET_GFX_SPARK);
1566 end
1567 else
1568 begin
1569 {$IF DEFINED(D2F_DEBUG)}
1570 stt := getTimeMicro()-stt;
1571 e_WriteLog(Format('*** new trace time: %u microseconds', [LongWord(stt)]), TMsgType.Notify);
1572 {$ENDIF}
1573 end;
1575 if CheckTrigger and g_Game_IsServer then g_Triggers_PressL(X, Y, wallHitX, wallHitY, SpawnerUID, ACTIVATE_SHOT);
1576 end;
1579 procedure g_Weapon_punch(x, y: Integer; d, SpawnerUID: Word);
1580 var
1581 obj: TObj;
1582 begin
1583 obj.X := X;
1584 obj.Y := Y;
1585 obj.rect.X := 0;
1586 obj.rect.Y := 0;
1587 obj.rect.Width := 39;
1588 obj.rect.Height := 52;
1589 obj.Vel.X := 0;
1590 obj.Vel.Y := 0;
1591 obj.Accel.X := 0;
1592 obj.Accel.Y := 0;
1594 if g_Weapon_Hit(@obj, d, SpawnerUID, HIT_SOME) <> 0 then
1595 g_Sound_PlayExAt('SOUND_WEAPON_HITPUNCH', x, y)
1596 else
1597 g_Sound_PlayExAt('SOUND_WEAPON_MISSPUNCH', x, y);
1598 end;
1600 function g_Weapon_chainsaw(x, y: Integer; d, SpawnerUID: Word): Integer;
1601 var
1602 obj: TObj;
1603 begin
1604 obj.X := X;
1605 obj.Y := Y;
1606 obj.rect.X := 0;
1607 obj.rect.Y := 0;
1608 obj.rect.Width := 32;
1609 obj.rect.Height := 52;
1610 obj.Vel.X := 0;
1611 obj.Vel.Y := 0;
1612 obj.Accel.X := 0;
1613 obj.Accel.Y := 0;
1615 Result := g_Weapon_Hit(@obj, d, SpawnerUID, HIT_SOME);
1616 end;
1618 procedure g_Weapon_rocket(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1619 Silent: Boolean = False; compat: Boolean = true);
1620 var
1621 find_id: DWORD;
1622 dx, dy: Integer;
1623 begin
1624 if WID < 0 then
1625 find_id := FindShot()
1626 else
1627 begin
1628 find_id := WID;
1629 if Integer(find_id) >= High(Shots) then
1630 SetLength(Shots, find_id + 64)
1631 end;
1633 with Shots[find_id] do
1634 begin
1635 g_Obj_Init(@Obj);
1637 Obj.Rect.Width := SHOT_ROCKETLAUNCHER_WIDTH;
1638 Obj.Rect.Height := SHOT_ROCKETLAUNCHER_HEIGHT;
1640 if compat then
1641 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1642 else
1643 dx := -(Obj.Rect.Width div 2);
1644 dy := -(Obj.Rect.Height div 2);
1646 ShotType := WEAPON_ROCKETLAUNCHER;
1647 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 12);
1649 triggers := nil;
1650 time := gTime;
1651 end;
1653 Shots[find_id].SpawnerUID := SpawnerUID;
1655 if not Silent then
1656 g_Sound_PlayExAt('SOUND_WEAPON_FIREROCKET', x, y);
1657 end;
1659 procedure g_Weapon_revf(x, y, xd, yd: Integer; SpawnerUID, TargetUID: Word;
1660 WID: Integer = -1; Silent: Boolean = False);
1661 var
1662 find_id: DWORD;
1663 dx, dy: Integer;
1664 begin
1665 if WID < 0 then
1666 find_id := FindShot()
1667 else
1668 begin
1669 find_id := WID;
1670 if Integer(find_id) >= High(Shots) then
1671 SetLength(Shots, find_id + 64)
1672 end;
1674 with Shots[find_id] do
1675 begin
1676 g_Obj_Init(@Obj);
1678 Obj.Rect.Width := SHOT_SKELFIRE_WIDTH;
1679 Obj.Rect.Height := SHOT_SKELFIRE_HEIGHT;
1681 dx := -(Obj.Rect.Width div 2);
1682 dy := -(Obj.Rect.Height div 2);
1684 ShotType := WEAPON_SKEL_FIRE;
1685 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 12);
1687 triggers := nil;
1688 target := TargetUID;
1689 time := gTime;
1690 end;
1692 Shots[find_id].SpawnerUID := SpawnerUID;
1694 if not Silent then
1695 g_Sound_PlayExAt('SOUND_WEAPON_FIREREV', x, y);
1696 end;
1698 procedure g_Weapon_plasma(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1699 Silent: Boolean = False; compat: Boolean = true);
1700 var
1701 find_id: DWORD;
1702 dx, dy: Integer;
1703 begin
1704 if WID < 0 then
1705 find_id := FindShot()
1706 else
1707 begin
1708 find_id := WID;
1709 if Integer(find_id) >= High(Shots) then
1710 SetLength(Shots, find_id + 64);
1711 end;
1713 with Shots[find_id] do
1714 begin
1715 g_Obj_Init(@Obj);
1717 Obj.Rect.Width := SHOT_PLASMA_WIDTH;
1718 Obj.Rect.Height := SHOT_PLASMA_HEIGHT;
1720 if compat then
1721 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1722 else
1723 dx := -(Obj.Rect.Width div 2);
1724 dy := -(Obj.Rect.Height div 2);
1726 ShotType := WEAPON_PLASMA;
1727 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1729 triggers := nil;
1730 time := gTime;
1731 end;
1733 Shots[find_id].SpawnerUID := SpawnerUID;
1735 if not Silent then
1736 g_Sound_PlayExAt('SOUND_WEAPON_FIREPLASMA', x, y);
1737 end;
1739 procedure g_Weapon_flame(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1740 Silent: Boolean = False; compat: Boolean = true);
1741 var
1742 find_id: DWORD;
1743 dx, dy: Integer;
1744 begin
1745 if WID < 0 then
1746 find_id := FindShot()
1747 else
1748 begin
1749 find_id := WID;
1750 if Integer(find_id) >= High(Shots) then
1751 SetLength(Shots, find_id + 64);
1752 end;
1754 with Shots[find_id] do
1755 begin
1756 g_Obj_Init(@Obj);
1758 Obj.Rect.Width := SHOT_FLAME_WIDTH;
1759 Obj.Rect.Height := SHOT_FLAME_HEIGHT;
1761 if compat then
1762 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1763 else
1764 dx := -(Obj.Rect.Width div 2);
1765 dy := -(Obj.Rect.Height div 2);
1767 ShotType := WEAPON_FLAMETHROWER;
1768 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1770 triggers := nil;
1771 time := gTime;
1772 end;
1774 Shots[find_id].SpawnerUID := SpawnerUID;
1776 // if not Silent then
1777 // g_Sound_PlayExAt('SOUND_WEAPON_FIREPLASMA', x, y);
1778 end;
1780 procedure g_Weapon_ball1(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1781 Silent: Boolean = False; compat: Boolean = true);
1782 var
1783 find_id: DWORD;
1784 dx, dy: Integer;
1785 begin
1786 if WID < 0 then
1787 find_id := FindShot()
1788 else
1789 begin
1790 find_id := WID;
1791 if Integer(find_id) >= High(Shots) then
1792 SetLength(Shots, find_id + 64)
1793 end;
1795 with Shots[find_id] do
1796 begin
1797 g_Obj_Init(@Obj);
1799 Obj.Rect.Width := 16;
1800 Obj.Rect.Height := 16;
1802 if compat then
1803 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1804 else
1805 dx := -(Obj.Rect.Width div 2);
1806 dy := -(Obj.Rect.Height div 2);
1808 ShotType := WEAPON_IMP_FIRE;
1809 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1811 triggers := nil;
1812 time := gTime;
1813 end;
1815 Shots[find_id].SpawnerUID := SpawnerUID;
1817 if not Silent then
1818 g_Sound_PlayExAt('SOUND_WEAPON_FIREBALL', x, y);
1819 end;
1821 procedure g_Weapon_ball2(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1822 Silent: Boolean = False; compat: Boolean = true);
1823 var
1824 find_id: DWORD;
1825 dx, dy: Integer;
1826 begin
1827 if WID < 0 then
1828 find_id := FindShot()
1829 else
1830 begin
1831 find_id := WID;
1832 if Integer(find_id) >= High(Shots) then
1833 SetLength(Shots, find_id + 64)
1834 end;
1836 with Shots[find_id] do
1837 begin
1838 g_Obj_Init(@Obj);
1840 Obj.Rect.Width := 16;
1841 Obj.Rect.Height := 16;
1843 if compat then
1844 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1845 else
1846 dx := -(Obj.Rect.Width div 2);
1847 dy := -(Obj.Rect.Height div 2);
1849 ShotType := WEAPON_CACO_FIRE;
1850 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1852 triggers := nil;
1853 time := gTime;
1854 end;
1856 Shots[find_id].SpawnerUID := SpawnerUID;
1858 if not Silent then
1859 g_Sound_PlayExAt('SOUND_WEAPON_FIREBALL', x, y);
1860 end;
1862 procedure g_Weapon_ball7(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1863 Silent: Boolean = False; compat: Boolean = true);
1864 var
1865 find_id: DWORD;
1866 dx, dy: Integer;
1867 begin
1868 if WID < 0 then
1869 find_id := FindShot()
1870 else
1871 begin
1872 find_id := WID;
1873 if Integer(find_id) >= High(Shots) then
1874 SetLength(Shots, find_id + 64)
1875 end;
1877 with Shots[find_id] do
1878 begin
1879 g_Obj_Init(@Obj);
1881 Obj.Rect.Width := 32;
1882 Obj.Rect.Height := 16;
1884 if compat then
1885 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1886 else
1887 dx := -(Obj.Rect.Width div 2);
1888 dy := -(Obj.Rect.Height div 2);
1890 ShotType := WEAPON_BARON_FIRE;
1891 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1893 triggers := nil;
1894 time := gTime;
1895 end;
1897 Shots[find_id].SpawnerUID := SpawnerUID;
1899 if not Silent then
1900 g_Sound_PlayExAt('SOUND_WEAPON_FIREBALL', x, y);
1901 end;
1903 procedure g_Weapon_aplasma(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1904 Silent: Boolean = False; compat: Boolean = true);
1905 var
1906 find_id: DWORD;
1907 dx, dy: Integer;
1908 begin
1909 if WID < 0 then
1910 find_id := FindShot()
1911 else
1912 begin
1913 find_id := WID;
1914 if Integer(find_id) >= High(Shots) then
1915 SetLength(Shots, find_id + 64)
1916 end;
1918 with Shots[find_id] do
1919 begin
1920 g_Obj_Init(@Obj);
1922 Obj.Rect.Width := 16;
1923 Obj.Rect.Height := 16;
1925 if compat then
1926 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1927 else
1928 dx := -(Obj.Rect.Width div 2);
1929 dy := -(Obj.Rect.Height div 2);
1931 ShotType := WEAPON_BSP_FIRE;
1932 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1934 triggers := nil;
1935 time := gTime;
1936 end;
1938 Shots[find_id].SpawnerUID := SpawnerUID;
1940 if not Silent then
1941 g_Sound_PlayExAt('SOUND_WEAPON_FIREPLASMA', x, y);
1942 end;
1944 procedure g_Weapon_manfire(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1945 Silent: Boolean = False; compat: Boolean = true);
1946 var
1947 find_id: DWORD;
1948 dx, dy: Integer;
1949 begin
1950 if WID < 0 then
1951 find_id := FindShot()
1952 else
1953 begin
1954 find_id := WID;
1955 if Integer(find_id) >= High(Shots) then
1956 SetLength(Shots, find_id + 64)
1957 end;
1959 with Shots[find_id] do
1960 begin
1961 g_Obj_Init(@Obj);
1963 Obj.Rect.Width := 32;
1964 Obj.Rect.Height := 32;
1966 if compat then
1967 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1968 else
1969 dx := -(Obj.Rect.Width div 2);
1970 dy := -(Obj.Rect.Height div 2);
1972 ShotType := WEAPON_MANCUB_FIRE;
1973 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1975 triggers := nil;
1976 time := gTime;
1977 end;
1979 Shots[find_id].SpawnerUID := SpawnerUID;
1981 if not Silent then
1982 g_Sound_PlayExAt('SOUND_WEAPON_FIREBALL', x, y);
1983 end;
1985 procedure g_Weapon_bfgshot(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1986 Silent: Boolean = False; compat: Boolean = true);
1987 var
1988 find_id: DWORD;
1989 dx, dy: Integer;
1990 begin
1991 if WID < 0 then
1992 find_id := FindShot()
1993 else
1994 begin
1995 find_id := WID;
1996 if Integer(find_id) >= High(Shots) then
1997 SetLength(Shots, find_id + 64)
1998 end;
2000 with Shots[find_id] do
2001 begin
2002 g_Obj_Init(@Obj);
2004 Obj.Rect.Width := SHOT_BFG_WIDTH;
2005 Obj.Rect.Height := SHOT_BFG_HEIGHT;
2007 if compat then
2008 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
2009 else
2010 dx := -(Obj.Rect.Width div 2);
2011 dy := -(Obj.Rect.Height div 2);
2013 ShotType := WEAPON_BFG;
2014 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
2016 triggers := nil;
2017 time := gTime;
2018 end;
2020 Shots[find_id].SpawnerUID := SpawnerUID;
2022 if not Silent then
2023 g_Sound_PlayExAt('SOUND_WEAPON_FIREBFG', x, y);
2024 end;
2026 procedure g_Weapon_bfghit(x, y: Integer);
2027 begin
2028 {$IFDEF ENABLE_GFX}
2029 g_GFX_QueueEffect(R_GFX_BFG_HIT, x - 32, y - 32);
2030 {$ENDIF}
2031 end;
2033 procedure g_Weapon_pistol(x, y, xd, yd: Integer; SpawnerUID: Word;
2034 Silent: Boolean = False);
2035 begin
2036 if not Silent then
2037 g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', x, y);
2039 g_Weapon_gun(x, y, xd, yd, 1, 3, SpawnerUID, True);
2040 if gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF] then
2041 begin
2042 if ABS(x-xd) >= ABS(y-yd) then
2043 begin
2044 g_Weapon_gun(x, y+1, xd, yd+1, 1, 3, SpawnerUID, False);
2045 g_Weapon_gun(x, y-1, xd, yd-1, 1, 2, SpawnerUID, False);
2046 end
2047 else
2048 begin
2049 g_Weapon_gun(x+1, y, xd+1, yd, 1, 3, SpawnerUID, False);
2050 g_Weapon_gun(x-1, y, xd-1, yd, 1, 2, SpawnerUID, False);
2051 end;
2052 end;
2053 end;
2055 procedure g_Weapon_mgun(x, y, xd, yd: Integer; SpawnerUID: Word;
2056 Silent: Boolean = False);
2057 begin
2058 if not Silent then
2059 if gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', x, y);
2061 g_Weapon_gun(x, y, xd, yd, 1, 3, SpawnerUID, True);
2062 if (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF]) and
2063 (g_GetUIDType(SpawnerUID) = UID_PLAYER) then
2064 begin
2065 if ABS(x-xd) >= ABS(y-yd) then
2066 begin
2067 g_Weapon_gun(x, y+1, xd, yd+1, 1, 2, SpawnerUID, False);
2068 g_Weapon_gun(x, y-1, xd, yd-1, 1, 2, SpawnerUID, False);
2069 end
2070 else
2071 begin
2072 g_Weapon_gun(x+1, y, xd+1, yd, 1, 2, SpawnerUID, False);
2073 g_Weapon_gun(x-1, y, xd-1, yd, 1, 2, SpawnerUID, False);
2074 end;
2075 end;
2076 end;
2078 procedure g_Weapon_shotgun(x, y, xd, yd: Integer; SpawnerUID: Word;
2079 Silent: Boolean = False);
2080 var
2081 i, j, k: Integer;
2082 begin
2083 if not Silent then
2084 if gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', x, y);
2086 for i := 0 to 9 do
2087 begin
2088 j := 0; k := 0;
2089 if ABS(x-xd) >= ABS(y-yd) then j := Random(17) - 8 else k := Random(17) - 8; // -8 .. 8
2090 g_Weapon_gun(x+k, y+j, xd+k, yd+j, IfThen(i mod 2 <> 0, 1, 0), 3, SpawnerUID, i=0);
2091 end;
2092 end;
2094 procedure g_Weapon_dshotgun(x, y, xd, yd: Integer; SpawnerUID: Word;
2095 Silent: Boolean = False);
2096 var
2097 a, i, j, k: Integer;
2098 begin
2099 if not Silent then
2100 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN2', x, y);
2102 if gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF] then a := 25 else a := 20;
2103 for i := 0 to a do
2104 begin
2105 j := 0; k := 0;
2106 if ABS(x-xd) >= ABS(y-yd) then j := Random(41) - 20 else k := Random(41) - 20; // -20 .. 20
2107 g_Weapon_gun(x+k, y+j, xd+k, yd+j, IfThen(i mod 3 <> 0, 0, 1), 3, SpawnerUID, i=0);
2108 end;
2109 end;
2111 procedure g_Weapon_PreUpdate();
2112 var
2113 i: Integer;
2114 begin
2115 if Shots = nil then Exit;
2116 for i := 0 to High(Shots) do
2117 if Shots[i].ShotType <> 0 then
2118 begin
2119 Shots[i].Obj.oldX := Shots[i].Obj.X;
2120 Shots[i].Obj.oldY := Shots[i].Obj.Y;
2121 end;
2122 end;
2124 procedure g_Weapon_Update();
2125 var
2126 i, a, h, cx, cy, oldvx, oldvy, tf: Integer;
2127 t: DWArray;
2128 st: Word;
2129 o: TObj;
2130 spl: Boolean;
2131 Loud: Boolean;
2132 {$IFDEF ENABLE_GFX}
2133 var tcx, tcy: Integer;
2134 {$ENDIF}
2135 begin
2136 if Shots = nil then
2137 Exit;
2139 for i := 0 to High(Shots) do
2140 begin
2141 if Shots[i].ShotType = 0 then
2142 Continue;
2144 Loud := True;
2146 with Shots[i] do
2147 begin
2148 Timeout := Timeout - 1;
2149 oldvx := Obj.Vel.X;
2150 oldvy := Obj.Vel.Y;
2151 // Àêòèâèðîâàòü òðèããåðû ïî ïóòè (êðîìå óæå àêòèâèðîâàííûõ):
2152 if (Stopped = 0) and g_Game_IsServer then
2153 t := g_Triggers_PressR(Obj.X, Obj.Y, Obj.Rect.Width, Obj.Rect.Height,
2154 SpawnerUID, ACTIVATE_SHOT, triggers)
2155 else
2156 t := nil;
2158 if t <> nil then
2159 begin
2160 // Ïîïîëíÿåì ñïèñîê àêòèâèðîâàííûõ òðèããåðîâ:
2161 if triggers = nil then
2162 triggers := t
2163 else
2164 begin
2165 h := High(t);
2167 for a := 0 to h do
2168 if not InDWArray(t[a], triggers) then
2169 begin
2170 SetLength(triggers, Length(triggers)+1);
2171 triggers[High(triggers)] := t[a];
2172 end;
2173 end;
2174 end;
2176 // Äâèæåíèå:
2177 spl := (ShotType <> WEAPON_PLASMA) and
2178 (ShotType <> WEAPON_BFG) and
2179 (ShotType <> WEAPON_BSP_FIRE) and
2180 (ShotType <> WEAPON_FLAMETHROWER);
2182 if Stopped = 0 then
2183 begin
2184 st := g_Obj_Move_Projectile(@Obj, False, spl);
2185 end
2186 else
2187 begin
2188 st := 0;
2189 end;
2190 positionChanged(); // this updates spatial accelerators
2192 if WordBool(st and MOVE_FALLOUT) or (Obj.X < -1000) or
2193 (Obj.X > gMapInfo.Width+1000) or (Obj.Y < -1000) then
2194 begin
2195 // Íà êëèåíòå ñêîðåå âñåãî è òàê óæå âûïàë.
2196 ShotType := 0;
2197 time := 0;
2198 Continue;
2199 end;
2201 cx := Obj.X + (Obj.Rect.Width div 2);
2202 cy := Obj.Y + (Obj.Rect.Height div 2);
2204 case ShotType of
2205 WEAPON_ROCKETLAUNCHER, WEAPON_SKEL_FIRE: // Ðàêåòû è ñíàðÿäû Ñêåëåòà
2206 begin
2207 // Âûëåòåëà èç âîäû:
2208 if WordBool(st and MOVE_HITAIR) then
2209 g_Obj_SetSpeed(@Obj, 12);
2211 // Â âîäå øëåéô - ïóçûðè, â âîçäóõå øëåéô - äûì:
2212 if WordBool(st and MOVE_INWATER) then
2213 begin
2214 {$IFDEF ENABLE_GFX}
2215 g_GFX_Bubbles(cx, cy, 1+Random(3), 16, 16);
2216 {$ENDIF}
2217 if Random(2) = 0
2218 then g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', cx, cy)
2219 else g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', cx, cy);
2220 end
2221 else
2222 begin
2223 {$IFDEF ENABLE_GFX}
2224 g_GFX_QueueEffect(R_GFX_SMOKE_TRANS, Obj.X-14+Random(9), cy-20+Random(9));
2225 {$ENDIF}
2226 end;
2228 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2229 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL)) or
2230 (g_Weapon_Hit(@Obj, 10, SpawnerUID, HIT_SOME, False) <> 0) or
2231 (Timeout < 1) then
2232 begin
2233 Obj.Vel.X := 0;
2234 Obj.Vel.Y := 0;
2236 g_Weapon_Explode(cx, cy, 60, SpawnerUID);
2238 if ShotType = WEAPON_SKEL_FIRE then
2239 begin // Âçðûâ ñíàðÿäà Ñêåëåòà
2240 {$IFDEF ENABLE_GFX}
2241 g_GFX_QueueEffect(R_GFX_EXPLODE_SKELFIRE, Obj.X + 32 - 58, Obj.Y + 8 - 36);
2242 g_DynLightExplosion((Obj.X+32), (Obj.Y+8), 64, 1, 0, 0);
2243 {$ENDIF}
2244 end
2245 else
2246 begin // Âçðûâ Ðàêåòû
2247 {$IFDEF ENABLE_GFX}
2248 g_GFX_QueueEffect(R_GFX_EXPLODE_ROCKET, cx - 64, cy - 64);
2249 g_DynLightExplosion(cx, cy, 64, 1, 0, 0);
2250 {$ENDIF}
2251 end;
2253 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEROCKET', Obj.X, Obj.Y);
2255 ShotType := 0;
2256 end;
2258 if ShotType = WEAPON_SKEL_FIRE then
2259 begin // Ñàìîíàâîäêà ñíàðÿäà Ñêåëåòà:
2260 if GetPos(target, @o) then
2261 throw(i, Obj.X, Obj.Y,
2262 o.X+o.Rect.X+(o.Rect.Width div 2)+o.Vel.X+o.Accel.X,
2263 o.Y+o.Rect.Y+(o.Rect.Height div 2)+o.Vel.Y+o.Accel.Y,
2264 12);
2265 end;
2266 end;
2268 WEAPON_PLASMA, WEAPON_BSP_FIRE: // Ïëàçìà, ïëàçìà Àðàõíàòðîíà
2269 begin
2270 // Ïîïàëà â âîäó - ýëåêòðîøîê ïî âîäå:
2271 if WordBool(st and (MOVE_INWATER or MOVE_HITWATER)) then
2272 begin
2273 g_Sound_PlayExAt('SOUND_WEAPON_PLASMAWATER', Obj.X, Obj.Y);
2274 if g_Game_IsServer then CheckTrap(i, 10, HIT_ELECTRO);
2275 ShotType := 0;
2276 Continue;
2277 end;
2279 // Âåëè÷èíà óðîíà:
2280 if (ShotType = WEAPON_PLASMA) and
2281 (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF]) then
2282 a := 10
2283 else
2284 a := 5;
2286 if ShotType = WEAPON_BSP_FIRE then
2287 a := 10;
2289 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2290 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL)) or
2291 (g_Weapon_Hit(@Obj, a, SpawnerUID, HIT_SOME, False) <> 0) or
2292 (Timeout < 1) then
2293 begin
2294 {$IFDEF ENABLE_GFX}
2295 if ShotType = WEAPON_PLASMA then
2296 g_GFX_QueueEffect(R_GFX_EXPLODE_PLASMA, cx - 16, cy - 16)
2297 else
2298 g_GFX_QueueEffect(R_GFX_EXPLODE_BSPFIRE, cx - 16, cy - 16);
2299 g_DynLightExplosion(cx, cy, 32, 0, 0.5, 0.5);
2300 {$ENDIF}
2301 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEPLASMA', Obj.X, Obj.Y);
2302 ShotType := 0;
2303 end;
2304 end;
2306 WEAPON_FLAMETHROWER: // Îãíåìåò
2307 begin
2308 // Ñî âðåìåíåì óìèðàåò
2309 if (Timeout < 1) then
2310 begin
2311 ShotType := 0;
2312 Continue;
2313 end;
2314 // Ïîä âîäîé òîæå
2315 if WordBool(st and (MOVE_HITWATER or MOVE_INWATER)) then
2316 begin
2317 if WordBool(st and MOVE_HITWATER) then
2318 begin
2319 {$IFDEF ENABLE_GFX}
2320 tcx := Random(8);
2321 tcy := Random(8);
2322 g_GFX_QueueEffect(R_GFX_SMOKE, cx-4+tcx-(R_GFX_SMOKE_WIDTH div 2), cy-4+tcy-(R_GFX_SMOKE_HEIGHT div 2));
2323 {$ENDIF}
2324 end
2325 else
2326 begin
2327 {$IFDEF ENABLE_GFX}
2328 g_GFX_Bubbles(cx, cy, 1+Random(3), 16, 16);
2329 {$ENDIF}
2330 if Random(2) = 0
2331 then g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', cx, cy)
2332 else g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', cx, cy);
2333 end;
2334 ShotType := 0;
2335 Continue;
2336 end;
2338 // Ãðàâèòàöèÿ
2339 if Stopped = 0 then
2340 Obj.Accel.Y := Obj.Accel.Y + 1;
2341 // Ïîïàëè â ñòåíó èëè â âîäó:
2342 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL or MOVE_HITWATER)) then
2343 begin
2344 // Ïðèëèïàåì:
2345 Obj.Vel.X := 0;
2346 Obj.Vel.Y := 0;
2347 Obj.Accel.Y := 0;
2348 if WordBool(st and MOVE_HITWALL) then
2349 Stopped := MOVE_HITWALL
2350 else if WordBool(st and MOVE_HITLAND) then
2351 Stopped := MOVE_HITLAND
2352 else if WordBool(st and MOVE_HITCEIL) then
2353 Stopped := MOVE_HITCEIL;
2354 end;
2356 a := IfThen(Stopped = 0, 10, 1);
2357 // Åñëè â êîãî-òî ïîïàëè
2358 if g_Weapon_Hit(@Obj, a, SpawnerUID, HIT_FLAME, False) <> 0 then
2359 begin
2360 // HIT_FLAME ñàì ïîäîææåò
2361 // Åñëè â ïîëåòå ïîïàëè, èñ÷åçàåì
2362 if Stopped = 0 then
2363 ShotType := 0;
2364 end;
2366 if Stopped = 0 then
2367 tf := 2
2368 else
2369 tf := 3;
2371 if (gTime mod LongWord(tf) = 0) then
2372 begin
2373 {$IFDEF ENABLE_GFX}
2374 case Stopped of
2375 MOVE_HITWALL: begin tcx := cx-4+Random(8); tcy := cy-12+Random(24); end;
2376 MOVE_HITLAND: begin tcx := cx-12+Random(24); tcy := cy-10+Random(8); end;
2377 MOVE_HITCEIL: begin tcx := cx-12+Random(24); tcy := cy+6+Random(8); end;
2378 else begin tcx := cx-4+Random(8); tcy := cy-4+Random(8); end;
2379 end;
2380 g_GFX_QueueEffect(R_GFX_FLAME_RAND, tcx - (R_GFX_FLAME_WIDTH div 2), tcy - (R_GFX_FLAME_HEIGHT div 2));
2381 //g_DynLightExplosion(tcx, tcy, 1, 1, 0.8, 0.3);
2382 {$ENDIF}
2383 end;
2384 end;
2386 WEAPON_BFG: // BFG
2387 begin
2388 // Ïîïàëà â âîäó - ýëåêòðîøîê ïî âîäå:
2389 if WordBool(st and (MOVE_INWATER or MOVE_HITWATER)) then
2390 begin
2391 g_Sound_PlayExAt('SOUND_WEAPON_BFGWATER', Obj.X, Obj.Y);
2392 if g_Game_IsServer then CheckTrap(i, 1000, HIT_ELECTRO);
2393 ShotType := 0;
2394 Continue;
2395 end;
2397 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2398 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL)) or
2399 (g_Weapon_Hit(@Obj, SHOT_BFG_DAMAGE, SpawnerUID, HIT_BFG, False) <> 0) or
2400 (Timeout < 1) then
2401 begin
2402 // Ëó÷è BFG:
2403 if g_Game_IsServer then g_Weapon_BFG9000(cx, cy, SpawnerUID);
2404 {$IFDEF ENABLE_GFX}
2405 g_GFX_QueueEffect(R_GFX_EXPLODE_BFG, cx - 64, cy - 64);
2406 g_DynLightExplosion(cx, cy, 96, 0, 1, 0);
2407 {$ENDIF}
2408 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBFG', Obj.X, Obj.Y);
2409 ShotType := 0;
2410 end;
2411 end;
2413 WEAPON_IMP_FIRE, WEAPON_CACO_FIRE, WEAPON_BARON_FIRE: // Âûñòðåëû Áåñà, Êàêîäåìîíà Ðûöàðÿ/Áàðîíà àäà
2414 begin
2415 // Âûëåòåë èç âîäû:
2416 if WordBool(st and MOVE_HITAIR) then
2417 g_Obj_SetSpeed(@Obj, 16);
2419 // Âåëè÷èíà óðîíà:
2420 if ShotType = WEAPON_IMP_FIRE then
2421 a := 5
2422 else
2423 if ShotType = WEAPON_CACO_FIRE then
2424 a := 20
2425 else
2426 a := 40;
2428 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2429 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL)) or
2430 (g_Weapon_Hit(@Obj, a, SpawnerUID, HIT_SOME) <> 0) or
2431 (Timeout < 1) then
2432 begin
2433 {$IFDEF ENABLE_GFX}
2434 case ShotType of
2435 WEAPON_IMP_FIRE: g_GFX_QueueEffect(R_GFX_EXPLODE_IMPFIRE, cx - 32, cy - 32);
2436 WEAPON_CACO_FIRE: g_GFX_QueueEffect(R_GFX_EXPLODE_CACOFIRE, cx - 32, cy - 32);
2437 WEAPON_BARON_FIRE: g_GFX_QueueEffect(R_GFX_EXPLODE_BARONFIRE, cx - 32, cy - 32);
2438 end;
2439 {$ENDIF}
2440 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBALL', Obj.X, Obj.Y);
2441 ShotType := 0;
2442 end;
2443 end;
2445 WEAPON_MANCUB_FIRE: // Âûñòðåë Ìàíêóáóñà
2446 begin
2447 // Âûëåòåë èç âîäû:
2448 if WordBool(st and MOVE_HITAIR) then
2449 g_Obj_SetSpeed(@Obj, 16);
2451 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2452 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL)) or
2453 (g_Weapon_Hit(@Obj, 40, SpawnerUID, HIT_SOME, False) <> 0) or
2454 (Timeout < 1) then
2455 begin
2456 // Âçðûâ:
2457 {$IFDEF ENABLE_GFX}
2458 g_GFX_QueueEffect(R_GFX_EXPLODE_ROCKET, cx - 64, cy - 64);
2459 {$ENDIF}
2460 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBALL', Obj.X, Obj.Y);
2461 ShotType := 0;
2462 end;
2463 end;
2464 end; // case ShotType of...
2466 // Åñëè ñíàðÿäà óæå íåò, óäàëÿåì àíèìàöèþ:
2467 if (ShotType = 0) then
2468 begin
2469 if gGameSettings.GameType = GT_SERVER then
2470 MH_SEND_DeleteShot(i, Obj.X, Obj.Y, Loud);
2471 time := 0;
2472 end
2473 else if (ShotType <> WEAPON_FLAMETHROWER) and ((oldvx <> Obj.Vel.X) or (oldvy <> Obj.Vel.Y)) then
2474 if gGameSettings.GameType = GT_SERVER then
2475 MH_SEND_UpdateShot(i);
2476 end;
2477 end;
2478 end;
2480 function g_Weapon_Danger(UID: Word; X, Y: Integer; Width, Height: Word; Time: Byte): Boolean;
2481 var
2482 a: Integer;
2483 begin
2484 Result := False;
2486 if Shots = nil then
2487 Exit;
2489 for a := 0 to High(Shots) do
2490 if (Shots[a].ShotType <> 0) and (Shots[a].SpawnerUID <> UID) then
2491 if ((Shots[a].Obj.Vel.Y = 0) and (Shots[a].Obj.Vel.X > 0) and (Shots[a].Obj.X < X)) or
2492 (Shots[a].Obj.Vel.Y = 0) and (Shots[a].Obj.Vel.X < 0) and (Shots[a].Obj.X > X) then
2493 if (Abs(X-Shots[a].Obj.X) < Abs(Shots[a].Obj.Vel.X*Time)) and
2494 g_Collide(X, Y, Width, Height, X, Shots[a].Obj.Y,
2495 Shots[a].Obj.Rect.Width, Shots[a].Obj.Rect.Height) and
2496 g_TraceVector(X, Y, Shots[a].Obj.X, Shots[a].Obj.Y) then
2497 begin
2498 Result := True;
2499 Exit;
2500 end;
2501 end;
2503 procedure g_Weapon_SaveState (st: TStream);
2504 var
2505 count, i, j: Integer;
2506 begin
2507 // Ñ÷èòàåì êîëè÷åñòâî ñóùåñòâóþùèõ ñíàðÿäîâ
2508 count := 0;
2509 for i := 0 to High(Shots) do if (Shots[i].ShotType <> 0) then Inc(count);
2511 // Êîëè÷åñòâî ñíàðÿäîâ
2512 utils.WriteInt(st, count);
2514 if (count = 0) then exit;
2516 for i := 0 to High(Shots) do
2517 begin
2518 if Shots[i].ShotType <> 0 then
2519 begin
2520 // Ñèãíàòóðà ñíàðÿäà
2521 utils.writeSign(st, 'SHOT');
2522 utils.writeInt(st, Byte(0)); // version
2523 // Òèï ñíàðÿäà
2524 utils.writeInt(st, Byte(Shots[i].ShotType));
2525 // Öåëü
2526 utils.writeInt(st, Word(Shots[i].Target));
2527 // UID ñòðåëÿâøåãî
2528 utils.writeInt(st, Word(Shots[i].SpawnerUID));
2529 // Ðàçìåð ïîëÿ Triggers
2530 utils.writeInt(st, Integer(Length(Shots[i].Triggers)));
2531 // Òðèããåðû, àêòèâèðîâàííûå âûñòðåëîì
2532 for j := 0 to Length(Shots[i].Triggers)-1 do utils.writeInt(st, LongWord(Shots[i].Triggers[j]));
2533 // Îáúåêò ñíàðÿäà
2534 Obj_SaveState(st, @Shots[i].Obj);
2535 // Êîñòûëèíà åáàíàÿ
2536 utils.writeInt(st, Byte(Shots[i].Stopped));
2537 end;
2538 end;
2539 end;
2541 procedure g_Weapon_LoadState (st: TStream);
2542 var
2543 count, tc, i, j: Integer;
2544 begin
2545 if (st = nil) then exit;
2547 // Êîëè÷åñòâî ñíàðÿäîâ
2548 count := utils.readLongInt(st);
2549 if (count < 0) or (count > 1024*1024) then raise XStreamError.Create('invalid shots counter');
2551 SetLength(Shots, count);
2553 if (count = 0) then exit;
2555 for i := 0 to count-1 do
2556 begin
2557 // Ñèãíàòóðà ñíàðÿäà
2558 if not utils.checkSign(st, 'SHOT') then raise XStreamError.Create('invalid shot signature');
2559 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid shot version');
2560 // Òèï ñíàðÿäà:
2561 Shots[i].ShotType := utils.readByte(st);
2562 // Öåëü
2563 Shots[i].Target := utils.readWord(st);
2564 // UID ñòðåëÿâøåãî
2565 Shots[i].SpawnerUID := utils.readWord(st);
2566 // Ðàçìåð ïîëÿ Triggers
2567 tc := utils.readLongInt(st);
2568 if (tc < 0) or (tc > 1024*1024) then raise XStreamError.Create('invalid shot triggers counter');
2569 SetLength(Shots[i].Triggers, tc);
2570 // Òðèããåðû, àêòèâèðîâàííûå âûñòðåëîì
2571 for j := 0 to tc-1 do Shots[i].Triggers[j] := utils.readLongWord(st);
2572 // Îáúåêò ïðåäìåòà
2573 Obj_LoadState(@Shots[i].Obj, st);
2574 // Êîñòûëèíà åáàíàÿ
2575 Shots[i].Stopped := utils.readByte(st);
2577 Shots[i].time := gTime; // TODO save time?
2578 end;
2579 end;
2581 procedure g_Weapon_DestroyShot(I: Integer; X, Y: Integer; Loud: Boolean = True);
2582 {$IFDEF ENABLE_GFX}
2583 var cx, cy: Integer;
2584 {$ENDIF}
2585 begin
2586 if Shots = nil then
2587 Exit;
2588 if (I > High(Shots)) or (I < 0) then Exit;
2590 with Shots[I] do
2591 begin
2592 if ShotType = 0 then Exit;
2593 Obj.X := X;
2594 Obj.Y := Y;
2595 {$IFDEF ENABLE_GFX}
2596 cx := Obj.X + (Obj.Rect.Width div 2);
2597 cy := Obj.Y + (Obj.Rect.Height div 2);
2598 {$ENDIF}
2600 case ShotType of
2601 WEAPON_ROCKETLAUNCHER, WEAPON_SKEL_FIRE: // Ðàêåòû è ñíàðÿäû Ñêåëåòà
2602 begin
2603 if Loud then
2604 begin
2605 {$IFDEF ENABLE_GFX}
2606 if ShotType = WEAPON_SKEL_FIRE then
2607 g_GFX_QueueEffect(R_GFX_EXPLODE_SKELFIRE, (Obj.X + 32) - 32, (Obj.Y + 8) - 32)
2608 else
2609 g_GFX_QueueEffect(R_GFX_EXPLODE_ROCKET, cx - 64, cy - 64);
2610 {$ENDIF}
2611 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEROCKET', Obj.X, Obj.Y);
2612 end;
2613 end;
2615 WEAPON_PLASMA, WEAPON_BSP_FIRE: // Ïëàçìà, ïëàçìà Àðàõíàòðîíà
2616 begin
2617 if loud then
2618 begin
2619 {$IFDEF ENABLE_GFX}
2620 if ShotType = WEAPON_PLASMA then
2621 g_GFX_QueueEffect(R_GFX_EXPLODE_PLASMA, cx - 16, cy - 16)
2622 else
2623 g_GFX_QueueEffect(R_GFX_EXPLODE_BSPFIRE, cx - 16, cy - 16);
2624 {$ENDIF}
2625 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEPLASMA', Obj.X, Obj.Y);
2626 end;
2627 end;
2629 WEAPON_BFG: // BFG
2630 begin
2631 {$IFDEF ENABLE_GFX}
2632 g_GFX_QueueEffect(R_GFX_EXPLODE_BFG, cx - 64, cy - 64);
2633 {$ENDIF}
2634 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBFG', Obj.X, Obj.Y);
2635 end;
2637 WEAPON_IMP_FIRE, WEAPON_CACO_FIRE, WEAPON_BARON_FIRE: // Âûñòðåëû Áåñà, Êàêîäåìîíà Ðûöàðÿ/Áàðîíà àäà
2638 begin
2639 if loud then
2640 begin
2641 {$IFDEF ENABLE_GFX}
2642 case ShotType of
2643 WEAPON_IMP_FIRE: g_GFX_QueueEffect(R_GFX_EXPLODE_IMPFIRE, cx - 32, cy - 32);
2644 WEAPON_CACO_FIRE: g_GFX_QueueEffect(R_GFX_EXPLODE_CACOFIRE, cx - 32, cy - 32);
2645 WEAPON_BARON_FIRE: g_GFX_QueueEffect(R_GFX_EXPLODE_BARONFIRE, cx - 32, cy - 32);
2646 end;
2647 {$ENDIF}
2648 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBALL', Obj.X, Obj.Y);
2649 end;
2650 end;
2652 WEAPON_MANCUB_FIRE: // Âûñòðåë Ìàíêóáóñà
2653 begin
2654 {$IFDEF ENABLE_GFX}
2655 g_GFX_QueueEffect(R_GFX_EXPLODE_ROCKET, cx - 64, cy - 64);
2656 {$ENDIF}
2657 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBALL', Obj.X, Obj.Y);
2658 end;
2659 end; // case ShotType of...
2661 ShotType := 0;
2662 end;
2663 end;
2666 procedure g_Weapon_AddDynLights();
2667 var
2668 i: Integer;
2669 begin
2670 if Shots = nil then Exit;
2671 for i := 0 to High(Shots) do
2672 begin
2673 if Shots[i].ShotType = 0 then continue;
2674 if (Shots[i].ShotType = WEAPON_ROCKETLAUNCHER) or
2675 (Shots[i].ShotType = WEAPON_BARON_FIRE) or
2676 (Shots[i].ShotType = WEAPON_MANCUB_FIRE) or
2677 (Shots[i].ShotType = WEAPON_SKEL_FIRE) or
2678 (Shots[i].ShotType = WEAPON_IMP_FIRE) or
2679 (Shots[i].ShotType = WEAPON_CACO_FIRE) or
2680 (Shots[i].ShotType = WEAPON_MANCUB_FIRE) or
2681 (Shots[i].ShotType = WEAPON_BSP_FIRE) or
2682 (Shots[i].ShotType = WEAPON_PLASMA) or
2683 (Shots[i].ShotType = WEAPON_BFG) or
2684 (Shots[i].ShotType = WEAPON_FLAMETHROWER) or
2685 false then
2686 begin
2687 if (Shots[i].ShotType = WEAPON_PLASMA) then
2688 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)
2689 else if (Shots[i].ShotType = WEAPON_BFG) then
2690 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)
2691 else if (Shots[i].ShotType = WEAPON_FLAMETHROWER) then
2692 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)
2693 else
2694 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);
2695 end;
2696 end;
2697 end;
2700 procedure TShot.positionChanged (); begin end;
2703 end.