feat: trait bonus & forge item

This commit is contained in:
Somebody
2025-12-07 12:27:21 +08:00
parent 4ee0bca766
commit 63cc1418ca
23 changed files with 1969 additions and 147 deletions

View File

@@ -0,0 +1,26 @@
using EggLink.DanhengServer.Enums.GridFight;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace EggLink.DanhengServer.Data.Excel;
[ResourceEntity("GridFightForge.json")]
public class GridFightForgeExcel : ExcelResource
{
public uint ID { get; set; }
public uint EquipNum { get; set; }
public List<uint> ParamList { get; set; } = [];
[JsonConverter(typeof(StringEnumConverter))]
public GridFightForgeFuncTypeEnum FuncType { get; set; }
public override int GetId()
{
return (int)ID;
}
public override void Loaded()
{
GameData.GridFightForgeData.TryAdd(ID, this);
}
}

View File

@@ -0,0 +1,25 @@
using EggLink.DanhengServer.Enums.GridFight;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace EggLink.DanhengServer.Data.Excel;
[ResourceEntity("GridFightTraitBonusAddRule.json")]
public class GridFightTraitBonusAddRuleExcel : ExcelResource
{
public uint ID { get; set; }
public List<uint> ParamList { get; set; } = [];
[JsonConverter(typeof(StringEnumConverter))]
public GridFightTraitBonusAddTypeEnum TraitBonusType { get; set; }
public override int GetId()
{
return (int)ID;
}
public override void Loaded()
{
GameData.GridFightTraitBonusAddRuleData.TryAdd(ID, this);
}
}

View File

@@ -0,0 +1,27 @@
using EggLink.DanhengServer.Enums.GridFight;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace EggLink.DanhengServer.Data.Excel;
[ResourceEntity("GridFightTraitBonus.json")]
public class GridFightTraitBonusExcel : ExcelResource
{
public uint ID { get; set; }
public uint BonusThreshold { get; set; }
public List<uint> BonusParamList { get; set; } = [];
[JsonConverter(typeof(StringEnumConverter))]
public GridFightTraitBonusTypeEnum BonusType { get; set; }
public override int GetId()
{
return (int)ID;
}
public override void Loaded()
{
GameData.GridFightTraitBonusData.TryAdd(ID, []);
GameData.GridFightTraitBonusData[ID].TryAdd(BonusThreshold, this);
}
}

View File

@@ -130,7 +130,13 @@ public static class GameData
public static Dictionary<uint, GridFightDivisionInfoExcel> GridFightDivisionInfoData { get; private set; } = [];
public static Dictionary<uint, GridFightDivisionStageExcel> GridFightDivisionStageData { get; private set; } = [];
public static Dictionary<uint, GridFightEquipmentExcel> GridFightEquipmentData { get; private set; } = [];
public static Dictionary<uint, GridFightForgeExcel> GridFightForgeData { get; private set; } = [];
public static Dictionary<uint, GridFightTraitEffectExcel> GridFightTraitEffectData { get; private set; } = [];
public static Dictionary<uint, GridFightTraitBonusAddRuleExcel>
GridFightTraitBonusAddRuleData { get; private set; } = [];
public static Dictionary<uint, Dictionary<uint, GridFightTraitBonusExcel>> GridFightTraitBonusData{ get; private set; } = [];
public static Dictionary<uint, GridFightEquipUpgradeExcel> GridFightEquipUpgradeData { get; private set; } = [];
public static Dictionary<uint, GridFightConsumablesExcel> GridFightConsumablesData { get; private set; } = [];
public static Dictionary<uint, GridFightCampExcel> GridFightCampData { get; private set; } = [];

View File

@@ -0,0 +1,8 @@
namespace EggLink.DanhengServer.Enums.GridFight;
public enum GridFightForgeFuncTypeEnum
{
None = 0,
Equip = 1,
Role = 2
}

View File

@@ -0,0 +1,8 @@
namespace EggLink.DanhengServer.Enums.GridFight;
public enum GridFightTraitBonusAddTypeEnum
{
ByGameplay = 0,
ByEquipNum = 1,
ByConstWithPerfectPass = 2
}

View File

@@ -0,0 +1,10 @@
namespace EggLink.DanhengServer.Enums.GridFight;
public enum GridFightTraitBonusTypeEnum
{
None = 0,
Bonus = 1,
Enhance = 2,
TraitMemberPropertyRatio = 3,
AllMemberPropertyRatio = 4
}

View File

