Go Back   EQEmulator Home > EQEmulator Forums > Development > Development::Server Code Submissions

Reply
 
Thread Tools Display Modes
  #1  
Old 07-18-2008, 01:29 PM
CodeMephit
Fire Beetle
 
Join Date: Oct 2006
Posts: 18
Default New Character: Hunger / Thirst

One of the first things I noticed when trying EQEmu about a year ago was that newly created players would eat 2 bread cakes and drink 2 milk skins almost as soon as they were first logged in. On a Live server, it normally takes ~10-15 minutes to eat your first item.

I have never programmed in C++ nor do I have any working knowledge of the language. So, I just simply accepted the fact that my new characters were very hungry and thirsty

I decided to download the source code and Visual C++ 2008 Express. It took a good bit of reading to get everything to go in the right place, but in less than a morning's time, I was able to compile the source code and run my newly made server.

Now it was time to delve into the code..........

I had no idea what a ride I was in for. It took several hours just to get a working idea of how the files were set up and what general form the project (and C++) used.

I *finally* tracked down where characters are created and where the starting stats are defined. A few searches through the code for things like "food", "hunger", "hungry", "eat" and "consume" generated several items for me to look at (and learn from).

I tried a few different things before I finally came up with something that would work (took a few tries just to get a compile to work LOL). I have tested my changes and they seem to work without any issues.

Please keep in mind, this is my *FIRST* ever attempt at working with C++. As such, I may have overlooked some major item, and if so, simply reply and I will try to learn.

The idea is that we max out the hunger and thirst variables so the player does not need to eat or drink for a while after they are first created. This is more inline with the experience on a Live server.

In \EQEmu-0.7.0-1118\world\client.cpp (Line 957)
Change:
Code:
	//what was the point of this? zone dosent handle this:
	//pp.expAA			= 0xFFFFFFFF;

	// FIXME: FV roleplay, database goodness...
To:
Code:
	//what was the point of this? zone dosent handle this:
	//pp.expAA			= 0xFFFFFFFF;

	// Codemephit [2008.07.18] - Set the hunger / thirst values to 'full'.
	pp.hunger_level = 6000;
	pp.thirst_levlel = 6000;

	// FIXME: FV roleplay, database goodness...
It would be nice to know that I was able to give a little something back to a community that has given me so much already!

-Codemephit
Reply With Quote
  #2  
Old 07-18-2008, 02:15 PM
ChaosSlayer
Demi-God
 
Join Date: May 2007
Posts: 1,032
Default

i simply altered the hunger variable in Rule section =)
the default consumtion ratio was too heavy anyway - you were eating and drinking like every 10 min
Reply With Quote
  #3  
Old 07-18-2008, 08:34 PM
CodeMephit
Fire Beetle
 
Join Date: Oct 2006
Posts: 18
Default

You make a very good point. I did not know about the hunger rule variable. But if I had, I may never have been motivated to look at C++

Anyhow, I found where food is consumed. True to what you said, it is about 1 item every 10 minutes.

I created a new character on a Live server and deleted all but 10 each of his bread / milk. I then used my logs to determine how long the food lasted and timed the 'you are low on food', 'you are out of food', 'you are hungry' messages.

I found that 1 item is eaten / drank about once per hour, give or take a little. This was on a human by the way. If I recall correctly, small races ate slower and large races ate faster. Also, I remember something about eating more if you had a mount summoned. I will see what I can dig up on that.

For now though, this will bring normal eating / drinking habits more inline with Live.

