DEADSOFTWARE

game: disable gibs for server
[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 Math, g_map, g_player, g_sound, g_panel,
121 g_console, g_options, g_game,
122 g_triggers, MAPDEF, e_log, g_monsters, g_saveload,
123 g_language, g_netmsg, g_grid,
124 geom, binheap, hashtable, utils, xstreams
127 type
128 TWaterPanel = record
129 X, Y: Integer;
130 Width, Height: Word;
131 Active: Boolean;
132 end;
134 const
135 SHOT_ROCKETLAUNCHER_WIDTH = 14;
136 SHOT_ROCKETLAUNCHER_HEIGHT = 14;
138 SHOT_SKELFIRE_WIDTH = 14;
139 SHOT_SKELFIRE_HEIGHT = 14;
141 SHOT_PLASMA_WIDTH = 16;
142 SHOT_PLASMA_HEIGHT = 16;
144 SHOT_BFG_WIDTH = 32;
145 SHOT_BFG_HEIGHT = 32;
146 SHOT_BFG_DAMAGE = 100;
147 SHOT_BFG_RADIUS = 256;
149 SHOT_FLAME_WIDTH = 4;
150 SHOT_FLAME_HEIGHT = 4;
151 SHOT_FLAME_LIFETIME = 180;
153 SHOT_SIGNATURE = $544F4853; // 'SHOT'
155 type
156 PHitTime = ^THitTime;
157 THitTime = record
158 distSq: Integer;
159 mon: TMonster;
160 plridx: Integer; // if mon=nil
161 x, y: Integer;
162 end;
164 TBinHeapKeyHitTime = class
165 public
166 class function less (const a, b: Integer): Boolean; inline;
167 end;
169 // indicies in `wgunHitTime` array
170 TBinaryHeapHitTimes = specialize TBinaryHeapBase<Integer, TBinHeapKeyHitTime>;
172 var
173 WaterMap: array of array of DWORD = nil;
174 //wgunMonHash: THashIntInt = nil;
175 wgunHitHeap: TBinaryHeapHitTimes = nil;
176 wgunHitTime: array of THitTime = nil;
177 wgunHitTimeUsed: Integer = 0;
180 class function TBinHeapKeyHitTime.less (const a, b: Integer): Boolean;
181 var
182 hta, htb: PHitTime;
183 begin
184 hta := @wgunHitTime[a];
185 htb := @wgunHitTime[b];
186 if (hta.distSq <> htb.distSq) then begin result := (hta.distSq < htb.distSq); exit; end;
187 if (hta.mon <> nil) then
188 begin
189 // a is monster
190 if (htb.mon = nil) then begin result := false; exit; end; // players first
191 result := (hta.mon.UID < htb.mon.UID); // why not?
192 end
193 else
194 begin
195 // a is player
196 if (htb.mon <> nil) then begin result := true; exit; end; // players first
197 result := (hta.plridx < htb.plridx); // why not?
198 end;
199 end;
202 procedure appendHitTimeMon (adistSq: Integer; amon: TMonster; ax, ay: Integer);
203 begin
204 if (wgunHitTimeUsed = Length(wgunHitTime)) then SetLength(wgunHitTime, wgunHitTimeUsed+128);
205 with wgunHitTime[wgunHitTimeUsed] do
206 begin
207 distSq := adistSq;
208 mon := amon;
209 plridx := -1;
210 x := ax;
211 y := ay;
212 end;
213 wgunHitHeap.insert(wgunHitTimeUsed);
214 Inc(wgunHitTimeUsed);
215 end;
218 procedure appendHitTimePlr (adistSq: Integer; aplridx: Integer; ax, ay: Integer);
219 begin
220 if (wgunHitTimeUsed = Length(wgunHitTime)) then SetLength(wgunHitTime, wgunHitTimeUsed+128);
221 with wgunHitTime[wgunHitTimeUsed] do
222 begin
223 distSq := adistSq;
224 mon := nil;
225 plridx := aplridx;
226 x := ax;
227 y := ay;
228 end;
229 wgunHitHeap.insert(wgunHitTimeUsed);
230 Inc(wgunHitTimeUsed);
231 end;
234 function FindShot(): DWORD;
235 var
236 i: Integer;
237 begin
238 if Shots <> nil then
239 for i := 0 to High(Shots) do
240 if Shots[i].ShotType = 0 then
241 begin
242 Result := i;
243 LastShotID := Result;
244 Exit;
245 end;
247 if Shots = nil then
248 begin
249 SetLength(Shots, 128);
250 Result := 0;
251 end
252 else
253 begin
254 Result := High(Shots) + 1;
255 SetLength(Shots, Length(Shots) + 128);
256 end;
257 LastShotID := Result;
258 end;
260 procedure CreateWaterMap();
261 var
262 WaterArray: Array of TWaterPanel;
263 a, b, c, m: Integer;
264 ok: Boolean;
265 begin
266 if gWater = nil then
267 Exit;
269 SetLength(WaterArray, Length(gWater));
271 for a := 0 to High(gWater) do
272 begin
273 WaterArray[a].X := gWater[a].X;
274 WaterArray[a].Y := gWater[a].Y;
275 WaterArray[a].Width := gWater[a].Width;
276 WaterArray[a].Height := gWater[a].Height;
277 WaterArray[a].Active := True;
278 end;
280 g_Game_SetLoadingText(_lc[I_LOAD_WATER_MAP], High(WaterArray), False);
282 for a := 0 to High(WaterArray) do
283 if WaterArray[a].Active then
284 begin
285 WaterArray[a].Active := False;
286 m := Length(WaterMap);
287 SetLength(WaterMap, m+1);
288 SetLength(WaterMap[m], 1);
289 WaterMap[m][0] := a;
290 ok := True;
292 while ok do
293 begin
294 ok := False;
295 for b := 0 to High(WaterArray) do
296 if WaterArray[b].Active then
297 for c := 0 to High(WaterMap[m]) do
298 if g_CollideAround(WaterArray[b].X,
299 WaterArray[b].Y,
300 WaterArray[b].Width,
301 WaterArray[b].Height,
302 WaterArray[WaterMap[m][c]].X,
303 WaterArray[WaterMap[m][c]].Y,
304 WaterArray[WaterMap[m][c]].Width,
305 WaterArray[WaterMap[m][c]].Height) then
306 begin
307 WaterArray[b].Active := False;
308 SetLength(WaterMap[m],
309 Length(WaterMap[m])+1);
310 WaterMap[m][High(WaterMap[m])] := b;
311 ok := True;
312 Break;
313 end;
314 end;
316 g_Game_StepLoading();
317 end;
319 WaterArray := nil;
320 end;
323 var
324 chkTrap_pl: array [0..256] of Integer;
325 chkTrap_mn: array [0..65535] of TMonster;
327 procedure CheckTrap(ID: DWORD; dm: Integer; t: Byte);
328 var
329 //a, b, c, d, i1, i2: Integer;
330 //chkTrap_pl, chkTrap_mn: WArray;
331 plaCount: Integer = 0;
332 mnaCount: Integer = 0;
333 frameId: DWord;
336 function monsWaterCheck (mon: TMonster): Boolean;
337 begin
338 result := false; // don't stop
339 if mon.alive and mon.Collide(gWater[WaterMap[a][c]]) and (not InWArray(monidx, chkTrap_mn)) and (i2 < 1023) then //FIXME
340 begin
341 i2 += 1;
342 chkTrap_mn[i2] := monidx;
343 end;
344 end;
347 function monsWaterCheck (mon: TMonster): Boolean;
348 begin
349 result := false; // don't stop
350 if (mon.trapCheckFrameId <> frameId) then
351 begin
352 mon.trapCheckFrameId := frameId;
353 chkTrap_mn[mnaCount] := mon;
354 Inc(mnaCount);
355 end;
356 end;
358 var
359 a, b, c, d, f: Integer;
360 pan: TPanel;
361 begin
362 if (gWater = nil) or (WaterMap = nil) then Exit;
364 frameId := g_Mons_getNewTrapFrameId();
366 //i1 := -1;
367 //i2 := -1;
369 //SetLength(chkTrap_pl, 1024);
370 //SetLength(chkTrap_mn, 1024);
371 //for d := 0 to 1023 do chkTrap_pl[d] := $FFFF;
372 //for d := 0 to 1023 do chkTrap_mn[d] := $FFFF;
374 for a := 0 to High(WaterMap) do
375 begin
376 for b := 0 to High(WaterMap[a]) do
377 begin
378 pan := gWater[WaterMap[a][b]];
379 if not g_Obj_Collide(pan.X, pan.Y, pan.Width, pan.Height, @Shots[ID].Obj) then continue;
381 for c := 0 to High(WaterMap[a]) do
382 begin
383 pan := gWater[WaterMap[a][c]];
384 for d := 0 to High(gPlayers) do
385 begin
386 if (gPlayers[d] <> nil) and (gPlayers[d].alive) then
387 begin
388 if gPlayers[d].Collide(pan) then
389 begin
390 f := 0;
391 while (f < plaCount) and (chkTrap_pl[f] <> d) do Inc(f);
392 if (f = plaCount) then
393 begin
394 chkTrap_pl[plaCount] := d;
395 Inc(plaCount);
396 if (plaCount = Length(chkTrap_pl)) then break;
397 end;
398 end;
399 end;
400 end;
402 //g_Mons_ForEach(monsWaterCheck);
403 g_Mons_ForEachAliveAt(pan.X, pan.Y, pan.Width, pan.Height, monsWaterCheck);
404 end;
406 for f := 0 to plaCount-1 do gPlayers[chkTrap_pl[f]].Damage(dm, Shots[ID].SpawnerUID, 0, 0, t);
407 for f := 0 to mnaCount-1 do chkTrap_mn[f].Damage(dm, 0, 0, Shots[ID].SpawnerUID, t);
408 end;
409 end;
411 //chkTrap_pl := nil;
412 //chkTrap_mn := nil;
413 end;
415 function HitMonster(m: TMonster; d: Integer; vx, vy: Integer; SpawnerUID: Word; t: Byte): Boolean;
416 var
417 tt, mt: Byte;
418 mon: TMonster;
419 begin
420 Result := False;
422 tt := g_GetUIDType(SpawnerUID);
423 if tt = UID_MONSTER then
424 begin
425 mon := g_Monsters_ByUID(SpawnerUID);
426 if mon <> nil then
427 mt := g_Monsters_ByUID(SpawnerUID).MonsterType
428 else
429 mt := 0;
430 end
431 else
432 mt := 0;
434 if m = nil then Exit;
435 if m.UID = SpawnerUID then
436 begin
437 // Ñàì ñåáÿ ìîæåò ðàíèòü òîëüêî ðàêåòîé è òîêîì:
438 if (t <> HIT_ROCKET) and (t <> HIT_ELECTRO) then
439 Exit;
440 // Êèáåð äåìîí è áî÷êà âîîáùå íå ìîãóò ñåáÿ ðàíèòü:
441 if (m.MonsterType = MONSTER_CYBER) or
442 (m.MonsterType = MONSTER_BARREL) then
443 begin
444 Result := True;
445 Exit;
446 end;
447 end;
449 if tt = UID_MONSTER then
450 begin
451 // Lost_Soul íå ìîæåò ðàíèòü Pain_Elemental'à:
452 if (mt = MONSTER_SOUL) and (m.MonsterType = MONSTER_PAIN) then
453 Exit;
455 // Îáà ìîíñòðà îäíîãî âèäà:
456 if mt = m.MonsterType then
457 case mt of
458 MONSTER_IMP, MONSTER_DEMON, MONSTER_BARON, MONSTER_KNIGHT, MONSTER_CACO,
459 MONSTER_SOUL, MONSTER_MANCUB, MONSTER_SKEL, MONSTER_FISH:
460 Exit; // Ýòè íå áüþò ñâîèõ
461 end;
462 end;
464 if g_Game_IsServer then
465 begin
466 if (t <> HIT_FLAME) or (m.FFireTime = 0) or (vx <> 0) or (vy <> 0) then
467 Result := m.Damage(d, vx, vy, SpawnerUID, t)
468 else
469 Result := (gLMSRespawn = LMS_RESPAWN_NONE); // don't hit monsters when it's warmup time
470 if t = HIT_FLAME then
471 m.CatchFire(SpawnerUID);
472 end
473 else
474 Result := (gLMSRespawn = LMS_RESPAWN_NONE); // don't hit monsters when it's warmup time
475 end;
478 function HitPlayer (p: TPlayer; d: Integer; vx, vy: Integer; SpawnerUID: Word; t: Byte): Boolean;
479 begin
480 result := False;
482 // Ñàì ñåáÿ ìîæåò ðàíèòü òîëüêî ðàêåòîé è òîêîì
483 if (p.UID = SpawnerUID) and (t <> HIT_ROCKET) and (t <> HIT_ELECTRO) then exit;
485 if g_Game_IsServer then
486 begin
487 if (t <> HIT_FLAME) or (p.FFireTime = 0) or (vx <> 0) or (vy <> 0) then p.Damage(d, SpawnerUID, vx, vy, t);
488 if (t = HIT_FLAME) then p.CatchFire(SpawnerUID);
489 end;
491 result := true;
492 end;
495 procedure g_Weapon_BFG9000(X, Y: Integer; SpawnerUID: Word);
497 function monsCheck (mon: TMonster): Boolean;
498 begin
499 result := false; // don't stop
500 if (mon.alive) and (mon.UID <> SpawnerUID) then
501 begin
502 with mon do
503 begin
504 if (g_PatchLength(X, Y, Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
505 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)) <= SHOT_BFG_RADIUS) and
506 g_TraceVector(X, Y, Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
507 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)) then
508 begin
509 if HitMonster(mon, 50, 0, 0, SpawnerUID, HIT_SOME) then mon.BFGHit();
510 end;
511 end;
512 end;
513 end;
515 var
516 i, h: Integer;
517 st: Byte;
518 pl: TPlayer;
519 b: Boolean;
520 begin
521 //g_Sound_PlayEx('SOUND_WEAPON_EXPLODEBFG', 255);
523 h := High(gCorpses);
525 if gAdvCorpses and (h <> -1) then
526 for i := 0 to h do
527 if (gCorpses[i] <> nil) and (gCorpses[i].State <> CORPSE_STATE_REMOVEME) then
528 with gCorpses[i] do
529 if (g_PatchLength(X, Y, Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
530 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)) <= SHOT_BFG_RADIUS) and
531 g_TraceVector(X, Y, Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
532 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)) then
533 begin
534 Damage(50, SpawnerUID, 0, 0);
535 g_Weapon_BFGHit(Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
536 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2));
537 end;
539 st := TEAM_NONE;
540 pl := g_Player_Get(SpawnerUID);
541 if pl <> nil then
542 st := pl.Team;
544 h := High(gPlayers);
546 if h <> -1 then
547 for i := 0 to h do
548 if (gPlayers[i] <> nil) and (gPlayers[i].alive) and (gPlayers[i].UID <> SpawnerUID) then
549 with gPlayers[i] do
550 if (g_PatchLength(X, Y, GameX+PLAYER_RECT.X+(PLAYER_RECT.Width div 2),
551 GameY+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)) <= SHOT_BFG_RADIUS) and
552 g_TraceVector(X, Y, GameX+PLAYER_RECT.X+(PLAYER_RECT.Width div 2),
553 GameY+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)) then
554 begin
555 if (st = TEAM_NONE) or (st <> gPlayers[i].Team) then
556 b := HitPlayer(gPlayers[i], 50, 0, 0, SpawnerUID, HIT_SOME)
557 else
558 b := HitPlayer(gPlayers[i], 25, 0, 0, SpawnerUID, HIT_SOME);
559 if b then
560 gPlayers[i].BFGHit();
561 end;
563 //FIXME
564 g_Mons_ForEachAlive(monsCheck);
565 end;
567 function g_Weapon_CreateShot(I: Integer; ShotType: Byte; Spawner, TargetUID: Word; X, Y, XV, YV: Integer): LongWord;
568 var
569 find_id: DWord;
570 begin
571 if I < 0 then
572 find_id := FindShot()
573 else
574 begin
575 find_id := I;
576 if Integer(find_id) >= High(Shots) then
577 SetLength(Shots, find_id + 64)
578 end;
580 case ShotType of
581 WEAPON_ROCKETLAUNCHER:
582 begin
583 with Shots[find_id] do
584 begin
585 g_Obj_Init(@Obj);
587 Obj.Rect.Width := SHOT_ROCKETLAUNCHER_WIDTH;
588 Obj.Rect.Height := SHOT_ROCKETLAUNCHER_HEIGHT;
590 Animation := nil;
591 Triggers := nil;
592 ShotType := WEAPON_ROCKETLAUNCHER;
593 end;
594 end;
596 WEAPON_PLASMA:
597 begin
598 with Shots[find_id] do
599 begin
600 g_Obj_Init(@Obj);
602 Obj.Rect.Width := SHOT_PLASMA_WIDTH;
603 Obj.Rect.Height := SHOT_PLASMA_HEIGHT;
605 Triggers := nil;
606 ShotType := WEAPON_PLASMA;
607 Animation := TAnimationState.Create(True, 5, 2); // !!! put values into table
608 end;
609 end;
611 WEAPON_BFG:
612 begin
613 with Shots[find_id] do
614 begin
615 g_Obj_Init(@Obj);
617 Obj.Rect.Width := SHOT_BFG_WIDTH;
618 Obj.Rect.Height := SHOT_BFG_HEIGHT;
620 Triggers := nil;
621 ShotType := WEAPON_BFG;
622 Animation := TAnimationState.Create(True, 6, 2); // !!! put values into table
623 end;
624 end;
626 WEAPON_FLAMETHROWER:
627 begin
628 with Shots[find_id] do
629 begin
630 g_Obj_Init(@Obj);
632 Obj.Rect.Width := SHOT_FLAME_WIDTH;
633 Obj.Rect.Height := SHOT_FLAME_HEIGHT;
635 Triggers := nil;
636 ShotType := WEAPON_FLAMETHROWER;
637 // Animation := TAnimationState.Create(True, 6, 0); // drawed as gfx
638 end;
639 end;
641 WEAPON_IMP_FIRE:
642 begin
643 with Shots[find_id] do
644 begin
645 g_Obj_Init(@Obj);
647 Obj.Rect.Width := 16;
648 Obj.Rect.Height := 16;
650 Triggers := nil;
651 ShotType := WEAPON_IMP_FIRE;
652 Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
653 end;
654 end;
656 WEAPON_CACO_FIRE:
657 begin
658 with Shots[find_id] do
659 begin
660 g_Obj_Init(@Obj);
662 Obj.Rect.Width := 16;
663 Obj.Rect.Height := 16;
665 Triggers := nil;
666 ShotType := WEAPON_CACO_FIRE;
667 Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
668 end;
669 end;
671 WEAPON_MANCUB_FIRE:
672 begin
673 with Shots[find_id] do
674 begin
675 g_Obj_Init(@Obj);
677 Obj.Rect.Width := 32;
678 Obj.Rect.Height := 32;
680 Triggers := nil;
681 ShotType := WEAPON_MANCUB_FIRE;
682 Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
683 end;
684 end;
686 WEAPON_BARON_FIRE:
687 begin
688 with Shots[find_id] do
689 begin
690 g_Obj_Init(@Obj);
692 Obj.Rect.Width := 16;
693 Obj.Rect.Height := 16;
695 Triggers := nil;
696 ShotType := WEAPON_BARON_FIRE;
697 Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
698 end;
699 end;
701 WEAPON_BSP_FIRE:
702 begin
703 with Shots[find_id] do
704 begin
705 g_Obj_Init(@Obj);
707 Obj.Rect.Width := 16;
708 Obj.Rect.Height := 16;
710 Triggers := nil;
711 ShotType := WEAPON_BSP_FIRE;
712 Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
713 end;
714 end;
716 WEAPON_SKEL_FIRE:
717 begin
718 with Shots[find_id] do
719 begin
720 g_Obj_Init(@Obj);
722 Obj.Rect.Width := SHOT_SKELFIRE_WIDTH;
723 Obj.Rect.Height := SHOT_SKELFIRE_HEIGHT;
725 Triggers := nil;
726 ShotType := WEAPON_SKEL_FIRE;
727 target := TargetUID;
728 Animation := TAnimationState.Create(True, 5, 2); // !!! put values into table
729 end;
730 end;
731 end;
733 Shots[find_id].Obj.oldX := X;
734 Shots[find_id].Obj.oldY := Y;
735 Shots[find_id].Obj.X := X;
736 Shots[find_id].Obj.Y := Y;
737 Shots[find_id].Obj.Vel.X := XV;
738 Shots[find_id].Obj.Vel.Y := YV;
739 Shots[find_id].Obj.Accel.X := 0;
740 Shots[find_id].Obj.Accel.Y := 0;
741 Shots[find_id].SpawnerUID := Spawner;
742 if (ShotType = WEAPON_FLAMETHROWER) and (XV = 0) and (YV = 0) then
743 Shots[find_id].Stopped := 255
744 else
745 Shots[find_id].Stopped := 0;
746 Result := find_id;
747 end;
749 procedure throw(i, x, y, xd, yd, s: Integer);
750 var
751 a: Integer;
752 begin
753 yd := yd - y;
754 xd := xd - x;
756 a := Max(Abs(xd), Abs(yd));
757 if a = 0 then
758 a := 1;
760 Shots[i].Obj.oldX := x;
761 Shots[i].Obj.oldY := y;
762 Shots[i].Obj.X := x;
763 Shots[i].Obj.Y := y;
764 Shots[i].Obj.Vel.X := (xd*s) div a;
765 Shots[i].Obj.Vel.Y := (yd*s) div a;
766 Shots[i].Obj.Accel.X := 0;
767 Shots[i].Obj.Accel.Y := 0;
768 Shots[i].Stopped := 0;
769 if Shots[i].ShotType in [WEAPON_ROCKETLAUNCHER, WEAPON_BFG] then
770 Shots[i].Timeout := 900 // ~25 sec
771 else
772 begin
773 if Shots[i].ShotType = WEAPON_FLAMETHROWER then
774 Shots[i].Timeout := SHOT_FLAME_LIFETIME
775 else
776 Shots[i].Timeout := 550; // ~15 sec
777 end;
778 end;
780 function g_Weapon_Hit(obj: PObj; d: Integer; SpawnerUID: Word; t: Byte; HitCorpses: Boolean = True): Byte;
781 var
782 i, h: Integer;
784 function PlayerHit(Team: Byte = 0): Boolean;
785 var
786 i: Integer;
787 ChkTeam: Boolean;
788 p: TPlayer;
789 begin
790 Result := False;
791 h := High(gPlayers);
793 if h <> -1 then
794 for i := 0 to h do
795 if (gPlayers[i] <> nil) and gPlayers[i].alive and g_Obj_Collide(obj, @gPlayers[i].Obj) then
796 begin
797 ChkTeam := True;
798 if (Team > 0) and (g_GetUIDType(SpawnerUID) = UID_PLAYER) then
799 begin
800 p := g_Player_Get(SpawnerUID);
801 if p <> nil then
802 ChkTeam := (p.Team = gPlayers[i].Team) xor (Team = 2);
803 end;
804 if ChkTeam then
805 if HitPlayer(gPlayers[i], d, obj^.Vel.X, obj^.Vel.Y, SpawnerUID, t) then
806 begin
807 if t <> HIT_FLAME then
808 gPlayers[i].Push((obj^.Vel.X+obj^.Accel.X)*IfThen(t = HIT_BFG, 8, 1) div 4,
809 (obj^.Vel.Y+obj^.Accel.Y)*IfThen(t = HIT_BFG, 8, 1) div 4);
810 if t = HIT_BFG then
811 g_Game_DelayEvent(DE_BFGHIT, 1000, SpawnerUID);
812 Result := True;
813 break;
814 end;
815 end;
816 end;
819 function monsCheckHit (monidx: Integer; mon: TMonster): Boolean;
820 begin
821 result := false; // don't stop
822 if mon.alive and g_Obj_Collide(obj, @mon.Obj) then
823 begin
824 if HitMonster(mon, d, obj^.Vel.X, obj^.Vel.Y, SpawnerUID, t) then
825 begin
826 if (t <> HIT_FLAME) then
827 begin
828 mon.Push((obj^.Vel.X+obj^.Accel.X)*IfThen(t = HIT_BFG, 8, 1) div 4,
829 (obj^.Vel.Y+obj^.Accel.Y)*IfThen(t = HIT_BFG, 8, 1) div 4);
830 end;
831 result := True;
832 end;
833 end;
834 end;
837 function monsCheckHit (mon: TMonster): Boolean;
838 begin
839 result := false; // don't stop
840 if HitMonster(mon, d, obj.Vel.X, obj.Vel.Y, SpawnerUID, t) then
841 begin
842 if (t <> HIT_FLAME) then
843 begin
844 mon.Push((obj.Vel.X+obj.Accel.X)*IfThen(t = HIT_BFG, 8, 1) div 4,
845 (obj.Vel.Y+obj.Accel.Y)*IfThen(t = HIT_BFG, 8, 1) div 4);
846 end;
847 result := true;
848 end;
849 end;
851 function MonsterHit(): Boolean;
852 begin
853 //result := g_Mons_ForEach(monsCheckHit);
854 //FIXME: accelerate this!
855 result := g_Mons_ForEachAliveAt(obj.X+obj.Rect.X, obj.Y+obj.Rect.Y, obj.Rect.Width, obj.Rect.Height, monsCheckHit);
856 end;
858 begin
859 Result := 0;
861 if HitCorpses then
862 begin
863 h := High(gCorpses);
865 if gAdvCorpses and (h <> -1) then
866 for i := 0 to h do
867 if (gCorpses[i] <> nil) and (gCorpses[i].State <> CORPSE_STATE_REMOVEME) and
868 g_Obj_Collide(obj, @gCorpses[i].Obj) then
869 begin
870 // Ðàñïèëèâàåì òðóï:
871 gCorpses[i].Damage(d, SpawnerUID, (obj^.Vel.X+obj^.Accel.X) div 4,
872 (obj^.Vel.Y+obj^.Accel.Y) div 4);
873 Result := 1;
874 end;
875 end;
877 case gGameSettings.GameMode of
878 // Êàìïàíèÿ:
879 GM_COOP, GM_SINGLE:
880 begin
881 // Ñíà÷àëà áü¸ì ìîíñòðîâ, åñëè åñòü, ïîòîì ïûòàåìñÿ áèòü èãðîêîâ
882 if MonsterHit() then
883 begin
884 Result := 2;
885 Exit;
886 end;
888 // È â êîíöå èãðîêîâ, íî òîëüêî åñëè ïîëîæåíî
889 // (èëè ñíàðÿä îò ìîíñòðà, èëè friendlyfire, èëè friendly_hit_projectile)
890 if (g_GetUIDType(SpawnerUID) <> UID_PLAYER) or
891 LongBool(gGameSettings.Options and (GAME_OPTION_TEAMDAMAGE or GAME_OPTION_TEAMHITPROJECTILE)) then
892 begin
893 if PlayerHit() then
894 begin
895 Result := 1;
896 Exit;
897 end;
898 end;
899 end;
901 // Äåçìàò÷:
902 GM_DM:
903 begin
904 // Ñíà÷àëà áü¸ì èãðîêîâ, åñëè åñòü, ïîòîì ïûòàåìñÿ áèòü ìîíñòðîâ
905 if PlayerHit() then
906 begin
907 Result := 1;
908 Exit;
909 end;
911 if MonsterHit() then
912 begin
913 Result := 2;
914 Exit;
915 end;
916 end;
918 // Êîìàíäíûå:
919 GM_TDM, GM_CTF:
920 begin
921 // Ñíà÷àëà áü¸ì èãðîêîâ êîìàíäû ñîïåðíèêà
922 if PlayerHit(2) then
923 begin
924 Result := 1;
925 Exit;
926 end;
928 // Ïîòîì ìîíñòðîâ
929 if MonsterHit() then
930 begin
931 Result := 2;
932 Exit;
933 end;
935 // È â êîíöå ñâîèõ èãðîêîâ, íî òîëüêî åñëè ïîëîæåíî
936 // (èëè friendlyfire, èëè friendly_hit_projectile)
937 if LongBool(gGameSettings.Options and (GAME_OPTION_TEAMDAMAGE or GAME_OPTION_TEAMHITPROJECTILE)) then
938 begin
939 if PlayerHit(1) then
940 begin
941 Result := 1;
942 Exit;
943 end;
944 end;
945 end;
947 end;
948 end;
950 function g_Weapon_HitUID(UID: Word; d: Integer; SpawnerUID: Word; t: Byte): Boolean;
951 begin
952 Result := False;
954 case g_GetUIDType(UID) of
955 UID_PLAYER: Result := HitPlayer(g_Player_Get(UID), d, 0, 0, SpawnerUID, t);
956 UID_MONSTER: Result := HitMonster(g_Monsters_ByUID(UID), d, 0, 0, SpawnerUID, t);
957 else Exit;
958 end;
959 end;
961 function g_Weapon_Explode(X, Y: Integer; rad: Integer; SpawnerUID: Word): Boolean;
962 var
963 r: Integer; // squared radius
965 function monsExCheck (mon: TMonster): Boolean;
966 var
967 dx, dy, mm: Integer;
968 begin
969 result := false; // don't stop
970 begin
971 dx := mon.Obj.X+mon.Obj.Rect.X+(mon.Obj.Rect.Width div 2)-X;
972 dy := mon.Obj.Y+mon.Obj.Rect.Y+(mon.Obj.Rect.Height div 2)-Y;
974 if dx > 1000 then dx := 1000;
975 if dy > 1000 then dy := 1000;
977 if (dx*dx+dy*dy < r) then
978 begin
979 //m := PointToRect(X, Y, Obj.X+Obj.Rect.X, Obj.Y+Obj.Rect.Y, Obj.Rect.Width, Obj.Rect.Height);
980 //e_WriteLog(Format('explo monster #%d: x=%d; y=%d; rad=%d; dx=%d; dy=%d', [monidx, X, Y, rad, dx, dy]), MSG_NOTIFY);
982 mm := Max(abs(dx), abs(dy));
983 if mm = 0 then mm := 1;
985 if mon.alive then
986 begin
987 HitMonster(mon, ((mon.Obj.Rect.Width div 4)*10*(rad-mm)) div rad, 0, 0, SpawnerUID, HIT_ROCKET);
988 end;
990 mon.Push((dx*7) div mm, (dy*7) div mm);
991 end;
992 end;
993 end;
995 var i, h, dx, dy, m, mm: Integer;
996 {$IFDEF ENABLE_GIBS}
997 var _angle: SmallInt;
998 {$ENDIF}
999 begin
1000 result := false;
1002 g_Triggers_PressC(X, Y, rad, SpawnerUID, ACTIVATE_SHOT);
1004 r := rad*rad;
1006 h := High(gPlayers);
1008 if h <> -1 then
1009 for i := 0 to h do
1010 if (gPlayers[i] <> nil) and gPlayers[i].alive then
1011 with gPlayers[i] do
1012 begin
1013 dx := Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2)-X;
1014 dy := Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)-Y;
1016 if dx > 1000 then dx := 1000;
1017 if dy > 1000 then dy := 1000;
1019 if dx*dx+dy*dy < r then
1020 begin
1021 //m := PointToRect(X, Y, GameX+PLAYER_RECT.X, GameY+PLAYER_RECT.Y,
1022 // PLAYER_RECT.Width, PLAYER_RECT.Height);
1024 mm := Max(abs(dx), abs(dy));
1025 if mm = 0 then mm := 1;
1027 HitPlayer(gPlayers[i], (100*(rad-mm)) div rad, (dx*10) div mm, (dy*10) div mm, SpawnerUID, HIT_ROCKET);
1028 gPlayers[i].Push((dx*7) div mm, (dy*7) div mm);
1029 end;
1030 end;
1032 //g_Mons_ForEach(monsExCheck);
1033 g_Mons_ForEachAt(X-(rad+32), Y-(rad+32), (rad+32)*2, (rad+32)*2, monsExCheck);
1035 h := High(gCorpses);
1037 if gAdvCorpses and (h <> -1) then
1038 for i := 0 to h do
1039 if (gCorpses[i] <> nil) and (gCorpses[i].State <> CORPSE_STATE_REMOVEME) then
1040 with gCorpses[i] do
1041 begin
1042 dx := Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2)-X;
1043 dy := Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)-Y;
1045 if dx > 1000 then dx := 1000;
1046 if dy > 1000 then dy := 1000;
1048 if dx*dx+dy*dy < r then
1049 begin
1050 m := PointToRect(X, Y, Obj.X+Obj.Rect.X, Obj.Y+Obj.Rect.Y,
1051 Obj.Rect.Width, Obj.Rect.Height);
1053 mm := Max(abs(dx), abs(dy));
1054 if mm = 0 then mm := 1;
1056 Damage(Round(100*(rad-m)/rad), SpawnerUID, (dx*10) div mm, (dy*10) div mm);
1057 end;
1058 end;
1060 {$IFDEF ENABLE_GIBS}
1061 h := High(gGibs);
1062 if gAdvGibs and (h <> -1) then
1063 for i := 0 to h do
1064 if gGibs[i].alive then
1065 with gGibs[i] do
1066 begin
1067 dx := Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2)-X;
1068 dy := Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)-Y;
1069 if dx > 1000 then dx := 1000;
1070 if dy > 1000 then dy := 1000;
1071 if dx*dx+dy*dy < r then
1072 begin
1073 m := PointToRect(X, Y, Obj.X+Obj.Rect.X, Obj.Y+Obj.Rect.Y,
1074 Obj.Rect.Width, Obj.Rect.Height);
1075 _angle := GetAngle(Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
1076 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2), X, Y);
1077 g_Obj_PushA(@Obj, Round(15*(rad-m)/rad), _angle);
1078 positionChanged(); // this updates spatial accelerators
1079 end;
1080 end;
1081 {$ENDIF}
1082 end;
1084 procedure g_Weapon_Init();
1085 begin
1086 CreateWaterMap();
1087 end;
1089 procedure g_Weapon_Free();
1090 var
1091 i: Integer;
1092 begin
1093 if Shots <> nil then
1094 begin
1095 for i := 0 to High(Shots) do
1096 if Shots[i].ShotType <> 0 then
1097 Shots[i].Animation.Free();
1099 Shots := nil;
1100 end;
1102 WaterMap := nil;
1103 end;
1105 procedure g_Weapon_LoadData();
1106 begin
1107 e_WriteLog('Loading weapons data...', TMsgType.Notify);
1109 g_Sound_CreateWADEx('SOUND_WEAPON_HITPUNCH', GameWAD+':SOUNDS\HITPUNCH');
1110 g_Sound_CreateWADEx('SOUND_WEAPON_MISSPUNCH', GameWAD+':SOUNDS\MISSPUNCH');
1111 g_Sound_CreateWADEx('SOUND_WEAPON_HITBERSERK', GameWAD+':SOUNDS\HITBERSERK');
1112 g_Sound_CreateWADEx('SOUND_WEAPON_MISSBERSERK', GameWAD+':SOUNDS\MISSBERSERK');
1113 g_Sound_CreateWADEx('SOUND_WEAPON_SELECTSAW', GameWAD+':SOUNDS\SELECTSAW');
1114 g_Sound_CreateWADEx('SOUND_WEAPON_IDLESAW', GameWAD+':SOUNDS\IDLESAW');
1115 g_Sound_CreateWADEx('SOUND_WEAPON_HITSAW', GameWAD+':SOUNDS\HITSAW');
1116 g_Sound_CreateWADEx('SOUND_WEAPON_FIRESHOTGUN2', GameWAD+':SOUNDS\FIRESHOTGUN2');
1117 g_Sound_CreateWADEx('SOUND_WEAPON_FIRESHOTGUN', GameWAD+':SOUNDS\FIRESHOTGUN');
1118 g_Sound_CreateWADEx('SOUND_WEAPON_FIRESAW', GameWAD+':SOUNDS\FIRESAW');
1119 g_Sound_CreateWADEx('SOUND_WEAPON_FIREROCKET', GameWAD+':SOUNDS\FIREROCKET');
1120 g_Sound_CreateWADEx('SOUND_WEAPON_FIREPLASMA', GameWAD+':SOUNDS\FIREPLASMA');
1121 g_Sound_CreateWADEx('SOUND_WEAPON_FIREPISTOL', GameWAD+':SOUNDS\FIREPISTOL');
1122 g_Sound_CreateWADEx('SOUND_WEAPON_FIRECGUN', GameWAD+':SOUNDS\FIRECGUN');
1123 g_Sound_CreateWADEx('SOUND_WEAPON_FIREBFG', GameWAD+':SOUNDS\FIREBFG');
1124 g_Sound_CreateWADEx('SOUND_FIRE', GameWAD+':SOUNDS\FIRE');
1125 g_Sound_CreateWADEx('SOUND_IGNITE', GameWAD+':SOUNDS\IGNITE');
1126 g_Sound_CreateWADEx('SOUND_WEAPON_STARTFIREBFG', GameWAD+':SOUNDS\STARTFIREBFG');
1127 g_Sound_CreateWADEx('SOUND_WEAPON_EXPLODEROCKET', GameWAD+':SOUNDS\EXPLODEROCKET');
1128 g_Sound_CreateWADEx('SOUND_WEAPON_EXPLODEBFG', GameWAD+':SOUNDS\EXPLODEBFG');
1129 g_Sound_CreateWADEx('SOUND_WEAPON_BFGWATER', GameWAD+':SOUNDS\BFGWATER');
1130 g_Sound_CreateWADEx('SOUND_WEAPON_EXPLODEPLASMA', GameWAD+':SOUNDS\EXPLODEPLASMA');
1131 g_Sound_CreateWADEx('SOUND_WEAPON_PLASMAWATER', GameWAD+':SOUNDS\PLASMAWATER');
1132 g_Sound_CreateWADEx('SOUND_WEAPON_FIREBALL', GameWAD+':SOUNDS\FIREBALL');
1133 g_Sound_CreateWADEx('SOUND_WEAPON_EXPLODEBALL', GameWAD+':SOUNDS\EXPLODEBALL');
1134 g_Sound_CreateWADEx('SOUND_WEAPON_FIREREV', GameWAD+':SOUNDS\FIREREV');
1135 g_Sound_CreateWADEx('SOUND_WEAPON_FLAMEON', GameWAD+':SOUNDS\STARTFLM');
1136 g_Sound_CreateWADEx('SOUND_WEAPON_FLAMEOFF', GameWAD+':SOUNDS\STOPFLM');
1137 g_Sound_CreateWADEx('SOUND_WEAPON_FLAMEWORK', GameWAD+':SOUNDS\WORKFLM');
1138 g_Sound_CreateWADEx('SOUND_PLAYER_JETFLY', GameWAD+':SOUNDS\WORKJETPACK');
1139 g_Sound_CreateWADEx('SOUND_PLAYER_JETON', GameWAD+':SOUNDS\STARTJETPACK');
1140 g_Sound_CreateWADEx('SOUND_PLAYER_JETOFF', GameWAD+':SOUNDS\STOPJETPACK');
1141 g_Sound_CreateWADEx('SOUND_PLAYER_CASING1', GameWAD+':SOUNDS\CASING1');
1142 g_Sound_CreateWADEx('SOUND_PLAYER_CASING2', GameWAD+':SOUNDS\CASING2');
1143 g_Sound_CreateWADEx('SOUND_PLAYER_SHELL1', GameWAD+':SOUNDS\SHELL1');
1144 g_Sound_CreateWADEx('SOUND_PLAYER_SHELL2', GameWAD+':SOUNDS\SHELL2');
1146 //wgunMonHash := hashNewIntInt();
1147 wgunHitHeap := TBinaryHeapHitTimes.Create();
1148 end;
1150 procedure g_Weapon_FreeData();
1151 begin
1152 e_WriteLog('Releasing weapons data...', TMsgType.Notify);
1154 g_Sound_Delete('SOUND_WEAPON_HITPUNCH');
1155 g_Sound_Delete('SOUND_WEAPON_MISSPUNCH');
1156 g_Sound_Delete('SOUND_WEAPON_HITBERSERK');
1157 g_Sound_Delete('SOUND_WEAPON_MISSBERSERK');
1158 g_Sound_Delete('SOUND_WEAPON_SELECTSAW');
1159 g_Sound_Delete('SOUND_WEAPON_IDLESAW');
1160 g_Sound_Delete('SOUND_WEAPON_HITSAW');
1161 g_Sound_Delete('SOUND_WEAPON_FIRESHOTGUN2');
1162 g_Sound_Delete('SOUND_WEAPON_FIRESHOTGUN');
1163 g_Sound_Delete('SOUND_WEAPON_FIRESAW');
1164 g_Sound_Delete('SOUND_WEAPON_FIREROCKET');
1165 g_Sound_Delete('SOUND_WEAPON_FIREPLASMA');
1166 g_Sound_Delete('SOUND_WEAPON_FIREPISTOL');
1167 g_Sound_Delete('SOUND_WEAPON_FIRECGUN');
1168 g_Sound_Delete('SOUND_WEAPON_FIREBFG');
1169 g_Sound_Delete('SOUND_FIRE');
1170 g_Sound_Delete('SOUND_IGNITE');
1171 g_Sound_Delete('SOUND_WEAPON_STARTFIREBFG');
1172 g_Sound_Delete('SOUND_WEAPON_EXPLODEROCKET');
1173 g_Sound_Delete('SOUND_WEAPON_EXPLODEBFG');
1174 g_Sound_Delete('SOUND_WEAPON_BFGWATER');
1175 g_Sound_Delete('SOUND_WEAPON_EXPLODEPLASMA');
1176 g_Sound_Delete('SOUND_WEAPON_PLASMAWATER');
1177 g_Sound_Delete('SOUND_WEAPON_FIREBALL');
1178 g_Sound_Delete('SOUND_WEAPON_EXPLODEBALL');
1179 g_Sound_Delete('SOUND_WEAPON_FIREREV');
1180 g_Sound_Delete('SOUND_WEAPON_FLAMEON');
1181 g_Sound_Delete('SOUND_WEAPON_FLAMEOFF');
1182 g_Sound_Delete('SOUND_WEAPON_FLAMEWORK');
1183 g_Sound_Delete('SOUND_PLAYER_JETFLY');
1184 g_Sound_Delete('SOUND_PLAYER_JETON');
1185 g_Sound_Delete('SOUND_PLAYER_JETOFF');
1186 g_Sound_Delete('SOUND_PLAYER_CASING1');
1187 g_Sound_Delete('SOUND_PLAYER_CASING2');
1188 g_Sound_Delete('SOUND_PLAYER_SHELL1');
1189 g_Sound_Delete('SOUND_PLAYER_SHELL2');
1190 end;
1193 function GunHitPlayer (X, Y: Integer; vx, vy: Integer; dmg: Integer; SpawnerUID: Word; AllowPush: Boolean): Boolean;
1194 var
1195 i: Integer;
1196 begin
1197 result := false;
1198 for i := 0 to High(gPlayers) do
1199 begin
1200 if (gPlayers[i] <> nil) and gPlayers[i].alive and gPlayers[i].Collide(X, Y) then
1201 begin
1202 if HitPlayer(gPlayers[i], dmg, vx*10, vy*10-3, SpawnerUID, HIT_SOME) then
1203 begin
1204 if AllowPush then gPlayers[i].Push(vx, vy);
1205 result := true;
1206 end;
1207 end;
1208 end;
1209 end;
1212 function GunHit (X, Y: Integer; vx, vy: Integer; dmg: Integer; SpawnerUID: Word; AllowPush: Boolean): Byte;
1214 function monsCheck (mon: TMonster): Boolean;
1215 begin
1216 result := false; // don't stop
1217 if HitMonster(mon, dmg, vx*10, vy*10-3, SpawnerUID, HIT_SOME) then
1218 begin
1219 if AllowPush then mon.Push(vx, vy);
1220 result := true;
1221 end;
1222 end;
1224 begin
1225 result := 0;
1226 if GunHitPlayer(X, Y, vx, vy, dmg, SpawnerUID, AllowPush) then result := 1
1227 else if g_Mons_ForEachAliveAt(X, Y, 1, 1, monsCheck) then result := 2;
1228 end;
1231 (*
1232 procedure g_Weapon_gunOld(const x, y, xd, yd, v, dmg: Integer; SpawnerUID: Word; CheckTrigger: Boolean);
1233 var
1234 a: Integer;
1235 x2, y2: Integer;
1236 dx, dy: Integer;
1237 xe, ye: Integer;
1238 xi, yi: Integer;
1239 s, c: Extended;
1240 //vx, vy: Integer;
1241 xx, yy, d: Integer;
1242 i: Integer;
1243 t1, _collide: Boolean;
1244 w, h: Word;
1245 {$IF DEFINED(D2F_DEBUG)}
1246 stt: UInt64;
1247 showTime: Boolean = true;
1248 {$ENDIF}
1249 begin
1250 a := GetAngle(x, y, xd, yd)+180;
1252 SinCos(DegToRad(-a), s, c);
1254 if Abs(s) < 0.01 then s := 0;
1255 if Abs(c) < 0.01 then c := 0;
1257 x2 := x+Round(c*gMapInfo.Width);
1258 y2 := y+Round(s*gMapInfo.Width);
1260 t1 := gWalls <> nil;
1261 _collide := False;
1262 w := gMapInfo.Width;
1263 h := gMapInfo.Height;
1265 xe := 0;
1266 ye := 0;
1267 dx := x2-x;
1268 dy := y2-y;
1270 if (xd = 0) and (yd = 0) then Exit;
1272 if dx > 0 then xi := 1 else if dx < 0 then xi := -1 else xi := 0;
1273 if dy > 0 then yi := 1 else if dy < 0 then yi := -1 else yi := 0;
1275 dx := Abs(dx);
1276 dy := Abs(dy);
1278 if dx > dy then d := dx else d := dy;
1280 //blood vel, for Monster.Damage()
1281 //vx := (dx*10 div d)*xi;
1282 //vy := (dy*10 div d)*yi;
1284 {$IF DEFINED(D2F_DEBUG)}
1285 stt := getTimeMicro();
1286 {$ENDIF}
1288 xx := x;
1289 yy := y;
1291 for i := 1 to d do
1292 begin
1293 xe := xe+dx;
1294 ye := ye+dy;
1296 if xe > d then
1297 begin
1298 xe := xe-d;
1299 xx := xx+xi;
1300 end;
1302 if ye > d then
1303 begin
1304 ye := ye-d;
1305 yy := yy+yi;
1306 end;
1308 if (yy > h) or (yy < 0) then Break;
1309 if (xx > w) or (xx < 0) then Break;
1311 if t1 then
1312 if ByteBool(gCollideMap[yy, xx] and MARK_BLOCKED) then
1313 begin
1314 _collide := True;
1315 {$IF DEFINED(D2F_DEBUG)}
1316 stt := getTimeMicro()-stt;
1317 e_WriteLog(Format('*** old trace time: %u microseconds', [LongWord(stt)]), MSG_NOTIFY);
1318 showTime := false;
1319 {$ENDIF}
1320 g_GFX_Spark(xx-xi, yy-yi, 2+Random(2), 180+a, 0, 0);
1321 if g_Game_IsServer and g_Game_IsNet then
1322 MH_SEND_Effect(xx-xi, yy-yi, 180+a, NET_GFX_SPARK);
1323 end;
1325 if not _collide then
1326 begin
1327 _collide := GunHit(xx, yy, xi*v, yi*v, dmg, SpawnerUID, v <> 0) <> 0;
1328 end;
1330 if _collide then Break;
1331 end;
1333 {$IF DEFINED(D2F_DEBUG)}
1334 if showTime then
1335 begin
1336 stt := getTimeMicro()-stt;
1337 e_WriteLog(Format('*** old trace time: %u microseconds', [LongWord(stt)]), MSG_NOTIFY);
1338 end;
1339 {$ENDIF}
1341 if CheckTrigger and g_Game_IsServer then
1342 g_Triggers_PressL(X, Y, xx-xi, yy-yi, SpawnerUID, ACTIVATE_SHOT);
1343 end;
1344 *)
1347 //!!!FIXME!!!
1348 procedure g_Weapon_gun (const x, y, xd, yd, v, indmg: Integer; SpawnerUID: Word; CheckTrigger: Boolean);
1349 var
1350 x0, y0: Integer;
1351 x2, y2: Integer;
1352 xi, yi: Integer;
1353 wallDistSq: Integer = $3fffffff;
1354 spawnerPlr: TPlayer = nil;
1355 dmg: Integer;
1357 function doPlayerHit (idx: Integer; hx, hy: Integer): Boolean;
1358 begin
1359 result := false;
1360 if (idx < 0) or (idx > High(gPlayers)) then exit;
1361 if (gPlayers[idx] = nil) or not gPlayers[idx].alive then exit;
1362 if (spawnerPlr <> nil) then
1363 begin
1364 if ((gGameSettings.Options and (GAME_OPTION_TEAMHITTRACE or GAME_OPTION_TEAMDAMAGE)) = 0) and
1365 (spawnerPlr.Team <> TEAM_NONE) and (spawnerPlr.Team = gPlayers[idx].Team) then
1366 begin
1367 if (spawnerPlr <> gPlayers[idx]) and ((gGameSettings.Options and GAME_OPTION_TEAMABSORBDAMAGE) = 0) then
1368 dmg := Max(1, dmg div 2);
1369 exit;
1370 end;
1371 end;
1372 result := HitPlayer(gPlayers[idx], dmg, (xi*v)*10, (yi*v)*10-3, SpawnerUID, HIT_SOME);
1373 if result and (v <> 0) then gPlayers[idx].Push((xi*v), (yi*v));
1374 {$IF DEFINED(D2F_DEBUG)}
1375 //if result then e_WriteLog(Format(' PLAYER #%d HIT', [idx]), MSG_NOTIFY);
1376 {$ENDIF}
1377 end;
1379 function doMonsterHit (mon: TMonster; hx, hy: Integer): Boolean;
1380 begin
1381 result := false;
1382 if (mon = nil) then exit;
1383 result := HitMonster(mon, dmg, (xi*v)*10, (yi*v)*10-3, SpawnerUID, HIT_SOME);
1384 if result and (v <> 0) then mon.Push((xi*v), (yi*v));
1385 {$IF DEFINED(D2F_DEBUG)}
1386 //if result then e_WriteLog(Format(' MONSTER #%u HIT', [LongWord(mon.UID)]), MSG_NOTIFY);
1387 {$ENDIF}
1388 end;
1390 // collect players along hitray
1391 // return `true` if instant hit was detected
1392 function playerPossibleHit (): Boolean;
1393 var
1394 i: Integer;
1395 px, py, pw, ph: Integer;
1396 inx, iny: Integer;
1397 distSq: Integer;
1398 plr: TPlayer;
1399 begin
1400 result := false;
1401 for i := 0 to High(gPlayers) do
1402 begin
1403 plr := gPlayers[i];
1404 if (plr <> nil) and plr.alive then
1405 begin
1406 plr.getMapBox(px, py, pw, ph);
1407 if lineAABBIntersects(x, y, x2, y2, px, py, pw, ph, inx, iny) then
1408 begin
1409 distSq := distanceSq(x, y, inx, iny);
1410 if (distSq = 0) then
1411 begin
1412 // contains
1413 if doPlayerHit(i, x, y) then begin result := true; exit; end;
1414 end
1415 else if (distSq < wallDistSq) then
1416 begin
1417 appendHitTimePlr(distSq, i, inx, iny);
1418 end;
1419 end;
1420 end;
1421 end;
1422 end;
1424 procedure sqchecker (mon: TMonster);
1425 var
1426 mx, my, mw, mh: Integer;
1427 inx, iny: Integer;
1428 distSq: Integer;
1429 begin
1430 mon.getMapBox(mx, my, mw, mh);
1431 if lineAABBIntersects(x0, y0, x2, y2, mx, my, mw, mh, inx, iny) then
1432 begin
1433 distSq := distanceSq(x0, y0, inx, iny);
1434 if (distSq < wallDistSq) then appendHitTimeMon(distSq, mon, inx, iny);
1435 end;
1436 end;
1438 var
1439 a: Integer;
1440 dx, dy: Integer;
1441 xe, ye: Integer;
1442 s, c: Extended;
1443 i: Integer;
1444 wallHitFlag: Boolean = false;
1445 wallHitX: Integer = 0;
1446 wallHitY: Integer = 0;
1447 didHit: Boolean = false;
1448 {$IF DEFINED(D2F_DEBUG)}
1449 stt: UInt64;
1450 {$ENDIF}
1451 mit: PMonster;
1452 it: TMonsterGrid.Iter;
1453 begin
1454 (*
1455 if not gwep_debug_fast_trace then
1456 begin
1457 g_Weapon_gunOld(x, y, xd, yd, v, dmg, SpawnerUID, CheckTrigger);
1458 exit;
1459 end;
1460 *)
1462 if (xd = 0) and (yd = 0) then exit;
1464 if (g_GetUIDType(SpawnerUID) = UID_PLAYER) then
1465 spawnerPlr := g_Player_Get(SpawnerUID);
1467 dmg := indmg;
1469 //wgunMonHash.reset(); //FIXME: clear hash on level change
1470 wgunHitHeap.clear();
1471 wgunHitTimeUsed := 0;
1473 a := GetAngle(x, y, xd, yd)+180;
1475 SinCos(DegToRad(-a), s, c);
1477 if Abs(s) < 0.01 then s := 0;
1478 if Abs(c) < 0.01 then c := 0;
1480 x0 := x;
1481 y0 := y;
1482 x2 := x+Round(c*gMapInfo.Width);
1483 y2 := y+Round(s*gMapInfo.Width);
1485 dx := x2-x;
1486 dy := y2-y;
1488 if (dx > 0) then xi := 1 else if (dx < 0) then xi := -1 else xi := 0;
1489 if (dy > 0) then yi := 1 else if (dy < 0) then yi := -1 else yi := 0;
1491 {$IF DEFINED(D2F_DEBUG)}
1492 e_WriteLog(Format('GUN TRACE: (%d,%d) to (%d,%d)', [x, y, x2, y2]), TMsgType.Notify);
1493 stt := getTimeMicro();
1494 {$ENDIF}
1496 wallHitFlag := (g_Map_traceToNearestWall(x, y, x2, y2, @wallHitX, @wallHitY) <> nil);
1497 if wallHitFlag then
1498 begin
1499 x2 := wallHitX;
1500 y2 := wallHitY;
1501 wallDistSq := distanceSq(x, y, wallHitX, wallHitY);
1502 end
1503 else
1504 begin
1505 wallHitX := x2;
1506 wallHitY := y2;
1507 end;
1509 if playerPossibleHit() then exit; // instant hit
1511 // collect monsters
1512 //g_Mons_AlongLine(x, y, x2, y2, sqchecker);
1514 it := monsGrid.forEachAlongLine(x, y, x2, y2, -1);
1515 for mit in it do sqchecker(mit^);
1516 it.release();
1518 // here, we collected all monsters and players in `wgunHitHeap` and `wgunHitTime`
1519 // also, if `wallWasHit` is `true`, then `wallHitX` and `wallHitY` contains spark coords
1520 while (wgunHitHeap.count > 0) do
1521 begin
1522 // has some entities to check, do it
1523 i := wgunHitHeap.front;
1524 wgunHitHeap.popFront();
1525 // hitpoint
1526 xe := wgunHitTime[i].x;
1527 ye := wgunHitTime[i].y;
1528 // check if it is not behind the wall
1529 if (wgunHitTime[i].mon <> nil) then
1530 begin
1531 didHit := doMonsterHit(wgunHitTime[i].mon, xe, ye);
1532 end
1533 else
1534 begin
1535 didHit := doPlayerHit(wgunHitTime[i].plridx, xe, ye);
1536 end;
1537 if didHit then
1538 begin
1539 // need new coords for trigger
1540 wallHitX := xe;
1541 wallHitY := ye;
1542 wallHitFlag := false; // no sparks
1543 break;
1544 end;
1545 end;
1547 // need sparks?
1548 if wallHitFlag then
1549 begin
1550 {$IF DEFINED(D2F_DEBUG)}
1551 stt := getTimeMicro()-stt;
1552 e_WriteLog(Format('*** new trace time: %u microseconds', [LongWord(stt)]), TMsgType.Notify);
1553 {$ENDIF}
1554 {$IFDEF ENABLE_GFX}
1555 g_GFX_Spark(wallHitX, wallHitY, 2+Random(2), 180+a, 0, 0);
1556 {$ENDIF}
1557 if g_Game_IsServer and g_Game_IsNet then MH_SEND_Effect(wallHitX, wallHitY, 180+a, NET_GFX_SPARK);
1558 end
1559 else
1560 begin
1561 {$IF DEFINED(D2F_DEBUG)}
1562 stt := getTimeMicro()-stt;
1563 e_WriteLog(Format('*** new trace time: %u microseconds', [LongWord(stt)]), TMsgType.Notify);
1564 {$ENDIF}
1565 end;
1567 if CheckTrigger and g_Game_IsServer then g_Triggers_PressL(X, Y, wallHitX, wallHitY, SpawnerUID, ACTIVATE_SHOT);
1568 end;
1571 procedure g_Weapon_punch(x, y: Integer; d, SpawnerUID: Word);
1572 var
1573 obj: TObj;
1574 begin
1575 obj.X := X;
1576 obj.Y := Y;
1577 obj.rect.X := 0;
1578 obj.rect.Y := 0;
1579 obj.rect.Width := 39;
1580 obj.rect.Height := 52;
1581 obj.Vel.X := 0;
1582 obj.Vel.Y := 0;
1583 obj.Accel.X := 0;
1584 obj.Accel.Y := 0;
1586 if g_Weapon_Hit(@obj, d, SpawnerUID, HIT_SOME) <> 0 then
1587 g_Sound_PlayExAt('SOUND_WEAPON_HITPUNCH', x, y)
1588 else
1589 g_Sound_PlayExAt('SOUND_WEAPON_MISSPUNCH', x, y);
1590 end;
1592 function g_Weapon_chainsaw(x, y: Integer; d, SpawnerUID: Word): Integer;
1593 var
1594 obj: TObj;
1595 begin
1596 obj.X := X;
1597 obj.Y := Y;
1598 obj.rect.X := 0;
1599 obj.rect.Y := 0;
1600 obj.rect.Width := 32;
1601 obj.rect.Height := 52;
1602 obj.Vel.X := 0;
1603 obj.Vel.Y := 0;
1604 obj.Accel.X := 0;
1605 obj.Accel.Y := 0;
1607 Result := g_Weapon_Hit(@obj, d, SpawnerUID, HIT_SOME);
1608 end;
1610 procedure g_Weapon_rocket(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1611 Silent: Boolean = False; compat: Boolean = true);
1612 var
1613 find_id: DWORD;
1614 dx, dy: Integer;
1615 begin
1616 if WID < 0 then
1617 find_id := FindShot()
1618 else
1619 begin
1620 find_id := WID;
1621 if Integer(find_id) >= High(Shots) then
1622 SetLength(Shots, find_id + 64)
1623 end;
1625 with Shots[find_id] do
1626 begin
1627 g_Obj_Init(@Obj);
1629 Obj.Rect.Width := SHOT_ROCKETLAUNCHER_WIDTH;
1630 Obj.Rect.Height := SHOT_ROCKETLAUNCHER_HEIGHT;
1632 if compat then
1633 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1634 else
1635 dx := -(Obj.Rect.Width div 2);
1636 dy := -(Obj.Rect.Height div 2);
1638 ShotType := WEAPON_ROCKETLAUNCHER;
1639 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 12);
1641 Animation := nil;
1642 triggers := nil;
1643 end;
1645 Shots[find_id].SpawnerUID := SpawnerUID;
1647 if not Silent then
1648 g_Sound_PlayExAt('SOUND_WEAPON_FIREROCKET', x, y);
1649 end;
1651 procedure g_Weapon_revf(x, y, xd, yd: Integer; SpawnerUID, TargetUID: Word;
1652 WID: Integer = -1; Silent: Boolean = False);
1653 var
1654 find_id: DWORD;
1655 dx, dy: Integer;
1656 begin
1657 if WID < 0 then
1658 find_id := FindShot()
1659 else
1660 begin
1661 find_id := WID;
1662 if Integer(find_id) >= High(Shots) then
1663 SetLength(Shots, find_id + 64)
1664 end;
1666 with Shots[find_id] do
1667 begin
1668 g_Obj_Init(@Obj);
1670 Obj.Rect.Width := SHOT_SKELFIRE_WIDTH;
1671 Obj.Rect.Height := SHOT_SKELFIRE_HEIGHT;
1673 dx := -(Obj.Rect.Width div 2);
1674 dy := -(Obj.Rect.Height div 2);
1676 ShotType := WEAPON_SKEL_FIRE;
1677 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 12);
1679 triggers := nil;
1680 target := TargetUID;
1681 Animation := TAnimationState.Create(True, 5, 2); // !!! put values into table
1682 end;
1684 Shots[find_id].SpawnerUID := SpawnerUID;
1686 if not Silent then
1687 g_Sound_PlayExAt('SOUND_WEAPON_FIREREV', x, y);
1688 end;
1690 procedure g_Weapon_plasma(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1691 Silent: Boolean = False; compat: Boolean = true);
1692 var
1693 find_id: DWORD;
1694 dx, dy: Integer;
1695 begin
1696 if WID < 0 then
1697 find_id := FindShot()
1698 else
1699 begin
1700 find_id := WID;
1701 if Integer(find_id) >= High(Shots) then
1702 SetLength(Shots, find_id + 64);
1703 end;
1705 with Shots[find_id] do
1706 begin
1707 g_Obj_Init(@Obj);
1709 Obj.Rect.Width := SHOT_PLASMA_WIDTH;
1710 Obj.Rect.Height := SHOT_PLASMA_HEIGHT;
1712 if compat then
1713 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1714 else
1715 dx := -(Obj.Rect.Width div 2);
1716 dy := -(Obj.Rect.Height div 2);
1718 ShotType := WEAPON_PLASMA;
1719 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1721 triggers := nil;
1722 Animation := TAnimationState.Create(True, 5, 2); // !!! put values into table
1723 end;
1725 Shots[find_id].SpawnerUID := SpawnerUID;
1727 if not Silent then
1728 g_Sound_PlayExAt('SOUND_WEAPON_FIREPLASMA', x, y);
1729 end;
1731 procedure g_Weapon_flame(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1732 Silent: Boolean = False; compat: Boolean = true);
1733 var
1734 find_id: DWORD;
1735 dx, dy: Integer;
1736 begin
1737 if WID < 0 then
1738 find_id := FindShot()
1739 else
1740 begin
1741 find_id := WID;
1742 if Integer(find_id) >= High(Shots) then
1743 SetLength(Shots, find_id + 64);
1744 end;
1746 with Shots[find_id] do
1747 begin
1748 g_Obj_Init(@Obj);
1750 Obj.Rect.Width := SHOT_FLAME_WIDTH;
1751 Obj.Rect.Height := SHOT_FLAME_HEIGHT;
1753 if compat then
1754 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1755 else
1756 dx := -(Obj.Rect.Width div 2);
1757 dy := -(Obj.Rect.Height div 2);
1759 ShotType := WEAPON_FLAMETHROWER;
1760 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1762 triggers := nil;
1763 Animation := nil;
1764 end;
1766 Shots[find_id].SpawnerUID := SpawnerUID;
1768 // if not Silent then
1769 // g_Sound_PlayExAt('SOUND_WEAPON_FIREPLASMA', x, y);
1770 end;
1772 procedure g_Weapon_ball1(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1773 Silent: Boolean = False; compat: Boolean = true);
1774 var
1775 find_id: DWORD;
1776 dx, dy: Integer;
1777 begin
1778 if WID < 0 then
1779 find_id := FindShot()
1780 else
1781 begin
1782 find_id := WID;
1783 if Integer(find_id) >= High(Shots) then
1784 SetLength(Shots, find_id + 64)
1785 end;
1787 with Shots[find_id] do
1788 begin
1789 g_Obj_Init(@Obj);
1791 Obj.Rect.Width := 16;
1792 Obj.Rect.Height := 16;
1794 if compat then
1795 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1796 else
1797 dx := -(Obj.Rect.Width div 2);
1798 dy := -(Obj.Rect.Height div 2);
1800 ShotType := WEAPON_IMP_FIRE;
1801 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1803 triggers := nil;
1804 Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
1805 end;
1807 Shots[find_id].SpawnerUID := SpawnerUID;
1809 if not Silent then
1810 g_Sound_PlayExAt('SOUND_WEAPON_FIREBALL', x, y);
1811 end;
1813 procedure g_Weapon_ball2(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1814 Silent: Boolean = False; compat: Boolean = true);
1815 var
1816 find_id: DWORD;
1817 dx, dy: Integer;
1818 begin
1819 if WID < 0 then
1820 find_id := FindShot()
1821 else
1822 begin
1823 find_id := WID;
1824 if Integer(find_id) >= High(Shots) then
1825 SetLength(Shots, find_id + 64)
1826 end;
1828 with Shots[find_id] do
1829 begin
1830 g_Obj_Init(@Obj);
1832 Obj.Rect.Width := 16;
1833 Obj.Rect.Height := 16;
1835 if compat then
1836 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1837 else
1838 dx := -(Obj.Rect.Width div 2);
1839 dy := -(Obj.Rect.Height div 2);
1841 ShotType := WEAPON_CACO_FIRE;
1842 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1844 triggers := nil;
1845 Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
1846 end;
1848 Shots[find_id].SpawnerUID := SpawnerUID;
1850 if not Silent then
1851 g_Sound_PlayExAt('SOUND_WEAPON_FIREBALL', x, y);
1852 end;
1854 procedure g_Weapon_ball7(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1855 Silent: Boolean = False; compat: Boolean = true);
1856 var
1857 find_id: DWORD;
1858 dx, dy: Integer;
1859 begin
1860 if WID < 0 then
1861 find_id := FindShot()
1862 else
1863 begin
1864 find_id := WID;
1865 if Integer(find_id) >= High(Shots) then
1866 SetLength(Shots, find_id + 64)
1867 end;
1869 with Shots[find_id] do
1870 begin
1871 g_Obj_Init(@Obj);
1873 Obj.Rect.Width := 32;
1874 Obj.Rect.Height := 16;
1876 if compat then
1877 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1878 else
1879 dx := -(Obj.Rect.Width div 2);
1880 dy := -(Obj.Rect.Height div 2);
1882 ShotType := WEAPON_BARON_FIRE;
1883 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1885 triggers := nil;
1886 Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
1887 end;
1889 Shots[find_id].SpawnerUID := SpawnerUID;
1891 if not Silent then
1892 g_Sound_PlayExAt('SOUND_WEAPON_FIREBALL', x, y);
1893 end;
1895 procedure g_Weapon_aplasma(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1896 Silent: Boolean = False; compat: Boolean = true);
1897 var
1898 find_id: DWORD;
1899 dx, dy: Integer;
1900 begin
1901 if WID < 0 then
1902 find_id := FindShot()
1903 else
1904 begin
1905 find_id := WID;
1906 if Integer(find_id) >= High(Shots) then
1907 SetLength(Shots, find_id + 64)
1908 end;
1910 with Shots[find_id] do
1911 begin
1912 g_Obj_Init(@Obj);
1914 Obj.Rect.Width := 16;
1915 Obj.Rect.Height := 16;
1917 if compat then
1918 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1919 else
1920 dx := -(Obj.Rect.Width div 2);
1921 dy := -(Obj.Rect.Height div 2);
1923 ShotType := WEAPON_BSP_FIRE;
1924 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1926 triggers := nil;
1928 Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
1929 end;
1931 Shots[find_id].SpawnerUID := SpawnerUID;
1933 if not Silent then
1934 g_Sound_PlayExAt('SOUND_WEAPON_FIREPLASMA', x, y);
1935 end;
1937 procedure g_Weapon_manfire(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1938 Silent: Boolean = False; compat: Boolean = true);
1939 var
1940 find_id: DWORD;
1941 dx, dy: Integer;
1942 begin
1943 if WID < 0 then
1944 find_id := FindShot()
1945 else
1946 begin
1947 find_id := WID;
1948 if Integer(find_id) >= High(Shots) then
1949 SetLength(Shots, find_id + 64)
1950 end;
1952 with Shots[find_id] do
1953 begin
1954 g_Obj_Init(@Obj);
1956 Obj.Rect.Width := 32;
1957 Obj.Rect.Height := 32;
1959 if compat then
1960 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1961 else
1962 dx := -(Obj.Rect.Width div 2);
1963 dy := -(Obj.Rect.Height div 2);
1965 ShotType := WEAPON_MANCUB_FIRE;
1966 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1968 triggers := nil;
1970 Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
1971 end;
1973 Shots[find_id].SpawnerUID := SpawnerUID;
1975 if not Silent then
1976 g_Sound_PlayExAt('SOUND_WEAPON_FIREBALL', x, y);
1977 end;
1979 procedure g_Weapon_bfgshot(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1980 Silent: Boolean = False; compat: Boolean = true);
1981 var
1982 find_id: DWORD;
1983 dx, dy: Integer;
1984 begin
1985 if WID < 0 then
1986 find_id := FindShot()
1987 else
1988 begin
1989 find_id := WID;
1990 if Integer(find_id) >= High(Shots) then
1991 SetLength(Shots, find_id + 64)
1992 end;
1994 with Shots[find_id] do
1995 begin
1996 g_Obj_Init(@Obj);
1998 Obj.Rect.Width := SHOT_BFG_WIDTH;
1999 Obj.Rect.Height := SHOT_BFG_HEIGHT;
2001 if compat then
2002 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
2003 else
2004 dx := -(Obj.Rect.Width div 2);
2005 dy := -(Obj.Rect.Height div 2);
2007 ShotType := WEAPON_BFG;
2008 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
2010 triggers := nil;
2011 Animation := TAnimationState.Create(True, 6, 2); // !!! put values into table
2012 end;
2014 Shots[find_id].SpawnerUID := SpawnerUID;
2016 if not Silent then
2017 g_Sound_PlayExAt('SOUND_WEAPON_FIREBFG', x, y);
2018 end;
2020 procedure g_Weapon_bfghit(x, y: Integer);
2021 begin
2022 {$IFDEF ENABLE_GFX}
2023 g_GFX_QueueEffect(R_GFX_BFG_HIT, x - 32, y - 32);
2024 {$ENDIF}
2025 end;
2027 procedure g_Weapon_pistol(x, y, xd, yd: Integer; SpawnerUID: Word;
2028 Silent: Boolean = False);
2029 begin
2030 if not Silent then
2031 g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', x, y);
2033 g_Weapon_gun(x, y, xd, yd, 1, 3, SpawnerUID, True);
2034 if gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF] then
2035 begin
2036 if ABS(x-xd) >= ABS(y-yd) then
2037 begin
2038 g_Weapon_gun(x, y+1, xd, yd+1, 1, 3, SpawnerUID, False);
2039 g_Weapon_gun(x, y-1, xd, yd-1, 1, 2, SpawnerUID, False);
2040 end
2041 else
2042 begin
2043 g_Weapon_gun(x+1, y, xd+1, yd, 1, 3, SpawnerUID, False);
2044 g_Weapon_gun(x-1, y, xd-1, yd, 1, 2, SpawnerUID, False);
2045 end;
2046 end;
2047 end;
2049 procedure g_Weapon_mgun(x, y, xd, yd: Integer; SpawnerUID: Word;
2050 Silent: Boolean = False);
2051 begin
2052 if not Silent then
2053 if gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', x, y);
2055 g_Weapon_gun(x, y, xd, yd, 1, 3, SpawnerUID, True);
2056 if (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF]) and
2057 (g_GetUIDType(SpawnerUID) = UID_PLAYER) then
2058 begin
2059 if ABS(x-xd) >= ABS(y-yd) then
2060 begin
2061 g_Weapon_gun(x, y+1, xd, yd+1, 1, 2, SpawnerUID, False);
2062 g_Weapon_gun(x, y-1, xd, yd-1, 1, 2, SpawnerUID, False);
2063 end
2064 else
2065 begin
2066 g_Weapon_gun(x+1, y, xd+1, yd, 1, 2, SpawnerUID, False);
2067 g_Weapon_gun(x-1, y, xd-1, yd, 1, 2, SpawnerUID, False);
2068 end;
2069 end;
2070 end;
2072 procedure g_Weapon_shotgun(x, y, xd, yd: Integer; SpawnerUID: Word;
2073 Silent: Boolean = False);
2074 var
2075 i, j, k: Integer;
2076 begin
2077 if not Silent then
2078 if gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', x, y);
2080 for i := 0 to 9 do
2081 begin
2082 j := 0; k := 0;
2083 if ABS(x-xd) >= ABS(y-yd) then j := Random(17) - 8 else k := Random(17) - 8; // -8 .. 8
2084 g_Weapon_gun(x+k, y+j, xd+k, yd+j, IfThen(i mod 2 <> 0, 1, 0), 3, SpawnerUID, i=0);
2085 end;
2086 end;
2088 procedure g_Weapon_dshotgun(x, y, xd, yd: Integer; SpawnerUID: Word;
2089 Silent: Boolean = False);
2090 var
2091 a, i, j, k: Integer;
2092 begin
2093 if not Silent then
2094 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN2', x, y);
2096 if gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF] then a := 25 else a := 20;
2097 for i := 0 to a do
2098 begin
2099 j := 0; k := 0;
2100 if ABS(x-xd) >= ABS(y-yd) then j := Random(41) - 20 else k := Random(41) - 20; // -20 .. 20
2101 g_Weapon_gun(x+k, y+j, xd+k, yd+j, IfThen(i mod 3 <> 0, 0, 1), 3, SpawnerUID, i=0);
2102 end;
2103 end;
2105 procedure g_Weapon_PreUpdate();
2106 var
2107 i: Integer;
2108 begin
2109 if Shots = nil then Exit;
2110 for i := 0 to High(Shots) do
2111 if Shots[i].ShotType <> 0 then
2112 begin
2113 Shots[i].Obj.oldX := Shots[i].Obj.X;
2114 Shots[i].Obj.oldY := Shots[i].Obj.Y;
2115 end;
2116 end;
2118 procedure g_Weapon_Update();
2119 var
2120 i, a, h, cx, cy, oldvx, oldvy, tf: Integer;
2121 t: DWArray;
2122 st: Word;
2123 o: TObj;
2124 spl: Boolean;
2125 Loud: Boolean;
2126 {$IFDEF ENABLE_GFX}
2127 var tcx, tcy: Integer;
2128 {$ENDIF}
2129 begin
2130 if Shots = nil then
2131 Exit;
2133 for i := 0 to High(Shots) do
2134 begin
2135 if Shots[i].ShotType = 0 then
2136 Continue;
2138 Loud := True;
2140 with Shots[i] do
2141 begin
2142 Timeout := Timeout - 1;
2143 oldvx := Obj.Vel.X;
2144 oldvy := Obj.Vel.Y;
2145 // Àêòèâèðîâàòü òðèããåðû ïî ïóòè (êðîìå óæå àêòèâèðîâàííûõ):
2146 if (Stopped = 0) and g_Game_IsServer then
2147 t := g_Triggers_PressR(Obj.X, Obj.Y, Obj.Rect.Width, Obj.Rect.Height,
2148 SpawnerUID, ACTIVATE_SHOT, triggers)
2149 else
2150 t := nil;
2152 if t <> nil then
2153 begin
2154 // Ïîïîëíÿåì ñïèñîê àêòèâèðîâàííûõ òðèããåðîâ:
2155 if triggers = nil then
2156 triggers := t
2157 else
2158 begin
2159 h := High(t);
2161 for a := 0 to h do
2162 if not InDWArray(t[a], triggers) then
2163 begin
2164 SetLength(triggers, Length(triggers)+1);
2165 triggers[High(triggers)] := t[a];
2166 end;
2167 end;
2168 end;
2170 // Àíèìàöèÿ ñíàðÿäà:
2171 if Animation <> nil then
2172 Animation.Update();
2174 // Äâèæåíèå:
2175 spl := (ShotType <> WEAPON_PLASMA) and
2176 (ShotType <> WEAPON_BFG) and
2177 (ShotType <> WEAPON_BSP_FIRE) and
2178 (ShotType <> WEAPON_FLAMETHROWER);
2180 if Stopped = 0 then
2181 begin
2182 st := g_Obj_Move_Projectile(@Obj, False, spl);
2183 end
2184 else
2185 begin
2186 st := 0;
2187 end;
2188 positionChanged(); // this updates spatial accelerators
2190 if WordBool(st and MOVE_FALLOUT) or (Obj.X < -1000) or
2191 (Obj.X > gMapInfo.Width+1000) or (Obj.Y < -1000) then
2192 begin
2193 // Íà êëèåíòå ñêîðåå âñåãî è òàê óæå âûïàë.
2194 ShotType := 0;
2195 Animation.Free();
2196 Continue;
2197 end;
2199 cx := Obj.X + (Obj.Rect.Width div 2);
2200 cy := Obj.Y + (Obj.Rect.Height div 2);
2202 case ShotType of
2203 WEAPON_ROCKETLAUNCHER, WEAPON_SKEL_FIRE: // Ðàêåòû è ñíàðÿäû Ñêåëåòà
2204 begin
2205 // Âûëåòåëà èç âîäû:
2206 if WordBool(st and MOVE_HITAIR) then
2207 g_Obj_SetSpeed(@Obj, 12);
2209 // Â âîäå øëåéô - ïóçûðè, â âîçäóõå øëåéô - äûì:
2210 if WordBool(st and MOVE_INWATER) then
2211 begin
2212 {$IFDEF ENABLE_GFX}
2213 g_GFX_Bubbles(cx, cy, 1+Random(3), 16, 16);
2214 {$ENDIF}
2215 if Random(2) = 0
2216 then g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', cx, cy)
2217 else g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', cx, cy);
2218 end
2219 else
2220 begin
2221 {$IFDEF ENABLE_GFX}
2222 g_GFX_QueueEffect(R_GFX_SMOKE_TRANS, Obj.X-14+Random(9), cy-20+Random(9));
2223 {$ENDIF}
2224 end;
2226 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2227 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL)) or
2228 (g_Weapon_Hit(@Obj, 10, SpawnerUID, HIT_SOME, False) <> 0) or
2229 (Timeout < 1) then
2230 begin
2231 Obj.Vel.X := 0;
2232 Obj.Vel.Y := 0;
2234 g_Weapon_Explode(cx, cy, 60, SpawnerUID);
2236 if ShotType = WEAPON_SKEL_FIRE then
2237 begin // Âçðûâ ñíàðÿäà Ñêåëåòà
2238 {$IFDEF ENABLE_GFX}
2239 g_GFX_QueueEffect(R_GFX_EXPLODE_SKELFIRE, Obj.X + 32 - 58, Obj.Y + 8 - 36);
2240 g_DynLightExplosion((Obj.X+32), (Obj.Y+8), 64, 1, 0, 0);
2241 {$ENDIF}
2242 end
2243 else
2244 begin // Âçðûâ Ðàêåòû
2245 {$IFDEF ENABLE_GFX}
2246 g_GFX_QueueEffect(R_GFX_EXPLODE_ROCKET, cx - 64, cy - 64);
2247 g_DynLightExplosion(cx, cy, 64, 1, 0, 0);
2248 {$ENDIF}
2249 end;
2251 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEROCKET', Obj.X, Obj.Y);
2253 ShotType := 0;
2254 end;
2256 if ShotType = WEAPON_SKEL_FIRE then
2257 begin // Ñàìîíàâîäêà ñíàðÿäà Ñêåëåòà:
2258 if GetPos(target, @o) then
2259 throw(i, Obj.X, Obj.Y,
2260 o.X+o.Rect.X+(o.Rect.Width div 2)+o.Vel.X+o.Accel.X,
2261 o.Y+o.Rect.Y+(o.Rect.Height div 2)+o.Vel.Y+o.Accel.Y,
2262 12);
2263 end;
2264 end;
2266 WEAPON_PLASMA, WEAPON_BSP_FIRE: // Ïëàçìà, ïëàçìà Àðàõíàòðîíà
2267 begin
2268 // Ïîïàëà â âîäó - ýëåêòðîøîê ïî âîäå:
2269 if WordBool(st and (MOVE_INWATER or MOVE_HITWATER)) then
2270 begin
2271 g_Sound_PlayExAt('SOUND_WEAPON_PLASMAWATER', Obj.X, Obj.Y);
2272 if g_Game_IsServer then CheckTrap(i, 10, HIT_ELECTRO);
2273 ShotType := 0;
2274 Continue;
2275 end;
2277 // Âåëè÷èíà óðîíà:
2278 if (ShotType = WEAPON_PLASMA) and
2279 (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF]) then
2280 a := 10
2281 else
2282 a := 5;
2284 if ShotType = WEAPON_BSP_FIRE then
2285 a := 10;
2287 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2288 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL)) or
2289 (g_Weapon_Hit(@Obj, a, SpawnerUID, HIT_SOME, False) <> 0) or
2290 (Timeout < 1) then
2291 begin
2292 {$IFDEF ENABLE_GFX}
2293 if ShotType = WEAPON_PLASMA then
2294 g_GFX_QueueEffect(R_GFX_EXPLODE_PLASMA, cx - 16, cy - 16)
2295 else
2296 g_GFX_QueueEffect(R_GFX_EXPLODE_BSPFIRE, cx - 16, cy - 16);
2297 g_DynLightExplosion(cx, cy, 32, 0, 0.5, 0.5);
2298 {$ENDIF}
2299 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEPLASMA', Obj.X, Obj.Y);
2300 ShotType := 0;
2301 end;
2302 end;
2304 WEAPON_FLAMETHROWER: // Îãíåìåò
2305 begin
2306 // Ñî âðåìåíåì óìèðàåò
2307 if (Timeout < 1) then
2308 begin
2309 ShotType := 0;
2310 Continue;
2311 end;
2312 // Ïîä âîäîé òîæå
2313 if WordBool(st and (MOVE_HITWATER or MOVE_INWATER)) then
2314 begin
2315 if WordBool(st and MOVE_HITWATER) then
2316 begin
2317 {$IFDEF ENABLE_GFX}
2318 tcx := Random(8);
2319 tcy := Random(8);
2320 g_GFX_QueueEffect(R_GFX_SMOKE, cx-4+tcx-(R_GFX_SMOKE_WIDTH div 2), cy-4+tcy-(R_GFX_SMOKE_HEIGHT div 2));
2321 {$ENDIF}
2322 end
2323 else
2324 begin
2325 {$IFDEF ENABLE_GFX}
2326 g_GFX_Bubbles(cx, cy, 1+Random(3), 16, 16);
2327 {$ENDIF}
2328 if Random(2) = 0
2329 then g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', cx, cy)
2330 else g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', cx, cy);
2331 end;
2332 ShotType := 0;
2333 Continue;
2334 end;
2336 // Ãðàâèòàöèÿ
2337 if Stopped = 0 then
2338 Obj.Accel.Y := Obj.Accel.Y + 1;
2339 // Ïîïàëè â ñòåíó èëè â âîäó:
2340 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL or MOVE_HITWATER)) then
2341 begin
2342 // Ïðèëèïàåì:
2343 Obj.Vel.X := 0;
2344 Obj.Vel.Y := 0;
2345 Obj.Accel.Y := 0;
2346 if WordBool(st and MOVE_HITWALL) then
2347 Stopped := MOVE_HITWALL
2348 else if WordBool(st and MOVE_HITLAND) then
2349 Stopped := MOVE_HITLAND
2350 else if WordBool(st and MOVE_HITCEIL) then
2351 Stopped := MOVE_HITCEIL;
2352 end;
2354 a := IfThen(Stopped = 0, 10, 1);
2355 // Åñëè â êîãî-òî ïîïàëè
2356 if g_Weapon_Hit(@Obj, a, SpawnerUID, HIT_FLAME, False) <> 0 then
2357 begin
2358 // HIT_FLAME ñàì ïîäîææåò
2359 // Åñëè â ïîëåòå ïîïàëè, èñ÷åçàåì
2360 if Stopped = 0 then
2361 ShotType := 0;
2362 end;
2364 if Stopped = 0 then
2365 tf := 2
2366 else
2367 tf := 3;
2369 if (gTime mod LongWord(tf) = 0) then
2370 begin
2371 {$IFDEF ENABLE_GFX}
2372 case Stopped of
2373 MOVE_HITWALL: begin tcx := cx-4+Random(8); tcy := cy-12+Random(24); end;
2374 MOVE_HITLAND: begin tcx := cx-12+Random(24); tcy := cy-10+Random(8); end;
2375 MOVE_HITCEIL: begin tcx := cx-12+Random(24); tcy := cy+6+Random(8); end;
2376 else begin tcx := cx-4+Random(8); tcy := cy-4+Random(8); end;
2377 end;
2378 g_GFX_QueueEffect(R_GFX_FLAME_RAND, tcx - (R_GFX_FLAME_WIDTH div 2), tcy - (R_GFX_FLAME_HEIGHT div 2));
2379 //g_DynLightExplosion(tcx, tcy, 1, 1, 0.8, 0.3);
2380 {$ENDIF}
2381 end;
2382 end;
2384 WEAPON_BFG: // BFG
2385 begin
2386 // Ïîïàëà â âîäó - ýëåêòðîøîê ïî âîäå:
2387 if WordBool(st and (MOVE_INWATER or MOVE_HITWATER)) then
2388 begin
2389 g_Sound_PlayExAt('SOUND_WEAPON_BFGWATER', Obj.X, Obj.Y);
2390 if g_Game_IsServer then CheckTrap(i, 1000, HIT_ELECTRO);
2391 ShotType := 0;
2392 Continue;
2393 end;
2395 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2396 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL)) or
2397 (g_Weapon_Hit(@Obj, SHOT_BFG_DAMAGE, SpawnerUID, HIT_BFG, False) <> 0) or
2398 (Timeout < 1) then
2399 begin
2400 // Ëó÷è BFG:
2401 if g_Game_IsServer then g_Weapon_BFG9000(cx, cy, SpawnerUID);
2402 {$IFDEF ENABLE_GFX}
2403 g_GFX_QueueEffect(R_GFX_EXPLODE_BFG, cx - 64, cy - 64);
2404 g_DynLightExplosion(cx, cy, 96, 0, 1, 0);
2405 {$ENDIF}
2406 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBFG', Obj.X, Obj.Y);
2407 ShotType := 0;
2408 end;
2409 end;
2411 WEAPON_IMP_FIRE, WEAPON_CACO_FIRE, WEAPON_BARON_FIRE: // Âûñòðåëû Áåñà, Êàêîäåìîíà Ðûöàðÿ/Áàðîíà àäà
2412 begin
2413 // Âûëåòåë èç âîäû:
2414 if WordBool(st and MOVE_HITAIR) then
2415 g_Obj_SetSpeed(@Obj, 16);
2417 // Âåëè÷èíà óðîíà:
2418 if ShotType = WEAPON_IMP_FIRE then
2419 a := 5
2420 else
2421 if ShotType = WEAPON_CACO_FIRE then
2422 a := 20
2423 else
2424 a := 40;
2426 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2427 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL)) or
2428 (g_Weapon_Hit(@Obj, a, SpawnerUID, HIT_SOME) <> 0) or
2429 (Timeout < 1) then
2430 begin
2431 {$IFDEF ENABLE_GFX}
2432 case ShotType of
2433 WEAPON_IMP_FIRE: g_GFX_QueueEffect(R_GFX_EXPLODE_IMPFIRE, cx - 32, cy - 32);
2434 WEAPON_CACO_FIRE: g_GFX_QueueEffect(R_GFX_EXPLODE_CACOFIRE, cx - 32, cy - 32);
2435 WEAPON_BARON_FIRE: g_GFX_QueueEffect(R_GFX_EXPLODE_BARONFIRE, cx - 32, cy - 32);
2436 end;
2437 {$ENDIF}
2438 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBALL', Obj.X, Obj.Y);
2439 ShotType := 0;
2440 end;
2441 end;
2443 WEAPON_MANCUB_FIRE: // Âûñòðåë Ìàíêóáóñà
2444 begin
2445 // Âûëåòåë èç âîäû:
2446 if WordBool(st and MOVE_HITAIR) then
2447 g_Obj_SetSpeed(@Obj, 16);
2449 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2450 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL)) or
2451 (g_Weapon_Hit(@Obj, 40, SpawnerUID, HIT_SOME, False) <> 0) or
2452 (Timeout < 1) then
2453 begin
2454 // Âçðûâ:
2455 {$IFDEF ENABLE_GFX}
2456 g_GFX_QueueEffect(R_GFX_EXPLODE_ROCKET, cx - 64, cy - 64);
2457 {$ENDIF}
2458 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBALL', Obj.X, Obj.Y);
2459 ShotType := 0;
2460 end;
2461 end;
2462 end; // case ShotType of...
2464 // Åñëè ñíàðÿäà óæå íåò, óäàëÿåì àíèìàöèþ:
2465 if (ShotType = 0) then
2466 begin
2467 if gGameSettings.GameType = GT_SERVER then
2468 MH_SEND_DeleteShot(i, Obj.X, Obj.Y, Loud);
2469 if Animation <> nil then
2470 begin
2471 Animation.Free();
2472 Animation := nil;
2473 end;
2474 end
2475 else if (ShotType <> WEAPON_FLAMETHROWER) and ((oldvx <> Obj.Vel.X) or (oldvy <> Obj.Vel.Y)) then
2476 if gGameSettings.GameType = GT_SERVER then
2477 MH_SEND_UpdateShot(i);
2478 end;
2479 end;
2480 end;
2482 function g_Weapon_Danger(UID: Word; X, Y: Integer; Width, Height: Word; Time: Byte): Boolean;
2483 var
2484 a: Integer;
2485 begin
2486 Result := False;
2488 if Shots = nil then
2489 Exit;
2491 for a := 0 to High(Shots) do
2492 if (Shots[a].ShotType <> 0) and (Shots[a].SpawnerUID <> UID) then
2493 if ((Shots[a].Obj.Vel.Y = 0) and (Shots[a].Obj.Vel.X > 0) and (Shots[a].Obj.X < X)) or
2494 (Shots[a].Obj.Vel.Y = 0) and (Shots[a].Obj.Vel.X < 0) and (Shots[a].Obj.X > X) then
2495 if (Abs(X-Shots[a].Obj.X) < Abs(Shots[a].Obj.Vel.X*Time)) and
2496 g_Collide(X, Y, Width, Height, X, Shots[a].Obj.Y,
2497 Shots[a].Obj.Rect.Width, Shots[a].Obj.Rect.Height) and
2498 g_TraceVector(X, Y, Shots[a].Obj.X, Shots[a].Obj.Y) then
2499 begin
2500 Result := True;
2501 Exit;
2502 end;
2503 end;
2505 procedure g_Weapon_SaveState (st: TStream);
2506 var
2507 count, i, j: Integer;
2508 begin
2509 // Ñ÷èòàåì êîëè÷åñòâî ñóùåñòâóþùèõ ñíàðÿäîâ
2510 count := 0;
2511 for i := 0 to High(Shots) do if (Shots[i].ShotType <> 0) then Inc(count);
2513 // Êîëè÷åñòâî ñíàðÿäîâ
2514 utils.WriteInt(st, count);
2516 if (count = 0) then exit;
2518 for i := 0 to High(Shots) do
2519 begin
2520 if Shots[i].ShotType <> 0 then
2521 begin
2522 // Ñèãíàòóðà ñíàðÿäà
2523 utils.writeSign(st, 'SHOT');
2524 utils.writeInt(st, Byte(0)); // version
2525 // Òèï ñíàðÿäà
2526 utils.writeInt(st, Byte(Shots[i].ShotType));
2527 // Öåëü
2528 utils.writeInt(st, Word(Shots[i].Target));
2529 // UID ñòðåëÿâøåãî
2530 utils.writeInt(st, Word(Shots[i].SpawnerUID));
2531 // Ðàçìåð ïîëÿ Triggers
2532 utils.writeInt(st, Integer(Length(Shots[i].Triggers)));
2533 // Òðèããåðû, àêòèâèðîâàííûå âûñòðåëîì
2534 for j := 0 to Length(Shots[i].Triggers)-1 do utils.writeInt(st, LongWord(Shots[i].Triggers[j]));
2535 // Îáúåêò ñíàðÿäà
2536 Obj_SaveState(st, @Shots[i].Obj);
2537 // Êîñòûëèíà åáàíàÿ
2538 utils.writeInt(st, Byte(Shots[i].Stopped));
2539 end;
2540 end;
2541 end;
2543 procedure g_Weapon_LoadState (st: TStream);
2544 var
2545 count, tc, i, j: Integer;
2546 begin
2547 if (st = nil) then exit;
2549 // Êîëè÷åñòâî ñíàðÿäîâ
2550 count := utils.readLongInt(st);
2551 if (count < 0) or (count > 1024*1024) then raise XStreamError.Create('invalid shots counter');
2553 SetLength(Shots, count);
2555 if (count = 0) then exit;
2557 for i := 0 to count-1 do
2558 begin
2559 // Ñèãíàòóðà ñíàðÿäà
2560 if not utils.checkSign(st, 'SHOT') then raise XStreamError.Create('invalid shot signature');
2561 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid shot version');
2562 // Òèï ñíàðÿäà:
2563 Shots[i].ShotType := utils.readByte(st);
2564 // Öåëü
2565 Shots[i].Target := utils.readWord(st);
2566 // UID ñòðåëÿâøåãî
2567 Shots[i].SpawnerUID := utils.readWord(st);
2568 // Ðàçìåð ïîëÿ Triggers
2569 tc := utils.readLongInt(st);
2570 if (tc < 0) or (tc > 1024*1024) then raise XStreamError.Create('invalid shot triggers counter');
2571 SetLength(Shots[i].Triggers, tc);
2572 // Òðèããåðû, àêòèâèðîâàííûå âûñòðåëîì
2573 for j := 0 to tc-1 do Shots[i].Triggers[j] := utils.readLongWord(st);
2574 // Îáúåêò ïðåäìåòà
2575 Obj_LoadState(@Shots[i].Obj, st);
2576 // Êîñòûëèíà åáàíàÿ
2577 Shots[i].Stopped := utils.readByte(st);
2579 // Óñòàíîâêà òåêñòóðû èëè àíèìàöèè
2580 Shots[i].Animation := nil;
2582 case Shots[i].ShotType of
2583 WEAPON_ROCKETLAUNCHER, WEAPON_SKEL_FIRE:
2584 begin
2585 end;
2586 WEAPON_PLASMA:
2587 begin
2588 Shots[i].Animation := TAnimationState.Create(True, 5, 2); // !!! put values into table
2589 end;
2590 WEAPON_BFG:
2591 begin
2592 Shots[i].Animation := TAnimationState.Create(True, 6, 2); // !!! put values into table
2593 end;
2594 WEAPON_IMP_FIRE:
2595 begin
2596 Shots[i].Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
2597 end;
2598 WEAPON_BSP_FIRE:
2599 begin
2600 Shots[i].Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
2601 end;
2602 WEAPON_CACO_FIRE:
2603 begin
2604 Shots[i].Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
2605 end;
2606 WEAPON_BARON_FIRE:
2607 begin
2608 Shots[i].Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
2609 end;
2610 WEAPON_MANCUB_FIRE:
2611 begin
2612 Shots[i].Animation := TAnimationState.Create(True, 4, 2); // !!! put values into table
2613 end;
2614 end;
2615 end;
2616 end;
2618 procedure g_Weapon_DestroyShot(I: Integer; X, Y: Integer; Loud: Boolean = True);
2619 {$IFDEF ENABLE_GFX}
2620 var cx, cy: Integer;
2621 {$ENDIF}
2622 begin
2623 if Shots = nil then
2624 Exit;
2625 if (I > High(Shots)) or (I < 0) then Exit;
2627 with Shots[I] do
2628 begin
2629 if ShotType = 0 then Exit;
2630 Obj.X := X;
2631 Obj.Y := Y;
2632 {$IFDEF ENABLE_GFX}
2633 cx := Obj.X + (Obj.Rect.Width div 2);
2634 cy := Obj.Y + (Obj.Rect.Height div 2);
2635 {$ENDIF}
2637 case ShotType of
2638 WEAPON_ROCKETLAUNCHER, WEAPON_SKEL_FIRE: // Ðàêåòû è ñíàðÿäû Ñêåëåòà
2639 begin
2640 if Loud then
2641 begin
2642 {$IFDEF ENABLE_GFX}
2643 if ShotType = WEAPON_SKEL_FIRE then
2644 g_GFX_QueueEffect(R_GFX_EXPLODE_SKELFIRE, (Obj.X + 32) - 32, (Obj.Y + 8) - 32)
2645 else
2646 g_GFX_QueueEffect(R_GFX_EXPLODE_ROCKET, cx - 64, cy - 64);
2647 {$ENDIF}
2648 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEROCKET', Obj.X, Obj.Y);
2649 end;
2650 end;
2652 WEAPON_PLASMA, WEAPON_BSP_FIRE: // Ïëàçìà, ïëàçìà Àðàõíàòðîíà
2653 begin
2654 if loud then
2655 begin
2656 {$IFDEF ENABLE_GFX}
2657 if ShotType = WEAPON_PLASMA then
2658 g_GFX_QueueEffect(R_GFX_EXPLODE_PLASMA, cx - 16, cy - 16)
2659 else
2660 g_GFX_QueueEffect(R_GFX_EXPLODE_BSPFIRE, cx - 16, cy - 16);
2661 {$ENDIF}
2662 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEPLASMA', Obj.X, Obj.Y);
2663 end;
2664 end;
2666 WEAPON_BFG: // BFG
2667 begin
2668 {$IFDEF ENABLE_GFX}
2669 g_GFX_QueueEffect(R_GFX_EXPLODE_BFG, cx - 64, cy - 64);
2670 {$ENDIF}
2671 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBFG', Obj.X, Obj.Y);
2672 end;
2674 WEAPON_IMP_FIRE, WEAPON_CACO_FIRE, WEAPON_BARON_FIRE: // Âûñòðåëû Áåñà, Êàêîäåìîíà Ðûöàðÿ/Áàðîíà àäà
2675 begin
2676 if loud then
2677 begin
2678 {$IFDEF ENABLE_GFX}
2679 case ShotType of
2680 WEAPON_IMP_FIRE: g_GFX_QueueEffect(R_GFX_EXPLODE_IMPFIRE, cx - 32, cy - 32);
2681 WEAPON_CACO_FIRE: g_GFX_QueueEffect(R_GFX_EXPLODE_CACOFIRE, cx - 32, cy - 32);
2682 WEAPON_BARON_FIRE: g_GFX_QueueEffect(R_GFX_EXPLODE_BARONFIRE, cx - 32, cy - 32);
2683 end;
2684 {$ENDIF}
2685 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBALL', Obj.X, Obj.Y);
2686 end;
2687 end;
2689 WEAPON_MANCUB_FIRE: // Âûñòðåë Ìàíêóáóñà
2690 begin
2691 {$IFDEF ENABLE_GFX}
2692 g_GFX_QueueEffect(R_GFX_EXPLODE_ROCKET, cx - 64, cy - 64);
2693 {$ENDIF}
2694 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBALL', Obj.X, Obj.Y);
2695 end;
2696 end; // case ShotType of...
2698 ShotType := 0;
2699 Animation.Free();
2700 end;
2701 end;
2704 procedure g_Weapon_AddDynLights();
2705 var
2706 i: Integer;
2707 begin
2708 if Shots = nil then Exit;
2709 for i := 0 to High(Shots) do
2710 begin
2711 if Shots[i].ShotType = 0 then continue;
2712 if (Shots[i].ShotType = WEAPON_ROCKETLAUNCHER) or
2713 (Shots[i].ShotType = WEAPON_BARON_FIRE) or
2714 (Shots[i].ShotType = WEAPON_MANCUB_FIRE) or
2715 (Shots[i].ShotType = WEAPON_SKEL_FIRE) or
2716 (Shots[i].ShotType = WEAPON_IMP_FIRE) or
2717 (Shots[i].ShotType = WEAPON_CACO_FIRE) or
2718 (Shots[i].ShotType = WEAPON_MANCUB_FIRE) or
2719 (Shots[i].ShotType = WEAPON_BSP_FIRE) or
2720 (Shots[i].ShotType = WEAPON_PLASMA) or
2721 (Shots[i].ShotType = WEAPON_BFG) or
2722 (Shots[i].ShotType = WEAPON_FLAMETHROWER) or
2723 false then
2724 begin
2725 if (Shots[i].ShotType = WEAPON_PLASMA) then
2726 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)
2727 else if (Shots[i].ShotType = WEAPON_BFG) then
2728 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)
2729 else if (Shots[i].ShotType = WEAPON_FLAMETHROWER) then
2730 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)
2731 else
2732 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);
2733 end;
2734 end;
2735 end;
2738 procedure TShot.positionChanged (); begin end;
2741 end.