@@ -33,9 +33,15 @@ public class BattleGridFightOptions(GridFightGameSectionInfo curSection, GridFig
BaseAvatars = avatars.Concat(backAvatars).Select(y => new LineupAvatarInfo
{
BaseAvatarId = y.BaseAvatarId
}).ToList()
}).ToList(),
LineupType = (int)ExtraLineupType.LineupGridFight
};
foreach (var baseAvatarInfo in avatars.Concat(backAvatars))
{
baseAvatarInfo.SetCurHp(10000, true);
}
var formatted = avatars.Select(x =>
x.ToBattleProto(
new PlayerDataCollection(Player.Data, Player.InventoryManager!.Data, tempLineup),
@@ -118,7 +124,8 @@ public class BattleGridFightOptions(GridFightGameSectionInfo curSection, GridFig
PenaltyBonusRuleId = ruleId,
GridFightAugmentInfo = { AugmentComponent.Data.Augments.Select(x => x.ToBattleInfo()) },
GridFightPortalBuffList = { LevelComponent.PortalBuffs.Select(x => x.ToBattleInfo()) },
GridFightTraitInfo = { TraitComponent.Data.Traits.Select(x => x.ToBattleInfo(RoleComponent)) }
GridFightTraitInfo = { TraitComponent.Data.Traits.Select(x => x.ToBattleInfo(RoleComponent)) },
GridGameNpcList = { RoleComponent.Data.Npcs.Select(x => x.ToBattleInfo(ItemsComponent.Data)) }
};
}
}

View File

@@ -19,7 +19,8 @@ public class GridFightBasicComponent(GridFightInstance inst) : BaseGridFightComp
MaxAvatarNum = 3,
BuyLevelCost = 4,
CurGold = 0,
MaxInterest = 5
MaxInterest = 5,
MaxLevel = 10
};
#endregion
@@ -182,7 +183,7 @@ public class GridFightBasicComponent(GridFightInstance inst) : BaseGridFightComp
GridFightTargetGuideCode = Data.GuideCode,
TrackTraitIdList = { Data.TrackingTraits },
RoleTrackEquipmentList = { Data.TrackingEquipments.Select(x => x.ToProto(roleComp, itemsComp)) },
GridFightMaxLevel = 10
GridFightMaxLevel = Data.MaxLevel
}
};
}

View File

@@ -198,10 +198,11 @@ public class GridFightItemsComponent(GridFightInstance inst) : BaseGridFightComp
}
case GridFightDropType.Item:
{
// consumable or equipment
// consumable or equipment or forge
if (GameData.GridFightConsumablesData.ContainsKey(item.DropItemId))
{
syncs.AddRange(await UpdateConsumable(item.DropItemId, (int)item.Num, src, false, 0, param));
syncs.AddRange(await UpdateConsumable(item.DropItemId, (int)item.Num, src, false, groupId,
param));
}
else if (GameData.GridFightEquipmentData.ContainsKey(item.DropItemId))
{
@@ -210,6 +211,13 @@ public class GridFightItemsComponent(GridFightInstance inst) : BaseGridFightComp
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;
}
@@ -328,7 +336,7 @@ public class GridFightItemsComponent(GridFightInstance inst) : BaseGridFightComp
DropType = GridFightDropType.Item
});
// check if consumable or equipment
// check if consumable or equipment or forge
if (GameData.GridFightEquipmentData.ContainsKey(itemId))
{
syncs.AddRange((await AddEquipment(itemId, src, false, groupId)).Item2);
@@ -338,6 +346,10 @@ public class GridFightItemsComponent(GridFightInstance inst) : BaseGridFightComp
{
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;
}
@@ -490,7 +502,7 @@ public class GridFightItemsComponent(GridFightInstance inst) : BaseGridFightComp
// unequip old equipment
foreach (var equipmentUid in role.EquipmentIds)
{
syncs.AddRange(await RemoveEquipment(equipmentUid, GridFightSrc.KGridFightSrcUseConsumable, false));
syncs.AddRange(await RollEquipment(equipmentUid));
}
role.EquipmentIds.Clear();

View File

@@ -612,10 +612,10 @@ public class GridFightGameMonsterWaveInfo
{
DropType = GridFightDropType.Orb,
Num = 1,
DropItemId = Random.Shared.Next(10) switch
DropItemId = Random.Shared.Next(30) switch
{
> 5 and < 9 => OrbList[1].RandomElement(),
9 => OrbList[2].RandomElement(),
> 23 and < 29 => OrbList[1].RandomElement(),
29 => OrbList[2].RandomElement(),
_ => OrbList[0].RandomElement()
}
});

View File