In \EQEmu-0.7.0-1118\zone\client_packet.cpp (Line 1291)
Change:
Code:
		m_pp.hunger_level += eat_item->CastTime*cons_mod; //roughly 1 item per 10 minutes
		DeleteItemInInventory(pcs->slot, 1, false);
		
		if(pcs->auto_consumed != 0xffffffff) //no message if the client consumed for us
			entity_list.MessageClose_StringID(this, true, 50, 0, EATING_MESSAGE, GetName(), eat_item->Name);
	}
	else if (pcs->type == 0x02) {
#if EQDEBUG >= 1
		LogFile->write(EQEMuLog::Debug, "Drinking from slot:%i", (int)pcs->slot);
#endif
		// 6000 is the max. value
		//m_pp.thirst_level += 1000;
		m_pp.thirst_level += eat_item->CastTime*cons_mod; //roughly 1 item per 10 minutes
		DeleteItemInInventory(pcs->slot, 1, false);
To:
Code:
	// Codemephit [2008.07.18] - Added a multiplier of  '6'.  This makes food last about as long as on Live.
		m_pp.hunger_level += eat_item->CastTime*cons_mod*6; //roughly 1 item per 60 minutes
		DeleteItemInInventory(pcs->slot, 1, false);
		
		if(pcs->auto_consumed != 0xffffffff) //no message if the client consumed for us
			entity_list.MessageClose_StringID(this, true, 50, 0, EATING_MESSAGE, GetName(), eat_item->Name);
	}
	else if (pcs->type == 0x02) {
#if EQDEBUG >= 1
		LogFile->write(EQEMuLog::Debug, "Drinking from slot:%i", (int)pcs->slot);
#endif
		// 6000 is the max. value
		//m_pp.thirst_level += 1000;
		m_pp.thirst_level += eat_item->CastTime*cons_mod*6; //roughly 1 item per 60 minutes
		DeleteItemInInventory(pcs->slot, 1, false);
Again, today is my first day working with / writing C++ code. I have tested my changes, and they seem to work fine. However if anyone sees any major issues, please let me know. Thanks.

-Codemephit
Reply With Quote
  #4  
Old 07-18-2008, 08:53 PM
ChaosSlayer
Demi-God
 
Join Date: May 2007
Posts: 1,032
Default

you do realize that with the Rule variable you can allready set consumtipion to ANY level? every 5 min or every 5 days? no C coding requred

personaly i PREFER than char eats faster - cuase this makes them spend more money of food
Reply With Quote
  #5  
Old 07-19-2008, 01:12 AM
KLS
Administrator
 
Join Date: Sep 2006
Posts: 1,348
Default

I've thought about upping the base consumption before the rule several times before honestly. I also don't see a problem with setting a character to having eaten something on character creation.
Reply With Quote
  #6  
Old 07-19-2008, 12:08 PM
So_1337
Dragon
 
Join Date: May 2006
Location: Cincinnati, OH
Posts: 689
Default

I think trying to find the most live-like starting point only makes the variable setting more accurate. If it's twice 'normal', then we have to be sure that 'normal' is 'normal' (as far as Live goes) before we modify it one way or the other =P

Good work, Mephit =)
Reply With Quote
  #7  
Old 07-20-2008, 03:00 AM
ChaosSlayer
Demi-God
 
Join Date: May 2007
Posts: 1,032
Default

the variable that curently in rules allready VERY flexiable - ANYONE can set it to his liking to be more or less. there is absolutly no need for exact live-like hard coding
Reply With Quote
  #8  
Old 07-20-2008, 09:09 AM
CodeMephit
Fire Beetle
 
Join Date: Oct 2006
Posts: 18
Default

/shrug

Maybe I am missing something, but all I can find in the rules is:

Character:ConsumpionMultiplier = 200

There is no comment or description. How is one to know what to set the value at to achieve a live-like consumption rate without trial and error? I understand that some folks want characters to eat more. This is perfectly fine. However, not everybody has that same desire. I believe that 'fast' or 'slow' eating should be the exception to the rule, and 'regular' eating should be the default.

The change I am proposing will not stop a custom server from doing what the Admin wants it to do. They can still set the rule, and it will still take effect. Character will eat slower or faster.

But for all the other folks, who simply want a like-like server, to me, this is a viable offering to the code-base.

Another issue: I don't see how the Character:ConsumptionMultiplier handles the issues of small or large characters needing less or more food respectively. I also do not see it handling a mount's hunger. I know the code I have posted so far does not do these things either, but I am currently witting code that does. I am still learning C++ (teaching myself) so I can't churn a whole block of code out over night. It takes a little while.

Here is the psuedo-code of what I am trying to accomplish:

Code:
NutritionValue = 10
If Race = Small, Then NutritionValue = (NutritionValue + 2.5)
If Race = Large, Then NutritionValue = (NutritionValue - 2.5)
For Each SpellIcon
 If SpellIcon = Mount, Then NutritionValue = (NutritionValue - 2.5)
