static List <CardAIPlanData> RefineMultiCastPlan(CardAI ai, CardAIPlanData entryState, List <CardAIPlanData> plans) { NetBattlefield bf = entryState.bf; //get top plans and try to refine them int min = Mathf.Min(plans.Count, ai.refiningSize); for (int i = 0; i < min; i++) { for (int k = 0; k < ai.intensity; k++) { //prepare source for cast copying target enumerator so that it can be reused by the further casts CardAIPlanData source = entryState; NetSkill ns = bf.GetSkillByID(plans[i].GetTopCardPlay().skill); //cast form the source plan data so that it is not casted "after" action we want to reiterate to different targets CardAIPlanData d = CastSkillOnece(ai, ns, ns.GetOwner(bf), source, bf.CloneAndRemember(), plans[i].GetTopCardPlay().targetEnumerator); if (d.valid && plans[i].value < d.value) { d.SetEnumerator(plans[i].GetTopCardPlay().targetEnumerator); plans[i] = d; } } } //select best plan per skill return(plans.GetRange(0, min)); }
static public List <CardAIPlanData> ProduceCastPlans(CardAI ai, CardAIPlanData data, List <NetSkill> skills, int count) { List <CardAIPlanData> plans = new List <CardAIPlanData>(skills.Count); NetBattlefield bf = data.bf; for (int i = 0; i < skills.Count; i++) { NetSkill ns = skills[i]; IEnumerator <List <int> > targetEnumerator = null; for (int k = 0; k < count; k++) { CardAIPlanData d = CastSkillOnece(ai, ns, ns.GetOwner(bf), data, bf.CloneAndRemember(), targetEnumerator); if (d.valid) { if (targetEnumerator == null) { targetEnumerator = d.GetTopCardPlay().targetEnumerator; } plans.Add(d); } } } return(plans); }
static List <int> GetSecondaryPlayTargets(NetSkill ns, NetCard nc, NetBattlefield bf, List <int> primaryTargets, MHRandom r) { if (primaryTargets == null || primaryTargets.Count < 1) { return(null); } Subskill ss = ns.GetSubSkill(); if (ss.trigger.triggerGroup == ETriggerGroupType.DoAttack || ss.trigger.triggerGroup == ETriggerGroupType.DoAlternateAttack) { //there is no secondary targets of playing card to the battelfield unless its a spell return(null); } if (ss.targets.script2 == null || string.IsNullOrEmpty(ss.targets.script2.scriptName)) { return(null); } return(ns.FindSecondaryTargets(bf, -1, primaryTargets)); }
static NetBattlefield ProduceFakeBFIfNeeded(int playerID, NetBattlefield bf) { //fake battlefield will contain all friendly cards currently not on battlefield //positioned on free positions so that AI can see if it can produce //any targets. there is no simulation involved so fake state should not harm "thinking" //we will not need fake BF IF all owned cards are already on BF. HashSet <NetCard> ownCastedCards = bf.GetCardsOnBattelfield(playerID); NetCardPlayerData ncd = bf.GetPlayerByPlayerID(playerID); if (ncd == null || NetType.IsNullOrEmpty(ncd.HandCards)) { return(bf); } List <int> ownCardsAtHand = ncd.HandCards.value; List <NetCard> cardsToCast = new List <NetCard>(); //this is different set than just cards which were used, because card which casts summons or //does support will not be present on battlefield and still cost more than base AP foreach (var v in ownCardsAtHand) { NetCard nc = bf.GetCardByID(v); if (!ownCastedCards.Contains(nc)) { cardsToCast.Add(nc); } } if (cardsToCast.Count == 0) { return(bf); } //there are cards which should be represented on battle positions //which requires new BF //we would do simple but fake casts NetBattlefield bf2 = bf.CloneAndRemember(); int halfSize = bf2.BattlefieldSize / 2; int start = playerID == 0 ? 0 : halfSize; int cardListIndex = 0; for (int i = 0; i < halfSize; i++) { if (cardListIndex == cardsToCast.Count) { break; } int index = i + start; if (bf2.BattlefieldPositions.value[index] == 0) { bf2.BattlefieldPositions.value[index] = cardsToCast[cardListIndex].CardID; } } return(bf2); }
static CardAIPlanData ProduceSingleRefination(CardAI ai, CardAIPlanData entryState, CardAIPlanData plan) { NetBattlefield bf = entryState.bf; NetSkill ns = bf.GetSkillByID(plan.GetTopCardPlay().skill); //cast form the source plan data so that it is not casted "after" action we want to reiterate to different targets CardAIPlanData d = CastSkillOnece(ai, ns, ns.GetOwner(bf), entryState, bf.CloneAndRemember(), plan.GetTopCardPlay().targetEnumerator); if (d.valid && plan.value < d.value) { d.SetEnumerator(plan.GetTopCardPlay().targetEnumerator); } return(d); }
// Helper that applies normal damage and shield leech (affected by leechFactor). static public object Act_ShieldLeech_Helper(NetBattlefield bf, NetQueueItem q, FInt leechFactor) { NetSkill ns = q.GetNetSkill(bf); NetCard owner = bf.GetCardByID(ns.OwnerCardID); NetCard target = null; if (NetType.IsNullOrEmpty(q.Targets)) { return(null); } FInt damage = owner.GetSkillCastingStrength(ns); int shieldLeeched = 0; // Primary targets. foreach (var v in q.Targets.value) { target = bf.ConvertPositionIDToCard(v); if (target != null) { target.ReciveNormalDamage(damage, bf, q, v); shieldLeeched += (damage * leechFactor).ToInt(); } } // Secondary targets. if (!NetType.IsNullOrEmpty(q.SecondaryTargets)) { damage *= 0.5f; foreach (var v in q.SecondaryTargets.value) { target = bf.ConvertPositionIDToCard(v); if (target != null) { target.ReciveNormalDamage(damage, bf, q, v); shieldLeeched += (damage * leechFactor).ToInt(); } } } if (shieldLeeched > 0) { FInt currentShields = owner.GetCA_SHIELD(); owner.SetCA_SHIELD(currentShields + shieldLeeched); } return(null); }
// Ancient version of the built-in script Act_DrainHealthEssence, which leeches 80% instead. // Note: Built-in script Act_DrainHealthAncient does not appear to do this. static public object Act_LifeLeech_Ancient(NetBattlefield bf, NetQueueItem q, List <NetQueueItem> stack, List <NetQueueItem> previousItems, MHRandom random) { NetSkill ns = q.GetNetSkill(bf); NetCard owner = bf.GetCardByID(ns.OwnerCardID); NetCard target = null; if (NetType.IsNullOrEmpty(q.Targets)) { return(null); } FInt damage = owner.GetSkillCastingStrength(ns); int lifeLeeched = 0; // Primary targets. foreach (var v in q.Targets.value) { target = bf.ConvertPositionIDToCard(v); if (target != null) { target.ReciveNormalDamage(damage, bf, q, v); lifeLeeched += (damage * 0.8f).ToInt(); } } // Secondary targets. if (!NetType.IsNullOrEmpty(q.SecondaryTargets)) { damage *= 0.5f; foreach (var v in q.SecondaryTargets.value) { target = bf.ConvertPositionIDToCard(v); if (target != null) { target.ReciveNormalDamage(damage, bf, q, v); lifeLeeched += (damage * 0.8f).ToInt(); } } } if (lifeLeeched > 0) { owner.ReciveHealthNormal(lifeLeeched, bf, q, t); } return(null); }
// // static public CardAIPlanData _ProducePlan(int playerID, CardAIPlanData data, int avaliableAP, MHRandom r, int calculationIntensity = 1) // { // if (calculationIntensity < 1 ) // { // Debug.LogError("[ERROR]0 level for AI will nto produce any plans!, Increase calculation intensity to minimum 1!"); // return new CardAIPlanData(); // } // // NetBattlefield bf = data.bf; // // List<NetCard> cards = CardsWithinAPRange(playerID, bf, avaliableAP); // if (cards == null) return new CardAIPlanData(); // // List<NetSkill> skills = SelectSkillsToCast(cards); // if (skills == null || skills.Count == 0) return new CardAIPlanData(); // // bool friendlyTurn = playerID > 0; // // //Do single cast of all skills to build some expectations // CardAIPlanData[] plans = new CardAIPlanData[skills.Count]; // // for (int i=0; i<skills.Count; i++) // { // NetSkill ns = skills[i]; // CardAIPlanData d = CastSkillOnece(friendlyTurn, ns, ns.GetOwner(bf), data, bf.CloneAndRemember(), r); // if (d.valid) // { // plans[i] = d; // } // } // // //sort result based on their value // List<CardAIPlanData> plansL = new List<CardAIPlanData>(plans); // plansL.Sort(delegate (CardAIPlanData a, CardAIPlanData b) // { // return a.value.CompareTo(b.value); // }); // // //get top plans and try to refine them before selecting the best // int min = Mathf.Min(plansL.Count, calculationIntensity); // plansL = plansL.GetRange(0, min); // // int refiningLevel = 8; // plans = new CardAIPlanData[plansL.Count * refiningLevel]; // for(int i=0; i < plansL.Count; i++) // { // for(int k=0; k< refiningLevel; k++) // { // //plansL[i].validTargets // } // } // // // Start secondary plans from the selected plans // return new CardAIPlanData(); // } static public List <NetCard> CardsWithinAPRange(CardAI ai, NetBattlefield bf, int testedAPRange) { NetListInt ni = bf.GetPlayerByPlayerID(ai.playerID).HandCards; if (NetType.IsNullOrEmpty(ni)) { return(null); } List <NetCard> ncs = new List <NetCard>(); foreach (var v in ni.value) { NetCard nc = bf.GetCardByID(v); if (nc.GetCastingCost() <= testedAPRange) { ncs.Add(nc); } } return(ncs); }
static List <NetSkill> WillDoTwoCastSimulation(CardAI ai, CardAIPlanData data, List <NetSkill> consideredSkills) { List <NetSkill> supportSkills = new List <NetSkill>(); //if there is no skills which can be used there is no activity to consider if (consideredSkills == null || consideredSkills.Count < 1) { return(supportSkills); } NetBattlefield bf = null; foreach (var v in consideredSkills) { if (v.IsCastingSpell()) { if (bf == null) { bf = ProduceFakeBFIfNeeded(ai.playerID, data.bf); } List <int> pos = v.FindValidTargets(bf, -1); if (pos != null) { if (pos.FindIndex(o => bf.IsBattleSlot(o) && !bf.IsSlotFree(o) && bf.IsSameSideSlot(ai.playerID, o)) == -1) { //this skill does not have allied targets continue; } supportSkills.Add(v); } } } return(supportSkills); }
/// <summary> /// Target any enemy character /// </summary> /// <param name="bf"></param> /// <param name="ns"></param> /// <param name="bfPosition"></param> /// <returns></returns> static public List <int> Trg_RangeTargeting_Unblocked(NetBattlefield bf, NetSkill ns, int bfPosition) { if (bf == null || ns == null) { return(null); } NetCard nc = bf.GetCardByID(ns.OwnerCardID); if (nc == null) { return(null); } //this skill should work only when activated from battlefield if (!bf.IsBattleSlot(bfPosition)) { return(null); } return(SubskillScript.TrgU_FallbackTargets(bf, ns, nc)); }
static List <CardAIPlanData> ProduceRefinePlans(CardAI ai, CardAIPlanData entryState, List <CardAIPlanData> plans) { NetBattlefield bf = entryState.bf; //get top plans and try to refine them int min = Mathf.Min(plans.Count, ai.refiningSize); for (int i = 0; i < min; i++) { for (int k = 0; k < ai.intensity; k++) { CardAIPlanData d = ProduceSingleRefination(ai, entryState, plans[i]); if (d.valid && plans[i].value < d.value) { d.SetEnumerator(plans[i].GetTopCardPlay().targetEnumerator); plans[i] = d; } } } //select best plan per skill return(plans.GetRange(0, min)); }
static IList <int> GetPlayCardTargets(bool friendlyTurn, NetSkill ns, NetCard nc, NetBattlefield bf) { Subskill ss = ns.GetSubSkill(); if (ss.trigger.triggerGroup == ETriggerGroupType.DoAttack || ss.trigger.triggerGroup == ETriggerGroupType.DoAlternateAttack) { if (ss.trigger.requiredToBeInFrontline) { if (friendlyTurn) { return(bf.GetFreeFriendlyMeleeSlots(false)); } else { return(bf.GetFreeEnemyMeleeSlots(false)); } } else if (ss.trigger.requiredToBeInBackline) { if (friendlyTurn) { return(bf.GetFreeFriendlyRangedSlots(false)); } else { return(bf.GetFreeEnemyRangedSlots(false)); } } else { List <int> both; if (friendlyTurn) { IList <int> a = bf.GetFreeFriendlyMeleeSlots(false); IList <int> b = bf.GetFreeFriendlyRangedSlots(false); both = new List <int>(a.Count + b.Count); both.AddRange(a); both.AddRange(b); return(both); } else { IList <int> a = bf.GetFreeEnemyMeleeSlots(false); IList <int> b = bf.GetFreeEnemyRangedSlots(false); both = new List <int>(a.Count + b.Count); both.AddRange(a); both.AddRange(b); return(both); } } } else { return(ns.FindValidTargets(bf, -1)); } }
static CardAIPlanData CastSkillOnece(CardAI ai, NetSkill ns, NetCard nc, CardAIPlanData data, NetBattlefield bf, IEnumerator <List <int> > targetEnumerator) { //structure makes a new copy CardAIPlanData result = data; //using previous bf allows to utilize already cached data, as well as share caching with following casts. //later we will use new bf to ensure we do not override the same bf. NetBattlefield prevBf = data.bf; IList <int> targets = GetPlayCardTargets(ai.FriendlyTurn, ns, nc, prevBf); if (targets == null || targets.Count < 1) { return(new CardAIPlanData()); } if (targetEnumerator == null) { targetEnumerator = GetTargetEnumerator(ns, nc, targets, ai); if (targetEnumerator == null) { return(new CardAIPlanData()); } } targetEnumerator.MoveNext(); List <int> selectedTargets = targetEnumerator.Current; if (selectedTargets == null || selectedTargets.Count == 0) { return(new CardAIPlanData()); } List <int> secTargets = GetSecondaryPlayTargets(ns, nc, prevBf, selectedTargets, ai.r); FInt skillDelay = ns.GetSkillDelay(prevBf); int cost = prevBf.GetCardCastingCostFast(ns.GetOwner(prevBf).CardID); //add cost if something need to be casted next turn by AI var ncp = bf.GetPlayerByPlayerID(ai.playerID); if (cost > ncp.ApLeft) { skillDelay += 2; } //operate from now on its own bf result.bf = bf; NetQueueItem q; if (ns.IsCastingSpell()) { q = new NetQueueItem(bf, ns, new NetListInt(selectedTargets), new NetListInt(secTargets), skillDelay, -1); } else { q = new NetQueueItem(bf, ns, null, null, skillDelay, selectedTargets[0]); } //if ap cost were larger than current ap then we will result in negative ap. //because its just simulation we do not care for that now, as the sum of this and next turn ap //for estimating cost would be identical ncp.ApLeft -= nc.GetCastingCost(); bf.PlayCard(q, ai.r); float value = bf.GetValueByCloneSimulation(ai.iterations, ai.r); result.value = value; result.AddCardPlay(nc.CardID, ns.SkillInBattleID, q); result.SetEnumerator(targetEnumerator); result.valid = true; return(result); }
static public object Act_AddShieldingImproved( NetBattlefield bf, NetQueueItem q, List <NetQueueItem> stack, List <NetQueueItem> previousItems, MHRandom random) { NetSkill ns = q.GetNetSkill(bf); NetCard owner = bf.GetCardByID(ns.OwnerCardID); //skill needs target(-s) if (!NetTypeAtomic.IsValid(q.Targets)) { return(null); } FInt extra = FInt.ZERO; if (ns.MainAtt != null) { extra = owner.GetSkillCastingStrength(ns); } FInt value = ns.GetFloatAttribute("TAG-CA_SHIELD"); string sign = ns.GetStringAttribute("TAG-CA_SHIELD"); if (value == FInt.ZERO) { if (bf.ChallengeType == EChallengeType.TypePhysical) { value = ns.GetFloatAttribute("TAG-SHIELDING_PHYSICAL"); } else if (bf.ChallengeType == EChallengeType.TypeMental) { value = ns.GetFloatAttribute("TAG-SHIELDING_MENTAL"); } else if (bf.ChallengeType == EChallengeType.TypeSpirit) { value = ns.GetFloatAttribute("TAG-SHIELDING_SPIRIT"); } } if (string.IsNullOrEmpty(sign)) { if (bf.ChallengeType == EChallengeType.TypePhysical) { sign = ns.GetStringAttribute("TAG-SHIELDING_PHYSICAL"); } else if (bf.ChallengeType == EChallengeType.TypeMental) { sign = ns.GetStringAttribute("TAG-SHIELDING_MENTAL"); } else if (bf.ChallengeType == EChallengeType.TypeSpirit) { sign = ns.GetStringAttribute("TAG-SHIELDING_SPIRIT"); } } value += extra; value.CutToInt(); if (sign == "*") { value = value * 0.01f; } foreach (var v in q.Targets.value) { NetCard target = bf.ConvertPositionIDToCard(v); if (target == null) { continue; } FInt prev = target.GetCA_SHIELD(); if (sign == "+") { target.SetCA_SHIELD(value + prev); } else if (sign == "*") { target.SetCA_SHIELD(value * prev); } } if (NetType.IsNullOrEmpty(q.SecondaryTargets)) { return(null); } foreach (var v in q.SecondaryTargets.value) { NetCard target = bf.ConvertPositionIDToCard(v); if (target == null) { continue; } FInt prev = target.GetCA_SHIELD(); if (sign == "+") { target.SetCA_SHIELD(value + prev); } else if (sign == "*") { target.SetCA_SHIELD(value * prev); } } return(null); }
// TODO: I'm not sure if this needs a special import to reference the built-in script. static public object Act_LifeAndShieldLeech_Essence(NetBattlefield bf, NetQueueItem q, List <NetQueueItem> stack, List <NetQueueItem> previousItems, MHRandom random) { // Act_DrainHealthEssence(bf, q, stack, previousItems, random); return(Act_ShieldLeech_Essence(bf, q, stack, previousItems, random)); }
static public object Act_LifeAndShieldLeech_Ancient(NetBattlefield bf, NetQueueItem q, List <NetQueueItem> stack, List <NetQueueItem> previousItems, MHRandom random) { Act_LifeLeech_Ancient(bf, q, stack, previousItems, random); return(Act_ShieldLeech_Ancient(bf, q, stack, previousItems, random)); }
static public object Act_ShieldLeech_Ancient(NetBattlefield bf, NetQueueItem q, List <NetQueueItem> stack, List <NetQueueItem> previousItems, MHRandom random) { return(Act_ShieldLeech_Helper(bf, q, 1.0f)); }
// AI 2.0 #region Core planning static public CardAIPlanData ProducePlan(CardAI ai, CardAIPlanData data, bool allowTwoStages) { if (ai.intensity < 1) { Debug.LogError("[ERROR]0 level for AI will not produce any plans!, Increase calculation intensity to minimum 1!"); return(new CardAIPlanData()); } NetBattlefield bf = data.bf; int apNextTurn = bf.APNextTurn(ai.playerID); int leftAP = bf.LeftAPThisTurn(ai.playerID) + apNextTurn; float value = bf.GetValueByCloneSimulation(ai.iterations, ai.r); List <NetCard> cards = CardsWithinAPRange(ai, bf, ai.totalAP); if (cards == null) { return(new CardAIPlanData()); } List <NetSkill> supportSkills2 = new List <NetSkill>(); List <NetSkill> skills = SelectSkillsToCast(cards); if (skills == null || skills.Count == 0) { Debug.LogWarning("[!! AI] Avaliable skills to cast = 0"); return(new CardAIPlanData()); } List <NetSkill> skills2 = null; List <int> importantPos = null; #region detect potential two-cast suggestions if (allowTwoStages) { List <NetCard> cards2 = CardsWithinAPRange(ai, bf, ai.totalAP - 1); if (cards2 != null) { skills2 = SelectSkillsToCast(cards2); //two cast simulation would be considered only in case of at least two skills //being of value for that activity if (skills2 != null && skills2.Count > 1) { supportSkills2 = WillDoTwoCastSimulation(ai, data, skills2); if (supportSkills2.Count > 0) { importantPos = FindImportantPositions(ai, data, supportSkills2); } //expensive casts which exhausts available AP will be casted as single-casts //support skills will be casted as single-casts //cheap enough non-supports will be BASE for multi-casts //then supports skills will be casted on top skills2 = skills2.Except(supportSkills2).ToList(); skills = skills.Except(skills2).ToList(); } } } #endregion #region produce single-cast results List <CardAIPlanData> plans = ProduceCastPlans(ai, data, skills, 1); if (plans != null && plans.Count > 0) { plans.Sort(PlanSorter); plans = ProduceRefinePlans(ai, data, plans); } #endregion #region produce two-cast results. //TODO start by doing first layer of casts, with preferences if possible if (skills2 != null && skills2.Count > 0) { List <CardAIPlanData> plans2 = ProduceCastPlans(ai, data, skills2, 1); if (plans2 != null && plans2.Count > 0) { plans2.Sort(PlanSorter); plans2 = ProduceRefinePlans(ai, data, plans2); plans.AddRange(plans2); } //if supports are possible validate do another single turn pass for remaining skills. if (supportSkills2 != null && supportSkills2.Count > 0) { //register important positions if any to ensure they are preferred during position planning. ai.preferredStrategicPlaces = importantPos; //First Cast plans2 = ProduceCastPlans(ai, data, skills2, 1); if (plans2 != null && plans2.Count > 0) { plans2.Sort(PlanSorter); plans2 = ProduceRefinePlans(ai, data, plans2); //Second Cast (aka support cast) //Use first plan and select boosts which seems to be most efficient among available options. //this does one base cast and then one boost for each which account for: AxB List <CardAIPlanData> supportPlans = new List <CardAIPlanData>(); if (plans2 != null && plans2.Count > 0) { for (int i = 0; i < ai.supportCasts && i < plans2.Count; i++) { CardAIPlanData plan = plans2[i]; supportPlans.AddRange(ProduceCastPlans(ai, plan, supportSkills2, 1)); } } // select the best result of AxB to find the best theoretical result after two casts //do deeper planning for the base positioning and then cast support skills on the chosen plan to //acquire actual final value supportPlans.Sort(PlanSorter); if (supportPlans.Count > 0) { CardAIPlanData cpd = supportPlans[0]; CardAIPlanData planBase = plans2.Find(o => o.firstPlay.skill == cpd.firstPlay.skill); CardAIPlanData result = ProduceSingleRefination(ai, data, planBase); if (result.valid) { List <CardAIPlanData> results = ProduceCastPlans(ai, result, supportSkills2, 1); if (results != null && results.Count > 0) { results.Sort(PlanSorter); results = ProduceRefinePlans(ai, data, results); plans.AddRange(results); } } } } } } #endregion #region compare all results and select the best // all results are compared based on how much AP they use. // positive gain produced by all results are based on final result and final AP cost // GainedAdvantage / (2 + AP used) which results in: // 1/2 multiplier for 1 AP actions // 1/3 multiplier for 2 AP actions // 1/4 multiplier for 3+ AP actions // Single-casts would be considered as if they would use-up AvaliableAP amount // to ensure we do not consider them better even though they cannot provide any better results //if two-cast actions are available to some actions // aka: there is no point in having cheaper cost if AP cannot be later used. if (plans.Count == 0) { Debug.LogWarning("[!! AI] Available plans = 0"); return(new CardAIPlanData()); } CardAIPlanData selected = plans[0]; float selectedV = float.MinValue; for (int i = 0; i < plans.Count; i++) { CardAIPlanData cpd = plans[i]; int newLeftAP = cpd.bf.LeftAPThisTurn(ai.playerID) + apNextTurn; int apUsed = leftAP - newLeftAP; float v = cpd.value - value; v = v / (1f + apUsed); if (selectedV < v) { selected = plans[i]; selectedV = v; } } #endregion return(selected); }
/// <summary> /// /// </summary> /// <param name="bf">general battlefield information</param> /// <param name="q">queue element which is already executed which trigers this skill</param> /// <param name="stack">Items which are still in stack including "q", which most likely is first element</param> /// <param name="previousItems">Queue items which were already executed in order they were executed</param> /// <param name="random">random generator specific to this thread</param> /// <returns></returns> /// <summary> /// Basic damage /// </summary> static public object Act_Damage_Procedural( NetBattlefield bf, NetQueueItem q, List <NetQueueItem> stack, List <NetQueueItem> previousItems, MHRandom random) { NetSkill ns = q.GetNetSkill(bf); NetCard owner = bf.GetCardByID(ns.OwnerCardID); NetCard target = null; if (NetType.IsNullOrEmpty(q.Targets)) { return(null); } int flags = ns.Flags; bool essence = (flags & (int)EActivatorBlocks.Essence) > 0; bool ancient = (flags & (int)EActivatorBlocks.Ancient) > 0; bool gray = !ancient && !essence; FInt damage = GameplayUtils.GetDamageFor(ns, owner); // if ((flags & (int)SkillGenerator.EActivatorBlocks.Additive) > 0) // { // // if (essence) // { // //essence is estimated to be equal between additive and multiplicative at // // value of 10 (A x 0.2 + 8) ~ (A = 10) // damage = owner.GetSkillCastingAdditiveStrength(ns, SkillGenerator.ESSENCE_ADDITIVE_BASE); // } // else if(ancient) // { // //essence is estimated to be equal between additive and multiplicative at // // value of 15 (A x 0.2 + 12) ~ (A = 15) // damage = owner.GetSkillCastingAdditiveStrength(ns, SkillGenerator.ANCIENT_ADDITIVE_BASE); // } // else // { // //essence is estimated to be equal between additive and multiplicative at // // value of 6 (A x 0.2 + 5) ~ (A = 6) // damage = owner.GetSkillCastingAdditiveStrength(ns, SkillGenerator.GRAY_ADDITIVE_BASE); // } // } // else // { // damage = owner.GetSkillCastingStrength(ns); // } bool trueDamage = (flags & (int)EActivatorBlocks.TrueDamage) > 0; bool poisonDamage = (flags & (int)EActivatorBlocks.PoisonDamage) > 0; bool lifeLeech = (flags & (int)EActivatorBlocks.LifeLeech) > 0; bool shieldLeech = (flags & (int)EActivatorBlocks.ShieldLeech) > 0; bool additive = (flags & (int)EActivatorBlocks.Additive) > 0; int lifeLeeched = 0; int shieldLeeched = 0; #region Primary targets foreach (var v in q.Targets.value) { target = bf.ConvertPositionIDToCard(v); if (target != null) { FInt dmg = damage; if (poisonDamage && target.IsWounded()) { if (essence) { dmg *= 1.35f; } else { dmg *= 1.6f; } } if (trueDamage) { if (essence) { target.ReciveTrueDamageEssence(dmg, bf, q, v); } else //no test for ancient { target.ReciveTrueDamageAncient(dmg, bf, q, v); } } else { target.ReciveNormalDamage(dmg, bf, q, v); } if (lifeLeech) { if (essence) { lifeLeeched += (dmg * 0.4f).ToInt(); } else { lifeLeeched += (dmg * 0.8f).ToInt(); } } if (shieldLeech) { if (essence) { shieldLeeched += (dmg * 0.5f).ToInt(); } else { shieldLeeched += (dmg * 1.0f).ToInt(); } } } } #endregion #region Secondary targets ////do splash if needed if (!NetType.IsNullOrEmpty(q.SecondaryTargets)) { bool splashBonus = (flags & (int)EActivatorBlocks.AOE) > 0; if (gray || !splashBonus) { damage *= 0.5f; } else { //this is !gray && AOE if (essence) { damage *= 0.75f; } if (ancient) { damage *= 1f; } } //ancient damage is 100% splash foreach (var v in q.SecondaryTargets.value) { target = bf.ConvertPositionIDToCard(v); if (target != null) { FInt dmg = damage; if (poisonDamage && target.IsWounded()) { if (essence) { dmg *= 1.35f; } else { dmg *= 1.6f; } } if (trueDamage) { if (essence) { target.ReciveTrueDamageEssence(dmg, bf, q, v); } else //no test for ancient { target.ReciveTrueDamageAncient(dmg, bf, q, v); } } else { target.ReciveNormalDamage(dmg, bf, q, v); } if (lifeLeech) { if (essence) { lifeLeeched += (dmg * 0.4f).ToInt(); } else { lifeLeeched += (dmg * 0.8f).ToInt(); } } if (shieldLeech) { if (essence) { shieldLeeched += (dmg * 0.5f).ToInt(); } else { shieldLeeched += (dmg * 1.0f).ToInt(); } } } } } #endregion #region Leech if (lifeLeeched > 0) { FInt max = owner.GetCA_MAX_HEALTH(); FInt cur = owner.GetCA_HEALTH(); if (cur < max) { if (lifeLeeched > max - cur) { owner.SetCA_HEALTH(max); } else { owner.SetCA_HEALTH(cur + lifeLeeched); } } } if (shieldLeeched > 0) { FInt cur = owner.GetCA_SHIELD(); owner.SetCA_SHIELD(cur + shieldLeeched); } #endregion return(null); }