Пример #1
0
        /// <summary>
        /// Returns whether a status spell automatically hits a monster based purely on elemental weakness.
        /// </summary>
        /// <param name="spell"></param>
        /// <returns></returns>
        private bool StatusAutoHits(Spell spell)
        {
            // Create a monster that is weak to all elements
            Monster monster = new Monster();

            foreach (Element e in Enum.GetValues(typeof(Element)))
            {
                monster.Weaknesses.Add(e);
            }
            monster.Weaknesses.Remove(Element.None);  // Monsters cannot be weak to the fabled None element

            // Set spell's accuracy to 0, which normally would not allow it to hit, and make sure it hits on a single cast
            spell.Accuracy = 0;
            Assert.AreEqual(0, spell.Accuracy);

            if (String.Equals(spell.Effect.ToUpper(), "TEMPSTATUS"))
            {
                TempStatus tempStatus = (TempStatus)Enum.Parse(typeof(TempStatus), (string)spell.Status);
                Assert.IsFalse(monster.HasTempStatus(tempStatus));
                SpellManager.CastSpell(monster, monster, spell, 1);
                return(monster.HasTempStatus(tempStatus));
            }
            if (String.Equals(spell.Effect.ToUpper(), "PERMSTATUS"))
            {
                PermStatus permStatus = (PermStatus)Enum.Parse(typeof(PermStatus), (string)spell.Status);
                Assert.IsFalse(monster.HasPermStatus(permStatus));
                SpellManager.CastSpell(monster, monster, spell, 1);
                return(monster.HasPermStatus(permStatus));
            }

            Debug.WriteLine("StatusAutoHits spell fell through: " + spell.Name);
            return(false);
        }
Пример #2
0
 /// <summary>
 /// Helper. Add and remove a PermStatus from a monster.
 /// </summary>
 /// <param name="monster"></param>
 /// <param name="permStatus"></param>
 private void AddRemovePermStatus(Monster monster, PermStatus permStatus)
 {
     // Test that the status can be applied and removed
     monster.AddPermStatus(permStatus);
     Assert.IsTrue(monster.HasPermStatus(permStatus));
     monster.RemovePermStatus(permStatus);
     Assert.IsFalse(monster.HasPermStatus(permStatus));
 }
Пример #3
0
 /// <summary>
 /// Attempt to add a permanent status to the monster.
 /// If the status is KO, Stone, or Toad, the monster is instead killed.
 /// </summary>
 /// <returns>Whether or not the status was successfully added</returns>
 public virtual bool AddPermStatus(PermStatus permStatus)
 {
     if (permStatus == PermStatus.KO || permStatus == PermStatus.Stone || permStatus == PermStatus.Toad)
     {
         // TODO: Animation?
         Kill();
     }
     return(PermStatuses.Add(permStatus));
 }
Пример #4
0
        public override bool RemovePermStatus(PermStatus permStatus)
        {
            // Remove the appropriate StatusSprite
            if (statSprites.ContainsKey(permStatus))
            {
                statSprites[permStatus].Visible = false;
                statSprites.Remove(permStatus);
            }

            return(base.RemovePermStatus(permStatus));
        }
Пример #5
0
        public override bool AddPermStatus(PermStatus permStatus)
        {
            bool ret = base.AddPermStatus(permStatus);

            if (ret)
            {
                // If the status is not an instant kill, add the status sprite
                PermStatus[] killStatuses = new PermStatus[] { PermStatus.KO, PermStatus.Stone, PermStatus.Toad };
                if (!killStatuses.Contains(permStatus))
                {
                    StatusSprite statSpr = StatusSpriteManager.GetStatusSprite();
                    SetStatusSprite(statSpr, permStat: permStatus);
                }
            }


            return(ret);
        }
