summaryrefslogtreecommitdiff
path: root/player.c
diff options
context:
space:
mode:
Diffstat (limited to 'player.c')
-rw-r--r--player.c282
1 files changed, 282 insertions, 0 deletions
diff --git a/player.c b/player.c
new file mode 100644
index 0000000..a8c039f
--- /dev/null
+++ b/player.c
@@ -0,0 +1,282 @@
+#include "all.h"
+#include "raymath.h"
+#include <float.h>
+
+static void PlayerRotate(void) {
+ if (!game.cursor_captured) return;
+
+ Vector2 mouseDelta = GetMouseDelta();
+ game.yaw += -mouseDelta.x * PLAYER_MOUSE_SENSITIVITY;
+ game.pitch += mouseDelta.y * PLAYER_MOUSE_SENSITIVITY;
+
+ // Clamp pitch to avoid gimbal lock/flipping (approx 89 degrees)
+ if (game.pitch > 89.0f * DEG2RAD) game.pitch = 89.0f * DEG2RAD;
+ if (game.pitch < -89.0f * DEG2RAD) game.pitch = -89.0f * DEG2RAD;
+}
+
+static bool CheckWorldCollision(Vector3 start, Vector3 end, RayCollision *outCollision) {
+ Vector3 diff = Vector3Subtract(end, start);
+ float maxDist = Vector3Length(diff);
+ if (maxDist < 0.001f) return false;
+
+ Ray ray = { 0 };
+ ray.position = start;
+ ray.direction = Vector3Scale(diff, 1.0f / maxDist);
+
+ RayCollision closest = { 0 };
+ closest.distance = FLT_MAX;
+ closest.hit = false;
+
+ for (int i = 0; i < game.world_model_count; i++) {
+ Model model = game.world_models[i];
+ for (int m = 0; m < model.meshCount; m++) {
+ RayCollision col = GetRayCollisionMesh(ray, model.meshes[m], model.transform);
+ if (col.hit && col.distance < closest.distance && col.distance <= maxDist) {
+ closest = col;
+ }
+ }
+ }
+
+ if (closest.hit) {
+ if (outCollision) *outCollision = closest;
+ return true;
+ }
+ return false;
+}
+
+static void MoveNormal(float dt) {
+ float eyeHeight = PLAYER_EYE_HEIGHT - (game.crouch_amount * PLAYER_CROUCH_OFFSET);
+
+ // Movement vectors based on yaw
+ Vector3 forward = { sinf(game.yaw), 0, cosf(game.yaw) };
+ Vector3 right = { sinf(game.yaw - PI/2.0f), 0, cosf(game.yaw - PI/2.0f) };
+
+ Vector3 moveDir = { 0 };
+ if (IsKeyDown(KEY_W)) moveDir = Vector3Add(moveDir, forward);
+ if (IsKeyDown(KEY_S)) moveDir = Vector3Subtract(moveDir, forward);
+ if (IsKeyDown(KEY_D)) moveDir = Vector3Add(moveDir, right);
+ if (IsKeyDown(KEY_A)) moveDir = Vector3Subtract(moveDir, right);
+
+ float speed = PLAYER_MOVE_SPEED;
+ if (IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT)) speed *= PLAYER_SPRINT_MULTIPLIER;
+
+ // Apply crouch speed penalty
+ speed *= Lerp(1.0f, PLAYER_CROUCH_MOVE_MULTIPLIER, game.crouch_amount);
+
+ if (Vector3Length(moveDir) > 0) {
+ moveDir = Vector3Scale(Vector3Normalize(moveDir), speed * dt);
+ }
+
+ // Apply gravity
+ game.velocity.y -= PLAYER_GRAVITY * dt;
+
+ // Jump
+ if (game.is_grounded && IsKeyPressed(KEY_SPACE)) {
+ game.velocity.y = PLAYER_JUMP_FORCE;
+ game.is_grounded = false;
+ }
+
+ // Horizontal movement with sliding collision
+ Vector3 remainingMove = moveDir;
+ for (int iter = 0; iter < 4 && Vector3Length(remainingMove) > 0.001f; iter++) {
+ RayCollision closestHit = { 0 };
+ closestHit.distance = FLT_MAX;
+ bool hitFound = false;
+
+ float heights[] = { -eyeHeight + 10.0f, -eyeHeight / 2.0f, 0.0f };
+ Vector3 currentPos = game.pos;
+
+ for (int i = 0; i < 3; i++) {
+ Vector3 start = Vector3Add(currentPos, (Vector3){0, heights[i], 0});
+ Vector3 dir = Vector3Normalize(remainingMove);
+ float dist = Vector3Length(remainingMove) + PLAYER_RADIUS;
+ Vector3 end = Vector3Add(start, Vector3Scale(dir, dist));
+
+ RayCollision col;
+ if (CheckWorldCollision(start, end, &col)) {
+ // Adjust distance to be relative to the player boundary
+ float adjustedDist = col.distance - PLAYER_RADIUS;
+ if (adjustedDist < closestHit.distance) {
+ closestHit = col;
+ closestHit.distance = adjustedDist;
+ hitFound = true;
+ }
+ }
+ }
+
+ if (hitFound) {
+ // Move as far as possible
+ float moveDist = fmaxf(0, closestHit.distance - 0.1f);
+ Vector3 moveStep = Vector3Scale(Vector3Normalize(remainingMove), moveDist);
+ game.pos.x += moveStep.x;
+ game.pos.z += moveStep.z;
+
+ // Project remaining movement onto the plane of the wall
+ Vector3 slideNormal = { closestHit.normal.x, 0, closestHit.normal.z };
+ if (Vector3Length(slideNormal) > 0.001f) {
+ slideNormal = Vector3Normalize(slideNormal);
+ remainingMove = Vector3Subtract(remainingMove, moveStep);
+ float dot = Vector3DotProduct(remainingMove, slideNormal);
+ remainingMove = Vector3Subtract(remainingMove, Vector3Scale(slideNormal, dot));
+ } else {
+ remainingMove = (Vector3){0, 0, 0};
+ }
+ } else {
+ // No collision found, move the rest of the way
+ game.pos.x += remainingMove.x;
+ game.pos.z += remainingMove.z;
+ break;
+ }
+ }
+
+ // Vertical movement with collision
+ float verticalMove = game.velocity.y * dt;
+ Vector3 vStart = game.pos;
+
+ if (verticalMove < 0) { // Falling/Down
+ Vector3 start = vStart;
+ // Check slightly below feet
+ Vector3 end = Vector3Add(vStart, (Vector3){0, verticalMove - eyeHeight, 0});
+ RayCollision vCol;
+ if (CheckWorldCollision(start, end, &vCol) && vCol.normal.y > 0.5f) {
+ game.pos.y = vCol.point.y + eyeHeight;
+ game.velocity.y = 0;
+ game.is_grounded = true;
+ } else {
+ game.pos.y += verticalMove;
+ game.is_grounded = false;
+ }
+ } else if (verticalMove > 0) { // Jumping/Up
+ Vector3 start = vStart;
+ // Check above head
+ float headHeight = PLAYER_HEIGHT - PLAYER_EYE_HEIGHT;
+ Vector3 end = Vector3Add(vStart, (Vector3){0, verticalMove + headHeight, 0});
+ RayCollision vCol;
+ if (CheckWorldCollision(start, end, &vCol)) {
+ game.pos.y = vCol.point.y - headHeight - 1.0f;
+ game.velocity.y = 0;
+ } else {
+ game.pos.y += verticalMove;
+ }
+ game.is_grounded = false;
+ } else {
+ // Not moving vertically, but check if we are still on ground
+ Vector3 start = vStart;
+ Vector3 end = Vector3Add(vStart, (Vector3){0, -eyeHeight - 2.0f, 0});
+ RayCollision vCol;
+ if (CheckWorldCollision(start, end, &vCol) && vCol.normal.y > 0.5f) {
+ game.is_grounded = true;
+ // Snap to floor if very close
+ if (vCol.distance < eyeHeight + 1.0f) {
+ game.pos.y = vCol.point.y + eyeHeight;
+ }
+ } else {
+ game.is_grounded = false;
+ }
+ }
+}
+
+static void MoveFly(float dt) {
+ // Full 3D movement based on yaw/pitch
+ Vector3 forward = {
+ cosf(game.pitch) * sinf(game.yaw),
+ -sinf(game.pitch),
+ cosf(game.pitch) * cosf(game.yaw)
+ };
+ Vector3 right = { sinf(game.yaw - PI/2.0f), 0, cosf(game.yaw - PI/2.0f) };
+
+ Vector3 move = { 0 };
+ if (IsKeyDown(KEY_W)) move = Vector3Add(move, forward);
+ if (IsKeyDown(KEY_S)) move = Vector3Subtract(move, forward);
+ if (IsKeyDown(KEY_D)) move = Vector3Add(move, right);
+ if (IsKeyDown(KEY_A)) move = Vector3Subtract(move, right);
+
+ // Fly up/down
+ if (IsKeyDown(KEY_SPACE)) move.y += 1.0f;
+ if (IsKeyDown(KEY_LEFT_CONTROL)) move.y -= 1.0f;
+
+ float speed = PLAYER_FLY_SPEED;
+ if (IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT)) speed *= PLAYER_SPRINT_MULTIPLIER;
+
+ if (Vector3Length(move) > 0) {
+ move = Vector3Scale(Vector3Normalize(move), speed * dt);
+ game.pos = Vector3Add(game.pos, move);
+ }
+
+ game.velocity = (Vector3){0, 0, 0};
+ game.is_grounded = false;
+}
+
+void UpdatePlayer(void) {
+ float dt = GetFrameTime();
+ Vector3 oldPos = game.pos;
+
+ if (IsKeyPressed(KEY_F)) {
+ game.move_mode = (game.move_mode == MOVE_NORMAL) ? MOVE_FLY : MOVE_NORMAL;
+ TraceLog(LOG_INFO, "Movement mode: %s", (game.move_mode == MOVE_NORMAL) ? "NORMAL" : "FLY");
+ }
+
+ PlayerRotate();
+
+ // Crouching logic
+ bool canStand = true;
+ if (game.crouch_amount > 0.1f) {
+ Vector3 headPos = game.pos;
+ Vector3 headEnd = Vector3Add(headPos, (Vector3){0, PLAYER_HEIGHT - PLAYER_EYE_HEIGHT + PLAYER_CROUCH_OFFSET, 0});
+ RayCollision col;
+ if (CheckWorldCollision(headPos, headEnd, &col)) {
+ canStand = false;
+ }
+ }
+
+ float targetCrouch = 0.0f;
+ if (IsKeyDown(KEY_LEFT_CONTROL) || !canStand) targetCrouch = 1.0f;
+ game.crouch_amount = Lerp(game.crouch_amount, targetCrouch, PLAYER_CROUCH_SPEED * dt);
+
+ // Leaning logic
+ float targetLean = 0.0f;
+ if (IsKeyDown(KEY_Q)) targetLean -= 1.0f;
+ if (IsKeyDown(KEY_E)) targetLean += 1.0f;
+
+ game.lean_amount = Lerp(game.lean_amount, targetLean, PLAYER_LEAN_SPEED * dt);
+
+ if (game.move_mode == MOVE_FLY) {
+ MoveFly(dt);
+ } else {
+ MoveNormal(dt);
+ }
+
+ // Apply lean as a pivot from the neck/waist
+ float leanAngle = game.lean_amount * PLAYER_LEAN_ANGLE * DEG2RAD;
+ Vector3 bodyForward = { sinf(game.yaw), 0, cosf(game.yaw) };
+
+ float currentEyeHeight = PLAYER_EYE_HEIGHT - (game.crouch_amount * PLAYER_CROUCH_OFFSET);
+ float pivotDist = fminf(PLAYER_LEAN_PIVOT_DISTANCE, currentEyeHeight * 0.8f);
+
+ Vector3 neckToEye = { 0, pivotDist, 0 };
+ Vector3 rotatedOffset = Vector3RotateByAxisAngle(neckToEye, bodyForward, leanAngle);
+ Vector3 pivot = Vector3Subtract(game.pos, (Vector3){ 0, pivotDist, 0 });
+
+ // Update camera based on physical pos + visual lean pivot
+ game.camera.position = Vector3Add(pivot, rotatedOffset);
+
+ // Apply roll to up vector
+ Vector3 forward = {
+ cosf(game.pitch) * sinf(game.yaw),
+ -sinf(game.pitch),
+ cosf(game.pitch) * cosf(game.yaw)
+ };
+ Vector3 up = { 0, 1, 0 };
+ up = Vector3RotateByAxisAngle(up, forward, leanAngle);
+ game.camera.up = up;
+
+ game.camera.target = Vector3Add(game.camera.position, forward);
+
+ // Calculate horizontal speed for UI
+ if (dt > 0) {
+ Vector2 velH = { game.pos.x - oldPos.x, game.pos.z - oldPos.z };
+ game.horizontal_speed = Vector2Length(velH) / dt;
+ } else {
+ game.horizontal_speed = 0;
+ }
+}