Added rules for what int mobs need to not attack red cons and how much food and drink is taken per stamina update.

Added mod hooks for food/drink values and mob aggro.
Added quest functions for getting/setting hunger and thirst.
This commit is contained in:
Tabasco 2013-08-01 19:24:15 -05:00
parent 936c8cce4b
commit fc03ee94e2
11 changed files with 296 additions and 37 deletions

View File

@ -96,6 +96,7 @@ RULE_BOOL ( Character, MaintainIntoxicationAcrossZones, true ) // If true, alcoh
RULE_BOOL ( Character, EnableDiscoveredItems, true ) // If enabled, it enables EVENT_DISCOVER_ITEM and also saves character names and timestamps for the first time an item is discovered.
RULE_BOOL ( Character, EnableXTargetting, true) // Enable Extended Targetting Window, for users with UF and later clients.
RULE_BOOL ( Character, KeepLevelOverMax, false) // Don't delevel a character that has somehow gone over the level cap
RULE_INT ( Character, FoodLossPerUpdate, 35) // How much food/water you lose per stamina update
RULE_CATEGORY_END()
RULE_CATEGORY( Mercs )
@ -398,6 +399,7 @@ RULE_INT ( Aggro, SongAggroMod, 33 )
RULE_INT ( Aggro, PetSpellAggroMod, 10 )
RULE_REAL ( Aggro, TunnelVisionAggroMod, 0.75 ) //people not currently the top hate generate this much hate on a Tunnel Vision mob
RULE_INT ( Aggro, MaxStunProcAggro, 400 ) // Set to -1 for no limit. Maxmimum amount of aggro that a stun based proc will add.
RULE_INT ( Aggro, IntAggroThreshold, 75 ) // Int <= this will aggro regardless of level difference.
RULE_CATEGORY_END()
RULE_CATEGORY ( TaskSystem)

View File

