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 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
, 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;
100 red
, green
, blue
: Byte;
102 time
, liveTime
: Word;
104 particleType
: TPartType
;
105 offsetX
, offsetY
: ShortInt;
107 liquidTopY
: Integer; // don't float higher than this
109 stickDX
: Integer; // STATE_STICK: -1,1: stuck to a wall; 0: stuck to ceiling
110 justSticked
: Boolean; // not used
111 floorY
: Integer; // actually, floor-1; `Unknown`: unknown
112 floorType
: TFloorType
;
113 env
: TEnvType
; // where particle is now
114 ceilingY
: Integer; // actually, ceiling+1; `Unknown`: unknown
115 wallEndY
: Integer; // if we stuck to a wall, this is where wall ends
121 //k8: sorry, i have to emulate virtual methods this way, 'cause i haet `Object`
122 procedure thinkerBlood ();
123 procedure thinkerSpark ();
124 procedure thinkerBubble ();
125 procedure thinkerWater ();
127 procedure findFloor (force
: Boolean=false); // this updates `floorY` if forced or Unknown
128 procedure findCeiling (force
: Boolean=false); // this updates `ceilingY` if forced or Unknown
130 procedure freeze (); inline; // remove velocities and acceleration
131 procedure sleep (); inline; // switch to sleep mode
133 function isSleeping (): Boolean; inline;
134 procedure awake (); inline;
136 function alive (): Boolean; inline;
137 procedure die (); inline;
138 procedure think (); inline;
144 Animation
: TAnimation
;
149 Particles
: array of TParticle
= nil;
150 OnceAnims
: array of TOnceAnim
= nil;
151 MaxParticles
: Integer = 0;
152 CurrentParticle
: Integer = 0;
153 // awakeMap has one bit for each map grid cell; on g_Mark,
154 // corresponding bits will be set, and in `think()` all particles
155 // in marked cells will be awaken
156 awakeMap
: packed array of LongWord = nil;
157 awakeMapH
: Integer = -1;
158 awakeMapW
: Integer = -1;
159 awakeMinX
, awakeMinY
: Integer;
162 // ////////////////////////////////////////////////////////////////////////// //
163 // HACK! using mapgrid
164 procedure awmClear (); inline;
166 if (awakeMapW
> 0) then FillDWord(awakeMap
[0], Length(awakeMap
), 0);
170 procedure awmSetup ();
172 assert(mapGrid
<> nil);
173 awakeMapW
:= (mapGrid
.gridWidth
+mapGrid
.tileSize
-1) div mapGrid
.tileSize
;
174 awakeMapW
:= (awakeMapW
+31) div 32; // LongWord has 32 bits ;-)
175 awakeMapH
:= (mapGrid
.gridHeight
+mapGrid
.tileSize
-1) div mapGrid
.tileSize
;
176 awakeMinX
:= mapGrid
.gridX0
;
177 awakeMinY
:= mapGrid
.gridY0
;
178 SetLength(awakeMap
, awakeMapW
*awakeMapH
);
179 {$IF DEFINED(D2F_DEBUG)}
180 e_LogWritefln('particle awake map: %sx%s (for grid of size %sx%s)', [awakeMapW
, awakeMapH
, mapGrid
.gridWidth
, mapGrid
.gridHeight
]);
186 function awmIsSet (x
, y
: Integer): Boolean; inline;
188 x
:= (x
-awakeMinX
) div mapGrid
.tileSize
;
189 y
:= (y
-awakeMinY
) div mapGrid
.tileSize
;
190 if (x
>= 0) and (y
>= 0) and (x
div 32 < awakeMapW
) and (y
< awakeMapH
) then
192 {$IF DEFINED(D2F_DEBUG)}
193 assert(y
*awakeMapW
+x
div 32 < Length(awakeMap
));
195 result
:= ((awakeMap
[y
*awakeMapW
+x
div 32] and (LongWord(1) shl (x
mod 32))) <> 0);
204 procedure awmSet (x
, y
: Integer); inline;
208 x
:= (x
-awakeMinX
) div mapGrid
.tileSize
;
209 y
:= (y
-awakeMinY
) div mapGrid
.tileSize
;
210 if (x
>= 0) and (y
>= 0) and (x
div 32 < awakeMapW
) and (y
< awakeMapH
) then
212 {$IF DEFINED(D2F_DEBUG)}
213 assert(y
*awakeMapW
+x
div 32 < Length(awakeMap
));
215 v
:= @awakeMap
[y
*awakeMapW
+x
div 32];
216 v
^ := v
^ or (LongWord(1) shl (x
mod 32));
221 // ////////////////////////////////////////////////////////////////////////// //
222 function TParticle
.alive (): Boolean; inline; begin result
:= (state
<> TPartState
.Free
); end;
223 procedure TParticle
.die (); inline; begin state
:= TPartState
.Free
; end;
225 function TParticle
.isSleeping (): Boolean; inline;
227 result
:= alive
and (onGround
or (not justSticked
and (state
= TPartState
.Stuck
)));
230 procedure TParticle
.awake (); inline;
232 if {alive and} (onGround
or (not justSticked
and (state
= TPartState
.Stuck
))) then
234 // wakeup this particle
236 if (part.ParticleType = PARTICLE_SPARK) then
238 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]);
241 justSticked
:= true; // so sticked state will be re-evaluated
244 if (velY
= 0) then velY
:= 0.1;
245 if (accelY
= 0) then accelY
:= 0.5;
247 onGround
:= false; // so onground state will be re-evaluated
253 // remove velocities and acceleration
254 procedure TParticle
.freeze (); inline;
256 // stop right there, you criminal scum!
264 // switch to sleep mode
265 procedure TParticle
.sleep (); inline;
267 state
:= TPartState
.Sleeping
;
272 procedure TParticle
.findFloor (force
: Boolean=false);
277 if (not force
) and (floorY
<> Unknown
) then exit
;
278 // stuck in the wall? rescan, 'cause it can be mplat
279 if (env
= TEnvType
.EWall
) then
281 pan
:= g_Map_PanelAtPoint(x
, y
, (GridTagObstacle
or GridTagLiquid
));
284 // either in a wall, or in a liquid
285 if ((pan
.tag
and GridTagObstacle
) <> 0) then
287 // we are in the wall, wtf?!
289 env
:= TEnvType
.EWall
;
290 floorType
:= TFloorType
.Wall
;
291 state
:= TPartState
.Sleeping
; // anyway
294 // we are in liquid, trace to liquid end
295 env
:= TEnvType
.ELiquid
;
298 // are we in a liquid?
299 if (env
= TEnvType
.ELiquid
) then
301 // trace out of the liquid
302 //env := TEnvType.ELiquid;
303 floorType
:= TFloorType
.LiquidOut
;
304 //e_LogWritefln('tracing out of a liquid; floorY=%s; y=%s', [floorY, y]);
305 mapGrid
.traceOrthoRayWhileIn(ex
, floorY
, x
, y
, x
, g_Map_MaxY
, GridTagLiquid
);
306 floorY
+= 1; // so `floorY` is just out of a liquid
307 //e_LogWritefln(' traced out of a liquid; floorY=%s; y=%s', [floorY, y]);
312 assert(env
= TEnvType
.EAir
);
313 //env := TEnvType.EAir;
314 pan
:= g_Map_traceToNearest(x
, y
, x
, g_Map_MaxY
, (GridTagObstacle
or GridTagLiquid
), @ex
, @floorY
);
318 if ((pan
.tag
and GridTagObstacle
) <> 0) then
321 floorType
:= TFloorType
.Wall
;
326 floorType
:= TFloorType
.LiquidIn
; // entering liquid
327 floorY
+= 1; // so `floorY` is just in a liquid
332 // out of the level; assume wall, but it doesn't really matter
333 floorType
:= TFloorType
.Wall
;
334 floorY
:= g_Map_MaxY
+2;
340 procedure TParticle
.findCeiling (force
: Boolean=false);
344 if (not force
) and (ceilingY
<> Unknown
) then exit
;
345 if (nil = g_Map_traceToNearest(x
, y
, x
, g_Map_MinY
, GridTagObstacle
, @ex
, @ceilingY
)) then
347 ceilingY
:= g_Map_MinY
-2;
352 procedure TParticle
.think (); inline;
354 // awake sleeping particle, if necessary
355 if (state
= TPartState
.Sleeping
) and awmIsSet(x
, y
) then state
:= TPartState
.Normal
;
357 TPartType
.Blood
: thinkerBlood();
358 //TPartType.Spark: thinkerSpark();
359 //TPartType.Bubbles: thinkerBubble();
360 //TPartType.Water: thinkerWater();
365 // ////////////////////////////////////////////////////////////////////////// //
366 procedure TParticle
.thinkerBlood ();
367 procedure stickToCeiling ();
369 state
:= TPartState
.Stuck
;
372 ceilingY
:= y
; // yep
375 procedure stickToWall (dx
: Integer);
379 state
:= TPartState
.Stuck
;
380 if (dX
> 0) then stickDX
:= 1 else stickDX
:= -1;
382 // find next floor transition
385 mapGrid
.traceOrthoRayWhileIn(ex
, wallEndY
, x
+stickDX
, y
, x
+stickDX
, floorY
+1, (GridTagWall
or GridTagDoor
or GridTagStep
));
386 //if (wallEndY > floorY) then wallEndY := floorY; // just in case
389 procedure hitAFloor ();
391 state
:= TPartState
.Sleeping
; // we aren't moving anymore
394 floorType
:= TFloorType
.Wall
; // yep
397 // `true`: didn't, get outa thinker
398 function drip (): Boolean;
400 result
:= (Random(200) = 100);
401 if result
then begin velY
:= 0.5; accelY
:= 0.15; end;
404 // `true`: affected by air stream
405 function checkAirStreams (): Boolean;
409 pan
:= g_Map_PanelAtPoint(x
, y
, GridTagLift
);
410 result
:= (pan
<> nil);
413 if ((pan
.PanelType
and PANEL_LIFTUP
) <> 0) then
415 if (velY
> -4-Random(3)) then velY
-= 0.8;
416 if (abs(velX
) > 0.1) then velX
-= velX
/10.0;
417 velX
+= (Random
-Random
)*0.2;
420 else if ((pan
.PanelType
and PANEL_LIFTLEFT
) <> 0) then
422 if (velX
> -8-Random(3)) then velX
-= 0.8;
425 else if ((pan
.PanelType
and PANEL_LIFTRIGHT
) <> 0) then
427 if (velX
< 8+Random(3)) then velX
+= 0.8;
435 if result
and (state
= TPartState
.Sleeping
) then state
:= TPartState
.Normal
;
439 // switch to freefall mode
440 procedure freefall ();
442 state
:= TPartState
.Normal
;
447 procedure applyGravity (inLiquid
: Boolean);
449 state
:= TPartState
.Normal
;
471 // still check for air streams when sleeping
472 if (state
= TPartState
.Sleeping
) then begin checkAirStreams(); goto _done
; end; // so blood will dissolve
474 // process stuck particles
475 if (state
= TPartState
.Stuck
) then
477 // stuck to a ceiling?
478 if (stickDX
= 0) then
480 // yeah, stuck to a ceiling
481 assert(ceilingY
<> Unknown
);
482 // dropped from a ceiling?
483 if (y
> ceilingY
) then
488 state
:= TPartState
.Normal
;
492 // otherwise, try to drip
493 if drip() then goto _done
;
499 assert(wallEndY
<> Unknown
);
504 TFloorType
.Wall
: // hit the ground
507 goto _done
; // nothing to do anymore
509 TFloorType
.LiquidIn
: // entering the liquid
511 // rescan, so we'll know when we'll exit the liquid
512 findFloor(true); // force rescan
514 TFloorType
.LiquidOut
: // exiting the liquid
516 // rescan, so we'll know when we'll enter something interesting
517 findFloor(true); // force rescan
518 if (floorType
= TFloorType
.Wall
) and (floorY
= y
) then begin sleep(); goto _done
; end;
523 if (y
= wallEndY
) then
525 // just unstuck from the wall, switch to freefall mode
530 // otherwise, try to drip
531 if drip() then goto _done
;
534 // nope, process as usual
537 // it is important to have it here
541 // gravity, if not stuck
542 if (state
<> TPartState
.Stuck
) and (abs(velX
) < 0.1) and (abs(velY
) < 0.1) then
544 if (floorY
= Unknown
) then findFloor();
549 TFloorType
.Wall
: // hit the ground
553 TFloorType
.LiquidIn
: // entering the liquid
555 // rescan, so we'll know when we'll exit the liquid
556 findFloor(true); // force rescan
559 TFloorType
.LiquidOut
: // exiting the liquid
561 // rescan, so we'll know when we'll enter something interesting
562 findFloor(true); // force rescan
563 if (floorType
<> TFloorType
.Wall
) or (floorY
<> y
) then applyGravity(floorType
= TFloorType
.LiquidIn
);
569 // looks like we're in the air
577 // has some horizontal velocity
578 pan
:= g_Map_traceToNearest(x
, y
, x
+dX
, y
+dY
, GridTagObstacle
, @ex
, @ey
);
579 if (x
<> ex
) then begin floorY
:= Unknown
; ceilingY
:= Unknown
; end; // dunno yet
582 if (x
< g_Map_MinX
) or (x
> g_Map_MaxX
) then begin die(); exit
; end;
586 // the only case when we can have both ceiling and wall is corner; stick to wall in this case
587 // check environment (air/liquid)
588 if (g_Map_PanelAtPoint(x
, y
, GridTagLiquid
) <> nil) then env
:= TEnvType
.ELiquid
else env
:= TEnvType
.EAir
;
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 // environmend didn't changed
619 if (floorY
= Unknown
) then findFloor(); // need to do this anyway
621 //e_LogWritefln('floorY=%s; newy=%s; dY=%s; floorType=%s', [floorY, y, dY, floorType]);
622 if (y
>= floorY
) then
627 //e_LogWritefln(' HIT FLOORY: floorY=%s; newy=%s; dY=%s; floorType=%s', [floorY, y, dY, floorType]);
629 TFloorType
.Wall
: // hit the ground
631 // environmend didn't changed
633 break
; // done with vertical movement
635 TFloorType
.LiquidIn
: // entering the liquid
637 // we're entered the liquid
638 env
:= TEnvType
.ELiquid
;
639 // rescan, so we'll know when we'll exit the liquid
640 findFloor(true); // force rescan
642 TFloorType
.LiquidOut
: // exiting the liquid
644 // we're exited the liquid
645 env
:= TEnvType
.EAir
;
646 // rescan, so we'll know when we'll enter something interesting
647 findFloor(true); // force rescan
648 if (floorType
= TFloorType
.Wall
) and (floorY
= y
) then
651 break
; // done with vertical movement
658 break
; // done with vertical movement
671 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;
672 if (g_Map_PanelAtPoint(x
, y
, GridTagObstacle
) <> nil) then begin die(); exit
; end;
679 // blood will dissolve in other liquids
680 if (env
= TEnvType
.ELiquid
) then
683 ex
:= 255-trunc((255.0*time
)/liveTime
);
684 if (ex
>= 255) then begin die(); exit
; end;
685 if (ex
< 0) then ex
:= 0;
691 procedure g_GFX_SparkVel (fX
, fY
: Integer; count
: Word; VX
, VY
: Integer; devX
, devY
: Byte); forward;
693 procedure g_GFX_Blood (fX
, fY
: Integer; count
: Word; vx
, vy
: Integer;
694 devX
, devY
: Word; cr
, cg
, cb
: Byte; kind
: Byte = BLOOD_NORMAL
);
696 function genColor (cbase
, crnd
: Integer; def
: Byte=0): Byte;
701 if (cbase
< 0) then result
:= 0
702 else if (cbase
> 255) then result
:= 255
703 else result
:= Byte(cbase
);
713 devX1
, devX2
, devY1
, devY2
: Word;
718 if not gpart_dbg_enabled
then Exit
;
720 if (kind
= BLOOD_SPARKS
) then
722 g_GFX_SparkVel(fX
, fY
, 2+Random(2), -VX
div 2, -VY
div 2, devX
, devY
);
726 l
:= Length(Particles
);
727 if (l
= 0) then exit
;
728 if (count
> l
) then count
:= l
;
735 for a
:= 1 to count
do
737 with Particles
[CurrentParticle
] do
739 x
:= fX
-devX1
+Random(devX2
);
740 y
:= fY
-devY1
+Random(devY2
);
742 // check for level bounds
743 if (x
< g_Map_MinX
) or (y
< g_Map_MinY
) or (x
> g_Map_MaxX
) or (y
> g_Map_MaxY
) then continue
;
745 // in what environment we are starting in?
746 pan
:= g_Map_PanelAtPoint(x
, y
, (GridTagObstacle
or GridTagLiquid
));
749 // either in a wall, or in a liquid
750 if ((pan
.tag
and GridTagObstacle
) <> 0) then continue
; // don't spawn in walls
751 env
:= TEnvType
.ELiquid
;
755 env
:= TEnvType
.EAir
;
758 velX
:= vx
+(Random
-Random
)*3;
759 velY
:= vy
+(Random
-Random
)*3;
763 if (velY
-4 < -4) then velY
:= -4 else velY
:= velY
-4;
766 accelX
:= -sign(velX
)*Random
/100;
769 crnd
:= 20*Random(6)-50;
771 red
:= genColor(cr
, CRnd
, 0);
772 green
:= genColor(cg
, CRnd
, 0);
773 blue
:= genColor(cb
, CRnd
, 0);
776 particleType
:= TPartType
.Blood
;
777 state
:= TPartState
.Normal
;
779 liveTime
:= 120+Random(40);
784 if (CurrentParticle
>= MaxParticles
-1) then CurrentParticle
:= 0 else CurrentParticle
+= 1;
789 // ////////////////////////////////////////////////////////////////////////// //
790 function isBlockedAt (x
, y
: Integer): Boolean; inline;
792 if not gpart_dbg_phys_enabled
then begin result
:= false; exit
; end;
793 result
:= g_Map_HasAnyPanelAtPoint(x
, y
, (PANEL_WALL
or PANEL_OPENDOOR
or PANEL_CLOSEDOOR
or PANEL_STEP
));
797 function isWallAt (x
, y
: Integer): Boolean; inline;
799 if not gpart_dbg_phys_enabled
then begin result
:= false; exit
; end;
800 result
:= g_Map_HasAnyPanelAtPoint(x
, y
, (PANEL_WALL
or PANEL_STEP
));
803 function isLiftUpAt (x
, y
: Integer): Boolean; inline;
805 if not gpart_dbg_phys_enabled
then begin result
:= false; exit
; end;
806 result
:= g_Map_HasAnyPanelAtPoint(x
, y
, PANEL_LIFTUP
);
809 function isLiftDownAt (x
, y
: Integer): Boolean; inline;
811 if not gpart_dbg_phys_enabled
then begin result
:= false; exit
; end;
812 result
:= g_Map_HasAnyPanelAtPoint(x
, y
, PANEL_LIFTDOWN
);
815 function isLiftLeftAt (x
, y
: Integer): Boolean; inline;
817 if not gpart_dbg_phys_enabled
then begin result
:= false; exit
; end;
818 result
:= g_Map_HasAnyPanelAtPoint(x
, y
, PANEL_LIFTLEFT
);
821 function isLiftRightAt (x
, y
: Integer): Boolean; inline;
823 if not gpart_dbg_phys_enabled
then begin result
:= false; exit
; end;
824 result
:= g_Map_HasAnyPanelAtPoint(x
, y
, PANEL_LIFTRIGHT
);
827 function isLiquidAt (x
, y
: Integer): Boolean; inline;
829 if not gpart_dbg_phys_enabled
then begin result
:= false; exit
; end;
830 result
:= g_Map_HasAnyPanelAtPoint(x
, y
, (PANEL_WATER
or PANEL_ACID1
or PANEL_ACID2
));
833 function isAnythingAt (x
, y
: Integer): Boolean; inline;
835 if not gpart_dbg_phys_enabled
then begin result
:= false; exit
; end;
836 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
));
840 // ////////////////////////////////////////////////////////////////////////// //
841 procedure TParticle
.thinkerWater ();
844 {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
853 {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
855 h
:= gMapInfo
.Height
;
858 //TODO: trace wall end when water becomes stick
859 if (state
= TPartState
.Stuck
) and (Random(30) = 15) then
860 begin // Ñòåêàåò/îòëèïàåò
863 {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
864 if (not isBlockedAt(x
-1, y
) {ByteBool(gCollideMap[Y, X-1] and MARK_BLOCKED)}) and
865 (not isBlockedAt(x
+1, y
) {ByteBool(gCollideMap[Y, X+1] and MARK_BLOCKED)}) then
866 state
:= TPartState
.Normal
;
868 if (stickDX
= 0) then
870 // no walls around, drop
871 state
:= TPartState
.Normal
;
877 if not mapGrid
.traceOrthoRayWhileIn(ex
, ey
, x
+stickDX
, y
, x
+stickDX
, mapGrid
.gridY0
+mapGrid
.gridHeight
, GridTagWall
or GridTagDoor
or GridTagStep
) then
880 state
:= TPartState
.Normal
;
881 //e_LogWritefln('juststicked unsticked: X=%s; X+stickDX=%s; stickDX=%s; Y=%s', [X, X+stickDX, stickDX, Y]);
886 justSticked
:= false;
887 if (nil <> g_Map_traceToNearest(x
, y
, x
, stickEY
, (GridTagWall
or GridTagDoor
or GridTagStep
or GridTagAcid1
or GridTagAcid2
or GridTagWater
), @ex
, @ey
)) then
889 if (ey
> stickEY
) then stickEY
:= ey
-1;
891 //e_LogWritefln('juststicked: X=%s; X+stickDX=%s; stickDX=%s; Y=%s; stickEY=%s', [X, X+stickDX, stickDX, Y, stickEY]);
896 if (y
>= stickEY
) then state
:= TPartState
.Normal
;
898 //if not g_Map_CollidePanel(X-1, Y-1, 3, 3, (PANEL_STEP or PANEL_WALL or PANEL_OPENDOOR or PANEL_CLOSEDOOR))
904 {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
905 if not isBlockedAt(x
, y
) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)} then
907 if isLiftUpAt(x
, y
) {ByteBool(gCollideMap[Y, X] and MARK_LIFTUP)} then
909 if velY
> -4-Random(3) then
911 if Abs(velX
) > 0.1 then
912 velX
:= velX
- velX
/10.0;
913 velX
:= velX
+ (Random
-Random
)*0.2;
916 if isLiftLeftAt(x
, y
) {ByteBool(gCollideMap[Y, X] and MARK_LIFTLEFT)} then
918 if velX
> -8-Random(3) then
922 if isLiftRightAt(x
, y
) {ByteBool(gCollideMap[Y, X] and MARK_LIFTRIGHT)} then
923 begin // Ïîòîê âïðàâî
924 if velX
< 8+Random(3) then
930 pan
:= g_Map_PanelAtPoint(x
, y
, (GridTagAcid1
or GridTagAcid2
or GridTagWater
or GridTagLift
));
933 if ((pan
.tag
and (GridTagAcid1
or GridTagAcid2
or GridTagWater
)) <> 0) then begin die(); exit
; end;
934 if ((pan
.PanelType
and PANEL_LIFTUP
) <> 0) then
936 if (velY
> -4-Random(3)) then velY
-= 0.8;
937 if (Abs(velX
) > 0.1) then velX
-= velX
/10.0;
938 velX
+= (Random
-Random
)*0.2;
941 if ((pan
.PanelType
and PANEL_LIFTLEFT
) <> 0) then
943 if (velX
> -8-Random(3)) then velX
-= 0.8;
946 if ((pan
.PanelType
and PANEL_LIFTRIGHT
) <> 0) then
948 if (velX
< 8+Random(3)) then velX
+= 0.8;
957 {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
958 if (Abs(velX
) < 0.1) and (Abs(velY
) < 0.1) then
960 if (state
<> TPartState
.Stuck
) and
961 (not isBlockedAt(x
, y
-1) {ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)}) and
962 (not isBlockedAt(x
, y
) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)}) and
963 (not isBlockedAt(x
, y
+1) {ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)}) then
964 begin // Âèñèò â âîçäóõå - êàïàåò
967 state
:= TPartState
.Normal
;
971 if (state
<> TPartState
.Stuck
) and (Abs(velX
) < 0.1) and (Abs(velY
) < 0.1) then
973 // Âèñèò â âîçäóõå - êàïàåò
974 if (nil = g_Map_traceToNearest(x
, y
-1, x
, y
+1, (GridTagWall
or GridTagDoor
or GridTagStep
or GridTagAcid1
or GridTagAcid2
or GridTagWater
), @ex
, @ey
)) then
978 state
:= TPartState
.Normal
;
983 {$IF DEFINED(D2F_NEW_SPARK_THINKER)}
987 pan
:= g_Map_traceToNearest(x
, y
, x
+dX
, y
, (GridTagWall
or GridTagDoor
or GridTagStep
or GridTagAcid1
or GridTagAcid2
or GridTagWater
), @ex
, @ey
);
988 if (x
<> ex
) then onGround
:= false;
994 if (dY
> 0) and ((pan
.tag
and (GridTagAcid1
or GridTagAcid2
or GridTagWater
)) <> 0) then begin die(); exit
; end;
996 if ((pan
.tag
and (GridTagWall
or GridTagDoor
or GridTagStep
)) <> 0) then
1002 state
:= TPartState
.Stuck
;
1003 justSticked
:= true;
1004 if (dX
> 0) then stickDX
:= 1 else stickDX
:= -1;
1007 if (x
< 0) or (x
>= gMapInfo
.Width
) then begin die(); exit
; end;
1012 if (dY
< 0) or not onGround
then
1014 pan
:= g_Map_traceToNearest(x
, y
, x
, y
+dY
, (GridTagWall
or GridTagDoor
or GridTagStep
or GridTagAcid1
or GridTagAcid2
or GridTagWater
), @ex
, @ey
);
1017 if (pan
<> nil) then
1020 if (dY
> 0) and ((pan
.tag
and (GridTagAcid1
or GridTagAcid2
or GridTagWater
)) <> 0) then begin die(); exit
; end;
1022 if ((pan
.tag
and (GridTagWall
or GridTagDoor
or GridTagStep
)) <> 0) then
1028 if (dY
> 0) and (state
<> TPartState
.Stuck
) then
1030 state
:= TPartState
.Normal
;
1034 state
:= TPartState
.Stuck
;
1035 if (g_Map_PanelAtPoint(x
-1, y
, (GridTagWall
or GridTagDoor
or GridTagStep
)) <> nil) then stickDX
:= -1
1036 else if (g_Map_PanelAtPoint(x
+1, y
, (GridTagWall
or GridTagDoor
or GridTagStep
)) <> nil) then stickDX
:= 1
1038 justSticked
:= true;
1042 onGround
:= (velY
>= 0) and g_Map_HasAnyPanelAtPoint(x
, y
+1, (PANEL_WALL
or PANEL_CLOSEDOOR
or PANEL_STEP
));
1044 if (y
< 0) or (y
>= gMapInfo
.Height
) then begin die(); exit
; end;
1050 if (dX
> 0) then s
:= 1 else s
:= -1;
1051 for b
:= 1 to Abs(dX
) do
1054 if (x
+s
>= w
) or (x
+s
<= 0) then begin die(); break
;end;
1055 //c := gCollideMap[Y, X+s];
1056 // Ñáîêó æèäêîñòü, à ÷àñòèöà óæå ïàäàåò?
1057 if isLiquidAt(x
+s
, y
) {ByteBool(c and MARK_LIQUID)} and (dY
> 0) then begin die(); break
; end;
1058 if isBlockedAt(x
+s
, y
) {ByteBool(c and MARK_BLOCKED)} then
1059 begin // Ñòåíà/äâåðü
1064 state
:= TPartState
.Stuck
;
1065 justSticked
:= true;
1074 if (dY
> 0) then s
:= 1 else s
:= -1;
1075 for b
:= 1 to Abs(dY
) do
1077 // Ñíèçó/ñâåðõó ãðàíèöà
1078 if (y
+s
>= h
) or (y
+s
<= 0) then begin die(); break
; end;
1079 //c := gCollideMap[Y+s, X];
1080 // Ñíèçó æèäêîñòü, à ÷àñòèöà óæå ïàäàåò
1081 if isLiquidAt(x
, y
+s
) {ByteBool(c and MARK_LIQUID)} and (dY
> 0) then begin die(); break
; end;
1082 if isBlockedAt(x
, y
+s
) {ByteBool(c and MARK_BLOCKED)} then
1083 begin // Ñòåíà/äâåðü
1088 if (s
> 0) and (state
<> TPartState
.Stuck
) then state
:= TPartState
.Normal
else state
:= TPartState
.Stuck
;
1089 justSticked
:= (state
= TPartState
.Stuck
);
1104 // ////////////////////////////////////////////////////////////////////////// //
1105 procedure TParticle
.thinkerSpark ();
1108 {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
1119 {$IF DEFINED(D2F_NEW_SPARK_THINKER)}
1120 if (Abs(velX
) < 0.1) and (Abs(velY
) < 0.1) then
1122 pan
:= g_Map_traceToNearest(x
, y
-1, x
, y
+1, (GridTagWall
or GridTagDoor
or GridTagStep
or GridTagAcid1
or GridTagAcid2
or GridTagWater
), @ex
, @ey
);
1125 if (Abs(velX
) < 0.1) and (Abs(velY
) < 0.1) and
1126 (not isBlockedAt(x
, y
-1) {ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)}) and
1127 (not isBlockedAt(x
, y
) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)}) and
1128 (not isBlockedAt(x
, y
+1) {ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)}) then
1129 begin // Âèñèò â âîçäóõå
1137 {$IF DEFINED(D2F_NEW_SPARK_THINKER)}
1138 pan
:= g_Map_traceToNearest(x
, y
, x
+dX
, y
, (GridTagWall
or GridTagDoor
or GridTagStep
or GridTagAcid1
or GridTagAcid2
or GridTagWater
), @ex
, @ey
);
1139 //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);
1140 if (x
<> ex
) then onGround
:= false;
1143 if (pan
<> nil) then
1146 if ((pan
.tag
and (GridTagAcid1
or GridTagAcid2
or GridTagWater
)) <> 0) then begin die(); exit
; end;
1150 if (x
< 0) or (x
>= gMapInfo
.Width
) then begin die(); exit
; end;
1152 if (dX
> 0) then s
:= 1 else s
:= -1;
1156 if (x
+s
>= gMapInfo
.Width
) or (x
+s
<= 0) then begin die(); break
; end;
1157 //c := gCollideMap[Y, X+s];
1158 if isBlockedAt(x
+s
, y
) {ByteBool(c and MARK_BLOCKED)} then
1159 begin // Ñòåíà/äâåðü - ïàäàåò âåðòèêàëüíî
1165 if not isAnythingAt(x
+s
, y
) {c = MARK_FREE} then
1178 {$IF DEFINED(D2F_NEW_SPARK_THINKER)}
1179 if (dY
< 0) or not onGround
then
1181 pan
:= g_Map_traceToNearest(x
, y
, x
, y
+dY
, (GridTagWall
or GridTagDoor
or GridTagStep
or GridTagAcid1
or GridTagAcid2
or GridTagWater
), @ex
, @ey
);
1187 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]);
1191 if (pan
<> nil) then
1194 if ((pan
.tag
and (GridTagAcid1
or GridTagAcid2
or GridTagWater
)) <> 0) then begin die(); exit
; end;
1198 accelY
:= abs(accelY
);
1208 onGround
:= (velY
>= 0) and g_Map_HasAnyPanelAtPoint(x
, y
+1, (PANEL_WALL
or PANEL_CLOSEDOOR
or PANEL_STEP
));
1210 if (y
< 0) or (y
>= gMapInfo
.Height
) then begin die(); exit
; end;
1212 if (dY
> 0) then s
:= 1 else s
:= -1;
1216 if (y
+s
>= gMapInfo
.Height
) or (y
+s
<= 0) then begin die(); break
; end;
1217 //c := gCollideMap[Y+s, X];
1218 if isBlockedAt(x
, y
+s
) {ByteBool(c and MARK_BLOCKED)} then
1219 begin // Ñòåíà/äâåðü - ïàäàåò âåðòèêàëüíî
1223 accelY
:= Abs(accelY
);
1225 else // Èëè íå ïàäàåò
1236 if not isAnythingAt(x
, y
+s
) {c = MARK_FREE} then
1247 if (velX
<> 0.0) then velX
+= accelX
;
1249 if (velY
<> 0.0) then
1251 if (accelY
< 10) then accelY
+= 0.08;
1258 // ////////////////////////////////////////////////////////////////////////// //
1259 procedure TParticle
.thinkerBubble ();
1266 h
:= gMapInfo
.Height
;
1277 for b
:= 1 to Abs(dY
) do
1279 if (y
+s
>= h
) or (y
+s
<= 0) then begin die(); break
; end;
1282 if not isLiquidAt(X, Y+s) {ByteBool(gCollideMap[Y+s, X] and MARK_LIQUID)} then
1283 begin // Óæå íå æèäêîñòü
1284 State := STATE_FREE;
1288 // we traced liquid before, so don't bother checking
1289 if (y
+s
<= liquidTopY
) then begin die(); break
; end;
1296 velY
:= velY
+ accelY
;
1302 // ////////////////////////////////////////////////////////////////////////// //
1303 procedure g_GFX_SparkVel (fX
, fY
: Integer; count
: Word; VX
, VY
: Integer; devX
, devY
: Byte);
1311 if not gpart_dbg_enabled
then Exit
;
1312 l
:= Length(Particles
);
1314 if count
> l
then count
:= l
;
1316 devX1
:= devX
div 2;
1318 devY1
:= devY
div 2;
1321 for a
:= 1 to count
do
1323 with Particles
[CurrentParticle
] do
1325 x
:= fX
-devX1
+Random(devX2
);
1326 y
:= fY
-devY1
+Random(devY2
);
1328 velX
:= VX
+ (Random
-Random
)*3;
1329 velY
:= VY
+ (Random
-Random
)*3;
1337 accelX
:= -Sign(velX
)*Random
/100;
1341 green
:= 100+Random(155);
1345 state
:= TPartState
.Normal
;
1347 liveTime
:= 30+Random(60);
1348 particleType
:= TPartType
.Spark
;
1349 justSticked
:= false;
1350 onGround
:= (velY
>= 0) and g_Map_HasAnyPanelAtPoint(x
, y
+1, (PANEL_WALL
or PANEL_CLOSEDOOR
or PANEL_STEP
));
1354 if CurrentParticle
+2 > MaxParticles
then
1355 CurrentParticle
:= 0
1357 CurrentParticle
:= CurrentParticle
+1;
1362 procedure g_GFX_Spark(fX
, fY
: Integer; count
: Word; angle
: SmallInt; devX
, devY
: Byte);
1368 BaseVelX
, BaseVelY
: Single;
1372 if not gpart_dbg_enabled
then Exit
;
1373 l
:= Length(Particles
);
1379 angle
:= 360 - angle
;
1381 devX1
:= devX
div 2;
1383 devY1
:= devY
div 2;
1386 b
:= DegToRad(angle
);
1388 BaseVelY
:= 1.6*sin(b
);
1389 if Abs(BaseVelX
) < 0.01 then
1391 if Abs(BaseVelY
) < 0.01 then
1393 for a
:= 1 to count
do
1395 with Particles
[CurrentParticle
] do
1397 x
:= fX
-devX1
+Random(devX2
);
1398 y
:= fY
-devY1
+Random(devY2
);
1400 velX
:= BaseVelX
*Random
;
1401 velY
:= BaseVelY
-Random
;
1406 green
:= 100+Random(155);
1410 state
:= TPartState
.Normal
;
1412 liveTime
:= 30+Random(60);
1413 particleType
:= TPartType
.Spark
;
1414 justSticked
:= false;
1415 onGround
:= (velY
>= 0) and g_Map_HasAnyPanelAtPoint(x
, y
+1, (PANEL_WALL
or PANEL_CLOSEDOOR
or PANEL_STEP
));
1419 if CurrentParticle
+2 > MaxParticles
then
1420 CurrentParticle
:= 0
1422 CurrentParticle
:= CurrentParticle
+1;
1427 procedure g_GFX_Water(fX
, fY
: Integer; count
: Word; fVelX
, fVelY
: Single; devX
, devY
, Color
: Byte);
1435 if not gpart_dbg_enabled
then Exit
;
1436 l
:= Length(Particles
);
1442 if Abs(fVelX
) < 3.0 then
1443 fVelX
:= 3.0 - 6.0*Random
;
1445 devX1
:= devX
div 2;
1447 devY1
:= devY
div 2;
1450 for a
:= 1 to count
do
1452 with Particles
[CurrentParticle
] do
1454 x
:= fX
-devX1
+Random(devX2
);
1455 y
:= fY
-devY1
+Random(devY2
);
1457 if Abs(fVelX
) < 0.5 then
1458 velX
:= 1.0 - 2.0*Random
1460 velX
:= fVelX
*Random
;
1461 if Random(10) < 7 then
1463 velY
:= fVelY
*Random
;
1470 red
:= 155 + Random(9)*10;
1471 green
:= Trunc(150*Random
);
1476 red
:= Trunc(150*Random
);
1477 green
:= 175 + Random(9)*10;
1482 red
:= Trunc(200*Random
);
1484 blue
:= 175 + Random(9)*10;
1488 red
:= 90 + Random(12)*10;
1496 state
:= TPartState
.Normal
;
1498 liveTime
:= 60+Random(60);
1499 particleType
:= TPartType
.Water
;
1500 justSticked
:= false;
1501 onGround
:= (velY
>= 0) and g_Map_HasAnyPanelAtPoint(x
, y
+1, (PANEL_WALL
or PANEL_CLOSEDOOR
or PANEL_STEP
));
1505 if CurrentParticle
+2 > MaxParticles
then
1506 CurrentParticle
:= 0
1508 CurrentParticle
:= CurrentParticle
+1;
1513 procedure g_GFX_SimpleWater(fX
, fY
: Integer; count
: Word; fVelX
, fVelY
: Single; defColor
, cr
, cg
, cb
: Byte);
1519 if not gpart_dbg_enabled
then Exit
;
1520 l
:= Length(Particles
);
1526 for a
:= 1 to count
do
1528 with Particles
[CurrentParticle
] do
1541 red
:= 155 + Random(9)*10;
1542 green
:= Trunc(150*Random
);
1547 red
:= Trunc(150*Random
);
1548 green
:= 175 + Random(9)*10;
1553 red
:= Trunc(200*Random
);
1555 blue
:= 175 + Random(9)*10;
1557 4: // Ñâîé öâåò, ñâåòëåå
1559 red
:= 20 + Random(19)*10;
1562 red
:= Min(red
+ cr
, 255);
1563 green
:= Min(green
+ cg
, 255);
1564 blue
:= Min(blue
+ cb
, 255);
1566 5: // Ñâîé öâåò, òåìíåå
1568 red
:= 20 + Random(19)*10;
1571 red
:= Max(cr
- red
, 0);
1572 green
:= Max(cg
- green
, 0);
1573 blue
:= Max(cb
- blue
, 0);
1577 red
:= 90 + Random(12)*10;
1585 state
:= TPartState
.Normal
;
1587 liveTime
:= 60+Random(60);
1588 particleType
:= TPartType
.Water
;
1589 justSticked
:= false;
1590 onGround
:= (velY
>= 0) and g_Map_HasAnyPanelAtPoint(x
, y
+1, (PANEL_WALL
or PANEL_CLOSEDOOR
or PANEL_STEP
));
1594 if CurrentParticle
+2 > MaxParticles
then
1595 CurrentParticle
:= 0
1597 CurrentParticle
:= CurrentParticle
+1;
1602 {.$DEFINE D2F_DEBUG_BUBBLES}
1603 procedure g_GFX_Bubbles(fX
, fY
: Integer; count
: Word; devX
, devY
: Byte);
1608 l
, liquidx
: Integer;
1609 {$IF DEFINED(D2F_DEBUG_BUBBLES)}
1615 if not gpart_dbg_enabled
then Exit
;
1616 l
:= Length(Particles
);
1622 devX1
:= devX
div 2;
1624 devY1
:= devY
div 2;
1627 for a
:= 1 to count
do
1629 with Particles
[CurrentParticle
] do
1631 x
:= fX
-devX1
+Random(devX2
);
1632 y
:= fY
-devY1
+Random(devY2
);
1634 if (x
>= gMapInfo
.Width
) or (x
<= 0) or
1635 (y
>= gMapInfo
.Height
) or (y
<= 0) then
1639 // don't spawn bubbles outside of the liquid
1640 if not isLiquidAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIQUID)} then
1644 // trace liquid, so we'll know where it ends; do it in 8px steps for speed
1645 // tracer will return `false` if we started outside of the liquid
1647 {$IF DEFINED(D2F_DEBUG_BUBBLES)}
1648 stt
:= curTimeMicro();
1649 ptr
:= mapGrid
.traceOrthoRayWhileIn(liquidx
, liquidTopY
, x
, y
, x
, 0, GridTagWater
or GridTagAcid1
or GridTagAcid2
);
1650 stt
:= curTimeMicro()-stt
;
1651 e_LogWritefln('traceOrthoRayWhileIn: time=%s (%s); liquidTopY=%s', [Integer(stt
), ptr
, liquidTopY
]);
1653 stt
:= curTimeMicro();
1654 nptr
:= g_Map_TraceLiquidNonPrecise(x
, y
, 0, -8, liquidx
, liquidTopY
);
1655 stt
:= curTimeMicro()-stt
;
1656 e_LogWritefln('g_Map_TraceLiquidNonPrecise: time=%s (%s); liquidTopY=%s', [Integer(stt
), nptr
, liquidTopY
]);
1657 if not nptr
then continue
;
1659 if not g_Map_TraceLiquidNonPrecise(x
, y
, 0, -8, liquidx
, liquidTopY
) then continue
;
1672 state
:= TPartState
.Normal
;
1675 particleType
:= TPartType
.Bubbles
;
1676 justSticked
:= false;
1677 onGround
:= (velY
>= 0) and g_Map_HasAnyPanelAtPoint(x
, y
+1, (PANEL_WALL
or PANEL_CLOSEDOOR
or PANEL_STEP
));
1681 if CurrentParticle
+2 > MaxParticles
then
1682 CurrentParticle
:= 0
1684 CurrentParticle
:= CurrentParticle
+1;
1689 // ////////////////////////////////////////////////////////////////////////// //
1690 procedure g_GFX_SetMax(count
: Integer);
1694 if count
> 50000 then count
:= 50000;
1695 if (count
< 1) then count
:= 1;
1697 SetLength(Particles
, count
);
1698 for a
:= 0 to High(Particles
) do Particles
[a
].die();
1699 MaxParticles
:= count
;
1700 //if CurrentParticle >= Count then
1701 CurrentParticle
:= 0;
1705 function g_GFX_GetMax(): Integer;
1707 Result
:= MaxParticles
;
1711 function FindOnceAnim (): DWORD
;
1715 if OnceAnims
<> nil then
1716 for i
:= 0 to High(OnceAnims
) do
1717 if OnceAnims
[i
].Animation
= nil then
1723 if OnceAnims
= nil then
1725 SetLength(OnceAnims
, 16);
1730 Result
:= High(OnceAnims
) + 1;
1731 SetLength(OnceAnims
, Length(OnceAnims
) + 16);
1736 procedure g_GFX_OnceAnim (x
, y
: Integer; Anim
: TAnimation
; AnimType
: Byte = 0);
1740 if not gpart_dbg_enabled
then Exit
;
1744 find_id
:= FindOnceAnim();
1746 OnceAnims
[find_id
].AnimType
:= AnimType
;
1747 OnceAnims
[find_id
].Animation
:= TAnimation
.Create(Anim
.FramesID
, Anim
.Loop
, Anim
.Speed
);
1748 OnceAnims
[find_id
].Animation
.Blending
:= Anim
.Blending
;
1749 OnceAnims
[find_id
].Animation
.alpha
:= Anim
.alpha
;
1750 OnceAnims
[find_id
].x
:= x
;
1751 OnceAnims
[find_id
].y
:= y
;
1755 // ////////////////////////////////////////////////////////////////////////// //
1759 procedure g_Mark(x
, y
, Width
, Height
: Integer; t
: Byte; st
: Boolean=true);
1761 cx
, ex
, ey
: Integer;
1764 if (Width
< 1) or (Height
< 1) then exit
;
1765 // make some border, so we'll hit particles lying around the panel
1767 y
-= 1; Height
+= 2;
1770 ts
:= mapGrid
.tileSize
;
1784 // ////////////////////////////////////////////////////////////////////////// //
1785 {$IF DEFINED(HAS_COLLIDE_BITMAP)}
1786 procedure CreateCollideMap();
1790 //g_Game_SetLoadingText(_lc[I_LOAD_COLLIDE_MAP]+' 1/6', 0, False);
1791 //SetLength(gCollideMap, gMapInfo.Height+1);
1792 //for a := 0 to High(gCollideMap) do SetLength(gCollideMap[a], gMapInfo.Width+1);
1797 procedure g_GFX_Init();
1799 //CreateCollideMap();
1802 gpart_dbg_enabled
:= False;
1807 procedure g_GFX_Free();
1812 SetLength(Particles
, MaxParticles
);
1813 for a
:= 0 to High(Particles
) do Particles
[a
].die();
1814 CurrentParticle
:= 0;
1816 if (OnceAnims
<> nil) then
1818 for a
:= 0 to High(OnceAnims
) do OnceAnims
[a
].Animation
.Free();
1829 // ////////////////////////////////////////////////////////////////////////// //
1830 procedure g_GFX_Update ();
1836 if not gpart_dbg_enabled
then exit
;
1838 if (Particles
<> nil) then
1840 w
:= gMapInfo
.Width
;
1841 h
:= gMapInfo
.Height
;
1843 len
:= High(Particles
);
1845 for a
:= 0 to len
do
1847 if Particles
[a
].alive
then
1849 with Particles
[a
] do
1851 if (time
= liveTime
) then begin die(); continue
; end;
1852 if (x
+1 >= w
) or (y
+1 >= h
) or (x
<= 0) or (y
<= 0) then begin die(); end;
1853 //if not alive then Continue;
1854 //e_WriteLog(Format('particle #%d: %d', [State, ParticleType]), MSG_NOTIFY);
1859 end; // Particles <> nil
1864 if OnceAnims
<> nil then
1866 for a
:= 0 to High(OnceAnims
) do
1867 if OnceAnims
[a
].Animation
<> nil then
1869 case OnceAnims
[a
].AnimType
of
1872 if Random(3) = 0 then
1873 OnceAnims
[a
].x
:= OnceAnims
[a
].x
-1+Random(3);
1874 if Random(2) = 0 then
1875 OnceAnims
[a
].y
:= OnceAnims
[a
].y
-Random(2);
1879 if OnceAnims
[a
].Animation
.Played
then
1881 OnceAnims
[a
].Animation
.Free();
1882 OnceAnims
[a
].Animation
:= nil;
1885 OnceAnims
[a
].Animation
.Update();
1891 procedure g_GFX_Draw ();
1895 if Particles
<> nil then
1897 glDisable(GL_TEXTURE_2D
);
1901 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
);
1905 len
:= High(Particles
);
1907 for a
:= 0 to len
do
1908 with Particles
[a
] do
1909 if alive
and (x
>= sX
) and (y
>= sY
) and (x
<= sX
+sWidth
) and (sY
<= sY
+sHeight
) then
1911 glColor4ub(red
, green
, blue
, alpha
);
1912 glVertex2i(x
+ offsetX
, y
+ offsetY
);
1917 glDisable(GL_BLEND
);
1920 if OnceAnims
<> nil then
1921 for a
:= 0 to High(OnceAnims
) do
1922 if OnceAnims
[a
].Animation
<> nil then
1923 with OnceAnims
[a
] do
1924 Animation
.Draw(x
, y
, M_NONE
);