Next SpellIcon
m_pp.hunger_level += eat_item->CastTime*cons_mod*NutritionValue;
m_pp.thirst_level += eat_item->CastTime*cons_mod*NutritionValue;
This will set a 'base' rate for a medium sized character, and make each food item worth more to a small character, worth less to a large character and worth less if you have a mount summoned. I am currently working on what variables are available to me to test for a character's race, and then I can write out the actual if/thens or cases. Then I will tackle figuring out which spells are active on a character. But the code above gives a quick idea of my goals.

-Codemephit
Reply With Quote
  #9  
Old 07-20-2008, 01:19 PM
ChaosSlayer
Demi-God
 
Join Date: May 2007
Posts: 1,032
Default

Quote:
Originally Posted by CodeMephit View Post
/shrug

Maybe I am missing something, but all I can find in the rules is:

Character:ConsumpionMultiplier = 200

There is no comment or description. How is one to know what to set the value at to achieve a live-like consumption rate without trial and error?
thats would be a valid question to a person who prabobly forgot to add the description =) (and it wasn't me =)

anyway, the higher the number the longer it takes your char to get hungry.

as far as diffirent races eating at diffirent rate - if you going to code it in, please make sure to include a rule to turn it off =)

otherwise when food cost a 100 plat, soem race will be spendign way more than others wihout any benefit (its all back from the day when each race had diffirent XP gain modifier)
Balance wise, Ogres, Trolls, and Iksar should be the only races who eat more due to their inherited racial bonus which actualy decently strong (given they actualy coded in)
Reply With Quote
  #10  
Old 07-22-2008, 08:09 AM
CodeMephit
Fire Beetle
 
Join Date: Oct 2006
Posts: 18
Default

I could use a little guidance on variable usage please.

I have added the following line to \EQEmu-0.7.0-1118\zone\client_packet.cpp just before a character eats:
Code:
Message(13, "My current race is: ");
This will print a message in the client chat window.

I then began changing it to print the 'race' variable. I have tried:
Code:
Message(13, m_pp.race);
Message(13, pp.race);
Message(13, race);
In hopes of printing the 'race' variable to the chat window. The pourpose of this is so that I have an idea what to test for. At the present, I don't know if race is stored as a number (ie. Human=1, Elf=2, Dwarf=3) or if it is stored as a string (ie. Human="HUMAN", Elf="ELF", Dwarf="DWARF"). I also don't exactly know the name of the variable that holds the race value.

None of the above attempts would work. I have also seen in other code where things like the following were used:
Code:
scs->zone_id
zone->GetZoneID()
Maybe I need to use the -> thing to point to where the race variable is?

If anyone happens to know and can point me in the right direction, I would be *very* appreciative.

Thanks
-Codemephit
Reply With Quote
  #11  
Old 07-22-2008, 08:33 AM
Derision
Developer
 
Join Date: Feb 2004
Location: UK
Posts: 1,540
Default

Try:

Code:
Message(13, "My race is %d", m_pp.race);
%d means there is an integer argument following to substitute into the string. Lookup the C printf function for other types you can use.
Reply With Quote
  #12  
Old 07-22-2008, 02:57 PM
CodeMephit
Fire Beetle
 
Join Date: Oct 2006
Posts: 18
Default

Many thanks Derision. I now have a working race detection system in place.
Dwarves, Halflings & Gnomes have a 25% increase in food value.
Barbarians, Trolls, Ogres, Iksar & Vah Shir have a 25% decrease in food value.

To answer ChaosSlayer's request:
Quote:
as far as diffirent races eating at diffirent rate - if you going to code it in, please make sure to include a rule to turn it off =)
Yes. I am adding support to turn it on / off.

If anyone can help point me in a good direction for testing which spells are currently active (in the buff window), I can code the mount's consumption. As always, any and all help is *greatly appreciated*!

Thanks,
-Codemephit
Reply With Quote
  #13  
Old 07-22-2008, 03:12 PM
Derision
Developer
 
Join Date: Feb 2004
Location: UK
Posts: 1,540
Default

This might work for testing if the player is on a mount (untested):

Code:
        if(GetBuffSlotFromType(SE_SummonHorse) != -1) {
		// We are on a mount ... your code here
		
        }
