diff --git a/changelog.txt b/changelog.txt index 9d3535d62..72434e602 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,14 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 10/08/2017 == +Mackal: Rework regens + +Regen will now match whats reported by modern clients, besides where they lie due to known bugs + +HP and END regens are now based on the BaseData.txt values allowing easy customization +Those cases: + - The client always applies hunger penalties, it appears they don't exist anymore on live you can turn them on with a rule + - The way the client gets buff mana/end regen benefits incorrectly applies the bard mod making these values lie sometimes == 9/17/2017 == Akkadius: Add model/race offset to FixZ calc (KLS) diff --git a/common/base_data.h b/common/base_data.h index fb2766f45..4d8cf4a9f 100644 --- a/common/base_data.h +++ b/common/base_data.h @@ -24,8 +24,8 @@ struct BaseDataStruct double base_hp; double base_mana; double base_end; - double unk1; - double unk2; + double hp_regen; + double end_regen; double hp_factor; double mana_factor; double endurance_factor; diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index 79b8f7250..46aade124 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -854,6 +854,7 @@ static const uint32 MAX_PP_REF_SPELLBOOK = 480; // Set for Player Profile size r static const uint32 MAX_PP_REF_MEMSPELL = 9; // Set for Player Profile size retain static const uint32 MAX_PP_SKILL = PACKET_SKILL_ARRAY_SIZE; // 100 - actual skills buffer size +static const uint32 MAX_PP_INNATE_SKILL = 25; static const uint32 MAX_PP_AA_ARRAY = 240; static const uint32 MAX_GROUP_MEMBERS = 6; static const uint32 MAX_RECAST_TYPES = 20; @@ -993,7 +994,8 @@ struct PlayerProfile_Struct /*4768*/ int32 platinum_shared; // Platinum shared between characters /*4772*/ uint8 unknown4808[24]; /*4796*/ uint32 skills[MAX_PP_SKILL]; // [400] List of skills // 100 dword buffer -/*5196*/ uint8 unknown5132[184]; +/*5196*/ uint32 InnateSkills[MAX_PP_INNATE_SKILL]; +/*5296*/ uint8 unknown5132[84]; /*5380*/ uint32 pvp2; // /*5384*/ uint32 unknown5420; // /*5388*/ uint32 pvptype; // diff --git a/common/patches/rof.cpp b/common/patches/rof.cpp index 7ce6f900f..1df8d1aa5 100644 --- a/common/patches/rof.cpp +++ b/common/patches/rof.cpp @@ -2184,11 +2184,11 @@ namespace RoF outapp->WriteUInt32(emu->skills[r]); } - outapp->WriteUInt32(25); // Unknown count + outapp->WriteUInt32(structs::MAX_PP_INNATE_SKILL); // Innate Skills count - for (uint32 r = 0; r < 25; r++) + for (uint32 r = 0; r < structs::MAX_PP_INNATE_SKILL; r++) { - outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(emu->InnateSkills[r]); // Innate Skills (regen, slam, etc) } outapp->WriteUInt32(structs::MAX_PP_DISCIPLINES); // Discipline count diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index afb994526..57a8d9eb6 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -2261,11 +2261,11 @@ namespace RoF2 outapp->WriteUInt32(emu->skills[r]); } - outapp->WriteUInt32(25); // Unknown count + outapp->WriteUInt32(structs::MAX_PP_INNATE_SKILL); // Innate Skills count - for (uint32 r = 0; r < 25; r++) + for (uint32 r = 0; r < structs::MAX_PP_INNATE_SKILL; r++) { - outapp->WriteUInt32(0); // Unknown + outapp->WriteUInt32(emu->InnateSkills[r]); // Innate Skills (regen, slam, etc) } outapp->WriteUInt32(structs::MAX_PP_DISCIPLINES); // Discipline count diff --git a/common/patches/rof2_structs.h b/common/patches/rof2_structs.h index 41fc9528a..28905dd4a 100644 --- a/common/patches/rof2_structs.h +++ b/common/patches/rof2_structs.h @@ -131,6 +131,7 @@ static const uint32 MAX_PP_LANGUAGE = 32; // was 25 static const uint32 MAX_PP_SPELLBOOK = 720; // was 480 static const uint32 MAX_PP_MEMSPELL = 16; // was 12 static const uint32 MAX_PP_SKILL = PACKET_SKILL_ARRAY_SIZE; // 100 - actual skills buffer size +static const uint32 MAX_PP_INNATE_SKILL = 25; static const uint32 MAX_PP_AA_ARRAY = 300; static const uint32 MAX_PP_DISCIPLINES = 300; // was 200 static const uint32 MAX_GROUP_MEMBERS = 6; @@ -1155,8 +1156,8 @@ union /*01012*/ AA_Array aa_array[MAX_PP_AA_ARRAY]; // [300] 3600 bytes - AAs 12 bytes each /*04612*/ uint32 skill_count; // Seen 100 /*04616*/ uint32 skills[MAX_PP_SKILL]; // [100] 400 bytes - List of skills -/*05016*/ uint32 unknown15_count; // Seen 25 -/*05020*/ uint32 unknown_rof15[25]; // Most are 255 or 0 +/*05016*/ uint32 InnateSkills_count; // Seen 25 +/*05020*/ uint32 InnateSkills[MAX_PP_INNATE_SKILL]; // Most are 255 or 0 /*05120*/ uint32 discipline_count; // Seen 200 /*05124*/ Disciplines_Struct disciplines; // [200] 800 bytes Known disciplines /*05924*/ uint32 timestamp_count; // Seen 20 diff --git a/common/patches/rof_structs.h b/common/patches/rof_structs.h index 24439d7f0..8de4fb9ab 100644 --- a/common/patches/rof_structs.h +++ b/common/patches/rof_structs.h @@ -131,6 +131,7 @@ static const uint32 MAX_PP_LANGUAGE = 32; // was 25 static const uint32 MAX_PP_SPELLBOOK = 720; // was 480 static const uint32 MAX_PP_MEMSPELL = 16; // was 12 static const uint32 MAX_PP_SKILL = PACKET_SKILL_ARRAY_SIZE; // 100 - actual skills buffer size +static const uint32 MAX_PP_INNATE_SKILL = 25; static const uint32 MAX_PP_AA_ARRAY = 300; static const uint32 MAX_PP_DISCIPLINES = 200; // was 100 static const uint32 MAX_GROUP_MEMBERS = 6; @@ -1096,8 +1097,8 @@ union /*01012*/ AA_Array aa_array[MAX_PP_AA_ARRAY]; // [300] 3600 bytes - AAs 12 bytes each /*04612*/ uint32 skill_count; // Seen 100 /*04616*/ uint32 skills[MAX_PP_SKILL]; // [100] 400 bytes - List of skills -/*05016*/ uint32 unknown15_count; // Seen 25 -/*05020*/ uint32 unknown_rof15[25]; // Most are 255 or 0 +/*05016*/ uint32 InnateSkills_count; // Seen 25 +/*05020*/ uint32 InnateSkills[MAX_PP_INNATE_SKILL]; // Most are 255 or 0 /*05120*/ uint32 discipline_count; // Seen 200 /*05124*/ Disciplines_Struct disciplines; // [200] 800 bytes Known disciplines /*05924*/ uint32 timestamp_count; // Seen 20 diff --git a/common/patches/sod.cpp b/common/patches/sod.cpp index 276f939e5..a77feedbc 100644 --- a/common/patches/sod.cpp +++ b/common/patches/sod.cpp @@ -1605,6 +1605,7 @@ namespace SoD OUT(copper_cursor); OUT_array(skills, structs::MAX_PP_SKILL); // 1:1 direct copy (100 dword) + OUT_array(InnateSkills, structs::MAX_PP_INNATE_SKILL); // 1:1 direct copy (25 dword) // OUT(unknown04760[236]); OUT(toxicity); diff --git a/common/patches/sod_structs.h b/common/patches/sod_structs.h index af449f758..88ccd7788 100644 --- a/common/patches/sod_structs.h +++ b/common/patches/sod_structs.h @@ -818,6 +818,7 @@ static const uint32 MAX_PP_LANGUAGE = 25; // static const uint32 MAX_PP_SPELLBOOK = 480; // Confirmed 60 pages on Live now static const uint32 MAX_PP_MEMSPELL = 10; //was 9 now 10 on Live static const uint32 MAX_PP_SKILL = PACKET_SKILL_ARRAY_SIZE; // 100 - actual skills buffer size +static const uint32 MAX_PP_INNATE_SKILL = 25; static const uint32 MAX_PP_AA_ARRAY = 300; //was 299 static const uint32 MAX_GROUP_MEMBERS = 6; static const uint32 MAX_RECAST_TYPES = 20; @@ -923,7 +924,8 @@ struct PlayerProfile_Struct /*06488*/ uint32 silver_cursor; // Silver Pieces on cursor /*06492*/ uint32 copper_cursor; // Copper Pieces on cursor /*06496*/ uint32 skills[MAX_PP_SKILL]; // [400] List of skills // 100 dword buffer -/*06896*/ uint8 unknown04760[136]; +/*06896*/ uint32 InnateSkills[MAX_PP_SKILL]; +/*06996*/ uint8 unknown04760[36]; /*07032*/ uint32 toxicity; // Potion Toxicity (15=too toxic, each potion adds 3) /*07036*/ uint32 thirst_level; // Drink (ticks till next drink) /*07040*/ uint32 hunger_level; // Food (ticks till next eat) diff --git a/common/patches/sof.cpp b/common/patches/sof.cpp index 77d65ce16..9df6ca1df 100644 --- a/common/patches/sof.cpp +++ b/common/patches/sof.cpp @@ -1276,6 +1276,7 @@ namespace SoF OUT(copper_cursor); OUT_array(skills, structs::MAX_PP_SKILL); // 1:1 direct copy (100 dword) + OUT_array(InnateSkills, structs::MAX_PP_INNATE_SKILL); // 1:1 direct copy (25 dword) // OUT(unknown04760[236]); OUT(toxicity); diff --git a/common/patches/sof_structs.h b/common/patches/sof_structs.h index 00fcd4ecf..2a1eab3f0 100644 --- a/common/patches/sof_structs.h +++ b/common/patches/sof_structs.h @@ -799,6 +799,7 @@ static const uint32 MAX_PP_LANGUAGE = 25; // static const uint32 MAX_PP_SPELLBOOK = 480; // Confirmed 60 pages on Live now static const uint32 MAX_PP_MEMSPELL = 10; //was 9 now 10 on Live static const uint32 MAX_PP_SKILL = PACKET_SKILL_ARRAY_SIZE; // 100 - actual skills buffer size +static const uint32 MAX_PP_INNATE_SKILL = 25; static const uint32 MAX_PP_AA_ARRAY = 300; //was 299 static const uint32 MAX_GROUP_MEMBERS = 6; static const uint32 MAX_RECAST_TYPES = 20; @@ -903,7 +904,8 @@ struct PlayerProfile_Struct //23576 Octets /*06488*/ uint32 silver_cursor; // Silver Pieces on cursor /*06492*/ uint32 copper_cursor; // Copper Pieces on cursor /*06496*/ uint32 skills[MAX_PP_SKILL]; // [400] List of skills // 100 dword buffer -/*06896*/ uint8 unknown04760[136]; +/*06896*/ uint32 InnateSkills[MAX_PP_INNATE_SKILL]; +/*06996*/ uint8 unknown04760[36]; /*07032*/ uint32 toxicity; // Potion Toxicity (15=too toxic, each potion adds 3) /*07036*/ uint32 thirst_level; // Drink (ticks till next drink) /*07040*/ uint32 hunger_level; // Food (ticks till next eat) diff --git a/common/patches/titanium.cpp b/common/patches/titanium.cpp index e9679dc06..45f267223 100644 --- a/common/patches/titanium.cpp +++ b/common/patches/titanium.cpp @@ -1020,6 +1020,7 @@ namespace Titanium OUT(copper_cursor); OUT_array(skills, structs::MAX_PP_SKILL); // 1:1 direct copy (100 dword) + OUT_array(InnateSkills, structs::MAX_PP_INNATE_SKILL); // 1:1 direct copy (25 dword) // OUT(unknown04760[236]); OUT(toxicity); diff --git a/common/patches/titanium_structs.h b/common/patches/titanium_structs.h index 69988dd7c..56213854a 100644 --- a/common/patches/titanium_structs.h +++ b/common/patches/titanium_structs.h @@ -740,6 +740,7 @@ static const uint32 MAX_PP_LANGUAGE = 28; static const uint32 MAX_PP_SPELLBOOK = 400; static const uint32 MAX_PP_MEMSPELL = 9; static const uint32 MAX_PP_SKILL = PACKET_SKILL_ARRAY_SIZE; // 100 - actual skills buffer size +static const uint32 MAX_PP_INNATE_SKILL = 25; static const uint32 MAX_PP_AA_ARRAY = 240; static const uint32 MAX_GROUP_MEMBERS = 6; static const uint32 MAX_RECAST_TYPES = 20; @@ -844,7 +845,8 @@ struct PlayerProfile_Struct /*04452*/ uint32 silver_cursor; // Silver Pieces on cursor /*04456*/ uint32 copper_cursor; // Copper Pieces on cursor /*04460*/ uint32 skills[MAX_PP_SKILL]; // [400] List of skills // 100 dword buffer -/*04860*/ uint8 unknown04760[136]; +/*04860*/ uint32 InnateSkills[MAX_PP_INNATE_SKILL]; +/*04960*/ uint8 unknown04760[36]; /*04996*/ uint32 toxicity; // Potion Toxicity (15=too toxic, each potion adds 3) /*05000*/ uint32 thirst_level; // Drink (ticks till next drink) /*05004*/ uint32 hunger_level; // Food (ticks till next eat) diff --git a/common/patches/uf.cpp b/common/patches/uf.cpp index 8b0c86cc5..87cd6c700 100644 --- a/common/patches/uf.cpp +++ b/common/patches/uf.cpp @@ -1850,6 +1850,7 @@ namespace UF OUT(copper_cursor); OUT_array(skills, structs::MAX_PP_SKILL); // 1:1 direct copy (100 dword) + OUT_array(InnateSkills, structs::MAX_PP_INNATE_SKILL); // 1:1 direct copy (25 dword) // OUT(unknown04760[236]); OUT(toxicity); diff --git a/common/patches/uf_structs.h b/common/patches/uf_structs.h index b187f9f82..cc5564e11 100644 --- a/common/patches/uf_structs.h +++ b/common/patches/uf_structs.h @@ -848,6 +848,7 @@ static const uint32 MAX_PP_LANGUAGE = 25; // static const uint32 MAX_PP_SPELLBOOK = 720; // Confirmed 60 pages on Underfoot now static const uint32 MAX_PP_MEMSPELL = 12; //was 9 now 10 on Underfoot static const uint32 MAX_PP_SKILL = PACKET_SKILL_ARRAY_SIZE; // 100 - actual skills buffer size +static const uint32 MAX_PP_INNATE_SKILL = 25; static const uint32 MAX_PP_AA_ARRAY = 300; //was 299 static const uint32 MAX_GROUP_MEMBERS = 6; static const uint32 MAX_RECAST_TYPES = 20; @@ -954,7 +955,8 @@ struct PlayerProfile_Struct /*07336*/ uint32 silver_cursor; // Silver Pieces on cursor /*07340*/ uint32 copper_cursor; // Copper Pieces on cursor /*07344*/ uint32 skills[MAX_PP_SKILL]; // [400] List of skills // 100 dword buffer -/*07744*/ uint8 unknown07644[136]; +/*07744*/ uint32 InnateSkills[MAX_PP_INNATE_SKILL]; +/*07844*/ uint8 unknown07644[36]; /*07880*/ uint32 toxicity; // Potion Toxicity (15=too toxic, each potion adds 3) /*07884*/ uint32 thirst_level; // Drink (ticks till next drink) /*07888*/ uint32 hunger_level; // Food (ticks till next eat) diff --git a/common/ruletypes.h b/common/ruletypes.h index 67591d402..e1475fa79 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -70,7 +70,7 @@ RULE_INT(Character, ConsumptionMultiplier, 100) //item's hunger restored = this RULE_BOOL(Character, HealOnLevel, false) RULE_BOOL(Character, FeignKillsPet, false) RULE_INT(Character, ItemManaRegenCap, 15) -RULE_INT(Character, ItemHealthRegenCap, 35) +RULE_INT(Character, ItemHealthRegenCap, 30) RULE_INT(Character, ItemDamageShieldCap, 30) RULE_INT(Character, ItemAccuracyCap, 150) RULE_INT(Character, ItemAvoidanceCap, 100) @@ -91,10 +91,12 @@ RULE_INT(Character, HasteCap, 100) // Haste cap for non-v3(overhaste) haste. RULE_INT(Character, SkillUpModifier, 100) //skill ups are at 100% RULE_BOOL(Character, SharedBankPlat, false) //off by default to prevent duping for now RULE_BOOL(Character, BindAnywhere, false) -RULE_INT(Character, RestRegenPercent, 0) // Set to >0 to enable rest state bonus HP and mana regen. +RULE_BOOL(Character, RestRegenEnabled, true) // Enable OOC Regen +RULE_INT(Character, RestRegenHP, 180) // seconds until full from 0. this is actually zone setable, but most or all zones are 180 +RULE_INT(Character, RestRegenMana, 180) // seconds until full from 0. this is actually zone setable, but most or all zones are 180 +RULE_INT(Character, RestRegenEnd, 180) // seconds until full from 0. this is actually zone setable, but most or all zones are 180 RULE_INT(Character, RestRegenTimeToActivate, 30) // Time in seconds for rest state regen to kick in. RULE_INT(Character, RestRegenRaidTimeToActivate, 300) // Time in seconds for rest state regen to kick in with a raid target. -RULE_BOOL(Character, RestRegenEndurance, false) // Whether rest regen will work for endurance or not. RULE_INT(Character, KillsPerGroupLeadershipAA, 250) // Number of dark blues or above per Group Leadership AA RULE_INT(Character, KillsPerRaidLeadershipAA, 250) // Number of dark blues or above per Raid Leadership AA RULE_INT(Character, MaxFearDurationForPlayerCharacter, 4) //4 tics, each tic calculates every 6 seconds. @@ -118,6 +120,7 @@ RULE_BOOL(Character, EnableXTargetting, true) // Enable Extended Targetting Wind RULE_BOOL(Character, EnableAggroMeter, true) // Enable Aggro Meter, for users with RoF 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, 32) // How much food/water you lose per stamina update +RULE_BOOL(Character, EnableHungerPenalties, false) // being hungry/thirsty has negative effects -- it does appear normal live servers do not have penalties RULE_INT(Character, BaseInstrumentSoftCap, 36) // Softcap for instrument mods, 36 commonly referred to as "3.6" as well. RULE_BOOL(Character, UseSpellFileSongCap, true) // When they removed the AA that increased the cap they removed the above and just use the spell field RULE_INT(Character, BaseRunSpeedCap, 158) // Base Run Speed Cap, on live it's 158% which will give you a runspeed of 1.580 hard capped to 225. diff --git a/common/shareddb.cpp b/common/shareddb.cpp index 331826ef2..09c936425 100644 --- a/common/shareddb.cpp +++ b/common/shareddb.cpp @@ -1817,8 +1817,8 @@ void SharedDatabase::LoadBaseData(void *data, int max_level) { bd->base_hp = atof(row[2]); bd->base_mana = atof(row[3]); bd->base_end = atof(row[4]); - bd->unk1 = atof(row[5]); - bd->unk2 = atof(row[6]); + bd->hp_regen = atof(row[5]); + bd->end_regen = atof(row[6]); bd->hp_factor = atof(row[7]); bd->mana_factor = atof(row[8]); bd->endurance_factor = atof(row[9]); diff --git a/common/spdat.h b/common/spdat.h index 605e9a6cf..0f9e7a47a 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -131,6 +131,11 @@ enum SpellAffectIndex { SAI_NPC_Special_80 = 80, SAI_Trap_Lock = 88 }; + +enum class GlobalGroup { + Lich = 46, +}; + enum RESISTTYPE { RESIST_NONE = 0, diff --git a/utils/sql/git/optional/2017_10_07_new_default_rule_values.sql b/utils/sql/git/optional/2017_10_07_new_default_rule_values.sql new file mode 100644 index 000000000..47ebcf913 --- /dev/null +++ b/utils/sql/git/optional/2017_10_07_new_default_rule_values.sql @@ -0,0 +1,3 @@ +UPDATE `rule_values` SET `rule_value` = '32' WHERE `rule_name` = 'Character:FoodLossPerUpdate'; +UPDATE `rule_values` SET `rule_value` = '30' WHERE `rule_name` = 'Character:ItemHealthRegenCap'; + diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index acf604cf1..c444ff9dd 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -915,7 +915,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) newbon->GivePetGroupTarget = true; break; case SE_ItemHPRegenCapIncrease: - newbon->ItemHPRegenCap = +base1; + newbon->ItemHPRegenCap += base1; break; case SE_Ambidexterity: newbon->Ambidexterity += base1; diff --git a/zone/bot.cpp b/zone/bot.cpp index a2e7a2fec..6a1521d6b 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -6727,7 +6727,7 @@ int32 Bot::CalcATK() { } void Bot::CalcRestState() { - if(!RuleI(Character, RestRegenPercent)) + if(!RuleB(Character, RestRegenEnabled)) return; RestRegenHP = RestRegenMana = RestRegenEndurance = 0; @@ -6743,10 +6743,9 @@ void Bot::CalcRestState() { } } - RestRegenHP = (GetMaxHP() * RuleI(Character, RestRegenPercent) / 100); - RestRegenMana = (GetMaxMana() * RuleI(Character, RestRegenPercent) / 100); - if(RuleB(Character, RestRegenEndurance)) - RestRegenEndurance = (GetMaxEndurance() * RuleI(Character, RestRegenPercent) / 100); + RestRegenHP = 6 * (GetMaxHP() / RuleI(Character, RestRegenHP)); + RestRegenMana = 6 * (GetMaxMana() / RuleI(Character, RestRegenMana)); + RestRegenEndurance = 6 * (GetMaxEndurance() / RuleI(Character, RestRegenEnd)); } int32 Bot::LevelRegen() { diff --git a/zone/client.cpp b/zone/client.cpp index 19dfeb711..f72d510d5 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -160,7 +160,8 @@ Client::Client(EQStreamInterface* ieqs) npc_close_scan_timer(6000), hp_self_update_throttle_timer(300), hp_other_update_throttle_timer(500), - position_update_timer(10000) + position_update_timer(10000), + tmSitting(0) { for (int client_filter = 0; client_filter < _FilterCount; client_filter++) @@ -270,9 +271,10 @@ Client::Client(EQStreamInterface* ieqs) m_ClientVersion = EQEmu::versions::ClientVersion::Unknown; m_ClientVersionBit = 0; AggroCount = 0; - RestRegenHP = 0; - RestRegenMana = 0; - RestRegenEndurance = 0; + ooc_regen = false; + AreaHPRegen = 1.0f; + AreaManaRegen = 1.0f; + AreaEndRegen = 1.0f; XPRate = 100; current_endurance = 0; @@ -329,6 +331,9 @@ Client::Client(EQStreamInterface* ieqs) interrogateinv_flag = false; + for (int i = 0; i < InnateSkillMax; ++i) + m_pp.InnateSkills[i] = InnateDisabled; + AI_Init(); } @@ -4598,7 +4603,7 @@ void Client::IncrementAggroCount() { // AggroCount++; - if(!RuleI(Character, RestRegenPercent)) + if(!RuleB(Character, RestRegenEnabled)) return; // If we already had aggro before this method was called, the combat indicator should already be up for SoF clients, @@ -4635,7 +4640,7 @@ void Client::DecrementAggroCount() { AggroCount--; - if(!RuleI(Character, RestRegenPercent)) + if(!RuleB(Character, RestRegenEnabled)) return; // Something else is still aggro on us, can't rest yet. @@ -6722,7 +6727,7 @@ void Client::SendStatsWindow(Client* client, bool use_window) cap_regen_field = itoa(CalcHPRegenCap()); spell_regen_field = itoa(spellbonuses.HPRegen); aa_regen_field = itoa(aabonuses.HPRegen); - total_regen_field = itoa(CalcHPRegen()); + total_regen_field = itoa(CalcHPRegen(true)); break; } case 1: { @@ -6735,7 +6740,7 @@ void Client::SendStatsWindow(Client* client, bool use_window) cap_regen_field = itoa(CalcManaRegenCap()); spell_regen_field = itoa(spellbonuses.ManaRegen); aa_regen_field = itoa(aabonuses.ManaRegen); - total_regen_field = itoa(CalcManaRegen()); + total_regen_field = itoa(CalcManaRegen(true)); } else { continue; } break; @@ -6749,7 +6754,7 @@ void Client::SendStatsWindow(Client* client, bool use_window) cap_regen_field = itoa(CalcEnduranceRegenCap()); spell_regen_field = itoa(spellbonuses.EnduranceRegen); aa_regen_field = itoa(aabonuses.EnduranceRegen); - total_regen_field = itoa(CalcEnduranceRegen()); + total_regen_field = itoa(CalcEnduranceRegen(true)); break; } default: { break; } @@ -9066,3 +9071,196 @@ void Client::SetPetCommandState(int button, int state) FastQueuePacket(&app); } +bool Client::CanMedOnHorse() +{ + // no horse is false + if (GetHorseId() == 0) + return false; + + // can't med while attacking + if (auto_attack) + return false; + + return animation == 0 && m_Delta.x == 0.0f && m_Delta.y == 0.0f; // TODO: animation is SpeedRun +} + +void Client::EnableAreaHPRegen(int value) +{ + AreaHPRegen = value * 0.001f; + SendAppearancePacket(AT_AreaHPRegen, value); // does this send to whole zone? + // send test and particles? +} + +void Client::DisableAreaHPRegen() +{ + AreaHPRegen = 1.0f; + SendAppearancePacket(AT_AreaHPRegen, 1000); +} + +void Client::EnableAreaManaRegen(int value) +{ + AreaManaRegen = value * 0.001f; + SendAppearancePacket(AT_AreaManaRegen, value); // does this send to whole zone? + // send test and particles? +} + +void Client::DisableAreaManaRegen() +{ + AreaManaRegen = 1.0f; + SendAppearancePacket(AT_AreaManaRegen, 1000); +} + +void Client::EnableAreaEndRegen(int value) +{ + AreaEndRegen = value * 0.001f; + SendAppearancePacket(AT_AreaEndRegen, value); // does this send to whole zone? + // send test and particles? +} + +void Client::DisableAreaEndRegen() +{ + AreaEndRegen = 1.0f; + SendAppearancePacket(AT_AreaEndRegen, 1000); +} + +void Client::EnableAreaRegens(int value) +{ + EnableAreaHPRegen(value); + EnableAreaManaRegen(value); + EnableAreaEndRegen(value); +} + +void Client::DisableAreaRegens() +{ + DisableAreaHPRegen(); + DisableAreaManaRegen(); + DisableAreaEndRegen(); +} + +void Client::InitInnates() +{ + // this function on the client also inits the level one innate skills (like swimming, hide, etc) + // we won't do that here, lets just do the InnateSkills for now. Basically translation of what the client is doing + // A lot of these we could probably have ignored because they have no known use or are 100% client side + // but I figured just in case we'll do them all out + // + // The client calls this in a few places. When you remove a vision buff and in SetHeights, which is called in + // illusions, mounts, and a bunch of other cases. All of the calls to InitInnates are wrapped in restoring regen + // besides the call initializing the first time + auto race = GetRace(); + auto class_ = GetClass(); + + for (int i = 0; i < InnateSkillMax; ++i) + m_pp.InnateSkills[i] = InnateDisabled; + + m_pp.InnateSkills[InnateInspect] = InnateEnabled; + m_pp.InnateSkills[InnateOpen] = InnateEnabled; + if (race >= RT_FROGLOK_3) { + if (race == RT_SKELETON_2 || race == RT_FROGLOK_3) + m_pp.InnateSkills[InnateUltraVision] = InnateEnabled; + else + m_pp.InnateSkills[InnateInfravision] = InnateEnabled; + } + switch (race) { + case RT_BARBARIAN: + case RT_BARBARIAN_2: + m_pp.InnateSkills[InnateSlam] = InnateEnabled; + break; + case RT_ERUDITE: + case RT_ERUDITE_2: + m_pp.InnateSkills[InnateLore] = InnateEnabled; + break; + case RT_WOOD_ELF: + case RT_GUARD_3: + m_pp.InnateSkills[InnateInfravision] = InnateEnabled; + break; + case RT_HIGH_ELF: + case RT_GUARD_2: + m_pp.InnateSkills[InnateInfravision] = InnateEnabled; + m_pp.InnateSkills[InnateLore] = InnateEnabled; + break; + case RT_DARK_ELF: + case RT_DARK_ELF_2: + case RT_VAMPIRE_2: + m_pp.InnateSkills[InnateUltraVision] = InnateEnabled; + break; + case RT_TROLL: + case RT_TROLL_2: + m_pp.InnateSkills[InnateRegen] = InnateEnabled; + m_pp.InnateSkills[InnateSlam] = InnateEnabled; + m_pp.InnateSkills[InnateInfravision] = InnateEnabled; + break; + case RT_DWARF: + case RT_DWARF_2: + m_pp.InnateSkills[InnateInfravision] = InnateEnabled; + break; + case RT_OGRE: + case RT_OGRE_2: + m_pp.InnateSkills[InnateInfravision] = InnateEnabled; + m_pp.InnateSkills[InnateSlam] = InnateEnabled; + m_pp.InnateSkills[InnateNoBash] = InnateEnabled; + m_pp.InnateSkills[InnateBashDoor] = InnateEnabled; + break; + case RT_HALFLING: + case RT_HALFLING_2: + m_pp.InnateSkills[InnateInfravision] = InnateEnabled; + break; + case RT_GNOME: + m_pp.InnateSkills[InnateInfravision] = InnateEnabled; + m_pp.InnateSkills[InnateLore] = InnateEnabled; + break; + case RT_IKSAR: + m_pp.InnateSkills[InnateRegen] = InnateEnabled; + m_pp.InnateSkills[InnateInfravision] = InnateEnabled; + break; + case RT_VAH_SHIR: + m_pp.InnateSkills[InnateInfravision] = InnateEnabled; + break; + case RT_FROGLOK_2: + case RT_GHOST: + case RT_GHOUL: + case RT_SKELETON: + case RT_VAMPIRE: + case RT_WILL_O_WISP: + case RT_ZOMBIE: + case RT_SPECTRE: + case RT_GHOST_2: + case RT_GHOST_3: + case RT_DRAGON_2: + case RT_INNORUUK: + m_pp.InnateSkills[InnateUltraVision] = InnateEnabled; + break; + case RT_HUMAN: + case RT_GUARD: + case RT_BEGGAR: + case RT_HUMAN_2: + case RT_HUMAN_3: + case RT_FROGLOK_3: // client does froglok weird, but this should work out fine + break; + default: + m_pp.InnateSkills[InnateInfravision] = InnateEnabled; + break; + } + + switch (class_) { + case DRUID: + m_pp.InnateSkills[InnateHarmony] = InnateEnabled; + break; + case BARD: + m_pp.InnateSkills[InnateReveal] = InnateEnabled; + break; + case ROGUE: + m_pp.InnateSkills[InnateSurprise] = InnateEnabled; + m_pp.InnateSkills[InnateReveal] = InnateEnabled; + break; + case RANGER: + m_pp.InnateSkills[InnateAwareness] = InnateEnabled; + break; + case MONK: + m_pp.InnateSkills[InnateSurprise] = InnateEnabled; + m_pp.InnateSkills[InnateAwareness] = InnateEnabled; + default: + break; + } +} + diff --git a/zone/client.h b/zone/client.h index 051d433e4..e334f8fd9 100644 --- a/zone/client.h +++ b/zone/client.h @@ -199,6 +199,27 @@ struct RespawnOption float heading; }; +// do not ask what all these mean because I have no idea! +// named from the client's CEverQuest::GetInnateDesc, they're missing some +enum eInnateSkill { + InnateEnabled = 0, + InnateAwareness = 1, + InnateBashDoor = 2, + InnateBreathFire = 3, + InnateHarmony = 4, + InnateInfravision = 6, + InnateLore = 8, + InnateNoBash = 9, + InnateRegen = 10, + InnateSlam = 11, + InnateSurprise = 12, + InnateUltraVision = 13, + InnateInspect = 14, + InnateOpen = 15, + InnateReveal = 16, + InnateSkillMax = 25, // size of array in client + InnateDisabled = 255 +}; const uint32 POPUPID_UPDATE_SHOWSTATSWINDOW = 1000000; @@ -406,6 +427,16 @@ public: const int32& SetMana(int32 amount); int32 CalcManaRegenCap(); + // guild pool regen shit. Sends a SpawnAppearance with a value that regens to value * 0.001 + void EnableAreaHPRegen(int value); + void DisableAreaHPRegen(); + void EnableAreaManaRegen(int value); + void DisableAreaManaRegen(); + void EnableAreaEndRegen(int value); + void DisableAreaEndRegen(); + void EnableAreaRegens(int value); + void DisableAreaRegens(); + void ServerFilter(SetServerFilter_Struct* filter); void BulkSendTraderInventory(uint32 char_id); void SendSingleTraderItem(uint32 char_id, int uniqueid); @@ -540,7 +571,7 @@ public: /*Endurance and such*/ void CalcMaxEndurance(); //This calculates the maximum endurance we can have int32 CalcBaseEndurance(); //Calculates Base End - int32 CalcEnduranceRegen(); //Calculates endurance regen used in DoEnduranceRegen() + int32 CalcEnduranceRegen(bool bCombat = false); //Calculates endurance regen used in DoEnduranceRegen() int32 GetEndurance() const {return current_endurance;} //This gets our current endurance int32 GetMaxEndurance() const {return max_end;} //This gets our endurance from the last CalcMaxEndurance() call int32 CalcEnduranceRegenCap(); @@ -719,6 +750,7 @@ public: void SendTradeskillDetails(uint32 recipe_id); bool TradeskillExecute(DBTradeskillRecipe_Struct *spec); void CheckIncreaseTradeskill(int16 bonusstat, int16 stat_modifier, float skillup_modifier, uint16 success_modifier, EQEmu::skills::SkillType tradeskill); + void InitInnates(); void GMKill(); inline bool IsMedding() const {return medding;} @@ -760,6 +792,9 @@ public: void SummonHorse(uint16 spell_id); void SetHorseId(uint16 horseid_in); uint16 GetHorseId() const { return horseId; } + bool CanMedOnHorse(); + + bool CanFastRegen() const { return ooc_regen; } void NPCSpawn(NPC *target_npc, const char *identifier, uint32 extra = 0); @@ -862,6 +897,7 @@ public: void SetHunger(int32 in_hunger); void SetThirst(int32 in_thirst); void SetConsumption(int32 in_hunger, int32 in_thirst); + bool IsStarved() const { if (GetGM() || !RuleB(Character, EnableHungerPenalties)) return false; return m_pp.hunger_level == 0 || m_pp.thirst_level == 0; } bool CheckTradeLoreConflict(Client* other); bool CheckTradeNonDroppable(); @@ -1345,8 +1381,8 @@ private: int32 CalcCorrup(); int32 CalcMaxHP(); int32 CalcBaseHP(); - int32 CalcHPRegen(); - int32 CalcManaRegen(); + int32 CalcHPRegen(bool bCombat = false); + int32 CalcManaRegen(bool bCombat = false); int32 CalcBaseManaRegen(); uint32 GetClassHPFactor(); void DoHPRegen(); @@ -1410,6 +1446,7 @@ private: std::string BuyerWelcomeMessage; bool AbilityTimer; int Haste; //precalced value + uint32 tmSitting; // time stamp started sitting, used for HP regen bonus added on MAY 5, 2004 int32 max_end; int32 current_endurance; @@ -1513,9 +1550,10 @@ private: unsigned int AggroCount; // How many mobs are aggro on us. - unsigned int RestRegenHP; - unsigned int RestRegenMana; - unsigned int RestRegenEndurance; + bool ooc_regen; + float AreaHPRegen; + float AreaManaRegen; + float AreaEndRegen; bool EngagedRaidTarget; uint32 SavedRaidRestTimer; diff --git a/zone/client_mods.cpp b/zone/client_mods.cpp index 6db0437c5..541454a58 100644 --- a/zone/client_mods.cpp +++ b/zone/client_mods.cpp @@ -22,6 +22,8 @@ #include "../common/rulesys.h" #include "../common/spdat.h" +#include "../common/data_verification.h" + #include "client.h" #include "mob.h" @@ -231,16 +233,81 @@ int32 Client::LevelRegen() return hp; } -int32 Client::CalcHPRegen() +int32 Client::CalcHPRegen(bool bCombat) { - int32 regen = LevelRegen() + itembonuses.HPRegen + spellbonuses.HPRegen; - regen += aabonuses.HPRegen + GroupLeadershipAAHealthRegeneration(); + int item_regen = itembonuses.HPRegen; // worn spells and +regen, already capped + item_regen += GetHeroicSTA() / 20; + + item_regen += aabonuses.HPRegen; + + int base = 0; + auto base_data = database.GetBaseData(GetLevel(), GetClass()); + if (base_data) + base = static_cast(base_data->hp_regen); + + auto level = GetLevel(); + bool skip_innate = false; + + if (IsSitting()) { + if (level >= 50) { + base++; + if (level >= 65) + base++; + } + + if ((Timer::GetCurrentTime() - tmSitting) > 60000) { + if (!IsAffectedByBuffByGlobalGroup(GlobalGroup::Lich)) { + auto tic_diff = std::min((Timer::GetCurrentTime() - tmSitting) / 60000, static_cast(9)); + if (tic_diff != 1) { // starts at 2 mins + int tic_bonus = tic_diff * 1.5 * base; + if (m_pp.InnateSkills[InnateRegen] != InnateDisabled) + tic_bonus = tic_bonus * 1.2; + base = tic_bonus; + skip_innate = true; + } else if (m_pp.InnateSkills[InnateRegen] == InnateDisabled) { // no innate regen gets first tick + int tic_bonus = base * 1.5; + base = tic_bonus; + } + } + } + } + + if (!skip_innate && m_pp.InnateSkills[InnateRegen] != InnateDisabled) { + if (level >= 50) { + ++base; + if (level >= 55) + ++base; + } + base *= 2; + } + + if (IsStarved()) + base = 0; + + base += GroupLeadershipAAHealthRegeneration(); + // some IsKnockedOut that sets to -1 + base = base * 100.0f * AreaHPRegen * 0.01f + 0.5f; + // another check for IsClient && !(base + item_regen) && Cur_HP <= 0 do --base; do later + + if (!bCombat && CanFastRegen() && (IsSitting() || CanMedOnHorse())) { + auto fast_mod = RuleI(Character, RestRegenHP); // TODO: this is actually zone based + auto max_hp = GetMaxHP(); + int fast_regen = 6 * (max_hp / fast_mod); + if (base < fast_regen) // weird, but what the client is doing + base = fast_regen; + } + + int regen = base + item_regen + spellbonuses.HPRegen; // TODO: client does this in buff tick return (regen * RuleI(Character, HPRegenMultiplier) / 100); } int32 Client::CalcHPRegenCap() { - int cap = RuleI(Character, ItemHealthRegenCap) + itembonuses.HeroicSTA / 25; + int cap = RuleI(Character, ItemHealthRegenCap); + if (GetLevel() > 60) + cap = std::max(cap, GetLevel() - 30); // if the rule is set greater than normal I guess + if (GetLevel() > 65) + cap += GetLevel() - 65; cap += aabonuses.ItemHPRegenCap + spellbonuses.ItemHPRegenCap + itembonuses.ItemHPRegenCap; return (cap * RuleI(Character, HPRegenMultiplier) / 100); } @@ -1169,43 +1236,72 @@ int32 Client::CalcBaseManaRegen() return regen; } -int32 Client::CalcManaRegen() +int32 Client::CalcManaRegen(bool bCombat) { - uint8 clevel = GetLevel(); - int32 regen = 0; - //this should be changed so we dont med while camping, etc... - if (IsSitting() || (GetHorseId() != 0)) { - BuffFadeBySitModifier(); - if (HasSkill(EQEmu::skills::SkillMeditate)) { - this->medding = true; - regen = (((GetSkill(EQEmu::skills::SkillMeditate) / 10) + (clevel - (clevel / 4))) / 4) + 4; - regen += spellbonuses.ManaRegen + itembonuses.ManaRegen; - CheckIncreaseSkill(EQEmu::skills::SkillMeditate, nullptr, -5); - } - else { - regen = 2 + spellbonuses.ManaRegen + itembonuses.ManaRegen; + int regen = 0; + auto level = GetLevel(); + if (!IsStarved()) { + // client does some base regen for shrouds here + if (IsSitting() || CanMedOnHorse()) { + // kind of weird to do it here w/e + // client does some base medding regen for shrouds here + if (GetClass() != BARD) { + auto skill = GetSkill(EQEmu::skills::SkillMeditate); + if (skill > 0) { + regen++; + if (skill > 1) + regen++; + if (skill >= 15) + regen += skill / 15; + } + } } } - else { - this->medding = false; - regen = 2 + spellbonuses.ManaRegen + itembonuses.ManaRegen; + + if (level > 61) { + regen++; + if (level > 63) + regen++; } - //AAs + regen += aabonuses.ManaRegen; + // add in + 1 bonus for SE_CompleteHeal, but we don't do anything for it yet? + + int item_bonus = itembonuses.ManaRegen; // this is capped already + int heroic_bonus = 0; + + switch (GetCasterClass()) { + case 'W': + heroic_bonus = GetHeroicWIS(); + break; + default: + heroic_bonus = GetHeroicINT(); + break; + } + + item_bonus += heroic_bonus / 25; + regen += item_bonus; + + if (level <= 70 && regen > 65) + regen = 65; + + regen = regen * 100.0f * AreaManaRegen * 0.01f + 0.5f; + + if (!bCombat && CanFastRegen() && (IsSitting() || CanMedOnHorse())) { + auto fast_mod = RuleI(Character, RestRegenMana); // TODO: this is actually zone based + auto max_mana = GetMaxMana(); + int fast_regen = 6 * (max_mana / fast_mod); + if (regen < fast_regen) // weird, but what the client is doing + regen = fast_regen; + } + + regen += spellbonuses.ManaRegen; // TODO: live does this in buff tick return (regen * RuleI(Character, ManaRegenMultiplier) / 100); } int32 Client::CalcManaRegenCap() { int32 cap = RuleI(Character, ItemManaRegenCap) + aabonuses.ItemManaRegenCap; - switch (GetCasterClass()) { - case 'I': - cap += (itembonuses.HeroicINT / 25); - break; - case 'W': - cap += (itembonuses.HeroicWIS / 25); - break; - } return (cap * RuleI(Character, ManaRegenMultiplier) / 100); } @@ -2091,16 +2187,91 @@ int32 Client::CalcBaseEndurance() return base_end; } -int32 Client::CalcEnduranceRegen() +int32 Client::CalcEnduranceRegen(bool bCombat) { - int32 regen = int32(GetLevel() * 4 / 10) + 2; - regen += aabonuses.EnduranceRegen + spellbonuses.EnduranceRegen + itembonuses.EnduranceRegen; + int base = 0; + if (!IsStarved()) { + auto base_data = database.GetBaseData(GetLevel(), GetClass()); + if (base_data) { + base = static_cast(base_data->end_regen); + if (!auto_attack && base > 0) + base += base / 2; + } + } + + // so when we are mounted, our local client SpeedRun is always 0, so this is always false, but the packets we process it to our own shit :P + bool is_running = runmode && animation != 0 && GetHorseId() == 0; // TODO: animation is really what MQ2 calls SpeedRun + + int weight_limit = GetSTR(); + auto level = GetLevel(); + if (GetClass() == MONK) { + if (level > 99) + weight_limit = 58; + else if (level > 94) + weight_limit = 57; + else if (level > 89) + weight_limit = 56; + else if (level > 84) + weight_limit = 55; + else if (level > 79) + weight_limit = 54; + else if (level > 64) + weight_limit = 53; + else if (level > 63) + weight_limit = 50; + else if (level > 61) + weight_limit = 47; + else if (level > 59) + weight_limit = 45; + else if (level > 54) + weight_limit = 40; + else if (level > 50) + weight_limit = 38; + else if (level > 44) + weight_limit = 36; + else if (level > 29) + weight_limit = 34; + else if (level > 14) + weight_limit = 32; + else + weight_limit = 30; + } + + bool encumbered = (CalcCurrentWeight() / 10) >= weight_limit; + + if (is_running) + base += level / -15; + + if (encumbered) + base += level / -15; + + auto item_bonus = GetHeroicAGI() + GetHeroicDEX() + GetHeroicSTA() + GetHeroicSTR(); + item_bonus = item_bonus / 4 / 50; + item_bonus += itembonuses.EnduranceRegen; // this is capped already + base += item_bonus; + + base = base * AreaEndRegen + 0.5f; + + auto aa_regen = aabonuses.EnduranceRegen; + + int regen = base; + if (!bCombat && CanFastRegen() && (IsSitting() || CanMedOnHorse())) { + auto fast_mod = RuleI(Character, RestRegenEnd); // TODO: this is actually zone based + auto max_end = GetMaxEndurance(); + int fast_regen = 6 * (max_end / fast_mod); + if (aa_regen < fast_regen) // weird, but what the client is doing + aa_regen = fast_regen; + } + + regen += aa_regen; + regen += spellbonuses.EnduranceRegen; // TODO: client does this in buff tick + return (regen * RuleI(Character, EnduranceRegenMultiplier) / 100); } int32 Client::CalcEnduranceRegenCap() { - int cap = (RuleI(Character, ItemEnduranceRegenCap) + itembonuses.HeroicSTR / 25 + itembonuses.HeroicDEX / 25 + itembonuses.HeroicAGI / 25 + itembonuses.HeroicSTA / 25); + int cap = RuleI(Character, ItemEnduranceRegenCap); return (cap * RuleI(Character, EnduranceRegenMultiplier) / 100); } diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 7b48b207b..fe5ab8d92 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -1414,6 +1414,8 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) if (class_ == MONK) consume_food_timer.SetTimer(CONSUMPTION_MNK_TIMER); + InitInnates(); + /* If GM not set in DB, and does not meet min status to be GM, reset */ if (m_pp.gm && admin < minStatusToBeGM) m_pp.gm = 0; @@ -13054,6 +13056,8 @@ void Client::Handle_OP_SpawnAppearance(const EQApplicationPacket *app) InterruptSpell(); SetFeigned(false); BindWound(this, false, true); + tmSitting = Timer::GetCurrentTime(); + BuffFadeBySitModifier(); } else if (sa->parameter == ANIM_CROUCH) { if (!UseBardSpellLogic()) diff --git a/zone/client_process.cpp b/zone/client_process.cpp index 4802c24a9..a0bdeead2 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -1820,7 +1820,7 @@ void Client::OPGMSummon(const EQApplicationPacket *app) } void Client::DoHPRegen() { - SetHP(GetHP() + CalcHPRegen() + RestRegenHP); + SetHP(GetHP() + CalcHPRegen()); SendHPUpdate(); } @@ -1828,7 +1828,10 @@ void Client::DoManaRegen() { if (GetMana() >= max_mana && spellbonuses.ManaRegen >= 0) return; - SetMana(GetMana() + CalcManaRegen() + RestRegenMana); + if (GetMana() < max_mana && (IsSitting() || CanMedOnHorse()) && HasSkill(EQEmu::skills::SkillMeditate)) + CheckIncreaseSkill(EQEmu::skills::SkillMeditate, nullptr, -5); + + SetMana(GetMana() + CalcManaRegen()); CheckManaEndUpdate(); } @@ -1869,10 +1872,11 @@ void Client::DoStaminaHungerUpdate() void Client::DoEnduranceRegen() { - if(GetEndurance() >= GetMaxEndurance()) - return; + // endurance has some negative mods that could result in a negative regen when starved + int regen = CalcEnduranceRegen(); - SetEndurance(GetEndurance() + CalcEnduranceRegen() + RestRegenEndurance); + if (regen < 0 || (regen > 0 && GetEndurance() < GetMaxEndurance())) + SetEndurance(GetEndurance() + regen); } void Client::DoEnduranceUpkeep() { @@ -1921,12 +1925,12 @@ void Client::CalcRestState() { // The client must have been out of combat for RuleI(Character, RestRegenTimeToActivate) seconds, // must be sitting down, and must not have any detrimental spells affecting them. // - if(!RuleI(Character, RestRegenPercent)) + if(!RuleB(Character, RestRegenEnabled)) return; - RestRegenHP = RestRegenMana = RestRegenEndurance = 0; + ooc_regen = false; - if(AggroCount || !IsSitting()) + if(AggroCount || !(IsSitting() || CanMedOnHorse())) return; if(!rest_timer.Check(false)) @@ -1941,12 +1945,8 @@ void Client::CalcRestState() { } } - RestRegenHP = (GetMaxHP() * RuleI(Character, RestRegenPercent) / 100); + ooc_regen = true; - RestRegenMana = (GetMaxMana() * RuleI(Character, RestRegenPercent) / 100); - - if(RuleB(Character, RestRegenEndurance)) - RestRegenEndurance = (GetMaxEndurance() * RuleI(Character, RestRegenPercent) / 100); } void Client::DoTracking() diff --git a/zone/horse.cpp b/zone/horse.cpp index efe4fe898..fe5762d5a 100644 --- a/zone/horse.cpp +++ b/zone/horse.cpp @@ -151,6 +151,7 @@ void Client::SummonHorse(uint16 spell_id) { uint16 tmpID = horse->GetID(); SetHorseId(tmpID); + BuffFadeBySitModifier(); } diff --git a/zone/merc.cpp b/zone/merc.cpp index a3ff051f2..03e618881 100644 --- a/zone/merc.cpp +++ b/zone/merc.cpp @@ -1154,7 +1154,7 @@ void Merc::CalcRestState() { // The bot must have been out of combat for RuleI(Character, RestRegenTimeToActivate) seconds, // must be sitting down, and must not have any detrimental spells affecting them. // - if(!RuleI(Character, RestRegenPercent)) + if(!RuleB(Character, RestRegenEnabled)) return; RestRegenHP = RestRegenMana = RestRegenEndurance = 0; @@ -1174,12 +1174,11 @@ void Merc::CalcRestState() { } } - RestRegenHP = (GetMaxHP() * RuleI(Character, RestRegenPercent) / 100); + RestRegenHP = 6 * (GetMaxHP() / RuleI(Character, RestRegenHP)); - RestRegenMana = (GetMaxMana() * RuleI(Character, RestRegenPercent) / 100); + RestRegenMana = 6 * (GetMaxMana() / RuleI(Character, RestRegenMana)); - if(RuleB(Character, RestRegenEndurance)) - RestRegenEndurance = (GetMaxEndurance() * RuleI(Character, RestRegenPercent) / 100); + RestRegenEndurance = 6 * (GetMaxEndurance() / RuleI(Character, RestRegenEnd)); } bool Merc::HasSkill(EQEmu::skills::SkillType skill_id) const { diff --git a/zone/mob.h b/zone/mob.h index ccf92014b..e0aecb95b 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -336,6 +336,7 @@ public: void BuffFadeDetrimentalByCaster(Mob *caster); void BuffFadeBySitModifier(); bool IsAffectedByBuff(uint16 spell_id); + bool IsAffectedByBuffByGlobalGroup(GlobalGroup group); void BuffModifyDurationBySpellID(uint16 spell_id, int32 newDuration); int AddBuff(Mob *caster, const uint16 spell_id, int duration = 0, int32 level_override = -1); int CanBuffStack(uint16 spellid, uint8 caster_level, bool iFailIfOverwrite = false); diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 46b44fb3a..375572014 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -60,6 +60,9 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove const SPDat_Spell_Struct &spell = spells[spell_id]; + if (spell.disallow_sit && IsBuffSpell(spell_id) && IsClient() && (CastToClient()->IsSitting() || CastToClient()->GetHorseId() != 0)) + return false; + bool c_override = false; if (caster && caster->IsClient() && GetCastedSpellInvSlot() > 0) { const EQEmu::ItemInstance *inst = caster->CastToClient()->GetInv().GetItem(GetCastedSpellInvSlot()); diff --git a/zone/spells.cpp b/zone/spells.cpp index 0bc897912..877886757 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -4234,6 +4234,19 @@ bool Mob::IsAffectedByBuff(uint16 spell_id) return false; } +bool Mob::IsAffectedByBuffByGlobalGroup(GlobalGroup group) +{ + int buff_count = GetMaxTotalSlots(); + for (int i = 0; i < buff_count; ++i) { + if (buffs[i].spellid == SPELL_UNKNOWN) + continue; + if (spells[buffs[i].spellid].spell_category == static_cast(group)) + return true; + } + + return false; +} + // checks if 'this' can be affected by spell_id from caster // returns true if the spell should fail, false otherwise bool Mob::IsImmuneToSpell(uint16 spell_id, Mob *caster)