GameHotkeyActions.cs

Part of the MatchController partial class that routes keyboard shortcuts. TickGameHotkeys reads the keyboard each frame, blocks input while a text entry has focus or a button has activation focus, and ExecuteHotkey maps an action id to game actions (pause, speed, settings, substitutions, tactics, player orders, field commands) through the current modal scope.

Rough Code
using Sandbox;
using Sandbox.UI;
using Socer;
using System;
using System.Linq;

public sealed partial class MatchController
{
    // A single dispatcher keeps the keyboard subject to the same modal and
    // simulation restrictions as the visible controls.
    public bool HotkeyTextInputActive=>InputFocus.Current is TextEntry ||
        InputFocus.Current?.Ancestors.Any(p=>p is TextEntry)==true;

    GameHotkeyScope CurrentHotkeyScope=>MatchExitPromptOpen?GameHotkeyScope.Match:GameSettingsOpen?GameHotkeyScope.Settings:
        InjuryChoiceOpen || SubstitutionOpen?GameHotkeyScope.Player:
        ClubOpen?GameHotkeyScope.Club:
        MatchIntroActive || Replay.Playing?GameHotkeyScope.IntroReplay:
        Match.ManualRestartWaiting?GameHotkeyScope.Standard:GameHotkeyScope.Match;

    void TickGameHotkeys()
    {
        if(GameHotkeys.Capturing && !GameSettingsOpen)GameHotkeys.CancelCapture();
        if(GameHotkeys.TickCapture())return;
        if(HotkeyTextInputActive)return;
        // Tab-focused controls keep their native Enter/Space activation. Do
        // not execute a second action from the physical key in the same frame.
        var focus=InputFocus.Current;
        bool modified=Input.Keyboard.Down("CTRL") || Input.Keyboard.Down("RCTRL") ||
            Input.Keyboard.Down("SHIFT") || Input.Keyboard.Down("RSHIFT") || Input.Keyboard.Down("ALT") || Input.Keyboard.Down("RALT");
        if(!modified && (Input.Keyboard.Pressed("ENTER") || Input.Keyboard.Pressed("SPACE")) &&
            (focus is Button || focus?.Ancestors.Any(p=>p is Button)==true))return;
        foreach(var action in GameHotkeys.Definitions)
            if((action.Scope&CurrentHotkeyScope)!=0 && GameHotkeys.Pressed(action.Id) && ExecuteHotkey(action.Id))return;
    }

