/*public bool IsProperTarget(PlayedFighter caster, Fighter target, Spell spell) * { * // SpellTargetType * if (target == null) return spell.LevelTemplate.needFreeCell && spell.IsAvailable(null, null); * * foreach (var spellEffect in spell.GetEffects()) * if (EffectBase.canAffectTarget(spellEffect, spell, caster, target)) return true; * return false; * }*/ #endregion Availability management #region Spell selection /*public IEnumerable<Spell> GetOrderListOfSimpleBoostSpells(PlayedFighter caster, Fighter target, Spell.SpellCategory category) * { * return m_spells.Where(spell => (caster.Stats.CurrentAP >= spell.LevelTemplate.apCost) && spell.IsAvailable(caster.Id, category) && IsProperTarget(caster, target, spell)).OrderByDescending(spell => spell.Level).ThenByDescending(spell => spell.LevelTemplate.minPlayerLevel); * } * * public IEnumerable<Spell> GetOrderedAttackSpells(PlayedFighter caster, Fighter target, Spell.SpellCategory category = Spell.SpellCategory.Damages) * { * Debug.Assert(((category & Spell.SpellCategory.Damages) > 0), "category do not include Damage effects"); * return m_spells.Where(spell => * (caster.Stats.CurrentAP >= spell.LevelTemplate.apCost) && * spell.IsAvailable(target.Id, category) && * IsProperTarget(caster, target, spell)) * .OrderByDescending(spell => (int)(spell.GetSpellDamages(caster, target, Spell.SpellCategory.Damages).Damage) * (caster.Stats.CurrentAP / (int)spell.LevelTemplate.apCost)); * }*/ public SpellTarget FindBestUsage(PlayedFighter pc, List <int> spellIds, IEnumerable <Cell> possiblePlacement = null) { 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); SpellTarget spellTarget = new SpellTarget(); foreach (int spellId in spellIds) { Spell spell = m_spells.FirstOrDefault(sp => sp.Template.id == spellId); if (spell == null && spellId == 0) { spell = WeaponSpellLike(); } if (spell != null) { if (spell.IsAvailable(null) && ((spellId != 0 && pc.CanCastSpells) || (spellId == 0 && pc.CanFight)) && spell.LevelTemplate.apCost <= pc.Stats.CurrentAP && pc.CanCastSpell(spellId)) { if (spell != null && spell.IsAvailable(null) && spell.LevelTemplate.apCost <= pc.Stats.CurrentAP) { SpellTarget lastSpellTarget = spell.FindBestTarget(pc, sourceCells, actorsWithoutPC, Spell.SpellCategory.All); if (lastSpellTarget != null && lastSpellTarget.Efficiency > spellTarget.Efficiency) { spellTarget = lastSpellTarget; break; // Stop on the first spell with positive efficiency } } } } } timer.Stop(); spellTarget.TimeSpan = timer.Elapsed; return(spellTarget); }
// 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); }
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); }
// 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); }
// 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; }
// 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; }
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; }
/*public bool IsProperTarget(PlayedFighter caster, Fighter target, Spell spell) { // SpellTargetType if (target == null) return spell.LevelTemplate.needFreeCell && spell.IsAvailable(null, null); foreach (var spellEffect in spell.GetEffects()) if (EffectBase.canAffectTarget(spellEffect, spell, caster, target)) return true; return false; }*/ #endregion Availability management #region Spell selection /*public IEnumerable<Spell> GetOrderListOfSimpleBoostSpells(PlayedFighter caster, Fighter target, Spell.SpellCategory category) { return m_spells.Where(spell => (caster.Stats.CurrentAP >= spell.LevelTemplate.apCost) && spell.IsAvailable(caster.Id, category) && IsProperTarget(caster, target, spell)).OrderByDescending(spell => spell.Level).ThenByDescending(spell => spell.LevelTemplate.minPlayerLevel); } public IEnumerable<Spell> GetOrderedAttackSpells(PlayedFighter caster, Fighter target, Spell.SpellCategory category = Spell.SpellCategory.Damages) { Debug.Assert(((category & Spell.SpellCategory.Damages) > 0), "category do not include Damage effects"); return m_spells.Where(spell => (caster.Stats.CurrentAP >= spell.LevelTemplate.apCost) && spell.IsAvailable(target.Id, category) && IsProperTarget(caster, target, spell)) .OrderByDescending(spell => (int)(spell.GetSpellDamages(caster, target, Spell.SpellCategory.Damages).Damage) * (caster.Stats.CurrentAP / (int)spell.LevelTemplate.apCost)); }*/ public SpellTarget FindBestUsage(PlayedFighter pc, List<int> spellIds, IEnumerable<Cell> possiblePlacement = null) { 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); SpellTarget spellTarget = new SpellTarget(); foreach (int spellId in spellIds) { Spell spell = m_spells.FirstOrDefault(sp => sp.Template.id == spellId); if (spell == null && spellId == 0) spell = WeaponSpellLike(); if (spell != null) { if (spell.IsAvailable(null) && ((spellId != 0 && pc.CanCastSpells) || (spellId == 0 && pc.CanFight)) && spell.LevelTemplate.apCost <= pc.Stats.CurrentAP && pc.CanCastSpell(spellId)) if (spell != null && spell.IsAvailable(null) && spell.LevelTemplate.apCost <= pc.Stats.CurrentAP) { SpellTarget lastSpellTarget = spell.FindBestTarget(pc, sourceCells, actorsWithoutPC, Spell.SpellCategory.All); if (lastSpellTarget != null && lastSpellTarget.Efficiency > spellTarget.Efficiency) { spellTarget = lastSpellTarget; break; // Stop on the first spell with positive efficiency } } } } timer.Stop(); spellTarget.TimeSpan = timer.Elapsed; return spellTarget; }