From 466eb6398c53955b7cd0a579cce345234b206b04 Mon Sep 17 00:00:00 2001 From: plenarius Date: Sun, 8 Sep 2024 19:52:50 -0400 Subject: [PATCH 1/3] MaxLevel: use new plugin format --- Plugins/MaxLevel/MaxLevel.cpp | 172 ++++++++++++++++++---------------- Plugins/MaxLevel/MaxLevel.hpp | 33 ------- 2 files changed, 91 insertions(+), 114 deletions(-) delete mode 100644 Plugins/MaxLevel/MaxLevel.hpp diff --git a/Plugins/MaxLevel/MaxLevel.cpp b/Plugins/MaxLevel/MaxLevel.cpp index 4cf55aade4b..54878980840 100644 --- a/Plugins/MaxLevel/MaxLevel.cpp +++ b/Plugins/MaxLevel/MaxLevel.cpp @@ -1,4 +1,4 @@ -#include "MaxLevel.hpp" +#include "nwnx.hpp" #include "API/C2DA.hpp" #include "API/CTwoDimArrays.hpp" @@ -26,20 +26,6 @@ using namespace NWNXLib::API::Constants; const int CORE_MAX_LEVEL = 40; const int MAX_LEVEL_MAX = 60; -static MaxLevel::MaxLevel* g_plugin; - -NWNX_PLUGIN_ENTRY Plugin* PluginLoad(Services::ProxyServiceList* services) -{ - g_plugin = new MaxLevel::MaxLevel(services); - return g_plugin; -} - -namespace MaxLevel { - -using namespace NWNXLib; -using namespace NWNXLib::API; -using namespace NWNXLib::API::Constants; - static Hooks::Hook s_GetServerInfoFromIniFileHook; static Hooks::Hook s_LoadModuleStartHook; static Hooks::Hook s_LevelDownHook; @@ -53,80 +39,106 @@ static Hooks::Hook s_GetExpNeededForLevelUpHook; static Hooks::Hook s_GetSpellGainHook; static Hooks::Hook s_GetSpellsKnownPerLevelHook; +uint8_t s_maxLevel; + +std::unordered_map s_nExperienceTableAdded; +std::unordered_map>> s_nSpellGainTableAdded; +std::unordered_map>> s_nSpellKnownTableAdded; +std::unordered_map> s_nSpellLevelsPerLevelAdded; + +static void GetServerInfoFromIniFileHook(CServerExoAppInternal* pServerExoAppInternal); +static uint32_t LoadModuleStartHook(CNWSModule *pModule, CExoString sModuleName, int32_t bIsSaveGame, int32_t nSourceType, const NWSync::Advertisement* nwsyncModuleSourceAdvert); +static int32_t CanLevelUpHook(CNWSCreatureStats* pStats); +static uint32_t GetExpNeededForLevelUpHook(CNWSCreatureStats *pStats); +static void LevelDownHook(CNWSCreatureStats *pStats, CNWLevelStats *pLevelStats); +static void SummonAssociateHook(CNWSCreature *pCreature, CResRef cResRef, CExoString p_sAssociateName, uint16_t nAssociateType); +static void LoadSpellGainTableHook(CNWClass* pClass, CExoString sTable); +static void LoadSpellKnownTableHook(CNWClass* pClass, CExoString sTable); +static uint8_t GetSpellGainHook(CNWClass *pClass, uint8_t nLevel, uint8_t nSpellLevel); +static uint8_t GetSpellsKnownPerLevelHook(CNWClass *pClass, uint8_t nLevel, uint8_t nSpellLevel, uint8_t nClass, uint16_t nRace, uint8_t nCHABase); + +void MaxLevel() __attribute__((constructor)); +void MaxLevel() +{ + s_maxLevel = static_cast(std::clamp(Config::Get("MAX", (uint8_t)CORE_MAX_LEVEL), 0, MAX_LEVEL_MAX)); + if (s_maxLevel > CORE_MAX_LEVEL) + { + LOG_INFO("Max Level increased to %d.", s_maxLevel); + s_GetServerInfoFromIniFileHook = Hooks::HookFunction( + &CServerExoAppInternal::GetServerInfoFromIniFile, &GetServerInfoFromIniFileHook, Hooks::Order::Early); + s_LoadModuleStartHook = Hooks::HookFunction( + &CNWSModule::LoadModuleStart, + (void*)&LoadModuleStartHook, Hooks::Order::Early); -MaxLevel::MaxLevel(Services::ProxyServiceList* services) - : Plugin(services) -{ - m_maxLevel = Config::Get("MAX", (uint8_t)CORE_MAX_LEVEL); - if (m_maxLevel > MAX_LEVEL_MAX) - m_maxLevel = MAX_LEVEL_MAX; + s_CanLevelUpHook = Hooks::HookFunction( + &CNWSCreatureStats::CanLevelUp, + &CanLevelUpHook, Hooks::Order::Final); - if (m_maxLevel > CORE_MAX_LEVEL) - { - s_GetServerInfoFromIniFileHook = Hooks::HookFunction(&CServerExoAppInternal::GetServerInfoFromIniFile, - &GetServerInfoFromIniFileHook, Hooks::Order::Early); - s_LoadModuleStartHook = Hooks::HookFunction(Functions::_ZN10CNWSModule15LoadModuleStartE10CExoStringiiRKN6NWSync13AdvertisementE, - (void*)&LoadModuleStartHook, Hooks::Order::Early); - s_CanLevelUpHook = Hooks::HookFunction(&CNWSCreatureStats::CanLevelUp, - &CanLevelUpHook, Hooks::Order::Final); - s_GetExpNeededForLevelUpHook = Hooks::HookFunction(&CNWSCreatureStats::GetExpNeededForLevelUp, - &GetExpNeededForLevelUpHook, Hooks::Order::Final); - s_LevelDownHook = Hooks::HookFunction(&CNWSCreatureStats::LevelDown, - &LevelDownHook, Hooks::Order::Late); - s_SummonAssociateHook = Hooks::HookFunction(&CNWSCreature::SummonAssociate, - &SummonAssociateHook, Hooks::Order::Late); - s_LoadSpellGainTableHook = Hooks::HookFunction(&CNWClass::LoadSpellGainTable, - &LoadSpellGainTableHook, Hooks::Order::Early); - s_LoadSpellKnownTableHook = Hooks::HookFunction(&CNWClass::LoadSpellKnownTable, - &LoadSpellKnownTableHook, Hooks::Order::Early); - s_GetSpellGainHook = Hooks::HookFunction(&CNWClass::GetSpellGain, - &GetSpellGainHook, Hooks::Order::Final); - s_GetSpellsKnownPerLevelHook = Hooks::HookFunction(&CNWClass::GetSpellsKnownPerLevel, - &GetSpellsKnownPerLevelHook, Hooks::Order::Final); - } -} + s_GetExpNeededForLevelUpHook = Hooks::HookFunction( + &CNWSCreatureStats::GetExpNeededForLevelUp, + &GetExpNeededForLevelUpHook, Hooks::Order::Final); -MaxLevel::~MaxLevel() -{ + s_LevelDownHook = Hooks::HookFunction( + &CNWSCreatureStats::LevelDown, + &LevelDownHook, Hooks::Order::Late); + + s_SummonAssociateHook = Hooks::HookFunction( + &CNWSCreature::SummonAssociate, + &SummonAssociateHook, Hooks::Order::Late); + + s_LoadSpellGainTableHook = Hooks::HookFunction( + &CNWClass::LoadSpellGainTable, + &LoadSpellGainTableHook, Hooks::Order::Early); + + s_LoadSpellKnownTableHook = Hooks::HookFunction( + &CNWClass::LoadSpellKnownTable, + &LoadSpellKnownTableHook, Hooks::Order::Early); + + s_GetSpellGainHook = Hooks::HookFunction( + &CNWClass::GetSpellGain, + &GetSpellGainHook, Hooks::Order::Final); + + s_GetSpellsKnownPerLevelHook = Hooks::HookFunction( + &CNWClass::GetSpellsKnownPerLevel, + &GetSpellsKnownPerLevelHook, Hooks::Order::Final); + } } -void MaxLevel::GetServerInfoFromIniFileHook(CServerExoAppInternal* pServerExoAppInternal) +static void GetServerInfoFromIniFileHook(CServerExoAppInternal* pServerExoAppInternal) { s_GetServerInfoFromIniFileHook->CallOriginal(pServerExoAppInternal); - pServerExoAppInternal->m_pServerInfo->m_JoiningRestrictions.nMaxLevel = g_plugin->m_maxLevel; + pServerExoAppInternal->m_pServerInfo->m_JoiningRestrictions.nMaxLevel = s_maxLevel; } // After Rules aggregates all its information we add to our custom experience table map -uint32_t MaxLevel::LoadModuleStartHook(CNWSModule *pModule, CExoString sModuleName, int32_t bIsSaveGame, int32_t nSourceType, const NWSync::Advertisement* nwsyncModuleSourceAdvert) +static uint32_t LoadModuleStartHook(CNWSModule *pModule, CExoString sModuleName, int32_t bIsSaveGame, int32_t nSourceType, const NWSync::Advertisement* nwsyncModuleSourceAdvert) { auto retVal = s_LoadModuleStartHook->CallOriginal(pModule, sModuleName, bIsSaveGame, nSourceType, nwsyncModuleSourceAdvert); auto *twoda = Globals::Rules()->m_p2DArrays->GetCached2DA("EXPTABLE", true); twoda->Load2DArray(); - for (int i = CORE_MAX_LEVEL; i < g_plugin->m_maxLevel; i++) + for (int i = CORE_MAX_LEVEL; i < s_maxLevel; i++) { int32_t xpLevel = 0; twoda->GetINTEntry(i, 1, &xpLevel); if (!xpLevel) { LOG_ERROR("No xp threshold set for level %d!. Max level reverted to 40.", 1 + i); - g_plugin->m_maxLevel = CORE_MAX_LEVEL; + s_maxLevel = CORE_MAX_LEVEL; return retVal; } else { - g_plugin->m_nExperienceTableAdded[i] = xpLevel; + s_nExperienceTableAdded[i] = xpLevel; } } - if (g_plugin->m_maxLevel > CORE_MAX_LEVEL) - LOG_INFO("Max Level increased to %d.", g_plugin->m_maxLevel); return retVal; } // If level is greater than 40 seek the xp_threshold from our custom map -int32_t MaxLevel::CanLevelUpHook(CNWSCreatureStats* pStats) +static int32_t CanLevelUpHook(CNWSCreatureStats* pStats) { auto pCreature = pStats->m_pBaseCreature; if ((pCreature->m_nAssociateType >= 5 && pCreature->m_nAssociateType <= 8) || pCreature->m_nAssociateType == 3) @@ -134,7 +146,7 @@ int32_t MaxLevel::CanLevelUpHook(CNWSCreatureStats* pStats) int32_t totalLevels = pStats->GetLevel(false); - if ((!pStats->m_bIsPC && totalLevels >= MAX_LEVEL_MAX) || (pStats->m_bIsPC && totalLevels >= g_plugin->m_maxLevel)) + if ((!pStats->m_bIsPC && totalLevels >= MAX_LEVEL_MAX) || (pStats->m_bIsPC && totalLevels >= s_maxLevel)) return 0; if (!pStats->m_bIsPC) @@ -148,7 +160,7 @@ int32_t MaxLevel::CanLevelUpHook(CNWSCreatureStats* pStats) } else { - xp_threshold = g_plugin->m_nExperienceTableAdded[totalLevels]; + xp_threshold = s_nExperienceTableAdded[totalLevels]; } if (xp < xp_threshold) @@ -158,7 +170,7 @@ int32_t MaxLevel::CanLevelUpHook(CNWSCreatureStats* pStats) } // If the player is at level 39 or lower we get the XP required from the normal array, otherwise we use our map -uint32_t MaxLevel::GetExpNeededForLevelUpHook(CNWSCreatureStats *pStats) +static uint32_t GetExpNeededForLevelUpHook(CNWSCreatureStats *pStats) { uint32_t xp_threshold = 0; int32_t totalLevels = pStats->GetLevel(false); @@ -168,14 +180,14 @@ uint32_t MaxLevel::GetExpNeededForLevelUpHook(CNWSCreatureStats *pStats) } else { - xp_threshold = g_plugin->m_nExperienceTableAdded[totalLevels]; + xp_threshold = s_nExperienceTableAdded[totalLevels]; } return xp_threshold; } // Instead of rewriting SetExperience we just make sure if the call to LevelDown is not necessary we skip it // If the player is at level 40 or lower we get the XP required from the normal array, otherwise we use our map -void MaxLevel::LevelDownHook(CNWSCreatureStats *pStats, CNWLevelStats *pLevelStats) +static void LevelDownHook(CNWSCreatureStats *pStats, CNWLevelStats *pLevelStats) { if (!pStats->m_bIsPC) { @@ -191,7 +203,7 @@ void MaxLevel::LevelDownHook(CNWSCreatureStats *pStats, CNWLevelStats *pLevelSta } else { - xp_threshold = g_plugin->m_nExperienceTableAdded[totalLevels - 1]; + xp_threshold = s_nExperienceTableAdded[totalLevels - 1]; } if (nXP < xp_threshold) s_LevelDownHook->CallOriginal(pStats, pLevelStats); @@ -200,7 +212,7 @@ void MaxLevel::LevelDownHook(CNWSCreatureStats *pStats, CNWLevelStats *pLevelSta // The resref passed into SummonAssociate has the template utc with the character's level appended to it, just // swap that level back to 40 if that utc doesn't exist. This is easier than rewriting SummonFamilar and // SummonAnimalCompanion completely -void MaxLevel::SummonAssociateHook(CNWSCreature *pCreature, CResRef cResRef, CExoString p_sAssociateName, uint16_t nAssociateType) +static void SummonAssociateHook(CNWSCreature *pCreature, CResRef cResRef, CExoString p_sAssociateName, uint16_t nAssociateType) { auto cUsedResRef = cResRef; std::string sResRef = cResRef.GetResRef(); @@ -214,14 +226,14 @@ void MaxLevel::SummonAssociateHook(CNWSCreature *pCreature, CResRef cResRef, CEx } // After the server loads 1-40 we populate our map with the values for 41+ -void MaxLevel::LoadSpellGainTableHook(CNWClass* pClass, CExoString sTable) +static void LoadSpellGainTableHook(CNWClass* pClass, CExoString sTable) { s_LoadSpellGainTableHook->CallOriginal(pClass, sTable); C2DA twoda(sTable, true); twoda.Load2DArray(); - for (int i = CORE_MAX_LEVEL; i < g_plugin->m_maxLevel; i++) + for (int i = CORE_MAX_LEVEL; i < s_maxLevel; i++) { int32_t numSpellLevels = 0; uint8_t lastFoundSpellGainLevel = CORE_MAX_LEVEL; @@ -234,7 +246,7 @@ void MaxLevel::LoadSpellGainTableHook(CNWClass* pClass, CExoString sTable) 1 + i, pClass->GetNameText(), lastFoundSpellGainLevel); twoda.GetINTEntry(lastFoundSpellGainLevel - 1, "NumSpellLevels", &numSpellLevels); } - g_plugin->m_nSpellLevelsPerLevelAdded[pClass->m_nName][i] = numSpellLevels; + s_nSpellLevelsPerLevelAdded[pClass->m_nName][i] = numSpellLevels; // Now find the spells gained per level for each spell level for (int j = 0; j < numSpellLevels; j++) @@ -249,14 +261,14 @@ void MaxLevel::LoadSpellGainTableHook(CNWClass* pClass, CExoString sTable) { lastFoundSpellGainLevel = 1 + i; } - g_plugin->m_nSpellGainTableAdded[pClass->m_nName][i][j] = iNumSpells; + s_nSpellGainTableAdded[pClass->m_nName][i][j] = iNumSpells; } } } // If the player is at level 40 or lower we get the spell gain for that class from the normal array, // otherwise we use our map -uint8_t MaxLevel::GetSpellGainHook(CNWClass *pClass, uint8_t nLevel, uint8_t nSpellLevel) +static uint8_t GetSpellGainHook(CNWClass *pClass, uint8_t nLevel, uint8_t nSpellLevel) { uint8_t result = -1; @@ -269,28 +281,28 @@ uint8_t MaxLevel::GetSpellGainHook(CNWClass *pClass, uint8_t nLevel, uint8_t nSp } else { - if ( nSpellLevel < g_plugin->m_nSpellLevelsPerLevelAdded[pClass->m_nName][nLevel - 1] ) + if ( nSpellLevel < s_nSpellLevelsPerLevelAdded[pClass->m_nName][nLevel - 1] ) { - result = g_plugin->m_nSpellGainTableAdded[pClass->m_nName][nLevel - 1][nSpellLevel]; + result = s_nSpellGainTableAdded[pClass->m_nName][nLevel - 1][nSpellLevel]; } } return result; } // After the server loads 1-40 we populate our map with the values for 41+ -void MaxLevel::LoadSpellKnownTableHook(CNWClass* pClass, CExoString sTable) +static void LoadSpellKnownTableHook(CNWClass* pClass, CExoString sTable) { s_LoadSpellKnownTableHook->CallOriginal(pClass, sTable); C2DA twoda(sTable, true); twoda.Load2DArray(); - for (int i = CORE_MAX_LEVEL; i < g_plugin->m_maxLevel; i++) + for (int i = CORE_MAX_LEVEL; i < s_maxLevel; i++) { uint8_t lastFoundSpellKnownLevel = CORE_MAX_LEVEL; // Now find the spells known per level for each spell level - for (int j = 0; j < g_plugin->m_nSpellLevelsPerLevelAdded[pClass->m_nName][i]; j++) + for (int j = 0; j < s_nSpellLevelsPerLevelAdded[pClass->m_nName][i]; j++) { int32_t iNumSpells = 0; twoda.GetINTEntry(i, 1 + j, &iNumSpells); @@ -302,14 +314,14 @@ void MaxLevel::LoadSpellKnownTableHook(CNWClass* pClass, CExoString sTable) { lastFoundSpellKnownLevel = 1 + i; } - g_plugin->m_nSpellKnownTableAdded[pClass->m_nName][i][j] = iNumSpells; + s_nSpellKnownTableAdded[pClass->m_nName][i][j] = iNumSpells; } } } // If the player is at level 40 or lower we get the spell known for that class from the normal array, // otherwise we use our map -uint8_t MaxLevel::GetSpellsKnownPerLevelHook(CNWClass *pClass, uint8_t nLevel, uint8_t nSpellLevel, uint8_t nClass, +static uint8_t GetSpellsKnownPerLevelHook(CNWClass *pClass, uint8_t nLevel, uint8_t nSpellLevel, uint8_t nClass, uint16_t nRace, uint8_t nCHABase) { uint8_t result = 0; @@ -327,12 +339,12 @@ uint8_t MaxLevel::GetSpellsKnownPerLevelHook(CNWClass *pClass, uint8_t nLevel, u } else { - spellLevelsPerLevel = g_plugin->m_nSpellLevelsPerLevelAdded[pClass->m_nName][nLevel - 1]; - spellsKnownPerSpellLevel = g_plugin->m_nSpellKnownTableAdded[pClass->m_nName][nLevel - 1][nSpellLevel]; + spellLevelsPerLevel = s_nSpellLevelsPerLevelAdded[pClass->m_nName][nLevel - 1]; + spellsKnownPerSpellLevel = s_nSpellKnownTableAdded[pClass->m_nName][nLevel - 1][nSpellLevel]; if (nLevel == CORE_MAX_LEVEL + 1) spellsKnownPreviousSpellLevel = pClass->m_lstSpellKnownTable[CORE_MAX_LEVEL - 1][nSpellLevel - 1]; else - spellsKnownPreviousSpellLevel = g_plugin->m_nSpellKnownTableAdded[pClass->m_nName][nLevel - 1][nSpellLevel - 1]; + spellsKnownPreviousSpellLevel = s_nSpellKnownTableAdded[pClass->m_nName][nLevel - 1][nSpellLevel - 1]; } } @@ -344,6 +356,4 @@ uint8_t MaxLevel::GetSpellsKnownPerLevelHook(CNWClass *pClass, uint8_t nLevel, u result = spellsKnownPerSpellLevel; } return result; -} - -} +} \ No newline at end of file diff --git a/Plugins/MaxLevel/MaxLevel.hpp b/Plugins/MaxLevel/MaxLevel.hpp deleted file mode 100644 index 502899cb55c..00000000000 --- a/Plugins/MaxLevel/MaxLevel.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include "nwnx.hpp" -#include "API/CNWSModule.hpp" - -namespace MaxLevel { - -class MaxLevel : public NWNXLib::Plugin -{ -public: - MaxLevel(NWNXLib::Services::ProxyServiceList* services); - virtual ~MaxLevel(); - -private: - uint8_t m_maxLevel; - std::unordered_map m_nExperienceTableAdded; - std::unordered_map>> m_nSpellGainTableAdded; - std::unordered_map>> m_nSpellKnownTableAdded; - std::unordered_map> m_nSpellLevelsPerLevelAdded; - - static uint32_t LoadModuleStartHook(CNWSModule*, CExoString, int32_t, int32_t, const NWSync::Advertisement*); - static void LoadSpellGainTableHook(CNWClass*, CExoString); - static void LoadSpellKnownTableHook(CNWClass*, CExoString); - static uint8_t GetSpellGainHook(CNWClass*, uint8_t, uint8_t); - static uint8_t GetSpellsKnownPerLevelHook(CNWClass*, uint8_t, uint8_t, uint8_t, uint16_t, uint8_t); - static int32_t CanLevelUpHook(CNWSCreatureStats*); - static void SummonAssociateHook(CNWSCreature*, CResRef, CExoString, uint16_t); - static void LevelDownHook(CNWSCreatureStats*, CNWLevelStats*); - static uint32_t GetExpNeededForLevelUpHook(CNWSCreatureStats*); - static void GetServerInfoFromIniFileHook(CServerExoAppInternal*); -}; - -} From bdc21ae113bd6bb2baf8da4b554838fd730ebd6a Mon Sep 17 00:00:00 2001 From: plenarius Date: Sun, 8 Sep 2024 19:55:33 -0400 Subject: [PATCH 2/3] MaxLevel: fix levelling down --- CHANGELOG.md | 1 + Plugins/MaxLevel/MaxLevel.cpp | 48 +++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a28f60d6d44..f66bb96ca45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ https://github.com/nwnxee/unified/compare/build8193.36.12...HEAD ### Fixed - Race: Documentation updated as `NWNX_Utils` is no longer required with introduction of native `Get2DARowCount()`. - Core README indicated wrong argument for logging. `NWNX_CORE_LOG_FILE_NAME` should have been `NWNX_CORE_LOG_FILE_PATH`. +- MaxLevel: Fixed levelling down not working correctly. ## 8193.36.10 https://github.com/nwnxee/unified/compare/build8193.36.9...build8193.36.10 diff --git a/Plugins/MaxLevel/MaxLevel.cpp b/Plugins/MaxLevel/MaxLevel.cpp index 54878980840..f4b82c01347 100644 --- a/Plugins/MaxLevel/MaxLevel.cpp +++ b/Plugins/MaxLevel/MaxLevel.cpp @@ -1,6 +1,8 @@ #include "nwnx.hpp" #include "API/C2DA.hpp" +#include "API/CAppManager.hpp" +#include "API/CServerExoApp.hpp" #include "API/CTwoDimArrays.hpp" #include "API/CNWClass.hpp" #include "API/CNWRules.hpp" @@ -14,6 +16,7 @@ #include "API/CNWRace.hpp" #include "API/CNWSpellArray.hpp" #include "API/CNWLevelStats.hpp" +#include "API/CNWCCMessageData.hpp" #include "API/Functions.hpp" #include "API/Globals.hpp" #include "API/Constants.hpp" @@ -22,6 +25,7 @@ using namespace NWNXLib; using namespace NWNXLib::API; using namespace NWNXLib::API::Constants; +using namespace NWNXLib::API::Globals; const int CORE_MAX_LEVEL = 40; const int MAX_LEVEL_MAX = 60; @@ -38,6 +42,7 @@ static Hooks::Hook s_CanLevelUpHook; static Hooks::Hook s_GetExpNeededForLevelUpHook; static Hooks::Hook s_GetSpellGainHook; static Hooks::Hook s_GetSpellsKnownPerLevelHook; +static Hooks::Hook s_SetExperienceHook; uint8_t s_maxLevel; @@ -56,6 +61,7 @@ static void LoadSpellGainTableHook(CNWClass* pClass, CExoString sTable); static void LoadSpellKnownTableHook(CNWClass* pClass, CExoString sTable); static uint8_t GetSpellGainHook(CNWClass *pClass, uint8_t nLevel, uint8_t nSpellLevel); static uint8_t GetSpellsKnownPerLevelHook(CNWClass *pClass, uint8_t nLevel, uint8_t nSpellLevel, uint8_t nClass, uint16_t nRace, uint8_t nCHABase); +static void SetExperienceHook(CNWSCreatureStats *pStats, uint32_t nValue, bool bDoLevel); void MaxLevel() __attribute__((constructor)); void MaxLevel() @@ -102,6 +108,10 @@ void MaxLevel() s_GetSpellsKnownPerLevelHook = Hooks::HookFunction( &CNWClass::GetSpellsKnownPerLevel, &GetSpellsKnownPerLevelHook, Hooks::Order::Final); + + s_SetExperienceHook = Hooks::HookFunction( + &CNWSCreatureStats::SetExperience, + &SetExperienceHook, Hooks::Order::Early); } } @@ -356,4 +366,42 @@ static uint8_t GetSpellsKnownPerLevelHook(CNWClass *pClass, uint8_t nLevel, uint result = spellsKnownPerSpellLevel; } return result; +} + +static void SetExperienceHook(CNWSCreatureStats *pStats, uint32_t nValue, bool bDoLevel) +{ + int32_t nExpDiff = nValue - pStats->m_nExperience; + + if (pStats->GetIsDM() || nExpDiff >= 0) + { + s_SetExperienceHook->CallOriginal(pStats, nValue, bDoLevel); + return; + } + + auto *pPlayer = Globals::AppManager()->m_pServerExoApp->GetClientObjectByObjectId(pStats->m_pBaseCreature->m_idSelf); + + if ( bDoLevel && pPlayer != NULL ) + { + auto *pMessageData = new CNWCCMessageData; + + pMessageData->SetInteger(0,nExpDiff * -1); + pStats->m_pBaseCreature->SendFeedbackMessage(183,pMessageData); + } + + pStats->m_nExperience = nValue; + if ( bDoLevel ) + { + uint8_t currentLevel = pStats->GetLevel(); + while (currentLevel > Globals::AppManager()->m_pServerExoApp->GetServerInfo()->m_JoiningRestrictions.nMinLevel && + currentLevel > 0 && + ((currentLevel <= CORE_MAX_LEVEL && g_pRules->m_nExperienceTable[currentLevel-1] > pStats->m_nExperience) || + (currentLevel > CORE_MAX_LEVEL && s_nExperienceTableAdded[currentLevel-1] > pStats->m_nExperience))) + { + auto *pLevelStat = pStats->GetLevelStats(--currentLevel); + if (pLevelStat) + { + pStats->LevelDown(pLevelStat); + } + } + } } \ No newline at end of file From 71e3fb7738e68ef06f601cf8aa9abb589c4b7bf3 Mon Sep 17 00:00:00 2001 From: plenarius Date: Sun, 8 Sep 2024 20:14:32 -0400 Subject: [PATCH 3/3] MaxLevel: we can also callOriginal if bDoLevel is false --- Plugins/MaxLevel/MaxLevel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/MaxLevel/MaxLevel.cpp b/Plugins/MaxLevel/MaxLevel.cpp index f4b82c01347..55f609512e4 100644 --- a/Plugins/MaxLevel/MaxLevel.cpp +++ b/Plugins/MaxLevel/MaxLevel.cpp @@ -372,7 +372,7 @@ static void SetExperienceHook(CNWSCreatureStats *pStats, uint32_t nValue, bool b { int32_t nExpDiff = nValue - pStats->m_nExperience; - if (pStats->GetIsDM() || nExpDiff >= 0) + if (!bDoLevel || pStats->GetIsDM() || nExpDiff >= 0) { s_SetExperienceHook->CallOriginal(pStats, nValue, bDoLevel); return;