Compare commits

..

4 Commits

Author SHA1 Message Date
Fryguy b044d8533e [Release] 22.51.1 (#4353)
### Fixes

* Adjust return for perl release check  @Akkadius 2024-05-26
* Corrected issue with bazaar purchase via parcels where an incorrect quantity would be calculated. ([#4352](https://github.com/EQEmu/Server/pull/4352)) @neckkola 2024-05-27

### Performance

* Improve SkillCaps::GetTrainLevel() Efficiency ([#4350](https://github.com/EQEmu/Server/pull/4350)) @Kinglykrab 2024-05-26

### Rules

* Legacy Compute Defense against modern agi based defense. ([#4349](https://github.com/EQEmu/Server/pull/4349)) @fryguy503 2024-05-27
2024-05-27 16:21:55 -05:00
Mitch Freeman d810cb02c3 [Fix] Corrected issue with bazaar purchase via parcels where an incorrect quantity would be calculated. (#4352) 2024-05-27 17:06:30 -04:00
Fryguy 992a5cc132 [Rule] Legacy Compute Defense against modern agi based defense. (#4349)
* [Rule] Legacy Compute Defense against modern agi based defense.

In new code, AGI becomes a large contributor to avoidance at low levels, since AGI isn't capped by Level but Defense is A scale factor is implemented for PCs to reduce the effect of AGI at low levels.  This isn't applied to NPCs since they can be easily controlled via the Database.

* `snake_case`

---------

Co-authored-by: Kinglykrab <kinglykrab@gmail.com>
2024-05-26 20:27:18 -04:00
Alex King c50fda0f73 [Performance] Improve SkillCaps::GetTrainLevel() Efficiency (#4350)
* [Performance] Improve SkillCaps::GetTrainLevel() Efficiency

* Finalize
2024-05-26 18:55:00 -05:00
16 changed files with 138 additions and 65 deletions
+15
View File
@@ -1,3 +1,18 @@
## [22.51.1] 5/27/2024
### Fixes
* Adjust return for perl release check @Akkadius 2024-05-26
* Corrected issue with bazaar purchase via parcels where an incorrect quantity would be calculated. ([#4352](https://github.com/EQEmu/Server/pull/4352)) @neckkola 2024-05-27
### Performance
* Improve SkillCaps::GetTrainLevel() Efficiency ([#4350](https://github.com/EQEmu/Server/pull/4350)) @Kinglykrab 2024-05-26
### Rules
* Legacy Compute Defense against modern agi based defense. ([#4349](https://github.com/EQEmu/Server/pull/4349)) @fryguy503 2024-05-27
## [22.51.0] 5/26/2024 ## [22.51.0] 5/26/2024
### Commands ### Commands
+1
View File
@@ -614,6 +614,7 @@ RULE_INT(Combat, PCAttackPowerScaling, 100, "Applies scaling to PC Attack Power
RULE_INT(Combat, PCAccuracyAvoidanceMod2Scale, 100, "Scale Factor for PC Accuracy and Avoidance (Mod2, found on items). Found a value of 100 to make both too strong (75 = x0.75). DEFAULT: 100 to not adjust existing Servers") RULE_INT(Combat, PCAccuracyAvoidanceMod2Scale, 100, "Scale Factor for PC Accuracy and Avoidance (Mod2, found on items). Found a value of 100 to make both too strong (75 = x0.75). DEFAULT: 100 to not adjust existing Servers")
RULE_BOOL(Combat, AllowRaidTargetBlind, false, "Toggle to allow raid targets to be blinded, default is false (Live-like)") RULE_BOOL(Combat, AllowRaidTargetBlind, false, "Toggle to allow raid targets to be blinded, default is false (Live-like)")
RULE_BOOL(Combat, RogueBackstabHasteCorrection, false, "Toggle to enable correction for Haste impacting Backstab DPS too much. DEFAULT: false") RULE_BOOL(Combat, RogueBackstabHasteCorrection, false, "Toggle to enable correction for Haste impacting Backstab DPS too much. DEFAULT: false")
RULE_BOOL(Combat, LegacyComputeDefense, false, "Trim AGI Scaling of defense mostly for lower levels to help compensate for the newer agi based defense system. Default: False")
RULE_CATEGORY_END() RULE_CATEGORY_END()
RULE_CATEGORY(NPC) RULE_CATEGORY(NPC)
+7 -6
View File
@@ -1,4 +1,5 @@
#include "skill_caps.h" #include "skill_caps.h"
#include "timer.h"
SkillCaps *SkillCaps::SetContentDatabase(Database *db) SkillCaps *SkillCaps::SetContentDatabase(Database *db)
{ {
@@ -13,15 +14,17 @@ SkillCapsRepository::SkillCaps SkillCaps::GetSkillCap(uint8 class_id, EQ::skills
return SkillCapsRepository::NewEntity(); return SkillCapsRepository::NewEntity();
} }
uint64_t key = (class_id * 1000000) + (level * 1000) + static_cast<uint32>(skill_id); const uint64_t key = (class_id * 1000000) + (level * 1000) + static_cast<uint32>(skill_id);
auto pos = m_skill_caps.find(key); auto pos = m_skill_caps.find(key);
if (pos != m_skill_caps.end()) { if (pos != m_skill_caps.end()) {
return pos->second; return pos->second;
} }
return SkillCapsRepository::NewEntity(); return SkillCapsRepository::NewEntity();
} }
uint8 SkillCaps::GetTrainLevel(uint8 class_id, EQ::skills::SkillType skill_id, uint8 level) uint8 SkillCaps::GetSkillTrainLevel(uint8 class_id, EQ::skills::SkillType skill_id, uint8 level)
{ {
if ( if (
!IsPlayerClass(class_id) || !IsPlayerClass(class_id) ||
@@ -38,16 +41,14 @@ uint8 SkillCaps::GetTrainLevel(uint8 class_id, EQ::skills::SkillType skill_id, u
); );
const uint8 max_level = level > skill_cap_max_level ? level : skill_cap_max_level; const uint8 max_level = level > skill_cap_max_level ? level : skill_cap_max_level;
const uint64_t key = (class_id * 1000000) + (level * 1000) + static_cast<uint32>(skill_id);
for (const auto &e: m_skill_caps) {
for (uint8 current_level = 1; current_level <= max_level; current_level++) { for (uint8 current_level = 1; current_level <= max_level; current_level++) {
uint64_t key = (class_id * 1000000) + (level * 1000) + static_cast<uint32>(skill_id);
auto pos = m_skill_caps.find(key); auto pos = m_skill_caps.find(key);
if (pos != m_skill_caps.end()) { if (pos != m_skill_caps.end()) {
return current_level; return current_level;
} }
} }
}
return 0; return 0;
} }
@@ -67,7 +68,7 @@ void SkillCaps::LoadSkillCaps()
continue; continue;
} }
uint64_t key = (e.class_id * 1000000) + (e.level * 1000) + e.skill_id; const uint64_t key = (e.class_id * 1000000) + (e.level * 1000) + e.skill_id;
m_skill_caps[key] = e; m_skill_caps[key] = e;
} }
+1 -1
View File
@@ -10,7 +10,7 @@ class SkillCaps {
public: public:
inline void ClearSkillCaps() { m_skill_caps.clear(); } inline void ClearSkillCaps() { m_skill_caps.clear(); }
SkillCapsRepository::SkillCaps GetSkillCap(uint8 class_id, EQ::skills::SkillType skill_id, uint8 level); SkillCapsRepository::SkillCaps GetSkillCap(uint8 class_id, EQ::skills::SkillType skill_id, uint8 level);
uint8 GetTrainLevel(uint8 class_id, EQ::skills::SkillType skill_id, uint8 level); uint8 GetSkillTrainLevel(uint8 class_id, EQ::skills::SkillType skill_id, uint8 level);
void LoadSkillCaps(); void LoadSkillCaps();
void ReloadSkillCaps(); void ReloadSkillCaps();
+1 -1
View File
@@ -25,7 +25,7 @@
// Build variables // Build variables
// these get injected during the build pipeline // these get injected during the build pipeline
#define CURRENT_VERSION "22.51.0-dev" // always append -dev to the current version for custom-builds #define CURRENT_VERSION "22.51.1-dev" // always append -dev to the current version for custom-builds
#define LOGIN_VERSION "0.8.0" #define LOGIN_VERSION "0.8.0"
#define COMPILE_DATE __DATE__ #define COMPILE_DATE __DATE__
#define COMPILE_TIME __TIME__ #define COMPILE_TIME __TIME__
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "eqemu-server", "name": "eqemu-server",
"version": "22.51.0", "version": "22.51.1",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/EQEmu/Server.git" "url": "https://github.com/EQEmu/Server.git"
+28 -3
View File
@@ -256,19 +256,44 @@ int Mob::GetTotalToHit(EQ::skills::SkillType skill, int chance_mod)
int Mob::compute_defense() int Mob::compute_defense()
{ {
int defense = GetSkill(EQ::skills::SkillDefense) * 400 / 225; int defense = GetSkill(EQ::skills::SkillDefense) * 400 / 225;
// In new code, AGI becomes a large contributor to avoidance at low levels, since AGI isn't capped by Level but Defense is
// A scale factor is implemented for PCs to reduce the effect of AGI at low levels. This isn't applied to NPCs since they can be
// easily controlled via the Database.
if (RuleB(Combat, LegacyComputeDefense)) {
int agi_scale_factor = 1000;
if (IsOfClientBot()) {
agi_scale_factor = std::min(1000, static_cast<int>(GetLevel()) * 1000 / 70); // Scales Agi Contribution for PC's Level, max Contribution at Level 70
}
defense += agi_scale_factor * (800 * (GetAGI() - 40)) / 3600 / 1000;
if (IsOfClientBot()) {
defense += GetHeroicAGI() / 10;
}
defense += itembonuses.AvoidMeleeChance * RuleI(Combat, PCAccuracyAvoidanceMod2Scale) / 100; // item mod2
} else {
defense += (8000 * (GetAGI() - 40)) / 36000; defense += (8000 * (GetAGI() - 40)) / 36000;
if (IsOfClientBot()) { if (IsOfClientBot()) {
defense += itembonuses.heroic_agi_avoidance; defense += itembonuses.heroic_agi_avoidance;
} }
defense += itembonuses.AvoidMeleeChance; // item mod2
}
//516 SE_AC_Mitigation_Max_Percent //516 SE_AC_Mitigation_Max_Percent
auto ac_bonus = itembonuses.AC_Mitigation_Max_Percent + aabonuses.AC_Mitigation_Max_Percent + spellbonuses.AC_Mitigation_Max_Percent; auto ac_bonus = itembonuses.AC_Mitigation_Max_Percent + aabonuses.AC_Mitigation_Max_Percent + spellbonuses.AC_Mitigation_Max_Percent;
if (ac_bonus) if (ac_bonus) {
defense += round(static_cast<double>(defense) * static_cast<double>(ac_bonus) * 0.0001); defense += round(static_cast<double>(defense) * static_cast<double>(ac_bonus) * 0.0001);
}
defense += itembonuses.AvoidMeleeChance; // item mod2 if (IsNPC()) {
if (IsNPC())
defense += CastToNPC()->GetAvoidanceRating(); defense += CastToNPC()->GetAvoidanceRating();
}
if (IsClient()) { if (IsClient()) {
double reduction = CastToClient()->GetIntoxication() / 2.0; double reduction = CastToClient()->GetIntoxication() / 2.0;
+2 -2
View File
@@ -2803,7 +2803,7 @@ uint16 Client::MaxSkill(EQ::skills::SkillType skill_id, uint8 class_id, uint8 le
return skill_caps.GetSkillCap(class_id, skill_id, level).cap; return skill_caps.GetSkillCap(class_id, skill_id, level).cap;
} }
uint8 Client::SkillTrainLevel(EQ::skills::SkillType skill_id, uint8 class_id) uint8 Client::GetSkillTrainLevel(EQ::skills::SkillType skill_id, uint8 class_id)
{ {
if ( if (
ClientVersion() < EQ::versions::ClientVersion::RoF2 && ClientVersion() < EQ::versions::ClientVersion::RoF2 &&
@@ -2813,7 +2813,7 @@ uint8 Client::SkillTrainLevel(EQ::skills::SkillType skill_id, uint8 class_id)
skill_id = EQ::skills::Skill2HPiercing; skill_id = EQ::skills::Skill2HPiercing;
} }
return skill_caps.GetTrainLevel(class_id, skill_id, RuleI(Character, MaxLevel)); return skill_caps.GetSkillTrainLevel(class_id, skill_id, RuleI(Character, MaxLevel));
} }
uint16 Client::GetMaxSkillAfterSpecializationRules(EQ::skills::SkillType skillid, uint16 maxSkill) uint16 Client::GetMaxSkillAfterSpecializationRules(EQ::skills::SkillType skillid, uint16 maxSkill)
+1 -1
View File
@@ -866,7 +866,7 @@ public:
uint16 MaxSkill(EQ::skills::SkillType skill_id, uint8 class_id, uint8 level) const; uint16 MaxSkill(EQ::skills::SkillType skill_id, uint8 class_id, uint8 level) const;
inline uint16 MaxSkill(EQ::skills::SkillType skill_id) const { return MaxSkill(skill_id, GetClass(), GetLevel()); } inline uint16 MaxSkill(EQ::skills::SkillType skill_id) const { return MaxSkill(skill_id, GetClass(), GetLevel()); }
uint8 SkillTrainLevel(EQ::skills::SkillType skill_id, uint8 class_id); uint8 GetSkillTrainLevel(EQ::skills::SkillType skill_id, uint8 class_id);
void MaxSkills(); void MaxSkills();
void SendTradeskillSearchResults(const std::string &query, unsigned long objtype, unsigned long someid); void SendTradeskillSearchResults(const std::string &query, unsigned long objtype, unsigned long someid);
+1 -1
View File
@@ -1718,7 +1718,7 @@ void Client::OPGMTrainSkill(const EQApplicationPacket *app)
if (skilllevel == 0) { if (skilllevel == 0) {
//this is a new skill.. //this is a new skill..
uint16 t_level = SkillTrainLevel(skill, GetClass()); uint16 t_level = GetSkillTrainLevel(skill, GetClass());
if (t_level == 0) { if (t_level == 0) {
LogSkills("Tried to train a new skill [{}] which is invalid for this race/class.", skill); LogSkills("Tried to train a new skill [{}] which is invalid for this race/class.", skill);
+7
View File
@@ -3375,6 +3375,12 @@ void Lua_Client::ResetLeadershipAA()
self->ResetLeadershipAA(); self->ResetLeadershipAA();
} }
uint8 Lua_Client::GetSkillTrainLevel(int skill_id)
{
Lua_Safe_Call_Int();
return self->GetSkillTrainLevel(static_cast<EQ::skills::SkillType>(skill_id), self->GetClass());
}
luabind::scope lua_register_client() { luabind::scope lua_register_client() {
return luabind::class_<Lua_Client, Lua_Mob>("Client") return luabind::class_<Lua_Client, Lua_Mob>("Client")
.def(luabind::constructor<>()) .def(luabind::constructor<>())
@@ -3624,6 +3630,7 @@ luabind::scope lua_register_client() {
.def("GetScribeableSpells", (luabind::object(Lua_Client::*)(lua_State* L,uint8,uint8))&Lua_Client::GetScribeableSpells) .def("GetScribeableSpells", (luabind::object(Lua_Client::*)(lua_State* L,uint8,uint8))&Lua_Client::GetScribeableSpells)
.def("GetScribedSpells", (luabind::object(Lua_Client::*)(lua_State* L))&Lua_Client::GetScribedSpells) .def("GetScribedSpells", (luabind::object(Lua_Client::*)(lua_State* L))&Lua_Client::GetScribedSpells)
.def("GetSkillPoints", (int(Lua_Client::*)(void))&Lua_Client::GetSkillPoints) .def("GetSkillPoints", (int(Lua_Client::*)(void))&Lua_Client::GetSkillPoints)
.def("GetSkillTrainLevel", (uint8(Lua_Client::*)(int))&Lua_Client::GetSkillTrainLevel)
.def("GetSpellDamage", (int(Lua_Client::*)(void))&Lua_Client::GetSpellDamage) .def("GetSpellDamage", (int(Lua_Client::*)(void))&Lua_Client::GetSpellDamage)
.def("GetSpellIDByBookSlot", (uint32(Lua_Client::*)(int))&Lua_Client::GetSpellIDByBookSlot) .def("GetSpellIDByBookSlot", (uint32(Lua_Client::*)(int))&Lua_Client::GetSpellIDByBookSlot)
.def("GetSpentAA", (int(Lua_Client::*)(void))&Lua_Client::GetSpentAA) .def("GetSpentAA", (int(Lua_Client::*)(void))&Lua_Client::GetSpentAA)
+1
View File
@@ -504,6 +504,7 @@ public:
bool SetAutoLoginCharacterName(std::string character_name); bool SetAutoLoginCharacterName(std::string character_name);
void DescribeSpecialAbilities(Lua_NPC n); void DescribeSpecialAbilities(Lua_NPC n);
void ResetLeadershipAA(); void ResetLeadershipAA();
uint8 GetSkillTrainLevel(int skill_id);
void ApplySpell(int spell_id); void ApplySpell(int spell_id);
void ApplySpell(int spell_id, int duration); void ApplySpell(int spell_id, int duration);
+6
View File
@@ -3173,6 +3173,11 @@ void Perl_Client_ResetLeadershipAA(Client* self)
self->ResetLeadershipAA(); self->ResetLeadershipAA();
} }
uint8 Perl_Client_GetSkillTrainLevel(Client* self, int skill_id)
{
return self->GetSkillTrainLevel(static_cast<EQ::skills::SkillType>(skill_id), self->GetClass());
}
void perl_register_client() void perl_register_client()
{ {
perl::interpreter perl(PERL_GET_THX); perl::interpreter perl(PERL_GET_THX);
@@ -3431,6 +3436,7 @@ void perl_register_client()
package.add("GetTaskActivityDoneCount", &Perl_Client_GetTaskActivityDoneCount); package.add("GetTaskActivityDoneCount", &Perl_Client_GetTaskActivityDoneCount);
package.add("GetThirst", &Perl_Client_GetThirst); package.add("GetThirst", &Perl_Client_GetThirst);
package.add("GetTotalSecondsPlayed", &Perl_Client_GetTotalSecondsPlayed); package.add("GetTotalSecondsPlayed", &Perl_Client_GetTotalSecondsPlayed);
package.add("GetSkillTrainLevel", &Perl_Client_GetSkillTrainLevel);
package.add("GetWeight", &Perl_Client_GetWeight); package.add("GetWeight", &Perl_Client_GetWeight);
package.add("GetPEQZoneFlags", &Perl_Client_GetPEQZoneFlags); package.add("GetPEQZoneFlags", &Perl_Client_GetPEQZoneFlags);
package.add("GetZoneFlags", &Perl_Client_GetZoneFlags); package.add("GetZoneFlags", &Perl_Client_GetZoneFlags);
+11
View File
@@ -3555,6 +3555,17 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati
ps.item_slot = parcel_out.slot_id; ps.item_slot = parcel_out.slot_id;
strn0cpy(ps.send_to, GetCleanName(), sizeof(ps.send_to)); strn0cpy(ps.send_to, GetCleanName(), sizeof(ps.send_to));
if (trader_item.item_charges == tbs->quantity) {
TraderRepository::DeleteOne(database, trader_item.id);
} else {
TraderRepository::UpdateQuantity(
database,
trader_item.char_id,
trader_item.item_sn,
trader_item.item_charges - tbs->quantity
);
}
SendParcelDeliveryToWorld(ps); SendParcelDeliveryToWorld(ps);
if (RuleB(Bazaar, AuditTrail)) { if (RuleB(Bazaar, AuditTrail)) {
+25 -13
View File
@@ -1381,32 +1381,44 @@ int64 Mob::TuneGetTotalDefense(int avoidance_override, int add_avoidance)
int64 Mob::Tunecompute_defense(int avoidance_override, int add_avoidance) int64 Mob::Tunecompute_defense(int avoidance_override, int add_avoidance)
{ {
int defense = GetSkill(EQ::skills::SkillDefense) * 400 / 225; int defense = GetSkill(EQ::skills::SkillDefense) * 400 / 225;
defense += (8000 * (GetAGI() - 40)) / 36000;
// In new code, AGI becomes a large contributor to avoidance at low levels, since AGI isn't capped by Level but Defense is
// A scale factor is implemented for PCs to reduce the effect of AGI at low levels. This isn't applied to NPCs since they can be
// easily controlled via the Database.
if (RuleB(Combat, LegacyComputeDefense)) {
int agi_scale_factor = 1000;
if (IsOfClientBot()) { if (IsOfClientBot()) {
if (avoidance_override) { agi_scale_factor = std::min(1000, static_cast<int>(GetLevel()) * 1000 / 70); // Scales Agi Contribution for PC's Level, max Contribution at Level 70
defense = avoidance_override;
} }
else {
defense += agi_scale_factor * (800 * (GetAGI() - 40)) / 3600 / 1000;
if (IsOfClientBot()) {
defense += GetHeroicAGI() / 10;
}
defense += itembonuses.AvoidMeleeChance * RuleI(Combat, PCAccuracyAvoidanceMod2Scale) / 100; // item mod2
} else {
defense += (8000 * (GetAGI() - 40)) / 36000;
if (IsOfClientBot()) {
defense += itembonuses.heroic_agi_avoidance; defense += itembonuses.heroic_agi_avoidance;
} }
defense += add_avoidance; //1 pt = 10 heroic agi
defense += itembonuses.AvoidMeleeChance; // item mod2
} }
//516 SE_AC_Mitigation_Max_Percent //516 SE_AC_Mitigation_Max_Percent
auto ac_bonus = itembonuses.AC_Mitigation_Max_Percent + aabonuses.AC_Mitigation_Max_Percent + spellbonuses.AC_Mitigation_Max_Percent; auto ac_bonus = itembonuses.AC_Mitigation_Max_Percent + aabonuses.AC_Mitigation_Max_Percent + spellbonuses.AC_Mitigation_Max_Percent;
if (ac_bonus) if (ac_bonus) {
defense += round(static_cast<double>(defense) * static_cast<double>(ac_bonus) * 0.0001); defense += round(static_cast<double>(defense) * static_cast<double>(ac_bonus) * 0.0001);
}
defense += itembonuses.AvoidMeleeChance; // item mod2
if (IsNPC()) { if (IsNPC()) {
if (avoidance_override) {
defense += avoidance_override;
}
else {
defense += CastToNPC()->GetAvoidanceRating(); defense += CastToNPC()->GetAvoidanceRating();
} }
defense += add_avoidance;
}
if (IsClient()) { if (IsClient()) {
double reduction = CastToClient()->GetIntoxication() / 2.0; double reduction = CastToClient()->GetIntoxication() / 2.0;
-6
View File
@@ -3974,12 +3974,6 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
data->price = in->trader_buy_struct.price * in->trader_buy_struct.quantity; data->price = in->trader_buy_struct.price * in->trader_buy_struct.quantity;
} }
TraderRepository::UpdateQuantity(
database,
trader_pc->CharacterID(),
item_sn,
in->item_quantity_available - in->trader_buy_struct.quantity
);
TraderRepository::UpdateActiveTransaction(database, in->id, false); TraderRepository::UpdateActiveTransaction(database, in->id, false);
trader_pc->RemoveItemBySerialNumber(item_sn, in->trader_buy_struct.quantity); trader_pc->RemoveItemBySerialNumber(item_sn, in->trader_buy_struct.quantity);