layout: restructure into src/ tests/ android/ scripts/ tools/

- extension/src/{port,platform,codepage} -> src/; native_render -> src/host
  (+ dxt, shaders/); libgr2 -> src/gr2; extension/third_party -> third_party
- extension/tests -> tests/port, libgr2/tests -> tests/gr2
- android-native -> android (build.sh, push-client.sh moved in)
- script -> scripts; tools/40250 -> tools/server; oracle -> tools/granny-oracle;
  perf tools -> tools/perf
- all build trees under build/ (native, release, android, port-gate)
- xrender:: CMake aliases -> mt::; port-map ledger impl paths rewritten

No code changes. ctest 15/15, port_gate macos+android PASS, port_map check 0 errors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-09-29 19:08:19 +09:00
co-authored by Claude Opus 5.5
parent 70710477cf
commit a46093104c
2817 changed files with 13728 additions and 13744 deletions
@@ -0,0 +1,875 @@
// 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 quick-slot skills, the MODE_CLICK_ITEM reserve that walks to a ground item and
// sends the pick-up, and CG_ITEM_USE for potions. No packet is sent that manual play would not send.
#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>
#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_fAutoHuntTagHitTime = 0.35f; // gather: attacking a mob this long has pulled it
const float c_fAutoHuntGatherTimeout = 25.0f;
const float c_fAutoHuntGatherRange = 1500.0f; // gather: the next mob this close to the player
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();
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_fAutoHuntHitTime = 0.0f;
m_fAutoHuntLastUpdateTime = m_fAutoHuntStateTime;
}
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()
{
int iCount = 0;
for (DWORD dwVID : m_kSet_dwAutoHuntGathered)
{
CInstanceBase* pkInst = NEW_FindActorPtr(dwVID);
if (pkInst && !pkInst->IsDead())
++iCount;
}
return iCount;
}
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;
m_kAutoHuntConfig = c_rkConfig;
m_kAutoHuntConfig.iRange = MINMAX(500, m_kAutoHuntConfig.iRange, 5000);
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 = MINMAX(0, m_kAutoHuntConfig.iGatherCount, 10);
__AutoHunt_SaveConfig();
}
void CPythonPlayer::__AutoHunt_LoadConfig()
{
if (m_isAutoHuntConfigLoaded)
return;
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;
}
fclose(fp);
}
void CPythonPlayer::__AutoHunt_SaveConfig()
{
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);
}
void CPythonPlayer::__AutoHunt_Stop(int eReason)
{
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();
}
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;
if (AUTO_HUNT_PAUSED != m_eAutoHuntState)
{
__ClearAutoAttackTargetActorID();
__ClearReservedAction();
m_dwAutoHuntTargetVID = 0;
m_dwAutoHuntItemID = 0;
m_eAutoHuntState = AUTO_HUNT_PAUSED;
}
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;
m_fAutoHuntHitTime = 0.0f;
SetTarget(dwVID);
__ClearReservedAction();
__SetAutoAttackTargetActorID(dwVID);
}
DWORD CPythonPlayer::__AutoHunt_FindTarget(CInstanceBase& rkInstMain, float fNow, bool isExceptGathered)
{
CPythonCharacterManager& rkChrMgr = CPythonCharacterManager::Instance();
CPythonNonPlayer& rkNonPlayer = CPythonNonPlayer::Instance();
DWORD dwBestVID = 0;
float fBestDistance = 1e9f;
for (CPythonCharacterManager::CharacterIterator i = rkChrMgr.CharacterInstanceBegin(); i != rkChrMgr.CharacterInstanceEnd(); ++i)
{
CInstanceBase* pkInst = *i;
if (!pkInst || pkInst == &rkInstMain || pkInst->IsDead())
continue;
if (!pkInst->IsEnemy() && !(m_kAutoHuntConfig.bAttackStone && pkInst->IsStone()))
continue;
if (!rkInstMain.IsAttackableInstance(*pkInst) || pkInst->IsInSafe())
continue;
if (!m_kAutoHuntConfig.bAttackBoss)
{
const CPythonNonPlayer::TMobTable* pMobTable = rkNonPlayer.GetTable(pkInst->GetRace());
if (pMobTable && pMobTable->bRank >= c_byMobRankBoss)
continue;
}
std::map<DWORD, float>::iterator f = m_kMap_dwAutoHuntSkipVID.find(pkInst->GetVirtualID());
if (f != m_kMap_dwAutoHuntSkipVID.end() && fNow < f->second)
continue;
if (isExceptGathered && m_kSet_dwAutoHuntGathered.count(pkInst->GetVirtualID()))
continue;
TPixelPosition kPPos;
pkInst->NEW_GetPixelPosition(&kPPos);
if (PixelDistance(kPPos, m_kPPosAutoHuntAnchor) > float(m_kAutoHuntConfig.iRange))
continue;
const float fDistance = rkInstMain.GetDistance(pkInst);
// The first pull may be anywhere in range; the rest near the pack that follows the character.
if (isExceptGathered && !m_kSet_dwAutoHuntGathered.empty() && fDistance > c_fAutoHuntGatherRange)
continue;
if (fDistance < fBestDistance)
{
fBestDistance = fDistance;
dwBestVID = pkInst->GetVirtualID();
}
}
return dwBestVID;
}
// 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 || pkInst->IsDead())
continue;
const float fDistance = rkInstMain.GetDistance(pkInst);
if (fDistance < fBestDistance)
{
fBestDistance = fDistance;
dwBestVID = dwVID;
}
}
return dwBestVID;
}
// 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())
i = m_kSet_dwAutoHuntGathered.erase(i);
else
++i;
}
}
bool CPythonPlayer::__AutoHunt_IsGatherDone(float fNow)
{
if (int(m_kSet_dwAutoHuntGathered.size()) >= m_kAutoHuntConfig.iGatherCount)
return true;
if (fNow - m_fAutoHuntGatherStartTime > c_fAutoHuntGatherTimeout)
return true;
const int iMaxHP = GetStatus(POINT_MAX_HP);
if (iMaxHP > 0 && GetStatus(POINT_HP) * 100 < iMaxHP * c_iAutoHuntGatherHPPercent)
{
AutoHuntTrace("gather stop: hp %d/%d", GetStatus(POINT_HP), iMaxHP);
return true;
}
return false;
}
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 ((pkInstMain->IsMountingHorse() ? true : false) != (pSkillData->IsHorseSkill() ? true : false))
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;
}
// Self buffs from the enabled quick slots 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)
{
if (!(m_kAutoHuntConfig.dwSkillSlotMask & (1 << i)))
continue;
DWORD dwType = SLOT_TYPE_NONE, dwSkillSlot = 0;
GetLocalQuickSlotData(i, &dwType, &dwSkillSlot);
if (SLOT_TYPE_SKILL != dwType)
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 (__UseSkill(dwSkillSlot))
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 i = 0; i < AUTO_HUNT_SKILL_SLOT_NUM; ++i)
{
if (!(m_kAutoHuntConfig.dwSkillSlotMask & (1 << i)))
continue;
DWORD dwType = SLOT_TYPE_NONE, dwSkillSlot = 0;
GetLocalQuickSlotData(i, &dwType, &dwSkillSlot);
if (SLOT_TYPE_SKILL != dwType)
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 (__UseSkill(dwSkillSlot))
{
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;
const int iPercent = isHP ? m_kAutoHuntConfig.iHPPercent : m_kAutoHuntConfig.iSPPercent;
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;
if (rkInstMain.IsDead())
{
__AutoHunt_Stop(AUTO_HUNT_STOP_DEAD);
return;
}
if (m_stAutoHuntMapName != CPythonBackground::Instance().GetWarpMapName())
{
__AutoHunt_Stop(AUTO_HUNT_STOP_MAP);
return;
}
const float fNow = CTimer::Instance().GetCurrentSecond();
const float fElapsed = fMINMAX(0.0f, fNow - m_fAutoHuntLastUpdateTime, 0.1f);
m_fAutoHuntLastUpdateTime = fNow;
__AutoHunt_UpdatePotion(rkInstMain, fNow);
if (IsOpenPrivateShop() || IsObserverMode() || rkInstMain.IsFishingMode())
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;
// Gather mode: hit up to N mobs once each so they follow, then fight them where they stand.
__AutoHunt_PurgeGathered();
if (m_kAutoHuntConfig.iGatherCount > 0 && !m_isAutoHuntGathering && m_kSet_dwAutoHuntGathered.empty())
{
m_isAutoHuntGathering = true;
m_fAutoHuntGatherStartTime = fNow;
}
if (m_isAutoHuntGathering)
{
DWORD dwVID = __AutoHunt_IsGatherDone(fNow) ? 0 : __AutoHunt_FindTarget(rkInstMain, fNow, true);
if (dwVID)
{
__AutoHunt_Engage(dwVID, fNow);
return;
}
m_isAutoHuntGathering = false;
if (!m_kSet_dwAutoHuntGathered.empty())
AutoHuntTrace("gather done: %d mobs, %.1fs", int(m_kSet_dwAutoHuntGathered.size()),
fNow - m_fAutoHuntGatherStartTime);
}
if (DWORD dwVID = __AutoHunt_FindGatheredTarget(rkInstMain))
{
__AutoHunt_Engage(dwVID, fNow);
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)
{
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 (!rkInstMain.IsWalking())
rkInstMain.NEW_MoveToDestPixelPositionDirection(m_kPPosAutoHuntAnchor);
break;
}
case AUTO_HUNT_ENGAGE:
{
CInstanceBase* pkInstVictim = NEW_FindActorPtr(m_dwAutoHuntTargetVID);
if (!pkInstVictim || pkInstVictim->IsDead())
{
__ClearAutoAttackTargetActorID();
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;
}
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;
m_fAutoHuntHitTime += fElapsed;
}
// Gather: this one is hit and follows now; go and hit the next.
if (m_isAutoHuntGathering && m_fAutoHuntHitTime >= c_fAutoHuntTagHitTime &&
!m_kSet_dwAutoHuntGathered.count(m_dwAutoHuntTargetVID))
{
m_kSet_dwAutoHuntGathered.insert(m_dwAutoHuntTargetVID);
__AutoHunt_PurgeGathered();
AutoHuntTrace("gather tag %u (%d/%d)", (unsigned)m_dwAutoHuntTargetVID, int(m_kSet_dwAutoHuntGathered.size()),
m_kAutoHuntConfig.iGatherCount);
}
if (m_isAutoHuntGathering && m_kSet_dwAutoHuntGathered.count(m_dwAutoHuntTargetVID) && !rkInstMain.IsUsingSkill())
{
DWORD dwNextVID = __AutoHunt_IsGatherDone(fNow) ? 0 : __AutoHunt_FindTarget(rkInstMain, fNow, true);
if (dwNextVID)
{
__AutoHunt_Engage(dwNextVID, fNow);
return;
}
m_isAutoHuntGathering = false;
AutoHuntTrace("gather done: %d mobs, %.1fs", int(m_kSet_dwAutoHuntGathered.size()),
fNow - m_fAutoHuntGatherStartTime);
}
// 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();
m_dwAutoHuntTargetVID = 0;
m_eAutoHuntState = AUTO_HUNT_SEARCH;
m_fAutoHuntStateTime = fNow;
return;
}
__AutoHunt_UpdateBuff(rkInstMain, fNow);
if (!m_isAutoHuntGathering)
__AutoHunt_UpdateAttackSkill(rkInstMain, *pkInstVictim, fNow);
// 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 (MODE_CLICK_ITEM != m_eReservedMode && !rkInstMain.IsWalking())
__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
{
__ReserveClickItem(dwItemID);
}
break;
}
}
}