/// <summary> /// Find all cells where a spell can be cast, from casterCell position /// Todo : takes traps into account /// </summary> /// <param name="caster"></param> /// <param name="casterCell"></param> /// <param name="actors"></param> /// <returns></returns> private IEnumerable <Cell> GetCellsAtSpellRange(PlayedFighter caster, Cell casterCell, IEnumerable <Fighter> actors) { int maxRange = caster.GetRealSpellRange(LevelTemplate); if (LevelTemplate.castTestLos) { _losMap.UpdateTargetCell(casterCell, true, false); } Func <Cell, bool> filter = cell => { if (LevelTemplate.castInLine || LevelTemplate.castInDiagonal) { if (!(LevelTemplate.castInLine && (casterCell.X == cell.X || casterCell.Y == cell.Y) || LevelTemplate.castInDiagonal && (Math.Abs(casterCell.X - cell.X) == Math.Abs(casterCell.Y - cell.Y)))) { return(false); } } if (LevelTemplate.castTestLos) { if (!_losMap[cell]) { return(false); } } if (LevelTemplate.needFreeCell) { if (!(cell.Walkable && !cell.NonWalkableDuringFight && cell != casterCell && !actors.Any(actor => actor.Cell != null && actor.Cell.Id == cell.Id))) { return(false); } } if (LevelTemplate.needTakenCell) { if (!(cell == casterCell || actors.Any(actor => actor.Cell != null && actor.Cell.Id == cell.Id))) { return(false); } } if (LevelTemplate.needFreeTrapCell) { return(false); // Do not play traps yet } int?targetId = caster.Fight.GetActorsOnCell(cell).Select(actor => (int?)actor.Id).FirstOrDefault(); if (targetId != null) { if (!IsAvailable(targetId)) { return(false); } } return(true); }; return(casterCell.GetAllCellsInRange((int)LevelTemplate.minRange, maxRange, false, filter)); }
public SpellImpact GetSpellDamages(PlayedFighter caster, Fighter target /*, Spell.SpellCategory categories*/) { SpellImpact damages = null; foreach (var effect in LevelTemplate.effects) { CumulEffects(effect, ref damages, caster, target /*, categories*/, this); } return(damages); }
private void OnFightJoined(PlayedCharacter character, Fight fight) { if (m_checkTimer != null) { m_checkTimer.Dispose(); } m_character = character.Fighter; character.Fighter.TurnStarted += OnTurnStarted; fight.StateChanged += OnStateChanged; }
public void Update(FighterStatsListMessage message) { PlayedFighter?.Update(message.Stats); if (PlayedFighter != null) { _account.Game.Character.Stats.MaxLifePoints = (uint)PlayedFighter?.MaxLifePoints; _account.Game.Character.Stats.LifePoints = (uint)PlayedFighter?.LifePoints; } FighterStatsUpdated?.Invoke(); }
public void EnterFight(GameFightJoinMessage message) { if (IsFighting()) { throw new Exception("Player already fighting !"); } var fight = new Fight(message, Map); Fighter = new PlayedFighter(this, fight); NotifyFightJoined(Fight); }
public DamageType GetTotalDamageOnAllEnemies(PlayedFighter caster) { DamageType damages = 0; foreach (Fighter enemy in caster.Team == null ? caster.Fight.AliveActors : caster.GetOpposedTeam().FightersAlive) { SpellImpact impact = null; foreach (var effect in LevelTemplate.effects) { damages += CumulEffects(effect, ref impact, caster, enemy /*, Spell.SpellCategory.Damages*/, this); } } return(damages); }
private static int GetSafetotal(PlayedFighter caster, Stats.PlayerField field) { if (caster == null) { return(0); } StatsRow row = caster.PCStats[field]; if (row == null) { return(0); } return(row.Total); }
public static void HandleGameActionFightNoSpellCastMessage(Bot bot, GameActionFightNoSpellCastMessage message) { if (bot == null || bot.Character == null || bot.Character.Fight == null) { logger.Error("Fight is not properly initialized."); return; // Can't handle the message } PlayedFighter fighter = bot.Character.Fighter; if (fighter != null) { fighter.Update(message); } }
public void EnterFight(GameFightJoinMessage message) { if (IsFighting()) { throw new Exception("Player already fighting !"); } var fight = new Fight(message, Map); Fighter = new PlayedFighter(this, fight); Context = Fight; Bot.AddFrame(new FightHandler(Bot)); OnFightJoined(Fight); }
public override void OnDetached() { if (Bot.Character != null) { Bot.Character.FightJoined -= OnFightJoined; Bot.Character.FightLeft -= OnFightLeft; Bot.Character.MapJoined -= OnMapJoined; } if (m_character != null) { m_character.TurnStarted -= OnTurnStarted; m_character.Fight.StateChanged -= OnStateChanged; m_character = null; } }
public void LeaveFight() { if (!IsFighting()) { logger.Error("Cannot leave the fight : the character is not in fight"); return; } if (Fight.Phase != FightPhase.Ended) { // todo : have to leave fight } NotifyFightLeft(Fight); Fighter = null; }
/// <summary> /// Warning : this method says if this affect may affect this target. But NOT if the target can be the target of the spell /// (cf ƩpƩe divine, where you cast it on yourself despite it effects only enemies around you) /// </summary> /// <param name="spellEffect"></param> /// <param name="spell"></param> /// <param name="caster"></param> /// <param name="target"></param> /// <returns></returns> public static bool canAffectTarget(EffectDice spellEffect, Spell spell, PlayedFighter caster, Fighter target) { if (spell.LevelTemplate.spellBreed == (uint)BreedEnum.Eniripsa && spell.Categories == Spell.SpellCategory.Healing && caster.HasState(76)) { return(false); } //if (!spell.IsAvailable(target == null ? null : (int?)target.Id)) return false; //BiM.Behaviors.Game.Spells.Spell.SpellCategory categories = 0; uint surface = spellEffect.Surface; //categories = BiM.Behaviors.Game.Spells.Spell.GetEffectCategories((uint)spellEffect.Id, spell.LevelTemplate.id); if (spellEffect.Targets == SpellTargetType.NONE) { spellEffect.Targets = SpellTargetType.ALL; } //if (target == null) return !spell.LevelTemplate.needTakenCell; if (caster == target) // Self { return((spellEffect.Targets & (SpellTargetType.ONLY_SELF | SpellTargetType.SELF)) != 0); } if (caster.Team == target.Team) // Ally { if (target.Summoned) { return((spellEffect.Targets & SpellTargetType.ALLIES_SUMMON) != 0); } else { return((spellEffect.Targets & SpellTargetType.ALLIES_NON_SUMMON) != 0); } } if (target.Summoned) // Enemies { return((spellEffect.Targets & SpellTargetType.ENEMIES_SUMMON) != 0); } else { return((spellEffect.Targets & SpellTargetType.ENEMIES_NON_SUMMON) != 0); } }
public void DispelTarget(int targetId) { Fighter fighter = GetFighter(targetId); foreach (var effectList in Effects) { foreach (var effectT in effectList.Value.ToArray()) { if (effectT.Item1.targetId == targetId && effectT.Item1.dispelable != 2) { if (fighter is PlayedFighter) { PlayedFighter playedFighter = fighter as PlayedFighter; Spell spell = new Spell(new SpellItem(0, effectT.Item1.spellId, 1)); playedFighter.Character.SendInformation("Effect {0} from spell {1} dispeled (dispelable : {2})", (EffectsEnum)effectT.Item1.TypeId, spell.Name, effectT.Item1.dispelable); } Effects[effectList.Key].Remove(effectT); } } } }
/*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); }
public void Update(GameActionFightDispellableEffectMessage message) { if (message.Effect is FightTemporaryBoostStateEffect ftbse) { if (ftbse.TargetId == PlayedFighter?.ContextualId) { if (_effectsDurations.ContainsKey(ftbse.StateId)) { _effectsDurations.Remove(ftbse.StateId); } //_account.Logger.LogWarning("", $"Added state{ftbse.StateId} for {ftbse.TurnDuration} turns."); _effectsDurations.Add(ftbse.StateId, ftbse.TurnDuration); } } else if (message.Effect is FightTemporaryBoostEffect ftbe) { if (ftbe.TargetId == PlayedFighter?.ContextualId) { PlayedFighter.Update(message.ActionId, ftbe); } } }
// 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); }
// 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); }
private void OnFightLeft(PlayedCharacter character, Fight fight) { m_character = null; character.Fighter.TurnStarted -= OnTurnStarted; fight.StateChanged -= OnStateChanged; }
/// <summary> /// Reset old PathFinding path from the cells. /// </summary> public void ClearLogic(short[] startingCells, short[] exitCells) { // Reset some information about the cells. foreach (CellInfo cell in _cells) { if (cell != null) { cell.DistanceSteps = CellInfo.DEFAULT_DISTANCE; cell.IsInPath = cell.IsCloseToEnemy = false; cell.IsOpen = null; } } PathResult = new List <short>(); StartingCell = CellInfo.CELL_ERROR; ExitCell = CellInfo.CELL_ERROR; if (_isCautious || _isInFight) { // Find cells of all enemies on the map IEnumerable <Cell> enemyCells; if (_isInFight) { PlayedFighter player = _map.Actors.OfType <PlayedFighter>().FirstOrDefault(); if (player != null) { enemyCells = player.GetOpposedTeam().FightersAlive.Select(fighter => fighter.Cell); } else { enemyCells = null; } } else { enemyCells = _map.Actors.OfType <GroupMonster>().Select(fighter => fighter.Cell); } // Remove (/starting and) exit cells from those if (enemyCells != null && startingCells != null) { //if (startingCells.Length > 0) // enemyCells = enemyCells.Except(startingCells.Select(cellid => _cells[cellid].Cell)); if (exitCells != null && exitCells.Length > 0) { enemyCells = enemyCells.Except(exitCells.Select(cellid => _cells[cellid].Cell)); } // Then for each remainding cell, set all surrouding cells as "isCloseToEnemy" PlayedFighter playedFighter = null; if (_map is Fight) { playedFighter = (_map as Fight).CurrentPlayer as PlayedFighter; //if (playedFighter != null) // playedFighter.Character.ResetCellsHighlight(); } foreach (var cellid in enemyCells.Where(cell => cell != null).SelectMany(cell => cell.GetAdjacentCells()).Select(cell => cell.Id)) { if (_cells[cellid] != null) { //if (playedFighter != null) // playedFighter.Character.HighlightCell(cellid, System.Drawing.Color.Black); _cells[cellid].IsCloseToEnemy = true; } } } } }
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); }
/// <summary> /// Add spellImpact for a given effect, taking into account caster bonus and target resistance. /// </summary> /// <param name="effect"></param> /// <param name="spellImpact"></param> /// <param name="caster"></param> /// <param name="target"></param> /// <returns></returns> public static DamageType CumulEffects(EffectInstanceDice effect, ref SpellImpact spellImpact, PlayedFighter caster, Fighter target /*, Spell.SpellCategory Categories*/, Spell spell) { bool isFriend = caster.Team.Id == target.Team.Id; SpellImpact result = new SpellImpact(); SpellTargetType targetType = (SpellTargetType)effect.targetId; //if ((targetType & SpellTargetType.ENEMIES) == 0) return spellImpact; // No enemy can be targeted SpellCategory category = GetEffectCategories(effect.effectId, spell.LevelTemplate.id) /* & Categories*/; if (category == 0) { return(0); // No category selected in this spell } if (spell.Template.id == 0) // Weapon => ignore non heal or damage effects { if ((category & (SpellCategory.Damages | SpellCategory.Healing)) == 0) { return(0); } } double chanceToHappen = 1.0; // // When chances to happen is under 100%, then we reduce spellImpact accordingly, for simplicity, but after having apply damage bonus & reduction. // So average damage should remain exact even if Min and Max are not. if (effect.random > 0) { chanceToHappen = effect.random / 100.0; } if (target.Summoned && (caster.Breed.Id != (int)BreedEnum.Osamodas || target.Team.Id != caster.Team.Id)) { chanceToHappen /= 2; // It's much better to hit non-summoned foes => effect on summons (except allies summon for Osa) is divided by 2. } SpellException spellException = null; if (spellExceptions.ContainsKey(spell.LevelTemplate.id)) { spellException = spellExceptions[spell.LevelTemplate.id]; } if ((category & SpellCategory.DamagesNeutral) > 0) { AdjustDamage(result, spellException != null ? spellException.MinNeutral : effect.diceNum, spellException != null ? spellException.MaxNeutral : effect.diceSide, SpellCategory.DamagesNeutral, chanceToHappen, GetSafetotal(caster, Stats.PlayerField.NeutralDamageBonus) + GetSafetotal(caster, Stats.PlayerField.DamageBonus) + GetSafetotal(caster, Stats.PlayerField.PhysicalDamage), GetSafetotal(caster, Stats.PlayerField.DamageBonusPercent) + GetSafetotal(caster, Stats.PlayerField.Strength), target == null ? 0 : target.Stats.NeutralElementReduction, target == null ? 0 : target.Stats.NeutralResistPercent, isFriend); } if ((category & SpellCategory.DamagesFire) > 0) { AdjustDamage(result, spellException != null ? spellException.MinFire : effect.diceNum, spellException != null ? spellException.MaxFire : effect.diceSide, SpellCategory.DamagesFire, chanceToHappen, GetSafetotal(caster, Stats.PlayerField.FireDamageBonus) + GetSafetotal(caster, Stats.PlayerField.DamageBonus) + GetSafetotal(caster, Stats.PlayerField.MagicDamage), GetSafetotal(caster, Stats.PlayerField.DamageBonusPercent) + GetSafetotal(caster, Stats.PlayerField.Intelligence), target == null ? 0 : target.Stats.FireElementReduction, target == null ? 0 : target.Stats.FireResistPercent, isFriend); } if ((category & SpellCategory.DamagesAir) > 0) { AdjustDamage(result, spellException != null ? spellException.MinAir : effect.diceNum, spellException != null ? spellException.MaxAir : effect.diceSide, SpellCategory.DamagesAir, chanceToHappen, GetSafetotal(caster, Stats.PlayerField.AirDamageBonus) + GetSafetotal(caster, Stats.PlayerField.DamageBonus) + GetSafetotal(caster, Stats.PlayerField.MagicDamage), GetSafetotal(caster, Stats.PlayerField.DamageBonusPercent) + GetSafetotal(caster, Stats.PlayerField.Agility), target == null ? 0 : target.Stats.AirElementReduction, target == null ? 0 : target.Stats.AirResistPercent, isFriend); } if ((category & SpellCategory.DamagesWater) > 0) { AdjustDamage(result, spellException != null ? spellException.MinWater : effect.diceNum, spellException != null ? spellException.MaxWater : effect.diceSide, SpellCategory.DamagesWater, chanceToHappen, GetSafetotal(caster, Stats.PlayerField.WaterDamageBonus) + GetSafetotal(caster, Stats.PlayerField.DamageBonus) + GetSafetotal(caster, Stats.PlayerField.MagicDamage), GetSafetotal(caster, Stats.PlayerField.DamageBonusPercent) + GetSafetotal(caster, Stats.PlayerField.Chance), target == null ? 0 : target.Stats.WaterElementReduction, target == null ? 0 : target.Stats.WaterResistPercent, isFriend); } if ((category & SpellCategory.DamagesEarth) > 0) { AdjustDamage(result, spellException != null ? spellException.MinEarth : effect.diceNum, spellException != null ? spellException.MaxEarth : effect.diceSide, SpellCategory.DamagesEarth, chanceToHappen, GetSafetotal(caster, Stats.PlayerField.EarthDamageBonus) + GetSafetotal(caster, Stats.PlayerField.DamageBonus) + GetSafetotal(caster, Stats.PlayerField.MagicDamage), GetSafetotal(caster, Stats.PlayerField.DamageBonusPercent) + GetSafetotal(caster, Stats.PlayerField.Strength), target == null ? 0 : target.Stats.EarthElementReduction, target == null ? 0 : target.Stats.EarthResistPercent, isFriend); } if ((category & SpellCategory.Healing) > 0) { bool steal = (category & SpellCategory.Damages) > 0; if (steal) { target = caster; // Probably hp steal } uint hptoHeal = (uint)(Math.Max(0, target.Stats.MaxHealth - target.Stats.Health)); // Can't heal over max if (steal) { result.MinHeal = -Math.Min(hptoHeal, Math.Abs(result.MinDamage)); result.MaxHeal = -Math.Min(hptoHeal, Math.Abs(result.MaxDamage)); } else { bool skip = false; if (spell.Template.id == 140) // Mot de reconstruction => do only use it on purpose { if (hptoHeal < target.Stats.Health || hptoHeal < 400) { skip = true; // Only heal targets with under 50% of health and at least 400 hp to heal } } if (!skip && hptoHeal > 0) { AdjustDamage(result, Math.Min(effect.diceNum, hptoHeal), Math.Min(effect.diceSide, hptoHeal), SpellCategory.Healing, chanceToHappen, GetSafetotal(caster, Stats.PlayerField.HealBonus), GetSafetotal(caster, Stats.PlayerField.Intelligence), 0, 0, isFriend); if (result.Heal > hptoHeal) { if (isFriend) { result.MinHeal = result.MaxHeal = -hptoHeal; } else { result.MinHeal = result.MaxHeal = hptoHeal; } } } } } if ((category & SpellCategory.Buff) > 0) { if (isFriend) { result.Boost -= spell.Level * chanceToHappen; } else { result.Boost += spell.Level * chanceToHappen; } } if ((category & SpellCategory.Curse) > 0) { DamageType ratio = spell.Level * chanceToHappen; if (effect.effectId == (int)EffectsEnum.Effect_SkipTurn) // Let say this effect counts as 2 damage per level of the target { ratio = target.Level * 2 * chanceToHappen; } if (isFriend) { result.Curse -= 2 * ratio; } else { result.Curse += ratio; } } if (isFriend) { result.Add(result); // amplify (double) effects on friends. } if (!isFriend && ((category & SpellCategory.Damages) > 0) && result.MinDamage > target.Stats.Health) // Enough damage to kill the target => affect an arbitrary 50% of max heal (with at least current health), so strong spells are not favored anymore. { double ratio = Math.Max(target.Stats.MaxHealth / 2, target.Stats.Health) / result.MinDamage; result.Multiply(ratio); } if (spell.Template.id == 114) // Rekop { if (target.Stats.Health < 1000) { result.Multiply(0.1); } else if (target.Stats.Health < 2000) { result.Multiply(0.5); } } // Damage reflection if (((category & SpellCategory.Damages) > 0) && result.Damage > 0 && !isFriend) { DamageType reflected = spell.GetDamageReflection(target); if (reflected > 0) { if (reflected >= spellImpact.Damage) { return(0); // Reflect all damages } result.MinHeal += reflected * 2; result.MaxHeal += reflected * 2; } } if (spell.Template.id == 0 && (category & SpellCategory.Damages) > 0) // Weapon => consider effect of "maƮtrise" { Weapon weapon = caster.Character.Inventory.GetEquippedWeapon(); if (weapon != null) { foreach (var boost in caster.GetBoostWeaponDamagesEffects()) { if (boost.weaponTypeId == weapon.typeId) { result.Multiply(1.0 + boost.delta / 100.0); } } } } if (spellImpact != null) { spellImpact.Add(result); } else { spellImpact = result; } return(result.Damage); }