// public static List<IObject> GetEnemyByAOI(Skill skillObj) // { // if (skillObj == null) // { // return null; // } // // return GetEnemyByAOI(skillObj.mSelfObj, (SkillRangeType)1, 90.0f, skillObj.mSkillProperty.mSkillRange, 0.0f); // } /// <summary> /// 根据AOI获取可攻击范围内列表 /// </summary> /// <param name="self"></param> /// <param name="type"></param> /// <param name="width"></param> /// <param name="maxDistance"></param> /// <param name="minDistance"></param> public static List <IObject> GetEnemyByAOI(IObject self, SkillRangeType type, float maxDistance, float width, float minDistance = 0.0f) { if (self == null || self.mAOIList == null) { return(null); } List <IObject> list = new List <IObject>(); if (type == SkillRangeType.SkillRangeType_Self) { list.Add(self); } else if (type == SkillRangeType.SkillRangeType_Single) { IObject target = null; float tempDistance = float.MaxValue; for (int i = 0; i < self.mAOIList.Count; ++i) { IObject obj = self.mAOIList[i]; if (obj == null) { continue; } float distance = UtilTools.Vec2Distance(self, obj); if (distance <= maxDistance && distance < tempDistance) { tempDistance = distance; target = obj; } } list.Add(target); } else { for (int i = 0; i < self.mAOIList.Count; ++i) { IObject obj = self.mAOIList[i]; if (!IsObjectInRange(self, obj, maxDistance, type, width, minDistance)) { continue; } list.Add(obj); } } return(list); }
public Skill() { Id = 1; NameLang = 10000; DescriptionLang = 10001; Image = Resources.Load("Skill/LinerSword", typeof(Sprite)) as Sprite; HotKeyIndex = 0; RangeType = SkillRangeType.Line; Range = 3; EffectType = SkillEffectType.PhysicalDamage; CD = 2; Power = 10000; }
public Vector2Int[] GetRangeWithRangeType(Vector2Int origin, int maxLength, SkillRangeType rangeType, int beginDistance = 1) { switch (rangeType) { case SkillRangeType.Circle: return(GetCircleRange(origin, maxLength)); case SkillRangeType.Square: return(null); case SkillRangeType.FourRay: return(GetFourRayRange(origin, maxLength, beginDistance)); } return(null); }
/// <summary> /// 计算技能作用的对象; /// </summary> /// <returns><c>true</c>, if range sector was set, <c>false</c> otherwise.</returns> public List <Transform> GetTargets(Vector3 center, Vector3 direction, int skillId) { SkillRangeType my_SkillRangeType = SkillRangeType.SkillRangeType_Unknown; switch (my_SkillRangeType) { case SkillRangeType.SkillRangeType_Unknown: break; case SkillRangeType.SkillRangeType_SelfSector: break; case SkillRangeType.SkillRangeType_SelfLine: break; case SkillRangeType.SkillRangeType_SelfRound: break; default: break; } return(null); }
/// <summary> /// 判断对象是否在一个对象的范围内 /// </summary> /// <param name="self"></param> /// <param name="target"></param> /// <param name="maxDistance"></param> /// <param name="width"></param> /// <param name="rangeType"></param> /// <returns></returns> public static bool IsObjectInRange(IObject self, IObject target, float maxDistance, SkillRangeType rangeType, float width, float minDistance = 0.0f) { if (self == null || target == null) { return(false); } if (Mathf.Abs(maxDistance) < 0.01) { return(false); } if (rangeType >= SkillRangeType.SkillRangeType_MaxCount || rangeType <= SkillRangeType.SkillRangeType_Unknown) { return(false); } Vector3 selfPosition = self.mPosition; Vector3 selfForward = self.mGameObject.transform.forward; Quaternion r = self.mGameObject.transform.rotation; Vector3 targetPosition = target.mPosition; Vector3 direction = (targetPosition - selfPosition).normalized; bool isIn = false; switch (rangeType) { case SkillRangeType.SkillRangeType_Single: break; case SkillRangeType.SkillRangeType_SelfLine: // Vector3 left = selfPosition + (r * Vector3.left) * width / 2; // Vector3 right = selfPosition + (r * Vector3.right) * width / 2; // Vector3 leftEnd = (left + (r * Vector3.forward) * maxDistance); // Vector3 rightEnd = (right + (r * Vector3.forward) * maxDistance); // //Debug.Log("left = "+left); // //Debug.Log("right = "+right); // //Debug.Log("leftEnd = "+leftEnd); // //Debug.Log("rightEnd = "+rightEnd); // singletonNpcTest.GetSingletonNpcTest().svv[0] = left; // singletonNpcTest.GetSingletonNpcTest().svv[1] = right; // singletonNpcTest.GetSingletonNpcTest().svv[2] = leftEnd; // singletonNpcTest.GetSingletonNpcTest().svv[3] = rightEnd; // // // if (IsInRect(targetPosition, leftEnd, rightEnd, right, left)) // { // //Debug.Log("============true================="); // isIn = true; // } if (AttackRangeDefine.IsLine(target.mPosition, self.mPosition, self.mGameObject.transform.forward, width, maxDistance)) { isIn = true; } else { isIn = false; } break; // 此处width为扇形夹角 case SkillRangeType.SkillRangeType_SelfSector: Vector3 f0 = selfPosition + (r * Vector3.forward) * maxDistance; Quaternion r0 = Quaternion.Euler(r.eulerAngles.x, r.eulerAngles.y - width / 2, r.eulerAngles.z); Quaternion r1 = Quaternion.Euler(r.eulerAngles.x, r.eulerAngles.y + width / 2, r.eulerAngles.z); Vector3 f1 = selfPosition + (r0 * Vector3.forward) * maxDistance; Vector3 f2 = selfPosition + (r1 * Vector3.forward) * maxDistance; if (IsInTriangle(targetPosition, selfPosition, f1, f0) || IsInTriangle(targetPosition, selfPosition, f2, f0)) { isIn = true; } break; case SkillRangeType.SkillRangeType_SelfRound: if (Vector3.Distance(selfPosition, targetPosition) <= maxDistance && Vector3.Distance(selfPosition, targetPosition) > minDistance) { isIn = true; } break; default: break; } return(isIn); }
public static DamageHandler CalculateDamage(SkillCast skill, IFieldActor source, IFieldActor target, double luckCoefficient) { // TODO: get accuracyWeakness from enemy stats from enemy buff. new stat recommended const double AccuracyWeakness = 0; double hitRate = (source.Stats[StatAttribute.Accuracy].Total + AccuracyWeakness) / Math.Max(target.Stats[StatAttribute.Evasion].Total, 0.1); if (Random.Shared.NextDouble() > hitRate) { return(new(source, target, 0, HitType.Miss)); // we missed } bool isCrit = skill.IsGuaranteedCrit() || RollCrit(source, target, luckCoefficient); double finalCritDamage = 1; if (isCrit) { // TODO: get critResist from enemy stats from enemy buff. new stat recommended const double CritResist = 1; double critDamage = 1000 + source.Stats[StatAttribute.CritDamage].Total; finalCritDamage = CritResist * ((critDamage / 1000) - 1) + 1; } double damageBonus = 1 + FetchMultiplier(source.Stats, StatAttribute.TotalDamage); damageBonus *= finalCritDamage; switch (skill.GetElement()) { case Element.Fire: damageBonus += FetchMultiplier(source.Stats, StatAttribute.FireDamage); break; case Element.Ice: damageBonus += FetchMultiplier(source.Stats, StatAttribute.IceDamage); break; case Element.Electric: damageBonus += FetchMultiplier(source.Stats, StatAttribute.ElectricDamage); break; case Element.Holy: damageBonus += FetchMultiplier(source.Stats, StatAttribute.HolyDamage); break; case Element.Dark: damageBonus += FetchMultiplier(source.Stats, StatAttribute.DarkDamage); break; case Element.Poison: damageBonus += FetchMultiplier(source.Stats, StatAttribute.PoisonDamage); break; } SkillRangeType rangeType = skill.GetRangeType(); if (rangeType != SkillRangeType.Special) { damageBonus += FetchMultiplier(source.Stats, rangeType == SkillRangeType.Melee ? StatAttribute.MeleeDamage : StatAttribute.RangedDamage); } bool isBoss = false; if (target is INpc npc) { isBoss = npc.Value.IsBoss(); } damageBonus += isBoss ? FetchMultiplier(source.Stats, StatAttribute.BossDamage) : 0; // TODO: properly fetch enemy attack speed weakness from enemy buff. new stat recommended const double AttackSpeedWeakness = 0; damageBonus += AttackSpeedWeakness * FetchMultiplier(source.Stats, StatAttribute.AttackSpeed); double damageMultiplier = damageBonus * skill.GetDamageRate(); // TODO: properly fetch enemy pierce resistance from enemy buff. new stat recommended const double EnemyPierceResistance = 1; double defensePierce = 1 - Math.Min(0.3, EnemyPierceResistance * FetchMultiplier(source.Stats, StatAttribute.Pierce)); damageMultiplier *= 1 / (Math.Max(target.Stats[StatAttribute.Defense].Total, 1) * defensePierce); bool isPhysical = skill.GetSkillDamageType() == DamageType.Physical; StatAttribute resistanceStat = isPhysical ? StatAttribute.PhysicalRes : StatAttribute.MagicRes; StatAttribute attackStat = isPhysical ? StatAttribute.PhysicalAtk : StatAttribute.MagicAtk; StatAttribute piercingStat = isPhysical ? StatAttribute.PhysicalPiercing : StatAttribute.MagicPiercing; double targetRes = target.Stats[resistanceStat].Total; double attackType = source.Stats[attackStat].Total; double resPierce = FetchMultiplier(source.Stats, piercingStat); double resistance = (1500.0 - Math.Max(0, targetRes - 1500 * resPierce)) / 1500; // does this need to be divided by anything at all to account for raw physical attack? damageMultiplier *= attackType * resistance; // TODO: apply special standalone multipliers like Spicy Maple Noodles buff? it seems to have had it's own multiplier. new stat recommended const double FinalDamageMultiplier = 1; damageMultiplier *= FinalDamageMultiplier; double attackDamage = 300; if (source is IFieldActor <Player> player) { double bonusAttack = player.Stats[StatAttribute.BonusAtk].Total + 0.4 * player.Stats[StatAttribute.PetBonusAtk].Total; // TODO: properly fetch enemy bonus attack weakness from enemy buff. new stat recommended const double BonusAttackWeakness = 1; double minDamage = player.Stats[StatAttribute.MinWeaponAtk].Total + BonusAttackWeakness * bonusAttack; double maxDamage = player.Stats[StatAttribute.MaxWeaponAtk].Total + BonusAttackWeakness * bonusAttack; attackDamage = minDamage + (maxDamage - minDamage) * Random.Shared.NextDouble(); } attackDamage *= damageMultiplier; return(new(source, target, Math.Max(1, attackDamage), isCrit ? HitType.Critical : HitType.Normal)); }
protected override List <SkillMetadata> Parse() { List <SkillMetadata> skillList = new(); foreach (PackFileEntry entry in Resources.XmlReader.Files) { // Parsing Skills if (entry.Name.StartsWith("skill")) { XmlDocument document = Resources.XmlReader.GetXmlDocument(entry); XmlNode ui = document.SelectSingleNode("/ms2/basic/ui"); XmlNode kinds = document.SelectSingleNode("/ms2/basic/kinds"); XmlNode stateAttr = document.SelectSingleNode("/ms2/basic/stateAttr"); XmlNodeList levels = document.SelectNodes("/ms2/level"); int skillId = int.Parse(Path.GetFileNameWithoutExtension(entry.Name)); string skillState = kinds.Attributes["state"]?.Value ?? ""; byte skillAttackType = byte.Parse(ui.Attributes["attackType"]?.Value ?? "0"); SkillType skillType = (SkillType)byte.Parse(kinds.Attributes["type"].Value); SkillSubType skillSubType = (SkillSubType)byte.Parse(kinds.Attributes["subType"]?.Value ?? "0"); SkillRangeType skillRangeType = (SkillRangeType)byte.Parse(kinds.Attributes["rangeType"]?.Value ?? "0"); byte skillElement = byte.Parse(kinds.Attributes["element"].Value); byte skillSuperArmor = byte.Parse(stateAttr.Attributes["superArmor"].Value); bool skillRecovery = int.Parse(kinds.Attributes["spRecoverySkill"]?.Value ?? "0") == 1; List <SkillLevel> skillLevels = new(); foreach (XmlNode level in levels) { // Getting all skills level string feature = level.Attributes["feature"]?.Value ?? ""; int levelValue = int.Parse(level.Attributes["value"].Value ?? "0"); // We prevent duplicates levels from older balances. if (skillLevels.Exists(x => x.Level == levelValue)) { continue; } List <SkillMotion> skillMotions = new(); foreach (XmlNode motionNode in level.SelectNodes("motion")) { string sequenceName = motionNode.SelectSingleNode("motionProperty")?.Attributes["sequenceName"].Value ?? ""; string motionEffect = motionNode.SelectSingleNode("motionProperty")?.Attributes["motionEffect"].Value ?? ""; List <SkillAttack> skillAttacks = new(); foreach (XmlNode attackNode in motionNode.SelectNodes("attack")) { // TODO: Parse other properties like: pause, arrow DamageProperty damageProperty = ParseDamageProperty(attackNode); RangeProperty rangeProperty = ParseRangeProperty(attackNode); byte attackPoint = byte.Parse(Regex.Match(attackNode.Attributes["point"]?.Value, @"\d").Value); short targetCount = short.Parse(attackNode.Attributes["targetCount"].Value); long magicPathId = long.Parse(attackNode.Attributes["magicPathID"]?.Value ?? "0"); long cubeMagicPathId = long.Parse(attackNode.Attributes["cubeMagicPathID"]?.Value ?? "0"); List <SkillCondition> skillConditions = new(); foreach (XmlNode conditionNode in attackNode.SelectNodes("conditionSkill")) { int conditionSkillId = int.Parse(conditionNode.Attributes["skillID"]?.Value ?? "0"); short conditionSkillLevel = short.Parse(conditionNode.Attributes["level"]?.Value ?? "0"); bool splash = conditionNode.Attributes["splash"]?.Value == "1"; byte target = byte.Parse(conditionNode.Attributes["skillTarget"].Value ?? "0"); byte owner = byte.Parse(conditionNode.Attributes["skillOwner"]?.Value ?? "0"); bool immediateActive = conditionNode.Attributes["immediateActive"]?.Value == "1"; short fireCount = short.Parse(conditionNode.Attributes["fireCount"].Value ?? "0"); int interval = int.Parse(conditionNode.Attributes["interval"].Value ?? "0"); skillConditions.Add(new(conditionSkillId, conditionSkillLevel, splash, target, owner, fireCount, interval, immediateActive)); } skillAttacks.Add(new(attackPoint, targetCount, magicPathId, cubeMagicPathId, rangeProperty, skillConditions, damageProperty)); } skillMotions.Add(new(sequenceName, motionEffect, skillAttacks)); } SkillUpgrade skillUpgrade = ParseSkillUpgrade(level); (int spirit, int stamina) = ParseConsume(level); skillLevels.Add(new(levelValue, spirit, stamina, feature, skillMotions, skillUpgrade)); } skillList.Add(new(skillId, skillLevels, skillState, skillAttackType, skillType, skillSubType, skillElement, skillSuperArmor, skillRecovery, skillRangeType)); } // Parsing SubSkills if (entry.Name.StartsWith("table/job")) { XmlDocument document = Resources.XmlReader.GetXmlDocument(entry); XmlNodeList jobs = document.SelectNodes("/ms2/job"); foreach (XmlNode job in jobs) { // Grabs all the skills and them the jobCode. XmlNodeList skills = job.SelectNodes("skills/skill"); int jobCode = int.Parse(job.Attributes["code"].Value); foreach (XmlNode skill in skills) { int id = int.Parse(skill.Attributes["main"].Value); short maxLevel = short.Parse(skill.Attributes["maxLevel"]?.Value ?? "1"); skillList.Find(x => x.SkillId == id).Job = jobCode; skillList.Find(x => x.SkillId == id).MaxLevel = maxLevel; // If it has subSkill, add as well. if (skill.Attributes["sub"] == null) { continue; } int[] sub = skill.Attributes["sub"].Value.SplitAndParseToInt(',').ToArray(); skillList.Find(x => x.SkillId == id).SubSkills = sub; foreach (int subSkillId in sub) { SkillMetadata subSkill = skillList.FirstOrDefault(x => x.SkillId == subSkillId); if (subSkill is null) { continue; } subSkill.Job = jobCode; } } } } } // Parsing Additional Data foreach (PackFileEntry entry in Resources.XmlReader.Files) { if (!entry.Name.StartsWith("additionaleffect")) { continue; } XmlDocument document = Resources.XmlReader.GetXmlDocument(entry); XmlNodeList levelNodes = document.SelectNodes("/ms2/level"); int skillId = int.Parse(Path.GetFileNameWithoutExtension(entry.Name)); SkillMetadata skill = skillList.FirstOrDefault(x => x.SkillId == skillId); if (skill is null) { continue; } foreach (XmlNode levelNode in levelNodes) { int currentLevel = int.Parse(levelNode.SelectSingleNode("BasicProperty").Attributes["level"]?.Value ?? "0"); SkillLevel skillLevel = skill.SkillLevels.FirstOrDefault(x => x.Level == currentLevel); if (skillLevel is null) { continue; } skillLevel.SkillAdditionalData = ParseSkillData(levelNode); } } return(skillList); }
public bool LoadFromLua(LuaTable skill) { if (skill["DisType"] == null || skill["DisType"].GetType().ToString() != "System.Double") { Debug.LogError("DisType of Skill is invalid." + skill["DisType"]); return(false); } int type = Convert.ToInt32((double)skill["DisType"]); m_disType = (SkillDisType)type; if (skill["RangeType"] == null || skill["RangeType"].GetType().ToString() != "System.Double") { Debug.LogError("RangeType of Skill is invalid."); return(false); } type = Convert.ToInt32((double)skill["RangeType"]); m_rangeType = (SkillRangeType)type; if (skill["HurtType"] == null || skill["HurtType"].GetType().ToString() != "System.Double") { Debug.LogError("HurtType of Skill is invalid."); return(false); } type = Convert.ToInt32((double)skill["HurtType"]); m_hurtType = (SkillHurtType)type; if (skill["CDGroup"] == null || skill["CDGroup"].GetType().ToString() != "System.Double") { Debug.LogError("CDGroup of Skill is invalid."); return(false); } int id = Convert.ToInt32((double)skill["CDGroup"]); m_nCDGroup = id; if (skill["Damage"] == null || skill["Damage"].GetType().ToString() != "System.Double") { Debug.LogError("Damage of Skill is invalid."); return(false); } int damage = Convert.ToInt32((double)skill["Damage"]); m_nDamage = damage; if (skill["CostSP"] == null || skill["CostSP"].GetType().ToString() != "System.Double") { Debug.LogError("CostSP of Skill is invalid."); return(false); } int costSP = Convert.ToInt32((double)skill["CostSP"]); m_nCostSP = costSP; string animName1 = (string)skill["animation"]; if (animName1 != null && animName1 != "") { m_hash1 = Animator.StringToHash(animName1); } string animName2 = (string)skill["animation"]; if (animName2 != null && animName2 != "") { m_hash2 = Animator.StringToHash(animName2); } if (skill["CheckType"] == null || skill["CheckType"].GetType().ToString() != "System.Double") { Debug.LogError("CheckType of Skill is invalid."); return(false); } type = Convert.ToInt32((double)skill["CheckType"]); m_checkType = (CStateType)type; if (skill["AfterType"] == null || skill["AfterType"].GetType().ToString() != "System.Double") { Debug.LogError("AfterType of Skill is invalid."); return(false); } type = Convert.ToInt32((double)skill["AfterType"]); m_afterType = (CStateType)type; m_timer = m_life.GetCDTimerById(m_nCDGroup); if (m_timer == null) { Debug.LogError("Failed to LoadFromFile. CDTimer doesn't exist."); return(false); } return(true); }