DEADSOFTWARE

498bd7587489f9faf6e72ffca873c72e06de6702
[d2df-sdl.git] / src / game / g_phys.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 unit g_phys;
19 interface
21 uses
22 e_graphics;
24 type
25 PObj = ^TObj;
26 TObj = record
27 X, Y: Integer;
28 Rect: TRectWH;
29 Vel: TPoint2i;
30 Accel: TPoint2i;
31 end;
33 const
34 MAX_YV = 30;
35 LIMIT_VEL = 16384;
36 LIMIT_ACCEL = 1024;
38 MOVE_NONE = 0;
39 MOVE_HITWALL = 1;
40 MOVE_HITCEIL = 2;
41 MOVE_HITLAND = 4;
42 MOVE_FALLOUT = 8;
43 MOVE_INWATER = 16;
44 MOVE_HITWATER = 32;
45 MOVE_HITAIR = 64;
46 MOVE_BLOCK = 128;
48 procedure g_Obj_Init(Obj: PObj);
49 function g_Obj_Move(Obj: PObj; Fallable: Boolean; Splash: Boolean; ClimbSlopes: Boolean = False): Word;
50 function g_Obj_Collide(Obj1, Obj2: PObj): Boolean; overload;
51 function g_Obj_Collide(X, Y: Integer; Width, Height: Word; Obj: PObj): Boolean; overload;
52 function g_Obj_CollidePoint(X, Y: Integer; Obj: PObj): Boolean;
53 function g_Obj_CollideLevel(Obj: PObj; XInc, YInc: Integer): Boolean;
54 function g_Obj_CollideStep(Obj: PObj; XInc, YInc: Integer): Boolean;
55 function g_Obj_CollideWater(Obj: PObj; XInc, YInc: Integer): Boolean;
56 function g_Obj_CollideLiquid(Obj: PObj; XInc, YInc: Integer): Boolean;
57 function g_Obj_CollidePanel(Obj: PObj; XInc, YInc: Integer; PanelType: Word): Boolean;
58 function g_Obj_StayOnStep(Obj: PObj): Boolean;
59 procedure g_Obj_Push(Obj: PObj; VelX, VelY: Integer);
60 procedure g_Obj_PushA(Obj: PObj; Vel: Integer; Angle: SmallInt);
61 procedure g_Obj_SetSpeed(Obj: PObj; s: Integer);
62 function z_dec(a, b: Integer): Integer;
63 function z_fdec(a, b: Double): Double;
65 var
66 gMon: Boolean = False;
68 implementation
70 uses
71 g_map, g_basic, Math, g_player, g_console, SysUtils,
72 g_sound, g_gfx, MAPDEF, g_monsters, g_game, BinEditor;
74 function g_Obj_StayOnStep(Obj: PObj): Boolean;
75 begin
76 Result := not g_Map_CollidePanel(Obj^.X+Obj^.Rect.X, Obj^.Y+Obj^.Rect.Y+Obj^.Rect.Height-1,
77 Obj^.Rect.Width, 1,
78 PANEL_STEP, False)
79 and g_Map_CollidePanel(Obj^.X+Obj^.Rect.X, Obj^.Y+Obj^.Rect.Y+Obj^.Rect.Height,
80 Obj^.Rect.Width, 1,
81 PANEL_STEP, False);
82 end;
84 function CollideLiquid(Obj: PObj; XInc, YInc: Integer): Boolean;
85 begin
86 Result := g_Map_CollidePanel(Obj^.X+Obj^.Rect.X+XInc, Obj^.Y+Obj^.Rect.Y+YInc,
87 Obj^.Rect.Width, Obj^.Rect.Height*2 div 3,
88 PANEL_WATER or PANEL_ACID1 or PANEL_ACID2, False);
89 end;
91 function CollideLift(Obj: PObj; XInc, YInc: Integer): Integer;
92 begin
93 if g_Map_CollidePanel(Obj^.X+Obj^.Rect.X+XInc, Obj^.Y+Obj^.Rect.Y+YInc,
94 Obj^.Rect.Width, Obj^.Rect.Height,
95 PANEL_LIFTUP, False) then
96 Result := -1
97 else if g_Map_CollidePanel(Obj^.X+Obj^.Rect.X+XInc, Obj^.Y+Obj^.Rect.Y+YInc,
98 Obj^.Rect.Width, Obj^.Rect.Height,
99 PANEL_LIFTDOWN, False) then
100 Result := 1
101 else
102 Result := 0;
103 end;
105 function CollideHorLift(Obj: PObj; XInc, YInc: Integer): Integer;
106 var
107 left, right: Boolean;
108 begin
109 left := g_Map_CollidePanel(Obj^.X+Obj^.Rect.X+XInc, Obj^.Y+Obj^.Rect.Y+YInc,
110 Obj^.Rect.Width, Obj^.Rect.Height,
111 PANEL_LIFTLEFT, False);
112 right := g_Map_CollidePanel(Obj^.X+Obj^.Rect.X+XInc, Obj^.Y+Obj^.Rect.Y+YInc,
113 Obj^.Rect.Width, Obj^.Rect.Height,
114 PANEL_LIFTRIGHT, False);
115 if left and not right then
116 Result := -1
117 else if right and not left then
118 Result := 1
119 else
120 Result := 0;
121 end;
123 function CollidePlayers(_Obj: PObj; XInc, YInc: Integer): Boolean;
124 var
125 a: Integer;
126 begin
127 Result := False;
129 if gPlayers = nil then
130 Exit;
132 for a := 0 to High(gPlayers) do
133 if gPlayers[a] <> nil then
134 with gPlayers[a] do
135 if Live and
136 g_Collide(GameX+PLAYER_RECT.X, GameY+PLAYER_RECT.Y,
137 PLAYER_RECT.Width, PLAYER_RECT.Height,
138 _Obj^.X+_Obj^.Rect.X+XInc, _Obj^.Y+_Obj^.Rect.Y+YInc,
139 _Obj^.Rect.Width, _Obj^.Rect.Height) then
140 begin
141 Result := True;
142 Exit;
143 end;
144 end;
146 function Blocked(Obj: PObj; XInc, YInc: Integer): Boolean;
147 begin
148 Result := g_Map_CollidePanel(Obj^.X+Obj^.Rect.X+XInc, Obj^.Y+Obj.Rect.Y+YInc,
149 Obj^.Rect.Width, Obj^.Rect.Height,
150 PANEL_BLOCKMON, False);
151 end;
153 function g_Obj_CollideLevel(Obj: PObj; XInc, YInc: Integer): Boolean;
154 begin
155 Result := g_Map_CollidePanel(Obj^.X+Obj^.Rect.X+XInc, Obj^.Y+Obj.Rect.Y+YInc,
156 Obj^.Rect.Width, Obj^.Rect.Height,
157 PANEL_WALL, False);
158 end;
160 function g_Obj_CollideStep(Obj: PObj; XInc, YInc: Integer): Boolean;
161 begin
162 Result := g_Map_CollidePanel(Obj^.X+Obj^.Rect.X+XInc, Obj^.Y+Obj.Rect.Y+YInc,
163 Obj^.Rect.Width, Obj^.Rect.Height,
164 PANEL_STEP, False);
165 end;
167 function g_Obj_CollideWater(Obj: PObj; XInc, YInc: Integer): Boolean;
168 begin
169 Result := g_Map_CollidePanel(Obj^.X+Obj^.Rect.X+XInc, Obj^.Y+Obj.Rect.Y+YInc,
170 Obj^.Rect.Width, Obj^.Rect.Height,
171 PANEL_WATER, False);
172 end;
174 function g_Obj_CollideLiquid(Obj: PObj; XInc, YInc: Integer): Boolean;
175 begin
176 Result := g_Map_CollidePanel(Obj^.X+Obj^.Rect.X+XInc, Obj^.Y+Obj.Rect.Y+YInc,
177 Obj^.Rect.Width, Obj^.Rect.Height,
178 PANEL_WATER or PANEL_ACID1 or PANEL_ACID2, False);
179 end;
181 function g_Obj_CollidePanel(Obj: PObj; XInc, YInc: Integer; PanelType: Word): Boolean;
182 begin
183 Result := g_Map_CollidePanel(Obj^.X+Obj^.Rect.X+XInc, Obj^.Y+Obj.Rect.Y+YInc,
184 Obj^.Rect.Width, Obj^.Rect.Height,
185 PanelType, False);
186 end;
188 procedure g_Obj_Splash(Obj: PObj; Color: Byte);
189 var
190 MaxVel: Integer;
191 begin
192 MaxVel := Max(Abs(Obj^.Vel.X), Abs(Obj^.Vel.Y));
193 if MaxVel > 4 then begin
194 if MaxVel < 10 then
195 g_Sound_PlayExAt('SOUND_GAME_BULK1', Obj^.X, Obj^.Y)
196 else
197 g_Sound_PlayExAt('SOUND_GAME_BULK2', Obj^.X, Obj^.Y);
198 end;
200 g_GFX_Water(Obj^.X+Obj^.Rect.X+(Obj^.Rect.Width div 2),
201 Obj^.Y+Obj^.Rect.Y+(Obj^.Rect.Height div 2),
202 Min(5*(Abs(Obj^.Vel.X)+Abs(Obj^.Vel.Y)), 50),
203 -Obj^.Vel.X, -Obj^.Vel.Y,
204 Obj^.Rect.Width, 16, Color);
205 end;
207 function move(Obj: PObj; dx, dy: Integer; ClimbSlopes: Boolean): Word;
208 var
209 i: Integer;
210 sx, sy: ShortInt;
211 st: Word;
213 procedure slope(s: Integer);
214 var
215 i: Integer;
216 begin
217 i := 0;
218 while g_Obj_CollideLevel(Obj, sx, 0) and (i < 4) do
219 begin
220 Obj^.Y := Obj^.Y + s;
221 Inc(i);
222 end;
223 Obj^.X := Obj^.X + sx;
224 end;
226 function movex(): Boolean;
227 begin
228 Result := False;
230 // Åñëè ìîíñòðó øàãíóòü â ñòîðîíó, à òàì áëîêìîí:
231 if gMon and not WordBool(st and MOVE_BLOCK) then
232 if Blocked(Obj, sx, 0) then
233 st := st or MOVE_BLOCK;
235 // Åñëè øàãíóòü â ñòîðîíó, à òàì ñòåíà => øàãàòü íåëüçÿ:
236 if g_Obj_CollideLevel(Obj, sx, 0) then
237 begin
238 if ClimbSlopes and (Abs(dy) < 2) then
239 begin
240 Result := True;
241 if (not g_Obj_CollideLevel(Obj, sx, -12)) // çàáèðàåìñÿ íà 12 ïèêñåëåé âëåâî/âïðàâî
242 and g_Obj_CollidePanel(Obj, 0, 1, PANEL_WALL or PANEL_STEP) // òîëüêî åñëè åñòü çåìëÿ ïîä íîãàìè
243 then
244 slope(-1)
245 else
246 begin
247 Result := False;
248 st := st or MOVE_HITWALL;
249 end;
250 end
251 else
252 st := st or MOVE_HITWALL;
253 end
254 else // Òàì ñòåíû íåò
255 begin
256 if CollideLiquid(Obj, sx, 0) then
257 begin // Åñëè øàãíóòü â ñòîðîíó, à òàì òåïåðü æèäêîñòü
258 if not WordBool(st and MOVE_INWATER) then
259 st := st or MOVE_HITWATER;
260 end
261 else // Åñëè øàãíóòü â ñòîðîíó, à òàì óæå íåò æèäêîñòè
262 if WordBool(st and MOVE_INWATER) then
263 st := st or MOVE_HITAIR;
265 // Øàã:
266 Obj^.X := Obj^.X + sx;
267 Result := True;
268 end;
269 end;
271 function movey(): Boolean;
272 begin
273 Result := False;
275 // Åñëè ìîíñòðó øàãíóòü ïî âåðòèêàëè, à òàì áëîêìîí:
276 if gMon and not WordBool(st and MOVE_BLOCK) then
277 if Blocked(Obj, 0, sy) then
278 st := st or MOVE_BLOCK;
280 // Åñëè øàãíóòü â ïî âåðòèêàëè, à òàì ñòåíà => øàãàòü íåëüçÿ:
281 // Èëè åñëè øàãíóòü âíèç, à òàì ñòóïåíü => øàãàòü íåëüçÿ:
282 if g_Obj_CollideLevel(Obj, 0, sy) or
283 ((sy > 0) and g_Obj_StayOnStep(Obj)) then
284 begin
285 if sy > 0 then
286 st := st or MOVE_HITLAND
287 else
288 st := st or MOVE_HITCEIL;
289 end
290 else // Òàì ñòåíû íåò. È ñòóïåíè ñíèçó òîæå íåò
291 begin
292 if CollideLiquid(Obj, 0, sy) then
293 begin // Åñëè øàãíóòü â ïî âåðòèêàëè, à òàì òåïåðü æèäêîñòü
294 if not WordBool(st and MOVE_INWATER) then
295 st := st or MOVE_HITWATER;
296 end
297 else // Åñëè øàãíóòü â ïî âåðòèêàëè, à òàì óæå íåò æèäêîñòè
298 if WordBool(st and MOVE_INWATER) then
299 st := st or MOVE_HITAIR;
301 // Øàã:
302 Obj^.Y := Obj^.Y + sy;
304 Result := True;
305 end;
306 end;
308 begin
309 st := MOVE_NONE;
311 // Îáúåêò â æèäêîñòè:
312 if CollideLiquid(Obj, 0, 0) then
313 st := st or MOVE_INWATER;
315 // Ìîíñòð â áëîêìîíå:
316 if gMon then
317 if Blocked(Obj, 0, 0) then
318 st := st or MOVE_BLOCK;
320 // Äâèãàòüñÿ íå íàäî:
321 if (dx = 0) and (dy = 0) then
322 begin
323 Result := st;
324 Exit;
325 end;
327 sx := g_basic.Sign(dx);
328 sy := g_basic.Sign(dy);
329 dx := Abs(dx);
330 dy := Abs(dy);
332 for i := 1 to dx do
333 if not movex() then
334 Break;
336 for i := 1 to dy do
337 if not movey() then
338 Break;
340 Result := st;
341 end;
343 procedure g_Obj_Init(Obj: PObj);
344 begin
345 ZeroMemory(Obj, SizeOf(TObj));
346 end;
348 function g_Obj_Move(Obj: PObj; Fallable: Boolean; Splash: Boolean; ClimbSlopes: Boolean = False): Word;
349 var
350 xv, yv, dx, dy: Integer;
351 inwater: Boolean;
352 c: Boolean;
353 wtx: DWORD;
354 label
355 _move;
356 begin
357 // Ëèìèòû íà ñêîðîñòü è óñêîðåíèå
358 if Obj^.Vel.X < -LIMIT_VEL then Obj^.Vel.X := -LIMIT_VEL
359 else if Obj^.Vel.X > LIMIT_VEL then Obj^.Vel.X := LIMIT_VEL;
360 if Obj^.Vel.Y < -LIMIT_VEL then Obj^.Vel.Y := -LIMIT_VEL
361 else if Obj^.Vel.Y > LIMIT_VEL then Obj^.Vel.Y := LIMIT_VEL;
362 if Obj^.Accel.X < -LIMIT_ACCEL then Obj^.Accel.X := -LIMIT_ACCEL
363 else if Obj^.Accel.X > LIMIT_ACCEL then Obj^.Accel.X := LIMIT_ACCEL;
364 if Obj^.Accel.Y < -LIMIT_ACCEL then Obj^.Accel.Y := -LIMIT_ACCEL
365 else if Obj^.Accel.Y > LIMIT_ACCEL then Obj^.Accel.Y := LIMIT_ACCEL;
367 // Âûëåòåë çà íèæíþþ ãðàíèöó êàðòû:
368 if Obj^.Y > gMapInfo.Height+128 then
369 begin
370 Result := MOVE_FALLOUT;
371 Exit;
372 end;
374 // Ìåíÿåì ñêîðîñòü è óñêîðåíèå òîëüêî ïî ÷åòíûì êàäðàì:
375 c := gTime mod (GAME_TICK*2) <> 0;
377 if c then
378 goto _move;
380 case CollideLift(Obj, 0, 0) of
381 -1: //up
382 begin
383 Obj^.Vel.Y := Obj^.Vel.Y - 1; // Ëèôò ââåðõ
384 if Obj^.Vel.Y < -5 then
385 Obj^.Vel.Y := Obj^.Vel.Y + 1;
386 end;
388 1: //down
389 begin
390 if Obj^.Vel.Y > 5 then
391 Obj^.Vel.Y := Obj^.Vel.Y - 1;
392 Obj^.Vel.Y := Obj^.Vel.Y + 1; // Ãðàâèòàöèÿ èëè ëèôò âíèç
393 end;
395 0:
396 begin
397 if Fallable then
398 Obj^.Vel.Y := Obj^.Vel.Y + 1; // Ãðàâèòàöèÿ
399 if Obj^.Vel.Y > MAX_YV then
400 Obj^.Vel.Y := Obj^.Vel.Y - 1;
401 end;
402 end;
404 case CollideHorLift(Obj, 0, 0) of
405 -1: //left
406 begin
407 Obj^.Vel.X := Obj^.Vel.X - 3; // Ëèôò ââåðõ
408 if Obj^.Vel.X < -9 then
409 Obj^.Vel.X := Obj^.Vel.X + 3;
410 end;
412 1: //right
413 begin
414 Obj^.Vel.X := Obj^.Vel.X + 3;
415 if Obj^.Vel.X > 9 then
416 Obj^.Vel.X := Obj^.Vel.X - 3;
417 end;
418 // 0 is not needed here
419 end;
421 inwater := CollideLiquid(Obj, 0, 0);
422 if inwater then
423 begin
424 xv := Abs(Obj^.Vel.X)+1;
425 if xv > 5 then
426 Obj^.Vel.X := z_dec(Obj^.Vel.X, (xv div 2)-2);
428 yv := Abs(Obj^.Vel.Y)+1;
429 if yv > 5 then
430 Obj^.Vel.Y := z_dec(Obj^.Vel.Y, (yv div 2)-2);
432 xv := Abs(Obj^.Accel.X)+1;
433 if xv > 5 then
434 Obj^.Accel.X := z_dec(Obj^.Accel.X, (xv div 2)-2);
436 yv := Abs(Obj^.Accel.Y)+1;
437 if yv > 5 then
438 Obj^.Accel.Y := z_dec(Obj^.Accel.Y, (yv div 2)-2);
439 end;
441 // Óìåíüøàåì ïðèáàâêó ê ñêîðîñòè:
442 Obj^.Accel.X := z_dec(Obj^.Accel.X, 1);
443 Obj^.Accel.Y := z_dec(Obj^.Accel.Y, 1);
445 _move:
447 xv := Obj^.Vel.X + Obj^.Accel.X;
448 yv := Obj^.Vel.Y + Obj^.Accel.Y;
450 dx := xv;
451 dy := yv;
453 Result := move(Obj, dx, dy, ClimbSlopes);
455 // Áðûçãè (åñëè íóæíû):
456 if Splash then
457 if WordBool(Result and MOVE_HITWATER) then
458 begin
459 wtx := g_Map_CollideLiquid_Texture(Obj^.X+Obj^.Rect.X,
460 Obj^.Y+Obj^.Rect.Y,
461 Obj^.Rect.Width,
462 Obj^.Rect.Height*2 div 3);
463 case wtx of
464 LongWord(TEXTURE_SPECIAL_WATER):
465 g_Obj_Splash(Obj, 3);
466 LongWord(TEXTURE_SPECIAL_ACID1):
467 g_Obj_Splash(Obj, 2);
468 LongWord(TEXTURE_SPECIAL_ACID2):
469 g_Obj_Splash(Obj, 1);
470 LongWord(TEXTURE_NONE):
472 else
473 g_Obj_Splash(Obj, 0);
474 end;
475 end;
477 // Ìåíÿåì ñêîðîñòü è óñêîðåíèå òîëüêî ïî ÷åòíûì êàäðàì:
478 if c then
479 Exit;
481 // Âðåçàëèñü â ñòåíó - ñòîï:
482 if WordBool(Result and MOVE_HITWALL) then
483 begin
484 Obj^.Vel.X := 0;
485 Obj^.Accel.X := 0;
486 end;
488 // Âðåçàëèñü â ïîë èëè ïîòîëîê - ñòîï:
489 if WordBool(Result and (MOVE_HITCEIL or MOVE_HITLAND)) then
490 begin
491 Obj^.Vel.Y := 0;
492 Obj^.Accel.Y := 0;
493 end;
494 end;
496 function g_Obj_Collide(Obj1, Obj2: PObj): Boolean;
497 begin
498 Result := g_Collide(Obj1^.X+Obj1^.Rect.X, Obj1^.Y+Obj1^.Rect.Y,
499 Obj1^.Rect.Width, Obj1^.Rect.Height,
500 Obj2^.X+Obj2^.Rect.X, Obj2^.Y+Obj2^.Rect.Y,
501 Obj2^.Rect.Width, Obj2^.Rect.Height);
502 end;
504 function g_Obj_Collide(X, Y: Integer; Width, Height: Word; Obj: PObj): Boolean;
505 begin
506 Result := g_Collide(X, Y,
507 Width, Height,
508 Obj^.X+Obj^.Rect.X, Obj^.Y+Obj^.Rect.Y,
509 Obj^.Rect.Width, Obj^.Rect.Height);
510 end;
512 function g_Obj_CollidePoint(X, Y: Integer; Obj: PObj): Boolean;
513 begin
514 Result := g_CollidePoint(X, Y, Obj^.X+Obj^.Rect.X, Obj^.Y+Obj^.Rect.Y,
515 Obj^.Rect.Width, Obj^.Rect.Height);
516 end;
518 procedure g_Obj_Push(Obj: PObj; VelX, VelY: Integer);
519 begin
520 Obj^.Vel.X := Obj^.Vel.X + VelX;
521 Obj^.Vel.Y := Obj^.Vel.Y + VelY;
522 end;
524 procedure g_Obj_PushA(Obj: PObj; Vel: Integer; Angle: SmallInt);
525 var
526 s, c: Extended;
528 begin
529 SinCos(DegToRad(-Angle), s, c);
531 Obj^.Vel.X := Obj^.Vel.X + Round(Vel*c);
532 Obj^.Vel.Y := Obj^.Vel.Y + Round(Vel*s);
533 end;
535 procedure g_Obj_SetSpeed(Obj: PObj; s: Integer);
536 var
537 m, vx, vy: Integer;
538 begin
539 vx := Obj^.Vel.X;
540 vy := Obj^.Vel.Y;
542 m := Max(Abs(vx), Abs(vy));
543 if m = 0 then
544 m := 1;
546 Obj^.Vel.X := (vx*s) div m;
547 Obj^.Vel.Y := (vy*s) div m;
548 end;
550 function z_dec(a, b: Integer): Integer;
551 begin
552 // Ïðèáëèæàåì a ê 0 íà b åäèíèö:
553 if Abs(a) < b then
554 Result := 0
555 else
556 if a > 0 then
557 Result := a - b
558 else
559 if a < 0 then
560 Result := a + b
561 else // a = 0
562 Result := 0;
563 end;
565 function z_fdec(a, b: Double): Double;
566 begin
567 // Ïðèáëèæàåì a ê 0.0 íà b åäèíèö:
568 if Abs(a) < b then
569 Result := 0.0
570 else
571 if a > 0.0 then
572 Result := a - b
573 else
574 if a < 0.0 then
575 Result := a + b
576 else // a = 0.0
577 Result := 0.0;
578 end;
580 end.