Improve auto-hunt navigation, recovery and mounted skill casting
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
//
|
||||
// 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 quick-slot skills, the MODE_CLICK_ITEM reserve that walks to a ground item and
|
||||
// __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"
|
||||
@@ -21,6 +21,9 @@
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <vector>
|
||||
#include "AutoHuntNavigation.h"
|
||||
#include "../EterLib/CullingManager.h"
|
||||
#include "../EterLib/GrpObjectInstance.h"
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <android/log.h>
|
||||
@@ -90,6 +93,9 @@ void CPythonPlayer::AutoHunt_SetEnabled(bool isEnable)
|
||||
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;
|
||||
@@ -157,6 +163,7 @@ void CPythonPlayer::AutoHunt_SetConfig(const SAutoHuntConfig& c_rkConfig)
|
||||
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);
|
||||
@@ -174,38 +181,92 @@ void CPythonPlayer::__AutoHunt_LoadConfig()
|
||||
m_isAutoHuntConfigLoaded = true;
|
||||
|
||||
FILE* fp = fopen(c_szAutoHuntConfigFileName, "rt");
|
||||
if (!fp)
|
||||
return;
|
||||
|
||||
SAutoHuntConfig kConfig = m_kAutoHuntConfig;
|
||||
int iStone = 0, iBoss = 0, iGather = 0;
|
||||
unsigned int uMask = 0;
|
||||
// 7 fields before gather mode.
|
||||
const int iRead = fscanf(fp, "%d %d %d %d %d %d %u %d", &kConfig.iRange, &kConfig.iHPPercent, &kConfig.iSPPercent,
|
||||
&kConfig.iPickup, &iStone, &iBoss, &uMask, &iGather);
|
||||
if (iRead >= 7)
|
||||
{
|
||||
kConfig.iGatherCount = 8 == iRead ? iGather : 0;
|
||||
kConfig.bAttackStone = iStone != 0;
|
||||
kConfig.bAttackBoss = iBoss != 0;
|
||||
kConfig.dwSkillSlotMask = uMask;
|
||||
fclose(fp);
|
||||
AutoHunt_SetConfig(kConfig);
|
||||
return;
|
||||
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<count;++i) { unsigned index=0; if (fscanf(fp,"%u",&index)!=1) break; config.vecSkillIndices.push_back(index); }
|
||||
config.bAttackStone=stone!=0; config.bAttackBoss=boss!=0;
|
||||
config.bAutoRevive=revive!=0; config.bDismountSkill=dismount!=0;
|
||||
fclose(fp); AutoHunt_SetConfig(config); return;
|
||||
}
|
||||
} else {
|
||||
rewind(fp);
|
||||
const int read=fscanf(fp,"%d %d %d %d %d %d %u %d", &config.iRange,&config.iHPPercent,
|
||||
&config.iSPPercent,&config.iPickup,&stone,&boss,&mask,&config.iGatherCount);
|
||||
if (read>=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, "%d %d %d %d %d %d %u %d\n", m_kAutoHuntConfig.iRange, m_kAutoHuntConfig.iHPPercent,
|
||||
m_kAutoHuntConfig.iSPPercent, m_kAutoHuntConfig.iPickup, m_kAutoHuntConfig.bAttackStone ? 1 : 0,
|
||||
m_kAutoHuntConfig.bAttackBoss ? 1 : 0, (unsigned int)m_kAutoHuntConfig.dwSkillSlotMask,
|
||||
m_kAutoHuntConfig.iGatherCount);
|
||||
fclose(fp);
|
||||
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<DWORD> CPythonPlayer::AutoHunt_GetAvailableSkills()
|
||||
{
|
||||
__AutoHunt_LoadConfig();
|
||||
__AutoHunt_MigrateSkills();
|
||||
std::vector<DWORD> indices;
|
||||
for (DWORD slot=0;slot<SKILL_MAX_NUM;++slot) {
|
||||
const auto& instance=m_playerStatus.aSkill[slot];
|
||||
CPythonSkill::TSkillData* data=nullptr;
|
||||
if (!instance.dwIndex || instance.iLevel<=0 || !CPythonSkill::Instance().GetSkillData(instance.dwIndex,&data)) continue;
|
||||
if ((data->byType==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<AUTO_HUNT_SKILL_SLOT_NUM;++i) {
|
||||
DWORD type=0,slot=0;
|
||||
GetLocalQuickSlotData(i,&type,&slot);
|
||||
if ((m_kAutoHuntConfig.dwSkillSlotMask & (1<<i)) && type==SLOT_TYPE_SKILL && slot<SKILL_MAX_NUM && GetSkillIndex(slot))
|
||||
m_kAutoHuntConfig.vecSkillIndices.push_back(GetSkillIndex(slot));
|
||||
}
|
||||
m_isAutoHuntLegacySkills=false;
|
||||
AutoHunt_SetSkills(m_kAutoHuntConfig.vecSkillIndices);
|
||||
}
|
||||
|
||||
void CPythonPlayer::AutoHunt_SetSkills(const std::vector<DWORD>& indices)
|
||||
{
|
||||
__AutoHunt_LoadConfig();
|
||||
m_isAutoHuntLegacySkills=false;
|
||||
std::vector<DWORD> 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()
|
||||
@@ -221,14 +282,11 @@ void CPythonPlayer::__AutoHunt_CancelPull()
|
||||
void CPythonPlayer::__AutoHunt_Stop(int eReason)
|
||||
{
|
||||
__AutoHunt_CancelPull();
|
||||
if (AUTO_HUNT_ENGAGE == m_eAutoHuntState || AUTO_HUNT_LOOT == m_eAutoHuntState || AUTO_HUNT_RETURN == m_eAutoHuntState)
|
||||
{
|
||||
__ClearAutoAttackTargetActorID();
|
||||
__ClearReservedAction();
|
||||
if (AUTO_HUNT_RETURN == m_eAutoHuntState)
|
||||
if (CInstanceBase* pkInstMain = NEW_GetMainActorPtr())
|
||||
pkInstMain->NEW_Stop();
|
||||
}
|
||||
__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;
|
||||
@@ -244,6 +302,11 @@ 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();
|
||||
@@ -267,7 +330,8 @@ void CPythonPlayer::__AutoHunt_Engage(DWORD dwVID, float fNow)
|
||||
|
||||
SetTarget(dwVID);
|
||||
__ClearReservedAction();
|
||||
__SetAutoAttackTargetActorID(dwVID);
|
||||
__ClearAutoAttackTargetActorID();
|
||||
__AutoHunt_ResetMovement();
|
||||
}
|
||||
|
||||
// PORT: stones have their own option; their mob_proto rank must not count as a boss.
|
||||
@@ -311,58 +375,59 @@ bool CPythonPlayer::__AutoHunt_RequestPull(CInstanceBase& main, float now)
|
||||
DWORD CPythonPlayer::__AutoHunt_FindTarget(CInstanceBase& rkInstMain, float fNow)
|
||||
{
|
||||
CPythonCharacterManager& rkChrMgr = CPythonCharacterManager::Instance();
|
||||
|
||||
DWORD dwBestVID = 0;
|
||||
float fBestDistance = 1e9f;
|
||||
bool bestStone = false;
|
||||
for (CPythonCharacterManager::CharacterIterator i = rkChrMgr.CharacterInstanceBegin(); i != rkChrMgr.CharacterInstanceEnd(); ++i)
|
||||
{
|
||||
CInstanceBase* pkInst = *i;
|
||||
if (!pkInst || !__AutoHunt_IsTargetAllowed(rkInstMain, *pkInst))
|
||||
continue;
|
||||
|
||||
std::map<DWORD, float>::iterator f = m_kMap_dwAutoHuntSkipVID.find(pkInst->GetVirtualID());
|
||||
if (f != m_kMap_dwAutoHuntSkipVID.end() && fNow < f->second)
|
||||
continue;
|
||||
|
||||
TPixelPosition kPPos;
|
||||
pkInst->NEW_GetPixelPosition(&kPPos);
|
||||
if (PixelDistance(kPPos, m_kPPosAutoHuntAnchor) > float(m_kAutoHuntConfig.iRange))
|
||||
continue;
|
||||
|
||||
const float fDistance = rkInstMain.GetDistance(pkInst);
|
||||
const bool stone = pkInst->IsStone();
|
||||
if ((stone && !bestStone) || (stone == bestStone && fDistance < fBestDistance))
|
||||
{
|
||||
bestStone = stone;
|
||||
fBestDistance = fDistance;
|
||||
dwBestVID = pkInst->GetVirtualID();
|
||||
}
|
||||
if (fNow<m_fAutoHuntTargetScanTime) {
|
||||
auto* cached=NEW_FindActorPtr(m_dwAutoHuntScanTarget);
|
||||
const auto skip=m_kMap_dwAutoHuntSkipVID.find(m_dwAutoHuntScanTarget);
|
||||
if (!m_dwAutoHuntScanTarget) return 0;
|
||||
if (cached && __AutoHunt_IsTargetAllowed(rkInstMain,*cached) &&
|
||||
(skip==m_kMap_dwAutoHuntSkipVID.end() || fNow>=skip->second)) return m_dwAutoHuntScanTarget;
|
||||
}
|
||||
return dwBestVID;
|
||||
m_fAutoHuntTargetScanTime=fNow+0.5f; m_dwAutoHuntScanTarget=0;
|
||||
std::vector<CInstanceBase*> 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() && fNow<skip->second) 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)<rkInstMain.GetDistance(b);
|
||||
});
|
||||
int attempts=0;
|
||||
for (auto* target:candidates) {
|
||||
if (++attempts>4) break;
|
||||
TPixelPosition goal; target->NEW_GetPixelPosition(&goal);
|
||||
std::vector<TPixelPosition> 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)
|
||||
{
|
||||
DWORD dwBestVID = 0;
|
||||
float fBestDistance = 1e9f;
|
||||
for (DWORD dwVID : m_kSet_dwAutoHuntGathered)
|
||||
{
|
||||
CInstanceBase* pkInst = NEW_FindActorPtr(dwVID);
|
||||
if (!pkInst || !__AutoHunt_IsTargetAllowed(rkInstMain, *pkInst))
|
||||
continue;
|
||||
const auto skipped = m_kMap_dwAutoHuntSkipVID.find(dwVID);
|
||||
if (skipped != m_kMap_dwAutoHuntSkipVID.end() && CTimer::Instance().GetCurrentSecond() < skipped->second)
|
||||
continue;
|
||||
const float fDistance = rkInstMain.GetDistance(pkInst);
|
||||
if (fDistance < fBestDistance)
|
||||
{
|
||||
fBestDistance = fDistance;
|
||||
dwBestVID = dwVID;
|
||||
}
|
||||
std::vector<CInstanceBase*> 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);
|
||||
}
|
||||
return dwBestVID;
|
||||
std::sort(targets.begin(),targets.end(),[&](CInstanceBase* a,CInstanceBase* b) {return rkInstMain.GetDistance(a)<rkInstMain.GetDistance(b);});
|
||||
int attempts=0;
|
||||
for (auto* target:targets) {
|
||||
if (++attempts>4) break;
|
||||
TPixelPosition goal; target->NEW_GetPixelPosition(&goal);
|
||||
std::vector<TPixelPosition> 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.
|
||||
@@ -495,8 +560,9 @@ bool CPythonPlayer::__AutoHunt_IsSkillReady(DWORD dwSkillSlot, CPythonSkill::TSk
|
||||
return false;
|
||||
if (!pSkillData->IsCanUseSkill())
|
||||
return false;
|
||||
if ((pkInstMain->IsMountingHorse() ? true : false) != (pSkillData->IsHorseSkill() ? true : false))
|
||||
return false;
|
||||
if (pSkillData->IsHorseSkill() && !pkInstMain->IsMountingHorse()) return false;
|
||||
if (!pSkillData->IsHorseSkill() && pkInstMain->IsMountingHorse() &&
|
||||
(!m_kAutoHuntConfig.bDismountSkill || CTimer::Instance().GetCurrentSecond()<m_fAutoHuntMountNextTime)) return false;
|
||||
if (!pSkillData->CanUseWeaponType(pkInstMain->GetWeaponType()))
|
||||
return false;
|
||||
if (pSkillData->IsNeedBow() && !__HasEnoughArrow())
|
||||
@@ -519,21 +585,16 @@ bool CPythonPlayer::__AutoHunt_IsSkillReady(DWORD dwSkillSlot, CPythonSkill::TSk
|
||||
return true;
|
||||
}
|
||||
|
||||
// Self buffs from the enabled quick slots whose effect is not on the character.
|
||||
// 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 i = 0; i < AUTO_HUNT_SKILL_SLOT_NUM; ++i)
|
||||
for (DWORD index : m_kAutoHuntConfig.vecSkillIndices)
|
||||
{
|
||||
if (!(m_kAutoHuntConfig.dwSkillSlotMask & (1 << i)))
|
||||
continue;
|
||||
|
||||
DWORD dwType = SLOT_TYPE_NONE, dwSkillSlot = 0;
|
||||
GetLocalQuickSlotData(i, &dwType, &dwSkillSlot);
|
||||
if (SLOT_TYPE_SKILL != dwType)
|
||||
continue;
|
||||
DWORD dwSkillSlot = 0;
|
||||
if (!GetSkillSlotIndex(index, &dwSkillSlot)) continue;
|
||||
|
||||
CPythonSkill::TSkillData* pSkillData;
|
||||
if (!__AutoHunt_IsSkillReady(dwSkillSlot, &pSkillData))
|
||||
@@ -560,7 +621,7 @@ bool CPythonPlayer::__AutoHunt_UpdateBuff(CInstanceBase& rkInstMain, float fNow)
|
||||
}
|
||||
|
||||
m_fAutoHuntNextSkillTime = fNow + 0.5f;
|
||||
if (__UseSkill(dwSkillSlot))
|
||||
if (__AutoHunt_CastSkill(dwSkillSlot, fNow))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -573,15 +634,10 @@ void CPythonPlayer::__AutoHunt_UpdateAttackSkill(CInstanceBase& rkInstMain, CIns
|
||||
m_fAutoHuntNextSkillTime = fNow + 0.3f;
|
||||
|
||||
const float fDistance = rkInstMain.GetDistance(&rkInstVictim);
|
||||
for (DWORD i = 0; i < AUTO_HUNT_SKILL_SLOT_NUM; ++i)
|
||||
for (DWORD index : m_kAutoHuntConfig.vecSkillIndices)
|
||||
{
|
||||
if (!(m_kAutoHuntConfig.dwSkillSlotMask & (1 << i)))
|
||||
continue;
|
||||
|
||||
DWORD dwType = SLOT_TYPE_NONE, dwSkillSlot = 0;
|
||||
GetLocalQuickSlotData(i, &dwType, &dwSkillSlot);
|
||||
if (SLOT_TYPE_SKILL != dwType)
|
||||
continue;
|
||||
DWORD dwSkillSlot = 0;
|
||||
if (!GetSkillSlotIndex(index, &dwSkillSlot)) continue;
|
||||
|
||||
CPythonSkill::TSkillData* pSkillData;
|
||||
if (!__AutoHunt_IsSkillReady(dwSkillSlot, &pSkillData) || !pSkillData->IsAttackSkill())
|
||||
@@ -593,7 +649,7 @@ void CPythonPlayer::__AutoHunt_UpdateAttackSkill(CInstanceBase& rkInstMain, CIns
|
||||
continue;
|
||||
|
||||
SetTarget(rkInstVictim.GetVirtualID());
|
||||
if (__UseSkill(dwSkillSlot))
|
||||
if (__AutoHunt_CastSkill(dwSkillSlot, fNow))
|
||||
{
|
||||
m_fAutoHuntNextSkillTime = fNow + 0.5f;
|
||||
return;
|
||||
@@ -611,7 +667,8 @@ void CPythonPlayer::__AutoHunt_UpdatePotion(CInstanceBase& rkInstMain, float fNo
|
||||
for (int iValue = 0; iValue < 2; ++iValue)
|
||||
{
|
||||
const bool isHP = 0 == iValue;
|
||||
const int iPercent = isHP ? m_kAutoHuntConfig.iHPPercent : m_kAutoHuntConfig.iSPPercent;
|
||||
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;
|
||||
@@ -657,18 +714,19 @@ void CPythonPlayer::__Update_AutoHunt()
|
||||
return;
|
||||
CInstanceBase& rkInstMain = *pkInstMain;
|
||||
|
||||
if (rkInstMain.IsDead())
|
||||
{
|
||||
__AutoHunt_Stop(AUTO_HUNT_STOP_DEAD);
|
||||
return;
|
||||
}
|
||||
const float fNow = CTimer::Instance().GetCurrentSecond();
|
||||
if (m_stAutoHuntMapName != CPythonBackground::Instance().GetWarpMapName())
|
||||
{
|
||||
__AutoHunt_Stop(AUTO_HUNT_STOP_MAP);
|
||||
return;
|
||||
}
|
||||
|
||||
const float fNow = CTimer::Instance().GetCurrentSecond();
|
||||
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;
|
||||
@@ -678,6 +736,7 @@ void CPythonPlayer::__Update_AutoHunt()
|
||||
|
||||
if (IsOpenPrivateShop() || IsObserverMode() || rkInstMain.IsFishingMode())
|
||||
return;
|
||||
if (__AutoHunt_UpdateMount(rkInstMain,fNow)) return;
|
||||
|
||||
switch (m_eAutoHuntState)
|
||||
{
|
||||
@@ -746,6 +805,7 @@ void CPythonPlayer::__Update_AutoHunt()
|
||||
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;
|
||||
@@ -775,8 +835,11 @@ void CPythonPlayer::__Update_AutoHunt()
|
||||
m_fAutoHuntStateTime = fNow;
|
||||
return;
|
||||
}
|
||||
if (!rkInstMain.IsWalking())
|
||||
rkInstMain.NEW_MoveToDestPixelPositionDirection(m_kPPosAutoHuntAnchor);
|
||||
if (!__AutoHunt_MoveTo(m_kPPosAutoHuntAnchor,100.f,fNow)) {
|
||||
rkInstMain.NEW_Stop();
|
||||
m_eAutoHuntState=AUTO_HUNT_SEARCH;
|
||||
m_fAutoHuntNextActionTime=fNow+2.f;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -785,7 +848,8 @@ void CPythonPlayer::__Update_AutoHunt()
|
||||
CInstanceBase* pkInstVictim = NEW_FindActorPtr(m_dwAutoHuntTargetVID);
|
||||
if (!pkInstVictim || !__AutoHunt_IsTargetAllowed(rkInstMain, *pkInstVictim))
|
||||
{
|
||||
__ClearAutoAttackTargetActorID();
|
||||
__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.
|
||||
@@ -826,17 +890,27 @@ void CPythonPlayer::__Update_AutoHunt()
|
||||
{
|
||||
m_kMap_dwAutoHuntSkipVID[m_dwAutoHuntTargetVID] = fNow + c_fAutoHuntSkipTime;
|
||||
__ClearAutoAttackTargetActorID();
|
||||
__ClearReservedAction();
|
||||
__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)
|
||||
@@ -870,7 +944,10 @@ void CPythonPlayer::__Update_AutoHunt()
|
||||
}
|
||||
else
|
||||
{
|
||||
if (MODE_CLICK_ITEM != m_eReservedMode && !rkInstMain.IsWalking())
|
||||
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;
|
||||
}
|
||||
@@ -902,9 +979,205 @@ void CPythonPlayer::__Update_AutoHunt()
|
||||
}
|
||||
else
|
||||
{
|
||||
__ReserveClickItem(dwItemID);
|
||||
__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<TPixelPosition>& 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<int64_t(maxHP)*70) return true;
|
||||
m_kMap_dwAutoHuntSkipVID.clear(); m_kMap_dwAutoHuntSkipItem.clear();
|
||||
__AutoHunt_ResetMovement();
|
||||
m_eAutoHuntState=AUTO_HUNT_SEARCH; m_fAutoHuntStateTime=now;
|
||||
m_fAutoHuntNextActionTime=0; m_fAutoHuntNextSkillTime=0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CPythonPlayer::__AutoHunt_CastSkill(DWORD slot, float now)
|
||||
{
|
||||
auto* main=NEW_GetMainActorPtr();
|
||||
if (!main || slot>=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<TPixelPosition> 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 || now<m_fAutoHuntMountNextTime) return false;
|
||||
__ClearAutoAttackTargetActorID(); __ClearReservedAction(); main->NEW_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<m_fAutoHuntMountDeadline) {
|
||||
if (__AutoHunt_UpdateBuff(main,now)) return true;
|
||||
auto* target=NEW_FindActorPtr(m_dwAutoHuntTargetVID);
|
||||
if (target && __AutoHunt_IsTargetAllowed(main,*target)) __AutoHunt_UpdateAttackSkill(main,*target,now);
|
||||
}
|
||||
if (main.IsUsingSkill() || MODE_USE_SKILL==m_eReservedMode || now<m_fAutoHuntMountCastEnd) return true;
|
||||
__ClearReservedAction(); main.NEW_Stop();
|
||||
CPythonNetworkStream::Instance().SendChatPacket("/ride");
|
||||
m_iAutoHuntMountState=3; m_fAutoHuntMountDeadline=now+8.f;
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user