mirror of
https://github.com/EQEmu/Server.git
synced 2026-05-27 08:52:27 +00:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7d194083aa | |||
| dd41fc5fcd | |||
| 6bf36f3e77 | |||
| dfb06db17b | |||
| 3e958c575b | |||
| e5db19965f | |||
| ce73f6bfe1 | |||
| 700f4645e2 | |||
| 5a7d544c5b | |||
| 0f7f71334a | |||
| c731f3f560 | |||
| b30fbc70a3 | |||
| 195cb80d56 | |||
| b2d5007466 | |||
| 47e2eb0acf | |||
| c36b3f030b | |||
| 2b821e50ff | |||
| 0aa07e9529 |
@@ -1,3 +1,34 @@
|
||||
## [22.41.0] - 1/8/2024
|
||||
|
||||
### Bug
|
||||
|
||||
* DI Buff Fade ([#3919](https://github.com/EQEmu/Server/pull/3919)) @fryguy503 2024-01-08
|
||||
* NPCs will now only proc on hit ([#3913](https://github.com/EQEmu/Server/pull/3913)) @fryguy503 2024-01-08
|
||||
* Pets should not ignore Z axis ([#3912](https://github.com/EQEmu/Server/pull/3912)) @fryguy503 2024-01-08
|
||||
|
||||
### Fixes
|
||||
|
||||
* Disciplines should show when someone casts them. ([#3901](https://github.com/EQEmu/Server/pull/3901)) @fryguy503 2024-01-08
|
||||
* Fix Typo in Character Skills loading ([#3937](https://github.com/EQEmu/Server/pull/3937)) @Kinglykrab 2024-01-09
|
||||
* Fix for HasLockoutByCharacterID ([#3927](https://github.com/EQEmu/Server/pull/3927)) @fryguy503 2024-01-08
|
||||
* Harm Touch, Improved Harm Touch, and Unholy Touch ([#3904](https://github.com/EQEmu/Server/pull/3904)) @fryguy503 2024-01-08
|
||||
* Legacy Manaburn should have hard cap. ([#3905](https://github.com/EQEmu/Server/pull/3905)) @fryguy503 2024-01-08
|
||||
* TGB - Added logic to stop bard errors on group songs. ([#3906](https://github.com/EQEmu/Server/pull/3906)) @fryguy503 2024-01-08
|
||||
* World Shutdown Filter ([#3930](https://github.com/EQEmu/Server/pull/3930)) @fryguy503 2024-01-08
|
||||
|
||||
### Info
|
||||
|
||||
* Adding textual feedback when trying to sell alt items back to … ([#3917](https://github.com/EQEmu/Server/pull/3917)) @fryguy503 2024-01-08
|
||||
|
||||
### Rules
|
||||
|
||||
* Backstab Damage Modifier ([#3908](https://github.com/EQEmu/Server/pull/3908)) @fryguy503 2024-01-08
|
||||
* Classic Tradeskill Skill Clamp ([#3914](https://github.com/EQEmu/Server/pull/3914)) @fryguy503 2024-01-08
|
||||
* Classic Triple Attack ([#3903](https://github.com/EQEmu/Server/pull/3903)) @fryguy503 2024-01-08
|
||||
* Ensure mana taps only effect NPC's that have mana. ([#3907](https://github.com/EQEmu/Server/pull/3907)) @fryguy503 2024-01-08
|
||||
* Over Taunt Hate ([#3900](https://github.com/EQEmu/Server/pull/3900)) @fryguy503 2024-01-08
|
||||
* Stun Chance Percent Rule ([#3922](https://github.com/EQEmu/Server/pull/3922)) @fryguy503 2024-01-08
|
||||
|
||||
## [22.40.0] - 1/7/2024
|
||||
|
||||
### Account
|
||||
|
||||
@@ -254,6 +254,7 @@ RULE_BOOL(Skills, TrainSenseHeading, false, "Switch whether SenseHeading is trai
|
||||
RULE_INT(Skills, SenseHeadingStartValue, 200, "Start value of sense heading skill")
|
||||
RULE_BOOL(Skills, SelfLanguageLearning, true, "Enabling self-learning of languages")
|
||||
RULE_BOOL(Skills, RequireTomeHandin, false, "Disable click-to-learn and force hand in to Guild Master")
|
||||
RULE_INT(Skills, TradeSkillClamp, 0, "Legacy tradeskills would clamp at 252 regardless of item modifiers and skill combination. DEFAULT: 0 will bypass clamp. Legacy value 252")
|
||||
RULE_CATEGORY_END()
|
||||
|
||||
RULE_CATEGORY(Pets)
|
||||
@@ -471,10 +472,12 @@ RULE_BOOL(Spells, DOTBonusDamageSplitOverDuration, true, "Disable to have Damage
|
||||
RULE_BOOL(Spells, HOTBonusHealingSplitOverDuration, true, "Disable to have Heal Over Time total bonus healing added to each tick instead of divided across duration")
|
||||
RULE_BOOL(Spells, UseLegacyFizzleCode, false, "Enable will turn on the legacy fizzle code which is far stricter and more accurate to 2001/2002 testing.")
|
||||
RULE_BOOL(Spells, LegacyManaburn, false, "Enable to have the legacy manaburn system from 2003 and earlier.")
|
||||
RULE_INT(Spells, LegacyManaburnCap, 9492, "Adjusted the hard cap (Normal or Crit) for the Legacy Manaburn system. DEFAULT: 9492")
|
||||
RULE_BOOL(Spells, EvacClearAggroInSameZone, false, "Enable to clear aggro on clients when evacing in same zone.")
|
||||
RULE_BOOL(Spells, CharmAggroOverLevel, false, "Enabling this rule will cause Charm casts over level to show resisted and cause aggro. Early EQ style.")
|
||||
RULE_BOOL(Spells, RequireMnemonicRetention, true, "Enabling will require spell slots 9-12 to have the appropriate Mnemonic Retention AA learned.")
|
||||
RULE_BOOL(Spells, EvacClearCharmPet, false, "Enable to have evac in zone clear charm from charm pets and detach buffs.")
|
||||
RULE_BOOL(Spells, ManaTapsRequireNPCMana, false, "Enabling will require target to have mana to tap. Default off as many npc's are caster class with 0 mana and need fixed.")
|
||||
RULE_CATEGORY_END()
|
||||
|
||||
RULE_CATEGORY(Combat)
|
||||
@@ -512,6 +515,7 @@ RULE_REAL(Combat, AvgDefProcsPerMinute, 2.0, "Average defense procs per minute")
|
||||
RULE_REAL(Combat, DefProcPerMinAgiContrib, 0.075, "How much agility contributes to defensive proc rate")
|
||||
RULE_INT(Combat, NPCFlurryChance, 20, "Chance for NPC to flurry")
|
||||
RULE_BOOL(Combat, TauntOverLevel, 1, "Allows you to taunt NPC's over warriors level")
|
||||
RULE_INT(Combat, TauntOverAggro, 0, "+ amount over hate_top it will add before any bonus hate.")
|
||||
RULE_REAL(Combat, TauntSkillFalloff, 0.33, "For every taunt skill point that's not maxed you lose this percentage chance to taunt")
|
||||
RULE_BOOL(Combat, EXPFromDmgShield, false, "Determine if damage from a damage shield counts for experience gain")
|
||||
RULE_INT(Combat, QuiverHasteCap, 1000, "Quiver haste cap 1000 on live for a while, currently 700 on live")
|
||||
@@ -555,6 +559,12 @@ RULE_BOOL(Combat, WaterMatchRequiredForAutoFireLoS, true, "Enable/Disable the re
|
||||
RULE_INT(Combat, ExtraAllowedKickClassesBitmask, 0, "Bitmask for allowing extra classes beyond Warrior, Ranger, Beastlord, and Berserker to kick, No Extra Classes (0) by default")
|
||||
RULE_INT(Combat, MaxProcs, 4, "Adjustable maximum number of procs per round, the hard cap is MAX_PROCS (11). Requires mob repop or client zone when changed")
|
||||
RULE_BOOL(Combat, FinishingBlowOnlyWhenFleeing, false, "Enable to only allow Finishing Blow when fleeing (Original Style Finishing Blow)")
|
||||
RULE_BOOL(Combat, ClassicTripleAttack, false, "enable to use non-skill based classic triple attack. Originally it was Warrior Only but was expanded, can use the TripleAttackChance to tune the classes out.")
|
||||
RULE_INT(Combat, ClassicTripleAttackChanceWarrior, 100, "Innate Chance for Warrior to Triple Attack after a Double Attack (125 = 12.5%). DEFAULT: 100")
|
||||
RULE_INT(Combat, ClassicTripleAttackChanceMonk, 100, "Innate Chance for Monk to Triple Attack after a Double Attack (200 = 20%). DEFAULT: 100")
|
||||
RULE_INT(Combat, ClassicTripleAttackChanceBerserker, 100, "Innate Chance for Berserker to Triple Attack after a Double Attack (200 = 20%). DEFAULT: 100")
|
||||
RULE_INT(Combat, ClassicTripleAttackChanceRanger, 100, "Innate Chance for Ranger to Triple Attack after a Double Attack (200 = 20%). DEFAULT: 100")
|
||||
RULE_INT(Combat, StunChance, 12, "Percent chance that client will be stunned when mob is behind player. DEFAULT: 12")
|
||||
RULE_CATEGORY_END()
|
||||
|
||||
RULE_CATEGORY(NPC)
|
||||
@@ -580,6 +590,7 @@ RULE_INT(NPC, NPCToNPCAggroTimerMin, 500, "Minimum time span after which one NPC
|
||||
RULE_INT(NPC, NPCToNPCAggroTimerMax, 6000, "Maximum time span after which one NPC aggro another NPC (milliseconds)")
|
||||
RULE_BOOL(NPC, UseClassAsLastName, true, "Uses class archetype as LastName for NPC with none")
|
||||
RULE_BOOL(NPC, NewLevelScaling, true, "Better level scaling, use old if new formulas would break your server")
|
||||
RULE_REAL(NPC,NPCBackstabMod, 1.9, "Multiplier for NPC Backstab, Higher = Lower backstab amount")
|
||||
RULE_INT(NPC, NPCGatePercent, 20, " Percentage at which the NPC Will attempt to gate at")
|
||||
RULE_BOOL(NPC, NPCGateNearBind, false, "Will NPC attempt to gate when near bind location?")
|
||||
RULE_INT(NPC, NPCGateDistanceBind, 75, "Distance from bind before NPC will attempt to gate")
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@
|
||||
|
||||
// Build variables
|
||||
// these get injected during the build pipeline
|
||||
#define CURRENT_VERSION "22.40.0-dev" // always append -dev to the current version for custom-builds
|
||||
#define CURRENT_VERSION "22.41.0-dev" // always append -dev to the current version for custom-builds
|
||||
#define LOGIN_VERSION "0.8.0"
|
||||
#define COMPILE_DATE __DATE__
|
||||
#define COMPILE_TIME __TIME__
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "eqemu-server",
|
||||
"version": "22.40.0",
|
||||
"version": "22.41.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/EQEmu/Server.git"
|
||||
|
||||
+2
-2
@@ -111,7 +111,7 @@ void ZSList::Process() {
|
||||
0,
|
||||
0,
|
||||
AccountStatus::Player,
|
||||
Chat::Yellow,
|
||||
Chat::System,
|
||||
fmt::format(
|
||||
"[SYSTEM] World will be shutting down in {} minutes.",
|
||||
((shutdowntimer->GetRemainingTime() / 1000) / 60)
|
||||
@@ -759,7 +759,7 @@ void ZSList::WorldShutDown(uint32 time, uint32 interval)
|
||||
0,
|
||||
0,
|
||||
AccountStatus::Player,
|
||||
Chat::Yellow,
|
||||
Chat::System,
|
||||
fmt::format(
|
||||
"[SYSTEM] World will be shutting down in {} minutes.",
|
||||
(time / 60)
|
||||
|
||||
+71
-29
@@ -1419,7 +1419,7 @@ void Mob::DoAttack(Mob *other, DamageHitInfo &hit, ExtraAttackOptions *opts, boo
|
||||
if (other->CheckHitChance(this, hit)) {
|
||||
if (IsNPC() && other->IsClient() && other->animation > 0 && GetLevel() >= 5 && BehindMob(other, GetX(), GetY())) {
|
||||
// ~ 12% chance
|
||||
if (zone->random.Roll(12)) {
|
||||
if (zone->random.Roll(RuleI(Combat, StunChance))) {
|
||||
int stun_resist2 = other->spellbonuses.FrontalStunResist + other->itembonuses.FrontalStunResist + other->aabonuses.FrontalStunResist;
|
||||
int stun_resist = other->spellbonuses.StunResist + other->itembonuses.StunResist + other->aabonuses.StunResist;
|
||||
if (zone->random.Roll(stun_resist2)) {
|
||||
@@ -2281,49 +2281,51 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool
|
||||
if (other->IsClient() && IsPet() && GetOwner()->IsClient()) {
|
||||
//pets do half damage to clients in pvp
|
||||
my_hit.damage_done /= 2;
|
||||
if (my_hit.damage_done < 1)
|
||||
if (my_hit.damage_done < 1) {
|
||||
my_hit.damage_done = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
my_hit.damage_done = DMG_INVULNERABLE;
|
||||
}
|
||||
|
||||
if (GetHP() > 0 && !other->HasDied()) {
|
||||
other->Damage(this, my_hit.damage_done, SPELL_UNKNOWN, my_hit.skill, true, -1, false, m_specialattacks); // Not avoidable client already had thier chance to Avoid
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
|
||||
if (HasDied()) //killed by damage shield ect
|
||||
if (HasDied()) { //killed by damage shield ect
|
||||
return false;
|
||||
}
|
||||
|
||||
MeleeLifeTap(my_hit.damage_done);
|
||||
|
||||
CommonBreakInvisibleFromCombat();
|
||||
|
||||
//I doubt this works...
|
||||
if (!GetTarget())
|
||||
if (!GetTarget()) {
|
||||
return true; //We killed them
|
||||
|
||||
if (!bRiposte && !other->HasDied()) {
|
||||
TryWeaponProc(nullptr, weapon, other, Hand); //no weapon
|
||||
|
||||
if (!other->HasDied())
|
||||
TrySpellProc(nullptr, weapon, other, Hand);
|
||||
|
||||
if (my_hit.damage_done > 0 && HasSkillProcSuccess() && !other->HasDied())
|
||||
TrySkillProc(other, my_hit.skill, 0, true, Hand);
|
||||
}
|
||||
|
||||
if (GetHP() > 0 && !other->HasDied())
|
||||
bool has_hit = my_hit.damage_done > 0;
|
||||
if (has_hit && !bRiposte && !other->HasDied()) {
|
||||
TryWeaponProc(nullptr, weapon, other, Hand);
|
||||
|
||||
if (!other->HasDied()) {
|
||||
TrySpellProc(nullptr, weapon, other, Hand);
|
||||
}
|
||||
|
||||
if (HasSkillProcSuccess() && !other->HasDied()) {
|
||||
TrySkillProc(other, my_hit.skill, 0, true, Hand);
|
||||
}
|
||||
}
|
||||
|
||||
if (GetHP() > 0 && !other->HasDied()) {
|
||||
TriggerDefensiveProcs(other, Hand, true, my_hit.damage_done);
|
||||
}
|
||||
|
||||
if (my_hit.damage_done > 0)
|
||||
return true;
|
||||
|
||||
else
|
||||
return false;
|
||||
return has_hit;
|
||||
}
|
||||
|
||||
void NPC::Damage(Mob* other, int64 damage, uint16 spell_id, EQ::skills::SkillType attack_skill, bool avoidable, int8 buffslot, bool iBuffTic, eSpecialAttacks special) {
|
||||
@@ -3724,9 +3726,43 @@ bool Client::CheckDoubleAttack()
|
||||
// with varying triple attack skill (1-3% error at least)
|
||||
bool Client::CheckTripleAttack()
|
||||
{
|
||||
int chance = GetSkill(EQ::skills::SkillTripleAttack);
|
||||
if (chance < 1)
|
||||
int chance;
|
||||
|
||||
if (RuleB(Combat, ClassicTripleAttack)) {
|
||||
if (
|
||||
IsClient() &&
|
||||
GetLevel() >= 60 &&
|
||||
(
|
||||
GetClass() == Class::Warrior ||
|
||||
GetClass() == Class::Ranger ||
|
||||
GetClass() == Class::Monk ||
|
||||
GetClass() == Class::Berserker
|
||||
)
|
||||
) {
|
||||
switch (GetClass()) {
|
||||
case Class::Warrior:
|
||||
chance = RuleI(Combat, ClassicTripleAttackChanceWarrior);
|
||||
break;
|
||||
case Class::Ranger:
|
||||
chance = RuleI(Combat, ClassicTripleAttackChanceRanger);
|
||||
break;
|
||||
case Class::Monk:
|
||||
chance = RuleI(Combat, ClassicTripleAttackChanceMonk);
|
||||
break;
|
||||
case Class::Berserker:
|
||||
chance = RuleI(Combat, ClassicTripleAttackChanceBerserker);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
chance = GetSkill(EQ::skills::SkillTripleAttack);
|
||||
}
|
||||
|
||||
if (chance < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int inc = aabonuses.TripleAttackChance + spellbonuses.TripleAttackChance + itembonuses.TripleAttackChance;
|
||||
chance = static_cast<int>(chance * (1 + inc / 100.0f));
|
||||
@@ -6320,15 +6356,21 @@ void Client::DoAttackRounds(Mob *target, int hand, bool IsFromSpell)
|
||||
|
||||
// you can only triple from the main hand
|
||||
if (hand == EQ::invslot::slotPrimary && CanThisClassTripleAttack()) {
|
||||
CheckIncreaseSkill(EQ::skills::SkillTripleAttack, target, -10);
|
||||
if (!RuleB(Combat, ClassicTripleAttack)) {
|
||||
CheckIncreaseSkill(EQ::skills::SkillTripleAttack, target, -10);
|
||||
}
|
||||
|
||||
if (CheckTripleAttack()) {
|
||||
Attack(target, hand, false, false, IsFromSpell);
|
||||
auto flurrychance = aabonuses.FlurryChance + spellbonuses.FlurryChance +
|
||||
int flurry_chance = aabonuses.FlurryChance + spellbonuses.FlurryChance +
|
||||
itembonuses.FlurryChance;
|
||||
if (flurrychance && zone->random.Roll(flurrychance)) {
|
||||
|
||||
if (flurry_chance && zone->random.Roll(flurry_chance)) {
|
||||
Attack(target, hand, false, false, IsFromSpell);
|
||||
if (zone->random.Roll(flurrychance))
|
||||
|
||||
if (zone->random.Roll(flurry_chance)) {
|
||||
Attack(target, hand, false, false, IsFromSpell);
|
||||
}
|
||||
MessageString(Chat::NPCFlurry, YOU_FLURRY);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2740,6 +2740,7 @@ void Client::Handle_OP_AltCurrencySell(const EQApplicationPacket *app)
|
||||
}
|
||||
|
||||
if (!RuleB(Merchant, EnableAltCurrencySell)) {
|
||||
Message(Chat::Red, "Selling alternate currency items is disabled.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -10766,7 +10767,7 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app)
|
||||
}
|
||||
|
||||
if ((mypet->GetPetType() == petAnimation && aabonuses.PetCommands[PetCommand]) || mypet->GetPetType() != petAnimation) {
|
||||
if (target != this && DistanceSquaredNoZ(mypet->GetPosition(), target->GetPosition()) <= (RuleR(Pets, AttackCommandRange)*RuleR(Pets, AttackCommandRange))) {
|
||||
if (target != this && DistanceSquared(mypet->GetPosition(), target->GetPosition()) <= (RuleR(Pets, AttackCommandRange)*RuleR(Pets, AttackCommandRange))) {
|
||||
mypet->SetFeigned(false);
|
||||
if (mypet->IsPetStop()) {
|
||||
mypet->SetPetStop(false);
|
||||
|
||||
+27
-10
@@ -60,15 +60,7 @@ int64 Mob::GetActSpellDamage(uint16 spell_id, int64 value, Mob* target) {
|
||||
bool Critical = false;
|
||||
int64 base_value = value;
|
||||
int chance = 0;
|
||||
|
||||
// Need to scale HT damage differently after level 40! It no longer scales by the constant value in the spell file. It scales differently, instead of 10 more damage per level, it does 30 more damage per level. So we multiply the level minus 40 times 20 if they are over level 40.
|
||||
if ((spell_id == SPELL_HARM_TOUCH || spell_id == SPELL_HARM_TOUCH2 || spell_id == SPELL_IMP_HARM_TOUCH ) && GetLevel() > 40)
|
||||
value -= (GetLevel() - 40) * 20;
|
||||
|
||||
//This adds the extra damage from the AA Unholy Touch, 450 per level to the AA Improved Harm TOuch.
|
||||
if (spell_id == SPELL_IMP_HARM_TOUCH && IsOfClientBot()) { //Improved Harm Touch
|
||||
value -= GetAA(aaUnholyTouch) * 450; //Unholy Touch
|
||||
}
|
||||
int legacy_manaburn_cap = RuleI(Spells, LegacyManaburnCap);
|
||||
|
||||
chance = RuleI(Spells, BaseCritChance); //Wizard base critical chance is 2% (Does not scale with level)
|
||||
chance += itembonuses.CriticalSpellChance + spellbonuses.CriticalSpellChance + aabonuses.CriticalSpellChance;
|
||||
@@ -138,12 +130,30 @@ int64 Mob::GetActSpellDamage(uint16 spell_id, int64 value, Mob* target) {
|
||||
value -= GetExtraSpellAmt(spell_id, itembonuses.SpellDmg, base_value) * ratio / 100;
|
||||
}
|
||||
|
||||
// legacy manaburn can crit, but is still held to the same cap
|
||||
if (RuleB(Spells, LegacyManaburn) && spell_id == SPELL_MANA_BURN) {
|
||||
if (value < -legacy_manaburn_cap) {
|
||||
value = -legacy_manaburn_cap;
|
||||
}
|
||||
}
|
||||
|
||||
entity_list.FilteredMessageCloseString(
|
||||
this, true, 100, Chat::SpellCrit, FilterSpellCrits,
|
||||
OTHER_CRIT_BLAST, nullptr, GetName(), itoa(-value));
|
||||
|
||||
if (IsClient())
|
||||
if (IsClient()) {
|
||||
MessageString(Chat::SpellCrit, YOU_CRIT_BLAST, itoa(-value));
|
||||
}
|
||||
|
||||
// Need to scale HT damage differently after level 40! It no longer scales by the constant value in the spell file. It scales differently, instead of 10 more damage per level, it does 30 more damage per level. So we multiply the level minus 40 times 20 if they are over level 40.
|
||||
if ((spell_id == SPELL_HARM_TOUCH || spell_id == SPELL_HARM_TOUCH2 || spell_id == SPELL_IMP_HARM_TOUCH) && GetLevel() > 40) {
|
||||
value -= (GetLevel() - 40) * 20;
|
||||
}
|
||||
|
||||
//This adds the extra damage from the AA Unholy Touch, 450 per level to the AA Improved Harm Touch.
|
||||
if (spell_id == SPELL_IMP_HARM_TOUCH && IsOfClientBot()) { //Improved Harm Touch
|
||||
value -= GetAA(aaUnholyTouch) * 450; //Unholy Touch
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
@@ -183,6 +193,13 @@ int64 Mob::GetActSpellDamage(uint16 spell_id, int64 value, Mob* target) {
|
||||
value -= GetExtraSpellAmt(spell_id, GetSpellDmg(), base_value);
|
||||
}
|
||||
|
||||
// Apply Manaburn Damage Cap
|
||||
if (RuleB(Spells, LegacyManaburn) && spell_id == SPELL_MANA_BURN) {
|
||||
if (value < -legacy_manaburn_cap) {
|
||||
value = -legacy_manaburn_cap;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1171,7 +1171,7 @@ bool Expedition::HasLockoutByCharacterID(
|
||||
{
|
||||
auto lockouts = Expedition::GetExpeditionLockoutsByCharacterID(character_id);
|
||||
return std::any_of(lockouts.begin(), lockouts.end(), [&](const ExpeditionLockoutTimer& lockout) {
|
||||
return lockout.IsSameLockout(expedition_name, event_name);
|
||||
return !lockout.IsExpired() && lockout.IsSameLockout(expedition_name, event_name);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ void command_worldshutdown(Client *c, const Seperator *sep)
|
||||
) {
|
||||
int time_minutes = (time / 60);
|
||||
quest_manager.WorldWideMessage(
|
||||
Chat::Yellow,
|
||||
Chat::System,
|
||||
fmt::format(
|
||||
"[SYSTEM] World will be shutting down in {} minutes.",
|
||||
time_minutes
|
||||
|
||||
+16
-3
@@ -4539,10 +4539,23 @@ bool Mob::CanThisClassDoubleAttack(void) const
|
||||
|
||||
bool Mob::CanThisClassTripleAttack() const
|
||||
{
|
||||
if (!IsClient())
|
||||
if (!IsClient()) {
|
||||
return false; // When they added the real triple attack skill, mobs lost the ability to triple
|
||||
else
|
||||
return CastToClient()->HasSkill(EQ::skills::SkillTripleAttack);
|
||||
} else {
|
||||
if (RuleB(Combat, ClassicTripleAttack)) {
|
||||
return (
|
||||
GetLevel() >= 60 &&
|
||||
(
|
||||
GetClass() == Class::Warrior ||
|
||||
GetClass() == Class::Ranger ||
|
||||
GetClass() == Class::Monk ||
|
||||
GetClass() == Class::Berserker
|
||||
)
|
||||
);
|
||||
} else {
|
||||
return CastToClient()->HasSkill(EQ::skills::SkillTripleAttack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Mob::IsWarriorClass(void) const
|
||||
|
||||
+1
-1
@@ -1147,7 +1147,7 @@ public:
|
||||
void StartEnrage();
|
||||
void ProcessEnrage();
|
||||
bool IsEnraged();
|
||||
void Taunt(NPC *who, bool always_succeed, int chance_bonus = 0, bool FromSpell = false, int32 bonus_hate = 0);
|
||||
void Taunt(NPC *who, bool always_succeed, int chance_bonus = 0, bool from_spell = false, int32 bonus_hate = 0);
|
||||
|
||||
virtual void AI_Init();
|
||||
virtual void AI_Start(uint32 iMoveDelay = 0);
|
||||
|
||||
+53
-43
@@ -24,6 +24,7 @@
|
||||
#include "mob.h"
|
||||
#include "string_ids.h"
|
||||
#include "lua_parser.h"
|
||||
#include "npc.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
@@ -182,7 +183,7 @@ int Mob::GetBaseSkillDamage(EQ::skills::SkillType skill, Mob *target)
|
||||
}
|
||||
} else if (IsNPC()) {
|
||||
auto *npc = CastToNPC();
|
||||
base = std::max(base, npc->GetBaseDamage());
|
||||
base = round((npc->GetMaxDMG() - npc->GetMinDMG()) / RuleR(NPC, NPCBackstabMod));
|
||||
// parses show relatively low BS mods from lots of NPCs, so either their BS skill is super low
|
||||
// or their mod is divided again, this is probably not the right mod, but it's better
|
||||
skill_bonus /= 3.0f;
|
||||
@@ -2127,19 +2128,15 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte)
|
||||
}
|
||||
}
|
||||
|
||||
void Mob::Taunt(NPC *who, bool always_succeed, int chance_bonus, bool FromSpell, int32 bonus_hate)
|
||||
void Mob::Taunt(NPC *who, bool always_succeed, int chance_bonus, bool from_spell, int32 bonus_hate)
|
||||
{
|
||||
if (who == nullptr)
|
||||
if (!who || DivineAura() || (!from_spell && !CombatRange(who))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (DivineAura())
|
||||
return;
|
||||
|
||||
if (!FromSpell && !CombatRange(who))
|
||||
return;
|
||||
|
||||
if (!always_succeed && IsClient())
|
||||
if (!always_succeed && IsClient()) {
|
||||
CastToClient()->CheckIncreaseSkill(EQ::skills::SkillTaunt, who, 10);
|
||||
}
|
||||
|
||||
Mob *hate_top = who->GetHateMost();
|
||||
|
||||
@@ -2147,57 +2144,63 @@ void Mob::Taunt(NPC *who, bool always_succeed, int chance_bonus, bool FromSpell,
|
||||
bool success = false;
|
||||
|
||||
// Support for how taunt worked pre 2000 on LIVE - Can not taunt NPC over your level.
|
||||
if ((RuleB(Combat, TauntOverLevel) == false) && (level_difference < 0) ||
|
||||
who->GetSpecialAbility(IMMUNE_TAUNT)) {
|
||||
if (
|
||||
!RuleB(Combat, TauntOverLevel) &&
|
||||
level_difference < 0 ||
|
||||
who->GetSpecialAbility(IMMUNE_TAUNT)
|
||||
) {
|
||||
MessageString(Chat::SpellFailure, FAILED_TAUNT);
|
||||
return;
|
||||
}
|
||||
|
||||
// All values used based on live parses after taunt was updated in 2006.
|
||||
if ((hate_top && hate_top->GetHPRatio() >= 20) || hate_top == nullptr || chance_bonus) {
|
||||
if (
|
||||
(hate_top && hate_top->GetHPRatio() >= 20) ||
|
||||
!hate_top ||
|
||||
chance_bonus
|
||||
) {
|
||||
// SE_Taunt this is flat chance
|
||||
if (chance_bonus) {
|
||||
success = zone->random.Roll(chance_bonus);
|
||||
} else {
|
||||
float tauntchance = 50.0f;
|
||||
|
||||
if (always_succeed)
|
||||
tauntchance = 101.0f;
|
||||
|
||||
else {
|
||||
float taunt_chance = 50.0f;
|
||||
|
||||
if (always_succeed) {
|
||||
taunt_chance = 101.0f;
|
||||
} else {
|
||||
if (level_difference < 0) {
|
||||
tauntchance += static_cast<float>(level_difference) * 3.0f;
|
||||
if (tauntchance < 20)
|
||||
tauntchance = 20.0f;
|
||||
}
|
||||
|
||||
else {
|
||||
tauntchance += static_cast<float>(level_difference) * 5.0f;
|
||||
if (tauntchance > 65)
|
||||
tauntchance = 65.0f;
|
||||
taunt_chance += static_cast<float>(level_difference) * 3.0f;
|
||||
if (taunt_chance < 20) {
|
||||
taunt_chance = 20.0f;
|
||||
}
|
||||
} else {
|
||||
taunt_chance += static_cast<float>(level_difference) * 5.0f;
|
||||
if (taunt_chance > 65) {
|
||||
taunt_chance = 65.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TauntSkillFalloff rate is not based on any real data. Default of 33% gives a reasonable
|
||||
// result.
|
||||
if (IsClient() && !always_succeed)
|
||||
tauntchance -= (RuleR(Combat, TauntSkillFalloff) *
|
||||
if (IsClient() && !always_succeed) {
|
||||
taunt_chance -= (RuleR(Combat, TauntSkillFalloff) *
|
||||
(CastToClient()->MaxSkill(EQ::skills::SkillTaunt) -
|
||||
GetSkill(EQ::skills::SkillTaunt)));
|
||||
}
|
||||
|
||||
if (tauntchance < 1)
|
||||
tauntchance = 1.0f;
|
||||
if (taunt_chance < 1) {
|
||||
taunt_chance = 1.0f;
|
||||
}
|
||||
|
||||
tauntchance /= 100.0f;
|
||||
|
||||
success = tauntchance > zone->random.Real(0, 1);
|
||||
taunt_chance /= 100.0f;
|
||||
success = taunt_chance > zone->random.Real(0, 1);
|
||||
|
||||
LogHate(
|
||||
"Taunter mob {} target npc {} tauntchance [{}] success [{}] hate_top [{}]",
|
||||
"Taunter mob {} target npc {} taunt_chance [{}] success [{}] hate_top [{}]",
|
||||
GetMobDescription(),
|
||||
who->GetMobDescription(),
|
||||
tauntchance,
|
||||
taunt_chance,
|
||||
success ? "true" : "false",
|
||||
hate_top ? hate_top->GetMobDescription() : "not found"
|
||||
);
|
||||
@@ -2205,27 +2208,34 @@ void Mob::Taunt(NPC *who, bool always_succeed, int chance_bonus, bool FromSpell,
|
||||
|
||||
if (success) {
|
||||
if (hate_top && hate_top != this) {
|
||||
int64 newhate = (who->GetNPCHate(hate_top) - who->GetNPCHate(this)) + 1 + bonus_hate;
|
||||
int64 new_hate = (
|
||||
(who->GetNPCHate(hate_top) - who->GetNPCHate(this)) +
|
||||
bonus_hate +
|
||||
RuleI(Combat, TauntOverAggro) +
|
||||
1
|
||||
);
|
||||
|
||||
LogHate(
|
||||
"Taunter mob {} target npc {} newhate [{}] hated_top {} hate_of_top [{}] this_hate [{}] bonus_hate [{}]",
|
||||
"Not Top Hate - Taunter [{}] Target [{}] Hated Top [{}] Hate Top Amt [{}] This Character Amt [{}] Bonus_Hate Amt [{}] TauntOverAggro Amt [{}] - Total [{}]",
|
||||
GetMobDescription(),
|
||||
who->GetMobDescription(),
|
||||
newhate,
|
||||
hate_top->GetMobDescription(),
|
||||
who->GetNPCHate(hate_top),
|
||||
who->GetNPCHate(this),
|
||||
bonus_hate
|
||||
bonus_hate,
|
||||
RuleI(Combat, TauntOverAggro),
|
||||
new_hate
|
||||
);
|
||||
|
||||
who->CastToNPC()->AddToHateList(this, newhate);
|
||||
who->CastToNPC()->AddToHateList(this, new_hate);
|
||||
success = true;
|
||||
} else {
|
||||
who->CastToNPC()->AddToHateList(this, 12);
|
||||
}
|
||||
|
||||
if (who->CanTalk())
|
||||
if (who->CanTalk()) {
|
||||
who->SayString(SUCCESSFUL_TAUNT, GetCleanName());
|
||||
}
|
||||
} else {
|
||||
MessageString(Chat::SpellFailure, FAILED_TAUNT);
|
||||
}
|
||||
|
||||
@@ -7094,6 +7094,7 @@ bool Mob::TryDeathSave() {
|
||||
}
|
||||
|
||||
SendHPUpdate();
|
||||
BuffFadeBySlot(buffSlot);
|
||||
return true;
|
||||
}
|
||||
else if (UD_HealMod) {
|
||||
|
||||
+49
-35
@@ -220,6 +220,23 @@ bool Mob::CastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot,
|
||||
BuffFadeByEffect(SE_NegateIfCombat);
|
||||
}
|
||||
|
||||
// check to see if target is a caster mob before performing a mana tap
|
||||
if(GetTarget() && IsManaTapSpell(spell_id)) {
|
||||
if (
|
||||
GetTarget()->GetCasterClass() == 'N' &&
|
||||
(
|
||||
!RuleB(Spells, ManaTapsRequireNPCMana) ||
|
||||
(
|
||||
RuleB(Spells, ManaTapsRequireNPCMana) &&
|
||||
GetTarget()->GetMana() == 0
|
||||
)
|
||||
)
|
||||
) {
|
||||
InterruptSpell(TARGET_NO_MANA, 0x121, spell_id);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//Casting a spell from an item click will also stop bard pulse.
|
||||
if (HasActiveSong() && (IsBardSong(spell_id) || slot == CastingSlot::Item)) {
|
||||
LogSpells("Casting a new song while singing a song. Killing old song [{}]", bardsong);
|
||||
@@ -2481,14 +2498,6 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, in
|
||||
return false;
|
||||
}
|
||||
|
||||
// check to see if target is a caster mob before performing a mana tap
|
||||
if(spell_target && IsManaTapSpell(spell_id)) {
|
||||
if(spell_target->GetCasterClass() == 'N') {
|
||||
MessageString(Chat::Red, TARGET_NO_MANA);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//range check our target, if we have one and it is not us
|
||||
float range = spells[spell_id].range + GetRangeDistTargetSizeMod(spell_target);
|
||||
if(IsClient() && CastToClient()->TGB() && IsTGBCompatibleSpell(spell_id) && IsGroupSpell(spell_id))
|
||||
@@ -2619,74 +2628,75 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, in
|
||||
|
||||
case GroupSpell:
|
||||
{
|
||||
if(IsBot()) {
|
||||
bool StopLogic = false;
|
||||
if(!CastToBot()->DoFinishedSpellGroupTarget(spell_id, spell_target, slot, StopLogic))
|
||||
if (IsBot()) {
|
||||
bool stop_logic = false;
|
||||
if (!CastToBot()->DoFinishedSpellGroupTarget(spell_id, spell_target, slot, stop_logic)) {
|
||||
return false;
|
||||
if(StopLogic)
|
||||
}
|
||||
|
||||
if(stop_logic) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// We hold off turning MBG off so we can still use it to calc the mana cost
|
||||
if(spells[spell_id].can_mgb && HasMGB())
|
||||
{
|
||||
if (spells[spell_id].can_mgb && HasMGB()) {
|
||||
SpellOnTarget(spell_id, this);
|
||||
entity_list.MassGroupBuff(this, this, spell_id, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// at this point spell_target is a member of the other group, or the
|
||||
// caster if they're not using TGB
|
||||
// NOTE: this will always hit the caster, plus the target's group so
|
||||
// it can affect up to 7 people if the targeted group is not our own
|
||||
|
||||
// Allow pets who cast group spells to affect the group.
|
||||
if (spell_target->IsPetOwnerClient() && IsPetOwnerClient()){
|
||||
if (spell_target->IsPetOwnerClient() && IsPetOwnerClient()) {
|
||||
Mob* owner = spell_target->GetOwner();
|
||||
|
||||
if (owner)
|
||||
if (owner) {
|
||||
spell_target = owner;
|
||||
}
|
||||
}
|
||||
|
||||
if(spell_target->IsGrouped())
|
||||
{
|
||||
if (spell_target->IsGrouped()) {
|
||||
Group *target_group = entity_list.GetGroupByMob(spell_target);
|
||||
if(target_group)
|
||||
{
|
||||
if (target_group) {
|
||||
target_group->CastGroupSpell(this, spell_id);
|
||||
if (GetClass() != Class::Bard) {
|
||||
SpellOnTarget(spell_id, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(spell_target->IsRaidGrouped() && spell_target->IsClient())
|
||||
{
|
||||
} else if (spell_target->IsRaidGrouped() && spell_target->IsClient()) {
|
||||
Raid *target_raid = entity_list.GetRaidByClient(spell_target->CastToClient());
|
||||
uint32 gid = 0xFFFFFFFF;
|
||||
if(target_raid){
|
||||
if (target_raid) {
|
||||
gid = target_raid->GetGroup(spell_target->GetName());
|
||||
if(gid < 12)
|
||||
if (gid < 12) {
|
||||
target_raid->CastGroupSpell(this, spell_id, gid);
|
||||
else
|
||||
} else {
|
||||
SpellOnTarget(spell_id, spell_target);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// if target is grouped, CastGroupSpell will cast it on the caster
|
||||
// too, but if not then we have to do that here.
|
||||
|
||||
if(spell_target != this){
|
||||
if (spell_target != this) {
|
||||
SpellOnTarget(spell_id, this);
|
||||
#ifdef GROUP_BUFF_PETS
|
||||
//pet too
|
||||
if (spells[spell_id].target_type != ST_GroupNoPets && GetPet() && HasPetAffinity() && !GetPet()->IsCharmed())
|
||||
if (spells[spell_id].target_type != ST_GroupNoPets && GetPet() && HasPetAffinity() && !GetPet()->IsCharmed()) {
|
||||
SpellOnTarget(spell_id, GetPet());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
SpellOnTarget(spell_id, spell_target);
|
||||
#ifdef GROUP_BUFF_PETS
|
||||
//pet too
|
||||
if (spells[spell_id].target_type != ST_GroupNoPets && spell_target->GetPet() && spell_target->HasPetAffinity() && !spell_target->GetPet()->IsCharmed())
|
||||
if (spells[spell_id].target_type != ST_GroupNoPets && spell_target->GetPet() && spell_target->HasPetAffinity() && !spell_target->GetPet()->IsCharmed()) {
|
||||
SpellOnTarget(spell_id, spell_target->GetPet());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -3797,6 +3807,10 @@ bool Mob::SpellOnTarget(
|
||||
|
||||
LogSpells("Casting spell [{}] on [{}] with effective caster level [{}]", spell_id, spelltar->GetName(), caster_level);
|
||||
|
||||
if (IsClient() && (IsDiscipline(spell_id) || spells[spell_id].is_discipline)) {
|
||||
entity_list.MessageClose(this, false, 200, 0, fmt::format("{}{}", GetCleanName(), spells[spell_id].cast_on_other).c_str());
|
||||
}
|
||||
|
||||
// Actual cast action - this causes the caster animation and the particles
|
||||
// around the target
|
||||
// we do this first, that way we get the particles even if the spell
|
||||
|
||||
@@ -952,10 +952,16 @@ void Client::SendTradeskillDetails(uint32 recipe_id) {
|
||||
|
||||
//returns true on success
|
||||
bool Client::TradeskillExecute(DBTradeskillRecipe_Struct *spec) {
|
||||
if(spec == nullptr)
|
||||
return(false);
|
||||
if (!spec) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16 user_skill = GetSkill(spec->tradeskill);
|
||||
|
||||
if (RuleI(Skills, TradeSkillClamp) != 0 && user_skill > RuleI(Skills, TradeSkillClamp)) {
|
||||
user_skill = RuleI(Skills, TradeSkillClamp);
|
||||
}
|
||||
|
||||
float chance = 0.0;
|
||||
float skillup_modifier = 0.0;
|
||||
int16 thirdstat = 0;
|
||||
|
||||
+1
-1
@@ -800,7 +800,7 @@ bool ZoneDatabase::LoadCharacterSkills(uint32 character_id, PlayerProfile_Struct
|
||||
const auto& l = CharacterSkillsRepository::GetWhere(
|
||||
*this,
|
||||
fmt::format(
|
||||
"`id` = {} ORDER BY `skill_id",
|
||||
"`id` = {} ORDER BY `skill_id`",
|
||||
character_id
|
||||
)
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user