// PORT: auto hunt. 40250 has no counterpart; see PythonPlayer.h. // // Everything here goes through the paths a player's own input takes: the auto-attack target // (__SetAutoAttackTargetActorID -> __Update_AutoAttack -> MODE_CLICK_ACTOR walks up and attacks), // __UseSkill for the independently selected skills, the MODE_CLICK_ITEM reserve that walks to a ground item and // sends the pick-up, and CG_ITEM_USE for potions. Cloak-style pulling is a user-requested server extension // sent through CG_CHAT; it does not spoof item use or client-side monster movement. #include "StdAfx.h" #include "PythonPlayer.h" #include "PythonItem.h" #include "PythonNonPlayer.h" #include "PythonBackground.h" #include "PythonCharacterManager.h" #include "PythonNetworkStream.h" #include "InstanceBase.h" #include "../GameLib/ItemManager.h" #include "../EterBase/Timer.h" #include #include #include #include #include #include "AutoHuntNavigation.h" #include "../EterLib/CullingManager.h" #include "../EterLib/GrpObjectInstance.h" #ifdef __ANDROID__ #include #endif namespace { const char c_szAutoHuntConfigFileName[] = "autohunt.cfg"; const float c_fAutoHuntResumeDelay = 3.0f; // after the player's own input const float c_fAutoHuntNoProgressTime = 4.0f; // neither closer to the target nor hitting it const float c_fAutoHuntTargetTimeout = 60.0f; const float c_fAutoHuntSkipTime = 30.0f; // an unreachable target is left alone this long const float c_fAutoHuntLootDelay = 0.6f; // drops land a moment after the kill const float c_fAutoHuntLootTimeout = 15.0f; const float c_fAutoHuntItemTimeout = 5.0f; // one ground item (someone else's, blocked path) const float c_fAutoHuntItemSkipTime = 60.0f; const float c_fAutoHuntItemRange = 1500.0f; const float c_fAutoHuntReturnDistance = 300.0f; const float c_fAutoHuntReturnTimeout = 20.0f; const float c_fAutoHuntSkillDistance = 400.0f; // melee skills are cast once this close const BYTE c_byMobRankBoss = 4; // PAWN, S_PAWN, KNIGHT, S_KNIGHT, BOSS, KING const DWORD c_dwGoldVnum = 1; const float c_fAutoHuntPullCooldown = 10.0f; const float c_fAutoHuntPullReplyTimeout = 3.0f; const float c_fAutoHuntPullApproachTimeout = 8.0f; const int c_iAutoHuntGatherHPPercent = 50; // gather: stop pulling below this // Trace: stderr with MT_AUTOHUNT_TRACE=1, logcat (tag mt_autohunt) on Android. void AutoHuntTrace(const char* c_szFormat, ...) { static const bool s_isTrace = getenv("MT_AUTOHUNT_TRACE") != NULL; va_list args; #ifdef __ANDROID__ va_start(args, c_szFormat); __android_log_vprint(ANDROID_LOG_INFO, "mt_autohunt", c_szFormat, args); va_end(args); #endif if (!s_isTrace) return; va_start(args, c_szFormat); fprintf(stderr, "autohunt: "); vfprintf(stderr, c_szFormat, args); fprintf(stderr, "\n"); va_end(args); } float PixelDistance(const TPixelPosition& a, const TPixelPosition& b) { const float dx = a.x - b.x; const float dy = a.y - b.y; return sqrtf(dx * dx + dy * dy); } } void CPythonPlayer::AutoHunt_SetEnabled(bool isEnable) { if (!isEnable) { if (AutoHunt_IsEnabled()) __AutoHunt_Stop(AUTO_HUNT_STOP_USER); return; } CInstanceBase* pkInstMain = NEW_GetMainActorPtr(); if (!pkInstMain || pkInstMain->IsDead()) return; __AutoHunt_LoadConfig(); __AutoHunt_MigrateSkills(); __AutoHunt_ResetMovement(); m_iAutoHuntMountState = 0; pkInstMain->NEW_GetPixelPosition(&m_kPPosAutoHuntAnchor); m_stAutoHuntMapName = CPythonBackground::Instance().GetWarpMapName(); m_eAutoHuntState = AUTO_HUNT_SEARCH; m_eAutoHuntStopReason = AUTO_HUNT_STOP_NONE; m_dwAutoHuntTargetVID = 0; m_dwAutoHuntItemID = 0; m_fAutoHuntStateTime = CTimer::Instance().GetCurrentSecond(); m_fAutoHuntNextActionTime = 0.0f; m_fAutoHuntNextSkillTime = 0.0f; m_fAutoHuntNextPotionTime = 0.0f; m_isAutoHuntNoHPPotion = false; m_isAutoHuntNoSPPotion = false; m_kMap_dwAutoHuntSkipVID.clear(); m_kMap_dwAutoHuntSkipItem.clear(); m_kSet_dwAutoHuntGathered.clear(); m_isAutoHuntGathering = false; m_iAutoHuntGatheredGroups = 0; } bool CPythonPlayer::AutoHunt_IsEnabled() { return AUTO_HUNT_OFF != m_eAutoHuntState; } int CPythonPlayer::AutoHunt_GetState() { return m_eAutoHuntState; } int CPythonPlayer::AutoHunt_GetStopReason() { return m_eAutoHuntStopReason; } bool CPythonPlayer::AutoHunt_IsGathering() { return AutoHunt_IsEnabled() && m_isAutoHuntGathering; } int CPythonPlayer::AutoHunt_GetGatheredCount() { return AutoHunt_IsEnabled() ? m_iAutoHuntGatheredGroups : 0; } bool CPythonPlayer::AutoHunt_HasNoPotion(bool isHP) { return isHP ? m_isAutoHuntNoHPPotion : m_isAutoHuntNoSPPotion; } const CPythonPlayer::SAutoHuntConfig& CPythonPlayer::AutoHunt_GetConfig() { __AutoHunt_LoadConfig(); return m_kAutoHuntConfig; } void CPythonPlayer::AutoHunt_SetConfig(const SAutoHuntConfig& c_rkConfig) { m_isAutoHuntConfigLoaded = true; if (AutoHunt_IsEnabled() && (m_kAutoHuntConfig.iGatherCount != c_rkConfig.iGatherCount || m_kAutoHuntConfig.bAttackBoss != c_rkConfig.bAttackBoss || m_kAutoHuntConfig.iRange != c_rkConfig.iRange)) { __AutoHunt_CancelPull(); ++m_dwAutoHuntPullRequest; // invalidate replies for the previous selection m_isAutoHuntGathering = false; m_kSet_dwAutoHuntGathered.clear(); m_iAutoHuntGatheredGroups = 0; } m_kAutoHuntConfig = c_rkConfig; __AutoHunt_ResetMovement(); m_kAutoHuntConfig.iRange = MINMAX(3500, m_kAutoHuntConfig.iRange, 15000); m_kAutoHuntConfig.iHPPercent = MINMAX(0, m_kAutoHuntConfig.iHPPercent, 95); m_kAutoHuntConfig.iSPPercent = MINMAX(0, m_kAutoHuntConfig.iSPPercent, 95); m_kAutoHuntConfig.iPickup = MINMAX(0, m_kAutoHuntConfig.iPickup, int(AUTO_HUNT_PICKUP_GOLD_EQUIP)); m_kAutoHuntConfig.dwSkillSlotMask &= (1 << AUTO_HUNT_SKILL_SLOT_NUM) - 1; m_kAutoHuntConfig.iGatherCount = m_kAutoHuntConfig.iGatherCount <= 0 ? 0 : (m_kAutoHuntConfig.iGatherCount <= 3 ? 3 : (m_kAutoHuntConfig.iGatherCount <= 10 ? 10 : 20)); __AutoHunt_SaveConfig(); } void CPythonPlayer::__AutoHunt_LoadConfig() { if (m_isAutoHuntConfigLoaded) return; m_isAutoHuntConfigLoaded = true; FILE* fp = fopen(c_szAutoHuntConfigFileName, "rt"); if (!fp) return; SAutoHuntConfig config = m_kAutoHuntConfig; char header[32] = {}; int stone=0, boss=0, revive=0, dismount=1, count=0; unsigned mask=0; if (fscanf(fp, "%31s", header)==1 && !strcmp(header,"AUTOHUNT2")) { if (fscanf(fp,"%d %d %d %d %d %d %d %d %d %d", &config.iRange,&config.iHPPercent, &config.iSPPercent,&config.iPickup,&stone,&boss,&config.iGatherCount,&revive,&dismount,&count)==10 && count>=0 && count<=32) { config.vecSkillIndices.clear(); for (int i=0;i=7) { if (read==7) config.iGatherCount=0; config.bAttackStone=stone!=0; config.bAttackBoss=boss!=0; config.dwSkillSlotMask=mask; m_kAutoHuntConfig=config; m_isAutoHuntLegacySkills=true; } } fclose(fp); } void CPythonPlayer::__AutoHunt_SaveConfig() { // Defer legacy migration until the game's skill/quickslot packets have arrived. if (m_isAutoHuntLegacySkills) return; FILE* fp = fopen(c_szAutoHuntConfigFileName, "wt"); if (!fp) return; fprintf(fp,"AUTOHUNT2\n%d %d %d %d %d %d %d %d %d %u\n", m_kAutoHuntConfig.iRange, m_kAutoHuntConfig.iHPPercent,m_kAutoHuntConfig.iSPPercent,m_kAutoHuntConfig.iPickup, m_kAutoHuntConfig.bAttackStone,m_kAutoHuntConfig.bAttackBoss,m_kAutoHuntConfig.iGatherCount, m_kAutoHuntConfig.bAutoRevive,m_kAutoHuntConfig.bDismountSkill,unsigned(m_kAutoHuntConfig.vecSkillIndices.size())); for (DWORD index:m_kAutoHuntConfig.vecSkillIndices) fprintf(fp,"%u ",unsigned(index)); fprintf(fp,"\n"); fclose(fp); } std::vector CPythonPlayer::AutoHunt_GetAvailableSkills() { __AutoHunt_LoadConfig(); __AutoHunt_MigrateSkills(); std::vector indices; for (DWORD slot=0;slotbyType==CPythonSkill::SKILL_TYPE_ACTIVE || data->byType==CPythonSkill::SKILL_TYPE_HORSE) && data->IsCanUseSkill() && std::find(indices.begin(),indices.end(),instance.dwIndex)==indices.end()) indices.push_back(instance.dwIndex); } return indices; } void CPythonPlayer::__AutoHunt_MigrateSkills() { if (!m_isAutoHuntLegacySkills) return; m_kAutoHuntConfig.vecSkillIndices.clear(); for (DWORD i=0;i& indices) { __AutoHunt_LoadConfig(); m_isAutoHuntLegacySkills=false; std::vector clean; for (DWORD index:indices) { CPythonSkill::TSkillData* data=nullptr; if (clean.size()>=32) break; if (index && CPythonSkill::Instance().GetSkillData(index,&data) && data->IsCanUseSkill() && (data->byType==CPythonSkill::SKILL_TYPE_ACTIVE || data->byType==CPythonSkill::SKILL_TYPE_HORSE) && std::find(clean.begin(),clean.end(),index)==clean.end()) clean.push_back(index); } m_kAutoHuntConfig.vecSkillIndices=clean; __AutoHunt_SaveConfig(); } void CPythonPlayer::__AutoHunt_CancelPull() { if (AutoHunt_IsEnabled() && m_kAutoHuntConfig.iGatherCount > 0) { char command[96]; snprintf(command, sizeof(command), "/autohunt_pull 0 %d %d %u", m_kAutoHuntConfig.iRange, m_kAutoHuntConfig.bAttackBoss ? 1 : 0, unsigned(m_dwAutoHuntPullRequest)); CPythonNetworkStream::Instance().SendChatPacket(command); } } void CPythonPlayer::__AutoHunt_Stop(int eReason) { __AutoHunt_CancelPull(); __ClearAutoAttackTargetActorID(); __ClearReservedAction(); __AutoHunt_ResetMovement(); m_iAutoHuntMountState = 0; if (auto* main=NEW_GetMainActorPtr()) main->NEW_Stop(); m_eAutoHuntState = AUTO_HUNT_OFF; m_eAutoHuntStopReason = eReason; m_dwAutoHuntTargetVID = 0; m_dwAutoHuntItemID = 0; m_isAutoHuntGathering = false; m_kSet_dwAutoHuntGathered.clear(); } // Direction keys, the touch stick, a world click or the attack key: the player takes over, and the // hunt picks up again from wherever they leave the character once they stop for a moment. void CPythonPlayer::__AutoHunt_OnManualInput() { if (!AutoHunt_IsEnabled()) return; __AutoHunt_ResetMovement(); m_iAutoHuntMountState = 0; // manual control also cancels pending mount/skill commands if (AUTO_HUNT_REVIVE == m_eAutoHuntState || AUTO_HUNT_RECOVER == m_eAutoHuntState) { __AutoHunt_Stop(AUTO_HUNT_STOP_USER); return; } if (AUTO_HUNT_PAUSED != m_eAutoHuntState) { __ClearAutoAttackTargetActorID(); __ClearReservedAction(); m_dwAutoHuntTargetVID = 0; m_dwAutoHuntItemID = 0; m_eAutoHuntState = AUTO_HUNT_PAUSED; m_isAutoHuntGathering = false; } m_fAutoHuntResumeTime = CTimer::Instance().GetCurrentSecond() + c_fAutoHuntResumeDelay; } void CPythonPlayer::__AutoHunt_Engage(DWORD dwVID, float fNow) { m_dwAutoHuntTargetVID = dwVID; m_eAutoHuntState = AUTO_HUNT_ENGAGE; m_fAutoHuntStateTime = fNow; m_fAutoHuntProgressTime = fNow; m_fAutoHuntBestDistance = 1e9f; m_fAutoHuntNextActionTime = fNow + 0.5f; SetTarget(dwVID); __ClearReservedAction(); __ClearAutoAttackTargetActorID(); __AutoHunt_ResetMovement(); } // PORT: stones have their own option; their mob_proto rank must not count as a boss. bool CPythonPlayer::__AutoHunt_IsTargetAllowed(CInstanceBase& main, CInstanceBase& target) { if (&main == &target || target.IsDead() || target.IsInSafe() || !main.IsAttackableInstance(target)) return false; if (target.IsStone()) return m_kAutoHuntConfig.bAttackStone; if (!target.IsEnemy()) return false; const auto* mob = CPythonNonPlayer::Instance().GetTable(target.GetRace()); return m_kAutoHuntConfig.bAttackBoss || !mob || mob->bRank < c_byMobRankBoss; } // PORT: cloak collection and the server pursuit leash follow the hunting range. bool CPythonPlayer::__AutoHunt_RequestPull(CInstanceBase& main, float now) { const int maxHP = GetStatus(POINT_MAX_HP); if (m_kAutoHuntConfig.iGatherCount <= 0 || m_isAutoHuntGathering || now < m_fAutoHuntNextPullTime || main.IsInSafe() || maxHP <= 0 || GetStatus(POINT_HP) * 100 < maxHP * c_iAutoHuntGatherHPPercent) return false; if (m_kAutoHuntConfig.bAttackStone) { auto* target = NEW_FindActorPtr(__AutoHunt_FindTarget(main, now)); if (target && target->IsStone()) return false; } const int radius = m_kAutoHuntConfig.iRange; char command[128]; ++m_dwAutoHuntPullRequest; snprintf(command, sizeof(command), "/autohunt_pull %d %d %d %u", m_kAutoHuntConfig.iGatherCount, radius, m_kAutoHuntConfig.bAttackBoss ? 1 : 0, unsigned(m_dwAutoHuntPullRequest)); m_fAutoHuntNextPullTime = now + c_fAutoHuntPullCooldown; if (!CPythonNetworkStream::Instance().SendChatPacket(command)) return false; m_kSet_dwAutoHuntGathered.clear(); m_iAutoHuntGatheredGroups = 0; m_isAutoHuntGathering = true; m_fAutoHuntGatherStartTime = now; AutoHuntTrace("cloak request: %d groups, radius %d", m_kAutoHuntConfig.iGatherCount, radius); return true; } DWORD CPythonPlayer::__AutoHunt_FindTarget(CInstanceBase& rkInstMain, float fNow) { CPythonCharacterManager& rkChrMgr = CPythonCharacterManager::Instance(); if (fNow=skip->second)) return m_dwAutoHuntScanTarget; } m_fAutoHuntTargetScanTime=fNow+0.5f; m_dwAutoHuntScanTarget=0; std::vector candidates; for (auto i=rkChrMgr.CharacterInstanceBegin();i!=rkChrMgr.CharacterInstanceEnd();++i) { CInstanceBase* target=*i; if (!target || !__AutoHunt_IsTargetAllowed(rkInstMain,*target)) continue; const auto skip=m_kMap_dwAutoHuntSkipVID.find(target->GetVirtualID()); if (skip!=m_kMap_dwAutoHuntSkipVID.end() && fNowsecond) continue; TPixelPosition position; target->NEW_GetPixelPosition(&position); if (PixelDistance(position,m_kPPosAutoHuntAnchor)<=m_kAutoHuntConfig.iRange) candidates.push_back(target); } std::sort(candidates.begin(),candidates.end(),[&](CInstanceBase* a,CInstanceBase* b) { if (a->IsStone()!=b->IsStone()) return bool(a->IsStone()); return rkInstMain.GetDistance(a)4) break; TPixelPosition goal; target->NEW_GetPixelPosition(&goal); std::vector path; if (__AutoHunt_BuildPath(goal,100.f,path)) return m_dwAutoHuntScanTarget=target->GetVirtualID(); m_kMap_dwAutoHuntSkipVID[target->GetVirtualID()]=fNow+5.f; } return 0; } // Gather mode: the nearest pulled mob still alive. DWORD CPythonPlayer::__AutoHunt_FindGatheredTarget(CInstanceBase& rkInstMain) { std::vector targets; const float now=CTimer::Instance().GetCurrentSecond(); for (DWORD vid:m_kSet_dwAutoHuntGathered) { auto* target=NEW_FindActorPtr(vid); const auto skip=m_kMap_dwAutoHuntSkipVID.find(vid); if (target && __AutoHunt_IsTargetAllowed(rkInstMain,*target) && (skip==m_kMap_dwAutoHuntSkipVID.end() || now>=skip->second)) targets.push_back(target); } std::sort(targets.begin(),targets.end(),[&](CInstanceBase* a,CInstanceBase* b) {return rkInstMain.GetDistance(a)4) break; TPixelPosition goal; target->NEW_GetPixelPosition(&goal); std::vector path; if (__AutoHunt_BuildPath(goal,100.f,path)) return target->GetVirtualID(); m_kMap_dwAutoHuntSkipVID[target->GetVirtualID()]=now+5.f; } return 0; } // Dead or out of sight. void CPythonPlayer::__AutoHunt_PurgeGathered() { for (std::set::iterator i = m_kSet_dwAutoHuntGathered.begin(); i != m_kSet_dwAutoHuntGathered.end();) { CInstanceBase* pkInst = NEW_FindActorPtr(*i); if ((pkInst && pkInst->IsDead()) || (!pkInst && CTimer::Instance().GetCurrentSecond() - m_fAutoHuntGatherStartTime >= std::max(c_fAutoHuntPullApproachTimeout, m_kAutoHuntConfig.iRange / 100.0f))) i = m_kSet_dwAutoHuntGathered.erase(i); else ++i; } } // PORT: only the server can establish aggro. Replies carry a generation so stopped, // paused or previous requests cannot repopulate the current pack. bool CPythonPlayer::AutoHunt_HandleServerCommand(const char* command) { if (strncmp(command, "AutoHuntGather ", 15)) return false; unsigned int request = 0, value = 0; char kind[8] = {}, extra = 0; if (sscanf(command + 15, "%u %7s %u %c", &request, kind, &value, &extra) != 3) return true; if (!AutoHunt_IsEnabled() || !m_isAutoHuntGathering || request != m_dwAutoHuntPullRequest) return true; if (!strcmp(kind, "mob")) { if (value && m_kSet_dwAutoHuntGathered.size() < 1024) m_kSet_dwAutoHuntGathered.insert(value); } else if (!strcmp(kind, "done")) { m_iAutoHuntGatheredGroups = MIN(value, unsigned(m_kAutoHuntConfig.iGatherCount)); m_isAutoHuntGathering = false; m_fAutoHuntNextActionTime = 0.0f; AutoHuntTrace("cloak pull: %d groups, %u mobs", m_iAutoHuntGatheredGroups, unsigned(m_kSet_dwAutoHuntGathered.size())); } return true; } DWORD CPythonPlayer::__AutoHunt_FindItem(CInstanceBase& rkInstMain, float fNow) { CPythonItem& rkItem = CPythonItem::Instance(); std::vector kVecVID; rkItem.GetGroundItemVIDs(&kVecVID); TPixelPosition kPPosMain; rkInstMain.NEW_GetPixelPosition(&kPPosMain); const float fRange = MIN(float(m_kAutoHuntConfig.iRange), c_fAutoHuntItemRange); DWORD dwBestID = 0; float fBestDistance = 1e9f; for (DWORD dwID : kVecVID) { std::map::iterator f = m_kMap_dwAutoHuntSkipItem.find(dwID); if (f != m_kMap_dwAutoHuntSkipItem.end() && fNow < f->second) continue; // Someone else's drop: the server refuses it until the ownership runs out. const char* c_szOwner = NULL; if (rkItem.GetOwnership(dwID, &c_szOwner) && c_szOwner && *c_szOwner && m_stName != c_szOwner) continue; if (AUTO_HUNT_PICKUP_GOLD_EQUIP == m_kAutoHuntConfig.iPickup) { const DWORD dwVnum = rkItem.GetVirtualNumberOfGroundItem(dwID); CItemData* pItemData = NULL; const bool isGold = c_dwGoldVnum == dwVnum; const bool isEquip = CItemManager::Instance().GetItemDataPointer(dwVnum, &pItemData) && pItemData && (CItemData::ITEM_TYPE_WEAPON == pItemData->GetType() || CItemData::ITEM_TYPE_ARMOR == pItemData->GetType() || CItemData::ITEM_TYPE_ELK == pItemData->GetType()); if (!isGold && !isEquip) continue; } TPixelPosition kPPosItem; if (!rkItem.GetGroundItemPosition(dwID, &kPPosItem)) continue; const float fDistance = PixelDistance(kPPosItem, kPPosMain); if (fDistance > fRange) continue; if (fDistance < fBestDistance) { fBestDistance = fDistance; dwBestID = dwID; } } return dwBestID; } // uiinventory's cells: a big item covers the cells below its own, page by page. bool CPythonPlayer::__AutoHunt_IsInventoryFull() { const DWORD c_dwColumns = 5; bool abCovered[c_ItemSlot_Count] = {}; for (DWORD i = 0; i < c_ItemSlot_Count; ++i) { const DWORD dwVnum = GetItemIndex(TItemPos(INVENTORY, WORD(i))); if (!dwVnum) continue; DWORD dwSize = 1; CItemData* pItemData = NULL; if (CItemManager::Instance().GetItemDataPointer(dwVnum, &pItemData) && pItemData) dwSize = MAX(1, pItemData->GetSize()); const DWORD dwPageEnd = (i / c_Inventory_Page_Size + 1) * c_Inventory_Page_Size; for (DWORD k = 0; k < dwSize && i + k * c_dwColumns < dwPageEnd; ++k) abCovered[i + k * c_dwColumns] = true; } for (DWORD i = 0; i < c_ItemSlot_Count; ++i) if (!abCovered[i]) return false; return true; } // __CheckSkillUsable without its "cannot use" chat lines: the hunt only casts what will go through. bool CPythonPlayer::__AutoHunt_IsSkillReady(DWORD dwSkillSlot, CPythonSkill::TSkillData** ppSkillData) { if (dwSkillSlot >= SKILL_MAX_NUM) return false; CInstanceBase* pkInstMain = NEW_GetMainActorPtr(); if (!pkInstMain) return false; TSkillInstance& rkSkillInst = m_playerStatus.aSkill[dwSkillSlot]; if (!rkSkillInst.dwIndex || rkSkillInst.iLevel <= 0) return false; CPythonSkill::TSkillData* pSkillData; if (!CPythonSkill::Instance().GetSkillData(rkSkillInst.dwIndex, &pSkillData)) return false; if (CPythonSkill::SKILL_TYPE_ACTIVE != pSkillData->byType && CPythonSkill::SKILL_TYPE_HORSE != pSkillData->byType) return false; if (!pSkillData->IsCanUseSkill()) return false; if (pSkillData->IsHorseSkill() && !pkInstMain->IsMountingHorse()) return false; if (!pSkillData->IsHorseSkill() && pkInstMain->IsMountingHorse() && (!m_kAutoHuntConfig.bDismountSkill || CTimer::Instance().GetCurrentSecond()CanUseWeaponType(pkInstMain->GetWeaponType())) return false; if (pSkillData->IsNeedBow() && !__HasEnoughArrow()) return false; if (pSkillData->IsNeedEmptyBottle() && !__HasItem(27995)) return false; if (pSkillData->IsNeedPoisonBottle() && !__HasItem(27996)) return false; if (__CheckRestSkillCoolTime(dwSkillSlot)) return false; if (__CheckShortLife(rkSkillInst, *pSkillData)) return false; const int iNeedSP = pSkillData->GetNeedSP(rkSkillInst.fcurEfficientPercentage); const int iCurSP = GetStatus(POINT_SP); if (pSkillData->CanUseIfNotEnough() ? iCurSP <= 0 : iCurSP < iNeedSP) return false; *ppSkillData = pSkillData; return true; } // Selected self buffs whose effect is not on the character. bool CPythonPlayer::__AutoHunt_UpdateBuff(CInstanceBase& rkInstMain, float fNow) { if (fNow < m_fAutoHuntNextSkillTime || rkInstMain.IsUsingSkill() || rkInstMain.IsInSafe()) return false; for (DWORD index : m_kAutoHuntConfig.vecSkillIndices) { DWORD dwSkillSlot = 0; if (!GetSkillSlotIndex(index, &dwSkillSlot)) continue; CPythonSkill::TSkillData* pSkillData; if (!__AutoHunt_IsSkillReady(dwSkillSlot, &pSkillData)) continue; if (pSkillData->IsAttackSkill()) continue; if (!pSkillData->IsStandingSkill() && !pSkillData->CanUseForMe()) continue; if (pSkillData->IsToggleSkill()) { if (IsSkillActive(dwSkillSlot)) continue; } else { bool isActive = false; const DWORD dwSkillIndex = m_playerStatus.aSkill[dwSkillSlot].dwIndex; for (std::map::iterator f = m_kMap_dwAffectIndexToSkillIndex.begin(); f != m_kMap_dwAffectIndexToSkillIndex.end(); ++f) if (f->second == dwSkillIndex && rkInstMain.IsAffect(f->first)) isActive = true; if (isActive) continue; } m_fAutoHuntNextSkillTime = fNow + 0.5f; if (__AutoHunt_CastSkill(dwSkillSlot, fNow)) return true; } return false; } void CPythonPlayer::__AutoHunt_UpdateAttackSkill(CInstanceBase& rkInstMain, CInstanceBase& rkInstVictim, float fNow) { if (fNow < m_fAutoHuntNextSkillTime || rkInstMain.IsUsingSkill()) return; m_fAutoHuntNextSkillTime = fNow + 0.3f; const float fDistance = rkInstMain.GetDistance(&rkInstVictim); for (DWORD index : m_kAutoHuntConfig.vecSkillIndices) { DWORD dwSkillSlot = 0; if (!GetSkillSlotIndex(index, &dwSkillSlot)) continue; CPythonSkill::TSkillData* pSkillData; if (!__AutoHunt_IsSkillReady(dwSkillSlot, &pSkillData) || !pSkillData->IsAttackSkill()) continue; // Ranged skills fire from their range; the rest once the auto attack has walked up. const float fSkillRange = MAX(float(pSkillData->GetTargetRange()), c_fAutoHuntSkillDistance); if (fDistance > fSkillRange) continue; SetTarget(rkInstVictim.GetVirtualID()); if (__AutoHunt_CastSkill(dwSkillSlot, fNow)) { m_fAutoHuntNextSkillTime = fNow + 0.5f; return; } } } // uitaskbar's potions: the first USE_POTION stack in the inventory that restores what is low. void CPythonPlayer::__AutoHunt_UpdatePotion(CInstanceBase& rkInstMain, float fNow) { if (fNow < m_fAutoHuntNextPotionTime) return; m_fAutoHuntNextPotionTime = fNow + 0.5f; for (int iValue = 0; iValue < 2; ++iValue) { const bool isHP = 0 == iValue; int iPercent = isHP ? m_kAutoHuntConfig.iHPPercent : m_kAutoHuntConfig.iSPPercent; if (isHP && iPercent>0 && m_eAutoHuntState==AUTO_HUNT_RECOVER) iPercent=MAX(iPercent,70); const int iMax = GetStatus(isHP ? POINT_MAX_HP : POINT_MAX_SP); const int iCur = GetStatus(isHP ? POINT_HP : POINT_SP); bool& isNoPotion = isHP ? m_isAutoHuntNoHPPotion : m_isAutoHuntNoSPPotion; if (iPercent <= 0 || iMax <= 0 || iCur * 100 >= iMax * iPercent) continue; isNoPotion = true; for (DWORD i = 0; i < c_ItemSlot_Count; ++i) { const TItemPos kCell(INVENTORY, WORD(i)); CItemData* pItemData = NULL; if (!CItemManager::Instance().GetItemDataPointer(GetItemIndex(kCell), &pItemData) || !pItemData) continue; if (CItemData::ITEM_TYPE_USE != pItemData->GetType() || CItemData::USE_POTION != pItemData->GetSubType()) continue; if (pItemData->GetValue(iValue) <= 0) continue; isNoPotion = false; CPythonNetworkStream::Instance().SendItemUsePacket(kCell); m_fAutoHuntNextPotionTime = fNow + 1.0f; return; } } } void CPythonPlayer::__Update_AutoHunt() { static int s_iLastState = AUTO_HUNT_OFF; if (s_iLastState != m_eAutoHuntState) { AutoHuntTrace("state %d -> %d (reason %d, target %u, item %u, gathering %d, gathered %d)", s_iLastState, m_eAutoHuntState, m_eAutoHuntStopReason, (unsigned)m_dwAutoHuntTargetVID, (unsigned)m_dwAutoHuntItemID, m_isAutoHuntGathering ? 1 : 0, int(m_kSet_dwAutoHuntGathered.size())); s_iLastState = m_eAutoHuntState; } if (!AutoHunt_IsEnabled()) return; CInstanceBase* pkInstMain = NEW_GetMainActorPtr(); if (!pkInstMain) return; CInstanceBase& rkInstMain = *pkInstMain; const float fNow = CTimer::Instance().GetCurrentSecond(); if (m_stAutoHuntMapName != CPythonBackground::Instance().GetWarpMapName()) { __AutoHunt_Stop(AUTO_HUNT_STOP_MAP); return; } if (rkInstMain.IsDead()) { AutoHunt_OnDeath(); if (AutoHunt_IsEnabled()) __AutoHunt_UpdateRecovery(rkInstMain,fNow); return; } if (__AutoHunt_UpdateRecovery(rkInstMain,fNow)) return; __AutoHunt_UpdatePotion(rkInstMain, fNow); if (m_isAutoHuntGathering && fNow - m_fAutoHuntGatherStartTime >= c_fAutoHuntPullReplyTimeout) { m_isAutoHuntGathering = false; AutoHuntTrace("cloak pull reply timed out; continue ordinary hunt"); } __AutoHunt_PurgeGathered(); if (IsOpenPrivateShop() || IsObserverMode() || rkInstMain.IsFishingMode()) return; if (__AutoHunt_UpdateMount(rkInstMain,fNow)) return; switch (m_eAutoHuntState) { case AUTO_HUNT_PAUSED: { if (fNow < m_fAutoHuntResumeTime) return; rkInstMain.NEW_GetPixelPosition(&m_kPPosAutoHuntAnchor); m_eAutoHuntState = AUTO_HUNT_SEARCH; m_fAutoHuntStateTime = fNow; break; } case AUTO_HUNT_SEARCH: { if (__AutoHunt_UpdateBuff(rkInstMain, fNow)) return; if (fNow < m_fAutoHuntNextActionTime || rkInstMain.IsUsingSkill()) return; m_fAutoHuntNextActionTime = fNow + 0.25f; if (m_isAutoHuntGathering) return; const DWORD preferred = __AutoHunt_FindTarget(rkInstMain, fNow); CInstanceBase* preferredActor = NEW_FindActorPtr(preferred); if (preferredActor && preferredActor->IsStone()) { __AutoHunt_Engage(preferred, fNow); return; } if (__AutoHunt_RequestPull(rkInstMain, fNow)) { __ClearAutoAttackTargetActorID(); __ClearReservedAction(); rkInstMain.NEW_Stop(); return; } const int maxHP = GetStatus(POINT_MAX_HP); if (DWORD dwVID = __AutoHunt_FindGatheredTarget(rkInstMain)) { CInstanceBase* pulled = NEW_FindActorPtr(dwVID); if (pulled && rkInstMain.GetDistance(pulled) > c_fAutoHuntSkillDistance && fNow - m_fAutoHuntGatherStartTime < c_fAutoHuntPullApproachTimeout && GetStatus(POINT_HP) * 100 >= maxHP * c_iAutoHuntGatherHPPercent) return; // let the server-controlled monsters approach the stationary player __AutoHunt_Engage(dwVID, fNow); return; } if (!m_kSet_dwAutoHuntGathered.empty() && fNow - m_fAutoHuntGatherStartTime < c_fAutoHuntPullApproachTimeout) return; if (DWORD dwVID = __AutoHunt_FindTarget(rkInstMain, fNow)) { __AutoHunt_Engage(dwVID, fNow); return; } // Nothing to fight: drops the post-kill window missed (a kill farther than the loot range). if (AUTO_HUNT_PICKUP_NONE != m_kAutoHuntConfig.iPickup && __AutoHunt_FindItem(rkInstMain, fNow)) { m_dwAutoHuntItemID = 0; m_eAutoHuntState = AUTO_HUNT_LOOT; m_fAutoHuntStateTime = fNow - c_fAutoHuntLootDelay; m_fAutoHuntNextActionTime = 0.0f; return; } TPixelPosition kPPosMain; rkInstMain.NEW_GetPixelPosition(&kPPosMain); if (PixelDistance(kPPosMain, m_kPPosAutoHuntAnchor) > c_fAutoHuntReturnDistance) { __AutoHunt_ResetMovement(); m_eAutoHuntState = AUTO_HUNT_RETURN; m_fAutoHuntStateTime = fNow; m_fAutoHuntNextActionTime = 0.0f; } break; } case AUTO_HUNT_RETURN: { if (fNow < m_fAutoHuntNextActionTime) return; m_fAutoHuntNextActionTime = fNow + 0.5f; if (DWORD dwVID = __AutoHunt_FindTarget(rkInstMain, fNow)) { __AutoHunt_Engage(dwVID, fNow); return; } TPixelPosition kPPosMain; rkInstMain.NEW_GetPixelPosition(&kPPosMain); if (PixelDistance(kPPosMain, m_kPPosAutoHuntAnchor) < c_fAutoHuntReturnDistance * 0.5f || fNow - m_fAutoHuntStateTime > c_fAutoHuntReturnTimeout) { rkInstMain.NEW_Stop(); m_eAutoHuntState = AUTO_HUNT_SEARCH; m_fAutoHuntStateTime = fNow; return; } if (!__AutoHunt_MoveTo(m_kPPosAutoHuntAnchor,100.f,fNow)) { rkInstMain.NEW_Stop(); m_eAutoHuntState=AUTO_HUNT_SEARCH; m_fAutoHuntNextActionTime=fNow+2.f; } break; } case AUTO_HUNT_ENGAGE: { CInstanceBase* pkInstVictim = NEW_FindActorPtr(m_dwAutoHuntTargetVID); if (!pkInstVictim || !__AutoHunt_IsTargetAllowed(rkInstMain, *pkInstVictim)) { __ClearAutoAttackTargetActorID(); __ClearReservedAction(); __AutoHunt_ResetMovement(); rkInstMain.NEW_Stop(); m_dwAutoHuntTargetVID = 0; m_dwAutoHuntItemID = 0; // The rest of the pack is still on the character: loot once it is down. __AutoHunt_PurgeGathered(); const bool isPackAlive = m_isAutoHuntGathering || !m_kSet_dwAutoHuntGathered.empty(); m_eAutoHuntState = AUTO_HUNT_PICKUP_NONE == m_kAutoHuntConfig.iPickup || isPackAlive ? AUTO_HUNT_SEARCH : AUTO_HUNT_LOOT; m_fAutoHuntStateTime = fNow; m_fAutoHuntNextActionTime = 0.0f; return; } if (m_kAutoHuntConfig.bAttackStone && !pkInstVictim->IsStone()) { const DWORD preferred = __AutoHunt_FindTarget(rkInstMain, fNow); CInstanceBase* actor = NEW_FindActorPtr(preferred); if (actor && actor->IsStone()) { __AutoHunt_Engage(preferred, fNow); return; } } // Keep the configured pack supplied even while attacking, without interrupting it. if (!pkInstVictim->IsStone()) __AutoHunt_RequestPull(rkInstMain, fNow); const float fDistance = rkInstMain.GetDistance(pkInstVictim); if (fDistance < m_fAutoHuntBestDistance - 30.0f) { m_fAutoHuntBestDistance = fDistance; m_fAutoHuntProgressTime = fNow; } if (rkInstMain.IsAttacking() || rkInstMain.IsUsingSkill()) { m_fAutoHuntProgressTime = fNow; } // Blocked path, a target out of reach, or a safe zone that refuses the attack. const bool isBlockedBySafeZone = rkInstMain.IsInSafe() && rkInstMain.NEW_IsClickableDistanceDestInstance(*pkInstVictim); if (isBlockedBySafeZone || fNow - m_fAutoHuntProgressTime > c_fAutoHuntNoProgressTime || fNow - m_fAutoHuntStateTime > c_fAutoHuntTargetTimeout) { m_kMap_dwAutoHuntSkipVID[m_dwAutoHuntTargetVID] = fNow + c_fAutoHuntSkipTime; __ClearAutoAttackTargetActorID(); __ClearReservedAction(); __AutoHunt_ResetMovement(); rkInstMain.NEW_Stop(); m_dwAutoHuntTargetVID = 0; m_eAutoHuntState = AUTO_HUNT_SEARCH; m_fAutoHuntStateTime = fNow; return; } TPixelPosition goal; pkInstVictim->NEW_GetPixelPosition(&goal); // Use the planner even in attack range: never swing or cast through a wall. if (!__AutoHunt_MoveTo(goal,100.f,fNow)) { m_kMap_dwAutoHuntSkipVID[m_dwAutoHuntTargetVID]=fNow+c_fAutoHuntSkipTime; __ClearAutoAttackTargetActorID(); __ClearReservedAction(); rkInstMain.NEW_Stop(); m_dwAutoHuntTargetVID=0; m_eAutoHuntState=AUTO_HUNT_SEARCH; return; } if (!m_vecAutoHuntPath.empty()) return; __AutoHunt_UpdateBuff(rkInstMain, fNow); if (m_iAutoHuntMountState) return; if (!m_isAutoHuntGathering) __AutoHunt_UpdateAttackSkill(rkInstMain, *pkInstVictim, fNow); if (m_iAutoHuntMountState) return; // A cast or an interrupted approach drops the auto-attack target; put it back. if (m_dwAutoAttackTargetVID != m_dwAutoHuntTargetVID && fNow >= m_fAutoHuntNextActionTime && !rkInstMain.IsUsingSkill() && MODE_USE_SKILL != m_eReservedMode) { m_fAutoHuntNextActionTime = fNow + 0.5f; SetTarget(m_dwAutoHuntTargetVID); __SetAutoAttackTargetActorID(m_dwAutoHuntTargetVID); } break; } case AUTO_HUNT_LOOT: { if (fNow - m_fAutoHuntStateTime < c_fAutoHuntLootDelay || fNow < m_fAutoHuntNextActionTime) return; m_fAutoHuntNextActionTime = fNow + 0.2f; CPythonItem& rkItem = CPythonItem::Instance(); TPixelPosition kPPosItem; if (m_dwAutoHuntItemID) { if (!rkItem.GetGroundItemPosition(m_dwAutoHuntItemID, &kPPosItem)) { m_dwAutoHuntItemID = 0; // picked up (or gone) } else if (fNow - m_fAutoHuntProgressTime > c_fAutoHuntItemTimeout) { m_kMap_dwAutoHuntSkipItem[m_dwAutoHuntItemID] = fNow + c_fAutoHuntItemSkipTime; m_dwAutoHuntItemID = 0; __ClearReservedAction(); } else { if (!__AutoHunt_MoveTo(kPPosItem,100.f,fNow)) { m_kMap_dwAutoHuntSkipItem[m_dwAutoHuntItemID]=fNow+c_fAutoHuntItemSkipTime; m_dwAutoHuntItemID=0; __AutoHunt_ResetMovement(); } else if (m_vecAutoHuntPath.empty() && MODE_CLICK_ITEM != m_eReservedMode) __ReserveClickItem(m_dwAutoHuntItemID); return; } } const DWORD dwItemID = fNow - m_fAutoHuntStateTime < c_fAutoHuntLootTimeout ? __AutoHunt_FindItem(rkInstMain, fNow) : 0; if (!dwItemID || !rkItem.GetGroundItemPosition(dwItemID, &kPPosItem)) { m_eAutoHuntState = AUTO_HUNT_SEARCH; m_fAutoHuntStateTime = fNow; m_fAutoHuntNextActionTime = 0.0f; return; } if (c_dwGoldVnum != rkItem.GetVirtualNumberOfGroundItem(dwItemID) && __AutoHunt_IsInventoryFull()) { __AutoHunt_Stop(AUTO_HUNT_STOP_INVENTORY_FULL); return; } // __OnPressItem without its once-per-item guard. m_dwAutoHuntItemID = dwItemID; m_fAutoHuntProgressTime = fNow; __ClearReservedAction(); if (rkInstMain.NEW_IsClickableDistanceDestPixelPosition(kPPosItem)) { rkInstMain.NEW_Stop(); SendClickItemPacket(dwItemID); } else { __AutoHunt_ResetMovement(); if (!__AutoHunt_MoveTo(kPPosItem,100.f,fNow)) { m_kMap_dwAutoHuntSkipItem[dwItemID]=fNow+c_fAutoHuntItemSkipTime; m_dwAutoHuntItemID=0; } } break; } } } void CPythonPlayer::__AutoHunt_ResetMovement() { m_vecAutoHuntPath.clear(); m_fAutoHuntPathTime=0; m_fAutoHuntPathMoveTime=0; m_fAutoHuntTargetScanTime=0; } bool CPythonPlayer::__AutoHunt_BuildPath(const TPixelPosition& goal, float arrival, std::vector& path) { auto* main=NEW_GetMainActorPtr(); auto& background=CPythonBackground::Instance(); if (!main || !background.IsMapReady()) return false; TPixelPosition start; main->NEW_GetPixelPosition(&start); const float bound=std::max(float(m_kAutoHuntConfig.iRange),PixelDistance(start,m_kPPosAutoHuntAnchor))+200.f; auto walkable=[&](AutoHuntNavigation::Point p) { if (std::hypot(p.x-m_kPPosAutoHuntAnchor.x,p.y-m_kPPosAutoHuntAnchor.y)>bound) return false; // Missing/unloaded terrain is not a traversable cell. A small footprint keeps turns off wall edges. for (const auto offset : {AutoHuntNavigation::Point{0,0}, {25,0}, {-25,0}, {0,25}, {0,-25}}) { BYTE attr=0; if (!background.GetAttr(p.x+offset.x,p.y+offset.y,&attr) || (attr&CTerrainImpl::ATTRIBUTE_BLOCK)) return false; } struct CollisionProbe { CDynamicSphereInstance sphere; bool blocked=false; void operator()(CGraphicObjectInstance* object) { if (!blocked && object && object->CollisionDynamicSphere(sphere)) blocked=true; } } probe; const float height=background.GetHeight(p.x,p.y); probe.sphere.v3Position=D3DXVECTOR3(p.x,-p.y,height+50.f); probe.sphere.v3LastPosition=probe.sphere.v3Position; probe.sphere.fRadius=25.f; CCullingManager::Instance().ForInRange(Vector3d(p.x,-p.y,height+50.f),100.f,&probe); return !probe.blocked; }; const auto points=AutoHuntNavigation::FindPath({start.x,start.y},{goal.x,goal.y},arrival,walkable); path.clear(); for (const auto& point:points) { TPixelPosition position=goal; position.x=point.x; position.y=point.y; path.push_back(position); } return !path.empty(); } bool CPythonPlayer::__AutoHunt_MoveTo(const TPixelPosition& goal, float arrival, float now) { auto* main=NEW_GetMainActorPtr(); if (!main) return false; TPixelPosition here; main->NEW_GetPixelPosition(&here); if (m_fAutoHuntPathTime<=0 || (now-m_fAutoHuntPathTime>=1.f && (PixelDistance(goal,m_kAutoHuntPathGoal)>100.f || !main->IsWalking()))) { if (!__AutoHunt_BuildPath(goal,arrival,m_vecAutoHuntPath)) return false; m_kAutoHuntPathGoal=goal; m_fAutoHuntPathTime=now; m_fAutoHuntPathMoveTime=0; } while (!m_vecAutoHuntPath.empty() && PixelDistance(here,m_vecAutoHuntPath.front())<=50.f) m_vecAutoHuntPath.erase(m_vecAutoHuntPath.begin()); // The final planned point may be an accessible attack/pickup position beside a blocked target cell. if (m_vecAutoHuntPath.size()==1 && PixelDistance(here,m_vecAutoHuntPath.front())<=arrival) m_vecAutoHuntPath.clear(); if (m_vecAutoHuntPath.empty()) return true; __ClearAutoAttackTargetActorID(); __ClearReservedAction(); if (!main->IsUsingSkill() && now>=m_fAutoHuntPathMoveTime) { m_fAutoHuntPathMoveTime=now+0.5f; main->NEW_MoveToDestPixelPositionDirection(m_vecAutoHuntPath.front()); } // Waypoint progress can temporarily increase distance to the enemy while going around a wall. if (main->IsWalking() && PixelDistance(here,m_kAutoHuntPathLastPosition)>30.f) { m_fAutoHuntProgressTime=now; m_kAutoHuntPathLastPosition=here; } return true; } void CPythonPlayer::AutoHunt_OnDeath() { if (!AutoHunt_IsEnabled()) return; if (!m_kAutoHuntConfig.bAutoRevive) { __AutoHunt_Stop(AUTO_HUNT_STOP_DEAD); return; } if (m_eAutoHuntState==AUTO_HUNT_REVIVE) return; __AutoHunt_CancelPull(); ++m_dwAutoHuntPullRequest; __ClearAutoAttackTargetActorID(); __ClearReservedAction(); __AutoHunt_ResetMovement(); m_dwAutoHuntTargetVID=0; m_dwAutoHuntItemID=0; m_iAutoHuntMountState=0; m_isAutoHuntGathering=false; m_kSet_dwAutoHuntGathered.clear(); m_iAutoHuntGatheredGroups=0; m_eAutoHuntState=AUTO_HUNT_REVIVE; m_fAutoHuntStateTime=CTimer::Instance().GetCurrentSecond(); m_fAutoHuntReviveNextTime=m_fAutoHuntStateTime+10.f; } bool CPythonPlayer::__AutoHunt_UpdateRecovery(CInstanceBase& main, float now) { if (m_eAutoHuntState!=AUTO_HUNT_REVIVE && m_eAutoHuntState!=AUTO_HUNT_RECOVER) return false; if (!m_kAutoHuntConfig.bAutoRevive) { __AutoHunt_Stop(AUTO_HUNT_STOP_DEAD); return true; } if (main.IsDead()) { if (now>=m_fAutoHuntReviveNextTime) { CPythonNetworkStream::Instance().SendChatPacket("/restart_here"); m_fAutoHuntReviveNextTime=now+10.f; // server decides cooldown and whether revival is legal } return true; } if (m_eAutoHuntState==AUTO_HUNT_REVIVE) { m_eAutoHuntState=AUTO_HUNT_RECOVER; m_fAutoHuntStateTime=now; __ClearAutoAttackTargetActorID(); __ClearReservedAction(); main.NEW_Stop(); m_fAutoHuntNextPotionTime=0; } __AutoHunt_UpdatePotion(main,now); const int maxHP=GetStatus(POINT_MAX_HP); if (now-m_fAutoHuntStateTime<5.f || maxHP<=0 || int64_t(GetStatus(POINT_HP))*100=SKILL_MAX_NUM) return false; CPythonSkill::TSkillData* data=nullptr; if (!__AutoHunt_IsSkillReady(slot,&data)) return false; if (data->IsAttackSkill()) { auto* target=NEW_FindActorPtr(m_dwAutoHuntTargetVID); if (!target || !__AutoHunt_IsTargetAllowed(*main,*target)) return false; TPixelPosition goal; target->NEW_GetPixelPosition(&goal); std::vector line; if (!__AutoHunt_BuildPath(goal,0.f,line) || line.size()!=1 || PixelDistance(line.back(),goal)>1.f) return false; } if (main->IsMountingHorse() && !data->IsHorseSkill()) { if (m_iAutoHuntMountState || !m_kAutoHuntConfig.bDismountSkill || nowNEW_Stop(); __AutoHunt_ResetMovement(); if (!CPythonNetworkStream::Instance().SendChatPacket("/ride")) return false; m_dwAutoHuntPendingSkill=m_playerStatus.aSkill[slot].dwIndex; m_iAutoHuntMountState=1; m_fAutoHuntMountDeadline=now+8.f; return true; } const bool cast=__UseSkill(slot); if (cast && m_iAutoHuntMountState==2) m_fAutoHuntMountCastEnd=now+1.f; return cast; } bool CPythonPlayer::__AutoHunt_UpdateMount(CInstanceBase& main, float now) { if (!m_iAutoHuntMountState) return false; __ClearAutoAttackTargetActorID(); if (m_iAutoHuntMountState==1) { if (!main.IsMountingHorse()) { m_iAutoHuntMountState=2; m_fAutoHuntMountDeadline=now+12.f; m_fAutoHuntMountCastEnd=now+1.f; } else if (now>=m_fAutoHuntMountDeadline) { m_iAutoHuntMountState=0; m_fAutoHuntMountNextTime=now+30.f; } return true; } if (m_iAutoHuntMountState==3) { if (main.IsMountingHorse() || now>=m_fAutoHuntMountDeadline) { m_iAutoHuntMountState=0; m_fAutoHuntMountNextTime=now+10.f; __AutoHunt_ResetMovement(); m_fAutoHuntProgressTime=now; m_fAutoHuntStateTime=now; } return true; } if (main.IsMountingHorse()) { m_iAutoHuntMountState=0; return true; } if (now>=m_fAutoHuntMountDeadline && MODE_USE_SKILL==m_eReservedMode) __ClearReservedAction(); if (main.IsUsingSkill() || MODE_USE_SKILL==m_eReservedMode) return true; if (m_dwAutoHuntPendingSkill) { const DWORD index=m_dwAutoHuntPendingSkill; m_dwAutoHuntPendingSkill=0; DWORD slot=0; if (std::find(m_kAutoHuntConfig.vecSkillIndices.begin(),m_kAutoHuntConfig.vecSkillIndices.end(),index)!=m_kAutoHuntConfig.vecSkillIndices.end() && GetSkillSlotIndex(index,&slot)) { CPythonSkill::TSkillData* data=nullptr; auto* target=NEW_FindActorPtr(m_dwAutoHuntTargetVID); if (__AutoHunt_IsSkillReady(slot,&data) && (!data->IsAttackSkill() || (target && __AutoHunt_IsTargetAllowed(main,*target) && main.GetDistance(target)<=std::max(float(data->GetTargetRange()),c_fAutoHuntSkillDistance)))) { if (__AutoHunt_CastSkill(slot,now)) { m_fAutoHuntNextSkillTime=now+0.5f; return true; } } } } // Finish other selected, ready skills while already on foot, then restore riding once animations end. if (now