Go Back   EQEmulator Home > EQEmulator Forums > Development > Development::Bug Reports

Development::Bug Reports Post detailed bug reports and what you would like to see next in the emu here.

Reply
 
Thread Tools Display Modes
  #1  
Old 09-21-2018, 06:41 PM
Pyrate
Fire Beetle
 
Join Date: Mar 2004
Posts: 9
Default Levelling beyond 89 (and a half)

Let me start off by saying I'm not overly familiar with the deepest inner workings of the source code, but I do have enough programming knowledge that I felt comfortable enough to download the source and dig through it to try and find my problem and fix it before bringing it here.


I've recently raised my level cap on my small server from 85 to 100, mostly for my own fun, but I discovered a seeming inability to maintain a level beyond 89. I've done some digging on the forums and found a few fairly old threads (one in partcular, http://www.eqemulator.org/forums/showthread.php?t=28822) that mention this issue.

In my testing and efforts to determine what causes this I found a trail of things that I *think* led me to the answer.

1) I discovered that it IS possible to level beyond 89, and through merciless GM-style pummeling of numerous enemies even to level 100. However, ...
2) Whenever that character zones (regardless of whether the new zone had to be booted or was already up), the NEXT exp-generating kill (even if AA is set to 100%) results in the immediate de-levelling to approximately level 89.5.
3) Examination of the "character_data.exp" field in the database before zoning, after zoning, and after the new kill shows that the database value is appropriately large both before and after zoning, but after the kill it resets to ~MAX-SINT32 (approx 2,147,483,647).
4) However, AFTER this kill, I can once again mass-murder NPCs and level up to 100 again. (As long I don't zone)
5) The problem presents every time the character zones (or logs in), which lead me to determine that the problem lies in the packet(s) sent back and forth when loading a character into a zone (regardless of whether or not the zone has already booted or not)

When I downloaded the recent source and began to dig, I found this line, which, I believe *may* be the culprit. The exp data-type in the structure is large enough, but I believe the use of atoi() truncates the experience value to MAX-SINT32 when reading character data from the database is the problem.

Code:
zone/zonedb.cpp:1119 
 pp->exp = atoi(row[r]); r++;
I believe should be

Code:
zone/zonedb.cpp:1119 
 pp->exp = atol(row[r]); r++;