GetBuffSlotFromType looks at the buff slots for a spell with the given effect and returns a slot number if such a buff is on the player, or -1 otherwise.

zone/spdat.h has all the Spell Effect types you can use.

Another way (probably more efficient) could be:

Code:
if(GetHorseId()) {
      // We are on a mount
}
Reply With Quote
  #14  
Old 07-23-2008, 08:50 AM
CodeMephit
Fire Beetle
 
Join Date: Oct 2006
Posts: 18
Default

I have now finished the consumption code. It handles small and large races and mounts. Both of which can be turned off by rules in the database.
I made 3 characters on a live server and timed their eating & drinking. I can post the math for anyone interested, but the end result is I have an almost perfect live-like consumption rate (even with the rules turned off). I used unsigned integers which have no decimal place. To achieve a 'true' live-like rate, I would have to change this to use decimals. As it stands, food may only last a few extra seconds (42 to be exact), and I didn't think it was a big enough deal to worry with changing.

For all of this to work, 4 changes must be made. 3 to the code and 1 to the database.

In file \EQEmu-0.7.0-1119\common\ruletypes.h (Line 49) - ADD
Code:
RULE_BOOL( Character, RacialConsumption, true)
RULE_BOOL( Character, MountConsumption, true)
In file \EQEmu-0.7.0-1119\world\client.cpp (Line 957) - ADD
Code:
	pp.hunger_level		= 5000;
	pp.thirst_level		= 5000;
