Jump to content

Recommended Posts

Hello, there is a common error/bug in Metin2 related to the taskbar and character skill window.
This error occurs when the skill has the status of "TOGGLE" and somebody or something killed us.
When we are killed the game client still thinks that the skill is active somehow not updating its status (right clicking on slot causes the packet to be sent to the server again).

What's even more funny, when we kill our character by command or when we've been shot down at once this problem does not occur ?
Here are some screenshots explaining the bug:

Spoiler

180046bug.png180046bug3.png180046bug2.png


If you are interested in fixing this error, please follow the instructions below:

Clientside:
Open UserInterface/PythonPlayerModule.cpp and search:
 

{ "ClearSkillDict",						playerClearSkillDict,						METH_VARARGS },


add below:
 

{ "DisableToggleSkill",					playerDisableToggleSkill,					METH_VARARGS },

scroll down and search for:
 

PyObject * playerClearSkillDict(PyObject * poSelf, PyObject * poArgs)

add under:

PyObject * playerDisableToggleSkill(PyObject * poSelf, PyObject * poArgs)
{
	int iSlotIndex;
	if (!PyTuple_GetInteger(poArgs, 0, &iSlotIndex))
		return Py_BadArgument();
	CPythonPlayer::Instance().Disable_Toggle_Skill(iSlotIndex);
	return Py_BuildNone();
}

 

180046like.png

Now move on to the UserInterface/PythonPlayer.cpp and search for:

void CPythonPlayer::ClearSkillDict()

add below:
 

void CPythonPlayer::Disable_Toggle_Skill(DWORD dwSlotIndex)
{
	CInstanceBase * pkInstMain = NEW_GetMainActorPtr();
	if (!pkInstMain)
		return;
	if (!pkInstMain->IsDead())
		return;
	if (dwSlotIndex >= SKILL_MAX_NUM)
		return;
	for (dwSlotIndex; dwSlotIndex <= SKILL_MAX_NUM; ++dwSlotIndex)
	{
		if (IsSkillActive(dwSlotIndex))
		{
			m_playerStatus.aSkill[dwSlotIndex].bActive = FALSE;
			PyCallClassMemberFunc(m_ppyGameWindow, "DeactivateSkillSlot", Py_BuildValue("(i)", dwSlotIndex));
		}
	}
}

after that UserInterface/PythonPlayer.h search:
 

void	ClearSkillDict(); // ľřľîÁö°ĹłŞ ClearGame ÂĘŔ¸·Î Ć÷Ç﵃ ÇÔĽö

add:
 

void	Disable_Toggle_Skill(DWORD dwSlotIndex);


UserInterface/PythonPlayerSkill.cpp
Search:
 

void CPythonPlayer::ClickSkillSlot(DWORD dwSlotIndex)

and under:

