Add command history to single-player and multiplayer

The last 50 valid commands starting with / are saved to memory.
Regular chat messages are not saved to prevent spam.
The command history persists across single-player and multiplayer games.
Press arrow up/down to show the previously typed commands within the current OpenRA session.
This commit is contained in:
xan2622
2026-02-12 22:28:44 +01:00
committed by Gustas Kažukauskas
parent 096ad0c413
commit 4a04f8df83
3 changed files with 141 additions and 0 deletions

View File

@@ -0,0 +1,89 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System.Collections.Generic;
namespace OpenRA.Mods.Common.Widgets.Logic
{
public sealed class CommandHistory
{
const int MaxHistorySize = 50;
static CommandHistory instance;
static readonly object LockObject = new();
readonly List<string> history = [];
int currentIndex = -1;
CommandHistory() { }
public static CommandHistory Instance
{
get
{
if (instance == null)
{
lock (LockObject)
{
instance ??= new CommandHistory();
}
}
return instance;
}
}
public void AddCommand(string command)
{
if (string.IsNullOrWhiteSpace(command) || !command.StartsWith('/'))
return;
var trimmedCommand = command.Trim();
history.RemoveAll(c => c == trimmedCommand);
history.Insert(0, trimmedCommand);
if (history.Count > MaxHistorySize)
history.RemoveAt(history.Count - 1);
currentIndex = -1;
}
public string GetPrevious()
{
if (history.Count == 0)
return null;
if (currentIndex < history.Count - 1)
currentIndex++;
return history[currentIndex];
}
public string GetNext()
{
if (history.Count == 0)
return null;
if (currentIndex > 0)
{
currentIndex--;
return history[currentIndex];
}
if (currentIndex == 0)
currentIndex = -1;
return null;
}
public void Reset()
{
currentIndex = -1;
}
}
}

View File

@@ -49,6 +49,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
readonly Dictionary<TextNotificationPool, Widget> templates = [];
readonly TabCompletionLogic tabCompletion = new();
readonly CommandHistory commandHistory = CommandHistory.Instance;
readonly string chatLineSound = ChromeMetrics.Get<string>("ChatLineSound");
@@ -159,11 +160,18 @@ namespace OpenRA.Mods.Common.Widgets.Logic
{
var text = chatText.Text.Trim();
var from = world.IsReplay ? null : orderManager.LocalClient.Name;
var commandName = text[1..].Split(' ')[0].ToLowerInvariant();
var isValidCommand = !string.IsNullOrEmpty(commandName) &&
chatTraits.OfType<ChatCommands>().Any(trait => trait.Commands.ContainsKey(commandName));
foreach (var trait in chatTraits)
trait.OnChat(from, text);
if (isValidCommand)
commandHistory.AddCommand(text);
}
}
commandHistory.Reset();
chatText.Text = "";
if (!isMenuChat)
CloseChat();
@@ -191,9 +199,40 @@ namespace OpenRA.Mods.Common.Widgets.Logic
else
chatText.YieldKeyboardFocus();
commandHistory.Reset();
return true;
};
chatText.OnArrowUp = _ =>
{
var previousCommand = commandHistory.GetPrevious();
if (previousCommand != null)
{
chatText.Text = previousCommand;
chatText.CursorPosition = chatText.Text.Length;
return true;
}
return false;
};
chatText.OnArrowDown = _ =>
{
var nextCommand = commandHistory.GetNext();
if (nextCommand != null)
{
chatText.Text = nextCommand;
chatText.CursorPosition = chatText.Text.Length;
return true;
}
else
{
chatText.Text = "";
commandHistory.Reset();
return true;
}
};
chatAvailableIn = new CachedTransform<int, string>(x => FluentProvider.GetMessage(ChatAvailability, "seconds", x));
if (!isMenuChat)
@@ -265,6 +304,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
public void OpenChat()
{
chatText.Text = "";
commandHistory.Reset();
chatChrome.Visible = true;
chatScrollPanel.ScrollToBottom();
if (!chatText.IsDisabled())

View File

@@ -57,6 +57,8 @@ namespace OpenRA.Mods.Common.Widgets
public Func<KeyInput, bool> OnEnterKey = _ => false;
public Func<KeyInput, bool> OnTabKey = _ => false;
public Func<KeyInput, bool> OnEscKey = _ => false;
public Func<KeyInput, bool> OnArrowUp = _ => false;
public Func<KeyInput, bool> OnArrowDown = _ => false;
public Func<bool> OnAltKey = () => false;
public Action OnLoseFocus = () => { };
public Action OnTextEdited = () => { };
@@ -282,6 +284,16 @@ namespace OpenRA.Mods.Common.Widgets
break;
case Keycode.UP:
if (OnArrowUp(e))
return true;
break;
case Keycode.DOWN:
if (OnArrowDown(e))
return true;
break;
case Keycode.HOME:
ResetBlinkCycle();
if (e.Modifiers.HasModifier(Modifiers.Shift))