Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Corysia Taware 2014-04-07 10:05:46 -07:00
commit 4d70cb20e7
21 changed files with 375 additions and 76 deletions

View File

@ -1,5 +1,37 @@
EQEMu Changelog (Started on Sept 24, 2003 15:50)
-------------------------------------------------------
== 04/06/2014 ==
Uleat: Changed Mob::CanThisClassDualWield() behavior. This should let non-monk/beastlord dual-wielding classes attack with either fist as long as the other hand is occupied.
Notes:
See this thread for more information and to provide feedback: http://www.eqemulator.org/forums/showthread.php?p=229328#post229328
== 04/05/2014 ==
Akkadius: Fix for the Fix for the Fix: Rule Combat:OneProcPerWeapon was created so that you can revert to the original proc functionality
for custom servers that have balanced their content around having more than 1 aug proc on weapons. By having this rule set to 'false' you revert this functionality.
This rule is set to 'true' by default as the original functionality from Live was intended to be
Akkadius: (Performance Adjustment) Removed AsyncLoadVariables from InterserverTimer.Check() in both zone and world. By watching the MySQL general.log file on mass zone idle activity, you can
see that the query 'SELECT varname, value, unix_timestamp() FROM variables where unix_timestamp(ts) >= timestamp' is called every 10 seconds. This function is loading
variables that are initially loaded on World and Zone bootup. When running a large amount of zone servers, the amount of MySQL chatter that is produced is enormous and
unnecessary. For example, if I ran 400 zone servers, I would see 3,456,000 unnecessary queries from all idle or active zone processes in a 24 hour interval.
Secrets: Added a rule to enable multiple procs from the same weapon's other slots if a proc is deemed to trigger, Defaults to true.
If Combat:OneProcPerWeapon is not enabled, we reset the try for that weapon regardless of if we procced or not.
This is for some servers that may want to have as many procs triggering from weapons as possible in a single round.
Optional SQL: utils/sql/git/optional/2014_04_05_ProcRules.sql
== 04/04/2014 ==
Kayen: Implemented 'Physical Resists' (Resist Type 9) to be consistent with live based on extensive parsing.
SQL will add new field to npc_types 'PhR' and fill in database with values consistent with observations.
Required SQL: utils/sql/git/optional/2014_04_04_PhysicalResists.sql
== 04/03/2014 ==
Kayen: Implemented live like spell projectiles (ie. Mage Bolts).
Optional SQL: utils/sql/git/optional/2014_04_03_SpellProjectileRules.sql
Note: The rules in this SQL are for setting the item id for the graphic used by the projectile on different clients.
== 04/01/2014 ==
demonstar55: Implemented ability for a merchant to open and close shop.
Lua quest functions: e.self:MerchantOpenShop() and e.self:MerchantCloseShop()

View File

@ -304,6 +304,9 @@ RULE_BOOL ( Spells, BuffLevelRestrictions, true) //Buffs will not land on low le
RULE_INT ( Spells, RootBreakCheckChance, 70) //Determines chance for a root break check to occur each buff tick.
RULE_INT ( Spells, FearBreakCheckChance, 70) //Determines chance for a fear break check to occur each buff tick.
RULE_INT ( Spells, SuccorFailChance, 2) //Determines chance for a succor spell not to teleport an invidual player
RULE_INT ( Spells, FRProjectileItem_Titanium, 1113) // Item id for Titanium clients for Fire 'spell projectile'.
RULE_INT ( Spells, FRProjectileItem_SOF, 80684) // Item id for SOF clients for Fire 'spell projectile'.
RULE_INT ( Spells, FRProjectileItem_NPC, 80684) // Item id for NPC Fire 'spell projectile'.
RULE_CATEGORY_END()
RULE_CATEGORY( Combat )
@ -393,6 +396,7 @@ RULE_BOOL ( Combat, UseArcheryBonusRoll, false) //Make the 51+ archery bonus req
RULE_INT ( Combat, ArcheryBonusChance, 50)
RULE_INT ( Combat, BerserkerFrenzyStart, 35)
RULE_INT ( Combat, BerserkerFrenzyEnd, 45)
RULE_BOOL ( Combat, OneProcPerWeapon, true) //If enabled, One proc per weapon per round
RULE_CATEGORY_END()
RULE_CATEGORY( NPC )

