Files
DanhengServer-OpenSource/GameServer/Game/GridFight/Component/GridFightItemsComponent.cs
2025-12-07 12:27:21 +08:00

679 lines
26 KiB
C#

using EggLink.DanhengServer.Data;
using EggLink.DanhengServer.Data.Excel;
using EggLink.DanhengServer.Enums.GridFight;
using EggLink.DanhengServer.GameServer.Game.GridFight.PendingAction;
using EggLink.DanhengServer.GameServer.Game.GridFight.Sync;
using EggLink.DanhengServer.GameServer.Server.Packet.Send.GridFight;
using EggLink.DanhengServer.Proto;
using EggLink.DanhengServer.Proto.ServerSide;
using EggLink.DanhengServer.Util;
namespace EggLink.DanhengServer.GameServer.Game.GridFight.Component;
public class GridFightItemsComponent(GridFightInstance inst) : BaseGridFightComponent(inst)
{
public GridFightItemsInfoPb Data { get; set; } = new();
#region Add & Remove & Craft
public async ValueTask<(GridFightEquipmentItemPb?, List<BaseGridFightSyncData>)> AddEquipment(uint equipmentId, GridFightSrc src = GridFightSrc.KGridFightSrcNone, bool sendPacket = true, uint groupId = 0, params uint[] param)
{
if (!GameData.GridFightEquipmentData.ContainsKey(equipmentId))
return (null, []);
var roleComp = Inst.GetComponent<GridFightRoleComponent>();
var info = new GridFightEquipmentItemPb
{
ItemId = equipmentId,
UniqueId = ++roleComp.Data.CurUniqueId
};
Data.EquipmentItems.Add(info);
var syncData = new GridFightAddGameItemSyncData(src, [info], [], groupId, param);
if (sendPacket)
await Inst.Player.SendPacket(new PacketGridFightSyncUpdateResultScNotify(syncData));
return (info, [syncData]);
}
public async ValueTask<List<BaseGridFightSyncData>> UpdateConsumable(uint consumableId, int count, GridFightSrc src = GridFightSrc.KGridFightSrcNone, bool sendPacket = true, uint groupId = 0, params uint[] param)
{
if (!GameData.GridFightConsumablesData.ContainsKey(consumableId) || count == 0)
return [];
var existingItem = Data.ConsumableItems.FirstOrDefault(x => x.ItemId == consumableId);
var isRemove = false;
var isUpdate = false;
if (existingItem != null)
{
isUpdate = true;
if (count < 0)
{
count = (int)-Math.Min(existingItem.Count, -count);
existingItem.Count -= (uint)-count;
if (existingItem.Count == 0)
{
Data.ConsumableItems.Remove(existingItem);
isRemove = true;
}
}
else
{
existingItem.Count += (uint)count;
}
}
else
{
if (count < 0) return [];
var info = new GridFightConsumableItemPb
{
ItemId = consumableId,
Count = (uint)count // safe
};
Data.ConsumableItems.Add(info);
existingItem = info;
}
BaseGridFightSyncData syncData = isRemove
? new GridFightRemoveGameItemSyncData(src, [], [existingItem.ToUpdateInfo(count)], groupId, param)
: isUpdate
? new GridFightUpdateGameItemSyncData(src, [], [existingItem.ToUpdateInfo(count)], groupId, param)
: new GridFightAddGameItemSyncData(src, [], [existingItem.ToUpdateInfo(count)], groupId, param);
if (sendPacket)
await Inst.Player.SendPacket(new PacketGridFightSyncUpdateResultScNotify(syncData));
return [syncData];
}
public async ValueTask<List<BaseGridFightSyncData>> RemoveEquipment(uint uniqueId, GridFightSrc src = GridFightSrc.KGridFightSrcNone, bool sendPacket = true, params uint[] param)
{
var existingItem = Data.EquipmentItems.FirstOrDefault(x => x.UniqueId == uniqueId);
if (existingItem == null)
return [];
Data.EquipmentItems.Remove(existingItem);
List<BaseGridFightSyncData> syncDatas =
[
new GridFightRemoveGameItemSyncData(src, [existingItem], [], 0, param)
];
// if equipped
var roleComp = Inst.GetComponent<GridFightRoleComponent>();
foreach (var role in roleComp.Data.Roles)
{
if (!role.EquipmentIds.Contains(existingItem.ItemId)) continue;
role.EquipmentIds.Remove(existingItem.ItemId);
syncDatas.Add(new GridFightRoleUpdateSyncData(src, role.Clone(), 0, param));
}
if (sendPacket)
await Inst.Player.SendPacket(new PacketGridFightSyncUpdateResultScNotify(syncDatas));
return syncDatas;
}
public async ValueTask<List<BaseGridFightSyncData>> CraftEquipment(uint targetEquipId, List<uint> materials)
{
List<BaseGridFightSyncData> syncDatas = [];
// remove materials
foreach (var matId in materials)
{
syncDatas.AddRange(await RemoveEquipment(matId, GridFightSrc.KGridFightSrcCraftEquip, false));
}
// add crafted equipment
var addEquipDatas = await AddEquipment(targetEquipId, GridFightSrc.KGridFightSrcCraftEquip, false);
syncDatas.AddRange(addEquipDatas.Item2);
// if auto equip to a role
var roleComp = Inst.GetComponent<GridFightRoleComponent>();
var roleUnique =
(syncDatas.FirstOrDefault(x => x is GridFightRoleUpdateSyncData) as GridFightRoleUpdateSyncData)?.Role
.UniqueId ?? 0;
if (roleUnique != 0 && addEquipDatas.Item1 != null)
{
syncDatas.AddRange(await roleComp.DressRole(roleUnique, addEquipDatas.Item1.UniqueId, GridFightSrc.KGridFightSrcCraftEquip, false));
}
// sync
if (syncDatas.Count > 0)
await Inst.Player.SendPacket(new PacketGridFightSyncUpdateResultScNotify(syncDatas));
return syncDatas;
}
#endregion
#region Rewards
public async ValueTask<List<BaseGridFightSyncData>> TakeDrop(List<GridFightDropItemInfo> drops,
bool sendPacket = false, GridFightSrc src = GridFightSrc.KGridFightSrcNone, uint groupId = 0,
params uint[] param)
{
var syncs = new List<BaseGridFightSyncData>();
var basicComp = Inst.GetComponent<GridFightBasicComponent>();
var shopComp = Inst.GetComponent<GridFightShopComponent>();
var roleComp = Inst.GetComponent<GridFightRoleComponent>();
var orbComp = Inst.GetComponent<GridFightOrbComponent>();
foreach (var item in drops)
{
// take drop
switch (item.DropType)
{
case GridFightDropType.Coin:
{
await basicComp.UpdateGoldNum((int)item.Num, false);
syncs.Add(new GridFightGoldSyncData(src, basicComp.Data, groupId, param));
break;
}
case GridFightDropType.Exp:
{
await basicComp.AddLevelExp(item.Num, false);
syncs.Add(new GridFightPlayerLevelSyncData(src, basicComp.Data, groupId, param));
break;
}
case GridFightDropType.Refresh:
{
shopComp.Data.FreeRefreshCount += item.Num;
syncs.Add(new GridFightShopSyncData(src, shopComp.Data, basicComp.Data.CurLevel, groupId, param));
break;
}
case GridFightDropType.Role:
{
syncs.AddRange(await roleComp.AddAvatar(item.DropItemId, item.DisplayValue.Tier, false, true,
src, groupId, 0, param));
break;
}
case GridFightDropType.Item:
{
// consumable or equipment or forge
if (GameData.GridFightConsumablesData.ContainsKey(item.DropItemId))
{
syncs.AddRange(await UpdateConsumable(item.DropItemId, (int)item.Num, src, false, groupId,
param));
}
else if (GameData.GridFightEquipmentData.ContainsKey(item.DropItemId))
{
for (uint i = 0; i < item.Num; i++)
{
syncs.AddRange((await AddEquipment(item.DropItemId, src, false, groupId, param)).Item2);
}
}
else if (GameData.GridFightForgeData.ContainsKey(item.DropItemId))
{
for (uint i = 0; i < item.Num; i++)
{
syncs.AddRange(await roleComp.AddForgeItem(item.DropItemId, false, src, groupId, 0, param));
}
}
break;
}
case GridFightDropType.Orb:
{
// add orbs
syncs.AddRange(await orbComp.AddOrb(item.DropItemId, src, false, groupId, param));
break;
}
}
}
if (sendPacket && syncs.Count > 0)
{
await Inst.Player.SendPacket(new PacketGridFightSyncUpdateResultScNotify(syncs));
}
return syncs;
}
public async ValueTask<(List<BaseGridFightSyncData>, List<GridFightDropItemInfo>)> TakeBasicBonusItems(
List<GridFightBasicBonusPoolV2Excel> bonuses, GridFightSrc src = GridFightSrc.KGridFightSrcNone, uint groupId = 0, bool sendPacket = true)
{
var basicComp = Inst.GetComponent<GridFightBasicComponent>();
var shopComp = Inst.GetComponent<GridFightShopComponent>();
var roleComp = Inst.GetComponent<GridFightRoleComponent>();
var orbComp = Inst.GetComponent<GridFightOrbComponent>();
List<BaseGridFightSyncData> syncs = [];
List<GridFightDropItemInfo> drops = [];
foreach (var bonus in bonuses)
{
// get drop
switch (bonus.BonusType)
{
case GridFightBonusTypeEnum.Gold:
{
// add gold
await basicComp.UpdateGoldNum((int)bonus.Value, false);
syncs.Add(new GridFightGoldSyncData(src, basicComp.Data, groupId));
drops.Add(new GridFightDropItemInfo
{
DropType = GridFightDropType.Coin,
Num = bonus.Value
});
break;
}
case GridFightBonusTypeEnum.Refresh:
{
// add refresh count
shopComp.Data.FreeRefreshCount += bonus.Value;
syncs.Add(new GridFightShopSyncData(src, shopComp.Data.Clone(), basicComp.Data.CurLevel, groupId));
drops.Add(new GridFightDropItemInfo
{
DropType = GridFightDropType.Refresh,
Num = bonus.Value
});
break;
}
case GridFightBonusTypeEnum.SpecificAvatar:
{
// add role
var roleId = bonus.BonusTypeParamList[0];
var tier = bonus.BonusTypeParamList[1];
syncs.AddRange(await roleComp.AddAvatar(roleId, tier, false, true, src, groupId));
drops.Add(new GridFightDropItemInfo
{
DisplayValue = new GridDropItemDisplayInfo
{
Tier = tier
},
DropItemId = roleId,
DropType = GridFightDropType.Role,
Num = 1
});
break;
}
case GridFightBonusTypeEnum.RandomAvatar:
{
// add random role
var rarity = bonus.BonusTypeParamList[0];
var tier = bonus.BonusTypeParamList[1];
var role = GameData.GridFightRoleBasicInfoData.Values.Where(x => x.Rarity == rarity && x.IsInPool)
.ToList()
.RandomElement();
syncs.AddRange(await roleComp.AddAvatar(role.ID, tier, false, true, src, groupId));
drops.Add(new GridFightDropItemInfo
{
DisplayValue = new GridDropItemDisplayInfo
{
Tier = tier
},
DropItemId = role.ID,
DropType = GridFightDropType.Role,
Num = 1
});
break;
}
case GridFightBonusTypeEnum.Item:
{
// consumable or equipment
var itemId = bonus.BonusTypeParamList[0];
drops.Add(new GridFightDropItemInfo
{
DropItemId = itemId,
Num = 1,
DropType = GridFightDropType.Item
});
// check if consumable or equipment or forge
if (GameData.GridFightEquipmentData.ContainsKey(itemId))
{
syncs.AddRange((await AddEquipment(itemId, src, false, groupId)).Item2);
}
else if (GameData.GridFightConsumablesData.ContainsKey(itemId))
{
syncs.AddRange(await UpdateConsumable(itemId, 1, src, false, groupId));
}
else if (GameData.GridFightForgeData.ContainsKey(itemId))
{
syncs.AddRange(await roleComp.AddForgeItem(itemId, false, src, groupId));
}
break;
}
case GridFightBonusTypeEnum.Exp:
{
// add exp
await basicComp.AddLevelExp(bonus.Value, false);
syncs.Add(new GridFightPlayerLevelSyncData(src, basicComp.Data, groupId));
drops.Add(new GridFightDropItemInfo
{
DropType = GridFightDropType.Exp,
Num = bonus.Value
});
break;
}
case GridFightBonusTypeEnum.Orb:
{
// add orbs
var orbId = bonus.BonusTypeParamList[0];
syncs.AddRange(await orbComp.AddOrb(orbId, src, false, groupId));
drops.Add(new GridFightDropItemInfo
{
DropItemId = orbId,
DropType = GridFightDropType.Orb,
Num = bonus.Value
});
break;
}
case GridFightBonusTypeEnum.RandomEquipByCategory:
{
var category = bonus.BonusTypeParamList[0];
var equip = GameData.GridFightEquipmentData.Values.Where(x => (int)x.EquipCategory == category)
.ToList().RandomElement();
syncs.AddRange((await AddEquipment(equip.ID, src, false, groupId)).Item2);
drops.Add(new GridFightDropItemInfo
{
DropItemId = equip.ID,
DropType = GridFightDropType.Item,
Num = 1
});
break;
}
case GridFightBonusTypeEnum.RandomEquipByFunc:
{
var func = bonus.BonusTypeParamList[0];
var equip = GameData.GridFightEquipmentData.Values.Where(x => (int)x.EquipFunc == func)
.ToList().RandomElement();
syncs.AddRange((await AddEquipment(equip.ID, src, false, groupId)).Item2);
drops.Add(new GridFightDropItemInfo
{
DropItemId = equip.ID,
DropType = GridFightDropType.Item,
Num = 1
});
break;
}
case GridFightBonusTypeEnum.RandomSameAvatar:
{
// TODO (not used in official server)
break;
}
}
}
if (syncs.Count > 0 && sendPacket)
{
await Inst.Player.SendPacket(new PacketGridFightSyncUpdateResultScNotify(syncs));
}
return (syncs, drops);
}
#endregion
#region Consumables
public async ValueTask<Retcode> UseConsumable(uint itemId, GridFightConsumableTargetInfo target)
{
if (!GameData.GridFightConsumablesData.TryGetValue(itemId, out var consumablesExcel)) return Retcode.RetGridFightConfMiss;
// if owned
var item = Data.ConsumableItems.FirstOrDefault(x => x.ItemId == itemId);
if (item == null || item.Count == 0) return Retcode.RetGridFightItemNotEnough;
List<BaseGridFightSyncData> syncs = [];
if (consumablesExcel.IfConsume)
{
syncs.AddRange(await UpdateConsumable(itemId, -1, GridFightSrc.KGridFightSrcUseConsumable, false, 0, item.ItemId));
}
(Retcode, List<BaseGridFightSyncData>) res = consumablesExcel.ConsumableRule switch
{
GridFightConsumeTypeEnum.Remove => HandleRemoveConsumable(target),
GridFightConsumeTypeEnum.Roll => await HandleRollConsumable(target),
GridFightConsumeTypeEnum.Upgrade => await HandleUpgradeConsumable(target),
GridFightConsumeTypeEnum.Copy => await HandleCopyConsumable(target, consumablesExcel.ConsumableParamList),
GridFightConsumeTypeEnum.GainRecommendEquip => await HandleGainRecommendEquipConsumable(target, consumablesExcel.ConsumableParamList),
_ => (Retcode.RetGridFightConfMiss, [])
};
syncs.AddRange(res.Item2);
if (syncs.Count > 0)
{
await Inst.Player.SendPacket(new PacketGridFightSyncUpdateResultScNotify(syncs));
}
return res.Item1;
}
private (Retcode, List<BaseGridFightSyncData>) HandleRemoveConsumable(GridFightConsumableTargetInfo target)
{
List<BaseGridFightSyncData> syncs = [];
if (target.RemoveTypeTargetInfo == null) return (Retcode.RetReqParaInvalid, syncs);
var roleComp = Inst.GetComponent<GridFightRoleComponent>();
var role = roleComp.Data.Roles.FirstOrDefault(x => x.UniqueId == target.RemoveTypeTargetInfo.DressRoleUniqueId);
if (role == null) return (Retcode.RetGridFightRoleNotExist, syncs);
// unequip
role.EquipmentIds.Clear();
// sync
syncs.Add(new GridFightRoleUpdateSyncData(GridFightSrc.KGridFightSrcUseConsumable, role.Clone()));
return (Retcode.RetSucc, syncs);
}
private async ValueTask<(Retcode, List<BaseGridFightSyncData>)> HandleRollConsumable(GridFightConsumableTargetInfo target)
{
List<BaseGridFightSyncData> syncs = [];
if (target.RollTypeTargetInfo == null) return (Retcode.RetReqParaInvalid, syncs);
var roleComp = Inst.GetComponent<GridFightRoleComponent>();
var equipment = Data.EquipmentItems.FirstOrDefault(x => x.UniqueId == target.RollTypeTargetInfo.DressEquipmentUniqueId);
var role = roleComp.Data.Roles.FirstOrDefault(x => x.UniqueId == target.RollTypeTargetInfo.DressRoleUniqueId);
if (role == null && equipment == null) return (Retcode.RetGridFightRoleNotExist, syncs);
if (role != null)
{
// unequip old equipment
foreach (var equipmentUid in role.EquipmentIds)
{
syncs.AddRange(await RollEquipment(equipmentUid));
}
role.EquipmentIds.Clear();
// sync
syncs.Add(new GridFightRoleUpdateSyncData(GridFightSrc.KGridFightSrcUseConsumable, role.Clone()));
}
if (equipment != null)
{
syncs.AddRange(await RollEquipment(equipment.UniqueId));
}
return (Retcode.RetSucc, syncs);
}
private async ValueTask<List<BaseGridFightSyncData>> RollEquipment(uint uniqueId)
{
List<BaseGridFightSyncData> syncs = [];
var equipment = Data.EquipmentItems.FirstOrDefault(x => x.UniqueId == uniqueId);
if (equipment == null) return syncs;
// remove old equipment
syncs.AddRange(await RemoveEquipment(equipment.UniqueId, GridFightSrc.KGridFightSrcUseConsumable, false));
// add new equipment
var equipConf = GameData.GridFightEquipmentData[equipment.ItemId];
var newEquip = GameData.GridFightEquipmentData.Values
.Where(x => x.EquipCategory == equipConf.EquipCategory && x.ID != equipConf.ID).ToList()
.RandomElement();
syncs.AddRange((await AddEquipment(newEquip.ID, GridFightSrc.KGridFightSrcUseConsumable, false))
.Item2);
return syncs;
}
private async ValueTask<(Retcode, List<BaseGridFightSyncData>)> HandleUpgradeConsumable(GridFightConsumableTargetInfo target)
{
List<BaseGridFightSyncData> syncs = [];
if (target.UpgradeTypeTargetInfo == null) return (Retcode.RetReqParaInvalid, syncs);
var targetEquip = Data.EquipmentItems.FirstOrDefault(x => x.UniqueId == target.UpgradeTypeTargetInfo.DressEquipmentUniqueId);
if (targetEquip == null) return (Retcode.RetGridFightEquipNotExist, syncs);
var upgradeId = GameData.GridFightEquipUpgradeData.GetValueOrDefault(targetEquip.ItemId)?.UpgradeID;
if (upgradeId == null) return (Retcode.RetGridFightConfMiss, syncs);
// remove old equipment
syncs.AddRange(await RemoveEquipment(targetEquip.UniqueId, GridFightSrc.KGridFightSrcUseConsumable, false));
// add new equipment
syncs.AddRange((await AddEquipment(upgradeId.Value, GridFightSrc.KGridFightSrcUseConsumable, false))
.Item2);
return (Retcode.RetSucc, syncs);
}
private async ValueTask<(Retcode, List<BaseGridFightSyncData>)> HandleCopyConsumable(GridFightConsumableTargetInfo target, List<uint> param)
{
List<BaseGridFightSyncData> syncs = [];
if (target.CopyTypeTargetInfo == null) return (Retcode.RetReqParaInvalid, syncs);
var maxRarity = param[0];
var count = param[1];
var roleComp = Inst.GetComponent<GridFightRoleComponent>();
var targetRole = roleComp.Data.Roles.FirstOrDefault(x => x.UniqueId == target.CopyTypeTargetInfo.DressRoleUniqueId);
if (targetRole == null) return (Retcode.RetGridFightRoleNotExist, syncs);
// check if can copy
if (!GameData.GridFightRoleBasicInfoData.TryGetValue(targetRole.RoleId, out var roleConf))
return (Retcode.RetGridFightConfMiss, syncs);
if (roleConf.Rarity > maxRarity) return (Retcode.RetGridFightHighRariyForCopy, syncs);
// check if enough space
var canAddCount = roleComp.GetEmptyPosCount();
if (canAddCount < count) return (Retcode.RetGridFightNoPosCanPlace, syncs);
// copy role
for (uint i = 0; i < count; i++)
{
syncs.AddRange(await roleComp.AddAvatar(targetRole.RoleId, 1, false, true,
GridFightSrc.KGridFightSrcUseConsumable));
}
return (Retcode.RetSucc, syncs);
}
private async ValueTask<(Retcode, List<BaseGridFightSyncData>)> HandleGainRecommendEquipConsumable(
GridFightConsumableTargetInfo target, List<uint> param)
{
List<BaseGridFightSyncData> syncs = [];
if (target.GainRecommendEquipTypeTargetInfo == null) return (Retcode.RetReqParaInvalid, syncs);
var roleComp = Inst.GetComponent<GridFightRoleComponent>();
var targetRole = roleComp.Data.Roles.FirstOrDefault(x => x.UniqueId == target.GainRecommendEquipTypeTargetInfo.DressRoleUniqueId);
if (targetRole == null) return (Retcode.RetGridFightRoleNotExist, syncs);
// get recommend equipment
if (!GameData.GridFightRoleRecommendEquipData.TryGetValue(targetRole.RoleId, out var recommendConf))
return (Retcode.RetGridFightConfMiss, syncs);
var recommendList = recommendConf.FirstRecommendEquipList.Concat(recommendConf.SecondRecommendEquipList).ToList();
var recommendCount = Math.Min(param[0], (uint)recommendList.Count);
var recommends = recommendList.OrderBy(_ => Guid.NewGuid()).Take((int)recommendCount).ToList();
syncs.AddRange(
await Inst.CreatePendingAction<GridFightRecommendEquipmentPendingAction>(
GridFightSrc.KGridFightSrcUseConsumable, false, recommends));
return (Retcode.RetSucc, syncs);
}
#endregion
#region Serialization
public override GridFightGameInfo ToProto()
{
return new GridFightGameInfo
{
GridItemsInfo = new GridFightGameItemsInfo
{
GridFightEquipmentList = { Data.EquipmentItems.Select(x => x.ToProto()) },
GridFightConsumableList = { Data.ConsumableItems.Select(x => x.ToProto()) }
}
};
}
#endregion
}
public static class GridFightItemsComponentExtensions
{
public static GridFightEquipmentInfo ToProto(this GridFightEquipmentItemPb equipment)
{
return new GridFightEquipmentInfo
{
GridFightEquipmentId = equipment.ItemId,
UniqueId = equipment.UniqueId
};
}
public static GridFightConsumableInfo ToProto(this GridFightConsumableItemPb consumable)
{
return new GridFightConsumableInfo
{
ItemId = consumable.ItemId,
Num = consumable.Count
};
}
public static GridFightConsumableUpdateInfo ToUpdateInfo(this GridFightConsumableItemPb consumable, int updateCount)
{
return new GridFightConsumableUpdateInfo
{
ItemId = consumable.ItemId,
Num = consumable.Count,
ItemStackCount = updateCount
};
}
public static BattleGridFightEquipmentInfo ToBattleInfo(this GridFightEquipmentItemPb equipment)
{
return new BattleGridFightEquipmentInfo
{
GridFightEquipmentId = equipment.ItemId,
UniqueId = equipment.UniqueId
};
}
}