Пример #1
0
        /// <summary>
        /// Deal damage to a target, and build and return the result.
        /// </summary>
        private static SpellResult HandleDamageSpellResult(Monster target, int damage)
        {
            SpellResult res = new SpellResult(damage);

            target.DamageHP(damage);
            if (target.IsDead())
            {
                res.Results.Add(target.Name + " fell");
            }
            return(res);
        }
Пример #2
0
        private bool ExecuteActions()
        {
            turn = 0;
            foreach (Action action in GetSortedActionArray())
            {
                // Update and display turn number
                turn++;
                turnTotal++;
                TextManager.SetTurnText(turn);

                // If an actor was killed before its turn, ignore the turn
                if (action.Actor == null || action.Actor.IsDead())
                {
                    continue;
                }

                // Apply the action to each target
                foreach (MonoMonster target in action.Targets)
                {
                    // Ignore invalid target and spells against the dead
                    if (target == null || (action.Spell != null && target.IsDead()))
                    {
                        continue;
                    }

                    TextManager.SetActorText(action.Actor.Name);
                    Thread.Sleep(gameTick);

                    // If the action is nothing, display and bail out
                    if (action.Nothing)
                    {
                        TextManager.SetResultsText("Nothing");
                        continue;
                    }

                    TextManager.SetTargetText(target.Name);
                    Thread.Sleep(gameTick);

                    if (action.Physical)
                    {
                        // Physical attacks can target the dead, but are ineffective
                        if (target.IsDead())
                        {
                            TextManager.SetResultsText("Ineffective");
                            continue;
                        }

                        // Apply the attack and display the results
                        AttackResult atkRes = AttackManager.AttackMonster(action.Actor, target);

                        // On a miss, display and bail out
                        if (string.Equals(atkRes.DamageMessage, "Miss"))
                        {
                            TextManager.SetDamageText("Miss");
                            continue;
                        }

                        // Flicker sprite
                        // TODO: Different sounds and animations need to play based on the attack type
                        if (gameTick > 30)
                        {
                            SoundManager.PlaySound(SoundManager.Sound.Physical);
                            target.Flicker(16);
                        }

                        TextManager.SetHitsText(atkRes.HitsMessage);
                        Thread.Sleep(gameTick);

                        TextManager.SetDamageText(atkRes.DamageMessage);
                        Thread.Sleep(gameTick);

                        // Fade the monster if dead
                        // TODO: This is awkward here
                        if (target.IsDead())
                        {
                            target.StartDeath();
                            Thread.Sleep(30); // 300
                        }

                        // Display each result, tearing down existing results as needed
                        for (int i = 0; i < atkRes.Results.Count; i++)
                        {
                            string res = atkRes.Results[i];
                            TextManager.SetResultsText(res);
                            if (i < atkRes.Results.Count - 1)
                            {
                                Thread.Sleep(gameTick * 2);
                                TextManager.TearDownResults();
                                Thread.Sleep(teardownTick);
                            }
                        }

                        break;
                    }
                    else
                    {
                        // Casting a spell
                        TextManager.SetHitsText(action.Spell.Name + " " + action.SpellLevel);
                        Thread.Sleep(gameTick);

                        // Cast the spell and display the results
                        SpellResult spellRes = SpellManager.CastSpell(action.Actor, target, action.Spell, action.SpellLevel, action.Targets.Count > 1);

                        // Set the spell animation and sound based on the spell's effect (kinda). This is awkward but fine for now.
                        MagicSprite.MagicAnimation magicAnim = MagicSprite.MagicAnimation.Attack;
                        SoundManager.Sound         magicSnd  = SoundManager.Sound.AttackSpell;
                        if (action.Spell.Effect == "Buff")
                        {
                            magicAnim = MagicSprite.MagicAnimation.Buff;
                            magicSnd  = SoundManager.Sound.Buff;
                        }
                        if (action.Spell.Effect == "Debuff" || action.Spell.Effect == "TempStatus" || action.Spell.Effect == "PermStatus")
                        {
                            magicAnim = MagicSprite.MagicAnimation.Debuff;
                            magicSnd  = SoundManager.Sound.Debuff;
                        }
                        if (action.Spell.Effect == "Heal" || action.Spell.Effect == "Revive" || action.Spell.Effect == "ItemFullRestore")
                        {
                            magicAnim = MagicSprite.MagicAnimation.Heal;
                            magicSnd  = SoundManager.Sound.Heal;
                        }

                        MagicSpriteManager.GenerateSpellBurst((int)target.Position.X, (int)target.Position.Y, target.Width, target.Height, magicAnim);
                        SoundManager.PlaySound(magicSnd);
                        Thread.Sleep(SPELL_ANIMATION_DELAY);

                        if (spellRes.Damage >= 0)
                        {
                            TextManager.SetDamageText(spellRes.Damage.ToString());
                            Thread.Sleep(gameTick);
                        }

                        // Kill the target if it's dead.
                        // TODO: This is weird here, should be handled by the monster itself?
                        if (target.IsDead())
                        {
                            target.StartDeath();
                            Thread.Sleep(300);
                        }

                        // Display each result, tearing down existing results as needed
                        for (int i = 0; i < spellRes.Results.Count; i++)
                        {
                            string res = spellRes.Results[i];
                            TextManager.SetResultsText(res);
                            if (i < spellRes.Results.Count - 1)
                            {
                                Thread.Sleep(gameTick * 2);
                                TextManager.TearDownResults();
                                Thread.Sleep(teardownTick);
                            }
                        }

                        // Tear down between each target
                        Thread.Sleep(gameTick * 5);
                        while (TextManager.TearDownText())
                        {
                            Thread.Sleep(teardownTick);
                        }
                    }

                    // TODO: If both scenes only contain "Soul" enemies, they cannot kill eachother
                    if (!sceneOne.HasLivingMonsters() || !sceneTwo.HasLivingMonsters() || round >= ROUND_LIMIT)
                    {
                        break;
                    }
                }

                // Turn end, clean up text display
                Thread.Sleep(gameTick * 5);
                while (TextManager.TearDownText())
                {
                    Thread.Sleep(teardownTick);
                }

                if (!sceneOne.HasLivingMonsters() || !sceneTwo.HasLivingMonsters() || round >= ROUND_LIMIT)
                {
                    break;
                }
            }

            return(false);
        }
Пример #3
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);
        }