/*** * * Copyright (c) 2016-2019 Marco 'eukara' Hladik. All rights reserved. * * See the file LICENSE attached with the sources for usage details. * ****/ #define AIRCONTROL #define PHY_JUMP_CHAINWINDOW 0.5 #define PHY_JUMP_CHAIN 100 #define PHY_JUMP_CHAINDECAY 50 #define FL_JUMPRELEASED 4096 /*FIXME: jumptime should use the time global, as time intervals are not predictable - decrement it based upon input_timelength*/ .float jumptime; .float waterlevel; .float watertype; .float teleport_time; .float maxspeed; .vector view_ofs; int trace_endcontentsi; /* ================= PMove_Init ================= */ void PMove_Init(void) { localcmd("serverinfo phy_stepheight 18\n"); localcmd("serverinfo phy_airstepheight 18\n"); localcmd("serverinfo phy_friction 4\n"); localcmd("serverinfo phy_edgefriction 1\n"); localcmd("serverinfo phy_stopspeed 75\n"); localcmd("serverinfo phy_gravity 800\n"); #ifdef CSTRIKE localcmd("serverinfo phy_accelerate 4\n"); localcmd("serverinfo phy_maxspeed 240\n"); #endif #ifdef VALVE localcmd("serverinfo phy_accelerate 8\n"); localcmd("serverinfo phy_maxspeed 270\n"); #endif } /* ================= PMove_Contents Wrapper that emulates pointcontents' behaviour ================= */ int PMove_Contents(vector org) { int oldhitcontents = self.hitcontentsmaski; self.hitcontentsmaski = -1; traceline(org, org, TRUE, self); self.hitcontentsmaski = oldhitcontents; return trace_endcontentsi; } /* ================= PMove_Categorize Figures out where we are in the game world. Whether we are in water, on the ground etc. ================= */ void PMove_Categorize(void) { int contents; if (self.flags & FL_CROUCHING) { self.mins = VEC_CHULL_MIN; self.maxs = VEC_CHULL_MAX; self.view_ofs = VEC_PLAYER_CVIEWPOS; } else { self.mins = VEC_HULL_MIN; self.maxs = VEC_HULL_MAX; self.view_ofs = VEC_PLAYER_VIEWPOS; } tracebox(self.origin, self.mins, self.maxs, self.origin - '0 0 0.25', FALSE, self); if (!trace_startsolid) { if ((trace_fraction < 1) && (trace_plane_normal[2] > 0.7)) { self.flags |= FL_ONGROUND; } else { self.flags &= ~FL_ONGROUND; } } contents = PMove_Contents(self.origin + self.mins + [0,0,1]); if (contents & CONTENTBIT_WATER) { contents = CONTENT_WATER; } else if (contents & CONTENTBIT_SLIME) { contents = CONTENT_SLIME; } else if (contents & CONTENTBIT_LAVA) { contents = CONTENT_LAVA; } if (contents < CONTENT_SOLID) { self.watertype = contents; if (PMove_Contents(self.origin + (self.mins + self.maxs) * 0.5) & CONTENTBITS_FLUID) { if (PMove_Contents(self.origin + self.maxs - '0 0 1') & CONTENTBITS_FLUID) { self.waterlevel = 3; } else { self.waterlevel = 2; } } else { self.waterlevel = 1; } } else { self.watertype = CONTENT_EMPTY; self.waterlevel = 0; } } /* =========== PMove_WaterMove ============ */ void PMove_WaterMove(void) { if (self.movetype == MOVETYPE_NOCLIP) { return; } /*if (self.health < 0) { return; }*/ #if 0 CPlayer plPlayer = (CPlayer)self; if (plPlayer.waterlevel != 3) { if (plPlayer.m_flAirFinished < time) { //sound (plPlayer, CHAN_VOICE, "player/gasp2.wav", 1, ATTN_NORM); } else if (plPlayer.m_flAirFinished < time + 9) { //sound (plPlayer, CHAN_VOICE, "player/gasp1.wav", 1, ATTN_NORM); } plPlayer.m_flAirFinished = time + 12; plPlayer.dmg = 2; } else if (plPlayer.m_flAirFinished < time) { if (plPlayer.m_flPainFinished < time) { plPlayer.dmg = plPlayer.dmg + 2; if (plPlayer.dmg > 15) { plPlayer.dmg = 10; } Damage_Apply(plPlayer, world, plPlayer.dmg, DAMAGE_DROWNING, WEAPON_NONE); plPlayer.m_flPainFinished = time + 1; } } #endif if (!self.waterlevel){ if (self.flags & FL_INWATER) { #if 0 //sound (self, CHAN_BODY, "misc/outwater.wav", 1, ATTN_NORM); #endif self.flags &= ~FL_INWATER; } return; } #if 0 if (plPlayer.watertype == CONTENT_LAVA) { if (plPlayer.m_flDamageTime < time) { plPlayer.m_flDamageTime = time + 0.2; Damage_Apply(plPlayer, world, 10*plPlayer.waterlevel, DAMAGE_BURN, WEAPON_NONE); } } else if (plPlayer.watertype == CONTENT_SLIME) { if (plPlayer.m_flDamageTime < time) { plPlayer.m_flDamageTime = time + 1; Damage_Apply(plPlayer, world, 4*plPlayer.waterlevel, DAMAGE_ACID, WEAPON_NONE); } } #endif if (!(self.flags & FL_INWATER)) { #if 0 sound (self, CHAN_BODY, "player/land/slosh.wav", 1, ATTN_NORM); plPlayer.m_flDamageTime = 0; #endif self.flags |= FL_INWATER; } if (!(self.flags & FL_WATERJUMP)) { self.velocity = self.velocity - 0.8 * self.waterlevel * frametime * self.velocity; } } void PMove_CheckWaterJump(void) { vector vStart; vector vEnd; makevectors(self.angles); vStart = self.origin; vStart[2] = vStart[2] + 8; v_forward[2] = 0; normalize(v_forward); vEnd = vStart + (v_forward * 24); traceline(vStart, vEnd, TRUE, self); if (trace_fraction < 1) { vStart[2] = vStart[2] + self.maxs[2]; vEnd = vStart + (v_forward * 24); //self.movedir = trace_plane_normal * -50; traceline(vStart, vEnd, TRUE, self); if (trace_fraction == 1) { //self.flags = self.flags | FL_WATERJUMP; self.velocity[2] = 350; self.flags &= ~FL_JUMPRELEASED; return; } } } int QPMove_IsStuck(entity eTarget, vector vOffset, vector vecMins, vector vecMaxs) { if (eTarget.solid != SOLID_SLIDEBOX) { return FALSE; } tracebox(eTarget.origin + vOffset, vecMins, vecMaxs, eTarget.origin + vOffset, FALSE, eTarget); return trace_startsolid; } /* ================= PMove_Run_Acceleration This function applies the velocity changes the player wishes to apply ================= */ void PMove_Run_Acceleration(float flMovetime, float flBefore) { vector vecWishVel; vector vecWishDir; vector vecTemp; float flWishSpeed; float flFriction; float flJumptimeDelta; float flChainBonus; int iFixCrouch = FALSE; PMove_Categorize(); // Update the timer self.jumptime -= flMovetime; self.teleport_time -= flMovetime; // Corpses if (self.movetype == MOVETYPE_TOSS) { self.velocity[2] = self.velocity[2] - (serverkeyfloat("phy_gravity") * flMovetime); return; } if (self.movetype == MOVETYPE_WALK) { // Crouching if (input_buttons & INPUT_BUTTON8) { self.flags |= FL_CROUCHING; } else { // If we aren't holding down duck anymore and 'attempt' to stand up, prevent it if (self.flags & FL_CROUCHING) { if (QPMove_IsStuck(self, '0 0 36', VEC_HULL_MIN, VEC_HULL_MAX) == FALSE) { self.flags &= ~FL_CROUCHING; iFixCrouch = TRUE; } } else { self.flags &= ~FL_CROUCHING; } } if (self.flags & FL_CROUCHING) { setsize(self, VEC_CHULL_MIN, VEC_CHULL_MAX); self.view_ofs = VEC_PLAYER_CVIEWPOS; } else { setsize(self, VEC_HULL_MIN, VEC_HULL_MAX); if (iFixCrouch && QPMove_IsStuck(self, [0,0,0], VEC_HULL_MIN, VEC_HULL_MAX)) { for (int i = 0; i < 36; i++) { self.origin[2] += 1; if (QPMove_IsStuck(self, [0,0,0], self.mins, self.maxs) == FALSE) { break; } } } setorigin(self, self.origin); self.view_ofs = VEC_PLAYER_VIEWPOS; } } makevectors(input_angles); // swim if (self.waterlevel >= 2) { if (self.movetype != MOVETYPE_NOCLIP) { self.flags &= ~FL_ONGROUND; if (input_movevalues == [0,0,0]) { vecWishVel = [0,0,-60]; // drift towards bottom } else { vecWishVel = v_forward * input_movevalues[0]; vecWishVel += v_right * input_movevalues[1]; vecWishVel += v_up * input_movevalues[2]; } flWishSpeed = vlen(vecWishVel); if (flWishSpeed > self.maxspeed) { flWishSpeed = self.maxspeed; } flWishSpeed = flWishSpeed * 0.7; // water friction if (self.velocity != [0,0,0]) { flFriction = vlen(self.velocity) * (1 - flMovetime * serverkeyfloat("phy_friction")); if (flFriction > 0) { self.velocity = normalize(self.velocity) * flFriction; } else { self.velocity = [0,0,0]; } } else { flFriction = 0; } // water acceleration if (flWishSpeed <= flFriction) { return; } flFriction = min(flWishSpeed - flFriction, serverkeyfloat("phy_accelerate") * flWishSpeed * flMovetime); self.velocity = self.velocity + normalize(vecWishVel) * flFriction; return; } } // hack to not let you back into teleporter if (self.teleport_time > 0 && input_movevalues[0] < 0) { vecWishVel = v_right * input_movevalues[1]; } else { if (self.movetype == MOVETYPE_NOCLIP) { } else if (self.flags & FL_ONGROUND) { makevectors (input_angles[1] * [0,1,0]); } vecWishVel = v_forward * input_movevalues[0] + v_right * input_movevalues[1]; } if (self.movetype != MOVETYPE_WALK) { vecWishVel[2] += input_movevalues[2]; } else { vecWishVel[2] = 0; } vecWishDir = normalize(vecWishVel); flWishSpeed = vlen(vecWishVel); if (flWishSpeed > self.maxspeed) { flWishSpeed = self.maxspeed; } if (self.movetype == MOVETYPE_NOCLIP) { self.flags &= ~FL_ONGROUND; self.velocity = vecWishDir * flWishSpeed; } else { /*FIXME: pogostick*/ if (self.flags & FL_ONGROUND) if (!(self.flags & FL_WATERJUMP)) if (self.flags & FL_JUMPRELEASED) if (input_buttons & INPUT_BUTTON2 && flBefore) { if (self.velocity[2] < 0) { self.velocity[2] = 0; } if (self.waterlevel >= 2) { if (self.watertype == CONTENT_WATER) { self.velocity[2] = 100; } else if (self.watertype == CONTENT_SLIME) { self.velocity[2] = 80; } else { self.velocity[2] = 50; } } else { self.velocity[2] += 240; } if (self.jumptime > 0) { // time since last jump flJumptimeDelta = 0 - (self.jumptime - PHY_JUMP_CHAINWINDOW); //self.velocity[2] += PHY_JUMP_CHAIN; flChainBonus = PHY_JUMP_CHAIN - (((PHY_JUMP_CHAINWINDOW - (PHY_JUMP_CHAINWINDOW - flJumptimeDelta)) * 2) * PHY_JUMP_CHAINDECAY); self.velocity[2] += flChainBonus; } self.jumptime = PHY_JUMP_CHAINWINDOW; self.flags &= ~FL_ONGROUND; self.flags &= ~FL_JUMPRELEASED; } // not pressing jump, set released flag if (!(input_buttons & INPUT_BUTTON2)) { self.flags |= FL_JUMPRELEASED; } if (self.flags & FL_ONGROUND) { // friction if (self.velocity[0] || self.velocity[1]) { vecTemp = self.velocity; vecTemp[2] = 0; flFriction = vlen(vecTemp); // if the leading edge is over a dropoff, increase friction vecTemp = self.origin + normalize(vecTemp) * 16 + '0 0 1' * VEC_HULL_MIN[2]; traceline(vecTemp, vecTemp + '0 0 -34', TRUE, self); // apply friction if (trace_fraction == 1.0) { if (flFriction < serverkeyfloat("phy_stopspeed")) { flFriction = 1 - flMovetime * (serverkeyfloat("phy_stopspeed") / flFriction) * serverkeyfloat("phy_friction") * serverkeyfloat("phy_edgefriction"); } else { flFriction = 1 - flMovetime * serverkeyfloat("phy_friction") * serverkeyfloat("phy_edgefriction"); } } else { if (flFriction < serverkeyfloat("phy_stopspeed")) { flFriction = 1 - flMovetime * (serverkeyfloat("phy_stopspeed") / flFriction) * serverkeyfloat("phy_friction"); } else { flFriction = 1 - flMovetime * serverkeyfloat("phy_friction"); } } if (flFriction < 0) { self.velocity = [0,0,0]; } else { self.velocity = self.velocity * flFriction; } } /*if (self.flags & FL_ONLADDER) { vector vPlayerVector; makevectors(input_angles); vPlayerVector = v_forward; vPlayerVector = (vPlayerVector * 240); if (input_movevalues[0] > 0) { self.velocity = vPlayerVector; } else { self.velocity = [0,0,0]; } }*/ // acceleration flFriction = flWishSpeed - (self.velocity * vecWishDir); if (flFriction > 0) { self.velocity = self.velocity + vecWishDir * min(flFriction, serverkeyfloat("phy_accelerate") * flMovetime * flWishSpeed); } } else { /* apply gravity */ self.velocity[2] = self.velocity[2] - (serverkeyfloat("phy_gravity") * flMovetime); if (flWishSpeed < 30) { flFriction = flWishSpeed - (self.velocity * vecWishDir); } else { flFriction = 30 - (self.velocity * vecWishDir); } if (flFriction > 0) { self.velocity = self.velocity + vecWishDir * (min(flFriction, serverkeyfloat("phy_accelerate")) * flWishSpeed * flMovetime); } } } /*if (self.gflags & GF_FROZEN) { self.velocity[0] = self.velocity[1] = 0; }*/ } /* ================= PMove_Rebound Calls somethings touch() function upon hit. ================= */ void PMove_DoTouch(entity tother) { entity oself = self; if (tother.touch) { other = self; self = tother; self.touch(); } self = oself; } /* ================= PMove_Rebound This function 'bounces' off any surfaces that were hit ================= */ static void PMove_Rebound(vector vecNormal) { self.velocity = self.velocity - vecNormal * (self.velocity * vecNormal); } /* ================= PMove_Fix_Origin Incase BSP precision messes up, this function will attempt to correct the origin to stop it from being invalid. ================= */ float PMove_Fix_Origin(void) { float x, y, z; vector norg, oorg = self.origin; for (z = 0; z < 3; z++) { norg[2] = oorg[2] + ((z==2)?-1:z)*0.0125; for (x = 0; x < 3; x++) { norg[0] = oorg[0] + ((x==2)?-1:x)*0.0125; for (y = 0; y < 3; y++) { norg[1] = oorg[1] + ((y==2)?-1:y)*0.0125; tracebox(norg, self.mins, self.maxs, norg, FALSE, self); if (!trace_startsolid) { //dprint("[PHYSICS] Unstuck\n"); self.origin = norg; return TRUE; } } } } //dprint("[PHYSICS] Stuck\n"); return FALSE; } /* ================= PMove_Run_Move This function is responsible for moving the entity forwards according to the various inputs/state. ================= */ void PMove_Run_Move(void) { vector vecDestPos; vector vecSavePlane; float flStepped; float flMoveTime; int iAttempts; if (self.movetype == MOVETYPE_NOCLIP) { self.origin = self.origin + self.velocity * input_timelength; return; } // we need to bounce off surfaces (in order to slide along them), so we need at 2 attempts for (iAttempts = 3, flMoveTime = input_timelength; flMoveTime > 0 && iAttempts; iAttempts--) { vecDestPos = self.origin + (self.velocity * flMoveTime); tracebox(self.origin, self.mins, self.maxs, vecDestPos, FALSE, self); if (trace_startsolid) { if (!PMove_Fix_Origin()) { return; } continue; } self.origin = trace_endpos; if (trace_fraction < 1) { vecSavePlane = trace_plane_normal; flMoveTime -= flMoveTime * trace_fraction; if (flMoveTime) { // step up if we can trace_endpos = self.origin; if (self.flags & FL_ONGROUND) { trace_endpos[2] += serverkeyfloat("phy_stepheight"); } else { trace_endpos[2] += serverkeyfloat("phy_airstepheight"); } tracebox(self.origin, self.mins, self.maxs, trace_endpos, FALSE, self); flStepped = trace_endpos[2] - self.origin[2]; float roof_fraction = trace_fraction; vector roof_plane_normal = trace_plane_normal; vecDestPos = trace_endpos + self.velocity*flMoveTime; vecDestPos[2] = trace_endpos[2]; /*only horizontally*/ // move forwards tracebox(trace_endpos, self.mins, self.maxs, vecDestPos, FALSE, self); // if we got anywhere, make this raised-step move count if (trace_fraction != 0) { float fwfrac = trace_fraction; vector fwplane = trace_plane_normal; // move down vecDestPos = trace_endpos; vecDestPos[2] -= flStepped + 1; tracebox(trace_endpos, self.mins, self.maxs, vecDestPos, FALSE, self); if (trace_fraction < 1 && trace_plane_normal[2] > 0.7f) { flMoveTime -= flMoveTime * fwfrac; /* bounce off the ceiling if we hit it while airstepping */ if (roof_fraction < 1) { PMove_Rebound(roof_plane_normal); } /* FIXME: you probably want this: && self.velocity[2] < 0 */ if (trace_fraction < 1) { PMove_Rebound(trace_plane_normal); } else if (fwfrac < 1) { PMove_Rebound(fwplane); } self.origin = trace_endpos; continue; } } } //stepping failed, just bounce off PMove_Rebound(vecSavePlane); PMove_DoTouch(trace_ent); } else { break; } } /* touch whatever is below */ if (self.flags & FL_ONGROUND) { vecDestPos = self.origin; vecDestPos[2] -= serverkeyfloat("phy_stepheight"); tracebox(self.origin, self.mins, self.maxs, vecDestPos, FALSE, self); if (trace_fraction >= 1) { return; } /*if (trace_startsolid) { if (!PMove_Fix_Origin()) { return; } }*/ PMove_DoTouch(trace_ent); } } /* ================= PMove_Run Runs the physics for one input frame. ================= */ void PMove_Run(void) { #ifdef VALVE self.maxspeed = (self.flags & FL_CROUCHING) ? 135 : 270; if (input_buttons & INPUT_BUTTON5) { input_movevalues *= 0.50; } #endif Game_Input(); PMove_WaterMove(); if (self.waterlevel >= 2) { PMove_CheckWaterJump(); } if (input_buttons & INPUT_BUTTON2) { input_movevalues[2] = 240; } if (input_buttons & INPUT_BUTTON8) { input_movevalues[2] = -240; } /* Call accelerate before and after the actual move, * with half the move each time. * This reduces framerate dependance. */ PMove_Run_Acceleration(input_timelength / 2, TRUE); PMove_Run_Move(); PMove_Run_Acceleration(input_timelength / 2, FALSE); /* NOTE: should clip to network precision here if lower than a float */ self.angles = input_angles; self.angles[0] *= -0.333; touchtriggers(); }