@ -163,7 +163,7 @@ void NPC::DescribeAggro(Client *towho, Mob *mob, bool verbose) {
return;
}
if(GetINT() > 75 && mob->GetLevelCon(GetLevel()) == CON_GREEN ) {
if(GetINT() > RuleI(Aggro, IntAggroThreshold) && mob->GetLevelCon(GetLevel()) == CON_GREEN ) {
towho->Message(0, "...%s is red to me (basically)", mob->GetName(),
dist2, iAggroRange2);
return;
@ -325,7 +325,7 @@ bool Mob::CheckWillAggro(Mob *mob) {
(
//old InZone check taken care of above by !mob->CastToClient()->Connected()
(
( GetINT() <= 75 )
( GetINT() <= RuleI(Aggro, IntAggroThreshold) )
||( mob->IsClient() && mob->CastToClient()->IsSitting() )
||( mob->GetLevelCon(GetLevel()) != CON_GREEN )
@ -352,7 +352,7 @@ bool Mob::CheckWillAggro(Mob *mob) {
#if EQDEBUG>=6
LogFile->write(EQEMuLog::Debug, "Check aggro for %s target %s.", GetName(), mob->GetName());
#endif
return(true);
return( mod_will_aggro(mob, this) );
}
}
#if EQDEBUG >= 6

View File

@ -7955,3 +7955,103 @@ bool Client::RemoveRespawnOption(uint8 position)
}
return false;
}
void Client::SetHunger(int32 in_hunger)
{
EQApplicationPacket *outapp;
outapp = new EQApplicationPacket(OP_Stamina, sizeof(Stamina_Struct));
Stamina_Struct* sta = (Stamina_Struct*)outapp->pBuffer;
sta->food = in_hunger;
sta->water = m_pp.thirst_level > 6000 ? 6000 : m_pp.thirst_level;
m_pp.hunger_level = in_hunger;
QueuePacket(outapp);
safe_delete(outapp);
}
void Client::SetThirst(int32 in_thirst)
{
EQApplicationPacket *outapp;
outapp = new EQApplicationPacket(OP_Stamina, sizeof(Stamina_Struct));
Stamina_Struct* sta = (Stamina_Struct*)outapp->pBuffer;
sta->food = m_pp.hunger_level > 6000 ? 6000 : m_pp.hunger_level;
sta->water = in_thirst;
m_pp.thirst_level = in_thirst;
QueuePacket(outapp);
safe_delete(outapp);
}
void Client::SetConsumption(int32 in_hunger, int32 in_thirst)
{
EQApplicationPacket *outapp;
outapp = new EQApplicationPacket(OP_Stamina, sizeof(Stamina_Struct));
Stamina_Struct* sta = (Stamina_Struct*)outapp->pBuffer;
sta->food = in_hunger;
sta->water = in_thirst;
m_pp.hunger_level = in_hunger;
m_pp.thirst_level = in_thirst;
QueuePacket(outapp);
safe_delete(outapp);
}
void Client::Consume(const Item_Struct *item, uint8 type, int16 slot, bool auto_consume)
{
if(!item) { return; }
uint16 cons_mod = 180;
switch(GetAA(aaInnateMetabolism)){
case 1:
cons_mod = cons_mod * 110 * RuleI(Character, ConsumptionMultiplier) / 10000;
break;
case 2:
cons_mod = cons_mod * 125 * RuleI(Character, ConsumptionMultiplier) / 10000;
break;
case 3:
cons_mod = cons_mod * 150 * RuleI(Character, ConsumptionMultiplier) / 10000;
break;
default:
cons_mod = cons_mod * RuleI(Character, ConsumptionMultiplier) / 100;
break;
}
if(type == ItemTypeFood)
{
int hchange = item->CastTime * cons_mod;
hchange = mod_food_value(item, hchange);
if(hchange < 0) { return; }
m_pp.hunger_level += hchange;
DeleteItemInInventory(slot, 1, false);
if(!auto_consume) //no message if the client consumed for us
entity_list.MessageClose_StringID(this, true, 50, 0, EATING_MESSAGE, GetName(), item->Name);
#if EQDEBUG >= 1
LogFile->write(EQEMuLog::Debug, "Eating from slot:%i", (int)slot);
#endif
}
else
{
int tchange = item->CastTime * cons_mod;
tchange = mod_drink_value(item, tchange);
if(tchange < 0) { return; }
m_pp.thirst_level += tchange;
DeleteItemInInventory(slot, 1, false);
if(auto_consume) //no message if the client consumed for us
entity_list.MessageClose_StringID(this, true, 50, 0, DRINKING_MESSAGE, GetName(), item->Name);
#if EQDEBUG >= 1
LogFile->write(EQEMuLog::Debug, "Drinking from slot:%i", (int)slot);
#endif
}
}

View File

@ -807,6 +807,11 @@ public:
bool Hungry() const {if (GetGM()) return false; return m_pp.hunger_level <= 3000;}
bool Thirsty() const {if (GetGM()) return false; return m_pp.thirst_level <= 3000;}
int32 GetHunger() const { return m_pp.hunger_level; }
int32 GetThirst() const { return m_pp.thirst_level; }
void SetHunger(int32 in_hunger);
void SetThirst(int32 in_thirst);
void SetConsumption(int32 in_hunger, int32 in_thirst);
bool CheckTradeLoreConflict(Client* other);
void LinkDead();
@ -1148,6 +1153,7 @@ public:
void LoadAccountFlags();
void SetAccountFlag(std::string flag, std::string val);
std::string GetAccountFlag(std::string flag); float GetDamageMultiplier(SkillType);
void Consume(const Item_Struct *item, uint8 type, int16 slot, bool auto_consume);
int mod_client_damage(int damage, SkillType skillinuse, int hand, const ItemInst* weapon, Mob* other);
bool mod_client_message(char* message, uint8 chan_num);
bool mod_can_increase_skill(SkillType skillid, Mob* against_who);
@ -1167,6 +1173,9 @@ public:
int32 mod_client_xp(int32 in_exp, NPC *npc);
uint32 mod_client_xp_for_level(uint32 xp, uint16 check_level);
int mod_client_haste_cap(int cap);
int mod_consume(Item_Struct *item, ItemTypes type, int change);
int mod_food_value(const Item_Struct *item, int change);
int mod_drink_value(const Item_Struct *item, int change);
protected:
friend class Mob;

View File

@ -1934,26 +1934,10 @@ void Client::Handle_OP_Consume(const EQApplicationPacket *app)
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*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);
Consume(eat_item, ItemTypeFood, pcs->slot, (pcs->auto_consumed == 0xffffffff));
}
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);
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);
Consume(eat_item, ItemTypeDrink, pcs->slot, (pcs->auto_consumed == 0xffffffff));
}
else {
LogFile->write(EQEMuLog::Error, "OP_Consume: unknown type, type:%i", (int)pcs->type);
@ -2183,22 +2167,11 @@ void Client::Handle_OP_ItemVerifyRequest(const EQApplicationPacket *app)
if (item->ItemType == ItemTypeFood && m_pp.hunger_level < 5000)
{
#if EQDEBUG >= 1
LogFile->write(EQEMuLog::Debug, "Eating from slot:%i", slot_id);
#endif
m_pp.hunger_level += item->CastTime*cons_mod; //roughly 1 item per 10 minutes
DeleteItemInInventory(slot_id, 1, false);
entity_list.MessageClose_StringID(this, true, 50, 0, EATING_MESSAGE, GetName(), item->Name);
Consume(item, item->ItemType, slot_id, false);
}
else if (item->ItemType == ItemTypeDrink && m_pp.thirst_level < 5000)
{
#if EQDEBUG >= 1
LogFile->write(EQEMuLog::Debug, "Drinking from slot:%i", slot_id);
#endif
// 6000 is the max. value
m_pp.thirst_level += item->CastTime*cons_mod; //roughly 1 item per 10 minutes
DeleteItemInInventory(slot_id, 1, false);
entity_list.MessageClose_StringID(this, true, 50, 0, DRINKING_MESSAGE, GetName(), item->Name);
Consume(item, item->ItemType, slot_id, false);
}
else if (item->ItemType == ItemTypeAlcohol)
{

View File

@ -1886,10 +1886,11 @@ void Client::DoStaminaUpdate() {
Stamina_Struct* sta = (Stamina_Struct*)outapp->pBuffer;
if(zone->GetZoneID() != 151) {
int loss = RuleI(Character, FoodLossPerUpdate);
if (m_pp.hunger_level > 0)
m_pp.hunger_level-=35;
m_pp.hunger_level-=loss;
if (m_pp.thirst_level > 0)
m_pp.thirst_level-=35;
m_pp.thirst_level-=loss;
sta->food = m_pp.hunger_level > 6000 ? 6000 : m_pp.hunger_level;
sta->water = m_pp.thirst_level> 6000 ? 6000 : m_pp.thirst_level;
}

View File

@ -1204,6 +1204,31 @@ void Lua_Client::QueuePacket(Lua_Packet app, bool ack_req, int client_connection
self->QueuePacket(app, ack_req, static_cast<Mob::CLIENT_CONN_STATUS>(client_connection_status), static_cast<eqFilterType>(filter));
}
int32 Lua_Client::GetHunger() {
Lua_Safe_Call_Int();
return self->GetHunger();
}
int32 Lua_Client::GetThirst() {
Lua_Safe_Call_Int();
return self->GetThirst();
}
void Lua_Client::SetHunger(int32 in_hunger) {
Lua_Safe_Call_Void();
self->SetHunger(in_hunger);
}
void Lua_Client::SetThirst(int32 in_thirst) {
Lua_Safe_Call_Void();
self->SetThirst(in_thirst);
}
void Lua_Client::SetConsumption(int32 in_hunger, int32 in_thirst) {
Lua_Safe_Call_Void();
self->SetConsumption(in_hunger, in_thirst);
}
luabind::scope lua_register_client() {
return luabind::class_<Lua_Client, Lua_Mob>("Client")
.def(luabind::constructor<>())
@ -1444,6 +1469,12 @@ luabind::scope lua_register_client() {
.def("QueuePacket", (void(Lua_Client::*)(Lua_Packet,bool))&Lua_Client::QueuePacket)
.def("QueuePacket", (void(Lua_Client::*)(Lua_Packet,bool,int))&Lua_Client::QueuePacket)
.def("QueuePacket", (void(Lua_Client::*)(Lua_Packet,bool,int,int))&Lua_Client::QueuePacket);
.def("QueuePacket", (void(Lua_Client::*)(Lua_Packet,bool,int,int))&Lua_Client::QueuePacket)
.def("GetHunger", (int32(Lua_Client::*)(void))&Lua_Client::GetHunger)
.def("GetThirst", (int32(Lua_Client::*)(void))&Lua_Client::GetThirst)
.def("SetHunger", (void(Lua_Client::*)(int32))&Lua_Client::SetHunger)
.def("SetThirst", (void(Lua_Client::*)(int32))&Lua_Client::SetThirst)
.def("SetConsumption", (void(Lua_Client::*)(int32, int32))&Lua_Client::SetConsumption);
}
luabind::scope lua_register_inventory_where() {

View File

@ -268,6 +268,11 @@ public:
void QueuePacket(Lua_Packet app, bool ack_req);
void QueuePacket(Lua_Packet app, bool ack_req, int client_connection_status);
void QueuePacket(Lua_Packet app, bool ack_req, int client_connection_status, int filter);
int32 GetHunger();
int32 GetThirst();
void SetHunger(int32 in_hunger);
void SetThirst(int32 in_thirst);
void SetConsumption(int32 in_hunger, int32 in_thirst);
};
#endif

View File

@ -833,6 +833,7 @@ public:
int mod_spell_stack(uint16 spellid1, int caster_level1, Mob* caster1, uint16 spellid2, int caster_level2, Mob* caster2);
int mod_spell_resist(int resist_chance, int level_mod, int resist_modifier, int target_resist, uint8 resist_type, uint16 spell_id, Mob* caster);
void mod_spell_cast(uint16 spell_id, Mob* spelltar, bool reflect, bool use_resist_adjust, int16 resist_adjust, bool isproc);
bool mod_will_aggro(Mob *attacker, Mob *on);
protected:
void CommonDamage(Mob* other, int32 &damage, const uint16 spell_id, const SkillType attack_skill, bool &avoidable, const int8 buffslot, const bool iBuffTic);

View File

@ -110,6 +110,10 @@ int32 Client::mod_client_xp(int32 in_xp, NPC *npc) { return(in_xp); }
//To adjust how much XP is given per kill, use mod_client_xp
uint32 Client::mod_client_xp_for_level(uint32 xp, uint16 check_level) { return(xp); }
//Food and drink values as computed by consume requests. Return < 0 to abort the request.
int Client::mod_food_value(const Item_Struct *item, int change) { return(change); }
int Client::mod_drink_value(const Item_Struct *item, int change) { return(change); }
//effect_vallue - Spell effect value as calculated by default formulas. You will want to ignore effects that don't lend themselves to scaling - pet ID's, gate coords, etc.
int Mob::mod_effect_value(int effect_value, uint16 spell_id, int effect_type, Mob* caster) { return(effect_value); }
@ -185,3 +189,6 @@ int Mob::mod_spell_resist(int resist_chance, int level_mod, int resist_modifier,
//Spell is cast by this on spelltar, called from spellontarget after the event_cast_on NPC event
void Mob::mod_spell_cast(uint16 spell_id, Mob* spelltar, bool reflect, bool use_resist_adjust, int16 resist_adjust, bool isproc) { return; }
//At this point all applicable aggro checks have succeeded. Attacker should aggro unless we return false.
bool Mob::mod_will_aggro(Mob *attacker, Mob *on) { return(true); }

View File

@ -5692,6 +5692,131 @@ XS(XS_Client_GetAccountFlag)
XSRETURN(1);
}
XS(XS_Client_GetHunger); /* prototype to pass -Wmissing-prototypes */
XS(XS_Client_GetHunger)
{
dXSARGS;
if (items != 1)
Perl_croak(aTHX_ "Usage: Client::GetHunger(THIS)");
{
Client * THIS;
int32 RETVAL;
dXSTARG;
if (sv_derived_from(ST(0), "Client")) {
IV tmp = SvIV((SV*)SvRV(ST(0)));
THIS = INT2PTR(Client *,tmp);
}
else
Perl_croak(aTHX_ "THIS is not of type Client");
if(THIS == nullptr)
Perl_croak(aTHX_ "THIS is nullptr, avoiding crash.");
RETVAL = THIS->GetHunger();
XSprePUSH; PUSHi((IV)RETVAL);
}
XSRETURN(1);
}
XS(XS_Client_GetThirst); /* prototype to pass -Wmissing-prototypes */
XS(XS_Client_GetThirst)
{
dXSARGS;
if (items != 1)
Perl_croak(aTHX_ "Usage: Client::GetThirst(THIS)");
{
Client * THIS;
int32 RETVAL;
dXSTARG;
if (sv_derived_from(ST(0), "Client")) {
IV tmp = SvIV((SV*)SvRV(ST(0)));
THIS = INT2PTR(Client *,tmp);
}
else
Perl_croak(aTHX_ "THIS is not of type Client");
if(THIS == nullptr)
Perl_croak(aTHX_ "THIS is nullptr, avoiding crash.");
RETVAL = THIS->GetThirst();
XSprePUSH; PUSHi((IV)RETVAL);
}
XSRETURN(1);
}
XS(XS_Client_SetHunger); /* prototype to pass -Wmissing-prototypes */
XS(XS_Client_SetHunger)
{
dXSARGS;
if (items != 2)
Perl_croak(aTHX_ "Usage: Client::SetHunger(THIS, in_hunger)");
{
Client * THIS;
int32 in_hunger = (uint32)SvUV(ST(1));
if (sv_derived_from(ST(0), "Client")) {
IV tmp = SvIV((SV*)SvRV(ST(0)));
THIS = INT2PTR(Client *,tmp);
}
else
Perl_croak(aTHX_ "THIS is not of type Client");
if(THIS == nullptr)
Perl_croak(aTHX_ "THIS is nullptr, avoiding crash.");
THIS->SetHunger(in_hunger);
}
XSRETURN_EMPTY;
}
XS(XS_Client_SetThirst); /* prototype to pass -Wmissing-prototypes */
XS(XS_Client_SetThirst)
{
dXSARGS;
if (items != 2)
Perl_croak(aTHX_ "Usage: Client::SetThirst(THIS, in_thirst)");
{
Client * THIS;
int32 in_thirst = (uint32)SvUV(ST(1));
if (sv_derived_from(ST(0), "Client")) {
IV tmp = SvIV((SV*)SvRV(ST(0)));
THIS = INT2PTR(Client *,tmp);
}
else
Perl_croak(aTHX_ "THIS is not of type Client");
if(THIS == nullptr)
Perl_croak(aTHX_ "THIS is nullptr, avoiding crash.");
THIS->SetThirst(in_thirst);
}
XSRETURN_EMPTY;
}
XS(XS_Client_SetConsumption); /* prototype to pass -Wmissing-prototypes */
XS(XS_Client_SetConsumption)
{
dXSARGS;
if (items != 3)
Perl_croak(aTHX_ "Usage: Client::SetHunger(THIS, in_hunger, in_thirst)");
{
Client * THIS;
int32 in_hunger = (uint32)SvUV(ST(1));
int32 in_thirst = (uint32)SvUV(ST(2));
if (sv_derived_from(ST(0), "Client")) {
IV tmp = SvIV((SV*)SvRV(ST(0)));
THIS = INT2PTR(Client *,tmp);
}
else
Perl_croak(aTHX_ "THIS is not of type Client");
if(THIS == nullptr)
Perl_croak(aTHX_ "THIS is nullptr, avoiding crash.");
THIS->SetConsumption(in_hunger, in_thirst);
}
XSRETURN_EMPTY;
}
#ifdef __cplusplus
extern "C"
#endif
@ -5920,6 +6045,11 @@ XS(boot_Client)
newXSproto(strcpy(buf, "HasSpellScribed"), XS_Client_HasSkill, file, "$$");
newXSproto(strcpy(buf, "SetAccountFlag"), XS_Client_SetAccountFlag, file, "$$");
newXSproto(strcpy(buf, "GetAccountFlag"), XS_Client_GetAccountFlag, file, "$$");
newXSproto(strcpy(buf, "GetHunger"), XS_Client_GetHunger, file, "$$");
newXSproto(strcpy(buf, "GetThirst"), XS_Client_GetThirst, file, "$$");
newXSproto(strcpy(buf, "SetHunger"), XS_Client_SetHunger, file, "$$");
newXSproto(strcpy(buf, "SetThirst"), XS_Client_SetThirst, file, "$$");
newXSproto(strcpy(buf, "SetConsumption"), XS_Client_SetConsumption, file, "$$$");
XSRETURN_YES;
}