Compare commits
6 Commits
issue-4-bi
...
issue-10-t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
762603aaf5 | ||
|
|
91d1cfe519 | ||
|
|
1708a12e17 | ||
|
|
9e5bd67979 | ||
|
|
9f5851dac6 | ||
|
|
d935f92975 |
@@ -113,6 +113,7 @@ enum ECostumeSubTypes
|
||||
{
|
||||
COSTUME_BODY = ARMOR_BODY, // [중요!!] ECostumeSubTypes enum value는 종류별로 EArmorSubTypes의 그것과 같아야 함.
|
||||
COSTUME_HAIR = ARMOR_HEAD, // 이는 코스츔 아이템에 추가 속성을 붙이겠다는 사업부의 요청에 따라서 기존 로직을 활용하기 위함임.
|
||||
COSTUME_SASH = ARMOR_NUM_TYPES,
|
||||
COSTUME_NUM_TYPES,
|
||||
};
|
||||
|
||||
@@ -325,6 +326,8 @@ enum EItemWearableFlag
|
||||
WEARABLE_COSTUME_BODY = (1 << 12),
|
||||
WEARABLE_COSTUME_HAIR = (1 << 13),
|
||||
WEARABLE_BELT = (1 << 14),
|
||||
WEARABLE_COSTUME_SASH = (1 << 15),
|
||||
WEARABLE_TALISMAN = (1 << 16),
|
||||
};
|
||||
|
||||
enum ELimitTypes
|
||||
|
||||
@@ -123,6 +123,8 @@ enum EWearPositions
|
||||
WEAR_RING2, // 22 : 신규 반지슬롯2 (오른쪽)
|
||||
|
||||
WEAR_BELT, // 23 : 신규 벨트슬롯
|
||||
WEAR_COSTUME_SASH, // 24
|
||||
WEAR_TALISMAN, // 25
|
||||
|
||||
WEAR_MAX = 32 //
|
||||
};
|
||||
@@ -233,6 +235,7 @@ enum EParts
|
||||
PART_WEAPON,
|
||||
PART_HEAD,
|
||||
PART_HAIR,
|
||||
PART_ACCE,
|
||||
|
||||
PART_MAX_NUM,
|
||||
PART_WEAPON_SUB,
|
||||
|
||||
@@ -122,7 +122,7 @@ int get_Item_SubType_Value(int type_value, string inputString)
|
||||
"RESOURCE_STONE", "RESOURCE_METIN", "RESOURCE_ORE" };
|
||||
static string arSub16[] = { "UNIQUE_NONE", "UNIQUE_BOOK", "UNIQUE_SPECIAL_RIDE", "UNIQUE_3", "UNIQUE_4", "UNIQUE_5",
|
||||
"UNIQUE_6", "UNIQUE_7", "UNIQUE_8", "UNIQUE_9", "USE_SPECIAL"};
|
||||
static string arSub28[] = { "COSTUME_BODY", "COSTUME_HAIR" };
|
||||
static string arSub28[] = { "COSTUME_BODY", "COSTUME_HAIR", "COSTUME_SASH" };
|
||||
static string arSub29[] = { "DS_SLOT1", "DS_SLOT2", "DS_SLOT3", "DS_SLOT4", "DS_SLOT5", "DS_SLOT6" };
|
||||
static string arSub31[] = { "EXTRACT_DRAGON_SOUL", "EXTRACT_DRAGON_HEART" };
|
||||
|
||||
@@ -298,8 +298,9 @@ int get_Item_Flag_Value(string inputString)
|
||||
int get_Item_WearFlag_Value(string inputString)
|
||||
{
|
||||
|
||||
string arWearrFlag[] = {"WEAR_BODY", "WEAR_HEAD", "WEAR_FOOTS", "WEAR_WRIST", "WEAR_WEAPON", "WEAR_NECK", "WEAR_EAR", "WEAR_SHIELD", "WEAR_UNIQUE",
|
||||
"WEAR_ARROW", "WEAR_HAIR", "WEAR_ABILITY"};
|
||||
string arWearrFlag[] = {"WEAR_BODY", "WEAR_HEAD", "WEAR_FOOTS", "WEAR_WRIST", "WEAR_WEAPON", "WEAR_NECK", "WEAR_EAR", "WEAR_UNIQUE",
|
||||
"WEAR_SHIELD", "WEAR_ARROW", "WEAR_HAIR", "WEAR_ABILITY", "WEAR_COSTUME_BODY", "WEAR_COSTUME_HAIR",
|
||||
"WEAR_BELT", "WEAR_COSTUME_SASH", "WEAR_TALISMAN"};
|
||||
|
||||
|
||||
int retValue = 0;
|
||||
|
||||
@@ -214,6 +214,9 @@ void CHARACTER::Initialize()
|
||||
m_pkPoisonEvent = NULL;
|
||||
m_pkFireEvent = NULL;
|
||||
m_pkCheckSpeedHackEvent = NULL;
|
||||
m_pkAutoPickupEvent = NULL;
|
||||
m_pkStoneQueueEvent = NULL;
|
||||
m_pkSwitchbotEvent = NULL;
|
||||
m_speed_hack_count = 0;
|
||||
|
||||
m_pkAffectEvent = NULL;
|
||||
@@ -304,6 +307,20 @@ void CHARACTER::Initialize()
|
||||
// REFINE_NPC
|
||||
m_dwRefineNPCVID = 0;
|
||||
// END_OF_REFINE_NPC
|
||||
m_bStoneQueueScrollCell = 0;
|
||||
m_iStoneQueueCurrentIndex = 0;
|
||||
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;
|
||||
|
||||
@@ -561,6 +578,9 @@ void CHARACTER::Destroy()
|
||||
// MINING
|
||||
event_cancel(&m_pkMiningEvent);
|
||||
// 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)
|
||||
@@ -953,6 +973,7 @@ void CHARACTER::EncodeInsertPacket(LPENTITY entity)
|
||||
addPacket.awPart[CHR_EQUIPPART_WEAPON] = GetPart(PART_WEAPON);
|
||||
addPacket.awPart[CHR_EQUIPPART_HEAD] = GetPart(PART_HEAD);
|
||||
addPacket.awPart[CHR_EQUIPPART_HAIR] = GetPart(PART_HAIR);
|
||||
addPacket.awPart[CHR_EQUIPPART_ACCE] = GetPart(PART_ACCE);
|
||||
|
||||
addPacket.bPKMode = m_bPKMode;
|
||||
addPacket.dwMountVnum = GetMountVnum();
|
||||
@@ -1090,6 +1111,7 @@ void CHARACTER::UpdatePacket()
|
||||
pack.awPart[CHR_EQUIPPART_WEAPON] = GetPart(PART_WEAPON);
|
||||
pack.awPart[CHR_EQUIPPART_HEAD] = GetPart(PART_HEAD);
|
||||
pack.awPart[CHR_EQUIPPART_HAIR] = GetPart(PART_HAIR);
|
||||
pack.awPart[CHR_EQUIPPART_ACCE] = GetPart(PART_ACCE);
|
||||
|
||||
pack.bMovingSpeed = GetLimitPoint(POINT_MOV_SPEED);
|
||||
pack.bAttackSpeed = GetLimitPoint(POINT_ATT_SPEED);
|
||||
@@ -1788,6 +1810,7 @@ void CHARACTER::SetPlayerProto(const TPlayerTable * t)
|
||||
|
||||
m_pointsInstant.bBasePart = t->part_base;
|
||||
SetPart(PART_HAIR, t->parts[PART_HAIR]);
|
||||
SetPart(PART_ACCE, t->parts[PART_ACCE]);
|
||||
|
||||
m_points.iRandomHP = t->sRandomHP;
|
||||
m_points.iRandomSP = t->sRandomSP;
|
||||
@@ -2290,6 +2313,7 @@ void CHARACTER::ComputePoints()
|
||||
SetPart(PART_WEAPON, GetOriginalPart(PART_WEAPON));
|
||||
SetPart(PART_HEAD, GetOriginalPart(PART_HEAD));
|
||||
SetPart(PART_HAIR, GetOriginalPart(PART_HAIR));
|
||||
SetPart(PART_ACCE, GetOriginalPart(PART_ACCE));
|
||||
|
||||
SetPoint(POINT_PARTY_ATTACKER_BONUS, lAttackerBonus);
|
||||
SetPoint(POINT_PARTY_TANKER_BONUS, lTankerBonus);
|
||||
@@ -4447,6 +4471,9 @@ WORD CHARACTER::GetOriginalPart(BYTE bPartPos) const
|
||||
case PART_HAIR:
|
||||
return GetPart(PART_HAIR);
|
||||
|
||||
case PART_ACCE:
|
||||
return GetPart(PART_ACCE);
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
@@ -6476,6 +6503,493 @@ void CHARACTER::SetBlockModeForce(BYTE bFlag)
|
||||
ChatPacket(CHAT_TYPE_COMMAND, "setblockmode %d", m_pointsInstant.bBlockMode);
|
||||
}
|
||||
|
||||
bool CHARACTER::IsAutoPickupEnabled() const
|
||||
{
|
||||
return GetQuestFlag("autopickup.enabled") > 0;
|
||||
}
|
||||
|
||||
int CHARACTER::GetAutoPickupFilterMode() const
|
||||
{
|
||||
return GetQuestFlag("autopickup.mode") > 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
int CHARACTER::GetAutoPickupFilterMask() const
|
||||
{
|
||||
return GetQuestFlag("autopickup.mask") & 31;
|
||||
}
|
||||
|
||||
bool CHARACTER::HasAutoPickupVip() const
|
||||
{
|
||||
return GetPremiumRemainSeconds(PREMIUM_AUTOLOOT) > 0 || IsEquipUniqueGroup(UNIQUE_GROUP_AUTOLOOT);
|
||||
}
|
||||
|
||||
int CHARACTER::GetAutoPickupRadius() const
|
||||
{
|
||||
return HasAutoPickupVip() ? 300 : 220;
|
||||
}
|
||||
|
||||
void CHARACTER::SendAutoPickupState()
|
||||
{
|
||||
ChatPacket(CHAT_TYPE_COMMAND,
|
||||
"AutoPickupState %d %d %d %d",
|
||||
IsAutoPickupEnabled() ? 1 : 0,
|
||||
GetAutoPickupFilterMode(),
|
||||
GetAutoPickupFilterMask(),
|
||||
HasAutoPickupVip() ? 1 : 0);
|
||||
}
|
||||
|
||||
void CHARACTER::StopAutoPickupEvent()
|
||||
{
|
||||
event_cancel(&m_pkAutoPickupEvent);
|
||||
}
|
||||
|
||||
void CHARACTER::RefreshAutoPickup()
|
||||
{
|
||||
if (GetQuestFlag("autopickup.initialized") <= 0)
|
||||
{
|
||||
SetQuestFlag("autopickup.initialized", 1);
|
||||
SetQuestFlag("autopickup.mode", 0);
|
||||
SetQuestFlag("autopickup.mask", 31);
|
||||
}
|
||||
|
||||
if (IsAutoPickupEnabled())
|
||||
StartAutoPickupEvent();
|
||||
else
|
||||
StopAutoPickupEvent();
|
||||
|
||||
SendAutoPickupState();
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
const int kStoneQueueTick = PASSES_PER_SEC(1);
|
||||
const int kSwitchbotSlotMax = 5;
|
||||
const int kSwitchbotSpeedSeconds[] = { 3, 2, 1 };
|
||||
enum
|
||||
{
|
||||
HYUNIRON_CHN = 1,
|
||||
MUSIN_SCROLL = 3,
|
||||
BDRAGON_SCROLL = 6,
|
||||
};
|
||||
|
||||
int GetStoneQueueRefineType(LPITEM scrollItem, LPITEM targetItem)
|
||||
{
|
||||
if (!scrollItem || !targetItem)
|
||||
return -1;
|
||||
|
||||
if (scrollItem->GetType() != ITEM_USE || scrollItem->GetSubType() != USE_TUNING)
|
||||
return -1;
|
||||
|
||||
if (scrollItem->GetValue(0) == MUSIN_SCROLL)
|
||||
return REFINE_TYPE_MUSIN;
|
||||
|
||||
if (scrollItem->GetValue(0) == HYUNIRON_CHN)
|
||||
return REFINE_TYPE_HYUNIRON;
|
||||
|
||||
if (scrollItem->GetValue(0) == BDRAGON_SCROLL)
|
||||
{
|
||||
if (targetItem->GetRefineSet() != 702)
|
||||
return -1;
|
||||
|
||||
return REFINE_TYPE_BDRAGON;
|
||||
}
|
||||
|
||||
if (targetItem->GetRefineSet() == 501)
|
||||
return -1;
|
||||
|
||||
return REFINE_TYPE_SCROLL;
|
||||
}
|
||||
|
||||
EVENTFUNC(stone_queue_event)
|
||||
{
|
||||
char_event_info* info = dynamic_cast<char_event_info*>(event->info);
|
||||
if (!info)
|
||||
{
|
||||
sys_err("stone_queue_event> <Factor> Null pointer");
|
||||
return 0;
|
||||
}
|
||||
|
||||
LPCHARACTER ch = info->ch;
|
||||
if (!ch)
|
||||
return 0;
|
||||
|
||||
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
|
||||
{
|
||||
return GetPremiumRemainSeconds(PREMIUM_GOLD) > 0 ? 8 : 3;
|
||||
}
|
||||
|
||||
void CHARACTER::SendStoneQueueState()
|
||||
{
|
||||
ChatPacket(CHAT_TYPE_COMMAND,
|
||||
"StoneQueueState %d %d %d %d %d",
|
||||
GetStoneQueueMax(),
|
||||
m_pkStoneQueueEvent ? 1 : 0,
|
||||
m_iStoneQueueCurrentIndex,
|
||||
m_iStoneQueueSuccessCount,
|
||||
m_iStoneQueueFailCount);
|
||||
}
|
||||
|
||||
void CHARACTER::CancelStoneQueue(bool resetResults)
|
||||
{
|
||||
event_cancel(&m_pkStoneQueueEvent);
|
||||
m_vecStoneQueueSlots.clear();
|
||||
m_bStoneQueueScrollCell = 0;
|
||||
if (resetResults)
|
||||
{
|
||||
m_iStoneQueueCurrentIndex = 0;
|
||||
m_iStoneQueueSuccessCount = 0;
|
||||
m_iStoneQueueFailCount = 0;
|
||||
}
|
||||
SendStoneQueueState();
|
||||
}
|
||||
|
||||
void CHARACTER::StartStoneQueue(const std::vector<BYTE>& slots, BYTE scrollCell)
|
||||
{
|
||||
CancelStoneQueue();
|
||||
m_vecStoneQueueSlots = slots;
|
||||
m_bStoneQueueScrollCell = scrollCell;
|
||||
char_event_info* info = AllocEventInfo<char_event_info>();
|
||||
info->ch = this;
|
||||
m_pkStoneQueueEvent = event_create(stone_queue_event, info, 1);
|
||||
SendStoneQueueState();
|
||||
}
|
||||
|
||||
void CHARACTER::OnStoneQueueRefineResult(bool success)
|
||||
{
|
||||
if (!m_pkStoneQueueEvent)
|
||||
return;
|
||||
|
||||
if (success)
|
||||
++m_iStoneQueueSuccessCount;
|
||||
else
|
||||
++m_iStoneQueueFailCount;
|
||||
|
||||
++m_iStoneQueueCurrentIndex;
|
||||
|
||||
if (m_iStoneQueueCurrentIndex >= static_cast<int>(m_vecStoneQueueSlots.size()))
|
||||
{
|
||||
ChatPacket(CHAT_TYPE_INFO, LC_TEXT("Stone queue finished. Success: %d Fail: %d"), m_iStoneQueueSuccessCount, m_iStoneQueueFailCount);
|
||||
CancelStoneQueue(false);
|
||||
return;
|
||||
}
|
||||
|
||||
SendStoneQueueState();
|
||||
}
|
||||
|
||||
bool CHARACTER::ProcessStoneQueueTick()
|
||||
{
|
||||
if (!IsPC() || IsDead())
|
||||
{
|
||||
CancelStoneQueue();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_iStoneQueueCurrentIndex >= static_cast<int>(m_vecStoneQueueSlots.size()))
|
||||
{
|
||||
CancelStoneQueue();
|
||||
return false;
|
||||
}
|
||||
|
||||
LPITEM scrollItem = GetInventoryItem(m_bStoneQueueScrollCell);
|
||||
if (!scrollItem)
|
||||
{
|
||||
CancelStoneQueue();
|
||||
return false;
|
||||
}
|
||||
|
||||
const BYTE targetSlot = m_vecStoneQueueSlots[m_iStoneQueueCurrentIndex];
|
||||
LPITEM targetItem = GetInventoryItem(targetSlot);
|
||||
const int refineType = GetStoneQueueRefineType(scrollItem, targetItem);
|
||||
|
||||
if (!targetItem || targetItem->GetType() != ITEM_METIN || refineType < 0)
|
||||
{
|
||||
OnStoneQueueRefineResult(false);
|
||||
return m_pkStoneQueueEvent != NULL;
|
||||
}
|
||||
|
||||
SetRefineMode(m_bStoneQueueScrollCell);
|
||||
SetRefineTime();
|
||||
if (!DoRefineWithScroll(targetItem, refineType))
|
||||
{
|
||||
OnStoneQueueRefineResult(false);
|
||||
return m_pkStoneQueueEvent != NULL;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@@ -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"
|
||||
@@ -749,6 +750,30 @@ class CHARACTER : public CEntity, public CFSM, public CHorseRider
|
||||
void SetBlockMode(BYTE bFlag);
|
||||
void SetBlockModeForce(BYTE bFlag);
|
||||
bool IsBlockMode(BYTE bFlag) const { return (m_pointsInstant.bBlockMode & bFlag)?true:false; }
|
||||
void SendAutoPickupState();
|
||||
void RefreshAutoPickup();
|
||||
void StartAutoPickupEvent();
|
||||
void StopAutoPickupEvent();
|
||||
bool IsAutoPickupEnabled() const;
|
||||
int GetAutoPickupFilterMode() const;
|
||||
int GetAutoPickupFilterMask() const;
|
||||
bool HasAutoPickupVip() const;
|
||||
int GetAutoPickupRadius() const;
|
||||
bool ShouldAutoPickupItem(LPITEM item) const;
|
||||
int GetStoneQueueMax() const;
|
||||
void SendStoneQueueState();
|
||||
void StartStoneQueue(const std::vector<BYTE>& slots, BYTE scrollCell);
|
||||
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; } // 이전 스텟을 유지하는 폴리모프.
|
||||
@@ -1169,6 +1194,21 @@ class CHARACTER : public CEntity, public CFSM, public CHorseRider
|
||||
int m_iRefineAdditionalCell;
|
||||
bool m_bUnderRefine;
|
||||
DWORD m_dwRefineNPCVID;
|
||||
std::vector<BYTE> m_vecStoneQueueSlots;
|
||||
BYTE m_bStoneQueueScrollCell;
|
||||
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:
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -1774,6 +1814,9 @@ class CHARACTER : public CEntity, public CFSM, public CHorseRider
|
||||
LPEVENT m_pkWarpEvent;
|
||||
LPEVENT m_pkCheckSpeedHackEvent;
|
||||
LPEVENT m_pkDestroyWhenIdleEvent;
|
||||
LPEVENT m_pkAutoPickupEvent;
|
||||
LPEVENT m_pkStoneQueueEvent;
|
||||
LPEVENT m_pkSwitchbotEvent;
|
||||
LPEVENT m_pkPetSystemUpdateEvent;
|
||||
|
||||
bool IsWarping() const { return m_pkWarpEvent ? true : false; }
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
namespace
|
||||
{
|
||||
constexpr int kRefineNpcMaxDistance = 2000;
|
||||
const int kAutoPickupTick = passes_per_sec / 4;
|
||||
constexpr DWORD kPickupPatternWindowMs = 8 * 60 * 1000;
|
||||
constexpr int kPickupPatternMinSamples = 12;
|
||||
constexpr int kPickupPatternMaxIntervalSpreadMs = 180;
|
||||
@@ -53,6 +54,37 @@ namespace
|
||||
constexpr int kPickupPatternMinRouteStep = 250;
|
||||
constexpr int kPickupPatternMaxIntervalMs = 15000;
|
||||
|
||||
enum ETalismanElement
|
||||
{
|
||||
TALISMAN_ELEMENT_FIRE = 1,
|
||||
TALISMAN_ELEMENT_ICE = 2,
|
||||
TALISMAN_ELEMENT_LIGHTNING = 3,
|
||||
TALISMAN_ELEMENT_WIND = 4,
|
||||
TALISMAN_ELEMENT_EARTH = 5,
|
||||
};
|
||||
|
||||
const char* GetTalismanEffectPath(LPITEM item)
|
||||
{
|
||||
if (!item || item->GetType() != ITEM_TOTEM)
|
||||
return nullptr;
|
||||
|
||||
switch (item->GetValue(0))
|
||||
{
|
||||
case TALISMAN_ELEMENT_FIRE:
|
||||
return "d:/ymir work/effect/etc/firecracker/newyear_firecracker.mse";
|
||||
case TALISMAN_ELEMENT_ICE:
|
||||
return "d:/ymir work/effect/etc/recuperation/drugup_blue.mse";
|
||||
case TALISMAN_ELEMENT_LIGHTNING:
|
||||
return "d:/ymir work/effect/etc/recuperation/drugup_purple.mse";
|
||||
case TALISMAN_ELEMENT_WIND:
|
||||
return "d:/ymir work/effect/etc/recuperation/drugup_green.mse";
|
||||
case TALISMAN_ELEMENT_EARTH:
|
||||
return "d:/ymir work/effect/etc/buff/buff_item3.mse";
|
||||
default:
|
||||
return "d:/ymir work/effect/etc/buff/buff_item1.mse";
|
||||
}
|
||||
}
|
||||
|
||||
bool ValidateStoredRefineNpc(LPCHARACTER ch, DWORD dwRefineNpcVID, const char* action)
|
||||
{
|
||||
if (!ch)
|
||||
@@ -102,6 +134,117 @@ namespace
|
||||
return true;
|
||||
}
|
||||
|
||||
enum EAutoPickupFilterBits
|
||||
{
|
||||
AUTO_PICKUP_FILTER_WEAPON = 1 << 0,
|
||||
AUTO_PICKUP_FILTER_ARMOR = 1 << 1,
|
||||
AUTO_PICKUP_FILTER_YANG = 1 << 2,
|
||||
AUTO_PICKUP_FILTER_STONE = 1 << 3,
|
||||
AUTO_PICKUP_FILTER_MATERIAL = 1 << 4,
|
||||
};
|
||||
|
||||
int GetAutoPickupFilterBit(LPITEM item)
|
||||
{
|
||||
if (!item)
|
||||
return 0;
|
||||
|
||||
switch (item->GetType())
|
||||
{
|
||||
case ITEM_WEAPON:
|
||||
return AUTO_PICKUP_FILTER_WEAPON;
|
||||
|
||||
case ITEM_ARMOR:
|
||||
return AUTO_PICKUP_FILTER_ARMOR;
|
||||
|
||||
case ITEM_ELK:
|
||||
return AUTO_PICKUP_FILTER_YANG;
|
||||
|
||||
case ITEM_METIN:
|
||||
return AUTO_PICKUP_FILTER_STONE;
|
||||
|
||||
case ITEM_MATERIAL:
|
||||
return AUTO_PICKUP_FILTER_MATERIAL;
|
||||
|
||||
case ITEM_RESOURCE:
|
||||
if (item->GetSubType() == RESOURCE_STONE || item->GetSubType() == RESOURCE_METIN)
|
||||
return AUTO_PICKUP_FILTER_STONE;
|
||||
|
||||
return AUTO_PICKUP_FILTER_MATERIAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct AutoPickupScan
|
||||
{
|
||||
LPCHARACTER ch;
|
||||
LPITEM bestItem;
|
||||
int bestDistance;
|
||||
int radius;
|
||||
|
||||
AutoPickupScan(LPCHARACTER character, int maxDistance)
|
||||
: ch(character), bestItem(NULL), bestDistance(maxDistance + 1), radius(maxDistance)
|
||||
{
|
||||
}
|
||||
|
||||
void operator () (LPENTITY entity)
|
||||
{
|
||||
if (!entity || !entity->IsType(ENTITY_ITEM))
|
||||
return;
|
||||
|
||||
LPITEM item = static_cast<LPITEM>(entity);
|
||||
if (!item || !item->GetSectree() || !item->IsOwnership(ch))
|
||||
return;
|
||||
|
||||
if (!ch->ShouldAutoPickupItem(item))
|
||||
return;
|
||||
|
||||
const int distance = DISTANCE_APPROX(item->GetX() - ch->GetX(), item->GetY() - ch->GetY());
|
||||
if (distance > radius || distance >= bestDistance)
|
||||
return;
|
||||
|
||||
bestDistance = distance;
|
||||
bestItem = item;
|
||||
}
|
||||
};
|
||||
|
||||
EVENTFUNC(autopickup_event)
|
||||
{
|
||||
char_event_info* info = dynamic_cast<char_event_info*>(event->info);
|
||||
if (!info)
|
||||
{
|
||||
sys_err("autopickup_event> <Factor> Null pointer");
|
||||
return 0;
|
||||
}
|
||||
|
||||
LPCHARACTER ch = info->ch;
|
||||
if (!ch)
|
||||
return 0;
|
||||
|
||||
if (!ch->GetSectree() || !ch->IsPC() || ch->IsDead())
|
||||
{
|
||||
ch->m_pkAutoPickupEvent = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!ch->IsAutoPickupEnabled())
|
||||
{
|
||||
ch->m_pkAutoPickupEvent = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!ch->CanHandleItem() || ch->IsObserverMode())
|
||||
return MAX(1, kAutoPickupTick);
|
||||
|
||||
AutoPickupScan scan(ch, ch->GetAutoPickupRadius());
|
||||
ch->GetSectree()->ForEachAround(scan);
|
||||
|
||||
if (scan.bestItem)
|
||||
ch->PickupItem(scan.bestItem->GetVID());
|
||||
|
||||
return MAX(1, kAutoPickupTick);
|
||||
}
|
||||
|
||||
void ResetPickupPatternWindow(LPCHARACTER ch, DWORD now, long x, long y)
|
||||
{
|
||||
ch->m_dwPickupPatternWindowStart = now;
|
||||
@@ -918,6 +1061,7 @@ void NotifyRefineSuccess(LPCHARACTER ch, LPITEM item, const char* way, int iType
|
||||
if (NULL != ch && item != NULL)
|
||||
{
|
||||
ch->ChatPacket(CHAT_TYPE_COMMAND, "RefineSuceeded %d", iType);
|
||||
ch->OnStoneQueueRefineResult(true);
|
||||
|
||||
LogManager::instance().RefineLog(ch->GetPlayerID(), item->GetName(), item->GetID(), item->GetRefineLevel(), 1, way);
|
||||
}
|
||||
@@ -928,6 +1072,7 @@ void NotifyRefineFail(LPCHARACTER ch, LPITEM item, const char* way, int iType, i
|
||||
if (NULL != ch && NULL != item)
|
||||
{
|
||||
ch->ChatPacket(CHAT_TYPE_COMMAND, "RefineFailed %d", iType);
|
||||
ch->OnStoneQueueRefineResult(false);
|
||||
|
||||
LogManager::instance().RefineLog(ch->GetPlayerID(), item->GetName(), item->GetID(), item->GetRefineLevel(), success, way);
|
||||
}
|
||||
@@ -6222,6 +6367,30 @@ bool CHARACTER::PickupItem(DWORD dwVID)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CHARACTER::ShouldAutoPickupItem(LPITEM item) const
|
||||
{
|
||||
const int mask = GetAutoPickupFilterMask();
|
||||
const int filterBit = GetAutoPickupFilterBit(item);
|
||||
|
||||
if (GetAutoPickupFilterMode() == 0)
|
||||
return filterBit != 0 && (mask & filterBit) != 0;
|
||||
|
||||
if (filterBit == 0)
|
||||
return true;
|
||||
|
||||
return (mask & filterBit) == 0;
|
||||
}
|
||||
|
||||
void CHARACTER::StartAutoPickupEvent()
|
||||
{
|
||||
if (m_pkAutoPickupEvent || !IsPC() || !IsAutoPickupEnabled())
|
||||
return;
|
||||
|
||||
char_event_info* info = AllocEventInfo<char_event_info>();
|
||||
info->ch = this;
|
||||
m_pkAutoPickupEvent = event_create(autopickup_event, info, MAX(1, kAutoPickupTick));
|
||||
}
|
||||
|
||||
bool CHARACTER::SwapItem(BYTE bCell, BYTE bDestCell)
|
||||
{
|
||||
if (!CanHandleItem())
|
||||
@@ -6480,6 +6649,11 @@ bool CHARACTER::EquipItem(LPITEM item, int iCandidateCell)
|
||||
{
|
||||
this->EffectPacket(SE_EQUIP_LOVE_PENDANT);
|
||||
}
|
||||
else if (ITEM_TOTEM == item->GetType())
|
||||
{
|
||||
if (const char* c_szEffect = GetTalismanEffectPath(item))
|
||||
SpecificEffectPacket(c_szEffect);
|
||||
}
|
||||
// ITEM_UNIQUE의 경우, SpecialItemGroup에 정의되어 있고, (item->GetSIGVnum() != NULL)
|
||||
//
|
||||
else if (ITEM_UNIQUE == item->GetType() && 0 != item->GetSIGVnum())
|
||||
@@ -6568,7 +6742,7 @@ void CHARACTER::BuffOnAttr_ValueChange(BYTE bType, BYTE bOldValue, BYTE bNewValu
|
||||
break;
|
||||
case POINT_COSTUME_ATTR_BONUS:
|
||||
{
|
||||
static BYTE abSlot[] = { WEAR_COSTUME_BODY, WEAR_COSTUME_HAIR };
|
||||
static BYTE abSlot[] = { WEAR_COSTUME_BODY, WEAR_COSTUME_HAIR, WEAR_COSTUME_SASH };
|
||||
static std::vector <BYTE> vec_slots (abSlot, abSlot + _countof(abSlot));
|
||||
pBuff = M2_NEW CBuffOnAttributes(this, bType, &vec_slots);
|
||||
}
|
||||
|
||||
@@ -208,6 +208,11 @@ ACMD(do_get_mob_count);
|
||||
ACMD(do_dice);
|
||||
ACMD(do_special_item);
|
||||
ACMD(do_biolog_submit);
|
||||
ACMD(do_teleport_system);
|
||||
ACMD(do_autopickup);
|
||||
ACMD(do_stone_queue);
|
||||
ACMD(do_switchbot);
|
||||
ACMD(do_sash);
|
||||
|
||||
ACMD(do_click_mall);
|
||||
|
||||
@@ -465,6 +470,11 @@ struct command_info cmd_info[] =
|
||||
{ "inventory", do_inventory, 0, POS_DEAD, GM_LOW_WIZARD },
|
||||
{ "cube", do_cube, 0, POS_DEAD, GM_PLAYER },
|
||||
{ "biolog_submit", do_biolog_submit, 0, POS_DEAD, GM_PLAYER },
|
||||
{ "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 },
|
||||
{ "sash", do_sash, 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 },
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "stdafx.h"
|
||||
#include <sodium.h>
|
||||
#include <set>
|
||||
|
||||
#include "utils.h"
|
||||
#include "config.h"
|
||||
@@ -1866,6 +1867,418 @@ ACMD(do_biolog_submit)
|
||||
quest::CQuestManager::instance().QuestButton(ch->GetPlayerID(), questIndex);
|
||||
}
|
||||
|
||||
ACMD(do_teleport_system)
|
||||
{
|
||||
char arg1[256];
|
||||
char arg2[256];
|
||||
two_arguments(argument, arg1, sizeof(arg1), arg2, sizeof(arg2));
|
||||
|
||||
int action = 0;
|
||||
if (!strcmp(arg1, "save"))
|
||||
action = 1;
|
||||
else if (!strcmp(arg1, "saved"))
|
||||
action = 2;
|
||||
else if (!strcmp(arg1, "preset"))
|
||||
action = 3;
|
||||
else
|
||||
{
|
||||
ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("Unknown teleport action."));
|
||||
return;
|
||||
}
|
||||
|
||||
int arg = 0;
|
||||
str_to_number(arg, arg2);
|
||||
if (arg <= 0)
|
||||
{
|
||||
ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("Invalid teleport argument."));
|
||||
return;
|
||||
}
|
||||
|
||||
quest::PC* pPC = quest::CQuestManager::instance().GetPC(ch->GetPlayerID());
|
||||
if (!pPC)
|
||||
{
|
||||
ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("퀘스트를 로드하는 중입니다. 잠시만 기다려 주십시오."));
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string questName = "teleport_system";
|
||||
const DWORD questIndex = quest::CQuestManager::instance().GetQuestIndexByName(questName);
|
||||
if (questIndex == 0)
|
||||
{
|
||||
ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("Teleport quest could not be found."));
|
||||
return;
|
||||
}
|
||||
|
||||
pPC->SetFlag(questName + ".remote_action", action, true);
|
||||
pPC->SetFlag(questName + ".remote_arg", arg, true);
|
||||
quest::CQuestManager::instance().QuestButton(ch->GetPlayerID(), questIndex);
|
||||
}
|
||||
|
||||
ACMD(do_autopickup)
|
||||
{
|
||||
char arg1[256];
|
||||
char arg2[256];
|
||||
argument = one_argument(argument, arg1, sizeof(arg1));
|
||||
one_argument(argument, arg2, sizeof(arg2));
|
||||
|
||||
if (!*arg1 || !strcmp(arg1, "sync"))
|
||||
{
|
||||
ch->SendAutoPickupState();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!strcmp(arg1, "enable"))
|
||||
{
|
||||
int enabled = 0;
|
||||
str_to_number(enabled, arg2);
|
||||
ch->SetQuestFlag("autopickup.enabled", enabled > 0 ? 1 : 0);
|
||||
ch->RefreshAutoPickup();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!strcmp(arg1, "mode"))
|
||||
{
|
||||
int mode = 0;
|
||||
if (!strcmp(arg2, "blacklist"))
|
||||
mode = 1;
|
||||
else if (strcmp(arg2, "whitelist"))
|
||||
{
|
||||
ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("Unknown autopickup mode."));
|
||||
return;
|
||||
}
|
||||
|
||||
ch->SetQuestFlag("autopickup.mode", mode);
|
||||
ch->SendAutoPickupState();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!strcmp(arg1, "mask"))
|
||||
{
|
||||
int mask = 0;
|
||||
str_to_number(mask, arg2);
|
||||
ch->SetQuestFlag("autopickup.mask", MAX(0, MIN(31, mask)));
|
||||
ch->SendAutoPickupState();
|
||||
return;
|
||||
}
|
||||
|
||||
ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("Usage: /autopickup [sync|enable|mode|mask]"));
|
||||
}
|
||||
|
||||
ACMD(do_stone_queue)
|
||||
{
|
||||
char arg1[256];
|
||||
argument = one_argument(argument, arg1, sizeof(arg1));
|
||||
|
||||
if (!*arg1 || !strcmp(arg1, "sync"))
|
||||
{
|
||||
ch->SendStoneQueueState();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!strcmp(arg1, "cancel"))
|
||||
{
|
||||
ch->CancelStoneQueue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(arg1, "start"))
|
||||
{
|
||||
ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("Usage: /stone_queue [sync|cancel|start]"));
|
||||
return;
|
||||
}
|
||||
|
||||
char scrollArg[256];
|
||||
argument = one_argument(argument, scrollArg, sizeof(scrollArg));
|
||||
|
||||
int scrollCell = -1;
|
||||
str_to_number(scrollCell, scrollArg);
|
||||
if (scrollCell < 0 || scrollCell >= INVENTORY_MAX_NUM)
|
||||
{
|
||||
ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("Invalid stone queue scroll slot."));
|
||||
return;
|
||||
}
|
||||
|
||||
LPITEM scrollItem = ch->GetInventoryItem(scrollCell);
|
||||
if (!scrollItem || scrollItem->GetType() != ITEM_USE || scrollItem->GetSubType() != USE_TUNING)
|
||||
{
|
||||
ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("Invalid stone queue scroll item."));
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<BYTE> slots;
|
||||
std::set<int> uniqueSlots;
|
||||
char slotArg[256];
|
||||
|
||||
while (*argument)
|
||||
{
|
||||
argument = one_argument(argument, slotArg, sizeof(slotArg));
|
||||
if (!*slotArg)
|
||||
break;
|
||||
|
||||
int slot = -1;
|
||||
str_to_number(slot, slotArg);
|
||||
if (slot < 0 || slot >= INVENTORY_MAX_NUM)
|
||||
continue;
|
||||
|
||||
if (uniqueSlots.find(slot) != uniqueSlots.end())
|
||||
continue;
|
||||
|
||||
LPITEM targetItem = ch->GetInventoryItem(slot);
|
||||
if (!targetItem || targetItem->GetType() != ITEM_METIN)
|
||||
continue;
|
||||
|
||||
if (slots.size() >= static_cast<size_t>(ch->GetStoneQueueMax()))
|
||||
break;
|
||||
|
||||
uniqueSlots.insert(slot);
|
||||
slots.push_back(static_cast<BYTE>(slot));
|
||||
}
|
||||
|
||||
if (slots.empty())
|
||||
{
|
||||
ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("Stone queue is empty."));
|
||||
return;
|
||||
}
|
||||
|
||||
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."));
|
||||
}
|
||||
|
||||
static bool FN_is_sash_item(LPITEM item)
|
||||
{
|
||||
return item && item->GetType() == ITEM_COSTUME && item->GetSubType() == COSTUME_SASH;
|
||||
}
|
||||
|
||||
static bool FN_is_sash_absorb_source(LPITEM item)
|
||||
{
|
||||
if (!item)
|
||||
return false;
|
||||
|
||||
if (item->GetType() == ITEM_WEAPON)
|
||||
return item->GetSubType() != WEAPON_ARROW;
|
||||
|
||||
if (item->GetType() != ITEM_ARMOR)
|
||||
return false;
|
||||
|
||||
switch (item->GetSubType())
|
||||
{
|
||||
case ARMOR_BODY:
|
||||
case ARMOR_HEAD:
|
||||
case ARMOR_SHIELD:
|
||||
case ARMOR_WRIST:
|
||||
case ARMOR_FOOTS:
|
||||
case ARMOR_NECK:
|
||||
case ARMOR_EAR:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void FN_set_sash_attrs_from_source(LPITEM sash, LPITEM source, int absorbPct)
|
||||
{
|
||||
struct SAttrEntry
|
||||
{
|
||||
BYTE type;
|
||||
short value;
|
||||
};
|
||||
|
||||
std::vector<SAttrEntry> attrs;
|
||||
const TItemTable* proto = source->GetProto();
|
||||
if (!proto)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < ITEM_APPLY_MAX_NUM; ++i)
|
||||
{
|
||||
const TItemApply& apply = proto->aApplies[i];
|
||||
if (apply.bType == APPLY_NONE || apply.lValue == 0)
|
||||
continue;
|
||||
|
||||
long scaled = (apply.lValue * absorbPct) / 100;
|
||||
if (scaled == 0)
|
||||
scaled = (apply.lValue > 0) ? 1 : -1;
|
||||
|
||||
attrs.push_back({ apply.bType, static_cast<short>(scaled) });
|
||||
if (attrs.size() >= ITEM_ATTRIBUTE_MAX_NUM)
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = 0; i < source->GetAttributeCount() && attrs.size() < ITEM_ATTRIBUTE_MAX_NUM; ++i)
|
||||
{
|
||||
const TPlayerItemAttribute& attr = source->GetAttribute(i);
|
||||
if (!attr.bType || !attr.sValue)
|
||||
continue;
|
||||
|
||||
bool duplicated = false;
|
||||
for (size_t j = 0; j < attrs.size(); ++j)
|
||||
{
|
||||
if (attrs[j].type == attr.bType)
|
||||
{
|
||||
duplicated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (duplicated)
|
||||
continue;
|
||||
|
||||
long scaled = (attr.sValue * absorbPct) / 100;
|
||||
if (scaled == 0)
|
||||
scaled = (attr.sValue > 0) ? 1 : -1;
|
||||
|
||||
attrs.push_back({ attr.bType, static_cast<short>(scaled) });
|
||||
}
|
||||
|
||||
sash->ClearAttribute();
|
||||
for (size_t i = 0; i < attrs.size(); ++i)
|
||||
sash->SetForceAttribute(static_cast<int>(i), attrs[i].type, attrs[i].value);
|
||||
}
|
||||
|
||||
ACMD(do_sash)
|
||||
{
|
||||
char action[256];
|
||||
argument = one_argument(argument, action, sizeof(action));
|
||||
|
||||
if (strcmp(action, "absorb"))
|
||||
{
|
||||
ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("Usage: /sash absorb <sash_slot> <item_slot>"));
|
||||
return;
|
||||
}
|
||||
|
||||
char sashArg[256];
|
||||
char sourceArg[256];
|
||||
argument = one_argument(argument, sashArg, sizeof(sashArg));
|
||||
argument = one_argument(argument, sourceArg, sizeof(sourceArg));
|
||||
|
||||
int sashCell = -1;
|
||||
int sourceCell = -1;
|
||||
str_to_number(sashCell, sashArg);
|
||||
str_to_number(sourceCell, sourceArg);
|
||||
|
||||
if (sashCell < 0 || sashCell >= INVENTORY_MAX_NUM || sourceCell < 0 || sourceCell >= INVENTORY_MAX_NUM || sashCell == sourceCell)
|
||||
{
|
||||
ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("Invalid sash slots."));
|
||||
return;
|
||||
}
|
||||
|
||||
LPITEM sash = ch->GetInventoryItem(sashCell);
|
||||
LPITEM source = ch->GetInventoryItem(sourceCell);
|
||||
|
||||
if (!FN_is_sash_item(sash))
|
||||
{
|
||||
ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("The selected item is not a sash."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!FN_is_sash_absorb_source(source))
|
||||
{
|
||||
ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("This item cannot be absorbed by a sash."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (sash->IsEquipped() || source->IsEquipped())
|
||||
{
|
||||
ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("Unequip items before absorbing bonuses."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (sash->GetSocket(0) != 0)
|
||||
{
|
||||
ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("This sash already has absorbed bonuses."));
|
||||
return;
|
||||
}
|
||||
|
||||
int absorbPct = MINMAX(1, static_cast<int>(sash->GetValue(0)), 100);
|
||||
FN_set_sash_attrs_from_source(sash, source, absorbPct);
|
||||
|
||||
sash->SetSocket(0, source->GetVnum());
|
||||
sash->SetSocket(1, absorbPct);
|
||||
sash->SetSocket(2, static_cast<int32_t>(source->GetID()));
|
||||
|
||||
source->SetCount(0);
|
||||
ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("The sash absorbed bonuses successfully."));
|
||||
}
|
||||
|
||||
ACMD(do_in_game_mall)
|
||||
{
|
||||
if (LC_IsYMIR() == true || LC_IsKorea() == true)
|
||||
|
||||
@@ -552,6 +552,7 @@ void CInputLogin::Entergame(LPDESC d, const char * data)
|
||||
ch->StartSaveEvent();
|
||||
ch->StartRecoveryEvent();
|
||||
ch->StartCheckSpeedHackEvent();
|
||||
ch->RefreshAutoPickup();
|
||||
|
||||
CPVPManager::instance().Connect(ch);
|
||||
CPVPManager::instance().SendList(d);
|
||||
|
||||
@@ -523,6 +523,8 @@ int CItem::FindEquipCell(LPCHARACTER ch, int iCandidateCell)
|
||||
return WEAR_COSTUME_BODY;
|
||||
else if (GetSubType() == COSTUME_HAIR)
|
||||
return WEAR_COSTUME_HAIR;
|
||||
else if (GetSubType() == COSTUME_SASH)
|
||||
return WEAR_COSTUME_SASH;
|
||||
}
|
||||
else if (GetType() == ITEM_RING)
|
||||
{
|
||||
@@ -533,6 +535,8 @@ int CItem::FindEquipCell(LPCHARACTER ch, int iCandidateCell)
|
||||
}
|
||||
else if (GetType() == ITEM_BELT)
|
||||
return WEAR_BELT;
|
||||
else if (GetType() == ITEM_TOTEM)
|
||||
return WEAR_TALISMAN;
|
||||
else if (GetWearFlag() & WEARABLE_BODY)
|
||||
return WEAR_BODY;
|
||||
else if (GetWearFlag() & WEARABLE_HEAD)
|
||||
@@ -781,6 +785,11 @@ void CItem::ModifyPoints(bool bAdd)
|
||||
// [NOTE] 갑옷은 아이템 vnum을 보내고 헤어는 shape(value3)값을 보내는 이유는.. 기존 시스템이 그렇게 되어있음...
|
||||
toSetValue = (true == bAdd) ? this->GetValue(3) : 0;
|
||||
}
|
||||
else if (GetSubType() == COSTUME_SASH)
|
||||
{
|
||||
toSetPart = PART_ACCE;
|
||||
toSetValue = (true == bAdd) ? this->GetVnum() : 0;
|
||||
}
|
||||
|
||||
if (PART_MAX_NUM != toSetPart)
|
||||
{
|
||||
|
||||
@@ -583,6 +583,7 @@ enum ECharacterEquipmentPart
|
||||
CHR_EQUIPPART_WEAPON,
|
||||
CHR_EQUIPPART_HEAD,
|
||||
CHR_EQUIPPART_HAIR,
|
||||
CHR_EQUIPPART_ACCE,
|
||||
CHR_EQUIPPART_NUM,
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user