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}
41 MARK_BLOCKED
= MARK_WALL
or MARK_DOOR
;
42 MARK_LIQUID
= MARK_WATER
or MARK_ACID
;
43 MARK_LIFT
= MARK_LIFTDOWN
or MARK_LIFTUP
or MARK_LIFTLEFT
or MARK_LIFTRIGHT
;
46 procedure g_GFX_Init ();
47 procedure g_GFX_Free ();
49 procedure g_GFX_Blood (fX
, fY
: Integer; count
: Word; vx
, vy
: Integer;
50 devX
, devY
: Word; cr
, cg
, cb
: Byte; kind
: Byte=BLOOD_NORMAL
);
51 procedure g_GFX_Spark (fX
, fY
: Integer; count
: Word; angle
: SmallInt; devX
, devY
: Byte);
52 procedure g_GFX_Water (fX
, fY
: Integer; count
: Word; fVelX
, fVelY
: Single; devX
, devY
, color
: Byte;
53 simple
: Boolean=false; cr
: Byte=0; cg
: Byte=0; cb
: Byte=0);
54 procedure g_GFX_SimpleWater (fX
, fY
: Integer; count
: Word; fVelX
, fVelY
: Single; defColor
, cr
, cg
, cb
: Byte);
55 procedure g_GFX_Bubbles (fX
, fY
: Integer; count
: Word; devX
, devY
: Byte);
57 procedure g_GFX_SetMax (count
: Integer);
58 function g_GFX_GetMax (): Integer;
60 procedure g_GFX_OnceAnim (X
, Y
: Integer; Anim
: TAnimation
; AnimType
: Byte = 0);
62 procedure g_Mark (x
, y
, Width
, Height
: Integer; t
: Byte; st
: Boolean=true);
64 procedure g_GFX_Update ();
65 procedure g_GFX_Draw ();
69 gpart_dbg_enabled
: Boolean = true;
70 gpart_dbg_phys_enabled
: Boolean = true;
76 g_map
, g_panel
, g_basic
, Math
, e_graphics
, GL
, GLExt
,
77 g_options
, g_console
, SysUtils
, g_triggers
, MAPDEF
,
78 g_game
, g_language
, g_net
, utils
, xprofiler
;
82 Unknown
= Integer($7fffffff);
86 TPartType
= (Blood
, Spark
, Bubbles
, Water
);
87 TPartState
= (Free
, Normal
, Stuck
, Sleeping
);
88 TFloorType
= (Wall
, LiquidIn
, LiquidOut
);
89 // Wall: floorY is just before floor
90 // LiquidIn: floorY is liquid *start* (i.e. just in a liquid)
91 // LiquidOut: floorY is liquid *end* (i.e. just out of a liquid)
92 TEnvType
= (EAir
, ELiquid
, EWall
); // where particle is now
94 // note: this MUST be record, so we can keep it in
95 // dynamic array and has sequential memory access pattern
96 PParticle
= ^TParticle
;
100 accelX
, accelY
: Single;
101 red
, green
, blue
: Byte;
103 time
, liveTime
: Word;
105 particleType
: TPartType
;
106 offsetX
, offsetY
: ShortInt;
108 stickDX
: Integer; // STATE_STICK: -1,1: stuck to a wall; 0: stuck to ceiling
109 justSticked
: Boolean; // not used
110 floorY
: Integer; // actually, floor-1; `Unknown`: unknown
111 floorType
: TFloorType
;
112 env
: TEnvType
; // where particle is now
113 ceilingY
: Integer; // actually, ceiling+1; `Unknown`: unknown
114 wallEndY
: Integer; // if we stuck to a wall, this is where wall ends
120 //k8: sorry, i have to emulate virtual methods this way, 'cause i haet `Object`
121 procedure thinkerBloodAndWater ();
122 procedure thinkerSpark ();
123 procedure thinkerBubble ();
125 procedure findFloor (force
: Boolean=false); // this updates `floorY` if forced or Unknown
126 procedure findCeiling (force
: Boolean=false); // this updates `ceilingY` if forced or Unknown
128 procedure freeze (); inline; // remove velocities and acceleration
129 procedure sleep (); inline; // switch to sleep mode
131 function isSleeping (): Boolean; inline;
132 procedure awake (); inline;
134 function alive (): Boolean; inline;
135 procedure die (); inline;
136 procedure think (); inline;
142 Animation
: TAnimation
;
147 Particles
: array of TParticle
= nil;
148 OnceAnims
: array of TOnceAnim
= nil;
149 MaxParticles
: Integer = 0;
150 CurrentParticle
: Integer = 0;
151 // awakeMap has one bit for each map grid cell; on g_Mark,
152 // corresponding bits will be set, and in `think()` all particles
153 // in marked cells will be awaken
154 awakeMap
: packed array of LongWord = nil;
155 awakeMapH
: Integer = -1;
156 awakeMapW
: Integer = -1;
157 awakeMinX
, awakeMinY
: Integer;
160 // ////////////////////////////////////////////////////////////////////////// //
161 // HACK! using mapgrid
162 procedure awmClear (); inline;
164 if (awakeMapW
> 0) then FillDWord(awakeMap
[0], Length(awakeMap
), 0);
168 procedure awmSetup ();
170 assert(mapGrid
<> nil);
171 awakeMapW
:= (mapGrid
.gridWidth
+mapGrid
.tileSize
-1) div mapGrid
.tileSize
;
172 awakeMapW
:= (awakeMapW
+31) div 32; // LongWord has 32 bits ;-)
173 awakeMapH
:= (mapGrid
.gridHeight
+mapGrid
.tileSize
-1) div mapGrid
.tileSize
;
174 awakeMinX
:= mapGrid
.gridX0
;
175 awakeMinY
:= mapGrid
.gridY0
;
176 SetLength(awakeMap
, awakeMapW
*awakeMapH
);
177 {$IF DEFINED(D2F_DEBUG)}
178 e_LogWritefln('particle awake map: %sx%s (for grid of size %sx%s)', [awakeMapW
, awakeMapH
, mapGrid
.gridWidth
, mapGrid
.gridHeight
]);
184 function awmIsSet (x
, y
: Integer): Boolean; inline;
186 x
:= (x
-awakeMinX
) div mapGrid
.tileSize
;
187 y
:= (y
-awakeMinY
) div mapGrid
.tileSize
;
188 if (x
>= 0) and (y
>= 0) and (x
div 32 < awakeMapW
) and (y
< awakeMapH
) then
190 {$IF DEFINED(D2F_DEBUG)}
191 assert(y
*awakeMapW
+x
div 32 < Length(awakeMap
));
193 result
:= ((awakeMap
[y
*awakeMapW
+x
div 32] and (LongWord(1) shl (x
mod 32))) <> 0);
202 procedure awmSet (x
, y
: Integer); inline;
206 x
:= (x
-awakeMinX
) div mapGrid
.tileSize
;
207 y
:= (y
-awakeMinY
) div mapGrid
.tileSize
;
208 if (x
>= 0) and (y
>= 0) and (x
div 32 < awakeMapW
) and (y
< awakeMapH
) then
210 {$IF DEFINED(D2F_DEBUG)}
211 assert(y
*awakeMapW
+x
div 32 < Length(awakeMap
));
213 v
:= @awakeMap
[y
*awakeMapW
+x
div 32];
214 v
^ := v
^ or (LongWord(1) shl (x
mod 32));
219 // ////////////////////////////////////////////////////////////////////////// //
220 function TParticle
.alive (): Boolean; inline; begin result
:= (state
<> TPartState
.Free
); end;
221 procedure TParticle
.die (); inline; begin state
:= TPartState
.Free
; end;
223 function TParticle
.isSleeping (): Boolean; inline;
225 result
:= alive
and (onGround
or (not justSticked
and (state
= TPartState
.Stuck
)));
228 procedure TParticle
.awake (); inline;
230 if {alive and} (onGround
or (not justSticked
and (state
= TPartState
.Stuck
))) then
232 // wakeup this particle
234 if (part.ParticleType = PARTICLE_SPARK) then
236 e_LogWritefln('waking up particle of type %s; justSticked=%s; onGround=%s; VelY=%s; AccelY=%s', [part.ParticleType, part.justSticked, part.onGround, part.VelY, part.AccelY]);
239 justSticked
:= true; // so sticked state will be re-evaluated
242 if (velY
= 0) then velY
:= 0.1;
243 if (accelY
= 0) then accelY
:= 0.5;
245 onGround
:= false; // so onground state will be re-evaluated
251 // remove velocities and acceleration
252 procedure TParticle
.freeze (); inline;
254 // stop right there, you criminal scum!
262 // switch to sleep mode
263 procedure TParticle
.sleep (); inline;
265 state
:= TPartState
.Sleeping
;
270 procedure TParticle
.findFloor (force
: Boolean=false);
275 if (not force
) and (floorY
<> Unknown
) then exit
;
276 // stuck in the wall? rescan, 'cause it can be mplat
277 if (env
= TEnvType
.EWall
) then
279 pan
:= g_Map_PanelAtPoint(x
, y
, (GridTagObstacle
or GridTagLiquid
));
282 // either in a wall, or in a liquid
283 if ((pan
.tag
and GridTagObstacle
) <> 0) then
285 // we are in the wall, wtf?!
287 env
:= TEnvType
.EWall
;
288 floorType
:= TFloorType
.Wall
;
289 state
:= TPartState
.Sleeping
; // anyway
292 // we are in liquid, trace to liquid end
293 env
:= TEnvType
.ELiquid
;
296 // are we in a liquid?
297 if (env
= TEnvType
.ELiquid
) then
299 // trace out of the liquid
300 //env := TEnvType.ELiquid;
301 floorType
:= TFloorType
.LiquidOut
;
302 //e_LogWritefln('tracing out of a liquid; floorY=%s; y=%s', [floorY, y]);
303 mapGrid
.traceOrthoRayWhileIn(ex
, floorY
, x
, y
, x
, g_Map_MaxY
, GridTagLiquid
);
304 floorY
+= 1; // so `floorY` is just out of a liquid
305 //e_LogWritefln(' traced out of a liquid; floorY=%s; y=%s', [floorY, y]);
310 assert(env
= TEnvType
.EAir
);
311 //env := TEnvType.EAir;
312 pan
:= g_Map_traceToNearest(x
, y
, x
, g_Map_MaxY
, (GridTagObstacle
or GridTagLiquid
), @ex
, @floorY
);
316 if ((pan
.tag
and GridTagObstacle
) <> 0) then
319 floorType
:= TFloorType
.Wall
;
324 floorType
:= TFloorType
.LiquidIn
; // entering liquid
325 floorY
+= 1; // so `floorY` is just in a liquid
330 // out of the level; assume wall, but it doesn't really matter
331 floorType
:= TFloorType
.Wall
;
332 floorY
:= g_Map_MaxY
+2;
338 procedure TParticle
.findCeiling (force
: Boolean=false);
342 if (not force
) and (ceilingY
<> Unknown
) then exit
;
343 if (nil = g_Map_traceToNearest(x
, y
, x
, g_Map_MinY
, GridTagObstacle
, @ex
, @ceilingY
)) then
345 ceilingY
:= g_Map_MinY
-2;
350 procedure TParticle
.think (); inline;
352 // awake sleeping particle, if necessary
353 if (state
= TPartState
.Sleeping
) and awmIsSet(x
, y
) then state
:= TPartState
.Normal
;
355 TPartType
.Blood
, TPartType
.Water
: thinkerBloodAndWater();
356 //TPartType.Spark: thinkerSpark();
357 TPartType
.Bubbles
: thinkerBubble();
362 // ////////////////////////////////////////////////////////////////////////// //
363 procedure TParticle
.thinkerBloodAndWater ();
364 procedure stickToCeiling ();
366 state
:= TPartState
.Stuck
;
369 ceilingY
:= y
; // yep
372 procedure stickToWall (dx
: Integer);
376 state
:= TPartState
.Stuck
;
377 if (dX
> 0) then stickDX
:= 1 else stickDX
:= -1;
379 // find next floor transition
382 mapGrid
.traceOrthoRayWhileIn(ex
, wallEndY
, x
+stickDX
, y
, x
+stickDX
, floorY
+1, (GridTagWall
or GridTagDoor
or GridTagStep
));
383 //if (wallEndY > floorY) then wallEndY := floorY; // just in case
386 procedure hitAFloor ();
388 state
:= TPartState
.Sleeping
; // we aren't moving anymore
391 floorType
:= TFloorType
.Wall
; // yep
394 // `true`: didn't, get outa thinker
395 function drip (): Boolean;
398 TPartType
.Blood
: result
:= (Random(200) = 100);
399 TPartType
.Water
: result
:= (Random(30) = 15);
400 else raise Exception
.Create('internal error in particle engine: drip');
402 if result
then begin velY
:= 0.5; accelY
:= 0.15; end;
405 // `true`: affected by air stream
406 function checkAirStreams (): Boolean;
410 pan
:= g_Map_PanelAtPoint(x
, y
, GridTagLift
);
411 result
:= (pan
<> nil);
414 if ((pan
.PanelType
and PANEL_LIFTUP
) <> 0) then
416 if (velY
> -4-Random(3)) then velY
-= 0.8;
417 if (abs(velX
) > 0.1) then velX
-= velX
/10.0;
418 velX
+= (Random
-Random
)*0.2;
421 else if ((pan
.PanelType
and PANEL_LIFTLEFT
) <> 0) then
423 if (velX
> -8-Random(3)) then velX
-= 0.8;
426 else if ((pan
.PanelType
and PANEL_LIFTRIGHT
) <> 0) then
428 if (velX
< 8+Random(3)) then velX
+= 0.8;
436 if result
and (state
= TPartState
.Sleeping
) then state
:= TPartState
.Normal
;
440 // switch to freefall mode
441 procedure freefall ();
443 state
:= TPartState
.Normal
;
448 procedure applyGravity (inLiquid
: Boolean);
450 state
:= TPartState
.Normal
;
472 // still check for air streams when sleeping
473 if (state
= TPartState
.Sleeping
) then begin checkAirStreams(); goto _done
; end; // so blood will dissolve
475 // process stuck particles
476 if (state
= TPartState
.Stuck
) then
478 // stuck to a ceiling?
479 if (stickDX
= 0) then
481 // yeah, stuck to a ceiling
482 assert(ceilingY
<> Unknown
);
483 // dropped from a ceiling?
484 if (y
> ceilingY
) then
489 state
:= TPartState
.Normal
;
493 // otherwise, try to drip
494 if drip() then goto _done
;
500 assert(wallEndY
<> Unknown
);
505 TFloorType
.Wall
: // hit the ground
508 goto _done
; // nothing to do anymore
510 TFloorType
.LiquidIn
: // entering the liquid
512 // rescan, so we'll know when we'll exit the liquid
513 findFloor(true); // force rescan
515 TFloorType
.LiquidOut
: // exiting the liquid
517 // rescan, so we'll know when we'll enter something interesting
518 findFloor(true); // force rescan
519 if (floorType
= TFloorType
.Wall
) and (floorY
= y
) then begin sleep(); goto _done
; end;
524 if (y
= wallEndY
) then
526 // just unstuck from the wall, switch to freefall mode
531 // otherwise, try to drip
532 if drip() then goto _done
;
535 // nope, process as usual
538 // it is important to have it here
542 // gravity, if not stuck
543 if (state
<> TPartState
.Stuck
) and (abs(velX
) < 0.1) and (abs(velY
) < 0.1) then
545 if (floorY
= Unknown
) then findFloor();
550 TFloorType
.Wall
: // hit the ground
554 TFloorType
.LiquidIn
: // entering the liquid
556 // rescan, so we'll know when we'll exit the liquid
557 findFloor(true); // force rescan
560 TFloorType
.LiquidOut
: // exiting the liquid
562 // rescan, so we'll know when we'll enter something interesting
563 findFloor(true); // force rescan
564 if (floorType
<> TFloorType
.Wall
) or (floorY
<> y
) then applyGravity(floorType
= TFloorType
.LiquidIn
);
570 // looks like we're in the air
578 // has some horizontal velocity
579 pan
:= g_Map_traceToNearest(x
, y
, x
+dX
, y
+dY
, GridTagObstacle
, @ex
, @ey
);
580 if (x
<> ex
) then begin floorY
:= Unknown
; ceilingY
:= Unknown
; end; // dunno yet
583 if (x
< g_Map_MinX
) or (x
> g_Map_MaxX
) then begin die(); exit
; end;
587 // the only case when we can have both ceiling and wall is corner; stick to wall in this case
588 // check environment (air/liquid)
589 if (g_Map_PanelAtPoint(x
, y
, GridTagLiquid
) <> nil) then env
:= TEnvType
.ELiquid
else env
:= TEnvType
.EAir
;
590 // check if we stuck to a wall
591 if (dX
< 0) then dX
:= -1 else dX
:= 1;
592 if (g_Map_PanelAtPoint(x
+dX
, y
, GridTagObstacle
) <> nil) then
599 // stuck to a ceiling
604 else if (dY
<> 0) then
606 // has only vertical velocity
610 if (ceilingY
= Unknown
) then findCeiling(); // need to do this anyway
612 if (y
<= ceilingY
) then begin y
:= ceilingY
; stickToCeiling(); end; // oops, hit a ceiling
613 // environmend didn't changed
620 if (floorY
= Unknown
) then findFloor(); // need to do this anyway
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 // environmend 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 (x
> g_Map_MaxX
) or (y
> g_Map_MaxY
) or (x
< g_Map_MinX
) or (y
< g_Map_MinY
) then begin die(); exit
; end;
673 if (g_Map_PanelAtPoint(x
, y
, GridTagObstacle
) <> nil) then begin die(); exit
; end;
680 // blood will dissolve in other liquids
681 if (particleType
= TPartType
.Blood
) then
683 if (env
= TEnvType
.ELiquid
) then
686 if (liveTime
<= 0) then begin die(); exit
; end;
687 ex
:= 255-trunc(255.0*time
/liveTime
);
688 if (ex
>= 250) then begin die(); exit
; end;
689 if (ex
< 0) then ex
:= 0;
695 // water will disappear in water (?)
696 if (env
= TEnvType
.ELiquid
) then die();
702 // ////////////////////////////////////////////////////////////////////////// //
703 procedure g_GFX_SparkVel (fX
, fY
: Integer; count
: Word; VX
, VY
: Integer; devX
, devY
: Byte); forward;
705 procedure g_GFX_Blood (fX
, fY
: Integer; count
: Word; vx
, vy
: Integer;
706 devX
, devY
: Word; cr
, cg
, cb
: Byte; kind
: Byte = BLOOD_NORMAL
);
708 function genColor (cbase
, crnd
: Integer; def
: Byte=0): Byte;
713 if (cbase
< 0) then result
:= 0
714 else if (cbase
> 255) then result
:= 255
715 else result
:= Byte(cbase
);
725 devX1
, devX2
, devY1
, devY2
: Integer;
730 if not gpart_dbg_enabled
then Exit
;
732 if (kind
= BLOOD_SPARKS
) then
734 g_GFX_SparkVel(fX
, fY
, 2+Random(2), -VX
div 2, -VY
div 2, devX
, devY
);
738 l
:= Length(Particles
);
739 if (l
= 0) then exit
;
740 if (count
> l
) then count
:= l
;
747 for a
:= 1 to count
do
749 with Particles
[CurrentParticle
] do
751 x
:= fX
-devX1
+Random(devX2
);
752 y
:= fY
-devY1
+Random(devY2
);
754 // check for level bounds
755 if (x
< g_Map_MinX
) or (y
< g_Map_MinY
) or (x
> g_Map_MaxX
) or (y
> g_Map_MaxY
) then continue
;
757 // in what environment we are starting in?
758 pan
:= g_Map_PanelAtPoint(x
, y
, (GridTagObstacle
or GridTagLiquid
));
761 // either in a wall, or in a liquid
762 if ((pan
.tag
and GridTagObstacle
) <> 0) then continue
; // don't spawn in walls
763 env
:= TEnvType
.ELiquid
;
767 env
:= TEnvType
.EAir
;
770 velX
:= vx
+(Random
-Random
)*3;
771 velY
:= vy
+(Random
-Random
)*3;
775 if (velY
-4 < -4) then velY
:= -4 else velY
:= velY
-4;
778 accelX
:= -sign(velX
)*Random
/100;
781 crnd
:= 20*Random(6)-50;
783 red
:= genColor(cr
, CRnd
, 0);
784 green
:= genColor(cg
, CRnd
, 0);
785 blue
:= genColor(cb
, CRnd
, 0);
788 particleType
:= TPartType
.Blood
;
789 state
:= TPartState
.Normal
;
791 liveTime
:= 120+Random(40);
796 if (CurrentParticle
>= MaxParticles
-1) then CurrentParticle
:= 0 else CurrentParticle
+= 1;
801 procedure g_GFX_Water (fX
, fY
: Integer; count
: Word; fVelX
, fVelY
: Single; devX
, devY
, color
: Byte;
802 simple
: Boolean=false; cr
: Byte=0; cg
: Byte=0; cb
: Byte=0);
805 devX1
, devX2
, devY1
, devY2
: Integer;
809 if not gpart_dbg_enabled
then Exit
;
811 l
:= Length(Particles
);
812 if (l
= 0) then exit
;
813 if (count
> l
) then count
:= l
;
815 if (abs(fVelX
) < 3.0) then fVelX
:= 3.0-6.0*Random
;
822 if (not simple
) and (color
> 3) then color
:= 0;
824 for a
:= 1 to count
do
826 with Particles
[CurrentParticle
] do
830 x
:= fX
-devX1
+Random(devX2
);
831 y
:= fY
-devY1
+Random(devY2
);
833 if (abs(fVelX
) < 0.5) then velX
:= 1.0-2.0*Random
else velX
:= fVelX
*Random
;
834 if (Random(10) < 7) then velX
:= -velX
;
835 velY
:= fVelY
*Random
;
850 // check for level bounds
851 if (x
< g_Map_MinX
) or (y
< g_Map_MinY
) or (x
> g_Map_MaxX
) or (y
> g_Map_MaxY
) then continue
;
853 // in what environment we are starting in?
854 pan
:= g_Map_PanelAtPoint(x
, y
, (GridTagObstacle
or GridTagLiquid
));
857 // either in a wall, or in a liquid
858 //if ((pan.tag and GridTagObstacle) <> 0) then continue; // don't spawn in walls
859 //env := TEnvType.ELiquid;
864 env
:= TEnvType
.EAir
;
871 red
:= 155+Random(9)*10;
872 green
:= trunc(150*Random
);
877 red
:= trunc(150*Random
);
878 green
:= 175+Random(9)*10;
883 red
:= trunc(200*Random
);
885 blue
:= 175+Random(9)*10;
887 4: // Ñâîé öâåò, ñâåòëåå
889 red
:= 20+Random(19)*10;
892 red
:= nmin(red
+cr
, 255);
893 green
:= nmin(green
+cg
, 255);
894 blue
:= nmin(blue
+cb
, 255);
896 5: // Ñâîé öâåò, òåìíåå
898 red
:= 20+Random(19)*10;
901 red
:= nmax(cr
-red
, 0);
902 green
:= nmax(cg
-green
, 0);
903 blue
:= nmax(cb
-blue
, 0);
907 red
:= 90+random(12)*10;
914 particleType
:= TPartType
.Water
;
915 state
:= TPartState
.Normal
;
917 liveTime
:= 60+Random(60);
922 if (CurrentParticle
>= MaxParticles
-1) then CurrentParticle
:= 0 else CurrentParticle
+= 1;
927 procedure g_GFX_SimpleWater (fX
, fY
: Integer; count
: Word; fVelX
, fVelY
: Single; defColor
, cr
, cg
, cb
: Byte);
929 g_GFX_Water(fX
, fY
, count
, 0, 0, 0, 0, defColor
, true, cr
, cg
, cb
);
933 // ////////////////////////////////////////////////////////////////////////// //
934 procedure TParticle
.thinkerBubble ();
945 if (y
<= ceilingY
) then begin die(); exit
; end;
949 if (y
>= floorY
) then begin die(); exit
; end;
951 if (y
< g_Map_MinY
) or (y
> g_Map_MaxY
) then begin die(); exit
; end;
954 if (velY
> -4) then velY
+= accelY
;
960 {.$DEFINE D2F_DEBUG_BUBBLES}
961 procedure g_GFX_Bubbles (fX
, fY
: Integer; count
: Word; devX
, devY
: Byte);
964 devX1
, devX2
, devY1
, devY2
: Integer;
966 {$IF DEFINED(D2F_DEBUG_BUBBLES)}
971 if not gpart_dbg_enabled
then Exit
;
973 l
:= Length(Particles
);
974 if (l
= 0) then exit
;
975 if (count
> l
) then count
:= l
;
982 for a
:= 1 to count
do
984 with Particles
[CurrentParticle
] do
986 x
:= fX
-devX1
+Random(devX2
);
987 y
:= fY
-devY1
+Random(devY2
);
989 // check for level bounds
990 if (x
< g_Map_MinX
) or (y
< g_Map_MinY
) or (x
> g_Map_MaxX
) or (y
> g_Map_MaxY
) then continue
;
993 // don't spawn bubbles outside of the liquid
994 if not isLiquidAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIQUID)} then
998 // trace liquid, so we'll know where it ends; do it in 8px steps for speed
999 // tracer will return `false` if we started outside of the liquid
1001 {$IF DEFINED(D2F_DEBUG_BUBBLES)}
1002 stt
:= curTimeMicro();
1003 ptr
:= mapGrid
.traceOrthoRayWhileIn(liquidx
, liquidTopY
, x
, y
, x
, 0, GridTagWater
or GridTagAcid1
or GridTagAcid2
);
1004 stt
:= curTimeMicro()-stt
;
1005 e_LogWritefln('traceOrthoRayWhileIn: time=%s (%s); liquidTopY=%s', [Integer(stt
), ptr
, liquidTopY
]);
1007 stt
:= curTimeMicro();
1008 nptr
:= g_Map_TraceLiquidNonPrecise(x
, y
, 0, -8, liquidx
, liquidTopY
);
1009 stt
:= curTimeMicro()-stt
;
1010 e_LogWritefln('g_Map_TraceLiquidNonPrecise: time=%s (%s); liquidTopY=%s', [Integer(stt
), nptr
, liquidTopY
]);
1011 if not nptr
then continue
;
1013 if not g_Map_TraceLiquidNonPrecise(x
, y
, 0, -8, liquidx
, ceilingY
) then continue
;
1014 if not g_Map_TraceLiquidNonPrecise(x
, y
, 0, +8, liquidx
, floorY
) then continue
;
1027 state
:= TPartState
.Normal
;
1028 particleType
:= TPartType
.Bubbles
;
1033 if (CurrentParticle
>= MaxParticles
-1) then CurrentParticle
:= 0 else CurrentParticle
+= 1;
1038 // ////////////////////////////////////////////////////////////////////////// //
1039 function isBlockedAt (x
, y
: Integer): Boolean; inline;
1041 if not gpart_dbg_phys_enabled
then begin result
:= false; exit
; end;
1042 result
:= g_Map_HasAnyPanelAtPoint(x
, y
, (PANEL_WALL
or PANEL_OPENDOOR
or PANEL_CLOSEDOOR
or PANEL_STEP
));
1046 function isWallAt (x
, y
: Integer): Boolean; inline;
1048 if not gpart_dbg_phys_enabled
then begin result
:= false; exit
; end;
1049 result
:= g_Map_HasAnyPanelAtPoint(x
, y
, (PANEL_WALL
or PANEL_STEP
));
1052 function isLiftUpAt (x
, y
: Integer): Boolean; inline;
1054 if not gpart_dbg_phys_enabled
then begin result
:= false; exit
; end;
1055 result
:= g_Map_HasAnyPanelAtPoint(x
, y
, PANEL_LIFTUP
);
1058 function isLiftDownAt (x
, y
: Integer): Boolean; inline;
1060 if not gpart_dbg_phys_enabled
then begin result
:= false; exit
; end;
1061 result
:= g_Map_HasAnyPanelAtPoint(x
, y
, PANEL_LIFTDOWN
);
1064 function isLiftLeftAt (x
, y
: Integer): Boolean; inline;
1066 if not gpart_dbg_phys_enabled
then begin result
:= false; exit
; end;
1067 result
:= g_Map_HasAnyPanelAtPoint(x
, y
, PANEL_LIFTLEFT
);
1070 function isLiftRightAt (x
, y
: Integer): Boolean; inline;
1072 if not gpart_dbg_phys_enabled
then begin result
:= false; exit
; end;
1073 result
:= g_Map_HasAnyPanelAtPoint(x
, y
, PANEL_LIFTRIGHT
);
1076 function isLiquidAt (x
, y
: Integer): Boolean; inline;
1078 if not gpart_dbg_phys_enabled
then begin result
:= false; exit
; end;
1079 result
:= g_Map_HasAnyPanelAtPoint(x
, y
, (PANEL_WATER
or PANEL_ACID1
or PANEL_ACID2
));
1082 function isAnythingAt (x
, y
: Integer): Boolean; inline;
1084 if not gpart_dbg_phys_enabled
then begin result
:= false; exit
; end;
1085 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
));
1089 // ////////////////////////////////////////////////////////////////////////// //
1090 procedure TParticle
.thinkerSpark ();
1093 {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
1104 {$IF DEFINED(D2F_NEW_SPARK_THINKER)}
1105 if (abs(velX
) < 0.1) and (abs(velY
) < 0.1) then
1107 pan
:= g_Map_traceToNearest(x
, y
-1, x
, y
+1, (GridTagWall
or GridTagDoor
or GridTagStep
or GridTagAcid1
or GridTagAcid2
or GridTagWater
), @ex
, @ey
);
1110 if (abs(velX
) < 0.1) and (abs(velY
) < 0.1) and
1111 (not isBlockedAt(x
, y
-1) {ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)}) and
1112 (not isBlockedAt(x
, y
) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)}) and
1113 (not isBlockedAt(x
, y
+1) {ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)}) then
1114 begin // Âèñèò â âîçäóõå
1122 {$IF DEFINED(D2F_NEW_SPARK_THINKER)}
1123 pan
:= g_Map_traceToNearest(x
, y
, x
+dX
, y
, (GridTagWall
or GridTagDoor
or GridTagStep
or GridTagAcid1
or GridTagAcid2
or GridTagWater
), @ex
, @ey
);
1124 //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);
1125 if (x
<> ex
) then onGround
:= false;
1128 if (pan
<> nil) then
1131 if ((pan
.tag
and (GridTagAcid1
or GridTagAcid2
or GridTagWater
)) <> 0) then begin die(); exit
; end;
1135 if (x
< 0) or (x
>= gMapInfo
.Width
) then begin die(); exit
; end;
1137 if (dX
> 0) then s
:= 1 else s
:= -1;
1141 if (x
+s
>= gMapInfo
.Width
) or (x
+s
<= 0) then begin die(); break
; end;
1142 //c := gCollideMap[Y, X+s];
1143 if isBlockedAt(x
+s
, y
) {ByteBool(c and MARK_BLOCKED)} then
1144 begin // Ñòåíà/äâåðü - ïàäàåò âåðòèêàëüíî
1150 if not isAnythingAt(x
+s
, y
) {c = MARK_FREE} then
1163 {$IF DEFINED(D2F_NEW_SPARK_THINKER)}
1164 if (dY
< 0) or not onGround
then
1166 pan
:= g_Map_traceToNearest(x
, y
, x
, y
+dY
, (GridTagWall
or GridTagDoor
or GridTagStep
or GridTagAcid1
or GridTagAcid2
or GridTagWater
), @ex
, @ey
);
1172 e_LogWritefln('AWAKEN particle of type %s; justSticked=%s; onGround=%s; VelY=%s; AccelY=%s; Y=%s; ey=%s', [ParticleType, justSticked, onGround, VelY, AccelY, Y, ey]);
1176 if (pan
<> nil) then
1179 if ((pan
.tag
and (GridTagAcid1
or GridTagAcid2
or GridTagWater
)) <> 0) then begin die(); exit
; end;
1183 accelY
:= abs(accelY
);
1193 onGround
:= (velY
>= 0) and g_Map_HasAnyPanelAtPoint(x
, y
+1, (PANEL_WALL
or PANEL_CLOSEDOOR
or PANEL_STEP
));
1195 if (y
< 0) or (y
>= gMapInfo
.Height
) then begin die(); exit
; end;
1197 if (dY
> 0) then s
:= 1 else s
:= -1;
1201 if (y
+s
>= gMapInfo
.Height
) or (y
+s
<= 0) then begin die(); break
; end;
1202 //c := gCollideMap[Y+s, X];
1203 if isBlockedAt(x
, y
+s
) {ByteBool(c and MARK_BLOCKED)} then
1204 begin // Ñòåíà/äâåðü - ïàäàåò âåðòèêàëüíî
1208 accelY
:= abs(accelY
);
1210 else // Èëè íå ïàäàåò
1221 if not isAnythingAt(x
, y
+s
) {c = MARK_FREE} then
1232 if (velX
<> 0.0) then velX
+= accelX
;
1234 if (velY
<> 0.0) then
1236 if (accelY
< 10) then accelY
+= 0.08;
1244 // ////////////////////////////////////////////////////////////////////////// //
1245 procedure g_GFX_SparkVel (fX
, fY
: Integer; count
: Word; VX
, VY
: Integer; devX
, devY
: Byte);
1253 if not gpart_dbg_enabled
then Exit
;
1254 l
:= Length(Particles
);
1256 if count
> l
then count
:= l
;
1258 devX1
:= devX
div 2;
1260 devY1
:= devY
div 2;
1263 for a
:= 1 to count
do
1265 with Particles
[CurrentParticle
] do
1267 x
:= fX
-devX1
+Random(devX2
);
1268 y
:= fY
-devY1
+Random(devY2
);
1270 velX
:= VX
+ (Random
-Random
)*3;
1271 velY
:= VY
+ (Random
-Random
)*3;
1279 accelX
:= -Sign(velX
)*Random
/100;
1283 green
:= 100+Random(155);
1287 state
:= TPartState
.Normal
;
1289 liveTime
:= 30+Random(60);
1290 particleType
:= TPartType
.Spark
;
1291 justSticked
:= false;
1292 onGround
:= (velY
>= 0) and g_Map_HasAnyPanelAtPoint(x
, y
+1, (PANEL_WALL
or PANEL_CLOSEDOOR
or PANEL_STEP
));
1296 if CurrentParticle
+2 > MaxParticles
then
1297 CurrentParticle
:= 0
1299 CurrentParticle
:= CurrentParticle
+1;
1304 procedure g_GFX_Spark(fX
, fY
: Integer; count
: Word; angle
: SmallInt; devX
, devY
: Byte);
1310 BaseVelX
, BaseVelY
: Single;
1314 if not gpart_dbg_enabled
then Exit
;
1315 l
:= Length(Particles
);
1321 angle
:= 360 - angle
;
1323 devX1
:= devX
div 2;
1325 devY1
:= devY
div 2;
1328 b
:= DegToRad(angle
);
1330 BaseVelY
:= 1.6*sin(b
);
1331 if abs(BaseVelX
) < 0.01 then
1333 if abs(BaseVelY
) < 0.01 then
1335 for a
:= 1 to count
do
1337 with Particles
[CurrentParticle
] do
1339 x
:= fX
-devX1
+Random(devX2
);
1340 y
:= fY
-devY1
+Random(devY2
);
1342 velX
:= BaseVelX
*Random
;
1343 velY
:= BaseVelY
-Random
;
1348 green
:= 100+Random(155);
1352 state
:= TPartState
.Normal
;
1354 liveTime
:= 30+Random(60);
1355 particleType
:= TPartType
.Spark
;
1356 justSticked
:= false;
1357 onGround
:= (velY
>= 0) and g_Map_HasAnyPanelAtPoint(x
, y
+1, (PANEL_WALL
or PANEL_CLOSEDOOR
or PANEL_STEP
));
1361 if CurrentParticle
+2 > MaxParticles
then
1362 CurrentParticle
:= 0
1364 CurrentParticle
:= CurrentParticle
+1;
1369 // ////////////////////////////////////////////////////////////////////////// //
1370 procedure g_GFX_SetMax(count
: Integer);
1374 if count
> 50000 then count
:= 50000;
1375 if (count
< 1) then count
:= 1;
1377 SetLength(Particles
, count
);
1378 for a
:= 0 to High(Particles
) do Particles
[a
].die();
1379 MaxParticles
:= count
;
1380 //if CurrentParticle >= Count then
1381 CurrentParticle
:= 0;
1385 function g_GFX_GetMax(): Integer;
1387 Result
:= MaxParticles
;
1391 function FindOnceAnim (): DWORD
;
1395 if OnceAnims
<> nil then
1396 for i
:= 0 to High(OnceAnims
) do
1397 if OnceAnims
[i
].Animation
= nil then
1403 if OnceAnims
= nil then
1405 SetLength(OnceAnims
, 16);
1410 Result
:= High(OnceAnims
) + 1;
1411 SetLength(OnceAnims
, Length(OnceAnims
) + 16);
1416 procedure g_GFX_OnceAnim (x
, y
: Integer; Anim
: TAnimation
; AnimType
: Byte = 0);
1420 if not gpart_dbg_enabled
then Exit
;
1424 find_id
:= FindOnceAnim();
1426 OnceAnims
[find_id
].AnimType
:= AnimType
;
1427 OnceAnims
[find_id
].Animation
:= TAnimation
.Create(Anim
.FramesID
, Anim
.Loop
, Anim
.Speed
);
1428 OnceAnims
[find_id
].Animation
.Blending
:= Anim
.Blending
;
1429 OnceAnims
[find_id
].Animation
.alpha
:= Anim
.alpha
;
1430 OnceAnims
[find_id
].x
:= x
;
1431 OnceAnims
[find_id
].y
:= y
;
1435 // ////////////////////////////////////////////////////////////////////////// //
1439 procedure g_Mark(x
, y
, Width
, Height
: Integer; t
: Byte; st
: Boolean=true);
1441 cx
, ex
, ey
: Integer;
1444 if (Width
< 1) or (Height
< 1) then exit
;
1445 // make some border, so we'll hit particles lying around the panel
1447 y
-= 1; Height
+= 2;
1450 ts
:= mapGrid
.tileSize
;
1464 // ////////////////////////////////////////////////////////////////////////// //
1465 {$IF DEFINED(HAS_COLLIDE_BITMAP)}
1466 procedure CreateCollideMap();
1470 //g_Game_SetLoadingText(_lc[I_LOAD_COLLIDE_MAP]+' 1/6', 0, False);
1471 //SetLength(gCollideMap, gMapInfo.Height+1);
1472 //for a := 0 to High(gCollideMap) do SetLength(gCollideMap[a], gMapInfo.Width+1);
1477 procedure g_GFX_Init();
1479 //CreateCollideMap();
1482 gpart_dbg_enabled
:= False;
1487 procedure g_GFX_Free();
1492 SetLength(Particles
, MaxParticles
);
1493 for a
:= 0 to High(Particles
) do Particles
[a
].die();
1494 CurrentParticle
:= 0;
1496 if (OnceAnims
<> nil) then
1498 for a
:= 0 to High(OnceAnims
) do OnceAnims
[a
].Animation
.Free();
1509 // ////////////////////////////////////////////////////////////////////////// //
1510 procedure g_GFX_Update ();
1516 if not gpart_dbg_enabled
then exit
;
1518 if (Particles
<> nil) then
1520 w
:= gMapInfo
.Width
;
1521 h
:= gMapInfo
.Height
;
1523 len
:= High(Particles
);
1525 for a
:= 0 to len
do
1527 if Particles
[a
].alive
then
1529 with Particles
[a
] do
1531 if (time
= liveTime
) then begin die(); continue
; end;
1532 if (x
+1 >= w
) or (y
+1 >= h
) or (x
<= 0) or (y
<= 0) then begin die(); end;
1533 //if not alive then Continue;
1534 //e_WriteLog(Format('particle #%d: %d', [State, ParticleType]), MSG_NOTIFY);
1539 end; // Particles <> nil
1544 if OnceAnims
<> nil then
1546 for a
:= 0 to High(OnceAnims
) do
1547 if OnceAnims
[a
].Animation
<> nil then
1549 case OnceAnims
[a
].AnimType
of
1552 if Random(3) = 0 then
1553 OnceAnims
[a
].x
:= OnceAnims
[a
].x
-1+Random(3);
1554 if Random(2) = 0 then
1555 OnceAnims
[a
].y
:= OnceAnims
[a
].y
-Random(2);
1559 if OnceAnims
[a
].Animation
.Played
then
1561 OnceAnims
[a
].Animation
.Free();
1562 OnceAnims
[a
].Animation
:= nil;
1565 OnceAnims
[a
].Animation
.Update();
1571 procedure g_GFX_Draw ();
1575 if Particles
<> nil then
1577 glDisable(GL_TEXTURE_2D
);
1581 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
);
1585 len
:= High(Particles
);
1587 for a
:= 0 to len
do
1588 with Particles
[a
] do
1589 if alive
and (x
>= sX
) and (y
>= sY
) and (x
<= sX
+sWidth
) and (sY
<= sY
+sHeight
) then
1591 glColor4ub(red
, green
, blue
, alpha
);
1592 glVertex2i(x
+ offsetX
, y
+ offsetY
);
1597 glDisable(GL_BLEND
);
1600 if OnceAnims
<> nil then
1601 for a
:= 0 to High(OnceAnims
) do
1602 if OnceAnims
[a
].Animation
<> nil then
1603 with OnceAnims
[a
] do
1604 Animation
.Draw(x
, y
, M_NONE
);