(* Copyright (C) DooM 2D:Forever Developers * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . *) {$INCLUDE ../shared/a_modes.inc} unit g_phys; interface uses e_graphics; type PObj = ^TObj; TObj = record X, Y: Integer; Rect: TRectWH; Vel: TPoint2i; Accel: TPoint2i; end; const MAX_YV = 30; LIMIT_VEL = 16384; LIMIT_ACCEL = 1024; MOVE_NONE = 0; MOVE_HITWALL = 1; MOVE_HITCEIL = 2; MOVE_HITLAND = 4; MOVE_FALLOUT = 8; MOVE_INWATER = 16; MOVE_HITWATER = 32; MOVE_HITAIR = 64; MOVE_BLOCK = 128; procedure g_Obj_Init(Obj: PObj); function g_Obj_Move(Obj: PObj; Fallable: Boolean; Splash: Boolean; ClimbSlopes: Boolean = False): Word; function g_Obj_Collide(Obj1, Obj2: PObj): Boolean; overload; function g_Obj_Collide(X, Y: Integer; Width, Height: Word; Obj: PObj): Boolean; overload; function g_Obj_CollidePoint(X, Y: Integer; Obj: PObj): Boolean; function g_Obj_CollideLevel(Obj: PObj; XInc, YInc: Integer): Boolean; function g_Obj_CollideStep(Obj: PObj; XInc, YInc: Integer): Boolean; function g_Obj_CollideWater(Obj: PObj; XInc, YInc: Integer): Boolean; function g_Obj_CollideLiquid(Obj: PObj; XInc, YInc: Integer): Boolean; function g_Obj_CollidePanel(Obj: PObj; XInc, YInc: Integer; PanelType: Word): Boolean; function g_Obj_StayOnStep(Obj: PObj): Boolean; procedure g_Obj_Push(Obj: PObj; VelX, VelY: Integer); procedure g_Obj_PushA(Obj: PObj; Vel: Integer; Angle: SmallInt); procedure g_Obj_SetSpeed(Obj: PObj; s: Integer); function z_dec(a, b: Integer): Integer; function z_fdec(a, b: Double): Double; var gMon: Boolean = False; implementation uses g_map, g_basic, Math, g_player, g_console, SysUtils, g_sound, g_gfx, MAPDEF, g_monsters, g_game, BinEditor; function g_Obj_StayOnStep(Obj: PObj): Boolean; begin Result := not g_Map_CollidePanel(Obj^.X+Obj^.Rect.X, Obj^.Y+Obj^.Rect.Y+Obj^.Rect.Height-1, Obj^.Rect.Width, 1, PANEL_STEP, False) and g_Map_CollidePanel(Obj^.X+Obj^.Rect.X, Obj^.Y+Obj^.Rect.Y+Obj^.Rect.Height, Obj^.Rect.Width, 1, PANEL_STEP, False); end; function CollideLiquid(Obj: PObj; XInc, YInc: Integer): Boolean; begin Result := g_Map_CollidePanel(Obj^.X+Obj^.Rect.X+XInc, Obj^.Y+Obj^.Rect.Y+YInc, Obj^.Rect.Width, Obj^.Rect.Height*2 div 3, PANEL_WATER or PANEL_ACID1 or PANEL_ACID2, False); end; function CollideLift(Obj: PObj; XInc, YInc: Integer): Integer; begin if g_Map_CollidePanel(Obj^.X+Obj^.Rect.X+XInc, Obj^.Y+Obj^.Rect.Y+YInc, Obj^.Rect.Width, Obj^.Rect.Height, PANEL_LIFTUP, False) then Result := -1 else if g_Map_CollidePanel(Obj^.X+Obj^.Rect.X+XInc, Obj^.Y+Obj^.Rect.Y+YInc, Obj^.Rect.Width, Obj^.Rect.Height, PANEL_LIFTDOWN, False) then Result := 1 else Result := 0; end; function CollideHorLift(Obj: PObj; XInc, YInc: Integer): Integer; var left, right: Boolean; begin left := g_Map_CollidePanel(Obj^.X+Obj^.Rect.X+XInc, Obj^.Y+Obj^.Rect.Y+YInc, Obj^.Rect.Width, Obj^.Rect.Height, PANEL_LIFTLEFT, False); right := g_Map_CollidePanel(Obj^.X+Obj^.Rect.X+XInc, Obj^.Y+Obj^.Rect.Y+YInc, Obj^.Rect.Width, Obj^.Rect.Height, PANEL_LIFTRIGHT, False); if left and not right then Result := -1 else if right and not left then Result := 1 else Result := 0; end; function CollidePlayers(_Obj: PObj; XInc, YInc: Integer): Boolean; var a: Integer; begin Result := False; if gPlayers = nil then Exit; for a := 0 to High(gPlayers) do if gPlayers[a] <> nil then with gPlayers[a] do if Live and g_Collide(GameX+PLAYER_RECT.X, GameY+PLAYER_RECT.Y, PLAYER_RECT.Width, PLAYER_RECT.Height, _Obj^.X+_Obj^.Rect.X+XInc, _Obj^.Y+_Obj^.Rect.Y+YInc, _Obj^.Rect.Width, _Obj^.Rect.Height) then begin Result := True; Exit; end; end; function Blocked(Obj: PObj; XInc, YInc: Integer): Boolean; begin Result := g_Map_CollidePanel(Obj^.X+Obj^.Rect.X+XInc, Obj^.Y+Obj.Rect.Y+YInc, Obj^.Rect.Width, Obj^.Rect.Height, PANEL_BLOCKMON, False); end; function g_Obj_CollideLevel(Obj: PObj; XInc, YInc: Integer): Boolean; begin Result := g_Map_CollidePanel(Obj^.X+Obj^.Rect.X+XInc, Obj^.Y+Obj.Rect.Y+YInc, Obj^.Rect.Width, Obj^.Rect.Height, PANEL_WALL, False); end; function g_Obj_CollideStep(Obj: PObj; XInc, YInc: Integer): Boolean; begin Result := g_Map_CollidePanel(Obj^.X+Obj^.Rect.X+XInc, Obj^.Y+Obj.Rect.Y+YInc, Obj^.Rect.Width, Obj^.Rect.Height, PANEL_STEP, False); end; function g_Obj_CollideWater(Obj: PObj; XInc, YInc: Integer): Boolean; begin Result := g_Map_CollidePanel(Obj^.X+Obj^.Rect.X+XInc, Obj^.Y+Obj.Rect.Y+YInc, Obj^.Rect.Width, Obj^.Rect.Height, PANEL_WATER, False); end; function g_Obj_CollideLiquid(Obj: PObj; XInc, YInc: Integer): Boolean; begin Result := g_Map_CollidePanel(Obj^.X+Obj^.Rect.X+XInc, Obj^.Y+Obj.Rect.Y+YInc, Obj^.Rect.Width, Obj^.Rect.Height, PANEL_WATER or PANEL_ACID1 or PANEL_ACID2, False); end; function g_Obj_CollidePanel(Obj: PObj; XInc, YInc: Integer; PanelType: Word): Boolean; begin Result := g_Map_CollidePanel(Obj^.X+Obj^.Rect.X+XInc, Obj^.Y+Obj.Rect.Y+YInc, Obj^.Rect.Width, Obj^.Rect.Height, PanelType, False); end; procedure g_Obj_Splash(Obj: PObj; Color: Byte); var MaxVel: Integer; begin MaxVel := Max(Abs(Obj^.Vel.X), Abs(Obj^.Vel.Y)); if MaxVel > 4 then begin if MaxVel < 10 then g_Sound_PlayExAt('SOUND_GAME_BULK1', Obj^.X, Obj^.Y) else g_Sound_PlayExAt('SOUND_GAME_BULK2', Obj^.X, Obj^.Y); end; g_GFX_Water(Obj^.X+Obj^.Rect.X+(Obj^.Rect.Width div 2), Obj^.Y+Obj^.Rect.Y+(Obj^.Rect.Height div 2), Min(5*(Abs(Obj^.Vel.X)+Abs(Obj^.Vel.Y)), 50), -Obj^.Vel.X, -Obj^.Vel.Y, Obj^.Rect.Width, 16, Color); end; function move(Obj: PObj; dx, dy: Integer; ClimbSlopes: Boolean): Word; var i: Integer; sx, sy: ShortInt; st: Word; procedure slope(s: Integer); var i: Integer; begin i := 0; while g_Obj_CollideLevel(Obj, sx, 0) and (i < 4) do begin Obj^.Y := Obj^.Y + s; Inc(i); end; Obj^.X := Obj^.X + sx; end; function movex(): Boolean; begin Result := False; // Если монстру шагнуть в сторону, а там блокмон: if gMon and not WordBool(st and MOVE_BLOCK) then if Blocked(Obj, sx, 0) then st := st or MOVE_BLOCK; // Если шагнуть в сторону, а там стена => шагать нельзя: if g_Obj_CollideLevel(Obj, sx, 0) then begin if ClimbSlopes and (Abs(dy) < 2) then begin Result := True; if (not g_Obj_CollideLevel(Obj, sx, -12)) // забираемся на 12 пикселей влево/вправо and g_Obj_CollidePanel(Obj, 0, 1, PANEL_WALL or PANEL_STEP) // только если есть земля под ногами then slope(-1) else begin Result := False; st := st or MOVE_HITWALL; end; end else st := st or MOVE_HITWALL; end else // Там стены нет begin if CollideLiquid(Obj, sx, 0) then begin // Если шагнуть в сторону, а там теперь жидкость if not WordBool(st and MOVE_INWATER) then st := st or MOVE_HITWATER; end else // Если шагнуть в сторону, а там уже нет жидкости if WordBool(st and MOVE_INWATER) then st := st or MOVE_HITAIR; // Шаг: Obj^.X := Obj^.X + sx; Result := True; end; end; function movey(): Boolean; begin Result := False; // Если монстру шагнуть по вертикали, а там блокмон: if gMon and not WordBool(st and MOVE_BLOCK) then if Blocked(Obj, 0, sy) then st := st or MOVE_BLOCK; // Если шагнуть в по вертикали, а там стена => шагать нельзя: // Или если шагнуть вниз, а там ступень => шагать нельзя: if g_Obj_CollideLevel(Obj, 0, sy) or ((sy > 0) and g_Obj_StayOnStep(Obj)) then begin if sy > 0 then st := st or MOVE_HITLAND else st := st or MOVE_HITCEIL; end else // Там стены нет. И ступени снизу тоже нет begin if CollideLiquid(Obj, 0, sy) then begin // Если шагнуть в по вертикали, а там теперь жидкость if not WordBool(st and MOVE_INWATER) then st := st or MOVE_HITWATER; end else // Если шагнуть в по вертикали, а там уже нет жидкости if WordBool(st and MOVE_INWATER) then st := st or MOVE_HITAIR; // Шаг: Obj^.Y := Obj^.Y + sy; Result := True; end; end; begin st := MOVE_NONE; // Объект в жидкости: if CollideLiquid(Obj, 0, 0) then st := st or MOVE_INWATER; // Монстр в блокмоне: if gMon then if Blocked(Obj, 0, 0) then st := st or MOVE_BLOCK; // Двигаться не надо: if (dx = 0) and (dy = 0) then begin Result := st; Exit; end; sx := g_basic.Sign(dx); sy := g_basic.Sign(dy); dx := Abs(dx); dy := Abs(dy); for i := 1 to dx do if not movex() then Break; for i := 1 to dy do if not movey() then Break; Result := st; end; procedure g_Obj_Init(Obj: PObj); begin ZeroMemory(Obj, SizeOf(TObj)); end; function g_Obj_Move(Obj: PObj; Fallable: Boolean; Splash: Boolean; ClimbSlopes: Boolean = False): Word; var xv, yv, dx, dy: Integer; inwater: Boolean; c: Boolean; wtx: DWORD; label _move; begin // Лимиты на скорость и ускорение if Obj^.Vel.X < -LIMIT_VEL then Obj^.Vel.X := -LIMIT_VEL else if Obj^.Vel.X > LIMIT_VEL then Obj^.Vel.X := LIMIT_VEL; if Obj^.Vel.Y < -LIMIT_VEL then Obj^.Vel.Y := -LIMIT_VEL else if Obj^.Vel.Y > LIMIT_VEL then Obj^.Vel.Y := LIMIT_VEL; if Obj^.Accel.X < -LIMIT_ACCEL then Obj^.Accel.X := -LIMIT_ACCEL else if Obj^.Accel.X > LIMIT_ACCEL then Obj^.Accel.X := LIMIT_ACCEL; if Obj^.Accel.Y < -LIMIT_ACCEL then Obj^.Accel.Y := -LIMIT_ACCEL else if Obj^.Accel.Y > LIMIT_ACCEL then Obj^.Accel.Y := LIMIT_ACCEL; // Вылетел за нижнюю границу карты: if Obj^.Y > gMapInfo.Height+128 then begin Result := MOVE_FALLOUT; Exit; end; // Меняем скорость и ускорение только по четным кадрам: c := gTime mod (GAME_TICK*2) <> 0; if c then goto _move; case CollideLift(Obj, 0, 0) of -1: //up begin Obj^.Vel.Y := Obj^.Vel.Y - 1; // Лифт вверх if Obj^.Vel.Y < -5 then Obj^.Vel.Y := Obj^.Vel.Y + 1; end; 1: //down begin if Obj^.Vel.Y > 5 then Obj^.Vel.Y := Obj^.Vel.Y - 1; Obj^.Vel.Y := Obj^.Vel.Y + 1; // Гравитация или лифт вниз end; 0: begin if Fallable then Obj^.Vel.Y := Obj^.Vel.Y + 1; // Гравитация if Obj^.Vel.Y > MAX_YV then Obj^.Vel.Y := Obj^.Vel.Y - 1; end; end; case CollideHorLift(Obj, 0, 0) of -1: //left begin Obj^.Vel.X := Obj^.Vel.X - 3; // Лифт вверх if Obj^.Vel.X < -9 then Obj^.Vel.X := Obj^.Vel.X + 3; end; 1: //right begin Obj^.Vel.X := Obj^.Vel.X + 3; if Obj^.Vel.X > 9 then Obj^.Vel.X := Obj^.Vel.X - 3; end; // 0 is not needed here end; inwater := CollideLiquid(Obj, 0, 0); if inwater then begin xv := Abs(Obj^.Vel.X)+1; if xv > 5 then Obj^.Vel.X := z_dec(Obj^.Vel.X, (xv div 2)-2); yv := Abs(Obj^.Vel.Y)+1; if yv > 5 then Obj^.Vel.Y := z_dec(Obj^.Vel.Y, (yv div 2)-2); xv := Abs(Obj^.Accel.X)+1; if xv > 5 then Obj^.Accel.X := z_dec(Obj^.Accel.X, (xv div 2)-2); yv := Abs(Obj^.Accel.Y)+1; if yv > 5 then Obj^.Accel.Y := z_dec(Obj^.Accel.Y, (yv div 2)-2); end; // Уменьшаем прибавку к скорости: Obj^.Accel.X := z_dec(Obj^.Accel.X, 1); Obj^.Accel.Y := z_dec(Obj^.Accel.Y, 1); _move: xv := Obj^.Vel.X + Obj^.Accel.X; yv := Obj^.Vel.Y + Obj^.Accel.Y; dx := xv; dy := yv; Result := move(Obj, dx, dy, ClimbSlopes); // Брызги (если нужны): if Splash then if WordBool(Result and MOVE_HITWATER) then begin wtx := g_Map_CollideLiquid_Texture(Obj^.X+Obj^.Rect.X, Obj^.Y+Obj^.Rect.Y, Obj^.Rect.Width, Obj^.Rect.Height*2 div 3); case wtx of LongWord(TEXTURE_SPECIAL_WATER): g_Obj_Splash(Obj, 3); LongWord(TEXTURE_SPECIAL_ACID1): g_Obj_Splash(Obj, 2); LongWord(TEXTURE_SPECIAL_ACID2): g_Obj_Splash(Obj, 1); LongWord(TEXTURE_NONE): ; else g_Obj_Splash(Obj, 0); end; end; // Меняем скорость и ускорение только по четным кадрам: if c then Exit; // Врезались в стену - стоп: if WordBool(Result and MOVE_HITWALL) then begin Obj^.Vel.X := 0; Obj^.Accel.X := 0; end; // Врезались в пол или потолок - стоп: if WordBool(Result and (MOVE_HITCEIL or MOVE_HITLAND)) then begin Obj^.Vel.Y := 0; Obj^.Accel.Y := 0; end; end; function g_Obj_Collide(Obj1, Obj2: PObj): Boolean; begin Result := g_Collide(Obj1^.X+Obj1^.Rect.X, Obj1^.Y+Obj1^.Rect.Y, Obj1^.Rect.Width, Obj1^.Rect.Height, Obj2^.X+Obj2^.Rect.X, Obj2^.Y+Obj2^.Rect.Y, Obj2^.Rect.Width, Obj2^.Rect.Height); end; function g_Obj_Collide(X, Y: Integer; Width, Height: Word; Obj: PObj): Boolean; begin Result := g_Collide(X, Y, Width, Height, Obj^.X+Obj^.Rect.X, Obj^.Y+Obj^.Rect.Y, Obj^.Rect.Width, Obj^.Rect.Height); end; function g_Obj_CollidePoint(X, Y: Integer; Obj: PObj): Boolean; begin Result := g_CollidePoint(X, Y, Obj^.X+Obj^.Rect.X, Obj^.Y+Obj^.Rect.Y, Obj^.Rect.Width, Obj^.Rect.Height); end; procedure g_Obj_Push(Obj: PObj; VelX, VelY: Integer); begin Obj^.Vel.X := Obj^.Vel.X + VelX; Obj^.Vel.Y := Obj^.Vel.Y + VelY; end; procedure g_Obj_PushA(Obj: PObj; Vel: Integer; Angle: SmallInt); var s, c: Extended; begin SinCos(DegToRad(-Angle), s, c); Obj^.Vel.X := Obj^.Vel.X + Round(Vel*c); Obj^.Vel.Y := Obj^.Vel.Y + Round(Vel*s); end; procedure g_Obj_SetSpeed(Obj: PObj; s: Integer); var m, vx, vy: Integer; begin vx := Obj^.Vel.X; vy := Obj^.Vel.Y; m := Max(Abs(vx), Abs(vy)); if m = 0 then m := 1; Obj^.Vel.X := (vx*s) div m; Obj^.Vel.Y := (vy*s) div m; end; function z_dec(a, b: Integer): Integer; begin // Приближаем a к 0 на b единиц: if Abs(a) < b then Result := 0 else if a > 0 then Result := a - b else if a < 0 then Result := a + b else // a = 0 Result := 0; end; function z_fdec(a, b: Double): Double; begin // Приближаем a к 0.0 на b единиц: if Abs(a) < b then Result := 0.0 else if a > 0.0 then Result := a - b else if a < 0.0 then Result := a + b else // a = 0.0 Result := 0.0; end; end.