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.
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;
}
}