/// <summary> /// Handles skill casting. Will return false if skill is unable to cast for any reason, such as not enough resources or invalid skill. /// </summary> /// <param name="nSkillID"></param> /// <param name="bLeft">If the skill is happening to the left of the character</param> /// <param name="bOutsideHandling">When this is true, the function will only process the resource/cooldown portion.</param> /// <returns></returns> public bool Cast(int nSkillID, bool bLeft, bool bOutsideHandling = false, int nSpiritJavelinItemID = 0) { if (Parent.Stats.nHP <= 0) { return(false); // REEEEEEEEE } #if DEBUG Parent.SendMessage("Casting spell " + nSkillID); #endif var skill = Get(nSkillID, true); if (skill == null) { Parent.SendMessage("Unable to find skill."); return(false); } var template = skill.Template; var nSLV = skill.nSLV; if (Parent.Cooldowns.OnCooldown(nSkillID)) { Parent.SendMessage("Trying to cast skill while skill on cooldown."); return(false); } double costMp = skill.MPCost; // done var costMeso = template.MesoR(nSLV); // may not be done.. var itemConsumeId = template.ItemConsume; // done if (Parent.Buffs.Contains((int)Skills.BISHOP_INFINITY)) // party buff { costMp = 0; } else { switch (Parent.Stats.nJob / 10) { case 21: if (Parent.Buffs.Contains((int)Skills.ARCHMAGE1_INFINITY)) { costMp = 0; break; } costMp *= (Parent.Skills.Get((int)Skills.MAGE1_ELEMENT_AMPLIFICATION)?.X_Effect ?? 100.0) * 0.01; goto default; case 22: if (Parent.Buffs.Contains((int)Skills.ARCHMAGE2_INFINITY)) { costMp = 0; break; } costMp *= (Parent.Skills.Get((int)Skills.MAGE2_ELEMENT_AMPLIFICATION)?.X_Effect ?? 100.0) * 0.01; goto default; case 121: // koc mage costMp *= (Parent.Skills.Get((int)Skills.FLAMEWIZARD_ELEMENT_AMPLIFICATION)?.X_Effect ?? 100.0) * 0.01; goto default; case 221: // evan case 222: // evan rounded up (2215-2218) costMp *= (Parent.Skills.Get((int)Skills.EVAN_ELEMENT_AMPLIFICATION)?.X_Effect ?? 100.0) * 0.01; goto default; case 31: if (Parent.Buffs[(int)Skills.BOWMASTER_CONCENTRATION] is BuffSkill pBuff) { costMp *= 1 - (pBuff.Template.X(pBuff.nSLV) * 0.01); } goto default; default: if (Parent.Stats.nMP < Math.Floor(costMp)) { Log.Debug($"Not enough mp to perform skill. Current MP: {Parent.Stats.nMP}. MP required: {costMp}. SkillID: {nSkillID}. CharID: {Parent.dwId}. CharName: {Parent.Stats.sCharacterName}."); return(false); } break; } } // calculate HP cost int costHp; switch ((Skills)nSkillID) { case Skills.DUAL5_FINAL_CUT: case Skills.DRAGONKNIGHT_DRAGON_ROAR: case Skills.DRAGONKNIGHT_SACRIFICE: case Skills.INFIGHTER_MP_RECOVERY: costHp = (int)(Parent.BasicStats.nMHP * template.X(skill.nSLV) * 0.01); break; default: costHp = 0; break; } if (Parent.Stats.nHP <= costHp) { Log.Debug($"Not enough hp to perform skill. Hp required: {costHp}"); return(false); } if (Parent.Stats.nMoney < costMeso) { Log.Debug($"Not enough meso to perform skill. Meso required: {costMeso}"); return(false); } var itemConsumeAmount = template.ItemConsumeAmount; if (nSkillID == (int)Skills.NIGHTLORD_SPIRIT_JAVELIN) { if (!ItemConstants.IsThrowingStar(nSpiritJavelinItemID)) { return(false); } itemConsumeId = nSpiritJavelinItemID; itemConsumeAmount = 200; // we hardcodin' bois } if (itemConsumeId > 0 && itemConsumeAmount > 0) { var valid = false; var itemType = ItemConstants.GetInventoryType(itemConsumeId); foreach (var item in itemType == InventoryType.Etc ? Parent.InventoryEtc : Parent.InventoryConsume) // lol ternary operator in a loop { if (item.Value.nItemID == itemConsumeId && item.Value.nNumber >= itemConsumeAmount) { valid = true; InventoryManipulator.RemoveFrom(Parent, itemType, item.Key, (short)itemConsumeAmount); break; } } if (!valid) { return(false); } } // energy skills switch ((Skills)nSkillID) { case Skills.BUCCANEER_ENERGY_BURSTER: case Skills.STRIKER_ENERGY_BURSTER: case Skills.BUCCANEER_ENERGY_DRAIN: case Skills.STRIKER_ENERGY_DRAIN: case Skills.VIPER_ENERGY_ORB: if (Parent.Combat.nEnergy != SkillLogic.EnergyMax) { Parent.SendMessage($"Insufficient energy. ({Parent.Combat.nEnergy} != {SkillLogic.EnergyMax})."); return(false); } break; } Parent.Modify.Stats(ctx => { if (costHp > 0) { ctx.HP -= costHp; } if (costMp > 0) { ctx.MP -= (int)Math.Floor(costMp); } if (costMeso > 0) { ctx.Money -= costMeso; } }); if (Parent.nPreparedSkill == 0) { var nCdSkillId = nSkillID; var nCdTime = template.Cooltime(skill.nSLV); switch ((Skills)nSkillID) { case Skills.MECHANIC_SAFETY: { if (!Parent.Field.Summons.Any(s => s.dwParentID == dwParentID && (s.nSkillID == (int)Skills.MECHANIC_SATELITE || s.nSkillID == (int)Skills.MECHANIC_SATELITE2 || s.nSkillID == (int)Skills.MECHANIC_SATELITE3))) { return(false); // satellites must be active to cast this skill } } break; case Skills.WILDHUNTER_SWALLOW: case Skills.MECHANIC_TESLA_COIL: nCdTime = 0; break; case Skills.WILDHUNTER_SWALLOW_DUMMY_ATTACK: case Skills.WILDHUNTER_SWALLOW_DUMMY_BUFF: var pTempTemplate = Parent.Skills.Get((int)Skills.WILDHUNTER_SWALLOW); nCdSkillId = pTempTemplate.nSkillID; nCdTime = (short)pTempTemplate.CoolTimeSeconds; break; } if (nCdTime > 0) { Parent.Cooldowns.UpdateOrInsert(nCdSkillId, (short)nCdTime); } } if (template.is_heros_will_skill) { Parent.Buffs.CancelAllDebuffs(); return(true); } if (bOutsideHandling || template.IsNotBuff) { return(true); } if (SkillLogic.is_teleport_mastery_skill(nSkillID)) { HandleTeleportMastery(nSkillID, nSLV); return(true); } switch ((Skills)nSkillID) { case Skills.HERO_ENRAGE: { Parent.Buffs.Remove((int)Skills.CRUSADER_COMBO_ATTACK); Parent.Combat.ComboCounter = 0; } break; case Skills.CRUSADER_MAGIC_CRASH: case Skills.DRAGONKNIGHT_MAGIC_CRASH: case Skills.KNIGHT_MAGIC_CRASH: CastAOEMobStat(nSkillID); return(true); case Skills.NIGHTLORD_SPIRIT_JAVELIN: { Parent.Buffs.Remove(nSkillID); var buff = new BuffSkill(nSkillID, nSLV); buff.GenerateSpiritJavelin(nSpiritJavelinItemID); Parent.Buffs.Add(buff); } return(true); case Skills.BMAGE_SUPER_BODY: DoSuperBody(nSLV); return(true); case Skills.BMAGE_AURA_BLUE: DoAuraSkill(SecondaryStatFlag.BlueAura, (int)Skills.BMAGE_AURA_BLUE_ADVANCED, nSkillID, nSLV); return(true); case Skills.BMAGE_AURA_YELLOW: DoAuraSkill(SecondaryStatFlag.YellowAura, (int)Skills.BMAGE_AURA_YELLOW_ADVANCED, nSkillID, nSLV); return(true); case Skills.BMAGE_AURA_DARK: DoAuraSkill(SecondaryStatFlag.DarkAura, (int)Skills.BMAGE_AURA_DARK_ADVANCED, nSkillID, nSLV); return(true); case Skills.SHADOWER_SMOKE_SHELL: case Skills.EVAN_RECOVERY_AURA: case Skills.MAGE1_POISON_MIST: case Skills.FLAMEWIZARD_FLAME_GEAR: case Skills.BMAGE_SHELTER: CastAffectedAreaSkill (nSkillID, skill.nSLV, (short)skill.BuffTime, Parent.Position.CurrentXY, template.LT, template.RB); return(true); case Skills.THIEFMASTER_CHAKRA: if (Parent.Stats.nHP < Parent.BasicStats.nMHP * 0.5) { // BMS // dwFlaga = sd->nY + 100; // tCur = CRand32::Random(&g_rand) % 100 + 100; // CQWUser::IncHP(v5, ((tCur * v5->m_basicStat.nLUK * 0.033 + v5->m_basicStat.nDEX) * dwFlaga * 0.002), 0); var nMultiplier = skill.Y_Effect + 100; var nRand = Constants.Rand.Next() % 100 + 100; Parent.Modify.Heal((int)((nRand * Parent.BasicStats.nLUK * 0.033 + Parent.BasicStats.nDEX) * nMultiplier * 0.002)); return(true); } return(false); case Skills.KNIGHT_RESTORATION: Parent.Modify.Heal((int)(Parent.BasicStats.nMHP * skill.X_Effect)); return(true); case Skills.FLAMEWIZARD_SLOW: case Skills.WIZARD1_SLOW: case Skills.WIZARD2_SLOW: case Skills.NIGHTLORD_NINJA_AMBUSH: case Skills.SHADOWER_NINJA_AMBUSH: case Skills.HERMIT_SHADOW_WEB: case Skills.NIGHTWALKER_SHADOW_WEB: case Skills.DUAL3_FLASH_BANG: case Skills.DUAL4_UPPER_STAB: case Skills.HUNTER_ARROW_BOMB: case Skills.BOWMASTER_VENGEANCE: case Skills.PAGE_THREATEN: CastAOEMobStat(nSkillID); return(true); case Skills.DUAL4_OWL_DEATH: if (skill.DoProp()) { Parent.Buffs.AddSkillBuff(nSkillID, nSLV, Parent.Buffs.BuffTimeModifier()); Parent.Combat.OwlSpiritCount = 10; } return(true); case Skills.ADMIN_HOLY_SYMBOL: foreach (var pChar in Parent.Field.Users) { pChar.Buffs.AddSkillBuff(nSkillID, nSLV, Parent.Buffs.BuffTimeModifier()); } return(true); case Skills.WILDHUNTER_SWALLOW: SwallowMob(Parent.m_dwSwallowMobID, nSLV); return(true); case Skills.VALKYRIE_DICE: case Skills.MECHANIC_DICE: case Skills.BUCCANEER_DICE: DoDice(nSkillID, nSLV); return(true); case Skills.INFIGHTER_MP_RECOVERY: Parent.Modify.Heal(0, (int)(Parent.BasicStats.nMHP * (0.5 + (0.1 * nSLV)))); // hard coding ftw return(true); case Skills.CLERIC_HEAL: DoPartyHeal(nSkillID, template.HP(nSLV)); return(true); case Skills.BISHOP_RESURRECTION: if (Parent.Party?.Count > 1) { Parent.Party.ApplyBuffToParty(Parent, Parent.Field.dwUniqueId, nSkillID, nSLV); } return(true); case Skills.PRIEST_DISPEL: if (Parent.Party?.Count > 1) { Parent.Party.ApplyBuffToParty(Parent, Parent.Field.dwUniqueId, nSkillID, nSLV); } else { Parent.Buffs.CancelAllDebuffs(); } CastAOEMobStat(nSkillID); // lazy impl return(true); case Skills.KNIGHT_COMBAT_ORDERS: DoCombatOrders(nSkillID, nSLV); return(true); case Skills.BISHOP_INFINITY: // only a party buff for clerics (custom) if (Parent.Party?.Count > 1) { Parent.Party.ApplyBuffToParty(Parent, Parent.Field.dwUniqueId, nSkillID, nSLV, 2); return(true); } break; case Skills.VIPER_TIME_LEAP: DoTimeleap(nSkillID); return(true); case Skills.MECHANIC_HN07: { if (Parent.Skills.Get((int)Skills.MECHANIC_HN07_UPGRADE, false) is SkillEntry se) { Parent.Buffs.AddSkillBuff(se.nSkillID, se.nSLV); return(true); } } break; } if (template.Time(nSLV) != 0) { var buffTimeModifier = Parent.Buffs.BuffTimeModifier(); if (Parent.Party?.Count > 1 && template.IsPartyBuff) { Parent.Party.ApplyBuffToParty (Parent, Parent.Field.dwUniqueId, nSkillID, nSLV, buffTimeModifier); } else { Parent.Buffs.AddSkillBuff(nSkillID, nSLV, buffTimeModifier); } } return(true); }