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
;
451 if not gpart_dbg_phys_enabled
then goto _done
;
455 // still check for air streams when sleeping
456 if (state
= TPartState
.Sleeping
) then begin checkAirStreams(); goto _done
; end; // so blood will dissolve
458 // process stuck particles
459 if (state
= TPartState
.Stuck
) then
461 // stuck to a ceiling?
462 if (stickDX
= 0) then
464 // yeah, stuck to a ceiling
465 assert(ceilingY
<> Unknown
);
466 // dropped from a ceiling?
467 if (y
> ceilingY
) then
472 state
:= TPartState
.Normal
;
476 // otherwise, try to drip
477 if drip() then goto _done
;
483 assert(wallEndY
<> Unknown
);
485 if (wallEndY
<= floorY
) and (y
>= floorY
) then
489 TFloorType
.Wall
: // hit the ground
492 goto _done
; // nothing to do anymore
494 TFloorType
.LiquidIn
: // entering the liquid
496 // rescan, so we'll know when we'll exit the liquid
497 findFloor(true); // force rescan
499 TFloorType
.LiquidOut
: // exiting the liquid
501 // rescan, so we'll know when we'll enter something interesting
502 findFloor(true); // force rescan
503 if (floorType
= TFloorType
.Wall
) and (floorY
= y
) then begin sleep(); goto _done
; end;
508 if (floorY
<= wallEndY
) and (y
>= wallEndY
) then
510 // just unstuck from the wall, switch to freefall mode
516 // otherwise, try to drip
517 if drip() then goto _done
;
520 // nope, process as usual
523 // it is important to have it here
527 // gravity, if not stuck
528 if (state
<> TPartState
.Stuck
) and (abs(velX
) < 0.1) and (abs(velY
) < 0.1) then
530 if (floorY
= Unknown
) then findFloor();
535 TFloorType
.Wall
: // hit the ground
539 TFloorType
.LiquidIn
: // entering the liquid
541 // rescan, so we'll know when we'll exit the liquid
542 findFloor(true); // force rescan
545 TFloorType
.LiquidOut
: // exiting the liquid
547 // rescan, so we'll know when we'll enter something interesting
548 findFloor(true); // force rescan
549 if (floorType
<> TFloorType
.Wall
) or (floorY
<> y
) then applyGravity(floorType
= TFloorType
.LiquidIn
);
555 // looks like we're in the air
563 // has some horizontal velocity
564 pan
:= g_Map_traceToNearest(x
, y
, x
+dX
, y
+dY
, GridTagObstacle
, @ex
, @ey
);
565 if (x
<> ex
) then begin floorY
:= Unknown
; ceilingY
:= Unknown
; end; // dunno yet
571 // the only case when we can have both ceiling and wall is corner; stick to wall in this case
572 // check environment (air/liquid)
573 if (g_Map_PanelAtPoint(x
, y
, GridTagLiquid
) <> nil) then env
:= TEnvType
.ELiquid
else env
:= TEnvType
.EAir
;
574 // check if we stuck to a wall
575 if (dX
< 0) then dX
:= -1 else dX
:= 1;
576 if (g_Map_PanelAtPoint(x
+dX
, y
, GridTagObstacle
) <> nil) then
583 // stuck to a ceiling
588 else if (dY
<> 0) then
590 // has only vertical velocity
594 if (ceilingY
= Unknown
) then findCeiling(); // need to do this anyway
596 if (y
<= ceilingY
) then begin y
:= ceilingY
; stickToCeiling(); end; // oops, hit a ceiling
597 // environment didn't changed
604 if (floorY
= Unknown
) then findFloor(); // need to do this anyway
606 //e_LogWritefln('floorY=%s; newy=%s; dY=%s; floorType=%s', [floorY, y, dY, floorType]);
607 if (y
>= floorY
) then
612 //e_LogWritefln(' HIT FLOORY: floorY=%s; newy=%s; dY=%s; floorType=%s', [floorY, y, dY, floorType]);
614 TFloorType
.Wall
: // hit the ground
616 // environment didn't changed
618 break
; // done with vertical movement
620 TFloorType
.LiquidIn
: // entering the liquid
622 // we're entered the liquid
623 env
:= TEnvType
.ELiquid
;
624 // rescan, so we'll know when we'll exit the liquid
625 findFloor(true); // force rescan
627 TFloorType
.LiquidOut
: // exiting the liquid
629 // we're exited the liquid
630 env
:= TEnvType
.EAir
;
631 // rescan, so we'll know when we'll enter something interesting
632 findFloor(true); // force rescan
633 if (floorType
= TFloorType
.Wall
) and (floorY
= y
) then
636 break
; // done with vertical movement
643 break
; // done with vertical movement
656 if (g_Map_PanelAtPoint(x
, y
, GridTagObstacle
) <> nil) then begin die(); exit
; end;
660 if (x
< g_Map_MinX
) or (y
< g_Map_MinY
) or (x
> g_Map_MaxX
) or (y
> g_Map_MaxY
) then begin die(); end;
665 // blood will dissolve in other liquids
666 if (particleType
= TPartType
.Blood
) then
668 if (env
= TEnvType
.ELiquid
) then
671 if (liveTime
<= 0) then begin die(); exit
; end;
672 ex
:= 255-trunc(255.0*time
/liveTime
);
673 if (ex
>= 250) then begin die(); exit
; end;
674 if (ex
< 0) then ex
:= 0;
680 // water will disappear in water (?)
681 if (env
= TEnvType
.ELiquid
) then die();
687 // ////////////////////////////////////////////////////////////////////////// //
688 procedure g_GFX_SparkVel (fX
, fY
: Integer; count
: Word; vx
, vy
: Integer; devX
, devY
: Byte); forward;
690 procedure g_GFX_Blood (fX
, fY
: Integer; count
: Word; vx
, vy
: Integer;
691 devX
, devY
: Word; cr
, cg
, cb
: Byte; kind
: Byte = BLOOD_NORMAL
);
693 function genColor (cbase
, crnd
: Integer; def
: Byte=0): Byte;
698 if (cbase
< 0) then result
:= 0
699 else if (cbase
> 255) then result
:= 255
700 else result
:= Byte(cbase
);
710 devX1
, devX2
, devY1
, devY2
: Integer;
715 if not gpart_dbg_enabled
then exit
;
717 if (kind
= BLOOD_SPARKS
) then
719 g_GFX_SparkVel(fX
, fY
, 2+Random(2), -vx
div 2, -vy
div 2, devX
, devY
);
723 l
:= Length(Particles
);
724 if (l
= 0) then exit
;
725 if (count
> l
) then count
:= l
;
732 for a
:= 1 to count
do
734 with Particles
[CurrentParticle
] do
736 x
:= fX
-devX1
+Random(devX2
);
737 y
:= fY
-devY1
+Random(devY2
);
739 // check for level bounds
740 if (x
< g_Map_MinX
) or (y
< g_Map_MinY
) or (x
> g_Map_MaxX
) or (y
> g_Map_MaxY
) then continue
;
742 // in what environment we are starting in?
743 pan
:= g_Map_PanelAtPoint(x
, y
, (GridTagObstacle
or GridTagLiquid
));
746 // either in a wall, or in a liquid
747 if ((pan
.tag
and GridTagObstacle
) <> 0) then continue
; // don't spawn in walls
748 env
:= TEnvType
.ELiquid
;
752 env
:= TEnvType
.EAir
;
755 velX
:= vx
+(Random
-Random
)*3;
756 velY
:= vy
+(Random
-Random
)*3;
760 if (velY
-4 < -4) then velY
:= -4 else velY
:= velY
-4;
763 accelX
:= -sign(velX
)*Random
/100;
766 crnd
:= 20*Random(6)-50;
768 red
:= genColor(cr
, CRnd
, 0);
769 green
:= genColor(cg
, CRnd
, 0);
770 blue
:= genColor(cb
, CRnd
, 0);
773 particleType
:= TPartType
.Blood
;
774 state
:= TPartState
.Normal
;
776 liveTime
:= 120+Random(40);
781 if (CurrentParticle
>= MaxParticles
-1) then CurrentParticle
:= 0 else CurrentParticle
+= 1;
786 procedure g_GFX_Water (fX
, fY
: Integer; count
: Word; fVelX
, fVelY
: Single; devX
, devY
, color
: Byte;
787 simple
: Boolean=false; cr
: Byte=0; cg
: Byte=0; cb
: Byte=0);
790 devX1
, devX2
, devY1
, devY2
: Integer;
794 if not gpart_dbg_enabled
then exit
;
796 l
:= Length(Particles
);
797 if (l
= 0) then exit
;
798 if (count
> l
) then count
:= l
;
800 if (abs(fVelX
) < 3.0) then fVelX
:= 3.0-6.0*Random
;
807 if (not simple
) and (color
> 3) then color
:= 0;
809 for a
:= 1 to count
do
811 with Particles
[CurrentParticle
] do
815 x
:= fX
-devX1
+Random(devX2
);
816 y
:= fY
-devY1
+Random(devY2
);
818 if (abs(fVelX
) < 0.5) then velX
:= 1.0-2.0*Random
else velX
:= fVelX
*Random
;
819 if (Random(10) < 7) then velX
:= -velX
;
820 velY
:= fVelY
*Random
;
835 // check for level bounds
836 if (x
< g_Map_MinX
) or (y
< g_Map_MinY
) or (x
> g_Map_MaxX
) or (y
> g_Map_MaxY
) then continue
;
838 // in what environment we are starting in?
839 pan
:= g_Map_PanelAtPoint(x
, y
, (GridTagObstacle
or GridTagLiquid
));
842 // either in a wall, or in a liquid
843 //if ((pan.tag and GridTagObstacle) <> 0) then continue; // don't spawn in walls
844 //env := TEnvType.ELiquid;
849 env
:= TEnvType
.EAir
;
856 red
:= 155+Random(9)*10;
857 green
:= trunc(150*Random
);
862 red
:= trunc(150*Random
);
863 green
:= 175+Random(9)*10;
868 red
:= trunc(200*Random
);
870 blue
:= 175+Random(9)*10;
872 4: // Ñâîé öâåò, ñâåòëåå
874 red
:= 20+Random(19)*10;
877 red
:= nmin(red
+cr
, 255);
878 green
:= nmin(green
+cg
, 255);
879 blue
:= nmin(blue
+cb
, 255);
881 5: // Ñâîé öâåò, òåìÃåå
883 red
:= 20+Random(19)*10;
886 red
:= nmax(cr
-red
, 0);
887 green
:= nmax(cg
-green
, 0);
888 blue
:= nmax(cb
-blue
, 0);
892 red
:= 90+random(12)*10;
899 particleType
:= TPartType
.Water
;
900 state
:= TPartState
.Normal
;
902 liveTime
:= 60+Random(60);
907 if (CurrentParticle
>= MaxParticles
-1) then CurrentParticle
:= 0 else CurrentParticle
+= 1;
912 procedure g_GFX_SimpleWater (fX
, fY
: Integer; count
: Word; fVelX
, fVelY
: Single; defColor
, cr
, cg
, cb
: Byte);
914 g_GFX_Water(fX
, fY
, count
, 0, 0, 0, 0, defColor
, true, cr
, cg
, cb
);
918 // ////////////////////////////////////////////////////////////////////////// //
919 procedure TParticle
.thinkerBubble ();
930 if (y
<= ceilingY
) then begin die(); exit
; end;
934 if (y
>= floorY
) then begin die(); exit
; end;
936 if (y
< g_Map_MinY
) or (y
> g_Map_MaxY
) then begin die(); exit
; end;
939 if (velY
> -4) then velY
+= accelY
;
945 {.$DEFINE D2F_DEBUG_BUBBLES}
946 procedure g_GFX_Bubbles (fX
, fY
: Integer; count
: Word; devX
, devY
: Byte);
949 devX1
, devX2
, devY1
, devY2
: Integer;
951 {$IF DEFINED(D2F_DEBUG_BUBBLES)}
956 if not gpart_dbg_enabled
then exit
;
958 l
:= Length(Particles
);
959 if (l
= 0) then exit
;
960 if (count
> l
) then count
:= l
;
967 for a
:= 1 to count
do
969 with Particles
[CurrentParticle
] do
971 x
:= fX
-devX1
+Random(devX2
);
972 y
:= fY
-devY1
+Random(devY2
);
974 // check for level bounds
975 if (x
< g_Map_MinX
) or (y
< g_Map_MinY
) or (x
> g_Map_MaxX
) or (y
> g_Map_MaxY
) then continue
;
978 // don't spawn bubbles outside of the liquid
979 if not isLiquidAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIQUID)} then
983 // trace liquid, so we'll know where it ends; do it in 8px steps for speed
984 // tracer will return `false` if we started outside of the liquid
986 {$IF DEFINED(D2F_DEBUG_BUBBLES)}
987 stt
:= curTimeMicro();
988 ptr
:= mapGrid
.traceOrthoRayWhileIn(liquidx
, liquidTopY
, x
, y
, x
, 0, GridTagWater
or GridTagAcid1
or GridTagAcid2
);
989 stt
:= curTimeMicro()-stt
;
990 e_LogWritefln('traceOrthoRayWhileIn: time=%s (%s); liquidTopY=%s', [Integer(stt
), ptr
, liquidTopY
]);
992 stt
:= curTimeMicro();
993 nptr
:= g_Map_TraceLiquidNonPrecise(x
, y
, 0, -8, liquidx
, liquidTopY
);
994 stt
:= curTimeMicro()-stt
;
995 e_LogWritefln('g_Map_TraceLiquidNonPrecise: time=%s (%s); liquidTopY=%s', [Integer(stt
), nptr
, liquidTopY
]);
996 if not nptr
then continue
;
998 if not g_Map_TraceLiquidNonPrecise(x
, y
, 0, -8, liquidx
, ceilingY
) then continue
;
999 if not g_Map_TraceLiquidNonPrecise(x
, y
, 0, +8, liquidx
, floorY
) then continue
;
1012 state
:= TPartState
.Normal
;
1013 particleType
:= TPartType
.Bubbles
;
1018 if (CurrentParticle
>= MaxParticles
-1) then CurrentParticle
:= 0 else CurrentParticle
+= 1;
1023 // ////////////////////////////////////////////////////////////////////////// //
1024 procedure TParticle
.thinkerSpark ();
1032 if not gpart_dbg_phys_enabled
then goto _done
;
1038 if (abs(velX
) < 0.1) and (abs(velY
) < 0.1) then
1047 // has some horizontal velocity
1048 pan
:= g_Map_traceToNearest(x
, y
, x
+dX
, y
+dY
, GridTagObstacle
, @ex
, @ey
);
1049 if (x
<> ex
) then begin floorY
:= Unknown
; ceilingY
:= Unknown
; end; // dunno yet
1052 if (pan
<> nil) then
1054 // hit the wall; falling down vertically
1059 else if (dY
<> 0) then
1061 // has some vertical velocity
1065 if (ceilingY
= Unknown
) then findCeiling(); // need to do this anyway
1067 if (y
<= ceilingY
) then
1069 // oops, hit a ceiling
1072 accelY
:= abs(accelY
);
1074 // environment didn't changed
1079 if (floorY
= Unknown
) then findFloor(); // need to do this anyway
1081 if (y
>= floorY
) then
1083 // hit something except a floor?
1084 if (floorType
<> TFloorType
.Wall
) then begin die(); exit
; end; // yep: just die
1085 // otherwise, go to sleep
1088 // environment didn't changed
1094 if (x
< g_Map_MinX
) or (y
< g_Map_MinY
) or (x
> g_Map_MaxX
) or (y
> g_Map_MaxY
) then begin die(); end;
1096 if (velX
<> 0.0) then velX
+= accelX
;
1098 if (velY
<> 0.0) then
1100 if (accelY
< 10) then accelY
+= 0.08;
1108 // ////////////////////////////////////////////////////////////////////////// //
1109 procedure g_GFX_SparkVel (fX
, fY
: Integer; count
: Word; vx
, vy
: Integer; devX
, devY
: Byte);
1112 devX1
, devX2
, devY1
, devY2
: Integer;
1116 if not gpart_dbg_enabled
then exit
;
1118 l
:= Length(Particles
);
1119 if (l
= 0) then exit
;
1120 if (count
> l
) then count
:= l
;
1122 devX1
:= devX
div 2;
1124 devY1
:= devY
div 2;
1127 for a
:= 1 to count
do
1129 with Particles
[CurrentParticle
] do
1131 x
:= fX
-devX1
+Random(devX2
);
1132 y
:= fY
-devY1
+Random(devY2
);
1134 // check for level bounds
1135 if (x
< g_Map_MinX
) or (y
< g_Map_MinY
) or (x
> g_Map_MaxX
) or (y
> g_Map_MaxY
) then continue
;
1137 // in what environment we are starting in?
1138 pan
:= g_Map_PanelAtPoint(x
, y
, (GridTagObstacle
or GridTagLiquid
));
1139 if (pan
<> nil) then
1141 // either in a wall, or in a liquid
1142 //if ((pan.tag and GridTagObstacle) <> 0) then continue; // don't spawn in walls
1143 //env := TEnvType.ELiquid;
1148 env
:= TEnvType
.EAir
;
1151 velX
:= vx
+(Random
-Random
)*3;
1152 velY
:= vy
+(Random
-Random
)*3;
1156 if (velY
-4 < -4) then velY
:= -4 else velY
:= velY
-4;
1159 accelX
:= -sign(velX
)*Random
/100;
1163 green
:= 100+Random(155);
1167 particleType
:= TPartType
.Spark
;
1168 state
:= TPartState
.Normal
;
1170 liveTime
:= 30+Random(60);
1172 ceilingY
:= Unknown
;
1175 if (CurrentParticle
>= MaxParticles
-1) then CurrentParticle
:= 0 else CurrentParticle
+= 1;
1180 procedure g_GFX_Spark (fX
, fY
: Integer; count
: Word; angle
: SmallInt; devX
, devY
: Byte);
1184 devX1
, devX2
, devY1
, devY2
: Integer;
1185 baseVelX
, baseVelY
: Single;
1189 if not gpart_dbg_enabled
then exit
;
1191 l
:= Length(Particles
);
1192 if (l
= 0) then exit
;
1193 if (count
> l
) then count
:= l
;
1197 devX1
:= devX
div 2;
1199 devY1
:= devY
div 2;
1202 b
:= DegToRad(angle
);
1204 baseVelY
:= 1.6*sin(b
);
1205 if (abs(baseVelX
) < 0.01) then baseVelX
:= 0.0;
1206 if (abs(baseVelY
) < 0.01) then baseVelY
:= 0.0;
1208 for a
:= 1 to count
do
1210 with Particles
[CurrentParticle
] do
1212 x
:= fX
-devX1
+Random(devX2
);
1213 y
:= fY
-devY1
+Random(devY2
);
1215 // check for level bounds
1216 if (x
< g_Map_MinX
) or (y
< g_Map_MinY
) or (x
> g_Map_MaxX
) or (y
> g_Map_MaxY
) then continue
;
1218 // in what environment we are starting in?
1219 pan
:= g_Map_PanelAtPoint(x
, y
, (GridTagObstacle
or GridTagLiquid
));
1220 if (pan
<> nil) then
1222 // either in a wall, or in a liquid
1223 //if ((pan.tag and GridTagObstacle) <> 0) then continue; // don't spawn in walls
1224 //env := TEnvType.ELiquid;
1229 env
:= TEnvType
.EAir
;
1232 velX
:= baseVelX
*Random
;
1233 velY
:= baseVelY
-Random
;
1238 green
:= 100+Random(155);
1242 particleType
:= TPartType
.Spark
;
1243 state
:= TPartState
.Normal
;
1245 liveTime
:= 30+Random(60);
1247 ceilingY
:= Unknown
;
1250 if (CurrentParticle
>= MaxParticles
-1) then CurrentParticle
:= 0 else CurrentParticle
+= 1;
1255 // ////////////////////////////////////////////////////////////////////////// //
1256 procedure g_GFX_SetMax (count
: Integer);
1260 if count
> 50000 then count
:= 50000;
1261 if (count
< 1) then count
:= 1;
1262 SetLength(Particles
, count
);
1263 for a
:= 0 to High(Particles
) do Particles
[a
].die();
1264 MaxParticles
:= count
;
1265 CurrentParticle
:= 0;
1269 function g_GFX_GetMax (): Integer;
1271 result
:= MaxParticles
;
1275 function FindOnceAnim (): DWORD
;
1279 if OnceAnims
<> nil then
1280 for i
:= 0 to High(OnceAnims
) do
1281 if OnceAnims
[i
].Animation
= nil then
1287 if OnceAnims
= nil then
1289 SetLength(OnceAnims
, 16);
1294 Result
:= High(OnceAnims
) + 1;
1295 SetLength(OnceAnims
, Length(OnceAnims
) + 16);
1300 procedure g_GFX_OnceAnim (x
, y
: Integer; Anim
: TAnimation
; AnimType
: Byte = 0);
1304 if not gpart_dbg_enabled
then exit
;
1306 if (Anim
= nil) then exit
;
1308 find_id
:= FindOnceAnim();
1310 OnceAnims
[find_id
].AnimType
:= AnimType
;
1311 OnceAnims
[find_id
].Animation
:= TAnimation
.Create(Anim
.FramesID
, Anim
.Loop
, Anim
.Speed
);
1312 OnceAnims
[find_id
].Animation
.Blending
:= Anim
.Blending
;
1313 OnceAnims
[find_id
].Animation
.alpha
:= Anim
.alpha
;
1314 OnceAnims
[find_id
].x
:= x
;
1315 OnceAnims
[find_id
].y
:= y
;
1319 // ////////////////////////////////////////////////////////////////////////// //
1323 procedure g_Mark (x
, y
, Width
, Height
: Integer; t
: Byte; st
: Boolean=true);
1325 cx
, ex
, ey
: Integer;
1328 if not gpart_dbg_enabled
then exit
;
1330 if (Width
< 1) or (Height
< 1) then exit
;
1331 // make some border, so we'll hit particles lying around the panel
1333 y
-= 1; Height
+= 2;
1336 ts
:= mapGrid
.tileSize
;
1350 // ////////////////////////////////////////////////////////////////////////// //
1351 procedure g_GFX_Init ();
1353 //g_Game_SetLoadingText(_lc[I_LOAD_COLLIDE_MAP]+' 1/6', 0, False);
1354 //SetLength(gCollideMap, gMapInfo.Height+1);
1355 //for a := 0 to High(gCollideMap) do SetLength(gCollideMap[a], gMapInfo.Width+1);
1358 gpart_dbg_enabled
:= false;
1363 procedure g_GFX_Free ();
1368 SetLength(Particles
, MaxParticles
);
1369 for a
:= 0 to High(Particles
) do Particles
[a
].die();
1370 CurrentParticle
:= 0;
1372 if (OnceAnims
<> nil) then
1374 for a
:= 0 to High(OnceAnims
) do OnceAnims
[a
].Animation
.Free();
1385 // ////////////////////////////////////////////////////////////////////////// //
1386 procedure g_GFX_Update ();
1392 if not gpart_dbg_enabled
then exit
;
1394 if (Particles
<> nil) then
1396 w
:= gMapInfo
.Width
;
1397 h
:= gMapInfo
.Height
;
1399 len
:= High(Particles
);
1401 for a
:= 0 to len
do
1403 if Particles
[a
].alive
then
1405 with Particles
[a
] do
1407 if (time
= liveTime
) then begin die(); continue
; end;
1408 if (x
+1 >= w
) or (y
+1 >= h
) or (x
<= 0) or (y
<= 0) then begin die(); end;
1413 end; // Particles <> nil
1418 if OnceAnims
<> nil then
1420 for a
:= 0 to High(OnceAnims
) do
1421 if OnceAnims
[a
].Animation
<> nil then
1423 case OnceAnims
[a
].AnimType
of
1426 if Random(3) = 0 then
1427 OnceAnims
[a
].x
:= OnceAnims
[a
].x
-1+Random(3);
1428 if Random(2) = 0 then
1429 OnceAnims
[a
].y
:= OnceAnims
[a
].y
-Random(2);
1433 if OnceAnims
[a
].Animation
.Played
then
1435 OnceAnims
[a
].Animation
.Free();
1436 OnceAnims
[a
].Animation
:= nil;
1439 OnceAnims
[a
].Animation
.Update();
1445 procedure g_GFX_Draw ();
1449 if not gpart_dbg_enabled
then exit
;
1451 if (Particles
<> nil) then
1453 glDisable(GL_TEXTURE_2D
);
1457 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
);
1461 len
:= High(Particles
);
1462 for a
:= 0 to len
do
1464 with Particles
[a
] do
1466 if alive
and (x
>= sX
) and (y
>= sY
) and (x
<= sX
+sWidth
) and (sY
<= sY
+sHeight
) then
1468 glColor4ub(red
, green
, blue
, alpha
);
1476 glDisable(GL_BLEND
);
1479 if (OnceAnims
<> nil) then
1481 len
:= High(OnceAnims
);
1482 for a
:= 0 to len
do
1484 if (OnceAnims
[a
].Animation
<> nil) then
1486 with OnceAnims
[a
] do Animation
.Draw(x
, y
, M_NONE
);