View File

@ -258,7 +258,7 @@ typedef enum {
#define SE_Familiar 108 // implemented
#define SE_SummonItemIntoBag 109 // implemented - summons stuff into container
//#define SE_IncreaseArchery 110 // not used
#define SE_ResistAll 111 // implemented
#define SE_ResistAll 111 // implemented - Note: Physical Resists are not modified by this effect.
#define SE_CastingLevel 112 // implemented
#define SE_SummonHorse 113 // implemented
#define SE_ChangeAggro 114 // implemented - Hate modifing buffs(ie horrifying visage)
@ -270,7 +270,7 @@ typedef enum {
#define SE_HealRate 120 // implemented - reduces healing by a %
#define SE_ReverseDS 121 // implemented
//#define SE_ReduceSkill 122 // not used
#define SE_Screech 123 // implemented? Spell Blocker(can only have one buff with this effect at one time)
#define SE_Screech 123 // implemented Spell Blocker(If have buff with value +1 will block any effect with -1)
#define SE_ImprovedDamage 124 // implemented
#define SE_ImprovedHeal 125 // implemented
#define SE_SpellResistReduction 126 // implemented

View File

@ -0,0 +1,3 @@
INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells:FRProjectileItem_Titanium', '1113', 'Item id for Titanium clients for Fire spell projectile.');
INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells:FRProjectileItem_SOF', '80684', 'Item id for Titanium clients for Fire spell projectile.');
INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells:FRProjectileItem_NPC', '80684', 'Item id for Titanium clients for Fire spell projectile.');

View File

@ -0,0 +1 @@
INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Combat:OneProcPerWeapon', 'true', 'If OneProcPerWeapon is not enabled, we reset the proc try for that weapon regardless of if we procced or not.');

View File

@ -0,0 +1,7 @@
ALTER TABLE `npc_types` ADD `PhR` smallint( 5 ) UNSIGNED NOT NULL DEFAULT '0' AFTER `Corrup`;
-- Approximate baseline live npc values based on extensive parsing.
UPDATE npc_types SET PhR = 10 WHERE PhR = 0 AND level <= 50;
UPDATE npc_types SET PhR = (10 + (level - 50)) WHERE PhR = 0 AND (level > 50 AND level <= 60);
UPDATE npc_types SET PhR = (20 + ((level - 60)*4)) WHERE PhR = 0 AND level > 60;

View File

@ -461,7 +461,7 @@ int main(int argc, char** argv) {
if (InterserverTimer.Check()) {
InterserverTimer.Start();
database.ping();
AsyncLoadVariables(dbasync, &database);
// AsyncLoadVariables(dbasync, &database);
ReconnectCounter++;
if (ReconnectCounter >= 12) { // only create thread to reconnect every 10 minutes. previously we were creating a new thread every 10 seconds
ReconnectCounter = 0;

View File

@ -262,6 +262,7 @@
#define DISCIPLINE_REUSE_MSG 5807 //You can use the ability %1 again in %2 hour(s) %3 minute(s) %4 seconds.
#define DISCIPLINE_REUSE_MSG2 5808 //You can use the ability %1 again in %2 minute(s) %3 seconds.
#define FAILED_TAUNT 5811 //You have failed to taunt your target.
#define PHYSICAL_RESIST_FAIL 5817 //Your target avoided your %1 ability.
#define AA_NO_TARGET 5825 //You must first select a target for this ability!
#define FORAGE_MASTERY 6012 //Your forage mastery has enabled you to find something else!
#define GUILD_BANK_CANNOT_DEPOSIT 6097 // Cannot deposit this item. Containers must be empty, and only one of each LORE and no NO TRADE or TEMPORARY items may be deposited.
@ -333,6 +334,7 @@
#define ALREADY_CASTING 12442 //You are already casting a spell!
#define SENSE_CORPSE_NOT_NAME 12446 //You don't sense any corpses of that name.
#define SENSE_CORPSE_NONE 12447 //You don't sense any corpses.
#define SCREECH_BUFF_BLOCK 12448 //Your immunity buff protected you from the spell %1!
#define NOT_HOLDING_ITEM 12452 //You are not holding an item!
#define SENSE_UNDEAD 12471 //You sense undead in this direction.
#define SENSE_ANIMAL 12472 //You sense an animal in this direction.

View File

@ -4085,6 +4085,10 @@ void Mob::TryWeaponProc(const ItemInst *inst, const Item_Struct *weapon, Mob *on
}
}
}
//If OneProcPerWeapon is not enabled, we reset the try for that weapon regardless of if we procced or not.
//This is for some servers that may want to have as many procs triggering from weapons as possible in a single round.
if(!RuleB(Combat, OneProcPerWeapon))
proced = false;
if (!proced && inst) {
for (int r = 0; r < MAX_AUGMENT_SLOTS; r++) {
@ -4109,7 +4113,8 @@ void Mob::TryWeaponProc(const ItemInst *inst, const Item_Struct *weapon, Mob *on
}
} else {
ExecWeaponProc(aug_i, aug->Proc.Effect, on);
break;
if (RuleB(Combat, OneProcPerWeapon))
break;
}
}
}

View File

@ -2588,6 +2588,12 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne
case SE_NegateIfCombat:
newbon->NegateIfCombat = true;
break;
case SE_Screech:
newbon->Screech = effect_value;
break;
}
}
}

