feat: better relic logic & farming config

This commit is contained in:
losunet
2025-02-24 11:38:44 +08:00
parent 256f0e3f69
commit 017b03cb73
11 changed files with 369 additions and 62 deletions

View File

@@ -85,6 +85,12 @@ public class ServerOption
public ServerProfile ServerProfile { get; set; } = new();
public bool AutoCreateUser { get; set; } = true;
public bool SavePersonalDebugFile { get; set; } = false;
public int FarmingDropRate { get; set; } = 1;
public int ValidFarmingDropRate()
{
return Math.Max(Math.Min(FarmingDropRate, 999), 1);
}
}
public class ServerAnnounce

View File

@@ -1,4 +1,7 @@
using EggLink.DanhengServer.Enums.Item;
using EggLink.DanhengServer.Database.Inventory;
using EggLink.DanhengServer.Enums.Item;
using EggLink.DanhengServer.Util;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
@@ -183,6 +186,129 @@ public class MappingInfoExcel : ExcelResource
}
}
}
public List<ItemData> GenerateRelicDrops()
{
var relicsMap = new Dictionary<int, List<MappingInfoItem>>();
foreach (var relic in DropRelicItemList)
{
GameData.ItemConfigData.TryGetValue(relic.ItemID, out var itemData);
if (itemData == null) continue;
switch (itemData.Rarity)
{
case ItemRarityEnum.NotNormal:
AddRelicToMap(relic, 2, relicsMap);
break;
case ItemRarityEnum.Rare:
AddRelicToMap(relic, 3, relicsMap);
break;
case ItemRarityEnum.VeryRare:
AddRelicToMap(relic, 4, relicsMap);
break;
case ItemRarityEnum.SuperRare:
AddRelicToMap(relic, 5, relicsMap);
break;
default:
continue;
}
}
List<ItemData> drops = [];
// Add higher rarity relics first
for (var rarity = 5; rarity >= 2; rarity--)
{
var count = GetRelicCountByWorldLevel(rarity) *
ConfigManager.Config.ServerOption.ValidFarmingDropRate();
if (count <= 0) continue;
if (!relicsMap.TryGetValue(rarity, out var value)) continue;
if (value.IsNullOrEmpty()) continue;
while (count > 0)
{
var relic = value.RandomElement();
drops.Add(new ItemData
{
ItemId = relic.ItemID,
Count = 1
});
count--;
}
}
return drops;
}
private void AddRelicToMap(MappingInfoItem relic, int rarity, Dictionary<int, List<MappingInfoItem>> relicsMap)
{
if (relicsMap.TryGetValue(rarity, out var value))
{
value.Add(relic);
}
else
{
relicsMap.Add(rarity, [relic]);
}
}
private int GetRelicCountByWorldLevel(int rarity)
{
return WorldLevel switch
{
1 => rarity switch
{
2 => 6,
3 => 3,
4 => 1,
5 => 0,
_ => 0
},
2 => rarity switch
{
2 => 2,
3 => 4,
4 => 2 + LuckyRelicDropped(),
5 => 0,
_ => 0
},
3 => rarity switch
{
2 => 0,
3 => 4,
4 => 2,
5 => 1,
_ => 0
},
4 => rarity switch
{
2 => 0,
3 => 3,
4 => 2 + LuckyRelicDropped(),
5 => 1 + LuckyRelicDropped(),
_ => 0
},
5 => rarity switch
{
2 => 0,
3 => 1 + LuckyRelicDropped(),
4 => 3,
5 => 2,
_ => 0
},
6 => rarity switch
{
2 => 0,
3 => 0,
4 => 5,
5 => 2 + LuckyRelicDropped(),
_ => 0
},
_ => 0
};
}
private int LuckyRelicDropped()
{
return Random.Shared.Next(100) < 25 ? 1 : 0;
}
}
public class MappingInfoItem

View File

