Merge branch 'main' into fix/client-freeze-on-drag

This commit is contained in:
rtw1x1
2026-01-04 11:10:28 +00:00
committed by GitHub
40 changed files with 2035 additions and 686 deletions

View File

@@ -2116,7 +2116,6 @@ void CInstanceBase::SetStateFlags(DWORD dwStateFlags)
// MR-4: Fix PK Mode Bug
// Prevent killer mode for same-guild attacks in GUILD PK mode
bool skipKiller = false;
if ((dwStateFlags & ADD_CHARACTER_STATE_KILLER) && PK_MODE_GUILD == GetPKMode()) {
CPythonPlayer& rkPlayer = CPythonPlayer::Instance();
@@ -2225,6 +2224,7 @@ bool CInstanceBase::IsAttackableInstance(CInstanceBase& rkInstVictim)
return false;
}
}
if (PK_MODE_GUILD == GetPKMode())
if (GetGuildID() == rkInstVictim.GetGuildID())
return false;
@@ -2255,6 +2255,9 @@ bool CInstanceBase::IsAttackableInstance(CInstanceBase& rkInstVictim)
if (IsPVPInstance(rkInstVictim))
return true;
if (rkInstVictim.GetPKMode() == PK_MODE_PROTECT)
return false;
// MR-4: Fix PK Mode Bug
if (PK_MODE_REVENGE == GetPKMode())
{
@@ -2262,12 +2265,13 @@ bool CInstanceBase::IsAttackableInstance(CInstanceBase& rkInstVictim)
{
if (
(GetGuildID() == 0 || GetGuildID() != rkInstVictim.GetGuildID()) &&
IsConflictAlignmentInstance(rkInstVictim) &&
rkInstVictim.GetAlignment() < 0
)
return true;
}
}
return false;
// MR-4: -- END OF -- Fix PK Mode Bug
}
else

View File

@@ -372,6 +372,7 @@ class CInstanceBase
EFFECT_HAPPINESS_RING_EQUIP, // 행복의 반지 착용 순간에 발동하는 이펙트
EFFECT_LOVE_PENDANT_EQUIP, // 행복의 반지 착용 순간에 발동하는 이펙트
EFFECT_TEMP,
EFFECT_AGGREGATE_MONSTER,
EFFECT_NUM,
};

View File

