Spaces:
Running
Running
File size: 6,585 Bytes
1390db3 fd12cd0 1390db3 d6dfcf1 1390db3 fd12cd0 1390db3 d6dfcf1 1390db3 d6dfcf1 1390db3 a646668 d6dfcf1 1390db3 d6dfcf1 1390db3 d6dfcf1 1390db3 d6dfcf1 a646668 d6dfcf1 1390db3 d6dfcf1 1390db3 d6dfcf1 1390db3 d6dfcf1 1390db3 d6dfcf1 fd12cd0 1390db3 d6dfcf1 1390db3 d6dfcf1 1390db3 d6dfcf1 1390db3 d6dfcf1 bf6239d 1390db3 d6dfcf1 1390db3 d6dfcf1 1390db3 d6dfcf1 1390db3 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 |
import * as THREE from 'three';
import { CFG } from './config.js';
import { G } from './globals.js';
import { getTerrainHeight, getNearbyTrees } from './world.js';
// Working vectors
const FWD = new THREE.Vector3();
const RIGHT = new THREE.Vector3();
const NEXT = new THREE.Vector3();
const PREV = new THREE.Vector3();
// Helpers implementing Quake/Source-like movement in XZ plane
function applyFriction(vel, friction, stopSpeed, dt) {
const vx = vel.x, vz = vel.z;
const speed = Math.hypot(vx, vz);
if (speed <= 0.0001) return;
const control = Math.max(speed, stopSpeed);
const drop = control * friction * dt;
const newSpeed = Math.max(0, speed - drop);
if (newSpeed !== speed) {
const k = newSpeed / speed;
vel.x *= k;
vel.z *= k;
}
}
function accelerate(vel, wishDir, wishSpeed, accel, dt) {
const current = vel.x * wishDir.x + vel.z * wishDir.z;
let add = wishSpeed - current;
if (add <= 0) return;
const push = Math.min(accel * wishSpeed * dt, add);
vel.x += wishDir.x * push;
vel.z += wishDir.z * push;
}
function airAccelerate(vel, wishDir, wishSpeedCap, airAccel, dt) {
const current = vel.x * wishDir.x + vel.z * wishDir.z;
const wishSpeed = Math.min(wishSpeedCap, Math.hypot(wishDir.x, wishDir.z) > 0 ? wishSpeedCap : 0);
let add = wishSpeed - current;
if (add <= 0) return;
const push = Math.min(airAccel * wishSpeed * dt, add);
vel.x += wishDir.x * push;
vel.z += wishDir.z * push;
}
export function updatePlayer(delta) {
if (!G.player.alive) return;
const P = G.player;
const M = CFG.player.move;
// Handle timed movement buff
if (G.movementBuffTimer > 0) {
G.movementBuffTimer -= delta;
if (G.movementBuffTimer <= 0) {
G.movementBuffTimer = 0;
G.movementMult = 1;
}
}
// Forward/right in the horizontal plane
G.camera.getWorldDirection(FWD);
FWD.y = 0; FWD.normalize();
RIGHT.crossVectors(FWD, G.camera.up).normalize();
// Build wish direction from inputs
let wishX = 0, wishZ = 0;
if (G.input.w) { wishX += FWD.x; wishZ += FWD.z; }
if (G.input.s) { wishX -= FWD.x; wishZ -= FWD.z; }
if (G.input.d) { wishX += RIGHT.x; wishZ += RIGHT.z; }
if (G.input.a) { wishX -= RIGHT.x; wishZ -= RIGHT.z; }
const wishLen = Math.hypot(wishX, wishZ);
if (wishLen > 0.0001) { wishX /= wishLen; wishZ /= wishLen; }
// Desired speeds
let baseSpeed = P.speed * (G.movementMult || 1) * (G.input.sprint ? CFG.player.sprintMult : 1);
const crouchMult = (CFG.player.crouchMult || 1);
// If not sliding, crouch reduces speed
if (G.input.crouch && !P.sliding) baseSpeed *= crouchMult;
// Timers: jump buffer and coyote time
// Buffer jump on key press (edge), not hold
if (G.input.jump && !P.jumpHeld) {
P.jumpBuffer = M.jumpBuffer;
} else {
P.jumpBuffer = Math.max(0, P.jumpBuffer - delta);
}
P.jumpHeld = !!G.input.jump;
if (P.grounded) P.coyoteTimer = M.coyoteTime; else P.coyoteTimer = Math.max(0, P.coyoteTimer - delta);
P.wallContactTimer = Math.max(0, P.wallContactTimer - delta);
// Sliding enter/exit
const horizSpeed = Math.hypot(P.vel.x, P.vel.z);
if (P.grounded && G.input.crouch && (horizSpeed >= M.slideMinSpeed)) {
P.sliding = true;
} else if (!G.input.crouch || !P.grounded) {
P.sliding = false;
}
// Jump handling (ground, coyote, or wall bounce)
let skippedFriction = false;
if (P.jumpBuffer > 0) {
if (P.coyoteTimer > 0) {
// Ground/coyote jump
P.yVel = M.jumpSpeed;
// Slide-jump: preserve momentum and add small boost
if (P.sliding) {
const sp = Math.hypot(P.vel.x, P.vel.z);
if (sp > 0.0001) {
const nx = P.vel.x / sp, nz = P.vel.z / sp;
P.vel.x += nx * M.slideJumpBoost;
P.vel.z += nz * M.slideJumpBoost;
}
skippedFriction = true;
}
P.grounded = false;
P.jumpBuffer = 0; // consume
P.coyoteTimer = 0;
} else if (!P.grounded && P.wallContactTimer > 0) {
// Wall bounce: reflect into-wall component and add outward pop
const n = P.lastWallNormal;
const dot = P.vel.x * n.x + P.vel.z * n.z;
// Remove into-wall component
P.vel.x -= n.x * dot;
P.vel.z -= n.z * dot;
// Add outward impulse
P.vel.x += n.x * M.wallBounceImpulse;
P.vel.z += n.z * M.wallBounceImpulse;
// Give a small jump
P.yVel = M.jumpSpeed;
P.jumpBuffer = 0;
P.wallContactTimer = 0;
}
}
// State-based friction and acceleration
if (P.grounded) {
// Friction (skip on slide-jump frame)
if (!skippedFriction) {
const fric = P.sliding ? M.slideFriction : M.friction;
applyFriction(P.vel, fric, M.stopSpeed, delta);
}
// Accelerate toward wishdir
accelerate(P.vel, { x: wishX, z: wishZ }, baseSpeed, P.sliding ? M.slideAccel : M.groundAccel, delta);
} else {
// Air movement
airAccelerate(P.vel, { x: wishX, z: wishZ }, M.airSpeedCap, M.airAccel, delta);
}
// Gravity (vertical only)
P.yVel -= M.gravity * delta;
// Integrate position
NEXT.copy(P.pos);
NEXT.x += P.vel.x * delta;
NEXT.z += P.vel.z * delta;
NEXT.y += P.yVel * delta;
// Collide with tree trunks (cylinders in XZ), push out
const nearTrees = getNearbyTrees(NEXT.x, NEXT.z, 3.5);
for (let i = 0; i < nearTrees.length; i++) {
const tree = nearTrees[i];
const dx = NEXT.x - tree.x;
const dz = NEXT.z - tree.z;
const dist = Math.hypot(dx, dz);
const minDist = P.radius + tree.radius;
if (dist < minDist && dist > 0) {
const nx = dx / dist;
const nz = dz / dist;
const push = (minDist - dist);
NEXT.x += nx * push;
NEXT.z += nz * push;
// Record wall contact for potential wall-bounce when airborne
if (!P.grounded) {
P.lastWallNormal.set(nx, 0, nz);
P.wallContactTimer = Math.max(P.wallContactTimer, CFG.player.move.wallBounceWindow);
}
}
}
// Bounds clamp
const halfSize = CFG.forestSize / 2 - P.radius;
NEXT.x = Math.max(-halfSize, Math.min(halfSize, NEXT.x));
NEXT.z = Math.max(-halfSize, Math.min(halfSize, NEXT.z));
// Ground resolve against terrain (using eye height)
const eye = G.input.crouch ? (CFG.player.crouchEyeHeight || 1.8) : (CFG.player.eyeHeight || 1.8);
const groundEye = getTerrainHeight(NEXT.x, NEXT.z) + eye;
if (NEXT.y <= groundEye) {
NEXT.y = groundEye;
P.yVel = 0;
P.grounded = true;
} else {
P.grounded = false;
}
// Commit position and keep camera in sync
PREV.copy(P.pos);
P.pos.copy(NEXT);
G.camera.position.copy(P.pos);
}
|