@@ -1,4 +1,5 @@
using EggLink.DanhengServer.Database.Inventory;
using EggLink.DanhengServer.Util;
using Newtonsoft.Json;
namespace EggLink.DanhengServer.Data.Excel;
@@ -54,7 +55,7 @@ public class MonsterDropExcel : ExcelResource
result.Add(new ItemData
{
ItemId = item.ItemID,
Count = count
Count = count * ConfigManager.Config.ServerOption.ValidFarmingDropRate()
});
}

View File

@@ -1,5 +1,6 @@
using EggLink.DanhengServer.Data;
using EggLink.DanhengServer.Data.Excel;
using EggLink.DanhengServer.Enums.Item;
using EggLink.DanhengServer.Proto;
using EggLink.DanhengServer.Util;
using SqlSugar;
@@ -60,15 +61,22 @@ public class ItemData
public void AddRandomRelicSubAffix(int count = 1)
{
// Avoid illegal count of relic sub affixes
if (count is < 1 or > 4 || SubAffixes.Count >= 4) return;
GameData.RelicConfigData.TryGetValue(ItemId, out var config);
if (config == null) return;
GameData.RelicSubAffixData.TryGetValue(config.SubAffixGroup, out var affixes);
if (affixes == null) return;
// Avoid same property on both main affix and sub affixes
GameData.RelicMainAffixData.TryGetValue(config.MainAffixGroup, out var mainAffixes);
if (mainAffixes == null) return;
var mainProperty = mainAffixes[MainAffix].Property;
var rollPool = new List<RelicSubAffixConfigExcel>();
foreach (var affix in affixes.Values)
if (SubAffixes.Find(x => x.Id == affix.AffixID) == null)
if (affix.Property != mainProperty & SubAffixes.Find(x => x.Id == affix.AffixID) == null)
rollPool.Add(affix);
for (var i = 0; i < count; i++)
@@ -77,11 +85,68 @@ public class ItemData
ItemSubAffix subAffix = new(affixConfig, 1);
SubAffixes.Add(subAffix);
rollPool.Remove(affixConfig);
if (SubAffixes.Count >= 4) break;
}
}
/**
* Init relic sub affixes based on rarity
* 20% chance to get one more affix
* r3 1-2
* r4 2-3
* r5 3-4
*/
public void InitRandomRelicSubAffixesByRarity(ItemRarityEnum rarity = ItemRarityEnum.Unknown)
{
if (rarity == ItemRarityEnum.Unknown)
{
GameData.ItemConfigData.TryGetValue(ItemId, out var config);
if (config == null) return;
rarity = config.Rarity;
}
int initSubAffixesCount;
switch (rarity)
{
case ItemRarityEnum.Rare:
initSubAffixesCount = 1 + LuckyRelicSubAffixCount();
break;
case ItemRarityEnum.VeryRare:
initSubAffixesCount = 2 + LuckyRelicSubAffixCount();
break;
case ItemRarityEnum.SuperRare:
initSubAffixesCount = 3 + LuckyRelicSubAffixCount();
break;
default:
return;
}
AddRandomRelicSubAffix(initSubAffixesCount);
}
public int LuckyRelicSubAffixCount()
{
return Random.Shared.Next(100) < 20 ? 1 : 0;
}
#endregion
public int CalcTotalExpGained()
{
if (Level <= 0) return Exp;
GameData.RelicConfigData.TryGetValue(ItemId, out var costExcel);
if (costExcel == null) return 0;
var exp = 0;
for (var i = 0; i < Level; i++)
{
GameData.RelicExpTypeData.TryGetValue(costExcel.ExpType * 100 + i, out var typeExcel);
if (typeExcel != null)
exp += typeExcel.Exp;
}
return exp + Exp;
}
#region Serialization
public Material ToMaterialProto()

View File

