feat(launcher): Campaign LA add Avalonia desktop shell
This commit is contained in:
parent
6c4cd2bbc6
commit
d0a9c65d85
31 changed files with 4831 additions and 16 deletions
|
|
@ -325,13 +325,59 @@ public sealed class LauncherProfileStore
|
|||
server.Accounts.Remove(profile);
|
||||
}
|
||||
|
||||
// --- Character settings (roster-driven add/remove; user-edited settings) ---
|
||||
// --- Character CRUD / user-owned settings --------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Edits the user-owned settings of an existing character row. There
|
||||
/// is no manual add/remove for characters — the roster (
|
||||
/// <see cref="MergeRoster"/>) is the only source of new rows, per
|
||||
/// spec §5/§6.
|
||||
/// Adds a manually configured character row. Normal operation discovers
|
||||
/// characters through <see cref="MergeRoster"/>, but LA4's full in-UI
|
||||
/// CRUD contract also lets a user create a cached row before a successful
|
||||
/// probe (for example, to launch by a known character name while a server
|
||||
/// is temporarily unavailable). A later roster merge remains
|
||||
/// authoritative for the id/name pair and preserves these user settings.
|
||||
/// </summary>
|
||||
public CharacterProfile AddCharacter(
|
||||
string serverName,
|
||||
string account,
|
||||
string characterName,
|
||||
string? id = null,
|
||||
LaunchMode launchMode = LaunchMode.GuiSelect,
|
||||
IReadOnlyList<string>? plugins = null,
|
||||
IReadOnlyList<string>? loginCommands = null)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(characterName);
|
||||
ServerProfile server = FindServerOrThrow(serverName);
|
||||
AccountProfile profile = FindAccountOrThrow(server, account);
|
||||
|
||||
if (FindCharacter(profile, characterName) is not null)
|
||||
{
|
||||
throw new LauncherProfileException(
|
||||
$"Character '{characterName}' already exists on account '{account}'.");
|
||||
}
|
||||
|
||||
string? normalizedId = NormalizeCharacterId(id);
|
||||
if (normalizedId is not null
|
||||
&& profile.Characters.Any(character => CharacterIdsEqual(character.Id, normalizedId)))
|
||||
{
|
||||
throw new LauncherProfileException(
|
||||
$"Character id '{normalizedId}' already exists on account '{account}'.");
|
||||
}
|
||||
|
||||
var character = new CharacterProfile
|
||||
{
|
||||
Name = characterName,
|
||||
Id = normalizedId,
|
||||
LaunchMode = launchMode,
|
||||
Plugins = plugins is null ? [] : [.. plugins],
|
||||
LoginCommands = loginCommands is null ? [] : [.. loginCommands],
|
||||
};
|
||||
profile.Characters.Add(character);
|
||||
return character;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Edits the identity cache and/or user-owned settings of an existing
|
||||
/// character row. Passing an empty <paramref name="newId"/> clears a
|
||||
/// manually entered id so launches fall back to the character name.
|
||||
/// </summary>
|
||||
public void EditCharacter(
|
||||
string serverName,
|
||||
|
|
@ -339,12 +385,42 @@ public sealed class LauncherProfileStore
|
|||
string characterName,
|
||||
LaunchMode? launchMode = null,
|
||||
IReadOnlyList<string>? plugins = null,
|
||||
IReadOnlyList<string>? loginCommands = null)
|
||||
IReadOnlyList<string>? loginCommands = null,
|
||||
string? newName = null,
|
||||
string? newId = null)
|
||||
{
|
||||
ServerProfile server = FindServerOrThrow(serverName);
|
||||
AccountProfile profile = FindAccountOrThrow(server, account);
|
||||
CharacterProfile character = FindCharacterOrThrow(profile, characterName);
|
||||
|
||||
if (newName is not null)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(newName);
|
||||
if (!string.Equals(newName, character.Name, StringComparison.Ordinal)
|
||||
&& FindCharacter(profile, newName) is not null)
|
||||
{
|
||||
throw new LauncherProfileException(
|
||||
$"Character '{newName}' already exists on account '{account}'.");
|
||||
}
|
||||
|
||||
character.Name = newName;
|
||||
}
|
||||
|
||||
if (newId is not null)
|
||||
{
|
||||
string? normalizedId = NormalizeCharacterId(newId);
|
||||
if (normalizedId is not null
|
||||
&& profile.Characters.Any(candidate =>
|
||||
!ReferenceEquals(candidate, character)
|
||||
&& CharacterIdsEqual(candidate.Id, normalizedId)))
|
||||
{
|
||||
throw new LauncherProfileException(
|
||||
$"Character id '{normalizedId}' already exists on account '{account}'.");
|
||||
}
|
||||
|
||||
character.Id = normalizedId;
|
||||
}
|
||||
|
||||
if (launchMode is not null)
|
||||
{
|
||||
character.LaunchMode = launchMode.Value;
|
||||
|
|
@ -361,6 +437,17 @@ public sealed class LauncherProfileStore
|
|||
}
|
||||
}
|
||||
|
||||
public void RemoveCharacter(
|
||||
string serverName,
|
||||
string account,
|
||||
string characterName)
|
||||
{
|
||||
ServerProfile server = FindServerOrThrow(serverName);
|
||||
AccountProfile profile = FindAccountOrThrow(server, account);
|
||||
CharacterProfile character = FindCharacterOrThrow(profile, characterName);
|
||||
profile.Characters.Remove(character);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Folds a reported character roster into an account's
|
||||
/// <see cref="AccountProfile.Characters"/> (Campaign LA spec §3/§5/
|
||||
|
|
@ -456,20 +543,46 @@ public sealed class LauncherProfileStore
|
|||
$"No account '{account}' on server '{server.Name}'.");
|
||||
}
|
||||
|
||||
private static CharacterProfile? FindCharacter(
|
||||
AccountProfile profile,
|
||||
string characterName) =>
|
||||
profile.Characters.Find(
|
||||
character => string.Equals(
|
||||
character.Name,
|
||||
characterName,
|
||||
StringComparison.Ordinal));
|
||||
|
||||
private static CharacterProfile FindCharacterOrThrow(
|
||||
AccountProfile profile,
|
||||
string characterName)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(characterName);
|
||||
return profile.Characters.Find(
|
||||
character => string.Equals(
|
||||
character.Name,
|
||||
characterName,
|
||||
StringComparison.Ordinal))
|
||||
return FindCharacter(profile, characterName)
|
||||
?? throw new LauncherProfileException(
|
||||
$"No character '{characterName}' on account '{profile.Account}'.");
|
||||
}
|
||||
|
||||
private static string? NormalizeCharacterId(string? id)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(id))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!CharacterIdFormat.TryParse(id, out uint parsed) || parsed == 0)
|
||||
{
|
||||
throw new LauncherProfileException(
|
||||
"Character id must be a non-zero hexadecimal value with a 0x prefix.");
|
||||
}
|
||||
|
||||
return CharacterIdFormat.ToHexString(parsed);
|
||||
}
|
||||
|
||||
private static bool CharacterIdsEqual(string? left, string? right) =>
|
||||
CharacterIdFormat.TryParse(left, out uint leftId)
|
||||
&& CharacterIdFormat.TryParse(right, out uint rightId)
|
||||
&& leftId == rightId;
|
||||
|
||||
private static void RequireValidPort(int port)
|
||||
{
|
||||
if (port is < 1 or > 65535)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue