DEADSOFTWARE

new code for blood particles (other particles are turned off temporarily): almost...
[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 {$DEFINE D2F_NEW_SPARK_THINKER}
18 unit g_gfx;
20 interface
22 uses
23 e_log, g_textures;
25 const
26 BLOOD_NORMAL = 0;
27 BLOOD_SPARKS = 1;
29 ONCEANIM_NONE = 0;
30 ONCEANIM_SMOKE = 1;
32 MARK_FREE = 0;
33 MARK_WALL = 1;
34 MARK_WATER = 2;
35 MARK_ACID = 4;
36 MARK_LIFTDOWN = 8;
37 MARK_LIFTUP = 16;
38 MARK_DOOR = 32;
39 MARK_LIFTLEFT = 64;
40 MARK_LIFTRIGHT = 128;
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 ();
67 var
68 gpart_dbg_enabled: Boolean = true;
69 gpart_dbg_phys_enabled: Boolean = true;
72 implementation
74 uses
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;
80 const
81 Unknown = Integer($7fffffff);
84 type
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)
92 PParticle = ^TParticle;
93 TParticle = record
94 x, y: Integer;
95 velX, velY: Single;
96 accelX, accelY: Single;
97 red, green, blue: Byte;
98 alpha: Byte;
99 time, liveTime: Word;
100 state: TPartState;
101 particleType: TPartType;
102 offsetX, offsetY: ShortInt;
103 // for bubbles
104 liquidTopY: Integer; // don't float higher than this
105 // for water
106 stickDX: Integer; // STATE_STICK: -1,1: stuck to a wall; 0: stuck to ceiling
107 justSticked: Boolean; // not used
108 floorY: Integer; // actually, floor-1; `Unknown`: unknown
109 floorType: TFloorType;
110 ceilingY: Integer; // actually, ceiling+1; `Unknown`: unknown
111 wallEndY: Integer; // if we stuck to a wall, this is where wall ends; will never be > floorY
112 // for all
113 onGround: Boolean;
114 awaken: Boolean;
115 stickEY: Integer;
117 //k8: sorry, i have to emulate virtual methods this way, 'cause i haet `Object`
118 procedure thinkerBlood ();
119 procedure thinkerSpark ();
120 procedure thinkerBubble ();
121 procedure thinkerWater ();
123 procedure findFloor (force: Boolean=false); // this updates `floorY` if forced or Unknown
124 procedure findCeiling (force: Boolean=false); // this updates `ceilingY` if forced or Unknown
126 function isSleeping (): Boolean; inline;
127 procedure awake (); inline;
129 function alive (): Boolean; inline;
130 procedure die (); inline;
131 procedure think (); inline;
132 end;
134 TOnceAnim = record
135 AnimType: Byte;
136 x, y: Integer;
137 Animation: TAnimation;
138 end;
141 var
142 Particles: array of TParticle = nil;
143 OnceAnims: array of TOnceAnim = nil;
144 MaxParticles: Integer = 0;
145 CurrentParticle: Integer = 0;
146 // awakeMap has one bit for each map grid cell; on g_Mark,
147 // corresponding bits will be set, and in `think()` all particles
148 // in marked cells will be awaken
149 awakeMap: packed array of LongWord = nil;
150 awakeMapH: Integer = -1;
151 awakeMapW: Integer = -1;
152 awakeMinX, awakeMinY: Integer;
155 // ////////////////////////////////////////////////////////////////////////// //
156 // HACK! using mapgrid
157 procedure awmClear (); inline;
158 begin
159 if (awakeMapW > 0) then FillDWord(awakeMap[0], Length(awakeMap), 0);
160 end;
163 procedure awmSetup ();
164 begin
165 assert(mapGrid <> nil);
166 awakeMapW := (mapGrid.gridWidth+mapGrid.tileSize-1) div mapGrid.tileSize;
167 awakeMapW := (awakeMapW+31) div 32; // LongWord has 32 bits ;-)
168 awakeMapH := (mapGrid.gridHeight+mapGrid.tileSize-1) div mapGrid.tileSize;
169 awakeMinX := mapGrid.gridX0;
170 awakeMinY := mapGrid.gridY0;
171 SetLength(awakeMap, awakeMapW*awakeMapH);
172 {$IF DEFINED(D2F_DEBUG)}
173 e_LogWritefln('particle awake map: %sx%s (for grid of size %sx%s)', [awakeMapW, awakeMapH, mapGrid.gridWidth, mapGrid.gridHeight]);
174 {$ENDIF}
175 awmClear();
176 end;
179 function awmIsSet (x, y: Integer): Boolean; inline;
180 begin
181 x := (x-awakeMinX) div mapGrid.tileSize;
182 y := (y-awakeMinY) div mapGrid.tileSize;
183 if (x >= 0) and (y >= 0) and (x div 32 < awakeMapW) and (y < awakeMapH) then
184 begin
185 {$IF DEFINED(D2F_DEBUG)}
186 assert(y*awakeMapW+x div 32 < Length(awakeMap));
187 {$ENDIF}
188 result := ((awakeMap[y*awakeMapW+x div 32] and (LongWord(1) shl (x mod 32))) <> 0);
189 end
190 else
191 begin
192 result := false;
193 end;
194 end;
197 procedure awmSet (x, y: Integer); inline;
198 var
199 v: PLongWord;
200 begin
201 x := (x-awakeMinX) div mapGrid.tileSize;
202 y := (y-awakeMinY) div mapGrid.tileSize;
203 if (x >= 0) and (y >= 0) and (x div 32 < awakeMapW) and (y < awakeMapH) then
204 begin
205 {$IF DEFINED(D2F_DEBUG)}
206 assert(y*awakeMapW+x div 32 < Length(awakeMap));
207 {$ENDIF}
208 v := @awakeMap[y*awakeMapW+x div 32];
209 v^ := v^ or (LongWord(1) shl (x mod 32));
210 end;
211 end;
214 // ////////////////////////////////////////////////////////////////////////// //
215 function TParticle.alive (): Boolean; inline; begin result := (state <> TPartState.Free); end;
216 procedure TParticle.die (); inline; begin state := TPartState.Free; end;
218 function TParticle.isSleeping (): Boolean; inline;
219 begin
220 result := alive and (onGround or (not justSticked and (state = TPartState.Stuck)));
221 end;
223 procedure TParticle.awake (); inline;
224 begin
225 if {alive and} (onGround or (not justSticked and (state = TPartState.Stuck))) then
226 begin
227 // wakeup this particle
229 if (part.ParticleType = PARTICLE_SPARK) then
230 begin
231 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]);
232 end;
234 justSticked := true; // so sticked state will be re-evaluated
235 if onGround then
236 begin
237 if (velY = 0) then velY := 0.1;
238 if (accelY = 0) then accelY := 0.5;
239 end;
240 onGround := false; // so onground state will be re-evaluated
241 awaken := true;
242 end;
243 end;
246 procedure TParticle.findFloor (force: Boolean=false);
247 var
248 ex: Integer;
249 pan: TPanel;
250 begin
251 if (not force) and (floorY <> Unknown) then exit;
252 // are we in a liquid?
253 pan := g_Map_PanelAtPoint(x, y, (GridTagObstacle or GridTagLiquid));
254 if (pan <> nil) then
255 begin
256 // either in a wall, or in a liquid
257 if ((pan.tag and GridTagObstacle) <> 0) then
258 begin
259 // we are in the wall, wtf?!
260 floorY := y;
261 floorType := TFloorType.Wall;
262 state := TPartState.Sleeping; // anyway
263 exit;
264 end;
265 // we are in liquid, trace to liquid end
266 floorType := TFloorType.LiquidOut; // exiting liquid
267 //e_LogWritefln('tracing out of a liquid; floorY=%s; y=%s', [floorY, y]);
268 mapGrid.traceOrthoRayWhileIn(ex, floorY, x, y, x, g_Map_MaxY, GridTagLiquid);
269 floorY += 1; // so `floorY` is just out of a liquid
270 //e_LogWritefln(' traced out of a liquid; floorY=%s; y=%s', [floorY, y]);
271 end
272 else
273 begin
274 // not in a wall
275 pan := g_Map_traceToNearest(x, y, x, g_Map_MaxY, (GridTagObstacle or GridTagLiquid), @ex, @floorY);
276 if (pan <> nil) then
277 begin
278 // wall or liquid
279 if ((pan.tag and GridTagObstacle) <> 0) then
280 begin
281 // wall
282 floorType := TFloorType.Wall;
283 end
284 else
285 begin
286 // liquid
287 floorType := TFloorType.LiquidIn; // entering liquid
288 floorY += 1; // so `floorY` is just in a liquid
289 end;
290 end
291 else
292 begin
293 // out of the level; assume wall, but it doesn't really matter
294 floorType := TFloorType.Wall;
295 floorY := g_Map_MaxY+2;
296 end;
297 end;
298 end;
301 procedure TParticle.findCeiling (force: Boolean=false);
302 var
303 ex: Integer;
304 begin
305 if (not force) and (ceilingY <> Unknown) then exit;
306 if (nil = g_Map_traceToNearest(x, y, x, g_Map_MinY, GridTagObstacle, @ex, @ceilingY)) then
307 begin
308 ceilingY := g_Map_MinY-2;
309 end;
310 end;
313 procedure TParticle.think (); inline;
314 begin
315 // awake sleeping particle, if necessary
316 if (state = TPartState.Sleeping) and awmIsSet(x, y) then state := TPartState.Normal;
317 case particleType of
318 TPartType.Blood: thinkerBlood();
319 //TPartType.Spark: thinkerSpark();
320 //TPartType.Bubbles: thinkerBubble();
321 //TPartType.Water: thinkerWater();
322 end;
323 end;
326 // ////////////////////////////////////////////////////////////////////////// //
327 function isBlockedAt (x, y: Integer): Boolean; inline;
328 begin
329 if not gpart_dbg_phys_enabled then begin result := false; exit; end;
330 result := g_Map_HasAnyPanelAtPoint(x, y, (PANEL_WALL or PANEL_OPENDOOR or PANEL_CLOSEDOOR or PANEL_STEP));
331 end;
333 // ???
334 function isWallAt (x, y: Integer): Boolean; inline;
335 begin
336 if not gpart_dbg_phys_enabled then begin result := false; exit; end;
337 result := g_Map_HasAnyPanelAtPoint(x, y, (PANEL_WALL or PANEL_STEP));
338 end;
340 function isLiftUpAt (x, y: Integer): Boolean; inline;
341 begin
342 if not gpart_dbg_phys_enabled then begin result := false; exit; end;
343 result := g_Map_HasAnyPanelAtPoint(x, y, PANEL_LIFTUP);
344 end;
346 function isLiftDownAt (x, y: Integer): Boolean; inline;
347 begin
348 if not gpart_dbg_phys_enabled then begin result := false; exit; end;
349 result := g_Map_HasAnyPanelAtPoint(x, y, PANEL_LIFTDOWN);
350 end;
352 function isLiftLeftAt (x, y: Integer): Boolean; inline;
353 begin
354 if not gpart_dbg_phys_enabled then begin result := false; exit; end;
355 result := g_Map_HasAnyPanelAtPoint(x, y, PANEL_LIFTLEFT);
356 end;
358 function isLiftRightAt (x, y: Integer): Boolean; inline;
359 begin
360 if not gpart_dbg_phys_enabled then begin result := false; exit; end;
361 result := g_Map_HasAnyPanelAtPoint(x, y, PANEL_LIFTRIGHT);
362 end;
364 function isLiquidAt (x, y: Integer): Boolean; inline;
365 begin
366 if not gpart_dbg_phys_enabled then begin result := false; exit; end;
367 result := g_Map_HasAnyPanelAtPoint(x, y, (PANEL_WATER or PANEL_ACID1 or PANEL_ACID2));
368 end;
370 function isAnythingAt (x, y: Integer): Boolean; inline;
371 begin
372 if not gpart_dbg_phys_enabled then begin result := false; exit; end;
373 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));
374 end;
377 // ////////////////////////////////////////////////////////////////////////// //
378 // st: set mark
379 // t: mark type
380 // currently unused
381 procedure g_Mark(x, y, Width, Height: Integer; t: Byte; st: Boolean=true);
382 var
383 cx, ex, ey: Integer;
384 ts: Integer;
385 begin
386 if (Width < 1) or (Height < 1) then exit;
387 // make some border, so we'll hit particles lying around the panel
388 x -= 1; Width += 2;
389 y -= 1; Height += 2;
390 ex := x+Width;
391 ey := y+Height;
392 ts := mapGrid.tileSize;
393 while (y < ey) do
394 begin
395 cx := x;
396 while (cx < ex) do
397 begin
398 awmSet(cx, y);
399 Inc(cx, ts);
400 end;
401 Inc(y, ts);
402 end;
403 end;
406 // ////////////////////////////////////////////////////////////////////////// //
407 {$IF DEFINED(HAS_COLLIDE_BITMAP)}
408 procedure CreateCollideMap();
409 var
410 a: Integer;
411 begin
412 //g_Game_SetLoadingText(_lc[I_LOAD_COLLIDE_MAP]+' 1/6', 0, False);
413 //SetLength(gCollideMap, gMapInfo.Height+1);
414 //for a := 0 to High(gCollideMap) do SetLength(gCollideMap[a], gMapInfo.Width+1);
415 end;
416 {$ENDIF}
419 procedure g_GFX_Init();
420 begin
421 //CreateCollideMap();
422 awmSetup();
423 {$IFDEF HEADLESS}
424 gpart_dbg_enabled := False;
425 {$ENDIF}
426 end;
429 procedure g_GFX_Free();
430 var
431 a: Integer;
432 begin
433 Particles := nil;
434 SetLength(Particles, MaxParticles);
435 for a := 0 to High(Particles) do Particles[a].die();
436 CurrentParticle := 0;
438 if (OnceAnims <> nil) then
439 begin
440 for a := 0 to High(OnceAnims) do OnceAnims[a].Animation.Free();
441 OnceAnims := nil;
442 end;
444 awakeMap := nil;
445 // why not?
446 awakeMapH := -1;
447 awakeMapW := -1;
448 end;
451 // ////////////////////////////////////////////////////////////////////////// //
452 procedure TParticle.thinkerBlood ();
453 procedure stickToCeiling ();
454 begin
455 state := TPartState.Stuck;
456 stickDX := 0;
457 // stop right there, you criminal scum!
458 velX := 0;
459 velY := 0;
460 accelX := 0;
461 accelY := 0;
462 ceilingY := y; // yep
463 end;
465 procedure stickToWall (dx: Integer);
466 var
467 ex: Integer;
468 begin
469 state := TPartState.Stuck;
470 if (dX > 0) then stickDX := 1 else stickDX := -1;
471 // stop right there, you criminal scum!
472 velX := 0;
473 velY := 0;
474 accelX := 0;
475 accelY := 0;
476 // find next floor transition
477 findFloor();
478 // find `wallEndY`
479 mapGrid.traceOrthoRayWhileIn(ex, wallEndY, x+stickDX, y, x+stickDX, floorY+1, (GridTagWall or GridTagDoor or GridTagStep));
480 //if (wallEndY > floorY) then wallEndY := floorY; // just in case
481 end;
483 procedure hitAFloor ();
484 begin
485 state := TPartState.Sleeping; // we aren't moving anymore
486 // stop right there, you criminal scum!
487 velX := 0;
488 velY := 0;
489 accelX := 0;
490 accelY := 0;
491 floorY := y; // yep
492 floorType := TFloorType.Wall; // yep
493 end;
495 // `true`: didn't, get outa thinker
496 function drip (): Boolean;
497 begin
498 result := (Random(200) = 100);
499 if result then begin velY := 0.5; accelY := 0.15; end;
500 end;
502 // `true`: affected by air stream
503 function checkAirStreams (): Boolean;
504 var
505 pan: TPanel;
506 begin
507 pan := g_Map_PanelAtPoint(x, y, GridTagLift);
508 result := (pan <> nil);
509 if result then
510 begin
511 if ((pan.PanelType and PANEL_LIFTUP) <> 0) then
512 begin
513 if (velY > -4-Random(3)) then velY -= 0.8;
514 if (abs(velX) > 0.1) then velX -= velX/10.0;
515 velX += (Random-Random)*0.2;
516 accelY := 0.15;
517 end
518 else if ((pan.PanelType and PANEL_LIFTLEFT) <> 0) then
519 begin
520 if (velX > -8-Random(3)) then velX -= 0.8;
521 accelY := 0.15;
522 end
523 else if ((pan.PanelType and PANEL_LIFTRIGHT) <> 0) then
524 begin
525 if (velX < 8+Random(3)) then velX += 0.8;
526 accelY := 0.15;
527 end
528 else
529 begin
530 result := false;
531 end;
532 // awake
533 if result and (state = TPartState.Sleeping) then state := TPartState.Normal;
534 end;
535 end;
537 // switch to sleep mode
538 procedure sleep ();
539 begin
540 state := TPartState.Sleeping;
541 // stop right there, you criminal scum!
542 velX := 0;
543 velY := 0;
544 accelX := 0;
545 accelY := 0;
546 end;
548 // switch to freefall mode
549 procedure freefall ();
550 begin
551 state := TPartState.Normal;
552 velY := 0.5;
553 accelY := 0.15;
554 end;
556 procedure applyGravity (inLiquid: Boolean);
557 begin
558 state := TPartState.Normal;
559 if (inLiquid) then
560 begin
561 velY := 0.5;
562 accelY := 0.15;
563 end
564 else
565 begin
566 velY := 0.8;
567 accelY := 0.5;
568 end;
569 end;
571 label
572 _done;
573 var
574 pan: TPanel;
575 dX, dY: SmallInt;
576 ex, ey: Integer;
577 begin
578 if gAdvBlood then
579 begin
580 // still check for air streams when sleeping
581 if (state = TPartState.Sleeping) and not checkAirStreams() then exit;
583 // process stuck particles
584 if (state = TPartState.Stuck) then
585 begin
586 // stuck to a ceiling?
587 if (stickDX = 0) then
588 begin
589 assert(ceilingY <> Unknown);
590 // dropped from a ceiling?
591 if (y > ceilingY) then
592 begin
593 // yep
594 velY := 0.5;
595 accelY := 0.15;
596 state := TPartState.Normal;
597 end
598 else
599 begin
600 // otherwise, try to drip
601 if drip() then goto _done;
602 end;
603 end
604 else
605 begin
606 // stuck to a wall
607 assert(wallEndY <> Unknown);
608 // floor transition?
609 if (y = floorY) then
610 begin
611 case floorType of
612 TFloorType.Wall: // hit the ground
613 begin
614 sleep();
615 goto _done; // nothing to do anymore
616 end;
617 TFloorType.LiquidIn: // entering the liquid
618 begin
619 // rescan, so we'll know when we'll exit the liquid
620 findFloor(true); // force rescan
621 end;
622 TFloorType.LiquidOut: // exiting the liquid
623 begin
624 // rescan, so we'll know when we'll enter something interesting
625 findFloor(true); // force rescan
626 if (floorType = TFloorType.Wall) and (floorY = y) then begin sleep(); goto _done; end;
627 end;
628 end;
629 end;
630 // wall transition?
631 if (y = wallEndY) then
632 begin
633 // just unstuck from the wall, switch to freefall mode
634 freefall();
635 end
636 else
637 begin
638 // otherwise, try to drip
639 if drip() then goto _done;
640 end;
641 end;
642 // nope, process as usual
643 end;
645 // it is important to have it here
646 dX := round(velX);
647 dY := round(velY);
649 // gravity, if not stuck
650 if (state <> TPartState.Stuck) and (abs(velX) < 0.1) and (abs(velY) < 0.1) then
651 begin
652 if (floorY = Unknown) then findFloor();
653 // floor transition?
654 if (y = floorY) then
655 begin
656 case floorType of
657 TFloorType.Wall: // hit the ground
658 begin
659 // nothing to do
660 end;
661 TFloorType.LiquidIn: // entering the liquid
662 begin
663 // rescan, so we'll know when we'll exit the liquid
664 findFloor(true); // force rescan
665 applyGravity(true);
666 end;
667 TFloorType.LiquidOut: // exiting the liquid
668 begin
669 // rescan, so we'll know when we'll enter something interesting
670 findFloor(true); // force rescan
671 if (floorType <> TFloorType.Wall) or (floorY <> y) then applyGravity(floorType = TFloorType.LiquidIn);
672 end;
673 end;
674 end
675 else
676 begin
677 // looks like we're in the air
678 applyGravity(false);
679 end;
680 end;
682 // horizontal movement
683 if (dX <> 0) then
684 begin
685 pan := g_Map_traceToNearest(x, y, x+dX, y, (GridTagWall or GridTagDoor or GridTagStep), @ex, @ey);
686 if (x <> ex) then begin floorY := Unknown; ceilingY := Unknown; end; // dunno yet
687 x := ex;
688 if (x < g_Map_MinX) or (x > g_Map_MaxX) then begin die(); exit; end;
689 if (pan <> nil) then stickToWall(dX); // nope, we stuck
690 end;
692 // vertical movement
693 if (dY < 0) then
694 begin
695 // flying up
696 if (ceilingY = Unknown) then findCeiling(); // need to do this anyway
697 y += dY;
698 if (y <= ceilingY) then begin y := ceilingY; stickToCeiling(); end; // oops, hit a ceiling
699 end
700 else
701 begin
702 while (dY > 0) do
703 begin
704 // falling down
705 if (floorY = Unknown) then findFloor(); // need to do this anyway
706 y += dY;
707 //e_LogWritefln('floorY=%s; newy=%s; dY=%s; floorType=%s', [floorY, y, dY, floorType]);
708 if (y >= floorY) then
709 begin
710 // floor transition
711 dY := y-floorY;
712 y := floorY;
713 //e_LogWritefln(' HIT FLOORY: floorY=%s; newy=%s; dY=%s; floorType=%s', [floorY, y, dY, floorType]);
714 case floorType of
715 TFloorType.Wall: // hit the ground
716 begin
717 hitAFloor();
718 break; // done with vertical movement
719 end;
720 TFloorType.LiquidIn: // entering the liquid
721 begin
722 // rescan, so we'll know when we'll exit the liquid
723 findFloor(true); // force rescan
724 end;
725 TFloorType.LiquidOut: // exiting the liquid
726 begin
727 // rescan, so we'll know when we'll enter something interesting
728 findFloor(true); // force rescan
729 if (floorType = TFloorType.Wall) and (floorY = y) then
730 begin
731 hitAFloor();
732 break; // done with vertical movement
733 end;
734 end;
735 end;
736 end
737 else
738 begin
739 break; // done with vertical movement
740 end;
741 end;
742 end;
743 if (y < g_Map_MinY) or (y > g_Map_MaxY) then begin die(); exit; end;
744 end // if gAdvBlood
745 else
746 begin
747 // simple blood
748 dX := Round(velX);
749 dY := Round(velY);
750 if (x+dX > g_Map_MaxX) or (y+dY > g_Map_MaxY) or (x+dX < g_Map_MinX) or (y+dY < g_Map_MinY) or isBlockedAt(x+dX, y+dY) then
751 begin
752 // Ñòåíà/äâåðü/ãðàíèöà
753 die();
754 exit;
755 end
756 else
757 begin
758 y += dY;
759 x += dX;
760 end;
761 end;
763 _done:
764 velX += accelX;
765 velY += accelY;
767 // blood will dissolve in other liquids
768 if (floorType = TFloorType.LiquidOut) then
769 begin
770 time += 1;
771 alpha := 255-trunc((255.0*time)/liveTime);
772 end;
773 (*
774 // Êðîâü ðàñòâîðÿåòñÿ â æèäêîñòè
775 if isLiquidAt(x, y) {ByteBool(gCollideMap[Y, X] and MARK_LIQUID)} then
776 begin
777 time += 1;
778 alpha := 255-trunc((255.0*time)/liveTime);
779 end;
780 *)
781 end;
784 procedure g_GFX_SparkVel (fX, fY: Integer; Count: Word; VX, VY: Integer; DevX, DevY: Byte); forward;
786 procedure g_GFX_Blood (fX, fY: Integer; Count: Word; vx, vy: Integer;
787 DevX, DevY: Word; CR, CG, CB: Byte; Kind: Byte = BLOOD_NORMAL);
788 var
789 a: Integer;
790 DevX1, DevX2, DevY1, DevY2: Word;
791 l: Integer;
792 CRnd: Byte;
793 CC: SmallInt;
794 begin
795 if not gpart_dbg_enabled then Exit;
797 if (Kind = BLOOD_SPARKS) then
798 begin
799 g_GFX_SparkVel(fX, fY, 2+Random(2), -VX div 2, -VY div 2, DevX, DevY);
800 exit;
801 end;
803 l := Length(Particles);
804 if (l = 0) then exit;
805 if (Count > l) then Count := l;
807 DevX1 := DevX div 2;
808 DevX2 := DevX+1;
809 DevY1 := DevY div 2;
810 DevY2 := DevY+1;
812 for a := 1 to Count do
813 begin
814 with Particles[CurrentParticle] do
815 begin
816 x := fX-DevX1+Random(DevX2);
817 y := fY-DevY1+Random(DevY2);
820 if (X < 0) or (X > gMapInfo.Width-1) or
821 (Y < 0) or (Y > gMapInfo.Height-1) or
822 ByteBool(gCollideMap[Y, X] and MARK_WALL) then
823 Continue;
825 if isWallAt(x, y) then continue;
827 velX := vx+(Random-Random)*3;
828 velY := vy+(Random-Random)*3;
830 if (velY > -4) then
831 begin
832 if (velY-4 < -4) then velY := -4 else velY := velY-4;
833 end;
835 accelX := -sign(velX)*Random/100;
836 accelY := 0.8;
838 CRnd := 20*Random(6);
839 if (CR > 0) then
840 begin
841 CC := CR+CRnd-50;
842 if (CC < 0) then CC := 0
843 else if (CC > 255) then CC := 255;
844 red := CC;
845 end
846 else
847 begin
848 red := 0;
849 end;
850 if CG > 0 then
851 begin
852 CC := CG+CRnd-50;
853 if (CC < 0) then CC := 0
854 else if (CC > 255) then CC := 255;
855 green := CC;
856 end
857 else
858 begin
859 green := 0;
860 end;
861 if (CB > 0) then
862 begin
863 CC := CB+CRnd-50;
864 if (CC < 0) then CC := 0
865 else if (CC > 255) then CC := 255;
866 blue := CC;
867 end
868 else
869 begin
870 blue := 0;
871 end;
873 alpha := 255;
875 state := TPartState.Normal;
876 time := 0;
877 liveTime := 120+Random(40);
878 particleType := TPartType.Blood;
879 //justSticked := false;
880 //onGround := (velY >= 0) and g_Map_HasAnyPanelAtPoint(x, y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
881 //awaken := false;
882 //stickEY := 0;
883 floorY := Unknown;
884 ceilingY := Unknown;
885 end;
887 if CurrentParticle >= MaxParticles-1 then
888 CurrentParticle := 0
889 else
890 CurrentParticle := CurrentParticle+1;
891 end;
892 end;
895 // ////////////////////////////////////////////////////////////////////////// //
896 procedure TParticle.thinkerWater ();
897 var
898 dX, dY: SmallInt;
899 {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
900 w, h: Integer;
901 b: Integer;
902 s: ShortInt;
903 {$ELSE}
904 pan: TPanel;
905 ex, ey: Integer;
906 {$ENDIF}
907 begin
908 {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
909 w := gMapInfo.Width;
910 h := gMapInfo.Height;
911 {$ENDIF}
913 //TODO: trace wall end when water becomes stick
914 if (state = TPartState.Stuck) and (Random(30) = 15) then
915 begin // Ñòåêàåò/îòëèïàåò
916 velY := 0.5;
917 accelY := 0.15;
918 {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
919 if (not isBlockedAt(x-1, y) {ByteBool(gCollideMap[Y, X-1] and MARK_BLOCKED)}) and
920 (not isBlockedAt(x+1, y) {ByteBool(gCollideMap[Y, X+1] and MARK_BLOCKED)}) then
921 state := TPartState.Normal;
922 {$ELSE}
923 if (stickDX = 0) then
924 begin
925 // no walls around, drop
926 state := TPartState.Normal;
927 end
928 else
929 begin
930 if justSticked then
931 begin
932 if not mapGrid.traceOrthoRayWhileIn(ex, ey, x+stickDX, y, x+stickDX, mapGrid.gridY0+mapGrid.gridHeight, GridTagWall or GridTagDoor or GridTagStep) then
933 begin
934 // îòëèïëà
935 state := TPartState.Normal;
936 //e_LogWritefln('juststicked unsticked: X=%s; X+stickDX=%s; stickDX=%s; Y=%s', [X, X+stickDX, stickDX, Y]);
937 end
938 else
939 begin
940 stickEY := ey+1;
941 justSticked := false;
942 if (nil <> g_Map_traceToNearest(x, y, x, stickEY, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey)) then
943 begin
944 if (ey > stickEY) then stickEY := ey-1;
945 end;
946 //e_LogWritefln('juststicked: X=%s; X+stickDX=%s; stickDX=%s; Y=%s; stickEY=%s', [X, X+stickDX, stickDX, Y, stickEY]);
947 end;
948 end
949 else
950 begin
951 if (y >= stickEY) then state := TPartState.Normal;
952 end;
953 //if not g_Map_CollidePanel(X-1, Y-1, 3, 3, (PANEL_STEP or PANEL_WALL or PANEL_OPENDOOR or PANEL_CLOSEDOOR))
954 end;
955 {$ENDIF}
956 exit;
957 end;
959 {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
960 if not isBlockedAt(x, y) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)} then
961 begin
962 if isLiftUpAt(x, y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTUP)} then
963 begin // Ëèôò ââåðõ
964 if velY > -4-Random(3) then
965 velY := velY - 0.8;
966 if Abs(velX) > 0.1 then
967 velX := velX - velX/10.0;
968 velX := velX + (Random-Random)*0.2;
969 accelY := 0.15;
970 end;
971 if isLiftLeftAt(x, y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTLEFT)} then
972 begin // Ïîòîê âëåâî
973 if velX > -8-Random(3) then
974 velX := velX - 0.8;
975 accelY := 0.15;
976 end;
977 if isLiftRightAt(x, y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTRIGHT)} then
978 begin // Ïîòîê âïðàâî
979 if velX < 8+Random(3) then
980 velX := velX + 0.8;
981 accelY := 0.15;
982 end;
983 end;
984 {$ELSE}
985 pan := g_Map_PanelAtPoint(x, y, (GridTagAcid1 or GridTagAcid2 or GridTagWater or GridTagLift));
986 if (pan <> nil) then
987 begin
988 if ((pan.tag and (GridTagAcid1 or GridTagAcid2 or GridTagWater)) <> 0) then begin die(); exit; end;
989 if ((pan.PanelType and PANEL_LIFTUP) <> 0) then
990 begin
991 if (velY > -4-Random(3)) then velY -= 0.8;
992 if (Abs(velX) > 0.1) then velX -= velX/10.0;
993 velX += (Random-Random)*0.2;
994 accelY := 0.15;
995 end;
996 if ((pan.PanelType and PANEL_LIFTLEFT) <> 0) then
997 begin
998 if (velX > -8-Random(3)) then velX -= 0.8;
999 accelY := 0.15;
1000 end;
1001 if ((pan.PanelType and PANEL_LIFTRIGHT) <> 0) then
1002 begin
1003 if (velX < 8+Random(3)) then velX += 0.8;
1004 accelY := 0.15;
1005 end;
1006 end;
1007 {$ENDIF}
1009 dX := Round(velX);
1010 dY := Round(velY);
1012 {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
1013 if (Abs(velX) < 0.1) and (Abs(velY) < 0.1) then
1014 begin
1015 if (state <> TPartState.Stuck) and
1016 (not isBlockedAt(x, y-1) {ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)}) and
1017 (not isBlockedAt(x, y) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)}) and
1018 (not isBlockedAt(x, y+1) {ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)}) then
1019 begin // Âèñèò â âîçäóõå - êàïàåò
1020 velY := 0.8;
1021 accelY := 0.5;
1022 state := TPartState.Normal;
1023 end;
1024 end;
1025 {$ELSE}
1026 if (state <> TPartState.Stuck) and (Abs(velX) < 0.1) and (Abs(velY) < 0.1) then
1027 begin
1028 // Âèñèò â âîçäóõå - êàïàåò
1029 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
1030 begin
1031 velY := 0.8;
1032 accelY := 0.5;
1033 state := TPartState.Normal;
1034 end;
1035 end;
1036 {$ENDIF}
1038 {$IF DEFINED(D2F_NEW_SPARK_THINKER)}
1039 // horizontal
1040 if (dX <> 0) then
1041 begin
1042 pan := g_Map_traceToNearest(x, y, x+dX, y, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey);
1043 if (x <> ex) then onGround := false;
1044 x := ex;
1045 // free to ride?
1046 if (pan <> nil) then
1047 begin
1048 // nope
1049 if (dY > 0) and ((pan.tag and (GridTagAcid1 or GridTagAcid2 or GridTagWater)) <> 0) then begin die(); exit; end;
1050 // Ñòåíà/äâåðü?
1051 if ((pan.tag and (GridTagWall or GridTagDoor or GridTagStep)) <> 0) then
1052 begin
1053 velX := 0;
1054 velY := 0;
1055 accelX := 0;
1056 accelY := 0;
1057 state := TPartState.Stuck;
1058 justSticked := true;
1059 if (dX > 0) then stickDX := 1 else stickDX := -1;
1060 end;
1061 end;
1062 if (x < 0) or (x >= gMapInfo.Width) then begin die(); exit; end;
1063 end;
1064 // vertical
1065 if (dY <> 0) then
1066 begin
1067 if (dY < 0) or not onGround then
1068 begin
1069 pan := g_Map_traceToNearest(x, y, x, y+dY, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey);
1070 y := ey;
1071 // free to ride?
1072 if (pan <> nil) then
1073 begin
1074 // nope
1075 if (dY > 0) and ((pan.tag and (GridTagAcid1 or GridTagAcid2 or GridTagWater)) <> 0) then begin die(); exit; end;
1076 // Ñòåíà/äâåðü?
1077 if ((pan.tag and (GridTagWall or GridTagDoor or GridTagStep)) <> 0) then
1078 begin
1079 velX := 0;
1080 velY := 0;
1081 accelX := 0;
1082 accelY := 0;
1083 if (dY > 0) and (state <> TPartState.Stuck) then
1084 begin
1085 state := TPartState.Normal;
1086 end
1087 else
1088 begin
1089 state := TPartState.Stuck;
1090 if (g_Map_PanelAtPoint(x-1, y, (GridTagWall or GridTagDoor or GridTagStep)) <> nil) then stickDX := -1
1091 else if (g_Map_PanelAtPoint(x+1, y, (GridTagWall or GridTagDoor or GridTagStep)) <> nil) then stickDX := 1
1092 else stickDX := 0;
1093 justSticked := true;
1094 end;
1095 end;
1096 end;
1097 onGround := (velY >= 0) and g_Map_HasAnyPanelAtPoint(x, y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
1098 end;
1099 if (y < 0) or (y >= gMapInfo.Height) then begin die(); exit; end;
1100 end;
1101 {$ELSE}
1102 // horizontal
1103 if (dX <> 0) then
1104 begin
1105 if (dX > 0) then s := 1 else s := -1;
1106 for b := 1 to Abs(dX) do
1107 begin
1108 // Ñáîêó ãðàíèöà?
1109 if (x+s >= w) or (x+s <= 0) then begin die(); break;end;
1110 //c := gCollideMap[Y, X+s];
1111 // Ñáîêó æèäêîñòü, à ÷àñòèöà óæå ïàäàåò?
1112 if isLiquidAt(x+s, y) {ByteBool(c and MARK_LIQUID)} and (dY > 0) then begin die(); break; end;
1113 if isBlockedAt(x+s, y) {ByteBool(c and MARK_BLOCKED)} then
1114 begin // Ñòåíà/äâåðü
1115 velX := 0;
1116 velY := 0;
1117 accelX := 0;
1118 accelY := 0;
1119 state := TPartState.Stuck;
1120 justSticked := true;
1121 Break;
1122 end;
1123 x := x+s;
1124 end;
1125 end;
1126 // vertical
1127 if (dY <> 0) then
1128 begin
1129 if (dY > 0) then s := 1 else s := -1;
1130 for b := 1 to Abs(dY) do
1131 begin
1132 // Ñíèçó/ñâåðõó ãðàíèöà
1133 if (y+s >= h) or (y+s <= 0) then begin die(); break; end;
1134 //c := gCollideMap[Y+s, X];
1135 // Ñíèçó æèäêîñòü, à ÷àñòèöà óæå ïàäàåò
1136 if isLiquidAt(x, y+s) {ByteBool(c and MARK_LIQUID)} and (dY > 0) then begin die(); break; end;
1137 if isBlockedAt(x, y+s) {ByteBool(c and MARK_BLOCKED)} then
1138 begin // Ñòåíà/äâåðü
1139 velX := 0;
1140 velY := 0;
1141 accelX := 0;
1142 accelY := 0;
1143 if (s > 0) and (state <> TPartState.Stuck) then state := TPartState.Normal else state := TPartState.Stuck;
1144 justSticked := (state = TPartState.Stuck);
1145 break;
1146 end;
1147 y := y+s;
1148 end;
1149 end;
1150 {$ENDIF}
1152 velX += accelX;
1153 velY += accelY;
1155 time += 1;
1156 end;
1159 // ////////////////////////////////////////////////////////////////////////// //
1160 procedure TParticle.thinkerSpark ();
1161 var
1162 dX, dY: SmallInt;
1163 {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
1164 b: Integer;
1165 s: ShortInt;
1166 {$ELSE}
1167 pan: TPanel;
1168 ex, ey: Integer;
1169 {$ENDIF}
1170 begin
1171 dX := Round(velX);
1172 dY := Round(velY);
1174 {$IF DEFINED(D2F_NEW_SPARK_THINKER)}
1175 if (Abs(velX) < 0.1) and (Abs(velY) < 0.1) then
1176 begin
1177 pan := g_Map_traceToNearest(x, y-1, x, y+1, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey);
1178 end;
1179 {$ELSE}
1180 if (Abs(velX) < 0.1) and (Abs(velY) < 0.1) and
1181 (not isBlockedAt(x, y-1) {ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)}) and
1182 (not isBlockedAt(x, y) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)}) and
1183 (not isBlockedAt(x, y+1) {ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)}) then
1184 begin // Âèñèò â âîçäóõå
1185 velY := 0.8;
1186 accelY := 0.5;
1187 end;
1188 {$ENDIF}
1190 if (dX <> 0) then
1191 begin
1192 {$IF DEFINED(D2F_NEW_SPARK_THINKER)}
1193 pan := g_Map_traceToNearest(x, y, x+dX, y, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey);
1194 //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);
1195 if (x <> ex) then onGround := false;
1196 x := ex;
1197 // free to ride?
1198 if (pan <> nil) then
1199 begin
1200 // nope
1201 if ((pan.tag and (GridTagAcid1 or GridTagAcid2 or GridTagWater)) <> 0) then begin die(); exit; end;
1202 velX := 0;
1203 accelX := 0;
1204 end;
1205 if (x < 0) or (x >= gMapInfo.Width) then begin die(); exit; end;
1206 {$ELSE}
1207 if (dX > 0) then s := 1 else s := -1;
1208 dX := Abs(dX);
1209 for b := 1 to dX do
1210 begin
1211 if (x+s >= gMapInfo.Width) or (x+s <= 0) then begin die(); break; end;
1212 //c := gCollideMap[Y, X+s];
1213 if isBlockedAt(x+s, y) {ByteBool(c and MARK_BLOCKED)} then
1214 begin // Ñòåíà/äâåðü - ïàäàåò âåðòèêàëüíî
1215 velX := 0;
1216 accelX := 0;
1217 Break;
1218 end
1219 else // Ïóñòî:
1220 if not isAnythingAt(x+s, y) {c = MARK_FREE} then
1221 x := x + s
1222 else // Îñòàëüíîå:
1223 begin
1224 die();
1225 break;
1226 end;
1227 end;
1228 {$ENDIF}
1229 end;
1231 if (dY <> 0) then
1232 begin
1233 {$IF DEFINED(D2F_NEW_SPARK_THINKER)}
1234 if (dY < 0) or not onGround then
1235 begin
1236 pan := g_Map_traceToNearest(x, y, x, y+dY, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey);
1237 y := ey;
1239 if awaken then
1240 begin
1241 awaken := false;
1242 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]);
1243 end;
1245 // free to ride?
1246 if (pan <> nil) then
1247 begin
1248 // nope
1249 if ((pan.tag and (GridTagAcid1 or GridTagAcid2 or GridTagWater)) <> 0) then begin die(); exit; end;
1250 if (dY < 0) then
1251 begin
1252 velY := -velY;
1253 accelY := abs(accelY);
1254 end
1255 else
1256 begin
1257 velX := 0;
1258 accelX := 0;
1259 velY := 0;
1260 accelY := 0.8;
1261 end;
1262 end;
1263 onGround := (velY >= 0) and g_Map_HasAnyPanelAtPoint(x, y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
1264 end;
1265 if (y < 0) or (y >= gMapInfo.Height) then begin die(); exit; end;
1266 {$ELSE}
1267 if (dY > 0) then s := 1 else s := -1;
1268 dY := Abs(dY);
1269 for b := 1 to dY do
1270 begin
1271 if (y+s >= gMapInfo.Height) or (y+s <= 0) then begin die(); break; end;
1272 //c := gCollideMap[Y+s, X];
1273 if isBlockedAt(x, y+s) {ByteBool(c and MARK_BLOCKED)} then
1274 begin // Ñòåíà/äâåðü - ïàäàåò âåðòèêàëüíî
1275 if s < 0 then
1276 begin
1277 velY := -velY;
1278 accelY := Abs(accelY);
1279 end
1280 else // Èëè íå ïàäàåò
1281 begin
1282 velX := 0;
1283 accelX := 0;
1284 velY := 0;
1285 accelY := 0.8;
1286 end;
1288 Break;
1289 end
1290 else // Ïóñòî:
1291 if not isAnythingAt(x, y+s) {c = MARK_FREE} then
1292 y := y + s
1293 else // Îñàëüíîå:
1294 begin
1295 die();
1296 break;
1297 end;
1298 end;
1299 {$ENDIF}
1300 end;
1302 if (velX <> 0.0) then velX += accelX;
1304 if (velY <> 0.0) then
1305 begin
1306 if (accelY < 10) then accelY += 0.08;
1307 velY += accelY;
1308 end;
1310 time += 1;
1311 end;
1313 // ////////////////////////////////////////////////////////////////////////// //
1314 procedure TParticle.thinkerBubble ();
1315 var
1316 h: Integer;
1317 dY: SmallInt;
1318 b: Integer;
1319 s: ShortInt;
1320 begin
1321 h := gMapInfo.Height;
1323 dY := Round(velY);
1325 if dY <> 0 then
1326 begin
1327 if dY > 0 then
1328 s := 1
1329 else
1330 s := -1;
1332 for b := 1 to Abs(dY) do
1333 begin
1334 if (y+s >= h) or (y+s <= 0) then begin die(); break; end;
1336 (*
1337 if not isLiquidAt(X, Y+s) {ByteBool(gCollideMap[Y+s, X] and MARK_LIQUID)} then
1338 begin // Óæå íå æèäêîñòü
1339 State := STATE_FREE;
1340 Break;
1341 end;
1342 *)
1343 // we traced liquid before, so don't bother checking
1344 if (y+s <= liquidTopY) then begin die(); break; end;
1346 y := y+s;
1347 end;
1348 end;
1350 if velY > -4 then
1351 velY := velY + accelY;
1353 time := time + 1;
1354 end;
1357 // ////////////////////////////////////////////////////////////////////////// //
1358 procedure g_GFX_SparkVel (fX, fY: Integer; Count: Word; VX, VY: Integer; DevX, DevY: Byte);
1359 var
1360 a: Integer;
1361 DevX1, DevX2,
1362 DevY1, DevY2: Byte;
1363 l: Integer;
1364 begin
1365 exit;
1366 if not gpart_dbg_enabled then Exit;
1367 l := Length(Particles);
1368 if l = 0 then exit;
1369 if Count > l then Count := l;
1371 DevX1 := DevX div 2;
1372 DevX2 := DevX + 1;
1373 DevY1 := DevY div 2;
1374 DevY2 := DevY + 1;
1376 for a := 1 to Count do
1377 begin
1378 with Particles[CurrentParticle] do
1379 begin
1380 x := fX-DevX1+Random(DevX2);
1381 y := fY-DevY1+Random(DevY2);
1383 velX := VX + (Random-Random)*3;
1384 velY := VY + (Random-Random)*3;
1386 if velY > -4 then
1387 if velY-4 < -4 then
1388 velY := -4
1389 else
1390 velY := velY-4;
1392 accelX := -Sign(velX)*Random/100;
1393 accelY := 0.8;
1395 red := 255;
1396 green := 100+Random(155);
1397 blue := 64;
1398 alpha := 255;
1400 state := TPartState.Normal;
1401 time := 0;
1402 liveTime := 30+Random(60);
1403 particleType := TPartType.Spark;
1404 justSticked := false;
1405 onGround := (velY >= 0) and g_Map_HasAnyPanelAtPoint(x, y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
1406 awaken := false;
1407 end;
1409 if CurrentParticle+2 > MaxParticles then
1410 CurrentParticle := 0
1411 else
1412 CurrentParticle := CurrentParticle+1;
1413 end;
1414 end;
1417 procedure g_GFX_Spark(fX, fY: Integer; Count: Word; Angle: SmallInt; DevX, DevY: Byte);
1418 var
1419 a: Integer;
1420 b: Single;
1421 DevX1, DevX2,
1422 DevY1, DevY2: Byte;
1423 BaseVelX, BaseVelY: Single;
1424 l: Integer;
1425 begin
1426 exit;
1427 if not gpart_dbg_enabled then Exit;
1428 l := Length(Particles);
1429 if l = 0 then
1430 Exit;
1431 if Count > l then
1432 Count := l;
1434 Angle := 360 - Angle;
1436 DevX1 := DevX div 2;
1437 DevX2 := DevX + 1;
1438 DevY1 := DevY div 2;
1439 DevY2 := DevY + 1;
1441 b := DegToRad(Angle);
1442 BaseVelX := cos(b);
1443 BaseVelY := 1.6*sin(b);
1444 if Abs(BaseVelX) < 0.01 then
1445 BaseVelX := 0.0;
1446 if Abs(BaseVelY) < 0.01 then
1447 BaseVelY := 0.0;
1448 for a := 1 to Count do
1449 begin
1450 with Particles[CurrentParticle] do
1451 begin
1452 x := fX-DevX1+Random(DevX2);
1453 y := fY-DevY1+Random(DevY2);
1455 velX := BaseVelX*Random;
1456 velY := BaseVelY-Random;
1457 accelX := velX/3.0;
1458 accelY := velY/5.0;
1460 red := 255;
1461 green := 100+Random(155);
1462 blue := 64;
1463 alpha := 255;
1465 state := TPartState.Normal;
1466 time := 0;
1467 liveTime := 30+Random(60);
1468 particleType := TPartType.Spark;
1469 justSticked := false;
1470 onGround := (velY >= 0) and g_Map_HasAnyPanelAtPoint(x, y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
1471 awaken := false;
1472 end;
1474 if CurrentParticle+2 > MaxParticles then
1475 CurrentParticle := 0
1476 else
1477 CurrentParticle := CurrentParticle+1;
1478 end;
1479 end;
1482 procedure g_GFX_Water(fX, fY: Integer; Count: Word; fVelX, fVelY: Single; DevX, DevY, Color: Byte);
1483 var
1484 a: Integer;
1485 DevX1, DevX2,
1486 DevY1, DevY2: Byte;
1487 l: Integer;
1488 begin
1489 exit;
1490 if not gpart_dbg_enabled then Exit;
1491 l := Length(Particles);
1492 if l = 0 then
1493 Exit;
1494 if Count > l then
1495 Count := l;
1497 if Abs(fVelX) < 3.0 then
1498 fVelX := 3.0 - 6.0*Random;
1500 DevX1 := DevX div 2;
1501 DevX2 := DevX + 1;
1502 DevY1 := DevY div 2;
1503 DevY2 := DevY + 1;
1505 for a := 1 to Count do
1506 begin
1507 with Particles[CurrentParticle] do
1508 begin
1509 x := fX-DevX1+Random(DevX2);
1510 y := fY-DevY1+Random(DevY2);
1512 if Abs(fVelX) < 0.5 then
1513 velX := 1.0 - 2.0*Random
1514 else
1515 velX := fVelX*Random;
1516 if Random(10) < 7 then
1517 velX := -velX;
1518 velY := fVelY*Random;
1519 accelX := 0.0;
1520 accelY := 0.8;
1522 case Color of
1523 1: // Êðàñíûé
1524 begin
1525 red := 155 + Random(9)*10;
1526 green := Trunc(150*Random);
1527 blue := green;
1528 end;
1529 2: // Çåëåíûé
1530 begin
1531 red := Trunc(150*Random);
1532 green := 175 + Random(9)*10;
1533 blue := red;
1534 end;
1535 3: // Ñèíèé
1536 begin
1537 red := Trunc(200*Random);
1538 green := red;
1539 blue := 175 + Random(9)*10;
1540 end;
1541 else // Ñåðûé
1542 begin
1543 red := 90 + Random(12)*10;
1544 green := red;
1545 blue := red;
1546 end;
1547 end;
1549 alpha := 255;
1551 state := TPartState.Normal;
1552 time := 0;
1553 liveTime := 60+Random(60);
1554 particleType := TPartType.Water;
1555 justSticked := false;
1556 onGround := (velY >= 0) and g_Map_HasAnyPanelAtPoint(x, y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
1557 awaken := false;
1558 end;
1560 if CurrentParticle+2 > MaxParticles then
1561 CurrentParticle := 0
1562 else
1563 CurrentParticle := CurrentParticle+1;
1564 end;
1565 end;
1568 procedure g_GFX_SimpleWater(fX, fY: Integer; Count: Word; fVelX, fVelY: Single; DefColor, CR, CG, CB: Byte);
1569 var
1570 a: Integer;
1571 l: Integer;
1572 begin
1573 exit;
1574 if not gpart_dbg_enabled then Exit;
1575 l := Length(Particles);
1576 if l = 0 then
1577 Exit;
1578 if Count > l then
1579 Count := l;
1581 for a := 1 to Count do
1582 begin
1583 with Particles[CurrentParticle] do
1584 begin
1585 x := fX;
1586 y := fY;
1588 velX := fVelX;
1589 velY := fVelY;
1590 accelX := 0.0;
1591 accelY := 0.8;
1593 case DefColor of
1594 1: // Êðàñíûé
1595 begin
1596 red := 155 + Random(9)*10;
1597 green := Trunc(150*Random);
1598 blue := green;
1599 end;
1600 2: // Çåëåíûé
1601 begin
1602 red := Trunc(150*Random);
1603 green := 175 + Random(9)*10;
1604 blue := red;
1605 end;
1606 3: // Ñèíèé
1607 begin
1608 red := Trunc(200*Random);
1609 green := red;
1610 blue := 175 + Random(9)*10;
1611 end;
1612 4: // Ñâîé öâåò, ñâåòëåå
1613 begin
1614 red := 20 + Random(19)*10;
1615 green := red;
1616 blue := red;
1617 red := Min(red + CR, 255);
1618 green := Min(green + CG, 255);
1619 blue := Min(blue + CB, 255);
1620 end;
1621 5: // Ñâîé öâåò, òåìíåå
1622 begin
1623 red := 20 + Random(19)*10;
1624 green := red;
1625 blue := red;
1626 red := Max(CR - red, 0);
1627 green := Max(CG - green, 0);
1628 blue := Max(CB - blue, 0);
1629 end;
1630 else // Ñåðûé
1631 begin
1632 red := 90 + Random(12)*10;
1633 green := red;
1634 blue := red;
1635 end;
1636 end;
1638 alpha := 255;
1640 state := TPartState.Normal;
1641 time := 0;
1642 liveTime := 60+Random(60);
1643 particleType := TPartType.Water;
1644 justSticked := false;
1645 onGround := (velY >= 0) and g_Map_HasAnyPanelAtPoint(x, y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
1646 awaken := false;
1647 end;
1649 if CurrentParticle+2 > MaxParticles then
1650 CurrentParticle := 0
1651 else
1652 CurrentParticle := CurrentParticle+1;
1653 end;
1654 end;
1657 {.$DEFINE D2F_DEBUG_BUBBLES}
1658 procedure g_GFX_Bubbles(fX, fY: Integer; Count: Word; DevX, DevY: Byte);
1659 var
1660 a: Integer;
1661 DevX1, DevX2,
1662 DevY1, DevY2: Byte;
1663 l, liquidx: Integer;
1664 {$IF DEFINED(D2F_DEBUG_BUBBLES)}
1665 stt: UInt64;
1666 nptr, ptr: Boolean;
1667 {$ENDIF}
1668 begin
1669 exit;
1670 if not gpart_dbg_enabled then Exit;
1671 l := Length(Particles);
1672 if l = 0 then
1673 Exit;
1674 if Count > l then
1675 Count := l;
1677 DevX1 := DevX div 2;
1678 DevX2 := DevX + 1;
1679 DevY1 := DevY div 2;
1680 DevY2 := DevY + 1;
1682 for a := 1 to Count do
1683 begin
1684 with Particles[CurrentParticle] do
1685 begin
1686 x := fX-DevX1+Random(DevX2);
1687 y := fY-DevY1+Random(DevY2);
1689 if (x >= gMapInfo.Width) or (x <= 0) or
1690 (y >= gMapInfo.Height) or (y <= 0) then
1691 Continue;
1693 (*
1694 // don't spawn bubbles outside of the liquid
1695 if not isLiquidAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIQUID)} then
1696 Continue;
1697 *)
1699 // trace liquid, so we'll know where it ends; do it in 8px steps for speed
1700 // tracer will return `false` if we started outside of the liquid
1702 {$IF DEFINED(D2F_DEBUG_BUBBLES)}
1703 stt := curTimeMicro();
1704 ptr := mapGrid.traceOrthoRayWhileIn(liquidx, liquidTopY, x, y, x, 0, GridTagWater or GridTagAcid1 or GridTagAcid2);
1705 stt := curTimeMicro()-stt;
1706 e_LogWritefln('traceOrthoRayWhileIn: time=%s (%s); liquidTopY=%s', [Integer(stt), ptr, liquidTopY]);
1707 //
1708 stt := curTimeMicro();
1709 nptr := g_Map_TraceLiquidNonPrecise(x, y, 0, -8, liquidx, liquidTopY);
1710 stt := curTimeMicro()-stt;
1711 e_LogWritefln('g_Map_TraceLiquidNonPrecise: time=%s (%s); liquidTopY=%s', [Integer(stt), nptr, liquidTopY]);
1712 if not nptr then continue;
1713 {$ELSE}
1714 if not g_Map_TraceLiquidNonPrecise(x, y, 0, -8, liquidx, liquidTopY) then continue;
1715 {$ENDIF}
1717 velX := 0;
1718 velY := -1-Random;
1719 accelX := 0;
1720 accelY := velY/10;
1722 red := 255;
1723 green := 255;
1724 blue := 255;
1725 alpha := 255;
1727 state := TPartState.Normal;
1728 time := 0;
1729 liveTime := 65535;
1730 particleType := TPartType.Bubbles;
1731 justSticked := false;
1732 onGround := (velY >= 0) and g_Map_HasAnyPanelAtPoint(x, y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
1733 awaken := false;
1734 end;
1736 if CurrentParticle+2 > MaxParticles then
1737 CurrentParticle := 0
1738 else
1739 CurrentParticle := CurrentParticle+1;
1740 end;
1741 end;
1744 // ////////////////////////////////////////////////////////////////////////// //
1745 procedure g_GFX_SetMax(Count: Integer);
1746 var
1747 a: Integer;
1748 begin
1749 if Count > 50000 then Count := 50000;
1750 if (Count < 1) then Count := 1;
1752 SetLength(Particles, Count);
1753 for a := 0 to High(Particles) do Particles[a].die();
1754 MaxParticles := Count;
1755 //if CurrentParticle >= Count then
1756 CurrentParticle := 0;
1757 end;
1760 function g_GFX_GetMax(): Integer;
1761 begin
1762 Result := MaxParticles;
1763 end;
1766 function FindOnceAnim (): DWORD;
1767 var
1768 i: Integer;
1769 begin
1770 if OnceAnims <> nil then
1771 for i := 0 to High(OnceAnims) do
1772 if OnceAnims[i].Animation = nil then
1773 begin
1774 Result := i;
1775 Exit;
1776 end;
1778 if OnceAnims = nil then
1779 begin
1780 SetLength(OnceAnims, 16);
1781 Result := 0;
1782 end
1783 else
1784 begin
1785 Result := High(OnceAnims) + 1;
1786 SetLength(OnceAnims, Length(OnceAnims) + 16);
1787 end;
1788 end;
1791 procedure g_GFX_OnceAnim (x, y: Integer; Anim: TAnimation; AnimType: Byte = 0);
1792 var
1793 find_id: DWORD;
1794 begin
1795 if not gpart_dbg_enabled then Exit;
1796 if Anim = nil then
1797 Exit;
1799 find_id := FindOnceAnim();
1801 OnceAnims[find_id].AnimType := AnimType;
1802 OnceAnims[find_id].Animation := TAnimation.Create(Anim.FramesID, Anim.Loop, Anim.Speed);
1803 OnceAnims[find_id].Animation.Blending := Anim.Blending;
1804 OnceAnims[find_id].Animation.alpha := Anim.alpha;
1805 OnceAnims[find_id].x := x;
1806 OnceAnims[find_id].y := y;
1807 end;
1810 procedure g_GFX_Update ();
1811 var
1812 a: Integer;
1813 w, h: Integer;
1814 len: Integer;
1815 begin
1816 if not gpart_dbg_enabled then exit;
1818 if (Particles <> nil) then
1819 begin
1820 w := gMapInfo.Width;
1821 h := gMapInfo.Height;
1823 len := High(Particles);
1825 for a := 0 to len do
1826 begin
1827 if Particles[a].alive then
1828 begin
1829 with Particles[a] do
1830 begin
1831 if (time = liveTime) then begin die(); continue; end;
1832 if (x+1 >= w) or (y+1 >= h) or (x <= 0) or (y <= 0) then begin die(); end;
1833 //if not alive then Continue;
1834 //e_WriteLog(Format('particle #%d: %d', [State, ParticleType]), MSG_NOTIFY);
1835 think();
1836 end; // with
1837 end; // if
1838 end; // for
1839 end; // Particles <> nil
1841 // clear awake map
1842 awmClear();
1844 if OnceAnims <> nil then
1845 begin
1846 for a := 0 to High(OnceAnims) do
1847 if OnceAnims[a].Animation <> nil then
1848 begin
1849 case OnceAnims[a].AnimType of
1850 ONCEANIM_SMOKE:
1851 begin
1852 if Random(3) = 0 then
1853 OnceAnims[a].x := OnceAnims[a].x-1+Random(3);
1854 if Random(2) = 0 then
1855 OnceAnims[a].y := OnceAnims[a].y-Random(2);
1856 end;
1857 end;
1859 if OnceAnims[a].Animation.Played then
1860 begin
1861 OnceAnims[a].Animation.Free();
1862 OnceAnims[a].Animation := nil;
1863 end
1864 else
1865 OnceAnims[a].Animation.Update();
1866 end;
1867 end;
1868 end;
1871 procedure g_GFX_Draw ();
1872 var
1873 a, len: Integer;
1874 begin
1875 if Particles <> nil then
1876 begin
1877 glDisable(GL_TEXTURE_2D);
1878 glPointSize(2);
1880 glEnable(GL_BLEND);
1881 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1883 glBegin(GL_POINTS);
1885 len := High(Particles);
1887 for a := 0 to len do
1888 with Particles[a] do
1889 if alive and (x >= sX) and (y >= sY) and (x <= sX+sWidth) and (sY <= sY+sHeight) then
1890 begin
1891 glColor4ub(red, green, blue, alpha);
1892 glVertex2i(x + offsetX, y + offsetY);
1893 end;
1895 glEnd();
1897 glDisable(GL_BLEND);
1898 end;
1900 if OnceAnims <> nil then
1901 for a := 0 to High(OnceAnims) do
1902 if OnceAnims[a].Animation <> nil then
1903 with OnceAnims[a] do
1904 Animation.Draw(x, y, M_NONE);
1905 end;
1908 end.