Jump to content

Recommended Posts

  • Active+ Member

For about a month now I've been trying to improve the slot system regarding skills and togglables and I'm almost there. I know there are guides about this in here but I found them all incomplete. I tested thourough and would always found a little bug plus I wanted to add a little feature on skill reseting. I've made huge progress but the reason I'm posting here instead of Guides is because this fix is incomplete! And therefore I am asking the help of the community to complete it.

Why am I doing this:

Since the beginning of my work in this fix I wanted to release it for free to the public. These are bugs from YMIR since 2014 and no free client out there has them resolved. I intend to release it for free for everyone. I believe that after 10 years we should be overflowing with unbugged clients/sources/serverfiles (40K clean ofc, not customized with extra systems, but still, simple and clean bases for anyone to get started).

The goal:

What I'm trying to fix here is:

  • The lack of auto-updating for skill cooldowns (changing levels/grades, (de)activating, etc...)    [NEED HELP]
  • Wrong activation/deactivation border effect synchronization    [FIXED]
  • [NEW] Skill cooldown end upon reset. If a skill's level goes to 0, it would be a good idea to end the cooldown since it cannot be used.    [NEED HELP]
  • [NEW] Skill deactivation upon reset and remove its affect. This is clearly my own preference because for me its more realistic to remove an affect that requires the character to constantly maintain it with their SP points. Anyone can easily modify this to their liking (all skills, selected skills/skill types, etc...) or even deactivate it completely, for me reseting togglables only just makes sense. The fix I have implemented works with skill booleans such as IsToggle() which catches automatically all togglables. No furher edits required if you have/wish to add wolfman character or turn all affect-enabling skills into togglables.    [FIXED]
  • [NEW] Horse page visibility and functionality while no skill group assigned to the character.    [FIXED]
  • [NEW] Passive skills reset when reseting stats bug (levels reappear normal once a skill group is assigned).    [FIXED]
  • [NEW] Adjust the skill_group quest so no relog is required after stats reset.    [NEED HELP]

 

Before I tell you what's unfinished and what the problem is let me share with you what I've done so far:

I first started working on the basics with @Shang's extremely useful guide for fixing the slots cooldown and activation

I used @ Mali's amazing guide for zeroing a skill's cooldown on reset (⚠️ heavily modified functions as you will see below in the code)

And finally I used this fix for the horse page (fix by @ Criminus and @ VegaS™ in the comments)

 

As you can see the fixes are public, they just need some modifications. Most of them I did on my own but my lack of experience brings me to this post. Here is how to implement these fixes so far:

Disclaimer: I know some parts of this code may be redundant because I tried a lot of things for a lot of different fixes until I get the right one. Solution first, cleanup later.

 

Server source

common/service.h

Spoiler
#define FIX_REFRESH_SKILL_COOLDOWN // Add me somewhere. I use macros for maintaining the original code as well, if you wish so, feel free to implement without a macro

 

 

game/char.h

Spoiler
// Find
void				ResetSkill();

// Add below
#ifdef FIX_REFRESH_SKILL_COOLDOWN
		void				ResetSkillCoolTimes();
#endif

...

// Find
		// RESET_ONE_SKILL
		bool ResetOneSkill(DWORD dwVnum);
		// END_RESET_ONE_SKILL

// Add below
#ifdef FIX_REFRESH_SKILL_COOLDOWN
		void ResetOneSkillCoolTime(DWORD dwVnum);
#endif

 

 

game/char_skill.cpp

Spoiler
// Find
void CHARACTER::ResetSkill()

// Modify the while statement like this:
	while (iter != vec.end())
	{
		const std::pair<DWORD, TPlayerSkill>& pair = *(iter++);
		m_pSkillLevels[pair.first] = pair.second;

#ifdef FIX_REFRESH_SKILL_COOLDOWN
		ResetSkillCoolTimes();
#endif
	}

// Add this after the function end
#ifdef FIX_REFRESH_SKILL_COOLDOWN
void CHARACTER::ResetSkillCoolTimes()
{
	for (std::map<int, TSkillUseInfo>::iterator it = m_SkillUseInfo.begin(); it != m_SkillUseInfo.end(); ++it)
		ResetOneSkillCoolTime(it->first);
}
#endif

...

// Find in bool CHARACTER::ResetOneSkill(DWORD dwVnum) this line
	m_pSkillLevels[dwVnum].tNextRead = 0;

// Add below
#ifdef FIX_REFRESH_SKILL_COOLDOWN
	ResetOneSkillCoolTime(dwVnum);
#endif
  
// Add this after the function end
ifdef FIX_REFRESH_SKILL_COOLDOWN
void CHARACTER::ResetOneSkillCoolTime(DWORD dwVnum)
{
	if (dwVnum >= SKILL_MAX_NUM)
		return;

	if (!GetSkillGroup() || m_SkillUseInfo.empty())
	{
		// still try to disable toggle state even when no cooldown info exists
		CSkillProto* pkSkNoInfo = CSkillManager::instance().Get(dwVnum);

		if (pkSkNoInfo && IS_SET(pkSkNoInfo->dwFlag, SKILL_FLAG_TOGGLE))
			RemoveAffect(pkSkNoInfo->dwVnum);

		return;
	}

	std::map<int, TSkillUseInfo>::iterator it = m_SkillUseInfo.find(dwVnum);

	if (it != m_SkillUseInfo.end())
		it->second.dwNextSkillUsableTime = 0;

	// If the skill is togglable, ensure it is turned off.
	CSkillProto* pkSk = CSkillManager::instance().Get(dwVnum);

	if (pkSk && IS_SET(pkSk->dwFlag, SKILL_FLAG_TOGGLE))
		RemoveAffect(pkSk->dwVnum);
}
#endif

 

 

Client source

UserInterface/Locale_inc.h

Spoiler
#define FIX_REFRESH_SKILL_COOLDOWN // Add me somewhere
#define FIX_HORSE_SKILLS_TAB // Add me somewhere

 

 

EterPython/PythonSlotWindow.h

Spoiler
// Find
			typedef TSlotList::iterator TSlotListIterator;

// Add below
#ifdef FIX_REFRESH_SKILL_COOLDOWN
			typedef struct SStoreCoolDown { float fCoolTime; float fElapsedTime; bool bActive; };
#endif

...

// Find
			void SetSlotType(DWORD dwType);

// Add below
#ifdef FIX_REFRESH_SKILL_COOLDOWN
			DWORD GetSlotType() const;
#endif

...

// Find
			void SetSlotCoolTime(DWORD dwIndex, float fCoolTime, float fElapsedTime = 0.0f);

// Add below
#ifdef FIX_REFRESH_SKILL_COOLDOWN
			void StoreSlotCoolTime(DWORD dwKey, DWORD dwSlotIndex, float fCoolTime, float fElapsedTime = .0f);
			void RestoreSlotCoolTime(DWORD dwKey);
			void TransferSlotCoolTime(DWORD dwIndex1, DWORD dwIndex2);
#endif

...

// Find
			void ReserveDestroyCoolTimeFinishEffect(DWORD dwSlotIndex);

// Add below
#ifdef FIX_REFRESH_SKILL_COOLDOWN
			void ClearStoredSlotCoolTime(DWORD dwKey, DWORD dwSlotIndex);
#endif

...

// Find
			DWORD m_dwToolTipSlotNumber;

// Add below
#ifdef FIX_REFRESH_SKILL_COOLDOWN
			std::map<DWORD, std::map<DWORD, SStoreCoolDown>>	m_CoolDownStore;
#endif

 

 

EterPython/PythonSlotWindow.cpp

Spoiler
// Add in the includes
#ifdef FIX_REFRESH_SKILL_COOLDOWN
#include "UserInterface/PythonSkill.h"
#include "UserInterface/PythonPlayer.h"
#endif
  
...
  
// Find
void CSlotWindow::SetSlotType(DWORD dwType)
{
	m_dwSlotType = dwType;
}

// Add below
#ifdef FIX_REFRESH_SKILL_COOLDOWN
DWORD CSlotWindow::GetSlotType() const
{
	return m_dwSlotType;
}
#endif
  