Пример #6
0
        /// <summary>
        /// Returns whether a status spell is resisted (ineffective) against a monster who resists its element
        /// </summary>
        /// <param name="spell"></param>
        /// <returns></returns>
        private bool StatusIsResisted(Spell spell)
        {
            // Create a monster that resists all elements
            Monster monster = new Monster();

            foreach (Element e in Enum.GetValues(typeof(Element)))
            {
                monster.Resistances.Add(e);
            }
            monster.Resistances.Remove(Element.None);  // Monsters cannot resist the fabled None element

            // Cast the spell a ton of times and make sure the monster isn't inflicted with the status
            if (String.Equals(spell.Effect.ToUpper(), "TEMPSTATUS"))
            {
                TempStatus tempStatus = (TempStatus)Enum.Parse(typeof(TempStatus), (string)spell.Status);
                Assert.IsFalse(monster.HasTempStatus(tempStatus));
                for (int i = 0; i < 10; i++)
                {
                    SpellManager.CastSpell(monster, monster, spell, 16);
                }
                return(!monster.HasTempStatus(tempStatus)); // Opposite. If inflicted, it didn't resist
            }
            if (String.Equals(spell.Effect.ToUpper(), "PERMSTATUS"))
            {
                PermStatus permStatus = (PermStatus)Enum.Parse(typeof(PermStatus), (string)spell.Status);
                Assert.IsFalse(monster.HasPermStatus(permStatus));
                for (int i = 0; i < 10; i++)
                {
                    SpellManager.CastSpell(monster, monster, spell, 16);
                }
                return(!monster.HasPermStatus(permStatus)); // Opposite. If inflicted, it didn't resist
            }

            Debug.WriteLine("StatusIsResisted spell fell through: " + spell.Name);
            return(false);
        }
