1 (* Copyright (C) DooM 2D:Forever Developers
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, either version 3 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 {$INCLUDE ../shared/a_modes.inc}
17 {$DEFINE D2F_NEW_SPARK_THINKER}
39 MARK_BLOCKED
= MARK_WALL
+ MARK_DOOR
;
40 MARK_LIQUID
= MARK_WATER
+ MARK_ACID
;
41 MARK_LIFT
= MARK_LIFTDOWN
+ MARK_LIFTUP
+ MARK_LIFTLEFT
+ MARK_LIFTRIGHT
;
43 procedure g_GFX_Init();
44 procedure g_GFX_Free();
46 procedure g_GFX_Blood(fX
, fY
: Integer; Count
: Word; vx
, vy
: Integer;
47 DevX
, DevY
: Word; CR
, CG
, CB
: Byte; Kind
: Byte = BLOOD_NORMAL
);
48 procedure g_GFX_Spark(fX
, fY
: Integer; Count
: Word; Angle
: SmallInt; DevX
, DevY
: Byte);
49 procedure g_GFX_Water(fX
, fY
: Integer; Count
: Word; fVelX
, fVelY
: Single; DevX
, DevY
, Color
: Byte);
50 procedure g_GFX_SimpleWater(fX
, fY
: Integer; Count
: Word; fVelX
, fVelY
: Single; DefColor
, CR
, CG
, CB
: Byte);
51 procedure g_GFX_Bubbles(fX
, fY
: Integer; Count
: Word; DevX
, DevY
: Byte);
52 procedure g_GFX_SetMax(Count
: Integer);
53 function g_GFX_GetMax(): Integer;
55 procedure g_GFX_OnceAnim(X
, Y
: Integer; Anim
: TAnimation
; AnimType
: Byte = 0);
57 procedure g_Mark(x
, y
, Width
, Height
: Integer; t
: Byte; st
: Boolean);
59 procedure g_GFX_Update();
60 procedure g_GFX_Draw();
64 gpart_dbg_enabled
: Boolean = true;
65 gpart_dbg_phys_enabled
: Boolean = true;
71 g_map
, g_panel
, g_basic
, Math
, e_graphics
, GL
, GLExt
,
72 g_options
, g_console
, SysUtils
, g_triggers
, MAPDEF
,
73 g_game
, g_language
, g_net
;
76 PParticle
= ^TParticle
;
81 AccelX
, AccelY
: Single;
82 Red
, Green
, Blue
: Byte;
87 offsetX
, offsetY
: ShortInt;
89 liquidTopY
: Integer; // don't float higher than this
91 //k8: sorry, i have to emulate virtual methods this way, 'cause i haet `Object`
93 procedure thinkerBlood ();
94 procedure thinkerSpark ();
95 procedure thinkerBubble ();
96 procedure thinkerWater ();
98 function alive (): Boolean; inline;
99 procedure die (); inline;
100 procedure think (); inline;
106 Animation
: TAnimation
;
112 PARTICLE_BUBBLES
= 2;
119 Particles
: array of TParticle
;
120 OnceAnims
: array of TOnceAnim
;
121 MaxParticles
: Integer;
122 CurrentParticle
: Integer;
125 // ////////////////////////////////////////////////////////////////////////// //
126 function TParticle
.alive (): Boolean; inline; begin result
:= (State
<> STATE_FREE
); end;
127 procedure TParticle
.die (); inline; begin State
:= STATE_FREE
; end;
129 procedure TParticle
.think (); inline;
132 PARTICLE_BLOOD
: thinkerBlood();
133 PARTICLE_SPARK
: thinkerSpark();
134 PARTICLE_BUBBLES
: thinkerBubble();
135 PARTICLE_WATER
: thinkerWater();
140 // ////////////////////////////////////////////////////////////////////////// //
141 function isBlockedAt (x
, y
: Integer): Boolean; inline;
143 if not gpart_dbg_phys_enabled
then begin result
:= false; exit
; end;
144 result
:= g_Map_HasAnyPanelAtPoint(x
, y
, (PANEL_WALL
or PANEL_CLOSEDOOR
or PANEL_STEP
));
148 function isWallAt (x
, y
: Integer): Boolean; inline;
150 if not gpart_dbg_phys_enabled
then begin result
:= false; exit
; end;
151 result
:= g_Map_HasAnyPanelAtPoint(x
, y
, (PANEL_WALL
or PANEL_STEP
));
154 function isLiftUpAt (x
, y
: Integer): Boolean; inline;
156 if not gpart_dbg_phys_enabled
then begin result
:= false; exit
; end;
157 result
:= g_Map_HasAnyPanelAtPoint(x
, y
, PANEL_LIFTUP
);
160 function isLiftDownAt (x
, y
: Integer): Boolean; inline;
162 if not gpart_dbg_phys_enabled
then begin result
:= false; exit
; end;
163 result
:= g_Map_HasAnyPanelAtPoint(x
, y
, PANEL_LIFTDOWN
);
166 function isLiftLeftAt (x
, y
: Integer): Boolean; inline;
168 if not gpart_dbg_phys_enabled
then begin result
:= false; exit
; end;
169 result
:= g_Map_HasAnyPanelAtPoint(x
, y
, PANEL_LIFTLEFT
);
172 function isLiftRightAt (x
, y
: Integer): Boolean; inline;
174 if not gpart_dbg_phys_enabled
then begin result
:= false; exit
; end;
175 result
:= g_Map_HasAnyPanelAtPoint(x
, y
, PANEL_LIFTRIGHT
);
178 function isLiquidAt (x
, y
: Integer): Boolean; inline;
180 if not gpart_dbg_phys_enabled
then begin result
:= false; exit
; end;
181 result
:= g_Map_HasAnyPanelAtPoint(x
, y
, (PANEL_WATER
or PANEL_ACID1
or PANEL_ACID2
));
184 function isAnythingAt (x
, y
: Integer): Boolean; inline;
186 if not gpart_dbg_phys_enabled
then begin result
:= false; exit
; end;
187 result
:= g_Map_HasAnyPanelAtPoint(x
, y
, (PANEL_WALL
or PANEL_CLOSEDOOR
or PANEL_OPENDOOR
or PANEL_WATER
or PANEL_ACID1
or PANEL_ACID2
or PANEL_STEP
or PANEL_LIFTUP
or PANEL_LIFTDOWN
or PANEL_LIFTLEFT
or PANEL_LIFTRIGHT
));
191 procedure g_Mark(x
, y
, Width
, Height
: Integer; t
: Byte; st
: Boolean);
192 {$IF not DEFINED(HAS_COLLIDE_BITMAP)}
197 yy
, y2
, xx
, x2
: Integer;
210 Height
:= Height
+ y
;
217 if x
> gMapInfo
.Width
then
219 if y
> gMapInfo
.Height
then
222 y2
:= y
+ Height
- 1;
223 if y2
> gMapInfo
.Height
then
224 y2
:= gMapInfo
.Height
;
227 if x2
> gMapInfo
.Width
then
228 x2
:= gMapInfo
.Width
;
231 begin // Óñòàíîâèòü ïðèçíàê
234 gCollideMap
[yy
][xx
] := gCollideMap
[yy
][xx
] or t
;
237 begin // Óáðàòü ïðèçíàê
241 gCollideMap
[yy
][xx
] := gCollideMap
[yy
][xx
] and t
;
247 {$IF DEFINED(HAS_COLLIDE_BITMAP)}
248 procedure CreateCollideMap();
252 g_Game_SetLoadingText(_lc
[I_LOAD_COLLIDE_MAP
]+' 1/6', 0, False);
253 SetLength(gCollideMap
, gMapInfo
.Height
+1);
254 for a
:= 0 to High(gCollideMap
) do
255 SetLength(gCollideMap
[a
], gMapInfo
.Width
+1);
257 if gWater
<> nil then
259 g_Game_SetLoadingText(_lc
[I_LOAD_COLLIDE_MAP
]+' 2/6', 0, True);
260 for a
:= 0 to High(gWater
) do
262 g_Mark(X
, Y
, Width
, Height
, MARK_WATER
, True);
265 if gAcid1
<> nil then
267 g_Game_SetLoadingText(_lc
[I_LOAD_COLLIDE_MAP
]+' 3/6', 0, True);
268 for a
:= 0 to High(gAcid1
) do
270 g_Mark(X
, Y
, Width
, Height
, MARK_ACID
, True);
273 if gAcid2
<> nil then
275 g_Game_SetLoadingText(_lc
[I_LOAD_COLLIDE_MAP
]+' 4/6', 0, True);
276 for a
:= 0 to High(gAcid2
) do
278 g_Mark(X
, Y
, Width
, Height
, MARK_ACID
, True);
281 if gLifts
<> nil then
283 g_Game_SetLoadingText(_lc
[I_LOAD_COLLIDE_MAP
]+' 5/6', 0, True);
284 for a
:= 0 to High(gLifts
) do
287 g_Mark(X
, Y
, Width
, Height
, MARK_LIFT
, False);
290 g_Mark(X
, Y
, Width
, Height
, MARK_LIFTUP
, True)
291 else if LiftType
= 1 then
292 g_Mark(X
, Y
, Width
, Height
, MARK_LIFTDOWN
, True)
293 else if LiftType
= 2 then
294 g_Mark(X
, Y
, Width
, Height
, MARK_LIFTLEFT
, True)
295 else if LiftType
= 3 then
296 g_Mark(X
, Y
, Width
, Height
, MARK_LIFTRIGHT
, True)
300 if gWalls
<> nil then
302 g_Game_SetLoadingText(_lc
[I_LOAD_COLLIDE_MAP
]+' 6/6', 0, True);
303 for a
:= 0 to High(gWalls
) do
305 if gWalls
[a
].Door
then
308 if gWalls
[a
].Enabled
then
310 g_Mark(X
, Y
, Width
, Height
, MARK_DOOR
, True)
311 else // Îòêðûòàÿ äâåðü:
312 if gWalls
[a
].Enabled
then
314 g_Mark(X
, Y
, Width
, Height
, MARK_DOOR
, False);
318 g_Mark(X
, Y
, Width
, Height
, MARK_WALL
, True);
325 procedure g_GFX_Init();
327 //CreateCollideMap();
331 procedure g_GFX_Free();
336 SetLength(Particles
, MaxParticles
);
337 for a
:= 0 to High(Particles
) do Particles
[a
].die();
338 CurrentParticle
:= 0;
340 if OnceAnims
<> nil then
342 for a
:= 0 to High(OnceAnims
) do
343 OnceAnims
[a
].Animation
.Free();
351 procedure CorrectOffsets(id: Integer); inline;
355 part := @Particles[id];
358 // check for upper wall
359 if isBlockedAt(part.X, part.Y-1) then part.offsetY := 1;
360 // check for left wall
361 if isBlockedAt(part.X-1, part.Y) then part.offsetX := 1;
366 // ////////////////////////////////////////////////////////////////////////// //
367 procedure TParticle
.thinkerBlood ();
375 h
:= gMapInfo
.Height
;
379 if (State
= STATE_STICK
) then
381 if (not ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)) and
382 (not ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)) and
383 (not ByteBool(gCollideMap[Y, X-1] and MARK_BLOCKED)) and
384 (not ByteBool(gCollideMap[Y, X+1] and MARK_BLOCKED))
387 if (not isBlockedAt(X
, Y
-1)) and
388 (not isBlockedAt(X
, Y
+1)) and
389 (not isBlockedAt(X
-1, Y
)) and
390 (not isBlockedAt(X
+1, Y
))
392 begin // Îòëèïëà - êàïàåò
395 State
:= STATE_NORMAL
;
398 if Random(200) = 100 then
399 begin // Ïðèëåïëåíà - íî âîçìîæíî ñòåêàåò
405 if not isBlockedAt(X
, Y
) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)} then
407 if isLiftUpAt(X
, Y
) {ByteBool(gCollideMap[Y, X] and MARK_LIFTUP)} then
409 if VelY
> -4-Random(3) then
411 if Abs(VelX
) > 0.1 then
412 VelX
:= VelX
- VelX
/10.0;
413 VelX
:= VelX
+ (Random
-Random
)*0.2;
416 if isLiftLeftAt(X
, Y
) {ByteBool(gCollideMap[Y, X] and MARK_LIFTLEFT)} then
418 if VelX
> -8-Random(3) then
422 if isLiftRightAt(X
, Y
) {ByteBool(gCollideMap[Y, X] and MARK_LIFTRIGHT)} then
423 begin // Ïîòîê âïðàâî
424 if VelX
< 8+Random(3) then
433 if (Abs(VelX
) < 0.1) and (Abs(VelY
) < 0.1) then
434 if (State
<> STATE_STICK
) and
435 (not isBlockedAt(X
, Y
-1) {ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)}) and
436 (not isBlockedAt(X
, Y
) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)}) and
437 (not isBlockedAt(X
, Y
+1) {ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)}) then
438 begin // Âèñèò â âîçäóõå - êàïàåò
441 State
:= STATE_NORMAL
;
455 if (X
+s
>= w
) or (X
+s
<= 0) then begin die(); break
; end;
457 //c := gCollideMap[Y, X+s];
459 if isBlockedAt(X
+s
, Y
) {ByteBool(c and MARK_BLOCKED)} then
465 State
:= STATE_STICK
;
484 if (Y
+s
>= h
) or (Y
+s
<= 0) then begin die(); break
; end;
486 //c := gCollideMap[Y+s, X];
488 if isBlockedAt(X
, Y
+s
) {ByteBool(c and MARK_BLOCKED)} then
494 if (s
> 0) and (State
<> STATE_STICK
) then
495 State
:= STATE_NORMAL
497 State
:= STATE_STICK
;
510 if (X
+dX
>= w
) or (Y
+dY
>= h
) or
511 (X
+dX
<= 0) or (Y
+dY
<= 0) or
512 isBlockedAt(X
+dX
, Y
+dY
) {ByteBool(gCollideMap[Y+dY, X+dX] and MARK_BLOCKED)} then
513 begin // Ñòåíà/äâåðü/ãðàíèöà
525 VelX
:= VelX
+ AccelX
;
526 VelY
:= VelY
+ AccelY
;
528 // Êðîâü ðàñòâîðÿåòñÿ â æèäêîñòè:
529 if isLiquidAt(X
, Y
) {ByteBool(gCollideMap[Y, X] and MARK_LIQUID)} then
533 Alpha
:= 255 - Trunc((255.0 * Time
) / LiveTime
);
538 // ////////////////////////////////////////////////////////////////////////// //
539 procedure TParticle
.thinkerSpark ();
542 {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
553 {$IF DEFINED(D2F_NEW_SPARK_THINKER)}
554 if (Abs(VelX
) < 0.1) and (Abs(VelY
) < 0.1) then
556 pan
:= g_Map_traceToNearest(X
, Y
-1, X
, Y
+1, (GridTagWall
or GridTagDoor
or GridTagStep
or GridTagAcid1
or GridTagAcid2
or GridTagWater
), @ex
, @ey
);
559 if (Abs(VelX
) < 0.1) and (Abs(VelY
) < 0.1) and
560 (not isBlockedAt(X
, Y
-1) {ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)}) and
561 (not isBlockedAt(X
, Y
) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)}) and
562 (not isBlockedAt(X
, Y
+1) {ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)}) then
563 begin // Âèñèò â âîçäóõå
571 {$IF DEFINED(D2F_NEW_SPARK_THINKER)}
572 pan
:= g_Map_traceToNearest(X
, Y
, X
+dX
, Y
, (GridTagWall
or GridTagDoor
or GridTagStep
or GridTagAcid1
or GridTagAcid2
or GridTagWater
), @ex
, @ey
);
573 //e_WriteLog(Format('spark h-trace: (%d,%d)-(%d,%d); dx=%d; end=(%d,%d); hit=%d', [X, Y, X+dX, Y, dX, ex, ey, Integer(pan <> nil)]), MSG_NOTIFY);
579 if ((pan
.tag
and (GridTagAcid1
or GridTagAcid2
or GridTagWater
)) <> 0) then begin die(); exit
; end;
583 if (X
< 0) or (X
>= gMapInfo
.Width
) then begin die(); exit
; end;
585 if (dX
> 0) then s
:= 1 else s
:= -1;
589 if (X
+s
>= gMapInfo
.Width
) or (X
+s
<= 0) then begin die(); break
; end;
590 //c := gCollideMap[Y, X+s];
591 if isBlockedAt(X
+s
, Y
) {ByteBool(c and MARK_BLOCKED)} then
592 begin // Ñòåíà/äâåðü - ïàäàåò âåðòèêàëüíî
598 if not isAnythingAt(X
+s
, Y
) {c = MARK_FREE} then
611 {$IF DEFINED(D2F_NEW_SPARK_THINKER)}
612 pan
:= g_Map_traceToNearest(X
, Y
, X
, Y
+dY
, (GridTagWall
or GridTagDoor
or GridTagStep
or GridTagAcid1
or GridTagAcid2
or GridTagWater
), @ex
, @ey
);
613 //e_WriteLog(Format('spark y-trace: (%d,%d)-(%d,%d); dy=%d; end=(%d,%d); hit=%d', [X, Y, X, Y+dY, dY, ex, ey, Integer(pan <> nil)]), MSG_NOTIFY);
617 e_WriteLog(Format('spark y-trace: %08x (%d,%d)-(%d,%d); dy=%d; end=(%d,%d); hittag=%04x', [LongWord(@self), X, Y, X, Y+dY, dY, ex, ey, pan.tag]), MSG_NOTIFY);
621 e_WriteLog(Format('spark y-trace: %08x (%d,%d)-(%d,%d); dy=%d; end=(%d,%d); hit=%d', [LongWord(@self), X, Y, X, Y+dY, dY, ex, ey, Integer(pan <> nil)]), MSG_NOTIFY);
630 if ((pan
.tag
and (GridTagAcid1
or GridTagAcid2
or GridTagWater
)) <> 0) then begin die(); exit
; end;
634 AccelY
:= abs(AccelY
);
644 if (Y
< 0) or (Y
>= gMapInfo
.Height
) then begin die(); exit
; end;
646 if (dY
> 0) then s
:= 1 else s
:= -1;
650 if (Y
+s
>= gMapInfo
.Height
) or (Y
+s
<= 0) then begin die(); break
; end;
651 //c := gCollideMap[Y+s, X];
652 if isBlockedAt(X
, Y
+s
) {ByteBool(c and MARK_BLOCKED)} then
653 begin // Ñòåíà/äâåðü - ïàäàåò âåðòèêàëüíî
657 AccelY
:= Abs(AccelY
);
659 else // Èëè íå ïàäàåò
670 if not isAnythingAt(X
, Y
+s
) {c = MARK_FREE} then
681 if (VelX
<> 0.0) then VelX
+= AccelX
;
683 if (VelY
<> 0.0) then
685 if (AccelY
< 10) then AccelY
+= 0.08;
693 // ////////////////////////////////////////////////////////////////////////// //
694 procedure TParticle
.thinkerBubble ();
701 h
:= gMapInfo
.Height
;
712 for b
:= 1 to Abs(dY
) do
714 if (Y
+s
>= h
) or (Y
+s
<= 0) then begin die(); break
; end;
717 if not isLiquidAt(X, Y+s) {ByteBool(gCollideMap[Y+s, X] and MARK_LIQUID)} then
718 begin // Óæå íå æèäêîñòü
723 // we traced liquid before, so don't bother checking
724 if (Y
+s
<= liquidTopY
) then begin die(); break
; end;
731 VelY
:= VelY
+ AccelY
;
737 // ////////////////////////////////////////////////////////////////////////// //
738 procedure TParticle
.thinkerWater ();
746 h
:= gMapInfo
.Height
;
748 if (State
= STATE_STICK
) and (Random(30) = 15) then
749 begin // Ñòåêàåò/îòëèïàåò
752 if (not isBlockedAt(X
-1, Y
) {ByteBool(gCollideMap[Y, X-1] and MARK_BLOCKED)}) and
753 (not isBlockedAt(X
+1, Y
) {ByteBool(gCollideMap[Y, X+1] and MARK_BLOCKED)}) then
754 State
:= STATE_NORMAL
;
758 if not isBlockedAt(X
, Y
) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)} then
760 if isLiftUpAt(X
, Y
) {ByteBool(gCollideMap[Y, X] and MARK_LIFTUP)} then
762 if VelY
> -4-Random(3) then
764 if Abs(VelX
) > 0.1 then
765 VelX
:= VelX
- VelX
/10.0;
766 VelX
:= VelX
+ (Random
-Random
)*0.2;
769 if isLiftLeftAt(X
, Y
) {ByteBool(gCollideMap[Y, X] and MARK_LIFTLEFT)} then
771 if VelX
> -8-Random(3) then
775 if isLiftRightAt(X
, Y
) {ByteBool(gCollideMap[Y, X] and MARK_LIFTRIGHT)} then
776 begin // Ïîòîê âïðàâî
777 if VelX
< 8+Random(3) then
786 if (Abs(VelX
) < 0.1) and (Abs(VelY
) < 0.1) then
787 if (State
<> STATE_STICK
) and
788 (not isBlockedAt(X
, Y
-1) {ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)}) and
789 (not isBlockedAt(X
, Y
) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)}) and
790 (not isBlockedAt(X
, Y
+1) {ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)}) then
791 begin // Âèñèò â âîçäóõå - êàïàåò
794 State
:= STATE_NORMAL
;
804 for b
:= 1 to Abs(dX
) do
807 if (X
+s
>= w
) or (X
+s
<= 0) then begin die(); break
;end;
809 //c := gCollideMap[Y, X+s];
811 // Ñáîêó æèäêîñòü, à ÷àñòèöà óæå ïàäàåò?
812 if isLiquidAt(X
+s
, Y
) {ByteBool(c and MARK_LIQUID)} and (dY
> 0) then begin die(); break
; end;
814 if isBlockedAt(X
+s
, Y
) {ByteBool(c and MARK_BLOCKED)} then
820 State
:= STATE_STICK
;
835 for b
:= 1 to Abs(dY
) do
837 // Ñíèçó/ñâåðõó ãðàíèöà
838 if (Y
+s
>= h
) or (Y
+s
<= 0) then begin die(); break
; end;
840 //c := gCollideMap[Y+s, X];
842 // Ñíèçó æèäêîñòü, à ÷àñòèöà óæå ïàäàåò
843 if isLiquidAt(X
, Y
+s
) {ByteBool(c and MARK_LIQUID)} and (dY
> 0) then begin die(); break
; end;
845 if isBlockedAt(X
, Y
+s
) {ByteBool(c and MARK_BLOCKED)} then
851 if (s
> 0) and (State
<> STATE_STICK
) then
852 State
:= STATE_NORMAL
854 State
:= STATE_STICK
;
862 VelX
:= VelX
+ AccelX
;
863 VelY
:= VelY
+ AccelY
;
869 // ////////////////////////////////////////////////////////////////////////// //
870 procedure g_GFX_SparkVel (fX
, fY
: Integer; Count
: Word; VX
, VY
: Integer; DevX
, DevY
: Byte);
877 l
:= Length(Particles
);
879 if Count
> l
then Count
:= l
;
886 for a
:= 1 to Count
do
888 with Particles
[CurrentParticle
] do
890 X
:= fX
-DevX1
+Random(DevX2
);
891 Y
:= fY
-DevY1
+Random(DevY2
);
893 VelX
:= VX
+ (Random
-Random
)*3;
894 VelY
:= VY
+ (Random
-Random
)*3;
902 AccelX
:= -Sign(VelX
)*Random
/100;
906 Green
:= 100+Random(155);
910 State
:= STATE_NORMAL
;
912 LiveTime
:= 30+Random(60);
913 ParticleType
:= PARTICLE_SPARK
;
915 {CorrectOffsets(CurrentParticle);}
918 if CurrentParticle
+2 > MaxParticles
then
921 CurrentParticle
:= CurrentParticle
+1;
926 procedure g_GFX_Blood(fX
, fY
: Integer; Count
: Word; vx
, vy
: Integer;
927 DevX
, DevY
: Word; CR
, CG
, CB
: Byte; Kind
: Byte = BLOOD_NORMAL
);
936 if Kind
= BLOOD_SPARKS
then
938 g_GFX_SparkVel(fX
, fY
, 2 + Random(2), -VX
div 2, -VY
div 2, DevX
, DevY
);
941 l
:= Length(Particles
);
952 for a
:= 1 to Count
do
954 with Particles
[CurrentParticle
] do
956 X
:= fX
- DevX1
+ Random(DevX2
);
957 Y
:= fY
- DevY1
+ Random(DevY2
);
960 if (X < 0) or (X > gMapInfo.Width-1) or
961 (Y < 0) or (Y > gMapInfo.Height-1) or
962 ByteBool(gCollideMap[Y, X] and MARK_WALL) then
965 if isWallAt(X
, Y
) then continue
;
967 VelX
:= vx
+ (Random
-Random
)*3;
968 VelY
:= vy
+ (Random
-Random
)*3;
976 AccelX
:= -Sign(VelX
)*Random
/100;
979 CRnd
:= 20*Random(6);
982 CC
:= CR
+ CRnd
- 50;
983 if CC
< 0 then CC
:= 0;
984 if CC
> 255 then CC
:= 255;
990 CC
:= CG
+ CRnd
- 50;
991 if CC
< 0 then CC
:= 0;
992 if CC
> 255 then CC
:= 255;
998 CC
:= CB
+ CRnd
- 50;
999 if CC
< 0 then CC
:= 0;
1000 if CC
> 255 then CC
:= 255;
1007 State
:= STATE_NORMAL
;
1009 LiveTime
:= 120+Random(40);
1010 ParticleType
:= PARTICLE_BLOOD
;
1012 {CorrectOffsets(CurrentParticle);}
1015 if CurrentParticle
>= MaxParticles
-1 then
1016 CurrentParticle
:= 0
1018 CurrentParticle
:= CurrentParticle
+1;
1023 procedure g_GFX_Spark(fX
, fY
: Integer; Count
: Word; Angle
: SmallInt; DevX
, DevY
: Byte);
1029 BaseVelX
, BaseVelY
: Single;
1032 l
:= Length(Particles
);
1038 Angle
:= 360 - Angle
;
1040 DevX1
:= DevX
div 2;
1042 DevY1
:= DevY
div 2;
1045 b
:= DegToRad(Angle
);
1047 BaseVelY
:= 1.6*sin(b
);
1048 if Abs(BaseVelX
) < 0.01 then
1050 if Abs(BaseVelY
) < 0.01 then
1052 for a
:= 1 to Count
do
1054 with Particles
[CurrentParticle
] do
1056 X
:= fX
-DevX1
+Random(DevX2
);
1057 Y
:= fY
-DevY1
+Random(DevY2
);
1059 VelX
:= BaseVelX
*Random
;
1060 VelY
:= BaseVelY
-Random
;
1065 Green
:= 100+Random(155);
1069 State
:= STATE_NORMAL
;
1071 LiveTime
:= 30+Random(60);
1072 ParticleType
:= PARTICLE_SPARK
;
1074 {CorrectOffsets(CurrentParticle);}
1077 if CurrentParticle
+2 > MaxParticles
then
1078 CurrentParticle
:= 0
1080 CurrentParticle
:= CurrentParticle
+1;
1084 procedure g_GFX_Water(fX
, fY
: Integer; Count
: Word; fVelX
, fVelY
: Single; DevX
, DevY
, Color
: Byte);
1091 l
:= Length(Particles
);
1097 if Abs(fVelX
) < 3.0 then
1098 fVelX
:= 3.0 - 6.0*Random
;
1100 DevX1
:= DevX
div 2;
1102 DevY1
:= DevY
div 2;
1105 for a
:= 1 to Count
do
1107 with Particles
[CurrentParticle
] do
1109 X
:= fX
-DevX1
+Random(DevX2
);
1110 Y
:= fY
-DevY1
+Random(DevY2
);
1112 if Abs(fVelX
) < 0.5 then
1113 VelX
:= 1.0 - 2.0*Random
1115 VelX
:= fVelX
*Random
;
1116 if Random(10) < 7 then
1118 VelY
:= fVelY
*Random
;
1125 Red
:= 155 + Random(9)*10;
1126 Green
:= Trunc(150*Random
);
1131 Red
:= Trunc(150*Random
);
1132 Green
:= 175 + Random(9)*10;
1137 Red
:= Trunc(200*Random
);
1139 Blue
:= 175 + Random(9)*10;
1143 Red
:= 90 + Random(12)*10;
1151 State
:= STATE_NORMAL
;
1153 LiveTime
:= 60+Random(60);
1154 ParticleType
:= PARTICLE_WATER
;
1156 {CorrectOffsets(CurrentParticle);}
1159 if CurrentParticle
+2 > MaxParticles
then
1160 CurrentParticle
:= 0
1162 CurrentParticle
:= CurrentParticle
+1;
1166 procedure g_GFX_SimpleWater(fX
, fY
: Integer; Count
: Word; fVelX
, fVelY
: Single; DefColor
, CR
, CG
, CB
: Byte);
1171 l
:= Length(Particles
);
1177 for a
:= 1 to Count
do
1179 with Particles
[CurrentParticle
] do
1192 Red
:= 155 + Random(9)*10;
1193 Green
:= Trunc(150*Random
);
1198 Red
:= Trunc(150*Random
);
1199 Green
:= 175 + Random(9)*10;
1204 Red
:= Trunc(200*Random
);
1206 Blue
:= 175 + Random(9)*10;
1208 4: // Ñâîé öâåò, ñâåòëåå
1210 Red
:= 20 + Random(19)*10;
1213 Red
:= Min(Red
+ CR
, 255);
1214 Green
:= Min(Green
+ CG
, 255);
1215 Blue
:= Min(Blue
+ CB
, 255);
1217 5: // Ñâîé öâåò, òåìíåå
1219 Red
:= 20 + Random(19)*10;
1222 Red
:= Max(CR
- Red
, 0);
1223 Green
:= Max(CG
- Green
, 0);
1224 Blue
:= Max(CB
- Blue
, 0);
1228 Red
:= 90 + Random(12)*10;
1236 State
:= STATE_NORMAL
;
1238 LiveTime
:= 60+Random(60);
1239 ParticleType
:= PARTICLE_WATER
;
1241 {CorrectOffsets(CurrentParticle);}
1244 if CurrentParticle
+2 > MaxParticles
then
1245 CurrentParticle
:= 0
1247 CurrentParticle
:= CurrentParticle
+1;
1252 procedure g_GFX_Bubbles(fX
, fY
: Integer; Count
: Word; DevX
, DevY
: Byte);
1257 l
, liquidx
: Integer;
1259 l
:= Length(Particles
);
1265 DevX1
:= DevX
div 2;
1267 DevY1
:= DevY
div 2;
1270 for a
:= 1 to Count
do
1272 with Particles
[CurrentParticle
] do
1274 X
:= fX
-DevX1
+Random(DevX2
);
1275 Y
:= fY
-DevY1
+Random(DevY2
);
1277 if (X
>= gMapInfo
.Width
) or (X
<= 0) or
1278 (Y
>= gMapInfo
.Height
) or (Y
<= 0) then
1282 // don't spawn bubbles outside of the liquid
1283 if not isLiquidAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIQUID)} then
1287 // trace liquid, so we'll know where it ends; do it in 8px steps for speed
1288 // tracer will return `false` if we started outside of the liquid
1289 if not g_Map_TraceLiquid(X
, Y
, 0, -8, liquidx
, liquidTopY
) then continue
;
1301 State
:= STATE_NORMAL
;
1304 ParticleType
:= PARTICLE_BUBBLES
;
1306 {CorrectOffsets(CurrentParticle);}
1309 if CurrentParticle
+2 > MaxParticles
then
1310 CurrentParticle
:= 0
1312 CurrentParticle
:= CurrentParticle
+1;
1316 procedure g_GFX_SetMax(Count
: Integer);
1320 if Count
> 50000 then Count
:= 50000;
1321 if (Count
< 1) then Count
:= 1;
1323 SetLength(Particles
, Count
);
1324 for a
:= 0 to High(Particles
) do Particles
[a
].die();
1325 MaxParticles
:= Count
;
1326 //if CurrentParticle >= Count then
1327 CurrentParticle
:= 0;
1330 function g_GFX_GetMax(): Integer;
1332 Result
:= MaxParticles
;
1335 function FindOnceAnim
: DWORD
;
1339 if OnceAnims
<> nil then
1340 for i
:= 0 to High(OnceAnims
) do
1341 if OnceAnims
[i
].Animation
= nil then
1347 if OnceAnims
= nil then
1349 SetLength(OnceAnims
, 16);
1354 Result
:= High(OnceAnims
) + 1;
1355 SetLength(OnceAnims
, Length(OnceAnims
) + 16);
1359 procedure g_GFX_OnceAnim(X
, Y
: Integer; Anim
: TAnimation
; AnimType
: Byte = 0);
1366 find_id
:= FindOnceAnim();
1368 OnceAnims
[find_id
].AnimType
:= AnimType
;
1369 OnceAnims
[find_id
].Animation
:= TAnimation
.Create(Anim
.FramesID
, Anim
.Loop
, Anim
.Speed
);
1370 OnceAnims
[find_id
].Animation
.Blending
:= Anim
.Blending
;
1371 OnceAnims
[find_id
].Animation
.Alpha
:= Anim
.Alpha
;
1372 OnceAnims
[find_id
].X
:= X
;
1373 OnceAnims
[find_id
].Y
:= Y
;
1376 procedure g_GFX_Update();
1382 if not gpart_dbg_enabled
then exit
;
1383 if Particles
<> nil then
1385 w
:= gMapInfo
.Width
;
1386 h
:= gMapInfo
.Height
;
1388 len
:= High(Particles
);
1390 for a
:= 0 to len
do
1392 if Particles
[a
].alive
then
1394 with Particles
[a
] do
1396 if (Time
= LiveTime
) then begin die(); continue
; end;
1397 if (X
+1 >= w
) or (Y
+1 >= h
) or (X
<= 0) or (Y
<= 0) then begin die(); end;
1398 //if not alive then Continue;
1399 //e_WriteLog(Format('particle #%d: %d', [State, ParticleType]), MSG_NOTIFY);
1401 {CorrectOffsets(a);}
1405 end; // Particles <> nil
1407 if OnceAnims
<> nil then
1409 for a
:= 0 to High(OnceAnims
) do
1410 if OnceAnims
[a
].Animation
<> nil then
1412 case OnceAnims
[a
].AnimType
of
1415 if Random(3) = 0 then
1416 OnceAnims
[a
].X
:= OnceAnims
[a
].X
-1+Random(3);
1417 if Random(2) = 0 then
1418 OnceAnims
[a
].Y
:= OnceAnims
[a
].Y
-Random(2);
1422 if OnceAnims
[a
].Animation
.Played
then
1424 OnceAnims
[a
].Animation
.Free();
1425 OnceAnims
[a
].Animation
:= nil;
1428 OnceAnims
[a
].Animation
.Update();
1433 procedure g_GFX_Draw();
1437 if Particles
<> nil then
1439 glDisable(GL_TEXTURE_2D
);
1443 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
);
1447 len
:= High(Particles
);
1449 for a
:= 0 to len
do
1450 with Particles
[a
] do
1451 if alive
and (X
>= sX
) and (Y
>= sY
) and (X
<= sX
+sWidth
) and (sY
<= sY
+sHeight
) then
1453 glColor4ub(Red
, Green
, Blue
, Alpha
);
1454 glVertex2i(X
+ offsetX
, Y
+ offsetY
);
1459 glDisable(GL_BLEND
);
1462 if OnceAnims
<> nil then
1463 for a
:= 0 to High(OnceAnims
) do
1464 if OnceAnims
[a
].Animation
<> nil then
1465 with OnceAnims
[a
] do
1466 Animation
.Draw(X
, Y
, M_NONE
);