if (pSkillData->IsStandingSkill())
	{
		if (pSkillData->IsToggleSkill())
		{
			if (IsSkillActive(dwSlotIndex))
			{
				CInstanceBase * pkInstMain = NEW_GetMainActorPtr();
				if (!pkInstMain)
					return;
				if (pkInstMain->IsUsingSkill())
					return;

add new if statement:
 

if (pkInstMain->IsDead())
					return;

like that:

 

180046likethat.png

Rebuild solution.

Python part:
root/uitaskbar.py
Search in OnUpdate(self):

if app.GetGlobalTime() - self.lastUpdateQuickSlot > 500:

add at the end:

player.DisableToggleSkill(0)


 

Spoiler

180202usage.png




Thanks to @filipw1 idea there's no need to use python part anymore.
Back to client source:

UserInterface/PythonPlayer.cpp:

search:

 

void CPythonPlayer::NotifyDeadMainCharacter()

and change void to this:

void CPythonPlayer::NotifyDeadMainCharacter()
{
	__ClearAutoAttackTargetActorID();
	Disable_Toggle_Skill(0);
}

Rebuild solution.
If you want to execute it from python scriptplayer.DisableToggleSkill(skill_startIndex)

Final result:

https://metin2.download/picture/OOJnyD9ih6yBgfB2hXiKJHDrY684UYZd/.gif

This is my way to solve this graphic problem. 
All suggestions are welcome.
Have a nice day ?

Edited by Metin2 Dev
Core X - External 2 Internal
  • Love 9

I completely abandoned working on the files for this game. I do not respond to private messages.

.png

Link to comment
https://metin2.dev/topic/21864-fix-skill-toggle-when-dead/
Share on other sites

  • Premium

This is a bad idea. Why don't you use packet client receives when characters dies? I mean OnUpdate is poor solution for this problem. You should execute this code exactly from binary for example inside CPythonPlayer::NotifyCharacterDead(DWORD dwVID). Anyways, thanks, I didn't think about that 

  • Love 1
Link to comment
https://metin2.dev/topic/21864-fix-skill-toggle-when-dead/#findComment-118875
Share on other sites

6 hours ago, filipw1 said:

This is a bad idea. Why don't you use packet client receives when characters dies? I mean OnUpdate is poor solution for this problem. You should execute this code exactly from binary for example inside CPythonPlayer::NotifyCharacterDead(DWORD dwVID). Anyways, thanks, I didn't think about that 

OnUpdate() is executed only when character is dead for this case so don't worry - but you're right with only binary thing. I've forgot about NotifyDeadMainCharacter. I'm going to update this guide, thanks ?

  • Love 2

I completely abandoned working on the files for this game. I do not respond to private messages.

.png

Link to comment
https://metin2.dev/topic/21864-fix-skill-toggle-when-dead/#findComment-118880
Share on other sites

  • 1 year later...
  • Active+ Member

Hello, I'm having the same problem, but mine is a little different. Skill is not TOGGLE.

Normal warrior skill does not cancel when killed by a creature. There is no problem when it dies with the /kill command.

 

The effect of the skill disappears, but it still appears as an image.

How can I solve this?

 

Edit: this solution not work.

spacer.png

 

Edited by Metin2 Dev
Core X - External 2 Internal
Link to comment
https://metin2.dev/topic/21864-fix-skill-toggle-when-dead/#findComment-140049
Share on other sites

  • 4 years later...
On 6/5/2021 at 3:01 PM, Kaiser said:

Hello, I'm having the same problem, but mine is a little different. Skill is not TOGGLE.

Normal warrior skill does not cancel when killed by a creature. There is no problem when it dies with the /kill command.

 

The effect of the skill disappears, but it still appears as an image.

How can I solve this?

 

Edit: this solution not work.

spacer.png

 

Has anyone found a solution to this problem? 

Link to comment
https://metin2.dev/topic/21864-fix-skill-toggle-when-dead/#findComment-174600
Share on other sites

  • Active+ Member

The Aura of the Sword fix is a rendering issue and I am not aware of any fixes about it so far. It is a "YMIR original bug" as I call them and I probably will start exploring it in the near future. Feel free to post a fix if you find one until then. 🙂

Edited by Mind Rapist
Link to comment
https://metin2.dev/topic/21864-fix-skill-toggle-when-dead/#findComment-174611
Share on other sites

  • 6 months later...
  • Active+ Member
On 2/25/2026 at 2:28 PM, WhiteBambo said:

Has anyone found a solution to this problem? 

How to fix the Aura of the Sword/Stealth/Feather Walk/Swiftness and similar effects being attached to the mesh after death:

This is about what @WhiteBambo asked specifically, I've seen a lot of clients having this and this is not relevant to togglable slots and skills. The reason this is happening is because of the server's 3-second stun-death (classic mechanism from YMIR) and this should be an easy fix by this guide:

Please adjust changes for your own source!!!

Normally, when a monster or player kills you, the server first sends a stun packet and only 3 seconds later the real death with the affect clear. The client handles that stun packet by calling Die() at once for your own character, and Die() wipes the affect flags without running each affect's off path. When the server's "flags off" update arrives 3 seconds later, the client sees no change and never detaches the visuals that live outside the affect slot array, such as the Aura of the Sword weapon glow, the reach scale and the stealth alpha. That is why only your own corpse shows it, never other players' screens, and why /kill and one-shot deaths are clean: those skip the stun packet. The client fix below makes the early clear run every off path properly. Making deaths instant server-side also avoids it, but that is a gameplay decision, not a fix.

Some client-side fixes:

  1. Swiftness and Feather Walk can survive this fix in some clients, as well as overwrite eachother, or show to observer clients even if the character's movement has stopped! Test whether you have this before applying the following fix:
    Spoiler

    In UserInterface/InstanceBase.h:
     

    // Find
    protected:
    		DWORD	__AttachEffect(UINT eEftType);
    		DWORD	__AttachEffect(char filename[128]);
    		void	__DetachEffect(DWORD dwEID);
    
    // Add right below the last one:
    		void	__AttachAffectEffect(UINT eAffect);
    		void	__DetachAffectEffect(UINT eAffect);

    In UserInterface/InstanceBaseEffect.cpp:
     

    // In void CInstanceBase::__ClearAffects(), find:
    		for (int iAffect=0; iAffect<AFFECT_NUM; ++iAffect)
    		{
    			__DetachEffect(m_adwCRCAffectEffect[iAffect]);
    			m_adwCRCAffectEffect[iAffect]=0;
    		}
    
    // Replace with:
    		for (int iAffect = 0; iAffect < AFFECT_NUM; ++iAffect)
    			__DetachAffectEffect(iAffect);
    
    // Then find this:
    void CInstanceBase::__Warrior_SetGeomgyeongAffect(bool isVisible)
    {
      ...
    }
    
    // And right after its closing bracket add these:
    void CInstanceBase::__AttachAffectEffect(UINT eAffect)
    {
    	if (m_adwCRCAffectEffect[eAffect])
    		return;
    
    	m_adwCRCAffectEffect[eAffect] = __AttachEffect(EFFECT_AFFECT + eAffect);
    }
    
    void CInstanceBase::__DetachAffectEffect(UINT eAffect)
    {
    	if (!m_adwCRCAffectEffect[eAffect])
    		return;
    
    	__DetachEffect(m_adwCRCAffectEffect[eAffect]);
    	m_adwCRCAffectEffect[eAffect] = 0;
    }
    
    // Then in void CInstanceBase::__SetAffect(UINT eAffect, bool isVisible), go all the way down to the bottom of that block and find:
    	if (isVisible)
    	{
    		if (!m_adwCRCAffectEffect[eAffect])
    		{
    			m_adwCRCAffectEffect[eAffect]=__AttachEffect(EFFECT_AFFECT+eAffect);
    		}
    	}
    	else
    	{
    		if (m_adwCRCAffectEffect[eAffect])
    		{
    			__DetachEffect(m_adwCRCAffectEffect[eAffect]);
    			m_adwCRCAffectEffect[eAffect]=0;
    		}
    	}
    
    // and simply change it into:
    	if (isVisible)
    		__AttachAffectEffect(eAffect);
    	else
    		__DetachAffectEffect(eAffect);

    Finally, in UserInterface/InstanceBaseMovement.cpp:
     

    // Change:
    void CInstanceBase::StartWalking()
    {
    	m_GraphicThingInstance.Move();
    
    	if (IsAffect(AFFECT_GYEONGGONG))
    	{
    		m_adwCRCAffectEffect[AFFECT_GYEONGGONG] = __EffectContainer_AttachEffect(EFFECT_AFFECT_GYEONGGONG);
    	}
    	else if (IsAffect(AFFECT_KWAESOK))
    	{
    		m_adwCRCAffectEffect[AFFECT_KWAESOK] = __EffectContainer_AttachEffect(EFFECT_AFFECT_KWAESOK);
    	}
    }
    
    // into:
    void CInstanceBase::StartWalking()
    {
    	m_GraphicThingInstance.Move();
    
    	if (IsAffect(AFFECT_GYEONGGONG))
    		__AttachAffectEffect(AFFECT_GYEONGGONG);
    
    	if (IsAffect(AFFECT_KWAESOK))
    		__AttachAffectEffect(AFFECT_KWAESOK);
    }
    
    // Then find:
    void CInstanceBase::EndWalking(float fBlendingTime)
    {
    	assert(!IsWaiting() && "CInstanceBase::EndWalking");
    
    	m_isGoing = FALSE;
    
    	if (IsWalking() || !IsAttacked())
        {
    		m_GraphicThingInstance.Stop(fBlendingTime);
    		if (IsAffect(AFFECT_GYEONGGONG))
    		{
    			__EffectContainer_DetachEffect(EFFECT_AFFECT_GYEONGGONG);
    		}
    		else if (IsAffect(AFFECT_KWAESOK))
    		{
    			__EffectContainer_DetachEffect(EFFECT_AFFECT_KWAESOK);
    		}
    	}
      
    // and change it like this:
    void CInstanceBase::EndWalking(float fBlendingTime)
    {
    	assert(!IsWaiting() && "CInstanceBase::EndWalking");
    
    	m_isGoing = FALSE;
    
    	__DetachAffectEffect(AFFECT_GYEONGGONG);
    	__DetachAffectEffect(AFFECT_KWAESOK);
    
    	if (IsWalking() || !IsAttacked())
    		m_GraphicThingInstance.Stop(fBlendingTime);

     

  2. Catching false-positive flags and disabling them (the actual client-side fix for this issue):
    Spoiler

    In UserInterface/InstanceBaseEffect.cpp:
     

    // Find:
    void CInstanceBase::__ClearAffects()
      
    // and add this inside the else branch, BEFORE the existing for loop:
    	for (int iAffect = 0; iAffect < AFFECT_NUM; ++iAffect)
    		if (IsAffect(iAffect))
    			__SetAffect(iAffect, false);

    It is important that despite being the same, 2 loops on need to exist one after the other:

    • The first (new one) runs each active affect's own off path to catch any flags that show an activated state, which is the only way to free up handler-owned visuals.
    • The second frees whatever is still in the slot array, such as the run-only effects the walk hooks attach without a flag.

    Only then are all the flags cleared.

     

Link to comment
https://metin2.dev/topic/21864-fix-skill-toggle-when-dead/#findComment-176609
Share on other sites

Don't use any images from : imgur, turkmmop, freakgamers, inforge, hizliresim... Or your content will be deleted without notice...
Use : https://metin2.download/media/add/

Please use https://metin2.download/ when uploading files smaller than 100MB, otherwise the approval will take longer due to manual upload.

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now
×
×
  • Create New...

Important Information

Terms of Use / Privacy Policy / Guidelines / We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.