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}
39 MARK_BLOCKED
= MARK_WALL
+ MARK_DOOR
;
40 MARK_LIQUID
= MARK_WATER
+ MARK_ACID
;
41 MARK_LIFT
= MARK_LIFTDOWN
+ MARK_LIFTUP
+ MARK_LIFTLEFT
+ MARK_LIFTRIGHT
;
43 procedure g_GFX_Init();
44 procedure g_GFX_Free();
46 procedure g_GFX_Blood(fX
, fY
: Integer; Count
: Word; vx
, vy
: Integer;
47 DevX
, DevY
: Word; CR
, CG
, CB
: Byte; Kind
: Byte = BLOOD_NORMAL
);
48 procedure g_GFX_Spark(fX
, fY
: Integer; Count
: Word; Angle
: SmallInt; DevX
, DevY
: Byte);
49 procedure g_GFX_Water(fX
, fY
: Integer; Count
: Word; fVelX
, fVelY
: Single; DevX
, DevY
, Color
: Byte);
50 procedure g_GFX_SimpleWater(fX
, fY
: Integer; Count
: Word; fVelX
, fVelY
: Single; DefColor
, CR
, CG
, CB
: Byte);
51 procedure g_GFX_Bubbles(fX
, fY
: Integer; Count
: Word; DevX
, DevY
: Byte);
52 procedure g_GFX_SetMax(Count
: Integer);
53 function g_GFX_GetMax(): Integer;
55 procedure g_GFX_OnceAnim(X
, Y
: Integer; Anim
: TAnimation
; AnimType
: Byte = 0);
57 procedure g_Mark(x
, y
, Width
, Height
: Integer; t
: Byte; st
: Boolean);
59 procedure g_GFX_Update();
60 procedure g_GFX_Draw();
64 gpart_dbg_enabled
: Boolean = true;
65 gpart_dbg_phys_enabled
: Boolean = true;
71 g_map
, g_panel
, g_basic
, Math
, e_graphics
, GL
, GLExt
,
72 g_options
, g_console
, SysUtils
, g_triggers
, MAPDEF
,
73 g_game
, g_language
, g_net
;
76 PParticle
= ^TParticle
;
81 AccelX
, AccelY
: Single;
82 Red
, Green
, Blue
: Byte;
87 offsetX
, offsetY
: ShortInt;
89 liquidTopY
: Integer; // don't float higher than this
93 //k8: sorry, i have to emulate virtual methods this way, 'cause i haet `Object`
95 procedure thinkerBlood ();
96 procedure thinkerSpark ();
97 procedure thinkerBubble ();
98 procedure thinkerWater ();
100 function alive (): Boolean; inline;
101 procedure die (); inline;
102 procedure think (); inline;
108 Animation
: TAnimation
;
114 PARTICLE_BUBBLES
= 2;
121 Particles
: array of TParticle
;
122 OnceAnims
: array of TOnceAnim
;
123 MaxParticles
: Integer;
124 CurrentParticle
: Integer;
127 // ////////////////////////////////////////////////////////////////////////// //
128 function TParticle
.alive (): Boolean; inline; begin result
:= (State
<> STATE_FREE
); end;
129 procedure TParticle
.die (); inline; begin State
:= STATE_FREE
; end;
131 procedure TParticle
.think (); inline;
134 PARTICLE_BLOOD
: thinkerBlood();
135 PARTICLE_SPARK
: thinkerSpark();
136 PARTICLE_BUBBLES
: thinkerBubble();
137 PARTICLE_WATER
: thinkerWater();
142 // ////////////////////////////////////////////////////////////////////////// //
143 function isBlockedAt (x
, y
: Integer): Boolean; inline;
145 if not gpart_dbg_phys_enabled
then begin result
:= false; exit
; end;
146 result
:= g_Map_HasAnyPanelAtPoint(x
, y
, (PANEL_WALL
or PANEL_CLOSEDOOR
or PANEL_STEP
));
150 function isWallAt (x
, y
: Integer): Boolean; inline;
152 if not gpart_dbg_phys_enabled
then begin result
:= false; exit
; end;
153 result
:= g_Map_HasAnyPanelAtPoint(x
, y
, (PANEL_WALL
or PANEL_STEP
));
156 function isLiftUpAt (x
, y
: Integer): Boolean; inline;
158 if not gpart_dbg_phys_enabled
then begin result
:= false; exit
; end;
159 result
:= g_Map_HasAnyPanelAtPoint(x
, y
, PANEL_LIFTUP
);
162 function isLiftDownAt (x
, y
: Integer): Boolean; inline;
164 if not gpart_dbg_phys_enabled
then begin result
:= false; exit
; end;
165 result
:= g_Map_HasAnyPanelAtPoint(x
, y
, PANEL_LIFTDOWN
);
168 function isLiftLeftAt (x
, y
: Integer): Boolean; inline;
170 if not gpart_dbg_phys_enabled
then begin result
:= false; exit
; end;
171 result
:= g_Map_HasAnyPanelAtPoint(x
, y
, PANEL_LIFTLEFT
);
174 function isLiftRightAt (x
, y
: Integer): Boolean; inline;
176 if not gpart_dbg_phys_enabled
then begin result
:= false; exit
; end;
177 result
:= g_Map_HasAnyPanelAtPoint(x
, y
, PANEL_LIFTRIGHT
);
180 function isLiquidAt (x
, y
: Integer): Boolean; inline;
182 if not gpart_dbg_phys_enabled
then begin result
:= false; exit
; end;
183 result
:= g_Map_HasAnyPanelAtPoint(x
, y
, (PANEL_WATER
or PANEL_ACID1
or PANEL_ACID2
));
186 function isAnythingAt (x
, y
: Integer): Boolean; inline;
188 if not gpart_dbg_phys_enabled
then begin result
:= false; exit
; end;
189 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
));
193 procedure g_Mark(x
, y
, Width
, Height
: Integer; t
: Byte; st
: Boolean);
194 {$IF not DEFINED(HAS_COLLIDE_BITMAP)}
199 yy
, y2
, xx
, x2
: Integer;
212 Height
:= Height
+ y
;
219 if x
> gMapInfo
.Width
then
221 if y
> gMapInfo
.Height
then
224 y2
:= y
+ Height
- 1;
225 if y2
> gMapInfo
.Height
then
226 y2
:= gMapInfo
.Height
;
229 if x2
> gMapInfo
.Width
then
230 x2
:= gMapInfo
.Width
;
233 begin // Óñòàíîâèòü ïðèçíàê
236 gCollideMap
[yy
][xx
] := gCollideMap
[yy
][xx
] or t
;
239 begin // Óáðàòü ïðèçíàê
243 gCollideMap
[yy
][xx
] := gCollideMap
[yy
][xx
] and t
;
249 {$IF DEFINED(HAS_COLLIDE_BITMAP)}
250 procedure CreateCollideMap();
254 g_Game_SetLoadingText(_lc
[I_LOAD_COLLIDE_MAP
]+' 1/6', 0, False);
255 SetLength(gCollideMap
, gMapInfo
.Height
+1);
256 for a
:= 0 to High(gCollideMap
) do
257 SetLength(gCollideMap
[a
], gMapInfo
.Width
+1);
259 if gWater
<> nil then
261 g_Game_SetLoadingText(_lc
[I_LOAD_COLLIDE_MAP
]+' 2/6', 0, True);
262 for a
:= 0 to High(gWater
) do
264 g_Mark(X
, Y
, Width
, Height
, MARK_WATER
, True);
267 if gAcid1
<> nil then
269 g_Game_SetLoadingText(_lc
[I_LOAD_COLLIDE_MAP
]+' 3/6', 0, True);
270 for a
:= 0 to High(gAcid1
) do
272 g_Mark(X
, Y
, Width
, Height
, MARK_ACID
, True);
275 if gAcid2
<> nil then
277 g_Game_SetLoadingText(_lc
[I_LOAD_COLLIDE_MAP
]+' 4/6', 0, True);
278 for a
:= 0 to High(gAcid2
) do
280 g_Mark(X
, Y
, Width
, Height
, MARK_ACID
, True);
283 if gLifts
<> nil then
285 g_Game_SetLoadingText(_lc
[I_LOAD_COLLIDE_MAP
]+' 5/6', 0, True);
286 for a
:= 0 to High(gLifts
) do
289 g_Mark(X
, Y
, Width
, Height
, MARK_LIFT
, False);
292 g_Mark(X
, Y
, Width
, Height
, MARK_LIFTUP
, True)
293 else if LiftType
= 1 then
294 g_Mark(X
, Y
, Width
, Height
, MARK_LIFTDOWN
, True)
295 else if LiftType
= 2 then
296 g_Mark(X
, Y
, Width
, Height
, MARK_LIFTLEFT
, True)
297 else if LiftType
= 3 then
298 g_Mark(X
, Y
, Width
, Height
, MARK_LIFTRIGHT
, True)
302 if gWalls
<> nil then
304 g_Game_SetLoadingText(_lc
[I_LOAD_COLLIDE_MAP
]+' 6/6', 0, True);
305 for a
:= 0 to High(gWalls
) do
307 if gWalls
[a
].Door
then
310 if gWalls
[a
].Enabled
then
312 g_Mark(X
, Y
, Width
, Height
, MARK_DOOR
, True)
313 else // Îòêðûòàÿ äâåðü:
314 if gWalls
[a
].Enabled
then
316 g_Mark(X
, Y
, Width
, Height
, MARK_DOOR
, False);
320 g_Mark(X
, Y
, Width
, Height
, MARK_WALL
, True);
327 procedure g_GFX_Init();
329 //CreateCollideMap();
333 procedure g_GFX_Free();
338 SetLength(Particles
, MaxParticles
);
339 for a
:= 0 to High(Particles
) do Particles
[a
].die();
340 CurrentParticle
:= 0;
342 if OnceAnims
<> nil then
344 for a
:= 0 to High(OnceAnims
) do
345 OnceAnims
[a
].Animation
.Free();
353 procedure CorrectOffsets(id: Integer); inline;
357 part := @Particles[id];
360 // check for upper wall
361 if isBlockedAt(part.X, part.Y-1) then part.offsetY := 1;
362 // check for left wall
363 if isBlockedAt(part.X-1, part.Y) then part.offsetX := 1;
368 // ////////////////////////////////////////////////////////////////////////// //
369 procedure TParticle
.thinkerBlood ();
377 h
:= gMapInfo
.Height
;
381 if (State
= STATE_STICK
) then
383 if (not ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)) and
384 (not ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)) and
385 (not ByteBool(gCollideMap[Y, X-1] and MARK_BLOCKED)) and
386 (not ByteBool(gCollideMap[Y, X+1] and MARK_BLOCKED))
389 if (not isBlockedAt(X
, Y
-1)) and
390 (not isBlockedAt(X
, Y
+1)) and
391 (not isBlockedAt(X
-1, Y
)) and
392 (not isBlockedAt(X
+1, Y
))
394 begin // Îòëèïëà - êàïàåò
397 State
:= STATE_NORMAL
;
400 if Random(200) = 100 then
401 begin // Ïðèëåïëåíà - íî âîçìîæíî ñòåêàåò
407 if not isBlockedAt(X
, Y
) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)} then
409 if isLiftUpAt(X
, Y
) {ByteBool(gCollideMap[Y, X] and MARK_LIFTUP)} then
411 if VelY
> -4-Random(3) then
413 if Abs(VelX
) > 0.1 then
414 VelX
:= VelX
- VelX
/10.0;
415 VelX
:= VelX
+ (Random
-Random
)*0.2;
418 if isLiftLeftAt(X
, Y
) {ByteBool(gCollideMap[Y, X] and MARK_LIFTLEFT)} then
420 if VelX
> -8-Random(3) then
424 if isLiftRightAt(X
, Y
) {ByteBool(gCollideMap[Y, X] and MARK_LIFTRIGHT)} then
425 begin // Ïîòîê âïðàâî
426 if VelX
< 8+Random(3) then
435 if (Abs(VelX
) < 0.1) and (Abs(VelY
) < 0.1) then
436 if (State
<> STATE_STICK
) and
437 (not isBlockedAt(X
, Y
-1) {ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)}) and
438 (not isBlockedAt(X
, Y
) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)}) and
439 (not isBlockedAt(X
, Y
+1) {ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)}) then
440 begin // Âèñèò â âîçäóõå - êàïàåò
443 State
:= STATE_NORMAL
;
457 if (X
+s
>= w
) or (X
+s
<= 0) then begin die(); break
; end;
459 //c := gCollideMap[Y, X+s];
461 if isBlockedAt(X
+s
, Y
) {ByteBool(c and MARK_BLOCKED)} then
467 State
:= STATE_STICK
;
486 if (Y
+s
>= h
) or (Y
+s
<= 0) then begin die(); break
; end;
488 //c := gCollideMap[Y+s, X];
490 if isBlockedAt(X
, Y
+s
) {ByteBool(c and MARK_BLOCKED)} then
496 if (s
> 0) and (State
<> STATE_STICK
) then
497 State
:= STATE_NORMAL
499 State
:= STATE_STICK
;
512 if (X
+dX
>= w
) or (Y
+dY
>= h
) or
513 (X
+dX
<= 0) or (Y
+dY
<= 0) or
514 isBlockedAt(X
+dX
, Y
+dY
) {ByteBool(gCollideMap[Y+dY, X+dX] and MARK_BLOCKED)} then
515 begin // Ñòåíà/äâåðü/ãðàíèöà
527 VelX
:= VelX
+ AccelX
;
528 VelY
:= VelY
+ AccelY
;
530 // Êðîâü ðàñòâîðÿåòñÿ â æèäêîñòè:
531 if isLiquidAt(X
, Y
) {ByteBool(gCollideMap[Y, X] and MARK_LIQUID)} then
535 Alpha
:= 255 - Trunc((255.0 * Time
) / LiveTime
);
540 // ////////////////////////////////////////////////////////////////////////// //
541 procedure TParticle
.thinkerSpark ();
544 {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
555 {$IF DEFINED(D2F_NEW_SPARK_THINKER)}
556 if (Abs(VelX
) < 0.1) and (Abs(VelY
) < 0.1) then
558 pan
:= g_Map_traceToNearest(X
, Y
-1, X
, Y
+1, (GridTagWall
or GridTagDoor
or GridTagStep
or GridTagAcid1
or GridTagAcid2
or GridTagWater
), @ex
, @ey
);
561 if (Abs(VelX
) < 0.1) and (Abs(VelY
) < 0.1) and
562 (not isBlockedAt(X
, Y
-1) {ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)}) and
563 (not isBlockedAt(X
, Y
) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)}) and
564 (not isBlockedAt(X
, Y
+1) {ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)}) then
565 begin // Âèñèò â âîçäóõå
573 {$IF DEFINED(D2F_NEW_SPARK_THINKER)}
574 pan
:= g_Map_traceToNearest(X
, Y
, X
+dX
, Y
, (GridTagWall
or GridTagDoor
or GridTagStep
or GridTagAcid1
or GridTagAcid2
or GridTagWater
), @ex
, @ey
);
575 //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);
581 if ((pan
.tag
and (GridTagAcid1
or GridTagAcid2
or GridTagWater
)) <> 0) then begin die(); exit
; end;
585 if (X
< 0) or (X
>= gMapInfo
.Width
) then begin die(); exit
; end;
587 if (dX
> 0) then s
:= 1 else s
:= -1;
591 if (X
+s
>= gMapInfo
.Width
) or (X
+s
<= 0) then begin die(); break
; end;
592 //c := gCollideMap[Y, X+s];
593 if isBlockedAt(X
+s
, Y
) {ByteBool(c and MARK_BLOCKED)} then
594 begin // Ñòåíà/äâåðü - ïàäàåò âåðòèêàëüíî
600 if not isAnythingAt(X
+s
, Y
) {c = MARK_FREE} then
613 {$IF DEFINED(D2F_NEW_SPARK_THINKER)}
614 pan
:= g_Map_traceToNearest(X
, Y
, X
, Y
+dY
, (GridTagWall
or GridTagDoor
or GridTagStep
or GridTagAcid1
or GridTagAcid2
or GridTagWater
), @ex
, @ey
);
615 //e_WriteLog(Format('spark y-trace: (%d,%d)-(%d,%d); dy=%d; end=(%d,%d); hit=%d', [X, Y, X, Y+dY, dY, ex, ey, Integer(pan <> nil)]), MSG_NOTIFY);
619 e_WriteLog(Format('spark y-trace: %08x (%d,%d)-(%d,%d); dy=%d; end=(%d,%d); hittag=%04x', [LongWord(@self), X, Y, X, Y+dY, dY, ex, ey, pan.tag]), MSG_NOTIFY);
623 e_WriteLog(Format('spark y-trace: %08x (%d,%d)-(%d,%d); dy=%d; end=(%d,%d); hit=%d', [LongWord(@self), X, Y, X, Y+dY, dY, ex, ey, Integer(pan <> nil)]), MSG_NOTIFY);
632 if ((pan
.tag
and (GridTagAcid1
or GridTagAcid2
or GridTagWater
)) <> 0) then begin die(); exit
; end;
636 AccelY
:= abs(AccelY
);
646 if (Y
< 0) or (Y
>= gMapInfo
.Height
) then begin die(); exit
; end;
648 if (dY
> 0) then s
:= 1 else s
:= -1;
652 if (Y
+s
>= gMapInfo
.Height
) or (Y
+s
<= 0) then begin die(); break
; end;
653 //c := gCollideMap[Y+s, X];
654 if isBlockedAt(X
, Y
+s
) {ByteBool(c and MARK_BLOCKED)} then
655 begin // Ñòåíà/äâåðü - ïàäàåò âåðòèêàëüíî
659 AccelY
:= Abs(AccelY
);
661 else // Èëè íå ïàäàåò
672 if not isAnythingAt(X
, Y
+s
) {c = MARK_FREE} then
683 if (VelX
<> 0.0) then VelX
+= AccelX
;
685 if (VelY
<> 0.0) then
687 if (AccelY
< 10) then AccelY
+= 0.08;
695 // ////////////////////////////////////////////////////////////////////////// //
696 procedure TParticle
.thinkerWater ();
699 {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
708 {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
710 h
:= gMapInfo
.Height
;
713 //TODO: trace wall end when water becomes stick
714 if (State
= STATE_STICK
) and (Random(30) = 15) then
715 begin // Ñòåêàåò/îòëèïàåò
718 {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
719 if (not isBlockedAt(X
-1, Y
) {ByteBool(gCollideMap[Y, X-1] and MARK_BLOCKED)}) and
720 (not isBlockedAt(X
+1, Y
) {ByteBool(gCollideMap[Y, X+1] and MARK_BLOCKED)}) then
721 State
:= STATE_NORMAL
;
723 if (stickDX
= 0) then
725 // no walls around, drop
726 State
:= STATE_NORMAL
;
730 if (g_Map_PanelAtPoint(X
+stickDX
, Y
, (GridTagWall
or GridTagDoor
or GridTagStep
)) = nil) then State
:= STATE_NORMAL
;
736 {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
737 if not isBlockedAt(X
, Y
) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)} then
739 if isLiftUpAt(X
, Y
) {ByteBool(gCollideMap[Y, X] and MARK_LIFTUP)} then
741 if VelY
> -4-Random(3) then
743 if Abs(VelX
) > 0.1 then
744 VelX
:= VelX
- VelX
/10.0;
745 VelX
:= VelX
+ (Random
-Random
)*0.2;
748 if isLiftLeftAt(X
, Y
) {ByteBool(gCollideMap[Y, X] and MARK_LIFTLEFT)} then
750 if VelX
> -8-Random(3) then
754 if isLiftRightAt(X
, Y
) {ByteBool(gCollideMap[Y, X] and MARK_LIFTRIGHT)} then
755 begin // Ïîòîê âïðàâî
756 if VelX
< 8+Random(3) then
762 pan
:= g_Map_PanelAtPoint(X
, Y
, (GridTagWall
or GridTagDoor
or GridTagStep
or GridTagAcid1
or GridTagAcid2
or GridTagWater
or GridTagLift
));
765 if ((pan
.tag
and (GridTagAcid1
or GridTagAcid2
or GridTagWater
)) <> 0) then begin die(); exit
; end;
766 if ((pan
.PanelType
and PANEL_LIFTUP
) <> 0) then
768 if (VelY
> -4-Random(3)) then VelY
-= 0.8;
769 if (Abs(VelX
) > 0.1) then VelX
-= VelX
/10.0;
770 VelX
+= (Random
-Random
)*0.2;
773 if ((pan
.PanelType
and PANEL_LIFTLEFT
) <> 0) then
775 if (VelX
> -8-Random(3)) then VelX
-= 0.8;
778 if ((pan
.PanelType
and PANEL_LIFTRIGHT
) <> 0) then
780 if (VelX
< 8+Random(3)) then VelX
+= 0.8;
789 {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
790 if (Abs(VelX
) < 0.1) and (Abs(VelY
) < 0.1) then
792 if (State
<> STATE_STICK
) and
793 (not isBlockedAt(X
, Y
-1) {ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)}) and
794 (not isBlockedAt(X
, Y
) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)}) and
795 (not isBlockedAt(X
, Y
+1) {ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)}) then
796 begin // Âèñèò â âîçäóõå - êàïàåò
799 State
:= STATE_NORMAL
;
803 if (State
<> STATE_STICK
) and (Abs(VelX
) < 0.1) and (Abs(VelY
) < 0.1) then
805 // Âèñèò â âîçäóõå - êàïàåò
806 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
810 State
:= STATE_NORMAL
;
815 {$IF DEFINED(D2F_NEW_SPARK_THINKER)}
819 pan
:= g_Map_traceToNearest(X
, Y
, X
+dX
, Y
, (GridTagWall
or GridTagDoor
or GridTagStep
or GridTagAcid1
or GridTagAcid2
or GridTagWater
), @ex
, @ey
);
825 if (dY
> 0) and ((pan
.tag
and (GridTagAcid1
or GridTagAcid2
or GridTagWater
)) <> 0) then begin die(); exit
; end;
827 if ((pan
.tag
and (GridTagWall
or GridTagDoor
or GridTagStep
)) <> 0) then
833 State
:= STATE_STICK
;
834 if (dX
> 0) then stickDX
:= 1 else stickDX
:= -1;
837 if (X
< 0) or (X
>= gMapInfo
.Width
) then begin die(); exit
; end;
842 pan
:= g_Map_traceToNearest(X
, Y
, X
, Y
+dY
, (GridTagWall
or GridTagDoor
or GridTagStep
or GridTagAcid1
or GridTagAcid2
or GridTagWater
), @ex
, @ey
);
848 if (dY
> 0) and ((pan
.tag
and (GridTagAcid1
or GridTagAcid2
or GridTagWater
)) <> 0) then begin die(); exit
; end;
850 if ((pan
.tag
and (GridTagWall
or GridTagDoor
or GridTagStep
)) <> 0) then
856 if (dY
> 0) and (State
<> STATE_STICK
) then
858 State
:= STATE_NORMAL
;
862 State
:= STATE_STICK
;
863 if (g_Map_PanelAtPoint(X
-1, Y
, (GridTagWall
or GridTagDoor
or GridTagStep
)) <> nil) then stickDX
:= -1
864 else if (g_Map_PanelAtPoint(X
+1, Y
, (GridTagWall
or GridTagDoor
or GridTagStep
)) <> nil) then stickDX
:= 1
869 if (Y
< 0) or (Y
>= gMapInfo
.Height
) then begin die(); exit
; end;
875 if (dX
> 0) then s
:= 1 else s
:= -1;
876 for b
:= 1 to Abs(dX
) do
879 if (X
+s
>= w
) or (X
+s
<= 0) then begin die(); break
;end;
880 //c := gCollideMap[Y, X+s];
881 // Ñáîêó æèäêîñòü, à ÷àñòèöà óæå ïàäàåò?
882 if isLiquidAt(X
+s
, Y
) {ByteBool(c and MARK_LIQUID)} and (dY
> 0) then begin die(); break
; end;
883 if isBlockedAt(X
+s
, Y
) {ByteBool(c and MARK_BLOCKED)} then
889 State
:= STATE_STICK
;
898 if (dY
> 0) then s
:= 1 else s
:= -1;
899 for b
:= 1 to Abs(dY
) do
901 // Ñíèçó/ñâåðõó ãðàíèöà
902 if (Y
+s
>= h
) or (Y
+s
<= 0) then begin die(); break
; end;
903 //c := gCollideMap[Y+s, X];
904 // Ñíèçó æèäêîñòü, à ÷àñòèöà óæå ïàäàåò
905 if isLiquidAt(X
, Y
+s
) {ByteBool(c and MARK_LIQUID)} and (dY
> 0) then begin die(); break
; end;
906 if isBlockedAt(X
, Y
+s
) {ByteBool(c and MARK_BLOCKED)} then
912 if (s
> 0) and (State
<> STATE_STICK
) then State
:= STATE_NORMAL
else State
:= STATE_STICK
;
927 // ////////////////////////////////////////////////////////////////////////// //
928 procedure TParticle
.thinkerBubble ();
935 h
:= gMapInfo
.Height
;
946 for b
:= 1 to Abs(dY
) do
948 if (Y
+s
>= h
) or (Y
+s
<= 0) then begin die(); break
; end;
951 if not isLiquidAt(X, Y+s) {ByteBool(gCollideMap[Y+s, X] and MARK_LIQUID)} then
952 begin // Óæå íå æèäêîñòü
957 // we traced liquid before, so don't bother checking
958 if (Y
+s
<= liquidTopY
) then begin die(); break
; end;
965 VelY
:= VelY
+ AccelY
;
971 // ////////////////////////////////////////////////////////////////////////// //
972 procedure g_GFX_SparkVel (fX
, fY
: Integer; Count
: Word; VX
, VY
: Integer; DevX
, DevY
: Byte);
979 l
:= Length(Particles
);
981 if Count
> l
then Count
:= l
;
988 for a
:= 1 to Count
do
990 with Particles
[CurrentParticle
] do
992 X
:= fX
-DevX1
+Random(DevX2
);
993 Y
:= fY
-DevY1
+Random(DevY2
);
995 VelX
:= VX
+ (Random
-Random
)*3;
996 VelY
:= VY
+ (Random
-Random
)*3;
1004 AccelX
:= -Sign(VelX
)*Random
/100;
1008 Green
:= 100+Random(155);
1012 State
:= STATE_NORMAL
;
1014 LiveTime
:= 30+Random(60);
1015 ParticleType
:= PARTICLE_SPARK
;
1017 {CorrectOffsets(CurrentParticle);}
1020 if CurrentParticle
+2 > MaxParticles
then
1021 CurrentParticle
:= 0
1023 CurrentParticle
:= CurrentParticle
+1;
1028 procedure g_GFX_Blood(fX
, fY
: Integer; Count
: Word; vx
, vy
: Integer;
1029 DevX
, DevY
: Word; CR
, CG
, CB
: Byte; Kind
: Byte = BLOOD_NORMAL
);
1038 if Kind
= BLOOD_SPARKS
then
1040 g_GFX_SparkVel(fX
, fY
, 2 + Random(2), -VX
div 2, -VY
div 2, DevX
, DevY
);
1043 l
:= Length(Particles
);
1049 DevX1
:= DevX
div 2;
1051 DevY1
:= DevY
div 2;
1054 for a
:= 1 to Count
do
1056 with Particles
[CurrentParticle
] do
1058 X
:= fX
- DevX1
+ Random(DevX2
);
1059 Y
:= fY
- DevY1
+ Random(DevY2
);
1062 if (X < 0) or (X > gMapInfo.Width-1) or
1063 (Y < 0) or (Y > gMapInfo.Height-1) or
1064 ByteBool(gCollideMap[Y, X] and MARK_WALL) then
1067 if isWallAt(X
, Y
) then continue
;
1069 VelX
:= vx
+ (Random
-Random
)*3;
1070 VelY
:= vy
+ (Random
-Random
)*3;
1078 AccelX
:= -Sign(VelX
)*Random
/100;
1081 CRnd
:= 20*Random(6);
1084 CC
:= CR
+ CRnd
- 50;
1085 if CC
< 0 then CC
:= 0;
1086 if CC
> 255 then CC
:= 255;
1092 CC
:= CG
+ CRnd
- 50;
1093 if CC
< 0 then CC
:= 0;
1094 if CC
> 255 then CC
:= 255;
1100 CC
:= CB
+ CRnd
- 50;
1101 if CC
< 0 then CC
:= 0;
1102 if CC
> 255 then CC
:= 255;
1109 State
:= STATE_NORMAL
;
1111 LiveTime
:= 120+Random(40);
1112 ParticleType
:= PARTICLE_BLOOD
;
1114 {CorrectOffsets(CurrentParticle);}
1117 if CurrentParticle
>= MaxParticles
-1 then
1118 CurrentParticle
:= 0
1120 CurrentParticle
:= CurrentParticle
+1;
1125 procedure g_GFX_Spark(fX
, fY
: Integer; Count
: Word; Angle
: SmallInt; DevX
, DevY
: Byte);
1131 BaseVelX
, BaseVelY
: Single;
1134 l
:= Length(Particles
);
1140 Angle
:= 360 - Angle
;
1142 DevX1
:= DevX
div 2;
1144 DevY1
:= DevY
div 2;
1147 b
:= DegToRad(Angle
);
1149 BaseVelY
:= 1.6*sin(b
);
1150 if Abs(BaseVelX
) < 0.01 then
1152 if Abs(BaseVelY
) < 0.01 then
1154 for a
:= 1 to Count
do
1156 with Particles
[CurrentParticle
] do
1158 X
:= fX
-DevX1
+Random(DevX2
);
1159 Y
:= fY
-DevY1
+Random(DevY2
);
1161 VelX
:= BaseVelX
*Random
;
1162 VelY
:= BaseVelY
-Random
;
1167 Green
:= 100+Random(155);
1171 State
:= STATE_NORMAL
;
1173 LiveTime
:= 30+Random(60);
1174 ParticleType
:= PARTICLE_SPARK
;
1176 {CorrectOffsets(CurrentParticle);}
1179 if CurrentParticle
+2 > MaxParticles
then
1180 CurrentParticle
:= 0
1182 CurrentParticle
:= CurrentParticle
+1;
1186 procedure g_GFX_Water(fX
, fY
: Integer; Count
: Word; fVelX
, fVelY
: Single; DevX
, DevY
, Color
: Byte);
1193 l
:= Length(Particles
);
1199 if Abs(fVelX
) < 3.0 then
1200 fVelX
:= 3.0 - 6.0*Random
;
1202 DevX1
:= DevX
div 2;
1204 DevY1
:= DevY
div 2;
1207 for a
:= 1 to Count
do
1209 with Particles
[CurrentParticle
] do
1211 X
:= fX
-DevX1
+Random(DevX2
);
1212 Y
:= fY
-DevY1
+Random(DevY2
);
1214 if Abs(fVelX
) < 0.5 then
1215 VelX
:= 1.0 - 2.0*Random
1217 VelX
:= fVelX
*Random
;
1218 if Random(10) < 7 then
1220 VelY
:= fVelY
*Random
;
1227 Red
:= 155 + Random(9)*10;
1228 Green
:= Trunc(150*Random
);
1233 Red
:= Trunc(150*Random
);
1234 Green
:= 175 + Random(9)*10;
1239 Red
:= Trunc(200*Random
);
1241 Blue
:= 175 + Random(9)*10;
1245 Red
:= 90 + Random(12)*10;
1253 State
:= STATE_NORMAL
;
1255 LiveTime
:= 60+Random(60);
1256 ParticleType
:= PARTICLE_WATER
;
1258 {CorrectOffsets(CurrentParticle);}
1261 if CurrentParticle
+2 > MaxParticles
then
1262 CurrentParticle
:= 0
1264 CurrentParticle
:= CurrentParticle
+1;
1268 procedure g_GFX_SimpleWater(fX
, fY
: Integer; Count
: Word; fVelX
, fVelY
: Single; DefColor
, CR
, CG
, CB
: Byte);
1273 l
:= Length(Particles
);
1279 for a
:= 1 to Count
do
1281 with Particles
[CurrentParticle
] do
1294 Red
:= 155 + Random(9)*10;
1295 Green
:= Trunc(150*Random
);
1300 Red
:= Trunc(150*Random
);
1301 Green
:= 175 + Random(9)*10;
1306 Red
:= Trunc(200*Random
);
1308 Blue
:= 175 + Random(9)*10;
1310 4: // Ñâîé öâåò, ñâåòëåå
1312 Red
:= 20 + Random(19)*10;
1315 Red
:= Min(Red
+ CR
, 255);
1316 Green
:= Min(Green
+ CG
, 255);
1317 Blue
:= Min(Blue
+ CB
, 255);
1319 5: // Ñâîé öâåò, òåìíåå
1321 Red
:= 20 + Random(19)*10;
1324 Red
:= Max(CR
- Red
, 0);
1325 Green
:= Max(CG
- Green
, 0);
1326 Blue
:= Max(CB
- Blue
, 0);
1330 Red
:= 90 + Random(12)*10;
1338 State
:= STATE_NORMAL
;
1340 LiveTime
:= 60+Random(60);
1341 ParticleType
:= PARTICLE_WATER
;
1343 {CorrectOffsets(CurrentParticle);}
1346 if CurrentParticle
+2 > MaxParticles
then
1347 CurrentParticle
:= 0
1349 CurrentParticle
:= CurrentParticle
+1;
1354 procedure g_GFX_Bubbles(fX
, fY
: Integer; Count
: Word; DevX
, DevY
: Byte);
1359 l
, liquidx
: Integer;
1361 l
:= Length(Particles
);
1367 DevX1
:= DevX
div 2;
1369 DevY1
:= DevY
div 2;
1372 for a
:= 1 to Count
do
1374 with Particles
[CurrentParticle
] do
1376 X
:= fX
-DevX1
+Random(DevX2
);
1377 Y
:= fY
-DevY1
+Random(DevY2
);
1379 if (X
>= gMapInfo
.Width
) or (X
<= 0) or
1380 (Y
>= gMapInfo
.Height
) or (Y
<= 0) then
1384 // don't spawn bubbles outside of the liquid
1385 if not isLiquidAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIQUID)} then
1389 // trace liquid, so we'll know where it ends; do it in 8px steps for speed
1390 // tracer will return `false` if we started outside of the liquid
1391 if not g_Map_TraceLiquidNonPrecise(X
, Y
, 0, -8, liquidx
, liquidTopY
) then continue
;
1403 State
:= STATE_NORMAL
;
1406 ParticleType
:= PARTICLE_BUBBLES
;
1408 {CorrectOffsets(CurrentParticle);}
1411 if CurrentParticle
+2 > MaxParticles
then
1412 CurrentParticle
:= 0
1414 CurrentParticle
:= CurrentParticle
+1;
1418 procedure g_GFX_SetMax(Count
: Integer);
1422 if Count
> 50000 then Count
:= 50000;
1423 if (Count
< 1) then Count
:= 1;
1425 SetLength(Particles
, Count
);
1426 for a
:= 0 to High(Particles
) do Particles
[a
].die();
1427 MaxParticles
:= Count
;
1428 //if CurrentParticle >= Count then
1429 CurrentParticle
:= 0;
1432 function g_GFX_GetMax(): Integer;
1434 Result
:= MaxParticles
;
1437 function FindOnceAnim
: DWORD
;
1441 if OnceAnims
<> nil then
1442 for i
:= 0 to High(OnceAnims
) do
1443 if OnceAnims
[i
].Animation
= nil then
1449 if OnceAnims
= nil then
1451 SetLength(OnceAnims
, 16);
1456 Result
:= High(OnceAnims
) + 1;
1457 SetLength(OnceAnims
, Length(OnceAnims
) + 16);
1461 procedure g_GFX_OnceAnim(X
, Y
: Integer; Anim
: TAnimation
; AnimType
: Byte = 0);
1468 find_id
:= FindOnceAnim();
1470 OnceAnims
[find_id
].AnimType
:= AnimType
;
1471 OnceAnims
[find_id
].Animation
:= TAnimation
.Create(Anim
.FramesID
, Anim
.Loop
, Anim
.Speed
);
1472 OnceAnims
[find_id
].Animation
.Blending
:= Anim
.Blending
;
1473 OnceAnims
[find_id
].Animation
.Alpha
:= Anim
.Alpha
;
1474 OnceAnims
[find_id
].X
:= X
;
1475 OnceAnims
[find_id
].Y
:= Y
;
1478 procedure g_GFX_Update();
1484 if not gpart_dbg_enabled
then exit
;
1485 if Particles
<> nil then
1487 w
:= gMapInfo
.Width
;
1488 h
:= gMapInfo
.Height
;
1490 len
:= High(Particles
);
1492 for a
:= 0 to len
do
1494 if Particles
[a
].alive
then
1496 with Particles
[a
] do
1498 if (Time
= LiveTime
) then begin die(); continue
; end;
1499 if (X
+1 >= w
) or (Y
+1 >= h
) or (X
<= 0) or (Y
<= 0) then begin die(); end;
1500 //if not alive then Continue;
1501 //e_WriteLog(Format('particle #%d: %d', [State, ParticleType]), MSG_NOTIFY);
1503 {CorrectOffsets(a);}
1507 end; // Particles <> nil
1509 if OnceAnims
<> nil then
1511 for a
:= 0 to High(OnceAnims
) do
1512 if OnceAnims
[a
].Animation
<> nil then
1514 case OnceAnims
[a
].AnimType
of
1517 if Random(3) = 0 then
1518 OnceAnims
[a
].X
:= OnceAnims
[a
].X
-1+Random(3);
1519 if Random(2) = 0 then
1520 OnceAnims
[a
].Y
:= OnceAnims
[a
].Y
-Random(2);
1524 if OnceAnims
[a
].Animation
.Played
then
1526 OnceAnims
[a
].Animation
.Free();
1527 OnceAnims
[a
].Animation
:= nil;
1530 OnceAnims
[a
].Animation
.Update();
1535 procedure g_GFX_Draw();
1539 if Particles
<> nil then
1541 glDisable(GL_TEXTURE_2D
);
1545 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
);
1549 len
:= High(Particles
);
1551 for a
:= 0 to len
do
1552 with Particles
[a
] do
1553 if alive
and (X
>= sX
) and (Y
>= sY
) and (X
<= sX
+sWidth
) and (sY
<= sY
+sHeight
) then
1555 glColor4ub(Red
, Green
, Blue
, Alpha
);
1556 glVertex2i(X
+ offsetX
, Y
+ offsetY
);
1561 glDisable(GL_BLEND
);
1564 if OnceAnims
<> nil then
1565 for a
:= 0 to High(OnceAnims
) do
1566 if OnceAnims
[a
].Animation
<> nil then
1567 with OnceAnims
[a
] do
1568 Animation
.Draw(X
, Y
, M_NONE
);