@@ -6,11 +6,39 @@ using EggLink.DanhengServer.GameServer.Server.Packet.Send.GridFight;
using EggLink.DanhengServer.Proto;
using EggLink.DanhengServer.Proto.ServerSide;
using EggLink.DanhengServer.Util;
using System.Collections.Generic;
namespace EggLink.DanhengServer.GameServer.Game.GridFight.Component;
public class GridFightOrbComponent(GridFightInstance inst) : BaseGridFightComponent(inst)
{
public static List<GridFightBasicBonusPoolV2Excel> ExtractCombinationBonus(uint combineBonusId)
{
List<GridFightBasicBonusPoolV2Excel> bonusPools = [];
if (!GameData.GridFightCombinationBonusData.TryGetValue(combineBonusId, out var comboBonusInfo))
return bonusPools;
for (var i = 0; i < comboBonusInfo.CombinationBonusList.Count; i++)
{
var bonusId = comboBonusInfo.CombinationBonusList[i];
var bonusNum = comboBonusInfo.BonusNumberList[i];
if (bonusId == 1)
bonusPools.Add(new GridFightBasicBonusPoolV2Excel
{
BonusType = GridFightBonusTypeEnum.Gold,
Value = bonusNum / 10000
});
else if (GameData.GridFightBasicBonusPoolV2Data.TryGetValue(bonusId, out var bonusPool))
{
bonusPools.AddRange(Enumerable.Repeat(bonusPool, (int)(bonusNum / 10000)));
}
}
return bonusPools;
}
public GridFightOrbInfoPb Data { get; set; } = new();
public async ValueTask<List<BaseGridFightSyncData>> AddOrb(uint orbItemId, GridFightSrc src = GridFightSrc.KGridFightSrcNone, bool sendPacket = true, uint groupId = 0, params uint[] param)
@@ -73,24 +101,9 @@ public class GridFightOrbComponent(GridFightInstance inst) : BaseGridFightCompon
}
// check combination bonus
if (bonusPools.Count == 0 && GameData.GridFightCombinationBonusData.TryGetValue(excel.BonusID, out var comboBonusInfo))
if (bonusPools.Count == 0)
{
for (var i = 0; i < comboBonusInfo.CombinationBonusList.Count; i++)
{
var bonusId = comboBonusInfo.CombinationBonusList[i];
var bonusNum = comboBonusInfo.BonusNumberList[i];
if (bonusId == 1)
bonusPools.Add(new GridFightBasicBonusPoolV2Excel
{
BonusType = GridFightBonusTypeEnum.Gold,
Value = bonusNum / 10000
});
else if (GameData.GridFightBasicBonusPoolV2Data.TryGetValue(bonusId, out var bonusPool))
{
bonusPools.AddRange(Enumerable.Repeat(bonusPool, (int)(bonusNum / 10000)));
}
}
bonusPools.AddRange(ExtractCombinationBonus(excel.BonusID));
}
// check basic bonus

View File