@@ -3,7 +3,7 @@
public static class GameConstants
{
public const int INVENTORY_MAX_EQUIPMENT = 1500;
public const int INVENTORY_MAX_RELIC = 1500;
public const int INVENTORY_MAX_RELIC = 2000;
public const int INVENTORY_MAX_MATERIAL = 2000;
public const int MAX_LINEUP_COUNT = 9;

View File

@@ -13,6 +13,7 @@ using EggLink.DanhengServer.GameServer.Server.Packet.Send.Scene;
using EggLink.DanhengServer.Proto;
using EggLink.DanhengServer.Util;
using Google.Protobuf.Collections;
using Microsoft.Net.Http.Headers;
namespace EggLink.DanhengServer.GameServer.Game.Inventory;
@@ -99,7 +100,7 @@ public class InventoryManager(PlayerInstance player) : BasePlayerManager(player)
var item = await PutItem(itemId, 1, 1, level: 0, uniqueId: ++Data.NextUniqueId);
item.AddRandomRelicMainAffix();
item.AddRandomRelicSubAffix(3);
item.InitRandomRelicSubAffixesByRarity();
Data.RelicItems.Find(x => x.UniqueId == item.UniqueId)!.SubAffixes = item.SubAffixes;
itemData = item;
break;
@@ -459,43 +460,20 @@ public class InventoryManager(PlayerInstance player) : BasePlayerManager(player)
items.Add(new ItemData
{
ItemId = item.ItemID,
Count = amount
Count = amount * (item.ItemID == 22
? 1
: ConfigManager.Config.ServerOption.ValidFarmingDropRate())
});
}
}
// randomize the order of the relics
var relics = mapping.DropRelicItemList.OrderBy(x => Random.Shared.Next()).ToList();
var relic5Count = Random.Shared.Next(worldLevel - 4, worldLevel - 2);
var relic4Count = worldLevel - 2;
foreach (var relic in relics)
{
var random = Random.Shared.Next(0, 101);
if (random <= relic.Chance)
{
var amount = relic.ItemNum > 0
? relic.ItemNum
: Random.Shared.Next(relic.MinCount, relic.MaxCount + 1);
GameData.ItemConfigData.TryGetValue(relic.ItemID, out var itemData);
if (itemData == null) continue;
if (itemData.Rarity == ItemRarityEnum.SuperRare && relic5Count > 0)
relic5Count--;
else if (itemData.Rarity == ItemRarityEnum.VeryRare && relic4Count > 0)
relic4Count--;
else
continue;
items.Add(new ItemData
{
ItemId = relic.ItemID,
Count = 1
});
}
}
// Generate relics
var relicDrops = mapping.GenerateRelicDrops();
// Let AddItem notify relics count exceeding limit
items.AddRange(Data.RelicItems.Count + relicDrops.Count - 1 > GameConstants.INVENTORY_MAX_RELIC
? relicDrops[..(GameConstants.INVENTORY_MAX_RELIC - Data.RelicItems.Count + 1)]
: relicDrops);
foreach (var item in items)
{
@@ -621,42 +599,73 @@ public class InventoryManager(PlayerInstance player) : BasePlayerManager(player)
await RemoveItem(2, (int)(composeConfig.CoinCost * req.Count));
var relicId = (int)req.ComposeRelicId;
GameData.RelicConfigData.TryGetValue(relicId, out var itemConfig);
GameData.RelicSubAffixData.TryGetValue(itemConfig!.SubAffixGroup, out var subAffixConfig);
// Add relic
var subAffixes = req.SubAffixIdList.Select(subId => ((int)subId, 1)).ToList();
var mainAffix = (int)req.MainAffixId;
var itemData = new ItemData
{
ItemId = relicId,
Level = 0,
UniqueId = ++Data.NextUniqueId,
MainAffix = mainAffix,
SubAffixes = req.SubAffixIdList.Select(subId => new ItemSubAffix(subAffixConfig![(int)subId], 1)).ToList(),
Count = 1
};
if (mainAffix == 0) itemData.AddRandomRelicMainAffix();
itemData.AddRandomRelicSubAffix(3 - itemData.SubAffixes.Count + itemData.LuckyRelicSubAffixCount());
await AddItem(itemData, notify: false);
var (_, relic) = await HandleRelic(
(int)req.ComposeRelicId, ++Data.NextUniqueId, 0, (int)req.MainAffixId, subAffixes);
return relic;
return itemData;
}
public async ValueTask ReforgeRelic(int uniqueId)
{
var relic = Data.RelicItems.FirstOrDefault(x => x.UniqueId == uniqueId);
await RemoveItem(relic!.ItemId, 1, uniqueId, false);
var relic = Data.RelicItems.First(x => x.UniqueId == uniqueId);
var totalCount = 0;
var subAffixes = new List<(int, int)>();
foreach (var sub in relic.SubAffixes)
{
totalCount += sub.Count;
subAffixes.Add((sub.Id, 0));
totalCount = totalCount + sub.Count - 1;
subAffixes.Add((sub.Id, 1));
}
var remainCount = totalCount;
for (var i = 0; i < subAffixes.Count - 1; i++)
while (totalCount > 0)
{
var count = new Random().Next(1, remainCount - (subAffixes.Count - i - 1));
subAffixes[i] = (subAffixes[i].Item1, count);
remainCount -= count;
var idx = Random.Shared.Next(subAffixes.Count);
var cur = subAffixes[idx];
subAffixes[idx] = (cur.Item1, cur.Item2 + 1);
totalCount--;
}
GameData.RelicConfigData.TryGetValue(relic.ItemId, out var itemConfig);
GameData.RelicSubAffixData.TryGetValue(itemConfig!.SubAffixGroup, out var subAffixConfig);
for (var i = 0; i < subAffixes.Count; i++)
{
var (subId, subLevel) = subAffixes[i];
subAffixConfig!.TryGetValue(subId, out var subAffix);
var aff = new ItemSubAffix(subAffix!, subLevel);
relic.SubAffixes[i] = aff;
}
if (relic.EquipAvatar > 0)
{
var avatar = Player.AvatarManager!.GetAvatar(relic.EquipAvatar);
await Player.SendPacket(new PacketPlayerSyncScNotify(avatar!, relic));
}
else
{
await Player.SendPacket(new PacketPlayerSyncScNotify(relic));
}
subAffixes[^1] = (subAffixes[^1].Item1, remainCount);
await HandleRelic(relic.ItemId, uniqueId, relic.Level, relic.MainAffix, subAffixes);
await RemoveItem(238, 1);
}
public async ValueTask<List<ItemData>> SellItem(ItemCostData costData)
public async ValueTask<List<ItemData>> SellItem(ItemCostData costData, bool toMaterial = false)
{
List<ItemData> items = [];
Dictionary<int, int> itemMap = [];
@@ -683,10 +692,46 @@ public class InventoryManager(PlayerInstance player) : BasePlayerManager(player)
removeItems.Add((itemData.ItemId, 1, (int)cost.RelicUniqueId));
GameData.ItemConfigData.TryGetValue(itemData.ItemId, out var itemConfig);
if (itemConfig == null) continue;
foreach (var returnItem in itemConfig.ReturnItemIDList) // return items
if (itemConfig.Rarity != ItemRarityEnum.SuperRare || toMaterial)
{
if (!itemMap.ContainsKey(returnItem.ItemID)) itemMap[returnItem.ItemID] = 0;
itemMap[returnItem.ItemID] += returnItem.ItemNum;
foreach (var returnItem in itemConfig.ReturnItemIDList) // basic return items
{
itemMap.TryAdd(returnItem.ItemID, 0);
itemMap[returnItem.ItemID] += returnItem.ItemNum;
}
var expReturned = (int)(itemData.CalcTotalExpGained() * 0.8);
var credit = (int)(expReturned * 1.5);
if (credit > 0)
{
itemMap.TryAdd(2, 0);
itemMap[2] += (int)(expReturned * 1.5);
}
var lostGoldFragCnt = expReturned / 500;
if (lostGoldFragCnt > 0)
{
itemMap.TryAdd(232, 0);
itemMap[232] += lostGoldFragCnt;
}
var lostGoldLightdust = expReturned % 500 / 100;
if (lostGoldLightdust > 0)
{
itemMap.TryAdd(231, 0);
itemMap[231] += lostGoldLightdust;
}
}
else
{
var expGained = itemData.CalcTotalExpGained();
var remainsCnt = (int)(10 + expGained * 0.005144);
if (remainsCnt > 0)
{
itemMap.TryAdd(235, 0);
itemMap[235] += remainsCnt;
}
}
}
else

