From fc03ee94e246685416719a87e6b171f29fbf4e65 Mon Sep 17 00:00:00 2001 From: Tabasco Date: Thu, 1 Aug 2013 19:24:15 -0500 Subject: [PATCH] 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. --- common/ruletypes.h | 2 + zone/aggro.cpp | 6 +- zone/client.cpp | 100 +++++++++++++++++++++++++++++++ zone/client.h | 9 +++ zone/client_packet.cpp | 35 ++--------- zone/client_process.cpp | 5 +- zone/lua_client.cpp | 31 ++++++++++ zone/lua_client.h | 7 ++- zone/mob.h | 1 + zone/mod_functions.cpp | 7 +++ zone/perl_client.cpp | 130 ++++++++++++++++++++++++++++++++++++++++ 11 files changed, 296 insertions(+), 37 deletions(-) diff --git a/common/ruletypes.h b/common/ruletypes.h index 628fe3961..bdba9f334 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -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) diff --git a/zone/aggro.cpp b/zone/aggro.cpp index 3867a815d..4c9c635dd 100644 --- a/zone/aggro.cpp +++ b/zone/aggro.cpp @@ -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 diff --git a/zone/client.cpp b/zone/client.cpp index 5bd499c1d..9d8707aea 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -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 + } +} diff --git a/zone/client.h b/zone/client.h index 89ea53b98..ebf83c804 100644 --- a/zone/client.h +++ b/zone/client.h @@ -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; diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index dbc57aa5d..cd31f7626 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -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) { diff --git a/zone/client_process.cpp b/zone/client_process.cpp index 0ace11d54..3406d6fa4 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -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; } diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index 9768f2b30..f23281d66 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -1204,6 +1204,31 @@ void Lua_Client::QueuePacket(Lua_Packet app, bool ack_req, int client_connection self->QueuePacket(app, ack_req, static_cast(client_connection_status), static_cast(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_("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() { diff --git a/zone/lua_client.h b/zone/lua_client.h index dc757dd73..4de2bd8df 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -268,7 +268,12 @@ 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 -#endif \ No newline at end of file +#endif diff --git a/zone/mob.h b/zone/mob.h index 52bfec14d..e9c8ddac4 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -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); diff --git a/zone/mod_functions.cpp b/zone/mod_functions.cpp index 8beb6cd79..62f226197 100644 --- a/zone/mod_functions.cpp +++ b/zone/mod_functions.cpp @@ -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); } diff --git a/zone/perl_client.cpp b/zone/perl_client.cpp index da09a8e81..e1a3538c0 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -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; }