...
  
// Find
void CSlotWindow::SetSlotCoolTime(DWORD dwIndex, float fCoolTime, float fElapsedTime)
  
// Add below this function
#ifdef FIX_REFRESH_SKILL_COOLDOWN
void CSlotWindow::StoreSlotCoolTime(DWORD dwKey, DWORD dwSlotIndex, float fCoolTime, float fElapsedTime)
{
	std::map<DWORD, SStoreCoolDown>::iterator it = m_CoolDownStore[dwKey].find(dwSlotIndex);

	if (it != m_CoolDownStore[dwKey].end())
	{
		it->second.fCoolTime = fCoolTime;
		it->second.fElapsedTime = CTimer::Instance().GetCurrentSecond() - fElapsedTime;
		it->second.bActive = false;
	}
	else
	{
		SStoreCoolDown m_storeCoolDown;

		m_storeCoolDown.fCoolTime = fCoolTime;
		m_storeCoolDown.fElapsedTime = CTimer::Instance().GetCurrentSecond() - fElapsedTime;
		m_storeCoolDown.bActive = false;

		m_CoolDownStore[dwKey].insert(std::map<DWORD, SStoreCoolDown>::value_type(dwSlotIndex, m_storeCoolDown));
	}
}

void CSlotWindow::RestoreSlotCoolTime(DWORD dwKey)
{
	for (std::map<DWORD, SStoreCoolDown>::iterator it = m_CoolDownStore[dwKey].begin(); it != m_CoolDownStore[dwKey].end(); it++)
	{
		TSlot* pSlot;

		if (!GetSlotPointer(it->first, &pSlot))
			return;

		pSlot->fCoolTime = it->second.fCoolTime;
		pSlot->fStartCoolTime = it->second.fElapsedTime;

		if (it->second.bActive)
			ActivateSlot(it->first);
		else
			DeactivateSlot(it->first);
	}
}

void CSlotWindow::TransferSlotCoolTime(DWORD dwIndex1, DWORD dwIndex2)
{
	std::map<DWORD, SStoreCoolDown>::iterator it = m_CoolDownStore[CPythonSkill::SKILL_TYPE_ACTIVE].find(dwIndex1);

	if (it != m_CoolDownStore[CPythonSkill::SKILL_TYPE_ACTIVE].end())
	{
		TSlot* pSlot1;

		if (!GetSlotPointer(dwIndex1, &pSlot1))
			return;

		TSlot* pSlot2;

		if (!GetSlotPointer(dwIndex2, &pSlot2))
			return;

		// Replacing the cooldown from slot 1 to slot 2 and removing the slot 1 from the map.
		SStoreCoolDown slotCooldown = it->second;

		int iDestSkillGrade = CPythonPlayer::Instance().GetSkillGrade(dwIndex2);
		int iDestSkillLevel = CPythonPlayer::Instance().GetSkillLevel(dwIndex2);

		m_CoolDownStore[CPythonSkill::SKILL_TYPE_ACTIVE][dwIndex2] = slotCooldown;
		m_CoolDownStore[CPythonSkill::SKILL_TYPE_ACTIVE].erase(dwIndex1);

		// Removing the cooldown from the slot 1.
		pSlot1->fCoolTime = 0;
		pSlot1->fStartCoolTime = 0;

		if (slotCooldown.bActive)
			ActivateSlot(dwIndex2);

		if (iDestSkillLevel > 0)
		{
			// Setting the cooldown from slot 1 to slot 2.
			pSlot2->fCoolTime = slotCooldown.fCoolTime;
			pSlot2->fStartCoolTime = slotCooldown.fElapsedTime;
		}
	}
}
#endif
  
...
  
// Find this in void CSlotWindow::ActivateSlot(DWORD dwIndex)
	if (!m_pSlotActiveEffect)
	{
		__CreateSlotEnableEffect();
	}

// Add below
#ifdef FIX_REFRESH_SKILL_COOLDOWN
	std::map<DWORD, SStoreCoolDown>::iterator it = m_CoolDownStore[1].find(dwIndex);

	if (it != m_CoolDownStore[1].end())
		it->second.bActive = true;
#endif
  
...
  
// Find this in void CSlotWindow::DeactivateSlot(DWORD dwIndex)
	pSlot->bActive = FALSE;

// Add below
#ifdef FIX_REFRESH_SKILL_COOLDOWN
	std::map<DWORD, SStoreCoolDown>::iterator it = m_CoolDownStore[1].find(dwIndex);

	if (it != m_CoolDownStore[1].end())
		it->second.bActive = false;
#endif
  
...
  
// Find
void CSlotWindow::ReserveDestroyCoolTimeFinishEffect(DWORD dwSlotIndex)
{
	m_ReserveDestroyEffectDeque.push_back(dwSlotIndex);
}

// Add below
#ifdef FIX_REFRESH_SKILL_COOLDOWN
void CSlotWindow::ClearStoredSlotCoolTime(DWORD dwKey, DWORD dwSlotIndex)
{
	std::map<DWORD, SStoreCoolDown>& store = m_CoolDownStore[dwKey];
	std::map<DWORD, SStoreCoolDown>::iterator it = store.find(dwSlotIndex);
	
	if (it != store.end())
		store.erase(it);
}
#endif
  
...
  
// Find this in void CSlotWindow::__Initialize()
	m_dwToolTipSlotNumber = SLOT_NUMBER_NONE;

// Add below
#ifdef FIX_REFRESH_SKILL_COOLDOWN
	m_CoolDownStore.clear();
#endif

 

 

EterPython/PythonWindowManager.h

Spoiler
// Find
			void		ResetCapture();

// Add below
#ifdef FIX_REFRESH_SKILL_COOLDOWN
			void		ClearStoredSlotCoolTimeInAllSlotWindows(DWORD dwKey, DWORD dwSlotIndex);
#endif

 

 

EterPython/PythonWindowManager.cpp

Spoiler
// Find
void CWindowManager::ResetCapture()
{
	m_pLeftCaptureWindow = NULL;
	m_pMiddleCaptureWindow = NULL;
	m_pRightCaptureWindow = NULL;
}

// Add below
#ifdef FIX_REFRESH_SKILL_COOLDOWN
	void CWindowManager::ClearStoredSlotCoolTimeInAllSlotWindows(DWORD dwKey, DWORD dwSlotIndex)
	{
		// recursively walk the window tree starting from layers and clear stored cooldown entries
		std::function<void(CWindow*)> recurse;

		recurse = [&](CWindow* pWin)
		{
			if (!pWin)
				return;

			// If this is a slot window, call its helper
			if (pWin->IsType(UI::CSlotWindow::Type()))
			{
				UI::CSlotWindow * pSlotWin = static_cast<UI::CSlotWindow*>(pWin);
				pSlotWin->ClearStoredSlotCoolTime(dwKey, dwSlotIndex);
			}

			// Visit children
			for (CWindow* pChild : pWin->GetChildList())
			{
				recurse(pChild);
			}
		};

		// Walk all layer roots
		for (CWindow* pLayer : m_LayerWindowList)
		{
			recurse(pLayer);
		}
	}
#endif

 

 

EterPython/PythonWindow.h

Spoiler
// Find
int				GetChildCount()	{ return m_pChildList.size(); }

// Add below
#ifdef FIX_REFRESH_SKILL_COOLDOWN
			const TWindowContainer& GetChildList() const { return m_pChildList; }
#endif

 

 

EterPython/PythonWindowManagerModule.cpp

Spoiler
// Find
PyObject * wndMgrSetSlotCoolTime(PyObject * poSelf, PyObject * poArgs)