@@ -349,7 +349,12 @@ bool CInstanceBase::NEW_UseSkill(UINT uSkill, UINT uMot, UINT uMotLoopCount, boo
float fCurRot=m_GraphicThingInstance.GetTargetRotation();
SetAdvancingRotation(fCurRot);
m_GraphicThingInstance.InterceptOnceMotion(CRaceMotionData::NAME_SKILL + uMot, 0.1f, uSkill, 1.0f);
// MR-7: Don't show skill motion if character is invisible
if (!IsAffect(AFFECT_INVISIBILITY))
{
m_GraphicThingInstance.InterceptOnceMotion(CRaceMotionData::NAME_SKILL + uMot, 0.1f, uSkill, 1.0f);
}
// MR-7: -- END OF -- Don't show skill motion if character is invisible
m_GraphicThingInstance.__OnUseSkill(uMot, uMotLoopCount, isMovingSkill);

View File

@@ -1056,40 +1056,81 @@ void CInstanceBase::__DetachEffect(DWORD dwEID)
DWORD CInstanceBase::__AttachEffect(UINT eEftType)
{
// 2004.07.17.levites.isShow를 ViewFrustumCheck로 변경
if (IsAffect(AFFECT_INVISIBILITY))
return 0;
if (eEftType>=EFFECT_NUM)
if (eEftType >= EFFECT_NUM)
return 0;
if (ms_astAffectEffectAttachBone[eEftType].empty())
{
return m_GraphicThingInstance.AttachEffectByID(0, NULL, ms_adwCRCAffectEffect[eEftType]);
DWORD dwEftID = m_GraphicThingInstance.AttachEffectByID(0, NULL, ms_adwCRCAffectEffect[eEftType]);
// MR-7: Recover affect visual effects when coming out of invisibility
if (dwEftID && IsAffect(AFFECT_INVISIBILITY))
{
CEffectManager::Instance().SelectEffectInstance(dwEftID);
CEffectManager::Instance().HideEffect();
CEffectManager::Instance().ApplyAlwaysHidden();
}
return dwEftID;
// MR-7: -- END OF -- Recover affect visual effects when coming out of invisibility
}
else
{
std::string & rstrBoneName = ms_astAffectEffectAttachBone[eEftType];
const char * c_szBoneName;
// 양손에 붙일 때 사용한다.
// 이런 식의 예외 처리를 해놓은 것은 캐릭터 마다 Equip 의 Bone Name 이 다르기 때문.
if (0 == rstrBoneName.compare("PART_WEAPON"))
{
if (m_GraphicThingInstance.GetAttachingBoneName(CRaceData::PART_WEAPON, &c_szBoneName))
{
return m_GraphicThingInstance.AttachEffectByID(0, c_szBoneName, ms_adwCRCAffectEffect[eEftType]);
// MR-7: Recover affect visual effects when coming out of invisibility
DWORD dwEftID = m_GraphicThingInstance.AttachEffectByID(0, c_szBoneName, ms_adwCRCAffectEffect[eEftType]);
if (dwEftID && IsAffect(AFFECT_INVISIBILITY))
{
CEffectManager::Instance().SelectEffectInstance(dwEftID);
CEffectManager::Instance().HideEffect();
CEffectManager::Instance().ApplyAlwaysHidden();
}
return dwEftID;
// MR-7: -- END OF -- Recover affect visual effects when coming out of invisibility
}
}
else if (0 == rstrBoneName.compare("PART_WEAPON_LEFT"))
{
if (m_GraphicThingInstance.GetAttachingBoneName(CRaceData::PART_WEAPON_LEFT, &c_szBoneName))
{
return m_GraphicThingInstance.AttachEffectByID(0, c_szBoneName, ms_adwCRCAffectEffect[eEftType]);
// MR-7: Recover affect visual effects when coming out of invisibility
DWORD dwEftID = m_GraphicThingInstance.AttachEffectByID(0, c_szBoneName, ms_adwCRCAffectEffect[eEftType]);
if (dwEftID && IsAffect(AFFECT_INVISIBILITY))
{
CEffectManager::Instance().SelectEffectInstance(dwEftID);
CEffectManager::Instance().HideEffect();
CEffectManager::Instance().ApplyAlwaysHidden();
}
return dwEftID;
// MR-7: -- END OF -- Recover affect visual effects when coming out of invisibility
}
}
else
{
return m_GraphicThingInstance.AttachEffectByID(0, rstrBoneName.c_str(), ms_adwCRCAffectEffect[eEftType]);
// MR-7: Recover affect visual effects when coming out of invisibility
DWORD dwEftID = m_GraphicThingInstance.AttachEffectByID(0, rstrBoneName.c_str(), ms_adwCRCAffectEffect[eEftType]);
if (dwEftID && IsAffect(AFFECT_INVISIBILITY))
{
CEffectManager::Instance().SelectEffectInstance(dwEftID);
CEffectManager::Instance().HideEffect();
CEffectManager::Instance().ApplyAlwaysHidden();
}
return dwEftID;
// MR-7: -- END OF -- Recover affect visual effects when coming out of invisibility
}
}

View File

@@ -2260,7 +2260,8 @@ enum SPECIAL_EFFECT
SE_EQUIP_RAMADAN_RING, // 초승달의 반지를 착용하는 순간에 발동하는 이펙트
SE_EQUIP_HALLOWEEN_CANDY, // 할로윈 사탕을 착용(-_-;)한 순간에 발동하는 이펙트
SE_EQUIP_HAPPINESS_RING, // 크리스마스 행복의 반지를 착용하는 순간에 발동하는 이펙트
SE_EQUIP_LOVE_PENDANT, // 발렌타인 사랑의 팬던트(71145) 착용할 때 이펙트 (발동이펙트임, 지속이펙트 아님)
SE_EQUIP_LOVE_PENDANT, // 발렌타인 사랑의 팬던트(71145) 착용할 때 이펙트 (발동이펙트임, 지속이펙트 아님),
SE_AGGREGATE_MONSTER,
};
typedef struct SPacketGCSpecialEffect

View File

@@ -713,6 +713,12 @@ PyObject * chrmgrIsPossibleEmoticon(PyObject* poSelf, PyObject* poArgs)
return Py_BuildValue("i", result);
}
PyObject * chrmgrPreloadRaceMotions(PyObject* poSelf, PyObject* poArgs)
{
CRaceManager::PreloadPlayerRaceMotions();
return Py_BuildNone();
}
void initchrmgr()
{
static PyMethodDef s_methods[] =
@@ -746,6 +752,7 @@ void initchrmgr()
{ "SetAffect", chrmgrSetAffect, METH_VARARGS },
{ "SetEmoticon", chrmgrSetEmoticon, METH_VARARGS },
{ "IsPossibleEmoticon", chrmgrIsPossibleEmoticon, METH_VARARGS },
{ "PreloadRaceMotions", chrmgrPreloadRaceMotions, METH_VARARGS },
{ "RegisterEffect", chrmgrRegisterEffect, METH_VARARGS },
{ "RegisterCacheEffect", chrmgrRegisterCacheEffect, METH_VARARGS },
{ "RegisterPointEffect", chrmgrRegisterPointEffect, METH_VARARGS },
@@ -841,4 +848,5 @@ void initchrmgr()
PyModule_AddIntConstant(poModule, "EFFECT_HAPPINESS_RING_EQUIP", CInstanceBase::EFFECT_HAPPINESS_RING_EQUIP);
PyModule_AddIntConstant(poModule, "EFFECT_LOVE_PENDANT_EQUIP", CInstanceBase::EFFECT_LOVE_PENDANT_EQUIP);
PyModule_AddIntConstant(poModule, "EFFECT_AGGREGATE_MONSTER", CInstanceBase::EFFECT_AGGREGATE_MONSTER);
}

