DEADSOFTWARE

particles refucktoring
[d2df-sdl.git] / src / game / g_gfx.pas
1 (* Copyright (C) DooM 2D:Forever Developers
2 *
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.
7 *
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.
12 *
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/>.
15 *)
16 {$INCLUDE ../shared/a_modes.inc}
17 unit g_gfx;
19 interface
21 uses
22 e_log, g_textures;
24 const
25 BLOOD_NORMAL = 0;
26 BLOOD_SPARKS = 1;
27 ONCEANIM_NONE = 0;
28 ONCEANIM_SMOKE = 1;
29 MARK_FREE = 0;
30 MARK_WALL = 1;
31 MARK_WATER = 2;
32 MARK_ACID = 4;
33 MARK_LIFTDOWN = 8;
34 MARK_LIFTUP = 16;
35 MARK_DOOR = 32;
36 MARK_LIFTLEFT = 64;
37 MARK_LIFTRIGHT = 128;
38 MARK_BLOCKED = MARK_WALL + MARK_DOOR;
39 MARK_LIQUID = MARK_WATER + MARK_ACID;
40 MARK_LIFT = MARK_LIFTDOWN + MARK_LIFTUP + MARK_LIFTLEFT + MARK_LIFTRIGHT;
42 procedure g_GFX_Init();
43 procedure g_GFX_Free();
45 procedure g_GFX_Blood(fX, fY: Integer; Count: Word; vx, vy: Integer;
46 DevX, DevY: Word; CR, CG, CB: Byte; Kind: Byte = BLOOD_NORMAL);
47 procedure g_GFX_Spark(fX, fY: Integer; Count: Word; Angle: SmallInt; DevX, DevY: Byte);
48 procedure g_GFX_Water(fX, fY: Integer; Count: Word; fVelX, fVelY: Single; DevX, DevY, Color: Byte);
49 procedure g_GFX_SimpleWater(fX, fY: Integer; Count: Word; fVelX, fVelY: Single; DefColor, CR, CG, CB: Byte);
50 procedure g_GFX_Bubbles(fX, fY: Integer; Count: Word; DevX, DevY: Byte);
51 procedure g_GFX_SetMax(Count: Integer);
52 function g_GFX_GetMax(): Integer;
54 procedure g_GFX_OnceAnim(X, Y: Integer; Anim: TAnimation; AnimType: Byte = 0);
56 procedure g_Mark(x, y, Width, Height: Integer; t: Byte; st: Boolean);
58 procedure g_GFX_Update();
59 procedure g_GFX_Draw();
62 var
63 gpart_dbg_enabled: Boolean = true;
64 gpart_dbg_phys_enabled: Boolean = true;
67 implementation
69 uses
70 g_map, g_basic, Math, e_graphics, GL, GLExt,
71 g_options, g_console, SysUtils, g_triggers, MAPDEF,
72 g_game, g_language, g_net;
74 type
75 PParticle = ^TParticle;
77 TParticle = record
78 X, Y: Integer;
79 VelX, VelY: Single;
80 AccelX, AccelY: Single;
81 Red, Green, Blue: Byte;
82 Alpha: Byte;
83 Time, LiveTime: Word;
84 State: Byte;
85 ParticleType: Byte;
86 offsetX, offsetY: ShortInt;
87 // for bubbles
88 liquidTopY: Integer; // don't float higher than this
90 //k8: sorry, i have to emulate virtual methods this way, 'cause i haet `Object`
92 procedure thinkerBlood ();
93 procedure thinkerSpark ();
94 procedure thinkerBubble ();
95 procedure thinkerWater ();
97 function alive (): Boolean; inline;
98 procedure die (); inline;
99 procedure think (); inline;
100 end;
102 TOnceAnim = record
103 AnimType: Byte;
104 X, Y: Integer;
105 Animation: TAnimation;
106 end;
108 const
109 PARTICLE_BLOOD = 0;
110 PARTICLE_SPARK = 1;
111 PARTICLE_BUBBLES = 2;
112 PARTICLE_WATER = 3;
113 STATE_FREE = 0;
114 STATE_NORMAL = 1;
115 STATE_STICK = 2;
117 var
118 Particles: array of TParticle;
119 OnceAnims: array of TOnceAnim;
120 MaxParticles: Integer;
121 CurrentParticle: Integer;
124 // ////////////////////////////////////////////////////////////////////////// //
125 function TParticle.alive (): Boolean; inline; begin result := (State <> STATE_FREE); end;
126 procedure TParticle.die (); inline; begin State := STATE_FREE; end;
128 procedure TParticle.think (); inline;
129 begin
130 case ParticleType of
131 PARTICLE_BLOOD: thinkerBlood();
132 PARTICLE_SPARK: thinkerSpark();
133 PARTICLE_BUBBLES: thinkerBubble();
134 PARTICLE_WATER: thinkerWater();
135 end;
136 end;
139 // ////////////////////////////////////////////////////////////////////////// //
140 function isBlockedAt (x, y: Integer): Boolean; inline;
141 begin
142 if not gpart_dbg_phys_enabled then begin result := false; exit; end;
143 result := g_Map_HasAnyPanelAtPoint(x, y, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
144 end;
146 // ???
147 function isWallAt (x, y: Integer): Boolean; inline;
148 begin
149 if not gpart_dbg_phys_enabled then begin result := false; exit; end;
150 result := g_Map_HasAnyPanelAtPoint(x, y, (PANEL_WALL or PANEL_STEP));
151 end;
153 function isLiftUpAt (x, y: Integer): Boolean; inline;
154 begin
155 if not gpart_dbg_phys_enabled then begin result := false; exit; end;
156 result := g_Map_HasAnyPanelAtPoint(x, y, PANEL_LIFTUP);
157 end;
159 function isLiftDownAt (x, y: Integer): Boolean; inline;
160 begin
161 if not gpart_dbg_phys_enabled then begin result := false; exit; end;
162 result := g_Map_HasAnyPanelAtPoint(x, y, PANEL_LIFTDOWN);
163 end;
165 function isLiftLeftAt (x, y: Integer): Boolean; inline;
166 begin
167 if not gpart_dbg_phys_enabled then begin result := false; exit; end;
168 result := g_Map_HasAnyPanelAtPoint(x, y, PANEL_LIFTLEFT);
169 end;
171 function isLiftRightAt (x, y: Integer): Boolean; inline;
172 begin
173 if not gpart_dbg_phys_enabled then begin result := false; exit; end;
174 result := g_Map_HasAnyPanelAtPoint(x, y, PANEL_LIFTRIGHT);
175 end;
177 function isLiquidAt (x, y: Integer): Boolean; inline;
178 begin
179 if not gpart_dbg_phys_enabled then begin result := false; exit; end;
180 result := g_Map_HasAnyPanelAtPoint(x, y, (PANEL_WATER or PANEL_ACID1 or PANEL_ACID2));
181 end;
183 function isAnythingAt (x, y: Integer): Boolean; inline;
184 begin
185 if not gpart_dbg_phys_enabled then begin result := false; exit; end;
186 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));
187 end;
190 procedure g_Mark(x, y, Width, Height: Integer; t: Byte; st: Boolean);
191 {$IF not DEFINED(HAS_COLLIDE_BITMAP)}
192 begin
193 end;
194 {$ELSE}
195 var
196 yy, y2, xx, x2: Integer;
197 begin
198 if x < 0 then
199 begin
200 Width := Width + x;
201 x := 0;
202 end;
204 if Width < 0 then
205 Exit;
207 if y < 0 then
208 begin
209 Height := Height + y;
210 y := 0;
211 end;
213 if Height < 0 then
214 Exit;
216 if x > gMapInfo.Width then
217 Exit;
218 if y > gMapInfo.Height then
219 Exit;
221 y2 := y + Height - 1;
222 if y2 > gMapInfo.Height then
223 y2 := gMapInfo.Height;
225 x2 := x + Width - 1;
226 if x2 > gMapInfo.Width then
227 x2 := gMapInfo.Width;
229 if st then
230 begin // Óñòàíîâèòü ïðèçíàê
231 for yy := y to y2 do
232 for xx := x to x2 do
233 gCollideMap[yy][xx] := gCollideMap[yy][xx] or t;
234 end
235 else
236 begin // Óáðàòü ïðèçíàê
237 t := not t;
238 for yy := y to y2 do
239 for xx := x to x2 do
240 gCollideMap[yy][xx] := gCollideMap[yy][xx] and t;
241 end;
242 end;
243 {$ENDIF}
246 {$IF DEFINED(HAS_COLLIDE_BITMAP)}
247 procedure CreateCollideMap();
248 var
249 a: Integer;
250 begin
251 g_Game_SetLoadingText(_lc[I_LOAD_COLLIDE_MAP]+' 1/6', 0, False);
252 SetLength(gCollideMap, gMapInfo.Height+1);
253 for a := 0 to High(gCollideMap) do
254 SetLength(gCollideMap[a], gMapInfo.Width+1);
256 if gWater <> nil then
257 begin
258 g_Game_SetLoadingText(_lc[I_LOAD_COLLIDE_MAP]+' 2/6', 0, True);
259 for a := 0 to High(gWater) do
260 with gWater[a] do
261 g_Mark(X, Y, Width, Height, MARK_WATER, True);
262 end;
264 if gAcid1 <> nil then
265 begin
266 g_Game_SetLoadingText(_lc[I_LOAD_COLLIDE_MAP]+' 3/6', 0, True);
267 for a := 0 to High(gAcid1) do
268 with gAcid1[a] do
269 g_Mark(X, Y, Width, Height, MARK_ACID, True);
270 end;
272 if gAcid2 <> nil then
273 begin
274 g_Game_SetLoadingText(_lc[I_LOAD_COLLIDE_MAP]+' 4/6', 0, True);
275 for a := 0 to High(gAcid2) do
276 with gAcid2[a] do
277 g_Mark(X, Y, Width, Height, MARK_ACID, True);
278 end;
280 if gLifts <> nil then
281 begin
282 g_Game_SetLoadingText(_lc[I_LOAD_COLLIDE_MAP]+' 5/6', 0, True);
283 for a := 0 to High(gLifts) do
284 with gLifts[a] do
285 begin
286 g_Mark(X, Y, Width, Height, MARK_LIFT, False);
288 if LiftType = 0 then
289 g_Mark(X, Y, Width, Height, MARK_LIFTUP, True)
290 else if LiftType = 1 then
291 g_Mark(X, Y, Width, Height, MARK_LIFTDOWN, True)
292 else if LiftType = 2 then
293 g_Mark(X, Y, Width, Height, MARK_LIFTLEFT, True)
294 else if LiftType = 3 then
295 g_Mark(X, Y, Width, Height, MARK_LIFTRIGHT, True)
296 end;
297 end;
299 if gWalls <> nil then
300 begin
301 g_Game_SetLoadingText(_lc[I_LOAD_COLLIDE_MAP]+' 6/6', 0, True);
302 for a := 0 to High(gWalls) do
303 begin
304 if gWalls[a].Door then
305 begin
306 // Çàêðûòàÿ äâåðü:
307 if gWalls[a].Enabled then
308 with gWalls[a] do
309 g_Mark(X, Y, Width, Height, MARK_DOOR, True)
310 else // Îòêðûòàÿ äâåðü:
311 if gWalls[a].Enabled then
312 with gWalls[a] do
313 g_Mark(X, Y, Width, Height, MARK_DOOR, False);
314 end
315 else // Ñòåíà
316 with gWalls[a] do
317 g_Mark(X, Y, Width, Height, MARK_WALL, True);
318 end;
319 end;
320 end;
321 {$ENDIF}
324 procedure g_GFX_Init();
325 begin
326 //CreateCollideMap();
327 end;
330 procedure g_GFX_Free();
331 var
332 a: Integer;
333 begin
334 Particles := nil;
335 SetLength(Particles, MaxParticles);
336 for a := 0 to High(Particles) do Particles[a].die();
337 CurrentParticle := 0;
339 if OnceAnims <> nil then
340 begin
341 for a := 0 to High(OnceAnims) do
342 OnceAnims[a].Animation.Free();
344 OnceAnims := nil;
345 end;
346 end;
350 procedure CorrectOffsets(id: Integer); inline;
351 var
352 part: PParticle;
353 begin
354 part := @Particles[id];
355 part.offsetX := 0;
356 part.offsetY := 0;
357 // check for upper wall
358 if isBlockedAt(part.X, part.Y-1) then part.offsetY := 1;
359 // check for left wall
360 if isBlockedAt(part.X-1, part.Y) then part.offsetX := 1;
361 end;
365 // ////////////////////////////////////////////////////////////////////////// //
366 procedure TParticle.thinkerBlood ();
367 var
368 w, h: Integer;
369 dX, dY: SmallInt;
370 b: Integer;
371 s: ShortInt;
372 begin
373 w := gMapInfo.Width;
374 h := gMapInfo.Height;
376 if gAdvBlood then
377 begin
378 if (State = STATE_STICK) then
380 if (not ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)) and
381 (not ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)) and
382 (not ByteBool(gCollideMap[Y, X-1] and MARK_BLOCKED)) and
383 (not ByteBool(gCollideMap[Y, X+1] and MARK_BLOCKED))
384 then
386 if (not isBlockedAt(X, Y-1)) and
387 (not isBlockedAt(X, Y+1)) and
388 (not isBlockedAt(X-1, Y)) and
389 (not isBlockedAt(X+1, Y))
390 then
391 begin // Îòëèïëà - êàïàåò
392 VelY := 0.5;
393 AccelY := 0.15;
394 State := STATE_NORMAL;
395 end
396 else
397 if Random(200) = 100 then
398 begin // Ïðèëåïëåíà - íî âîçìîæíî ñòåêàåò
399 VelY := 0.5;
400 AccelY := 0.15;
401 exit;
402 end;
404 if not isBlockedAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)} then
405 begin
406 if isLiftUpAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTUP)} then
407 begin // Ëèôò ââåðõ
408 if VelY > -4-Random(3) then
409 VelY := VelY - 0.8;
410 if Abs(VelX) > 0.1 then
411 VelX := VelX - VelX/10.0;
412 VelX := VelX + (Random-Random)*0.2;
413 AccelY := 0.15;
414 end;
415 if isLiftLeftAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTLEFT)} then
416 begin // Ïîòîê âëåâî
417 if VelX > -8-Random(3) then
418 VelX := VelX - 0.8;
419 AccelY := 0.15;
420 end;
421 if isLiftRightAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTRIGHT)} then
422 begin // Ïîòîê âïðàâî
423 if VelX < 8+Random(3) then
424 VelX := VelX + 0.8;
425 AccelY := 0.15;
426 end;
427 end;
429 dX := Round(VelX);
430 dY := Round(VelY);
432 if (Abs(VelX) < 0.1) and (Abs(VelY) < 0.1) then
433 if (State <> STATE_STICK) and
434 (not isBlockedAt(X, Y-1) {ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)}) and
435 (not isBlockedAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)}) and
436 (not isBlockedAt(X, Y+1) {ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)}) then
437 begin // Âèñèò â âîçäóõå - êàïàåò
438 VelY := 0.8;
439 AccelY := 0.5;
440 State := STATE_NORMAL;
441 end;
443 if dX <> 0 then
444 begin
445 if dX > 0 then
446 s := 1
447 else
448 s := -1;
450 dX := Abs(dX);
452 for b := 1 to dX do
453 begin
454 if (X+s >= w) or (X+s <= 0) then begin die(); break; end;
456 //c := gCollideMap[Y, X+s];
458 if isBlockedAt(X+s, Y) {ByteBool(c and MARK_BLOCKED)} then
459 begin // Ñòåíà/äâåðü
460 VelX := 0;
461 VelY := 0;
462 AccelX := 0;
463 AccelY := 0;
464 State := STATE_STICK;
465 Break;
466 end;
468 X := X+s;
469 end;
470 end;
472 if dY <> 0 then
473 begin
474 if dY > 0 then
475 s := 1
476 else
477 s := -1;
479 dY := Abs(dY);
481 for b := 1 to dY do
482 begin
483 if (Y+s >= h) or (Y+s <= 0) then begin die(); break; end;
485 //c := gCollideMap[Y+s, X];
487 if isBlockedAt(X, Y+s) {ByteBool(c and MARK_BLOCKED)} then
488 begin // Ñòåíà/äâåðü
489 VelX := 0;
490 VelY := 0;
491 AccelX := 0;
492 AccelY := 0;
493 if (s > 0) and (State <> STATE_STICK) then
494 State := STATE_NORMAL
495 else
496 State := STATE_STICK;
497 Break;
498 end;
500 Y := Y+s;
501 end;
502 end;
503 end // if gAdvBlood
504 else
505 begin
506 dX := Round(VelX);
507 dY := Round(VelY);
509 if (X+dX >= w) or (Y+dY >= h) or
510 (X+dX <= 0) or (Y+dY <= 0) or
511 isBlockedAt(X+dX, Y+dY) {ByteBool(gCollideMap[Y+dY, X+dX] and MARK_BLOCKED)} then
512 begin // Ñòåíà/äâåðü/ãðàíèöà
513 die();
514 VelX := 0;
515 VelY := 0;
516 end
517 else
518 begin
519 Y := Y + dY;
520 X := X + dX;
521 end;
522 end;
524 VelX := VelX + AccelX;
525 VelY := VelY + AccelY;
527 // Êðîâü ðàñòâîðÿåòñÿ â æèäêîñòè:
528 if isLiquidAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIQUID)} then
529 begin
530 Inc(Time);
532 Alpha := 255 - Trunc((255.0 * Time) / LiveTime);
533 end;
534 end;
537 // ////////////////////////////////////////////////////////////////////////// //
538 procedure TParticle.thinkerSpark ();
539 var
540 w, h: Integer;
541 dX, dY: SmallInt;
542 b: Integer;
543 s: ShortInt;
544 begin
545 w := gMapInfo.Width;
546 h := gMapInfo.Height;
548 dX := Round(VelX);
549 dY := Round(VelY);
551 if (Abs(VelX) < 0.1) and (Abs(VelY) < 0.1) and
552 (not isBlockedAt(X, Y-1) {ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)}) and
553 (not isBlockedAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)}) and
554 (not isBlockedAt(X, Y+1) {ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)}) then
555 begin // Âèñèò â âîçäóõå
556 VelY := 0.8;
557 AccelY := 0.5;
558 end;
560 if dX <> 0 then
561 begin
562 if dX > 0 then
563 s := 1
564 else
565 s := -1;
567 dX := Abs(dX);
569 for b := 1 to dX do
570 begin
571 if (X+s >= w) or (X+s <= 0) then begin die(); break; end;
573 //c := gCollideMap[Y, X+s];
575 if isBlockedAt(X+s, Y) {ByteBool(c and MARK_BLOCKED)} then
576 begin // Ñòåíà/äâåðü - ïàäàåò âåðòèêàëüíî
577 VelX := 0;
578 AccelX := 0;
579 Break;
580 end
581 else // Ïóñòî:
582 if not isAnythingAt(X+s, Y) {c = MARK_FREE} then
583 X := X + s
584 else // Îñòàëüíîå:
585 begin
586 die();
587 break;
588 end;
589 end;
590 end;
592 if dY <> 0 then
593 begin
594 if dY > 0 then
595 s := 1
596 else
597 s := -1;
599 dY := Abs(dY);
601 for b := 1 to dY do
602 begin
603 if (Y+s >= h) or (Y+s <= 0) then begin die(); break; end;
605 //c := gCollideMap[Y+s, X];
607 if isBlockedAt(X, Y+s) {ByteBool(c and MARK_BLOCKED)} then
608 begin // Ñòåíà/äâåðü - ïàäàåò âåðòèêàëüíî
609 if s < 0 then
610 begin
611 VelY := -VelY;
612 AccelY := Abs(AccelY);
613 end
614 else // Èëè íå ïàäàåò
615 begin
616 VelX := 0;
617 AccelX := 0;
618 VelY := 0;
619 AccelY := 0.8;
620 end;
622 Break;
623 end
624 else // Ïóñòî:
625 if not isAnythingAt(X, Y+s) {c = MARK_FREE} then
626 Y := Y + s
627 else // Îñàëüíîå:
628 begin
629 die();
630 break;
631 end;
632 end;
633 end;
635 if VelX <> 0.0 then
636 VelX := VelX + AccelX;
637 if VelY <> 0.0 then
638 begin
639 if AccelY < 10 then
640 AccelY := AccelY + 0.08;
641 VelY := VelY + AccelY;
642 end;
644 Time := Time + 1;
645 end;
648 // ////////////////////////////////////////////////////////////////////////// //
649 procedure TParticle.thinkerBubble ();
650 var
651 h: Integer;
652 dY: SmallInt;
653 b: Integer;
654 s: ShortInt;
655 begin
656 h := gMapInfo.Height;
658 dY := Round(VelY);
660 if dY <> 0 then
661 begin
662 if dY > 0 then
663 s := 1
664 else
665 s := -1;
667 for b := 1 to Abs(dY) do
668 begin
669 if (Y+s >= h) or (Y+s <= 0) then begin die(); break; end;
671 (*
672 if not isLiquidAt(X, Y+s) {ByteBool(gCollideMap[Y+s, X] and MARK_LIQUID)} then
673 begin // Óæå íå æèäêîñòü
674 State := STATE_FREE;
675 Break;
676 end;
677 *)
678 // we traced liquid before, so don't bother checking
679 if (Y+s <= liquidTopY) then begin die(); break; end;
681 Y := Y+s;
682 end;
683 end;
685 if VelY > -4 then
686 VelY := VelY + AccelY;
688 Time := Time + 1;
689 end;
692 // ////////////////////////////////////////////////////////////////////////// //
693 procedure TParticle.thinkerWater ();
694 var
695 w, h: Integer;
696 dX, dY: SmallInt;
697 b: Integer;
698 s: ShortInt;
699 begin
700 w := gMapInfo.Width;
701 h := gMapInfo.Height;
703 if (State = STATE_STICK) and (Random(30) = 15) then
704 begin // Ñòåêàåò/îòëèïàåò
705 VelY := 0.5;
706 AccelY := 0.15;
707 if (not isBlockedAt(X-1, Y) {ByteBool(gCollideMap[Y, X-1] and MARK_BLOCKED)}) and
708 (not isBlockedAt(X+1, Y) {ByteBool(gCollideMap[Y, X+1] and MARK_BLOCKED)}) then
709 State := STATE_NORMAL;
710 exit;
711 end;
713 if not isBlockedAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)} then
714 begin
715 if isLiftUpAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTUP)} then
716 begin // Ëèôò ââåðõ
717 if VelY > -4-Random(3) then
718 VelY := VelY - 0.8;
719 if Abs(VelX) > 0.1 then
720 VelX := VelX - VelX/10.0;
721 VelX := VelX + (Random-Random)*0.2;
722 AccelY := 0.15;
723 end;
724 if isLiftLeftAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTLEFT)} then
725 begin // Ïîòîê âëåâî
726 if VelX > -8-Random(3) then
727 VelX := VelX - 0.8;
728 AccelY := 0.15;
729 end;
730 if isLiftRightAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTRIGHT)} then
731 begin // Ïîòîê âïðàâî
732 if VelX < 8+Random(3) then
733 VelX := VelX + 0.8;
734 AccelY := 0.15;
735 end;
736 end;
738 dX := Round(VelX);
739 dY := Round(VelY);
741 if (Abs(VelX) < 0.1) and (Abs(VelY) < 0.1) then
742 if (State <> STATE_STICK) and
743 (not isBlockedAt(X, Y-1) {ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)}) and
744 (not isBlockedAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)}) and
745 (not isBlockedAt(X, Y+1) {ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)}) then
746 begin // Âèñèò â âîçäóõå - êàïàåò
747 VelY := 0.8;
748 AccelY := 0.5;
749 State := STATE_NORMAL;
750 end;
752 if dX <> 0 then
753 begin
754 if dX > 0 then
755 s := 1
756 else
757 s := -1;
759 for b := 1 to Abs(dX) do
760 begin
761 // Ñáîêó ãðàíèöà?
762 if (X+s >= w) or (X+s <= 0) then begin die(); break;end;
764 //c := gCollideMap[Y, X+s];
766 // Ñáîêó æèäêîñòü, à ÷àñòèöà óæå ïàäàåò?
767 if isLiquidAt(X+s, Y) {ByteBool(c and MARK_LIQUID)} and (dY > 0) then begin die(); break; end;
769 if isBlockedAt(X+s, Y) {ByteBool(c and MARK_BLOCKED)} then
770 begin // Ñòåíà/äâåðü
771 VelX := 0;
772 VelY := 0;
773 AccelX := 0;
774 AccelY := 0;
775 State := STATE_STICK;
776 Break;
777 end;
779 X := X+s;
780 end;
781 end;
783 if dY <> 0 then
784 begin
785 if dY > 0 then
786 s := 1
787 else
788 s := -1;
790 for b := 1 to Abs(dY) do
791 begin
792 // Ñíèçó/ñâåðõó ãðàíèöà
793 if (Y+s >= h) or (Y+s <= 0) then begin die(); break; end;
795 //c := gCollideMap[Y+s, X];
797 // Ñíèçó æèäêîñòü, à ÷àñòèöà óæå ïàäàåò
798 if isLiquidAt(X, Y+s) {ByteBool(c and MARK_LIQUID)} and (dY > 0) then begin die(); break; end;
800 if isBlockedAt(X, Y+s) {ByteBool(c and MARK_BLOCKED)} then
801 begin // Ñòåíà/äâåðü
802 VelX := 0;
803 VelY := 0;
804 AccelX := 0;
805 AccelY := 0;
806 if (s > 0) and (State <> STATE_STICK) then
807 State := STATE_NORMAL
808 else
809 State := STATE_STICK;
810 Break;
811 end;
813 Y := Y+s;
814 end;
815 end;
817 VelX := VelX + AccelX;
818 VelY := VelY + AccelY;
820 Time := Time + 1;
821 end;
824 // ////////////////////////////////////////////////////////////////////////// //
825 procedure g_GFX_SparkVel (fX, fY: Integer; Count: Word; VX, VY: Integer; DevX, DevY: Byte);
826 var
827 a: Integer;
828 DevX1, DevX2,
829 DevY1, DevY2: Byte;
830 l: Integer;
831 begin
832 l := Length(Particles);
833 if l = 0 then exit;
834 if Count > l then Count := l;
836 DevX1 := DevX div 2;
837 DevX2 := DevX + 1;
838 DevY1 := DevY div 2;
839 DevY2 := DevY + 1;
841 for a := 1 to Count do
842 begin
843 with Particles[CurrentParticle] do
844 begin
845 X := fX-DevX1+Random(DevX2);
846 Y := fY-DevY1+Random(DevY2);
848 VelX := VX + (Random-Random)*3;
849 VelY := VY + (Random-Random)*3;
851 if VelY > -4 then
852 if VelY-4 < -4 then
853 VelY := -4
854 else
855 VelY := VelY-4;
857 AccelX := -Sign(VelX)*Random/100;
858 AccelY := 0.8;
860 Red := 255;
861 Green := 100+Random(155);
862 Blue := 64;
863 Alpha := 255;
865 State := STATE_NORMAL;
866 Time := 0;
867 LiveTime := 30+Random(60);
868 ParticleType := PARTICLE_SPARK;
870 {CorrectOffsets(CurrentParticle);}
871 end;
873 if CurrentParticle+2 > MaxParticles then
874 CurrentParticle := 0
875 else
876 CurrentParticle := CurrentParticle+1;
877 end;
878 end;
881 procedure g_GFX_Blood(fX, fY: Integer; Count: Word; vx, vy: Integer;
882 DevX, DevY: Word; CR, CG, CB: Byte; Kind: Byte = BLOOD_NORMAL);
883 var
884 a: Integer;
885 DevX1, DevX2,
886 DevY1, DevY2: Word;
887 l: Integer;
888 CRnd: Byte;
889 CC: SmallInt;
890 begin
891 if Kind = BLOOD_SPARKS then
892 begin
893 g_GFX_SparkVel(fX, fY, 2 + Random(2), -VX div 2, -VY div 2, DevX, DevY);
894 Exit;
895 end;
896 l := Length(Particles);
897 if l = 0 then
898 Exit;
899 if Count > l then
900 Count := l;
902 DevX1 := DevX div 2;
903 DevX2 := DevX + 1;
904 DevY1 := DevY div 2;
905 DevY2 := DevY + 1;
907 for a := 1 to Count do
908 begin
909 with Particles[CurrentParticle] do
910 begin
911 X := fX - DevX1 + Random(DevX2);
912 Y := fY - DevY1 + Random(DevY2);
915 if (X < 0) or (X > gMapInfo.Width-1) or
916 (Y < 0) or (Y > gMapInfo.Height-1) or
917 ByteBool(gCollideMap[Y, X] and MARK_WALL) then
918 Continue;
920 if isWallAt(X, Y) then continue;
922 VelX := vx + (Random-Random)*3;
923 VelY := vy + (Random-Random)*3;
925 if VelY > -4 then
926 if VelY-4 < -4 then
927 VelY := -4
928 else
929 VelY := VelY-4;
931 AccelX := -Sign(VelX)*Random/100;
932 AccelY := 0.8;
934 CRnd := 20*Random(6);
935 if CR > 0 then
936 begin
937 CC := CR + CRnd - 50;
938 if CC < 0 then CC := 0;
939 if CC > 255 then CC := 255;
940 Red := CC;
941 end else
942 Red := 0;
943 if CG > 0 then
944 begin
945 CC := CG + CRnd - 50;
946 if CC < 0 then CC := 0;
947 if CC > 255 then CC := 255;
948 Green := CC;
949 end else
950 Green := 0;
951 if CB > 0 then
952 begin
953 CC := CB + CRnd - 50;
954 if CC < 0 then CC := 0;
955 if CC > 255 then CC := 255;
956 Blue := CC;
957 end else
958 Blue := 0;
960 Alpha := 255;
962 State := STATE_NORMAL;
963 Time := 0;
964 LiveTime := 120+Random(40);
965 ParticleType := PARTICLE_BLOOD;
967 {CorrectOffsets(CurrentParticle);}
968 end;
970 if CurrentParticle >= MaxParticles-1 then
971 CurrentParticle := 0
972 else
973 CurrentParticle := CurrentParticle+1;
974 end;
975 end;
978 procedure g_GFX_Spark(fX, fY: Integer; Count: Word; Angle: SmallInt; DevX, DevY: Byte);
979 var
980 a: Integer;
981 b: Single;
982 DevX1, DevX2,
983 DevY1, DevY2: Byte;
984 BaseVelX, BaseVelY: Single;
985 l: Integer;
986 begin
987 l := Length(Particles);
988 if l = 0 then
989 Exit;
990 if Count > l then
991 Count := l;
993 Angle := 360 - Angle;
995 DevX1 := DevX div 2;
996 DevX2 := DevX + 1;
997 DevY1 := DevY div 2;
998 DevY2 := DevY + 1;
1000 b := DegToRad(Angle);
1001 BaseVelX := cos(b);
1002 BaseVelY := 1.6*sin(b);
1003 if Abs(BaseVelX) < 0.01 then
1004 BaseVelX := 0.0;
1005 if Abs(BaseVelY) < 0.01 then
1006 BaseVelY := 0.0;
1007 for a := 1 to Count do
1008 begin
1009 with Particles[CurrentParticle] do
1010 begin
1011 X := fX-DevX1+Random(DevX2);
1012 Y := fY-DevY1+Random(DevY2);
1014 VelX := BaseVelX*Random;
1015 VelY := BaseVelY-Random;
1016 AccelX := VelX/3.0;
1017 AccelY := VelY/5.0;
1019 Red := 255;
1020 Green := 100+Random(155);
1021 Blue := 64;
1022 Alpha := 255;
1024 State := STATE_NORMAL;
1025 Time := 0;
1026 LiveTime := 30+Random(60);
1027 ParticleType := PARTICLE_SPARK;
1029 {CorrectOffsets(CurrentParticle);}
1030 end;
1032 if CurrentParticle+2 > MaxParticles then
1033 CurrentParticle := 0
1034 else
1035 CurrentParticle := CurrentParticle+1;
1036 end;
1037 end;
1039 procedure g_GFX_Water(fX, fY: Integer; Count: Word; fVelX, fVelY: Single; DevX, DevY, Color: Byte);
1040 var
1041 a: Integer;
1042 DevX1, DevX2,
1043 DevY1, DevY2: Byte;
1044 l: Integer;
1045 begin
1046 l := Length(Particles);
1047 if l = 0 then
1048 Exit;
1049 if Count > l then
1050 Count := l;
1052 if Abs(fVelX) < 3.0 then
1053 fVelX := 3.0 - 6.0*Random;
1055 DevX1 := DevX div 2;
1056 DevX2 := DevX + 1;
1057 DevY1 := DevY div 2;
1058 DevY2 := DevY + 1;
1060 for a := 1 to Count do
1061 begin
1062 with Particles[CurrentParticle] do
1063 begin
1064 X := fX-DevX1+Random(DevX2);
1065 Y := fY-DevY1+Random(DevY2);
1067 if Abs(fVelX) < 0.5 then
1068 VelX := 1.0 - 2.0*Random
1069 else
1070 VelX := fVelX*Random;
1071 if Random(10) < 7 then
1072 VelX := -VelX;
1073 VelY := fVelY*Random;
1074 AccelX := 0.0;
1075 AccelY := 0.8;
1077 case Color of
1078 1: // Êðàñíûé
1079 begin
1080 Red := 155 + Random(9)*10;
1081 Green := Trunc(150*Random);
1082 Blue := Green;
1083 end;
1084 2: // Çåëåíûé
1085 begin
1086 Red := Trunc(150*Random);
1087 Green := 175 + Random(9)*10;
1088 Blue := Red;
1089 end;
1090 3: // Ñèíèé
1091 begin
1092 Red := Trunc(200*Random);
1093 Green := Red;
1094 Blue := 175 + Random(9)*10;
1095 end;
1096 else // Ñåðûé
1097 begin
1098 Red := 90 + Random(12)*10;
1099 Green := Red;
1100 Blue := Red;
1101 end;
1102 end;
1104 Alpha := 255;
1106 State := STATE_NORMAL;
1107 Time := 0;
1108 LiveTime := 60+Random(60);
1109 ParticleType := PARTICLE_WATER;
1111 {CorrectOffsets(CurrentParticle);}
1112 end;
1114 if CurrentParticle+2 > MaxParticles then
1115 CurrentParticle := 0
1116 else
1117 CurrentParticle := CurrentParticle+1;
1118 end;
1119 end;
1121 procedure g_GFX_SimpleWater(fX, fY: Integer; Count: Word; fVelX, fVelY: Single; DefColor, CR, CG, CB: Byte);
1122 var
1123 a: Integer;
1124 l: Integer;
1125 begin
1126 l := Length(Particles);
1127 if l = 0 then
1128 Exit;
1129 if Count > l then
1130 Count := l;
1132 for a := 1 to Count do
1133 begin
1134 with Particles[CurrentParticle] do
1135 begin
1136 X := fX;
1137 Y := fY;
1139 VelX := fVelX;
1140 VelY := fVelY;
1141 AccelX := 0.0;
1142 AccelY := 0.8;
1144 case DefColor of
1145 1: // Êðàñíûé
1146 begin
1147 Red := 155 + Random(9)*10;
1148 Green := Trunc(150*Random);
1149 Blue := Green;
1150 end;
1151 2: // Çåëåíûé
1152 begin
1153 Red := Trunc(150*Random);
1154 Green := 175 + Random(9)*10;
1155 Blue := Red;
1156 end;
1157 3: // Ñèíèé
1158 begin
1159 Red := Trunc(200*Random);
1160 Green := Red;
1161 Blue := 175 + Random(9)*10;
1162 end;
1163 4: // Ñâîé öâåò, ñâåòëåå
1164 begin
1165 Red := 20 + Random(19)*10;
1166 Green := Red;
1167 Blue := Red;
1168 Red := Min(Red + CR, 255);
1169 Green := Min(Green + CG, 255);
1170 Blue := Min(Blue + CB, 255);
1171 end;
1172 5: // Ñâîé öâåò, òåìíåå
1173 begin
1174 Red := 20 + Random(19)*10;
1175 Green := Red;
1176 Blue := Red;
1177 Red := Max(CR - Red, 0);
1178 Green := Max(CG - Green, 0);
1179 Blue := Max(CB - Blue, 0);
1180 end;
1181 else // Ñåðûé
1182 begin
1183 Red := 90 + Random(12)*10;
1184 Green := Red;
1185 Blue := Red;
1186 end;
1187 end;
1189 Alpha := 255;
1191 State := STATE_NORMAL;
1192 Time := 0;
1193 LiveTime := 60+Random(60);
1194 ParticleType := PARTICLE_WATER;
1196 {CorrectOffsets(CurrentParticle);}
1197 end;
1199 if CurrentParticle+2 > MaxParticles then
1200 CurrentParticle := 0
1201 else
1202 CurrentParticle := CurrentParticle+1;
1203 end;
1204 end;
1207 procedure g_GFX_Bubbles(fX, fY: Integer; Count: Word; DevX, DevY: Byte);
1208 var
1209 a: Integer;
1210 DevX1, DevX2,
1211 DevY1, DevY2: Byte;
1212 l, liquidx: Integer;
1213 begin
1214 l := Length(Particles);
1215 if l = 0 then
1216 Exit;
1217 if Count > l then
1218 Count := l;
1220 DevX1 := DevX div 2;
1221 DevX2 := DevX + 1;
1222 DevY1 := DevY div 2;
1223 DevY2 := DevY + 1;
1225 for a := 1 to Count do
1226 begin
1227 with Particles[CurrentParticle] do
1228 begin
1229 X := fX-DevX1+Random(DevX2);
1230 Y := fY-DevY1+Random(DevY2);
1232 if (X >= gMapInfo.Width) or (X <= 0) or
1233 (Y >= gMapInfo.Height) or (Y <= 0) then
1234 Continue;
1236 (*
1237 // don't spawn bubbles outside of the liquid
1238 if not isLiquidAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIQUID)} then
1239 Continue;
1240 *)
1242 // trace liquid, so we'll know where it ends; do it in 8px steps for speed
1243 // tracer will return `false` if we started outside of the liquid
1244 if not g_Map_TraceLiquid(X, Y, 0, -8, liquidx, liquidTopY) then continue;
1246 VelX := 0;
1247 VelY := -1-Random;
1248 AccelX := 0;
1249 AccelY := VelY/10;
1251 Red := 255;
1252 Green := 255;
1253 Blue := 255;
1254 Alpha := 255;
1256 State := STATE_NORMAL;
1257 Time := 0;
1258 LiveTime := 65535;
1259 ParticleType := PARTICLE_BUBBLES;
1261 {CorrectOffsets(CurrentParticle);}
1262 end;
1264 if CurrentParticle+2 > MaxParticles then
1265 CurrentParticle := 0
1266 else
1267 CurrentParticle := CurrentParticle+1;
1268 end;
1269 end;
1271 procedure g_GFX_SetMax(Count: Integer);
1272 var
1273 a: Integer;
1274 begin
1275 if Count > 50000 then Count := 50000;
1276 if (Count < 1) then Count := 1;
1278 SetLength(Particles, Count);
1279 for a := 0 to High(Particles) do Particles[a].die();
1280 MaxParticles := Count;
1281 //if CurrentParticle >= Count then
1282 CurrentParticle := 0;
1283 end;
1285 function g_GFX_GetMax(): Integer;
1286 begin
1287 Result := MaxParticles;
1288 end;
1290 function FindOnceAnim: DWORD;
1291 var
1292 i: Integer;
1293 begin
1294 if OnceAnims <> nil then
1295 for i := 0 to High(OnceAnims) do
1296 if OnceAnims[i].Animation = nil then
1297 begin
1298 Result := i;
1299 Exit;
1300 end;
1302 if OnceAnims = nil then
1303 begin
1304 SetLength(OnceAnims, 16);
1305 Result := 0;
1306 end
1307 else
1308 begin
1309 Result := High(OnceAnims) + 1;
1310 SetLength(OnceAnims, Length(OnceAnims) + 16);
1311 end;
1312 end;
1314 procedure g_GFX_OnceAnim(X, Y: Integer; Anim: TAnimation; AnimType: Byte = 0);
1315 var
1316 find_id: DWORD;
1317 begin
1318 if Anim = nil then
1319 Exit;
1321 find_id := FindOnceAnim();
1323 OnceAnims[find_id].AnimType := AnimType;
1324 OnceAnims[find_id].Animation := TAnimation.Create(Anim.FramesID, Anim.Loop, Anim.Speed);
1325 OnceAnims[find_id].Animation.Blending := Anim.Blending;
1326 OnceAnims[find_id].Animation.Alpha := Anim.Alpha;
1327 OnceAnims[find_id].X := X;
1328 OnceAnims[find_id].Y := Y;
1329 end;
1331 procedure g_GFX_Update();
1332 var
1333 a: Integer;
1334 w, h: Integer;
1335 len: Integer;
1336 begin
1337 if not gpart_dbg_enabled then exit;
1338 if Particles <> nil then
1339 begin
1340 w := gMapInfo.Width;
1341 h := gMapInfo.Height;
1343 len := High(Particles);
1345 for a := 0 to len do
1346 begin
1347 if Particles[a].alive then
1348 begin
1349 with Particles[a] do
1350 begin
1351 if (Time = LiveTime) then begin die(); continue; end;
1352 if (X+1 >= w) or (Y+1 >= h) or (X <= 0) or (Y <= 0) then begin die(); end;
1353 //if not alive then Continue;
1354 //e_WriteLog(Format('particle #%d: %d', [State, ParticleType]), MSG_NOTIFY);
1355 think();
1356 {CorrectOffsets(a);}
1357 end; // with
1358 end; // if
1359 end; // for
1360 end; // Particles <> nil
1362 if OnceAnims <> nil then
1363 begin
1364 for a := 0 to High(OnceAnims) do
1365 if OnceAnims[a].Animation <> nil then
1366 begin
1367 case OnceAnims[a].AnimType of
1368 ONCEANIM_SMOKE:
1369 begin
1370 if Random(3) = 0 then
1371 OnceAnims[a].X := OnceAnims[a].X-1+Random(3);
1372 if Random(2) = 0 then
1373 OnceAnims[a].Y := OnceAnims[a].Y-Random(2);
1374 end;
1375 end;
1377 if OnceAnims[a].Animation.Played then
1378 begin
1379 OnceAnims[a].Animation.Free();
1380 OnceAnims[a].Animation := nil;
1381 end
1382 else
1383 OnceAnims[a].Animation.Update();
1384 end;
1385 end;
1386 end;
1388 procedure g_GFX_Draw();
1389 var
1390 a, len: Integer;
1391 begin
1392 if Particles <> nil then
1393 begin
1394 glDisable(GL_TEXTURE_2D);
1395 glPointSize(2);
1397 glEnable(GL_BLEND);
1398 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1400 glBegin(GL_POINTS);
1402 len := High(Particles);
1404 for a := 0 to len do
1405 with Particles[a] do
1406 if alive and (X >= sX) and (Y >= sY) and (X <= sX+sWidth) and (sY <= sY+sHeight) then
1407 begin
1408 glColor4ub(Red, Green, Blue, Alpha);
1409 glVertex2i(X + offsetX, Y + offsetY);
1410 end;
1412 glEnd();
1414 glDisable(GL_BLEND);
1415 end;
1417 if OnceAnims <> nil then
1418 for a := 0 to High(OnceAnims) do
1419 if OnceAnims[a].Animation <> nil then
1420 with OnceAnims[a] do
1421 Animation.Draw(X, Y, M_NONE);
1422 end;
1424 end.