mirror of
https://github.com/MikuLeaks/MikuSB.git
synced 2026-06-04 05:23:59 +00:00
Compare commits
2 Commits
5aa5ef92d0
...
400db16f39
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
400db16f39 | ||
|
|
42b1ad1024 |
@@ -19,6 +19,11 @@ public static class ConfigManager
|
|||||||
//LoadHotfixData();
|
//LoadHotfixData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void SaveConfig()
|
||||||
|
{
|
||||||
|
SaveData(Config, ConfigFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
private static void LoadConfigData()
|
private static void LoadConfigData()
|
||||||
{
|
{
|
||||||
var file = new FileInfo(ConfigFilePath);
|
var file = new FileInfo(ConfigFilePath);
|
||||||
@@ -43,9 +48,26 @@ public static class ConfigManager
|
|||||||
Config = JsonConvert.DeserializeObject<ConfigContainer>(json)!;
|
Config = JsonConvert.DeserializeObject<ConfigContainer>(json)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Config.Loader.Arguments = NormalizeLoaderArguments(Config.Loader.Arguments);
|
||||||
SaveData(Config, ConfigFilePath);
|
SaveData(Config, ConfigFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string[] NormalizeLoaderArguments(string[]? arguments)
|
||||||
|
{
|
||||||
|
var result = new List<string>(arguments ?? []);
|
||||||
|
var userDataDirectory = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "Client_User_Data"));
|
||||||
|
Directory.CreateDirectory(userDataDirectory);
|
||||||
|
|
||||||
|
var userDirArgument = $"-userdir={userDataDirectory}";
|
||||||
|
var existingIndex = result.FindIndex(x => x.StartsWith("-userdir=", StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (existingIndex >= 0)
|
||||||
|
result[existingIndex] = userDirArgument;
|
||||||
|
else
|
||||||
|
result.Add(userDirArgument);
|
||||||
|
|
||||||
|
return result.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
private static void LoadHotfixData()
|
private static void LoadHotfixData()
|
||||||
{
|
{
|
||||||
var file = new FileInfo(HotfixFilePath);
|
var file = new FileInfo(HotfixFilePath);
|
||||||
|
|||||||
296
GameServer/Server/CallGS/Handlers/Girl/GirlCard_UpdateLevel.cs
Normal file
296
GameServer/Server/CallGS/Handlers/Girl/GirlCard_UpdateLevel.cs
Normal file
@@ -0,0 +1,296 @@
|
|||||||
|
using MikuSB.Data;
|
||||||
|
using MikuSB.Database;
|
||||||
|
using MikuSB.Database.Inventory;
|
||||||
|
using MikuSB.Database.Player;
|
||||||
|
using MikuSB.Enums.Item;
|
||||||
|
using MikuSB.Proto;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace MikuSB.GameServer.Server.CallGS.Handlers.Girl;
|
||||||
|
|
||||||
|
[CallGSApi("GirlCard_UpdateLevel")]
|
||||||
|
public class GirlCard_UpdateLevel : ICallGSHandler
|
||||||
|
{
|
||||||
|
private const uint CashGroupId = 1;
|
||||||
|
private const uint SilverMoneyType = 3;
|
||||||
|
private const uint SilverSid = SilverMoneyType * 2 + 1;
|
||||||
|
private const uint RoleMaxLevel = 80;
|
||||||
|
|
||||||
|
public async Task Handle(Connection connection, string param, ushort seqNo)
|
||||||
|
{
|
||||||
|
var player = connection.Player!;
|
||||||
|
var req = JsonSerializer.Deserialize<GirlCardUpdateLevelParam>(param);
|
||||||
|
if (req == null || req.Id == 0 || req.Materials == null || req.Materials.Count == 0)
|
||||||
|
{
|
||||||
|
await CallGSRouter.SendScript(connection, "GirlCard_UpdateLevel", "{\"sErr\":\"error.BadParam\"}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var card = player.CharacterManager.GetCharacterByGUID((uint)req.Id);
|
||||||
|
if (card == null)
|
||||||
|
{
|
||||||
|
await CallGSRouter.SendScript(connection, "GirlCard_UpdateLevel", "{\"sErr\":\"error.BadParam\"}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cardTemplate = GameData.CardData.Values.FirstOrDefault(x =>
|
||||||
|
GameResourceTemplateId.FromGdpl(x.Genre, x.Detail, x.Particular, x.Level) == card.TemplateId);
|
||||||
|
if (cardTemplate == null)
|
||||||
|
{
|
||||||
|
await CallGSRouter.SendScript(connection, "GirlCard_UpdateLevel", "{\"sErr\":\"error.BadParam\"}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var levelCap = GetCardLevelCap(player.Data.Level, cardTemplate.LevelLimitID);
|
||||||
|
if (levelCap == 0)
|
||||||
|
{
|
||||||
|
levelCap = card.Level;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (card.Level >= RoleMaxLevel)
|
||||||
|
{
|
||||||
|
await CallGSRouter.SendScript(connection, "GirlCard_UpdateLevel", "{\"sErr\":\"tip.card_max_level\"}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var requestedMaterials = new Dictionary<uint, uint>();
|
||||||
|
foreach (var row in req.Materials)
|
||||||
|
{
|
||||||
|
if (row == null || row.Id == 0 || row.Num == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
requestedMaterials[(uint)row.Id] = requestedMaterials.GetValueOrDefault((uint)row.Id) + row.Num;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestedMaterials.Count == 0)
|
||||||
|
{
|
||||||
|
await CallGSRouter.SendScript(connection, "GirlCard_UpdateLevel", "{\"sErr\":\"tip.material_not_enough\"}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong totalExp = 0;
|
||||||
|
ulong totalSilverCost = 0;
|
||||||
|
foreach (var (itemId, count) in requestedMaterials)
|
||||||
|
{
|
||||||
|
var item = player.InventoryManager.GetNormalItem(itemId);
|
||||||
|
if (item == null || item.ItemCount < count)
|
||||||
|
{
|
||||||
|
await CallGSRouter.SendScript(connection, "GirlCard_UpdateLevel", "{\"sErr\":\"tip.material_not_enough\"}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!GameData.SuppliesData.TryGetValue((uint)item.TemplateId, out var supplies) || supplies.ProvideExp == 0)
|
||||||
|
{
|
||||||
|
await CallGSRouter.SendScript(connection, "GirlCard_UpdateLevel", "{\"sErr\":\"error.BadParam\"}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
totalExp += (ulong)supplies.ProvideExp * count;
|
||||||
|
totalSilverCost += (ulong)supplies.ConsumeGold * count;
|
||||||
|
}
|
||||||
|
|
||||||
|
var silverAttr = GetOrCreateAttr(player.Data, CashGroupId, SilverSid);
|
||||||
|
if ((ulong)silverAttr.Val < totalSilverCost)
|
||||||
|
{
|
||||||
|
await CallGSRouter.SendScript(connection, "GirlCard_UpdateLevel", "{\"sErr\":\"tip.material_not_enough\"}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var syncItems = new List<Item>();
|
||||||
|
foreach (var (itemId, count) in requestedMaterials)
|
||||||
|
{
|
||||||
|
var item = player.InventoryManager.GetNormalItem(itemId)!;
|
||||||
|
item.ItemCount -= count;
|
||||||
|
|
||||||
|
if (item.ItemCount == 0)
|
||||||
|
{
|
||||||
|
player.InventoryManager.InventoryData.Items.Remove(item.UniqueId);
|
||||||
|
syncItems.Add(BuildRemovedProto(item));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
syncItems.Add(item.ToProto());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
silverAttr.Val -= checked((uint)totalSilverCost);
|
||||||
|
|
||||||
|
var (newLevel, newExp) = ApplyCardExp(card.Level, card.Exp, totalExp, levelCap);
|
||||||
|
card.Level = newLevel;
|
||||||
|
card.Exp = checked((int)newExp);
|
||||||
|
syncItems.Add(card.ToProto());
|
||||||
|
|
||||||
|
DatabaseHelper.SaveDatabaseType(player.InventoryManager.InventoryData);
|
||||||
|
DatabaseHelper.SaveDatabaseType(player.CharacterManager.CharacterData);
|
||||||
|
DatabaseHelper.SaveDatabaseType(player.Data);
|
||||||
|
|
||||||
|
var sync = new NtfSyncPlayer();
|
||||||
|
sync.Items.AddRange(syncItems);
|
||||||
|
sync.Custom[player.ToPackedAttrKey(CashGroupId, SilverSid)] = silverAttr.Val;
|
||||||
|
sync.Custom[player.ToShiftedAttrKey(CashGroupId, SilverSid)] = silverAttr.Val;
|
||||||
|
|
||||||
|
await CallGSRouter.SendScript(connection, "GirlCard_UpdateLevel", "null", sync);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PlayerAttr GetOrCreateAttr(PlayerGameData data, uint gid, uint sid)
|
||||||
|
{
|
||||||
|
var attr = data.Attrs.FirstOrDefault(x => x.Gid == gid && x.Sid == sid);
|
||||||
|
if (attr != null)
|
||||||
|
return attr;
|
||||||
|
|
||||||
|
attr = new PlayerAttr
|
||||||
|
{
|
||||||
|
Gid = gid,
|
||||||
|
Sid = sid,
|
||||||
|
Val = 0
|
||||||
|
};
|
||||||
|
data.Attrs.Add(attr);
|
||||||
|
return attr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Item BuildRemovedProto(BaseGameItemInfo item)
|
||||||
|
{
|
||||||
|
var proto = item.ToProto();
|
||||||
|
proto.Count = 0;
|
||||||
|
return proto;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static uint GetCardLevelCap(uint playerLevel, int levelLimitId)
|
||||||
|
{
|
||||||
|
var limits = LoadCardLevelLimit(levelLimitId);
|
||||||
|
if (limits.Count == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
uint nearestAccountLevel = 0;
|
||||||
|
uint nearestCardLevel = 0;
|
||||||
|
|
||||||
|
foreach (var (accountLevel, cardLevel) in limits)
|
||||||
|
{
|
||||||
|
if (accountLevel < playerLevel)
|
||||||
|
{
|
||||||
|
nearestAccountLevel = accountLevel;
|
||||||
|
nearestCardLevel = cardLevel;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accountLevel == playerLevel)
|
||||||
|
return Math.Min(cardLevel, RoleMaxLevel);
|
||||||
|
|
||||||
|
var distance = accountLevel - nearestAccountLevel;
|
||||||
|
if (distance == 0)
|
||||||
|
return Math.Min(cardLevel, RoleMaxLevel);
|
||||||
|
|
||||||
|
var percent = (playerLevel - nearestAccountLevel) / (double)distance;
|
||||||
|
var interpolated = (uint)Math.Floor(nearestCardLevel + ((cardLevel - nearestCardLevel) * percent));
|
||||||
|
return Math.Min(interpolated, RoleMaxLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.Min(nearestCardLevel, RoleMaxLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<(uint AccountLevel, uint CardLevel)> LoadCardLevelLimit(int levelLimitId)
|
||||||
|
{
|
||||||
|
var path = Path.Combine(
|
||||||
|
AppContext.BaseDirectory,
|
||||||
|
"Resources",
|
||||||
|
"item",
|
||||||
|
"level_limit.json");
|
||||||
|
|
||||||
|
if (!File.Exists(path))
|
||||||
|
return [];
|
||||||
|
|
||||||
|
using var doc = JsonDocument.Parse(File.ReadAllText(path));
|
||||||
|
var result = new List<(uint AccountLevel, uint CardLevel)>();
|
||||||
|
|
||||||
|
foreach (var row in doc.RootElement.EnumerateArray())
|
||||||
|
{
|
||||||
|
if (!row.TryGetProperty("ID", out var idProp) || idProp.GetInt32() != levelLimitId)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!row.TryGetProperty("Type", out var typeProp) || typeProp.GetInt32() != 1)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!row.TryGetProperty("Limit", out var limitProp) || limitProp.ValueKind != JsonValueKind.Object)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
foreach (var property in limitProp.EnumerateObject())
|
||||||
|
{
|
||||||
|
if (!uint.TryParse(property.Name, out var accountLevel))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
uint cardLevel;
|
||||||
|
if (property.Value.ValueKind == JsonValueKind.Number)
|
||||||
|
{
|
||||||
|
cardLevel = property.Value.GetUInt32();
|
||||||
|
}
|
||||||
|
else if (property.Value.ValueKind == JsonValueKind.String &&
|
||||||
|
uint.TryParse(property.Value.GetString(), out var parsed))
|
||||||
|
{
|
||||||
|
cardLevel = parsed;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Add((accountLevel, cardLevel));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Sort((a, b) => a.AccountLevel.CompareTo(b.AccountLevel));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (uint Level, ulong Exp) ApplyCardExp(uint level, int currentExp, ulong addedExp, uint levelCap)
|
||||||
|
{
|
||||||
|
var destLevel = level == 0 ? 1u : level;
|
||||||
|
var destExp = (ulong)Math.Max(0, currentExp) + addedExp;
|
||||||
|
|
||||||
|
if (levelCap > 0 && destLevel >= levelCap)
|
||||||
|
return (destLevel, destExp);
|
||||||
|
|
||||||
|
while (destLevel < RoleMaxLevel)
|
||||||
|
{
|
||||||
|
var needExp = GetCardNeedExp(destLevel);
|
||||||
|
if (needExp == 0 || destExp < needExp)
|
||||||
|
break;
|
||||||
|
|
||||||
|
destExp -= needExp;
|
||||||
|
destLevel++;
|
||||||
|
|
||||||
|
if (levelCap > 0 && destLevel >= levelCap)
|
||||||
|
return (levelCap, destExp);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (destLevel, destExp);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static uint GetCardNeedExp(uint currentLevel)
|
||||||
|
{
|
||||||
|
if (GameData.UpgradeExpData.TryGetValue((int)currentLevel, out var row))
|
||||||
|
return row.CardNeedExp;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class GirlCardUpdateLevelParam
|
||||||
|
{
|
||||||
|
[JsonPropertyName("Id")]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("tbMaterials")]
|
||||||
|
public List<GirlCardLevelMaterial> Materials { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class GirlCardLevelMaterial
|
||||||
|
{
|
||||||
|
[JsonPropertyName("Id")]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("Num")]
|
||||||
|
public uint Num { get; set; }
|
||||||
|
}
|
||||||
@@ -293,11 +293,14 @@ public sealed class LaunchOptions
|
|||||||
public static LaunchOptions FromConfig(IEnumerable<string>? extraGameArguments = null)
|
public static LaunchOptions FromConfig(IEnumerable<string>? extraGameArguments = null)
|
||||||
{
|
{
|
||||||
var config = ConfigManager.Config;
|
var config = ConfigManager.Config;
|
||||||
|
var serverBaseDirectory = AppContext.BaseDirectory;
|
||||||
var gamePath = ResolvePath(config.Loader.GamePath, AppContext.BaseDirectory);
|
var gamePath = ResolvePath(config.Loader.GamePath, AppContext.BaseDirectory);
|
||||||
var patchPaths = ResolvePatchPaths(config.Loader.PatchPaths, AppContext.BaseDirectory);
|
var patchPaths = ResolvePatchPaths(config.Loader.PatchPaths, serverBaseDirectory);
|
||||||
var gameArgs = new List<string>(config.Loader.Arguments ?? []);
|
var gameArgs = new List<string>(config.Loader.Arguments ?? []);
|
||||||
if (extraGameArguments is not null)
|
if (extraGameArguments is not null)
|
||||||
gameArgs.AddRange(extraGameArguments.Where(x => !string.IsNullOrWhiteSpace(x)));
|
gameArgs.AddRange(extraGameArguments.Where(x => !string.IsNullOrWhiteSpace(x)));
|
||||||
|
gameArgs = EnsureUserDirArgument(gameArgs, serverBaseDirectory);
|
||||||
|
PersistResolvedArgumentsIfChanged(config, gameArgs);
|
||||||
|
|
||||||
var env = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
var env = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
if (config.Loader.SetAllProxy && config.Proxy.Enabled)
|
if (config.Loader.SetAllProxy && config.Proxy.Enabled)
|
||||||
@@ -330,6 +333,31 @@ public sealed class LaunchOptions
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static List<string> EnsureUserDirArgument(List<string> gameArgs, string baseDirectory)
|
||||||
|
{
|
||||||
|
var userDataDirectory = Path.GetFullPath(Path.Combine(baseDirectory, "Client_User_Data"));
|
||||||
|
Directory.CreateDirectory(userDataDirectory);
|
||||||
|
|
||||||
|
var userDirArgument = $"-userdir={userDataDirectory}";
|
||||||
|
var existingIndex = gameArgs.FindIndex(x => x.StartsWith("-userdir=", StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (existingIndex >= 0)
|
||||||
|
gameArgs[existingIndex] = userDirArgument;
|
||||||
|
else
|
||||||
|
gameArgs.Add(userDirArgument);
|
||||||
|
|
||||||
|
return gameArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void PersistResolvedArgumentsIfChanged(Configuration.ConfigContainer config, List<string> gameArgs)
|
||||||
|
{
|
||||||
|
var currentArgs = config.Loader.Arguments ?? [];
|
||||||
|
if (currentArgs.SequenceEqual(gameArgs, StringComparer.Ordinal))
|
||||||
|
return;
|
||||||
|
|
||||||
|
config.Loader.Arguments = gameArgs.ToArray();
|
||||||
|
ConfigManager.SaveConfig();
|
||||||
|
}
|
||||||
|
|
||||||
private static string? ResolvePath(string? value, string baseDirectory)
|
private static string? ResolvePath(string? value, string baseDirectory)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(value))
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
v=2.9
|
v=3.0
|
||||||
Reference in New Issue
Block a user