@@ -1,26 +1,20 @@
using EggLink.DanhengServer.Data;
using EggLink.DanhengServer.Database.Avatar;
using EggLink.DanhengServer.Enums.GridFight;
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 GridFightRoleComponent(GridFightInstance inst) : BaseGridFightComponent(inst)
{
public const uint PrepareAreaPos = 13;
public GridFightAvatarInfoPb Data { get; set; } = new();
public GridFightTeamInfoPb Data { get; set; } = new();
public bool HasAnyEmptyPos()
{
return Data.Roles.Where(x => x.Pos > PrepareAreaPos).ToList().Count < 9;
}
public uint GetEmptyPosCount()
{
return (uint)(9 - Data.Roles.Where(x => x.Pos > PrepareAreaPos).ToList().Count);
}
#region Role
public async ValueTask<List<BaseGridFightSyncData>> AddAvatar(uint roleId, uint tier = 1, bool sendPacket = true,
bool checkMerge = true, GridFightSrc src = GridFightSrc.KGridFightSrcBuyGoods, uint syncGroup = 0, uint targetPos = 0, params uint[] param)
@@ -31,7 +25,9 @@ public class GridFightRoleComponent(GridFightInstance inst) : BaseGridFightCompo
var initialPos = targetPos > 0 ? targetPos : PrepareAreaPos + 1;
// get first empty pos
var usedPos = Data.Roles.Select(x => x.Pos).ToHashSet();
var usedPos = Data.Roles.Select(x => x.Pos).Concat(Data.Forges.Select(x => x.Pos))
.Concat(Data.Npcs.Select(x => x.Pos)).ToHashSet();
for (var i = initialPos; i <= PrepareAreaPos + 999; i++) // temp store area
{
if (usedPos.Contains(i)) continue;
@@ -251,6 +247,149 @@ public class GridFightRoleComponent(GridFightInstance inst) : BaseGridFightCompo
return res;
}
#endregion
#region Forge
public async ValueTask<List<BaseGridFightSyncData>> AddForgeItem(uint forgeItemId, bool sendPacket = true,
GridFightSrc src = GridFightSrc.KGridFightSrcNone, uint syncGroup = 0, uint targetPos = 0, params uint[] param)
{
if (!GameData.GridFightForgeData.TryGetValue(forgeItemId, out var forgeExcel)) return [];
var pos = 0u;
var initialPos = targetPos > 0 ? targetPos : PrepareAreaPos + 1;
// get first empty pos
var usedPos = Data.Roles.Select(x => x.Pos).Concat(Data.Forges.Select(x => x.Pos))
.Concat(Data.Npcs.Select(x => x.Pos)).ToHashSet();
for (var i = initialPos; i <= PrepareAreaPos + 999; i++) // temp store area
{
if (usedPos.Contains(i)) continue;
pos = i;
break;
}
// check if any empty
if (pos == 0)
{
return [];
}
var info = new GridFightForgeInfoPb
{
ForgeItemId = forgeItemId,
UniqueId = ++Data.CurUniqueId,
Pos = pos
};
// generate goods
if (forgeExcel.FuncType == GridFightForgeFuncTypeEnum.Role)
{
var roleRarity = forgeExcel.ParamList[0];
var tier = forgeExcel.ParamList[1];
var candidateRoles = GameData.GridFightRoleBasicInfoData.Values
.Where(x => x.Rarity == roleRarity)
.ToList(); // filter
if (candidateRoles.Count == 0) return []; // no candidate roles (should not happen)
for (var i = 0; i < forgeExcel.EquipNum; i++)
{
var role = candidateRoles.RandomElement();
info.Goods.Add(new GridFightForgeGoodsInfoPb
{
RoleInfo = new GridFightForgeRoleGoodsInfoPb
{
RoleId = role.ID,
Tier = tier
}
});
}
}
else if (forgeExcel.FuncType == GridFightForgeFuncTypeEnum.Equip)
{
var equipCategory = forgeExcel.ParamList[0];
var candidateEquips = GameData.GridFightEquipmentData.Values
.Where(x => (uint)x.EquipCategory == equipCategory)
.ToList(); // filter
if (candidateEquips.Count == 0) return []; // no candidate equips (should not happen)
for (var i = 0; i < forgeExcel.EquipNum; i++)
{
var equip = candidateEquips.RandomElement();
info.Goods.Add(new GridFightForgeGoodsInfoPb
{
ItemId = equip.ID
});
}
}
Data.Forges.Add(info);
List<BaseGridFightSyncData> syncs = [new GridFightAddForgeSyncData(src, info, syncGroup, param)];
if (sendPacket)
{
await Inst.Player.SendPacket(new PacketGridFightSyncUpdateResultScNotify(syncs));
}
return syncs;
}
public async ValueTask<List<BaseGridFightSyncData>> UseForgeItem(uint uniqueId, uint targetIndex)
{
var forge = Data.Forges.FirstOrDefault(x => x.UniqueId == uniqueId);
if (forge == null)
{
return [];
}
List<BaseGridFightSyncData> syncs = [];
var good = forge.Goods[(int)targetIndex];
if (good.HasItemId)
{
// equipment
var addEquipSyncs = await Inst.GetComponent<GridFightItemsComponent>()
.AddEquipment(good.ItemId, GridFightSrc.KGridFightSrcUseForge, false, uniqueId);
syncs.AddRange(addEquipSyncs.Item2);
}
else
{
// role
var addRoleSyncs = await AddAvatar(good.RoleInfo.RoleId, good.RoleInfo.Tier, true,
false, GridFightSrc.KGridFightSrcUseForge, uniqueId);
syncs.AddRange(addRoleSyncs);
}
// remove used forge item
Data.Forges.Remove(forge);
syncs.Add(new GridFightRemoveForgeSyncData(GridFightSrc.KGridFightSrcUseForge, forge, uniqueId, uniqueId,
forge.ForgeItemId));
await Inst.Player.SendPacket(new PacketGridFightSyncUpdateResultScNotify(syncs));
return syncs;
}
#endregion
#region Metadata
public bool HasAnyEmptyPos()
{
return Data.Roles.Where(x => x.Pos > PrepareAreaPos).ToList().Count < 9;
}
public uint GetEmptyPosCount()
{
return (uint)(9 - Data.Roles.Where(x => x.Pos > PrepareAreaPos).ToList().Count);
}
public async ValueTask<Retcode> UpdatePos(List<GridFightPosInfo> posList)
{
foreach (var pos in posList.Where(x => x.Pos <= PrepareAreaPos))
@@ -260,17 +399,32 @@ public class GridFightRoleComponent(GridFightInstance inst) : BaseGridFightCompo
if (Data.Roles.Where(x => x.UniqueId != pos.UniqueId && x.Pos <= PrepareAreaPos).Any(x => x.RoleId == role.RoleId))
return Retcode.RetGridFightSameRoleInBattle;
}
} // only check role
List<BaseGridFightSyncData> syncs = [];
foreach (var pos in posList)
{
var role = Data.Roles.FirstOrDefault(x => x.UniqueId == pos.UniqueId);
var forge = Data.Forges.FirstOrDefault(x => x.UniqueId == pos.UniqueId);
var npc = Data.Npcs.FirstOrDefault(x => x.UniqueId == pos.UniqueId);
if (role != null)
{
role.Pos = pos.Pos;
syncs.Add(new GridFightRoleUpdateSyncData(GridFightSrc.KGridFightSrcNone, role));
}
if (forge != null)
{
forge.Pos = pos.Pos;
syncs.Add(new GridFightForgeUpdateSyncData(GridFightSrc.KGridFightSrcNone, forge));
}
if (npc != null)
{
npc.Pos = pos.Pos;
syncs.Add(new GridFightNpcUpdateSyncData(GridFightSrc.KGridFightSrcNone, npc));
}
}
if (syncs.Count > 0)
@@ -283,13 +437,17 @@ public class GridFightRoleComponent(GridFightInstance inst) : BaseGridFightCompo
return Retcode.RetSucc;
}
#endregion
public override GridFightGameInfo ToProto()
{
return new GridFightGameInfo
{
GridTeamGameInfo = new GridFightGameTeamInfo
{
GridGameRoleList = { Data.Roles.Select(x => x.ToProto()) }
GridGameRoleList = { Data.Roles.Select(x => x.ToProto()) },
GridGameForgeItemList = { Data.Forges.Select(x => x.ToProto()) },
GridGameNpcList = { Data.Npcs.Select(x => x.ToProto()) }
}
};
}
@@ -310,6 +468,51 @@ public static class GridFightRoleInfoPbExtensions
};
}
public static GridGameNpcInfo ToProto(this GridFightNpcInfoPb info)
{
return new GridGameNpcInfo
{
Id = info.NpcId,
UniqueId = info.UniqueId,
Pos = info.Pos,
EquipUniqueIdList = { info.EquipmentIds }
};
}
public static GridGameForgeItemInfo ToProto(this GridFightForgeInfoPb info)
{
return new GridGameForgeItemInfo
{
ForgeItemId = info.ForgeItemId,
UniqueId = info.UniqueId,
Pos = info.Pos,
ForgeGoodsList = { info.Goods.Select(x => x.ToProto()) }
};
}
public static GridFightForgeGoodsInfo ToProto(this GridFightForgeGoodsInfoPb info)
{
var proto = new GridFightForgeGoodsInfo();
if (info.HasItemId)
{
proto.EquipmentGoodsInfo = new GridFightForgeEquipmentInfo
{
GridFightEquipmentId = info.ItemId
};
}
else
{
proto.RoleGoodsInfo = new GridFightForgeRoleInfo
{
RoleBasicId = info.RoleInfo.RoleId,
ForgeRoleTier = info.RoleInfo.Tier
};
}
return proto;
}
public static BattleGridFightRoleInfo ToBattleInfo(this GridFightRoleInfoPb info, GridFightItemsInfoPb item)
{
return new BattleGridFightRoleInfo
@@ -326,4 +529,18 @@ public static class GridFightRoleInfoPbExtensions
GameSavedValueMap = { info.SavedValues }
};
}
public static BattleGridFightNpcInfo ToBattleInfo(this GridFightNpcInfoPb info, GridFightItemsInfoPb item)
{
return new BattleGridFightNpcInfo
{
NpcId = info.NpcId,
UniqueId = info.UniqueId,
Pos = info.Pos,
GridFightEquipmentList =
{
item.EquipmentItems.Where(x => info.EquipmentIds.Contains(x.UniqueId)).Select(x => x.ToBattleInfo())
}
};
}
}

