DEADSOFTWARE

render: use TAnimationState for projectiles
[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 Math, g_map, g_player, g_gfx, g_sound, g_panel,
115 g_console, g_options, g_game, r_textures, r_animations,
116 g_triggers, MAPDEF, e_log, g_monsters, g_saveload,
117 g_language, g_netmsg, g_grid,
118 geom, binheap, hashtable, utils, xstreams;
120 type
121 TWaterPanel = record
122 X, Y: Integer;
123 Width, Height: Word;
124 Active: Boolean;
125 end;
127 const
128 SHOT_ROCKETLAUNCHER_WIDTH = 14;
129 SHOT_ROCKETLAUNCHER_HEIGHT = 14;
131 SHOT_SKELFIRE_WIDTH = 14;
132 SHOT_SKELFIRE_HEIGHT = 14;
134 SHOT_PLASMA_WIDTH = 16;
135 SHOT_PLASMA_HEIGHT = 16;
137 SHOT_BFG_WIDTH = 32;
138 SHOT_BFG_HEIGHT = 32;
139 SHOT_BFG_DAMAGE = 100;
140 SHOT_BFG_RADIUS = 256;
142 SHOT_FLAME_WIDTH = 4;
143 SHOT_FLAME_HEIGHT = 4;
144 SHOT_FLAME_LIFETIME = 180;
146 SHOT_SIGNATURE = $544F4853; // 'SHOT'
148 type
149 PHitTime = ^THitTime;
150 THitTime = record
151 distSq: Integer;
152 mon: TMonster;
153 plridx: Integer; // if mon=nil
154 x, y: Integer;
155 end;
157 TBinHeapKeyHitTime = class
158 public
159 class function less (const a, b: Integer): Boolean; inline;
160 end;
162 // indicies in `wgunHitTime` array
163 TBinaryHeapHitTimes = specialize TBinaryHeapBase<Integer, TBinHeapKeyHitTime>;
165 var
166 WaterMap: array of array of DWORD = nil;
167 //wgunMonHash: THashIntInt = nil;
168 wgunHitHeap: TBinaryHeapHitTimes = nil;
169 wgunHitTime: array of THitTime = nil;
170 wgunHitTimeUsed: Integer = 0;
173 class function TBinHeapKeyHitTime.less (const a, b: Integer): Boolean;
174 var
175 hta, htb: PHitTime;
176 begin
177 hta := @wgunHitTime[a];
178 htb := @wgunHitTime[b];
179 if (hta.distSq <> htb.distSq) then begin result := (hta.distSq < htb.distSq); exit; end;
180 if (hta.mon <> nil) then
181 begin
182 // a is monster
183 if (htb.mon = nil) then begin result := false; exit; end; // players first
184 result := (hta.mon.UID < htb.mon.UID); // why not?
185 end
186 else
187 begin
188 // a is player
189 if (htb.mon <> nil) then begin result := true; exit; end; // players first
190 result := (hta.plridx < htb.plridx); // why not?
191 end;
192 end;
195 procedure appendHitTimeMon (adistSq: Integer; amon: TMonster; ax, ay: Integer);
196 begin
197 if (wgunHitTimeUsed = Length(wgunHitTime)) then SetLength(wgunHitTime, wgunHitTimeUsed+128);
198 with wgunHitTime[wgunHitTimeUsed] do
199 begin
200 distSq := adistSq;
201 mon := amon;
202 plridx := -1;
203 x := ax;
204 y := ay;
205 end;
206 wgunHitHeap.insert(wgunHitTimeUsed);
207 Inc(wgunHitTimeUsed);
208 end;
211 procedure appendHitTimePlr (adistSq: Integer; aplridx: Integer; ax, ay: Integer);
212 begin
213 if (wgunHitTimeUsed = Length(wgunHitTime)) then SetLength(wgunHitTime, wgunHitTimeUsed+128);
214 with wgunHitTime[wgunHitTimeUsed] do
215 begin
216 distSq := adistSq;
217 mon := nil;
218 plridx := aplridx;
219 x := ax;
220 y := ay;
221 end;
222 wgunHitHeap.insert(wgunHitTimeUsed);
223 Inc(wgunHitTimeUsed);
224 end;
227 function FindShot(): DWORD;
228 var
229 i: Integer;
230 begin
231 if Shots <> nil then
232 for i := 0 to High(Shots) do
233 if Shots[i].ShotType = 0 then
234 begin
235 Result := i;
236 LastShotID := Result;
237 Exit;
238 end;
240 if Shots = nil then
241 begin
242 SetLength(Shots, 128);
243 Result := 0;
244 end
245 else
246 begin
247 Result := High(Shots) + 1;
248 SetLength(Shots, Length(Shots) + 128);
249 end;
250 LastShotID := Result;
251 end;
253 procedure CreateWaterMap();
254 var
255 WaterArray: Array of TWaterPanel;
256 a, b, c, m: Integer;
257 ok: Boolean;
258 begin
259 if gWater = nil then
260 Exit;
262 SetLength(WaterArray, Length(gWater));
264 for a := 0 to High(gWater) do
265 begin
266 WaterArray[a].X := gWater[a].X;
267 WaterArray[a].Y := gWater[a].Y;
268 WaterArray[a].Width := gWater[a].Width;
269 WaterArray[a].Height := gWater[a].Height;
270 WaterArray[a].Active := True;
271 end;
273 g_Game_SetLoadingText(_lc[I_LOAD_WATER_MAP], High(WaterArray), False);
275 for a := 0 to High(WaterArray) do
276 if WaterArray[a].Active then
277 begin
278 WaterArray[a].Active := False;
279 m := Length(WaterMap);
280 SetLength(WaterMap, m+1);
281 SetLength(WaterMap[m], 1);
282 WaterMap[m][0] := a;
283 ok := True;
285 while ok do
286 begin
287 ok := False;
288 for b := 0 to High(WaterArray) do
289 if WaterArray[b].Active then
290 for c := 0 to High(WaterMap[m]) do
291 if g_CollideAround(WaterArray[b].X,
292 WaterArray[b].Y,
293 WaterArray[b].Width,
294 WaterArray[b].Height,
295 WaterArray[WaterMap[m][c]].X,
296 WaterArray[WaterMap[m][c]].Y,
297 WaterArray[WaterMap[m][c]].Width,
298 WaterArray[WaterMap[m][c]].Height) then
299 begin
300 WaterArray[b].Active := False;
301 SetLength(WaterMap[m],
302 Length(WaterMap[m])+1);
303 WaterMap[m][High(WaterMap[m])] := b;
304 ok := True;
305 Break;
306 end;
307 end;
309 g_Game_StepLoading();
310 end;
312 WaterArray := nil;
313 end;
316 var
317 chkTrap_pl: array [0..256] of Integer;
318 chkTrap_mn: array [0..65535] of TMonster;
320 procedure CheckTrap(ID: DWORD; dm: Integer; t: Byte);
321 var
322 //a, b, c, d, i1, i2: Integer;
323 //chkTrap_pl, chkTrap_mn: WArray;
324 plaCount: Integer = 0;
325 mnaCount: Integer = 0;
326 frameId: DWord;
329 function monsWaterCheck (mon: TMonster): Boolean;
330 begin
331 result := false; // don't stop
332 if mon.alive and mon.Collide(gWater[WaterMap[a][c]]) and (not InWArray(monidx, chkTrap_mn)) and (i2 < 1023) then //FIXME
333 begin
334 i2 += 1;
335 chkTrap_mn[i2] := monidx;
336 end;
337 end;
340 function monsWaterCheck (mon: TMonster): Boolean;
341 begin
342 result := false; // don't stop
343 if (mon.trapCheckFrameId <> frameId) then
344 begin
345 mon.trapCheckFrameId := frameId;
346 chkTrap_mn[mnaCount] := mon;
347 Inc(mnaCount);
348 end;
349 end;
351 var
352 a, b, c, d, f: Integer;
353 pan: TPanel;
354 begin
355 if (gWater = nil) or (WaterMap = nil) then Exit;
357 frameId := g_Mons_getNewTrapFrameId();
359 //i1 := -1;
360 //i2 := -1;
362 //SetLength(chkTrap_pl, 1024);
363 //SetLength(chkTrap_mn, 1024);
364 //for d := 0 to 1023 do chkTrap_pl[d] := $FFFF;
365 //for d := 0 to 1023 do chkTrap_mn[d] := $FFFF;
367 for a := 0 to High(WaterMap) do
368 begin
369 for b := 0 to High(WaterMap[a]) do
370 begin
371 pan := gWater[WaterMap[a][b]];
372 if not g_Obj_Collide(pan.X, pan.Y, pan.Width, pan.Height, @Shots[ID].Obj) then continue;
374 for c := 0 to High(WaterMap[a]) do
375 begin
376 pan := gWater[WaterMap[a][c]];
377 for d := 0 to High(gPlayers) do
378 begin
379 if (gPlayers[d] <> nil) and (gPlayers[d].alive) then
380 begin
381 if gPlayers[d].Collide(pan) then
382 begin
383 f := 0;
384 while (f < plaCount) and (chkTrap_pl[f] <> d) do Inc(f);
385 if (f = plaCount) then
386 begin
387 chkTrap_pl[plaCount] := d;
388 Inc(plaCount);
389 if (plaCount = Length(chkTrap_pl)) then break;
390 end;
391 end;
392 end;
393 end;
395 //g_Mons_ForEach(monsWaterCheck);
396 g_Mons_ForEachAliveAt(pan.X, pan.Y, pan.Width, pan.Height, monsWaterCheck);
397 end;
399 for f := 0 to plaCount-1 do gPlayers[chkTrap_pl[f]].Damage(dm, Shots[ID].SpawnerUID, 0, 0, t);
400 for f := 0 to mnaCount-1 do chkTrap_mn[f].Damage(dm, 0, 0, Shots[ID].SpawnerUID, t);
401 end;
402 end;
404 //chkTrap_pl := nil;
405 //chkTrap_mn := nil;
406 end;
408 function HitMonster(m: TMonster; d: Integer; vx, vy: Integer; SpawnerUID: Word; t: Byte): Boolean;
409 var
410 tt, mt: Byte;
411 mon: TMonster;
412 begin
413 Result := False;
415 tt := g_GetUIDType(SpawnerUID);
416 if tt = UID_MONSTER then
417 begin
418 mon := g_Monsters_ByUID(SpawnerUID);
419 if mon <> nil then
420 mt := g_Monsters_ByUID(SpawnerUID).MonsterType
421 else
422 mt := 0;
423 end
424 else
425 mt := 0;
427 if m = nil then Exit;
428 if m.UID = SpawnerUID then
429 begin
430 // Ñàì ñåáÿ ìîæåò ðàíèòü òîëüêî ðàêåòîé è òîêîì:
431 if (t <> HIT_ROCKET) and (t <> HIT_ELECTRO) then
432 Exit;
433 // Êèáåð äåìîí è áî÷êà âîîáùå íå ìîãóò ñåáÿ ðàíèòü:
434 if (m.MonsterType = MONSTER_CYBER) or
435 (m.MonsterType = MONSTER_BARREL) then
436 begin
437 Result := True;
438 Exit;
439 end;
440 end;
442 if tt = UID_MONSTER then
443 begin
444 // Lost_Soul íå ìîæåò ðàíèòü Pain_Elemental'à:
445 if (mt = MONSTER_SOUL) and (m.MonsterType = MONSTER_PAIN) then
446 Exit;
448 // Îáà ìîíñòðà îäíîãî âèäà:
449 if mt = m.MonsterType then
450 case mt of
451 MONSTER_IMP, MONSTER_DEMON, MONSTER_BARON, MONSTER_KNIGHT, MONSTER_CACO,
452 MONSTER_SOUL, MONSTER_MANCUB, MONSTER_SKEL, MONSTER_FISH:
453 Exit; // Ýòè íå áüþò ñâîèõ
454 end;
455 end;
457 if g_Game_IsServer then
458 begin
459 if (t <> HIT_FLAME) or (m.FFireTime = 0) or (vx <> 0) or (vy <> 0) then
460 Result := m.Damage(d, vx, vy, SpawnerUID, t)
461 else
462 Result := (gLMSRespawn = LMS_RESPAWN_NONE); // don't hit monsters when it's warmup time
463 if t = HIT_FLAME then
464 m.CatchFire(SpawnerUID);
465 end
466 else
467 Result := (gLMSRespawn = LMS_RESPAWN_NONE); // don't hit monsters when it's warmup time
468 end;
471 function HitPlayer (p: TPlayer; d: Integer; vx, vy: Integer; SpawnerUID: Word; t: Byte): Boolean;
472 begin
473 result := False;
475 // Ñàì ñåáÿ ìîæåò ðàíèòü òîëüêî ðàêåòîé è òîêîì
476 if (p.UID = SpawnerUID) and (t <> HIT_ROCKET) and (t <> HIT_ELECTRO) then exit;
478 if g_Game_IsServer then
479 begin
480 if (t <> HIT_FLAME) or (p.FFireTime = 0) or (vx <> 0) or (vy <> 0) then p.Damage(d, SpawnerUID, vx, vy, t);
481 if (t = HIT_FLAME) then p.CatchFire(SpawnerUID);
482 end;
484 result := true;
485 end;
488 procedure g_Weapon_BFG9000(X, Y: Integer; SpawnerUID: Word);
490 function monsCheck (mon: TMonster): Boolean;
491 begin
492 result := false; // don't stop
493 if (mon.alive) and (mon.UID <> SpawnerUID) then
494 begin
495 with mon do
496 begin
497 if (g_PatchLength(X, Y, Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
498 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)) <= SHOT_BFG_RADIUS) and
499 g_TraceVector(X, Y, Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
500 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)) then
501 begin
502 if HitMonster(mon, 50, 0, 0, SpawnerUID, HIT_SOME) then mon.BFGHit();
503 end;
504 end;
505 end;
506 end;
508 var
509 i, h: Integer;
510 st: Byte;
511 pl: TPlayer;
512 b: Boolean;
513 begin
514 //g_Sound_PlayEx('SOUND_WEAPON_EXPLODEBFG', 255);
516 h := High(gCorpses);
518 if gAdvCorpses and (h <> -1) then
519 for i := 0 to h do
520 if (gCorpses[i] <> nil) and (gCorpses[i].State <> CORPSE_STATE_REMOVEME) then
521 with gCorpses[i] do
522 if (g_PatchLength(X, Y, Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
523 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)) <= SHOT_BFG_RADIUS) and
524 g_TraceVector(X, Y, Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
525 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)) then
526 begin
527 Damage(50, SpawnerUID, 0, 0);
528 g_Weapon_BFGHit(Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
529 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2));
530 end;
532 st := TEAM_NONE;
533 pl := g_Player_Get(SpawnerUID);
534 if pl <> nil then
535 st := pl.Team;
537 h := High(gPlayers);
539 if h <> -1 then
540 for i := 0 to h do
541 if (gPlayers[i] <> nil) and (gPlayers[i].alive) and (gPlayers[i].UID <> SpawnerUID) then
542 with gPlayers[i] do
543 if (g_PatchLength(X, Y, GameX+PLAYER_RECT.X+(PLAYER_RECT.Width div 2),
544 GameY+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)) <= SHOT_BFG_RADIUS) and
545 g_TraceVector(X, Y, GameX+PLAYER_RECT.X+(PLAYER_RECT.Width div 2),
546 GameY+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)) then
547 begin
548 if (st = TEAM_NONE) or (st <> gPlayers[i].Team) then
549 b := HitPlayer(gPlayers[i], 50, 0, 0, SpawnerUID, HIT_SOME)
550 else
551 b := HitPlayer(gPlayers[i], 25, 0, 0, SpawnerUID, HIT_SOME);
552 if b then
553 gPlayers[i].BFGHit();
554 end;
556 //FIXME
557 g_Mons_ForEachAlive(monsCheck);
558 end;
560 function g_Weapon_CreateShot(I: Integer; ShotType: Byte; Spawner, TargetUID: Word; X, Y, XV, YV: Integer): LongWord;
561 var
562 find_id: DWord;
563 begin
564 if I < 0 then
565 find_id := FindShot()
566 else
567 begin
568 find_id := I;
569 if Integer(find_id) >= High(Shots) then
570 SetLength(Shots, find_id + 64)
571 end;
573 case ShotType of
574 WEAPON_ROCKETLAUNCHER:
575 begin
576 with Shots[find_id] do
577 begin
578 g_Obj_Init(@Obj);
580 Obj.Rect.Width := SHOT_ROCKETLAUNCHER_WIDTH;
581 Obj.Rect.Height := SHOT_ROCKETLAUNCHER_HEIGHT;
583 Animation := nil;
584 Triggers := nil;
585 ShotType := WEAPON_ROCKETLAUNCHER;
586 end;
587 end;
589 WEAPON_PLASMA:
590 begin
591 with Shots[find_id] do
592 begin
593 g_Obj_Init(@Obj);
595 Obj.Rect.Width := SHOT_PLASMA_WIDTH;
596 Obj.Rect.Height := SHOT_PLASMA_HEIGHT;
598 Triggers := nil;
599 ShotType := WEAPON_PLASMA;
600 Animation := TAnimationState.Create(True, 5, 2); // !!! put values into table
601 end;
602 end;
604 WEAPON_BFG:
605 begin
606 with Shots[find_id] do
607 begin
608 g_Obj_Init(@Obj);
610 Obj.Rect.Width := SHOT_BFG_WIDTH;
611 Obj.Rect.Height := SHOT_BFG_HEIGHT;
613 Triggers := nil;
614 ShotType := WEAPON_BFG;
615 Animation := TAnimationState.Create(True, 6, 2); // !!! put values into table
616 end;
617 end;
619 WEAPON_FLAMETHROWER:
620 begin
621 with Shots[find_id] do
622 begin
623 g_Obj_Init(@Obj);
625 Obj.Rect.Width := SHOT_FLAME_WIDTH;
626 Obj.Rect.Height := SHOT_FLAME_HEIGHT;
628 Triggers := nil;
629 ShotType := WEAPON_FLAMETHROWER;
630 // Animation := TAnimationState.Create(True, 6, 0); // drawed as gfx
631 end;
632 end;
634 WEAPON_IMP_FIRE:
635 begin
636 with Shots[find_id] do
637 begin
638 g_Obj_Init(@Obj);
640 Obj.Rect.Width := 16;
641 Obj.Rect.Height := 16;
643 Triggers := nil;
644 ShotType := WEAPON_IMP_FIRE;
645 Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
646 end;
647 end;
649 WEAPON_CACO_FIRE:
650 begin
651 with Shots[find_id] do
652 begin
653 g_Obj_Init(@Obj);
655 Obj.Rect.Width := 16;
656 Obj.Rect.Height := 16;
658 Triggers := nil;
659 ShotType := WEAPON_CACO_FIRE;
660 Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
661 end;
662 end;
664 WEAPON_MANCUB_FIRE:
665 begin
666 with Shots[find_id] do
667 begin
668 g_Obj_Init(@Obj);
670 Obj.Rect.Width := 32;
671 Obj.Rect.Height := 32;
673 Triggers := nil;
674 ShotType := WEAPON_MANCUB_FIRE;
675 Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
676 end;
677 end;
679 WEAPON_BARON_FIRE:
680 begin
681 with Shots[find_id] do
682 begin
683 g_Obj_Init(@Obj);
685 Obj.Rect.Width := 16;
686 Obj.Rect.Height := 16;
688 Triggers := nil;
689 ShotType := WEAPON_BARON_FIRE;
690 Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
691 end;
692 end;
694 WEAPON_BSP_FIRE:
695 begin
696 with Shots[find_id] do
697 begin
698 g_Obj_Init(@Obj);
700 Obj.Rect.Width := 16;
701 Obj.Rect.Height := 16;
703 Triggers := nil;
704 ShotType := WEAPON_BSP_FIRE;
705 Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
706 end;
707 end;
709 WEAPON_SKEL_FIRE:
710 begin
711 with Shots[find_id] do
712 begin
713 g_Obj_Init(@Obj);
715 Obj.Rect.Width := SHOT_SKELFIRE_WIDTH;
716 Obj.Rect.Height := SHOT_SKELFIRE_HEIGHT;
718 Triggers := nil;
719 ShotType := WEAPON_SKEL_FIRE;
720 target := TargetUID;
721 Animation := TAnimationState.Create(True, 5, 2); // !!! put values into table
722 end;
723 end;
724 end;
726 Shots[find_id].Obj.oldX := X;
727 Shots[find_id].Obj.oldY := Y;
728 Shots[find_id].Obj.X := X;
729 Shots[find_id].Obj.Y := Y;
730 Shots[find_id].Obj.Vel.X := XV;
731 Shots[find_id].Obj.Vel.Y := YV;
732 Shots[find_id].Obj.Accel.X := 0;
733 Shots[find_id].Obj.Accel.Y := 0;
734 Shots[find_id].SpawnerUID := Spawner;
735 if (ShotType = WEAPON_FLAMETHROWER) and (XV = 0) and (YV = 0) then
736 Shots[find_id].Stopped := 255
737 else
738 Shots[find_id].Stopped := 0;
739 Result := find_id;
740 end;
742 procedure throw(i, x, y, xd, yd, s: Integer);
743 var
744 a: Integer;
745 begin
746 yd := yd - y;
747 xd := xd - x;
749 a := Max(Abs(xd), Abs(yd));
750 if a = 0 then
751 a := 1;
753 Shots[i].Obj.oldX := x;
754 Shots[i].Obj.oldY := y;
755 Shots[i].Obj.X := x;
756 Shots[i].Obj.Y := y;
757 Shots[i].Obj.Vel.X := (xd*s) div a;
758 Shots[i].Obj.Vel.Y := (yd*s) div a;
759 Shots[i].Obj.Accel.X := 0;
760 Shots[i].Obj.Accel.Y := 0;
761 Shots[i].Stopped := 0;
762 if Shots[i].ShotType in [WEAPON_ROCKETLAUNCHER, WEAPON_BFG] then
763 Shots[i].Timeout := 900 // ~25 sec
764 else
765 begin
766 if Shots[i].ShotType = WEAPON_FLAMETHROWER then
767 Shots[i].Timeout := SHOT_FLAME_LIFETIME
768 else
769 Shots[i].Timeout := 550; // ~15 sec
770 end;
771 end;
773 function g_Weapon_Hit(obj: PObj; d: Integer; SpawnerUID: Word; t: Byte; HitCorpses: Boolean = True): Byte;
774 var
775 i, h: Integer;
777 function PlayerHit(Team: Byte = 0): Boolean;
778 var
779 i: Integer;
780 ChkTeam: Boolean;
781 p: TPlayer;
782 begin
783 Result := False;
784 h := High(gPlayers);
786 if h <> -1 then
787 for i := 0 to h do
788 if (gPlayers[i] <> nil) and gPlayers[i].alive and g_Obj_Collide(obj, @gPlayers[i].Obj) then
789 begin
790 ChkTeam := True;
791 if (Team > 0) and (g_GetUIDType(SpawnerUID) = UID_PLAYER) then
792 begin
793 p := g_Player_Get(SpawnerUID);
794 if p <> nil then
795 ChkTeam := (p.Team = gPlayers[i].Team) xor (Team = 2);
796 end;
797 if ChkTeam then
798 if HitPlayer(gPlayers[i], d, obj^.Vel.X, obj^.Vel.Y, SpawnerUID, t) then
799 begin
800 if t <> HIT_FLAME then
801 gPlayers[i].Push((obj^.Vel.X+obj^.Accel.X)*IfThen(t = HIT_BFG, 8, 1) div 4,
802 (obj^.Vel.Y+obj^.Accel.Y)*IfThen(t = HIT_BFG, 8, 1) div 4);
803 if t = HIT_BFG then
804 g_Game_DelayEvent(DE_BFGHIT, 1000, SpawnerUID);
805 Result := True;
806 break;
807 end;
808 end;
809 end;
812 function monsCheckHit (monidx: Integer; mon: TMonster): Boolean;
813 begin
814 result := false; // don't stop
815 if mon.alive and g_Obj_Collide(obj, @mon.Obj) then
816 begin
817 if HitMonster(mon, d, obj^.Vel.X, obj^.Vel.Y, SpawnerUID, t) then
818 begin
819 if (t <> HIT_FLAME) then
820 begin
821 mon.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 end;
824 result := True;
825 end;
826 end;
827 end;
830 function monsCheckHit (mon: TMonster): Boolean;
831 begin
832 result := false; // don't stop
833 if HitMonster(mon, d, obj.Vel.X, obj.Vel.Y, SpawnerUID, t) then
834 begin
835 if (t <> HIT_FLAME) then
836 begin
837 mon.Push((obj.Vel.X+obj.Accel.X)*IfThen(t = HIT_BFG, 8, 1) div 4,
838 (obj.Vel.Y+obj.Accel.Y)*IfThen(t = HIT_BFG, 8, 1) div 4);
839 end;
840 result := true;
841 end;
842 end;
844 function MonsterHit(): Boolean;
845 begin
846 //result := g_Mons_ForEach(monsCheckHit);
847 //FIXME: accelerate this!
848 result := g_Mons_ForEachAliveAt(obj.X+obj.Rect.X, obj.Y+obj.Rect.Y, obj.Rect.Width, obj.Rect.Height, monsCheckHit);
849 end;
851 begin
852 Result := 0;
854 if HitCorpses then
855 begin
856 h := High(gCorpses);
858 if gAdvCorpses and (h <> -1) then
859 for i := 0 to h do
860 if (gCorpses[i] <> nil) and (gCorpses[i].State <> CORPSE_STATE_REMOVEME) and
861 g_Obj_Collide(obj, @gCorpses[i].Obj) then
862 begin
863 // Ðàñïèëèâàåì òðóï:
864 gCorpses[i].Damage(d, SpawnerUID, (obj^.Vel.X+obj^.Accel.X) div 4,
865 (obj^.Vel.Y+obj^.Accel.Y) div 4);
866 Result := 1;
867 end;
868 end;
870 case gGameSettings.GameMode of
871 // Êàìïàíèÿ:
872 GM_COOP, GM_SINGLE:
873 begin
874 // Ñíà÷àëà áü¸ì ìîíñòðîâ, åñëè åñòü, ïîòîì ïûòàåìñÿ áèòü èãðîêîâ
875 if MonsterHit() then
876 begin
877 Result := 2;
878 Exit;
879 end;
881 // È â êîíöå èãðîêîâ, íî òîëüêî åñëè ïîëîæåíî
882 // (èëè ñíàðÿä îò ìîíñòðà, èëè friendlyfire, èëè friendly_hit_projectile)
883 if (g_GetUIDType(SpawnerUID) <> UID_PLAYER) or
884 LongBool(gGameSettings.Options and (GAME_OPTION_TEAMDAMAGE or GAME_OPTION_TEAMHITPROJECTILE)) then
885 begin
886 if PlayerHit() then
887 begin
888 Result := 1;
889 Exit;
890 end;
891 end;
892 end;
894 // Äåçìàò÷:
895 GM_DM:
896 begin
897 // Ñíà÷àëà áü¸ì èãðîêîâ, åñëè åñòü, ïîòîì ïûòàåìñÿ áèòü ìîíñòðîâ
898 if PlayerHit() then
899 begin
900 Result := 1;
901 Exit;
902 end;
904 if MonsterHit() then
905 begin
906 Result := 2;
907 Exit;
908 end;
909 end;
911 // Êîìàíäíûå:
912 GM_TDM, GM_CTF:
913 begin
914 // Ñíà÷àëà áü¸ì èãðîêîâ êîìàíäû ñîïåðíèêà
915 if PlayerHit(2) then
916 begin
917 Result := 1;
918 Exit;
919 end;
921 // Ïîòîì ìîíñòðîâ
922 if MonsterHit() then
923 begin
924 Result := 2;
925 Exit;
926 end;
928 // È â êîíöå ñâîèõ èãðîêîâ, íî òîëüêî åñëè ïîëîæåíî
929 // (èëè friendlyfire, èëè friendly_hit_projectile)
930 if LongBool(gGameSettings.Options and (GAME_OPTION_TEAMDAMAGE or GAME_OPTION_TEAMHITPROJECTILE)) then
931 begin
932 if PlayerHit(1) then
933 begin
934 Result := 1;
935 Exit;
936 end;
937 end;
938 end;
940 end;
941 end;
943 function g_Weapon_HitUID(UID: Word; d: Integer; SpawnerUID: Word; t: Byte): Boolean;
944 begin
945 Result := False;
947 case g_GetUIDType(UID) of
948 UID_PLAYER: Result := HitPlayer(g_Player_Get(UID), d, 0, 0, SpawnerUID, t);
949 UID_MONSTER: Result := HitMonster(g_Monsters_ByUID(UID), d, 0, 0, SpawnerUID, t);
950 else Exit;
951 end;
952 end;
954 function g_Weapon_Explode(X, Y: Integer; rad: Integer; SpawnerUID: Word): Boolean;
955 var
956 r: Integer; // squared radius
958 function monsExCheck (mon: TMonster): Boolean;
959 var
960 dx, dy, mm: Integer;
961 begin
962 result := false; // don't stop
963 begin
964 dx := mon.Obj.X+mon.Obj.Rect.X+(mon.Obj.Rect.Width div 2)-X;
965 dy := mon.Obj.Y+mon.Obj.Rect.Y+(mon.Obj.Rect.Height div 2)-Y;
967 if dx > 1000 then dx := 1000;
968 if dy > 1000 then dy := 1000;
970 if (dx*dx+dy*dy < r) then
971 begin
972 //m := PointToRect(X, Y, Obj.X+Obj.Rect.X, Obj.Y+Obj.Rect.Y, Obj.Rect.Width, Obj.Rect.Height);
973 //e_WriteLog(Format('explo monster #%d: x=%d; y=%d; rad=%d; dx=%d; dy=%d', [monidx, X, Y, rad, dx, dy]), MSG_NOTIFY);
975 mm := Max(abs(dx), abs(dy));
976 if mm = 0 then mm := 1;
978 if mon.alive then
979 begin
980 HitMonster(mon, ((mon.Obj.Rect.Width div 4)*10*(rad-mm)) div rad, 0, 0, SpawnerUID, HIT_ROCKET);
981 end;
983 mon.Push((dx*7) div mm, (dy*7) div mm);
984 end;
985 end;
986 end;
988 var
989 i, h, dx, dy, m, mm: Integer;
990 _angle: SmallInt;
991 begin
992 result := false;
994 g_Triggers_PressC(X, Y, rad, SpawnerUID, ACTIVATE_SHOT);
996 r := rad*rad;
998 h := High(gPlayers);
1000 if h <> -1 then
1001 for i := 0 to h do
1002 if (gPlayers[i] <> nil) and gPlayers[i].alive then
1003 with gPlayers[i] do
1004 begin
1005 dx := Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2)-X;
1006 dy := Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)-Y;
1008 if dx > 1000 then dx := 1000;
1009 if dy > 1000 then dy := 1000;
1011 if dx*dx+dy*dy < r then
1012 begin
1013 //m := PointToRect(X, Y, GameX+PLAYER_RECT.X, GameY+PLAYER_RECT.Y,
1014 // PLAYER_RECT.Width, PLAYER_RECT.Height);
1016 mm := Max(abs(dx), abs(dy));
1017 if mm = 0 then mm := 1;
1019 HitPlayer(gPlayers[i], (100*(rad-mm)) div rad, (dx*10) div mm, (dy*10) div mm, SpawnerUID, HIT_ROCKET);
1020 gPlayers[i].Push((dx*7) div mm, (dy*7) div mm);
1021 end;
1022 end;
1024 //g_Mons_ForEach(monsExCheck);
1025 g_Mons_ForEachAt(X-(rad+32), Y-(rad+32), (rad+32)*2, (rad+32)*2, monsExCheck);
1027 h := High(gCorpses);
1029 if gAdvCorpses and (h <> -1) then
1030 for i := 0 to h do
1031 if (gCorpses[i] <> nil) and (gCorpses[i].State <> CORPSE_STATE_REMOVEME) then
1032 with gCorpses[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, Obj.X+Obj.Rect.X, Obj.Y+Obj.Rect.Y,
1043 Obj.Rect.Width, Obj.Rect.Height);
1045 mm := Max(abs(dx), abs(dy));
1046 if mm = 0 then mm := 1;
1048 Damage(Round(100*(rad-m)/rad), SpawnerUID, (dx*10) div mm, (dy*10) div mm);
1049 end;
1050 end;
1052 h := High(gGibs);
1054 if gAdvGibs and (h <> -1) then
1055 for i := 0 to h do
1056 if gGibs[i].alive then
1057 with gGibs[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;
1062 if dx > 1000 then dx := 1000;
1063 if dy > 1000 then dy := 1000;
1065 if dx*dx+dy*dy < r then
1066 begin
1067 m := PointToRect(X, Y, Obj.X+Obj.Rect.X, Obj.Y+Obj.Rect.Y,
1068 Obj.Rect.Width, Obj.Rect.Height);
1069 _angle := GetAngle(Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
1070 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2), X, Y);
1072 g_Obj_PushA(@Obj, Round(15*(rad-m)/rad), _angle);
1073 positionChanged(); // this updates spatial accelerators
1074 end;
1075 end;
1076 end;
1078 procedure g_Weapon_Init();
1079 begin
1080 CreateWaterMap();
1081 end;
1083 procedure g_Weapon_Free();
1084 var
1085 i: Integer;
1086 begin
1087 if Shots <> nil then
1088 begin
1089 for i := 0 to High(Shots) do
1090 if Shots[i].ShotType <> 0 then
1091 Shots[i].Animation.Free();
1093 Shots := nil;
1094 end;
1096 WaterMap := nil;
1097 end;
1099 procedure g_Weapon_LoadData();
1100 begin
1101 e_WriteLog('Loading weapons data...', TMsgType.Notify);
1103 g_Sound_CreateWADEx('SOUND_WEAPON_HITPUNCH', GameWAD+':SOUNDS\HITPUNCH');
1104 g_Sound_CreateWADEx('SOUND_WEAPON_MISSPUNCH', GameWAD+':SOUNDS\MISSPUNCH');
1105 g_Sound_CreateWADEx('SOUND_WEAPON_HITBERSERK', GameWAD+':SOUNDS\HITBERSERK');
1106 g_Sound_CreateWADEx('SOUND_WEAPON_MISSBERSERK', GameWAD+':SOUNDS\MISSBERSERK');
1107 g_Sound_CreateWADEx('SOUND_WEAPON_SELECTSAW', GameWAD+':SOUNDS\SELECTSAW');
1108 g_Sound_CreateWADEx('SOUND_WEAPON_IDLESAW', GameWAD+':SOUNDS\IDLESAW');
1109 g_Sound_CreateWADEx('SOUND_WEAPON_HITSAW', GameWAD+':SOUNDS\HITSAW');
1110 g_Sound_CreateWADEx('SOUND_WEAPON_FIRESHOTGUN2', GameWAD+':SOUNDS\FIRESHOTGUN2');
1111 g_Sound_CreateWADEx('SOUND_WEAPON_FIRESHOTGUN', GameWAD+':SOUNDS\FIRESHOTGUN');
1112 g_Sound_CreateWADEx('SOUND_WEAPON_FIRESAW', GameWAD+':SOUNDS\FIRESAW');
1113 g_Sound_CreateWADEx('SOUND_WEAPON_FIREROCKET', GameWAD+':SOUNDS\FIREROCKET');
1114 g_Sound_CreateWADEx('SOUND_WEAPON_FIREPLASMA', GameWAD+':SOUNDS\FIREPLASMA');
1115 g_Sound_CreateWADEx('SOUND_WEAPON_FIREPISTOL', GameWAD+':SOUNDS\FIREPISTOL');
1116 g_Sound_CreateWADEx('SOUND_WEAPON_FIRECGUN', GameWAD+':SOUNDS\FIRECGUN');
1117 g_Sound_CreateWADEx('SOUND_WEAPON_FIREBFG', GameWAD+':SOUNDS\FIREBFG');
1118 g_Sound_CreateWADEx('SOUND_FIRE', GameWAD+':SOUNDS\FIRE');
1119 g_Sound_CreateWADEx('SOUND_IGNITE', GameWAD+':SOUNDS\IGNITE');
1120 g_Sound_CreateWADEx('SOUND_WEAPON_STARTFIREBFG', GameWAD+':SOUNDS\STARTFIREBFG');
1121 g_Sound_CreateWADEx('SOUND_WEAPON_EXPLODEROCKET', GameWAD+':SOUNDS\EXPLODEROCKET');
1122 g_Sound_CreateWADEx('SOUND_WEAPON_EXPLODEBFG', GameWAD+':SOUNDS\EXPLODEBFG');
1123 g_Sound_CreateWADEx('SOUND_WEAPON_BFGWATER', GameWAD+':SOUNDS\BFGWATER');
1124 g_Sound_CreateWADEx('SOUND_WEAPON_EXPLODEPLASMA', GameWAD+':SOUNDS\EXPLODEPLASMA');
1125 g_Sound_CreateWADEx('SOUND_WEAPON_PLASMAWATER', GameWAD+':SOUNDS\PLASMAWATER');
1126 g_Sound_CreateWADEx('SOUND_WEAPON_FIREBALL', GameWAD+':SOUNDS\FIREBALL');
1127 g_Sound_CreateWADEx('SOUND_WEAPON_EXPLODEBALL', GameWAD+':SOUNDS\EXPLODEBALL');
1128 g_Sound_CreateWADEx('SOUND_WEAPON_FIREREV', GameWAD+':SOUNDS\FIREREV');
1129 g_Sound_CreateWADEx('SOUND_WEAPON_FLAMEON', GameWAD+':SOUNDS\STARTFLM');
1130 g_Sound_CreateWADEx('SOUND_WEAPON_FLAMEOFF', GameWAD+':SOUNDS\STOPFLM');
1131 g_Sound_CreateWADEx('SOUND_WEAPON_FLAMEWORK', GameWAD+':SOUNDS\WORKFLM');
1132 g_Sound_CreateWADEx('SOUND_PLAYER_JETFLY', GameWAD+':SOUNDS\WORKJETPACK');
1133 g_Sound_CreateWADEx('SOUND_PLAYER_JETON', GameWAD+':SOUNDS\STARTJETPACK');
1134 g_Sound_CreateWADEx('SOUND_PLAYER_JETOFF', GameWAD+':SOUNDS\STOPJETPACK');
1135 g_Sound_CreateWADEx('SOUND_PLAYER_CASING1', GameWAD+':SOUNDS\CASING1');
1136 g_Sound_CreateWADEx('SOUND_PLAYER_CASING2', GameWAD+':SOUNDS\CASING2');
1137 g_Sound_CreateWADEx('SOUND_PLAYER_SHELL1', GameWAD+':SOUNDS\SHELL1');
1138 g_Sound_CreateWADEx('SOUND_PLAYER_SHELL2', GameWAD+':SOUNDS\SHELL2');
1140 g_Texture_CreateWADEx('TEXTURE_SHELL_BULLET', GameWAD+':TEXTURES\EBULLET');
1141 g_Texture_CreateWADEx('TEXTURE_SHELL_SHELL', GameWAD+':TEXTURES\ESHELL');
1143 //wgunMonHash := hashNewIntInt();
1144 wgunHitHeap := TBinaryHeapHitTimes.Create();
1145 end;
1147 procedure g_Weapon_FreeData();
1148 begin
1149 e_WriteLog('Releasing weapons data...', TMsgType.Notify);
1151 g_Sound_Delete('SOUND_WEAPON_HITPUNCH');
1152 g_Sound_Delete('SOUND_WEAPON_MISSPUNCH');
1153 g_Sound_Delete('SOUND_WEAPON_HITBERSERK');
1154 g_Sound_Delete('SOUND_WEAPON_MISSBERSERK');
1155 g_Sound_Delete('SOUND_WEAPON_SELECTSAW');
1156 g_Sound_Delete('SOUND_WEAPON_IDLESAW');
1157 g_Sound_Delete('SOUND_WEAPON_HITSAW');
1158 g_Sound_Delete('SOUND_WEAPON_FIRESHOTGUN2');
1159 g_Sound_Delete('SOUND_WEAPON_FIRESHOTGUN');
1160 g_Sound_Delete('SOUND_WEAPON_FIRESAW');
1161 g_Sound_Delete('SOUND_WEAPON_FIREROCKET');
1162 g_Sound_Delete('SOUND_WEAPON_FIREPLASMA');
1163 g_Sound_Delete('SOUND_WEAPON_FIREPISTOL');
1164 g_Sound_Delete('SOUND_WEAPON_FIRECGUN');
1165 g_Sound_Delete('SOUND_WEAPON_FIREBFG');
1166 g_Sound_Delete('SOUND_FIRE');
1167 g_Sound_Delete('SOUND_IGNITE');
1168 g_Sound_Delete('SOUND_WEAPON_STARTFIREBFG');
1169 g_Sound_Delete('SOUND_WEAPON_EXPLODEROCKET');
1170 g_Sound_Delete('SOUND_WEAPON_EXPLODEBFG');
1171 g_Sound_Delete('SOUND_WEAPON_BFGWATER');
1172 g_Sound_Delete('SOUND_WEAPON_EXPLODEPLASMA');
1173 g_Sound_Delete('SOUND_WEAPON_PLASMAWATER');
1174 g_Sound_Delete('SOUND_WEAPON_FIREBALL');
1175 g_Sound_Delete('SOUND_WEAPON_EXPLODEBALL');
1176 g_Sound_Delete('SOUND_WEAPON_FIREREV');
1177 g_Sound_Delete('SOUND_WEAPON_FLAMEON');
1178 g_Sound_Delete('SOUND_WEAPON_FLAMEOFF');
1179 g_Sound_Delete('SOUND_WEAPON_FLAMEWORK');
1180 g_Sound_Delete('SOUND_PLAYER_JETFLY');
1181 g_Sound_Delete('SOUND_PLAYER_JETON');
1182 g_Sound_Delete('SOUND_PLAYER_JETOFF');
1183 g_Sound_Delete('SOUND_PLAYER_CASING1');
1184 g_Sound_Delete('SOUND_PLAYER_CASING2');
1185 g_Sound_Delete('SOUND_PLAYER_SHELL1');
1186 g_Sound_Delete('SOUND_PLAYER_SHELL2');
1187 end;
1190 function GunHitPlayer (X, Y: Integer; vx, vy: Integer; dmg: Integer; SpawnerUID: Word; AllowPush: Boolean): Boolean;
1191 var
1192 i: Integer;
1193 begin
1194 result := false;
1195 for i := 0 to High(gPlayers) do
1196 begin
1197 if (gPlayers[i] <> nil) and gPlayers[i].alive and gPlayers[i].Collide(X, Y) then
1198 begin
1199 if HitPlayer(gPlayers[i], dmg, vx*10, vy*10-3, SpawnerUID, HIT_SOME) then
1200 begin
1201 if AllowPush then gPlayers[i].Push(vx, vy);
1202 result := true;
1203 end;
1204 end;
1205 end;
1206 end;
1209 function GunHit (X, Y: Integer; vx, vy: Integer; dmg: Integer; SpawnerUID: Word; AllowPush: Boolean): Byte;
1211 function monsCheck (mon: TMonster): Boolean;
1212 begin
1213 result := false; // don't stop
1214 if HitMonster(mon, dmg, vx*10, vy*10-3, SpawnerUID, HIT_SOME) then
1215 begin
1216 if AllowPush then mon.Push(vx, vy);
1217 result := true;
1218 end;
1219 end;
1221 begin
1222 result := 0;
1223 if GunHitPlayer(X, Y, vx, vy, dmg, SpawnerUID, AllowPush) then result := 1
1224 else if g_Mons_ForEachAliveAt(X, Y, 1, 1, monsCheck) then result := 2;
1225 end;
1228 (*
1229 procedure g_Weapon_gunOld(const x, y, xd, yd, v, dmg: Integer; SpawnerUID: Word; CheckTrigger: Boolean);
1230 var
1231 a: Integer;
1232 x2, y2: Integer;
1233 dx, dy: Integer;
1234 xe, ye: Integer;
1235 xi, yi: Integer;
1236 s, c: Extended;
1237 //vx, vy: Integer;
1238 xx, yy, d: Integer;
1239 i: Integer;
1240 t1, _collide: Boolean;
1241 w, h: Word;
1242 {$IF DEFINED(D2F_DEBUG)}
1243 stt: UInt64;
1244 showTime: Boolean = true;
1245 {$ENDIF}
1246 begin
1247 a := GetAngle(x, y, xd, yd)+180;
1249 SinCos(DegToRad(-a), s, c);
1251 if Abs(s) < 0.01 then s := 0;
1252 if Abs(c) < 0.01 then c := 0;
1254 x2 := x+Round(c*gMapInfo.Width);
1255 y2 := y+Round(s*gMapInfo.Width);
1257 t1 := gWalls <> nil;
1258 _collide := False;
1259 w := gMapInfo.Width;
1260 h := gMapInfo.Height;
1262 xe := 0;
1263 ye := 0;
1264 dx := x2-x;
1265 dy := y2-y;
1267 if (xd = 0) and (yd = 0) then Exit;
1269 if dx > 0 then xi := 1 else if dx < 0 then xi := -1 else xi := 0;
1270 if dy > 0 then yi := 1 else if dy < 0 then yi := -1 else yi := 0;
1272 dx := Abs(dx);
1273 dy := Abs(dy);
1275 if dx > dy then d := dx else d := dy;
1277 //blood vel, for Monster.Damage()
1278 //vx := (dx*10 div d)*xi;
1279 //vy := (dy*10 div d)*yi;
1281 {$IF DEFINED(D2F_DEBUG)}
1282 stt := getTimeMicro();
1283 {$ENDIF}
1285 xx := x;
1286 yy := y;
1288 for i := 1 to d do
1289 begin
1290 xe := xe+dx;
1291 ye := ye+dy;
1293 if xe > d then
1294 begin
1295 xe := xe-d;
1296 xx := xx+xi;
1297 end;
1299 if ye > d then
1300 begin
1301 ye := ye-d;
1302 yy := yy+yi;
1303 end;
1305 if (yy > h) or (yy < 0) then Break;
1306 if (xx > w) or (xx < 0) then Break;
1308 if t1 then
1309 if ByteBool(gCollideMap[yy, xx] and MARK_BLOCKED) then
1310 begin
1311 _collide := True;
1312 {$IF DEFINED(D2F_DEBUG)}
1313 stt := getTimeMicro()-stt;
1314 e_WriteLog(Format('*** old trace time: %u microseconds', [LongWord(stt)]), MSG_NOTIFY);
1315 showTime := false;
1316 {$ENDIF}
1317 g_GFX_Spark(xx-xi, yy-yi, 2+Random(2), 180+a, 0, 0);
1318 if g_Game_IsServer and g_Game_IsNet then
1319 MH_SEND_Effect(xx-xi, yy-yi, 180+a, NET_GFX_SPARK);
1320 end;
1322 if not _collide then
1323 begin
1324 _collide := GunHit(xx, yy, xi*v, yi*v, dmg, SpawnerUID, v <> 0) <> 0;
1325 end;
1327 if _collide then Break;
1328 end;
1330 {$IF DEFINED(D2F_DEBUG)}
1331 if showTime then
1332 begin
1333 stt := getTimeMicro()-stt;
1334 e_WriteLog(Format('*** old trace time: %u microseconds', [LongWord(stt)]), MSG_NOTIFY);
1335 end;
1336 {$ENDIF}
1338 if CheckTrigger and g_Game_IsServer then
1339 g_Triggers_PressL(X, Y, xx-xi, yy-yi, SpawnerUID, ACTIVATE_SHOT);
1340 end;
1341 *)
1344 //!!!FIXME!!!
1345 procedure g_Weapon_gun (const x, y, xd, yd, v, indmg: Integer; SpawnerUID: Word; CheckTrigger: Boolean);
1346 var
1347 x0, y0: Integer;
1348 x2, y2: Integer;
1349 xi, yi: Integer;
1350 wallDistSq: Integer = $3fffffff;
1351 spawnerPlr: TPlayer = nil;
1352 dmg: Integer;
1354 function doPlayerHit (idx: Integer; hx, hy: Integer): Boolean;
1355 begin
1356 result := false;
1357 if (idx < 0) or (idx > High(gPlayers)) then exit;
1358 if (gPlayers[idx] = nil) or not gPlayers[idx].alive then exit;
1359 if (spawnerPlr <> nil) then
1360 begin
1361 if ((gGameSettings.Options and (GAME_OPTION_TEAMHITTRACE or GAME_OPTION_TEAMDAMAGE)) = 0) and
1362 (spawnerPlr.Team <> TEAM_NONE) and (spawnerPlr.Team = gPlayers[idx].Team) then
1363 begin
1364 if (spawnerPlr <> gPlayers[idx]) and ((gGameSettings.Options and GAME_OPTION_TEAMABSORBDAMAGE) = 0) then
1365 dmg := Max(1, dmg div 2);
1366 exit;
1367 end;
1368 end;
1369 result := HitPlayer(gPlayers[idx], dmg, (xi*v)*10, (yi*v)*10-3, SpawnerUID, HIT_SOME);
1370 if result and (v <> 0) then gPlayers[idx].Push((xi*v), (yi*v));
1371 {$IF DEFINED(D2F_DEBUG)}
1372 //if result then e_WriteLog(Format(' PLAYER #%d HIT', [idx]), MSG_NOTIFY);
1373 {$ENDIF}
1374 end;
1376 function doMonsterHit (mon: TMonster; hx, hy: Integer): Boolean;
1377 begin
1378 result := false;
1379 if (mon = nil) then exit;
1380 result := HitMonster(mon, dmg, (xi*v)*10, (yi*v)*10-3, SpawnerUID, HIT_SOME);
1381 if result and (v <> 0) then mon.Push((xi*v), (yi*v));
1382 {$IF DEFINED(D2F_DEBUG)}
1383 //if result then e_WriteLog(Format(' MONSTER #%u HIT', [LongWord(mon.UID)]), MSG_NOTIFY);
1384 {$ENDIF}
1385 end;
1387 // collect players along hitray
1388 // return `true` if instant hit was detected
1389 function playerPossibleHit (): Boolean;
1390 var
1391 i: Integer;
1392 px, py, pw, ph: Integer;
1393 inx, iny: Integer;
1394 distSq: Integer;
1395 plr: TPlayer;
1396 begin
1397 result := false;
1398 for i := 0 to High(gPlayers) do
1399 begin
1400 plr := gPlayers[i];
1401 if (plr <> nil) and plr.alive then
1402 begin
1403 plr.getMapBox(px, py, pw, ph);
1404 if lineAABBIntersects(x, y, x2, y2, px, py, pw, ph, inx, iny) then
1405 begin
1406 distSq := distanceSq(x, y, inx, iny);
1407 if (distSq = 0) then
1408 begin
1409 // contains
1410 if doPlayerHit(i, x, y) then begin result := true; exit; end;
1411 end
1412 else if (distSq < wallDistSq) then
1413 begin
1414 appendHitTimePlr(distSq, i, inx, iny);
1415 end;
1416 end;
1417 end;
1418 end;
1419 end;
1421 procedure sqchecker (mon: TMonster);
1422 var
1423 mx, my, mw, mh: Integer;
1424 inx, iny: Integer;
1425 distSq: Integer;
1426 begin
1427 mon.getMapBox(mx, my, mw, mh);
1428 if lineAABBIntersects(x0, y0, x2, y2, mx, my, mw, mh, inx, iny) then
1429 begin
1430 distSq := distanceSq(x0, y0, inx, iny);
1431 if (distSq < wallDistSq) then appendHitTimeMon(distSq, mon, inx, iny);
1432 end;
1433 end;
1435 var
1436 a: Integer;
1437 dx, dy: Integer;
1438 xe, ye: Integer;
1439 s, c: Extended;
1440 i: Integer;
1441 wallHitFlag: Boolean = false;
1442 wallHitX: Integer = 0;
1443 wallHitY: Integer = 0;
1444 didHit: Boolean = false;
1445 {$IF DEFINED(D2F_DEBUG)}
1446 stt: UInt64;
1447 {$ENDIF}
1448 mit: PMonster;
1449 it: TMonsterGrid.Iter;
1450 begin
1451 (*
1452 if not gwep_debug_fast_trace then
1453 begin
1454 g_Weapon_gunOld(x, y, xd, yd, v, dmg, SpawnerUID, CheckTrigger);
1455 exit;
1456 end;
1457 *)
1459 if (xd = 0) and (yd = 0) then exit;
1461 if (g_GetUIDType(SpawnerUID) = UID_PLAYER) then
1462 spawnerPlr := g_Player_Get(SpawnerUID);
1464 dmg := indmg;
1466 //wgunMonHash.reset(); //FIXME: clear hash on level change
1467 wgunHitHeap.clear();
1468 wgunHitTimeUsed := 0;
1470 a := GetAngle(x, y, xd, yd)+180;
1472 SinCos(DegToRad(-a), s, c);
1474 if Abs(s) < 0.01 then s := 0;
1475 if Abs(c) < 0.01 then c := 0;
1477 x0 := x;
1478 y0 := y;
1479 x2 := x+Round(c*gMapInfo.Width);
1480 y2 := y+Round(s*gMapInfo.Width);
1482 dx := x2-x;
1483 dy := y2-y;
1485 if (dx > 0) then xi := 1 else if (dx < 0) then xi := -1 else xi := 0;
1486 if (dy > 0) then yi := 1 else if (dy < 0) then yi := -1 else yi := 0;
1488 {$IF DEFINED(D2F_DEBUG)}
1489 e_WriteLog(Format('GUN TRACE: (%d,%d) to (%d,%d)', [x, y, x2, y2]), TMsgType.Notify);
1490 stt := getTimeMicro();
1491 {$ENDIF}
1493 wallHitFlag := (g_Map_traceToNearestWall(x, y, x2, y2, @wallHitX, @wallHitY) <> nil);
1494 if wallHitFlag then
1495 begin
1496 x2 := wallHitX;
1497 y2 := wallHitY;
1498 wallDistSq := distanceSq(x, y, wallHitX, wallHitY);
1499 end
1500 else
1501 begin
1502 wallHitX := x2;
1503 wallHitY := y2;
1504 end;
1506 if playerPossibleHit() then exit; // instant hit
1508 // collect monsters
1509 //g_Mons_AlongLine(x, y, x2, y2, sqchecker);
1511 it := monsGrid.forEachAlongLine(x, y, x2, y2, -1);
1512 for mit in it do sqchecker(mit^);
1513 it.release();
1515 // here, we collected all monsters and players in `wgunHitHeap` and `wgunHitTime`
1516 // also, if `wallWasHit` is `true`, then `wallHitX` and `wallHitY` contains spark coords
1517 while (wgunHitHeap.count > 0) do
1518 begin
1519 // has some entities to check, do it
1520 i := wgunHitHeap.front;
1521 wgunHitHeap.popFront();
1522 // hitpoint
1523 xe := wgunHitTime[i].x;
1524 ye := wgunHitTime[i].y;
1525 // check if it is not behind the wall
1526 if (wgunHitTime[i].mon <> nil) then
1527 begin
1528 didHit := doMonsterHit(wgunHitTime[i].mon, xe, ye);
1529 end
1530 else
1531 begin
1532 didHit := doPlayerHit(wgunHitTime[i].plridx, xe, ye);
1533 end;
1534 if didHit then
1535 begin
1536 // need new coords for trigger
1537 wallHitX := xe;
1538 wallHitY := ye;
1539 wallHitFlag := false; // no sparks
1540 break;
1541 end;
1542 end;
1544 // need sparks?
1545 if wallHitFlag then
1546 begin
1547 {$IF DEFINED(D2F_DEBUG)}
1548 stt := getTimeMicro()-stt;
1549 e_WriteLog(Format('*** new trace time: %u microseconds', [LongWord(stt)]), TMsgType.Notify);
1550 {$ENDIF}
1551 g_GFX_Spark(wallHitX, wallHitY, 2+Random(2), 180+a, 0, 0);
1552 if g_Game_IsServer and g_Game_IsNet then MH_SEND_Effect(wallHitX, wallHitY, 180+a, NET_GFX_SPARK);
1553 end
1554 else
1555 begin
1556 {$IF DEFINED(D2F_DEBUG)}
1557 stt := getTimeMicro()-stt;
1558 e_WriteLog(Format('*** new trace time: %u microseconds', [LongWord(stt)]), TMsgType.Notify);
1559 {$ENDIF}
1560 end;
1562 if CheckTrigger and g_Game_IsServer then g_Triggers_PressL(X, Y, wallHitX, wallHitY, SpawnerUID, ACTIVATE_SHOT);
1563 end;
1566 procedure g_Weapon_punch(x, y: Integer; d, SpawnerUID: Word);
1567 var
1568 obj: TObj;
1569 begin
1570 obj.X := X;
1571 obj.Y := Y;
1572 obj.rect.X := 0;
1573 obj.rect.Y := 0;
1574 obj.rect.Width := 39;
1575 obj.rect.Height := 52;
1576 obj.Vel.X := 0;
1577 obj.Vel.Y := 0;
1578 obj.Accel.X := 0;
1579 obj.Accel.Y := 0;
1581 if g_Weapon_Hit(@obj, d, SpawnerUID, HIT_SOME) <> 0 then
1582 g_Sound_PlayExAt('SOUND_WEAPON_HITPUNCH', x, y)
1583 else
1584 g_Sound_PlayExAt('SOUND_WEAPON_MISSPUNCH', x, y);
1585 end;
1587 function g_Weapon_chainsaw(x, y: Integer; d, SpawnerUID: Word): Integer;
1588 var
1589 obj: TObj;
1590 begin
1591 obj.X := X;
1592 obj.Y := Y;
1593 obj.rect.X := 0;
1594 obj.rect.Y := 0;
1595 obj.rect.Width := 32;
1596 obj.rect.Height := 52;
1597 obj.Vel.X := 0;
1598 obj.Vel.Y := 0;
1599 obj.Accel.X := 0;
1600 obj.Accel.Y := 0;
1602 Result := g_Weapon_Hit(@obj, d, SpawnerUID, HIT_SOME);
1603 end;
1605 procedure g_Weapon_rocket(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1606 Silent: Boolean = False; compat: Boolean = true);
1607 var
1608 find_id: DWORD;
1609 dx, dy: Integer;
1610 begin
1611 if WID < 0 then
1612 find_id := FindShot()
1613 else
1614 begin
1615 find_id := WID;
1616 if Integer(find_id) >= High(Shots) then
1617 SetLength(Shots, find_id + 64)
1618 end;
1620 with Shots[find_id] do
1621 begin
1622 g_Obj_Init(@Obj);
1624 Obj.Rect.Width := SHOT_ROCKETLAUNCHER_WIDTH;
1625 Obj.Rect.Height := SHOT_ROCKETLAUNCHER_HEIGHT;
1627 if compat then
1628 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1629 else
1630 dx := -(Obj.Rect.Width div 2);
1631 dy := -(Obj.Rect.Height div 2);
1633 ShotType := WEAPON_ROCKETLAUNCHER;
1634 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 12);
1636 Animation := nil;
1637 triggers := nil;
1638 end;
1640 Shots[find_id].SpawnerUID := SpawnerUID;
1642 if not Silent then
1643 g_Sound_PlayExAt('SOUND_WEAPON_FIREROCKET', x, y);
1644 end;
1646 procedure g_Weapon_revf(x, y, xd, yd: Integer; SpawnerUID, TargetUID: Word;
1647 WID: Integer = -1; Silent: Boolean = False);
1648 var
1649 find_id: DWORD;
1650 dx, dy: Integer;
1651 begin
1652 if WID < 0 then
1653 find_id := FindShot()
1654 else
1655 begin
1656 find_id := WID;
1657 if Integer(find_id) >= High(Shots) then
1658 SetLength(Shots, find_id + 64)
1659 end;
1661 with Shots[find_id] do
1662 begin
1663 g_Obj_Init(@Obj);
1665 Obj.Rect.Width := SHOT_SKELFIRE_WIDTH;
1666 Obj.Rect.Height := SHOT_SKELFIRE_HEIGHT;
1668 dx := -(Obj.Rect.Width div 2);
1669 dy := -(Obj.Rect.Height div 2);
1671 ShotType := WEAPON_SKEL_FIRE;
1672 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 12);
1674 triggers := nil;
1675 target := TargetUID;
1676 Animation := TAnimationState.Create(True, 5, 2); // !!! put values into table
1677 end;
1679 Shots[find_id].SpawnerUID := SpawnerUID;
1681 if not Silent then
1682 g_Sound_PlayExAt('SOUND_WEAPON_FIREREV', x, y);
1683 end;
1685 procedure g_Weapon_plasma(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1686 Silent: Boolean = False; compat: Boolean = true);
1687 var
1688 find_id: DWORD;
1689 dx, dy: Integer;
1690 begin
1691 if WID < 0 then
1692 find_id := FindShot()
1693 else
1694 begin
1695 find_id := WID;
1696 if Integer(find_id) >= High(Shots) then
1697 SetLength(Shots, find_id + 64);
1698 end;
1700 with Shots[find_id] do
1701 begin
1702 g_Obj_Init(@Obj);
1704 Obj.Rect.Width := SHOT_PLASMA_WIDTH;
1705 Obj.Rect.Height := SHOT_PLASMA_HEIGHT;
1707 if compat then
1708 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1709 else
1710 dx := -(Obj.Rect.Width div 2);
1711 dy := -(Obj.Rect.Height div 2);
1713 ShotType := WEAPON_PLASMA;
1714 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1716 triggers := nil;
1717 Animation := TAnimationState.Create(True, 5, 2); // !!! put values into table
1718 end;
1720 Shots[find_id].SpawnerUID := SpawnerUID;
1722 if not Silent then
1723 g_Sound_PlayExAt('SOUND_WEAPON_FIREPLASMA', x, y);
1724 end;
1726 procedure g_Weapon_flame(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1727 Silent: Boolean = False; compat: Boolean = true);
1728 var
1729 find_id: DWORD;
1730 dx, dy: Integer;
1731 begin
1732 if WID < 0 then
1733 find_id := FindShot()
1734 else
1735 begin
1736 find_id := WID;
1737 if Integer(find_id) >= High(Shots) then
1738 SetLength(Shots, find_id + 64);
1739 end;
1741 with Shots[find_id] do
1742 begin
1743 g_Obj_Init(@Obj);
1745 Obj.Rect.Width := SHOT_FLAME_WIDTH;
1746 Obj.Rect.Height := SHOT_FLAME_HEIGHT;
1748 if compat then
1749 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1750 else
1751 dx := -(Obj.Rect.Width div 2);
1752 dy := -(Obj.Rect.Height div 2);
1754 ShotType := WEAPON_FLAMETHROWER;
1755 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1757 triggers := nil;
1758 Animation := nil;
1759 end;
1761 Shots[find_id].SpawnerUID := SpawnerUID;
1763 // if not Silent then
1764 // g_Sound_PlayExAt('SOUND_WEAPON_FIREPLASMA', x, y);
1765 end;
1767 procedure g_Weapon_ball1(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1768 Silent: Boolean = False; compat: Boolean = true);
1769 var
1770 find_id: DWORD;
1771 dx, dy: Integer;
1772 begin
1773 if WID < 0 then
1774 find_id := FindShot()
1775 else
1776 begin
1777 find_id := WID;
1778 if Integer(find_id) >= High(Shots) then
1779 SetLength(Shots, find_id + 64)
1780 end;
1782 with Shots[find_id] do
1783 begin
1784 g_Obj_Init(@Obj);
1786 Obj.Rect.Width := 16;
1787 Obj.Rect.Height := 16;
1789 if compat then
1790 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1791 else
1792 dx := -(Obj.Rect.Width div 2);
1793 dy := -(Obj.Rect.Height div 2);
1795 ShotType := WEAPON_IMP_FIRE;
1796 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1798 triggers := nil;
1799 Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
1800 end;
1802 Shots[find_id].SpawnerUID := SpawnerUID;
1804 if not Silent then
1805 g_Sound_PlayExAt('SOUND_WEAPON_FIREBALL', x, y);
1806 end;
1808 procedure g_Weapon_ball2(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1809 Silent: Boolean = False; compat: Boolean = true);
1810 var
1811 find_id: DWORD;
1812 dx, dy: Integer;
1813 begin
1814 if WID < 0 then
1815 find_id := FindShot()
1816 else
1817 begin
1818 find_id := WID;
1819 if Integer(find_id) >= High(Shots) then
1820 SetLength(Shots, find_id + 64)
1821 end;
1823 with Shots[find_id] do
1824 begin
1825 g_Obj_Init(@Obj);
1827 Obj.Rect.Width := 16;
1828 Obj.Rect.Height := 16;
1830 if compat then
1831 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1832 else
1833 dx := -(Obj.Rect.Width div 2);
1834 dy := -(Obj.Rect.Height div 2);
1836 ShotType := WEAPON_CACO_FIRE;
1837 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1839 triggers := nil;
1840 Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
1841 end;
1843 Shots[find_id].SpawnerUID := SpawnerUID;
1845 if not Silent then
1846 g_Sound_PlayExAt('SOUND_WEAPON_FIREBALL', x, y);
1847 end;
1849 procedure g_Weapon_ball7(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1850 Silent: Boolean = False; compat: Boolean = true);
1851 var
1852 find_id: DWORD;
1853 dx, dy: Integer;
1854 begin
1855 if WID < 0 then
1856 find_id := FindShot()
1857 else
1858 begin
1859 find_id := WID;
1860 if Integer(find_id) >= High(Shots) then
1861 SetLength(Shots, find_id + 64)
1862 end;
1864 with Shots[find_id] do
1865 begin
1866 g_Obj_Init(@Obj);
1868 Obj.Rect.Width := 32;
1869 Obj.Rect.Height := 16;
1871 if compat then
1872 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1873 else
1874 dx := -(Obj.Rect.Width div 2);
1875 dy := -(Obj.Rect.Height div 2);
1877 ShotType := WEAPON_BARON_FIRE;
1878 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1880 triggers := nil;
1881 Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
1882 end;
1884 Shots[find_id].SpawnerUID := SpawnerUID;
1886 if not Silent then
1887 g_Sound_PlayExAt('SOUND_WEAPON_FIREBALL', x, y);
1888 end;
1890 procedure g_Weapon_aplasma(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1891 Silent: Boolean = False; compat: Boolean = true);
1892 var
1893 find_id, FramesID: DWORD;
1894 dx, dy: Integer;
1895 begin
1896 if WID < 0 then
1897 find_id := FindShot()
1898 else
1899 begin
1900 find_id := WID;
1901 if Integer(find_id) >= High(Shots) then
1902 SetLength(Shots, find_id + 64)
1903 end;
1905 with Shots[find_id] do
1906 begin
1907 g_Obj_Init(@Obj);
1909 Obj.Rect.Width := 16;
1910 Obj.Rect.Height := 16;
1912 if compat then
1913 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1914 else
1915 dx := -(Obj.Rect.Width div 2);
1916 dy := -(Obj.Rect.Height div 2);
1918 ShotType := WEAPON_BSP_FIRE;
1919 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1921 triggers := nil;
1923 Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
1924 end;
1926 Shots[find_id].SpawnerUID := SpawnerUID;
1928 if not Silent then
1929 g_Sound_PlayExAt('SOUND_WEAPON_FIREPLASMA', x, y);
1930 end;
1932 procedure g_Weapon_manfire(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1933 Silent: Boolean = False; compat: Boolean = true);
1934 var
1935 find_id: DWORD;
1936 dx, dy: Integer;
1937 begin
1938 if WID < 0 then
1939 find_id := FindShot()
1940 else
1941 begin
1942 find_id := WID;
1943 if Integer(find_id) >= High(Shots) then
1944 SetLength(Shots, find_id + 64)
1945 end;
1947 with Shots[find_id] do
1948 begin
1949 g_Obj_Init(@Obj);
1951 Obj.Rect.Width := 32;
1952 Obj.Rect.Height := 32;
1954 if compat then
1955 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1956 else
1957 dx := -(Obj.Rect.Width div 2);
1958 dy := -(Obj.Rect.Height div 2);
1960 ShotType := WEAPON_MANCUB_FIRE;
1961 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1963 triggers := nil;
1965 Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
1966 end;
1968 Shots[find_id].SpawnerUID := SpawnerUID;
1970 if not Silent then
1971 g_Sound_PlayExAt('SOUND_WEAPON_FIREBALL', x, y);
1972 end;
1974 procedure g_Weapon_bfgshot(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1975 Silent: Boolean = False; compat: Boolean = true);
1976 var
1977 find_id: DWORD;
1978 dx, dy: Integer;
1979 begin
1980 if WID < 0 then
1981 find_id := FindShot()
1982 else
1983 begin
1984 find_id := WID;
1985 if Integer(find_id) >= High(Shots) then
1986 SetLength(Shots, find_id + 64)
1987 end;
1989 with Shots[find_id] do
1990 begin
1991 g_Obj_Init(@Obj);
1993 Obj.Rect.Width := SHOT_BFG_WIDTH;
1994 Obj.Rect.Height := SHOT_BFG_HEIGHT;
1996 if compat then
1997 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1998 else
1999 dx := -(Obj.Rect.Width div 2);
2000 dy := -(Obj.Rect.Height div 2);
2002 ShotType := WEAPON_BFG;
2003 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
2005 triggers := nil;
2006 Animation := TAnimationState.Create(True, 6, 2); // !!! put values into table
2007 end;
2009 Shots[find_id].SpawnerUID := SpawnerUID;
2011 if not Silent then
2012 g_Sound_PlayExAt('SOUND_WEAPON_FIREBFG', x, y);
2013 end;
2015 procedure g_Weapon_bfghit(x, y: Integer);
2016 var
2017 ID: DWORD;
2018 Anim: TAnimation;
2019 begin
2020 if g_Frames_Get(ID, 'FRAMES_BFGHIT') then
2021 begin
2022 Anim := TAnimation.Create(ID, False, 4);
2023 g_GFX_OnceAnim(x-32, y-32, Anim);
2024 Anim.Free();
2025 end;
2026 end;
2028 procedure g_Weapon_pistol(x, y, xd, yd: Integer; SpawnerUID: Word;
2029 Silent: Boolean = False);
2030 begin
2031 if not Silent then
2032 g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', x, y);
2034 g_Weapon_gun(x, y, xd, yd, 1, 3, SpawnerUID, True);
2035 if gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF] then
2036 begin
2037 if ABS(x-xd) >= ABS(y-yd) then
2038 begin
2039 g_Weapon_gun(x, y+1, xd, yd+1, 1, 3, SpawnerUID, False);
2040 g_Weapon_gun(x, y-1, xd, yd-1, 1, 2, SpawnerUID, False);
2041 end
2042 else
2043 begin
2044 g_Weapon_gun(x+1, y, xd+1, yd, 1, 3, SpawnerUID, False);
2045 g_Weapon_gun(x-1, y, xd-1, yd, 1, 2, SpawnerUID, False);
2046 end;
2047 end;
2048 end;
2050 procedure g_Weapon_mgun(x, y, xd, yd: Integer; SpawnerUID: Word;
2051 Silent: Boolean = False);
2052 begin
2053 if not Silent then
2054 if gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', x, y);
2056 g_Weapon_gun(x, y, xd, yd, 1, 3, SpawnerUID, True);
2057 if (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF]) and
2058 (g_GetUIDType(SpawnerUID) = UID_PLAYER) 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, 2, 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, 2, 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_shotgun(x, y, xd, yd: Integer; SpawnerUID: Word;
2074 Silent: Boolean = False);
2075 var
2076 i, j, k: Integer;
2077 begin
2078 if not Silent then
2079 if gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', x, y);
2081 for i := 0 to 9 do
2082 begin
2083 j := 0; k := 0;
2084 if ABS(x-xd) >= ABS(y-yd) then j := Random(17) - 8 else k := Random(17) - 8; // -8 .. 8
2085 g_Weapon_gun(x+k, y+j, xd+k, yd+j, IfThen(i mod 2 <> 0, 1, 0), 3, SpawnerUID, i=0);
2086 end;
2087 end;
2089 procedure g_Weapon_dshotgun(x, y, xd, yd: Integer; SpawnerUID: Word;
2090 Silent: Boolean = False);
2091 var
2092 a, i, j, k: Integer;
2093 begin
2094 if not Silent then
2095 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN2', x, y);
2097 if gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF] then a := 25 else a := 20;
2098 for i := 0 to a do
2099 begin
2100 j := 0; k := 0;
2101 if ABS(x-xd) >= ABS(y-yd) then j := Random(41) - 20 else k := Random(41) - 20; // -20 .. 20
2102 g_Weapon_gun(x+k, y+j, xd+k, yd+j, IfThen(i mod 3 <> 0, 0, 1), 3, SpawnerUID, i=0);
2103 end;
2104 end;
2106 procedure g_Weapon_PreUpdate();
2107 var
2108 i: Integer;
2109 begin
2110 if Shots = nil then Exit;
2111 for i := 0 to High(Shots) do
2112 if Shots[i].ShotType <> 0 then
2113 begin
2114 Shots[i].Obj.oldX := Shots[i].Obj.X;
2115 Shots[i].Obj.oldY := Shots[i].Obj.Y;
2116 end;
2117 end;
2119 procedure g_Weapon_Update();
2120 var
2121 i, a, h, cx, cy, oldvx, oldvy, tf: Integer;
2122 _id: DWORD;
2123 Anim: TAnimation;
2124 t: DWArray;
2125 st: Word;
2126 TextureID: DWORD = DWORD(-1);
2127 s: String;
2128 o: TObj;
2129 spl: Boolean;
2130 Loud: Boolean;
2131 tcx, tcy: Integer;
2132 begin
2133 if Shots = nil then
2134 Exit;
2136 for i := 0 to High(Shots) do
2137 begin
2138 if Shots[i].ShotType = 0 then
2139 Continue;
2141 Loud := True;
2143 with Shots[i] do
2144 begin
2145 Timeout := Timeout - 1;
2146 oldvx := Obj.Vel.X;
2147 oldvy := Obj.Vel.Y;
2148 // Àêòèâèðîâàòü òðèããåðû ïî ïóòè (êðîìå óæå àêòèâèðîâàííûõ):
2149 if (Stopped = 0) and g_Game_IsServer then
2150 t := g_Triggers_PressR(Obj.X, Obj.Y, Obj.Rect.Width, Obj.Rect.Height,
2151 SpawnerUID, ACTIVATE_SHOT, triggers)
2152 else
2153 t := nil;
2155 if t <> nil then
2156 begin
2157 // Ïîïîëíÿåì ñïèñîê àêòèâèðîâàííûõ òðèããåðîâ:
2158 if triggers = nil then
2159 triggers := t
2160 else
2161 begin
2162 h := High(t);
2164 for a := 0 to h do
2165 if not InDWArray(t[a], triggers) then
2166 begin
2167 SetLength(triggers, Length(triggers)+1);
2168 triggers[High(triggers)] := t[a];
2169 end;
2170 end;
2171 end;
2173 // Àíèìàöèÿ ñíàðÿäà:
2174 if Animation <> nil then
2175 Animation.Update();
2177 // Äâèæåíèå:
2178 spl := (ShotType <> WEAPON_PLASMA) and
2179 (ShotType <> WEAPON_BFG) and
2180 (ShotType <> WEAPON_BSP_FIRE) and
2181 (ShotType <> WEAPON_FLAMETHROWER);
2183 if Stopped = 0 then
2184 begin
2185 st := g_Obj_Move_Projectile(@Obj, False, spl);
2186 end
2187 else
2188 begin
2189 st := 0;
2190 end;
2191 positionChanged(); // this updates spatial accelerators
2193 if WordBool(st and MOVE_FALLOUT) or (Obj.X < -1000) or
2194 (Obj.X > gMapInfo.Width+1000) or (Obj.Y < -1000) then
2195 begin
2196 // Íà êëèåíòå ñêîðåå âñåãî è òàê óæå âûïàë.
2197 ShotType := 0;
2198 Animation.Free();
2199 Continue;
2200 end;
2202 cx := Obj.X + (Obj.Rect.Width div 2);
2203 cy := Obj.Y + (Obj.Rect.Height div 2);
2205 TextureID := DWORD(-1); // !!!
2207 case ShotType of
2208 WEAPON_ROCKETLAUNCHER, WEAPON_SKEL_FIRE: // Ðàêåòû è ñíàðÿäû Ñêåëåòà
2209 begin
2210 // Âûëåòåëà èç âîäû:
2211 if WordBool(st and MOVE_HITAIR) then
2212 g_Obj_SetSpeed(@Obj, 12);
2214 // Â âîäå øëåéô - ïóçûðè, â âîçäóõå øëåéô - äûì:
2215 if WordBool(st and MOVE_INWATER) then
2216 begin
2217 g_GFX_Bubbles(cx, cy, 1+Random(3), 16, 16);
2218 if Random(2) = 0
2219 then g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', cx, cy)
2220 else g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', cx, cy);
2221 end
2222 else if g_Frames_Get(_id, 'FRAMES_SMOKE') then
2223 begin
2224 Anim := TAnimation.Create(_id, False, 3);
2225 Anim.Alpha := 150;
2226 g_GFX_OnceAnim(Obj.X-14+Random(9), cy-20+Random(9),
2227 Anim, ONCEANIM_SMOKE);
2228 Anim.Free();
2229 end;
2231 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2232 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL)) or
2233 (g_Weapon_Hit(@Obj, 10, SpawnerUID, HIT_SOME, False) <> 0) or
2234 (Timeout < 1) then
2235 begin
2236 Obj.Vel.X := 0;
2237 Obj.Vel.Y := 0;
2239 g_Weapon_Explode(cx, cy, 60, SpawnerUID);
2241 if ShotType = WEAPON_SKEL_FIRE then
2242 begin // Âçðûâ ñíàðÿäà Ñêåëåòà
2243 if g_Frames_Get(TextureID, 'FRAMES_EXPLODE_SKELFIRE') then
2244 begin
2245 Anim := TAnimation.Create(TextureID, False, 8);
2246 Anim.Blending := False;
2247 g_GFX_OnceAnim((Obj.X+32)-58, (Obj.Y+8)-36, Anim);
2248 g_DynLightExplosion((Obj.X+32), (Obj.Y+8), 64, 1, 0, 0);
2249 Anim.Free();
2250 end;
2251 end
2252 else
2253 begin // Âçðûâ Ðàêåòû
2254 if g_Frames_Get(TextureID, 'FRAMES_EXPLODE_ROCKET') then
2255 begin
2256 Anim := TAnimation.Create(TextureID, False, 6);
2257 Anim.Blending := False;
2258 g_GFX_OnceAnim(cx-64, cy-64, Anim);
2259 g_DynLightExplosion(cx, cy, 64, 1, 0, 0);
2260 Anim.Free();
2261 end;
2262 end;
2264 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEROCKET', Obj.X, Obj.Y);
2266 ShotType := 0;
2267 end;
2269 if ShotType = WEAPON_SKEL_FIRE then
2270 begin // Ñàìîíàâîäêà ñíàðÿäà Ñêåëåòà:
2271 if GetPos(target, @o) then
2272 throw(i, Obj.X, Obj.Y,
2273 o.X+o.Rect.X+(o.Rect.Width div 2)+o.Vel.X+o.Accel.X,
2274 o.Y+o.Rect.Y+(o.Rect.Height div 2)+o.Vel.Y+o.Accel.Y,
2275 12);
2276 end;
2277 end;
2279 WEAPON_PLASMA, WEAPON_BSP_FIRE: // Ïëàçìà, ïëàçìà Àðàõíàòðîíà
2280 begin
2281 // Ïîïàëà â âîäó - ýëåêòðîøîê ïî âîäå:
2282 if WordBool(st and (MOVE_INWATER or MOVE_HITWATER)) then
2283 begin
2284 g_Sound_PlayExAt('SOUND_WEAPON_PLASMAWATER', Obj.X, Obj.Y);
2285 if g_Game_IsServer then CheckTrap(i, 10, HIT_ELECTRO);
2286 ShotType := 0;
2287 Continue;
2288 end;
2290 // Âåëè÷èíà óðîíà:
2291 if (ShotType = WEAPON_PLASMA) and
2292 (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF]) then
2293 a := 10
2294 else
2295 a := 5;
2297 if ShotType = WEAPON_BSP_FIRE then
2298 a := 10;
2300 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2301 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL)) or
2302 (g_Weapon_Hit(@Obj, a, SpawnerUID, HIT_SOME, False) <> 0) or
2303 (Timeout < 1) then
2304 begin
2305 if ShotType = WEAPON_PLASMA then
2306 s := 'FRAMES_EXPLODE_PLASMA'
2307 else
2308 s := 'FRAMES_EXPLODE_BSPFIRE';
2310 // Âçðûâ Ïëàçìû:
2311 if g_Frames_Get(TextureID, s) then
2312 begin
2313 Anim := TAnimation.Create(TextureID, False, 3);
2314 Anim.Blending := False;
2315 g_GFX_OnceAnim(cx-16, cy-16, Anim);
2316 Anim.Free();
2317 g_DynLightExplosion(cx, cy, 32, 0, 0.5, 0.5);
2318 end;
2320 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEPLASMA', Obj.X, Obj.Y);
2322 ShotType := 0;
2323 end;
2324 end;
2326 WEAPON_FLAMETHROWER: // Îãíåìåò
2327 begin
2328 // Ñî âðåìåíåì óìèðàåò
2329 if (Timeout < 1) then
2330 begin
2331 ShotType := 0;
2332 Continue;
2333 end;
2334 // Ïîä âîäîé òîæå
2335 if WordBool(st and (MOVE_HITWATER or MOVE_INWATER)) then
2336 begin
2337 if WordBool(st and MOVE_HITWATER) then
2338 begin
2339 if g_Frames_Get(_id, 'FRAMES_SMOKE') then
2340 begin
2341 Anim := TAnimation.Create(_id, False, 3);
2342 Anim.Alpha := 0;
2343 tcx := Random(8);
2344 tcy := Random(8);
2345 g_GFX_OnceAnim(cx-4+tcx-(Anim.Width div 2),
2346 cy-4+tcy-(Anim.Height div 2),
2347 Anim, ONCEANIM_SMOKE);
2348 Anim.Free();
2349 end;
2350 end
2351 else
2352 begin
2353 g_GFX_Bubbles(cx, cy, 1+Random(3), 16, 16);
2354 if Random(2) = 0
2355 then g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', cx, cy)
2356 else g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', cx, cy);
2357 end;
2358 ShotType := 0;
2359 Continue;
2360 end;
2362 // Ãðàâèòàöèÿ
2363 if Stopped = 0 then
2364 Obj.Accel.Y := Obj.Accel.Y + 1;
2365 // Ïîïàëè â ñòåíó èëè â âîäó:
2366 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL or MOVE_HITWATER)) then
2367 begin
2368 // Ïðèëèïàåì:
2369 Obj.Vel.X := 0;
2370 Obj.Vel.Y := 0;
2371 Obj.Accel.Y := 0;
2372 if WordBool(st and MOVE_HITWALL) then
2373 Stopped := MOVE_HITWALL
2374 else if WordBool(st and MOVE_HITLAND) then
2375 Stopped := MOVE_HITLAND
2376 else if WordBool(st and MOVE_HITCEIL) then
2377 Stopped := MOVE_HITCEIL;
2378 end;
2380 a := IfThen(Stopped = 0, 10, 1);
2381 // Åñëè â êîãî-òî ïîïàëè
2382 if g_Weapon_Hit(@Obj, a, SpawnerUID, HIT_FLAME, False) <> 0 then
2383 begin
2384 // HIT_FLAME ñàì ïîäîææåò
2385 // Åñëè â ïîëåòå ïîïàëè, èñ÷åçàåì
2386 if Stopped = 0 then
2387 ShotType := 0;
2388 end;
2390 if Stopped = 0 then
2391 tf := 2
2392 else
2393 tf := 3;
2395 if (gTime mod LongWord(tf) = 0) then
2396 begin
2397 g_Frames_Get(TextureID, 'FRAMES_FLAME');
2398 Anim := TAnimation.Create(TextureID, False, 2 + Random(2));
2399 Anim.Alpha := 0;
2400 case Stopped of
2401 MOVE_HITWALL: begin tcx := cx-4+Random(8); tcy := cy-12+Random(24); end;
2402 MOVE_HITLAND: begin tcx := cx-12+Random(24); tcy := cy-10+Random(8); end;
2403 MOVE_HITCEIL: begin tcx := cx-12+Random(24); tcy := cy+6+Random(8); end;
2404 else begin tcx := cx-4+Random(8); tcy := cy-4+Random(8); end;
2405 end;
2406 g_GFX_OnceAnim(tcx-(Anim.Width div 2), tcy-(Anim.Height div 2), Anim, ONCEANIM_SMOKE);
2407 Anim.Free();
2408 //g_DynLightExplosion(tcx, tcy, 1, 1, 0.8, 0.3);
2409 end;
2410 end;
2412 WEAPON_BFG: // BFG
2413 begin
2414 // Ïîïàëà â âîäó - ýëåêòðîøîê ïî âîäå:
2415 if WordBool(st and (MOVE_INWATER or MOVE_HITWATER)) then
2416 begin
2417 g_Sound_PlayExAt('SOUND_WEAPON_BFGWATER', Obj.X, Obj.Y);
2418 if g_Game_IsServer then CheckTrap(i, 1000, HIT_ELECTRO);
2419 ShotType := 0;
2420 Continue;
2421 end;
2423 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2424 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL)) or
2425 (g_Weapon_Hit(@Obj, SHOT_BFG_DAMAGE, SpawnerUID, HIT_BFG, False) <> 0) or
2426 (Timeout < 1) then
2427 begin
2428 // Ëó÷è BFG:
2429 if g_Game_IsServer then g_Weapon_BFG9000(cx, cy, SpawnerUID);
2431 // Âçðûâ BFG:
2432 if g_Frames_Get(TextureID, 'FRAMES_EXPLODE_BFG') then
2433 begin
2434 Anim := TAnimation.Create(TextureID, False, 6);
2435 Anim.Blending := False;
2436 g_GFX_OnceAnim(cx-64, cy-64, Anim);
2437 Anim.Free();
2438 g_DynLightExplosion(cx, cy, 96, 0, 1, 0);
2439 end;
2441 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBFG', Obj.X, Obj.Y);
2443 ShotType := 0;
2444 end;
2445 end;
2447 WEAPON_IMP_FIRE, WEAPON_CACO_FIRE, WEAPON_BARON_FIRE: // Âûñòðåëû Áåñà, Êàêîäåìîíà Ðûöàðÿ/Áàðîíà àäà
2448 begin
2449 // Âûëåòåë èç âîäû:
2450 if WordBool(st and MOVE_HITAIR) then
2451 g_Obj_SetSpeed(@Obj, 16);
2453 // Âåëè÷èíà óðîíà:
2454 if ShotType = WEAPON_IMP_FIRE then
2455 a := 5
2456 else
2457 if ShotType = WEAPON_CACO_FIRE then
2458 a := 20
2459 else
2460 a := 40;
2462 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2463 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL)) or
2464 (g_Weapon_Hit(@Obj, a, SpawnerUID, HIT_SOME) <> 0) or
2465 (Timeout < 1) then
2466 begin
2467 if ShotType = WEAPON_IMP_FIRE then
2468 s := 'FRAMES_EXPLODE_IMPFIRE'
2469 else
2470 if ShotType = WEAPON_CACO_FIRE then
2471 s := 'FRAMES_EXPLODE_CACOFIRE'
2472 else
2473 s := 'FRAMES_EXPLODE_BARONFIRE';
2475 // Âçðûâ:
2476 if g_Frames_Get(TextureID, s) then
2477 begin
2478 Anim := TAnimation.Create(TextureID, False, 6);
2479 Anim.Blending := False;
2480 g_GFX_OnceAnim(cx-32, cy-32, Anim);
2481 Anim.Free();
2482 end;
2484 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBALL', Obj.X, Obj.Y);
2486 ShotType := 0;
2487 end;
2488 end;
2490 WEAPON_MANCUB_FIRE: // Âûñòðåë Ìàíêóáóñà
2491 begin
2492 // Âûëåòåë èç âîäû:
2493 if WordBool(st and MOVE_HITAIR) then
2494 g_Obj_SetSpeed(@Obj, 16);
2496 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2497 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL)) or
2498 (g_Weapon_Hit(@Obj, 40, SpawnerUID, HIT_SOME, False) <> 0) or
2499 (Timeout < 1) then
2500 begin
2501 // Âçðûâ:
2502 if g_Frames_Get(TextureID, 'FRAMES_EXPLODE_ROCKET') then
2503 begin
2504 Anim := TAnimation.Create(TextureID, False, 6);
2505 Anim.Blending := False;
2506 g_GFX_OnceAnim(cx-64, cy-64, Anim);
2507 Anim.Free();
2508 end;
2510 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBALL', Obj.X, Obj.Y);
2512 ShotType := 0;
2513 end;
2514 end;
2515 end; // case ShotType of...
2517 // Åñëè ñíàðÿäà óæå íåò, óäàëÿåì àíèìàöèþ:
2518 if (ShotType = 0) then
2519 begin
2520 if gGameSettings.GameType = GT_SERVER then
2521 MH_SEND_DeleteShot(i, Obj.X, Obj.Y, Loud);
2522 if Animation <> nil then
2523 begin
2524 Animation.Free();
2525 Animation := nil;
2526 end;
2527 end
2528 else if (ShotType <> WEAPON_FLAMETHROWER) and ((oldvx <> Obj.Vel.X) or (oldvy <> Obj.Vel.Y)) then
2529 if gGameSettings.GameType = GT_SERVER then
2530 MH_SEND_UpdateShot(i);
2531 end;
2532 end;
2533 end;
2535 function g_Weapon_Danger(UID: Word; X, Y: Integer; Width, Height: Word; Time: Byte): Boolean;
2536 var
2537 a: Integer;
2538 begin
2539 Result := False;
2541 if Shots = nil then
2542 Exit;
2544 for a := 0 to High(Shots) do
2545 if (Shots[a].ShotType <> 0) and (Shots[a].SpawnerUID <> UID) then
2546 if ((Shots[a].Obj.Vel.Y = 0) and (Shots[a].Obj.Vel.X > 0) and (Shots[a].Obj.X < X)) or
2547 (Shots[a].Obj.Vel.Y = 0) and (Shots[a].Obj.Vel.X < 0) and (Shots[a].Obj.X > X) then
2548 if (Abs(X-Shots[a].Obj.X) < Abs(Shots[a].Obj.Vel.X*Time)) and
2549 g_Collide(X, Y, Width, Height, X, Shots[a].Obj.Y,
2550 Shots[a].Obj.Rect.Width, Shots[a].Obj.Rect.Height) and
2551 g_TraceVector(X, Y, Shots[a].Obj.X, Shots[a].Obj.Y) then
2552 begin
2553 Result := True;
2554 Exit;
2555 end;
2556 end;
2558 procedure g_Weapon_SaveState (st: TStream);
2559 var
2560 count, i, j: Integer;
2561 begin
2562 // Ñ÷èòàåì êîëè÷åñòâî ñóùåñòâóþùèõ ñíàðÿäîâ
2563 count := 0;
2564 for i := 0 to High(Shots) do if (Shots[i].ShotType <> 0) then Inc(count);
2566 // Êîëè÷åñòâî ñíàðÿäîâ
2567 utils.WriteInt(st, count);
2569 if (count = 0) then exit;
2571 for i := 0 to High(Shots) do
2572 begin
2573 if Shots[i].ShotType <> 0 then
2574 begin
2575 // Ñèãíàòóðà ñíàðÿäà
2576 utils.writeSign(st, 'SHOT');
2577 utils.writeInt(st, Byte(0)); // version
2578 // Òèï ñíàðÿäà
2579 utils.writeInt(st, Byte(Shots[i].ShotType));
2580 // Öåëü
2581 utils.writeInt(st, Word(Shots[i].Target));
2582 // UID ñòðåëÿâøåãî
2583 utils.writeInt(st, Word(Shots[i].SpawnerUID));
2584 // Ðàçìåð ïîëÿ Triggers
2585 utils.writeInt(st, Integer(Length(Shots[i].Triggers)));
2586 // Òðèããåðû, àêòèâèðîâàííûå âûñòðåëîì
2587 for j := 0 to Length(Shots[i].Triggers)-1 do utils.writeInt(st, LongWord(Shots[i].Triggers[j]));
2588 // Îáúåêò ñíàðÿäà
2589 Obj_SaveState(st, @Shots[i].Obj);
2590 // Êîñòûëèíà åáàíàÿ
2591 utils.writeInt(st, Byte(Shots[i].Stopped));
2592 end;
2593 end;
2594 end;
2596 procedure g_Weapon_LoadState (st: TStream);
2597 var
2598 count, tc, i, j: Integer;
2599 begin
2600 if (st = nil) then exit;
2602 // Êîëè÷åñòâî ñíàðÿäîâ
2603 count := utils.readLongInt(st);
2604 if (count < 0) or (count > 1024*1024) then raise XStreamError.Create('invalid shots counter');
2606 SetLength(Shots, count);
2608 if (count = 0) then exit;
2610 for i := 0 to count-1 do
2611 begin
2612 // Ñèãíàòóðà ñíàðÿäà
2613 if not utils.checkSign(st, 'SHOT') then raise XStreamError.Create('invalid shot signature');
2614 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid shot version');
2615 // Òèï ñíàðÿäà:
2616 Shots[i].ShotType := utils.readByte(st);
2617 // Öåëü
2618 Shots[i].Target := utils.readWord(st);
2619 // UID ñòðåëÿâøåãî
2620 Shots[i].SpawnerUID := utils.readWord(st);
2621 // Ðàçìåð ïîëÿ Triggers
2622 tc := utils.readLongInt(st);
2623 if (tc < 0) or (tc > 1024*1024) then raise XStreamError.Create('invalid shot triggers counter');
2624 SetLength(Shots[i].Triggers, tc);
2625 // Òðèããåðû, àêòèâèðîâàííûå âûñòðåëîì
2626 for j := 0 to tc-1 do Shots[i].Triggers[j] := utils.readLongWord(st);
2627 // Îáúåêò ïðåäìåòà
2628 Obj_LoadState(@Shots[i].Obj, st);
2629 // Êîñòûëèíà åáàíàÿ
2630 Shots[i].Stopped := utils.readByte(st);
2632 // Óñòàíîâêà òåêñòóðû èëè àíèìàöèè
2633 Shots[i].Animation := nil;
2635 case Shots[i].ShotType of
2636 WEAPON_ROCKETLAUNCHER, WEAPON_SKEL_FIRE:
2637 begin
2638 end;
2639 WEAPON_PLASMA:
2640 begin
2641 Shots[i].Animation := TAnimationState.Create(True, 5, 2); // !!! put values into table
2642 end;
2643 WEAPON_BFG:
2644 begin
2645 Shots[i].Animation := TAnimationState.Create(True, 6, 2); // !!! put values into table
2646 end;
2647 WEAPON_IMP_FIRE:
2648 begin
2649 Shots[i].Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
2650 end;
2651 WEAPON_BSP_FIRE:
2652 begin
2653 Shots[i].Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
2654 end;
2655 WEAPON_CACO_FIRE:
2656 begin
2657 Shots[i].Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
2658 end;
2659 WEAPON_BARON_FIRE:
2660 begin
2661 Shots[i].Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
2662 end;
2663 WEAPON_MANCUB_FIRE:
2664 begin
2665 Shots[i].Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
2666 end;
2667 end;
2668 end;
2669 end;
2671 procedure g_Weapon_DestroyShot(I: Integer; X, Y: Integer; Loud: Boolean = True);
2672 var
2673 cx, cy: Integer;
2674 Anim: TAnimation;
2675 s: string;
2676 TextureID: DWORD = DWORD(-1);
2677 begin
2678 if Shots = nil then
2679 Exit;
2680 if (I > High(Shots)) or (I < 0) then Exit;
2682 with Shots[I] do
2683 begin
2684 if ShotType = 0 then Exit;
2685 Obj.X := X;
2686 Obj.Y := Y;
2687 cx := Obj.X + (Obj.Rect.Width div 2);
2688 cy := Obj.Y + (Obj.Rect.Height div 2);
2690 case ShotType of
2691 WEAPON_ROCKETLAUNCHER, WEAPON_SKEL_FIRE: // Ðàêåòû è ñíàðÿäû Ñêåëåòà
2692 begin
2693 if Loud then
2694 begin
2695 if ShotType = WEAPON_SKEL_FIRE then
2696 begin // Âçðûâ ñíàðÿäà Ñêåëåòà
2697 if g_Frames_Get(TextureID, 'FRAMES_EXPLODE_SKELFIRE') then
2698 begin
2699 Anim := TAnimation.Create(TextureID, False, 8);
2700 Anim.Blending := False;
2701 g_GFX_OnceAnim((Obj.X+32)-32, (Obj.Y+8)-32, Anim);
2702 Anim.Free();
2703 end;
2704 end
2705 else
2706 begin // Âçðûâ Ðàêåòû
2707 if g_Frames_Get(TextureID, 'FRAMES_EXPLODE_ROCKET') then
2708 begin
2709 Anim := TAnimation.Create(TextureID, False, 6);
2710 Anim.Blending := False;
2711 g_GFX_OnceAnim(cx-64, cy-64, Anim);
2712 Anim.Free();
2713 end;
2714 end;
2715 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEROCKET', Obj.X, Obj.Y);
2716 end;
2717 end;
2719 WEAPON_PLASMA, WEAPON_BSP_FIRE: // Ïëàçìà, ïëàçìà Àðàõíàòðîíà
2720 begin
2721 if ShotType = WEAPON_PLASMA then
2722 s := 'FRAMES_EXPLODE_PLASMA'
2723 else
2724 s := 'FRAMES_EXPLODE_BSPFIRE';
2726 if g_Frames_Get(TextureID, s) and loud then
2727 begin
2728 Anim := TAnimation.Create(TextureID, False, 3);
2729 Anim.Blending := False;
2730 g_GFX_OnceAnim(cx-16, cy-16, Anim);
2731 Anim.Free();
2733 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEPLASMA', Obj.X, Obj.Y);
2734 end;
2735 end;
2737 WEAPON_BFG: // BFG
2738 begin
2739 // Âçðûâ BFG:
2740 if g_Frames_Get(TextureID, 'FRAMES_EXPLODE_BFG') and Loud then
2741 begin
2742 Anim := TAnimation.Create(TextureID, False, 6);
2743 Anim.Blending := False;
2744 g_GFX_OnceAnim(cx-64, cy-64, Anim);
2745 Anim.Free();
2747 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBFG', Obj.X, Obj.Y);
2748 end;
2749 end;
2751 WEAPON_IMP_FIRE, WEAPON_CACO_FIRE, WEAPON_BARON_FIRE: // Âûñòðåëû Áåñà, Êàêîäåìîíà Ðûöàðÿ/Áàðîíà àäà
2752 begin
2753 if ShotType = WEAPON_IMP_FIRE then
2754 s := 'FRAMES_EXPLODE_IMPFIRE'
2755 else
2756 if ShotType = WEAPON_CACO_FIRE then
2757 s := 'FRAMES_EXPLODE_CACOFIRE'
2758 else
2759 s := 'FRAMES_EXPLODE_BARONFIRE';
2761 if g_Frames_Get(TextureID, s) and Loud then
2762 begin
2763 Anim := TAnimation.Create(TextureID, False, 6);
2764 Anim.Blending := False;
2765 g_GFX_OnceAnim(cx-32, cy-32, Anim);
2766 Anim.Free();
2768 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBALL', Obj.X, Obj.Y);
2769 end;
2770 end;
2772 WEAPON_MANCUB_FIRE: // Âûñòðåë Ìàíêóáóñà
2773 begin
2774 if g_Frames_Get(TextureID, 'FRAMES_EXPLODE_ROCKET') and Loud then
2775 begin
2776 Anim := TAnimation.Create(TextureID, False, 6);
2777 Anim.Blending := False;
2778 g_GFX_OnceAnim(cx-64, cy-64, Anim);
2779 Anim.Free();
2781 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBALL', Obj.X, Obj.Y);
2782 end;
2783 end;
2784 end; // case ShotType of...
2786 ShotType := 0;
2787 Animation.Free();
2788 end;
2789 end;
2792 procedure g_Weapon_AddDynLights();
2793 var
2794 i: Integer;
2795 begin
2796 if Shots = nil then Exit;
2797 for i := 0 to High(Shots) do
2798 begin
2799 if Shots[i].ShotType = 0 then continue;
2800 if (Shots[i].ShotType = WEAPON_ROCKETLAUNCHER) or
2801 (Shots[i].ShotType = WEAPON_BARON_FIRE) or
2802 (Shots[i].ShotType = WEAPON_MANCUB_FIRE) or
2803 (Shots[i].ShotType = WEAPON_SKEL_FIRE) or
2804 (Shots[i].ShotType = WEAPON_IMP_FIRE) or
2805 (Shots[i].ShotType = WEAPON_CACO_FIRE) or
2806 (Shots[i].ShotType = WEAPON_MANCUB_FIRE) or
2807 (Shots[i].ShotType = WEAPON_BSP_FIRE) or
2808 (Shots[i].ShotType = WEAPON_PLASMA) or
2809 (Shots[i].ShotType = WEAPON_BFG) or
2810 (Shots[i].ShotType = WEAPON_FLAMETHROWER) or
2811 false then
2812 begin
2813 if (Shots[i].ShotType = WEAPON_PLASMA) then
2814 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)
2815 else if (Shots[i].ShotType = WEAPON_BFG) then
2816 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)
2817 else if (Shots[i].ShotType = WEAPON_FLAMETHROWER) then
2818 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)
2819 else
2820 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);
2821 end;
2822 end;
2823 end;
2826 procedure TShot.positionChanged (); begin end;
2829 end.