1#include "all.h"
  2#include "raymath.h"
  3
  4#include <float.h>
  5
  6static void PlayerRotate(void) {
  7	if (!game.cursor_captured) return;
  8
  9	Vector2 mouseDelta = GetMouseDelta();
 10	game.player.yaw += -mouseDelta.x * PLAYER_MOUSE_SENSITIVITY;
 11	game.player.pitch += mouseDelta.y * PLAYER_MOUSE_SENSITIVITY;
 12
 13	if (game.player.pitch > 89.0f * DEG2RAD) game.player.pitch = 89.0f * DEG2RAD;
 14	if (game.player.pitch < -89.0f * DEG2RAD) game.player.pitch = -89.0f * DEG2RAD;
 15}
 16
 17static void MoveNormal(float dt) {
 18	float eyeHeight = PLAYER_EYE_HEIGHT - (game.player.crouch_amount * PLAYER_CROUCH_OFFSET);
 19
 20	Vector3 forward = { sinf(game.player.yaw), 0, cosf(game.player.yaw) };
 21	Vector3 right = { sinf(game.player.yaw - PI/2.0f), 0, cosf(game.player.yaw - PI/2.0f) };
 22
 23	Vector3 moveDir = { 0 };
 24	if (IsKeyDown(KEY_W)) moveDir = Vector3Add(moveDir, forward);
 25	if (IsKeyDown(KEY_S)) moveDir = Vector3Subtract(moveDir, forward);
 26	if (IsKeyDown(KEY_D)) moveDir = Vector3Add(moveDir, right);
 27	if (IsKeyDown(KEY_A)) moveDir = Vector3Subtract(moveDir, right);
 28
 29	float speed = PLAYER_MOVE_SPEED;
 30	if (IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT)) speed *= PLAYER_SPRINT_MULTIPLIER;
 31	speed *= Lerp(1.0f, PLAYER_CROUCH_MOVE_MULTIPLIER, game.player.crouch_amount);
 32
 33	if (Vector3Length(moveDir) > 0) {
 34		moveDir = Vector3Scale(Vector3Normalize(moveDir), speed * dt);
 35	}
 36
 37	game.player.velocity.y -= PLAYER_GRAVITY * dt;
 38
 39	if (game.player.is_grounded && IsKeyPressed(KEY_SPACE)) {
 40		game.player.velocity.y = PLAYER_JUMP_FORCE;
 41		game.player.is_grounded = false;
 42	}
 43
 44	Vector3 remainingMove = moveDir;
 45	for (int iter = 0; iter < 4 && Vector3Length(remainingMove) > 0.001f; iter++) {
 46		RayCollision closestHit = { 0 };
 47		closestHit.distance = FLT_MAX;
 48		bool hitFound = false;
 49
 50		// Cast rays at different heights to check for obstacles.
 51		// The bottom ray is raised slightly to allow stepping over small ledges/stairs.
 52		float heights[] = { -eyeHeight + 18.0f, -eyeHeight / 2.0f, 0.0f };
 53		Vector3 currentPos = game.player.pos;
 54
 55		for (int i = 0; i < 3; i++) {
 56			Vector3 start = Vector3Add(currentPos, (Vector3){0, heights[i], 0});
 57			Vector3 dir = Vector3Normalize(remainingMove);
 58			float dist = Vector3Length(remainingMove) + PLAYER_RADIUS;
 59			Vector3 end = Vector3Add(start, Vector3Scale(dir, dist));
 60
 61			RayCollision col;
 62			if (check_map_collision(start, end, &col)) {
 63				float adjustedDist = col.distance - PLAYER_RADIUS;
 64				if (adjustedDist < closestHit.distance) {
 65					closestHit = col;
 66					closestHit.distance = adjustedDist;
 67					hitFound = true;
 68				}
 69			}
 70		}
 71
 72		if (hitFound) {
 73			float moveDist = fmaxf(0, closestHit.distance - 0.1f);
 74			Vector3 moveStep = Vector3Scale(Vector3Normalize(remainingMove), moveDist);
 75			game.player.pos.x += moveStep.x;
 76			game.player.pos.z += moveStep.z;
 77
 78			Vector3 slideNormal = { closestHit.normal.x, 0, closestHit.normal.z };
 79			if (Vector3Length(slideNormal) > 0.001f) {
 80				slideNormal = Vector3Normalize(slideNormal);
 81				remainingMove = Vector3Subtract(remainingMove, moveStep);
 82				float dot = Vector3DotProduct(remainingMove, slideNormal);
 83				remainingMove = Vector3Subtract(remainingMove, Vector3Scale(slideNormal, dot));
 84			} else {
 85				remainingMove = (Vector3){0, 0, 0};
 86			}
 87		} else {
 88			game.player.pos.x += remainingMove.x;
 89			game.player.pos.z += remainingMove.z;
 90			break;
 91		}
 92	}
 93
 94	float verticalMove = game.player.velocity.y * dt;
 95	Vector3 vStart = game.player.pos;
 96	float oldY = game.player.pos.y; // Record Y for stair smoothing snap detection
 97
 98	if (verticalMove < 0) {
 99		Vector3 start = vStart;
100		Vector3 end = Vector3Add(vStart, (Vector3){0, verticalMove - eyeHeight, 0});
101		RayCollision vCol;
102		if (check_map_collision(start, end, &vCol) && vCol.normal.y > 0.5f) {
103			game.player.pos.y = vCol.point.y + eyeHeight;
104			game.player.velocity.y = 0;
105			game.player.is_grounded = true;
106		} else {
107			game.player.pos.y += verticalMove;
108			game.player.is_grounded = false;
109		}
110	} else if (verticalMove > 0) {
111		Vector3 start = vStart;
112		float headHeight = PLAYER_HEIGHT - PLAYER_EYE_HEIGHT;
113		Vector3 end = Vector3Add(vStart, (Vector3){0, verticalMove + headHeight, 0});
114		RayCollision vCol;
115		if (check_map_collision(start, end, &vCol)) {
116			game.player.pos.y = vCol.point.y - headHeight - 1.0f;
117			game.player.velocity.y = 0;
118		} else {
119			game.player.pos.y += verticalMove;
120		}
121		game.player.is_grounded = false;
122	} else {
123		Vector3 start = vStart;
124		Vector3 end = Vector3Add(vStart, (Vector3){0, -eyeHeight - 2.0f, 0});
125		RayCollision vCol;
126		if (check_map_collision(start, end, &vCol) && vCol.normal.y > 0.5f) {
127			game.player.is_grounded = true;
128			if (vCol.distance < eyeHeight + 1.0f) {
129				game.player.pos.y = vCol.point.y + eyeHeight;
130			}
131		} else {
132			game.player.is_grounded = false;
133		}
134	}
135
136	// Stair smoothing: detect if the player position snapped vertically (e.g. stepping up a stair)
137	// and apply a counter-offset to the camera to keep the movement visually smooth.
138	float deltaY = game.player.pos.y - oldY;
139	if (game.player.is_grounded && deltaY != 0) {
140		game.player.camera_offset_y -= deltaY;
141	}
142}
143
144static void MoveFly(float dt) {
145	Vector3 forward = { 
146		cosf(game.player.pitch) * sinf(game.player.yaw), 
147		-sinf(game.player.pitch), 
148		cosf(game.player.pitch) * cosf(game.player.yaw) 
149	};
150	Vector3 right = { sinf(game.player.yaw - PI/2.0f), 0, cosf(game.player.yaw - PI/2.0f) };
151
152	Vector3 move = { 0 };
153	if (IsKeyDown(KEY_W)) move = Vector3Add(move, forward);
154	if (IsKeyDown(KEY_S)) move = Vector3Subtract(move, forward);
155	if (IsKeyDown(KEY_D)) move = Vector3Add(move, right);
156	if (IsKeyDown(KEY_A)) move = Vector3Subtract(move, right);
157
158	if (IsKeyDown(KEY_SPACE)) move.y += 1.0f;
159	if (IsKeyDown(KEY_LEFT_CONTROL)) move.y -= 1.0f;
160
161	float speed = PLAYER_FLY_SPEED;
162	if (IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT)) speed *= PLAYER_SPRINT_MULTIPLIER;
163
164	if (Vector3Length(move) > 0) {
165		move = Vector3Scale(Vector3Normalize(move), speed * dt);
166		game.player.pos = Vector3Add(game.player.pos, move);
167	}
168
169	game.player.velocity = (Vector3){0, 0, 0};
170	game.player.is_grounded = false;
171}
172
173void update_player(void) {
174	float dt = GetFrameTime();
175	Vector3 oldPos = game.player.pos;
176
177	// Decay the stair smoothing offset over time
178	game.player.camera_offset_y = Lerp(game.player.camera_offset_y, 0.0f, 15.0f * dt);
179	if (fabsf(game.player.camera_offset_y) < 0.001f) game.player.camera_offset_y = 0.0f;
180
181	if (IsKeyPressed(KEY_F)) {
182		game.player.move_mode = (game.player.move_mode == MOVE_NORMAL) ? MOVE_FLY : MOVE_NORMAL;
183	}
184
185	PlayerRotate();
186
187	bool canStand = true;
188	if (game.player.crouch_amount > 0.1f) {
189		Vector3 headPos = game.player.pos;
190		Vector3 headEnd = Vector3Add(headPos, (Vector3){0, PLAYER_HEIGHT - PLAYER_EYE_HEIGHT + PLAYER_CROUCH_OFFSET, 0});
191		RayCollision col;
192		if (check_map_collision(headPos, headEnd, &col)) {
193			canStand = false;
194		}
195	}
196
197	float targetCrouch = 0.0f;
198	if (IsKeyDown(KEY_LEFT_CONTROL) || !canStand) targetCrouch = 1.0f;
199	game.player.crouch_amount = Lerp(game.player.crouch_amount, targetCrouch, PLAYER_CROUCH_SPEED * dt);
200
201	float targetLean = 0.0f;
202	if (IsKeyDown(KEY_Q)) targetLean -= 1.0f;
203	if (IsKeyDown(KEY_E)) targetLean += 1.0f;
204	game.player.lean_amount = Lerp(game.player.lean_amount, targetLean, PLAYER_LEAN_SPEED * dt);
205
206	if (game.player.move_mode == MOVE_FLY) {
207		MoveFly(dt);
208	} else {
209		MoveNormal(dt);
210	}
211
212	float leanAngle = game.player.lean_amount * PLAYER_LEAN_ANGLE * DEG2RAD;
213	Vector3 bodyForward = { sinf(game.player.yaw), 0, cosf(game.player.yaw) };
214
215	float currentEyeHeight = PLAYER_EYE_HEIGHT - (game.player.crouch_amount * PLAYER_CROUCH_OFFSET);
216	float pivotDist = fminf(PLAYER_LEAN_PIVOT_DISTANCE, currentEyeHeight * 0.8f);
217
218	Vector3 neckToEye = { 0, pivotDist, 0 };
219	Vector3 rotatedOffset = Vector3RotateByAxisAngle(neckToEye, bodyForward, leanAngle);
220	Vector3 pivot = Vector3Subtract(game.player.pos, (Vector3){ 0, pivotDist, 0 });
221
222	game.camera.position = Vector3Add(pivot, rotatedOffset);
223	game.camera.position.y += game.player.camera_offset_y;
224
225	Vector3 forward = {
226		cosf(game.player.pitch) * sinf(game.player.yaw),
227		-sinf(game.player.pitch),
228		cosf(game.player.pitch) * cosf(game.player.yaw)
229	};
230
231	Vector3 up = { 0, 1, 0 };
232	up = Vector3RotateByAxisAngle(up, forward, leanAngle);
233	game.camera.up = up;
234	game.camera.target = Vector3Add(game.camera.position, Vector3Scale(forward, 20.0f));
235
236	if (dt > 0) {
237		Vector2 velH = { game.player.pos.x - oldPos.x, game.player.pos.z - oldPos.z };
238		game.player.horizontal_speed = Vector2Length(velH) / dt;
239	} else {
240		game.player.horizontal_speed = 0;
241	}
242}