/// <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); }
/// <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)); }
/// <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)); }
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)); }
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); }
/// <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); }
/// <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); }
public bool HasPermStatus(PermStatus permStatus) { return(PermStatuses.Contains(permStatus)); }
public virtual bool RemovePermStatus(PermStatus permStatus) { return(PermStatuses.Remove(permStatus)); }