public SpellTarget FindBestUsage(PlayedFighter pc, Spell.SpellCategory category, bool withWeapon, IEnumerable <Cell> possiblePlacement = null) { SpellTarget spellTarget = new SpellTarget(); if (pc.PCStats.CurrentAP <= 0) { return(spellTarget); } Stopwatch timer = new Stopwatch(); timer.Start(); PathFinder pathFinder = new PathFinder(pc.Fight, true); IEnumerable <Cell> sourceCells = possiblePlacement == null?pc.GetPossibleMoves(true, true, pathFinder) : possiblePlacement; IEnumerable <Fighter> actorsWithoutPC = pc.Fight.AliveActors.Where(actor => actor != pc); List <Spell> spells = m_spells.ToList(); if (withWeapon && ((category & (Spell.SpellCategory.Damages | Spell.SpellCategory.Healing)) != 0)) { spells.Add(WeaponSpellLike()); } //logger.Debug("***FindBestUsage {0}, {1} spells in book. {2} PA. {3}/{4} HP ***", category, spells.Count, pc.PCStats.CurrentAP, pc.PCStats.Health, pc.PCStats.MaxHealth); Object thisLock = new Object(); //foreach(Spell spell in spells) int NbSpellsChecked = 0; Parallel.ForEach(spells, spell => { NbSpellsChecked++; if (spell != null && !IgnoredSpells.Contains(spell.Template.id)) { int spellId = spell.Template.id; if (spell.IsAvailable(null) && ((spellId != 0 && pc.CanCastSpells) || (spellId == 0 && pc.CanFight)) && spell.LevelTemplate.apCost <= pc.Stats.CurrentAP && pc.CanCastSpell(spellId)) { if ((spell.Categories & category) > 0) { SpellTarget lastSpellTarget = spell.FindBestTarget(pc, sourceCells, actorsWithoutPC, category); if (lastSpellTarget != null && lastSpellTarget.Efficiency > spellTarget.Efficiency) { //lock (thisLock) spellTarget = lastSpellTarget; } //if (lastSpellTarget != null) // logger.Debug("efficiency {0} = {1} ({2})", lastSpellTarget.Spell, lastSpellTarget.Efficiency, lastSpellTarget.Comment); //lock (thisLock) // logger.Debug("efficiency {0} = ???", spell); // else //lock (thisLock) // logger.Debug("efficiency {0} = {1} ({2})", lastSpellTarget.Spell, lastSpellTarget.Efficiency, lastSpellTarget.Comment); } } else { // lock (thisLock) // logger.Info("{0} skipped : available={1} ({6}), canUse={2}, ApCost={3}, CanCast({4})={5}", spell, spell.IsAvailable(null), ((spellId != 0 && pc.CanCastSpells) || (spellId == 0 && pc.CanFight)), spell.LevelTemplate.apCost <= pc.Stats.CurrentAP, spellId, pc.CanCastSpell(spellId), spell.AvailabilityExplainString(null)); } } } ); //Debug.Assert(NbSpellsChecked == spells.Count); timer.Stop(); spellTarget.TimeSpan = timer.Elapsed; //pc.Character.SendInformation("Spell {0} selected - efficientcy : {1} - comment = {2}", spellTarget.Spell, spellTarget.Efficiency, spellTarget.Comment); return(spellTarget); }
// Precise compute efficiency of a spell for a given category (beware to remove pc from friend and friendcells lists before calling this !) // Returns -1 if it would hit friends (todo : optimize if needed) public int GetFullAreaEffect(PlayedFighter pc, Cell source, Cell dest, IEnumerable <Fighter> actors, Spell.SpellCategory category, ref string comment) { SpellImpact spellImpact = new SpellImpact(); foreach (EffectInstanceDice effect in LevelTemplate.effects) { if ((Spell.GetEffectCategories((uint)effect.effectId, LevelTemplate.id) & category) > 0) { comment += " Effect " + (EffectsEnum)(effect.effectId) + " : "; EffectDice effectCl = new EffectDice(effect); IEnumerable <Cell> cells = effectCl.GetArea(source, dest); SpellTargetType targetType = (SpellTargetType)effect.targetId; int nbAffectedTargets = 0; if (EffectBase.canAffectTarget(effectCl, this, pc, pc) && cells.Contains(source)) { // Caster would be affected DamageType efficiency = Spell.CumulEffects(effect, ref spellImpact, pc, pc /*, category*/, this); if (efficiency != 0) { comment += string.Format("{0} on {1} => {2}, ", (decimal)efficiency, pc, (decimal)spellImpact.Damage); } nbAffectedTargets++; if (efficiency < 0) { return(0); // The caster would be affected by a bad spell => give up } } foreach (var actor in actors.Where(act => cells.Contains(act.Cell))) // All actors within the area covered by the spell { if (!EffectBase.canAffectTarget(effectCl, this, pc, actor)) { continue; // This actor is not affected by the spell } DamageType damage = Spell.CumulEffects(effect, ref spellImpact, pc, actor /*, category*/, this); if (damage != 0) { comment += string.Format(" - {0} on {1} => {2}", (decimal)damage, actor, (decimal)spellImpact.Damage); } nbAffectedTargets++; //if (damage > 0 && actor.Team == pc.Team) return 0; // Harmful on a friend => give up } //if (nbAffectedTargets > 1) //{ // pc.Character.SendWarning("Spell {0} : {1} targets affected for {2} damage - {3}", this, nbAffectedTargets, spellImpact.Damage, comment); //} } } if (Template.id == 139) // Mot d'altruisme : only use near end of fight or if lot of spellImpact to heal { int hpLeftOnFoes = actors.Where(actor => actor.Team.Id != pc.Team.Id).Sum(actor => actor.Stats.Health); comment += string.Format(" - special \"Mot d'altruisme\" processing : hpLeftOnFoes = {0}, efficiency = {1}", hpLeftOnFoes, (int)spellImpact.Damage); if (hpLeftOnFoes > 500) // Not the end of the fight { if (spellImpact.Damage < 300) { return(0); // Do not cast it if less than 300 hp of healing } else { return((int)spellImpact.Damage / 3); // Otherwise, far from the end of the fight, divide efficiency by 3 } } // if close to the end of the fight, then returns full result. } return((int)spellImpact.Damage); }
// Find the best cell to cast a given spell, retrieve damage done and best cell (todo : optimize if needed) public SpellTarget FindBestTarget(PlayedFighter pc, Cell source, IEnumerable <Cell> destCells, IEnumerable <Fighter> actors, Spell.SpellCategory category) { SpellTarget result = null; foreach (Cell dest in destCells) { string comment = string.Empty; int efficientcy = 0; if (areaDependsOnDirection || !efficiencyCache.TryGetValue(dest.Id, out efficientcy)) { efficientcy = GetFullAreaEffect(pc, source, dest, actors, category, ref comment); if (!areaDependsOnDirection) { efficiencyCache[dest.Id] = efficientcy; } } if (efficientcy > 0) { if (result == null) { result = new SpellTarget(efficientcy, source, dest, this); result.Comment = comment; } else if (efficientcy > result.Efficiency) { result.Efficiency = efficientcy; result.FromCell = source; result.TargetCell = dest; result.Spell = this; result.Comment = comment; } } } return(result); }
// Find where the PC should come to cast the spell, and the best target there (todo : optimize if needed) public SpellTarget FindBestTarget(PlayedFighter pc, IEnumerable <Cell> sourceCells, IEnumerable <Fighter> actors, Spell.SpellCategory category) { if (LevelTemplate.statesForbidden != null) { if (LevelTemplate.statesForbidden.Any(state => pc.HasState(state))) { logger.Debug("Spell {0} skipped : statesForbidden {1}", this, string.Join(",", LevelTemplate.statesForbidden)); pc.Character.SendWarning("Spell {0} skipped : statesForbidden {1}", this, string.Join(",", LevelTemplate.statesForbidden)); return(null); // Can't cast this : all the required states are not on the caster } } if (LevelTemplate.statesRequired != null) { if (!LevelTemplate.statesRequired.All(state => pc.HasState(state))) { logger.Debug("Spell {0} skipped : statesRequired {1}", this, string.Join(",", LevelTemplate.statesForbidden)); pc.Character.SendWarning("Spell {0} skipped : statesRequired {1}", this, string.Join(",", LevelTemplate.statesForbidden)); return(null); // Can't cast this : at least one required state is not on the caster } } if (IsMaitrise(null)) // If the spell is a maitrise, then ignore it if not of the proper type for equipped weapon. { Weapon weapon = pc.Character.Inventory.GetEquippedWeapon(); if (weapon == null) { return(null); } if (!IsMaitrise(weapon.typeId)) { return(null); } } _losMap = new LOSMap(pc.Fight); SpellTarget bestResult = null; #region optimisations IEnumerable <Fighter> enemies = pc.GetOpposedTeam().FightersAlive; IEnumerable <Fighter> friends = pc.Team.FightersAlive; bool goodSpell = (Categories & (SpellCategory.Buff | SpellCategory.Healing)) != 0; bool badSpell = (Categories & (SpellCategory.Damages | SpellCategory.Curse)) != 0; IEnumerable <Fighter> targets = null; int targetsCount = 0; if (goodSpell && badSpell) { goodSpell = badSpell = false; } else { targets = goodSpell ? friends : enemies; targetsCount = targets.Count(); } uint surface = this.GetArea(category); efficiencyCache = null; if (!areaDependsOnDirection) { efficiencyCache = new Dictionary <short, int>(); } #endregion if (surface == 1 && LevelTemplate.range == 0) // Hack fast Cure and protect self { var res = GetSpellDamages(pc, pc); if (res.Damage > 0) { bestResult = new SpellTarget(res.Damage, pc.Cell, pc.Cell, this); } } else { foreach (Cell source in sourceCells) { IEnumerable <Cell> destCells = GetCellsAtSpellRange(pc, source, actors); if (goodSpell || badSpell) { if (surface <= 1 && LevelTemplate.range > 0) { destCells = destCells.Intersect(targets.Select(fighter => fighter.Cell)); // for spells that have an area of effect of 1, just find enemies or friends as targets. No need to scan all the range. } } if (surface >= 560 && destCells.Count() > 1) // For spells that cover the full map, use only the first cell { destCells = destCells.Take(1); } SpellTarget newResult = FindBestTarget(pc, source, destCells, actors, category); if (newResult == null || newResult.Efficiency <= 0) { continue; } if (bestResult == null || bestResult.Efficiency < newResult.Efficiency) { bestResult = newResult; if (surface >= 560) { break; // if spell covers all map, and we have some hit, then no need to continue (first source cells are nearest) } if (targetsCount == 1 && surface == 1) { break; // only one target and 1 cell area spell => no need to loop further } } } } if (bestResult != null) { bestResult.Efficiency *= (pc.Stats.CurrentAP / LevelTemplate.apCost); bestResult.cast = false; } return(bestResult); }