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}
40 MARK_BLOCKED
= MARK_WALL
or MARK_DOOR
;
41 MARK_LIQUID
= MARK_WATER
or MARK_ACID
;
42 MARK_LIFT
= MARK_LIFTDOWN
or MARK_LIFTUP
or MARK_LIFTLEFT
or MARK_LIFTRIGHT
;
45 procedure g_GFX_Init ();
46 procedure g_GFX_Free ();
48 procedure g_GFX_Blood (fX
, fY
: Integer; count
: Word; vx
, vy
: Integer;
49 devX
, devY
: Word; cr
, cg
, cb
: Byte; kind
: Byte=BLOOD_NORMAL
);
50 procedure g_GFX_Spark (fX
, fY
: Integer; count
: Word; angle
: SmallInt; devX
, devY
: Byte);
51 procedure g_GFX_Water (fX
, fY
: Integer; count
: Word; fVelX
, fVelY
: Single; devX
, devY
, color
: Byte;
52 simple
: Boolean=false; cr
: Byte=0; cg
: Byte=0; cb
: Byte=0);
53 procedure g_GFX_SimpleWater (fX
, fY
: Integer; count
: Word; fVelX
, fVelY
: Single; defColor
, cr
, cg
, cb
: Byte);
54 procedure g_GFX_Bubbles (fX
, fY
: Integer; count
: Word; devX
, devY
: Byte);
56 procedure g_GFX_SetMax (count
: Integer);
57 function g_GFX_GetMax (): Integer;
59 procedure g_GFX_OnceAnim (X
, Y
: Integer; Anim
: TAnimation
; AnimType
: Byte = 0);
61 procedure g_Mark (x
, y
, Width
, Height
: Integer; t
: Byte; st
: Boolean=true);
63 procedure g_GFX_Update ();
64 procedure g_GFX_Draw ();
68 gpart_dbg_enabled
: Boolean = true;
69 gpart_dbg_phys_enabled
: Boolean = true;
75 g_map
, g_panel
, g_basic
, Math
, e_graphics
, GL
, GLExt
,
76 g_options
, g_console
, SysUtils
, g_triggers
, MAPDEF
,
77 g_game
, g_language
, g_net
, utils
, xprofiler
;
81 Unknown
= Integer($7fffffff);
85 TPartType
= (Blood
, Spark
, Bubbles
, Water
);
86 TPartState
= (Free
, Normal
, Stuck
, Sleeping
);
87 TFloorType
= (Wall
, LiquidIn
, LiquidOut
);
88 // Wall: floorY is just before floor
89 // LiquidIn: floorY is liquid *start* (i.e. just in a liquid)
90 // LiquidOut: floorY is liquid *end* (i.e. just out of a liquid)
91 TEnvType
= (EAir
, ELiquid
, EWall
); // where particle is now
93 // note: this MUST be record, so we can keep it in
94 // dynamic array and has sequential memory access pattern
95 PParticle
= ^TParticle
;
99 accelX
, accelY
: Single;
101 particleType
: TPartType
;
102 red
, green
, blue
: Byte;
104 time
, liveTime
: Word;
105 stickDX
: Integer; // STATE_STICK: -1,1: stuck to a wall; 0: stuck to ceiling
106 justSticked
: Boolean; // not used
107 floorY
: Integer; // actually, floor-1; `Unknown`: unknown
108 floorType
: TFloorType
;
109 env
: TEnvType
; // where particle is now
110 ceilingY
: Integer; // actually, ceiling+1; `Unknown`: unknown
111 wallEndY
: Integer; // if we stuck to a wall, this is where wall ends
113 //k8: sorry, i have to emulate virtual methods this way, 'cause i haet `Object`
114 procedure thinkerBloodAndWater ();
115 procedure thinkerSpark ();
116 procedure thinkerBubble ();
118 procedure findFloor (force
: Boolean=false); // this updates `floorY` if forced or Unknown
119 procedure findCeiling (force
: Boolean=false); // this updates `ceilingY` if forced or Unknown
121 procedure freeze (); inline; // remove velocities and acceleration
122 procedure sleep (); inline; // switch to sleep mode
124 function alive (): Boolean; inline;
125 procedure die (); inline;
126 procedure think (); inline;
132 Animation
: TAnimation
;
137 Particles
: array of TParticle
= nil;
138 OnceAnims
: array of TOnceAnim
= nil;
139 MaxParticles
: Integer = 0;
140 CurrentParticle
: Integer = 0;
141 // awakeMap has one bit for each map grid cell; on g_Mark,
142 // corresponding bits will be set, and in `think()` all particles
143 // in marked cells will be awaken
144 awakeMap
: packed array of LongWord = nil;
145 awakeMapH
: Integer = -1;
146 awakeMapW
: Integer = -1;
147 awakeMinX
, awakeMinY
: Integer;
148 awakeDirty
: Boolean = false;
151 // ////////////////////////////////////////////////////////////////////////// //
152 // HACK! using mapgrid
153 procedure awmClear (); inline;
155 if awakeDirty
and (awakeMapW
> 0) then
157 FillDWord(awakeMap
[0], Length(awakeMap
), 0);
163 procedure awmSetup ();
165 assert(mapGrid
<> nil);
166 awakeMapW
:= (mapGrid
.gridWidth
+mapGrid
.tileSize
-1) div mapGrid
.tileSize
;
167 awakeMapW
:= (awakeMapW
+31) div 32; // LongWord has 32 bits ;-)
168 awakeMapH
:= (mapGrid
.gridHeight
+mapGrid
.tileSize
-1) div mapGrid
.tileSize
;
169 awakeMinX
:= mapGrid
.gridX0
;
170 awakeMinY
:= mapGrid
.gridY0
;
171 SetLength(awakeMap
, awakeMapW
*awakeMapH
);
172 {$IF DEFINED(D2F_DEBUG)}
173 e_LogWritefln('particle awake map: %sx%s (for grid of size %sx%s)', [awakeMapW
, awakeMapH
, mapGrid
.gridWidth
, mapGrid
.gridHeight
]);
180 function awmIsSet (x
, y
: Integer): Boolean; inline;
182 x
:= (x
-awakeMinX
) div mapGrid
.tileSize
;
183 y
:= (y
-awakeMinY
) div mapGrid
.tileSize
;
184 if (x
>= 0) and (y
>= 0) and (x
div 32 < awakeMapW
) and (y
< awakeMapH
) then
186 {$IF DEFINED(D2F_DEBUG)}
187 assert(y
*awakeMapW
+x
div 32 < Length(awakeMap
));
189 result
:= ((awakeMap
[y
*awakeMapW
+x
div 32] and (LongWord(1) shl (x
mod 32))) <> 0);
198 procedure awmSet (x
, y
: Integer); inline;
202 x
:= (x
-awakeMinX
) div mapGrid
.tileSize
;
203 y
:= (y
-awakeMinY
) div mapGrid
.tileSize
;
204 if (x
>= 0) and (y
>= 0) and (x
div 32 < awakeMapW
) and (y
< awakeMapH
) then
206 {$IF DEFINED(D2F_DEBUG)}
207 assert(y
*awakeMapW
+x
div 32 < Length(awakeMap
));
209 v
:= @awakeMap
[y
*awakeMapW
+x
div 32];
210 v
^ := v
^ or (LongWord(1) shl (x
mod 32));
216 // ////////////////////////////////////////////////////////////////////////// //
217 function TParticle
.alive (): Boolean; inline; begin result
:= (state
<> TPartState
.Free
); end;
218 procedure TParticle
.die (); inline; begin state
:= TPartState
.Free
; end;
220 // remove velocities and acceleration
221 procedure TParticle
.freeze (); inline;
223 // stop right there, you criminal scum!
231 // switch to sleep mode
232 procedure TParticle
.sleep (); inline;
234 state
:= TPartState
.Sleeping
;
239 procedure TParticle
.findFloor (force
: Boolean=false);
244 if (not force
) and (floorY
<> Unknown
) then exit
;
245 // stuck in the wall? rescan, 'cause it can be mplat
246 if (env
= TEnvType
.EWall
) then
248 pan
:= g_Map_PanelAtPoint(x
, y
, (GridTagObstacle
or GridTagLiquid
));
251 // either in a wall, or in a liquid
252 if ((pan
.tag
and GridTagObstacle
) <> 0) then
254 // we are in the wall, wtf?!
256 env
:= TEnvType
.EWall
;
257 floorType
:= TFloorType
.Wall
;
258 state
:= TPartState
.Sleeping
; // anyway
261 // we are in liquid, trace to liquid end
262 env
:= TEnvType
.ELiquid
;
265 // are we in a liquid?
266 if (env
= TEnvType
.ELiquid
) then
268 // trace out of the liquid
269 //env := TEnvType.ELiquid;
270 floorType
:= TFloorType
.LiquidOut
;
271 //e_LogWritefln('tracing out of a liquid; floorY=%s; y=%s', [floorY, y]);
272 mapGrid
.traceOrthoRayWhileIn(ex
, floorY
, x
, y
, x
, g_Map_MaxY
, GridTagLiquid
);
273 floorY
+= 1; // so `floorY` is just out of a liquid
274 //e_LogWritefln(' traced out of a liquid; floorY=%s; y=%s', [floorY, y]);
279 assert(env
= TEnvType
.EAir
);
280 //env := TEnvType.EAir;
281 pan
:= g_Map_traceToNearest(x
, y
, x
, g_Map_MaxY
, (GridTagObstacle
or GridTagLiquid
), @ex
, @floorY
);
285 if ((pan
.tag
and GridTagObstacle
) <> 0) then
288 floorType
:= TFloorType
.Wall
;
293 floorType
:= TFloorType
.LiquidIn
; // entering liquid
294 floorY
+= 1; // so `floorY` is just in a liquid
299 // out of the level; assume wall, but it doesn't really matter
300 floorType
:= TFloorType
.Wall
;
301 floorY
:= g_Map_MaxY
+2;
307 procedure TParticle
.findCeiling (force
: Boolean=false);
311 if (not force
) and (ceilingY
<> Unknown
) then exit
;
312 if (nil = g_Map_traceToNearest(x
, y
, x
, g_Map_MinY
, GridTagObstacle
, @ex
, @ceilingY
)) then
314 ceilingY
:= g_Map_MinY
-2;
319 procedure TParticle
.think (); inline;
321 // awake sleeping particle, if necessary
325 TPartState
.Sleeping
, TPartState
.Stuck
:
326 if awmIsSet(x
, y
) then
328 state
:= TPartState
.Normal
;
331 if (velY
= 0) then velY
:= 0.1;
332 if (accelY
= 0) then accelY
:= 0.5;
337 TPartType
.Blood
, TPartType
.Water
: thinkerBloodAndWater();
338 TPartType
.Spark
: thinkerSpark();
339 TPartType
.Bubbles
: thinkerBubble();
344 // ////////////////////////////////////////////////////////////////////////// //
345 procedure TParticle
.thinkerBloodAndWater ();
346 procedure stickToCeiling ();
348 state
:= TPartState
.Stuck
;
351 ceilingY
:= y
; // yep
354 procedure stickToWall (dx
: Integer);
358 state
:= TPartState
.Stuck
;
359 if (dX
> 0) then stickDX
:= 1 else stickDX
:= -1;
361 // find next floor transition
364 mapGrid
.traceOrthoRayWhileIn(ex
, wallEndY
, x
+stickDX
, y
, x
+stickDX
, floorY
+1, (GridTagWall
or GridTagDoor
or GridTagStep
));
367 procedure hitAFloor ();
369 state
:= TPartState
.Sleeping
; // we aren't moving anymore
372 floorType
:= TFloorType
.Wall
; // yep
375 // `true`: didn't, get outa thinker
376 function drip (): Boolean;
379 TPartType
.Blood
: result
:= (Random(200) = 100);
380 TPartType
.Water
: result
:= (Random(30) = 15);
381 else raise Exception
.Create('internal error in particle engine: drip');
383 if result
then begin velY
:= 0.5; accelY
:= 0.15; end;
386 // `true`: affected by air stream
387 function checkAirStreams (): Boolean;
391 pan
:= g_Map_PanelAtPoint(x
, y
, GridTagLift
);
392 result
:= (pan
<> nil);
395 if ((pan
.PanelType
and PANEL_LIFTUP
) <> 0) then
397 if (velY
> -4-Random(3)) then velY
-= 0.8;
398 if (abs(velX
) > 0.1) then velX
-= velX
/10.0;
399 velX
+= (Random
-Random
)*0.2;
402 else if ((pan
.PanelType
and PANEL_LIFTLEFT
) <> 0) then
404 if (velX
> -8-Random(3)) then velX
-= 0.8;
407 else if ((pan
.PanelType
and PANEL_LIFTRIGHT
) <> 0) then
409 if (velX
< 8+Random(3)) then velX
+= 0.8;
417 if result
and (state
= TPartState
.Sleeping
) then state
:= TPartState
.Normal
;
421 // switch to freefall mode
422 procedure freefall ();
424 state
:= TPartState
.Normal
;
429 procedure applyGravity (inLiquid
: Boolean);
431 state
:= TPartState
.Normal
;
452 if not gpart_dbg_phys_enabled
then goto _done
;
456 // still check for air streams when sleeping
457 if (state
= TPartState
.Sleeping
) then begin checkAirStreams(); goto _done
; end; // so blood will dissolve
459 // process stuck particles
460 if (state
= TPartState
.Stuck
) then
462 // stuck to a ceiling?
463 if (stickDX
= 0) then
465 // yeah, stuck to a ceiling
466 assert(ceilingY
<> Unknown
);
467 // dropped from a ceiling?
468 if (y
> ceilingY
) then
473 state
:= TPartState
.Normal
;
477 // otherwise, try to drip
478 if drip() then goto _done
;
484 assert(wallEndY
<> Unknown
);
486 if (wallEndY
<= floorY
) and (y
>= floorY
) then
490 TFloorType
.Wall
: // hit the ground
493 goto _done
; // nothing to do anymore
495 TFloorType
.LiquidIn
: // entering the liquid
497 // rescan, so we'll know when we'll exit the liquid
498 findFloor(true); // force rescan
500 TFloorType
.LiquidOut
: // exiting the liquid
502 // rescan, so we'll know when we'll enter something interesting
503 findFloor(true); // force rescan
504 if (floorType
= TFloorType
.Wall
) and (floorY
= y
) then begin sleep(); goto _done
; end;
509 if (floorY
<= wallEndY
) and (y
>= wallEndY
) then
511 // just unstuck from the wall, switch to freefall mode
517 // otherwise, try to drip
518 if drip() then goto _done
;
521 // nope, process as usual
524 // it is important to have it here
528 // gravity, if not stuck
529 if (state
<> TPartState
.Stuck
) and (abs(velX
) < 0.1) and (abs(velY
) < 0.1) then
531 if (floorY
= Unknown
) then findFloor();
536 TFloorType
.Wall
: // hit the ground
540 TFloorType
.LiquidIn
: // entering the liquid
542 // rescan, so we'll know when we'll exit the liquid
543 findFloor(true); // force rescan
546 TFloorType
.LiquidOut
: // exiting the liquid
548 // rescan, so we'll know when we'll enter something interesting
549 findFloor(true); // force rescan
550 if (floorType
<> TFloorType
.Wall
) or (floorY
<> y
) then applyGravity(floorType
= TFloorType
.LiquidIn
);
556 // looks like we're in the air
564 // has some horizontal velocity
565 pan
:= g_Map_traceToNearest(x
, y
, x
+dX
, y
+dY
, GridTagObstacle
, @ex
, @ey
);
566 checkEnv
:= (x
<> ex
);
574 // check environment (air/liquid)
575 if (g_Map_PanelAtPoint(x
, y
, GridTagLiquid
) <> nil) then env
:= TEnvType
.ELiquid
else env
:= TEnvType
.EAir
;
580 // the only case when we can have both ceiling and wall is corner; stick to wall in this case
581 // check if we stuck to a wall
582 if (dX
< 0) then dX
:= -1 else dX
:= 1;
583 if (g_Map_PanelAtPoint(x
+dX
, y
, GridTagObstacle
) <> nil) then
590 // stuck to a ceiling
595 else if (dY
<> 0) then
597 // has only vertical velocity
601 if (ceilingY
= Unknown
) then findCeiling(); // need to do this anyway
603 if (y
<= ceilingY
) then begin y
:= ceilingY
; stickToCeiling(); end; // oops, hit a ceiling
604 // environment didn't changed
611 if (floorY
= Unknown
) then findFloor(); // need to do this anyway
612 if (floorType
= TFloorType
.LiquidOut
) then env
:= TEnvType
.ELiquid
else env
:= TEnvType
.EAir
;
614 //e_LogWritefln('floorY=%s; newy=%s; dY=%s; floorType=%s', [floorY, y, dY, floorType]);
615 if (y
>= floorY
) then
620 //e_LogWritefln(' HIT FLOORY: floorY=%s; newy=%s; dY=%s; floorType=%s', [floorY, y, dY, floorType]);
622 TFloorType
.Wall
: // hit the ground
624 // environment didn't changed
626 break
; // done with vertical movement
628 TFloorType
.LiquidIn
: // entering the liquid
630 // we're entered the liquid
631 env
:= TEnvType
.ELiquid
;
632 // rescan, so we'll know when we'll exit the liquid
633 findFloor(true); // force rescan
635 TFloorType
.LiquidOut
: // exiting the liquid
637 // we're exited the liquid
638 env
:= TEnvType
.EAir
;
639 // rescan, so we'll know when we'll enter something interesting
640 findFloor(true); // force rescan
641 if (floorType
= TFloorType
.Wall
) and (floorY
= y
) then
644 break
; // done with vertical movement
651 break
; // done with vertical movement
664 if (g_Map_PanelAtPoint(x
, y
, GridTagObstacle
) <> nil) then begin die(); exit
; end;
668 if (x
< g_Map_MinX
) or (y
< g_Map_MinY
) or (x
> g_Map_MaxX
) or (y
> g_Map_MaxY
) then begin die(); end;
673 // blood will dissolve in other liquids
674 if (particleType
= TPartType
.Blood
) then
676 if (env
= TEnvType
.ELiquid
) then
679 if (liveTime
<= 0) then begin die(); exit
; end;
680 ex
:= 255-trunc(255.0*time
/liveTime
);
681 if (ex
<= 10) then begin die(); exit
; end;
682 if (ex
> 250) then ex
:= 255;
688 // water will disappear in any liquid
689 if (env
= TEnvType
.ELiquid
) then begin die(); exit
; end;
692 if (liveTime
<= 0) then begin die(); exit
; end;
693 ex
:= 255-trunc(255.0*time
/liveTime
);
694 if (ex
<= 10) then begin die(); exit
; end;
695 if (ex
> 250) then ex
:= 255;
701 // ////////////////////////////////////////////////////////////////////////// //
702 procedure g_GFX_SparkVel (fX
, fY
: Integer; count
: Word; vx
, vy
: Integer; devX
, devY
: Byte); forward;
704 procedure g_GFX_Blood (fX
, fY
: Integer; count
: Word; vx
, vy
: Integer;
705 devX
, devY
: Word; cr
, cg
, cb
: Byte; kind
: Byte = BLOOD_NORMAL
);
707 function genColor (cbase
, crnd
: Integer; def
: Byte=0): Byte;
712 if (cbase
< 0) then result
:= 0
713 else if (cbase
> 255) then result
:= 255
714 else result
:= Byte(cbase
);
724 devX1
, devX2
, devY1
, devY2
: Integer;
729 if not gpart_dbg_enabled
then exit
;
731 if (kind
= BLOOD_SPARKS
) then
733 g_GFX_SparkVel(fX
, fY
, 2+Random(2), -vx
div 2, -vy
div 2, devX
, devY
);
737 l
:= Length(Particles
);
738 if (l
= 0) then exit
;
739 if (count
> l
) then count
:= l
;
746 for a
:= 1 to count
do
748 with Particles
[CurrentParticle
] do
750 x
:= fX
-devX1
+Random(devX2
);
751 y
:= fY
-devY1
+Random(devY2
);
753 // check for level bounds
754 if (x
< g_Map_MinX
) or (y
< g_Map_MinY
) or (x
> g_Map_MaxX
) or (y
> g_Map_MaxY
) then continue
;
756 // in what environment we are starting in?
757 pan
:= g_Map_PanelAtPoint(x
, y
, (GridTagObstacle
or GridTagLiquid
));
760 // either in a wall, or in a liquid
761 if ((pan
.tag
and GridTagObstacle
) <> 0) then continue
; // don't spawn in walls
762 env
:= TEnvType
.ELiquid
;
766 env
:= TEnvType
.EAir
;
769 velX
:= vx
+(Random
-Random
)*3;
770 velY
:= vy
+(Random
-Random
)*3;
774 if (velY
-4 < -4) then velY
:= -4 else velY
:= velY
-4;
777 accelX
:= -sign(velX
)*Random
/100;
780 crnd
:= 20*Random(6)-50;
782 red
:= genColor(cr
, CRnd
, 0);
783 green
:= genColor(cg
, CRnd
, 0);
784 blue
:= genColor(cb
, CRnd
, 0);
787 particleType
:= TPartType
.Blood
;
788 state
:= TPartState
.Normal
;
790 liveTime
:= 120+Random(40);
795 if (CurrentParticle
>= MaxParticles
-1) then CurrentParticle
:= 0 else CurrentParticle
+= 1;
800 procedure g_GFX_Water (fX
, fY
: Integer; count
: Word; fVelX
, fVelY
: Single; devX
, devY
, color
: Byte;
801 simple
: Boolean=false; cr
: Byte=0; cg
: Byte=0; cb
: Byte=0);
804 devX1
, devX2
, devY1
, devY2
: Integer;
808 if not gpart_dbg_enabled
then exit
;
810 l
:= Length(Particles
);
811 if (l
= 0) then exit
;
812 if (count
> l
) then count
:= l
;
814 if (abs(fVelX
) < 3.0) then fVelX
:= 3.0-6.0*Random
;
821 if (not simple
) and (color
> 3) then color
:= 0;
823 for a
:= 1 to count
do
825 with Particles
[CurrentParticle
] do
829 x
:= fX
-devX1
+Random(devX2
);
830 y
:= fY
-devY1
+Random(devY2
);
832 if (abs(fVelX
) < 0.5) then velX
:= 1.0-2.0*Random
else velX
:= fVelX
*Random
;
833 if (Random(10) < 7) then velX
:= -velX
;
834 velY
:= fVelY
*Random
;
849 // check for level bounds
850 if (x
< g_Map_MinX
) or (y
< g_Map_MinY
) or (x
> g_Map_MaxX
) or (y
> g_Map_MaxY
) then continue
;
852 // in what environment we are starting in?
853 pan
:= g_Map_PanelAtPoint(x
, y
, (GridTagObstacle
or GridTagLiquid
));
856 // either in a wall, or in a liquid
857 //if ((pan.tag and GridTagObstacle) <> 0) then continue; // don't spawn in walls
858 //env := TEnvType.ELiquid;
863 env
:= TEnvType
.EAir
;
870 red
:= 155+Random(9)*10;
871 green
:= trunc(150*Random
);
876 red
:= trunc(150*Random
);
877 green
:= 175+Random(9)*10;
882 red
:= trunc(200*Random
);
884 blue
:= 175+Random(9)*10;
886 4: // Ñâîé öâåò, ñâåòëåå
888 red
:= 20+Random(19)*10;
891 red
:= nmin(red
+cr
, 255);
892 green
:= nmin(green
+cg
, 255);
893 blue
:= nmin(blue
+cb
, 255);
895 5: // Ñâîé öâåò, òåìÃåå
897 red
:= 20+Random(19)*10;
900 red
:= nmax(cr
-red
, 0);
901 green
:= nmax(cg
-green
, 0);
902 blue
:= nmax(cb
-blue
, 0);
906 red
:= 90+random(12)*10;
913 particleType
:= TPartType
.Water
;
914 state
:= TPartState
.Normal
;
916 liveTime
:= 60+Random(60);
921 if (CurrentParticle
>= MaxParticles
-1) then CurrentParticle
:= 0 else CurrentParticle
+= 1;
926 procedure g_GFX_SimpleWater (fX
, fY
: Integer; count
: Word; fVelX
, fVelY
: Single; defColor
, cr
, cg
, cb
: Byte);
928 g_GFX_Water(fX
, fY
, count
, 0, 0, 0, 0, defColor
, true, cr
, cg
, cb
);
932 // ////////////////////////////////////////////////////////////////////////// //
933 procedure TParticle
.thinkerBubble ();
944 if (y
<= ceilingY
) then begin die(); exit
; end;
948 if (y
>= floorY
) then begin die(); exit
; end;
950 if (y
< g_Map_MinY
) or (y
> g_Map_MaxY
) then begin die(); exit
; end;
953 if (velY
> -4) then velY
+= accelY
;
959 {.$DEFINE D2F_DEBUG_BUBBLES}
960 procedure g_GFX_Bubbles (fX
, fY
: Integer; count
: Word; devX
, devY
: Byte);
963 devX1
, devX2
, devY1
, devY2
: Integer;
965 {$IF DEFINED(D2F_DEBUG_BUBBLES)}
970 if not gpart_dbg_enabled
then exit
;
972 l
:= Length(Particles
);
973 if (l
= 0) then exit
;
974 if (count
> l
) then count
:= l
;
981 for a
:= 1 to count
do
983 with Particles
[CurrentParticle
] do
985 x
:= fX
-devX1
+Random(devX2
);
986 y
:= fY
-devY1
+Random(devY2
);
988 // check for level bounds
989 if (x
< g_Map_MinX
) or (y
< g_Map_MinY
) or (x
> g_Map_MaxX
) or (y
> g_Map_MaxY
) then continue
;
992 // don't spawn bubbles outside of the liquid
993 if not isLiquidAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIQUID)} then
997 // trace liquid, so we'll know where it ends; do it in 8px steps for speed
998 // tracer will return `false` if we started outside of the liquid
1000 {$IF DEFINED(D2F_DEBUG_BUBBLES)}
1001 stt
:= curTimeMicro();
1002 ptr
:= mapGrid
.traceOrthoRayWhileIn(liquidx
, liquidTopY
, x
, y
, x
, 0, GridTagWater
or GridTagAcid1
or GridTagAcid2
);
1003 stt
:= curTimeMicro()-stt
;
1004 e_LogWritefln('traceOrthoRayWhileIn: time=%s (%s); liquidTopY=%s', [Integer(stt
), ptr
, liquidTopY
]);
1006 stt
:= curTimeMicro();
1007 nptr
:= g_Map_TraceLiquidNonPrecise(x
, y
, 0, -8, liquidx
, liquidTopY
);
1008 stt
:= curTimeMicro()-stt
;
1009 e_LogWritefln('g_Map_TraceLiquidNonPrecise: time=%s (%s); liquidTopY=%s', [Integer(stt
), nptr
, liquidTopY
]);
1010 if not nptr
then continue
;
1012 if not g_Map_TraceLiquidNonPrecise(x
, y
, 0, -8, liquidx
, ceilingY
) then continue
;
1013 if not g_Map_TraceLiquidNonPrecise(x
, y
, 0, +8, liquidx
, floorY
) then continue
;
1026 state
:= TPartState
.Normal
;
1027 particleType
:= TPartType
.Bubbles
;
1032 if (CurrentParticle
>= MaxParticles
-1) then CurrentParticle
:= 0 else CurrentParticle
+= 1;
1037 // ////////////////////////////////////////////////////////////////////////// //
1038 procedure TParticle
.thinkerSpark ();
1046 if not gpart_dbg_phys_enabled
then goto _done
;
1052 if (abs(velX
) < 0.1) and (abs(velY
) < 0.1) then
1061 // has some horizontal velocity
1062 pan
:= g_Map_traceToNearest(x
, y
, x
+dX
, y
+dY
, (GridTagObstacle
or GridTagLiquid
), @ex
, @ey
);
1063 if (x
<> ex
) then begin floorY
:= Unknown
; ceilingY
:= Unknown
; end; // dunno yet
1066 if (pan
<> nil) then
1068 if ((pan
.tag
and GridTagLiquid
) <> 0) then begin die(); exit
; end; // die in liquid
1069 // hit the wall; falling down vertically
1074 else if (dY
<> 0) then
1076 // has some vertical velocity
1080 if (ceilingY
= Unknown
) then findCeiling(); // need to do this anyway
1082 if (y
<= ceilingY
) then
1084 // oops, hit a ceiling
1087 accelY
:= abs(accelY
);
1089 // environment didn't changed
1094 if (floorY
= Unknown
) then findFloor(); // need to do this anyway
1096 if (y
>= floorY
) then
1098 // hit something except a floor?
1099 if (floorType
<> TFloorType
.Wall
) then begin die(); exit
; end; // yep: just die
1100 // otherwise, go to sleep
1103 // environment didn't changed
1109 if (x
< g_Map_MinX
) or (y
< g_Map_MinY
) or (x
> g_Map_MaxX
) or (y
> g_Map_MaxY
) then begin die(); end;
1111 if (velX
<> 0.0) then velX
+= accelX
;
1113 if (velY
<> 0.0) then
1115 if (accelY
< 10) then accelY
+= 0.08;
1123 // ////////////////////////////////////////////////////////////////////////// //
1124 procedure g_GFX_SparkVel (fX
, fY
: Integer; count
: Word; vx
, vy
: Integer; devX
, devY
: Byte);
1127 devX1
, devX2
, devY1
, devY2
: Integer;
1131 if not gpart_dbg_enabled
then exit
;
1133 l
:= Length(Particles
);
1134 if (l
= 0) then exit
;
1135 if (count
> l
) then count
:= l
;
1137 devX1
:= devX
div 2;
1139 devY1
:= devY
div 2;
1142 for a
:= 1 to count
do
1144 with Particles
[CurrentParticle
] do
1146 x
:= fX
-devX1
+Random(devX2
);
1147 y
:= fY
-devY1
+Random(devY2
);
1149 // check for level bounds
1150 if (x
< g_Map_MinX
) or (y
< g_Map_MinY
) or (x
> g_Map_MaxX
) or (y
> g_Map_MaxY
) then continue
;
1152 // in what environment we are starting in?
1153 pan
:= g_Map_PanelAtPoint(x
, y
, (GridTagObstacle
or GridTagLiquid
));
1154 if (pan
<> nil) then
1156 // either in a wall, or in a liquid
1157 //if ((pan.tag and GridTagObstacle) <> 0) then continue; // don't spawn in walls
1158 //env := TEnvType.ELiquid;
1163 env
:= TEnvType
.EAir
;
1166 velX
:= vx
+(Random
-Random
)*3;
1167 velY
:= vy
+(Random
-Random
)*3;
1171 if (velY
-4 < -4) then velY
:= -4 else velY
:= velY
-4;
1174 accelX
:= -sign(velX
)*Random
/100;
1178 green
:= 100+Random(155);
1182 particleType
:= TPartType
.Spark
;
1183 state
:= TPartState
.Normal
;
1185 liveTime
:= 30+Random(60);
1187 ceilingY
:= Unknown
;
1190 if (CurrentParticle
>= MaxParticles
-1) then CurrentParticle
:= 0 else CurrentParticle
+= 1;
1195 procedure g_GFX_Spark (fX
, fY
: Integer; count
: Word; angle
: SmallInt; devX
, devY
: Byte);
1199 devX1
, devX2
, devY1
, devY2
: Integer;
1200 baseVelX
, baseVelY
: Single;
1204 if not gpart_dbg_enabled
then exit
;
1206 l
:= Length(Particles
);
1207 if (l
= 0) then exit
;
1208 if (count
> l
) then count
:= l
;
1212 devX1
:= devX
div 2;
1214 devY1
:= devY
div 2;
1217 b
:= DegToRad(angle
);
1219 baseVelY
:= 1.6*sin(b
);
1220 if (abs(baseVelX
) < 0.01) then baseVelX
:= 0.0;
1221 if (abs(baseVelY
) < 0.01) then baseVelY
:= 0.0;
1223 for a
:= 1 to count
do
1225 with Particles
[CurrentParticle
] do
1227 x
:= fX
-devX1
+Random(devX2
);
1228 y
:= fY
-devY1
+Random(devY2
);
1230 // check for level bounds
1231 if (x
< g_Map_MinX
) or (y
< g_Map_MinY
) or (x
> g_Map_MaxX
) or (y
> g_Map_MaxY
) then continue
;
1233 // in what environment we are starting in?
1234 pan
:= g_Map_PanelAtPoint(x
, y
, (GridTagObstacle
or GridTagLiquid
));
1235 if (pan
<> nil) then
1237 // either in a wall, or in a liquid
1238 //if ((pan.tag and GridTagObstacle) <> 0) then continue; // don't spawn in walls
1239 //env := TEnvType.ELiquid;
1244 env
:= TEnvType
.EAir
;
1247 velX
:= baseVelX
*Random
;
1248 velY
:= baseVelY
-Random
;
1253 green
:= 100+Random(155);
1257 particleType
:= TPartType
.Spark
;
1258 state
:= TPartState
.Normal
;
1260 liveTime
:= 30+Random(60);
1262 ceilingY
:= Unknown
;
1265 if (CurrentParticle
>= MaxParticles
-1) then CurrentParticle
:= 0 else CurrentParticle
+= 1;
1270 // ////////////////////////////////////////////////////////////////////////// //
1271 procedure g_GFX_SetMax (count
: Integer);
1275 if count
> 50000 then count
:= 50000;
1276 if (count
< 1) then count
:= 1;
1277 SetLength(Particles
, count
);
1278 for a
:= 0 to High(Particles
) do Particles
[a
].die();
1279 MaxParticles
:= count
;
1280 CurrentParticle
:= 0;
1284 function g_GFX_GetMax (): Integer;
1286 result
:= MaxParticles
;
1290 function FindOnceAnim (): DWORD
;
1294 if OnceAnims
<> nil then
1295 for i
:= 0 to High(OnceAnims
) do
1296 if OnceAnims
[i
].Animation
= nil then
1302 if OnceAnims
= nil then
1304 SetLength(OnceAnims
, 16);
1309 Result
:= High(OnceAnims
) + 1;
1310 SetLength(OnceAnims
, Length(OnceAnims
) + 16);
1315 procedure g_GFX_OnceAnim (x
, y
: Integer; Anim
: TAnimation
; AnimType
: Byte = 0);
1319 if not gpart_dbg_enabled
then exit
;
1321 if (Anim
= nil) then exit
;
1323 find_id
:= FindOnceAnim();
1325 OnceAnims
[find_id
].AnimType
:= AnimType
;
1326 OnceAnims
[find_id
].Animation
:= TAnimation
.Create(Anim
.FramesID
, Anim
.Loop
, Anim
.Speed
);
1327 OnceAnims
[find_id
].Animation
.Blending
:= Anim
.Blending
;
1328 OnceAnims
[find_id
].Animation
.alpha
:= Anim
.alpha
;
1329 OnceAnims
[find_id
].x
:= x
;
1330 OnceAnims
[find_id
].y
:= y
;
1334 // ////////////////////////////////////////////////////////////////////////// //
1338 procedure g_Mark (x
, y
, Width
, Height
: Integer; t
: Byte; st
: Boolean=true);
1340 cx
, ex
, ey
: Integer;
1343 if not gpart_dbg_enabled
then exit
;
1345 if (Width
< 1) or (Height
< 1) then exit
;
1346 // make some border, so we'll hit particles lying around the panel
1348 y
-= 1; Height
+= 2;
1351 ts
:= mapGrid
.tileSize
;
1365 // ////////////////////////////////////////////////////////////////////////// //
1366 procedure g_GFX_Init ();
1368 //g_Game_SetLoadingText(_lc[I_LOAD_COLLIDE_MAP]+' 1/6', 0, False);
1369 //SetLength(gCollideMap, gMapInfo.Height+1);
1370 //for a := 0 to High(gCollideMap) do SetLength(gCollideMap[a], gMapInfo.Width+1);
1373 gpart_dbg_enabled
:= false;
1378 procedure g_GFX_Free ();
1383 SetLength(Particles
, MaxParticles
);
1384 for a
:= 0 to High(Particles
) do Particles
[a
].die();
1385 CurrentParticle
:= 0;
1387 if (OnceAnims
<> nil) then
1389 for a
:= 0 to High(OnceAnims
) do OnceAnims
[a
].Animation
.Free();
1400 // ////////////////////////////////////////////////////////////////////////// //
1401 procedure g_GFX_Update ();
1407 if not gpart_dbg_enabled
then exit
;
1409 if (Particles
<> nil) then
1411 w
:= gMapInfo
.Width
;
1412 h
:= gMapInfo
.Height
;
1414 len
:= High(Particles
);
1416 for a
:= 0 to len
do
1418 if Particles
[a
].alive
then
1420 with Particles
[a
] do
1422 if (time
= liveTime
) then begin die(); continue
; end;
1423 if (x
+1 >= w
) or (y
+1 >= h
) or (x
<= 0) or (y
<= 0) then begin die(); end;
1428 end; // Particles <> nil
1433 if OnceAnims
<> nil then
1435 for a
:= 0 to High(OnceAnims
) do
1436 if OnceAnims
[a
].Animation
<> nil then
1438 case OnceAnims
[a
].AnimType
of
1441 if Random(3) = 0 then
1442 OnceAnims
[a
].x
:= OnceAnims
[a
].x
-1+Random(3);
1443 if Random(2) = 0 then
1444 OnceAnims
[a
].y
:= OnceAnims
[a
].y
-Random(2);
1448 if OnceAnims
[a
].Animation
.Played
then
1450 OnceAnims
[a
].Animation
.Free();
1451 OnceAnims
[a
].Animation
:= nil;
1454 OnceAnims
[a
].Animation
.Update();
1460 procedure g_GFX_Draw ();
1464 if not gpart_dbg_enabled
then exit
;
1466 if (Particles
<> nil) then
1468 glDisable(GL_TEXTURE_2D
);
1472 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
);
1476 len
:= High(Particles
);
1477 for a
:= 0 to len
do
1479 with Particles
[a
] do
1481 if alive
and (x
>= sX
) and (y
>= sY
) and (x
<= sX
+sWidth
) and (sY
<= sY
+sHeight
) then
1483 glColor4ub(red
, green
, blue
, alpha
);
1484 glVertex2f(x
+0.37, y
+0.37);
1491 glDisable(GL_BLEND
);
1494 if (OnceAnims
<> nil) then
1496 len
:= High(OnceAnims
);
1497 for a
:= 0 to len
do
1499 if (OnceAnims
[a
].Animation
<> nil) then
1501 with OnceAnims
[a
] do Animation
.Draw(x
, y
, M_NONE
);