Added: game list - adding, removing and persisting
Added: auto-detection of game location
This commit is contained in:
parent
75f07a0680
commit
736cb4b231
@ -3,9 +3,15 @@ unit Resources;
|
||||
interface
|
||||
const
|
||||
AssetsPath = 'assets\';
|
||||
AssetChivalryMedievalWarfareMapListFileName = 'Chivalry.MedievalWarfare.MapList.ini';
|
||||
|
||||
|
||||
UserDataPath = 'Chivalry Server Launcher\';
|
||||
UserGamesFileName = 'Games.json';
|
||||
|
||||
|
||||
function GetAssetPath(const AAsset: string): string;
|
||||
function GetUserDataPath(const AAsset: string): string;
|
||||
|
||||
implementation
|
||||
uses
|
||||
@ -17,4 +23,10 @@ begin
|
||||
Result := App.Path + AssetsPath + AAsset;
|
||||
end;
|
||||
|
||||
|
||||
function GetUserDataPath(const AAsset: string): string;
|
||||
begin
|
||||
Result := App.UserPath + UserDataPath + AAsset;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
@ -9,16 +9,22 @@ type
|
||||
TCustomGame = class(TInterfacedPersistent)
|
||||
private
|
||||
FLocation: string;
|
||||
FLoaded: Boolean;
|
||||
protected
|
||||
procedure Notify(const APropertyName: string); virtual;
|
||||
|
||||
procedure DoLoad; virtual; abstract;
|
||||
procedure DoSave; virtual; abstract;
|
||||
public
|
||||
class function GetGameName: string; virtual; abstract;
|
||||
class function GameName: string; virtual; abstract;
|
||||
class function AutoDetect: TCustomGame; virtual;
|
||||
|
||||
constructor Create(const ALocation: string); virtual;
|
||||
|
||||
procedure Load; virtual; abstract;
|
||||
procedure Save; virtual; abstract;
|
||||
procedure Load; virtual;
|
||||
procedure Save; virtual;
|
||||
|
||||
property Loaded: Boolean read FLoaded;
|
||||
property Location: string read FLocation;
|
||||
end;
|
||||
|
||||
@ -40,6 +46,25 @@ begin
|
||||
end;
|
||||
|
||||
|
||||
class function TCustomGame.AutoDetect: TCustomGame;
|
||||
begin
|
||||
Result := nil;
|
||||
end;
|
||||
|
||||
|
||||
procedure TCustomGame.Load;
|
||||
begin
|
||||
DoLoad;
|
||||
FLoaded := True;
|
||||
end;
|
||||
|
||||
|
||||
procedure TCustomGame.Save;
|
||||
begin
|
||||
DoSave;
|
||||
end;
|
||||
|
||||
|
||||
procedure TCustomGame.Notify(const APropertyName: string);
|
||||
begin
|
||||
TBindings.Notify(Self, APropertyName);
|
||||
|
@ -4,6 +4,7 @@ interface
|
||||
uses
|
||||
System.Generics.Collections,
|
||||
|
||||
Game.Base,
|
||||
Game.Chivalry,
|
||||
Game.Intf;
|
||||
|
||||
@ -16,7 +17,8 @@ type
|
||||
protected
|
||||
procedure LoadSupportedMapList(AList: TList<IGameMap>); override;
|
||||
public
|
||||
class function GetGameName: string; override;
|
||||
class function GameName: string; override;
|
||||
class function AutoDetect: TCustomGame; override;
|
||||
end;
|
||||
|
||||
|
||||
@ -25,19 +27,60 @@ uses
|
||||
System.Classes,
|
||||
System.IniFiles,
|
||||
System.SysUtils,
|
||||
System.Win.Registry,
|
||||
Winapi.Windows,
|
||||
|
||||
Resources,
|
||||
Game.Map,
|
||||
Game.Registry;
|
||||
|
||||
|
||||
const
|
||||
SteamKey = '\Software\Valve\Steam';
|
||||
SteamValuePath = 'SteamPath';
|
||||
|
||||
ChivalryServerSteamPath = 'steamapps\common\chivalry_ded_server\';
|
||||
|
||||
|
||||
{ TChivalryMedievalWarfareGame }
|
||||
class function TChivalryMedievalWarfareGame.GetGameName: string;
|
||||
class function TChivalryMedievalWarfareGame.GameName: string;
|
||||
begin
|
||||
Result := 'Chivalry: Medieval Warfare';
|
||||
end;
|
||||
|
||||
|
||||
class function TChivalryMedievalWarfareGame.AutoDetect: TCustomGame;
|
||||
var
|
||||
registry: TRegistry;
|
||||
steamPath: string;
|
||||
serverPath: string;
|
||||
|
||||
begin
|
||||
steamPath := '';
|
||||
|
||||
registry := TRegistry.Create(KEY_READ);
|
||||
try
|
||||
registry.RootKey := HKEY_CURRENT_USER;
|
||||
if registry.OpenKeyReadOnly(SteamKey) then
|
||||
try
|
||||
if registry.ValueExists(SteamValuePath) then
|
||||
steamPath := StringReplace(registry.ReadString(SteamValuePath), '/', '\', [rfReplaceAll]);
|
||||
finally
|
||||
registry.CloseKey;
|
||||
end;
|
||||
finally
|
||||
FreeAndNil(registry);
|
||||
end;
|
||||
|
||||
if (Length(steamPath) > 0) then
|
||||
begin
|
||||
serverPath := IncludeTrailingPathDelimiter(steamPath) + ChivalryServerSteamPath;
|
||||
if DirectoryExists(serverPath) then
|
||||
Result := TChivalryMedievalWarfareGame.Create(serverPath);
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
procedure TChivalryMedievalWarfareGame.LoadSupportedMapList(AList: TList<IGameMap>);
|
||||
var
|
||||
mapListFileName: string;
|
||||
@ -48,7 +91,7 @@ var
|
||||
mapIndex: Integer;
|
||||
|
||||
begin
|
||||
mapListFileName := GetAssetPath('Chivalry.MedievalWarfare.MapList.ini');
|
||||
mapListFileName := Resources.GetAssetPath(Resources.AssetChivalryMedievalWarfareMapListFileName);
|
||||
if not FileExists(mapListFileName) then
|
||||
exit;
|
||||
|
||||
|
@ -15,12 +15,16 @@ type
|
||||
class procedure Finalize;
|
||||
public
|
||||
class function Instance: TGameList;
|
||||
|
||||
procedure AutoDetect;
|
||||
end;
|
||||
|
||||
|
||||
implementation
|
||||
uses
|
||||
System.SysUtils;
|
||||
System.SysUtils,
|
||||
|
||||
Game.Registry;
|
||||
|
||||
|
||||
{ TGameList }
|
||||
@ -39,6 +43,21 @@ begin
|
||||
end;
|
||||
|
||||
|
||||
procedure TGameList.AutoDetect;
|
||||
var
|
||||
gameClass: TCustomGameClass;
|
||||
game: TCustomGame;
|
||||
|
||||
begin
|
||||
for gameClass in TGameRegistry.RegisteredGames do
|
||||
begin
|
||||
game := gameClass.AutoDetect;
|
||||
if Assigned(game) then
|
||||
Add(game);
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
initialization
|
||||
finalization
|
||||
TGameList.Finalize;
|
||||
|
@ -22,6 +22,7 @@ type
|
||||
class procedure Finalize;
|
||||
public
|
||||
class function RegisteredGames: TGameRegistryEnumerable;
|
||||
class function ByClassName(const AClassName: string): TCustomGameClass;
|
||||
|
||||
class procedure Register(AGameClass: TCustomGameClass);
|
||||
class procedure Unregister(AGameClass: TCustomGameClass);
|
||||
@ -54,6 +55,22 @@ begin
|
||||
end;
|
||||
|
||||
|
||||
class function TGameRegistry.ByClassName(const AClassName: string): TCustomGameClass;
|
||||
var
|
||||
gameClass: TCustomGameClass;
|
||||
|
||||
begin
|
||||
Result := nil;
|
||||
|
||||
for gameClass in RegisteredGames do
|
||||
if SameText(gameClass.ClassName, AClassName) then
|
||||
begin
|
||||
Result := gameClass;
|
||||
break;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
class procedure TGameRegistry.Register(AGameClass: TCustomGameClass);
|
||||
begin
|
||||
if not SRegisteredGames.Contains(AGameClass) then
|
||||
|
@ -9,19 +9,71 @@ type
|
||||
TGameListPersist = class(TObject)
|
||||
public
|
||||
class procedure Load(const AFileName: string; AList: TGameList);
|
||||
class procedure Save(const AFileName: string; AList: TGameList);
|
||||
end;
|
||||
|
||||
|
||||
implementation
|
||||
uses
|
||||
System.SysUtils;
|
||||
System.IOUtils,
|
||||
System.SysUtils,
|
||||
|
||||
superobject,
|
||||
|
||||
Game.Base,
|
||||
Game.Registry;
|
||||
|
||||
|
||||
const
|
||||
KeyClassName = 'className';
|
||||
KeyLocation = 'location';
|
||||
|
||||
|
||||
{ TGameListPersist }
|
||||
class procedure TGameListPersist.Load(const AFileName: string; AList: TGameList);
|
||||
var
|
||||
inputList: ISuperObject;
|
||||
inputGame: ISuperObject;
|
||||
gameClass: TCustomGameClass;
|
||||
|
||||
begin
|
||||
if not FileExists(AFileName) then
|
||||
exit;
|
||||
|
||||
inputList := SO(TFile.ReadAllText(AFileName));
|
||||
if Assigned(inputList) then
|
||||
begin
|
||||
for inputGame in inputList do
|
||||
begin
|
||||
gameClass := TGameRegistry.ByClassName(inputGame.S[KeyClassName]);
|
||||
if Assigned(gameClass) then
|
||||
AList.Add(gameClass.Create(inputGame.S[KeyLocation]));
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
class procedure TGameListPersist.Save(const AFileName: string; AList: TGameList);
|
||||
var
|
||||
outputList: ISuperObject;
|
||||
outputGame: ISuperObject;
|
||||
game: TCustomGame;
|
||||
|
||||
begin
|
||||
outputList := SA([]);
|
||||
|
||||
for game in AList do
|
||||
begin
|
||||
outputGame := SO();
|
||||
outputGame.S[KeyClassName] := game.ClassName;
|
||||
outputGame.S[KeyLocation] := game.Location;
|
||||
// outputGame.B['active'] := True;
|
||||
|
||||
outputList.AsArray.Add(outputGame);
|
||||
end;
|
||||
|
||||
if ForceDirectories(ExtractFilePath(AFileName)) then
|
||||
TFile.WriteAllText(AFileName, outputList.AsJSon(True));
|
||||
end;
|
||||
|
||||
end.
|
||||
|
@ -59,7 +59,6 @@ object GameForm: TGameForm
|
||||
Style = csDropDownList
|
||||
Anchors = [akLeft, akTop, akRight]
|
||||
TabOrder = 0
|
||||
ExplicitWidth = 509
|
||||
end
|
||||
object deLocation: TJvDirectoryEdit
|
||||
Left = 108
|
||||
|
@ -77,7 +77,7 @@ begin
|
||||
cmbGame.Items.Clear;
|
||||
|
||||
for gameClass in TGameRegistry.RegisteredGames do
|
||||
cmbGame.Items.AddObject(gameClass.GetGameName, TObject(gameClass));
|
||||
cmbGame.Items.AddObject(gameClass.GameName, TObject(gameClass));
|
||||
finally
|
||||
cmbGame.Items.EndUpdate;
|
||||
cmbGame.ItemIndex := 0;
|
||||
|
@ -43,7 +43,7 @@ object MainForm: TMainForm
|
||||
Top = 75
|
||||
Width = 565
|
||||
Height = 472
|
||||
ActivePage = tsAbout
|
||||
ActivePage = tsGames
|
||||
Align = alClient
|
||||
Style = tsButtons
|
||||
TabOrder = 2
|
||||
@ -119,7 +119,6 @@ object MainForm: TMainForm
|
||||
object tsConfiguration: TTabSheet
|
||||
Caption = 'Server - Configuration'
|
||||
ImageIndex = 1
|
||||
ExplicitLeft = 6
|
||||
object gbServerName: TGroupBox
|
||||
AlignWithMargins = True
|
||||
Left = 8
|
||||
@ -202,7 +201,7 @@ object MainForm: TMainForm
|
||||
object tsGames: TTabSheet
|
||||
Caption = 'Launcher - Game locations'
|
||||
ImageIndex = 3
|
||||
object VirtualStringTree2: TVirtualStringTree
|
||||
object vstGames: TVirtualStringTree
|
||||
AlignWithMargins = True
|
||||
Left = 8
|
||||
Top = 30
|
||||
@ -213,17 +212,29 @@ object MainForm: TMainForm
|
||||
Margins.Right = 8
|
||||
Margins.Bottom = 8
|
||||
Align = alClient
|
||||
Header.AutoSizeIndex = 0
|
||||
Header.AutoSizeIndex = 1
|
||||
Header.Font.Charset = DEFAULT_CHARSET
|
||||
Header.Font.Color = clWindowText
|
||||
Header.Font.Height = -11
|
||||
Header.Font.Name = 'Tahoma'
|
||||
Header.Font.Style = []
|
||||
Header.Options = [hoColumnResize, hoDrag, hoShowSortGlyphs, hoVisible]
|
||||
Header.Options = [hoAutoResize, hoColumnResize, hoDrag, hoShowSortGlyphs, hoVisible]
|
||||
TabOrder = 1
|
||||
TreeOptions.PaintOptions = [toHideFocusRect, toShowDropmark, toShowTreeLines, toThemeAware, toUseBlendedImages]
|
||||
TreeOptions.SelectionOptions = [toFullRowSelect, toMiddleClickSelect, toRightClickSelect]
|
||||
OnFocusChanged = vstGamesFocusChanged
|
||||
OnGetText = vstGamesGetText
|
||||
OnInitNode = vstGamesInitNode
|
||||
Columns = <
|
||||
item
|
||||
Position = 0
|
||||
Width = 200
|
||||
WideText = 'Game'
|
||||
end
|
||||
item
|
||||
Position = 1
|
||||
Width = 337
|
||||
WideText = 'Location'
|
||||
end>
|
||||
end
|
||||
object pnlGamesWarning: TPanel
|
||||
@ -240,6 +251,7 @@ object MainForm: TMainForm
|
||||
BevelOuter = bvLowered
|
||||
ParentBackground = False
|
||||
TabOrder = 2
|
||||
Visible = False
|
||||
object imgGamesWarning: TImage
|
||||
Left = 12
|
||||
Top = 12
|
||||
|
@ -5,6 +5,7 @@ uses
|
||||
System.Bindings.Expression,
|
||||
System.Classes,
|
||||
System.Generics.Collections,
|
||||
Vcl.ActnList,
|
||||
Vcl.ComCtrls,
|
||||
Vcl.Controls,
|
||||
Vcl.ExtCtrls,
|
||||
@ -26,7 +27,7 @@ uses
|
||||
X2CLmusikCubeMenuBarPainter,
|
||||
|
||||
Game.Base,
|
||||
Game.Intf, Vcl.ActnList;
|
||||
Game.Intf;
|
||||
|
||||
|
||||
type
|
||||
@ -85,7 +86,7 @@ type
|
||||
tsMapList: TTabSheet;
|
||||
tsNetwork: TTabSheet;
|
||||
vstMapList: TVirtualStringTree;
|
||||
VirtualStringTree2: TVirtualStringTree;
|
||||
vstGames: TVirtualStringTree;
|
||||
alMain: TActionList;
|
||||
actGameAdd: TAction;
|
||||
actGameRemove: TAction;
|
||||
@ -101,6 +102,9 @@ type
|
||||
procedure EditChange(Sender: TObject);
|
||||
procedure actGameAddExecute(Sender: TObject);
|
||||
procedure actGameRemoveExecute(Sender: TObject);
|
||||
procedure vstGamesInitNode(Sender: TBaseVirtualTree; ParentNode, Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
|
||||
procedure vstGamesGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
|
||||
procedure vstGamesFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex);
|
||||
private type
|
||||
TBindingExpressionList = TList<TBindingExpression>;
|
||||
TPageMenuMap = TDictionary<TTabSheet, TX2MenuBarItem>;
|
||||
@ -120,6 +124,8 @@ type
|
||||
procedure BindGameMapList;
|
||||
procedure UpdateMenu;
|
||||
|
||||
procedure UpdateGameList;
|
||||
|
||||
property ActiveGame: TCustomGame read FActiveGame write SetActiveGame;
|
||||
property PageMenuMap: TPageMenuMap read FPageMenuMap;
|
||||
property UIBindings: TBindingExpressionList read FUIBindings;
|
||||
@ -144,7 +150,8 @@ uses
|
||||
Forms.Game,
|
||||
Game.Chivalry.MedievalWarfare,
|
||||
Game.List,
|
||||
Persist.GameList;
|
||||
Persist.GameList,
|
||||
Resources;
|
||||
|
||||
|
||||
type
|
||||
@ -159,11 +166,18 @@ type
|
||||
end;
|
||||
|
||||
|
||||
PCustomGame = ^TCustomGame;
|
||||
|
||||
|
||||
const
|
||||
INIHintPrefix = 'INI:';
|
||||
INIHintSeparator = '>';
|
||||
|
||||
|
||||
GameColumnName = 0;
|
||||
GameColumnLocation = 1;
|
||||
|
||||
|
||||
{$R *.dfm}
|
||||
|
||||
|
||||
@ -174,7 +188,7 @@ var
|
||||
pageIndex: Integer;
|
||||
menuGroup: TX2MenuBarGroup;
|
||||
menuItem: TX2MenuBarItem;
|
||||
// game: TCustomGame;
|
||||
userGamesFileName: string;
|
||||
|
||||
begin
|
||||
FUIBindings := TBindingExpressionList.Create;
|
||||
@ -202,11 +216,19 @@ begin
|
||||
lightBtnFace := BlendColors(clBtnFace, clWindow, 196);
|
||||
pnlGamesWarning.Color := lightBtnFace;
|
||||
|
||||
// #ToDo1 -oMvR: 30-6-2014: load last active game
|
||||
// game := TChivalryMedievalWarfareGame.Create('D:\Steam\steamapps\common\chivalry_ded_server');
|
||||
// game.Load;
|
||||
// ActiveGame := game;
|
||||
|
||||
{ Load games }
|
||||
userGamesFileName := Resources.GetUserDataPath(Resources.UserGamesFileName);
|
||||
if FileExists(userGamesFileName) then
|
||||
TGameListPersist.Load(userGamesFileName, TGameList.Instance)
|
||||
else
|
||||
TGameList.Instance.AutoDetect;
|
||||
|
||||
UpdateGameList;
|
||||
|
||||
// #ToDo1 -oMvR: 30-6-2014: load last active game
|
||||
if TGameList.Instance.Count > 0 then
|
||||
ActiveGame := TGameList.Instance.First;
|
||||
|
||||
{ Initialize menu }
|
||||
mbpMenuPainter.GroupColors.Hot.Assign(mbpMenuPainter.GroupColors.Normal);
|
||||
@ -227,6 +249,10 @@ end;
|
||||
procedure TMainForm.ActiveGameChanged;
|
||||
begin
|
||||
ClearUIBindings;
|
||||
vstMapList.Clear;
|
||||
|
||||
if Assigned(ActiveGame) and (not ActiveGame.Loaded) then
|
||||
ActiveGame.Load;
|
||||
|
||||
// #ToDo1 -oMvR: 30-6-2014: attach observer to monitor changes
|
||||
|
||||
@ -317,6 +343,15 @@ begin
|
||||
end;
|
||||
|
||||
|
||||
procedure TMainForm.UpdateGameList;
|
||||
begin
|
||||
vstGames.NodeDataSize := SizeOf(TCustomGame);
|
||||
vstGames.RootNodeCount := TGameList.Instance.Count;
|
||||
|
||||
pnlGamesWarning.Visible := (TGameList.Instance.Count = 0);
|
||||
end;
|
||||
|
||||
|
||||
procedure TMainForm.SetActiveGame(const Value: TCustomGame);
|
||||
begin
|
||||
if Value <> FActiveGame then
|
||||
@ -364,14 +399,81 @@ var
|
||||
begin
|
||||
if TGameForm.Insert(Self, game) then
|
||||
begin
|
||||
//
|
||||
TGameList.Instance.Add(game);
|
||||
|
||||
// #ToDo1 -oMvR: 30-6-2014: move to shared spot
|
||||
TGameListPersist.Save(Resources.GetUserDataPath(Resources.UserGamesFileName), TGameList.Instance);
|
||||
UpdateGameList;
|
||||
|
||||
ActiveGame := game;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
procedure TMainForm.actGameRemoveExecute(Sender: TObject);
|
||||
var
|
||||
nodeData: PCustomGame;
|
||||
|
||||
begin
|
||||
//
|
||||
if not Assigned(vstGames.FocusedNode) then
|
||||
exit;
|
||||
|
||||
nodeData := vstGames.GetNodeData(vstGames.FocusedNode);
|
||||
if MessageBox(Self.Handle, 'Do you want to remove the selected game?', 'Remove', MB_YESNO or MB_ICONQUESTION) = ID_YES then
|
||||
begin
|
||||
vstGames.BeginUpdate;
|
||||
try
|
||||
TGameList.Instance.Remove(nodeData^);
|
||||
|
||||
if nodeData^ = ActiveGame then
|
||||
begin
|
||||
if TGameList.Instance.Count > 0 then
|
||||
ActiveGame := TGameList.Instance.First
|
||||
else
|
||||
ActiveGame := nil;
|
||||
end;
|
||||
|
||||
// #ToDo1 -oMvR: 30-6-2014: move to shared spot
|
||||
TGameListPersist.Save(Resources.GetUserDataPath(Resources.UserGamesFileName), TGameList.Instance);
|
||||
|
||||
UpdateGameList;
|
||||
finally
|
||||
vstGames.EndUpdate;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
procedure TMainForm.vstGamesInitNode(Sender: TBaseVirtualTree; ParentNode, Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
|
||||
var
|
||||
nodeData: PCustomGame;
|
||||
|
||||
begin
|
||||
nodeData := Sender.GetNodeData(Node);
|
||||
nodeData^ := TGameList.Instance[Node^.Index];
|
||||
end;
|
||||
|
||||
|
||||
procedure TMainForm.vstGamesGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
|
||||
var
|
||||
nodeData: PCustomGame;
|
||||
|
||||
begin
|
||||
nodeData := Sender.GetNodeData(Node);
|
||||
|
||||
case Column of
|
||||
GameColumnName:
|
||||
CellText := nodeData^.GameName;
|
||||
|
||||
GameColumnLocation:
|
||||
CellText := nodeData^.Location;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
procedure TMainForm.vstGamesFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex);
|
||||
begin
|
||||
actGameRemove.Enabled := Assigned(Sender.FocusedNode);
|
||||
end;
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user