-
Posts
25 -
Joined
-
Last visited
-
Feedback
0%
About saudidos

Informations
-
Gender
Male
-
Country
Saudi Arabia
-
Nationality
Saudi
Recent Profile Visitors
saudidos's Achievements
-
Over the last few weeks I ran into a very specific client-side crash that only occurs during warp / map change / respawn moments. The crash would happen randomly when the client tries to attach a weapon model before the race data, model instance, or bone map is actually ready. In other words: AttachWeapon() was being called during a window where the model for that character wasn’t finished loading yet. This results in a nullptr dereference inside: CGraphicThingInstance::RegisterModelThing CActorInstance::AttachWeapon CInstanceBase::Update Depending on your client, this may appear only when a protection module like CShield is enabled (because it shifts frame timing slightly and exposes the race condition more often). Why This Happens During warp/spawn, the weapon attachment code fires immediately. But the model + bones are not always fully available in the same frame. So the game tries to attach to a bone that does not exist yet → crash. This has existed in the client codebase for years. Not related to CShield itself. This is a real engine bug. Solution The correct fix is to defer weapon attachment until the character’s model and bone hierarchy become valid. This was implemented by: Hardening RegisterModelThing() against null resources. Introducing a deferred attach queue (QueuePendingWeaponAttach / ProcessPendingWeaponAttach). Running ProcessPendingWeaponAttach() early in CInstanceBase::Update() so the weapon attaches as soon as the model is ready. Minimal Patch Highlights Resource / Debug Safe Names Source-client/EterLib/Resource.h protected: static bool ms_bDeleteImmediately; }; Add #ifdef ENABLE_CSHIELD_DEBUG static inline const char* CSafeResName(const CResource* r) { return r ? r->GetFileName() : "<null-resource>"; } #else static inline const char* CSafeResName(const CResource* r) { return r ? r->GetFileName() : ""; } #endif RegisterModelThing Null Guard Source-client/EterGrnLib/ThingInstance.cpp search for : void CGraphicThingInstance::RegisterModelThing(int iModelThing, CGraphicThing* pModelThing) { if (!CheckModelThingIndex(iModelThing)) { TraceError("CGraphicThingInstance::RegisterModelThing(iModelThing=%d, pModelThing=%s)\n", iModelThing, pModelThing->GetFileName()); return; } m_modelThingSetVector[iModelThing].Clear(); if (pModelThing) RegisterLODThing(iModelThing, pModelThing); } replace it with: void CGraphicThingInstance::RegisterModelThing(int iModelThing, CGraphicThing* pModelThing) { // Validate destination slot if (!CheckModelThingIndex(iModelThing)) { #ifdef ENABLE_CSHIELD_DEBUG TraceError("CGraphicThingInstance::RegisterModelThing: invalid index iModelThing=%d this=%p", iModelThing, this); #endif return; } // Null model guard #ifdef ENABLE_CSHIELD_DEBUG if (!pModelThing) { TraceError("[MODEL] RegisterModelThing: null pModelThing iModelThing=%d this=%p", iModelThing, this); // Clear destination and bail m_modelThingSetVector[iModelThing].Clear(); return; } #endif if (!pModelThing) { m_modelThingSetVector[iModelThing].Clear(); return; } // Resource guard auto* __res = static_cast<CResource*>(pModelThing); if (!__res) { #ifdef ENABLE_CSHIELD_DEBUG TraceError("[MODEL] RegisterModelThing: null Resource ptr iModelThing=%d this=%p", iModelThing, this); #endif m_modelThingSetVector[iModelThing].Clear(); return; } // Clear previous LOD refs m_modelThingSetVector[iModelThing].Clear(); // Register primary LOD when resource is valid RegisterLODThing(iModelThing, pModelThing); } Deferred Attach Support Source-client/GameLib/ActorInstance.h search for: void AttachWeapon(DWORD dwItemIndex, DWORD dwParentPartIndex = CRaceData::PART_MAIN, DWORD dwPartIndex = CRaceData::PART_WEAPON); void AttachWeapon(DWORD dwParentPartIndex, DWORD dwPartIndex, CItemData* pItemData); replace it with : void AttachWeapon(DWORD dwItemIndex, DWORD dwParentPartIndex = CRaceData::PART_MAIN, DWORD dwPartIndex = CRaceData::PART_WEAPON); void AttachWeapon(DWORD dwParentPartIndex, DWORD dwPartIndex, CItemData* pItemData); // Deferred weapon attach API (to avoid crashes when model resource isn't ready yet) void QueuePendingWeaponAttach(DWORD dwItemIndex, DWORD dwParentPartIndex = CRaceData::PART_MAIN, DWORD dwPartIndex = CRaceData::PART_WEAPON); bool ProcessPendingWeaponAttach(); also search for : DWORD m_adwPartItemID[CRaceData::PART_MAX_NUM]; replace it with : DWORD m_adwPartItemID[CRaceData::PART_MAX_NUM]; // Pending weapon attachment (deferred until model resource is ready) struct SPendingWeapon { DWORD dwItemIndex; DWORD dwParentPartIndex; DWORD dwPartIndex; DWORD dwRetryCount; SPendingWeapon() : dwItemIndex(0) , dwParentPartIndex(CRaceData::PART_MAIN) , dwPartIndex(CRaceData::PART_WEAPON) , dwRetryCount(0) {} }; bool m_bPendingWeaponAttach; SPendingWeapon m_PendingWeapon; The Part That Was Missing Before (Important!) Source-client/GameLib/ActorInstanceAttach.cpp This is the critical logic to retry attach attempts until bones exist search for : ------- DWORD Vietnam_ConvertWeaponVnum(DWORD vnum) { DWORD base = vnum / 10 * 10; DWORD rest = vnum % 10; switch (base) { case 10:base = 5000; break; case 20:base = 5010; break; case 30:base = 5020; break; case 40:base = 5030; break; case 50:base = 5030; break; case 60:base = 5040; break; case 70:base = 5040; break; case 80:base = 5050; break; case 90:base = 5050; break; case 100:base = 5060; break; case 110:base = 5060; break; case 120:base = 5070; break; case 130:base = 5070; break; case 140:base = 5080; break; case 150:base = 5080; break; case 160:base = 5090; break; case 170:base = 5090; break; case 180:base = 5100; break; case 190:base = 5100; break; case 200:base = 5110; break; case 210:base = 5110; break; case 220:base = 5120; break; case 230:base = 5120; break; case 240:base = 5130; break; case 250:base = 5130; break; case 260:base = 5140; break; case 270:base = 5140; break; case 280:base = 5150; break; case 290:base = 5150; break; case 1000:base = 5000; break; case 1010:base = 5010; break; case 1020:base = 5020; break; case 1030:base = 5030; break; case 1040:base = 5040; break; case 1050:base = 5050; break; case 1060:base = 5060; break; case 1070:base = 5070; break; case 1080:base = 5080; break; case 1090:base = 5090; break; case 1100:base = 5100; break; case 1110:base = 5110; break; case 1120:base = 5120; break; case 1130:base = 5130; break; case 1140:base = 5140; break; case 1150:base = 5150; break; case 1160:base = 5150; break; case 1170:base = 5150; break; case 3000:base = 5000; break; case 3010:base = 5010; break; case 3020:base = 5020; break; case 3030:base = 5030; break; case 3040:base = 5040; break; case 3050:base = 5050; break; case 3060:base = 5060; break; case 3070:base = 5070; break; case 3080:base = 5080; break; case 3090:base = 5090; break; case 3100:base = 5100; break; case 3110:base = 5100; break; case 3120:base = 5110; break; case 3130:base = 5110; break; case 3140:base = 5120; break; case 3150:base = 5120; break; case 3160:base = 5130; break; case 3170:base = 5130; break; case 3180:base = 5140; break; case 3190:base = 5140; break; case 3200:base = 5150; break; case 3210:base = 5150; break; } return base + rest; } replace with : DWORD Vietnam_ConvertWeaponVnum(DWORD vnum) { DWORD base = vnum / 10 * 10; DWORD rest = vnum % 10; switch (base) { case 10:base = 5000; break; case 20:base = 5010; break; case 30:base = 5020; break; case 40:base = 5030; break; case 50:base = 5030; break; case 60:base = 5040; break; case 70:base = 5040; break; case 80:base = 5050; break; case 90:base = 5050; break; case 100:base = 5060; break; case 110:base = 5060; break; case 120:base = 5070; break; case 130:base = 5070; break; case 140:base = 5080; break; case 150:base = 5080; break; case 160:base = 5090; break; case 170:base = 5090; break; case 180:base = 5100; break; case 190:base = 5100; break; case 200:base = 5110; break; case 210:base = 5110; break; case 220:base = 5120; break; case 230:base = 5120; break; case 240:base = 5130; break; case 250:base = 5130; break; case 260:base = 5140; break; case 270:base = 5140; break; case 280:base = 5150; break; case 290:base = 5150; break; case 1000:base = 5000; break; case 1010:base = 5010; break; case 1020:base = 5020; break; case 1030:base = 5030; break; case 1040:base = 5040; break; case 1050:base = 5050; break; case 1060:base = 5060; break; case 1070:base = 5070; break; case 1080:base = 5080; break; case 1090:base = 5090; break; case 1100:base = 5100; break; case 1110:base = 5110; break; case 1120:base = 5120; break; case 1130:base = 5130; break; case 1140:base = 5140; break; case 1150:base = 5150; break; case 1160:base = 5150; break; case 1170:base = 5150; break; case 3000:base = 5000; break; case 3010:base = 5010; break; case 3020:base = 5020; break; case 3030:base = 5030; break; case 3040:base = 5040; break; case 3050:base = 5050; break; case 3060:base = 5060; break; case 3070:base = 5070; break; case 3080:base = 5080; break; case 3090:base = 5090; break; case 3100:base = 5100; break; case 3110:base = 5100; break; case 3120:base = 5110; break; case 3130:base = 5110; break; case 3140:base = 5120; break; case 3150:base = 5120; break; case 3160:base = 5130; break; case 3170:base = 5130; break; case 3180:base = 5140; break; case 3190:base = 5140; break; case 3200:base = 5150; break; case 3210:base = 5150; break; } return base + rest; } // Deferred weapon attach queue/process methods void CActorInstance::QueuePendingWeaponAttach(DWORD dwItemIndex, DWORD dwParentPartIndex, DWORD dwPartIndex) { // Avoid resetting retries if same request is already pending if (m_bPendingWeaponAttach && m_PendingWeapon.dwItemIndex == dwItemIndex && m_PendingWeapon.dwParentPartIndex == dwParentPartIndex && m_PendingWeapon.dwPartIndex == dwPartIndex) { return; } m_PendingWeapon.dwItemIndex = dwItemIndex; m_PendingWeapon.dwParentPartIndex = dwParentPartIndex; m_PendingWeapon.dwPartIndex = dwPartIndex; m_PendingWeapon.dwRetryCount = 0; m_bPendingWeaponAttach = true; #ifdef ENABLE_CSHIELD_DEBUG TraceError("[PENDING] QueueWeaponAttach item=%u parent=%u part=%u", dwItemIndex, dwParentPartIndex, dwPartIndex); #endif } bool CActorInstance::ProcessPendingWeaponAttach() { if (!m_bPendingWeaponAttach) return false; // Increment retry counter and attempt ++m_PendingWeapon.dwRetryCount; AttachWeapon(m_PendingWeapon.dwItemIndex, m_PendingWeapon.dwParentPartIndex, m_PendingWeapon.dwPartIndex); // Heuristic: if model/bone is resolvable now, clear pending const char* szBoneName = nullptr; if (GetAttachingBoneName(m_PendingWeapon.dwPartIndex, &szBoneName) && szBoneName) { int iBoneIndex = -1; if (FindBoneIndex(m_PendingWeapon.dwPartIndex, szBoneName, &iBoneIndex)) { #ifdef ENABLE_CSHIELD_DEBUG TraceError("[PENDING] Weapon attach ready item=%u part=%u after %u retries", m_PendingWeapon.dwItemIndex, m_PendingWeapon.dwPartIndex, m_PendingWeapon.dwRetryCount); #endif m_bPendingWeaponAttach = false; return true; } } // Safety cap if (m_PendingWeapon.dwRetryCount > 200) { #ifdef ENABLE_CSHIELD_DEBUG TraceError("[PENDING] Giving up weapon attach item=%u part=%u", m_PendingWeapon.dwItemIndex, m_PendingWeapon.dwPartIndex); #endif m_bPendingWeaponAttach = false; } return true; } also search for : void CActorInstance::AttachWeapon(DWORD dwItemIndex, DWORD dwParentPartIndex, DWORD dwPartIndex) { if (dwPartIndex >= CRaceData::PART_MAX_NUM) return; m_adwPartItemID[dwPartIndex] = dwItemIndex; if (USE_VIETNAM_CONVERT_WEAPON_VNUM) dwItemIndex = Vietnam_ConvertWeaponVnum(dwItemIndex); CItemData* pItemData; if (!CItemManager::Instance().GetItemDataPointer(dwItemIndex, &pItemData)) { RegisterModelThing(dwPartIndex, NULL); SetModelInstance(dwPartIndex, dwPartIndex, 0); RegisterModelThing(CRaceData::PART_WEAPON_LEFT, NULL); SetModelInstance(CRaceData::PART_WEAPON_LEFT, CRaceData::PART_WEAPON_LEFT, 0); RefreshActorInstance(); return; } __DestroyWeaponTrace(); // 양손무기(자객 이도류) 왼손,오른손 모두에 장착. DWORD dwWeaponType = pItemData->GetWeaponType(); #ifdef ENABLE_WEAPON_COSTUME_SYSTEM if (pItemData->GetType() == CItemData::ITEM_TYPE_COSTUME) { DWORD typeDec = pItemData->GetValue(3); if (__IsRightHandWeapon(typeDec)) AttachWeapon(dwParentPartIndex, CRaceData::PART_WEAPON, pItemData); if (__IsLeftHandWeapon(typeDec)) AttachWeapon(dwParentPartIndex, CRaceData::PART_WEAPON_LEFT, pItemData); } else { if (m_eRace == CRaceData::RACE_WOLFMAN_M) { const char* szAttachingBoneName = "equip_right_weapon"; if (dwWeaponType != CItemData::WEAPON_CLAW) szAttachingBoneName = "equip_right"; m_pkCurRaceData->ChangeAttachingBoneName(CRaceData::PART_WEAPON, szAttachingBoneName); } if (__IsRightHandWeapon(dwWeaponType)) AttachWeapon(dwParentPartIndex, CRaceData::PART_WEAPON, pItemData); if (__IsLeftHandWeapon(dwWeaponType)) AttachWeapon(dwParentPartIndex, CRaceData::PART_WEAPON_LEFT, pItemData); } #else if (__IsRightHandWeapon(dwWeaponType)) AttachWeapon(dwParentPartIndex, CRaceData::PART_WEAPON, pItemData); if (__IsLeftHandWeapon(dwWeaponType)) AttachWeapon(dwParentPartIndex, CRaceData::PART_WEAPON_LEFT, pItemData); #endif //#ifdef ENABLE_INBUILD_ANIMATION // if (CGrannyLODController* pLODController = m_LODControllerVector[dwPartIndex]) // { // if (CGrannyModelInstance* pWeaponModelInstance = pLODController->GetModelInstance()) // { // CGraphicThing* pItemGraphicThing = pItemData->GetModelThing(); // if (CGrannyMotion* pItemMotion = pItemGraphicThing->GetMotionPointer(0)) // { // pWeaponModelInstance->SetMotionPointer(pItemMotion); // } // } // } //#endif } replace with : void CActorInstance::AttachWeapon(DWORD dwItemIndex, DWORD dwParentPartIndex, DWORD dwPartIndex) { if (dwPartIndex >= CRaceData::PART_MAX_NUM) return; // Track chosen item per part m_adwPartItemID[dwPartIndex] = dwItemIndex; if (USE_VIETNAM_CONVERT_WEAPON_VNUM) dwItemIndex = Vietnam_ConvertWeaponVnum(dwItemIndex); CItemData* pItemData; if (!CItemManager::Instance().GetItemDataPointer(dwItemIndex, &pItemData)) { // Clear both hands on invalid item RegisterModelThing(dwPartIndex, NULL); SetModelInstance(dwPartIndex, dwPartIndex, 0); RegisterModelThing(CRaceData::PART_WEAPON_LEFT, NULL); SetModelInstance(CRaceData::PART_WEAPON_LEFT, CRaceData::PART_WEAPON_LEFT, 0); RefreshActorInstance(); return; } // Defer when race/model context isn't ready yet if (!m_pkCurRaceData) { QueuePendingWeaponAttach(dwItemIndex, dwParentPartIndex, dwPartIndex); return; } __DestroyWeaponTrace(); // Handle both hands when necessary DWORD dwWeaponType = pItemData->GetWeaponType(); #ifdef ENABLE_WEAPON_COSTUME_SYSTEM if (pItemData->GetType() == CItemData::ITEM_TYPE_COSTUME) { DWORD typeDec = pItemData->GetValue(3); if (__IsRightHandWeapon(typeDec)) { m_adwPartItemID[CRaceData::PART_WEAPON] = dwItemIndex; AttachWeapon(dwParentPartIndex, CRaceData::PART_WEAPON, pItemData); } if (__IsLeftHandWeapon(typeDec)) { m_adwPartItemID[CRaceData::PART_WEAPON_LEFT] = dwItemIndex; AttachWeapon(dwParentPartIndex, CRaceData::PART_WEAPON_LEFT, pItemData); } } else { if (m_eRace == CRaceData::RACE_WOLFMAN_M) { const char* szAttachingBoneName = "equip_right_weapon"; if (dwWeaponType != CItemData::WEAPON_CLAW) szAttachingBoneName = "equip_right"; m_pkCurRaceData->ChangeAttachingBoneName(CRaceData::PART_WEAPON, szAttachingBoneName); } if (__IsRightHandWeapon(dwWeaponType)) { m_adwPartItemID[CRaceData::PART_WEAPON] = dwItemIndex; AttachWeapon(dwParentPartIndex, CRaceData::PART_WEAPON, pItemData); } if (__IsLeftHandWeapon(dwWeaponType)) { m_adwPartItemID[CRaceData::PART_WEAPON_LEFT] = dwItemIndex; AttachWeapon(dwParentPartIndex, CRaceData::PART_WEAPON_LEFT, pItemData); } } #else if (__IsRightHandWeapon(dwWeaponType)) { m_adwPartItemID[CRaceData::PART_WEAPON] = dwItemIndex; AttachWeapon(dwParentPartIndex, CRaceData::PART_WEAPON, pItemData); } if (__IsLeftHandWeapon(dwWeaponType)) { m_adwPartItemID[CRaceData::PART_WEAPON_LEFT] = dwItemIndex; AttachWeapon(dwParentPartIndex, CRaceData::PART_WEAPON_LEFT, pItemData); } #endif // In-build animation handling intentionally unchanged (commented) } also search for : void CActorInstance::AttachWeapon(DWORD dwParentPartIndex, DWORD dwPartIndex, CItemData* pItemData) { //assert(m_pkCurRaceData); if (!pItemData) return; const char* szBoneName; if (!GetAttachingBoneName(dwPartIndex, &szBoneName)) return; // NOTE : (이도류처리)단도일 경우 형태가 다른 것으로 얻는다. 없을 경우 디폴트를 리턴 if (CRaceData::PART_WEAPON_LEFT == dwPartIndex) { RegisterModelThing(dwPartIndex, pItemData->GetSubModelThing()); } else { RegisterModelThing(dwPartIndex, pItemData->GetModelThing()); } for (DWORD i = 0; i < pItemData->GetLODModelThingCount(); ++i) { CGraphicThing* pThing; if (!pItemData->GetLODModelThingPointer(i, &pThing)) continue; RegisterLODThing(dwPartIndex, pThing); } SetModelInstance(dwPartIndex, dwPartIndex, 0); AttachModelInstance(dwParentPartIndex, szBoneName, dwPartIndex); // 20041208.myevan.무기스펙큘러(값옷은 SetShape에서 직접 해준다.) if (USE_WEAPON_SPECULAR) { SMaterialData kMaterialData; kMaterialData.pImage = NULL; kMaterialData.isSpecularEnable = TRUE; kMaterialData.fSpecularPower = pItemData->GetSpecularPowerf(); kMaterialData.bSphereMapIndex = 1; SetMaterialData(dwPartIndex, NULL, kMaterialData); } // Weapon Trace #ifdef ENABLE_WEAPON_COSTUME_SYSTEM if (pItemData->GetType() == CItemData::ITEM_TYPE_COSTUME) { DWORD typeDec = pItemData->GetValue(3); if (__IsWeaponTrace(typeDec)) { CWeaponTrace* pWeaponTrace = CWeaponTrace::New(); pWeaponTrace->SetWeaponInstance(this, dwPartIndex, szBoneName); m_WeaponTraceVector.push_back(pWeaponTrace); } } else { if (__IsWeaponTrace(pItemData->GetWeaponType())) { CWeaponTrace* pWeaponTrace = CWeaponTrace::New(); pWeaponTrace->SetWeaponInstance(this, dwPartIndex, szBoneName); m_WeaponTraceVector.push_back(pWeaponTrace); } } #else if (__IsWeaponTrace(pItemData->GetWeaponType())) { CWeaponTrace* pWeaponTrace = CWeaponTrace::New(); pWeaponTrace->SetWeaponInstance(this, dwPartIndex, szBoneName); m_WeaponTraceVector.push_back(pWeaponTrace); } #endif } replace with : void CActorInstance::AttachWeapon(DWORD dwParentPartIndex, DWORD dwPartIndex, CItemData* pItemData) { if (!pItemData) return; // Race/model context not ready yet - defer if (!m_pkCurRaceData) { DWORD vnum = m_adwPartItemID[dwPartIndex]; if (vnum) QueuePendingWeaponAttach(vnum, dwParentPartIndex, dwPartIndex); return; } const char* szBoneName; if (!GetAttachingBoneName(dwPartIndex, &szBoneName) || !szBoneName) { DWORD vnum = m_adwPartItemID[dwPartIndex]; if (vnum) QueuePendingWeaponAttach(vnum, dwParentPartIndex, dwPartIndex); return; } // Choose model thing for the requested part, ensure it's valid CGraphicThing* pModelThing = (CRaceData::PART_WEAPON_LEFT == dwPartIndex) ? pItemData->GetSubModelThing() : pItemData->GetModelThing(); if (!pModelThing) { DWORD vnum = m_adwPartItemID[dwPartIndex]; if (vnum) QueuePendingWeaponAttach(vnum, dwParentPartIndex, dwPartIndex); return; } RegisterModelThing(dwPartIndex, pModelThing); for (DWORD i = 0; i < pItemData->GetLODModelThingCount(); ++i) { CGraphicThing* pThing; if (!pItemData->GetLODModelThingPointer(i, &pThing)) continue; RegisterLODThing(dwPartIndex, pThing); } // If SetModelInstance fails (resource not resolved yet), retry later if (!SetModelInstance(dwPartIndex, dwPartIndex, 0)) { DWORD vnum = m_adwPartItemID[dwPartIndex]; if (vnum) QueuePendingWeaponAttach(vnum, dwParentPartIndex, dwPartIndex); return; } AttachModelInstance(dwParentPartIndex, szBoneName, dwPartIndex); // Weapon specular (costumes handled in SetShape) if (USE_WEAPON_SPECULAR) { SMaterialData kMaterialData; kMaterialData.pImage = NULL; kMaterialData.isSpecularEnable = TRUE; kMaterialData.fSpecularPower = pItemData->GetSpecularPowerf(); kMaterialData.bSphereMapIndex = 1; SetMaterialData(dwPartIndex, NULL, kMaterialData); } // Weapon Trace #ifdef ENABLE_WEAPON_COSTUME_SYSTEM if (pItemData->GetType() == CItemData::ITEM_TYPE_COSTUME) { DWORD typeDec = pItemData->GetValue(3); if (__IsWeaponTrace(typeDec)) { CWeaponTrace* pWeaponTrace = CWeaponTrace::New(); pWeaponTrace->SetWeaponInstance(this, dwPartIndex, szBoneName); m_WeaponTraceVector.push_back(pWeaponTrace); } } else { if (__IsWeaponTrace(pItemData->GetWeaponType())) { CWeaponTrace* pWeaponTrace = CWeaponTrace::New(); pWeaponTrace->SetWeaponInstance(this, dwPartIndex, szBoneName); m_WeaponTraceVector.push_back(pWeaponTrace); } } #else if (__IsWeaponTrace(pItemData->GetWeaponType())) { CWeaponTrace* pWeaponTrace = CWeaponTrace::New(); pWeaponTrace->SetWeaponInstance(this, dwPartIndex, szBoneName); m_WeaponTraceVector.push_back(pWeaponTrace); } #endif } Process Deferred Attach Each Frame Source-client/UserInterface/InstanceBase.cpp search for : void CInstanceBase::Update() { ++ms_dwUpdateCounter; StateProcess(); replace with : void CInstanceBase::Update() { ++ms_dwUpdateCounter; // Attempt any deferred weapon attachment (model/bones may not be ready right after spawn/warp) m_GraphicThingInstance.ProcessPendingWeaponAttach(); StateProcess(); And last Source-client\GameLib\ActorInstance.cpp : in void CActorInstance::__Initialize() search for or add in the last of the void : #ifdef ENABLE_SKILL_COLOR_SYSTEM memset(m_dwSkillColor, 0, sizeof(m_dwSkillColor)); #endif #if defined(ENABLE_NPC_WEAR_ITEM) m_dwRealRaceIndex = 0; #endif replace with : #ifdef ENABLE_SKILL_COLOR_SYSTEM memset(m_dwSkillColor, 0, sizeof(m_dwSkillColor)); #endif #if defined(ENABLE_NPC_WEAR_ITEM) m_dwRealRaceIndex = 0; #endif // Initialize deferred weapon attachment state m_bPendingWeaponAttach = false; m_PendingWeapon = SPendingWeapon(); } Effect After Fix No more random warp crash. No more null bone reference. Weapon attaches reliably as soon as model data is ready. Tested over 100+ warp transitions under stress. Zero crashes. Credits Special thanks to: Doofy (CShield) for support and verification Everyone who helped reproduce the issue reliably. FAQ Q: Is this caused by CShield? A: No. CShield just makes the race condition timing more visible. The underlying bug is in the client attach pipeline itself. Q: Should every client apply this fix? A: Yes. If your client equips weapons and warps, this affects you. If anyone wants I can also provide: A build with debug traces to test in your environment Just reply here. Done. This finally eliminates one of the oldest silent crash sources in the client. Hope it helps someone else. Enjoy.
-
- 6
-
-
-
saudidos changed their profile photo
-
Hey bro, Your problem is simple. The skill 107 (BYEURAK) is missing in your CFuncShoot switch-case. That's why you see this syserr: CFuncShoot: I don't know this type [107] of range attack. Just go to your CFuncShoot function, and add this: case SKILL_BYEURAK: { m_me->OnMove(true); pkVictim->OnMove(); if (pkVictim->CanBeginFight()) pkVictim->BeginFight(m_me); sys_log(0, "%s - Skill %d -> %s", m_me->GetName(), m_bType, pkVictim->GetName()); m_me->ComputeSkill(m_bType, pkVictim); } break; Rebuild, and your problem is gone Also, double-check your skill_proto — if your skill type is wrong (should not be "RANGE"), it can cause this too. Good luck!
-
Bind an effect to a key?
saudidos replied to josehdelaro's topic in Community Support - Questions & Answers
Can you provide the game.py file? It will be easier to help you. -
[Bug on all servers] How to fix this?
saudidos replied to HFWhite's topic in Community Support - Questions & Answers
helle bro ,, i test this one it's working fine -
[Bug on all servers] How to fix this?
saudidos replied to HFWhite's topic in Community Support - Questions & Answers
in char_item.cpp in this faction : bool CHARACTER::MoveItem you need to add under this if (!IsValidItemPosition(DestCell)) this code : if (!IsEmptyItemGrid(DestCell, item->GetSize(), -1)) { ChatPacket(CHAT_TYPE_INFO, LC_TEXT("Target slot is occupied!")); return false; } thanks man -
[Bug on all servers] How to fix this?
saudidos replied to HFWhite's topic in Community Support - Questions & Answers
SYSERR: Jul 25 09:24:43 :: CreateItem: ITEM_ID_DUP: 327817689 نصل العناصر الخمس+9 owner 0x52caa700 SYSERR: Jul 25 09:24:43 :: ItemLoad: cannot create item by vnum 1349 (name [DEV]AL38lAlmdbeR id 327817689) SYSERR: Jul 25 09:26:06 :: CreateItem: ITEM_ID_DUP: 327817689 نصل العناصر الخمس+9 owner 0x52caa700 SYSERR: Jul 25 09:26:06 :: ItemLoad: cannot create item by vnum 1349 (name [DEV]AL38lAlmdbeR id 327817689) your right this is the syserr also -
[Bug on all servers] How to fix this?
saudidos replied to HFWhite's topic in Community Support - Questions & Answers
i test it on my server it's owsap source code this bug is work -
Hello everyone, This is my first system release here on this forum, aimed at supporting server owners. This system helps you track hackers or any suspicious activity that a GM should know about.The system allows you to send direct messages (DM) to any GM online. For example, if someone attempts a hack or fails to solve a captcha, it sends a DM to the GM to alert them. The GM can then move to the player to investigate. Here is the system I use the system in CShield protection and you can : This setup should help GMs be instantly notified about any suspicious activities or issues, allowing them to take appropriate action swiftly.
-
- 5
-
-
[Bug on all servers] How to fix this?
saudidos replied to HFWhite's topic in Community Support - Questions & Answers
i had a similar issue before with the Change Look System. The problem was that any player could change the look of a WEAPON_TWO_HANDED sword to look like a one-handed sword (WEAPON_SWORD). I fixed it using the GetSize() method because, according to the item_proto.txt, the WEAPON_TWO_HANDED has a size of 3. I wrote code to check the size and ensure it matches before allowing the change. if (bPos == 1) { bool bStop = false; if ((pkItem->GetSize() == 3 && pkItemMaterial[0]->GetSize() == 3) || (pkItem->GetSize() == 2 && pkItemMaterial[0]->GetSize() == 2) || (pkItem->GetSize() == 1 && pkItemMaterial[0]->GetSize() == 1)) { sys_err("Both items have size %d, allowing transmutation.", pkItem->GetSize()); } else { if (pkItem->GetType() != pkItemMaterial[0]->GetType()) { sys_err("Type mismatch: %d vs %d", pkItem->GetType(), pkItemMaterial[0]->GetType()); bStop = true; } else if (pkItem->GetSubType() != pkItemMaterial[0]->GetSubType()) { sys_err("SubType mismatch: %d vs %d", pkItem->GetSubType(), pkItemMaterial[0]->GetSubType()); bStop = true; } else if (pkItem->GetSize() != pkItemMaterial[0]->GetSize()) { sys_err("Size mismatch: %d vs %d", pkItem->GetSize(), pkItemMaterial[0]->GetSize()); bStop = true; } else if (pkItem->IsCostumeBody() && pkItemMaterial[0]->IsArmorBody()) bStop = false; else if (pkItem->IsCostumeWeapon() && pkItemMaterial[0]->IsMainWeapon()) bStop = false; else if (pkItem->IsArmorBody() && pkItemMaterial[0]->IsCostumeBody()) bStop = false; else if (pkItem->IsMainWeapon() && pkItemMaterial[0]->IsCostumeWeapon()) bStop = false; else if (pkItem->IsCostumeHair() && !pkItemMaterial[0]->IsCostumeHair()) bStop = false; else bStop = true; } if (bStop) { sys_err("Type/subtype/size mismatch for transmutation: %d (size %d) vs %d (size %d)", pkItem->GetVnum(), pkItem->GetSize(), pkItemMaterial[0]->GetVnum(), pkItemMaterial[0]->GetSize()); return; } if (pkItemMaterial[0]->GetOriginalVnum() == pkItem->GetOriginalVnum()) bStop = true; else if (((IS_SET(pkItemMaterial[0]->GetAntiFlag(), ITEM_ANTIFLAG_FEMALE)) && (!IS_SET(pkItem->GetAntiFlag(), ITEM_ANTIFLAG_FEMALE))) || ((IS_SET(pkItemMaterial[0]->GetAntiFlag(), ITEM_ANTIFLAG_MALE)) && (!IS_SET(pkItem->GetAntiFlag(), ITEM_ANTIFLAG_MALE)))) bStop = true; else if ((pkItem->GetAntiFlag() & ITEM_ANTIFLAG_WARRIOR) && (!IS_SET(pkItemMaterial[0]->GetAntiFlag(), ITEM_ANTIFLAG_WARRIOR))) bStop = true; else if ((pkItem->GetAntiFlag() & ITEM_ANTIFLAG_ASSASSIN) && (!IS_SET(pkItemMaterial[0]->GetAntiFlag(), ITEM_ANTIFLAG_ASSASSIN))) bStop = true; else if ((pkItem->GetAntiFlag() & ITEM_ANTIFLAG_SHAMAN) && (!IS_SET(pkItemMaterial[0]->GetAntiFlag(), ITEM_ANTIFLAG_SHAMAN))) bStop = true; else if ((pkItem->GetAntiFlag() & ITEM_ANTIFLAG_SURA) && (!IS_SET(pkItemMaterial[0]->GetAntiFlag(), ITEM_ANTIFLAG_SURA))) bStop = true; else if ((pkItem->GetAntiFlag() & ITEM_ANTIFLAG_WOLFMAN) && (!IS_SET(pkItemMaterial[0]->GetAntiFlag(), ITEM_ANTIFLAG_WOLFMAN))) bStop = true; else if (IS_SET(pkItemMaterial[0]->GetAntiFlag(), ITEM_ANTIFLAG_CHANGELOOK) || IS_SET(pkItem->GetAntiFlag(), ITEM_ANTIFLAG_CHANGELOOK)) bStop = true; else if ((pkItem->IsCostume() && pkItemMaterial[0]->IsCostume()) && ((pkItem->IsCostumeWeapon()) && (pkItemMaterial[0]->IsCostumeWeapon()))) if (pkItem->GetValue(3) != pkItemMaterial[0]->GetValue(3)) bStop = true; if (bStop) return; } you can use the same logic in the code to prevent items from overlapping. -
[Bug on all servers] How to fix this?
saudidos replied to HFWhite's topic in Community Support - Questions & Answers
To fix the issue where a 1-cell item can overlap a 2 or 3-cell item, you need to add a check for the destination cells before moving the item. This will ensure that the destination cells are free and not occupied by another item. Here's an updated version of the MoveItem function with the necessary checks: this is my code u can have a look : bool CHARACTER::MoveItem(TItemPos Cell, TItemPos DestCell, WORD count) { LPITEM item = NULL; // NOTE : Prevent the item from being moved to the original slot. if (Cell.cell == DestCell.cell) return false; if (!IsValidItemPosition(Cell)) return false; if (!(item = GetItem(Cell))) return false; if (item->IsExchanging()) return false; if (item->GetCount() < count) return false; if (INVENTORY == Cell.window_type && Cell.cell >= INVENTORY_MAX_NUM && IS_SET(item->GetFlag(), ITEM_FLAG_IRREMOVABLE)) return false; if (true == item->isLocked()) return false; if (!IsValidItemPosition(DestCell)) return false; #ifdef __GROWTH_PET_SYSTEM__ if (GetPetWindowType() == PET_WINDOW_ATTR_CHANGE || GetPetWindowType() == PET_WINDOW_PRIMIUM_FEEDSTUFF) { ChatPacket(CHAT_TYPE_INFO, "You cannot move items while modifying your pet's stats."); return false; } #endif if (!CanHandleItem()) { if (NULL != DragonSoul_RefineWindow_GetOpener()) ChatPacket(CHAT_TYPE_INFO, LC_TEXT("강화창을 연 상태에서는 아이템을 옮길 수 없습니다.")); #ifdef ENABLE_AURA_SYSTEM if (IsAuraRefineWindowOpen()) ChatPacket(CHAT_TYPE_INFO, LC_TEXT("<아우라> 오라 창이 열려있을 때까지 항목을 이동할 수 없습니다.")); #endif return false; } // 기획자의 요청으로 벨트 인벤토리에는 특정 타입의 아이템만 넣을 수 있다. if (DestCell.window_type != DRAGON_SOUL_INVENTORY && DestCell.IsBeltInventoryPosition() && !CBeltInventoryHelper::CanMoveIntoBeltInventory(item)) { ChatPacket(CHAT_TYPE_INFO, LC_TEXT("이 아이템은 벨트 인벤토리로 옮길 수 없습니다.")); return false; } #ifdef ENABLE_SWITCHBOT if (Cell.IsSwitchbotPosition() && CSwitchbotManager::Instance().IsActive(GetPlayerID(), Cell.cell)) { ChatPacket(CHAT_TYPE_INFO, LC_TEXT("Cannot move active switchbot item.")); return false; } if (Cell.IsSwitchbotPosition() && DestCell.IsSkillBookInventoryPosition()) { if (!item->IsSkillBook()) { ChatPacket(CHAT_TYPE_INFO, LC_TEXT("You can only move skill books into this inventory.")); return false; } } if (Cell.IsSwitchbotPosition() && DestCell.IsUpgradeItemsInventoryPosition()) { if (!item->IsUpgradeItem()) { ChatPacket(CHAT_TYPE_INFO, LC_TEXT("You can only move upgrade items into this inventory.")); return false; } } if (Cell.IsSwitchbotPosition() && DestCell.IsStoneInventoryPosition()) { if (!item->IsStone()) { ChatPacket(CHAT_TYPE_INFO, LC_TEXT("You can only move stones into this inventory.")); return false; } } if (Cell.IsSwitchbotPosition() && DestCell.IsGiftBoxInventoryPosition()) { if (!item->IsGiftBox()) { ChatPacket(CHAT_TYPE_INFO, LC_TEXT("You can only move chests into this inventory.")); return false; } } if (DestCell.IsSwitchbotPosition() && !SwitchbotHelper::IsValidItem(item)) { ChatPacket(CHAT_TYPE_INFO, LC_TEXT("Invalid item type for switchbot.")); return false; } if (Cell.IsSwitchbotPosition() && DestCell.IsEquipPosition()) { ChatPacket(CHAT_TYPE_INFO, LC_TEXT("Cannot equip items directly from switchbot.")); return false; } if (DestCell.IsSwitchbotPosition() && Cell.IsEquipPosition()) { ChatPacket(CHAT_TYPE_INFO, LC_TEXT("Cannot move equipped items to switchbot.")); return false; } #endif #if defined(__SPECIAL_INVENTORY_SYSTEM__) if (DestCell.IsSkillBookInventoryPosition() && (item->IsEquipped() || Cell.IsBeltInventoryPosition())) { ChatPacket(CHAT_TYPE_INFO, LC_TEXT("You can't move this item into this inventory.")); return false; } if (DestCell.IsUpgradeItemsInventoryPosition() && (item->IsEquipped() || Cell.IsBeltInventoryPosition())) { ChatPacket(CHAT_TYPE_INFO, LC_TEXT("You can't move this item into this inventory.")); return false; } if (DestCell.IsStoneInventoryPosition() && (item->IsEquipped() || Cell.IsBeltInventoryPosition())) { ChatPacket(CHAT_TYPE_INFO, LC_TEXT("You can't move this item into this inventory.")); return false; } if (DestCell.IsGiftBoxInventoryPosition() && (item->IsEquipped() || Cell.IsBeltInventoryPosition())) { ChatPacket(CHAT_TYPE_INFO, LC_TEXT("You can't move this item into this inventory.")); return false; } if ((Cell.IsSkillBookInventoryPosition() && !DestCell.IsSkillBookInventoryPosition() && !DestCell.IsDefaultInventoryPosition())) { ChatPacket(CHAT_TYPE_INFO, LC_TEXT("You can't move this item into this inventory.")); return false; } if (Cell.IsUpgradeItemsInventoryPosition() && !DestCell.IsUpgradeItemsInventoryPosition() && !DestCell.IsDefaultInventoryPosition()) { ChatPacket(CHAT_TYPE_INFO, LC_TEXT("You can't move this item into this inventory.")); return false; } if (Cell.IsStoneInventoryPosition() && !DestCell.IsStoneInventoryPosition() && !DestCell.IsDefaultInventoryPosition()) { ChatPacket(CHAT_TYPE_INFO, LC_TEXT("You can't move this item into this inventory.")); return false; } if (Cell.IsGiftBoxInventoryPosition() && !DestCell.IsGiftBoxInventoryPosition() && !DestCell.IsDefaultInventoryPosition()) { ChatPacket(CHAT_TYPE_INFO, LC_TEXT("You can't move this item into this inventory.")); return false; } if (Cell.IsDefaultInventoryPosition() && DestCell.IsSkillBookInventoryPosition()) { if (!item->IsSkillBook()) { ChatPacket(CHAT_TYPE_INFO, LC_TEXT("You can only move skill books into this inventory.")); return false; } } if (Cell.IsDefaultInventoryPosition() && DestCell.IsUpgradeItemsInventoryPosition()) { if (!item->IsUpgradeItem()) { ChatPacket(CHAT_TYPE_INFO, LC_TEXT("You can only move upgrade items into this inventory.")); return false; } } if (Cell.IsDefaultInventoryPosition() && DestCell.IsStoneInventoryPosition()) { if (!item->IsStone()) { ChatPacket(CHAT_TYPE_INFO, LC_TEXT("You can only move stones into this inventory.")); return false; } } if (Cell.IsDefaultInventoryPosition() && DestCell.IsGiftBoxInventoryPosition()) { if (!item->IsGiftBox()) { ChatPacket(CHAT_TYPE_INFO, LC_TEXT("You can only move chests into this inventory.")); return false; } } #endif // Check for overlapping items in destination slots int itemSize = item->GetSize(); for (int i = 0; i < itemSize; ++i) { if (!IsEmptyItemGrid(DestCell + i, 1, Cell.cell)) { ChatPacket(CHAT_TYPE_INFO, LC_TEXT("Target slot is occupied!")); return false; } } // 이미 착용중인 아이템을 다른 곳으로 옮기는 경우, '장책 해제' 가능한 지 확인하고 옮김 if (Cell.IsEquipPosition()) { if (!CanUnequipNow(item)) return false; #if defined(__WEAPON_COSTUME_SYSTEM__) int iWearCell = item->FindEquipCell(this); if (iWearCell == WEAR_WEAPON) { LPITEM pkCostumeWeapon = GetWear(WEAR_COSTUME_WEAPON); if (pkCostumeWeapon) { ChatPacket(CHAT_TYPE_INFO, LC_TEXT("If you want to change weapons, you must remove the weapon skin first.")); return false; } } #endif } if (item->IsBelt() && item->IsEquipped() && CBeltInventoryHelper::IsExistItemInBeltInventory(this)) { ChatPacket(CHAT_TYPE_INFO, LC_TEXT("You can only discard the belt when there are no longer any items in its inventory.")); return false; } if (DestCell.IsEquipPosition()) { if (GetItem(DestCell)) // 장비일 경우 한 곳만 검사해도 된다. { ChatPacket(CHAT_TYPE_INFO, LC_TEXT("이미 장비를 착용하고 있습니다.")); return false; } EquipItem(item, DestCell.cell - INVENTORY_MAX_NUM); } else { if (item->IsDragonSoul()) { if (item->IsEquipped()) { return DSManager::instance().PullOut(this, DestCell, item); } else { if (DestCell.window_type != DRAGON_SOUL_INVENTORY) { return false; } if (!DSManager::instance().IsValidCellForThisItem(item, DestCell)) return false; } } // 용혼석이 아닌 아이템은 용혼석 인벤에 들어갈 수 없다. else if (DRAGON_SOUL_INVENTORY == DestCell.window_type) return false; LPITEM item2; if ((item2 = GetItem(DestCell)) && item != item2 && item2->IsStackable() && !IS_SET(item2->GetAntiFlag(), ITEM_ANTIFLAG_STACK) && item2->GetVnum() == item->GetVnum() && !item2->IsExchanging()) // 합칠 수 있는 아이템의 경우 { #if defined(__EXTENDED_BLEND_AFFECT__) if (item->IsBlendItem() && item2->IsBlendItem()) { if (count == 0) count = item->GetCount(); for (int i = 0; i < 2; ++i) { if (item2->GetSocket(i) != item->GetSocket(i)) { return false; } } #if defined(ENABLE_COMMON_CHANGES) item2->SetSocket(2, item2->GetSocket(2) + (item->GetSocket(2) * count)); #else item2->SetSocket(2, item2->GetSocket(2) + item->GetSocket(2)); #endif item->SetCount(item->GetCount() - count); return true; } #endif for (int i = 0; i < ITEM_SOCKET_MAX_NUM; ++i) if (item2->GetSocket(i) != item->GetSocket(i)) return false; if (count == 0) count = item->GetCount(); sys_log(0, "%s: ITEM_STACK %s (window: %d, cell : %d) -> (window:%d, cell %d) count %d", GetName(), item->GetName(), Cell.window_type, Cell.cell, DestCell.window_type, DestCell.cell, count); count = MIN(ITEM_MAX_COUNT - item2->GetCount(), count); item->SetCount(item->GetCount() - count); item2->SetCount(item2->GetCount() + count); return true; } if (!IsEmptyItemGrid(DestCell, item->GetSize(), Cell.cell)) { #if defined(__SWAP_ITEM_SYSTEM__) if (count != 0 && count != item->GetCount()) return false; if (!DestCell.IsDefaultInventoryPosition() || !Cell.IsDefaultInventoryPosition()) return false; LPITEM targetItem = GetItem_NEW(DestCell); if (targetItem && targetItem->GetVID() == item->GetVID()) return false; if (targetItem) { DestCell = TItemPos(INVENTORY, targetItem->GetCell()); } if (item->IsExchanging() || (targetItem && targetItem->IsExchanging())) return false; BYTE basePage = DestCell.cell / (INVENTORY_MAX_NUM / INVENTORY_PAGE_COUNT); std::map<WORD, LPITEM> moveItemMap; BYTE sizeLeft = item->GetSize(); for (WORD i = 0; i < item->GetSize(); ++i) { WORD cellNumber = DestCell.cell + i * 5; BYTE cPage = cellNumber / (INVENTORY_MAX_NUM / INVENTORY_PAGE_COUNT); if (basePage != cPage) return false; LPITEM mvItem = GetItem(TItemPos(INVENTORY, cellNumber)); if (mvItem) { if (mvItem->GetSize() > item->GetSize()) return false; if (mvItem->IsExchanging()) return false; moveItemMap.insert({ Cell.cell + i * 5, mvItem }); sizeLeft -= mvItem->GetSize(); if (mvItem->GetSize() > 1) i += mvItem->GetSize() - 1; } else { sizeLeft -= 1; } } if (sizeLeft != 0) return false; std::map<WORD, WORD> syncCells; syncCells.insert({ GetQuickslotPosition(QUICKSLOT_TYPE_ITEM, item->GetCell()), DestCell.cell }); item->RemoveFromCharacter(); for (auto it = moveItemMap.begin(); it != moveItemMap.end(); ++it) { WORD toCellNumber = it->first; LPITEM mvItem = it->second; syncCells.insert({ GetQuickslotPosition(QUICKSLOT_TYPE_ITEM, mvItem->GetCell()), toCellNumber }); mvItem->RemoveFromCharacter(); SetItem(TItemPos(INVENTORY, toCellNumber), mvItem); } SetItem(DestCell, item); for (auto& sCell : syncCells) { TQuickslot qs; qs.type = QUICKSLOT_TYPE_ITEM; qs.pos = sCell.second; SetQuickslot(sCell.first, qs); } return true; #else return false; #endif } if (count == 0 || count >= item->GetCount() || !item->IsStackable() || IS_SET(item->GetAntiFlag(), ITEM_ANTIFLAG_STACK)) { sys_log(0, "%s: ITEM_MOVE %s (window: %d, cell : %d) -> (window:%d, cell %d) count %d", GetName(), item->GetName(), Cell.window_type, Cell.cell, DestCell.window_type, DestCell.cell, count); item->RemoveFromCharacter(); SetItem(DestCell, item); if (INVENTORY == Cell.window_type && INVENTORY == DestCell.window_type) SyncQuickslot(QUICKSLOT_TYPE_ITEM, Cell.cell, DestCell.cell); } else if (count < item->GetCount()) { #ifdef ENABLE_PULSE_MANAGER if (!PulseManager::Instance().IncreaseClock(GetPlayerID(), ePulse::MoveItem, std::chrono::milliseconds(1000))) { ChatPacket(CHAT_TYPE_INFO, "2"); return false; } #endif sys_log(0, "%s: ITEM_SPLIT %s (window: %d, cell : %d) -> (window:%d, cell %d) count %d", GetName(), item->GetName(), Cell.window_type, Cell.cell, DestCell.window_type, DestCell.cell, count); item->SetCount(item->GetCount() - count); LPITEM item2 = ITEM_MANAGER::instance().CreateItem(item->GetVnum(), count); // Copy socket -- by mhh FN_copy_item_socket(item2, item); item2->AddToCharacter(this, DestCell); char szBuf[51 + 1]; snprintf(szBuf, sizeof(szBuf), "%u %u %u %u ", item2->GetID(), item2->GetCount(), item->GetCount(), item->GetCount() + item2->GetCount()); LogManager::instance().ItemLog(this, item, "ITEM_SPLIT", szBuf); } } return true; } -
Thank you for your input! You're right; incrementing WEAR_MAX_NUM does shift the positions of Dragon Soul equipment cells, which is why we encountered the issue. I understand that extending INVENTORY instead of creating new window types can lead to similar problems with multi-inventories. Your suggestion of creating a WEAR_BASE_MAX_NUM and then adding new WEAR_* types for cells after the Dragon Soul equipment is a valid approach. It would certainly help maintain the current positions of existing items and avoid shifting issues. However, as you mentioned, this requires adjusting all functions that check equipment and adapting them accordingly, which can be quite a bit of work, especially on a live server. In our case, we opted for the SQL fix to promptly address the issue without requiring significant changes to the codebase. This approach allowed us to ensure that the Dragon Soul items were returned to their correct positions in the Dragon Soul Inventory with minimal disruption to our live server. Thanks again for your suggestion. It’s always good to have multiple approaches to solving such issues.
-
Losing items after reboot
saudidos replied to Diogo Neto's topic in Community Support - Questions & Answers
[Hidden Content] see this post and don't use reboot for your server in-game use commend line /shutdown -
Hello, Today, I am sharing a fix for the slot issue of Dragon Soul (DS) items and other slots in the inventory. The issue occurred when I added a system that required extending the WEAR_MAX_NUM by +1. I noticed that most of the slots were different, especially the Dragon Soul equip items. As shown in the photo below, the Dragon Soul items appeared in the inventory despite being active, indicating that their positions had changed in the database. To fix this issue, you need to move all Dragon Soul items back to the Dragon Soul Inventory, as shown in the following photo. Since my server is live and has online users, I asked them to make some space in the Dragon Soul Inventory. It is crucial to ensure that players do not lose their Dragon Soul items. After applying the following SQL command, I observed that the items returned to the Dragon Soul Inventory: Here is the SQL code used to fix the issue: UPDATE `item` SET `pos` = 0 WHERE `vnum` BETWEEN 110000 AND 165460; SET @cur_owner_id = 0; SET @cur_pos = 0; -- For vnum range 110000 to 110460 SET @cur_pos = 0; UPDATE `item` i JOIN ( SELECT id, owner_id, @cur_pos := IF(@cur_owner_id = owner_id, @cur_pos + 1, 0) AS new_pos, @cur_owner_id := owner_id FROM `item` WHERE `vnum` BETWEEN 110000 AND 110460 ORDER BY owner_id, vnum, id ) sub ON i.id = sub.id SET i.`pos` = sub.new_pos, i.`window` = 'DRAGON_SOUL_INVENTORY' WHERE i.`vnum` BETWEEN 110000 AND 110460; -- For vnum range 111000 to 111460 SET @cur_pos = 32; UPDATE `item` i JOIN ( SELECT id, owner_id, @cur_pos := IF(@cur_owner_id = owner_id, @cur_pos + 1, 32) AS new_pos, @cur_owner_id := owner_id FROM `item` WHERE `vnum` BETWEEN 111000 AND 111460 ORDER BY owner_id, vnum, id ) sub ON i.id = sub.id SET i.`pos` = sub.new_pos, i.`window` = 'DRAGON_SOUL_INVENTORY' WHERE i.`vnum` BETWEEN 111000 AND 111460; -- For vnum range 112000 to 112460 SET @cur_pos = 64; UPDATE `item` i JOIN ( SELECT id, owner_id, @cur_pos := IF(@cur_owner_id = owner_id, @cur_pos + 1, 64) AS new_pos, @cur_owner_id := owner_id FROM `item` WHERE `vnum` BETWEEN 112000 AND 112460 ORDER BY owner_id, vnum, id ) sub ON i.id = sub.id SET i.`pos` = sub.new_pos, i.`window` = 'DRAGON_SOUL_INVENTORY' WHERE i.`vnum` BETWEEN 112000 AND 112460; -- For vnum range 113000 to 113460 SET @cur_pos = 96; UPDATE `item` i JOIN ( SELECT id, owner_id, @cur_pos := IF(@cur_owner_id = owner_id, @cur_pos + 1, 96) AS new_pos, @cur_owner_id := owner_id FROM `item` WHERE `vnum` BETWEEN 113000 AND 113460 ORDER BY owner_id, vnum, id ) sub ON i.id = sub.id SET i.`pos` = sub.new_pos, i.`window` = 'DRAGON_SOUL_INVENTORY' WHERE i.`vnum` BETWEEN 113000 AND 113460; -- For vnum range 114000 to 114460 SET @cur_pos = 128; UPDATE `item` i JOIN ( SELECT id, owner_id, @cur_pos := IF(@cur_owner_id = owner_id, @cur_pos + 1, 128) AS new_pos, @cur_owner_id := owner_id FROM `item` WHERE `vnum` BETWEEN 114000 AND 114460 ORDER BY owner_id, vnum, id ) sub ON i.id = sub.id SET i.`pos` = sub.new_pos, i.`window` = 'DRAGON_SOUL_INVENTORY' WHERE i.`vnum` BETWEEN 114000 AND 114460; -- For vnum range 115000 to 115460 SET @cur_pos = 160; UPDATE `item` i JOIN ( SELECT id, owner_id, @cur_pos := IF(@cur_owner_id = owner_id, @cur_pos + 1, 160) AS new_pos, @cur_owner_id := owner_id FROM `item` WHERE `vnum` BETWEEN 115000 AND 115460 ORDER BY owner_id, vnum, id ) sub ON i.id = sub.id SET i.`pos` = sub.new_pos, i.`window` = 'DRAGON_SOUL_INVENTORY' WHERE i.`vnum` BETWEEN 115000 AND 115460; -- For vnum range 120000 to 120460 SET @cur_pos = 192; UPDATE `item` i JOIN ( SELECT id, owner_id, @cur_pos := IF(@cur_owner_id = owner_id, @cur_pos + 1, 192) AS new_pos, @cur_owner_id := owner_id FROM `item` WHERE `vnum` BETWEEN 120000 AND 120460 ORDER BY owner_id, vnum, id ) sub ON i.id = sub.id SET i.`pos` = sub.new_pos, i.`window` = 'DRAGON_SOUL_INVENTORY' WHERE i.`vnum` BETWEEN 120000 AND 120460; -- For vnum range 121000 to 121460 SET @cur_pos = 224; UPDATE `item` i JOIN ( SELECT id, owner_id, @cur_pos := IF(@cur_owner_id = owner_id, @cur_pos + 1, 224) AS new_pos, @cur_owner_id := owner_id FROM `item` WHERE `vnum` BETWEEN 121000 AND 121460 ORDER BY owner_id, vnum, id ) sub ON i.id = sub.id SET i.`pos` = sub.new_pos, i.`window` = 'DRAGON_SOUL_INVENTORY' WHERE i.`vnum` BETWEEN 121000 AND 121460; -- For vnum range 122000 to 122460 SET @cur_pos = 256; UPDATE `item` i JOIN ( SELECT id, owner_id, @cur_pos := IF(@cur_owner_id = owner_id, @cur_pos + 1, 256) AS new_pos, @cur_owner_id := owner_id FROM `item` WHERE `vnum` BETWEEN 122000 AND 122460 ORDER BY owner_id, vnum, id ) sub ON i.id = sub.id SET i.`pos` = sub.new_pos, i.`window` = 'DRAGON_SOUL_INVENTORY' WHERE i.`vnum` BETWEEN 122000 AND 122460; -- For vnum range 123000 to 123460 SET @cur_pos = 288; UPDATE `item` i JOIN ( SELECT id, owner_id, @cur_pos := IF(@cur_owner_id = owner_id, @cur_pos + 1, 288) AS new_pos, @cur_owner_id := owner_id FROM `item` WHERE `vnum` BETWEEN 123000 AND 123460 ORDER BY owner_id, vnum, id ) sub ON i.id = sub.id SET i.`pos` = sub.new_pos, i.`window` = 'DRAGON_SOUL_INVENTORY' WHERE i.`vnum` BETWEEN 123000 AND 123460; -- For vnum range 124000 to 124460 SET @cur_pos = 320; UPDATE `item` i JOIN ( SELECT id, owner_id, @cur_pos := IF(@cur_owner_id = owner_id, @cur_pos + 1, 320) AS new_pos, @cur_owner_id := owner_id FROM `item` WHERE `vnum` BETWEEN 124000 AND 124460 ORDER BY owner_id, vnum, id ) sub ON i.id = sub.id SET i.`pos` = sub.new_pos, i.`window` = 'DRAGON_SOUL_INVENTORY' WHERE i.`vnum` BETWEEN 124000 AND 124460; -- For vnum range 125000 to 125460 SET @cur_pos = 352; UPDATE `item` i JOIN ( SELECT id, owner_id, @cur_pos := IF(@cur_owner_id = owner_id, @cur_pos + 1, 352) AS new_pos, @cur_owner_id := owner_id FROM `item` WHERE `vnum` BETWEEN 125000 AND 125460 ORDER BY owner_id, vnum, id ) sub ON i.id = sub.id SET i.`pos` = sub.new_pos, i.`window` = 'DRAGON_SOUL_INVENTORY' WHERE i.`vnum` BETWEEN 125000 AND 125460; -- For vnum range 130000 to 130460 SET @cur_pos = 384; UPDATE `item` i JOIN ( SELECT id, owner_id, @cur_pos := IF(@cur_owner_id = owner_id, @cur_pos + 1, 384) AS new_pos, @cur_owner_id := owner_id FROM `item` WHERE `vnum` BETWEEN 130000 AND 130460 ORDER BY owner_id, vnum, id ) sub ON i.id = sub.id SET i.`pos` = sub.new_pos, i.`window` = 'DRAGON_SOUL_INVENTORY' WHERE i.`vnum` BETWEEN 130000 AND 130460; -- For vnum range 131000 to 131460 SET @cur_pos = 416; UPDATE `item` i JOIN ( SELECT id, owner_id, @cur_pos := IF(@cur_owner_id = owner_id, @cur_pos + 1, 416) AS new_pos, @cur_owner_id := owner_id FROM `item` WHERE `vnum` BETWEEN 131000 AND 131460 ORDER BY owner_id, vnum, id ) sub ON i.id = sub.id SET i.`pos` = sub.new_pos, i.`window` = 'DRAGON_SOUL_INVENTORY' WHERE i.`vnum` BETWEEN 131000 AND 131460; -- For vnum range 132000 to 132460 SET @cur_pos = 448; UPDATE `item` i JOIN ( SELECT id, owner_id, @cur_pos := IF(@cur_owner_id = owner_id, @cur_pos + 1, 448) AS new_pos, @cur_owner_id := owner_id FROM `item` WHERE `vnum` BETWEEN 132000 AND 132460 ORDER BY owner_id, vnum, id ) sub ON i.id = sub.id SET i.`pos` = sub.new_pos, i.`window` = 'DRAGON_SOUL_INVENTORY' WHERE i.`vnum` BETWEEN 132000 AND 132460; -- For vnum range 133000 to 133460 SET @cur_pos = 480; UPDATE `item` i JOIN ( SELECT id, owner_id, @cur_pos := IF(@cur_owner_id = owner_id, @cur_pos + 1, 480) AS new_pos, @cur_owner_id := owner_id FROM `item` WHERE `vnum` BETWEEN 133000 AND 133460 ORDER BY owner_id, vnum, id ) sub ON i.id = sub.id SET i.`pos` = sub.new_pos, i.`window` = 'DRAGON_SOUL_INVENTORY' WHERE i.`vnum` BETWEEN 133000 AND 133460; -- For vnum range 134000 to 134460 SET @cur_pos = 512; UPDATE `item` i JOIN ( SELECT id, owner_id, @cur_pos := IF(@cur_owner_id = owner_id, @cur_pos + 1, 512) AS new_pos, @cur_owner_id := owner_id FROM `item` WHERE `vnum` BETWEEN 134000 AND 134460 ORDER BY owner_id, vnum, id ) sub ON i.id = sub.id SET i.`pos` = sub.new_pos, i.`window` = 'DRAGON_SOUL_INVENTORY' WHERE i.`vnum` BETWEEN 134000 AND 134460; -- For vnum range 135000 to 135460 SET @cur_pos = 544; UPDATE `item` i JOIN ( SELECT id, owner_id, @cur_pos := IF(@cur_owner_id = owner_id, @cur_pos + 1, 544) AS new_pos, @cur_owner_id := owner_id FROM `item` WHERE `vnum` BETWEEN 135000 AND 135460 ORDER BY owner_id, vnum, id ) sub ON i.id = sub.id SET i.`pos` = sub.new_pos, i.`window` = 'DRAGON_SOUL_INVENTORY' WHERE i.`vnum` BETWEEN 135000 AND 135460; -- For vnum range 140000 to 140460 SET @cur_pos = 576; UPDATE `item` i JOIN ( SELECT id, owner_id, @cur_pos := IF(@cur_owner_id = owner_id, @cur_pos + 1, 576) AS new_pos, @cur_owner_id := owner_id FROM `item` WHERE `vnum` BETWEEN 140000 AND 140460 ORDER BY owner_id, vnum, id ) sub ON i.id = sub.id SET i.`pos` = sub.new_pos, i.`window` = 'DRAGON_SOUL_INVENTORY' WHERE i.`vnum` BETWEEN 140000 AND 140460; -- For vnum range 141000 to 141460 SET @cur_pos = 608; UPDATE `item` i JOIN ( SELECT id, owner_id, @cur_pos := IF(@cur_owner_id = owner_id, @cur_pos + 1, 608) AS new_pos, @cur_owner_id := owner_id FROM `item` WHERE `vnum` BETWEEN 141000 AND 141460 ORDER BY owner_id, vnum, id ) sub ON i.id = sub.id SET i.`pos` = sub.new_pos, i.`window` = 'DRAGON_SOUL_INVENTORY' WHERE i.`vnum` BETWEEN 141000 AND 141460; -- For vnum range 142000 to 142460 SET @cur_pos = 640; UPDATE `item` i JOIN ( SELECT id, owner_id, @cur_pos := IF(@cur_owner_id = owner_id, @cur_pos + 1, 640) AS new_pos, @cur_owner_id := owner_id FROM `item` WHERE `vnum` BETWEEN 142000 AND 142460 ORDER BY owner_id, vnum, id ) sub ON i.id = sub.id SET i.`pos` = sub.new_pos, i.`window` = 'DRAGON_SOUL_INVENTORY' WHERE i.`vnum` BETWEEN 142000 AND 142460; -- For vnum range 143000 to 143460 SET @cur_pos = 672; UPDATE `item` i JOIN ( SELECT id, owner_id, @cur_pos := IF(@cur_owner_id = owner_id, @cur_pos + 1, 672) AS new_pos, @cur_owner_id := owner_id FROM `item` WHERE `vnum` BETWEEN 143000 AND 143460 ORDER BY owner_id, vnum, id ) sub ON i.id = sub.id SET i.`pos` = sub.new_pos, i.`window` = 'DRAGON_SOUL_INVENTORY' WHERE i.`vnum` BETWEEN 143000 AND 143460; -- For vnum range 144000 to 144460 SET @cur_pos = 704; UPDATE `item` i JOIN ( SELECT id, owner_id, @cur_pos := IF(@cur_owner_id = owner_id, @cur_pos + 1, 704) AS new_pos, @cur_owner_id := owner_id FROM `item` WHERE `vnum` BETWEEN 144000 AND 144460 ORDER BY owner_id, vnum, id ) sub ON i.id = sub.id SET i.`pos` = sub.new_pos, i.`window` = 'DRAGON_SOUL_INVENTORY' WHERE i.`vnum` BETWEEN 144000 AND 144460; -- For vnum range 145000 to 145460 SET @cur_pos = 736; UPDATE `item` i JOIN ( SELECT id, owner_id, @cur_pos := IF(@cur_owner_id = owner_id, @cur_pos + 1, 736) AS new_pos, @cur_owner_id := owner_id FROM `item` WHERE `vnum` BETWEEN 145000 AND 145460 ORDER BY owner_id, vnum, id ) sub ON i.id = sub.id SET i.`pos` = sub.new_pos, i.`window` = 'DRAGON_SOUL_INVENTORY' WHERE i.`vnum` BETWEEN 145000 AND 145460; -- For vnum range 150000 to 150460 SET @cur_pos = 768; UPDATE `item` i JOIN ( SELECT id, owner_id, @cur_pos := IF(@cur_owner_id = owner_id, @cur_pos + 1, 768) AS new_pos, @cur_owner_id := owner_id FROM `item` WHERE `vnum` BETWEEN 150000 AND 150460 ORDER BY owner_id, vnum, id ) sub ON i.id = sub.id SET i.`pos` = sub.new_pos, i.`window` = 'DRAGON_SOUL_INVENTORY' WHERE i.`vnum` BETWEEN 150000 AND 150460; -- For vnum range 151000 to 151460 SET @cur_pos = 800; UPDATE `item` i JOIN ( SELECT id, owner_id, @cur_pos := IF(@cur_owner_id = owner_id, @cur_pos + 1, 800) AS new_pos, @cur_owner_id := owner_id FROM `item` WHERE `vnum` BETWEEN 151000 AND 151460 ORDER BY owner_id, vnum, id ) sub ON i.id = sub.id SET i.`pos` = sub.new_pos, i.`window` = 'DRAGON_SOUL_INVENTORY' WHERE i.`vnum` BETWEEN 151000 AND 151460; -- For vnum range 152000 to 152460 SET @cur_pos = 832; UPDATE `item` i JOIN ( SELECT id, owner_id, @cur_pos := IF(@cur_owner_id = owner_id, @cur_pos + 1, 832) AS new_pos, @cur_owner_id := owner_id FROM `item` WHERE `vnum` BETWEEN 152000 AND 152460 ORDER BY owner_id, vnum, id ) sub ON i.id = sub.id SET i.`pos` = sub.new_pos, i.`window` = 'DRAGON_SOUL_INVENTORY' WHERE i.`vnum` BETWEEN 152000 AND 152460; -- For vnum range 153000 to 153460 SET @cur_pos = 864; UPDATE `item` i JOIN ( SELECT id, owner_id, @cur_pos := IF(@cur_owner_id = owner_id, @cur_pos + 1, 864) AS new_pos, @cur_owner_id := owner_id FROM `item` WHERE `vnum` BETWEEN 153000 AND 153460 ORDER BY owner_id, vnum, id ) sub ON i.id = sub.id SET i.`pos` = sub.new_pos, i.`window` = 'DRAGON_SOUL_INVENTORY' WHERE i.`vnum` BETWEEN 153000 AND 153460; -- For vnum range 154000 to 154460 SET @cur_pos = 896; UPDATE `item` i JOIN ( SELECT id, owner_id, @cur_pos := IF(@cur_owner_id = owner_id, @cur_pos + 1, 896) AS new_pos, @cur_owner_id := owner_id FROM `item` WHERE `vnum` BETWEEN 154000 AND 154460 ORDER BY owner_id, vnum, id ) sub ON i.id = sub.id SET i.`pos` = sub.new_pos, i.`window` = 'DRAGON_SOUL_INVENTORY' WHERE i.`vnum` BETWEEN 154000 AND 154460; -- For vnum range 155000 to 155460 SET @cur_pos = 928; UPDATE `item` i JOIN ( SELECT id, owner_id, @cur_pos := IF(@cur_owner_id = owner_id, @cur_pos + 1, 928) AS new_pos, @cur_owner_id := owner_id FROM `item` WHERE `vnum` BETWEEN 155000 AND 155460 ORDER BY owner_id, vnum, id ) sub ON i.id = sub.id SET i.`pos` = sub.new_pos, i.`window` = 'DRAGON_SOUL_INVENTORY' WHERE i.`vnum` BETWEEN 155000 AND 155460; -- For vnum range 160000 to 160460 SET @cur_pos = 960; UPDATE `item` i JOIN ( SELECT id, owner_id, @cur_pos := IF(@cur_owner_id = owner_id, @cur_pos + 1, 960) AS new_pos, @cur_owner_id := owner_id FROM `item` WHERE `vnum` BETWEEN 160000 AND 160460 ORDER BY owner_id, vnum, id ) sub ON i.id = sub.id SET i.`pos` = sub.new_pos, i.`window` = 'DRAGON_SOUL_INVENTORY' WHERE i.`vnum` BETWEEN 160000 AND 160460; -- For vnum range 161000 to 161460 SET @cur_pos = 992; UPDATE `item` i JOIN ( SELECT id, owner_id, @cur_pos := IF(@cur_owner_id = owner_id, @cur_pos + 1, 992) AS new_pos, @cur_owner_id := owner_id FROM `item` WHERE `vnum` BETWEEN 161000 AND 161460 ORDER BY owner_id, vnum, id ) sub ON i.id = sub.id SET i.`pos` = sub.new_pos, i.`window` = 'DRAGON_SOUL_INVENTORY' WHERE i.`vnum` BETWEEN 161000 AND 161460; -- For vnum range 162000 to 162460 SET @cur_pos = 1024; UPDATE `item` i JOIN ( SELECT id, owner_id, @cur_pos := IF(@cur_owner_id = owner_id, @cur_pos + 1, 1024) AS new_pos, @cur_owner_id := owner_id FROM `item` WHERE `vnum` BETWEEN 162000 AND 162460 ORDER BY owner_id, vnum, id ) sub ON i.id = sub.id SET i.`pos` = sub.new_pos, i.`window` = 'DRAGON_SOUL_INVENTORY' WHERE i.`vnum` BETWEEN 162000 AND 162460; -- For vnum range 163000 to 163460 SET @cur_pos = 1056; UPDATE `item` i JOIN ( SELECT id, owner_id, @cur_pos := IF(@cur_owner_id = owner_id, @cur_pos + 1, 1056) AS new_pos, @cur_owner_id := owner_id FROM `item` WHERE `vnum` BETWEEN 163000 AND 163460 ORDER BY owner_id, vnum, id ) sub ON i.id = sub.id SET i.`pos` = sub.new_pos, i.`window` = 'DRAGON_SOUL_INVENTORY' WHERE i.`vnum` BETWEEN 163000 AND 163460; -- For vnum range 164000 to 164460 SET @cur_pos = 1088; UPDATE `item` i JOIN ( SELECT id, owner_id, @cur_pos := IF(@cur_owner_id = owner_id, @cur_pos + 1, 1088) AS new_pos, @cur_owner_id := owner_id FROM `item` WHERE `vnum` BETWEEN 164000 AND 164460 ORDER BY owner_id, vnum, id ) sub ON i.id = sub.id SET i.`pos` = sub.new_pos, i.`window` = 'DRAGON_SOUL_INVENTORY' WHERE i.`vnum` BETWEEN 164000 AND 164460; -- For vnum range 165000 to 165460 SET @cur_pos = 1120; UPDATE `item` i JOIN ( SELECT id, owner_id, @cur_pos := IF(@cur_owner_id = owner_id, @cur_pos + 1, 1120) AS new_pos, @cur_owner_id := owner_id FROM `item` WHERE `vnum` BETWEEN 165000 AND 165460 ORDER BY owner_id, vnum, id ) sub ON i.id = sub.id SET i.`pos` = sub.new_pos, i.`window` = 'DRAGON_SOUL_INVENTORY' WHERE i.`vnum` BETWEEN 165000 AND 165460; Note: Replace vnum with the appropriate vnum based on your system's configuration.
-
hello great job but u forget to add #include <memory> in header of heart.h: #ifndef __INC_LIBTHECORE_HEART_H__ #define __INC_LIBTHECORE_HEART_H__ #include <memory> struct HEART; using LPHEART = std::shared_ptr<HEART>; typedef void (*HEARTFUNC) (LPHEART heart, int pulse); struct HEART { HEARTFUNC func; struct timeval before_sleep; struct timeval opt_time; struct timeval last_time; int passes_per_sec; int pulse; }; // Function declarations extern LPHEART heart_new(int opt_usec, HEARTFUNC func); extern int heart_idle(LPHEART ht); // ¸î pulse°¡ Áö³µ³ª ¸®ÅÏÇÑ´Ù. extern void heart_beat(LPHEART ht, int pulses); #endif // __INC_LIBTHECORE_HEART_H__