// Add below the end of that function
#ifdef FIX_REFRESH_SKILL_COOLDOWN
PyObject* wndMgrStoreSlotCoolTime(PyObject* poSelf, PyObject* poArgs)
{
	UI::CWindow* pWin;
	if (!PyTuple_GetWindow(poArgs, 0, &pWin))
		return Py_BuildException();

	int iKey;
	if (!PyTuple_GetInteger(poArgs, 1, &iKey))
		return Py_BuildException();

	int iSlotIndex;
	if (!PyTuple_GetInteger(poArgs, 2, &iSlotIndex))
		return Py_BuildException();

	float fCoolTime;
	if (!PyTuple_GetFloat(poArgs, 3, &fCoolTime))
		return Py_BuildException();

	float fElapsedTime = 0.0f;
	PyTuple_GetFloat(poArgs, 4, &fElapsedTime);

	if (!pWin->IsType(UI::CSlotWindow::Type()))
		return Py_BuildException();

	UI::CSlotWindow* pSlotWin = (UI::CSlotWindow*)pWin;
	pSlotWin->StoreSlotCoolTime(iKey, iSlotIndex, fCoolTime, fElapsedTime);

	return Py_BuildNone();
}

PyObject* wndMgrRestoreSlotCoolTime(PyObject* poSelf, PyObject* poArgs)
{
	UI::CWindow* pWin;
	if (!PyTuple_GetWindow(poArgs, 0, &pWin))
		return Py_BuildException();

	int iKey;
	if (!PyTuple_GetInteger(poArgs, 1, &iKey))
		return Py_BuildException();

	if (!pWin->IsType(UI::CSlotWindow::Type()))
		return Py_BuildException();

	UI::CSlotWindow* pSlotWin = (UI::CSlotWindow*)pWin;
	pSlotWin->RestoreSlotCoolTime(iKey);

	return Py_BuildNone();
}

PyObject* wndMgrTransferSlotCoolTime(PyObject* poSelf, PyObject* poArgs)
{
	UI::CWindow* pWin;
	if (!PyTuple_GetWindow(poArgs, 0, &pWin))
		return Py_BuildException();

	int iIndex1;
	if (!PyTuple_GetInteger(poArgs, 1, &iIndex1))
		return Py_BuildException();

	int iIndex2;
	if (!PyTuple_GetInteger(poArgs, 2, &iIndex2))
		return Py_BuildException();

	if (!pWin->IsType(UI::CSlotWindow::Type()))
		return Py_BuildException();

	UI::CSlotWindow* pSlotWin = (UI::CSlotWindow*)pWin;
	pSlotWin->TransferSlotCoolTime(iIndex1, iIndex2);

	return Py_BuildNone();
}
#endif
  
...
  
// Find
		{ "SetSlotCoolTime",			wndMgrSetSlotCoolTime,				METH_VARARGS },
// Add below
#ifdef FIX_REFRESH_SKILL_COOLDOWN
		{ "StoreSlotCoolTime",			wndMgrStoreSlotCoolTime,			METH_VARARGS },
		{ "RestoreSlotCoolTime",		wndMgrRestoreSlotCoolTime,			METH_VARARGS },
		{ "TransferSlotCoolTime",		wndMgrTransferSlotCoolTime,			METH_VARARGS },
#endif

 

 

UserInterface/PythonPlayer.h

Spoiler
// Find
		void	NotifyChangePKMode();

// Add below
#ifdef FIX_REFRESH_SKILL_COOLDOWN
		void	ResetSkillCoolTimes();
		void	ResetSkillCoolTimeForSlot(DWORD dwSlotIndex);
#endif

 

 

UserInterface/PythonPlayer.cpp

Spoiler
// Find
void CPythonPlayer::NotifyChangePKMode()
{
	PyCallClassMemberFunc(m_ppyGameWindow, "OnChangePKMode", Py_BuildValue("()"));
}

// Add below
#ifdef FIX_REFRESH_SKILL_COOLDOWN
void CPythonPlayer::ResetSkillCoolTimes()
{
	for (int i = 0; i < SKILL_MAX_NUM; ++i)
		ResetSkillCoolTimeForSlot(i);
}

void CPythonPlayer::ResetSkillCoolTimeForSlot(DWORD dwSlotIndex)
{
	// Require skill-group to be present (same check as original global implementation)
	if (!CPythonNetworkStream::Instance().GetMainActorSkillGroup())
		return;

	if (dwSlotIndex >= SKILL_MAX_NUM)
		return;

	TSkillInstance& rkSkillInst = m_playerStatus.aSkill[dwSlotIndex];

	// If this skill is a toggle and currently active, deactivate it so UI/state is consistent.
	// __DeactivateSkillSlot is a private/protected helper on this class.
	if (IsToggleSkill(dwSlotIndex) && IsSkillActive(dwSlotIndex))
		__DeactivateSkillSlot(dwSlotIndex);

	// If nothing to clear, skip
	if (!rkSkillInst.fLastUsedTime && !rkSkillInst.fCoolTime)
		return;

	// Clear cooldown timers
	rkSkillInst.fLastUsedTime = rkSkillInst.fCoolTime = 0.0f;

	UI::CWindowManager::Instance().ClearStoredSlotCoolTimeInAllSlotWindows(CPythonSkill::SKILL_TYPE_ACTIVE, dwSlotIndex);

	// Inform Python/UI which slot was cleared (SkillClearCoolTime expects the slot index)
	PyCallClassMemberFunc(m_ppyGameWindow, "SkillClearCoolTime", Py_BuildValue("(i)", (int)dwSlotIndex));
}
#endif

...
  
// Find this in void CPythonPlayer::SetSkillLevel_(DWORD dwSkillIndex, DWORD dwSkillGrade, DWORD dwSkillLevel)
if (dwSkillLevel > SKILL_MAX_LEVEL)
{
	m_playerStatus.aSkill[dwSlotIndex].fcurEfficientPercentage = 0.0f;
	m_playerStatus.aSkill[dwSlotIndex].fnextEfficientPercentage = 0.0f;

	TraceError("CPythonPlayer::SetSkillLevel(SlotIndex=%d, SkillLevel=%d)", dwSlotIndex, dwSkillLevel);

	return;
}

// Add below this if statement
#ifdef FIX_REFRESH_SKILL_COOLDOWN
	if (m_playerStatus.aSkill[dwSlotIndex].iLevel <= 0)
	{
		ResetSkillCoolTimeForSlot(dwSlotIndex);
	}
#endif
  
...
  
// Find
void CPythonPlayer::NEW_ClearSkillData(bool bAll)
  
// Modify like this
void CPythonPlayer::NEW_ClearSkillData(bool bAll)
{
	std::map<DWORD, DWORD>::iterator it;

	for (it = m_skillSlotDict.begin(); it != m_skillSlotDict.end();)
	{
#ifdef FIX_REFRESH_SKILL_COOLDOWN
		if (__GetSkillType(it->first) == CPythonSkill::SKILL_TYPE_ACTIVE)
#else
		if (bAll || __GetSkillType(it->first) == CPythonSkill::SKILL_TYPE_ACTIVE)
#endif
			it = m_skillSlotDict.erase(it);
		else
			++it;
	}

	for (int i = 0; i < SKILL_MAX_NUM; ++i)
	{
#ifdef FIX_REFRESH_SKILL_COOLDOWN
		DWORD dwSkillIndex = m_playerStatus.aSkill[i].dwIndex;
		CPythonSkill::TSkillData* pSkillData = NULL;

		// Skip empty slots or unknown skills
		if (dwSkillIndex == 0 || !CPythonSkill::Instance().GetSkillData(dwSkillIndex, &pSkillData))
			continue;

		if (pSkillData->byType != CPythonSkill::SKILL_TYPE_ACTIVE)
			continue;
#endif
		ZeroMemory(&m_playerStatus.aSkill[i], sizeof(TSkillInstance));
	}

	for (int j = 0; j < SKILL_MAX_NUM; ++j)
	{
#ifdef FIX_REFRESH_SKILL_COOLDOWN
		DWORD dwSkillIndex = m_playerStatus.aSkill[j].dwIndex;
		CPythonSkill::TSkillData* pSkillData = NULL;

		// Skip empty slots or unknown skills
		if (dwSkillIndex == 0 || !CPythonSkill::Instance().GetSkillData(dwSkillIndex, &pSkillData))
			continue;

		// Only operate on active-type skills
		if (pSkillData->byType != CPythonSkill::SKILL_TYPE_ACTIVE)
			continue;
#endif

		// 2004.09.30.myevan.스킬갱신시 스킬 포인트업[+] 버튼이 안나와 처리
		m_playerStatus.aSkill[j].iGrade = 0;
		m_playerStatus.aSkill[j].fcurEfficientPercentage = 0.0f;
		m_playerStatus.aSkill[j].fnextEfficientPercentage = 0.05f;

#ifdef FIX_REFRESH_SKILL_COOLDOWN
		m_playerStatus.aSkill[j].isCoolTime = false;
		m_playerStatus.aSkill[j].fCoolTime = 0.0f;
		m_playerStatus.aSkill[j].fLastUsedTime = 0.0f;

		//ResetSkillCoolTimeForSlot(j);
		UI::CWindowManager::Instance().ClearStoredSlotCoolTimeInAllSlotWindows(CPythonSkill::SKILL_TYPE_ACTIVE, j);
#endif
	}

	if (m_ppyGameWindow)
		PyCallClassMemberFunc(m_ppyGameWindow, "BINARY_CheckGameButton", Py_BuildNone());
}

 

 