    public bool ExecuteHotkey(string id)
    {
        if(GameHotkeys.Capturing || HotkeyTextInputActive)return false;
        var definition=GameHotkeys.Definitions.FirstOrDefault(d=>d.Id==id);
        if(definition==null || (definition.Scope&CurrentHotkeyScope)==0)return false;
        if(MatchExitPromptOpen)
        {
            if(id is "app.confirm" or "match.finish_text")ConfirmMatchExitInText();
            else if(id is "app.back" or "app.club")ContinueAfterMatchExitPrompt();
            return true;
        }
        if(GameSettingsOpen)
        {
            if(id is "app.settings" or "app.back"){ToggleGameSettings();return true;}
            return false;
        }
        if(id=="app.settings"){ToggleGameSettings();return true;}
        if(id=="app.back")return BackHotkey();
        if(RelegationChoiceOpen)return false;
        if(InjuryChoiceOpen || InjurySubstitutionRequired)
            return Scene.GetAllComponents<MatchHud>().SelectMany(c=>c.Panel.Descendants)
                .OfType<InjurySubstitutionPanel>().FirstOrDefault()?.HandleHotkey(id)==true;
        if(ClubOpen)
        {
            if(QuickMatchRunning)return false;
            var club=Scene.GetAllComponents<ClubHud>().FirstOrDefault();
            if(club?.HandleHotkey(id)==true)return true;
            if(club?.HotkeyModalOpen==true || PreparingMatch || MatchModeSelection || TutorialStep>=0)return false;
            if(id=="app.club"){ResumeMatch();return true;}
            return false;
        }
        if(StrategyLabOpen)return false;
        if(id=="match.skip")
        {
            if(MatchIntroActive){SkipMatchIntro();return true;}
            if(Replay.Playing){SkipGoalReplay();return true;}
            return false;
        }
        if(id=="app.club" && !MatchTutorialActive){OpenClub();return true;}
        if(SubstitutionOpen)
        {
            if(id=="match.substitutions"){CloseSubstitutions();return true;}
            if(id=="ui.search")
            {
                var search=Scene.GetAllComponents<MatchHud>().SelectMany(c=>c.Panel.Descendants)
                    .OfType<SubstitutionPanel>().SelectMany(p=>p.Descendants).OfType<TextEntry>().FirstOrDefault();
                if(search==null)return false;search.Focus();return true;
            }
            return Scene.GetAllComponents<MatchHud>().SelectMany(c=>c.Panel.Descendants)
                .OfType<SubstitutionPanel>().FirstOrDefault()?.HandleHotkey(id)==true;
        }
        if(id is "match.rematch" or "match.new_match")
        {
            if(IsRulesPreview || MediaLeagueActive || MatchTutorialActive)return false;
            Restart(id=="match.new_match");return true;
        }
        if(Match.Finished)
        {
            if(id=="app.confirm" && !MatchTutorialActive)
                return Scene.GetAllComponents<MatchHud>().SelectMany(c=>c.Panel.Descendants).OfType<MatchResult>().FirstOrDefault()?.HandleHotkey(id)==true;
            return false;
        }
        if(id=="match.pause")
        {
            if(MatchIntroActive)return false;
            Paused=!Paused;accumulator=0;return true;
        }
        if(MatchIntroActive || Replay.Playing)return false;
        if(Match.ManualRestartWaiting && id.StartsWith("standard."))
            return Scene.GetAllComponents<MatchHud>().SelectMany(c=>c.Panel.Descendants)
                .OfType<SetPieceControl>().FirstOrDefault()?.HandleHotkey(id)==true;
        if(id=="match.substitutions")
        {
            if(MediaLeagueActive || Match.MedicalActive || Match.ShootoutActive || (IsRulesPreview && !MatchTutorialSubstitutionLesson))return false;
            OpenSubstitutions();return SubstitutionOpen;
        }
        if(!MediaLeagueActive)
        {
            switch(id)
            {
                case "match.slower":Speed=(Director.Automatic?Director.PlaybackRate:Speed)>1.001f?1:.5f;return true;
                case "match.faster":Speed=(Director.Automatic?Director.PlaybackRate:Speed)<.999f?1:1.5f;return true;
                case "match.normal_speed":Speed=1;return true;
                case "match.auto_speed":EnableAutomatic();return true;
            }
        }
        switch(id)
        {
            case "match.finish_text":if(!CanFinishCurrentMatchQuickly)return false;FinishCurrentMatchQuickly();return true;
            case "camera.toggle":ToggleBroadcastCamera();return true;
            case "view.vision":ShowVision=!ShowVision;return true;
            case "view.speech":ShowSpeech=!ShowSpeech;return true;
            case "standards.manual":if(MediaLeagueActive || Match.ShootoutActive)return false;ManualSetPieces=!ManualSetPieces;return true;
        }
        if(Match.ShootoutActive || Match.ManualRestartWaiting || Match.MedicalActive)return false;
        if(MatchTutorialActive && !id.StartsWith("field.") && !id.StartsWith("player."))return false;
        switch(id)
        {
            case "tactics.counter":SetClubTactic(0);return true;
            case "tactics.balanced":SetClubTactic(1);return true;
            case "tactics.pressing":SetClubTactic(2);return true;
            case "coach.order_1":Match.Command(1);return true;
            case "coach.order_2":Match.Command(2);return true;
            case "coach.order_3":Match.Command(3);return true;
            case "play.run":if(SelectedMatchStrategy==null)return false;CallSavedStrategy();return true;
            case "play.automatic":if(SelectedMatchStrategy==null)return false;ToggleAutomaticStrategies();return true;
            case "play.next":if(StrategyLibrary.Count==0)return false;MatchStrategyIndex=(MatchStrategyIndex+1)%StrategyLibrary.Count;return true;
            case "player.previous":CycleHotkeyPlayer(-1);return true;
            case "player.next":CycleHotkeyPlayer(1);return true;
            case "player.role":Instruction(OrderedPlayer,(Match.Players[OrderedPlayer].Instruction+1)%4);return true;
            case "player.calm":Match.CalmPlayer(OrderedPlayer);return true;
            case "player.inspect":
                if(MediaLeagueActive || MatchTutorialActive)return false;
                int card=MatchCardId(Math.Clamp(SelectedPlayer,0,10));
                OpenClub();if(ClubOpen)Scene.GetAllComponents<ClubHud>().FirstOrDefault()?.InspectHotkeyPlayer(card);return true;
        }
        return FieldHotkey(id);
    }

