Updated profile templates to be a dictionary, cleaned up all uses of it throughout code
Added `profileHelper.GetProfileTemplateForSide`
This commit is contained in:
@@ -1,4 +1,3 @@
|
|||||||
using SPTarkov.Common.Extensions;
|
|
||||||
using SPTarkov.DI.Annotations;
|
using SPTarkov.DI.Annotations;
|
||||||
using SPTarkov.Server.Core.Helpers;
|
using SPTarkov.Server.Core.Helpers;
|
||||||
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
|
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
|
||||||
@@ -38,43 +37,30 @@ public class LauncherController(
|
|||||||
public ConnectResponse Connect()
|
public ConnectResponse Connect()
|
||||||
{
|
{
|
||||||
// Get all possible profile types + filter out any that are blacklisted
|
// Get all possible profile types + filter out any that are blacklisted
|
||||||
|
var profileTemplates = _databaseService.GetProfileTemplates()
|
||||||
var profiles = typeof(ProfileTemplates).GetProperties()
|
.Where(profile => !_coreConfig.Features.CreateNewProfileTypesBlacklist.Contains(profile.Key))
|
||||||
.Where(p => p.CanWrite)
|
.ToDictionary();
|
||||||
.Select(p => p.GetJsonName())
|
|
||||||
.Where(profileName => !_coreConfig.Features.CreateNewProfileTypesBlacklist.Contains(profileName))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
return new ConnectResponse
|
return new ConnectResponse
|
||||||
{
|
{
|
||||||
BackendUrl = _httpServerHelper.GetBackendUrl(),
|
BackendUrl = _httpServerHelper.GetBackendUrl(),
|
||||||
Name = _coreConfig.ServerName,
|
Name = _coreConfig.ServerName,
|
||||||
Editions = profiles,
|
Editions = profileTemplates.Select(x => x.Key).ToList(),
|
||||||
ProfileDescriptions = GetProfileDescriptions()
|
ProfileDescriptions = GetProfileDescriptions(profileTemplates)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get descriptive text for each of the profile editions a player can choose, keyed by profile.json profile type e.g. "Edge Of Darkness"
|
/// Get descriptive text for each of the profile editions a player can choose, keyed by profile.json profile type e.g. "Edge Of Darkness"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="profileTemplates">Profiles to get descriptions of</param>
|
||||||
/// <returns>Dictionary of profile types with related descriptive text</returns>
|
/// <returns>Dictionary of profile types with related descriptive text</returns>
|
||||||
protected Dictionary<string, string> GetProfileDescriptions()
|
protected Dictionary<string, string> GetProfileDescriptions(Dictionary<string, ProfileSides> profileTemplates)
|
||||||
{
|
{
|
||||||
var result = new Dictionary<string, string>();
|
var result = new Dictionary<string, string>();
|
||||||
var dbProfiles = _databaseService.GetProfiles();
|
foreach (var (profileKey, profile) in profileTemplates)
|
||||||
foreach (var templatesProperty in typeof(ProfileTemplates).GetProperties()
|
|
||||||
.Where(p => p.CanWrite
|
|
||||||
&& !string.Equals(p.Name, "extensiondata", StringComparison.InvariantCultureIgnoreCase)))
|
|
||||||
{
|
{
|
||||||
var propertyValue = templatesProperty.GetValue(dbProfiles);
|
result.TryAdd(profileKey, _localisationService.GetText(profile.DescriptionLocaleKey));
|
||||||
if (propertyValue == null)
|
|
||||||
{
|
|
||||||
_logger.Warning(_localisationService.GetText("launcher-missing_property", templatesProperty.Name));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var casterPropertyValue = propertyValue as ProfileSides;
|
|
||||||
result[templatesProperty.GetJsonName()] = _localisationService.GetText(casterPropertyValue?.DescriptionLocaleKey!);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
using SPTarkov.Common.Extensions;
|
|
||||||
using SPTarkov.DI.Annotations;
|
using SPTarkov.DI.Annotations;
|
||||||
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
|
|
||||||
using SPTarkov.Server.Core.Models.Eft.Launcher;
|
using SPTarkov.Server.Core.Models.Eft.Launcher;
|
||||||
using SPTarkov.Server.Core.Models.Eft.Profile;
|
using SPTarkov.Server.Core.Models.Eft.Profile;
|
||||||
using SPTarkov.Server.Core.Models.Spt.Config;
|
using SPTarkov.Server.Core.Models.Spt.Config;
|
||||||
@@ -46,21 +44,11 @@ public class LauncherV2Controller(
|
|||||||
public Dictionary<string, string> Types()
|
public Dictionary<string, string> Types()
|
||||||
{
|
{
|
||||||
var result = new Dictionary<string, string>();
|
var result = new Dictionary<string, string>();
|
||||||
var dbProfiles = _databaseService.GetProfiles();
|
var dbProfiles = _databaseService.GetProfileTemplates();
|
||||||
|
|
||||||
foreach (var templatesProperty in typeof(ProfileTemplates).GetProperties()
|
foreach (var profileKvP in dbProfiles)
|
||||||
.Where(p => p.CanWrite
|
|
||||||
&& !string.Equals(p.Name, "extensiondata", StringComparison.InvariantCultureIgnoreCase)))
|
|
||||||
{
|
{
|
||||||
var propertyValue = templatesProperty.GetValue(dbProfiles);
|
result.TryAdd(profileKvP.Key, _localisationService.GetText(profileKvP.Value.DescriptionLocaleKey));
|
||||||
if (propertyValue == null)
|
|
||||||
{
|
|
||||||
_logger.Warning(_localisationService.GetText("launcher-missing_property", templatesProperty.Name));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var casterPropertyValue = propertyValue as ProfileSides;
|
|
||||||
result[templatesProperty.GetJsonName()] = _localisationService.GetText(casterPropertyValue?.DescriptionLocaleKey!);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
using SPTarkov.Common.Extensions;
|
|
||||||
using SPTarkov.DI.Annotations;
|
using SPTarkov.DI.Annotations;
|
||||||
using SPTarkov.Server.Core.Models.Eft.Common;
|
using SPTarkov.Server.Core.Models.Eft.Common;
|
||||||
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
|
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
|
||||||
using SPTarkov.Server.Core.Models.Eft.Profile;
|
using SPTarkov.Server.Core.Models.Eft.Profile;
|
||||||
using SPTarkov.Server.Core.Models.Spt.Config;
|
using SPTarkov.Server.Core.Models.Spt.Config;
|
||||||
using SPTarkov.Server.Core.Servers;
|
using SPTarkov.Server.Core.Servers;
|
||||||
using SPTarkov.Server.Core.Services;
|
|
||||||
using SPTarkov.Server.Core.Utils;
|
using SPTarkov.Server.Core.Utils;
|
||||||
using BodyPartHealth = SPTarkov.Server.Core.Models.Eft.Common.Tables.BodyPartHealth;
|
using BodyPartHealth = SPTarkov.Server.Core.Models.Eft.Common.Tables.BodyPartHealth;
|
||||||
using Vitality = SPTarkov.Server.Core.Models.Eft.Profile.Vitality;
|
using Vitality = SPTarkov.Server.Core.Models.Eft.Profile.Vitality;
|
||||||
@@ -16,7 +14,7 @@ namespace SPTarkov.Server.Core.Helpers;
|
|||||||
public class HealthHelper(
|
public class HealthHelper(
|
||||||
TimeUtil _timeUtil,
|
TimeUtil _timeUtil,
|
||||||
SaveServer _saveServer,
|
SaveServer _saveServer,
|
||||||
DatabaseService _databaseService,
|
ProfileHelper _profileHelper,
|
||||||
ConfigServer _configServer
|
ConfigServer _configServer
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
@@ -128,8 +126,6 @@ public class HealthHelper(
|
|||||||
/// <param name="postRaidHealth">Post raid data</param>
|
/// <param name="postRaidHealth">Post raid data</param>
|
||||||
/// <param name="sessionID">Session id</param>
|
/// <param name="sessionID">Session id</param>
|
||||||
/// <param name="isDead">Is player dead</param>
|
/// <param name="isDead">Is player dead</param>
|
||||||
/// <param name="addEffects">Should effects be added to profile (default - true)</param>
|
|
||||||
/// <param name="deleteExistingEffects">Should all prior effects be removed before apply new ones (default - true)</param>
|
|
||||||
public void UpdateProfileHealthPostRaid(
|
public void UpdateProfileHealthPostRaid(
|
||||||
PmcData pmcData,
|
PmcData pmcData,
|
||||||
BotBaseHealth postRaidHealth,
|
BotBaseHealth postRaidHealth,
|
||||||
@@ -139,16 +135,14 @@ public class HealthHelper(
|
|||||||
var fullProfile = _saveServer.GetProfile(sessionID);
|
var fullProfile = _saveServer.GetProfile(sessionID);
|
||||||
var profileEdition = fullProfile.ProfileInfo.Edition;
|
var profileEdition = fullProfile.ProfileInfo.Edition;
|
||||||
var profileSide = fullProfile.CharacterData.PmcData.Info.Side;
|
var profileSide = fullProfile.CharacterData.PmcData.Info.Side;
|
||||||
|
|
||||||
|
// Get matching 'side e.g. USEC
|
||||||
|
var matchingSide = _profileHelper.GetProfileTemplateForSide(profileEdition, profileSide);
|
||||||
|
|
||||||
var defaultTemperature =
|
var defaultTemperature = matchingSide?.Character?.Health?.Temperature ?? new CurrentMinMax
|
||||||
_databaseService.GetProfiles()
|
{
|
||||||
.GetByJsonProp<ProfileSides>(profileEdition)
|
Current = 36.6
|
||||||
.GetByJsonProp<TemplateSide>(profileSide.ToLower())
|
};
|
||||||
?.Character?.Health?.Temperature ??
|
|
||||||
new CurrentMinMax
|
|
||||||
{
|
|
||||||
Current = 36.6
|
|
||||||
};
|
|
||||||
|
|
||||||
StoreHydrationEnergyTempInProfile(
|
StoreHydrationEnergyTempInProfile(
|
||||||
fullProfile,
|
fullProfile,
|
||||||
|
|||||||
@@ -754,4 +754,23 @@ public class ProfileHelper(
|
|||||||
fullProfile.SptData.ExtraRepeatableQuests[repeatableId] += rewardValue;
|
fullProfile.SptData.ExtraRepeatableQuests[repeatableId] += rewardValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a profile template by the account and side
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="accountEdition">Edition of profile desired, e.g. "Standard"</param>
|
||||||
|
/// <param name="side">Side of profile desired, e.g. "Bear"</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public TemplateSide GetProfileTemplateForSide(string accountEdition, string side)
|
||||||
|
{
|
||||||
|
var profileTemplates = _databaseService.GetProfileTemplates();
|
||||||
|
|
||||||
|
// Get matching profile 'type' e.g. 'standard'
|
||||||
|
profileTemplates.TryGetValue(accountEdition, out var matchingProfileTemplate);
|
||||||
|
|
||||||
|
// Get matching profile by 'side' e.g. USEC
|
||||||
|
return string.Equals(side, "bear", StringComparison.OrdinalIgnoreCase)
|
||||||
|
? matchingProfileTemplate.Bear
|
||||||
|
: matchingProfileTemplate.Usec;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using SPTarkov.Common.Extensions;
|
using System.Security.Principal;
|
||||||
using SPTarkov.DI.Annotations;
|
using SPTarkov.DI.Annotations;
|
||||||
using SPTarkov.Server.Core.Models.Common;
|
using SPTarkov.Server.Core.Models.Common;
|
||||||
using SPTarkov.Server.Core.Models.Eft.Common;
|
using SPTarkov.Server.Core.Models.Eft.Common;
|
||||||
@@ -136,7 +136,7 @@ public class TraderHelper(
|
|||||||
/// <param name="traderID">trader id to reset</param>
|
/// <param name="traderID">trader id to reset</param>
|
||||||
public void ResetTrader(string sessionID, string traderID)
|
public void ResetTrader(string sessionID, string traderID)
|
||||||
{
|
{
|
||||||
var profiles = _databaseService.GetProfiles();
|
var profiles = _databaseService.GetProfileTemplates();
|
||||||
var trader = _databaseService.GetTrader(traderID);
|
var trader = _databaseService.GetTrader(traderID);
|
||||||
|
|
||||||
var fullProfile = _profileHelper.GetFullProfile(sessionID);
|
var fullProfile = _profileHelper.GetFullProfile(sessionID);
|
||||||
@@ -145,34 +145,34 @@ public class TraderHelper(
|
|||||||
throw new Exception(_localisationService.GetText("trader-unable_to_find_profile_by_id", sessionID));
|
throw new Exception(_localisationService.GetText("trader-unable_to_find_profile_by_id", sessionID));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get matching profile 'type' e.g. 'standard'
|
||||||
|
profiles.TryGetValue(fullProfile.ProfileInfo.Edition, out var matchingProfileTemplate);
|
||||||
var pmcData = fullProfile.CharacterData.PmcData;
|
var pmcData = fullProfile.CharacterData.PmcData;
|
||||||
var rawProfileTemplate = profiles.GetByJsonProp<ProfileSides>(fullProfile.ProfileInfo.Edition)
|
var matchingSide = _profileHelper.GetProfileTemplateForSide(fullProfile.ProfileInfo.Edition, pmcData.Info.Side);
|
||||||
.GetByJsonProp<TemplateSide>(pmcData.Info.Side.ToLower())
|
|
||||||
.Trader;
|
// Profiles trader settings
|
||||||
|
var profileTemplateTraderData = matchingSide.Trader;
|
||||||
|
|
||||||
var newTraderData = new TraderInfo
|
var newTraderData = new TraderInfo
|
||||||
{
|
{
|
||||||
Disabled = false,
|
Disabled = false,
|
||||||
LoyaltyLevel = rawProfileTemplate.InitialLoyaltyLevel.GetValueOrDefault(traderID, 1),
|
LoyaltyLevel = profileTemplateTraderData.InitialLoyaltyLevel.GetValueOrDefault(traderID, 1),
|
||||||
SalesSum = rawProfileTemplate.InitialSalesSum,
|
SalesSum = profileTemplateTraderData.InitialSalesSum,
|
||||||
Standing = GetStartingStanding(traderID, rawProfileTemplate),
|
Standing = GetStartingStanding(traderID, profileTemplateTraderData),
|
||||||
NextResupply = trader.Base.NextResupply,
|
NextResupply = trader.Base.NextResupply,
|
||||||
Unlocked = trader.Base.UnlockedByDefault
|
Unlocked = trader.Base.UnlockedByDefault
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!pmcData.TradersInfo.TryAdd(traderID, newTraderData))
|
// Add trader to profile if it doesn't already
|
||||||
{
|
pmcData.TradersInfo.TryAdd(traderID, newTraderData);
|
||||||
pmcData.TradersInfo[traderID] = newTraderData;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Check if trader should be locked by default
|
// Check if trader should be locked by default
|
||||||
if (rawProfileTemplate.LockedByDefaultOverride?.Contains(traderID) ?? false)
|
if (profileTemplateTraderData.LockedByDefaultOverride?.Contains(traderID) ?? false)
|
||||||
{
|
{
|
||||||
pmcData.TradersInfo[traderID].Unlocked = true;
|
pmcData.TradersInfo[traderID].Unlocked = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rawProfileTemplate.PurchaseAllClothingByDefaultForTrader?.Contains(traderID) ?? false)
|
if (profileTemplateTraderData.PurchaseAllClothingByDefaultForTrader?.Contains(traderID) ?? false)
|
||||||
{
|
{
|
||||||
// Get traders clothing
|
// Get traders clothing
|
||||||
var clothing = _databaseService.GetTrader(traderID).Suits;
|
var clothing = _databaseService.GetTrader(traderID).Suits;
|
||||||
@@ -186,16 +186,18 @@ public class TraderHelper(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((rawProfileTemplate.FleaBlockedDays ?? 0) > 0)
|
// Template has flea block
|
||||||
|
if ((profileTemplateTraderData.FleaBlockedDays ?? 0) > 0)
|
||||||
{
|
{
|
||||||
var newBanDateTime = _timeUtil.GetTimeStampFromNowDays(rawProfileTemplate.FleaBlockedDays ?? 0);
|
var newBanDateTime = _timeUtil.GetTimeStampFromNowDays(profileTemplateTraderData.FleaBlockedDays ?? 0);
|
||||||
var existingBan = pmcData.Info.Bans.FirstOrDefault(ban => ban.BanType == BanType.RagFair);
|
var existingBan = pmcData.Info.Bans?.FirstOrDefault(ban => ban.BanType == BanType.RagFair);
|
||||||
if (existingBan is not null)
|
if (existingBan is not null)
|
||||||
{
|
{
|
||||||
existingBan.DateTime = newBanDateTime;
|
existingBan.DateTime = newBanDateTime;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
pmcData.Info.Bans ??= [];
|
||||||
pmcData.Info.Bans.Add(
|
pmcData.Info.Bans.Add(
|
||||||
new Ban
|
new Ban
|
||||||
{
|
{
|
||||||
@@ -208,7 +210,7 @@ public class TraderHelper(
|
|||||||
|
|
||||||
if (traderID == Traders.JAEGER)
|
if (traderID == Traders.JAEGER)
|
||||||
{
|
{
|
||||||
pmcData.TradersInfo[traderID].Unlocked = rawProfileTemplate.JaegerUnlocked;
|
pmcData.TradersInfo[traderID].Unlocked = profileTemplateTraderData.JaegerUnlocked;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,75 +3,6 @@ using SPTarkov.Server.Core.Models.Eft.Profile;
|
|||||||
|
|
||||||
namespace SPTarkov.Server.Core.Models.Eft.Common.Tables;
|
namespace SPTarkov.Server.Core.Models.Eft.Common.Tables;
|
||||||
|
|
||||||
public record ProfileTemplates
|
|
||||||
{
|
|
||||||
[JsonExtensionData]
|
|
||||||
public Dictionary<string, object> ExtensionData { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("Standard")]
|
|
||||||
public ProfileSides? Standard
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonPropertyName("Left Behind")]
|
|
||||||
public ProfileSides? LeftBehind
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonPropertyName("Prepare To Escape")]
|
|
||||||
public ProfileSides? PrepareToEscape
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonPropertyName("Edge Of Darkness")]
|
|
||||||
public ProfileSides? EdgeOfDarkness
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonPropertyName("Unheard")]
|
|
||||||
public ProfileSides? Unheard
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonPropertyName("Tournament")]
|
|
||||||
public ProfileSides? Tournament
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonPropertyName("SPT Developer")]
|
|
||||||
public ProfileSides? SPTDeveloper
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonPropertyName("SPT Easy start")]
|
|
||||||
public ProfileSides? SPTEasyStart
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonPropertyName("SPT Zero to hero")]
|
|
||||||
public ProfileSides? SPTZeroToHero
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public record ProfileSides
|
public record ProfileSides
|
||||||
{
|
{
|
||||||
[JsonExtensionData]
|
[JsonExtensionData]
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ public record Templates
|
|||||||
/// The profile templates listed in the launcher on profile creation, split by account type (e.g. Standard) then side (e.g. bear/usec)
|
/// The profile templates listed in the launcher on profile creation, split by account type (e.g. Standard) then side (e.g. bear/usec)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonPropertyName("profiles")]
|
[JsonPropertyName("profiles")]
|
||||||
public ProfileTemplates? Profiles
|
public Dictionary<string, ProfileSides>? Profiles
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
|
|||||||
@@ -43,9 +43,8 @@ public class CreateProfileService(
|
|||||||
public string CreateProfile(string sessionId, ProfileCreateRequestData request)
|
public string CreateProfile(string sessionId, ProfileCreateRequestData request)
|
||||||
{
|
{
|
||||||
var account = _cloner.Clone(_saveServer.GetProfile(sessionId));
|
var account = _cloner.Clone(_saveServer.GetProfile(sessionId));
|
||||||
var profileTemplateClone = _cloner.Clone(
|
var profileTemplateClone = _cloner.Clone(_profileHelper.GetProfileTemplateForSide(account.ProfileInfo.Edition, request.Side));
|
||||||
_databaseService.GetProfiles()?.GetByJsonProp<ProfileSides>(account.ProfileInfo.Edition)?.GetByJsonProp<TemplateSide>(request.Side.ToLower())
|
|
||||||
);
|
|
||||||
var pmcData = profileTemplateClone.Character;
|
var pmcData = profileTemplateClone.Character;
|
||||||
|
|
||||||
// Delete existing profile
|
// Delete existing profile
|
||||||
|
|||||||
@@ -257,7 +257,7 @@ public class DatabaseService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <returns> assets/database/templates/profiles.json </returns>
|
/// <returns> assets/database/templates/profiles.json </returns>
|
||||||
public ProfileTemplates GetProfiles()
|
public Dictionary<string, ProfileSides> GetProfileTemplates()
|
||||||
{
|
{
|
||||||
if (_databaseServer.GetTables().Templates?.Profiles == null)
|
if (_databaseServer.GetTables().Templates?.Profiles == null)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user