View File

@ -570,6 +570,9 @@ bool Client::Process() {
viral_timer_counter = 0;
}
if(projectile_timer.Check())
SpellProjectileEffect();
if(spellbonuses.GravityEffect == 1) {
if(gravity_timer.Check())
DoGravityEffect();

View File

@ -5,6 +5,7 @@
#include "../common/spdat.h"
#define HIGHEST_RESIST 9 //Max resist type value
#define MAX_SPELL_PROJECTILE 10 //Max amount of spell projectiles that can be active by a single mob.
/* solar: macros for IsAttackAllowed, IsBeneficialAllowed */
#define _CLIENT(x) (x && x->IsClient() && !x->CastToClient()->IsBecomeNPC())
@ -345,6 +346,7 @@ struct StatBonuses {
uint16 AbsorbMagicAtt[2]; // 0 = magic rune value 1 = buff slot
uint16 MeleeRune[2]; // 0 = rune value 1 = buff slot
bool NegateIfCombat; // Bool Drop buff if cast or melee
int8 Screech; // -1 = Will be blocked if another Screech is +(1)
// AAs
int8 Packrat; //weight reduction for items, 1 point = 10%

View File

@ -3415,9 +3415,14 @@ void EntityList::ReloadAllClientsTaskState(int TaskID)
bool EntityList::IsMobInZone(Mob *who)
{
auto it = mob_list.find(who->GetID());
if (it != mob_list.end())
return who == it->second;
//We don't use mob_list.find(who) because this code needs to be able to handle dangling pointers for the quest code.
auto it = mob_list.begin();
while(it != mob_list.end()) {
if(it->second == who) {
return true;
}
++it;
}
return false;
}

View File

@ -276,6 +276,14 @@ Mob::Mob(const char* in_name,
casting_spell_inventory_slot = 0;
target = 0;
for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_spell_id[i] = 0; }
for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_target_id[i] = 0; }
for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_increment[i] = 0; }
for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_x[i] = 0; }
for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_y[i] = 0; }
for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_z[i] = 0; }
projectile_timer.Disable();
memset(&itembonuses, 0, sizeof(StatBonuses));
memset(&spellbonuses, 0, sizeof(StatBonuses));
memset(&aabonuses, 0, sizeof(StatBonuses));
@ -2080,27 +2088,35 @@ void Mob::SetAttackTimer() {
}
bool Mob::CanThisClassDualWield(void) const
{
if (!IsClient()) {
bool Mob::CanThisClassDualWield(void) const {
if(!IsClient()) {
return(GetSkill(SkillDualWield) > 0);
} else {
const ItemInst* inst = CastToClient()->GetInv().GetItem(SLOT_PRIMARY);
}
else if(CastToClient()->HasSkill(SkillDualWield)) {
const ItemInst* pinst = CastToClient()->GetInv().GetItem(SLOT_PRIMARY);
const ItemInst* sinst = CastToClient()->GetInv().GetItem(SLOT_SECONDARY);
// 2HS, 2HB, or 2HP
if (inst && inst->IsType(ItemClassCommon)) {
const Item_Struct* item = inst->GetItem();
if ((item->ItemType == ItemType2HBlunt) || (item->ItemType == ItemType2HSlash) || (item->ItemType == ItemType2HPiercing))
if(pinst && pinst->IsWeapon()) {
const Item_Struct* item = pinst->GetItem();
if((item->ItemType == ItemType2HBlunt) || (item->ItemType == ItemType2HSlash) || (item->ItemType == ItemType2HPiercing))
return false;
} else {
//No weapon in hand... using hand-to-hand...
//only monks and beastlords? can dual wield their fists.
if(class_ != MONK && class_ != MONKGM && class_ != BEASTLORD && class_ != BEASTLORDGM) {
return false;
}
}
return (CastToClient()->HasSkill(SkillDualWield)); // No skill = no chance
// OffHand Weapon
if(sinst && !sinst->IsWeapon())
return false;
// Dual-Wielding Empty Fists
if(!pinst && !sinst)
if(class_ != MONK && class_ != MONKGM && class_ != BEASTLORD && class_ != BEASTLORDGM)
return false;
return true;
}
return false;
}
bool Mob::CanThisClassDoubleAttack(void) const
@ -4358,6 +4374,49 @@ bool Mob::TryReflectSpell(uint32 spell_id)
return false;
}
void Mob::SpellProjectileEffect()
{
bool time_disable = false;
for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) {
if (projectile_increment[i] == 0){
continue;
}
Mob* target = entity_list.GetMobID(projectile_target_id[i]);
float dist = 0;
if (target)
dist = target->CalculateDistance(projectile_x[i], projectile_y[i], projectile_z[i]);
int increment_end = 0;
increment_end = (dist / 10) - 1; //This pretty accurately determines end time for speed for 1.5 and timer of 250 ms
if (increment_end <= projectile_increment[i]){
if (target && IsValidSpell(projectile_spell_id[i]))
SpellOnTarget(projectile_spell_id[i], target, false, true, spells[projectile_spell_id[i]].ResistDiff, true);
projectile_spell_id[i] = 0;
projectile_target_id[i] = 0;
projectile_x[i] = 0, projectile_y[i] = 0, projectile_z[i] = 0;
projectile_increment[i] = 0;
time_disable = true;
}
else {
projectile_increment[i]++;
time_disable = false;
}
}
if (time_disable)
projectile_timer.Disable();
}
void Mob::DoGravityEffect()
{
Mob *caster = nullptr;

View File

@ -192,6 +192,7 @@ public:
virtual int32 GetActSpellCasttime(uint16 spell_id, int32 casttime);
float ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use_resist_override = false,
int resist_override = 0, bool CharismaCheck = false, bool CharmTick = false, bool IsRoot = false);
int ResistPhysical(int level_diff, uint8 caster_level);
uint16 GetSpecializeSkillValue(uint16 spell_id) const;
void SendSpellBarDisable();
void SendSpellBarEnable(uint16 spellid);
@ -222,6 +223,8 @@ public:
uint16 CastingSpellID() const { return casting_spell_id; }
bool DoCastingChecks();
bool TryDispel(uint8 caster_level, uint8 buff_level, int level_modifier);
void SpellProjectileEffect();
bool TrySpellProjectile(Mob* spell_target, uint16 spell_id);
//Buff
void BuffProcess();
@ -337,6 +340,7 @@ public:
inline virtual int16 GetPR() const { return PR + itembonuses.PR + spellbonuses.PR; }
inline virtual int16 GetCR() const { return CR + itembonuses.CR + spellbonuses.CR; }
inline virtual int16 GetCorrup() const { return Corrup + itembonuses.Corrup + spellbonuses.Corrup; }
inline virtual int16 GetPhR() const { return PhR; }
inline StatBonuses GetItemBonuses() const { return itembonuses; }
inline StatBonuses GetSpellBonuses() const { return spellbonuses; }
inline StatBonuses GetAABonuses() const { return aabonuses; }
@ -915,6 +919,7 @@ protected:
int16 DR;
int16 PR;
int16 Corrup;
int16 PhR;
bool moving;
int targeted;
bool findable;
@ -1040,6 +1045,12 @@ protected:
uint8 bardsong_slot;
uint32 bardsong_target_id;
Timer projectile_timer;
uint32 projectile_spell_id[MAX_SPELL_PROJECTILE];
uint16 projectile_target_id[MAX_SPELL_PROJECTILE];
uint8 projectile_increment[MAX_SPELL_PROJECTILE];
float projectile_x[MAX_SPELL_PROJECTILE], projectile_y[MAX_SPELL_PROJECTILE], projectile_z[MAX_SPELL_PROJECTILE];
float rewind_x;
float rewind_y;
float rewind_z;

View File

@ -447,7 +447,7 @@ int main(int argc, char** argv) {
if (InterserverTimer.Check()) {
InterserverTimer.Start();
database.ping();
AsyncLoadVariables(dbasync, &database);
// AsyncLoadVariables(dbasync, &database);
entity_list.UpdateWho();
if (worldserver.TryReconnect() && (!worldserver.Connected()))
worldserver.AsyncConnect();

View File

@ -158,6 +158,7 @@ NPC::NPC(const NPCType* d, Spawn2* in_respawn, float x, float y, float z, float
FR = d->FR;
PR = d->PR;
Corrup = d->Corrup;
PhR = d->PhR;
STR = d->STR;
STA = d->STA;
@ -659,6 +660,9 @@ bool NPC::Process()
viral_timer_counter = 0;
}
if(projectile_timer.Check())
SpellProjectileEffect();
if(spellbonuses.GravityEffect == 1) {
if(gravity_timer.Check())
DoGravityEffect();
@ -2059,6 +2063,8 @@ void NPC::CalcNPCResists() {
PR = (GetLevel() * 11)/10;
if (!Corrup)
Corrup = 15;
if (!PhR)
PhR = 10;
return;
}

View File

@ -326,7 +326,13 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial)
if(inuse)
break;
Heal();
int32 val = 0;
val = 7500*effect_value;
val = caster->GetActSpellHealing(spell_id, val, this);
if (val > 0)
HealDamage(val, caster);
break;
}
@ -6014,3 +6020,73 @@ bool Mob::PassCastRestriction(bool UseCastRestriction, int16 value, bool IsDama
return false;
}
bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id){
/*For mage 'Bolt' line and other various spells.
-This is mostly accurate for how the modern clients handle this effect.
-It was changed at some point to use an actual projectile as done here (opposed to a particle effect in classic)
-The projectile graphic appears to be that of 'Ball of Sunlight' ID 80648 and will be visible to anyone in SoF+
-There is no LOS check to prevent a bolt from being cast. If you don't have LOS your bolt simply goes into whatever barrier
and you lose your mana. If there is LOS the bolt will lock onto your target and the damage is applied when it hits the target.
-If your target moves the bolt moves with it in any direction or angle (consistent with other projectiles).
-The way this is written once a bolt is cast a timer checks the distance from the initial cast to the target repeatedly
and calculates at what predicted time the bolt should hit that target in client_process (therefore accounting for any target movement).
When bolt hits its predicted point the damage is then done to target.
Note: Projectile speed of 1 takes 3 seconds to go 100 distance units. Calculations are based on this constant.
Live Bolt speed: Projectile speed of X takes 5 seconds to go 300 distance units.
Pending Implementation: What this code can not do is prevent damage if the bolt hits a barrier after passing the initial LOS check
because the target has moved while the bolt is in motion. (it is rare to actual get this to occur on live in normal game play)
*/
if (!spell_target)
return false;
uint8 anim = spells[spell_id].CastingAnim;
int bolt_id = -1;
//Make sure there is an avialable bolt to be cast.
for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) {
if (projectile_spell_id[i] == 0){
bolt_id = i;
break;
}
}
if (bolt_id < 0)
return false;
if (CheckLosFN(spell_target)) {
projectile_spell_id[bolt_id] = spell_id;
projectile_target_id[bolt_id] = spell_target->GetID();
projectile_x[bolt_id] = GetX(), projectile_y[bolt_id] = GetY(), projectile_z[bolt_id] = GetZ();
projectile_increment[bolt_id] = 1;
projectile_timer.Start(250);
}
//Only use fire graphic for fire spells.
if (spells[spell_id].resisttype == RESIST_FIRE) {
if (IsClient()){
if (CastToClient()->GetClientVersionBit() <= 4) //Titanium needs alternate graphic.
ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_Titanium)), false, 1.5);
else
ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_SOF)), false, 1.5);
}
else
ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_NPC)), false, 1.5);
if (spells[spell_id].CastingAnim == 64)
anim = 44; //Corrects for animation error.
}
//Pending other types of projectile graphics. (They will function but with a default arrow graphic for now)
else
ProjectileAnimation(spell_target,0, 1, 1.5);
DoAnim(anim, 0, true, IsClient() ? FilterPCSpells : FilterNPCSpells); //Override the default projectile animation.
return true;
}