    void CycleHotkeyPlayer(int direction)
    {
        var players=Match.ActivePlayers.Where(p=>p.Team==0).OrderBy(p=>p.Number).ToArray();
        if(players.Length==0)return;
        int index=Array.FindIndex(players,p=>p.Id==SelectedPlayer);
        SelectPlayer(players[(Math.Max(index,0)+direction+players.Length)%players.Length].Id);
    }

    bool FieldHotkey(string id)
    {
        FieldCommand? kind=id switch
        {
            "field.move"=>FieldCommand.Move,"field.mark"=>FieldCommand.Mark,"field.press"=>FieldCommand.Press,
            "field.zone"=>FieldCommand.Zone,"field.run"=>FieldCommand.Run,"field.pass"=>FieldCommand.Pass,
            "field.attack"=>FieldCommand.Attack,"field.shoot"=>FieldCommand.Shoot,"field.clear"=>FieldCommand.Clear,_=>null
        };
        if(kind==null)return false;
        if(CommandWheelOpen){ExecuteFieldCommand(kind.Value);return true;}
        switch(kind.Value)
        {
            case FieldCommand.Mark:BeginDirectOrder(DirectOrderKind.MarkPlayer);break;
            case FieldCommand.Press:BeginDirectOrder(DirectOrderKind.PressPlayer);break;
            case FieldCommand.Zone:BeginDirectOrder(DirectOrderKind.GuardZone);break;
            case FieldCommand.Run:BeginDirectOrder(DirectOrderKind.AttackRun);break;
            case FieldCommand.Clear:ClearDirectOrder();break;
            case FieldCommand.Attack:CallAttackTarget();break;
            case FieldCommand.Shoot:CallAttackTarget(true);break;
            case FieldCommand.Pass:CallPassTarget(SelectedPlayer);break;
            case FieldCommand.Move:
                var pointer=Scene.GetAllComponents<MatchHud>().SelectMany(c=>c.Panel.Descendants).OfType<FieldCallInput>().FirstOrDefault();
                if(pointer?.CursorVisible==true)CallMoveTarget(OrderedPlayer,pointer.CursorField);
                else BeginDirectOrder(DirectOrderKind.GuardZone);
                break;
        }
        return true;
    }

    bool BackHotkey()
    {
        if(QuickMatchRunning || QuickBroadcastOpen && !Match.Finished)return true;
        if(RelegationChoiceOpen || InjuryChoiceOpen || InjurySubstitutionRequired)return false;
        if(CommandWheelOpen){CloseCommandWheel();return true;}
        if(AwaitingOrder!=DirectOrderKind.None){CancelDirectOrder();return true;}
        if(SubstitutionOpen){CloseSubstitutions();return true;}
        if(ClubOpen)
        {
            if(Scene.GetAllComponents<ClubHud>().FirstOrDefault()?.HandleHotkey("app.back")==true)return true;
            if(MatchModeSelection){ReturnToLineup();return true;}
            if(PreparingMatch){CancelPreparation();return true;}
            if(TutorialStep>=0){FinishTutorial();return true;}
            if(ClubPage!="Главная"){ClubPage="Главная";return true;}
            return false;
        }
        if(MatchTutorialActive){EndMatchTutorial();return true;}
        if(MatchIntroActive){SkipMatchIntro();return true;}
        if(Replay.Playing){SkipGoalReplay();return true;}
        if(Match.Finished){ExitFinishedMatch();return true;}
        ToggleGameSettings();return true;
    }
}