DEADSOFTWARE

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