bool CMobEntity::CanLink(position_t* pos, int16 superLink) { // handle super linking if (superLink && getMobMod(MOBMOD_SUPERLINK) == superLink) { return true; } // can't link right now if (m_neutral) { return false; } // link only if I see him if (m_Detects & DETECT_SIGHT) { if (!isFaceing(loc.p, *pos, 40)) { return false; } } if (distance(loc.p, *pos) > getMobMod(MOBMOD_LINK_RADIUS)) { return false; } if (!PAI->PathFind->CanSeePoint(*pos)) { return false; } return true; }
/************************************************************************ * * * Is blocked. * * * ************************************************************************/ bool IsBlocked(CBattleEntity* PAttacker, CBattleEntity* PDefender) { if(isFaceing(PDefender->loc.p, PAttacker->loc.p, 40)) { return(WELL512::irand() % 100 < battleutils::GetBlockRate(PAttacker, PDefender)); } return false; }
/************************************************************************ * * * Is blocked. * * * ************************************************************************/ bool IsBlocked(CBattleEntity* PAttacker, CBattleEntity* PDefender) { if (isFaceing(PDefender->loc.p, PAttacker->loc.p, 40)) { return(dsprand::GetRandomNumber(100) < battleutils::GetBlockRate(PAttacker, PDefender)); } return false; }
/************************************************************************ * * * Is guarded. * * * ************************************************************************/ bool IsGuarded(CBattleEntity* PAttacker, CBattleEntity* PDefender) { if(isFaceing(PDefender->loc.p, PAttacker->loc.p, 40)) { return(rand() % 100 < battleutils::GetGuardRate(PAttacker, PDefender)); } return false; }
bool CAttack::CheckCounter() { if (!m_victim->PAI->IsEngaged()) { m_isCountered = false; return m_isCountered; } uint8 meritCounter = 0; if (m_victim->objtype == TYPE_PC && charutils::hasTrait((CCharEntity*)m_victim, TRAIT_COUNTER)) { if (m_victim->GetMJob() == JOB_MNK || m_victim->GetMJob() == JOB_PUP) { meritCounter = ((CCharEntity*)m_victim)->PMeritPoints->GetMeritValue(MERIT_COUNTER_RATE, (CCharEntity*)m_victim); } } //counter check (rate AND your hit rate makes it land, else its just a regular hit) //having seigan active gives chance to counter at 25% of the zanshin proc rate uint16 seiganChance = 0; if (m_victim->objtype == TYPE_PC && m_victim->StatusEffectContainer->HasStatusEffect(EFFECT_SEIGAN)) { seiganChance = m_victim->getMod(Mod::ZANSHIN) + ((CCharEntity*)m_victim)->PMeritPoints->GetMeritValue(MERIT_ZASHIN_ATTACK_RATE, (CCharEntity*)m_victim); seiganChance = std::clamp<uint16>(seiganChance, 0, 100); seiganChance /= 4; } if ((dsprand::GetRandomNumber(100) < (m_victim->getMod(Mod::COUNTER) + meritCounter) || dsprand::GetRandomNumber(100) < seiganChance) && isFaceing(m_victim->loc.p, m_attacker->loc.p, 40) && dsprand::GetRandomNumber(100) < battleutils::GetHitRate(m_victim, m_attacker)) { m_isCountered = true; m_isCritical = (dsprand::GetRandomNumber(100) < battleutils::GetCritHitRate(m_victim, m_attacker, false)); } else if (m_victim->StatusEffectContainer->HasStatusEffect(EFFECT_PERFECT_COUNTER)) { //Perfect Counter only counters hits that normal counter misses, always critical, can counter 1-3 times before wearing m_isCountered = true; m_isCritical = true; m_victim->StatusEffectContainer->DelStatusEffect(EFFECT_PERFECT_COUNTER); } return m_isCountered; }
bool CMobEntity::CanLink(position_t* pos, int16 superLink) { // handle super linking if(superLink && getMobMod(MOBMOD_SUPERLINK) == superLink) { return true; } // can't link right now if(m_neutral) { return false; } // link only if I see him if ((m_Aggro & AGGRO_DETECT_SIGHT) || (m_Aggro & AGGRO_DETECT_TRUESIGHT)){ if(!isFaceing(loc.p, *pos, 40)) return false; } // link if close enough return distance(loc.p, *pos) <= getMobMod(MOBMOD_LINK_RADIUS); }
/** * Checks if the mob can detect the target using it's detection (sight, sound, etc) * This is used to aggro and deaggro (Mobs start to deaggro after failing to detect target). **/ bool CMobController::CanDetectTarget(CBattleEntity* PTarget, bool forceSight) { if (PTarget->isDead() || PTarget->animation == ANIMATION_CHOCOBO) return false; float verticalDistance = abs(PMob->loc.p.y - PTarget->loc.p.y); if (verticalDistance > 8) { return false; } auto detects = PMob->m_Detects; auto currentDistance = distance(PTarget->loc.p, PMob->loc.p) + PTarget->getMod(MOD_STEALTH); bool detectSight = (detects & DETECT_SIGHT) || forceSight; bool hasInvisible = false; bool hasSneak = false; if (!PMob->m_TrueDetection) { hasInvisible = PTarget->StatusEffectContainer->HasStatusEffectByFlag(EFFECTFLAG_INVISIBLE); hasSneak = PTarget->StatusEffectContainer->HasStatusEffect(EFFECT_SNEAK); } if (detectSight && !hasInvisible && currentDistance < PMob->getMobMod(MOBMOD_SIGHT_RANGE) && isFaceing(PMob->loc.p, PTarget->loc.p, 40)) { return CanSeePoint(PTarget->loc.p); } if ((PMob->m_Behaviour & BEHAVIOUR_AGGRO_AMBUSH) && currentDistance < 3 && !hasSneak) { return true; } if ((detects & DETECT_HEARING) && currentDistance < PMob->getMobMod(MOBMOD_SOUND_RANGE) && !hasSneak) { return CanSeePoint(PTarget->loc.p); } // everything below require distance to be below 20 if (currentDistance > 20) { return false; } if ((detects & DETECT_LOWHP) && PTarget->GetHPP() < 75) { return CanSeePoint(PTarget->loc.p); } if ((detects & DETECT_MAGIC) && PTarget->PAI->IsCurrentState<CMagicState>() && static_cast<CMagicState*>(PTarget->PAI->GetCurrentState())->GetSpell()->hasMPCost()) { return CanSeePoint(PTarget->loc.p); } if ((detects & DETECT_WEAPONSKILL) && PTarget->PAI->IsCurrentState<CWeaponSkillState>()) { return CanSeePoint(PTarget->loc.p); } if ((detects & DETECT_JOBABILITY) && PTarget->PAI->IsCurrentState<CAbilityState>()) { return CanSeePoint(PTarget->loc.p); } return false; }
bool CMobEntity::CanDetectTarget(CBattleEntity* PTarget, bool forceSight) { if (PTarget->isDead() || m_Aggro == AGGRO_NONE || PTarget->animation == ANIMATION_CHOCOBO) return false; float verticalDistance = abs(loc.p.y - PTarget->loc.p.y); if(verticalDistance > 8) { return false; } float currentDistance = distance(PTarget->loc.p, loc.p) + PTarget->getMod(MOD_STEALTH); bool detectSight = (m_Aggro & AGGRO_DETECT_SIGHT) || forceSight; if (detectSight && !PTarget->StatusEffectContainer->HasStatusEffectByFlag(EFFECTFLAG_INVISIBLE) && currentDistance < getMobMod(MOBMOD_SIGHT_RANGE) && isFaceing(loc.p, PTarget->loc.p, 40)) { return true; } if ((m_Aggro & AGGRO_DETECT_TRUESIGHT) && currentDistance < getMobMod(MOBMOD_SIGHT_RANGE) && isFaceing(loc.p, PTarget->loc.p, 40)) { return true; } if ((m_Aggro & AGGRO_DETECT_TRUEHEARING) && currentDistance < getMobMod(MOBMOD_SOUND_RANGE)) { return true; } if ((m_Behaviour & BEHAVIOUR_AGGRO_AMBUSH) && currentDistance < 3 && !PTarget->StatusEffectContainer->HasStatusEffect(EFFECT_SNEAK)) { return true; } if ((m_Aggro & AGGRO_DETECT_HEARING) && currentDistance < getMobMod(MOBMOD_SOUND_RANGE) && !PTarget->StatusEffectContainer->HasStatusEffect(EFFECT_SNEAK)) { return true; } // everything below require distance to be below 20 if(currentDistance > 20) { return false; } if ((m_Aggro & AGGRO_DETECT_LOWHP) && PTarget->GetHPP() < 75) { return true; } if ((m_Aggro & AGGRO_DETECT_MAGIC) && PTarget->PBattleAI->GetCurrentAction() == ACTION_MAGIC_CASTING && PTarget->PBattleAI->GetCurrentSpell()->hasMPCost()) { return true; } if ((m_Aggro & AGGRO_DETECT_WEAPONSKILL) && PTarget->PBattleAI->GetCurrentAction() == ACTION_WEAPONSKILL_FINISH) { return true; } if ((m_Aggro & AGGRO_DETECT_JOBABILITY) && PTarget->PBattleAI->GetCurrentAction() == ACTION_JOBABILITY_FINISH) { return true; } return false; }
bool CRangeState::CanUseRangedAttack(CBattleEntity* PTarget) { if (!PTarget) { m_errorMsg = std::make_unique<CMessageBasicPacket>(m_PEntity, m_PEntity, 0, 0, MSGBASIC_CANNOT_ATTACK_TARGET); return false; } CItemWeapon* PRanged = (CItemWeapon*)m_PEntity->getEquip(SLOT_RANGED); CItemWeapon* PAmmo = (CItemWeapon*)m_PEntity->getEquip(SLOT_AMMO); if (!(PRanged && PRanged->isType(ITEM_WEAPON) || PAmmo && PAmmo->isThrowing())) { m_errorMsg = std::make_unique<CMessageBasicPacket>(m_PEntity, m_PEntity, 0, 0, MSGBASIC_NO_RANGED_WEAPON); return false; } auto SkillType = PRanged ? PRanged->getSkillType() : PAmmo->getSkillType(); switch (SkillType) { case SKILL_THR: { // remove barrage, doesn't work here m_PEntity->StatusEffectContainer->DelStatusEffect(EFFECT_BARRAGE); break; } case SKILL_ARC: case SKILL_MRK: { PRanged = (CItemWeapon*)m_PEntity->getEquip(SLOT_AMMO); if (PRanged != nullptr && PRanged->isType(ITEM_WEAPON)) { break; } } default: { m_errorMsg = std::make_unique<CMessageBasicPacket>(m_PEntity, m_PEntity, 0, 0, MSGBASIC_NO_RANGED_WEAPON); return false; } } if (!isFaceing(m_PEntity->loc.p, PTarget->loc.p, 40)) { m_errorMsg = std::make_unique<CMessageBasicPacket>(m_PEntity, PTarget, 0, 0, MSGBASIC_CANNOT_SEE); return false; } if (distance(m_PEntity->loc.p, PTarget->loc.p) > 25) { m_errorMsg = std::make_unique<CMessageBasicPacket>(m_PEntity, PTarget, 0, 0, MSGBASIC_TOO_FAR_AWAY); return false; } if (!m_PEntity->PAI->TargetFind->canSee(&PTarget->loc.p)) { m_errorMsg = std::make_unique<CMessageBasicPacket>(m_PEntity, PTarget, 0, 0, MSGBASIC_CANNOT_PERFORM_ACTION); return false; } return true; }
void CPlayerController::WeaponSkill(uint16 targid, uint16 wsid) { auto PChar = static_cast<CCharEntity*>(POwner); if (PChar->PAI->CanChangeState()) { //#TODO: put all this in weaponskill_state CWeaponSkill* PWeaponSkill = battleutils::GetWeaponSkill(wsid); if (PWeaponSkill && !charutils::hasWeaponSkill(PChar, PWeaponSkill->getID())) { PChar->pushPacket(new CMessageBasicPacket(PChar, PChar, 0, 0, MSGBASIC_CANNOT_USE_WS)); return; } if (PChar->StatusEffectContainer->HasStatusEffect(EFFECT_AMNESIA)) { PChar->pushPacket(new CMessageBasicPacket(PChar, PChar, 0, 0, MSGBASIC_CANNOT_USE_ANY_WS)); return; } if (PChar->health.tp < 1000) { PChar->pushPacket(new CMessageBasicPacket(PChar, PChar, 0, 0, MSGBASIC_NOT_ENOUGH_TP)); return; } if (PWeaponSkill->getType() == SKILL_ARC || PWeaponSkill->getType() == SKILL_MRK) { CItemWeapon* PItem = (CItemWeapon*)PChar->getEquip(SLOT_AMMO); // before allowing ranged weapon skill... if (PItem == nullptr || !(PItem->isType(ITEM_WEAPON)) || !PChar->m_Weapons[SLOT_AMMO]->isRanged() || !PChar->m_Weapons[SLOT_RANGED]->isRanged() || PChar->equip[SLOT_AMMO] == 0) { PChar->pushPacket(new CMessageBasicPacket(PChar, PChar, 0, 0, MSGBASIC_NO_RANGED_WEAPON)); return; } } std::unique_ptr<CMessageBasicPacket> errMsg; auto PTarget = PChar->IsValidTarget(targid, battleutils::isValidSelfTargetWeaponskill(wsid) ? TARGET_SELF : TARGET_ENEMY, errMsg); if (PTarget) { if (!isFaceing(PChar->loc.p, PTarget->loc.p, 40)) { PChar->pushPacket(new CMessageBasicPacket(PChar, PTarget, 0, 0, MSGBASIC_CANNOT_SEE)); return; } CController::WeaponSkill(targid, wsid); } else if (errMsg) { PChar->pushPacket(std::move(errMsg)); } } else { PChar->pushPacket(new CMessageBasicPacket(PChar, PChar, 0, 0, MSGBASIC_UNABLE_TO_USE_WS)); } }