I have tested that this compiles, but I haven't been able to verify the change fixes the problem yet, as there seems to be another issue in my compile that doesn't allow me to zone at all (I saw that issue even before I made this change, and there seems to be a much more recent forum post about a similar issue: http://www.eqemulator.org/forums/showthread.php?t=42079)
Reply With Quote
  #2  
Old 09-22-2018, 03:58 AM
dagulus2
Hill Giant
 
Join Date: Feb 2013
Posts: 220
Default

Daft Question, but have you set both 'Character:MaxExpLevel' and 'Character:MaxLevel' to 100?
Reply With Quote
  #3  
Old 09-22-2018, 10:08 AM
Pyrate
Fire Beetle
 
Join Date: Mar 2004
Posts: 9
Default

Yes, the rules are set correctly, and it is possible level to 100. The problem is that the very first experience generating kill after loading into a new zone immediately results in my experience value being truncated to the max signed-int32 value, resulting in the immediate loss of all levels above 89.

However, once I'm in the zone and undergone this loss of levels and experience, I can once again freely kill and level all the way back up to level 100 (or whatever I have the rules set to).... until I zone or logout (and back in again).
Reply With Quote
  #4  
Old 09-22-2018, 04:57 PM
Kingly_Krab
Administrator
 
Join Date: May 2013
Location: United States
Posts: 1,596
Default

There is a lot more to this than just changing a singular line. You'd need to restructure packets, modify structs, and change any methods or functionality that uses experience as a uint32. I'll look in to it when I get a chance. Sounds like Live EQ already uses uint64_t for experience to avoid this issue.
Reply With Quote
  #5  
Old 09-22-2018, 07:41 PM
Pyrate
Fire Beetle
 
Join Date: Mar 2004
Posts: 9
Default

The packet structure in the PlayerProfile_Struct already uses uint32 to represent exp, and the database already supports values this large.

Code:
pp->exp is defined as a uint32 in eq_packet_structs.h:961
row[r] is the field returned from the database query, returned as a char* from the indexing operator of MySQLRequestRow, but the actual numeric value coming in here from the database can't ever exceed the unsigned in32 max, because that's how the column is defined in the database.
atoi() returns a SIGNED int32; returning INT_MAX = 2147483647, if the parsed argument is > INT_MAX
See https://docs.microsoft.com/en-us/cpp...l?view=vs-2017 for example of atoi behavior in this case.

It's taking a value (albeit as a string) that is already an unsigned int32 (from the database), running it through a function (atoi) that returns a SIGNED int32, and then storing it in an unsigned int32.

The atoi() function is what looks to be truncating the value when loading character_data from the database when the client connects to a new zone.

This doesn't look like any structs or packets need to be changed, just use a function that returns an unsigned int32, or since there doesn't seem to be one of those, use one that returns a signed int64, the return value of which will easily fit into an unsigned int32, since the input value (from the database) is already an unsigned int32.

I don't usually like the easy fix myself, and would generally agree there's got to be something more in depth wrong, but in this case, I really think that it's something that wasn't an issue when it was implemented. Given that leveling beyond 85 wasn't a big concern at the time,and depending on the current state of what is considered "end-game" development for EQEmu, maybe it still is not a concern--I just was putting it up here since I perceived it to be a bug that maybe one day would need to be addressed.
Reply With Quote
  #6  
Old 09-22-2018, 08:09 PM
Kingly_Krab
Administrator
 
Join Date: May 2013
Location: United States
Posts: 1,596
Default

You don't seem to understand what I'm saying. There are multiple Lua/Perl methods that may use experience differently, clients may have different limitations to their experience, and it's just all around a lot more work than simply changing a couple lines in the source code. I get that you're trying to help, but a signed int64 (9,223,372,036,854,775,807) can still contain values well beyond the limitations of an unsigned int32 (4,294,967,295). You can assume that if this were an easy change, one of the community's many Developers would have already resolved this issue long ago. You can modify "Client::GetEXPForLevel(uint16 check_level)" and change this code to anything you want if you know the math to determine the experience necessary for each level.
Code:
if (check_level < 31)
	mod = 1.0;
else if (check_level < 36)
	mod = 1.1;
else if (check_level < 41)
	mod = 1.2;
else if (check_level < 46)
	mod = 1.3;
else if (check_level < 52)
	mod = 1.4;
else if (check_level < 53)
	mod = 1.5;
else if (check_level < 54)
	mod = 1.6;
else if (check_level < 55)
	mod = 1.7;
else if (check_level < 56)
	mod = 1.9;
else if (check_level < 57)
	mod = 2.1;
else if (check_level < 58)
	mod = 2.3;
else if (check_level < 59)
	mod = 2.5;
else if (check_level < 60)
	mod = 2.7;
else if (check_level < 61)
	mod = 3.0;
else
	mod = 3.1;
Reply With Quote
  #7  
Old 09-22-2018, 08:33 PM
Uleat's Avatar
Uleat
Developer
 
Join Date: Apr 2012
Location: North Carolina
Posts: 2,815
Default

You could use atoul() and get the desired result..but, there's no guarantee that it won't break down in other systems.

Any local variable that uses the type int32 (or int on a 32-bit build) will cause problems for the section of code it is declared in.

Additionally, each of the supported clients would need to be tested for abberant behavior as a result of allowing values greater than int32.max.


The server code is designed around client models..we can't make the client honor server changes.


For ref:

https://github.com/EQEmu/Server/blob...tches/rof2.cpp

https://github.com/EQEmu/Server/blob...nium.cpp#L1102
__________________
Uleat of Bertoxxulous

Compilin' Dirty
Reply With Quote
  #8  
Old 09-22-2018, 10:14 PM
Pyrate
Fire Beetle
 
Join Date: Mar 2004
Posts: 9
Default

Understood.

For reference, using atoul() here seems to work well with the RoF2 client, the one I'm using.

Thanks for the feedback.
Reply With Quote
  #9  
Old 09-23-2018, 02:13 AM
Huppy's Avatar
Huppy
Demi-God
 
Join Date: Oct 2010
Posts: 1,332
Default

Quote:
Originally Posted by Kingly_Krab View Post
There is a lot more to this than just changing a singular line. You'd need to restructure packets, modify structs, and change any methods or functionality that uses experience as a uint32. I'll look in to it when I get a chance. Sounds like Live EQ already uses uint64_t for experience to avoid this issue.
I like to read and learn, even if I don't understand a damn thing about source code. I'm always scared, that if I do start understanding it, it will get boring :P

Code:
2^2 = 4
2^4 = 16
2^8 = 256
2^16 = 65536
2^32 = 4294967296
2^64 = 18446744073709551616


// testValue
unsigned long long testValue     = 0xFFFFFFFFFFFFFFFF; // 18446744073709551615

// 1 byte -> [0-255] or [0x00-0xFF]
uint8_t         number8     = testValue; // 255
unsigned char    numberChar    = testValue; // 255

// 2 bytes -> [0-65535] or [0x0000-0xFFFF]
uint16_t         number16     = testValue; // 65535
unsigned short    numberShort    = testValue; // 65535

// 4 bytes -> [0-4294967295] or [0x00000000-0xFFFFFFFF]
uint32_t         number32     = testValue; // 4294967295
unsigned int     numberInt    = testValue; // 4294967295

 // 8 bytes -> [0-18446744073709551615] or [0x0000000000000000-0xFFFFFFFFFFFFFFFF]
uint64_t             number64         = testValue; // 18446744073709551615
unsigned long long     numberLongLong    = testValue; // 18446744073709551615
Reply With Quote
Reply


Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump

   

All times are GMT -4. The time now is 12:07 PM.


 

Everquest is a registered trademark of Daybreak Game Company LLC.
EQEmulator is not associated or affiliated in any way with Daybreak Game Company LLC.
Except where otherwise noted, this site is licensed under a Creative Commons License.
       
Powered by vBulletin®, Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Template by Bluepearl Design and vBulletin Templates - Ver3.3