Untitled
unknown
c_cpp
4 years ago
22 kB
3
Indexable
/* * MIT License * * Copyright (c) 2021 ETJump team <zero@etjump.com> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ // etj_pmove.cpp - reimplementation of relevants parts of bg_pmove.cpp // for calculation of pmove-centric parts of cgame code #include "etj_pmove.h" namespace ETJump { void Pmove::InitPmove(void) { p.pm_ps = cg.predictedPlayerState; p.pm.tracemask = p.pm_ps.pm_type == PM_DEAD ? MASK_PLAYERSOLID & ~CONTENTS_BODY : MASK_PLAYERSOLID; Pmove::PmoveSingle(); } void Pmove::PmoveSingle(void) { auto const uCmdScale = p.pm_ps.stats[STAT_USERCMD_BUTTONS] & (BUTTON_WALKING << 8) ? 64 : 127; if (!cg.demoPlayback && !(p.pm_ps.pm_flags & PMF_FOLLOW)) { auto const cmdNum = trap_GetCurrentCmdNumber(); trap_GetUserCmd(cmdNum, &p.pm.cmd); } else { p.pm.cmd.forwardmove = uCmdScale * (!!(p.pm_ps.stats[STAT_USERCMD_MOVE] & UMOVE_FORWARD) - !!(p.pm_ps.stats[STAT_USERCMD_MOVE] & UMOVE_BACKWARD)); p.pm.cmd.rightmove = uCmdScale * (!!(p.pm_ps.stats[STAT_USERCMD_MOVE] & UMOVE_RIGHT) - !!(p.pm_ps.stats[STAT_USERCMD_MOVE] & UMOVE_LEFT)); p.pm.cmd.upmove = uCmdScale * (!!(p.pm_ps.stats[STAT_USERCMD_MOVE] & UMOVE_UP) - !!(p.pm_ps.stats[STAT_USERCMD_MOVE] & UMOVE_DOWN)); } // CG_Printf("%d %d %d\n", p.pm.cmd.forwardmove, p.pm.cmd.rightmove, p.pm.cmd.upmove); // clear all pmove local vars memset(&p.pml, 0, sizeof(p.pml)); // these were in pml previously, make sure they are cleared too p.pm.walking = qfalse; p.pm.groundPlane = qfalse; memset(&p.pm.groundTrace, 0, sizeof(p.pm.groundTrace)); // save old velocity for crashlanding VectorCopy(p.pm_ps.velocity, p.pml.previous_velocity); AngleVectors(p.pm_ps.viewangles, p.pml.forward, p.pml.right, p.pml.up); if (p.pm.cmd.upmove < 10) { // not holding jump p.pm_ps.pm_flags &= ~PMF_JUMP_HELD; } if (p.pm_ps.pm_type >= PM_DEAD || p.pm_ps.pm_flags & (PMF_LIMBO | PMF_TIME_LOCKPLAYER)) // DHM - Nerve { p.pm.cmd.forwardmove = 0; p.pm.cmd.rightmove = 0; p.pm.cmd.upmove = 0; } // use default key combination when no user input if (!p.pm.cmd.forwardmove && !p.pm.cmd.rightmove) { p.pm.cmd.forwardmove = uCmdScale; p.pm.cmd.rightmove = uCmdScale; } // set mins, maxs, and viewheight if (!PM_CheckProne(&p.pm, &p.pm_ps)) { PM_CheckDuck(&p.pm, &p.pm_ps); } // set watertype, and waterlevel PM_SetWaterLevel(&p.pm, &p.pm_ps); // set groundentity PM_GroundTrace(&p.pm, &p.pm_ps, &p.pml); // for simplicity, we omit ladders and water // and only consider ground, air and ice movement if (p.pml.ladder) { return; } else if (p.pm_ps.pm_flags & PMF_TIME_WATERJUMP) { return; } else if (p.pm.waterlevel > 1) { // swimming return; } else if (p.pm.walking && !(p.pm_ps.eFlags & EF_MOUNTEDTANK)) { // walking on ground PM_WalkMove(&p.pm, &p.pm_ps, &p.pml); } else if (!(p.pm_ps.eFlags & EF_MOUNTEDTANK)) { // airborne PM_AirMove(&p.pm, &p.pm_ps, &p.pml); } } void Pmove::PM_AirMove(pmove_t* pm, playerState_t* pm_ps, pml_t* pml) { PM_Friction(pm, pm_ps, pml); auto const scale = p.PM_CmdScale(pm_ps, &pm->cmd); // project moves down to flat plane pml->forward[2] = 0; pml->right[2] = 0; VectorNormalize(pml->forward); VectorNormalize(pml->right); p.PM_UpdateWishVel(pm, pml); auto const wishspeed = scale * VectorLength2(p.wishvel); // TODO: this is where we are going to seperate different // cgame functions that use pmove calculations // PM_Accelerate(wishdir, wishspeed, pm_airaccelerate); } void Pmove::PM_WalkMove(pmove_t* pm, playerState_t* pm_ps, pml_t* pml) { if (pm->waterlevel > 2 && DotProduct(pml->forward, pm->groundTrace.plane.normal) > 0) { // begin swimming // PM_WaterMove(); return; } if (PM_CheckJump(pm, pm_ps)) { // jumped away if (pm->waterlevel > 1) { // PM_WaterMove(); } else { PM_AirMove(pm, pm_ps, pml); } if (!(pm->cmd.serverTime - p.pmext.jumpTime < ETJump::JUMP_DELAY_TIME)) { p.pmext.sprintTime -= 2500; if (p.pmext.sprintTime < 0) { p.pmext.sprintTime = 0; } } p.pmext.jumpTime = pm->cmd.serverTime; // JPW NERVE pm_ps->jumpTime = pm->cmd.serverTime; // Arnout: NOTE : TEMP DEBUG return; } PM_Friction(pm, pm_ps, pml); auto const scale = p.PM_CmdScale(pm_ps, &pm->cmd); // project moves down to flat plane pml->forward[2] = 0; pml->right[2] = 0; // FIXME: only flat ground is correct // project the forward and right directions onto the ground plane // PM_ClipVelocity(pml->forward, pm->groundTrace.plane.normal, pml->forward, OVERCLIP); // PM_ClipVelocity(pml->right, pm->groundTrace.plane.normal, pml->right, OVERCLIP); // VectorNormalize(pml->forward); VectorNormalize(pml->right); PM_UpdateWishVel(pm, pml); float wishspeed = scale * VectorLength2(p.wishvel); // clamp the speed lower if prone if (pm_ps->eFlags & EF_PRONE) { if (wishspeed > pm_ps->speed * pm_proneSpeedScale) { wishspeed = pm_ps->speed * pm_proneSpeedScale; } } else if (pm_ps->pm_flags & PMF_DUCKED) // clamp the speed lower if ducking { if (wishspeed > pm_ps->speed * pm_ps->crouchSpeedScale) { wishspeed = pm_ps->speed * pm_ps->crouchSpeedScale; } } // clamp the speed lower if wading or walking on the bottom if (pm->waterlevel) { float waterScale; waterScale = pm->waterlevel / 3.0; if (pm->watertype == CONTENTS_SLIME) //----(SA) slag { waterScale = 1.0 - (1.0 - pm_slagSwimScale) * waterScale; } else { waterScale = 1.0 - (1.0 - pm_waterSwimScale) * waterScale; } if (wishspeed > pm_ps->speed * waterScale) { wishspeed = pm_ps->speed * waterScale; } } #if 0 // when a player gets hit, they temporarily lose // full control, which allows them to be moved a bit // TODO: PM_Accelerate if ((pm->groundTrace.surfaceFlags & SURF_SLICK) || pm_ps->pm_flags & PMF_TIME_KNOCKBACK) { PM_Accelerate(wishdir, wishspeed, pm_airaccelerate); } else { PM_Accelerate(wishdir, wishspeed, pm_accelerate); } #endif } void Pmove::PM_Friction(pmove_t* pm, playerState_t* pm_ps, pml_t* pml) { // ignore slope movement auto const speed = pm->walking ? VectorLength2(pm_ps->velocity) : VectorLength(pm_ps->velocity); // rain - #179 don't do this for PM_SPECTATOR/PM_NOCLIP, we always want them to stop if (speed < 1 && pm_ps->pm_type != PM_SPECTATOR && pm_ps->pm_type != PM_NOCLIP) { pm_ps->velocity[0] = 0; pm_ps->velocity[1] = 0; // allow sinking underwater // FIXME: still have z friction underwater? return; } float drop = 0; // apply ground friction if (pm->waterlevel <= 1) { if (pm->walking && !(pm->groundTrace.surfaceFlags & SURF_SLICK)) { // if getting knocked back, no friction if (!(pm_ps->pm_flags & PMF_TIME_KNOCKBACK)) { auto const control = speed < pm_stopspeed ? pm_stopspeed : speed; drop += control * pm_friction * pml->frametime; } } } // apply water friction even if just wading if (pm->waterlevel) { if (pm->watertype == CONTENTS_SLIME) //----(SA) slag { drop += speed * pm_slagfriction * pm->waterlevel * pml->frametime; } else { drop += speed * pm_waterfriction * pm->waterlevel * pml->frametime; } } if (pm_ps->pm_type == PM_SPECTATOR) { drop += speed * pm_spectatorfriction * pml->frametime; } // apply ladder strafe friction if (pml->ladder) { drop += speed * pm_ladderfriction * pml->frametime; } // scale the velocity auto newspeed = speed - drop; if (newspeed < 0) { newspeed = 0; } newspeed /= speed; // rain - if we're barely moving and barely slowing down, we want to // help things along--we don't want to end up getting snapped back to // our previous speed if (pm_ps->pm_type == PM_SPECTATOR || pm_ps->pm_type == PM_NOCLIP) { if (drop < 1.0f && speed < 3.0f) { newspeed = 0.0; } } // rain - used VectorScale instead of multiplying by hand VectorScale(pm_ps->velocity, newspeed, pm_ps->velocity); } void Pmove::PM_UpdateWishVel(pmove_t* pm, pml_t* pml) { for (auto i = 0; i < 2; i++) { p.wishvel[i] = pml->forward[i] * pm->cmd.forwardmove + pml->right[i] * pm->cmd.rightmove; } } float Pmove::PM_CmdScale(playerState_t const* pm_ps, usercmd_t const* cmd) { auto max = abs(cmd->forwardmove); if (abs(cmd->rightmove) > max) { max = abs(cmd->rightmove); } if (abs(cmd->upmove) > max) { max = abs(cmd->upmove); } if (!max) { return 0; } auto const total = sqrtf(cmd->forwardmove * cmd->forwardmove + cmd->rightmove * cmd->rightmove + cmd->upmove * cmd->upmove); auto const scale = (float)pm_ps->speed * max / (127.f * total); if (pm_ps->stats[STAT_USERCMD_BUTTONS] & (BUTTON_SPRINT << 8) && p.pmext.sprintTime > 50) { return scale * pm_ps->sprintSpeedScale; } else { return scale * pm_ps->runSpeedScale; } } bool Pmove::PM_CheckJump(pmove_t* pm, playerState_t* pm_ps) { // no jumpin when prone if (pm_ps->eFlags & EF_PRONE) { return false; } // JPW NERVE -- jumping in multiplayer uses and requires sprint juice (to prevent turbo skating, sprint + jumps) // don't allow jump accel // rain - revert to using pmext for this since pmext is fixed now. // fix for #166 if (pm->cmd.serverTime - p.pmext.jumpTime < ETJump::JUMP_DELAY_TIME) { if (ETJump::hasJustStoodUp()) { return false; } if (pm->shared & BG_LEVEL_NO_JUMPDELAY) { if (pm->groundTrace.surfaceFlags & SURF_NOJUMPDELAY) { return false; } } else { if (!(pm->groundTrace.surfaceFlags & SURF_NOJUMPDELAY)) { return false; } } } if (pm_ps->pm_flags & PMF_RESPAWNED) { return false; // don't allow jump until all buttons are up } if (pm->cmd.upmove < 10) { // not holding jump return false; } // must wait for jump to be released if (pm_ps->pm_flags & PMF_JUMP_HELD) { // clear upmove so cmdscale doesn't lower running speed pm->cmd.upmove = 0; return false; } pm->groundPlane = qfalse; // jumping away pm->walking = qfalse; pm_ps->pm_flags |= PMF_JUMP_HELD; p.pmext.isJumpLand = true; pm_ps->groundEntityNum = ENTITYNUM_NONE; pm_ps->velocity[2] = JUMP_VELOCITY; return true; } void Pmove::PM_CheckDuck(pmove_t* pm, playerState_t* pm_ps) { trace_t trace; // Ridah, modified this for configurable bounding boxes pm->mins[0] = pm_ps->mins[0]; pm->mins[1] = pm_ps->mins[1]; pm->maxs[0] = pm_ps->maxs[0]; pm->maxs[1] = pm_ps->maxs[1]; pm->mins[2] = pm_ps->mins[2]; if (pm_ps->pm_type == PM_DEAD) { pm->maxs[2] = pm_ps->maxs[2]; // NOTE: must set death bounding box in game code pm_ps->viewheight = pm_ps->deadViewHeight; return; } if ((pm->cmd.upmove < 0 && !(pm_ps->eFlags & EF_MOUNTEDTANK) && !(pm_ps->pm_flags & PMF_LADDER)) || pm_ps->weapon == WP_MORTAR_SET) { // duck pm_ps->pm_flags |= PMF_DUCKED; } else { // stand up if possible if (pm_ps->pm_flags & PMF_DUCKED) { // try to stand up pm->maxs[2] = pm_ps->maxs[2]; PM_TraceAll(&trace, pm_ps->origin, pm_ps->origin); if (!trace.allsolid) { pm_ps->pm_flags &= ~PMF_DUCKED; } } } if (pm_ps->pm_flags & PMF_DUCKED) { pm->maxs[2] = pm_ps->crouchMaxZ; pm_ps->viewheight = pm_ps->crouchViewHeight; } else { pm->maxs[2] = pm_ps->maxs[2]; pm_ps->viewheight = pm_ps->standViewHeight; } } bool Pmove::PM_CheckProne(pmove_t* pm, playerState_t* pm_ps) { trace_t trace; //pm->trace(&trace, pm_ps->origin, pm_ps->mins, pm_ps->maxs, pm_ps->origin, pm_ps->clientNum, CONTENTS_NOPRONE); trap_CM_CapsuleTrace(&trace, pm_ps->origin, pm_ps->mins, pm_ps->maxs, pm_ps->origin, 0, CONTENTS_NOPRONE); if (pm->shared & BG_LEVEL_NO_PRONE) { if (trace.fraction == 1.0f) { pm_ps->eFlags &= ~EF_PRONE; pm_ps->eFlags &= ~EF_PRONE_MOVING; return false; } } else { if (trace.fraction != 1.0f) { pm_ps->eFlags &= ~EF_PRONE; pm_ps->eFlags &= ~EF_PRONE_MOVING; return false; } } if (!(pm_ps->eFlags & EF_PRONE)) { // can't go prone on ladders if (pm_ps->pm_flags & PMF_LADDER) { return false; } // no prone when using mg42's if (pm_ps->persistant[PERS_HWEAPON_USE] || pm_ps->eFlags & EF_MOUNTEDTANK) { return false; } if (pm_ps->weaponDelay && pm_ps->weapon == WP_PANZERFAUST) { return false; } if (pm_ps->weapon == WP_MORTAR_SET) { return false; } // can't go prone while swimming if (pm->waterlevel > 1) { return false; } if (((pm_ps->pm_flags & PMF_DUCKED && pm->cmd.doubleTap == DT_FORWARD) || (pm->cmd.wbuttons & WBUTTON_PRONE)) && pm->cmd.serverTime - p.pmext.proneTime > ETJump::PRONE_DELAY_TIME) { trace_t trace; pm->mins[0] = pm_ps->mins[0]; pm->mins[1] = pm_ps->mins[1]; pm->maxs[0] = pm_ps->maxs[0]; pm->maxs[1] = pm_ps->maxs[1]; pm->mins[2] = pm_ps->mins[2]; pm->maxs[2] = pm_ps->crouchMaxZ; pm_ps->eFlags |= EF_PRONE; PM_TraceAll(&trace, pm_ps->origin, pm_ps->origin); pm_ps->eFlags &= ~EF_PRONE; if (!trace.startsolid && !trace.allsolid) { // go prone pm_ps->pm_flags |= PMF_DUCKED; // crouched as well pm_ps->eFlags |= EF_PRONE; p.pmext.proneTime = pm->cmd.serverTime; // timestamp 'go prone' p.pmext.proneGroundTime = pm->cmd.serverTime; } } } if (pm_ps->eFlags & EF_PRONE) { if (pm->waterlevel > 1 || pm_ps->pm_type == PM_DEAD || pm_ps->eFlags & EF_MOUNTEDTANK || // zinx - what was the reason for this, anyway? removing fixes bug 424 // pm->cmd.serverTime - pm->pmext->proneGroundTime > 450 || ((pm->cmd.doubleTap == DT_BACK || pm->cmd.upmove > 10 || pm->cmd.wbuttons & WBUTTON_PRONE) && pm->cmd.serverTime - p.pmext.proneTime > ETJump::PRONE_DELAY_TIME)) { trace_t trace; // see if we have the space to stop prone pm->mins[0] = pm_ps->mins[0]; pm->mins[1] = pm_ps->mins[1]; pm->maxs[0] = pm_ps->maxs[0]; pm->maxs[1] = pm_ps->maxs[1]; pm->mins[2] = pm_ps->mins[2]; pm->maxs[2] = pm_ps->crouchMaxZ; pm_ps->eFlags &= ~EF_PRONE; PM_TraceAll(&trace, pm_ps->origin, pm_ps->origin); pm_ps->eFlags |= EF_PRONE; if (!trace.allsolid) { // crouch for a bit pm_ps->pm_flags |= PMF_DUCKED; // stop prone pm_ps->eFlags &= ~EF_PRONE; pm_ps->eFlags &= ~EF_PRONE_MOVING; p.pmext.proneTime = pm->cmd.serverTime; // timestamp 'stop prone' // don't jump for a bit p.pmext.jumpTime = pm->cmd.serverTime - ETJump::PRONE_JUMP_DELAY_TIME; pm_ps->jumpTime = pm->cmd.serverTime - ETJump::PRONE_JUMP_DELAY_TIME; } } } if (pm_ps->eFlags & EF_PRONE) { // See if we are moving float spd = VectorLength(pm_ps->velocity); bool userinput = abs(pm->cmd.forwardmove) + abs(pm->cmd.rightmove) > 10 ? true : false; if (userinput && spd > 40.f && !(pm_ps->eFlags & EF_PRONE_MOVING)) { pm_ps->eFlags |= EF_PRONE_MOVING; } else if (!userinput && spd < 20.0f && (pm_ps->eFlags & EF_PRONE_MOVING)) { pm_ps->eFlags &= ~EF_PRONE_MOVING; } pm->mins[0] = pm_ps->mins[0]; pm->mins[1] = pm_ps->mins[1]; pm->maxs[0] = pm_ps->maxs[0]; pm->maxs[1] = pm_ps->maxs[1]; pm->mins[2] = pm_ps->mins[2]; pm->maxs[2] = pm_ps->maxs[2] - pm_ps->standViewHeight - PRONE_VIEWHEIGHT; pm_ps->viewheight = PRONE_VIEWHEIGHT; return true; } return false; } void Pmove::PM_GroundTrace(pmove_t* pm, playerState_t* pm_ps, pml_t* pml) { vec3_t point; trace_t trace; point[0] = pm_ps->origin[0]; point[1] = pm_ps->origin[1]; point[2] = pm_ps->origin[2] - 0.25f; //PM_TraceAllLegs(&trace, &pm->pmext->proneLegsOffset, pm_ps->origin, point); trap_CM_CapsuleTrace(&trace, pm_ps->origin, point, pm->mins, pm->maxs, 0, pm->tracemask); pm->groundTrace = trace; // do something corrective if the trace starts in a solid... if (trace.allsolid && !(pm_ps->eFlags & EF_MOUNTEDTANK)) { if (!PM_CorrectAllSolid(&trace, pm, pm_ps)) { return; } } // if the trace didn't hit anything, we are in free fall if (trace.fraction == 1.0) { PM_GroundTraceMissed(pm, pm_ps); return; } // check if getting thrown off the ground if (pm_ps->velocity[2] > 0 && DotProduct(pm_ps->velocity, trace.plane.normal) > 10) { pm_ps->groundEntityNum = ENTITYNUM_NONE; pm->groundPlane = qfalse; pm->walking = qfalse; return; } // slopes that are too steep will not be considered onground if (trace.plane.normal[2] < MIN_WALK_NORMAL) { // FIXME: if they can't slide down the slope, let them // walk (sharp crevices) pm_ps->groundEntityNum = ENTITYNUM_NONE; pm->groundPlane = qtrue; pm->walking = qfalse; return; } pm->groundPlane = qtrue; pm->walking = qtrue; // hitting solid ground will end a waterjump if (pm_ps->pm_flags & PMF_TIME_WATERJUMP) { pm_ps->pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND); pm_ps->pm_time = 0; } if (pm_ps->groundEntityNum == ENTITYNUM_NONE) { // just hit the ground // TODO: do we need this..? //Feen: // PM_CheckPortal(); // don't do landing time if we were just going down a slope if (pml->previous_velocity[2] < -200) { // don't allow another jump for a little while pm_ps->pm_flags |= PMF_TIME_LAND; pm_ps->pm_time = 250; } } pm_ps->groundEntityNum = trace.entityNum; } void Pmove::PM_GroundTraceMissed(pmove_t* pm, playerState_t* pm_ps) { // If we've never yet touched the ground, it's because we're spawning, so don't // set to "in air" if (pm_ps->groundEntityNum != -1) { // Signify that we're in mid-air pm_ps->groundEntityNum = ENTITYNUM_NONE; } // if (pm_ps->groundEntityNum != -1)... pm->groundPlane = qfalse; pm->walking = qfalse; } bool Pmove::PM_CorrectAllSolid(trace_t* trace, pmove_t *pm, playerState_t* pm_ps) { vec3_t point; // jitter around for (auto i = -1; i <= 1; i++) { for (auto j = -1; j <= 1; j++) { for (auto k = -1; k <= 1; k++) { VectorCopy(pm_ps->origin, point); point[0] += (float)i; point[1] += (float)j; point[2] += (float)k; PM_TraceAll(trace, point, point); if (!trace->allsolid) { point[0] = pm_ps->origin[0]; point[1] = pm_ps->origin[1]; point[2] = pm_ps->origin[2] - 0.25; PM_TraceAll(trace, pm_ps->origin, point); pm->groundTrace = *trace; return true; } } } } pm_ps->groundEntityNum = ENTITYNUM_NONE; pm->groundPlane = qfalse; pm->walking = qfalse; return false; } void Pmove::PM_SetWaterLevel(pmove_t* pm, playerState_t* pm_ps) { vec3_t point; int cont; int sample1; int sample2; // get waterlevel, accounting for ducking pm->waterlevel = 0; pm->watertype = 0; // Ridah, modified this point[0] = pm_ps->origin[0]; point[1] = pm_ps->origin[1]; point[2] = pm_ps->origin[2] + pm_ps->mins[2] + 1; //cont = pm->pointcontents(point, pm_ps->clientNum); cont = trap_CM_PointContents(point, 0); if (cont & MASK_WATER) { sample2 = pm_ps->viewheight - pm_ps->mins[2]; sample1 = sample2 / 2; pm->watertype = cont; pm->waterlevel = 1; point[2] = pm_ps->origin[2] + pm_ps->mins[2] + sample1; cont = trap_CM_PointContents(point, 0); if (cont & MASK_WATER) { pm->waterlevel = 2; point[2] = pm_ps->origin[2] + pm_ps->mins[2] + sample2; cont = trap_CM_PointContents(point, 0); if (cont & MASK_WATER) { pm->waterlevel = 3; } } } } }
Editor is loading...