diff --git a/common/database_conversions.cpp b/common/database_conversions.cpp index aa772c8b1..fb2cc80d1 100644 --- a/common/database_conversions.cpp +++ b/common/database_conversions.cpp @@ -493,7 +493,7 @@ bool Database::CheckDatabaseConversions() { /* Check for a new version of this script, the arg passed would have to be higher than the copy they have downloaded locally and they will re fetch */ - system("perl eqemu_update.pl V 10"); + system("perl eqemu_update.pl V 11"); /* Run Automatic Database Upgrade Script */ system("perl eqemu_update.pl ran_from_world"); diff --git a/common/shareddb.cpp b/common/shareddb.cpp index 7ccfa5cbd..ed4106598 100644 --- a/common/shareddb.cpp +++ b/common/shareddb.cpp @@ -1659,27 +1659,29 @@ void SharedDatabase::LoadSpells(void *data, int max_spells) { sp[tempid].directional_start = static_cast(atoi(row[194])); sp[tempid].directional_end = static_cast(atoi(row[195])); sp[tempid].sneak = atoi(row[196]) != 0; - sp[tempid].not_extendable = atoi(row[197]) != 0; + sp[tempid].not_focusable = atoi(row[197]) != 0; + sp[tempid].no_detrimental_spell_aggro = atoi(row[198]) != 0; sp[tempid].suspendable = atoi(row[200]) != 0; sp[tempid].viral_range = atoi(row[201]); sp[tempid].songcap = atoi(row[202]); sp[tempid].no_block = atoi(row[205]); sp[tempid].spellgroup=atoi(row[207]); sp[tempid].rank = atoi(row[208]); - sp[tempid].powerful_flag=atoi(row[209]); + sp[tempid].no_resist=atoi(row[209]); sp[tempid].CastRestriction = atoi(row[211]); sp[tempid].AllowRest = atoi(row[212]) != 0; sp[tempid].InCombat = atoi(row[213]) != 0; sp[tempid].OutofCombat = atoi(row[214]) != 0; sp[tempid].override_crit_chance = atoi(row[217]); sp[tempid].aemaxtargets = atoi(row[218]); - sp[tempid].maxtargets = atoi(row[219]); + sp[tempid].no_heal_damage_item_mod = atoi(row[219]); sp[tempid].persistdeath = atoi(row[224]) != 0; sp[tempid].min_dist = atof(row[227]); sp[tempid].min_dist_mod = atof(row[228]); sp[tempid].max_dist = atof(row[229]); sp[tempid].max_dist_mod = atof(row[230]); sp[tempid].min_range = static_cast(atoi(row[231])); + sp[tempid].no_remove = atoi(row[232]) != 0; sp[tempid].DamageShieldType = 0; } diff --git a/common/spdat.cpp b/common/spdat.cpp index bb6097566..02803b173 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -1092,6 +1092,14 @@ bool DetrimentalSpellAllowsRest(uint16 spell_id) return false; } +bool NoDetrimentalSpellAggro(uint16 spell_id) +{ + if (IsValidSpell(spell_id)) + return spells[spell_id].no_detrimental_spell_aggro; + + return false; +} + uint32 GetNimbusEffect(uint16 spell_id) { if (IsValidSpell(spell_id)) diff --git a/common/spdat.h b/common/spdat.h index c49628d01..41fd35f1f 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -381,7 +381,7 @@ typedef enum { #define SE_GiveDoubleAttack 225 // implemented[AA] - Allow any class to double attack with set chance. #define SE_TwoHandBash 226 // *not implemented as bonus #define SE_ReduceSkillTimer 227 // implemented -#define SE_ReduceFallDamage 228 // not implented as bonus - reduce the damage that you take from falling +#define SE_ReduceFallDamage 228 // implented - reduce the damage that you take from falling #define SE_PersistantCasting 229 // implemented #define SE_ExtendedShielding 230 // not used as bonus - increase range of /shield ability #define SE_StunBashChance 231 // implemented - increase chance to stun from bash. @@ -400,7 +400,7 @@ typedef enum { #define SE_RootBreakChance 244 // implemented[AA] reduce the chance that your root will break. #define SE_TrapCircumvention 245 // *not implemented[AA] - decreases the chance that you will set off a trap when opening a chest #define SE_SetBreathLevel 246 // *not implemented as bonus -#define SE_RaiseSkillCap 247 // *not implemented[AA] - adds skill over the skill cap. +#define SE_RaiseSkillCap 247 // implemented[AA] - adds skill over the skill cap. #define SE_SecondaryForte 248 // not implemented as bonus(gives you a 2nd specialize skill that can go past 50 to 100) #define SE_SecondaryDmgInc 249 // implemented[AA] Allows off hand weapon to recieve a damage bonus (Sinister Strikes) #define SE_SpellProcChance 250 // implemented - Increase chance to proc from melee proc spells (ie Spirit of Panther) @@ -409,19 +409,19 @@ typedef enum { #define SE_FrontalBackstabMinDmg 253 // implemented[AA] - allow a frontal backstab for mininum damage. #define SE_Blank 254 // implemented #define SE_ShieldDuration 255 // not implemented as bonus - increases duration of /shield -#define SE_ShroudofStealth 256 // not implemented as bonus - rogue improved invs +#define SE_ShroudofStealth 256 // implemented #define SE_PetDiscipline 257 // not implemented as bonus - /pet hold #define SE_TripleBackstab 258 // implemented[AA] - chance to perform a triple backstab #define SE_CombatStability 259 // implemented[AA] - damage mitigation #define SE_AddSingingMod 260 // implemented[AA] - Instrument/Singing Mastery, base1 is the mod, base2 is the ItemType #define SE_SongModCap 261 // implemented[AA] - Song Mod cap increase (no longer used on live) #define SE_RaiseStatCap 262 // implemented -#define SE_TradeSkillMastery 263 // not implemented - lets you raise more than one tradeskill above master. +#define SE_TradeSkillMastery 263 // implemented - lets you raise more than one tradeskill above master. #define SE_HastenedAASkill 264 // implemented #define SE_MasteryofPast 265 // implemented[AA] - Spells less than effect values level can not be fizzled #define SE_ExtraAttackChance 266 // implemented - increase chance to score an extra attack with a 2-Handed Weapon. #define SE_PetDiscipline2 267 // *not implemented - /pet focus, /pet no cast -#define SE_ReduceTradeskillFail 268 // *not implemented? - reduces chance to fail with given tradeskill by a percent chance +#define SE_ReduceTradeskillFail 268 // implemented - reduces chance to fail with given tradeskill by a percent chance #define SE_MaxBindWound 269 // implemented[AA] - Increase max HP you can bind wound. #define SE_BardSongRange 270 // implemented[AA] - increase range of beneficial bard songs (Sionachie's Crescendo) #define SE_BaseMovementSpeed 271 // implemented[AA] - mods basemove speed, doesn't stack with other move mods @@ -478,7 +478,7 @@ typedef enum { #define SE_GateToHomeCity 322 // implemented #define SE_DefensiveProc 323 // implemented #define SE_HPToMana 324 // implemented -//#define SE_ChanceInvsBreakToAoE 325 // *not implemented[AA] - [AA Nerves of Steel] increasing chance to remain hidden when they are an indirect target of an AoE spell. +//#define SE_NoBreakAESneak 325 // *not implemented[AA] - [AA Nerves of Steel] increasing chance to remain hidden when they are an indirect target of an AoE spell. #define SE_SpellSlotIncrease 326 // *not implemented as bonus - increases your spell slot availability #define SE_MysticalAttune 327 // implemented - increases amount of buffs that a player can have #define SE_DelayDeath 328 // implemented - increases how far you can fall below 0 hp before you die @@ -486,7 +486,7 @@ typedef enum { #define SE_CriticalDamageMob 330 // implemented #define SE_Salvage 331 // implemented - chance to recover items that would be destroyed in failed tradeskill combine //#define SE_SummonToCorpse 332 // *not implemented AA - Call of the Wild (Druid/Shaman Res spell with no exp) -#define SE_CastOnRuneFadeEffect 333 // implemented +#define SE_CastOnRuneFadeEffect 333 // implemented #define SE_BardAEDot 334 // implemented #define SE_BlockNextSpellFocus 335 // implemented - base1 chance to block next spell ie Puratus (8494) //#define SE_IllusionaryTarget 336 // not used @@ -619,7 +619,7 @@ typedef enum { //#define SE_Shield_Target 463 // #define SE_PC_Pet_Rampage 464 // implemented - Base1 % chance to do rampage for base2 % of damage each melee round //#define SE_PC_Pet_AE_Rampage 465 // Would assume as above but need to confirm. -//#define SE_PC_Pet_Flurry_Chance 466 // +#define SE_PC_Pet_Flurry_Chance 466 // implemented - Base1 % chance to do flurry from double attack hit. //#define SE_DS_Mitigation_Amount 467 // //#define SE_DS_Mitigation_Percentage 468 // //#define SE_Chance_Best_in_Spell_Grp 469 // @@ -744,8 +744,9 @@ struct SPDat_Spell_Struct /* 194 */ float directional_start; //Cone Start Angle: /* 195 */ float directional_end; // Cone End Angle: /* 196 */ bool sneak; // effect can only be used if sneaking (rogue 'Daggerfall' ect) -/* 197 */ bool not_extendable; -/* 198- 199 */ +/* 197 */ bool not_focusable; //prevents focus effects from being applied to spell +/* 198 */ bool no_detrimental_spell_aggro; +/* 199 */ /* 200 */ bool suspendable; // buff is suspended in suspended buff zones /* 201 */ int viral_range; /* 202 */ int songcap; // individual song cap @@ -755,7 +756,7 @@ struct SPDat_Spell_Struct /* 206 */ /* 207 */ int spellgroup; /* 208 */ int rank; //increments AA effects with same name -/* 209 */ int powerful_flag; // Need more investigation to figure out what to call this, for now we know -1 makes charm spells not break before their duration is complete, it does alot more though +/* 209 */ int no_resist; //makes spells unresistable, which makes charms unbreakable as well. /* 210 */ // bool DurationFrozen; ??? /* 211 */ int CastRestriction; //Various restriction categories for spells most seem targetable race related but have also seen others for instance only castable if target hp 20% or lower or only if target out of combat /* 212 */ bool AllowRest; @@ -764,7 +765,7 @@ struct SPDat_Spell_Struct /* 215 - 216 */ /* 217 */ int override_crit_chance; //Places a cap on the max chance to critical /* 218 */ int aemaxtargets; //Is used for various AE effects -/* 219 */ int maxtargets; //Is used for beam and ring spells for target # limits (not implemented) +/* 219 */ int no_heal_damage_item_mod; /* 220 - 223 */ /* 224 */ bool persistdeath; // buff doesn't get stripped on death /* 225 - 226 */ @@ -773,7 +774,8 @@ struct SPDat_Spell_Struct /* 229 */ float max_dist; //spell power modified by distance from caster (Max Distance) /* 230 */ float max_dist_mod; //spell power modified by distance from caster (Modifier at Max Distance) /* 231 */ float min_range; //Min casting range -/* 232 - 236 */ +/* 232 */ bool no_remove; //prevents buff from being removed by click +/* 233 - 236 */ uint8 DamageShieldType; // This field does not exist in spells_us.txt }; @@ -879,6 +881,7 @@ uint32 GetPartialMeleeRuneReduction(uint32 spell_id); uint32 GetPartialMagicRuneReduction(uint32 spell_id); uint32 GetPartialMeleeRuneAmount(uint32 spell_id); uint32 GetPartialMagicRuneAmount(uint32 spell_id); +bool NoDetrimentalSpellAggro(uint16 spell_id); int CalcPetHp(int levelb, int classb, int STA = 75); const char *GetRandPetName(); diff --git a/utils/scripts/eqemu_update.pl b/utils/scripts/eqemu_update.pl index 00d506630..9c1556d21 100644 --- a/utils/scripts/eqemu_update.pl +++ b/utils/scripts/eqemu_update.pl @@ -14,6 +14,7 @@ use POSIX qw(strftime); use File::Path; use File::Find; use URI::Escape; +use Time::HiRes qw(usleep); $time_stamp = strftime('%m-%d-%Y', gmtime()); @@ -22,7 +23,7 @@ if($Config{osname}=~/linux/i){ $OS = "Linux"; } if($Config{osname}=~/Win|MS/i){ $OS = "Windows"; } #::: If current version is less than what world is reporting, then download a new one... -$current_version = 10; +$current_version = 11; if($ARGV[0] eq "V"){ if($ARGV[1] > $current_version){ @@ -49,9 +50,6 @@ no warnings; ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(); -#::: Cleanup staged folder... -rmtree("updates_staged/"); - my $confile = "eqemu_config.xml"; #default open(F, "<$confile"); my $indb = 0; @@ -103,12 +101,40 @@ if($OS eq "Linux"){ } #::: Path not found, error and exit -if($path eq ""){ +if($path eq ""){ print "MySQL path not found, please add the path for automatic database upgrading to continue... \n\n"; print "script_exiting...\n"; exit; } +if($ARGV[0] eq "installer"){ + print "Running EQEmu Server installer routines...\n"; + mkdir('logs'); + mkdir('updates_staged'); + fetch_latest_windows_binaries(); + map_files_fetch_bulk(); + opcodes_fetch(); + plugins_fetch(); + quest_files_fetch(); + lua_modules_fetch(); + get_remote_file("https://raw.githubusercontent.com/Akkadius/EQEmuInstall/master/lua51.dll", "lua51.dll", 1); + + #::: Database Routines + print "MariaDB :: Creating Database 'peq'\n"; + print `"$path" --host $host --user $user --password="$pass" -N -B -e "DROP DATABASE peq;CREATE DATABASE peq"`; + if($OS eq "Windows"){ @db_version = split(': ', `world db_version`); } + if($OS eq "Linux"){ @db_version = split(': ', `./world db_version`); } + $bin_db_ver = trim($db_version[1]); + check_db_version_table(); + $local_db_ver = trim(get_mysql_result("SELECT version FROM db_version LIMIT 1")); + fetch_peq_db_full(); + print "\nFetching Latest Database Updates...\n"; + main_db_management(); + print "\nApplying Latest Database Updates...\n"; + main_db_management(); + exit; +} + #::: Create db_update working directory if not created mkdir('db_update'); @@ -118,15 +144,19 @@ if(trim(get_mysql_result("SHOW COLUMNS FROM db_version LIKE 'Revision'")) ne "" print "Old db_version table present, dropping...\n\n"; } -if(get_mysql_result("SHOW TABLES LIKE 'db_version'") eq "" && $db){ - print get_mysql_result(" - CREATE TABLE db_version ( - version int(11) DEFAULT '0' - ) ENGINE=InnoDB DEFAULT CHARSET=latin1; - INSERT INTO db_version (version) VALUES ('1000');"); - print "Table 'db_version' does not exists.... Creating...\n\n"; +sub check_db_version_table{ + if(get_mysql_result("SHOW TABLES LIKE 'db_version'") eq "" && $db){ + print get_mysql_result(" + CREATE TABLE db_version ( + version int(11) DEFAULT '0' + ) ENGINE=InnoDB DEFAULT CHARSET=latin1; + INSERT INTO db_version (version) VALUES ('1000');"); + print "Table 'db_version' does not exists.... Creating...\n\n"; + } } +check_db_version_table(); + if($OS eq "Windows"){ @db_version = split(': ', `world db_version`); } if($OS eq "Linux"){ @db_version = split(': ', `./world db_version`); } @@ -184,13 +214,13 @@ sub show_menu_prompt { 1 => \&database_dump, 2 => \&database_dump_compress, 3 => \&main_db_management, - 4 => \&aa_fetch, + 4 => \&bots_db_management, 5 => \&opcodes_fetch, 6 => \&map_files_fetch, 7 => \&plugins_fetch, 8 => \&quest_files_fetch, 9 => \&lua_modules_fetch, - 10 => \&bots_db_management, + 10 => \&aa_fetch, 20 => \&do_update_self, 0 => \&script_exit, ); @@ -241,31 +271,33 @@ sub menu_options { $bots_management = "Install bots database pre-requisites (Requires bots server binaries)"; } else{ - $bots_management = "Check for Bot pending REQUIRED database updates..."; + $bots_management = "Check for Bot pending REQUIRED database updates... (Must have bots enabled)"; } } } else{ $option[3] = "Check and stage pending REQUIRED Database updates"; - $bots_management = "Check for Bot REQUIRED database updates..."; + $bots_management = "Check for Bot REQUIRED database updates... (Must have bots enabled)"; } return <new; - $ua->timeout(10); - $ua->env_proxy; - my $response = $ua->get($URL); - - if ($response->is_success){ - open (FILE, '> ' . $Dest_File . ''); - print FILE $response->decoded_content; - close (FILE); - print " o URL: (" . $URL . ")\n"; - print " o Saved: (" . $Dest_File . ") \n"; - } - else { - print "Error, no connection or failed request...\n\n"; + $break = 0; + while($break == 0) { + require LWP::UserAgent; + my $ua = LWP::UserAgent->new; + $ua->timeout(10); + $ua->env_proxy; + my $response = $ua->get($URL); + if ($response->is_success){ + open (FILE, '> ' . $Dest_File . ''); + print FILE $response->decoded_content; + close (FILE); + } + else { + # print "Error, no connection or failed request...\n\n"; + } + if(-e $Dest_File) { + $break = 1; + print " [URL] :: " . $URL . "\n"; + print " [Saved] :: " . $Dest_File . "\n"; + } else { $break = 0; } + usleep(500); } } } - if($OS eq "Linux"){ + if($OS eq "Linux"){ #::: wget -O db_update/db_update_manifest.txt https://raw.githubusercontent.com/EQEmu/Server/master/utils/sql/db_update_manifest.txt $wget = `wget --no-check-certificate --quiet -O $Dest_File $URL`; print " o URL: (" . $URL . ")\n"; @@ -461,6 +510,75 @@ sub copy_file{ copy $l_source_file, $l_dest_file; } +sub fetch_latest_windows_binaries{ + print "\n --- Fetching Latest Windows Binaries... --- \n"; + get_remote_file("https://raw.githubusercontent.com/Akkadius/EQEmuInstall/master/master_windows_build.zip", "updates_staged/master_windows_build.zip", 1); + print "\n --- Fetched Latest Windows Binaries... --- \n"; + print "\n --- Extracting... --- \n"; + unzip('updates_staged/master_windows_build.zip', 'updates_staged/binaries/'); + my @files; + my $start_dir = "updates_staged/binaries"; + find( + sub { push @files, $File::Find::name unless -d; }, + $start_dir + ); + for my $file (@files) { + $dest_file = $file; + $dest_file =~s/updates_staged\/binaries\///g; + print "Installing :: " . $dest_file . "\n"; + copy_file($file, $dest_file); + } + print "\n --- Done... --- \n"; + + rmtree('updates_staged'); +} + +sub fetch_peq_db_full{ + print "Downloading latest PEQ Database... Please wait...\n"; + get_remote_file("http://edit.peqtgc.com/weekly/peq_beta.zip", "updates_staged/peq_beta.zip", 1); + print "Downloaded latest PEQ Database... Extracting...\n"; + unzip('updates_staged/peq_beta.zip', 'updates_staged/peq_db/'); + my $start_dir = "updates_staged\\peq_db"; + find( + sub { push @files, $File::Find::name unless -d; }, + $start_dir + ); + for my $file (@files) { + $dest_file = $file; + $dest_file =~s/updates_staged\\peq_db\///g; + if($file=~/peqbeta|player_tables/i){ + print "MariaDB :: Installing :: " . $dest_file . "\n"; + get_mysql_result_from_file($file); + } + if($file=~/eqtime/i){ + print "Installing eqtime.cfg\n"; + copy_file($file, "eqtime.cfg"); + } + } +} + +sub map_files_fetch_bulk{ + print "\n --- Fetching Latest Maps... (This could take a few minutes...) --- \n"; + get_remote_file("http://github.com/Akkadius/EQEmuMaps/archive/master.zip", "maps/maps.zip", 1); + unzip('maps/maps.zip', 'maps/'); + my @files; + my $start_dir = "maps\\EQEmuMaps-master\\maps"; + find( + sub { push @files, $File::Find::name unless -d; }, + $start_dir + ); + for my $file (@files) { + $dest_file = $file; + $dest_file =~s/maps\\EQEmuMaps-master\\maps\///g; + print "Installing :: " . $dest_file . "\n"; + copy_file($file, "maps/" . $new_file); + } + print "\n --- Fetched Latest Maps... --- \n"; + + rmtree('maps/EQEmuMaps-master'); + unlink('maps/maps.zip'); +} + sub map_files_fetch{ print "\n --- Fetching Latest Maps --- \n"; @@ -549,6 +667,8 @@ sub quest_files_fetch{ } } + rmtree('updates_staged'); + if($fc == 0){ print "\nNo Quest Updates found... \n\n"; } diff --git a/zone/aggro.cpp b/zone/aggro.cpp index 06716b3df..574ac335d 100644 --- a/zone/aggro.cpp +++ b/zone/aggro.cpp @@ -960,6 +960,9 @@ bool Mob::CheckLosFN(float posX, float posY, float posZ, float mobSize) { //offensive spell aggro int32 Mob::CheckAggroAmount(uint16 spell_id, Mob *target, bool isproc) { + if (NoDetrimentalSpellAggro(spell_id)) + return 0; + int32 AggroAmount = 0; int32 nonModifiedAggro = 0; uint16 slevel = GetLevel(); @@ -1240,7 +1243,7 @@ bool Mob::PassCharismaCheck(Mob* caster, uint16 spell_id) { if(IsCharmSpell(spell_id)) { - if (spells[spell_id].powerful_flag == -1) //If charm spell has this set(-1), it can not break till end of duration. + if (spells[spell_id].no_resist) //If charm spell has this set(-1), it can not break till end of duration. return true; //1: The mob has a default 25% chance of being allowed a resistance check against the charm. diff --git a/zone/attack.cpp b/zone/attack.cpp index 7a62aaff4..2f94716bf 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -2410,7 +2410,7 @@ bool NPC::Death(Mob* killerMob, int32 damage, uint16 spell, SkillUseTypes attack return true; } -void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, bool iYellForHelp /*= true*/, bool bFrenzy /*= false*/, bool iBuffTic /*= false*/) +void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, bool iYellForHelp /*= true*/, bool bFrenzy /*= false*/, bool iBuffTic /*= false*/, uint16 spell_id) { if(!other) return; @@ -2466,6 +2466,9 @@ void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, b if(IsFamiliar() || GetSpecialAbility(IMMUNE_AGGRO)) return; + if (spell_id != SPELL_UNKNOWN && NoDetrimentalSpellAggro(spell_id)) + return; + if (other == myowner) return; @@ -3139,15 +3142,15 @@ void Mob::CommonDamage(Mob* attacker, int32 &damage, const uint16 spell_id, cons if(!RuleB(Combat, EXPFromDmgShield)) { // Damage shield damage shouldn't count towards who gets EXP if(!attacker->CastToClient()->GetFeigned() && !FromDamageShield) - AddToHateList(attacker, 0, damage, true, false, iBuffTic); + AddToHateList(attacker, 0, damage, true, false, iBuffTic, spell_id); } else { if(!attacker->CastToClient()->GetFeigned()) - AddToHateList(attacker, 0, damage, true, false, iBuffTic); + AddToHateList(attacker, 0, damage, true, false, iBuffTic, spell_id); } } else - AddToHateList(attacker, 0, damage, true, false, iBuffTic); + AddToHateList(attacker, 0, damage, true, false, iBuffTic, spell_id); } if(damage > 0) { @@ -3172,7 +3175,7 @@ void Mob::CommonDamage(Mob* attacker, int32 &damage, const uint16 spell_id, cons { if (!pet->IsHeld()) { Log.Out(Logs::Detail, Logs::Aggro, "Sending pet %s into battle due to attack.", pet->GetName()); - pet->AddToHateList(attacker, 1); + pet->AddToHateList(attacker, 1,0, true, false, false, spell_id); pet->SetTarget(attacker); Message_StringID(10, PET_ATTACKING, pet->GetCleanName(), attacker->GetCleanName()); } @@ -4760,8 +4763,15 @@ void Mob::DoMainHandAttackRounds(Mob *target, ExtraAttackOptions *opts, int spec // A "quad" on live really is just a successful dual wield where both double attack // The mobs that could triple lost the ability to when the triple attack skill was added in Attack(target, MainPrimary, false, false, false, opts, special); - if (CanThisClassDoubleAttack() && CheckDoubleAttack()) + if (CanThisClassDoubleAttack() && CheckDoubleAttack()){ Attack(target, MainPrimary, false, false, false, opts, special); + + if ((IsPet() || IsTempPet()) && IsPetOwnerClient()){ + int chance = spellbonuses.PC_Pet_Flurry + itembonuses.PC_Pet_Flurry + aabonuses.PC_Pet_Flurry; + if (chance && zone->random.Roll(chance)) + Flurry(nullptr); + } + } return; } @@ -4811,8 +4821,15 @@ void Mob::DoOffHandAttackRounds(Mob *target, ExtraAttackOptions *opts, int speci GetEquipment(MaterialSecondary) != 0) { if (CheckDualWield()) { Attack(target, MainSecondary, false, false, false, opts, special); - if (CanThisClassDoubleAttack() && GetLevel() > 35 && CheckDoubleAttack()) + if (CanThisClassDoubleAttack() && GetLevel() > 35 && CheckDoubleAttack()){ Attack(target, MainSecondary, false, false, false, opts, special); + + if ((IsPet() || IsTempPet()) && IsPetOwnerClient()){ + int chance = spellbonuses.PC_Pet_Flurry + itembonuses.PC_Pet_Flurry + aabonuses.PC_Pet_Flurry; + if (chance && zone->random.Roll(chance)) + Flurry(nullptr); + } + } } } } diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index 772f875e5..ab7d8bfee 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -1199,13 +1199,14 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; } - // Kayen: Not sure best way to implement this yet. // Physically raises skill cap ie if 55/55 it will raise to 55/60 case SE_RaiseSkillCap: { - if (newbon->RaiseSkillCap[0] < base1) { - newbon->RaiseSkillCap[0] = base1; // value - newbon->RaiseSkillCap[1] = base2; // skill - } + + if (base2 > HIGHEST_SKILL) + break; + + if (newbon->RaiseSkillCap[base2] < base1) + newbon->RaiseSkillCap[base2] = base1; break; } @@ -1423,22 +1424,45 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) if (newbon->PC_Pet_Rampage[1] < base2) newbon->PC_Pet_Rampage[1] = base2; //Damage modifer - take highest break; - } + } + + case SE_PC_Pet_Flurry_Chance: + newbon->PC_Pet_Flurry += base1; //Chance to Flurry + break; + + case SE_ShroudofStealth: + newbon->ShroudofStealth = true; + break; + + case SE_ReduceFallDamage: + newbon->ReduceFallDamage += base1; + break; + + case SE_ReduceTradeskillFail:{ + + if (base2 > HIGHEST_SKILL) + break; + + newbon->ReduceTradeskillFail[base2] += base1; + break; + } + + case SE_TradeSkillMastery: + if (newbon->TradeSkillMastery < base1) + newbon->TradeSkillMastery = base1; + break; + // to do case SE_PetDiscipline: break; case SE_PetDiscipline2: break; - case SE_ReduceTradeskillFail: - break; case SE_PotionBeltSlots: break; case SE_BandolierSlots: break; case SE_ForageSkill: break; - case SE_TradeSkillMastery: - break; case SE_SecondaryForte: break; case SE_FeignedCastOnChance: @@ -1453,13 +1477,9 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) break; case SE_TrapCircumvention: break; - case SE_ShroudofStealth: - break; case SE_FeignedMinion: break; - // handled client side - case SE_ReduceFallDamage: // not handled here case SE_HastenedAASkill: // not handled here but don't want to clutter debug log -- these may need to be verified to ignore @@ -3132,8 +3152,43 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne if (new_bonus->PC_Pet_Rampage[1] < base2) new_bonus->PC_Pet_Rampage[1] = base2; //Damage modifer - take highest break; - } + } + case SE_PC_Pet_Flurry_Chance: + new_bonus->PC_Pet_Flurry += effect_value; //Chance to Flurry + break; + + case SE_ShroudofStealth: + new_bonus->ShroudofStealth = true; + break; + + case SE_ReduceFallDamage: + new_bonus->ReduceFallDamage += effect_value; + break; + + case SE_ReduceTradeskillFail:{ + + if (base2 > HIGHEST_SKILL) + break; + + new_bonus->ReduceTradeskillFail[base2] += effect_value; + break; + } + + case SE_TradeSkillMastery: + if (new_bonus->TradeSkillMastery < effect_value) + new_bonus->TradeSkillMastery = effect_value; + break; + + case SE_RaiseSkillCap: { + if (base2 > HIGHEST_SKILL) + break; + + if (new_bonus->RaiseSkillCap[base2] < effect_value) + new_bonus->RaiseSkillCap[base2] = effect_value; + break; + } + //Special custom cases for loading effects on to NPC from 'npc_spels_effects' table if (IsAISpellEffect) { diff --git a/zone/client.cpp b/zone/client.cpp index ef0c9bf7d..24a916b2a 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -2439,18 +2439,8 @@ uint16 Client::GetMaxSkillAfterSpecializationRules(SkillUseTypes skillid, uint16 } } - // This should possibly be handled by bonuses rather than here. - switch(skillid) - { - case SkillTracking: - { - Result += ((GetAA(aaAdvancedTracking) * 10) + (GetAA(aaTuneofPursuance) * 10)); - break; - } - - default: - break; - } + + Result += spellbonuses.RaiseSkillCap[skillid] + itembonuses.RaiseSkillCap[skillid] + aabonuses.RaiseSkillCap[skillid]; return Result; } diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index f1ec8e8ec..d8ffb6a68 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -3734,7 +3734,7 @@ void Client::Handle_OP_BuffRemoveRequest(const EQApplicationPacket *app) uint16 SpellID = m->GetSpellIDFromSlot(brrs->SlotID); - if (SpellID && IsBeneficialSpell(SpellID)) + if (SpellID && IsBeneficialSpell(SpellID) && !spells[SpellID].no_remove) m->BuffFadeBySlot(brrs->SlotID, true); } @@ -5076,18 +5076,12 @@ void Client::Handle_OP_DisarmTraps(const EQApplicationPacket *app) Message(13, "Ability recovery time not yet met."); return; } - int reuse = DisarmTrapsReuseTime; - switch (GetAA(aaAdvTrapNegotiation)) { - case 1: - reuse -= 1; - break; - case 2: - reuse -= 3; - break; - case 3: - reuse -= 5; - break; - } + + int reuse = DisarmTrapsReuseTime - GetSkillReuseTime(SkillDisarmTraps); + + if (reuse < 1) + reuse = 1; + p_timers.Start(pTimerDisarmTraps, reuse - 1); Trap* trap = entity_list.FindNearbyTrap(this, 60); @@ -5359,17 +5353,9 @@ void Client::Handle_OP_EnvDamage(const EQApplicationPacket *app) if (ed->dmgtype == 252) { - switch (GetAA(aaAcrobatics)) { //Don't know what acrobatics effect is yet but it should be done client side via aa effect.. till then - case 1: - damage = damage * 95 / 100; - break; - case 2: - damage = damage * 90 / 100; - break; - case 3: - damage = damage * 80 / 100; - break; - } + int mod = spellbonuses.ReduceFallDamage + itembonuses.ReduceFallDamage + aabonuses.ReduceFallDamage; + + damage -= damage * mod / 100; } if (damage < 0) @@ -5443,19 +5429,13 @@ void Client::Handle_OP_FeignDeath(const EQApplicationPacket *app) Message(13, "Ability recovery time not yet met."); return; } + int reuse = FeignDeathReuseTime; - switch (GetAA(aaRapidFeign)) - { - case 1: - reuse -= 1; - break; - case 2: - reuse -= 2; - break; - case 3: - reuse -= 5; - break; - } + reuse -= GetSkillReuseTime(SkillFeignDeath); + + if (reuse < 1) + reuse = 1; + p_timers.Start(pTimerFeignDeath, reuse - 1); //BreakInvis(); @@ -7762,7 +7742,11 @@ void Client::Handle_OP_Hide(const EQApplicationPacket *app) Message(13, "Ability recovery time not yet met."); return; } - int reuse = HideReuseTime - GetAA(209); + int reuse = HideReuseTime - GetSkillReuseTime(SkillHide); + + if (reuse < 1) + reuse = 1; + p_timers.Start(pTimerHide, reuse - 1); float hidechance = ((GetSkill(SkillHide) / 250.0f) + .25) * 100; @@ -7776,7 +7760,7 @@ void Client::Handle_OP_Hide(const EQApplicationPacket *app) sa_out->parameter = 1; entity_list.QueueClients(this, outapp, true); safe_delete(outapp); - if (GetAA(aaShroudofStealth)){ + if (spellbonuses.ShroudofStealth || aabonuses.ShroudofStealth || itembonuses.ShroudofStealth){ improved_hidden = true; hidden = true; } @@ -11698,18 +11682,12 @@ void Client::Handle_OP_SenseTraps(const EQApplicationPacket *app) Message(13, "Ability recovery time not yet met."); return; } - int reuse = SenseTrapsReuseTime; - switch (GetAA(aaAdvTrapNegotiation)) { - case 1: - reuse -= 1; - break; - case 2: - reuse -= 3; - break; - case 3: - reuse -= 5; - break; - } + + int reuse = SenseTrapsReuseTime - GetSkillReuseTime(SkillSenseTraps); + + if (reuse < 1) + reuse = 1; + p_timers.Start(pTimerSenseTraps, reuse - 1); Trap* trap = entity_list.FindNearbyTrap(this, 800); diff --git a/zone/common.h b/zone/common.h index af53e1a93..88aa58264 100644 --- a/zone/common.h +++ b/zone/common.h @@ -408,6 +408,7 @@ struct StatBonuses { uint32 SkillProc[MAX_SKILL_PROCS]; // Max number of spells containing skill_procs. uint32 SkillProcSuccess[MAX_SKILL_PROCS]; // Max number of spells containing skill_procs_success. uint32 PC_Pet_Rampage[2]; // 0= % chance to rampage, 1=damage modifier + uint32 PC_Pet_Flurry; // Percent chance flurry from double attack // AAs int8 Packrat; //weight reduction for items, 1 point = 10% @@ -438,7 +439,7 @@ struct StatBonuses { int32 CombatStability; // Melee damage mitigation. int32 DoubleRiposte; // Chance to double riposte int32 GiveDoubleRiposte[3]; // 0=Regular Chance, 1=Skill Attack Chance, 2=Skill - uint32 RaiseSkillCap[2]; // Raise a specific skill cap (1 = value, 2=skill) + uint32 RaiseSkillCap[HIGHEST_SKILL+1]; // Raise a specific skill cap (base1= value, base2=skill) int32 Ambidexterity; // Increase chance to duel wield by adding bonus 'skill'. int32 PetMaxHP; // Increase the max hp of your pet. int32 PetFlurry; // Chance for pet to flurry. @@ -466,6 +467,10 @@ struct StatBonuses { int32 PetMeleeMitigation; // Add AC to owner's pet. bool IllusionPersistence; // Causes illusions not to fade. uint16 extra_xtargets; // extra xtarget entries + bool ShroudofStealth; // rogue improved invisiblity + uint16 ReduceFallDamage; // reduce fall damage by percent + int32 ReduceTradeskillFail[HIGHEST_SKILL+1]; // Reduces chance for trade skills to fail by percent. + uint8 TradeSkillMastery; // Allow number of tradeskills to exceed 200 skill. }; typedef struct diff --git a/zone/effects.cpp b/zone/effects.cpp index 2e43ad8f5..f2706cae1 100644 --- a/zone/effects.cpp +++ b/zone/effects.cpp @@ -112,7 +112,7 @@ int32 Mob::GetActSpellDamage(uint16 spell_id, int32 value, Mob* target) { value -= GetFocusEffect(focusFcDamageAmt, spell_id); value -= GetFocusEffect(focusFcDamageAmt2, spell_id); - if(itembonuses.SpellDmg && spells[spell_id].classes[(GetClass()%16) - 1] >= GetLevel() - 5) + if(!spells[spell_id].no_heal_damage_item_mod && itembonuses.SpellDmg && spells[spell_id].classes[(GetClass()%16) - 1] >= GetLevel() - 5) value -= GetExtraSpellAmt(spell_id, itembonuses.SpellDmg, value)*ratio/100; else if (IsNPC() && CastToNPC()->GetSpellScale()) @@ -145,7 +145,7 @@ int32 Mob::GetActSpellDamage(uint16 spell_id, int32 value, Mob* target) { value -= GetFocusEffect(focusFcDamageAmt, spell_id); value -= GetFocusEffect(focusFcDamageAmt2, spell_id); - if(itembonuses.SpellDmg && spells[spell_id].classes[(GetClass()%16) - 1] >= GetLevel() - 5) + if(!spells[spell_id].no_heal_damage_item_mod && itembonuses.SpellDmg && spells[spell_id].classes[(GetClass()%16) - 1] >= GetLevel() - 5) value -= GetExtraSpellAmt(spell_id, itembonuses.SpellDmg, value); if (IsNPC() && CastToNPC()->GetSpellScale()) @@ -287,7 +287,7 @@ int32 Mob::GetActSpellHealing(uint16 spell_id, int32 value, Mob* target) { value += GetFocusEffect(focusFcHealAmt, spell_id); value += target->GetFocusIncoming(focusFcHealAmtIncoming, SE_FcHealAmtIncoming, this, spell_id); - if(itembonuses.HealAmt && spells[spell_id].classes[(GetClass()%16) - 1] >= GetLevel() - 5) + if(!spells[spell_id].no_heal_damage_item_mod && itembonuses.HealAmt && spells[spell_id].classes[(GetClass()%16) - 1] >= GetLevel() - 5) value += GetExtraSpellAmt(spell_id, itembonuses.HealAmt, value) * modifier; value += value*target->GetHealRate(spell_id, this)/100; @@ -430,9 +430,6 @@ int32 Client::GetActSpellCost(uint16 spell_id, int32 cost) int32 Mob::GetActSpellDuration(uint16 spell_id, int32 duration) { - if (spells[spell_id].not_extendable) - return duration; - int increase = 100; increase += GetFocusEffect(focusSpellDuration, spell_id); int tic_inc = 0; diff --git a/zone/lua_spell.cpp b/zone/lua_spell.cpp index e99b9ba84..18353efaa 100644 --- a/zone/lua_spell.cpp +++ b/zone/lua_spell.cpp @@ -406,7 +406,7 @@ int Lua_Spell::GetSpellGroup() { int Lua_Spell::GetPowerfulFlag() { Lua_Safe_Call_Int(); - return self->powerful_flag; + return self->no_resist; } int Lua_Spell::GetCastRestriction() { @@ -436,7 +436,7 @@ int Lua_Spell::GetAEMaxTargets() { int Lua_Spell::GetMaxTargets() { Lua_Safe_Call_Int(); - return self->maxtargets; + return self->no_heal_damage_item_mod; } bool Lua_Spell::GetPersistDeath() { diff --git a/zone/mob.cpp b/zone/mob.cpp index 24f17a531..fc46aac75 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -5598,18 +5598,18 @@ int32 Mob::GetSpellStat(uint32 spell_id, const char *identifier, uint8 slot) else if (id == "NimbusEffect") {return spells[spell_id].NimbusEffect; } else if (id == "directional_start") {return static_cast(spells[spell_id].directional_start); } else if (id == "directional_end") {return static_cast(spells[spell_id].directional_end); } - else if (id == "not_extendable") {return spells[spell_id].not_extendable; } + else if (id == "not_focusable") {return spells[spell_id].not_focusable; } else if (id == "suspendable") {return spells[spell_id].suspendable; } else if (id == "viral_range") {return spells[spell_id].viral_range; } else if (id == "spellgroup") {return spells[spell_id].spellgroup; } else if (id == "rank") {return spells[spell_id].rank; } - else if (id == "powerful_flag") {return spells[spell_id].powerful_flag; } + else if (id == "no_resist") {return spells[spell_id].no_resist; } else if (id == "CastRestriction") {return spells[spell_id].CastRestriction; } else if (id == "AllowRest") {return spells[spell_id].AllowRest; } else if (id == "InCombat") {return spells[spell_id].InCombat; } else if (id == "OutofCombat") {return spells[spell_id].OutofCombat; } else if (id == "aemaxtargets") {return spells[spell_id].aemaxtargets; } - else if (id == "maxtargets") {return spells[spell_id].maxtargets; } + else if (id == "no_heal_damage_item_mod") {return spells[spell_id].no_heal_damage_item_mod; } else if (id == "persistdeath") {return spells[spell_id].persistdeath; } else if (id == "min_dist") {return static_cast(spells[spell_id].min_dist); } else if (id == "min_dist_mod") {return static_cast(spells[spell_id].min_dist_mod); } diff --git a/zone/mob.h b/zone/mob.h index 651065296..e48eda680 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -489,7 +489,7 @@ public: inline uint32 GetLevelCon(uint8 iOtherLevel) const { return this ? GetLevelCon(GetLevel(), iOtherLevel) : CON_GREEN; } virtual void AddToHateList(Mob* other, uint32 hate = 0, int32 damage = 0, bool iYellForHelp = true, - bool bFrenzy = false, bool iBuffTic = false); + bool bFrenzy = false, bool iBuffTic = false, uint16 spell_id = SPELL_UNKNOWN); bool RemoveFromHateList(Mob* mob); void SetHateAmountOnEnt(Mob* other, int32 hate = 0, int32 damage = 0) { hate_list.SetHateAmountOnEnt(other,hate,damage);} void HalveAggro(Mob *other) { uint32 in_hate = GetHateAmount(other); SetHateAmountOnEnt(other, (in_hate > 1 ? in_hate / 2 : 1)); } diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 754ced55d..8d3b252ad 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -780,7 +780,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove // define spells with fixed duration // charm spells with -1 in field 209 are all of fixed duration, so lets use that instead of spell_ids - if(spells[spell_id].powerful_flag == -1) + if(spells[spell_id].no_resist) bBreak = true; if (!bBreak) @@ -5228,6 +5228,9 @@ int16 Client::GetFocusEffect(focusType type, uint16 spell_id) if (IsBardSong(spell_id) && type != focusFcBaseEffects && type != focusSpellDuration) return 0; + if (spells[spell_id].not_focusable) + return 0; + int16 realTotal = 0; int16 realTotal2 = 0; int16 realTotal3 = 0; @@ -5499,6 +5502,9 @@ int16 Client::GetFocusEffect(focusType type, uint16 spell_id) int16 NPC::GetFocusEffect(focusType type, uint16 spell_id) { + if (spells[spell_id].not_focusable) + return 0; + int16 realTotal = 0; int16 realTotal2 = 0; bool rand_effectiveness = false; diff --git a/zone/spells.cpp b/zone/spells.cpp index 44bd8e3de..999ab684d 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -750,14 +750,6 @@ bool Client::CheckFizzle(uint16 spell_id) // always at least 1% chance to fail or 5% to succeed fizzlechance = fizzlechance < 1 ? 1 : (fizzlechance > 95 ? 95 : fizzlechance); - /* - if(IsBardSong(spell_id)) - { - //This was a channel chance modifier - no evidence for fizzle reduction - fizzlechance -= GetAA(aaInternalMetronome) * 1.5f; - } - */ - float fizzle_roll = zone->random.Real(0, 100); Log.Out(Logs::Detail, Logs::Spells, "Check Fizzle %s spell %d fizzlechance: %0.2f%% diff: %0.2f roll: %0.2f", GetName(), spell_id, fizzlechance, diff, fizzle_roll); @@ -4067,7 +4059,7 @@ bool Mob::IsImmuneToSpell(uint16 spell_id, Mob *caster) if(aggro > 0) { AddToHateList(caster, aggro); } else { - AddToHateList(caster, 1); + AddToHateList(caster, 1,0,true,false,false,spell_id); } return true; } @@ -4094,7 +4086,7 @@ bool Mob::IsImmuneToSpell(uint16 spell_id, Mob *caster) if(aggro > 0) { AddToHateList(caster, aggro); } else { - AddToHateList(caster, 1); + AddToHateList(caster, 1,0,true,false,false,spell_id); } return true; } @@ -4110,7 +4102,7 @@ bool Mob::IsImmuneToSpell(uint16 spell_id, Mob *caster) if(aggro > 0) { AddToHateList(caster, aggro); } else { - AddToHateList(caster, 1); + AddToHateList(caster, 1,0,true,false,false,spell_id); } return true; } else if(IsClient() && caster->IsClient() && (caster->CastToClient()->GetGM() == false)) @@ -4127,7 +4119,7 @@ bool Mob::IsImmuneToSpell(uint16 spell_id, Mob *caster) if (aggro > 0) { AddToHateList(caster, aggro); } else { - AddToHateList(caster, 1); + AddToHateList(caster, 1,0,true,false,false,spell_id); } return true; } @@ -4150,7 +4142,7 @@ bool Mob::IsImmuneToSpell(uint16 spell_id, Mob *caster) if(aggro > 0) { AddToHateList(caster, aggro); } else { - AddToHateList(caster, 1); + AddToHateList(caster, 1,0,true,false,false,spell_id); } return true; } @@ -4190,7 +4182,7 @@ bool Mob::IsImmuneToSpell(uint16 spell_id, Mob *caster) if(aggro > 0) { AddToHateList(caster, aggro); } else { - AddToHateList(caster, 1); + AddToHateList(caster, 1,0,true,false,false,spell_id); } return true; } @@ -4298,7 +4290,7 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use } //Get the resist chance for the target - if(resist_type == RESIST_NONE) + if(resist_type == RESIST_NONE || spells[spell_id].no_resist) { Log.Out(Logs::Detail, Logs::Spells, "Spell was unresistable"); return 100; diff --git a/zone/tradeskills.cpp b/zone/tradeskills.cpp index 6ed516da0..903c7c34c 100644 --- a/zone/tradeskills.cpp +++ b/zone/tradeskills.cpp @@ -941,135 +941,10 @@ bool Client::TradeskillExecute(DBTradeskillRecipe_Struct *spec) { float res = zone->random.Real(0, 99); int aa_chance = 0; - //AA modifiers - //can we do this with nested switches? - if(spec->tradeskill == SkillAlchemy){ - switch(GetAA(aaAlchemyMastery)){ - case 1: - aa_chance = 10; - break; - case 2: - aa_chance = 25; - break; - case 3: - aa_chance = 50; - break; - } - } + aa_chance = spellbonuses.ReduceTradeskillFail[spec->tradeskill] + itembonuses.ReduceTradeskillFail[spec->tradeskill] + aabonuses.ReduceTradeskillFail[spec->tradeskill]; - if(spec->tradeskill == SkillJewelryMaking){ - switch(GetAA(aaJewelCraftMastery)){ - case 1: - aa_chance = 10; - break; - case 2: - aa_chance = 25; - break; - case 3: - aa_chance = 50; - break; - } - } const Item_Struct* item = nullptr; - - if (spec->tradeskill == SkillBlacksmithing) { - switch(GetAA(aaBlacksmithingMastery)) { - case 1: - aa_chance = 10; - break; - case 2: - aa_chance = 25; - break; - case 3: - aa_chance = 50; - break; - } - } - - if (spec->tradeskill == SkillBaking) { - switch(GetAA(aaBakingMastery)) { - case 1: - aa_chance = 10; - break; - case 2: - aa_chance = 25; - break; - case 3: - aa_chance = 50; - break; - } - } - - if (spec->tradeskill == SkillBrewing) { - switch(GetAA(aaBrewingMastery)) { - case 1: - aa_chance = 10; - break; - case 2: - aa_chance = 25; - break; - case 3: - aa_chance = 50; - break; - } - } - - if (spec->tradeskill == SkillFletching) { - switch(GetAA(aaFletchingMastery2)) { - case 1: - aa_chance = 10; - break; - case 2: - aa_chance = 25; - break; - case 3: - aa_chance = 50; - break; - } - } - - if (spec->tradeskill == SkillPottery) { - switch(GetAA(aaPotteryMastery)) { - case 1: - aa_chance = 10; - break; - case 2: - aa_chance = 25; - break; - case 3: - aa_chance = 50; - break; - } - } - - if (spec->tradeskill == SkillTailoring) { - switch(GetAA(aaTailoringMastery)) { - case 1: - aa_chance = 10; - break; - case 2: - aa_chance = 25; - break; - case 3: - aa_chance = 50; - break; - } - } - - if (spec->tradeskill == SkillResearch) { - switch(GetAA(aaArcaneTongues)) { - case 1: - aa_chance = 10; - break; - case 2: - aa_chance = 25; - break; - case 3: - aa_chance = 50; - break; - } - } - + chance = mod_tradeskill_chance(chance, spec); if (((spec->tradeskill==75) || GetGM() || (chance > res)) || zone->random.Roll(aa_chance)) { @@ -1528,8 +1403,9 @@ bool Client::CanIncreaseTradeskill(SkillUseTypes tradeskill) { uint8 Pottery = (GetRawSkill(SkillPottery) > 200) ? 1 : 0; uint8 Tailoring = (GetRawSkill(SkillTailoring) > 200) ? 1 : 0; uint8 SkillTotal = Baking + Smithing + Brewing + Fletching + Jewelry + Pottery + Tailoring; //Tradeskills above 200 - uint32 aaLevel = GetAA(aaNewTanaanCraftingMastery); //New Tanaan AA: Each level allows an additional tradeskill above 200 (first one is free) - + //New Tanaan AA: Each level allows an additional tradeskill above 200 (first one is free) + uint8 aaLevel = spellbonuses.TradeSkillMastery + itembonuses.TradeSkillMastery + aabonuses.TradeSkillMastery; + switch (tradeskill) { case SkillBaking: case SkillBlacksmithing: