Beispiel #1
0
        //////////////
        // Monogame //
        //////////////

        public void Initialize()
        {
            // Populate columns with nulls to start
            for (int i = 0; i < columns.Length; i++)
            {
                columns[i] = new MonoMonster[] { null, null }
            }
            ;
        }
Beispiel #2
0
        /////////////
        // Publics //
        /////////////

        /// <summary>
        /// Setup and populate the scene with monsters using a very specifically formatted string...
        /// Expected format: "A;name-name-name-name-name-name-name-name"
        /// </summary>
        public void PopulateScene(string sceneString, ContentManager content)
        {
            // Cleanup any leftovers first
            ClearScene();

            // Rip apart the string into it's scene type and monster list
            SceneString = sceneString;
            string[] sceneSplit   = sceneString.Split(';');
            string   sType        = sceneSplit[0];
            string   monsterNames = sceneSplit[1];

            // Setup the scene, then populate with the monsters
            SceneType = (SceneType)Enum.Parse(typeof(SceneType), sType);
            LoadSceneType(SceneType);

            int col = 0, row = 0;

            foreach (string name in monsterNames.Split('-'))
            {
                MonoMonster monster = MonoMonsterManager.GetMonoMonsterByName(name);
                if (monster == null)
                {
                    continue;
                }

                if (content != null)
                {
                    monster.Initialize(content.Load <Texture2D>("Graphics\\Monsters\\" + monster.Name), Flipped);
                }

                monster.scene = this;

                columns[col / 2][row % 2] = monster;
                monster.Position          = slotPositions[col / 2][row % 2];
                row++;
                col++;

                // Tall monsters skip the next slot
                if (string.Equals(monster.size.ToUpper(), "TALL"))
                {
                    row++;
                    col++;
                }
            }

            // Set the display text for monsters
            UpdateSceneText();
        }
