mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-12 17:51:28 +00:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
4d70cb20e7
@ -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()
|
||||
|
||||
@ -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 )
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.');
|
||||
1
utils/sql/git/optional/2014_04_05_ProcRules.sql
Normal file
1
utils/sql/git/optional/2014_04_05_ProcRules.sql
Normal 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.');
|
||||
7
utils/sql/git/required/2014_04_04_PhysicalResist.sql
Normal file
7
utils/sql/git/required/2014_04_04_PhysicalResist.sql
Normal 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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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,6 +4113,7 @@ void Mob::TryWeaponProc(const ItemInst *inst, const Item_Struct *weapon, Mob *on
|
||||
}
|
||||
} else {
|
||||
ExecWeaponProc(aug_i, aug->Proc.Effect, on);
|
||||
if (RuleB(Combat, OneProcPerWeapon))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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%
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
85
zone/mob.cpp
85
zone/mob.cpp
@ -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
|
||||
{
|
||||
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(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;
|
||||
|
||||
11
zone/mob.h
11
zone/mob.h
@ -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;
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -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];
|
||||
@ -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());
|
||||
|
||||
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,16 +4211,31 @@ 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();
|
||||
|
||||
//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))
|
||||
{
|
||||
int a = (RuleI(Casting,ResistFalloff)-1) - caster->GetLevel();
|
||||
@ -4223,7 +4259,7 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use
|
||||
temp_level_diff = -9;
|
||||
}
|
||||
|
||||
int level_mod = temp_level_diff * temp_level_diff / 2;
|
||||
level_mod = temp_level_diff * temp_level_diff / 2;
|
||||
if(temp_level_diff < 0)
|
||||
{
|
||||
level_mod = -level_mod;
|
||||
@ -4252,6 +4288,7 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use
|
||||
}
|
||||
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;
|
||||
|
||||
@ -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++]);
|
||||
|
||||
@ -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?
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user