Пример #7
0
        /// <summary>
        /// Cast a spell against a target. Applies the effects and returns the result.
        /// </summary>
        /// <param name="multiTarget">Whether the spell being cast is targeting multiple monsters. Halves accuracy and quarters power.</param>
        /// <returns>Result of casting the spell</returns>
        public static SpellResult CastSpell(Monster caster, Monster target, Spell spell, int level, bool multiTarget = false)
        {
            // Catch the errors first
            if (caster == null || target == null || spell == null)
            {
                throw new ArgumentNullException("Invalid parameter provided");
            }
            if (level < 0 || level > 16)
            {
                throw new ArgumentOutOfRangeException("Level out of range. Must be 0-16. Found: " + level);
            }

            // Helpers
            SpellResult failedResult = new SpellResult(new List <string> {
                FAILED_SPELL_MESSAGE
            });
            SpellResult statusSuccessResult = new SpellResult(new List <string> {
                spell.SuccessMessage
            });

            // If target's wall is high enough, the spell fails outright
            if (target.HasBuff(Buff.Wall) && level <= target.GetBuffStacks(Buff.Wall))
            {
                return(failedResult);
            }

            // Reduce accuracy and power if multi-targetting
            int adjustedAccuracy = spell.Accuracy;

            if (multiTarget)
            {
                adjustedAccuracy = adjustedAccuracy / 2;
            }

            int adjustedPower = spell.Power;

            if (multiTarget)
            {
                adjustedPower = adjustedPower / 4;
            }

            // Check for absorption. No effect except HP gain. All spells calculate damage.
            if (target.IsAbsorbentTo(spell.Element))
            {
                int totalHeal = GetDamage(adjustedAccuracy, level);
                target.HealHP(totalHeal);
                return(new SpellResult(new List <string> {
                    "HP up!"
                }));
            }

            //// Notes of below
            // Resistances: Success fail. Damage spells achieve minimum successes (level) and halve damage
            // Weaknesses: Success is automatic. Damage spells achieve perfect successes (lvl*2) and double damage
            // If resisted and weak, successes fail, damage is not halved or doubled
            // Strictly positive effects not subject to Magic Resistance. (e.g. CURE, BLINK, etc.)
            switch (spell.Effect)
            {
            case "Damage":
            case "Damage_2":
            case "Damage_3":
                if (target.IsResistantTo(spell.Element))
                {
                    return(HandleDamageSpellResult(target, GetDamage(adjustedPower, level) / 2));
                }

                if (target.IsWeakTo(spell.Element))
                {
                    return(HandleDamageSpellResult(target, GetDamage(adjustedPower, level * 2) * 2));
                }

                if (target.IsWeakTo(spell.Element) && target.IsResistantTo(spell.Element))
                {
                    // Damage as usual. Best guess at hits: level - blocks
                    int rwHits = level - target.RollMagicBlocks();
                    return(HandleDamageSpellResult(target, GetDamage(adjustedPower, rwHits)));
                }

                // Normal logic
                int hits = level + GetSpellSuccesses(level, adjustedAccuracy) - target.RollMagicBlocks();
                return(HandleDamageSpellResult(target, GetDamage(adjustedPower, hits)));

            case "Damage_Ultima":
                // TODO: Ultima damage bug
                break;

            case "Heal":
                // This is CURE, not HEAL. Damage undead, heal otherwise
                if (target.Families.Contains(MonsterFamily.Undead))
                {
                    int healHits = GetHitsAgainstTarget(level, adjustedAccuracy, target);
                    return(HandleDamageSpellResult(target, GetDamage(adjustedPower, healHits)));
                }

                int totalHeal = GetDamage(adjustedPower, GetSpellSuccesses(level, adjustedAccuracy));
                target.HealHP(totalHeal);

                return(new SpellResult(new List <string> {
                    "HP up!"
                }));

            case "Revive":
                // Chance to kill undead, otherwise fail. Fails if multi-targeting.
                if (multiTarget)
                {
                    return(failedResult);
                }

                if (target.Families.Contains(MonsterFamily.Undead))
                {
                    if (GetHitsAgainstTarget(level, adjustedAccuracy, target) > 0)
                    {
                        target.Kill();
                        return(new SpellResult(new List <string> {
                            target.Name + " fell", "Collapsed"
                        }));
                    }
                }

                break;

            case "Buff":
                // Autohits, ignores magic resistance rolls
                Buff buff     = (Buff)Enum.Parse(typeof(Buff), spell.Status);
                int  buffHits = GetSpellSuccesses(level, adjustedAccuracy);

                if (buffHits == 0)
                {
                    return(failedResult);
                }

                if (spell.Name == "SAFE")
                {
                    // NES bug. SAFE only works on the caster
                    if (!Globals.BUG_FIXES)
                    {
                        if (target == caster)
                        {
                            target.AddBuff(buff, buffHits);
                        }
                        else
                        {
                            return(failedResult);
                        }
                    }
                }

                target.AddBuff(buff, buffHits);

                // AURA and BARR have their unique results based on # of successes. Messages are displayed highest to lowest.
                switch (spell.Name)
                {
                case "AURA":
                    string[]      auraMessages = { "White", "Yellow", "Green", "Black", "Blue", "Orange", "Red" };
                    List <string> auraMsgList  = new List <string>();
                    buffHits--;

                    for (int i = buffHits > 6 ? 6 : buffHits; i >= 0; i--)
                    {
                        auraMsgList.Add(auraMessages[i] + " Aura");
                    }

                    return(new SpellResult(auraMsgList));

                case "BARR":
                    string[]      barrMessages = { "Ice", "Critical Hit!", "Poison", "Death", "Bolt", "Soul", "Fire" };
                    List <string> barrMsgList  = new List <string>();
                    buffHits--;

                    for (int i = buffHits > 6 ? 6 : buffHits; i >= 0; i--)
                    {
                        barrMsgList.Add(barrMessages[i] + " Df");
                    }

                    return(new SpellResult(barrMsgList));

                default:
                    return(statusSuccessResult);
                }

            case "Debuff":
                if (target.IsResistantTo(spell.Element))
                {
                    return(failedResult);
                }

                if (target.IsWeakTo(spell.Element))
                {
                    Debuff debuff = (Debuff)Enum.Parse(typeof(Debuff), spell.Status);
                    target.AddDebuff(debuff, level);
                    // TODO: DSPL has unique results messages based on # of successes
                    return(statusSuccessResult);
                }

                // Normal logic
                int debuffHits = GetHitsAgainstTarget(level, adjustedAccuracy, target);
                if (debuffHits > 0)
                {
                    Debuff debuff = (Debuff)Enum.Parse(typeof(Debuff), spell.Status);
                    target.AddDebuff(debuff, debuffHits);
                    return(statusSuccessResult);
                }

                break;

            case "TempStatus":
                if (target.IsResistantTo(spell.Element))
                {
                    return(failedResult);
                }

                if (target.IsWeakTo(spell.Element))
                {
                    TempStatus tempStatus = (TempStatus)Enum.Parse(typeof(TempStatus), spell.Status);
                    target.AddTempStatus(tempStatus);
                    return(statusSuccessResult);
                }

                // Normal logic. A single hit = success
                if (GetHitsAgainstTarget(level, adjustedAccuracy, target) > 0)
                {
                    TempStatus tempStatus = (TempStatus)Enum.Parse(typeof(TempStatus), spell.Status);
                    target.AddTempStatus(tempStatus);
                    return(statusSuccessResult);
                }

                break;

            case "PermStatus":
                if (target.IsResistantTo(spell.Element))
                {
                    return(failedResult);
                }

                if (target.IsWeakTo(spell.Element))
                {
                    PermStatus permStatus = (PermStatus)Enum.Parse(typeof(PermStatus), spell.Status);
                    target.AddPermStatus(permStatus);
                    return(statusSuccessResult);
                }

                // Normal logic. A single hit = success
                if (GetHitsAgainstTarget(level, adjustedAccuracy, target) > 0)
                {
                    PermStatus permStatus = (PermStatus)Enum.Parse(typeof(PermStatus), spell.Status);
                    target.AddPermStatus(permStatus);
                    return(statusSuccessResult);
                }

                break;

            case "CureTempStatus":
                // This is PEEP
                // 1 = Venom & Sleep, then one more per level, up to 5
                TempStatus[]  tempCureOrder = { TempStatus.Venom, TempStatus.Sleep, TempStatus.Mini, TempStatus.Mute, TempStatus.Paralysis, TempStatus.Confuse };
                String[]      peepMsgOrder  = { "Devenomed", "Scared", "Grew", "Can speak", "Can move", "Normal" };
                List <string> peepMsgs      = new List <string>();

                if (target.RemoveTempStatus(TempStatus.Venom))
                {
                    peepMsgs.Add("Devenomed");
                }

                for (int i = 1; i < level; i++)
                {
                    if (i >= tempCureOrder.Length)
                    {
                        break;
                    }

                    if (target.RemoveTempStatus(tempCureOrder[i]))
                    {
                        peepMsgs.Add(peepMsgOrder[i]);
                    }
                }

                // TODO: If nothing is cured, is ineffective returned?
                return(new SpellResult(peepMsgs));

            case "CurePermStatus":
                // This is HEAL. Cure everything up to and including level.
                PermStatus[]  permCureOrder = { PermStatus.Darkness, PermStatus.Poison, PermStatus.Curse, PermStatus.Amnesia, PermStatus.Toad, PermStatus.Stone, PermStatus.KO };
                String[]      healMsgOrder  = { "Can see", "Poison left", "Uncursed", "Remembers", "Regained form", "Normal body", "" };
                List <string> healMsgs      = new List <string>();

                for (int i = 0; i < level; i++)
                {
                    if (i >= permCureOrder.Length)
                    {
                        break;
                    }

                    if (target.RemovePermStatus(permCureOrder[i]))
                    {
                        healMsgs.Add(healMsgOrder[i]);
                    }
                }

                // TODO: If nothing is cured, is ineffective returned?
                return(new SpellResult(healMsgs));

            case "Dispel":
                //
                break;

            case "Special":
                // Spells that have unique effects: DRAN, ASPL, CHNG, ANTI, Blast
                switch (spell.Name.ToUpper())
                {
                case "DRAN":                            // Drain HP
                    // Each hit target loses 1/16 (round down) of Max HP. Caster gains that amount
                    int hpDrainAmt = target.HPMax / 16; // NOTE: Int division
                    int dranHits   = GetHitsAgainstTarget(level, adjustedAccuracy, target);
                    if (dranHits > 0)
                    {
                        int dranAmt = hpDrainAmt * dranHits;
                        // Opposite effect vs. Undead
                        if (target.Families.Contains(MonsterFamily.Undead))
                        {
                            caster.DamageHP(dranAmt);
                            // TODO: This is awkward here
                            if (caster.IsDead())
                            {
                                MonoMonster mm = (MonoMonster)caster;
                                mm.IsFading = true;
                            }
                            target.HealHP(dranAmt);
                        }
                        else
                        {
                            caster.HealHP(dranAmt);
                            target.DamageHP(dranAmt);
                        }
                        return(statusSuccessResult);
                    }

                    break;

                case "ASPL":                            // Drain MP
                    // Each hit target loses 1/16 (round down) of Max MP. Caster gains that amount
                    int mpDrainAmt = target.MPMax / 16; // NOTE: Int division
                    int asplHits   = GetHitsAgainstTarget(level, adjustedAccuracy, target);
                    if (asplHits > 0)
                    {
                        int asplAmt = mpDrainAmt * asplHits;
                        // Opposite effect vs. Undead
                        if (target.Families.Contains(MonsterFamily.Undead))
                        {
                            caster.DamageMP(asplAmt);
                            target.HealMP(asplAmt);
                        }
                        else
                        {
                            caster.HealMP(asplAmt);
                            target.DamageMP(asplAmt);
                        }
                        return(statusSuccessResult);
                    }

                    break;

                case "ANTI":         // Halve MP
                    if (target.IsResistantTo(spell.Element))
                    {
                        return(failedResult);
                    }

                    if (GetHitsAgainstTarget(level, adjustedAccuracy, target) > 0)
                    {
                        // Target loses half of MP
                        if (target.MP > 0)
                        {
                            target.MP = target.MP / 2;
                        }
                        // NES_BUG: This only effects the first byte of the MP value
                        return(statusSuccessResult);
                    }

                    break;

                case "CHNG":         // Caster and Target swap HP and MP
                    if (target.IsResistantTo(spell.Element))
                    {
                        return(failedResult);
                    }

                    if (GetHitsAgainstTarget(level, adjustedAccuracy, target) > 0)
                    {
                        int casterHP = caster.HP;
                        int casterMP = caster.MP;
                        caster.HP = target.HP;
                        caster.MP = target.MP;
                        target.HP = casterHP;
                        target.MP = casterMP;
                        return(statusSuccessResult);
                    }

                    break;

                case "BLAST":
                    // Bomb's Explosion Spell
                    // Fails if HP is full, otherwise kills the caster
                    // Deals damage like a physical attack
                    if (caster.HP == caster.HPMax)
                    {
                        return(failedResult);
                    }

                    int blastSum = 0;
                    for (int i = 0; i < level; i++)
                    {
                        int blastRoll = Globals.rnd.Next(20, 41) - target.Defense;
                        blastSum += blastRoll > 0 ? blastRoll : 0;
                    }

                    caster.Kill();
                    // Is this legit?
                    MonoMonster m = (MonoMonster)caster;
                    m.IsFading = true;

                    return(HandleDamageSpellResult(target, blastSum));
                }
                break;

            case "ItemFullRestore":
                // Elixir
                target.HealHP(target.HPMax);
                target.HealMP(target.MPMax);
                return(statusSuccessResult);

            // The below are currently unused
            case "ItemHeal":
            case "ItemCure":
            case "ItemRevive":
                break;

            default:
                throw new Exception("Invalid spell effect found: " + spell.Effect);
            }
            return(failedResult);
        }
Пример #8
0
 public bool HasPermStatus(PermStatus permStatus)
 {
     return(PermStatuses.Contains(permStatus));
 }
Пример #9
0
 public virtual bool RemovePermStatus(PermStatus permStatus)
 {
     return(PermStatuses.Remove(permStatus));
 }