Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[cpp] Increase cap for exp/cp bonus. #7106

Open
wants to merge 1 commit into
base: base
Choose a base branch
from

Conversation

shigukahz
Copy link
Contributor

Changed int16 for cap (subpower) to int32 to allow for higher bonuses to be applied to experience items, or even with using !addeffect.

I affirm:

  • I understand that if I do not agree to the following points by completing the checkboxes my PR will be ignored.
  • I understand I should leave resolving conversations to the LandSandBoat team so that reviewers won't miss what was said.
  • I have read and understood the Contributing Guide and the Code of Conduct.
  • I have tested my code and the things my code has changed since the last commit in the PR and will test after any later commits.

What does this pull request do?

This change allows values higher than 32k to be placed into the subpower for the status effect commitment and dedication. Doesn't effect the actual max exp you can earn in one go, just how many bonus points you can get per effect application.

Steps to test these changes

Tried to add either effect while int16 was the cap, anything above 33k crashed map and client. Changed to int32 and retried the process, no crash. Once I established it wasn't crashing I applied commitment with power 500 and subpower 99999. Killed enough mobs to simulate the bonus of 99999 to confirm it was applied.

Changed int16 for cap (subpower) to int32 to allow for higher bonuses to be applied to experience items, or even with using !addeffect.
@Xaver-DaRed
Copy link
Contributor

If I may ask, what would be the use-case for this? In other words, is there any particular reason to change this value other than to allow non-retail/custom behabior to happen, or is there an actual use for it?

@shigukahz
Copy link
Contributor Author

If I may ask, what would be the use-case for this? In other words, is there any particular reason to change this value other than to allow non-retail/custom behabior to happen, or is there an actual use for it?

I'm unaware of any full retail reasoning, would mostly be just something for server owners to be able to change without crashing their server, without having to write a module for it. The only use for it is to allow subPower on those 2 effects to exceed the normal 30,000.

@shigukahz shigukahz marked this pull request as ready for review February 21, 2025 00:46
@@ -5963,7 +5963,7 @@ namespace charutils
{
CStatusEffect* dedication = PChar->StatusEffectContainer->GetStatusEffect(EFFECT_DEDICATION);
int16 percentage = dedication->GetPower();
int16 cap = dedication->GetSubPower();
int32 cap = dedication->GetSubPower();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Get/SetSubPower() is defined as:

uint16 CStatusEffect::GetSubPower() const
{
    return m_SubPower;
}

void CStatusEffect::SetSubPower(uint16 subPower)
{
    m_SubPower = subPower;
}

If your goal is to store more in subpower and then have the maths here support that, then subpower also has to change.

The truncation happens here:

dedication->SetSubPower(cap -= bonus);

Where cap is originally int16 (the subpower) and bonus is int32, but SetSubPower takes a uint16. There's a terrible amount of mixing of widths and signedness's here. I'd work on getting all of those in a row, rather than just boosting the width of cap, since it will get immediately truncated on the way into SetSubPower.

When this is eventually sent to the client, it's done through:

CMessageCombatPacket::CMessageCombatPacket(CBaseEntity* PSender, CBaseEntity* PTarget, int32 param0, int32 param1, uint16 messageID)
...
    ref<uint32>(0x10) = param0;

So this ending up as int32 is fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So even though the change I made works for raising the subpower for those two effects overall in long run it will cause problems even for those two, unless

uint16 CStatusEffect::GetSubPower() const
{
    return m_SubPower;
}

void CStatusEffect::SetSubPower(uint16 subPower)
{
    m_SubPower = subPower;
}

the uint16 in here matches the other changes made to subpowers around the src? I can't imagine anywhere subpower will ever exceed uint16 let alone int16, other than those two effects so if more needs changing just to apply a quality of life on the exp bonuses, it seems a bit un worth.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The point I was getting at is that m_SubPower and its getters/setters are all handling it as a uint16. It's being captured currently into an int16, and your change is just extending the range of that int16 to int32. GetPower is also uint16.

@WinterSolstice8
Copy link
Member

I'm not sure this is a QoL thing, it looks like you've stumbled on a legitimate bug like Zach mentioned, this function has some suspicious looking wrapping issues,

Untested, but the exact if statement needs an audit, something like this:

diff --git a/src/map/utils/charutils.cpp b/src/map/utils/charutils.cpp
index b5c8c64ddb..086366c3f4 100644
--- a/src/map/utils/charutils.cpp
+++ b/src/map/utils/charutils.cpp
@@ -4730,10 +4730,13 @@ namespace charutils
         if (PChar->StatusEffectContainer->GetStatusEffect(EFFECT_COMMITMENT) && PChar->loc.zone->GetRegionID() != REGION_TYPE::ABYSSEA)
         {
             CStatusEffect* commitment = PChar->StatusEffectContainer->GetStatusEffect(EFFECT_COMMITMENT);
-            int16          percentage = commitment->GetPower();
-            int16          cap        = commitment->GetSubPower();
-            rawBonus += std::clamp<int32>(((capacityPoints * percentage) / 100), 0, cap);
-            commitment->SetSubPower(cap -= rawBonus);
+            float          percentage = static_cast<float>(commitment->GetPower() / 100.f);
+            int32          cap        = static_cast<int32>(commitment->GetSubPower()); // Upcast from uint16 to avoid underflow
+
+            rawBonus += std::clamp<int32>(std::floor(capacityPoints * percentage), 0, cap);
+
+            // Make sure to clamp back down in range of uint16
+            commitment->SetSubPower(std::clamp<int32>(cap -= rawBonus, std::numeric_limits<uint16>::min(), std::numeric_limits<uint16>::max()));

@shigukahz
Copy link
Contributor Author

I'm not sure this is a QoL thing, it looks like you've stumbled on a legitimate bug like Zach mentioned, this function has some suspicious looking wrapping issues,

Untested, but the exact if statement needs an audit, something like this:

diff --git a/src/map/utils/charutils.cpp b/src/map/utils/charutils.cpp
index b5c8c64ddb..086366c3f4 100644
--- a/src/map/utils/charutils.cpp
+++ b/src/map/utils/charutils.cpp
@@ -4730,10 +4730,13 @@ namespace charutils
         if (PChar->StatusEffectContainer->GetStatusEffect(EFFECT_COMMITMENT) && PChar->loc.zone->GetRegionID() != REGION_TYPE::ABYSSEA)
         {
             CStatusEffect* commitment = PChar->StatusEffectContainer->GetStatusEffect(EFFECT_COMMITMENT);
-            int16          percentage = commitment->GetPower();
-            int16          cap        = commitment->GetSubPower();
-            rawBonus += std::clamp<int32>(((capacityPoints * percentage) / 100), 0, cap);
-            commitment->SetSubPower(cap -= rawBonus);
+            float          percentage = static_cast<float>(commitment->GetPower() / 100.f);
+            int32          cap        = static_cast<int32>(commitment->GetSubPower()); // Upcast from uint16 to avoid underflow
+
+            rawBonus += std::clamp<int32>(std::floor(capacityPoints * percentage), 0, cap);
+
+            // Make sure to clamp back down in range of uint16
+            commitment->SetSubPower(std::clamp<int32>(cap -= rawBonus, std::numeric_limits<uint16>::min(), std::numeric_limits<uint16>::max()));

Oh I assumed it wasn't a bug just as it was meant to be. This is beyond my scope of knowledge now though, would you like me to close the PR and maybe open an issue so it can be looked at when its time?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants