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:
co-authored by
Claude Opus 5.5
parent
70710477cf
commit
a46093104c
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user