diff options
| author | navewindre <nw@moneybot.cc> | 2023-12-04 18:06:10 +0100 |
|---|---|---|
| committer | navewindre <nw@moneybot.cc> | 2023-12-04 18:06:10 +0100 |
| commit | aef0d1c1268ab7d4bc18996c9c6b4da16a40aadc (patch) | |
| tree | 43e766b51704f4ab8b383583bdc1871eeeb9c698 /sourcemod/scripting/gokz-mode-simplekz.sp | |
| parent | 38f1140c11724da05a23a10385061200b907cf6e (diff) | |
bbbbbbbbwaaaaaaaaaaa
Diffstat (limited to 'sourcemod/scripting/gokz-mode-simplekz.sp')
| -rw-r--r-- | sourcemod/scripting/gokz-mode-simplekz.sp | 846 |
1 files changed, 846 insertions, 0 deletions
diff --git a/sourcemod/scripting/gokz-mode-simplekz.sp b/sourcemod/scripting/gokz-mode-simplekz.sp new file mode 100644 index 0000000..99d76ac --- /dev/null +++ b/sourcemod/scripting/gokz-mode-simplekz.sp @@ -0,0 +1,846 @@ +#include <sourcemod> + +#include <sdkhooks> +#include <sdktools> +#include <dhooks> + +#include <movementapi> + +#undef REQUIRE_EXTENSIONS +#undef REQUIRE_PLUGIN +#include <gokz/core> +#include <updater> + +#include <gokz/kzplayer> + +#pragma newdecls required +#pragma semicolon 1 + + + +public Plugin myinfo = +{ + name = "GOKZ Mode - SimpleKZ", + author = "DanZay", + description = "SimpleKZ mode for GOKZ", + version = GOKZ_VERSION, + url = GOKZ_SOURCE_URL +}; + +#define UPDATER_URL GOKZ_UPDATER_BASE_URL..."gokz-mode-simplekz.txt" + +#define MODE_VERSION 21 +#define PS_MAX_REWARD_TURN_RATE 0.703125 // Degrees per tick (90 degrees per second) +#define PS_MAX_TURN_RATE_DECREMENT 0.015625 // Degrees per tick (2 degrees per second) +#define PS_SPEED_MAX 26.54321 // Units +#define PS_SPEED_INCREMENT 0.35 // Units per tick +#define PS_SPEED_DECREMENT_MIDAIR 0.2824 // Units per tick (lose PS_SPEED_MAX in 0 offset jump i.e. 94 ticks) +#define PS_GRACE_TICKS 3 // No. of ticks allowed to fail prestrafe checks when prestrafing - helps players with low fps +#define DUCK_SPEED_NORMAL 8.0 +#define DUCK_SPEED_MINIMUM 6.0234375 // Equal to if you just ducked/unducked for the first time in a while + +float gF_ModeCVarValues[MODECVAR_COUNT] = +{ + 6.5, // sv_accelerate + 0.0, // sv_accelerate_use_weapon_speed + 100.0, // sv_airaccelerate + 30.0, // sv_air_max_wishspeed + 1.0, // sv_enablebunnyhopping + 5.2, // sv_friction + 800.0, // sv_gravity + 301.993377, // sv_jump_impulse + 1.0, // sv_ladder_scale_speed + 0.0, // sv_ledge_mantle_helper + 320.0, // sv_maxspeed + 3500.0, // sv_maxvelocity + 0.0, // sv_staminajumpcost + 0.0, // sv_staminalandcost + 0.0, // sv_staminamax + 0.0, // sv_staminarecoveryrate + 0.7, // sv_standable_normal + 0.0, // sv_timebetweenducks + 0.7, // sv_walkable_normal + 10.0, // sv_wateraccelerate + 0.8, // sv_water_movespeed_multiplier + 0.0, // sv_water_swim_mode + 0.0, // sv_weapon_encumbrance_per_item + 0.0 // sv_weapon_encumbrance_scale +}; + +bool gB_GOKZCore; +ConVar gCV_ModeCVar[MODECVAR_COUNT]; +bool gB_HitTweakedPerf[MAXPLAYERS + 1]; +int gI_Cmdnum[MAXPLAYERS + 1]; +float gF_PSBonusSpeed[MAXPLAYERS + 1]; +float gF_PSVelMod[MAXPLAYERS + 1]; +float gF_PSVelModLanding[MAXPLAYERS + 1]; +bool gB_PSTurningLeft[MAXPLAYERS + 1]; +float gF_PSTurnRate[MAXPLAYERS + 1]; +int gI_PSTicksSinceIncrement[MAXPLAYERS + 1]; +Handle gH_GetPlayerMaxSpeed; +DynamicDetour gH_CanUnduck; +int gI_TickCount[MAXPLAYERS + 1]; +DynamicDetour gH_AirAccelerate; +int gI_OldButtons[MAXPLAYERS + 1]; +int gI_OldFlags[MAXPLAYERS + 1]; +bool gB_OldOnGround[MAXPLAYERS + 1]; +float gF_OldOrigin[MAXPLAYERS + 1][3]; +float gF_OldAngles[MAXPLAYERS + 1][3]; +float gF_OldVelocity[MAXPLAYERS + 1][3]; +int gI_LastJumpButtonCmdnum[MAXPLAYERS + 1]; +int gI_OffsetCGameMovement_player; + + + +// =====[ PLUGIN EVENTS ]===== + +public void OnPluginStart() +{ + if (FloatAbs(1.0 / GetTickInterval() - 128.0) > EPSILON) + { + SetFailState("gokz-mode-simplekz only supports 128 tickrate servers."); + } + HookEvents(); + CreateConVars(); +} + +public void OnAllPluginsLoaded() +{ + if (LibraryExists("updater")) + { + Updater_AddPlugin(UPDATER_URL); + } + if (LibraryExists("gokz-core")) + { + gB_GOKZCore = true; + GOKZ_SetModeLoaded(Mode_SimpleKZ, true, MODE_VERSION); + } + + for (int client = 1; client <= MaxClients; client++) + { + if (IsClientInGame(client)) + { + OnClientPutInServer(client); + } + } +} + +public void OnPluginEnd() +{ + if (gB_GOKZCore) + { + GOKZ_SetModeLoaded(Mode_SimpleKZ, false); + } +} + +public void OnLibraryAdded(const char[] name) +{ + if (StrEqual(name, "updater")) + { + Updater_AddPlugin(UPDATER_URL); + } + else if (StrEqual(name, "gokz-core")) + { + gB_GOKZCore = true; + GOKZ_SetModeLoaded(Mode_SimpleKZ, true, MODE_VERSION); + } +} + +public void OnLibraryRemoved(const char[] name) +{ + gB_GOKZCore = gB_GOKZCore && !StrEqual(name, "gokz-core"); +} + + + +// =====[ CLIENT EVENTS ]===== + +public void OnClientPutInServer(int client) +{ + ResetClient(client); + if (IsValidClient(client)) + { + HookClientEvents(client); + } + if (IsUsingMode(client)) + { + ReplicateConVars(client); + } +} + +public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3], float angles[3], int &weapon, int &subtype, int &cmdnum, int &tickcount, int &seed, int mouse[2]) +{ + if (!IsPlayerAlive(client) || !IsUsingMode(client)) + { + return Plugin_Continue; + } + + KZPlayer player = KZPlayer(client); + RemoveCrouchJumpBind(player, buttons); + ReduceDuckSlowdown(player); + CalcPrestrafeVelMod(player, angles); + FixWaterBoost(player, buttons); + FixDisplacementStuck(player); + + gB_HitTweakedPerf[player.ID] = false; + gI_Cmdnum[player.ID] = cmdnum; + gI_OldButtons[player.ID] = buttons; + gI_OldFlags[player.ID] = GetEntityFlags(player.ID); + gB_OldOnGround[player.ID] = player.OnGround; + gI_TickCount[player.ID] = tickcount; + player.GetOrigin(gF_OldOrigin[player.ID]); + player.GetEyeAngles(gF_OldAngles[player.ID]); + player.GetVelocity(gF_OldVelocity[player.ID]); + + return Plugin_Continue; +} + +public MRESReturn DHooks_OnGetPlayerMaxSpeed(int client, Handle hReturn) +{ + if (!IsUsingMode(client)) + { + return MRES_Ignored; + } + DHookSetReturn(hReturn, SPEED_NORMAL * gF_PSVelMod[client]); + return MRES_Supercede; +} + +public MRESReturn DHooks_OnAirAccelerate_Pre(Address pThis, DHookParam hParams) +{ + int client = GOKZGetClientFromGameMovementAddress(pThis, gI_OffsetCGameMovement_player); + if (!IsPlayerAlive(client) || !IsUsingMode(client)) + { + return MRES_Ignored; + } + + // NOTE: Prestrafing changes GetPlayerMaxSpeed, which changes + // air acceleration, so remove gF_PreVelMod[client] from wishspeed/maxspeed. + // This also applies to when the player is ducked: their wishspeed is + // 85 and with prestrafing can be ~93. + float wishspeed = DHookGetParam(hParams, 2); + if (gF_PSVelMod[client] > 1.0) + { + DHookSetParam(hParams, 2, wishspeed / gF_PSVelMod[client]); + return MRES_ChangedHandled; + } + + return MRES_Ignored; +} + +public MRESReturn DHooks_OnCanUnduck_Pre(Address pThis, DHookReturn hReturn) +{ + int client = GOKZGetClientFromGameMovementAddress(pThis, gI_OffsetCGameMovement_player); + if (!IsPlayerAlive(client) || !IsUsingMode(client)) + { + return MRES_Ignored; + } + // Just landed fully ducked, you can't unduck. + if (Movement_GetLandingTick(client) == (gI_TickCount[client] - 1) && GetEntPropFloat(client, Prop_Send, "m_flDuckAmount") >= 1.0 && GetEntProp(client, Prop_Send, "m_bDucked")) + { + hReturn.Value = false; + return MRES_Supercede; + } + return MRES_Ignored; +} + +public void OnPlayerRunCmdPost(int client, int buttons, int impulse, const float vel[3], const float angles[3], int weapon, int subtype, int cmdnum, int tickcount, int seed, const int mouse[2]) +{ + if (!IsValidClient(client) || !IsPlayerAlive(client) || !IsUsingMode(client)) + { + return; + } + + if (buttons & IN_JUMP) + { + gI_LastJumpButtonCmdnum[client] = cmdnum; + } +} + +public void SDKHook_OnClientPreThink_Post(int client) +{ + if (!IsPlayerAlive(client) || !IsUsingMode(client)) + { + return; + } + + // Don't tweak convars if GOKZ isn't running + if (gB_GOKZCore) + { + TweakConVars(); + } +} + +public void Movement_OnStartTouchGround(int client) +{ + if (!IsUsingMode(client)) + { + return; + } + + KZPlayer player = KZPlayer(client); + gF_PSVelModLanding[player.ID] = gF_PSVelMod[player.ID]; +} + +public Action Movement_OnJumpPre(int client, float origin[3], float velocity[3]) +{ + if (!IsPlayerAlive(client) || !IsUsingMode(client)) + { + return Plugin_Continue; + } + + KZPlayer player = KZPlayer(client); + return TweakJump(player, origin, velocity); +} + +public Action Movement_OnCategorizePositionPost(int client, float origin[3], float velocity[3]) +{ + if (!IsPlayerAlive(client) || !IsUsingMode(client)) + { + return Plugin_Continue; + } + return SlopeFix(client, origin, velocity); +} + +public void Movement_OnChangeMovetype(int client, MoveType oldMovetype, MoveType newMovetype) +{ + if (!IsUsingMode(client)) + { + return; + } + + KZPlayer player = KZPlayer(client); + if (gB_GOKZCore && newMovetype == MOVETYPE_WALK) + { + player.GOKZHitPerf = false; + player.GOKZTakeoffSpeed = player.TakeoffSpeed; + } +} + +public void GOKZ_OnOptionChanged(int client, const char[] option, any newValue) +{ + if (StrEqual(option, gC_CoreOptionNames[Option_Mode]) && newValue == Mode_SimpleKZ) + { + ReplicateConVars(client); + } +} + +public void GOKZ_OnCountedTeleport_Post(int client) +{ + ResetClient(client); +} + + + +// =====[ GENERAL ]===== + +bool IsUsingMode(int client) +{ + // If GOKZ core isn't loaded, then apply mode at all times + return !gB_GOKZCore || GOKZ_GetCoreOption(client, Option_Mode) == Mode_SimpleKZ; +} + +void ResetClient(int client) +{ + KZPlayer player = KZPlayer(client); + ResetVelMod(player); +} + +void HookEvents() +{ + GameData gameData = LoadGameConfigFile("movementapi.games"); + if (gameData == INVALID_HANDLE) + { + SetFailState("Failed to find movementapi.games config"); + } + + int offset = gameData.GetOffset("GetPlayerMaxSpeed"); + if (offset == -1) + { + SetFailState("Failed to get GetPlayerMaxSpeed offset"); + } + gH_GetPlayerMaxSpeed = DHookCreate(offset, HookType_Entity, ReturnType_Float, ThisPointer_CBaseEntity, DHooks_OnGetPlayerMaxSpeed); + + gH_AirAccelerate = DynamicDetour.FromConf(gameData, "CGameMovement::AirAccelerate"); + if (gH_AirAccelerate == INVALID_HANDLE) + { + SetFailState("Failed to find CGameMovement::AirAccelerate function signature"); + } + + if (!gH_AirAccelerate.Enable(Hook_Pre, DHooks_OnAirAccelerate_Pre)) + { + SetFailState("Failed to enable detour on CGameMovement::AirAccelerate"); + } + + char buffer[16]; + if (!gameData.GetKeyValue("CGameMovement::player", buffer, sizeof(buffer))) + { + SetFailState("Failed to get CGameMovement::player offset."); + } + gI_OffsetCGameMovement_player = StringToInt(buffer); + + gameData = LoadGameConfigFile("gokz-core.games"); + gH_CanUnduck = DynamicDetour.FromConf(gameData, "CCSGameMovement::CanUnduck"); + if (gH_CanUnduck == INVALID_HANDLE) + { + SetFailState("Failed to find CCSGameMovement::CanUnduck function signature"); + } + + if (!gH_CanUnduck.Enable(Hook_Pre, DHooks_OnCanUnduck_Pre)) + { + SetFailState("Failed to enable detour on CCSGameMovement::CanUnduck"); + } + delete gameData; +} + +// =====[ CONVARS ]===== + +void CreateConVars() +{ + for (int i = 0; i < MODECVAR_COUNT; i++) + { + gCV_ModeCVar[i] = FindConVar(gC_ModeCVars[i]); + } +} + +void TweakConVars() +{ + for (int i = 0; i < MODECVAR_COUNT; i++) + { + gCV_ModeCVar[i].FloatValue = gF_ModeCVarValues[i]; + } +} + +void ReplicateConVars(int client) +{ + /* + Replicate convars only when player changes mode in GOKZ + so that lagg isn't caused by other players using other + modes, and also as an optimisation. + */ + + if (IsFakeClient(client)) + { + return; + } + + for (int i = 0; i < MODECVAR_COUNT; i++) + { + gCV_ModeCVar[i].ReplicateToClient(client, FloatToStringEx(gF_ModeCVarValues[i])); + } +} + + + +// =====[ VELOCITY MODIFIER ]===== + +void HookClientEvents(int client) +{ + DHookEntity(gH_GetPlayerMaxSpeed, true, client); + SDKHook(client, SDKHook_PreThinkPost, SDKHook_OnClientPreThink_Post); +} + +void ResetVelMod(KZPlayer player) +{ + gF_PSBonusSpeed[player.ID] = 0.0; + gF_PSVelMod[player.ID] = 1.0; + gF_PSTurnRate[player.ID] = 0.0; +} + +void CalcPrestrafeVelMod(KZPlayer player, const float angles[3]) +{ + gI_PSTicksSinceIncrement[player.ID]++; + + // Short circuit if speed is 0 (also avoids divide by 0 errors) + if (player.Speed < EPSILON) + { + ResetVelMod(player); + return; + } + + // Current speed without bonus + float baseSpeed = FloatMin(SPEED_NORMAL, player.Speed / gF_PSVelMod[player.ID]); + + float newBonusSpeed = gF_PSBonusSpeed[player.ID]; + + // If player is in mid air, decrement their velocity modifier + if (!player.OnGround) + { + newBonusSpeed -= PS_SPEED_DECREMENT_MIDAIR; + } + // If player is turning at the required speed, and has the correct button inputs, reward it + else if (player.Turning && ValidPrestrafeButtons(player)) + { + // If player changes their prestrafe direction, reset it + if (player.TurningLeft && !gB_PSTurningLeft[player.ID] + || player.TurningRight && gB_PSTurningLeft[player.ID]) + { + ResetVelMod(player); + newBonusSpeed = 0.0; + } + + // Keep track of the direction of the turn + gB_PSTurningLeft[player.ID] = player.TurningLeft; + + // Step one of calculating new turn rate + float newTurningRate = FloatAbs(CalcDeltaAngle(gF_OldAngles[player.ID][1], angles[1])); + + // If no turning for just a few ticks, then forgive and calculate reward based on that no. of ticks + if (gI_PSTicksSinceIncrement[player.ID] <= PS_GRACE_TICKS) + { + // This turn occurred over multiple ticks, so scale appropriately + // Also cap turn rate at maximum reward turn rate + newTurningRate = FloatMin(PS_MAX_REWARD_TURN_RATE, + newTurningRate / gI_PSTicksSinceIncrement[player.ID]); + + // Limit how fast turn rate can decrease (also scaled appropriately) + gF_PSTurnRate[player.ID] = FloatMax(newTurningRate, + gF_PSTurnRate[player.ID] - PS_MAX_TURN_RATE_DECREMENT * gI_PSTicksSinceIncrement[player.ID]); + + newBonusSpeed += CalcPreRewardSpeed(gF_PSTurnRate[player.ID], baseSpeed) * gI_PSTicksSinceIncrement[player.ID]; + } + else + { + // Cap turn rate at maximum reward turn rate + newTurningRate = FloatMin(PS_MAX_REWARD_TURN_RATE, newTurningRate); + + // Limit how fast turn rate can decrease + gF_PSTurnRate[player.ID] = FloatMax(newTurningRate, + gF_PSTurnRate[player.ID] - PS_MAX_TURN_RATE_DECREMENT); + + // This is normal turning behaviour + newBonusSpeed += CalcPreRewardSpeed(gF_PSTurnRate[player.ID], baseSpeed); + } + + gI_PSTicksSinceIncrement[player.ID] = 0; + } + else if (gI_PSTicksSinceIncrement[player.ID] > PS_GRACE_TICKS) + { + // They definitely aren't turning, but limit how fast turn rate can decrease + gF_PSTurnRate[player.ID] = FloatMax(0.0, + gF_PSTurnRate[player.ID] - PS_MAX_TURN_RATE_DECREMENT); + } + + if (newBonusSpeed < 0.0) + { + // Keep velocity modifier positive + newBonusSpeed = 0.0; + } + else + { + // Scale the bonus speed based on current base speed and turn rate + float baseSpeedScaleFactor = baseSpeed / SPEED_NORMAL; // Max 1.0 + float turnRateScaleFactor = FloatMin(1.0, gF_PSTurnRate[player.ID] / PS_MAX_REWARD_TURN_RATE); + float scaledMaxBonusSpeed = PS_SPEED_MAX * baseSpeedScaleFactor * turnRateScaleFactor; + newBonusSpeed = FloatMin(newBonusSpeed, scaledMaxBonusSpeed); + } + + gF_PSBonusSpeed[player.ID] = newBonusSpeed; + gF_PSVelMod[player.ID] = 1.0 + (newBonusSpeed / baseSpeed); +} + +bool ValidPrestrafeButtons(KZPlayer player) +{ + bool forwardOrBack = player.Buttons & (IN_FORWARD | IN_BACK) && !(player.Buttons & IN_FORWARD && player.Buttons & IN_BACK); + bool leftOrRight = player.Buttons & (IN_MOVELEFT | IN_MOVERIGHT) && !(player.Buttons & IN_MOVELEFT && player.Buttons & IN_MOVERIGHT); + return forwardOrBack || leftOrRight; +} + +float CalcPreRewardSpeed(float yawDiff, float baseSpeed) +{ + // Formula + float reward; + if (yawDiff >= PS_MAX_REWARD_TURN_RATE) + { + reward = PS_SPEED_INCREMENT; + } + else + { + reward = PS_SPEED_INCREMENT * (yawDiff / PS_MAX_REWARD_TURN_RATE); + } + + return reward * baseSpeed / SPEED_NORMAL; +} + + + + +// =====[ JUMPING ]===== + +Action TweakJump(KZPlayer player, float origin[3], float velocity[3]) +{ + // TakeoffCmdnum and TakeoffSpeed is not defined here because the player technically hasn't taken off yet. + int cmdsSinceLanding = gI_Cmdnum[player.ID] - player.LandingCmdNum; + gB_HitTweakedPerf[player.ID] = cmdsSinceLanding <= 1 + || cmdsSinceLanding <= 3 && gI_Cmdnum[player.ID] - gI_LastJumpButtonCmdnum[player.ID] <= 3; + + if (gB_HitTweakedPerf[player.ID]) + { + if (cmdsSinceLanding <= 1) + { + NerfRealPerf(player, origin); + } + + ApplyTweakedTakeoffSpeed(player, velocity); + + if (cmdsSinceLanding > 1 || player.TakeoffSpeed > SPEED_NORMAL) + { + // Restore prestrafe lost due to briefly being on the ground + gF_PSVelMod[player.ID] = gF_PSVelModLanding[player.ID]; + } + return Plugin_Changed; + } + return Plugin_Continue; +} + +public Action Movement_OnJumpPost(int client) +{ + if (!IsUsingMode(client)) + { + return Plugin_Continue; + } + KZPlayer player = KZPlayer(client); + player.GOKZHitPerf = gB_HitTweakedPerf[player.ID]; + player.GOKZTakeoffSpeed = player.TakeoffSpeed; + return Plugin_Continue; +} + +public void Movement_OnStopTouchGround(int client) +{ + if (!IsUsingMode(client)) + { + return; + } + KZPlayer player = KZPlayer(client); + player.GOKZHitPerf = gB_HitTweakedPerf[player.ID]; + player.GOKZTakeoffSpeed = player.TakeoffSpeed; +} + +void NerfRealPerf(KZPlayer player, float origin[3]) +{ + // Not worth worrying about if player is already falling + // player.VerticalVelocity is not updated yet! Use processing velocity. + float velocity[3]; + Movement_GetProcessingVelocity(player.ID, velocity); + if (velocity[2] < EPSILON) + { + return; + } + + // Work out where the ground was when they bunnyhopped + float startPosition[3], endPosition[3], mins[3], maxs[3], groundOrigin[3]; + + startPosition = origin; + + endPosition = startPosition; + endPosition[2] = endPosition[2] - 2.0; // Should be less than 2.0 units away + + GetEntPropVector(player.ID, Prop_Send, "m_vecMins", mins); + GetEntPropVector(player.ID, Prop_Send, "m_vecMaxs", maxs); + + Handle trace = TR_TraceHullFilterEx( + startPosition, + endPosition, + mins, + maxs, + MASK_PLAYERSOLID, + TraceEntityFilterPlayers, + player.ID); + + // This is expected to always hit, previously this can fail upon jumpbugs. + if (TR_DidHit(trace)) + { + TR_GetEndPosition(groundOrigin, trace); + origin[2] = groundOrigin[2]; + } + + delete trace; +} + +void ApplyTweakedTakeoffSpeed(KZPlayer player, float velocity[3]) +{ + // Note that resulting velocity has same direction as landing velocity, not current velocity + // because current velocity direction can change drastically in just one tick (eg. walls) + // and it doesnt make sense for the new velocity to push you in that direction. + + float newVelocity[3], baseVelocity[3]; + player.GetLandingVelocity(newVelocity); + player.GetBaseVelocity(baseVelocity); + SetVectorHorizontalLength(newVelocity, CalcTweakedTakeoffSpeed(player)); + AddVectors(newVelocity, baseVelocity, newVelocity); // For backwards compatibility + velocity[0] = newVelocity[0]; + velocity[1] = newVelocity[1]; +} + +// Takeoff speed assuming player has met the conditions to need tweaking +float CalcTweakedTakeoffSpeed(KZPlayer player) +{ + // Formula + if (player.LandingSpeed > SPEED_NORMAL) + { + return FloatMin(player.LandingSpeed, (0.2 * player.LandingSpeed + 200) * gF_PSVelModLanding[player.ID]); + } + return player.LandingSpeed; +} + + + +// =====[ SLOPEFIX ]===== + +// ORIGINAL AUTHORS : Mev & Blacky +// URL : https://forums.alliedmods.net/showthread.php?p=2322788 +// NOTE : Modified by DanZay for this plugin + +Action SlopeFix(int client, float origin[3], float velocity[3]) +{ + KZPlayer player = KZPlayer(client); + // Check if player landed on the ground + if (Movement_GetOnGround(client) && !gB_OldOnGround[client]) + { + float vMins[] = {-16.0, -16.0, 0.0}; + // Always use ducked hull as the real hull size isn't updated yet. + // Might cause slight issues in extremely rare scenarios. + float vMaxs[] = {16.0, 16.0, 54.0}; + + float vEndPos[3]; + vEndPos[0] = origin[0]; + vEndPos[1] = origin[1]; + vEndPos[2] = origin[2] - gF_ModeCVarValues[ModeCVar_MaxVelocity]; + + // Set up and do tracehull to find out if the player landed on a slope + TR_TraceHullFilter(origin, vEndPos, vMins, vMaxs, MASK_PLAYERSOLID_BRUSHONLY, TraceRayDontHitSelf, client); + + if (TR_DidHit()) + { + // Gets the normal vector of the surface under the player + float vPlane[3], vLast[3]; + player.GetLandingVelocity(vLast); + TR_GetPlaneNormal(null, vPlane); + + // Make sure it's not flat ground and not a surf ramp (1.0 = flat ground, < 0.7 = surf ramp) + if (0.7 <= vPlane[2] < 1.0) + { + /* + Copy the ClipVelocity function from sdk2013 + (https://mxr.alliedmods.net/hl2sdk-sdk2013/source/game/shared/gamemovement.cpp#3145) + With some minor changes to make it actually work + */ + + float fBackOff = GetVectorDotProduct(vLast, vPlane); + + float change, vVel[3]; + for (int i; i < 2; i++) + { + change = vPlane[i] * fBackOff; + vVel[i] = vLast[i] - change; + } + + float fAdjust = GetVectorDotProduct(vVel, vPlane); + if (fAdjust < 0.0) + { + for (int i; i < 2; i++) + { + vVel[i] -= (vPlane[i] * fAdjust); + } + } + + vVel[2] = 0.0; + vLast[2] = 0.0; + + // Make sure the player is going down a ramp by checking if they actually will gain speed from the boost + if (GetVectorLength(vVel) > GetVectorLength(vLast)) + { + CopyVector(vVel, velocity); + player.SetLandingVelocity(velocity); + return Plugin_Changed; + } + } + } + } + return Plugin_Continue; +} + +public bool TraceRayDontHitSelf(int entity, int mask, any data) +{ + return entity != data && !(0 < entity <= MaxClients); +} + + + +// =====[ OTHER ]===== + +void FixWaterBoost(KZPlayer player, int buttons) +{ + if (GetEntProp(player.ID, Prop_Send, "m_nWaterLevel") >= 2) // WL_Waist = 2 + { + // If duck is being pressed and we're not already ducking or on ground + if (GetEntityFlags(player.ID) & (FL_DUCKING | FL_ONGROUND) == 0 + && buttons & IN_DUCK && ~gI_OldButtons[player.ID] & IN_DUCK) + { + float newOrigin[3]; + Movement_GetOrigin(player.ID, newOrigin); + newOrigin[2] += 9.0; + + TR_TraceHullFilter(newOrigin, newOrigin, view_as<float>( { -16.0, -16.0, 0.0 } ), view_as<float>( { 16.0, 16.0, 54.0 } ), MASK_PLAYERSOLID, TraceEntityFilterPlayers); + if (!TR_DidHit()) + { + TeleportEntity(player.ID, newOrigin, NULL_VECTOR, NULL_VECTOR); + } + } + } +} + +void FixDisplacementStuck(KZPlayer player) +{ + int flags = GetEntityFlags(player.ID); + bool unducked = ~flags & FL_DUCKING && gI_OldFlags[player.ID] & FL_DUCKING; + + float standingMins[] = {-16.0, -16.0, 0.0}; + float standingMaxs[] = {16.0, 16.0, 72.0}; + + if (unducked) + { + // check if we're stuck after unducking and if we're stuck then force duck + float origin[3]; + Movement_GetOrigin(player.ID, origin); + TR_TraceHullFilter(origin, origin, standingMins, standingMaxs, MASK_PLAYERSOLID, TraceEntityFilterPlayers); + + if (TR_DidHit()) + { + player.SetVelocity(gF_OldVelocity[player.ID]); + SetEntProp(player.ID, Prop_Send, "m_bDucking", true); + } + } +} + +void RemoveCrouchJumpBind(KZPlayer player, int &buttons) +{ + if (player.OnGround && buttons & IN_JUMP && !(gI_OldButtons[player.ID] & IN_JUMP) && !(gI_OldButtons[player.ID] & IN_DUCK)) + { + buttons &= ~IN_DUCK; + } +} + +void ReduceDuckSlowdown(KZPlayer player) +{ + /* + Duck speed is reduced by the game upon ducking or unducking. + The goal here is to accept that duck speed is reduced, but + stop it from being reduced further when spamming duck. + + This is done by enforcing a minimum duck speed equivalent to + the value as if the player only ducked once. When not in not + in the middle of ducking, duck speed is reset to its normal + value in effort to reduce the number of times the minimum + duck speed is enforced. This should reduce noticeable lagg. + */ + + if (!GetEntProp(player.ID, Prop_Send, "m_bDucking") + && player.DuckSpeed < DUCK_SPEED_NORMAL - EPSILON) + { + player.DuckSpeed = DUCK_SPEED_NORMAL; + } + else if (player.DuckSpeed < DUCK_SPEED_MINIMUM - EPSILON) + { + player.DuckSpeed = DUCK_SPEED_MINIMUM; + } +} |
