DEADSOFTWARE

don't hit monsters on the client when it's warmup time
[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, e_graphics, 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: TAnimation;
34 TextureID: DWORD;
35 Timeout: DWORD;
36 Stopped: Byte;
38 procedure positionChanged (); //WARNING! call this after monster position was changed, or coldet will not work right!
39 end;
42 var
43 Shots: array of TShot = nil;
44 LastShotID: Integer = 0;
46 procedure g_Weapon_LoadData();
47 procedure g_Weapon_FreeData();
48 procedure g_Weapon_Init();
49 procedure g_Weapon_Free();
50 function g_Weapon_Hit(obj: PObj; d: Integer; SpawnerUID: Word; t: Byte; HitCorpses: Boolean = True): Byte;
51 function g_Weapon_HitUID(UID: Word; d: Integer; SpawnerUID: Word; t: Byte): Boolean;
52 function g_Weapon_CreateShot(I: Integer; ShotType: Byte; Spawner, TargetUID: Word; X, Y, XV, YV: Integer): LongWord;
54 procedure g_Weapon_gun(const x, y, xd, yd, v, dmg: Integer; SpawnerUID: Word; CheckTrigger: Boolean);
55 procedure g_Weapon_punch(x, y: Integer; d, SpawnerUID: Word);
56 function g_Weapon_chainsaw(x, y: Integer; d, SpawnerUID: Word): Integer;
57 procedure g_Weapon_rocket(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1; Silent: Boolean = False);
58 procedure g_Weapon_revf(x, y, xd, yd: Integer; SpawnerUID, TargetUID: Word; WID: Integer = -1; Silent: Boolean = False);
59 procedure g_Weapon_flame(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1; Silent: Boolean = False);
60 procedure g_Weapon_plasma(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1; Silent: Boolean = False);
61 procedure g_Weapon_ball1(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1; Silent: Boolean = False);
62 procedure g_Weapon_ball2(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1; Silent: Boolean = False);
63 procedure g_Weapon_ball7(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1; Silent: Boolean = False);
64 procedure g_Weapon_aplasma(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1; Silent: Boolean = False);
65 procedure g_Weapon_manfire(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1; Silent: Boolean = False);
66 procedure g_Weapon_bfgshot(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1; Silent: Boolean = False);
67 procedure g_Weapon_bfghit(x, y: Integer);
68 procedure g_Weapon_pistol(x, y, xd, yd: Integer; SpawnerUID: Word; Silent: Boolean = False);
69 procedure g_Weapon_mgun(x, y, xd, yd: Integer; SpawnerUID: Word; Silent: Boolean = False);
70 procedure g_Weapon_shotgun(x, y, xd, yd: Integer; SpawnerUID: Word; Silent: Boolean = False);
71 procedure g_Weapon_dshotgun(x, y, xd, yd: Integer; SpawnerUID: Word; Silent: Boolean = False);
73 function g_Weapon_Explode(X, Y: Integer; rad: Integer; SpawnerUID: Word): Boolean;
74 procedure g_Weapon_BFG9000(X, Y: Integer; SpawnerUID: Word);
75 procedure g_Weapon_Update();
76 procedure g_Weapon_Draw();
77 function g_Weapon_Danger(UID: Word; X, Y: Integer; Width, Height: Word; Time: Byte): Boolean;
78 procedure g_Weapon_DestroyShot(I: Integer; X, Y: Integer; Loud: Boolean = True);
80 procedure g_Weapon_SaveState (st: TStream);
81 procedure g_Weapon_LoadState (st: TStream);
83 procedure g_Weapon_AddDynLights();
85 const
86 WEAPON_KASTET = 0;
87 WEAPON_SAW = 1;
88 WEAPON_PISTOL = 2;
89 WEAPON_SHOTGUN1 = 3;
90 WEAPON_SHOTGUN2 = 4;
91 WEAPON_CHAINGUN = 5;
92 WEAPON_ROCKETLAUNCHER = 6;
93 WEAPON_PLASMA = 7;
94 WEAPON_BFG = 8;
95 WEAPON_SUPERPULEMET = 9;
96 WEAPON_FLAMETHROWER = 10;
97 WEAPON_ZOMBY_PISTOL = 20;
98 WEAPON_IMP_FIRE = 21;
99 WEAPON_BSP_FIRE = 22;
100 WEAPON_CACO_FIRE = 23;
101 WEAPON_BARON_FIRE = 24;
102 WEAPON_MANCUB_FIRE = 25;
103 WEAPON_SKEL_FIRE = 26;
105 WP_FIRST = WEAPON_KASTET;
106 WP_LAST = WEAPON_FLAMETHROWER;
109 var
110 gwep_debug_fast_trace: Boolean = true;
113 implementation
115 uses
116 Math, g_map, g_player, g_gfx, g_sound, g_main, g_panel,
117 g_console, g_options, g_game,
118 g_triggers, MAPDEF, e_log, g_monsters, g_saveload,
119 g_language, g_netmsg, g_grid,
120 geom, binheap, hashtable, utils, xstreams;
122 type
123 TWaterPanel = record
124 X, Y: Integer;
125 Width, Height: Word;
126 Active: Boolean;
127 end;
129 const
130 SHOT_ROCKETLAUNCHER_WIDTH = 14;
131 SHOT_ROCKETLAUNCHER_HEIGHT = 14;
133 SHOT_SKELFIRE_WIDTH = 14;
134 SHOT_SKELFIRE_HEIGHT = 14;
136 SHOT_PLASMA_WIDTH = 16;
137 SHOT_PLASMA_HEIGHT = 16;
139 SHOT_BFG_WIDTH = 32;
140 SHOT_BFG_HEIGHT = 32;
141 SHOT_BFG_DAMAGE = 100;
142 SHOT_BFG_RADIUS = 256;
144 SHOT_FLAME_WIDTH = 4;
145 SHOT_FLAME_HEIGHT = 4;
146 SHOT_FLAME_LIFETIME = 180;
148 SHOT_SIGNATURE = $544F4853; // 'SHOT'
150 type
151 PHitTime = ^THitTime;
152 THitTime = record
153 distSq: Integer;
154 mon: TMonster;
155 plridx: Integer; // if mon=nil
156 x, y: Integer;
157 end;
159 TBinHeapKeyHitTime = class
160 public
161 class function less (const a, b: Integer): Boolean; inline;
162 end;
164 // indicies in `wgunHitTime` array
165 TBinaryHeapHitTimes = specialize TBinaryHeapBase<Integer, TBinHeapKeyHitTime>;
167 var
168 WaterMap: array of array of DWORD = nil;
169 //wgunMonHash: THashIntInt = nil;
170 wgunHitHeap: TBinaryHeapHitTimes = nil;
171 wgunHitTime: array of THitTime = nil;
172 wgunHitTimeUsed: Integer = 0;
175 class function TBinHeapKeyHitTime.less (const a, b: Integer): Boolean;
176 var
177 hta, htb: PHitTime;
178 begin
179 hta := @wgunHitTime[a];
180 htb := @wgunHitTime[b];
181 if (hta.distSq <> htb.distSq) then begin result := (hta.distSq < htb.distSq); exit; end;
182 if (hta.mon <> nil) then
183 begin
184 // a is monster
185 if (htb.mon = nil) then begin result := false; exit; end; // players first
186 result := (hta.mon.UID < htb.mon.UID); // why not?
187 end
188 else
189 begin
190 // a is player
191 if (htb.mon <> nil) then begin result := true; exit; end; // players first
192 result := (hta.plridx < htb.plridx); // why not?
193 end;
194 end;
197 procedure appendHitTimeMon (adistSq: Integer; amon: TMonster; ax, ay: Integer);
198 begin
199 if (wgunHitTimeUsed = Length(wgunHitTime)) then SetLength(wgunHitTime, wgunHitTimeUsed+128);
200 with wgunHitTime[wgunHitTimeUsed] do
201 begin
202 distSq := adistSq;
203 mon := amon;
204 plridx := -1;
205 x := ax;
206 y := ay;
207 end;
208 wgunHitHeap.insert(wgunHitTimeUsed);
209 Inc(wgunHitTimeUsed);
210 end;
213 procedure appendHitTimePlr (adistSq: Integer; aplridx: Integer; ax, ay: Integer);
214 begin
215 if (wgunHitTimeUsed = Length(wgunHitTime)) then SetLength(wgunHitTime, wgunHitTimeUsed+128);
216 with wgunHitTime[wgunHitTimeUsed] do
217 begin
218 distSq := adistSq;
219 mon := nil;
220 plridx := aplridx;
221 x := ax;
222 y := ay;
223 end;
224 wgunHitHeap.insert(wgunHitTimeUsed);
225 Inc(wgunHitTimeUsed);
226 end;
229 function FindShot(): DWORD;
230 var
231 i: Integer;
232 begin
233 if Shots <> nil then
234 for i := 0 to High(Shots) do
235 if Shots[i].ShotType = 0 then
236 begin
237 Result := i;
238 LastShotID := Result;
239 Exit;
240 end;
242 if Shots = nil then
243 begin
244 SetLength(Shots, 128);
245 Result := 0;
246 end
247 else
248 begin
249 Result := High(Shots) + 1;
250 SetLength(Shots, Length(Shots) + 128);
251 end;
252 LastShotID := Result;
253 end;
255 procedure CreateWaterMap();
256 var
257 WaterArray: Array of TWaterPanel;
258 a, b, c, m: Integer;
259 ok: Boolean;
260 begin
261 if gWater = nil then
262 Exit;
264 SetLength(WaterArray, Length(gWater));
266 for a := 0 to High(gWater) do
267 begin
268 WaterArray[a].X := gWater[a].X;
269 WaterArray[a].Y := gWater[a].Y;
270 WaterArray[a].Width := gWater[a].Width;
271 WaterArray[a].Height := gWater[a].Height;
272 WaterArray[a].Active := True;
273 end;
275 g_Game_SetLoadingText(_lc[I_LOAD_WATER_MAP], High(WaterArray), False);
277 for a := 0 to High(WaterArray) do
278 if WaterArray[a].Active then
279 begin
280 WaterArray[a].Active := False;
281 m := Length(WaterMap);
282 SetLength(WaterMap, m+1);
283 SetLength(WaterMap[m], 1);
284 WaterMap[m][0] := a;
285 ok := True;
287 while ok do
288 begin
289 ok := False;
290 for b := 0 to High(WaterArray) do
291 if WaterArray[b].Active then
292 for c := 0 to High(WaterMap[m]) do
293 if g_CollideAround(WaterArray[b].X,
294 WaterArray[b].Y,
295 WaterArray[b].Width,
296 WaterArray[b].Height,
297 WaterArray[WaterMap[m][c]].X,
298 WaterArray[WaterMap[m][c]].Y,
299 WaterArray[WaterMap[m][c]].Width,
300 WaterArray[WaterMap[m][c]].Height) then
301 begin
302 WaterArray[b].Active := False;
303 SetLength(WaterMap[m],
304 Length(WaterMap[m])+1);
305 WaterMap[m][High(WaterMap[m])] := b;
306 ok := True;
307 Break;
308 end;
309 end;
311 g_Game_StepLoading();
312 end;
314 WaterArray := nil;
315 end;
318 var
319 chkTrap_pl: array [0..256] of Integer;
320 chkTrap_mn: array [0..65535] of TMonster;
322 procedure CheckTrap(ID: DWORD; dm: Integer; t: Byte);
323 var
324 //a, b, c, d, i1, i2: Integer;
325 //chkTrap_pl, chkTrap_mn: WArray;
326 plaCount: Integer = 0;
327 mnaCount: Integer = 0;
328 frameId: DWord;
331 function monsWaterCheck (mon: TMonster): Boolean;
332 begin
333 result := false; // don't stop
334 if mon.alive and mon.Collide(gWater[WaterMap[a][c]]) and (not InWArray(monidx, chkTrap_mn)) and (i2 < 1023) then //FIXME
335 begin
336 i2 += 1;
337 chkTrap_mn[i2] := monidx;
338 end;
339 end;
342 function monsWaterCheck (mon: TMonster): Boolean;
343 begin
344 result := false; // don't stop
345 if (mon.trapCheckFrameId <> frameId) then
346 begin
347 mon.trapCheckFrameId := frameId;
348 chkTrap_mn[mnaCount] := mon;
349 Inc(mnaCount);
350 end;
351 end;
353 var
354 a, b, c, d, f: Integer;
355 pan: TPanel;
356 begin
357 if (gWater = nil) or (WaterMap = nil) then Exit;
359 frameId := g_Mons_getNewTrapFrameId();
361 //i1 := -1;
362 //i2 := -1;
364 //SetLength(chkTrap_pl, 1024);
365 //SetLength(chkTrap_mn, 1024);
366 //for d := 0 to 1023 do chkTrap_pl[d] := $FFFF;
367 //for d := 0 to 1023 do chkTrap_mn[d] := $FFFF;
369 for a := 0 to High(WaterMap) do
370 begin
371 for b := 0 to High(WaterMap[a]) do
372 begin
373 pan := gWater[WaterMap[a][b]];
374 if not g_Obj_Collide(pan.X, pan.Y, pan.Width, pan.Height, @Shots[ID].Obj) then continue;
376 for c := 0 to High(WaterMap[a]) do
377 begin
378 pan := gWater[WaterMap[a][c]];
379 for d := 0 to High(gPlayers) do
380 begin
381 if (gPlayers[d] <> nil) and (gPlayers[d].alive) then
382 begin
383 if gPlayers[d].Collide(pan) then
384 begin
385 f := 0;
386 while (f < plaCount) and (chkTrap_pl[f] <> d) do Inc(f);
387 if (f = plaCount) then
388 begin
389 chkTrap_pl[plaCount] := d;
390 Inc(plaCount);
391 if (plaCount = Length(chkTrap_pl)) then break;
392 end;
393 end;
394 end;
395 end;
397 //g_Mons_ForEach(monsWaterCheck);
398 g_Mons_ForEachAliveAt(pan.X, pan.Y, pan.Width, pan.Height, monsWaterCheck);
399 end;
401 for f := 0 to plaCount-1 do gPlayers[chkTrap_pl[f]].Damage(dm, Shots[ID].SpawnerUID, 0, 0, t);
402 for f := 0 to mnaCount-1 do chkTrap_mn[f].Damage(dm, 0, 0, Shots[ID].SpawnerUID, t);
403 end;
404 end;
406 //chkTrap_pl := nil;
407 //chkTrap_mn := nil;
408 end;
410 function HitMonster(m: TMonster; d: Integer; vx, vy: Integer; SpawnerUID: Word; t: Byte): Boolean;
411 var
412 tt, mt: Byte;
413 mon: TMonster;
414 begin
415 Result := False;
417 tt := g_GetUIDType(SpawnerUID);
418 if tt = UID_MONSTER then
419 begin
420 mon := g_Monsters_ByUID(SpawnerUID);
421 if mon <> nil then
422 mt := g_Monsters_ByUID(SpawnerUID).MonsterType
423 else
424 mt := 0;
425 end
426 else
427 mt := 0;
429 if m = nil then Exit;
430 if m.UID = SpawnerUID then
431 begin
432 // Ñàì ñåáÿ ìîæåò ðàíèòü òîëüêî ðàêåòîé è òîêîì:
433 if (t <> HIT_ROCKET) and (t <> HIT_ELECTRO) then
434 Exit;
435 // Êèáåð äåìîí è áî÷êà âîîáùå íå ìîãóò ñåáÿ ðàíèòü:
436 if (m.MonsterType = MONSTER_CYBER) or
437 (m.MonsterType = MONSTER_BARREL) then
438 begin
439 Result := True;
440 Exit;
441 end;
442 end;
444 if tt = UID_MONSTER then
445 begin
446 // Lost_Soul íå ìîæåò ðàíèòü Pain_Elemental'à:
447 if (mt = MONSTER_SOUL) and (m.MonsterType = MONSTER_PAIN) then
448 Exit;
450 // Îáà ìîíñòðà îäíîãî âèäà:
451 if mt = m.MonsterType then
452 case mt of
453 MONSTER_IMP, MONSTER_DEMON, MONSTER_BARON, MONSTER_KNIGHT, MONSTER_CACO,
454 MONSTER_SOUL, MONSTER_MANCUB, MONSTER_SKEL, MONSTER_FISH:
455 Exit; // Ýòè íå áüþò ñâîèõ
456 end;
457 end;
459 if g_Game_IsServer then
460 begin
461 if (t <> HIT_FLAME) or (m.FFireTime = 0) or (vx <> 0) or (vy <> 0) then
462 Result := m.Damage(d, vx, vy, SpawnerUID, t)
463 else
464 Result := (gLMSRespawn <> LMS_RESPAWN_WARMUP); // don't hit monsters when it's warmup time
465 if t = HIT_FLAME then
466 m.CatchFire(SpawnerUID);
467 end
468 else
469 Result := (gLMSRespawn <> LMS_RESPAWN_WARMUP); // don't hit monsters when it's warmup time
470 end;
473 function HitPlayer (p: TPlayer; d: Integer; vx, vy: Integer; SpawnerUID: Word; t: Byte): Boolean;
474 begin
475 result := False;
477 // Ñàì ñåáÿ ìîæåò ðàíèòü òîëüêî ðàêåòîé è òîêîì
478 if (p.UID = SpawnerUID) and (t <> HIT_ROCKET) and (t <> HIT_ELECTRO) then exit;
480 if g_Game_IsServer then
481 begin
482 if (t <> HIT_FLAME) or (p.FFireTime = 0) or (vx <> 0) or (vy <> 0) then p.Damage(d, SpawnerUID, vx, vy, t);
483 if (t = HIT_FLAME) then p.CatchFire(SpawnerUID);
484 end;
486 result := true;
487 end;
490 procedure g_Weapon_BFG9000(X, Y: Integer; SpawnerUID: Word);
492 function monsCheck (mon: TMonster): Boolean;
493 begin
494 result := false; // don't stop
495 if (mon.alive) and (mon.UID <> SpawnerUID) then
496 begin
497 with mon do
498 begin
499 if (g_PatchLength(X, Y, Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
500 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)) <= SHOT_BFG_RADIUS) and
501 g_TraceVector(X, Y, Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
502 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)) then
503 begin
504 if HitMonster(mon, 50, 0, 0, SpawnerUID, HIT_SOME) then mon.BFGHit();
505 end;
506 end;
507 end;
508 end;
510 var
511 i, h: Integer;
512 st: Byte;
513 pl: TPlayer;
514 b: Boolean;
515 begin
516 //g_Sound_PlayEx('SOUND_WEAPON_EXPLODEBFG', 255);
518 h := High(gCorpses);
520 if gAdvCorpses and (h <> -1) then
521 for i := 0 to h do
522 if (gCorpses[i] <> nil) and (gCorpses[i].State <> CORPSE_STATE_REMOVEME) then
523 with gCorpses[i] do
524 if (g_PatchLength(X, Y, Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
525 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)) <= SHOT_BFG_RADIUS) and
526 g_TraceVector(X, Y, Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
527 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)) then
528 begin
529 Damage(50, 0, 0);
530 g_Weapon_BFGHit(Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
531 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2));
532 end;
534 st := TEAM_NONE;
535 pl := g_Player_Get(SpawnerUID);
536 if pl <> nil then
537 st := pl.Team;
539 h := High(gPlayers);
541 if h <> -1 then
542 for i := 0 to h do
543 if (gPlayers[i] <> nil) and (gPlayers[i].alive) and (gPlayers[i].UID <> SpawnerUID) then
544 with gPlayers[i] do
545 if (g_PatchLength(X, Y, GameX+PLAYER_RECT.X+(PLAYER_RECT.Width div 2),
546 GameY+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)) <= SHOT_BFG_RADIUS) and
547 g_TraceVector(X, Y, GameX+PLAYER_RECT.X+(PLAYER_RECT.Width div 2),
548 GameY+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)) then
549 begin
550 if (st = TEAM_NONE) or (st <> gPlayers[i].Team) then
551 b := HitPlayer(gPlayers[i], 50, 0, 0, SpawnerUID, HIT_SOME)
552 else
553 b := HitPlayer(gPlayers[i], 25, 0, 0, SpawnerUID, HIT_SOME);
554 if b then
555 gPlayers[i].BFGHit();
556 end;
558 //FIXME
559 g_Mons_ForEachAlive(monsCheck);
560 end;
562 function g_Weapon_CreateShot(I: Integer; ShotType: Byte; Spawner, TargetUID: Word; X, Y, XV, YV: Integer): LongWord;
563 var
564 find_id: DWord;
565 FramesID: DWORD = 0;
566 begin
567 if I < 0 then
568 find_id := FindShot()
569 else
570 begin
571 find_id := I;
572 if Integer(find_id) >= High(Shots) then
573 SetLength(Shots, find_id + 64)
574 end;
576 case ShotType of
577 WEAPON_ROCKETLAUNCHER:
578 begin
579 with Shots[find_id] do
580 begin
581 g_Obj_Init(@Obj);
583 Obj.Rect.Width := SHOT_ROCKETLAUNCHER_WIDTH;
584 Obj.Rect.Height := SHOT_ROCKETLAUNCHER_HEIGHT;
586 Animation := nil;
587 Triggers := nil;
588 ShotType := WEAPON_ROCKETLAUNCHER;
589 g_Texture_Get('TEXTURE_WEAPON_ROCKET', TextureID);
590 end;
591 end;
593 WEAPON_PLASMA:
594 begin
595 with Shots[find_id] do
596 begin
597 g_Obj_Init(@Obj);
599 Obj.Rect.Width := SHOT_PLASMA_WIDTH;
600 Obj.Rect.Height := SHOT_PLASMA_HEIGHT;
602 Triggers := nil;
603 ShotType := WEAPON_PLASMA;
604 g_Frames_Get(FramesID, 'FRAMES_WEAPON_PLASMA');
605 Animation := TAnimation.Create(FramesID, True, 5);
606 end;
607 end;
609 WEAPON_BFG:
610 begin
611 with Shots[find_id] do
612 begin
613 g_Obj_Init(@Obj);
615 Obj.Rect.Width := SHOT_BFG_WIDTH;
616 Obj.Rect.Height := SHOT_BFG_HEIGHT;
618 Triggers := nil;
619 ShotType := WEAPON_BFG;
620 g_Frames_Get(FramesID, 'FRAMES_WEAPON_BFG');
621 Animation := TAnimation.Create(FramesID, True, 6);
622 end;
623 end;
625 WEAPON_FLAMETHROWER:
626 begin
627 with Shots[find_id] do
628 begin
629 g_Obj_Init(@Obj);
631 Obj.Rect.Width := SHOT_FLAME_WIDTH;
632 Obj.Rect.Height := SHOT_FLAME_HEIGHT;
634 Triggers := nil;
635 ShotType := WEAPON_FLAMETHROWER;
636 Animation := nil;
637 TextureID := 0;
638 g_Frames_Get(TextureID, 'FRAMES_FLAME');
639 end;
640 end;
642 WEAPON_IMP_FIRE:
643 begin
644 with Shots[find_id] do
645 begin
646 g_Obj_Init(@Obj);
648 Obj.Rect.Width := 16;
649 Obj.Rect.Height := 16;
651 Triggers := nil;
652 ShotType := WEAPON_IMP_FIRE;
653 g_Frames_Get(FramesID, 'FRAMES_WEAPON_IMPFIRE');
654 Animation := TAnimation.Create(FramesID, True, 4);
655 end;
656 end;
658 WEAPON_CACO_FIRE:
659 begin
660 with Shots[find_id] do
661 begin
662 g_Obj_Init(@Obj);
664 Obj.Rect.Width := 16;
665 Obj.Rect.Height := 16;
667 Triggers := nil;
668 ShotType := WEAPON_CACO_FIRE;
669 g_Frames_Get(FramesID, 'FRAMES_WEAPON_CACOFIRE');
670 Animation := TAnimation.Create(FramesID, True, 4);
671 end;
672 end;
674 WEAPON_MANCUB_FIRE:
675 begin
676 with Shots[find_id] do
677 begin
678 g_Obj_Init(@Obj);
680 Obj.Rect.Width := 32;
681 Obj.Rect.Height := 32;
683 Triggers := nil;
684 ShotType := WEAPON_MANCUB_FIRE;
685 g_Frames_Get(FramesID, 'FRAMES_WEAPON_MANCUBFIRE');
686 Animation := TAnimation.Create(FramesID, True, 4);
687 end;
688 end;
690 WEAPON_BARON_FIRE:
691 begin
692 with Shots[find_id] do
693 begin
694 g_Obj_Init(@Obj);
696 Obj.Rect.Width := 32;
697 Obj.Rect.Height := 16;
699 Triggers := nil;
700 ShotType := WEAPON_BARON_FIRE;
701 g_Frames_Get(FramesID, 'FRAMES_WEAPON_BARONFIRE');
702 Animation := TAnimation.Create(FramesID, True, 4);
703 end;
704 end;
706 WEAPON_BSP_FIRE:
707 begin
708 with Shots[find_id] do
709 begin
710 g_Obj_Init(@Obj);
712 Obj.Rect.Width := 16;
713 Obj.Rect.Height := 16;
715 Triggers := nil;
716 ShotType := WEAPON_BSP_FIRE;
717 g_Frames_Get(FramesID, 'FRAMES_WEAPON_BSPFIRE');
718 Animation := TAnimation.Create(FramesID, True, 4);
719 end;
720 end;
722 WEAPON_SKEL_FIRE:
723 begin
724 with Shots[find_id] do
725 begin
726 g_Obj_Init(@Obj);
728 Obj.Rect.Width := SHOT_SKELFIRE_WIDTH;
729 Obj.Rect.Height := SHOT_SKELFIRE_HEIGHT;
731 Triggers := nil;
732 ShotType := WEAPON_SKEL_FIRE;
733 target := TargetUID;
734 g_Frames_Get(FramesID, 'FRAMES_WEAPON_SKELFIRE');
735 Animation := TAnimation.Create(FramesID, True, 5);
736 end;
737 end;
738 end;
740 Shots[find_id].Obj.X := X;
741 Shots[find_id].Obj.Y := Y;
742 Shots[find_id].Obj.Vel.X := XV;
743 Shots[find_id].Obj.Vel.Y := YV;
744 Shots[find_id].Obj.Accel.X := 0;
745 Shots[find_id].Obj.Accel.Y := 0;
746 Shots[find_id].SpawnerUID := Spawner;
747 if (ShotType = WEAPON_FLAMETHROWER) and (XV = 0) and (YV = 0) then
748 Shots[find_id].Stopped := 255
749 else
750 Shots[find_id].Stopped := 0;
751 Result := find_id;
752 end;
754 procedure throw(i, x, y, xd, yd, s: Integer);
755 var
756 a: Integer;
757 begin
758 yd := yd - y;
759 xd := xd - x;
761 a := Max(Abs(xd), Abs(yd));
762 if a = 0 then
763 a := 1;
765 Shots[i].Obj.X := x;
766 Shots[i].Obj.Y := y;
767 Shots[i].Obj.Vel.X := (xd*s) div a;
768 Shots[i].Obj.Vel.Y := (yd*s) div a;
769 Shots[i].Obj.Accel.X := 0;
770 Shots[i].Obj.Accel.Y := 0;
771 Shots[i].Stopped := 0;
772 if Shots[i].ShotType in [WEAPON_ROCKETLAUNCHER, WEAPON_BFG] then
773 Shots[i].Timeout := 900 // ~25 sec
774 else
775 begin
776 if Shots[i].ShotType = WEAPON_FLAMETHROWER then
777 Shots[i].Timeout := SHOT_FLAME_LIFETIME
778 else
779 Shots[i].Timeout := 550; // ~15 sec
780 end;
781 end;
783 function g_Weapon_Hit(obj: PObj; d: Integer; SpawnerUID: Word; t: Byte; HitCorpses: Boolean = True): Byte;
784 var
785 i, h: Integer;
787 function PlayerHit(Team: Byte = 0): Boolean;
788 var
789 i: Integer;
790 ChkTeam: Boolean;
791 p: TPlayer;
792 begin
793 Result := False;
794 h := High(gPlayers);
796 if h <> -1 then
797 for i := 0 to h do
798 if (gPlayers[i] <> nil) and gPlayers[i].alive and g_Obj_Collide(obj, @gPlayers[i].Obj) then
799 begin
800 ChkTeam := True;
801 if (Team > 0) and (g_GetUIDType(SpawnerUID) = UID_PLAYER) then
802 begin
803 p := g_Player_Get(SpawnerUID);
804 if p <> nil then
805 ChkTeam := (p.Team = gPlayers[i].Team) xor (Team = 2);
806 end;
807 if ChkTeam then
808 if HitPlayer(gPlayers[i], d, obj^.Vel.X, obj^.Vel.Y, SpawnerUID, t) then
809 begin
810 if t <> HIT_FLAME then
811 gPlayers[i].Push((obj^.Vel.X+obj^.Accel.X)*IfThen(t = HIT_BFG, 8, 1) div 4,
812 (obj^.Vel.Y+obj^.Accel.Y)*IfThen(t = HIT_BFG, 8, 1) div 4);
813 if t = HIT_BFG then
814 g_Game_DelayEvent(DE_BFGHIT, 1000, SpawnerUID);
815 Result := True;
816 break;
817 end;
818 end;
819 end;
822 function monsCheckHit (monidx: Integer; mon: TMonster): Boolean;
823 begin
824 result := false; // don't stop
825 if mon.alive and g_Obj_Collide(obj, @mon.Obj) then
826 begin
827 if HitMonster(mon, d, obj^.Vel.X, obj^.Vel.Y, SpawnerUID, t) then
828 begin
829 if (t <> HIT_FLAME) then
830 begin
831 mon.Push((obj^.Vel.X+obj^.Accel.X)*IfThen(t = HIT_BFG, 8, 1) div 4,
832 (obj^.Vel.Y+obj^.Accel.Y)*IfThen(t = HIT_BFG, 8, 1) div 4);
833 end;
834 result := True;
835 end;
836 end;
837 end;
840 function monsCheckHit (mon: TMonster): Boolean;
841 begin
842 result := false; // don't stop
843 if HitMonster(mon, d, obj.Vel.X, obj.Vel.Y, SpawnerUID, t) then
844 begin
845 if (t <> HIT_FLAME) then
846 begin
847 mon.Push((obj.Vel.X+obj.Accel.X)*IfThen(t = HIT_BFG, 8, 1) div 4,
848 (obj.Vel.Y+obj.Accel.Y)*IfThen(t = HIT_BFG, 8, 1) div 4);
849 end;
850 result := true;
851 end;
852 end;
854 function MonsterHit(): Boolean;
855 begin
856 //result := g_Mons_ForEach(monsCheckHit);
857 //FIXME: accelerate this!
858 result := g_Mons_ForEachAliveAt(obj.X+obj.Rect.X, obj.Y+obj.Rect.Y, obj.Rect.Width, obj.Rect.Height, monsCheckHit);
859 end;
861 begin
862 Result := 0;
864 if HitCorpses then
865 begin
866 h := High(gCorpses);
868 if gAdvCorpses and (h <> -1) then
869 for i := 0 to h do
870 if (gCorpses[i] <> nil) and (gCorpses[i].State <> CORPSE_STATE_REMOVEME) and
871 g_Obj_Collide(obj, @gCorpses[i].Obj) then
872 begin
873 // Ðàñïèëèâàåì òðóï:
874 gCorpses[i].Damage(d, (obj^.Vel.X+obj^.Accel.X) div 4,
875 (obj^.Vel.Y+obj^.Accel.Y) div 4);
876 Result := 1;
877 end;
878 end;
880 case gGameSettings.GameMode of
881 // Êàìïàíèÿ:
882 GM_COOP, GM_SINGLE:
883 begin
884 // Ñíà÷àëà áü¸ì ìîíñòðîâ, åñëè åñòü, ïîòîì ïûòàåìñÿ áèòü èãðîêîâ
885 if MonsterHit() then
886 begin
887 Result := 2;
888 Exit;
889 end;
891 if PlayerHit() then
892 begin
893 Result := 1;
894 Exit;
895 end;
896 end;
898 // Äåçìàò÷:
899 GM_DM:
900 begin
901 // Ñíà÷àëà áü¸ì èãðîêîâ, åñëè åñòü, ïîòîì ïûòàåìñÿ áèòü ìîíñòðîâ
902 if PlayerHit() then
903 begin
904 Result := 1;
905 Exit;
906 end;
908 if MonsterHit() then
909 begin
910 Result := 2;
911 Exit;
912 end;
913 end;
915 // Êîìàíäíûå:
916 GM_TDM, GM_CTF:
917 begin
918 // Ñíà÷àëà áü¸ì èãðîêîâ êîìàíäû ñîïåðíèêà
919 if PlayerHit(2) then
920 begin
921 Result := 1;
922 Exit;
923 end;
925 // Ïîòîì ìîíñòðîâ
926 if MonsterHit() then
927 begin
928 Result := 2;
929 Exit;
930 end;
932 // È â êîíöå ñâîèõ èãðîêîâ
933 if PlayerHit(1) then
934 begin
935 Result := 1;
936 Exit;
937 end;
938 end;
940 end;
941 end;
943 function g_Weapon_HitUID(UID: Word; d: Integer; SpawnerUID: Word; t: Byte): Boolean;
944 begin
945 Result := False;
947 case g_GetUIDType(UID) of
948 UID_PLAYER: Result := HitPlayer(g_Player_Get(UID), d, 0, 0, SpawnerUID, t);
949 UID_MONSTER: Result := HitMonster(g_Monsters_ByUID(UID), d, 0, 0, SpawnerUID, t);
950 else Exit;
951 end;
952 end;
954 function g_Weapon_Explode(X, Y: Integer; rad: Integer; SpawnerUID: Word): Boolean;
955 var
956 r: Integer; // squared radius
958 function monsExCheck (mon: TMonster): Boolean;
959 var
960 dx, dy, mm: Integer;
961 begin
962 result := false; // don't stop
963 begin
964 dx := mon.Obj.X+mon.Obj.Rect.X+(mon.Obj.Rect.Width div 2)-X;
965 dy := mon.Obj.Y+mon.Obj.Rect.Y+(mon.Obj.Rect.Height div 2)-Y;
967 if dx > 1000 then dx := 1000;
968 if dy > 1000 then dy := 1000;
970 if (dx*dx+dy*dy < r) then
971 begin
972 //m := PointToRect(X, Y, Obj.X+Obj.Rect.X, Obj.Y+Obj.Rect.Y, Obj.Rect.Width, Obj.Rect.Height);
973 //e_WriteLog(Format('explo monster #%d: x=%d; y=%d; rad=%d; dx=%d; dy=%d', [monidx, X, Y, rad, dx, dy]), MSG_NOTIFY);
975 mm := Max(abs(dx), abs(dy));
976 if mm = 0 then mm := 1;
978 if mon.alive then
979 begin
980 HitMonster(mon, ((mon.Obj.Rect.Width div 4)*10*(rad-mm)) div rad, 0, 0, SpawnerUID, HIT_ROCKET);
981 end;
983 mon.Push((dx*7) div mm, (dy*7) div mm);
984 end;
985 end;
986 end;
988 var
989 i, h, dx, dy, m, mm: Integer;
990 _angle: SmallInt;
991 begin
992 result := false;
994 g_Triggers_PressC(X, Y, rad, SpawnerUID, ACTIVATE_SHOT);
996 r := rad*rad;
998 h := High(gPlayers);
1000 if h <> -1 then
1001 for i := 0 to h do
1002 if (gPlayers[i] <> nil) and gPlayers[i].alive then
1003 with gPlayers[i] do
1004 begin
1005 dx := Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2)-X;
1006 dy := Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)-Y;
1008 if dx > 1000 then dx := 1000;
1009 if dy > 1000 then dy := 1000;
1011 if dx*dx+dy*dy < r then
1012 begin
1013 //m := PointToRect(X, Y, GameX+PLAYER_RECT.X, GameY+PLAYER_RECT.Y,
1014 // PLAYER_RECT.Width, PLAYER_RECT.Height);
1016 mm := Max(abs(dx), abs(dy));
1017 if mm = 0 then mm := 1;
1019 HitPlayer(gPlayers[i], (100*(rad-mm)) div rad, (dx*10) div mm, (dy*10) div mm, SpawnerUID, HIT_ROCKET);
1020 gPlayers[i].Push((dx*7) div mm, (dy*7) div mm);
1021 end;
1022 end;
1024 //g_Mons_ForEach(monsExCheck);
1025 g_Mons_ForEachAt(X-(rad+32), Y-(rad+32), (rad+32)*2, (rad+32)*2, monsExCheck);
1027 h := High(gCorpses);
1029 if gAdvCorpses and (h <> -1) then
1030 for i := 0 to h do
1031 if (gCorpses[i] <> nil) and (gCorpses[i].State <> CORPSE_STATE_REMOVEME) then
1032 with gCorpses[i] do
1033 begin
1034 dx := Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2)-X;
1035 dy := Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)-Y;
1037 if dx > 1000 then dx := 1000;
1038 if dy > 1000 then dy := 1000;
1040 if dx*dx+dy*dy < r then
1041 begin
1042 m := PointToRect(X, Y, Obj.X+Obj.Rect.X, Obj.Y+Obj.Rect.Y,
1043 Obj.Rect.Width, Obj.Rect.Height);
1045 mm := Max(abs(dx), abs(dy));
1046 if mm = 0 then mm := 1;
1048 Damage(Round(100*(rad-m)/rad), (dx*10) div mm, (dy*10) div mm);
1049 end;
1050 end;
1052 h := High(gGibs);
1054 if gAdvGibs and (h <> -1) then
1055 for i := 0 to h do
1056 if gGibs[i].alive then
1057 with gGibs[i] do
1058 begin
1059 dx := Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2)-X;
1060 dy := Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2)-Y;
1062 if dx > 1000 then dx := 1000;
1063 if dy > 1000 then dy := 1000;
1065 if dx*dx+dy*dy < r then
1066 begin
1067 m := PointToRect(X, Y, Obj.X+Obj.Rect.X, Obj.Y+Obj.Rect.Y,
1068 Obj.Rect.Width, Obj.Rect.Height);
1069 _angle := GetAngle(Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2),
1070 Obj.Y+Obj.Rect.Y+(Obj.Rect.Height div 2), X, Y);
1072 g_Obj_PushA(@Obj, Round(15*(rad-m)/rad), _angle);
1073 positionChanged(); // this updates spatial accelerators
1074 end;
1075 end;
1076 end;
1078 procedure g_Weapon_Init();
1079 begin
1080 CreateWaterMap();
1081 end;
1083 procedure g_Weapon_Free();
1084 var
1085 i: Integer;
1086 begin
1087 if Shots <> nil then
1088 begin
1089 for i := 0 to High(Shots) do
1090 if Shots[i].ShotType <> 0 then
1091 Shots[i].Animation.Free();
1093 Shots := nil;
1094 end;
1096 WaterMap := nil;
1097 end;
1099 procedure g_Weapon_LoadData();
1100 begin
1101 e_WriteLog('Loading weapons data...', TMsgType.Notify);
1103 g_Sound_CreateWADEx('SOUND_WEAPON_HITPUNCH', GameWAD+':SOUNDS\HITPUNCH');
1104 g_Sound_CreateWADEx('SOUND_WEAPON_MISSPUNCH', GameWAD+':SOUNDS\MISSPUNCH');
1105 g_Sound_CreateWADEx('SOUND_WEAPON_HITBERSERK', GameWAD+':SOUNDS\HITBERSERK');
1106 g_Sound_CreateWADEx('SOUND_WEAPON_MISSBERSERK', GameWAD+':SOUNDS\MISSBERSERK');
1107 g_Sound_CreateWADEx('SOUND_WEAPON_SELECTSAW', GameWAD+':SOUNDS\SELECTSAW');
1108 g_Sound_CreateWADEx('SOUND_WEAPON_IDLESAW', GameWAD+':SOUNDS\IDLESAW');
1109 g_Sound_CreateWADEx('SOUND_WEAPON_HITSAW', GameWAD+':SOUNDS\HITSAW');
1110 g_Sound_CreateWADEx('SOUND_WEAPON_FIRESHOTGUN2', GameWAD+':SOUNDS\FIRESHOTGUN2');
1111 g_Sound_CreateWADEx('SOUND_WEAPON_FIRESHOTGUN', GameWAD+':SOUNDS\FIRESHOTGUN');
1112 g_Sound_CreateWADEx('SOUND_WEAPON_FIRESAW', GameWAD+':SOUNDS\FIRESAW');
1113 g_Sound_CreateWADEx('SOUND_WEAPON_FIREROCKET', GameWAD+':SOUNDS\FIREROCKET');
1114 g_Sound_CreateWADEx('SOUND_WEAPON_FIREPLASMA', GameWAD+':SOUNDS\FIREPLASMA');
1115 g_Sound_CreateWADEx('SOUND_WEAPON_FIREPISTOL', GameWAD+':SOUNDS\FIREPISTOL');
1116 g_Sound_CreateWADEx('SOUND_WEAPON_FIRECGUN', GameWAD+':SOUNDS\FIRECGUN');
1117 g_Sound_CreateWADEx('SOUND_WEAPON_FIREBFG', GameWAD+':SOUNDS\FIREBFG');
1118 g_Sound_CreateWADEx('SOUND_FIRE', GameWAD+':SOUNDS\FIRE');
1119 g_Sound_CreateWADEx('SOUND_IGNITE', GameWAD+':SOUNDS\IGNITE');
1120 g_Sound_CreateWADEx('SOUND_WEAPON_STARTFIREBFG', GameWAD+':SOUNDS\STARTFIREBFG');
1121 g_Sound_CreateWADEx('SOUND_WEAPON_EXPLODEROCKET', GameWAD+':SOUNDS\EXPLODEROCKET');
1122 g_Sound_CreateWADEx('SOUND_WEAPON_EXPLODEBFG', GameWAD+':SOUNDS\EXPLODEBFG');
1123 g_Sound_CreateWADEx('SOUND_WEAPON_BFGWATER', GameWAD+':SOUNDS\BFGWATER');
1124 g_Sound_CreateWADEx('SOUND_WEAPON_EXPLODEPLASMA', GameWAD+':SOUNDS\EXPLODEPLASMA');
1125 g_Sound_CreateWADEx('SOUND_WEAPON_PLASMAWATER', GameWAD+':SOUNDS\PLASMAWATER');
1126 g_Sound_CreateWADEx('SOUND_WEAPON_FIREBALL', GameWAD+':SOUNDS\FIREBALL');
1127 g_Sound_CreateWADEx('SOUND_WEAPON_EXPLODEBALL', GameWAD+':SOUNDS\EXPLODEBALL');
1128 g_Sound_CreateWADEx('SOUND_WEAPON_FIREREV', GameWAD+':SOUNDS\FIREREV');
1129 g_Sound_CreateWADEx('SOUND_WEAPON_FLAMEON', GameWAD+':SOUNDS\STARTFLM');
1130 g_Sound_CreateWADEx('SOUND_WEAPON_FLAMEOFF', GameWAD+':SOUNDS\STOPFLM');
1131 g_Sound_CreateWADEx('SOUND_WEAPON_FLAMEWORK', GameWAD+':SOUNDS\WORKFLM');
1132 g_Sound_CreateWADEx('SOUND_PLAYER_JETFLY', GameWAD+':SOUNDS\WORKJETPACK');
1133 g_Sound_CreateWADEx('SOUND_PLAYER_JETON', GameWAD+':SOUNDS\STARTJETPACK');
1134 g_Sound_CreateWADEx('SOUND_PLAYER_JETOFF', GameWAD+':SOUNDS\STOPJETPACK');
1135 g_Sound_CreateWADEx('SOUND_PLAYER_CASING1', GameWAD+':SOUNDS\CASING1');
1136 g_Sound_CreateWADEx('SOUND_PLAYER_CASING2', GameWAD+':SOUNDS\CASING2');
1137 g_Sound_CreateWADEx('SOUND_PLAYER_SHELL1', GameWAD+':SOUNDS\SHELL1');
1138 g_Sound_CreateWADEx('SOUND_PLAYER_SHELL2', GameWAD+':SOUNDS\SHELL2');
1140 g_Texture_CreateWADEx('TEXTURE_WEAPON_ROCKET', GameWAD+':TEXTURES\BROCKET');
1141 g_Frames_CreateWAD(nil, 'FRAMES_WEAPON_SKELFIRE', GameWAD+':TEXTURES\BSKELFIRE', 64, 16, 2);
1142 g_Frames_CreateWAD(nil, 'FRAMES_WEAPON_BFG', GameWAD+':TEXTURES\BBFG', 64, 64, 2);
1143 g_Frames_CreateWAD(nil, 'FRAMES_WEAPON_PLASMA', GameWAD+':TEXTURES\BPLASMA', 16, 16, 2);
1144 g_Frames_CreateWAD(nil, 'FRAMES_WEAPON_IMPFIRE', GameWAD+':TEXTURES\BIMPFIRE', 16, 16, 2);
1145 g_Frames_CreateWAD(nil, 'FRAMES_WEAPON_BSPFIRE', GameWAD+':TEXTURES\BBSPFIRE', 16, 16, 2);
1146 g_Frames_CreateWAD(nil, 'FRAMES_WEAPON_CACOFIRE', GameWAD+':TEXTURES\BCACOFIRE', 16, 16, 2);
1147 g_Frames_CreateWAD(nil, 'FRAMES_WEAPON_BARONFIRE', GameWAD+':TEXTURES\BBARONFIRE', 64, 16, 2);
1148 g_Frames_CreateWAD(nil, 'FRAMES_WEAPON_MANCUBFIRE', GameWAD+':TEXTURES\BMANCUBFIRE', 64, 32, 2);
1149 g_Frames_CreateWAD(nil, 'FRAMES_EXPLODE_ROCKET', GameWAD+':TEXTURES\EROCKET', 128, 128, 6);
1150 g_Frames_CreateWAD(nil, 'FRAMES_EXPLODE_SKELFIRE', GameWAD+':TEXTURES\ESKELFIRE', 64, 64, 3);
1151 g_Frames_CreateWAD(nil, 'FRAMES_EXPLODE_BFG', GameWAD+':TEXTURES\EBFG', 128, 128, 6);
1152 g_Frames_CreateWAD(nil, 'FRAMES_EXPLODE_IMPFIRE', GameWAD+':TEXTURES\EIMPFIRE', 64, 64, 3);
1153 g_Frames_CreateWAD(nil, 'FRAMES_BFGHIT', GameWAD+':TEXTURES\BFGHIT', 64, 64, 4);
1154 g_Frames_CreateWAD(nil, 'FRAMES_FIRE', GameWAD+':TEXTURES\FIRE', 64, 128, 8);
1155 g_Frames_CreateWAD(nil, 'FRAMES_FLAME', GameWAD+':TEXTURES\FLAME', 32, 32, 11);
1156 g_Frames_CreateWAD(nil, 'FRAMES_EXPLODE_PLASMA', GameWAD+':TEXTURES\EPLASMA', 32, 32, 4, True);
1157 g_Frames_CreateWAD(nil, 'FRAMES_EXPLODE_BSPFIRE', GameWAD+':TEXTURES\EBSPFIRE', 32, 32, 5);
1158 g_Frames_CreateWAD(nil, 'FRAMES_EXPLODE_CACOFIRE', GameWAD+':TEXTURES\ECACOFIRE', 64, 64, 3);
1159 g_Frames_CreateWAD(nil, 'FRAMES_EXPLODE_BARONFIRE', GameWAD+':TEXTURES\EBARONFIRE', 64, 64, 3);
1160 g_Frames_CreateWAD(nil, 'FRAMES_SMOKE', GameWAD+':TEXTURES\SMOKE', 32, 32, 10, False);
1162 g_Texture_CreateWADEx('TEXTURE_SHELL_BULLET', GameWAD+':TEXTURES\EBULLET');
1163 g_Texture_CreateWADEx('TEXTURE_SHELL_SHELL', GameWAD+':TEXTURES\ESHELL');
1165 //wgunMonHash := hashNewIntInt();
1166 wgunHitHeap := TBinaryHeapHitTimes.Create();
1167 end;
1169 procedure g_Weapon_FreeData();
1170 begin
1171 e_WriteLog('Releasing weapons data...', TMsgType.Notify);
1173 g_Sound_Delete('SOUND_WEAPON_HITPUNCH');
1174 g_Sound_Delete('SOUND_WEAPON_MISSPUNCH');
1175 g_Sound_Delete('SOUND_WEAPON_HITBERSERK');
1176 g_Sound_Delete('SOUND_WEAPON_MISSBERSERK');
1177 g_Sound_Delete('SOUND_WEAPON_SELECTSAW');
1178 g_Sound_Delete('SOUND_WEAPON_IDLESAW');
1179 g_Sound_Delete('SOUND_WEAPON_HITSAW');
1180 g_Sound_Delete('SOUND_WEAPON_FIRESHOTGUN2');
1181 g_Sound_Delete('SOUND_WEAPON_FIRESHOTGUN');
1182 g_Sound_Delete('SOUND_WEAPON_FIRESAW');
1183 g_Sound_Delete('SOUND_WEAPON_FIREROCKET');
1184 g_Sound_Delete('SOUND_WEAPON_FIREPLASMA');
1185 g_Sound_Delete('SOUND_WEAPON_FIREPISTOL');
1186 g_Sound_Delete('SOUND_WEAPON_FIRECGUN');
1187 g_Sound_Delete('SOUND_WEAPON_FIREBFG');
1188 g_Sound_Delete('SOUND_FIRE');
1189 g_Sound_Delete('SOUND_IGNITE');
1190 g_Sound_Delete('SOUND_WEAPON_STARTFIREBFG');
1191 g_Sound_Delete('SOUND_WEAPON_EXPLODEROCKET');
1192 g_Sound_Delete('SOUND_WEAPON_EXPLODEBFG');
1193 g_Sound_Delete('SOUND_WEAPON_BFGWATER');
1194 g_Sound_Delete('SOUND_WEAPON_EXPLODEPLASMA');
1195 g_Sound_Delete('SOUND_WEAPON_PLASMAWATER');
1196 g_Sound_Delete('SOUND_WEAPON_FIREBALL');
1197 g_Sound_Delete('SOUND_WEAPON_EXPLODEBALL');
1198 g_Sound_Delete('SOUND_WEAPON_FIREREV');
1199 g_Sound_Delete('SOUND_WEAPON_FLAMEON');
1200 g_Sound_Delete('SOUND_WEAPON_FLAMEOFF');
1201 g_Sound_Delete('SOUND_WEAPON_FLAMEWORK');
1202 g_Sound_Delete('SOUND_PLAYER_JETFLY');
1203 g_Sound_Delete('SOUND_PLAYER_JETON');
1204 g_Sound_Delete('SOUND_PLAYER_JETOFF');
1205 g_Sound_Delete('SOUND_PLAYER_CASING1');
1206 g_Sound_Delete('SOUND_PLAYER_CASING2');
1207 g_Sound_Delete('SOUND_PLAYER_SHELL1');
1208 g_Sound_Delete('SOUND_PLAYER_SHELL2');
1210 g_Texture_Delete('TEXTURE_WEAPON_ROCKET');
1211 g_Frames_DeleteByName('FRAMES_WEAPON_BFG');
1212 g_Frames_DeleteByName('FRAMES_WEAPON_PLASMA');
1213 g_Frames_DeleteByName('FRAMES_WEAPON_IMPFIRE');
1214 g_Frames_DeleteByName('FRAMES_WEAPON_BSPFIRE');
1215 g_Frames_DeleteByName('FRAMES_WEAPON_CACOFIRE');
1216 g_Frames_DeleteByName('FRAMES_WEAPON_MANCUBFIRE');
1217 g_Frames_DeleteByName('FRAMES_EXPLODE_ROCKET');
1218 g_Frames_DeleteByName('FRAMES_EXPLODE_BFG');
1219 g_Frames_DeleteByName('FRAMES_EXPLODE_IMPFIRE');
1220 g_Frames_DeleteByName('FRAMES_BFGHIT');
1221 g_Frames_DeleteByName('FRAMES_FIRE');
1222 g_Frames_DeleteByName('FRAMES_EXPLODE_PLASMA');
1223 g_Frames_DeleteByName('FRAMES_EXPLODE_BSPFIRE');
1224 g_Frames_DeleteByName('FRAMES_EXPLODE_CACOFIRE');
1225 g_Frames_DeleteByName('FRAMES_SMOKE');
1226 g_Frames_DeleteByName('FRAMES_WEAPON_BARONFIRE');
1227 g_Frames_DeleteByName('FRAMES_EXPLODE_BARONFIRE');
1228 end;
1231 function GunHitPlayer (X, Y: Integer; vx, vy: Integer; dmg: Integer; SpawnerUID: Word; AllowPush: Boolean): Boolean;
1232 var
1233 i: Integer;
1234 begin
1235 result := false;
1236 for i := 0 to High(gPlayers) do
1237 begin
1238 if (gPlayers[i] <> nil) and gPlayers[i].alive and gPlayers[i].Collide(X, Y) then
1239 begin
1240 if HitPlayer(gPlayers[i], dmg, vx*10, vy*10-3, SpawnerUID, HIT_SOME) then
1241 begin
1242 if AllowPush then gPlayers[i].Push(vx, vy);
1243 result := true;
1244 end;
1245 end;
1246 end;
1247 end;
1250 function GunHit (X, Y: Integer; vx, vy: Integer; dmg: Integer; SpawnerUID: Word; AllowPush: Boolean): Byte;
1252 function monsCheck (mon: TMonster): Boolean;
1253 begin
1254 result := false; // don't stop
1255 if HitMonster(mon, dmg, vx*10, vy*10-3, SpawnerUID, HIT_SOME) then
1256 begin
1257 if AllowPush then mon.Push(vx, vy);
1258 result := true;
1259 end;
1260 end;
1262 begin
1263 result := 0;
1264 if GunHitPlayer(X, Y, vx, vy, dmg, SpawnerUID, AllowPush) then result := 1
1265 else if g_Mons_ForEachAliveAt(X, Y, 1, 1, monsCheck) then result := 2;
1266 end;
1269 (*
1270 procedure g_Weapon_gunOld(const x, y, xd, yd, v, dmg: Integer; SpawnerUID: Word; CheckTrigger: Boolean);
1271 var
1272 a: Integer;
1273 x2, y2: Integer;
1274 dx, dy: Integer;
1275 xe, ye: Integer;
1276 xi, yi: Integer;
1277 s, c: Extended;
1278 //vx, vy: Integer;
1279 xx, yy, d: Integer;
1280 i: Integer;
1281 t1, _collide: Boolean;
1282 w, h: Word;
1283 {$IF DEFINED(D2F_DEBUG)}
1284 stt: UInt64;
1285 showTime: Boolean = true;
1286 {$ENDIF}
1287 begin
1288 a := GetAngle(x, y, xd, yd)+180;
1290 SinCos(DegToRad(-a), s, c);
1292 if Abs(s) < 0.01 then s := 0;
1293 if Abs(c) < 0.01 then c := 0;
1295 x2 := x+Round(c*gMapInfo.Width);
1296 y2 := y+Round(s*gMapInfo.Width);
1298 t1 := gWalls <> nil;
1299 _collide := False;
1300 w := gMapInfo.Width;
1301 h := gMapInfo.Height;
1303 xe := 0;
1304 ye := 0;
1305 dx := x2-x;
1306 dy := y2-y;
1308 if (xd = 0) and (yd = 0) then Exit;
1310 if dx > 0 then xi := 1 else if dx < 0 then xi := -1 else xi := 0;
1311 if dy > 0 then yi := 1 else if dy < 0 then yi := -1 else yi := 0;
1313 dx := Abs(dx);
1314 dy := Abs(dy);
1316 if dx > dy then d := dx else d := dy;
1318 //blood vel, for Monster.Damage()
1319 //vx := (dx*10 div d)*xi;
1320 //vy := (dy*10 div d)*yi;
1322 {$IF DEFINED(D2F_DEBUG)}
1323 stt := getTimeMicro();
1324 {$ENDIF}
1326 xx := x;
1327 yy := y;
1329 for i := 1 to d do
1330 begin
1331 xe := xe+dx;
1332 ye := ye+dy;
1334 if xe > d then
1335 begin
1336 xe := xe-d;
1337 xx := xx+xi;
1338 end;
1340 if ye > d then
1341 begin
1342 ye := ye-d;
1343 yy := yy+yi;
1344 end;
1346 if (yy > h) or (yy < 0) then Break;
1347 if (xx > w) or (xx < 0) then Break;
1349 if t1 then
1350 if ByteBool(gCollideMap[yy, xx] and MARK_BLOCKED) then
1351 begin
1352 _collide := True;
1353 {$IF DEFINED(D2F_DEBUG)}
1354 stt := getTimeMicro()-stt;
1355 e_WriteLog(Format('*** old trace time: %u microseconds', [LongWord(stt)]), MSG_NOTIFY);
1356 showTime := false;
1357 {$ENDIF}
1358 g_GFX_Spark(xx-xi, yy-yi, 2+Random(2), 180+a, 0, 0);
1359 if g_Game_IsServer and g_Game_IsNet then
1360 MH_SEND_Effect(xx-xi, yy-yi, 180+a, NET_GFX_SPARK);
1361 end;
1363 if not _collide then
1364 begin
1365 _collide := GunHit(xx, yy, xi*v, yi*v, dmg, SpawnerUID, v <> 0) <> 0;
1366 end;
1368 if _collide then Break;
1369 end;
1371 {$IF DEFINED(D2F_DEBUG)}
1372 if showTime then
1373 begin
1374 stt := getTimeMicro()-stt;
1375 e_WriteLog(Format('*** old trace time: %u microseconds', [LongWord(stt)]), MSG_NOTIFY);
1376 end;
1377 {$ENDIF}
1379 if CheckTrigger and g_Game_IsServer then
1380 g_Triggers_PressL(X, Y, xx-xi, yy-yi, SpawnerUID, ACTIVATE_SHOT);
1381 end;
1382 *)
1385 //!!!FIXME!!!
1386 procedure g_Weapon_gun (const x, y, xd, yd, v, dmg: Integer; SpawnerUID: Word; CheckTrigger: Boolean);
1387 var
1388 x0, y0: Integer;
1389 x2, y2: Integer;
1390 xi, yi: Integer;
1391 wallDistSq: Integer = $3fffffff;
1393 function doPlayerHit (idx: Integer; hx, hy: Integer): Boolean;
1394 begin
1395 result := false;
1396 if (idx < 0) or (idx > High(gPlayers)) then exit;
1397 if (gPlayers[idx] = nil) or not gPlayers[idx].alive then exit;
1398 result := HitPlayer(gPlayers[idx], dmg, (xi*v)*10, (yi*v)*10-3, SpawnerUID, HIT_SOME);
1399 if result and (v <> 0) then gPlayers[idx].Push((xi*v), (yi*v));
1400 {$IF DEFINED(D2F_DEBUG)}
1401 //if result then e_WriteLog(Format(' PLAYER #%d HIT', [idx]), MSG_NOTIFY);
1402 {$ENDIF}
1403 end;
1405 function doMonsterHit (mon: TMonster; hx, hy: Integer): Boolean;
1406 begin
1407 result := false;
1408 if (mon = nil) then exit;
1409 result := HitMonster(mon, dmg, (xi*v)*10, (yi*v)*10-3, SpawnerUID, HIT_SOME);
1410 if result and (v <> 0) then mon.Push((xi*v), (yi*v));
1411 {$IF DEFINED(D2F_DEBUG)}
1412 //if result then e_WriteLog(Format(' MONSTER #%u HIT', [LongWord(mon.UID)]), MSG_NOTIFY);
1413 {$ENDIF}
1414 end;
1416 // collect players along hitray
1417 // return `true` if instant hit was detected
1418 function playerPossibleHit (): Boolean;
1419 var
1420 i: Integer;
1421 px, py, pw, ph: Integer;
1422 inx, iny: Integer;
1423 distSq: Integer;
1424 plr: TPlayer;
1425 begin
1426 result := false;
1427 for i := 0 to High(gPlayers) do
1428 begin
1429 plr := gPlayers[i];
1430 if (plr <> nil) and plr.alive then
1431 begin
1432 plr.getMapBox(px, py, pw, ph);
1433 if lineAABBIntersects(x, y, x2, y2, px, py, pw, ph, inx, iny) then
1434 begin
1435 distSq := distanceSq(x, y, inx, iny);
1436 if (distSq = 0) then
1437 begin
1438 // contains
1439 if doPlayerHit(i, x, y) then begin result := true; exit; end;
1440 end
1441 else if (distSq < wallDistSq) then
1442 begin
1443 appendHitTimePlr(distSq, i, inx, iny);
1444 end;
1445 end;
1446 end;
1447 end;
1448 end;
1450 procedure sqchecker (mon: TMonster);
1451 var
1452 mx, my, mw, mh: Integer;
1453 inx, iny: Integer;
1454 distSq: Integer;
1455 begin
1456 mon.getMapBox(mx, my, mw, mh);
1457 if lineAABBIntersects(x0, y0, x2, y2, mx, my, mw, mh, inx, iny) then
1458 begin
1459 distSq := distanceSq(x0, y0, inx, iny);
1460 if (distSq < wallDistSq) then appendHitTimeMon(distSq, mon, inx, iny);
1461 end;
1462 end;
1464 var
1465 a: Integer;
1466 dx, dy: Integer;
1467 xe, ye: Integer;
1468 s, c: Extended;
1469 i: Integer;
1470 wallHitFlag: Boolean = false;
1471 wallHitX: Integer = 0;
1472 wallHitY: Integer = 0;
1473 didHit: Boolean = false;
1474 {$IF DEFINED(D2F_DEBUG)}
1475 stt: UInt64;
1476 {$ENDIF}
1477 mit: PMonster;
1478 it: TMonsterGrid.Iter;
1479 begin
1480 (*
1481 if not gwep_debug_fast_trace then
1482 begin
1483 g_Weapon_gunOld(x, y, xd, yd, v, dmg, SpawnerUID, CheckTrigger);
1484 exit;
1485 end;
1486 *)
1488 if (xd = 0) and (yd = 0) then exit;
1490 //wgunMonHash.reset(); //FIXME: clear hash on level change
1491 wgunHitHeap.clear();
1492 wgunHitTimeUsed := 0;
1494 a := GetAngle(x, y, xd, yd)+180;
1496 SinCos(DegToRad(-a), s, c);
1498 if Abs(s) < 0.01 then s := 0;
1499 if Abs(c) < 0.01 then c := 0;
1501 x0 := x;
1502 y0 := y;
1503 x2 := x+Round(c*gMapInfo.Width);
1504 y2 := y+Round(s*gMapInfo.Width);
1506 dx := x2-x;
1507 dy := y2-y;
1509 if (dx > 0) then xi := 1 else if (dx < 0) then xi := -1 else xi := 0;
1510 if (dy > 0) then yi := 1 else if (dy < 0) then yi := -1 else yi := 0;
1512 {$IF DEFINED(D2F_DEBUG)}
1513 e_WriteLog(Format('GUN TRACE: (%d,%d) to (%d,%d)', [x, y, x2, y2]), TMsgType.Notify);
1514 stt := getTimeMicro();
1515 {$ENDIF}
1517 wallHitFlag := (g_Map_traceToNearestWall(x, y, x2, y2, @wallHitX, @wallHitY) <> nil);
1518 if wallHitFlag then
1519 begin
1520 x2 := wallHitX;
1521 y2 := wallHitY;
1522 wallDistSq := distanceSq(x, y, wallHitX, wallHitY);
1523 end
1524 else
1525 begin
1526 wallHitX := x2;
1527 wallHitY := y2;
1528 end;
1530 if playerPossibleHit() then exit; // instant hit
1532 // collect monsters
1533 //g_Mons_AlongLine(x, y, x2, y2, sqchecker);
1535 it := monsGrid.forEachAlongLine(x, y, x2, y2, -1);
1536 for mit in it do sqchecker(mit^);
1537 it.release();
1539 // here, we collected all monsters and players in `wgunHitHeap` and `wgunHitTime`
1540 // also, if `wallWasHit` is `true`, then `wallHitX` and `wallHitY` contains spark coords
1541 while (wgunHitHeap.count > 0) do
1542 begin
1543 // has some entities to check, do it
1544 i := wgunHitHeap.front;
1545 wgunHitHeap.popFront();
1546 // hitpoint
1547 xe := wgunHitTime[i].x;
1548 ye := wgunHitTime[i].y;
1549 // check if it is not behind the wall
1550 if (wgunHitTime[i].mon <> nil) then
1551 begin
1552 didHit := doMonsterHit(wgunHitTime[i].mon, xe, ye);
1553 end
1554 else
1555 begin
1556 didHit := doPlayerHit(wgunHitTime[i].plridx, xe, ye);
1557 end;
1558 if didHit then
1559 begin
1560 // need new coords for trigger
1561 wallHitX := xe;
1562 wallHitY := ye;
1563 wallHitFlag := false; // no sparks
1564 break;
1565 end;
1566 end;
1568 // need sparks?
1569 if wallHitFlag then
1570 begin
1571 {$IF DEFINED(D2F_DEBUG)}
1572 stt := getTimeMicro()-stt;
1573 e_WriteLog(Format('*** new trace time: %u microseconds', [LongWord(stt)]), TMsgType.Notify);
1574 {$ENDIF}
1575 g_GFX_Spark(wallHitX, wallHitY, 2+Random(2), 180+a, 0, 0);
1576 if g_Game_IsServer and g_Game_IsNet then MH_SEND_Effect(wallHitX, wallHitY, 180+a, NET_GFX_SPARK);
1577 end
1578 else
1579 begin
1580 {$IF DEFINED(D2F_DEBUG)}
1581 stt := getTimeMicro()-stt;
1582 e_WriteLog(Format('*** new trace time: %u microseconds', [LongWord(stt)]), TMsgType.Notify);
1583 {$ENDIF}
1584 end;
1586 if CheckTrigger and g_Game_IsServer then g_Triggers_PressL(X, Y, wallHitX, wallHitY, SpawnerUID, ACTIVATE_SHOT);
1587 end;
1590 procedure g_Weapon_punch(x, y: Integer; d, SpawnerUID: Word);
1591 var
1592 obj: TObj;
1593 begin
1594 obj.X := X;
1595 obj.Y := Y;
1596 obj.rect.X := 0;
1597 obj.rect.Y := 0;
1598 obj.rect.Width := 39;
1599 obj.rect.Height := 52;
1600 obj.Vel.X := 0;
1601 obj.Vel.Y := 0;
1602 obj.Accel.X := 0;
1603 obj.Accel.Y := 0;
1605 if g_Weapon_Hit(@obj, d, SpawnerUID, HIT_SOME) <> 0 then
1606 g_Sound_PlayExAt('SOUND_WEAPON_HITPUNCH', x, y)
1607 else
1608 g_Sound_PlayExAt('SOUND_WEAPON_MISSPUNCH', x, y);
1609 end;
1611 function g_Weapon_chainsaw(x, y: Integer; d, SpawnerUID: Word): Integer;
1612 var
1613 obj: TObj;
1614 begin
1615 obj.X := X;
1616 obj.Y := Y;
1617 obj.rect.X := 0;
1618 obj.rect.Y := 0;
1619 obj.rect.Width := 32;
1620 obj.rect.Height := 52;
1621 obj.Vel.X := 0;
1622 obj.Vel.Y := 0;
1623 obj.Accel.X := 0;
1624 obj.Accel.Y := 0;
1626 Result := g_Weapon_Hit(@obj, d, SpawnerUID, HIT_SOME);
1627 end;
1629 procedure g_Weapon_rocket(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1630 Silent: Boolean = False);
1631 var
1632 find_id: DWORD;
1633 dx, dy: Integer;
1634 begin
1635 if WID < 0 then
1636 find_id := FindShot()
1637 else
1638 begin
1639 find_id := WID;
1640 if Integer(find_id) >= High(Shots) then
1641 SetLength(Shots, find_id + 64)
1642 end;
1644 with Shots[find_id] do
1645 begin
1646 g_Obj_Init(@Obj);
1648 Obj.Rect.Width := SHOT_ROCKETLAUNCHER_WIDTH;
1649 Obj.Rect.Height := SHOT_ROCKETLAUNCHER_HEIGHT;
1651 dx := IfThen(xd > x, -Obj.Rect.Width, 0);
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 := nil;
1658 triggers := nil;
1659 g_Texture_Get('TEXTURE_WEAPON_ROCKET', TextureID);
1660 end;
1662 Shots[find_id].SpawnerUID := SpawnerUID;
1664 if not Silent then
1665 g_Sound_PlayExAt('SOUND_WEAPON_FIREROCKET', x, y);
1666 end;
1668 procedure g_Weapon_revf(x, y, xd, yd: Integer; SpawnerUID, TargetUID: Word;
1669 WID: Integer = -1; Silent: Boolean = False);
1670 var
1671 find_id, FramesID: DWORD;
1672 dx, dy: Integer;
1673 begin
1674 if WID < 0 then
1675 find_id := FindShot()
1676 else
1677 begin
1678 find_id := WID;
1679 if Integer(find_id) >= High(Shots) then
1680 SetLength(Shots, find_id + 64)
1681 end;
1683 with Shots[find_id] do
1684 begin
1685 g_Obj_Init(@Obj);
1687 Obj.Rect.Width := SHOT_SKELFIRE_WIDTH;
1688 Obj.Rect.Height := SHOT_SKELFIRE_HEIGHT;
1690 dx := -(Obj.Rect.Width div 2);
1691 dy := -(Obj.Rect.Height div 2);
1693 ShotType := WEAPON_SKEL_FIRE;
1694 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 12);
1696 triggers := nil;
1697 target := TargetUID;
1698 g_Frames_Get(FramesID, 'FRAMES_WEAPON_SKELFIRE');
1699 Animation := TAnimation.Create(FramesID, True, 5);
1700 end;
1702 Shots[find_id].SpawnerUID := SpawnerUID;
1704 if not Silent then
1705 g_Sound_PlayExAt('SOUND_WEAPON_FIREREV', x, y);
1706 end;
1708 procedure g_Weapon_plasma(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1709 Silent: Boolean = False);
1710 var
1711 find_id, FramesID: DWORD;
1712 dx, dy: Integer;
1713 begin
1714 if WID < 0 then
1715 find_id := FindShot()
1716 else
1717 begin
1718 find_id := WID;
1719 if Integer(find_id) >= High(Shots) then
1720 SetLength(Shots, find_id + 64);
1721 end;
1723 with Shots[find_id] do
1724 begin
1725 g_Obj_Init(@Obj);
1727 Obj.Rect.Width := SHOT_PLASMA_WIDTH;
1728 Obj.Rect.Height := SHOT_PLASMA_HEIGHT;
1730 dx := IfThen(xd>x, -Obj.Rect.Width, 0);
1731 dy := -(Obj.Rect.Height div 2);
1733 ShotType := WEAPON_PLASMA;
1734 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1736 triggers := nil;
1737 g_Frames_Get(FramesID, 'FRAMES_WEAPON_PLASMA');
1738 Animation := TAnimation.Create(FramesID, True, 5);
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);
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 dx := IfThen(xd>x, -Obj.Rect.Width, 0);
1770 dy := -(Obj.Rect.Height div 2);
1772 ShotType := WEAPON_FLAMETHROWER;
1773 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1775 triggers := nil;
1776 Animation := nil;
1777 TextureID := 0;
1778 g_Frames_Get(TextureID, 'FRAMES_FLAME');
1779 end;
1781 Shots[find_id].SpawnerUID := SpawnerUID;
1783 // if not Silent then
1784 // g_Sound_PlayExAt('SOUND_WEAPON_FIREPLASMA', x, y);
1785 end;
1787 procedure g_Weapon_ball1(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1788 Silent: Boolean = False);
1789 var
1790 find_id, FramesID: DWORD;
1791 dx, dy: Integer;
1792 begin
1793 if WID < 0 then
1794 find_id := FindShot()
1795 else
1796 begin
1797 find_id := WID;
1798 if Integer(find_id) >= High(Shots) then
1799 SetLength(Shots, find_id + 64)
1800 end;
1802 with Shots[find_id] do
1803 begin
1804 g_Obj_Init(@Obj);
1806 Obj.Rect.Width := 16;
1807 Obj.Rect.Height := 16;
1809 dx := IfThen(xd>x, -Obj.Rect.Width, 0);
1810 dy := -(Obj.Rect.Height div 2);
1812 ShotType := WEAPON_IMP_FIRE;
1813 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1815 triggers := nil;
1816 g_Frames_Get(FramesID, 'FRAMES_WEAPON_IMPFIRE');
1817 Animation := TAnimation.Create(FramesID, True, 4);
1818 end;
1820 Shots[find_id].SpawnerUID := SpawnerUID;
1822 if not Silent then
1823 g_Sound_PlayExAt('SOUND_WEAPON_FIREBALL', x, y);
1824 end;
1826 procedure g_Weapon_ball2(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1827 Silent: Boolean = False);
1828 var
1829 find_id, FramesID: DWORD;
1830 dx, dy: Integer;
1831 begin
1832 if WID < 0 then
1833 find_id := FindShot()
1834 else
1835 begin
1836 find_id := WID;
1837 if Integer(find_id) >= High(Shots) then
1838 SetLength(Shots, find_id + 64)
1839 end;
1841 with Shots[find_id] do
1842 begin
1843 g_Obj_Init(@Obj);
1845 Obj.Rect.Width := 16;
1846 Obj.Rect.Height := 16;
1848 dx := IfThen(xd>x, -Obj.Rect.Width, 0);
1849 dy := -(Obj.Rect.Height div 2);
1851 ShotType := WEAPON_CACO_FIRE;
1852 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1854 triggers := nil;
1855 g_Frames_Get(FramesID, 'FRAMES_WEAPON_CACOFIRE');
1856 Animation := TAnimation.Create(FramesID, True, 4);
1857 end;
1859 Shots[find_id].SpawnerUID := SpawnerUID;
1861 if not Silent then
1862 g_Sound_PlayExAt('SOUND_WEAPON_FIREBALL', x, y);
1863 end;
1865 procedure g_Weapon_ball7(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1866 Silent: Boolean = False);
1867 var
1868 find_id, FramesID: DWORD;
1869 dx, dy: Integer;
1870 begin
1871 if WID < 0 then
1872 find_id := FindShot()
1873 else
1874 begin
1875 find_id := WID;
1876 if Integer(find_id) >= High(Shots) then
1877 SetLength(Shots, find_id + 64)
1878 end;
1880 with Shots[find_id] do
1881 begin
1882 g_Obj_Init(@Obj);
1884 Obj.Rect.Width := 32;
1885 Obj.Rect.Height := 16;
1887 dx := IfThen(xd>x, -Obj.Rect.Width, 0);
1888 dy := -(Obj.Rect.Height div 2);
1890 ShotType := WEAPON_BARON_FIRE;
1891 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1893 triggers := nil;
1894 g_Frames_Get(FramesID, 'FRAMES_WEAPON_BARONFIRE');
1895 Animation := TAnimation.Create(FramesID, True, 4);
1896 end;
1898 Shots[find_id].SpawnerUID := SpawnerUID;
1900 if not Silent then
1901 g_Sound_PlayExAt('SOUND_WEAPON_FIREBALL', x, y);
1902 end;
1904 procedure g_Weapon_aplasma(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1905 Silent: Boolean = False);
1906 var
1907 find_id, FramesID: DWORD;
1908 dx, dy: Integer;
1909 begin
1910 if WID < 0 then
1911 find_id := FindShot()
1912 else
1913 begin
1914 find_id := WID;
1915 if Integer(find_id) >= High(Shots) then
1916 SetLength(Shots, find_id + 64)
1917 end;
1919 with Shots[find_id] do
1920 begin
1921 g_Obj_Init(@Obj);
1923 Obj.Rect.Width := 16;
1924 Obj.Rect.Height := 16;
1926 dx := IfThen(xd>x, -Obj.Rect.Width, 0);
1927 dy := -(Obj.Rect.Height div 2);
1929 ShotType := WEAPON_BSP_FIRE;
1930 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1932 triggers := nil;
1934 g_Frames_Get(FramesID, 'FRAMES_WEAPON_BSPFIRE');
1935 Animation := TAnimation.Create(FramesID, True, 4);
1936 end;
1938 Shots[find_id].SpawnerUID := SpawnerUID;
1940 if not Silent then
1941 g_Sound_PlayExAt('SOUND_WEAPON_FIREPLASMA', x, y);
1942 end;
1944 procedure g_Weapon_manfire(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1945 Silent: Boolean = False);
1946 var
1947 find_id, FramesID: DWORD;
1948 dx, dy: Integer;
1949 begin
1950 if WID < 0 then
1951 find_id := FindShot()
1952 else
1953 begin
1954 find_id := WID;
1955 if Integer(find_id) >= High(Shots) then
1956 SetLength(Shots, find_id + 64)
1957 end;
1959 with Shots[find_id] do
1960 begin
1961 g_Obj_Init(@Obj);
1963 Obj.Rect.Width := 32;
1964 Obj.Rect.Height := 32;
1966 dx := IfThen(xd>x, -Obj.Rect.Width, 0);
1967 dy := -(Obj.Rect.Height div 2);
1969 ShotType := WEAPON_MANCUB_FIRE;
1970 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
1972 triggers := nil;
1974 g_Frames_Get(FramesID, 'FRAMES_WEAPON_MANCUBFIRE');
1975 Animation := TAnimation.Create(FramesID, True, 4);
1976 end;
1978 Shots[find_id].SpawnerUID := SpawnerUID;
1980 if not Silent then
1981 g_Sound_PlayExAt('SOUND_WEAPON_FIREBALL', x, y);
1982 end;
1984 procedure g_Weapon_bfgshot(x, y, xd, yd: Integer; SpawnerUID: Word; WID: Integer = -1;
1985 Silent: Boolean = False);
1986 var
1987 find_id, FramesID: DWORD;
1988 dx, dy: Integer;
1989 begin
1990 if WID < 0 then
1991 find_id := FindShot()
1992 else
1993 begin
1994 find_id := WID;
1995 if Integer(find_id) >= High(Shots) then
1996 SetLength(Shots, find_id + 64)
1997 end;
1999 with Shots[find_id] do
2000 begin
2001 g_Obj_Init(@Obj);
2003 Obj.Rect.Width := SHOT_BFG_WIDTH;
2004 Obj.Rect.Height := SHOT_BFG_HEIGHT;
2006 dx := IfThen(xd>x, -Obj.Rect.Width, 0);
2007 dy := -(Obj.Rect.Height div 2);
2009 ShotType := WEAPON_BFG;
2010 throw(find_id, x+dx, y+dy, xd+dx, yd+dy, 16);
2012 triggers := nil;
2013 g_Frames_Get(FramesID, 'FRAMES_WEAPON_BFG');
2014 Animation := TAnimation.Create(FramesID, True, 6);
2015 end;
2017 Shots[find_id].SpawnerUID := SpawnerUID;
2019 if not Silent then
2020 g_Sound_PlayExAt('SOUND_WEAPON_FIREBFG', x, y);
2021 end;
2023 procedure g_Weapon_bfghit(x, y: Integer);
2024 var
2025 ID: DWORD;
2026 Anim: TAnimation;
2027 begin
2028 if g_Frames_Get(ID, 'FRAMES_BFGHIT') then
2029 begin
2030 Anim := TAnimation.Create(ID, False, 4);
2031 g_GFX_OnceAnim(x-32, y-32, Anim);
2032 Anim.Free();
2033 end;
2034 end;
2036 procedure g_Weapon_pistol(x, y, xd, yd: Integer; SpawnerUID: Word;
2037 Silent: Boolean = False);
2038 begin
2039 if not Silent then
2040 g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', x, y);
2042 g_Weapon_gun(x, y, xd, yd, 1, 3, SpawnerUID, True);
2043 if gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF] then
2044 begin
2045 g_Weapon_gun(x, y+1, xd, yd+1, 1, 3, SpawnerUID, False);
2046 g_Weapon_gun(x, y-1, xd, yd-1, 1, 2, SpawnerUID, False);
2047 end;
2048 end;
2050 procedure g_Weapon_mgun(x, y, xd, yd: Integer; SpawnerUID: Word;
2051 Silent: Boolean = False);
2052 begin
2053 if not Silent then
2054 if gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', x, y);
2056 g_Weapon_gun(x, y, xd, yd, 1, 3, SpawnerUID, True);
2057 if (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF]) and
2058 (g_GetUIDType(SpawnerUID) = UID_PLAYER) then
2059 begin
2060 g_Weapon_gun(x, y+1, xd, yd+1, 1, 2, SpawnerUID, False);
2061 g_Weapon_gun(x, y-1, xd, yd-1, 1, 2, SpawnerUID, False);
2062 end;
2063 end;
2065 procedure g_Weapon_shotgun(x, y, xd, yd: Integer; SpawnerUID: Word;
2066 Silent: Boolean = False);
2067 var
2068 i, j: Integer;
2069 begin
2070 if not Silent then
2071 if gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', x, y);
2073 for i := 0 to 9 do
2074 begin
2075 j := Random(17)-8; // -8 .. 8
2076 g_Weapon_gun(x, y+j, xd, yd+j, IfThen(i mod 2 <> 0, 1, 0), 3, SpawnerUID, i=0);
2077 end;
2078 end;
2080 procedure g_Weapon_dshotgun(x, y, xd, yd: Integer; SpawnerUID: Word;
2081 Silent: Boolean = False);
2082 var
2083 a, i, j: Integer;
2084 begin
2085 if not Silent then
2086 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN2', x, y);
2088 if gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF] then a := 25 else a := 20;
2089 for i := 0 to a do
2090 begin
2091 j := Random(41)-20; // -20 .. 20
2092 g_Weapon_gun(x, y+j, xd, yd+j, IfThen(i mod 3 <> 0, 0, 1), 3, SpawnerUID, i=0);
2093 end;
2094 end;
2096 procedure g_Weapon_Update();
2097 var
2098 i, a, h, cx, cy, oldvx, oldvy, tf: Integer;
2099 _id: DWORD;
2100 Anim: TAnimation;
2101 t: DWArray;
2102 st: Word;
2103 s: String;
2104 o: TObj;
2105 spl: Boolean;
2106 Loud: Boolean;
2107 tcx, tcy: Integer;
2108 begin
2109 if Shots = nil then
2110 Exit;
2112 for i := 0 to High(Shots) do
2113 begin
2114 if Shots[i].ShotType = 0 then
2115 Continue;
2117 Loud := True;
2119 with Shots[i] do
2120 begin
2121 Timeout := Timeout - 1;
2122 oldvx := Obj.Vel.X;
2123 oldvy := Obj.Vel.Y;
2124 // Àêòèâèðîâàòü òðèããåðû ïî ïóòè (êðîìå óæå àêòèâèðîâàííûõ):
2125 if (Stopped = 0) and g_Game_IsServer then
2126 t := g_Triggers_PressR(Obj.X, Obj.Y, Obj.Rect.Width, Obj.Rect.Height,
2127 SpawnerUID, ACTIVATE_SHOT, triggers)
2128 else
2129 t := nil;
2131 if t <> nil then
2132 begin
2133 // Ïîïîëíÿåì ñïèñîê àêòèâèðîâàííûõ òðèããåðîâ:
2134 if triggers = nil then
2135 triggers := t
2136 else
2137 begin
2138 h := High(t);
2140 for a := 0 to h do
2141 if not InDWArray(t[a], triggers) then
2142 begin
2143 SetLength(triggers, Length(triggers)+1);
2144 triggers[High(triggers)] := t[a];
2145 end;
2146 end;
2147 end;
2149 // Àíèìàöèÿ ñíàðÿäà:
2150 if Animation <> nil then
2151 Animation.Update();
2153 // Äâèæåíèå:
2154 spl := (ShotType <> WEAPON_PLASMA) and
2155 (ShotType <> WEAPON_BFG) and
2156 (ShotType <> WEAPON_BSP_FIRE) and
2157 (ShotType <> WEAPON_FLAMETHROWER);
2159 if Stopped = 0 then
2160 begin
2161 st := g_Obj_Move_Projectile(@Obj, False, spl);
2162 end
2163 else
2164 begin
2165 st := 0;
2166 end;
2167 positionChanged(); // this updates spatial accelerators
2169 if WordBool(st and MOVE_FALLOUT) or (Obj.X < -1000) or
2170 (Obj.X > gMapInfo.Width+1000) or (Obj.Y < -1000) then
2171 begin
2172 // Íà êëèåíòå ñêîðåå âñåãî è òàê óæå âûïàë.
2173 ShotType := 0;
2174 Animation.Free();
2175 Continue;
2176 end;
2178 cx := Obj.X + (Obj.Rect.Width div 2);
2179 cy := Obj.Y + (Obj.Rect.Height div 2);
2181 case ShotType of
2182 WEAPON_ROCKETLAUNCHER, WEAPON_SKEL_FIRE: // Ðàêåòû è ñíàðÿäû Ñêåëåòà
2183 begin
2184 // Âûëåòåëà èç âîäû:
2185 if WordBool(st and MOVE_HITAIR) then
2186 g_Obj_SetSpeed(@Obj, 12);
2188 // Â âîäå øëåéô - ïóçûðè, â âîçäóõå øëåéô - äûì:
2189 if WordBool(st and MOVE_INWATER) then
2190 g_GFX_Bubbles(Obj.X+(Obj.Rect.Width div 2),
2191 Obj.Y+(Obj.Rect.Height div 2),
2192 1+Random(3), 16, 16)
2193 else
2194 if g_Frames_Get(_id, 'FRAMES_SMOKE') then
2195 begin
2196 Anim := TAnimation.Create(_id, False, 3);
2197 Anim.Alpha := 150;
2198 g_GFX_OnceAnim(Obj.X-14+Random(9),
2199 Obj.Y+(Obj.Rect.Height div 2)-20+Random(9),
2200 Anim, ONCEANIM_SMOKE);
2201 Anim.Free();
2202 end;
2204 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2205 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL)) or
2206 (g_Weapon_Hit(@Obj, 10, SpawnerUID, HIT_SOME, False) <> 0) or
2207 (Timeout < 1) then
2208 begin
2209 Obj.Vel.X := 0;
2210 Obj.Vel.Y := 0;
2212 g_Weapon_Explode(cx, cy, 60, SpawnerUID);
2214 if ShotType = WEAPON_SKEL_FIRE then
2215 begin // Âçðûâ ñíàðÿäà Ñêåëåòà
2216 if g_Frames_Get(TextureID, 'FRAMES_EXPLODE_SKELFIRE') then
2217 begin
2218 Anim := TAnimation.Create(TextureID, False, 8);
2219 Anim.Blending := False;
2220 g_GFX_OnceAnim((Obj.X+32)-58, (Obj.Y+8)-36, Anim);
2221 g_DynLightExplosion((Obj.X+32), (Obj.Y+8), 64, 1, 0, 0);
2222 Anim.Free();
2223 end;
2224 end
2225 else
2226 begin // Âçðûâ Ðàêåòû
2227 if g_Frames_Get(TextureID, 'FRAMES_EXPLODE_ROCKET') then
2228 begin
2229 Anim := TAnimation.Create(TextureID, False, 6);
2230 Anim.Blending := False;
2231 g_GFX_OnceAnim(cx-64, cy-64, Anim);
2232 g_DynLightExplosion(cx, cy, 64, 1, 0, 0);
2233 Anim.Free();
2234 end;
2235 end;
2237 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEROCKET', Obj.X, Obj.Y);
2239 ShotType := 0;
2240 end;
2242 if ShotType = WEAPON_SKEL_FIRE then
2243 begin // Ñàìîíàâîäêà ñíàðÿäà Ñêåëåòà:
2244 if GetPos(target, @o) then
2245 throw(i, Obj.X, Obj.Y,
2246 o.X+o.Rect.X+(o.Rect.Width div 2)+o.Vel.X+o.Accel.X,
2247 o.Y+o.Rect.Y+(o.Rect.Height div 2)+o.Vel.Y+o.Accel.Y,
2248 12);
2249 end;
2250 end;
2252 WEAPON_PLASMA, WEAPON_BSP_FIRE: // Ïëàçìà, ïëàçìà Àðàõíàòðîíà
2253 begin
2254 // Ïîïàëà â âîäó - ýëåêòðîøîê ïî âîäå:
2255 if WordBool(st and (MOVE_INWATER or MOVE_HITWATER)) then
2256 begin
2257 g_Sound_PlayExAt('SOUND_WEAPON_PLASMAWATER', Obj.X, Obj.Y);
2258 if g_Game_IsServer then CheckTrap(i, 10, HIT_ELECTRO);
2259 ShotType := 0;
2260 Continue;
2261 end;
2263 // Âåëè÷èíà óðîíà:
2264 if (ShotType = WEAPON_PLASMA) and
2265 (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF]) then
2266 a := 10
2267 else
2268 a := 5;
2270 if ShotType = WEAPON_BSP_FIRE then
2271 a := 10;
2273 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2274 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL)) or
2275 (g_Weapon_Hit(@Obj, a, SpawnerUID, HIT_SOME, False) <> 0) or
2276 (Timeout < 1) then
2277 begin
2278 if ShotType = WEAPON_PLASMA then
2279 s := 'FRAMES_EXPLODE_PLASMA'
2280 else
2281 s := 'FRAMES_EXPLODE_BSPFIRE';
2283 // Âçðûâ Ïëàçìû:
2284 if g_Frames_Get(TextureID, s) then
2285 begin
2286 Anim := TAnimation.Create(TextureID, False, 3);
2287 Anim.Blending := False;
2288 g_GFX_OnceAnim(cx-16, cy-16, Anim);
2289 Anim.Free();
2290 g_DynLightExplosion(cx, cy, 32, 0, 0.5, 0.5);
2291 end;
2293 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEPLASMA', Obj.X, Obj.Y);
2295 ShotType := 0;
2296 end;
2297 end;
2299 WEAPON_FLAMETHROWER: // Îãíåìåò
2300 begin
2301 // Ñî âðåìåíåì óìèðàåò
2302 if (Timeout < 1) then
2303 begin
2304 ShotType := 0;
2305 Continue;
2306 end;
2307 // Ïîä âîäîé òîæå
2308 if WordBool(st and (MOVE_HITWATER or MOVE_INWATER)) then
2309 begin
2310 if WordBool(st and MOVE_HITWATER) then
2311 begin
2312 if g_Frames_Get(_id, 'FRAMES_SMOKE') then
2313 begin
2314 Anim := TAnimation.Create(_id, False, 3);
2315 Anim.Alpha := 0;
2316 tcx := Random(8);
2317 tcy := Random(8);
2318 g_GFX_OnceAnim(cx-4+tcx-(Anim.Width div 2),
2319 cy-4+tcy-(Anim.Height div 2),
2320 Anim, ONCEANIM_SMOKE);
2321 Anim.Free();
2322 end;
2323 end
2324 else
2325 g_GFX_Bubbles(cx, cy, 1+Random(3), 16, 16);
2326 ShotType := 0;
2327 Continue;
2328 end;
2330 // Ãðàâèòàöèÿ
2331 if Stopped = 0 then
2332 Obj.Accel.Y := Obj.Accel.Y + 1;
2333 // Ïîïàëè â ñòåíó èëè â âîäó:
2334 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL or MOVE_HITWATER)) then
2335 begin
2336 // Ïðèëèïàåì:
2337 Obj.Vel.X := 0;
2338 Obj.Vel.Y := 0;
2339 Obj.Accel.Y := 0;
2340 if WordBool(st and MOVE_HITWALL) then
2341 Stopped := MOVE_HITWALL
2342 else if WordBool(st and MOVE_HITLAND) then
2343 Stopped := MOVE_HITLAND
2344 else if WordBool(st and MOVE_HITCEIL) then
2345 Stopped := MOVE_HITCEIL;
2346 end;
2348 a := IfThen(Stopped = 0, 10, 1);
2349 // Åñëè â êîãî-òî ïîïàëè
2350 if g_Weapon_Hit(@Obj, a, SpawnerUID, HIT_FLAME, False) <> 0 then
2351 begin
2352 // HIT_FLAME ñàì ïîäîææåò
2353 // Åñëè â ïîëåòå ïîïàëè, èñ÷åçàåì
2354 if Stopped = 0 then
2355 ShotType := 0;
2356 end;
2358 if Stopped = 0 then
2359 tf := 2
2360 else
2361 tf := 3;
2363 if (gTime mod LongWord(tf) = 0) then
2364 begin
2365 Anim := TAnimation.Create(TextureID, False, 2 + Random(2));
2366 Anim.Alpha := 0;
2367 case Stopped of
2368 MOVE_HITWALL: begin tcx := cx-4+Random(8); tcy := cy-12+Random(24); end;
2369 MOVE_HITLAND: begin tcx := cx-12+Random(24); tcy := cy-10+Random(8); end;
2370 MOVE_HITCEIL: begin tcx := cx-12+Random(24); tcy := cy+6+Random(8); end;
2371 else begin tcx := cx-4+Random(8); tcy := cy-4+Random(8); end;
2372 end;
2373 g_GFX_OnceAnim(tcx-(Anim.Width div 2), tcy-(Anim.Height div 2), Anim, ONCEANIM_SMOKE);
2374 Anim.Free();
2375 //g_DynLightExplosion(tcx, tcy, 1, 1, 0.8, 0.3);
2376 end;
2377 end;
2379 WEAPON_BFG: // BFG
2380 begin
2381 // Ïîïàëà â âîäó - ýëåêòðîøîê ïî âîäå:
2382 if WordBool(st and (MOVE_INWATER or MOVE_HITWATER)) then
2383 begin
2384 g_Sound_PlayExAt('SOUND_WEAPON_BFGWATER', Obj.X, Obj.Y);
2385 if g_Game_IsServer then CheckTrap(i, 1000, HIT_ELECTRO);
2386 ShotType := 0;
2387 Continue;
2388 end;
2390 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2391 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL)) or
2392 (g_Weapon_Hit(@Obj, SHOT_BFG_DAMAGE, SpawnerUID, HIT_BFG, False) <> 0) or
2393 (Timeout < 1) then
2394 begin
2395 // Ëó÷è BFG:
2396 if g_Game_IsServer then g_Weapon_BFG9000(cx, cy, SpawnerUID);
2398 // Âçðûâ BFG:
2399 if g_Frames_Get(TextureID, 'FRAMES_EXPLODE_BFG') then
2400 begin
2401 Anim := TAnimation.Create(TextureID, False, 6);
2402 Anim.Blending := False;
2403 g_GFX_OnceAnim(cx-64, cy-64, Anim);
2404 Anim.Free();
2405 g_DynLightExplosion(cx, cy, 96, 0, 1, 0);
2406 end;
2408 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBFG', Obj.X, Obj.Y);
2410 ShotType := 0;
2411 end;
2412 end;
2414 WEAPON_IMP_FIRE, WEAPON_CACO_FIRE, WEAPON_BARON_FIRE: // Âûñòðåëû Áåñà, Êàêîäåìîíà Ðûöàðÿ/Áàðîíà àäà
2415 begin
2416 // Âûëåòåë èç âîäû:
2417 if WordBool(st and MOVE_HITAIR) then
2418 g_Obj_SetSpeed(@Obj, 16);
2420 // Âåëè÷èíà óðîíà:
2421 if ShotType = WEAPON_IMP_FIRE then
2422 a := 5
2423 else
2424 if ShotType = WEAPON_CACO_FIRE then
2425 a := 20
2426 else
2427 a := 40;
2429 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2430 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL)) or
2431 (g_Weapon_Hit(@Obj, a, SpawnerUID, HIT_SOME) <> 0) or
2432 (Timeout < 1) then
2433 begin
2434 if ShotType = WEAPON_IMP_FIRE then
2435 s := 'FRAMES_EXPLODE_IMPFIRE'
2436 else
2437 if ShotType = WEAPON_CACO_FIRE then
2438 s := 'FRAMES_EXPLODE_CACOFIRE'
2439 else
2440 s := 'FRAMES_EXPLODE_BARONFIRE';
2442 // Âçðûâ:
2443 if g_Frames_Get(TextureID, s) then
2444 begin
2445 Anim := TAnimation.Create(TextureID, False, 6);
2446 Anim.Blending := False;
2447 g_GFX_OnceAnim(cx-32, cy-32, Anim);
2448 Anim.Free();
2449 end;
2451 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBALL', Obj.X, Obj.Y);
2453 ShotType := 0;
2454 end;
2455 end;
2457 WEAPON_MANCUB_FIRE: // Âûñòðåë Ìàíêóáóñà
2458 begin
2459 // Âûëåòåë èç âîäû:
2460 if WordBool(st and MOVE_HITAIR) then
2461 g_Obj_SetSpeed(@Obj, 16);
2463 // Ïîïàëè â êîãî-òî èëè â ñòåíó:
2464 if WordBool(st and (MOVE_HITWALL or MOVE_HITLAND or MOVE_HITCEIL)) or
2465 (g_Weapon_Hit(@Obj, 40, SpawnerUID, HIT_SOME, False) <> 0) or
2466 (Timeout < 1) then
2467 begin
2468 // Âçðûâ:
2469 if g_Frames_Get(TextureID, 'FRAMES_EXPLODE_ROCKET') then
2470 begin
2471 Anim := TAnimation.Create(TextureID, False, 6);
2472 Anim.Blending := False;
2473 g_GFX_OnceAnim(cx-64, cy-64, Anim);
2474 Anim.Free();
2475 end;
2477 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBALL', Obj.X, Obj.Y);
2479 ShotType := 0;
2480 end;
2481 end;
2482 end; // case ShotType of...
2484 // Åñëè ñíàðÿäà óæå íåò, óäàëÿåì àíèìàöèþ:
2485 if (ShotType = 0) then
2486 begin
2487 if gGameSettings.GameType = GT_SERVER then
2488 MH_SEND_DeleteShot(i, Obj.X, Obj.Y, Loud);
2489 if Animation <> nil then
2490 begin
2491 Animation.Free();
2492 Animation := nil;
2493 end;
2494 end
2495 else if (ShotType <> WEAPON_FLAMETHROWER) and ((oldvx <> Obj.Vel.X) or (oldvy <> Obj.Vel.Y)) then
2496 if gGameSettings.GameType = GT_SERVER then
2497 MH_SEND_UpdateShot(i);
2498 end;
2499 end;
2500 end;
2502 procedure g_Weapon_Draw();
2503 var
2504 i: Integer;
2505 a: SmallInt;
2506 p: TDFPoint;
2507 begin
2508 if Shots = nil then
2509 Exit;
2511 for i := 0 to High(Shots) do
2512 if Shots[i].ShotType <> 0 then
2513 with Shots[i] do
2514 begin
2515 if (Shots[i].ShotType = WEAPON_ROCKETLAUNCHER) or
2516 (Shots[i].ShotType = WEAPON_BARON_FIRE) or
2517 (Shots[i].ShotType = WEAPON_MANCUB_FIRE) or
2518 (Shots[i].ShotType = WEAPON_SKEL_FIRE) then
2519 a := -GetAngle2(Obj.Vel.X, Obj.Vel.Y)
2520 else
2521 a := 0;
2523 p.X := Obj.Rect.Width div 2;
2524 p.Y := Obj.Rect.Height div 2;
2526 if Animation <> nil then
2527 begin
2528 if (Shots[i].ShotType = WEAPON_BARON_FIRE) or
2529 (Shots[i].ShotType = WEAPON_MANCUB_FIRE) or
2530 (Shots[i].ShotType = WEAPON_SKEL_FIRE) then
2531 Animation.DrawEx(Obj.X, Obj.Y, TMirrorType.None, p, a)
2532 else
2533 Animation.Draw(Obj.X, Obj.Y, TMirrorType.None);
2534 end
2535 else if TextureID <> 0 then
2536 begin
2537 if (Shots[i].ShotType = WEAPON_ROCKETLAUNCHER) then
2538 e_DrawAdv(TextureID, Obj.X, Obj.Y, 0, True, False, a, @p, TMirrorType.None)
2539 else if (Shots[i].ShotType <> WEAPON_FLAMETHROWER) then
2540 e_Draw(TextureID, Obj.X, Obj.Y, 0, True, False);
2541 end;
2543 if g_debug_Frames then
2544 begin
2545 e_DrawQuad(Obj.X+Obj.Rect.X,
2546 Obj.Y+Obj.Rect.Y,
2547 Obj.X+Obj.Rect.X+Obj.Rect.Width-1,
2548 Obj.Y+Obj.Rect.Y+Obj.Rect.Height-1,
2549 0, 255, 0);
2550 end;
2551 end;
2552 end;
2554 function g_Weapon_Danger(UID: Word; X, Y: Integer; Width, Height: Word; Time: Byte): Boolean;
2555 var
2556 a: Integer;
2557 begin
2558 Result := False;
2560 if Shots = nil then
2561 Exit;
2563 for a := 0 to High(Shots) do
2564 if (Shots[a].ShotType <> 0) and (Shots[a].SpawnerUID <> UID) then
2565 if ((Shots[a].Obj.Vel.Y = 0) and (Shots[a].Obj.Vel.X > 0) and (Shots[a].Obj.X < X)) or
2566 (Shots[a].Obj.Vel.Y = 0) and (Shots[a].Obj.Vel.X < 0) and (Shots[a].Obj.X > X) then
2567 if (Abs(X-Shots[a].Obj.X) < Abs(Shots[a].Obj.Vel.X*Time)) and
2568 g_Collide(X, Y, Width, Height, X, Shots[a].Obj.Y,
2569 Shots[a].Obj.Rect.Width, Shots[a].Obj.Rect.Height) and
2570 g_TraceVector(X, Y, Shots[a].Obj.X, Shots[a].Obj.Y) then
2571 begin
2572 Result := True;
2573 Exit;
2574 end;
2575 end;
2577 procedure g_Weapon_SaveState (st: TStream);
2578 var
2579 count, i, j: Integer;
2580 begin
2581 // Ñ÷èòàåì êîëè÷åñòâî ñóùåñòâóþùèõ ñíàðÿäîâ
2582 count := 0;
2583 for i := 0 to High(Shots) do if (Shots[i].ShotType <> 0) then Inc(count);
2585 // Êîëè÷åñòâî ñíàðÿäîâ
2586 utils.WriteInt(st, count);
2588 if (count = 0) then exit;
2590 for i := 0 to High(Shots) do
2591 begin
2592 if Shots[i].ShotType <> 0 then
2593 begin
2594 // Ñèãíàòóðà ñíàðÿäà
2595 utils.writeSign(st, 'SHOT');
2596 utils.writeInt(st, Byte(0)); // version
2597 // Òèï ñíàðÿäà
2598 utils.writeInt(st, Byte(Shots[i].ShotType));
2599 // Öåëü
2600 utils.writeInt(st, Word(Shots[i].Target));
2601 // UID ñòðåëÿâøåãî
2602 utils.writeInt(st, Word(Shots[i].SpawnerUID));
2603 // Ðàçìåð ïîëÿ Triggers
2604 utils.writeInt(st, Integer(Length(Shots[i].Triggers)));
2605 // Òðèããåðû, àêòèâèðîâàííûå âûñòðåëîì
2606 for j := 0 to Length(Shots[i].Triggers)-1 do utils.writeInt(st, LongWord(Shots[i].Triggers[j]));
2607 // Îáúåêò ñíàðÿäà
2608 Obj_SaveState(st, @Shots[i].Obj);
2609 // Êîñòûëèíà åáàíàÿ
2610 utils.writeInt(st, Byte(Shots[i].Stopped));
2611 end;
2612 end;
2613 end;
2615 procedure g_Weapon_LoadState (st: TStream);
2616 var
2617 count, tc, i, j: Integer;
2618 dw: LongWord;
2619 begin
2620 if (st = nil) then exit;
2622 // Êîëè÷åñòâî ñíàðÿäîâ
2623 count := utils.readLongInt(st);
2624 if (count < 0) or (count > 1024*1024) then raise XStreamError.Create('invalid shots counter');
2626 SetLength(Shots, count);
2628 if (count = 0) then exit;
2630 for i := 0 to count-1 do
2631 begin
2632 // Ñèãíàòóðà ñíàðÿäà
2633 if not utils.checkSign(st, 'SHOT') then raise XStreamError.Create('invalid shot signature');
2634 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid shot version');
2635 // Òèï ñíàðÿäà:
2636 Shots[i].ShotType := utils.readByte(st);
2637 // Öåëü
2638 Shots[i].Target := utils.readWord(st);
2639 // UID ñòðåëÿâøåãî
2640 Shots[i].SpawnerUID := utils.readWord(st);
2641 // Ðàçìåð ïîëÿ Triggers
2642 tc := utils.readLongInt(st);
2643 if (tc < 0) or (tc > 1024*1024) then raise XStreamError.Create('invalid shot triggers counter');
2644 SetLength(Shots[i].Triggers, tc);
2645 // Òðèããåðû, àêòèâèðîâàííûå âûñòðåëîì
2646 for j := 0 to tc-1 do Shots[i].Triggers[j] := utils.readLongWord(st);
2647 // Îáúåêò ïðåäìåòà
2648 Obj_LoadState(@Shots[i].Obj, st);
2649 // Êîñòûëèíà åáàíàÿ
2650 Shots[i].Stopped := utils.readByte(st);
2652 // Óñòàíîâêà òåêñòóðû èëè àíèìàöèè
2653 Shots[i].TextureID := DWORD(-1);
2654 Shots[i].Animation := nil;
2656 case Shots[i].ShotType of
2657 WEAPON_ROCKETLAUNCHER, WEAPON_SKEL_FIRE:
2658 begin
2659 g_Texture_Get('TEXTURE_WEAPON_ROCKET', Shots[i].TextureID);
2660 end;
2661 WEAPON_PLASMA:
2662 begin
2663 g_Frames_Get(dw, 'FRAMES_WEAPON_PLASMA');
2664 Shots[i].Animation := TAnimation.Create(dw, True, 5);
2665 end;
2666 WEAPON_BFG:
2667 begin
2668 g_Frames_Get(dw, 'FRAMES_WEAPON_BFG');
2669 Shots[i].Animation := TAnimation.Create(dw, True, 6);
2670 end;
2671 WEAPON_IMP_FIRE:
2672 begin
2673 g_Frames_Get(dw, 'FRAMES_WEAPON_IMPFIRE');
2674 Shots[i].Animation := TAnimation.Create(dw, True, 4);
2675 end;
2676 WEAPON_BSP_FIRE:
2677 begin
2678 g_Frames_Get(dw, 'FRAMES_WEAPON_BSPFIRE');
2679 Shots[i].Animation := TAnimation.Create(dw, True, 4);
2680 end;
2681 WEAPON_CACO_FIRE:
2682 begin
2683 g_Frames_Get(dw, 'FRAMES_WEAPON_CACOFIRE');
2684 Shots[i].Animation := TAnimation.Create(dw, True, 4);
2685 end;
2686 WEAPON_BARON_FIRE:
2687 begin
2688 g_Frames_Get(dw, 'FRAMES_WEAPON_BARONFIRE');
2689 Shots[i].Animation := TAnimation.Create(dw, True, 4);
2690 end;
2691 WEAPON_MANCUB_FIRE:
2692 begin
2693 g_Frames_Get(dw, 'FRAMES_WEAPON_MANCUBFIRE');
2694 Shots[i].Animation := TAnimation.Create(dw, True, 4);
2695 end;
2696 end;
2697 end;
2698 end;
2700 procedure g_Weapon_DestroyShot(I: Integer; X, Y: Integer; Loud: Boolean = True);
2701 var
2702 cx, cy: Integer;
2703 Anim: TAnimation;
2704 s: string;
2705 begin
2706 if Shots = nil then
2707 Exit;
2708 if (I > High(Shots)) or (I < 0) then Exit;
2710 with Shots[I] do
2711 begin
2712 if ShotType = 0 then Exit;
2713 Obj.X := X;
2714 Obj.Y := Y;
2715 cx := Obj.X + (Obj.Rect.Width div 2);
2716 cy := Obj.Y + (Obj.Rect.Height div 2);
2718 case ShotType of
2719 WEAPON_ROCKETLAUNCHER, WEAPON_SKEL_FIRE: // Ðàêåòû è ñíàðÿäû Ñêåëåòà
2720 begin
2721 if Loud then
2722 begin
2723 if ShotType = WEAPON_SKEL_FIRE then
2724 begin // Âçðûâ ñíàðÿäà Ñêåëåòà
2725 if g_Frames_Get(TextureID, 'FRAMES_EXPLODE_SKELFIRE') then
2726 begin
2727 Anim := TAnimation.Create(TextureID, False, 8);
2728 Anim.Blending := False;
2729 g_GFX_OnceAnim((Obj.X+32)-32, (Obj.Y+8)-32, Anim);
2730 Anim.Free();
2731 end;
2732 end
2733 else
2734 begin // Âçðûâ Ðàêåòû
2735 if g_Frames_Get(TextureID, 'FRAMES_EXPLODE_ROCKET') then
2736 begin
2737 Anim := TAnimation.Create(TextureID, False, 6);
2738 Anim.Blending := False;
2739 g_GFX_OnceAnim(cx-64, cy-64, Anim);
2740 Anim.Free();
2741 end;
2742 end;
2743 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEROCKET', Obj.X, Obj.Y);
2744 end;
2745 end;
2747 WEAPON_PLASMA, WEAPON_BSP_FIRE: // Ïëàçìà, ïëàçìà Àðàõíàòðîíà
2748 begin
2749 if ShotType = WEAPON_PLASMA then
2750 s := 'FRAMES_EXPLODE_PLASMA'
2751 else
2752 s := 'FRAMES_EXPLODE_BSPFIRE';
2754 if g_Frames_Get(TextureID, s) and loud then
2755 begin
2756 Anim := TAnimation.Create(TextureID, False, 3);
2757 Anim.Blending := False;
2758 g_GFX_OnceAnim(cx-16, cy-16, Anim);
2759 Anim.Free();
2761 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEPLASMA', Obj.X, Obj.Y);
2762 end;
2763 end;
2765 WEAPON_BFG: // BFG
2766 begin
2767 // Âçðûâ BFG:
2768 if g_Frames_Get(TextureID, 'FRAMES_EXPLODE_BFG') and Loud then
2769 begin
2770 Anim := TAnimation.Create(TextureID, False, 6);
2771 Anim.Blending := False;
2772 g_GFX_OnceAnim(cx-64, cy-64, Anim);
2773 Anim.Free();
2775 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBFG', Obj.X, Obj.Y);
2776 end;
2777 end;
2779 WEAPON_IMP_FIRE, WEAPON_CACO_FIRE, WEAPON_BARON_FIRE: // Âûñòðåëû Áåñà, Êàêîäåìîíà Ðûöàðÿ/Áàðîíà àäà
2780 begin
2781 if ShotType = WEAPON_IMP_FIRE then
2782 s := 'FRAMES_EXPLODE_IMPFIRE'
2783 else
2784 if ShotType = WEAPON_CACO_FIRE then
2785 s := 'FRAMES_EXPLODE_CACOFIRE'
2786 else
2787 s := 'FRAMES_EXPLODE_BARONFIRE';
2789 if g_Frames_Get(TextureID, s) and Loud then
2790 begin
2791 Anim := TAnimation.Create(TextureID, False, 6);
2792 Anim.Blending := False;
2793 g_GFX_OnceAnim(cx-32, cy-32, Anim);
2794 Anim.Free();
2796 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBALL', Obj.X, Obj.Y);
2797 end;
2798 end;
2800 WEAPON_MANCUB_FIRE: // Âûñòðåë Ìàíêóáóñà
2801 begin
2802 if g_Frames_Get(TextureID, 'FRAMES_EXPLODE_ROCKET') and Loud then
2803 begin
2804 Anim := TAnimation.Create(TextureID, False, 6);
2805 Anim.Blending := False;
2806 g_GFX_OnceAnim(cx-64, cy-64, Anim);
2807 Anim.Free();
2809 g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBALL', Obj.X, Obj.Y);
2810 end;
2811 end;
2812 end; // case ShotType of...
2814 ShotType := 0;
2815 Animation.Free();
2816 end;
2817 end;
2820 procedure g_Weapon_AddDynLights();
2821 var
2822 i: Integer;
2823 begin
2824 if Shots = nil then Exit;
2825 for i := 0 to High(Shots) do
2826 begin
2827 if Shots[i].ShotType = 0 then continue;
2828 if (Shots[i].ShotType = WEAPON_ROCKETLAUNCHER) or
2829 (Shots[i].ShotType = WEAPON_BARON_FIRE) or
2830 (Shots[i].ShotType = WEAPON_MANCUB_FIRE) or
2831 (Shots[i].ShotType = WEAPON_SKEL_FIRE) or
2832 (Shots[i].ShotType = WEAPON_IMP_FIRE) or
2833 (Shots[i].ShotType = WEAPON_CACO_FIRE) or
2834 (Shots[i].ShotType = WEAPON_MANCUB_FIRE) or
2835 (Shots[i].ShotType = WEAPON_BSP_FIRE) or
2836 (Shots[i].ShotType = WEAPON_PLASMA) or
2837 (Shots[i].ShotType = WEAPON_BFG) or
2838 (Shots[i].ShotType = WEAPON_FLAMETHROWER) or
2839 false then
2840 begin
2841 if (Shots[i].ShotType = WEAPON_PLASMA) then
2842 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)
2843 else if (Shots[i].ShotType = WEAPON_BFG) then
2844 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)
2845 else if (Shots[i].ShotType = WEAPON_FLAMETHROWER) then
2846 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)
2847 else
2848 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);
2849 end;
2850 end;
2851 end;
2854 procedure TShot.positionChanged (); begin end;
2857 end.