In file \EQEmu-0.7.0-1119\zone\client_packet.cpp (Line 1255 - 1327) - REPLACE
Code:
void Client::Handle_OP_Consume(const EQApplicationPacket *app)
{
//o----------------------------------------------------------------------------
//| Codemephit [2008.07.22] - Rewrote the consuption routine to handle racial
//|   size and mounts.
//|   Two rules were added.  They are both Boolean.
//|		Character:RacialConsumption - Turns on/off the racial size check.
//|		Character:MountConsumption - Turns on/off the mount present check.	
//o----------------------------------------------------------------------------
	//o------------------------------------------------------------------------
	//| Make sure the OP_Consume packet is valid and with no errors.  If it is
	//| not, then log the error and return to calling code.
	//o------------------------------------------------------------------------
	if (app->size != sizeof(Consume_Struct))
	{
		LogFile->write(EQEMuLog::Error, "OP size error: OP_Consume expected:%i got:%i", sizeof(Consume_Struct), app->size);
		return;
	}

	Consume_Struct* pcs = (Consume_Struct*)app->pBuffer;

	// This is the base Consumption Modifier.
	uint16 intBaseConsMod = 107; //<-- This should be 106.5 but uint16 = no decimal.
	// Apply the rule Character::ConsumptionModifier.  We need to keep the base
	// modifier seperate from the actual modifier to prevent compound calculations.
	uint16 intConsMod = intBaseConsMod * RuleI(Character, ConsumptionMultiplier) / 100;

	//o------------------------------------------------------------------------
	//| Update intConsMod for Character Size.  Only small or large characters
	//| need to be updated, as there is no modifier for a medium sized player.
	//o------------------------------------------------------------------------
	if (RuleB(Character, RacialConsumption))
	{
		if (m_pp.race == 8 || m_pp.race == 11 || m_pp.race == 12)
		{
			// Dwarf = 8, Halfling = 11, Gnome = 12
			// Player is a Small Race. (Modifier = +20%)
			intConsMod = intConsMod + (intBaseConsMod * .20);
		}

		if (m_pp.race == 2 || m_pp.race == 9 || m_pp.race == 10 || m_pp.race == 128 || m_pp.race == 130)
		{
			// Barbarian = 2, Troll = 9, Ogre = 10, Iksar = 128, Vha Shir = 130
			// Player is a Large Race.  (Modifier = -20%)
			intConsMod = intConsMod - (intBaseConsMod * .20);
		}
	}

	//o------------------------------------------------------------------------
	//| Update intConsMod for a summoned mount.  Having this check before the
	//| AA Innate Metabolism check will allow a mount to benifit from the AA
	//| as well as the player. We caculate -25% from the 'base' modifer.  This
	//| ensures that mounts will eat the same ammount regardless of what size
	//| thier owner is.
	//o------------------------------------------------------------------------
	if (RuleB(Character, MountConsumption))
	{
		if (GetHorseId() != 0)
		{
			// A mount is currently summoned.
			intConsMod = intConsMod - (intBaseConsMod * .25);
		}
	}

	//o------------------------------------------------------------------------
	//| Update intConsMod for the Innate Metabolism AA.
	//| All of these updates are done against the current modifer.
	//o------------------------------------------------------------------------
	switch(GetAA(aaInnateMetabolism))
	{
		case 1:
			// Player has Innate Metabolism I (10% increase)
			intConsMod = intConsMod + (intConsMod * .10);
			break;
		case 2:
			// Player has Innate Metabolism II (25% increase)
			intConsMod = intConsMod + (intConsMod * .25);
			break;
		case 3:
			// Player has Innate Metabolism III (50% increase)
			intConsMod = intConsMod + (intConsMod * .50);
			break;
		default:
			// Player has no ranks in Innate Metabolism (0% increase)
			break;
	}

	ItemInst *myitem = GetInv().GetItem(pcs->slot);
	if(myitem == NULL)
	{
		LogFile->write(EQEMuLog::Error, "Consuming from empty slot %d", pcs->slot);
		return;
	}
	
	const Item_Struct* eat_item = myitem->GetItem();
	if (pcs->type == 0x01)
	{
		#if EQDEBUG >= 1
			LogFile->write(EQEMuLog::Debug, "Eating from slot:%i", (int)pcs->slot);
		#endif
		m_pp.hunger_level += eat_item->CastTime*intConsMod;
		DeleteItemInInventory(pcs->slot, 1, false);
		
		if(pcs->auto_consumed != 0xffffffff) //no message if the client consumed for us
			entity_list.MessageClose_StringID(this, true, 50, 0, EATING_MESSAGE, GetName(), eat_item->Name);
	}

	else if (pcs->type == 0x02)
	{
		#if EQDEBUG >= 1
			LogFile->write(EQEMuLog::Debug, "Drinking from slot:%i", (int)pcs->slot);
		#endif
		m_pp.thirst_level += eat_item->CastTime*intConsMod;
		DeleteItemInInventory(pcs->slot, 1, false);
		
		if(pcs->auto_consumed != 0xffffffff) //no message if the client consumed for us
			entity_list.MessageClose_StringID(this, true, 50, 0, DRINKING_MESSAGE, GetName(), eat_item->Name);
	}
	else
	{
		LogFile->write(EQEMuLog::Error, "OP_Consume: unknown type, type:%i", (int)pcs->type);
		return;
	}
	if (m_pp.hunger_level > 6000)
		m_pp.hunger_level = 6000;
	if (m_pp.thirst_level > 6000)
		m_pp.thirst_level = 6000;
	EQApplicationPacket *outapp;
	outapp = new EQApplicationPacket(OP_Stamina, sizeof(Stamina_Struct));
	Stamina_Struct* sta = (Stamina_Struct*)outapp->pBuffer;
	sta->food = m_pp.hunger_level;
	sta->water = m_pp.thirst_level;

	QueuePacket(outapp);
	safe_delete(outapp);
	return;
}
Database SQL
Code:
INSERT INTO rule_values (ruleset_id, rule_name, rule_value)
   VALUES (1, 'Character:RacialConsumption', 'true'); 
INSERT INTO rule_values (ruleset_id, rule_name, rule_value)
   VALUES (1, 'Character:MountConsumption', 'true');
With all of this in place, you can set these 2 rules in the database to true or false. This turn on or off the modifiers for race and mounts.

Also for the rule Character:ConsumptionMultiplier:
A value of 100 = Normal rate.
A lower value means faster eating. (ie. 50 would mean twice as fast)
A higher value means slower eating. (ie 200 would mean twice as slow)
Just keep in mind that everything is rounded to the nearest whole number during calculations.

Thanks for all the help and ideas guys =)
-Codemephit
Reply With Quote
  #15  
Old 07-23-2008, 10:11 AM
So_1337
Dragon
 
Join Date: May 2006
Location: Cincinnati, OH
Posts: 689
Default

Great work, and quick, too!

Possibly related, but do the AAs that affect metabolism work currently? And does this code affect them or vice versa?
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 07:50 AM.


 

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