DEADSOFTWARE

particle code uglyfying; more gitiks in holmes
[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);
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;
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
91 //k8: sorry, i have to emulate virtual methods this way, 'cause i haet `Object`
93 procedure thinkerBlood ();
94 procedure thinkerSpark ();
95 procedure thinkerBubble ();
96 procedure thinkerWater ();
98 function alive (): Boolean; inline;
99 procedure die (); inline;
100 procedure think (); inline;
101 end;
103 TOnceAnim = record
104 AnimType: Byte;
105 X, Y: Integer;
106 Animation: TAnimation;
107 end;
109 const
110 PARTICLE_BLOOD = 0;
111 PARTICLE_SPARK = 1;
112 PARTICLE_BUBBLES = 2;
113 PARTICLE_WATER = 3;
114 STATE_FREE = 0;
115 STATE_NORMAL = 1;
116 STATE_STICK = 2;
118 var
119 Particles: array of TParticle;
120 OnceAnims: array of TOnceAnim;
121 MaxParticles: Integer;
122 CurrentParticle: Integer;
125 // ////////////////////////////////////////////////////////////////////////// //
126 function TParticle.alive (): Boolean; inline; begin result := (State <> STATE_FREE); end;
127 procedure TParticle.die (); inline; begin State := STATE_FREE; end;
129 procedure TParticle.think (); inline;
130 begin
131 case ParticleType of
132 PARTICLE_BLOOD: thinkerBlood();
133 PARTICLE_SPARK: thinkerSpark();
134 PARTICLE_BUBBLES: thinkerBubble();
135 PARTICLE_WATER: thinkerWater();
136 end;
137 end;
140 // ////////////////////////////////////////////////////////////////////////// //
141 function isBlockedAt (x, y: Integer): Boolean; inline;
142 begin
143 if not gpart_dbg_phys_enabled then begin result := false; exit; end;
144 result := g_Map_HasAnyPanelAtPoint(x, y, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
145 end;
147 // ???
148 function isWallAt (x, y: Integer): Boolean; inline;
149 begin
150 if not gpart_dbg_phys_enabled then begin result := false; exit; end;
151 result := g_Map_HasAnyPanelAtPoint(x, y, (PANEL_WALL or PANEL_STEP));
152 end;
154 function isLiftUpAt (x, y: Integer): Boolean; inline;
155 begin
156 if not gpart_dbg_phys_enabled then begin result := false; exit; end;
157 result := g_Map_HasAnyPanelAtPoint(x, y, PANEL_LIFTUP);
158 end;
160 function isLiftDownAt (x, y: Integer): Boolean; inline;
161 begin
162 if not gpart_dbg_phys_enabled then begin result := false; exit; end;
163 result := g_Map_HasAnyPanelAtPoint(x, y, PANEL_LIFTDOWN);
164 end;
166 function isLiftLeftAt (x, y: Integer): Boolean; inline;
167 begin
168 if not gpart_dbg_phys_enabled then begin result := false; exit; end;
169 result := g_Map_HasAnyPanelAtPoint(x, y, PANEL_LIFTLEFT);
170 end;
172 function isLiftRightAt (x, y: Integer): Boolean; inline;
173 begin
174 if not gpart_dbg_phys_enabled then begin result := false; exit; end;
175 result := g_Map_HasAnyPanelAtPoint(x, y, PANEL_LIFTRIGHT);
176 end;
178 function isLiquidAt (x, y: Integer): Boolean; inline;
179 begin
180 if not gpart_dbg_phys_enabled then begin result := false; exit; end;
181 result := g_Map_HasAnyPanelAtPoint(x, y, (PANEL_WATER or PANEL_ACID1 or PANEL_ACID2));
182 end;
184 function isAnythingAt (x, y: Integer): Boolean; inline;
185 begin
186 if not gpart_dbg_phys_enabled then begin result := false; exit; end;
187 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));
188 end;
191 procedure g_Mark(x, y, Width, Height: Integer; t: Byte; st: Boolean);
192 {$IF not DEFINED(HAS_COLLIDE_BITMAP)}
193 begin
194 end;
195 {$ELSE}
196 var
197 yy, y2, xx, x2: Integer;
198 begin
199 if x < 0 then
200 begin
201 Width := Width + x;
202 x := 0;
203 end;
205 if Width < 0 then
206 Exit;
208 if y < 0 then
209 begin
210 Height := Height + y;
211 y := 0;
212 end;
214 if Height < 0 then
215 Exit;
217 if x > gMapInfo.Width then
218 Exit;
219 if y > gMapInfo.Height then
220 Exit;
222 y2 := y + Height - 1;
223 if y2 > gMapInfo.Height then
224 y2 := gMapInfo.Height;
226 x2 := x + Width - 1;
227 if x2 > gMapInfo.Width then
228 x2 := gMapInfo.Width;
230 if st then
231 begin // Óñòàíîâèòü ïðèçíàê
232 for yy := y to y2 do
233 for xx := x to x2 do
234 gCollideMap[yy][xx] := gCollideMap[yy][xx] or t;
235 end
236 else
237 begin // Óáðàòü ïðèçíàê
238 t := not t;
239 for yy := y to y2 do
240 for xx := x to x2 do
241 gCollideMap[yy][xx] := gCollideMap[yy][xx] and t;
242 end;
243 end;
244 {$ENDIF}
247 {$IF DEFINED(HAS_COLLIDE_BITMAP)}
248 procedure CreateCollideMap();
249 var
250 a: Integer;
251 begin
252 g_Game_SetLoadingText(_lc[I_LOAD_COLLIDE_MAP]+' 1/6', 0, False);
253 SetLength(gCollideMap, gMapInfo.Height+1);
254 for a := 0 to High(gCollideMap) do
255 SetLength(gCollideMap[a], gMapInfo.Width+1);
257 if gWater <> nil then
258 begin
259 g_Game_SetLoadingText(_lc[I_LOAD_COLLIDE_MAP]+' 2/6', 0, True);
260 for a := 0 to High(gWater) do
261 with gWater[a] do
262 g_Mark(X, Y, Width, Height, MARK_WATER, True);
263 end;
265 if gAcid1 <> nil then
266 begin
267 g_Game_SetLoadingText(_lc[I_LOAD_COLLIDE_MAP]+' 3/6', 0, True);
268 for a := 0 to High(gAcid1) do
269 with gAcid1[a] do
270 g_Mark(X, Y, Width, Height, MARK_ACID, True);
271 end;
273 if gAcid2 <> nil then
274 begin
275 g_Game_SetLoadingText(_lc[I_LOAD_COLLIDE_MAP]+' 4/6', 0, True);
276 for a := 0 to High(gAcid2) do
277 with gAcid2[a] do
278 g_Mark(X, Y, Width, Height, MARK_ACID, True);
279 end;
281 if gLifts <> nil then
282 begin
283 g_Game_SetLoadingText(_lc[I_LOAD_COLLIDE_MAP]+' 5/6', 0, True);
284 for a := 0 to High(gLifts) do
285 with gLifts[a] do
286 begin
287 g_Mark(X, Y, Width, Height, MARK_LIFT, False);
289 if LiftType = 0 then
290 g_Mark(X, Y, Width, Height, MARK_LIFTUP, True)
291 else if LiftType = 1 then
292 g_Mark(X, Y, Width, Height, MARK_LIFTDOWN, True)
293 else if LiftType = 2 then
294 g_Mark(X, Y, Width, Height, MARK_LIFTLEFT, True)
295 else if LiftType = 3 then
296 g_Mark(X, Y, Width, Height, MARK_LIFTRIGHT, True)
297 end;
298 end;
300 if gWalls <> nil then
301 begin
302 g_Game_SetLoadingText(_lc[I_LOAD_COLLIDE_MAP]+' 6/6', 0, True);
303 for a := 0 to High(gWalls) do
304 begin
305 if gWalls[a].Door then
306 begin
307 // Çàêðûòàÿ äâåðü:
308 if gWalls[a].Enabled then
309 with gWalls[a] do
310 g_Mark(X, Y, Width, Height, MARK_DOOR, True)
311 else // Îòêðûòàÿ äâåðü:
312 if gWalls[a].Enabled then
313 with gWalls[a] do
314 g_Mark(X, Y, Width, Height, MARK_DOOR, False);
315 end
316 else // Ñòåíà
317 with gWalls[a] do
318 g_Mark(X, Y, Width, Height, MARK_WALL, True);
319 end;
320 end;
321 end;
322 {$ENDIF}
325 procedure g_GFX_Init();
326 begin
327 //CreateCollideMap();
328 end;
331 procedure g_GFX_Free();
332 var
333 a: Integer;
334 begin
335 Particles := nil;
336 SetLength(Particles, MaxParticles);
337 for a := 0 to High(Particles) do Particles[a].die();
338 CurrentParticle := 0;
340 if OnceAnims <> nil then
341 begin
342 for a := 0 to High(OnceAnims) do
343 OnceAnims[a].Animation.Free();
345 OnceAnims := nil;
346 end;
347 end;
351 procedure CorrectOffsets(id: Integer); inline;
352 var
353 part: PParticle;
354 begin
355 part := @Particles[id];
356 part.offsetX := 0;
357 part.offsetY := 0;
358 // check for upper wall
359 if isBlockedAt(part.X, part.Y-1) then part.offsetY := 1;
360 // check for left wall
361 if isBlockedAt(part.X-1, part.Y) then part.offsetX := 1;
362 end;
366 // ////////////////////////////////////////////////////////////////////////// //
367 procedure TParticle.thinkerBlood ();
368 var
369 w, h: Integer;
370 dX, dY: SmallInt;
371 b: Integer;
372 s: ShortInt;
373 begin
374 w := gMapInfo.Width;
375 h := gMapInfo.Height;
377 if gAdvBlood then
378 begin
379 if (State = STATE_STICK) then
381 if (not ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)) and
382 (not ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)) and
383 (not ByteBool(gCollideMap[Y, X-1] and MARK_BLOCKED)) and
384 (not ByteBool(gCollideMap[Y, X+1] and MARK_BLOCKED))
385 then
387 if (not isBlockedAt(X, Y-1)) and
388 (not isBlockedAt(X, Y+1)) and
389 (not isBlockedAt(X-1, Y)) and
390 (not isBlockedAt(X+1, Y))
391 then
392 begin // Îòëèïëà - êàïàåò
393 VelY := 0.5;
394 AccelY := 0.15;
395 State := STATE_NORMAL;
396 end
397 else
398 if Random(200) = 100 then
399 begin // Ïðèëåïëåíà - íî âîçìîæíî ñòåêàåò
400 VelY := 0.5;
401 AccelY := 0.15;
402 exit;
403 end;
405 if not isBlockedAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)} then
406 begin
407 if isLiftUpAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTUP)} then
408 begin // Ëèôò ââåðõ
409 if VelY > -4-Random(3) then
410 VelY := VelY - 0.8;
411 if Abs(VelX) > 0.1 then
412 VelX := VelX - VelX/10.0;
413 VelX := VelX + (Random-Random)*0.2;
414 AccelY := 0.15;
415 end;
416 if isLiftLeftAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTLEFT)} then
417 begin // Ïîòîê âëåâî
418 if VelX > -8-Random(3) then
419 VelX := VelX - 0.8;
420 AccelY := 0.15;
421 end;
422 if isLiftRightAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTRIGHT)} then
423 begin // Ïîòîê âïðàâî
424 if VelX < 8+Random(3) then
425 VelX := VelX + 0.8;
426 AccelY := 0.15;
427 end;
428 end;
430 dX := Round(VelX);
431 dY := Round(VelY);
433 if (Abs(VelX) < 0.1) and (Abs(VelY) < 0.1) then
434 if (State <> STATE_STICK) and
435 (not isBlockedAt(X, Y-1) {ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)}) and
436 (not isBlockedAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)}) and
437 (not isBlockedAt(X, Y+1) {ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)}) then
438 begin // Âèñèò â âîçäóõå - êàïàåò
439 VelY := 0.8;
440 AccelY := 0.5;
441 State := STATE_NORMAL;
442 end;
444 if dX <> 0 then
445 begin
446 if dX > 0 then
447 s := 1
448 else
449 s := -1;
451 dX := Abs(dX);
453 for b := 1 to dX do
454 begin
455 if (X+s >= w) or (X+s <= 0) then begin die(); break; end;
457 //c := gCollideMap[Y, X+s];
459 if isBlockedAt(X+s, Y) {ByteBool(c and MARK_BLOCKED)} then
460 begin // Ñòåíà/äâåðü
461 VelX := 0;
462 VelY := 0;
463 AccelX := 0;
464 AccelY := 0;
465 State := STATE_STICK;
466 Break;
467 end;
469 X := X+s;
470 end;
471 end;
473 if dY <> 0 then
474 begin
475 if dY > 0 then
476 s := 1
477 else
478 s := -1;
480 dY := Abs(dY);
482 for b := 1 to dY do
483 begin
484 if (Y+s >= h) or (Y+s <= 0) then begin die(); break; end;
486 //c := gCollideMap[Y+s, X];
488 if isBlockedAt(X, Y+s) {ByteBool(c and MARK_BLOCKED)} then
489 begin // Ñòåíà/äâåðü
490 VelX := 0;
491 VelY := 0;
492 AccelX := 0;
493 AccelY := 0;
494 if (s > 0) and (State <> STATE_STICK) then
495 State := STATE_NORMAL
496 else
497 State := STATE_STICK;
498 Break;
499 end;
501 Y := Y+s;
502 end;
503 end;
504 end // if gAdvBlood
505 else
506 begin
507 dX := Round(VelX);
508 dY := Round(VelY);
510 if (X+dX >= w) or (Y+dY >= h) or
511 (X+dX <= 0) or (Y+dY <= 0) or
512 isBlockedAt(X+dX, Y+dY) {ByteBool(gCollideMap[Y+dY, X+dX] and MARK_BLOCKED)} then
513 begin // Ñòåíà/äâåðü/ãðàíèöà
514 die();
515 VelX := 0;
516 VelY := 0;
517 end
518 else
519 begin
520 Y := Y + dY;
521 X := X + dX;
522 end;
523 end;
525 VelX := VelX + AccelX;
526 VelY := VelY + AccelY;
528 // Êðîâü ðàñòâîðÿåòñÿ â æèäêîñòè:
529 if isLiquidAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIQUID)} then
530 begin
531 Inc(Time);
533 Alpha := 255 - Trunc((255.0 * Time) / LiveTime);
534 end;
535 end;
538 // ////////////////////////////////////////////////////////////////////////// //
539 procedure TParticle.thinkerSpark ();
540 var
541 dX, dY: SmallInt;
542 {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
543 b: Integer;
544 s: ShortInt;
545 {$ELSE}
546 pan: TPanel;
547 ex, ey: Integer;
548 {$ENDIF}
549 begin
550 dX := Round(VelX);
551 dY := Round(VelY);
553 {$IF DEFINED(D2F_NEW_SPARK_THINKER)}
554 if (Abs(VelX) < 0.1) and (Abs(VelY) < 0.1) then
555 begin
556 pan := g_Map_traceToNearest(X, Y-1, X, Y+1, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey);
557 end;
558 {$ELSE}
559 if (Abs(VelX) < 0.1) and (Abs(VelY) < 0.1) and
560 (not isBlockedAt(X, Y-1) {ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)}) and
561 (not isBlockedAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)}) and
562 (not isBlockedAt(X, Y+1) {ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)}) then
563 begin // Âèñèò â âîçäóõå
564 VelY := 0.8;
565 AccelY := 0.5;
566 end;
567 {$ENDIF}
569 if (dX <> 0) then
570 begin
571 {$IF DEFINED(D2F_NEW_SPARK_THINKER)}
572 pan := g_Map_traceToNearest(X, Y, X+dX, Y, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey);
573 //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);
574 X := ex;
575 // free to ride?
576 if (pan <> nil) then
577 begin
578 // nope
579 if ((pan.tag and (GridTagAcid1 or GridTagAcid2 or GridTagWater)) <> 0) then begin die(); exit; end;
580 VelX := 0;
581 AccelX := 0;
582 end;
583 if (X < 0) or (X >= gMapInfo.Width) then begin die(); exit; end;
584 {$ELSE}
585 if (dX > 0) then s := 1 else s := -1;
586 dX := Abs(dX);
587 for b := 1 to dX do
588 begin
589 if (X+s >= gMapInfo.Width) or (X+s <= 0) then begin die(); break; end;
590 //c := gCollideMap[Y, X+s];
591 if isBlockedAt(X+s, Y) {ByteBool(c and MARK_BLOCKED)} then
592 begin // Ñòåíà/äâåðü - ïàäàåò âåðòèêàëüíî
593 VelX := 0;
594 AccelX := 0;
595 Break;
596 end
597 else // Ïóñòî:
598 if not isAnythingAt(X+s, Y) {c = MARK_FREE} then
599 X := X + s
600 else // Îñòàëüíîå:
601 begin
602 die();
603 break;
604 end;
605 end;
606 {$ENDIF}
607 end;
609 if (dY <> 0) then
610 begin
611 {$IF DEFINED(D2F_NEW_SPARK_THINKER)}
612 pan := g_Map_traceToNearest(X, Y, X, Y+dY, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey);
613 //e_WriteLog(Format('spark y-trace: (%d,%d)-(%d,%d); dy=%d; end=(%d,%d); hit=%d', [X, Y, X, Y+dY, dY, ex, ey, Integer(pan <> nil)]), MSG_NOTIFY);
614 (*
615 if (pan <> nil) then
616 begin
617 e_WriteLog(Format('spark y-trace: %08x (%d,%d)-(%d,%d); dy=%d; end=(%d,%d); hittag=%04x', [LongWord(@self), X, Y, X, Y+dY, dY, ex, ey, pan.tag]), MSG_NOTIFY);
618 end
619 else
620 begin
621 e_WriteLog(Format('spark y-trace: %08x (%d,%d)-(%d,%d); dy=%d; end=(%d,%d); hit=%d', [LongWord(@self), X, Y, X, Y+dY, dY, ex, ey, Integer(pan <> nil)]), MSG_NOTIFY);
622 end;
623 *)
624 Y := ey;
625 // free to ride?
626 if (pan <> nil) then
627 begin
628 //die(); exit;
629 // nope
630 if ((pan.tag and (GridTagAcid1 or GridTagAcid2 or GridTagWater)) <> 0) then begin die(); exit; end;
631 if (dY < 0) then
632 begin
633 VelY := -VelY;
634 AccelY := abs(AccelY);
635 end
636 else
637 begin
638 VelX := 0;
639 AccelX := 0;
640 VelY := 0;
641 AccelY := 0.8;
642 end;
643 end;
644 if (Y < 0) or (Y >= gMapInfo.Height) then begin die(); exit; end;
645 {$ELSE}
646 if (dY > 0) then s := 1 else s := -1;
647 dY := Abs(dY);
648 for b := 1 to dY do
649 begin
650 if (Y+s >= gMapInfo.Height) or (Y+s <= 0) then begin die(); break; end;
651 //c := gCollideMap[Y+s, X];
652 if isBlockedAt(X, Y+s) {ByteBool(c and MARK_BLOCKED)} then
653 begin // Ñòåíà/äâåðü - ïàäàåò âåðòèêàëüíî
654 if s < 0 then
655 begin
656 VelY := -VelY;
657 AccelY := Abs(AccelY);
658 end
659 else // Èëè íå ïàäàåò
660 begin
661 VelX := 0;
662 AccelX := 0;
663 VelY := 0;
664 AccelY := 0.8;
665 end;
667 Break;
668 end
669 else // Ïóñòî:
670 if not isAnythingAt(X, Y+s) {c = MARK_FREE} then
671 Y := Y + s
672 else // Îñàëüíîå:
673 begin
674 die();
675 break;
676 end;
677 end;
678 {$ENDIF}
679 end;
681 if (VelX <> 0.0) then VelX += AccelX;
683 if (VelY <> 0.0) then
684 begin
685 if (AccelY < 10) then AccelY += 0.08;
686 VelY += AccelY;
687 end;
689 Time += 1;
690 end;
693 // ////////////////////////////////////////////////////////////////////////// //
694 procedure TParticle.thinkerBubble ();
695 var
696 h: Integer;
697 dY: SmallInt;
698 b: Integer;
699 s: ShortInt;
700 begin
701 h := gMapInfo.Height;
703 dY := Round(VelY);
705 if dY <> 0 then
706 begin
707 if dY > 0 then
708 s := 1
709 else
710 s := -1;
712 for b := 1 to Abs(dY) do
713 begin
714 if (Y+s >= h) or (Y+s <= 0) then begin die(); break; end;
716 (*
717 if not isLiquidAt(X, Y+s) {ByteBool(gCollideMap[Y+s, X] and MARK_LIQUID)} then
718 begin // Óæå íå æèäêîñòü
719 State := STATE_FREE;
720 Break;
721 end;
722 *)
723 // we traced liquid before, so don't bother checking
724 if (Y+s <= liquidTopY) then begin die(); break; end;
726 Y := Y+s;
727 end;
728 end;
730 if VelY > -4 then
731 VelY := VelY + AccelY;
733 Time := Time + 1;
734 end;
737 // ////////////////////////////////////////////////////////////////////////// //
738 procedure TParticle.thinkerWater ();
739 var
740 w, h: Integer;
741 dX, dY: SmallInt;
742 b: Integer;
743 s: ShortInt;
744 begin
745 w := gMapInfo.Width;
746 h := gMapInfo.Height;
748 if (State = STATE_STICK) and (Random(30) = 15) then
749 begin // Ñòåêàåò/îòëèïàåò
750 VelY := 0.5;
751 AccelY := 0.15;
752 if (not isBlockedAt(X-1, Y) {ByteBool(gCollideMap[Y, X-1] and MARK_BLOCKED)}) and
753 (not isBlockedAt(X+1, Y) {ByteBool(gCollideMap[Y, X+1] and MARK_BLOCKED)}) then
754 State := STATE_NORMAL;
755 exit;
756 end;
758 if not isBlockedAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)} then
759 begin
760 if isLiftUpAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTUP)} then
761 begin // Ëèôò ââåðõ
762 if VelY > -4-Random(3) then
763 VelY := VelY - 0.8;
764 if Abs(VelX) > 0.1 then
765 VelX := VelX - VelX/10.0;
766 VelX := VelX + (Random-Random)*0.2;
767 AccelY := 0.15;
768 end;
769 if isLiftLeftAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTLEFT)} then
770 begin // Ïîòîê âëåâî
771 if VelX > -8-Random(3) then
772 VelX := VelX - 0.8;
773 AccelY := 0.15;
774 end;
775 if isLiftRightAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTRIGHT)} then
776 begin // Ïîòîê âïðàâî
777 if VelX < 8+Random(3) then
778 VelX := VelX + 0.8;
779 AccelY := 0.15;
780 end;
781 end;
783 dX := Round(VelX);
784 dY := Round(VelY);
786 if (Abs(VelX) < 0.1) and (Abs(VelY) < 0.1) then
787 if (State <> STATE_STICK) and
788 (not isBlockedAt(X, Y-1) {ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)}) and
789 (not isBlockedAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)}) and
790 (not isBlockedAt(X, Y+1) {ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)}) then
791 begin // Âèñèò â âîçäóõå - êàïàåò
792 VelY := 0.8;
793 AccelY := 0.5;
794 State := STATE_NORMAL;
795 end;
797 if dX <> 0 then
798 begin
799 if dX > 0 then
800 s := 1
801 else
802 s := -1;
804 for b := 1 to Abs(dX) do
805 begin
806 // Ñáîêó ãðàíèöà?
807 if (X+s >= w) or (X+s <= 0) then begin die(); break;end;
809 //c := gCollideMap[Y, X+s];
811 // Ñáîêó æèäêîñòü, à ÷àñòèöà óæå ïàäàåò?
812 if isLiquidAt(X+s, Y) {ByteBool(c and MARK_LIQUID)} and (dY > 0) then begin die(); break; end;
814 if isBlockedAt(X+s, Y) {ByteBool(c and MARK_BLOCKED)} then
815 begin // Ñòåíà/äâåðü
816 VelX := 0;
817 VelY := 0;
818 AccelX := 0;
819 AccelY := 0;
820 State := STATE_STICK;
821 Break;
822 end;
824 X := X+s;
825 end;
826 end;
828 if dY <> 0 then
829 begin
830 if dY > 0 then
831 s := 1
832 else
833 s := -1;
835 for b := 1 to Abs(dY) do
836 begin
837 // Ñíèçó/ñâåðõó ãðàíèöà
838 if (Y+s >= h) or (Y+s <= 0) then begin die(); break; end;
840 //c := gCollideMap[Y+s, X];
842 // Ñíèçó æèäêîñòü, à ÷àñòèöà óæå ïàäàåò
843 if isLiquidAt(X, Y+s) {ByteBool(c and MARK_LIQUID)} and (dY > 0) then begin die(); break; end;
845 if isBlockedAt(X, Y+s) {ByteBool(c and MARK_BLOCKED)} then
846 begin // Ñòåíà/äâåðü
847 VelX := 0;
848 VelY := 0;
849 AccelX := 0;
850 AccelY := 0;
851 if (s > 0) and (State <> STATE_STICK) then
852 State := STATE_NORMAL
853 else
854 State := STATE_STICK;
855 Break;
856 end;
858 Y := Y+s;
859 end;
860 end;
862 VelX := VelX + AccelX;
863 VelY := VelY + AccelY;
865 Time := Time + 1;
866 end;
869 // ////////////////////////////////////////////////////////////////////////// //
870 procedure g_GFX_SparkVel (fX, fY: Integer; Count: Word; VX, VY: Integer; DevX, DevY: Byte);
871 var
872 a: Integer;
873 DevX1, DevX2,
874 DevY1, DevY2: Byte;
875 l: Integer;
876 begin
877 l := Length(Particles);
878 if l = 0 then exit;
879 if Count > l then Count := l;
881 DevX1 := DevX div 2;
882 DevX2 := DevX + 1;
883 DevY1 := DevY div 2;
884 DevY2 := DevY + 1;
886 for a := 1 to Count do
887 begin
888 with Particles[CurrentParticle] do
889 begin
890 X := fX-DevX1+Random(DevX2);
891 Y := fY-DevY1+Random(DevY2);
893 VelX := VX + (Random-Random)*3;
894 VelY := VY + (Random-Random)*3;
896 if VelY > -4 then
897 if VelY-4 < -4 then
898 VelY := -4
899 else
900 VelY := VelY-4;
902 AccelX := -Sign(VelX)*Random/100;
903 AccelY := 0.8;
905 Red := 255;
906 Green := 100+Random(155);
907 Blue := 64;
908 Alpha := 255;
910 State := STATE_NORMAL;
911 Time := 0;
912 LiveTime := 30+Random(60);
913 ParticleType := PARTICLE_SPARK;
915 {CorrectOffsets(CurrentParticle);}
916 end;
918 if CurrentParticle+2 > MaxParticles then
919 CurrentParticle := 0
920 else
921 CurrentParticle := CurrentParticle+1;
922 end;
923 end;
926 procedure g_GFX_Blood(fX, fY: Integer; Count: Word; vx, vy: Integer;
927 DevX, DevY: Word; CR, CG, CB: Byte; Kind: Byte = BLOOD_NORMAL);
928 var
929 a: Integer;
930 DevX1, DevX2,
931 DevY1, DevY2: Word;
932 l: Integer;
933 CRnd: Byte;
934 CC: SmallInt;
935 begin
936 if Kind = BLOOD_SPARKS then
937 begin
938 g_GFX_SparkVel(fX, fY, 2 + Random(2), -VX div 2, -VY div 2, DevX, DevY);
939 Exit;
940 end;
941 l := Length(Particles);
942 if l = 0 then
943 Exit;
944 if Count > l then
945 Count := l;
947 DevX1 := DevX div 2;
948 DevX2 := DevX + 1;
949 DevY1 := DevY div 2;
950 DevY2 := DevY + 1;
952 for a := 1 to Count do
953 begin
954 with Particles[CurrentParticle] do
955 begin
956 X := fX - DevX1 + Random(DevX2);
957 Y := fY - DevY1 + Random(DevY2);
960 if (X < 0) or (X > gMapInfo.Width-1) or
961 (Y < 0) or (Y > gMapInfo.Height-1) or
962 ByteBool(gCollideMap[Y, X] and MARK_WALL) then
963 Continue;
965 if isWallAt(X, Y) then continue;
967 VelX := vx + (Random-Random)*3;
968 VelY := vy + (Random-Random)*3;
970 if VelY > -4 then
971 if VelY-4 < -4 then
972 VelY := -4
973 else
974 VelY := VelY-4;
976 AccelX := -Sign(VelX)*Random/100;
977 AccelY := 0.8;
979 CRnd := 20*Random(6);
980 if CR > 0 then
981 begin
982 CC := CR + CRnd - 50;
983 if CC < 0 then CC := 0;
984 if CC > 255 then CC := 255;
985 Red := CC;
986 end else
987 Red := 0;
988 if CG > 0 then
989 begin
990 CC := CG + CRnd - 50;
991 if CC < 0 then CC := 0;
992 if CC > 255 then CC := 255;
993 Green := CC;
994 end else
995 Green := 0;
996 if CB > 0 then
997 begin
998 CC := CB + CRnd - 50;
999 if CC < 0 then CC := 0;
1000 if CC > 255 then CC := 255;
1001 Blue := CC;
1002 end else
1003 Blue := 0;
1005 Alpha := 255;
1007 State := STATE_NORMAL;
1008 Time := 0;
1009 LiveTime := 120+Random(40);
1010 ParticleType := PARTICLE_BLOOD;
1012 {CorrectOffsets(CurrentParticle);}
1013 end;
1015 if CurrentParticle >= MaxParticles-1 then
1016 CurrentParticle := 0
1017 else
1018 CurrentParticle := CurrentParticle+1;
1019 end;
1020 end;
1023 procedure g_GFX_Spark(fX, fY: Integer; Count: Word; Angle: SmallInt; DevX, DevY: Byte);
1024 var
1025 a: Integer;
1026 b: Single;
1027 DevX1, DevX2,
1028 DevY1, DevY2: Byte;
1029 BaseVelX, BaseVelY: Single;
1030 l: Integer;
1031 begin
1032 l := Length(Particles);
1033 if l = 0 then
1034 Exit;
1035 if Count > l then
1036 Count := l;
1038 Angle := 360 - Angle;
1040 DevX1 := DevX div 2;
1041 DevX2 := DevX + 1;
1042 DevY1 := DevY div 2;
1043 DevY2 := DevY + 1;
1045 b := DegToRad(Angle);
1046 BaseVelX := cos(b);
1047 BaseVelY := 1.6*sin(b);
1048 if Abs(BaseVelX) < 0.01 then
1049 BaseVelX := 0.0;
1050 if Abs(BaseVelY) < 0.01 then
1051 BaseVelY := 0.0;
1052 for a := 1 to Count do
1053 begin
1054 with Particles[CurrentParticle] do
1055 begin
1056 X := fX-DevX1+Random(DevX2);
1057 Y := fY-DevY1+Random(DevY2);
1059 VelX := BaseVelX*Random;
1060 VelY := BaseVelY-Random;
1061 AccelX := VelX/3.0;
1062 AccelY := VelY/5.0;
1064 Red := 255;
1065 Green := 100+Random(155);
1066 Blue := 64;
1067 Alpha := 255;
1069 State := STATE_NORMAL;
1070 Time := 0;
1071 LiveTime := 30+Random(60);
1072 ParticleType := PARTICLE_SPARK;
1074 {CorrectOffsets(CurrentParticle);}
1075 end;
1077 if CurrentParticle+2 > MaxParticles then
1078 CurrentParticle := 0
1079 else
1080 CurrentParticle := CurrentParticle+1;
1081 end;
1082 end;
1084 procedure g_GFX_Water(fX, fY: Integer; Count: Word; fVelX, fVelY: Single; DevX, DevY, Color: Byte);
1085 var
1086 a: Integer;
1087 DevX1, DevX2,
1088 DevY1, DevY2: Byte;
1089 l: Integer;
1090 begin
1091 l := Length(Particles);
1092 if l = 0 then
1093 Exit;
1094 if Count > l then
1095 Count := l;
1097 if Abs(fVelX) < 3.0 then
1098 fVelX := 3.0 - 6.0*Random;
1100 DevX1 := DevX div 2;
1101 DevX2 := DevX + 1;
1102 DevY1 := DevY div 2;
1103 DevY2 := DevY + 1;
1105 for a := 1 to Count do
1106 begin
1107 with Particles[CurrentParticle] do
1108 begin
1109 X := fX-DevX1+Random(DevX2);
1110 Y := fY-DevY1+Random(DevY2);
1112 if Abs(fVelX) < 0.5 then
1113 VelX := 1.0 - 2.0*Random
1114 else
1115 VelX := fVelX*Random;
1116 if Random(10) < 7 then
1117 VelX := -VelX;
1118 VelY := fVelY*Random;
1119 AccelX := 0.0;
1120 AccelY := 0.8;
1122 case Color of
1123 1: // Êðàñíûé
1124 begin
1125 Red := 155 + Random(9)*10;
1126 Green := Trunc(150*Random);
1127 Blue := Green;
1128 end;
1129 2: // Çåëåíûé
1130 begin
1131 Red := Trunc(150*Random);
1132 Green := 175 + Random(9)*10;
1133 Blue := Red;
1134 end;
1135 3: // Ñèíèé
1136 begin
1137 Red := Trunc(200*Random);
1138 Green := Red;
1139 Blue := 175 + Random(9)*10;
1140 end;
1141 else // Ñåðûé
1142 begin
1143 Red := 90 + Random(12)*10;
1144 Green := Red;
1145 Blue := Red;
1146 end;
1147 end;
1149 Alpha := 255;
1151 State := STATE_NORMAL;
1152 Time := 0;
1153 LiveTime := 60+Random(60);
1154 ParticleType := PARTICLE_WATER;
1156 {CorrectOffsets(CurrentParticle);}
1157 end;
1159 if CurrentParticle+2 > MaxParticles then
1160 CurrentParticle := 0
1161 else
1162 CurrentParticle := CurrentParticle+1;
1163 end;
1164 end;
1166 procedure g_GFX_SimpleWater(fX, fY: Integer; Count: Word; fVelX, fVelY: Single; DefColor, CR, CG, CB: Byte);
1167 var
1168 a: Integer;
1169 l: Integer;
1170 begin
1171 l := Length(Particles);
1172 if l = 0 then
1173 Exit;
1174 if Count > l then
1175 Count := l;
1177 for a := 1 to Count do
1178 begin
1179 with Particles[CurrentParticle] do
1180 begin
1181 X := fX;
1182 Y := fY;
1184 VelX := fVelX;
1185 VelY := fVelY;
1186 AccelX := 0.0;
1187 AccelY := 0.8;
1189 case DefColor of
1190 1: // Êðàñíûé
1191 begin
1192 Red := 155 + Random(9)*10;
1193 Green := Trunc(150*Random);
1194 Blue := Green;
1195 end;
1196 2: // Çåëåíûé
1197 begin
1198 Red := Trunc(150*Random);
1199 Green := 175 + Random(9)*10;
1200 Blue := Red;
1201 end;
1202 3: // Ñèíèé
1203 begin
1204 Red := Trunc(200*Random);
1205 Green := Red;
1206 Blue := 175 + Random(9)*10;
1207 end;
1208 4: // Ñâîé öâåò, ñâåòëåå
1209 begin
1210 Red := 20 + Random(19)*10;
1211 Green := Red;
1212 Blue := Red;
1213 Red := Min(Red + CR, 255);
1214 Green := Min(Green + CG, 255);
1215 Blue := Min(Blue + CB, 255);
1216 end;
1217 5: // Ñâîé öâåò, òåìíåå
1218 begin
1219 Red := 20 + Random(19)*10;
1220 Green := Red;
1221 Blue := Red;
1222 Red := Max(CR - Red, 0);
1223 Green := Max(CG - Green, 0);
1224 Blue := Max(CB - Blue, 0);
1225 end;
1226 else // Ñåðûé
1227 begin
1228 Red := 90 + Random(12)*10;
1229 Green := Red;
1230 Blue := Red;
1231 end;
1232 end;
1234 Alpha := 255;
1236 State := STATE_NORMAL;
1237 Time := 0;
1238 LiveTime := 60+Random(60);
1239 ParticleType := PARTICLE_WATER;
1241 {CorrectOffsets(CurrentParticle);}
1242 end;
1244 if CurrentParticle+2 > MaxParticles then
1245 CurrentParticle := 0
1246 else
1247 CurrentParticle := CurrentParticle+1;
1248 end;
1249 end;
1252 procedure g_GFX_Bubbles(fX, fY: Integer; Count: Word; DevX, DevY: Byte);
1253 var
1254 a: Integer;
1255 DevX1, DevX2,
1256 DevY1, DevY2: Byte;
1257 l, liquidx: Integer;
1258 begin
1259 l := Length(Particles);
1260 if l = 0 then
1261 Exit;
1262 if Count > l then
1263 Count := l;
1265 DevX1 := DevX div 2;
1266 DevX2 := DevX + 1;
1267 DevY1 := DevY div 2;
1268 DevY2 := DevY + 1;
1270 for a := 1 to Count do
1271 begin
1272 with Particles[CurrentParticle] do
1273 begin
1274 X := fX-DevX1+Random(DevX2);
1275 Y := fY-DevY1+Random(DevY2);
1277 if (X >= gMapInfo.Width) or (X <= 0) or
1278 (Y >= gMapInfo.Height) or (Y <= 0) then
1279 Continue;
1281 (*
1282 // don't spawn bubbles outside of the liquid
1283 if not isLiquidAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIQUID)} then
1284 Continue;
1285 *)
1287 // trace liquid, so we'll know where it ends; do it in 8px steps for speed
1288 // tracer will return `false` if we started outside of the liquid
1289 if not g_Map_TraceLiquid(X, Y, 0, -8, liquidx, liquidTopY) then continue;
1291 VelX := 0;
1292 VelY := -1-Random;
1293 AccelX := 0;
1294 AccelY := VelY/10;
1296 Red := 255;
1297 Green := 255;
1298 Blue := 255;
1299 Alpha := 255;
1301 State := STATE_NORMAL;
1302 Time := 0;
1303 LiveTime := 65535;
1304 ParticleType := PARTICLE_BUBBLES;
1306 {CorrectOffsets(CurrentParticle);}
1307 end;
1309 if CurrentParticle+2 > MaxParticles then
1310 CurrentParticle := 0
1311 else
1312 CurrentParticle := CurrentParticle+1;
1313 end;
1314 end;
1316 procedure g_GFX_SetMax(Count: Integer);
1317 var
1318 a: Integer;
1319 begin
1320 if Count > 50000 then Count := 50000;
1321 if (Count < 1) then Count := 1;
1323 SetLength(Particles, Count);
1324 for a := 0 to High(Particles) do Particles[a].die();
1325 MaxParticles := Count;
1326 //if CurrentParticle >= Count then
1327 CurrentParticle := 0;
1328 end;
1330 function g_GFX_GetMax(): Integer;
1331 begin
1332 Result := MaxParticles;
1333 end;
1335 function FindOnceAnim: DWORD;
1336 var
1337 i: Integer;
1338 begin
1339 if OnceAnims <> nil then
1340 for i := 0 to High(OnceAnims) do
1341 if OnceAnims[i].Animation = nil then
1342 begin
1343 Result := i;
1344 Exit;
1345 end;
1347 if OnceAnims = nil then
1348 begin
1349 SetLength(OnceAnims, 16);
1350 Result := 0;
1351 end
1352 else
1353 begin
1354 Result := High(OnceAnims) + 1;
1355 SetLength(OnceAnims, Length(OnceAnims) + 16);
1356 end;
1357 end;
1359 procedure g_GFX_OnceAnim(X, Y: Integer; Anim: TAnimation; AnimType: Byte = 0);
1360 var
1361 find_id: DWORD;
1362 begin
1363 if Anim = nil then
1364 Exit;
1366 find_id := FindOnceAnim();
1368 OnceAnims[find_id].AnimType := AnimType;
1369 OnceAnims[find_id].Animation := TAnimation.Create(Anim.FramesID, Anim.Loop, Anim.Speed);
1370 OnceAnims[find_id].Animation.Blending := Anim.Blending;
1371 OnceAnims[find_id].Animation.Alpha := Anim.Alpha;
1372 OnceAnims[find_id].X := X;
1373 OnceAnims[find_id].Y := Y;
1374 end;
1376 procedure g_GFX_Update();
1377 var
1378 a: Integer;
1379 w, h: Integer;
1380 len: Integer;
1381 begin
1382 if not gpart_dbg_enabled then exit;
1383 if Particles <> nil then
1384 begin
1385 w := gMapInfo.Width;
1386 h := gMapInfo.Height;
1388 len := High(Particles);
1390 for a := 0 to len do
1391 begin
1392 if Particles[a].alive then
1393 begin
1394 with Particles[a] do
1395 begin
1396 if (Time = LiveTime) then begin die(); continue; end;
1397 if (X+1 >= w) or (Y+1 >= h) or (X <= 0) or (Y <= 0) then begin die(); end;
1398 //if not alive then Continue;
1399 //e_WriteLog(Format('particle #%d: %d', [State, ParticleType]), MSG_NOTIFY);
1400 think();
1401 {CorrectOffsets(a);}
1402 end; // with
1403 end; // if
1404 end; // for
1405 end; // Particles <> nil
1407 if OnceAnims <> nil then
1408 begin
1409 for a := 0 to High(OnceAnims) do
1410 if OnceAnims[a].Animation <> nil then
1411 begin
1412 case OnceAnims[a].AnimType of
1413 ONCEANIM_SMOKE:
1414 begin
1415 if Random(3) = 0 then
1416 OnceAnims[a].X := OnceAnims[a].X-1+Random(3);
1417 if Random(2) = 0 then
1418 OnceAnims[a].Y := OnceAnims[a].Y-Random(2);
1419 end;
1420 end;
1422 if OnceAnims[a].Animation.Played then
1423 begin
1424 OnceAnims[a].Animation.Free();
1425 OnceAnims[a].Animation := nil;
1426 end
1427 else
1428 OnceAnims[a].Animation.Update();
1429 end;
1430 end;
1431 end;
1433 procedure g_GFX_Draw();
1434 var
1435 a, len: Integer;
1436 begin
1437 if Particles <> nil then
1438 begin
1439 glDisable(GL_TEXTURE_2D);
1440 glPointSize(2);
1442 glEnable(GL_BLEND);
1443 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1445 glBegin(GL_POINTS);
1447 len := High(Particles);
1449 for a := 0 to len do
1450 with Particles[a] do
1451 if alive and (X >= sX) and (Y >= sY) and (X <= sX+sWidth) and (sY <= sY+sHeight) then
1452 begin
1453 glColor4ub(Red, Green, Blue, Alpha);
1454 glVertex2i(X + offsetX, Y + offsetY);
1455 end;
1457 glEnd();
1459 glDisable(GL_BLEND);
1460 end;
1462 if OnceAnims <> nil then
1463 for a := 0 to High(OnceAnims) do
1464 if OnceAnims[a].Animation <> nil then
1465 with OnceAnims[a] do
1466 Animation.Draw(X, Y, M_NONE);
1467 end;
1469 end.