UserInterface/PythonApplicationModule.cpp

Spoiler
// Add these all the way at the bottom of void initapp(), at the end of the file
#ifdef FIX_REFRESH_SKILL_COOLDOWN
	PyModule_AddIntConstant(poModule, "FIX_REFRESH_SKILL_COOLDOWN", 1);
#else
	PyModule_AddIntConstant(poModule, "FIX_REFRESH_SKILL_COOLDOWN", 0);
#endif

#ifdef FIX_HORSE_SKILLS_TAB
	PyModule_AddIntConstant(poModule, "FIX_HORSE_SKILLS_TAB", 1);
#else
	PyModule_AddIntConstant(poModule, "FIX_HORSE_SKILLS_TAB", 0);
#endif

 

 

Make sure you #include "GameType.h" at packet.h!

 

Python

root/game.py

Spoiler
# Add this at the bottom of the file
	if app.FIX_REFRESH_SKILL_COOLDOWN:
		def	SkillClearCoolTime(self, slotIndex):
			self.interface.SkillClearCoolTime(slotIndex)

 

 

root/interfaceModule.py

Spoiler
# Add this below def OnChangeCurrentSkill(self, skillSlotNumber):
if app.FIX_REFRESH_SKILL_COOLDOWN:
		def	SkillClearCoolTime(self, slotIndex):
			self.wndCharacter.SkillClearCoolTime(slotIndex)
			self.wndTaskBar.SkillClearCoolTime(slotIndex)

 

 

root/ui.py

Spoiler
# Add this below def SetSlotCoolTime(self, slotIndex, coolTime, elapsedTime = 0.0):
if app.FIX_REFRESH_SKILL_COOLDOWN:
		def StoreSlotCoolTime(self, key, slotIndex, coolTime, elapsedTime = 0.0):
			wndMgr.StoreSlotCoolTime(self.hWnd, key, slotIndex, coolTime, elapsedTime)
		
		def RestoreSlotCoolTime(self, key):
			wndMgr.RestoreSlotCoolTime(self.hWnd, key)

		def TransferSlotCoolTime(self, slotIndex1, slotIndex2):
			wndMgr.TransferSlotCoolTime(self.hWnd, slotIndex1, slotIndex2)

		def ClearSlotCoolTime(self, slotIndex):
			wndMgr.ClearSlotCoolTime(self.hWnd, slotIndex)

 

 

root/uiCharacter.py

Spoiler
# Find
	PAGE_HORSE = 2

#Modify like this:
	if app.FIX_HORSE_SKILLS_TAB:
		PAGE_HORSE = 3
	else:
		PAGE_HORSE = 2

#Find
		self.skillGroupButton2 = None

# Add below
		if app.FIX_HORSE_SKILLS_TAB:
			self.skillGroupButton3 = None
        
# Find
		self.skillGroupButton2 = self.GetChild("Skill_Group_Button_2")

# Add below
		if app.FIX_HORSE_SKILLS_TAB:
			self.skillGroupButton3 = self.GetChild("Skill_Group_Button_3")
      
# Find
		self.skillGroupButton = (
			self.GetChild("Skill_Group_Button_1"),
			self.GetChild("Skill_Group_Button_2"),
		)
  
# Modify like this
		self.skillGroupButton = (
			self.GetChild("Skill_Group_Button_1"),
			self.GetChild("Skill_Group_Button_2"),
		) + ((self.GetChild("Skill_Group_Button_3"),) if app.FIX_HORSE_SKILLS_TAB else ())
        
# Find and modify the entire def __RefreshSkillPage(self, name, slotCount) like this:
	## Skill Process
	def __RefreshSkillPage(self, name, slotCount):
		global SHOW_LIMIT_SUPPORT_SKILL_LIST

		skillPage = self.skillPageDict[name]
		startSlotIndex = skillPage.GetStartIndex()

		if "ACTIVE" == name:
			if self.PAGE_HORSE == self.curSelectedSkillGroup:
				startSlotIndex += slotCount

		getSkillType = skill.GetSkillType
		getSkillIndex = player.GetSkillIndex
		getSkillGrade = player.GetSkillGrade
		getSkillLevel = player.GetSkillLevel
		getSkillLevelUpPoint = skill.GetSkillLevelUpPoint
		getSkillMaxLevel = skill.GetSkillMaxLevel

		for i in xrange(slotCount + 1):
			slotIndex = i + startSlotIndex
			skillIndex = getSkillIndex(slotIndex)

			for j in xrange(skill.SKILL_GRADE_COUNT):
				skillPage.ClearSlot(self.__GetRealSkillSlot(j, i))

			if 0 == skillIndex:
				continue

			skillGrade = getSkillGrade(slotIndex)
			skillLevel = getSkillLevel(slotIndex)
			skillType = getSkillType(skillIndex)

			## 승마 스킬 예외 처리
			if player.SKILL_INDEX_RIDING == skillIndex:
				if 1 == skillGrade:
					skillLevel += 19
				elif 2 == skillGrade:
					skillLevel += 29
				elif 3 == skillGrade:
					skillLevel = 40

				skillPage.SetSkillSlotNew(slotIndex, skillIndex, max(skillLevel - 1, 0), skillLevel)
				skillPage.SetSlotCount(slotIndex, skillLevel)

			## ACTIVE
			elif skill.SKILL_TYPE_ACTIVE == skillType:
				for j in xrange(skill.SKILL_GRADE_COUNT):
					realSlotIndex = self.__GetRealSkillSlot(j, slotIndex)

					skillPage.SetSkillSlotNew(realSlotIndex, skillIndex, j, skillLevel)
					skillPage.SetCoverButton(realSlotIndex)

					if (skillGrade == skill.SKILL_GRADE_COUNT) and j == (skill.SKILL_GRADE_COUNT - 1):
						skillPage.SetSlotCountNew(realSlotIndex, skillGrade, skillLevel)
					elif (not self.__CanUseSkillNow()) or (skillGrade != j):
						skillPage.SetSlotCount(realSlotIndex, 0)
						skillPage.DisableCoverButton(realSlotIndex)

						if app.FIX_REFRESH_SKILL_COOLDOWN:
							if not player.IsSkillActive(slotIndex):
								skillPage.DeactivateSlot(realSlotIndex)

							if player.IsSkillCoolTime(slotIndex) and skillGrade != j:
								skillPage.TransferSlotCoolTime(realSlotIndex, self.__GetRealSkillSlot(skillGrade, i))
							else:
								self.SkillClearCoolTime(realSlotIndex)
					else:
						skillPage.SetSlotCountNew(realSlotIndex, skillGrade, skillLevel)

					if app.FIX_REFRESH_SKILL_COOLDOWN:
						if player.IsSkillActive(slotIndex) and (skillGrade == j):
							skillPage.ActivateSlot(realSlotIndex)
			else:
				if not SHOW_LIMIT_SUPPORT_SKILL_LIST or skillIndex in SHOW_LIMIT_SUPPORT_SKILL_LIST:
					realSlotIndex = self.__GetETCSkillRealSlotIndex(slotIndex)

					skillPage.SetSkillSlot(realSlotIndex, skillIndex, skillLevel)
					skillPage.SetSlotCountNew(realSlotIndex, skillGrade, skillLevel)

					if skill.CanUseSkill(skillIndex):
						skillPage.SetCoverButton(realSlotIndex)

					if app.FIX_REFRESH_SKILL_COOLDOWN:
						if player.IsSkillCoolTime(slotIndex) and skillGrade != j:
							skillPage.TransferSlotCoolTime(realSlotIndex, self.__GetRealSkillSlot(skillGrade, i))

						if player.IsSkillActive(slotIndex) and (skillGrade == j or (skillGrade >= skill.SKILL_GRADE_COUNT) and j == (skill.SKILL_GRADE_COUNT - 1)):
							skillPage.ActivateSlot(realSlotIndex)
						else:
							skillPage.DeactivateSlot(realSlotIndex)
					else:
						if not player.IsSkillActive(slotIndex):
							skillPage.DeactivateSlot(realSlotIndex)

			skillPage.RefreshSlot()

		if app.FIX_REFRESH_SKILL_COOLDOWN:
			self.__RestoreSlotCoolTime(skillPage)