View File

@@ -5,6 +5,8 @@ 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 System.Data.OscarClient;
using EggLink.DanhengServer.Data.Excel;
namespace EggLink.DanhengServer.GameServer.Game.GridFight.Component;
@@ -12,6 +14,47 @@ public class GridFightTraitComponent(GridFightInstance inst) : BaseGridFightComp
{
public GridFightTraitInfoPb Data { get; set; } = new();
public List<GridFightRoleInfoPb> GetTraitRoles(uint traitId)
{
var roleComp = Inst.GetComponent<GridFightRoleComponent>();
var onGroundRoles = roleComp.Data.Roles.Where(x => x.Pos <= GridFightRoleComponent.PrepareAreaPos).ToList();
var traitRoles = onGroundRoles.Where(x => GameData.GridFightRoleBasicInfoData
.GetValueOrDefault(x.RoleId)?.TraitList.Contains(traitId) == true).ToList();
// check equipment traits
foreach (var role in onGroundRoles.Except(traitRoles))
{
var roleExcel = GameData.GridFightRoleBasicInfoData.GetValueOrDefault(role.RoleId);
if (roleExcel == null) continue;
foreach (var equipmentUid in role.EquipmentIds)
{
// get item
var equipmentItem = roleComp.Inst.GetComponent<GridFightItemsComponent>().Data.EquipmentItems
.FirstOrDefault(x => x.UniqueId == equipmentUid);
if (equipmentItem == null) continue;
// get conf
var equipmentExcel = GameData.GridFightEquipmentData.GetValueOrDefault(equipmentItem.ItemId);
if (equipmentExcel == null) continue;
// category (emblem)
if (equipmentExcel.EquipCategory != GridFightEquipCategoryEnum.Emblem) continue;
if (equipmentExcel.EquipFuncParamList.Contains(traitId))
{
// we can add this role directly becuz foreach has Except option
traitRoles.Add(role);
}
}
}
return traitRoles;
}
public async ValueTask CheckTrait()
{
var itemsComp = Inst.GetComponent<GridFightItemsComponent>();
@@ -139,7 +182,6 @@ public class GridFightTraitComponent(GridFightInstance inst) : BaseGridFightComp
effectLayerPas.TryGetValue(prevLayer, out var prevEffectParam);
effectLayerPas.TryGetValue(nextLayer, out var nextEffectParam);
// Handle different effect types
switch (traitConf.TraitEffectType)
{
@@ -174,15 +216,17 @@ public class GridFightTraitComponent(GridFightInstance inst) : BaseGridFightComp
}
case GridFightTraitEffectTypeEnum.TraitBonus:
{
effect.Threshold = 0; // initialize
if (!effect.HasThreshold)
effect.Threshold = 0; // initialize
break;
}
case GridFightTraitEffectTypeEnum.CoreRoleChoose:
{
// create pending action
syncList.AddRange(await Inst.CreatePendingAction<GridFightTraitPendingAction>(
GridFightSrc.KGridFightSrcTraitEffectUpdate,
false, effect));
if (nextLayer != 0)
syncList.AddRange(await Inst.CreatePendingAction<GridFightTraitPendingAction>(
GridFightSrc.KGridFightSrcTraitEffectUpdate,
false, effect));
break;
}
case GridFightTraitEffectTypeEnum.CoreRoleByEquipNum:
@@ -207,6 +251,93 @@ public class GridFightTraitComponent(GridFightInstance inst) : BaseGridFightComp
return syncList;
}
public async ValueTask HandleBattleEnd(PVEBattleResultCsReq req, bool success)
{
var itemsComp = Inst.GetComponent<GridFightItemsComponent>();
List<BaseGridFightSyncData> syncDatas = [];
foreach (var traitInfo in Data.Traits.Where(x => x.TraitLayer > 0))
{
foreach (var effectInfo in traitInfo.Effects)
{
if (!GameData.GridFightTraitEffectData.TryGetValue(effectInfo.EffectId, out var effectConf) ||
!GameData.GridFightTraitEffectLayerPaData.TryGetValue(effectInfo.EffectId,
out var effectLayerPas) ||
!effectLayerPas.TryGetValue(traitInfo.TraitLayer, out var layerEffectPa)) continue;
if (effectConf.TraitEffectType != GridFightTraitEffectTypeEnum.TraitBonus) continue;
if (!GameData.GridFightTraitBonusAddRuleData.TryGetValue(effectInfo.EffectId,
out var bonusAddRuleExcel)) continue;
// add bonus
var baseBonusValue = req.Stt.GridFightBattleStt.TraitBattleStt
.FirstOrDefault(x => x.TraitId == traitInfo.TraitId)?.TraitEffectInfoList
.FirstOrDefault(x => x.EffectId == effectConf.ID)?.SwitchList
.FirstOrDefault() ??
effectInfo.Threshold;
// base value
var addValue = bonusAddRuleExcel.TraitBonusType switch
{
GridFightTraitBonusAddTypeEnum.ByEquipNum => 10u +
(uint)GetTraitRoles(traitInfo.TraitId)
.Sum(x => x.EquipmentIds.Count),
GridFightTraitBonusAddTypeEnum.ByConstWithPerfectPass => (uint)(layerEffectPa.EffectParamList
.FirstOrDefault()?.Value ?? 0),
_ => 0u
};
// rate
addValue *= bonusAddRuleExcel.TraitBonusType switch
{
GridFightTraitBonusAddTypeEnum.ByEquipNum => (uint)(layerEffectPa.EffectParamList.FirstOrDefault()
?.Value ?? 0),
GridFightTraitBonusAddTypeEnum.ByConstWithPerfectPass => success
? bonusAddRuleExcel.ParamList[1]
: bonusAddRuleExcel.ParamList[0],
_ => 1
};
baseBonusValue += addValue;
// set
var prev = effectInfo.Threshold;
effectInfo.Threshold = baseBonusValue;
// sync
syncDatas.Add(new GridFightTraitSyncData(GridFightSrc.KGridFightSrcTraitEffectUpdate, effectInfo,
effectInfo.EffectId, traitInfo.TraitId, effectInfo.EffectId));
var addBonuses = GameData.GridFightTraitBonusData
.GetValueOrDefault(effectInfo.EffectId)?.Values.Where(x =>
x.BonusThreshold > prev && x.BonusThreshold <= baseBonusValue &&
x.BonusType == GridFightTraitBonusTypeEnum.Bonus).ToList() ?? [];
// take bonus effect
var bonusIdList = addBonuses.SelectMany(x => x.BonusParamList).ToList();
List<GridFightBasicBonusPoolV2Excel> bonusPool = [];
foreach (var id in bonusIdList)
{
bonusPool.AddRange(GridFightOrbComponent.ExtractCombinationBonus(id));
}
// take effect
var res = await itemsComp.TakeBasicBonusItems(bonusPool, GridFightSrc.KGridFightSrcTraitEffectUpdate,
effectInfo.EffectId, false);
syncDatas.AddRange(res.Item1);
}
}
if (syncDatas.Count > 0)
{
await Inst.Player.SendPacket(new PacketGridFightSyncUpdateResultScNotify(syncDatas));
}
}
public override GridFightGameInfo ToProto()
{
var roleComp = Inst.GetComponent<GridFightRoleComponent>();
@@ -314,12 +445,20 @@ public static class GridFightTraitInfoPbExtensions
public static GridFightTraitEffectInfo ToProto(this GridFightGameTraitEffectPb info)
{
return new GridFightTraitEffectInfo
var proto = new GridFightTraitEffectInfo
{
EffectId = info.EffectId,
TraitEffectLevelExp = info.Threshold,
TraitCoreRole = info.CoreRoleUniqueId
};
if (info.HasThreshold)
proto.TraitEffectLevelExp = info.Threshold;
if (info.HasCoreRoleUniqueId)
proto.TraitCoreRole = info.CoreRoleUniqueId;
return proto;
}
public static BattleGridFightTraitEffectInfo ToBattleInfo(this GridFightGameTraitEffectPb info, GridFightRoleComponent roleComp)

View File

@@ -34,6 +34,7 @@ public class GridFightInstance(PlayerInstance player, uint season, uint division
var basicComp = GetComponent<GridFightBasicComponent>();
var levelComp = GetComponent<GridFightLevelComponent>();
var traitComp = GetComponent<GridFightTraitComponent>();
var itemsComponent = GetComponent<GridFightItemsComponent>();
var prevData = basicComp.Data.Clone();
var curEncounter = levelComp.CurrentSection.Encounters[(int)(levelComp.CurrentSection.BranchId - 1)];
@@ -107,6 +108,9 @@ public class GridFightInstance(PlayerInstance player, uint season, uint division
await Player.SendPacket(new PacketGridFightSyncUpdateResultScNotify(syncs));
// trait
await traitComp.HandleBattleEnd(req, progress == 100);
if (end)
{
// settle

View File

@@ -0,0 +1,16 @@
using EggLink.DanhengServer.GameServer.Game.GridFight.Component;
using EggLink.DanhengServer.Proto;
using EggLink.DanhengServer.Proto.ServerSide;
namespace EggLink.DanhengServer.GameServer.Game.GridFight.Sync;
public class GridFightAddForgeSyncData(GridFightSrc src, GridFightForgeInfoPb info, uint groupId = 0, params uint[] syncParams) : BaseGridFightSyncData(src, groupId, syncParams)
{
public override GridFightSyncData ToProto()
{
return new GridFightSyncData
{
AddForgeInfo = info.ToProto()
};
}
}

View File

@@ -0,0 +1,16 @@
using EggLink.DanhengServer.GameServer.Game.GridFight.Component;
using EggLink.DanhengServer.Proto;
using EggLink.DanhengServer.Proto.ServerSide;
namespace EggLink.DanhengServer.GameServer.Game.GridFight.Sync;
public class GridFightForgeUpdateSyncData(GridFightSrc src, GridFightForgeInfoPb forge, uint groupId = 0, params uint[] syncParams) : BaseGridFightSyncData(src, groupId, syncParams)
{
public override GridFightSyncData ToProto()
{
return new GridFightSyncData
{
UpdateForgeInfo = forge.ToProto()
};
}
}

View File

@@ -0,0 +1,16 @@
using EggLink.DanhengServer.GameServer.Game.GridFight.Component;
using EggLink.DanhengServer.Proto;
using EggLink.DanhengServer.Proto.ServerSide;
namespace EggLink.DanhengServer.GameServer.Game.GridFight.Sync;
public class GridFightNpcUpdateSyncData(GridFightSrc src, GridFightNpcInfoPb npc, uint groupId = 0, params uint[] syncParams) : BaseGridFightSyncData(src, groupId, syncParams)
{
public override GridFightSyncData ToProto()
{
return new GridFightSyncData
{
UpdateNpcInfo = npc.ToProto()
};
}
}

View File

@@ -12,7 +12,8 @@ public class GridFightPlayerLevelSyncData(GridFightSrc src, GridFightBasicInfoPb
PlayerLevel = new GridFightPlayerLevelSyncInfo
{
Exp = info.LevelExp,
Level = info.CurLevel
Level = info.CurLevel,
MaxLevel = info.MaxLevel
}
};
}

View File

@@ -0,0 +1,15 @@
using EggLink.DanhengServer.Proto;
using EggLink.DanhengServer.Proto.ServerSide;
namespace EggLink.DanhengServer.GameServer.Game.GridFight.Sync;
public class GridFightRemoveForgeSyncData(GridFightSrc src, GridFightForgeInfoPb info, uint groupId = 0, params uint[] syncParams) : BaseGridFightSyncData(src, groupId, syncParams)
{
public override GridFightSyncData ToProto()
{
return new GridFightSyncData
{
RemoveForgeUniqueId = info.UniqueId
};
}
}

View File

@@ -0,0 +1,26 @@
using EggLink.DanhengServer.GameServer.Game.GridFight.Component;
using EggLink.DanhengServer.Kcp;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.GameServer.Server.Packet.Recv.GridFight;
[Opcode(CmdIds.GridFightUseForgeCsReq)]
public class HandlerGridFightUseForgeCsReq : Handler
{
public override async Task OnHandle(Connection connection, byte[] header, byte[] data)
{
var req = GridFightUseForgeCsReq.Parser.ParseFrom(data);
var inst = connection.Player!.GridFightManager!.GridFightInstance;
if (inst == null)
{
await connection.SendPacket(CmdIds.GridFightUseForgeScRsp);
return;
}
var component = inst.GetComponent<GridFightRoleComponent>();
await component.UseForgeItem(req.UniqueId, req.ForgeTargetIndex);
await connection.SendPacket(CmdIds.GridFightUseForgeScRsp);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -44,6 +44,7 @@ message GridFightBasicInfoPb {
repeated uint32 TrackingTraits = 11;
repeated GridFightEquipmentTrackInfoPb TrackingEquipments = 12;
string GuideCode = 13;
uint32 MaxLevel = 14;
}
message GridFightEquipmentTrackInfoPb {
@@ -61,9 +62,37 @@ message GridFightRoleInfoPb {
repeated uint32 EquipmentIds = 6;
}
message GridFightAvatarInfoPb {
message GridFightNpcInfoPb {
uint32 NpcId = 1;
uint32 Pos = 2;
uint32 UniqueId = 3;
repeated uint32 EquipmentIds = 4;
}
message GridFightForgeInfoPb {
uint32 ForgeItemId = 1;
uint32 Pos = 2;
uint32 UniqueId = 3;
repeated GridFightForgeGoodsInfoPb Goods = 4;
}
message GridFightForgeGoodsInfoPb {
oneof GoodsType {
uint32 ItemId = 1;
GridFightForgeRoleGoodsInfoPb RoleInfo = 2;
}
}
message GridFightForgeRoleGoodsInfoPb {
uint32 RoleId = 1;
uint32 Tier = 2;
}
message GridFightTeamInfoPb {
repeated GridFightRoleInfoPb Roles = 1;
uint32 CurUniqueId = 2;
repeated GridFightNpcInfoPb Npcs = 2;
repeated GridFightForgeInfoPb Forges = 3;
uint32 CurUniqueId = 4;
}
message GridFightGameOrbPb {
@@ -122,7 +151,7 @@ message GridFightComponentPb {
oneof ComponentType {
GridFightShopInfoPb ShopInfo = 1;
GridFightBasicInfoPb BasicInfo = 2;
GridFightAvatarInfoPb AvatarInfo = 3;
GridFightTeamInfoPb TeamInfo = 3;
GridFightOrbInfoPb OrbInfo = 4;
GridFightAugmentInfoPb AugmentInfo = 5;
GridFightTraitInfoPb TraitInfo = 6;