View File

@ -1830,7 +1830,7 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, uint16 slot, uint16
}
// check line of sight to target if it's a detrimental spell
if(spell_target && IsDetrimentalSpell(spell_id) && !CheckLosFN(spell_target) && !IsHarmonySpell(spell_id))
if(spell_target && IsDetrimentalSpell(spell_id) && !CheckLosFN(spell_target) && !IsHarmonySpell(spell_id) && spells[spell_id].targettype != ST_TargetOptional)
{
mlog(SPELLS__CASTING, "Spell %d: cannot see target %s", spell_target->GetName());
Message_StringID(13,CANT_SEE_TARGET);
@ -1895,7 +1895,12 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, uint16 slot, uint16
if (isproc) {
SpellOnTarget(spell_id, spell_target, false, true, resist_adjust, true);
} else {
if(!SpellOnTarget(spell_id, spell_target, false, true, resist_adjust, false)) {
if (spells[spell_id].targettype == ST_TargetOptional){
if (!TrySpellProjectile(spell_target, spell_id))
return false;
}
else if(!SpellOnTarget(spell_id, spell_target, false, true, resist_adjust, false)) {
if(IsBuffSpell(spell_id) && IsBeneficialSpell(spell_id)) {
// Prevent mana usage/timers being set for beneficial buffs
if(casting_spell_type == 1)
@ -1904,6 +1909,7 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, uint16 slot, uint16
}
}
}
if(IsPlayerIllusionSpell(spell_id)
&& IsClient()
&& CastToClient()->CheckAAEffect(aaEffectProjectIllusion)){
@ -2607,6 +2613,14 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2,
{
effect1 = sp1.effectid[i];
effect2 = sp2.effectid[i];
if (spellbonuses.Screech == 1) {
if (effect2 == SE_Screech && sp2.base[i] == -1) {
Message_StringID(MT_SpellFailure, SCREECH_BUFF_BLOCK, sp2.name);
return -1;
}
}
if(effect2 == SE_StackingCommand_Overwrite)
{
overwrite_effect = sp2.base[i];
@ -2651,7 +2665,7 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2,
mlog(SPELLS__STACKING, "%s (%d) blocks effect %d on slot %d below %d, but we do not have that effect on that slot. Ignored.",
sp1.name, spellid1, blocked_effect, blocked_slot, blocked_below_value);
}
}
}
}
} else {
mlog(SPELLS__STACKING, "%s (%d) and %s (%d) appear to be in the same line, skipping Stacking Overwrite/Blocking checks",
@ -3440,8 +3454,15 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob* spelltar, bool reflect, bool use_r
if(spell_effectiveness == 0 || !IsPartialCapableSpell(spell_id) )
{
mlog(SPELLS__RESISTS, "Spell %d was completely resisted by %s", spell_id, spelltar->GetName());
Message_StringID(MT_SpellFailure, TARGET_RESISTED, spells[spell_id].name);
spelltar->Message_StringID(MT_SpellFailure, YOU_RESIST, spells[spell_id].name);
if (spells[spell_id].resisttype == RESIST_PHYSICAL){
Message_StringID(MT_SpellFailure, PHYSICAL_RESIST_FAIL,spells[spell_id].name);
spelltar->Message_StringID(MT_SpellFailure, YOU_RESIST, spells[spell_id].name);
}
else {
Message_StringID(MT_SpellFailure, TARGET_RESISTED, spells[spell_id].name);
spelltar->Message_StringID(MT_SpellFailure, YOU_RESIST, spells[spell_id].name);
}
if(spelltar->IsAIControlled()){
int32 aggro = CheckAggroAmount(spell_id);
@ -4190,67 +4211,83 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use
}
break;
case RESIST_PHYSICAL:
{
if (IsNPC())
target_resist = GetPhR();
else
target_resist = 0;
}
default:
//This is guessed but the others are right
target_resist = (GetSTA() / 4);
target_resist = 0;
}
//Setup our base resist chance.
int resist_chance = 0;
int level_mod = 0;
//Adjust our resist chance based on level modifiers
int temp_level_diff = GetLevel() - caster->GetLevel();
if(IsNPC() && GetLevel() >= RuleI(Casting,ResistFalloff))
{
int a = (RuleI(Casting,ResistFalloff)-1) - caster->GetLevel();
if(a > 0)
//Physical Resists are calclated using their own formula derived from extensive parsing.
if (resist_type == RESIST_PHYSICAL) {
level_mod = ResistPhysical(temp_level_diff, caster->GetLevel());
}
else {
if(IsNPC() && GetLevel() >= RuleI(Casting,ResistFalloff))
{
temp_level_diff = a;
}
else
{
temp_level_diff = 0;
}
}
if(IsClient() && GetLevel() >= 21 && temp_level_diff > 15)
{
temp_level_diff = 15;
}
if(IsNPC() && temp_level_diff < -9)
{
temp_level_diff = -9;
}
int level_mod = temp_level_diff * temp_level_diff / 2;
if(temp_level_diff < 0)
{
level_mod = -level_mod;
}
if(IsNPC() && (caster->GetLevel() - GetLevel()) < -20)
{
level_mod = 1000;
}
//Even more level stuff this time dealing with damage spells
if(IsNPC() && IsDamageSpell(spell_id) && GetLevel() >= 17)
{
int level_diff;
if(GetLevel() >= RuleI(Casting,ResistFalloff))
{
level_diff = (RuleI(Casting,ResistFalloff)-1) - caster->GetLevel();
if(level_diff < 0)
int a = (RuleI(Casting,ResistFalloff)-1) - caster->GetLevel();
if(a > 0)
{
level_diff = 0;
temp_level_diff = a;
}
else
{
temp_level_diff = 0;
}
}
else
if(IsClient() && GetLevel() >= 21 && temp_level_diff > 15)
{
level_diff = GetLevel() - caster->GetLevel();
temp_level_diff = 15;
}
if(IsNPC() && temp_level_diff < -9)
{
temp_level_diff = -9;
}
level_mod = temp_level_diff * temp_level_diff / 2;
if(temp_level_diff < 0)
{
level_mod = -level_mod;
}
if(IsNPC() && (caster->GetLevel() - GetLevel()) < -20)
{
level_mod = 1000;
}
//Even more level stuff this time dealing with damage spells
if(IsNPC() && IsDamageSpell(spell_id) && GetLevel() >= 17)
{
int level_diff;
if(GetLevel() >= RuleI(Casting,ResistFalloff))
{
level_diff = (RuleI(Casting,ResistFalloff)-1) - caster->GetLevel();
if(level_diff < 0)
{
level_diff = 0;
}
}
else
{
level_diff = GetLevel() - caster->GetLevel();
}
level_mod += (2 * level_diff);
}
level_mod += (2 * level_diff);
}
if (CharismaCheck)
@ -4397,6 +4434,43 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use
}
}
int Mob::ResistPhysical(int level_diff, uint8 caster_level)
{
/* Physical resists use the standard level mod calculation in
conjunction with a resist fall off formula that greatly prevents you
from landing abilities on mobs that are higher level than you.
After level 12, every 4 levels gained the max level you can hit
your target without a sharp resist penalty is raised by 1.
Extensive parsing confirms this, along with baseline phyiscal resist rates used.
*/
if (level_diff == 0)
return level_diff;
int level_mod = 0;
if (level_diff > 0) {
int ResistFallOff = 0;
if (caster_level <= 12)
ResistFallOff = 3;
else
ResistFallOff = caster_level/4;
if (level_diff > ResistFallOff || level_diff >= 15)
level_mod = ((level_diff * 10) + level_diff)*2;
else
level_mod = level_diff * level_diff / 2;
}
else
level_mod = -(level_diff * level_diff / 2);
return level_mod;
}
int16 Mob::CalcResistChanceBonus()
{
int resistchance = spellbonuses.ResistSpellChance + itembonuses.ResistSpellChance;

View File

@ -1047,6 +1047,7 @@ const NPCType* ZoneDatabase::GetNPCType (uint32 id) {
"npc_types.FR,"
"npc_types.PR,"
"npc_types.Corrup,"
"npc_types.PhR,"
"npc_types.mindmg,"
"npc_types.maxdmg,"
"npc_types.attack_count,"
@ -1143,6 +1144,7 @@ const NPCType* ZoneDatabase::GetNPCType (uint32 id) {
tmpNPCType->FR = atoi(row[r++]);
tmpNPCType->PR = atoi(row[r++]);
tmpNPCType->Corrup = atoi(row[r++]);
tmpNPCType->PhR = atoi(row[r++]);
tmpNPCType->min_dmg = atoi(row[r++]);
tmpNPCType->max_dmg = atoi(row[r++]);
tmpNPCType->attack_count = atoi(row[r++]);

View File

@ -75,6 +75,7 @@ struct NPCType
int16 PR;
int16 DR;
int16 Corrup;
int16 PhR;
uint8 haircolor;
uint8 beardcolor;
uint8 eyecolor1; // the eyecolors always seem to be the same, maybe left and right eye?