# Add this right after the end of that def:
	if app.FIX_REFRESH_SKILL_COOLDOWN:
		def __RestoreSlotCoolTime(self, skillPage):
			restoreType = skill.SKILL_TYPE_NONE

			if self.PAGE_HORSE == self.curSelectedSkillGroup:
				restoreType = skill.SKILL_TYPE_HORSE
			else:
				restoreType = skill.SKILL_TYPE_ACTIVE

			skillPage.RestoreSlotCoolTime(restoreType)

# Find def OnUseSkill(self, slotIndex, coolTime): and modify the FOR loop like this:
		for slotWindow in self.skillPageDict.values():
			if slotWindow.HasSlot(slotIndex):
				if app.FIX_REFRESH_SKILL_COOLDOWN:
					slotWindow.StoreSlotCoolTime(skillType, slotIndex, coolTime)
					self.__RestoreSlotCoolTime(slotWindow)
				else:
					slotWindow.SetSlotCoolTime(slotIndex, coolTime)

				return

# Add this below def __ShowJobToolTip(self):
	if app.FIX_REFRESH_SKILL_COOLDOWN:
		def SkillClearCoolTime(self, slotIndex):
			slotIndex = self.__GetRealSkillSlot(player.GetSkillGrade(slotIndex), slotIndex)

			for slotWindow in self.skillPageDict.values():
				if slotWindow.HasSlot(slotIndex):
					slotWindow.SetSlotCoolTime(slotIndex, 0)
                    
# Find and modify the entire def __SetSkillGroupName(self, race, group): like this:
	def __SetSkillGroupName(self, race, group):
		job = chr.RaceToJob(race)

		if not self.SKILL_GROUP_NAME_DICT.has_key(job):
			return

		nameList = self.SKILL_GROUP_NAME_DICT[job]

		if 0 == group:
			self.skillGroupButton1.SetText(nameList[1])
			self.skillGroupButton2.SetText(nameList[2])

			if app.FIX_HORSE_SKILLS_TAB:
				if self.__CanUseHorseSkill():
					self.skillGroupButton3.SetText(localeInfo.SKILL_GROUP_HORSE)
					self.skillGroupButton3.Show()
					self.skillGroupButton3.SetPosition(95, 2)
				else:
					self.skillGroupButton3.Hide()

			self.skillGroupButton1.Show()
			self.skillGroupButton2.Show()
			self.activeSkillGroupName.Hide()
		else:
			if self.__CanUseHorseSkill():
				self.activeSkillGroupName.Hide()
				self.skillGroupButton1.SetText(nameList.get(group, "Noname"))

				if app.FIX_HORSE_SKILLS_TAB:
					self.skillGroupButton3.SetText(localeInfo.SKILL_GROUP_HORSE)
					self.skillGroupButton1.Show()
					self.skillGroupButton2.Hide()
					self.skillGroupButton3.Show()
					self.skillGroupButton3.SetPosition(50, 2)
				else:
					self.skillGroupButton2.SetText(localeInfo.SKILL_GROUP_HORSE)
					self.skillGroupButton1.Show()
					self.skillGroupButton2.Show()
			else:
				self.activeSkillGroupName.SetText(nameList.get(group, "Noname"))
				self.activeSkillGroupName.Show()
				self.skillGroupButton1.Hide()
				self.skillGroupButton2.Hide()

				if app.FIX_HORSE_SKILLS_TAB:
					self.skillGroupButton3.Hide()

# Find and modify the entire def __SelectSkillGroup(self, index): like this:
	if app.FIX_HORSE_SKILLS_TAB:
		def __SelectSkillGroup(self, pageIndex, (PAGE_SKILL_1, PAGE_SKILL_2, PAGE_SKILL_HORSE) = range(3)):
			for pageButton in self.skillGroupButton:
				pageButton.SetUp()

			self.skillGroupButton[pageIndex].Down()

			if pageIndex in (PAGE_SKILL_1, PAGE_SKILL_2):
				skillGroupIndex = net.GetMainActorSkillGroup()

				if bool(skillGroupIndex):
					(tmpCurSkillGroup, tmpSkillGroup) = (skillGroupIndex - 1, skillGroupIndex)
				else:
					(tmpCurSkillGroup, tmpSkillGroup) = (pageIndex, pageIndex + 1)

				self.curSelectedSkillGroup = tmpCurSkillGroup
				self.__SetSkillSlotData(net.GetMainActorRace(), tmpSkillGroup, net.GetMainActorEmpire())
			elif pageIndex == PAGE_SKILL_HORSE and self.__CanUseHorseSkill():
				self.curSelectedSkillGroup = self.PAGE_HORSE

			self.RefreshSkill()
	else:
		def __SelectSkillGroup(self, index):
			for btn in self.skillGroupButton:
				btn.SetUp()

			self.skillGroupButton[index].Down()

			if self.__CanUseHorseSkill():
				if 0 == index:
					index = net.GetMainActorSkillGroup() - 1
				elif 1 == index:
					index = self.PAGE_HORSE

			self.curSelectedSkillGroup = index
			self.__SetSkillSlotData(net.GetMainActorRace(), index + 1, net.GetMainActorEmpire())

 

 

root/uiTaskbar.py

Spoiler
# Add this below def RefreshSkill(self):
	if app.FIX_REFRESH_SKILL_COOLDOWN:
		def SkillClearCoolTime(self, usedSlotIndex):
			QUICK_SLOT_SLOT_COUNT = 4
			slotIndex = 0

			for slotWindow in self.quickslot:
				for i in xrange(QUICK_SLOT_SLOT_COUNT):
					(Type, Position) = player.GetLocalQuickSlot(slotIndex)

					if Type == player.SLOT_TYPE_SKILL:
						if usedSlotIndex == Position:
							slotWindow.SetSlotCoolTime(slotIndex, 0)

							return

					slotIndex += 1

# In def RefreshQuickSlot(self): find this line (arg names might differ, located towards the end of the def):
							slot.SetSlotCoolTime(slotNumber, coolTime, elapsedTime)

# And modify it like this:
					if app.FIX_REFRESH_SKILL_COOLDOWN:
						if player.IsSkillCoolTime(Position) and skillLevel > 0:
							(coolTime, elapsedTime) = player.GetSkillCoolTime(Position)

							slot.SetSlotCoolTime(slotNumber, coolTime, elapsedTime)
						else:
							if skillType != skill.SKILL_TYPE_GUILD and skillLevel <= 0:
								slot.SetSlotCoolTime(slotNumber, 0, 0)
					else:
						if player.IsSkillCoolTime(Position):
							(coolTime, elapsedTime) = player.GetSkillCoolTime(Position)

							slot.SetSlotCoolTime(slotNumber, coolTime, elapsedTime)

# Right after that, find:
					if player.IsSkillActive(Position):
						slot.ActivateSlot(slotNumber)
      
# Add this else statement below it:
					else:
						if app.FIX_REFRESH_SKILL_COOLDOWN:
							slot.DeactivateSlot(slotNumber)

 

 

