DEADSOFTWARE

95137cdf456c88c61a4a800f93efae210b53e6b8
[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: TAnimationState;
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;
104 WP_FIRST = WEAPON_KASTET;
105 WP_LAST = WEAPON_FLAMETHROWER;
107 var
108 gwep_debug_fast_trace: Boolean = true;
111 implementation
113 uses
114 {$IFDEF ENABLE_GFX}
115 g_gfx,
116 {$ENDIF}
117 {$IFDEF ENABLE_GIBS}
118 g_gibs,
119 {$ENDIF}
120 {$IFDEF ENABLE_CORPSES}
121 g_corpses,
122 {$ENDIF}
123 Math, g_map, g_player, g_sound, g_panel,
124 g_console, g_options, g_game,
125 g_triggers, MAPDEF, e_log, g_monsters, g_saveload,
126 g_language, g_netmsg, g_grid,
127 geom, binheap, hashtable, utils, xstreams
130 type
131 TWaterPanel = record
132 X, Y: Integer;
133 Width, Height: Word;
134 Active: Boolean;
135 end;
137 const
138 SHOT_ROCKETLAUNCHER_WIDTH = 14;
139 SHOT_ROCKETLAUNCHER_HEIGHT = 14;
141 SHOT_SKELFIRE_WIDTH = 14;
142 SHOT_SKELFIRE_HEIGHT = 14;
144 SHOT_PLASMA_WIDTH = 16;
145 SHOT_PLASMA_HEIGHT = 16;
147 SHOT_BFG_WIDTH = 32;
148 SHOT_BFG_HEIGHT = 32;
149 SHOT_BFG_DAMAGE = 100;
150 SHOT_BFG_RADIUS = 256;
152 SHOT_FLAME_WIDTH = 4;
153 SHOT_FLAME_HEIGHT = 4;
154 SHOT_FLAME_LIFETIME = 180;
156 SHOT_SIGNATURE = $544F4853; // 'SHOT'
158 type
159 PHitTime = ^THitTime;
160 THitTime = record
161 distSq: Integer;
162 mon: TMonster;
163 plridx: Integer; // if mon=nil
164 x, y: Integer;
165 end;
167 TBinHeapKeyHitTime = class
168 public
169 class function less (const a, b: Integer): Boolean; inline;
170 end;
172 // indicies in `wgunHitTime` array
173 TBinaryHeapHitTimes = specialize TBinaryHeapBase<Integer, TBinHeapKeyHitTime>;
175 var
176 WaterMap: array of array of DWORD = nil;
177 //wgunMonHash: THashIntInt = nil;
178 wgunHitHeap: TBinaryHeapHitTimes = nil;
179 wgunHitTime: array of THitTime = nil;
180 wgunHitTimeUsed: Integer = 0;
183 class function TBinHeapKeyHitTime.less (const a, b: Integer): Boolean;
184 var
185 hta, htb: PHitTime;
186 begin
187 hta := @wgunHitTime[a];
188 htb := @wgunHitTime[b];
189 if (hta.distSq <> htb.distSq) then begin result := (hta.distSq < htb.distSq); exit; end;
190 if (hta.mon <> nil) then
191 begin
192 // a is monster
193 if (htb.mon = nil) then begin result := false; exit; end; // players first
194 result := (hta.mon.UID < htb.mon.UID); // why not?
195 end
196 else
197 begin
198 // a is player
199 if (htb.mon <> nil) then begin result := true; exit; end; // players first
200 result := (hta.plridx < htb.plridx); // why not?
201 end;
202 end;
205 procedure appendHitTimeMon (adistSq: Integer; amon: TMonster; ax, ay: Integer);
206 begin
207 if (wgunHitTimeUsed = Length(wgunHitTime)) then SetLength(wgunHitTime, wgunHitTimeUsed+128);
208 with wgunHitTime[wgunHitTimeUsed] do
209 begin
210 distSq := adistSq;
211 mon := amon;
212 plridx := -1;
213 x := ax;
214 y := ay;
215 end;
216 wgunHitHeap.insert(wgunHitTimeUsed);
217 Inc(wgunHitTimeUsed);
218 end;
221 procedure appendHitTimePlr (adistSq: Integer; aplridx: Integer; ax, ay: Integer);
222 begin
223 if (wgunHitTimeUsed = Length(wgunHitTime)) then SetLength(wgunHitTime, wgunHitTimeUsed+128);
224 with wgunHitTime[wgunHitTimeUsed] do
225 begin
226 distSq := adistSq;
227 mon := nil;
228 plridx := aplridx;
229 x := ax;
230 y := ay;
231 end;
232 wgunHitHeap.insert(wgunHitTimeUsed);
233 Inc(wgunHitTimeUsed);
234 end;
237 function FindShot(): DWORD;
238 var
239 i: Integer;
240 begin
241 if Shots <> nil then
242 for i := 0 to High(Shots) do
243 if Shots[i].ShotType = 0 then
244 begin
245 Result := i;
246 LastShotID := Result;
247 Exit;
248 end;
250 if Shots = nil then
251 begin
252 SetLength(Shots, 128);
253 Result := 0;
254 end
255 else
256 begin
257 Result := High(Shots) + 1;
258 SetLength(Shots, Length(Shots) + 128);
259 end;
260 LastShotID := Result;
261 end;
263 procedure CreateWaterMap();
264 var
265 WaterArray: Array of TWaterPanel;
266 a, b, c, m: Integer;
267 ok: Boolean;
268 begin
269 if gWater = nil then
270 Exit;
272 SetLength(WaterArray, Length(gWater));
274 for a := 0 to High(gWater) do
275 begin
276 WaterArray[a].X := gWater[a].X;
277 WaterArray[a].Y := gWater[a].Y;
278 WaterArray[a].Width := gWater[a].Width;
279 WaterArray[a].Height := gWater[a].Height;
280 WaterArray[a].Active := True;
281 end;
283 g_Game_SetLoadingText(_lc[I_LOAD_WATER_MAP], High(WaterArray), False);
285 for a := 0 to High(WaterArray) do
286 if WaterArray[a].Active then
287 begin
288 WaterArray[a].Active := False;
289 m := Length(WaterMap);
290 SetLength(WaterMap, m+1);
291 SetLength(WaterMap[m], 1);
292 WaterMap[m][0] := a;
293 ok := True;
295 while ok do
296 begin
297 ok := False;
298 for b := 0 to High(WaterArray) do
299 if WaterArray[b].Active then
300 for c := 0 to High(WaterMap[m]) do
301 if g_CollideAround(WaterArray[b].X,
302 WaterArray[b].Y,
303 WaterArray[b].Width,
304 WaterArray[b].Height,
305 WaterArray[WaterMap[m][c]].X,
306 WaterArray[WaterMap[m][c]].Y,
307 WaterArray[WaterMap[m][c]].Width,
308 WaterArray[WaterMap[m][c]].Height) then
309 begin
310 WaterArray[b].Active := False;
311 SetLength(WaterMap[m],
312 Length(WaterMap[m])+1);
313 WaterMap[m][High(WaterMap[m])] := b;
314 ok := True;
315 Break;
316 end;
317 end;
319 g_Game_StepLoading();
320 end;
322 WaterArray := nil;
323 end;
326 var
327 chkTrap_pl: array [0..256] of Integer;
328 chkTrap_mn: array [0..65535] of TMonster;
330 procedure CheckTrap(ID: DWORD; dm: Integer; t: Byte);
331 var
332 //a, b, c, d, i1, i2: Integer;
333 //chkTrap_pl, chkTrap_mn: WArray;
334 plaCount: Integer = 0;
335 mnaCount: Integer = 0;
336 frameId: DWord;
339 function monsWaterCheck (mon: TMonster): Boolean;
340 begin
341 result := false; // don't stop
342 if mon.alive and mon.Collide(gWater[WaterMap[a][c]]) and (not InWArray(monidx, chkTrap_mn)) and (i2 < 1023) then //FIXME
343 begin
344 i2 += 1;
345 chkTrap_mn[i2] := monidx;
346 end;
347 end;
350 function monsWaterCheck (mon: TMonster): Boolean;
351 begin
352 result := false; // don't stop
353 if (mon.trapCheckFrameId <> frameId) then
354 begin
355 mon.trapCheckFrameId := frameId;
356 chkTrap_mn[mnaCount] := mon;
357 Inc(mnaCount);
358 end;
359 end;
361 var
362 a, b, c, d, f: Integer;
363 pan: TPanel;
364 begin
365 if (gWater = nil) or (WaterMap = nil) then Exit;
367 frameId := g_Mons_getNewTrapFrameId();
369 //i1 := -1;
370 //i2 := -1;
372 //SetLength(chkTrap_pl, 1024);
373 //SetLength(chkTrap_mn, 1024);
374 //for d := 0 to 1023 do chkTrap_pl[d] := $FFFF;
375 //for d := 0 to 1023 do chkTrap_mn[d] := $FFFF;
377 for a := 0 to High(WaterMap) do
378 begin
379 for b := 0 to High(WaterMap[a]) do
380 begin
381 pan := gWater[WaterMap[a][b]];
382 if not g_Obj_Collide(pan.X, pan.Y, pan.Width, pan.Height, @Shots[ID].Obj) then continue;
384 for c := 0 to High(WaterMap[a]) do
385 begin
386 pan := gWater[WaterMap[a][c]];
387 for d := 0 to High(gPlayers) do
388 begin
389 if (gPlayers[d] <> nil) and (gPlayers[d].alive) then
390 begin
391 if gPlayers[d].Collide(pan) then
392 begin
393 f := 0;
394 while (f < plaCount) and (chkTrap_pl[f] <> d) do Inc(f);
395 if (f = plaCount) then
396 begin
397 chkTrap_pl[plaCount] := d;
398 Inc(plaCount);
399 if (plaCount = Length(chkTrap_pl)) then break;
400 end;
401 end;
402 end;
403 end;
405 //g_Mons_ForEach(monsWaterCheck);
406 g_Mons_ForEachAliveAt(pan.X, pan.Y, pan.Width, pan.Height, monsWaterCheck);
407 end;
409 for f := 0 to plaCount-1 do gPlayers[chkTrap_pl[f]].Damage(dm, Shots[ID].SpawnerUID, 0, 0, t);
410 for f := 0 to mnaCount-1 do chkTrap_mn[f].Damage(dm, 0, 0, Shots[ID].SpawnerUID, t);
411 end;
412 end;
414 //chkTrap_pl := nil;
415 //chkTrap_mn := nil;
416 end;
418 function HitMonster(m: TMonster; d: Integer; vx, vy: Integer; SpawnerUID: Word; t: Byte): Boolean;
419 var
420 tt, mt: Byte;
421 mon: TMonster;
422 begin
423 Result := False;
425 tt := g_GetUIDType(SpawnerUID);
426 if tt = UID_MONSTER then
427 begin
428 mon := g_Monsters_ByUID(SpawnerUID);
429 if mon <> nil then
430 mt := g_Monsters_ByUID(SpawnerUID).MonsterType
431 else
432 mt := 0;
433 end
434 else
435 mt := 0;
437 if m = nil then Exit;
438 if m.UID = SpawnerUID then
439 begin
440 // Ñàì ñåáÿ ìîæåò ðàíèòü òîëüêî ðàêåòîé è òîêîì:
441 if (t <> HIT_ROCKET) and (t <> HIT_ELECTRO) then
442 Exit;
443 // Êèáåð äåìîí è áî÷êà âîîáùå íå ìîãóò ñåáÿ ðàíèòü:
444 if (m.MonsterType = MONSTER_CYBER) or
445 (m.MonsterType = MONSTER_BARREL) then
446 begin
447 Result := True;
448 Exit;
449 end;
450 end;
452 if tt = UID_MONSTER then
453 begin
454 // Lost_Soul íå ìîæåò ðàíèòü Pain_Elemental'à:
455 if (mt = MONSTER_SOUL) and (m.MonsterType = MONSTER_PAIN) then
456 Exit;
458 // Îáà ìîíñòðà îäíîãî âèäà:
459 if mt = m.MonsterType then
460 case mt of
461 MONSTER_IMP, MONSTER_DEMON, MONSTER_BARON, MONSTER_KNIGHT, MONSTER_CACO,
462 MONSTER_SOUL, MONSTER_MANCUB, MONSTER_SKEL, MONSTER_FISH:
463 Exit; // Ýòè íå áüþò ñâîèõ
464 end;
465 end;
467 if g_Game_IsServer then
468 begin
469 if (t <> HIT_FLAME) or (m.FFireTime = 0) or (vx <> 0) or (vy <> 0) then
470 Result := m.Damage(d, vx, vy, SpawnerUID, t)
471 else
472 Result := (gLMSRespawn = LMS_RESPAWN_NONE); // don't hit monsters when it's warmup time
473 if t = HIT_FLAME then
474 m.CatchFire(SpawnerUID);
475 end
476 else
477 Result := (gLMSRespawn = LMS_RESPAWN_NONE); // don't hit monsters when it's warmup time
478 end;
481 function HitPlayer (p: TPlayer; d: Integer; vx, vy: Integer; SpawnerUID: Word; t: Byte): Boolean;
482 begin
483 result := False;
485 // Ñàì ñåáÿ ìîæåò ðàíèòü òîëüêî ðàêåòîé è òîêîì
486 if (p.UID = SpawnerUID) and (t <> HIT_ROCKET) and (t <> HIT_ELECTRO) then exit;
488 if g_Game_IsServer then
489 begin
490 if (t <> HIT_FLAME) or (p.FFireTime = 0) or (vx <> 0) or (vy <> 0) then p.Damage(d, SpawnerUID, vx, vy, t);
491 if (t = HIT_FLAME) then p.CatchFire(SpawnerUID);
492 end;
494 result := true;
495 end;
498 procedure g_Weapon_BFG9000(X, Y: Integer; SpawnerUID: Word);
500 function monsCheck (mon: TMonster): Boolean;
501 begin
502 result := false; // don't stop
503 if (mon.alive) and (mon.UID <> SpawnerUID) then
504 begin
505 with mon do
506 begin
507 if (g_PatchLength(X, Y, Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
508 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)) <= SHOT_BFG_RADIUS) and
509 g_TraceVector(X, Y, Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
510 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)) then
511 begin
512 if HitMonster(mon, 50, 0, 0, SpawnerUID, HIT_SOME) then mon.BFGHit();
513 end;
514 end;
515 end;
516 end;
518 var
519 i, h: Integer;
520 st: Byte;
521 pl: TPlayer;
522 b: Boolean;
523 begin
524 //g_Sound_PlayEx('SOUND_WEAPON_EXPLODEBFG', 255);
526 {$IFDEF ENABLE_CORPSES}
527 h := High(gCorpses);
528 if gAdvCorpses and (h <> -1) then
529 begin
530 for i := 0 to h do
531 begin
532 if (gCorpses[i] <> nil) and (gCorpses[i].State <> CORPSE_STATE_REMOVEME) then
533 begin
534 with gCorpses[i] do
535 begin
536 if (g_PatchLength(X, Y, Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
537 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)) <= SHOT_BFG_RADIUS) and
538 g_TraceVector(X, Y, Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
539 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)) then
540 begin
541 Damage(50, SpawnerUID, 0, 0);
542 g_Weapon_BFGHit(Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2), Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2));
543 end;
544 end;
545 end;
546 end;
547 end;
548 {$ENDIF}
550 st := TEAM_NONE;
551 pl := g_Player_Get(SpawnerUID);
552 if pl <> nil then
553 st := pl.Team;
555 h := High(gPlayers);
557 if h <> -1 then
558 for i := 0 to h do
559 if (gPlayers[i] <> nil) and (gPlayers[i].alive) and (gPlayers[i].UID <> SpawnerUID) then
560 with gPlayers[i] do
561 if (g_PatchLength(X, Y, GameX+PLAYER_RECT.X+(PLAYER_RECT.Width div 2),
562 GameY+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)) <= SHOT_BFG_RADIUS) and
563 g_TraceVector(X, Y, GameX+PLAYER_RECT.X+(PLAYER_RECT.Width div 2),
564 GameY+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)) then
565 begin
566 if (st = TEAM_NONE) or (st <> gPlayers[i].Team) then
567 b := HitPlayer(gPlayers[i], 50, 0, 0, SpawnerUID, HIT_SOME)
568 else
569 b := HitPlayer(gPlayers[i], 25, 0, 0, SpawnerUID, HIT_SOME);
570 if b then
571 gPlayers[i].BFGHit();
572 end;
574 //FIXME
575 g_Mons_ForEachAlive(monsCheck);
576 end;
578 function g_Weapon_CreateShot(I: Integer; ShotType: Byte; Spawner, TargetUID: Word; X, Y, XV, YV: Integer): LongWord;
579 var
580 find_id: DWord;
581 begin
582 if I < 0 then
583 find_id := FindShot()
584 else
585 begin
586 find_id := I;
587 if Integer(find_id) >= High(Shots) then
588 SetLength(Shots, find_id + 64)
589 end;
591 case ShotType of
592 WEAPON_ROCKETLAUNCHER:
593 begin
594 with Shots[find_id] do
595 begin
596 g_Obj_Init(@Obj);
598 Obj.Rect.Width := SHOT_ROCKETLAUNCHER_WIDTH;
599 Obj.Rect.Height := SHOT_ROCKETLAUNCHER_HEIGHT;
601 Animation := nil;
602 Triggers := nil;
603 ShotType := WEAPON_ROCKETLAUNCHER;
604 end;
605 end;
607 WEAPON_PLASMA:
608 begin
609 with Shots[find_id] do
610 begin
611 g_Obj_Init(@Obj);
613 Obj.Rect.Width := SHOT_PLASMA_WIDTH;
614 Obj.Rect.Height := SHOT_PLASMA_HEIGHT;
616 Triggers := nil;
617 ShotType := WEAPON_PLASMA;
618 Animation := TAnimationState.Create(True, 5, 2); // !!! put values into table
619 end;
620 end;
622 WEAPON_BFG:
623 begin
624 with Shots[find_id] do
625 begin
626 g_Obj_Init(@Obj);
628 Obj.Rect.Width := SHOT_BFG_WIDTH;
629 Obj.Rect.Height := SHOT_BFG_HEIGHT;
631 Triggers := nil;
632 ShotType := WEAPON_BFG;
633 Animation := TAnimationState.Create(True, 6, 2); // !!! put values into table
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 // Animation := TAnimationState.Create(True, 6, 0); // drawed as gfx
649 end;
650 end;
652 WEAPON_IMP_FIRE:
653 begin
654 with Shots[find_id] do
655 begin
656 g_Obj_Init(@Obj);
658 Obj.Rect.Width := 16;
659 Obj.Rect.Height := 16;
661 Triggers := nil;
662 ShotType := WEAPON_IMP_FIRE;
663 Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
664 end;
665 end;
667 WEAPON_CACO_FIRE:
668 begin
669 with Shots[find_id] do
670 begin
671 g_Obj_Init(@Obj);
673 Obj.Rect.Width := 16;
674 Obj.Rect.Height := 16;
676 Triggers := nil;
677 ShotType := WEAPON_CACO_FIRE;
678 Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
679 end;
680 end;
682 WEAPON_MANCUB_FIRE:
683 begin
684 with Shots[find_id] do
685 begin
686 g_Obj_Init(@Obj);
688 Obj.Rect.Width := 32;
689 Obj.Rect.Height := 32;
691 Triggers := nil;
692 ShotType := WEAPON_MANCUB_FIRE;
693 Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
694 end;
695 end;
697 WEAPON_BARON_FIRE:
698 begin
699 with Shots[find_id] do
700 begin
701 g_Obj_Init(@Obj);
703 Obj.Rect.Width := 16;
704 Obj.Rect.Height := 16;
706 Triggers := nil;
707 ShotType := WEAPON_BARON_FIRE;
708 Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
709 end;
710 end;
712 WEAPON_BSP_FIRE:
713 begin
714 with Shots[find_id] do
715 begin
716 g_Obj_Init(@Obj);
718 Obj.Rect.Width := 16;
719 Obj.Rect.Height := 16;
721 Triggers := nil;
722 ShotType := WEAPON_BSP_FIRE;
723 Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
724 end;
725 end;
727 WEAPON_SKEL_FIRE:
728 begin
729 with Shots[find_id] do
730 begin
731 g_Obj_Init(@Obj);
733 Obj.Rect.Width := SHOT_SKELFIRE_WIDTH;
734 Obj.Rect.Height := SHOT_SKELFIRE_HEIGHT;
736 Triggers := nil;
737 ShotType := WEAPON_SKEL_FIRE;
738 target := TargetUID;
739 Animation := TAnimationState.Create(True, 5, 2); // !!! put values into table
740 end;
741 end;
742 end;
744 Shots[find_id].Obj.oldX := X;
745 Shots[find_id].Obj.oldY := Y;
746 Shots[find_id].Obj.X := X;
747 Shots[find_id].Obj.Y := Y;
748 Shots[find_id].Obj.Vel.X := XV;
749 Shots[find_id].Obj.Vel.Y := YV;
750 Shots[find_id].Obj.Accel.X := 0;
751 Shots[find_id].Obj.Accel.Y := 0;
752 Shots[find_id].SpawnerUID := Spawner;
753 if (ShotType = WEAPON_FLAMETHROWER) and (XV = 0) and (YV = 0) then
754 Shots[find_id].Stopped := 255
755 else
756 Shots[find_id].Stopped := 0;
757 Result := find_id;
758 end;
760 procedure throw(i, x, y, xd, yd, s: Integer);
761 var
762 a: Integer;
763 begin
764 yd := yd - y;
765 xd := xd - x;
767 a := Max(Abs(xd), Abs(yd));
768 if a = 0 then
769 a := 1;
771 Shots[i].Obj.oldX := x;
772 Shots[i].Obj.oldY := y;
773 Shots[i].Obj.X := x;
774 Shots[i].Obj.Y := y;
775 Shots[i].Obj.Vel.X := (xd*s) div a;
776 Shots[i].Obj.Vel.Y := (yd*s) div a;
777 Shots[i].Obj.Accel.X := 0;
778 Shots[i].Obj.Accel.Y := 0;
779 Shots[i].Stopped := 0;
780 if Shots[i].ShotType in [WEAPON_ROCKETLAUNCHER, WEAPON_BFG] then
781 Shots[i].Timeout := 900 // ~25 sec
782 else
783 begin
784 if Shots[i].ShotType = WEAPON_FLAMETHROWER then
785 Shots[i].Timeout := SHOT_FLAME_LIFETIME
786 else
787 Shots[i].Timeout := 550; // ~15 sec
788 end;
789 end;
791 function g_Weapon_Hit(obj: PObj; d: Integer; SpawnerUID: Word; t: Byte; HitCorpses: Boolean = True): Byte;
792 {$IFDEF ENABLE_CORPSES}
793 var i: Integer;
794 {$ENDIF}
795 var h: Integer;
797 function PlayerHit(Team: Byte = 0): Boolean;
798 var
799 i: Integer;
800 ChkTeam: Boolean;
801 p: TPlayer;
802 begin
803 Result := False;
804 h := High(gPlayers);
806 if h <> -1 then
807 for i := 0 to h do
808 if (gPlayers[i] <> nil) and gPlayers[i].alive and g_Obj_Collide(obj, @gPlayers[i].Obj) then
809 begin
810 ChkTeam := True;
811 if (Team > 0) and (g_GetUIDType(SpawnerUID) = UID_PLAYER) then
812 begin
813 p := g_Player_Get(SpawnerUID);
814 if p <> nil then
815 ChkTeam := (p.Team = gPlayers[i].Team) xor (Team = 2);
816 end;
817 if ChkTeam then
818 if HitPlayer(gPlayers[i], d, obj^.Vel.X, obj^.Vel.Y, SpawnerUID, t) then
819 begin
820 if t <> HIT_FLAME then
821 gPlayers[i].Push((obj^.Vel.X+obj^.Accel.X)*IfThen(t = HIT_BFG, 8, 1) div 4,
822 (obj^.Vel.Y+obj^.Accel.Y)*IfThen(t = HIT_BFG, 8, 1) div 4);
823 if t = HIT_BFG then
824 g_Game_DelayEvent(DE_BFGHIT, 1000, SpawnerUID);
825 Result := True;
826 break;
827 end;
828 end;
829 end;
832 function monsCheckHit (monidx: Integer; mon: TMonster): Boolean;
833 begin
834 result := false; // don't stop
835 if mon.alive and g_Obj_Collide(obj, @mon.Obj) then
836 begin
837 if HitMonster(mon, d, obj^.Vel.X, obj^.Vel.Y, SpawnerUID, t) then
838 begin
839 if (t <> HIT_FLAME) then
840 begin
841 mon.Push((obj^.Vel.X+obj^.Accel.X)*IfThen(t = HIT_BFG, 8, 1) div 4,
842 (obj^.Vel.Y+obj^.Accel.Y)*IfThen(t = HIT_BFG, 8, 1) div 4);
843 end;
844 result := True;
845 end;
846 end;
847 end;
850 function monsCheckHit (mon: TMonster): Boolean;
851 begin
852 result := false; // don't stop
853 if HitMonster(mon, d, obj.Vel.X, obj.Vel.Y, SpawnerUID, t) then
854 begin
855 if (t <> HIT_FLAME) then
856 begin
857 mon.Push((obj.Vel.X+obj.Accel.X)*IfThen(t = HIT_BFG, 8, 1) div 4,
858 (obj.Vel.Y+obj.Accel.Y)*IfThen(t = HIT_BFG, 8, 1) div 4);
859 end;
860 result := true;
861 end;
862 end;
864 function MonsterHit(): Boolean;
865 begin
866 //result := g_Mons_ForEach(monsCheckHit);
867 //FIXME: accelerate this!
868 result := g_Mons_ForEachAliveAt(obj.X+obj.Rect.X, obj.Y+obj.Rect.Y, obj.Rect.Width, obj.Rect.Height, monsCheckHit);
869 end;
871 begin
872 Result := 0;
874 {$IFDEF ENABLE_CORPSES}
875 if HitCorpses then
876 begin
877 h := High(gCorpses);
878 if gAdvCorpses and (h <> -1) then
879 begin
880 for i := 0 to h do
881 begin
882 if (gCorpses[i] <> nil) and (gCorpses[i].State <> CORPSE_STATE_REMOVEME) and
883 g_Obj_Collide(obj, @gCorpses[i].Obj) then
884 begin
885 // Ðàñïèëèâàåì òðóï:
886 gCorpses[i].Damage(d, SpawnerUID, (obj^.Vel.X+obj^.Accel.X) div 4,
887 (obj^.Vel.Y+obj^.Accel.Y) div 4);
888 Result := 1;
889 end;
890 end;
891 end;
892 end;
893 {$ENDIF}
895 case gGameSettings.GameMode of
896 // Êàìïàíèÿ:
897 GM_COOP, GM_SINGLE:
898 begin
899 // Ñíà÷àëà áü¸ì ìîíñòðîâ, åñëè åñòü, ïîòîì ïûòàåìñÿ áèòü èãðîêîâ
900 if MonsterHit() then
901 begin
902 Result := 2;
903 Exit;
904 end;
906 // È â êîíöå èãðîêîâ, íî òîëüêî åñëè ïîëîæåíî
907 // (èëè ñíàðÿä îò ìîíñòðà, èëè friendlyfire, èëè friendly_hit_projectile)
908 if (g_GetUIDType(SpawnerUID) <> UID_PLAYER) or
909 LongBool(gGameSettings.Options and (GAME_OPTION_TEAMDAMAGE or GAME_OPTION_TEAMHITPROJECTILE)) then
910 begin
911 if PlayerHit() then
912 begin
913 Result := 1;
914 Exit;
915 end;
916 end;
917 end;
919 // Äåçìàò÷:
920 GM_DM:
921 begin
922 // Ñíà÷àëà áü¸ì èãðîêîâ, åñëè åñòü, ïîòîì ïûòàåìñÿ áèòü ìîíñòðîâ
923 if PlayerHit() then
924 begin
925 Result := 1;
926 Exit;
927 end;
929 if MonsterHit() then
930 begin
931 Result := 2;
932 Exit;
933 end;
934 end;
936 // Êîìàíäíûå:
937 GM_TDM, GM_CTF:
938 begin
939 // Ñíà÷àëà áü¸ì èãðîêîâ êîìàíäû ñîïåðíèêà
940 if PlayerHit(2) then
941 begin
942 Result := 1;
943 Exit;
944 end;
946 // Ïîòîì ìîíñòðîâ
947 if MonsterHit() then
948 begin
949 Result := 2;
950 Exit;
951 end;
953 // È â êîíöå ñâîèõ èãðîêîâ, íî òîëüêî åñëè ïîëîæåíî
954 // (èëè friendlyfire, èëè friendly_hit_projectile)
955 if LongBool(gGameSettings.Options and (GAME_OPTION_TEAMDAMAGE or GAME_OPTION_TEAMHITPROJECTILE)) then
956 begin
957 if PlayerHit(1) then
958 begin
959 Result := 1;
960 Exit;
961 end;
962 end;
963 end;
965 end;
966 end;
968 function g_Weapon_HitUID(UID: Word; d: Integer; SpawnerUID: Word; t: Byte): Boolean;
969 begin
970 Result := False;
972 case g_GetUIDType(UID) of
973 UID_PLAYER: Result := HitPlayer(g_Player_Get(UID), d, 0, 0, SpawnerUID, t);
974 UID_MONSTER: Result := HitMonster(g_Monsters_ByUID(UID), d, 0, 0, SpawnerUID, t);
975 else Exit;
976 end;
977 end;
979 function g_Weapon_Explode(X, Y: Integer; rad: Integer; SpawnerUID: Word): Boolean;
980 var
981 r: Integer; // squared radius
983 function monsExCheck (mon: TMonster): Boolean;
984 var
985 dx, dy, mm: Integer;
986 begin
987 result := false; // don't stop
988 begin
989 dx := mon.Obj.X+mon.Obj.Rect.X+(mon.Obj.Rect.Width div 2)-X;
990 dy := mon.Obj.Y+mon.Obj.Rect.Y+(mon.Obj.Rect.Height div 2)-Y;
992 if dx > 1000 then dx := 1000;
993 if dy > 1000 then dy := 1000;
995 if (dx*dx+dy*dy < r) then
996 begin
997 //m := PointToRect(X, Y, Obj.X+Obj.Rect.X, Obj.Y+Obj.Rect.Y, Obj.Rect.Width, Obj.Rect.Height);
998 //e_WriteLog(Format('explo monster #%d: x=%d; y=%d; rad=%d; dx=%d; dy=%d', [monidx, X, Y, rad, dx, dy]), MSG_NOTIFY);
1000 mm := Max(abs(dx), abs(dy));
1001 if mm = 0 then mm := 1;
1003 if mon.alive then
1004 begin
1005 HitMonster(mon, ((mon.Obj.Rect.Width div 4)*10*(rad-mm)) div rad, 0, 0, SpawnerUID, HIT_ROCKET);
1006 end;
1008 mon.Push((dx*7) div mm, (dy*7) div mm);
1009 end;
1010 end;
1011 end;
1013 var i, h, dx, dy, mm: Integer;
1014 {$IFDEF ENABLE_GIBS}
1015 var _angle: SmallInt;
1016 {$ENDIF}
1017 {$IF DEFINED(ENABLE_GIBS) OR DEFINED(ENABLE_CORPSES)}
1018 var m: Integer;
1019 {$ENDIF}
1020 begin
1021 result := false;
1023 g_Triggers_PressC(X, Y, rad, SpawnerUID, ACTIVATE_SHOT);
1025 r := rad*rad;
1027 h := High(gPlayers);
1029 if h <> -1 then
1030 for i := 0 to h do
1031 if (gPlayers[i] <> nil) and gPlayers[i].alive then
1032 with gPlayers[i] do
1033 begin
1034 dx := Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2)-X;
1035 dy := Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)-Y;
1037 if dx > 1000 then dx := 1000;
1038 if dy > 1000 then dy := 1000;
1040 if dx*dx+dy*dy < r then
1041 begin
1042 //m := PointToRect(X, Y, GameX+PLAYER_RECT.X, GameY+PLAYER_RECT.Y,
1043 // PLAYER_RECT.Width, PLAYER_RECT.Height);
1045 mm := Max(abs(dx), abs(dy));
1046 if mm = 0 then mm := 1;
1048 HitPlayer(gPlayers[i], (100*(rad-mm)) div rad, (dx*10) div mm, (dy*10) div mm, SpawnerUID, HIT_ROCKET);
1049 gPlayers[i].Push((dx*7) div mm, (dy*7) div mm);
1050 end;
1051 end;
1053 //g_Mons_ForEach(monsExCheck);
1054 g_Mons_ForEachAt(X-(rad+32), Y-(rad+32), (rad+32)*2, (rad+32)*2, monsExCheck);
1056 {$IFDEF ENABLE_CORPSES}
1057 h := High(gCorpses);
1058 if gAdvCorpses and (h <> -1) then
1059 begin
1060 for i := 0 to h do
1061 begin
1062 if (gCorpses[i] <> nil) and (gCorpses[i].State <> CORPSE_STATE_REMOVEME) then
1063 begin
1064 with gCorpses[i] do
1065 begin
1066 dx := Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2)-X;
1067 dy := Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)-Y;
1068 if dx > 1000 then dx := 1000;
1069 if dy > 1000 then dy := 1000;
1070 if dx*dx+dy*dy < r then
1071 begin
1072 m := PointToRect(X, Y, Obj.X+Obj.Rect.X, Obj.Y+Obj.Rect.Y, Obj.Rect.Width, Obj.Rect.Height);
1073 mm := Max(abs(dx), abs(dy));
1074 if mm = 0 then
1075 mm := 1;
1076 Damage(Round(100*(rad-m)/rad), SpawnerUID, (dx*10) div mm, (dy*10) div mm);
1077 end;
1078 end;
1079 end;
1080 end;
1081 end;
1082 {$ENDIF}
1084 {$IFDEF ENABLE_GIBS}
1085 h := High(gGibs);
1086 if gAdvGibs and (h <> -1) then
1087 for i := 0 to h do
1088 if gGibs[i].alive then
1089 with gGibs[i] do
1090 begin
1091 dx := Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2)-X;
1092 dy := Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)-Y;
1093 if dx > 1000 then dx := 1000;
1094 if dy > 1000 then dy := 1000;
1095 if dx*dx+dy*dy < r then
1096 begin
1097 m := PointToRect(X, Y, Obj.X+Obj.Rect.X, Obj.Y+Obj.Rect.Y,
1098 Obj.Rect.Width, Obj.Rect.Height);
1099 _angle := GetAngle(Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
1100 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2), X, Y);
1101 g_Obj_PushA(@Obj, Round(15*(rad-m)/rad), _angle);
1102 positionChanged(); // this updates spatial accelerators
1103 end;
1104 end;
1105 {$ENDIF}
1106 end;
1108 procedure g_Weapon_Init();
1109 begin
1110 CreateWaterMap();
1111 end;
1113 procedure g_Weapon_Free();
1114 var
1115 i: Integer;
1116 begin
1117 if Shots <> nil then
1118 begin
1119 for i := 0 to High(Shots) do
1120 if Shots[i].ShotType <> 0 then
1121 Shots[i].Animation.Free();
1123 Shots := nil;
1124 end;
1126 WaterMap := nil;
1127 end;
1129 procedure g_Weapon_LoadData();
1130 begin
1131 e_WriteLog('Loading weapons data...', TMsgType.Notify);
1133 g_Sound_CreateWADEx('SOUND_WEAPON_HITPUNCH', GameWAD+':SOUNDS\HITPUNCH');
1134 g_Sound_CreateWADEx('SOUND_WEAPON_MISSPUNCH', GameWAD+':SOUNDS\MISSPUNCH');
1135 g_Sound_CreateWADEx('SOUND_WEAPON_HITBERSERK', GameWAD+':SOUNDS\HITBERSERK');
1136 g_Sound_CreateWADEx('SOUND_WEAPON_MISSBERSERK', GameWAD+':SOUNDS\MISSBERSERK');
1137 g_Sound_CreateWADEx('SOUND_WEAPON_SELECTSAW', GameWAD+':SOUNDS\SELECTSAW');
1138 g_Sound_CreateWADEx('SOUND_WEAPON_IDLESAW', GameWAD+':SOUNDS\IDLESAW');
1139 g_Sound_CreateWADEx('SOUND_WEAPON_HITSAW', GameWAD+':SOUNDS\HITSAW');
1140 g_Sound_CreateWADEx('SOUND_WEAPON_FIRESHOTGUN2', GameWAD+':SOUNDS\FIRESHOTGUN2');
1141 g_Sound_CreateWADEx('SOUND_WEAPON_FIRESHOTGUN', GameWAD+':SOUNDS\FIRESHOTGUN');
1142 g_Sound_CreateWADEx('SOUND_WEAPON_FIRESAW', GameWAD+':SOUNDS\FIRESAW');
1143 g_Sound_CreateWADEx('SOUND_WEAPON_FIREROCKET', GameWAD+':SOUNDS\FIREROCKET');
1144 g_Sound_CreateWADEx('SOUND_WEAPON_FIREPLASMA', GameWAD+':SOUNDS\FIREPLASMA');
1145 g_Sound_CreateWADEx('SOUND_WEAPON_FIREPISTOL', GameWAD+':SOUNDS\FIREPISTOL');
1146 g_Sound_CreateWADEx('SOUND_WEAPON_FIRECGUN', GameWAD+':SOUNDS\FIRECGUN');
1147 g_Sound_CreateWADEx('SOUND_WEAPON_FIREBFG', GameWAD+':SOUNDS\FIREBFG');
1148 g_Sound_CreateWADEx('SOUND_FIRE', GameWAD+':SOUNDS\FIRE');
1149 g_Sound_CreateWADEx('SOUND_IGNITE', GameWAD+':SOUNDS\IGNITE');
1150 g_Sound_CreateWADEx('SOUND_WEAPON_STARTFIREBFG', GameWAD+':SOUNDS\STARTFIREBFG');
1151 g_Sound_CreateWADEx('SOUND_WEAPON_EXPLODEROCKET', GameWAD+':SOUNDS\EXPLODEROCKET');
1152 g_Sound_CreateWADEx('SOUND_WEAPON_EXPLODEBFG', GameWAD+':SOUNDS\EXPLODEBFG');
1153 g_Sound_CreateWADEx('SOUND_WEAPON_BFGWATER', GameWAD+':SOUNDS\BFGWATER');
1154 g_Sound_CreateWADEx('SOUND_WEAPON_EXPLODEPLASMA', GameWAD+':SOUNDS\EXPLODEPLASMA');
1155 g_Sound_CreateWADEx('SOUND_WEAPON_PLASMAWATER', GameWAD+':SOUNDS\PLASMAWATER');
1156 g_Sound_CreateWADEx('SOUND_WEAPON_FIREBALL', GameWAD+':SOUNDS\FIREBALL');
1157 g_Sound_CreateWADEx('SOUND_WEAPON_EXPLODEBALL', GameWAD+':SOUNDS\EXPLODEBALL');
1158 g_Sound_CreateWADEx('SOUND_WEAPON_FIREREV', GameWAD+':SOUNDS\FIREREV');
1159 g_Sound_CreateWADEx('SOUND_WEAPON_FLAMEON', GameWAD+':SOUNDS\STARTFLM');
1160 g_Sound_CreateWADEx('SOUND_WEAPON_FLAMEOFF', GameWAD+':SOUNDS\STOPFLM');
1161 g_Sound_CreateWADEx('SOUND_WEAPON_FLAMEWORK', GameWAD+':SOUNDS\WORKFLM');
1162 g_Sound_CreateWADEx('SOUND_PLAYER_JETFLY', GameWAD+':SOUNDS\WORKJETPACK');
1163 g_Sound_CreateWADEx('SOUND_PLAYER_JETON', GameWAD+':SOUNDS\STARTJETPACK');
1164 g_Sound_CreateWADEx('SOUND_PLAYER_JETOFF', GameWAD+':SOUNDS\STOPJETPACK');
1165 g_Sound_CreateWADEx('SOUND_PLAYER_CASING1', GameWAD+':SOUNDS\CASING1');
1166 g_Sound_CreateWADEx('SOUND_PLAYER_CASING2', GameWAD+':SOUNDS\CASING2');
1167 g_Sound_CreateWADEx('SOUND_PLAYER_SHELL1', GameWAD+':SOUNDS\SHELL1');
1168 g_Sound_CreateWADEx('SOUND_PLAYER_SHELL2', GameWAD+':SOUNDS\SHELL2');
1170 //wgunMonHash := hashNewIntInt();
1171 wgunHitHeap := TBinaryHeapHitTimes.Create();
1172 end;
1174 procedure g_Weapon_FreeData();
1175 begin
1176 e_WriteLog('Releasing weapons data...', TMsgType.Notify);
1178 g_Sound_Delete('SOUND_WEAPON_HITPUNCH');
1179 g_Sound_Delete('SOUND_WEAPON_MISSPUNCH');
1180 g_Sound_Delete('SOUND_WEAPON_HITBERSERK');
1181 g_Sound_Delete('SOUND_WEAPON_MISSBERSERK');
1182 g_Sound_Delete('SOUND_WEAPON_SELECTSAW');
1183 g_Sound_Delete('SOUND_WEAPON_IDLESAW');
1184 g_Sound_Delete('SOUND_WEAPON_HITSAW');
1185 g_Sound_Delete('SOUND_WEAPON_FIRESHOTGUN2');
1186 g_Sound_Delete('SOUND_WEAPON_FIRESHOTGUN');
1187 g_Sound_Delete('SOUND_WEAPON_FIRESAW');
1188 g_Sound_Delete('SOUND_WEAPON_FIREROCKET');
1189 g_Sound_Delete('SOUND_WEAPON_FIREPLASMA');
1190 g_Sound_Delete('SOUND_WEAPON_FIREPISTOL');
1191 g_Sound_Delete('SOUND_WEAPON_FIRECGUN');
1192 g_Sound_Delete('SOUND_WEAPON_FIREBFG');
1193 g_Sound_Delete('SOUND_FIRE');
1194 g_Sound_Delete('SOUND_IGNITE');
1195 g_Sound_Delete('SOUND_WEAPON_STARTFIREBFG');
1196 g_Sound_Delete('SOUND_WEAPON_EXPLODEROCKET');
1197 g_Sound_Delete('SOUND_WEAPON_EXPLODEBFG');
1198 g_Sound_Delete('SOUND_WEAPON_BFGWATER');
1199 g_Sound_Delete('SOUND_WEAPON_EXPLODEPLASMA');
1200 g_Sound_Delete('SOUND_WEAPON_PLASMAWATER');
1201 g_Sound_Delete('SOUND_WEAPON_FIREBALL');
1202 g_Sound_Delete('SOUND_WEAPON_EXPLODEBALL');
1203 g_Sound_Delete('SOUND_WEAPON_FIREREV');
1204 g_Sound_Delete('SOUND_WEAPON_FLAMEON');
1205 g_Sound_Delete('SOUND_WEAPON_FLAMEOFF');
1206 g_Sound_Delete('SOUND_WEAPON_FLAMEWORK');
1207 g_Sound_Delete('SOUND_PLAYER_JETFLY');
1208 g_Sound_Delete('SOUND_PLAYER_JETON');
1209 g_Sound_Delete('SOUND_PLAYER_JETOFF');
1210 g_Sound_Delete('SOUND_PLAYER_CASING1');
1211 g_Sound_Delete('SOUND_PLAYER_CASING2');
1212 g_Sound_Delete('SOUND_PLAYER_SHELL1');
1213 g_Sound_Delete('SOUND_PLAYER_SHELL2');
1214 end;
1217 function GunHitPlayer (X, Y: Integer; vx, vy: Integer; dmg: Integer; SpawnerUID: Word; AllowPush: Boolean): Boolean;
1218 var
1219 i: Integer;
1220 begin
1221 result := false;
1222 for i := 0 to High(gPlayers) do
1223 begin
1224 if (gPlayers[i] <> nil) and gPlayers[i].alive and gPlayers[i].Collide(X, Y) then
1225 begin
1226 if HitPlayer(gPlayers[i], dmg, vx*10, vy*10-3, SpawnerUID, HIT_SOME) then
1227 begin
1228 if AllowPush then gPlayers[i].Push(vx, vy);
1229 result := true;
1230 end;
1231 end;
1232 end;
1233 end;
1236 function GunHit (X, Y: Integer; vx, vy: Integer; dmg: Integer; SpawnerUID: Word; AllowPush: Boolean): Byte;
1238 function monsCheck (mon: TMonster): Boolean;
1239 begin
1240 result := false; // don't stop
1241 if HitMonster(mon, dmg, vx*10, vy*10-3, SpawnerUID, HIT_SOME) then
1242 begin
1243 if AllowPush then mon.Push(vx, vy);
1244 result := true;
1245 end;
1246 end;
1248 begin
1249 result := 0;
1250 if GunHitPlayer(X, Y, vx, vy, dmg, SpawnerUID, AllowPush) then result := 1
1251 else if g_Mons_ForEachAliveAt(X, Y, 1, 1, monsCheck) then result := 2;
1252 end;
1255 (*
1256 procedure g_Weapon_gunOld(const x, y, xd, yd, v, dmg: Integer; SpawnerUID: Word; CheckTrigger: Boolean);
1257 var
1258 a: Integer;
1259 x2, y2: Integer;
1260 dx, dy: Integer;
1261 xe, ye: Integer;
1262 xi, yi: Integer;
1263 s, c: Extended;
1264 //vx, vy: Integer;
1265 xx, yy, d: Integer;
1266 i: Integer;
1267 t1, _collide: Boolean;
1268 w, h: Word;
1269 {$IF DEFINED(D2F_DEBUG)}
1270 stt: UInt64;
1271 showTime: Boolean = true;
1272 {$ENDIF}
1273 begin
1274 a := GetAngle(x, y, xd, yd)+180;
1276 SinCos(DegToRad(-a), s, c);
1278 if Abs(s) < 0.01 then s := 0;
1279 if Abs(c) < 0.01 then c := 0;
1281 x2 := x+Round(c*gMapInfo.Width);
1282 y2 := y+Round(s*gMapInfo.Width);
1284 t1 := gWalls <> nil;
1285 _collide := False;
1286 w := gMapInfo.Width;
1287 h := gMapInfo.Height;
1289 xe := 0;
1290 ye := 0;
1291 dx := x2-x;
1292 dy := y2-y;
1294 if (xd = 0) and (yd = 0) then Exit;
1296 if dx > 0 then xi := 1 else if dx < 0 then xi := -1 else xi := 0;
1297 if dy > 0 then yi := 1 else if dy < 0 then yi := -1 else yi := 0;
1299 dx := Abs(dx);
1300 dy := Abs(dy);
1302 if dx > dy then d := dx else d := dy;
1304 //blood vel, for Monster.Damage()
1305 //vx := (dx*10 div d)*xi;
1306 //vy := (dy*10 div d)*yi;
1308 {$IF DEFINED(D2F_DEBUG)}
1309 stt := getTimeMicro();
1310 {$ENDIF}
1312 xx := x;
1313 yy := y;
1315 for i := 1 to d do
1316 begin
1317 xe := xe+dx;
1318 ye := ye+dy;
1320 if xe > d then
1321 begin
1322 xe := xe-d;
1323 xx := xx+xi;
1324 end;
1326 if ye > d then
1327 begin
1328 ye := ye-d;
1329 yy := yy+yi;
1330 end;
1332 if (yy > h) or (yy < 0) then Break;
1333 if (xx > w) or (xx < 0) then Break;
1335 if t1 then
1336 if ByteBool(gCollideMap[yy, xx] and MARK_BLOCKED) then
1337 begin
1338 _collide := True;
1339 {$IF DEFINED(D2F_DEBUG)}
1340 stt := getTimeMicro()-stt;
1341 e_WriteLog(Format('*** old trace time: %u microseconds', [LongWord(stt)]), MSG_NOTIFY);
1342 showTime := false;
1343 {$ENDIF}
1344 g_GFX_Spark(xx-xi, yy-yi, 2+Random(2), 180+a, 0, 0);
1345 if g_Game_IsServer and g_Game_IsNet then
1346 MH_SEND_Effect(xx-xi, yy-yi, 180+a, NET_GFX_SPARK);
1347 end;
1349 if not _collide then
1350 begin
1351 _collide := GunHit(xx, yy, xi*v, yi*v, dmg, SpawnerUID, v <> 0) <> 0;
1352 end;
1354 if _collide then Break;
1355 end;
1357 {$IF DEFINED(D2F_DEBUG)}
1358 if showTime then
1359 begin
1360 stt := getTimeMicro()-stt;
1361 e_WriteLog(Format('*** old trace time: %u microseconds', [LongWord(stt)]), MSG_NOTIFY);
1362 end;
1363 {$ENDIF}
1365 if CheckTrigger and g_Game_IsServer then
1366 g_Triggers_PressL(X, Y, xx-xi, yy-yi, SpawnerUID, ACTIVATE_SHOT);
1367 end;
1368 *)
1371 //!!!FIXME!!!
1372 procedure g_Weapon_gun (const x, y, xd, yd, v, indmg: Integer; SpawnerUID: Word; CheckTrigger: Boolean);
1373 var
1374 x0, y0: Integer;
1375 x2, y2: Integer;
1376 xi, yi: Integer;
1377 wallDistSq: Integer = $3fffffff;
1378 spawnerPlr: TPlayer = nil;
1379 dmg: Integer;
1381 function doPlayerHit (idx: Integer; hx, hy: Integer): Boolean;
1382 begin
1383 result := false;
1384 if (idx < 0) or (idx > High(gPlayers)) then exit;
1385 if (gPlayers[idx] = nil) or not gPlayers[idx].alive then exit;
1386 if (spawnerPlr <> nil) then
1387 begin
1388 if ((gGameSettings.Options and (GAME_OPTION_TEAMHITTRACE or GAME_OPTION_TEAMDAMAGE)) = 0) and
1389 (spawnerPlr.Team <> TEAM_NONE) and (spawnerPlr.Team = gPlayers[idx].Team) then
1390 begin
1391 if (spawnerPlr <> gPlayers[idx]) and ((gGameSettings.Options and GAME_OPTION_TEAMABSORBDAMAGE) = 0) then
1392 dmg := Max(1, dmg div 2);
1393 exit;
1394 end;
1395 end;
1396 result := HitPlayer(gPlayers[idx], dmg, (xi*v)*10, (yi*v)*10-3, SpawnerUID, HIT_SOME);
1397 if result and (v <> 0) then gPlayers[idx].Push((xi*v), (yi*v));
1398 {$IF DEFINED(D2F_DEBUG)}
1399 //if result then e_WriteLog(Format(' PLAYER #%d HIT', [idx]), MSG_NOTIFY);
1400 {$ENDIF}
1401 end;
1403 function doMonsterHit (mon: TMonster; hx, hy: Integer): Boolean;
1404 begin
1405 result := false;
1406 if (mon = nil) then exit;
1407 result := HitMonster(mon, dmg, (xi*v)*10, (yi*v)*10-3, SpawnerUID, HIT_SOME);
1408 if result and (v <> 0) then mon.Push((xi*v), (yi*v));
1409 {$IF DEFINED(D2F_DEBUG)}
1410 //if result then e_WriteLog(Format(' MONSTER #%u HIT', [LongWord(mon.UID)]), MSG_NOTIFY);
1411 {$ENDIF}
1412 end;
1414 // collect players along hitray
1415 // return `true` if instant hit was detected
1416 function playerPossibleHit (): Boolean;
1417 var
1418 i: Integer;
1419 px, py, pw, ph: Integer;
1420 inx, iny: Integer;
1421 distSq: Integer;
1422 plr: TPlayer;
1423 begin
1424 result := false;
1425 for i := 0 to High(gPlayers) do
1426 begin
1427 plr := gPlayers[i];
1428 if (plr <> nil) and plr.alive then
1429 begin
1430 plr.getMapBox(px, py, pw, ph);
1431 if lineAABBIntersects(x, y, x2, y2, px, py, pw, ph, inx, iny) then
1432 begin
1433 distSq := distanceSq(x, y, inx, iny);
1434 if (distSq = 0) then
1435 begin
1436 // contains
1437 if doPlayerHit(i, x, y) then begin result := true; exit; end;
1438 end
1439 else if (distSq < wallDistSq) then
1440 begin
1441 appendHitTimePlr(distSq, i, inx, iny);
1442 end;
1443 end;
1444 end;
1445 end;
1446 end;
1448 procedure sqchecker (mon: TMonster);
1449 var
1450 mx, my, mw, mh: Integer;
1451 inx, iny: Integer;
1452 distSq: Integer;
1453 begin
1454 mon.getMapBox(mx, my, mw, mh);
1455 if lineAABBIntersects(x0, y0, x2, y2, mx, my, mw, mh, inx, iny) then
1456 begin
1457 distSq := distanceSq(x0, y0, inx, iny);
1458 if (distSq < wallDistSq) then appendHitTimeMon(distSq, mon, inx, iny);
1459 end;
1460 end;
1462 var
1463 a: Integer;
1464 dx, dy: Integer;
1465 xe, ye: Integer;
1466 s, c: Extended;
1467 i: Integer;
1468 wallHitFlag: Boolean = false;
1469 wallHitX: Integer = 0;
1470 wallHitY: Integer = 0;
1471 didHit: Boolean = false;
1472 {$IF DEFINED(D2F_DEBUG)}
1473 stt: UInt64;
1474 {$ENDIF}
1475 mit: PMonster;
1476 it: TMonsterGrid.Iter;
1477 begin
1478 (*
1479 if not gwep_debug_fast_trace then
1480 begin
1481 g_Weapon_gunOld(x, y, xd, yd, v, dmg, SpawnerUID, CheckTrigger);
1482 exit;
1483 end;
1484 *)
1486 if (xd = 0) and (yd = 0) then exit;
1488 if (g_GetUIDType(SpawnerUID) = UID_PLAYER) then
1489 spawnerPlr := g_Player_Get(SpawnerUID);
1491 dmg := indmg;
1493 //wgunMonHash.reset(); //FIXME: clear hash on level change
1494 wgunHitHeap.clear();
1495 wgunHitTimeUsed := 0;
1497 a := GetAngle(x, y, xd, yd)+180;
1499 SinCos(DegToRad(-a), s, c);
1501 if Abs(s) < 0.01 then s := 0;
1502 if Abs(c) < 0.01 then c := 0;
1504 x0 := x;
1505 y0 := y;
1506 x2 := x+Round(c*gMapInfo.Width);
1507 y2 := y+Round(s*gMapInfo.Width);
1509 dx := x2-x;
1510 dy := y2-y;
1512 if (dx > 0) then xi := 1 else if (dx < 0) then xi := -1 else xi := 0;
1513 if (dy > 0) then yi := 1 else if (dy < 0) then yi := -1 else yi := 0;
1515 {$IF DEFINED(D2F_DEBUG)}
1516 e_WriteLog(Format('GUN TRACE: (%d,%d) to (%d,%d)', [x, y, x2, y2]), TMsgType.Notify);
1517 stt := getTimeMicro();
1518 {$ENDIF}
1520 wallHitFlag := (g_Map_traceToNearestWall(x, y, x2, y2, @wallHitX, @wallHitY) <> nil);
1521 if wallHitFlag then
1522 begin
1523 x2 := wallHitX;
1524 y2 := wallHitY;
1525 wallDistSq := distanceSq(x, y, wallHitX, wallHitY);
1526 end
1527 else
1528 begin
1529 wallHitX := x2;
1530 wallHitY := y2;
1531 end;
1533 if playerPossibleHit() then exit; // instant hit
1535 // collect monsters
1536 //g_Mons_AlongLine(x, y, x2, y2, sqchecker);
1538 it := monsGrid.forEachAlongLine(x, y, x2, y2, -1);
1539 for mit in it do sqchecker(mit^);
1540 it.release();
1542 // here, we collected all monsters and players in `wgunHitHeap` and `wgunHitTime`
1543 // also, if `wallWasHit` is `true`, then `wallHitX` and `wallHitY` contains spark coords
1544 while (wgunHitHeap.count > 0) do
1545 begin
1546 // has some entities to check, do it
1547 i := wgunHitHeap.front;
1548 wgunHitHeap.popFront();
1549 // hitpoint
1550 xe := wgunHitTime[i].x;
1551 ye := wgunHitTime[i].y;
1552 // check if it is not behind the wall
1553 if (wgunHitTime[i].mon <> nil) then
1554 begin
1555 didHit := doMonsterHit(wgunHitTime[i].mon, xe, ye);
1556 end
1557 else
1558 begin
1559 didHit := doPlayerHit(wgunHitTime[i].plridx, xe, ye);
1560 end;
1561 if didHit then
1562 begin
1563 // need new coords for trigger
1564 wallHitX := xe;
1565 wallHitY := ye;
1566 wallHitFlag := false; // no sparks
1567 break;
1568 end;
1569 end;
1571 // need sparks?
1572 if wallHitFlag then
1573 begin
1574 {$IF DEFINED(D2F_DEBUG)}
1575 stt := getTimeMicro()-stt;
1576 e_WriteLog(Format('*** new trace time: %u microseconds', [LongWord(stt)]), TMsgType.Notify);
1577 {$ENDIF}
1578 {$IFDEF ENABLE_GFX}
1579 g_GFX_Spark(wallHitX, wallHitY, 2+Random(2), 180+a, 0, 0);
1580 {$ENDIF}
1581 if g_Game_IsServer and g_Game_IsNet then MH_SEND_Effect(wallHitX, wallHitY, 180+a, NET_GFX_SPARK);
1582 end
1583 else
1584 begin
1585 {$IF DEFINED(D2F_DEBUG)}
1586 stt := getTimeMicro()-stt;
1587 e_WriteLog(Format('*** new trace time: %u microseconds', [LongWord(stt)]), TMsgType.Notify);
1588 {$ENDIF}
1589 end;
1591 if CheckTrigger and g_Game_IsServer then g_Triggers_PressL(X, Y, wallHitX, wallHitY, SpawnerUID, ACTIVATE_SHOT);
1592 end;
1595 procedure g_Weapon_punch(x, y: Integer; d, SpawnerUID: Word);
1596 var
1597 obj: TObj;
1598 begin
1599 obj.X := X;
1600 obj.Y := Y;
1601 obj.rect.X := 0;
1602 obj.rect.Y := 0;
1603 obj.rect.Width := 39;
1604 obj.rect.Height := 52;
1605 obj.Vel.X := 0;
1606 obj.Vel.Y := 0;
1607 obj.Accel.X := 0;
1608 obj.Accel.Y := 0;
1610 if g_Weapon_Hit(@obj, d, SpawnerUID, HIT_SOME) <> 0 then
1611 g_Sound_PlayExAt('SOUND_WEAPON_HITPUNCH', x, y)
1612 else
1613 g_Sound_PlayExAt('SOUND_WEAPON_MISSPUNCH', x, y);
1614 end;
1616 function g_Weapon_chainsaw(x, y: Integer; d, SpawnerUID: Word): Integer;
1617 var
1618 obj: TObj;
1619 begin
1620 obj.X := X;
1621 obj.Y := Y;
1622 obj.rect.X := 0;
1623 obj.rect.Y := 0;
1624 obj.rect.Width := 32;
1625 obj.rect.Height := 52;
1626 obj.Vel.X := 0;
1627 obj.Vel.Y := 0;
1628 obj.Accel.X := 0;
1629 obj.Accel.Y := 0;
1631 Result := g_Weapon_Hit(@obj, d, SpawnerUID, HIT_SOME);
1632 end;
1634 procedure g_Weapon_rocket(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1635 Silent: Boolean = False; compat: Boolean = true);
1636 var
1637 find_id: DWORD;
1638 dx, dy: Integer;
1639 begin
1640 if WID < 0 then
1641 find_id := FindShot()
1642 else
1643 begin
1644 find_id := WID;
1645 if Integer(find_id) >= High(Shots) then
1646 SetLength(Shots, find_id + 64)
1647 end;
1649 with Shots[find_id] do
1650 begin
1651 g_Obj_Init(@Obj);
1653 Obj.Rect.Width := SHOT_ROCKETLAUNCHER_WIDTH;
1654 Obj.Rect.Height := SHOT_ROCKETLAUNCHER_HEIGHT;
1656 if compat then
1657 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1658 else
1659 dx := -(Obj.Rect.Width div 2);
1660 dy := -(Obj.Rect.Height div 2);
1662 ShotType := WEAPON_ROCKETLAUNCHER;
1663 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 12);
1665 Animation := nil;
1666 triggers := nil;
1667 end;
1669 Shots[find_id].SpawnerUID := SpawnerUID;
1671 if not Silent then
1672 g_Sound_PlayExAt('SOUND_WEAPON_FIREROCKET', x, y);
1673 end;
1675 procedure g_Weapon_revf(x, y, xd, yd: Integer; SpawnerUID, TargetUID: Word;
1676 WID: Integer = -1; Silent: Boolean = False);
1677 var
1678 find_id: DWORD;
1679 dx, dy: Integer;
1680 begin
1681 if WID < 0 then
1682 find_id := FindShot()
1683 else
1684 begin
1685 find_id := WID;
1686 if Integer(find_id) >= High(Shots) then
1687 SetLength(Shots, find_id + 64)
1688 end;
1690 with Shots[find_id] do
1691 begin
1692 g_Obj_Init(@Obj);
1694 Obj.Rect.Width := SHOT_SKELFIRE_WIDTH;
1695 Obj.Rect.Height := SHOT_SKELFIRE_HEIGHT;
1697 dx := -(Obj.Rect.Width div 2);
1698 dy := -(Obj.Rect.Height div 2);
1700 ShotType := WEAPON_SKEL_FIRE;
1701 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 12);
1703 triggers := nil;
1704 target := TargetUID;
1705 Animation := TAnimationState.Create(True, 5, 2); // !!! put values into table
1706 end;
1708 Shots[find_id].SpawnerUID := SpawnerUID;
1710 if not Silent then
1711 g_Sound_PlayExAt('SOUND_WEAPON_FIREREV', x, y);
1712 end;
1714 procedure g_Weapon_plasma(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1715 Silent: Boolean = False; compat: Boolean = true);
1716 var
1717 find_id: DWORD;
1718 dx, dy: Integer;
1719 begin
1720 if WID < 0 then
1721 find_id := FindShot()
1722 else
1723 begin
1724 find_id := WID;
1725 if Integer(find_id) >= High(Shots) then
1726 SetLength(Shots, find_id + 64);
1727 end;
1729 with Shots[find_id] do
1730 begin
1731 g_Obj_Init(@Obj);
1733 Obj.Rect.Width := SHOT_PLASMA_WIDTH;
1734 Obj.Rect.Height := SHOT_PLASMA_HEIGHT;
1736 if compat then
1737 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1738 else
1739 dx := -(Obj.Rect.Width div 2);
1740 dy := -(Obj.Rect.Height div 2);
1742 ShotType := WEAPON_PLASMA;
1743 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1745 triggers := nil;
1746 Animation := TAnimationState.Create(True, 5, 2); // !!! put values into table
1747 end;
1749 Shots[find_id].SpawnerUID := SpawnerUID;
1751 if not Silent then
1752 g_Sound_PlayExAt('SOUND_WEAPON_FIREPLASMA', x, y);
1753 end;
1755 procedure g_Weapon_flame(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1756 Silent: Boolean = False; compat: Boolean = true);
1757 var
1758 find_id: DWORD;
1759 dx, dy: Integer;
1760 begin
1761 if WID < 0 then
1762 find_id := FindShot()
1763 else
1764 begin
1765 find_id := WID;
1766 if Integer(find_id) >= High(Shots) then
1767 SetLength(Shots, find_id + 64);
1768 end;
1770 with Shots[find_id] do
1771 begin
1772 g_Obj_Init(@Obj);
1774 Obj.Rect.Width := SHOT_FLAME_WIDTH;
1775 Obj.Rect.Height := SHOT_FLAME_HEIGHT;
1777 if compat then
1778 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1779 else
1780 dx := -(Obj.Rect.Width div 2);
1781 dy := -(Obj.Rect.Height div 2);
1783 ShotType := WEAPON_FLAMETHROWER;
1784 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1786 triggers := nil;
1787 Animation := nil;
1788 end;
1790 Shots[find_id].SpawnerUID := SpawnerUID;
1792 // if not Silent then
1793 // g_Sound_PlayExAt('SOUND_WEAPON_FIREPLASMA', x, y);
1794 end;
1796 procedure g_Weapon_ball1(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1797 Silent: Boolean = False; compat: Boolean = true);
1798 var
1799 find_id: DWORD;
1800 dx, dy: Integer;
1801 begin
1802 if WID < 0 then
1803 find_id := FindShot()
1804 else
1805 begin
1806 find_id := WID;
1807 if Integer(find_id) >= High(Shots) then
1808 SetLength(Shots, find_id + 64)
1809 end;
1811 with Shots[find_id] do
1812 begin
1813 g_Obj_Init(@Obj);
1815 Obj.Rect.Width := 16;
1816 Obj.Rect.Height := 16;
1818 if compat then
1819 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1820 else
1821 dx := -(Obj.Rect.Width div 2);
1822 dy := -(Obj.Rect.Height div 2);
1824 ShotType := WEAPON_IMP_FIRE;
1825 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1827 triggers := nil;
1828 Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
1829 end;
1831 Shots[find_id].SpawnerUID := SpawnerUID;
1833 if not Silent then
1834 g_Sound_PlayExAt('SOUND_WEAPON_FIREBALL', x, y);
1835 end;
1837 procedure g_Weapon_ball2(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1838 Silent: Boolean = False; compat: Boolean = true);
1839 var
1840 find_id: DWORD;
1841 dx, dy: Integer;
1842 begin
1843 if WID < 0 then
1844 find_id := FindShot()
1845 else
1846 begin
1847 find_id := WID;
1848 if Integer(find_id) >= High(Shots) then
1849 SetLength(Shots, find_id + 64)
1850 end;
1852 with Shots[find_id] do
1853 begin
1854 g_Obj_Init(@Obj);
1856 Obj.Rect.Width := 16;
1857 Obj.Rect.Height := 16;
1859 if compat then
1860 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1861 else
1862 dx := -(Obj.Rect.Width div 2);
1863 dy := -(Obj.Rect.Height div 2);
1865 ShotType := WEAPON_CACO_FIRE;
1866 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1868 triggers := nil;
1869 Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
1870 end;
1872 Shots[find_id].SpawnerUID := SpawnerUID;
1874 if not Silent then
1875 g_Sound_PlayExAt('SOUND_WEAPON_FIREBALL', x, y);
1876 end;
1878 procedure g_Weapon_ball7(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1879 Silent: Boolean = False; compat: Boolean = true);
1880 var
1881 find_id: DWORD;
1882 dx, dy: Integer;
1883 begin
1884 if WID < 0 then
1885 find_id := FindShot()
1886 else
1887 begin
1888 find_id := WID;
1889 if Integer(find_id) >= High(Shots) then
1890 SetLength(Shots, find_id + 64)
1891 end;
1893 with Shots[find_id] do
1894 begin
1895 g_Obj_Init(@Obj);
1897 Obj.Rect.Width := 32;
1898 Obj.Rect.Height := 16;
1900 if compat then
1901 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1902 else
1903 dx := -(Obj.Rect.Width div 2);
1904 dy := -(Obj.Rect.Height div 2);
1906 ShotType := WEAPON_BARON_FIRE;
1907 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1909 triggers := nil;
1910 Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
1911 end;
1913 Shots[find_id].SpawnerUID := SpawnerUID;
1915 if not Silent then
1916 g_Sound_PlayExAt('SOUND_WEAPON_FIREBALL', x, y);
1917 end;
1919 procedure g_Weapon_aplasma(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1920 Silent: Boolean = False; compat: Boolean = true);
1921 var
1922 find_id: DWORD;
1923 dx, dy: Integer;
1924 begin
1925 if WID < 0 then
1926 find_id := FindShot()
1927 else
1928 begin
1929 find_id := WID;
1930 if Integer(find_id) >= High(Shots) then
1931 SetLength(Shots, find_id + 64)
1932 end;
1934 with Shots[find_id] do
1935 begin
1936 g_Obj_Init(@Obj);
1938 Obj.Rect.Width := 16;
1939 Obj.Rect.Height := 16;
1941 if compat then
1942 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1943 else
1944 dx := -(Obj.Rect.Width div 2);
1945 dy := -(Obj.Rect.Height div 2);
1947 ShotType := WEAPON_BSP_FIRE;
1948 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1950 triggers := nil;
1952 Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
1953 end;
1955 Shots[find_id].SpawnerUID := SpawnerUID;
1957 if not Silent then
1958 g_Sound_PlayExAt('SOUND_WEAPON_FIREPLASMA', x, y);
1959 end;
1961 procedure g_Weapon_manfire(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1962 Silent: Boolean = False; compat: Boolean = true);
1963 var
1964 find_id: DWORD;
1965 dx, dy: Integer;
1966 begin
1967 if WID < 0 then
1968 find_id := FindShot()
1969 else
1970 begin
1971 find_id := WID;
1972 if Integer(find_id) >= High(Shots) then
1973 SetLength(Shots, find_id + 64)
1974 end;
1976 with Shots[find_id] do
1977 begin
1978 g_Obj_Init(@Obj);
1980 Obj.Rect.Width := 32;
1981 Obj.Rect.Height := 32;
1983 if compat then
1984 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1985 else
1986 dx := -(Obj.Rect.Width div 2);
1987 dy := -(Obj.Rect.Height div 2);
1989 ShotType := WEAPON_MANCUB_FIRE;
1990 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1992 triggers := nil;
1994 Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
1995 end;
1997 Shots[find_id].SpawnerUID := SpawnerUID;
1999 if not Silent then
2000 g_Sound_PlayExAt('SOUND_WEAPON_FIREBALL', x, y);
2001 end;
2003 procedure g_Weapon_bfgshot(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
2004 Silent: Boolean = False; compat: Boolean = true);
2005 var
2006 find_id: DWORD;
2007 dx, dy: Integer;
2008 begin
2009 if WID < 0 then
2010 find_id := FindShot()
2011 else
2012 begin
2013 find_id := WID;
2014 if Integer(find_id) >= High(Shots) then
2015 SetLength(Shots, find_id + 64)
2016 end;
2018 with Shots[find_id] do
2019 begin
2020 g_Obj_Init(@Obj);
2022 Obj.Rect.Width := SHOT_BFG_WIDTH;
2023 Obj.Rect.Height := SHOT_BFG_HEIGHT;
2025 if compat then
2026 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
2027 else
2028 dx := -(Obj.Rect.Width div 2);
2029 dy := -(Obj.Rect.Height div 2);
2031 ShotType := WEAPON_BFG;
2032 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
2034 triggers := nil;
2035 Animation := TAnimationState.Create(True, 6, 2); // !!! put values into table
2036 end;
2038 Shots[find_id].SpawnerUID := SpawnerUID;
2040 if not Silent then
2041 g_Sound_PlayExAt('SOUND_WEAPON_FIREBFG', x, y);
2042 end;
2044 procedure g_Weapon_bfghit(x, y: Integer);
2045 begin
2046 {$IFDEF ENABLE_GFX}
2047 g_GFX_QueueEffect(R_GFX_BFG_HIT, x - 32, y - 32);
2048 {$ENDIF}
2049 end;
2051 procedure g_Weapon_pistol(x, y, xd, yd: Integer; SpawnerUID: Word;
2052 Silent: Boolean = False);
2053 begin
2054 if not Silent then
2055 g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', x, y);
2057 g_Weapon_gun(x, y, xd, yd, 1, 3, SpawnerUID, True);
2058 if gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF] then
2059 begin
2060 if ABS(x-xd) >= ABS(y-yd) then
2061 begin
2062 g_Weapon_gun(x, y+1, xd, yd+1, 1, 3, SpawnerUID, False);
2063 g_Weapon_gun(x, y-1, xd, yd-1, 1, 2, SpawnerUID, False);
2064 end
2065 else
2066 begin
2067 g_Weapon_gun(x+1, y, xd+1, yd, 1, 3, SpawnerUID, False);
2068 g_Weapon_gun(x-1, y, xd-1, yd, 1, 2, SpawnerUID, False);
2069 end;
2070 end;
2071 end;
2073 procedure g_Weapon_mgun(x, y, xd, yd: Integer; SpawnerUID: Word;
2074 Silent: Boolean = False);
2075 begin
2076 if not Silent then
2077 if gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', x, y);
2079 g_Weapon_gun(x, y, xd, yd, 1, 3, SpawnerUID, True);
2080 if (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF]) and
2081 (g_GetUIDType(SpawnerUID) = UID_PLAYER) then
2082 begin
2083 if ABS(x-xd) >= ABS(y-yd) then
2084 begin
2085 g_Weapon_gun(x, y+1, xd, yd+1, 1, 2, SpawnerUID, False);
2086 g_Weapon_gun(x, y-1, xd, yd-1, 1, 2, SpawnerUID, False);
2087 end
2088 else
2089 begin
2090 g_Weapon_gun(x+1, y, xd+1, yd, 1, 2, SpawnerUID, False);
2091 g_Weapon_gun(x-1, y, xd-1, yd, 1, 2, SpawnerUID, False);
2092 end;
2093 end;
2094 end;
2096 procedure g_Weapon_shotgun(x, y, xd, yd: Integer; SpawnerUID: Word;
2097 Silent: Boolean = False);
2098 var
2099 i, j, k: Integer;
2100 begin
2101 if not Silent then
2102 if gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', x, y);
2104 for i := 0 to 9 do
2105 begin
2106 j := 0; k := 0;
2107 if ABS(x-xd) >= ABS(y-yd) then j := Random(17) - 8 else k := Random(17) - 8; // -8 .. 8
2108 g_Weapon_gun(x+k, y+j, xd+k, yd+j, IfThen(i mod 2 <> 0, 1, 0), 3, SpawnerUID, i=0);
2109 end;
2110 end;
2112 procedure g_Weapon_dshotgun(x, y, xd, yd: Integer; SpawnerUID: Word;
2113 Silent: Boolean = False);
2114 var
2115 a, i, j, k: Integer;
2116 begin
2117 if not Silent then
2118 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN2', x, y);
2120 if gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF] then a := 25 else a := 20;
2121 for i := 0 to a do
2122 begin
2123 j := 0; k := 0;
2124 if ABS(x-xd) >= ABS(y-yd) then j := Random(41) - 20 else k := Random(41) - 20; // -20 .. 20
2125 g_Weapon_gun(x+k, y+j, xd+k, yd+j, IfThen(i mod 3 <> 0, 0, 1), 3, SpawnerUID, i=0);
2126 end;
2127 end;
2129 procedure g_Weapon_PreUpdate();
2130 var
2131 i: Integer;
2132 begin
2133 if Shots = nil then Exit;
2134 for i := 0 to High(Shots) do
2135 if Shots[i].ShotType <> 0 then
2136 begin
2137 Shots[i].Obj.oldX := Shots[i].Obj.X;
2138 Shots[i].Obj.oldY := Shots[i].Obj.Y;
2139 end;
2140 end;
2142 procedure g_Weapon_Update();
2143 var
2144 i, a, h, cx, cy, oldvx, oldvy, tf: Integer;
2145 t: DWArray;
2146 st: Word;
2147 o: TObj;
2148 spl: Boolean;
2149 Loud: Boolean;
2150 {$IFDEF ENABLE_GFX}
2151 var tcx, tcy: Integer;
2152 {$ENDIF}
2153 begin
2154 if Shots = nil then
2155 Exit;
2157 for i := 0 to High(Shots) do
2158 begin
2159 if Shots[i].ShotType = 0 then
2160 Continue;
2162 Loud := True;
2164 with Shots[i] do
2165 begin
2166 Timeout := Timeout - 1;
2167 oldvx := Obj.Vel.X;
2168 oldvy := Obj.Vel.Y;
2169 // Àêòèâèðîâàòü òðèããåðû ïî ïóòè (êðîìå óæå àêòèâèðîâàííûõ):
2170 if (Stopped = 0) and g_Game_IsServer then
2171 t := g_Triggers_PressR(Obj.X, Obj.Y, Obj.Rect.Width, Obj.Rect.Height,
2172 SpawnerUID, ACTIVATE_SHOT, triggers)
2173 else
2174 t := nil;
2176 if t <> nil then
2177 begin
2178 // Ïîïîëíÿåì ñïèñîê àêòèâèðîâàííûõ òðèããåðîâ:
2179 if triggers = nil then
2180 triggers := t
2181 else
2182 begin
2183 h := High(t);
2185 for a := 0 to h do
2186 if not InDWArray(t[a], triggers) then
2187 begin
2188 SetLength(triggers, Length(triggers)+1);
2189 triggers[High(triggers)] := t[a];
2190 end;
2191 end;
2192 end;
2194 // Àíèìàöèÿ ñíàðÿäà:
2195 if Animation <> nil then
2196 Animation.Update();
2198 // Äâèæåíèå:
2199 spl := (ShotType <> WEAPON_PLASMA) and
2200 (ShotType <> WEAPON_BFG) and
2201 (ShotType <> WEAPON_BSP_FIRE) and
2202 (ShotType <> WEAPON_FLAMETHROWER);
2204 if Stopped = 0 then
2205 begin
2206 st := g_Obj_Move_Projectile(@Obj, False, spl);
2207 end
2208 else
2209 begin
2210 st := 0;
2211 end;
2212 positionChanged(); // this updates spatial accelerators
2214 if WordBool(st and MOVE_FALLOUT) or (Obj.X < -1000) or
2215 (Obj.X > gMapInfo.Width+1000) or (Obj.Y < -1000) then
2216 begin
2217 // Íà êëèåíòå ñêîðåå âñåãî è òàê óæå âûïàë.
2218 ShotType := 0;
2219 Animation.Free();
2220 Continue;
2221 end;
2223 cx := Obj.X + (Obj.Rect.Width div 2);
2224 cy := Obj.Y + (Obj.Rect.Height div 2);
2226 case ShotType of
2227 WEAPON_ROCKETLAUNCHER, WEAPON_SKEL_FIRE: // Ðàêåòû è ñíàðÿäû Ñêåëåòà
2228 begin
2229 // Âûëåòåëà èç âîäû:
2230 if WordBool(st and MOVE_HITAIR) then
2231 g_Obj_SetSpeed(@Obj, 12);
2233 // Â âîäå øëåéô - ïóçûðè, â âîçäóõå øëåéô - äûì:
2234 if WordBool(st and MOVE_INWATER) then
2235 begin
2236 {$IFDEF ENABLE_GFX}
2237 g_GFX_Bubbles(cx, cy, 1+Random(3), 16, 16);
2238 {$ENDIF}
2239 if Random(2) = 0
2240 then g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', cx, cy)
2241 else g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', cx, cy);
2242 end
2243 else
2244 begin
2245 {$IFDEF ENABLE_GFX}
2246 g_GFX_QueueEffect(R_GFX_SMOKE_TRANS, Obj.X-14+Random(9), cy-20+Random(9));
2247 {$ENDIF}
2248 end;
2250 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2251 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL)) or
2252 (g_Weapon_Hit(@Obj, 10, SpawnerUID, HIT_SOME, False) <> 0) or
2253 (Timeout < 1) then
2254 begin
2255 Obj.Vel.X := 0;
2256 Obj.Vel.Y := 0;
2258 g_Weapon_Explode(cx, cy, 60, SpawnerUID);
2260 if ShotType = WEAPON_SKEL_FIRE then
2261 begin // Âçðûâ ñíàðÿäà Ñêåëåòà
2262 {$IFDEF ENABLE_GFX}
2263 g_GFX_QueueEffect(R_GFX_EXPLODE_SKELFIRE, Obj.X + 32 - 58, Obj.Y + 8 - 36);
2264 g_DynLightExplosion((Obj.X+32), (Obj.Y+8), 64, 1, 0, 0);
2265 {$ENDIF}
2266 end
2267 else
2268 begin // Âçðûâ Ðàêåòû
2269 {$IFDEF ENABLE_GFX}
2270 g_GFX_QueueEffect(R_GFX_EXPLODE_ROCKET, cx - 64, cy - 64);
2271 g_DynLightExplosion(cx, cy, 64, 1, 0, 0);
2272 {$ENDIF}
2273 end;
2275 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEROCKET', Obj.X, Obj.Y);
2277 ShotType := 0;
2278 end;
2280 if ShotType = WEAPON_SKEL_FIRE then
2281 begin // Ñàìîíàâîäêà ñíàðÿäà Ñêåëåòà:
2282 if GetPos(target, @o) then
2283 throw(i, Obj.X, Obj.Y,
2284 o.X+o.Rect.X+(o.Rect.Width div 2)+o.Vel.X+o.Accel.X,
2285 o.Y+o.Rect.Y+(o.Rect.Height div 2)+o.Vel.Y+o.Accel.Y,
2286 12);
2287 end;
2288 end;
2290 WEAPON_PLASMA, WEAPON_BSP_FIRE: // Ïëàçìà, ïëàçìà Àðàõíàòðîíà
2291 begin
2292 // Ïîïàëà â âîäó - ýëåêòðîøîê ïî âîäå:
2293 if WordBool(st and (MOVE_INWATER or MOVE_HITWATER)) then
2294 begin
2295 g_Sound_PlayExAt('SOUND_WEAPON_PLASMAWATER', Obj.X, Obj.Y);
2296 if g_Game_IsServer then CheckTrap(i, 10, HIT_ELECTRO);
2297 ShotType := 0;
2298 Continue;
2299 end;
2301 // Âåëè÷èíà óðîíà:
2302 if (ShotType = WEAPON_PLASMA) and
2303 (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF]) then
2304 a := 10
2305 else
2306 a := 5;
2308 if ShotType = WEAPON_BSP_FIRE then
2309 a := 10;
2311 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2312 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL)) or
2313 (g_Weapon_Hit(@Obj, a, SpawnerUID, HIT_SOME, False) <> 0) or
2314 (Timeout < 1) then
2315 begin
2316 {$IFDEF ENABLE_GFX}
2317 if ShotType = WEAPON_PLASMA then
2318 g_GFX_QueueEffect(R_GFX_EXPLODE_PLASMA, cx - 16, cy - 16)
2319 else
2320 g_GFX_QueueEffect(R_GFX_EXPLODE_BSPFIRE, cx - 16, cy - 16);
2321 g_DynLightExplosion(cx, cy, 32, 0, 0.5, 0.5);
2322 {$ENDIF}
2323 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEPLASMA', Obj.X, Obj.Y);
2324 ShotType := 0;
2325 end;
2326 end;
2328 WEAPON_FLAMETHROWER: // Îãíåìåò
2329 begin
2330 // Ñî âðåìåíåì óìèðàåò
2331 if (Timeout < 1) then
2332 begin
2333 ShotType := 0;
2334 Continue;
2335 end;
2336 // Ïîä âîäîé òîæå
2337 if WordBool(st and (MOVE_HITWATER or MOVE_INWATER)) then
2338 begin
2339 if WordBool(st and MOVE_HITWATER) then
2340 begin
2341 {$IFDEF ENABLE_GFX}
2342 tcx := Random(8);
2343 tcy := Random(8);
2344 g_GFX_QueueEffect(R_GFX_SMOKE, cx-4+tcx-(R_GFX_SMOKE_WIDTH div 2), cy-4+tcy-(R_GFX_SMOKE_HEIGHT div 2));
2345 {$ENDIF}
2346 end
2347 else
2348 begin
2349 {$IFDEF ENABLE_GFX}
2350 g_GFX_Bubbles(cx, cy, 1+Random(3), 16, 16);
2351 {$ENDIF}
2352 if Random(2) = 0
2353 then g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', cx, cy)
2354 else g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', cx, cy);
2355 end;
2356 ShotType := 0;
2357 Continue;
2358 end;
2360 // Ãðàâèòàöèÿ
2361 if Stopped = 0 then
2362 Obj.Accel.Y := Obj.Accel.Y + 1;
2363 // Ïîïàëè â ñòåíó èëè â âîäó:
2364 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL or MOVE_HITWATER)) then
2365 begin
2366 // Ïðèëèïàåì:
2367 Obj.Vel.X := 0;
2368 Obj.Vel.Y := 0;
2369 Obj.Accel.Y := 0;
2370 if WordBool(st and MOVE_HITWALL) then
2371 Stopped := MOVE_HITWALL
2372 else if WordBool(st and MOVE_HITLAND) then
2373 Stopped := MOVE_HITLAND
2374 else if WordBool(st and MOVE_HITCEIL) then
2375 Stopped := MOVE_HITCEIL;
2376 end;
2378 a := IfThen(Stopped = 0, 10, 1);
2379 // Åñëè â êîãî-òî ïîïàëè
2380 if g_Weapon_Hit(@Obj, a, SpawnerUID, HIT_FLAME, False) <> 0 then
2381 begin
2382 // HIT_FLAME ñàì ïîäîææåò
2383 // Åñëè â ïîëåòå ïîïàëè, èñ÷åçàåì
2384 if Stopped = 0 then
2385 ShotType := 0;
2386 end;
2388 if Stopped = 0 then
2389 tf := 2
2390 else
2391 tf := 3;
2393 if (gTime mod LongWord(tf) = 0) then
2394 begin
2395 {$IFDEF ENABLE_GFX}
2396 case Stopped of
2397 MOVE_HITWALL: begin tcx := cx-4+Random(8); tcy := cy-12+Random(24); end;
2398 MOVE_HITLAND: begin tcx := cx-12+Random(24); tcy := cy-10+Random(8); end;
2399 MOVE_HITCEIL: begin tcx := cx-12+Random(24); tcy := cy+6+Random(8); end;
2400 else begin tcx := cx-4+Random(8); tcy := cy-4+Random(8); end;
2401 end;
2402 g_GFX_QueueEffect(R_GFX_FLAME_RAND, tcx - (R_GFX_FLAME_WIDTH div 2), tcy - (R_GFX_FLAME_HEIGHT div 2));
2403 //g_DynLightExplosion(tcx, tcy, 1, 1, 0.8, 0.3);
2404 {$ENDIF}
2405 end;
2406 end;
2408 WEAPON_BFG: // BFG
2409 begin
2410 // Ïîïàëà â âîäó - ýëåêòðîøîê ïî âîäå:
2411 if WordBool(st and (MOVE_INWATER or MOVE_HITWATER)) then
2412 begin
2413 g_Sound_PlayExAt('SOUND_WEAPON_BFGWATER', Obj.X, Obj.Y);
2414 if g_Game_IsServer then CheckTrap(i, 1000, HIT_ELECTRO);
2415 ShotType := 0;
2416 Continue;
2417 end;
2419 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2420 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL)) or
2421 (g_Weapon_Hit(@Obj, SHOT_BFG_DAMAGE, SpawnerUID, HIT_BFG, False) <> 0) or
2422 (Timeout < 1) then
2423 begin
2424 // Ëó÷è BFG:
2425 if g_Game_IsServer then g_Weapon_BFG9000(cx, cy, SpawnerUID);
2426 {$IFDEF ENABLE_GFX}
2427 g_GFX_QueueEffect(R_GFX_EXPLODE_BFG, cx - 64, cy - 64);
2428 g_DynLightExplosion(cx, cy, 96, 0, 1, 0);
2429 {$ENDIF}
2430 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBFG', Obj.X, Obj.Y);
2431 ShotType := 0;
2432 end;
2433 end;
2435 WEAPON_IMP_FIRE, WEAPON_CACO_FIRE, WEAPON_BARON_FIRE: // Âûñòðåëû Áåñà, Êàêîäåìîíà Ðûöàðÿ/Áàðîíà àäà
2436 begin
2437 // Âûëåòåë èç âîäû:
2438 if WordBool(st and MOVE_HITAIR) then
2439 g_Obj_SetSpeed(@Obj, 16);
2441 // Âåëè÷èíà óðîíà:
2442 if ShotType = WEAPON_IMP_FIRE then
2443 a := 5
2444 else
2445 if ShotType = WEAPON_CACO_FIRE then
2446 a := 20
2447 else
2448 a := 40;
2450 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2451 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL)) or
2452 (g_Weapon_Hit(@Obj, a, SpawnerUID, HIT_SOME) <> 0) or
2453 (Timeout < 1) then
2454 begin
2455 {$IFDEF ENABLE_GFX}
2456 case ShotType of
2457 WEAPON_IMP_FIRE: g_GFX_QueueEffect(R_GFX_EXPLODE_IMPFIRE, cx - 32, cy - 32);
2458 WEAPON_CACO_FIRE: g_GFX_QueueEffect(R_GFX_EXPLODE_CACOFIRE, cx - 32, cy - 32);
2459 WEAPON_BARON_FIRE: g_GFX_QueueEffect(R_GFX_EXPLODE_BARONFIRE, cx - 32, cy - 32);
2460 end;
2461 {$ENDIF}
2462 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBALL', Obj.X, Obj.Y);
2463 ShotType := 0;
2464 end;
2465 end;
2467 WEAPON_MANCUB_FIRE: // Âûñòðåë Ìàíêóáóñà
2468 begin
2469 // Âûëåòåë èç âîäû:
2470 if WordBool(st and MOVE_HITAIR) then
2471 g_Obj_SetSpeed(@Obj, 16);
2473 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2474 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL)) or
2475 (g_Weapon_Hit(@Obj, 40, SpawnerUID, HIT_SOME, False) <> 0) or
2476 (Timeout < 1) then
2477 begin
2478 // Âçðûâ:
2479 {$IFDEF ENABLE_GFX}
2480 g_GFX_QueueEffect(R_GFX_EXPLODE_ROCKET, cx - 64, cy - 64);
2481 {$ENDIF}
2482 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBALL', Obj.X, Obj.Y);
2483 ShotType := 0;
2484 end;
2485 end;
2486 end; // case ShotType of...
2488 // Åñëè ñíàðÿäà óæå íåò, óäàëÿåì àíèìàöèþ:
2489 if (ShotType = 0) then
2490 begin
2491 if gGameSettings.GameType = GT_SERVER then
2492 MH_SEND_DeleteShot(i, Obj.X, Obj.Y, Loud);
2493 if Animation <> nil then
2494 begin
2495 Animation.Free();
2496 Animation := nil;
2497 end;
2498 end
2499 else if (ShotType <> WEAPON_FLAMETHROWER) and ((oldvx <> Obj.Vel.X) or (oldvy <> Obj.Vel.Y)) then
2500 if gGameSettings.GameType = GT_SERVER then
2501 MH_SEND_UpdateShot(i);
2502 end;
2503 end;
2504 end;
2506 function g_Weapon_Danger(UID: Word; X, Y: Integer; Width, Height: Word; Time: Byte): Boolean;
2507 var
2508 a: Integer;
2509 begin
2510 Result := False;
2512 if Shots = nil then
2513 Exit;
2515 for a := 0 to High(Shots) do
2516 if (Shots[a].ShotType <> 0) and (Shots[a].SpawnerUID <> UID) then
2517 if ((Shots[a].Obj.Vel.Y = 0) and (Shots[a].Obj.Vel.X > 0) and (Shots[a].Obj.X < X)) or
2518 (Shots[a].Obj.Vel.Y = 0) and (Shots[a].Obj.Vel.X < 0) and (Shots[a].Obj.X > X) then
2519 if (Abs(X-Shots[a].Obj.X) < Abs(Shots[a].Obj.Vel.X*Time)) and
2520 g_Collide(X, Y, Width, Height, X, Shots[a].Obj.Y,
2521 Shots[a].Obj.Rect.Width, Shots[a].Obj.Rect.Height) and
2522 g_TraceVector(X, Y, Shots[a].Obj.X, Shots[a].Obj.Y) then
2523 begin
2524 Result := True;
2525 Exit;
2526 end;
2527 end;
2529 procedure g_Weapon_SaveState (st: TStream);
2530 var
2531 count, i, j: Integer;
2532 begin
2533 // Ñ÷èòàåì êîëè÷åñòâî ñóùåñòâóþùèõ ñíàðÿäîâ
2534 count := 0;
2535 for i := 0 to High(Shots) do if (Shots[i].ShotType <> 0) then Inc(count);
2537 // Êîëè÷åñòâî ñíàðÿäîâ
2538 utils.WriteInt(st, count);
2540 if (count = 0) then exit;
2542 for i := 0 to High(Shots) do
2543 begin
2544 if Shots[i].ShotType <> 0 then
2545 begin
2546 // Ñèãíàòóðà ñíàðÿäà
2547 utils.writeSign(st, 'SHOT');
2548 utils.writeInt(st, Byte(0)); // version
2549 // Òèï ñíàðÿäà
2550 utils.writeInt(st, Byte(Shots[i].ShotType));
2551 // Öåëü
2552 utils.writeInt(st, Word(Shots[i].Target));
2553 // UID ñòðåëÿâøåãî
2554 utils.writeInt(st, Word(Shots[i].SpawnerUID));
2555 // Ðàçìåð ïîëÿ Triggers
2556 utils.writeInt(st, Integer(Length(Shots[i].Triggers)));
2557 // Òðèããåðû, àêòèâèðîâàííûå âûñòðåëîì
2558 for j := 0 to Length(Shots[i].Triggers)-1 do utils.writeInt(st, LongWord(Shots[i].Triggers[j]));
2559 // Îáúåêò ñíàðÿäà
2560 Obj_SaveState(st, @Shots[i].Obj);
2561 // Êîñòûëèíà åáàíàÿ
2562 utils.writeInt(st, Byte(Shots[i].Stopped));
2563 end;
2564 end;
2565 end;
2567 procedure g_Weapon_LoadState (st: TStream);
2568 var
2569 count, tc, i, j: Integer;
2570 begin
2571 if (st = nil) then exit;
2573 // Êîëè÷åñòâî ñíàðÿäîâ
2574 count := utils.readLongInt(st);
2575 if (count < 0) or (count > 1024*1024) then raise XStreamError.Create('invalid shots counter');
2577 SetLength(Shots, count);
2579 if (count = 0) then exit;
2581 for i := 0 to count-1 do
2582 begin
2583 // Ñèãíàòóðà ñíàðÿäà
2584 if not utils.checkSign(st, 'SHOT') then raise XStreamError.Create('invalid shot signature');
2585 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid shot version');
2586 // Òèï ñíàðÿäà:
2587 Shots[i].ShotType := utils.readByte(st);
2588 // Öåëü
2589 Shots[i].Target := utils.readWord(st);
2590 // UID ñòðåëÿâøåãî
2591 Shots[i].SpawnerUID := utils.readWord(st);
2592 // Ðàçìåð ïîëÿ Triggers
2593 tc := utils.readLongInt(st);
2594 if (tc < 0) or (tc > 1024*1024) then raise XStreamError.Create('invalid shot triggers counter');
2595 SetLength(Shots[i].Triggers, tc);
2596 // Òðèããåðû, àêòèâèðîâàííûå âûñòðåëîì
2597 for j := 0 to tc-1 do Shots[i].Triggers[j] := utils.readLongWord(st);
2598 // Îáúåêò ïðåäìåòà
2599 Obj_LoadState(@Shots[i].Obj, st);
2600 // Êîñòûëèíà åáàíàÿ
2601 Shots[i].Stopped := utils.readByte(st);
2603 // Óñòàíîâêà òåêñòóðû èëè àíèìàöèè
2604 Shots[i].Animation := nil;
2606 case Shots[i].ShotType of
2607 WEAPON_ROCKETLAUNCHER, WEAPON_SKEL_FIRE:
2608 begin
2609 end;
2610 WEAPON_PLASMA:
2611 begin
2612 Shots[i].Animation := TAnimationState.Create(True, 5, 2); // !!! put values into table
2613 end;
2614 WEAPON_BFG:
2615 begin
2616 Shots[i].Animation := TAnimationState.Create(True, 6, 2); // !!! put values into table
2617 end;
2618 WEAPON_IMP_FIRE:
2619 begin
2620 Shots[i].Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
2621 end;
2622 WEAPON_BSP_FIRE:
2623 begin
2624 Shots[i].Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
2625 end;
2626 WEAPON_CACO_FIRE:
2627 begin
2628 Shots[i].Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
2629 end;
2630 WEAPON_BARON_FIRE:
2631 begin
2632 Shots[i].Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
2633 end;
2634 WEAPON_MANCUB_FIRE:
2635 begin
2636 Shots[i].Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
2637 end;
2638 end;
2639 end;
2640 end;
2642 procedure g_Weapon_DestroyShot(I: Integer; X, Y: Integer; Loud: Boolean = True);
2643 {$IFDEF ENABLE_GFX}
2644 var cx, cy: Integer;
2645 {$ENDIF}
2646 begin
2647 if Shots = nil then
2648 Exit;
2649 if (I > High(Shots)) or (I < 0) then Exit;
2651 with Shots[I] do
2652 begin
2653 if ShotType = 0 then Exit;
2654 Obj.X := X;
2655 Obj.Y := Y;
2656 {$IFDEF ENABLE_GFX}
2657 cx := Obj.X + (Obj.Rect.Width div 2);
2658 cy := Obj.Y + (Obj.Rect.Height div 2);
2659 {$ENDIF}
2661 case ShotType of
2662 WEAPON_ROCKETLAUNCHER, WEAPON_SKEL_FIRE: // Ðàêåòû è ñíàðÿäû Ñêåëåòà
2663 begin
2664 if Loud then
2665 begin
2666 {$IFDEF ENABLE_GFX}
2667 if ShotType = WEAPON_SKEL_FIRE then
2668 g_GFX_QueueEffect(R_GFX_EXPLODE_SKELFIRE, (Obj.X + 32) - 32, (Obj.Y + 8) - 32)
2669 else
2670 g_GFX_QueueEffect(R_GFX_EXPLODE_ROCKET, cx - 64, cy - 64);
2671 {$ENDIF}
2672 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEROCKET', Obj.X, Obj.Y);
2673 end;
2674 end;
2676 WEAPON_PLASMA, WEAPON_BSP_FIRE: // Ïëàçìà, ïëàçìà Àðàõíàòðîíà
2677 begin
2678 if loud then
2679 begin
2680 {$IFDEF ENABLE_GFX}
2681 if ShotType = WEAPON_PLASMA then
2682 g_GFX_QueueEffect(R_GFX_EXPLODE_PLASMA, cx - 16, cy - 16)
2683 else
2684 g_GFX_QueueEffect(R_GFX_EXPLODE_BSPFIRE, cx - 16, cy - 16);
2685 {$ENDIF}
2686 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEPLASMA', Obj.X, Obj.Y);
2687 end;
2688 end;
2690 WEAPON_BFG: // BFG
2691 begin
2692 {$IFDEF ENABLE_GFX}
2693 g_GFX_QueueEffect(R_GFX_EXPLODE_BFG, cx - 64, cy - 64);
2694 {$ENDIF}
2695 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBFG', Obj.X, Obj.Y);
2696 end;
2698 WEAPON_IMP_FIRE, WEAPON_CACO_FIRE, WEAPON_BARON_FIRE: // Âûñòðåëû Áåñà, Êàêîäåìîíà Ðûöàðÿ/Áàðîíà àäà
2699 begin
2700 if loud then
2701 begin
2702 {$IFDEF ENABLE_GFX}
2703 case ShotType of
2704 WEAPON_IMP_FIRE: g_GFX_QueueEffect(R_GFX_EXPLODE_IMPFIRE, cx - 32, cy - 32);
2705 WEAPON_CACO_FIRE: g_GFX_QueueEffect(R_GFX_EXPLODE_CACOFIRE, cx - 32, cy - 32);
2706 WEAPON_BARON_FIRE: g_GFX_QueueEffect(R_GFX_EXPLODE_BARONFIRE, cx - 32, cy - 32);
2707 end;
2708 {$ENDIF}
2709 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBALL', Obj.X, Obj.Y);
2710 end;
2711 end;
2713 WEAPON_MANCUB_FIRE: // Âûñòðåë Ìàíêóáóñà
2714 begin
2715 {$IFDEF ENABLE_GFX}
2716 g_GFX_QueueEffect(R_GFX_EXPLODE_ROCKET, cx - 64, cy - 64);
2717 {$ENDIF}
2718 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBALL', Obj.X, Obj.Y);
2719 end;
2720 end; // case ShotType of...
2722 ShotType := 0;
2723 Animation.Free();
2724 end;
2725 end;
2728 procedure g_Weapon_AddDynLights();
2729 var
2730 i: Integer;
2731 begin
2732 if Shots = nil then Exit;
2733 for i := 0 to High(Shots) do
2734 begin
2735 if Shots[i].ShotType = 0 then continue;
2736 if (Shots[i].ShotType = WEAPON_ROCKETLAUNCHER) or
2737 (Shots[i].ShotType = WEAPON_BARON_FIRE) or
2738 (Shots[i].ShotType = WEAPON_MANCUB_FIRE) or
2739 (Shots[i].ShotType = WEAPON_SKEL_FIRE) or
2740 (Shots[i].ShotType = WEAPON_IMP_FIRE) or
2741 (Shots[i].ShotType = WEAPON_CACO_FIRE) or
2742 (Shots[i].ShotType = WEAPON_MANCUB_FIRE) or
2743 (Shots[i].ShotType = WEAPON_BSP_FIRE) or
2744 (Shots[i].ShotType = WEAPON_PLASMA) or
2745 (Shots[i].ShotType = WEAPON_BFG) or
2746 (Shots[i].ShotType = WEAPON_FLAMETHROWER) or
2747 false then
2748 begin
2749 if (Shots[i].ShotType = WEAPON_PLASMA) then
2750 g_AddDynLight(Shots[i].Obj.X+(Shots[i].Obj.Rect.Width div 2), Shots[i].Obj.Y+(Shots[i].Obj.Rect.Height div 2), 128, 0, 0.3, 1, 0.4)
2751 else if (Shots[i].ShotType = WEAPON_BFG) then
2752 g_AddDynLight(Shots[i].Obj.X+(Shots[i].Obj.Rect.Width div 2), Shots[i].Obj.Y+(Shots[i].Obj.Rect.Height div 2), 128, 0, 1, 0, 0.5)
2753 else if (Shots[i].ShotType = WEAPON_FLAMETHROWER) then
2754 g_AddDynLight(Shots[i].Obj.X+(Shots[i].Obj.Rect.Width div 2), Shots[i].Obj.Y+(Shots[i].Obj.Rect.Height div 2), 42, 1, 0.8, 0, 0.4)
2755 else
2756 g_AddDynLight(Shots[i].Obj.X+(Shots[i].Obj.Rect.Width div 2), Shots[i].Obj.Y+(Shots[i].Obj.Rect.Height div 2), 128, 1, 0, 0, 0.4);
2757 end;
2758 end;
2759 end;
2762 procedure TShot.positionChanged (); begin end;
2765 end.