View File

@@ -499,9 +499,27 @@ void CPythonChat::AppendChat(int iType, const char * c_szChat)
SChatLine * pChatLine = SChatLine::New();
pChatLine->iType = iType;
// Pass chat text as-is to BiDi algorithm
// BuildVisualBidiText_Tagless will detect chat format and handle reordering
pChatLine->Instance.SetValue(c_szChat);
// Parse chat format "name : message" for proper BiDi handling
// This avoids issues with usernames containing " : "
const char* colonPos = strstr(c_szChat, " : ");
if (colonPos && colonPos != c_szChat) // Make sure " : " is not at the start
{
// Extract name and message
size_t nameLen = colonPos - c_szChat;
const char* msgStart = colonPos + 3; // Skip " : "
// Create temporary buffers
std::string name(c_szChat, nameLen);
std::string message(msgStart);
// Use new SetChatValue API for proper BiDi handling
pChatLine->Instance.SetChatValue(name.c_str(), message.c_str());
}
else
{
// Fallback: Not in chat format (INFO, NOTICE, etc.)
pChatLine->Instance.SetValue(c_szChat);
}
if (IsRTL())
{
@@ -768,9 +786,23 @@ void CWhisper::AppendChat(int iType, const char * c_szChat)
SChatLine * pChatLine = SChatLine::New();
// Pass chat text as-is to BiDi algorithm
// BuildVisualBidiText_Tagless will detect chat format and handle reordering
pChatLine->Instance.SetValue(c_szChat);
// Parse chat format "name : message" for proper BiDi handling
const char* colonPos = strstr(c_szChat, " : ");
if (colonPos && colonPos != c_szChat)
{
// Extract name and message
size_t nameLen = colonPos - c_szChat;
const char* msgStart = colonPos + 3;
std::string name(c_szChat, nameLen);
std::string message(msgStart);
pChatLine->Instance.SetChatValue(name.c_str(), message.c_str());
}
else
{
pChatLine->Instance.SetValue(c_szChat);
}
if (IsRTL())
{

View File

@@ -222,7 +222,9 @@ void CPythonMiniMap::Update(float fCenterX, float fCenterY)
}
}
const float c_fMiniMapWindowRadius = 55.0f;
// Calculate dynamic radius based on actual minimap window size
// Subtract border width (approx 9 pixels) to keep markers inside visible area
const float c_fMiniMapWindowRadius = (m_fWidth < m_fHeight ? m_fWidth : m_fHeight) / 2.0f - 9.0f;
float fDistanceFromCenterX = (rAtlasMarkInfo.m_fX - m_fCenterX) * fooCellScale * m_fScale;
float fDistanceFromCenterY = (rAtlasMarkInfo.m_fY - m_fCenterY) * fooCellScale * m_fScale;
@@ -230,12 +232,11 @@ void CPythonMiniMap::Update(float fCenterX, float fCenterY)
if (fDistanceFromCenter >= c_fMiniMapWindowRadius)
{
float fRadianX = acosf(fDistanceFromCenterX / fDistanceFromCenter);
float fRadianY = asinf(fDistanceFromCenterY / fDistanceFromCenter);
fDistanceFromCenterX = 55.0f * cosf(fRadianX);
fDistanceFromCenterY = 55.0f * sinf(fRadianY);
rAtlasMarkInfo.m_fMiniMapX = ( m_fWidth - (float)m_WhiteMark.GetWidth() ) / 2.0f + fDistanceFromCenterX + m_fScreenX + 2.0f;
rAtlasMarkInfo.m_fMiniMapY = ( m_fHeight - (float)m_WhiteMark.GetHeight() ) / 2.0f + fDistanceFromCenterY + m_fScreenY + 2.0f;
float fRadian = atan2f(fDistanceFromCenterY, fDistanceFromCenterX);
fDistanceFromCenterX = c_fMiniMapWindowRadius * cosf(fRadian);
fDistanceFromCenterY = c_fMiniMapWindowRadius * sinf(fRadian);
rAtlasMarkInfo.m_fMiniMapX = ( m_fWidth - (float)m_WhiteMark.GetWidth() ) / 2.0f + fDistanceFromCenterX + m_fScreenX;
rAtlasMarkInfo.m_fMiniMapY = ( m_fHeight - (float)m_WhiteMark.GetHeight() ) / 2.0f + fDistanceFromCenterY + m_fScreenY;
}
else
{
@@ -465,7 +466,10 @@ void CPythonMiniMap::Render(float fScreenX, float fScreenY)
if (rAtlasMarkInfo.m_fMiniMapY <= 0.0f)
continue;
__RenderTargetMark(rAtlasMarkInfo.m_fMiniMapX, rAtlasMarkInfo.m_fMiniMapY);
__RenderTargetMark(
rAtlasMarkInfo.m_fMiniMapX + m_WhiteMark.GetWidth() / 2,
rAtlasMarkInfo.m_fMiniMapY + m_WhiteMark.GetHeight() / 2
);
}
}
@@ -665,7 +669,10 @@ bool CPythonMiniMap::Create()
void CPythonMiniMap::__SetPosition()
{
m_fMiniMapRadius = fMIN(6400.0f / ((float) CTerrainImpl::CELLSCALE) * m_fScale, 64.0f);
// Calculate dynamic radius - use smaller dimension to ensure circular clipping
// Subtract border width (approx 9 pixels) to keep markers inside visible area
float fWindowRadius = (m_fWidth < m_fHeight ? m_fWidth : m_fHeight) / 2.0f - 9.0f;
m_fMiniMapRadius = fMIN(6400.0f / ((float) CTerrainImpl::CELLSCALE) * m_fScale, fWindowRadius);
m_matWorld._11 = m_fWidth * m_fScale;
m_matWorld._22 = m_fHeight * m_fScale;
@@ -1022,11 +1029,19 @@ void CPythonMiniMap::RenderAtlas(float fScreenX, float fScreenY)
if (TYPE_TARGET == rAtlasMarkInfo.m_byType)
{
__RenderMiniWayPointMark(rAtlasMarkInfo.m_fScreenX, rAtlasMarkInfo.m_fScreenY);
// Convert from WhiteMark-centered to actual center for rendering
__RenderMiniWayPointMark(
rAtlasMarkInfo.m_fScreenX + m_WhiteMark.GetWidth() / 2,
rAtlasMarkInfo.m_fScreenY + m_WhiteMark.GetHeight() / 2
);
}
else
{
__RenderWayPointMark(rAtlasMarkInfo.m_fScreenX, rAtlasMarkInfo.m_fScreenY);
// Convert from WhiteMark-centered to actual center for rendering
__RenderWayPointMark(
rAtlasMarkInfo.m_fScreenX + m_WhiteMark.GetWidth() / 2,
rAtlasMarkInfo.m_fScreenY + m_WhiteMark.GetHeight() / 2
);
}
}

