PDA

View Full Version : New Character: Hunger / Thirst


CodeMephit
07-18-2008, 01:29 PM
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. :-D 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:

//what was the point of this? zone dosent handle this:
//pp.expAA = 0xFFFFFFFF;

// FIXME: FV roleplay, database goodness...



To:

//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

ChaosSlayer
07-18-2008, 02:15 PM
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

CodeMephit
07-18-2008, 08:34 PM
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++ :D

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:

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:

// 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

ChaosSlayer
07-18-2008, 08:53 PM
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

KLS
07-19-2008, 01:12 AM
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.

So_1337
07-19-2008, 12:08 PM
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 =)

ChaosSlayer
07-20-2008, 03:00 AM
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

CodeMephit
07-20-2008, 09:09 AM
/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:


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

ChaosSlayer
07-20-2008, 01:19 PM
/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)

CodeMephit
07-22-2008, 08:09 AM
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:

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:

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:

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

Derision
07-22-2008, 08:33 AM
Try:


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.

CodeMephit
07-22-2008, 02:57 PM
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:

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

Derision
07-22-2008, 03:12 PM
This might work for testing if the player is on a mount (untested):


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:


if(GetHorseId()) {
// We are on a mount
}

CodeMephit
07-23-2008, 08:50 AM
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

RULE_BOOL( Character, RacialConsumption, true)
RULE_BOOL( Character, MountConsumption, true)


In file \EQEmu-0.7.0-1119\world\client.cpp (Line 957) - ADD

pp.hunger_level = 5000;
pp.thirst_level = 5000;


In file \EQEmu-0.7.0-1119\zone\client_packet.cpp (Line 1255 - 1327) - REPLACE

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

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

So_1337
07-23-2008, 10:11 AM
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?

CodeMephit
07-23-2008, 11:56 AM
There is only 1 AA (Innate Metabolism) that I know of that affects eating or drinking. If there are others, please let me know and I will look into them.

My code takes into account a player having the Innate Metabolism AA and adjusts their consumption by the correct amount. This is assuming that there is support elsewhere in the code for the AA system (I am pretty sure that there is).

I will run a few tests and report back with my findings.

-Codemephit

CodeMephit
07-23-2008, 02:17 PM
Tests are complete.

Possibly related, but do the AAs that affect metabolism work currently?

Yes. Innate Metabolism works exactly as it should.


And does this code affect them or vice versa?

My code makes no changes to anything AA related. It does, however, check to see if you have any ranks in the AA Innate Metabolism and then adjusts your consumption rate accordingly.

I hope this code is of use to the community.
-Codemephit

CodeMephit
08-02-2008, 06:28 AM
If your or someone else's thread isn't stickied and it has not been applied feel free to bump it.
Odds are if it's not stickied right now I've lost it. =p


/bump =)

-Codemephit

Flare83
08-06-2008, 03:51 PM
/bump tested. Works awsome =)