예제 #1
0
        /// <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));
        }
예제 #2
0
        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);
        }
예제 #3
0
        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;
        }
예제 #4
0
        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();
        }
예제 #5
0
        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);
        }
예제 #6
0
        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);
        }
예제 #7
0
        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);
        }
예제 #8
0
        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);
            }
        }
예제 #9
0
        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);
        }
예제 #10
0
        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;
            }
        }
예제 #11
0
        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;
        }
예제 #12
0
        /// <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);
            }
        }
예제 #13
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);
                    }
                }
            }
        }
예제 #14
0
        /*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);
        }
예제 #15
0
        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);
                }
            }
        }
예제 #16
0
        // 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);
        }
예제 #17
0
        // 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);
        }
예제 #18
0
 private void OnFightLeft(PlayedCharacter character, Fight fight)
 {
     m_character = null;
     character.Fighter.TurnStarted -= OnTurnStarted;
     fight.StateChanged            -= OnStateChanged;
 }
예제 #19
0
        /// <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;
                        }
                    }
                }
            }
        }
예제 #20
0
        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);
        }
예제 #21
0
        // 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);
        }
예제 #22
0
        /// <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);
        }