1184 lines
45 KiB
C++
1184 lines
45 KiB
C++
// 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 <cmath>
|
|
#include <cstdarg>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <vector>
|
|
#include "AutoHuntNavigation.h"
|
|
#include "../EterLib/CullingManager.h"
|
|
#include "../EterLib/GrpObjectInstance.h"
|
|
|
|
#ifdef __ANDROID__
|
|
#include <android/log.h>
|
|
#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<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,"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()
|
|
{
|
|
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<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;
|
|
}
|
|
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)
|
|
{
|
|
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);
|
|
}
|
|
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.
|
|
void CPythonPlayer::__AutoHunt_PurgeGathered()
|
|
{
|
|
for (std::set<DWORD>::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<DWORD> 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<DWORD, float>::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()<m_fAutoHuntMountNextTime)) return false;
|
|
if (!pSkillData->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<DWORD, DWORD>::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<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;
|
|
}
|