issue-8: add switchbot automation

This commit is contained in:
server
2026-04-16 20:38:12 +02:00
parent 9e5bd67979
commit 1708a12e17
4 changed files with 384 additions and 0 deletions

View File

@@ -216,6 +216,7 @@ void CHARACTER::Initialize()
m_pkCheckSpeedHackEvent = NULL;
m_pkAutoPickupEvent = NULL;
m_pkStoneQueueEvent = NULL;
m_pkSwitchbotEvent = NULL;
m_speed_hack_count = 0;
m_pkAffectEvent = NULL;
@@ -311,6 +312,15 @@ void CHARACTER::Initialize()
m_iStoneQueueSuccessCount = 0;
m_iStoneQueueFailCount = 0;
m_vecStoneQueueSlots.clear();
for (size_t i = 0; i < m_switchbotSlots.size(); ++i)
{
m_switchbotSlots[i].active = false;
m_switchbotSlots[i].itemCell = 0;
m_switchbotSlots[i].attrType = 0;
m_switchbotSlots[i].minValue = 0;
m_switchbotSlots[i].attempts = 0;
}
m_iSwitchbotSpeedIndex = 1;
m_dwPolymorphRace = 0;
@@ -570,6 +580,7 @@ void CHARACTER::Destroy()
// END_OF_MINING
event_cancel(&m_pkAutoPickupEvent);
event_cancel(&m_pkStoneQueueEvent);
event_cancel(&m_pkSwitchbotEvent);
for (itertype(m_mapMobSkillEvent) it = m_mapMobSkillEvent.begin(); it != m_mapMobSkillEvent.end(); ++it)
@@ -6545,6 +6556,8 @@ void CHARACTER::RefreshAutoPickup()
namespace
{
const int kStoneQueueTick = PASSES_PER_SEC(1);
const int kSwitchbotSlotMax = 5;
const int kSwitchbotSpeedSeconds[] = { 3, 2, 1 };
enum
{
HYUNIRON_CHN = 1,
@@ -6595,6 +6608,61 @@ namespace
return ch->ProcessStoneQueueTick() ? kStoneQueueTick : 0;
}
bool IsSwitchbotScroll(LPITEM item)
{
if (!item || item->GetType() != ITEM_USE)
return false;
return item->GetSubType() == USE_CHANGE_ATTRIBUTE || item->GetSubType() == USE_CHANGE_ATTRIBUTE2;
}
bool IsSwitchbotEligibleItem(LPITEM item)
{
if (!item)
return false;
if (item->IsEquipped() || item->IsExchanging())
return false;
if (item->GetType() == ITEM_COSTUME)
return false;
if (item->GetAttributeSetIndex() == -1 || item->GetAttributeCount() == 0)
return false;
return item->GetType() == ITEM_WEAPON || item->GetType() == ITEM_ARMOR;
}
bool IsSwitchbotTargetReached(LPITEM item, BYTE attrType, short minValue)
{
if (!item || attrType == 0 || minValue <= 0)
return false;
for (int i = 0; i < ITEM_ATTRIBUTE_MAX_NUM; ++i)
{
if (item->GetAttributeType(i) == attrType && item->GetAttributeValue(i) >= minValue)
return true;
}
return false;
}
EVENTFUNC(switchbot_event)
{
char_event_info* info = dynamic_cast<char_event_info*>(event->info);
if (!info)
{
sys_err("switchbot_event> <Factor> Null pointer");
return 0;
}
LPCHARACTER ch = info->ch;
if (!ch)
return 0;
return ch->ProcessSwitchbotTick() ? PASSES_PER_SEC(ch->GetSwitchbotSpeedSeconds()) : 0;
}
}
int CHARACTER::GetStoneQueueMax() const
@@ -6702,6 +6770,219 @@ bool CHARACTER::ProcessStoneQueueTick()
return true;
}
int CHARACTER::GetSwitchbotActiveCount() const
{
int activeCount = 0;
for (size_t i = 0; i < m_switchbotSlots.size(); ++i)
{
if (m_switchbotSlots[i].active)
++activeCount;
}
return activeCount;
}
int CHARACTER::GetSwitchbotSpeedSeconds() const
{
const int speedIndexMax = sizeof(kSwitchbotSpeedSeconds) / sizeof(kSwitchbotSpeedSeconds[0]);
const int safeIndex = MINMAX(0, m_iSwitchbotSpeedIndex, speedIndexMax - 1);
return kSwitchbotSpeedSeconds[safeIndex];
}
void CHARACTER::SendSwitchbotState()
{
int switchItemCount = 0;
for (int i = 0; i < INVENTORY_MAX_NUM; ++i)
{
LPITEM item = GetInventoryItem(i);
if (!IsSwitchbotScroll(item))
continue;
switchItemCount += item->GetCount();
}
const int activeCount = GetSwitchbotActiveCount();
const int etaSeconds = activeCount > 0 ? (switchItemCount * GetSwitchbotSpeedSeconds()) / activeCount : 0;
ChatPacket(CHAT_TYPE_COMMAND, "SwitchbotState %d %d %d %d", m_iSwitchbotSpeedIndex, activeCount, switchItemCount, etaSeconds);
for (size_t i = 0; i < m_switchbotSlots.size(); ++i)
{
const TSwitchbotSlotState& slot = m_switchbotSlots[i];
ChatPacket(
CHAT_TYPE_COMMAND,
"SwitchbotSlot %d %d %d %d %d %d",
static_cast<int>(i),
slot.active ? 1 : 0,
slot.itemCell,
slot.attrType,
slot.minValue,
slot.attempts);
}
}
void CHARACTER::SetSwitchbotSpeed(int speedIndex)
{
const int speedIndexMax = sizeof(kSwitchbotSpeedSeconds) / sizeof(kSwitchbotSpeedSeconds[0]);
m_iSwitchbotSpeedIndex = MINMAX(0, speedIndex, speedIndexMax - 1);
if (m_pkSwitchbotEvent)
{
event_cancel(&m_pkSwitchbotEvent);
if (GetSwitchbotActiveCount() > 0)
{
char_event_info* info = AllocEventInfo<char_event_info>();
info->ch = this;
m_pkSwitchbotEvent = event_create(switchbot_event, info, PASSES_PER_SEC(GetSwitchbotSpeedSeconds()));
}
}
SendSwitchbotState();
}
bool CHARACTER::StartSwitchbotSlot(int switchbotSlot, BYTE itemCell, BYTE attrType, short minValue)
{
if (switchbotSlot < 0 || switchbotSlot >= kSwitchbotSlotMax)
return false;
LPITEM item = GetInventoryItem(itemCell);
if (!IsSwitchbotEligibleItem(item))
return false;
if (attrType == 0 || minValue <= 0)
return false;
TSwitchbotSlotState& slot = m_switchbotSlots[switchbotSlot];
slot.active = true;
slot.itemCell = itemCell;
slot.attrType = attrType;
slot.minValue = minValue;
slot.attempts = 0;
if (!m_pkSwitchbotEvent)
{
char_event_info* info = AllocEventInfo<char_event_info>();
info->ch = this;
m_pkSwitchbotEvent = event_create(switchbot_event, info, PASSES_PER_SEC(GetSwitchbotSpeedSeconds()));
}
SendSwitchbotState();
return true;
}
void CHARACTER::StopSwitchbotSlot(int switchbotSlot, bool clearConfig)
{
if (switchbotSlot < 0 || switchbotSlot >= kSwitchbotSlotMax)
return;
TSwitchbotSlotState& slot = m_switchbotSlots[switchbotSlot];
slot.active = false;
if (clearConfig)
{
slot.itemCell = 0;
slot.attrType = 0;
slot.minValue = 0;
slot.attempts = 0;
}
if (GetSwitchbotActiveCount() == 0)
event_cancel(&m_pkSwitchbotEvent);
SendSwitchbotState();
}
void CHARACTER::StopAllSwitchbotSlots(bool clearConfig)
{
for (size_t i = 0; i < m_switchbotSlots.size(); ++i)
{
m_switchbotSlots[i].active = false;
if (clearConfig)
{
m_switchbotSlots[i].itemCell = 0;
m_switchbotSlots[i].attrType = 0;
m_switchbotSlots[i].minValue = 0;
m_switchbotSlots[i].attempts = 0;
}
}
event_cancel(&m_pkSwitchbotEvent);
SendSwitchbotState();
}
bool CHARACTER::ProcessSwitchbotTick()
{
if (!IsPC() || IsDead())
{
StopAllSwitchbotSlots();
return false;
}
bool stateChanged = false;
for (size_t i = 0; i < m_switchbotSlots.size(); ++i)
{
TSwitchbotSlotState& slot = m_switchbotSlots[i];
if (!slot.active)
continue;
LPITEM targetItem = GetInventoryItem(slot.itemCell);
if (!IsSwitchbotEligibleItem(targetItem))
{
slot.active = false;
stateChanged = true;
continue;
}
if (IsSwitchbotTargetReached(targetItem, slot.attrType, slot.minValue))
{
slot.active = false;
stateChanged = true;
ChatPacket(CHAT_TYPE_INFO, LC_TEXT("Switchbot slot %d finished."), static_cast<int>(i) + 1);
continue;
}
int switchItemCell = -1;
for (int cell = 0; cell < INVENTORY_MAX_NUM; ++cell)
{
LPITEM switchItem = GetInventoryItem(cell);
if (!IsSwitchbotScroll(switchItem))
continue;
switchItemCell = cell;
break;
}
if (switchItemCell < 0)
{
ChatPacket(CHAT_TYPE_INFO, LC_TEXT("No switch items left."));
StopAllSwitchbotSlots();
return false;
}
if (!UseItem(TItemPos(INVENTORY, switchItemCell), TItemPos(INVENTORY, slot.itemCell)))
{
slot.active = false;
stateChanged = true;
continue;
}
++slot.attempts;
stateChanged = true;
}
if (GetSwitchbotActiveCount() == 0)
event_cancel(&m_pkSwitchbotEvent);
if (stateChanged)
SendSwitchbotState();
return m_pkSwitchbotEvent != NULL;
}
bool CHARACTER::IsGuardNPC() const
{
return IsNPC() && (GetRaceNum() == 11000 || GetRaceNum() == 11002 || GetRaceNum() == 11004);

View File

@@ -1,6 +1,7 @@
#ifndef __INC_METIN_II_CHAR_H__
#define __INC_METIN_II_CHAR_H__
#include <array>
#include <unordered_map>
#include "common/stl.h"
@@ -765,6 +766,14 @@ class CHARACTER : public CEntity, public CFSM, public CHorseRider
void CancelStoneQueue(bool resetResults = true);
void OnStoneQueueRefineResult(bool success);
bool ProcessStoneQueueTick();
int GetSwitchbotActiveCount() const;
int GetSwitchbotSpeedSeconds() const;
void SendSwitchbotState();
void SetSwitchbotSpeed(int speedIndex);
bool StartSwitchbotSlot(int switchbotSlot, BYTE itemCell, BYTE attrType, short minValue);
void StopSwitchbotSlot(int switchbotSlot, bool clearConfig = false);
void StopAllSwitchbotSlots(bool clearConfig = false);
bool ProcessSwitchbotTick();
bool IsPolymorphed() const { return m_dwPolymorphRace>0; }
bool IsPolyMaintainStat() const { return m_bPolyMaintainStat; } // 이전 스텟을 유지하는 폴리모프.
@@ -1190,6 +1199,16 @@ class CHARACTER : public CEntity, public CFSM, public CHorseRider
int m_iStoneQueueCurrentIndex;
int m_iStoneQueueSuccessCount;
int m_iStoneQueueFailCount;
struct TSwitchbotSlotState
{
bool active;
BYTE itemCell;
BYTE attrType;
short minValue;
int attempts;
};
std::array<TSwitchbotSlotState, 5> m_switchbotSlots;
int m_iSwitchbotSpeedIndex;
public:
////////////////////////////////////////////////////////////////////////////////////////
@@ -1797,6 +1816,7 @@ class CHARACTER : public CEntity, public CFSM, public CHorseRider
LPEVENT m_pkDestroyWhenIdleEvent;
LPEVENT m_pkAutoPickupEvent;
LPEVENT m_pkStoneQueueEvent;
LPEVENT m_pkSwitchbotEvent;
LPEVENT m_pkPetSystemUpdateEvent;
bool IsWarping() const { return m_pkWarpEvent ? true : false; }

View File

@@ -211,6 +211,7 @@ ACMD(do_biolog_submit);
ACMD(do_teleport_system);
ACMD(do_autopickup);
ACMD(do_stone_queue);
ACMD(do_switchbot);
ACMD(do_click_mall);
@@ -471,6 +472,7 @@ struct command_info cmd_info[] =
{ "teleport_system", do_teleport_system, 0, POS_DEAD, GM_PLAYER },
{ "autopickup", do_autopickup, 0, POS_DEAD, GM_PLAYER },
{ "stone_queue", do_stone_queue, 0, POS_DEAD, GM_PLAYER },
{ "switchbot", do_switchbot, 0, POS_DEAD, GM_PLAYER },
{ "siege", do_siege, 0, POS_DEAD, GM_LOW_WIZARD },
{ "temp", do_temp, 0, POS_DEAD, GM_IMPLEMENTOR },
{ "frog", do_frog, 0, POS_DEAD, GM_HIGH_WIZARD },

View File

@@ -2043,6 +2043,87 @@ ACMD(do_stone_queue)
ch->StartStoneQueue(slots, static_cast<BYTE>(scrollCell));
}
ACMD(do_switchbot)
{
char action[256];
argument = one_argument(argument, action, sizeof(action));
if (!*action || !strcmp(action, "sync"))
{
ch->SendSwitchbotState();
return;
}
if (!strcmp(action, "stop_all"))
{
ch->StopAllSwitchbotSlots();
return;
}
if (!strcmp(action, "speed"))
{
char speedArg[256];
argument = one_argument(argument, speedArg, sizeof(speedArg));
int speedIndex = 1;
str_to_number(speedIndex, speedArg);
ch->SetSwitchbotSpeed(speedIndex);
return;
}
char slotArg[256];
argument = one_argument(argument, slotArg, sizeof(slotArg));
int switchbotSlot = -1;
str_to_number(switchbotSlot, slotArg);
if (switchbotSlot < 0 || switchbotSlot >= 5)
{
ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("Invalid switchbot slot."));
return;
}
if (!strcmp(action, "stop"))
{
ch->StopSwitchbotSlot(switchbotSlot);
return;
}
if (!strcmp(action, "clear"))
{
ch->StopSwitchbotSlot(switchbotSlot, true);
return;
}
if (strcmp(action, "start"))
{
ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("Usage: /switchbot [sync|speed|start|stop|clear|stop_all]"));
return;
}
char itemArg[256];
char attrArg[256];
char valueArg[256];
argument = one_argument(argument, itemArg, sizeof(itemArg));
argument = one_argument(argument, attrArg, sizeof(attrArg));
argument = one_argument(argument, valueArg, sizeof(valueArg));
int itemCell = -1;
int attrType = 0;
int minValue = 0;
str_to_number(itemCell, itemArg);
str_to_number(attrType, attrArg);
str_to_number(minValue, valueArg);
if (itemCell < 0 || itemCell >= INVENTORY_MAX_NUM || attrType <= 0 || minValue <= 0)
{
ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("Invalid switchbot config."));
return;
}
if (!ch->StartSwitchbotSlot(switchbotSlot, static_cast<BYTE>(itemCell), static_cast<BYTE>(attrType), static_cast<short>(minValue)))
ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("Unable to start switchbot slot."));
}
ACMD(do_in_game_mall)
{
if (LC_IsYMIR() == true || LC_IsKorea() == true)