DEADSOFTWARE

faster particles awakening
[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;
28 ONCEANIM_NONE = 0;
29 ONCEANIM_SMOKE = 1;
30 MARK_FREE = 0;
31 MARK_WALL = 1;
32 MARK_WATER = 2;
33 MARK_ACID = 4;
34 MARK_LIFTDOWN = 8;
35 MARK_LIFTUP = 16;
36 MARK_DOOR = 32;
37 MARK_LIFTLEFT = 64;
38 MARK_LIFTRIGHT = 128;
39 MARK_BLOCKED = MARK_WALL + MARK_DOOR;
40 MARK_LIQUID = MARK_WATER + MARK_ACID;
41 MARK_LIFT = MARK_LIFTDOWN + MARK_LIFTUP + MARK_LIFTLEFT + MARK_LIFTRIGHT;
43 procedure g_GFX_Init();
44 procedure g_GFX_Free();
46 procedure g_GFX_Blood(fX, fY: Integer; Count: Word; vx, vy: Integer;
47 DevX, DevY: Word; CR, CG, CB: Byte; Kind: Byte = BLOOD_NORMAL);
48 procedure g_GFX_Spark(fX, fY: Integer; Count: Word; Angle: SmallInt; DevX, DevY: Byte);
49 procedure g_GFX_Water(fX, fY: Integer; Count: Word; fVelX, fVelY: Single; DevX, DevY, Color: Byte);
50 procedure g_GFX_SimpleWater(fX, fY: Integer; Count: Word; fVelX, fVelY: Single; DefColor, CR, CG, CB: Byte);
51 procedure g_GFX_Bubbles(fX, fY: Integer; Count: Word; DevX, DevY: Byte);
52 procedure g_GFX_SetMax(Count: Integer);
53 function g_GFX_GetMax(): Integer;
55 procedure g_GFX_OnceAnim(X, Y: Integer; Anim: TAnimation; AnimType: Byte = 0);
57 procedure g_Mark (x, y, Width, Height: Integer; t: Byte; st: Boolean=true);
59 procedure g_GFX_Update ();
60 procedure g_GFX_Draw ();
63 var
64 gpart_dbg_enabled: Boolean = true;
65 gpart_dbg_phys_enabled: Boolean = true;
68 implementation
70 uses
71 g_map, g_panel, g_basic, Math, e_graphics, GL, GLExt,
72 g_options, g_console, SysUtils, g_triggers, MAPDEF,
73 g_game, g_language, g_net, xprofiler;
75 type
76 PParticle = ^TParticle;
78 TParticle = record
79 X, Y: Integer;
80 VelX, VelY: Single;
81 AccelX, AccelY: Single;
82 Red, Green, Blue: Byte;
83 Alpha: Byte;
84 Time, LiveTime: Word;
85 State: Byte;
86 ParticleType: Byte;
87 offsetX, offsetY: ShortInt;
88 // for bubbles
89 liquidTopY: Integer; // don't float higher than this
90 // for water
91 stickDX: Integer;
92 // for blood
93 justSticked: Boolean;
94 stickEY: Integer;
95 // for all
96 onGround: Boolean;
97 awaken: Boolean;
99 //k8: sorry, i have to emulate virtual methods this way, 'cause i haet `Object`
101 procedure thinkerBlood ();
102 procedure thinkerSpark ();
103 procedure thinkerBubble ();
104 procedure thinkerWater ();
106 function isSleeping (): Boolean; inline;
107 procedure awake (); inline;
109 function alive (): Boolean; inline;
110 procedure die (); inline;
111 procedure think (); inline;
112 end;
114 TOnceAnim = record
115 AnimType: Byte;
116 X, Y: Integer;
117 Animation: TAnimation;
118 end;
120 const
121 PARTICLE_BLOOD = 0;
122 PARTICLE_SPARK = 1;
123 PARTICLE_BUBBLES = 2;
124 PARTICLE_WATER = 3;
125 STATE_FREE = 0;
126 STATE_NORMAL = 1;
127 STATE_STICK = 2;
129 var
130 Particles: array of TParticle;
131 OnceAnims: array of TOnceAnim;
132 MaxParticles: Integer;
133 CurrentParticle: Integer;
134 // awakeMap has one bit for each map grid cell; on g_Mark,
135 // corresponding bits will be set, and in `think()` all particles
136 // in marked cells will be awaken
137 awakeMap: packed array of LongWord = nil;
138 awakeMapH: Integer = -1;
139 awakeMapW: Integer = -1;
140 awakeMinX, awakeMinY: Integer;
143 // ////////////////////////////////////////////////////////////////////////// //
144 // HACK! using mapgrid
145 procedure awmClear (); inline;
146 begin
147 if (awakeMapW > 0) then FillDWord(awakeMap[0], Length(awakeMap), 0);
148 end;
151 procedure awmSetup ();
152 begin
153 assert(mapGrid <> nil);
154 awakeMapW := (mapGrid.gridWidth+mapGrid.tileSize-1) div mapGrid.tileSize;
155 awakeMapW := (awakeMapW+31) div 32; // LongWord has 32 bits ;-)
156 awakeMapH := (mapGrid.gridHeight+mapGrid.tileSize-1) div mapGrid.tileSize;
157 awakeMinX := mapGrid.gridX0;
158 awakeMinY := mapGrid.gridY0;
159 SetLength(awakeMap, awakeMapW*awakeMapH);
160 {$IF DEFINED(D2F_DEBUG)}
161 e_LogWritefln('particle awake map: %sx%s (for grid of size %sx%s)', [awakeMapW, awakeMapH, mapGrid.gridWidth, mapGrid.gridHeight]);
162 {$ENDIF}
163 awmClear();
164 end;
167 function awmIsSet (x, y: Integer): Boolean; inline;
168 begin
169 x := (x-awakeMinX) div mapGrid.tileSize;
170 y := (y-awakeMinY) div mapGrid.tileSize;
171 if (x >= 0) and (y >= 0) and (x div 32 < awakeMapW) and (y < awakeMapH) then
172 begin
173 {$IF DEFINED(D2F_DEBUG)}
174 assert(y*awakeMapW+x div 32 < Length(awakeMap));
175 {$ENDIF}
176 result := ((awakeMap[y*awakeMapW+x div 32] and (LongWord(1) shl (x mod 32))) <> 0);
177 end
178 else
179 begin
180 result := false;
181 end;
182 end;
185 procedure awmSet (x, y: Integer); inline;
186 var
187 v: PLongWord;
188 begin
189 x := (x-awakeMinX) div mapGrid.tileSize;
190 y := (y-awakeMinY) div mapGrid.tileSize;
191 if (x >= 0) and (y >= 0) and (x div 32 < awakeMapW) and (y < awakeMapH) then
192 begin
193 {$IF DEFINED(D2F_DEBUG)}
194 assert(y*awakeMapW+x div 32 < Length(awakeMap));
195 {$ENDIF}
196 v := @awakeMap[y*awakeMapW+x div 32];
197 v^ := v^ or (LongWord(1) shl (x mod 32));
198 end;
199 end;
202 // ////////////////////////////////////////////////////////////////////////// //
203 function TParticle.alive (): Boolean; inline; begin result := (State <> STATE_FREE); end;
204 procedure TParticle.die (); inline; begin State := STATE_FREE; end;
206 function TParticle.isSleeping (): Boolean; inline;
207 begin
208 result := alive and (onGround or (not justSticked and (State = STATE_STICK)));
209 end;
211 procedure TParticle.awake (); inline;
212 begin
213 if {alive and} (onGround or (not justSticked and (State = STATE_STICK))) then
214 begin
215 // wakeup this particle
217 if (part.ParticleType = PARTICLE_SPARK) then
218 begin
219 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]);
220 end;
222 justSticked := true; // so sticked state will be re-evaluated
223 if onGround then
224 begin
225 if (VelY = 0) then VelY := 0.1;
226 if (AccelY = 0) then AccelY := 0.5;
227 end;
228 onGround := false; // so onground state will be re-evaluated
229 awaken := true;
230 end;
231 end;
234 procedure TParticle.think (); inline;
235 begin
236 // awake sleeping particle, if necessary
237 if isSleeping then
238 begin
239 if awmIsSet(X, Y) then awake();
240 end;
241 case ParticleType of
242 PARTICLE_BLOOD: thinkerBlood();
243 PARTICLE_SPARK: thinkerSpark();
244 PARTICLE_BUBBLES: thinkerBubble();
245 PARTICLE_WATER: thinkerWater();
246 end;
247 end;
250 // ////////////////////////////////////////////////////////////////////////// //
251 function isBlockedAt (x, y: Integer): Boolean; inline;
252 begin
253 if not gpart_dbg_phys_enabled then begin result := false; exit; end;
254 result := g_Map_HasAnyPanelAtPoint(x, y, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
255 end;
257 // ???
258 function isWallAt (x, y: Integer): Boolean; inline;
259 begin
260 if not gpart_dbg_phys_enabled then begin result := false; exit; end;
261 result := g_Map_HasAnyPanelAtPoint(x, y, (PANEL_WALL or PANEL_STEP));
262 end;
264 function isLiftUpAt (x, y: Integer): Boolean; inline;
265 begin
266 if not gpart_dbg_phys_enabled then begin result := false; exit; end;
267 result := g_Map_HasAnyPanelAtPoint(x, y, PANEL_LIFTUP);
268 end;
270 function isLiftDownAt (x, y: Integer): Boolean; inline;
271 begin
272 if not gpart_dbg_phys_enabled then begin result := false; exit; end;
273 result := g_Map_HasAnyPanelAtPoint(x, y, PANEL_LIFTDOWN);
274 end;
276 function isLiftLeftAt (x, y: Integer): Boolean; inline;
277 begin
278 if not gpart_dbg_phys_enabled then begin result := false; exit; end;
279 result := g_Map_HasAnyPanelAtPoint(x, y, PANEL_LIFTLEFT);
280 end;
282 function isLiftRightAt (x, y: Integer): Boolean; inline;
283 begin
284 if not gpart_dbg_phys_enabled then begin result := false; exit; end;
285 result := g_Map_HasAnyPanelAtPoint(x, y, PANEL_LIFTRIGHT);
286 end;
288 function isLiquidAt (x, y: Integer): Boolean; inline;
289 begin
290 if not gpart_dbg_phys_enabled then begin result := false; exit; end;
291 result := g_Map_HasAnyPanelAtPoint(x, y, (PANEL_WATER or PANEL_ACID1 or PANEL_ACID2));
292 end;
294 function isAnythingAt (x, y: Integer): Boolean; inline;
295 begin
296 if not gpart_dbg_phys_enabled then begin result := false; exit; end;
297 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));
298 end;
301 // st: set mark
302 // t: mark type
303 // currently unused
304 procedure g_Mark(x, y, Width, Height: Integer; t: Byte; st: Boolean=true);
305 var
306 cx, ex, ey: Integer;
307 ts: Integer;
308 begin
309 if (Width < 1) or (Height < 1) then exit;
310 // make some border, so we'll hit particles lying around the panel
311 x -= 1; Width += 2;
312 y -= 1; Height += 2;
313 ex := x+Width;
314 ey := y+Height;
315 ts := mapGrid.tileSize;
316 while (y < ey) do
317 begin
318 cx := x;
319 while (cx < ex) do
320 begin
321 awmSet(cx, y);
322 Inc(cx, ts);
323 end;
324 Inc(y, ts);
325 end;
326 end;
329 {$IF DEFINED(HAS_COLLIDE_BITMAP)}
330 procedure CreateCollideMap();
331 var
332 a: Integer;
333 begin
334 //g_Game_SetLoadingText(_lc[I_LOAD_COLLIDE_MAP]+' 1/6', 0, False);
335 //SetLength(gCollideMap, gMapInfo.Height+1);
336 //for a := 0 to High(gCollideMap) do SetLength(gCollideMap[a], gMapInfo.Width+1);
337 end;
338 {$ENDIF}
341 procedure g_GFX_Init();
342 begin
343 //CreateCollideMap();
344 awmSetup();
345 {$IFDEF HEADLESS}
346 gpart_dbg_enabled := False;
347 {$ENDIF}
348 end;
351 procedure g_GFX_Free();
352 var
353 a: Integer;
354 begin
355 Particles := nil;
356 SetLength(Particles, MaxParticles);
357 for a := 0 to High(Particles) do Particles[a].die();
358 CurrentParticle := 0;
360 if (OnceAnims <> nil) then
361 begin
362 for a := 0 to High(OnceAnims) do OnceAnims[a].Animation.Free();
363 OnceAnims := nil;
364 end;
366 awakeMap := nil;
367 // why not?
368 awakeMapH := -1;
369 awakeMapW := -1;
370 end;
373 // ////////////////////////////////////////////////////////////////////////// //
374 procedure TParticle.thinkerBlood ();
375 var
376 w, h: Integer;
377 dX, dY: SmallInt;
378 {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
379 b: Integer;
380 s: ShortInt;
381 {$ELSE}
382 pan: TPanel;
383 ex, ey: Integer;
384 {$ENDIF}
385 begin
386 w := gMapInfo.Width;
387 h := gMapInfo.Height;
389 if gAdvBlood then
390 begin
391 if (State = STATE_STICK) then
392 begin
393 {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
395 if (not ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)) and
396 (not ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)) and
397 (not ByteBool(gCollideMap[Y, X-1] and MARK_BLOCKED)) and
398 (not ByteBool(gCollideMap[Y, X+1] and MARK_BLOCKED))
399 then
401 if (not isBlockedAt(X, Y-1)) and
402 (not isBlockedAt(X, Y+1)) and
403 (not isBlockedAt(X-1, Y)) and
404 (not isBlockedAt(X+1, Y))
405 {$ELSE}
406 if justSticked then
407 begin
408 if not mapGrid.traceOrthoRayWhileIn(ex, ey, X+stickDX, Y, X+stickDX, mapGrid.gridY0+mapGrid.gridHeight, GridTagWall or GridTagDoor or GridTagStep) then
409 begin
410 // îòëèïëà
411 State := STATE_NORMAL;
412 //e_LogWritefln('juststicked unsticked: X=%s; X+stickDX=%s; stickDX=%s; Y=%s', [X, X+stickDX, stickDX, Y]);
413 end
414 else
415 begin
416 stickEY := ey+1;
417 if (nil <> g_Map_traceToNearest(X, Y, X, stickEY, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey)) then
418 begin
419 if (ey > stickEY) then stickEY := ey-1;
420 end;
421 justSticked := false;
422 //e_LogWritefln('juststicked: X=%s; X+stickDX=%s; stickDX=%s; Y=%s; stickEY=%s', [X, X+stickDX, stickDX, Y, stickEY]);
423 end;
424 end;
425 if (State <> STATE_STICK) or (Y >= stickEY)
426 //if not g_Map_CollidePanel(X-1, Y-1, 3, 3, (PANEL_STEP or PANEL_WALL or PANEL_OPENDOOR or PANEL_CLOSEDOOR))
427 {$ENDIF}
428 then
429 begin // Îòëèïëà - êàïàåò
430 VelY := 0.5;
431 AccelY := 0.15;
432 State := STATE_NORMAL;
433 end
434 else if (Random(200) = 100) then
435 begin // Ïðèëåïëåíà - íî âîçìîæíî ñòåêàåò
436 VelY := 0.5;
437 AccelY := 0.15;
438 exit;
439 end;
440 end;
442 {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
443 if not isBlockedAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)} then
444 begin
445 if isLiftUpAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTUP)} then
446 begin // Ëèôò ââåðõ
447 if (VelY > -4-Random(3)) then VelY -= 0.8;
448 if (abs(VelX) > 0.1) then VelX -= VelX/10.0;
449 VelX += (Random-Random)*0.2;
450 AccelY := 0.15;
451 end;
452 if isLiftLeftAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTLEFT)} then
453 begin // Ïîòîê âëåâî
454 if (VelX > -8-Random(3)) then VelX -= 0.8;
455 AccelY := 0.15;
456 end;
457 if isLiftRightAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTRIGHT)} then
458 begin // Ïîòîê âïðàâî
459 if (VelX < 8+Random(3)) then VelX += 0.8;
460 AccelY := 0.15;
461 end;
462 end;
463 {$ELSE}
464 pan := g_Map_PanelAtPoint(X, Y, GridTagLift);
465 if (pan <> nil) then
466 begin
467 if ((pan.PanelType and PANEL_LIFTUP) <> 0) then
468 begin
469 if (VelY > -4-Random(3)) then VelY -= 0.8;
470 if (abs(VelX) > 0.1) then VelX -= VelX/10.0;
471 VelX += (Random-Random)*0.2;
472 AccelY := 0.15;
473 end;
474 if ((pan.PanelType and PANEL_LIFTLEFT) <> 0) then
475 begin
476 if (VelX > -8-Random(3)) then VelX -= 0.8;
477 AccelY := 0.15;
478 end;
479 if ((pan.PanelType and PANEL_LIFTRIGHT) <> 0) then
480 begin
481 if (VelX < 8+Random(3)) then VelX += 0.8;
482 AccelY := 0.15;
483 end;
484 end;
485 {$ENDIF}
487 dX := Round(VelX);
488 dY := Round(VelY);
490 {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
491 if (Abs(VelX) < 0.1) and (Abs(VelY) < 0.1) then
492 begin
493 if (State <> STATE_STICK) and
494 (not isBlockedAt(X, Y-1) {ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)}) and
495 (not isBlockedAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)}) and
496 (not isBlockedAt(X, Y+1) {ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)}) then
497 begin // Âèñèò â âîçäóõå - êàïàåò
498 VelY := 0.8;
499 AccelY := 0.5;
500 State := STATE_NORMAL;
501 end;
502 end;
503 {$ELSE}
504 if (State <> STATE_STICK) and (Abs(VelX) < 0.1) and (Abs(VelY) < 0.1) then
505 begin
506 // Âèñèò â âîçäóõå - êàïàåò
507 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
508 begin
509 VelY := 0.8;
510 AccelY := 0.5;
511 State := STATE_NORMAL;
512 end;
513 end;
514 {$ENDIF}
516 {$IF DEFINED(D2F_NEW_SPARK_THINKER)}
517 // horizontal
518 if (dX <> 0) then
519 begin
520 pan := g_Map_traceToNearest(X, Y, X+dX, Y, (GridTagWall or GridTagDoor or GridTagStep), @ex, @ey);
521 X := ex;
522 // free to ride?
523 if (pan <> nil) then
524 begin
525 // Ñòåíà/äâåðü
526 VelX := 0;
527 VelY := 0;
528 AccelX := 0;
529 AccelY := 0;
530 State := STATE_STICK;
531 justSticked := true;
532 if (dX > 0) then stickDX := 1 else stickDX := -1;
533 end;
534 if (X < 0) or (X >= w) then begin die(); exit; end;
535 end;
536 // vertical
537 if (dY <> 0) then
538 begin
539 if (dY < 0) or not onGround then
540 begin
541 pan := g_Map_traceToNearest(X, Y, X, Y+dY, (GridTagWall or GridTagDoor or GridTagStep), @ex, @ey);
542 Y := ey;
543 // free to ride?
544 if (pan <> nil) then
545 begin
546 // Ñòåíà/äâåðü
547 VelX := 0;
548 VelY := 0;
549 AccelX := 0;
550 AccelY := 0;
551 if (dY > 0) and (State <> STATE_STICK) then
552 begin
553 State := STATE_NORMAL;
554 end
555 else
556 begin
557 State := STATE_STICK;
558 if (g_Map_PanelAtPoint(X-1, Y, (GridTagWall or GridTagDoor or GridTagStep)) <> nil) then stickDX := -1
559 else if (g_Map_PanelAtPoint(X+1, Y, (GridTagWall or GridTagDoor or GridTagStep)) <> nil) then stickDX := 1
560 else stickDX := 0;
561 justSticked := true;
562 end;
563 end;
564 onGround := (VelY >= 0) and g_Map_HasAnyPanelAtPoint(X, Y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
565 end;
566 if (Y < 0) or (Y >= h) then begin die(); exit; end;
567 end;
568 {$ELSE}
569 // horizontal
570 if (dX <> 0) then
571 begin
572 if (dX > 0) then s := 1 else s := -1;
573 dX := Abs(dX);
574 for b := 1 to dX do
575 begin
576 if (X+s >= w) or (X+s <= 0) then begin die(); break; end;
577 //c := gCollideMap[Y, X+s];
578 if isBlockedAt(X+s, Y) {ByteBool(c and MARK_BLOCKED)} then
579 begin // Ñòåíà/äâåðü
580 VelX := 0;
581 VelY := 0;
582 AccelX := 0;
583 AccelY := 0;
584 State := STATE_STICK;
585 justSticked := true;
586 break;
587 end;
588 X := X+s;
589 end;
590 end;
591 // vertical
592 if (dY <> 0) then
593 begin
594 if (dY > 0) then s := 1 else s := -1;
595 dY := Abs(dY);
596 for b := 1 to dY do
597 begin
598 if (Y+s >= h) or (Y+s <= 0) then begin die(); break; end;
599 //c := gCollideMap[Y+s, X];
600 if isBlockedAt(X, Y+s) {ByteBool(c and MARK_BLOCKED)} then
601 begin // Ñòåíà/äâåðü
602 VelX := 0;
603 VelY := 0;
604 AccelX := 0;
605 AccelY := 0;
606 if (s > 0) and (State <> STATE_STICK) then State := STATE_NORMAL else State := STATE_STICK;
607 justSticked := (State = STATE_STICK);
608 break;
609 end;
610 Y := Y+s;
611 end;
612 end;
613 {$ENDIF}
614 end // if gAdvBlood
615 else
616 begin
617 dX := Round(VelX);
618 dY := Round(VelY);
619 if (X+dX >= w) or (Y+dY >= h) or (X+dX <= 0) or (Y+dY <= 0) or isBlockedAt(X+dX, Y+dY) {ByteBool(gCollideMap[Y+dY, X+dX] and MARK_BLOCKED)} then
620 begin // Ñòåíà/äâåðü/ãðàíèöà
621 die();
622 exit;
623 //VelX := 0;
624 //VelY := 0;
625 end
626 else
627 begin
628 Y += dY;
629 X += dX;
630 end;
631 end;
633 VelX += AccelX;
634 VelY += AccelY;
636 // Êðîâü ðàñòâîðÿåòñÿ â æèäêîñòè:
637 if isLiquidAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIQUID)} then
638 begin
639 Time += 1;
640 Alpha := 255-trunc((255.0*Time)/LiveTime);
641 end;
642 end;
646 // ////////////////////////////////////////////////////////////////////////// //
647 procedure TParticle.thinkerWater ();
648 var
649 dX, dY: SmallInt;
650 {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
651 w, h: Integer;
652 b: Integer;
653 s: ShortInt;
654 {$ELSE}
655 pan: TPanel;
656 ex, ey: Integer;
657 {$ENDIF}
658 begin
659 {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
660 w := gMapInfo.Width;
661 h := gMapInfo.Height;
662 {$ENDIF}
664 //TODO: trace wall end when water becomes stick
665 if (State = STATE_STICK) and (Random(30) = 15) then
666 begin // Ñòåêàåò/îòëèïàåò
667 VelY := 0.5;
668 AccelY := 0.15;
669 {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
670 if (not isBlockedAt(X-1, Y) {ByteBool(gCollideMap[Y, X-1] and MARK_BLOCKED)}) and
671 (not isBlockedAt(X+1, Y) {ByteBool(gCollideMap[Y, X+1] and MARK_BLOCKED)}) then
672 State := STATE_NORMAL;
673 {$ELSE}
674 if (stickDX = 0) then
675 begin
676 // no walls around, drop
677 State := STATE_NORMAL;
678 end
679 else
680 begin
681 if justSticked then
682 begin
683 if not mapGrid.traceOrthoRayWhileIn(ex, ey, X+stickDX, Y, X+stickDX, mapGrid.gridY0+mapGrid.gridHeight, GridTagWall or GridTagDoor or GridTagStep) then
684 begin
685 // îòëèïëà
686 State := STATE_NORMAL;
687 //e_LogWritefln('juststicked unsticked: X=%s; X+stickDX=%s; stickDX=%s; Y=%s', [X, X+stickDX, stickDX, Y]);
688 end
689 else
690 begin
691 stickEY := ey+1;
692 justSticked := false;
693 if (nil <> g_Map_traceToNearest(X, Y, X, stickEY, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey)) then
694 begin
695 if (ey > stickEY) then stickEY := ey-1;
696 end;
697 //e_LogWritefln('juststicked: X=%s; X+stickDX=%s; stickDX=%s; Y=%s; stickEY=%s', [X, X+stickDX, stickDX, Y, stickEY]);
698 end;
699 end
700 else
701 begin
702 if (Y >= stickEY) then State := STATE_NORMAL;
703 end;
704 //if not g_Map_CollidePanel(X-1, Y-1, 3, 3, (PANEL_STEP or PANEL_WALL or PANEL_OPENDOOR or PANEL_CLOSEDOOR))
705 end;
706 {$ENDIF}
707 exit;
708 end;
710 {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
711 if not isBlockedAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)} then
712 begin
713 if isLiftUpAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTUP)} then
714 begin // Ëèôò ââåðõ
715 if VelY > -4-Random(3) then
716 VelY := VelY - 0.8;
717 if Abs(VelX) > 0.1 then
718 VelX := VelX - VelX/10.0;
719 VelX := VelX + (Random-Random)*0.2;
720 AccelY := 0.15;
721 end;
722 if isLiftLeftAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTLEFT)} then
723 begin // Ïîòîê âëåâî
724 if VelX > -8-Random(3) then
725 VelX := VelX - 0.8;
726 AccelY := 0.15;
727 end;
728 if isLiftRightAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTRIGHT)} then
729 begin // Ïîòîê âïðàâî
730 if VelX < 8+Random(3) then
731 VelX := VelX + 0.8;
732 AccelY := 0.15;
733 end;
734 end;
735 {$ELSE}
736 pan := g_Map_PanelAtPoint(X, Y, (GridTagAcid1 or GridTagAcid2 or GridTagWater or GridTagLift));
737 if (pan <> nil) then
738 begin
739 if ((pan.tag and (GridTagAcid1 or GridTagAcid2 or GridTagWater)) <> 0) then begin die(); exit; end;
740 if ((pan.PanelType and PANEL_LIFTUP) <> 0) then
741 begin
742 if (VelY > -4-Random(3)) then VelY -= 0.8;
743 if (Abs(VelX) > 0.1) then VelX -= VelX/10.0;
744 VelX += (Random-Random)*0.2;
745 AccelY := 0.15;
746 end;
747 if ((pan.PanelType and PANEL_LIFTLEFT) <> 0) then
748 begin
749 if (VelX > -8-Random(3)) then VelX -= 0.8;
750 AccelY := 0.15;
751 end;
752 if ((pan.PanelType and PANEL_LIFTRIGHT) <> 0) then
753 begin
754 if (VelX < 8+Random(3)) then VelX += 0.8;
755 AccelY := 0.15;
756 end;
757 end;
758 {$ENDIF}
760 dX := Round(VelX);
761 dY := Round(VelY);
763 {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
764 if (Abs(VelX) < 0.1) and (Abs(VelY) < 0.1) then
765 begin
766 if (State <> STATE_STICK) and
767 (not isBlockedAt(X, Y-1) {ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)}) and
768 (not isBlockedAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)}) and
769 (not isBlockedAt(X, Y+1) {ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)}) then
770 begin // Âèñèò â âîçäóõå - êàïàåò
771 VelY := 0.8;
772 AccelY := 0.5;
773 State := STATE_NORMAL;
774 end;
775 end;
776 {$ELSE}
777 if (State <> STATE_STICK) and (Abs(VelX) < 0.1) and (Abs(VelY) < 0.1) then
778 begin
779 // Âèñèò â âîçäóõå - êàïàåò
780 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
781 begin
782 VelY := 0.8;
783 AccelY := 0.5;
784 State := STATE_NORMAL;
785 end;
786 end;
787 {$ENDIF}
789 {$IF DEFINED(D2F_NEW_SPARK_THINKER)}
790 // horizontal
791 if (dX <> 0) then
792 begin
793 pan := g_Map_traceToNearest(X, Y, X+dX, Y, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey);
794 X := ex;
795 // free to ride?
796 if (pan <> nil) then
797 begin
798 // nope
799 if (dY > 0) and ((pan.tag and (GridTagAcid1 or GridTagAcid2 or GridTagWater)) <> 0) then begin die(); exit; end;
800 // Ñòåíà/äâåðü?
801 if ((pan.tag and (GridTagWall or GridTagDoor or GridTagStep)) <> 0) then
802 begin
803 VelX := 0;
804 VelY := 0;
805 AccelX := 0;
806 AccelY := 0;
807 State := STATE_STICK;
808 justSticked := true;
809 if (dX > 0) then stickDX := 1 else stickDX := -1;
810 end;
811 end;
812 if (X < 0) or (X >= gMapInfo.Width) then begin die(); exit; end;
813 end;
814 // vertical
815 if (dY <> 0) then
816 begin
817 if (dY < 0) or not onGround then
818 begin
819 pan := g_Map_traceToNearest(X, Y, X, Y+dY, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey);
820 Y := ey;
821 // free to ride?
822 if (pan <> nil) then
823 begin
824 // nope
825 if (dY > 0) and ((pan.tag and (GridTagAcid1 or GridTagAcid2 or GridTagWater)) <> 0) then begin die(); exit; end;
826 // Ñòåíà/äâåðü?
827 if ((pan.tag and (GridTagWall or GridTagDoor or GridTagStep)) <> 0) then
828 begin
829 VelX := 0;
830 VelY := 0;
831 AccelX := 0;
832 AccelY := 0;
833 if (dY > 0) and (State <> STATE_STICK) then
834 begin
835 State := STATE_NORMAL;
836 end
837 else
838 begin
839 State := STATE_STICK;
840 if (g_Map_PanelAtPoint(X-1, Y, (GridTagWall or GridTagDoor or GridTagStep)) <> nil) then stickDX := -1
841 else if (g_Map_PanelAtPoint(X+1, Y, (GridTagWall or GridTagDoor or GridTagStep)) <> nil) then stickDX := 1
842 else stickDX := 0;
843 justSticked := true;
844 end;
845 end;
846 end;
847 onGround := (VelY >= 0) and g_Map_HasAnyPanelAtPoint(X, Y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
848 end;
849 if (Y < 0) or (Y >= gMapInfo.Height) then begin die(); exit; end;
850 end;
851 {$ELSE}
852 // horizontal
853 if (dX <> 0) then
854 begin
855 if (dX > 0) then s := 1 else s := -1;
856 for b := 1 to Abs(dX) do
857 begin
858 // Ñáîêó ãðàíèöà?
859 if (X+s >= w) or (X+s <= 0) then begin die(); break;end;
860 //c := gCollideMap[Y, X+s];
861 // Ñáîêó æèäêîñòü, à ÷àñòèöà óæå ïàäàåò?
862 if isLiquidAt(X+s, Y) {ByteBool(c and MARK_LIQUID)} and (dY > 0) then begin die(); break; end;
863 if isBlockedAt(X+s, Y) {ByteBool(c and MARK_BLOCKED)} then
864 begin // Ñòåíà/äâåðü
865 VelX := 0;
866 VelY := 0;
867 AccelX := 0;
868 AccelY := 0;
869 State := STATE_STICK;
870 justSticked := true;
871 Break;
872 end;
873 X := X+s;
874 end;
875 end;
876 // vertical
877 if (dY <> 0) then
878 begin
879 if (dY > 0) then s := 1 else s := -1;
880 for b := 1 to Abs(dY) do
881 begin
882 // Ñíèçó/ñâåðõó ãðàíèöà
883 if (Y+s >= h) or (Y+s <= 0) then begin die(); break; end;
884 //c := gCollideMap[Y+s, X];
885 // Ñíèçó æèäêîñòü, à ÷àñòèöà óæå ïàäàåò
886 if isLiquidAt(X, Y+s) {ByteBool(c and MARK_LIQUID)} and (dY > 0) then begin die(); break; end;
887 if isBlockedAt(X, Y+s) {ByteBool(c and MARK_BLOCKED)} then
888 begin // Ñòåíà/äâåðü
889 VelX := 0;
890 VelY := 0;
891 AccelX := 0;
892 AccelY := 0;
893 if (s > 0) and (State <> STATE_STICK) then State := STATE_NORMAL else State := STATE_STICK;
894 justSticked := (State = STATE_STICK);
895 break;
896 end;
897 Y := Y+s;
898 end;
899 end;
900 {$ENDIF}
902 VelX += AccelX;
903 VelY += AccelY;
905 Time += 1;
906 end;
909 // ////////////////////////////////////////////////////////////////////////// //
910 procedure TParticle.thinkerSpark ();
911 var
912 dX, dY: SmallInt;
913 {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
914 b: Integer;
915 s: ShortInt;
916 {$ELSE}
917 pan: TPanel;
918 ex, ey: Integer;
919 {$ENDIF}
920 begin
921 dX := Round(VelX);
922 dY := Round(VelY);
924 {$IF DEFINED(D2F_NEW_SPARK_THINKER)}
925 if (Abs(VelX) < 0.1) and (Abs(VelY) < 0.1) then
926 begin
927 pan := g_Map_traceToNearest(X, Y-1, X, Y+1, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey);
928 end;
929 {$ELSE}
930 if (Abs(VelX) < 0.1) and (Abs(VelY) < 0.1) and
931 (not isBlockedAt(X, Y-1) {ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)}) and
932 (not isBlockedAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)}) and
933 (not isBlockedAt(X, Y+1) {ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)}) then
934 begin // Âèñèò â âîçäóõå
935 VelY := 0.8;
936 AccelY := 0.5;
937 end;
938 {$ENDIF}
940 if (dX <> 0) then
941 begin
942 {$IF DEFINED(D2F_NEW_SPARK_THINKER)}
943 pan := g_Map_traceToNearest(X, Y, X+dX, Y, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey);
944 //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);
945 X := ex;
946 // free to ride?
947 if (pan <> nil) then
948 begin
949 // nope
950 if ((pan.tag and (GridTagAcid1 or GridTagAcid2 or GridTagWater)) <> 0) then begin die(); exit; end;
951 VelX := 0;
952 AccelX := 0;
953 end;
954 if (X < 0) or (X >= gMapInfo.Width) then begin die(); exit; end;
955 {$ELSE}
956 if (dX > 0) then s := 1 else s := -1;
957 dX := Abs(dX);
958 for b := 1 to dX do
959 begin
960 if (X+s >= gMapInfo.Width) or (X+s <= 0) then begin die(); break; end;
961 //c := gCollideMap[Y, X+s];
962 if isBlockedAt(X+s, Y) {ByteBool(c and MARK_BLOCKED)} then
963 begin // Ñòåíà/äâåðü - ïàäàåò âåðòèêàëüíî
964 VelX := 0;
965 AccelX := 0;
966 Break;
967 end
968 else // Ïóñòî:
969 if not isAnythingAt(X+s, Y) {c = MARK_FREE} then
970 X := X + s
971 else // Îñòàëüíîå:
972 begin
973 die();
974 break;
975 end;
976 end;
977 {$ENDIF}
978 end;
980 if (dY <> 0) then
981 begin
982 {$IF DEFINED(D2F_NEW_SPARK_THINKER)}
983 if (dY < 0) or not onGround then
984 begin
985 pan := g_Map_traceToNearest(X, Y, X, Y+dY, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey);
986 Y := ey;
988 if awaken then
989 begin
990 awaken := false;
991 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]);
992 end;
994 // free to ride?
995 if (pan <> nil) then
996 begin
997 // nope
998 if ((pan.tag and (GridTagAcid1 or GridTagAcid2 or GridTagWater)) <> 0) then begin die(); exit; end;
999 if (dY < 0) then
1000 begin
1001 VelY := -VelY;
1002 AccelY := abs(AccelY);
1003 end
1004 else
1005 begin
1006 VelX := 0;
1007 AccelX := 0;
1008 VelY := 0;
1009 AccelY := 0.8;
1010 end;
1011 end;
1012 onGround := (VelY >= 0) and g_Map_HasAnyPanelAtPoint(X, Y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
1013 end;
1014 if (Y < 0) or (Y >= gMapInfo.Height) then begin die(); exit; end;
1015 {$ELSE}
1016 if (dY > 0) then s := 1 else s := -1;
1017 dY := Abs(dY);
1018 for b := 1 to dY do
1019 begin
1020 if (Y+s >= gMapInfo.Height) or (Y+s <= 0) then begin die(); break; end;
1021 //c := gCollideMap[Y+s, X];
1022 if isBlockedAt(X, Y+s) {ByteBool(c and MARK_BLOCKED)} then
1023 begin // Ñòåíà/äâåðü - ïàäàåò âåðòèêàëüíî
1024 if s < 0 then
1025 begin
1026 VelY := -VelY;
1027 AccelY := Abs(AccelY);
1028 end
1029 else // Èëè íå ïàäàåò
1030 begin
1031 VelX := 0;
1032 AccelX := 0;
1033 VelY := 0;
1034 AccelY := 0.8;
1035 end;
1037 Break;
1038 end
1039 else // Ïóñòî:
1040 if not isAnythingAt(X, Y+s) {c = MARK_FREE} then
1041 Y := Y + s
1042 else // Îñàëüíîå:
1043 begin
1044 die();
1045 break;
1046 end;
1047 end;
1048 {$ENDIF}
1049 end;
1051 if (VelX <> 0.0) then VelX += AccelX;
1053 if (VelY <> 0.0) then
1054 begin
1055 if (AccelY < 10) then AccelY += 0.08;
1056 VelY += AccelY;
1057 end;
1059 Time += 1;
1060 end;
1062 // ////////////////////////////////////////////////////////////////////////// //
1063 procedure TParticle.thinkerBubble ();
1064 var
1065 h: Integer;
1066 dY: SmallInt;
1067 b: Integer;
1068 s: ShortInt;
1069 begin
1070 h := gMapInfo.Height;
1072 dY := Round(VelY);
1074 if dY <> 0 then
1075 begin
1076 if dY > 0 then
1077 s := 1
1078 else
1079 s := -1;
1081 for b := 1 to Abs(dY) do
1082 begin
1083 if (Y+s >= h) or (Y+s <= 0) then begin die(); break; end;
1085 (*
1086 if not isLiquidAt(X, Y+s) {ByteBool(gCollideMap[Y+s, X] and MARK_LIQUID)} then
1087 begin // Óæå íå æèäêîñòü
1088 State := STATE_FREE;
1089 Break;
1090 end;
1091 *)
1092 // we traced liquid before, so don't bother checking
1093 if (Y+s <= liquidTopY) then begin die(); break; end;
1095 Y := Y+s;
1096 end;
1097 end;
1099 if VelY > -4 then
1100 VelY := VelY + AccelY;
1102 Time := Time + 1;
1103 end;
1106 // ////////////////////////////////////////////////////////////////////////// //
1107 procedure g_GFX_SparkVel (fX, fY: Integer; Count: Word; VX, VY: Integer; DevX, DevY: Byte);
1108 var
1109 a: Integer;
1110 DevX1, DevX2,
1111 DevY1, DevY2: Byte;
1112 l: Integer;
1113 begin
1114 if not gpart_dbg_enabled then Exit;
1115 l := Length(Particles);
1116 if l = 0 then exit;
1117 if Count > l then Count := l;
1119 DevX1 := DevX div 2;
1120 DevX2 := DevX + 1;
1121 DevY1 := DevY div 2;
1122 DevY2 := DevY + 1;
1124 for a := 1 to Count do
1125 begin
1126 with Particles[CurrentParticle] do
1127 begin
1128 X := fX-DevX1+Random(DevX2);
1129 Y := fY-DevY1+Random(DevY2);
1131 VelX := VX + (Random-Random)*3;
1132 VelY := VY + (Random-Random)*3;
1134 if VelY > -4 then
1135 if VelY-4 < -4 then
1136 VelY := -4
1137 else
1138 VelY := VelY-4;
1140 AccelX := -Sign(VelX)*Random/100;
1141 AccelY := 0.8;
1143 Red := 255;
1144 Green := 100+Random(155);
1145 Blue := 64;
1146 Alpha := 255;
1148 State := STATE_NORMAL;
1149 Time := 0;
1150 LiveTime := 30+Random(60);
1151 ParticleType := PARTICLE_SPARK;
1152 justSticked := false;
1153 onGround := (VelY >= 0) and g_Map_HasAnyPanelAtPoint(X, Y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
1154 awaken := false;
1155 end;
1157 if CurrentParticle+2 > MaxParticles then
1158 CurrentParticle := 0
1159 else
1160 CurrentParticle := CurrentParticle+1;
1161 end;
1162 end;
1165 procedure g_GFX_Blood(fX, fY: Integer; Count: Word; vx, vy: Integer;
1166 DevX, DevY: Word; CR, CG, CB: Byte; Kind: Byte = BLOOD_NORMAL);
1167 var
1168 a: Integer;
1169 DevX1, DevX2,
1170 DevY1, DevY2: Word;
1171 l: Integer;
1172 CRnd: Byte;
1173 CC: SmallInt;
1174 begin
1175 if not gpart_dbg_enabled then Exit;
1176 if Kind = BLOOD_SPARKS then
1177 begin
1178 g_GFX_SparkVel(fX, fY, 2 + Random(2), -VX div 2, -VY div 2, DevX, DevY);
1179 Exit;
1180 end;
1181 l := Length(Particles);
1182 if l = 0 then
1183 Exit;
1184 if Count > l then
1185 Count := l;
1187 DevX1 := DevX div 2;
1188 DevX2 := DevX + 1;
1189 DevY1 := DevY div 2;
1190 DevY2 := DevY + 1;
1192 for a := 1 to Count do
1193 begin
1194 with Particles[CurrentParticle] do
1195 begin
1196 X := fX - DevX1 + Random(DevX2);
1197 Y := fY - DevY1 + Random(DevY2);
1200 if (X < 0) or (X > gMapInfo.Width-1) or
1201 (Y < 0) or (Y > gMapInfo.Height-1) or
1202 ByteBool(gCollideMap[Y, X] and MARK_WALL) then
1203 Continue;
1205 if isWallAt(X, Y) then continue;
1207 VelX := vx + (Random-Random)*3;
1208 VelY := vy + (Random-Random)*3;
1210 if VelY > -4 then
1211 if VelY-4 < -4 then
1212 VelY := -4
1213 else
1214 VelY := VelY-4;
1216 AccelX := -Sign(VelX)*Random/100;
1217 AccelY := 0.8;
1219 CRnd := 20*Random(6);
1220 if CR > 0 then
1221 begin
1222 CC := CR + CRnd - 50;
1223 if CC < 0 then CC := 0;
1224 if CC > 255 then CC := 255;
1225 Red := CC;
1226 end else
1227 Red := 0;
1228 if CG > 0 then
1229 begin
1230 CC := CG + CRnd - 50;
1231 if CC < 0 then CC := 0;
1232 if CC > 255 then CC := 255;
1233 Green := CC;
1234 end else
1235 Green := 0;
1236 if CB > 0 then
1237 begin
1238 CC := CB + CRnd - 50;
1239 if CC < 0 then CC := 0;
1240 if CC > 255 then CC := 255;
1241 Blue := CC;
1242 end else
1243 Blue := 0;
1245 Alpha := 255;
1247 State := STATE_NORMAL;
1248 Time := 0;
1249 LiveTime := 120+Random(40);
1250 ParticleType := PARTICLE_BLOOD;
1251 justSticked := false;
1252 onGround := (VelY >= 0) and g_Map_HasAnyPanelAtPoint(X, Y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
1253 awaken := false;
1254 //stickEY := 0;
1255 end;
1257 if CurrentParticle >= MaxParticles-1 then
1258 CurrentParticle := 0
1259 else
1260 CurrentParticle := CurrentParticle+1;
1261 end;
1262 end;
1265 procedure g_GFX_Spark(fX, fY: Integer; Count: Word; Angle: SmallInt; DevX, DevY: Byte);
1266 var
1267 a: Integer;
1268 b: Single;
1269 DevX1, DevX2,
1270 DevY1, DevY2: Byte;
1271 BaseVelX, BaseVelY: Single;
1272 l: Integer;
1273 begin
1274 if not gpart_dbg_enabled then Exit;
1275 l := Length(Particles);
1276 if l = 0 then
1277 Exit;
1278 if Count > l then
1279 Count := l;
1281 Angle := 360 - Angle;
1283 DevX1 := DevX div 2;
1284 DevX2 := DevX + 1;
1285 DevY1 := DevY div 2;
1286 DevY2 := DevY + 1;
1288 b := DegToRad(Angle);
1289 BaseVelX := cos(b);
1290 BaseVelY := 1.6*sin(b);
1291 if Abs(BaseVelX) < 0.01 then
1292 BaseVelX := 0.0;
1293 if Abs(BaseVelY) < 0.01 then
1294 BaseVelY := 0.0;
1295 for a := 1 to Count do
1296 begin
1297 with Particles[CurrentParticle] do
1298 begin
1299 X := fX-DevX1+Random(DevX2);
1300 Y := fY-DevY1+Random(DevY2);
1302 VelX := BaseVelX*Random;
1303 VelY := BaseVelY-Random;
1304 AccelX := VelX/3.0;
1305 AccelY := VelY/5.0;
1307 Red := 255;
1308 Green := 100+Random(155);
1309 Blue := 64;
1310 Alpha := 255;
1312 State := STATE_NORMAL;
1313 Time := 0;
1314 LiveTime := 30+Random(60);
1315 ParticleType := PARTICLE_SPARK;
1316 justSticked := false;
1317 onGround := (VelY >= 0) and g_Map_HasAnyPanelAtPoint(X, Y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
1318 awaken := false;
1319 end;
1321 if CurrentParticle+2 > MaxParticles then
1322 CurrentParticle := 0
1323 else
1324 CurrentParticle := CurrentParticle+1;
1325 end;
1326 end;
1328 procedure g_GFX_Water(fX, fY: Integer; Count: Word; fVelX, fVelY: Single; DevX, DevY, Color: Byte);
1329 var
1330 a: Integer;
1331 DevX1, DevX2,
1332 DevY1, DevY2: Byte;
1333 l: Integer;
1334 begin
1335 if not gpart_dbg_enabled then Exit;
1336 l := Length(Particles);
1337 if l = 0 then
1338 Exit;
1339 if Count > l then
1340 Count := l;
1342 if Abs(fVelX) < 3.0 then
1343 fVelX := 3.0 - 6.0*Random;
1345 DevX1 := DevX div 2;
1346 DevX2 := DevX + 1;
1347 DevY1 := DevY div 2;
1348 DevY2 := DevY + 1;
1350 for a := 1 to Count do
1351 begin
1352 with Particles[CurrentParticle] do
1353 begin
1354 X := fX-DevX1+Random(DevX2);
1355 Y := fY-DevY1+Random(DevY2);
1357 if Abs(fVelX) < 0.5 then
1358 VelX := 1.0 - 2.0*Random
1359 else
1360 VelX := fVelX*Random;
1361 if Random(10) < 7 then
1362 VelX := -VelX;
1363 VelY := fVelY*Random;
1364 AccelX := 0.0;
1365 AccelY := 0.8;
1367 case Color of
1368 1: // Êðàñíûé
1369 begin
1370 Red := 155 + Random(9)*10;
1371 Green := Trunc(150*Random);
1372 Blue := Green;
1373 end;
1374 2: // Çåëåíûé
1375 begin
1376 Red := Trunc(150*Random);
1377 Green := 175 + Random(9)*10;
1378 Blue := Red;
1379 end;
1380 3: // Ñèíèé
1381 begin
1382 Red := Trunc(200*Random);
1383 Green := Red;
1384 Blue := 175 + Random(9)*10;
1385 end;
1386 else // Ñåðûé
1387 begin
1388 Red := 90 + Random(12)*10;
1389 Green := Red;
1390 Blue := Red;
1391 end;
1392 end;
1394 Alpha := 255;
1396 State := STATE_NORMAL;
1397 Time := 0;
1398 LiveTime := 60+Random(60);
1399 ParticleType := PARTICLE_WATER;
1400 justSticked := false;
1401 onGround := (VelY >= 0) and g_Map_HasAnyPanelAtPoint(X, Y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
1402 awaken := false;
1403 end;
1405 if CurrentParticle+2 > MaxParticles then
1406 CurrentParticle := 0
1407 else
1408 CurrentParticle := CurrentParticle+1;
1409 end;
1410 end;
1412 procedure g_GFX_SimpleWater(fX, fY: Integer; Count: Word; fVelX, fVelY: Single; DefColor, CR, CG, CB: Byte);
1413 var
1414 a: Integer;
1415 l: Integer;
1416 begin
1417 if not gpart_dbg_enabled then Exit;
1418 l := Length(Particles);
1419 if l = 0 then
1420 Exit;
1421 if Count > l then
1422 Count := l;
1424 for a := 1 to Count do
1425 begin
1426 with Particles[CurrentParticle] do
1427 begin
1428 X := fX;
1429 Y := fY;
1431 VelX := fVelX;
1432 VelY := fVelY;
1433 AccelX := 0.0;
1434 AccelY := 0.8;
1436 case DefColor of
1437 1: // Êðàñíûé
1438 begin
1439 Red := 155 + Random(9)*10;
1440 Green := Trunc(150*Random);
1441 Blue := Green;
1442 end;
1443 2: // Çåëåíûé
1444 begin
1445 Red := Trunc(150*Random);
1446 Green := 175 + Random(9)*10;
1447 Blue := Red;
1448 end;
1449 3: // Ñèíèé
1450 begin
1451 Red := Trunc(200*Random);
1452 Green := Red;
1453 Blue := 175 + Random(9)*10;
1454 end;
1455 4: // Ñâîé öâåò, ñâåòëåå
1456 begin
1457 Red := 20 + Random(19)*10;
1458 Green := Red;
1459 Blue := Red;
1460 Red := Min(Red + CR, 255);
1461 Green := Min(Green + CG, 255);
1462 Blue := Min(Blue + CB, 255);
1463 end;
1464 5: // Ñâîé öâåò, òåìíåå
1465 begin
1466 Red := 20 + Random(19)*10;
1467 Green := Red;
1468 Blue := Red;
1469 Red := Max(CR - Red, 0);
1470 Green := Max(CG - Green, 0);
1471 Blue := Max(CB - Blue, 0);
1472 end;
1473 else // Ñåðûé
1474 begin
1475 Red := 90 + Random(12)*10;
1476 Green := Red;
1477 Blue := Red;
1478 end;
1479 end;
1481 Alpha := 255;
1483 State := STATE_NORMAL;
1484 Time := 0;
1485 LiveTime := 60+Random(60);
1486 ParticleType := PARTICLE_WATER;
1487 justSticked := false;
1488 onGround := (VelY >= 0) and g_Map_HasAnyPanelAtPoint(X, Y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
1489 awaken := false;
1490 end;
1492 if CurrentParticle+2 > MaxParticles then
1493 CurrentParticle := 0
1494 else
1495 CurrentParticle := CurrentParticle+1;
1496 end;
1497 end;
1500 {.$DEFINE D2F_DEBUG_BUBBLES}
1501 procedure g_GFX_Bubbles(fX, fY: Integer; Count: Word; DevX, DevY: Byte);
1502 var
1503 a: Integer;
1504 DevX1, DevX2,
1505 DevY1, DevY2: Byte;
1506 l, liquidx: Integer;
1507 {$IF DEFINED(D2F_DEBUG_BUBBLES)}
1508 stt: UInt64;
1509 nptr, ptr: Boolean;
1510 {$ENDIF}
1511 begin
1512 if not gpart_dbg_enabled then Exit;
1513 l := Length(Particles);
1514 if l = 0 then
1515 Exit;
1516 if Count > l then
1517 Count := l;
1519 DevX1 := DevX div 2;
1520 DevX2 := DevX + 1;
1521 DevY1 := DevY div 2;
1522 DevY2 := DevY + 1;
1524 for a := 1 to Count do
1525 begin
1526 with Particles[CurrentParticle] do
1527 begin
1528 X := fX-DevX1+Random(DevX2);
1529 Y := fY-DevY1+Random(DevY2);
1531 if (X >= gMapInfo.Width) or (X <= 0) or
1532 (Y >= gMapInfo.Height) or (Y <= 0) then
1533 Continue;
1535 (*
1536 // don't spawn bubbles outside of the liquid
1537 if not isLiquidAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIQUID)} then
1538 Continue;
1539 *)
1541 // trace liquid, so we'll know where it ends; do it in 8px steps for speed
1542 // tracer will return `false` if we started outside of the liquid
1544 {$IF DEFINED(D2F_DEBUG_BUBBLES)}
1545 stt := curTimeMicro();
1546 ptr := mapGrid.traceOrthoRayWhileIn(liquidx, liquidTopY, X, Y, X, 0, GridTagWater or GridTagAcid1 or GridTagAcid2);
1547 stt := curTimeMicro()-stt;
1548 e_LogWritefln('traceOrthoRayWhileIn: time=%s (%s); liquidTopY=%s', [Integer(stt), ptr, liquidTopY]);
1549 //
1550 stt := curTimeMicro();
1551 nptr := g_Map_TraceLiquidNonPrecise(X, Y, 0, -8, liquidx, liquidTopY);
1552 stt := curTimeMicro()-stt;
1553 e_LogWritefln('g_Map_TraceLiquidNonPrecise: time=%s (%s); liquidTopY=%s', [Integer(stt), nptr, liquidTopY]);
1554 if not nptr then continue;
1555 {$ELSE}
1556 if not g_Map_TraceLiquidNonPrecise(X, Y, 0, -8, liquidx, liquidTopY) then continue;
1557 {$ENDIF}
1559 VelX := 0;
1560 VelY := -1-Random;
1561 AccelX := 0;
1562 AccelY := VelY/10;
1564 Red := 255;
1565 Green := 255;
1566 Blue := 255;
1567 Alpha := 255;
1569 State := STATE_NORMAL;
1570 Time := 0;
1571 LiveTime := 65535;
1572 ParticleType := PARTICLE_BUBBLES;
1573 justSticked := false;
1574 onGround := (VelY >= 0) and g_Map_HasAnyPanelAtPoint(X, Y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
1575 awaken := false;
1576 end;
1578 if CurrentParticle+2 > MaxParticles then
1579 CurrentParticle := 0
1580 else
1581 CurrentParticle := CurrentParticle+1;
1582 end;
1583 end;
1585 procedure g_GFX_SetMax(Count: Integer);
1586 var
1587 a: Integer;
1588 begin
1589 if Count > 50000 then Count := 50000;
1590 if (Count < 1) then Count := 1;
1592 SetLength(Particles, Count);
1593 for a := 0 to High(Particles) do Particles[a].die();
1594 MaxParticles := Count;
1595 //if CurrentParticle >= Count then
1596 CurrentParticle := 0;
1597 end;
1599 function g_GFX_GetMax(): Integer;
1600 begin
1601 Result := MaxParticles;
1602 end;
1604 function FindOnceAnim: DWORD;
1605 var
1606 i: Integer;
1607 begin
1608 if OnceAnims <> nil then
1609 for i := 0 to High(OnceAnims) do
1610 if OnceAnims[i].Animation = nil then
1611 begin
1612 Result := i;
1613 Exit;
1614 end;
1616 if OnceAnims = nil then
1617 begin
1618 SetLength(OnceAnims, 16);
1619 Result := 0;
1620 end
1621 else
1622 begin
1623 Result := High(OnceAnims) + 1;
1624 SetLength(OnceAnims, Length(OnceAnims) + 16);
1625 end;
1626 end;
1628 procedure g_GFX_OnceAnim(X, Y: Integer; Anim: TAnimation; AnimType: Byte = 0);
1629 var
1630 find_id: DWORD;
1631 begin
1632 if not gpart_dbg_enabled then Exit;
1633 if Anim = nil then
1634 Exit;
1636 find_id := FindOnceAnim();
1638 OnceAnims[find_id].AnimType := AnimType;
1639 OnceAnims[find_id].Animation := TAnimation.Create(Anim.FramesID, Anim.Loop, Anim.Speed);
1640 OnceAnims[find_id].Animation.Blending := Anim.Blending;
1641 OnceAnims[find_id].Animation.Alpha := Anim.Alpha;
1642 OnceAnims[find_id].X := X;
1643 OnceAnims[find_id].Y := Y;
1644 end;
1646 procedure g_GFX_Update();
1647 var
1648 a: Integer;
1649 w, h: Integer;
1650 len: Integer;
1651 begin
1652 if not gpart_dbg_enabled then exit;
1654 if (Particles <> nil) then
1655 begin
1656 w := gMapInfo.Width;
1657 h := gMapInfo.Height;
1659 len := High(Particles);
1661 for a := 0 to len do
1662 begin
1663 if Particles[a].alive then
1664 begin
1665 with Particles[a] do
1666 begin
1667 if (Time = LiveTime) then begin die(); continue; end;
1668 if (X+1 >= w) or (Y+1 >= h) or (X <= 0) or (Y <= 0) then begin die(); end;
1669 //if not alive then Continue;
1670 //e_WriteLog(Format('particle #%d: %d', [State, ParticleType]), MSG_NOTIFY);
1671 think();
1672 end; // with
1673 end; // if
1674 end; // for
1675 end; // Particles <> nil
1677 // clear awake map
1678 awmClear();
1680 if OnceAnims <> nil then
1681 begin
1682 for a := 0 to High(OnceAnims) do
1683 if OnceAnims[a].Animation <> nil then
1684 begin
1685 case OnceAnims[a].AnimType of
1686 ONCEANIM_SMOKE:
1687 begin
1688 if Random(3) = 0 then
1689 OnceAnims[a].X := OnceAnims[a].X-1+Random(3);
1690 if Random(2) = 0 then
1691 OnceAnims[a].Y := OnceAnims[a].Y-Random(2);
1692 end;
1693 end;
1695 if OnceAnims[a].Animation.Played then
1696 begin
1697 OnceAnims[a].Animation.Free();
1698 OnceAnims[a].Animation := nil;
1699 end
1700 else
1701 OnceAnims[a].Animation.Update();
1702 end;
1703 end;
1704 end;
1706 procedure g_GFX_Draw();
1707 var
1708 a, len: Integer;
1709 begin
1710 if Particles <> nil then
1711 begin
1712 glDisable(GL_TEXTURE_2D);
1713 glPointSize(2);
1715 glEnable(GL_BLEND);
1716 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1718 glBegin(GL_POINTS);
1720 len := High(Particles);
1722 for a := 0 to len do
1723 with Particles[a] do
1724 if alive and (X >= sX) and (Y >= sY) and (X <= sX+sWidth) and (sY <= sY+sHeight) then
1725 begin
1726 glColor4ub(Red, Green, Blue, Alpha);
1727 glVertex2i(X + offsetX, Y + offsetY);
1728 end;
1730 glEnd();
1732 glDisable(GL_BLEND);
1733 end;
1735 if OnceAnims <> nil then
1736 for a := 0 to High(OnceAnims) do
1737 if OnceAnims[a].Animation <> nil then
1738 with OnceAnims[a] do
1739 Animation.Draw(X, Y, M_NONE);
1740 end;
1742 end.