uiScript/characterWindow.py (some may have it as "characterDetails.py")

Spoiler
# Find
								{
									"name" : "Skill_Group_Button_2",
									"type" : "radio_button",

									"x" : 50,
									"y" : 2,

									"text" : "Group2",
									"text_color" : 0xFFFFE3AD,

									"default_image" : "d:/ymir work/ui/game/windows/skill_tab_button_01.sub",
									"over_image" : "d:/ymir work/ui/game/windows/skill_tab_button_02.sub",
									"down_image" : "d:/ymir work/ui/game/windows/skill_tab_button_03.sub",
								},
  
# Add below
##horse skill page
								{
									"name" : "Skill_Group_Button_3",
									"type" : "radio_button",

									"x" : 95,
									"y" : 2,

									"text" : "Group3",
									"text_color" : 0xFFFFE3AD,

									"default_image" : "d:/ymir work/ui/game/windows/skill_tab_button_01.sub",
									"over_image" : "d:/ymir work/ui/game/windows/skill_tab_button_02.sub",
									"down_image" : "d:/ymir work/ui/game/windows/skill_tab_button_03.sub",
								},

 

 

Warning: These instructions are meant to be followed in a clean source/client. If you have modifications in place, check compatibility before applying!

 

I have tested the results with these changes and they are pretty wild:

  • The cooldown and toggle effect is being transfered when the skill is upgraded and even changing grades. It works even with /setsk commands from 1 to P!
  • Changing pages does not remove cooldown or toggle effects.
  • Mounting/unmounting does not remove cooldown or toggle effects.
  • Horse page is visible and responsive without an assigned skill group.
  • Cooldown and toggle effect is cleared upon single skill reset.
  • Toggle effect is cleared upon stats reset.
  • Affect from togglable skills are removed upon single skill or stats reset.
  • Multiple togglable skills with cooldowns can work together seamlessly at the same time without issues.
  • Passive skills do not visually zero when changing skill group.

 

