diff --git a/common/repositories/base/base_npc_types_repository.h b/common/repositories/base/base_npc_types_repository.h index 8d380d904..2f4bc3534 100644 --- a/common/repositories/base/base_npc_types_repository.h +++ b/common/repositories/base/base_npc_types_repository.h @@ -143,6 +143,7 @@ public: int flymode; int always_aggro; int exp_mod; + int heroic_strikethrough; }; static std::string PrimaryKey() @@ -277,6 +278,7 @@ public: "flymode", "always_aggro", "exp_mod", + "heroic_strikethrough", }; } @@ -407,6 +409,7 @@ public: "flymode", "always_aggro", "exp_mod", + "heroic_strikethrough", }; } @@ -571,6 +574,7 @@ public: e.flymode = -1; e.always_aggro = 0; e.exp_mod = 100; + e.heroic_strikethrough = 0; return e; } @@ -730,6 +734,7 @@ public: e.flymode = atoi(row[121]); e.always_aggro = atoi(row[122]); e.exp_mod = atoi(row[123]); + e.heroic_strikethrough = atoi(row[124]); return e; } @@ -886,6 +891,7 @@ public: v.push_back(columns[121] + " = " + std::to_string(e.flymode)); v.push_back(columns[122] + " = " + std::to_string(e.always_aggro)); v.push_back(columns[123] + " = " + std::to_string(e.exp_mod)); + v.push_back(columns[124] + " = " + std::to_string(e.heroic_strikethrough)); auto results = db.QueryDatabase( fmt::format( @@ -1184,6 +1190,7 @@ public: v.push_back(std::to_string(e.flymode)); v.push_back(std::to_string(e.always_aggro)); v.push_back(std::to_string(e.exp_mod)); + v.push_back(std::to_string(e.heroic_strikethrough)); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } @@ -1341,6 +1348,7 @@ public: e.flymode = atoi(row[121]); e.always_aggro = atoi(row[122]); e.exp_mod = atoi(row[123]); + e.heroic_strikethrough = atoi(row[124]); all_entries.push_back(e); } @@ -1489,6 +1497,7 @@ public: e.flymode = atoi(row[121]); e.always_aggro = atoi(row[122]); e.exp_mod = atoi(row[123]); + e.heroic_strikethrough = atoi(row[124]); all_entries.push_back(e); } diff --git a/common/version.h b/common/version.h index c8929da8a..cccc71eba 100644 --- a/common/version.h +++ b/common/version.h @@ -34,7 +34,7 @@ * Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt */ -#define CURRENT_BINARY_DATABASE_VERSION 9200 +#define CURRENT_BINARY_DATABASE_VERSION 9201 #ifdef BOTS #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9029 diff --git a/utils/sql/db_update_manifest.txt b/utils/sql/db_update_manifest.txt index 792bdb5b0..8dfbbdc4a 100644 --- a/utils/sql/db_update_manifest.txt +++ b/utils/sql/db_update_manifest.txt @@ -454,6 +454,7 @@ 9198|2022_08_14_exp_modifier_instance_versions.sql|SHOW COLUMNS FROM `character_exp_modifiers` LIKE 'instance_version'|empty| 9199|2022_08_08_task_req_activity_id.sql|SHOW COLUMNS FROM `task_activities` LIKE 'req_activity_id'|empty| 9200|2022_08_19_zone_expansion_consistency.sql|SELECT * FROM db_version WHERE version >= 9200|empty| +9201|2022_08_22_npc_types_heroic_strikethrough.sql|SHOW COLUMNS FROM `npc_types` LIKE 'heroic_strikethrough'|empty| # Upgrade conditions: # This won't be needed after this system is implemented, but it is used database that are not diff --git a/utils/sql/git/required/2022_08_22_npc_types_heroic_strikethrough.sql b/utils/sql/git/required/2022_08_22_npc_types_heroic_strikethrough.sql new file mode 100644 index 000000000..cf5abb1fe --- /dev/null +++ b/utils/sql/git/required/2022_08_22_npc_types_heroic_strikethrough.sql @@ -0,0 +1,2 @@ +ALTER TABLE `npc_types` +ADD COLUMN `heroic_strikethrough` INT NOT NULL DEFAULT 0 AFTER `exp_mod`; \ No newline at end of file diff --git a/zone/attack.cpp b/zone/attack.cpp index a1a73ce67..0314cc997 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -385,7 +385,7 @@ bool Mob::AvoidDamage(Mob *other, DamageHitInfo &hit) * * Formula (all int math) * (posted for parry, assume rest at the same) - * Chance = (((SKILL + 100) + [((SKILL+100) * SPA(175).Base1) / 100]) / 45) + [(hDex / 25) - min([hDex / 25], hStrikethrough)]. + * Chance = (((SKILL + 100) + [((SKILL+100) * SPA(175).Base1) / 100]) / 45) + [(hDex / 25) - min([hStrikethrough, hDex / 25])]. * hStrikethrough is a mob stat that was added to counter the bonuses of heroic stats * Number rolled against 100, if the chance is greater than 100 it happens 100% of time * @@ -398,8 +398,7 @@ bool Mob::AvoidDamage(Mob *other, DamageHitInfo &hit) /* This special ability adds a negative modifer to the defenders riposte/block/parry/chance - therefore reducing the defenders chance to successfully avoid the melee attack. At present - time this is the only way to fine tune counter these mods on players. This may + therefore reducing the defenders chance to successfully avoid the melee attack. Works in tandem with Heroic Strikethrough. This may ultimately end up being more useful as fields in npc_types. */ @@ -431,6 +430,24 @@ bool Mob::AvoidDamage(Mob *other, DamageHitInfo &hit) modify_dodge = GetSpecialAbilityParam(MODIFY_AVOID_DAMAGE, 4); } + /* Heroic Strikethrough Implementation per Dev Quotes (2018): + * https://forums.daybreakgames.com/eq/index.php?threads/illusions-benefit-neza-10-dodge.246757/#post-3622670 + * Step1 = HeroicStrikethrough(NPC) + * Step2 = HeroicAgility / 25 + * Step3 = MIN( Step1, Step2 ) + * Step4 = DodgeSkill + 100 + * Step5 = Step4 + ( DodgeSkill * DodgeSPA ) / 100 + * Step6 = Step5 / 45 + * DodgeChance = Step6 + ( Step2 - Step3 ) + + * Formula (all int math) + * (posted for parry, dodge appears to be the same as confirmed per Dev, assuming Riposte/Block are the same) + * Chance = (((SKILL + 100) + [((SKILL+100) * SPA(175).Base1) / 100]) / 45) + [(hDex / 25) - min([hStrikethrough, hDex / 25])]. + If an NPC's Heroic Strikethrough is higher than your character's Heroic Agility bonus, you are disqualified from the "bonus" you would have gotten at the end. + */ + + int hstrikethrough = attacker->GetHeroicStrikethrough(); + // riposte -- it may seem crazy, but if the attacker has SPA 173 on them, they are immune to Ripo bool ImmuneRipo = false; if (!RuleB(Combat, UseLiveRiposteMechanics)) { @@ -465,7 +482,7 @@ bool Mob::AvoidDamage(Mob *other, DamageHitInfo &hit) int chance = GetSkill(EQ::skills::SkillRiposte) + 100; chance += (chance * (aabonuses.RiposteChance + spellbonuses.RiposteChance + itembonuses.RiposteChance)) / 100; chance /= 50; - chance += itembonuses.HeroicDEX / 25; // live has "heroic strickthrough" here to counter + chance += (itembonuses.HeroicDEX / 25) - std::min(hstrikethrough,(itembonuses.HeroicDEX / 25)); // "Heroic Strikethrough" subtracted here to counter HeroicDEX if (counter_riposte || counter_all) { float counter = (counter_riposte + counter_all) / 100.0f; chance -= chance * counter; @@ -508,7 +525,7 @@ bool Mob::AvoidDamage(Mob *other, DamageHitInfo &hit) int chance = GetSkill(EQ::skills::SkillBlock) + 100; chance += (chance * (aabonuses.IncreaseBlockChance + spellbonuses.IncreaseBlockChance + itembonuses.IncreaseBlockChance)) / 100; chance /= 25; - chance += itembonuses.HeroicDEX / 25; // live has "heroic strickthrough" here to counter + chance += (itembonuses.HeroicDEX / 25) - std::min(hstrikethrough,(itembonuses.HeroicDEX / 25)); // "Heroic Strikethrough" subtracted here to counter HeroicDEX if (counter_block || counter_all) { float counter = (counter_block + counter_all) / 100.0f; chance -= chance * counter; @@ -535,7 +552,7 @@ bool Mob::AvoidDamage(Mob *other, DamageHitInfo &hit) int chance = GetSkill(EQ::skills::SkillParry) + 100; chance += (chance * (aabonuses.ParryChance + spellbonuses.ParryChance + itembonuses.ParryChance)) / 100; chance /= 45; - chance += itembonuses.HeroicDEX / 25; // live has "heroic strickthrough" here to counter + chance += (itembonuses.HeroicDEX / 25) - std::min(hstrikethrough,(itembonuses.HeroicDEX / 25)); // "Heroic Strikethrough" subtracted here to counter HeroicDEX if (counter_parry || counter_all) { float counter = (counter_parry + counter_all) / 100.0f; chance -= chance * counter; @@ -562,7 +579,7 @@ bool Mob::AvoidDamage(Mob *other, DamageHitInfo &hit) int chance = GetSkill(EQ::skills::SkillDodge) + 100; chance += (chance * (aabonuses.DodgeChance + spellbonuses.DodgeChance + itembonuses.DodgeChance)) / 100; chance /= 45; - chance += itembonuses.HeroicAGI / 25; // live has "heroic strickthrough" here to counter + chance += (itembonuses.HeroicAGI / 25) - std::min(hstrikethrough,(itembonuses.HeroicAGI / 25)); // "Heroic Strikethrough" subtracted here to counter HeroicAGI if (counter_dodge || counter_all) { float counter = (counter_dodge + counter_all) / 100.0f; chance -= chance * counter; diff --git a/zone/beacon.cpp b/zone/beacon.cpp index c9eb703fd..c63172632 100644 --- a/zone/beacon.cpp +++ b/zone/beacon.cpp @@ -56,7 +56,7 @@ Beacon::Beacon(const glm::vec4 &in_pos, int lifetime) :Mob ( nullptr, nullptr, 0, 0, 0, INVISIBLE_MAN, 0, BT_NoTarget, 0, 0, 0, 0, 0, in_pos, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, EQ::TintProfile(), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, EQ::TintProfile(), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false ), remove_timer(lifetime), spell_timer(0) diff --git a/zone/client.cpp b/zone/client.cpp index bb40d1788..e57ac1adc 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -135,6 +135,7 @@ Client::Client(EQStreamInterface* ieqs) 0, 0, 0, + 0, false ), hpupdate_timer(2000), diff --git a/zone/corpse.cpp b/zone/corpse.cpp index 7b5594d83..39c6d04c8 100644 --- a/zone/corpse.cpp +++ b/zone/corpse.cpp @@ -154,7 +154,7 @@ Corpse::Corpse(NPC* in_npc, ItemList* in_itemlist, uint32 in_npctypeid, const NP in_npc->GetDeity(),in_npc->GetLevel(),in_npc->GetNPCTypeID(),in_npc->GetSize(),0, in_npc->GetPosition(), in_npc->GetInnateLightType(), in_npc->GetTexture(),in_npc->GetHelmTexture(), 0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,EQ::TintProfile(),0xff,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,EQ::TintProfile(),0xff,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, (*in_npctypedata)->use_model, false), corpse_decay_timer(in_decaytime), corpse_rez_timer(0), @@ -263,7 +263,8 @@ Corpse::Corpse(Client* client, int32 in_rezexp) : Mob ( 0, // uint8 in_legtexture, 0, // uint8 in_feettexture, 0, // uint8 in_usemodel, - 0 // bool in_always_aggro + 0, // bool in_always_aggro, + 0 // Int32 in_heroic_strikethrough ), corpse_decay_timer(RuleI(Character, CorpseDecayTimeMS)), corpse_rez_timer(RuleI(Character, CorpseResTimeMS)), @@ -503,6 +504,7 @@ EQ::TintProfile(), 0, 0, 0, +0, false), corpse_decay_timer(RuleI(Character, CorpseDecayTimeMS)), corpse_rez_timer(RuleI(Character, CorpseResTimeMS)), diff --git a/zone/encounter.cpp b/zone/encounter.cpp index 5eba3b028..0508c5a87 100644 --- a/zone/encounter.cpp +++ b/zone/encounter.cpp @@ -36,7 +36,7 @@ Encounter::Encounter(const char* enc_name) :Mob ( nullptr, nullptr, 0, 0, 0, INVISIBLE_MAN, 0, BT_NoTarget, 0, 0, 0, 0, 0, glm::vec4(0,0,0,0), 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, EQ::TintProfile(), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, EQ::TintProfile(), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false ) { encounter_name[0] = 0; diff --git a/zone/mob.cpp b/zone/mob.cpp index e0181349a..c6815f5cb 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -97,6 +97,7 @@ Mob::Mob( uint8 in_feettexture, uint16 in_usemodel, bool in_always_aggro, + int32 in_heroic_strikethrough, int64 in_hp_regen_per_second ) : attack_timer(2000), @@ -283,6 +284,7 @@ Mob::Mob( spawned = false; rare_spawn = false; always_aggro = in_always_aggro; + heroic_strikethrough = in_heroic_strikethrough; InitializeBuffSlots(); diff --git a/zone/mob.h b/zone/mob.h index 9a4acc2b5..ddffa38d8 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -164,6 +164,7 @@ public: uint8 in_feettexture, uint16 in_usemodel, bool in_always_aggros_foes, + int32 in_heroic_strikethrough, int64 in_hp_regen_per_second = 0 ); virtual ~Mob(); @@ -640,6 +641,7 @@ public: void SetIsBoat(bool boat) { is_boat = boat; } bool IsControllableBoat() const; inline const bool AlwaysAggro() const { return always_aggro; } + inline int32 GetHeroicStrikethrough() const { return heroic_strikethrough; } //Group virtual bool HasRaid() = 0; @@ -1473,7 +1475,8 @@ protected: bool follow_run; bool no_target_hotkey; bool rare_spawn; - + int32 heroic_strikethrough; + uint32 m_PlayerState; uint32 GetPlayerState() { return m_PlayerState; } void AddPlayerState(uint32 new_state) { m_PlayerState |= new_state; } diff --git a/zone/npc.cpp b/zone/npc.cpp index a7abca981..96716c96e 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -115,7 +115,8 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi npc_type_data->feettexture, npc_type_data->use_model, npc_type_data->always_aggro, - npc_type_data->hp_regen_per_second + npc_type_data->hp_regen_per_second, + npc_type_data->heroic_strikethrough ), attacked_timer(CombatEventTimer_expire), swarm_timer(100), @@ -203,6 +204,7 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi accuracy_rating = npc_type_data->accuracy_rating; avoidance_rating = npc_type_data->avoidance_rating; ATK = npc_type_data->ATK; + heroic_strikethrough = npc_type_data->heroic_strikethrough; // used for when switch back to charm default_ac = npc_type_data->AC; @@ -2646,6 +2648,10 @@ void NPC::ModifyNPCStat(const char *identifier, const char *new_value) AI_AddNPCSpellsEffects(atoi(val.c_str())); CalcBonuses(); return; + } + else if (id == "heroic_strikethrough") { + heroic_strikethrough = atoi(val.c_str()); + return; } } @@ -2786,6 +2792,9 @@ float NPC::GetNPCStat(const char *identifier) else if (id == "npc_spells_effects_id") { return npc_spells_effects_id; } + else if (id == "heroic_strikethrough") { + return heroic_strikethrough; + } //default values else if (id == "default_ac") { return default_ac; diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index 7fe57ab1d..305b6b7d4 100755 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -2561,6 +2561,8 @@ const NPCType *ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load t->exp_mod = n.exp_mod; t->skip_auto_scale = false; // hardcoded here for now t->hp_regen_per_second = n.hp_regen_per_second; + t->heroic_strikethrough = n.heroic_strikethrough; + // If NPC with duplicate NPC id already in table, // free item we attempted to add. diff --git a/zone/zonedump.h b/zone/zonedump.h index 255ed0046..55782340d 100644 --- a/zone/zonedump.h +++ b/zone/zonedump.h @@ -150,6 +150,7 @@ struct NPCType int8 flymode; bool always_aggro; int exp_mod; + int heroic_strikethrough; }; namespace player_lootitem {