Beispiel #3
0
        /////////////
        // Publics //
        /////////////

        /// <summary>
        /// Retrieve a MonoMonster object using the supplied name
        /// </summary>
        /// <param name="name">Any given name of the monster (ignores case)</param>
        /// <returns>The MonoMonster, if any. Null if there's an error.</returns>
        public static Monster GetMonsterByName(string name)
        {
            if (String.IsNullOrEmpty(name))
            {
                throw new ArgumentException("Invalid monster name supplied");
            }

            // Look through monster data and pick the matching monster by name
            foreach (dynamic data in monsterData)
            {
                if (String.Equals(name, (string)data.name, StringComparison.OrdinalIgnoreCase))
                {
                    MonoMonster mon = new MonoMonster();
                    mon.size = data.size;

                    // Doing this by hand since generated .json naming isn't consistent, nor formatted 100%
                    mon.Name          = data.name;
                    mon.HPMax         = data.HP; // Set HPMax before HP
                    mon.HP            = data.HP;
                    mon.MPMax         = data.MP; // Set MPMax before MP
                    mon.MP            = data.MP;
                    mon.Strength      = data.strength;
                    mon.Hits          = data.hits;
                    mon.Defense       = data.defense;
                    mon.Blocks        = data.blocks;
                    mon.Evasion       = data.evade;
                    mon.MagicBlocks   = data.mBlocks;
                    mon.Accuracy      = data.accuracy;
                    mon.MagicEvasion  = data.mEvade;
                    mon.Fear          = data.cowardice;
                    mon.AttackEffects = data.attackEffect.ToObject <HashSet <String> >();
                    mon.ActionList    = data.attackList.ToObject <List <MonsterAction> >();
                    mon.Families      = data.race.ToObject <HashSet <MonsterFamily> >();
                    mon.Weaknesses    = data.weak.ToObject <HashSet <Element> >();
                    mon.Resistances   = data.resist.ToObject <HashSet <Element> >();
                    mon.Absorbs       = data.absorb.ToObject <HashSet <Element> >();

                    // Below may or may not be implemented
                    // mon.Level = data.skillLevel;
                    // mon.GilDrops = data.gilDrops.ToObject<List<String>>();
                    // mon.ItemDrops = data.itemDrops.ToObject<List<String>>();

                    return(mon);
                }
            }

            throw new Exception("No monster found by name: " + name);
        }
        /// <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);
        }
        /////////////
        // Publics //
        /////////////

        /// <summary>
        /// Get the result of one monster attacking another
        /// </summary>
        public static AttackResult AttackMonster(Monster actor, Monster target)
        {
            // Catch them errors first and foremost
            if (actor == null)
            {
                throw new ArgumentNullException("Invalid actor supplied");
            }
            if (target == null)
            {
                throw new ArgumentNullException("Invalid target supplied");
            }

            // Get overall attack score. If actor has beneficial AURA stacks, add +20 to attack
            int attackScore = actor.Strength;

            for (int i = 0; i < actor.GetBuffStacks(Buff.Aura); i++)
            {
                // Ignore Undead unless bug fixed
                if (!Globals.BUG_FIXES && i >= 7)
                {
                    break;
                }

                // Attack bonus only applies once for a family match
                if (target.Families.Contains((MonsterFamily)i))
                {
                    attackScore += AURA_BONUS;
                    break;
                }
            }

            // Determine total successful hits and overall damage. Apply status effects if necessary.
            List <string> results   = new List <string>();
            int           totalHits = GetTotalHits(actor, target);

            if (totalHits == 0)
            {
                return(new AttackResult(0, 0, new List <string>()));
            }

            int  damage   = 0;
            bool critFlag = false;

            for (int i = 0; i < totalHits; i++)
            {
                // Get damage and add critical bonus damage if rolled
                int dmgRoll = Globals.rnd.Next(attackScore, attackScore * 2 + 1) - target.Defense;
                damage += dmgRoll > 0 ? dmgRoll : 0;
                if (Globals.rnd.Next(100) < CRIT_RATE)
                {
                    damage += attackScore;
                    // Only add the crit message once
                    if (!critFlag)
                    {
                        critFlag = true;
                        results.Add(CRIT_MESSAGE);
                    }
                }
            }

            // Apply HP and MP drain effects based on totalHits
            if (actor.AttackEffects.Contains("Drain HP"))
            {
                int hpAmt   = target.HPMax / 16;
                int dranAmt = hpAmt * totalHits;

                // Opposite effect vs. Undead
                if (target.Families.Contains(MonsterFamily.Undead))
                {
                    actor.DamageHP(dranAmt);

                    // TODO: This is awkward here
                    if (actor.IsDead())
                    {
                        MonoMonster m = (MonoMonster)actor;
                        m.IsFading = true;
                    }
                    target.HealHP(dranAmt);
                }
                else
                {
                    actor.HealHP(dranAmt);
                    target.DamageHP(dranAmt);
                }
            }

            if (actor.AttackEffects.Contains("Drain MP"))
            {
                int mpAmt   = target.MPMax / 16;
                int asplAmt = mpAmt * totalHits;

                // Opposite effect vs. Undead
                if (target.Families.Contains(MonsterFamily.Undead))
                {
                    actor.DamageMP(asplAmt);
                    target.HealMP(asplAmt);
                }
                else
                {
                    actor.HealMP(asplAmt);
                    target.DamageMP(asplAmt);
                }
            }

            // Apply actor's attack effect(s), if any, to the target
            if (actor.AttackEffects.Count > 0)
            {
                // Target rolls magic blocks against totalHits. If any hit makes it through, apply all status effects
                int statusHits = totalHits - target.RollMagicBlocks();
                if (statusHits > 0)
                {
                    foreach (string effect in actor.AttackEffects)
                    {
                        if (Enum.TryParse <PermStatus>(effect, out PermStatus permStat))
                        {
                            target.AddPermStatus(permStat);
                            results.Add(permStatusMessageDict[permStat]);
                            continue;
                        }

                        if (Enum.TryParse <TempStatus>(effect, out TempStatus tempStat))
                        {
                            target.AddTempStatus(tempStat);
                            results.Add(tempStatusMessageDict[tempStat]);
                            continue;
                        }
                    }
                }
            }

            // Apply the damage and return the overall results
            target.DamageHP(damage);
            if (target.IsDead())
            {
                results.Add(target.Name + " fell");
            }
            return(new AttackResult(totalHits, damage, results));
        }