void CAIGeneral::SetCurrentSpell(uint16 SpellID) { if (m_ActionType != ACTION_MAGIC_START && m_ActionType != ACTION_MAGIC_CASTING && m_ActionType != ACTION_MAGIC_FINISH && m_ActionType != ACTION_MAGIC_INTERRUPT) { CSpell* spell = spell::GetSpell(SpellID); if (spell) { if (spell->getSpellGroup() == SPELLGROUP_BLUE) { m_PSpell = std::make_unique<CBlueSpell>(*static_cast<CBlueSpell*>(spell)); } else { m_PSpell = std::make_unique<CSpell>(*spell); } } else { m_PSpell = nullptr; } } }
/** ** Check what computer units can do with magic. ** In fact, turn on autocast for AI. */ void AiCheckMagic() { CPlayer &player = *AiPlayer->Player; const int n = player.GetUnitCount(); for (int i = 0; i < n; ++i) { CUnit &unit = player.GetUnit(i); if (unit.Type->Spells.size() > 0) { // Check only idle magic units for (size_t i = 0; i != unit.Orders.size(); ++i) { if (unit.Orders[i]->Action == UnitActionSpellCast) { return; } } for (unsigned int j = 0; j < unit.Type->Spells.size(); ++j) { CSpell *spell = unit.Type->Spells[j]; // Check if we can cast this spell. SpellIsAvailable checks for upgrades. if (spell->IsAvailableForUnit(unit) && spell->AICast) { if (AutoCastSpell(unit, *spell)) { break; } } } } } }
CMagicState::CMagicState(CBattleEntity* PEntity, uint16 targid, uint16 spellid, uint8 flags) : CState(PEntity, targid), m_PEntity(PEntity), m_PSpell(nullptr) { CSpell* PSpell = spell::GetSpell(spellid); m_PSpell = PSpell->clone(); if (!m_PSpell) { throw CStateInitException(std::make_unique<CMessageBasicPacket>(m_PEntity, m_PEntity, m_PSpell->getID(), 0, MSGBASIC_CANNOT_CAST_SPELL)); } auto PTarget = m_PEntity->IsValidTarget(m_targid, m_PSpell->getValidTarget(), m_errorMsg); if (!PTarget || m_errorMsg) { throw CStateInitException(std::move(m_errorMsg)); } if (!CanCastSpell(PTarget)) { throw CStateInitException(std::move(m_errorMsg)); } auto errorMsg = luautils::OnMagicCastingCheck(m_PEntity, PTarget, GetSpell()); if (errorMsg) { throw CStateInitException(std::make_unique<CMessageBasicPacket>(m_PEntity, PTarget, m_PSpell->getID(), 0, errorMsg == 1 ? MSGBASIC_CANNOT_CAST_SPELL : errorMsg)); } m_flags = flags; m_castTime = std::chrono::milliseconds(battleutils::CalculateSpellCastTime(m_PEntity, GetSpell())); m_startPos = m_PEntity->loc.p; action_t action; action.id = m_PEntity->id; action.spellgroup = m_PSpell->getSpellGroup(); action.actiontype = ACTION_MAGIC_START; actionList_t& actionList = action.getNewActionList(); actionList.ActionTargetID = PTarget->id; actionTarget_t& actionTarget = actionList.getNewActionTarget(); actionTarget.reaction = REACTION_NONE; actionTarget.speceffect = SPECEFFECT_NONE; actionTarget.animation = 0; actionTarget.param = m_PSpell->getID(); actionTarget.messageID = 327; // starts casting m_PEntity->PAI->EventHandler.triggerListener("MAGIC_START", m_PEntity, m_PSpell.get(), &action); //TODO: weaponskill lua object m_PEntity->loc.zone->PushPacket(m_PEntity, CHAR_INRANGE_SELF, new CActionPacket(action)); }
void CMobController::CastSpell(uint16 spellid) { CSpell* PSpell = spell::GetSpell(spellid); if (PSpell == nullptr) { ShowWarning(CL_YELLOW"ai_mob_dummy::CastSpell: SpellId <%i> is not found\n" CL_RESET, spellid); } else { CBattleEntity* PCastTarget = nullptr; // check valid targets if (PSpell->getValidTarget() & TARGET_SELF) { PCastTarget = PMob; // only buff other targets if i'm roaming if ((PSpell->getValidTarget() & TARGET_PLAYER_PARTY)) { // chance to target my master if (PMob->PMaster != nullptr && dsprand::GetRandomNumber(2) == 0) { // target my master PCastTarget = PMob->PMaster; } else if (dsprand::GetRandomNumber(2) == 0) { // chance to target party PMob->PAI->TargetFind->reset(); PMob->PAI->TargetFind->findWithinArea(PMob, AOERADIUS_ATTACKER, PSpell->getRange()); if (!PMob->PAI->TargetFind->m_targets.empty()) { // randomly select a target PCastTarget = PMob->PAI->TargetFind->m_targets[dsprand::GetRandomNumber(PMob->PAI->TargetFind->m_targets.size())]; // only target if are on same action if (PMob->PAI->IsEngaged() == PCastTarget->PAI->IsEngaged()) { PCastTarget = PMob; } } } } } else { PCastTarget = PTarget; } Cast(PCastTarget->targid, spellid); } }
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 != NULL) { PLearnableSpells.push_back(PSpell); } } if (PLearnableSpells.size() == 0) { return; } std::vector<CCharEntity*> PBlueMages; // populate PBlueMages if (PChar->PParty != NULL) { 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 (int 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 (int spell=0; spell<PLearnableSpells.size(); spell++) { CSpell* PSpell = PLearnableSpells[spell]; if (charutils::hasSpell(PBlueMage, PSpell->getID())) { continue; } uint8 learnableLevel = PSpell->getJob(JOB_BLU); if (learnableLevel > 0 && learnableLevel < PBlueMage->GetMLevel()+7) { // TODO: Use blue magic skill check rather than level if (rand()%100 < 33) { if (charutils::addSpell(PBlueMage, PSpell->getID())) { PBlueMage->pushPacket(new CMessageBasicPacket(PBlueMage, PBlueMage, PSpell->getID(), 0, MSGBASIC_LEARNS_SPELL)); charutils::SaveSpells(PBlueMage); PBlueMage->pushPacket(new CCharSpellsPacket(PBlueMage)); } } break; // only one attempt at learning a spell, regardless of learn or not. } } } }
void CMagicState::FinishSpell() { DSP_DEBUG_BREAK_IF(m_PSpell == NULL); DSP_DEBUG_BREAK_IF(m_PEntity->PBattleAI->GetCurrentAction() != ACTION_MAGIC_FINISH); CSpell* PSpellCopy = new CSpell(*m_PSpell); luautils::OnSpellPrecast(m_PEntity, PSpellCopy); SpendCost(PSpellCopy); SetRecast(m_PSpell); // remove effects based on spell cast first int16 effectFlags = EFFECTFLAG_INVISIBLE | EFFECTFLAG_MAGIC_BEGIN; if (PSpellCopy->canTargetEnemy()) { effectFlags |= EFFECTFLAG_DETECTABLE; } m_PEntity->StatusEffectContainer->DelStatusEffectsByFlag(effectFlags); m_PTargetFind->reset(); m_PEntity->m_ActionList.clear(); // setup special targeting flags // can this spell target the dead? uint8 flags = FINDFLAGS_NONE; if (PSpellCopy->getValidTarget() & TARGET_PLAYER_DEAD) { flags |= FINDFLAGS_DEAD; } if (PSpellCopy->getFlag() & SPELLFLAG_HIT_ALL) { flags |= FINDFLAGS_HIT_ALL; } uint8 aoeType = battleutils::GetSpellAoEType(m_PEntity, PSpellCopy); if (aoeType == SPELLAOE_RADIAL) { float distance = spell::GetSpellRadius(PSpellCopy, m_PEntity); m_PTargetFind->findWithinArea(m_PTarget, AOERADIUS_TARGET, distance, flags); } else if (aoeType == SPELLAOE_CONAL) { //TODO: actual radius calculation float radius = spell::GetSpellRadius(PSpellCopy, m_PEntity); m_PTargetFind->findWithinCone(m_PTarget, radius, 45, flags); } else { // only add target m_PTargetFind->findSingleTarget(m_PTarget, flags); } uint16 totalTargets = m_PTargetFind->m_targets.size(); PSpellCopy->setTotalTargets(totalTargets); apAction_t action; action.ActionTarget = m_PTarget; action.reaction = REACTION_NONE; action.speceffect = SPECEFFECT_NONE; action.animation = PSpellCopy->getAnimationID(); action.param = 0; action.messageID = 0; uint16 msg = 0; int16 ce = 0; int16 ve = 0; for (std::vector<CBattleEntity*>::iterator it = m_PTargetFind->m_targets.begin() ; it != m_PTargetFind->m_targets.end(); ++it) { CBattleEntity* PTarget = *it; action.ActionTarget = PTarget; ce = PSpellCopy->getCE(); ve = PSpellCopy->getVE(); // take all shadows if (PSpellCopy->canTargetEnemy() && aoeType > 0) { PTarget->StatusEffectContainer->DelStatusEffect(EFFECT_BLINK); PTarget->StatusEffectContainer->DelStatusEffect(EFFECT_COPY_IMAGE); } // TODO: this is really hacky and should eventually be moved into lua if (PSpellCopy->canHitShadow() && aoeType == SPELLAOE_NONE && battleutils::IsAbsorbByShadow(PTarget)) { // take shadow msg = 31; action.param = 1; ve = 0; ce = 0; } else { action.param = luautils::OnSpellCast(m_PEntity, PTarget, PSpellCopy); // remove effects from damage if (PSpellCopy->canTargetEnemy() && action.param > 0 && m_PSpell->dealsDamage()) { PTarget->StatusEffectContainer->DelStatusEffectsByFlag(EFFECTFLAG_DAMAGE); } if(msg == 0) { msg = PSpellCopy->getMessage(); } else { msg = PSpellCopy->getAoEMessage(); } } action.messageID = msg; if (PSpellCopy->getID() != 305) //I hate to do this, but there really is no other spell like Odin CharOnTarget(&action, ce, ve); m_PEntity->m_ActionList.push_back(action); } CharAfterFinish(); m_PEntity->StatusEffectContainer->DelStatusEffectsByFlag(EFFECTFLAG_MAGIC_END); DSP_DEBUG_BREAK_IF(m_PEntity->PBattleAI->GetCurrentAction() != ACTION_MAGIC_FINISH); m_PEntity->loc.zone->PushPacket(m_PEntity, CHAR_INRANGE_SELF, new CActionPacket(m_PEntity)); delete PSpellCopy; Clear(); }
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) { // TODO: check for blue learning bonus and adjust base percent if (dsprand::GetRandomNumber(100) < 33) { 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. } } } }
bool CanUseSpell(CBattleEntity* PCaster, uint16 SpellID) { bool usable = false; CSpell* spell = GetSpell(SpellID); if (spell != NULL) { uint8 JobMLVL = spell->getJob(PCaster->GetMJob()); uint8 JobSLVL = spell->getJob(PCaster->GetSJob()); uint8 requirements = spell->getRequirements(); if(PCaster->GetMLevel() >= JobMLVL) { usable = true; if (requirements & SPELLREQ_TABULA_RASA) { if (!PCaster->StatusEffectContainer->HasStatusEffect(EFFECT_TABULA_RASA)) { usable = false; } } if (requirements & SPELLREQ_ADDENDUM_BLACK && PCaster->GetMJob() == JOB_SCH) { if(!PCaster->StatusEffectContainer->HasStatusEffect(EFFECT_ADDENDUM_BLACK) && !PCaster->StatusEffectContainer->HasStatusEffect(EFFECT_ENLIGHTENMENT)) { usable = false; } } else if (requirements & SPELLREQ_ADDENDUM_WHITE && PCaster->GetMJob() == JOB_SCH) { if (!PCaster->StatusEffectContainer->HasStatusEffect(EFFECT_ADDENDUM_WHITE) && !PCaster->StatusEffectContainer->HasStatusEffect(EFFECT_ENLIGHTENMENT)) { usable = false; } } else if (SpellID > 0x200) { if (PCaster->objtype == TYPE_PC) { if (!blueutils::IsSpellSet((CCharEntity*)PCaster, (CBlueSpell*)spell)) { usable = false; } } } if (usable) { return true; } } if(PCaster->GetSLevel() >= JobSLVL) { usable = true; if (requirements & SPELLREQ_TABULA_RASA) { if (!PCaster->StatusEffectContainer->HasStatusEffect(EFFECT_TABULA_RASA)) { usable = false; } } if (requirements & SPELLREQ_ADDENDUM_BLACK && PCaster->GetSJob() == JOB_SCH) { if (!PCaster->StatusEffectContainer->HasStatusEffect(EFFECT_ADDENDUM_BLACK) && !PCaster->StatusEffectContainer->HasStatusEffect(EFFECT_ENLIGHTENMENT)) { usable = false; } } else if (requirements & SPELLREQ_ADDENDUM_WHITE && PCaster->GetSJob() == JOB_SCH) { if (!PCaster->StatusEffectContainer->HasStatusEffect(EFFECT_ADDENDUM_WHITE) && !PCaster->StatusEffectContainer->HasStatusEffect(EFFECT_ENLIGHTENMENT)) { usable = false; } } else if (SpellID > 0x200) { if (PCaster->objtype == TYPE_PC) { if (!blueutils::IsSpellSet((CCharEntity*)PCaster, (CBlueSpell*)spell)) { usable = false; } } } } } return usable; }