using System; using System.Linq; using ProtoCSStruct; using Sog; namespace Game { /** * 玩家天赋系统 */ public static class TalentSvc { private const int DefaultTalentLevel = 1; public static void OnPlayerBorn(PlayerOnGame player) { ref var talent = ref player.RoleData.Talent; talent.Clear(); var talentPage = new DbTalentPageData { PageIndex = (int)PageIndex.Normal }; talent.TalentPage.CopyFrom(ref talentPage); var peakPage = new DbTalentPageData { PageIndex = (int)PageIndex.Peak }; talent.PeakData.CopyFrom(ref peakPage); talent.TalentPage.Point = DefaultTalentLevel; player.MakeDirty(); } public static void OnTalentOption(PlayerOnGame player, StructPacket packet) { var res = new CSTalentOptionsRes(); var unlock = SystemUnlockSvc.IsUnlockSys(player, (int)SystemUnlockId.Talent); if (!unlock) { res.Ret = CSErrCode.SysNotUnlock; player.SendToClient((int)CSGameMsgID.TalentOptionsRes, ref res); return; } var change = ChapterSvc.CanChangeTalentOrEquip(player); if (!change) { res.Ret = CSErrCode.Fail; player.SendToClient((int)CSGameMsgID.TalentOptionsRes, ref res); return; } ref var req = ref packet.GetMessage(); res = new CSTalentOptionsRes() { Option = req.Option, PageIndex = req.PageIndex }; switch (req.PageIndex) { case (int)PageIndex.Peak: { ref var data = ref player.RoleData.Talent.PeakData; player.RoleData.Talent.PeakData.Items.Clear(); TalentPeakSvc.Recalculate(player); res.Data.CopyFrom(ref data); } break; case (int)PageIndex.Normal: { ref var data = ref player.RoleData.Talent.TalentPage; data.Items.Clear(); data.IsActive = true; res.Ret = CSErrCode.None; RecalculateTalent(ref data); res.Data.CopyFrom(ref data); } break; default: TraceLog.Error("pageIndex not found {0}", req.PageIndex); break; } player.MakeDirty(); player.SendToClient((int)CSGameMsgID.TalentOptionsRes, ref res); } //分配/减少天赋点数 public static void OnActiveOption(PlayerOnGame player, StructPacket packet) { ref var req = ref packet.GetMessage(); var talentId = req.TalentId; var desc = TalentDescMgr.Instance.GetConfig(talentId); if (desc == null) { ref var res = ref CSTalentActiveRes.Parser.GetMessageClear(); res.Ret = CSErrCode.Fail; player.SendToClient((int)CSGameMsgID.TalentActiveRes, ref res); return; } var pageIndex = (PageIndex)req.PageIndex; switch (pageIndex) { case PageIndex.Normal: OnTalentActiveOption(player, req); break; case PageIndex.Peak: TalentPeakSvc.OnActiveOption(player, req); break; default: TraceLog.Error("pageIndex not found {0}", pageIndex); break; } } private static void OnTalentActiveOption(PlayerOnGame player, CSTalentActiveReq req) { ref var res = ref CSTalentActiveRes.Parser.GetMessageClear(); var change = ChapterSvc.CanChangeTalentOrEquip(player); if (!change) { res.Ret = CSErrCode.Fail; player.SendToClient((int)CSGameMsgID.TalentActiveRes, ref res); return; } ref var talentPageData = ref player.RoleData.Talent.TalentPage; if (!talentPageData.IsActive) { res.Ret = CSErrCode.Fail; player.SendToClient((int)CSGameMsgID.TalentActiveRes, ref res); return; } if (!TalentHelper.IsCurrentPageTalent(PageIndex.Normal, req.TalentId)) { res.Ret = CSErrCode.Fail; player.SendToClient((int)CSGameMsgID.TalentActiveRes, ref res); return; } ; var desc = TalentDescMgr.Instance.GetConfig(req.TalentId); var def = desc.type == TalentType.Core && desc.LevelPoint[0] == 0; if (def) { res.Ret = CSErrCode.Fail; player.SendToClient((int)CSGameMsgID.TalentActiveRes, ref res); return; } var ret = req.Backup ? TalentBackup(ref talentPageData, req.TalentId) : TalentActive(ref talentPageData, req.TalentId); if (ret != CSErrCode.None) { res.Ret = ret; player.SendToClient((int)CSGameMsgID.TalentActiveRes, ref res); return; } RecalculateTalent(ref talentPageData); player.MakeDirty(); res.PageIndex = (int)PageIndex.Normal; res.Data = talentPageData; res.Ret = ret; player.SendToClient((int)CSGameMsgID.TalentActiveRes, ref res); } public static void SyncData(PlayerOnGame player) { ref var res = ref CSTalentDataSync.Parser.GetMessageClear(); res.Talent.CopyFrom(ref player.RoleData.Talent); player.SendToClient((int)CSGameMsgID.TalentDataSync, ref res); } private static CSErrCode TalentBackup(ref DbTalentPageData roleData, int descId) { ref var talent = ref roleData.Items; var desc = TalentDescMgr.Instance.GetConfig(descId); if (desc == null) { return CSErrCode.Fail; } var index = -1; for (var t = 0; t < talent.Count; t++) { if (talent[t].Id != descId) continue; index = t; break; } if (index < 0) { return CSErrCode.DescNotFound; } var level = talent[index].Value; if (level == 1) { talent.RemoveAt(index); } else { talent[index].Value -= 1; } return CSErrCode.None; } private static CSErrCode TalentActive(ref DbTalentPageData talent, int descId) { var desc = TalentDescMgr.Instance.GetConfig(descId); if (!talent.IsActive) { return CSErrCode.Fail; //不可以被激活 不是当前系统页签的天赋,需要激活final类型的 } var index = -1; var unlockConditionTalentIds = desc.unlockConditionTalent; for (var i = 0; i < talent.Items.Count; i++) { var tempTalentId = talent.Items[i].Id; var tempDesc = TalentDescMgr.Instance.GetConfig(tempTalentId); if (desc.lockTalent.Contains(tempTalentId)) { TraceLog.Error("TalentSvc.Active Talent Lock talentId={0},lockIds={1}", desc.id, string.Join(",", tempDesc.lockTalent)); return CSErrCode.Fail; //全局流派互斥 } if (tempTalentId == descId) { index = i; if (talent.Items[i].Value >= desc.Level) { TraceLog.Error("TalentSvc.Active Talent Level Max talentId={0}", talent.Items[i].Value); return CSErrCode.Fail; //超过最大上限了 } } if (unlockConditionTalentIds.Contains(tempTalentId)) { //前置天赋 var dk = -1; for (var j = 0; j < unlockConditionTalentIds.Length; j++) { if (unlockConditionTalentIds[j] == tempTalentId) { dk = j; break; } } if (dk >= desc.unlockConditionTalentLevel.Length) { dk = desc.unlockConditionTalentLevel.Length - 1; //兼容一下,不然会数组越界 } if (dk != -1) { var level = desc.unlockConditionTalentLevel[dk]; if (talent.Items[i].Value < level) { TraceLog.Error("TalentSvc.Active pre talent level low talentId={0},level={1}", talent.Items[i].Id, level); return CSErrCode.Fail; //前置条件不满足 } } } } //上一层是否有激活的天赋 var unlockConditionPoint = desc.unlockConditionPoint; if (unlockConditionPoint > 0) { var count = GetPreAllTalentCount(talent, descId); if (count < unlockConditionPoint) { TraceLog.Error("TalentSvc.Active pre talent point low unlock point={0}, cost={1}", count, unlockConditionPoint); return CSErrCode.Fail; //前置点数不够 } } var costIndex = 0; if (index >= 0) { costIndex = talent.Items[index].Value - 1; } if (costIndex >= desc.LevelPoint.Length) { costIndex = desc.LevelPoint.Length - 1; } var point = desc.LevelPoint[costIndex]; //这一次升级需要的点数 var talentCanUsePoint = GetTalentCanUsePoint(ref talent); if (point != 0 && talentCanUsePoint < point) //需要的点数不够 { TraceLog.Error("TalentSvc.Active need talent point low unlock point={0}, cost={1}", point, talentCanUsePoint); return CSErrCode.Fail; } if (index != -1) { talent.Items[index].Value++; return CSErrCode.None; } talent.Items.Add(NewDbTalentOne(desc.id, DefaultTalentLevel)); return CSErrCode.None; } private static IDValue32 NewDbTalentOne(int id, int value) { return new IDValue32() { Id = id, Value = value, }; } public static void AddTalentPoint(PlayerOnGame player, int point) { var limits = CommParamDescMgr.Instance.TalentPoint.int_list; ref var talent = ref player.RoleData.Talent.TalentPage; var res = new CSTalentOptionsRes(); var left = limits[0]; var right = limits[1]; if (talent.Point < left) { talent.Point += point; res.Data.CopyFrom(ref talent); res.PageIndex = (int)PageIndex.Normal; } else { player.RoleData.Talent.PeakData.Point += point; player.RoleData.Talent.PeakData.Point = Math.Min(right, player.RoleData.Talent.PeakData.Point); TalentPeakSvc.Active(player); res.Data.CopyFrom(ref player.RoleData.Talent.PeakData); res.PageIndex = (int)PageIndex.Peak; } player.SendToClient((int)CSGameMsgID.TalentOptionsRes, ref res); } //重新计算激活 private static void RecalculateTalent(ref DbTalentPageData talentPageData) { var newTalentData = new DbTalentPageData { Point = talentPageData.Point, IsActive = talentPageData.IsActive, PageIndex = (int)talentPageData.PageIndex }; //处理当前页 ProcCurrentTalent(talentPageData, ref newTalentData); talentPageData.CopyFrom(ref newTalentData); } //重算当前激活页天赋 private static void ProcCurrentTalent(DbTalentPageData oldTalentData, ref DbTalentPageData newTalentData) { foreach (var kv in TalentDescMgr.Instance.ItemTable) { var desc = kv.Value; if (!TalentHelper.IsCurrentPageTalent(PageIndex.Normal, desc.id)) //是否属于当前页的天赋技能 { continue; } var def = desc.type == TalentType.Core && desc.LevelPoint[0] == 0; if (def) //默认天赋技能 { newTalentData.Items.Add(NewDbTalentOne(desc.id, DefaultTalentLevel)); continue; } var index = -1; for (var i = 0; i < oldTalentData.Items.Count; i++) { if (oldTalentData.Items[i].Id == desc.id) { index = i; break; } } if (index < 0) { continue; } var oneData = oldTalentData.Items[index]; var descUnlockConditionPoint = desc.unlockConditionPoint; var preTalentCount = GetPreAllTalentCount(oldTalentData, oneData.Id); if (descUnlockConditionPoint > 0 && descUnlockConditionPoint > preTalentCount) //所有前置总点数 { continue; } var preTalentIndex = GetPreTalentIndex(oldTalentData, oneData.Id); var unlockConditionTalent = desc.unlockConditionTalent; var unlockConditionLevel = desc.unlockConditionTalentLevel; var prePathConduction = (preTalentIndex > -1 && unlockConditionTalent.Length > 0) || (unlockConditionTalent.Length == 0); while (preTalentIndex > -1 && unlockConditionTalent.Length > 0) //前置天赋的前置天赋 { var preOldOne = oldTalentData.Items[preTalentIndex]; var preTalentId = preOldOne.Id; var preTalentLevel = preOldOne.Value; var cid = 0; for (var i = 0; i < unlockConditionTalent.Length; i++) { if (unlockConditionTalent[i] == preTalentId) { cid = i; break; } } if (cid >= unlockConditionLevel.Length) { cid = unlockConditionLevel.Length - 1; } var tarLevel = unlockConditionLevel[cid]; prePathConduction = preTalentLevel >= tarLevel; preTalentIndex = GetPreTalentIndex(oldTalentData, preOldOne.Id); var temDesc = TalentDescMgr.Instance.GetConfig(preOldOne.Id); unlockConditionTalent = temDesc.unlockConditionTalent; unlockConditionLevel = temDesc.unlockConditionTalentLevel; if (!prePathConduction) { break; } } //此时的unlockConditionLevel应该是根天赋,所有路径上的天赋都满足才对(当然巅峰天赋没有上下级,也无所谓) prePathConduction = prePathConduction && unlockConditionLevel.Length == 0; if (!prePathConduction) { continue; } newTalentData.Items.Add(oneData); //前置行点数够了 } } //剩余天赋点数 private static int GetTalentCanUsePoint(ref DbTalentPageData talentPageData) { var allPoint = talentPageData.Point; var cost = GetAllTalentCostCount(ref talentPageData); return Math.Max(0, allPoint - cost); } /** * 获取当前页所有消耗的点数 */ private static int GetAllTalentCostCount(ref DbTalentPageData pageData) { var cost = 0; //已消耗的点数 var talents = pageData.Items; for (var i = 0; i < talents.Count; i++) { var id = talents[i].Id; var level = talents[i].Value; var desc = TalentDescMgr.Instance.GetConfig(id); var levelPoint = desc.LevelPoint; for (var j = 0; j < level; j++) { if (j >= levelPoint.Length) { cost += levelPoint[^1]; } else { cost += levelPoint[j]; } } } return cost; } //前置天赋点了多少点 private static int GetPreAllTalentCount(DbTalentPageData talent, int targetId) { var preTalentIndex = GetPreTalentIndex(talent, targetId); if (preTalentIndex < 0) //没有前置天赋 { return 0; } var ret = 0; while (preTalentIndex > 0) { var talentOne = talent.Items[preTalentIndex]; var preTalentId = talentOne.Id; var level = talentOne.Value; var preDesc = TalentDescMgr.Instance.GetConfig(preTalentId); var pl = preDesc.LevelPoint; for (var i = 0; i < level; i++) { if (i >= pl.Length) { ret += pl[^1]; } else { ret += pl[i]; } } preTalentIndex = GetPreTalentIndex(talent, preTalentId); } return ret; } //获取某个天赋的已经点亮的前置天赋索引 private static int GetPreTalentIndex(DbTalentPageData talent, int targetId) { var desc = TalentDescMgr.Instance.GetConfig(targetId); var unlockConditionTalent = desc.unlockConditionTalent; var index = -1; foreach (var lockId in unlockConditionTalent) { for (var j = 0; j < talent.Items.Count; j++) { if (talent.Items[j].Id != lockId) continue; index = j; break; } if (index > -1) { break; } } return index; } } }