View File

@@ -1,6 +1,7 @@
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Item;
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Proto;
using EggLink.DanhengServer.Util;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Item;
@@ -11,10 +12,15 @@ public class HandlerComposeSelectedRelicCsReq : Handler
{
var req = ComposeSelectedRelicCsReq.Parser.ParseFrom(data);
var player = connection.Player!;
var item = await player.InventoryManager!.ComposeRelic(req);
if (player.InventoryManager!.Data.RelicItems.Count >= GameConstants.INVENTORY_MAX_RELIC)
{
await connection.SendPacket(new PacketComposeSelectedRelicScRsp(req.ComposeId, Retcode.RetRelicExceedLimit));
return;
}
var item = await player.InventoryManager.ComposeRelic(req);
if (item == null)
{
await connection.SendPacket(new PacketComposeSelectedRelicScRsp());
await connection.SendPacket(new PacketComposeSelectedRelicScRsp(req.ComposeId));
return;
}

View File

@@ -10,7 +10,7 @@ public class HandlerSellItemCsReq : Handler
public override async Task OnHandle(Connection connection, byte[] header, byte[] data)
{
var req = SellItemCsReq.Parser.ParseFrom(data);
var items = await connection.Player!.InventoryManager!.SellItem(req.CostData);
var items = await connection.Player!.InventoryManager!.SellItem(req.CostData, req.ToMaterial);
await connection.SendPacket(new PacketSellItemScRsp(items));
}
}

