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 checkAirStreams (): Boolean; // `true`: affected by air stream
126 function alive (): Boolean; inline;
127 procedure die (); inline;
128 procedure think (); inline;
134 Animation
: TAnimation
;
139 Particles
: array of TParticle
= nil;
140 OnceAnims
: array of TOnceAnim
= nil;
141 MaxParticles
: Integer = 0;
142 CurrentParticle
: Integer = 0;
143 // awakeMap has one bit for each map grid cell; on g_Mark,
144 // corresponding bits will be set, and in `think()` all particles
145 // in marked cells will be awaken
146 awakeMap
: packed array of LongWord = nil;
147 awakeMapH
: Integer = -1;
148 awakeMapW
: Integer = -1;
149 awakeMinX
, awakeMinY
: Integer;
150 awakeDirty
: Boolean = false;
153 // ////////////////////////////////////////////////////////////////////////// //
154 // HACK! using mapgrid
155 procedure awmClear (); inline;
157 if awakeDirty
and (awakeMapW
> 0) then
159 FillDWord(awakeMap
[0], Length(awakeMap
), 0);
165 procedure awmSetup ();
167 assert(mapGrid
<> nil);
168 awakeMapW
:= (mapGrid
.gridWidth
+mapGrid
.tileSize
-1) div mapGrid
.tileSize
;
169 awakeMapW
:= (awakeMapW
+31) div 32; // LongWord has 32 bits ;-)
170 awakeMapH
:= (mapGrid
.gridHeight
+mapGrid
.tileSize
-1) div mapGrid
.tileSize
;
171 awakeMinX
:= mapGrid
.gridX0
;
172 awakeMinY
:= mapGrid
.gridY0
;
173 SetLength(awakeMap
, awakeMapW
*awakeMapH
);
174 {$IF DEFINED(D2F_DEBUG)}
175 e_LogWritefln('particle awake map: %sx%s (for grid of size %sx%s)', [awakeMapW
, awakeMapH
, mapGrid
.gridWidth
, mapGrid
.gridHeight
]);
182 function awmIsSet (x
, y
: Integer): Boolean; inline;
184 x
:= (x
-awakeMinX
) div mapGrid
.tileSize
;
185 y
:= (y
-awakeMinY
) div mapGrid
.tileSize
;
186 if (x
>= 0) and (y
>= 0) and (x
div 32 < awakeMapW
) and (y
< awakeMapH
) then
188 {$IF DEFINED(D2F_DEBUG)}
189 assert(y
*awakeMapW
+x
div 32 < Length(awakeMap
));
191 result
:= ((awakeMap
[y
*awakeMapW
+x
div 32] and (LongWord(1) shl (x
mod 32))) <> 0);
200 procedure awmSet (x
, y
: Integer); inline;
204 x
:= (x
-awakeMinX
) div mapGrid
.tileSize
;
205 y
:= (y
-awakeMinY
) div mapGrid
.tileSize
;
206 if (x
>= 0) and (y
>= 0) and (x
div 32 < awakeMapW
) and (y
< awakeMapH
) then
208 {$IF DEFINED(D2F_DEBUG)}
209 assert(y
*awakeMapW
+x
div 32 < Length(awakeMap
));
211 v
:= @awakeMap
[y
*awakeMapW
+x
div 32];
212 v
^ := v
^ or (LongWord(1) shl (x
mod 32));
218 // ////////////////////////////////////////////////////////////////////////// //
219 function TParticle
.alive (): Boolean; inline; begin result
:= (state
<> TPartState
.Free
); end;
220 procedure TParticle
.die (); inline; begin state
:= TPartState
.Free
; end;
222 // remove velocities and acceleration
223 procedure TParticle
.freeze (); inline;
225 // stop right there, you criminal scum!
233 // `true`: affected by air stream
234 function TParticle
.checkAirStreams (): Boolean;
238 pan
:= g_Map_PanelAtPoint(x
, y
, GridTagLift
);
239 result
:= (pan
<> nil);
242 if ((pan
.PanelType
and PANEL_LIFTUP
) <> 0) then
244 if (velY
> -4-Random(3)) then velY
-= 0.8;
245 if (abs(velX
) > 0.1) then velX
-= velX
/10.0;
246 velX
+= (Random
-Random
)*0.2;
249 else if ((pan
.PanelType
and PANEL_LIFTLEFT
) <> 0) then
251 if (velX
> -8-Random(3)) then velX
-= 0.8;
254 else if ((pan
.PanelType
and PANEL_LIFTRIGHT
) <> 0) then
256 if (velX
< 8+Random(3)) then velX
+= 0.8;
264 if result
and (state
= TPartState
.Sleeping
) then state
:= TPartState
.Normal
;
269 // switch to sleep mode
270 procedure TParticle
.sleep (); inline;
272 if not checkAirStreams() then
274 state
:= TPartState
.Sleeping
;
280 procedure TParticle
.findFloor (force
: Boolean=false);
285 if (not force
) and (floorY
<> Unknown
) then exit
;
286 // stuck in the wall? rescan, 'cause it can be mplat
287 if (env
= TEnvType
.EWall
) then
289 pan
:= g_Map_PanelAtPoint(x
, y
, (GridTagObstacle
or GridTagLiquid
));
292 // either in a wall, or in a liquid
293 if ((pan
.tag
and GridTagObstacle
) <> 0) then
295 // we are in the wall, wtf?!
297 env
:= TEnvType
.EWall
;
298 floorType
:= TFloorType
.Wall
;
299 state
:= TPartState
.Sleeping
; // anyway
302 // we are in liquid, trace to liquid end
303 env
:= TEnvType
.ELiquid
;
306 // are we in a liquid?
307 if (env
= TEnvType
.ELiquid
) then
309 // trace out of the liquid
310 //env := TEnvType.ELiquid;
311 floorType
:= TFloorType
.LiquidOut
;
312 //e_LogWritefln('tracing out of a liquid; floorY=%s; y=%s', [floorY, y]);
313 mapGrid
.traceOrthoRayWhileIn(ex
, floorY
, x
, y
, x
, g_Map_MaxY
, GridTagLiquid
);
314 floorY
+= 1; // so `floorY` is just out of a liquid
315 //e_LogWritefln(' traced out of a liquid; floorY=%s; y=%s', [floorY, y]);
320 assert(env
= TEnvType
.EAir
);
321 //env := TEnvType.EAir;
322 pan
:= g_Map_traceToNearest(x
, y
, x
, g_Map_MaxY
, (GridTagObstacle
or GridTagLiquid
), @ex
, @floorY
);
326 if ((pan
.tag
and GridTagObstacle
) <> 0) then
329 floorType
:= TFloorType
.Wall
;
334 floorType
:= TFloorType
.LiquidIn
; // entering liquid
335 floorY
+= 1; // so `floorY` is just in a liquid
340 // out of the level; assume wall, but it doesn't really matter
341 floorType
:= TFloorType
.Wall
;
342 floorY
:= g_Map_MaxY
+2;
348 procedure TParticle
.findCeiling (force
: Boolean=false);
352 if (not force
) and (ceilingY
<> Unknown
) then exit
;
353 if (nil = g_Map_traceToNearest(x
, y
, x
, g_Map_MinY
, GridTagObstacle
, @ex
, @ceilingY
)) then
355 ceilingY
:= g_Map_MinY
-2;
360 procedure TParticle
.think (); inline;
362 // awake sleeping particle, if necessary
366 TPartState
.Sleeping
, TPartState
.Stuck
:
367 if awmIsSet(x
, y
) then
369 state
:= TPartState
.Normal
;
372 if (velY
= 0) then velY
:= 0.1;
373 if (accelY
= 0) then accelY
:= 0.5;
378 TPartType
.Blood
, TPartType
.Water
: thinkerBloodAndWater();
379 TPartType
.Spark
: thinkerSpark();
380 TPartType
.Bubbles
: thinkerBubble();
385 // ////////////////////////////////////////////////////////////////////////// //
386 procedure TParticle
.thinkerBloodAndWater ();
387 procedure stickToCeiling ();
389 state
:= TPartState
.Stuck
;
392 ceilingY
:= y
; // yep
395 procedure stickToWall (dx
: Integer);
399 state
:= TPartState
.Stuck
;
400 if (dX
> 0) then stickDX
:= 1 else stickDX
:= -1;
402 // find next floor transition
405 mapGrid
.traceOrthoRayWhileIn(ex
, wallEndY
, x
+stickDX
, y
, x
+stickDX
, floorY
+1, (GridTagWall
or GridTagDoor
or GridTagStep
));
408 procedure hitAFloor ();
410 state
:= TPartState
.Sleeping
; // we aren't moving anymore
413 floorType
:= TFloorType
.Wall
; // yep
416 // `true`: didn't, get outa thinker
417 function drip (): Boolean;
420 TPartType
.Blood
: result
:= (Random(200) = 100);
421 TPartType
.Water
: result
:= (Random(30) = 15);
422 else raise Exception
.Create('internal error in particle engine: drip');
424 if result
then begin velY
:= 0.5; accelY
:= 0.15; end;
427 // switch to freefall mode
428 procedure freefall ();
430 state
:= TPartState
.Normal
;
435 procedure applyGravity (inLiquid
: Boolean);
437 state
:= TPartState
.Normal
;
458 if not gpart_dbg_phys_enabled
then goto _done
;
462 // still check for air streams when sleeping (no)
463 if (state
= TPartState
.Sleeping
) then begin {checkAirStreams();} goto _done
; end; // so blood will dissolve
465 // process stuck particles
466 if (state
= TPartState
.Stuck
) then
468 // stuck to a ceiling?
469 if (stickDX
= 0) then
471 // yeah, stuck to a ceiling
472 assert(ceilingY
<> Unknown
);
473 // dropped from a ceiling?
474 if (y
> ceilingY
) then
479 state
:= TPartState
.Normal
;
483 // otherwise, try to drip
484 if drip() then goto _done
;
490 assert(wallEndY
<> Unknown
);
492 if (wallEndY
<= floorY
) and (y
>= floorY
) then
496 TFloorType
.Wall
: // hit the ground
499 goto _done
; // nothing to do anymore
501 TFloorType
.LiquidIn
: // entering the liquid
503 // rescan, so we'll know when we'll exit the liquid
504 findFloor(true); // force rescan
506 TFloorType
.LiquidOut
: // exiting the liquid
508 // rescan, so we'll know when we'll enter something interesting
509 findFloor(true); // force rescan
510 if (floorType
= TFloorType
.Wall
) and (floorY
= y
) then begin sleep(); goto _done
; end;
515 if (floorY
<= wallEndY
) and (y
>= wallEndY
) then
517 // just unstuck from the wall, switch to freefall mode
523 // otherwise, try to drip
524 if drip() then goto _done
;
527 // nope, process as usual
530 // it is important to have it here
534 if (state
= TPartState
.Normal
) then checkAirStreams();
536 // gravity, if not stuck
537 if (state
<> TPartState
.Stuck
) and (abs(velX
) < 0.1) and (abs(velY
) < 0.1) then
539 if (floorY
= Unknown
) then findFloor();
544 TFloorType
.Wall
: // hit the ground
548 TFloorType
.LiquidIn
: // entering the liquid
550 // rescan, so we'll know when we'll exit the liquid
551 findFloor(true); // force rescan
554 TFloorType
.LiquidOut
: // exiting the liquid
556 // rescan, so we'll know when we'll enter something interesting
557 findFloor(true); // force rescan
558 if (floorType
<> TFloorType
.Wall
) or (floorY
<> y
) then applyGravity(floorType
= TFloorType
.LiquidIn
);
564 // looks like we're in the air
572 // has some horizontal velocity
573 pan
:= g_Map_traceToNearest(x
, y
, x
+dX
, y
+dY
, GridTagObstacle
, @ex
, @ey
);
574 checkEnv
:= (x
<> ex
);
582 // check environment (air/liquid)
583 if (g_Map_PanelAtPoint(x
, y
, GridTagLiquid
) <> nil) then env
:= TEnvType
.ELiquid
else env
:= TEnvType
.EAir
;
588 // the only case when we can have both ceiling and wall is corner; stick to wall in this case
589 // check if we stuck to a wall
590 if (dX
< 0) then dX
:= -1 else dX
:= 1;
591 if (g_Map_PanelAtPoint(x
+dX
, y
, GridTagObstacle
) <> nil) then
598 // stuck to a ceiling
603 else if (dY
<> 0) then
605 // has only vertical velocity
609 if (ceilingY
= Unknown
) then findCeiling(); // need to do this anyway
611 if (y
<= ceilingY
) then begin y
:= ceilingY
; stickToCeiling(); end; // oops, hit a ceiling
612 // environment didn't changed
619 if (floorY
= Unknown
) then findFloor(); // need to do this anyway
620 if (floorType
= TFloorType
.LiquidOut
) then env
:= TEnvType
.ELiquid
else env
:= TEnvType
.EAir
;
622 //e_LogWritefln('floorY=%s; newy=%s; dY=%s; floorType=%s', [floorY, y, dY, floorType]);
623 if (y
>= floorY
) then
628 //e_LogWritefln(' HIT FLOORY: floorY=%s; newy=%s; dY=%s; floorType=%s', [floorY, y, dY, floorType]);
630 TFloorType
.Wall
: // hit the ground
632 // environment didn't changed
634 break
; // done with vertical movement
636 TFloorType
.LiquidIn
: // entering the liquid
638 // we're entered the liquid
639 env
:= TEnvType
.ELiquid
;
640 // rescan, so we'll know when we'll exit the liquid
641 findFloor(true); // force rescan
643 TFloorType
.LiquidOut
: // exiting the liquid
645 // we're exited the liquid
646 env
:= TEnvType
.EAir
;
647 // rescan, so we'll know when we'll enter something interesting
648 findFloor(true); // force rescan
649 if (floorType
= TFloorType
.Wall
) and (floorY
= y
) then
652 break
; // done with vertical movement
659 break
; // done with vertical movement
672 if (g_Map_PanelAtPoint(x
, y
, GridTagObstacle
) <> nil) then begin die(); exit
; end;
676 if (x
< g_Map_MinX
) or (y
< g_Map_MinY
) or (x
> g_Map_MaxX
) or (y
> g_Map_MaxY
) then begin die(); end;
681 // blood will dissolve in other liquids
682 if (particleType
= TPartType
.Blood
) then
684 if (env
= TEnvType
.ELiquid
) then
687 if (liveTime
<= 0) then begin die(); exit
; end;
688 ex
:= 255-trunc(255.0*time
/liveTime
);
689 if (ex
<= 10) then begin die(); exit
; end;
690 if (ex
> 250) then ex
:= 255;
696 // water will disappear in any liquid
697 if (env
= TEnvType
.ELiquid
) then begin die(); exit
; end;
700 if (liveTime
<= 0) then begin die(); exit
; end;
701 ex
:= 255-trunc(255.0*time
/liveTime
);
702 if (ex
<= 10) then begin die(); exit
; end;
703 if (ex
> 250) then ex
:= 255;
709 // ////////////////////////////////////////////////////////////////////////// //
710 procedure g_GFX_SparkVel (fX
, fY
: Integer; count
: Word; vx
, vy
: Integer; devX
, devY
: Byte); forward;
712 procedure g_GFX_Blood (fX
, fY
: Integer; count
: Word; vx
, vy
: Integer;
713 devX
, devY
: Word; cr
, cg
, cb
: Byte; kind
: Byte = BLOOD_NORMAL
);
715 function genColor (cbase
, crnd
: Integer; def
: Byte=0): Byte;
720 if (cbase
< 0) then result
:= 0
721 else if (cbase
> 255) then result
:= 255
722 else result
:= Byte(cbase
);
732 devX1
, devX2
, devY1
, devY2
: Integer;
737 if not gpart_dbg_enabled
then exit
;
739 if (kind
= BLOOD_SPARKS
) then
741 g_GFX_SparkVel(fX
, fY
, 2+Random(2), -vx
div 2, -vy
div 2, devX
, devY
);
745 l
:= Length(Particles
);
746 if (l
= 0) then exit
;
747 if (count
> l
) then count
:= l
;
754 for a
:= 1 to count
do
756 with Particles
[CurrentParticle
] do
758 x
:= fX
-devX1
+Random(devX2
);
759 y
:= fY
-devY1
+Random(devY2
);
761 // check for level bounds
762 if (x
< g_Map_MinX
) or (y
< g_Map_MinY
) or (x
> g_Map_MaxX
) or (y
> g_Map_MaxY
) then continue
;
764 // in what environment we are starting in?
765 pan
:= g_Map_PanelAtPoint(x
, y
, (GridTagObstacle
or GridTagLiquid
));
768 // either in a wall, or in a liquid
769 if ((pan
.tag
and GridTagObstacle
) <> 0) then continue
; // don't spawn in walls
770 env
:= TEnvType
.ELiquid
;
774 env
:= TEnvType
.EAir
;
777 velX
:= vx
+(Random
-Random
)*3;
778 velY
:= vy
+(Random
-Random
)*3;
782 if (velY
-4 < -4) then velY
:= -4 else velY
:= velY
-4;
785 accelX
:= -sign(velX
)*Random
/100;
788 crnd
:= 20*Random(6)-50;
790 red
:= genColor(cr
, CRnd
, 0);
791 green
:= genColor(cg
, CRnd
, 0);
792 blue
:= genColor(cb
, CRnd
, 0);
795 particleType
:= TPartType
.Blood
;
796 state
:= TPartState
.Normal
;
798 liveTime
:= 120+Random(40);
803 if (CurrentParticle
>= MaxParticles
-1) then CurrentParticle
:= 0 else CurrentParticle
+= 1;
808 procedure g_GFX_Water (fX
, fY
: Integer; count
: Word; fVelX
, fVelY
: Single; devX
, devY
, color
: Byte;
809 simple
: Boolean=false; cr
: Byte=0; cg
: Byte=0; cb
: Byte=0);
812 devX1
, devX2
, devY1
, devY2
: Integer;
816 if not gpart_dbg_enabled
then exit
;
818 l
:= Length(Particles
);
819 if (l
= 0) then exit
;
820 if (count
> l
) then count
:= l
;
822 if (abs(fVelX
) < 3.0) then fVelX
:= 3.0-6.0*Random
;
829 if (not simple
) and (color
> 3) then color
:= 0;
831 for a
:= 1 to count
do
833 with Particles
[CurrentParticle
] do
837 x
:= fX
-devX1
+Random(devX2
);
838 y
:= fY
-devY1
+Random(devY2
);
840 if (abs(fVelX
) < 0.5) then velX
:= 1.0-2.0*Random
else velX
:= fVelX
*Random
;
841 if (Random(10) < 7) then velX
:= -velX
;
842 velY
:= fVelY
*Random
;
857 // check for level bounds
858 if (x
< g_Map_MinX
) or (y
< g_Map_MinY
) or (x
> g_Map_MaxX
) or (y
> g_Map_MaxY
) then continue
;
860 // this hack will allow water spawned in water to fly out
861 // it can happen when player fell from a huge height (see "DOOM2D.WAD:\MAP03", for example)
864 // in what environment we are starting in?
865 pan
:= g_Map_PanelAtPoint(x
, y
, (GridTagObstacle
or GridTagLiquid
));
869 pan
:= g_Map_PanelAtPoint(x
, y
, GridTagObstacle
);
871 if (pan
<> nil) then continue
;
872 env
:= TEnvType
.EAir
;
878 red
:= 155+Random(9)*10;
879 green
:= trunc(150*Random
);
884 red
:= trunc(150*Random
);
885 green
:= 175+Random(9)*10;
890 red
:= trunc(200*Random
);
892 blue
:= 175+Random(9)*10;
894 4: // Ñâîé öâåò, ñâåòëåå
896 red
:= 20+Random(19)*10;
899 red
:= nmin(red
+cr
, 255);
900 green
:= nmin(green
+cg
, 255);
901 blue
:= nmin(blue
+cb
, 255);
903 5: // Ñâîé öâåò, òåìÃåå
905 red
:= 20+Random(19)*10;
908 red
:= nmax(cr
-red
, 0);
909 green
:= nmax(cg
-green
, 0);
910 blue
:= nmax(cb
-blue
, 0);
914 red
:= 90+random(12)*10;
921 particleType
:= TPartType
.Water
;
922 state
:= TPartState
.Normal
;
924 liveTime
:= 60+Random(60);
929 if (CurrentParticle
>= MaxParticles
-1) then CurrentParticle
:= 0 else CurrentParticle
+= 1;
934 procedure g_GFX_SimpleWater (fX
, fY
: Integer; count
: Word; fVelX
, fVelY
: Single; defColor
, cr
, cg
, cb
: Byte);
936 g_GFX_Water(fX
, fY
, count
, 0, 0, 0, 0, defColor
, true, cr
, cg
, cb
);
940 // ////////////////////////////////////////////////////////////////////////// //
941 procedure TParticle
.thinkerBubble ();
952 if (y
<= ceilingY
) then begin die(); exit
; end;
956 if (y
>= floorY
) then begin die(); exit
; end;
958 if (y
< g_Map_MinY
) or (y
> g_Map_MaxY
) then begin die(); exit
; end;
961 if (velY
> -4) then velY
+= accelY
;
967 {.$DEFINE D2F_DEBUG_BUBBLES}
968 procedure g_GFX_Bubbles (fX
, fY
: Integer; count
: Word; devX
, devY
: Byte);
971 devX1
, devX2
, devY1
, devY2
: Integer;
973 {$IF DEFINED(D2F_DEBUG_BUBBLES)}
978 if not gpart_dbg_enabled
then exit
;
980 l
:= Length(Particles
);
981 if (l
= 0) then exit
;
982 if (count
> l
) then count
:= l
;
989 for a
:= 1 to count
do
991 with Particles
[CurrentParticle
] do
993 x
:= fX
-devX1
+Random(devX2
);
994 y
:= fY
-devY1
+Random(devY2
);
996 // check for level bounds
997 if (x
< g_Map_MinX
) or (y
< g_Map_MinY
) or (x
> g_Map_MaxX
) or (y
> g_Map_MaxY
) then continue
;
1000 // don't spawn bubbles outside of the liquid
1001 if not isLiquidAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIQUID)} then
1005 // trace liquid, so we'll know where it ends; do it in 8px steps for speed
1006 // tracer will return `false` if we started outside of the liquid
1008 {$IF DEFINED(D2F_DEBUG_BUBBLES)}
1009 stt
:= curTimeMicro();
1010 ptr
:= mapGrid
.traceOrthoRayWhileIn(liquidx
, liquidTopY
, x
, y
, x
, 0, GridTagWater
or GridTagAcid1
or GridTagAcid2
);
1011 stt
:= curTimeMicro()-stt
;
1012 e_LogWritefln('traceOrthoRayWhileIn: time=%s (%s); liquidTopY=%s', [Integer(stt
), ptr
, liquidTopY
]);
1014 stt
:= curTimeMicro();
1015 nptr
:= g_Map_TraceLiquidNonPrecise(x
, y
, 0, -8, liquidx
, liquidTopY
);
1016 stt
:= curTimeMicro()-stt
;
1017 e_LogWritefln('g_Map_TraceLiquidNonPrecise: time=%s (%s); liquidTopY=%s', [Integer(stt
), nptr
, liquidTopY
]);
1018 if not nptr
then continue
;
1020 if not g_Map_TraceLiquidNonPrecise(x
, y
, 0, -8, liquidx
, ceilingY
) then continue
;
1021 if not g_Map_TraceLiquidNonPrecise(x
, y
, 0, +8, liquidx
, floorY
) then continue
;
1034 state
:= TPartState
.Normal
;
1035 particleType
:= TPartType
.Bubbles
;
1040 if (CurrentParticle
>= MaxParticles
-1) then CurrentParticle
:= 0 else CurrentParticle
+= 1;
1045 // ////////////////////////////////////////////////////////////////////////// //
1046 procedure TParticle
.thinkerSpark ();
1054 if not gpart_dbg_phys_enabled
then goto _done
;
1060 if (abs(velX
) < 0.1) and (abs(velY
) < 0.1) then
1069 // has some horizontal velocity
1070 pan
:= g_Map_traceToNearest(x
, y
, x
+dX
, y
+dY
, (GridTagObstacle
or GridTagLiquid
), @ex
, @ey
);
1071 if (x
<> ex
) then begin floorY
:= Unknown
; ceilingY
:= Unknown
; end; // dunno yet
1074 if (pan
<> nil) then
1076 if ((pan
.tag
and GridTagLiquid
) <> 0) then begin die(); exit
; end; // die in liquid
1077 // hit the wall; falling down vertically
1082 else if (dY
<> 0) then
1084 // has some vertical velocity
1088 if (ceilingY
= Unknown
) then findCeiling(); // need to do this anyway
1090 if (y
<= ceilingY
) then
1092 // oops, hit a ceiling
1095 accelY
:= abs(accelY
);
1097 // environment didn't changed
1102 if (floorY
= Unknown
) then findFloor(); // need to do this anyway
1104 if (y
>= floorY
) then
1106 // hit something except a floor?
1107 if (floorType
<> TFloorType
.Wall
) then begin die(); exit
; end; // yep: just die
1108 // otherwise, go to sleep
1111 // environment didn't changed
1117 if (x
< g_Map_MinX
) or (y
< g_Map_MinY
) or (x
> g_Map_MaxX
) or (y
> g_Map_MaxY
) then begin die(); end;
1119 if (velX
<> 0.0) then velX
+= accelX
;
1121 if (velY
<> 0.0) then
1123 if (accelY
< 10) then accelY
+= 0.08;
1131 // ////////////////////////////////////////////////////////////////////////// //
1132 procedure g_GFX_SparkVel (fX
, fY
: Integer; count
: Word; vx
, vy
: Integer; devX
, devY
: Byte);
1135 devX1
, devX2
, devY1
, devY2
: Integer;
1139 if not gpart_dbg_enabled
then exit
;
1141 l
:= Length(Particles
);
1142 if (l
= 0) then exit
;
1143 if (count
> l
) then count
:= l
;
1145 devX1
:= devX
div 2;
1147 devY1
:= devY
div 2;
1150 for a
:= 1 to count
do
1152 with Particles
[CurrentParticle
] do
1154 x
:= fX
-devX1
+Random(devX2
);
1155 y
:= fY
-devY1
+Random(devY2
);
1157 // check for level bounds
1158 if (x
< g_Map_MinX
) or (y
< g_Map_MinY
) or (x
> g_Map_MaxX
) or (y
> g_Map_MaxY
) then continue
;
1160 // in what environment we are starting in?
1161 pan
:= g_Map_PanelAtPoint(x
, y
, (GridTagObstacle
or GridTagLiquid
));
1162 if (pan
<> nil) then
1164 // either in a wall, or in a liquid
1165 //if ((pan.tag and GridTagObstacle) <> 0) then continue; // don't spawn in walls
1166 //env := TEnvType.ELiquid;
1171 env
:= TEnvType
.EAir
;
1174 velX
:= vx
+(Random
-Random
)*3;
1175 velY
:= vy
+(Random
-Random
)*3;
1179 if (velY
-4 < -4) then velY
:= -4 else velY
:= velY
-4;
1182 accelX
:= -sign(velX
)*Random
/100;
1186 green
:= 100+Random(155);
1190 particleType
:= TPartType
.Spark
;
1191 state
:= TPartState
.Normal
;
1193 liveTime
:= 30+Random(60);
1195 ceilingY
:= Unknown
;
1198 if (CurrentParticle
>= MaxParticles
-1) then CurrentParticle
:= 0 else CurrentParticle
+= 1;
1203 procedure g_GFX_Spark (fX
, fY
: Integer; count
: Word; angle
: SmallInt; devX
, devY
: Byte);
1207 devX1
, devX2
, devY1
, devY2
: Integer;
1208 baseVelX
, baseVelY
: Single;
1212 if not gpart_dbg_enabled
then exit
;
1214 l
:= Length(Particles
);
1215 if (l
= 0) then exit
;
1216 if (count
> l
) then count
:= l
;
1220 devX1
:= devX
div 2;
1222 devY1
:= devY
div 2;
1225 b
:= DegToRad(angle
);
1227 baseVelY
:= 1.6*sin(b
);
1228 if (abs(baseVelX
) < 0.01) then baseVelX
:= 0.0;
1229 if (abs(baseVelY
) < 0.01) then baseVelY
:= 0.0;
1231 for a
:= 1 to count
do
1233 with Particles
[CurrentParticle
] do
1235 x
:= fX
-devX1
+Random(devX2
);
1236 y
:= fY
-devY1
+Random(devY2
);
1238 // check for level bounds
1239 if (x
< g_Map_MinX
) or (y
< g_Map_MinY
) or (x
> g_Map_MaxX
) or (y
> g_Map_MaxY
) then continue
;
1241 // in what environment we are starting in?
1242 pan
:= g_Map_PanelAtPoint(x
, y
, (GridTagObstacle
or GridTagLiquid
));
1243 if (pan
<> nil) then
1245 // either in a wall, or in a liquid
1246 //if ((pan.tag and GridTagObstacle) <> 0) then continue; // don't spawn in walls
1247 //env := TEnvType.ELiquid;
1252 env
:= TEnvType
.EAir
;
1255 velX
:= baseVelX
*Random
;
1256 velY
:= baseVelY
-Random
;
1261 green
:= 100+Random(155);
1265 particleType
:= TPartType
.Spark
;
1266 state
:= TPartState
.Normal
;
1268 liveTime
:= 30+Random(60);
1270 ceilingY
:= Unknown
;
1273 if (CurrentParticle
>= MaxParticles
-1) then CurrentParticle
:= 0 else CurrentParticle
+= 1;
1278 // ////////////////////////////////////////////////////////////////////////// //
1279 procedure g_GFX_SetMax (count
: Integer);
1283 if count
> 50000 then count
:= 50000;
1284 if (count
< 1) then count
:= 1;
1285 SetLength(Particles
, count
);
1286 for a
:= 0 to High(Particles
) do Particles
[a
].die();
1287 MaxParticles
:= count
;
1288 CurrentParticle
:= 0;
1292 function g_GFX_GetMax (): Integer;
1294 result
:= MaxParticles
;
1298 function FindOnceAnim (): DWORD
;
1302 if OnceAnims
<> nil then
1303 for i
:= 0 to High(OnceAnims
) do
1304 if OnceAnims
[i
].Animation
= nil then
1310 if OnceAnims
= nil then
1312 SetLength(OnceAnims
, 16);
1317 Result
:= High(OnceAnims
) + 1;
1318 SetLength(OnceAnims
, Length(OnceAnims
) + 16);
1323 procedure g_GFX_OnceAnim (x
, y
: Integer; Anim
: TAnimation
; AnimType
: Byte = 0);
1327 if not gpart_dbg_enabled
then exit
;
1329 if (Anim
= nil) then exit
;
1331 find_id
:= FindOnceAnim();
1333 OnceAnims
[find_id
].AnimType
:= AnimType
;
1334 OnceAnims
[find_id
].Animation
:= TAnimation
.Create(Anim
.FramesID
, Anim
.Loop
, Anim
.Speed
);
1335 OnceAnims
[find_id
].Animation
.Blending
:= Anim
.Blending
;
1336 OnceAnims
[find_id
].Animation
.alpha
:= Anim
.alpha
;
1337 OnceAnims
[find_id
].x
:= x
;
1338 OnceAnims
[find_id
].y
:= y
;
1342 // ////////////////////////////////////////////////////////////////////////// //
1346 procedure g_Mark (x
, y
, Width
, Height
: Integer; t
: Byte; st
: Boolean=true);
1348 cx
, ex
, ey
: Integer;
1351 if not gpart_dbg_enabled
then exit
;
1353 if (Width
< 1) or (Height
< 1) then exit
;
1354 // make some border, so we'll hit particles lying around the panel
1356 y
-= 1; Height
+= 2;
1359 ts
:= mapGrid
.tileSize
;
1373 // ////////////////////////////////////////////////////////////////////////// //
1374 procedure g_GFX_Init ();
1376 //g_Game_SetLoadingText(_lc[I_LOAD_COLLIDE_MAP]+' 1/6', 0, False);
1377 //SetLength(gCollideMap, gMapInfo.Height+1);
1378 //for a := 0 to High(gCollideMap) do SetLength(gCollideMap[a], gMapInfo.Width+1);
1381 gpart_dbg_enabled
:= false;
1386 procedure g_GFX_Free ();
1391 SetLength(Particles
, MaxParticles
);
1392 for a
:= 0 to High(Particles
) do Particles
[a
].die();
1393 CurrentParticle
:= 0;
1395 if (OnceAnims
<> nil) then
1397 for a
:= 0 to High(OnceAnims
) do OnceAnims
[a
].Animation
.Free();
1408 // ////////////////////////////////////////////////////////////////////////// //
1409 procedure g_GFX_Update ();
1415 if not gpart_dbg_enabled
then exit
;
1417 if (Particles
<> nil) then
1419 w
:= gMapInfo
.Width
;
1420 h
:= gMapInfo
.Height
;
1422 len
:= High(Particles
);
1424 for a
:= 0 to len
do
1426 if Particles
[a
].alive
then
1428 with Particles
[a
] do
1430 if (time
= liveTime
) then begin die(); continue
; end;
1431 if (x
+1 >= w
) or (y
+1 >= h
) or (x
<= 0) or (y
<= 0) then begin die(); end;
1436 end; // Particles <> nil
1441 if OnceAnims
<> nil then
1443 for a
:= 0 to High(OnceAnims
) do
1444 if OnceAnims
[a
].Animation
<> nil then
1446 case OnceAnims
[a
].AnimType
of
1449 if Random(3) = 0 then
1450 OnceAnims
[a
].x
:= OnceAnims
[a
].x
-1+Random(3);
1451 if Random(2) = 0 then
1452 OnceAnims
[a
].y
:= OnceAnims
[a
].y
-Random(2);
1456 if OnceAnims
[a
].Animation
.Played
then
1458 OnceAnims
[a
].Animation
.Free();
1459 OnceAnims
[a
].Animation
:= nil;
1462 OnceAnims
[a
].Animation
.Update();
1468 procedure g_GFX_Draw ();
1472 if not gpart_dbg_enabled
then exit
;
1474 if (Particles
<> nil) then
1476 glDisable(GL_TEXTURE_2D
);
1480 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
);
1484 len
:= High(Particles
);
1485 for a
:= 0 to len
do
1487 with Particles
[a
] do
1489 if alive
and (x
>= sX
) and (y
>= sY
) and (x
<= sX
+sWidth
) and (sY
<= sY
+sHeight
) then
1491 glColor4ub(red
, green
, blue
, alpha
);
1492 glVertex2f(x
+0.37, y
+0.37);
1499 glDisable(GL_BLEND
);
1502 if (OnceAnims
<> nil) then
1504 len
:= High(OnceAnims
);
1505 for a
:= 0 to len
do
1507 if (OnceAnims
[a
].Animation
<> nil) then
1509 with OnceAnims
[a
] do Animation
.Draw(x
, y
, M_NONE
);