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}
20 interface
22 uses
25 const
52 procedure g_GFX_Water (fX, fY: Integer; count: Word; fVelX, fVelY: Single; devX, devY, color: Byte;
54 procedure g_GFX_SimpleWater (fX, fY: Integer; count: Word; fVelX, fVelY: Single; defColor, cr, cg, cb: Byte);
68 var
73 implementation
75 uses
81 const
85 type
89 // Wall: floorY is just before floor
90 // LiquidIn: floorY is liquid *start* (i.e. just in a liquid)
91 // LiquidOut: floorY is liquid *end* (i.e. just out of a liquid)
94 // note: this MUST be record, so we can keep it in
95 // dynamic array and has sequential memory access pattern
107 // for water
115 // for all
120 //k8: sorry, i have to emulate virtual methods this way, 'cause i haet `Object`
146 var
151 // awakeMap has one bit for each map grid cell; on g_Mark,
152 // corresponding bits will be set, and in `think()` all particles
153 // in marked cells will be awaken
160 // ////////////////////////////////////////////////////////////////////////// //
161 // HACK! using mapgrid
163 begin
169 begin
177 {$IF DEFINED(D2F_DEBUG)}
178 e_LogWritefln('particle awake map: %sx%s (for grid of size %sx%s)', [awakeMapW, awakeMapH, mapGrid.gridWidth, mapGrid.gridHeight]);
179 {$ENDIF}
185 begin
189 begin
190 {$IF DEFINED(D2F_DEBUG)}
192 {$ENDIF}
194 end
195 else
196 begin
203 var
205 begin
209 begin
210 {$IF DEFINED(D2F_DEBUG)}
212 {$ENDIF}
219 // ////////////////////////////////////////////////////////////////////////// //
224 begin
229 begin
231 begin
232 // wakeup this particle
233 {
234 if (part.ParticleType = PARTICLE_SPARK) then
235 begin
236 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]);
237 end;
238 }
241 begin
251 // remove velocities and acceleration
253 begin
254 // stop right there, you criminal scum!
262 // switch to sleep mode
264 begin
271 var
274 begin
276 // stuck in the wall? rescan, 'cause it can be mplat
278 begin
281 begin
282 // either in a wall, or in a liquid
284 begin
285 // we are in the wall, wtf?!
290 exit;
292 // we are in liquid, trace to liquid end
296 // are we in a liquid?
298 begin
299 // trace out of the liquid
300 //env := TEnvType.ELiquid;
302 //e_LogWritefln('tracing out of a liquid; floorY=%s; y=%s', [floorY, y]);
305 //e_LogWritefln(' traced out of a liquid; floorY=%s; y=%s', [floorY, y]);
306 end
307 else
308 begin
309 // in the air
311 //env := TEnvType.EAir;
312 pan := g_Map_traceToNearest(x, y, x, g_Map_MaxY, (GridTagObstacle or GridTagLiquid), @ex, @floorY);
314 begin
315 // wall or liquid
317 begin
318 // wall
320 end
321 else
322 begin
323 // liquid
327 end
328 else
329 begin
330 // out of the level; assume wall, but it doesn't really matter
339 var
341 begin
344 begin
351 begin
352 // awake sleeping particle, if necessary
356 //TPartType.Spark: thinkerSpark();
362 // ////////////////////////////////////////////////////////////////////////// //
365 begin
373 var
375 begin
379 // find next floor transition
381 // find `wallEndY`
382 mapGrid.traceOrthoRayWhileIn(ex, wallEndY, x+stickDX, y, x+stickDX, floorY+1, (GridTagWall or GridTagDoor or GridTagStep));
383 //if (wallEndY > floorY) then wallEndY := floorY; // just in case
387 begin
394 // `true`: didn't, get outa thinker
396 begin
405 // `true`: affected by air stream
407 var
409 begin
413 begin
415 begin
420 end
422 begin
425 end
427 begin
430 end
431 else
432 begin
435 // awake
440 // switch to freefall mode
442 begin
449 begin
452 begin
455 end
456 else
457 begin
463 label
464 _done;
465 var
469 begin
471 begin
472 // still check for air streams when sleeping
473 if (state = TPartState.Sleeping) then begin checkAirStreams(); goto _done; end; // so blood will dissolve
475 // process stuck particles
477 begin
478 // stuck to a ceiling?
480 begin
481 // yeah, stuck to a ceiling
483 // dropped from a ceiling?
485 begin
486 // yep
490 end
491 else
492 begin
493 // otherwise, try to drip
496 end
497 else
498 begin
499 // stuck to a wall
501 // floor transition?
503 begin
506 begin
511 begin
512 // rescan, so we'll know when we'll exit the liquid
516 begin
517 // rescan, so we'll know when we'll enter something interesting
523 // wall transition?
525 begin
526 // just unstuck from the wall, switch to freefall mode
528 end
529 else
530 begin
531 // otherwise, try to drip
535 // nope, process as usual
538 // it is important to have it here
542 // gravity, if not stuck
544 begin
546 // floor transition?
548 begin
551 begin
552 // nothing to do
555 begin
556 // rescan, so we'll know when we'll exit the liquid
561 begin
562 // rescan, so we'll know when we'll enter something interesting
564 if (floorType <> TFloorType.Wall) or (floorY <> y) then applyGravity(floorType = TFloorType.LiquidIn);
567 end
568 else
569 begin
570 // looks like we're in the air
575 // trace movement
577 begin
578 // has some horizontal velocity
585 begin
586 // we stuck
587 // the only case when we can have both ceiling and wall is corner; stick to wall in this case
588 // check environment (air/liquid)
589 if (g_Map_PanelAtPoint(x, y, GridTagLiquid) <> nil) then env := TEnvType.ELiquid else env := TEnvType.EAir;
590 // check if we stuck to a wall
593 begin
594 // stuck to a wall
596 end
597 else
598 begin
599 // stuck to a ceiling
603 end
605 begin
606 // has only vertical velocity
608 begin
609 // flying up
613 // environmend didn't changed
614 end
615 else
616 begin
618 begin
619 // falling down
622 //e_LogWritefln('floorY=%s; newy=%s; dY=%s; floorType=%s', [floorY, y, dY, floorType]);
624 begin
625 // floor transition
628 //e_LogWritefln(' HIT FLOORY: floorY=%s; newy=%s; dY=%s; floorType=%s', [floorY, y, dY, floorType]);
631 begin
632 // environmend didn't changed
637 begin
638 // we're entered the liquid
640 // rescan, so we'll know when we'll exit the liquid
644 begin
645 // we're exited the liquid
647 // rescan, so we'll know when we'll enter something interesting
650 begin
656 end
657 else
658 begin
665 else
666 begin
667 // simple blood
672 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;
676 _done:
680 // blood will dissolve in other liquids
682 begin
684 begin
692 end
693 else
694 begin
695 // water will disappear in water (?)
702 // ////////////////////////////////////////////////////////////////////////// //
703 procedure g_GFX_SparkVel (fX, fY: Integer; count: Word; VX, VY: Integer; devX, devY: Byte); forward;
709 begin
711 begin
716 end
717 else
718 begin
723 var
729 begin
733 begin
735 exit;
748 begin
750 begin
754 // check for level bounds
757 // in what environment we are starting in?
760 begin
761 // either in a wall, or in a liquid
764 end
765 else
766 begin
774 begin
801 procedure g_GFX_Water (fX, fY: Integer; count: Word; fVelX, fVelY: Single; devX, devY, color: Byte;
803 var
808 begin
825 begin
827 begin
829 begin
838 end
839 else
840 begin
850 // check for level bounds
853 // in what environment we are starting in?
856 begin
857 // either in a wall, or in a liquid
858 //if ((pan.tag and GridTagObstacle) <> 0) then continue; // don't spawn in walls
859 //env := TEnvType.ELiquid;
860 continue;
861 end
862 else
863 begin
867 // color
870 begin
876 begin
882 begin
888 begin
897 begin
906 begin
927 procedure g_GFX_SimpleWater (fX, fY: Integer; count: Word; fVelX, fVelY: Single; defColor, cr, cg, cb: Byte);
928 begin
933 // ////////////////////////////////////////////////////////////////////////// //
935 var
937 begin
941 begin
944 begin
946 end
947 else
948 begin
960 {.$DEFINE D2F_DEBUG_BUBBLES}
962 var
966 {$IF DEFINED(D2F_DEBUG_BUBBLES)}
969 {$ENDIF}
970 begin
983 begin
985 begin
989 // check for level bounds
992 (*
993 // don't spawn bubbles outside of the liquid
994 if not isLiquidAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIQUID)} then
995 Continue;
996 *)
998 // trace liquid, so we'll know where it ends; do it in 8px steps for speed
999 // tracer will return `false` if we started outside of the liquid
1001 {$IF DEFINED(D2F_DEBUG_BUBBLES)}
1003 ptr := mapGrid.traceOrthoRayWhileIn(liquidx, liquidTopY, x, y, x, 0, GridTagWater or GridTagAcid1 or GridTagAcid2);
1005 e_LogWritefln('traceOrthoRayWhileIn: time=%s (%s); liquidTopY=%s', [Integer(stt), ptr, liquidTopY]);
1006 //
1010 e_LogWritefln('g_Map_TraceLiquidNonPrecise: time=%s (%s); liquidTopY=%s', [Integer(stt), nptr, liquidTopY]);
1012 {$ELSE}
1015 {$ENDIF}
1038 // ////////////////////////////////////////////////////////////////////////// //
1040 begin
1042 result := g_Map_HasAnyPanelAtPoint(x, y, (PANEL_WALL or PANEL_OPENDOOR or PANEL_CLOSEDOOR or PANEL_STEP));
1045 // ???
1047 begin
1053 begin
1059 begin
1065 begin
1071 begin
1077 begin
1083 begin
1085 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));
1089 // ////////////////////////////////////////////////////////////////////////// //
1090 (*
1091 procedure TParticle.thinkerWater ();
1092 var
1093 dX, dY: SmallInt;
1094 pan: TPanel;
1095 ex, ey: Integer;
1096 begin
1097 //TODO: trace wall end when water becomes stick
1098 if (state = TPartState.Stuck) and (Random(30) = 15) then
1099 begin // Ñòåêàåò/îòëèïàåò
1100 velY := 0.5;
1101 accelY := 0.15;
1102 if (stickDX = 0) then
1103 begin
1104 // no walls around, drop
1105 state := TPartState.Normal;
1106 end
1107 else
1108 begin
1109 if justSticked then
1110 begin
1111 if not mapGrid.traceOrthoRayWhileIn(ex, ey, x+stickDX, y, x+stickDX, mapGrid.gridY0+mapGrid.gridHeight, GridTagWall or GridTagDoor or GridTagStep) then
1112 begin
1113 // îòëèïëà
1114 state := TPartState.Normal;
1115 //e_LogWritefln('juststicked unsticked: X=%s; X+stickDX=%s; stickDX=%s; Y=%s', [X, X+stickDX, stickDX, Y]);
1116 end
1117 else
1118 begin
1119 stickEY := ey+1;
1120 justSticked := false;
1121 if (nil <> g_Map_traceToNearest(x, y, x, stickEY, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey)) then
1122 begin
1123 if (ey > stickEY) then stickEY := ey-1;
1124 end;
1125 //e_LogWritefln('juststicked: X=%s; X+stickDX=%s; stickDX=%s; Y=%s; stickEY=%s', [X, X+stickDX, stickDX, Y, stickEY]);
1126 end;
1127 end
1128 else
1129 begin
1130 if (y >= stickEY) then state := TPartState.Normal;
1131 end;
1132 //if not g_Map_CollidePanel(X-1, Y-1, 3, 3, (PANEL_STEP or PANEL_WALL or PANEL_OPENDOOR or PANEL_CLOSEDOOR))
1133 end;
1134 exit;
1135 end;
1137 pan := g_Map_PanelAtPoint(x, y, (GridTagAcid1 or GridTagAcid2 or GridTagWater or GridTagLift));
1138 if (pan <> nil) then
1139 begin
1140 if ((pan.tag and (GridTagAcid1 or GridTagAcid2 or GridTagWater)) <> 0) then begin die(); exit; end;
1141 if ((pan.PanelType and PANEL_LIFTUP) <> 0) then
1142 begin
1143 if (velY > -4-Random(3)) then velY -= 0.8;
1144 if (Abs(velX) > 0.1) then velX -= velX/10.0;
1145 velX += (Random-Random)*0.2;
1146 accelY := 0.15;
1147 end;
1148 if ((pan.PanelType and PANEL_LIFTLEFT) <> 0) then
1149 begin
1150 if (velX > -8-Random(3)) then velX -= 0.8;
1151 accelY := 0.15;
1152 end;
1153 if ((pan.PanelType and PANEL_LIFTRIGHT) <> 0) then
1154 begin
1155 if (velX < 8+Random(3)) then velX += 0.8;
1156 accelY := 0.15;
1157 end;
1158 end;
1160 dX := Round(velX);
1161 dY := Round(velY);
1163 if (state <> TPartState.Stuck) and (Abs(velX) < 0.1) and (Abs(velY) < 0.1) then
1164 begin
1165 // Âèñèò â âîçäóõå - êàïàåò
1166 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
1167 begin
1168 velY := 0.8;
1169 accelY := 0.5;
1170 state := TPartState.Normal;
1171 end;
1172 end;
1174 // horizontal
1175 if (dX <> 0) then
1176 begin
1177 pan := g_Map_traceToNearest(x, y, x+dX, y, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey);
1178 if (x <> ex) then onGround := false;
1179 x := ex;
1180 // free to ride?
1181 if (pan <> nil) then
1182 begin
1183 // nope
1184 if (dY > 0) and ((pan.tag and (GridTagAcid1 or GridTagAcid2 or GridTagWater)) <> 0) then begin die(); exit; end;
1185 // Ñòåíà/äâåðü?
1186 if ((pan.tag and (GridTagWall or GridTagDoor or GridTagStep)) <> 0) then
1187 begin
1188 velX := 0;
1189 velY := 0;
1190 accelX := 0;
1191 accelY := 0;
1192 state := TPartState.Stuck;
1193 justSticked := true;
1194 if (dX > 0) then stickDX := 1 else stickDX := -1;
1195 end;
1196 end;
1197 if (x < 0) or (x >= gMapInfo.Width) then begin die(); exit; end;
1198 end;
1199 // vertical
1200 if (dY <> 0) then
1201 begin
1202 if (dY < 0) or not onGround then
1203 begin
1204 pan := g_Map_traceToNearest(x, y, x, y+dY, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey);
1205 y := ey;
1206 // free to ride?
1207 if (pan <> nil) then
1208 begin
1209 // nope
1210 if (dY > 0) and ((pan.tag and (GridTagAcid1 or GridTagAcid2 or GridTagWater)) <> 0) then begin die(); exit; end;
1211 // Ñòåíà/äâåðü?
1212 if ((pan.tag and (GridTagWall or GridTagDoor or GridTagStep)) <> 0) then
1213 begin
1214 velX := 0;
1215 velY := 0;
1216 accelX := 0;
1217 accelY := 0;
1218 if (dY > 0) and (state <> TPartState.Stuck) then
1219 begin
1220 state := TPartState.Normal;
1221 end
1222 else
1223 begin
1224 state := TPartState.Stuck;
1225 if (g_Map_PanelAtPoint(x-1, y, (GridTagWall or GridTagDoor or GridTagStep)) <> nil) then stickDX := -1
1226 else if (g_Map_PanelAtPoint(x+1, y, (GridTagWall or GridTagDoor or GridTagStep)) <> nil) then stickDX := 1
1227 else stickDX := 0;
1228 justSticked := true;
1229 end;
1230 end;
1231 end;
1232 onGround := (velY >= 0) and g_Map_HasAnyPanelAtPoint(x, y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
1233 end;
1234 if (y < 0) or (y >= gMapInfo.Height) then begin die(); exit; end;
1235 end;
1237 velX += accelX;
1238 velY += accelY;
1240 time += 1;
1241 end;
1242 *)
1245 // ////////////////////////////////////////////////////////////////////////// //
1247 var
1249 {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
1252 {$ELSE}
1255 {$ENDIF}
1256 begin
1260 {$IF DEFINED(D2F_NEW_SPARK_THINKER)}
1262 begin
1263 pan := g_Map_traceToNearest(x, y-1, x, y+1, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey);
1265 {$ELSE}
1274 {$ENDIF}
1277 begin
1278 {$IF DEFINED(D2F_NEW_SPARK_THINKER)}
1279 pan := g_Map_traceToNearest(x, y, x+dX, y, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey);
1280 //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);
1283 // free to ride?
1285 begin
1286 // nope
1287 if ((pan.tag and (GridTagAcid1 or GridTagAcid2 or GridTagWater)) <> 0) then begin die(); exit; end;
1292 {$ELSE}
1296 begin
1298 //c := gCollideMap[Y, X+s];
1303 Break;
1304 end
1309 begin
1311 break;
1314 {$ENDIF}
1318 begin
1319 {$IF DEFINED(D2F_NEW_SPARK_THINKER)}
1321 begin
1322 pan := g_Map_traceToNearest(x, y, x, y+dY, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey);
1324 {
1325 if awaken then
1326 begin
1327 awaken := false;
1328 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]);
1329 end;
1330 }
1331 // free to ride?
1333 begin
1334 // nope
1335 if ((pan.tag and (GridTagAcid1 or GridTagAcid2 or GridTagWater)) <> 0) then begin die(); exit; end;
1337 begin
1340 end
1341 else
1342 begin
1349 onGround := (velY >= 0) and g_Map_HasAnyPanelAtPoint(x, y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
1352 {$ELSE}
1356 begin
1358 //c := gCollideMap[Y+s, X];
1362 begin
1365 end
1367 begin
1374 Break;
1375 end
1380 begin
1382 break;
1385 {$ENDIF}
1391 begin
1400 // ////////////////////////////////////////////////////////////////////////// //
1402 var
1407 begin
1408 exit;
1420 begin
1422 begin
1432 else
1448 onGround := (velY >= 0) and g_Map_HasAnyPanelAtPoint(x, y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
1454 else
1461 var
1468 begin
1469 exit;
1473 Exit;
1492 begin
1494 begin
1513 onGround := (velY >= 0) and g_Map_HasAnyPanelAtPoint(x, y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
1519 else
1525 // ////////////////////////////////////////////////////////////////////////// //
1527 var
1529 begin
1536 //if CurrentParticle >= Count then
1542 begin
1548 var
1550 begin
1554 begin
1556 Exit;
1560 begin
1563 end
1564 else
1565 begin
1573 var
1575 begin
1578 Exit;
1591 // ////////////////////////////////////////////////////////////////////////// //
1592 // st: set mark
1593 // t: mark type
1594 // currently unused
1596 var
1599 begin
1601 // make some border, so we'll hit particles lying around the panel
1608 begin
1611 begin
1620 // ////////////////////////////////////////////////////////////////////////// //
1621 {$IF DEFINED(HAS_COLLIDE_BITMAP)}
1623 var
1625 begin
1626 //g_Game_SetLoadingText(_lc[I_LOAD_COLLIDE_MAP]+' 1/6', 0, False);
1627 //SetLength(gCollideMap, gMapInfo.Height+1);
1628 //for a := 0 to High(gCollideMap) do SetLength(gCollideMap[a], gMapInfo.Width+1);
1630 {$ENDIF}
1634 begin
1635 //CreateCollideMap();
1637 {$IFDEF HEADLESS}
1639 {$ENDIF}
1644 var
1646 begin
1653 begin
1659 // why not?
1665 // ////////////////////////////////////////////////////////////////////////// //
1667 var
1671 begin
1675 begin
1682 begin
1684 begin
1686 begin
1689 //if not alive then Continue;
1690 //e_WriteLog(Format('particle #%d: %d', [State, ParticleType]), MSG_NOTIFY);
1697 // clear awake map
1701 begin
1704 begin
1706 ONCEANIM_SMOKE:
1707 begin
1716 begin
1719 end
1720 else
1728 var
1730 begin
1732 begin
1746 begin