diff --git a/.gitignore b/.gitignore index 3885073..3ae6c4e 100644 --- a/.gitignore +++ b/.gitignore @@ -6,12 +6,11 @@ /Build/**/* /System/ -/IG+ Extras/ -/OtherMods/ +/Extras/ build_all.sh build.sh .env .cursorignore -.DS_Store \ No newline at end of file +.DS_Store diff --git a/Classes/ClientSettings.uc b/Classes/ClientSettings.uc index 81616a5..12d8ef6 100644 --- a/Classes/ClientSettings.uc +++ b/Classes/ClientSettings.uc @@ -143,6 +143,7 @@ var config bool bSniperUseClientSideAnimations; var config bool bTranslocatorUseClientSideAnimations; var config float MenuX, MenuY, MenuWidth, MenuHeight; +var config string ServerAdminPasswords[16]; var Sound DefaultHitSound[16]; var Sound LoadedHitSound[16]; diff --git a/Classes/IGPlus_InterpSnapshot.uc b/Classes/IGPlus_InterpSnapshot.uc new file mode 100644 index 0000000..306223c --- /dev/null +++ b/Classes/IGPlus_InterpSnapshot.uc @@ -0,0 +1,19 @@ +class IGPlus_InterpSnapshot extends Object; + +var vector Loc; +var vector Vel; +var vector RelLoc; +var vector MoverLoc; +var rotator Rot; +var name AnimSeq; +var byte AnimFrameByte; +var byte AnimIntentBits; +var byte AnimDodgeDir; +var byte AnimPhysics; +var float Time; +var bool bBasedOnMover; +var Actor BaseMover; + +defaultproperties +{ +} diff --git a/Classes/IGPlus_ServerSettingsContent.uc b/Classes/IGPlus_ServerSettingsContent.uc new file mode 100644 index 0000000..f38c6ba --- /dev/null +++ b/Classes/IGPlus_ServerSettingsContent.uc @@ -0,0 +1,1338 @@ +class IGPlus_ServerSettingsContent extends UMenuPageWindow; + +enum EEditControlType { + ECT_Text, + ECT_Integer, + ECT_Real +}; + +var UWindowLabelControl Lbl_Header; +var localized string HeaderText; + +var UWindowLabelControl Lbl_Status; +var localized string StatusAdminText; +var localized string StatusLoginText; +var localized string StatusLoadingText; +var localized string StatusLoginPendingText; + +var UWindowLabelControl Lbl_MoreInformation; +var localized string MoreInformationText; + +var UWindowLabelControl Lbl_Login; +var localized string LoginText; + +var IGPlus_ComboBox Cmb_AdminPassword; +var localized string AdminPasswordText; +var localized string AdminPasswordHelp; +var IGPlus_Button Btn_DeletePassword; +var localized string DeletePasswordButtonText; +var localized string DeletePasswordButtonHelp; + +var IGPlus_Button Btn_AdminAuth; +var localized string LoginButtonText; +var localized string LoginButtonHelp; +var localized string LogoutButtonText; +var localized string LogoutButtonHelp; + +var localized string LoginRequiredText; + +var UWindowLabelControl Lbl_General; +var localized string GeneralText; +var UWindowCheckbox Chk_bAutoPause; +var localized string bAutoPauseText; +var localized string bAutoPauseHelp; +var IGPlus_EditControl Edit_PauseTotalTime; +var localized string PauseTotalTimeText; +var localized string PauseTotalTimeHelp; +var IGPlus_EditControl Edit_PauseTime; +var localized string PauseTimeText; +var localized string PauseTimeHelp; +var UWindowCheckbox Chk_bForceDemo; +var localized string bForceDemoText; +var localized string bForceDemoHelp; +var UWindowCheckbox Chk_bRestrictTrading; +var localized string bRestrictTradingText; +var localized string bRestrictTradingHelp; +var IGPlus_EditControl Edit_MaxTradeTimeMargin; +var localized string MaxTradeTimeMarginText; +var localized string MaxTradeTimeMarginHelp; +var IGPlus_EditControl Edit_TradePingMargin; +var localized string TradePingMarginText; +var localized string TradePingMarginHelp; +var IGPlus_EditControl Edit_KillCamDelay; +var localized string KillCamDelayText; +var localized string KillCamDelayHelp; +var IGPlus_EditControl Edit_KillCamDuration; +var localized string KillCamDurationText; +var localized string KillCamDurationHelp; +var IGPlus_ComboBox Cmb_BrightskinMode; +var localized string BrightskinModeText; +var localized string BrightskinModeHelp; +var localized string BrightskinModeDisabled; +var localized string BrightskinModeUnlit; +var IGPlus_EditControl Edit_PlayerScale; +var localized string PlayerScaleText; +var localized string PlayerScaleHelp; +var UWindowCheckbox Chk_bAlwaysRenderFlagCarrier; +var localized string bAlwaysRenderFlagCarrierText; +var localized string bAlwaysRenderFlagCarrierHelp; +var UWindowCheckbox Chk_bAlwaysRenderDroppedFlags; +var localized string bAlwaysRenderDroppedFlagsText; +var localized string bAlwaysRenderDroppedFlagsHelp; +var IGPlus_ComboBox Cmb_HitFeedbackMode; +var localized string HitFeedbackModeText; +var localized string HitFeedbackModeHelp; +var localized string HitFeedbackModeDisabled; +var localized string HitFeedbackModeVisibleOnly; +var localized string HitFeedbackModeAlways; +var localized string bEnablePingCompensatedSpawnHelp; + +var UWindowLabelControl Lbl_Movement; +var localized string MovementText; +var UWindowCheckbox Chk_bJumpingPreservesMomentum; +var localized string bJumpingPreservesMomentumText; +var localized string bJumpingPreservesMomentumHelp; +var UWindowCheckbox Chk_bOldLandingMomentum; +var localized string bOldLandingMomentumText; +var localized string bOldLandingMomentumHelp; +var UWindowCheckbox Chk_bEnableSingleButtonDodge; +var localized string bEnableSingleButtonDodgeText; +var localized string bEnableSingleButtonDodgeHelp; +var UWindowCheckbox Chk_bUseFlipAnimation; +var localized string bUseFlipAnimationText; +var localized string bUseFlipAnimationHelp; +var UWindowCheckbox Chk_bEnableWallDodging; +var localized string bEnableWallDodgingText; +var localized string bEnableWallDodgingHelp; +var UWindowCheckbox Chk_bDodgePreserveZMomentum; +var localized string bDodgePreserveZMomentumText; +var localized string bDodgePreserveZMomentumHelp; +var IGPlus_EditControl Edit_MaxMultiDodges; +var localized string MaxMultiDodgesText; +var localized string MaxMultiDodgesHelp; +var UWindowCheckbox Chk_bEnablePingCompensatedSpawn; +var localized string bEnablePingCompensatedSpawnText; + +var UWindowLabelControl Lbl_Networking; +var localized string NetworkingText; +var IGPlus_EditControl Edit_MaxPosError; +var localized string MaxPosErrorText; +var localized string MaxPosErrorHelp; +var IGPlus_EditControl Edit_MaxHitError; +var localized string MaxHitErrorText; +var localized string MaxHitErrorHelp; +var IGPlus_EditControl Edit_MaxJitterTime; +var localized string MaxJitterTimeText; +var localized string MaxJitterTimeHelp; +var IGPlus_EditControl Edit_WarpFixDelay; +var localized string WarpFixDelayText; +var localized string WarpFixDelayHelp; +var IGPlus_EditControl Edit_FireTimeout; +var localized string FireTimeoutText; +var localized string FireTimeoutHelp; +var IGPlus_EditControl Edit_MinNetUpdateRate; +var localized string MinNetUpdateRateText; +var localized string MinNetUpdateRateHelp; +var IGPlus_EditControl Edit_MaxNetUpdateRate; +var localized string MaxNetUpdateRateText; +var localized string MaxNetUpdateRateHelp; +var UWindowCheckbox Chk_bEnableInputReplication; +var localized string bEnableInputReplicationText; +var localized string bEnableInputReplicationHelp; +var UWindowCheckbox Chk_bEnableServerExtrapolation; +var localized string bEnableServerExtrapolationText; +var localized string bEnableServerExtrapolationHelp; +var UWindowCheckbox Chk_bEnableServerPacketReordering; +var localized string bEnableServerPacketReorderingText; +var localized string bEnableServerPacketReorderingHelp; +var UWindowCheckbox Chk_bEnableLoosePositionCheck; +var localized string bEnableLoosePositionCheckText; +var localized string bEnableLoosePositionCheckHelp; +var UWindowCheckbox Chk_bPlayersAlwaysRelevant; +var localized string bPlayersAlwaysRelevantText; +var localized string bPlayersAlwaysRelevantHelp; +var UWindowCheckbox Chk_bEnableJitterBounding; +var localized string bEnableJitterBoundingText; +var localized string bEnableJitterBoundingHelp; +var IGPlus_EditControl Edit_LooseCheckCorrectionFactor; +var localized string LooseCheckCorrectionFactorText; +var localized string LooseCheckCorrectionFactorHelp; +var IGPlus_EditControl Edit_LooseCheckCorrectionFactorOnMover; +var localized string LooseCheckCorrectionFactorOnMoverText; +var localized string LooseCheckCorrectionFactorOnMoverHelp; +var UWindowCheckbox Chk_bEnableSnapshotInterpolation; +var localized string bEnableSnapshotInterpolationText; +var localized string bEnableSnapshotInterpolationHelp; +var IGPlus_EditControl Edit_SnapshotInterpSendHz; +var localized string SnapshotInterpSendHzText; +var localized string SnapshotInterpSendHzHelp; +var IGPlus_EditControl Edit_SnapshotInterpRewindMs; +var localized string SnapshotInterpRewindMsText; +var localized string SnapshotInterpRewindMsHelp; +var UWindowCheckbox Chk_bEnableWarpFix; +var localized string bEnableWarpFixText; +var localized string bEnableWarpFixHelp; + +var UWindowLabelControl Lbl_Debug; +var localized string DebugText; +var UWindowCheckbox Chk_ShowTouchedPackage; +var localized string ShowTouchedPackageText; +var localized string ShowTouchedPackageHelp; +var UWindowCheckbox Chk_bEnableDamageDebugMode; +var localized string bEnableDamageDebugModeText; +var localized string bEnableDamageDebugModeHelp; +var UWindowCheckbox Chk_bEnableDamageDebugConsoleMessages; +var localized string bEnableDamageDebugConsoleMessagesText; +var localized string bEnableDamageDebugConsoleMessagesHelp; +var UWindowCheckbox Chk_bEnableHitboxDebugMode; +var localized string bEnableHitboxDebugModeText; +var localized string bEnableHitboxDebugModeHelp; + +var float PaddingX; +var float PaddingY; +var float LineSpacing; +var float SeparatorSpacing; +var float ControlOffset; +var bool bLastAdminState; +var bool bLoadSucceeded; +var bool bPendingAdminLogin; +var float LoginPendingUntilTime; +var float NextRefreshRequestTime; + +function ClientSettings FindSettingsObject() { + local bbPlayer P; + local bbCHSpectator S; + + if (GetPlayerOwner().IsA('bbPlayer') && bbPlayer(GetPlayerOwner()).Settings != none) + return bbPlayer(GetPlayerOwner()).Settings; + + if (GetPlayerOwner().IsA('bbCHSpectator') && bbCHSpectator(GetPlayerOwner()).Settings != none) + return bbCHSpectator(GetPlayerOwner()).Settings; + + foreach GetPlayerOwner().AllActors(class'bbPlayer', P) + if (P.Settings != none) + return P.Settings; + + foreach GetPlayerOwner().AllActors(class'bbCHSpectator', S) + if (S.Settings != none) + return S.Settings; + + return none; +} + +function PlayerPawn ResolveOwnerPawn() { + return GetPlayerOwner(); +} + +function bbPlayer ResolveOwnerBBPlayer() { + return bbPlayer(ResolveOwnerPawn()); +} + +function bbCHSpectator ResolveOwnerBBSpectator() { + return bbCHSpectator(ResolveOwnerPawn()); +} + +function ServerSettings FindServerSettingsObject() { + local bbPlayer P; + local bbCHSpectator S; + + P = ResolveOwnerBBPlayer(); + if (P != none) + return P.IGPlus_GetServerSettingsObject(); + + S = ResolveOwnerBBSpectator(); + if (S != none) + return S.IGPlus_GetServerSettingsObject(); + + return none; +} + +function ResetLocalServerSettingsCache() { + local bbPlayer P; + local bbCHSpectator S; + + P = ResolveOwnerBBPlayer(); + if (P != none) { + P.IGPlus_ServerSettingsInit(); + return; + } + + S = ResolveOwnerBBSpectator(); + if (S != none) + S.IGPlus_ServerSettingsInit(); +} + +function bool AreServerSettingsLoaded() { + local bbPlayer P; + local bbCHSpectator S; + + P = ResolveOwnerBBPlayer(); + if (P != none) + return P.IGPlus_ServerSettingsMenuLoaded; + + S = ResolveOwnerBBSpectator(); + if (S != none) + return S.IGPlus_ServerSettingsMenuLoaded; + + return false; +} + +function bool HasServerAdminAccess() { + local bbPlayer P; + local bbCHSpectator S; + + P = ResolveOwnerBBPlayer(); + if (P != none) + return P.IGPlus_ServerSettingsMenuCanEdit; + + S = ResolveOwnerBBSpectator(); + if (S != none) + return S.IGPlus_ServerSettingsMenuCanEdit; + + return false; +} + +function float GetNowTimeSeconds() { + local LevelInfo L; + + L = GetLevel(); + if (L == none) + return 0; + + return L.TimeSeconds; +} + +function bool IsAdmin() { + local PlayerPawn P; + + P = ResolveOwnerPawn(); + if (P == none) + return false; + + if (P.bAdmin) + return true; + + if (P.PlayerReplicationInfo != none && P.PlayerReplicationInfo.bAdmin) + return true; + + return false; +} + +function RequestServerSettings(optional bool bForce) { + local bbPlayer P; + local bbCHSpectator S; + local float NowTime; + + NowTime = GetNowTimeSeconds(); + if (bForce == false && NowTime < NextRefreshRequestTime) + return; + + P = ResolveOwnerBBPlayer(); + if (P != none) { + P.IGPlus_ServerRequestSettings(); + NextRefreshRequestTime = NowTime + 1.0; + return; + } + + S = ResolveOwnerBBSpectator(); + if (S != none) { + S.IGPlus_ServerRequestSettings(); + NextRefreshRequestTime = NowTime + 1.0; + } +} + +function SetStatusText(string Text) { + Lbl_Status.SetText(Text); +} + +function UpdateStatusText() { + if (HasServerAdminAccess() == false) { + if (bPendingAdminLogin && GetNowTimeSeconds() <= LoginPendingUntilTime) + SetStatusText(StatusLoginPendingText); + else + SetStatusText(StatusLoginText); + } else if (AreServerSettingsLoaded() == false) + SetStatusText(StatusLoadingText); + else + SetStatusText(StatusAdminText); +} + +function UpdateAuthButton() { + if (Btn_AdminAuth == none) + return; + + if (HasServerAdminAccess()) { + Btn_AdminAuth.SetText(LogoutButtonText); + Btn_AdminAuth.SetHelpText(LogoutButtonHelp); + } else { + Btn_AdminAuth.SetText(LoginButtonText); + Btn_AdminAuth.SetHelpText(LoginButtonHelp); + } +} + +function string BoolToString(bool B) { + if (B) + return "True"; + return "False"; +} + +function SendMutateCommand(string Cmd) { + local PlayerPawn P; + + P = ResolveOwnerPawn(); + if (P == none) + return; + + P.Mutate(Cmd); +} + +function SendServerSetting(string Key, string Value, optional bool bSendFeedback) { + if (bSendFeedback) + SendMutateCommand("IGPlusServerSet "$Key$" "$Value); + else + SendMutateCommand("IGPlusServerSetSilent "$Key$" "$Value); +} + +function bool AreServerSettingValuesEqual(string ValueA, string ValueB) { + return Caps(class'StringUtils'.static.Trim(ValueA)) == Caps(class'StringUtils'.static.Trim(ValueB)); +} + +function bool SaveServerSettingIfChanged(ServerSettings S, string Key, string Value) { + local string CurrentValue; + + if (S != none) + CurrentValue = S.GetPropertyText(Key); + + if (S == none || AreServerSettingValuesEqual(CurrentValue, Value) == false) { + // Use the non-silent command only for changed values so admins get a concise confirmation. + SendServerSetting(Key, Value, true); + if (S != none) + S.SetPropertyText(Key, Value); + return true; + } + + return false; +} + +function string HitFeedbackModeIndexToValue(int Index) { + switch (Clamp(Index, 0, 2)) { + case 0: return "HFM_Disabled"; + case 1: return "HFM_VisibleOnly"; + case 2: return "HFM_Always"; + } + + return "HFM_Disabled"; +} + +function PopulatePasswordHistory() { + local ClientSettings Settings; + local int i; + local string FirstPassword; + + if (Cmb_AdminPassword == none) + return; + + Settings = FindSettingsObject(); + Cmb_AdminPassword.Clear(); + if (Settings == none) + return; + + for (i = 0; i < arraycount(Settings.ServerAdminPasswords); i++) { + if (Settings.ServerAdminPasswords[i] != "") { + Cmb_AdminPassword.AddItem(Settings.ServerAdminPasswords[i]); + if (FirstPassword == "") + FirstPassword = Settings.ServerAdminPasswords[i]; + } + } + + if (FirstPassword != "") + Cmb_AdminPassword.SetValue(FirstPassword); + else + Cmb_AdminPassword.SetValue(""); +} + +function SavePasswordHistory(string Password) { + local ClientSettings Settings; + local int i; + local int FoundIndex; + + if (Password == "") + return; + + Settings = FindSettingsObject(); + if (Settings == none) + return; + + FoundIndex = -1; + for (i = 0; i < arraycount(Settings.ServerAdminPasswords); i++) { + if (Settings.ServerAdminPasswords[i] == Password) { + FoundIndex = i; + break; + } + } + + if (FoundIndex >= 0) { + for (i = FoundIndex + 1; i < arraycount(Settings.ServerAdminPasswords); i++) + Settings.ServerAdminPasswords[i - 1] = Settings.ServerAdminPasswords[i]; + } + + for (i = arraycount(Settings.ServerAdminPasswords) - 1; i > 0; i--) + Settings.ServerAdminPasswords[i] = Settings.ServerAdminPasswords[i - 1]; + + Settings.ServerAdminPasswords[0] = Password; + Settings.SaveConfig(); + + PopulatePasswordHistory(); + if (Cmb_AdminPassword != none) + Cmb_AdminPassword.SetValue(Password); +} + +function DeletePasswordFromHistory(string Password) { + local ClientSettings Settings; + local int i; + local int WriteIndex; + local string CurrentPassword; + + if (Password == "") + return; + + Settings = FindSettingsObject(); + if (Settings == none) + return; + + WriteIndex = 0; + for (i = 0; i < arraycount(Settings.ServerAdminPasswords); i++) { + CurrentPassword = Settings.ServerAdminPasswords[i]; + if (CurrentPassword != "" && CurrentPassword != Password) { + Settings.ServerAdminPasswords[WriteIndex] = CurrentPassword; + WriteIndex++; + } + } + + for (i = WriteIndex; i < arraycount(Settings.ServerAdminPasswords); i++) + Settings.ServerAdminPasswords[i] = ""; + + Settings.SaveConfig(); + PopulatePasswordHistory(); +} + +function DeletePasswordFromUI() { + local string Password; + + if (Cmb_AdminPassword == none) + return; + + Password = class'StringUtils'.static.Trim(Cmb_AdminPassword.GetValue()); + DeletePasswordFromHistory(Password); +} + +function SaveServerSettings() { + local ServerSettings S; + + S = FindServerSettingsObject(); + + SaveServerSettingIfChanged(S, "bAutoPause", BoolToString(Chk_bAutoPause.bChecked)); + SaveServerSettingIfChanged(S, "PauseTotalTime", Edit_PauseTotalTime.GetValue()); + SaveServerSettingIfChanged(S, "PauseTime", Edit_PauseTime.GetValue()); + SaveServerSettingIfChanged(S, "bForceDemo", BoolToString(Chk_bForceDemo.bChecked)); + SaveServerSettingIfChanged(S, "bRestrictTrading", BoolToString(Chk_bRestrictTrading.bChecked)); + SaveServerSettingIfChanged(S, "MaxTradeTimeMargin", Edit_MaxTradeTimeMargin.GetValue()); + SaveServerSettingIfChanged(S, "TradePingMargin", Edit_TradePingMargin.GetValue()); + SaveServerSettingIfChanged(S, "KillCamDelay", Edit_KillCamDelay.GetValue()); + SaveServerSettingIfChanged(S, "KillCamDuration", Edit_KillCamDuration.GetValue()); + SaveServerSettingIfChanged(S, "BrightskinMode", string(Cmb_BrightskinMode.GetSelectedIndex())); + SaveServerSettingIfChanged(S, "PlayerScale", Edit_PlayerScale.GetValue()); + SaveServerSettingIfChanged(S, "bAlwaysRenderFlagCarrier", BoolToString(Chk_bAlwaysRenderFlagCarrier.bChecked)); + SaveServerSettingIfChanged(S, "bAlwaysRenderDroppedFlags", BoolToString(Chk_bAlwaysRenderDroppedFlags.bChecked)); + SaveServerSettingIfChanged(S, "HitFeedbackMode", HitFeedbackModeIndexToValue(Cmb_HitFeedbackMode.GetSelectedIndex())); + SaveServerSettingIfChanged(S, "bEnablePingCompensatedSpawn", BoolToString(Chk_bEnablePingCompensatedSpawn.bChecked)); + + SaveServerSettingIfChanged(S, "bJumpingPreservesMomentum", BoolToString(Chk_bJumpingPreservesMomentum.bChecked)); + SaveServerSettingIfChanged(S, "bOldLandingMomentum", BoolToString(Chk_bOldLandingMomentum.bChecked)); + SaveServerSettingIfChanged(S, "bEnableSingleButtonDodge", BoolToString(Chk_bEnableSingleButtonDodge.bChecked)); + SaveServerSettingIfChanged(S, "bUseFlipAnimation", BoolToString(Chk_bUseFlipAnimation.bChecked)); + SaveServerSettingIfChanged(S, "bEnableWallDodging", BoolToString(Chk_bEnableWallDodging.bChecked)); + SaveServerSettingIfChanged(S, "bDodgePreserveZMomentum", BoolToString(Chk_bDodgePreserveZMomentum.bChecked)); + SaveServerSettingIfChanged(S, "MaxMultiDodges", Edit_MaxMultiDodges.GetValue()); + + SaveServerSettingIfChanged(S, "MaxPosError", Edit_MaxPosError.GetValue()); + SaveServerSettingIfChanged(S, "MaxHitError", Edit_MaxHitError.GetValue()); + SaveServerSettingIfChanged(S, "FireTimeout", Edit_FireTimeout.GetValue()); + SaveServerSettingIfChanged(S, "MinNetUpdateRate", Edit_MinNetUpdateRate.GetValue()); + SaveServerSettingIfChanged(S, "MaxNetUpdateRate", Edit_MaxNetUpdateRate.GetValue()); + SaveServerSettingIfChanged(S, "bEnableServerExtrapolation", BoolToString(Chk_bEnableServerExtrapolation.bChecked)); + SaveServerSettingIfChanged(S, "bEnableServerPacketReordering", BoolToString(Chk_bEnableServerPacketReordering.bChecked)); + SaveServerSettingIfChanged(S, "bPlayersAlwaysRelevant", BoolToString(Chk_bPlayersAlwaysRelevant.bChecked)); + SaveServerSettingIfChanged(S, "bEnableJitterBounding", BoolToString(Chk_bEnableJitterBounding.bChecked)); + SaveServerSettingIfChanged(S, "MaxJitterTime", Edit_MaxJitterTime.GetValue()); + SaveServerSettingIfChanged(S, "bEnableInputReplication", BoolToString(Chk_bEnableInputReplication.bChecked)); + SaveServerSettingIfChanged(S, "bEnableSnapshotInterpolation", BoolToString(Chk_bEnableSnapshotInterpolation.bChecked)); + SaveServerSettingIfChanged(S, "SnapshotInterpSendHz", Edit_SnapshotInterpSendHz.GetValue()); + SaveServerSettingIfChanged(S, "SnapshotInterpRewindMs", Edit_SnapshotInterpRewindMs.GetValue()); + SaveServerSettingIfChanged(S, "bEnableLoosePositionCheck", BoolToString(Chk_bEnableLoosePositionCheck.bChecked)); + SaveServerSettingIfChanged(S, "LooseCheckCorrectionFactor", Edit_LooseCheckCorrectionFactor.GetValue()); + SaveServerSettingIfChanged(S, "LooseCheckCorrectionFactorOnMover", Edit_LooseCheckCorrectionFactorOnMover.GetValue()); + SaveServerSettingIfChanged(S, "bEnableWarpFix", BoolToString(Chk_bEnableWarpFix.bChecked)); + SaveServerSettingIfChanged(S, "WarpFixDelay", Edit_WarpFixDelay.GetValue()); + + SaveServerSettingIfChanged(S, "ShowTouchedPackage", BoolToString(Chk_ShowTouchedPackage.bChecked)); + SaveServerSettingIfChanged(S, "bEnableDamageDebugMode", BoolToString(Chk_bEnableDamageDebugMode.bChecked)); + SaveServerSettingIfChanged(S, "bEnableDamageDebugConsoleMessages", BoolToString(Chk_bEnableDamageDebugConsoleMessages.bChecked)); + SaveServerSettingIfChanged(S, "bEnableHitboxDebugMode", BoolToString(Chk_bEnableHitboxDebugMode.bChecked)); +} + +function AdminLoginFromUI() { + local string Password; + local PlayerPawn P; + + P = ResolveOwnerPawn(); + if (P == none) + return; + + Password = class'StringUtils'.static.Trim(Cmb_AdminPassword.GetValue()); + if (Password == "") { + SetStatusText(LoginRequiredText); + return; + } + + SavePasswordHistory(Password); + P.AdminLogin(Password); + bLoadSucceeded = false; + bPendingAdminLogin = true; + LoginPendingUntilTime = GetNowTimeSeconds() + 6.0; + ResetLocalServerSettingsCache(); + RequestServerSettings(true); +} + +function AdminLogoutFromUI() { + local PlayerPawn P; + + P = ResolveOwnerPawn(); + if (P == none) + return; + + P.AdminLogout(); + bLoadSucceeded = false; + bPendingAdminLogin = false; + ResetLocalServerSettingsCache(); + RequestServerSettings(true); +} + +function UWindowCheckbox CreateCheckbox(string T, optional string HT) { + local UWindowCheckbox Chk; + + Chk = UWindowCheckbox(CreateControl(class'IGPlus_Checkbox', PaddingX, ControlOffset, 200, 1)); + Chk.SetText(T); + Chk.SetHelpText(HT); + Chk.ToolTipString = HT; + Chk.SetFont(F_Normal); + Chk.Align = TA_Left; + ControlOffset += LineSpacing; + + return Chk; +} + +function IGPlus_EditControl CreateEdit( + EEditControlType ECT, + string T, + optional string HT, + optional int MaxLength, + optional float EditBoxWidth +) { + local IGPlus_EditControl Edit; + + Edit = IGPlus_EditControl(CreateControl(class'IGPlus_EditControl', PaddingX, ControlOffset, 200, 1)); + Edit.SetText(T); + Edit.SetHelpText(HT); + Edit.SetFont(F_Normal); + Edit.Align = TA_Left; + if (MaxLength > 0) + Edit.SetMaxLength(MaxLength); + + if (EditBoxWidth > 0) { + Edit.EditBoxWidthFraction = 0.5; + Edit.EditBoxMinWidth = EditBoxWidth; + Edit.EditBoxMaxWidth = EditBoxWidth; + } + + switch(ECT) { + case ECT_Text: + Edit.SetNumericOnly(false); + Edit.SetNumericFloat(false); + break; + case ECT_Integer: + Edit.SetNumericOnly(true); + Edit.SetNumericFloat(false); + break; + case ECT_Real: + Edit.SetNumericOnly(true); + Edit.SetNumericFloat(true); + break; + } + + ControlOffset += LineSpacing; + + return Edit; +} + +function UWindowLabelControl CreateLabel(string T, optional string HT) { + local UWindowLabelControl Lbl; + + Lbl = UWindowLabelControl(CreateControl(class'IGPlus_Label', PaddingX, ControlOffset, 200, 1)); + Lbl.SetText(T); + Lbl.SetHelpText(HT); + Lbl.SetFont(F_Normal); + Lbl.Align = TA_Left; + ControlOffset += LineSpacing; + + return Lbl; +} + +function UWindowLabelControl CreateSeparator(string T, optional string HT) { + local UWindowLabelControl Lbl; + + if (ControlOffset > PaddingY) + ControlOffset += (SeparatorSpacing - LineSpacing); + + Lbl = UWindowLabelControl(CreateControl(class'IGPlus_Separator', PaddingX, ControlOffset, 200, 1)); + Lbl.SetText(T); + Lbl.SetHelpText(HT); + Lbl.SetFont(F_Normal); + Lbl.Align = TA_Left; + ControlOffset += LineSpacing; + + return Lbl; +} + +function IGPlus_ComboBox CreateComboBox( + string T, + optional string HT, + optional bool bCanEdit, + optional float EditBoxWidth +) { + local IGPlus_ComboBox Cmb; + + Cmb = IGPlus_ComboBox(CreateControl(class'IGPlus_ComboBox', PaddingX, ControlOffset, 200, 1)); + Cmb.SetText(T); + Cmb.SetHelpText(HT); + Cmb.SetFont(F_Normal); + Cmb.Align = TA_Left; + Cmb.SetEditable(bCanEdit); + + if (EditBoxWidth > 0) { + Cmb.EditBoxWidthFraction = 0.5; + Cmb.EditBoxMinWidth = EditBoxWidth; + Cmb.EditBoxMaxWidth = EditBoxWidth; + } + + ControlOffset += LineSpacing; + + return Cmb; +} + +function IGPlus_Button CreateButton(string T, optional string HT) { + local IGPlus_Button Btn; + + Btn = IGPlus_Button(CreateControl(class'IGPlus_Button', PaddingX, ControlOffset, 200, 1)); + Btn.SetText(T); + Btn.SetHelpText(HT); + Btn.Align = TA_Left; + ControlOffset += LineSpacing; + + return Btn; +} + +function LayoutControl(UWindowDialogControl C, bool bVisible, float Width, out float Y) { + local float Height; + + if (C == none) + return; + + if (bVisible) { + if (C.bWindowVisible == false) + C.ShowWindow(); + C.WinLeft = PaddingX; + C.WinTop = Y; + Height = FMax(C.WinHeight, 1); + C.SetSize(Width, Height); + Y += LineSpacing; + return; + } + + if (C.bWindowVisible) + C.HideWindow(); +} + +function LayoutPasswordControls(Canvas C, bool bVisible, float Width, out float Y) { + local float Height; + local float Gap; + local float DeleteButtonWidth; + local float ComboWidth; + + if (Cmb_AdminPassword == none || Btn_DeletePassword == none) + return; + + if (bVisible) { + if (Cmb_AdminPassword.bWindowVisible == false) + Cmb_AdminPassword.ShowWindow(); + if (Btn_DeletePassword.bWindowVisible == false) + Btn_DeletePassword.ShowWindow(); + + Gap = 4; + DeleteButtonWidth = 20; + ComboWidth = FMax(Width - DeleteButtonWidth - Gap, 40); + ConfigurePasswordCombo(C, ComboWidth); + + Cmb_AdminPassword.WinLeft = PaddingX; + Cmb_AdminPassword.WinTop = Y; + Height = FMax(Cmb_AdminPassword.WinHeight, 1); + Cmb_AdminPassword.SetSize(ComboWidth, Height); + + Btn_DeletePassword.WinLeft = PaddingX + ComboWidth + Gap; + Btn_DeletePassword.WinTop = Y; + Height = FMax(Btn_DeletePassword.WinHeight, 1); + Btn_DeletePassword.SetSize(DeleteButtonWidth, Height); + + Y += LineSpacing; + return; + } + + if (Cmb_AdminPassword.bWindowVisible) + Cmb_AdminPassword.HideWindow(); + if (Btn_DeletePassword.bWindowVisible) + Btn_DeletePassword.HideWindow(); +} + +function ConfigurePasswordCombo(Canvas C, float ControlWidth) { + local float LabelWidth; + local float LabelHeight; + local float MaxWidth; + + if (Cmb_AdminPassword == none) + return; + + Cmb_AdminPassword.TextSize(C, Cmb_AdminPassword.Text, LabelWidth, LabelHeight); + + // Keep the editable area as wide as possible while preserving label visibility. + MaxWidth = FMax(ControlWidth - LabelWidth - 8, 40); + Cmb_AdminPassword.EditBoxMinWidth = FMin(70, MaxWidth); + Cmb_AdminPassword.EditBoxMaxWidth = MaxWidth; + Cmb_AdminPassword.EditBoxWidth = FClamp( + ControlWidth * Cmb_AdminPassword.EditBoxWidthFraction, + Cmb_AdminPassword.EditBoxMinWidth, + Cmb_AdminPassword.EditBoxMaxWidth + ); +} + +function float GetLabeledInputMaxWidth(UWindowDialogControl Control, Canvas C, float ControlWidth, optional float MinWidth) { + local float LabelWidth; + local float LabelHeight; + + if (Control == none) + return 0; + + if (MinWidth <= 0) + MinWidth = 40; + + Control.TextSize(C, Control.Text, LabelWidth, LabelHeight); + return FMax(ControlWidth - LabelWidth - 8, MinWidth); +} + +function ConfigureFixedWidthEdit(IGPlus_EditControl Edit, Canvas C, float ControlWidth, float PreferredWidth) { + local float MaxWidth; + + if (Edit == none) + return; + + MaxWidth = GetLabeledInputMaxWidth(Edit, C, ControlWidth, 40); + PreferredWidth = FClamp(PreferredWidth, 40, MaxWidth); + Edit.EditBoxMinWidth = PreferredWidth; + Edit.EditBoxMaxWidth = PreferredWidth; + Edit.EditBoxWidth = PreferredWidth; +} + +function ConfigureFixedWidthCombo(IGPlus_ComboBox Cmb, Canvas C, float ControlWidth, float PreferredWidth) { + local float MaxWidth; + + if (Cmb == none) + return; + + MaxWidth = GetLabeledInputMaxWidth(Cmb, C, ControlWidth, 40); + PreferredWidth = FClamp(PreferredWidth, 40, MaxWidth); + Cmb.EditBoxMinWidth = PreferredWidth; + Cmb.EditBoxMaxWidth = PreferredWidth; + Cmb.EditBoxWidth = PreferredWidth; +} + +function ConfigureResponsiveServerControls(Canvas C, float ControlWidth) { + ConfigureFixedWidthCombo(Cmb_BrightskinMode, C, ControlWidth, 180); + ConfigureFixedWidthCombo(Cmb_HitFeedbackMode, C, ControlWidth, 180); + + ConfigureFixedWidthEdit(Edit_PauseTotalTime, C, ControlWidth, 80); + ConfigureFixedWidthEdit(Edit_PauseTime, C, ControlWidth, 80); + ConfigureFixedWidthEdit(Edit_MaxTradeTimeMargin, C, ControlWidth, 80); + ConfigureFixedWidthEdit(Edit_TradePingMargin, C, ControlWidth, 80); + ConfigureFixedWidthEdit(Edit_KillCamDelay, C, ControlWidth, 80); + ConfigureFixedWidthEdit(Edit_KillCamDuration, C, ControlWidth, 80); + ConfigureFixedWidthEdit(Edit_PlayerScale, C, ControlWidth, 80); + ConfigureFixedWidthEdit(Edit_MaxMultiDodges, C, ControlWidth, 80); + + ConfigureFixedWidthEdit(Edit_MaxPosError, C, ControlWidth, 80); + ConfigureFixedWidthEdit(Edit_MaxHitError, C, ControlWidth, 80); + ConfigureFixedWidthEdit(Edit_FireTimeout, C, ControlWidth, 80); + ConfigureFixedWidthEdit(Edit_MinNetUpdateRate, C, ControlWidth, 80); + ConfigureFixedWidthEdit(Edit_MaxNetUpdateRate, C, ControlWidth, 80); + ConfigureFixedWidthEdit(Edit_MaxJitterTime, C, ControlWidth, 80); + ConfigureFixedWidthEdit(Edit_SnapshotInterpSendHz, C, ControlWidth, 80); + ConfigureFixedWidthEdit(Edit_SnapshotInterpRewindMs, C, ControlWidth, 80); + ConfigureFixedWidthEdit(Edit_LooseCheckCorrectionFactor, C, ControlWidth, 80); + ConfigureFixedWidthEdit(Edit_LooseCheckCorrectionFactorOnMover, C, ControlWidth, 80); + ConfigureFixedWidthEdit(Edit_WarpFixDelay, C, ControlWidth, 80); +} + +function LoadServerSettings() { + local ServerSettings S; + + S = FindServerSettingsObject(); + if (S == none) + return; + + Chk_bAutoPause.bChecked = S.bAutoPause; + Edit_PauseTotalTime.SetValue(string(S.PauseTotalTime)); + Edit_PauseTime.SetValue(string(S.PauseTime)); + Chk_bForceDemo.bChecked = S.bForceDemo; + Chk_bRestrictTrading.bChecked = S.bRestrictTrading; + Edit_MaxTradeTimeMargin.SetValue(string(S.MaxTradeTimeMargin)); + Edit_TradePingMargin.SetValue(string(S.TradePingMargin)); + Edit_KillCamDelay.SetValue(string(S.KillCamDelay)); + Edit_KillCamDuration.SetValue(string(S.KillCamDuration)); + Cmb_BrightskinMode.SetSelectedIndex(Clamp(int(S.BrightskinMode), 0, 1)); + Edit_PlayerScale.SetValue(string(S.PlayerScale)); + Chk_bAlwaysRenderFlagCarrier.bChecked = S.bAlwaysRenderFlagCarrier; + Chk_bAlwaysRenderDroppedFlags.bChecked = S.bAlwaysRenderDroppedFlags; + Cmb_HitFeedbackMode.SetSelectedIndex(Clamp(int(S.HitFeedbackMode), 0, 2)); + + Chk_bJumpingPreservesMomentum.bChecked = S.bJumpingPreservesMomentum; + Chk_bOldLandingMomentum.bChecked = S.bOldLandingMomentum; + Chk_bEnableSingleButtonDodge.bChecked = S.bEnableSingleButtonDodge; + Chk_bUseFlipAnimation.bChecked = S.bUseFlipAnimation; + Chk_bEnableWallDodging.bChecked = S.bEnableWallDodging; + Chk_bDodgePreserveZMomentum.bChecked = S.bDodgePreserveZMomentum; + Edit_MaxMultiDodges.SetValue(string(S.MaxMultiDodges)); + Chk_bEnablePingCompensatedSpawn.bChecked = S.bEnablePingCompensatedSpawn; + + Edit_MaxPosError.SetValue(string(S.MaxPosError)); + Edit_MaxHitError.SetValue(string(S.MaxHitError)); + Edit_MaxJitterTime.SetValue(string(S.MaxJitterTime)); + Edit_WarpFixDelay.SetValue(string(S.WarpFixDelay)); + Edit_FireTimeout.SetValue(string(S.FireTimeout)); + Edit_MinNetUpdateRate.SetValue(string(S.MinNetUpdateRate)); + Edit_MaxNetUpdateRate.SetValue(string(S.MaxNetUpdateRate)); + Chk_bEnableInputReplication.bChecked = S.bEnableInputReplication; + Chk_bEnableServerExtrapolation.bChecked = S.bEnableServerExtrapolation; + Chk_bEnableServerPacketReordering.bChecked = S.bEnableServerPacketReordering; + Chk_bEnableLoosePositionCheck.bChecked = S.bEnableLoosePositionCheck; + Chk_bPlayersAlwaysRelevant.bChecked = S.bPlayersAlwaysRelevant; + Chk_bEnableJitterBounding.bChecked = S.bEnableJitterBounding; + Edit_LooseCheckCorrectionFactor.SetValue(string(S.LooseCheckCorrectionFactor)); + Edit_LooseCheckCorrectionFactorOnMover.SetValue(string(S.LooseCheckCorrectionFactorOnMover)); + Chk_bEnableSnapshotInterpolation.bChecked = S.bEnableSnapshotInterpolation; + Edit_SnapshotInterpSendHz.SetValue(string(S.SnapshotInterpSendHz)); + Edit_SnapshotInterpRewindMs.SetValue(string(S.SnapshotInterpRewindMs)); + Chk_bEnableWarpFix.bChecked = S.bEnableWarpFix; + + Chk_ShowTouchedPackage.bChecked = S.ShowTouchedPackage; + Chk_bEnableDamageDebugMode.bChecked = S.bEnableDamageDebugMode; + Chk_bEnableDamageDebugConsoleMessages.bChecked = S.bEnableDamageDebugConsoleMessages; + Chk_bEnableHitboxDebugMode.bChecked = S.bEnableHitboxDebugMode; + + bLoadSucceeded = true; +} + +function Created() { + super.Created(); + + ControlOffset = PaddingY; + + Lbl_Header = CreateLabel(HeaderText); + Lbl_Header.Align = TA_Center; + Lbl_Header.SetFont(F_Bold); + + Lbl_Status = CreateLabel(""); + Lbl_Status.Align = TA_Center; + + Lbl_MoreInformation = CreateLabel(MoreInformationText); + Lbl_MoreInformation.Align = TA_Center; + Lbl_MoreInformation.SetFont(F_Bold); + + Lbl_Login = CreateSeparator(LoginText); + Cmb_AdminPassword = CreateComboBox(AdminPasswordText, AdminPasswordHelp, true); + Cmb_AdminPassword.SetMaxLength(64); + Cmb_AdminPassword.SetNumericOnly(false); + Cmb_AdminPassword.EditBoxWidthFraction = 0.85; + Cmb_AdminPassword.EditBoxMinWidth = 70; + Cmb_AdminPassword.EditBoxMaxWidth = 65535; + Btn_DeletePassword = CreateButton(DeletePasswordButtonText, DeletePasswordButtonHelp); + ControlOffset -= LineSpacing; + Btn_AdminAuth = CreateButton(LoginButtonText, LoginButtonHelp); + + Lbl_General = CreateSeparator(GeneralText); + Chk_bAutoPause = CreateCheckbox(bAutoPauseText, bAutoPauseHelp); + Edit_PauseTotalTime = CreateEdit(ECT_Integer, PauseTotalTimeText, PauseTotalTimeHelp, 6, 80); + Edit_PauseTime = CreateEdit(ECT_Integer, PauseTimeText, PauseTimeHelp, 6, 80); + Chk_bForceDemo = CreateCheckbox(bForceDemoText, bForceDemoHelp); + Chk_bRestrictTrading = CreateCheckbox(bRestrictTradingText, bRestrictTradingHelp); + Edit_MaxTradeTimeMargin = CreateEdit(ECT_Real, MaxTradeTimeMarginText, MaxTradeTimeMarginHelp, 16, 80); + Edit_TradePingMargin = CreateEdit(ECT_Real, TradePingMarginText, TradePingMarginHelp, 16, 80); + Edit_KillCamDelay = CreateEdit(ECT_Real, KillCamDelayText, KillCamDelayHelp, 16, 80); + Edit_KillCamDuration = CreateEdit(ECT_Real, KillCamDurationText, KillCamDurationHelp, 16, 80); + Cmb_BrightskinMode = CreateComboBox(BrightskinModeText, BrightskinModeHelp, false, 180); + Cmb_BrightskinMode.AddItem(BrightskinModeDisabled); + Cmb_BrightskinMode.AddItem(BrightskinModeUnlit); + Edit_PlayerScale = CreateEdit(ECT_Real, PlayerScaleText, PlayerScaleHelp, 16, 80); + Chk_bAlwaysRenderFlagCarrier = CreateCheckbox(bAlwaysRenderFlagCarrierText, bAlwaysRenderFlagCarrierHelp); + Chk_bAlwaysRenderDroppedFlags = CreateCheckbox(bAlwaysRenderDroppedFlagsText, bAlwaysRenderDroppedFlagsHelp); + Cmb_HitFeedbackMode = CreateComboBox(HitFeedbackModeText, HitFeedbackModeHelp, false, 180); + Cmb_HitFeedbackMode.AddItem(HitFeedbackModeDisabled); + Cmb_HitFeedbackMode.AddItem(HitFeedbackModeVisibleOnly); + Cmb_HitFeedbackMode.AddItem(HitFeedbackModeAlways); + Chk_bEnablePingCompensatedSpawn = CreateCheckbox(bEnablePingCompensatedSpawnText, bEnablePingCompensatedSpawnHelp); + + Lbl_Movement = CreateSeparator(MovementText); + Chk_bJumpingPreservesMomentum = CreateCheckbox(bJumpingPreservesMomentumText, bJumpingPreservesMomentumHelp); + Chk_bOldLandingMomentum = CreateCheckbox(bOldLandingMomentumText, bOldLandingMomentumHelp); + Chk_bEnableSingleButtonDodge = CreateCheckbox(bEnableSingleButtonDodgeText, bEnableSingleButtonDodgeHelp); + Chk_bUseFlipAnimation = CreateCheckbox(bUseFlipAnimationText, bUseFlipAnimationHelp); + Chk_bEnableWallDodging = CreateCheckbox(bEnableWallDodgingText, bEnableWallDodgingHelp); + Chk_bDodgePreserveZMomentum = CreateCheckbox(bDodgePreserveZMomentumText, bDodgePreserveZMomentumHelp); + Edit_MaxMultiDodges = CreateEdit(ECT_Integer, MaxMultiDodgesText, MaxMultiDodgesHelp, 6, 80); + + Lbl_Networking = CreateSeparator(NetworkingText); + Edit_MaxPosError = CreateEdit(ECT_Integer, MaxPosErrorText, MaxPosErrorHelp, 8, 80); + Edit_MaxHitError = CreateEdit(ECT_Integer, MaxHitErrorText, MaxHitErrorHelp, 8, 80); + Edit_FireTimeout = CreateEdit(ECT_Real, FireTimeoutText, FireTimeoutHelp, 16, 80); + Edit_MinNetUpdateRate = CreateEdit(ECT_Real, MinNetUpdateRateText, MinNetUpdateRateHelp, 16, 80); + Edit_MaxNetUpdateRate = CreateEdit(ECT_Real, MaxNetUpdateRateText, MaxNetUpdateRateHelp, 16, 80); + Chk_bEnableServerExtrapolation = CreateCheckbox(bEnableServerExtrapolationText, bEnableServerExtrapolationHelp); + Chk_bEnableServerPacketReordering = CreateCheckbox(bEnableServerPacketReorderingText, bEnableServerPacketReorderingHelp); + Chk_bPlayersAlwaysRelevant = CreateCheckbox(bPlayersAlwaysRelevantText, bPlayersAlwaysRelevantHelp); + Chk_bEnableJitterBounding = CreateCheckbox(bEnableJitterBoundingText, bEnableJitterBoundingHelp); + Edit_MaxJitterTime = CreateEdit(ECT_Real, MaxJitterTimeText, MaxJitterTimeHelp, 16, 80); + Chk_bEnableInputReplication = CreateCheckbox(bEnableInputReplicationText, bEnableInputReplicationHelp); + Chk_bEnableSnapshotInterpolation = CreateCheckbox(bEnableSnapshotInterpolationText, bEnableSnapshotInterpolationHelp); + Edit_SnapshotInterpSendHz = CreateEdit(ECT_Real, SnapshotInterpSendHzText, SnapshotInterpSendHzHelp, 16, 80); + Edit_SnapshotInterpRewindMs = CreateEdit(ECT_Real, SnapshotInterpRewindMsText, SnapshotInterpRewindMsHelp, 16, 80); + Chk_bEnableLoosePositionCheck = CreateCheckbox(bEnableLoosePositionCheckText, bEnableLoosePositionCheckHelp); + Edit_LooseCheckCorrectionFactor = CreateEdit(ECT_Real, LooseCheckCorrectionFactorText, LooseCheckCorrectionFactorHelp, 16, 80); + Edit_LooseCheckCorrectionFactorOnMover = CreateEdit(ECT_Real, LooseCheckCorrectionFactorOnMoverText, LooseCheckCorrectionFactorOnMoverHelp, 16, 80); + Chk_bEnableWarpFix = CreateCheckbox(bEnableWarpFixText, bEnableWarpFixHelp); + Edit_WarpFixDelay = CreateEdit(ECT_Real, WarpFixDelayText, WarpFixDelayHelp, 16, 80); + + Lbl_Debug = CreateSeparator(DebugText); + Chk_ShowTouchedPackage = CreateCheckbox(ShowTouchedPackageText, ShowTouchedPackageHelp); + Chk_bEnableDamageDebugMode = CreateCheckbox(bEnableDamageDebugModeText, bEnableDamageDebugModeHelp); + Chk_bEnableDamageDebugConsoleMessages = CreateCheckbox(bEnableDamageDebugConsoleMessagesText, bEnableDamageDebugConsoleMessagesHelp); + Chk_bEnableHitboxDebugMode = CreateCheckbox(bEnableHitboxDebugModeText, bEnableHitboxDebugModeHelp); + + ControlOffset += PaddingY - 4; + + Load(); +} + +function AfterCreate() { + super.AfterCreate(); + + DesiredWidth = 180; + DesiredHeight = ControlOffset; +} + +function BeforePaint(Canvas C, float X, float Y) { + local float WndWidth; + local float Top; + local bool bAdmin; + local bool bShowSettings; + + super.BeforePaint(C, X, Y); + + if (bPendingAdminLogin && GetNowTimeSeconds() > LoginPendingUntilTime) + bPendingAdminLogin = false; + + if (bPendingAdminLogin || AreServerSettingsLoaded() == false) + RequestServerSettings(); + + bAdmin = HasServerAdminAccess(); + if (bAdmin) + bPendingAdminLogin = false; + if (bLastAdminState != bAdmin) { + bLastAdminState = bAdmin; + bLoadSucceeded = false; + } + + if (bAdmin && AreServerSettingsLoaded() && bLoadSucceeded == false) + LoadServerSettings(); + + UpdateStatusText(); + UpdateAuthButton(); + if (Btn_DeletePassword != none && Cmb_AdminPassword != none) + Btn_DeletePassword.bDisabled = (class'StringUtils'.static.Trim(Cmb_AdminPassword.GetValue()) == ""); + + WndWidth = WinWidth - 2*PaddingX; + ConfigureResponsiveServerControls(C, WndWidth); + Top = PaddingY; + bShowSettings = bAdmin && AreServerSettingsLoaded(); + + LayoutControl(Lbl_Header, true, WndWidth, Top); + LayoutControl(Lbl_Status, true, WndWidth, Top); + LayoutControl(Lbl_MoreInformation, bAdmin, WndWidth, Top); + LayoutControl(Btn_AdminAuth, true, WndWidth, Top); + + LayoutControl(Lbl_Login, !bAdmin, WndWidth, Top); + LayoutPasswordControls(C, !bAdmin, WndWidth, Top); + + LayoutControl(Lbl_General, bShowSettings, WndWidth, Top); + LayoutControl(Chk_bAutoPause, bShowSettings, WndWidth, Top); + LayoutControl(Edit_PauseTotalTime, bShowSettings, WndWidth, Top); + LayoutControl(Edit_PauseTime, bShowSettings, WndWidth, Top); + LayoutControl(Chk_bForceDemo, bShowSettings, WndWidth, Top); + LayoutControl(Chk_bRestrictTrading, bShowSettings, WndWidth, Top); + LayoutControl(Edit_MaxTradeTimeMargin, bShowSettings, WndWidth, Top); + LayoutControl(Edit_TradePingMargin, bShowSettings, WndWidth, Top); + LayoutControl(Edit_KillCamDelay, bShowSettings, WndWidth, Top); + LayoutControl(Edit_KillCamDuration, bShowSettings, WndWidth, Top); + LayoutControl(Cmb_BrightskinMode, bShowSettings, WndWidth, Top); + LayoutControl(Edit_PlayerScale, bShowSettings, WndWidth, Top); + LayoutControl(Chk_bAlwaysRenderFlagCarrier, bShowSettings, WndWidth, Top); + LayoutControl(Chk_bAlwaysRenderDroppedFlags, bShowSettings, WndWidth, Top); + LayoutControl(Cmb_HitFeedbackMode, bShowSettings, WndWidth, Top); + LayoutControl(Chk_bEnablePingCompensatedSpawn, bShowSettings, WndWidth, Top); + + LayoutControl(Lbl_Movement, bShowSettings, WndWidth, Top); + LayoutControl(Chk_bJumpingPreservesMomentum, bShowSettings, WndWidth, Top); + LayoutControl(Chk_bOldLandingMomentum, bShowSettings, WndWidth, Top); + LayoutControl(Chk_bEnableSingleButtonDodge, bShowSettings, WndWidth, Top); + LayoutControl(Chk_bUseFlipAnimation, bShowSettings, WndWidth, Top); + LayoutControl(Chk_bEnableWallDodging, bShowSettings, WndWidth, Top); + LayoutControl(Chk_bDodgePreserveZMomentum, bShowSettings, WndWidth, Top); + LayoutControl(Edit_MaxMultiDodges, bShowSettings, WndWidth, Top); + + LayoutControl(Lbl_Networking, bShowSettings, WndWidth, Top); + LayoutControl(Edit_MaxPosError, bShowSettings, WndWidth, Top); + LayoutControl(Edit_MaxHitError, bShowSettings, WndWidth, Top); + LayoutControl(Edit_FireTimeout, bShowSettings, WndWidth, Top); + LayoutControl(Edit_MinNetUpdateRate, bShowSettings, WndWidth, Top); + LayoutControl(Edit_MaxNetUpdateRate, bShowSettings, WndWidth, Top); + LayoutControl(Chk_bEnableServerExtrapolation, bShowSettings, WndWidth, Top); + LayoutControl(Chk_bEnableServerPacketReordering, bShowSettings, WndWidth, Top); + LayoutControl(Chk_bPlayersAlwaysRelevant, bShowSettings, WndWidth, Top); + LayoutControl(Chk_bEnableJitterBounding, bShowSettings, WndWidth, Top); + LayoutControl(Edit_MaxJitterTime, bShowSettings, WndWidth, Top); + LayoutControl(Chk_bEnableInputReplication, bShowSettings, WndWidth, Top); + LayoutControl(Chk_bEnableSnapshotInterpolation, bShowSettings, WndWidth, Top); + LayoutControl(Edit_SnapshotInterpSendHz, bShowSettings, WndWidth, Top); + LayoutControl(Edit_SnapshotInterpRewindMs, bShowSettings, WndWidth, Top); + LayoutControl(Chk_bEnableLoosePositionCheck, bShowSettings, WndWidth, Top); + LayoutControl(Edit_LooseCheckCorrectionFactor, bShowSettings, WndWidth, Top); + LayoutControl(Edit_LooseCheckCorrectionFactorOnMover, bShowSettings, WndWidth, Top); + LayoutControl(Chk_bEnableWarpFix, bShowSettings, WndWidth, Top); + LayoutControl(Edit_WarpFixDelay, bShowSettings, WndWidth, Top); + + LayoutControl(Lbl_Debug, bShowSettings, WndWidth, Top); + LayoutControl(Chk_ShowTouchedPackage, bShowSettings, WndWidth, Top); + LayoutControl(Chk_bEnableDamageDebugMode, bShowSettings, WndWidth, Top); + LayoutControl(Chk_bEnableDamageDebugConsoleMessages, bShowSettings, WndWidth, Top); + LayoutControl(Chk_bEnableHitboxDebugMode, bShowSettings, WndWidth, Top); + + DesiredHeight = Top + PaddingY; +} + +function Notify(UWindowDialogControl C, byte E) { + super.Notify(C, E); + + if (E == DE_Click && C == Btn_AdminAuth) { + if (HasServerAdminAccess()) + AdminLogoutFromUI(); + else + AdminLoginFromUI(); + } else if (E == DE_Click && C == Btn_DeletePassword) { + DeletePasswordFromUI(); + } +} + +function Load() { + bLastAdminState = HasServerAdminAccess(); + bLoadSucceeded = false; + bPendingAdminLogin = false; + ResetLocalServerSettingsCache(); + RequestServerSettings(true); + PopulatePasswordHistory(); + UpdateStatusText(); + UpdateAuthButton(); +} + +function Save() { + if (HasServerAdminAccess() == false) { + bPendingAdminLogin = false; + SetStatusText(StatusLoginText); + return; + } + + if (AreServerSettingsLoaded() == false) { + ResetLocalServerSettingsCache(); + RequestServerSettings(true); + return; + } + + SaveServerSettings(); + bPendingAdminLogin = false; + SetStatusText(StatusAdminText); +} + +function SaveConfigs() { + local ClientSettings Settings; + local UWindowWindow Dialog; + + super.SaveConfigs(); + + Settings = FindSettingsObject(); + if (Settings == none) + return; + + Dialog = GetParent(class'UWindowFramedWindow'); + if (Dialog == none) + return; + + Settings.MenuX = Dialog.WinLeft; + Settings.MenuY = Dialog.WinTop; + Settings.MenuWidth = Dialog.WinWidth; + Settings.MenuHeight = Dialog.WinHeight; + Settings.SaveConfig(); +} + +defaultproperties +{ + HeaderText="Server Settings" + StatusAdminText="Admin access granted. Edit settings and click Save." + StatusLoginText="Admin access required. Enter password and click Login." + StatusLoadingText="Loading server settings..." + StatusLoginPendingText="Login requested..." + MoreInformationText="Right-click on settings to get more information" + + LoginText="Admin Login" + AdminPasswordText="Password" + AdminPasswordHelp="Enter password" + DeletePasswordButtonText="X" + DeletePasswordButtonHelp="Delete this password from saved history" + LoginButtonText="Login" + LoginButtonHelp="Log in as admin" + LogoutButtonText="Logout" + LogoutButtonHelp="Log out of admin account" + LoginRequiredText="Please enter admin password first." + + GeneralText="General" + bAutoPauseText="Enable Auto Pause" + bAutoPauseHelp="If checked, teams can use match pause functionality in tournament mode" + PauseTotalTimeText="Total Pause Time" + PauseTotalTimeHelp="Total pause time in seconds available to each team" + PauseTimeText="Pause Time" + PauseTimeHelp="Maximum duration in seconds of a single pause" + bForceDemoText="Force Demo Recording" + bForceDemoHelp="If checked, players are forced to record demos" + bRestrictTradingText="Restrict Trading" + bRestrictTradingHelp="If checked, trade kills are only accepted within limited timing margins" + MaxTradeTimeMarginText="Max Trade Time Margin" + MaxTradeTimeMarginHelp="Maximum allowed trade window in seconds" + TradePingMarginText="Trade Ping Margin" + TradePingMarginHelp="Additional trade allowance scaled by player ping" + KillCamDelayText="Kill Cam Delay" + KillCamDelayHelp="Delay in seconds before kill cam starts" + KillCamDurationText="Kill Cam Duration" + KillCamDurationHelp="How long kill cam runs in seconds" + BrightskinModeText="Brightskin Mode" + BrightskinModeHelp="Select how player skins are rendered on clients" + BrightskinModeDisabled="Disabled" + BrightskinModeUnlit="Unlit" + PlayerScaleText="Player Scale" + PlayerScaleHelp="Scale multiplier applied to player models" + bAlwaysRenderFlagCarrierText="Always Render Flag Carrier" + bAlwaysRenderFlagCarrierHelp="If checked, flag carriers are always rendered" + bAlwaysRenderDroppedFlagsText="Always Render Dropped Flags" + bAlwaysRenderDroppedFlagsHelp="If checked, dropped flags are always rendered" + HitFeedbackModeText="Hit Feedback Mode" + HitFeedbackModeHelp="Controls when hit feedback events are allowed" + HitFeedbackModeDisabled="Disabled" + HitFeedbackModeVisibleOnly="Visible Only" + HitFeedbackModeAlways="Always" + bEnablePingCompensatedSpawnHelp="If checked, enables ping-compensated spawn behavior" + + MovementText="Movement" + bJumpingPreservesMomentumText="Jumping Preserves Momentum" + bJumpingPreservesMomentumHelp="If checked, jumping keeps horizontal momentum more aggressively" + bOldLandingMomentumText="Old Landing Momentum" + bOldLandingMomentumHelp="If checked, uses legacy landing momentum handling" + bEnableSingleButtonDodgeText="Enable Single Button Dodge" + bEnableSingleButtonDodgeHelp="If checked, allows dodging from single key press input" + bUseFlipAnimationText="Use Flip Animation" + bUseFlipAnimationHelp="If checked, uses flip animation for dodge-style movement" + bEnableWallDodgingText="Enable Wall Dodging" + bEnableWallDodgingHelp="If checked, allows players to wall dodge" + bDodgePreserveZMomentumText="Preserve Dodge Z Momentum" + bDodgePreserveZMomentumHelp="If checked, dodge keeps more vertical momentum" + MaxMultiDodgesText="Max Multi Dodges" + MaxMultiDodgesHelp="Maximum allowed chained dodges before touching ground" + bEnablePingCompensatedSpawnText="Enable Ping Compensated Spawn" + + NetworkingText="Networking" + MaxPosErrorText="Max Position Error" + MaxPosErrorHelp="Maximum position error before server correction is forced" + MaxHitErrorText="Max Hit Error" + MaxHitErrorHelp="Maximum tolerated hit registration error" + MaxJitterTimeText="Max Jitter Time" + MaxJitterTimeHelp="Maximum jitter time in seconds used for movement correction" + WarpFixDelayText="Warp Fix Delay" + WarpFixDelayHelp="Delay in seconds before warp-fix smoothing is applied" + FireTimeoutText="Fire Timeout" + FireTimeoutHelp="Time in seconds before queued fire input expires" + MinNetUpdateRateText="Min Net Update Rate" + MinNetUpdateRateHelp="Minimum per-client update rate the server allows" + MaxNetUpdateRateText="Max Net Update Rate" + MaxNetUpdateRateHelp="Maximum per-client update rate the server allows" + bEnableInputReplicationText="Enable Input Replication" + bEnableInputReplicationHelp="If checked, replicates movement input data for validation" + bEnableServerExtrapolationText="Enable Server Extrapolation" + bEnableServerExtrapolationHelp="If checked, server extrapolates movement between updates" + bEnableServerPacketReorderingText="Enable Packet Reordering" + bEnableServerPacketReorderingHelp="If checked, server tries to reorder delayed movement packets" + bEnableLoosePositionCheckText="Enable Loose Position Check" + bEnableLoosePositionCheckHelp="If checked, uses looser position validation rules" + bPlayersAlwaysRelevantText="Players Always Relevant" + bPlayersAlwaysRelevantHelp="If checked, players stay network relevant regardless of distance" + bEnableJitterBoundingText="Enable Jitter Bounding" + bEnableJitterBoundingHelp="If checked, bounds movement jitter spikes" + LooseCheckCorrectionFactorText="Loose Check Correction Factor" + LooseCheckCorrectionFactorHelp="Correction strength used by loose position checks" + LooseCheckCorrectionFactorOnMoverText="Loose Check Correction On Mover" + LooseCheckCorrectionFactorOnMoverHelp="Loose check correction strength while standing on movers" + bEnableSnapshotInterpolationText="Enable Snapshot Interpolation" + bEnableSnapshotInterpolationHelp="If checked, clients smooth movement using snapshot interpolation" + SnapshotInterpSendHzText="Snapshot Send Hz" + SnapshotInterpSendHzHelp="Snapshot transmission frequency in Hz" + SnapshotInterpRewindMsText="Snapshot Rewind (ms)" + SnapshotInterpRewindMsHelp="Interpolation rewind delay in milliseconds" + bEnableWarpFixText="Enable Warp Fix" + bEnableWarpFixHelp="If checked, enables warp correction smoothing" + + DebugText="Debug" + ShowTouchedPackageText="Show Touched Package" + ShowTouchedPackageHelp="If checked, logs touched-package details for diagnostics" + bEnableDamageDebugModeText="Enable Damage Debug Mode" + bEnableDamageDebugModeHelp="If checked, enables server-side damage debug tracing" + bEnableDamageDebugConsoleMessagesText="Damage Debug Console Messages" + bEnableDamageDebugConsoleMessagesHelp="If checked, prints damage debug messages to client consoles" + bEnableHitboxDebugModeText="Enable Hitbox Debug Mode" + bEnableHitboxDebugModeHelp="If checked, enables hitbox debug diagnostics" + + PaddingX=20 + PaddingY=12 + LineSpacing=22 + SeparatorSpacing=30 +} diff --git a/Classes/IGPlus_SettingsContent.uc b/Classes/IGPlus_SettingsContent.uc index 17acb3c..f35abb1 100644 --- a/Classes/IGPlus_SettingsContent.uc +++ b/Classes/IGPlus_SettingsContent.uc @@ -2,6 +2,9 @@ class IGPlus_SettingsContent extends UMenuPageWindow; var ClientSettings Settings; +var UWindowLabelControl Lbl_Header; +var localized string HeaderText; + var UWindowLabelControl Lbl_MoreInformation; var localized string MoreInformationText; @@ -842,6 +845,10 @@ function Created() { ControlOffset = PaddingY; ControlWidth = WinWidth - 2*PaddingX; + Lbl_Header = CreateLabel(HeaderText); + Lbl_Header.Align = TA_Center; + Lbl_Header.SetFont(F_Bold); + Lbl_MoreInformation = CreateLabel(MoreInformationText); Lbl_MoreInformation.Align = TA_Center; Lbl_MoreInformation.SetFont(F_Bold); @@ -1290,6 +1297,7 @@ function SaveConfigs() { defaultproperties { + HeaderText="Client Settings" MoreInformationText="Right-click on settings to get more information" GeneralText="General" @@ -1596,31 +1604,31 @@ defaultproperties ClientSideAnimationsText="Ping Compensation Settings" - BioUseClientSideAnimationsText="Use Client-Side Animations for Bio Rifle" + BioUseClientSideAnimationsText="Client-Side Animations Bio Rifle" BioUseClientSideAnimationsHelp="If checked, use client-side animations for Bio Rifle" - ShockBeamUseClientSideAnimationsText="Use Client-Side Animations for Shock Beam" + ShockBeamUseClientSideAnimationsText="Client-Side Animations Shock Beam" ShockBeamUseClientSideAnimationsHelp="If checked, use client-side animations for Shock Beam" - ShockProjectileUseClientSideAnimationsText="Use Client-Side Animations for Shock Projectile" + ShockProjectileUseClientSideAnimationsText="Client-Side Animations Shock Projectile" ShockProjectileUseClientSideAnimationsHelp="If checked, use client-side animations for Shock Projectile" - PulseUseClientSideAnimationsText="Use Client-Side Animations for Pulse Rifle" + PulseUseClientSideAnimationsText="Client-Side Animations Pulse Rifle" PulseUseClientSideAnimationsHelp="If checked, use client-side animations for Pulse Rifle" - RipperUseClientSideAnimationsText="Use Client-Side Animations for Ripper" + RipperUseClientSideAnimationsText="Client-Side Animations Ripper" RipperUseClientSideAnimationsHelp="If checked, use client-side animations for Ripper" - FlakUseClientSideAnimationsText="Use Client-Side Animations for Flak Cannon" + FlakUseClientSideAnimationsText="Client-Side Animations Flak Cannon" FlakUseClientSideAnimationsHelp="If checked, use client-side animations for Flak Cannon" - RocketUseClientSideAnimationsText="Use Client-Side Animations for Rocket Launcher" + RocketUseClientSideAnimationsText="Client-Side Animations Rocket Launcher" RocketUseClientSideAnimationsHelp="If checked, use client-side animations for Rocket Launcher" - SniperUseClientSideAnimationsText="Use Client-Side Animations for Sniper Rifle" + SniperUseClientSideAnimationsText="Client-Side Animations Sniper Rifle" SniperUseClientSideAnimationsHelp="If checked, use client-side animations for Sniper Rifle" - TranslocatorUseClientSideAnimationsText="Use Client-Side Animations for Translocator" + TranslocatorUseClientSideAnimationsText="Client-Side Animations Translocator" TranslocatorUseClientSideAnimationsHelp="If checked, use client-side animations for Translocator" PaddingX=20 diff --git a/Classes/IGPlus_SettingsScroll.uc b/Classes/IGPlus_SettingsScroll.uc index eee8617..91327a5 100644 --- a/Classes/IGPlus_SettingsScroll.uc +++ b/Classes/IGPlus_SettingsScroll.uc @@ -1,16 +1,93 @@ class IGPlus_SettingsScroll extends UWindowScrollingDialogClient; +enum ESettingsTab { + ST_Client, + ST_Server +}; + var UWindowSmallButton Btn_Close; var UWindowSmallButton Btn_Save; +var UWindowSmallButton Btn_TabToggle; var localized string SaveButtonText; var localized string SaveButtonToolTip; +var localized string ClientTabText; +var localized string ClientTabToolTip; +var localized string ServerTabText; +var localized string ServerTabToolTip; + +var class ClientTabClass; +var class ServerTabClass; +var ESettingsTab ActiveTab; +var UWindowDialogClientWindow ClientTabArea; +var UWindowDialogClientWindow ServerTabArea; var float FixedPaddingX; var float FixedPaddingY; +function UpdateTabToggleButton() { + if (Btn_TabToggle == none) + return; + + if (ActiveTab == ST_Client) { + Btn_TabToggle.SetText(ServerTabText); + Btn_TabToggle.ToolTipString = ServerTabToolTip; + } else { + Btn_TabToggle.SetText(ClientTabText); + Btn_TabToggle.ToolTipString = ClientTabToolTip; + } +} + +function EnsureServerTabArea() { + if (ServerTabArea != none) + return; + + if (ServerTabClass == none) + return; + + ServerTabArea = UWindowDialogClientWindow(CreateWindow(ServerTabClass, 0, 0, WinWidth, WinHeight, OwnerWindow)); + if (ServerTabArea != none) + ServerTabArea.HideWindow(); +} + +function SetActiveTab(ESettingsTab NewTab) { + if (NewTab == ActiveTab && ClientArea != none) + return; + + EnsureServerTabArea(); + + ActiveTab = NewTab; + if (ActiveTab == ST_Server && ServerTabArea != none) { + if (ClientTabArea != none && ClientTabArea.bWindowVisible) + ClientTabArea.HideWindow(); + if (ServerTabArea.bWindowVisible == false) + ServerTabArea.ShowWindow(); + ClientArea = ServerTabArea; + } else { + if (ServerTabArea != none && ServerTabArea.bWindowVisible) + ServerTabArea.HideWindow(); + if (ClientTabArea != none && ClientTabArea.bWindowVisible == false) + ClientTabArea.ShowWindow(); + ClientArea = ClientTabArea; + ActiveTab = ST_Client; + } + + UpdateTabToggleButton(); + + Load(); +} + function Created() { super.Created(); + Btn_TabToggle = UWindowSmallButton(FixedArea.CreateControl( + class'UWindowSmallButton', + FixedPaddingX, + FixedPaddingY, + 32, + 16 + )); + Btn_TabToggle.Register(self); + Btn_Save = UWindowSmallButton(FixedArea.CreateControl( class'UWindowSmallButton', FixedArea.WinWidth-FixedPaddingX-72, @@ -31,21 +108,37 @@ function Created() { )); FixedArea.WinHeight = 2*FixedPaddingY + 16; + + ClientTabArea = ClientArea; + EnsureServerTabArea(); + + // Force first SetActiveTab call to run initialization path. + ActiveTab = ST_Server; + SetActiveTab(ST_Client); } function Notify(UWindowDialogControl C, byte E) { Super.Notify(C, E); - if (E == DE_Click && C == Btn_Save) { + if (E == DE_Click && C == Btn_TabToggle) { + if (ActiveTab == ST_Client) + SetActiveTab(ST_Server); + else + SetActiveTab(ST_Client); + } else if (E == DE_Click && C == Btn_Save) { Save(); - Load(); + if (IGPlus_SettingsContent(ClientArea) != none) + Load(); } } function BeforePaint(Canvas C, float X, float Y) { super.BeforePaint(C, X, Y); + Btn_TabToggle.AutoWidth(C); + Btn_TabToggle.WinLeft = FixedPaddingX; + Btn_Close.AutoWidth(C); Btn_Close.WinLeft = FixedArea.WinWidth-FixedPaddingX-Btn_Close.WinWidth; @@ -54,21 +147,33 @@ function BeforePaint(Canvas C, float X, float Y) { } function Load() { - IGPlus_SettingsContent(ClientArea).Load(); + if (IGPlus_SettingsContent(ClientArea) != none) + IGPlus_SettingsContent(ClientArea).Load(); + else if (IGPlus_ServerSettingsContent(ClientArea) != none) + IGPlus_ServerSettingsContent(ClientArea).Load(); } function Save() { - IGPlus_SettingsContent(ClientArea).Save(); + if (IGPlus_SettingsContent(ClientArea) != none) + IGPlus_SettingsContent(ClientArea).Save(); + else if (IGPlus_ServerSettingsContent(ClientArea) != none) + IGPlus_ServerSettingsContent(ClientArea).Save(); } defaultproperties { ClientClass=class'IGPlus_SettingsContent' FixedAreaClass=class'UWindowDialogClientWindow' + ClientTabClass=class'IGPlus_SettingsContent' + ServerTabClass=class'IGPlus_ServerSettingsContent' SaveButtonText="Save" SaveButtonToolTip="Saves the current settings to InstaGibPlus.ini" + ClientTabText="Client" + ClientTabToolTip="Open client settings" + ServerTabText="Server" + ServerTabToolTip="Open server settings" FixedPaddingX=20 FixedPaddingY=5 -} \ No newline at end of file +} diff --git a/Classes/IGPlus_WeaponImplementation.uc b/Classes/IGPlus_WeaponImplementation.uc index 26bd503..28db156 100644 --- a/Classes/IGPlus_WeaponImplementation.uc +++ b/Classes/IGPlus_WeaponImplementation.uc @@ -70,6 +70,24 @@ simulated function bool CheckBodyShotCompensated(UTPlusDummy D, vector HitLocati function float GetAverageTickRate(); +function float IGPlus_GetOneWayLatencyMs(Pawn Instigator); + +function float IGPlus_GetHitscanRewindMs(Pawn Instigator); + +function vector IGPlus_AdjustLocationToCurrentMoverFrame( + Pawn Instigator, + vector FireLoc, + float SampleLatencyMs, + optional Mover BaseMover +); + +function vector IGPlus_AdjustLocationToHistoricalMoverFrame( + Pawn Instigator, + vector FireLoc, + float SampleLatencyMs, + optional Mover BaseMover +); + function SimulateProjectileWithHistory(ST_TranslocatorTarget TTarget, int Ping); function SimulateProjectile(Projectile P, int Ping); diff --git a/Classes/IGPlus_WeaponImplementationBase.uc b/Classes/IGPlus_WeaponImplementationBase.uc index ff1297a..ca21031 100644 --- a/Classes/IGPlus_WeaponImplementationBase.uc +++ b/Classes/IGPlus_WeaponImplementationBase.uc @@ -17,6 +17,179 @@ var int ExplosionWriteIndex; var float CachedTickRate; +function float IGPlus_GetSnapshotInterpRewindMs(Pawn Instigator) { + local bbPlayer bbP; + local float AcceptedAge; + + if (Instigator == None) + return 0.0; + + bbP = bbPlayer(Instigator); + if (bbP == None || bbP.IGPlus_EnableSnapshotInterpolation == false) + return 0.0; + + AcceptedAge = bbP.Level.TimeSeconds - bbP.IGPlus_ServerSnapInterpLastAcceptedTime; + if (AcceptedAge < 0.0) + AcceptedAge = 0.0; + + if (bbP.IGPlus_ServerSnapInterpDelayValid && + bbP.IGPlus_ServerSnapInterpTrusted && + AcceptedAge <= 1.0 + ) { + return FMax(0.0, bbP.IGPlus_ServerSnapInterpDelayMsSmoothed); + } + + if (bbP.zzUTPure != None && bbP.zzUTPure.Settings != None) + return FMax(0.0, bbP.zzUTPure.Settings.SnapshotInterpRewindMs); + + return 0.0; +} + +function float IGPlus_GetLatencyMs(Pawn Instigator, optional bool bOneWay) { + local bbPlayer bbP; + local float PingMs; + + if (Instigator == None) + return 0.0; + + bbP = bbPlayer(Instigator); + if (bbP != None) { + if (bbP.PingAverage > 0) + PingMs = float(bbP.PingAverage); + if (PingMs <= 0.0 && bbP.PlayerReplicationInfo != None && bbP.PlayerReplicationInfo.Ping > 0) + PingMs = float(bbP.PlayerReplicationInfo.Ping); + } + + if (PingMs <= 0.0 && Instigator.PlayerReplicationInfo != None && Instigator.PlayerReplicationInfo.Ping > 0) + PingMs = float(Instigator.PlayerReplicationInfo.Ping); + + if (bOneWay) + return FMax(0.0, 0.5 * PingMs); + return FMax(0.0, PingMs); +} + +function float IGPlus_GetOneWayLatencyMs(Pawn Instigator) { + return IGPlus_GetLatencyMs(Instigator, true); +} + +function float IGPlus_GetHitscanRewindMs(Pawn Instigator) { + local bbPlayer bbP; + local float EffectivePing; + local float InterpMs; + local float BasePingMs; + + if (Instigator == None) + return 0.0; + + bbP = bbPlayer(Instigator); + if (bbP == None) { + return IGPlus_GetLatencyMs(Instigator, false); + } + + InterpMs = IGPlus_GetSnapshotInterpRewindMs(Instigator); + BasePingMs = IGPlus_GetLatencyMs(Instigator, false); + if (InterpMs > 0.0) { + if (bbP.PingAverage > 0) + EffectivePing = (0.5 * float(bbP.PingAverage)) + InterpMs; + else + EffectivePing = (0.5 * BasePingMs) + InterpMs; + } else + EffectivePing = BasePingMs; + + if (WSettingsRepl != None && EffectivePing > WSettingsRepl.PingCompensationMax) + EffectivePing = WSettingsRepl.PingCompensationMax; + if (EffectivePing < 0.0) + EffectivePing = 0.0; + + return EffectivePing; +} + +function vector IGPlus_AdjustLocationToCurrentMoverFrame( + Pawn Instigator, + vector FireLoc, + float SampleLatencyMs, + optional Mover BaseMover +) { + local bbPlayer bbP; + local UTPure PureRef; + local ST_MoverDummy MD; + local Mover M; + local float SampleSeconds; + local float SampleTimeStamp; + local vector HistoricalLoc; + + if (Instigator == None || SampleLatencyMs <= 0.0) + return FireLoc; + + M = BaseMover; + if (M == None) + M = Mover(Instigator.Base); + if (M == None) + return FireLoc; + + bbP = bbPlayer(Instigator); + if (bbP == None) + return FireLoc; + + PureRef = bbP.zzUTPure; + if (PureRef == None) + return FireLoc; + + SampleSeconds = 0.001 * SampleLatencyMs * Level.TimeDilation; + + MD = PureRef.FindMoverDummy(M); + if (MD == None) + return FireLoc + M.Velocity * SampleSeconds; + + SampleTimeStamp = Level.TimeSeconds - SampleSeconds; + HistoricalLoc = MD.GetHistoricalLocation(SampleTimeStamp); + + return FireLoc + (M.Location - HistoricalLoc); +} + +function vector IGPlus_AdjustLocationToHistoricalMoverFrame( + Pawn Instigator, + vector FireLoc, + float SampleLatencyMs, + optional Mover BaseMover +) { + local bbPlayer bbP; + local UTPure PureRef; + local ST_MoverDummy MD; + local Mover M; + local float SampleSeconds; + local float SampleTimeStamp; + local vector HistoricalLoc; + + if (Instigator == None || SampleLatencyMs <= 0.0) + return FireLoc; + + M = BaseMover; + if (M == None) + M = Mover(Instigator.Base); + if (M == None) + return FireLoc; + + bbP = bbPlayer(Instigator); + if (bbP == None) + return FireLoc; + + PureRef = bbP.zzUTPure; + if (PureRef == None) + return FireLoc; + + SampleSeconds = 0.001 * SampleLatencyMs * Level.TimeDilation; + + MD = PureRef.FindMoverDummy(M); + if (MD == None) + return FireLoc - M.Velocity * SampleSeconds; + + SampleTimeStamp = Level.TimeSeconds - SampleSeconds; + HistoricalLoc = MD.GetHistoricalLocation(SampleTimeStamp); + + return FireLoc + (HistoricalLoc - M.Location); +} + function PostBeginPlay() { super.PostBeginPlay(); @@ -647,6 +820,148 @@ function bool LineIntersectsCylinder(vector Start, vector End, vector CylinderCe return (DistXYSq <= Square(Radius) && DistZ <= HalfHeight); } +function bool PointInsideCylinder(vector P, vector CylinderCenter, float Radius, float HalfHeight) { + local float DistXYSq; + + DistXYSq = Square(P.X - CylinderCenter.X) + Square(P.Y - CylinderCenter.Y); + return (DistXYSq <= Square(Radius) && Abs(P.Z - CylinderCenter.Z) <= HalfHeight); +} + +// Prevent compensated hits through a target's lift when firing from below. +// Uses mover history at the same rewind time as pawn compensation. +function bool IsCompensatedHitBlockedByTargetMover( + UTPlusDummy TargetDummy, + Pawn Shooter, + vector StartTrace, + vector HitLocation, + int RewindPing +) { + local Mover BaseMover; + local vector HistoricalMoverLoc; + + if (TargetDummy == None || TargetDummy.Actual == None || Shooter == None) + return false; + + BaseMover = Mover(TargetDummy.Actual.Base); + if (BaseMover == None) + return false; + + // Same-base shots should not be blocked by this protection. + if (Shooter.Base == BaseMover) + return false; + + HistoricalMoverLoc = BaseMover.Location; + // Fallback stays at current mover location if history is unavailable. + GetHistoricalMoverLocationForRewind(Shooter, BaseMover, RewindPing, HistoricalMoverLoc); + + // This guard is only for the "from below through mover" case. + if (StartTrace.Z >= HistoricalMoverLoc.Z || HitLocation.Z <= HistoricalMoverLoc.Z) + return false; + + return LineIntersectsCylinder( + StartTrace, + HitLocation, + HistoricalMoverLoc, + BaseMover.CollisionRadius, + BaseMover.CollisionHeight + ); +} + +function bool ShouldIgnoreCompensatedSelfBaseHit( + Pawn Shooter, + vector StartTrace, + vector HitLocation, + int RewindPing +) { + local Mover BaseMover; + local vector HistoricalMoverLoc; + local bool bHasHistorical; + local vector ShotDir; + + if (Shooter == None) + return false; + + BaseMover = Mover(Shooter.Base); + if (BaseMover == None) + return false; + + HistoricalMoverLoc = BaseMover.Location; + bHasHistorical = GetHistoricalMoverLocationForRewind(Shooter, BaseMover, RewindPing, HistoricalMoverLoc); + if (!bHasHistorical) + return false; + + // Our mover proxy is cylindrical while real movers are brush geometry. When + // the shooter rides an upward mover, compensated muzzle origins can land + // inside this proxy even when the actual shot should clear the lift. + ShotDir = Normal(HitLocation - StartTrace); + if (ShotDir.Z >= 0.0 && + PointInsideCylinder( + StartTrace, + HistoricalMoverLoc, + BaseMover.CollisionRadius, + BaseMover.CollisionHeight + ) + ) { + return true; + } + + // Ignore a self-base hit only if the shot segment does not intersect the + // mover at the same historical rewind frame. + return !LineIntersectsCylinder( + StartTrace, + HitLocation, + HistoricalMoverLoc, + BaseMover.CollisionRadius, + BaseMover.CollisionHeight + ); +} + +function bool GetHistoricalMoverLocationForRewind( + Pawn Shooter, + Mover M, + int RewindPing, + out vector HistoricalMoverLoc +) { + local bbPlayer bbShooter; + local UTPure PureRef; + local ST_MoverDummy MD; + local float TargetTimeStamp; + + if (M == None || Shooter == None || RewindPing <= 0) + return false; + + bbShooter = bbPlayer(Shooter); + if (bbShooter == None) + return false; + + PureRef = bbShooter.zzUTPure; + if (PureRef == None) + return false; + + MD = PureRef.FindMoverDummy(M); + if (MD == None) + return false; + + TargetTimeStamp = Level.TimeSeconds - 0.001 * float(RewindPing) * Level.TimeDilation; + HistoricalMoverLoc = MD.GetHistoricalLocation(TargetTimeStamp); + return true; +} + +function int ResolveSelfBaseSamplePing(Pawn Shooter, int RewindPing) { + local int OneWayPing; + + if (Shooter == None) + return RewindPing; + + // For own-base occlusion we want the lift frame the shooter saw when firing, + // so use one-way latency instead of target rewind timeline. + OneWayPing = int(IGPlus_GetOneWayLatencyMs(Shooter)); + if (OneWayPing > 0) + return OneWayPing; + + return RewindPing; +} + // Check for beam weapon (PBolt/Pulse Gun) collisions during simulation // Returns true if the translocator was hit function bool CheckSimulationBeamCollisions(ST_TranslocatorTarget TTarget, float DeltaTime) { @@ -801,6 +1116,7 @@ function SimulateProjectileWithHistory(ST_TranslocatorTarget TTarget, int Ping) local float GameDeltaTime; local float RealDeltaTime; local float SimPing; + local float InterpMs; local float RealTimeRemaining; local float AccumulatedGameTime; local int HistoryInterval; @@ -817,7 +1133,8 @@ function SimulateProjectileWithHistory(ST_TranslocatorTarget TTarget, int Ping) PureRef = bbPlayer(TTarget.Instigator).zzUTPure; - SimPing = float(Ping); + InterpMs = IGPlus_GetSnapshotInterpRewindMs(TTarget.Instigator); + SimPing = float(Ping) + InterpMs; // Cap ping compensation if (SimPing > WeaponSettings.PingCompensationMax) @@ -901,6 +1218,7 @@ function SimulateProjectileWithHistory(ST_TranslocatorTarget TTarget, int Ping) function SimulateProjectile(Projectile P, int Ping) { local float DeltaTime; local float SimPing; + local float InterpMs; local UTPure PureRef; // Early exit if the projectile is already destroyed @@ -909,7 +1227,8 @@ function SimulateProjectile(Projectile P, int Ping) { PureRef = bbPlayer(P.Instigator).zzUTPure; - SimPing = Ping * 0.5; + InterpMs = IGPlus_GetSnapshotInterpRewindMs(P.Instigator); + SimPing = Ping * 0.5 + InterpMs; // Cap ping compensation if (SimPing > WeaponSettings.PingCompensationMax) @@ -940,6 +1259,7 @@ function SimulateProjectile(Projectile P, int Ping) { function BatchSimulateProjectiles(Projectile Projectiles[6], int NumProjectiles, int Ping) { local float DeltaTime; local float SimPing; + local float InterpMs; local UTPure PureRef; local int i; local bool bAnyAlive; @@ -949,7 +1269,8 @@ function BatchSimulateProjectiles(Projectile Projectiles[6], int NumProjectiles, PureRef = bbPlayer(Projectiles[0].Instigator).zzUTPure; - SimPing = Ping * 0.5; // Simulate only 1-way latency + InterpMs = IGPlus_GetSnapshotInterpRewindMs(Projectiles[0].Instigator); + SimPing = Ping * 0.5 + InterpMs; // Simulate only 1-way latency if (SimPing > WeaponSettings.PingCompensationMax) SimPing = WeaponSettings.PingCompensationMax; @@ -996,6 +1317,7 @@ simulated function Actor TraceShotInternal( vector EndTrace, vector StartTrace, Pawn PawnOwner, + int RewindPing, bool bWeaponShock, bool bSProjBlocks, bool bCompensated @@ -1019,6 +1341,8 @@ simulated function Actor TraceShotInternal( if (CheckBodyShotCompensated(D, HitLocation, Dir) == false && CheckHeadShotCompensated(D, HitLocation, Dir) == false) continue; + if (IsCompensatedHitBlockedByTargetMover(D, PawnOwner, StartTrace, HitLocation, RewindPing)) + continue; Other = D.Actual; break; } @@ -1055,10 +1379,16 @@ simulated function Actor TraceShotInternal( } } - if ((A == Level) || (Mover(A) != None) || A.bProjTarget || (A.bBlockPlayers && A.bBlockActors)) { - if (bSProjBlocks || A.IsA('ShockProj') == false || bWeaponShock) { - Other = A; - break; + if ((A == Level) || (Mover(A) != None) || A.bProjTarget || (A.bBlockPlayers && A.bBlockActors)) { + // If compensated muzzle origin clips into the instigator's own mover, skip + // only the immediate self-base hit so the ray can continue normally. + if (bCompensated && A == PawnOwner.Base && Mover(A) != None) { + if (ShouldIgnoreCompensatedSelfBaseHit(PawnOwner, StartTrace, HitLocation, ResolveSelfBaseSamplePing(PawnOwner, RewindPing))) + continue; + } + if (bSProjBlocks || A.IsA('ShockProj') == false || bWeaponShock) { + Other = A; + break; } } } @@ -1074,6 +1404,7 @@ simulated function Actor TraceShot(out vector HitLocation, out vector HitNormal, local bbPlayer bbP; local int Ping; local UTPure PureRef; + local float EffectivePing; Ping = 0; @@ -1082,10 +1413,8 @@ simulated function Actor TraceShot(out vector HitLocation, out vector HitNormal, if (bbP != None) { PureRef = bbP.zzUTPure; - Ping = bbP.PingAverage; - - if (Ping > WSettingsRepl.PingCompensationMax) - Ping = WSettingsRepl.PingCompensationMax; + EffectivePing = IGPlus_GetHitscanRewindMs(PawnOwner); + Ping = int(EffectivePing); } bWeaponShock = (PawnOwner.Weapon != none && PawnOwner.Weapon.IsA('ShockRifle')); @@ -1095,12 +1424,32 @@ simulated function Actor TraceShot(out vector HitLocation, out vector HitNormal, if (WSettingsRepl.bEnablePingCompensation && bbP != none) { PureRef.CompensateFor(Ping, PawnOwner); - Other = TraceShotInternal(HitLocation, HitNormal, EndTrace, StartTrace, PawnOwner, bWeaponShock, bSProjBlocks, true); + Other = TraceShotInternal( + HitLocation, + HitNormal, + EndTrace, + StartTrace, + PawnOwner, + Ping, + bWeaponShock, + bSProjBlocks, + true + ); PureRef.EndCompensation(); return Other; } - return TraceShotInternal(HitLocation, HitNormal, EndTrace, StartTrace, PawnOwner, bWeaponShock, bSProjBlocks, false); + return TraceShotInternal( + HitLocation, + HitNormal, + EndTrace, + StartTrace, + PawnOwner, + 0, + bWeaponShock, + bSProjBlocks, + false + ); } simulated function Actor TraceShotClient(out vector HitLocation, out vector HitNormal, vector EndTrace, vector StartTrace, Pawn PawnOwner) @@ -1111,9 +1460,19 @@ simulated function Actor TraceShotClient(out vector HitLocation, out vector HitN bSProjBlocks = WSettingsRepl.ShockProjectileBlockBullets; bWeaponShock = (PawnOwner.Weapon != none && PawnOwner.Weapon.IsA('ShockRifle')); - return TraceShotInternal(HitLocation, HitNormal, EndTrace, StartTrace, PawnOwner, bWeaponShock, bSProjBlocks, false); + return TraceShotInternal( + HitLocation, + HitNormal, + EndTrace, + StartTrace, + PawnOwner, + 0, + bWeaponShock, + bSProjBlocks, + false + ); } defaultproperties { -} \ No newline at end of file +} diff --git a/Classes/ST_ShockProj.uc b/Classes/ST_ShockProj.uc index 5f35648..38920b7 100644 --- a/Classes/ST_ShockProj.uc +++ b/Classes/ST_ShockProj.uc @@ -52,6 +52,7 @@ simulated function PostBeginPlay() { Health = WImp.WeaponSettings.ShockProjectileHealth; } } + Super.PostBeginPlay(); } diff --git a/Classes/ST_ShockRifle.uc b/Classes/ST_ShockRifle.uc index 04355fe..4fad87c 100644 --- a/Classes/ST_ShockRifle.uc +++ b/Classes/ST_ShockRifle.uc @@ -11,10 +11,12 @@ var float yMod; var vector CDO; var ST_ShockProj LocalDummy; +var vector PendingSmokeLocation; // Explicit client aim data (sent via ServerExplicitFire/AltFire) var vector ExplicitClientLoc; var rotator ExplicitClientRot; +var Mover ExplicitClientBaseMover; var bool bUseExplicitData; var bool bClientShownVisuals; @@ -90,9 +92,16 @@ function bool IsPositionReasonable(vector ClientLoc) } // Called by ClientFire. Sends exact Client data to server. -function ServerExplicitFire(vector ClientLoc, rotator ClientRot, bool bClientVisuals, optional bool bIsSwitching) +function ServerExplicitFire( + vector ClientLoc, + rotator ClientRot, + bool bClientVisuals, + optional bool bIsSwitching +) { local PlayerPawn P; + local vector RawClientLoc; + local vector CheckLoc; P = PlayerPawn(Owner); if (P == None) @@ -100,6 +109,17 @@ function ServerExplicitFire(vector ClientLoc, rotator ClientRot, bool bClientVis if (!IsPingCompEnabled()) return; + + RawClientLoc = ClientLoc; + CheckLoc = RawClientLoc; + if (WImp != None) + CheckLoc = WImp.IGPlus_AdjustLocationToCurrentMoverFrame( + Pawn(Owner), + CheckLoc, + WImp.IGPlus_GetOneWayLatencyMs(Pawn(Owner)) + ); + else if (bbPlayer(Owner) != None) + CheckLoc.Z += bbPlayer(Owner).GetMoverFireZOffset(); // Handle Switching Fire (High Priority) if ( (AmmoType != None) && (AmmoType.AmmoAmount > 0) && (bIsSwitching || (P.PendingWeapon != None && P.PendingWeapon != self) || P.Weapon != self) ) @@ -112,12 +132,14 @@ function ServerExplicitFire(vector ClientLoc, rotator ClientRot, bool bClientVis LastServerFireTime = Level.TimeSeconds; // Position validation - if (bbPlayer(Owner) != None) - ClientLoc.Z += bbPlayer(Owner).GetMoverFireZOffset(); - if (IsPositionReasonable(ClientLoc)) - ExplicitClientLoc = ClientLoc; - else + if (IsPositionReasonable(CheckLoc)) { + ExplicitClientLoc = RawClientLoc; + ExplicitClientBaseMover = Mover(Owner.Base); + } else { ExplicitClientLoc = Owner.Location; + // Fallback already uses server-current position; skip mover re-adjust later. + ExplicitClientBaseMover = none; + } ExplicitClientRot = ClientRot; bUseExplicitData = true; @@ -149,13 +171,14 @@ function ServerExplicitFire(vector ClientLoc, rotator ClientRot, bool bClientVis if (Level.TimeSeconds - LastServerFireTime < FIRE_RATE_LIMIT) return; - // Position validation - use server position if client position is unreasonable - if (bbPlayer(Owner) != None) - ClientLoc.Z += bbPlayer(Owner).GetMoverFireZOffset(); - if (IsPositionReasonable(ClientLoc)) - ExplicitClientLoc = ClientLoc; - else - ExplicitClientLoc = Owner.Location; + // Position validation - use server position if client position is unreasonable + if (IsPositionReasonable(CheckLoc)) { + ExplicitClientLoc = RawClientLoc; + ExplicitClientBaseMover = Mover(Owner.Base); + } else { + ExplicitClientLoc = Owner.Location; + ExplicitClientBaseMover = none; + } ExplicitClientRot = ClientRot; bUseExplicitData = true; @@ -185,9 +208,16 @@ function ServerExplicitFire(vector ClientLoc, rotator ClientRot, bool bClientVis bClientShownVisuals = false; } -function ServerExplicitAltFire(vector ClientLoc, rotator ClientRot, bool bClientVisuals, optional bool bIsSwitching) +function ServerExplicitAltFire( + vector ClientLoc, + rotator ClientRot, + bool bClientVisuals, + optional bool bIsSwitching +) { local PlayerPawn P; + local vector RawClientLoc; + local vector CheckLoc; P = PlayerPawn(Owner); if (P == None) @@ -196,17 +226,29 @@ function ServerExplicitAltFire(vector ClientLoc, rotator ClientRot, bool bClient if (!IsPingCompEnabled()) return; + RawClientLoc = ClientLoc; + CheckLoc = RawClientLoc; + if (WImp != None) + CheckLoc = WImp.IGPlus_AdjustLocationToCurrentMoverFrame( + Pawn(Owner), + CheckLoc, + WImp.IGPlus_GetOneWayLatencyMs(Pawn(Owner)) + ); + else if (bbPlayer(Owner) != None) + CheckLoc.Z += bbPlayer(Owner).GetMoverFireZOffset(); + // Handle Switching Fire (High Priority) if ( (AmmoType != None) && (AmmoType.AmmoAmount > 0) && (bIsSwitching || (P.PendingWeapon != None && P.PendingWeapon != self) || P.Weapon != self) ) { AmmoType.UseAmmo(1); - if (bbPlayer(Owner) != None) - ClientLoc.Z += bbPlayer(Owner).GetMoverFireZOffset(); - if (IsPositionReasonable(ClientLoc)) - ExplicitClientLoc = ClientLoc; - else + if (IsPositionReasonable(CheckLoc)) { + ExplicitClientLoc = RawClientLoc; + ExplicitClientBaseMover = Mover(Owner.Base); + } else { ExplicitClientLoc = Owner.Location; + ExplicitClientBaseMover = none; + } ExplicitClientRot = ClientRot; bUseExplicitData = true; @@ -233,13 +275,14 @@ function ServerExplicitAltFire(vector ClientLoc, rotator ClientRot, bool bClient if (bChangeWeapon || IsInState('DownWeapon')) return; - // Position validation - use server position if client position is unreasonable - if (bbPlayer(Owner) != None) - ClientLoc.Z += bbPlayer(Owner).GetMoverFireZOffset(); - if (IsPositionReasonable(ClientLoc)) - ExplicitClientLoc = ClientLoc; - else - ExplicitClientLoc = Owner.Location; + // Position validation - use server position if client position is unreasonable + if (IsPositionReasonable(CheckLoc)) { + ExplicitClientLoc = RawClientLoc; + ExplicitClientBaseMover = Mover(Owner.Base); + } else { + ExplicitClientLoc = Owner.Location; + ExplicitClientBaseMover = none; + } ExplicitClientRot = ClientRot; bUseExplicitData = true; @@ -271,12 +314,27 @@ function ServerExplicitAltFire(vector ClientLoc, rotator ClientRot, bool bClient function Projectile ExplicitProjectileFire(class ProjClass, float ProjSpeed, bool bWarn) { local Vector Start, X,Y,Z; + local Pawn PawnOwner; + local vector AdjustedClientLoc; - Owner.MakeNoise(Pawn(Owner).SoundDampening); + PawnOwner = Pawn(Owner); + Owner.MakeNoise(PawnOwner.SoundDampening); - // Use Explicit Client Rotation - GetAxes(ExplicitClientRot,X,Y,Z); - Start = ExplicitClientLoc + CalcDrawOffset() + FireOffset.X * X + FireOffset.Y * Y + FireOffset.Z * Z; + // Use Explicit Client Rotation + GetAxes(ExplicitClientRot,X,Y,Z); + AdjustedClientLoc = ExplicitClientLoc; + // Apply mover-frame conversion only for explicit data captured while riding a mover. + if (WImp != None && IsPingCompEnabled() && ExplicitClientBaseMover != none) + AdjustedClientLoc = WImp.IGPlus_AdjustLocationToCurrentMoverFrame( + PawnOwner, + AdjustedClientLoc, + WImp.IGPlus_GetOneWayLatencyMs(PawnOwner), + ExplicitClientBaseMover + ); + + // For projectile spawn, use current mover frame to avoid immediate collision + // with the shooter's own lift on server. + Start = AdjustedClientLoc + CalcDrawOffset() + FireOffset.X * X + FireOffset.Y * Y + FireOffset.Z * Z; AdjustedAim = ExplicitClientRot; @@ -538,6 +596,7 @@ function TraceFire(float Accuracy) { local Pawn PawnOwner; local rotator AimRot; local vector AimLoc; + local vector SmokeLocation; PawnOwner = Pawn(Owner); @@ -548,6 +607,9 @@ function TraceFire(float Accuracy) { { AimRot = ExplicitClientRot; AimLoc = ExplicitClientLoc; + // ExplicitClientLoc is captured at client fire time. For hitscan beam traces, + // keep that origin as-is; shifting it to current mover frame skews vertical + // aim while riding movers. } else { @@ -557,6 +619,7 @@ function TraceFire(float Accuracy) { GetAxes(AimRot,X,Y,Z); StartTrace = AimLoc + CalcDrawOffset() + FireOffset.Y * Y + FireOffset.Z * Z; + SmokeLocation = AimLoc + CalcDrawOffset() + (FireOffset.X + 20) * X + FireOffset.Y * Y + FireOffset.Z * Z; EndTrace = StartTrace + (Accuracy * (FRand() - 0.5 )* Y * 1000) + (Accuracy * (FRand() - 0.5 ) * Z * 1000); @@ -584,6 +647,7 @@ function TraceFire(float Accuracy) { else Other = PawnOwner.TraceShot(HitLocation,HitNormal,EndTrace,StartTrace); + PendingSmokeLocation = SmokeLocation; ProcessTraceHit(Other, HitLocation, HitNormal, vector(AdjustedAim), Y, Z); } @@ -610,8 +674,12 @@ function ProcessTraceHit(Actor Other, Vector HitLocation, Vector HitNormal, Vect if (PlayerOwner != None) PlayerOwner.ClientInstantFlash(-0.4, vect(450, 190, 650)); + if (PendingSmokeLocation == vect(0,0,0)) + PendingSmokeLocation = Owner.Location + CalcDrawOffset() + (FireOffset.X + 20) * X + FireOffset.Y * Y + FireOffset.Z * Z; + // Server-side beam spawning - SpawnEffect(HitLocation, Owner.Location + CalcDrawOffset() + (FireOffset.X + 20) * X + FireOffset.Y * Y + FireOffset.Z * Z); + SpawnEffect(HitLocation, PendingSmokeLocation); + PendingSmokeLocation = vect(0,0,0); if (IsPingCompEnabled() && bbP != None && bbP.ClientWeaponSettingsData.bShockProjectileUseClientSideAnimations == false) { Dummy = ST_ProjectileDummy(Other); diff --git a/Classes/ST_SniperRifle.uc b/Classes/ST_SniperRifle.uc index 986b579..54de5d0 100644 --- a/Classes/ST_SniperRifle.uc +++ b/Classes/ST_SniperRifle.uc @@ -154,14 +154,18 @@ function TraceFire(float Accuracy) { local vector HitLocation, HitNormal, StartTrace, EndTrace, X,Y,Z; local actor Other; local Pawn PawnOwner; + local float RewindMs; PawnOwner = Pawn(Owner); Owner.MakeNoise(PawnOwner.SoundDampening); GetAxes(PawnOwner.ViewRotation,X,Y,Z); StartTrace = Owner.Location + PawnOwner.Eyeheight * vect(0,0,1); - if (bbPlayer(Owner) != None && WImp.WeaponSettings.bEnablePingCompensation) - StartTrace.Z -= bbPlayer(Owner).GetMoverFireZOffset(true); + if (WImp != None && WImp.WeaponSettings.bEnablePingCompensation) + { + RewindMs = WImp.IGPlus_GetHitscanRewindMs(PawnOwner); + StartTrace = WImp.IGPlus_AdjustLocationToHistoricalMoverFrame(PawnOwner, StartTrace, RewindMs); + } AdjustedAim = PawnOwner.AdjustAim(1000000, StartTrace, 2*AimError, False, False); X = vector(AdjustedAim); EndTrace = StartTrace + 100000 * X; diff --git a/Classes/ST_UT_FlakCannon.uc b/Classes/ST_UT_FlakCannon.uc index 4da045d..d79b224 100644 --- a/Classes/ST_UT_FlakCannon.uc +++ b/Classes/ST_UT_FlakCannon.uc @@ -633,8 +633,6 @@ simulated function SpawnClientChunk(class ChunkClass, vector Pos, ro C.ChunkIndex = Index; C.bCollideWorld = true; C.SetCollision(false, false, false); - if (Pawn(Owner) != None && Pawn(Owner).PlayerReplicationInfo != None) - C.LifeSpan = Pawn(Owner).PlayerReplicationInfo.Ping * 0.00125 * Level.TimeDilation; } simulated function SpawnClientSideChunks() { diff --git a/Classes/ServerSettings.uc b/Classes/ServerSettings.uc index 56426e3..9124cc3 100644 --- a/Classes/ServerSettings.uc +++ b/Classes/ServerSettings.uc @@ -78,6 +78,9 @@ var config bool bEnablePingCompensatedSpawn; var config bool bEnableJitterBounding; var config float LooseCheckCorrectionFactor; var config float LooseCheckCorrectionFactorOnMover; +var config bool bEnableSnapshotInterpolation; +var config float SnapshotInterpSendHz; +var config float SnapshotInterpRewindMs; var config bool bEnableWarpFix; var config bool bEnableCarcassCollision; var config bool ShowTouchedPackage; @@ -145,6 +148,9 @@ function DumpServerSettings(PlayerPawn P) { DumpSetting(P, "bEnableJitterBounding"); DumpSetting(P, "LooseCheckCorrectionFactor"); DumpSetting(P, "LooseCheckCorrectionFactorOnMover"); + DumpSetting(P, "bEnableSnapshotInterpolation"); + DumpSetting(P, "SnapshotInterpSendHz"); + DumpSetting(P, "SnapshotInterpRewindMs"); DumpSetting(P, "bEnableWarpFix"); DumpSetting(P, "ShowTouchedPackage"); DumpSetting(P, "HitFeedbackMode"); @@ -216,10 +222,13 @@ defaultproperties bEnableJitterBounding=False LooseCheckCorrectionFactor=1.0 LooseCheckCorrectionFactorOnMover=1.0 + bEnableSnapshotInterpolation=False + SnapshotInterpSendHz=30.0 + SnapshotInterpRewindMs=66.0 bEnableWarpFix=True bEnableCarcassCollision=True HitFeedbackMode=HFM_Always bEnableDamageDebugMode=False bEnableDamageDebugConsoleMessages=False bEnableHitboxDebugMode=False -} \ No newline at end of file +} diff --git a/Classes/UTPlusDummy.uc b/Classes/UTPlusDummy.uc index b08a593..5648ac3 100644 --- a/Classes/UTPlusDummy.uc +++ b/Classes/UTPlusDummy.uc @@ -218,6 +218,11 @@ function TakeDamage( function CompSwap(UTPlusSnapshot SnapA, UTPlusSnapshot SnapB, float Alpha, float TargetTimeStamp) { local vector TargetLoc; local vector TargetVel; + local bbPlayer BP; + local ST_MoverDummy MD; + local Mover BaseMover; + local vector HistoricalMoverLoc; + local float SnapshotMoverZ; if (Actual == None || Actual.bDeleteMe || SnapA == None || bCompActive) return; @@ -237,8 +242,29 @@ function CompSwap(UTPlusSnapshot SnapA, UTPlusSnapshot SnapB, float Alpha, float TargetVel = SnapA.Vel; } - if (SnapA.bBasedOnMover && Actual.Base == SnapA.BaseMover) - TargetLoc.Z += Actual.Base.Location.Z - SnapA.MoverZ; + if (SnapA.bBasedOnMover && Actual.Base == SnapA.BaseMover) { + BaseMover = Mover(SnapA.BaseMover); + if (BaseMover != None) { + BP = bbPlayer(Actual); + if (BP != None && BP.zzUTPure != None) + MD = BP.zzUTPure.FindMoverDummy(BaseMover); + + // If sub-tick interpolation is active and both snapshots are from the + // same mover base, interpolate the mover snapshot Z consistently. + SnapshotMoverZ = SnapA.MoverZ; + if (SnapB != None && SnapB.bBasedOnMover && SnapB.BaseMover == SnapA.BaseMover) + SnapshotMoverZ = SnapA.MoverZ + (SnapB.MoverZ - SnapA.MoverZ) * FClamp(Alpha, 0.0, 1.0); + + // Use historical mover position at the same rewind timestamp. + // Fallback to live mover only when no history actor is available. + if (MD != None) + HistoricalMoverLoc = MD.GetHistoricalLocation(TargetTimeStamp); + else + HistoricalMoverLoc = BaseMover.Location; + + TargetLoc.Z += HistoricalMoverLoc.Z - SnapshotMoverZ; + } + } ActualWasColliding = Actual.bCollideActors; ActualWasBlockingActors = Actual.bBlockActors; @@ -321,4 +347,4 @@ simulated function bool AdjustHitLocation(out vector HitLocation, vector TraceDi defaultproperties { bHidden=True RemoteRole=ROLE_None; -} \ No newline at end of file +} diff --git a/Classes/UTPure.uc b/Classes/UTPure.uc index df0e607..078d13c 100644 --- a/Classes/UTPure.uc +++ b/Classes/UTPure.uc @@ -530,15 +530,10 @@ function RegisterProjectile(Projectile P) { function CompensateFor(int Ping, optional Pawn Instigator) { local UTPlusDummy D; local ST_ProjectileDummy PD; - local ST_MoverDummy MD; local Pawn DActual; bCompensationIsActive = true; - for (MD = MoverDummies; MD != none; MD = MD.Next) { - MD.CompStart(Ping, Instigator); - } - for (D = CompDummies; D != none; D = D.Next) { DActual = D.Actual; @@ -989,6 +984,131 @@ function bool MutatorBroadcastLocalizedMessage( Actor Sender, Pawn Receiver, out } // MutatorBroadcastLocalizedMessage +function string IGPlus_NormalizeServerSettingKey(string Key) { + if (Key ~= "bAutoPause") return "bAutoPause"; + if (Key ~= "PauseTotalTime") return "PauseTotalTime"; + if (Key ~= "PauseTime") return "PauseTime"; + if (Key ~= "bForceDemo") return "bForceDemo"; + if (Key ~= "bRestrictTrading") return "bRestrictTrading"; + if (Key ~= "MaxTradeTimeMargin") return "MaxTradeTimeMargin"; + if (Key ~= "TradePingMargin") return "TradePingMargin"; + if (Key ~= "KillCamDelay") return "KillCamDelay"; + if (Key ~= "KillCamDuration") return "KillCamDuration"; + if (Key ~= "bJumpingPreservesMomentum") return "bJumpingPreservesMomentum"; + if (Key ~= "bOldLandingMomentum") return "bOldLandingMomentum"; + if (Key ~= "bEnableSingleButtonDodge") return "bEnableSingleButtonDodge"; + if (Key ~= "bUseFlipAnimation") return "bUseFlipAnimation"; + if (Key ~= "bEnableWallDodging") return "bEnableWallDodging"; + if (Key ~= "bDodgePreserveZMomentum") return "bDodgePreserveZMomentum"; + if (Key ~= "MaxMultiDodges") return "MaxMultiDodges"; + if (Key ~= "BrightskinMode") return "BrightskinMode"; + if (Key ~= "PlayerScale") return "PlayerScale"; + if (Key ~= "bAlwaysRenderFlagCarrier") return "bAlwaysRenderFlagCarrier"; + if (Key ~= "bAlwaysRenderDroppedFlags") return "bAlwaysRenderDroppedFlags"; + if (Key ~= "MaxPosError") return "MaxPosError"; + if (Key ~= "MaxHitError") return "MaxHitError"; + if (Key ~= "MaxJitterTime") return "MaxJitterTime"; + if (Key ~= "WarpFixDelay") return "WarpFixDelay"; + if (Key ~= "FireTimeout") return "FireTimeout"; + if (Key ~= "MinNetUpdateRate") return "MinNetUpdateRate"; + if (Key ~= "MaxNetUpdateRate") return "MaxNetUpdateRate"; + if (Key ~= "bEnableInputReplication") return "bEnableInputReplication"; + if (Key ~= "bEnableServerExtrapolation") return "bEnableServerExtrapolation"; + if (Key ~= "bEnableServerPacketReordering") return "bEnableServerPacketReordering"; + if (Key ~= "bEnableLoosePositionCheck") return "bEnableLoosePositionCheck"; + if (Key ~= "bPlayersAlwaysRelevant") return "bPlayersAlwaysRelevant"; + if (Key ~= "bEnablePingCompensatedSpawn") return "bEnablePingCompensatedSpawn"; + if (Key ~= "bEnableJitterBounding") return "bEnableJitterBounding"; + if (Key ~= "LooseCheckCorrectionFactor") return "LooseCheckCorrectionFactor"; + if (Key ~= "LooseCheckCorrectionFactorOnMover") return "LooseCheckCorrectionFactorOnMover"; + if (Key ~= "bEnableSnapshotInterpolation") return "bEnableSnapshotInterpolation"; + if (Key ~= "SnapshotInterpSendHz") return "SnapshotInterpSendHz"; + if (Key ~= "SnapshotInterpRewindMs") return "SnapshotInterpRewindMs"; + if (Key ~= "bEnableWarpFix") return "bEnableWarpFix"; + if (Key ~= "ShowTouchedPackage") return "ShowTouchedPackage"; + if (Key ~= "HitFeedbackMode") return "HitFeedbackMode"; + if (Key ~= "bEnableDamageDebugMode") return "bEnableDamageDebugMode"; + if (Key ~= "bEnableDamageDebugConsoleMessages") return "bEnableDamageDebugConsoleMessages"; + if (Key ~= "bEnableHitboxDebugMode") return "bEnableHitboxDebugMode"; + return ""; +} + +function IGPlus_ApplyServerSettingsToPlayers() { + local Pawn P; + local bbPlayer BP; + local bool bOldSnap; + + for (P = Level.PawnList; P != None; P = P.NextPawn) { + BP = bbPlayer(P); + if (BP == None) + continue; + + BP.TeleRadius = Settings.TeleRadius; + BP.BrightskinMode = Settings.BrightskinMode; + BP.KillCamDelay = FMax(0.0, Settings.KillCamDelay); + BP.KillCamDuration = Settings.KillCamDuration; + BP.bJumpingPreservesMomentum = Settings.bJumpingPreservesMomentum; + BP.bOldLandingMomentum = Settings.bOldLandingMomentum; + BP.bEnableSingleButtonDodge = Settings.bEnableSingleButtonDodge; + BP.bUseFlipAnimation = Settings.bUseFlipAnimation; + BP.bCanWallDodge = Settings.bEnableWallDodging; + BP.bDodgePreserveZMomentum = Settings.bDodgePreserveZMomentum; + BP.bAlwaysRelevant = Settings.bPlayersAlwaysRelevant || (BP.PlayerReplicationInfo != none && BP.PlayerReplicationInfo.HasFlag != none); + BP.IGPlus_EnableWarpFix = Settings.bEnableWarpFix; + BP.IGPlus_WarpFixDelay = Settings.WarpFixDelay; + BP.IGPlus_AlwaysRenderFlagCarrier = Settings.bAlwaysRenderFlagCarrier; + BP.IGPlus_AlwaysRenderDroppedFlags = Settings.bAlwaysRenderDroppedFlags; + BP.IGPlus_UseFastWeaponSwitch = Settings.bUseFastWeaponSwitch; + BP.IGPlus_EnableInputReplication = Settings.bEnableInputReplication; + bOldSnap = BP.IGPlus_EnableSnapshotInterpolation; + BP.IGPlus_EnableSnapshotInterpolation = Settings.bEnableSnapshotInterpolation; + if (bOldSnap != BP.IGPlus_EnableSnapshotInterpolation) { + BP.IGPlus_SnapInterp_ServerNextTime = 0; + BP.IGPlus_SnapInterp_ServerHasLast = false; + } + BP.bEnableDamageDebugMode = Settings.bEnableDamageDebugMode; + BP.bEnableDamageDebugConsoleMessages = Settings.bEnableDamageDebugConsoleMessages; + } +} + +function IGPlus_PrintServerSetting(PlayerPawn Sender, string Key) { + local string NormalKey; + + if (Settings == None) { + Sender.ClientMessage("Server settings unavailable"); + return; + } + + NormalKey = IGPlus_NormalizeServerSettingKey(Key); + if (NormalKey == "") { + Sender.ClientMessage("Unknown server setting:"@Key); + return; + } + + Sender.ClientMessage(NormalKey$"="$Settings.GetPropertyText(NormalKey)); +} + +function IGPlus_SetServerSetting(PlayerPawn Sender, string Key, string Value, optional bool bSendFeedback) { + local string NormalKey; + + if (Settings == None) { + Sender.ClientMessage("Server settings unavailable"); + return; + } + + NormalKey = IGPlus_NormalizeServerSettingKey(Key); + if (NormalKey == "") { + Sender.ClientMessage("Unknown server setting:"@Key); + return; + } + + Settings.SetPropertyText(NormalKey, Value); + Settings.SaveConfig(); + IGPlus_ApplyServerSettingsToPlayers(); + if (bSendFeedback) + Sender.ClientMessage(NormalKey$"="$Settings.GetPropertyText(NormalKey)); +} + // ================================================================================== // Mutate - Accepts commands from the users // ================================================================================== @@ -1000,6 +1120,7 @@ function Mutate(string MutateString, PlayerPawn Sender) local int zzi; local bool zzb; local string zzS; + local string zzS2; local PlayerReplicationInfo zzPRI; if (MutateString ~= "CheatInfo") @@ -1045,6 +1166,9 @@ function Mutate(string MutateString, PlayerPawn Sender) } if (CHSpectator(Sender) != None) Sender.ClientMessage("As spectator, you may need to add 'mutate pure' + command (mutate pureshowtickrate)"); + Sender.ClientMessage("Admin setting commands:"); + Sender.ClientMessage("- mutate IGPlusServerGet "); + Sender.ClientMessage("- mutate IGPlusServerSet "); } else if (MutateString ~= "PingCompSettings") { @@ -1070,6 +1194,83 @@ function Mutate(string MutateString, PlayerPawn Sender) else if (Sender.IsA('bbCHSpectator')) bbCHSpectator(Sender).IGPlusMenu(); } + else if (MutateString ~= "IGPlusServerGet") + { + Sender.ClientMessage("Usage: mutate IGPlusServerGet "); + } + else if (Left(MutateString, 16) ~= "IGPlusServerGet ") + { + if (Sender.bAdmin == false) + { + Sender.ClientMessage(BADminText); + } + else + { + zzS = class'StringUtils'.static.Trim(Mid(MutateString, 16)); + if (zzS == "") + Sender.ClientMessage("Usage: mutate IGPlusServerGet "); + else + IGPlus_PrintServerSetting(Sender, zzS); + } + } + else if (Left(MutateString, 22) ~= "IGPlusServerSetSilent ") + { + if (Sender.bAdmin == false) + { + Sender.ClientMessage(BADminText); + } + else + { + zzS = class'StringUtils'.static.Trim(Mid(MutateString, 22)); + zzi = InStr(zzS, " "); + if (zzi < 0) + { + Sender.ClientMessage("Usage: mutate IGPlusServerSetSilent "); + } + else + { + zzS2 = class'StringUtils'.static.Trim(Left(zzS, zzi)); + zzS = class'StringUtils'.static.Trim(Mid(zzS, zzi + 1)); + if (zzS2 == "" || zzS == "") + Sender.ClientMessage("Usage: mutate IGPlusServerSetSilent "); + else + IGPlus_SetServerSetting(Sender, zzS2, zzS, false); + } + } + } + else if (Left(MutateString, 16) ~= "IGPlusServerSet ") + { + if (Sender.bAdmin == false) + { + Sender.ClientMessage(BADminText); + } + else + { + zzS = class'StringUtils'.static.Trim(Mid(MutateString, 16)); + zzi = InStr(zzS, " "); + if (zzi < 0) + { + Sender.ClientMessage("Usage: mutate IGPlusServerSet "); + } + else + { + zzS2 = class'StringUtils'.static.Trim(Left(zzS, zzi)); + zzS = class'StringUtils'.static.Trim(Mid(zzS, zzi + 1)); + if (zzS2 == "" || zzS == "") + Sender.ClientMessage("Usage: mutate IGPlusServerSet "); + else + IGPlus_SetServerSetting(Sender, zzS2, zzS, true); + } + } + } + else if (MutateString ~= "IGPlusServerSetSilent") + { + Sender.ClientMessage("Usage: mutate IGPlusServerSetSilent "); + } + else if (MutateString ~= "IGPlusServerSet") + { + Sender.ClientMessage("Usage: mutate IGPlusServerSet "); + } else if (MutateString ~= "EnablePure") { if (Sender.bAdmin) @@ -1491,4 +1692,4 @@ defaultproperties { BADminText="Not allowed - Log in as admin!" bAlwaysTick=True -} \ No newline at end of file +} diff --git a/Classes/bbCHSpectator.uc b/Classes/bbCHSpectator.uc index ee1b956..e1371dc 100644 --- a/Classes/bbCHSpectator.uc +++ b/Classes/bbCHSpectator.uc @@ -42,6 +42,10 @@ var Pawn FlagCarrierToFollow; var bool IGPlus_TryOpenSettingsMenu; var IGPlus_SettingsDialog IGPlus_SettingsMenu; +var Object IGPlus_ServerSettingsHelper; +var ServerSettings IGPlus_ServerSettingsMenuData; +var bool IGPlus_ServerSettingsMenuLoaded; +var bool IGPlus_ServerSettingsMenuCanEdit; replication { @@ -53,7 +57,14 @@ replication // Server->Client reliable if ( Role == ROLE_Authority ) - IGPlusMenu, xxSetTimes; //, xxClientActivateMover; + IGPlusMenu, + xxSetTimes, + IGPlus_ServerSettingsInit, + IGPlus_ServerSettingsSet, + IGPlus_ServerSettingsDone; //, xxClientActivateMover; + + reliable if (Role < ROLE_Authority) + IGPlus_ServerRequestSettings; reliable if ( (!bDemoRecording || (bClientDemoRecording && bClientDemoNetFunc) || (Level.NetMode==NM_Standalone)) && Role == ROLE_Authority ) ReceiveWeaponEffect; @@ -970,6 +981,104 @@ function IGPlus_OpenSettingsMenu() { IGPlus_SettingsMenu.Load(); } +simulated function ServerSettings IGPlus_GetServerSettingsObject() { + if (IGPlus_ServerSettingsMenuData != none) + return IGPlus_ServerSettingsMenuData; + + IGPlus_ServerSettingsHelper = new(none, 'InstaGibPlus') class'Object'; + IGPlus_ServerSettingsMenuData = new(IGPlus_ServerSettingsHelper, 'IGPlus_ServerSettingsMenu') class'ServerSettings'; + return IGPlus_ServerSettingsMenuData; +} + +function IGPlus_ServerSettingsInit() { + IGPlus_ServerSettingsMenuLoaded = false; + IGPlus_ServerSettingsMenuCanEdit = false; + IGPlus_ServerSettingsMenuData = none; +} + +function IGPlus_ServerSettingsSet(string Key, string Value) { + local ServerSettings S; + + S = IGPlus_GetServerSettingsObject(); + if (S == none) + return; + + S.SetPropertyText(Key, Value); +} + +function IGPlus_ServerSettingsDone(bool bCanEdit) { + IGPlus_ServerSettingsMenuLoaded = true; + IGPlus_ServerSettingsMenuCanEdit = bCanEdit; +} + +function IGPlus_ServerSendSetting(string Key) { + if (zzUTPure == none || zzUTPure.Settings == none) + return; + + IGPlus_ServerSettingsSet(Key, zzUTPure.Settings.GetPropertyText(Key)); +} + +function IGPlus_ServerRequestSettings() { + if (bAdmin == false || zzUTPure == none || zzUTPure.Settings == none) { + IGPlus_ServerSettingsInit(); + IGPlus_ServerSettingsDone(false); + return; + } + + IGPlus_ServerSettingsInit(); + + IGPlus_ServerSendSetting("bAutoPause"); + IGPlus_ServerSendSetting("PauseTotalTime"); + IGPlus_ServerSendSetting("PauseTime"); + IGPlus_ServerSendSetting("bForceDemo"); + IGPlus_ServerSendSetting("bRestrictTrading"); + IGPlus_ServerSendSetting("MaxTradeTimeMargin"); + IGPlus_ServerSendSetting("TradePingMargin"); + IGPlus_ServerSendSetting("KillCamDelay"); + IGPlus_ServerSendSetting("KillCamDuration"); + + IGPlus_ServerSendSetting("bJumpingPreservesMomentum"); + IGPlus_ServerSendSetting("bOldLandingMomentum"); + IGPlus_ServerSendSetting("bEnableSingleButtonDodge"); + IGPlus_ServerSendSetting("bUseFlipAnimation"); + IGPlus_ServerSendSetting("bEnableWallDodging"); + IGPlus_ServerSendSetting("bDodgePreserveZMomentum"); + IGPlus_ServerSendSetting("MaxMultiDodges"); + IGPlus_ServerSendSetting("BrightskinMode"); + IGPlus_ServerSendSetting("PlayerScale"); + IGPlus_ServerSendSetting("bAlwaysRenderFlagCarrier"); + IGPlus_ServerSendSetting("bAlwaysRenderDroppedFlags"); + + IGPlus_ServerSendSetting("MaxPosError"); + IGPlus_ServerSendSetting("MaxHitError"); + IGPlus_ServerSendSetting("MaxJitterTime"); + IGPlus_ServerSendSetting("WarpFixDelay"); + IGPlus_ServerSendSetting("FireTimeout"); + IGPlus_ServerSendSetting("MinNetUpdateRate"); + IGPlus_ServerSendSetting("MaxNetUpdateRate"); + IGPlus_ServerSendSetting("bEnableInputReplication"); + IGPlus_ServerSendSetting("bEnableServerExtrapolation"); + IGPlus_ServerSendSetting("bEnableServerPacketReordering"); + IGPlus_ServerSendSetting("bEnableLoosePositionCheck"); + IGPlus_ServerSendSetting("bPlayersAlwaysRelevant"); + IGPlus_ServerSendSetting("bEnablePingCompensatedSpawn"); + IGPlus_ServerSendSetting("bEnableJitterBounding"); + IGPlus_ServerSendSetting("LooseCheckCorrectionFactor"); + IGPlus_ServerSendSetting("LooseCheckCorrectionFactorOnMover"); + IGPlus_ServerSendSetting("bEnableSnapshotInterpolation"); + IGPlus_ServerSendSetting("SnapshotInterpSendHz"); + IGPlus_ServerSendSetting("SnapshotInterpRewindMs"); + IGPlus_ServerSendSetting("bEnableWarpFix"); + + IGPlus_ServerSendSetting("ShowTouchedPackage"); + IGPlus_ServerSendSetting("HitFeedbackMode"); + IGPlus_ServerSendSetting("bEnableDamageDebugMode"); + IGPlus_ServerSendSetting("bEnableDamageDebugConsoleMessages"); + IGPlus_ServerSendSetting("bEnableHitboxDebugMode"); + + IGPlus_ServerSettingsDone(true); +} + // Notification of other player respawning, play effects locally function IGPlus_NotifyPlayerRestart(vector Loc, rotator Dir, bbPlayer Other) { local UTTeleportEffect PTE; diff --git a/Classes/bbPlayer.uc b/Classes/bbPlayer.uc index 686366f..c7c249a 100644 --- a/Classes/bbPlayer.uc +++ b/Classes/bbPlayer.uc @@ -86,7 +86,6 @@ var float NextRealCAPTime; var decoration carriedFlag; var WeaponSettingsRepl WSettings; - // HUD stuff var Mutator zzHudMutes[50]; // Accepted Hud Mutators var Mutator zzWaitMutes[50]; // Hud Mutes waiting to be accepted @@ -296,6 +295,10 @@ var float IGPlus_ZoomToggle_SensitivityFactorY; var ReplicationInfo IGPlus_AdditionalReplicationInfo; var bool IGPlus_TryOpenSettingsMenu; var IGPlus_SettingsDialog IGPlus_SettingsMenu; +var Object IGPlus_ServerSettingsHelper; +var ServerSettings IGPlus_ServerSettingsMenuData; +var bool IGPlus_ServerSettingsMenuLoaded; +var bool IGPlus_ServerSettingsMenuCanEdit; struct IGPlus_WarpFix { var vector OldLocation; @@ -307,11 +310,29 @@ struct IGPlus_WarpFixClient { var float TimeStamp; }; +struct IGPlus_SnapData { + var vector Location; + var vector Velocity; + var rotator Rotation; + var name AnimSequence; + var byte AnimFrameByte; + var byte AnimIntentBits; + var byte AnimDodgeDir; + var byte AnimPhysics; + var vector MoverLocation; + var int Counter; + var bool bHardSnap; + var bool bBasedOnMover; + var Actor BaseMover; +}; + var bool IGPlus_EnableWarpFix; var bool IGPlus_WarpFixUpdate; var float IGPlus_WarpFixDelay; var IGPlus_WarpFix IGPlus_WarpFixData; var IGPlus_WarpFixClient IGPlus_WarpFixClientData; +var IGPlus_SnapData IGPlus_SnapReplicatedData; +var IGPlus_SnapData IGPlus_SnapReplicatedDataPrev; const IGPlus_LocationOffsetFix_DummyVel = vect(4.56,4.56,0.0); @@ -338,6 +359,59 @@ var IGPlus_CollisionDummy IGPlus_LocationOffsetFix_DummyList; var float LocalExtrapolationOwnPingFactor; var float LocalExtrapolationOtherPingFactor; +var IGPlus_InterpSnapshot IGPlus_SnapInterp_Data[16]; +var int IGPlus_SnapInterp_DataIndex; +var int IGPlus_SnapInterp_Count; +var float IGPlus_SnapInterp_InterpDelay; +var float IGPlus_SnapInterp_LastArrivalTime; +var float IGPlus_SnapInterp_MeanInterval; +var float IGPlus_SnapInterp_JitterEstimate; +var bool IGPlus_SnapInterp_NetDebug; +var float IGPlus_SnapInterp_NetDebugNextTime; +var int IGPlus_SnapInterp_LastCounter; +var byte IGPlus_SnapInterp_LastInterpMode; +var int IGPlus_SnapInterp_LastHintAgeMs; +var string IGPlus_SnapInterp_LastHintSource; +var float IGPlus_SnapInterp_ServerNextTime; +var vector IGPlus_SnapInterp_ServerLastLocation; +var float IGPlus_SnapInterp_ServerLastTime; +var bool IGPlus_SnapInterp_ServerHasLast; +var bool IGPlus_SnapInterp_EnabledLast; +var int IGPlus_SnapInterp_StatSnapPackets; +var int IGPlus_SnapInterp_StatInterp; +var int IGPlus_SnapInterp_StatExtrap; +var int IGPlus_SnapInterp_StatClamp; +var int IGPlus_SnapInterp_StatFail; +var int IGPlus_SnapInterp_StatHardSnap; +var int IGPlus_ClientSnapInterpDelayMs; +var int IGPlus_ClientSnapInterpDelaySampleCount; +var int IGPlus_ClientSnapInterpDelayMedianMs; +var int IGPlus_ClientSnapInterpDelaySpreadMs; +var float IGPlus_ServerSnapInterpDelayMsSmoothed; +var bool IGPlus_ServerSnapInterpDelayValid; +var bool IGPlus_ServerSnapInterpTrusted; +var float IGPlus_ServerSnapInterpLastAcceptedTime; +var float IGPlus_NextSnapInterpDelayReportTime; +var float IGPlus_NextSnapInterpDelayClientReportTime; +var int IGPlus_ServerSnapInterpDelayReportedMsEcho; +var int IGPlus_ServerSnapInterpDelayClampedMsEcho; +var int IGPlus_ServerSnapInterpDelaySmoothedMsEcho; +var bool IGPlus_ServerSnapInterpDelayValidEcho; +var bool IGPlus_ServerSnapInterpTrustedEcho; +var float IGPlus_ServerSnapInterpDelayEchoTime; +var float IGPlus_ServerSnapInterpLastAcceptedTimeEcho; +var bool IGPlus_SnapInterp_CheckActive; +var float IGPlus_SnapInterp_CheckDuration; +var float IGPlus_SnapInterp_CheckEndTime; +var string IGPlus_SnapInterp_CheckProfile; +var bbPlayer IGPlus_SnapInterp_CheckTarget; +var int IGPlus_SnapInterp_CheckBaseSnapPackets; +var int IGPlus_SnapInterp_CheckBaseInterp; +var int IGPlus_SnapInterp_CheckBaseExtrap; +var int IGPlus_SnapInterp_CheckBaseClamp; +var int IGPlus_SnapInterp_CheckBaseFail; +var int IGPlus_SnapInterp_CheckBaseHardSnap; + var bool IGPlus_AlwaysRenderFlagCarrier; var bool IGPlus_AlwaysRenderDroppedFlags; var bool IGPlus_InitFlagSprites; @@ -348,6 +422,7 @@ var bool IGPlus_UseFastWeaponSwitch; var bool IGPlus_DeferredWeaponSwitch; var bool IGPlus_EnableInputReplication; +var bool IGPlus_EnableSnapshotInterpolation; var bool IGPlus_PressedJumpSave; var IGPlus_SavedInputChain IGPlus_SavedInputChain; var IGPlus_DataBuffer IGPlus_InputReplicationBuffer; @@ -423,13 +498,23 @@ replication zzMinimumNetspeed, zzTrackFOV, zzWaitTime, - bEnableDamageDebugMode, - bEnableDamageDebugConsoleMessages; + IGPlus_ServerSnapInterpDelayReportedMsEcho, + IGPlus_ServerSnapInterpDelayClampedMsEcho, + IGPlus_ServerSnapInterpDelaySmoothedMsEcho, + IGPlus_ServerSnapInterpDelayValidEcho, + IGPlus_ServerSnapInterpTrustedEcho, + IGPlus_ServerSnapInterpDelayEchoTime, + IGPlus_ServerSnapInterpLastAcceptedTimeEcho, + bEnableDamageDebugMode, + bEnableDamageDebugConsoleMessages; unreliable if ( Role == ROLE_Authority ) DuckFractionRepl, IGPlus_AdditionalReplicationInfo, - IGPlus_WarpFixData; + IGPlus_WarpFixData, + IGPlus_EnableSnapshotInterpolation, + IGPlus_SnapReplicatedData, + IGPlus_SnapReplicatedDataPrev; unreliable if ( bDrawDebugData && RemoteRole == ROLE_AutonomousProxy ) clientForcedPosition, @@ -448,6 +533,9 @@ replication FakeCAPInterval, IGPlus_DamageEvent_ShowOnDeath, IGPlus_EnableDualButtonSwitch, + IGPlus_ClientSnapInterpDelayMs, + IGPlus_ClientSnapInterpDelaySampleCount, + IGPlus_ClientSnapInterpDelaySpreadMs, PingAverage, zzbDemoRecording, zzNetspeed, @@ -487,6 +575,9 @@ replication IGPlus_ForcedSettingsInit, IGPlus_ForcedSettingRegister, IGPlus_ForcedSettingsApply, + IGPlus_ServerSettingsInit, + IGPlus_ServerSettingsSet, + IGPlus_ServerSettingsDone, IGPlus_ClientReStart, IGPlus_NotifyPlayerRestart, ClientAddMomentum; @@ -519,6 +610,9 @@ replication IGPlus_ForcedSettingsOK, IGPlus_ForcedSettings_InitOK, PrintWeaponState, + IGPlus_ServerNetcodeHelp, + IGPlus_ServerSetNetcodeSetting, + IGPlus_ServerRequestSettings, ServerSetDodgeSettings, xxExplodeOther, xxNN_AltFire, @@ -679,6 +773,8 @@ simulated event Destroyed() { if (Role == ROLE_SimulatedProxy && IGPlus_LocationOffsetFix_CollisionDummy != none) { IGPlus_LocationOffsetFix_DestroyCollisionDummy(); } + if (Role == ROLE_SimulatedProxy) + IGPlus_SnapInterp_Reset(); if (bTraceInput && IGPlus_InputLogFile != none) IGPlus_InputLogFile.StopLog(); @@ -1212,6 +1308,7 @@ event Possess() IGPlus_AlwaysRenderDroppedFlags = zzUTPure.Settings.bAlwaysRenderDroppedFlags; IGPlus_UseFastWeaponSwitch = zzUTPure.Settings.bUseFastWeaponSwitch; IGPlus_EnableInputReplication = zzUTPure.Settings.bEnableInputReplication; + IGPlus_EnableSnapshotInterpolation = zzUTPure.Settings.bEnableSnapshotInterpolation; bEnableDamageDebugMode = zzUTPure.Settings.bEnableDamageDebugMode; bEnableDamageDebugConsoleMessages = zzUTPure.Settings.bEnableDamageDebugConsoleMessages; @@ -1992,7 +2089,8 @@ simulated function xxPureCAP(float TimeStamp, name newState, int MiscData, vecto { local Decoration Carried; local vector OldLoc; - local bool bOldCollWorld, bOldActCol, bOldBlockAct, bOldBlockPlayer; + local bool bOldCollWorld; + local bool bOldActCol, bOldBlockAct, bOldBlockPlayer; if (bDeleteMe) return; @@ -2484,7 +2582,12 @@ function ClearLastServerMoveParams() { function IGPlus_ProcessRemoteMovement() { if (IGPlus_DeferredWeaponSwitch) { - if (IGPlus_UseFastWeaponSwitch && PendingWeapon != None) + // Deferred fast switch is loose-check-only. If loose check is disabled, + // clear any stale deferred switch flag without applying it. + if (zzUTPure != None && + zzUTPure.Settings.bEnableLoosePositionCheck && + IGPlus_UseFastWeaponSwitch && + PendingWeapon != None) ChangedWeapon(); IGPlus_DeferredWeaponSwitch = false; } @@ -2751,7 +2854,8 @@ function IGPlus_ApplyServerMove(IGPlus_ServerMove SM) { } if (IGPlus_UseFastWeaponSwitch && PendingWeapon != None) { - if (zzUTPure.Settings.bEnableLoosePositionCheck) + // Defer only for loose-check path to avoid changing standard/input behavior. + if (zzUTPure != None && zzUTPure.Settings.bEnableLoosePositionCheck) IGPlus_DeferredWeaponSwitch = true; else ChangedWeapon(); @@ -2948,6 +3052,8 @@ function bool IGPlus_IsCAPNecessary() { local bool bCanTraceNewLoc; local bool bMovedToNewLoc; local bool bMoverLooseCheckContext; + local bool bAppliedQueuedMomentum; + local bool bPreserveServerVelocity; local Decoration Carried; local vector OldLoc; local float LooseCheckCorrectionFactor; @@ -2968,10 +3074,12 @@ function bool IGPlus_IsCAPNecessary() { debugClientLocError = ClientLocError; // Apply momentum that the client never got around to + bAppliedQueuedMomentum = false; while(LastAddVelocityAppliedIndex != LastAddVelocityIndex && AddVelocityCalls[LastAddVelocityAppliedIndex].TimeStamp < ServerTimeStamp) { IGPlus_ApplyMomentum(AddVelocityCalls[LastAddVelocityAppliedIndex].Momentum); AddVelocityCalls[LastAddVelocityAppliedIndex].Momentum = vect(0,0,0); zzbForceUpdate = true; + bAppliedQueuedMomentum = true; LastAddVelocityAppliedIndex = (LastAddVelocityAppliedIndex+1) & 0xF; } @@ -3040,12 +3148,15 @@ function bool IGPlus_IsCAPNecessary() { if (LooseCheckCorrectionFactor < 1.0) ClientLocAbs = Location + (ClientLocAbs - Location) * LooseCheckCorrectionFactor; + // Keep server-side knockback velocity authoritative for a short window. + bPreserveServerVelocity = bAppliedQueuedMomentum || (zzIgnoreUpdateUntil >= ServerTimeStamp); + bCanTraceNewLoc = FastTrace(ClientLocAbs); if (bCanTraceNewLoc) { clientForcedPosition = ClientLocAbs; zzLastClientErr = 0; bMovedToNewLoc = xxNewMoveSmooth(ClientLocAbs); - if (bMovedToNewLoc && ClientPhysics == Physics) + if (bMovedToNewLoc && ClientPhysics == Physics && !bPreserveServerVelocity) Velocity = ClientVel; } if (bCanTraceNewLoc == false) { @@ -3053,7 +3164,7 @@ function bool IGPlus_IsCAPNecessary() { OldLoc = Location; bCanTeleport = false; - if (SetLocation(ClientLocAbs) && ClientPhysics == Physics) + if (SetLocation(ClientLocAbs) && ClientPhysics == Physics && !bPreserveServerVelocity) Velocity = ClientVel; bCanTeleport = true; @@ -3412,9 +3523,6 @@ function ServerApplyInput(float RefTimeStamp, int NumBits, ReplBuffer B) { local float DeltaTime; local float ServerDeltaTime; local float LostTime; - local Mover PreReplayMover; - local ST_MoverDummy MD; - local vector MoverCompensation; if (Role < ROLE_Authority) { zzbLogoDone = True; @@ -3491,8 +3599,6 @@ function ServerApplyInput(float RefTimeStamp, int NumBits, ReplBuffer B) { if (LostTime > 0.001) SimMoveAutonomous(LostTime); - PreReplayMover = Mover(Base); - while(Old.Next != none) { PlayBackInput(Old, Old.Next); if (bTraceInput && IGPlus_InputLogFile != none) @@ -3500,19 +3606,6 @@ function ServerApplyInput(float RefTimeStamp, int NumBits, ReplBuffer B) { Old = Old.Next; } - // Compensate mover position desync: the server's mover is further along - // than where the client saw it when they jumped. Adjust the player's - // position to match the trajectory the client actually experienced. - if (PreReplayMover != None && Mover(Base) != PreReplayMover && PlayerReplicationInfo != None) { - MD = zzUTPure.FindMoverDummy(PreReplayMover); - if (MD != None) { - MoverCompensation = PreReplayMover.Location - - MD.GetHistoricalLocation(Level.TimeSeconds - PlayerReplicationInfo.Ping * 0.0005 * Level.TimeDilation); - if (VSize(MoverCompensation) > 0.1) - SetLocation(Location - MoverCompensation); - } - } - // clean up IGPlus_SavedInputChain.RemoveOutdatedNodes(Old.TimeStamp); @@ -3746,6 +3839,169 @@ simulated function actor NN_TraceShot(out vector HitLocation, out vector HitNorm return Other; } +function bool IGPlus_LineIntersectsCylinder( + vector Start, + vector End, + vector CylinderCenter, + float Radius, + float HalfHeight +) { + local vector ClosestPoint, LineDelta, ToCenter; + local float LineLengthSq, T, DistXYSq, DistZ; + + LineDelta = End - Start; + LineLengthSq = LineDelta dot LineDelta; + if (LineLengthSq < 0.000001) + return false; + + ToCenter = CylinderCenter - Start; + T = (ToCenter dot LineDelta) / LineLengthSq; + T = FClamp(T, 0.0, 1.0); + + ClosestPoint = Start + LineDelta * T; + + DistXYSq = + (ClosestPoint.X - CylinderCenter.X) * (ClosestPoint.X - CylinderCenter.X) + + (ClosestPoint.Y - CylinderCenter.Y) * (ClosestPoint.Y - CylinderCenter.Y); + DistZ = Abs(ClosestPoint.Z - CylinderCenter.Z); + + return (DistXYSq <= Radius * Radius && DistZ <= HalfHeight); +} + +function bool IGPlus_PointInsideCylinder( + vector P, + vector CylinderCenter, + float Radius, + float HalfHeight +) { + local float DistXYSq; + + DistXYSq = + (P.X - CylinderCenter.X) * (P.X - CylinderCenter.X) + + (P.Y - CylinderCenter.Y) * (P.Y - CylinderCenter.Y); + return (DistXYSq <= Radius * Radius && Abs(P.Z - CylinderCenter.Z) <= HalfHeight); +} + +function bool IGPlus_IsTraceBlockedByTargetMover( + UTPlusDummy TargetDummy, + vector StartTrace, + vector HitLocation +) { + local Mover BaseMover; + local vector HistoricalMoverLoc; + + if (TargetDummy == None || TargetDummy.Actual == None) + return false; + + BaseMover = Mover(TargetDummy.Actual.Base); + if (BaseMover == None) + return false; + + // Same-base shots should not be blocked by this protection. + if (Base == BaseMover) + return false; + + HistoricalMoverLoc = BaseMover.Location; + IGPlus_GetHistoricalMoverLocation(BaseMover, HistoricalMoverLoc); + + // Only guard "from below through mover" shots. + if (StartTrace.Z >= HistoricalMoverLoc.Z || HitLocation.Z <= HistoricalMoverLoc.Z) + return false; + + return IGPlus_LineIntersectsCylinder( + StartTrace, + HitLocation, + HistoricalMoverLoc, + BaseMover.CollisionRadius, + BaseMover.CollisionHeight + ); +} + +function float IGPlus_GetLatencyMs(optional bool bOneWay) { + local float PingMs; + + if (PingAverage > 0) + PingMs = float(PingAverage); + else if (PlayerReplicationInfo != None && PlayerReplicationInfo.Ping > 0) + PingMs = float(PlayerReplicationInfo.Ping); + + if (bOneWay) + return FMax(0.0, 0.5 * PingMs); + return FMax(0.0, PingMs); +} + +function float IGPlus_GetOneWayLatencyMs() { + return IGPlus_GetLatencyMs(true); +} + +function bool IGPlus_GetHistoricalMoverLocationByLatency( + Mover M, + float SampleLatencyMs, + out vector HistoricalMoverLoc +) { + local ST_MoverDummy MD; + local float TargetTimeStamp; + + if (M == None || zzUTPure == None || SampleLatencyMs <= 0.0) + return false; + + MD = zzUTPure.FindMoverDummy(M); + if (MD == None) + return false; + + TargetTimeStamp = Level.TimeSeconds - 0.001 * SampleLatencyMs * Level.TimeDilation; + HistoricalMoverLoc = MD.GetHistoricalLocation(TargetTimeStamp); + return true; +} + +function bool IGPlus_ShouldIgnoreOwnBaseHit( + vector StartTrace, + vector HitLocation +) { + local Mover BaseMover; + local vector HistoricalMoverLoc; + local bool bHasHistorical; + local vector ShotDir; + local float SampleLatencyMs; + + BaseMover = Mover(Base); + if (BaseMover == None) + return false; + + // Self-base occlusion should match shooter view time (one-way latency). + SampleLatencyMs = IGPlus_GetOneWayLatencyMs(); + bHasHistorical = IGPlus_GetHistoricalMoverLocationByLatency(BaseMover, SampleLatencyMs, HistoricalMoverLoc); + if (!bHasHistorical) + return false; + + ShotDir = Normal(HitLocation - StartTrace); + if (ShotDir.Z >= 0.0 && + IGPlus_PointInsideCylinder( + StartTrace, + HistoricalMoverLoc, + BaseMover.CollisionRadius, + BaseMover.CollisionHeight + ) + ) { + return true; + } + + // Ignore a self-base hit only if the shot segment does not intersect the + // mover at the same historical rewind frame. + return !IGPlus_LineIntersectsCylinder( + StartTrace, + HitLocation, + HistoricalMoverLoc, + BaseMover.CollisionRadius, + BaseMover.CollisionHeight + ); +} + +function bool IGPlus_GetHistoricalMoverLocation(Mover M, out vector HistoricalMoverLoc) { + // Target mover blocking uses the full compensation timeline. + return IGPlus_GetHistoricalMoverLocationByLatency(M, IGPlus_GetLatencyMs(false), HistoricalMoverLoc); +} + function Actor TraceShot(out vector HitLocation, out vector HitNormal, vector EndTrace, vector StartTrace) { local Actor A, Other; @@ -3772,15 +4028,17 @@ function Actor TraceShot(out vector HitLocation, out vector HitNormal, vector En continue; } - if (A.IsA('UTPlusDummy')) { - D = UTPlusDummy(A); - if (D != none && D.Actual != none && D.Actual != self) { - if (D.AdjustHitLocation(HitLocation, EndTrace - StartTrace)) { - Other = D.Actual; - break; + if (A.IsA('UTPlusDummy')) { + D = UTPlusDummy(A); + if (D != none && D.Actual != none && D.Actual != self) { + if (D.AdjustHitLocation(HitLocation, EndTrace - StartTrace)) { + if (IGPlus_IsTraceBlockedByTargetMover(D, StartTrace, HitLocation)) + continue; + Other = D.Actual; + break; + } } } - } else if (A.IsA('ST_ProjectileDummy')) { PD = ST_ProjectileDummy(A); if (PD.Actual != None) { @@ -3795,12 +4053,14 @@ function Actor TraceShot(out vector HitLocation, out vector HitNormal, vector En break; } } - } else if ((A == Level) || (Mover(A) != None) || A.bProjTarget || (A.bBlockPlayers && A.bBlockActors)) { - if (bSProjBlocks || !A.IsA('ShockProj') || bWeaponShock) { - Other = A; - break; + } else if ((A == Level) || (Mover(A) != None) || A.bProjTarget || (A.bBlockPlayers && A.bBlockActors)) { + if (A == Base && Mover(A) != None && IGPlus_ShouldIgnoreOwnBaseHit(StartTrace, HitLocation)) + continue; + if (bSProjBlocks || !A.IsA('ShockProj') || bWeaponShock) { + Other = A; + break; + } } - } if (Other != none) break; } @@ -6373,14 +6633,129 @@ function Died(pawn Killer, name damageType, vector HitLocation) } event ServerTick(float DeltaTime) { + local bool bSendSnapshot; + local bool bHardSnap; + local bool bOnMover; + local byte SnapshotIntentBits; + local byte SnapshotDodgeDir; + local byte SnapshotPhysics; + local float SnapshotDeltaTime; + local float SnapshotSendHz; + local float SnapshotInterval; + AverageServerDeltaTime = (AverageServerDeltaTime*99 + DeltaTime) * 0.01; IGPlus_ProcessRemoteMovement(); xxRememberPosition(); + IGPlus_UpdateServerSnapInterpDelayEstimate(); + + if (IGPlus_EnableSnapshotInterpolation) { + SnapshotSendHz = 30.0; + if (zzUTPure != None && zzUTPure.Settings.SnapshotInterpSendHz > 0.0) + SnapshotSendHz = zzUTPure.Settings.SnapshotInterpSendHz; + SnapshotInterval = 1.0 / FMax(SnapshotSendHz, 1.0); + + if (IGPlus_SnapInterp_ServerNextTime <= 0) + IGPlus_SnapInterp_ServerNextTime = Level.TimeSeconds; + + bSendSnapshot = Level.TimeSeconds >= IGPlus_SnapInterp_ServerNextTime; + if (bSendSnapshot) { + bHardSnap = false; + if (IGPlus_SnapInterp_ServerHasLast) { + SnapshotDeltaTime = FMax(Level.TimeSeconds - IGPlus_SnapInterp_ServerLastTime, 0.016); + if (VSize(Location - IGPlus_SnapInterp_ServerLastLocation) > 3000.0 * SnapshotDeltaTime) + bHardSnap = true; + } else { + bHardSnap = true; + } - if (IGPlus_WarpFixUpdate && IGPlus_EnableWarpFix) { - IGPlus_WarpFixData.OldLocation = Location; - IGPlus_WarpFixData.Counter += 1; + bOnMover = Mover(Base) != none; + IGPlus_SnapReplicatedDataPrev = IGPlus_SnapReplicatedData; + IGPlus_SnapReplicatedData.Location = Location; + IGPlus_SnapReplicatedData.Velocity = Velocity; + IGPlus_SnapReplicatedData.Rotation = Rotation; + IGPlus_SnapReplicatedData.Rotation.Roll = 0; + IGPlus_SnapReplicatedData.AnimSequence = AnimSequence; + IGPlus_SnapReplicatedData.AnimFrameByte = byte(FClamp(AnimFrame * 255.0, 0.0, 255.0)); + SnapshotIntentBits = 0; + if (bIsCrouching || DuckFraction > 0.25) + SnapshotIntentBits = SnapshotIntentBits | 1; + if (bDodging || DodgeDir == DODGE_Active) + SnapshotIntentBits = SnapshotIntentBits | 2; + + SnapshotDodgeDir = 0; + if (DodgeDir == DODGE_Left) + SnapshotDodgeDir = 1; + else if (DodgeDir == DODGE_Right) + SnapshotDodgeDir = 2; + else if (DodgeDir == DODGE_Forward) + SnapshotDodgeDir = 3; + else if (DodgeDir == DODGE_Back) + SnapshotDodgeDir = 4; + else if (DodgeDir == DODGE_Active) { + if (AnimSequence == 'DodgeL') + SnapshotDodgeDir = 1; + else if (AnimSequence == 'DodgeR') + SnapshotDodgeDir = 2; + else if (AnimSequence == 'DodgeB') + SnapshotDodgeDir = 4; + else if (AnimSequence == 'DodgeF' || AnimSequence == 'Flip') + SnapshotDodgeDir = 3; + } + // Bit 2 tags forward dodge visual intent: Flip vs DodgeF. + // Keep this strictly source-accurate: set only when the sampled + // sequence is actually Flip at snapshot time. + if (SnapshotDodgeDir == 3 && AnimSequence == 'Flip') + SnapshotIntentBits = SnapshotIntentBits | 4; + + switch (Physics) { + case PHYS_None: SnapshotPhysics = 0; break; + case PHYS_Walking: SnapshotPhysics = 1; break; + case PHYS_Falling: SnapshotPhysics = 2; break; + case PHYS_Swimming: SnapshotPhysics = 3; break; + case PHYS_Flying: SnapshotPhysics = 4; break; + case PHYS_Rotating: SnapshotPhysics = 5; break; + case PHYS_Projectile: SnapshotPhysics = 6; break; + case PHYS_Rolling: SnapshotPhysics = 7; break; + case PHYS_Interpolating: SnapshotPhysics = 8; break; + case PHYS_MovingBrush: SnapshotPhysics = 9; break; + case PHYS_Spider: SnapshotPhysics = 10; break; + case PHYS_Trailer: SnapshotPhysics = 11; break; + default: SnapshotPhysics = 0; break; + } + IGPlus_SnapReplicatedData.AnimIntentBits = SnapshotIntentBits; + IGPlus_SnapReplicatedData.AnimDodgeDir = SnapshotDodgeDir; + IGPlus_SnapReplicatedData.AnimPhysics = SnapshotPhysics; + IGPlus_SnapReplicatedData.Counter += 1; + IGPlus_SnapReplicatedData.bHardSnap = bHardSnap; + IGPlus_SnapReplicatedData.bBasedOnMover = bOnMover; + if (bOnMover) { + IGPlus_SnapReplicatedData.MoverLocation = Base.Location; + IGPlus_SnapReplicatedData.BaseMover = Base; + } else { + IGPlus_SnapReplicatedData.MoverLocation = vect(0,0,0); + IGPlus_SnapReplicatedData.BaseMover = None; + } + + IGPlus_SnapInterp_ServerLastLocation = Location; + IGPlus_SnapInterp_ServerLastTime = Level.TimeSeconds; + IGPlus_SnapInterp_ServerHasLast = true; + + while (IGPlus_SnapInterp_ServerNextTime <= Level.TimeSeconds) + IGPlus_SnapInterp_ServerNextTime += SnapshotInterval; + } + + // Snapshot interpolation uses a fixed send cadence and does not + // depend on per-move WarpFix update flags. IGPlus_WarpFixUpdate = false; + } else { + IGPlus_SnapInterp_ServerNextTime = 0; + IGPlus_SnapInterp_ServerHasLast = false; + + if (IGPlus_WarpFixUpdate && IGPlus_EnableWarpFix) { + IGPlus_WarpFixData.OldLocation = Location; + IGPlus_WarpFixData.Counter += 1; + IGPlus_WarpFixUpdate = false; + } } if (DelayedNavPoint != none) { @@ -8136,6 +8511,15 @@ function ApplyBrightskins(PlayerReplicationInfo PRI) { P.Weapon.bUnlit = false; } +function bool IGPlus_ShouldUseSnapshotInterpForProxy(optional bbPlayer P) { + if (P == none) + P = self; + + return P != none && + P.Role == ROLE_SimulatedProxy && + P.IGPlus_EnableSnapshotInterpolation; +} + function IGPlus_ApplyWarpFix(PlayerReplicationInfo PRI) { local bbPlayer P; if (PRI == PlayerReplicationInfo) @@ -8148,6 +8532,8 @@ function IGPlus_ApplyWarpFix(PlayerReplicationInfo PRI) { return; P = bbPlayer(PRI.Owner); + if (IGPlus_ShouldUseSnapshotInterpForProxy(P)) + return; if (Level.Pauser != "") { P.IGPlus_WarpFixClientData.TimeStamp = Level.TimeSeconds; @@ -8165,6 +8551,211 @@ function IGPlus_ApplyWarpFix(PlayerReplicationInfo PRI) { P.SetLocation(P.IGPlus_WarpFixClientData.Last.OldLocation); } +simulated function IGPlus_UpdateClientSnapInterpDelayReport() { + local int i; + local int j; + local int SampleCount; + local int MedianDelayMs; + local float Sample; + local float DelaySamples[32]; + local PlayerReplicationInfo PRI; + local bbPlayer P; + + if (Role != ROLE_AutonomousProxy || Level.NetMode != NM_Client) + return; + + if (IGPlus_EnableSnapshotInterpolation == false) { + IGPlus_ClientSnapInterpDelayMs = -1; + IGPlus_ClientSnapInterpDelaySampleCount = 0; + IGPlus_ClientSnapInterpDelayMedianMs = -1; + IGPlus_ClientSnapInterpDelaySpreadMs = -1; + return; + } + + if (Level.TimeSeconds < IGPlus_NextSnapInterpDelayClientReportTime) + return; + + IGPlus_NextSnapInterpDelayClientReportTime = Level.TimeSeconds + 0.25; + SampleCount = 0; + + if (GameReplicationInfo != None && PlayerReplicationInfo != None) { + for (i = 0; i < arraycount(GameReplicationInfo.PRIArray); ++i) { + PRI = GameReplicationInfo.PRIArray[i]; + if (PRI == none) + break; + + P = bbPlayer(PRI.Owner); + if (P == none || P == self) + continue; + if (P.Role != ROLE_SimulatedProxy) + continue; + if (P.IGPlus_EnableSnapshotInterpolation == false) + continue; + if (P.IGPlus_SnapInterp_Count < 2) + continue; + if (SampleCount >= arraycount(DelaySamples)) + break; + + DelaySamples[SampleCount] = FClamp(P.IGPlus_SnapInterp_InterpDelay * 1000.0, 0.0, 200.0); + SampleCount++; + } + } + + if (SampleCount <= 0) { + IGPlus_ClientSnapInterpDelayMs = -1; + IGPlus_ClientSnapInterpDelaySampleCount = 0; + IGPlus_ClientSnapInterpDelayMedianMs = -1; + IGPlus_ClientSnapInterpDelaySpreadMs = -1; + return; + } + + for (i = 1; i < SampleCount; ++i) { + Sample = DelaySamples[i]; + j = i - 1; + while (j >= 0 && DelaySamples[j] > Sample) { + DelaySamples[j + 1] = DelaySamples[j]; + j--; + } + DelaySamples[j + 1] = Sample; + } + + if ((SampleCount & 1) == 1) + MedianDelayMs = int(DelaySamples[SampleCount / 2]); + else + MedianDelayMs = int(0.5 * (DelaySamples[(SampleCount / 2) - 1] + DelaySamples[SampleCount / 2])); + + IGPlus_ClientSnapInterpDelaySampleCount = SampleCount; + IGPlus_ClientSnapInterpDelayMedianMs = MedianDelayMs; + IGPlus_ClientSnapInterpDelaySpreadMs = int(DelaySamples[SampleCount - 1] - DelaySamples[0]); + IGPlus_ClientSnapInterpDelayMs = Clamp(MedianDelayMs, 0, 200); +} + +function IGPlus_UpdateServerSnapInterpDelayEstimate() { + local float BaselineMs; + local float MinAllowedMs; + local float MaxAllowedMs; + local float ReportedMs; + local float SmoothedTargetMs; + local float SmoothedDeltaMs; + local int MinSampleCount; + local int MaxSpreadMs; + local float TrustTimeout; + + if (Role != ROLE_Authority) + return; + + MinSampleCount = 1; + MaxSpreadMs = 35; + TrustTimeout = 1.0; + + if (IGPlus_EnableSnapshotInterpolation == false || zzUTPure == None || zzUTPure.Settings == None) { + IGPlus_ServerSnapInterpDelayValid = false; + IGPlus_ServerSnapInterpTrusted = false; + IGPlus_ServerSnapInterpLastAcceptedTime = 0.0; + IGPlus_ServerSnapInterpDelayMsSmoothed = 0.0; + IGPlus_ServerSnapInterpDelayReportedMsEcho = -1; + IGPlus_ServerSnapInterpDelayClampedMsEcho = -1; + IGPlus_ServerSnapInterpDelaySmoothedMsEcho = 0; + IGPlus_ServerSnapInterpDelayValidEcho = false; + IGPlus_ServerSnapInterpTrustedEcho = false; + IGPlus_ServerSnapInterpDelayEchoTime = Level.TimeSeconds; + IGPlus_ServerSnapInterpLastAcceptedTimeEcho = 0.0; + return; + } + + if (IGPlus_ServerSnapInterpTrusted && + (Level.TimeSeconds - IGPlus_ServerSnapInterpLastAcceptedTime > TrustTimeout) + ) { + IGPlus_ServerSnapInterpTrusted = false; + } + + if (Level.TimeSeconds < IGPlus_NextSnapInterpDelayReportTime) + return; + + IGPlus_NextSnapInterpDelayReportTime = Level.TimeSeconds + 0.25; + IGPlus_ServerSnapInterpDelayReportedMsEcho = IGPlus_ClientSnapInterpDelayMs; + if (IGPlus_ClientSnapInterpDelayMs < 0 || + IGPlus_ClientSnapInterpDelaySampleCount < MinSampleCount || + IGPlus_ClientSnapInterpDelaySpreadMs < 0 || + IGPlus_ClientSnapInterpDelaySpreadMs > MaxSpreadMs + ) { + IGPlus_ServerSnapInterpTrusted = false; + IGPlus_ServerSnapInterpDelayClampedMsEcho = -1; + IGPlus_ServerSnapInterpDelaySmoothedMsEcho = int(IGPlus_ServerSnapInterpDelayMsSmoothed + 0.5); + IGPlus_ServerSnapInterpDelayValidEcho = false; + IGPlus_ServerSnapInterpTrustedEcho = false; + IGPlus_ServerSnapInterpDelayEchoTime = Level.TimeSeconds; + IGPlus_ServerSnapInterpLastAcceptedTimeEcho = IGPlus_ServerSnapInterpLastAcceptedTime; + return; + } + + BaselineMs = FMax(0.0, zzUTPure.Settings.SnapshotInterpRewindMs); + MinAllowedMs = FMax(0.0, BaselineMs - 30.0); + MaxAllowedMs = FMin(200.0, BaselineMs + 60.0); + ReportedMs = FClamp(float(IGPlus_ClientSnapInterpDelayMs), MinAllowedMs, MaxAllowedMs); + IGPlus_ServerSnapInterpDelayClampedMsEcho = int(ReportedMs + 0.5); + + if (IGPlus_ServerSnapInterpDelayValid == false) { + IGPlus_ServerSnapInterpDelayMsSmoothed = ReportedMs; + IGPlus_ServerSnapInterpDelayValid = true; + IGPlus_ServerSnapInterpTrusted = true; + IGPlus_ServerSnapInterpLastAcceptedTime = Level.TimeSeconds; + IGPlus_ServerSnapInterpDelaySmoothedMsEcho = int(IGPlus_ServerSnapInterpDelayMsSmoothed + 0.5); + IGPlus_ServerSnapInterpDelayValidEcho = true; + IGPlus_ServerSnapInterpTrustedEcho = true; + IGPlus_ServerSnapInterpDelayEchoTime = Level.TimeSeconds; + IGPlus_ServerSnapInterpLastAcceptedTimeEcho = IGPlus_ServerSnapInterpLastAcceptedTime; + return; + } + + SmoothedTargetMs = IGPlus_ServerSnapInterpDelayMsSmoothed + 0.2 * (ReportedMs - IGPlus_ServerSnapInterpDelayMsSmoothed); + SmoothedDeltaMs = FClamp(SmoothedTargetMs - IGPlus_ServerSnapInterpDelayMsSmoothed, -10.0, 10.0); + IGPlus_ServerSnapInterpDelayMsSmoothed = FClamp( + IGPlus_ServerSnapInterpDelayMsSmoothed + SmoothedDeltaMs, + MinAllowedMs, + MaxAllowedMs + ); + IGPlus_ServerSnapInterpTrusted = true; + IGPlus_ServerSnapInterpLastAcceptedTime = Level.TimeSeconds; + IGPlus_ServerSnapInterpDelaySmoothedMsEcho = int(IGPlus_ServerSnapInterpDelayMsSmoothed + 0.5); + IGPlus_ServerSnapInterpDelayValidEcho = true; + IGPlus_ServerSnapInterpTrustedEcho = true; + IGPlus_ServerSnapInterpDelayEchoTime = Level.TimeSeconds; + IGPlus_ServerSnapInterpLastAcceptedTimeEcho = IGPlus_ServerSnapInterpLastAcceptedTime; +} + +exec simulated function SnapInterpNetStatus(optional string TargetFilter) { + local int EchoAgeMs; + local int AcceptedAgeMs; + + EchoAgeMs = int((Level.TimeSeconds - IGPlus_ServerSnapInterpDelayEchoTime) * 1000.0); + if (EchoAgeMs < 0) + EchoAgeMs = 0; + AcceptedAgeMs = int((Level.TimeSeconds - IGPlus_ServerSnapInterpLastAcceptedTimeEcho) * 1000.0); + if (AcceptedAgeMs < 0) + AcceptedAgeMs = 0; + + ClientMessage( + "SnapInterpNet client reportMs="$IGPlus_ClientSnapInterpDelayMs$ + " medianMs="$IGPlus_ClientSnapInterpDelayMedianMs$ + " samples="$IGPlus_ClientSnapInterpDelaySampleCount$ + " spreadMs="$IGPlus_ClientSnapInterpDelaySpreadMs$ + " snapEnabled="$IGPlus_EnableSnapshotInterpolation + ); + + ClientMessage( + "SnapInterpNet server valid="$IGPlus_ServerSnapInterpDelayValidEcho$ + " trusted="$IGPlus_ServerSnapInterpTrustedEcho$ + " reportedMs="$IGPlus_ServerSnapInterpDelayReportedMsEcho$ + " clampedMs="$IGPlus_ServerSnapInterpDelayClampedMsEcho$ + " smoothedMs="$IGPlus_ServerSnapInterpDelaySmoothedMsEcho$ + " ageMs="$EchoAgeMs$ + " acceptedAgeMs="$AcceptedAgeMs + ); + + SnapInterpStatus(TargetFilter); +} + event PreRender( canvas zzCanvas ) { local int i; @@ -8180,6 +8771,17 @@ event PreRender( canvas zzCanvas ) // ensure players are visible during pauses IGPlus_LocationOffsetFix_AfterAll(0); + if (Role == ROLE_AutonomousProxy && Level.NetMode == NM_Client) + IGPlus_UpdateClientSnapInterpDelayReport(); + + if (IGPlus_SnapInterp_CheckActive && Level.TimeSeconds >= IGPlus_SnapInterp_CheckEndTime) + IGPlus_SnapInterp_FinishCheck(false); + + if (IGPlus_SnapInterp_NetDebug && Level.TimeSeconds >= IGPlus_SnapInterp_NetDebugNextTime) { + IGPlus_SnapInterp_NetDebugNextTime = Level.TimeSeconds + 1.0; + SnapInterpNetStatus(); + } + if (GameReplicationInfo != None && PlayerReplicationInfo != None) { for (i = 0; i < arraycount(GameReplicationInfo.PRIArray); ++i) { zzPRI = GameReplicationInfo.PRIArray[i]; @@ -8499,7 +9101,10 @@ function IGPlus_LocationOffsetFix_AfterAll(float DeltaTime) { if (P == none) continue; if (P.Role != ROLE_SimulatedProxy) continue; - P.IGPlus_LocationOffsetFix_After(DeltaTime); + if (IGPlus_ShouldUseSnapshotInterpForProxy(P)) + P.IGPlus_SnapInterp_After(DeltaTime); + else + P.IGPlus_LocationOffsetFix_After(DeltaTime); } } @@ -8514,8 +9119,17 @@ function IGPlus_LocationOffsetFix_AfterAll(float DeltaTime) { simulated event Tick(float DeltaTime) { super.Tick(DeltaTime); - if (Settings.bEnableLocationOffsetFix || IGPlus_LocationOffsetFix_Moved) + // Reset on both enable and disable transitions for simulated proxies. + if (Role == ROLE_SimulatedProxy && IGPlus_SnapInterp_EnabledLast != IGPlus_EnableSnapshotInterpolation) { + IGPlus_SnapInterp_Reset(); + } + + if (IGPlus_ShouldUseSnapshotInterpForProxy()) { + if (IGPlus_LocationOffsetFix_Moved) + IGPlus_SnapInterp_After(DeltaTime); + } else if (Settings.bEnableLocationOffsetFix || IGPlus_LocationOffsetFix_Moved) { IGPlus_LocationOffsetFix_After(DeltaTime); + } } simulated function IGPlus_LocationOffsetFix_Restore() { @@ -8640,6 +9254,763 @@ simulated function bool IGPlus_LocationOffsetFix_IsOnGround(out vector HitNormal && (HitNormal.Z >= 0.7); } +simulated function float IGPlus_SnapInterp_GetFallbackInterval() { + local float SnapshotSendHz; + local bbPlayer LP; + + SnapshotSendHz = 30.0; + if (zzUTPure != None && zzUTPure.Settings != None && zzUTPure.Settings.SnapshotInterpSendHz > 0.0) + SnapshotSendHz = zzUTPure.Settings.SnapshotInterpSendHz; + else { + LP = bbPlayer(GetLocalPlayer()); + if (LP != None && + LP.zzUTPure != None && + LP.zzUTPure.Settings != None && + LP.zzUTPure.Settings.SnapshotInterpSendHz > 0.0 + ) { + SnapshotSendHz = LP.zzUTPure.Settings.SnapshotInterpSendHz; + } + } + + return 1.0 / FMax(SnapshotSendHz, 1.0); +} + +simulated function float IGPlus_SnapInterp_GetExpectedInterval() { + return FMax(IGPlus_SnapInterp_MeanInterval, IGPlus_SnapInterp_GetFallbackInterval()); +} + +simulated function IGPlus_SnapInterp_UpdateTargetDelay(float ExpectedInterval) { + local float TargetDelay; + local float DelayDelta; + + ExpectedInterval = FMax(ExpectedInterval, 0.001); + TargetDelay = (2.5 * ExpectedInterval) + IGPlus_SnapInterp_JitterEstimate; + TargetDelay = FClamp(TargetDelay, 0.020, 0.200); + DelayDelta = FClamp(TargetDelay - IGPlus_SnapInterp_InterpDelay, -0.010, 0.020); + IGPlus_SnapInterp_InterpDelay = FClamp(IGPlus_SnapInterp_InterpDelay + DelayDelta, 0.020, 0.200); +} + +simulated function IGPlus_SnapInterp_BoostDelayForStress(optional float Scale) { + local float ExpectedInterval; + + if (Scale <= 0.0) + Scale = 0.5; + + ExpectedInterval = IGPlus_SnapInterp_GetExpectedInterval(); + IGPlus_SnapInterp_InterpDelay = FMin(0.200, IGPlus_SnapInterp_InterpDelay + Scale * ExpectedInterval); +} + +simulated function IGPlus_SnapInterp_Push( + vector Pos, + vector Vel, + rotator SnapRot, + name SnapAnimSeq, + byte SnapAnimFrameByte, + byte SnapAnimIntentBits, + byte SnapAnimDodgeDir, + byte SnapAnimPhysics, + float SnapTime, + float ArrivalTime, + optional bool bFromServerMoverState, + optional bool bBasedOnMover, + optional vector BaseMoverLocation, + optional Actor BaseMoverActor, + optional bool bUpdateDelayStats +) { + local int BufSize; + local int PrevIdx; + local float Interval; + local float ExpectedInterval; + local IGPlus_InterpSnapshot Snap; + local IGPlus_InterpSnapshot PrevSnap; + + BufSize = arraycount(IGPlus_SnapInterp_Data); + + if (IGPlus_SnapInterp_Data[IGPlus_SnapInterp_DataIndex] == None) + IGPlus_SnapInterp_Data[IGPlus_SnapInterp_DataIndex] = new class'IGPlus_InterpSnapshot'; + + Snap = IGPlus_SnapInterp_Data[IGPlus_SnapInterp_DataIndex]; + Snap.Loc = Pos; + Snap.Vel = Vel; + Snap.Rot = SnapRot; + Snap.Rot.Roll = 0; + Snap.AnimSeq = SnapAnimSeq; + Snap.AnimFrameByte = SnapAnimFrameByte; + Snap.AnimIntentBits = SnapAnimIntentBits; + Snap.AnimDodgeDir = SnapAnimDodgeDir; + Snap.AnimPhysics = SnapAnimPhysics; + Snap.Time = SnapTime; + if (bFromServerMoverState) { + Snap.bBasedOnMover = bBasedOnMover; + if (Snap.bBasedOnMover) { + Snap.MoverLoc = BaseMoverLocation; + Snap.RelLoc = Pos - BaseMoverLocation; + Snap.BaseMover = BaseMoverActor; + if (Snap.BaseMover == None) { + Snap.bBasedOnMover = false; + Snap.RelLoc = vect(0,0,0); + Snap.MoverLoc = vect(0,0,0); + } + } else { + Snap.RelLoc = vect(0,0,0); + Snap.MoverLoc = vect(0,0,0); + Snap.BaseMover = None; + } + } else { + Snap.bBasedOnMover = Mover(Base) != None; + if (Snap.bBasedOnMover) { + Snap.MoverLoc = Base.Location; + Snap.RelLoc = Pos - Base.Location; + Snap.BaseMover = Base; + } else { + Snap.RelLoc = vect(0,0,0); + Snap.MoverLoc = vect(0,0,0); + Snap.BaseMover = None; + } + } + + // Teleport detection: if distance to previous snapshot is unreasonably large, reset + if (IGPlus_SnapInterp_Count > 0) { + PrevIdx = (IGPlus_SnapInterp_DataIndex - 1 + BufSize) % BufSize; + PrevSnap = IGPlus_SnapInterp_Data[PrevIdx]; + if (PrevSnap != None && VSize(Pos - PrevSnap.Loc) > 3000.0 * FMax(SnapTime - PrevSnap.Time, 0.016)) { + IGPlus_SnapInterp_Reset(); + IGPlus_SnapInterp_Data[0] = Snap; + IGPlus_SnapInterp_DataIndex = 1 % BufSize; + IGPlus_SnapInterp_Count = 1; + if (bUpdateDelayStats) + IGPlus_SnapInterp_LastArrivalTime = ArrivalTime; + return; + } + } + + IGPlus_SnapInterp_DataIndex = (IGPlus_SnapInterp_DataIndex + 1) % BufSize; + if (IGPlus_SnapInterp_Count < BufSize) + IGPlus_SnapInterp_Count++; + + // Update adaptive interpolation delay. + if (bUpdateDelayStats && IGPlus_SnapInterp_LastArrivalTime > 0) { + Interval = ArrivalTime - IGPlus_SnapInterp_LastArrivalTime; + if (Interval > 0 && Interval < 1.0) { + IGPlus_SnapInterp_MeanInterval += 0.1 * (Interval - IGPlus_SnapInterp_MeanInterval); + IGPlus_SnapInterp_JitterEstimate += 0.1 * (Abs(Interval - IGPlus_SnapInterp_MeanInterval) - IGPlus_SnapInterp_JitterEstimate); + ExpectedInterval = IGPlus_SnapInterp_GetExpectedInterval(); + IGPlus_SnapInterp_UpdateTargetDelay(ExpectedInterval); + } + } + if (bUpdateDelayStats) + IGPlus_SnapInterp_LastArrivalTime = ArrivalTime; +} + +simulated function int IGPlus_SnapInterp_LerpAxis(int A, int B, float Alpha) { + local int AS; + local int BS; + local int Delta; + local int Out; + + AS = Utils.RotU2S(A); + BS = Utils.RotU2S(B); + Delta = ((BS - AS + 32768) & 65535) - 32768; + Out = AS + int(float(Delta) * Alpha); + return Utils.RotS2U(Out); +} + +simulated function rotator IGPlus_SnapInterp_LerpRot(rotator A, rotator B, float Alpha) { + local rotator R; + R.Yaw = IGPlus_SnapInterp_LerpAxis(A.Yaw, B.Yaw, Alpha); + R.Pitch = IGPlus_SnapInterp_LerpAxis(A.Pitch, B.Pitch, Alpha); + R.Roll = 0; + return R; +} + +simulated function bool IGPlus_SnapInterp_Interpolate( + float RenderTime, + out vector OutPos, + out vector OutVel, + out rotator OutRot, + out name OutAnimSeq, + out byte OutAnimFrameByte, + out byte OutAnimIntentBits, + out byte OutAnimDodgeDir, + out byte OutAnimPhysics, + out float OutHintTime, + out byte InterpMode +) { + local int BufSize; + local int Idx, Scans; + local IGPlus_InterpSnapshot OlderSnap; + local IGPlus_InterpSnapshot NewerSnap; + local float TimeDelta, Alpha; + local float ExtrapolationTime; + local vector MoverOffset; + + InterpMode = 0; + OutAnimSeq = ''; + OutAnimFrameByte = 0; + OutAnimIntentBits = 0; + OutAnimDodgeDir = 0; + OutAnimPhysics = 0; + OutHintTime = 0.0; + + if (IGPlus_SnapInterp_Count < 1) + return false; + + BufSize = arraycount(IGPlus_SnapInterp_Data); + Idx = (IGPlus_SnapInterp_DataIndex - 1 + BufSize) % BufSize; + NewerSnap = None; + + for (Scans = 0; Scans < IGPlus_SnapInterp_Count; Scans++) { + OlderSnap = IGPlus_SnapInterp_Data[Idx]; + + if (OlderSnap != None && OlderSnap.Time > 0) { + if (OlderSnap.Time <= RenderTime) { + if (NewerSnap != None) { + TimeDelta = NewerSnap.Time - OlderSnap.Time; + if (TimeDelta > 0.001) { + Alpha = (RenderTime - OlderSnap.Time) / TimeDelta; + if (Alpha > 1.0) Alpha = 1.0; + + // World-space base interpolation + OutPos = OlderSnap.Loc + (NewerSnap.Loc - OlderSnap.Loc) * Alpha; + + // Mover compensation: shifts each on-mover endpoint from its + // historical server-sampled mover position to the live client + // mover position. Alpha-weighting naturally fades the offset + // during mount/dismount transitions, avoiding velocity pops. + if (OlderSnap.bBasedOnMover && OlderSnap.BaseMover != None) { + MoverOffset = OlderSnap.BaseMover.Location - OlderSnap.MoverLoc; + if (VSize(MoverOffset) < 256.0) + OutPos += MoverOffset * (1.0 - Alpha); + } + if (NewerSnap.bBasedOnMover && NewerSnap.BaseMover != None) { + MoverOffset = NewerSnap.BaseMover.Location - NewerSnap.MoverLoc; + if (VSize(MoverOffset) < 256.0) + OutPos += MoverOffset * Alpha; + } + + OutVel = OlderSnap.Vel + (NewerSnap.Vel - OlderSnap.Vel) * Alpha; + OutRot = IGPlus_SnapInterp_LerpRot(OlderSnap.Rot, NewerSnap.Rot, Alpha); + if (Alpha < 0.5) { + OutAnimSeq = OlderSnap.AnimSeq; + OutAnimFrameByte = OlderSnap.AnimFrameByte; + OutAnimIntentBits = OlderSnap.AnimIntentBits; + OutAnimDodgeDir = OlderSnap.AnimDodgeDir; + OutAnimPhysics = OlderSnap.AnimPhysics; + OutHintTime = OlderSnap.Time; + } else { + OutAnimSeq = NewerSnap.AnimSeq; + OutAnimFrameByte = NewerSnap.AnimFrameByte; + OutAnimIntentBits = NewerSnap.AnimIntentBits; + OutAnimDodgeDir = NewerSnap.AnimDodgeDir; + OutAnimPhysics = NewerSnap.AnimPhysics; + OutHintTime = NewerSnap.Time; + } + InterpMode = 1; + return true; + } + } + ExtrapolationTime = FClamp(RenderTime - OlderSnap.Time, 0.0, 0.050); + if (OlderSnap.bBasedOnMover && OlderSnap.BaseMover != None) { + MoverOffset = OlderSnap.BaseMover.Location - OlderSnap.MoverLoc; + if (VSize(MoverOffset) < 256.0) + OutPos = OlderSnap.BaseMover.Location + OlderSnap.RelLoc; + else + OutPos = OlderSnap.Loc + OlderSnap.Vel * ExtrapolationTime; + } else { + OutPos = OlderSnap.Loc + OlderSnap.Vel * ExtrapolationTime; + } + OutVel = OlderSnap.Vel; + OutRot = OlderSnap.Rot; + OutRot.Roll = 0; + OutAnimSeq = OlderSnap.AnimSeq; + OutAnimFrameByte = OlderSnap.AnimFrameByte; + OutAnimIntentBits = OlderSnap.AnimIntentBits; + OutAnimDodgeDir = OlderSnap.AnimDodgeDir; + OutAnimPhysics = OlderSnap.AnimPhysics; + OutHintTime = OlderSnap.Time; + InterpMode = 2; + return true; + } + NewerSnap = OlderSnap; + } + + Idx = (Idx - 1 + BufSize) % BufSize; + } + + if (NewerSnap != None) { + if (NewerSnap.bBasedOnMover && NewerSnap.BaseMover != None) { + MoverOffset = NewerSnap.BaseMover.Location - NewerSnap.MoverLoc; + if (VSize(MoverOffset) < 256.0) + OutPos = NewerSnap.BaseMover.Location + NewerSnap.RelLoc; + else + OutPos = NewerSnap.Loc; + } else { + OutPos = NewerSnap.Loc; + } + OutVel = NewerSnap.Vel; + OutRot = NewerSnap.Rot; + OutRot.Roll = 0; + OutAnimSeq = NewerSnap.AnimSeq; + OutAnimFrameByte = NewerSnap.AnimFrameByte; + OutAnimIntentBits = NewerSnap.AnimIntentBits; + OutAnimDodgeDir = NewerSnap.AnimDodgeDir; + OutAnimPhysics = NewerSnap.AnimPhysics; + OutHintTime = NewerSnap.Time; + InterpMode = 3; + return true; + } + + return false; +} + +simulated function bool IGPlus_SnapInterp_ApplyIntentHint( + byte SnapAnimIntentBits, + byte SnapAnimDodgeDir, + byte SnapAnimPhysics, + optional byte InterpMode, + optional float SnapshotAgeSec +) { + local name TargetSeq; + local float TweenTime; + local float MaxIntentAgeSec; + + // Intent hints are only used in stressed modes. + if (InterpMode <= 1) + return false; + + // Ignore stale intent under heavy loss/clamp to avoid wrong transient pose hints. + MaxIntentAgeSec = 0.150; + if (SnapshotAgeSec > MaxIntentAgeSec) + return false; + + // Bit 1: dodging + if ((SnapAnimIntentBits & 2) != 0) { + switch (GetDodgeDir(SnapAnimDodgeDir)) { + case DODGE_Left: + TargetSeq = 'DodgeL'; + break; + case DODGE_Right: + TargetSeq = 'DodgeR'; + break; + case DODGE_Back: + TargetSeq = 'DodgeB'; + break; + case DODGE_Forward: + case DODGE_Active: + if ((SnapAnimIntentBits & 4) != 0) + TargetSeq = 'Flip'; + else + TargetSeq = 'DodgeF'; + break; + default: + TargetSeq = 'DodgeF'; + break; + } + + if (AnimSequence != TargetSeq) + TweenAnim(TargetSeq, 0.08); + return true; + } + + // Bit 0: ducking/crouch. + if ((SnapAnimIntentBits & 1) != 0) { + if (GetAnimGroup(AnimSequence) != 'Ducking') { + TweenTime = 0.12; + if (GetPhysics(int(SnapAnimPhysics)) == PHYS_Falling) + TweenTime = 0.08; + TweenAnim('DuckWlkS', TweenTime); + } + return true; + } + + return false; +} + +simulated function bool IGPlus_SnapInterp_IsTransientAnim(name SnapAnimSeq, name SnapAnimGroup) { + if (SnapAnimGroup == 'Jumping' || SnapAnimGroup == 'Landing') + return true; + + if (SnapAnimSeq == 'DodgeL' || + SnapAnimSeq == 'DodgeR' || + SnapAnimSeq == 'DodgeF' || + SnapAnimSeq == 'DodgeB' || + SnapAnimSeq == 'Flip') + return true; + + return false; +} + +simulated function float IGPlus_SnapInterp_GetAnimDurationSec(name SnapAnimSeq, name SnapAnimGroup) { + // Based on UT99 sequence data: + // - Jump/Land/Dodge are mostly 1-frame transients + // - Flip is 20 (male/boss) to 31 (female) frames. + if (SnapAnimSeq == 'Flip') + return 0.50; + if (SnapAnimSeq == 'DodgeL' || + SnapAnimSeq == 'DodgeR' || + SnapAnimSeq == 'DodgeF' || + SnapAnimSeq == 'DodgeB') + return 0.10; + if (SnapAnimSeq == 'JumpLgFr' || SnapAnimSeq == 'JumpSmFr') + return 0.08; + if (SnapAnimSeq == 'LandLgFr' || SnapAnimSeq == 'LandSmFr') + return 0.06; + + if (SnapAnimGroup == 'Landing') + return 0.08; + if (SnapAnimGroup == 'Jumping') + return 0.20; + if (SnapAnimGroup == 'Ducking') + return 1.00; + + return 0.25; +} + +simulated function bool IGPlus_SnapInterp_IsAnimHintFresh( + name SnapAnimSeq, + name SnapAnimGroup, + byte SnapAnimFrameByte, + float SnapshotAgeSec, + optional float FreshnessMarginSec +) { + local float SequenceDuration; + local float FrameAlpha; + local float RemainingDuration; + local float FreshnessLimit; + + SequenceDuration = IGPlus_SnapInterp_GetAnimDurationSec(SnapAnimSeq, SnapAnimGroup); + FrameAlpha = FClamp(float(SnapAnimFrameByte) / 255.0, 0.0, 1.0); + RemainingDuration = SequenceDuration * FClamp(1.0 - FrameAlpha, 0.0, 1.0); + FreshnessLimit = FMin(0.300, FMax(0.040, RemainingDuration + FreshnessMarginSec)); + + return SnapshotAgeSec <= FreshnessLimit; +} + +simulated function bool IGPlus_SnapInterp_ApplyAnimHint( + name SnapAnimSeq, + byte SnapAnimFrameByte, + optional byte InterpMode, + optional float SnapshotAgeSec +) { + local bool bTransient; + local float TweenTime; + local name SnapAnimGroup; + local float FreshnessMarginSec; + + // InterpMode 0 means no usable interpolation output. + if (InterpMode <= 0) + return false; + + if (SnapAnimSeq == '') + return false; + + SnapAnimGroup = GetAnimGroup(SnapAnimSeq); + bTransient = IGPlus_SnapInterp_IsTransientAnim(SnapAnimSeq, SnapAnimGroup); + + // In normal interpolation mode, only allow transient sequence hints. + if (InterpMode == 1 && bTransient == false) + return false; + + // Expire hints based on sequence duration + frame position. + FreshnessMarginSec = 0.060; + if (InterpMode > 1) + FreshnessMarginSec = 0.090; + if (IGPlus_SnapInterp_IsAnimHintFresh( + SnapAnimSeq, + SnapAnimGroup, + SnapAnimFrameByte, + SnapshotAgeSec, + FreshnessMarginSec + ) == false) + return false; + + // Jumping sequences are highly transient; only use while proxy is actually airborne. + if (SnapAnimGroup == 'Jumping') { + if (Physics != PHYS_Falling && Velocity.Z <= 0.0) + return false; + } + + if (AnimSequence != SnapAnimSeq) { + TweenTime = 0.06; + if (SnapAnimGroup == 'Jumping') + TweenTime = 0.08; + else if (SnapAnimGroup == 'Ducking') + TweenTime = 0.12; + + TweenAnim(SnapAnimSeq, TweenTime); + } + + AnimFrame = FClamp(float(SnapAnimFrameByte) / 255.0, 0.0, 1.0); + return true; +} + +simulated function IGPlus_SnapInterp_Reset() { + local int i; + + for (i = 0; i < arraycount(IGPlus_SnapInterp_Data); i++) + IGPlus_SnapInterp_Data[i] = None; + + IGPlus_SnapInterp_DataIndex = 0; + IGPlus_SnapInterp_Count = 0; + IGPlus_SnapInterp_InterpDelay = 0.050; + IGPlus_SnapInterp_LastArrivalTime = 0; + IGPlus_SnapInterp_MeanInterval = 0; + IGPlus_SnapInterp_JitterEstimate = 0; + IGPlus_SnapInterp_LastCounter = 0; + IGPlus_SnapInterp_EnabledLast = IGPlus_EnableSnapshotInterpolation; +} + +simulated function IGPlus_SnapInterp_After(float DeltaTime) { + local bool bReplicatedLocation; + local bool bCanRecoverPrev; + local bool bHasInterpolated; + local bool bAppliedIntentHint; + local bool bAppliedAnimHint; + local int RepCounter; + local int PrevCounter; + local byte InterpMode; + local float RenderTime; + local float ArrivalTime; + local float SnapshotTime; + local float PrevSnapshotTime; + local float ExpectedInterval; + local float OutHintTime; + local float HintAgeSec; + local name OutAnimSeq; + local byte OutAnimFrameByte; + local byte OutAnimIntentBits; + local byte OutAnimDodgeDir; + local byte OutAnimPhysics; + local vector OutPos; + local vector OutVel; + local rotator OutRot; + local vector FloorSnapLoc; + local vector FloorHitLoc; + local vector FloorHitNorm; + local Actor FloorActor; + local vector TraceExtent; + + RepCounter = IGPlus_SnapReplicatedData.Counter; + PrevCounter = IGPlus_SnapReplicatedDataPrev.Counter; + bHasInterpolated = false; + bAppliedIntentHint = false; + bAppliedAnimHint = false; + InterpMode = 0; + OutHintTime = 0.0; + HintAgeSec = 0.0; + IGPlus_SnapInterp_LastInterpMode = 0; + IGPlus_SnapInterp_LastHintAgeMs = 0; + IGPlus_SnapInterp_LastHintSource = "none"; + bReplicatedLocation = (RepCounter > IGPlus_SnapInterp_LastCounter) + || (IGPlus_SnapInterp_LastCounter == 0 && RepCounter != 0); + + if (bReplicatedLocation) { + ArrivalTime = Level.TimeSeconds; + // Use arrival time for interpolation to avoid server clock quantization issues. + SnapshotTime = ArrivalTime; + bCanRecoverPrev = true; + + if (IGPlus_SnapReplicatedData.bHardSnap) { + IGPlus_SnapInterp_StatHardSnap += 1; + IGPlus_SnapInterp_Reset(); + bCanRecoverPrev = false; + } + + if (bCanRecoverPrev && + IGPlus_SnapInterp_LastCounter > 0 && + PrevCounter > IGPlus_SnapInterp_LastCounter && + PrevCounter < RepCounter + ) { + ExpectedInterval = IGPlus_SnapInterp_GetExpectedInterval(); + PrevSnapshotTime = ArrivalTime - ExpectedInterval; + IGPlus_SnapInterp_Push( + IGPlus_SnapReplicatedDataPrev.Location, + IGPlus_SnapReplicatedDataPrev.Velocity, + IGPlus_SnapReplicatedDataPrev.Rotation, + IGPlus_SnapReplicatedDataPrev.AnimSequence, + IGPlus_SnapReplicatedDataPrev.AnimFrameByte, + IGPlus_SnapReplicatedDataPrev.AnimIntentBits, + IGPlus_SnapReplicatedDataPrev.AnimDodgeDir, + IGPlus_SnapReplicatedDataPrev.AnimPhysics, + PrevSnapshotTime, + ArrivalTime, + true, + IGPlus_SnapReplicatedDataPrev.bBasedOnMover, + IGPlus_SnapReplicatedDataPrev.MoverLocation, + IGPlus_SnapReplicatedDataPrev.BaseMover, + false); + } + + IGPlus_SnapInterp_LastCounter = RepCounter; + IGPlus_SnapInterp_StatSnapPackets += 1; + IGPlus_SnapInterp_Push( + IGPlus_SnapReplicatedData.Location, + IGPlus_SnapReplicatedData.Velocity, + IGPlus_SnapReplicatedData.Rotation, + IGPlus_SnapReplicatedData.AnimSequence, + IGPlus_SnapReplicatedData.AnimFrameByte, + IGPlus_SnapReplicatedData.AnimIntentBits, + IGPlus_SnapReplicatedData.AnimDodgeDir, + IGPlus_SnapReplicatedData.AnimPhysics, + SnapshotTime, + ArrivalTime, + true, + IGPlus_SnapReplicatedData.bBasedOnMover, + IGPlus_SnapReplicatedData.MoverLocation, + IGPlus_SnapReplicatedData.BaseMover, + true); + } + + if (IGPlus_SnapInterp_Count >= 2) { + RenderTime = Level.TimeSeconds - IGPlus_SnapInterp_InterpDelay; + bHasInterpolated = IGPlus_SnapInterp_Interpolate( + RenderTime, + OutPos, + OutVel, + OutRot, + OutAnimSeq, + OutAnimFrameByte, + OutAnimIntentBits, + OutAnimDodgeDir, + OutAnimPhysics, + OutHintTime, + InterpMode + ); + if (bHasInterpolated) { + IGPlus_SnapInterp_LastInterpMode = InterpMode; + HintAgeSec = FMax(0.0, Level.TimeSeconds - OutHintTime); + IGPlus_SnapInterp_LastHintAgeMs = int(HintAgeSec * 1000.0 + 0.5); + if (InterpMode == 1) + IGPlus_SnapInterp_StatInterp += 1; + else if (InterpMode == 2) { + IGPlus_SnapInterp_StatExtrap += 1; + IGPlus_SnapInterp_BoostDelayForStress(); + } else if (InterpMode == 3) { + IGPlus_SnapInterp_StatClamp += 1; + IGPlus_SnapInterp_BoostDelayForStress(); + } + } else { + IGPlus_SnapInterp_StatFail += 1; + IGPlus_SnapInterp_BoostDelayForStress(); + } + } + + if (IGPlus_LocationOffsetFix_Moved == false) + return; + + if (IGPlus_LocationOffsetFix_CollisionDummy != none) { + IGPlus_LocationOffsetFix_CollisionDummy.bCollideWorld = false; + IGPlus_LocationOffsetFix_CollisionDummy.SetCollision(false, false, false); + } + + if (IGPlus_SnapInterp_Count < 2) { + if (bReplicatedLocation) { + bCollideWorld = false; + SetLocation(IGPlus_SnapReplicatedData.Location); + bCollideWorld = true; + OutRot = IGPlus_SnapReplicatedData.Rotation; + OutRot.Roll = 0; + SetRotation(OutRot); + DesiredRotation = OutRot; + InterpMode = 2; + HintAgeSec = 0.0; + IGPlus_SnapInterp_LastInterpMode = InterpMode; + IGPlus_SnapInterp_LastHintAgeMs = 0; + bAppliedIntentHint = IGPlus_SnapInterp_ApplyIntentHint( + IGPlus_SnapReplicatedData.AnimIntentBits, + IGPlus_SnapReplicatedData.AnimDodgeDir, + IGPlus_SnapReplicatedData.AnimPhysics, + InterpMode, + HintAgeSec + ); + if (bAppliedIntentHint == false) { + bAppliedAnimHint = IGPlus_SnapInterp_ApplyAnimHint( + IGPlus_SnapReplicatedData.AnimSequence, + IGPlus_SnapReplicatedData.AnimFrameByte, + InterpMode, + HintAgeSec + ); + } + if (bAppliedIntentHint) + IGPlus_SnapInterp_LastHintSource = "intent"; + else if (bAppliedAnimHint) + IGPlus_SnapInterp_LastHintSource = "seq"; + } else { + bCollideWorld = false; + SetLocation(IGPlus_LocationOffsetFix_OldLocation); + bCollideWorld = true; + } + IGPlus_LocationOffsetFix_Moved = false; + return; + } + + if (bHasInterpolated) { + bCollideWorld = false; + SetLocation(OutPos); + bCollideWorld = true; + SetRotation(OutRot); + DesiredRotation = OutRot; + Velocity = OutVel; + bAppliedIntentHint = IGPlus_SnapInterp_ApplyIntentHint( + OutAnimIntentBits, + OutAnimDodgeDir, + OutAnimPhysics, + InterpMode, + HintAgeSec + ); + if (bAppliedIntentHint == false) { + bAppliedAnimHint = IGPlus_SnapInterp_ApplyAnimHint( + OutAnimSeq, + OutAnimFrameByte, + InterpMode, + HintAgeSec + ); + } + if (bAppliedIntentHint) + IGPlus_SnapInterp_LastHintSource = "intent"; + else if (bAppliedAnimHint) + IGPlus_SnapInterp_LastHintSource = "seq"; + } else { + bCollideWorld = false; + SetLocation(IGPlus_LocationOffsetFix_OldLocation); + bCollideWorld = true; + } + + // Floor-snap: SetLocation with bCollideWorld=false bypasses UE1 floor tracing. + // Use an extent trace so slope/ramp contact uses cylinder geometry and + // returns a center position we can snap to directly. + // Do not floor-snap while moving upward, otherwise jump ascent can look like + // discrete vertical snapping for simulated proxies. + if (!bCanFly && !Region.Zone.bWaterZone && Velocity.Z <= 0.0) { + TraceExtent.X = CollisionRadius; + TraceExtent.Y = CollisionRadius; + TraceExtent.Z = CollisionHeight; + FloorActor = Trace(FloorHitLoc, FloorHitNorm, + Location - vect(0,0,1) * (CollisionHeight + MaxStepHeight + 2), + Location + vect(0,0,1) * (MaxStepHeight + 2), + false, + TraceExtent); + if (FloorActor != None && FloorHitNorm.Z >= 0.7 && Mover(FloorActor) == None) { + // Preserve interpolated X/Y motion. Only correct Z when the proxy + // is slightly below the floor to avoid movement snapping. + if (FloorHitLoc.Z > Location.Z + 0.01 && FloorHitLoc.Z - Location.Z <= MaxStepHeight + 2) { + FloorSnapLoc = Location; + FloorSnapLoc.Z = FloorHitLoc.Z; + bCollideWorld = false; + SetLocation(FloorSnapLoc); + bCollideWorld = true; + } + } + } + + IGPlus_LocationOffsetFix_Moved = false; + + if (IGPlus_LocationOffsetFix_FootstepQueued) { + FootStepping(); + IGPlus_LocationOffsetFix_FootstepQueued = false; + } +} + simulated function bool IGPlus_LocationOffsetFix_IsOnMover() { local Actor HitActor; local vector HitLocation; @@ -8714,7 +10085,9 @@ function IGPlus_LocationOffsetFix_TickBefore() { if (P.bDeleteMe) continue; if (P.Role != ROLE_SimulatedProxy) continue; - if (Settings.bEnableLocationOffsetFix) + if (IGPlus_ShouldUseSnapshotInterpForProxy(P)) + P.IGPlus_LocationOffsetFix_Before(); + else if (Settings.bEnableLocationOffsetFix) P.IGPlus_LocationOffsetFix_Before(); else if (P.IGPlus_LocationOffsetFix_ShouldRunMoverOnly()) P.IGPlus_LocationOffsetFix_Before(); @@ -10530,6 +11903,575 @@ exec function PrintWeaponState() { if (Weapon != none) ClientMessage(Weapon.Name@Weapon.GetStateName()); } +exec function SnapInterpDebug() { + IGPlus_SnapInterp_NetDebug = !IGPlus_SnapInterp_NetDebug; + IGPlus_SnapInterp_NetDebugNextTime = 0; + + if (IGPlus_SnapInterp_NetDebug) + ClientMessage("SnapInterp debug enabled (client report + server echo + proxy status)"); + else + ClientMessage("SnapInterp debug disabled"); +} + +simulated function bbPlayer IGPlus_SnapInterp_FindTarget(optional string TargetFilter) { + local int i; + local PlayerReplicationInfo PRI; + local bbPlayer P; + local bbPlayer Target; + local string FilterCaps; + + if (TargetFilter != "") + FilterCaps = Caps(TargetFilter); + + P = bbPlayer(ViewTarget); + if (P != none && + P.Role == ROLE_SimulatedProxy && + (FilterCaps == "" || (P.PlayerReplicationInfo != none && InStr(Caps(P.PlayerReplicationInfo.PlayerName), FilterCaps) >= 0)) + ) { + Target = P; + } + + if (Target == none && GameReplicationInfo != None && PlayerReplicationInfo != None) { + for (i = 0; i < arraycount(GameReplicationInfo.PRIArray); ++i) { + PRI = GameReplicationInfo.PRIArray[i]; + if (PRI == none) + break; + P = bbPlayer(PRI.Owner); + if (P == none) + continue; + if (P == self) + continue; + if (P.Role != ROLE_SimulatedProxy) + continue; + if (FilterCaps != "" && (P.PlayerReplicationInfo == none || InStr(Caps(P.PlayerReplicationInfo.PlayerName), FilterCaps) < 0)) + continue; + Target = P; + break; + } + } + + return Target; +} + +simulated function string IGPlus_SnapInterp_NormalizeCheckProfile(optional string Profile) { + if (Profile ~= "lossy" || Profile ~= "loss" || Profile ~= "jitter") + return "lossy"; + + return "clean"; +} + +simulated function IGPlus_SnapInterp_GetCheckThresholds( + string Profile, + out float MinInterpPct, + out float MaxExtrapPct, + out float MaxClampPct, + out float MaxFailPct, + out float MaxHardSnapPerMin +) { + if (Profile ~= "lossy") { + MinInterpPct = 80.0; + MaxExtrapPct = 15.0; + MaxClampPct = 10.0; + MaxFailPct = 2.0; + MaxHardSnapPerMin = 3.0; + } else { + MinInterpPct = 95.0; + MaxExtrapPct = 4.0; + MaxClampPct = 3.0; + MaxFailPct = 0.5; + MaxHardSnapPerMin = 1.0; + } +} + +simulated function IGPlus_SnapInterp_FinishCheck(bool bStopped) { + local bbPlayer Target; + local string TargetName; + local float DurationSec; + local int DeltaSnapPackets; + local int DeltaInterp; + local int DeltaExtrap; + local int DeltaClamp; + local int DeltaFail; + local int DeltaHardSnap; + local int TotalModes; + local float InterpPct; + local float ExtrapPct; + local float ClampPct; + local float FailPct; + local float HardSnapPerMin; + local float MinInterpPct; + local float MaxExtrapPct; + local float MaxClampPct; + local float MaxFailPct; + local float MaxHardSnapPerMin; + local bool bPass; + local string FailReasons; + + if (IGPlus_SnapInterp_CheckActive == false) + return; + + IGPlus_SnapInterp_CheckActive = false; + Target = IGPlus_SnapInterp_CheckTarget; + + if (bStopped) { + ClientMessage("SnapInterpCheck stopped"); + return; + } + + if (Target == none || Target.bDeleteMe) { + ClientMessage("SnapInterpCheck aborted: target unavailable"); + return; + } + + TargetName = "Unknown"; + if (Target.PlayerReplicationInfo != none) + TargetName = Target.PlayerReplicationInfo.PlayerName; + + DurationSec = FMax(IGPlus_SnapInterp_CheckDuration, 0.001); + DeltaSnapPackets = Max(0, Target.IGPlus_SnapInterp_StatSnapPackets - IGPlus_SnapInterp_CheckBaseSnapPackets); + DeltaInterp = Max(0, Target.IGPlus_SnapInterp_StatInterp - IGPlus_SnapInterp_CheckBaseInterp); + DeltaExtrap = Max(0, Target.IGPlus_SnapInterp_StatExtrap - IGPlus_SnapInterp_CheckBaseExtrap); + DeltaClamp = Max(0, Target.IGPlus_SnapInterp_StatClamp - IGPlus_SnapInterp_CheckBaseClamp); + DeltaFail = Max(0, Target.IGPlus_SnapInterp_StatFail - IGPlus_SnapInterp_CheckBaseFail); + DeltaHardSnap = Max(0, Target.IGPlus_SnapInterp_StatHardSnap - IGPlus_SnapInterp_CheckBaseHardSnap); + TotalModes = DeltaInterp + DeltaExtrap + DeltaClamp + DeltaFail; + + if (TotalModes > 0) { + InterpPct = 100.0 * float(DeltaInterp) / float(TotalModes); + ExtrapPct = 100.0 * float(DeltaExtrap) / float(TotalModes); + ClampPct = 100.0 * float(DeltaClamp) / float(TotalModes); + FailPct = 100.0 * float(DeltaFail) / float(TotalModes); + } else { + InterpPct = 0.0; + ExtrapPct = 0.0; + ClampPct = 0.0; + FailPct = 0.0; + } + + HardSnapPerMin = 60.0 * float(DeltaHardSnap) / DurationSec; + IGPlus_SnapInterp_GetCheckThresholds( + IGPlus_SnapInterp_CheckProfile, + MinInterpPct, + MaxExtrapPct, + MaxClampPct, + MaxFailPct, + MaxHardSnapPerMin + ); + + bPass = true; + if (DeltaSnapPackets <= 0 || TotalModes <= 0) { + bPass = false; + FailReasons = "no_samples"; + } + if (InterpPct < MinInterpPct) { + bPass = false; + if (FailReasons != "") + FailReasons = FailReasons$","; + FailReasons = FailReasons$"interp"; + } + if (ExtrapPct > MaxExtrapPct) { + bPass = false; + if (FailReasons != "") + FailReasons = FailReasons$","; + FailReasons = FailReasons$"extrap"; + } + if (ClampPct > MaxClampPct) { + bPass = false; + if (FailReasons != "") + FailReasons = FailReasons$","; + FailReasons = FailReasons$"clamp"; + } + if (FailPct > MaxFailPct) { + bPass = false; + if (FailReasons != "") + FailReasons = FailReasons$","; + FailReasons = FailReasons$"fail"; + } + if (HardSnapPerMin > MaxHardSnapPerMin) { + bPass = false; + if (FailReasons != "") + FailReasons = FailReasons$","; + FailReasons = FailReasons$"hardsnap"; + } + + ClientMessage( + "SnapInterpCheck result target="$TargetName$ + " profile="$IGPlus_SnapInterp_CheckProfile$ + " durS="$int(DurationSec + 0.5)$ + " packets="$DeltaSnapPackets$ + " interpPct="$int(InterpPct + 0.5)$ + " extrapPct="$int(ExtrapPct + 0.5)$ + " clampPct="$int(ClampPct + 0.5)$ + " failPct="$int(FailPct + 0.5)$ + " hardPerMin="$int(HardSnapPerMin + 0.5)$ + " pass="$bPass + ); + if (bPass == false) + ClientMessage("SnapInterpCheck thresholds failed: "$FailReasons); +} + +exec simulated function SnapInterpCheck(optional string Profile, optional float DurationSeconds, optional string TargetFilter) { + local bbPlayer Target; + local string TargetName; + + if (DurationSeconds <= 0.0) + DurationSeconds = 30.0; + DurationSeconds = FClamp(DurationSeconds, 5.0, 180.0); + Profile = IGPlus_SnapInterp_NormalizeCheckProfile(Profile); + + Target = IGPlus_SnapInterp_FindTarget(TargetFilter); + if (Target == none) { + ClientMessage("SnapInterpCheck: no simulated proxy target found"); + return; + } + + if (Target.PlayerReplicationInfo != none) + TargetName = Target.PlayerReplicationInfo.PlayerName; + else + TargetName = "Unknown"; + + if (IGPlus_SnapInterp_CheckActive) + IGPlus_SnapInterp_FinishCheck(true); + + IGPlus_SnapInterp_CheckActive = true; + IGPlus_SnapInterp_CheckDuration = DurationSeconds; + IGPlus_SnapInterp_CheckEndTime = Level.TimeSeconds + DurationSeconds; + IGPlus_SnapInterp_CheckProfile = Profile; + IGPlus_SnapInterp_CheckTarget = Target; + IGPlus_SnapInterp_CheckBaseSnapPackets = Target.IGPlus_SnapInterp_StatSnapPackets; + IGPlus_SnapInterp_CheckBaseInterp = Target.IGPlus_SnapInterp_StatInterp; + IGPlus_SnapInterp_CheckBaseExtrap = Target.IGPlus_SnapInterp_StatExtrap; + IGPlus_SnapInterp_CheckBaseClamp = Target.IGPlus_SnapInterp_StatClamp; + IGPlus_SnapInterp_CheckBaseFail = Target.IGPlus_SnapInterp_StatFail; + IGPlus_SnapInterp_CheckBaseHardSnap = Target.IGPlus_SnapInterp_StatHardSnap; + + ClientMessage( + "SnapInterpCheck start target="$TargetName$ + " profile="$Profile$ + " durS="$int(DurationSeconds + 0.5) + ); +} + +exec simulated function SnapInterpCheckStop() { + if (IGPlus_SnapInterp_CheckActive == false) { + ClientMessage("SnapInterpCheck is not running"); + return; + } + + IGPlus_SnapInterp_FinishCheck(true); +} + +exec simulated function SnapInterpStatus(optional string TargetFilter) { + local bbPlayer Target; + local string TargetName; + local string DebugMode; + + Target = IGPlus_SnapInterp_FindTarget(TargetFilter); + if (Target == none) { + ClientMessage("SnapInterp status: no simulated proxy target found"); + return; + } + + TargetName = "Unknown"; + if (Target.PlayerReplicationInfo != none) + TargetName = Target.PlayerReplicationInfo.PlayerName; + + if (ViewTarget == Target) + DebugMode = "spectate"; + else + DebugMode = "play"; + + ClientMessage( + "SnapInterp status target="$TargetName$ + " mode="$DebugMode$ + " snapEnabled="$Target.IGPlus_EnableSnapshotInterpolation$ + " count="$Target.IGPlus_SnapInterp_Count$ + " rep="$Target.IGPlus_SnapReplicatedData.Counter$ + " last="$Target.IGPlus_SnapInterp_LastCounter$ + " moved="$Target.IGPlus_LocationOffsetFix_Moved$ + " delayMs="$int(Target.IGPlus_SnapInterp_InterpDelay * 1000.0)$ + " interpMode="$Target.IGPlus_SnapInterp_LastInterpMode$ + " hint="$Target.IGPlus_SnapInterp_LastHintSource$ + " hintAgeMs="$Target.IGPlus_SnapInterp_LastHintAgeMs + ); +} + +exec function bEnableLoosePositionCheck(optional string Value) { + IGPlus_SetNetcodeSetting("bEnableLoosePositionCheck", Value); +} + +exec function LooseCheckCorrectionFactor(optional string Value) { + IGPlus_SetNetcodeSetting("LooseCheckCorrectionFactor", Value); +} + +exec function LooseCheckCorrectionFactorOnMover(optional string Value) { + IGPlus_SetNetcodeSetting("LooseCheckCorrectionFactorOnMover", Value); +} + +exec function bEnableInputReplication(optional string Value) { + IGPlus_SetNetcodeSetting("bEnableInputReplication", Value); +} + +exec function bEnableSnapshotInterpolation(optional string Value) { + IGPlus_SetNetcodeSetting("bEnableSnapshotInterpolation", Value); +} + +exec function SnapshotInterpSendHz(optional string Value) { + IGPlus_SetNetcodeSetting("SnapshotInterpSendHz", Value); +} + +exec function SnapshotInterpRewindMs(optional string Value) { + IGPlus_SetNetcodeSetting("SnapshotInterpRewindMs", Value); +} + +exec function MaxJitterTime(optional string Value) { + IGPlus_SetNetcodeSetting("MaxJitterTime", Value); +} + +exec function bEnableJitterBounding(optional string Value) { + IGPlus_SetNetcodeSetting("bEnableJitterBounding", Value); +} + +exec function NetcodeHelp() { + if (Role < ROLE_Authority) { + IGPlus_ServerNetcodeHelp(); + return; + } + + IGPlus_PrintNetcodeHelp(); +} + +function IGPlus_ServerNetcodeHelp() { + IGPlus_PrintNetcodeHelp(); +} + +function IGPlus_PrintNetcodeHelp() { + local ServerSettings S; + + S = None; + if (zzUTPure != None) + S = zzUTPure.Settings; + + ClientMessage("Netcode settings (use ' '):"); + if (S == None) { + ClientMessage("Settings unavailable"); + return; + } + + ClientMessage("bEnableLoosePositionCheck="$S.GetPropertyText("bEnableLoosePositionCheck")); + ClientMessage("LooseCheckCorrectionFactor="$S.GetPropertyText("LooseCheckCorrectionFactor")); + ClientMessage("LooseCheckCorrectionFactorOnMover="$S.GetPropertyText("LooseCheckCorrectionFactorOnMover")); + ClientMessage("bEnableInputReplication="$S.GetPropertyText("bEnableInputReplication")); + ClientMessage("bEnableSnapshotInterpolation="$S.GetPropertyText("bEnableSnapshotInterpolation")); + ClientMessage("SnapshotInterpSendHz="$S.GetPropertyText("SnapshotInterpSendHz")); + ClientMessage("SnapshotInterpRewindMs="$S.GetPropertyText("SnapshotInterpRewindMs")); + ClientMessage("MaxJitterTime="$S.GetPropertyText("MaxJitterTime")); + ClientMessage("bEnableJitterBounding="$S.GetPropertyText("bEnableJitterBounding")); + ClientMessage("SnapInterpDebug (toggle client report + server echo + proxy status)"); + ClientMessage("SnapInterpStatus [NameFilter] (one-shot proxy runtime status)"); + ClientMessage("SnapInterpNetStatus [NameFilter] (client report + server echo + proxy status)"); + ClientMessage("SnapInterpCheck [clean|lossy] [DurationSec] [NameFilter]"); + ClientMessage("SnapInterpCheckStop"); +} + +exec function IGPlus_SetNetcodeSetting(string Key, optional string Value) { + if (Role < ROLE_Authority) { + IGPlus_ServerSetNetcodeSetting(Key, Value); + return; + } + + if (bAdmin == false) { + ClientMessage("Admin only"); + return; + } + + IGPlus_ApplyNetcodeSetting(Key, Value); +} + +function IGPlus_ServerSetNetcodeSetting(string Key, string Value) { + if (bAdmin == false) { + ClientMessage("Admin only"); + return; + } + + IGPlus_ApplyNetcodeSetting(Key, Value); +} + +function string IGPlus_NormalizeNetcodeKey(string Key) { + if (Key ~= "bEnableLoosePositionCheck") return "bEnableLoosePositionCheck"; + if (Key ~= "LooseCheckCorrectionFactor") return "LooseCheckCorrectionFactor"; + if (Key ~= "LooseCheckCorrectionFactorOnMover") return "LooseCheckCorrectionFactorOnMover"; + if (Key ~= "bEnableInputReplication") return "bEnableInputReplication"; + if (Key ~= "bEnableSnapshotInterpolation") return "bEnableSnapshotInterpolation"; + if (Key ~= "SnapshotInterpSendHz") return "SnapshotInterpSendHz"; + if (Key ~= "SnapshotInterpRewindMs") return "SnapshotInterpRewindMs"; + if (Key ~= "MaxJitterTime") return "MaxJitterTime"; + if (Key ~= "bEnableJitterBounding") return "bEnableJitterBounding"; + return ""; +} + +function IGPlus_ApplyNetcodeSettingsToPlayers() { + local Pawn P; + local bbPlayer BP; + local bool bOldSnap; + + for (P = Level.PawnList; P != None; P = P.NextPawn) { + BP = bbPlayer(P); + if (BP == None) + continue; + + BP.IGPlus_EnableInputReplication = zzUTPure.Settings.bEnableInputReplication; + bOldSnap = BP.IGPlus_EnableSnapshotInterpolation; + BP.IGPlus_EnableSnapshotInterpolation = zzUTPure.Settings.bEnableSnapshotInterpolation; + + if (bOldSnap != BP.IGPlus_EnableSnapshotInterpolation) { + BP.IGPlus_SnapInterp_ServerNextTime = 0; + BP.IGPlus_SnapInterp_ServerHasLast = false; + BP.IGPlus_SnapInterp_CheckActive = false; + BP.IGPlus_ServerSnapInterpDelayMsSmoothed = 0.0; + BP.IGPlus_ServerSnapInterpDelayValid = false; + BP.IGPlus_ServerSnapInterpTrusted = false; + BP.IGPlus_ServerSnapInterpLastAcceptedTime = 0.0; + BP.IGPlus_NextSnapInterpDelayReportTime = 0.0; + BP.IGPlus_NextSnapInterpDelayClientReportTime = 0.0; + BP.IGPlus_ClientSnapInterpDelayMs = -1; + BP.IGPlus_ClientSnapInterpDelaySampleCount = 0; + BP.IGPlus_ClientSnapInterpDelayMedianMs = -1; + BP.IGPlus_ClientSnapInterpDelaySpreadMs = -1; + BP.IGPlus_ServerSnapInterpDelayReportedMsEcho = -1; + BP.IGPlus_ServerSnapInterpDelayClampedMsEcho = -1; + BP.IGPlus_ServerSnapInterpDelaySmoothedMsEcho = 0; + BP.IGPlus_ServerSnapInterpDelayValidEcho = false; + BP.IGPlus_ServerSnapInterpTrustedEcho = false; + BP.IGPlus_ServerSnapInterpDelayEchoTime = 0.0; + BP.IGPlus_ServerSnapInterpLastAcceptedTimeEcho = 0.0; + } + } +} + +function IGPlus_ApplyNetcodeSetting(string Key, string Value) { + local string NormalKey; + + if (zzUTPure == None || zzUTPure.Settings == None) { + ClientMessage("No server settings loaded"); + return; + } + + NormalKey = IGPlus_NormalizeNetcodeKey(Key); + if (NormalKey == "") { + ClientMessage("Unknown netcode setting: " $ Key); + return; + } + + if (Value == "") { + ClientMessage(NormalKey $ "=" $ zzUTPure.Settings.GetPropertyText(NormalKey)); + return; + } + + zzUTPure.Settings.SetPropertyText(NormalKey, Value); + zzUTPure.Settings.SaveConfig(); + IGPlus_ApplyNetcodeSettingsToPlayers(); + ClientMessage(NormalKey $ "=" $ zzUTPure.Settings.GetPropertyText(NormalKey)); +} + +simulated function ServerSettings IGPlus_GetServerSettingsObject() { + if (IGPlus_ServerSettingsMenuData != none) + return IGPlus_ServerSettingsMenuData; + + IGPlus_ServerSettingsHelper = new(none, 'InstaGibPlus') class'Object'; + IGPlus_ServerSettingsMenuData = new(IGPlus_ServerSettingsHelper, 'IGPlus_ServerSettingsMenu') class'ServerSettings'; + return IGPlus_ServerSettingsMenuData; +} + +function IGPlus_ServerSettingsInit() { + IGPlus_ServerSettingsMenuLoaded = false; + IGPlus_ServerSettingsMenuCanEdit = false; + IGPlus_ServerSettingsMenuData = none; +} + +function IGPlus_ServerSettingsSet(string Key, string Value) { + local ServerSettings S; + + S = IGPlus_GetServerSettingsObject(); + if (S == none) + return; + + S.SetPropertyText(Key, Value); +} + +function IGPlus_ServerSettingsDone(bool bCanEdit) { + IGPlus_ServerSettingsMenuLoaded = true; + IGPlus_ServerSettingsMenuCanEdit = bCanEdit; +} + +function IGPlus_ServerSendSetting(string Key) { + if (zzUTPure == none || zzUTPure.Settings == none) + return; + + IGPlus_ServerSettingsSet(Key, zzUTPure.Settings.GetPropertyText(Key)); +} + +function IGPlus_ServerRequestSettings() { + if (bAdmin == false || zzUTPure == none || zzUTPure.Settings == none) { + IGPlus_ServerSettingsInit(); + IGPlus_ServerSettingsDone(false); + return; + } + + IGPlus_ServerSettingsInit(); + + IGPlus_ServerSendSetting("bAutoPause"); + IGPlus_ServerSendSetting("PauseTotalTime"); + IGPlus_ServerSendSetting("PauseTime"); + IGPlus_ServerSendSetting("bForceDemo"); + IGPlus_ServerSendSetting("bRestrictTrading"); + IGPlus_ServerSendSetting("MaxTradeTimeMargin"); + IGPlus_ServerSendSetting("TradePingMargin"); + IGPlus_ServerSendSetting("KillCamDelay"); + IGPlus_ServerSendSetting("KillCamDuration"); + + IGPlus_ServerSendSetting("bJumpingPreservesMomentum"); + IGPlus_ServerSendSetting("bOldLandingMomentum"); + IGPlus_ServerSendSetting("bEnableSingleButtonDodge"); + IGPlus_ServerSendSetting("bUseFlipAnimation"); + IGPlus_ServerSendSetting("bEnableWallDodging"); + IGPlus_ServerSendSetting("bDodgePreserveZMomentum"); + IGPlus_ServerSendSetting("MaxMultiDodges"); + IGPlus_ServerSendSetting("BrightskinMode"); + IGPlus_ServerSendSetting("PlayerScale"); + IGPlus_ServerSendSetting("bAlwaysRenderFlagCarrier"); + IGPlus_ServerSendSetting("bAlwaysRenderDroppedFlags"); + + IGPlus_ServerSendSetting("MaxPosError"); + IGPlus_ServerSendSetting("MaxHitError"); + IGPlus_ServerSendSetting("MaxJitterTime"); + IGPlus_ServerSendSetting("WarpFixDelay"); + IGPlus_ServerSendSetting("FireTimeout"); + IGPlus_ServerSendSetting("MinNetUpdateRate"); + IGPlus_ServerSendSetting("MaxNetUpdateRate"); + IGPlus_ServerSendSetting("bEnableInputReplication"); + IGPlus_ServerSendSetting("bEnableServerExtrapolation"); + IGPlus_ServerSendSetting("bEnableServerPacketReordering"); + IGPlus_ServerSendSetting("bEnableLoosePositionCheck"); + IGPlus_ServerSendSetting("bPlayersAlwaysRelevant"); + IGPlus_ServerSendSetting("bEnablePingCompensatedSpawn"); + IGPlus_ServerSendSetting("bEnableJitterBounding"); + IGPlus_ServerSendSetting("LooseCheckCorrectionFactor"); + IGPlus_ServerSendSetting("LooseCheckCorrectionFactorOnMover"); + IGPlus_ServerSendSetting("bEnableSnapshotInterpolation"); + IGPlus_ServerSendSetting("SnapshotInterpSendHz"); + IGPlus_ServerSendSetting("SnapshotInterpRewindMs"); + IGPlus_ServerSendSetting("bEnableWarpFix"); + + IGPlus_ServerSendSetting("ShowTouchedPackage"); + IGPlus_ServerSendSetting("HitFeedbackMode"); + IGPlus_ServerSendSetting("bEnableDamageDebugMode"); + IGPlus_ServerSendSetting("bEnableDamageDebugConsoleMessages"); + IGPlus_ServerSendSetting("bEnableHitboxDebugMode"); + + IGPlus_ServerSettingsDone(true); +} + exec function DrawServerLocation() { IGPlus_LocationOffsetFix_DrawServerLocation = !IGPlus_LocationOffsetFix_DrawServerLocation; } @@ -10591,4 +12533,8 @@ defaultproperties IGPlus_LocationOffsetFix_PredCompatMode=True IGPlus_EnableInputReplication=True -} \ No newline at end of file + IGPlus_ClientSnapInterpDelayMs=-1 + IGPlus_ClientSnapInterpDelayMedianMs=-1 + IGPlus_ServerSnapInterpDelayReportedMsEcho=-1 + IGPlus_ServerSnapInterpDelayClampedMsEcho=-1 +} diff --git a/README.md b/README.md index 636fd2e..e71a907 100644 --- a/README.md +++ b/README.md @@ -27,89 +27,102 @@ An InstaGib focussed fork of TimTim's NewNet mutator for Unreal Tournament. These settings can be found in **InstaGibPlus.ini** under section **\[ClientSettings\]**. 1. [bForceModels](#bforcemodels) -2. [DesiredSkin](#desiredskin) -3. [DesiredSkinFemale](#desiredskinfemale) -4. [DesiredTeamSkin](#desiredteamskin) -5. [DesiredTeamSkinFemale](#desiredteamskinfemale) -6. [SkinEnemyIndexMap](#skinenemyindexmap) -7. [SkinTeamIndexMap](#skinteamindexmap) -8. [bSkinEnemyUseIndexMap](#bskinenemyuseindexmap) -9. [bSkinTeamUseIndexMap](#bskinteamuseindexmap) -10. [bUnlitSkins](#bunlitskins) -11. [HitSound](#hitsound) -12. [TeamHitSound](#teamhitsound) -13. [bDisableForceHitSounds](#bdisableforcehitsounds) -14. [bEnableHitSounds](#benablehitsounds) -15. [bEnableTeamHitSounds](#benableteamhitsounds) -16. [bHitSoundPitchShift](#bhitsoundpitchshift) -17. [bHitSoundTeamPitchShift](#bhitsoundteampitchshift) -18. [HitSoundSource](#hitsoundsource) -19. [SelectedHitSound](#selectedhitsound) -20. [SelectedTeamHitSound](#selectedteamhitsound) -21. [HitSoundVolume](#hitsoundvolume) -22. [HitSoundTeamVolume](#hitsoundteamvolume) -23. [sHitSound](#shitsound) -24. [bDoEndShot](#bdoendshot) -25. [bAutoDemo](#bautodemo) -26. [DemoMask](#demomask) -27. [DemoPath](#demopath) -28. [DemoChar](#demochar) -29. [bTeamInfo](#bteaminfo) -30. [bShootDead](#bshootdead) -31. [cShockBeam](#cshockbeam) -32. [bHideOwnBeam](#bhideownbeam) -33. [BeamScale](#beamscale) -34. [BeamFadeCurve](#beamfadecurve) -35. [BeamDuration](#beamduration) -36. [BeamOriginMode](#beamoriginmode) -37. [BeamDestinationMode](#beamdestinationmode) -38. [SSRRingType](#ssrringtype) -39. [bNoOwnFootsteps](#bnoownfootsteps) -40. [DesiredNetUpdateRate](#desirednetupdaterate) -41. [DesiredNetspeed](#desirednetspeed) -42. [FakeCAPInterval](#fakecapinterval) -43. [bNoSmoothing](#bnosmoothing) -44. [bLogClientMessages](#blogclientmessages) -45. [bDebugMovement](#bdebugmovement) -46. [bEnableKillCam](#benablekillcam) -47. [MinDodgeClickTime](#mindodgeclicktime) -48. [bUseOldMouseInput](#buseoldmouseinput) -49. [SmoothVRController](#smoothvrcontroller) -50. [bShowFPS](#bshowfps) -51. [FPSLocationX](#fpslocationx) -52. [FPSLocationY](#fpslocationy) -53. [FPSDetail](#fpsdetail) -54. [FPSCounterSmoothingStrength](#fpscountersmoothingstrength) -55. [KillCamMinDelay](#killcammindelay) -56. [bAllowWeaponShake](#ballowweaponshake) -57. [bAutoReady](#bautoready) -58. [bShowDeathReport](#bshowdeathreport) -59. [bSmoothFOVChanges](#bsmoothfovchanges) -60. [bEnableKillFeed](#benablekillfeed) -61. [KillFeedX](#killfeedx) -62. [KillFeedY](#killfeedy) -63. [KillFeedSpeed](#killfeedspeed) -64. [KillFeedScale](#killfeedscale) -65. [FraggerScopeChoice](#fraggerscopechoice) -66. [bEnableNetStats](#benablenetstats) -67. [bNetStatsUnconfirmedTime](#bnetstatsunconfirmedtime) -68. [bNetStatsLocationError](#bnetstatslocationerror) -69. [bNetStatsFrameTime](#bnetstatsframetime) -69. [NetStatsLocationX](#netstatslocationx) -70. [NetStatsLocationY](#netstatslocationy) -71. [NetStatsWidth](#netstatswidth) -72. [bEnableHitMarker](#benablehitmarker) -73. [bEnableTeamHitMarker](#benableteamhitmarker) -74. [HitMarkerColorMode](#hitmarkercolormode) -75. [HitMarkerColor](#hitmarkercolor) -76. [HitMarkerTeamColor](#hitmarkerteamcolor) -77. [HitMarkerSize](#hitmarkersize) -78. [HitMarkerOffset](#hitmarkeroffset) -79. [HitMarkerDuration](#hitmarkerduration) -70. [HitMarkerDecayExponent](#hitmarkerdecayexponent) -81. [HitMarkerSource](#hitmarkersource) -82. [bUseCrosshairFactory](#busecrosshairfactory) -83. [CrosshairLayers](#crosshairlayers) +1. [DesiredSkin](#desiredskin) +1. [DesiredSkinFemale](#desiredskinfemale) +1. [DesiredTeamSkin](#desiredteamskin) +1. [DesiredTeamSkinFemale](#desiredteamskinfemale) +1. [SkinEnemyIndexMap](#skinenemyindexmap) +1. [SkinTeamIndexMap](#skinteamindexmap) +1. [bSkinEnemyUseIndexMap](#bskinenemyuseindexmap) +1. [bSkinTeamUseIndexMap](#bskinteamuseindexmap) +1. [bUnlitSkins](#bunlitskins) +1. [HitSound](#hitsound) +1. [TeamHitSound](#teamhitsound) +1. [bDisableForceHitSounds](#bdisableforcehitsounds) +1. [bEnableHitSounds](#benablehitsounds) +1. [bEnableTeamHitSounds](#benableteamhitsounds) +1. [bHitSoundPitchShift](#bhitsoundpitchshift) +1. [bHitSoundTeamPitchShift](#bhitsoundteampitchshift) +1. [HitSoundSource](#hitsoundsource) +1. [SelectedHitSound](#selectedhitsound) +1. [SelectedTeamHitSound](#selectedteamhitsound) +1. [HitSoundVolume](#hitsoundvolume) +1. [HitSoundTeamVolume](#hitsoundteamvolume) +1. [sHitSound](#shitsound) +1. [bDoEndShot](#bdoendshot) +1. [bAutoDemo](#bautodemo) +1. [DemoMask](#demomask) +1. [DemoPath](#demopath) +1. [DemoChar](#demochar) +1. [bTeamInfo](#bteaminfo) +1. [bShootDead](#bshootdead) +1. [cShockBeam](#cshockbeam) +1. [bHideOwnBeam](#bhideownbeam) +1. [bBeamEnableLight](#bbeamenablelight) +1. [BeamScale](#beamscale) +1. [BeamFadeCurve](#beamfadecurve) +1. [BeamDuration](#beamduration) +1. [BeamOriginMode](#beamoriginmode) +1. [BeamDestinationMode](#beamdestinationmode) +1. [SSRRingType](#ssrringtype) +1. [bNoOwnFootsteps](#bnoownfootsteps) +1. [DesiredNetUpdateRate](#desirednetupdaterate) +1. [DesiredNetspeed](#desirednetspeed) +1. [FakeCAPInterval](#fakecapinterval) +1. [bNoSmoothing](#bnosmoothing) +1. [bLogClientMessages](#blogclientmessages) +1. [bDemoShowClientMessages](#bdemoshowclientmessages) +1. [bDebugMovement](#bdebugmovement) +1. [bEnableKillCam](#benablekillcam) +1. [MinDodgeClickTime](#mindodgeclicktime) +1. [bUseOldMouseInput](#buseoldmouseinput) +1. [SmoothVRController](#smoothvrcontroller) +1. [bShowFPS](#bshowfps) +1. [FPSLocationX](#fpslocationx) +1. [FPSLocationY](#fpslocationy) +1. [FPSDetail](#fpsdetail) +1. [FPSCounterSmoothingStrength](#fpscountersmoothingstrength) +1. [KillCamMinDelay](#killcammindelay) +1. [bReduceEyeHeightInAir](#breduceeyeheightinair) +1. [bAllowWeaponShake](#ballowweaponshake) +1. [bAutoReady](#bautoready) +1. [bShowDeathReport](#bshowdeathreport) +1. [bSmoothFOVChanges](#bsmoothfovchanges) +1. [bEnableLocationOffsetFix](#benablelocationoffsetfix) +1. [bEnableKillFeed](#benablekillfeed) +1. [KillFeedX](#killfeedx) +1. [KillFeedY](#killfeedy) +1. [KillFeedSpeed](#killfeedspeed) +1. [KillFeedScale](#killfeedscale) +1. [FraggerScopeChoice](#fraggerscopechoice) +1. [bEnableNetStats](#benablenetstats) +1. [bNetStatsUnconfirmedTime](#bnetstatsunconfirmedtime) +1. [bNetStatsLocationError](#bnetstatslocationerror) +1. [bNetStatsFrameTime](#bnetstatsframetime) +1. [NetStatsLocationX](#netstatslocationx) +1. [NetStatsLocationY](#netstatslocationy) +1. [NetStatsWidth](#netstatswidth) +1. [bEnableHitMarker](#benablehitmarker) +1. [bEnableTeamHitMarker](#benableteamhitmarker) +1. [HitMarkerColorMode](#hitmarkercolormode) +1. [HitMarkerColor](#hitmarkercolor) +1. [HitMarkerTeamColor](#hitmarkerteamcolor) +1. [HitMarkerSize](#hitmarkersize) +1. [HitMarkerOffset](#hitmarkeroffset) +1. [HitMarkerDuration](#hitmarkerduration) +1. [HitMarkerDecayExponent](#hitmarkerdecayexponent) +1. [HitMarkerSource](#hitmarkersource) +1. [bUseCrosshairFactory](#busecrosshairfactory) +1. [CrosshairLayers](#crosshairlayers) +1. [bBioUseClientSideAnimations](#bbiouseclientsideanimations) +1. [bShockBeamUseClientSideAnimations](#bshockbeamuseclientsideanimations) +1. [bShockProjectileUseClientSideAnimations](#bshockprojectileuseclientsideanimations) +1. [bPulseUseClientSideAnimations](#bpulseuseclientsideanimations) +1. [bRipperUseClientSideAnimations](#bripperuseclientsideanimations) +1. [bFlakUseClientSideAnimations](#bflakuseclientsideanimations) +1. [bRocketUseClientSideAnimations](#brocketuseclientsideanimations) +1. [bSniperUseClientSideAnimations](#bsniperuseclientsideanimations) +1. [bTranslocatorUseClientSideAnimations](#btranslocatoruseclientsideanimations) ## bForceModels **Type: bool** @@ -221,6 +234,15 @@ Whether the HitSound from hitting enemies should be pitch shifted depending on d Whether the HitSound from hitting teammates should be pitch shifted depending on damage dealt or not. +## HitSoundSource +**Type: EHitSoundSource** +**Default: HSSRC_Server** + +Selects where hit sound events come from. + +* `HSSRC_Server` ➜ Use server-side hit confirmations (accurate but delayed by ping). +* `HSSRC_Client` ➜ Use client-side hit detection (instant but can be inaccurate). + ## SelectedHitSound **Type: int** **Default: 0** @@ -324,6 +346,13 @@ The style of beam to use for the SuperShockRifle. If `True`, hides your own SuperShockRifle beams, no matter the value of [cShockBeam](#cshockbeam). +## bBeamEnableLight +**Type: bool** +**Default: True** + +If `True`, shock beams emit dynamic light. +If `False`, dynamic light from beams is disabled. + ## BeamScale **Type: float** **Default: 0.45** @@ -402,7 +431,7 @@ Higher values result in less frequent acknowledgements which can result in degra ## bNoSmoothing **Type: bool** -**Default: False** +**Default: True** The default mouse input smoothing algorithm always smears input over at least two frames, half the input being applied on one frame, the other half on the next frame. If set to `True`, the game will always apply all input on the current frame. If set to `False`, the default algorithm will be used. @@ -413,6 +442,14 @@ This is a backport from UT99 client version 469, where the equivalent setting is **Default: True** Causes all ClientMessages to be logged, if set to `True` + +## bDemoShowClientMessages +**Type: bool** +**Default: False** + +If `True`, ClientMessages are shown while playing demos. +If `False`, ClientMessages are hidden while playing demos. + ## bDebugMovement **Type: bool** **Default: False** @@ -492,6 +529,13 @@ How many samples to average FPS over. Minimum time between death and when KillCam starts rotating towards killer. +## bReduceEyeHeightInAir +**Type: bool** +**Default: False** + +If `True`, reduces eye height while in air. +If `False`, uses normal eye height while in air. + ## bAllowWeaponShake **Type: bool** **Default: True** @@ -517,6 +561,13 @@ If `True`, show a report of damage taken that lead to death. The report starts f If `True`, smooth changes to your FOV, which can happen when spawning, teleporting or zooming. If `False`, your FOV immediately changes to the desired FOV without a smooth transition. +## bEnableLocationOffsetFix +**Type: bool** +**Default: True** + +If `True`, enables client-side location offset correction to reduce visual jitter/offset artifacts. +If `False`, disables that correction. + ## bEnableKillFeed **Type: bool** **Default: True** @@ -709,6 +760,60 @@ A crosshair is made up of individual images that are drawn in a specific order p * `bSmooth` controls whether sharp edges should be smoothed out when `ScaleX` or `ScaleY` are greater than 1 * `bUse` controls whether this layer should be drawn, `True` to draw, `False` to ignore +## bBioUseClientSideAnimations +**Type: bool** +**Default: False** + +Enable client-side animation timing for Bio Rifle ping-compensated logic. + +## bShockBeamUseClientSideAnimations +**Type: bool** +**Default: False** + +Enable client-side animation timing for Shock Beam ping-compensated logic. + +## bShockProjectileUseClientSideAnimations +**Type: bool** +**Default: False** + +Enable client-side animation timing for Shock Projectile ping-compensated logic. + +## bPulseUseClientSideAnimations +**Type: bool** +**Default: False** + +Enable client-side animation timing for Pulse Gun ping-compensated logic. + +## bRipperUseClientSideAnimations +**Type: bool** +**Default: False** + +Enable client-side animation timing for Ripper ping-compensated logic. + +## bFlakUseClientSideAnimations +**Type: bool** +**Default: False** + +Enable client-side animation timing for Flak ping-compensated logic. + +## bRocketUseClientSideAnimations +**Type: bool** +**Default: False** + +Enable client-side animation timing for Rocket Launcher ping-compensated logic. + +## bSniperUseClientSideAnimations +**Type: bool** +**Default: False** + +Enable client-side animation timing for Sniper ping-compensated logic. + +## bTranslocatorUseClientSideAnimations +**Type: bool** +**Default: False** + +Enable client-side animation timing for Translocator ping-compensated logic. + # Client Commands The following commands are additions by IG+ to the standard set of commands. @@ -722,36 +827,65 @@ Types for parameters: Parameters marked `optional` do not have to be supplied. 1. [EnableDebugData](#enabledebugdata) -2. [EnableHitSound](#enablehitsound) -3. [EnableTeamHitSound](#enableteamhitsound) -4. [SetHitSound](#sethitsound) -5. [SetTeamHitSound](#setteamhitsound) -6. [ForceModels](#forcemodels) -7. [ListSkins](#listskins) -8. [SetForcedSkins](#setforcedskins) -9. [SetForcedTeamSkins](#setforcedteamskins) -10. [SetShockBeam](#setshockbeam) -11. [SetBeamScale](#setbeamscale) -12. [MyIgSettings](#myigsettings) -13. [SetNetUpdateRate](#setnetupdaterate) -14. [SetMouseSmoothing](#setmousesmoothing) -15. [SetKillCamEnabled](#setkillcamenabled) -16. [DropFlag](#dropflag) -17. [PureLogo](#purelogo) -18. [TeamInfo](#teaminfo) -19. [SetMinDodgeClickTime](#setmindodgeclicktime) -20. [mdct](#mdct) -21. [EndShot](#endshot) -22. [Hold](#hold) -23. [Go](#go) -24. [AutoDemo](#autodemo) -25. [ShootDead](#shootdead) -26. [SetDemoMask](#setdemomask) -27. [DemoStart](#demostart) -28. [ShowFPS](#showfps) -29. [ShowOwnBeam](#showownbeam) -30. [Ready](#ready) -31. [ZoomToggle](#zoomtoggle) +1. [EnableHitSound](#enablehitsound) +1. [EnableTeamHitSound](#enableteamhitsound) +1. [SetHitSound](#sethitsound) +1. [SetTeamHitSound](#setteamhitsound) +1. [ForceModels](#forcemodels) +1. [ListSkins](#listskins) +1. [SetForcedSkins](#setforcedskins) +1. [SetForcedTeamSkins](#setforcedteamskins) +1. [SetShockBeam](#setshockbeam) +1. [SetBeamScale](#setbeamscale) +1. [MyIgSettings](#myigsettings) +1. [SetNetUpdateRate](#setnetupdaterate) +1. [SetMouseSmoothing](#setmousesmoothing) +1. [SetKillCamEnabled](#setkillcamenabled) +1. [DropFlag](#dropflag) +1. [PureLogo](#purelogo) +1. [TeamInfo](#teaminfo) +1. [SetMinDodgeClickTime](#setmindodgeclicktime) +1. [mdct](#mdct) +1. [EndShot](#endshot) +1. [Hold](#hold) +1. [Go](#go) +1. [AutoDemo](#autodemo) +1. [ShootDead](#shootdead) +1. [SetDemoMask](#setdemomask) +1. [DemoStart](#demostart) +1. [ShowFPS](#showfps) +1. [ShowOwnBeam](#showownbeam) +1. [Ready](#ready) +1. [ZoomToggle](#zoomtoggle) +1. [IGPlusMenu](#igplusmenu) +1. [EnableClientSideAnimations](#enableclientsideanimations) +1. [DisableClientSideAnimations](#disableclientsideanimations) +1. [BioClientSideAnimations](#bioclientsideanimations) +1. [ShockBeamClientSideAnimations](#shockbeamclientsideanimations) +1. [ShockProjectileClientSideAnimations](#shockprojectileclientsideanimations) +1. [PulseClientSideAnimations](#pulseclientsideanimations) +1. [RipperClientSideAnimations](#ripperclientsideanimations) +1. [FlakClientSideAnimations](#flakclientsideanimations) +1. [RocketClientSideAnimations](#rocketclientsideanimations) +1. [SniperClientSideAnimations](#sniperclientsideanimations) +1. [TranslocatorClientSideAnimations](#translocatorclientsideanimations) +1. [NetcodeHelp](#netcodehelp) +1. [bEnableLoosePositionCheck](#benableloosepositioncheck-command) +1. [LooseCheckCorrectionFactor](#loosecheckcorrectionfactor-command) +1. [LooseCheckCorrectionFactorOnMover](#loosecheckcorrectionfactoronmover-command) +1. [bEnableInputReplication](#benableinputreplication-command) +1. [bEnableSnapshotInterpolation](#benablesnapshotinterpolation-command) +1. [SnapshotInterpSendHz](#snapshotinterpsendhz-command) +1. [SnapshotInterpRewindMs](#snapshotinterprewindms-command) +1. [MaxJitterTime](#maxjittertime-command) +1. [bEnableJitterBounding](#benablejitterbounding-command) +1. [SnapInterpDebug](#snapinterpdebug) +1. [SnapInterpStatus](#snapinterpstatus) +1. [SnapInterpNetStatus](#snapinterpnetstatus) +1. [SnapInterpCheck](#snapinterpcheck) +1. [SnapInterpCheckStop](#snapinterpcheckstop) +1. [TraceInput](#traceinput) +1. [ShowTickrate / ShowNetSpeeds](#showtickrate--shownetspeeds) ## EnableDebugData **Parameters: (bool b)** @@ -880,6 +1014,138 @@ If `SensitivityY` is not provided by the user, it is assumed to be the same as ` - Same sensitivity as un-zoomed: `ZoomToggle 1.0` - Half the sensitivity as un-zoomed: `ZoomToggle 0.5` +## IGPlusMenu +Opens the IG+ settings window. + +## EnableClientSideAnimations +Enables all per-weapon client-side animation toggles (unless forced by server). + +## DisableClientSideAnimations +Disables all per-weapon client-side animation toggles (unless forced by server). + +## BioClientSideAnimations +**Parameters: (bool b)** + +Enable/disable client-side animations for Bio weapon behavior. + +## ShockBeamClientSideAnimations +**Parameters: (bool b)** + +Enable/disable client-side animations for Shock Beam behavior. + +## ShockProjectileClientSideAnimations +**Parameters: (bool b)** + +Enable/disable client-side animations for Shock Projectile behavior. + +## PulseClientSideAnimations +**Parameters: (bool b)** + +Enable/disable client-side animations for Pulse behavior. + +## RipperClientSideAnimations +**Parameters: (bool b)** + +Enable/disable client-side animations for Ripper behavior. + +## FlakClientSideAnimations +**Parameters: (bool b)** + +Enable/disable client-side animations for Flak behavior. + +## RocketClientSideAnimations +**Parameters: (bool b)** + +Enable/disable client-side animations for Rocket behavior. + +## SniperClientSideAnimations +**Parameters: (bool b)** + +Enable/disable client-side animations for Sniper behavior. + +## TranslocatorClientSideAnimations +**Parameters: (bool b)** + +Enable/disable client-side animations for Translocator behavior. + +## NetcodeHelp +Prints netcode runtime settings and available netcode debug commands. + +## bEnableLoosePositionCheck (command) +**Parameters: (optional string Value)** + +Read/set server setting [bEnableLoosePositionCheck](#benableloosepositioncheck) (admin only). + +## LooseCheckCorrectionFactor (command) +**Parameters: (optional string Value)** + +Read/set server setting [LooseCheckCorrectionFactor](#loosecheckcorrectionfactor) (admin only). + +## LooseCheckCorrectionFactorOnMover (command) +**Parameters: (optional string Value)** + +Read/set server setting [LooseCheckCorrectionFactorOnMover](#loosecheckcorrectionfactoronmover) (admin only). + +## bEnableInputReplication (command) +**Parameters: (optional string Value)** + +Read/set server setting [bEnableInputReplication](#benableinputreplication) (admin only). + +## bEnableSnapshotInterpolation (command) +**Parameters: (optional string Value)** + +Read/set server setting [bEnableSnapshotInterpolation](#benablesnapshotinterpolation) (admin only). + +## SnapshotInterpSendHz (command) +**Parameters: (optional string Value)** + +Read/set server setting [SnapshotInterpSendHz](#snapshotinterpsendhz) (admin only). + +## SnapshotInterpRewindMs (command) +**Parameters: (optional string Value)** + +Read/set server setting [SnapshotInterpRewindMs](#snapshotinterprewindms) (admin only). + +## MaxJitterTime (command) +**Parameters: (optional string Value)** + +Read/set server setting [MaxJitterTime](#maxjittertime) (admin only). + +## bEnableJitterBounding (command) +**Parameters: (optional string Value)** + +Read/set server setting [bEnableJitterBounding](#benablejitterbounding) (admin only). + +## SnapInterpDebug +Toggles snapshot interpolation debug reporting. + +## SnapInterpStatus +**Parameters: (optional string NameFilter)** + +Prints one-shot proxy runtime status. + +## SnapInterpNetStatus +**Parameters: (optional string NameFilter)** + +Prints client report + server echo + proxy status. + +## SnapInterpCheck +**Parameters: (optional string Profile, optional float DurationSec, optional string NameFilter)** + +Runs an interpolation quality check against a simulated proxy target. + +## SnapInterpCheckStop +Stops an active SnapInterpCheck run. + +## TraceInput +Toggles logging of client input trace data. + +## ShowTickrate / ShowNetSpeeds +These are mutate commands exposed by UTPure command handling: + +* `mutate PureShowTickrate` +* `mutate PureShowNetspeeds` + # Server Installation Extract the zipped files to your installation's folder. @@ -932,6 +1198,12 @@ When connected to the server type **'mutate playerhelp'** in the console to view - BanID x (Will Ban & Kick player with ID x) - EnablePure/DisablePure - ShowDemos (Will show who is recording demos) +- mutate IGPlusServerGet \ +- mutate IGPlusServerSet \ \ +- mutate IGPlusServerSetSilent \ \ +- mutate PureShowTickrate +- mutate PureShowNetspeeds +- mutate PingCompSettings As spectator, you may need to add 'mutate pure' + command (mutate pureshowtickrate) @@ -941,7 +1213,6 @@ Server settings can be found inside InstaGibPlus.ini. 1. [HeadshotDamage](#headshotdamage) 1. [SniperSpeed](#sniperspeed) 1. [SniperDamagePri](#sniperdamagepri) -1. [SetPendingWeapon](#setpendingweapon) 1. [NNAnnouncer](#nnannouncer) 1. [bUTPureEnabled](#butpureenabled) 1. [Advertise](#advertise) @@ -958,13 +1229,16 @@ Server settings can be found inside InstaGibPlus.ini. 1. [bAdvancedTeamSay](#badvancedteamsay) 1. [ForceSettingsLevel](#forcesettingslevel) 1. [bWarmup](#bwarmup) +1. [WarmupTimeLimit](#warmuptimelimit) 1. [bCoaches](#bcoaches) 1. [bAutoPause](#bautopause) 1. [PauseTotalTime](#pausetotaltime) 1. [PauseTime](#pausetime) +1. [Timeouts](#timeouts) 1. [ForceModels](#forcemodels) 1. [ImprovedHUD](#improvedhud) 1. [bDelayedPickupSpawn](#bdelayedpickupspawn) +1. [bUseFastWeaponSwitch](#busefastweaponswitch) 1. [bTellSpectators](#btellspectators) 1. [PlayerPacks](#playerpacks) 1. [DefaultHitSound](#defaulthitsound) @@ -989,7 +1263,6 @@ Server settings can be found inside InstaGibPlus.ini. 1. [PlayerScale](#playerscale) 1. [bAlwaysRenderFlagCarrier](#balwaysrenderflagcarrier) 1. [bAlwaysRenderDroppedFlags](#balwaysrenderdroppedflags) -1. [MinPosError](#minposerror) 1. [MaxPosError](#maxposerror) 1. [MaxHitError](#maxhiterror) 1. [MaxJitterTime](#maxjittertime) @@ -1002,11 +1275,19 @@ Server settings can be found inside InstaGibPlus.ini. 1. [bPlayersAlwaysRelevant](#bplayersalwaysrelevant) 1. [bEnablePingCompensatedSpawn](#benablepingcompensatedspawn) 1. [bEnableJitterBounding](#benablejitterbounding) +1. [LooseCheckCorrectionFactor](#loosecheckcorrectionfactor) +1. [LooseCheckCorrectionFactorOnMover](#loosecheckcorrectionfactoronmover) +1. [bEnableSnapshotInterpolation](#benablesnapshotinterpolation) +1. [SnapshotInterpSendHz](#snapshotinterpsendhz) +1. [SnapshotInterpRewindMs](#snapshotinterprewindms) 1. [bEnableWarpFix](#benablewarpfix) 1. [WarpFixDelay](#warpfixdelay) 1. [FireTimeout](#firetimeout) 1. [bEnableCarcassCollision](#benablecarcasscollision) 1. [ShowTouchedPackage](#showtouchedpackage) +1. [bEnableDamageDebugMode](#benabledamagedebugmode) +1. [bEnableDamageDebugConsoleMessages](#benabledamagedebugconsolemessages) +1. [bEnableHitboxDebugMode](#benablehitboxdebugmode) 1. [ExcludeMapsForKickers](#excludemapsforkickers) 1. [HitFeedbackMode](#hitfeedbackmode) 1. [ForcedSettings](#forcedsettings) @@ -1032,14 +1313,10 @@ Controls sniper rifle reload time, higher values lead to less time between shots Controls damage of body hits by sniper rifle. -## SetPendingWeapon - -**removed** - ## NNAnnouncer **Type: bool** -**Default: False** +**Default: True** Whether to automatically add an announcer for multi-kills, or not. @@ -1176,6 +1453,14 @@ When to check that default settings for all objects are correct client-side. Whether to allow warmup in tournament games or not. +## WarmupTimeLimit + +**Type: int** +**Default: 0** +**Unit: s** + +Maximum warmup length in seconds. `0` means no explicit warmup time limit. + ### bCoaches **Type: bool** @@ -1204,6 +1489,13 @@ Maximum time a team may pause the game for. Length of pause when a pause is triggered. +## Timeouts + +**Type: int** +**Default: 0** + +Maximum number of timeouts a team may take (used with coaches/tournament flow). + ### ForceModels **Type: int** @@ -1233,6 +1525,14 @@ Enable various HUD improvements. Depends on PureClickBoard mutator (set [bUseCli Enable or disable delayed first pickup spawn. +## bUseFastWeaponSwitch + +**Type: bool** +**Default: False** + +If `True`, use the fast weapon switch path. +If `False`, use normal weapon switching behavior. + ## bTellSpectators **Type: bool** @@ -1290,18 +1590,10 @@ Horizontal speed with which to throw weapons. Forces clients to do demos. -## MinPosError - -**Type: float** -**Default: 100** -**Unit: uu²** - -Unused. Intended to be minimum squared distance error for updating clients. - ## MaxPosError **Type: float** -**Default: 3000** +**Default: 1000** **Unit: uu²** Unused. Intended to be maximum squared distance error for updating clients. @@ -1321,6 +1613,27 @@ Distance to any position over the last 500ms for hits to be counted. Send package-names of touched actors to clients when those clients touch the actors. +## bEnableDamageDebugMode + +**Type: bool** +**Default: False** + +If `True`, enables server-side damage debug instrumentation. + +## bEnableDamageDebugConsoleMessages + +**Type: bool** +**Default: False** + +If `True`, emits damage debug information to console messages. + +## bEnableHitboxDebugMode + +**Type: bool** +**Default: False** + +If `True`, enables hitbox debug instrumentation. + ## ExcludeMapsForKickers **Type: string\[128\]** @@ -1448,6 +1761,45 @@ If enabled, updates by clients over more than [MaxJitterTime](#maxjittertime) wi Disable to restore default netcode behavior. +## LooseCheckCorrectionFactor + +**Type: float** +**Default: 1.0** + +Multiplier for loose position check correction strength. + +## LooseCheckCorrectionFactorOnMover + +**Type: float** +**Default: 1.0** + +Multiplier for loose position check correction strength while standing on movers. + +## bEnableSnapshotInterpolation + +**Type: bool** +**Default: False** + +If enabled, clients use snapshot interpolation for simulated proxies. + +This feature is highly experimental and should be enabled only for controlled testing. + +## SnapshotInterpSendHz + +**Type: float** +**Default: 30.0** +**Unit: Hz** + +Snapshot send rate used by snapshot interpolation. + +## SnapshotInterpRewindMs + +**Type: float** +**Default: 66.0** +**Unit: ms** + +Interpolation rewind delay used by snapshot interpolation. + ## bEnableWarpFix **Type: bool** @@ -1496,7 +1848,7 @@ If True, players retain the momentum of their in-air movement upon landing. If F ## bEnableSingleButtonDodge **Type: bool** -**Default: False** +**Default: True** Enables an input button clients can bind to a key, which makes the client dodge in the direction it is currently walking. @@ -1614,4 +1966,4 @@ Shots more than this long in the past are rejected. **spect** - Starting this project. **Deaod** - Maintenance. **UT99 Community** - For their endless patience, support and help testing and reporting bugs. -**Epic** - For not open sourcing a 20 year old game running on their 20 year old engine. \ No newline at end of file +**Epic** - For not open sourcing a 20 year old game running on their 20 year old engine.