diff --git a/addons/sourcemod/gamedata/gokz-core.games.txt b/addons/sourcemod/gamedata/gokz-core.games.txt index 517b2b9b..add8628e 100644 --- a/addons/sourcemod/gamedata/gokz-core.games.txt +++ b/addons/sourcemod/gamedata/gokz-core.games.txt @@ -2,6 +2,17 @@ { "#default" { + "Functions" + { + "CCSGameMovement::CanUnduck" + { + "signature" "CCSGameMovement::CanUnduck" + "callconv" "thiscall" + "this" "address" + "return" "bool" + } + } + "Keys" { "IGameMovement" "GameMovement001" @@ -17,6 +28,12 @@ "windows" "@CreateInterface" "linux" "@CreateInterface" } + "CCSGameMovement::CanUnduck" + { + "library" "server" + "windows" "\x55\x8B\xEC\x81\xEC\x8C\x00\x00\x00\x57\x8B\xF9\x8B\x87\x54\x0E\x00\x00" + "linux" "\x55\x89\xE5\x57\x56\x53\x81\xEC\xFC\x00\x00\x00\x8B\x5D\x08\x8B\x83\x54\x0E\x00\x00" + } } "Offsets" diff --git a/addons/sourcemod/gamedata/gokz-quiet.games.txt b/addons/sourcemod/gamedata/gokz-quiet.games.txt new file mode 100644 index 00000000..06f52203 --- /dev/null +++ b/addons/sourcemod/gamedata/gokz-quiet.games.txt @@ -0,0 +1,24 @@ +"Games" +{ + "csgo" + { + "Signatures" + { + "CGameClient::SendSound" + { + "library" "engine" + "windows" "\x55\x8B\xEC\x51\x56\x8B\xF1\x8B\x46\x04" + "linux" "\x55\x89\xE5\x57\x56\x53\x83\xEC\x2C\x8B\x5D\x08\x8B\x75\x10\x8B\x03" + } + } + + "Offsets" + { + "CBaseClient::GetPlayerSlot" + { + "windows" "16" + "linux" "17" + } + } + } +} diff --git a/addons/sourcemod/gamedata/gokz-replays.games.txt b/addons/sourcemod/gamedata/gokz-replays.games.txt index 7465f702..23af8b08 100644 --- a/addons/sourcemod/gamedata/gokz-replays.games.txt +++ b/addons/sourcemod/gamedata/gokz-replays.games.txt @@ -2,6 +2,13 @@ { "csgo" { + "Addresses" + { + "BotDuck" + { + "signature" "BotDuckSig" + } + } "Functions" { "CCSGameRules::TeamFull" @@ -26,6 +33,19 @@ "windows" "\x55\x8B\xEC\x56\x8B\xF1\xE8\x2A\x2A\x2A\x2A\x8B\x45\x08\x83\xE8\x01" "linux" "\x55\x89\xE5\x56\x53\x8B\x5D\x08\x8B\x75\x0C\x80\xBB\xAD\x0E\x00\x00\x00" } + "BotDuckSig" + { + "windows" "\x8B\x86\x54\x0E\x00\x00\x8B\xCE" + "linux" "\x8B\x83\x54\x0E\x00\x00\x83\xEC\x0C" + } + } + "Offsets" + { + "BotDuckPatchLength" + { + "windows" "23" + "linux" "38" + } } } } diff --git a/addons/sourcemod/gamedata/gokz-tpanglefix.games.txt b/addons/sourcemod/gamedata/gokz-tpanglefix.games.txt new file mode 100644 index 00000000..0aef1863 --- /dev/null +++ b/addons/sourcemod/gamedata/gokz-tpanglefix.games.txt @@ -0,0 +1,55 @@ +"Games" +{ + "csgo" + { + "Functions" + { + "CGameClient::WriteViewAngleUpdate" + { + "signature" "CGameClient::WriteViewAngleUpdate" + "callconv" "thiscall" + "this" "address" + "return" "void" + } + } + "Addresses" + { + "WriteViewAngleUpdate" + { + "windows" + { + "signature" "CGameClient::WriteViewAngleUpdate" + } + "linux" + { + "signature" "CGameClient::WriteViewAngleUpdate" + } + } + } + + "Offsets" + { + "WriteViewAngleUpdateReliableOffset" + { + "windows" "363" + "linux" "290" + } + "ClientIndexOffset" + { + "linux" "116" + "windows" "112" + } + } + + "Signatures" + { + //A few functions after "%c00000000000000" string + "CGameClient::WriteViewAngleUpdate" + { + "library" "engine" + "windows" "\x55\x8B\xEC\x83\xEC\x40\x56\x57" + "linux" "\x55\x89\xE5\x57\x56\x53\x83\xEC\x4C\x8B\x5D\x08\x8B\x03" + } + } + } +} diff --git a/addons/sourcemod/scripting/gokz-core.sp b/addons/sourcemod/scripting/gokz-core.sp index e0a04ab3..897403f4 100644 --- a/addons/sourcemod/scripting/gokz-core.sp +++ b/addons/sourcemod/scripting/gokz-core.sp @@ -182,6 +182,7 @@ public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3 { gI_CmdNum[client] = cmdnum; gI_TickCount[client] = tickcount; + OnPlayerRunCmd_Triggerfix(client); OnPlayerRunCmd_MapTriggers(client, buttons); OnPlayerRunCmd_Turnbinds(client, buttons, tickcount, angles); return Plugin_Continue; @@ -271,6 +272,11 @@ public void OnCSPlayerSpawnPost(int client) } } +public void OnClientPreThinkPost(int client) +{ + OnClientPreThinkPost_UseButtons(client); +} + public void Movement_OnChangeMovetype(int client, MoveType oldMovetype, MoveType newMovetype) { OnChangeMovetype_Timer(client, newMovetype); @@ -510,6 +516,7 @@ static void HookClientEvents(int client) DHookEntity(gH_DHooks_OnTeleport, true, client); DHookEntity(gH_DHooks_SetModel, true, client); SDKHook(client, SDKHook_SpawnPost, OnCSPlayerSpawnPost); + SDKHook(client, SDKHook_PreThinkPost, OnClientPreThinkPost); } static void UpdateTrackingVariables(int client, int cmdnum, int buttons) diff --git a/addons/sourcemod/scripting/gokz-core/misc.sp b/addons/sourcemod/scripting/gokz-core/misc.sp index 47e68c0f..a1178803 100644 --- a/addons/sourcemod/scripting/gokz-core/misc.sp +++ b/addons/sourcemod/scripting/gokz-core/misc.sp @@ -605,6 +605,176 @@ void OnMapStart_FixMissingSpawns() } } +// =====[ BUTTONS ]===== + +void OnClientPreThinkPost_UseButtons(int client) +{ + if (GOKZ_GetCoreOption(client, Option_ButtonThroughPlayers) == ButtonThroughPlayers_Enabled && GetEntProp(client, Prop_Data, "m_afButtonPressed") & IN_USE) + { + int entity = FindUseEntity(client); + if (entity != -1) + { + AcceptEntityInput(entity, "Use", client, client, 1); + } + } +} + +static int FindUseEntity(int client) +{ + float fwd[3]; + float angles[3]; + GetClientEyeAngles(client, angles); + GetAngleVectors(angles, fwd, NULL_VECTOR, NULL_VECTOR); + + Handle trace; + + float eyeOrigin[3]; + GetClientEyePosition(client, eyeOrigin); + int useableContents = (MASK_NPCSOLID_BRUSHONLY | MASK_OPAQUE_AND_NPCS) & ~CONTENTS_OPAQUE; + + float endpos[3]; + + // Check if +use trace collide with a player first, so we don't activate any button twice + trace = TR_TraceRayFilterEx(eyeOrigin, angles, useableContents, RayType_Infinite, TRFOtherPlayersOnly, client); + if (TR_DidHit(trace)) + { + int ent = TR_GetEntityIndex(trace); + if (ent < 1 || ent > MaxClients) + { + return -1; + } + // Search for a button behind it. + trace = TR_TraceRayFilterEx(eyeOrigin, angles, useableContents, RayType_Infinite, TraceEntityFilterPlayers); + if (TR_DidHit(trace)) + { + char buffer[20]; + ent = TR_GetEntityIndex(trace); + // Make sure that it is a button, and this button activates when pressed. + // If it is not a button, check its parent to see if it is a button. + bool isButton; + while (ent != -1) + { + GetEntityClassname(ent, buffer, sizeof(buffer)); + if (StrEqual("func_button", buffer, false) && GetEntProp(ent, Prop_Data, "m_spawnflags") & SF_BUTTON_USE_ACTIVATES) + { + isButton = true; + break; + } + else + { + ent = GetEntPropEnt(ent, Prop_Data, "m_hMoveParent"); + } + } + if (isButton) + { + TR_GetEndPosition(endpos, trace); + float delta[3]; + for (int i = 0; i < 2; i++) + { + delta[i] = endpos[i] - eyeOrigin[i]; + } + // Z distance is treated differently. + float m_vecMins[3]; + float m_vecMaxs[3]; + float m_vecOrigin[3]; + GetEntPropVector(ent, Prop_Send, "m_vecOrigin", m_vecOrigin); + GetEntPropVector(ent, Prop_Send, "m_vecMins", m_vecMins); + GetEntPropVector(ent, Prop_Send, "m_vecMaxs", m_vecMaxs); + + delta[2] = IntervalDistance(endpos[2], m_vecOrigin[2] + m_vecMins[2], m_vecOrigin[2] + m_vecMaxs[2]); + if (GetVectorLength(delta) < 80.0) + { + return ent; + } + } + } + } + + int nearestEntity; + float nearestPoint[3]; + float nearestDist = FLOAT_MAX; + ArrayList entities = new ArrayList(); + TR_EnumerateEntitiesSphere(eyeOrigin, 80.0, 1<<5, AddEntities, entities); + for (int i = 0; i < entities.Length; i++) + { + char buffer[64]; + int ent = entities.Get(i); + GetEntityClassname(ent, buffer, sizeof(buffer)); + // Check if the entity is a button and it is pressable. + if (StrEqual("func_button", buffer, false) && GetEntProp(ent, Prop_Data, "m_spawnflags") & SF_BUTTON_USE_ACTIVATES) + { + float point[3]; + CalcNearestPoint(ent, eyeOrigin, point); + + float dir[3]; + for (int j = 0; j < 3; j++) + { + dir[j] = point[j] - eyeOrigin[2]; + } + // Check the maximum angle the player can be away from the button. + float minimumDot = GetEntPropFloat(ent, Prop_Send, "m_flUseLookAtAngle"); + NormalizeVector(dir, dir); + float dot = GetVectorDotProduct(dir, fwd); + if (dot < minimumDot) + { + continue; + } + + float dist = CalcDistanceToLine(point, eyeOrigin, fwd); + if (dist < nearestDist) + { + trace = TR_TraceRayFilterEx(eyeOrigin, point, useableContents, RayType_EndPoint, TraceEntityFilterPlayers); + if (TR_GetFraction(trace) == 1.0 || TR_GetEntityIndex(trace) == ent) + { + CopyVector(point, nearestPoint); + nearestDist = dist; + nearestEntity = ent; + } + } + } + } + // We found the closest button, but we still need to check if there is a player in front of it or not. + // In the case that there isn't a player inbetween, we don't return the entity index, because that button will be pressed by the game function anyway. + // If there is, we will press two buttons at once, the "right" button found by this function and the "wrong" button that we only happen to press because + // there is a player in the way. + + trace = TR_TraceRayFilterEx(eyeOrigin, nearestPoint, useableContents, RayType_EndPoint, TRFOtherPlayersOnly); + if (TR_DidHit(trace)) + { + return nearestEntity; + } + return -1; +} + +public bool AddEntities(int entity, ArrayList entities) +{ + entities.Push(entity); + return true; +} + +static float IntervalDistance(float x, float x0, float x1) +{ + if (x0 > x1) + { + float tmp = x0; + x0 = x1; + x1 = tmp; + } + if (x < x0) + { + return x0 - x; + } + else if (x > x1) + { + return x - x1; + } + return 0.0; +} +// TraceRay filter for other players exclusively. +public bool TRFOtherPlayersOnly(int entity, int contentmask, int client) +{ + return (0 < entity <= MaxClients) && (entity != client); +} // =====[ SAFE MODE ]===== @@ -630,4 +800,4 @@ void ToggleProSafeGuard(int client) { GOKZ_SetCoreOption(client, Option_Safeguard, Safeguard_EnabledPRO); } -} \ No newline at end of file +} diff --git a/addons/sourcemod/scripting/gokz-core/teleports.sp b/addons/sourcemod/scripting/gokz-core/teleports.sp index 36b3479d..764fc6ee 100644 --- a/addons/sourcemod/scripting/gokz-core/teleports.sp +++ b/addons/sourcemod/scripting/gokz-core/teleports.sp @@ -892,7 +892,11 @@ static void CheckpointTeleportDo(int client) checkpoints[client].GetArray(checkpointIndex[client], cp); TeleportDo(client, cp.origin, cp.angles); - + if (cp.groundEnt != INVALID_ENT_REFERENCE) + { + SetEntPropEnt(client, Prop_Data, "m_hGroundEntity", cp.groundEnt); + SetEntityFlags(client, GetEntityFlags(client) | FL_ONGROUND); + } // Handle ladder stuff if (cp.onLadder) { diff --git a/addons/sourcemod/scripting/gokz-core/triggerfix.sp b/addons/sourcemod/scripting/gokz-core/triggerfix.sp index 4f3f53a8..424928d9 100644 --- a/addons/sourcemod/scripting/gokz-core/triggerfix.sp +++ b/addons/sourcemod/scripting/gokz-core/triggerfix.sp @@ -12,7 +12,7 @@ static int processMovementTicks[MAXPLAYERS+1]; static float playerFrameTime[MAXPLAYERS+1]; static bool touchingTrigger[MAXPLAYERS+1][2048]; -static bool triggerTouchFired[MAXPLAYERS+1][2048]; +static int triggerTouchFired[MAXPLAYERS+1][2048]; static int lastGroundEnt[MAXPLAYERS + 1]; static bool duckedLastTick[MAXPLAYERS + 1]; static bool mapTeleportedSequentialTicks[MAXPLAYERS+1]; @@ -171,7 +171,7 @@ public void OnClientConnected_Triggerfix(int client) for (int i = 0; i < sizeof(touchingTrigger[]); i++) { touchingTrigger[client][i] = false; - triggerTouchFired[client][i] = false; + triggerTouchFired[client][i] = 0; } } @@ -201,12 +201,23 @@ public void OnGameFrame_Triggerfix() // so this should not be a big problem. for (int trigger = 0; trigger < sizeof(triggerTouchFired[]); trigger++) { - triggerTouchFired[client][trigger] = false; + triggerTouchFired[client][trigger] = 0; } } } } +void OnPlayerRunCmd_Triggerfix(int client) +{ + // Reset the Touch tracking. + // While this is mostly unnecessary, it can also happen that the server runs multiple ticks of player movement at once, + // therefore the triggers need to be checked again. + for (int trigger = 0; trigger < sizeof(triggerTouchFired[]); trigger++) + { + triggerTouchFired[client][trigger] = 0; + } +} + static void Event_PlayerJump(Event event, const char[] name, bool dontBroadcast) { int client = GetClientOfUserId(event.GetInt("userid")); @@ -246,7 +257,7 @@ static Action Hook_TriggerTouch(int entity, int other) { if (1 <= other <= MaxClients) { - triggerTouchFired[other][entity] = true; + triggerTouchFired[other][entity]++; } return Plugin_Continue; } @@ -335,13 +346,14 @@ static bool DoTriggerFix(int client, bool filterFix = false) // Completely ignore push triggers. continue; } - if (filterFix && SDKCall(passesTriggerFilters, trigger, client)) + if (filterFix && SDKCall(passesTriggerFilters, trigger, client) && triggerTouchFired[client][trigger] < GOKZ_MAX_RETOUCH_TRIGGER_COUNT) { // MarkEntitiesAsTouching always fires the Touch function even if it was already fired this tick. SDKCall(markEntitiesAsTouching, serverGameEnts, client, trigger); // Player properties might be changed right after this so it will need to be triggered again. - triggerTouchFired[client][trigger] = false; + // Triggers changing this filter will loop onto itself infintely so we need to avoid that. + triggerTouchFired[client][trigger]++; didSomething = true; } else if (!triggerTouchFired[client][trigger]) @@ -349,6 +361,7 @@ static bool DoTriggerFix(int client, bool filterFix = false) // If the player is still touching the trigger on this tick, and Touch was not called for whatever reason // in the last tick, we make sure that it is called now. SDKCall(markEntitiesAsTouching, serverGameEnts, client, trigger); + triggerTouchFired[client][trigger]++; didSomething = true; } } diff --git a/addons/sourcemod/scripting/gokz-hud.sp b/addons/sourcemod/scripting/gokz-hud.sp index 8d1c80a3..e9db12a9 100644 --- a/addons/sourcemod/scripting/gokz-hud.sp +++ b/addons/sourcemod/scripting/gokz-hud.sp @@ -31,10 +31,12 @@ public Plugin myinfo = bool gB_GOKZRacing; bool gB_GOKZReplays; bool gB_MenuShowing[MAXPLAYERS + 1]; +int gI_ObserverTarget[MAXPLAYERS + 1]; bool gB_JBTakeoff[MAXPLAYERS + 1]; bool gB_FastUpdateRate[MAXPLAYERS + 1]; int gI_DynamicMenu[MAXPLAYERS + 1]; +#include "gokz-hud/spectate_text.sp" #include "gokz-hud/commands.sp" #include "gokz-hud/hide_weapon.sp" #include "gokz-hud/info_panel.sp" @@ -51,8 +53,8 @@ int gI_DynamicMenu[MAXPLAYERS + 1]; public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) { - RegPluginLibrary("gokz-hud"); CreateNatives(); + RegPluginLibrary("gokz-hud"); return APLRes_Success; } @@ -64,11 +66,17 @@ public void OnPluginStart() HookEvents(); RegisterCommands(); + UpdateSpecList(); OnPluginStart_RacingText(); OnPluginStart_SpeedText(); OnPluginStart_TimerText(); } +public void OnPluginEnd() +{ + OnPluginEnd_Menu(); +} + public void OnAllPluginsLoaded() { if (LibraryExists("updater")) @@ -114,6 +122,11 @@ public void OnLibraryRemoved(const char[] name) // =====[ CLIENT EVENTS ]===== +public void OnClientDisconnect(int client) +{ + gI_ObserverTarget[client] = -1; +} + public void OnClientPutInServer(int client) { SDKHook(client, SDKHook_PostThinkPost, OnPlayerPostThinkPost); @@ -263,6 +276,15 @@ public void GOKZ_OnOptionsLoaded(int client) // =====[ OTHER EVENTS ]===== +public void OnGameFrame() +{ + // Cache the spectator list every few ticks. + if (GetGameTickCount() % 4 == 0) + { + UpdateSpecList(); + } +} + public void GOKZ_OnOptionsMenuCreated(TopMenu topMenu) { OnOptionsMenuCreated_OptionsMenu(topMenu); @@ -308,4 +330,5 @@ static void SetHUDInfo(KZPlayer player, HUDInfo info, int cmdnum) info.TakeoffSpeed = player.GOKZTakeoffSpeed; info.IsTakeoff = Movement_GetTakeoffCmdNum(player.ID) == cmdnum; info.Buttons = player.Buttons; + info.CurrentTeleport = player.TeleportCount; } \ No newline at end of file diff --git a/addons/sourcemod/scripting/gokz-hud/info_panel.sp b/addons/sourcemod/scripting/gokz-hud/info_panel.sp index 83606552..35634047 100644 --- a/addons/sourcemod/scripting/gokz-hud/info_panel.sp +++ b/addons/sourcemod/scripting/gokz-hud/info_panel.sp @@ -27,14 +27,7 @@ bool IsDrawingInfoPanel(int client) void OnPlayerRunCmdPost_InfoPanel(int client, int cmdnum, HUDInfo info) { - int updateSpeed = 10; - if (gB_FastUpdateRate[client]) - { - // The hint text panel update speed depends on the client ping. - // To optimize resource usage, we scale the update speed with it. - // The fastest speed the client can get is around once every 2 ticks. - updateSpeed = IntMax(1, RoundToFloor(GetClientAvgLatency(client, NetFlow_Outgoing) / GetTickInterval())); - } + int updateSpeed = gB_FastUpdateRate[client] ? 1 : 10; if (cmdnum % updateSpeed == 0 || info.IsTakeoff) { UpdateInfoPanel(client, info); @@ -55,8 +48,12 @@ static void UpdateInfoPanel(int client, HUDInfo info) { return; } - - PrintCSGOHUDText(player.ID, GetInfoPanel(player, info)); + char infoPanelText[512]; + FormatEx(infoPanelText, sizeof(infoPanelText), GetInfoPanel(player, info)); + if (infoPanelText[0] != '\0') + { + PrintCSGOHUDText(player.ID, infoPanelText); + } } static bool NothingEnabledInInfoPanel(KZPlayer player) @@ -70,16 +67,40 @@ static bool NothingEnabledInInfoPanel(KZPlayer player) static char[] GetInfoPanel(KZPlayer player, HUDInfo info) { - char infoPanelText[320]; + char infoPanelText[512]; FormatEx(infoPanelText, sizeof(infoPanelText), - "%s%s%s", + "%s%s%s%s", + GetSpectatorString(player, info), GetTimeString(player, info), GetSpeedString(player, info), GetKeysString(player, info)); + if (infoPanelText[0] == '\0') + { + return infoPanelText; + } + else + { + Format(infoPanelText, sizeof(infoPanelText), "%s", infoPanelText); + } TrimString(infoPanelText); return infoPanelText; } +static char[] GetSpectatorString(KZPlayer player, HUDInfo info) +{ + char spectatorString[255]; + if (player.SpecListPosition != SpecListPosition_InfoPanel || player.ShowSpectators == ShowSpecs_Disabled) + { + return spectatorString; + } + // Only return something if the player is alive or observing someone else + if (player.Alive || player.ObserverTarget != -1) + { + FormatEx(spectatorString, sizeof(spectatorString), "%s", FormatSpectatorTextForInfoPanel(player, KZPlayer(info.ID))); + } + return spectatorString; +} + static char[] GetTimeString(KZPlayer player, HUDInfo info) { char timeString[128]; @@ -273,7 +294,7 @@ void PrintCSGOHUDText(int client, const char[] format) buff[sizeof(buff) - 1] = '\0'; - Protobuf pb = view_as(StartMessageOne("TextMsg", client, USERMSG_RELIABLE | USERMSG_BLOCKHOOKS)); + Protobuf pb = view_as(StartMessageOne("TextMsg", client, USERMSG_BLOCKHOOKS)); pb.SetInt("msg_dst", 4); pb.AddString("params", "#SFUI_ContractKillStart"); pb.AddString("params", buff); diff --git a/addons/sourcemod/scripting/gokz-hud/menu.sp b/addons/sourcemod/scripting/gokz-hud/menu.sp index fe1df43b..4b7259e7 100644 --- a/addons/sourcemod/scripting/gokz-hud/menu.sp +++ b/addons/sourcemod/scripting/gokz-hud/menu.sp @@ -82,4 +82,15 @@ void OnStartPositionSet_Menu(int client) { CancelGOKZHUDMenu(client); } -} \ No newline at end of file +} + +void OnPluginEnd_Menu() +{ + for (int client = 1; client <= MaxClients; client++) + { + if (IsValidClient(client)) + { + CancelGOKZHUDMenu(client); + } + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/gokz-hud/natives.sp b/addons/sourcemod/scripting/gokz-hud/natives.sp index f169689b..bb877e23 100644 --- a/addons/sourcemod/scripting/gokz-hud/natives.sp +++ b/addons/sourcemod/scripting/gokz-hud/natives.sp @@ -1,10 +1,33 @@ void CreateNatives() { CreateNative("GOKZ_HUD_ForceUpdateTPMenu", Native_ForceUpdateTPMenu); + CreateNative("GOKZ_HUD_GetMenuShowing", Native_GetMenuShowing); + CreateNative("GOKZ_HUD_SetMenuShowing", Native_SetMenuShowing); + CreateNative("GOKZ_HUD_GetMenuSpectatorText", Native_GetSpectatorText); } public int Native_ForceUpdateTPMenu(Handle plugin, int numParams) { SetForceUpdateTPMenu(GetNativeCell(1)); return 0; +} + +public int Native_GetMenuShowing(Handle plugin, int numParams) +{ + return view_as(gB_MenuShowing[GetNativeCell(1)]); +} + +public int Native_SetMenuShowing(Handle plugin, int numParams) +{ + gB_MenuShowing[GetNativeCell(1)] = view_as(GetNativeCell(2)); + return 0; +} + +public int Native_GetSpectatorText(Handle plugin, int numParams) +{ + HUDInfo info; + GetNativeArray(2, info, sizeof(HUDInfo)); + KZPlayer player = KZPlayer(GetNativeCell(1)); + FormatNativeString(3, 0, 0, GetNativeCell(4), _, "", FormatSpectatorTextForMenu(player, info)); + return 0; } \ No newline at end of file diff --git a/addons/sourcemod/scripting/gokz-hud/options.sp b/addons/sourcemod/scripting/gokz-hud/options.sp index 127d8221..84bbf2b3 100644 --- a/addons/sourcemod/scripting/gokz-hud/options.sp +++ b/addons/sourcemod/scripting/gokz-hud/options.sp @@ -154,6 +154,20 @@ static void PrintOptionChangeMessage(int client, HUDOption option, any newValue) } } } + case HUDOption_SpecListPosition: + { + switch (newValue) + { + case SpecListPosition_InfoPanel: + { + GOKZ_PrintToChat(client, true, "%t", "Option - Spectator List Position - Info Panel"); + } + case SpecListPosition_TPMenu: + { + GOKZ_PrintToChat(client, true, "%t", "Option - Spectator List Position - TP Menu"); + } + } + } case HUDOption_DynamicMenu: { switch (newValue) diff --git a/addons/sourcemod/scripting/gokz-hud/options_menu.sp b/addons/sourcemod/scripting/gokz-hud/options_menu.sp index 4feae327..705df27f 100644 --- a/addons/sourcemod/scripting/gokz-hud/options_menu.sp +++ b/addons/sourcemod/scripting/gokz-hud/options_menu.sp @@ -138,6 +138,12 @@ public void TopMenuHandler_HUD(TopMenu topmenu, TopMenuAction action, TopMenuObj gC_HUDOptionPhrases[option], param, gC_ShowSpecsPhrases[GOKZ_HUD_GetOption(param, option)], param); } + case HUDOption_SpecListPosition: + { + FormatEx(buffer, maxlength, "%T - %T", + gC_HUDOptionPhrases[option], param, + gC_SpecListPositionPhrases[GOKZ_HUD_GetOption(param, option)], param); + } case HUDOption_DynamicMenu: { FormatEx(buffer, maxlength, "%T - %T", diff --git a/addons/sourcemod/scripting/gokz-hud/spectate_text.sp b/addons/sourcemod/scripting/gokz-hud/spectate_text.sp new file mode 100644 index 00000000..9700c38f --- /dev/null +++ b/addons/sourcemod/scripting/gokz-hud/spectate_text.sp @@ -0,0 +1,119 @@ +/* + Responsible for spectator list on the HUD. +*/ + +#define SPECATATOR_LIST_MAX_COUNT 5 + +// =====[ PUBLIC ]===== + +char[] FormatSpectatorTextForMenu(KZPlayer player, HUDInfo info) +{ + int specCount; + char spectatorTextString[224]; + if (player.GetHUDOption(HUDOption_ShowSpectators) >= ShowSpecs_Number) + { + for (int i = 1; i <= MaxClients; i++) + { + if (gI_ObserverTarget[i] == info.ID) + { + specCount++; + if (player.GetHUDOption(HUDOption_ShowSpectators) == ShowSpecs_Full) + { + char buffer[64]; + if (specCount < SPECATATOR_LIST_MAX_COUNT) + { + GetClientName(i, buffer, sizeof(buffer)); + Format(spectatorTextString, sizeof(spectatorTextString), "%s\n%s", spectatorTextString, buffer); + } + else if (specCount == SPECATATOR_LIST_MAX_COUNT) + { + StrCat(spectatorTextString, sizeof(spectatorTextString), "\n..."); + } + } + } + } + if (specCount > 0) + { + if (player.GetHUDOption(HUDOption_ShowSpectators) == ShowSpecs_Full) + { + Format(spectatorTextString, sizeof(spectatorTextString), "%t\n ", "Spectator List - Menu (Full)", specCount, spectatorTextString); + } + else + { + Format(spectatorTextString, sizeof(spectatorTextString), "%t\n ", "Spectator List - Menu (Number)", specCount); + } + } + else + { + FormatEx(spectatorTextString, sizeof(spectatorTextString), ""); + } + } + return spectatorTextString; +} + +char[] FormatSpectatorTextForInfoPanel(KZPlayer player, KZPlayer targetPlayer) +{ + int specCount; + char spectatorTextString[160]; + if (player.GetHUDOption(HUDOption_ShowSpectators) >= ShowSpecs_Number) + { + for (int i = 1; i <= MaxClients; i++) + { + if (gI_ObserverTarget[i] == targetPlayer.ID) + { + specCount++; + if (player.GetHUDOption(HUDOption_ShowSpectators) == ShowSpecs_Full) + { + char buffer[64]; + if (specCount < SPECATATOR_LIST_MAX_COUNT) + { + GetClientName(i, buffer, sizeof(buffer)); + if (specCount == 1) + { + Format(spectatorTextString, sizeof(spectatorTextString), "%s", buffer); + } + else + { + Format(spectatorTextString, sizeof(spectatorTextString), "%s, %s", spectatorTextString, buffer); + } + } + else if (specCount == SPECATATOR_LIST_MAX_COUNT) + { + Format(spectatorTextString, sizeof(spectatorTextString), " ..."); + } + } + } + } + if (specCount > 0) + { + if (player.GetHUDOption(HUDOption_ShowSpectators) == ShowSpecs_Full) + { + Format(spectatorTextString, sizeof(spectatorTextString), "%t\n", "Spectator List - Info Panel (Full)", specCount, spectatorTextString); + } + else + { + Format(spectatorTextString, sizeof(spectatorTextString), "%t\n", "Spectator List - Info Panel (Number)", specCount); + } + } + else + { + FormatEx(spectatorTextString, sizeof(spectatorTextString), ""); + } + } + return spectatorTextString; +} + +void UpdateSpecList() +{ + for (int client = 1; client <= MaxClients; client++) + { + if (IsValidClient(client) && !IsFakeClient(client)) + { + gI_ObserverTarget[client] = GetObserverTarget(client); + } + else + { + gI_ObserverTarget[client] = -1; + } + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/gokz-hud/timer_text.sp b/addons/sourcemod/scripting/gokz-hud/timer_text.sp index a59901ad..a5fd17b8 100644 --- a/addons/sourcemod/scripting/gokz-hud/timer_text.sp +++ b/addons/sourcemod/scripting/gokz-hud/timer_text.sp @@ -16,18 +16,25 @@ static Handle timerHudSynchronizer; char[] FormatTimerTextForMenu(KZPlayer player, HUDInfo info) { char timerTextString[32]; - if (player.GetHUDOption(HUDOption_TimerType) == TimerType_Enabled) + if (info.TimerRunning) { - FormatEx(timerTextString, sizeof(timerTextString), - "%s %s", - gC_TimeTypeNames[info.TimeType], - GOKZ_HUD_FormatTime(player.ID, info.Time)); - } - else - { - FormatEx(timerTextString, sizeof(timerTextString), - "%s", - GOKZ_HUD_FormatTime(player.ID, info.Time)); + if (player.GetHUDOption(HUDOption_TimerType) == TimerType_Enabled) + { + FormatEx(timerTextString, sizeof(timerTextString), + "%s %s", + gC_TimeTypeNames[info.TimeType], + GOKZ_HUD_FormatTime(player.ID, info.Time)); + } + else + { + FormatEx(timerTextString, sizeof(timerTextString), + "%s", + GOKZ_HUD_FormatTime(player.ID, info.Time)); + } + if (info.Paused) + { + Format(timerTextString, sizeof(timerTextString), "%s (%T)", timerTextString, "Info Panel Text - PAUSED", player.ID); + } } return timerTextString; } @@ -68,16 +75,6 @@ void OnTimerStopped_TimerText(int client) ClearTimerText(client); } -public int PanelHandler_Menu(Menu menu, MenuAction action, int param1, int param2) -{ - if (action == MenuAction_Cancel) - { - gB_MenuShowing[param1] = false; - } - return 0; -} - - // =====[ PRIVATE ]===== @@ -108,36 +105,7 @@ static void ShowTimerText(KZPlayer player, HUDInfo info) } return; } - - if (player.TimerText == TimerText_TPMenu) - { - // If there is no menu showing, or if the TP menu is currently showing; - // and if player is spectating, or is alive with TP menu disabled and not paused - - // Note that we don't mind if player we're spectating is paused etc. as there are too - // many variables to track whether we need to update the timer text for the spectator. - - if ((gB_MenuShowing[player.ID] || GetClientMenu(player.ID) == MenuSource_None) - && (player.ID != info.ID || player.TPMenu == TPMenu_Disabled && !player.Paused)) - { - // Use a Panel if want to show ONLY timer text (not TP menu) - // as it doesn't seem to be possible to display a Menu with no items. - Panel panel = new Panel(null); - panel.SetTitle(FormatTimerTextForMenu(player, info)); - int observerTarget = GetObserverTarget(player.ID); - if (observerTarget != -1 && IsFakeClient(observerTarget) - && info.TimeType == TimeType_Nub) - { - char text[32]; - FormatEx(text, sizeof(text), "%t", "TP Menu - Spectator Teleports", info.CurrentTeleport); - panel.DrawItem(text, ITEMDRAW_RAWLINE); - } - panel.Send(player.ID, PanelHandler_Menu, MENU_TIME_FOREVER); - delete panel; - gB_MenuShowing[player.ID] = true; - } - } - else if (player.TimerText == TimerText_Top || player.TimerText == TimerText_Bottom) + if (player.TimerText == TimerText_Top || player.TimerText == TimerText_Bottom) { int colour[4]; // RGBA if (player.GetHUDOption(HUDOption_TimerType) == TimerType_Enabled) diff --git a/addons/sourcemod/scripting/gokz-hud/tp_menu.sp b/addons/sourcemod/scripting/gokz-hud/tp_menu.sp index ab72a460..bb78f1e4 100644 --- a/addons/sourcemod/scripting/gokz-hud/tp_menu.sp +++ b/addons/sourcemod/scripting/gokz-hud/tp_menu.sp @@ -35,6 +35,15 @@ void OnPlayerRunCmdPost_TPMenu(int client, int cmdnum, HUDInfo info) } } +public int PanelHandler_Menu(Menu menu, MenuAction action, int param1, int param2) +{ + if (action == MenuAction_Cancel) + { + gB_MenuShowing[param1] = false; + } + return 0; +} + public int MenuHandler_TPMenu(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) @@ -97,7 +106,7 @@ static void UpdateTPMenu(int client, HUDInfo info) { KZPlayer player = KZPlayer(client); - if (player.Fake || !player.Alive || player.TPMenu == TPMenu_Disabled) + if (player.Fake) { return; } @@ -111,13 +120,36 @@ static void UpdateTPMenu(int client, HUDInfo info) || player.CanPause != oldCanPause[client] || player.CanResume != oldCanResume[client]; - // If there is no menu showing, or if the TP menu is currently showing with timer text - if (GetClientMenu(client) == MenuSource_None - || gB_MenuShowing[player.ID] && GetClientAvgLoss(player.ID, NetFlow_Both) > EPSILON - || gB_MenuShowing[player.ID] && player.TimerRunning && !player.Paused && player.TimerText == TimerText_TPMenu - || gB_MenuShowing[player.ID] && force) + + if (player.Alive) { - ShowTPMenu(player, info); + if (player.TPMenu != TPMenu_Disabled) + { + if (GetClientMenu(client) == MenuSource_None + || gB_MenuShowing[player.ID] && GetClientAvgLoss(player.ID, NetFlow_Both) > EPSILON + || gB_MenuShowing[player.ID] && player.TimerRunning && !player.Paused && player.TimerText == TimerText_TPMenu + || gB_MenuShowing[player.ID] && force) + { + ShowTPMenu(player, info); + } + } + else + { + // There is no need to update this very often as there's no menu selection to be done here. + if (GetClientMenu(client) == MenuSource_None + || gB_MenuShowing[player.ID] && player.TimerRunning && !player.Paused && player.TimerText == TimerText_TPMenu) + { + ShowPanel(player, info); + } + } + } + else if (player.ObserverTarget != -1) // If the player is spectating someone else + { + // Check if the replay plugin wants to display the replay control menu. + if (!(IsFakeClient(player.ObserverTarget) && gB_GOKZReplays && GOKZ_RP_UpdateReplayControlMenu(client))) + { + ShowPanel(player, info); + } } oldCanMakeCP[client] = player.CanMakeCheckpoint; @@ -130,6 +162,42 @@ static void UpdateTPMenu(int client, HUDInfo info) forceRefresh[client] = false; } +static void ShowPanel(KZPlayer player, HUDInfo info) +{ + char panelTitle[256]; + // Spectator List + if (player.ShowSpectators >= ShowSpecs_Number && player.SpecListPosition == SpecListPosition_TPMenu) + { + Format(panelTitle, sizeof(panelTitle), "%s", FormatSpectatorTextForMenu(player, info)); + } + // Timer panel + if (player.TimerText == TimerText_TPMenu && info.TimerRunning) + { + if (panelTitle[0] != '\0') + { + Format(panelTitle, sizeof(panelTitle), "%s \n%s", panelTitle, FormatTimerTextForMenu(player, info)); + } + else + { + Format(panelTitle, sizeof(panelTitle), "%s", FormatTimerTextForMenu(player, info)); + } + if (info.TimeType == TimeType_Nub && info.CurrentTeleport != 0) + { + Format(panelTitle, sizeof(panelTitle), "%s\n%t", panelTitle, "TP Menu - Spectator Teleports", info.CurrentTeleport); + } + } + + if (panelTitle[0] != '\0' && GetClientMenu(player.ID) == MenuSource_None || gB_MenuShowing[player.ID]) + { + Panel panel = new Panel(null); + panel.SetTitle(panelTitle); + panel.Send(player.ID, PanelHandler_Menu, MENU_TIME_FOREVER); + + delete panel; + gB_MenuShowing[player.ID] = true; + } +} + static void ShowTPMenu(KZPlayer player, HUDInfo info) { Menu menu = new Menu(MenuHandler_TPMenu); @@ -144,25 +212,25 @@ static void ShowTPMenu(KZPlayer player, HUDInfo info) static void TPMenuSetTitle(KZPlayer player, Menu menu, HUDInfo info) { - switch (player.ShowSpectators) + char title[256]; + if (player.ShowSpectators >= ShowSpecs_Number && player.SpecListPosition == SpecListPosition_TPMenu) + { + Format(title, sizeof(title), "%s", FormatSpectatorTextForMenu(player, info)); + } + if (player.TimerRunning && player.TimerText == TimerText_TPMenu) { - case ShowSpecs_Number: + if (title[0] != '\0') { - menu.SetTitle("%T\n \n", "TP Menu - Spectators - Number", player.ID, GetNumSpectators(player)); + Format(title, sizeof(title), "%s \n%s", title, FormatTimerTextForMenu(player, info)); } - case ShowSpecs_Full: + else { - char display[512]; - FormatSpectatorNames(player, display); - menu.SetTitle("%T\n \n", "TP Menu - Spectators - Full", player.ID, GetNumSpectators(player), display); + Format(title, sizeof(title), "%s", FormatTimerTextForMenu(player, info)); } } - - if (player.TimerRunning && player.TimerText == TimerText_TPMenu) + if (title[0] != '\0') { - char display[512]; - menu.GetTitle(display, sizeof(display)); - menu.SetTitle("%s%s", display, FormatTimerTextForMenu(player,info)); + menu.SetTitle(title); } } @@ -344,60 +412,4 @@ static void TPMenuAddItemStart(KZPlayer player, Menu menu) FormatEx(display, sizeof(display), "%T", "TP Menu - Start", player.ID); menu.AddItem(ITEM_INFO_START, display, ITEMDRAW_DEFAULT); } -} - -static int GetNumSpectators(KZPlayer player) -{ - int count; - - for(int i = 1; i <= MaxClients; i++) - { - if (IsValidClient(i) && !IsPlayerAlive(i)) - { - int SpecMode = GetEntProp(i, Prop_Send, "m_iObserverMode"); - if (SpecMode == 4 || SpecMode == 5) - { - int target = GetEntPropEnt(i, Prop_Send, "m_hObserverTarget"); - if (target == player.ID) - { - count++; - } - } - } - } - - return count; -} - -static void FormatSpectatorNames(KZPlayer player, char display[512]) -{ - int count; - - for(int i = 1; i <= MaxClients; i++) - { - if (IsValidClient(i) && !IsPlayerAlive(i)) - { - int SpecMode = GetEntProp(i, Prop_Send, "m_iObserverMode"); - if (SpecMode == 4 || SpecMode == 5) - { - int target = GetEntPropEnt(i, Prop_Send, "m_hObserverTarget"); - if (target == player.ID) - { - count++; - //strip pound symbol from names - char cleanName[MAX_NAME_LENGTH]; - GetClientName(i, cleanName, sizeof(cleanName)); - ReplaceString(cleanName, sizeof(cleanName), "#", "", false); - if (count < 6) - { - Format(display, sizeof(display), "%s%s\n", display, cleanName); - } - } - if (count == 6) - { - Format(display, sizeof(display), "%s...", display); - } - } - } - } } \ No newline at end of file diff --git a/addons/sourcemod/scripting/gokz-jumpstats.sp b/addons/sourcemod/scripting/gokz-jumpstats.sp index d5ef58f4..74d92202 100644 --- a/addons/sourcemod/scripting/gokz-jumpstats.sp +++ b/addons/sourcemod/scripting/gokz-jumpstats.sp @@ -122,11 +122,6 @@ public void Movement_OnStartTouchGround(int client) OnStartTouchGround_JumpTracking(client); } -public void Movement_OnChangeMovetype(int client, MoveType oldMovetype, MoveType newMovetype) -{ - OnChangeMovetype_JumpTracking(client, oldMovetype, newMovetype); -} - public void GOKZ_OnJumpInvalidated(int client) { OnJumpInvalidated_JumpTracking(client); diff --git a/addons/sourcemod/scripting/gokz-jumpstats/jump_tracking.sp b/addons/sourcemod/scripting/gokz-jumpstats/jump_tracking.sp index e8efb59e..acd94428 100644 --- a/addons/sourcemod/scripting/gokz-jumpstats/jump_tracking.sp +++ b/addons/sourcemod/scripting/gokz-jumpstats/jump_tracking.sp @@ -26,6 +26,8 @@ static ArrayList entityTouchList[MAXPLAYERS + 1]; static int entityTouchDuration[MAXPLAYERS + 1]; static int lastNoclipTime[MAXPLAYERS + 1]; static int lastDuckbugTime[MAXPLAYERS + 1]; +static int lastGroundSpeedCappedTime[MAXPLAYERS + 1]; +static int lastMovementProcessedTime[MAXPLAYERS + 1]; static float lastJumpButtonTime[MAXPLAYERS + 1]; static bool validCmd[MAXPLAYERS + 1]; // Whether no illegal action is detected static const float playerMins[3] = { -16.0, -16.0, 0.0 }; @@ -60,7 +62,6 @@ enum struct JumpTracker int jumpoffTick; int poseIndex; int strafeDirection; - int ladderGrabTick; int lastJumpTick; int lastTeleportTick; int lastType; @@ -350,7 +351,11 @@ enum struct JumpTracker return JumpType_Other; } } - return this.HitDuckbugRecently() ? JumpType_Invalid : JumpType_LongJump; + if (this.HitDuckbugRecently() || !this.GroundSpeedCappedRecently()) + { + return JumpType_Invalid; + } + return JumpType_LongJump; } bool HitBhop() @@ -377,6 +382,12 @@ enum struct JumpTracker return this.tickCount - lastDuckbugTime[this.jumper] <= JS_MAX_DUCKBUG_RESET_TICKS; } + bool GroundSpeedCappedRecently() + { + // A valid longjump needs to have their ground speed capped the tick right before. + return lastGroundSpeedCappedTime[this.jumper] == lastMovementProcessedTime[this.jumper]; + } + // =====[ UPDATE HELPERS ]==================================================== // We split that up in two functions to get a reference to the pose so we @@ -1408,6 +1419,18 @@ void OnPlayerRunCmd_JumpTracking(int client, int buttons, int tickcount) } } +public Action Movement_OnWalkMovePost(int client) +{ + lastGroundSpeedCappedTime[client] = jumpTrackers[client].tickCount; + return Plugin_Continue; +} + +public Action Movement_OnPlayerMovePost(int client) +{ + lastMovementProcessedTime[client] = jumpTrackers[client].tickCount; + return Plugin_Continue; +} + public void OnPlayerRunCmdPost_JumpTracking(int client) { if (!IsValidClient(client) || !IsPlayerAlive(client)) @@ -1451,14 +1474,6 @@ public void OnPlayerRunCmdPost_JumpTracking(int client) } } -public void OnChangeMovetype_JumpTracking(int client, MoveType oldMovetype, MoveType newMovetype) -{ - if (newMovetype == MOVETYPE_LADDER) - { - jumpTrackers[client].ladderGrabTick = jumpTrackers[client].tickCount; - } -} - static MRESReturn DHooks_AcceptInput(int client, DHookReturn hReturn, DHookParam hParams) { if (!IsValidClient(client) || !IsPlayerAlive(client)) diff --git a/addons/sourcemod/scripting/gokz-mode-kztimer.sp b/addons/sourcemod/scripting/gokz-mode-kztimer.sp index cb422301..272652b1 100644 --- a/addons/sourcemod/scripting/gokz-mode-kztimer.sp +++ b/addons/sourcemod/scripting/gokz-mode-kztimer.sp @@ -29,7 +29,7 @@ public Plugin myinfo = #define UPDATER_URL GOKZ_UPDATER_BASE_URL..."gokz-mode-kztimer.txt" -#define MODE_VERSION 216 +#define MODE_VERSION 217 #define DUCK_SPEED_NORMAL 8.0 #define PRE_VELMOD_MAX 1.104 // Calculated 276/250 #define PERF_SPEED_CAP 380.0 @@ -69,11 +69,12 @@ float gF_PreVelModLastChange[MAXPLAYERS + 1]; float gF_RealPreVelMod[MAXPLAYERS + 1]; int gI_PreTickCounter[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_OldAngles[MAXPLAYERS + 1][3]; float gF_OldVelocity[MAXPLAYERS + 1][3]; bool gB_Jumpbugged[MAXPLAYERS + 1]; int gI_OffsetCGameMovement_player; @@ -173,9 +174,8 @@ public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3 gI_OldButtons[player.ID] = buttons; gI_OldFlags[player.ID] = GetEntityFlags(client); gB_OldOnGround[player.ID] = Movement_GetOnGround(client); - gF_OldAngles[player.ID] = angles; + gI_TickCount[player.ID] = tickcount; Movement_GetVelocity(client, gF_OldVelocity[client]); - return Plugin_Continue; } @@ -212,6 +212,22 @@ public MRESReturn DHooks_OnAirAccelerate_Pre(Address pThis, DHookParam hParams) 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 SDKHook_OnClientPreThink_Post(int client) { if (!IsPlayerAlive(client) || !IsUsingMode(client)) @@ -348,6 +364,19 @@ void HookEvents() 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 ]===== diff --git a/addons/sourcemod/scripting/gokz-mode-simplekz.sp b/addons/sourcemod/scripting/gokz-mode-simplekz.sp index 3973c102..99d76ac6 100644 --- a/addons/sourcemod/scripting/gokz-mode-simplekz.sp +++ b/addons/sourcemod/scripting/gokz-mode-simplekz.sp @@ -29,7 +29,7 @@ public Plugin myinfo = #define UPDATER_URL GOKZ_UPDATER_BASE_URL..."gokz-mode-simplekz.txt" -#define MODE_VERSION 20 +#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 @@ -78,6 +78,8 @@ 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]; @@ -185,6 +187,7 @@ public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3 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]); @@ -224,6 +227,22 @@ public MRESReturn DHooks_OnAirAccelerate_Pre(Address pThis, DHookParam hParams) 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)) @@ -358,6 +377,19 @@ void HookEvents() 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 ]===== diff --git a/addons/sourcemod/scripting/gokz-mode-vanilla.sp b/addons/sourcemod/scripting/gokz-mode-vanilla.sp index 14a1fc76..aecc201b 100644 --- a/addons/sourcemod/scripting/gokz-mode-vanilla.sp +++ b/addons/sourcemod/scripting/gokz-mode-vanilla.sp @@ -29,7 +29,7 @@ public Plugin myinfo = #define UPDATER_URL GOKZ_UPDATER_BASE_URL..."gokz-mode-vanilla.txt" -#define MODE_VERSION 16 +#define MODE_VERSION 17 float gF_ModeCVarValues[MODECVAR_COUNT] = { diff --git a/addons/sourcemod/scripting/gokz-momsurffix.sp b/addons/sourcemod/scripting/gokz-momsurffix.sp index ae87d2bd..2738e902 100644 --- a/addons/sourcemod/scripting/gokz-momsurffix.sp +++ b/addons/sourcemod/scripting/gokz-momsurffix.sp @@ -22,7 +22,7 @@ public Plugin myinfo = { #define FLT_EPSILON 1.192092896e-07 #define MAX_CLIP_PLANES 5 -#define ASM_PATCH_LEN 24 +#define ASM_PATCH_LEN 17 #define ASM_START_OFFSET 100 #define WALKABLE_PLANE_NORMAL 0.7 @@ -151,7 +151,7 @@ public Action SM_Prof(int client, int args) if(gProfTime <= 0.1) { - ReplyToCommand(client, SNAME..."Time should be higher then 0.1 seconds.") + ReplyToCommand(client, SNAME..."Time should be higher then 0.1 seconds."); return Plugin_Handled; } @@ -193,6 +193,8 @@ public Action Prof_Check_Timer(Handle timer, int client) delete gProf; delete gProfData; + + return Plugin_Handled; } #endif diff --git a/addons/sourcemod/scripting/gokz-quiet.sp b/addons/sourcemod/scripting/gokz-quiet.sp index e5d7a971..06cc2466 100644 --- a/addons/sourcemod/scripting/gokz-quiet.sp +++ b/addons/sourcemod/scripting/gokz-quiet.sp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -26,16 +27,13 @@ public Plugin myinfo = #define UPDATER_URL GOKZ_UPDATER_BASE_URL..."gokz-quiet.txt" -// Search for "coopcementplant.missionselect_blank" id with sv_soundscape_printdebuginfo. -#define BLANK_SOUNDSCAPEINDEX 482 -#define EFFECT_IMPACT 8 -#define EFFECT_KNIFESLASH 2 -TopMenu gTM_Options; -TopMenuObject gTMO_CatGeneral; -TopMenuObject gTMO_ItemsQuiet[QTOPTION_COUNT]; - -int gI_CurrentSoundscapeIndex[MAXPLAYERS + 1] = {BLANK_SOUNDSCAPEINDEX, ...}; +#include "gokz-quiet/ambient.sp" +#include "gokz-quiet/soundscape.sp" +#include "gokz-quiet/hideplayers.sp" +#include "gokz-quiet/falldamage.sp" +#include "gokz-quiet/gokz-sounds.sp" +#include "gokz-quiet/options.sp" // =====[ PLUGIN EVENTS ]===== @@ -47,23 +45,14 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max public void OnPluginStart() { - AddNormalSoundHook(Hook_NormalSound); - AddTempEntHook("Shotgun Shot", Hook_ShotgunShot); - AddTempEntHook("EffectDispatch", Hook_EffectDispatch); - HookUserMessage(GetUserMessageId("WeaponSound"), Hook_WeaponSound, true); + OnPluginStart_HidePlayers(); + OnPluginStart_FallDamage(); + OnPluginStart_Ambient(); LoadTranslations("gokz-common.phrases"); LoadTranslations("gokz-quiet.phrases"); RegisterCommands(); - - for (int client = 1; client <= MaxClients; client++) - { - if (IsValidClient(client)) - { - OnJoinTeam_HidePlayers(client, GetClientTeam(client)); - } - } } public void OnAllPluginsLoaded() @@ -111,24 +100,11 @@ public void OnPlayerRunCmdPost(int client, int buttons, int impulse, const float { return; } - - int soundscapeIndex = GetEntProp(client, Prop_Data, "soundscapeIndex"); - if (GOKZ_GetOption(client, gC_QTOptionNames[QTOption_MapSounds]) == MapSounds_Disabled) - { - if (soundscapeIndex != BLANK_SOUNDSCAPEINDEX) - { - gI_CurrentSoundscapeIndex[client] = soundscapeIndex; - } - SetEntProp(client, Prop_Data, "soundscapeIndex", BLANK_SOUNDSCAPEINDEX); - } - else - { - gI_CurrentSoundscapeIndex[client] = soundscapeIndex; - } + + OnPlayerRunCmdPost_Soundscape(client); } - // =====[ OTHER EVENTS ]===== public void GOKZ_OnOptionsMenuReady(TopMenu topMenu) @@ -146,285 +122,11 @@ public void GOKZ_OnOptionChanged(int client, const char[] option, any newValue) } } - - -// =====[ HIDE PLAYERS ]===== - -void OnJoinTeam_HidePlayers(int client, int team) -{ - // Make sure client is only ever hooked once - SDKUnhook(client, SDKHook_SetTransmit, OnSetTransmitClient); - - if (team == CS_TEAM_T || team == CS_TEAM_CT) - { - SDKHook(client, SDKHook_SetTransmit, OnSetTransmitClient); - } -} - -public Action OnSetTransmitClient(int entity, int client) -{ - if (GOKZ_GetOption(client, gC_QTOptionNames[QTOption_ShowPlayers]) == ShowPlayers_Disabled - && entity != client - && entity != GetObserverTarget(client)) - { - return Plugin_Handled; - } - return Plugin_Continue; -} - -public Action Hook_WeaponSound(UserMsg msg_id, Protobuf msg, const int[] players, int playersNum, bool reliable, bool init) -{ - int newClients[MAXPLAYERS], newTotal = 0; - int entidx = msg.ReadInt("entidx"); - for (int i = 0; i < playersNum; i++) - { - int client = players[i]; - if (!IsValidClient(client)) - { - continue; - } - if (GOKZ_GetOption(client, gC_QTOptionNames[QTOption_ShowPlayers]) == ShowPlayers_Enabled - || entidx == client - || entidx == GetObserverTarget(client)) - { - newClients[newTotal] = client; - newTotal++; - } - } - - // Nothing's changed, let the engine handle it. - if (newTotal == playersNum) - { - return Plugin_Continue; - } - - // No one to send to so it doesn't matter if we block or not. We block just to end the function early. - if (newTotal == 0) - { - return Plugin_Handled; - } - // Only way to modify the recipient list is to RequestFrame and create our own user message. - char path[PLATFORM_MAX_PATH]; - msg.ReadString("sound", path, sizeof(path)); - int flags = USERMSG_BLOCKHOOKS; - if (reliable) - { - flags |= USERMSG_RELIABLE; - } - if (init) - { - flags |= USERMSG_INITMSG; - } - - DataPack dp = new DataPack(); - dp.WriteCell(msg_id); - dp.WriteCell(newTotal); - dp.WriteCellArray(newClients, newTotal); - dp.WriteCell(flags); - dp.WriteCell(entidx); - dp.WriteFloat(msg.ReadFloat("origin_x")); - dp.WriteFloat(msg.ReadFloat("origin_y")); - dp.WriteFloat(msg.ReadFloat("origin_z")); - dp.WriteString(path); - dp.WriteFloat(msg.ReadFloat("timestamp")); - - RequestFrame(RequestFrame_WeaponSound, dp); - return Plugin_Handled; -} - -public void RequestFrame_WeaponSound(DataPack dp) -{ - dp.Reset(); - - UserMsg msg_id = dp.ReadCell(); - int newTotal = dp.ReadCell(); - int newClients[MAXPLAYERS]; - dp.ReadCellArray(newClients, newTotal); - int flags = dp.ReadCell(); - - Protobuf newMsg = view_as(StartMessageEx(msg_id, newClients, newTotal, flags)); - - newMsg.SetInt("entidx", dp.ReadCell()); - newMsg.SetFloat("origin_x", dp.ReadFloat()); - newMsg.SetFloat("origin_y", dp.ReadFloat()); - newMsg.SetFloat("origin_z", dp.ReadFloat()); - char path[PLATFORM_MAX_PATH]; - dp.ReadString(path, sizeof(path)); - newMsg.SetString("sound", path); - newMsg.SetFloat("timestamp", dp.ReadFloat()); - - EndMessage(); - - delete dp; -} - -public Action Hook_NormalSound(int clients[MAXPLAYERS], int& numClients, char sample[PLATFORM_MAX_PATH], int& entity, int& channel, float& volume, int& level, int& pitch, int& flags, char soundEntry[PLATFORM_MAX_PATH], int& seed) -{ - if (StrContains(sample, "Player.EquipArmor") != -1 || StrContains(sample, "BaseCombatCharacter.AmmoPickup") != -1) - { - // When the sound is emitted, the owner of these entities are not set yet. - // Hence we cannot do the entity parent stuff below. - // In that case, we just straight up block armor and ammo pickup sounds. - return Plugin_Stop; - } - int ent = entity; - while (ent > MAXPLAYERS) - { - // Block some gun and knife sounds by trying to find its parent entity. - ent = GetEntPropEnt(ent, Prop_Send, "moveparent"); - if (ent < MAXPLAYERS) - { - break; - } - else if (ent == -1) - { - return Plugin_Continue; - } - } - int numNewClients = 0; - for (int i = 0; i < numClients; i++) - { - int client = clients[i]; - if (GOKZ_GetOption(client, gC_QTOptionNames[QTOption_ShowPlayers]) == ShowPlayers_Enabled - || ent == client - || ent == GetObserverTarget(client)) - { - clients[numNewClients] = client; - numNewClients++; - } - } - - if (numNewClients != numClients) - { - numClients = numNewClients; - return Plugin_Changed; - } - - return Plugin_Continue; -} - -public Action Hook_ShotgunShot(const char[] te_name, const int[] players, int numClients, float delay) -{ - int newClients[MAXPLAYERS], newTotal = 0; - for (int i = 0; i < numClients; i++) - { - int client = players[i]; - if (GOKZ_GetOption(client, gC_QTOptionNames[QTOption_ShowPlayers]) == ShowPlayers_Enabled - || TE_ReadNum("m_iPlayer") + 1 == GetObserverTarget(client)) - { - newClients[newTotal] = client; - newTotal++; - } - } - - // Noone wants the sound - if (newTotal == 0) - { - return Plugin_Stop; - } - - // Nothing's changed, let the engine handle it. - if (newTotal == numClients) - { - return Plugin_Continue; - } - - float origin[3]; - TE_ReadVector("m_vecOrigin", origin); - - float angles[2]; - angles[0] = TE_ReadFloat("m_vecAngles[0]"); - angles[1] = TE_ReadFloat("m_vecAngles[1]"); - - int weapon = TE_ReadNum("m_weapon"); - int mode = TE_ReadNum("m_iMode"); - int seed = TE_ReadNum("m_iSeed"); - int player = TE_ReadNum("m_iPlayer"); - float inaccuracy = TE_ReadFloat("m_fInaccuracy"); - float recoilIndex = TE_ReadFloat("m_flRecoilIndex"); - float spread = TE_ReadFloat("m_fSpread"); - int itemIdx = TE_ReadNum("m_nItemDefIndex"); - int soundType = TE_ReadNum("m_iSoundType"); - - TE_Start("Shotgun Shot"); - TE_WriteVector("m_vecOrigin", origin); - TE_WriteFloat("m_vecAngles[0]", angles[0]); - TE_WriteFloat("m_vecAngles[1]", angles[1]); - TE_WriteNum("m_weapon", weapon); - TE_WriteNum("m_iMode", mode); - TE_WriteNum("m_iSeed", seed); - TE_WriteNum("m_iPlayer", player); - TE_WriteFloat("m_fInaccuracy", inaccuracy); - TE_WriteFloat("m_flRecoilIndex", recoilIndex); - TE_WriteFloat("m_fSpread", spread); - TE_WriteNum("m_nItemDefIndex", itemIdx); - TE_WriteNum("m_iSoundType", soundType); - - // Send the TE and stop the engine from processing its own. - TE_Send(newClients, newTotal, delay); - return Plugin_Stop; -} - -public Action Hook_EffectDispatch(const char[] te_name, const int[] players, int numClients, float delay) +public void GOKZ_OnOptionsMenuCreated(TopMenu topMenu) { - // Block bullet impact effects. - int effIndex = TE_ReadNum("m_iEffectName"); - if (effIndex != EFFECT_IMPACT && effIndex != EFFECT_KNIFESLASH) - { - return Plugin_Continue; - } - int newClients[MAXPLAYERS], newTotal = 0; - for (int i = 0; i < numClients; i++) - { - int client = players[i]; - if (GOKZ_GetOption(client, gC_QTOptionNames[QTOption_ShowPlayers]) == ShowPlayers_Enabled) - { - newClients[newTotal] = client; - newTotal++; - } - } - // Noone wants the sound - if (newTotal == 0) - { - return Plugin_Stop; - } - - // Nothing's changed, let the engine handle it. - if (newTotal == numClients) - { - return Plugin_Continue; - } - float origin[3], start[3]; - origin[0] = TE_ReadFloat("m_vOrigin.x"); - origin[1] = TE_ReadFloat("m_vOrigin.y"); - origin[2] = TE_ReadFloat("m_vOrigin.z"); - start[0] = TE_ReadFloat("m_vStart.x"); - start[1] = TE_ReadFloat("m_vStart.y"); - start[2] = TE_ReadFloat("m_vStart.z"); - int flags = TE_ReadNum("m_fFlags"); - float scale = TE_ReadFloat("m_flScale"); - int surfaceProp = TE_ReadNum("m_nSurfaceProp"); - int damageType = TE_ReadNum("m_nDamageType"); - int entindex = TE_ReadNum("entindex"); - int positionsAreRelativeToEntity = TE_ReadNum("m_bPositionsAreRelativeToEntity"); - - TE_Start("EffectDispatch"); - TE_WriteNum("m_iEffectName", effIndex); - TE_WriteFloatArray("m_vOrigin.x", origin, 3); - TE_WriteFloatArray("m_vStart.x", start, 3); - TE_WriteFloat("m_flScale", scale); - TE_WriteNum("m_nSurfaceProp", surfaceProp); - TE_WriteNum("m_nDamageType", damageType); - TE_WriteNum("entindex", entindex); - TE_WriteNum("m_bPositionsAreRelativeToEntity", positionsAreRelativeToEntity); - TE_WriteNum("m_fFlags", flags); - - // Send the TE and stop the engine from processing its own. - TE_Send(newClients, newTotal, delay); - return Plugin_Stop; + OnOptionsMenuCreated_OptionsMenu(topMenu); } - // =====[ STOP SOUNDS ]===== void StopSounds(int client) @@ -434,148 +136,6 @@ void StopSounds(int client) } - -// =====[ OPTIONS ]===== - -void OnOptionsMenuReady_Options() -{ - RegisterOptions(); -} - -void OnOptionChanged_Options(int client, QTOption option, any newValue) -{ - if (option == QTOption_MapSounds && newValue == MapSounds_Enabled) - { - if (gI_CurrentSoundscapeIndex[client] != BLANK_SOUNDSCAPEINDEX) - { - SetEntProp(client, Prop_Data, "soundscapeIndex", gI_CurrentSoundscapeIndex[client]); - } - } - PrintOptionChangeMessage(client, option, newValue); -} - -void PrintOptionChangeMessage(int client, QTOption option, any newValue) -{ - switch (option) - { - case QTOption_ShowPlayers: - { - switch (newValue) - { - case ShowPlayers_Disabled: - { - GOKZ_PrintToChat(client, true, "%t", "Option - Show Players - Disable"); - } - case ShowPlayers_Enabled: - { - GOKZ_PrintToChat(client, true, "%t", "Option - Show Players - Enable"); - } - } - } - case QTOption_MapSounds: - { - switch (newValue) - { - case MapSounds_Disabled: - { - GOKZ_PrintToChat(client, true, "%t", "Option - Map Sounds - Disable"); - } - case MapSounds_Enabled: - { - GOKZ_PrintToChat(client, true, "%t", "Option - Map Sounds - Enable"); - } - } - } - } -} - -void RegisterOptions() -{ - for (QTOption option; option < QTOPTION_COUNT; option++) - { - GOKZ_RegisterOption(gC_QTOptionNames[option], gC_QTOptionDescriptions[option], - OptionType_Int, gI_QTOptionDefaultValues[option], 0, gI_QTOptionCounts[option] - 1); - } -} - - - -// =====[ OPTIONS MENU ]===== - -void OnOptionsMenuReady_OptionsMenu(TopMenu topMenu) -{ - if (gTM_Options == topMenu) - { - return; - } - - gTM_Options = topMenu; - gTMO_CatGeneral = gTM_Options.FindCategory(GENERAL_OPTION_CATEGORY); - - // Add gokz-quiet option items - for (int option = 0; option < view_as(QTOPTION_COUNT); option++) - { - gTMO_ItemsQuiet[option] = gTM_Options.AddItem(gC_QTOptionNames[option], TopMenuHandler_QT, gTMO_CatGeneral); - } -} - - -public void TopMenuHandler_QT(TopMenu topmenu, TopMenuAction action, TopMenuObject topobj_id, int param, char[] buffer, int maxlength) -{ - QTOption option = QTOPTION_INVALID; - for (int i = 0; i < view_as(QTOPTION_COUNT); i++) - { - if (topobj_id == gTMO_ItemsQuiet[i]) - { - option = view_as(i); - break; - } - } - - if (option == QTOPTION_INVALID) - { - return; - } - - if (action == TopMenuAction_DisplayOption) - { - switch (option) - { - case QTOption_ShowPlayers: - { - FormatToggleableOptionDisplay(param, QTOption_ShowPlayers, buffer, maxlength); - } - case QTOption_MapSounds: - { - FormatToggleableOptionDisplay(param, QTOption_MapSounds, buffer, maxlength); - } - } - } - else if (action == TopMenuAction_SelectOption) - { - GOKZ_CycleOption(param, gC_QTOptionNames[option]); - gTM_Options.Display(param, TopMenuPosition_LastCategory); - } -} - -void FormatToggleableOptionDisplay(int client, QTOption option, char[] buffer, int maxlength) -{ - if (GOKZ_GetOption(client, gC_QTOptionNames[option]) == MapSounds_Disabled) - { - FormatEx(buffer, maxlength, "%T - %T", - gC_QTOptionPhrases[option], client, - "Options Menu - Disabled", client); - } - else - { - FormatEx(buffer, maxlength, "%T - %T", - gC_QTOptionPhrases[option], client, - "Options Menu - Enabled", client); - } -} - - - // =====[ COMMANDS ]===== void RegisterCommands() @@ -584,19 +144,6 @@ void RegisterCommands() RegConsoleCmd("sm_stopsound", CommandStopSound, "[KZ] Stop all sounds e.g. map soundscapes (music)."); } -public Action CommandToggleShowPlayers(int client, int args) -{ - if (GOKZ_GetOption(client, gC_QTOptionNames[QTOption_ShowPlayers]) == ShowPlayers_Disabled) - { - GOKZ_SetOption(client, gC_QTOptionNames[QTOption_ShowPlayers], ShowPlayers_Enabled); - } - else - { - GOKZ_SetOption(client, gC_QTOptionNames[QTOption_ShowPlayers], ShowPlayers_Disabled); - } - return Plugin_Handled; -} - public Action CommandStopSound(int client, int args) { StopSounds(client); diff --git a/addons/sourcemod/scripting/gokz-quiet/ambient.sp b/addons/sourcemod/scripting/gokz-quiet/ambient.sp new file mode 100644 index 00000000..d138a5ff --- /dev/null +++ b/addons/sourcemod/scripting/gokz-quiet/ambient.sp @@ -0,0 +1,100 @@ +/* + Hide sound effect from ambient_generics. + Credit to Haze - https://github.com/Haze1337/Sound-Manager +*/ + +Handle getPlayerSlot; + +void OnPluginStart_Ambient() +{ + HookSendSound(); +} +static void HookSendSound() +{ + GameData gd = LoadGameConfigFile("gokz-quiet.games"); + + DynamicDetour sendSoundDetour = DHookCreateDetour(Address_Null, CallConv_THISCALL, ReturnType_Void, ThisPointer_Address); + DHookSetFromConf(sendSoundDetour, gd, SDKConf_Signature, "CGameClient::SendSound"); + DHookAddParam(sendSoundDetour, HookParamType_ObjectPtr); + DHookAddParam(sendSoundDetour, HookParamType_Bool); + if (!DHookEnableDetour(sendSoundDetour, false, DHooks_OnSendSound)) + { + SetFailState("Couldn't enable CGameClient::SendSound detour."); + } + + StartPrepSDKCall(SDKCall_Raw); + PrepSDKCall_SetFromConf(gd, SDKConf_Virtual, "CBaseClient::GetPlayerSlot"); + PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain); + getPlayerSlot = EndPrepSDKCall(); + if (getPlayerSlot == null) + { + SetFailState("Could not initialize call to CBaseClient::GetPlayerSlot."); + } +} + + +/*struct SoundInfo_t +{ + Vector vOrigin; Offset: 0 | Size: 12 + Vector vDirection Offset: 12 | Size: 12 + Vector vListenerOrigin; Offset: 24 | Size: 12 + const char *pszName; Offset: 36 | Size: 4 + float fVolume; Offset: 40 | Size: 4 + float fDelay; Offset: 44 | Size: 4 + float fTickTime; Offset: 48 | Size: 4 + int nSequenceNumber; Offset: 52 | Size: 4 + int nEntityIndex; Offset: 56 | Size: 4 + int nChannel; Offset: 60 | Size: 4 + int nPitch; Offset: 64 | Size: 4 + int nFlags; Offset: 68 | Size: 4 + unsigned int nSoundNum; Offset: 72 | Size: 4 + int nSpeakerEntity; Offset: 76 | Size: 4 + int nRandomSeed; Offset: 80 | Size: 4 + soundlevel_t Soundlevel; Offset: 84 | Size: 4 + bool bIsSentence; Offset: 88 | Size: 1 + bool bIsAmbient; Offset: 89 | Size: 1 + bool bLooping; Offset: 90 | Size: 1 +};*/ + +//void CGameClient::SendSound( SoundInfo_t &sound, bool isReliable ) +public MRESReturn DHooks_OnSendSound(Address pThis, Handle hParams) +{ + // Check volume + float volume = DHookGetParamObjectPtrVar(hParams, 1, 40, ObjectValueType_Float); + if(volume == 0.0) + { + return MRES_Ignored; + } + + Address pIClient = pThis + view_as
(0x4); + int client = view_as(SDKCall(getPlayerSlot, pIClient)) + 1; + + if(!IsValidClient(client)) + { + return MRES_Ignored; + } + + bool isAmbient = DHookGetParamObjectPtrVar(hParams, 1, 89, ObjectValueType_Bool); + if (!isAmbient) + { + return MRES_Ignored; + } + + float newVolume; + if (GOKZ_QT_GetOption(client, QTOption_AmbientSounds) == -1 || GOKZ_QT_GetOption(client, QTOption_AmbientSounds) == 10) + { + newVolume = volume; + } + else + { + float volumeFactor = float(GOKZ_QT_GetOption(client, QTOption_AmbientSounds)) * 0.1; + newVolume = volume * volumeFactor; + } + + if (newVolume <= 0.0) + { + return MRES_Supercede; + } + DHookSetParamObjectPtrVar(hParams, 1, 40, ObjectValueType_Float, newVolume); + return MRES_ChangedHandled; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/gokz-quiet/falldamage.sp b/addons/sourcemod/scripting/gokz-quiet/falldamage.sp new file mode 100644 index 00000000..a55c6912 --- /dev/null +++ b/addons/sourcemod/scripting/gokz-quiet/falldamage.sp @@ -0,0 +1,40 @@ +/* + Toggle player's fall damage sounds. +*/ + +void OnPluginStart_FallDamage() +{ + AddNormalSoundHook(Hook_NormalSound); +} + +static Action Hook_NormalSound(int clients[MAXPLAYERS], int& numClients, char sample[PLATFORM_MAX_PATH], int& entity, int& channel, float& volume, int& level, int& pitch, int& flags, char soundEntry[PLATFORM_MAX_PATH], int& seed) +{ + if (!StrEqual(soundEntry, "Player.FallDamage")) + { + return Plugin_Continue; + } + + for (int i = 0; i < numClients; i++) + { + int client = clients[i]; + if (!IsValidClient(client)) + { + continue; + } + int clientArray[1]; + clientArray[0] = client; + float newVolume; + if (GOKZ_QT_GetOption(client, QTOption_FallDamageSound) == -1 || GOKZ_QT_GetOption(client, QTOption_FallDamageSound) == 10) + { + newVolume = volume; + } + else + { + float volumeFactor = float(GOKZ_QT_GetOption(client, QTOption_FallDamageSound)) * 0.1; + newVolume = volume * volumeFactor; + } + + EmitSoundEntry(clientArray, 1, soundEntry, sample, entity, channel, level, seed, flags, newVolume, pitch); + } + return Plugin_Handled; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/gokz-quiet/gokz-sounds.sp b/addons/sourcemod/scripting/gokz-quiet/gokz-sounds.sp new file mode 100644 index 00000000..bf38d6e6 --- /dev/null +++ b/addons/sourcemod/scripting/gokz-quiet/gokz-sounds.sp @@ -0,0 +1,71 @@ +/* + Volume options for various GOKZ sounds. +*/ + +public Action GOKZ_OnEmitSoundToClient(int client, const char[] sample, float &volume, const char[] description) +{ + int volumeFactor = 10; + if (StrEqual(description, "Checkpoint") || StrEqual(description, "Set Start Position")) + { + volumeFactor = GOKZ_QT_GetOption(client, QTOption_CheckpointVolume); + if (volumeFactor == -1) + { + return Plugin_Continue; + } + } + else if (StrEqual(description, "Checkpoint")) + { + volumeFactor = GOKZ_QT_GetOption(client, QTOption_TeleportVolume); + if (volumeFactor == -1) + { + return Plugin_Continue; + } + } + else if (StrEqual(description, "Timer Start") || StrEqual(description, "Timer End") || StrEqual(description, "Timer False End") || StrEqual(description, "Missed PB")) + { + volumeFactor = GOKZ_QT_GetOption(client, QTOption_TimerVolume); + if (volumeFactor == -1) + { + return Plugin_Continue; + } + } + else if (StrEqual(description, "Error")) + { + volumeFactor = GOKZ_QT_GetOption(client, QTOption_ErrorVolume); + if (volumeFactor == -1) + { + return Plugin_Continue; + } + } + else if (StrEqual(description, "Server Record")) + { + volumeFactor = GOKZ_QT_GetOption(client, QTOption_ServerRecordVolume); + if (volumeFactor == -1) + { + return Plugin_Continue; + } + } + else if (StrEqual(description, "World Record")) + { + volumeFactor = GOKZ_QT_GetOption(client, QTOption_WorldRecordVolume); + if (volumeFactor == -1) + { + return Plugin_Continue; + } + } + else if (StrEqual(description, "Jumpstats")) + { + volumeFactor = GOKZ_QT_GetOption(client, QTOption_JumpstatsVolume); + if (volumeFactor == -1) + { + return Plugin_Continue; + } + } + + if (volumeFactor == 10) + { + return Plugin_Continue; + } + volume *= float(volumeFactor) * 0.1; + return Plugin_Changed; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/gokz-quiet/hideplayers.sp b/addons/sourcemod/scripting/gokz-quiet/hideplayers.sp new file mode 100644 index 00000000..b4e1be4e --- /dev/null +++ b/addons/sourcemod/scripting/gokz-quiet/hideplayers.sp @@ -0,0 +1,309 @@ +/* + Hide sounds and effects from other players. +*/ + +void OnPluginStart_HidePlayers() +{ + AddNormalSoundHook(Hook_NormalSound); + AddTempEntHook("Shotgun Shot", Hook_ShotgunShot); + AddTempEntHook("EffectDispatch", Hook_EffectDispatch); + HookUserMessage(GetUserMessageId("WeaponSound"), Hook_WeaponSound, true); + + // Lateload support + for (int client = 1; client <= MaxClients; client++) + { + if (IsValidClient(client)) + { + OnJoinTeam_HidePlayers(client, GetClientTeam(client)); + } + } +} + +void OnJoinTeam_HidePlayers(int client, int team) +{ + // Make sure client is only ever hooked once + SDKUnhook(client, SDKHook_SetTransmit, OnSetTransmitClient); + + if (team == CS_TEAM_T || team == CS_TEAM_CT) + { + SDKHook(client, SDKHook_SetTransmit, OnSetTransmitClient); + } +} + +Action CommandToggleShowPlayers(int client, int args) +{ + if (GOKZ_GetOption(client, gC_QTOptionNames[QTOption_ShowPlayers]) == ShowPlayers_Disabled) + { + GOKZ_SetOption(client, gC_QTOptionNames[QTOption_ShowPlayers], ShowPlayers_Enabled); + } + else + { + GOKZ_SetOption(client, gC_QTOptionNames[QTOption_ShowPlayers], ShowPlayers_Disabled); + } + return Plugin_Handled; +} + +// =====[ PRIVATE ]===== + +// Hide most of the other players' actions. This function is expensive. +static Action OnSetTransmitClient(int entity, int client) +{ + if (GOKZ_GetOption(client, gC_QTOptionNames[QTOption_ShowPlayers]) == ShowPlayers_Disabled + && entity != client + && entity != GetObserverTarget(client)) + { + return Plugin_Handled; + } + return Plugin_Continue; +} + +// Hide reload sounds. Required if other players were visible at one point during the gameplay. +static Action Hook_WeaponSound(UserMsg msg_id, Protobuf msg, const int[] players, int playersNum, bool reliable, bool init) +{ + int newClients[MAXPLAYERS], newTotal = 0; + int entidx = msg.ReadInt("entidx"); + for (int i = 0; i < playersNum; i++) + { + int client = players[i]; + if (GOKZ_GetOption(client, gC_QTOptionNames[QTOption_ShowPlayers]) == ShowPlayers_Enabled + || entidx == client + || entidx == GetObserverTarget(client)) + { + newClients[newTotal] = client; + newTotal++; + } + } + + // Nothing's changed, let the engine handle it. + if (newTotal == playersNum) + { + return Plugin_Continue; + } + // No one to send to so it doesn't matter if we block or not. We block just to end the function early. + if (newTotal == 0) + { + return Plugin_Handled; + } + // Only way to modify the recipient list is to RequestFrame and create our own user message. + char path[PLATFORM_MAX_PATH]; + msg.ReadString("sound", path, sizeof(path)); + int flags = USERMSG_BLOCKHOOKS; + if (reliable) + { + flags |= USERMSG_RELIABLE; + } + if (init) + { + flags |= USERMSG_INITMSG; + } + + DataPack dp = new DataPack(); + dp.WriteCell(msg_id); + dp.WriteCell(newTotal); + dp.WriteCellArray(newClients, newTotal); + dp.WriteCell(flags); + dp.WriteCell(entidx); + dp.WriteFloat(msg.ReadFloat("origin_x")); + dp.WriteFloat(msg.ReadFloat("origin_y")); + dp.WriteFloat(msg.ReadFloat("origin_z")); + dp.WriteString(path); + dp.WriteFloat(msg.ReadFloat("timestamp")); + + RequestFrame(RequestFrame_WeaponSound, dp); + return Plugin_Handled; +} + +static void RequestFrame_WeaponSound(DataPack dp) +{ + dp.Reset(); + + UserMsg msg_id = dp.ReadCell(); + int newTotal = dp.ReadCell(); + int newClients[MAXPLAYERS]; + dp.ReadCellArray(newClients, newTotal); + int flags = dp.ReadCell(); + + Protobuf newMsg = view_as(StartMessageEx(msg_id, newClients, newTotal, flags)); + + newMsg.AddInt("entidx", dp.ReadCell()); + newMsg.AddFloat("origin_x", dp.ReadFloat()); + newMsg.AddFloat("origin_y", dp.ReadFloat()); + newMsg.AddFloat("origin_z", dp.ReadFloat()); + char path[PLATFORM_MAX_PATH]; + dp.ReadString(path, sizeof(path)); + newMsg.AddString("sound", path); + newMsg.AddFloat("timestamp", dp.ReadFloat()); + + EndMessage(); + + delete dp; +} + +// Hide various sounds that don't get blocked by SetTransmit hook. +static Action Hook_NormalSound(int clients[MAXPLAYERS], int& numClients, char sample[PLATFORM_MAX_PATH], int& entity, int& channel, float& volume, int& level, int& pitch, int& flags, char soundEntry[PLATFORM_MAX_PATH], int& seed) +{ + if (StrContains(sample, "Player.EquipArmor") != -1 || StrContains(sample, "BaseCombatCharacter.AmmoPickup") != -1) + { + // When the sound is emitted, the owner of these entities are not set yet. + // Hence we cannot do the entity parent stuff below. + // In that case, we just straight up block armor and ammo pickup sounds. + return Plugin_Stop; + } + int ent = entity; + while (ent > MAXPLAYERS) + { + // Block some gun and knife sounds by trying to find its parent entity. + ent = GetEntPropEnt(ent, Prop_Send, "moveparent"); + if (ent < MAXPLAYERS) + { + break; + } + else if (ent == -1) + { + return Plugin_Continue; + } + } + int numNewClients = 0; + for (int i = 0; i < numClients; i++) + { + int client = clients[i]; + if (GOKZ_GetOption(client, gC_QTOptionNames[QTOption_ShowPlayers]) == ShowPlayers_Enabled + || ent == client + || ent == GetObserverTarget(client)) + { + clients[numNewClients] = client; + numNewClients++; + } + } + + if (numNewClients != numClients) + { + numClients = numNewClients; + return Plugin_Changed; + } + + return Plugin_Continue; +} + +// Hide firing sounds. +static Action Hook_ShotgunShot(const char[] te_name, const int[] players, int numClients, float delay) +{ + int newClients[MAXPLAYERS], newTotal = 0; + for (int i = 0; i < numClients; i++) + { + int client = players[i]; + if (GOKZ_GetOption(client, gC_QTOptionNames[QTOption_ShowPlayers]) == ShowPlayers_Enabled + || TE_ReadNum("m_iPlayer") + 1 == GetObserverTarget(client)) + { + newClients[newTotal] = client; + newTotal++; + } + } + + // Noone wants the sound + if (newTotal == 0) + { + return Plugin_Stop; + } + + // Nothing's changed, let the engine handle it. + if (newTotal == numClients) + { + return Plugin_Continue; + } + + float origin[3]; + TE_ReadVector("m_vecOrigin", origin); + + float angles[2]; + angles[0] = TE_ReadFloat("m_vecAngles[0]"); + angles[1] = TE_ReadFloat("m_vecAngles[1]"); + + int weapon = TE_ReadNum("m_weapon"); + int mode = TE_ReadNum("m_iMode"); + int seed = TE_ReadNum("m_iSeed"); + int player = TE_ReadNum("m_iPlayer"); + float inaccuracy = TE_ReadFloat("m_fInaccuracy"); + float recoilIndex = TE_ReadFloat("m_flRecoilIndex"); + float spread = TE_ReadFloat("m_fSpread"); + int itemIdx = TE_ReadNum("m_nItemDefIndex"); + int soundType = TE_ReadNum("m_iSoundType"); + + TE_Start("Shotgun Shot"); + TE_WriteVector("m_vecOrigin", origin); + TE_WriteFloat("m_vecAngles[0]", angles[0]); + TE_WriteFloat("m_vecAngles[1]", angles[1]); + TE_WriteNum("m_weapon", weapon); + TE_WriteNum("m_iMode", mode); + TE_WriteNum("m_iSeed", seed); + TE_WriteNum("m_iPlayer", player); + TE_WriteFloat("m_fInaccuracy", inaccuracy); + TE_WriteFloat("m_flRecoilIndex", recoilIndex); + TE_WriteFloat("m_fSpread", spread); + TE_WriteNum("m_nItemDefIndex", itemIdx); + TE_WriteNum("m_iSoundType", soundType); + + // Send the TE and stop the engine from processing its own. + TE_Send(newClients, newTotal, delay); + return Plugin_Stop; +} + +// Hide knife and blood effect caused by other players. +static Action Hook_EffectDispatch(const char[] te_name, const int[] players, int numClients, float delay) +{ + // Block bullet impact effects. + int effIndex = TE_ReadNum("m_iEffectName"); + if (effIndex != EFFECT_IMPACT && effIndex != EFFECT_KNIFESLASH) + { + return Plugin_Continue; + } + int newClients[MAXPLAYERS], newTotal = 0; + for (int i = 0; i < numClients; i++) + { + int client = players[i]; + if (GOKZ_GetOption(client, gC_QTOptionNames[QTOption_ShowPlayers]) == ShowPlayers_Enabled) + { + newClients[newTotal] = client; + newTotal++; + } + } + // Noone wants the sound + if (newTotal == 0) + { + return Plugin_Stop; + } + + // Nothing's changed, let the engine handle it. + if (newTotal == numClients) + { + return Plugin_Continue; + } + float origin[3], start[3]; + origin[0] = TE_ReadFloat("m_vOrigin.x"); + origin[1] = TE_ReadFloat("m_vOrigin.y"); + origin[2] = TE_ReadFloat("m_vOrigin.z"); + start[0] = TE_ReadFloat("m_vStart.x"); + start[1] = TE_ReadFloat("m_vStart.y"); + start[2] = TE_ReadFloat("m_vStart.z"); + int flags = TE_ReadNum("m_fFlags"); + float scale = TE_ReadFloat("m_flScale"); + int surfaceProp = TE_ReadNum("m_nSurfaceProp"); + int damageType = TE_ReadNum("m_nDamageType"); + int entindex = TE_ReadNum("entindex"); + int positionsAreRelativeToEntity = TE_ReadNum("m_bPositionsAreRelativeToEntity"); + + TE_Start("EffectDispatch"); + TE_WriteNum("m_iEffectName", effIndex); + TE_WriteFloatArray("m_vOrigin.x", origin, 3); + TE_WriteFloatArray("m_vStart.x", start, 3); + TE_WriteFloat("m_flScale", scale); + TE_WriteNum("m_nSurfaceProp", surfaceProp); + TE_WriteNum("m_nDamageType", damageType); + TE_WriteNum("entindex", entindex); + TE_WriteNum("m_bPositionsAreRelativeToEntity", positionsAreRelativeToEntity); + TE_WriteNum("m_fFlags", flags); + + // Send the TE and stop the engine from processing its own. + TE_Send(newClients, newTotal, delay); + return Plugin_Stop; +} diff --git a/addons/sourcemod/scripting/gokz-quiet/options.sp b/addons/sourcemod/scripting/gokz-quiet/options.sp new file mode 100644 index 00000000..a9da3d7f --- /dev/null +++ b/addons/sourcemod/scripting/gokz-quiet/options.sp @@ -0,0 +1,206 @@ +// =====[ OPTIONS ]===== + +void OnOptionsMenuReady_Options() +{ + RegisterOptions(); +} + +void RegisterOptions() +{ + for (QTOption option; option < QTOPTION_COUNT; option++) + { + GOKZ_RegisterOption(gC_QTOptionNames[option], gC_QTOptionDescriptions[option], + OptionType_Int, gI_QTOptionDefaultValues[option], 0, gI_QTOptionCounts[option] - 1); + } +} + +void OnOptionChanged_Options(int client, QTOption option, any newValue) +{ + if (option == QTOption_Soundscapes && newValue == Soundscapes_Enabled) + { + EnableSoundscape(client); + } + PrintOptionChangeMessage(client, option, newValue); +} + +void PrintOptionChangeMessage(int client, QTOption option, any newValue) +{ + switch (option) + { + case QTOption_ShowPlayers: + { + switch (newValue) + { + case ShowPlayers_Disabled: + { + GOKZ_PrintToChat(client, true, "%t", "Option - Show Players - Disable"); + } + case ShowPlayers_Enabled: + { + GOKZ_PrintToChat(client, true, "%t", "Option - Show Players - Enable"); + } + } + } + case QTOption_Soundscapes: + { + switch (newValue) + { + case Soundscapes_Disabled: + { + GOKZ_PrintToChat(client, true, "%t", "Option - Soundscapes - Disable"); + } + case Soundscapes_Enabled: + { + GOKZ_PrintToChat(client, true, "%t", "Option - Soundscapes - Enable"); + } + } + } + } +} + +// =====[ OPTIONS MENU ]===== + +TopMenu gTM_Options; +TopMenuObject gTMO_CatQuiet; +TopMenuObject gTMO_ItemsQuiet[QTOPTION_COUNT]; + +void OnOptionsMenuCreated_OptionsMenu(TopMenu topMenu) +{ + if (gTM_Options == topMenu && gTMO_CatQuiet != INVALID_TOPMENUOBJECT) + { + return; + } + + gTMO_CatQuiet = topMenu.AddCategory(QUIET_OPTION_CATEGORY, TopMenuHandler_Categories); +} + +void OnOptionsMenuReady_OptionsMenu(TopMenu topMenu) +{ + // Make sure category exists + if (gTMO_CatQuiet == INVALID_TOPMENUOBJECT) + { + GOKZ_OnOptionsMenuCreated(topMenu); + } + + if (gTM_Options == topMenu) + { + return; + } + + gTM_Options = topMenu; + + // Add gokz-profile option items + for (int option = 0; option < view_as(QTOPTION_COUNT); option++) + { + gTMO_ItemsQuiet[option] = gTM_Options.AddItem(gC_QTOptionNames[option], TopMenuHandler_QT, gTMO_CatQuiet); + } +} + +public void TopMenuHandler_Categories(TopMenu topmenu, TopMenuAction action, TopMenuObject topobj_id, int param, char[] buffer, int maxlength) +{ + if (action == TopMenuAction_DisplayOption || action == TopMenuAction_DisplayTitle) + { + if (topobj_id == gTMO_CatQuiet) + { + Format(buffer, maxlength, "%T", "Options Menu - Quiet", param); + } + } +} + +public void TopMenuHandler_QT(TopMenu topmenu, TopMenuAction action, TopMenuObject topobj_id, int param, char[] buffer, int maxlength) +{ + QTOption option = QTOPTION_INVALID; + for (int i = 0; i < view_as(QTOPTION_COUNT); i++) + { + if (topobj_id == gTMO_ItemsQuiet[i]) + { + option = view_as(i); + break; + } + } + + if (option == QTOPTION_INVALID) + { + return; + } + + if (action == TopMenuAction_DisplayOption) + { + switch (option) + { + case QTOption_ShowPlayers: + { + FormatToggleableOptionDisplay(param, option, buffer, maxlength); + } + case QTOption_Soundscapes: + { + FormatToggleableOptionDisplay(param, option, buffer, maxlength); + } + case QTOption_FallDamageSound: + { + FormatVolumeOptionDisplay(param, option, buffer, maxlength); + } + case QTOption_AmbientSounds: + { + FormatVolumeOptionDisplay(param, option, buffer, maxlength); + } + case QTOption_CheckpointVolume: + { + FormatVolumeOptionDisplay(param, option, buffer, maxlength); + } + case QTOption_TeleportVolume: + { + FormatVolumeOptionDisplay(param, option, buffer, maxlength); + } + case QTOption_TimerVolume: + { + FormatVolumeOptionDisplay(param, option, buffer, maxlength); + } + case QTOption_ErrorVolume: + { + FormatVolumeOptionDisplay(param, option, buffer, maxlength); + } + case QTOption_ServerRecordVolume: + { + FormatVolumeOptionDisplay(param, option, buffer, maxlength); + } + case QTOption_WorldRecordVolume: + { + FormatVolumeOptionDisplay(param, option, buffer, maxlength); + } + case QTOption_JumpstatsVolume: + { + FormatVolumeOptionDisplay(param, option, buffer, maxlength); + } + } + } + else if (action == TopMenuAction_SelectOption) + { + GOKZ_CycleOption(param, gC_QTOptionNames[option]); + gTM_Options.Display(param, TopMenuPosition_LastCategory); + } +} + +void FormatToggleableOptionDisplay(int client, QTOption option, char[] buffer, int maxlength) +{ + if (GOKZ_GetOption(client, gC_QTOptionNames[option]) == 0) + { + FormatEx(buffer, maxlength, "%T - %T", + gC_QTOptionPhrases[option], client, + "Options Menu - Disabled", client); + } + else + { + FormatEx(buffer, maxlength, "%T - %T", + gC_QTOptionPhrases[option], client, + "Options Menu - Enabled", client); + } +} + +void FormatVolumeOptionDisplay(int client, QTOption option, char[] buffer, int maxlength) +{ + // Assume 10% volume steps. + FormatEx(buffer, maxlength, "%T - %i%", + gC_QTOptionPhrases[option], client, + GOKZ_QT_GetOption(client, option) * 10); +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/gokz-quiet/soundscape.sp b/addons/sourcemod/scripting/gokz-quiet/soundscape.sp new file mode 100644 index 00000000..5e740e12 --- /dev/null +++ b/addons/sourcemod/scripting/gokz-quiet/soundscape.sp @@ -0,0 +1,30 @@ +/* + Toggle soundscapes. +*/ + +static int currentSoundscapeIndex[MAXPLAYERS + 1] = {BLANK_SOUNDSCAPEINDEX, ...}; + +void EnableSoundscape(int client) +{ + if (currentSoundscapeIndex[client] != BLANK_SOUNDSCAPEINDEX) + { + SetEntProp(client, Prop_Data, "soundscapeIndex", currentSoundscapeIndex[client]); + } +} + +void OnPlayerRunCmdPost_Soundscape(int client) +{ + int soundscapeIndex = GetEntProp(client, Prop_Data, "soundscapeIndex"); + if (GOKZ_GetOption(client, gC_QTOptionNames[QTOption_Soundscapes]) == Soundscapes_Disabled) + { + if (soundscapeIndex != BLANK_SOUNDSCAPEINDEX) + { + currentSoundscapeIndex[client] = soundscapeIndex; + } + SetEntProp(client, Prop_Data, "soundscapeIndex", BLANK_SOUNDSCAPEINDEX); + } + else + { + currentSoundscapeIndex[client] = soundscapeIndex; + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/gokz-replays.sp b/addons/sourcemod/scripting/gokz-replays.sp index ce47b798..bc826c4c 100644 --- a/addons/sourcemod/scripting/gokz-replays.sp +++ b/addons/sourcemod/scripting/gokz-replays.sp @@ -36,12 +36,17 @@ public Plugin myinfo = #define UPDATER_URL GOKZ_UPDATER_BASE_URL..."gokz-replays.txt" +bool gB_GOKZHUD; bool gB_GOKZLocalDB; char gC_CurrentMap[64]; int gI_CurrentMapFileSize; bool gB_HideNameChange; bool gB_NubRecordMissed[MAXPLAYERS + 1]; ArrayList g_ReplayInfoCache; +Address gA_BotDuckAddr; +int gI_BotDuckPatchRestore[40]; // Size of patched section in gamedata +int gI_BotDuckPatchLength; + DynamicDetour gH_DHooks_TeamFull; #include "gokz-replays/commands.sp" @@ -81,7 +86,8 @@ public void OnAllPluginsLoaded() Updater_AddPlugin(UPDATER_URL); } gB_GOKZLocalDB = LibraryExists("gokz-localdb"); - + gB_GOKZHUD = LibraryExists("gokz-hud"); + for (int client = 1; client <= MaxClients; client++) { if (IsClientInGame(client)) @@ -98,14 +104,27 @@ public void OnLibraryAdded(const char[] name) Updater_AddPlugin(UPDATER_URL); } gB_GOKZLocalDB = gB_GOKZLocalDB || StrEqual(name, "gokz-localdb"); + gB_GOKZHUD = gB_GOKZHUD || StrEqual(name, "gokz-hud"); } public void OnLibraryRemoved(const char[] name) { gB_GOKZLocalDB = gB_GOKZLocalDB && !StrEqual(name, "gokz-localdb"); + gB_GOKZHUD = gB_GOKZHUD && !StrEqual(name, "gokz-hud"); } - +public void OnPluginEnd() +{ + // Restore bot auto duck behavior. + if (gA_BotDuckAddr == Address_Null) + { + return; + } + for (int i = 0; i < gI_BotDuckPatchLength; i++) + { + StoreToAddress(gA_BotDuckAddr + view_as
(i), gI_BotDuckPatchRestore[i], NumberType_Int8); + } +} // =====[ OTHER EVENTS ]===== @@ -204,12 +223,17 @@ public void OnClientDisconnect(int 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]) { - OnPlayerRunCmd_Playback(client, buttons); - return Plugin_Continue; + if (!IsFakeClient(client)) + { + return Plugin_Continue; + } + OnPlayerRunCmd_Playback(client, buttons, vel, angles); + return Plugin_Changed; } 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]) { + OnPlayerRunCmdPost_Playback(client); OnPlayerRunCmdPost_Recording(client, buttons, tickcount, vel, mouse); OnPlayerRunCmdPost_ReplayControls(client, cmdnum); } @@ -300,6 +324,15 @@ static void HookEvents() { SetFailState("Failed to enable detour on CCSGameRules::TeamFull"); } + + // Remove bot auto duck behavior. + gA_BotDuckAddr = gameData.GetAddress("BotDuck"); + gI_BotDuckPatchLength = gameData.GetOffset("BotDuckPatchLength"); + for (int i = 0; i < gI_BotDuckPatchLength; i++) + { + gI_BotDuckPatchRestore[i] = LoadFromAddress(gA_BotDuckAddr + view_as
(i), NumberType_Int8); + StoreToAddress(gA_BotDuckAddr + view_as
(i), 0x90, NumberType_Int8); + } delete gameData; } diff --git a/addons/sourcemod/scripting/gokz-replays/api.sp b/addons/sourcemod/scripting/gokz-replays/api.sp index 5aac2df1..3c115e1a 100644 --- a/addons/sourcemod/scripting/gokz-replays/api.sp +++ b/addons/sourcemod/scripting/gokz-replays/api.sp @@ -8,6 +8,7 @@ void CreateNatives() { CreateNative("GOKZ_RP_GetPlaybackInfo", Native_RP_GetPlaybackInfo); CreateNative("GOKZ_RP_LoadJumpReplay", Native_RP_LoadJumpReplay); + CreateNative("GOKZ_RP_UpdateReplayControlMenu", Native_RP_UpdateReplayControlMenu); } public int Native_RP_GetPlaybackInfo(Handle plugin, int numParams) @@ -28,6 +29,11 @@ public int Native_RP_LoadJumpReplay(Handle plugin, int numParams) return botClient; } +public int Native_RP_UpdateReplayControlMenu(Handle plugin, int numParams) +{ + return view_as(UpdateReplayControlMenu(GetNativeCell(1))); +} + // =====[ FORWARDS ]===== void CreateGlobalForwards() diff --git a/addons/sourcemod/scripting/gokz-replays/controls.sp b/addons/sourcemod/scripting/gokz-replays/controls.sp index df1943b5..cda7f07d 100644 --- a/addons/sourcemod/scripting/gokz-replays/controls.sp +++ b/addons/sourcemod/scripting/gokz-replays/controls.sp @@ -2,6 +2,10 @@ Lets player control the replay bot. */ +#define ITEM_INFO_PAUSE "pause" +#define ITEM_INFO_SKIP "skip" +#define ITEM_INFO_REWIND "rewind" +#define ITEM_INFO_FREECAM "freecam" static int controllingPlayer[RP_MAX_BOTS]; static int botTeleports[RP_MAX_BOTS]; @@ -13,24 +17,25 @@ static bool showReplayControls[MAXPLAYERS + 1]; void OnPlayerRunCmdPost_ReplayControls(int client, int cmdnum) { - if (cmdnum % 6 == 3) + // Let the HUD plugin takes care of this if possible. + if (cmdnum % 6 == 3 && !gB_GOKZHUD) { UpdateReplayControlMenu(client); } } -void UpdateReplayControlMenu(int client) +bool UpdateReplayControlMenu(int client) { if (!IsValidClient(client) || IsFakeClient(client)) { - return; + return false; } int botClient = GetObserverTarget(client); int bot = GetBotFromClient(botClient); if (bot == -1) { - return; + return false; } if (!IsReplayBotControlled(bot, botClient) && !InBreather(bot)) @@ -40,73 +45,92 @@ void UpdateReplayControlMenu(int client) } else if (controllingPlayer[bot] != client) { - return; + return false; } - if (showReplayControls[client] && - GOKZ_HUD_GetOption(client, HUDOption_ShowControls) == ReplayControls_Enabled && - (GetClientMenu(client) == MenuSource_None || - GetClientAvgLoss(client, NetFlow_Both) > EPSILON || - GOKZ_HUD_GetOption(client, HUDOption_TimerText) == TimerText_TPMenu)) + if (showReplayControls[client] && + GOKZ_HUD_GetOption(client, HUDOption_ShowControls) == ReplayControls_Enabled) { - botTeleports[bot] = PlaybackGetTeleports(bot); - ShowReplayControlMenu(client, bot); + // We have to update this often if bot uses teleports. + if (GetClientMenu(client) == MenuSource_None || + GOKZ_HUD_GetMenuShowing(client) && GetClientAvgLoss(client, NetFlow_Both) > EPSILON || + GOKZ_HUD_GetMenuShowing(client) && GOKZ_HUD_GetOption(client, HUDOption_TimerText) == TimerText_TPMenu || + GOKZ_HUD_GetMenuShowing(client) && PlaybackGetTeleports(bot) > 0) + { + botTeleports[bot] = PlaybackGetTeleports(bot); + ShowReplayControlMenu(client, bot); + } + return true; } + return false; } void ShowReplayControlMenu(int client, int bot) { - char text[32]; - - Panel panel = new Panel(); + char text[256]; - if (GOKZ_HUD_GetOption(client, HUDOption_TimerText) == TimerText_TPMenu) + Menu menu = new Menu(MenuHandler_ReplayControls); + menu.OptionFlags = MENUFLAG_NO_SOUND; + menu.Pagination = MENU_NO_PAGINATION; + menu.ExitButton = true; + if (gB_GOKZHUD) { - FormatEx(text, sizeof(text), "%T - %s", "Replay Controls - Title", client, - GOKZ_FormatTime(GetPlaybackTime(bot), GOKZ_HUD_GetOption(client, HUDOption_TimerStyle) == TimerStyle_Precise)); - panel.SetTitle(text); + if (GOKZ_HUD_GetOption(client, HUDOption_ShowSpectators) != ShowSpecs_Disabled && + GOKZ_HUD_GetOption(client, HUDOption_SpecListPosition) == SpecListPosition_TPMenu) + { + HUDInfo info; + GetPlaybackState(client, info); + GOKZ_HUD_GetMenuSpectatorText(client, info, text, sizeof(text)); + } + if (GOKZ_HUD_GetOption(client, HUDOption_TimerText) == TimerText_TPMenu) + { + Format(text, sizeof(text), "%s\n%T - %s", text, "Replay Controls - Title", client, + GOKZ_FormatTime(GetPlaybackTime(bot), GOKZ_HUD_GetOption(client, HUDOption_TimerStyle) == TimerStyle_Precise)); + } + else + { + Format(text, sizeof(text), "%s%T", text, "Replay Controls - Title", client); + } } else { - FormatEx(text, sizeof(text), "%T", "Replay Controls - Title", client); - panel.SetTitle(text); + Format(text, sizeof(text), "%s%T", text, "Replay Controls - Title", client); } - if(PlaybackGetTeleports(bot) > 0) + + if (botTeleports[bot] > 0) { - FormatEx(text, sizeof(text), "%T", "Replay Controls - Teleports", client, botTeleports[bot]); - panel.DrawItem(text, ITEMDRAW_RAWLINE); + Format(text, sizeof(text), "%s\n%T", text, "Replay Controls - Teleports", client, botTeleports[bot]); } + + menu.SetTitle(text); if (PlaybackPaused(bot)) { FormatEx(text, sizeof(text), "%T", "Replay Controls - Resume", client); - panel.DrawItem(text); + menu.AddItem(ITEM_INFO_PAUSE, text); } else { FormatEx(text, sizeof(text), "%T", "Replay Controls - Pause", client); - panel.DrawItem(text); + menu.AddItem(ITEM_INFO_PAUSE, text); } FormatEx(text, sizeof(text), "%T", "Replay Controls - Skip", client); - panel.DrawItem(text); + menu.AddItem(ITEM_INFO_SKIP, text); - FormatEx(text, sizeof(text), "%T", "Replay Controls - Rewind", client); - panel.DrawItem(text); - - panel.DrawItem("", ITEMDRAW_SPACER); + FormatEx(text, sizeof(text), "%T\n ", "Replay Controls - Rewind", client); + menu.AddItem(ITEM_INFO_REWIND, text); FormatEx(text, sizeof(text), "%T", "Replay Controls - Freecam", client); - panel.DrawItem(text); - - panel.DrawItem("", ITEMDRAW_SPACER); - - FormatEx(text, sizeof(text), "%T", "Replay Controls - Exit", client); - panel.DrawItem(text); + menu.AddItem(ITEM_INFO_FREECAM, text); - panel.Send(client, PanelHandler_ReplayControls, MENU_TIME_FOREVER); - delete panel; + menu.Display(client, MENU_TIME_FOREVER); + + if (gB_GOKZHUD) + { + GOKZ_HUD_SetMenuShowing(client, true); + } } void ToggleReplayControls(int client) @@ -133,7 +157,7 @@ bool IsReplayBotControlled(int bot, int botClient) GetEntProp(controllingPlayer[bot], Prop_Send, "m_iObserverMode") == 6); } -int PanelHandler_ReplayControls(Menu menu, MenuAction action, int param1, int param2) +int MenuHandler_ReplayControls(Menu menu, MenuAction action, int param1, int param2) { switch (action) { @@ -141,45 +165,48 @@ int PanelHandler_ReplayControls(Menu menu, MenuAction action, int param1, int pa { if (!IsValidClient(param1)) { - return 0; + return; } int bot = GetBotFromClient(GetObserverTarget(param1)); if (bot == -1 || controllingPlayer[bot] != param1) { - return 0; + return; } - // Pause/Resume - if (param2 == 1) + char info[16]; + menu.GetItem(param2, info, sizeof(info)); + if (StrEqual(info, ITEM_INFO_PAUSE, false)) { PlaybackTogglePause(bot); - ShowReplayControlMenu(param1, bot); } - // Forward - else if (param2 == 2) + else if (StrEqual(info, ITEM_INFO_SKIP, false)) { PlaybackSkipForward(bot); } - // Rewind - else if (param2 == 3) + else if (StrEqual(info, ITEM_INFO_REWIND, false)) { PlaybackSkipBack(bot); } - // Freecam - else if (param2 == 4) + else if (StrEqual(info, ITEM_INFO_FREECAM, false)) { SetEntProp(param1, Prop_Send, "m_iObserverMode", 6); } - // Exit - else if (param2 == 7) + GOKZ_HUD_SetMenuShowing(param1, false); + } + case MenuAction_Cancel: + { + GOKZ_HUD_SetMenuShowing(param1, false); + if (param2 == MenuCancel_Exit) { CancelReplayControls(param1); - delete menu; } } + case MenuAction_End: + { + delete menu; + } } - return 0; } void CancelReplayControls(int client) diff --git a/addons/sourcemod/scripting/gokz-replays/playback.sp b/addons/sourcemod/scripting/gokz-replays/playback.sp index 16ed9d05..b3f68659 100644 --- a/addons/sourcemod/scripting/gokz-replays/playback.sp +++ b/addons/sourcemod/scripting/gokz-replays/playback.sp @@ -52,6 +52,7 @@ static bool hitBhop[RP_MAX_BOTS]; static bool hitPerf[RP_MAX_BOTS]; static bool botJumped[RP_MAX_BOTS]; static bool botIsTakeoff[RP_MAX_BOTS]; +static bool botJustTeleported[RP_MAX_BOTS]; static float botLandingSpeed[RP_MAX_BOTS]; @@ -120,7 +121,7 @@ void GetPlaybackState(int client, HUDInfo info) info.TimerRunning = botReplayType[bot] == ReplayType_Jump ? false : true; if (botReplayVersion[bot] == 1) { - info.Time = botTime[bot]; + info.Time = playbackTick[bot] * GetTickInterval(); } else if (botReplayVersion[bot] == 2) { @@ -137,6 +138,7 @@ void GetPlaybackState(int client, HUDInfo info) info.Time = (playbackTick[bot] - preAndPostRunTickCount) * GetTickInterval(); } } + info.TimerRunning = true; info.TimeType = botTeleportsUsed[bot] > 0 ? TimeType_Nub : TimeType_Pro; info.Speed = botSpeed[bot]; info.Paused = false; @@ -220,7 +222,7 @@ void TrySkipToTime(int client, int seconds) return; } - int tick = seconds * 128; + int tick = seconds * 128 + preAndPostRunTickCount; int bot = GetBotFromClient(GetObserverTarget(client)); if (tick >= 0 && tick < playbackTickData[bot].Length) @@ -239,7 +241,7 @@ float GetPlaybackTime(int bot) { return 0.0; } - if (playbackTick[bot] >= playbackTickData[bot].Length - (preAndPostRunTickCount * 2)) + if (playbackTick[bot] >= playbackTickData[bot].Length - preAndPostRunTickCount) { return botTime[bot]; } @@ -301,7 +303,7 @@ void OnClientDisconnect_Playback(int client) } } -void OnPlayerRunCmd_Playback(int client, int &buttons) +void OnPlayerRunCmd_Playback(int client, int &buttons, float vel[3], float angles[3]) { if (!IsFakeClient(client)) { @@ -319,7 +321,24 @@ void OnPlayerRunCmd_Playback(int client, int &buttons) switch (botReplayVersion[bot]) { case 1: PlaybackVersion1(client, bot, buttons); - case 2: PlaybackVersion2(client, bot, buttons); + case 2: PlaybackVersion2(client, bot, buttons, vel, angles); + } + break; + } +} + +void OnPlayerRunCmdPost_Playback(int client) +{ + for (int bot; bot < RP_MAX_BOTS; bot++) + { + // Check if not the bot we're looking for + if (!botInGame[bot] || botClient[bot] != client || !botDataLoaded[bot]) + { + continue; + } + if (botReplayVersion[bot] == 2) + { + PlaybackVersion2Post(client, bot); } break; } @@ -337,7 +356,6 @@ void GOKZ_OnOptionsLoaded_Playback(int client) } } } - // =====[ PRIVATE ]===== // Returns false if there was a problem loading the playback e.g. doesn't exist @@ -882,7 +900,7 @@ static void PlaybackVersion1(int client, int bot, int &buttons) playbackTick[bot]++; } } -void PlaybackVersion2(int client, int bot, int &buttons) +void PlaybackVersion2(int client, int bot, int &buttons, float vel[3], float angles[3]) { int size = playbackTickData[bot].Length; ReplayTickData prevTickData; @@ -960,17 +978,20 @@ void PlaybackVersion2(int client, int bot, int &buttons) { GOKZ_EmitSoundToClientSpectators(client, gC_ModeEndSounds[GOKZ_GetCoreOption(client, Option_Mode)], _, "Timer End"); } - - // Set velocity to travel from current origin to recorded origin - float currentOrigin[3], velocity[3]; - Movement_GetOrigin(client, currentOrigin); - MakeVectorFromPoints(currentOrigin, currentTickData.origin, velocity); - ScaleVector(velocity, 1.0 / GetTickInterval()); - TeleportEntity(client, NULL_VECTOR, currentTickData.angles, velocity); + // We use the previous position/velocity data to recreate sounds accurately. + // This might not be necessary as we already did do this in OnPlayerRunCmdPost of last tick, + // but we do it again just in case the values don't match up somehow (eg. collision with moving objects?) + TeleportEntity(client, NULL_VECTOR, prevTickData.angles, prevTickData.velocity); + // TeleportEntity does not set the absolute origin and velocity so we need to do it + // to prevent inaccurate eye position interpolation. + SetEntPropVector(client, Prop_Data, "m_vecVelocity", prevTickData.velocity); + SetEntPropVector(client, Prop_Data, "m_vecAbsVelocity", prevTickData.velocity); - botSpeed[bot] = GetVectorHorizontalLength(currentTickData.velocity); + SetEntPropVector(client, Prop_Data, "m_vecAbsOrigin", prevTickData.origin); + SetEntPropVector(client, Prop_Data, "m_vecOrigin", prevTickData.origin); + - // Set buttons + // Set buttons and potential inputs. int newButtons; if (currentTickData.flags & RP_IN_ATTACK) { @@ -988,29 +1009,35 @@ void PlaybackVersion2(int client, int bot, int &buttons) { newButtons |= IN_DUCK; } + // Few assumptions here because the replay doesn't track them: Player doesn't use +klook or +strafe. + // If the assumptions are wrong we will just end up with wrong sound prediction, no big deal. if (currentTickData.flags & RP_IN_FORWARD) { newButtons |= IN_FORWARD; + vel[0] += RP_PLAYER_ACCELSPEED; } if (currentTickData.flags & RP_IN_BACK) { newButtons |= IN_BACK; - } - if (currentTickData.flags & RP_IN_LEFT) - { - newButtons |= IN_LEFT; - } - if (currentTickData.flags & RP_IN_RIGHT) - { - newButtons |= IN_RIGHT; + vel[0] -= RP_PLAYER_ACCELSPEED; } if (currentTickData.flags & RP_IN_MOVELEFT) { newButtons |= IN_MOVELEFT; + vel[1] -= RP_PLAYER_ACCELSPEED; } if (currentTickData.flags & RP_IN_MOVERIGHT) { newButtons |= IN_MOVERIGHT; + vel[1] += RP_PLAYER_ACCELSPEED; + } + if (currentTickData.flags & RP_IN_LEFT) + { + newButtons |= IN_LEFT; + } + if (currentTickData.flags & RP_IN_RIGHT) + { + newButtons |= IN_RIGHT; } if (currentTickData.flags & RP_IN_RELOAD) { @@ -1022,37 +1049,15 @@ void PlaybackVersion2(int client, int bot, int &buttons) } buttons = newButtons; botButtons[bot] = buttons; + // The angles might be wrong if the player teleports, but this should only affect sound prediction. + angles = currentTickData.angles; - int entityFlags = GetEntityFlags(client); // Set the bot's MoveType - MoveType replayMoveType = view_as(currentTickData.flags & RP_MOVETYPE_MASK); + MoveType replayMoveType = view_as(prevTickData.flags & RP_MOVETYPE_MASK); botMoveType[bot] = replayMoveType; - if (Movement_GetSpeed(client) > SPEED_NORMAL * 2) + if (replayMoveType == MOVETYPE_WALK) { - Movement_SetMovetype(client, MOVETYPE_NOCLIP); - } - else if (replayMoveType == MOVETYPE_WALK && currentTickData.flags & RP_FL_ONGROUND) - { - botPaused[bot] = false; - SetEntityFlags(client, entityFlags | FL_ONGROUND); Movement_SetMovetype(client, MOVETYPE_WALK); - // The bot is on the ground, so there must be a ground entity attributed to the bot. - int groundEnt = GetEntPropEnt(client, Prop_Send, "m_hGroundEntity"); - if (groundEnt == -1) - { - float endPosition[3], mins[3], maxs[3]; - GetEntPropVector(client, Prop_Send, "m_vecMaxs", maxs); - GetEntPropVector(client, Prop_Send, "m_vecMins", mins); - endPosition = currentTickData.origin; - endPosition[2] -= 2.0; - TR_TraceHullFilter(currentTickData.origin, endPosition, mins, maxs, MASK_PLAYERSOLID, TraceEntityFilterPlayers); - // This should always hit. - if (TR_DidHit()) - { - groundEnt = TR_GetEntityIndex(); - SetEntPropEnt(client, Prop_Data, "m_hGroundEntity", groundEnt); - } - } } else if (replayMoveType == MOVETYPE_LADDER) { @@ -1063,17 +1068,11 @@ void PlaybackVersion2(int client, int bot, int &buttons) { Movement_SetMovetype(client, MOVETYPE_NOCLIP); } - - if (currentTickData.flags & RP_UNDER_WATER) - { - SetEntityFlags(client, entityFlags | FL_INWATER); - } - // Set some variables if (currentTickData.flags & RP_TELEPORT_TICK) { + botJustTeleported[bot] = true; botCurrentTeleport[bot]++; - Movement_SetMovetype(client, MOVETYPE_NOCLIP); } if (currentTickData.flags & RP_TAKEOFF_TICK) @@ -1140,7 +1139,75 @@ void PlaybackVersion2(int client, int bot, int &buttons) PrintToServer("=============================================================="); } #endif + } +} + +void PlaybackVersion2Post(int client, int bot) +{ + if (botPlaybackPaused[bot]) + { + return; + } + int size = playbackTickData[bot].Length; + if (playbackTick[bot] != 0 && playbackTick[bot] != (size - 1)) + { + ReplayTickData currentTickData; + ReplayTickData prevTickData; + playbackTickData[bot].GetArray(playbackTick[bot], currentTickData); + playbackTickData[bot].GetArray(IntMax(playbackTick[bot] - 1, 0), prevTickData); + + // TeleportEntity does not set the absolute origin and velocity so we need to do it + // to prevent inaccurate eye position interpolation. + SetEntPropVector(client, Prop_Data, "m_vecVelocity", currentTickData.velocity); + SetEntPropVector(client, Prop_Data, "m_vecAbsVelocity", currentTickData.velocity); + + SetEntPropVector(client, Prop_Data, "m_vecAbsOrigin", currentTickData.origin); + SetEntPropVector(client, Prop_Data, "m_vecOrigin", currentTickData.origin); + SetEntPropFloat(client, Prop_Send, "m_angEyeAngles[0]", currentTickData.angles[0]); + SetEntPropFloat(client, Prop_Send, "m_angEyeAngles[1]", currentTickData.angles[1]); + + MoveType replayMoveType = view_as(currentTickData.flags & RP_MOVETYPE_MASK); + botMoveType[bot] = replayMoveType; + int entityFlags = GetEntityFlags(client); + if (replayMoveType == MOVETYPE_WALK) + { + if (currentTickData.flags & RP_FL_ONGROUND) + { + SetEntityFlags(client, entityFlags | FL_ONGROUND); + botPaused[bot] = false; + // The bot is on the ground, so there must be a ground entity attributed to the bot. + int groundEnt = GetEntPropEnt(client, Prop_Send, "m_hGroundEntity"); + if (groundEnt == -1 && botJustTeleported[bot]) + { + SetEntPropFloat(client, Prop_Send, "m_flFallVelocity", 0.0); + float endPosition[3], mins[3], maxs[3]; + GetEntPropVector(client, Prop_Send, "m_vecMaxs", maxs); + GetEntPropVector(client, Prop_Send, "m_vecMins", mins); + endPosition = currentTickData.origin; + endPosition[2] -= 2.0; + TR_TraceHullFilter(currentTickData.origin, endPosition, mins, maxs, MASK_PLAYERSOLID, TraceEntityFilterPlayers); + + // This should always hit. + if (TR_DidHit()) + { + groundEnt = TR_GetEntityIndex(); + SetEntPropEnt(client, Prop_Data, "m_hGroundEntity", groundEnt); + } + } + } + else + { + botJustTeleported[bot] = false; + } + } + + if (currentTickData.flags & RP_UNDER_WATER) + { + SetEntityFlags(client, entityFlags | FL_INWATER); + } + + botSpeed[bot] = GetVectorHorizontalLength(currentTickData.velocity); playbackTick[bot]++; } } diff --git a/addons/sourcemod/scripting/gokz-tpanglefix.sp b/addons/sourcemod/scripting/gokz-tpanglefix.sp new file mode 100644 index 00000000..6c3d392a --- /dev/null +++ b/addons/sourcemod/scripting/gokz-tpanglefix.sp @@ -0,0 +1,266 @@ +#include + +#include + +#include +#include + +#include + +#undef REQUIRE_EXTENSIONS +#undef REQUIRE_PLUGIN +#include + +#pragma newdecls required +#pragma semicolon 1 + + + +public Plugin myinfo = +{ + name = "GOKZ Teleport Angle Fix", + author = "zer0.k", + description = "Fix teleporting not modifying player's view angles due to packet loss", + version = GOKZ_VERSION, + url = "https://github.com/KZGlobalTeam/gokz" +}; + +#define UPDATER_URL GOKZ_UPDATER_BASE_URL..."gokz-tpanglefix.txt" + +TopMenu gTM_Options; +TopMenuObject gTMO_CatGeneral; +TopMenuObject gTMO_ItemTPAngleFix; +Address gA_ViewAnglePatchAddress; +bool gB_EnableFix[MAXPLAYERS + 1]; +DynamicDetour gH_WriteViewAngleUpdate; +int gI_ClientOffset; + +// =====[ PLUGIN EVENTS ]===== + +public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) +{ + RegPluginLibrary("gokz-tpanglefix"); + return APLRes_Success; +} + +public void OnPluginStart() +{ + LoadTranslations("gokz-common.phrases"); + LoadTranslations("gokz-tpanglefix.phrases"); + + SetupPatch(); + HookEvents(); + RegisterCommands(); +} + +void SetupPatch() +{ + GameData gamedataConf = LoadGameConfigFile("gokz-tpanglefix.games"); + if (gamedataConf == null) + { + SetFailState("Failed to load gokz-tpanglefix gamedata"); + } + // Get the patching address + Address addr = GameConfGetAddress(gamedataConf, "WriteViewAngleUpdate"); + if(addr == Address_Null) + { + SetFailState("Can't find WriteViewAngleUpdate address."); + } + + // Get the offset from the start of the signature to the start of our patch area. + int offset = GameConfGetOffset(gamedataConf, "WriteViewAngleUpdateReliableOffset"); + if(offset == -1) + { + SetFailState("Can't find WriteViewAngleUpdateReliableOffset in gamedata."); + } + gA_ViewAnglePatchAddress = view_as
(addr + view_as
(offset)); +} + +void HookEvents() +{ + GameData gamedataConf = LoadGameConfigFile("gokz-tpanglefix.games"); + if (gamedataConf == null) + { + SetFailState("Failed to load gokz-tpanglefix gamedata"); + } + gH_WriteViewAngleUpdate = DynamicDetour.FromConf(gamedataConf, "CGameClient::WriteViewAngleUpdate"); + + if (gH_WriteViewAngleUpdate == INVALID_HANDLE) + { + SetFailState("Failed to find CGameClient::WriteViewAngleUpdate function signature"); + } + + if (!gH_WriteViewAngleUpdate.Enable(Hook_Pre, DHooks_OnWriteViewAngleUpdate_Pre)) + { + SetFailState("Failed to enable detour on CGameClient::WriteViewAngleUpdate"); + } + // Prevent the server from crashing. + FindConVar("sv_parallel_sendsnapshot").SetBool(false); + gI_ClientOffset = gamedataConf.GetOffset("ClientIndexOffset"); + if (gI_ClientOffset == -1) + { + SetFailState("Failed to get ClientIndexOffset offset."); + } +} +public MRESReturn DHooks_OnWriteViewAngleUpdate_Pre(Address pThis) +{ + int client = LoadFromAddress(pThis + view_as
(gI_ClientOffset), NumberType_Int32); + if (gB_EnableFix[client]) + { + PatchAngleFix(); + } + else + { + RestoreAngleFix(); + } + return MRES_Ignored; +} + +void PatchAngleFix() +{ + if (LoadFromAddress(gA_ViewAnglePatchAddress, NumberType_Int8) == 0) + { + StoreToAddress(gA_ViewAnglePatchAddress, 1, NumberType_Int8); + } +} + +void RestoreAngleFix() +{ + if (LoadFromAddress(gA_ViewAnglePatchAddress, NumberType_Int8) == 1) + { + StoreToAddress(gA_ViewAnglePatchAddress, 0, NumberType_Int8); + } +} + +bool ToggleAngleFix(int client) +{ + gB_EnableFix[client] = !gB_EnableFix[client]; + return gB_EnableFix[client]; +} + +public void OnAllPluginsLoaded() +{ + if (LibraryExists("updater")) + { + Updater_AddPlugin(UPDATER_URL); + } + + TopMenu topMenu; + if (LibraryExists("gokz-core") && ((topMenu = GOKZ_GetOptionsTopMenu()) != null)) + { + GOKZ_OnOptionsMenuReady(topMenu); + } +} + +public void OnLibraryAdded(const char[] name) +{ + if (StrEqual(name, "updater")) + { + Updater_AddPlugin(UPDATER_URL); + } + +} + +// =====[ CLIENT EVENTS ]===== + +public void GOKZ_OnOptionChanged(int client, const char[] option, any newValue) +{ + OnOptionChanged_Options(client, option, newValue); +} + +// =====[ OTHER EVENTS ]===== + +public void GOKZ_OnOptionsMenuReady(TopMenu topMenu) +{ + OnOptionsMenuReady_Options(); + OnOptionsMenuReady_OptionsMenu(topMenu); +} + + +// =====[ OPTIONS ]===== + +void OnOptionsMenuReady_Options() +{ + RegisterOption(); +} + +void RegisterOption() +{ + GOKZ_RegisterOption(TPANGLEFIX_OPTION_NAME, TPANGLEFIX_OPTION_DESCRIPTION, + OptionType_Int, TPAngleFix_Disabled, 0, TPANGLEFIX_COUNT - 1); +} + +void OnOptionChanged_Options(int client, const char[] option, any newValue) +{ + if (StrEqual(option, TPANGLEFIX_OPTION_NAME)) + { + gB_EnableFix[client] = newValue; + switch (newValue) + { + case TPAngleFix_Disabled: + { + GOKZ_PrintToChat(client, true, "%t", "Option - TP Angle Fix - Disable"); + } + case TPAngleFix_Enabled: + { + GOKZ_PrintToChat(client, true, "%t", "Option - TP Angle Fix - Enable"); + } + } + } +} + +// =====[ OPTIONS MENU ]===== + +void OnOptionsMenuReady_OptionsMenu(TopMenu topMenu) +{ + if (gTM_Options == topMenu) + { + return; + } + + gTM_Options = topMenu; + gTMO_CatGeneral = gTM_Options.FindCategory(GENERAL_OPTION_CATEGORY); + gTMO_ItemTPAngleFix = gTM_Options.AddItem(TPANGLEFIX_OPTION_NAME, TopMenuHandler_TPAngleFix, gTMO_CatGeneral); +} + +public void TopMenuHandler_TPAngleFix(TopMenu topmenu, TopMenuAction action, TopMenuObject topobj_id, int param, char[] buffer, int maxlength) +{ + if (topobj_id != gTMO_ItemTPAngleFix) + { + return; + } + + if (action == TopMenuAction_DisplayOption) + { + if (GOKZ_GetOption(param, TPANGLEFIX_OPTION_NAME) == TPAngleFix_Disabled) + { + FormatEx(buffer, maxlength, "%T - %T", + "Options Menu - TP Angle Fix", param, + "Options Menu - Disabled", param); + } + else + { + FormatEx(buffer, maxlength, "%T - %T", + "Options Menu - TP Angle Fix", param, + "Options Menu - Enabled", param); + } + } + else if (action == TopMenuAction_SelectOption) + { + GOKZ_CycleOption(param, TPANGLEFIX_OPTION_NAME); + gTM_Options.Display(param, TopMenuPosition_LastCategory); + } +} + +// =====[ COMMANDS ]===== + +void RegisterCommands() +{ + RegConsoleCmd("sm_tpafix", CommandTPAFix, "[KZ] Toggle teleport angle fix."); +} + +public Action CommandTPAFix(int client, int args) +{ + GOKZ_SetOption(client, TPANGLEFIX_OPTION_NAME, ToggleAngleFix(client)); + return Plugin_Handled; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/include/gokz.inc b/addons/sourcemod/scripting/include/gokz.inc index 96e85c18..edbd8969 100644 --- a/addons/sourcemod/scripting/include/gokz.inc +++ b/addons/sourcemod/scripting/include/gokz.inc @@ -41,6 +41,8 @@ enum ObsMode #define PI 3.14159265359 #define SPEED_NORMAL 250.0 #define SPEED_NO_WEAPON 260.0 +#define FLOAT_MAX view_as(0x7F7FFFFF) +#define SF_BUTTON_USE_ACTIVATES 1024 #define IGNORE_JUMP_TIME 0.2 stock float PLAYER_MINS[3] = {-16.0, -16.0, 0.0}; stock float PLAYER_MAXS[3] = {16.0, 16.0, 72.0}; @@ -438,6 +440,8 @@ stock void TeleportPlayer(int client, const float origin[3], const float angles[ AcceptEntityInput(client, "ClearParent"); Movement_SetOrigin(client, origin); + Movement_SetVelocity(client, view_as( { 0.0, 0.0, 0.0 } )); + Movement_SetBaseVelocity(client, view_as( { 0.0, 0.0, 0.0 } )); if (setAngles) { // NOTE: changing angles with TeleportEntity can fail due to packet loss!!! @@ -548,7 +552,8 @@ stock void ForcePlayerDuck(int client) { // these are both necessary, because on their own the player will sometimes still be in a state that isn't fully ducked. SetEntPropFloat(client, Prop_Send, "m_flDuckAmount", 1.0, 0); - SetEntProp(client, Prop_Send, "m_bDucking", true); + SetEntProp(client, Prop_Send, "m_bDucking", false); + SetEntProp(client, Prop_Send, "m_bDucked", true); } /** @@ -1020,6 +1025,61 @@ stock int GOKZGetClientFromGameMovementAddress(Address addr, int offsetCGameMove return GOKZGetEntityFromAddress(playerAddr); } +/** + * Gets the nearest point in the oriented bounding box of an entity to a point. + * + * @param entity Entity index. + * @param origin Point's origin. + * @param result Result point. + */ +stock void CalcNearestPoint(int entity, float origin[3], float result[3]) +{ + float entOrigin[3], entMins[3], entMaxs[3], trueMins[3], trueMaxs[3]; + GetEntPropVector(entity, Prop_Send, "m_vecOrigin", entOrigin); + GetEntPropVector(entity, Prop_Send, "m_vecMaxs", entMaxs); + GetEntPropVector(entity, Prop_Send, "m_vecMins", entMins); + + AddVectors(entOrigin, entMins, trueMins); + AddVectors(entOrigin, entMaxs, trueMaxs); + + for (int i = 0; i < 3; i++) + { + result[i] = FloatClamp(origin[i], trueMins[i], trueMaxs[i]); + } +} + +/** + * Get the shortest distance from P to the (infinite) line through vLineA and vLineB. + * + * @param P Point's origin. + * @param vLineA Origin of the first point of the line. + * @param vLineB Origin of the first point of the line. + * @return The shortest distance from the point to the line. + */ +stock float CalcDistanceToLine(float P[3], float vLineA[3], float vLineB[3]) +{ + float vClosest[3]; + float vDir[3]; + float t; + float delta[3]; + SubtractVectors(vLineB, vLineA, vDir); + float div = GetVectorDotProduct(vDir, vDir); + if (div < EPSILON) + { + t = 0.0; + } + else + { + t = (GetVectorDotProduct(vDir, P) - GetVectorDotProduct(vDir, vLineA)) / div; + } + for (int i = 0; i < 3; i++) + { + vClosest[i] = vLineA[i] + vDir[i]*t; + } + SubtractVectors(P, vClosest, delta); + return GetVectorLength(delta); +} + /** * Gets the ideal amount of time the text should be held for HUD messages. * @@ -1034,4 +1094,4 @@ stock int GOKZGetClientFromGameMovementAddress(Address addr, int offsetCGameMove stock float GetTextHoldTime(int interval) { return 3 * interval * GetTickInterval(); -} \ No newline at end of file +} diff --git a/addons/sourcemod/scripting/include/gokz/core.inc b/addons/sourcemod/scripting/include/gokz/core.inc index c21678c6..fb450d10 100644 --- a/addons/sourcemod/scripting/include/gokz/core.inc +++ b/addons/sourcemod/scripting/include/gokz/core.inc @@ -79,6 +79,7 @@ enum Option: Option_ErrorSounds, Option_VirtualButtonIndicators, Option_TimerButtonZoneType, + Option_ButtonThroughPlayers, Option_Safeguard, OPTION_COUNT }; @@ -140,13 +141,20 @@ enum TIMERBUTTONZONETYPE_COUNT }; +enum +{ + ButtonThroughPlayers_Disabled = 0, + ButtonThroughPlayers_Enabled, + BUTTONTHROUGHPLAYERS_COUNT +}; + enum { Safeguard_Disabled = 0, Safeguard_EnabledNUB, Safeguard_EnabledPRO, SAFEGUARD_COUNT -} +}; enum { @@ -210,7 +218,7 @@ enum TriggerType // =====[ CONSTANTS ]===== -#define GOKZ_CHECKPOINT_VERSION 1 +#define GOKZ_CHECKPOINT_VERSION 2 #define GOKZ_MAX_CHECKPOINTS 2048 #define GOKZ_MAX_COURSES 100 @@ -285,6 +293,9 @@ enum TriggerType #define GOKZ_SAFEGUARD_RESTART_MIN_DELAY 0.6 #define GOKZ_SAFEGUARD_RESTART_MAX_DELAY 5.0 +// Prevents the player from retouching a trigger too often. +#define GOKZ_MAX_RETOUCH_TRIGGER_COUNT 4 + stock char gC_TimeTypeNames[TIMETYPE_COUNT][] = { "NUB", @@ -360,6 +371,7 @@ stock char gC_CoreOptionNames[OPTION_COUNT][] = "GOKZ - Error Sounds", "GOKZ - VB Indicators", "GOKZ - Timer Button Zone Type", + "GOKZ - Button Through Players", "GOKZ - Safeguard" }; @@ -373,7 +385,8 @@ stock char gC_CoreOptionDescriptions[OPTION_COUNT][] = "Error Sounds - 0 = Disabled, 1 = Enabled", "Virtual Button Indicators - 0 = Disabled, 1 = Enabled", "Timer Button Zone Type - 0 = Both buttons, 1 = Only end zone, 2 = Both zones", - "GOKZ - Safeguard - 0 = Disabled, 1 = Enabled (NUB), 2 = Enabled (PRO)" + "Button Through Players - 0 = Disabled, 1 = Enabled", + "Safeguard - 0 = Disabled, 1 = Enabled (NUB), 2 = Enabled (PRO)" }; stock char gC_CoreOptionPhrases[OPTION_COUNT][] = @@ -386,6 +399,7 @@ stock char gC_CoreOptionPhrases[OPTION_COUNT][] = "Options Menu - Error Sounds", "Options Menu - Virtual Button Indicators", "Options Menu - Timer Button Zone Type", + "Options Menu - Button Through Players", "Options Menu - Safeguard" }; @@ -413,6 +427,7 @@ stock int gI_CoreOptionCounts[OPTION_COUNT] = ERRORSOUNDS_COUNT, VIRTUALBUTTONINDICATORS_COUNT, TIMERBUTTONZONETYPE_COUNT, + BUTTONTHROUGHPLAYERS_COUNT, SAFEGUARD_COUNT }; @@ -426,6 +441,7 @@ stock int gI_CoreOptionDefaults[OPTION_COUNT] = ErrorSounds_Enabled, VirtualButtonIndicators_Disabled, TimerButtonZoneType_BothButtons, + ButtonThroughPlayers_Enabled, Safeguard_Disabled }; @@ -466,6 +482,7 @@ enum struct Checkpoint float angles[3]; float ladderNormal[3]; bool onLadder; + int groundEnt; void Create(int client) { @@ -473,6 +490,7 @@ enum struct Checkpoint Movement_GetEyeAngles(client, this.angles); GetEntPropVector(client, Prop_Send, "m_vecLadderNormal", this.ladderNormal); this.onLadder = Movement_GetMovetype(client) == MOVETYPE_LADDER; + this.groundEnt = GetEntPropEnt(client, Prop_Data, "m_hGroundEntity"); } } @@ -481,7 +499,7 @@ enum struct UndoTeleportData float tempOrigin[3]; float tempAngles[3]; float origin[3]; - float angles[3]; + float angles[3]; // Undo TP properties bool lastTeleportOnGround; bool lastTeleportInBhopTrigger; diff --git a/addons/sourcemod/scripting/include/gokz/hud.inc b/addons/sourcemod/scripting/include/gokz/hud.inc index 38b087f1..5d658ff8 100644 --- a/addons/sourcemod/scripting/include/gokz/hud.inc +++ b/addons/sourcemod/scripting/include/gokz/hud.inc @@ -26,8 +26,9 @@ enum HUDOption: HUDOption_ShowWeapon, HUDOption_ShowControls, HUDOption_DeadstrafeColor, - HUDOption_UpdateRate, HUDOption_ShowSpectators, + HUDOption_SpecListPosition, + HUDOption_UpdateRate, HUDOption_DynamicMenu, HUDOPTION_COUNT }; @@ -116,6 +117,12 @@ enum SHOWSPECS_COUNT }; +enum +{ + SpecListPosition_TPMenu = 0, + SpecListPosition_InfoPanel, + SPECLISTPOSITION_COUNT +} enum { @@ -176,8 +183,9 @@ stock char gC_HUDOptionNames[HUDOPTION_COUNT][] = "GOKZ HUD - Show Weapon", "GOKZ HUD - Show Controls", "GOKZ HUD - Dead Strafe", - "GOKZ HUD - Update Rate", "GOKZ HUD - Show Spectators", + "GOKZ HUD - Spec List Pos", + "GOKZ HUD - Update Rate", "GOKZ HUD - Dynamic Menu" }; @@ -193,8 +201,9 @@ stock char gC_HUDOptionDescriptions[HUDOPTION_COUNT][] = "Weapon Viewmodel - 0 = Disabled, 1 = Enabled", "Replay Controls Display - 0 = Disbled, 1 = Enabled", "Dead Strafe Indicator - 0 = Disabled, 1 = Enabled", - "HUD Update Rate - 0 = Slow, 1 = Fast", "Show Spectators - 0 = Disabled, 1 = Number Only, 2 = Number and Names", + "Spectator List Position - 0 = Teleport Menu, 2 = Center Panel", + "HUD Update Rate - 0 = Slow, 1 = Fast", "Dynamic Menu - 0 = Legacy, 1 = Disabled, 2 = Enabled" }; @@ -210,8 +219,9 @@ stock char gC_HUDOptionPhrases[HUDOPTION_COUNT][] = "Options Menu - Show Weapon", "Options Menu - Show Controls", "Options Menu - Dead Strafe Indicator", - "Options Menu - Update Rate", "Options Menu - Show Spectators", + "Options Menu - Spectator List Position", + "Options Menu - Update Rate", "Options Menu - Dynamic Menu" }; @@ -227,8 +237,9 @@ stock int gI_HUDOptionCounts[HUDOPTION_COUNT] = SHOWWEAPON_COUNT, REPLAYCONTROLS_COUNT, DEADSTRAFECOLOR_COUNT, - UPDATERATE_COUNT, SHOWSPECS_COUNT, + SPECLISTPOSITION_COUNT, + UPDATERATE_COUNT, DYNAMICMENU_COUNT }; @@ -244,8 +255,9 @@ stock int gI_HUDOptionDefaults[HUDOPTION_COUNT] = ShowWeapon_Enabled, ReplayControls_Enabled, DeadstrafeColor_Disabled, - UpdateRate_Slow, ShowSpecs_Disabled, + SpecListPosition_TPMenu, + UpdateRate_Slow, DynamicMenu_Legacy }; @@ -304,6 +316,12 @@ stock char gC_ShowSpecsPhrases[SHOWSPECS_COUNT][] = "Options Menu - Number and Names" }; +stock char gC_SpecListPositionPhrases[SPECLISTPOSITION_COUNT][] = +{ + "Options Menu - Teleport Menu", + "Options Menu - Info Panel" +}; + stock char gC_HUDUpdateRatePhrases[UPDATERATE_COUNT][]= { "Options Menu - Slow", @@ -319,6 +337,30 @@ stock char gC_DynamicMenuPhrases[DYNAMICMENU_COUNT][]= // =====[ NATIVES ]===== +/** + * Returns whether the GOKZ HUD menu is showing for a client. + * + * @param client Client index. + * @return Whether the GOKZ HUD menu is showing. + */ +native bool GOKZ_HUD_GetMenuShowing(int client); + +/** + * Sets whether the GOKZ HUD menu would be showing for a client. + * + * @param client Client index. + * @param value Whether the GOKZ HUD menu would be showing for a client. + */ +native void GOKZ_HUD_SetMenuShowing(int client, bool value); + +/** + * Gets the spectator text for the menu. Used by GOKZ-replays. + * + * @param client Client index. + * @param value Whether the GOKZ HUD menu would be showing for a client. + */ +native void GOKZ_HUD_GetMenuSpectatorText(int client, any[] info, char[] buffer, int size); + /** * Forces the client's TP menu to update. * @@ -418,6 +460,9 @@ public SharedPlugin __pl_gokz_hud = #if !defined REQUIRE_PLUGIN public void __pl_gokz_hud_SetNTVOptional() { + MarkNativeAsOptional("GOKZ_HUD_GetMenuShowing"); + MarkNativeAsOptional("GOKZ_HUD_SetMenuShowing"); + MarkNativeAsOptional("GOKZ_HUD_GetMenuSpectatorText"); MarkNativeAsOptional("GOKZ_HUD_ForceUpdateTPMenu"); } -#endif \ No newline at end of file +#endif diff --git a/addons/sourcemod/scripting/include/gokz/kzplayer.inc b/addons/sourcemod/scripting/include/gokz/kzplayer.inc index 49941903..8176d39a 100644 --- a/addons/sourcemod/scripting/include/gokz/kzplayer.inc +++ b/addons/sourcemod/scripting/include/gokz/kzplayer.inc @@ -456,7 +456,24 @@ methodmap KZPlayer < MovementAPIPlayer { this.SetHUDOption(HUDOption_ShowSpectators, value); } } - + + property int SpecListPosition { + public get() { + return this.GetHUDOption(HUDOption_SpecListPosition); + } + public set(int value){ + this.SetHUDOption(HUDOption_SpecListPosition, value); + } + } + + property bool MenuShowing { + public get() { + return GOKZ_HUD_GetMenuShowing(this.ID); + } + public set(bool value) { + GOKZ_HUD_SetMenuShowing(this.ID, value); + } + } property int DynamicMenu { public get() { return this.GetHUDOption(HUDOption_DynamicMenu); diff --git a/addons/sourcemod/scripting/include/gokz/quiet.inc b/addons/sourcemod/scripting/include/gokz/quiet.inc index c6d8d3a1..a328b7ee 100644 --- a/addons/sourcemod/scripting/include/gokz/quiet.inc +++ b/addons/sourcemod/scripting/include/gokz/quiet.inc @@ -1,6 +1,6 @@ /* gokz-quiet Plugin Include - + Website: https://bitbucket.org/kztimerglobalteam/gokz */ @@ -15,62 +15,120 @@ enum QTOption: { - QTOPTION_INVALID = -1, - QTOption_ShowPlayers, - QTOption_MapSounds, + QTOPTION_INVALID = -1, + QTOption_ShowPlayers, + QTOption_Soundscapes, + QTOption_FallDamageSound, + QTOption_AmbientSounds, + QTOption_CheckpointVolume, + QTOption_TeleportVolume, + QTOption_TimerVolume, + QTOption_ErrorVolume, + QTOption_ServerRecordVolume, + QTOption_WorldRecordVolume, + QTOption_JumpstatsVolume, QTOPTION_COUNT }; enum { - ShowPlayers_Disabled = 0, - ShowPlayers_Enabled, + ShowPlayers_Disabled = 0, + ShowPlayers_Enabled, SHOWPLAYERS_COUNT }; enum { - MapSounds_Enabled = 0, - MapSounds_Disabled, - MAPSOUNDS_COUNT + Soundscapes_Disabled = 0, + Soundscapes_Enabled, + SOUNDSCAPES_COUNT }; +// =====[ CONSTANTS ]===== +#define QUIET_OPTION_CATEGORY "Quiet" +#define DEFAULT_VOLUME 10 +#define VOLUME_COUNT 21 // Maximum of 200% -// =====[ CONSTANTS ]===== +#define EFFECT_IMPACT 8 +#define EFFECT_KNIFESLASH 2 +#define BLANK_SOUNDSCAPEINDEX 482 // Search for "coopcementplant.missionselect_blank" id with sv_soundscape_printdebuginfo. -stock char gC_QTOptionNames[QTOPTION_COUNT][] = +stock char gC_QTOptionNames[QTOPTION_COUNT][] = { "GOKZ QT - Show Players", - "GOKZ QT - Map Sounds" + "GOKZ QT - Soundscapes", + "GOKZ QT - Fall Damage Sound", + "GOKZ QT - Ambient Sounds", + "GOKZ QT - Checkpoint Volume", + "GOKZ QT - Teleport Volume", + "GOKZ QT - Timer Volume", + "GOKZ QT - Error Volume", + "GOKZ QT - Server Record Volume", + "GOKZ QT - World Record Volume", + "GOKZ QT - Jumpstats Volume" }; -stock char gC_QTOptionDescriptions[QTOPTION_COUNT][] = +stock char gC_QTOptionDescriptions[QTOPTION_COUNT][] = { "Visibility of Other Players - 0 = Disabled, 1 = Enabled", - "Play Ambient Map Sounds/Music - 0 = Enabled, 1 = Disabled" + "Play Soundscapes - 0 = Disabled, 1 = Enabled", + "Play Fall Damage Sound - 0 to 20 = 0% to 200%", + "Play Ambient Sounds - 0 to 20 = 0% to 200%", + "Checkpoint Volume - 0 to 20 = 0% to 200%", + "Teleport Volume - 0 to 20 = 0% to 200%", + "Timer Volume - 0 to 20 = 0% to 200%", + "Error Volume - 0 to 20 = 0% to 200%", + "Server Record Volume - 0 to 20 = 0% to 200%", + "World Record Volume - 0 to 20 = 0% to 200%", + "Jumpstats Volume - 0 to 20 = 0% to 200%" }; -stock int gI_QTOptionDefaultValues[QTOPTION_COUNT] = +stock int gI_QTOptionDefaultValues[QTOPTION_COUNT] = { ShowPlayers_Enabled, - MapSounds_Enabled + Soundscapes_Enabled, + DEFAULT_VOLUME, // Fall damage volume + DEFAULT_VOLUME, // Ambient volume + DEFAULT_VOLUME, // Checkpoint volume + DEFAULT_VOLUME, // Teleport volume + DEFAULT_VOLUME, // Timer volume + DEFAULT_VOLUME, // Error volume + DEFAULT_VOLUME, // Server Record Volume + DEFAULT_VOLUME, // World Record Volume + DEFAULT_VOLUME // Jumpstats Volume }; -stock int gI_QTOptionCounts[QTOPTION_COUNT] = +stock int gI_QTOptionCounts[QTOPTION_COUNT] = { SHOWPLAYERS_COUNT, - MAPSOUNDS_COUNT + SOUNDSCAPES_COUNT, + VOLUME_COUNT, // Fall damage volume + VOLUME_COUNT, // Ambient volume + VOLUME_COUNT, // Checkpoint volume + VOLUME_COUNT, // Teleport volume + VOLUME_COUNT, // Timer volume + VOLUME_COUNT, // Error volume + VOLUME_COUNT, // Server Record volume + VOLUME_COUNT, // World Record volume + VOLUME_COUNT // Jumpstats volume }; -stock char gC_QTOptionPhrases[QTOPTION_COUNT][] = +stock char gC_QTOptionPhrases[QTOPTION_COUNT][] = { "Options Menu - Show Players", - "Options Menu - Map Sounds" + "Options Menu - Soundscapes", + "Options Menu - Fall Damage Sounds", + "Options Menu - Ambient Sounds", + "Options Menu - Checkpoint Volume", + "Options Menu - Teleport Volume", + "Options Menu - Timer Volume", + "Options Menu - Error Volume", + "Options Menu - Server Record Volume", + "Options Menu - World Record Volume", + "Options Menu - Jumpstats Volume" }; - - // =====[ STOCKS ]===== /** @@ -135,13 +193,13 @@ stock bool GOKZ_QT_CycleOption(int client, QTOption option) // =====[ DEPENDENCY ]===== -public SharedPlugin __pl_gokz_quiet = +public SharedPlugin __pl_gokz_quiet = { - name = "gokz-quiet", - file = "gokz-quiet.smx", + name = "gokz-quiet", + file = "gokz-quiet.smx", #if defined REQUIRE_PLUGIN - required = 1, + required = 1, #else - required = 0, + required = 0, #endif -}; \ No newline at end of file +}; \ No newline at end of file diff --git a/addons/sourcemod/scripting/include/gokz/replays.inc b/addons/sourcemod/scripting/include/gokz/replays.inc index 9f601033..6aabdbd6 100644 --- a/addons/sourcemod/scripting/include/gokz/replays.inc +++ b/addons/sourcemod/scripting/include/gokz/replays.inc @@ -154,6 +154,8 @@ enum struct ReplayTickData #define RP_MAX_BHOP_GROUND_TICKS 5 #define RP_SKIP_TIME 10 // 10 seconds #define RP_MAX_DURATION 6451200 // 14 hours on 128 tick +#define RP_JUMP_STEP_SOUND_THRESHOLD 140.0 +#define RP_PLAYER_ACCELSPEED 450.0 #define RP_MOVETYPE_MASK (0xF) #define RP_IN_ATTACK (1 << 4) @@ -242,6 +244,12 @@ native int GOKZ_RP_GetPlaybackInfo(int client, any[] info); */ native int GOKZ_RP_LoadJumpReplay(int client, char[] path); +/** + * Called by the HUD to show the replay control menu. + * + * @param client Client index. + */ +native bool GOKZ_RP_UpdateReplayControlMenu(int client); // =====[ DEPENDENCY ]===== @@ -262,5 +270,6 @@ public void __pl_gokz_replays_SetNTVOptional() { MarkNativeAsOptional("GOKZ_RP_GetPlaybackInfo"); MarkNativeAsOptional("GOKZ_RP_LoadJumpReplay"); + MarkNativeAsOptional("GOKZ_RP_UpdateReplayControlMenu"); } #endif diff --git a/addons/sourcemod/scripting/include/gokz/tpanglefix.inc b/addons/sourcemod/scripting/include/gokz/tpanglefix.inc new file mode 100644 index 00000000..fc8faa29 --- /dev/null +++ b/addons/sourcemod/scripting/include/gokz/tpanglefix.inc @@ -0,0 +1,40 @@ +/* + gokz-tpanglefix Plugin Include + + Website: https://github.com/KZGlobalTeam/gokz +*/ + +#if defined _gokz_tpanglefix_included_ +#endinput +#endif +#define _gokz_tpanglefix_included_ + + +// =====[ ENUMS ]===== + +enum +{ + TPAngleFix_Disabled = 0, + TPAngleFix_Enabled, + TPANGLEFIX_COUNT +}; + + +// =====[ CONSTANTS ]===== + +#define TPANGLEFIX_OPTION_NAME "GOKZ - TPAngleFix" +#define TPANGLEFIX_OPTION_DESCRIPTION "TPAngleFix - 0 = Disabled, 1 = Enabled" + + +// =====[ DEPENDENCY ]===== + +public SharedPlugin __pl_gokz_tpanglefix = +{ + name = "gokz-tpanglefix", + file = "gokz-tpanglefix.smx", + #if defined REQUIRE_PLUGIN + required = 1, + #else + required = 0, + #endif +}; \ No newline at end of file diff --git a/addons/sourcemod/scripting/include/movementapi.inc b/addons/sourcemod/scripting/include/movementapi.inc index c0d87e3c..290c3f2a 100644 --- a/addons/sourcemod/scripting/include/movementapi.inc +++ b/addons/sourcemod/scripting/include/movementapi.inc @@ -107,6 +107,28 @@ forward void Movement_OnStopDucking(int client); */ forward void Movement_OnPlayerJump(int client, bool jumpbug); +/** + * Called before PlayerMove movement function is called. + * Modifying origin or velocity parameters will change player's origin and velocity accordingly. + * + * @param client Client index. + * @param origin Player origin. + * @param velocity Player velocity. + * @return Plugin_Changed if origin or velocity is changed, Plugin_Continue otherwise. + */ +forward Action Movement_OnPlayerMovePre(int client, float origin[3], float velocity[3]); + +/** + * Called after PlayerMove movement function is called. + * Modifying origin or velocity parameters will change player's origin and velocity accordingly. + * + * @param client Client index. + * @param origin Player origin. + * @param velocity Player velocity. + * @return Plugin_Changed if origin or velocity is changed, Plugin_Continue otherwise. + */ +forward Action Movement_OnPlayerMovePost(int client, float origin[3], float velocity[3]); + /** * Called before Duck movement function is called. * Modifying origin or velocity parameters will change player's origin and velocity accordingly. @@ -638,4 +660,4 @@ public void __pl_movementapi_SetNTVOptional() MarkNativeAsOptional("Movement_SetLandingOrigin"); MarkNativeAsOptional("Movement_SetLandingVelocity"); } -#endif +#endif \ No newline at end of file diff --git a/addons/sourcemod/scripting/momsurffix/gametrace.sp b/addons/sourcemod/scripting/momsurffix/gametrace.sp index 25d7892a..e8db1ad9 100644 --- a/addons/sourcemod/scripting/momsurffix/gametrace.sp +++ b/addons/sourcemod/scripting/momsurffix/gametrace.sp @@ -75,7 +75,7 @@ methodmap Cplane_t < AddressBase property float dist { - public get() { return view_as(LoadFromAddress(this.Address + offsets.cptoffsets.normal, NumberType_Int32)); } + public get() { return view_as(LoadFromAddress(this.Address + offsets.cptoffsets.dist, NumberType_Int32)); } } property char type @@ -297,7 +297,7 @@ methodmap CTraceFilterSimple < AllocatableBase property Address m_pExtraShouldHitCheckFunction { public get() { return view_as
(LoadFromAddress(this.Address + offsets.ctfsoffsets.m_pExtraShouldHitCheckFunction, NumberType_Int32)); } - public set(Address _checkfnc) { StoreToAddress(this.Address + offsets.ctfsoffsets.m_pExtraShouldHitCheckFunction, view_as(_checkfnc), NumberType_Int32), false; } + public set(Address _checkfnc) { StoreToAddress(this.Address + offsets.ctfsoffsets.m_pExtraShouldHitCheckFunction, view_as(_checkfnc), NumberType_Int32, false); } } public CTraceFilterSimple() @@ -477,4 +477,4 @@ stock void TraceRayAgainstLeafAndEntityList(Ray_t ray, ITraceListData traceData, stock void TraceRay(Ray_t ray, int mask, CTraceFilterSimple filter, CGameTrace trace) { SDKCall(gTraceRay, gEngineTrace, ray.Address, mask, filter.Address, trace.Address); -} \ No newline at end of file +} diff --git a/addons/sourcemod/translations/gokz-core.phrases.txt b/addons/sourcemod/translations/gokz-core.phrases.txt index 139e7644..b5c661ef 100644 --- a/addons/sourcemod/translations/gokz-core.phrases.txt +++ b/addons/sourcemod/translations/gokz-core.phrases.txt @@ -322,6 +322,10 @@ "en" "Timer button/zone type" "chi" "计时器模式手动/自动" } + "Options Menu - Button Through Players" + { + "en" "Press button through players" + } "Options Menu - Safeguard" { "en" "Run safeguard" diff --git a/addons/sourcemod/translations/gokz-hud.phrases.txt b/addons/sourcemod/translations/gokz-hud.phrases.txt index 787cb6c1..e1cfedae 100644 --- a/addons/sourcemod/translations/gokz-hud.phrases.txt +++ b/addons/sourcemod/translations/gokz-hud.phrases.txt @@ -89,7 +89,7 @@ } "Option - Show Spectators - Disable" { - "en" "{grey}Spectators are now not shown." + "en" "{grey}Spectators are no longer shown." "chi" "{grey}已隐藏观察者." } "Option - Show Spectators - Number" @@ -117,7 +117,14 @@ "en" "{grey}Dynamic menu enabled. Unavailable options are now unselectable. This option is not recommended with high latency!" "chi" "{grey}已启用动态菜单。不可用选项现在不可选择。对于高延迟,不建议使用此选项!" } - + "Option - Spectator List Position - Info Panel" + { + "en" "{grey}Spectator list now displays in the centre panel." + } + "Option - Spectator List Position - TP Menu" + { + "en" "{grey}Spectator list now displays in the teleport menu." + } // =====[ INFO PANEL ]===== "Info Panel Text - Time" @@ -317,6 +324,10 @@ "en" "Number and Names" "chi" "人数和名称" } + "Options Menu - Spectator List Position" + { + "en" "Spectator list position" + } "Options Menu - Dynamic Menu" { "en" "Dynamic menu" @@ -336,4 +347,36 @@ "chi" "开始!" "ru" "НАЧАЛИ!" } + + // =====[ SPECTATOR LIST ]===== + "Spectator List - Menu (Number)" + { + "#format" "{1:d}" + "en" "Specs: {1}" + } + "Spectator List - Menu (Full)" + { + "#format" "{1:d},{2:s}" + "en" "Specs ({1})\n{2}" + } + "Spectator List - Info Panel (Number)" + { + // Specs: 2 + "#format" "{1:d}" + "en" "Specs: {1}" + } + "Spectator List - Info Panel (Full)" + { + // Specs (2): P1, P2 + "#format" "{1:d},{2:s}" + "en" "Specs ({1}): {2}" + } + "Options Menu - Number" + { + "en" "Number" + } + "Options Menu - Number & Names" + { + "en" "Number & names" + } } \ No newline at end of file diff --git a/addons/sourcemod/translations/gokz-quiet.phrases.txt b/addons/sourcemod/translations/gokz-quiet.phrases.txt index 11eb0c0a..6025e22c 100644 --- a/addons/sourcemod/translations/gokz-quiet.phrases.txt +++ b/addons/sourcemod/translations/gokz-quiet.phrases.txt @@ -19,31 +19,65 @@ Phrases "chi" "{grey}已屏蔽其他玩家模型." "ru" "{grey}Теперь вы не видите остальных игроков." } - "Option - Map Sounds - Enable" + "Option - Soundscapes - Enable" { - "en" "{grey}Map sounds are now enabled." - "chi" "{grey}地图音乐已开启." - "ru" "{grey}Звуки карты теперь включены." + "en" "{grey}Soundscapes are now enabled." } - "Option - Map Sounds - Disable" + "Option - Soundscapes - Disable" { - "en" "{grey}Map sounds are now disabled." - "chi" "{grey}地图音乐已关闭." - "ru" "{grey}Звуки карты теперь выключены." + "en" "{grey}Soundscapes are now disabled." } // =====[ OPTIONS MENU ]===== + "Options Menu - Quiet" + { + "en" "Quiet" + } "Options Menu - Show Players" { "en" "Show players" "chi" "屏蔽其他玩家" "ru" "Показать игроков" } - "Options Menu - Map Sounds" + "Options Menu - Soundscapes" + { + "en" "Play soundscapes" + } + "Options Menu - Fall Damage Sounds" + { + "en" "Fall damage sounds" + } + "Options Menu - Ambient Sounds" + { + "en" "Ambient sounds" + } + "Options Menu - Checkpoint Volume" + { + "en" "Checkpoint volume" + } + "Options Menu - Teleport Volume" + { + "en" "Teleport volume" + } + "Options Menu - Timer Volume" + { + "en" "Timer volume" + } + "Options Menu - Error Volume" + { + "en" "Error volume" + } + "Options Menu - Server Record Volume" + { + "en" "Server record volume" + } + "Options Menu - World Record Volume" + { + "en" "World record volume" + } + "Options Menu - Jumpstats Volume" { - "en" "Play map sounds" - "chi" "播放地图音乐" - "ru" "Play map sounds" + "en" "Jumpstats volume" } } \ No newline at end of file diff --git a/addons/sourcemod/translations/gokz-tips-tpanglefix.phrases.txt b/addons/sourcemod/translations/gokz-tips-tpanglefix.phrases.txt new file mode 100644 index 00000000..48961c15 --- /dev/null +++ b/addons/sourcemod/translations/gokz-tips-tpanglefix.phrases.txt @@ -0,0 +1,7 @@ +"Phrases" +{ + "TP Angle Fix" + { + "en" "{grey}Having high packet loss? Type {default}!tpafix {grey} to fix teleport angles being broken." + } +} \ No newline at end of file diff --git a/addons/sourcemod/translations/gokz-tpanglefix.phrases.txt b/addons/sourcemod/translations/gokz-tpanglefix.phrases.txt new file mode 100644 index 00000000..f60d187d --- /dev/null +++ b/addons/sourcemod/translations/gokz-tpanglefix.phrases.txt @@ -0,0 +1,19 @@ +"Phrases" +{ + // =====[ CHAT MESSAGES ]===== + "Option - TP Angle Fix - Enable" + { + "en" "{grey}Teleport angle fix enabled. All angle changes due to teleporting will be guaranteed. This helps with high packet loss situations." + } + "Option - TP Angle Fix - Disable" + { + "en" "{grey}Teleport angle fix disabled." + } + + + // =====[ OPTIONS MENU ]===== + "Options Menu - TP Angle Fix" + { + "en" "Reliable teleport angles" + } +} \ No newline at end of file diff --git a/cfg/sourcemod/gokz/gokz.cfg b/cfg/sourcemod/gokz/gokz.cfg index a0ed7bc8..b1c68c71 100644 --- a/cfg/sourcemod/gokz/gokz.cfg +++ b/cfg/sourcemod/gokz/gokz.cfg @@ -53,7 +53,7 @@ mp_ignore_round_win_conditions 1 mp_match_end_changelevel 1 sv_ignoregrenaderadio 1 sv_disable_radar 1 -mp_footsteps_serverside 1 +mp_footsteps_serverside 0 sv_mincmdrate 128 sv_minupdaterate 128 mp_warmuptime_all_players_connected 0 diff --git a/cfg/sourcemod/gokz/options_menu_sorting.cfg b/cfg/sourcemod/gokz/options_menu_sorting.cfg index 655b8cb8..d08deb9f 100644 --- a/cfg/sourcemod/gokz/options_menu_sorting.cfg +++ b/cfg/sourcemod/gokz/options_menu_sorting.cfg @@ -10,7 +10,6 @@ "item" "GOKZ - Checkpoint Sounds" "item" "GOKZ - Teleport Sounds" "item" "GOKZ - Error Sounds" - "item" "GOKZ QT - Show Players" "item" "GOKZ - Pistol" "item" "GOKZ JB - Jump Beam Type" "item" "GOKZ - Auto Restart" @@ -20,6 +19,7 @@ "item" "GOKZ DB - Auto Load Timer Setup" "item" "GOKZ - Timer Button Zone Type" "item" "GOKZ QT - Map Sounds" + "item" "GOKZ - Button Through Players" } "HUD" { @@ -35,6 +35,8 @@ "item" "GOKZ HUD - Show Weapon" "item" "GOKZ HUD - Show Controls" "item" "GOKZ HUD - Show Time Type" + "item" "GOKZ HUD - Spectator List" + "item" "GOKZ HUD - Spectator List Position" } "Jumpstats" { @@ -60,4 +62,18 @@ "item" "GOKZ Profile - Show Rank Chat" "item" "GOKZ Profile - Show Rank Clan" } + "Quiet" + { + "item" "GOKZ QT - Show Players" + "item" "GOKZ QT - Soundscapes" + "item" "GOKZ QT - Fall Damage Sound" + "item" "GOKZ QT - Ambient Sounds" + "item" "GOKZ QT - Checkpoint Volume" + "item" "GOKZ QT - Teleport Volume" + "item" "GOKZ QT - Timer Volume" + "item" "GOKZ QT - Error Volume" + "item" "GOKZ QT - Server Record Volume" + "item" "GOKZ QT - World Record Volume" + "item" "GOKZ QT - Jumpstats Volume" + } }