View File

@@ -849,7 +849,9 @@ bool CPythonNetworkStream::RecvSpecialEffect()
case SE_EQUIP_LOVE_PENDANT:
effect = CInstanceBase::EFFECT_LOVE_PENDANT_EQUIP;
break;
case SE_AGGREGATE_MONSTER:
effect = CInstanceBase::EFFECT_AGGREGATE_MONSTER;
break;
default:
TraceError("%d 는 없는 스페셜 이펙트 번호입니다.TPacketGCSpecialEffect",kSpecialEffect.type);

View File

@@ -11,6 +11,7 @@
#include "MarkManager.h"
#include <utf8.h>
// EPlaceDir and TextTailBiDi() template are defined in utf8.h
const D3DXCOLOR c_TextTail_Player_Color = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);
const D3DXCOLOR c_TextTail_Monster_Color = D3DXCOLOR(1.0f, 0.0f, 0.0f, 1.0f);

View File

@@ -16,6 +16,8 @@
#include <filesystem>
#include <format>
#include <thread>
#include <atomic>
#include <stdlib.h>
#include <utf8.h>
@@ -166,11 +168,45 @@ bool PackInitialize(const char * c_pszFolder)
"uiloading",
};
CPackManager::instance().AddPack(std::format("{}/root.pck", c_pszFolder));
for (const std::string& packFileName : packFiles) {
CPackManager::instance().AddPack(std::format("{}/{}.pck", c_pszFolder, packFileName));
Tracef("PackInitialize: Loading root.pck...");
if (!CPackManager::instance().AddPack(std::format("{}/root.pck", c_pszFolder)))
{
TraceError("Failed to load root.pck");
return false;
}
Tracef("PackInitialize: Loading %d pack files in parallel...", packFiles.size());
const size_t numThreads = std::min<size_t>(std::thread::hardware_concurrency(), packFiles.size());
const size_t packsPerThread = (packFiles.size() + numThreads - 1) / numThreads;
std::vector<std::thread> threads;
std::atomic<size_t> failedCount(0);
for (size_t t = 0; t < numThreads; ++t)
{
threads.emplace_back([&, t]() {
size_t start = t * packsPerThread;
size_t end = std::min(start + packsPerThread, packFiles.size());
for (size_t i = start; i < end; ++i)
{
std::string packPath = std::format("{}/{}.pck", c_pszFolder, packFiles[i]);
if (!CPackManager::instance().AddPack(packPath))
{
TraceError("Failed to load %s", packPath.c_str());
failedCount++;
}
}
});
}
// Wait for all threads to complete
for (auto& thread : threads)
{
thread.join();
}
Tracef("PackInitialize: Completed! Failed: %d / %d", failedCount.load(), packFiles.size());
return true;
}