void TryLearningSpells(CCharEntity* PChar, CMobEntity* PMob) { if (PMob->m_UsedSkillIds.size() == 0) { // minor optimisation. return; } // prune the learnable blue spells std::vector<CSpell*> PLearnableSpells; for (std::map<uint16, uint16>::iterator i=PMob->m_UsedSkillIds.begin(); i != PMob->m_UsedSkillIds.end(); ++i) { CSpell* PSpell = spell::GetSpellByMonsterSkillId(i->first); if (PSpell != nullptr) { PLearnableSpells.push_back(PSpell); } } if (PLearnableSpells.size() == 0) { return; } std::vector<CCharEntity*> PBlueMages; // populate PBlueMages if (PChar->PParty != nullptr) { for (uint8 i = 0; i < PChar->PParty->members.size(); i++) { if (PChar->PParty->members[i]->GetMJob() == JOB_BLU && PChar->PParty->members[i]->objtype == TYPE_PC) { PBlueMages.push_back((CCharEntity*)PChar->PParty->members[i]); } } } else if (PChar->GetMJob() == JOB_BLU) { PBlueMages.push_back(PChar); } // loop through the list of BLUs and see if they can learn. for (size_t i = 0; i < PBlueMages.size(); i++) { CCharEntity* PBlueMage = PBlueMages[i]; if (PBlueMage->isDead()) { // too dead to learn continue; } if (distance(PBlueMage->loc.p, PMob->loc.p) > 100) { // too far away to learn continue; } for (size_t spell = 0; spell < PLearnableSpells.size(); spell++) { CSpell* PSpell = PLearnableSpells[spell]; if (charutils::hasSpell(PBlueMage, static_cast<uint16>(PSpell->getID()))) { continue; } // get the skill cap for the spell level auto skillLvlForSpell = battleutils::GetMaxSkill(SKILL_BLUE_MAGIC, JOB_BLU, PSpell->getJob(JOB_BLU)); // get player skill level with bonus from gear auto playerSkillLvl = PBlueMage->GetSkill(SKILL_BLUE_MAGIC); // make sure the difference between spell skill and player is at most 31 points if (playerSkillLvl >= skillLvlForSpell - 31) { auto chanceToLearn = 33 + PBlueMage->getMod(Mod::BLUE_LEARN_CHANCE); if (dsprand::GetRandomNumber(100) < chanceToLearn) { if (charutils::addSpell(PBlueMage, static_cast<uint16>(PSpell->getID()))) { PBlueMage->pushPacket(new CMessageBasicPacket(PBlueMage, PBlueMage, static_cast<uint16>(PSpell->getID()), 0, MSGBASIC_LEARNS_SPELL)); charutils::SaveSpell(PBlueMage, static_cast<uint16>(PSpell->getID())); PBlueMage->pushPacket(new CCharSpellsPacket(PBlueMage)); } } break; // only one attempt at learning a spell, regardless of learn or not. } } } }
/************************************************************************ * * * Creates up to many attacks for a particular hand. * * * ************************************************************************/ void CAttackRound::CreateAttacks(CItemWeapon* PWeapon, PHYSICAL_ATTACK_DIRECTION direction) { uint8 num = 1; bool isPC = m_attacker->objtype == TYPE_PC; // Checking the players weapon hit count if (PWeapon->getReqLvl() <= m_attacker->GetMLevel()) { num = PWeapon->getHitCount(); } // If the attacker is a mobentity or derived from mobentity, check to see if it has any special mutli-hit capabilties if (dynamic_cast<CMobEntity*>(m_attacker)) { auto multiHitMax = (uint8)static_cast<CMobEntity*>(m_attacker)->getMobMod(MOBMOD_MULTI_HIT); if (multiHitMax > 0) num = 1 + battleutils::getHitCount(multiHitMax); } AddAttackSwing(PHYSICAL_ATTACK_TYPE::NORMAL, direction, num); // Checking the players triple, double and quadruple attack int16 tripleAttack = m_attacker->getMod(Mod::TRIPLE_ATTACK); int16 doubleAttack = m_attacker->getMod(Mod::DOUBLE_ATTACK); int16 quadAttack = m_attacker->getMod(Mod::QUAD_ATTACK); //check for merit upgrades if (isPC) { CCharEntity* PChar = (CCharEntity*)m_attacker; //merit chance only applies if player has the job trait if (charutils::hasTrait(PChar, TRAIT_TRIPLE_ATTACK)) tripleAttack += PChar->PMeritPoints->GetMeritValue(MERIT_TRIPLE_ATTACK_RATE, PChar); // Ambush Augment adds +1% Triple Attack per merit (need to satisfy conditions for Ambush) if (charutils::hasTrait(PChar, TRAIT_AMBUSH) && PChar->getMod(Mod::AUGMENTS_AMBUSH) > 0 && abs(m_defender->loc.p.rotation - m_attacker->loc.p.rotation) < 23) { tripleAttack += PChar->PMeritPoints->GetMerit(MERIT_AMBUSH)->count; } if (charutils::hasTrait(PChar, TRAIT_DOUBLE_ATTACK)) doubleAttack += PChar->PMeritPoints->GetMeritValue(MERIT_DOUBLE_ATTACK_RATE, PChar); // TODO: Quadruple attack merits when SE release them. } quadAttack = std::clamp<int16>(quadAttack, 0, 100); doubleAttack = std::clamp<int16>(doubleAttack, 0, 100); tripleAttack = std::clamp<int16>(tripleAttack, 0, 100); // Checking Mikage Effect - Hits Vary With Num of Utsusemi Shadows for Main Weapon if (m_attacker->StatusEffectContainer->HasStatusEffect(EFFECT_MIKAGE) && m_attacker->m_Weapons[SLOT_MAIN]->getID() == PWeapon->getID()) { auto shadows = (uint8)m_attacker->getMod(Mod::UTSUSEMI); //ShowDebug(CL_CYAN"Create Attacks: Mikage Active, Rolling Attack Chance for %d Shadowss...\n" CL_RESET, shadows); AddAttackSwing(PHYSICAL_ATTACK_TYPE::NORMAL, direction, shadows); } else if (num == 1 && dsprand::GetRandomNumber(100) < quadAttack) AddAttackSwing(PHYSICAL_ATTACK_TYPE::QUAD, direction, 3); else if (num == 1 && dsprand::GetRandomNumber(100) < tripleAttack) AddAttackSwing(PHYSICAL_ATTACK_TYPE::TRIPLE, direction, 2); else if (num == 1 && dsprand::GetRandomNumber(100) < doubleAttack) AddAttackSwing(PHYSICAL_ATTACK_TYPE::DOUBLE, direction, 1); // Apply Mythic OAT mods (mainhand only) if (direction == PHYSICAL_ATTACK_DIRECTION::RIGHTATTACK) { int16 occAttThriceRate = std::clamp<int16>(m_attacker->getMod(Mod::MYTHIC_OCC_ATT_THRICE), 0, 100); int16 occAttTwiceRate = std::clamp<int16>(m_attacker->getMod(Mod::MYTHIC_OCC_ATT_TWICE), 0, 100); if (num == 1 && dsprand::GetRandomNumber(100) < occAttThriceRate) { AddAttackSwing(PHYSICAL_ATTACK_TYPE::NORMAL, direction, 2); } else if (num == 1 && dsprand::GetRandomNumber(100) < occAttTwiceRate) { AddAttackSwing(PHYSICAL_ATTACK_TYPE::NORMAL, direction, 1); } } // Ammo extra swing - players only if (isPC && m_attacker->getMod(Mod::AMMO_SWING) > 0) { // Check for ammo CCharEntity* PChar = (CCharEntity*)m_attacker; CItemArmor* PAmmo = PChar->getEquip(SLOT_AMMO); CItemArmor* PMain = PChar->getEquip(SLOT_MAIN); CItemArmor* PSub = PChar->getEquip(SLOT_SUB); uint8 slot = PChar->equip[SLOT_AMMO]; uint8 loc = PChar->equipLoc[SLOT_AMMO]; uint8 ammoCount = 0; // Handedness check, checking mod of the weapon for the purposes of level scaling if (battleutils::GetScaledItemModifier(PChar, PMain, Mod::AMMO_SWING_TYPE) == 2 && dsprand::GetRandomNumber(100) < m_attacker->getMod(Mod::AMMO_SWING) && PAmmo != nullptr && ammoCount < PAmmo->getQuantity()) { AddAttackSwing(PHYSICAL_ATTACK_TYPE::NORMAL, direction, 1); ammoCount += 1; } else { if (direction == RIGHTATTACK && battleutils::GetScaledItemModifier(PChar, PMain, Mod::AMMO_SWING_TYPE) == 1 && dsprand::GetRandomNumber(100) < m_attacker->getMod(Mod::AMMO_SWING) && PAmmo != nullptr && ammoCount < PAmmo->getQuantity()) { AddAttackSwing(PHYSICAL_ATTACK_TYPE::NORMAL, RIGHTATTACK, 1); ammoCount += 1; } if (direction == LEFTATTACK && PSub != nullptr && battleutils::GetScaledItemModifier(PChar, PSub, Mod::AMMO_SWING_TYPE) == 1 && dsprand::GetRandomNumber(100) < m_attacker->getMod(Mod::AMMO_SWING) && PAmmo != nullptr && ammoCount < PAmmo->getQuantity()) { AddAttackSwing(PHYSICAL_ATTACK_TYPE::NORMAL, LEFTATTACK, 1); ammoCount += 1; } } if (PAmmo != nullptr) { if (PAmmo->getQuantity() == ammoCount) { charutils::UnequipItem(PChar, SLOT_AMMO); charutils::SaveCharEquip(PChar); } charutils::UpdateItem(PChar, loc, slot, -ammoCount); PChar->pushPacket(new CInventoryFinishPacket()); } } // TODO: Possible Lua function for the nitty gritty stuff below. // Iga mod: Extra attack chance whilst dual wield is on. if (direction == LEFTATTACK && dsprand::GetRandomNumber(100) < m_attacker->getMod(Mod::EXTRA_DUAL_WIELD_ATTACK)) AddAttackSwing(PHYSICAL_ATTACK_TYPE::NORMAL, RIGHTATTACK, 1); }