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}