⚠️ ISSUES NOT YET FIXED:

  • On stats reset only, skill cooldown and toggle affect don't work when character adds points/changes skill level after using it, if the character has not relogged after selecting a new skill group! If the character relogs everything works fine.
  • On stats reset only, the skill does not lose cooldown if the skill used is M1+! Works normally for grade 1 skills.
  • When login with no skill group assigned, the character has all passive skills at 0
  • The combo passive skill (121) does not have a toggle effect in the character window but the effect is visible at the taskbar!
  • Points added to a skill are not visible if I switch tabs before selecting a skill group and I don't relog first. After relog the applied and new points are visible as they should. This does not happen if I don't switch skill group tabs in the skill window after I reset all my skills (stat reset).
  • I haven't managed to make the quest for training appear without a relog without issues. I tried the obvious
    set_quest_state("skill_group","run")

    in reset_scroll.quest but when I do that there are no arrows above the Teachers, the character can talk to all the Teachers even the ones that are not meant for their class, the character can talk to any Teacher even after selecting a group, and some quest scrolls may disappear. This does not happen in NPC 9006 which works fine, but it happens with stat reset items such as 71002. I have the "standard" skill_group.quest:

    Spoiler
    quest skill_group begin
    	state start begin
    		when login or levelup with pc.level >= 5 and pc.skillgroup == 0 begin
    			set_state(run)
    		end
    	end
    
    	state run begin
    		when login with pc.get_skill_group() != 0 begin
    			restart_quest()
    		end
    
    		when letter with pc.get_skill_group() == 0 begin
    
    			send_letter(gameforge.skill_group._10_sendLetter)
    			local job=pc.get_job()
    
    			if job==0 then
    				local v=pc_find_skill_teacher_vid(1)
    				if 0!=v then target.vid("teacher1", v, gameforge.skill_group._20_targetVid) end
    				local v=pc_find_skill_teacher_vid(2)
    				if 0!=v then target.vid("teacher2", v, gameforge.skill_group._30_targetVid) end
    			elseif job==1 then
    				local v=pc_find_skill_teacher_vid(1)
    				if 0!=v then target.vid("teacher3", v, gameforge.skill_group._40_targetVid) end
    				local v=pc_find_skill_teacher_vid(2)
    				if 0!=v then target.vid("teacher4", v, gameforge.skill_group._50_targetVid) end
    			elseif job==2 then
    				local v=pc_find_skill_teacher_vid(1)
    				if 0!=v then target.vid("teacher5", v, gameforge.skill_group._60_targetVid) end
    				local v=pc_find_skill_teacher_vid(2)
    				if 0!=v then target.vid("teacher6", v, gameforge.skill_group._70_targetVid) end
    			elseif job==3 then
    				local v=pc_find_skill_teacher_vid(1)
    				if 0!=v then target.vid("teacher7", v, gameforge.skill_group._80_targetVid) end
    				local v=pc_find_skill_teacher_vid(2)
    				if 0!=v then target.vid("teacher8", v, gameforge.skill_group._90_targetVid) end
    			clear_letter()
    			end
    		end
    
    		when leave begin
    			target.delete("teacher1")
    			target.delete("teacher2")
    			target.delete("teacher3")
    			target.delete("teacher4")
    			target.delete("teacher5")
    			target.delete("teacher6")
    			target.delete("teacher7")
    			target.delete("teacher8")
    		end
    
    		when button or info begin
    			if pc.job == 0 then
    				say_title(gameforge.skill_group._100_sayTitle)
    				say(gameforge.skill_group._110_sayReward)
    			elseif pc.job == 1 then
    				say_title(gameforge.skill_group._120_sayTitle)
    				say(gameforge.skill_group._130_sayReward)
    			elseif pc.job == 2 then
    				say_title(gameforge.skill_group._140_sayTitle)
    				say(gameforge.skill_group._150_sayReward)
    			elseif pc.job == 3 then
    				say_title(gameforge.skill_group._160_sayTitle)
    				say(gameforge.skill_group._170_sayReward)
    			end
    
    			 select(gameforge.locale.confirm)
    			setskin(NOWINDOW)
    		end
    
    		when teacher1.target.click or skill_group1_1.chat.gameforge.skill_group._180_npcChat or	skill_group2_1.chat.gameforge.skill_group._180_npcChat or	skill_group3_1.chat.gameforge.skill_group._180_npcChat 	begin 
    			target.delete("teacher1")
    
    			local pc_job=pc.get_job()
    			if pc_job != 0 then -- !!!
    				say_title(gameforge.skill_group._190_sayTitle)
    
    				say(gameforge.skill_group._200_say)
    				if pc_job==0 then
    					say_reward(gameforge.skill_group._210_sayReward)
    				elseif pc_job==1 then
    					say_reward(gameforge.skill_group._220_sayReward)
    				elseif pc_job==2 then
    					say_reward(gameforge.skill_group._230_sayReward)
    				elseif pc_job==3 then
    					say_reward(gameforge.skill_group._240_sayReward)
    				end
    								return
    			end
    
    			say_title(gameforge.skill_group._250_sayTitle)
    			say(gameforge.skill_group._260_say)
    			say_reward(gameforge.skill_group._270_sayReward)
    
    
    						local s= select(gameforge.skill_group._280_select, gameforge.skill_group._290_select)
    
    			if 2 == s then
    				return
    			end
    
    			if pc.get_job()!=0 or pc.get_skill_group()!=0 then
    				test_chat(string.format(gameforge.skill_group._300_chat, bool_to_str(npc_is_same_job()).."pc.get_skill_group:"..pc.get_skill_group().."pc_job:"..pc.get_job()))
    				return
    			end
    
    			set_state("start")
    			pc.set_skill_group(1) -- !!!
    			pc.clear_skill()
    			char_log(0, "CLEAR_SKILL", "Fertigkeit nach Lehrer löschen")
    
    			say_title(gameforge.skill_group._310_sayTitle)
    
    			say(gameforge.skill_group._320_say)
    			if not pc.is_clear_skill_group() then
    				say(gameforge.skill_group._330_say)
    				say_title(gameforge.buy_fishrod._210_sayTitle)
    				say_reward(gameforge.skill_group._340_sayReward)
    			else
    				say_reward(gameforge.skill_group._350_sayReward)
    			end
    			clear_letter()
    		end
    
    		when teacher2.target.click or skill_group1_2.chat.gameforge.skill_group._360_npcChat or	skill_group2_2.chat.gameforge.skill_group._360_npcChat or	skill_group3_2.chat.gameforge.skill_group._360_npcChat 	begin 
    			target.delete("teacher2")
    
    			local pc_job=pc.get_job()
    			if pc_job != 0 then
    				say_title(gameforge.skill_group._370_sayTitle)
    
    				say(gameforge.skill_group._380_say)
    				if pc_job==0 then
    					say_reward(gameforge.skill_group._210_sayReward)
    				elseif pc_job==1 then
    					say_reward(gameforge.skill_group._390_sayReward)
    				elseif pc_job==2 then
    					say_reward(gameforge.skill_group._230_sayReward)
    				elseif pc_job==3 then
    					say_reward(gameforge.skill_group._240_sayReward)
    				end
    								return
    			end
    
    			say_title(gameforge.skill_group._400_sayTitle)
    			say(gameforge.skill_group._410_say)
    			say_reward(gameforge.skill_group._270_sayReward)
    
    						local s= select(gameforge.skill_group._280_select, gameforge.skill_group._290_select)
    
    			if 2 == s then
    				return
    			end
    
    			if pc.get_job()!=0 or pc.get_skill_group()!=0 then
    				test_chat(string.format(gameforge.skill_group._300_chat, bool_to_str(npc_is_same_job()).."pc.get_skill_group:"..pc.get_skill_group().."pc_job:"..pc.get_job()))
    				return
    			end
    
    			set_state("start")
    			pc.set_skill_group(2)
    			pc.clear_skill()
    			char_log(0, "CLEAR_SKILL", "Fertigkeit nach Fertigkeitsmeister löschen")
    
    
    			say_title(gameforge.skill_group._420_sayTitle)
    
    			say(gameforge.skill_group._320_say)
    			if not pc.is_clear_skill_group() then
    				say(gameforge.skill_group._330_say)
    				say_title(gameforge.buy_fishrod._210_sayTitle)
    				say_reward(gameforge.skill_group._340_sayReward)
    			else
    				say_reward(gameforge.skill_group._350_sayReward)
    			end
    			clear_letter()
    		end
    
    
    		when teacher3.target.click or skill_group1_3.chat.gameforge.skill_group._430_npcChat or	skill_group2_3.chat.gameforge.skill_group._430_npcChat or	skill_group3_3.chat.gameforge.skill_group._430_npcChat	begin 
    			target.delete("teacher3")
    
    
    			local pc_job=pc.get_job()
    			if pc_job != 1 then -- !!!
    				say_title(gameforge.skill_group._190_sayTitle)
    
    				say(gameforge.skill_group._440_say)
    				if pc_job==0 then
    					say_reward(gameforge.skill_group._210_sayReward)
    				elseif pc_job==1 then
    					say_reward(gameforge.skill_group._220_sayReward)
    				elseif pc_job==2 then
    					say_reward(gameforge.skill_group._230_sayReward)
    				elseif pc_job==3 then
    					say_reward(gameforge.skill_group._240_sayReward)
    				end
    								return
    			end
    			say_title(gameforge.skill_group._450_sayTitle)
    			say(gameforge.skill_group._460_say)
    			say_reward(gameforge.skill_group._470_sayReward)
    
    						local s= select(gameforge.skill_group._280_select, gameforge.skill_group._290_select)
    
    			if 2 == s then
    				return
    			end
    
    			if pc.get_job()!=1 or pc.get_skill_group()!=0 then
    				test_chat(string.format(gameforge.skill_group._300_chat, bool_to_str(npc_is_same_job()).."pc.get_skill_group:"..pc.get_skill_group().."pc_job:"..pc.get_job()))
    				return
    			end
    
    			set_state("start")
    			pc.set_skill_group(1) -- !!!
    			pc.clear_skill()
    			char_log(0, "CLEAR_SKILL", "Fertigkeit nach Fertigkeitsmeister löschen")
    
    			say_title(gameforge.skill_group._480_sayTitle)
    
    			say(gameforge.skill_group._320_say)
    			if not pc.is_clear_skill_group() then
    				say(gameforge.skill_group._490_say)
    				say_title(gameforge.buy_fishrod._210_sayTitle)
    				say_reward(gameforge.skill_group._340_sayReward)
    			else
    				say_reward(gameforge.skill_group._350_sayReward)
    			end
    			clear_letter()
    		end
    
    
    		when teacher4.target.click or skill_group1_4.chat.gameforge.skill_group._500_npcChat or	skill_group2_4.chat.gameforge.skill_group._500_npcChat or	skill_group3_4.chat.gameforge.skill_group._500_npcChat	begin 
    			target.delete("teacher4")
    
    			local pc_job=pc.get_job()
    			if pc_job != 1 then -- !!!
    				say_title(gameforge.skill_group._190_sayTitle)
    
    				say(gameforge.skill_group._510_say)
    				if pc_job==0 then
    					say_reward(gameforge.skill_group._210_sayReward)
    				elseif pc_job==1 then
    					say_reward(gameforge.skill_group._220_sayReward)
    				elseif pc_job==2 then
    					say_reward(gameforge.skill_group._230_sayReward)
    				elseif pc_job==3 then
    					say_reward(gameforge.skill_group._240_sayReward)
    				end
    								return
    			end
    
    			say_title(gameforge.skill_group._520_sayTitle)
    			say(gameforge.skill_group._530_say)
    			say_reward(gameforge.skill_group._470_sayReward)
    			local s= select(gameforge.skill_group._280_select, gameforge.skill_group._290_select)
    			if 2 == s then
    				return
    			end
    
    			if pc.get_job()!=1 or pc.get_skill_group()!=0 then
    				test_chat(string.format(gameforge.skill_group._300_chat, bool_to_str(npc_is_same_job()).."pc.get_skill_group:"..pc.get_skill_group().."pc_job:"..pc.get_job()))
    				return
    			end
    
    			set_state("start")
    			pc.set_skill_group(2) -- !!!
    			pc.clear_skill()
    			char_log(0, "CLEAR_SKILL", "Fertigkeit nach Fertigkeitsmeister löschen")
    
    			say_title(gameforge.skill_group._540_sayTitle)
    
    			say(gameforge.skill_group._320_say)
    			if not pc.is_clear_skill_group() then
    				say(gameforge.skill_group._330_say)
    				say_title(gameforge.buy_fishrod._210_sayTitle)
    				say_reward(gameforge.skill_group._340_sayReward)
    			else
    				say_reward(gameforge.skill_group._350_sayReward)
    			end
    			clear_letter()
    		end
    
    
    		when teacher5.target.click or skill_group1_5.chat.gameforge.skill_group._550_npcChat or	skill_group2_5.chat.gameforge.skill_group._550_npcChat or	skill_group3_5.chat.gameforge.skill_group._550_npcChat	begin 
    			target.delete("teacher5")
    
    			local pc_job=pc.get_job()
    			if pc_job != 2 then -- !!!
    				say_title(gameforge.skill_group._370_sayTitle)
    
    				say(gameforge.skill_group._560_say)
    				if pc_job==0 then
    					say_reward(gameforge.skill_group._210_sayReward)
    				elseif pc_job==1 then
    					say_reward(gameforge.skill_group._220_sayReward)
    				elseif pc_job==2 then
    					say_reward(gameforge.skill_group._230_sayReward)
    				elseif pc_job==3 then
    					say_reward(gameforge.skill_group._240_sayReward)
    				end
    				return
    			end
    
    			say_title(gameforge.skill_group._570_sayTitle)
    			say(gameforge.skill_group._580_say)
    			say_reward(gameforge.skill_group._590_sayReward)
    			local s= select(gameforge.skill_group._280_select, gameforge.skill_group._290_select)
    			if 2 == s then
    				return
    			end
    
    			if pc.get_job()!=2 or pc.get_skill_group()!=0 then
    				test_chat(string.format(gameforge.skill_group._300_chat, bool_to_str(npc_is_same_job()).."pc.get_skill_group:"..pc.get_skill_group().."pc_job:"..pc.get_job()))
    				return
    			end
    
    			set_state("start")
    			pc.set_skill_group(1) -- !!!
    			pc.clear_skill()
    			char_log(0, "CLEAR_SKILL", "Fertigkeit nach Fertigkeitsmeister löschen")
    
    
    			say_title(gameforge.skill_group._600_sayTitle)
    
    			say(gameforge.skill_group._320_say)
    			if not pc.is_clear_skill_group() then
    				           ---                                                   l
    				say(gameforge.skill_group._330_say)
    				say_title(gameforge.buy_fishrod._210_sayTitle)
    								say_reward(gameforge.skill_group._340_sayReward)
    							else
    				say_reward(gameforge.skill_group._350_sayReward)
    							end
    			clear_letter()
    		end
    
    
    		when teacher6.target.click or skill_group1_6.chat.gameforge.skill_group._610_npcChat or	skill_group2_6.chat.gameforge.skill_group._610_npcChat or	skill_group3_6.chat.gameforge.skill_group._610_npcChat	begin 
    			target.delete("teacher6")
    
    			local pc_job=pc.get_job()
    			if pc_job != 2 then -- !!!
    				say_title(gameforge.skill_group._190_sayTitle)
    
    				say(gameforge.skill_group._620_say)
    				if pc_job==0 then
    					say_reward(gameforge.skill_group._210_sayReward)
    				elseif pc_job==1 then
    					say_reward(gameforge.skill_group._220_sayReward)
    				elseif pc_job==2 then
    					say_reward(gameforge.skill_group._230_sayReward)
    				elseif pc_job==3 then
    					say_reward(gameforge.skill_group._240_sayReward)
    				end
    								return
    			end
    
    			say_title(gameforge.skill_group._630_sayTitle)
    			say(gameforge.skill_group._640_say)
    			say_reward(gameforge.skill_group._590_sayReward)
    
    						local s= select(gameforge.skill_group._280_select, gameforge.skill_group._290_select)
    
    			if 2 == s then
    				return
    			end
    
    			if pc.get_job()!=2 or pc.get_skill_group()!=0 then
    				test_chat(string.format(gameforge.skill_group._300_chat, bool_to_str(npc_is_same_job()).."pc.get_skill_group:"..pc.get_skill_group().."pc_job:"..pc.get_job()))
    				return
    			end
    
    			set_state("start")
    			pc.set_skill_group(2) -- !!!
    			pc.clear_skill()
    			char_log(0, "CLEAR_SKILL", "Fertigkeit nach Fertigkeitsmeister löschen")
    
    			say_title(gameforge.skill_group._650_sayTitle)
    
    			say(gameforge.skill_group._320_say)
    			if not pc.is_clear_skill_group() then
    				           ---                                                   l
    				say(gameforge.skill_group._330_say)
    				say_title(gameforge.buy_fishrod._210_sayTitle)
    								say_reward(gameforge.skill_group._340_sayReward)
    							else
    				say_reward(gameforge.skill_group._350_sayReward)
    							end
    			clear_letter()
    		end
    
    
    		when teacher7.target.click or skill_group1_7.chat.gameforge.skill_group._660_npcChat or	skill_group2_7.chat.gameforge.skill_group._660_npcChat or	skill_group3_7.chat.gameforge.skill_group._660_npcChat	begin 
    			target.delete("teacher7")
    
    			local pc_job=pc.get_job()
    			if pc_job != 3 then -- !!!
    				say_title(gameforge.skill_group._190_sayTitle)
    
    				say(gameforge.skill_group._670_say)
    				if pc_job==0 then
    					say_reward(gameforge.skill_group._210_sayReward)
    				elseif pc_job==1 then
    					say_reward(gameforge.skill_group._220_sayReward)
    				elseif pc_job==2 then
    					say_reward(gameforge.skill_group._230_sayReward)
    				elseif pc_job==3 then
    					say_reward(gameforge.skill_group._240_sayReward)
    				end
    								return
    			end
    
    			say_title(gameforge.skill_group._680_sayTitle)
    			say(gameforge.skill_group._690_say)
    			say_reward(gameforge.skill_group._700_sayReward)
    
    						local s= select(gameforge.skill_group._280_select, gameforge.skill_group._290_select)
    
    			if 2 == s then
    				return
    			end
    
    			if pc.get_job()!=3 or pc.get_skill_group()!=0 then
    				test_chat(string.format(gameforge.skill_group._300_chat, bool_to_str(npc_is_same_job()).." pc.get_skill_group:"..pc.get_skill_group().."pc_job:"..pc.get_job()))
    				return
    			end
    
    			set_state("start")
    			pc.set_skill_group(1) -- !!!
    			pc.clear_skill()
    			char_log(0, "CLEAR_SKILL", "Fertigkeit nach Fertigkeitsmeister löschen")
    
    			say_title(gameforge.skill_group._710_sayTitle)
    
    			say(gameforge.skill_group._320_say)
    			if not pc.is_clear_skill_group() then
    				           ---                                                   l
    				say(gameforge.skill_group._330_say)
    				say_title(gameforge.buy_fishrod._210_sayTitle)
    								say_reward(gameforge.skill_group._340_sayReward)
    							else
    				say_reward(gameforge.skill_group._350_sayReward)
    							end
    			clear_letter()
    		end
    
    
    		when teacher8.target.click or skill_group1_8.chat.gameforge.skill_group._720_npcChat or	skill_group2_8.chat.gameforge.skill_group._720_npcChat or	skill_group3_8.chat.gameforge.skill_group._720_npcChat	begin 
    			target.delete("teacher8")
    
    			local pc_job=pc.get_job()
    			if pc_job != 3 then -- !!!
    				say_title(gameforge.skill_group._190_sayTitle)
    
    				say(gameforge.skill_group._730_say)
    				if pc_job==0 then
    					say_reward(gameforge.skill_group._210_sayReward)
    				elseif pc_job==1 then
    					say_reward(gameforge.skill_group._220_sayReward)
    				elseif pc_job==2 then
    					say_reward(gameforge.skill_group._230_sayReward)
    				elseif pc_job==3 then
    					say_reward(gameforge.skill_group._240_sayReward)
    				end
    				return
    			end
    
    
    			say_title(gameforge.skill_group._740_sayTitle)
    			say(gameforge.skill_group._750_say)
    			say_reward(gameforge.skill_group._700_sayReward)
    			local s= select(gameforge.skill_group._280_select, gameforge.skill_group._290_select)
    			if 2 == s then
    				return
    			end
    
    			if pc.get_job()!=3 or pc.get_skill_group()!=0 then
    				test_chat(string.format(gameforge.skill_group._300_chat, bool_to_str(npc_is_same_job()).."pc.get_skill_group:"..pc.get_skill_group().."pc_job:"..pc.get_job()))
    				return
    			end
    
    			set_state("start")
    			pc.set_skill_group(2) -- !!!
    			pc.clear_skill()
    			char_log(0, "CLEAR_SKILL", "Fertigkeit nach Fertigkeitsmeister löschen")
    
    			say_title(gameforge.skill_group._760_sayTitle)
    
    			say(gameforge.skill_group._320_say)
    			if not pc.is_clear_skill_group() then
    				           ---                                                   l
    				say(gameforge.skill_group._330_say)
    				say_title(gameforge.buy_fishrod._210_sayTitle)
    								say_reward(gameforge.skill_group._340_sayReward)
    							else
    				say_reward(gameforge.skill_group._350_sayReward)
    							end
    			clear_letter()
    		end
    
    	end -- end_of_state: run
    end -- end_of_quest

     

Again a huge shoutout to all the people that created the initial posts about these fixes and everyone else that contributed. I simply gathered everything together, tested some things and now asking for your help in fixing all the bugs of this page.

Like I said I intend to release the full fix to the public once we have it with all credits to the contributors of this fix.

Thank you for all your help! 

  • Metin2 Dev 1
  • Love 2
Link to comment
https://metin2.dev/topic/34140-perfecting-the-skills-page-client-interface/
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.