Add free cloak-style auto-hunt pull for 3 10 or 20 monster groups
This commit is contained in:
@@ -3,7 +3,8 @@
|
||||
// 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.
|
||||
// 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"
|
||||
@@ -43,9 +44,8 @@ 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 float c_fAutoHuntPullCooldown = 10.0f;
|
||||
const float c_fAutoHuntPullReplyTimeout = 3.0f;
|
||||
const int c_iAutoHuntGatherHPPercent = 50; // gather: stop pulling below this
|
||||
|
||||
// Trace: stderr with MT_AUTOHUNT_TRACE=1, logcat (tag mt_autohunt) on Android.
|
||||
@@ -105,8 +105,7 @@ void CPythonPlayer::AutoHunt_SetEnabled(bool isEnable)
|
||||
m_kMap_dwAutoHuntSkipItem.clear();
|
||||
m_kSet_dwAutoHuntGathered.clear();
|
||||
m_isAutoHuntGathering = false;
|
||||
m_fAutoHuntHitTime = 0.0f;
|
||||
m_fAutoHuntLastUpdateTime = m_fAutoHuntStateTime;
|
||||
m_iAutoHuntGatheredGroups = 0;
|
||||
}
|
||||
|
||||
bool CPythonPlayer::AutoHunt_IsEnabled()
|
||||
@@ -131,14 +130,7 @@ bool CPythonPlayer::AutoHunt_IsGathering()
|
||||
|
||||
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;
|
||||
return m_kSet_dwAutoHuntGathered.empty() ? 0 : m_iAutoHuntGatheredGroups;
|
||||
}
|
||||
|
||||
bool CPythonPlayer::AutoHunt_HasNoPotion(bool isHP)
|
||||
@@ -161,7 +153,8 @@ void CPythonPlayer::AutoHunt_SetConfig(const SAutoHuntConfig& c_rkConfig)
|
||||
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);
|
||||
m_kAutoHuntConfig.iGatherCount = m_kAutoHuntConfig.iGatherCount <= 0 ? 0 :
|
||||
(m_kAutoHuntConfig.iGatherCount <= 3 ? 3 : (m_kAutoHuntConfig.iGatherCount <= 10 ? 10 : 20));
|
||||
__AutoHunt_SaveConfig();
|
||||
}
|
||||
|
||||
@@ -238,6 +231,7 @@ void CPythonPlayer::__AutoHunt_OnManualInput()
|
||||
m_dwAutoHuntTargetVID = 0;
|
||||
m_dwAutoHuntItemID = 0;
|
||||
m_eAutoHuntState = AUTO_HUNT_PAUSED;
|
||||
m_isAutoHuntGathering = false;
|
||||
}
|
||||
m_fAutoHuntResumeTime = CTimer::Instance().GetCurrentSecond() + c_fAutoHuntResumeDelay;
|
||||
}
|
||||
@@ -250,14 +244,13 @@ void CPythonPlayer::__AutoHunt_Engage(DWORD dwVID, float 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)
|
||||
DWORD CPythonPlayer::__AutoHunt_FindTarget(CInstanceBase& rkInstMain, float fNow)
|
||||
{
|
||||
CPythonCharacterManager& rkChrMgr = CPythonCharacterManager::Instance();
|
||||
CPythonNonPlayer& rkNonPlayer = CPythonNonPlayer::Instance();
|
||||
@@ -287,18 +280,12 @@ DWORD CPythonPlayer::__AutoHunt_FindTarget(CInstanceBase& rkInstMain, float fNow
|
||||
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;
|
||||
@@ -316,8 +303,11 @@ DWORD CPythonPlayer::__AutoHunt_FindGatheredTarget(CInstanceBase& rkInstMain)
|
||||
for (DWORD dwVID : m_kSet_dwAutoHuntGathered)
|
||||
{
|
||||
CInstanceBase* pkInst = NEW_FindActorPtr(dwVID);
|
||||
if (!pkInst || pkInst->IsDead())
|
||||
if (!pkInst || pkInst->IsDead() || pkInst->IsInSafe() || !rkInstMain.IsAttackableInstance(*pkInst))
|
||||
continue;
|
||||
const auto skipped = m_kMap_dwAutoHuntSkipVID.find(dwVID);
|
||||
if (skipped != m_kMap_dwAutoHuntSkipVID.end() && CTimer::Instance().GetCurrentSecond() < skipped->second)
|
||||
continue;
|
||||
const float fDistance = rkInstMain.GetDistance(pkInst);
|
||||
if (fDistance < fBestDistance)
|
||||
{
|
||||
@@ -341,19 +331,24 @@ void CPythonPlayer::__AutoHunt_PurgeGathered()
|
||||
}
|
||||
}
|
||||
|
||||
bool CPythonPlayer::__AutoHunt_IsGatherDone(float fNow)
|
||||
// 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 (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;
|
||||
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)
|
||||
@@ -626,8 +621,6 @@ void CPythonPlayer::__Update_AutoHunt()
|
||||
}
|
||||
|
||||
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())
|
||||
@@ -653,28 +646,41 @@ void CPythonPlayer::__Update_AutoHunt()
|
||||
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);
|
||||
}
|
||||
// A cloak-style pull stays in place while the server starts the nearby groups.
|
||||
if (m_isAutoHuntGathering) {
|
||||
if (fNow - m_fAutoHuntGatherStartTime < c_fAutoHuntPullReplyTimeout) return;
|
||||
m_isAutoHuntGathering = false;
|
||||
m_kSet_dwAutoHuntGathered.clear();
|
||||
AutoHuntTrace("cloak pull reply timed out; continue ordinary hunt");
|
||||
}
|
||||
__AutoHunt_PurgeGathered();
|
||||
const int maxHP = GetStatus(POINT_MAX_HP);
|
||||
if (m_kAutoHuntConfig.iGatherCount > 0 && m_kSet_dwAutoHuntGathered.empty() &&
|
||||
fNow >= m_fAutoHuntNextPullTime && !rkInstMain.IsInSafe() &&
|
||||
maxHP > 0 && GetStatus(POINT_HP) * 100 >= maxHP * c_iAutoHuntGatherHPPercent &&
|
||||
__AutoHunt_FindTarget(rkInstMain, fNow)) {
|
||||
char command[128];
|
||||
++m_dwAutoHuntPullRequest;
|
||||
snprintf(command, sizeof(command), "/autohunt_pull %d %d %d %u",
|
||||
m_kAutoHuntConfig.iGatherCount, m_kAutoHuntConfig.iRange,
|
||||
m_kAutoHuntConfig.bAttackBoss ? 1 : 0, unsigned(m_dwAutoHuntPullRequest));
|
||||
m_fAutoHuntNextPullTime = fNow + c_fAutoHuntPullCooldown;
|
||||
if (CPythonNetworkStream::Instance().SendChatPacket(command)) {
|
||||
__ClearAutoAttackTargetActorID();
|
||||
__ClearReservedAction();
|
||||
rkInstMain.NEW_Stop();
|
||||
m_isAutoHuntGathering = true;
|
||||
m_iAutoHuntGatheredGroups = 0;
|
||||
m_fAutoHuntGatherStartTime = fNow;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (DWORD dwVID = __AutoHunt_FindGatheredTarget(rkInstMain))
|
||||
{
|
||||
CInstanceBase* pulled = NEW_FindActorPtr(dwVID);
|
||||
if (pulled && rkInstMain.GetDistance(pulled) > c_fAutoHuntSkillDistance &&
|
||||
fNow - m_fAutoHuntGatherStartTime < 8.0f && GetStatus(POINT_HP) * 100 >= maxHP * c_iAutoHuntGatherHPPercent)
|
||||
return; // let the server-controlled monsters approach the stationary player
|
||||
__AutoHunt_Engage(dwVID, fNow);
|
||||
return;
|
||||
}
|
||||
@@ -759,29 +765,6 @@ void CPythonPlayer::__Update_AutoHunt()
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user