DEADSOFTWARE

gl: use TAnimInfo for animations calculated by render
[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_animations, 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: TAnimState;
34 Timeout: DWORD;
35 Stopped: Byte;
37 procedure positionChanged (); //WARNING! call this after monster position was changed, or coldet will not work right!
38 end;
41 var
42 Shots: array of TShot = nil;
43 LastShotID: Integer = 0;
45 procedure g_Weapon_LoadData();
46 procedure g_Weapon_FreeData();
47 procedure g_Weapon_Init();
48 procedure g_Weapon_Free();
49 function g_Weapon_Hit(obj: PObj; d: Integer; SpawnerUID: Word; t: Byte; HitCorpses: Boolean = True): Byte;
50 function g_Weapon_HitUID(UID: Word; d: Integer; SpawnerUID: Word; t: Byte): Boolean;
51 function g_Weapon_CreateShot(I: Integer; ShotType: Byte; Spawner, TargetUID: Word; X, Y, XV, YV: Integer): LongWord;
53 procedure g_Weapon_gun(const x, y, xd, yd, v, indmg: Integer; SpawnerUID: Word; CheckTrigger: Boolean);
54 procedure g_Weapon_punch(x, y: Integer; d, SpawnerUID: Word);
55 function g_Weapon_chainsaw(x, y: Integer; d, SpawnerUID: Word): Integer;
56 procedure g_Weapon_rocket(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1; Silent: Boolean = False; compat: Boolean = true);
57 procedure g_Weapon_revf(x, y, xd, yd: Integer; SpawnerUID, TargetUID: Word; WID: Integer = -1; Silent: Boolean = False);
58 procedure g_Weapon_flame(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1; Silent: Boolean = False; compat: Boolean = true);
59 procedure g_Weapon_plasma(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1; Silent: Boolean = False; compat: Boolean = true);
60 procedure g_Weapon_ball1(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1; Silent: Boolean = False; compat: Boolean = true);
61 procedure g_Weapon_ball2(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1; Silent: Boolean = False; compat: Boolean = true);
62 procedure g_Weapon_ball7(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1; Silent: Boolean = False; compat: Boolean = true);
63 procedure g_Weapon_aplasma(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1; Silent: Boolean = False; compat: Boolean = true);
64 procedure g_Weapon_manfire(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1; Silent: Boolean = False; compat: Boolean = true);
65 procedure g_Weapon_bfgshot(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1; Silent: Boolean = False; compat: Boolean = true);
66 procedure g_Weapon_bfghit(x, y: Integer);
67 procedure g_Weapon_pistol(x, y, xd, yd: Integer; SpawnerUID: Word; Silent: Boolean = False);
68 procedure g_Weapon_mgun(x, y, xd, yd: Integer; SpawnerUID: Word; Silent: Boolean = False);
69 procedure g_Weapon_shotgun(x, y, xd, yd: Integer; SpawnerUID: Word; Silent: Boolean = False);
70 procedure g_Weapon_dshotgun(x, y, xd, yd: Integer; SpawnerUID: Word; Silent: Boolean = False);
72 function g_Weapon_Explode(X, Y: Integer; rad: Integer; SpawnerUID: Word): Boolean;
73 procedure g_Weapon_BFG9000(X, Y: Integer; SpawnerUID: Word);
74 procedure g_Weapon_PreUpdate();
75 procedure g_Weapon_Update();
76 function g_Weapon_Danger(UID: Word; X, Y: Integer; Width, Height: Word; Time: Byte): Boolean;
77 procedure g_Weapon_DestroyShot(I: Integer; X, Y: Integer; Loud: Boolean = True);
79 procedure g_Weapon_SaveState (st: TStream);
80 procedure g_Weapon_LoadState (st: TStream);
82 procedure g_Weapon_AddDynLights();
84 const
85 WEAPON_KASTET = 0;
86 WEAPON_SAW = 1;
87 WEAPON_PISTOL = 2;
88 WEAPON_SHOTGUN1 = 3;
89 WEAPON_SHOTGUN2 = 4;
90 WEAPON_CHAINGUN = 5;
91 WEAPON_ROCKETLAUNCHER = 6;
92 WEAPON_PLASMA = 7;
93 WEAPON_BFG = 8;
94 WEAPON_SUPERPULEMET = 9;
95 WEAPON_FLAMETHROWER = 10;
96 WEAPON_ZOMBY_PISTOL = 20;
97 WEAPON_IMP_FIRE = 21;
98 WEAPON_BSP_FIRE = 22;
99 WEAPON_CACO_FIRE = 23;
100 WEAPON_BARON_FIRE = 24;
101 WEAPON_MANCUB_FIRE = 25;
102 WEAPON_SKEL_FIRE = 26;
103 WEAPON_LAST = WEAPON_SKEL_FIRE;
105 WP_FIRST = WEAPON_KASTET;
106 WP_LAST = WEAPON_FLAMETHROWER;
108 var
109 gwep_debug_fast_trace: Boolean = true;
112 implementation
114 uses
115 {$IFDEF ENABLE_GFX}
116 g_gfx,
117 {$ENDIF}
118 {$IFDEF ENABLE_GIBS}
119 g_gibs,
120 {$ENDIF}
121 {$IFDEF ENABLE_CORPSES}
122 g_corpses,
123 {$ENDIF}
124 Math, g_map, g_player, g_sound, g_panel,
125 g_console, g_options, g_game,
126 g_triggers, MAPDEF, e_log, g_monsters, g_saveload,
127 g_language, g_netmsg, g_grid,
128 geom, binheap, hashtable, utils, xstreams
131 type
132 TWaterPanel = record
133 X, Y: Integer;
134 Width, Height: Word;
135 Active: Boolean;
136 end;
138 const
139 SHOT_ROCKETLAUNCHER_WIDTH = 14;
140 SHOT_ROCKETLAUNCHER_HEIGHT = 14;
142 SHOT_SKELFIRE_WIDTH = 14;
143 SHOT_SKELFIRE_HEIGHT = 14;
145 SHOT_PLASMA_WIDTH = 16;
146 SHOT_PLASMA_HEIGHT = 16;
148 SHOT_BFG_WIDTH = 32;
149 SHOT_BFG_HEIGHT = 32;
150 SHOT_BFG_DAMAGE = 100;
151 SHOT_BFG_RADIUS = 256;
153 SHOT_FLAME_WIDTH = 4;
154 SHOT_FLAME_HEIGHT = 4;
155 SHOT_FLAME_LIFETIME = 180;
157 SHOT_SIGNATURE = $544F4853; // 'SHOT'
159 type
160 PHitTime = ^THitTime;
161 THitTime = record
162 distSq: Integer;
163 mon: TMonster;
164 plridx: Integer; // if mon=nil
165 x, y: Integer;
166 end;
168 TBinHeapKeyHitTime = class
169 public
170 class function less (const a, b: Integer): Boolean; inline;
171 end;
173 // indicies in `wgunHitTime` array
174 TBinaryHeapHitTimes = specialize TBinaryHeapBase<Integer, TBinHeapKeyHitTime>;
176 var
177 WaterMap: array of array of DWORD = nil;
178 //wgunMonHash: THashIntInt = nil;
179 wgunHitHeap: TBinaryHeapHitTimes = nil;
180 wgunHitTime: array of THitTime = nil;
181 wgunHitTimeUsed: Integer = 0;
184 class function TBinHeapKeyHitTime.less (const a, b: Integer): Boolean;
185 var
186 hta, htb: PHitTime;
187 begin
188 hta := @wgunHitTime[a];
189 htb := @wgunHitTime[b];
190 if (hta.distSq <> htb.distSq) then begin result := (hta.distSq < htb.distSq); exit; end;
191 if (hta.mon <> nil) then
192 begin
193 // a is monster
194 if (htb.mon = nil) then begin result := false; exit; end; // players first
195 result := (hta.mon.UID < htb.mon.UID); // why not?
196 end
197 else
198 begin
199 // a is player
200 if (htb.mon <> nil) then begin result := true; exit; end; // players first
201 result := (hta.plridx < htb.plridx); // why not?
202 end;
203 end;
206 procedure appendHitTimeMon (adistSq: Integer; amon: TMonster; ax, ay: Integer);
207 begin
208 if (wgunHitTimeUsed = Length(wgunHitTime)) then SetLength(wgunHitTime, wgunHitTimeUsed+128);
209 with wgunHitTime[wgunHitTimeUsed] do
210 begin
211 distSq := adistSq;
212 mon := amon;
213 plridx := -1;
214 x := ax;
215 y := ay;
216 end;
217 wgunHitHeap.insert(wgunHitTimeUsed);
218 Inc(wgunHitTimeUsed);
219 end;
222 procedure appendHitTimePlr (adistSq: Integer; aplridx: Integer; ax, ay: Integer);
223 begin
224 if (wgunHitTimeUsed = Length(wgunHitTime)) then SetLength(wgunHitTime, wgunHitTimeUsed+128);
225 with wgunHitTime[wgunHitTimeUsed] do
226 begin
227 distSq := adistSq;
228 mon := nil;
229 plridx := aplridx;
230 x := ax;
231 y := ay;
232 end;
233 wgunHitHeap.insert(wgunHitTimeUsed);
234 Inc(wgunHitTimeUsed);
235 end;
238 function FindShot(): DWORD;
239 var
240 i: Integer;
241 begin
242 if Shots <> nil then
243 for i := 0 to High(Shots) do
244 if Shots[i].ShotType = 0 then
245 begin
246 Result := i;
247 LastShotID := Result;
248 Exit;
249 end;
251 if Shots = nil then
252 begin
253 SetLength(Shots, 128);
254 Result := 0;
255 end
256 else
257 begin
258 Result := High(Shots) + 1;
259 SetLength(Shots, Length(Shots) + 128);
260 end;
261 LastShotID := Result;
262 end;
264 procedure CreateWaterMap();
265 var
266 WaterArray: Array of TWaterPanel;
267 a, b, c, m: Integer;
268 ok: Boolean;
269 begin
270 if gWater = nil then
271 Exit;
273 SetLength(WaterArray, Length(gWater));
275 for a := 0 to High(gWater) do
276 begin
277 WaterArray[a].X := gWater[a].X;
278 WaterArray[a].Y := gWater[a].Y;
279 WaterArray[a].Width := gWater[a].Width;
280 WaterArray[a].Height := gWater[a].Height;
281 WaterArray[a].Active := True;
282 end;
284 g_Game_SetLoadingText(_lc[I_LOAD_WATER_MAP], High(WaterArray), False);
286 for a := 0 to High(WaterArray) do
287 if WaterArray[a].Active then
288 begin
289 WaterArray[a].Active := False;
290 m := Length(WaterMap);
291 SetLength(WaterMap, m+1);
292 SetLength(WaterMap[m], 1);
293 WaterMap[m][0] := a;
294 ok := True;
296 while ok do
297 begin
298 ok := False;
299 for b := 0 to High(WaterArray) do
300 if WaterArray[b].Active then
301 for c := 0 to High(WaterMap[m]) do
302 if g_CollideAround(WaterArray[b].X,
303 WaterArray[b].Y,
304 WaterArray[b].Width,
305 WaterArray[b].Height,
306 WaterArray[WaterMap[m][c]].X,
307 WaterArray[WaterMap[m][c]].Y,
308 WaterArray[WaterMap[m][c]].Width,
309 WaterArray[WaterMap[m][c]].Height) then
310 begin
311 WaterArray[b].Active := False;
312 SetLength(WaterMap[m],
313 Length(WaterMap[m])+1);
314 WaterMap[m][High(WaterMap[m])] := b;
315 ok := True;
316 Break;
317 end;
318 end;
320 g_Game_StepLoading();
321 end;
323 WaterArray := nil;
324 end;
327 var
328 chkTrap_pl: array [0..256] of Integer;
329 chkTrap_mn: array [0..65535] of TMonster;
331 procedure CheckTrap(ID: DWORD; dm: Integer; t: Byte);
332 var
333 //a, b, c, d, i1, i2: Integer;
334 //chkTrap_pl, chkTrap_mn: WArray;
335 plaCount: Integer = 0;
336 mnaCount: Integer = 0;
337 frameId: DWord;
340 function monsWaterCheck (mon: TMonster): Boolean;
341 begin
342 result := false; // don't stop
343 if mon.alive and mon.Collide(gWater[WaterMap[a][c]]) and (not InWArray(monidx, chkTrap_mn)) and (i2 < 1023) then //FIXME
344 begin
345 i2 += 1;
346 chkTrap_mn[i2] := monidx;
347 end;
348 end;
351 function monsWaterCheck (mon: TMonster): Boolean;
352 begin
353 result := false; // don't stop
354 if (mon.trapCheckFrameId <> frameId) then
355 begin
356 mon.trapCheckFrameId := frameId;
357 chkTrap_mn[mnaCount] := mon;
358 Inc(mnaCount);
359 end;
360 end;
362 var
363 a, b, c, d, f: Integer;
364 pan: TPanel;
365 begin
366 if (gWater = nil) or (WaterMap = nil) then Exit;
368 frameId := g_Mons_getNewTrapFrameId();
370 //i1 := -1;
371 //i2 := -1;
373 //SetLength(chkTrap_pl, 1024);
374 //SetLength(chkTrap_mn, 1024);
375 //for d := 0 to 1023 do chkTrap_pl[d] := $FFFF;
376 //for d := 0 to 1023 do chkTrap_mn[d] := $FFFF;
378 for a := 0 to High(WaterMap) do
379 begin
380 for b := 0 to High(WaterMap[a]) do
381 begin
382 pan := gWater[WaterMap[a][b]];
383 if not g_Obj_Collide(pan.X, pan.Y, pan.Width, pan.Height, @Shots[ID].Obj) then continue;
385 for c := 0 to High(WaterMap[a]) do
386 begin
387 pan := gWater[WaterMap[a][c]];
388 for d := 0 to High(gPlayers) do
389 begin
390 if (gPlayers[d] <> nil) and (gPlayers[d].alive) then
391 begin
392 if gPlayers[d].Collide(pan) then
393 begin
394 f := 0;
395 while (f < plaCount) and (chkTrap_pl[f] <> d) do Inc(f);
396 if (f = plaCount) then
397 begin
398 chkTrap_pl[plaCount] := d;
399 Inc(plaCount);
400 if (plaCount = Length(chkTrap_pl)) then break;
401 end;
402 end;
403 end;
404 end;
406 //g_Mons_ForEach(monsWaterCheck);
407 g_Mons_ForEachAliveAt(pan.X, pan.Y, pan.Width, pan.Height, monsWaterCheck);
408 end;
410 for f := 0 to plaCount-1 do gPlayers[chkTrap_pl[f]].Damage(dm, Shots[ID].SpawnerUID, 0, 0, t);
411 for f := 0 to mnaCount-1 do chkTrap_mn[f].Damage(dm, 0, 0, Shots[ID].SpawnerUID, t);
412 end;
413 end;
415 //chkTrap_pl := nil;
416 //chkTrap_mn := nil;
417 end;
419 function HitMonster(m: TMonster; d: Integer; vx, vy: Integer; SpawnerUID: Word; t: Byte): Boolean;
420 var
421 tt, mt: Byte;
422 mon: TMonster;
423 begin
424 Result := False;
426 tt := g_GetUIDType(SpawnerUID);
427 if tt = UID_MONSTER then
428 begin
429 mon := g_Monsters_ByUID(SpawnerUID);
430 if mon <> nil then
431 mt := g_Monsters_ByUID(SpawnerUID).MonsterType
432 else
433 mt := 0;
434 end
435 else
436 mt := 0;
438 if m = nil then Exit;
439 if m.UID = SpawnerUID then
440 begin
441 // Ñàì ñåáÿ ìîæåò ðàíèòü òîëüêî ðàêåòîé è òîêîì:
442 if (t <> HIT_ROCKET) and (t <> HIT_ELECTRO) then
443 Exit;
444 // Êèáåð äåìîí è áî÷êà âîîáùå íå ìîãóò ñåáÿ ðàíèòü:
445 if (m.MonsterType = MONSTER_CYBER) or
446 (m.MonsterType = MONSTER_BARREL) then
447 begin
448 Result := True;
449 Exit;
450 end;
451 end;
453 if tt = UID_MONSTER then
454 begin
455 // Lost_Soul íå ìîæåò ðàíèòü Pain_Elemental'à:
456 if (mt = MONSTER_SOUL) and (m.MonsterType = MONSTER_PAIN) then
457 Exit;
459 // Îáà ìîíñòðà îäíîãî âèäà:
460 if mt = m.MonsterType then
461 case mt of
462 MONSTER_IMP, MONSTER_DEMON, MONSTER_BARON, MONSTER_KNIGHT, MONSTER_CACO,
463 MONSTER_SOUL, MONSTER_MANCUB, MONSTER_SKEL, MONSTER_FISH:
464 Exit; // Ýòè íå áüþò ñâîèõ
465 end;
466 end;
468 if g_Game_IsServer then
469 begin
470 if (t <> HIT_FLAME) or (m.FFireTime = 0) or (vx <> 0) or (vy <> 0) then
471 Result := m.Damage(d, vx, vy, SpawnerUID, t)
472 else
473 Result := (gLMSRespawn = LMS_RESPAWN_NONE); // don't hit monsters when it's warmup time
474 if t = HIT_FLAME then
475 m.CatchFire(SpawnerUID);
476 end
477 else
478 Result := (gLMSRespawn = LMS_RESPAWN_NONE); // don't hit monsters when it's warmup time
479 end;
482 function HitPlayer (p: TPlayer; d: Integer; vx, vy: Integer; SpawnerUID: Word; t: Byte): Boolean;
483 begin
484 result := False;
486 // Ñàì ñåáÿ ìîæåò ðàíèòü òîëüêî ðàêåòîé è òîêîì
487 if (p.UID = SpawnerUID) and (t <> HIT_ROCKET) and (t <> HIT_ELECTRO) then exit;
489 if g_Game_IsServer then
490 begin
491 if (t <> HIT_FLAME) or (p.FFireTime = 0) or (vx <> 0) or (vy <> 0) then p.Damage(d, SpawnerUID, vx, vy, t);
492 if (t = HIT_FLAME) then p.CatchFire(SpawnerUID);
493 end;
495 result := true;
496 end;
499 procedure g_Weapon_BFG9000(X, Y: Integer; SpawnerUID: Word);
501 function monsCheck (mon: TMonster): Boolean;
502 begin
503 result := false; // don't stop
504 if (mon.alive) and (mon.UID <> SpawnerUID) then
505 begin
506 with mon do
507 begin
508 if (g_PatchLength(X, Y, Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
509 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)) <= SHOT_BFG_RADIUS) and
510 g_TraceVector(X, Y, Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
511 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)) then
512 begin
513 if HitMonster(mon, 50, 0, 0, SpawnerUID, HIT_SOME) then mon.BFGHit();
514 end;
515 end;
516 end;
517 end;
519 var
520 i, h: Integer;
521 st: Byte;
522 pl: TPlayer;
523 b: Boolean;
524 begin
525 //g_Sound_PlayEx('SOUND_WEAPON_EXPLODEBFG', 255);
527 {$IFDEF ENABLE_CORPSES}
528 h := High(gCorpses);
529 if gAdvCorpses and (h <> -1) then
530 begin
531 for i := 0 to h do
532 begin
533 if (gCorpses[i] <> nil) and (gCorpses[i].State <> CORPSE_STATE_REMOVEME) then
534 begin
535 with gCorpses[i] do
536 begin
537 if (g_PatchLength(X, Y, Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
538 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)) <= SHOT_BFG_RADIUS) and
539 g_TraceVector(X, Y, Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
540 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)) then
541 begin
542 Damage(50, SpawnerUID, 0, 0);
543 g_Weapon_BFGHit(Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2), Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2));
544 end;
545 end;
546 end;
547 end;
548 end;
549 {$ENDIF}
551 st := TEAM_NONE;
552 pl := g_Player_Get(SpawnerUID);
553 if pl <> nil then
554 st := pl.Team;
556 h := High(gPlayers);
558 if h <> -1 then
559 for i := 0 to h do
560 if (gPlayers[i] <> nil) and (gPlayers[i].alive) and (gPlayers[i].UID <> SpawnerUID) then
561 with gPlayers[i] do
562 if (g_PatchLength(X, Y, GameX+PLAYER_RECT.X+(PLAYER_RECT.Width div 2),
563 GameY+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)) <= SHOT_BFG_RADIUS) and
564 g_TraceVector(X, Y, GameX+PLAYER_RECT.X+(PLAYER_RECT.Width div 2),
565 GameY+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)) then
566 begin
567 if (st = TEAM_NONE) or (st <> gPlayers[i].Team) then
568 b := HitPlayer(gPlayers[i], 50, 0, 0, SpawnerUID, HIT_SOME)
569 else
570 b := HitPlayer(gPlayers[i], 25, 0, 0, SpawnerUID, HIT_SOME);
571 if b then
572 gPlayers[i].BFGHit();
573 end;
575 //FIXME
576 g_Mons_ForEachAlive(monsCheck);
577 end;
579 function g_Weapon_CreateShot(I: Integer; ShotType: Byte; Spawner, TargetUID: Word; X, Y, XV, YV: Integer): LongWord;
580 var
581 find_id: DWord;
582 begin
583 if I < 0 then
584 find_id := FindShot()
585 else
586 begin
587 find_id := I;
588 if Integer(find_id) >= High(Shots) then
589 SetLength(Shots, find_id + 64)
590 end;
592 case ShotType of
593 WEAPON_ROCKETLAUNCHER:
594 begin
595 with Shots[find_id] do
596 begin
597 g_Obj_Init(@Obj);
599 Obj.Rect.Width := SHOT_ROCKETLAUNCHER_WIDTH;
600 Obj.Rect.Height := SHOT_ROCKETLAUNCHER_HEIGHT;
602 Triggers := nil;
603 ShotType := WEAPON_ROCKETLAUNCHER;
604 Animation.Invalidate;
605 end;
606 end;
608 WEAPON_PLASMA:
609 begin
610 with Shots[find_id] do
611 begin
612 g_Obj_Init(@Obj);
614 Obj.Rect.Width := SHOT_PLASMA_WIDTH;
615 Obj.Rect.Height := SHOT_PLASMA_HEIGHT;
617 Triggers := nil;
618 ShotType := WEAPON_PLASMA;
619 Animation := TAnimState.Create(True, 5, 2); // !!! put values into table
620 end;
621 end;
623 WEAPON_BFG:
624 begin
625 with Shots[find_id] do
626 begin
627 g_Obj_Init(@Obj);
629 Obj.Rect.Width := SHOT_BFG_WIDTH;
630 Obj.Rect.Height := SHOT_BFG_HEIGHT;
632 Triggers := nil;
633 ShotType := WEAPON_BFG;
634 Animation := TAnimState.Create(True, 6, 2); // !!! put values into table
635 end;
636 end;
638 WEAPON_FLAMETHROWER:
639 begin
640 with Shots[find_id] do
641 begin
642 g_Obj_Init(@Obj);
644 Obj.Rect.Width := SHOT_FLAME_WIDTH;
645 Obj.Rect.Height := SHOT_FLAME_HEIGHT;
647 Triggers := nil;
648 ShotType := WEAPON_FLAMETHROWER;
649 Animation.Invalidate;
650 // Animation := TAnimState.Create(True, 6, 0); // drawed as gfx
651 end;
652 end;
654 WEAPON_IMP_FIRE:
655 begin
656 with Shots[find_id] do
657 begin
658 g_Obj_Init(@Obj);
660 Obj.Rect.Width := 16;
661 Obj.Rect.Height := 16;
663 Triggers := nil;
664 ShotType := WEAPON_IMP_FIRE;
665 Animation := TAnimState.Create(True, 4, 2); // !!! put values into table
666 end;
667 end;
669 WEAPON_CACO_FIRE:
670 begin
671 with Shots[find_id] do
672 begin
673 g_Obj_Init(@Obj);
675 Obj.Rect.Width := 16;
676 Obj.Rect.Height := 16;
678 Triggers := nil;
679 ShotType := WEAPON_CACO_FIRE;
680 Animation := TAnimState.Create(True, 4, 2); // !!! put values into table
681 end;
682 end;
684 WEAPON_MANCUB_FIRE:
685 begin
686 with Shots[find_id] do
687 begin
688 g_Obj_Init(@Obj);
690 Obj.Rect.Width := 32;
691 Obj.Rect.Height := 32;
693 Triggers := nil;
694 ShotType := WEAPON_MANCUB_FIRE;
695 Animation := TAnimState.Create(True, 4, 2); // !!! put values into table
696 end;
697 end;
699 WEAPON_BARON_FIRE:
700 begin
701 with Shots[find_id] do
702 begin
703 g_Obj_Init(@Obj);
705 Obj.Rect.Width := 16;
706 Obj.Rect.Height := 16;
708 Triggers := nil;
709 ShotType := WEAPON_BARON_FIRE;
710 Animation := TAnimState.Create(True, 4, 2); // !!! put values into table
711 end;
712 end;
714 WEAPON_BSP_FIRE:
715 begin
716 with Shots[find_id] do
717 begin
718 g_Obj_Init(@Obj);
720 Obj.Rect.Width := 16;
721 Obj.Rect.Height := 16;
723 Triggers := nil;
724 ShotType := WEAPON_BSP_FIRE;
725 Animation := TAnimState.Create(True, 4, 2); // !!! put values into table
726 end;
727 end;
729 WEAPON_SKEL_FIRE:
730 begin
731 with Shots[find_id] do
732 begin
733 g_Obj_Init(@Obj);
735 Obj.Rect.Width := SHOT_SKELFIRE_WIDTH;
736 Obj.Rect.Height := SHOT_SKELFIRE_HEIGHT;
738 Triggers := nil;
739 ShotType := WEAPON_SKEL_FIRE;
740 target := TargetUID;
741 Animation := TAnimState.Create(True, 5, 2); // !!! put values into table
742 end;
743 end;
744 end;
746 Shots[find_id].Obj.oldX := X;
747 Shots[find_id].Obj.oldY := Y;
748 Shots[find_id].Obj.X := X;
749 Shots[find_id].Obj.Y := Y;
750 Shots[find_id].Obj.Vel.X := XV;
751 Shots[find_id].Obj.Vel.Y := YV;
752 Shots[find_id].Obj.Accel.X := 0;
753 Shots[find_id].Obj.Accel.Y := 0;
754 Shots[find_id].SpawnerUID := Spawner;
755 if (ShotType = WEAPON_FLAMETHROWER) and (XV = 0) and (YV = 0) then
756 Shots[find_id].Stopped := 255
757 else
758 Shots[find_id].Stopped := 0;
759 Result := find_id;
760 end;
762 procedure throw(i, x, y, xd, yd, s: Integer);
763 var
764 a: Integer;
765 begin
766 yd := yd - y;
767 xd := xd - x;
769 a := Max(Abs(xd), Abs(yd));
770 if a = 0 then
771 a := 1;
773 Shots[i].Obj.oldX := x;
774 Shots[i].Obj.oldY := y;
775 Shots[i].Obj.X := x;
776 Shots[i].Obj.Y := y;
777 Shots[i].Obj.Vel.X := (xd*s) div a;
778 Shots[i].Obj.Vel.Y := (yd*s) div a;
779 Shots[i].Obj.Accel.X := 0;
780 Shots[i].Obj.Accel.Y := 0;
781 Shots[i].Stopped := 0;
782 if Shots[i].ShotType in [WEAPON_ROCKETLAUNCHER, WEAPON_BFG] then
783 Shots[i].Timeout := 900 // ~25 sec
784 else
785 begin
786 if Shots[i].ShotType = WEAPON_FLAMETHROWER then
787 Shots[i].Timeout := SHOT_FLAME_LIFETIME
788 else
789 Shots[i].Timeout := 550; // ~15 sec
790 end;
791 end;
793 function g_Weapon_Hit(obj: PObj; d: Integer; SpawnerUID: Word; t: Byte; HitCorpses: Boolean = True): Byte;
794 {$IFDEF ENABLE_CORPSES}
795 var i: Integer;
796 {$ENDIF}
797 var h: Integer;
799 function PlayerHit(Team: Byte = 0): Boolean;
800 var
801 i: Integer;
802 ChkTeam: Boolean;
803 p: TPlayer;
804 begin
805 Result := False;
806 h := High(gPlayers);
808 if h <> -1 then
809 for i := 0 to h do
810 if (gPlayers[i] <> nil) and gPlayers[i].alive and g_Obj_Collide(obj, @gPlayers[i].Obj) then
811 begin
812 ChkTeam := True;
813 if (Team > 0) and (g_GetUIDType(SpawnerUID) = UID_PLAYER) then
814 begin
815 p := g_Player_Get(SpawnerUID);
816 if p <> nil then
817 ChkTeam := (p.Team = gPlayers[i].Team) xor (Team = 2);
818 end;
819 if ChkTeam then
820 if HitPlayer(gPlayers[i], d, obj^.Vel.X, obj^.Vel.Y, SpawnerUID, t) then
821 begin
822 if t <> HIT_FLAME then
823 gPlayers[i].Push((obj^.Vel.X+obj^.Accel.X)*IfThen(t = HIT_BFG, 8, 1) div 4,
824 (obj^.Vel.Y+obj^.Accel.Y)*IfThen(t = HIT_BFG, 8, 1) div 4);
825 if t = HIT_BFG then
826 g_Game_DelayEvent(DE_BFGHIT, 1000, SpawnerUID);
827 Result := True;
828 break;
829 end;
830 end;
831 end;
834 function monsCheckHit (monidx: Integer; mon: TMonster): Boolean;
835 begin
836 result := false; // don't stop
837 if mon.alive and g_Obj_Collide(obj, @mon.Obj) then
838 begin
839 if HitMonster(mon, d, obj^.Vel.X, obj^.Vel.Y, SpawnerUID, t) then
840 begin
841 if (t <> HIT_FLAME) then
842 begin
843 mon.Push((obj^.Vel.X+obj^.Accel.X)*IfThen(t = HIT_BFG, 8, 1) div 4,
844 (obj^.Vel.Y+obj^.Accel.Y)*IfThen(t = HIT_BFG, 8, 1) div 4);
845 end;
846 result := True;
847 end;
848 end;
849 end;
852 function monsCheckHit (mon: TMonster): Boolean;
853 begin
854 result := false; // don't stop
855 if HitMonster(mon, d, obj.Vel.X, obj.Vel.Y, SpawnerUID, t) then
856 begin
857 if (t <> HIT_FLAME) then
858 begin
859 mon.Push((obj.Vel.X+obj.Accel.X)*IfThen(t = HIT_BFG, 8, 1) div 4,
860 (obj.Vel.Y+obj.Accel.Y)*IfThen(t = HIT_BFG, 8, 1) div 4);
861 end;
862 result := true;
863 end;
864 end;
866 function MonsterHit(): Boolean;
867 begin
868 //result := g_Mons_ForEach(monsCheckHit);
869 //FIXME: accelerate this!
870 result := g_Mons_ForEachAliveAt(obj.X+obj.Rect.X, obj.Y+obj.Rect.Y, obj.Rect.Width, obj.Rect.Height, monsCheckHit);
871 end;
873 begin
874 Result := 0;
876 {$IFDEF ENABLE_CORPSES}
877 if HitCorpses then
878 begin
879 h := High(gCorpses);
880 if gAdvCorpses and (h <> -1) then
881 begin
882 for i := 0 to h do
883 begin
884 if (gCorpses[i] <> nil) and (gCorpses[i].State <> CORPSE_STATE_REMOVEME) and
885 g_Obj_Collide(obj, @gCorpses[i].Obj) then
886 begin
887 // Ðàñïèëèâàåì òðóï:
888 gCorpses[i].Damage(d, SpawnerUID, (obj^.Vel.X+obj^.Accel.X) div 4,
889 (obj^.Vel.Y+obj^.Accel.Y) div 4);
890 Result := 1;
891 end;
892 end;
893 end;
894 end;
895 {$ENDIF}
897 case gGameSettings.GameMode of
898 // Êàìïàíèÿ:
899 GM_COOP, GM_SINGLE:
900 begin
901 // Ñíà÷àëà áü¸ì ìîíñòðîâ, åñëè åñòü, ïîòîì ïûòàåìñÿ áèòü èãðîêîâ
902 if MonsterHit() then
903 begin
904 Result := 2;
905 Exit;
906 end;
908 // È â êîíöå èãðîêîâ, íî òîëüêî åñëè ïîëîæåíî
909 // (èëè ñíàðÿä îò ìîíñòðà, èëè friendlyfire, èëè friendly_hit_projectile)
910 if (g_GetUIDType(SpawnerUID) <> UID_PLAYER) or
911 LongBool(gGameSettings.Options and (GAME_OPTION_TEAMDAMAGE or GAME_OPTION_TEAMHITPROJECTILE)) then
912 begin
913 if PlayerHit() then
914 begin
915 Result := 1;
916 Exit;
917 end;
918 end;
919 end;
921 // Äåçìàò÷:
922 GM_DM:
923 begin
924 // Ñíà÷àëà áü¸ì èãðîêîâ, åñëè åñòü, ïîòîì ïûòàåìñÿ áèòü ìîíñòðîâ
925 if PlayerHit() then
926 begin
927 Result := 1;
928 Exit;
929 end;
931 if MonsterHit() then
932 begin
933 Result := 2;
934 Exit;
935 end;
936 end;
938 // Êîìàíäíûå:
939 GM_TDM, GM_CTF:
940 begin
941 // Ñíà÷àëà áü¸ì èãðîêîâ êîìàíäû ñîïåðíèêà
942 if PlayerHit(2) then
943 begin
944 Result := 1;
945 Exit;
946 end;
948 // Ïîòîì ìîíñòðîâ
949 if MonsterHit() then
950 begin
951 Result := 2;
952 Exit;
953 end;
955 // È â êîíöå ñâîèõ èãðîêîâ, íî òîëüêî åñëè ïîëîæåíî
956 // (èëè friendlyfire, èëè friendly_hit_projectile)
957 if LongBool(gGameSettings.Options and (GAME_OPTION_TEAMDAMAGE or GAME_OPTION_TEAMHITPROJECTILE)) then
958 begin
959 if PlayerHit(1) then
960 begin
961 Result := 1;
962 Exit;
963 end;
964 end;
965 end;
967 end;
968 end;
970 function g_Weapon_HitUID(UID: Word; d: Integer; SpawnerUID: Word; t: Byte): Boolean;
971 begin
972 Result := False;
974 case g_GetUIDType(UID) of
975 UID_PLAYER: Result := HitPlayer(g_Player_Get(UID), d, 0, 0, SpawnerUID, t);
976 UID_MONSTER: Result := HitMonster(g_Monsters_ByUID(UID), d, 0, 0, SpawnerUID, t);
977 else Exit;
978 end;
979 end;
981 function g_Weapon_Explode(X, Y: Integer; rad: Integer; SpawnerUID: Word): Boolean;
982 var
983 r: Integer; // squared radius
985 function monsExCheck (mon: TMonster): Boolean;
986 var
987 dx, dy, mm: Integer;
988 begin
989 result := false; // don't stop
990 begin
991 dx := mon.Obj.X+mon.Obj.Rect.X+(mon.Obj.Rect.Width div 2)-X;
992 dy := mon.Obj.Y+mon.Obj.Rect.Y+(mon.Obj.Rect.Height div 2)-Y;
994 if dx > 1000 then dx := 1000;
995 if dy > 1000 then dy := 1000;
997 if (dx*dx+dy*dy < r) then
998 begin
999 //m := PointToRect(X, Y, Obj.X+Obj.Rect.X, Obj.Y+Obj.Rect.Y, Obj.Rect.Width, Obj.Rect.Height);
1000 //e_WriteLog(Format('explo monster #%d: x=%d; y=%d; rad=%d; dx=%d; dy=%d', [monidx, X, Y, rad, dx, dy]), MSG_NOTIFY);
1002 mm := Max(abs(dx), abs(dy));
1003 if mm = 0 then mm := 1;
1005 if mon.alive then
1006 begin
1007 HitMonster(mon, ((mon.Obj.Rect.Width div 4)*10*(rad-mm)) div rad, 0, 0, SpawnerUID, HIT_ROCKET);
1008 end;
1010 mon.Push((dx*7) div mm, (dy*7) div mm);
1011 end;
1012 end;
1013 end;
1015 var i, h, dx, dy, mm: Integer;
1016 {$IFDEF ENABLE_GIBS}
1017 var _angle: SmallInt;
1018 {$ENDIF}
1019 {$IF DEFINED(ENABLE_GIBS) OR DEFINED(ENABLE_CORPSES)}
1020 var m: Integer;
1021 {$ENDIF}
1022 begin
1023 result := false;
1025 g_Triggers_PressC(X, Y, rad, SpawnerUID, ACTIVATE_SHOT);
1027 r := rad*rad;
1029 h := High(gPlayers);
1031 if h <> -1 then
1032 for i := 0 to h do
1033 if (gPlayers[i] <> nil) and gPlayers[i].alive then
1034 with gPlayers[i] do
1035 begin
1036 dx := Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2)-X;
1037 dy := Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)-Y;
1039 if dx > 1000 then dx := 1000;
1040 if dy > 1000 then dy := 1000;
1042 if dx*dx+dy*dy < r then
1043 begin
1044 //m := PointToRect(X, Y, GameX+PLAYER_RECT.X, GameY+PLAYER_RECT.Y,
1045 // PLAYER_RECT.Width, PLAYER_RECT.Height);
1047 mm := Max(abs(dx), abs(dy));
1048 if mm = 0 then mm := 1;
1050 HitPlayer(gPlayers[i], (100*(rad-mm)) div rad, (dx*10) div mm, (dy*10) div mm, SpawnerUID, HIT_ROCKET);
1051 gPlayers[i].Push((dx*7) div mm, (dy*7) div mm);
1052 end;
1053 end;
1055 //g_Mons_ForEach(monsExCheck);
1056 g_Mons_ForEachAt(X-(rad+32), Y-(rad+32), (rad+32)*2, (rad+32)*2, monsExCheck);
1058 {$IFDEF ENABLE_CORPSES}
1059 h := High(gCorpses);
1060 if gAdvCorpses and (h <> -1) then
1061 begin
1062 for i := 0 to h do
1063 begin
1064 if (gCorpses[i] <> nil) and (gCorpses[i].State <> CORPSE_STATE_REMOVEME) then
1065 begin
1066 with gCorpses[i] do
1067 begin
1068 dx := Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2)-X;
1069 dy := Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)-Y;
1070 if dx > 1000 then dx := 1000;
1071 if dy > 1000 then dy := 1000;
1072 if dx*dx+dy*dy < r then
1073 begin
1074 m := PointToRect(X, Y, Obj.X+Obj.Rect.X, Obj.Y+Obj.Rect.Y, Obj.Rect.Width, Obj.Rect.Height);
1075 mm := Max(abs(dx), abs(dy));
1076 if mm = 0 then
1077 mm := 1;
1078 Damage(Round(100*(rad-m)/rad), SpawnerUID, (dx*10) div mm, (dy*10) div mm);
1079 end;
1080 end;
1081 end;
1082 end;
1083 end;
1084 {$ENDIF}
1086 {$IFDEF ENABLE_GIBS}
1087 h := High(gGibs);
1088 if gAdvGibs and (h <> -1) then
1089 for i := 0 to h do
1090 if gGibs[i].alive then
1091 with gGibs[i] do
1092 begin
1093 dx := Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2)-X;
1094 dy := Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)-Y;
1095 if dx > 1000 then dx := 1000;
1096 if dy > 1000 then dy := 1000;
1097 if dx*dx+dy*dy < r then
1098 begin
1099 m := PointToRect(X, Y, Obj.X+Obj.Rect.X, Obj.Y+Obj.Rect.Y,
1100 Obj.Rect.Width, Obj.Rect.Height);
1101 _angle := GetAngle(Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
1102 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2), X, Y);
1103 g_Obj_PushA(@Obj, Round(15*(rad-m)/rad), _angle);
1104 positionChanged(); // this updates spatial accelerators
1105 end;
1106 end;
1107 {$ENDIF}
1108 end;
1110 procedure g_Weapon_Init();
1111 begin
1112 CreateWaterMap();
1113 end;
1115 procedure g_Weapon_Free();
1116 begin
1117 Shots := nil;
1118 WaterMap := nil;
1119 end;
1121 procedure g_Weapon_LoadData();
1122 begin
1123 e_WriteLog('Loading weapons data...', TMsgType.Notify);
1125 g_Sound_CreateWADEx('SOUND_WEAPON_HITPUNCH', GameWAD+':SOUNDS\HITPUNCH');
1126 g_Sound_CreateWADEx('SOUND_WEAPON_MISSPUNCH', GameWAD+':SOUNDS\MISSPUNCH');
1127 g_Sound_CreateWADEx('SOUND_WEAPON_HITBERSERK', GameWAD+':SOUNDS\HITBERSERK');
1128 g_Sound_CreateWADEx('SOUND_WEAPON_MISSBERSERK', GameWAD+':SOUNDS\MISSBERSERK');
1129 g_Sound_CreateWADEx('SOUND_WEAPON_SELECTSAW', GameWAD+':SOUNDS\SELECTSAW');
1130 g_Sound_CreateWADEx('SOUND_WEAPON_IDLESAW', GameWAD+':SOUNDS\IDLESAW');
1131 g_Sound_CreateWADEx('SOUND_WEAPON_HITSAW', GameWAD+':SOUNDS\HITSAW');
1132 g_Sound_CreateWADEx('SOUND_WEAPON_FIRESHOTGUN2', GameWAD+':SOUNDS\FIRESHOTGUN2');
1133 g_Sound_CreateWADEx('SOUND_WEAPON_FIRESHOTGUN', GameWAD+':SOUNDS\FIRESHOTGUN');
1134 g_Sound_CreateWADEx('SOUND_WEAPON_FIRESAW', GameWAD+':SOUNDS\FIRESAW');
1135 g_Sound_CreateWADEx('SOUND_WEAPON_FIREROCKET', GameWAD+':SOUNDS\FIREROCKET');
1136 g_Sound_CreateWADEx('SOUND_WEAPON_FIREPLASMA', GameWAD+':SOUNDS\FIREPLASMA');
1137 g_Sound_CreateWADEx('SOUND_WEAPON_FIREPISTOL', GameWAD+':SOUNDS\FIREPISTOL');
1138 g_Sound_CreateWADEx('SOUND_WEAPON_FIRECGUN', GameWAD+':SOUNDS\FIRECGUN');
1139 g_Sound_CreateWADEx('SOUND_WEAPON_FIREBFG', GameWAD+':SOUNDS\FIREBFG');
1140 g_Sound_CreateWADEx('SOUND_FIRE', GameWAD+':SOUNDS\FIRE');
1141 g_Sound_CreateWADEx('SOUND_IGNITE', GameWAD+':SOUNDS\IGNITE');
1142 g_Sound_CreateWADEx('SOUND_WEAPON_STARTFIREBFG', GameWAD+':SOUNDS\STARTFIREBFG');
1143 g_Sound_CreateWADEx('SOUND_WEAPON_EXPLODEROCKET', GameWAD+':SOUNDS\EXPLODEROCKET');
1144 g_Sound_CreateWADEx('SOUND_WEAPON_EXPLODEBFG', GameWAD+':SOUNDS\EXPLODEBFG');
1145 g_Sound_CreateWADEx('SOUND_WEAPON_BFGWATER', GameWAD+':SOUNDS\BFGWATER');
1146 g_Sound_CreateWADEx('SOUND_WEAPON_EXPLODEPLASMA', GameWAD+':SOUNDS\EXPLODEPLASMA');
1147 g_Sound_CreateWADEx('SOUND_WEAPON_PLASMAWATER', GameWAD+':SOUNDS\PLASMAWATER');
1148 g_Sound_CreateWADEx('SOUND_WEAPON_FIREBALL', GameWAD+':SOUNDS\FIREBALL');
1149 g_Sound_CreateWADEx('SOUND_WEAPON_EXPLODEBALL', GameWAD+':SOUNDS\EXPLODEBALL');
1150 g_Sound_CreateWADEx('SOUND_WEAPON_FIREREV', GameWAD+':SOUNDS\FIREREV');
1151 g_Sound_CreateWADEx('SOUND_WEAPON_FLAMEON', GameWAD+':SOUNDS\STARTFLM');
1152 g_Sound_CreateWADEx('SOUND_WEAPON_FLAMEOFF', GameWAD+':SOUNDS\STOPFLM');
1153 g_Sound_CreateWADEx('SOUND_WEAPON_FLAMEWORK', GameWAD+':SOUNDS\WORKFLM');
1154 g_Sound_CreateWADEx('SOUND_PLAYER_JETFLY', GameWAD+':SOUNDS\WORKJETPACK');
1155 g_Sound_CreateWADEx('SOUND_PLAYER_JETON', GameWAD+':SOUNDS\STARTJETPACK');
1156 g_Sound_CreateWADEx('SOUND_PLAYER_JETOFF', GameWAD+':SOUNDS\STOPJETPACK');
1157 g_Sound_CreateWADEx('SOUND_PLAYER_CASING1', GameWAD+':SOUNDS\CASING1');
1158 g_Sound_CreateWADEx('SOUND_PLAYER_CASING2', GameWAD+':SOUNDS\CASING2');
1159 g_Sound_CreateWADEx('SOUND_PLAYER_SHELL1', GameWAD+':SOUNDS\SHELL1');
1160 g_Sound_CreateWADEx('SOUND_PLAYER_SHELL2', GameWAD+':SOUNDS\SHELL2');
1162 //wgunMonHash := hashNewIntInt();
1163 wgunHitHeap := TBinaryHeapHitTimes.Create();
1164 end;
1166 procedure g_Weapon_FreeData();
1167 begin
1168 e_WriteLog('Releasing weapons data...', TMsgType.Notify);
1170 g_Sound_Delete('SOUND_WEAPON_HITPUNCH');
1171 g_Sound_Delete('SOUND_WEAPON_MISSPUNCH');
1172 g_Sound_Delete('SOUND_WEAPON_HITBERSERK');
1173 g_Sound_Delete('SOUND_WEAPON_MISSBERSERK');
1174 g_Sound_Delete('SOUND_WEAPON_SELECTSAW');
1175 g_Sound_Delete('SOUND_WEAPON_IDLESAW');
1176 g_Sound_Delete('SOUND_WEAPON_HITSAW');
1177 g_Sound_Delete('SOUND_WEAPON_FIRESHOTGUN2');
1178 g_Sound_Delete('SOUND_WEAPON_FIRESHOTGUN');
1179 g_Sound_Delete('SOUND_WEAPON_FIRESAW');
1180 g_Sound_Delete('SOUND_WEAPON_FIREROCKET');
1181 g_Sound_Delete('SOUND_WEAPON_FIREPLASMA');
1182 g_Sound_Delete('SOUND_WEAPON_FIREPISTOL');
1183 g_Sound_Delete('SOUND_WEAPON_FIRECGUN');
1184 g_Sound_Delete('SOUND_WEAPON_FIREBFG');
1185 g_Sound_Delete('SOUND_FIRE');
1186 g_Sound_Delete('SOUND_IGNITE');
1187 g_Sound_Delete('SOUND_WEAPON_STARTFIREBFG');
1188 g_Sound_Delete('SOUND_WEAPON_EXPLODEROCKET');
1189 g_Sound_Delete('SOUND_WEAPON_EXPLODEBFG');
1190 g_Sound_Delete('SOUND_WEAPON_BFGWATER');
1191 g_Sound_Delete('SOUND_WEAPON_EXPLODEPLASMA');
1192 g_Sound_Delete('SOUND_WEAPON_PLASMAWATER');
1193 g_Sound_Delete('SOUND_WEAPON_FIREBALL');
1194 g_Sound_Delete('SOUND_WEAPON_EXPLODEBALL');
1195 g_Sound_Delete('SOUND_WEAPON_FIREREV');
1196 g_Sound_Delete('SOUND_WEAPON_FLAMEON');
1197 g_Sound_Delete('SOUND_WEAPON_FLAMEOFF');
1198 g_Sound_Delete('SOUND_WEAPON_FLAMEWORK');
1199 g_Sound_Delete('SOUND_PLAYER_JETFLY');
1200 g_Sound_Delete('SOUND_PLAYER_JETON');
1201 g_Sound_Delete('SOUND_PLAYER_JETOFF');
1202 g_Sound_Delete('SOUND_PLAYER_CASING1');
1203 g_Sound_Delete('SOUND_PLAYER_CASING2');
1204 g_Sound_Delete('SOUND_PLAYER_SHELL1');
1205 g_Sound_Delete('SOUND_PLAYER_SHELL2');
1206 end;
1209 function GunHitPlayer (X, Y: Integer; vx, vy: Integer; dmg: Integer; SpawnerUID: Word; AllowPush: Boolean): Boolean;
1210 var
1211 i: Integer;
1212 begin
1213 result := false;
1214 for i := 0 to High(gPlayers) do
1215 begin
1216 if (gPlayers[i] <> nil) and gPlayers[i].alive and gPlayers[i].Collide(X, Y) then
1217 begin
1218 if HitPlayer(gPlayers[i], dmg, vx*10, vy*10-3, SpawnerUID, HIT_SOME) then
1219 begin
1220 if AllowPush then gPlayers[i].Push(vx, vy);
1221 result := true;
1222 end;
1223 end;
1224 end;
1225 end;
1228 function GunHit (X, Y: Integer; vx, vy: Integer; dmg: Integer; SpawnerUID: Word; AllowPush: Boolean): Byte;
1230 function monsCheck (mon: TMonster): Boolean;
1231 begin
1232 result := false; // don't stop
1233 if HitMonster(mon, dmg, vx*10, vy*10-3, SpawnerUID, HIT_SOME) then
1234 begin
1235 if AllowPush then mon.Push(vx, vy);
1236 result := true;
1237 end;
1238 end;
1240 begin
1241 result := 0;
1242 if GunHitPlayer(X, Y, vx, vy, dmg, SpawnerUID, AllowPush) then result := 1
1243 else if g_Mons_ForEachAliveAt(X, Y, 1, 1, monsCheck) then result := 2;
1244 end;
1247 (*
1248 procedure g_Weapon_gunOld(const x, y, xd, yd, v, dmg: Integer; SpawnerUID: Word; CheckTrigger: Boolean);
1249 var
1250 a: Integer;
1251 x2, y2: Integer;
1252 dx, dy: Integer;
1253 xe, ye: Integer;
1254 xi, yi: Integer;
1255 s, c: Extended;
1256 //vx, vy: Integer;
1257 xx, yy, d: Integer;
1258 i: Integer;
1259 t1, _collide: Boolean;
1260 w, h: Word;
1261 {$IF DEFINED(D2F_DEBUG)}
1262 stt: UInt64;
1263 showTime: Boolean = true;
1264 {$ENDIF}
1265 begin
1266 a := GetAngle(x, y, xd, yd)+180;
1268 SinCos(DegToRad(-a), s, c);
1270 if Abs(s) < 0.01 then s := 0;
1271 if Abs(c) < 0.01 then c := 0;
1273 x2 := x+Round(c*gMapInfo.Width);
1274 y2 := y+Round(s*gMapInfo.Width);
1276 t1 := gWalls <> nil;
1277 _collide := False;
1278 w := gMapInfo.Width;
1279 h := gMapInfo.Height;
1281 xe := 0;
1282 ye := 0;
1283 dx := x2-x;
1284 dy := y2-y;
1286 if (xd = 0) and (yd = 0) then Exit;
1288 if dx > 0 then xi := 1 else if dx < 0 then xi := -1 else xi := 0;
1289 if dy > 0 then yi := 1 else if dy < 0 then yi := -1 else yi := 0;
1291 dx := Abs(dx);
1292 dy := Abs(dy);
1294 if dx > dy then d := dx else d := dy;
1296 //blood vel, for Monster.Damage()
1297 //vx := (dx*10 div d)*xi;
1298 //vy := (dy*10 div d)*yi;
1300 {$IF DEFINED(D2F_DEBUG)}
1301 stt := getTimeMicro();
1302 {$ENDIF}
1304 xx := x;
1305 yy := y;
1307 for i := 1 to d do
1308 begin
1309 xe := xe+dx;
1310 ye := ye+dy;
1312 if xe > d then
1313 begin
1314 xe := xe-d;
1315 xx := xx+xi;
1316 end;
1318 if ye > d then
1319 begin
1320 ye := ye-d;
1321 yy := yy+yi;
1322 end;
1324 if (yy > h) or (yy < 0) then Break;
1325 if (xx > w) or (xx < 0) then Break;
1327 if t1 then
1328 if ByteBool(gCollideMap[yy, xx] and MARK_BLOCKED) then
1329 begin
1330 _collide := True;
1331 {$IF DEFINED(D2F_DEBUG)}
1332 stt := getTimeMicro()-stt;
1333 e_WriteLog(Format('*** old trace time: %u microseconds', [LongWord(stt)]), MSG_NOTIFY);
1334 showTime := false;
1335 {$ENDIF}
1336 g_GFX_Spark(xx-xi, yy-yi, 2+Random(2), 180+a, 0, 0);
1337 if g_Game_IsServer and g_Game_IsNet then
1338 MH_SEND_Effect(xx-xi, yy-yi, 180+a, NET_GFX_SPARK);
1339 end;
1341 if not _collide then
1342 begin
1343 _collide := GunHit(xx, yy, xi*v, yi*v, dmg, SpawnerUID, v <> 0) <> 0;
1344 end;
1346 if _collide then Break;
1347 end;
1349 {$IF DEFINED(D2F_DEBUG)}
1350 if showTime then
1351 begin
1352 stt := getTimeMicro()-stt;
1353 e_WriteLog(Format('*** old trace time: %u microseconds', [LongWord(stt)]), MSG_NOTIFY);
1354 end;
1355 {$ENDIF}
1357 if CheckTrigger and g_Game_IsServer then
1358 g_Triggers_PressL(X, Y, xx-xi, yy-yi, SpawnerUID, ACTIVATE_SHOT);
1359 end;
1360 *)
1363 //!!!FIXME!!!
1364 procedure g_Weapon_gun (const x, y, xd, yd, v, indmg: Integer; SpawnerUID: Word; CheckTrigger: Boolean);
1365 var
1366 x0, y0: Integer;
1367 x2, y2: Integer;
1368 xi, yi: Integer;
1369 wallDistSq: Integer = $3fffffff;
1370 spawnerPlr: TPlayer = nil;
1371 dmg: Integer;
1373 function doPlayerHit (idx: Integer; hx, hy: Integer): Boolean;
1374 begin
1375 result := false;
1376 if (idx < 0) or (idx > High(gPlayers)) then exit;
1377 if (gPlayers[idx] = nil) or not gPlayers[idx].alive then exit;
1378 if (spawnerPlr <> nil) then
1379 begin
1380 if ((gGameSettings.Options and (GAME_OPTION_TEAMHITTRACE or GAME_OPTION_TEAMDAMAGE)) = 0) and
1381 (spawnerPlr.Team <> TEAM_NONE) and (spawnerPlr.Team = gPlayers[idx].Team) then
1382 begin
1383 if (spawnerPlr <> gPlayers[idx]) and ((gGameSettings.Options and GAME_OPTION_TEAMABSORBDAMAGE) = 0) then
1384 dmg := Max(1, dmg div 2);
1385 exit;
1386 end;
1387 end;
1388 result := HitPlayer(gPlayers[idx], dmg, (xi*v)*10, (yi*v)*10-3, SpawnerUID, HIT_SOME);
1389 if result and (v <> 0) then gPlayers[idx].Push((xi*v), (yi*v));
1390 {$IF DEFINED(D2F_DEBUG)}
1391 //if result then e_WriteLog(Format(' PLAYER #%d HIT', [idx]), MSG_NOTIFY);
1392 {$ENDIF}
1393 end;
1395 function doMonsterHit (mon: TMonster; hx, hy: Integer): Boolean;
1396 begin
1397 result := false;
1398 if (mon = nil) then exit;
1399 result := HitMonster(mon, dmg, (xi*v)*10, (yi*v)*10-3, SpawnerUID, HIT_SOME);
1400 if result and (v <> 0) then mon.Push((xi*v), (yi*v));
1401 {$IF DEFINED(D2F_DEBUG)}
1402 //if result then e_WriteLog(Format(' MONSTER #%u HIT', [LongWord(mon.UID)]), MSG_NOTIFY);
1403 {$ENDIF}
1404 end;
1406 // collect players along hitray
1407 // return `true` if instant hit was detected
1408 function playerPossibleHit (): Boolean;
1409 var
1410 i: Integer;
1411 px, py, pw, ph: Integer;
1412 inx, iny: Integer;
1413 distSq: Integer;
1414 plr: TPlayer;
1415 begin
1416 result := false;
1417 for i := 0 to High(gPlayers) do
1418 begin
1419 plr := gPlayers[i];
1420 if (plr <> nil) and plr.alive then
1421 begin
1422 plr.getMapBox(px, py, pw, ph);
1423 if lineAABBIntersects(x, y, x2, y2, px, py, pw, ph, inx, iny) then
1424 begin
1425 distSq := distanceSq(x, y, inx, iny);
1426 if (distSq = 0) then
1427 begin
1428 // contains
1429 if doPlayerHit(i, x, y) then begin result := true; exit; end;
1430 end
1431 else if (distSq < wallDistSq) then
1432 begin
1433 appendHitTimePlr(distSq, i, inx, iny);
1434 end;
1435 end;
1436 end;
1437 end;
1438 end;
1440 procedure sqchecker (mon: TMonster);
1441 var
1442 mx, my, mw, mh: Integer;
1443 inx, iny: Integer;
1444 distSq: Integer;
1445 begin
1446 mon.getMapBox(mx, my, mw, mh);
1447 if lineAABBIntersects(x0, y0, x2, y2, mx, my, mw, mh, inx, iny) then
1448 begin
1449 distSq := distanceSq(x0, y0, inx, iny);
1450 if (distSq < wallDistSq) then appendHitTimeMon(distSq, mon, inx, iny);
1451 end;
1452 end;
1454 var
1455 a: Integer;
1456 dx, dy: Integer;
1457 xe, ye: Integer;
1458 s, c: Extended;
1459 i: Integer;
1460 wallHitFlag: Boolean = false;
1461 wallHitX: Integer = 0;
1462 wallHitY: Integer = 0;
1463 didHit: Boolean = false;
1464 {$IF DEFINED(D2F_DEBUG)}
1465 stt: UInt64;
1466 {$ENDIF}
1467 mit: PMonster;
1468 it: TMonsterGrid.Iter;
1469 begin
1470 (*
1471 if not gwep_debug_fast_trace then
1472 begin
1473 g_Weapon_gunOld(x, y, xd, yd, v, dmg, SpawnerUID, CheckTrigger);
1474 exit;
1475 end;
1476 *)
1478 if (xd = 0) and (yd = 0) then exit;
1480 if (g_GetUIDType(SpawnerUID) = UID_PLAYER) then
1481 spawnerPlr := g_Player_Get(SpawnerUID);
1483 dmg := indmg;
1485 //wgunMonHash.reset(); //FIXME: clear hash on level change
1486 wgunHitHeap.clear();
1487 wgunHitTimeUsed := 0;
1489 a := GetAngle(x, y, xd, yd)+180;
1491 SinCos(DegToRad(-a), s, c);
1493 if Abs(s) < 0.01 then s := 0;
1494 if Abs(c) < 0.01 then c := 0;
1496 x0 := x;
1497 y0 := y;
1498 x2 := x+Round(c*gMapInfo.Width);
1499 y2 := y+Round(s*gMapInfo.Width);
1501 dx := x2-x;
1502 dy := y2-y;
1504 if (dx > 0) then xi := 1 else if (dx < 0) then xi := -1 else xi := 0;
1505 if (dy > 0) then yi := 1 else if (dy < 0) then yi := -1 else yi := 0;
1507 {$IF DEFINED(D2F_DEBUG)}
1508 e_WriteLog(Format('GUN TRACE: (%d,%d) to (%d,%d)', [x, y, x2, y2]), TMsgType.Notify);
1509 stt := getTimeMicro();
1510 {$ENDIF}
1512 wallHitFlag := (g_Map_traceToNearestWall(x, y, x2, y2, @wallHitX, @wallHitY) <> nil);
1513 if wallHitFlag then
1514 begin
1515 x2 := wallHitX;
1516 y2 := wallHitY;
1517 wallDistSq := distanceSq(x, y, wallHitX, wallHitY);
1518 end
1519 else
1520 begin
1521 wallHitX := x2;
1522 wallHitY := y2;
1523 end;
1525 if playerPossibleHit() then exit; // instant hit
1527 // collect monsters
1528 //g_Mons_AlongLine(x, y, x2, y2, sqchecker);
1530 it := monsGrid.forEachAlongLine(x, y, x2, y2, -1);
1531 for mit in it do sqchecker(mit^);
1532 it.release();
1534 // here, we collected all monsters and players in `wgunHitHeap` and `wgunHitTime`
1535 // also, if `wallWasHit` is `true`, then `wallHitX` and `wallHitY` contains spark coords
1536 while (wgunHitHeap.count > 0) do
1537 begin
1538 // has some entities to check, do it
1539 i := wgunHitHeap.front;
1540 wgunHitHeap.popFront();
1541 // hitpoint
1542 xe := wgunHitTime[i].x;
1543 ye := wgunHitTime[i].y;
1544 // check if it is not behind the wall
1545 if (wgunHitTime[i].mon <> nil) then
1546 begin
1547 didHit := doMonsterHit(wgunHitTime[i].mon, xe, ye);
1548 end
1549 else
1550 begin
1551 didHit := doPlayerHit(wgunHitTime[i].plridx, xe, ye);
1552 end;
1553 if didHit then
1554 begin
1555 // need new coords for trigger
1556 wallHitX := xe;
1557 wallHitY := ye;
1558 wallHitFlag := false; // no sparks
1559 break;
1560 end;
1561 end;
1563 // need sparks?
1564 if wallHitFlag then
1565 begin
1566 {$IF DEFINED(D2F_DEBUG)}
1567 stt := getTimeMicro()-stt;
1568 e_WriteLog(Format('*** new trace time: %u microseconds', [LongWord(stt)]), TMsgType.Notify);
1569 {$ENDIF}
1570 {$IFDEF ENABLE_GFX}
1571 g_GFX_Spark(wallHitX, wallHitY, 2+Random(2), 180+a, 0, 0);
1572 {$ENDIF}
1573 if g_Game_IsServer and g_Game_IsNet then MH_SEND_Effect(wallHitX, wallHitY, 180+a, NET_GFX_SPARK);
1574 end
1575 else
1576 begin
1577 {$IF DEFINED(D2F_DEBUG)}
1578 stt := getTimeMicro()-stt;
1579 e_WriteLog(Format('*** new trace time: %u microseconds', [LongWord(stt)]), TMsgType.Notify);
1580 {$ENDIF}
1581 end;
1583 if CheckTrigger and g_Game_IsServer then g_Triggers_PressL(X, Y, wallHitX, wallHitY, SpawnerUID, ACTIVATE_SHOT);
1584 end;
1587 procedure g_Weapon_punch(x, y: Integer; d, SpawnerUID: Word);
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 := 39;
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 if g_Weapon_Hit(@obj, d, SpawnerUID, HIT_SOME) <> 0 then
1603 g_Sound_PlayExAt('SOUND_WEAPON_HITPUNCH', x, y)
1604 else
1605 g_Sound_PlayExAt('SOUND_WEAPON_MISSPUNCH', x, y);
1606 end;
1608 function g_Weapon_chainsaw(x, y: Integer; d, SpawnerUID: Word): Integer;
1609 var
1610 obj: TObj;
1611 begin
1612 obj.X := X;
1613 obj.Y := Y;
1614 obj.rect.X := 0;
1615 obj.rect.Y := 0;
1616 obj.rect.Width := 32;
1617 obj.rect.Height := 52;
1618 obj.Vel.X := 0;
1619 obj.Vel.Y := 0;
1620 obj.Accel.X := 0;
1621 obj.Accel.Y := 0;
1623 Result := g_Weapon_Hit(@obj, d, SpawnerUID, HIT_SOME);
1624 end;
1626 procedure g_Weapon_rocket(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1627 Silent: Boolean = False; compat: Boolean = true);
1628 var
1629 find_id: DWORD;
1630 dx, dy: Integer;
1631 begin
1632 if WID < 0 then
1633 find_id := FindShot()
1634 else
1635 begin
1636 find_id := WID;
1637 if Integer(find_id) >= High(Shots) then
1638 SetLength(Shots, find_id + 64)
1639 end;
1641 with Shots[find_id] do
1642 begin
1643 g_Obj_Init(@Obj);
1645 Obj.Rect.Width := SHOT_ROCKETLAUNCHER_WIDTH;
1646 Obj.Rect.Height := SHOT_ROCKETLAUNCHER_HEIGHT;
1648 if compat then
1649 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1650 else
1651 dx := -(Obj.Rect.Width div 2);
1652 dy := -(Obj.Rect.Height div 2);
1654 ShotType := WEAPON_ROCKETLAUNCHER;
1655 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 12);
1657 Animation.Invalidate;
1658 triggers := nil;
1659 end;
1661 Shots[find_id].SpawnerUID := SpawnerUID;
1663 if not Silent then
1664 g_Sound_PlayExAt('SOUND_WEAPON_FIREROCKET', x, y);
1665 end;
1667 procedure g_Weapon_revf(x, y, xd, yd: Integer; SpawnerUID, TargetUID: Word;
1668 WID: Integer = -1; Silent: Boolean = False);
1669 var
1670 find_id: DWORD;
1671 dx, dy: Integer;
1672 begin
1673 if WID < 0 then
1674 find_id := FindShot()
1675 else
1676 begin
1677 find_id := WID;
1678 if Integer(find_id) >= High(Shots) then
1679 SetLength(Shots, find_id + 64)
1680 end;
1682 with Shots[find_id] do
1683 begin
1684 g_Obj_Init(@Obj);
1686 Obj.Rect.Width := SHOT_SKELFIRE_WIDTH;
1687 Obj.Rect.Height := SHOT_SKELFIRE_HEIGHT;
1689 dx := -(Obj.Rect.Width div 2);
1690 dy := -(Obj.Rect.Height div 2);
1692 ShotType := WEAPON_SKEL_FIRE;
1693 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 12);
1695 triggers := nil;
1696 target := TargetUID;
1697 Animation := TAnimState.Create(True, 5, 2); // !!! put values into table
1698 end;
1700 Shots[find_id].SpawnerUID := SpawnerUID;
1702 if not Silent then
1703 g_Sound_PlayExAt('SOUND_WEAPON_FIREREV', x, y);
1704 end;
1706 procedure g_Weapon_plasma(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1707 Silent: Boolean = False; compat: Boolean = true);
1708 var
1709 find_id: DWORD;
1710 dx, dy: Integer;
1711 begin
1712 if WID < 0 then
1713 find_id := FindShot()
1714 else
1715 begin
1716 find_id := WID;
1717 if Integer(find_id) >= High(Shots) then
1718 SetLength(Shots, find_id + 64);
1719 end;
1721 with Shots[find_id] do
1722 begin
1723 g_Obj_Init(@Obj);
1725 Obj.Rect.Width := SHOT_PLASMA_WIDTH;
1726 Obj.Rect.Height := SHOT_PLASMA_HEIGHT;
1728 if compat then
1729 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1730 else
1731 dx := -(Obj.Rect.Width div 2);
1732 dy := -(Obj.Rect.Height div 2);
1734 ShotType := WEAPON_PLASMA;
1735 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1737 triggers := nil;
1738 Animation := TAnimState.Create(True, 5, 2); // !!! put values into table
1739 end;
1741 Shots[find_id].SpawnerUID := SpawnerUID;
1743 if not Silent then
1744 g_Sound_PlayExAt('SOUND_WEAPON_FIREPLASMA', x, y);
1745 end;
1747 procedure g_Weapon_flame(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1748 Silent: Boolean = False; compat: Boolean = true);
1749 var
1750 find_id: DWORD;
1751 dx, dy: Integer;
1752 begin
1753 if WID < 0 then
1754 find_id := FindShot()
1755 else
1756 begin
1757 find_id := WID;
1758 if Integer(find_id) >= High(Shots) then
1759 SetLength(Shots, find_id + 64);
1760 end;
1762 with Shots[find_id] do
1763 begin
1764 g_Obj_Init(@Obj);
1766 Obj.Rect.Width := SHOT_FLAME_WIDTH;
1767 Obj.Rect.Height := SHOT_FLAME_HEIGHT;
1769 if compat then
1770 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1771 else
1772 dx := -(Obj.Rect.Width div 2);
1773 dy := -(Obj.Rect.Height div 2);
1775 ShotType := WEAPON_FLAMETHROWER;
1776 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1778 triggers := nil;
1779 Animation.Invalidate;
1780 end;
1782 Shots[find_id].SpawnerUID := SpawnerUID;
1784 // if not Silent then
1785 // g_Sound_PlayExAt('SOUND_WEAPON_FIREPLASMA', x, y);
1786 end;
1788 procedure g_Weapon_ball1(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1789 Silent: Boolean = False; compat: Boolean = true);
1790 var
1791 find_id: DWORD;
1792 dx, dy: Integer;
1793 begin
1794 if WID < 0 then
1795 find_id := FindShot()
1796 else
1797 begin
1798 find_id := WID;
1799 if Integer(find_id) >= High(Shots) then
1800 SetLength(Shots, find_id + 64)
1801 end;
1803 with Shots[find_id] do
1804 begin
1805 g_Obj_Init(@Obj);
1807 Obj.Rect.Width := 16;
1808 Obj.Rect.Height := 16;
1810 if compat then
1811 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1812 else
1813 dx := -(Obj.Rect.Width div 2);
1814 dy := -(Obj.Rect.Height div 2);
1816 ShotType := WEAPON_IMP_FIRE;
1817 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1819 triggers := nil;
1820 Animation := TAnimState.Create(True, 4, 2); // !!! put values into table
1821 end;
1823 Shots[find_id].SpawnerUID := SpawnerUID;
1825 if not Silent then
1826 g_Sound_PlayExAt('SOUND_WEAPON_FIREBALL', x, y);
1827 end;
1829 procedure g_Weapon_ball2(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1830 Silent: Boolean = False; compat: Boolean = true);
1831 var
1832 find_id: DWORD;
1833 dx, dy: Integer;
1834 begin
1835 if WID < 0 then
1836 find_id := FindShot()
1837 else
1838 begin
1839 find_id := WID;
1840 if Integer(find_id) >= High(Shots) then
1841 SetLength(Shots, find_id + 64)
1842 end;
1844 with Shots[find_id] do
1845 begin
1846 g_Obj_Init(@Obj);
1848 Obj.Rect.Width := 16;
1849 Obj.Rect.Height := 16;
1851 if compat then
1852 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1853 else
1854 dx := -(Obj.Rect.Width div 2);
1855 dy := -(Obj.Rect.Height div 2);
1857 ShotType := WEAPON_CACO_FIRE;
1858 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1860 triggers := nil;
1861 Animation := TAnimState.Create(True, 4, 2); // !!! put values into table
1862 end;
1864 Shots[find_id].SpawnerUID := SpawnerUID;
1866 if not Silent then
1867 g_Sound_PlayExAt('SOUND_WEAPON_FIREBALL', x, y);
1868 end;
1870 procedure g_Weapon_ball7(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1871 Silent: Boolean = False; compat: Boolean = true);
1872 var
1873 find_id: DWORD;
1874 dx, dy: Integer;
1875 begin
1876 if WID < 0 then
1877 find_id := FindShot()
1878 else
1879 begin
1880 find_id := WID;
1881 if Integer(find_id) >= High(Shots) then
1882 SetLength(Shots, find_id + 64)
1883 end;
1885 with Shots[find_id] do
1886 begin
1887 g_Obj_Init(@Obj);
1889 Obj.Rect.Width := 32;
1890 Obj.Rect.Height := 16;
1892 if compat then
1893 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1894 else
1895 dx := -(Obj.Rect.Width div 2);
1896 dy := -(Obj.Rect.Height div 2);
1898 ShotType := WEAPON_BARON_FIRE;
1899 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1901 triggers := nil;
1902 Animation := TAnimState.Create(True, 4, 2); // !!! put values into table
1903 end;
1905 Shots[find_id].SpawnerUID := SpawnerUID;
1907 if not Silent then
1908 g_Sound_PlayExAt('SOUND_WEAPON_FIREBALL', x, y);
1909 end;
1911 procedure g_Weapon_aplasma(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1912 Silent: Boolean = False; compat: Boolean = true);
1913 var
1914 find_id: DWORD;
1915 dx, dy: Integer;
1916 begin
1917 if WID < 0 then
1918 find_id := FindShot()
1919 else
1920 begin
1921 find_id := WID;
1922 if Integer(find_id) >= High(Shots) then
1923 SetLength(Shots, find_id + 64)
1924 end;
1926 with Shots[find_id] do
1927 begin
1928 g_Obj_Init(@Obj);
1930 Obj.Rect.Width := 16;
1931 Obj.Rect.Height := 16;
1933 if compat then
1934 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1935 else
1936 dx := -(Obj.Rect.Width div 2);
1937 dy := -(Obj.Rect.Height div 2);
1939 ShotType := WEAPON_BSP_FIRE;
1940 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1942 triggers := nil;
1944 Animation := TAnimState.Create(True, 4, 2); // !!! put values into table
1945 end;
1947 Shots[find_id].SpawnerUID := SpawnerUID;
1949 if not Silent then
1950 g_Sound_PlayExAt('SOUND_WEAPON_FIREPLASMA', x, y);
1951 end;
1953 procedure g_Weapon_manfire(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1954 Silent: Boolean = False; compat: Boolean = true);
1955 var
1956 find_id: DWORD;
1957 dx, dy: Integer;
1958 begin
1959 if WID < 0 then
1960 find_id := FindShot()
1961 else
1962 begin
1963 find_id := WID;
1964 if Integer(find_id) >= High(Shots) then
1965 SetLength(Shots, find_id + 64)
1966 end;
1968 with Shots[find_id] do
1969 begin
1970 g_Obj_Init(@Obj);
1972 Obj.Rect.Width := 32;
1973 Obj.Rect.Height := 32;
1975 if compat then
1976 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
1977 else
1978 dx := -(Obj.Rect.Width div 2);
1979 dy := -(Obj.Rect.Height div 2);
1981 ShotType := WEAPON_MANCUB_FIRE;
1982 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1984 triggers := nil;
1986 Animation := TAnimState.Create(True, 4, 2); // !!! put values into table
1987 end;
1989 Shots[find_id].SpawnerUID := SpawnerUID;
1991 if not Silent then
1992 g_Sound_PlayExAt('SOUND_WEAPON_FIREBALL', x, y);
1993 end;
1995 procedure g_Weapon_bfgshot(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1996 Silent: Boolean = False; compat: Boolean = true);
1997 var
1998 find_id: DWORD;
1999 dx, dy: Integer;
2000 begin
2001 if WID < 0 then
2002 find_id := FindShot()
2003 else
2004 begin
2005 find_id := WID;
2006 if Integer(find_id) >= High(Shots) then
2007 SetLength(Shots, find_id + 64)
2008 end;
2010 with Shots[find_id] do
2011 begin
2012 g_Obj_Init(@Obj);
2014 Obj.Rect.Width := SHOT_BFG_WIDTH;
2015 Obj.Rect.Height := SHOT_BFG_HEIGHT;
2017 if compat then
2018 dx := IfThen(xd > x, -Obj.Rect.Width, 0)
2019 else
2020 dx := -(Obj.Rect.Width div 2);
2021 dy := -(Obj.Rect.Height div 2);
2023 ShotType := WEAPON_BFG;
2024 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
2026 triggers := nil;
2027 Animation := TAnimState.Create(True, 6, 2); // !!! put values into table
2028 end;
2030 Shots[find_id].SpawnerUID := SpawnerUID;
2032 if not Silent then
2033 g_Sound_PlayExAt('SOUND_WEAPON_FIREBFG', x, y);
2034 end;
2036 procedure g_Weapon_bfghit(x, y: Integer);
2037 begin
2038 {$IFDEF ENABLE_GFX}
2039 g_GFX_QueueEffect(R_GFX_BFG_HIT, x - 32, y - 32);
2040 {$ENDIF}
2041 end;
2043 procedure g_Weapon_pistol(x, y, xd, yd: Integer; SpawnerUID: Word;
2044 Silent: Boolean = False);
2045 begin
2046 if not Silent then
2047 g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', x, y);
2049 g_Weapon_gun(x, y, xd, yd, 1, 3, SpawnerUID, True);
2050 if gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF] then
2051 begin
2052 if ABS(x-xd) >= ABS(y-yd) then
2053 begin
2054 g_Weapon_gun(x, y+1, xd, yd+1, 1, 3, SpawnerUID, False);
2055 g_Weapon_gun(x, y-1, xd, yd-1, 1, 2, SpawnerUID, False);
2056 end
2057 else
2058 begin
2059 g_Weapon_gun(x+1, y, xd+1, yd, 1, 3, SpawnerUID, False);
2060 g_Weapon_gun(x-1, y, xd-1, yd, 1, 2, SpawnerUID, False);
2061 end;
2062 end;
2063 end;
2065 procedure g_Weapon_mgun(x, y, xd, yd: Integer; SpawnerUID: Word;
2066 Silent: Boolean = False);
2067 begin
2068 if not Silent then
2069 if gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', x, y);
2071 g_Weapon_gun(x, y, xd, yd, 1, 3, SpawnerUID, True);
2072 if (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF]) and
2073 (g_GetUIDType(SpawnerUID) = UID_PLAYER) then
2074 begin
2075 if ABS(x-xd) >= ABS(y-yd) then
2076 begin
2077 g_Weapon_gun(x, y+1, xd, yd+1, 1, 2, SpawnerUID, False);
2078 g_Weapon_gun(x, y-1, xd, yd-1, 1, 2, SpawnerUID, False);
2079 end
2080 else
2081 begin
2082 g_Weapon_gun(x+1, y, xd+1, yd, 1, 2, SpawnerUID, False);
2083 g_Weapon_gun(x-1, y, xd-1, yd, 1, 2, SpawnerUID, False);
2084 end;
2085 end;
2086 end;
2088 procedure g_Weapon_shotgun(x, y, xd, yd: Integer; SpawnerUID: Word;
2089 Silent: Boolean = False);
2090 var
2091 i, j, k: Integer;
2092 begin
2093 if not Silent then
2094 if gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', x, y);
2096 for i := 0 to 9 do
2097 begin
2098 j := 0; k := 0;
2099 if ABS(x-xd) >= ABS(y-yd) then j := Random(17) - 8 else k := Random(17) - 8; // -8 .. 8
2100 g_Weapon_gun(x+k, y+j, xd+k, yd+j, IfThen(i mod 2 <> 0, 1, 0), 3, SpawnerUID, i=0);
2101 end;
2102 end;
2104 procedure g_Weapon_dshotgun(x, y, xd, yd: Integer; SpawnerUID: Word;
2105 Silent: Boolean = False);
2106 var
2107 a, i, j, k: Integer;
2108 begin
2109 if not Silent then
2110 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN2', x, y);
2112 if gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF] then a := 25 else a := 20;
2113 for i := 0 to a do
2114 begin
2115 j := 0; k := 0;
2116 if ABS(x-xd) >= ABS(y-yd) then j := Random(41) - 20 else k := Random(41) - 20; // -20 .. 20
2117 g_Weapon_gun(x+k, y+j, xd+k, yd+j, IfThen(i mod 3 <> 0, 0, 1), 3, SpawnerUID, i=0);
2118 end;
2119 end;
2121 procedure g_Weapon_PreUpdate();
2122 var
2123 i: Integer;
2124 begin
2125 if Shots = nil then Exit;
2126 for i := 0 to High(Shots) do
2127 if Shots[i].ShotType <> 0 then
2128 begin
2129 Shots[i].Obj.oldX := Shots[i].Obj.X;
2130 Shots[i].Obj.oldY := Shots[i].Obj.Y;
2131 end;
2132 end;
2134 procedure g_Weapon_Update();
2135 var
2136 i, a, h, cx, cy, oldvx, oldvy, tf: Integer;
2137 t: DWArray;
2138 st: Word;
2139 o: TObj;
2140 spl: Boolean;
2141 Loud: Boolean;
2142 {$IFDEF ENABLE_GFX}
2143 var tcx, tcy: Integer;
2144 {$ENDIF}
2145 begin
2146 if Shots = nil then
2147 Exit;
2149 for i := 0 to High(Shots) do
2150 begin
2151 if Shots[i].ShotType = 0 then
2152 Continue;
2154 Loud := True;
2156 with Shots[i] do
2157 begin
2158 Timeout := Timeout - 1;
2159 oldvx := Obj.Vel.X;
2160 oldvy := Obj.Vel.Y;
2161 // Àêòèâèðîâàòü òðèããåðû ïî ïóòè (êðîìå óæå àêòèâèðîâàííûõ):
2162 if (Stopped = 0) and g_Game_IsServer then
2163 t := g_Triggers_PressR(Obj.X, Obj.Y, Obj.Rect.Width, Obj.Rect.Height,
2164 SpawnerUID, ACTIVATE_SHOT, triggers)
2165 else
2166 t := nil;
2168 if t <> nil then
2169 begin
2170 // Ïîïîëíÿåì ñïèñîê àêòèâèðîâàííûõ òðèããåðîâ:
2171 if triggers = nil then
2172 triggers := t
2173 else
2174 begin
2175 h := High(t);
2177 for a := 0 to h do
2178 if not InDWArray(t[a], triggers) then
2179 begin
2180 SetLength(triggers, Length(triggers)+1);
2181 triggers[High(triggers)] := t[a];
2182 end;
2183 end;
2184 end;
2186 // Àíèìàöèÿ ñíàðÿäà:
2187 if Animation.IsValid() then
2188 Animation.Update();
2190 // Äâèæåíèå:
2191 spl := (ShotType <> WEAPON_PLASMA) and
2192 (ShotType <> WEAPON_BFG) and
2193 (ShotType <> WEAPON_BSP_FIRE) and
2194 (ShotType <> WEAPON_FLAMETHROWER);
2196 if Stopped = 0 then
2197 begin
2198 st := g_Obj_Move_Projectile(@Obj, False, spl);
2199 end
2200 else
2201 begin
2202 st := 0;
2203 end;
2204 positionChanged(); // this updates spatial accelerators
2206 if WordBool(st and MOVE_FALLOUT) or (Obj.X < -1000) or
2207 (Obj.X > gMapInfo.Width+1000) or (Obj.Y < -1000) then
2208 begin
2209 // Íà êëèåíòå ñêîðåå âñåãî è òàê óæå âûïàë.
2210 ShotType := 0;
2211 Animation.Invalidate();
2212 Continue;
2213 end;
2215 cx := Obj.X + (Obj.Rect.Width div 2);
2216 cy := Obj.Y + (Obj.Rect.Height div 2);
2218 case ShotType of
2219 WEAPON_ROCKETLAUNCHER, WEAPON_SKEL_FIRE: // Ðàêåòû è ñíàðÿäû Ñêåëåòà
2220 begin
2221 // Âûëåòåëà èç âîäû:
2222 if WordBool(st and MOVE_HITAIR) then
2223 g_Obj_SetSpeed(@Obj, 12);
2225 // Â âîäå øëåéô - ïóçûðè, â âîçäóõå øëåéô - äûì:
2226 if WordBool(st and MOVE_INWATER) then
2227 begin
2228 {$IFDEF ENABLE_GFX}
2229 g_GFX_Bubbles(cx, cy, 1+Random(3), 16, 16);
2230 {$ENDIF}
2231 if Random(2) = 0
2232 then g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', cx, cy)
2233 else g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', cx, cy);
2234 end
2235 else
2236 begin
2237 {$IFDEF ENABLE_GFX}
2238 g_GFX_QueueEffect(R_GFX_SMOKE_TRANS, Obj.X-14+Random(9), cy-20+Random(9));
2239 {$ENDIF}
2240 end;
2242 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2243 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL)) or
2244 (g_Weapon_Hit(@Obj, 10, SpawnerUID, HIT_SOME, False) <> 0) or
2245 (Timeout < 1) then
2246 begin
2247 Obj.Vel.X := 0;
2248 Obj.Vel.Y := 0;
2250 g_Weapon_Explode(cx, cy, 60, SpawnerUID);
2252 if ShotType = WEAPON_SKEL_FIRE then
2253 begin // Âçðûâ ñíàðÿäà Ñêåëåòà
2254 {$IFDEF ENABLE_GFX}
2255 g_GFX_QueueEffect(R_GFX_EXPLODE_SKELFIRE, Obj.X + 32 - 58, Obj.Y + 8 - 36);
2256 g_DynLightExplosion((Obj.X+32), (Obj.Y+8), 64, 1, 0, 0);
2257 {$ENDIF}
2258 end
2259 else
2260 begin // Âçðûâ Ðàêåòû
2261 {$IFDEF ENABLE_GFX}
2262 g_GFX_QueueEffect(R_GFX_EXPLODE_ROCKET, cx - 64, cy - 64);
2263 g_DynLightExplosion(cx, cy, 64, 1, 0, 0);
2264 {$ENDIF}
2265 end;
2267 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEROCKET', Obj.X, Obj.Y);
2269 ShotType := 0;
2270 end;
2272 if ShotType = WEAPON_SKEL_FIRE then
2273 begin // Ñàìîíàâîäêà ñíàðÿäà Ñêåëåòà:
2274 if GetPos(target, @o) then
2275 throw(i, Obj.X, Obj.Y,
2276 o.X+o.Rect.X+(o.Rect.Width div 2)+o.Vel.X+o.Accel.X,
2277 o.Y+o.Rect.Y+(o.Rect.Height div 2)+o.Vel.Y+o.Accel.Y,
2278 12);
2279 end;
2280 end;
2282 WEAPON_PLASMA, WEAPON_BSP_FIRE: // Ïëàçìà, ïëàçìà Àðàõíàòðîíà
2283 begin
2284 // Ïîïàëà â âîäó - ýëåêòðîøîê ïî âîäå:
2285 if WordBool(st and (MOVE_INWATER or MOVE_HITWATER)) then
2286 begin
2287 g_Sound_PlayExAt('SOUND_WEAPON_PLASMAWATER', Obj.X, Obj.Y);
2288 if g_Game_IsServer then CheckTrap(i, 10, HIT_ELECTRO);
2289 ShotType := 0;
2290 Continue;
2291 end;
2293 // Âåëè÷èíà óðîíà:
2294 if (ShotType = WEAPON_PLASMA) and
2295 (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF]) then
2296 a := 10
2297 else
2298 a := 5;
2300 if ShotType = WEAPON_BSP_FIRE then
2301 a := 10;
2303 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2304 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL)) or
2305 (g_Weapon_Hit(@Obj, a, SpawnerUID, HIT_SOME, False) <> 0) or
2306 (Timeout < 1) then
2307 begin
2308 {$IFDEF ENABLE_GFX}
2309 if ShotType = WEAPON_PLASMA then
2310 g_GFX_QueueEffect(R_GFX_EXPLODE_PLASMA, cx - 16, cy - 16)
2311 else
2312 g_GFX_QueueEffect(R_GFX_EXPLODE_BSPFIRE, cx - 16, cy - 16);
2313 g_DynLightExplosion(cx, cy, 32, 0, 0.5, 0.5);
2314 {$ENDIF}
2315 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEPLASMA', Obj.X, Obj.Y);
2316 ShotType := 0;
2317 end;
2318 end;
2320 WEAPON_FLAMETHROWER: // Îãíåìåò
2321 begin
2322 // Ñî âðåìåíåì óìèðàåò
2323 if (Timeout < 1) then
2324 begin
2325 ShotType := 0;
2326 Continue;
2327 end;
2328 // Ïîä âîäîé òîæå
2329 if WordBool(st and (MOVE_HITWATER or MOVE_INWATER)) then
2330 begin
2331 if WordBool(st and MOVE_HITWATER) then
2332 begin
2333 {$IFDEF ENABLE_GFX}
2334 tcx := Random(8);
2335 tcy := Random(8);
2336 g_GFX_QueueEffect(R_GFX_SMOKE, cx-4+tcx-(R_GFX_SMOKE_WIDTH div 2), cy-4+tcy-(R_GFX_SMOKE_HEIGHT div 2));
2337 {$ENDIF}
2338 end
2339 else
2340 begin
2341 {$IFDEF ENABLE_GFX}
2342 g_GFX_Bubbles(cx, cy, 1+Random(3), 16, 16);
2343 {$ENDIF}
2344 if Random(2) = 0
2345 then g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', cx, cy)
2346 else g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', cx, cy);
2347 end;
2348 ShotType := 0;
2349 Continue;
2350 end;
2352 // Ãðàâèòàöèÿ
2353 if Stopped = 0 then
2354 Obj.Accel.Y := Obj.Accel.Y + 1;
2355 // Ïîïàëè â ñòåíó èëè â âîäó:
2356 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL or MOVE_HITWATER)) then
2357 begin
2358 // Ïðèëèïàåì:
2359 Obj.Vel.X := 0;
2360 Obj.Vel.Y := 0;
2361 Obj.Accel.Y := 0;
2362 if WordBool(st and MOVE_HITWALL) then
2363 Stopped := MOVE_HITWALL
2364 else if WordBool(st and MOVE_HITLAND) then
2365 Stopped := MOVE_HITLAND
2366 else if WordBool(st and MOVE_HITCEIL) then
2367 Stopped := MOVE_HITCEIL;
2368 end;
2370 a := IfThen(Stopped = 0, 10, 1);
2371 // Åñëè â êîãî-òî ïîïàëè
2372 if g_Weapon_Hit(@Obj, a, SpawnerUID, HIT_FLAME, False) <> 0 then
2373 begin
2374 // HIT_FLAME ñàì ïîäîææåò
2375 // Åñëè â ïîëåòå ïîïàëè, èñ÷åçàåì
2376 if Stopped = 0 then
2377 ShotType := 0;
2378 end;
2380 if Stopped = 0 then
2381 tf := 2
2382 else
2383 tf := 3;
2385 if (gTime mod LongWord(tf) = 0) then
2386 begin
2387 {$IFDEF ENABLE_GFX}
2388 case Stopped of
2389 MOVE_HITWALL: begin tcx := cx-4+Random(8); tcy := cy-12+Random(24); end;
2390 MOVE_HITLAND: begin tcx := cx-12+Random(24); tcy := cy-10+Random(8); end;
2391 MOVE_HITCEIL: begin tcx := cx-12+Random(24); tcy := cy+6+Random(8); end;
2392 else begin tcx := cx-4+Random(8); tcy := cy-4+Random(8); end;
2393 end;
2394 g_GFX_QueueEffect(R_GFX_FLAME_RAND, tcx - (R_GFX_FLAME_WIDTH div 2), tcy - (R_GFX_FLAME_HEIGHT div 2));
2395 //g_DynLightExplosion(tcx, tcy, 1, 1, 0.8, 0.3);
2396 {$ENDIF}
2397 end;
2398 end;
2400 WEAPON_BFG: // BFG
2401 begin
2402 // Ïîïàëà â âîäó - ýëåêòðîøîê ïî âîäå:
2403 if WordBool(st and (MOVE_INWATER or MOVE_HITWATER)) then
2404 begin
2405 g_Sound_PlayExAt('SOUND_WEAPON_BFGWATER', Obj.X, Obj.Y);
2406 if g_Game_IsServer then CheckTrap(i, 1000, HIT_ELECTRO);
2407 ShotType := 0;
2408 Continue;
2409 end;
2411 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2412 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL)) or
2413 (g_Weapon_Hit(@Obj, SHOT_BFG_DAMAGE, SpawnerUID, HIT_BFG, False) <> 0) or
2414 (Timeout < 1) then
2415 begin
2416 // Ëó÷è BFG:
2417 if g_Game_IsServer then g_Weapon_BFG9000(cx, cy, SpawnerUID);
2418 {$IFDEF ENABLE_GFX}
2419 g_GFX_QueueEffect(R_GFX_EXPLODE_BFG, cx - 64, cy - 64);
2420 g_DynLightExplosion(cx, cy, 96, 0, 1, 0);
2421 {$ENDIF}
2422 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBFG', Obj.X, Obj.Y);
2423 ShotType := 0;
2424 end;
2425 end;
2427 WEAPON_IMP_FIRE, WEAPON_CACO_FIRE, WEAPON_BARON_FIRE: // Âûñòðåëû Áåñà, Êàêîäåìîíà Ðûöàðÿ/Áàðîíà àäà
2428 begin
2429 // Âûëåòåë èç âîäû:
2430 if WordBool(st and MOVE_HITAIR) then
2431 g_Obj_SetSpeed(@Obj, 16);
2433 // Âåëè÷èíà óðîíà:
2434 if ShotType = WEAPON_IMP_FIRE then
2435 a := 5
2436 else
2437 if ShotType = WEAPON_CACO_FIRE then
2438 a := 20
2439 else
2440 a := 40;
2442 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2443 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL)) or
2444 (g_Weapon_Hit(@Obj, a, SpawnerUID, HIT_SOME) <> 0) or
2445 (Timeout < 1) then
2446 begin
2447 {$IFDEF ENABLE_GFX}
2448 case ShotType of
2449 WEAPON_IMP_FIRE: g_GFX_QueueEffect(R_GFX_EXPLODE_IMPFIRE, cx - 32, cy - 32);
2450 WEAPON_CACO_FIRE: g_GFX_QueueEffect(R_GFX_EXPLODE_CACOFIRE, cx - 32, cy - 32);
2451 WEAPON_BARON_FIRE: g_GFX_QueueEffect(R_GFX_EXPLODE_BARONFIRE, cx - 32, cy - 32);
2452 end;
2453 {$ENDIF}
2454 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBALL', Obj.X, Obj.Y);
2455 ShotType := 0;
2456 end;
2457 end;
2459 WEAPON_MANCUB_FIRE: // Âûñòðåë Ìàíêóáóñà
2460 begin
2461 // Âûëåòåë èç âîäû:
2462 if WordBool(st and MOVE_HITAIR) then
2463 g_Obj_SetSpeed(@Obj, 16);
2465 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2466 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL)) or
2467 (g_Weapon_Hit(@Obj, 40, SpawnerUID, HIT_SOME, False) <> 0) or
2468 (Timeout < 1) then
2469 begin
2470 // Âçðûâ:
2471 {$IFDEF ENABLE_GFX}
2472 g_GFX_QueueEffect(R_GFX_EXPLODE_ROCKET, cx - 64, cy - 64);
2473 {$ENDIF}
2474 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBALL', Obj.X, Obj.Y);
2475 ShotType := 0;
2476 end;
2477 end;
2478 end; // case ShotType of...
2480 // Åñëè ñíàðÿäà óæå íåò, óäàëÿåì àíèìàöèþ:
2481 if (ShotType = 0) then
2482 begin
2483 if gGameSettings.GameType = GT_SERVER then
2484 MH_SEND_DeleteShot(i, Obj.X, Obj.Y, Loud);
2485 Animation.Invalidate;
2486 end
2487 else if (ShotType <> WEAPON_FLAMETHROWER) and ((oldvx <> Obj.Vel.X) or (oldvy <> Obj.Vel.Y)) then
2488 if gGameSettings.GameType = GT_SERVER then
2489 MH_SEND_UpdateShot(i);
2490 end;
2491 end;
2492 end;
2494 function g_Weapon_Danger(UID: Word; X, Y: Integer; Width, Height: Word; Time: Byte): Boolean;
2495 var
2496 a: Integer;
2497 begin
2498 Result := False;
2500 if Shots = nil then
2501 Exit;
2503 for a := 0 to High(Shots) do
2504 if (Shots[a].ShotType <> 0) and (Shots[a].SpawnerUID <> UID) then
2505 if ((Shots[a].Obj.Vel.Y = 0) and (Shots[a].Obj.Vel.X > 0) and (Shots[a].Obj.X < X)) or
2506 (Shots[a].Obj.Vel.Y = 0) and (Shots[a].Obj.Vel.X < 0) and (Shots[a].Obj.X > X) then
2507 if (Abs(X-Shots[a].Obj.X) < Abs(Shots[a].Obj.Vel.X*Time)) and
2508 g_Collide(X, Y, Width, Height, X, Shots[a].Obj.Y,
2509 Shots[a].Obj.Rect.Width, Shots[a].Obj.Rect.Height) and
2510 g_TraceVector(X, Y, Shots[a].Obj.X, Shots[a].Obj.Y) then
2511 begin
2512 Result := True;
2513 Exit;
2514 end;
2515 end;
2517 procedure g_Weapon_SaveState (st: TStream);
2518 var
2519 count, i, j: Integer;
2520 begin
2521 // Ñ÷èòàåì êîëè÷åñòâî ñóùåñòâóþùèõ ñíàðÿäîâ
2522 count := 0;
2523 for i := 0 to High(Shots) do if (Shots[i].ShotType <> 0) then Inc(count);
2525 // Êîëè÷åñòâî ñíàðÿäîâ
2526 utils.WriteInt(st, count);
2528 if (count = 0) then exit;
2530 for i := 0 to High(Shots) do
2531 begin
2532 if Shots[i].ShotType <> 0 then
2533 begin
2534 // Ñèãíàòóðà ñíàðÿäà
2535 utils.writeSign(st, 'SHOT');
2536 utils.writeInt(st, Byte(0)); // version
2537 // Òèï ñíàðÿäà
2538 utils.writeInt(st, Byte(Shots[i].ShotType));
2539 // Öåëü
2540 utils.writeInt(st, Word(Shots[i].Target));
2541 // UID ñòðåëÿâøåãî
2542 utils.writeInt(st, Word(Shots[i].SpawnerUID));
2543 // Ðàçìåð ïîëÿ Triggers
2544 utils.writeInt(st, Integer(Length(Shots[i].Triggers)));
2545 // Òðèããåðû, àêòèâèðîâàííûå âûñòðåëîì
2546 for j := 0 to Length(Shots[i].Triggers)-1 do utils.writeInt(st, LongWord(Shots[i].Triggers[j]));
2547 // Îáúåêò ñíàðÿäà
2548 Obj_SaveState(st, @Shots[i].Obj);
2549 // Êîñòûëèíà åáàíàÿ
2550 utils.writeInt(st, Byte(Shots[i].Stopped));
2551 end;
2552 end;
2553 end;
2555 procedure g_Weapon_LoadState (st: TStream);
2556 var
2557 count, tc, i, j: Integer;
2558 begin
2559 if (st = nil) then exit;
2561 // Êîëè÷åñòâî ñíàðÿäîâ
2562 count := utils.readLongInt(st);
2563 if (count < 0) or (count > 1024*1024) then raise XStreamError.Create('invalid shots counter');
2565 SetLength(Shots, count);
2567 if (count = 0) then exit;
2569 for i := 0 to count-1 do
2570 begin
2571 // Ñèãíàòóðà ñíàðÿäà
2572 if not utils.checkSign(st, 'SHOT') then raise XStreamError.Create('invalid shot signature');
2573 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid shot version');
2574 // Òèï ñíàðÿäà:
2575 Shots[i].ShotType := utils.readByte(st);
2576 // Öåëü
2577 Shots[i].Target := utils.readWord(st);
2578 // UID ñòðåëÿâøåãî
2579 Shots[i].SpawnerUID := utils.readWord(st);
2580 // Ðàçìåð ïîëÿ Triggers
2581 tc := utils.readLongInt(st);
2582 if (tc < 0) or (tc > 1024*1024) then raise XStreamError.Create('invalid shot triggers counter');
2583 SetLength(Shots[i].Triggers, tc);
2584 // Òðèããåðû, àêòèâèðîâàííûå âûñòðåëîì
2585 for j := 0 to tc-1 do Shots[i].Triggers[j] := utils.readLongWord(st);
2586 // Îáúåêò ïðåäìåòà
2587 Obj_LoadState(@Shots[i].Obj, st);
2588 // Êîñòûëèíà åáàíàÿ
2589 Shots[i].Stopped := utils.readByte(st);
2591 // Óñòàíîâêà òåêñòóðû èëè àíèìàöèè
2592 Shots[i].Animation.Invalidate;
2594 case Shots[i].ShotType of
2595 WEAPON_ROCKETLAUNCHER, WEAPON_SKEL_FIRE:
2596 begin
2597 end;
2598 WEAPON_PLASMA:
2599 begin
2600 Shots[i].Animation := TAnimState.Create(True, 5, 2); // !!! put values into table
2601 end;
2602 WEAPON_BFG:
2603 begin
2604 Shots[i].Animation := TAnimState.Create(True, 6, 2); // !!! put values into table
2605 end;
2606 WEAPON_IMP_FIRE:
2607 begin
2608 Shots[i].Animation := TAnimState.Create(True, 4, 2); // !!! put values into table
2609 end;
2610 WEAPON_BSP_FIRE:
2611 begin
2612 Shots[i].Animation := TAnimState.Create(True, 4, 2); // !!! put values into table
2613 end;
2614 WEAPON_CACO_FIRE:
2615 begin
2616 Shots[i].Animation := TAnimState.Create(True, 4, 2); // !!! put values into table
2617 end;
2618 WEAPON_BARON_FIRE:
2619 begin
2620 Shots[i].Animation := TAnimState.Create(True, 4, 2); // !!! put values into table
2621 end;
2622 WEAPON_MANCUB_FIRE:
2623 begin
2624 Shots[i].Animation := TAnimState.Create(True, 4, 2); // !!! put values into table
2625 end;
2626 end;
2627 end;
2628 end;
2630 procedure g_Weapon_DestroyShot(I: Integer; X, Y: Integer; Loud: Boolean = True);
2631 {$IFDEF ENABLE_GFX}
2632 var cx, cy: Integer;
2633 {$ENDIF}
2634 begin
2635 if Shots = nil then
2636 Exit;
2637 if (I > High(Shots)) or (I < 0) then Exit;
2639 with Shots[I] do
2640 begin
2641 if ShotType = 0 then Exit;
2642 Obj.X := X;
2643 Obj.Y := Y;
2644 {$IFDEF ENABLE_GFX}
2645 cx := Obj.X + (Obj.Rect.Width div 2);
2646 cy := Obj.Y + (Obj.Rect.Height div 2);
2647 {$ENDIF}
2649 case ShotType of
2650 WEAPON_ROCKETLAUNCHER, WEAPON_SKEL_FIRE: // Ðàêåòû è ñíàðÿäû Ñêåëåòà
2651 begin
2652 if Loud then
2653 begin
2654 {$IFDEF ENABLE_GFX}
2655 if ShotType = WEAPON_SKEL_FIRE then
2656 g_GFX_QueueEffect(R_GFX_EXPLODE_SKELFIRE, (Obj.X + 32) - 32, (Obj.Y + 8) - 32)
2657 else
2658 g_GFX_QueueEffect(R_GFX_EXPLODE_ROCKET, cx - 64, cy - 64);
2659 {$ENDIF}
2660 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEROCKET', Obj.X, Obj.Y);
2661 end;
2662 end;
2664 WEAPON_PLASMA, WEAPON_BSP_FIRE: // Ïëàçìà, ïëàçìà Àðàõíàòðîíà
2665 begin
2666 if loud then
2667 begin
2668 {$IFDEF ENABLE_GFX}
2669 if ShotType = WEAPON_PLASMA then
2670 g_GFX_QueueEffect(R_GFX_EXPLODE_PLASMA, cx - 16, cy - 16)
2671 else
2672 g_GFX_QueueEffect(R_GFX_EXPLODE_BSPFIRE, cx - 16, cy - 16);
2673 {$ENDIF}
2674 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEPLASMA', Obj.X, Obj.Y);
2675 end;
2676 end;
2678 WEAPON_BFG: // BFG
2679 begin
2680 {$IFDEF ENABLE_GFX}
2681 g_GFX_QueueEffect(R_GFX_EXPLODE_BFG, cx - 64, cy - 64);
2682 {$ENDIF}
2683 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBFG', Obj.X, Obj.Y);
2684 end;
2686 WEAPON_IMP_FIRE, WEAPON_CACO_FIRE, WEAPON_BARON_FIRE: // Âûñòðåëû Áåñà, Êàêîäåìîíà Ðûöàðÿ/Áàðîíà àäà
2687 begin
2688 if loud then
2689 begin
2690 {$IFDEF ENABLE_GFX}
2691 case ShotType of
2692 WEAPON_IMP_FIRE: g_GFX_QueueEffect(R_GFX_EXPLODE_IMPFIRE, cx - 32, cy - 32);
2693 WEAPON_CACO_FIRE: g_GFX_QueueEffect(R_GFX_EXPLODE_CACOFIRE, cx - 32, cy - 32);
2694 WEAPON_BARON_FIRE: g_GFX_QueueEffect(R_GFX_EXPLODE_BARONFIRE, cx - 32, cy - 32);
2695 end;
2696 {$ENDIF}
2697 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBALL', Obj.X, Obj.Y);
2698 end;
2699 end;
2701 WEAPON_MANCUB_FIRE: // Âûñòðåë Ìàíêóáóñà
2702 begin
2703 {$IFDEF ENABLE_GFX}
2704 g_GFX_QueueEffect(R_GFX_EXPLODE_ROCKET, cx - 64, cy - 64);
2705 {$ENDIF}
2706 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBALL', Obj.X, Obj.Y);
2707 end;
2708 end; // case ShotType of...
2710 ShotType := 0;
2711 Animation.Invalidate;
2712 end;
2713 end;
2716 procedure g_Weapon_AddDynLights();
2717 var
2718 i: Integer;
2719 begin
2720 if Shots = nil then Exit;
2721 for i := 0 to High(Shots) do
2722 begin
2723 if Shots[i].ShotType = 0 then continue;
2724 if (Shots[i].ShotType = WEAPON_ROCKETLAUNCHER) or
2725 (Shots[i].ShotType = WEAPON_BARON_FIRE) or
2726 (Shots[i].ShotType = WEAPON_MANCUB_FIRE) or
2727 (Shots[i].ShotType = WEAPON_SKEL_FIRE) or
2728 (Shots[i].ShotType = WEAPON_IMP_FIRE) or
2729 (Shots[i].ShotType = WEAPON_CACO_FIRE) or
2730 (Shots[i].ShotType = WEAPON_MANCUB_FIRE) or
2731 (Shots[i].ShotType = WEAPON_BSP_FIRE) or
2732 (Shots[i].ShotType = WEAPON_PLASMA) or
2733 (Shots[i].ShotType = WEAPON_BFG) or
2734 (Shots[i].ShotType = WEAPON_FLAMETHROWER) or
2735 false then
2736 begin
2737 if (Shots[i].ShotType = WEAPON_PLASMA) then
2738 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)
2739 else if (Shots[i].ShotType = WEAPON_BFG) then
2740 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)
2741 else if (Shots[i].ShotType = WEAPON_FLAMETHROWER) then
2742 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)
2743 else
2744 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);
2745 end;
2746 end;
2747 end;
2750 procedure TShot.positionChanged (); begin end;
2753 end.