View File

@@ -0,0 +1,29 @@
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Player;
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.Player;
[Opcode(CmdIds.ReserveStaminaExchangeCsReq)]
public class HandlerReserveStaminaExchangeCsReq : Handler
{
public async override Task OnHandle(Connection connection, byte[] header, byte[] data)
{
var req = ReserveStaminaExchangeCsReq.Parser.ParseFrom(data);
var player = connection.Player;
if (player == null) return;
var amount = req.Num;
if (amount <= 0 || player.Data.StaminaReserve < amount)
{
await connection.SendPacket(new PacketReserveStaminaExchangeScRsp(0));
}
else
{
player.Data.StaminaReserve -= amount;
player.Data.Stamina += (int)amount;
await connection.SendPacket(new PacketStaminaInfoScNotify(player));
await connection.SendPacket(new PacketReserveStaminaExchangeScRsp(amount));
}
}
}

View File

@@ -6,16 +6,28 @@ namespace EggLink.DanhengServer.GameServer.Server.Packet.Send.Item;
public class PacketComposeSelectedRelicScRsp : BasePacket
{
public PacketComposeSelectedRelicScRsp() : base(CmdIds.ComposeSelectedRelicScRsp)
public PacketComposeSelectedRelicScRsp(uint composeId) : base(CmdIds.ComposeSelectedRelicScRsp)
{
var proto = new ComposeSelectedRelicScRsp
{
ComposeId = composeId,
Retcode = 1
};
SetData(proto);
}
public PacketComposeSelectedRelicScRsp(uint composeId, Retcode retcode) : base(CmdIds.ComposeSelectedRelicScRsp)
{
var proto = new ComposeSelectedRelicScRsp
{
ComposeId = composeId,
Retcode = (uint)retcode
};
SetData(proto);
}
public PacketComposeSelectedRelicScRsp(uint composeId, ItemData item)
: base(CmdIds.ComposeSelectedRelicScRsp)
{

View File

@@ -0,0 +1,17 @@
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Send.Player;
public class PacketReserveStaminaExchangeScRsp : BasePacket
{
public PacketReserveStaminaExchangeScRsp(uint amount) : base(CmdIds.ReserveStaminaExchangeScRsp)
{
var proto = new ReserveStaminaExchangeScRsp();
if (amount > 0) proto.Num = amount;
else proto.Retcode = (uint)Retcode.RetFail;
SetData(proto);
}
}