diff --git a/Bloodcraft.csproj b/Bloodcraft.csproj
index 6f35963..5acb000 100644
--- a/Bloodcraft.csproj
+++ b/Bloodcraft.csproj
@@ -1,19 +1,22 @@
ο»Ώ
-
- net6.0
- enable
- Bloodcraft
- 1.5.3
- True
-
- https://api.nuget.org/v3/index.json;
- https://nuget.bepinex.dev/v3/index.json;
-
- True
- io.zfolmt.Bloodcraft
- preview
- true
+
+ net6.0
+ enable
+ Bloodcraft
+ 1.5.3
+ True
+
+ https://api.nuget.org/v3/index.json;
+ https://nuget.bepinex.dev/v3/index.json;
+
+ True
+ io.zfolmt.Bloodcraft
+ preview
+ true
+ Exe
+ false
+ False
@@ -39,13 +42,10 @@
-
-
-
-
-
-
-
+
+
+
+
@@ -53,10 +53,9 @@
-
+
diff --git a/Commands/BloodCommands.cs b/Commands/BloodCommands.cs
index dbe39c0..16e7b66 100644
--- a/Commands/BloodCommands.cs
+++ b/Commands/BloodCommands.cs
@@ -13,7 +13,7 @@
namespace Bloodcraft.Commands;
-[CommandGroup("bloodlegacy", "bl")]
+[CommandGroup(name: "bloodlegacy", "bl")]
internal static class BloodCommands
{
static EntityManager EntityManager => Core.EntityManager;
diff --git a/Commands/ClassCommands.cs b/Commands/ClassCommands.cs
index 56efb1b..84fcf4f 100644
--- a/Commands/ClassCommands.cs
+++ b/Commands/ClassCommands.cs
@@ -11,7 +11,7 @@
namespace Bloodcraft.Commands;
-[CommandGroup("class")]
+[CommandGroup(name: "class")]
internal static class ClassCommands
{
static EntityManager EntityManager => Core.EntityManager;
diff --git a/Commands/FamiliarCommands.cs b/Commands/FamiliarCommands.cs
index adb37c4..2e6c876 100644
--- a/Commands/FamiliarCommands.cs
+++ b/Commands/FamiliarCommands.cs
@@ -2,7 +2,6 @@
using Bloodcraft.Patches;
using Bloodcraft.Services;
using Bloodcraft.Utilities;
-using Il2CppInterop.Runtime;
using ProjectM;
using ProjectM.Network;
using ProjectM.Scripting;
@@ -30,8 +29,6 @@ internal static class FamiliarCommands
static SystemService SystemService => Core.SystemService;
static PrefabCollectionSystem PrefabCollectionSystem => SystemService.PrefabCollectionSystem;
- static readonly PrefabGUID CombatBuff = new(581443919);
- static readonly PrefabGUID PvPCombatBuff = new(697095869);
static readonly PrefabGUID DominateBuff = new(-1447419822);
static readonly PrefabGUID TakeFlightBuff = new(1205505492);
@@ -41,21 +38,6 @@ internal static class FamiliarCommands
{"Shiny", FamiliarUtilities.ToggleShinies}
};
- static readonly ComponentType[] NetworkEventComponents =
- [
- ComponentType.ReadOnly(Il2CppType.Of()),
- ComponentType.ReadOnly(Il2CppType.Of()),
- ComponentType.ReadOnly(Il2CppType.Of()),
- ComponentType.ReadOnly(Il2CppType.Of())
- ];
-
- static readonly NetworkEventType EventType = new()
- {
- IsAdminEvent = false,
- EventId = NetworkEvents.EventId_RenameInteractable,
- IsDebugEvent = false
- };
-
[Command(name: "bind", shortHand: "b", adminOnly: false, usage: ".fam b [#]", description: "Activates specified familiar from current list.")]
public static void BindFamiliar(ChatCommandContext ctx, int boxIndex)
{
@@ -72,151 +54,6 @@ public static void BindFamiliar(ChatCommandContext ctx, int boxIndex)
FamiliarUtilities.BindFamiliar(character, userEntity, steamId, boxIndex);
}
- //[Command(name: "forcebind", shortHand: "fb", adminOnly: true, usage: ".fam fb [Name] [Box] [#]", description: "Activates specified familiar from entered player box.")]
- public static void ForceBindFamiliar(ChatCommandContext ctx, string name, string box, int choice)
- {
- if (!ConfigService.FamiliarSystem)
- {
- LocalizationService.HandleReply(ctx, "Familiars are not enabled.");
- return;
- }
-
- PlayerInfo playerInfo = PlayerCache.FirstOrDefault(kvp => kvp.Key.ToLower() == name.ToLower()).Value;
- if (!playerInfo.UserEntity.Exists())
- {
- ctx.Reply($"Couldn't find player.");
- return;
- }
-
- Entity character = playerInfo.CharEntity;
- Entity userEntity = playerInfo.UserEntity;
- ulong steamId = playerInfo.User.PlatformId;
-
- Entity familiar = FamiliarUtilities.FindPlayerFamiliar(character);
-
- /* skip this for forcebind
- if (ServerGameManager.HasBuff(character, combatBuff.ToIdentifier()) || ServerGameManager.HasBuff(character, pvpCombatBuff.ToIdentifier()) || ServerGameManager.HasBuff(character, dominateBuff.ToIdentifier()))
- {
- LocalizationService.HandleReply(ctx, "You can't bind a familiar while in combat or dominating presence is active.");
- return;
- }
- */
-
- // this is still a good check though
- if (familiar.Exists())
- {
- LocalizationService.HandleReply(ctx, $"{playerInfo.User.CharacterName.Value} already has an active familiar.");
- return;
- }
-
- UnlockedFamiliarData unlocksData = LoadUnlockedFamiliars(steamId);
-
- string set = unlocksData.UnlockedFamiliars.ContainsKey(box) ? box : "";
- if (string.IsNullOrEmpty(set))
- {
- LocalizationService.HandleReply(ctx, $"Couldn't find box for {playerInfo.User.CharacterName.Value}. List player boxes by entering '.fam lpf [Name]' without a following specific box.");
- return;
- }
-
- if (steamId.TryGetFamiliarActives(out var data) && data.Familiar.Equals(Entity.Null) && data.FamKey.Equals(0) && unlocksData.UnlockedFamiliars.TryGetValue(set, out var famKeys))
- {
- if (choice < 1 || choice > famKeys.Count)
- {
- LocalizationService.HandleReply(ctx, $"Invalid choice, please use 1 to {famKeys.Count} (Current List: {set})");
- return;
- }
-
- PlayerUtilities.SetPlayerBool(steamId, "Binding", true);
- steamId.SetFamiliarDefault(choice);
-
- data = new(Entity.Null, famKeys[choice - 1]);
- steamId.SetFamiliarActives(data);
-
- SummonFamiliar(character, userEntity, famKeys[choice - 1]);
- }
- else
- {
- LocalizationService.HandleReply(ctx, "Couldn't find familiar or familiar already active.");
- }
- }
-
- //[Command(name: "listplayerfams", shortHand: "lpf", adminOnly: true, usage: ".fam lpf [Name] [Box]", description: "Lists unlocked familiars from players active box if entered and found or list all player boxes if left blank.")]
- public static void ListPlayerFamiliars(ChatCommandContext ctx, string name, string box = "")
- {
- if (!ConfigService.FamiliarSystem)
- {
- LocalizationService.HandleReply(ctx, "Familiars are not enabled.");
- return;
- }
-
- PlayerInfo playerInfo = PlayerCache.FirstOrDefault(kvp => kvp.Key.ToLower() == name.ToLower()).Value;
- if (!playerInfo.UserEntity.Exists())
- {
- ctx.Reply($"Couldn't find player.");
- return;
- }
-
- ulong steamId = playerInfo.User.PlatformId;
-
- if (string.IsNullOrEmpty(box))
- {
- UnlockedFamiliarData data = LoadUnlockedFamiliars(steamId);
-
- if (data.UnlockedFamiliars.Keys.Count > 0)
- {
- List sets = [];
- foreach (var key in data.UnlockedFamiliars.Keys)
- {
- sets.Add(key);
- }
-
- string fams = string.Join(", ", sets.Select(set => $"{set}"));
- LocalizationService.HandleReply(ctx, $"Familiar Boxes for {playerInfo.User.CharacterName.Value}: {fams}");
- }
- else
- {
- LocalizationService.HandleReply(ctx, $"{playerInfo.User.CharacterName.Value} doesn't have any unlocked familiars yet.");
- }
-
- return;
- }
- else
- {
- UnlockedFamiliarData unlocksData = LoadUnlockedFamiliars(steamId);
- FamiliarBuffsData buffsData = LoadFamiliarBuffs(steamId);
-
- string set = unlocksData.UnlockedFamiliars.ContainsKey(box) ? box : "";
- if (unlocksData.UnlockedFamiliars.TryGetValue(set, out var famKeys))
- {
- int count = 1;
-
- foreach (var famKey in famKeys)
- {
- PrefabGUID famPrefab = new(famKey);
- string famName = famPrefab.GetPrefabName();
- string colorCode = ""; // Default color for the asterisk
-
- // Check if the familiar has buffs and update the color based on RandomVisuals
- if (buffsData.FamiliarBuffs.ContainsKey(famKey))
- {
- // Look up the color from the RandomVisuals dictionary if it exists
- if (ShinyBuffColorHexMap.TryGetValue(new(buffsData.FamiliarBuffs[famKey][0]), out var hexColor))
- {
- colorCode = $"";
- }
- }
-
- LocalizationService.HandleReply(ctx, $"{count}: {famName}{(buffsData.FamiliarBuffs.ContainsKey(famKey) ? $"{colorCode}*" : "")}");
- count++;
- }
- }
- else
- {
- LocalizationService.HandleReply(ctx, "Couldn't locate player box.");
- }
- }
- }
-
[Command(name: "unbind", shortHand: "ub", adminOnly: false, usage: ".fam ub", description: "Destroys active familiar.")]
public static void UnbindFamiliar(ChatCommandContext ctx)
{
@@ -1109,49 +946,5 @@ public static void ToggleFamiliarSetting(ChatCommandContext ctx, string option)
LocalizationService.HandleReply(ctx, $"Invalid option. Please choose from the following: {validOptions}");
}
}
-
- //[Command(name: "name", shortHand: "n", adminOnly: false, usage: ".fam n [Name]", description: "testing")] does not work at all D:
- public static void NameFamiliar(ChatCommandContext ctx, string name)
- {
- if (!ConfigService.FamiliarSystem)
- {
- LocalizationService.HandleReply(ctx, "Familiars are not enabled.");
- return;
- }
-
- Entity familiar = FamiliarUtilities.FindPlayerFamiliar(ctx.Event.SenderCharacterEntity);
- familiar.Add();
-
- if (!familiar.Exists() || !familiar.Has())
- {
- LocalizationService.HandleReply(ctx, "Make sure familiar is active and present before naming it.");
- return;
- }
-
- /*
- SendEventToUser sendEventToUser = new()
- {
- UserIndex = ctx.Event.User.Index
- };
- networkEntity.Write(sendEventToUser);
- */
-
- FromCharacter fromCharacter = new()
- {
- Character = ctx.Event.SenderCharacterEntity,
- User = ctx.Event.SenderUserEntity
- };
-
- InteractEvents_Client.RenameInteractable renameInteractable = new() // named means they show their nameplate?
- {
- InteractableId = familiar.Read(),
- NewName = new Unity.Collections.FixedString64Bytes(name)
- };
-
- Entity networkEntity = EntityManager.CreateEntity(NetworkEventComponents);
- networkEntity.Write(fromCharacter);
- networkEntity.Write(EventType);
- networkEntity.Write(renameInteractable);
- }
}
diff --git a/Commands/QuestCommands.cs b/Commands/QuestCommands.cs
index 9cf67f6..306a284 100644
--- a/Commands/QuestCommands.cs
+++ b/Commands/QuestCommands.cs
@@ -17,8 +17,6 @@ internal static class QuestCommands
static EntityManager EntityManager => Core.EntityManager;
static ServerGameManager ServerGameManager => Core.ServerGameManager;
- static readonly PrefabGUID ImprisonedBuff = new(1603329680);
-
[Command(name: "log", adminOnly: false, usage: ".quest log", description: "Toggles quest progress logging.")]
public static void LogQuestCommand(ChatCommandContext ctx)
{
diff --git a/Commands/WeaponCommands.cs b/Commands/WeaponCommands.cs
index 792f0fa..a745919 100644
--- a/Commands/WeaponCommands.cs
+++ b/Commands/WeaponCommands.cs
@@ -4,13 +4,10 @@
using ProjectM;
using ProjectM.Scripting;
using ProjectM.Shared;
-using Steamworks;
using Stunlock.Core;
-using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
-using UnityEngine.TextCore.Text;
using VampireCommandFramework;
using static Bloodcraft.Services.PlayerService;
using static Bloodcraft.Systems.Expertise.WeaponManager;
diff --git a/GenerateREADME.cs b/GenerateREADME.cs
index e29b5d8..599f4cb 100644
--- a/GenerateREADME.cs
+++ b/GenerateREADME.cs
@@ -1,131 +1,341 @@
ο»Ώusing System.Text;
using System.Text.RegularExpressions;
+using static Bloodcraft.Services.ConfigService;
-namespace Bloodcraft;
-
-internal static class GenerateREADME
+namespace Bloodcraft
{
- // Paths set by the user or build script
- static string CommandsPath { get; set; }
- static string ReadMePath { get; set; }
+ internal static class GenerateREADME
+ {
+ // Paths set by the user or build script
+ static string CommandsPath { get; set; }
+ static string ReadMePath { get; set; }
- // Static regex patterns for parsing commands
- static readonly Regex CommandGroupRegex = new(@"\[CommandGroup\((?:name:\s*""(?[^""]+)""\s*,\s*)?""(?[^""]+)""(?:\s*,\s*""(?[^""]+)"")?\)\]");
- static readonly Regex CommandAttributeRegex = new(@"\[Command\((?:name:\s*""(?[^""]+)"")?(?:,\s*shortHand:\s*""(?[^""]+)"")?(?:,\s*adminOnly:\s*(?\w+))?(?:,\s*usage:\s*""(?[^""]+)"")?(?:,\s*description:\s*""(?[^""]+)"")?\)\]");
- static readonly Regex CommandSectionPattern = new(@"^(?!.*using\s+static).*?\b[A-Z][a-zA-Z]*Commands\b");
+ // Regex patterns for parsing commands
+ static readonly Regex CommandGroupRegex1 = new(@"\[CommandGroup\(name:\s*""(?[^""]+)"",\s*""(?[^""]+)""\)\]"); // the first and second one here should really just be one but this works and tired so leaving >_>
+ static readonly Regex CommandGroupRegex2 = new(@"\[CommandGroup\(name:\s*""(?[^""]+)""(?:\s*,\s*short:\s*""(?[^""]+)"")?\)\]");
+ static readonly Regex CommandAttributeRegex = new(@"\[Command\(name:\s*""(?[^""]+)""(?:,\s*shortHand:\s*""(?[^""]+)"")?(?:,\s*adminOnly:\s*(?\w+))?(?:,\s*usage:\s*""(?[^""]+)"")?(?:,\s*description:\s*""(?[^""]+)"")?\)\]");
- // Entry point for post-build invocation
- public static void Main(string[] args)
- {
- if (args.Length < 2)
+ // Constants for README sections
+ const string COMMANDS_HEADER = "## Commands";
+ const string CONFIG_HEADER = "## Configuration";
+
+ // We'll store all commands in this structure before outputting them
+ static readonly Dictionary<(string groupName, string groupShort), List<(string name, string shortHand, bool adminOnly, string usage, string description)>> commandsByGroup
+ = [];
+
+ // Entry point for post-build invocation
+ public static void Main(string[] args)
{
- Console.WriteLine("Usage: GenerateREADME ");
- return;
- }
+ // Check if we're running in a GitHub Actions environment and skip
+ if (Environment.GetEnvironmentVariable("GITHUB_ACTIONS") == "true")
+ {
+ Console.WriteLine("GenerateREADME skipped during GitHub Actions build.");
+ return;
+ }
- // Set the paths from the command-line arguments
- CommandsPath = args[0];
- ReadMePath = args[1];
+ if (args.Length < 2)
+ {
+ Console.WriteLine("Usage: GenerateREADME ");
+ return;
+ }
- Generate();
- }
+ CommandsPath = args[0];
+ ReadMePath = args[1];
- // Main method to generate the README
- static void Generate()
- {
- try
- {
- // Call the command generation logic
- GenerateCommandsSection();
- Console.WriteLine("README generated successfully.");
+ try
+ {
+ Generate();
+ Console.WriteLine("README generated successfully.");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error generating README: {ex.Message}");
+ }
}
- catch (Exception ex)
+
+ // Main method to generate the README
+ static void Generate()
{
- Console.WriteLine($"Error generating README: {ex.Message}");
+ CollectCommands();
+ var commandsSection = BuildCommandsSection();
+ var configSection = BuildConfigSection();
+ UpdateReadme(commandsSection, configSection);
}
- }
- // Method to generate the commands section of the README
- static void GenerateCommandsSection()
- {
- // Get all C# files from the CommandsPath
- string[] files = Directory.GetFiles(CommandsPath, "*.cs");
+ // Collect commands from all files into the dictionary
+ static void CollectCommands()
+ {
+ var files = Directory.GetFiles(CommandsPath, "*.cs");
- // StringBuilder to construct new Commands section
- StringBuilder commandsSection = new();
- commandsSection.AppendLine("## Commands");
+ foreach (var file in files)
+ {
+ var fileContent = File.ReadAllText(file);
+ var commandGroupMatch = CommandGroupRegex1.Match(fileContent);
- foreach (string file in files)
- {
- // Load the file content
- string[] fileLines = File.ReadAllLines(file);
+ if (!commandGroupMatch.Success) commandGroupMatch = CommandGroupRegex2.Match(fileContent);
- // Extract command section
- string commandSection = Regex.Replace(fileLines.First(line => CommandSectionPattern.IsMatch(line)), "(? CommandGroupRegex.IsMatch(line));
- if (commandGroupLine != null)
+ // If optional attributes are missing, they won't have .Success = true
+ string shortHand = commandMatch.Groups["shortHand"].Success ? commandMatch.Groups["shortHand"].Value : string.Empty;
+ bool adminOnly = false;
+
+ if (commandMatch.Groups["adminOnly"].Success)
+ {
+ bool.TryParse(commandMatch.Groups["adminOnly"].Value, out adminOnly);
+ }
+
+ string usage = commandMatch.Groups["usage"].Success ? commandMatch.Groups["usage"].Value : string.Empty;
+ string description = commandMatch.Groups["description"].Success ? commandMatch.Groups["description"].Value : string.Empty;
+
+ cmdList.Add((name, shortHand, adminOnly, usage, description));
+ }
+ }
+ }
+
+ static string BuildCommandsSection()
+ {
+ StringBuilder sb = new();
+ sb.AppendLine("## Commands");
+ sb.AppendLine();
+
+ var orderedGroups = commandsByGroup.Keys.OrderBy(g => g.groupName).ToList();
+
+ foreach (var group in orderedGroups)
{
- var match = CommandGroupRegex.Match(commandGroupLine);
- commandGroup = match.Groups[1].Success ? match.Groups[1].Value : match.Groups[3].Value;
- commandGroupShort = match.Groups[2].Success ? match.Groups[2].Value : "";
+ var (groupName, groupShort) = group;
+ sb.AppendLine($"### {Capitalize(groupName)} Commands");
+
+ var cmdList = commandsByGroup[group];
+ foreach (var (name, shortHand, adminOnly, usage, description) in cmdList)
+ {
+ bool hasShorthand = !string.IsNullOrEmpty(shortHand);
+ bool hasGroupShort = !string.IsNullOrEmpty(groupShort);
+
+ // If has parameters and no shorthand replace
+ string commandUsage = string.IsNullOrEmpty(usage) ? name : usage;
+ string nameReplacement = commandUsage.EndsWith(name) || !hasShorthand ? name : string.Empty;
+
+ // Prebuild command line strings
+ string adminLock = adminOnly ? " π" : string.Empty;
+ string commandParameters = string.Empty;
+
+ if (hasGroupShort)
+ {
+ commandParameters = hasShorthand ? commandUsage.Replace($".{groupShort} {shortHand}", "") : commandUsage.Replace($".{groupShort} {nameReplacement}", "");
+ }
+ else
+ {
+ commandParameters = hasShorthand ? commandUsage.Replace($".{groupName} {shortHand}", "") : commandUsage.Replace($".{groupName} {nameReplacement}", "");
+ }
+
+ // Build main command line string
+ var commandLine = $"- `.{groupName} {name}{commandParameters}`{adminLock}";
+
+ // Handle misc formatting
+ if (groupName == "misc")
+ {
+ commandLine = commandLine.Replace("misc ", "");
+ int adjustmentLength = adminOnly ? usage.Length + 3 : usage.Length + 1; // +3 for " π"
+ commandLine = commandLine[..^adjustmentLength] + (adminOnly ? " π`" : "`");
+ }
+
+ sb.AppendLine(commandLine);
+
+ // Description line if available
+ if (!string.IsNullOrEmpty(description))
+ {
+ sb.AppendLine($" - {description}");
+ }
+
+ sb.AppendLine($" - Shortcut: *{commandUsage}*");
+ }
+
+ // Add spacing after each group, except the last one
+ if (orderedGroups.IndexOf(group) < orderedGroups.Count - 1)
+ {
+ sb.AppendLine();
+ }
}
- // Append section title
- commandsSection.AppendLine($"\n### {commandSection}");
+ return sb.ToString();
+ }
+ static string BuildConfigSection()
+ {
+ StringBuilder sb = new();
+ sb.AppendLine("## Configuration");
+ sb.AppendLine();
- // Find methods with command attribute
- foreach (string line in fileLines)
+ // Group config entries by their section
+ var groupedConfigEntries = ConfigInitialization.ConfigEntries
+ .GroupBy(entry => entry.Section)
+ .OrderBy(group => ConfigInitialization.SectionOrder.IndexOf(group.Key)).ToList();
+
+ foreach (var group in groupedConfigEntries)
{
- var match = CommandAttributeRegex.Match(line);
- if (match.Success)
+ sb.AppendLine($"### {group.Key}");
+
+ foreach (var entry in group)
{
- string name = match.Groups["name"].Value;
- string shortHand = match.Groups["shortHand"].Success ? match.Groups["shortHand"].Value : "";
- string adminOnly = match.Groups["adminOnly"].Value;
- string usage = match.Groups["usage"].Value;
- string description = match.Groups["description"].Value;
-
- // Formulate command prefix
- string commandPrefix = $"- `.{commandGroup} {usage}`";
-
- // Append information to the section
- commandsSection.AppendLine(commandPrefix);
- commandsSection.AppendLine($" - {description}");
- if (bool.Parse(adminOnly))
+ string defaultValue = entry.DefaultValue is string strValue ? $"\"{strValue}\"" : entry.DefaultValue.ToString();
+ string typeName = entry.DefaultValue.GetType().Name.ToLower();
+
+ // Adjust type naming for readability
+ if (typeName == "boolean") typeName = "bool";
+ else if (typeName == "single") typeName = "float";
+ else if (typeName == "int32") typeName = "int";
+
+ sb.AppendLine($"- **{AddSpacesToCamelCase(entry.Key)}**: `{entry.Key}` ({typeName}, default: {defaultValue})");
+ if (!string.IsNullOrEmpty(entry.Description))
{
- commandsSection.AppendLine($" - Admin-only");
+ sb.AppendLine($" {entry.Description}");
}
}
+
+ // Add spacing after each group, except the last one
+ if (groupedConfigEntries.IndexOf(group) < groupedConfigEntries.Count - 1)
+ {
+ sb.AppendLine();
+ }
}
+
+ return sb.ToString();
}
+ static string AddSpacesToCamelCase(string input)
+ {
+ if (string.IsNullOrEmpty(input))
+ return input;
- // Write the new Commands section to the README
- UpdateReadme(commandsSection.ToString());
- }
+ StringBuilder sb = new();
+ for (int i = 0; i < input.Length; i++)
+ {
+ char current = input[i];
- // Method to update the README with the new Commands section
- static void UpdateReadme(string commandsSection)
- {
- // Load the existing README file
- string[] readmeLines = File.ReadAllLines(ReadMePath);
+ // Check for capital letters but ignore consecutive ones (e.g., XP)
+ bool isUpperCase = char.IsUpper(current);
+ bool isNotFirstChar = i > 0;
+ bool isPreviousCharLowerCase = isNotFirstChar && char.IsLower(input[i - 1]);
+ bool isNextCharLowerCase = (i < input.Length - 1) && char.IsLower(input[i + 1]);
+
+ if (isNotFirstChar && isUpperCase && (isPreviousCharLowerCase || isNextCharLowerCase))
+ {
+ sb.Append(' ');
+ }
+
+ sb.Append(current);
+ }
+
+ return sb.ToString();
+ }
+ static void UpdateReadme(string commandsSection, string configSection)
+ {
+ bool inCommandsSection = false;
+ bool inConfigSection = false;
+ bool commandsReplaced = false;
+ bool configReplaced = false;
+
+ List newContent = [];
+
+ try
+ {
+ foreach (string line in File.ReadLines(ReadMePath))
+ {
+ if (line.Trim().Equals(COMMANDS_HEADER, StringComparison.OrdinalIgnoreCase))
+ {
+ // Start of "## Commands"
+ inCommandsSection = true;
+ commandsReplaced = true;
- // Find start and end of the Commands section
- int commandsStartIndex = Array.FindIndex(readmeLines, line => line.StartsWith("## Commands"));
- int commandsEndIndex = Array.FindIndex(readmeLines, commandsStartIndex + 1, line => line.StartsWith("## "));
+ newContent.Add(commandsSection); // Add new commands
- // Replace the old Commands section with the new one
- StringBuilder updatedReadme = new();
- updatedReadme.Append(string.Join(Environment.NewLine, readmeLines.Take(commandsStartIndex)));
- updatedReadme.AppendLine(commandsSection);
- updatedReadme.Append(string.Join(Environment.NewLine, readmeLines.Skip(commandsEndIndex)));
+ continue;
+ }
+
+ if (line.Trim().Equals(CONFIG_HEADER, StringComparison.OrdinalIgnoreCase))
+ {
+ // Start of "## Configuration"
+ inConfigSection = true;
+ configReplaced = true;
+
+ newContent.Add(configSection); // Add new configuration
+
+ continue;
+ }
+
+ if (inCommandsSection && line.Trim().StartsWith("## ", StringComparison.OrdinalIgnoreCase) &&
+ !line.Trim().Equals(COMMANDS_HEADER, StringComparison.OrdinalIgnoreCase))
+ {
+ // Reached the next section or a new header
+ inCommandsSection = false;
+ }
+
+ if (inConfigSection && line.Trim().StartsWith("## ", StringComparison.OrdinalIgnoreCase) &&
+ !line.Trim().Equals(CONFIG_HEADER, StringComparison.OrdinalIgnoreCase))
+ {
+ // Reached the next section or a new header
+ inConfigSection = false;
+ }
+
+ if (!inCommandsSection && !inConfigSection)
+ {
+ newContent.Add(line);
+ }
+ }
+
+ if (inConfigSection)
+ {
+ newContent.Add(configSection);
+ inConfigSection = false;
+ }
+
+ if (!commandsReplaced)
+ {
+ // Append new section if "## Commands" not found
+ newContent.Add(COMMANDS_HEADER);
+ newContent.Add(commandsSection);
+ }
+
+ if (!configReplaced)
+ {
+ // Append new config section if "## Configuration" not found
+ newContent.Add(CONFIG_HEADER);
+ newContent.Add(configSection);
+ }
+
+ File.WriteAllLines(ReadMePath, newContent);
+ }
+ catch (Exception ex)
+ {
+ Console.Error.WriteLine($"Error updating the readme: {ex.Message}");
+ throw;
+ }
+ }
- // Write the updated content back to the README file
- File.WriteAllText(ReadMePath, updatedReadme.ToString());
+ // Helper method to capitalize strings
+ static string Capitalize(string input) =>
+ string.IsNullOrEmpty(input) ? input : char.ToUpper(input[0]) + input[1..];
}
}
\ No newline at end of file
diff --git a/Patches/ServerBootstrapSystemPatches.cs b/Patches/ServerBootstrapSystemPatches.cs
index b8b7813..d12493a 100644
--- a/Patches/ServerBootstrapSystemPatches.cs
+++ b/Patches/ServerBootstrapSystemPatches.cs
@@ -422,7 +422,7 @@ static IEnumerator UpdatePlayerData(ulong steamId, Entity playerCharacter, Entit
//if (exists) FamiliarUtilities.ClearBuffers(playerCharacter, steamId);
Entity familiar = FamiliarUtilities.FindPlayerFamiliar(playerCharacter);
-
+
if (!familiar.Exists()) FamiliarUtilities.ClearFamiliarActives(steamId);
else
{
diff --git a/README.md b/README.md
index a0fc588..c53c94e 100644
--- a/README.md
+++ b/README.md
@@ -256,7 +256,7 @@ Jairon Orellana; Odjit; Jera; Eve winters; Kokuren TCG and Gaming Shop;
- `.prepareforthehunt`
- Completes GettingReadyForTheHunt if not already completed. This shouldn't be needed at this point but leaving just incase.
- Shortcut: *.prepare*
-- `.lockspell`
+- `.lockspells`
- Enables registering spells to use in unarmed slots if extra slots for unarmed are enabled. Toggle, move spells to slots, then toggle again and switch to unarmed.
- Shortcut: *.locksp*
- `.lockshift`
@@ -315,7 +315,7 @@ Jairon Orellana; Odjit; Jera; Eve winters; Kokuren TCG and Gaming Shop;
Enable or disable allowing clients to register with server for UI updates.
- **Elite Shard Bearers**: `EliteShardBearers` (bool, default: false)
Enable or disable elite shard bearers (significant increases to health, damage, movement speed and attack speed but will no longer scale to number of players).
-- **Shard Bearer Level**: `EliteShardBearers` (bool, default: false)
+- **Shard Bearer Level**: `ShardBearerLevel` (int, default: 0)
Sets level of shard bearers if elite shard bearers is enabled (will override game vblood settings for shard bearers as this is set when they spawn). Leave at 0 for no change.
### Leveling System
diff --git a/READMETEST.md b/READMETEST.md
new file mode 100644
index 0000000..6a40792
--- /dev/null
+++ b/READMETEST.md
@@ -0,0 +1,607 @@
+## Table of Contents
+
+Commands/config up to date, better feature summaries next on the list.
+
+- [Sponsors](#sponsors)
+- [Features](#features)
+- [Commands](#commands)
+- [Configuration](#configuration)
+- [Recommended Mods](#recommended)
+
+## Sponsor this project
+
+[![patreon](https://i.imgur.com/u6aAqeL.png)](https://www.patreon.com/join/4865914) [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/zfolmt)
+
+## Sponsors
+
+Jairon Orellana; Odjit; Jera; Eve winters; Kokuren TCG and Gaming Shop;
+
+## Commands
+
+### Bloodlegacy Commands
+- `.bloodlegacy get [BloodType]`
+ - Display your current blood legacy progress.
+ - Shortcut: *.bl get [BloodType]*
+- `.bloodlegacy log`
+ - Toggles Legacy progress logging.
+ - Shortcut: *.bl log*
+- `.bloodlegacy choosestat [Blood] [BloodStat]`
+ - Choose a blood stat to enhance based on your legacy.
+ - Shortcut: *.bl cst [Blood] [BloodStat]*
+- `.bloodlegacy resetstats`
+ - Reset stats for current blood.
+ - Shortcut: *.bl rst*
+- `.bloodlegacy liststats`
+ - Lists blood stats available.
+ - Shortcut: *.bl lst*
+- `.bloodlegacy set [Player] [Blood] [Level]` π
+ - Sets player Blood Legacy level.
+ - Shortcut: *.bl set [Player] [Blood] [Level]*
+- `.bloodlegacy list`
+ - Lists blood legacies available.
+ - Shortcut: *.bl l*
+
+### Class Commands
+- `.class choose [Class]`
+ - Choose class.
+ - Shortcut: *.class c [Class]*
+- `.class choosespell [#]`
+ - Sets shift spell for class if prestige level is high enough.
+ - Shortcut: *.class csp [#]*
+- `.class change [Class]`
+ - Change classes.
+ - Shortcut: *.class change [Class]*
+- `.class syncbuffs`
+ - Applies class buffs appropriately if not present.
+ - Shortcut: *.class sb*
+- `.class list`
+ - Lists classes.
+ - Shortcut: *.class l*
+- `.class listbuffs [ClassType]`
+ - Shows perks that can be gained from class.
+ - Shortcut: *.class lb [ClassType]*
+- `.class listspells [ClassType]`
+ - Shows spells that can be gained from class.
+ - Shortcut: *.class lsp [ClassType]*
+- `.class liststats [Class]`
+ - Shows weapon and blood stat synergies for a class.
+ - Shortcut: *.class lst [Class]*
+
+### Familiar Commands
+- `.familiar bind [#]`
+ - Activates specified familiar from current list.
+ - Shortcut: *.fam b [#]*
+- `.familiar unbind`
+ - Destroys active familiar.
+ - Shortcut: *.fam ub*
+- `.familiar list`
+ - Lists unlocked familiars from current box.
+ - Shortcut: *.fam l*
+- `.familiar boxes`
+ - Shows the available familiar boxes.
+ - Shortcut: *.fam box*
+- `.familiar choosebox [Name]`
+ - Choose active box of familiars.
+ - Shortcut: *.fam cb [Name]*
+- `.familiar renamebox [CurrentName] [NewName]`
+ - Renames a box.
+ - Shortcut: *.fam rb [CurrentName] [NewName]*
+- `.familiar movebox [BoxName]`
+ - Moves active familiar to specified box.
+ - Shortcut: *.fam mb [BoxName]*
+- `.familiar deletebox [BoxName]`
+ - Deletes specified box if empty.
+ - Shortcut: *.fam db [BoxName]*
+- `.familiar addbox [BoxName]`
+ - Adds empty box with name.
+ - Shortcut: *.fam ab [BoxName]*
+- `.familiar add [Name] [PrefabGUID/CHAR_Unit_Name]` π
+ - Unit testing.
+ - Shortcut: *.fam a [Name] [PrefabGUID/CHAR_Unit_Name]*
+- `.familiar remove [#]`
+ - Removes familiar from current set permanently.
+ - Shortcut: *.fam r [#]*
+- `.familiar getlevel`
+ - Display current familiar leveling progress.
+ - Shortcut: *.fam gl*
+- `.familiar setlevel [Player] [Level]` π
+ - Set current familiar level.
+ - Shortcut: *.fam sl [Player] [Level]*
+- `.familiar prestige [BonusStat]`
+ - Prestiges familiar if at max, raising base stats by configured multiplier and adding an extra chosen stat.
+ - Shortcut: *.fam pr [BonusStat]*
+- `.familiar reset`
+ - Resets (destroys) entities found in followerbuffer and clears familiar actives data.
+ - Shortcut: *.fam reset*
+- `.familiar search [Name]`
+ - Searches boxes for unit with entered name.
+ - Shortcut: *.fam s [Name]*
+- `.familiar shinybuff [SpellSchool]`
+ - Chooses shiny for current active familiar, one freebie then costs configured amount to change if already unlocked.
+ - Shortcut: *.fam shiny [SpellSchool]*
+- `.familiar resetshiny [Name]` π
+ - Allows player to choose another free visual, however, does not erase any visuals they have chosen previously. Mainly for testing.
+ - Shortcut: *.fam rs [Name]*
+- `.familiar toggleoption [Setting]`
+ - Toggles various familiar settings.
+ - Shortcut: *.fam option [Setting]*
+
+### Level Commands
+- `.level log`
+ - Toggles leveling progress logging.
+ - Shortcut: *.lvl log*
+- `.level get`
+ - Display current leveling progress.
+ - Shortcut: *.lvl get*
+- `.level set [Player] [Level]` π
+ - Sets player level.
+ - Shortcut: *.lvl set [Player] [Level]*
+
+### Misc Commands
+- `.reminders`
+ - Toggles general reminders for various mod features.
+ - Shortcut: *.remindme*
+- `.sct`
+ - Toggles scrolling text.
+ - Shortcut: *.sct*
+- `.starterkit`
+ - Provides starting kit.
+ - Shortcut: *.kitme*
+- `.prepareforthehunt`
+ - Completes GettingReadyForTheHunt if not already completed.
+ - Shortcut: *.prepare*
+- `.lockspells`
+ - Locks in the next spells equipped to use in your unarmed slots.
+ - Shortcut: *.locksp*
+- `.lockshift`
+ - Toggle shift spell.
+ - Shortcut: *.shift*
+- `.userstats`
+ - Shows neat information about the player.
+ - Shortcut: *.userstats*
+- `.silence`
+ - Resets music for player.
+ - Shortcut: *.silence*
+- `.exoform`
+ - Toggles taunting to enter exo form.
+ - Shortcut: *.exoform*
+- `.cleanupfams. π`
+ - Removes disabled, invisible familiars on the map preventing building.
+ - Shortcut: *.cleanupfams*
+
+### Party Commands
+- `.party toggleinvites`
+ - Toggles being able to be invited to parties, prevents damage and share exp.
+ - Shortcut: *.party inv*
+- `.party add [Player]`
+ - Adds player to party.
+ - Shortcut: *.party a [Player]*
+- `.party remove [Player]`
+ - Removes player from party.
+ - Shortcut: *.party r [Player]*
+- `.party listmembers`
+ - Lists party members of your active party.
+ - Shortcut: *.party lm*
+- `.party disband`
+ - Disbands party.
+ - Shortcut: *.party end*
+- `.party leave`
+ - Leaves party if in one.
+ - Shortcut: *.party drop*
+
+### Prestige Commands
+- `.prestige self [PrestigeType]`
+ - Handles player prestiging.
+ - Shortcut: *.prestige me [PrestigeType]*
+- `.prestige set [Name] [PrestigeType] [Level]` π
+ - Sets the specified player to a certain level of prestige in a certain type of prestige.
+ - Shortcut: *.prestige set [Name] [PrestigeType] [Level]*
+- `.prestige listbuffs`
+ - Lists prestige buff names.
+ - Shortcut: *.prestige lb*
+- `.prestige reset [Name] [PrestigeType]` π
+ - Handles resetting prestiging.
+ - Shortcut: *.prestige r [Name] [PrestigeType]*
+- `.prestige syncbuffs`
+ - Applies prestige buffs appropriately if not present.
+ - Shortcut: *.prestige sb*
+- `.prestige get [PrestigeType]`
+ - Shows information about player's prestige status.
+ - Shortcut: *.prestige get [PrestigeType]*
+- `.prestige list`
+ - Lists prestiges available.
+ - Shortcut: *.prestige l*
+- `.prestige leaderboard [PrestigeType]`
+ - Lists prestige leaderboard for type.
+ - Shortcut: *.prestige lb [PrestigeType]*
+- `.prestige shroud`
+ - Toggles permashroud if applicable.
+ - Shortcut: *.prestige shroud*
+
+### Profession Commands
+- `.profession log`
+ - Toggles profession progress logging.
+ - Shortcut: *.prof log*
+- `.profession get [Profession]`
+ - Display your current profession progress.
+ - Shortcut: *.prof get [Profession]*
+- `.profession set [Name] [Profession] [Level]` π
+ - Sets player profession level.
+ - Shortcut: *.prof set [Name] [Profession] [Level]*
+- `.profession list`
+ - Lists professions available.
+ - Shortcut: *.prof l*
+
+### Quest Commands
+- `.quest log`
+ - Toggles quest progress logging.
+ - Shortcut: *.quest log*
+- `.quest progress [QuestType]`
+ - Display your current quest progress.
+ - Shortcut: *.quest p [QuestType]*
+- `.quest track [QuestType]`
+ - Locate and track quest target.
+ - Shortcut: *.quest t [QuestType]*
+- `.quest refresh [Name]` π
+ - Refreshes daily and weekly quests for player.
+ - Shortcut: *.quest rf [Name]*
+- `.quest reroll [QuestType]`
+ - Reroll quest for cost (daily only currently).
+ - Shortcut: *.quest r [QuestType]*
+
+### Weapon Commands
+- `.weapon getexpertise`
+ - Displays your current expertise.
+ - Shortcut: *.wep get*
+- `.weapon logexpertise`
+ - Toggles expertise logging.
+ - Shortcut: *.wep log*
+- `.weapon choosestat [Weapon] [WeaponStat]`
+ - Choose a weapon stat to enhance based on your expertise.
+ - Shortcut: *.wep cst [Weapon] [WeaponStat]*
+- `.weapon resetwepstats`
+ - Reset the stats for current weapon.
+ - Shortcut: *.wep rst*
+- `.weapon setexpertise [Name] [Weapon] [Level]` π
+ - Sets player weapon expertise level.
+ - Shortcut: *.wep set [Name] [Weapon] [Level]*
+- `.weapon liststats`
+ - Lists weapon stats available.
+ - Shortcut: *.wep lst*
+- `.weapon list`
+ - Lists weapon expertises available.
+ - Shortcut: *.wep l*
+- `.weapon setspells [Name] [Slot] [PrefabGUID] [Radius]` π
+ - Manually sets spells for testing (if you enter a radius it will apply to players around the entered name).
+ - Shortcut: *.wep spell [Name] [Slot] [PrefabGUID] [Radius]*
+- `.weapon restorelevels`
+ - Fixes weapon levels if they are not correct. Don't use this unless you need to.
+ - Shortcut: *.wep restore*
+
+## Configuration
+
+### General
+- **Language Localization**: `LanguageLocalization` (string, default: "English")
+ The language localization for prefabs displayed to users. English by default. Options: Brazilian, English, French, German, Hungarian, Italian, Japanese, Koreana, Latam, Polish, Russian, SimplifiedChinese, Spanish, TraditionalChinese, Thai, Turkish, Vietnamese
+- **Client Companion**: `ClientCompanion` (bool, default: False)
+ Enable if using the client companion mod, can configure what's displayed in the client config.
+- **Elite Shard Bearers**: `EliteShardBearers` (bool, default: False)
+ Enable or disable elite shard bearers.
+- **Shard Bearer Level**: `ShardBearerLevel` (int, default: 0)
+ Sets level of shard bearers if elite shard bearers is enabled. Leave at 0 for no effect.
+- **Potion Stacking**: `PotionStacking` (bool, default: True)
+ Enable or disable potion stacking (can have t01 effects and t02 effects at the same time. also requires professions enabled).
+
+### StarterKit
+- **Starter Kit**: `StarterKit` (bool, default: False)
+ Enable or disable the starter kit.
+- **Kit Prefabs**: `KitPrefabs` (string, default: "862477668,-1531666018,-1593377811,1821405450")
+ The PrefabGUID hashes for the starter kit.
+- **Kit Quantities**: `KitQuantities` (string, default: "500,1000,1000,250")
+ The quantity of each item in the starter kit.
+
+### Quests
+- **Quest System**: `QuestSystem` (bool, default: False)
+ Enable or disable quests (currently only kill quests).
+- **Infinite Dailies**: `InfiniteDailies` (bool, default: False)
+ Enable or disable infinite dailies.
+- **Quest Rewards**: `QuestRewards` (string, default: "28358550,576389135,-257494203")
+ The PrefabGUID hashes for quest reward pool.
+- **Quest Reward Amounts**: `QuestRewardAmounts` (string, default: "50,250,50")
+ The amount of each reward in the pool. Will be multiplied accordingly for weeklies (*5) and vblood kill quests (*3).
+- **Reroll Daily Prefab**: `RerollDailyPrefab` (int, default: -949672483)
+ Prefab item for rerolling daily.
+- **Reroll Daily Amount**: `RerollDailyAmount` (int, default: 50)
+ Cost of prefab for rerolling daily.
+- **Reroll Weekly Prefab**: `RerollWeeklyPrefab` (int, default: -949672483)
+ Prefab item for rerolling weekly.
+- **Reroll Weekly Amount**: `RerollWeeklyAmount` (int, default: 50)
+ Cost of prefab for rerolling weekly. Won't work if already completed for the week.
+
+### Leveling
+- **Leveling System**: `LevelingSystem` (bool, default: False)
+ Enable or disable the leveling system.
+- **Rested XP System**: `RestedXPSystem` (bool, default: False)
+ Enable or disable rested experience for players logging out inside of coffins (half for wooden, full for stone). Prestiging level will reset accumulated rested xp.
+- **Rested XP Rate**: `RestedXPRate` (float, default: 0.05)
+ Rate of Rested XP accumulation per tick (as a percentage of maximum allowed rested XP, if configured to one tick per hour 20 hours offline in a stone coffin will provide maximum current rested XP).
+- **Rested XP Max**: `RestedXPMax` (int, default: 5)
+ Maximum extra levels worth of rested XP a player can accumulate.
+- **Rested XP Tick Rate**: `RestedXPTickRate` (float, default: 120)
+ Minutes required to accumulate one tick of Rested XP.
+- **Max Level**: `MaxLevel` (int, default: 90)
+ The maximum level a player can reach.
+- **Starting Level**: `StartingLevel` (int, default: 0)
+ Starting level for players if no data is found.
+- **Unit Leveling Multiplier**: `UnitLevelingMultiplier` (float, default: 7.5)
+ The multiplier for experience gained from units.
+- **V Blood Leveling Multiplier**: `VBloodLevelingMultiplier` (float, default: 15)
+ The multiplier for experience gained from VBloods.
+- **Docile Unit Multiplier**: `DocileUnitMultiplier` (float, default: 0.15)
+ The multiplier for experience gained from docile units.
+- **War Event Multiplier**: `WarEventMultiplier` (float, default: 0.2)
+ The multiplier for experience gained from war event trash spawns.
+- **Unit Spawner Multiplier**: `UnitSpawnerMultiplier` (float, default: 0)
+ The multiplier for experience gained from unit spawners (vermin nests, tombs).
+- **Group Leveling Multiplier**: `GroupLevelingMultiplier` (float, default: 1)
+ The multiplier for experience gained from group kills.
+- **Level Scaling Multiplier**: `LevelScalingMultiplier` (float, default: 0.05)
+ Reduces experience gained from kills with a large level gap between player and unit, increase to make harsher decrease or set to 0 to remove.
+- **Player Parties**: `PlayerParties` (bool, default: False)
+ Enable or disable the ability to group with players not in your clan for experience/familiar unlock sharing.
+- **Max Party Size**: `MaxPartySize` (int, default: 5)
+ The maximum number of players that can share experience in a group.
+- **Exp Share Distance**: `ExpShareDistance` (float, default: 25)
+ Default is ~5 floor tile lengths.
+
+### Prestige
+- **Prestige System**: `PrestigeSystem` (bool, default: False)
+ Enable or disable the prestige system (requires leveling to be enabled)
+- **Prestige Buffs**: `PrestigeBuffs` (string, default: "1504279833,475045773,1643157297,946705138,-1266262267,-773025435,-1043659405,-1583573438,-1869022798,-536284884")
+ The PrefabGUID hashes for general prestige buffs, use 0 to skip otherwise buff applies at the prestige level
+- **Prestige Levels To Unlock Class Spells**: `PrestigeLevelsToUnlockClassSpells` (string, default: "0,1,2,3,4,5")
+ The prestige levels at which class spells are unlocked. This should match the number of spells per class +1 to account for the default class spell. Can leave at 0 if you want them unlocked from the start.
+- **Max Leveling Prestiges**: `MaxLevelingPrestiges` (int, default: 10)
+ The maximum number of prestiges a player can reach in leveling
+- **Leveling Prestige Reducer**: `LevelingPrestigeReducer` (float, default: 0.05)
+ Flat factor by which experience is reduced per increment of prestige in leveling
+- **Prestige Rates Reducer**: `PrestigeRatesReducer` (float, default: 0.1)
+ Flat factor by which rates are reduced in expertise/legacy per increment of prestige in expertise/legacy
+- **Prestige Stat Multiplier**: `PrestigeStatMultiplier` (float, default: 0.1)
+ Flat factor by which stats are increased in expertise/legacy bonuses per increment of prestige in expertise/legacy
+- **Prestige Rate Multiplier**: `PrestigeRateMultiplier` (float, default: 0.1)
+ Flat factor by which rates are increased in expertise/legacy per increment of prestige in leveling
+- **Exo Prestiging**: `ExoPrestiging` (bool, default: False)
+ Enable or disable exo prestiges (need to max normal prestiges first).
+- **Exo Prestiges**: `ExoPrestiges` (int, default: 100)
+ The number of exo prestiges available
+- **Exo Prestige Reward**: `ExoPrestigeReward` (int, default: 28358550)
+ The reward for exo prestiging (tier 3 nether shards by default).
+- **Exo Prestige Reward Quantity**: `ExoPrestigeRewardQuantity` (int, default: 500)
+ The quantity of the reward for exo prestiging.
+
+### Expertise
+- **Expertise System**: `ExpertiseSystem` (bool, default: False)
+ Enable or disable the expertise system
+- **Max Expertise Prestiges**: `MaxExpertisePrestiges` (int, default: 10)
+ The maximum number of prestiges a player can reach in expertise
+- **Unarmed Slots**: `UnarmedSlots` (bool, default: False)
+ Enable or disable the ability to use extra unarmed spell slots
+- **Shift Slot**: `ShiftSlot` (bool, default: False)
+ Enable or disable using class spell on shift
+- **Max Expertise Level**: `MaxExpertiseLevel` (int, default: 100)
+ The maximum level a player can reach in weapon expertise
+- **Unit Expertise Multiplier**: `UnitExpertiseMultiplier` (float, default: 2)
+ The multiplier for expertise gained from units
+- **V Blood Expertise Multiplier**: `VBloodExpertiseMultiplier` (float, default: 5)
+ The multiplier for expertise gained from VBloods
+- **Unit Spawner Expertise Factor**: `UnitSpawnerExpertiseFactor` (float, default: 1)
+ The multiplier for experience gained from unit spawners (vermin nests, tombs).
+- **Expertise Stat Choices**: `ExpertiseStatChoices` (int, default: 2)
+ The maximum number of stat choices a player can pick for a weapon expertise. Max of 3 will be sent to client UI for display.
+- **Reset Expertise Item**: `ResetExpertiseItem` (int, default: 576389135)
+ Item PrefabGUID cost for resetting weapon stats
+- **Reset Expertise Item Quantity**: `ResetExpertiseItemQuantity` (int, default: 500)
+ Quantity of item required for resetting stats
+- **Max Health**: `MaxHealth` (float, default: 250)
+ The base cap for maximum health
+- **Movement Speed**: `MovementSpeed` (float, default: 0.25)
+ The base cap for movement speed
+- **Primary Attack Speed**: `PrimaryAttackSpeed` (float, default: 0.1)
+ The base cap for primary attack speed
+- **Physical Life Leech**: `PhysicalLifeLeech` (float, default: 0.1)
+ The base cap for physical life leech
+- **Spell Life Leech**: `SpellLifeLeech` (float, default: 0.1)
+ The base cap for spell life leech
+- **Primary Life Leech**: `PrimaryLifeLeech` (float, default: 0.15)
+ The base cap for primary life leech
+- **Physical Power**: `PhysicalPower` (float, default: 20)
+ The base cap for physical power
+- **Spell Power**: `SpellPower` (float, default: 10)
+ The base cap for spell power
+- **Physical Crit Chance**: `PhysicalCritChance` (float, default: 0.1)
+ The base cap for physical critical strike chance
+- **Physical Crit Damage**: `PhysicalCritDamage` (float, default: 0.5)
+ The base cap for physical critical strike damage
+- **Spell Crit Chance**: `SpellCritChance` (float, default: 0.1)
+ The base cap for spell critical strike chance
+- **Spell Crit Damage**: `SpellCritDamage` (float, default: 0.5)
+ The base cap for spell critical strike damage
+
+### Legacies
+- **Blood System**: `BloodSystem` (bool, default: False)
+ Enable or disable the blood legacy system
+- **Max Legacy Prestiges**: `MaxLegacyPrestiges` (int, default: 10)
+ The maximum number of prestiges a player can reach in blood legacies
+- **Max Blood Level**: `MaxBloodLevel` (int, default: 100)
+ The maximum level a player can reach in blood legacies
+- **Unit Legacy Multiplier**: `UnitLegacyMultiplier` (float, default: 1)
+ The multiplier for lineage gained from units
+- **V Blood Legacy Multiplier**: `VBloodLegacyMultiplier` (float, default: 5)
+ The multiplier for lineage gained from VBloods
+- **Legacy Stat Choices**: `LegacyStatChoices` (int, default: 2)
+ The maximum number of stat choices a player can pick for a blood legacy. Max of 3 will be sent to client UI for display.
+- **Reset Legacy Item**: `ResetLegacyItem` (int, default: 576389135)
+ Item PrefabGUID cost for resetting blood stats
+- **Reset Legacy Item Quantity**: `ResetLegacyItemQuantity` (int, default: 500)
+ Quantity of item required for resetting blood stats
+- **Healing Received**: `HealingReceived` (float, default: 0.15)
+ The base cap for healing received
+- **Damage Reduction**: `DamageReduction` (float, default: 0.05)
+ The base cap for damage reduction
+- **Physical Resistance**: `PhysicalResistance` (float, default: 0.1)
+ The base cap for physical resistance
+- **Spell Resistance**: `SpellResistance` (float, default: 0.1)
+ The base cap for spell resistance
+- **Resource Yield**: `ResourceYield` (float, default: 0.25)
+ The base cap for resource yield
+- **CC Reduction**: `CCReduction` (float, default: 0.2)
+ The base cap for crowd control reduction
+- **Spell Cooldown Recovery Rate**: `SpellCooldownRecoveryRate` (float, default: 0.1)
+ The base cap for spell cooldown recovery rate
+- **Weapon Cooldown Recovery Rate**: `WeaponCooldownRecoveryRate` (float, default: 0.1)
+ The base cap for weapon cooldown recovery rate
+- **Ultimate Cooldown Recovery Rate**: `UltimateCooldownRecoveryRate` (float, default: 0.2)
+ The base cap for ultimate cooldown recovery rate
+- **Minion Damage**: `MinionDamage` (float, default: 0.25)
+ The base cap for minion damage
+- **Shield Absorb**: `ShieldAbsorb` (float, default: 0.5)
+ The base cap for shield absorb
+- **Blood Efficiency**: `BloodEfficiency` (float, default: 0.1)
+ The base cap for blood efficiency
+
+### Professions
+- **Profession System**: `ProfessionSystem` (bool, default: False)
+ Enable or disable the profession system
+- **Max Profession Level**: `MaxProfessionLevel` (int, default: 100)
+ The maximum level a player can reach in professions
+- **Profession Multiplier**: `ProfessionMultiplier` (float, default: 10)
+ The multiplier for profession experience gained
+- **Extra Recipes**: `ExtraRecipes` (bool, default: False)
+ Enable or disable extra recipes
+
+### Familiars
+- **Familiar System**: `FamiliarSystem` (bool, default: False)
+ Enable or disable the familiar system
+- **Share Unlocks**: `ShareUnlocks` (bool, default: False)
+ Enable or disable sharing unlocks between players in clans or parties (uses exp share distance)
+- **Familiar Combat**: `FamiliarCombat` (bool, default: True)
+ Enable or disable combat for familiars.
+- **Familiar Pv P**: `FamiliarPvP` (bool, default: True)
+ Enable or disable PvP participation for familiars. (if set to false, familiars will be unbound when entering PvP combat)
+- **Familiar Prestige**: `FamiliarPrestige` (bool, default: False)
+ Enable or disable the prestige system for familiars
+- **Max Familiar Prestiges**: `MaxFamiliarPrestiges` (int, default: 10)
+ The maximum number of prestiges a familiar can reach
+- **Familiar Prestige Stat Multiplier**: `FamiliarPrestigeStatMultiplier` (float, default: 0.1)
+ The multiplier for stats gained from familiar prestiges
+- **Max Familiar Level**: `MaxFamiliarLevel` (int, default: 90)
+ The maximum level a familiar can reach
+- **Allow V Bloods**: `AllowVBloods` (bool, default: False)
+ Allow VBloods to be unlocked as familiars (this includes shardbearers, if you want those excluded use the bannedUnits list)
+- **Banned Units**: `BannedUnits` (string, default: "")
+ The PrefabGUID hashes for units that cannot be used as familiars. Same structure as the buff lists except unit prefabs
+- **Banned Types**: `BannedTypes` (string, default: "")
+ The types of units that cannot be used as familiars go here (Human, Undead, Demon, Mechanical, Beast)
+- **V Blood Damage Multiplier**: `VBloodDamageMultiplier` (float, default: 1)
+ Leave at 1 for no change (controls damage familiars do to VBloods)
+- **Unit Familiar Multiplier**: `UnitFamiliarMultiplier` (float, default: 7.5)
+ The multiplier for experience gained from units
+- **V Blood Familiar Multiplier**: `VBloodFamiliarMultiplier` (float, default: 15)
+ The multiplier for experience gained from VBloods
+- **Unit Unlock Chance**: `UnitUnlockChance` (float, default: 0.05)
+ The chance for a unit to unlock a familiar
+- **V Blood Unlock Chance**: `VBloodUnlockChance` (float, default: 0.01)
+ The chance for a VBlood to unlock a familiar
+- **Shiny Chance**: `ShinyChance` (float, default: 0.2)
+ The chance for a visual when unlocking a familiar
+- **Shiny Cost Item Prefab**: `ShinyCostItemPrefab` (int, default: -77477508)
+ Item PrefabGUID cost for changing shiny visual if one is already unlocked (currently demon fragment by default)
+- **Shiny Cost Item Quantity**: `ShinyCostItemQuantity` (int, default: 1)
+ Quantity of item required for changing shiny visual
+
+### Classes
+- **Soft Synergies**: `SoftSynergies` (bool, default: False)
+ Allow class synergies (turns on classes and does not restrict stat choices, do not use this and hard syergies at the same time)
+- **Hard Synergies**: `HardSynergies` (bool, default: False)
+ Enforce class synergies (turns on classes and restricts stat choices, do not use this and soft syergies at the same time)
+- **Change Class Item**: `ChangeClassItem` (int, default: 576389135)
+ Item PrefabGUID cost for changing class.
+- **Change Class Quantity**: `ChangeClassQuantity` (int, default: 750)
+ Quantity of item required for changing class.
+- **Class Spell School On Hit Effects**: `ClassSpellSchoolOnHitEffects` (bool, default: False)
+ Enable or disable class spell school on hit effects (respective debuff from spell school, leech chill condemn etc).
+- **On Hit Proc Chance**: `OnHitProcChance` (float, default: 0.075)
+ The chance for a class effect to proc on hit.
+- **Stat Synergy Multiplier**: `StatSynergyMultiplier` (float, default: 1.5)
+ Multiplier for class stat synergies to base stat cap
+- **Blood Knight Weapon**: `BloodKnightWeapon` (string, default: "0,3,5,6")
+ Blood Knight weapon synergies
+- **Blood Knight Blood**: `BloodKnightBlood` (string, default: "1,5,7,10")
+ Blood Knight blood synergies
+- **Demon Hunter Weapon**: `DemonHunterWeapon` (string, default: "1,2,8,9")
+ Demon Hunter weapon synergies
+- **Demon Hunter Blood**: `DemonHunterBlood` (string, default: "2,5,7,9")
+ Demon Hunter blood synergies
+- **Vampire Lord Weapon**: `VampireLordWeapon` (string, default: "0,4,6,7")
+ Vampire Lord weapon synergies
+- **Vampire Lord Blood**: `VampireLordBlood` (string, default: "1,3,8,11")
+ Vampire Lord blood synergies
+- **Shadow Blade Weapon**: `ShadowBladeWeapon` (string, default: "1,2,6,9")
+ Shadow Blade weapon synergies
+- **Shadow Blade Blood**: `ShadowBladeBlood` (string, default: "3,5,7,10")
+ Shadow Blade blood synergies
+- **Arcane Sorcerer Weapon**: `ArcaneSorcererWeapon` (string, default: "4,7,10,11")
+ Arcane Sorcerer weapon synergies
+- **Arcane Sorcerer Blood**: `ArcaneSorcererBlood` (string, default: "0,6,8,10")
+ Arcane Sorcerer blood synergies
+- **Death Mage Weapon**: `DeathMageWeapon` (string, default: "0,4,7,11")
+ Death Mage weapon synergies
+- **Death Mage Blood**: `DeathMageBlood` (string, default: "2,3,6,9")
+ Death Mage blood synergies
+- **Default Class Spell**: `DefaultClassSpell` (int, default: -433204738)
+ Default spell (veil of shadow) available to all classes.
+- **Blood Knight Buffs**: `BloodKnightBuffs` (string, default: "1828387635,-534491790,-1055766373,-584203677")
+ The PrefabGUID hashes for blood knight leveling blood buffs. Granted every MaxLevel/(# of blood buffs)
+- **Blood Knight Spells**: `BloodKnightSpells` (string, default: "-880131926,651613264,2067760264,189403977,375131842")
+ Blood Knight shift spells, granted at levels of prestige
+- **Demon Hunter Buffs**: `DemonHunterBuffs` (string, default: "-154702686,-285745649,-1510965956,-397097531")
+ The PrefabGUID hashes for demon hunter leveling blood buffs
+- **Demon Hunter Spells**: `DemonHunterSpells` (string, default: "-356990326,-987810170,1071205195,1249925269,-914344112")
+ Demon Hunter shift spells, granted at levels of prestige
+- **Vampire Lord Buffs**: `VampireLordBuffs` (string, default: "1558171501,997154800,-1413561088,1103099361")
+ The PrefabGUID hashes for vampire lord leveling blood buffs
+- **Vampire Lord Spells**: `VampireLordSpells` (string, default: "78384915,295045820,-1000260252,91249849,1966330719")
+ Vampire Lord shift spells, granted at levels of prestige
+- **Shadow Blade Buffs**: `ShadowBladeBuffs` (string, default: "894725875,-1596803256,-993492354,210193036")
+ The PrefabGUID hashes for shadow blade leveling blood buffs
+- **Shadow Blade Spells**: `ShadowBladeSpells` (string, default: "1019568127,1575317901,1112116762,-358319417,1174831223")
+ Shadow Blade shift spells, granted at levels of prestige
+- **Arcane Sorcerer Buffs**: `ArcaneSorcererBuffs` (string, default: "1614027598,884683323,-1576592687,-1859298707")
+ The PrefabGUID hashes for arcane leveling blood buffs
+- **Arcane Sorcerer Spells**: `ArcaneSorcererSpells` (string, default: "247896794,268059675,-242769430,-2053450457,1650878435")
+ Arcane Sorcerer shift spells, granted at levels of prestige
+- **Death Mage Buffs**: `DeathMageBuffs` (string, default: "-901503997,-651661301,1934870645,1201299233")
+ The PrefabGUID hashes for death mage leveling blood buffs
+- **Death Mage Spells**: `DeathMageSpells` (string, default: "-1204819086,481411985,1961570821,2138402840,-1781779733")
+ Death Mage shift spells, granted at levels of prestige
+
+## Recommended
+- [KindredCommands](https://thunderstore.io/c/v-rising/p/odjit/KindredCommands/)
+ Highly recommend getting this if you plan on using Bloodcraft or any other mods for V Rising in general. Invaluable set of tools and options that will greatly improve your modding experience.
+- [KindredLogistics](https://thunderstore.io/c/v-rising/p/Kindred/KindredLogistics/)
+ If you mourn QuickStash, this is for you! It comes with much more that will drastically improve your inventory-managing experience in V Rising, only requires server installation.
+- [KindredArenas](https://thunderstore.io/c/v-rising/p/odjit/KindredArenas/)
+ Allows for controlling the areas players can engage each other in on PvP servers.
+- [KindredSchematics](https://thunderstore.io/c/v-rising/p/odjit/KindredSchematics/)
+ Closest thing to creative mode we'll likely ever get. Copy/paste castles, build wherever with whatever you want!
+- [KindredPortals](https://thunderstore.io/c/v-rising/p/odjit/KindredPortals/)
+ Create custom portals and waygates for your server! Pairs great with KindredSchematics for making new areas.
+- [Sanguis](https://thunderstore.io/c/v-rising/p/zfolmt/Sanguis/)
+ Simple login reward/reward players for being online mod.
+- [BloodyNotify](https://thunderstore.io/c/v-rising/p/Trodi/Notify/)
+ Notifications for players coming online or going offline, VBlood kills, and more.
+- [BloodyMerchants](https://thunderstore.io/c/v-rising/p/Trodi/BloodyMerchant/)
+ Custom merchants! Great for letting players buy items they normally can't or providing a use for otherwise unused prefabs.
+- [XPRising](https://thunderstore.io/c/v-rising/p/XPRising/XPRising/)
+ If you like the idea of a mod with RPG features but Bloodcraft doesn't float your boat maybe this will!
diff --git a/Services/ConfigService.cs b/Services/ConfigService.cs
index 25231c3..04c495c 100644
--- a/Services/ConfigService.cs
+++ b/Services/ConfigService.cs
@@ -7,6 +7,7 @@ namespace Bloodcraft.Services;
public static class ConfigService
{
+ /*
public static string LanguageLocalization { get; private set; }
public static bool ClientCompanion { get; private set; }
public static bool EliteShardBearers { get; private set; }
@@ -55,8 +56,6 @@ public static class ConfigService
public static int ExoPrestiges { get; private set; }
public static int ExoPrestigeReward { get; private set; }
public static int ExoPrestigeRewardQuantity { get; private set; }
- public static float ExoPrestigeDamageTakenMultiplier { get; private set; }
- public static float ExoPrestigePowerBonus { get; private set; }
public static bool ExpertiseSystem { get; private set; }
public static int MaxExpertisePrestiges { get; private set; }
public static bool UnarmedSlots { get; private set; }
@@ -154,10 +153,445 @@ public static class ConfigService
public static string ArcaneSorcererSpells { get; private set; }
public static string DeathMageBuffs { get; private set; }
public static string DeathMageSpells { get; private set; }
+ */
+ static readonly Lazy _languageLocalization = new(() => GetConfigValue("LanguageLocalization"));
+ public static string LanguageLocalization => _languageLocalization.Value;
+
+ static readonly Lazy _clientCompanion = new(() => GetConfigValue("ClientCompanion"));
+ public static bool ClientCompanion => _clientCompanion.Value;
+
+ static readonly Lazy _eliteShardBearers = new(() => GetConfigValue("EliteShardBearers"));
+ public static bool EliteShardBearers => _eliteShardBearers.Value;
+
+ static readonly Lazy _shardBearerLevel = new(() => GetConfigValue("ShardBearerLevel"));
+ public static int ShardBearerLevel => _shardBearerLevel.Value;
+
+ static readonly Lazy _potionStacking = new(() => GetConfigValue("PotionStacking"));
+ public static bool PotionStacking => _potionStacking.Value;
+
+ static readonly Lazy _starterKit = new(() => GetConfigValue("StarterKit"));
+ public static bool StarterKit => _starterKit.Value;
+
+ static readonly Lazy _kitPrefabs = new(() => GetConfigValue("KitPrefabs"));
+ public static string KitPrefabs => _kitPrefabs.Value;
+
+ static readonly Lazy _kitQuantities = new(() => GetConfigValue("KitQuantities"));
+ public static string KitQuantities => _kitQuantities.Value;
+
+ static readonly Lazy _questSystem = new(() => GetConfigValue("QuestSystem"));
+ public static bool QuestSystem => _questSystem.Value;
+
+ static readonly Lazy _infiniteDailies = new(() => GetConfigValue("InfiniteDailies"));
+ public static bool InfiniteDailies => _infiniteDailies.Value;
+
+ static readonly Lazy _questRewards = new(() => GetConfigValue("QuestRewards"));
+ public static string QuestRewards => _questRewards.Value;
+
+ static readonly Lazy _questRewardAmounts = new(() => GetConfigValue("QuestRewardAmounts"));
+ public static string QuestRewardAmounts => _questRewardAmounts.Value;
+
+ static readonly Lazy _rerollDailyPrefab = new(() => GetConfigValue("RerollDailyPrefab"));
+ public static int RerollDailyPrefab => _rerollDailyPrefab.Value;
+
+ static readonly Lazy _rerollDailyAmount = new(() => GetConfigValue("RerollDailyAmount"));
+ public static int RerollDailyAmount => _rerollDailyAmount.Value;
+
+ static readonly Lazy _rerollWeeklyPrefab = new(() => GetConfigValue("RerollWeeklyPrefab"));
+ public static int RerollWeeklyPrefab => _rerollWeeklyPrefab.Value;
+
+ static readonly Lazy _rerollWeeklyAmount = new(() => GetConfigValue("RerollWeeklyAmount"));
+ public static int RerollWeeklyAmount => _rerollWeeklyAmount.Value;
+ static readonly Lazy _levelingSystem = new(() => GetConfigValue("LevelingSystem"));
+ public static bool LevelingSystem => _levelingSystem.Value;
+
+ static readonly Lazy _restedXPSystem = new(() => GetConfigValue("RestedXPSystem"));
+ public static bool RestedXPSystem => _restedXPSystem.Value;
+
+ static readonly Lazy _restedXPRate = new(() => GetConfigValue("RestedXPRate"));
+ public static float RestedXPRate => _restedXPRate.Value;
+
+ static readonly Lazy _restedXPMax = new(() => GetConfigValue("RestedXPMax"));
+ public static int RestedXPMax => _restedXPMax.Value;
+
+ static readonly Lazy _restedXPTickRate = new(() => GetConfigValue("RestedXPTickRate"));
+ public static float RestedXPTickRate => _restedXPTickRate.Value;
+
+ static readonly Lazy _maxLevel = new(() => GetConfigValue("MaxLevel"));
+ public static int MaxLevel => _maxLevel.Value;
+
+ static readonly Lazy _startingLevel = new(() => GetConfigValue("StartingLevel"));
+ public static int StartingLevel => _startingLevel.Value;
+
+ static readonly Lazy _unitLevelingMultiplier = new(() => GetConfigValue("UnitLevelingMultiplier"));
+ public static float UnitLevelingMultiplier => _unitLevelingMultiplier.Value;
+
+ static readonly Lazy _vBloodLevelingMultiplier = new(() => GetConfigValue("VBloodLevelingMultiplier"));
+ public static float VBloodLevelingMultiplier => _vBloodLevelingMultiplier.Value;
+
+ static readonly Lazy _docileUnitMultiplier = new(() => GetConfigValue("DocileUnitMultiplier"));
+ public static float DocileUnitMultiplier => _docileUnitMultiplier.Value;
+
+ static readonly Lazy _warEventMultiplier = new(() => GetConfigValue("WarEventMultiplier"));
+ public static float WarEventMultiplier => _warEventMultiplier.Value;
+
+ static readonly Lazy _unitSpawnerMultiplier = new(() => GetConfigValue("UnitSpawnerMultiplier"));
+ public static float UnitSpawnerMultiplier => _unitSpawnerMultiplier.Value;
+
+ static readonly Lazy _unitSpawnerExpertiseFactor = new(() => GetConfigValue("UnitSpawnerExpertiseFactor"));
+ public static float UnitSpawnerExpertiseFactor => _unitSpawnerExpertiseFactor.Value;
+
+ static readonly Lazy _changeClassItem = new(() => GetConfigValue("ChangeClassItem"));
+ public static int ChangeClassItem => _changeClassItem.Value;
+
+ static readonly Lazy _changeClassQuantity = new(() => GetConfigValue("ChangeClassQuantity"));
+ public static int ChangeClassQuantity => _changeClassQuantity.Value;
+
+ static readonly Lazy _groupLevelingMultiplier = new(() => GetConfigValue("GroupLevelingMultiplier"));
+ public static float GroupLevelingMultiplier => _groupLevelingMultiplier.Value;
+
+ static readonly Lazy _levelScalingMultiplier = new(() => GetConfigValue("LevelScalingMultiplier"));
+ public static float LevelScalingMultiplier => _levelScalingMultiplier.Value;
+
+ static readonly Lazy _playerParties = new(() => GetConfigValue("PlayerParties"));
+ public static bool PlayerParties => _playerParties.Value;
+
+ static readonly Lazy _maxPartySize = new(() => GetConfigValue("MaxPartySize"));
+ public static int MaxPartySize => _maxPartySize.Value;
+
+ static readonly Lazy _expShareDistance = new(() => GetConfigValue("ExpShareDistance"));
+ public static float ExpShareDistance => _expShareDistance.Value;
+
+ static readonly Lazy _prestigeSystem = new(() => GetConfigValue("PrestigeSystem"));
+ public static bool PrestigeSystem => _prestigeSystem.Value;
+
+ static readonly Lazy _prestigeBuffs = new(() => GetConfigValue("PrestigeBuffs"));
+ public static string PrestigeBuffs => _prestigeBuffs.Value;
+
+ static readonly Lazy _prestigeLevelsToUnlockClassSpells = new(() => GetConfigValue("PrestigeLevelsToUnlockClassSpells"));
+ public static string PrestigeLevelsToUnlockClassSpells => _prestigeLevelsToUnlockClassSpells.Value;
+
+ static readonly Lazy _maxLevelingPrestiges = new(() => GetConfigValue("MaxLevelingPrestiges"));
+ public static int MaxLevelingPrestiges => _maxLevelingPrestiges.Value;
+
+ static readonly Lazy _levelingPrestigeReducer = new(() => GetConfigValue("LevelingPrestigeReducer"));
+ public static float LevelingPrestigeReducer => _levelingPrestigeReducer.Value;
+
+ static readonly Lazy _prestigeRatesReducer = new(() => GetConfigValue("PrestigeRatesReducer"));
+ public static float PrestigeRatesReducer => _prestigeRatesReducer.Value;
+
+ static readonly Lazy _prestigeStatMultiplier = new(() => GetConfigValue("PrestigeStatMultiplier"));
+ public static float PrestigeStatMultiplier => _prestigeStatMultiplier.Value;
+
+ static readonly Lazy _prestigeRateMultiplier = new(() => GetConfigValue("PrestigeRateMultiplier"));
+ public static float PrestigeRateMultiplier => _prestigeRateMultiplier.Value;
+
+ static readonly Lazy _exoPrestiging = new(() => GetConfigValue("ExoPrestiging"));
+ public static bool ExoPrestiging => _exoPrestiging.Value;
+
+ static readonly Lazy _exoPrestiges = new(() => GetConfigValue("ExoPrestiges"));
+ public static int ExoPrestiges => _exoPrestiges.Value;
+
+ static readonly Lazy _exoPrestigeReward = new(() => GetConfigValue("ExoPrestigeReward"));
+ public static int ExoPrestigeReward => _exoPrestigeReward.Value;
+
+ static readonly Lazy _exoPrestigeRewardQuantity = new(() => GetConfigValue("ExoPrestigeRewardQuantity"));
+ public static int ExoPrestigeRewardQuantity => _exoPrestigeRewardQuantity.Value;
+
+ static readonly Lazy _expertiseSystem = new(() => GetConfigValue("ExpertiseSystem"));
+ public static bool ExpertiseSystem => _expertiseSystem.Value;
+
+ static readonly Lazy _maxExpertisePrestiges = new(() => GetConfigValue("MaxExpertisePrestiges"));
+ public static int MaxExpertisePrestiges => _maxExpertisePrestiges.Value;
+
+ static readonly Lazy _unarmedSlots = new(() => GetConfigValue("UnarmedSlots"));
+ public static bool UnarmedSlots => _unarmedSlots.Value;
+
+ static readonly Lazy _shiftSlot = new(() => GetConfigValue("ShiftSlot"));
+ public static bool ShiftSlot => _shiftSlot.Value;
+
+ static readonly Lazy _maxExpertiseLevel = new(() => GetConfigValue("MaxExpertiseLevel"));
+ public static int MaxExpertiseLevel => _maxExpertiseLevel.Value;
+
+ static readonly Lazy _unitExpertiseMultiplier = new(() => GetConfigValue("UnitExpertiseMultiplier"));
+ public static float UnitExpertiseMultiplier => _unitExpertiseMultiplier.Value;
+
+ static readonly Lazy _vBloodExpertiseMultiplier = new(() => GetConfigValue("VBloodExpertiseMultiplier"));
+ public static float VBloodExpertiseMultiplier => _vBloodExpertiseMultiplier.Value;
+
+ static readonly Lazy _expertiseStatChoices = new(() => GetConfigValue("ExpertiseStatChoices"));
+ public static int ExpertiseStatChoices => _expertiseStatChoices.Value;
+
+ static readonly Lazy _resetExpertiseItem = new(() => GetConfigValue("ResetExpertiseItem"));
+ public static int ResetExpertiseItem => _resetExpertiseItem.Value;
+
+ static readonly Lazy _resetExpertiseItemQuantity = new(() => GetConfigValue("ResetExpertiseItemQuantity"));
+ public static int ResetExpertiseItemQuantity => _resetExpertiseItemQuantity.Value;
+
+ static readonly Lazy _maxHealth = new(() => GetConfigValue("MaxHealth"));
+ public static float MaxHealth => _maxHealth.Value;
+
+ static readonly Lazy _movementSpeed = new(() => GetConfigValue("MovementSpeed"));
+ public static float MovementSpeed => _movementSpeed.Value;
+
+ static readonly Lazy _primaryAttackSpeed = new(() => GetConfigValue("PrimaryAttackSpeed"));
+ public static float PrimaryAttackSpeed => _primaryAttackSpeed.Value;
+
+ static readonly Lazy _physicalLifeLeech = new(() => GetConfigValue("PhysicalLifeLeech"));
+ public static float PhysicalLifeLeech => _physicalLifeLeech.Value;
+
+ static readonly Lazy _spellLifeLeech = new(() => GetConfigValue("SpellLifeLeech"));
+ public static float SpellLifeLeech => _spellLifeLeech.Value;
+
+ static readonly Lazy _primaryLifeLeech = new(() => GetConfigValue("PrimaryLifeLeech"));
+ public static float PrimaryLifeLeech => _primaryLifeLeech.Value;
+
+ static readonly Lazy _physicalPower = new(() => GetConfigValue("PhysicalPower"));
+ public static float PhysicalPower => _physicalPower.Value;
+
+ static readonly Lazy _spellPower = new(() => GetConfigValue("SpellPower"));
+ public static float SpellPower => _spellPower.Value;
+
+ static readonly Lazy _physicalCritChance = new(() => GetConfigValue("PhysicalCritChance"));
+ public static float PhysicalCritChance => _physicalCritChance.Value;
+
+ static readonly Lazy _physicalCritDamage = new(() => GetConfigValue("PhysicalCritDamage"));
+ public static float PhysicalCritDamage => _physicalCritDamage.Value;
+
+ static readonly Lazy _spellCritChance = new(() => GetConfigValue("SpellCritChance"));
+ public static float SpellCritChance => _spellCritChance.Value;
+
+ static readonly Lazy _spellCritDamage = new(() => GetConfigValue("SpellCritDamage"));
+ public static float SpellCritDamage => _spellCritDamage.Value;
+
+ static readonly Lazy _bloodSystem = new(() => GetConfigValue("BloodSystem"));
+ public static bool BloodSystem => _bloodSystem.Value;
+
+ static readonly Lazy _maxLegacyPrestiges = new(() => GetConfigValue("MaxLegacyPrestiges"));
+ public static int MaxLegacyPrestiges => _maxLegacyPrestiges.Value;
+
+ static readonly Lazy _bloodQualityBonus = new(() => GetConfigValue("BloodQualityBonus"));
+ public static bool BloodQualityBonus => _bloodQualityBonus.Value;
+
+ static readonly Lazy _prestigeBloodQuality = new(() => GetConfigValue("PrestigeBloodQuality"));
+ public static float PrestigeBloodQuality => _prestigeBloodQuality.Value;
+
+ static readonly Lazy _maxBloodLevel = new(() => GetConfigValue("MaxBloodLevel"));
+ public static int MaxBloodLevel => _maxBloodLevel.Value;
+
+ static readonly Lazy _unitLegacyMultiplier = new(() => GetConfigValue("UnitLegacyMultiplier"));
+ public static float UnitLegacyMultiplier => _unitLegacyMultiplier.Value;
+
+ static readonly Lazy _vBloodLegacyMultiplier = new(() => GetConfigValue("VBloodLegacyMultiplier"));
+ public static float VBloodLegacyMultiplier => _vBloodLegacyMultiplier.Value;
+
+ static readonly Lazy _legacyStatChoices = new(() => GetConfigValue("LegacyStatChoices"));
+ public static int LegacyStatChoices => _legacyStatChoices.Value;
+
+ static readonly Lazy _resetLegacyItem = new(() => GetConfigValue("ResetLegacyItem"));
+ public static int ResetLegacyItem => _resetLegacyItem.Value;
+
+ static readonly Lazy _resetLegacyItemQuantity = new(() => GetConfigValue("ResetLegacyItemQuantity"));
+ public static int ResetLegacyItemQuantity => _resetLegacyItemQuantity.Value;
+
+ static readonly Lazy _healingReceived = new(() => GetConfigValue("HealingReceived"));
+ public static float HealingReceived => _healingReceived.Value;
+
+ static readonly Lazy _damageReduction = new(() => GetConfigValue("DamageReduction"));
+ public static float DamageReduction => _damageReduction.Value;
+
+ static readonly Lazy _physicalResistance = new(() => GetConfigValue("PhysicalResistance"));
+ public static float PhysicalResistance => _physicalResistance.Value;
+
+ static readonly Lazy _spellResistance = new(() => GetConfigValue("SpellResistance"));
+ public static float SpellResistance => _spellResistance.Value;
+
+ static readonly Lazy _resourceYield = new(() => GetConfigValue("ResourceYield"));
+ public static float ResourceYield => _resourceYield.Value;
+
+ static readonly Lazy _ccReduction = new(() => GetConfigValue("CCReduction"));
+ public static float CCReduction => _ccReduction.Value;
+
+ static readonly Lazy _spellCooldownRecoveryRate = new(() => GetConfigValue("SpellCooldownRecoveryRate"));
+ public static float SpellCooldownRecoveryRate => _spellCooldownRecoveryRate.Value;
+
+ static readonly Lazy _weaponCooldownRecoveryRate = new(() => GetConfigValue("WeaponCooldownRecoveryRate"));
+ public static float WeaponCooldownRecoveryRate => _weaponCooldownRecoveryRate.Value;
+
+ static readonly Lazy _ultimateCooldownRecoveryRate = new(() => GetConfigValue("UltimateCooldownRecoveryRate"));
+ public static float UltimateCooldownRecoveryRate => _ultimateCooldownRecoveryRate.Value;
+
+ static readonly Lazy _minionDamage = new(() => GetConfigValue("MinionDamage"));
+ public static float MinionDamage => _minionDamage.Value;
+
+ static readonly Lazy _shieldAbsorb = new(() => GetConfigValue("ShieldAbsorb"));
+ public static float ShieldAbsorb => _shieldAbsorb.Value;
+
+ static readonly Lazy _bloodEfficiency = new(() => GetConfigValue("BloodEfficiency"));
+ public static float BloodEfficiency => _bloodEfficiency.Value;
+
+ static readonly Lazy _professionSystem = new(() => GetConfigValue("ProfessionSystem"));
+ public static bool ProfessionSystem => _professionSystem.Value;
+
+ static readonly Lazy _maxProfessionLevel = new(() => GetConfigValue("MaxProfessionLevel"));
+ public static int MaxProfessionLevel => _maxProfessionLevel.Value;
+
+ static readonly Lazy _professionMultiplier = new(() => GetConfigValue("ProfessionMultiplier"));
+ public static float ProfessionMultiplier => _professionMultiplier.Value;
+
+ static readonly Lazy _extraRecipes = new(() => GetConfigValue("ExtraRecipes"));
+ public static bool ExtraRecipes => _extraRecipes.Value;
+
+ static readonly Lazy _familiarSystem = new(() => GetConfigValue("FamiliarSystem"));
+ public static bool FamiliarSystem => _familiarSystem.Value;
+
+ static readonly Lazy _shareUnlocks = new(() => GetConfigValue("ShareUnlocks"));
+ public static bool ShareUnlocks => _shareUnlocks.Value;
+
+ static readonly Lazy _familiarCombat = new(() => GetConfigValue("FamiliarCombat"));
+ public static bool FamiliarCombat => _familiarCombat.Value;
+
+ static readonly Lazy _familiarPvP = new(() => GetConfigValue("FamiliarPvP"));
+ public static bool FamiliarPvP => _familiarPvP.Value;
+
+ static readonly Lazy _familiarPrestige = new(() => GetConfigValue("FamiliarPrestige"));
+ public static bool FamiliarPrestige => _familiarPrestige.Value;
+
+ static readonly Lazy _maxFamiliarPrestiges = new(() => GetConfigValue("MaxFamiliarPrestiges"));
+ public static int MaxFamiliarPrestiges => _maxFamiliarPrestiges.Value;
+
+ static readonly Lazy _familiarPrestigeStatMultiplier = new(() => GetConfigValue("FamiliarPrestigeStatMultiplier"));
+ public static float FamiliarPrestigeStatMultiplier => _familiarPrestigeStatMultiplier.Value;
+
+ static readonly Lazy _maxFamiliarLevel = new(() => GetConfigValue("MaxFamiliarLevel"));
+ public static int MaxFamiliarLevel => _maxFamiliarLevel.Value;
+
+ static readonly Lazy _allowVBloods = new(() => GetConfigValue("AllowVBloods"));
+ public static bool AllowVBloods => _allowVBloods.Value;
+
+ static readonly Lazy _bannedUnits = new(() => GetConfigValue("BannedUnits"));
+ public static string BannedUnits => _bannedUnits.Value;
+
+ static readonly Lazy _bannedTypes = new(() => GetConfigValue("BannedTypes"));
+ public static string BannedTypes => _bannedTypes.Value;
+
+ static readonly Lazy _vBloodDamageMultiplier = new(() => GetConfigValue("VBloodDamageMultiplier"));
+ public static float VBloodDamageMultiplier => _vBloodDamageMultiplier.Value;
+
+ static readonly Lazy _unitFamiliarMultiplier = new(() => GetConfigValue("UnitFamiliarMultiplier"));
+ public static float UnitFamiliarMultiplier => _unitFamiliarMultiplier.Value;
+
+ static readonly Lazy _vBloodFamiliarMultiplier = new(() => GetConfigValue("VBloodFamiliarMultiplier"));
+ public static float VBloodFamiliarMultiplier => _vBloodFamiliarMultiplier.Value;
+
+ static readonly Lazy _unitUnlockChance = new(() => GetConfigValue("UnitUnlockChance"));
+ public static float UnitUnlockChance => _unitUnlockChance.Value;
+
+ static readonly Lazy _vBloodUnlockChance = new(() => GetConfigValue