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

Development::Development Forum for development topics and for those interested in EQEMu development. (Not a support forum)

Reply
 
Thread Tools Display Modes
  #1  
Old 01-30-2014, 03:01 PM
DrakePhoenix
Fire Beetle
 
Join Date: Jan 2014
Posts: 22
Question Item bonuses calc when below recommended level?

Hi everyone,

First of all, I'm sorry if this would be better posted elsewhere, but based on the different boards, I felt this one might be most appropriate.

I've been working on a custom private server for my own use. It's all up and running, and seems to be working correctly (what I've tested so far anyway). However, I noticed an oddity with the item bonuses when the character level is below the recommended level...

I had made some duplicates of Elegant Defiant Chain armor pieces and set their required level to 0 and recommended level to 70 for testing out item bonus scaling. I noticed, however, that they would no longer update to max HP and Mana on the client interface (always had only partial HP and Mana). I'm using Rain of Fear client, so the first thing I did was to double-check that my database had "Character:SoDClientUseSoDHPManaEnd" correctly set in the rule_values table. It was set correctly. So I started taking the armor pieces off one at a time to see if there was any specific armor piece that might be an issue. I noticed that every time I removed a piece, the HP and Mana got closer to full bars on the client UI.

All of this led me to think that it might be due to the way item stat bonuses are calculated on the server, as there appears to be a discrepancy between what the server sees is the max HP and Mana, and thus capping off and allowing no further regen, vs. the what the client thinks the max should be.

So I went poking through the code and took a close look at the "Client::CalcRecommendedLevelBonus()" function in the bonuses.cpp file. I was always under the impression that item stats should scale dirrectly proportionate to the current level vs. the recommended level. So, for example, if the character's current level is 75 and the item's recommended level is 100, then stats would scale to exactly 75% of their maximums. Similarly, I thought that if the current level was 25 and recommended level was 100, then stats would scale to be exactly 25% of their maximums. I've never really been sure about how rounding works for that, but I wasn't too interested before. But the function code is very different. Instead of simply taking (basestat * (level / reclevel)) to determine scaled stat, the code instead takes (basestat * ((level * 10000) / reclevel)), then adds or subtracts 5000 depending on if the calculated value is positive or negative, and then divides the result by 10000. This results in item stats being scaled in such a way that the closer your level to the recommended level, the smaller the falloff amount, and the farther your level is from the recommended, the larger the falloff amount.

But since there appears to be a discrepancy between what the server sees as the max stats vs. what the client thinks (and displays) the max stats should be (and displays them as), it seems to me that this calculation may not be accurate.

So I was wondering where the current calculation method came form. How was it determined? Is it known to be in any way inaccurate? Did any clients after UF change the way that stat bonuses are calculated and displayed yet again and that simply hasn't been updated in the server code?

I'm not looking to change anything in the calculation on my server right now, I just would like more information about this particular issue.

Thanks,
Drake Phoenix
Reply With Quote
  #2  
Old 01-30-2014, 03:53 PM
DrakePhoenix
Fire Beetle
 
Join Date: Jan 2014
Posts: 22
Default

OK sorry, now that I've looked at the current function code some more, I realize that the method basically is a method of direct proportional scaling. I wasn't taking into account the fact that the method basically is causing a truncation of a float value. So basically ((level*10000)/reclevel)*basestat is a way to handle (level/reclevel)*basestat using int instead of float. The +/-5000 is the equivalent of +0.5 or -0.5 as the case may be if we were using float instead of int. So the odd values I was getting, where they had an extra 0.5 in the result was because I was not truncating off the decimals.

Now that I understand that more clearly, the question then is, why does the client and the server not seem to agree? Because this suggests that either the scaling method used by the client is *not* directly proportional, and/or that the rounding method employed by the client is not truncation, but is instead floor, ceiling, standard rounding, or banker's rounding.

Does anyone happen to have any raw test data for determining how the client handles this calculation? I'd be interested in seeing it if you do.

Thanks,
Drake Phoenix
Reply With Quote
  #3  
Old 02-05-2014, 12:43 PM
DrakePhoenix
Fire Beetle
 
Join Date: Jan 2014
Posts: 22
Default

Well since no one responded with any actual data that might help determine possible client vs. server discrepancies, I did some experimenting myself to come up with some raw data.

I created a test character, human necro with default stat distributions.

I created 3 test items, each granting +AC, -Str, +Cha, +HP, and +Mana. Item 1 granted +/- 100 to all, item 2 granted +/- 35 to all, and item 3 granted +/- 50 to all. All three items were given a required level of 0 and a recommended level of 100.

I then tested the character's adjusted stats as reported by the client at levels 1-10, even levels 12-20, and levels 30, 33, 50, and 67. I did this test with the character wearing only item 1, only item 2, only item 3, and items 2 and 3 together.

The results were interesting, but inconsistent. Following are my conclusions based on the raw data I received...

1 - It is clear from the data that the client does use fully directly proportional linear scaling for stat scaling when the character level is below the recommended level.
2 - It is clear that the client uses something close/similar to standard rounding method, but not precisely so, any value resulting in exactly X.5 *may* round up, or may round down, but does not round to nearest even integer so is not "banker's rounding". Values greater than X.5 seem to always round up, and values less than X.5 seem to always round down. It is *not* clear as to why X.5 values do not always round up or to even, nor is it clear what the client is actually doing to decide which direction to round. It is clear, however, that the client does not use Floor, Ceiling, Banker's, or Truncation methods.
3 - It is clear that negative stat bonuses do *not* scale, but take full effect regardless of character level.
4 - It is clear that the minimum integer value for a scaled positive stat bonus is 1.
5 - It is clear that item bonuses are calculated and rounded individually, and then the integer values are added together. That is, the bonuses from a single item are calculated for scaling and rounding using the client's usual rounding method to determine an integer value from that individual item. All items are treated individually in this way. Then the calculated integer values from all items are added together to get the total stat bonus that the client will display.
6 - It is clear that the client displays additional bonus AC beyond what is expected from the items, even at full recommended level with no stat scaling in effect, and identical Agi rating for all levels tested. This bonus varies somewhat, appears to be approximately +57% to +64% combined total, but it is not granted at every available level.


So, based on all of that, the calculation used for the "Client::CalcRecommendedLevelBonus" function is as correct as it can be with two exceptions: first, that it tries to scale negative values, and second, that it does not maintain a minimum positive value of 1. So I'm still not sure why there is a discrepancy between the server values and the client-side values for HP, but it's making the client show less than full HP and refusing to regen to full.

I'll play with the code some and see if I can't get it to at least try and properly regen to full.

Later,
Drake Phoenix
Reply With Quote
  #4  
Old 02-05-2014, 09:38 PM
Uleat's Avatar
Uleat
Developer
 
Join Date: Apr 2012
Location: North Carolina
Posts: 2,815
Default

I don't know if ScaleItem() if fully implemented yet..could very well be...

https://github.com/EQEmu/Server/blob.../Item.cpp#L710


The last assignment of that function sets the m_scaledItem->CharmFileID = 0 to keep the client from scaling it.

If the item is being sent with the original m_item data and not overriding the m_item->CharmFileID..and the client does its own
scaling modifications..you might see a double scaling going on...

I dunno..I haven't gotten that far in the code yet


If this is the case, I'll fix it in my rework..I've already got ornamentations planned. If it's not, I'll let someone else touch it because
I don't want to stop my rework
__________________
Uleat of Bertoxxulous

Compilin' Dirty
Reply With Quote
  #5  
Old 02-07-2014, 03:35 PM
DrakePhoenix
Fire Beetle
 
Join Date: Jan 2014
Posts: 22
Default

Thanks for the response Uleat.

I don't think the ScaleItem function actually affects item stat scaling for when the character level is below the recommended level. The ScaleItem function looks to me like it only affects evolving items, and here's why...

The function is only ever called if the m_scaling value of the item instance is true. The m_scaling value is only ever set to true in the Initialize function (at least, there is no other occasion that it is set to true in my current version of the source code, but I haven't updated since 01/23/2014), and then only if the CharmFileID for the item is non-zero. In the current peq database there are exceptionally few items that have a non-zero charmfileid (or at least the 01/24/2014 dump that I'm currently using has exceptionally few). Additionally, the ScaleItem function itself doesn't check against the character level for the MOB that has the item equipped, nor against the reclevel for the item instance. Instead, the function checks against the *item's* accumulated EXP. As far as I know, only evolving items ever accumulate EXP.

Also, the items that I've used and noticed discrepancies with do not have non-zero charmfileid values, so they wouldn't be scaling from the ScaleItem function anyway.

Thanks for the suggestion, but I think the issue has to be somewhere else in the code. Also, I've ignored the apparent discrepancies and played normally a bit anyway, and it seems as though the HP discrepancy diminishes as the character levels up, but that Mana and Endurance discrepancies appear and then increase as the character levels up. I only just started paying attention to this last night, so I'm not positive that is actually happening. I'm going to do some deliberate testing for this and see if I can confirm the trend.

Also, I was poking around the code in a few other places, and I noticed that item stat scaling does not occur for bots based on bot level being below the reclevel for the equipped item. Is there a specific reason for this? Or was it simply not addressed previously? It seems to me that bots should *not* get full, unscaled stats from items if they're below the reclevel for the item.

Anyway, I'll keep poking around and see if I can figure anything out for this.

Thanks,
Drake Phoneix
Reply With Quote
  #6  
Old 02-08-2014, 03:01 PM
DrakePhoenix
Fire Beetle
 
Join Date: Jan 2014
Posts: 22
Default

OK, so I did some more checking and experimenting...

It turns out that while the original code appears to handle scaled HP incorrectly, it does handle Mana and Endurance correctly. I had made changes to the code to try and get HP to work, and that's when the Mana and Endurance started showing discrepancies. I'm not sure why this is the case, but it appears that HP is handled differently than Mana and Endurance in terms of the actual data values used. I can't figure that one out, but I'm not too worried about it.

When I changed the code to try and fix the HP issue, I used implicit type conversion, and this caused the issue with Mana and Endurance to show up. Apparently there was some data loss and it was setting the return values for scaled Mana and Endurance to be 0, so the server was using values as though there was no bonus Mana or Endurance, while the client was showing the correct values for what should have been there. I changed it again to use explicit type conversion and now all three seem to work correctly, at least on my client and server combination (server is slightly modified from 01/23/2014 git, client is RoF).

If anyone is interested, all I changed was the CalcRecommendedLevelBonus function in bonuses.cpp to use float values instead of int values. I didn't honestly expect that to work, I just wanted the code to make more sense to me personally, but it seems to have solved the issue. Following is my modified function...

Code:
int Client::CalcRecommendedLevelBonus(uint8 level, uint8 reclevel, int basestat)
{
	if( (reclevel > 0) && (level < reclevel) )
	{
		// DrakePhoenix:  original function...
		/*int32 statmod = (level * 10000 / reclevel) * basestat;

		if( statmod < 0 )
		{
			statmod -= 5000;
			return (statmod/10000);
		}
		else
		{
			statmod += 5000;
			return (statmod/10000);
		}*/

		// DrakePhoenix:  my function... designed to use full float values, then use standard rounding
		// values between 0.0 and 1.0 will always be 1.0
		// negative values will not be scaled
		int32 statmod = 0;
		float statmodf = (float(level) / float(reclevel)) * float(basestat);

		statmodf = statmodf + 0.5; // used for rounding, necessary for float-to-int truncation
		// standard rounding would normally require a -0.5 for negative values, but since negative values are not scaled, no need

		// if scaled stat bonus is greater than 0.0 but less than 1.0, use value of 1.0
		if((statmodf > 0.0) && (statmodf < 1.0))
		{
			statmodf = 1.0;
		}

		statmod = int32(statmodf); // convert float value to int value so we can return int.

		// if basestat is a negative value, do not scale, but use full amount
		if(basestat < 0)
		{
			statmod = basestat;
		}

		return statmod;
	}
	
	return 0;
	
}
Using my modified function as above the client and server both now seem to agree on max and actual stats for HP, Mana, and Endurance when using level-scaled bonuses.

Take care,
Drake Phoenix
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:18 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